From 169323053db5c572d183d59c113ecd0c76e7aca5 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 13:40:43 +0000 Subject: [PATCH 1/2] fix(navigation): wire the forms section anchors, drop the dead presentation set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two clinical routes claimed an information-page section set and drew no navigation at all. `informationPageSectionDefinitions` takes the route before the mode branch, then `AvailableInformationPageNavigation` drops every section whose targetIds are absent from the DOM and returns null when all are dropped — silently, with no error and no failing test. That is /issues #256; specifiers and formulation were fixed in #1647, and these were the two suspected remainders, both now confirmed. Forms — wire the anchors. form-detail-page.tsx rendered zero `id` attributes, so all six declared sections resolved to nothing. Four map 1:1 onto sections that already existed and only needed an id. The two breakpoint-variant pairs use the wrappers already present on the mobile side, and single-child wrappers on the desktop side, so no component signature changes. `-desktop` for source/verification anchors the source card rather than spanning it and the two RailCards below: those are its siblings in the same scrollable rail, so landing there brings them into view without restructuring the aside. Differentials presentations — delete the set. Three of its six sections declared a `-mobile` targetId that no render could ever satisfy: ReviewPanels renders twice (tablet and xl), not three times, so faking them would have meant inventing markup nobody asked for. The page already owns navigation at every width — MobileTabs below xl, and an always-visible "Differential review sidebar" aside at xl showing every panel at once — so `hasLocalInformationPageNavigation` now says so directly. Behaviour-preserving: the route drew nothing before and draws nothing now, minus the dead declaration. Without that entry the route would have fallen through to the differentials mode bar on a submitted search, which is a surface nobody asked for. The browser spec is the point, not a formality. The source-text binding guard added alongside it matches `id="…"` and cannot see whether an anchor sits in a branch that never renders; jsdom applies no Tailwind, so a DOM test cannot tell a `-mobile` target from its `-desktop` twin either. Only a real browser at a real width can, and it earned its place immediately: the first run failed because `/forms/form-1` — the placeholder the unit tests use for pure predicates — is not a real catalogue slug, so nothing rendered while every source assertion passed. Registering the spec needed both allowlists in playwright.config.ts: the global `testMatch` and the per-project `productionSpecPattern`. Adding it to only one leaves a spec that silently never runs, which is the same dead-config shape being fixed here. Verification: lint exit 0; typecheck clean; check:gate-manifest and check:ci-scope pass with the new spec registered; full offline suite 518/519 files, the single failure (tests/pr-handoff-stop.test.ts) re-confirmed pre-existing on this base by a stashed re-run; ui-accessibility 16 passed; ui-forms-section-nav 2 passed against a real record at 390px and 1280px, with exactly one of each variant pair visible per width. The binding guard was mutation-checked — removing a single id turns it red. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01823Ctwj4vinGhGuRNyK7oE --- playwright.config.ts | 4 +- src/components/forms/form-detail-page.tsx | 35 ++++++--- src/components/page-secondary-navigation.tsx | 58 ++------------- tests/page-secondary-navigation.dom.test.tsx | 52 ++++++++++---- tests/ui-forms-section-nav.spec.ts | 76 ++++++++++++++++++++ 5 files changed, 149 insertions(+), 76 deletions(-) create mode 100644 tests/ui-forms-section-nav.spec.ts diff --git a/playwright.config.ts b/playwright.config.ts index 4ef1f7aa1e..b6b921bffd 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -23,7 +23,7 @@ const chromiumExecutablePath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH; // `tests/playwright-project-isolation.test.ts` asserts every such file on disk is // matched here. const productionSpecPattern = - /.*(?:answer-progress-ui-smoke|ui-(smoke|stress|accessibility|tools|overlap|universal-search|specifiers|formulation|chrome-scroll|therapy-nav-scroll|mode-nav-density|phone-scroll(?:-[a-z0-9-]+)?|pwa|route-coverage|style-contract|visual-artifacts|hydration))\.spec\.ts/; + /.*(?:answer-progress-ui-smoke|ui-(smoke|stress|accessibility|tools|overlap|universal-search|specifiers|formulation|forms-section-nav|chrome-scroll|therapy-nav-scroll|mode-nav-density|phone-scroll(?:-[a-z0-9-]+)?|pwa|route-coverage|style-contract|visual-artifacts|hydration))\.spec\.ts/; const mockupSpecPattern = /.*ui-(document-top-navigation-mockup|therapy-navigation-mockup|tools|tools-collapse|tools-task-directory)\.spec\.ts/; const mockupTag = /@mockup/; @@ -31,7 +31,7 @@ const mockupTag = /@mockup/; export default defineConfig({ testDir: "./tests", testMatch: - /.*(?:answer-progress-ui-smoke|ui-(smoke|stress|accessibility|document-top-navigation-mockup|therapy-navigation-mockup|tools|tools-collapse|tools-task-directory|overlap|universal-search|specifiers|formulation|chrome-scroll|therapy-nav-scroll|mode-nav-density|phone-scroll(?:-[a-z0-9-]+)?|pwa|route-coverage|style-contract|visual-artifacts|hydration))\.spec\.ts/, + /.*(?:answer-progress-ui-smoke|ui-(smoke|stress|accessibility|document-top-navigation-mockup|therapy-navigation-mockup|tools|tools-collapse|tools-task-directory|overlap|universal-search|specifiers|formulation|forms-section-nav|chrome-scroll|therapy-nav-scroll|mode-nav-density|phone-scroll(?:-[a-z0-9-]+)?|pwa|route-coverage|style-contract|visual-artifacts|hydration))\.spec\.ts/, timeout: 60_000, retries: 0, // Fail the run if a stray `test.only` is committed: otherwise it silently diff --git a/src/components/forms/form-detail-page.tsx b/src/components/forms/form-detail-page.tsx index ccedb5deb0..444c14a072 100644 --- a/src/components/forms/form-detail-page.tsx +++ b/src/components/forms/form-detail-page.tsx @@ -696,7 +696,10 @@ export function FormDetailPage({ form }: { form: FormRecord }) {
-
+
@@ -817,7 +820,7 @@ export function FormDetailPage({ form }: { form: FormRecord }) { />
-
+

Priority facts

@@ -828,7 +831,10 @@ export function FormDetailPage({ form }: { form: FormRecord }) {
-
+ -
+
{detailRows.map((row) => { const label = row.label.toLowerCase(); const Icon = label.includes("only") @@ -868,7 +874,12 @@ export function FormDetailPage({ form }: { form: FormRecord }) { })}
-
+ {/* The `-mobile`/`-desktop` id pairs below are the section anchors + `formSections` declares. Only one of each pair is ever visible: + `AvailableInformationPageNavigation` resolves a section to its + first VISIBLE target, and `lg:hidden` / `hidden lg:block` make + exactly one side `display:none` per breakpoint. */} +
-
+