Skip to content

feat(navigation): standardise multi-page mode menus in the header - #1645

Closed
BigSimmo wants to merge 18 commits into
mainfrom
codex/standardise-multi-page-mode-nav
Closed

feat(navigation): standardise multi-page mode menus in the header#1645
BigSimmo wants to merge 18 commits into
mainfrom
codex/standardise-multi-page-mode-nav

Conversation

@BigSimmo

@BigSimmoBigSimmo commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Intent

Complete the rollout started by #1642. Use the shared header-integrated ModeNav for every remaining multi-page mode while keeping single-destination modes free of redundant secondary menus.

Planned owners:

  • Differentials
  • DSM-5 Diagnosis
  • Specifiers
  • Formulation

Preserve Therapy's merged implementation, existing detail-page/local navigation ownership, route semantics, query-state handling, and the old SecondaryNavigation only for genuine “On this page” section navigation.

Current state

Test-first draft. The branch currently contains only failing contracts that define the intended behaviour. Production implementation follows after CI confirms the expected red signal.

Safety

UI/navigation only. No provider, retrieval, clinical-content, database, schema, auth, or deployment changes.

Summary by CodeRabbit

  • New Features
    • Introduced consistent shared navigation across Formulation, Specifiers, Differentials, and DSM workflows.
    • Navigation links now preserve relevant search parameters when moving between pages.
  • Improvements
    • Simplified secondary navigation across the clinical dashboard.
    • Redundant navigation is now hidden for single-destination sections.
    • Home routes and supported workflow pages now display navigation consistently.
  • Bug Fixes
    • Prevented unnecessary navigation from appearing on Documents search results pages.

@supabase

supabaseBot commented Aug 6, 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

coderabbitaiBot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Mode navigation now uses the shared RegistryModeNav component. Route checks suppress redundant navigation for single-destination modes and recognize multi-page mode routes. Formulation, specifier, shell, and secondary-navigation tests were updated.

Changes

Registry mode navigation

Layer / File(s)Summary
Registry navigation adapter
src/components/mode-nav/registry-mode-nav.tsx
Builds mode navigation from registry entries, preserves search parameters, assigns icons, and renders ModeNav.
Mode route eligibility
src/lib/mode-secondary-navigation.ts
Suppresses navigation for one-destination modes and updates valid routes for differentials, DSM, specifiers, formulation, and Therapy Compass.
Navigation integration and validation
src/components/formulation/formulation-ui.tsx, src/components/specifiers/specifier-ui.tsx, src/components/page-secondary-navigation.tsx, src/components/clinical-dashboard/global-search-shell.tsx, src/components/mode-nav/mode-nav-portal.tsx, tests/*
Delegates local navigation to RegistryModeNav, removes obsolete search props, updates ownership documentation, and validates route-specific rendering and suppression.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested labels:codex

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check nameStatusExplanationResolution
Description check⚠️ WarningThe description explains intent and safety but omits the required Summary, Verification, Risk and rollout, Clinical Governance Preflight, and Notes sections.Add the required template sections and record verification results or explicit reasons why required checks were not run.
Docstring Coverage⚠️ WarningDocstring coverage is 25.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the primary change: standardizing multi-page mode menus in the shared header.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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

@github-actions

github-actionsBot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

CI triage

CI failed on this PR. Automated classification of the 2 failed job(s):

  • Static PR checksneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.
  • PR requiredneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.

Compared with main CI run #8412 (cancelled).

Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger.

@BigSimmo
BigSimmo marked this pull request as ready for review August 6, 2026 13:01
@BigSimmo
BigSimmo requested a lite review from CopilotAugust 6, 2026 13:04

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:4dddce2a2c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +123 to +124
["specifiers", "/specifiers/builder", "Specifiers pages", "Build", "/specifiers/builder"],
["formulation", "/formulation/map", "Formulation pages", "Map", "/formulation/map"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Test Specifier/Formulation through their real owners

These direct PageSecondaryNavigation renders can pass while both workflows remain broken in the application: src/components/clinical-dashboard/global-search-shell.tsx:1004 still refuses to mount this component for specifiers and formulation, while their workflow pages still render local navigation (for example src/components/specifiers/specifier-builder-page.tsx:88 and src/components/formulation/formulation-builder-page.tsx:286). Consequently, a follow-up can satisfy this table yet show no shared header nav—or, after only removing the shell exclusion, stack it with the old subnav. Add an integration/static contract covering the shell and real workflow-page owners so the intended single shared navigation is enforced.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex fix

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Testing

  • npm run workflow:design-sweep -- --write-evidence
  • npm run ensure
  • curl -fsS http://127.0.0.1:3131/api/local-project-id
  • node --input-type=module <<'NODE' … — verified /specifiers/builder rendered exactly one mode-nav, no Specifier tools navigation, and captured /tmp/specifiers-builder-mode-nav.png.
  • npm run format
  • npm run test -- --run tests/page-secondary-navigation.dom.test.tsx tests/mode-secondary-navigation.test.ts tests/mode-home-loading-contract.test.ts3 test files passed; 66 tests passed.
  • npm run typecheck:source
  • git diff --check
  • ⚠️gh auth status — GitHub CLI reported that no GitHub host is authenticated, preventing push/PR creation through the CLI.

View task →

Co-authored-by: BigSimmo <87357024+BigSimmo@users.noreply.github.com>
@BigSimmo
BigSimmo enabled auto-merge (squash) August 6, 2026 13:09

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The updated contracts currently encode/require behavior not yet implemented and include test scaffolding issues (notably the next/link mock needing ref-forwarding) that could make the tests diverge from production behavior.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR introduces test-first contracts for completing the rollout of the shared header-integrated ModeNav across the remaining multi-page modes (Differentials, DSM-5 Diagnosis, Specifiers, Formulation), while ensuring one-destination modes don’t gain redundant secondary menus.

Changes:

  • Updates DOM contracts for PageSecondaryNavigation to assert multi-page workflows render the shared ModeNav (and suppress legacy SecondaryNavigation except for genuine “On this page” section navigation).
  • Tightens isModeSecondaryNavigationRoute expectations so submitted searches do not automatically imply secondary navigation for one-destination modes.
File summaries
FileDescription
tests/page-secondary-navigation.dom.test.tsxAdds/adjusts DOM contracts for when ModeNav vs SecondaryNavigation should appear.
tests/mode-secondary-navigation.test.tsUpdates mode-secondary-navigation contracts around submitted-search behavior and one-destination modes.
Review details

Suppressed comments (1)

tests/mode-secondary-navigation.test.ts:50

  • multiPageModes currently includes therapy-compass, which would make this contract expect a secondary navigation/menu on the Therapy mode home (/therapy-compass) after a submitted search. That conflicts with Therapy’s established behavior (mode home keeps no bar; Therapy owns its nav via ModeNav on its routed pages). Consider excluding therapy-compass from this specific "submitted search on clean landing" expectation to avoid encoding an unintended behavior change.
const multiPageModes = new Set<AppModeId>(["differentials", "dsm", "specifiers", "formulation", "therapy-compass"]);
describe("mode secondary navigation registry", () => {
it("covers all 13 modes with the approved destinations and no Home item", () => {
expect(Object.keys(modeSecondaryNavigationRegistry).sort()).toEqual([...appModeIds].sort());
expect(appModeIds).toHaveLength(13);
for (const modeId of appModeIds) {
  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +4 to +18
import { render, screen, waitFor } from "@testing-library/react";
import type { ReactNode } from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";

vi.mock("next/navigation", () => ({
usePathname: () => "/",
}));

vi.mock("next/link", () => ({
default: ({ children, href, ...rest }: { children: ReactNode; href: string }) => (
<a href={href} {...rest}>
{children}
</a>
),
}));
@BigSimmo

Copy link
Copy Markdown
OwnerAuthor

@copilot Fix the code for all comments in this review thread.

When a review comment includes a suggested change, apply the suggestion exactly.

Do not make changes beyond what is described in the linked review thread.

CopilotAI commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@copilot Fix the code for all comments in this review thread.

When a review comment includes a suggested change, apply t...

Addressed in 43a8318.

devin-ai-integration[bot]

This comment was marked as resolved.

@BigSimmoChatGPT Codex Connector

Copy link
Copy Markdown
OwnerAuthor

@codex please apply the exact repository Prettier output to the four files currently failing npm run format:changed, and make no behavioural changes:

  • src/components/mode-nav/registry-mode-nav.tsx
  • src/components/page-secondary-navigation.tsx
  • tests/mode-secondary-navigation.test.ts
  • tests/page-secondary-navigation.dom.test.tsx

After formatting, run the focused format check and preserve all current navigation behaviour and tests.

@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

Testing

  • npx prettier --write src/components/mode-nav/registry-mode-nav.tsx src/components/page-secondary-navigation.tsx tests/mode-secondary-navigation.test.ts tests/page-secondary-navigation.dom.test.tsx
  • git diff --check
  • npm run format:changedAll matched files use Prettier code style!
  • npm run format — repository-wide Prettier pass completed without producing additional changes.
  • git status --short --branch — clean working tree after commit.

View task →

@BigSimmo
BigSimmo disabled auto-merge August 6, 2026 13:43
Prettier-format the navigation contracts and drop the onSearch/stickyTop
props that the header-integrated mode nav never honours.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/formulation/formulation-ui.tsx`:
- Line 20: Update the Formulation navigation flow around FormulationSubnav and
its callers to accept and pass the current encoded route query string into
RegistryModeNav instead of relying on the empty default, preserving mechanism,
a, b, and template parameters when switching modes. Add a navigation test
covering a URL with selected mechanisms and a template.
In `@src/components/specifiers/specifier-ui.tsx`:
- Line 24: Pass the current encoded route query string through SpecifierSubnav
and all of its callers into RegistryModeNav instead of relying on the empty
default, preserving builder, compare, and map selection parameters when
navigating workflow pages. Add a navigation test covering these three selection
parameters.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2f3b09e8-6586-421b-8b0c-6ae108be8615

📥 Commits

Reviewing files that changed from the base of the PR and between 6e04add and c8c3c88.

📒 Files selected for processing (9)
  • src/components/clinical-dashboard/global-search-shell.tsx
  • src/components/formulation/formulation-ui.tsx
  • src/components/mode-nav/mode-nav-portal.tsx
  • src/components/mode-nav/registry-mode-nav.tsx
  • src/components/page-secondary-navigation.tsx
  • src/components/specifiers/specifier-ui.tsx
  • src/lib/mode-secondary-navigation.ts
  • tests/mode-secondary-navigation.test.ts
  • tests/page-secondary-navigation.dom.test.tsx

))}
</nav>
);
return <RegistryModeNav modeId="formulation" activeId={active} />;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Pass the current query string to RegistryModeNav.

This call uses the adapter default of "". A Formulation workflow URL with mechanism, a, b, or template loses that state when a user selects another mode-navigation item.

Thread the encoded route query string through FormulationSubnav and its callers. Add a navigation test that starts with selected mechanisms and a template.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/formulation/formulation-ui.tsx` at line 20, Update the
Formulation navigation flow around FormulationSubnav and its callers to accept
and pass the current encoded route query string into RegistryModeNav instead of
relying on the empty default, preserving mechanism, a, b, and template
parameters when switching modes. Add a navigation test covering a URL with
selected mechanisms and a template.

))}
</nav>
);
return <RegistryModeNav modeId="specifiers" activeId={active} />;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Pass the current query string to RegistryModeNav.

This call uses the adapter default of "". A Specifiers workflow URL with selected specifiers loses those selections when the user changes workflow pages.

Thread the encoded route query string through SpecifierSubnav and its callers. Add a navigation test with builder, compare, and map selection parameters.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/specifiers/specifier-ui.tsx` at line 24, Pass the current
encoded route query string through SpecifierSubnav and all of its callers into
RegistryModeNav instead of relying on the empty default, preserving builder,
compare, and map selection parameters when navigating workflow pages. Add a
navigation test covering these three selection parameters.

@devin-ai-integrationdevin-ai-integrationBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines 23 to 25
export function SpecifierSubnav({ active }: { active: "search" | "builder" | "compare" | "map" }) {
const items = [
{ id: "search" as const, label: "Find", shortLabel: "Find", href: "/specifiers" },
{ id: "builder" as const, label: "Build wording", shortLabel: "Build", href: "/specifiers/builder" },
{ id: "compare" as const, label: "Compare", shortLabel: "Compare", href: "/specifiers/compare" },
{ id: "map" as const, label: "Map", shortLabel: "Map", href: "/specifiers/map" },
];

return (
<nav
aria-label="Specifier tools"
className="polished-scroll flex max-w-full gap-1 overflow-x-auto rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-raised)] p-1 shadow-[var(--shadow-inset)]"
>
{items.map((item) => (
<Link
key={item.id}
href={item.href}
aria-label={item.label}
aria-current={active === item.id ? "page" : undefined}
className={cn(
"inline-flex min-h-tap shrink-0 items-center justify-center rounded-md px-3 text-xs font-bold transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] sm:text-sm",
active === item.id
? "bg-[color:var(--clinical-accent)] text-[color:var(--clinical-accent-contrast)] shadow-[var(--shadow-tight)]"
: "text-[color:var(--text-muted)] hover:bg-[color:var(--surface)] hover:text-[color:var(--text)]",
)}
>
<span className="sm:hidden" aria-hidden>
{item.shortLabel}
</span>
<span className="hidden sm:inline" aria-hidden>
{item.label}
</span>
</Link>
))}
</nav>
);
return <RegistryModeNav modeId="specifiers" activeId={active} />;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Selections are dropped when moving between Specifiers or Formulation workflow pages

The Specifiers and Formulation page menus are built without the page's current query string (<RegistryModeNav modeId="specifiers" activeId={active} /> at src/components/specifiers/specifier-ui.tsx:24 and the matching call at src/components/formulation/formulation-ui.tsx:20), so every menu link points at a bare page and the work already in progress is not carried across.
Impact: A clinician who has picked specifiers or mechanisms and then taps Build, Compare or Map from the header menu arrives at an empty page and has to re-pick everything.

Why the state-carrying helper never runs for these two modes

RegistryModeNav defaults searchParamString to "" (src/components/mode-nav/registry-mode-nav.tsx:25) and feeds that into modeSecondaryNavigationHref via new URLSearchParams("") (src/components/mode-nav/registry-mode-nav.tsx:31-43). That helper exists precisely to translate specifier=/a=/b=/selected= and mechanism=/template= into each destination URL (src/lib/mode-secondary-navigation.ts:168-247), but with empty params every branch degrades to the bare registry href.

The shared shell path does pass the bridged query string (src/components/page-secondary-navigation.tsx:279), yet GlobalSearchShell deliberately skips PageSecondaryNavigation for searchMode === "specifiers" | "formulation" (src/components/clinical-dashboard/global-search-shell.tsx:1005), so SpecifierSubnav/FormulationSubnav are the only owners of the bar on those routes and the translation is dead code there. Passing the current search params (e.g. via useSearchParams().toString() in a client wrapper, or threading the page's params down) would restore the intended behaviour.

Prompt for agents
SpecifierSubnav (src/components/specifiers/specifier-ui.tsx) and FormulationSubnav (src/components/formulation/formulation-ui.tsx) now delegate to RegistryModeNav but never supply searchParamString, which defaults to an empty string in src/components/mode-nav/registry-mode-nav.tsx. As a result modeSecondaryNavigationHref (src/lib/mode-secondary-navigation.ts) receives empty URLSearchParams and cannot carry the specifier/mechanism/template/a/b selection state between Find, Build, Compare and Map. Since GlobalSearchShell skips PageSecondaryNavigation for the specifiers and formulation modes, these two Subnavs are the only owners of the mode bar on those routes, so the state-carrying logic never runs at all for them. Consider giving these Subnavs access to the current query string (for example a small client wrapper reading useSearchParams, or threading the page's searchParams through the page components that render them) and passing it to RegistryModeNav, keeping the search-chrome invariant about not introducing a nested Suspense boundary under the standalone shell body in mind.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for freeto 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.

3 participants

@BigSimmo