From 31d5d6b9d1eaa1d3adc19255a0e924cf87eab784 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Mon, 13 Jul 2026 15:03:27 +0800 Subject: [PATCH 1/5] feat: polish result surfaces and add sorting --- src/components/AccessibleTable.tsx | 135 +++++------------- src/components/DocumentOrganizationBadges.tsx | 2 +- src/components/DocumentTagCloud.tsx | 8 +- src/components/DocumentViewer.tsx | 4 +- src/components/applications-launcher-page.tsx | 4 +- .../DocumentManagerPanel.tsx | 8 +- .../clinical-dashboard/clinical-badge.tsx | 4 +- .../clinical-dashboard/dashboard-nav.tsx | 2 +- .../clinical-dashboard/differentials-home.tsx | 98 ++++--------- .../clinical-dashboard/document-admin.tsx | 16 +-- .../document-search-results.tsx | 66 +++++---- .../clinical-dashboard/document-ui.tsx | 2 +- .../clinical-dashboard/evidence-panels.tsx | 8 +- .../clinical-dashboard/favourites-hub.tsx | 2 +- .../favourites-library-nav.tsx | 2 +- .../master-search-header.tsx | 10 +- .../medication-prescribing-workspace.tsx | 10 +- .../medication-record-page.tsx | 10 +- .../clinical-dashboard/output-panel.tsx | 2 +- .../search-results-header-band.tsx | 52 ++++--- .../clinical-dashboard/settings-dialog.tsx | 2 +- .../clinical-dashboard/visual-evidence.tsx | 4 +- .../differentials/diagnosis-map-panel.tsx | 6 +- .../differential-detail-page.tsx | 12 +- ...ifferential-presentation-workflow-page.tsx | 4 +- src/components/forms/form-detail-page.tsx | 10 +- .../forms/forms-search-results-page.tsx | 48 +++++-- .../services/service-detail-page.tsx | 2 +- .../services/services-navigator-page.tsx | 40 +++--- src/components/ui/sheet.tsx | 3 + src/components/use-result-sort.ts | 22 +++ src/lib/result-sort.ts | 25 ++++ tests/result-sort.test.ts | 31 ++++ tests/ui-smoke.spec.ts | 6 + tests/ui-tools.spec.ts | 40 +++++- 35 files changed, 388 insertions(+), 312 deletions(-) create mode 100644 src/components/use-result-sort.ts create mode 100644 src/lib/result-sort.ts create mode 100644 tests/result-sort.test.ts diff --git a/src/components/AccessibleTable.tsx b/src/components/AccessibleTable.tsx index dd9fb916f..e9d74eeb1 100644 --- a/src/components/AccessibleTable.tsx +++ b/src/components/AccessibleTable.tsx @@ -1,8 +1,9 @@ "use client"; -import { Maximize2, X } from "lucide-react"; -import { type ReactNode, useCallback, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react"; +import { Maximize2 } from "lucide-react"; +import { type ReactNode, useCallback, useMemo, useRef, useState, useSyncExternalStore } from "react"; import { cn, textMuted } from "@/components/ui-primitives"; +import { Sheet } from "@/components/ui/sheet"; import { normalizeAccessibleTable, type NormalizedAccessibleTable } from "@/lib/accessible-table-normalization"; import { normalizeExtractedGlyphs } from "@/lib/source-text-sanitizer"; @@ -144,7 +145,7 @@ function AccessibleTableMarkup({ : "whitespace-normal break-words", index > 0 && "border-l border-[color:var(--border)]/70", renderDensePreview - ? "px-2 py-1.5 text-3xs uppercase tracking-[0.06em]" + ? "px-2 py-1.5 text-2xs uppercase tracking-[0.06em]" : expanded ? "px-4 py-3 text-sm" : "px-3 py-2 text-xs", @@ -162,7 +163,7 @@ function AccessibleTableMarkup({ ? "overflow-hidden text-ellipsis whitespace-nowrap" : "whitespace-normal break-words", renderDensePreview - ? "px-2 py-1.5 text-3xs uppercase tracking-[0.06em]" + ? "px-2 py-1.5 text-2xs uppercase tracking-[0.06em]" : expanded ? "px-4 py-3 text-sm" : "px-3 py-2 text-xs", @@ -215,7 +216,7 @@ function AccessibleTableMarkup({ className={cn( renderDensePreview ? "sr-only" - : "mb-1 block text-3xs font-bold uppercase tracking-[0.08em] md:hidden", + : "mb-1 block text-2xs font-bold uppercase tracking-[0.08em] md:hidden", textMuted, )} > @@ -249,7 +250,7 @@ function AccessibleTableMarkup({ className={cn( renderDensePreview ? "sr-only" - : "mb-1 block text-3xs font-bold uppercase tracking-[0.08em] md:hidden", + : "mb-1 block text-2xs font-bold uppercase tracking-[0.08em] md:hidden", textMuted, )} > @@ -354,9 +355,6 @@ export function AccessibleTable({ // can pass it here to show the real table screenshot instead of that grid. lowConfidenceFallback?: ReactNode; }) { - const dialogId = useId(); - const closeButtonRef = useRef(null); - const dialogRef = useRef(null); const restoreFocusRef = useRef(null); const [open, setOpen] = useState(false); const canExpand = useMobileTableExpansion(expandOnMobile); @@ -380,47 +378,6 @@ export function AccessibleTable({ const dialogOpen = open; - useEffect(() => { - if (!dialogOpen) return; - const previousOverflow = document.body.style.overflow; - document.body.style.overflow = "hidden"; - const focusTimer = window.setTimeout(() => closeButtonRef.current?.focus(), 0); - const handleKeyDown = (event: globalThis.KeyboardEvent) => { - if (event.key === "Escape") { - event.preventDefault(); - setOpen(false); - return; - } - if (event.key !== "Tab") return; - const focusable = Array.from( - dialogRef.current?.querySelectorAll( - 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), summary, [tabindex]:not([tabindex="-1"])', - ) ?? [], - ).filter((element) => element.getAttribute("aria-hidden") !== "true"); - if (focusable.length === 0) return; - - const first = focusable[0]; - const last = focusable[focusable.length - 1]; - if (!dialogRef.current?.contains(document.activeElement)) { - event.preventDefault(); - (event.shiftKey ? last : first).focus(); - } else if (event.shiftKey && document.activeElement === first) { - event.preventDefault(); - last.focus(); - } else if (!event.shiftKey && document.activeElement === last) { - event.preventDefault(); - first.focus(); - } - }; - document.addEventListener("keydown", handleKeyDown); - return () => { - window.clearTimeout(focusTimer); - document.body.style.overflow = previousOverflow; - document.removeEventListener("keydown", handleKeyDown); - restoreFocusRef.current?.focus(); - }; - }, [dialogOpen]); - if (!normalized) return null; const { header, body } = normalized; @@ -476,7 +433,6 @@ export function AccessibleTable({ aria-label={`Open ${title} full screen`} aria-haspopup="dialog" aria-expanded={dialogOpen} - aria-controls={dialogOpen ? dialogId : undefined} onClick={(event) => { event.stopPropagation(); openDialog(event.currentTarget); @@ -488,56 +444,35 @@ export function AccessibleTable({ ) : null} - {dialogOpen ? ( - {!hasSourceEvidence && !sourcesChecked ? (
- {visibleResults.map((result) => { - // Rank number and "best" styling follow the overall ranked - // list, not whichever row happens to be first in a - // kind-filtered view. - const globalIndex = results.findIndex((entry) => entry.kind === result.kind && entry.id === result.id); + {visibleResults.map((result, displayIndex) => { + // Best-match styling remains tied to relevance while the row + // number follows the user's chosen presentation order. const isBest = result.kind === best.kind && result.id === best.id; return ( // The best answer is already featured above the phone list, @@ -1112,7 +1074,7 @@ function SearchResultsView({
toggleSelected(result.id) : undefined} @@ -1122,7 +1084,7 @@ function SearchResultsView({
toggleSelected(result.id) : undefined} /> diff --git a/src/components/clinical-dashboard/document-admin.tsx b/src/components/clinical-dashboard/document-admin.tsx index 3c01f5f49..8a242779b 100644 --- a/src/components/clinical-dashboard/document-admin.tsx +++ b/src/components/clinical-dashboard/document-admin.tsx @@ -234,7 +234,7 @@ function DocumentLabelReviewPanel({ if (!labelRows.length) return null; return (
-

+

{title}

@@ -248,10 +248,10 @@ function DocumentLabelReviewPanel({ {label.displayLabel} - + {label.tier} - + {labelTypeDisplay(label.labelType)}
@@ -422,7 +422,7 @@ function DocumentTagQualityPanel({ documents }: { documents: ClinicalDocument[] className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-3" >
- + {tagQualityLabel(issue.kind)}

{issue.label}

@@ -745,7 +745,7 @@ export function DocumentDrawer({
@@ -767,7 +767,7 @@ export function DocumentDrawer({
@@ -789,7 +789,7 @@ export function DocumentDrawer({
@@ -811,7 +811,7 @@ export function DocumentDrawer({
diff --git a/src/components/clinical-dashboard/document-search-results.tsx b/src/components/clinical-dashboard/document-search-results.tsx index cd97ee6ab..12557a0fd 100644 --- a/src/components/clinical-dashboard/document-search-results.tsx +++ b/src/components/clinical-dashboard/document-search-results.tsx @@ -3,7 +3,6 @@ import { memo, useMemo, useState } from "react"; import { BookOpen, - ChevronDown, Clock3, ExternalLink, FileImage, @@ -17,7 +16,6 @@ import { Shield, ShieldAlert, ShieldCheck, - SlidersHorizontal, Sparkles, Tag, Target, @@ -30,7 +28,8 @@ import { DocumentTagCloud } from "@/components/DocumentTagCloud"; import { documentDisplayTitle } from "@/components/DocumentOrganizationBadges"; import { isDeployedClinicalKb } from "@/lib/deployed-app"; import { ModeHomeTemplate, ModeHomeVerificationFooter } from "@/components/mode-home-template"; -import { SearchResultsHeaderBand } from "@/components/clinical-dashboard/search-results-header-band"; +import { ResultSortControl, SearchResultsHeaderBand } from "@/components/clinical-dashboard/search-results-header-band"; +import { useResultSort } from "@/components/use-result-sort"; import { SafeBoldText } from "@/components/SafeBoldText"; import { DocumentActionButton, @@ -61,6 +60,7 @@ import type { ServiceSearchMatch } from "@/lib/services"; import type { FormSearchMatch } from "@/lib/forms"; import type { ClinicalDocument, DocumentMatch, SearchResult } from "@/lib/types"; import type { RegistryRequestStatus } from "@/lib/use-registry-records"; +import { sortResultItems, type ResultSortValue } from "@/lib/result-sort"; import { documentRelevancePercent } from "./relevance-score"; type SearchFacet = { value: string; count: number }; @@ -191,7 +191,7 @@ function DocumentTagFacetRail({ )} > {facet.label} - + {facet.count} @@ -360,7 +360,17 @@ function DocumentSearchHome({ ); } -function SearchResultsHeader({ resultLabel, trimmedQuery }: { resultLabel: string; trimmedQuery: string }) { +function SearchResultsHeader({ + resultLabel, + trimmedQuery, + sortValue, + onSortChange, +}: { + resultLabel: string; + trimmedQuery: string; + sortValue: ResultSortValue; + onSortChange: (value: ResultSortValue) => void; +}) { return (
@@ -381,17 +391,7 @@ function SearchResultsHeader({ resultLabel, trimmedQuery }: { resultLabel: strin
- +
); } @@ -700,7 +700,7 @@ function SearchRecordResults({ key={card.id} className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-subtle)] p-2.5" > -
+
{card.label ?? card.id}
@@ -810,6 +810,7 @@ function DocumentSearchResultsPanelImpl({ desktopComposerSlotId?: string; }) { void _facets; + const [sortValue, setSortValue] = useResultSort(); const trimmedQuery = query.trim(); const [activeFacetState, setActiveFacetState] = useState<{ query: string; keys: string[] }>({ query: "", keys: [] }); const [activeResultType, setActiveResultType] = useState("all"); @@ -830,8 +831,12 @@ function DocumentSearchResultsPanelImpl({ () => filterMatchesByResultType(visibleMatches, effectiveResultType), [visibleMatches, effectiveResultType], ); + const sortedMatches = useMemo( + () => sortResultItems(displayedMatches, sortValue, documentDisplayTitle), + [displayedMatches, sortValue], + ); const selectedDocument = - displayedMatches.find((document) => document.document_id === selectedDocumentId) ?? displayedMatches[0] ?? null; + sortedMatches.find((document) => document.document_id === selectedDocumentId) ?? sortedMatches[0] ?? null; const recordMatchCount = recordMatches.length; const recordCopy = searchRecordConfig[recordMode]; const shouldShowHome = showHome || !trimmedQuery; @@ -863,12 +868,12 @@ function DocumentSearchResultsPanelImpl({ } if (recordMatchCount > 0 && matches.length > 0) { return `${recordMatchCount} ${recordCopy.recordLabel}${recordMatchCount === 1 ? "" : "s"} and ${ - displayedMatches.length - } document${displayedMatches.length === 1 ? "" : "s"}`; + sortedMatches.length + } document${sortedMatches.length === 1 ? "" : "s"}`; } if (recordMatchCount > 0) return `${recordMatchCount} ${recordCopy.recordLabel}${recordMatchCount === 1 ? "" : "s"}`; - if (matches.length) return `${displayedMatches.length} document${displayedMatches.length === 1 ? "" : "s"}`; + if (matches.length) return `${sortedMatches.length} document${sortedMatches.length === 1 ? "" : "s"}`; if (trimmedQuery) return "No matching documents"; return `${documentCount} document${documentCount === 1 ? "" : "s"}`; })(); @@ -879,8 +884,10 @@ function DocumentSearchResultsPanelImpl({
) : null} @@ -889,7 +896,12 @@ function DocumentSearchResultsPanelImpl({ (trimmedQuery && !shouldShowHome) || loading || (unavailableMessage && !shouldShowHome) ? ( - + ) : null} {unavailableMessage ? ( @@ -936,7 +948,7 @@ function DocumentSearchResultsPanelImpl({ <> 0 ? (
- {displayedMatches.length} result{displayedMatches.length === 1 ? "" : "s"} after filters + {sortedMatches.length} result{sortedMatches.length === 1 ? "" : "s"} after filters
) : null}
- {displayedMatches.length === 0 ? ( + {sortedMatches.length === 0 ? (
No document matches include all selected filters.
) : null} - {displayedMatches.map((document, index) => { + {sortedMatches.map((document, index) => { const relevanceDisplay = relevanceTone(document); const fileKind = documentFileKind(document.file_name, "DOC"); const relevanceVariant = relevanceDisplay.short === "High relevance" ? "high" : "relevant"; diff --git a/src/components/clinical-dashboard/document-ui.tsx b/src/components/clinical-dashboard/document-ui.tsx index a0a14fd14..b3abd90cd 100644 --- a/src/components/clinical-dashboard/document-ui.tsx +++ b/src/components/clinical-dashboard/document-ui.tsx @@ -48,7 +48,7 @@ export function DocumentFileTile({ {tab.label} {!isWarnRow ? ( - + {activeTab === "actions" ? "Action" : "Source"} ) : null} @@ -748,7 +748,7 @@ export function ClinicalNotesChecklistPanel({
{isWarnRow ? ( - Review + Review ) : ( S{row.sourceIndex} @@ -872,7 +872,7 @@ export function SafetyFindingsListContent({ findings }: { findings: SafetyFindin
- + {finding.label} - + View diff --git a/src/components/clinical-dashboard/favourites-library-nav.tsx b/src/components/clinical-dashboard/favourites-library-nav.tsx index 8d2e0f732..b95b9eaf9 100644 --- a/src/components/clinical-dashboard/favourites-library-nav.tsx +++ b/src/components/clinical-dashboard/favourites-library-nav.tsx @@ -283,7 +283,7 @@ export function FavouritesSidebar({ )} > - + {entry.count} diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx index 302a2d947..4b885e336 100644 --- a/src/components/clinical-dashboard/master-search-header.tsx +++ b/src/components/clinical-dashboard/master-search-header.tsx @@ -831,7 +831,7 @@ export function MasterSearchHeader({
{labelScopeFilterFields.map((field) => (