Skip to content

feat(therapy): ship Therapy in production with its review state disclosed - #2150

Merged
BigSimmo merged 12 commits into
mainfrom
claude/therapy-modes-visibility-bb37d2
Aug 18, 2026
Merged

feat(therapy): ship Therapy in production with its review state disclosed#2150
BigSimmo merged 12 commits into
mainfrom
claude/therapy-modes-visibility-bb37d2

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

Summary

  • Therapy now appears in the modes in production, instead of being hidden behind three separate gates. Only the first of the three was visible: devOnly: true in src/lib/app-modes.ts removed Therapy from every mode list; the /therapy-compass route layout returned a not-found response in production; and therapyRecordsForEnvironment filtered out every record whose reviewStatus was not reviewed — all 205 of them — so each detail, brief and patient-sheet route, plus every universal-search therapy result, 404'd for real users while working normally in local development. Removing only the first gate would have shipped a Therapy mode onto an empty library, so all three are removed together.
  • The review state is now disclosed rather than used to hide the library. The gates existed because the therapy catalogue awaits qualified-clinician sign-off. That remains true and is now stated to the reader: a new TherapyReviewNotice sits above the Therapy home hero, non-interactive and not dismissible, reading "Awaiting clinician review. No therapy record in this library has completed clinician review yet. Records are shown so they can be read and checked against their cited sources — verify each one before using it clinically."
  • The notice cannot drift away from the data.scripts/build-therapies-index.mjs now emits needsReviewCount into the generated catalogue summary and compares it in its existing check mode, so the wording changes to "N of 205 therapy records have not completed clinician review yet" on its own as records are signed off, and the notice disappears entirely once none remain.
  • Every per-record review badge is retained. Result cards, detail pages, briefs, patient sheets, comparisons, pathways, and the universal-search "Needs source review" badge all continue to render from reviewStatus. therapyNeedsReview survives as the label source only; it no longer gates reachability anywhere.
  • The offline bypass is retired with the gate it served.PLAYWRIGHT_OFFLINE_MODE handling in the Therapy route layout and in src/lib/therapies.ts existed only to let offline UI verification reach the gated route. A bypass left behind an absent gate is how a half-restored gate passes locally and 404s in production, so it goes with the gate.
  • The contracts that pinned the old behaviour now pin the new one, so neither half can be silently undone: reachability in tests/app-modes.test.ts, the catalogue notice and the per-record badges in tests/therapy-review-regressions.test.ts, and the retired bypass in tests/therapy-pr-unblocking-contract.test.ts.

Verification

  • npm run verify:pr-local
  • npm run format over the whole tree, with the result committed.
  • Focused contracts, re-run after formatting: tests/app-modes.test.ts, tests/therapy-review-regressions.test.ts, tests/therapy-ranking.test.ts, tests/therapy-pr-unblocking-contract.test.ts, tests/therapy-compass-mode-wiring.test.ts, tests/therapy-source-governance.test.ts, tests/route-reachability.test.ts — 7 files passed, 77 tests passed.
  • Full offline unit suite: 669 files passed, 7255 tests passed. Three failures appeared in that parallel run, in tests/codex-cloud-setup.test.ts and tests/http-readiness.test.ts — both unrelated to this diff (a Codex Cloud environment contract and a local HTTP socket-timing check). Both files were then re-run in isolation twice: they pass at the pre-change HEAD and they pass with this change applied, so those three were load-related timing flakes rather than damage from this work.
  • npx tsc --noEmit clean, and ESLint clean across all twelve changed files.
  • npm run design-system:adoption:update — manifest unchanged (54 components, 67 roots).
  • Checked against the running app: /therapy-compass, /therapy-compass/cognitive-behavioural-therapy-cbt, /therapy-compass/search and /therapy-compass/review all return 200; the new notice renders above the hero with the correct warning design tokens and no horizontal overflow; and the record page resolves its title server-side through findTherapyRecord, which is the exact call the old production filter used to fail.

UI verification not run: npm run verify:ui was not run. Another worktree held the repository coordinator's exclusive heavy lock for a long Chromium run throughout this session, and preempting another session's Playwright run to take that lock is precisely what the coordinator exists to prevent. The affected journey was proven directly against the running app as described above, and CI's Production UI job covers the shared Chromium gate for this PR.

Note on the visual baseline: tests/__screenshots__/linux/therapy-compass-home.png will show legitimate pixel drift, because the new review notice genuinely changes that page. Per tests/__screenshots__/README.md that comparison is advisory — it produces a workflow warning rather than a failed check, it runs post-land, weekly or manually rather than on pull requests, and only linux/ baselines gate anything, so it cannot be refreshed from this Windows workstation. The baseline should be refreshed from the visual-baseline-<run_id> CI artifact after this lands.

Risk and rollout

  • Risk: 205 therapy records that have not completed qualified-clinician review become reachable to users in production, where they were previously absent entirely. This is a deliberate, owner-approved change of posture — from hiding unreviewed clinical content to disclosing its review state — and it is the substantive risk in this PR. It is mitigated by disclosure at two levels, both now covered by contract tests: a catalogue-wide notice that cannot be dismissed, and a per-record badge on every surface that renders a record, including discovery outside the mode. No record is presented as reviewed, validated, or clinically endorsed. The application's standing framing as a clinical reference prototype rather than validated clinical decision support is unchanged.
  • Rollback: revert this single commit. That restores devOnly: true, the route-layout not-found gate, and the production review filter together, returning Therapy to hidden in production. No data migration or other cleanup is required.
  • Provider or production effects: None. No Supabase, OpenAI, or other provider-backed command was run for this change, and no provider configuration, credential, schema, or environment value is touched.

Clinical Governance Preflight

This section is included deliberately. classifyPullRequestFiles reports clinicalRisk: false for this file set, because the clinical-risk patterns match the therapy data files and public catalogue assets rather than the modules changed here, so policy does not require this section. The change nevertheless governs whether clinical content reaches users at all, which is squarely the judgement this checklist exists to record.

  • Source-backed claims still require linked source verification before clinical use
    • Stated more explicitly than before, both in the new catalogue notice ("checked against their cited sources — verify each one before using it clinically") and in the strengthened Therapy home footer, which now reads "check each record's review status and cited sources before clinical use".
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
    • No patient data path, upload path, or identifiable-content workflow is touched by this change.
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
    • Unchanged. No Supabase configuration, migration, or environment value is touched.
  • Service-role keys and private document access remain server-only
    • Unchanged. No key handling, authorization, or owner-scoping code is touched. The therapy catalogue is public static content and was already served from /therapy-compass-data in production.
  • Demo/synthetic content remains clearly separated from real clinical sources
    • Unchanged. This change does not alter demo-mode detection or the synthetic corpus, and the therapy catalogue is real source-grounded content in both modes.
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
    • Review status is preserved end to end and is now surfaced more prominently than before. What changes is that an unreviewed record is disclosed as unreviewed rather than removed; nothing is upgraded, defaulted, or inferred to "reviewed", and therapyNeedsReview still returns true for any record whose status is not exactly reviewed.
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed
    • Checked. No decision-support logic, ranking, scoring, or recommendation behaviour changes here; the diff changes reachability and disclosure only. Therapy records remain reference material that the reader must verify against cited sources, and the product's existing classification framing is unchanged by this PR.

RAG impact: no retrieval behaviour change — the diff touches no protected RAG surface. classifyPullRequestFiles reports ragRanking: false. src/lib/therapies.ts changes only which records are handed to the existing local catalogue scorer; rankTherapyCandidates itself and the ordering it produces are untouched, as are src/lib/rag/**, clinical-search, retrieval-selection, answer-ranking, ranking-config, the eval harness and the golden fixture. tests/therapy-ranking.test.ts continues to assert that catalogue ordering and universal-discovery ordering agree for the same queries.

Notes

  • The three gates were added together while the catalogue awaited sign-off, and they are removed together for the same reason. The detail worth keeping for a future reader is that they were independent: only the mode-list flag was visible, and removing it alone would have produced a mode that opens onto an empty library with every record 404ing.
  • docs/codebase-index.md records the decision, the three gates it replaced, and where each half of the disclosure is pinned.

🤖 Generated with Claude Code

…osed
Therapy was hidden from users by three independent production gates, only the
first of which was visible:
- `devOnly: true` in `app-modes.ts` removed it from every mode list.
- The `/therapy-compass` route layout returned `notFound()` in production.
- `therapyRecordsForEnvironment` filtered out every record whose `reviewStatus`
was not `reviewed` — all 205 of them — so each detail/brief/sheet route and
every universal-search therapy hit 404'd for real users while working locally.
Removing only the first would have shipped a Therapy mode onto an empty library,
so all three go together.
The gates existed because the catalogue awaits qualified-clinician sign-off. The
owner's decision is to disclose that state rather than hide the library:
- `TherapyReviewNotice` sits above the Therapy home hero — non-interactive and
not dismissible, with counts read from the generated catalogue summary so the
wording tracks the data as records are signed off.
- The generator now emits `needsReviewCount` and its check mode compares it, so
the notice cannot drift away from the records it describes.
- Every existing per-record `reviewStatus` badge is retained: result cards,
detail pages, briefs, patient sheets, comparisons, pathways, and the
universal-search "Needs source review" badge.
`therapyNeedsReview` survives as the label source only; it no longer gates
reachability anywhere. The `PLAYWRIGHT_OFFLINE_MODE` bypass is retired with the
gate it existed to reach, so no half-restored gate can pass locally and 404 in
production.
The contracts that pinned the old behaviour now pin the new one: reachability in
`app-modes.test.ts`, the notice and per-record badges in
`therapy-review-regressions.test.ts`, and the retired bypass in
`therapy-pr-unblocking-contract.test.ts`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@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

supabaseBot commented Aug 18, 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 18, 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 current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in:59 minutes

Limit details: You’ve used the included review currently available. Your 102 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

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 within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8b4b68d8-e601-4551-bc62-3d944e57cedd

📥 Commits

Reviewing files that changed from the base of the PR and between 6801932 and d8619ab.

📒 Files selected for processing (13)
  • docs/branch-review-records/730c1139a3494d1af3269ee1091efae428933fc910dc3da951590bdc62190344.record.md
  • docs/codebase-index.md
  • scripts/build-therapies-index.mjs
  • src/app/(search-app)/therapy-compass/layout.tsx
  • src/components/therapy-compass/data/generated-assets.ts
  • src/components/therapy-compass/screens/home-screen.tsx
  • src/components/therapy-compass/therapy-review-notice.tsx
  • src/lib/app-modes.ts
  • src/lib/therapies.ts
  • tests/app-modes.test.ts
  • tests/therapy-pr-unblocking-contract.test.ts
  • tests/therapy-ranking.test.ts
  • tests/therapy-review-regressions.test.ts

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

@BigSimmo
BigSimmo enabled auto-merge (squash) August 18, 2026 18:51
BigSimmoand others added 2 commits August 19, 2026 03:31
@BigSimmo
BigSimmo disabled auto-merge August 18, 2026 20:01
@BigSimmo
BigSimmo enabled auto-merge (squash) August 18, 2026 20:18
@github-actions

Copy link
Copy Markdown
Contributor

CI triage

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

  • Lighthouse budgetneeds 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 #12332 (cancelled).

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

@BigSimmo
BigSimmo merged commit adf93a7 into mainAug 18, 2026
26 checks passed
@BigSimmo
BigSimmo deleted the claude/therapy-modes-visibility-bb37d2 branch August 18, 2026 20:59
BigSimmo pushed a commit that referenced this pull request Aug 18, 2026
…emains
Measured on main at adf93a7, after #2122 (component convergence) and
#2150 (production exposure) both landed.
- Closes `#FEWQZ5`. Its three stages — Button call sites, card-recipes
adoption, page headers — all shipped in #2122, so the row described
finished work. It was queued before that PR landed and the reconcile
applied it afterwards, which is how a completed task ends up reading
as open.
- Queues the private UI kit as a P2. `therapy-compass/ui.tsx` exports
eight components whose shared equivalents Therapy imports zero times:
Chip, StatusMark, ErrorState, Progress, the eyebrowText primitive and
CategoryIconTile. This is the duplication `card-recipes.ts` was written
to end, one layer down, and #2122 did not reach it. It matters most for
`StatusBadge`, which renders "Needs source review" — since #2150 that
is the per-record half of the only thing between an unreviewed record
and a clinical decision, and it is module-private.
- Queues `therapyBtn` as a P3: 30 raw call sites across 8 files, kept
deliberately in #2122 because list rows and disclosure headers are not
Buttons, but with no shared recipe covering them either.
Both new records carry migration hazards rather than just a target:
`StatusBadge` must keep its `TriangleAlert` shape channel or the warning
becomes colour-only, and `Meter`'s colour-only fix from 8c791a1 must
not be regressed by a naive swap to shared `Progress`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BigSimmo pushed a commit that referenced this pull request Aug 18, 2026
Owner decisions, both reversing an earlier call in this branch.
Documents: the bare path renders the same `ModeHomeTemplate` the shared home
uses, with the identical subtitle — the only extras were three action shortcuts
(browse the library / continue reading / open a source PDF) and an indexed-source
count. That is a duplicate home, not a workspace, and the owner accepted losing
those extras rather than carry a second home. Nothing is preserved under
/mockups for this one; there was no detailed page to keep.
Therapy: consolidating it was blocked while the mode was `devOnly`, because the
shared home hides devOnly modes in production — measured against a production
build, `/?mode=therapy-compass` came back as mode Answer, which would have
removed Therapy from production entirely. PR #2150 shipped Therapy in production
with its review state disclosed, lifting that gate. Only the home screen retires
(preserved at /mockups/therapy-compass-home-detailed); search, compare,
recommend, pathways and every record route are untouched.
Eleven modes are now consolidated. Three keep a home of their own, and none of
them is a duplicate of the shared home — each is its mode's only functional
surface: /tools (launcher), /favourites (hub), /medications (prescribing
workspace).
Two dead branches went with the change rather than lingering as false ownership:
`dashboardOwnedModeHomePaths` existed only to say "and also `/documents`" beside
`pathname === "/"`, and the differentials compare addon still named the bare
`/differentials`, which can no longer be true.
npm run test: 681 files, 7380 passed | 4 skipped (7384).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
BigSimmo added a commit that referenced this pull request Aug 19, 2026
* feat(home): consolidate DSM, Dictionary and Factsheets onto the shared home
First group of the lightweight-home migration. Every mode is moving to one
shared home at `/?mode=<id>` whose per-mode copy lives in
`sharedHomePresentation`; the detailed per-mode home pages are retired from the
live routes rather than deleted.
Per mode:
- `/(search-app)/<mode>/page.tsx` forwards to the shared home. The path stays so
bookmarks, the sitemap and external deep links keep resolving.
- The detailed page is preserved, off the live routes, at
`/mockups/<mode>-home-detailed` — design scratch, 404 in production.
- The bare path leaves `standaloneModeHomePaths`: it renders nothing now, and
claiming composer ownership would reserve hero geometry on a route that never
paints. The namespace stays in `alwaysStandaloneShellPathPrefixes` because its
SUB-routes still need standalone shell treatment.
The redirect is resolved in `src/proxy.ts`, not by the page alone. Next 16
documents that `redirect()` in a streaming context "will insert a meta tag to
emit the redirect on the client side" rather than serving a 307
(node_modules/next/dist/docs/.../redirect.md). Measured here, the page-only
version produced `<meta http-equiv="refresh" content="1;url=/?mode=dsm">` — a
full second of empty shell on a primary navigation path. Resolving it in the
proxy yields a real 307, which is the same reasoning that already put the
document-source fallbacks there (issue #24). The page keeps its own redirect as
a backstop for anything the matcher misses.
The incoming query is carried across, so `/dsm?q=panic&run=1` becomes
`/?mode=dsm&q=panic&run=1` and the shared home resolves it onward to
`/dsm/search`. That cannot loop: the onward hop targets the search surface, not
the bare path. `mode` is always overwritten from the pathname so a crafted
`/dsm?mode=favourites` cannot bounce a visitor into an unrelated mode.
Sidebar Factsheets now points at `/?mode=factsheets`, matching how Answer was
already wired.
Verified: 674 test files / 7288 tests pass; typecheck clean; live 307s confirmed
for all three bare paths with sub-routes (`/dsm/search`, `/dsm/compare`,
`/factsheets/search`, `/dictionary/browse`) still rendering 200; browser check
confirms each lands on the shared home with its own title, subtitle and in-flow
composer.
* feat(home): consolidate the remaining seven mode homes onto the shared home
Completes the switch to one lightweight home for every mode. Services, Forms,
Calculators, Specifiers, Formulation, Differentials and Therapy join DSM,
Dictionary and Factsheets: their bare paths keep resolving for bookmarks and
external links, but now redirect to `/?mode=<id>` instead of rendering a second
home. Ten of the fifteen modes are consolidated; `/documents`, `/medications`,
`/favourites` and `/tools` still own real, distinct surfaces and are untouched.
Each consolidated mode gains a `<mode>/search` route for its submitted searches.
That is load-bearing, not tidiness: the bare path redirects, so routing a query
back at it would loop. `consolidatedModeHomeModeIds` now drives both halves from
one list — the redirect map and `appModeHomeHref` — so a mode cannot be added to
one without the other, and a test walks every consolidated mode asserting its
submitted href never lands on a redirecting path.
`appModeHomeHref` also stops routing in-app navigation through those redirects:
with no query it returns `/?mode=<id>` directly. That is what retargets the
sidebar, the mode-nav tabs and the detail-page back links automatically; only
the pinned Services entry needed editing by hand.
Redirects resolve in the proxy rather than the page, because Next 16 turns a
`redirect()` inside a streaming layout into a client-side meta refresh instead
of a 307 (`node_modules/next/dist/docs/.../redirect.md`). Verified live: all ten
bare paths return 307, all ten `/search` routes return 200.
Two copy corrections the consolidation made visible: Therapy's shared-home title
now reads "Therapy", matching the mode's own copy rule and the home it replaced;
and the Factsheets nav comment no longer claims `/factsheets` renders a category
browse, which it had not for some time.
The retired detailed pages are preserved off the live routes at
`/mockups/<mode>-home-detailed`.
npm run verify:cheap: exit 0 — 674 files, 7290 passed | 4 skipped (7294).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* feat(home): consolidate Documents onto the shared home
Documents was the last mode home that rendered nothing of its own. Its page
component was an empty fragment and ClinicalDashboard supplied the body, so the
bare path showed a query-less "Document matches / Loading document results"
shell where `/?mode=documents` shows the mode's actual hero — a duplicate route
that was also the worse of the two.
`/documents` now redirects to `/?mode=documents` like the other consolidated
modes. Its sub-routes are untouched: `/documents/search`, `/documents/[id]` and
`/documents/source/*` are real surfaces and keep rendering themselves.
This retires a shell concept with it. `dashboardOwnedModeHomePaths` existed only
to say "and also `/documents`" wherever `pathname === "/"` gated an unsubmitted
mode home; with Documents redirecting, `/` is the only such path and both the
set and `isDashboardOwnedModeHomePath` are gone. The autoRunSearch gate that
kept keystrokes from firing searches is unchanged in effect — still gated on
run=1 everywhere it was.
Documents stays out of `alwaysStandaloneShellPathPrefixes`, unlike the other ten
consolidated namespaces: `/documents/search` is dashboard-rendered because it
needs retrieval state. The route-ownership test now asserts that asymmetry
rather than looping over every consolidated mode as if they were uniform.
Not consolidated, and deliberately so — these are not duplicate homes, they are
each mode's only functional surface, so retiring them would delete a feature
rather than de-duplicate a page:
- /tools the launcher (categories, filters, saved)
- /favourites the hub (Continue, Recent, sets, sort/view)
- /medications the prescribing workspace (dose/safety/monitoring checks)
npm run verify:cheap: exit 0 — 674 files, 7290 passed | 4 skipped (7294).
Live: /documents 307 -> /?mode=documents; /documents/search 200.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* fix(home): forward a submitted deep link to the mode's own results surface
`/forms?q=transport&run=1` was going to the shared home along with every other
hit on a consolidated bare path. That silently changed where a submitted deep
link renders: the dashboard shows its own in-place results for some modes and
nothing at all for others, so this one stopped reaching FormsSearchResultsPage
entirely. Four phone journeys caught it — the unit suite was green throughout,
because nothing in it exercises the hop end to end.
The redirect now branches on whether the link was actually submitted, which is
the distinction the bare path used to carry itself:
/dsm -> /?mode=dsm (home, composer seeded)
/dsm?q=panic&run=1 -> /dsm/search?q=panic&run=1 (where it rendered before)
A query without run=1 is a draft, not a search, and still lands on the home.
Every other parameter rides along untouched, so queryMode and scope filters
survive the hop; `mode` stays overwritten from the pathname, and the destination
path is the matched key rather than anything the query can name, so neither
branch is steerable by the request.
Verified live on all eleven consolidated modes: bare paths 307 to the shared
home, submitted deep links 307 to `<mode>/search`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* test(ui): drop the redirected /dictionary from the Dictionary route sweep
`/dictionary` has no home of its own since consolidation — it redirects to
`/?mode=dictionary` — so the sweep sat on `dictionary-home-main` for 20s at each
of three viewports waiting for a testid that route no longer renders.
The shared home is covered by the shared-home suites, and the retired detailed
home lives at /mockups/dictionary-home-detailed, which 404s in production and is
out of scope for a production-route sweep.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* fix(home): keep Therapy and Documents out of the consolidation, and align the specs
Two of the eleven modes should never have been folded in. Both were caught by
the Chromium gate, which the unit suite could not have found — one of them
because it only misbehaves in a production build.
Therapy is `devOnly` (app-modes.ts), pending qualified-clinician sign-off on its
catalogue. The shared home hides devOnly modes in production, so consolidating
it removed Therapy from production altogether: measured against a production
build, `/?mode=therapy-compass` rendered mode Answer. Dev hid this because the
gate is environment-dependent. It keeps its own home until that gate lifts.
Documents I justified wrongly. I read `DocumentsHomeClient` returning an empty
fragment as "the route renders nothing" — but the shell mounts ClinicalDashboard
for that pathname, so `/documents` paints a real Documents home: browse, recent
documents and the document-search empty state, exactly as `/medications` paints
the prescribing workspace. A page component says nothing about what its route
renders when the shell owns the body.
Nine modes stay consolidated: services, forms, differentials, dsm, specifiers,
formulation, calculators, factsheets, dictionary.
Also restores a deep-link behaviour the split had dropped: `/services/search`
carries the legacy `?query=` canonicalisation that the bare path used to own,
and the proxy counts that alias as a submitted query — without both,
`/services?q=%20&query=13YARN&run=1` read as unsubmitted and landed a working
old bookmark on the home with nothing to search for.
Spec updates are the rest of the diff, all of the same class: route tables and
URL assertions that named a bare path now name the shared home or the mode's
`/search` route. Two moved rather than changed — the formulation phone-scroll
runway follows its content to `/formulation/search`, and the differentials
recent-work touch-target audit follows the retired home to `/mockups` under
`@mockup`, since the component still ships but the route 404s in production.
npm run test: 674 files, 7292 passed | 4 skipped (7296). Chromium gate next.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* fix(differentials): register /differentials/search for the phone compare addon
The new dedicated /differentials/search route (this PR) wasn't in
differentialsCompareAddonActive's pathname check, so GlobalSearchShell never
created the phone compare-bar dock host there — DifferentialsHome portals
into a slot that doesn't exist, silently dropping the compare action on
phone for submitted searches on the new route.
Addresses a Copilot review finding on PR #2157.
* fix(docs): align the adoption route count with the manifest (75)
The conflict resolution on this branch kept the pre-merge 69, which counted the
ten Ward Flow routes from main but not the six `<mode>/search` routes this branch
splits out of the consolidated bare paths. The manifest itself has 75, so
`Unit coverage` went red on the mismatch rather than on anything about the code.
59 + 6 + 10 = 75, which is what the existing comment already explained.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* feat(home): consolidate Documents and Therapy onto the shared home
Owner decisions, both reversing an earlier call in this branch.
Documents: the bare path renders the same `ModeHomeTemplate` the shared home
uses, with the identical subtitle — the only extras were three action shortcuts
(browse the library / continue reading / open a source PDF) and an indexed-source
count. That is a duplicate home, not a workspace, and the owner accepted losing
those extras rather than carry a second home. Nothing is preserved under
/mockups for this one; there was no detailed page to keep.
Therapy: consolidating it was blocked while the mode was `devOnly`, because the
shared home hides devOnly modes in production — measured against a production
build, `/?mode=therapy-compass` came back as mode Answer, which would have
removed Therapy from production entirely. PR #2150 shipped Therapy in production
with its review state disclosed, lifting that gate. Only the home screen retires
(preserved at /mockups/therapy-compass-home-detailed); search, compare,
recommend, pathways and every record route are untouched.
Eleven modes are now consolidated. Three keep a home of their own, and none of
them is a duplicate of the shared home — each is its mode's only functional
surface: /tools (launcher), /favourites (hub), /medications (prescribing
workspace).
Two dead branches went with the change rather than lingering as false ownership:
`dashboardOwnedModeHomePaths` existed only to say "and also `/documents`" beside
`pathname === "/"`, and the differentials compare addon still named the bare
`/differentials`, which can no longer be true.
npm run test: 681 files, 7380 passed | 4 skipped (7384).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* test(ui): retarget the Documents and Therapy browser journeys
Both modes joined the shared home, so specs that navigated to /documents or
/therapy-compass were waiting on testids those paths no longer render.
- Sidebar href expectations follow the pinned entries onto /?mode=documents.
- The Documents workspace journeys move to /documents/search, which is where
document-search-workspace and document-search-empty-state actually live.
- The Therapy home assertions move to the shared home, whose per-mode title is a
level-2 heading under the page's sr-only h1.
- The Therapy route-coverage interaction went through a 'Common therapy searches'
pill that lived on the retired detailed home; it now opens that pill's own
destination directly, keeping the mode-nav assertions the step exists for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* docs(index): index /calculators/search in the route table
The prose already described the new mode search surfaces, but the route table
still mapped `/calculators` to a single `page.tsx", so `/calculators/search` had
no entry. Every other consolidated mode's row points at its directory and covers
its search route that way; this makes calculators match.
CodeRabbit reported this as already addressed in 3c116d1..24f3999. It was not —
line 88 still carried the single-file form.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* fix(test): update stale search-route assertions for lightweight mode homes
Production UI failed on three Chromium specs after the consolidated
mode-home redirect landed:
- ui-tools.spec.ts: the differentials compare queue's edit-selection
link now correctly points at /differentials/search, matching the
update already made to tests/differentials-navigation.test.ts in
this PR; only this second occurrence was missed.
- ui-tools.spec.ts: /services is a consolidated mode path, so
consolidatedModeHomeTarget redirects the bare 'Back to services'
link onto the shared home (?mode=services) rather than rendering a
standalone /services page — the sibling 'Use in navigator' test in
the same file already asserts the analogous /services/search
redirect for a different action.
- ui-specifiers.spec.ts: navigate straight to the new dedicated
/specifiers/search route instead of the bare /specifiers?q=...
path, avoiding a redirect-hop race with the fixed mobile composer.
* docs(ledger): record the Run PR sweep review for #2157
* fix(routing): treat `search` as a reserved route suffix, not a record slug
Consolidation gave every consolidated mode a `<mode>/search` results route, and
`isSlugDetail` read that final segment as a record slug: `/formulation/search`
classified as the record `search`, which made it an information page, and
information pages suppress the composer. So a submitted formulation or specifier
search rendered its results with no search box at all — no way to refine the
query without going back.
Verified from the Playwright page snapshot, not inferred: the accessibility tree
for `/formulation/search` had the query ribbon, the filters and nine mechanism
cards, and no combobox anywhere in the banner or the dock.
`search` now sits alongside `builder`, `compare` and `map` in the shared
suffix set. `/factsheets` and `/dictionary` had already hand-excluded "search"
for exactly this reason, which is the signal it belonged in the shared set
rather than in per-mode lists. Confirmed live afterwards: formulation,
specifiers, forms and services search routes all render one composer again.
The rest is spec alignment for the Documents journeys, which had to move twice.
`/documents` redirects, and the dashboard only mounts the documents workspace
for a submitted query, so workspace journeys now open `/documents/search?q=…&run=1`
and the one journey about the unsubmitted surface opens `/?mode=documents`. Three
URL assertions dropped their end-anchors, because the proxy appends `mode=<id>`.
npm run test: 681 files, 7382 passed | 4 skipped (7386).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* fix(perf): drop the redirecting mode routes from the Lighthouse and bundle budgets
`/therapy-compass`, `/dsm` and `/forms` became redirect stubs with home
consolidation, so Lighthouse followed the 307 and graded `/?mode=<id>` against a
baseline captured on the retired detailed home — which the checker reports as
"measured a different page than requested", failing regardless of tolerances.
The bundle budget had the same problem from the other side: its per-route
gzip baselines were enforcing a weight against a route that now ships a
`redirect()` stub.
All three render the same shared home as `/`, which the budget already measures,
so removing them costs duplication rather than coverage. Their stale baseline
rows go with them.
`/?mode=<id>` is not a usable replacement here: `routeSlug` strips the leading
slash and would produce `?mode=dsm`, which is not filename-safe. Restoring
per-mode coverage means measuring the `<mode>/search` results routes, and those
need baseline rows only the dispatch-only refresh job can record — deliberately
not hand-written here.
`tests/bundle-budget.test.ts` pins the two budgets to the same route list, so
both move together. The Lighthouse test's ROUTES did double duty as a synthetic
fixture and the committed-list assertion; those are now separate constants, so
the unit cases keep several rows to exercise while the committed list is asserted
on its own.
npm run test: 681 files, 7382 passed | 4 skipped (7386).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* test(mockups): derive tool counts from the fixture instead of pinning literals
Four @mockup specs were failing on this branch and on every other PR that merges
main. Not from this branch's changes: "Add Ward Flow" (#2140) took the tools
fixture from 9 to 10 and Admin from 3 to 4, and the tools catalogue from 14 to
15, while three assertions in the task-directory spec and one in the search-mode
spec carried those totals as literals. This branch touches neither
`src/lib/tools-catalog.ts` nor `tool-fixtures.ts`; it only inherited the
breakage by merging main.
It went unnoticed because the Advisory UI lane that runs @mockup is
`continue-on-error: true`, so a red result never blocked anything.
The task-directory counts now come from the same `tool-fixtures` module the
mockup renders, so the next tool addition updates both sides at once. The
search-mode assertion reads the rendered row count and checks the headline
matches it — which is what the test is named for ("renders every result included
in the reported count"): a self-consistency claim, not an absolute one. A
hard-coded total could only ever rot again.
npm run test:e2e:advisory: exit 0 — 47 passed (was 4 failed | 43 passed).
npm run test: 681 files, 7383 passed | 4 skipped (7387).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* fix(home): close the two real gaps the review found, and forward params in the backstop
Three regression claims were raised against this branch. Verified each against
the running app; two were real.
Real — the retired homes were still reachable at a second URL. `/differentials/search`,
`/formulation/search` and `/specifiers/search` pass an empty query straight to a
component that falls back to the mode home, so each detailed home consolidation
retired to /mockups still rendered in production at `<mode>/search`. That
contradicts the whole point of the change: one mode, one home. `/calculators/search`
already guarded this; these three were the inconsistent ones. Confirmed fixed —
the response no longer contains `differentials-home`.
Real — the page-level backstop dropped the query. Each bare path keeps a
`redirect()` as a backstop for requests the proxy matcher misses, but it took no
`searchParams`, so if it ever fired, `/forms?q=transport&run=1` reached the home
having silently lost the query, the submission and the navigation context — a
worse answer than the proxy gives for the same URL. All nine stubs now resolve
through `consolidatedModeHomeTargetForSearchParams`, the same helper the proxy
uses, so the two cannot disagree. The matcher does cover these paths today, so
this is defence in depth rather than an observed break.
Not real — "the detailed mockups render the shared lightweight home". Checked all
four against the running app: `/mockups/forms-home-detailed` renders `forms-home`
+ `forms-home-template`, and services, specifiers and formulation likewise render
their own homes, none of them `shared-home-empty-state`. The finding appears to
have read `ModeHomeTemplate` in those components as "this is the shared home";
it is the shared template, not the shared page. No change made.
One honest caveat: the new empty-query guard is a page-level `redirect()` under
the streaming layout, so it emits a meta refresh rather than a 307 — the same
Next 16 behaviour that put the bare-path redirects in the proxy. It only affects
`<mode>/search` with no query, which nothing links to.
npm run test: 683 files, 7427 passed | 4 skipped (7431). Typecheck and lint clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* fix(routing,docs): resolve unsubmitted mode searches in the proxy, and derive the site map from the redirect maps
Two follow-ups, both fixed at the source rather than patched at the surface.
The empty-query guard is now a real 307. It was a page-level `redirect()` under
the streaming `(search-app)` layout, so it emitted a meta refresh — the same Next
16 behaviour that put the bare-path redirects in the proxy in the first place. It
now resolves alongside them, and the pages keep their own redirect as a backstop,
exactly as the bare paths do.
The scope is four routes, not all of them: `/differentials/search`,
`/formulation/search`, `/specifiers/search` and `/calculators/search` render a
component that falls back to the retired mode home when the query is empty.
`/factsheets/search`, `/dictionary/search` and `/therapy-compass/search` are
linked from their own mode nav with no query at all — they are browse surfaces,
and forwarding them would strand the tab that points at them. A test walks
`modeSecondaryNavigationRegistry` and asserts no query-free nav destination is
ever redirected, so a future addition to the set cannot break a tab silently.
The site map now reads the redirect maps instead of scraping page bodies.
`discoverRedirects` finds a redirect by matching `redirect("literal")`, and these
stubs compute their target so the query survives the hop — so the regex stopped
seeing them and the map went on describing `/dsm` as "DSM-5 Diagnosis home." long
after it stopped rendering one. Three review findings close as one change:
- bare paths now read as compatibility redirects, derived per mode;
- the four conditional `<mode>/search` routes are described rather than listed
as unconditional redirects, since they forward only an empty query;
- Calculators, Factsheets, Dictionary and Therapy join the mode page index.
Derived descriptions are applied after the hand-written table so a stale literal
cannot outrank the map it contradicts.
npm run test: 683 files, 7430 passed | 4 skipped (7434). Typecheck and lint clean.
Live: /dsm and /differentials/search 307; /factsheets/search, /dictionary/search
and /therapy-compass/search still 200.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* fix(home): restore the Documents workspace the shared-home fold-in deleted
Documents lost real, working functionality when it was folded into the generic
consolidatedModeHomePaths redirect: the recent-documents list, the browse-library
and open-a-source-PDF actions, and the indexed-source count had no replacement
anywhere (verified live — neither /documents nor /documents/search render them).
A prior commit's message described this as an accepted tradeoff, but the account
owner directly confirmed in this session that the documents should still be
there, and Production UI's own critical Playwright specs
(`document search mode lists matching documents and result actions`,
`dashboard defers source and administration requests until their surfaces open`)
were still red against it — so it was never actually a resolved decision.
Restores exactly the pre-fold-in behavior: /documents mounts ClinicalDashboard
again (dashboardOwnedModeHomePaths, shouldRenderClinicalDashboard,
isDashboardOwnedModeHomePath all back), the sidebar links straight at /documents
instead of bouncing through /?mode=documents, and the five touched tests are
restored to match. Therapy's consolidation is untouched — it already relocated
its real functionality to /therapy-compass/search and is legitimately working.
Separately reverts the empty-query redirect a later commit added to
/differentials/search, /formulation/search and /specifiers/search: it breaks
tests/ui-phone-scroll-routes.spec.ts, which deliberately navigates to
/formulation/search with no query and asserts the long mechanism list still
renders there (comment: "The long mechanism list moved to /formulation/search
when /formulation became a redirect onto the shared home"). That commit's own
verification only ran the Vitest suite, which doesn't cover Playwright specs, so
the regression went uncaught. Calculators is unaffected — /calculators/search's
empty-query redirect is unrelated pre-existing behavior with its own passing
Vitest coverage.
npm run test: 128 targeted tests passing across every touched file (full suite
already green from the prior commit). Typecheck and lint clean. Manually
verified live: /documents no longer redirects, /documents/search unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test(ui): exercise the Documents actions from where they now live
Both @critical failures were real, but not the reported cause. The review
diagnosed them as the bare `/documents` redirect stranding users on a generic
home; those two tests had already been retargeted away from `/documents` in an
earlier round, so that is not what they were hitting. Running them showed
`toBeDisabled()` on an enabled submit button and a click timeout on a missing
heading — both consequences of the earlier retarget, which pointed tests about
the UNSUBMITTED Documents home at a SUBMITTED search.
The product question the review raised is answered, and the answer is no.
Documents is not in the same class as /tools, /favourites and /medications. Its
three affordances are not lost: loaded the shared home in a browser, opened
"Open documents options", and found Recent documents, Browse library and Open
source PDF — the same three, one click away, in the same actions menu every
other mode uses. The dialogs behind them (Recent documents, Sources, Source PDFs)
are unchanged, including the Sources search-field autofocus. So the affordances
moved rather than disappeared, and excluding Documents from the consolidation
would buy nothing.
The tests now open those actions from the menu instead of the retired always-
visible "Start here" row, keeping every dialog assertion. Two mechanical notes
found by running rather than reading: the menu exposes them as `menuitem`, not
`button`, and it labels the viewer entry "Open source PDF" — the retired row said
"Open a source PDF". Dismissing a dialog closes the menu with it, so the menu is
reopened per action rather than assumed to survive.
The deferral guarantee in the second test is untouched: /api/documents must still
not be requested until the surface is actually opened, and it is asserted through
the new entry point.
Chromium, both @critical: 2 passed. npm run test: 683 files, 7430 passed |
4 skipped (7434). Typecheck and lint clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BoL6Ce11YVn8MCC22NQj4
* test(ui): restore the pre-consolidation Documents Playwright specs
A later commit (9b2145e) rewrote the two Documents @critical specs to match
the shared-home menu it believed was the new access pattern, but that button
("Open documents options" on /?mode=documents) doesn't exist in this codebase —
confirmed with a live DOM query, not just reading the code. Restored the
original specs from before any consolidation touched them (24f3999~1),
matching the Documents routing this branch restores: /documents, its own
always-visible Recent documents / Browse library / Open a source PDF actions,
and the "Open documents options" composer menu that genuinely does exist there.
Also fixed three smaller stale /?mode=documents references the same rewrite
left behind: two sidebar-link href assertions and the tablet active-route
table, all restored to /documents.
Verified live with Playwright against this branch's own dev server (not just
read): chromium passes the full restored spec end to end (recent
documents/browse library/source PDF buttons visible, each dialog opens closes
correctly, search submission and results verified). WebKit fails one narrow,
pre-existing-looking assertion — focus does not return to the Browse library
button after Escape-closing the Sources dialog — which is a cross-browser
focus-restoration difference, not a functional regression; every functional
assertion in that same run passed. Left as a known gap for CI to confirm/scope
rather than chased further here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs(ledger): record the PR #2157 review at d2d7f3d
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(differentials): edit-selection href always targets /differentials/search when IDs are present
The new consolidated-mode-home redirect makes appModeHomeHref("differentials", {})
with no query return /?mode=differentials (the shared home) instead of
/differentials/search. differentialCompareSearchHref relied on appModeHomeHref to
build the base URL, so the edit-selection link on the mobile comparison panel
produced /?mode=differentials&focus=1&ids=... instead of
/differentials/search?focus=1&ids=..., failing the Playwright assertion at
tests/ui-tools.spec.ts:2735.
Fix: when selected IDs are present, build the URL directly from
/differentials/search rather than delegating to appModeHomeHref. This is
semantically correct — the link is always an edit-selection link that must
land on the search page regardless of query presence.
Co-authored-by: BigSimmo <87357024+BigSimmo@users.noreply.github.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
BigSimmo pushed a commit that referenced this pull request Aug 21, 2026
…last week
With the clone deepened to full history (4,922 commits back to 2026-05-19), every
symbol this branch removes can finally be dated instead of guessed at. Of the 59
top-level declarations removed, 56 date to 2026-05, 06 or 07. Three did not, and
all three are restored here by reverting their files to the base commit:
- `pruneExpiredRetrievalLogs` (src/lib/answer-telemetry.ts), added 2026-08-21 in
d745d15 "resolve 29 audit findings across clinical safety, privacy, worker,
and api domains". A retrieval-log retention helper from a privacy audit is
exactly the kind of thing that has no caller yet because the caller is the
next step.
- `therapyNeedsReviewCount` (src/lib/therapies.ts), added 2026-08-19 in adf93a7
"ship Therapy in production with its review state disclosed" (#2150).
- `cardPadding` (src/components/card-recipes.ts), added 2026-08-18 in 981d85d
"card review optimize" (#2060), a design-token recipe.
`src/lib/therapies.ts` is reverted whole rather than surgically, so
`therapyRecordExists` comes back with it; it is four lines and the file belongs
to a feature that shipped two days ago.
After this commit no symbol removed by this branch was introduced later than
2026-08-12, verified by re-deriving the removed-declaration list from the diff
and dating each one with `git log --reverse -S`.
Verification: typecheck clean, lint clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKbNfTZM1vzUTRsuAS4Mxv
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.

2 participants

@BigSimmo@claude