diff --git a/docs/design-system/COMPONENTS.md b/docs/design-system/COMPONENTS.md index 50c76a6e8f..1ed23ff470 100644 --- a/docs/design-system/COMPONENTS.md +++ b/docs/design-system/COMPONENTS.md @@ -976,7 +976,7 @@ This generated snapshot is a local source-derived inventory. It does not assert | `Breadcrumb` | layout | yes | yes | inherited-global-root | yes | no | 1 | | `Button` | controls | yes | yes | inherited-global-root | yes | no | 13 | | `Checkbox` | controls | yes | yes | no | yes | no | 0 | -| `Chip` | controls | yes | yes | inherited-global-root | yes | no | 4 | +| `Chip` | controls | yes | yes | inherited-global-root | yes | no | 5 | | `Citation` | source | yes | yes | no | yes | no | 0 | | `CitationList` | source | yes | yes | no | yes | no | 0 | | `ConfirmDialog` | layout | yes | yes | no | yes | no | 0 | diff --git a/docs/design-system/adoption-manifest.json b/docs/design-system/adoption-manifest.json index 681c0d1ec8..3aee8a690e 100644 --- a/docs/design-system/adoption-manifest.json +++ b/docs/design-system/adoption-manifest.json @@ -375,12 +375,14 @@ "directImportFiles": [ "src/components/applications-launcher-page.tsx", "src/components/clinical-dashboard/favourites-command-library-page.tsx", + "src/components/therapy-compass/screens/pathways-screen.tsx", "src/components/therapy-compass/ui.tsx", "src/components/tools/tools-search-results-page.tsx" ], "productImportFiles": [ "src/components/applications-launcher-page.tsx", "src/components/clinical-dashboard/favourites-command-library-page.tsx", + "src/components/therapy-compass/screens/pathways-screen.tsx", "src/components/therapy-compass/ui.tsx", "src/components/tools/tools-search-results-page.tsx" ], diff --git a/src/components/mode-nav/registry-mode-nav.tsx b/src/components/mode-nav/registry-mode-nav.tsx index b17b744a46..bfc9d2cae9 100644 --- a/src/components/mode-nav/registry-mode-nav.tsx +++ b/src/components/mode-nav/registry-mode-nav.tsx @@ -3,6 +3,7 @@ import { BookOpenText, BookMarked, + ClipboardCheck, ClipboardList, GitCompareArrows, ListChecks, @@ -36,7 +37,7 @@ export const registryModeNavDensityProfiles = { formulation: "compact-four", differentials: "balanced-four", factsheets: "two-item", - "therapy-compass": "balanced-four", + "therapy-compass": "extended", // Four destinations with medium labels, the same family as differentials: // "Compare" and "Sources" are well inside the budget calibrated for // "Presentations". `extended` was chosen when the mode had five destinations @@ -53,6 +54,7 @@ export const registryModeNavDensityProfiles = { */ const iconByItemId: Record = { search: Search, + review: ClipboardCheck, diagnoses: Stethoscope, presentations: ClipboardList, compare: GitCompareArrows, diff --git a/src/components/therapy-compass/screens/pathways-screen.tsx b/src/components/therapy-compass/screens/pathways-screen.tsx index ae45cfee6f..65dae23192 100644 --- a/src/components/therapy-compass/screens/pathways-screen.tsx +++ b/src/components/therapy-compass/screens/pathways-screen.tsx @@ -4,6 +4,7 @@ import { useMemo } from "react"; import { Check, ChevronRight, Copy, FileText, ListChecks, Scale, TriangleAlert, Waypoints } from "lucide-react"; import { cardSurface } from "@/components/card-recipes"; +import { Chip } from "@/components/ui/chip"; import { PageHeader } from "@/components/ui/page-header"; import { cn, pageContainer } from "@/components/ui-primitives"; import { Button } from "@/components/ui/button"; @@ -18,6 +19,9 @@ export function PathwaysScreen() { const b = useTcBindings(); const bySlug = useMemo(() => new Map(b.therapies.map((t) => [t.slug, t])), [b.therapies]); const pathway = b.selectedPathway; + // Header counts. Summed here rather than in the header markup so the chips + // stay declarative and the totals survive a data shape that grows a step. + const linkedStepCount = useMemo(() => b.pathways.reduce((total, p) => total + p.steps.length, 0), [b.pathways]); // Called before the early return below, because it is a hook. const { copied, copy } = useClipboard(); @@ -41,14 +45,33 @@ export function PathwaysScreen() { return (
+ {/* The Review queue lived here as the page's one action. It is a curation + surface — which records still need source review — not something a + clinician reads a pathway to reach, so the header no longer carries it; + `/therapy-compass/review` still serves it directly. What replaces it is + scale, not another control: the two counts say how much of the + catalogue this page covers, and the accent chip is the only colour a + phone gets here, since `PageHeader` hides its icon tile below `sm`. + + The description loses "generated from imported therapy records" + because that provenance is already stated twice further down the same + screen — the note under the pathway list and the caution banner — and + on a 390px viewport it was the line that wrapped into the drawer + handle. */} - Review queue - + description="Step-by-step workflows for common clinical problems." + meta={ + <> + + {b.pathways.length} {b.pathways.length === 1 ? "pathway" : "pathways"} + + + {linkedStepCount} {linkedStepCount === 1 ? "linked step" : "linked steps"} + + } /> diff --git a/src/lib/mode-secondary-navigation.ts b/src/lib/mode-secondary-navigation.ts index d0e92dbe63..ad7f4688e6 100644 --- a/src/lib/mode-secondary-navigation.ts +++ b/src/lib/mode-secondary-navigation.ts @@ -60,6 +60,7 @@ export const modeSecondaryNavigationRegistry = { { id: "recommend", label: "Recommend", href: "/therapy-compass/recommend" }, { id: "compare", label: "Compare", href: "/therapy-compass/compare" }, { id: "pathways", label: "Pathways", href: "/therapy-compass/pathways" }, + { id: "review", label: "Review", href: "/therapy-compass/review" }, ], // Two genuinely distinct surfaces: the mode home and `/factsheets/search`, a // separate component with filters, a view toggle and result rows. @@ -191,6 +192,7 @@ export function activeModeSecondaryNavigationId(modeId: AppModeId, pathname: str if (pathname === "/therapy-compass/recommend") return "recommend"; if (pathname === "/therapy-compass/compare") return "compare"; if (pathname === "/therapy-compass/pathways") return "pathways"; + if (pathname === "/therapy-compass/review") return "review"; return null; } if (modeId === "dictionary") { diff --git a/tests/mode-nav-contract.test.ts b/tests/mode-nav-contract.test.ts index abb0788eb4..44c7723f05 100644 --- a/tests/mode-nav-contract.test.ts +++ b/tests/mode-nav-contract.test.ts @@ -188,12 +188,13 @@ describe("ModeNav item contract", () => { expect(modeNavSource).not.toMatch(/onClick\?:/); }); - it("gives Therapy the four shared workspace destinations in declared order", () => { + it("gives Therapy the five shared workspace destinations in declared order", () => { expect(modeSecondaryNavigationEntries("therapy-compass").map((entry) => entry.id)).toEqual([ "search", "recommend", "compare", "pathways", + "review", ]); }); @@ -301,10 +302,12 @@ describe("ModeNav density coverage", () => { for (const modeId of MODE_NAV_ADOPTED_MODES) { expect(covered.has(modeId), `${modeId} adopted the bar but the density spec never loads it`).toBe(true); } - // Therapy now uses the shared registry and deliberately exposes only the - // four workspace destinations. Record-owned outputs require a selected - // therapy and therefore stay off the global mode bar. - expect(covered.get("therapy-compass")).toBe(4); + // Therapy uses the shared registry and exposes five workspace + // destinations: the four clinical ones plus Review, the curation queue that + // would otherwise have no inbound link once this PR takes it off the + // Pathways header. Record-owned outputs (briefs, patient sheets) still + // require a selected therapy and stay off the global mode bar. + expect(covered.get("therapy-compass")).toBe(5); }); it("keeps each mode's declared destination count in step with the registry", () => { @@ -323,8 +326,8 @@ describe("ModeNav density coverage", () => { expect(MODE_NAV_DENSITY_PROFILES).toContain(profile); expect(coveredProfiles.get(modeId), `${modeId} browser profile`).toBe(profile); } - expect(coveredProfiles.get("therapy-compass")).toBe("balanced-four"); - expect(registryModeNavSource).toContain('"therapy-compass": "balanced-four"'); + expect(coveredProfiles.get("therapy-compass")).toBe("extended"); + expect(registryModeNavSource).toContain('"therapy-compass": "extended"'); }); }); diff --git a/tests/mode-secondary-navigation.test.ts b/tests/mode-secondary-navigation.test.ts index 25a1f0480e..f3cd4d1f1a 100644 --- a/tests/mode-secondary-navigation.test.ts +++ b/tests/mode-secondary-navigation.test.ts @@ -25,7 +25,7 @@ const expectedLabels: Record = { prescribing: [], tools: [], calculators: [], - "therapy-compass": ["Search", "Recommend", "Compare", "Pathways"], + "therapy-compass": ["Search", "Recommend", "Compare", "Pathways", "Review"], factsheets: ["Topics", "Search"], dictionary: ["Terms", "Topics", "Compare", "Sources"], }; @@ -302,6 +302,7 @@ describe("mode secondary navigation registry", () => { expect(activeModeSecondaryNavigationId("therapy-compass", "/therapy-compass/recommend")).toBe("recommend"); expect(activeModeSecondaryNavigationId("therapy-compass", "/therapy-compass/compare")).toBe("compare"); expect(activeModeSecondaryNavigationId("therapy-compass", "/therapy-compass/pathways")).toBe("pathways"); + expect(activeModeSecondaryNavigationId("therapy-compass", "/therapy-compass/review")).toBe("review"); expect(activeModeSecondaryNavigationId("therapy-compass", "/therapy-compass/cbt")).toBeNull(); // Dictionary's Search and Browse were one catalogue behind two routes and diff --git a/tests/therapy-compass-responsive-contract.test.ts b/tests/therapy-compass-responsive-contract.test.ts index eea2d41997..1e4fe906c1 100644 --- a/tests/therapy-compass-responsive-contract.test.ts +++ b/tests/therapy-compass-responsive-contract.test.ts @@ -80,7 +80,7 @@ describe("Therapy Compass responsive contract", () => { // bar, which folds its overflow into a sheet rather than off the screen // edge. Its own density and centring contract lives in mode-nav-contract. expect(registryModeNavSource).toContain("ModeNav"); - expect(registryModeNavSource).toContain('"therapy-compass": "balanced-four"'); + expect(registryModeNavSource).toContain('"therapy-compass": "extended"'); expect(registryModeNavSource).not.toContain("overflow-x-auto"); expect(registryModeNavSource).not.toContain("w-fit"); expect(globalsSource).toContain("position: relative;"); diff --git a/tests/ui-mode-nav-density.spec.ts b/tests/ui-mode-nav-density.spec.ts index e27e9ab980..5ce5c2a1db 100644 --- a/tests/ui-mode-nav-density.spec.ts +++ b/tests/ui-mode-nav-density.spec.ts @@ -80,7 +80,7 @@ const gotoTherapySearch = (page: Page) => gotoTherapy(page); * offline rather than leaving a mode uncovered. */ const MODES = [ - { modeId: "therapy-compass", route: "/therapy-compass/search?q=CBT&run=1", items: 4, profile: "balanced-four" }, + { modeId: "therapy-compass", route: "/therapy-compass/search?q=CBT&run=1", items: 5, profile: "extended" }, { modeId: "dsm", route: "/dsm/compare", items: 2, profile: "two-item" }, { modeId: "specifiers", route: "/specifiers/compare", items: 4, profile: "compact-four" }, { modeId: "formulation", route: "/formulation/compare", items: 4, profile: "compact-four" }, @@ -284,6 +284,7 @@ test.describe("ModeNav density", () => { ["/therapy-compass/compare", "Compare"], ["/therapy-compass/recommend", "Recommend"], ["/therapy-compass/pathways", "Pathways"], + ["/therapy-compass/review", "Review"], ] as const) { await gotoTherapy(page, route); await expect.poll(async () => (await readNav(page)).state, { timeout: 10_000 }).toBe("bar");