diff --git a/docs/outstanding-issues-inbox/61d609ea-0f7c-4934-ab71-b9a972f5a7be.json b/docs/outstanding-issues-inbox/61d609ea-0f7c-4934-ab71-b9a972f5a7be.json new file mode 100644 index 0000000000..ec9a87fcca --- /dev/null +++ b/docs/outstanding-issues-inbox/61d609ea-0f7c-4934-ab71-b9a972f5a7be.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "id": "61d609ea-0f7c-4934-ab71-b9a972f5a7be", + "createdOn": "2026-08-18", + "action": "add", + "payload": { + "pri": "P2", + "type": "issue", + "summary": "/tools on a phone is the only mode home with no visible patient-identifiable-information warning", + "detail": "Tools is the sole route setting mobileHomeComposerPlacement: 'footer' (src/lib/search-shell-props.ts). showsComposerPrivacyNotice in master-search-header.tsx:1813 is 'usesPhoneSearchLayout ? isDesktopHomeComposer : true', so the phone footer dock suppresses both the 'Do not enter patient-identifiable information.' line and the Privacy and data processing link. The composer placement matches the documented exception in docs/search-chrome-behaviour.md row 2, but the docs do not record that the exception costs the governance copy. Needs an owner decision for a clinical product. Found during the PR #2160 cross-mode audit.", + "source": "PR #2160 cross-mode home audit", + "issueUlid": "01M0BE9G1GD8JBCV161HS2GQQB" + } +} diff --git a/docs/outstanding-issues-inbox/6eb8b382-a129-4470-91ec-383bc580c25f.json b/docs/outstanding-issues-inbox/6eb8b382-a129-4470-91ec-383bc580c25f.json new file mode 100644 index 0000000000..322003c69a --- /dev/null +++ b/docs/outstanding-issues-inbox/6eb8b382-a129-4470-91ec-383bc580c25f.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "id": "6eb8b382-a129-4470-91ec-383bc580c25f", + "createdOn": "2026-08-18", + "action": "add", + "payload": { + "pri": "P3", + "type": "rec", + "summary": "Mode home copy drift: three placeholder-punctuation conventions, inconsistent heading levels, and a stale docs/site-map.md mode index", + "detail": "Placeholders use ASCII '...' (answer, documents, services, forms, favourites, dsm, specifiers, formulation, prescribing, tools, calculators), Unicode '…' (therapy-compass, factsheets, dictionary), and no terminator at all on differentials ('Ask or search a presentation'). Heading level is h2 on answer/documents/prescribing and h1 elsewhere, so the Documents home has an h2 and no h1 anywhere on the page. docs/site-map.md's mode page index covers 11 modes — Therapy, Factsheets, Dictionary and Calculators have no row — and CLAUDE.md still says '13 app modes' when app-modes.ts defines 15. Pick one convention per axis and refresh the generated docs. Found during the PR #2160 cross-mode audit.", + "source": "PR #2160 cross-mode home audit", + "issueUlid": "01M0BEA22B97VQK5QN24JSPFCM" + } +} diff --git a/docs/outstanding-issues-inbox/72ec8611-0294-4e33-aa36-92668ed25cd6.json b/docs/outstanding-issues-inbox/72ec8611-0294-4e33-aa36-92668ed25cd6.json new file mode 100644 index 0000000000..c6b70fc88e --- /dev/null +++ b/docs/outstanding-issues-inbox/72ec8611-0294-4e33-aa36-92668ed25cd6.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "id": "72ec8611-0294-4e33-aa36-92668ed25cd6", + "createdOn": "2026-08-18", + "action": "add", + "payload": { + "pri": "P2", + "type": "issue", + "summary": "Three standalone mode homes have no loading.tsx (/medications, /calculators, /dictionary) and the contract test cannot catch it", + "detail": "All three are in standaloneModeHomePaths (src/lib/search-route-ownership.ts) and chrome invariant 18 requires ModeHomeRouteLoading, but tests/mode-home-loading-contract.test.ts enumerates only ten routes and omits these three, so the gap is invisible to CI. /documents is legitimately exempt (dashboard-owned). Fix: add the three loading.tsx files and derive the contract test's route list from standaloneModeHomePaths instead of a hand-written list. Verified by direct filesystem check during the PR #2160 audit.", + "source": "PR #2160 cross-mode home audit", + "issueUlid": "01M0BE9FVW6K9YGQY9G8CYM421" + } +} diff --git a/docs/outstanding-issues-inbox/b10cb24d-17fa-40ca-b8bb-90eee806ea0a.json b/docs/outstanding-issues-inbox/b10cb24d-17fa-40ca-b8bb-90eee806ea0a.json new file mode 100644 index 0000000000..5709c95179 --- /dev/null +++ b/docs/outstanding-issues-inbox/b10cb24d-17fa-40ca-b8bb-90eee806ea0a.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "id": "b10cb24d-17fa-40ca-b8bb-90eee806ea0a", + "createdOn": "2026-08-18", + "action": "add", + "payload": { + "pri": "P3", + "type": "issue", + "summary": "Tools and Favourites bespoke home composer slots skip the SSR height reservation chrome invariant 15 requires", + "detail": "ModeHomeTemplate renders its composer slot with data-composer-reserve='pending' plus min-h tokens (mode-home-template.tsx:316-320) so the hero does not shift when the portal attaches. The two bespoke homes hand-roll the slot without either: favourites-command-library-page.tsx:1426 and tools-search-results-page.tsx:353, plus favourites-hub.tsx:187. Those three get no SSR height reservation, which is the CLS that invariant 15 exists to prevent. Found during the PR #2160 cross-mode audit.", + "source": "PR #2160 cross-mode home audit", + "issueUlid": "01M0BEA1WHYJ3R7Y86ZGZ9FEV4" + } +} diff --git a/docs/outstanding-issues-inbox/c09532b5-549c-47e0-a4b7-5977435d1d0f.json b/docs/outstanding-issues-inbox/c09532b5-549c-47e0-a4b7-5977435d1d0f.json new file mode 100644 index 0000000000..9cc50cd38a --- /dev/null +++ b/docs/outstanding-issues-inbox/c09532b5-549c-47e0-a4b7-5977435d1d0f.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "id": "c09532b5-549c-47e0-a4b7-5977435d1d0f", + "createdOn": "2026-08-18", + "action": "add", + "payload": { + "pri": "P2", + "type": "issue", + "summary": "Mode homes: two competing title systems disagree for 8 of 13 modes (sharedHomePresentation vs hard-coded standalone titles)", + "detail": "src/lib/ui-copy.ts sharedHomePresentation drives the shared home /, while each standalone *-home-page.tsx hard-codes its own title. Its doc comment claims each entry mirrors the standalone home 'so a clinician sees the same words whichever door they came through' — untrue today: Documents/Clinical Documents, Services/Clinical Services, Forms/Clinical Forms, Differentials/Differential Diagnosis, Specifiers/Diagnostic Specifiers, Formulation/Clinical Formulation, Medication/Medication Guidance, Therapy/Therapy Compass. Either derive one list from the other or correct the comment. Found during the PR #2160 cross-mode audit.", + "source": "PR #2160 cross-mode home audit", + "issueUlid": "01M0BE929PTWKWE4Q142YGCRTF" + } +} diff --git a/docs/outstanding-issues-inbox/c196b2fd-fde1-482b-9cac-355cc2b4ee04.json b/docs/outstanding-issues-inbox/c196b2fd-fde1-482b-9cac-355cc2b4ee04.json new file mode 100644 index 0000000000..c2b6a6f351 --- /dev/null +++ b/docs/outstanding-issues-inbox/c196b2fd-fde1-482b-9cac-355cc2b4ee04.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "id": "c196b2fd-fde1-482b-9cac-355cc2b4ee04", + "createdOn": "2026-08-18", + "action": "add", + "payload": { + "pri": "P3", + "type": "issue", + "summary": "Three mode homes override the canonical APP_MODE_ICON glyph (services, forms, dictionary)", + "detail": "services uses Users (canonical route), forms uses FileText (canonical fileSignature — and identical to the Documents home tile), dictionary uses BookOpen (canonical bookMarked). Same class as the therapy-compass magnifier fixed in PR #2160, which now derives from appModeIcons. Each remaining one needs its own visual-baseline re-adoption, so they were left out of that PR. Found during the PR #2160 cross-mode audit.", + "source": "PR #2160 cross-mode home audit", + "issueUlid": "01M0BE9FPF0EKBGCMBD20EHNDF" + } +} diff --git a/docs/outstanding-issues-inbox/db8e7ade-2f04-4255-b812-29900e801e47.json b/docs/outstanding-issues-inbox/db8e7ade-2f04-4255-b812-29900e801e47.json new file mode 100644 index 0000000000..fd4966397c --- /dev/null +++ b/docs/outstanding-issues-inbox/db8e7ade-2f04-4255-b812-29900e801e47.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "id": "db8e7ade-2f04-4255-b812-29900e801e47", + "createdOn": "2026-08-18", + "action": "add", + "payload": { + "pri": "P3", + "type": "issue", + "summary": "/favourites and /?mode=favourites render visibly different homes for the same mode", + "detail": "The standalone hero lockup was deliberately deleted from favourites-command-library-page.tsx (ledger #164), but the dashboard variant FavouritesHub (src/components/clinical-dashboard/favourites-hub.tsx:179) still renders ModeHomeHero with 'Favourites / Saved notes, sources, and sets.' So the same mode looks different depending on the door. Decide which treatment is canonical and apply it to both. Found during the PR #2160 cross-mode audit.", + "source": "PR #2160 cross-mode home audit", + "issueUlid": "01M0BEA1Q5V0EDR4MJSWW1F5GD" + } +} diff --git a/docs/outstanding-issues-inbox/ef811fd9-df86-4a1f-bd5e-cd86233d251e.json b/docs/outstanding-issues-inbox/ef811fd9-df86-4a1f-bd5e-cd86233d251e.json new file mode 100644 index 0000000000..9faf6818e9 --- /dev/null +++ b/docs/outstanding-issues-inbox/ef811fd9-df86-4a1f-bd5e-cd86233d251e.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "id": "ef811fd9-df86-4a1f-bd5e-cd86233d251e", + "createdOn": "2026-08-18", + "action": "add", + "payload": { + "pri": "P3", + "type": "rec", + "summary": "Mode home suggestion data is duplicated across three unrelated sources", + "detail": "searchCommandSurfaceByMode examples/suggestions (src/lib/search-command-surface.ts) drive the Try this ticket, rotating hint and prompt chips; per-page pills arrays (e.g. therapy-compass/screens/home-screen.tsx:14, services-home-page.tsx) drive the mode-home pill row; src/lib/tools-catalog.ts:348 is a third. Only the first drives the ticket, so after PR #2160 the Therapy home advertises two different suggestion sets — its five pills and the three ticket examples. Reconcile to one source per mode. Found during the PR #2160 cross-mode audit.", + "source": "PR #2160 cross-mode home audit", + "issueUlid": "01M0BEA27H90Y0FDZXZY53CRKF" + } +} diff --git a/docs/outstanding-issues-inbox/fa9f768a-9eee-4a67-96b5-ded98afbecd9.json b/docs/outstanding-issues-inbox/fa9f768a-9eee-4a67-96b5-ded98afbecd9.json new file mode 100644 index 0000000000..a3b44c5dd1 --- /dev/null +++ b/docs/outstanding-issues-inbox/fa9f768a-9eee-4a67-96b5-ded98afbecd9.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "id": "fa9f768a-9eee-4a67-96b5-ded98afbecd9", + "createdOn": "2026-08-18", + "action": "add", + "payload": { + "pri": "P2", + "type": "issue", + "summary": "Mode-home composer reserve does not account for the suggestion ticket, so every ticket-bearing home carries a ~0.035 CLS shift", + "detail": "ModeHomeTemplate reserves the composer slot with --spacing-mode-home-composer-phone (6.625rem) / --spacing-mode-home-composer-wide (5.5rem), but the portal content is UniversalSearchCommandSurface, which renders SmartRotatingHint (phone ticket) and the sm+ rotating line/prompt-chip row ABOVE the composer inside that same slot. The reserve therefore under-accounts, and the portal attaching post-hydration shifts content — the defect class chrome invariant 15 exists to prevent. Evidence from the PR #2160 Lighthouse run: mobile-dsm baseline CLS 0.0353, mobile-forms 0.088, mobile-root 0.016, while mobile-therapy-compass was 0.000 purely because Therapy had no command-surface entry and so rendered no ticket. Restoring the ticket moved Therapy to 0.032, matching its peers. Fix: raise the reserve tokens to include the hint row height (or reserve it separately), which should take every mode home toward ~0. Touches all 15 mode homes, so it needs verify:phone-chrome plus Lighthouse and visual baseline re-adoption — deliberately not bundled into PR #2160.", + "source": "PR #2160 Lighthouse budget failure", + "issueUlid": "01M0BF075ZJVYQEMAJJ5Y2R1WF" + } +} diff --git a/src/components/clinical-dashboard/search-results-header-band.tsx b/src/components/clinical-dashboard/search-results-header-band.tsx index 9c815dd33a..42d2156e8b 100644 --- a/src/components/clinical-dashboard/search-results-header-band.tsx +++ b/src/components/clinical-dashboard/search-results-header-band.tsx @@ -869,11 +869,13 @@ export function SearchResultsEmptyState({ const filtered = appliedFilters.length > 0; const Title = headingLevel ? (`h${headingLevel}` as "h2" | "h3" | "h4" | "h5" | "h6") : "p"; // What the panel can actually offer, decided before the copy describes it. - // `searchCommandSurfaceByMode` is a `Partial>` and therapy-compass - // has no entry, so there is neither an example nor a cross-mode route there — - // yet the body said "Try an example, or jump to another mode", naming two - // controls the reader could not see. Copy that promises absent affordances is - // the same defect class as a label that does not match its handler. + // Both halves need a handler as well as data: most hosts render this panel + // without `onTryExample`/`onCrossMode` (only Services, Calculators and Forms + // wire the first), so a mode with a full `searchCommandSurfaceByMode` entry can + // still have no example and no cross-mode route to offer — yet the body said + // "Try an example, or jump to another mode", naming two controls the reader + // could not see. Copy that promises absent affordances is the same defect class + // as a label that does not match its handler. const hasExample = Boolean(config?.examples[0] && onTryExample); const hasCrossMode = crossModes.length > 0 && Boolean(onCrossMode); // Degraded outranks filtered, which outranks a plain miss. A search whose diff --git a/src/components/therapy-compass/screens/home-screen.tsx b/src/components/therapy-compass/screens/home-screen.tsx index 039ad39859..fbd4790c55 100644 --- a/src/components/therapy-compass/screens/home-screen.tsx +++ b/src/components/therapy-compass/screens/home-screen.tsx @@ -1,9 +1,10 @@ "use client"; -import { GitCompareArrows, Network, Search, Sparkles, Waypoints } from "lucide-react"; +import { GitCompareArrows, Network, Sparkles, Waypoints } from "lucide-react"; import { useRouter } from "next/navigation"; import { ModeHomeMain, ModeHomeTemplate } from "@/components/mode-home-template"; +import { appModeIcons } from "@/lib/app-mode-icons"; import { modeHomeDesktopComposerSlotId } from "@/lib/mode-home-composer"; import { therapyHrefWithSearchParams, therapyScreenHref } from "@/lib/therapy-compass-navigation"; @@ -38,7 +39,11 @@ export function HomeScreen() { testId="therapy-compass" title="Therapy" subtitle={therapyCountCopy} - icon={Search} + // The mode's identity glyph is derived from APP_MODE_ICON rather than + // chosen here, so this medallion cannot drift from the one nav, the mode + // picker and the shared home `/` all render. It was a hard-coded magnifier, + // which made the same mode show two different identities by door. + icon={appModeIcons["therapy-compass"]} actionsLabel="Therapy workflows" desktopComposerSlotId={modeHomeDesktopComposerSlotId} actions={[ diff --git a/src/lib/search-command-surface.ts b/src/lib/search-command-surface.ts index d761eff350..8b17f3ba60 100644 --- a/src/lib/search-command-surface.ts +++ b/src/lib/search-command-surface.ts @@ -178,6 +178,24 @@ const searchCommandSurfaceByMode: Partial { expect(specifiers?.crossModes).toContain("formulation"); }); + it("covers every app mode, so no mode silently loses the command surface", () => { + // `UniversalSearchCommandSurface` early-returns on a null config, which takes + // the phone "Try this" ticker, the sm+ rotating hint, the prompt-chip row and + // the whole command dropdown with it. therapy-compass shipped in exactly that + // state — the single uncovered mode — so pin the whole set rather than the + // one mode that happened to be missing. + for (const modeId of appModeIds) { + const config = searchCommandSurfaceConfig(modeId); + expect(config, `${modeId} has no search command surface config`).not.toBeNull(); + expect(config?.examples.length, `${modeId} has no examples`).toBeGreaterThan(0); + expect(config?.suggestions.length, `${modeId} has no suggestions`).toBeGreaterThan(0); + } + }); + + it("keeps therapy suggestions local to the generated catalogue", () => { + const therapy = searchCommandSurfaceConfig("therapy-compass"); + expect(therapy?.examples).toContain("trauma-focused CBT"); + expect(therapy?.crossModes).toContain("documents"); + // Therapy reads public/therapy-compass-data, not the remote index. + expect(therapy?.remoteSearchEnabled).toBe(false); + }); + it("keeps calculator suggestions local while preserving remote typeahead elsewhere", () => { expect(commandSurfaceRemoteSearchEnabled("calculators")).toBe(false); expect(commandSurfaceRemoteSearchEnabled("documents")).toBe(true); diff --git a/tests/search-results-header-band.dom.test.tsx b/tests/search-results-header-band.dom.test.tsx index 821d803000..aa14526959 100644 --- a/tests/search-results-header-band.dom.test.tsx +++ b/tests/search-results-header-band.dom.test.tsx @@ -1431,10 +1431,12 @@ describe("SearchResultsEmptyState", () => { // owns that role on every search route, and a second one made singular // `getByRole("status")` queries across the suite ambiguous. expect(screen.getByText("No matches for “unmatched therapy”")).toBeVisible(); - // `searchCommandSurfaceByMode` is a `Partial>` with no - // therapy-compass entry, so this mode has neither an example nor a - // cross-mode route. The body must not tell the reader to "try an example, or - // jump to another mode" when the panel renders no control for either. + // No `onTryExample` or `onCrossMode` handler is passed here — matching the + // real Therapy call site (therapy-compass/screens/search-screen.tsx) — so + // this panel has neither an example nor a cross-mode route to offer, even + // though the mode now carries a full `searchCommandSurfaceByMode` entry. The + // body must not tell the reader to "try an example, or jump to another mode" + // when the panel renders no control for either. expect(screen.getByText("Check the spelling, or try a broader term.")).toBeVisible(); expect(screen.queryByRole("button", { name: /^Try:/ })).toBeNull(); expect(screen.queryByRole("button", { name: /^Search in / })).toBeNull(); diff --git a/tests/search-shell-props.test.ts b/tests/search-shell-props.test.ts index 720fd702c9..1712fb8692 100644 --- a/tests/search-shell-props.test.ts +++ b/tests/search-shell-props.test.ts @@ -40,8 +40,11 @@ describe("searchShellPropsForPathname", () => { }); it("maps therapy and home fallbacks", () => { + // Therapy was the one standalone mode home not declaring the hero placement, + // which left it on a different composer code path from its twelve peers. expect(searchShellPropsForPathname("/therapy-compass/search")).toEqual({ initialMode: "therapy-compass", + desktopSearchPlacement: "hero", }); expect(searchShellPropsForPathname("/")).toEqual({ initialMode: "answer" }); });