diff --git a/docs/branch-review-records/730c1139a3494d1af3269ee1091efae428933fc910dc3da951590bdc62190344.record.md b/docs/branch-review-records/730c1139a3494d1af3269ee1091efae428933fc910dc3da951590bdc62190344.record.md new file mode 100644 index 0000000000..be565add6d --- /dev/null +++ b/docs/branch-review-records/730c1139a3494d1af3269ee1091efae428933fc910dc3da951590bdc62190344.record.md @@ -0,0 +1 @@ +| 2026-08-18 | claude/therapy-modes-visibility-bb37d2 | 9b4b3056b1f4ef7355e8947d6b210dee63de182e | Therapy production visibility: remove devOnly gate, route-layout not-found gate and production review filter; add catalogue review notice + needsReviewCount; retire PLAYWRIGHT_OFFLINE_MODE bypass; update pinning contracts | Approved — reachability now disclosed rather than gated; per-record reviewStatus badges retained on every surface; single-commit revert restores all three gates | verify:pr-local (docs/ledger/lint/typecheck passed; test failed only on unrelated load-flaky tests/codex-cloud-setup.test.ts, which passes in isolation at HEAD and with the change); build from wiped .next compiled successfully with all 9 therapy routes; check:rag:fixtures, check:medication-interactions, check:medication-lexicon-report passed; focused therapy+route-reachability contracts 77 passed; eslint+tsc clean; dev-server route 200s. verify:ui not run - coordinator heavy lock held by another worktree | diff --git a/docs/codebase-index.md b/docs/codebase-index.md index 353e9ae08f..2b345f221f 100644 --- a/docs/codebase-index.md +++ b/docs/codebase-index.md @@ -64,6 +64,7 @@ Smaller top-level directories that are easy to miss: - **Home:** `src/app/(search-app)/page.tsx` — dashboard rendered by shell - **Dashboard:** `src/components/ClinicalDashboard.tsx` + `src/components/clinical-dashboard/` - **Modes (15):** `src/lib/app-modes.ts` — answer, documents, services, forms, favourites, differentials, DSM-5 diagnosis, specifiers, formulation, prescribing, tools, calculators, Therapy, Factsheets, Dictionary + - **Therapy review disclosure.** Therapy was `devOnly` while its 205-record catalogue awaited qualified-clinician sign-off. That hid the mode from production navigation, 404'd `/therapy-compass` in the route layout, and made `therapyRecordsForEnvironment` filter every record out — so all 205 detail/brief/sheet routes and every universal-search therapy hit 404'd for real users while working locally. The owner's decision (2026-08-19) replaced the gate with disclosure: reachability is no longer conditioned on review status anywhere, and the caveat is stated instead — catalogue-wide by `TherapyReviewNotice` above the Therapy home hero (counts from the generated `THERAPY_CATALOGUE_SUMMARY.needsReviewCount`, kept in step by the index generator's check mode), and per record by the `reviewStatus` badge on every card, detail page, brief, sheet, comparison, pathway, and universal-search result. `therapyNeedsReview` survives as the label source only. Pinned by `tests/app-modes.test.ts` (reachability), `tests/therapy-review-regressions.test.ts` (the notice and the per-record badges), and `tests/therapy-pr-unblocking-contract.test.ts` (the retired `PLAYWRIGHT_OFFLINE_MODE` bypass that existed only to reach the gated route). ### Product pages (`src/app/`) diff --git a/scripts/build-therapies-index.mjs b/scripts/build-therapies-index.mjs index e66efaca8e..dca56b27e5 100644 --- a/scripts/build-therapies-index.mjs +++ b/scripts/build-therapies-index.mjs @@ -86,6 +86,7 @@ function renderManifest(current, previous, summary) { "// not download and parse a record projection before its LCP can paint.", "export const THERAPY_CATALOGUE_SUMMARY = {", ` totalCount: ${summary.totalCount},`, + ` needsReviewCount: ${summary.needsReviewCount},`, ` defaultBriefSlug: ${JSON.stringify(summary.defaultBriefSlug)},`, ` defaultSheetSlug: ${JSON.stringify(summary.defaultSheetSlug)},`, "} as const;\n", @@ -152,6 +153,10 @@ const browserHomeProjected = therapies const catalogueSummary = { totalCount: browserHomeProjected.length, + // Records still awaiting qualified-clinician sign-off. Therapy ships its review + // state rather than hiding unreviewed records, so the library notice states this + // count; computing it here keeps the notice from drifting away from the data. + needsReviewCount: browserHomeProjected.filter((therapy) => therapy.reviewStatus !== "reviewed").length, defaultBriefSlug: browserHomeProjected.find((therapy) => therapy.briefInterventionAvailable)?.slug ?? null, defaultSheetSlug: browserHomeProjected.find((therapy) => therapy.patientSheetAvailable)?.slug ?? null, }; @@ -160,11 +165,13 @@ if (checkOnly) { const summaryBlock = extractConstObjectBody(currentManifest, "THERAPY_CATALOGUE_SUMMARY"); const recordedSummary = { totalCount: Number(summaryBlock.match(/totalCount: (\d+)/)?.[1] ?? Number.NaN), + needsReviewCount: Number(summaryBlock.match(/needsReviewCount: (\d+)/)?.[1] ?? Number.NaN), defaultBriefSlug: summaryBlock.match(/defaultBriefSlug: (null|"[^"]+")/)?.[1] ?? "", defaultSheetSlug: summaryBlock.match(/defaultSheetSlug: (null|"[^"]+")/)?.[1] ?? "", }; if ( recordedSummary.totalCount !== catalogueSummary.totalCount || + recordedSummary.needsReviewCount !== catalogueSummary.needsReviewCount || recordedSummary.defaultBriefSlug !== JSON.stringify(catalogueSummary.defaultBriefSlug) || recordedSummary.defaultSheetSlug !== JSON.stringify(catalogueSummary.defaultSheetSlug) ) { diff --git a/src/app/(search-app)/therapy-compass/layout.tsx b/src/app/(search-app)/therapy-compass/layout.tsx index 89d0c498f3..ff162c047d 100644 --- a/src/app/(search-app)/therapy-compass/layout.tsx +++ b/src/app/(search-app)/therapy-compass/layout.tsx @@ -1,25 +1,22 @@ import { Suspense } from "react"; import type { ReactNode } from "react"; -import { notFound } from "next/navigation"; import { ModeHomeRouteLoading } from "@/components/mode-home-page-skeleton"; import { TherapyCompassRouteLayout } from "@/components/therapy-compass/therapy-compass-route-layout"; -import { isAppModeVisible } from "@/lib/app-modes"; // Therapy-only state belongs at the deepest shared route segment. Keeping this // provider out of the global search shell prevents every other mode from // downloading Therapy's client graph, while the client boundary can still read // current pathname/search params on each navigation. +// +// This segment previously returned a not-found response in production, because +// Therapy was a dev-only mode while its catalogue awaited qualified-clinician +// sign-off. That gate is gone: the review state is now disclosed on the library +// notice and on every record instead of removing the mode, so this layout has no +// environment branch at all. See `src/lib/therapies.ts` and the contracts in +// tests/therapy-review-regressions.test.ts, which assert the gate stays absent +// by scanning this file — keep the prose here free of the literal call. export default function TherapyCompassLayout({ children }: { children: ReactNode }) { - const offlineReviewBuild = process.env.PLAYWRIGHT_OFFLINE_MODE === "true"; - if ( - process.env.NODE_ENV === "production" && - !offlineReviewBuild && - !isAppModeVisible("therapy-compass", "production") - ) { - notFound(); - } - return ( }> {children} diff --git a/src/components/therapy-compass/data/generated-assets.ts b/src/components/therapy-compass/data/generated-assets.ts index 103044ac17..10af4dda60 100644 --- a/src/components/therapy-compass/data/generated-assets.ts +++ b/src/components/therapy-compass/data/generated-assets.ts @@ -21,6 +21,7 @@ export const THERAPY_CATALOGUE_ASSETS_PREVIOUS = { // not download and parse a record projection before its LCP can paint. export const THERAPY_CATALOGUE_SUMMARY = { totalCount: 205, + needsReviewCount: 205, defaultBriefSlug: "acceptance-and-commitment-therapy-act", defaultSheetSlug: "acceptance-and-commitment-therapy-act", } as const; diff --git a/src/components/therapy-compass/screens/home-screen.tsx b/src/components/therapy-compass/screens/home-screen.tsx index 8cd0e33105..039ad39859 100644 --- a/src/components/therapy-compass/screens/home-screen.tsx +++ b/src/components/therapy-compass/screens/home-screen.tsx @@ -7,6 +7,8 @@ import { ModeHomeMain, ModeHomeTemplate } from "@/components/mode-home-template" import { modeHomeDesktopComposerSlotId } from "@/lib/mode-home-composer"; import { therapyHrefWithSearchParams, therapyScreenHref } from "@/lib/therapy-compass-navigation"; +import { TherapyReviewNotice } from "../therapy-review-notice"; + import { THERAPY_CATALOGUE_SUMMARY } from "../data/generated-assets"; const SUGGESTIONS = [ @@ -28,6 +30,10 @@ export function HomeScreen() { return ( + {/* Above the hero, not in the footer: the catalogue-wide review caveat is + the first thing a reader of this library needs, and the quiet footer + line is not load-bearing enough to carry it alone. */} +