From 4beb08fae35db1e6a8f87e407d93602d76f6bad1 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 11 Jul 2026 02:29:21 +0800 Subject: [PATCH 01/11] fix: resolve design and UX review findings --- docs/branch-review-ledger.md | 1 + package.json | 4 +- scripts/check-type-scale.mjs | 2 +- src/app/globals.css | 2 + src/components/ClinicalDashboard.tsx | 58 ++++-- .../account-setup-dialog.tsx | 2 +- .../clinical-output-helpers.tsx | 2 +- .../clinical-dashboard/evidence-panels.tsx | 6 +- .../global-mockup-search-shell.tsx | 35 +++- .../master-search-header.tsx | 14 +- .../medication-prescribing-workspace.tsx | 2 +- .../medication-record-page.tsx | 2 +- .../clinical-dashboard/settings-dialog.tsx | 2 +- src/components/mode-home-template.tsx | 12 +- .../source-overlay-redesign-mockups.tsx | 10 +- src/components/ui-primitives.tsx | 2 +- src/lib/app-modes.ts | 6 +- src/lib/document-flow-routes.ts | 5 +- src/lib/search-navigation-context.ts | 192 ++++++++++++++++++ tests/app-modes.test.ts | 16 ++ tests/document-flow-routes.test.ts | 16 ++ tests/search-navigation-context.test.ts | 54 +++++ 22 files changed, 390 insertions(+), 55 deletions(-) create mode 100644 src/lib/search-navigation-context.ts create mode 100644 tests/search-navigation-context.test.ts diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 50b0489a8..839d634d7 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -31,3 +31,4 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD | 2026-07-11 | codex/architecture-review-integration | b45df727b29aad8ba4ec5d4e96d1f0599d7dad8a | branch-integration-review | Replayed the reviewed architecture fixes onto current `origin/main`; preserved current CI/autofix history and found no new high-confidence defect in the integrated diff. | `npm run check:runtime`; `npm run check:github-actions`; `npm run sitemap:check`; `npm run lint`; `npm run typecheck`; focused Vitest (24 passed); full Vitest with `--testTimeout=30000` (1,433 passed, 1 skipped); `git diff --check` | | 2026-07-11 | codex/architecture-review-integration | 665103250ccc33b5870862b8d8467607a1ae5d23 | coderabbit-followup | Fixed POSIX project-root identity collisions and closed dynamic-import and self-cycle gaps in the architecture regression guard. | Local-server Vitest passed; architecture-boundaries Vitest passed (6 tests); `npm run typecheck`; focused Prettier; `git diff --check` | | 2026-07-11 | codex/architecture-review-followup | f5deaaee98864f1d32c1060ae14966a4f5975872 | coderabbit-test-followup | Removed probabilistic no-collision assertions from the local identity test and replaced them with deterministic normalization, repeatability, ID-shape, and port-range checks. | Local-server Vitest (2 passed); focused Prettier; `git diff --check`; hosted CI/SAST/Secret Scan passed on the reviewed head | +| 2026-07-10 | codex/design-ux-review-fixes | 648abfa3f7c91395b5eeca543f70e0b6ea59e9e0 | design-system + UX + design | Five issue groups confirmed; scoped fixes applied in the worktree. | `npm run check:type-scale`; focused Vitest (19/19); `npm run typecheck`; `npm run lint`; `npm run sitemap:check`; browser/API-backed checks awaiting approval | diff --git a/package.json b/package.json index 5b9d1e9e7..830a4b8c2 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "test:e2e:advisory": "node scripts/run-playwright.mjs --project=chromium --grep-invert @critical", "test:e2e:chromium": "node scripts/run-playwright.mjs --project=chromium", "test:e2e:visual": "node scripts/run-playwright.mjs --config=playwright.visual.config.ts", - "verify:cheap": "npm run check:runtime && npm run check:github-actions && npm run sitemap:check && npm run lint && npm run typecheck && npm run test", + "verify:cheap": "npm run check:runtime && npm run check:github-actions && npm run sitemap:check && npm run check:type-scale && npm run lint && npm run typecheck && npm run test", "verify:pr-local": "node scripts/verify-pr-local.mjs", "verify:ui": "npm run check:runtime && npm run test:e2e:chromium", "verify:release": "npm run check:runtime && npm run lint && npm run typecheck && npm run test && npm run build && npm run test:e2e && npm run check:production-readiness && npm run governance:release && npm run eval:quality:release", @@ -68,7 +68,7 @@ "check:indexing": "tsx scripts/check-indexing.ts", "check:m13-migration": "tsx scripts/check-m13-migration.ts", "check:july8-live-batch": "tsx scripts/check-july8-live-batch.ts", - "check:type-scale": "node scripts/check-type-scale.mjs", + "check:type-scale": "node scripts/check-type-scale.mjs --strict", "recover:ingestion": "tsx scripts/recover-ingestion-queue.ts", "registry:seed": "tsx scripts/seed-registry-records.ts", "registry:embed": "tsx scripts/embed-registry-records.ts", diff --git a/scripts/check-type-scale.mjs b/scripts/check-type-scale.mjs index 9276d487f..67bf46a2d 100644 --- a/scripts/check-type-scale.mjs +++ b/scripts/check-type-scale.mjs @@ -3,7 +3,7 @@ // (e.g. text-[12px], text-[1.45rem]) that bypass the design type scale. // // The scale lives in the @theme block of src/app/globals.css: named steps -// text-4xs … text-2xl-minus, on top of Tailwind's default xs/sm/base/lg/xl/2xl. +// text-4xs … text-3xl-minus, on top of Tailwind's default xs/sm/base/lg/xl/2xl/3xl. // Arbitrary text-[] values re-introduce off-scale sizes; this check // tracks that drift. Colour utilities (text-[color:var(--…)]) are the sanctioned // token-access form and are intentionally NOT flagged. diff --git a/src/app/globals.css b/src/app/globals.css index e7962a80b..4c24cbc66 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -67,6 +67,7 @@ --text-base-minus: 0.9375rem; --text-lg-minus: 1.0625rem; --text-2xl-minus: 1.375rem; + --text-3xl-minus: 1.625rem; /* Font families: bind Tailwind's font-sans / font-mono to the loaded Geist faces (variables set on by next/font). font-mono is used for @@ -234,6 +235,7 @@ --shadow-elevated: 0 1px 2px rgb(12 24 34 / 8%), 0 10px 20px rgb(12 24 34 / 9%), 0 24px 48px rgb(12 24 34 / 10%); --shadow-lux: inset 0 1px 0 rgb(255 255 255 / 64%), 0 12px 34px rgb(8 16 24 / 7%); --shadow-inset: inset 0 1px 0 rgb(255 255 255 / 58%); + --shadow-card: var(--shadow-tight); --ring-focus: 0 0 0 3px color-mix(in srgb, var(--focus) 30%, transparent); --space-1: 0.25rem; --space-2: 0.5rem; diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx index aab12a9d0..22cb28e4d 100644 --- a/src/components/ClinicalDashboard.tsx +++ b/src/components/ClinicalDashboard.tsx @@ -154,6 +154,7 @@ import { type AppModeSearchKind, } from "@/lib/app-modes"; import { documentsSearchHref } from "@/lib/document-flow-routes"; +import { readSearchNavigationContext } from "@/lib/search-navigation-context"; import { rankFormRecords } from "@/lib/forms"; import { rankServiceRecords } from "@/lib/services"; import { useRegistryRecords } from "@/lib/use-registry-records"; @@ -653,6 +654,7 @@ export function ClinicalDashboard({ }: { initialSearchMode?: AppModeId; initialQuery?: string; focusSearch?: boolean; autoRunSearch?: boolean } = {}) { const router = useRouter(); const searchParams = useSearchParams(); + const [initialSearchNavigationContext] = useState(() => readSearchNavigationContext(searchParams)); const mainRef = useRef(null); const [mainScrollRoot, setMainScrollRoot] = useState(null); const assignMainRef = useCallback((node: HTMLElement | null) => { @@ -709,7 +711,7 @@ export function ClinicalDashboard({ const [documentMatches, setDocumentMatches] = useState([]); const [searchRelevance, setSearchRelevance] = useState(null); const [searchFacets, setSearchFacets] = useState(null); - const [queryMode, setQueryMode] = useState("auto"); + const [queryMode, setQueryMode] = useState(initialSearchNavigationContext.queryMode); const activeModeSearch = appModeSearchConfig(searchMode); const activeModeResultKind = appModeResultKind(searchMode); const requestQueryMode = appModeQueryMode(searchMode, queryMode); @@ -827,7 +829,7 @@ export function ClinicalDashboard({ setAnswerProgress(null); setDifferentialEvidenceQuery(null); }, [resetAnswerThread]); - const [scopeFilters, setScopeFilters] = useState({}); + const [scopeFilters, setScopeFilters] = useState(initialSearchNavigationContext.scopeFilters); const [searchScope, setSearchScope] = useState(null); const [sourceGovernanceWarnings, setSourceGovernanceWarnings] = useState([]); const [answerViewMode, setAnswerViewMode] = useState("high_yield"); @@ -1585,6 +1587,9 @@ export function ClinicalDashboard({ const searchParamString = searchParams.toString(); if (lastSyncedSearchParamsRef.current === searchParamString) return; lastSyncedSearchParamsRef.current = searchParamString; + const nextSearchContext = readSearchNavigationContext(new URLSearchParams(searchParamString)); + setQueryMode(nextSearchContext.queryMode); + setScopeFilters(nextSearchContext.scopeFilters); if (searchParams.get("run") === "1") return; const mode = searchParams.get("mode"); @@ -2099,7 +2104,16 @@ export function ClinicalDashboard({ // Keep only the latest question in the URL; the full thread lives in // React state until refresh or New chat. modeChangeFromUiRef.current = true; - window.history.replaceState(null, "", appModeHomeHref(targetMode, { query: trimmedQuery, run: true })); + window.history.replaceState( + null, + "", + appModeHomeHref(targetMode, { + query: trimmedQuery, + run: true, + queryMode, + scopeFilters: filtersOverride, + }), + ); if (isAnswerFollowUp) { window.requestAnimationFrame(() => { const main = mainRef.current; @@ -2137,14 +2151,16 @@ export function ClinicalDashboard({ setAnswerProgress(null); rememberRecentQuery(trimmedSearchText); window.requestAnimationFrame(() => mainRef.current?.scrollTo({ top: 0, behavior: "smooth" })); - if (updateUrl) router.replace(appModeHomeHref("prescribing", { query: trimmedSearchText })); + if (updateUrl) { + router.replace(appModeHomeHref("prescribing", { query: trimmedSearchText, queryMode, scopeFilters })); + } } async function ask(searchText = query) { const trimmedQuery = searchText.trim(); if (searchMode === "documents" && trimmedQuery) { rememberRecentQuery(trimmedQuery); - router.push(documentsSearchHref({ query: trimmedQuery, focus: true, run: true })); + router.push(documentsSearchHref({ query: trimmedQuery, focus: true, run: true, queryMode, scopeFilters })); return; } if (searchMode === "prescribing") { @@ -2219,7 +2235,7 @@ export function ClinicalDashboard({ setMedicationSearchQuery(crossQuery); } setSearchMode(mode); - router.push(appModeHomeHref(mode, { query: crossQuery, focus: true, run: true })); + router.push(appModeHomeHref(mode, { query: crossQuery, focus: true, run: true, queryMode, scopeFilters })); } async function submitAnswerFeedback(feedbackType: AnswerFeedbackType) { @@ -2303,8 +2319,16 @@ export function ClinicalDashboard({ window.requestAnimationFrame(() => mainRef.current?.scrollTo({ top: 0, behavior: "smooth" })); } - function updateDocumentSearchUrl(searchText: string, mode: AppModeId = "documents") { - window.history.replaceState(null, "", appModeHomeHref(mode, { query: searchText })); + function updateDocumentSearchUrl( + searchText: string, + mode: AppModeId = "documents", + filtersOverride: SearchScopeFilters = scopeFilters, + ) { + window.history.replaceState( + null, + "", + appModeHomeHref(mode, { query: searchText, queryMode, scopeFilters: filtersOverride }), + ); } async function runDocumentSearchShortcut( @@ -2324,7 +2348,17 @@ export function ClinicalDashboard({ setAnswerProgress(null); rememberRecentQuery(trimmedSearchText); window.requestAnimationFrame(() => mainRef.current?.scrollTo({ top: 0, behavior: "smooth" })); - if (updateUrl) router.push(documentsSearchHref({ query: trimmedSearchText, focus: true, run: true })); + if (updateUrl) { + router.push( + documentsSearchHref({ + query: trimmedSearchText, + focus: true, + run: true, + queryMode, + scopeFilters: filtersOverride, + }), + ); + } return; } if (!canRunSearch) { @@ -2349,7 +2383,7 @@ export function ClinicalDashboard({ setAnswerViewMode("high_yield"); rememberRecentQuery(trimmedSearchText); window.requestAnimationFrame(() => mainRef.current?.scrollTo({ top: 0, behavior: "smooth" })); - if (updateUrl) updateDocumentSearchUrl(trimmedSearchText, targetMode); + if (updateUrl) updateDocumentSearchUrl(trimmedSearchText, targetMode, filtersOverride); const requestId = ++searchRequestSeqRef.current; @@ -2479,7 +2513,7 @@ export function ClinicalDashboard({ setSourceGovernanceWarnings([]); setDocumentMatches([]); setSearchMode(mode); - router.push(appModeHomeHref(mode)); + router.push(appModeHomeHref(mode, { queryMode, scopeFilters })); } function focusComposerInput() { @@ -3408,7 +3442,7 @@ export function ClinicalDashboard({ onClearQuery={() => { setQuery(""); setModeSearchSubmitted(false); - router.replace(appModeHomeHref("favourites", { focus: true })); + router.replace(appModeHomeHref("favourites", { focus: true, queryMode, scopeFilters })); }} onAddFavourite={() => setActionNotice({ tone: "success", message: "Favourite creation is ready to connect." }) diff --git a/src/components/clinical-dashboard/account-setup-dialog.tsx b/src/components/clinical-dashboard/account-setup-dialog.tsx index 9f5b7f790..e60942d87 100644 --- a/src/components/clinical-dashboard/account-setup-dialog.tsx +++ b/src/components/clinical-dashboard/account-setup-dialog.tsx @@ -117,7 +117,7 @@ export function AccountSetupDialog({ open, onClose }: { open: boolean; onClose:

Set up your workspace

diff --git a/src/components/clinical-dashboard/clinical-output-helpers.tsx b/src/components/clinical-dashboard/clinical-output-helpers.tsx index c9d784fb2..aa0177236 100644 --- a/src/components/clinical-dashboard/clinical-output-helpers.tsx +++ b/src/components/clinical-dashboard/clinical-output-helpers.tsx @@ -184,7 +184,7 @@ export function AnswerViewModeControl({ aria-label={`Show ${mode.label.toLowerCase()} answer view`} title={mode.label} className={cn( - "inline-flex min-h-9 min-w-0 flex-1 basis-[4.75rem] items-center justify-center gap-1.5 rounded-md px-2 text-xs font-semibold transition sm:flex-none sm:basis-auto sm:px-2.5", + "inline-flex min-h-tap min-w-0 flex-1 basis-[4.75rem] items-center justify-center gap-1.5 rounded-md px-2 text-xs font-semibold transition sm:flex-none sm:basis-auto sm:px-2.5 lg:min-h-9", active ? "bg-[color:var(--primary)] text-[color:var(--primary-contrast)] shadow-sm" : "text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text)]", diff --git a/src/components/clinical-dashboard/evidence-panels.tsx b/src/components/clinical-dashboard/evidence-panels.tsx index f930d10cd..60581db88 100644 --- a/src/components/clinical-dashboard/evidence-panels.tsx +++ b/src/components/clinical-dashboard/evidence-panels.tsx @@ -685,7 +685,7 @@ export function ClinicalNotesChecklistPanel({ @@ -1043,7 +1043,7 @@ export function MasterSearchHeader({
- + Label filters {activeLabelFilterCount ? `${activeLabelFilterCount} active` : "Medication, site, action, intent"} @@ -1055,7 +1055,7 @@ export function MasterSearchHeader({ diff --git a/src/components/clinical-dashboard/medication-prescribing-workspace.tsx b/src/components/clinical-dashboard/medication-prescribing-workspace.tsx index 35786ae0d..5e6404c0c 100644 --- a/src/components/clinical-dashboard/medication-prescribing-workspace.tsx +++ b/src/components/clinical-dashboard/medication-prescribing-workspace.tsx @@ -311,7 +311,7 @@ function MedicationResults({

Medication search

-

+

{resultCount} prescribing matches

diff --git a/src/components/clinical-dashboard/medication-record-page.tsx b/src/components/clinical-dashboard/medication-record-page.tsx index 816c52b3c..6e5411b60 100644 --- a/src/components/clinical-dashboard/medication-record-page.tsx +++ b/src/components/clinical-dashboard/medication-record-page.tsx @@ -185,7 +185,7 @@ function MedicationRecordDetail({