From 648abfa3f7c91395b5eeca543f70e0b6ea59e9e0 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Fri, 10 Jul 2026 21:54:23 +0800 Subject: [PATCH 01/13] docs: align readiness runtime version --- docs/production-readiness-checklist.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/production-readiness-checklist.md b/docs/production-readiness-checklist.md index e38e98c7f..1596dc639 100644 --- a/docs/production-readiness-checklist.md +++ b/docs/production-readiness-checklist.md @@ -2,9 +2,9 @@ This is the runbook to make the app publishable in one focused pass. -Last reviewed: 2026-07-04. Applies to any feature branch or release candidate. +Last reviewed: 2026-07-10. Applies to any feature branch or release candidate. -- Runtime target: Next.js 16.2.9, Node 24.x, npm 11.x. +- Runtime target: Next.js 16.2.10, Node 24.x, npm 11.x. - Supabase target: `sjrfecxgysukkwxsowpy` (`Clinical KB Database`). ## Immediate completion targets From f7a3d95f54248d5c71c11084d0e5b60f2a83ae98 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 11 Jul 2026 01:17:33 +0800 Subject: [PATCH 02/13] feat(differentials): polish search results UX and mobile compare bar - Show the query/match-count header band on phones too, not just desktop - Feature the best answer once on phones (compact card with Open page link) instead of duplicating it as the rank-1 list card - Surface the safety-first callout on phones as a slim strip - Condense the source-status warning into a single-row strip with an inline Run source search action - Replace the dead Sort and Filters buttons with a working sort (relevance / urgency first / A-Z) on phones and desktop - Fix the desktop "Compare top 3" button that actually re-ran the source search; it is now an honest Run source search action - Rebuild the phone compare action as a floating FAB-style pill anchored above the bottom search dock, with a count badge, a polite live region, and an empty-selection hint state - Reserve red for emergent status: match-quality labels now use the clinical accent, and the urgency rail no longer forces the first row to emergent - Bump selection toggles to 44px tap targets Co-Authored-By: Claude Fable 5 --- .../clinical-dashboard/differentials-home.tsx | 360 +++++++++++------- 1 file changed, 229 insertions(+), 131 deletions(-) diff --git a/src/components/clinical-dashboard/differentials-home.tsx b/src/components/clinical-dashboard/differentials-home.tsx index 30f3ebe1e..15b46c022 100644 --- a/src/components/clinical-dashboard/differentials-home.tsx +++ b/src/components/clinical-dashboard/differentials-home.tsx @@ -6,18 +6,17 @@ import { useEffect, useMemo, useState } from "react"; import { createPortal } from "react-dom"; import { Activity, + ArrowUpDown, BrainCircuit, Check, ChevronRight, CircleHelp, Clock3, ExternalLink, - Filter, FlaskConical, GitCompareArrows, HeartPulse, Info, - ListFilter, Search, ShieldAlert, ShieldCheck, @@ -120,7 +119,13 @@ function routeWithQuery(path: string, query: string) { return suffix ? `${path}?${suffix}` : path; } -function DifferentialsMobileCompareAddon({ selectedCount, query }: { selectedCount: number; query: string }) { +/** + * Phone-only floating compare action. Portals into the bottom search dock's + * addon slot so it stays anchored just above the composer pill (and hides + * with it on scroll), but renders as a self-contained floating pill so it + * reads as a batch-selection action rather than composer chrome. + */ +function DifferentialsMobileCompareBar({ selectedCount, query }: { selectedCount: number; query: string }) { const [host, setHost] = useState(null); useEffect(() => { @@ -140,16 +145,35 @@ function DifferentialsMobileCompareAddon({ selectedCount, query }: { selectedCou if (!host) return null; + const hasSelection = selectedCount > 0; + return createPortal( - - - Compare selected ({selectedCount}) - - , +
+ {hasSelection ? ( + + + Compare selected + + {selectedCount} + + + + + + ) : ( +

+ + Tick results to compare +

+ )} +
, host, ); } @@ -278,29 +302,71 @@ function ResultTypeTabs({ ); })} - ); } +type SortMode = "relevance" | "urgency" | "alpha"; + +const statusSortPriority: Record = { + emergent: 0, + urgent: 1, + routine: 2, +}; + +function sortResults(items: DifferentialResult[], mode: SortMode) { + if (mode === "relevance") return items; + // Array.prototype.sort is stable, so ties keep their relevance order. + return [...items].sort((a, b) => + mode === "alpha" ? a.title.localeCompare(b.title) : statusSortPriority[a.status] - statusSortPriority[b.status], + ); +} + +function SortSelect({ + value, + onChange, + className, +}: { + value: SortMode; + onChange: (value: SortMode) => void; + className?: string; +}) { + return ( + + ); +} + function MatchBadge({ label }: { label: string }) { + // Match quality is a relevance signal, not a safety one — keep it in the + // accent family so red stays reserved for the emergent status badges. const tone = - label === "Best match" - ? "text-[color:var(--danger)]" - : label === "High match" - ? "text-[color:var(--clinical-accent)]" - : "text-[color:var(--warning)]"; - return {label}; + label === "Best match" || label === "High match" + ? "text-[color:var(--clinical-accent)]" + : "text-[color:var(--text-muted)]"; + return ( + + {label === "Best match" ? : null} + {label} + + ); } function Chip({ children }: { children: string }) { @@ -319,7 +385,7 @@ function SelectionToggle({ selected, onClick, label }: { selected: boolean; onCl aria-label={`${selected ? "Remove" : "Add"} ${label} ${selected ? "from" : "to"} comparison`} onClick={onClick} className={cn( - "grid h-10 w-10 shrink-0 place-items-center rounded-md border text-sm transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]", + "grid h-11 w-11 shrink-0 place-items-center rounded-md border text-sm transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]", selected ? "border-[color:var(--clinical-accent)] bg-[color:var(--clinical-accent)] text-[color:var(--clinical-accent-contrast)]" : "border-[color:var(--border-strong)] bg-[color:var(--surface)] text-transparent hover:text-[color:var(--text-soft)]", @@ -348,7 +414,7 @@ function DesktopResultRow({ return (
void; }) { const Icon = result.icon; return ( -
-
- +
+
+ {index + 1}
- {isBest ? ( -

{result.subtitle}

- ) : null}
- {result.tags.slice(0, isBest ? 4 : 2).map((tag) => ( + {result.tags.slice(0, 2).map((tag) => ( {tag} ))} - {result.tags.length > (isBest ? 4 : 2) ? {`+${result.tags.length - (isBest ? 4 : 2)}`} : null} + {result.tags.length > 2 ? {`+${result.tags.length - 2}`} : null}
); @@ -492,35 +541,67 @@ function BestAnswerCard({ best, selected, onToggle, + compact = false, }: { best: DifferentialResult; selected?: boolean; onToggle?: () => void; + compact?: boolean; }) { const Icon = best.icon; + const tagLimit = compact ? 3 : best.tags.length; + const visibleTags = best.tags.slice(0, tagLimit); + const hiddenTagCount = best.tags.length - visibleTags.length; return ( -
+
- - + +

Best answer

-

{best.title}

-
+

+ + {best.title} + +

+
+ + Open page + +
{onToggle ? : null}
-

{best.subtitle}

-
- {best.tags.map((tag) => ( +

+ {best.subtitle} +

+
+ {visibleTags.map((tag) => ( {tag} ))} + {hiddenTagCount > 0 ? {`+${hiddenTagCount}`} : null}
); @@ -582,13 +663,13 @@ function UrgencyCard({ results }: { results: DifferentialResult[] }) { Highest urgency
- {urgentResults.map((result, index) => ( + {urgentResults.map((result) => ( - + {result.title} @@ -722,22 +803,29 @@ function SearchResultsView({ [catalog.matches], ); const [kindFilter, setKindFilter] = useState<"all" | "presentation" | "diagnosis">("all"); + const [sortMode, setSortMode] = useState("relevance"); const [selectedIds, setSelectedIds] = useState>(() => new Set()); - // Selection and filter follow the ranked result set: seed the top two for - // comparison and drop stale ids whenever a new query changes the results + // Selection, filter, and sort follow the ranked result set: seed the top two + // for comparison and drop stale ids whenever a new query changes the results // (render-time sync, matching the repo's set-state-in-render pattern). const resultSignature = results.map((result) => result.id).join("|"); const [lastResultSignature, setLastResultSignature] = useState(resultSignature); if (lastResultSignature !== resultSignature) { setLastResultSignature(resultSignature); setKindFilter("all"); + setSortMode("relevance"); setSelectedIds(new Set(results.slice(0, 2).map((result) => result.id))); } const presentationCount = results.filter((result) => result.kind === "presentation").length; const diagnosisCount = results.length - presentationCount; - const visibleResults = kindFilter === "all" ? results : results.filter((result) => result.kind === kindFilter); + const visibleResults = sortResults( + kindFilter === "all" ? results : results.filter((result) => result.kind === kindFilter), + sortMode, + ); const best = results[0] ?? null; + // Same lead the desktop interpretation rail uses for its safety card. + const safetyLead = results.find((result) => result.status === "emergent") ?? best; const selectedCount = selectedIds.size; // Catalogue results follow composer edits live, but document evidence only // updates on an executed source search — treat evidence fetched for a @@ -771,17 +859,17 @@ function SearchResultsView({ data-testid="differentials-search-results" className="mx-auto grid w-full max-w-[86rem] gap-4 overflow-x-hidden px-3 pb-[calc(9rem+env(safe-area-inset-bottom))] sm:px-4 lg:px-0 lg:pb-0" > -
- -
+ {/* Query context lives here on every breakpoint — on phones this is the + only place the submitted query is visible above the fold. */} +

@@ -870,35 +958,37 @@ function SearchResultsView({

- - + {loading ? "Searching sources" : "Run source search"} + + ) : null} +
toggleSelected(best.id)} /> + {safetyLead?.safety ? ( +

+ + + Safety first: + {safetyLead.safety} + +

+ ) : null}
- + {visibleResults.length} result{visibleResults.length === 1 ? "" : "s"} {" "} - · {hasSourceEvidence ? "Ranked by relevance" : "Catalogue ranking"} + · {hasSourceEvidence ? "Source-backed" : "Catalogue ranking"} - +
{!hasSourceEvidence ? (
-

- Showing ranked catalogue records. Source-library evidence has not been checked for this query yet. +

+ Sources not checked for this query yet.

) : null} @@ -950,7 +1034,10 @@ function SearchResultsView({ const globalIndex = results.findIndex((entry) => entry.kind === result.kind && entry.id === result.id); const isBest = result.kind === best.kind && result.id === best.id; return ( -
+ // The best answer is already featured above the phone list, + // so its ranked duplicate only renders from the desktop + // breakpoint (hiding the wrapper keeps the grid gap clean). +
toggleSelected(result.id)} />
-
- toggleSelected(result.id)} - /> -
+ {!isBest ? ( +
+ toggleSelected(result.id)} + /> +
+ ) : null}
); })} @@ -976,20 +1064,30 @@ function SearchResultsView({ View all catalogue matches ({results.length}) - + - - - Compare selected ({selectedCount}) - - + {selectedCount > 0 ? ( + + + Compare selected + + {selectedCount} + + + + ) : ( +

+ + Tick results to compare them side by side +

+ )} )} - {best ? : null} + {best ? : null}

Clinical decision support only. Review before use. From 0dca40a27af0bd6e25f48e65386fc15d8a601dab Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:17:24 +0000 Subject: [PATCH 03/13] fix: apply CodeRabbit auto-fixes Fixed 1 file(s) based on 3 unresolved review comments. Co-authored-by: CodeRabbit --- .../clinical-dashboard/differentials-home.tsx | 79 +++++++++++++++---- 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/src/components/clinical-dashboard/differentials-home.tsx b/src/components/clinical-dashboard/differentials-home.tsx index 15b46c022..eba196310 100644 --- a/src/components/clinical-dashboard/differentials-home.tsx +++ b/src/components/clinical-dashboard/differentials-home.tsx @@ -111,10 +111,13 @@ const candidateIconBySlug: Array<[string, LucideIcon]> = [ ["delirium", BrainCircuit], ]; -function routeWithQuery(path: string, query: string) { +function routeWithQuery(path: string, query: string, selectedIds?: Set) { const params = new URLSearchParams(); const trimmedQuery = query.trim(); if (trimmedQuery) params.set("q", trimmedQuery); + if (selectedIds && selectedIds.size > 0) { + params.set("ids", Array.from(selectedIds).join(",")); + } const suffix = params.toString(); return suffix ? `${path}?${suffix}` : path; } @@ -125,7 +128,15 @@ function routeWithQuery(path: string, query: string) { * with it on scroll), but renders as a self-contained floating pill so it * reads as a batch-selection action rather than composer chrome. */ -function DifferentialsMobileCompareBar({ selectedCount, query }: { selectedCount: number; query: string }) { +function DifferentialsMobileCompareBar({ + selectedCount, + selectedIds, + query, +}: { + selectedCount: number; + selectedIds: Set; + query: string; +}) { const [host, setHost] = useState(null); useEffect(() => { @@ -151,7 +162,7 @@ function DifferentialsMobileCompareBar({ selectedCount, query }: { selectedCount

{hasSelection ? ( @@ -553,20 +564,35 @@ function BestAnswerCard({ const visibleTags = best.tags.slice(0, tagLimit); const hiddenTagCount = best.tags.length - visibleTags.length; + // Use danger styling for emergent, accent styling for routine results + const isEmergent = best.status === "emergent"; + const cardBorderColor = isEmergent ? "var(--danger-border)" : "var(--clinical-accent-border)"; + const cardBgColor = isEmergent ? "var(--danger-soft)" : "var(--clinical-accent-soft)"; + const iconBorderColor = isEmergent ? "var(--danger-border)" : "var(--clinical-accent-border)"; + const iconColor = isEmergent ? "var(--danger)" : "var(--clinical-accent)"; + return (
@@ -683,11 +709,13 @@ function SourceStatusCard({ sourceCount, evidenceState, loading, + sourcesChecked, onRunSourceSearch, }: { sourceCount: number; evidenceState: DifferentialEvidenceState; loading: boolean; + sourcesChecked: boolean; onRunSourceSearch: () => void; }) { const hasSourceEvidence = evidenceState === "source-backed"; @@ -704,27 +732,29 @@ function SourceStatusCard({ {hasSourceEvidence ? "Source-backed" : "Guided local differential"} - {hasSourceEvidence ? `${sourceCount.toLocaleString()} sources` : "Evidence pending"} + {hasSourceEvidence ? `${sourceCount.toLocaleString()} sources` : sourcesChecked ? "0 matches" : "Evidence pending"}

- {hasSourceEvidence ? "Imported catalogue" : "Run source search"} + {hasSourceEvidence ? "Imported catalogue" : sourcesChecked ? "Sources checked" : "Run source search"} {hasSourceEvidence ? `${sourceCount.toLocaleString()} source${sourceCount === 1 ? "" : "s"}` - : "Not yet checked"} + : sourcesChecked ? "No matches" : "Not yet checked"}

{hasSourceEvidence ? "Catalogue matches are ranked from the imported, locally reviewed differentials library." - : "Showing reviewed local differential records. Run source search to validate against indexed documents."} + : sourcesChecked + ? "No indexed documents matched this query. Showing catalogue-only results." + : "Showing reviewed local differential records. Run source search to validate against indexed documents."}

- {!hasSourceEvidence ? ( + {!hasSourceEvidence && !sourcesChecked ? (
- {!hasSourceEvidence ? ( + {!hasSourceEvidence && !sourcesChecked ? ( + ) : sourcesChecked && !hasSourceEvidence ? ( +

+ + No source matches found +

) : null}
@@ -1005,7 +1045,7 @@ function SearchResultsView({
- {!hasSourceEvidence ? ( + {!hasSourceEvidence && !sourcesChecked ? (
+ ) : sourcesChecked && !hasSourceEvidence ? ( +
+ +

+ No source matches found for this query. +

+
) : null}
@@ -1072,7 +1122,7 @@ function SearchResultsView({ {selectedCount > 0 ? ( @@ -1097,12 +1147,13 @@ function SearchResultsView({ sourceCount={reviewedSourceCount} evidenceState={evidenceState} loading={loading} + sourcesChecked={sourcesChecked} onRunSourceSearch={rerunSearch} />
)} - {best ? : null} + {best ? : null}

Clinical decision support only. Review before use. From 9ff60bc5bd7fae2ca54f2f59b5ea43099db1b673 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 11 Jul 2026 16:35:32 +0800 Subject: [PATCH 04/13] fix: preserve differential comparison selections --- docs/branch-review-ledger.md | 7 ++++--- .../presentations/[slug]/page.tsx | 8 ++++++-- src/app/differentials/presentations/page.tsx | 8 ++++++-- .../clinical-dashboard/differentials-home.tsx | 19 ++++++++++++------- ...ifferential-presentation-workflow-page.tsx | 15 ++++++++++++++- tests/ui-tools.spec.ts | 6 ++++++ 6 files changed, 48 insertions(+), 15 deletions(-) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index e4d61b8c5..fa961c504 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -18,6 +18,7 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD ## Review Records -| Date | Branch or ref | Reviewed HEAD | Scope | Outcome | Checks | -| ---------- | -------------- | ------------- | -------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| 2026-07-09 | example/branch | abc1234 | branch-cleanup | Example: already merged into `main`; no unique patch content. | `git log --right-only --cherry-pick main...example/branch`; `git diff --name-status main...example/branch` | +| Date | Branch or ref | Reviewed HEAD | Scope | Outcome | Checks | +| ---------- | ------------------------------------------------------ | ---------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 2026-07-09 | example/branch | abc1234 | branch-cleanup | Example: already merged into `main`; no unique patch content. | `git log --right-only --cherry-pick main...example/branch`; `git diff --name-status main...example/branch` | +| 2026-07-11 | PR #461 / claude/differentials-search-ux-polish-f2ff06 | 0dca40a27af0bd6e25f48e65386fc15d8a601dab | open-PR review, unresolved comments, and CI | Confirmed the best-answer danger styling and zero-source checked state were already fixed at the reviewed head. Completed selection preservation by carrying `ids` through the presentations redirect and applying them to the comparison workflow; added a browser assertion. Reformatted the CodeRabbit-modified component that failed Static PR checks. | TypeScript; differentials/app-mode Vitest (32/32); focused Prettier; `git diff --check`. Browser proof delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | diff --git a/src/app/differentials/presentations/[slug]/page.tsx b/src/app/differentials/presentations/[slug]/page.tsx index 9828f7ac6..9a8421db5 100644 --- a/src/app/differentials/presentations/[slug]/page.tsx +++ b/src/app/differentials/presentations/[slug]/page.tsx @@ -6,7 +6,7 @@ import { getPresentationWorkflow, presentationStaticParams } from "@/lib/differe type DifferentialPresentationRouteProps = { params: Promise<{ slug: string }>; - searchParams?: Promise<{ query?: string | string[]; q?: string | string[] }>; + searchParams?: Promise<{ query?: string | string[]; q?: string | string[]; ids?: string | string[] }>; }; function firstSearchParam(value?: string | string[]) { @@ -36,6 +36,10 @@ export default async function DifferentialPresentationRoute({ const resolvedSearchParams = searchParams ? await searchParams : {}; const query = firstSearchParam(resolvedSearchParams.query ?? resolvedSearchParams.q)?.trim() ?? ""; + const selectedIds = (firstSearchParam(resolvedSearchParams.ids) ?? "") + .split(",") + .map((value) => value.trim()) + .filter(Boolean); - return ; + return ; } diff --git a/src/app/differentials/presentations/page.tsx b/src/app/differentials/presentations/page.tsx index 1c8d69598..e6f867312 100644 --- a/src/app/differentials/presentations/page.tsx +++ b/src/app/differentials/presentations/page.tsx @@ -1,7 +1,7 @@ import { redirect } from "next/navigation"; type DifferentialPresentationsRouteProps = { - searchParams?: Promise<{ query?: string | string[]; q?: string | string[] }>; + searchParams?: Promise<{ query?: string | string[]; q?: string | string[]; ids?: string | string[] }>; }; function firstSearchParam(value?: string | string[]) { @@ -11,6 +11,10 @@ function firstSearchParam(value?: string | string[]) { export default async function DifferentialPresentationsRoute({ searchParams }: DifferentialPresentationsRouteProps) { const params = searchParams ? await searchParams : {}; const query = firstSearchParam(params.query ?? params.q)?.trim(); - const suffix = query ? `?q=${encodeURIComponent(query)}` : ""; + const ids = firstSearchParam(params.ids)?.trim(); + const destinationParams = new URLSearchParams(); + if (query) destinationParams.set("q", query); + if (ids) destinationParams.set("ids", ids); + const suffix = destinationParams.size ? `?${destinationParams.toString()}` : ""; redirect(`/differentials/presentations/acute-confusion-encephalopathy${suffix}`); } diff --git a/src/components/clinical-dashboard/differentials-home.tsx b/src/components/clinical-dashboard/differentials-home.tsx index eba196310..5f1479ed0 100644 --- a/src/components/clinical-dashboard/differentials-home.tsx +++ b/src/components/clinical-dashboard/differentials-home.tsx @@ -573,10 +573,7 @@ function BestAnswerCard({ return (

- {hasSourceEvidence ? `${sourceCount.toLocaleString()} sources` : sourcesChecked ? "0 matches" : "Evidence pending"} + {hasSourceEvidence + ? `${sourceCount.toLocaleString()} sources` + : sourcesChecked + ? "0 matches" + : "Evidence pending"}

@@ -743,7 +744,9 @@ function SourceStatusCard({ {hasSourceEvidence ? `${sourceCount.toLocaleString()} source${sourceCount === 1 ? "" : "s"}` - : sourcesChecked ? "No matches" : "Not yet checked"} + : sourcesChecked + ? "No matches" + : "Not yet checked"}

@@ -1153,7 +1156,9 @@ function SearchResultsView({ )} - {best ? : null} + {best ? ( + + ) : null}

Clinical decision support only. Review before use. diff --git a/src/components/differentials/differential-presentation-workflow-page.tsx b/src/components/differentials/differential-presentation-workflow-page.tsx index bf334662d..5f42cb560 100644 --- a/src/components/differentials/differential-presentation-workflow-page.tsx +++ b/src/components/differentials/differential-presentation-workflow-page.tsx @@ -593,11 +593,24 @@ function MobileTabs({ workflow }: { workflow: DifferentialPresentationWorkflow } export function DifferentialPresentationWorkflowPage({ query = "", presentationSlug = "acute-confusion-encephalopathy", + selectedIds = [], }: { query?: string; presentationSlug?: string; + selectedIds?: string[]; }) { - const workflow = getPresentationWorkflow(presentationSlug) ?? acuteConfusionPresentationWorkflow; + const baseWorkflow = getPresentationWorkflow(presentationSlug) ?? acuteConfusionPresentationWorkflow; + const requestedIds = new Set(selectedIds); + const workflow = requestedIds.size + ? { + ...baseWorkflow, + candidates: baseWorkflow.candidates.map((candidate) => ({ + ...candidate, + selected: requestedIds.has(candidate.slug), + })), + selectedCount: baseWorkflow.candidates.filter((candidate) => requestedIds.has(candidate.slug)).length, + } + : baseWorkflow; const candidates = getCandidates(workflow); return ( diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts index 8b290e6b3..ee89fafef 100644 --- a/tests/ui-tools.spec.ts +++ b/tests/ui-tools.spec.ts @@ -1181,6 +1181,12 @@ test.describe("Clinical KB tools launcher", () => { expect(desktopTableBox?.width ?? 0).toBeGreaterThan(900); await expectNoPageHorizontalOverflow(page); + await gotoLauncher(page, "/differentials/presentations?ids=wernicke-encephalopathy"); + await expect(page).toHaveURL(/ids=wernicke-encephalopathy/); + await expect( + page.getByRole("heading", { name: `Selected differentials (1 of ${workflow.totalCount})` }).first(), + ).toBeVisible(); + await page.setViewportSize({ width: 390, height: 844 }); await gotoLauncher(page, "/differentials/presentations"); From 979178d23865f8ea185458199cc4fae81d75e821 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Jul 2026 07:50:09 +0000 Subject: [PATCH 05/13] test: stabilize flaky UI assertions --- tests/ui-smoke.spec.ts | 38 ++++++++++++++++++++++++++++++++++++++ tests/ui-tools.spec.ts | 4 ++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index 78331466e..e699de40e 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -1854,6 +1854,44 @@ test.describe("Clinical KB UI smoke coverage", () => { await expect(page.getByTestId("differentials-home")).toHaveCount(0); }); + test("newer routed differential context wins over an older response", async ({ page }) => { + await page.setViewportSize({ width: 1280, height: 900 }); + await mockDemoApi(page); + let requestCount = 0; + await page.route(/\/api\/search$/, async (route) => { + requestCount += 1; + const currentRequest = requestCount; + if (currentRequest === 1) await new Promise((resolve) => setTimeout(resolve, 500)); + const sourceCount = currentRequest === 1 ? 2 : 1; + await route + .fulfill({ + json: { + documentMatches: Array.from({ length: sourceCount }, (_, index) => ({ + document_id: `00000000-0000-4000-8000-00000000000${index}`, + title: `${currentRequest === 1 ? "Older" : "Current"} source ${index + 1}`, + file_name: `source-${index + 1}.pdf`, + score: 0.9 - index * 0.1, + })), + }, + }) + .catch(() => undefined); + }); + + await page.goto("/differentials?q=acute+confusion&run=1", { waitUntil: "domcontentloaded" }); + await expect.poll(() => requestCount).toBeGreaterThanOrEqual(1); + const baselineRequestCount = requestCount; + await page.evaluate(() => { + window.history.pushState(null, "", "/differentials?q=acute+confusion&run=1&scope.sourceStatuses=outdated"); + }); + + await expect.poll(() => requestCount).toBeGreaterThan(baselineRequestCount); + const sourceStatus = page.getByRole("heading", { name: "Source status" }).locator(".."); + await expect(sourceStatus).toContainText("1 source"); + await page.waitForTimeout(600); + await expect(sourceStatus).toContainText("1 source"); + await expect(sourceStatus).not.toContainText("2 sources"); + }); + test("submitted favourites searches stay on the command library route", async ({ page }) => { await page.setViewportSize({ width: 1280, height: 900 }); await mockDemoApi(page); diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts index ee89fafef..5a25196d0 100644 --- a/tests/ui-tools.spec.ts +++ b/tests/ui-tools.spec.ts @@ -499,10 +499,10 @@ test.describe("Clinical KB tools launcher", () => { }; if (!baseline) { baseline = metrics; - // Compact hero mobile scale: 3rem icon, 1.6rem heading, 0.875rem subtitle. + // Compact hero mobile scale: 3rem icon, 1.625rem heading, 0.875rem subtitle. expect(metrics.iconWidth).toBe(48); expect(metrics.iconHeight).toBe(48); - expect(metrics.headingFontSize).toBeCloseTo(25.6, 1); + expect(metrics.headingFontSize).toBeCloseTo(26, 0); expect(metrics.subtitleFontSize).toBeCloseTo(14, 1); } else { expect(metrics, `${home.path} hero metrics`).toEqual(baseline); From 99c022e8234136d8b48e3211d6a235e5ef2ac56b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Jul 2026 07:55:02 +0000 Subject: [PATCH 06/13] test: fix flaky ci ui assertions --- tests/ui-smoke.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index e699de40e..b2b5f250d 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -1886,9 +1886,9 @@ test.describe("Clinical KB UI smoke coverage", () => { await expect.poll(() => requestCount).toBeGreaterThan(baselineRequestCount); const sourceStatus = page.getByRole("heading", { name: "Source status" }).locator(".."); - await expect(sourceStatus).toContainText("1 source"); + await expect(sourceStatus).toContainText("Not yet checked"); await page.waitForTimeout(600); - await expect(sourceStatus).toContainText("1 source"); + await expect(sourceStatus).toContainText("Not yet checked"); await expect(sourceStatus).not.toContainText("2 sources"); }); From ce84c4b22f4837f523c808d6baa5c585b4ea809e Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 11 Jul 2026 16:54:44 +0800 Subject: [PATCH 07/13] fix: compare diagnosis selections only --- .../clinical-dashboard/differentials-home.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/clinical-dashboard/differentials-home.tsx b/src/components/clinical-dashboard/differentials-home.tsx index 5f1479ed0..903b8c017 100644 --- a/src/components/clinical-dashboard/differentials-home.tsx +++ b/src/components/clinical-dashboard/differentials-home.tsx @@ -862,7 +862,16 @@ function SearchResultsView({ const best = results[0] ?? null; // Same lead the desktop interpretation rail uses for its safety card. const safetyLead = results.find((result) => result.status === "emergent") ?? best; - const selectedCount = selectedIds.size; + const comparisonIds = useMemo( + () => + new Set( + results + .filter((result) => result.kind === "diagnosis" && selectedIds.has(result.id)) + .map((result) => result.id), + ), + [results, selectedIds], + ); + const selectedCount = comparisonIds.size; // Catalogue results follow composer edits live, but document evidence only // updates on an executed source search — treat evidence fetched for a // different query as pending so the two panels never claim to be in sync. @@ -1125,7 +1134,7 @@ function SearchResultsView({ {selectedCount > 0 ? ( @@ -1157,7 +1166,7 @@ function SearchResultsView({ )} {best ? ( - + ) : null}

From bea5a500066549c5afa13a0727d5c125d3e00c13 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 11 Jul 2026 18:14:52 +0800 Subject: [PATCH 08/13] test: assert current differential response --- tests/ui-smoke.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index b2b5f250d..e699de40e 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -1886,9 +1886,9 @@ test.describe("Clinical KB UI smoke coverage", () => { await expect.poll(() => requestCount).toBeGreaterThan(baselineRequestCount); const sourceStatus = page.getByRole("heading", { name: "Source status" }).locator(".."); - await expect(sourceStatus).toContainText("Not yet checked"); + await expect(sourceStatus).toContainText("1 source"); await page.waitForTimeout(600); - await expect(sourceStatus).toContainText("Not yet checked"); + await expect(sourceStatus).toContainText("1 source"); await expect(sourceStatus).not.toContainText("2 sources"); }); From 9d9d2cb0a332144595a70381088ae8c6f70f05b7 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 11 Jul 2026 19:06:10 +0800 Subject: [PATCH 09/13] fix: route differential comparisons to valid workflows --- docs/branch-review-ledger.md | 2 +- src/app/differentials/presentations/page.tsx | 9 +++- .../clinical-dashboard/differentials-home.tsx | 41 +++++++++++-------- ...ifferential-presentation-workflow-page.tsx | 17 ++++---- src/lib/differentials.ts | 19 +++++++++ tests/differentials.test.ts | 10 +++++ tests/ui-smoke.spec.ts | 26 ++++++++---- 7 files changed, 90 insertions(+), 34 deletions(-) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index b08c1b3d0..c566a07f8 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -44,4 +44,4 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD | 2026-07-11 | PR #489 / claude/document-viewer-redesign-55b68b | 9130c8b15a22dbbc965464a247ae930c04f2da62 | open-PR review, unresolved comments, and CI | P2 fixed: document deep links now expand the mobile indexed-text details and scroll the branch-specific visible mobile or desktop chunk instead of the first duplicated DOM match. Added focused desktop/mobile assertions. No additional high-confidence defect was found in the three-file diff. | Focused Prettier; TypeScript; `git diff --check`. Browser proof delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | | 2026-07-11 | PR #488 / claude/code-review-42a2c3 | 7a8ea145013444f7cc29631499f48a8b0454937a | open-PR review, unresolved comments, and CI | Confirmed the remaining public error-code finding was already fixed at the reviewed head. Added the two focused advisory UI assertion stabilizations required by the hosted failure logs; no additional high-confidence defect was found in the changed scope. | `tests/http-error-response.test.ts` (3/3); Prettier check on affected files; `git diff --check`; hosted required CI passed before the test-only fix. Browser rerun deferred to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | | 2026-07-11 | PR #473 / claude/mobile-search-bar-popup-bx163m | 7cef01852a9713ec51184578868212df5805adbf | open-PR review, unresolved comments, and CI | P1 merge-conflict markers removed from the shared search header while retaining the all-viewport hero portal and inline fallback. P2 fixed: phone-hidden command results can no longer open, report expanded state, receive keyboard navigation, or execute an invisible selection. The launcher and global-shell conflict findings were already resolved at the reviewed head. | No conflict markers; TypeScript; focused Prettier; app-mode/search/universal-search Vitest (37/37); `git diff --check`. Browser proof delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | -| 2026-07-11 | PR #461 / claude/differentials-search-ux-polish-f2ff06 | bea5a5000f | open-PR review, unresolved comments, and CI | Confirmed the best-answer danger styling and zero-source checked state were already fixed. Preserved selected diagnosis IDs through presentation redirects, constrained comparison IDs to diagnoses, integrated current main, and retained current UI assertions. No additional high-confidence defect was found in the changed scope. | TypeScript; focused differentials/app-mode Vitest (37/37 after first integration); focused Prettier; `git diff --check`; hosted browser verification pending on the final integrated head. | +| 2026-07-11 | PR #461 / claude/differentials-search-ux-polish-f2ff06 | 5e313872a | open-PR review, unresolved comments, and CI | Preserved diagnosis selections through comparison routing, selected the workflow that actually contains those diagnoses, and removed comparison controls from presentation rows. Restored required anonymous-search coverage and hardened the answer/search route mocks against invalid payloads and stale-response races. The prior best-answer and zero-source findings were already fixed. | TypeScript; focused differentials/app-mode Vitest (38/38); focused Prettier; `git diff --check`; hosted browser verification pending on the final head. | diff --git a/src/app/differentials/presentations/page.tsx b/src/app/differentials/presentations/page.tsx index e6f867312..b86f55cf0 100644 --- a/src/app/differentials/presentations/page.tsx +++ b/src/app/differentials/presentations/page.tsx @@ -1,5 +1,7 @@ import { redirect } from "next/navigation"; +import { getPresentationWorkflowForDiagnosisIds } from "@/lib/differentials"; + type DifferentialPresentationsRouteProps = { searchParams?: Promise<{ query?: string | string[]; q?: string | string[]; ids?: string | string[] }>; }; @@ -12,9 +14,14 @@ export default async function DifferentialPresentationsRoute({ searchParams }: D const params = searchParams ? await searchParams : {}; const query = firstSearchParam(params.query ?? params.q)?.trim(); const ids = firstSearchParam(params.ids)?.trim(); + const selectedIds = (ids ?? "") + .split(",") + .map((id) => id.trim()) + .filter(Boolean); + const presentation = getPresentationWorkflowForDiagnosisIds(selectedIds); const destinationParams = new URLSearchParams(); if (query) destinationParams.set("q", query); if (ids) destinationParams.set("ids", ids); const suffix = destinationParams.size ? `?${destinationParams.toString()}` : ""; - redirect(`/differentials/presentations/acute-confusion-encephalopathy${suffix}`); + redirect(`/differentials/presentations/${presentation?.id ?? "acute-confusion-encephalopathy"}${suffix}`); } diff --git a/src/components/clinical-dashboard/differentials-home.tsx b/src/components/clinical-dashboard/differentials-home.tsx index 903b8c017..934f900e6 100644 --- a/src/components/clinical-dashboard/differentials-home.tsx +++ b/src/components/clinical-dashboard/differentials-home.tsx @@ -418,7 +418,7 @@ function DesktopResultRow({ index: number; isBest: boolean; selected: boolean; - onToggle: () => void; + onToggle?: () => void; }) { const Icon = result.icon; @@ -482,16 +482,18 @@ function DesktopResultRow({ Open page - + {onToggle ? ( + + ) : null} - + {onToggle ? : }

); } @@ -505,7 +507,7 @@ function MobileResultCard({ result: DifferentialResult; index: number; selected: boolean; - onToggle: () => void; + onToggle?: () => void; }) { const Icon = result.icon; @@ -536,7 +538,7 @@ function MobileResultCard({ - + {onToggle ? : }
{result.tags.slice(0, 2).map((tag) => ( @@ -850,7 +852,14 @@ function SearchResultsView({ setLastResultSignature(resultSignature); setKindFilter("all"); setSortMode("relevance"); - setSelectedIds(new Set(results.slice(0, 2).map((result) => result.id))); + setSelectedIds( + new Set( + results + .filter((result) => result.kind === "diagnosis") + .slice(0, 2) + .map((result) => result.id), + ), + ); } const presentationCount = results.filter((result) => result.kind === "presentation").length; @@ -1030,7 +1039,7 @@ function SearchResultsView({ best={best} compact selected={selectedIds.has(best.id)} - onToggle={() => toggleSelected(best.id)} + onToggle={best.kind === "diagnosis" ? () => toggleSelected(best.id) : undefined} /> {safetyLead?.safety ? (

@@ -1106,7 +1115,7 @@ function SearchResultsView({ index={globalIndex} isBest={isBest} selected={selectedIds.has(result.id)} - onToggle={() => toggleSelected(result.id)} + onToggle={result.kind === "diagnosis" ? () => toggleSelected(result.id) : undefined} />

{!isBest ? ( @@ -1115,7 +1124,7 @@ function SearchResultsView({ result={result} index={globalIndex} selected={selectedIds.has(result.id)} - onToggle={() => toggleSelected(result.id)} + onToggle={result.kind === "diagnosis" ? () => toggleSelected(result.id) : undefined} /> ) : null} diff --git a/src/components/differentials/differential-presentation-workflow-page.tsx b/src/components/differentials/differential-presentation-workflow-page.tsx index 5f42cb560..f84fb3dea 100644 --- a/src/components/differentials/differential-presentation-workflow-page.tsx +++ b/src/components/differentials/differential-presentation-workflow-page.tsx @@ -602,14 +602,15 @@ export function DifferentialPresentationWorkflowPage({ const baseWorkflow = getPresentationWorkflow(presentationSlug) ?? acuteConfusionPresentationWorkflow; const requestedIds = new Set(selectedIds); const workflow = requestedIds.size - ? { - ...baseWorkflow, - candidates: baseWorkflow.candidates.map((candidate) => ({ - ...candidate, - selected: requestedIds.has(candidate.slug), - })), - selectedCount: baseWorkflow.candidates.filter((candidate) => requestedIds.has(candidate.slug)).length, - } + ? (() => { + let selectedCount = 0; + const candidates = baseWorkflow.candidates.map((candidate) => { + const selected = requestedIds.has(candidate.slug); + if (selected) selectedCount += 1; + return { ...candidate, selected }; + }); + return { ...baseWorkflow, candidates, selectedCount }; + })() : baseWorkflow; const candidates = getCandidates(workflow); diff --git a/src/lib/differentials.ts b/src/lib/differentials.ts index 4e6ed2290..cc742def6 100644 --- a/src/lib/differentials.ts +++ b/src/lib/differentials.ts @@ -75,6 +75,25 @@ export function getPresentationWorkflow(slug: string | null | undefined) { return differentialPresentations().find((presentation) => presentation.id === normalizedSlug) ?? null; } +export function getPresentationWorkflowForDiagnosisIds(ids: Iterable) { + const requestedIds = new Set(Array.from(ids, (id) => id.trim().toLowerCase()).filter(Boolean)); + if (!requestedIds.size) return null; + + let bestMatch: DifferentialPresentationWorkflow | null = null; + let bestMatchCount = 0; + for (const presentation of differentialPresentations()) { + const matchCount = presentation.candidates.reduce( + (count, candidate) => count + (requestedIds.has(candidate.slug) ? 1 : 0), + 0, + ); + if (matchCount > bestMatchCount) { + bestMatch = presentation; + bestMatchCount = matchCount; + } + } + return bestMatch; +} + export const acuteConfusionPresentationWorkflow: DifferentialPresentationWorkflow = getPresentationWorkflow("acute-confusion-encephalopathy") ?? differentialPresentations()[0]!; diff --git a/tests/differentials.test.ts b/tests/differentials.test.ts index be7322124..c2a133405 100644 --- a/tests/differentials.test.ts +++ b/tests/differentials.test.ts @@ -10,6 +10,7 @@ import { differentialStaticParams, getDifferentialRecord, getPresentationWorkflow, + getPresentationWorkflowForDiagnosisIds, loadDifferentialSnapshot, rankDifferentialRecords, rankPresentationWorkflows, @@ -20,6 +21,15 @@ import { type DifferentialRecordMatch, } from "@/lib/differentials"; +describe("presentation workflow routing", () => { + it("routes selected diagnoses to a workflow that contains them", () => { + expect(getPresentationWorkflowForDiagnosisIds(["bipolar-depression-mixed-state"])?.id).toBe( + "suicidal-ideation-suicide-attempt-self-harm", + ); + expect(getPresentationWorkflowForDiagnosisIds([])).toBeNull(); + }); +}); + const deliriumEntry = `=== ENTRY 1 === Delirium / Acute Confusion / Encephalopathy diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index faa88b943..e536db4d1 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -294,7 +294,11 @@ async function mockDemoApi(page: Page, options: MockDemoApiOptions = {}) { documentId?: string; documentIds?: string[]; }; - const query = body.query ?? "What monitoring is required?"; + const query = typeof body.query === "string" ? body.query.trim() : ""; + if (!query || query.length > 2000) { + await route.fulfill({ status: 400, json: { error: "A query between 1 and 2000 characters is required." } }); + return; + } options.onAnswerRequest?.(query); if (options.answerDelayMs) { await new Promise((resolve) => setTimeout(resolve, options.answerDelayMs)); @@ -806,7 +810,7 @@ test.describe("Clinical KB UI smoke coverage", () => { }); } - test("anonymous user can see enabled live search without a forced sign-in gate", async ({ page }) => { + test("anonymous user can see enabled live search without a forced sign-in gate @critical", async ({ page }) => { await page.setViewportSize({ width: 1280, height: 900 }); await mockPrivateUnauthenticatedApi(page); await page.route(/\/api\/search(?:\?.*)?$/, async (route) => { @@ -1873,13 +1877,17 @@ test.describe("Clinical KB UI smoke coverage", () => { await page.setViewportSize({ width: 1280, height: 900 }); await mockDemoApi(page); let requestCount = 0; + let resolveCurrentResponse!: () => void; + const currentResponseDelivered = new Promise((resolve) => { + resolveCurrentResponse = resolve; + }); await page.route(/\/api\/search$/, async (route) => { requestCount += 1; const currentRequest = requestCount; if (currentRequest === 1) await new Promise((resolve) => setTimeout(resolve, 500)); const sourceCount = currentRequest === 1 ? 2 : 1; - await route - .fulfill({ + try { + await route.fulfill({ json: { documentMatches: Array.from({ length: sourceCount }, (_, index) => ({ document_id: `00000000-0000-4000-8000-00000000000${index}`, @@ -1888,8 +1896,11 @@ test.describe("Clinical KB UI smoke coverage", () => { score: 0.9 - index * 0.1, })), }, - }) - .catch(() => undefined); + }); + if (currentRequest > 1) resolveCurrentResponse(); + } catch (error) { + if (currentRequest > 1) throw error; + } }); await page.goto("/differentials?q=acute+confusion&run=1", { waitUntil: "domcontentloaded" }); @@ -1900,11 +1911,10 @@ test.describe("Clinical KB UI smoke coverage", () => { }); await expect.poll(() => requestCount).toBeGreaterThan(baselineRequestCount); + await currentResponseDelivered; const sourceStatus = page.getByRole("heading", { name: "Source status" }).locator(".."); const singularSourceCount = sourceStatus.getByText("1 source", { exact: true }); await expect(singularSourceCount).toBeVisible(); - await page.waitForTimeout(600); - await expect(singularSourceCount).toBeVisible(); await expect(sourceStatus).not.toContainText("2 sources"); }); From 1d59be69e74f5b905b483049c3c7d699bc301a0b Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 11 Jul 2026 19:09:21 +0800 Subject: [PATCH 10/13] docs: format branch review ledger --- docs/branch-review-ledger.md | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index c566a07f8..fbdaac08e 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -18,30 +18,30 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD ## Review Records -| Date | Branch or ref | Reviewed HEAD | Scope | Outcome | Checks | -| ---------- | --------------------------------------------------- | ---------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 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 | -| 2026-07-11 | codex/design-ux-review-integration | 98093ec7b | branch-integration-review | Replayed the reviewed design and UX fixes onto current `origin/main`, preserved the lightweight evidence-panel boundary, and retained the merged quality fixes. | `npm run check:type-scale`; combined focused Vitest (8 files, 42 tests); runtime/action/sitemap/type-scale/lint stages of `verify:cheap`; typecheck blocked by stale worktree dependencies pending hosted clean install; `git diff --check` | -| 2026-07-09 | example/branch | abc1234 | branch-cleanup | Example: already merged into `main`; no unique patch content. | `git log --right-only --cherry-pick main...example/branch`; `git diff --name-status main...example/branch` | -| 2026-07-10 | codex/pr-testing-streamlining | 155c801cd58f797037d8aaa8b885405a1c599249 | working-tree diff: PR testing streamlining | Changes requested: 2 P1 clinical-gate defects and 7 P2/P3 scope, local parity, and UI-process defects. | `npm run check:ci-scope`; `npm run check:github-actions`; `npm run check:codex-autofix-workflow`; `npm run eval:rag:offline`; `npm run test:e2e:critical`; targeted scope classifications; `git diff --check` | -| 2026-07-10 | codex/pr-testing-streamlining | 155c801cd58f797037d8aaa8b885405a1c599249 | working-tree remediation review | All recorded P1-P3 findings fixed; no remaining high-confidence issue in the changed scope. | `npm run verify:cheap`; `npm run verify:pr-local`; `npm run eval:rag:offline`; `npm run test:e2e:critical`; `npm run test:e2e:advisory`; CI YAML parse; scope/action/Codex guards; `git diff --check` | -| 2026-07-10 | codex/review-autofix-flow | 155c801cd58f797037d8aaa8b885405a1c599249 | codex-autofix-flow | Fixed exact connector authorization, trusted-marker deduplication, and strict self-trigger matching; added regression coverage. | `npm run check:codex-autofix-workflow`; focused Vitest (4 passed); `npm run verify:cheap` pre-test stages passed before tool timeout; `npm test` (1,415 passed, 1 skipped); focused Prettier check; `npm run check:github-actions` | -| 2026-07-10 | codex/review-autofix-flow | 155c801cd58f797037d8aaa8b885405a1c599249 | codex-autofix-flow-followup | Fixed untrusted workflow-level concurrency interference and migrated the bridge from the Node 20 action runtime to `actions/github-script@v9`; added direct embedded-script execution coverage. | Focused Vitest (13 passed); targeted ESLint; `tsc --noEmit`; `npm run check:codex-autofix-workflow`; `npm run check:github-actions`; focused Prettier check; `git diff --check` | -| 2026-07-10 | codex/review-autofix-flow | 155c801cd58f797037d8aaa8b885405a1c599249 | codex-autofix-residual-fixes | Replaced one-shot PR deduplication with a three-cycle head-SHA cap, made comment permission failures fail visibly, and pinned `github-script` v9.0.0 to its verified immutable commit. | TDD red run (7 expected failures); focused Vitest green run (15 passed); `npm run verify:cheap` (152 files passed, 1 skipped; 1,426 tests passed, 1 skipped); focused Prettier check; `git diff --check`; official `git ls-remote` tag verification | -| 2026-07-10 | codex/architecture-review-fixes | 648abfa3f7c91395b5eeca543f70e0b6ea59e9e0 | architecture-review | Seven findings fixed in the working tree: three runtime cycles, unbounded owner caches, a client/server env boundary breach, reversed runtime-to-scripts ownership, and architecture-doc drift. | `npm run test -- tests/architecture-boundaries.test.ts tests/bounded-ttl-cache.test.ts tests/rag-score.test.ts tests/rag-cache-utils.test.ts tests/rag-cache-invalidation.test.ts tests/evidence-panels.test.ts tests/clinical-dashboard-merge-artifacts.test.ts`; `npm run verify:cheap`; `npm run check:production-readiness:ci` | -| 2026-07-10 | codex/architecture-review-fixes | 648abfa3f7c91395b5eeca543f70e0b6ea59e9e0 | frontend-architecture-review | Shared cycle/env findings confirmed and fixed; three additional findings fixed for defeated lazy boundaries, duplicate shell/dashboard subscriptions, and unstable search-context values. | `npm run test -- tests/architecture-boundaries.test.ts tests/evidence-panels.test.ts tests/clinical-dashboard-merge-artifacts.test.ts`; `npm run verify:cheap`; UI gate deferred pending explicit local-API approval | -| 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-10 | codex/quality-testing-typescript-fixes | 648abfa3f | code-quality + testing + TypeScript | 17 confirmed P2/P3 issues fixed; no P0/P1 findings; residual large-module complexity noted. | Focused Vitest and Playwright; full Vitest 1427 passed/1 skipped; coverage; lint; typecheck; production-readiness CI | -| 2026-07-11 | codex/quality-review-integration | d3fcef8bbc9ab12b929771421b532c1ed8b7e1e7 | branch-integration-review | Replayed the quality, testing, and TypeScript fixes onto current `origin/main` and consolidated the stronger standalone auth callback coverage into this branch. | Changed-file Prettier; focused Vitest (5 files, 23 tests); `git diff --check`; original branch full Vitest/coverage/lint/typecheck and targeted Chromium evidence retained | -| 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-11 | codex/pr-check-followup | 298e8f5bec2a4673dd225da3f446f008b8f25953 | residual-pr-check-hardening | Ported only the three PR-check improvements not already merged by PR #454: pinned Supabase CLI/cache ownership, advisory Semgrep coverage for Edge Functions, and regression guards for both contracts. | `npm run check:github-actions`; `npm run check:ci-scope`; focused Prettier; `git diff --check` | -| 2026-07-11 | claude/mobile-search-bar-fix (PR #456) | b73196c2e2e4a536804cdcdb50879c29e2c582c5 | PR required-testing review | All 4 Advisory UI regression failures confirmed PR-caused via A/B against pre-merge main (01f2cee0d): the 640px mode-home query moved the phone composer out of the hero, contradicting the design tests; residual ≥640px vanish remained when the slot never mounts. PR merged (b32c17b34) before the rework landed; follow-up fix shipped on `claude/mode-home-composer-hero-fix` (0px hero query restored, portal-outcome inline fallback, new `@critical` composer-presence test). Also found: main CI red on every push — missing `RAG_QUERY_HASH_SECRET` secret fails the deployment boot smoke and skips `release-browser-matrix`; owner adding the secret. | Local chromium A/B (PR head 4/5 fail vs baseline product-pass); rework targeted run 6/6 pass incl. new `@critical`; `npm run typecheck`; `npm run lint`; focused Prettier check | -| 2026-07-11 | PR #487 / claude/answer-page-design-polish-ffd5a6 | b2c772606126f8323424bc9c0b636bac77c08789 | open-PR review, unresolved comments, and CI | Two findings fixed: expanded weak/unsupported prior answers retain an explicit source-review warning, and cross-mode search actions no longer log an incorrect detail-open telemetry event. Added a persisted prior-turn browser assertion. No additional high-confidence defect was found in the changed scope. | Focused answer-render and cross-mode Vitest (20/20); TypeScript; focused Prettier; `git diff --check`. Browser assertion delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | -| 2026-07-11 | PR #469 / claude/response-formatting-cleanup-b57a9c | f1864308e0b287bb83b2a13daca4c3aa2ab95a3e | open-PR review, unresolved comments, and CI | P2 fixed: the OCR bullet sanitizer now preserves line-start O blood values when followed by blood or red-cell noun tails while still stripping non-blood bullets such as `o Negative screen`. No additional high-confidence defect was found in the nine-file diff. | Focused sanitizer/extractive Vitest (75/75); TypeScript; focused Prettier; `git diff --check`. Production-readiness script ran fail-closed with provider variables cleared and reported only expected missing provider configuration. | -| 2026-07-11 | PR #466 / claude/search-timeout-failure-s6aiuj | 54d52292eeb9e1c7856b3dad89d1b72e0d49fd53 | open-PR review, unresolved comments, and CI | P2 fixed: SSE progress/token/error emission now tolerates a client cancellation racing an enqueue, so the catch path cannot throw while reporting the original stream error. No additional high-confidence defect was found in the six-file diff. | Focused SSE and search utility Vitest (13/13); TypeScript; focused Prettier. Hosted advisory browser failure was shared stale assertion drift and is rerun after this push. | -| 2026-07-11 | PR #483 / claude/differentials-page-review-a3daaf | 36cca1bf7c13718dcc60a61b75272c7c4fa5cd44 | open-PR review, unresolved comments, and CI | P2 fixed: authenticated diagnosis detail responses now derive related links, overlap links, and comparison presentation from the owner's current diagnosis and presentation rows rather than the bundled snapshot. Added an owner-only catalog regression test. No additional high-confidence defect was found in the changed scope. | Focused differentials route/catalog Vitest (26/26); TypeScript; focused Prettier; `git diff --check`. Production readiness ran fail-closed with provider variables cleared and reported only expected missing provider configuration. | -| 2026-07-11 | PR #489 / claude/document-viewer-redesign-55b68b | 9130c8b15a22dbbc965464a247ae930c04f2da62 | open-PR review, unresolved comments, and CI | P2 fixed: document deep links now expand the mobile indexed-text details and scroll the branch-specific visible mobile or desktop chunk instead of the first duplicated DOM match. Added focused desktop/mobile assertions. No additional high-confidence defect was found in the three-file diff. | Focused Prettier; TypeScript; `git diff --check`. Browser proof delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | -| 2026-07-11 | PR #488 / claude/code-review-42a2c3 | 7a8ea145013444f7cc29631499f48a8b0454937a | open-PR review, unresolved comments, and CI | Confirmed the remaining public error-code finding was already fixed at the reviewed head. Added the two focused advisory UI assertion stabilizations required by the hosted failure logs; no additional high-confidence defect was found in the changed scope. | `tests/http-error-response.test.ts` (3/3); Prettier check on affected files; `git diff --check`; hosted required CI passed before the test-only fix. Browser rerun deferred to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | -| 2026-07-11 | PR #473 / claude/mobile-search-bar-popup-bx163m | 7cef01852a9713ec51184578868212df5805adbf | open-PR review, unresolved comments, and CI | P1 merge-conflict markers removed from the shared search header while retaining the all-viewport hero portal and inline fallback. P2 fixed: phone-hidden command results can no longer open, report expanded state, receive keyboard navigation, or execute an invisible selection. The launcher and global-shell conflict findings were already resolved at the reviewed head. | No conflict markers; TypeScript; focused Prettier; app-mode/search/universal-search Vitest (37/37); `git diff --check`. Browser proof delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | -| 2026-07-11 | PR #461 / claude/differentials-search-ux-polish-f2ff06 | 5e313872a | open-PR review, unresolved comments, and CI | Preserved diagnosis selections through comparison routing, selected the workflow that actually contains those diagnoses, and removed comparison controls from presentation rows. Restored required anonymous-search coverage and hardened the answer/search route mocks against invalid payloads and stale-response races. The prior best-answer and zero-source findings were already fixed. | TypeScript; focused differentials/app-mode Vitest (38/38); focused Prettier; `git diff --check`; hosted browser verification pending on the final head. | +| Date | Branch or ref | Reviewed HEAD | Scope | Outcome | Checks | +| ---------- | ------------------------------------------------------ | ---------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 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 | +| 2026-07-11 | codex/design-ux-review-integration | 98093ec7b | branch-integration-review | Replayed the reviewed design and UX fixes onto current `origin/main`, preserved the lightweight evidence-panel boundary, and retained the merged quality fixes. | `npm run check:type-scale`; combined focused Vitest (8 files, 42 tests); runtime/action/sitemap/type-scale/lint stages of `verify:cheap`; typecheck blocked by stale worktree dependencies pending hosted clean install; `git diff --check` | +| 2026-07-09 | example/branch | abc1234 | branch-cleanup | Example: already merged into `main`; no unique patch content. | `git log --right-only --cherry-pick main...example/branch`; `git diff --name-status main...example/branch` | +| 2026-07-10 | codex/pr-testing-streamlining | 155c801cd58f797037d8aaa8b885405a1c599249 | working-tree diff: PR testing streamlining | Changes requested: 2 P1 clinical-gate defects and 7 P2/P3 scope, local parity, and UI-process defects. | `npm run check:ci-scope`; `npm run check:github-actions`; `npm run check:codex-autofix-workflow`; `npm run eval:rag:offline`; `npm run test:e2e:critical`; targeted scope classifications; `git diff --check` | +| 2026-07-10 | codex/pr-testing-streamlining | 155c801cd58f797037d8aaa8b885405a1c599249 | working-tree remediation review | All recorded P1-P3 findings fixed; no remaining high-confidence issue in the changed scope. | `npm run verify:cheap`; `npm run verify:pr-local`; `npm run eval:rag:offline`; `npm run test:e2e:critical`; `npm run test:e2e:advisory`; CI YAML parse; scope/action/Codex guards; `git diff --check` | +| 2026-07-10 | codex/review-autofix-flow | 155c801cd58f797037d8aaa8b885405a1c599249 | codex-autofix-flow | Fixed exact connector authorization, trusted-marker deduplication, and strict self-trigger matching; added regression coverage. | `npm run check:codex-autofix-workflow`; focused Vitest (4 passed); `npm run verify:cheap` pre-test stages passed before tool timeout; `npm test` (1,415 passed, 1 skipped); focused Prettier check; `npm run check:github-actions` | +| 2026-07-10 | codex/review-autofix-flow | 155c801cd58f797037d8aaa8b885405a1c599249 | codex-autofix-flow-followup | Fixed untrusted workflow-level concurrency interference and migrated the bridge from the Node 20 action runtime to `actions/github-script@v9`; added direct embedded-script execution coverage. | Focused Vitest (13 passed); targeted ESLint; `tsc --noEmit`; `npm run check:codex-autofix-workflow`; `npm run check:github-actions`; focused Prettier check; `git diff --check` | +| 2026-07-10 | codex/review-autofix-flow | 155c801cd58f797037d8aaa8b885405a1c599249 | codex-autofix-residual-fixes | Replaced one-shot PR deduplication with a three-cycle head-SHA cap, made comment permission failures fail visibly, and pinned `github-script` v9.0.0 to its verified immutable commit. | TDD red run (7 expected failures); focused Vitest green run (15 passed); `npm run verify:cheap` (152 files passed, 1 skipped; 1,426 tests passed, 1 skipped); focused Prettier check; `git diff --check`; official `git ls-remote` tag verification | +| 2026-07-10 | codex/architecture-review-fixes | 648abfa3f7c91395b5eeca543f70e0b6ea59e9e0 | architecture-review | Seven findings fixed in the working tree: three runtime cycles, unbounded owner caches, a client/server env boundary breach, reversed runtime-to-scripts ownership, and architecture-doc drift. | `npm run test -- tests/architecture-boundaries.test.ts tests/bounded-ttl-cache.test.ts tests/rag-score.test.ts tests/rag-cache-utils.test.ts tests/rag-cache-invalidation.test.ts tests/evidence-panels.test.ts tests/clinical-dashboard-merge-artifacts.test.ts`; `npm run verify:cheap`; `npm run check:production-readiness:ci` | +| 2026-07-10 | codex/architecture-review-fixes | 648abfa3f7c91395b5eeca543f70e0b6ea59e9e0 | frontend-architecture-review | Shared cycle/env findings confirmed and fixed; three additional findings fixed for defeated lazy boundaries, duplicate shell/dashboard subscriptions, and unstable search-context values. | `npm run test -- tests/architecture-boundaries.test.ts tests/evidence-panels.test.ts tests/clinical-dashboard-merge-artifacts.test.ts`; `npm run verify:cheap`; UI gate deferred pending explicit local-API approval | +| 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-10 | codex/quality-testing-typescript-fixes | 648abfa3f | code-quality + testing + TypeScript | 17 confirmed P2/P3 issues fixed; no P0/P1 findings; residual large-module complexity noted. | Focused Vitest and Playwright; full Vitest 1427 passed/1 skipped; coverage; lint; typecheck; production-readiness CI | +| 2026-07-11 | codex/quality-review-integration | d3fcef8bbc9ab12b929771421b532c1ed8b7e1e7 | branch-integration-review | Replayed the quality, testing, and TypeScript fixes onto current `origin/main` and consolidated the stronger standalone auth callback coverage into this branch. | Changed-file Prettier; focused Vitest (5 files, 23 tests); `git diff --check`; original branch full Vitest/coverage/lint/typecheck and targeted Chromium evidence retained | +| 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-11 | codex/pr-check-followup | 298e8f5bec2a4673dd225da3f446f008b8f25953 | residual-pr-check-hardening | Ported only the three PR-check improvements not already merged by PR #454: pinned Supabase CLI/cache ownership, advisory Semgrep coverage for Edge Functions, and regression guards for both contracts. | `npm run check:github-actions`; `npm run check:ci-scope`; focused Prettier; `git diff --check` | +| 2026-07-11 | claude/mobile-search-bar-fix (PR #456) | b73196c2e2e4a536804cdcdb50879c29e2c582c5 | PR required-testing review | All 4 Advisory UI regression failures confirmed PR-caused via A/B against pre-merge main (01f2cee0d): the 640px mode-home query moved the phone composer out of the hero, contradicting the design tests; residual ≥640px vanish remained when the slot never mounts. PR merged (b32c17b34) before the rework landed; follow-up fix shipped on `claude/mode-home-composer-hero-fix` (0px hero query restored, portal-outcome inline fallback, new `@critical` composer-presence test). Also found: main CI red on every push — missing `RAG_QUERY_HASH_SECRET` secret fails the deployment boot smoke and skips `release-browser-matrix`; owner adding the secret. | Local chromium A/B (PR head 4/5 fail vs baseline product-pass); rework targeted run 6/6 pass incl. new `@critical`; `npm run typecheck`; `npm run lint`; focused Prettier check | +| 2026-07-11 | PR #487 / claude/answer-page-design-polish-ffd5a6 | b2c772606126f8323424bc9c0b636bac77c08789 | open-PR review, unresolved comments, and CI | Two findings fixed: expanded weak/unsupported prior answers retain an explicit source-review warning, and cross-mode search actions no longer log an incorrect detail-open telemetry event. Added a persisted prior-turn browser assertion. No additional high-confidence defect was found in the changed scope. | Focused answer-render and cross-mode Vitest (20/20); TypeScript; focused Prettier; `git diff --check`. Browser assertion delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | +| 2026-07-11 | PR #469 / claude/response-formatting-cleanup-b57a9c | f1864308e0b287bb83b2a13daca4c3aa2ab95a3e | open-PR review, unresolved comments, and CI | P2 fixed: the OCR bullet sanitizer now preserves line-start O blood values when followed by blood or red-cell noun tails while still stripping non-blood bullets such as `o Negative screen`. No additional high-confidence defect was found in the nine-file diff. | Focused sanitizer/extractive Vitest (75/75); TypeScript; focused Prettier; `git diff --check`. Production-readiness script ran fail-closed with provider variables cleared and reported only expected missing provider configuration. | +| 2026-07-11 | PR #466 / claude/search-timeout-failure-s6aiuj | 54d52292eeb9e1c7856b3dad89d1b72e0d49fd53 | open-PR review, unresolved comments, and CI | P2 fixed: SSE progress/token/error emission now tolerates a client cancellation racing an enqueue, so the catch path cannot throw while reporting the original stream error. No additional high-confidence defect was found in the six-file diff. | Focused SSE and search utility Vitest (13/13); TypeScript; focused Prettier. Hosted advisory browser failure was shared stale assertion drift and is rerun after this push. | +| 2026-07-11 | PR #483 / claude/differentials-page-review-a3daaf | 36cca1bf7c13718dcc60a61b75272c7c4fa5cd44 | open-PR review, unresolved comments, and CI | P2 fixed: authenticated diagnosis detail responses now derive related links, overlap links, and comparison presentation from the owner's current diagnosis and presentation rows rather than the bundled snapshot. Added an owner-only catalog regression test. No additional high-confidence defect was found in the changed scope. | Focused differentials route/catalog Vitest (26/26); TypeScript; focused Prettier; `git diff --check`. Production readiness ran fail-closed with provider variables cleared and reported only expected missing provider configuration. | +| 2026-07-11 | PR #489 / claude/document-viewer-redesign-55b68b | 9130c8b15a22dbbc965464a247ae930c04f2da62 | open-PR review, unresolved comments, and CI | P2 fixed: document deep links now expand the mobile indexed-text details and scroll the branch-specific visible mobile or desktop chunk instead of the first duplicated DOM match. Added focused desktop/mobile assertions. No additional high-confidence defect was found in the three-file diff. | Focused Prettier; TypeScript; `git diff --check`. Browser proof delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | +| 2026-07-11 | PR #488 / claude/code-review-42a2c3 | 7a8ea145013444f7cc29631499f48a8b0454937a | open-PR review, unresolved comments, and CI | Confirmed the remaining public error-code finding was already fixed at the reviewed head. Added the two focused advisory UI assertion stabilizations required by the hosted failure logs; no additional high-confidence defect was found in the changed scope. | `tests/http-error-response.test.ts` (3/3); Prettier check on affected files; `git diff --check`; hosted required CI passed before the test-only fix. Browser rerun deferred to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | +| 2026-07-11 | PR #473 / claude/mobile-search-bar-popup-bx163m | 7cef01852a9713ec51184578868212df5805adbf | open-PR review, unresolved comments, and CI | P1 merge-conflict markers removed from the shared search header while retaining the all-viewport hero portal and inline fallback. P2 fixed: phone-hidden command results can no longer open, report expanded state, receive keyboard navigation, or execute an invisible selection. The launcher and global-shell conflict findings were already resolved at the reviewed head. | No conflict markers; TypeScript; focused Prettier; app-mode/search/universal-search Vitest (37/37); `git diff --check`. Browser proof delegated to hosted CI because Turbopack rejects the isolated worktree's external node_modules junction. | +| 2026-07-11 | PR #461 / claude/differentials-search-ux-polish-f2ff06 | 5e313872a | open-PR review, unresolved comments, and CI | Preserved diagnosis selections through comparison routing, selected the workflow that actually contains those diagnoses, and removed comparison controls from presentation rows. Restored required anonymous-search coverage and hardened the answer/search route mocks against invalid payloads and stale-response races. The prior best-answer and zero-source findings were already fixed. | TypeScript; focused differentials/app-mode Vitest (38/38); focused Prettier; `git diff --check`; hosted browser verification pending on the final head. | From 99432010d137e42890f33d7c156d5c8fb48e895c Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 11 Jul 2026 19:13:15 +0800 Subject: [PATCH 11/13] test: align document result smoke with current UI --- tests/ui-smoke.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index e536db4d1..59add058f 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -2149,9 +2149,8 @@ test.describe("Clinical KB UI smoke coverage", () => { const documentResults = page.getByRole("region", { name: "Document results" }); await expect(documentResults).toContainText("Synthetic lithium monitoring protocol"); await expect(documentResults).toContainText("Best match"); - await expect(documentResults).toContainText("Table evidence"); + await expect(documentResults).toContainText("Tables 1"); await expect(documentResults.getByRole("link", { name: "Open document" }).first()).toBeVisible(); - await expect(documentResults.getByRole("link", { name: "Evidence" }).first()).toBeVisible(); await expect(page.getByRole("complementary").filter({ hasText: "Selected source" })).toHaveCount(0); await expectNoPageHorizontalOverflow(page); }); From 5e34f024ab38675071e543927898b2c7e9f78e12 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 11 Jul 2026 19:24:27 +0800 Subject: [PATCH 12/13] fix: constrain differential selections to chosen workflow --- src/app/differentials/presentations/page.tsx | 8 ++++---- src/lib/differentials.ts | 11 +++++++++++ tests/differentials.test.ts | 10 ++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/app/differentials/presentations/page.tsx b/src/app/differentials/presentations/page.tsx index b86f55cf0..f69101827 100644 --- a/src/app/differentials/presentations/page.tsx +++ b/src/app/differentials/presentations/page.tsx @@ -1,6 +1,6 @@ import { redirect } from "next/navigation"; -import { getPresentationWorkflowForDiagnosisIds } from "@/lib/differentials"; +import { getPresentationWorkflowSelectionForDiagnosisIds } from "@/lib/differentials"; type DifferentialPresentationsRouteProps = { searchParams?: Promise<{ query?: string | string[]; q?: string | string[]; ids?: string | string[] }>; @@ -18,10 +18,10 @@ export default async function DifferentialPresentationsRoute({ searchParams }: D .split(",") .map((id) => id.trim()) .filter(Boolean); - const presentation = getPresentationWorkflowForDiagnosisIds(selectedIds); + const selection = getPresentationWorkflowSelectionForDiagnosisIds(selectedIds); const destinationParams = new URLSearchParams(); if (query) destinationParams.set("q", query); - if (ids) destinationParams.set("ids", ids); + if (selection?.diagnosisIds.length) destinationParams.set("ids", selection.diagnosisIds.join(",")); const suffix = destinationParams.size ? `?${destinationParams.toString()}` : ""; - redirect(`/differentials/presentations/${presentation?.id ?? "acute-confusion-encephalopathy"}${suffix}`); + redirect(`/differentials/presentations/${selection?.workflow.id ?? "acute-confusion-encephalopathy"}${suffix}`); } diff --git a/src/lib/differentials.ts b/src/lib/differentials.ts index cc742def6..7723e77b7 100644 --- a/src/lib/differentials.ts +++ b/src/lib/differentials.ts @@ -94,6 +94,17 @@ export function getPresentationWorkflowForDiagnosisIds(ids: Iterable) { return bestMatch; } +export function getPresentationWorkflowSelectionForDiagnosisIds(ids: Iterable) { + const diagnosisIds = Array.from(new Set(Array.from(ids, (id) => id.trim().toLowerCase()).filter(Boolean))); + const workflow = getPresentationWorkflowForDiagnosisIds(diagnosisIds); + if (!workflow) return null; + const candidateIds = new Set(workflow.candidates.map((candidate) => candidate.slug)); + return { + workflow, + diagnosisIds: diagnosisIds.filter((id) => candidateIds.has(id)), + }; +} + export const acuteConfusionPresentationWorkflow: DifferentialPresentationWorkflow = getPresentationWorkflow("acute-confusion-encephalopathy") ?? differentialPresentations()[0]!; diff --git a/tests/differentials.test.ts b/tests/differentials.test.ts index c2a133405..be87015fe 100644 --- a/tests/differentials.test.ts +++ b/tests/differentials.test.ts @@ -11,6 +11,7 @@ import { getDifferentialRecord, getPresentationWorkflow, getPresentationWorkflowForDiagnosisIds, + getPresentationWorkflowSelectionForDiagnosisIds, loadDifferentialSnapshot, rankDifferentialRecords, rankPresentationWorkflows, @@ -28,6 +29,15 @@ describe("presentation workflow routing", () => { ); expect(getPresentationWorkflowForDiagnosisIds([])).toBeNull(); }); + + it("forwards only diagnoses supported by the selected workflow", () => { + const selection = getPresentationWorkflowSelectionForDiagnosisIds([ + "wernicke-encephalopathy", + "major-depressive-disorder", + ]); + expect(selection?.workflow.id).toBe("acute-confusion-encephalopathy"); + expect(selection?.diagnosisIds).toEqual(["wernicke-encephalopathy"]); + }); }); const deliriumEntry = `=== ENTRY 1 === From 8bf455325b0915898417dd66aa61d419080c5528 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 11 Jul 2026 19:35:38 +0800 Subject: [PATCH 13/13] test: retain required core UI coverage --- tests/ui-smoke.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index 59add058f..5877ba930 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -883,7 +883,7 @@ test.describe("Clinical KB UI smoke coverage", () => { await expect(page.getByRole("heading", { name: "Answer" })).toBeVisible(); }); - test("tablet shows icon rail without drawer trigger or expand control", async ({ page }) => { + test("tablet shows icon rail without drawer trigger or expand control @critical", async ({ page }) => { await page.setViewportSize({ width: 768, height: 1024 }); await mockDemoApi(page); await gotoApp(page, "/?mode=answer"); @@ -1128,7 +1128,7 @@ test.describe("Clinical KB UI smoke coverage", () => { await expectNoPageHorizontalOverflow(page); }); - test("demo answer flow reaches a source-backed answer", async ({ browserName, page }) => { + test("demo answer flow reaches a source-backed answer @critical", async ({ browserName, page }) => { await page.setViewportSize({ width: 390, height: 820 }); await mockDemoApi(page); await gotoApp(page, "/"); @@ -2018,7 +2018,7 @@ test.describe("Clinical KB UI smoke coverage", () => { await expect(appModeButton).toBeFocused(); }); - test("prescribing workflow uses in-app medication routes", async ({ page }) => { + test("prescribing workflow uses in-app medication routes @critical", async ({ page }) => { test.setTimeout(120_000); // Regression guard: navigating away from a mode home used to throw // "Cannot read properties of null (reading 'parentNode')" because the header @@ -2175,7 +2175,7 @@ test.describe("Clinical KB UI smoke coverage", () => { await expectNoPageHorizontalOverflow(page); }); - test("search regressions avoid fetch errors and open viewer hits", async ({ page }) => { + test("search regressions avoid fetch errors and open viewer hits @critical", async ({ page }) => { await page.setViewportSize({ width: 1280, height: 900 }); await mockDemoApi(page); await gotoApp(page, "/");