From dc217c4695d9eee2e1bc821ba5a08e3126b89fc9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 19:04:34 +0000 Subject: [PATCH 1/6] feat(therapy-compass): promote to a first-class app mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Therapy Compass was a dev-only mockup at /mockups/therapy-compass (404 in production, absent from the mode system). This wires it up as a real, production-visible mode reachable from the app navigation. - Register the "therapy-compass" mode in the app-mode registry (id, label, "/therapy-compass" href, and the benign "tools" search kind reused since the tool owns its in-library search), plus the required exhaustive maps: mode icon (Compass), universal-search domains (none — it searches the imported library, not an indexed domain), and the sitemap example. - Add the production route src/app/therapy-compass/{layout,page}.tsx: the layout mounts GlobalSearchShell with searchComposerVisible=false (the tool provides its own search surface), the page renders TherapyCompassPage. - Add it to the sidebar tool list with the Compass icon; the active-state highlight resolves on the route. - Regenerate docs/site-map.md. The dev-only mockup route stays for design reference. Bundled content is unchanged; the tool keeps its source-status / needs-review framing (source- grounded decision support surfaced with review status, not presented as validated). Verified in Chromium: the mode appears in the sidebar (with active highlight) and the MODE dropdown; selecting it renders the tool in the production shell with no double search bar; and every button works -- all 8 internal screens, Open record, Compare, Generate patient sheet, and Copy intervention -- with no console errors. typecheck, lint, prettier, and verify:cheap (2411 tests) pass. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01TJaXJntdH7Q98ejoSZF46K --- docs/site-map.md | 2 ++ scripts/generate-site-map.ts | 1 + src/app/therapy-compass/layout.tsx | 14 ++++++++++ src/app/therapy-compass/page.tsx | 13 +++++++++ .../clinical-dashboard/ClinicalSidebar.tsx | 1 + src/lib/app-mode-icons.ts | 2 ++ src/lib/app-modes.ts | 27 +++++++++++++++++++ src/lib/universal-search-mode-context.ts | 3 +++ 8 files changed, 63 insertions(+) create mode 100644 src/app/therapy-compass/layout.tsx create mode 100644 src/app/therapy-compass/page.tsx diff --git a/docs/site-map.md b/docs/site-map.md index 779d1a547..682494952 100644 --- a/docs/site-map.md +++ b/docs/site-map.md @@ -30,6 +30,7 @@ This file is generated by `npm run sitemap:update`. Run `npm run sitemap:check` - `/specifiers/builder` - Structured diagnostic wording builder. Source: `src/app/specifiers/builder/page.tsx`. - `/specifiers/compare` - Side-by-side psychiatric specifier comparison. Source: `src/app/specifiers/compare/page.tsx`. - `/specifiers/map` - Psychiatric specifier family map. Source: `src/app/specifiers/map/page.tsx`. +- `/therapy-compass` - Route discovered from app directory Source: `src/app/therapy-compass/page.tsx`. - `/tools` - Route discovered from app directory Source: `src/app/tools/page.tsx`. ## Mode/query routes @@ -45,6 +46,7 @@ This file is generated by `npm run sitemap:update`. Run `npm run sitemap:check` - `/formulation` - Formulation mode. Search kind: `formulation`. Query example: `/formulation?q=I+keep+going+over+it&focus=1&run=1`. - `/?mode=prescribing` - Medication mode. Search kind: `documents`. Query example: `/?mode=prescribing&q=acamprosate+renal+dose&focus=1&run=1`. - `/?mode=tools` - Tools mode. Search kind: `tools`. Query example: `/?mode=tools&q=medications&focus=1&run=1`. +- `/therapy-compass` - Therapy Compass mode. Search kind: `tools`. Query example: `/therapy-compass?q=behavioural+activation&focus=1&run=1`. ## Mode page index diff --git a/scripts/generate-site-map.ts b/scripts/generate-site-map.ts index 510ff32fc..921cf2b90 100644 --- a/scripts/generate-site-map.ts +++ b/scripts/generate-site-map.ts @@ -224,6 +224,7 @@ function renderModeRoutes() { formulation: appModeHomeHref("formulation", { query: "I keep going over it", focus: true, run: true }), prescribing: appModeHomeHref("prescribing", { query: "acamprosate renal dose", focus: true, run: true }), tools: appModeHomeHref("tools", { query: "medications", focus: true, run: true }), + "therapy-compass": appModeHomeHref("therapy-compass", { query: "behavioural activation", focus: true, run: true }), }; return appModeDefinitions.map((mode) => diff --git a/src/app/therapy-compass/layout.tsx b/src/app/therapy-compass/layout.tsx new file mode 100644 index 000000000..8e0c5ca66 --- /dev/null +++ b/src/app/therapy-compass/layout.tsx @@ -0,0 +1,14 @@ +import type { ReactNode } from "react"; + +import { GlobalSearchShell } from "@/components/clinical-dashboard/global-search-shell"; + +// Therapy Compass keeps the universal header + rail but provides its own primary +// search surface (the in-tool therapy search), so the shared search composer is +// suppressed here — mirroring how the mockup mounted it. +export default function TherapyCompassLayout({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} diff --git a/src/app/therapy-compass/page.tsx b/src/app/therapy-compass/page.tsx new file mode 100644 index 000000000..9fcbfc82f --- /dev/null +++ b/src/app/therapy-compass/page.tsx @@ -0,0 +1,13 @@ +import type { Metadata } from "next"; + +import { TherapyCompassPage } from "@/components/therapy-compass"; + +export const metadata: Metadata = { + title: "Therapy Compass - Clinical KB", + description: + "Source-grounded therapy decision support: search, compare, recommend, pathways, brief interventions and patient sheets.", +}; + +export default function TherapyCompassRoute() { + return ; +} diff --git a/src/components/clinical-dashboard/ClinicalSidebar.tsx b/src/components/clinical-dashboard/ClinicalSidebar.tsx index f0e1decdb..c346758af 100644 --- a/src/components/clinical-dashboard/ClinicalSidebar.tsx +++ b/src/components/clinical-dashboard/ClinicalSidebar.tsx @@ -82,6 +82,7 @@ const sidebarToolItems = [ { id: "formulation", label: "Formulation", icon: Network, href: "/formulation" }, { id: "prescribing", label: "Medication", icon: Pill, href: "/?mode=prescribing" }, { id: "tools", label: "Tools", icon: Wrench, href: "/?mode=tools" }, + { id: "therapy-compass", label: "Therapy Compass", icon: appModeIcons["therapy-compass"], href: "/therapy-compass" }, ] as const; function sidebarItemBadge(item: (typeof sidebarToolItems)[number]): string | undefined { diff --git a/src/lib/app-mode-icons.ts b/src/lib/app-mode-icons.ts index 5cfe91739..f4497c3b8 100644 --- a/src/lib/app-mode-icons.ts +++ b/src/lib/app-mode-icons.ts @@ -1,6 +1,7 @@ import { BookOpenCheck, BrainCircuit, + Compass, FileSignature, FileText, Heart, @@ -28,4 +29,5 @@ export const appModeIcons: Record = { formulation: Network, prescribing: Pill, tools: Wrench, + "therapy-compass": Compass, }; diff --git a/src/lib/app-modes.ts b/src/lib/app-modes.ts index 80c6ea5d7..7ff7a98a7 100644 --- a/src/lib/app-modes.ts +++ b/src/lib/app-modes.ts @@ -14,6 +14,7 @@ export const appModeIds = [ "formulation", "prescribing", "tools", + "therapy-compass", ] as const; export type AppModeId = (typeof appModeIds)[number]; @@ -307,6 +308,31 @@ export const appModeDefinitions = [ badgeLabel: null, }, }, + { + id: "therapy-compass", + label: "Therapy Compass", + description: "Source-grounded therapy decision support", + href: "/therapy-compass", + search: { + // Therapy Compass owns its in-tool search over the imported therapy library + // (not the document corpus), so it borrows the benign "tools" search kind and + // suppresses the universal composer on its route. + kind: "tools", + placeholder: "Search therapies, symptoms, or skills...", + inputAriaLabel: "Search therapies by problem, symptom, skill, or population", + submitIdleLabel: "Compass", + submitBusyLabel: "Compass", + submitAriaLabel: "Open Therapy Compass", + emptyTitle: "Browse the therapy library", + readyTitle: "Search source-grounded therapies", + progressLabel: "Loading the therapy library.", + resultKind: "tools", + resultHeading: "Therapies", + statusLabel: "Compass", + nextStep: "Open a therapy record", + badgeLabel: null, + }, + }, ] as const satisfies readonly AppModeDefinition[]; export function appModeDefinition(modeId: AppModeId) { @@ -339,6 +365,7 @@ const namespaceIsolatedModes = new Set([ "dsm", "specifiers", "formulation", + "therapy-compass", ]); export function appModeHomeHref(modeId: AppModeId, options: SearchNavigationOptions = {}) { diff --git a/src/lib/universal-search-mode-context.ts b/src/lib/universal-search-mode-context.ts index 21666e873..585e1ba7d 100644 --- a/src/lib/universal-search-mode-context.ts +++ b/src/lib/universal-search-mode-context.ts @@ -13,6 +13,9 @@ const preferredDomainsByMode: Record = { From 320fff3f513e056106e0d7ca125b3fb10661853a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 19:22:00 +0000 Subject: [PATCH 2/6] fix(therapy-compass): serve data off /mockups and keep run URLs on the tool route Two production-route breakages caught in Codex review of the mode promotion: - Data path: the loader fetched /mockups/therapy-compass/*.json, but proxy.ts 404s every /mockups path in production, so the live /therapy-compass route would load the shell with no dataset. Moved the JSON to public/therapy-compass-data/ and pointed the loader there (the dev-only mockup route uses the same loader, so it keeps working). - Shell routing: adding the mode to namespaceIsolatedModes makes appModeHomeHref(..., { run: true }) generate /therapy-compass?q=...&run=1, but GlobalSearchShell rendered ClinicalDashboard for any submitted-search mode not in its standalone exclusions. Excluded therapy-compass in both shouldRenderDashboardSearch blocks so run-enabled links keep rendering the tool. Adds tests/therapy-compass-mode-wiring.test.ts guarding both invariants. Verified in Chromium: /therapy-compass loads all 211 records, and /therapy-compass?q=...&run=1 renders the tool (not the dashboard), with no console errors. typecheck, lint, prettier, and verify:cheap (2414 tests) pass. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01TJaXJntdH7Q98ejoSZF46K --- .prettierignore | 2 +- .../pathways.json | 0 .../reference.json | 0 .../therapies.json | 0 .../global-search-shell.tsx | 6 +++ .../therapy-compass/data/use-therapy-data.ts | 10 +++-- tests/therapy-compass-mode-wiring.test.ts | 37 +++++++++++++++++++ 7 files changed, 50 insertions(+), 5 deletions(-) rename public/{mockups/therapy-compass => therapy-compass-data}/pathways.json (100%) rename public/{mockups/therapy-compass => therapy-compass-data}/reference.json (100%) rename public/{mockups/therapy-compass => therapy-compass-data}/therapies.json (100%) create mode 100644 tests/therapy-compass-mode-wiring.test.ts diff --git a/.prettierignore b/.prettierignore index de890bf5e..47aa49952 100644 --- a/.prettierignore +++ b/.prettierignore @@ -19,4 +19,4 @@ scratch/ src/lib/supabase/database.types.ts supabase/drift-manifest.json # Trimmed Therapy Compass content export (generated data assets); keep minified. -public/mockups/therapy-compass/ +public/therapy-compass-data/ diff --git a/public/mockups/therapy-compass/pathways.json b/public/therapy-compass-data/pathways.json similarity index 100% rename from public/mockups/therapy-compass/pathways.json rename to public/therapy-compass-data/pathways.json diff --git a/public/mockups/therapy-compass/reference.json b/public/therapy-compass-data/reference.json similarity index 100% rename from public/mockups/therapy-compass/reference.json rename to public/therapy-compass-data/reference.json diff --git a/public/mockups/therapy-compass/therapies.json b/public/therapy-compass-data/therapies.json similarity index 100% rename from public/mockups/therapy-compass/therapies.json rename to public/therapy-compass-data/therapies.json diff --git a/src/components/clinical-dashboard/global-search-shell.tsx b/src/components/clinical-dashboard/global-search-shell.tsx index 9c695da86..9be51e3af 100644 --- a/src/components/clinical-dashboard/global-search-shell.tsx +++ b/src/components/clinical-dashboard/global-search-shell.tsx @@ -129,6 +129,9 @@ function GlobalSearchShellClient(props: GlobalSearchShellProps) { resolvedSearchMode !== "dsm" && resolvedSearchMode !== "specifiers" && resolvedSearchMode !== "formulation" && + // Therapy Compass owns its route with an in-tool search; a run-enabled link + // (/therapy-compass?q=…&run=1) must keep rendering the tool, not the dashboard. + resolvedSearchMode !== "therapy-compass" && !isDocumentSearchMockupRoute; const isMedicationDetailRoute = /^\/medications\/[^/]+$/.test(pathname); const shouldRenderClinicalDashboard = !isMedicationDetailRoute && (isHomeRoute || shouldRenderDashboardSearch); @@ -251,6 +254,9 @@ function GlobalStandaloneSearchShellClient({ resolvedSearchMode !== "dsm" && resolvedSearchMode !== "specifiers" && resolvedSearchMode !== "formulation" && + // Therapy Compass owns its route with an in-tool search; a run-enabled link + // (/therapy-compass?q=…&run=1) must keep rendering the tool, not the dashboard. + resolvedSearchMode !== "therapy-compass" && !isDocumentSearchMockupRoute; const isStandaloneModeHome = !hasSubmittedModeSearch && diff --git a/src/components/therapy-compass/data/use-therapy-data.ts b/src/components/therapy-compass/data/use-therapy-data.ts index d1698e744..cecce45ad 100644 --- a/src/components/therapy-compass/data/use-therapy-data.ts +++ b/src/components/therapy-compass/data/use-therapy-data.ts @@ -4,11 +4,13 @@ import { useEffect, useState } from "react"; import type { Pathway, ReferenceData, Therapy, TherapyDataset } from "./types"; -const BASE = "/mockups/therapy-compass"; +// Served as static public assets. Kept outside /mockups so the production +// `/therapy-compass` route can load them — `proxy.ts` 404s every /mockups path +// in production, which would otherwise starve the tool of its dataset. +const BASE = "/therapy-compass-data"; -// Module-level cache so the ~2.7 MB dataset is fetched at most once per session, -// shared across every screen. The route is dev-only, so these static assets -// never ship to production. +// Module-level cache so the ~2.6 MB dataset is fetched at most once per session, +// shared across every screen. let cache: Promise | null = null; async function fetchJson(path: string): Promise { diff --git a/tests/therapy-compass-mode-wiring.test.ts b/tests/therapy-compass-mode-wiring.test.ts new file mode 100644 index 000000000..9cdaadd57 --- /dev/null +++ b/tests/therapy-compass-mode-wiring.test.ts @@ -0,0 +1,37 @@ +import { existsSync, readFileSync } from "node:fs"; + +import { describe, expect, it } from "vitest"; + +// Guards the two production-mode wiring invariants for Therapy Compass. Both were +// real breakages caught in review when the mockup was promoted to a live mode. + +const loaderSrc = readFileSync( + new URL("../src/components/therapy-compass/data/use-therapy-data.ts", import.meta.url), + "utf8", +); +const shellSrc = readFileSync( + new URL("../src/components/clinical-dashboard/global-search-shell.tsx", import.meta.url), + "utf8", +); +const dataDir = new URL("../public/therapy-compass-data/", import.meta.url); + +describe("Therapy Compass production-mode wiring", () => { + it("loads its dataset from a non-/mockups path (proxy.ts 404s every /mockups path in production)", () => { + const base = loaderSrc.match(/const BASE = "([^"]+)"/)?.[1]; + expect(base).toBeTruthy(); + expect(base).not.toMatch(/^\/mockups/); + }); + + it("ships the dataset at the non-mockups public path the loader points to", () => { + for (const file of ["therapies.json", "pathways.json", "reference.json"]) { + expect(existsSync(new URL(file, dataDir))).toBe(true); + } + }); + + it("excludes therapy-compass from the shell dashboard-search so run-enabled links keep the tool", () => { + // Both shouldRenderDashboardSearch blocks (standalone + non-standalone client) must exclude the mode, + // otherwise /therapy-compass?q=…&run=1 renders ClinicalDashboard over TherapyCompassPage. + const occurrences = shellSrc.match(/resolvedSearchMode !== "therapy-compass"/g) ?? []; + expect(occurrences.length).toBeGreaterThanOrEqual(2); + }); +}); From 7b2034cce1847e5e2eb51c9a91d4e9a2de67d890 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 19:32:03 +0000 Subject: [PATCH 3/6] fix(therapy-compass): honor run-enabled deep links in the tool Third of the run-enabled-link findings: /therapy-compass?q=...&run=1 now renders the tool (prior shell fix), but TcProvider ignored q/run and always started on Home, so a query submitted from the universal composer or a recent-search pick landed on the home page with no results. - The route reads q/run from searchParams and threads initialQuery/autoRunSearch through TherapyCompassPage into TcProvider (mirroring the formulation route). - TcProvider seeds screen="search" and search.query when run-enabled, so the submitted query runs in-tool; plain /therapy-compass still opens on Home. Extends tests/therapy-compass-mode-wiring.test.ts to guard the seeding. Verified in Chromium: /therapy-compass?q=behavioural%20activation&run=1 opens on Search with the query seeded and 18 results; /therapy-compass opens on Home; no console errors. typecheck, lint, prettier, and verify:cheap (2415 tests) pass. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01TJaXJntdH7Q98ejoSZF46K --- src/app/therapy-compass/page.tsx | 15 ++++++++++++-- src/components/therapy-compass/bindings.tsx | 20 ++++++++++++++++--- .../therapy-compass/therapy-compass-page.tsx | 10 ++++++++-- tests/therapy-compass-mode-wiring.test.ts | 13 ++++++++++++ 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/app/therapy-compass/page.tsx b/src/app/therapy-compass/page.tsx index 9fcbfc82f..45e31b924 100644 --- a/src/app/therapy-compass/page.tsx +++ b/src/app/therapy-compass/page.tsx @@ -8,6 +8,17 @@ export const metadata: Metadata = { "Source-grounded therapy decision support: search, compare, recommend, pathways, brief interventions and patient sheets.", }; -export default function TherapyCompassRoute() { - return ; +type TherapyCompassRouteProps = { + searchParams?: Promise<{ q?: string | string[]; run?: string | string[] }>; +}; + +function firstParam(value: string | string[] | undefined) { + return Array.isArray(value) ? value[0] : value; +} + +export default async function TherapyCompassRoute({ searchParams }: TherapyCompassRouteProps) { + const params = searchParams ? await searchParams : {}; + const query = firstParam(params.q)?.trim() ?? ""; + const autoRunSearch = firstParam(params.run) === "1" && query.length > 0; + return ; } diff --git a/src/components/therapy-compass/bindings.tsx b/src/components/therapy-compass/bindings.tsx index 31dc2dac4..4ee4cdb55 100644 --- a/src/components/therapy-compass/bindings.tsx +++ b/src/components/therapy-compass/bindings.tsx @@ -198,15 +198,29 @@ function chipStyle(on: boolean): CSSProperties { ); } -export function TcProvider({ children }: { children: ReactNode }) { +export function TcProvider({ + children, + initialQuery = "", + autoRunSearch = false, +}: { + children: ReactNode; + initialQuery?: string; + autoRunSearch?: boolean; +}) { const { data, loading, error } = useTherapyData(); const therapies = useMemo(() => data?.therapies ?? [], [data]); const pathways = useMemo(() => data?.pathways ?? [], [data]); - const [screen, setScreen] = useState("home"); + // Honor a run-enabled deep link (/therapy-compass?q=…&run=1): open on Search + // with the query seeded, so a query submitted from the universal composer or a + // recent-search pick actually runs in-tool instead of landing on Home. + const seededQuery = autoRunSearch ? initialQuery.trim() : ""; + const [screen, setScreen] = useState(seededQuery ? "search" : "home"); const [selectedSlug, setSelectedSlug] = useState(null); const [compareSlugs, setCompareSlugs] = useState([]); - const [search, setSearch] = useState(EMPTY_SEARCH); + const [search, setSearch] = useState( + seededQuery ? { ...EMPTY_SEARCH, query: seededQuery } : EMPTY_SEARCH, + ); const [recQuery, setRecQuery] = useState("What therapy for anxiety in outpatient care?"); const [recConstraints, setRecConstraints] = useState(["outpatient"]); const [selectedPathwaySlug, setSelectedPathwaySlug] = useState(null); diff --git a/src/components/therapy-compass/therapy-compass-page.tsx b/src/components/therapy-compass/therapy-compass-page.tsx index 4183daff8..f7c5fb4a5 100644 --- a/src/components/therapy-compass/therapy-compass-page.tsx +++ b/src/components/therapy-compass/therapy-compass-page.tsx @@ -68,9 +68,15 @@ function TherapyCompassShell() { * design's own left rail is dropped, with its destinations kept reachable via a * horizontal in-content nav under the global header. */ -export function TherapyCompassPage() { +export function TherapyCompassPage({ + initialQuery = "", + autoRunSearch = false, +}: { + initialQuery?: string; + autoRunSearch?: boolean; +}) { return ( - + diff --git a/tests/therapy-compass-mode-wiring.test.ts b/tests/therapy-compass-mode-wiring.test.ts index 9cdaadd57..7aa809830 100644 --- a/tests/therapy-compass-mode-wiring.test.ts +++ b/tests/therapy-compass-mode-wiring.test.ts @@ -34,4 +34,17 @@ describe("Therapy Compass production-mode wiring", () => { const occurrences = shellSrc.match(/resolvedSearchMode !== "therapy-compass"/g) ?? []; expect(occurrences.length).toBeGreaterThanOrEqual(2); }); + + it("honors run-enabled deep links by seeding the in-tool search instead of landing on Home", () => { + const routeSrc = readFileSync(new URL("../src/app/therapy-compass/page.tsx", import.meta.url), "utf8"); + const bindingsSrc = readFileSync( + new URL("../src/components/therapy-compass/bindings.tsx", import.meta.url), + "utf8", + ); + // The route reads q/run and threads autoRunSearch into the provider... + expect(routeSrc).toMatch(/searchParams/); + expect(routeSrc).toMatch(/autoRunSearch/); + // ...and the provider opens on Search (not Home) with the query seeded. + expect(bindingsSrc).toMatch(/seededQuery \? "search" : "home"/); + }); }); From f5ca25f8b6b14f41e63f708933fe4bb311994795 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 19:49:53 +0000 Subject: [PATCH 4/6] fix(therapy-compass): re-seed the tool when a new run-query deep link arrives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the run-enabled seeding: the App Router preserves TcProvider across /therapy-compass navigations, so its useState seed only ran on first mount — navigating to a different /therapy-compass?q=...&run=1 while already on the tool kept the previous search instead of running the new one. Rather than sync state in an effect (the repo bans synchronous setState in effects), remount the provider via a key derived from the run-query in TherapyCompassPage, so React re-runs the seed for each fresh deep link. In-tool navigation (which does not change the URL) leaves the key — and the tool state — untouched. Adds tests/therapy-compass-provider-seed.dom.test.tsx (rerender re-seeds on key change). typecheck, lint, prettier, and verify:cheap (2416 tests) pass. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01TJaXJntdH7Q98ejoSZF46K --- src/components/therapy-compass/bindings.tsx | 4 +- .../therapy-compass/therapy-compass-page.tsx | 6 ++- ...therapy-compass-provider-seed.dom.test.tsx | 43 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 tests/therapy-compass-provider-seed.dom.test.tsx diff --git a/src/components/therapy-compass/bindings.tsx b/src/components/therapy-compass/bindings.tsx index 4ee4cdb55..7a4ff5afb 100644 --- a/src/components/therapy-compass/bindings.tsx +++ b/src/components/therapy-compass/bindings.tsx @@ -213,7 +213,9 @@ export function TcProvider({ // Honor a run-enabled deep link (/therapy-compass?q=…&run=1): open on Search // with the query seeded, so a query submitted from the universal composer or a - // recent-search pick actually runs in-tool instead of landing on Home. + // recent-search pick runs in-tool instead of landing on Home. A fresh deep link + // while already mounted re-seeds via the provider key in TherapyCompassPage, + // which remounts this provider when the run-query changes. const seededQuery = autoRunSearch ? initialQuery.trim() : ""; const [screen, setScreen] = useState(seededQuery ? "search" : "home"); const [selectedSlug, setSelectedSlug] = useState(null); diff --git a/src/components/therapy-compass/therapy-compass-page.tsx b/src/components/therapy-compass/therapy-compass-page.tsx index f7c5fb4a5..a228f7c94 100644 --- a/src/components/therapy-compass/therapy-compass-page.tsx +++ b/src/components/therapy-compass/therapy-compass-page.tsx @@ -75,8 +75,12 @@ export function TherapyCompassPage({ initialQuery?: string; autoRunSearch?: boolean; }) { + // Remount the provider when a fresh run-enabled deep link arrives so its seed + // re-runs — the App Router preserves client state across same-route navigations, + // so without this a new /therapy-compass?q=…&run=1 would keep the prior search. + const seedKey = autoRunSearch && initialQuery.trim() ? `q:${initialQuery.trim()}` : "home"; return ( - + diff --git a/tests/therapy-compass-provider-seed.dom.test.tsx b/tests/therapy-compass-provider-seed.dom.test.tsx new file mode 100644 index 000000000..8016f2b9b --- /dev/null +++ b/tests/therapy-compass-provider-seed.dom.test.tsx @@ -0,0 +1,43 @@ +import { render } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { TcProvider, useTcBindings } from "@/components/therapy-compass/bindings"; + +// TcProvider fetches its dataset on mount; stub fetch so it mounts without +// network. The screen/search seed is independent of data loading, which is what +// this test exercises. +afterEach(() => vi.unstubAllGlobals()); + +function Probe() { + const b = useTcBindings(); + return
; +} + +describe("TcProvider run-enabled seeding", () => { + it("seeds Search + query on mount and re-seeds when the deep-link query changes", () => { + // Never-resolving fetch → the provider stays in its loading state without needing data. + vi.stubGlobal( + "fetch", + vi.fn(() => new Promise(() => {})), + ); + + const { getByTestId, rerender } = render( + + + , + ); + expect(getByTestId("probe").getAttribute("data-screen")).toBe("search"); + expect(getByTestId("probe").getAttribute("data-query")).toBe("alpha"); + + // A fresh /therapy-compass?q=beta&run=1 navigation changes the provider key + // (as TherapyCompassPage computes it), remounting the provider so its seed + // re-runs for the new query. + rerender( + + + , + ); + expect(getByTestId("probe").getAttribute("data-screen")).toBe("search"); + expect(getByTestId("probe").getAttribute("data-query")).toBe("beta"); + }); +}); From 1fd4467a03774663753aef3b7f4f45f558be4d37 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 15 Jul 2026 04:24:54 +0800 Subject: [PATCH 5/6] docs: record Therapy Compass PR review --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 03f8ed843..e08892b3e 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -491,3 +491,4 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD | 2026-07-14 | PR #655 / codex/release-blocker-remediation | a3f3a89676015cd5f018c07e8c3ad9483f91cef6 + reviewed follow-up diff | offline adversarial-latency follow-up | The final blocking offline-quality failure was an adversarial secret-exfiltration query that correctly refused but first spent about 25 seconds in lexical retrieval. Adversarial manipulation now short-circuits at the search boundary before provider-client creation, cache access, classification, aliases, or Supabase work, and is never cached. | Focused Vitest 2/2; scoped ESLint; Prettier; full TypeScript; `git diff --check`; live provider-free adversarial case completed in 100 ms with 0 ms RPC time; `eval:quality:release:offline` passed with zero blocking failures and zero model, request-ID, token, cost, or generation-latency evidence. Flaky local browser/composite suites intentionally not repeated; hosted CI remains authoritative. | | 2026-07-14 | PR #666 / codex/release-blocker-remediation | 9b56eebe4b23ab783207445fb827c317c8d59be8 + reviewed follow-up diff | review-followup | One late P2 retrieval-contract gap was confirmed: the optimized agitation query retained IM/PO but could drop other already-supported amount, route, and frequency aliases. Medication evidence intent is now shared with retrieval selection, and focused agitation queries preserve requested numeric units, SC, SL, PRN, and frequency signals without restoring the broad ten-term expansion. | GitHub review-thread inspection; focused clinical-search/retrieval Vitest 112/112; scoped ESLint; Prettier; `git diff --check`. Hosted final-head TypeScript/build/CI and exact-head staging evidence remain required after push. | | 2026-07-14 | codex/formulation-specifier-separation-current | 25d7f733f + uncommitted fix | Specifiers/Formulation boundary bug hunt and remediation | Confirmed two P1 clinical-navigation/content boundary defects: the completed Formulation change deleted the distinct psychiatric Specifiers workspace and redirected every `/specifiers/*` route into Formulation; a later universal-search contract also labelled Formulation mechanism results as `specifiers`. Restored the independent Specifiers catalogue, detail, builder, compare, and map surfaces; registered both app modes and action sets; kept submitted searches in their owning standalone workspace; and split universal search into real `specifiers` and `formulation` domains with correct records, headings, mode ownership, and destinations. | Offline only: full Vitest 2,404 passed/1 skipped, TypeScript, full ESLint, focused Vitest 56/56, sitemap check, `git diff --check`, production-readiness READY with expected missing-env warnings, identity-checked server at `http://localhost:3384`, and repaired Chromium journeys for Specifiers desktop/mobile, Formulation mobile, and explicit route-family separation all passed. No provider-backed or live API checks run. | +| 2026-07-15 | PR #679 / claude/therapy-compass-pages-rz0m5l | f5ca25f8b6b14f41e63f708933fe4bb311994795 | Therapy Compass production promotion and review-followup | All five review findings are fixed on the reviewed head: run-enabled shared-composer links stay on the Therapy Compass route, production data loads outside `/mockups`, deep links seed the in-tool search, and same-route query changes remount the provider. No additional high-confidence defect remains in the changed scope. | GitHub review-thread inventory; exact-head hosted build, critical UI smoke, UI regression, unit coverage, static, security, and image checks green; focused Vitest 28/28; TypeScript; `git diff --check`. No live Supabase/OpenAI checks run. | From 53151a5ecf6a0bb38e3d6c72c4764bf9c3669cd2 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 15 Jul 2026 04:34:53 +0800 Subject: [PATCH 6/6] docs: preserve Therapy Compass review record --- docs/branch-review-ledger.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index b4a094c44..d3898fabc 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -553,3 +553,5 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD | 2026-07-14 | cursor/fix-pr654-ci-53b4 | 9f880853ea7d268186d982f4623b71f46e77d3dc | branch-cleanup-deletion-pending | Pending deletion: content already on origin/main or superseded old-lineage snapshot / throwaway CI-retry branch; branch-only-file sweep found no novel work. Remote deletion DENIED (HTTP 403, session lacks ref-delete permission); branch still exists at this HEAD — operator must complete deletion. Scope is deletion-pending so future cleanup passes re-evaluate rather than skip. | Provider-backed GitHub PR inventory via MCP (400 PRs #264-#674, all merged=false); local read-only git: rewritten-main-aware cherry-pick + two-dot tree + branch-only-file novelty sweep vs origin/main e75fad90; adversarial re-check. Remote `git push --delete` attempted (provider-backed write) and DENIED with HTTP 403. No OpenAI/Supabase/live-eval checks. | | 2026-07-14 | fix/accessibility-remaining-findings | 9f880853ea7d268186d982f4623b71f46e77d3dc | branch-cleanup-deletion-pending | Pending deletion: content already on origin/main or superseded old-lineage snapshot / throwaway CI-retry branch; branch-only-file sweep found no novel work. Remote deletion DENIED (HTTP 403, session lacks ref-delete permission); branch still exists at this HEAD — operator must complete deletion. Scope is deletion-pending so future cleanup passes re-evaluate rather than skip. | Provider-backed GitHub PR inventory via MCP (400 PRs #264-#674, all merged=false); local read-only git: rewritten-main-aware cherry-pick + two-dot tree + branch-only-file novelty sweep vs origin/main e75fad90; adversarial re-check. Remote `git push --delete` attempted (provider-backed write) and DENIED with HTTP 403. No OpenAI/Supabase/live-eval checks. | | 2026-07-15 | PR #677 / claude/cleanup-branches-worktrees-mxov4x | a93db73a29ca6a19619a6592e2e30ca9ea2f8218 | active PR review and remediation | All three actionable review findings are fixed: every branch namespace is parsed from the ledger table, only an exact completed cleanup review at the current HEAD suppresses repeat work, pending deletions remain actionable, and GitHub provider provenance is accurate. No additional high-confidence defect remains in the changed scope. | GitHub unresolved-thread inventory (0 remaining); hosted required CI green; exact-head focused Vitest `tests/repo-hygiene.test.ts` (9/9); `node scripts/sweep-branch-ledger.mjs --no-fetch --json`; `git diff --check`. No Supabase, OpenAI, or other live-service checks. | +| 2026-07-15 | PR #656 / claude/specifiers-v2-design-r55baf | 58ce935758c31672a0a051c5b3e6b888a7d8d153 | full DSM/ICD specifier catalogue and clinical-content gate review | No remaining high-confidence code defect after the review sequence corrected source provenance, verified-content wording, search ranking/deduplication, empty-state behavior, and neutral mixed-source labelling. The 494 unverified definitions remain withheld from display and ranking. Residual risk is the PR-declared qualified-clinician and TGA classification review before broader clinical deployment. | GitHub review-thread inventory (0 unresolved); exact-head hosted required CI, build, critical UI, UI regression, coverage, static, and security checks green. No live Supabase/OpenAI or provider-backed clinical workflow run. | +| 2026-07-15 | PR #679 / claude/therapy-compass-pages-rz0m5l | f5ca25f8b6b14f41e63f708933fe4bb311994795 | Therapy Compass production promotion and review-followup | All five review findings are fixed on the reviewed head: run-enabled shared-composer links stay on the Therapy Compass route, production data loads outside `/mockups`, deep links seed the in-tool search, and same-route query changes remount the provider. No additional high-confidence defect remains in the changed scope. | GitHub review-thread inventory; exact-head hosted build, critical UI smoke, UI regression, unit coverage, static, security, and image checks green; focused Vitest 28/28; TypeScript; `git diff --check`. No live Supabase/OpenAI checks run. |