Skip to content

feat(navigation): adopt the shared mode nav for Factsheets - #1674

Merged
BigSimmo merged 3 commits into
mainfrom
claude/handover-review-nlhuln
Aug 7, 2026
Merged

feat(navigation): adopt the shared mode nav for Factsheets#1674
BigSimmo merged 3 commits into
mainfrom
claude/handover-review-nlhuln

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • Factsheets adopts the shared header ModeNav. It is the one of the eight remaining modes with a genuine second destination: /factsheets is the browse home (category chips + featured grid) and /factsheets/search is a separate component with filters, a card/list toggle and result rows. It previously registered a single action-only entry, which rendered one <button> inside a <nav> landmark whose only effect was focusing a composer already visible on the same screen. That is replaced by two routed entries — Topics and Search — plus factsheets in MODE_NAV_ADOPTED_MODES. Two items fit the 22rem band with no overflow slot, the same shape as DSM.
  • Topics deliberately carries no focus: true, unlike the Search/Find entry of every other adopted mode. Those tabs are their mode's search affordance, so focusing the composer on arrival is the point. Topics is a browse destination — focus=1 reaches inputRef.current?.focus() and would open the phone keyboard over the topics the user just asked to see. This mode's search affordance is the Search tab.
  • The Search tab carries q, category and run. It is the current tab on /factsheets/search, so its own link must not reset the filter being read. run matters specifically: hasSubmittedModeSearch = requestedRun && requestedQuery.length > 0 drives useCompactBottomSearch, so dropping it re-places the composer for a click that changed nothing.
  • factsheets gets its own activeModeSecondaryNavigationId branch. The registry[modeId][0]?.id fallback would otherwise mark Topics current on every unmatched path, including /factsheets/search.
  • Icon is BookOpenText, the Factsheets hero glyph — not LayoutGrid, which the search page already uses for its card/list view toggle. One glyph must not mean two things on one screen.
  • Adopting expires the incidental protection on /factsheets/[slug]. It was bar-free because the mode had fewer than MODE_NAV_MIN_ITEMS destinations. What keeps it clear now is only the hasLocalInformationPageNavigation early return, so that is pinned at render rather than left to the count.

No new routes, so no new-route checklist work: all three factsheets routes are already in docs/site-map.md and the home already links to /factsheets/search.

Three separate places pin the adopted-mode list and all three move together — tests/mode-secondary-navigation.test.ts, tests/mode-nav-addon-slot.dom.test.tsx, and the MODES table in tests/ui-mode-nav-density.spec.ts (cross-checked by mode-nav-contract). This is the guard that makes the PR #1645 silent-deletion failure impossible; it did not exist when that PR was written.

RAG impact: no retrieval behaviour change — navigation and layout only.

Verification

  • npm run verify:pr-local

Verification not run: verify:pr-local aborts in this container at check:installed-lock-parity — installed @playwright/test is 1.62.0 against a locked 1.62.1. Deliberately not resolved with npm ci: 1.62.1 expects Chromium build 1234 while only build 1194 is present and browser downloads are disabled, which would stop the density spec running at all. The gate's components were run directly instead:

  • npm run lint — clean.

  • npm run typecheck — clean. This is the gate that catches a missing iconByItemId entry; the map is exhaustive by a type derived from the registry literal, with no fallback.

  • npm run testTest Files 1 failed | 518 passed (519), Tests 1 failed | 5491 passed | 4 skipped (5496). The single failure is tests/pr-handoff-stop.test.ts:180, which exercises a shell hook untouched by this diff; confirmed pre-existing by re-running it with these changes stashed (1 failed | 10 passed on the clean tree).

  • Focused set — tests/mode-secondary-navigation.test.ts, mode-nav-contract, mode-nav-addon-slot.dom, page-secondary-navigation.dom, secondary-navigation.dom: Test Files 5 passed (5), Tests 95 passed (95).

  • npm run format — run and committed.

  • npm run verify:ui when UI, routing, styling, browser behavior, reduced-motion, or forced-colors behavior changed

UI verification run directly (same lock-parity reason): node scripts/run-playwright.mjs tests/ui-mode-nav-density.spec.ts --project=chromium55 passed (1.1m), including 7 new factsheets rows. Both labels render in full at 352/353/527/528/529/672px and the bar collapses to the single control at 351px, so nothing is clipped and nothing overflows.

Both new cross-file pins were mutation-checked rather than assumed:

  • Deleting the factsheets row from the density MODES table → Tests 2 failed | 22 passed in mode-nav-contract.test.ts.
  • Dropping the run carry from the href case → Tests 1 failed | 30 passed in mode-secondary-navigation.test.ts.

Risk and rollout

  • Risk: Low, and bounded to one mode. The change replaces a no-op button with two real destinations; ModeNav itself is unmodified. The one behaviour worth naming: the bar renders on /factsheets/search, and on /factsheets only via the hasSubmittedSearch early return — the clean browse home keeps showing its ModeHomeTemplate tiles instead, per the existing "a mode home renders no bar" rule. Since a factsheets search from the composer routes to /factsheets/search, Topics will rarely be the marked-current tab in practice; it functions as the return path to browse. This is precedented (DSM's Search tab points at /dsm?focus=1, also outside its route predicate) but is called out because a two-tab bar where one tab is usually the current one is new for this registry.
  • Rollback: Single revert. Nothing else depends on the topics id, and removing it narrows RoutedModeSecondaryNavigationId back automatically.
  • Provider or production effects: None.

Clinical Governance Preflight

Not applicable, and verified rather than assumed against scripts/pr-policy.mjs: src/lib/mode-secondary-navigation.ts does not match the clinicalRiskPatterns src/lib/… clause (it requires one of auth/permission/privacy/security/rag/retriev/rank/search/answer/clinical/citation/source/document/upload/download in the path), and the mode-nav/page-secondary-navigation component paths do not match the presentation clause, which is explicitly written so that living under a clinically-named directory is not by itself clinical-risk. Nothing matches ragRankingPatterns. The diff touches navigation wiring only — no ingestion, answer generation, ranking, source rendering, document access, privacy or production config.

Notes

This is the first of two PRs from the mode-navigation handover. The second removes the vestigial one-button strip from the seven modes that stay single-surface (answer, documents, services, forms, favourites, prescribing, tools) and must land after this one — reversing the order would strand /factsheets/search with no navigation at all.

Tools was assessed and deliberately not adopted: its five ToolCatalogArea values drive filter chips, labels and icon tones inside the launcher, with no area routes, and most catalogue records link out of /tools entirely. The launcher's own visible filter row already is that navigation.


Generated by Claude Code

Factsheets is the one of the eight remaining modes with a genuine second
destination: `/factsheets` is the browse home (category chips + featured
grid) and `/factsheets/search` is a separate component with filters, a
view toggle and result rows. It registered a single action-only entry,
which rendered one <button> whose only effect was focusing a composer
already visible on the same screen.

Replace that with two routed entries — Topics and Search — and add the
mode to MODE_NAV_ADOPTED_MODES. Two items fit the 22rem band with no
overflow slot, the same shape as DSM.

Details worth knowing:

- Topics carries no `focus: true`, unlike the Search/Find entry of every
  other adopted mode. Those tabs are their mode's search affordance;
  Topics is a browse destination, and autofocusing would open the phone
  keyboard over the topics the user asked to see.
- The Search tab carries q, category and run. It is the current tab on
  /factsheets/search, and dropping `run` flips hasSubmittedModeSearch,
  which re-places the composer for a click that changed nothing.
- factsheets gets its own activeModeSecondaryNavigationId branch. The
  array-index-0 fallback would otherwise mark Topics current on every
  unmatched path.
- The icon is BookOpenText, the Factsheets hero glyph, not LayoutGrid —
  the search page already uses LayoutGrid for its card/list view toggle.

Adopting expires the incidental protection that kept the bar off
/factsheets/[slug] (it had fewer than MODE_NAV_MIN_ITEMS destinations).
What keeps that route clear now is only the hasLocalInformationPageNavigation
early return, so it is pinned at render rather than left to the count.

Verification: typecheck and lint clean; 5 focused unit files (95 tests)
pass; full offline suite 518/519 files, with the single failure
(tests/pr-handoff-stop.test.ts) confirmed pre-existing by re-running it
with these changes stashed. ui-mode-nav-density: 55 passed, including 7
new factsheets rows showing both labels in full at every band >=352px
and collapsing at 351px. Both new cross-file pins were mutation-checked:
removing the density table row fails 2 tests in mode-nav-contract, and
dropping the `run` carry fails the href test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01823Ctwj4vinGhGuRNyK7oE
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@supabase

supabase Bot commented Aug 7, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 50 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dfb2a852-6080-4475-b1ca-27f12d524b37

📥 Commits

Reviewing files that changed from the base of the PR and between dfcbe20 and 14e98e6.

📒 Files selected for processing (7)
  • docs/branch-review-ledger.md
  • src/components/mode-nav/registry-mode-nav.tsx
  • src/lib/mode-secondary-navigation.ts
  • tests/mode-nav-addon-slot.dom.test.tsx
  • tests/mode-secondary-navigation.test.ts
  • tests/page-secondary-navigation.dom.test.tsx
  • tests/ui-mode-nav-density.spec.ts

Comment @coderabbitai help to get the list of available commands.

@BigSimmo
BigSimmo enabled auto-merge (squash) August 7, 2026 10:45
@BigSimmo
BigSimmo merged commit 5e9a9e3 into main Aug 7, 2026
25 checks passed
@BigSimmo
BigSimmo deleted the claude/handover-review-nlhuln branch August 7, 2026 10:51
BigSimmo added a commit that referenced this pull request Aug 7, 2026
* refactor(navigation): remove the vestigial one-button mode strip

Seven modes registered a single action-only entry — `answer` an "Ask"
button, and documents/services/forms/favourites/prescribing/tools a
"Search" one. Each rendered one <button> inside its own <nav> landmark
whose only effect was focusing a composer already visible on the same
screen. For a screen-reader user navigating by landmark that is a
navigation region announced for a no-op.

Every one of these modes is genuinely single-surface — records, or one
page — so there was nothing to adopt onto the shared header bar and
nothing to replace the button with. It is deleted, not ported. Factsheets,
the one of the eight with a real second destination, took the bar in
#1674, which is why that had to land first.

The one argument for keeping the strip does not hold: the shell renders
it with sticky={false} and never passes placeInShell, so it sits in
normal flow and scrolls off-screen at exactly the moment a phone composer
scroll-hides. It was never the fast way back to anything.

Details worth knowing:

- The `registry[modeId][0]?.id` fallback in activeModeSecondaryNavigationId
  is deleted, and the compiler forces it: an emptied `as const` entry is
  `readonly []`, so `[0]` is a hard TS2493. Anyone emptying the arrays
  without this gets a typecheck failure they might "fix" with a cast,
  quietly restoring the wrong-slot-current bug that function's own doc
  comment warns about.
- The `documents` clause in isModeSecondaryNavigationRoute goes with its
  entry. The `hasSubmittedSearch` early return STAYS — it is the only
  thing that puts the bar on a submitted-search mode home for all five
  adopted modes.
- PageSecondaryNavigation loses modeItems, modeAriaLabel, onSearch and
  stickyTop (which already had zero callers). The empty-registry return
  sits BELOW the informationDefinitions branch on purpose: services,
  forms, documents and prescribing register nothing yet still claim
  section navs, and hoisting it would strip navigation from every
  /services/*, /forms/*, /medications/* and /documents/<id> record. That
  ordering is mutation-checked.
- The `action` kind on SecondaryNavigation is kept, with a comment saying
  it now has no live consumer. It carries the tablist roving-focus
  behaviour and is covered directly by its own component tests, and
  check:knip runs without --include exports so it would not be caught by
  the dead-code gate. Deleting it is a clean separate change.

Noted while verifying, not fixed here: /forms/form-1 draws no section nav
at all. form-detail-page.tsx carries "form-decision-context-mobile" as a
testId rather than an element id, and the other five declared targetIds
are rendered nowhere, so AvailableInformationPageNavigation filters them
all out and returns null. That is /issues #256, pre-existing and
unaffected by this change — the section branch won on that route before
and still does. Recorded in the new test's comment.

Verification: lint and typecheck clean; focused set 5 files / 97 tests;
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-mode-nav-density + ui-accessibility: 71 passed — the
latter carries the duplicated-landmark scan, which is where seven deleted
<nav>s would have surfaced. The branch-order guard was mutation-checked:
hoisting it above the section branch fails 2 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01823Ctwj4vinGhGuRNyK7oE

* docs(ledger): record the vestigial mode-strip removal review (PR #1679)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01823Ctwj4vinGhGuRNyK7oE

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants