diff --git a/data/repo-awareness-snapshot.json b/data/repo-awareness-snapshot.json index 87cf2ba50d..ececfe256a 100644 --- a/data/repo-awareness-snapshot.json +++ b/data/repo-awareness-snapshot.json @@ -1,8 +1,8 @@ { "version": "repo-awareness-snapshot-v1", "captured_revision": { - "sha": "01fe261f36dd001734cfd52a0556193edfcbac15", - "committed_at": "2026-08-25T06:16:41+08:00" + "sha": "49642d65e7d916143dd9bf4eb3159a713f7d9bd5", + "committed_at": "2026-08-24T22:49:42Z" }, "routes": { "modes": [ @@ -223,6 +223,11 @@ "file": "src/app/(search-app)/factsheets/search/page.tsx", "area": "product" }, + { + "path": "/factsheets/topics", + "file": "src/app/(search-app)/factsheets/topics/page.tsx", + "area": "product" + }, { "path": "/favourites", "file": "src/app/(search-app)/favourites/page.tsx", @@ -658,6 +663,11 @@ "file": "src/app/mockups/factsheets-compact-view/page.tsx", "area": "mockup" }, + { + "path": "/mockups/factsheets-topics-phone", + "file": "src/app/mockups/factsheets-topics-phone/page.tsx", + "area": "mockup" + }, { "path": "/mockups/favourites-command-console", "file": "src/app/mockups/favourites-command-console/page.tsx", @@ -1344,9 +1354,9 @@ ], "counts": { "modes": 15, - "pages": 186, - "product_pages": 66, - "mockup_pages": 120, + "pages": 188, + "product_pages": 67, + "mockup_pages": 121, "redirects": 17, "api": 56 } diff --git a/docs/codebase-index.md b/docs/codebase-index.md index 7914e8f969..2a2ee1ace2 100644 --- a/docs/codebase-index.md +++ b/docs/codebase-index.md @@ -80,7 +80,7 @@ Smaller top-level directories that are easy to miss: | `/differentials`, `/diagnoses`, `/presentations`, `/compare` | `src/app/(search-app)/differentials/` | | `/dsm`, `/dsm/search`, `/dsm/compare`, `/dsm/diagnoses/[slug]` | `src/app/(search-app)/dsm/` | | `/documents/search`, `/source`, `/evidence`, `/[id]` | `src/app/(search-app)/documents/` | -| `/factsheets`, `/factsheets/search`, `/factsheets/[slug]` | `src/app/(search-app)/factsheets/` | +| `/factsheets`, `/factsheets/search`, `/factsheets/topics`, `/factsheets/[slug]` | `src/app/(search-app)/factsheets/` | | `/dictionary`, Terms (`/search`, one catalogue — `/browse` redirects to it), Topics, Definition, Compare, Sources | `src/app/(search-app)/dictionary/` | | `/favourites` | `src/app/(search-app)/favourites/page.tsx` | | `/forms`, `/forms/[slug]` | `src/app/(search-app)/forms/` | diff --git a/docs/design-system/ADOPTION.md b/docs/design-system/ADOPTION.md index bc6e4ac868..eab2d2ccd2 100644 --- a/docs/design-system/ADOPTION.md +++ b/docs/design-system/ADOPTION.md @@ -357,12 +357,12 @@ product exclusions; the only route-only dispositions are the documented legacy-r surfaces. Shared shell/component roots carry their own explicit `shared-shell` disposition. Registered public components: 54 -Declared product roots: 95 +Declared product roots: 96 Roots with a literal `.ckb-v2` opt-in: 1 -Roots inheriting `.ckb-v2` from the global ``: 94 +Roots inheriting `.ckb-v2` from the global ``: 95 Production surfaces observed under v2: 17/17 Dynamic `ckb-v2` constructions: 0 -Declared production page routes: 80/80 +Declared production page routes: 81/81 Source observation and contract declaration are independent. A literal `ckb-v2` on the global `` makes every production surface inherit v2, but it does not approve that adoption. The Proof column summarizes each surface's dark, forced-colours, 320px, print and browser declarations; exact statuses and evidence paths live in the manifest. @@ -371,7 +371,7 @@ Observed v2 under a compatibility declaration fails closed. A declared v2 shell | Surface | Disposition | Routes | Roots | Declared shell | Observed shell (mount) | Proof | Baseline | | ----------------------------------------------- | --------------- | -----: | ----: | -------------- | -------------------------- | -------------- | -------------- | | `root-shell-and-settings` | shared-shell | 3 | 6 | v2 | v2 (inherited-global-root) | passed | not-committed | -| `catalogues-forms-and-info` | owned | 23 | 23 | v2 | v2 (inherited-global-root) | passed | not-committed | +| `catalogues-forms-and-info` | owned | 24 | 24 | v2 | v2 (inherited-global-root) | passed | not-committed | | `differentials` | owned | 7 | 7 | v2 | v2 (inherited-global-root) | passed | not-committed | | `formulation` | owned | 6 | 6 | v2 | v2 (inherited-global-root) | passed | not-committed | | `specifiers` | owned | 6 | 6 | v2 | v2 (inherited-global-root) | passed | not-committed | diff --git a/docs/design-system/adoption-contract.json b/docs/design-system/adoption-contract.json index 448e525908..ee8b576869 100644 --- a/docs/design-system/adoption-contract.json +++ b/docs/design-system/adoption-contract.json @@ -138,6 +138,7 @@ "src/app/(search-app)/factsheets/[slug]/page.tsx", "src/app/(search-app)/factsheets/page.tsx", "src/app/(search-app)/factsheets/search/page.tsx", + "src/app/(search-app)/factsheets/topics/page.tsx", "src/app/(search-app)/forms/[slug]/page.tsx", "src/app/(search-app)/forms/page.tsx", "src/app/(search-app)/forms/search/page.tsx", diff --git a/docs/design-system/adoption-manifest.json b/docs/design-system/adoption-manifest.json index 7d1fe42fca..161f81fcc4 100644 --- a/docs/design-system/adoption-manifest.json +++ b/docs/design-system/adoption-manifest.json @@ -2270,6 +2270,7 @@ "src/app/(search-app)/factsheets/[slug]/page.tsx", "src/app/(search-app)/factsheets/page.tsx", "src/app/(search-app)/factsheets/search/page.tsx", + "src/app/(search-app)/factsheets/topics/page.tsx", "src/app/(search-app)/forms/[slug]/page.tsx", "src/app/(search-app)/forms/page.tsx", "src/app/(search-app)/forms/search/page.tsx", @@ -2476,6 +2477,16 @@ "v2MountMode": "inherited-global-root", "sanctionedPatternsPresent": ["SearchPage"] }, + { + "file": "src/app/(search-app)/factsheets/topics/page.tsx", + "exists": true, + "imports": [], + "importedFamilies": [], + "literalCkbV2": false, + "dynamicCkbV2": false, + "v2MountMode": "inherited-global-root", + "sanctionedPatternsPresent": [] + }, { "file": "src/app/(search-app)/forms/[slug]/page.tsx", "exists": true, @@ -4004,6 +4015,7 @@ "src/app/(search-app)/factsheets/[slug]/page.tsx", "src/app/(search-app)/factsheets/page.tsx", "src/app/(search-app)/factsheets/search/page.tsx", + "src/app/(search-app)/factsheets/topics/page.tsx", "src/app/(search-app)/favourites/page.tsx", "src/app/(search-app)/forms/[slug]/page.tsx", "src/app/(search-app)/forms/page.tsx", @@ -4086,6 +4098,7 @@ "src/app/(search-app)/factsheets/[slug]/page.tsx", "src/app/(search-app)/factsheets/page.tsx", "src/app/(search-app)/factsheets/search/page.tsx", + "src/app/(search-app)/factsheets/topics/page.tsx", "src/app/(search-app)/favourites/page.tsx", "src/app/(search-app)/forms/[slug]/page.tsx", "src/app/(search-app)/forms/page.tsx", @@ -4143,7 +4156,7 @@ }, "adoption": { "literalCkbV2RootCount": 1, - "inheritedCkbV2RootCount": 94, + "inheritedCkbV2RootCount": 95, "dynamicCkbV2RootCount": 0, "v2MountedSurfaceCount": 17, "declaredV2SurfaceCount": 17 @@ -4152,8 +4165,8 @@ "registeredComponentCount": 54, "previewCount": 54, "productImportedComponentCount": 34, - "rootCount": 95, - "productionRouteCount": 80, - "nextUiEntryCount": 141 + "rootCount": 96, + "productionRouteCount": 81, + "nextUiEntryCount": 142 } } diff --git a/docs/factsheets-reading-model-brief.md b/docs/factsheets-reading-model-brief.md index b8b899d53a..b8eedabcc6 100644 --- a/docs/factsheets-reading-model-brief.md +++ b/docs/factsheets-reading-model-brief.md @@ -10,13 +10,18 @@ Future patient-content work **extends the existing Factsheets reading model** (E | Piece | Location / behaviour | | --------------------------- | -------------------------------------------------------------------------------- | -| Mode home / search / detail | `/factsheets`, `/factsheets/search`, `/factsheets/[slug]` | +| Mode home / search / detail | `/factsheets`, `/factsheets/search`, `/factsheets/topics`, `/factsheets/[slug]` | +| ModeNav order | Search, then Topics — Dictionary’s Terms then Topics pattern | +| Topics browse | Stacked topic directory, one topic open at a time, first 8 rows then Show all | +| Phone study | `/mockups/factsheets-topics-phone` — directory / all-open / hundred-item stress | | Reading levels | `easy` \| `standard` toggle on med-rich detail (`factsheet-detail-page.tsx`) | | Content fields | `whatEasy` / `whatStandard` (and related section bodies) in `factsheets-data.ts` | | Presentation contracts | Existing accessibility, print/PDF, and theme tokens on the Factsheets surface | The Easy Read / Standard control is already a first-class reading-level switch, not a separate product mode. +Topics is a browse page, not a second search. The shell composer stays the only search field. ModeNav is Search then Topics. The four categories are a stacked directory — tap a topic to open its sheets, with the others closed — so a phone does not get a horizontal chip rail or a dump list. `?topic=` opens that topic on load; unknown values leave every topic closed. An open topic paints the first eight rows, then a wired Show all control, so a later catalogue of hundreds of sheets does not dump one list. Extra topics become extra directory rows. Phone alternatives live at `/mockups/factsheets-topics-phone` and stay design scratch. + ## Why not a second mode - A second mode would duplicate search chrome, nav, sitemap, reachability, and source-governance obligations. diff --git a/docs/site-map.md b/docs/site-map.md index 0ba5dcc2ae..99f5aaeca8 100644 --- a/docs/site-map.md +++ b/docs/site-map.md @@ -30,6 +30,7 @@ This file is generated by `npm run docs:update` (or `npm run sitemap:update` dir - `/dsm/search` - DSM diagnosis search and catalogue browser. Source: `src/app/(search-app)/dsm/search/page.tsx`. - `/factsheets` - Compatibility redirect to the shared home at `/?mode=factsheets`; a submitted `?q=…&run=1` forwards to `/factsheets/search`. Source: `src/app/(search-app)/factsheets/page.tsx`. - `/factsheets/search` - Patient information factsheet search command centre. Source: `src/app/(search-app)/factsheets/search/page.tsx`. +- `/factsheets/topics` - Patient information factsheets organised by topic. Source: `src/app/(search-app)/factsheets/topics/page.tsx`. - `/favourites` - Saved clinical items and sets. Source: `src/app/(search-app)/favourites/page.tsx`. - `/forms` - Compatibility redirect to the shared home at `/?mode=forms`; a submitted `?q=…&run=1` forwards to `/forms/search`. Source: `src/app/(search-app)/forms/page.tsx`. - `/forms/search` - Route discovered from app directory Source: `src/app/(search-app)/forms/search/page.tsx`. @@ -105,7 +106,7 @@ This file is generated by `npm run docs:update` (or `npm run sitemap:update` dir | Medication | `/medications` | `/?mode=prescribing&q=acamprosate+renal+dose&focus=1&run=1` | `/medications/[slug]`; submitted searches resolve to `/?mode=prescribing&q=…&run=1`. | | Tools | `/tools` | `/tools?q=medications&focus=1&run=1` | Canonical all-tools results directory at `/tools`; the universal mode picker opens it directly. `/?mode=tools` remains a dashboard-mode alias. | | Calculators | `/?mode=calculators` | `/calculators/search?q=PHQ-9&focus=1&run=1` | `/calculators/search` is the browsable calculator catalogue and scored-results surface; an empty query lists every calculator. | -| Factsheets | `/?mode=factsheets` | `/factsheets/search?q=sertraline&focus=1&run=1` | `/factsheets/search` is also a query-free browse surface linked from the mode nav; `/factsheets/[slug]` records. | +| Factsheets | `/?mode=factsheets` | `/factsheets/search?q=sertraline&focus=1&run=1` | `/factsheets/search` is the query-and-filter surface; `/factsheets/topics` organises the library by category; `/factsheets/[slug]` records. | | Dictionary | `/?mode=dictionary` | `/dictionary/search?q=MSE&focus=1&run=1` | `/dictionary/search` is one catalogue for both searching and browsing; `/dictionary/browse` redirects to it. Also `/topics`, `/topics/[slug]`, `/compare`, `/sources` and `/dictionary/[slug]` records. | | Therapy Compass | `/?mode=therapy-compass` | `/therapy-compass/search?q=CBT&focus=1&run=1` | Keeps a home of its own at `/therapy-compass`; `/search` (query-free browse), `/recommend`, `/compare`, `/pathways`, `/review`, and `/[slug]` records with `/brief` and `/sheet` outputs. | @@ -1224,6 +1225,7 @@ This file is generated by `npm run docs:update` (or `npm run sitemap:update` dir - `/mockups/document-search/source/evidence` - Route discovered from app directory Source: `src/app/mockups/document-search/source/evidence/page.tsx`. - `/mockups/document-top-navigation` - Route discovered from app directory Source: `src/app/mockups/document-top-navigation/page.tsx`. - `/mockups/factsheets-compact-view` - Route discovered from app directory Source: `src/app/mockups/factsheets-compact-view/page.tsx`. +- `/mockups/factsheets-topics-phone` - Route discovered from app directory Source: `src/app/mockups/factsheets-topics-phone/page.tsx`. - `/mockups/favourites-command-console` - Route discovered from app directory Source: `src/app/mockups/favourites-command-console/page.tsx`. - `/mockups/favourites-command-desk` - Route discovered from app directory Source: `src/app/mockups/favourites-command-desk/page.tsx`. - `/mockups/favourites-hub` - Route discovered from app directory Source: `src/app/mockups/favourites-hub/page.tsx`. diff --git a/scripts/generate-site-map.ts b/scripts/generate-site-map.ts index 58766c5b15..221bb48f0c 100644 --- a/scripts/generate-site-map.ts +++ b/scripts/generate-site-map.ts @@ -120,9 +120,10 @@ const routeDescriptions: Record = { "/dsm/diagnoses/[slug]": "DSM diagnosis criteria and information.", "/dsm/diagnoses/[slug]/differentials": "DSM diagnosis differential considerations.", "/dsm/search": "DSM diagnosis search and catalogue browser.", - "/factsheets": "Patient information factsheets home and topic browser.", + "/factsheets": "Compatibility redirect to the shared Factsheets home.", "/factsheets/[slug]": "Plain-language patient factsheet reading and printable handout view.", "/factsheets/search": "Patient information factsheet search command centre.", + "/factsheets/topics": "Patient information factsheets organised by topic.", "/favourites": "Saved clinical items and sets.", "/forms": "Forms home and search surface.", "/forms/[slug]": "Registry-backed form detail.", @@ -510,7 +511,7 @@ function renderModePageIndex() { home: appModeHomeHref("factsheets"), search: appModeHomeHref("factsheets", { query: "sertraline", focus: true, run: true }), detail: - "`/factsheets/search` is also a query-free browse surface linked from the mode nav; `/factsheets/[slug]` records.", + "`/factsheets/search` is the query-and-filter surface; `/factsheets/topics` organises the library by category; `/factsheets/[slug]` records.", }, { mode: "Dictionary", diff --git a/src/app/(search-app)/factsheets/topics/page.tsx b/src/app/(search-app)/factsheets/topics/page.tsx new file mode 100644 index 0000000000..ba1150bbbb --- /dev/null +++ b/src/app/(search-app)/factsheets/topics/page.tsx @@ -0,0 +1,23 @@ +import type { Metadata } from "next"; + +import { FactsheetsTopicsPage } from "@/components/factsheets/factsheets-topics-page"; +import { resolveFactsheetTopicParam } from "@/components/factsheets/factsheets-data"; + +export const metadata: Metadata = { + title: "Factsheet topics | Clinical KB", + description: "Browse patient information factsheets organised by topic.", +}; + +function firstValue(value?: string | string[]) { + return Array.isArray(value) ? value[0] : value; +} + +export default async function FactsheetsTopicsRoute({ + searchParams, +}: { + searchParams: Promise<{ topic?: string | string[] }>; +}) { + const params = await searchParams; + const selectedTopic = resolveFactsheetTopicParam(firstValue(params.topic)); + return ; +} diff --git a/src/app/globals.css b/src/app/globals.css index 3f15ea7116..90f53bf967 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -98,6 +98,7 @@ --radius-lg: 0.75rem; --radius-xl: 1rem; --radius-2xl: 1.25rem; + --radius-phone-frame: 1.85rem; /* Fully-round role (chips, pills, avatars). Call sites carried a bare `, 9999px` fallback before this was declared. */ --radius-pill: 9999px; @@ -116,6 +117,11 @@ the edge-to-edge dock keeps its height (see the max-width: 430px block, which covers the composer input as well as the icon/send buttons). */ --spacing-tap: 3rem; + --spacing-phone-frame: 52.75rem; + + /* Shared reading measure and the fixed geometry of the Factsheets phone study. */ + --container-reading: 64rem; + --container-phone-frame: 24.375rem; /* Specifier-map layout roles: jump target height, numbered step column, and wide-screen detail rail. These stay together so the page cannot drift back diff --git a/src/app/mockups/factsheets-topics-phone/page.tsx b/src/app/mockups/factsheets-topics-phone/page.tsx new file mode 100644 index 0000000000..31b9320c15 --- /dev/null +++ b/src/app/mockups/factsheets-topics-phone/page.tsx @@ -0,0 +1,5 @@ +import { FactsheetsTopicsPhoneMockupsPage } from "@/components/factsheets/factsheets-topics-phone-mockups"; + +export default function FactsheetsTopicsPhoneMockupRoute() { + return ; +} diff --git a/src/app/mockups/mockups-layout-client.tsx b/src/app/mockups/mockups-layout-client.tsx index 077c1c6830..3ec23d2e8b 100644 --- a/src/app/mockups/mockups-layout-client.tsx +++ b/src/app/mockups/mockups-layout-client.tsx @@ -103,6 +103,9 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) { // single search slot of its own. It is not a search mode, and its synthetic // patient context must never enter shared search. const isCarePlanMockup = pathname === "/mockups/care-plan" || pathname.startsWith("/mockups/care-plan/"); + // Draws its own phone frames with ModeNav Search | Topics and a docked composer, + // so shared chrome would read as a second real header and a second search bar. + const isFactsheetsTopicsPhoneMockup = pathname === "/mockups/factsheets-topics-phone"; return ( {children} diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx index 2d98868f61..a249828eb3 100644 --- a/src/components/clinical-dashboard/master-search-header.tsx +++ b/src/components/clinical-dashboard/master-search-header.tsx @@ -66,6 +66,7 @@ import { appModeDefinitions, appModeSelectionHref, appModeSearchConfig, + factsheetsTopicsHref, isSearchableAppMode, visibleAppModeDefinitionsForSession, type AppModeId, @@ -771,8 +772,7 @@ export function MasterSearchHeader({ return; } if (actionId === "factsheets-browse") { - onSearchModeChange("factsheets"); - onQueryChange(""); + router.push(factsheetsTopicsHref); return; } if (actionId === "dictionary-search") { diff --git a/src/components/factsheets/factsheet-detail-page.tsx b/src/components/factsheets/factsheet-detail-page.tsx index 6c70f11140..f2269cbe16 100644 --- a/src/components/factsheets/factsheet-detail-page.tsx +++ b/src/components/factsheets/factsheet-detail-page.tsx @@ -19,6 +19,7 @@ import { createPortal } from "react-dom"; import { categoryTheme, FACTSHEET_DEMO_NOTICE, + factsheetDetailHref, printBlocks, relatedFactsheets, sameTopicFactsheets, @@ -148,7 +149,7 @@ export function FactsheetDetailPage({ factsheet }: { factsheet: Factsheet }) { )} /> -
+
{/* hero band */}
@@ -261,7 +262,7 @@ export function FactsheetDetailPage({ factsheet }: { factsheet: Factsheet }) { return ( + + {factsheetGlyph(sheet.icon, compact ? "h-4 w-4" : "h-5 w-5")} + + + + + {sheet.title} + {sheet.brand ? {sheet.brand} : null} + + {compact ? null : ( + + {sheet.category} + + )} + + + {sheet.summary} + + + {sheet.audience} · {sheet.readTime} + + +