diff --git a/scripts/velocity/field-velocity-panel.test.mjs b/scripts/velocity/field-velocity-panel.test.mjs
index 7909f921..62fc8732 100644
--- a/scripts/velocity/field-velocity-panel.test.mjs
+++ b/scripts/velocity/field-velocity-panel.test.mjs
@@ -45,14 +45,19 @@ function elements(node) {
}
for (const { key, label } of FOCUS_AREAS) {
- test(`${key} staged panel retains charts and selected-area methodology/toolkit links without public route wiring`, async (t) => {
+ test(`${key} overview includes the shared panel, original strategy and selected-area methodology/toolkit links`, async (t) => {
assert.ok(existsSync('src/components/AreaFieldVelocity.tsx'), 'missing reusable area panel')
t.mock.method(globalThis, 'fetch', async () => new Response(null, { status: 503 }))
t.mock.method(console, 'warn', () => {})
const Panel = source('components/AreaFieldVelocity.tsx').default
- // The component is retained for a later launch, but no public page mounts it.
- // Public-route absence is covered separately by preview-only.test.mjs.
- const panelTree = await Panel({ area: key })
+ const Page = source(key === 'economies-governance' ? 'app/areas/economies-governance/page.tsx' : 'app/areas/[slug]/page.tsx').default
+ const tree = await Page({ params: Promise.resolve({ slug: key }) })
+ const children = elements(tree)
+ const panel = children.find(el => el.type === Panel)
+ assert.ok(panel, `${key} page does not mount shared panel`)
+ assert.equal(panel.props.area, key)
+ assert.ok(children.some(el => el.props.id === 'opportunity-spaces'), 'existing strategy preserved')
+ const panelTree = await Panel(panel.props)
if (key === 'economies-governance') {
assert.deepEqual(elements(panelTree).find(el => el.type === Dashboard).props.liveOutputs, {}, 'area panel must retain the overview live-output channel even when providers are unavailable')
}
@@ -64,7 +69,7 @@ for (const { key, label } of FOCUS_AREAS) {
assert.match(html, /id="field-velocity"/)
assert.ok(html.includes(`${label} field velocity`))
-
+ assert.ok(tree.props.className.includes('area-overview'))
assert.ok(panelTree.props.className.includes('area-field-velocity'))
assert.ok(panelTree.props.className.includes(key === 'neurotech' ? 'bg-gray-100' : 'bg-gray-200'))
assert.doesNotMatch(panelTree.props.className, /border-y|px-4|bg-gray-50/)
diff --git a/scripts/velocity/preview-only.test.mjs b/scripts/velocity/preview-only.test.mjs
index 731591cc..3048ec47 100644
--- a/scripts/velocity/preview-only.test.mjs
+++ b/scripts/velocity/preview-only.test.mjs
@@ -4,7 +4,6 @@ import React from 'react'
import { readFileSync } from 'node:fs'
import { source } from './test-source-loader.mjs'
-const Panel = source('components/AreaFieldVelocity.tsx').default
const Dashboard = source('components/ImpactDashboardV2.tsx').default
const Methodology = source('components/MeasuringQuestionsV2.tsx').default
const { FOCUS_AREAS, FIELD_VELOCITY_OVERVIEW } = source('lib/field-velocity.ts')
@@ -19,21 +18,8 @@ function offlineProviders(t) {
t.mock.method(console, 'warn', () => {})
}
-for (const { key } of FOCUS_AREAS) {
- test(`${key} public detail keeps existing content but does not mount field velocity or preview links`, async (t) => {
- offlineProviders(t)
- const route = key === 'economies-governance' ? 'app/areas/economies-governance/page.tsx' : 'app/areas/[slug]/page.tsx'
- const Page = source(route).default
- const nodes = elements(await Page({ params: Promise.resolve({ slug: key }) }))
- assert.ok(!nodes.some(node => node.type === Panel || node.type === Dashboard || node.type === Methodology), 'unreleased field velocity must not mount on public pages')
- assert.ok(!nodes.some(node => node.props.id === 'field-velocity'))
- assert.ok(!nodes.some(node => /impact-preview|#fv\/|#field-velocity|#methodology|#toolkit/.test(node.props.href ?? '')), 'no discovery links to the unlisted preview')
- assert.ok(nodes.some(node => node.props.id === 'opportunity-spaces'), 'original strategy remains')
- const existingLink = key === 'economies-governance' ? '/areas/economies-governance/projects/' : '/insights/'
- assert.ok(nodes.some(node => node.props.href === existingLink), 'original Explore/Insights content remains')
- assert.doesNotMatch(readFileSync(`src/${route}`, 'utf8'), /from ['"]@\/(?:components\/(?:AreaFieldVelocity|ImpactDashboardV2|MeasuringQuestionsV2)|lib\/field-velocity[^'"]*)['"]/, 'public routes must not load field-velocity modules')
- })
-}
+// Public detail mounting is covered by field-velocity-panel.test.mjs.
+// The cross-field overview itself keeps its existing unlisted route.
test('the exact unlisted overview retains all fields, charts, methodology and noindex metadata', async (t) => {
offlineProviders(t)
diff --git a/src/app/areas/[slug]/page.tsx b/src/app/areas/[slug]/page.tsx
index a581a5f7..b119b966 100644
--- a/src/app/areas/[slug]/page.tsx
+++ b/src/app/areas/[slug]/page.tsx
@@ -1,4 +1,6 @@
import type { Metadata } from 'next'
+import AreaFieldVelocity from '@/components/AreaFieldVelocity'
+import { isFocusAreaKey } from '@/lib/field-velocity-data'
import EditPageButton from '@/components/EditPageButton'
import { PageEditHistoryByline } from '@/components/EditHistoryByline'
@@ -287,7 +289,7 @@ export default async function AreaPage({ params }: Props) {
)}
- {/* Field velocity is preview-only until a separate public launch. */}
+ {isFocusAreaKey(slug) &&