From 941fe191993a4aecc79d69c07a7c86bc3c0c83d3 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Fri, 3 Jul 2026 01:56:17 +0800 Subject: [PATCH 01/18] fix(ui): design-review fixes for differentials, services, favourites - differentials detail: give each Compare Basket item its own severity tag (shared likelihoodTag helper) instead of stamping the parent's status on all items; show the real related count instead of length + 8 - differentials stream: replace internal QA-scaffolding copy ("Stream helper / workflow verification only") with clinician-facing "Keep exploring" guidance - services: expand "ATSI" display labels to "Aboriginal and Torres Strait Islander" while keeping "ATSI" as a search keyword in tags; rename the duplicate "Find a service" card to "Search services" - favourites: derive saved-set counts from real items (2/2/1 vs 12/9/7), relabel the ambiguous "Active" stat to "Filters", fix "1 item" pluralization Co-Authored-By: Claude Opus 4.8 --- .../clinical-dashboard/favourites-hub.tsx | 5 +- .../favourites-prototype-data.ts | 8 +- .../differential-detail-page.tsx | 77 +++++++++++-------- .../differential-stream-page.tsx | 16 ++-- .../services/services-home-page.tsx | 4 +- src/lib/services.ts | 12 +-- 6 files changed, 66 insertions(+), 56 deletions(-) diff --git a/src/components/clinical-dashboard/favourites-hub.tsx b/src/components/clinical-dashboard/favourites-hub.tsx index aacfdd19b..eddd2c9d1 100644 --- a/src/components/clinical-dashboard/favourites-hub.tsx +++ b/src/components/clinical-dashboard/favourites-hub.tsx @@ -135,7 +135,7 @@ export function FavouritesHub({ {[ { label: "Items", value: itemCount, icon: Heart }, { label: "Sets", value: setCount, icon: Folder }, - { label: "Active", value: activeFilterCount, icon: Filter }, + { label: "Filters", value: activeFilterCount, icon: Filter }, ].map((stat) => { const Icon = stat.icon; return ( @@ -490,7 +490,8 @@ function FavouriteSetRow({ selected ? "text-[color:var(--clinical-accent)]" : "text-[color:var(--text-muted)]", )} > - {favouriteSet.count} items{compact ? "" : ` · ${favouriteSet.meta}`} + {favouriteSet.count} {favouriteSet.count === 1 ? "item" : "items"} + {compact ? "" : ` · ${favouriteSet.meta}`} diff --git a/src/components/clinical-dashboard/favourites-prototype-data.ts b/src/components/clinical-dashboard/favourites-prototype-data.ts index 9ddf9d4b8..b2269fc3e 100644 --- a/src/components/clinical-dashboard/favourites-prototype-data.ts +++ b/src/components/clinical-dashboard/favourites-prototype-data.ts @@ -94,25 +94,27 @@ export const favouriteItems: FavouriteItem[] = [ }, ]; +const countItemsInSet = (title: string) => favouriteItems.filter((item) => item.set === title).length; + export const favouriteSets: FavouriteSet[] = [ { id: "ward-round", title: "Ward round", - count: 12, + count: countItemsInSet("Ward round"), meta: "Medication pages, renal checks, forms", keywords: "ward round acamprosate lithium renal mht forms", }, { id: "prescribing-safety", title: "Prescribing safety", - count: 9, + count: countItemsInSet("Prescribing safety"), meta: "Dose limits, pregnancy, renal cautions", keywords: "prescribing safety dose pregnancy renal qt interactions", }, { id: "clozapine-clinic", title: "Clozapine clinic", - count: 7, + count: countItemsInSet("Clozapine clinic"), meta: "Monitoring, ANC table, counselling", keywords: "clozapine clinic monitoring anc table counselling", }, diff --git a/src/components/differentials/differential-detail-page.tsx b/src/components/differentials/differential-detail-page.tsx index 5bf25bf9a..9ffc2b9fa 100644 --- a/src/components/differentials/differential-detail-page.tsx +++ b/src/components/differentials/differential-detail-page.tsx @@ -87,6 +87,13 @@ function statusLabel(status: DifferentialRecord["status"]) { return "Routine"; } +/** Maps a related node's likelihood to its own severity tag, mirroring the record-status tones. */ +function likelihoodTag(likelihood: DifferentialRecord["related"][number]["likelihood"]) { + if (likelihood === "must-not-miss") return { label: "Emergent", className: statusTone.emergent }; + if (likelihood === "possible") return { label: "Urgent", className: statusTone.urgent }; + return { label: "Review", className: statusTone.routine }; +} + function SectionRow({ section }: { section: DifferentialSection }) { const Icon = sectionIcons[section.tone]; const meta = rowMeta[section.tone]; @@ -202,29 +209,28 @@ function RelatedDiagnoses({ record }: { record: DifferentialRecord }) { Related diagnoses - View all related ({record.related.length + 8}) + View all related ({record.related.length}) @@ -250,7 +256,16 @@ function CurrentPresentation({ record }: { record: DifferentialRecord }) { } function CompareBasket({ record }: { record: DifferentialRecord }) { - const items = [record.title, ...record.related.slice(0, 2).map((node) => node.label)]; + const items = [ + { + id: "self", + label: record.title, + tag: { label: statusLabel(record.status), className: statusTone[record.status] }, + }, + ...record.related + .slice(0, 2) + .map((node) => ({ id: node.id, label: node.label, tag: likelihoodTag(node.likelihood) })), + ]; return (
@@ -265,15 +280,20 @@ function CompareBasket({ record }: { record: DifferentialRecord }) {
    {items.map((item) => (
  • - {item} + {item.label} - - {statusLabel(record.status)} + + {item.tag.label}
  • ))} @@ -382,16 +402,6 @@ function HeaderChrome() { > -
    - - - -
    -

    Mode

    -

    Differentials

    -
    - -
    @@ -486,6 +496,7 @@ export function DifferentialDetailPage({ record }: { record: DifferentialRecord
    +
    {record.sections.map((section) => ( diff --git a/src/components/differentials/differential-stream-page.tsx b/src/components/differentials/differential-stream-page.tsx index 3e8e027d3..71b2e24b9 100644 --- a/src/components/differentials/differential-stream-page.tsx +++ b/src/components/differentials/differential-stream-page.tsx @@ -1,5 +1,5 @@ import Link from "next/link"; -import { ArrowLeft, ArrowRight, CircleHelp, FileText } from "lucide-react"; +import { ArrowLeft, ArrowRight, FileText } from "lucide-react"; import { appModeHomeHref } from "@/lib/app-modes"; import { @@ -67,9 +67,9 @@ export function DifferentialStreamPage({ stream, query = "" }: DifferentialStrea >

    {card.title}

    {card.description}

    -
      +
        {card.examples.map((example) => ( -
      • +
      • {example}
      • @@ -82,14 +82,10 @@ export function DifferentialStreamPage({ stream, query = "" }: DifferentialStrea
        -

        Stream helper

        +

        Keep exploring

        - This stream contains differential diagnosis content only. Use it to move from presentation clues to - diagnosis detail pages without mixing in service or referral records. -

        -

        - - Use for workflow verification only + Return to the differentials home to start from a different presentation, or open search to look up another + differential.

        diff --git a/src/components/services/services-home-page.tsx b/src/components/services/services-home-page.tsx index 796512684..df91c35ba 100644 --- a/src/components/services/services-home-page.tsx +++ b/src/components/services/services-home-page.tsx @@ -13,7 +13,7 @@ import { defaultServiceSlug, serviceRecords } from "@/lib/services"; const taskCards: ModeHomeAction[] = [ { - title: "Find a service", + title: "Search services", description: "Search by need, catchment, provider, or keyword.", icon: FileSearch, href: appModeHomeHref("services", { focus: true }), @@ -43,7 +43,7 @@ const commonPathways: ModeHomePill[] = [ href: appModeHomeHref("services", { query: "crisis support services", focus: true, run: true }), }, { - label: "ATSI-specific", + label: "Aboriginal and Torres Strait Islander", tone: "info", href: appModeHomeHref("services", { query: "Aboriginal Torres Strait Islander services", diff --git a/src/lib/services.ts b/src/lib/services.ts index 502b88c1d..43ac294c8 100644 --- a/src/lib/services.ts +++ b/src/lib/services.ts @@ -82,7 +82,7 @@ export const serviceRecords: ServiceRecord[] = [ subtitle: "Urgent contact, crisis response, or acute support pathway.", statusChips: [ { label: "Crisis / urgent", tone: "danger" }, - { label: "ATSI-specific", tone: "info" }, + { label: "Aboriginal and Torres Strait Islander", tone: "info" }, { label: "Local confirmation", tone: "warning" }, ], primaryContact: { @@ -106,7 +106,7 @@ export const serviceRecords: ServiceRecord[] = [ }, ], route: "Self phone referral", - eligibility: "ATSI callers", + eligibility: "Aboriginal and Torres Strait Islander callers", cost: "Free", referral: "Self referral by phone. Escalate emergency medical danger through emergency services.", location: "Statewide / national", @@ -120,7 +120,7 @@ export const serviceRecords: ServiceRecord[] = [ { id: "eligibility", label: "Eligibility", - title: "ATSI callers", + title: "Aboriginal and Torres Strait Islander callers", detail: "See details", }, { @@ -140,7 +140,7 @@ export const serviceRecords: ServiceRecord[] = [ { label: "Primary route", value: "Contact: 13 92 76\nSelf phone referral" }, { label: "Phone", value: "13 92 76" }, { label: "Email", value: "None listed" }, - { label: "Provider", value: "ATSI crisis support service referenced by WACHS" }, + { label: "Provider", value: "Aboriginal and Torres Strait Islander crisis support service referenced by WACHS" }, { label: "Region", value: "Statewide / national" }, { label: "Patient group", value: "Aboriginal and Torres Strait Islander people" }, { label: "Hours", value: "Not publicly stated" }, @@ -148,7 +148,7 @@ export const serviceRecords: ServiceRecord[] = [ ], bestUse: "Culturally safe crisis phone support; escalate emergency danger elsewhere.", criteria: [ - { label: "ATSI support need", tone: "meet" }, + { label: "Aboriginal and Torres Strait Islander support need", tone: "meet" }, { label: "Crisis support pathway appropriate", tone: "meet" }, { label: "Phone referral available", tone: "meet" }, { label: "Emergency medical danger present", tone: "reject" }, @@ -411,7 +411,7 @@ export const serviceRecords: ServiceRecord[] = [ title: "State-wide Specialist Aboriginal Mental Health Service", subtitle: "Great Southern WACHS service combining cultural and clinical mental health expertise.", statusChips: [ - { label: "ATSI-specific", tone: "info" }, + { label: "Aboriginal and Torres Strait Islander", tone: "info" }, { label: "Regional WA", tone: "success" }, { label: "Local confirmation", tone: "warning" }, ], From b60fd61fbd57dce7698094507c9e990ac5411e54 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Fri, 3 Jul 2026 02:04:07 +0800 Subject: [PATCH 02/18] fix(ui): design-review fixes for service detail, search header, sidebar, form Commits the current working-tree state of these four files, which also carries in-progress edits already present on this branch. Design-review changes here: - service detail: use break-words instead of break-all so referral values no longer split mid-word ("service" -> "servic e", "Strait" -> "Stra it") - master search header: use the magnifier submit affordance for every search-mode home; differentials no longer uses the send arrow - sidebar: expand tool labels to full words (Favourites, Differentials, Medications) for consistency with the rest of the nav - form detail: render Verification notes as proper sentences (joinNotes) instead of a bare space-joined run-on Co-Authored-By: Claude Opus 4.8 --- .../clinical-dashboard/ClinicalSidebar.tsx | 65 ++++---- .../master-search-header.tsx | 14 +- src/components/forms/form-detail-page.tsx | 40 +++-- .../services/service-detail-page.tsx | 150 +++++++----------- 4 files changed, 127 insertions(+), 142 deletions(-) diff --git a/src/components/clinical-dashboard/ClinicalSidebar.tsx b/src/components/clinical-dashboard/ClinicalSidebar.tsx index 4de23418d..351b1f6ef 100644 --- a/src/components/clinical-dashboard/ClinicalSidebar.tsx +++ b/src/components/clinical-dashboard/ClinicalSidebar.tsx @@ -5,7 +5,6 @@ import Link from "next/link"; import { BookOpen, BrainCircuit, - ChevronDown, ClipboardList, FileText, Heart, @@ -47,14 +46,18 @@ export function deriveSidebarIdentity(email: string | null | undefined): Sidebar return { displayName, initials, detail: normalized, signedIn: true }; } +function accountProfileLabel(identity: SidebarIdentity) { + return `${identity.initials} ${identity.displayName} ${identity.detail}. Open account profile`; +} + const sidebarToolItems = [ { id: "answer", label: "Answer", icon: Sparkles, href: "/?mode=answer" }, { id: "documents", label: "Documents", icon: FileText, href: "/?mode=documents" }, { id: "services", label: "Services", icon: ClipboardList, href: "/services" }, { id: "forms", label: "Forms", icon: FileText, href: "/forms" }, - { id: "favourites", label: "Faves", icon: Heart, href: "/favourites" }, - { id: "differentials", label: "Diffs", icon: BrainCircuit, href: "/differentials" }, - { id: "prescribing", label: "Meds", icon: Pill, href: "/?mode=prescribing" }, + { id: "favourites", label: "Favourites", icon: Heart, href: "/favourites" }, + { id: "differentials", label: "Differentials", icon: BrainCircuit, href: "/differentials" }, + { id: "prescribing", label: "Medications", icon: Pill, href: "/?mode=prescribing" }, { id: "tools", label: "Tools", icon: Wrench, href: "/?mode=tools" }, ] as const; @@ -100,6 +103,7 @@ export function ClinicalSidebarContent({ const visibleRecentQueries = matchingRecentQueries.slice(0, 5); const ThemeIcon = theme === "dark" ? Sun : Moon; const nextThemeLabel = theme === "dark" ? "Light mode" : "Dark mode"; + const accountLabel = accountProfileLabel(identity); return (
        @@ -114,7 +118,7 @@ export function ClinicalSidebarContent({
        - - View tools - -
        @@ -277,7 +270,7 @@ export function ClinicalSidebarContent({ }} data-testid="sidebar-account-settings" className="mt-2 flex w-full items-center gap-3 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] px-3 py-2 text-left shadow-[var(--shadow-inset)] transition hover:border-[color:var(--clinical-accent-border)] hover:bg-[color:var(--clinical-accent-soft)]/40 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]" - aria-label={identity.signedIn ? `Open account profile for ${identity.detail}` : "Open account profile"} + aria-label={accountLabel} > {identity.initials} @@ -299,6 +292,7 @@ export function ClinicalSidebarContent({ export function ClinicalDesktopSidebar({ collapsed, + collapseLocked = false, recentQueries, identity, activeMode, @@ -312,6 +306,7 @@ export function ClinicalDesktopSidebar({ onPrefetchApplications, }: { collapsed: boolean; + collapseLocked?: boolean; recentQueries: string[]; identity: SidebarIdentity; activeMode: AppModeId; @@ -325,6 +320,7 @@ export function ClinicalDesktopSidebar({ onPrefetchApplications: () => void; }) { const CollapsedThemeIcon = theme === "dark" ? Sun : Moon; + const accountLabel = accountProfileLabel(identity); if (collapsed) { return ( @@ -333,16 +329,27 @@ export function ClinicalDesktopSidebar({ className="hidden min-h-0 border-r border-[color:var(--border)] bg-[color:var(--surface-lux)] py-4 shadow-[var(--shadow-soft)] lg:flex lg:w-[5.25rem] lg:flex-col lg:items-center" >
        - + {collapseLocked ? ( + + + + ) : ( + + )}
        @@ -358,7 +365,7 @@ export function ClinicalDesktopSidebar({ diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx index e287531d2..b38e91531 100644 --- a/src/components/clinical-dashboard/master-search-header.tsx +++ b/src/components/clinical-dashboard/master-search-header.tsx @@ -246,7 +246,8 @@ export function MasterSearchHeader({ const isWorkflowHeader = headerVariant === "workflow"; const isMobileBottomComposer = searchComposerVisible && mobileSearchPlacement === "bottom" && !isAnswerFooterComposer; const isHeroDesktopComposer = desktopSearchPlacement === "hero" && isMobileBottomComposer; - const canRunLocalSearch = selectedSearch.kind === "tools" || selectedSearch.kind === "favourites"; + const canRunLocalSearch = + selectedSearch.kind === "services" || selectedSearch.kind === "tools" || selectedSearch.kind === "favourites"; const canAsk = trimmedQuery.length >= 1 && !loading && selectedSearchable && (realDataReady || canRunLocalSearch); const indexedDocumentTotal = documentTotal ?? documents.length; const hasUnloadedDocuments = indexedDocumentTotal > documents.length; @@ -355,7 +356,6 @@ export function MasterSearchHeader({ searchMode === "prescribing" ? medicationModeActionItems : modeActionItemsFor(actionMenuSetId); const actionMenuTitle = selectedAppMode.label; const actionMenuButtonLabel = `Open ${selectedAppMode.label.toLowerCase()} options`; - const isStandaloneModeHomeHeader = Boolean(desktopHomeComposerSlotId); const useMobileBackControl = mobileLeadingAction === "back"; function currentUsesScopeSheet() { @@ -887,7 +887,9 @@ export function MasterSearchHeader({ const usesAnswerFooterStyle = isAnswerFooterComposer && !isDesktopHomeComposer; const usesMobileBottomStyle = isMobileBottomComposer && !isDesktopHomeComposer; const usesUniversalFooterStyle = usesAnswerFooterStyle || (usesMobileBottomStyle && usesPhoneSearchLayout); - const usesSendAffordance = usesAnswerFooterStyle || (isStandaloneModeHomeHeader && searchMode === "differentials"); + const showFooterSearchChips = usesUniversalFooterStyle && searchMode === "answer"; + // Only the Answer chat composer uses the send affordance; every search-mode home uses the magnifier. + const usesSendAffordance = usesAnswerFooterStyle; const composerPlaceholder = usesMobileBottomStyle && searchMode === "differentials" ? "Search a presentation" : queryPlaceholder; @@ -997,7 +999,7 @@ export function MasterSearchHeader({ {submitLabel}
        - {usesUniversalFooterStyle ? ( + {showFooterSearchChips ? (
        - ))} -
    - -
    - - - - - - - - - - - - - - - - {rows.map((row, index) => ( - - - - - - ))} - -
    Service referral information
    - Field - - Detail - - Action -
    - - - {renderRowIcon(row.label)} - - - - {row.label} - - {index === 0 ? ( - - Primary access route - - ) : null} - - - -

    - {displayText(row.value)} -

    -
    - -
    -
    - + ); + })} +
    ); } @@ -473,12 +430,14 @@ function CriteriaGroup({ } function TagList({ items, emptyLabel }: { items: string[]; emptyLabel: string }) { - if (!items.length) return

    {emptyLabel}

    ; + const uniqueItems = dedupeTagItems(items); + + if (!uniqueItems.length) return

    {emptyLabel}

    ; return (
    - {items.map((item) => ( - + {uniqueItems.map((item) => ( + {item} ))} @@ -573,7 +532,7 @@ export function ServiceDetailPage({ service }: { service: ServiceRecord }) { return (
    {notice ? ( @@ -661,7 +620,7 @@ export function ServiceDetailPage({ service }: { service: ServiceRecord }) {

    Contact

    -

    +

    Contact: {displayText(primaryContact?.value)}

    @@ -694,15 +653,16 @@ export function ServiceDetailPage({ service }: { service: ServiceRecord }) {

    -

    @@ -669,7 +669,7 @@ export default function AnswerEvidencePopupsMockupPage() { body="Small inline popover opened from the source-backed capsule underneath the natural-language answer." >

    -

    +

    Clozapine monitoring should include FBC/ANC, myocarditis symptoms, metabolic checks, constipation prevention, and shared-care communication.

    diff --git a/src/app/mockups/layout.tsx b/src/app/mockups/layout.tsx index 33a8f0387..eaad16382 100644 --- a/src/app/mockups/layout.tsx +++ b/src/app/mockups/layout.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from "react"; -import { GlobalMockupSearchShell } from "@/components/clinical-dashboard/global-mockup-search-shell"; +import { MockupsLayoutClient } from "./mockups-layout-client"; export default function MockupsLayout({ children }: { children: ReactNode }) { - return {children}; + return {children}; } diff --git a/src/app/mockups/mode-dropdown/page.tsx b/src/app/mockups/mode-dropdown/page.tsx index 884b3e86d..ba34d0301 100644 --- a/src/app/mockups/mode-dropdown/page.tsx +++ b/src/app/mockups/mode-dropdown/page.tsx @@ -77,7 +77,7 @@ function HeaderMockup({ expanded = false, compact = false }: { expanded?: boolea - + Mode @@ -118,7 +118,7 @@ function HeaderMockup({ expanded = false, compact = false }: { expanded?: boolea {mode.label} - + {mode.description} diff --git a/src/app/mockups/recent-searches-bottom/page.tsx b/src/app/mockups/recent-searches-bottom/page.tsx index 252250c3e..5d38f6984 100644 --- a/src/app/mockups/recent-searches-bottom/page.tsx +++ b/src/app/mockups/recent-searches-bottom/page.tsx @@ -17,7 +17,7 @@ const sourceCards = [ function RecentSearchRail() { return (
    - + Recent @@ -56,11 +56,11 @@ function SourcePreviewGrid() { - + {meta}
    -

    {title}

    +

    {title}

    {body}

    ))} @@ -79,10 +79,10 @@ export default function RecentSearchesBottomMockupPage() { - + Ready - + All sources
    @@ -101,7 +101,7 @@ export default function RecentSearchesBottomMockupPage() {

    Scope and mode stay primary

    - + Recent is secondary
    diff --git a/src/components/AccessibleTable.tsx b/src/components/AccessibleTable.tsx index 49bda5617..36e877133 100644 --- a/src/components/AccessibleTable.tsx +++ b/src/components/AccessibleTable.tsx @@ -126,7 +126,7 @@ function AccessibleTableMarkup({ aria-label={caption ?? undefined} className={cn( "w-full border-separate border-spacing-0 text-left md:table-fixed", - renderDensePreview ? "min-w-full table-fixed text-[11px]" : expanded ? "text-[15px]" : "text-sm", + renderDensePreview ? "min-w-full table-fixed text-2xs" : expanded ? "text-base-minus" : "text-sm", )} > @@ -148,7 +148,7 @@ function AccessibleTableMarkup({ : "whitespace-normal break-words [overflow-wrap:anywhere]", index > 0 && "border-l border-[color:var(--border)]/70", renderDensePreview - ? "px-2 py-1.5 text-[10px] uppercase tracking-[0.06em]" + ? "px-2 py-1.5 text-3xs uppercase tracking-[0.06em]" : expanded ? "px-4 py-3 text-sm" : "px-3 py-2 text-xs", @@ -164,7 +164,7 @@ function AccessibleTableMarkup({ "nums border-b border-l border-[color:var(--border)]/70 align-top font-semibold leading-5 text-[color:var(--text)]", "whitespace-normal break-words [overflow-wrap:anywhere]", renderDensePreview - ? "px-2 py-1.5 text-[10px] uppercase tracking-[0.06em]" + ? "px-2 py-1.5 text-3xs uppercase tracking-[0.06em]" : expanded ? "px-4 py-3 text-sm" : "px-3 py-2 text-xs", @@ -219,7 +219,7 @@ function AccessibleTableMarkup({ className={cn( renderDensePreview ? "sr-only" - : "mb-1 block text-[10px] font-bold uppercase tracking-[0.08em] md:hidden", + : "mb-1 block text-3xs font-bold uppercase tracking-[0.08em] md:hidden", textMuted, )} > @@ -229,7 +229,7 @@ function AccessibleTableMarkup({ className={cn( "block min-w-0", renderDensePreview - ? "truncate text-[11px] leading-4" + ? "truncate text-2xs leading-4" : "text-sm leading-6 md:text-inherit md:leading-inherit", )} > @@ -253,7 +253,7 @@ function AccessibleTableMarkup({ className={cn( renderDensePreview ? "sr-only" - : "mb-1 block text-[10px] font-bold uppercase tracking-[0.08em] md:hidden", + : "mb-1 block text-3xs font-bold uppercase tracking-[0.08em] md:hidden", textMuted, )} > @@ -463,7 +463,7 @@ export function AccessibleTable({
    event.stopPropagation()}>
    -

    Clinical table

    +

    Clinical table

    Sources

    - + {sourcePreviewPageCountLabel(previewSources)}
    @@ -897,7 +903,7 @@ function SourcePreviewContent({ )} > {index === 0 ? ( -

    +

    Best match

    @@ -1131,7 +1137,7 @@ function NaturalLanguageAnswer({ title="Sources" description="Open the original PDF page." titleAccessory={ - + {sourcePreviewPageCountLabel(previewSources)} } @@ -1177,7 +1183,7 @@ function UserQuestionBubble({ query }: { query: string }) { className="ml-auto max-w-[min(28rem,86%)] rounded-lg border border-[color:var(--border)] bg-[color:var(--clinical-accent-soft)] px-3 py-2 text-right shadow-[var(--shadow-inset)] sm:max-w-[28rem]" >

    {cleaned}

    -

    9:14 AM

    +

    9:14 AM

); @@ -1258,8 +1264,8 @@ function KeyClinicalItems({ return (
-

Key monitoring items

-
    +

    Key monitoring items

    +
      {items.map((item) => (
    • {item.label ? ( @@ -1769,7 +1775,7 @@ function ClinicalNoteDetailCard({ row }: { row: ClinicalNotesRow }) { const detail = sentenceCaseClinicalNoteDetail(row.detail); return (
      -

      +

      {clinicalNoteDetailLabel(row)}: {detail}

      @@ -1925,7 +1931,7 @@ function ClinicalNotesChecklistPanel({ {tab.label} @@ -2024,13 +2030,17 @@ function ClinicalNotesChecklistPanel({ {bestSource ? ( Source ) : ( - + Source @@ -2038,7 +2048,8 @@ function ClinicalNotesChecklistPanel({
      -

      {finding.text}

      +

      {finding.text}

      {formatCitationLabel(finding.citation)}

      @@ -2145,13 +2157,13 @@ function EvidenceGapPanel({

      What was found

      -

      {found}

      +

      {found}

      What was not found

      -

      {missing}

      +

      {missing}

      Closest sources

      @@ -2170,14 +2182,14 @@ function EvidenceGapPanel({ ))}
      ) : ( -

      No nearby indexed sources were returned.

      +

      No nearby indexed sources were returned.

      )}

      Suggested next search/upload

      -

      +

      Try a narrower query using the missing terms, scope to a likely document, or upload/index the guideline that directly covers "{query.trim()}".

      @@ -2224,7 +2236,7 @@ function EvidenceCounts({ className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-2.5" >

      {item.value}

      -

      {item.label}

      +

      {item.label}

      ))} @@ -2345,7 +2357,7 @@ function EvidenceSummaryCard({ {Math.round(Math.max(0, Math.min(1, bestSource.score)) * 100)}% match -

      +

      “{bestSource.snippet}”

      @@ -2575,7 +2587,7 @@ function AnswerInsightBar({ className="inline-flex min-h-8 min-w-0 items-center gap-1.5 rounded-md border border-[color:var(--border)] bg-[color:var(--surface)] px-2 text-xs font-semibold text-[color:var(--text)] shadow-[var(--shadow-inset)]" > - + {item.label} {item.value} @@ -2656,7 +2668,7 @@ function EvidenceVerificationStrip({ ) : ( )} -

      +

      {check.label}

      @@ -2665,7 +2677,7 @@ function EvidenceVerificationStrip({ ))}
      - + Pinned source {bestSource ? ( @@ -2725,7 +2737,7 @@ function AnswerFeedbackPanel({

      {pending ? ( - + Saving @@ -3066,7 +3078,7 @@ function QuoteCards({ -
      +
      “{quoteText}”
      - + {formatCompactCitationLabel(quote)} @@ -3189,7 +3201,7 @@ function ClinicalOutputPanel({ "min-h-12 min-w-0 justify-between gap-2 rounded-lg px-3 py-2 text-left sm:min-h-9", )} > - {item.label} + {item.label} {item.value} ))} @@ -3205,7 +3217,7 @@ function ClinicalOutputPanel({

      {leadSection.title}

      -

      +

      @@ -3249,7 +3261,7 @@ function ClinicalOutputPanel({
      -

      +

      {meta.eyebrow}

      @@ -3257,7 +3269,7 @@ function ClinicalOutputPanel({

      - {itemCount} + {itemCount} {section.tables?.length ? (
      @@ -3277,7 +3289,7 @@ function ClinicalOutputPanel({
      ) : null} {section.items.length ? ( -
        +
          {section.items.map((item, index) => (
        • -
          +
          {!hasStructuredTable ?

          {sourceHeader.title}

          : null} {!hasStructuredTable && sourceHeader.caption ?

          {sourceHeader.caption}

          : null} {displayLabels.map((label) => ( - + {label} ))} @@ -3507,14 +3519,14 @@ function VisualEvidenceStrip({ clinicalDivider, )} > - + {formatCompactCitationLabel(item)} {cleanDisplayTitle(item.title)}, page {item.page_number ?? "n/a"} {item.image_type && ( - + {item.image_type.replaceAll("_", " ")} )} @@ -3811,7 +3823,7 @@ function MobileEvidenceSheetContent({ > {tab} - {count ? {count} : null} + {count ? {count} : null} ); })} @@ -3860,13 +3872,17 @@ function MobileEvidenceSheetContent({ {primarySourceHref ? ( Source ) : ( - + Source @@ -3874,7 +3890,8 @@ function MobileEvidenceSheetContent({ {document.summary && ( -

          +

          )} @@ -4372,7 +4390,7 @@ function StagedAnswerResultSurface({

          {activeReviewPanel === "clinical" ? "Clinical notes" : "Evidence"}

          - + {activeReviewPanel === "clinical" ? clinicalNoteDisplayCount : "Supported"} @@ -4443,7 +4461,7 @@ function StagedAnswerResultSurface({ } titleAccessory={ - + {clinicalNoteDisplayCount} } @@ -4459,7 +4477,7 @@ function StagedAnswerResultSurface({ ) : null } headerClassName="gap-2 p-2.5 sm:p-3" - titleClassName="text-[15px] leading-5" + titleClassName="text-base-minus leading-5" closeButtonClassName="inline-flex h-8 w-8 items-center justify-center rounded-full text-[color:var(--text-muted)] transition hover:bg-[color:var(--surface-subtle)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]" contentClassName="max-h-[92dvh] translate-y-0 bg-[color:var(--surface-raised)] motion-safe:animate-none sm:h-auto sm:max-h-[88dvh] sm:max-w-lg" contentStyle={{ height: "80dvh" }} @@ -4486,7 +4504,7 @@ function StagedAnswerResultSurface({ title="Evidence" description="Review by evidence type." titleAccessory={ - {evidenceTrustLabel} + {evidenceTrustLabel} } closeLabel="Close evidence" headerLeading={ @@ -4546,7 +4564,7 @@ function AuthPanel() {

          Real-data sign-in unavailable

          -

          +

          Configure the Supabase public URL and publishable key before using private documents.

          @@ -4580,7 +4598,7 @@ function AuthPanel() {

          {isExpired ? "Sign-in link expired" : "Sign in for private documents"}

          -

          +

          {isExpired ? "Send a fresh link if this one failed or already timed out." : "Real-data search, upload, and source previews require a Supabase Auth session."} @@ -4766,14 +4784,14 @@ function DocumentLabelReviewPanel({ > {documentDisplayTitle(item.document)} -

          +

          {item.visible.length} visible · {item.ranking.length} ranking · {item.hidden.length} hidden

          {item.needsReview ? ( - Needs review + Needs review ) : ( - Reviewed + Reviewed )} @@ -4787,7 +4805,7 @@ function DocumentLabelReviewPanel({ if (!labelRows.length) return null; return (
          -

          +

          {title}

          @@ -4801,14 +4819,14 @@ function DocumentLabelReviewPanel({ {label.displayLabel} - + {label.tier} - + {labelTypeDisplay(label.labelType)}
          -

          +

          {label.source} · {Math.round(label.confidence * 100)}% · {label.reviewStatus}

          @@ -4825,7 +4843,7 @@ function DocumentLabelReviewPanel({ `restore:${label.id}`, ) } - className={cn(floatingControl, "min-h-8 px-2 text-[11px]")} + className={cn(floatingControl, "min-h-8 px-2 text-2xs")} > Restore @@ -4842,7 +4860,7 @@ function DocumentLabelReviewPanel({ `approve:${label.id}`, ) } - className={cn(floatingControl, "min-h-8 px-2 text-[11px]")} + className={cn(floatingControl, "min-h-8 px-2 text-2xs")} > Approve @@ -4857,7 +4875,7 @@ function DocumentLabelReviewPanel({ `hide:${label.id}`, ) } - className={cn(floatingControl, "min-h-8 px-2 text-[11px] text-[color:var(--danger)]")} + className={cn(floatingControl, "min-h-8 px-2 text-2xs text-[color:var(--danger)]")} > Hide @@ -4958,7 +4976,7 @@ function DocumentTagQualityPanel({ documents }: { documents: ClinicalDocument[]
          {(Object.keys(counts) as SmartDocumentTagQualityIssueKind[]).map((kind) => ( - + {tagQualityLabel(kind)}: {counts[kind]} ))} @@ -4971,17 +4989,17 @@ function DocumentTagQualityPanel({ documents }: { documents: ClinicalDocument[] className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-3" >
          - + {tagQualityLabel(issue.kind)}

          {issue.label}

          {issue.count > 1 ? ( - {issue.count} hits + {issue.count} hits ) : null}

          {issue.reason}

          {issue.examples.length || issue.documentTitles.length ? ( -

          +

          {[ issue.examples.length ? `examples: ${issue.examples.join(", ")}` : "", issue.documentTitles.length ? `docs: ${issue.documentTitles.join(", ")}` : "", @@ -5053,16 +5071,16 @@ function DocumentIndexRepairPanel({ documents }: { documents: ClinicalDocument[] >

          {item.document.title}

          - + index {Number.isFinite(item.score) ? item.score.toFixed(2) : "n/a"}
          - extraction:{item.extractionQuality} - sections:{item.sectionCount} - memory:{item.memoryCardCount} + extraction:{item.extractionQuality} + sections:{item.sectionCount} + memory:{item.memoryCardCount} {item.issues.slice(0, 4).map((issue) => ( - + {issue} ))} @@ -5288,7 +5306,7 @@ function DocumentDrawer({ {/* Dynamic Browse Library Filters */}
          - +
          - +
          - +
          - ) : (
          @@ -1289,7 +1289,7 @@ function PdfCanvasViewer({ url, title, initialPage }: { url: string; title: stri > -
          +
          + setDraftLabel(event.target.value)} placeholder="Add clean manual tag" disabled={!canManage || busyAction !== null} className={fieldControl} + aria-label="Manual tag" /> onQueryChange(event.target.value)} + aria-label="Search tools by clinical job, source, or workflow" + placeholder="Search tools..." + className="min-w-0 bg-transparent text-sm font-semibold text-[color:var(--text)] outline-none placeholder:text-[color:var(--text-soft)]" + /> + {query.trim() ? ( + + ) : ( + + + + )} +
          + ); +} + +function DashboardFilterBar({ + activeFilter, + onFilterChange, +}: { + activeFilter: DashboardFilterId; + onFilterChange: (filter: DashboardFilterId) => void; +}) { + return ( +
          + {dashboardFilterOptions.map((option) => { + const Icon = option.icon; + const active = option.id === activeFilter; + + return ( + + ); + })} +
          + ); +} + +function DashboardToolCard({ app, selected = false }: { app: LauncherApp; selected?: boolean }) { + return ( + +
          + +
          +
          +

          {app.title}

          + +
          +

          {app.description}

          +

          {app.detail}

          +
          +
          +
          + {app.lastUsed} + + {app.primaryAction} + + +
          + + ); +} + +function DashboardWideToolTile({ app, selected = false }: { app: LauncherApp; selected?: boolean }) { + return ( + +
          + +
          +
          +

          + {app.title} +

          + +
          +

          {app.description}

          +
          +
          + +
          +
          +

          + {app.areaLabel} +

          +

          {app.workflow}

          +
          + + {app.primaryAction} + + +
          + + ); +} + +function DashboardPhonePreview({ apps }: { apps: LauncherApp[] }) { + const phoneApps = apps.length ? apps.slice(0, 5) : launcherApps.slice(0, 5); + const phoneRecents = recentActivity.slice(0, 2); + + return ( + + ); +} + +function DashboardRecents({ onSelect }: { onSelect: (id: string) => void }) { + return ( +
          +
          +

          Recents

          + View all +
          +
          + {recentActivity.slice(0, 3).map((item) => { + const Icon = item.icon; + const app = appById(item.id); + return ( + + ); + })} +
          +
          + ); +} + +function DashboardToolsCommandCenter({ + query, + onQueryChange, + activeFilter, + onFilterChange, + filteredApps, + pinnedApps, + selectedId, + onSelect, + desktopComposerSlotId, + className, +}: { + query: string; + onQueryChange: (query: string) => void; + activeFilter: DashboardFilterId; + onFilterChange: (filter: DashboardFilterId) => void; + filteredApps: LauncherApp[]; + pinnedApps: LauncherApp[]; + selectedId: string; + onSelect: (id: string) => void; + desktopComposerSlotId?: string; + className?: string; +}) { + const startApps = seedPinnedIds.map(appById); + + return ( +
          +
          +
          +
          + + Recommended direction + + + Launcher first + +
          +

          + Tools command center +

          +

          + Open a tool directly, resume recent work, or search by clinical task. +

          +
          + +
          + +
          + {desktopComposerSlotId ?
          : null} + +
          + + +
          + +
          +
          +
          +

          Start here

          +

          Most-used tools stay one click away.

          +
          +
          + {startApps.map((app, index) => ( + + ))} +
          +
          + + +
          + +
          +
          +
          +
          + +

          All tools

          +
          +

          + Every tool opens directly. Pick by task, status, or last-used context. +

          +
          +
          + Showing {filteredApps.length} of {launcherApps.length} +
          +
          + + {filteredApps.length ? ( +
          + {filteredApps.map((app) => ( + + ))} +
          + ) : ( +
          +

          No tools match

          +

          + Clear the search or try another clinical workflow, tool name, or category. +

          +
          + )} +
          + + +
          +
          + ); +} + type ApplicationsLauncherWorkspaceProps = { variant?: LauncherVariant; query?: string; @@ -665,6 +1175,7 @@ export function ApplicationsLauncherWorkspace({ }: ApplicationsLauncherWorkspaceProps) { const [uncontrolledQuery, setUncontrolledQuery] = useState(""); const [activeFilter, setActiveFilter] = useState<(typeof filterOptions)[number]["id"]>("all"); + const [dashboardFilter, setDashboardFilter] = useState("all"); const [selectedId, setSelectedId] = useState("clinical-kb-search"); const [pinnedIds, setPinnedIds] = useState(seedPinnedIds); const [mobileDetailOpen, setMobileDetailOpen] = useState(false); @@ -692,6 +1203,24 @@ export function ApplicationsLauncherWorkspace({ }); }, [activeFilter, normalizedQuery]); + const dashboardFilteredApps = useMemo(() => { + return launcherApps.filter((app) => { + const matchesFilter = + dashboardFilter === "all" || + (dashboardFilter === "pinned" + ? pinnedIds.includes(app.id) + : dashboardFilter === "review_due" + ? app.status === "review_due" + : app.sourceBacked); + const matchesQuery = + !normalizedQuery || + [app.title, app.description, app.workflow, app.detail, app.areaLabel].some((value) => + value.toLowerCase().includes(normalizedQuery), + ); + return matchesFilter && matchesQuery; + }); + }, [dashboardFilter, normalizedQuery, pinnedIds]); + function togglePin(id: string) { setPinnedIds((current) => (current.includes(id) ? current.filter((item) => item !== id) : [id, ...current])); } @@ -716,6 +1245,23 @@ export function ApplicationsLauncherWorkspace({ if (firstMatch) selectApplication(firstMatch.id); } + if (isDashboardTools) { + return ( + + ); + } + const workspace = ( <> {isDashboardTools ? ( @@ -755,7 +1301,7 @@ export function ApplicationsLauncherWorkspace({

          {copy.heading}

          -

          +

          {copy.description}

          diff --git a/src/components/clinical-dashboard/ClinicalSidebar.tsx b/src/components/clinical-dashboard/ClinicalSidebar.tsx index 351b1f6ef..199f8fce9 100644 --- a/src/components/clinical-dashboard/ClinicalSidebar.tsx +++ b/src/components/clinical-dashboard/ClinicalSidebar.tsx @@ -153,7 +153,7 @@ export function ClinicalSidebarContent({
          -

          +

          Recent chats

          @@ -195,7 +195,7 @@ export function ClinicalSidebarContent({
          -

          Tools

          +

          Tools

          {sidebarToolItems.map((item) => { diff --git a/src/components/clinical-dashboard/DocumentManagerPanel.tsx b/src/components/clinical-dashboard/DocumentManagerPanel.tsx index 40cdea6b4..245f0b473 100644 --- a/src/components/clinical-dashboard/DocumentManagerPanel.tsx +++ b/src/components/clinical-dashboard/DocumentManagerPanel.tsx @@ -133,7 +133,7 @@ export function SetupChecklist({ checks }: { checks: SetupCheck[] }) { {item.label} @@ -461,7 +461,7 @@ export function IngestionQualityConsole({

          {(Object.keys(counts) as IngestionQualityReviewType[]).map((type) => ( - + {qualityReviewLabels[type]}: {counts[type]} ))} @@ -476,16 +476,16 @@ export function IngestionQualityConsole({
          - + {qualityReviewLabels[item.type]} {item.qualityScore !== null ? ( - + index {item.qualityScore.toFixed(2)} ) : null} {item.extractionQuality ? ( - + extraction:{item.extractionQuality} ) : null} @@ -499,7 +499,7 @@ export function IngestionQualityConsole({ {item.reasons.length ? (
          {item.reasons.slice(0, 4).map((reason) => ( - + {reason} ))} @@ -610,7 +610,7 @@ export function LibraryHealthStrip({ >

          Library health

          - Read-only status + Read-only status
          {items.map((item) => ( @@ -624,7 +624,7 @@ export function LibraryHealthStrip({ )} aria-label={item.actionLabel} > -

          {item.label}

          +

          {item.label}

          {item.value}

          ))} diff --git a/src/components/clinical-dashboard/dashboard-shell.tsx b/src/components/clinical-dashboard/dashboard-shell.tsx index e5cbd1d38..0b1cd8483 100644 --- a/src/components/clinical-dashboard/dashboard-shell.tsx +++ b/src/components/clinical-dashboard/dashboard-shell.tsx @@ -43,7 +43,7 @@ export function SectionHeading({
          -

          {title}

          +

          {title}

          {description && (

          {description} @@ -264,10 +264,10 @@ export function GuideDialog({ open, onClose }: { open: boolean; onClose: () => v key={section.title} className="rounded-xl border border-[color:var(--border)] bg-[color:var(--surface-lux)] p-3 shadow-[var(--shadow-inset)] transition hover:border-[color:var(--border-strong)]" > -

          +

          {section.title}

          -

          +

          {section.body}

      diff --git a/src/components/clinical-dashboard/differentials-home.tsx b/src/components/clinical-dashboard/differentials-home.tsx index c43c79bd6..a08ec0315 100644 --- a/src/components/clinical-dashboard/differentials-home.tsx +++ b/src/components/clinical-dashboard/differentials-home.tsx @@ -61,6 +61,8 @@ type DifferentialResult = { safety?: string; }; +type DifferentialEvidenceState = "source-backed" | "guided"; + const primaryActions: DifferentialAction[] = [ { label: "Search presentations", @@ -190,7 +192,7 @@ function StatusBadge({ status, className }: { status: DifferentialRecord["status return ( {label}; + return {label}; } function Chip({ children }: { children: string }) { return ( - + {children} ); @@ -223,10 +225,10 @@ function SelectionToggle({ selected, onClick, label }: { selected: boolean; onCl + ) : null}
); } @@ -543,10 +573,16 @@ function InterpretationRail({ best, results, sourceCount, + evidenceState, + loading, + onRunSourceSearch, }: { best: DifferentialResult; results: DifferentialResult[]; sourceCount: number; + evidenceState: DifferentialEvidenceState; + loading: boolean; + onRunSourceSearch: () => void; }) { return (