From d81b47154bac48ecb76eac75e2c2cf5237c32ffe Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Fri, 26 Jun 2026 19:39:56 +0800 Subject: [PATCH 01/46] Polish clinical guide answer and document UI --- src/components/ClinicalDashboard.tsx | 6 +++--- .../document-search-results.tsx | 16 ++++++++-------- .../clinical-dashboard/source-actions.tsx | 2 +- src/components/ui-primitives.tsx | 2 +- tests/ui-smoke.spec.ts | 13 ++++++++++++- 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx index 3fddc6e9e..5ec442684 100644 --- a/src/components/ClinicalDashboard.tsx +++ b/src/components/ClinicalDashboard.tsx @@ -3263,8 +3263,6 @@ function StagedAnswerResultSurface({ onCopy={onCopyAnswer} /> - - @@ -6468,7 +6466,9 @@ export function ClinicalDashboard() { [answer], ); const currentRelevance = answer?.relevance ?? answer?.smartPanel?.relevance ?? searchRelevance; - const weakEvidence = isWeakRelevance(currentRelevance) || answer?.retrievalDiagnostics?.gateStatus === "blocked"; + const weakEvidence = + (currentRelevance ? isWeakRelevance(currentRelevance) : answer?.grounded !== true) || + answer?.retrievalDiagnostics?.gateStatus === "blocked"; const safetyFindings = useMemo(() => extractSafetyFindings(answer), [answer]); const bestSource = answer?.bestSource ?? answer?.smartPanel?.bestSource ?? null; const sourceSummary = answer?.evidenceSummary ?? answer?.smartPanel?.evidenceSummary; diff --git a/src/components/clinical-dashboard/document-search-results.tsx b/src/components/clinical-dashboard/document-search-results.tsx index 0545f6262..46a6fe3ba 100644 --- a/src/components/clinical-dashboard/document-search-results.tsx +++ b/src/components/clinical-dashboard/document-search-results.tsx @@ -200,11 +200,11 @@ function compactEvidenceBadges(document: DocumentMatch) { function compactMatchReason(document: DocumentMatch) { const relevance = document.relevance; if (relevance?.verdict === "direct") { - if (document.tableCount > 0) return `Direct table match - ${documentPageLabel(document)}`; - if (document.imageCount > 0) return `Direct image match - ${documentPageLabel(document)}`; - return `Direct source match - ${documentPageLabel(document)}`; + if (document.tableCount > 0) return `Table match - ${documentPageLabel(document)}`; + if (document.imageCount > 0) return `Image match - ${documentPageLabel(document)}`; + return `Source match - ${documentPageLabel(document)}`; } - if (relevance?.verdict === "partial") return `Partial source support - ${documentPageLabel(document)}`; + if (relevance?.verdict === "partial") return `Related source - ${documentPageLabel(document)}`; if (document.matchReason) return document.matchReason; return `${documentKindLabel(document)} - ${documentPageLabel(document)}`; } @@ -226,9 +226,9 @@ function relevancePercent(document: DocumentMatch) { function relevanceTone(document: DocumentMatch) { const verdict = document.relevance?.verdict as string | undefined; const percent = relevancePercent(document); - if (verdict === "direct" || percent >= 90) return { label: "High relevance", short: `${percent}% high` }; - if (verdict === "partial" || percent >= 75) return { label: "Relevant", short: `${percent}% relevant` }; - return { label: "Nearby match", short: `${percent}% nearby` }; + if (verdict === "direct" || percent >= 90) return { label: "Strong match", short: `${percent}% match` }; + if (verdict === "partial" || percent >= 75) return { label: "Related source", short: `${percent}% related` }; + return { label: "Nearby source", short: `${percent}% nearby` }; } function FileTypeTile({ document }: { document: DocumentMatch }) { @@ -694,7 +694,7 @@ export function DocumentSearchResultsPanel({ + ); +} + +function SourceRows() { + return ( +
+ {sources.map(([title, meta, score], index) => ( +
+ + + {title} + {meta} + + {score} +
+ ))} +
+ ); +} + +function SourcePreviewPopover() { + return ( +
+
+
+

+ Sources behind this answer +

+

+ Preview first, then open the source document when needed. +

+
+ 3 sources +
+ +
+ “Monitor FBC/ANC, myocarditis symptoms, metabolic risk, constipation, and shared-care communication during + clozapine initiation.” +
+
+ + + Open PDF drawer + + + + Copy quote + + + + View section + +
+
+ ); +} + +function MobileSheetFrame({ + title, + description, + children, +}: { + title: string; + description: string; + children: ReactNode; +}) { + return ( +
+
+
+
+

{title}

+

{description}

+
+ +
+
{children}
+
+ ); +} + +function EvidenceTabs({ selected }: { selected: string }) { + return ( +
+
+ {tabs.map(([label, count, Icon]) => { + const active = label === selected; + return ( + + ); + })} +
+
+ ); +} + +function FeedbackPanel() { + return ( +
+

Answer review

+

+ Capture misses for retrieval and RAG evals without changing the answer. +

+
+ + + Verified + + + + Needs correction + + + + Source insufficient + +
+
+ ); +} + +function TablePreview({ expanded = false }: { expanded?: boolean }) { + return ( +
+
+ Clozapine monitoring schedule + {expanded ? ( + Verify against source + ) : ( + + )} +
+
+ + + + {["Domain", "Monitoring", "Action"].map((header) => ( + + ))} + + + + {tableRows.map((row) => ( + + {row.map((cell) => ( + + ))} + + ))} + +
+ {header} +
+ {cell} +
+
+
+ ); +} + +function SourceCards() { + return ( +
+ {sources.map(([title, meta, score]) => ( +
+
+ +
+

{title}

+

{meta}

+
+ {score} +
+

+ Retrieved passage includes monitoring, escalation, and shared-care follow-up wording. +

+
+ + + Open + + + + Scope + +
+
+ ))} +
+ ); +} + +function QuoteCards() { + return ( +
+ {[1, 2].map((item) => ( +
+

Exact quote

+
+ “FBC/ANC monitoring, myocarditis symptoms, metabolic review and constipation prevention should be checked + during initiation and ongoing care.” +
+
+ + + Copy + + + + Ask follow-up + +
+
+ ))} +
+ ); +} + +function ImageEvidence() { + const items = [ + { label: "Table crop", icon: Table2 }, + { label: "PDF page region", icon: FileImage }, + ] as const; + + return ( +
+ {items.map(({ label, icon: Icon }) => ( +
+
+ +
+
+

{label}

+

+ Clozapine monitoring visual evidence, page 14. +

+
+
+ ))} +
+ ); +} + +function PdfLinks() { + return ( +
+ {["Clozapine physical health protocol", "Shared-care communication checklist"].map((title, index) => ( +
+ + {title} + + {index === 0 ? "Main source" : "Supporting source"} - page {index === 0 ? 12 : 7} + + + +
+ ))} +
+ ); +} + +function EvidenceMap() { + const rows = [ + ["Monitoring", "Moderate", "2", "Current / locally reviewed"], + ["Escalation", "Strong", "3", "Current / locally reviewed"], + ["Metabolic review", "Partial", "1", "Review due"], + ] as const; + return ( +
+ + + + {["Section", "Support", "Citations", "Source status"].map((header) => ( + + ))} + + + + {rows.map((row) => ( + + {row.map((cell) => ( + + ))} + + ))} + +
+ {header} +
+ {cell} +
+
+ ); +} + +function MobileEvidencePanel({ selected }: { selected: string }) { + return ( + +
+ + + {selected === "Tables" ? : null} + {selected === "Sources" ? : null} + {selected === "Images" ? : null} + {selected === "Quotes" ? : null} + {selected === "PDFs" ? : null} + {selected === "Map" ? : null} +
+
+ ); +} + +function DesktopEvidenceDrawer() { + return ( +
+
+
+

Evidence

+

3 sources, 2 quotes, 1 table, 2 images

+
+ + + Source-backed + +
+
+
+ + +
+
+
+

Pinned source

+

Clozapine physical health protocol

+
+ Current + Locally reviewed +
+
+ +
+
+
+ ); +} + +function TableDialog() { + return ( +
+
+
+

Clinical table

+

Expanded table preview from visual evidence.

+
+ +
+
+ +
+
+ ); +} + +function WeakEvidencePopup() { + return ( +
+
+ + + +
+

Evidence support is limited

+

+ Treat this as a source-finding result until the linked passage is verified. Closest sources are shown, but + there is no strong direct answer support. +

+
+ + + Open closest source + + + + Limit to local/current sources + +
+
+
+
+ ); +} + +export default function AnswerEvidencePopupsMockupPage() { + return ( + +
+
+
+

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

+ +
+ +
+
+
+ +
+ + + +
+ +
+
+ {["Tables", "Sources", "Images", "Quotes", "PDFs", "Map"].map((tab) => ( +
+

+ {tab} tab +

+ +
+ ))} +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ ); +} diff --git a/mockups/user-home-profile/page.tsx b/mockups/user-home-profile/page.tsx new file mode 100644 index 000000000..48a94fa75 --- /dev/null +++ b/mockups/user-home-profile/page.tsx @@ -0,0 +1 @@ +export { default } from "../../src/app/mockups/user-home-profile/page"; diff --git a/src/app/home/page.tsx b/src/app/home/page.tsx new file mode 100644 index 000000000..a6a865122 --- /dev/null +++ b/src/app/home/page.tsx @@ -0,0 +1,12 @@ +import type { Metadata } from "next"; + +import { UserHomeProfilePage } from "@/components/user-home-profile"; + +export const metadata: Metadata = { + title: "Home - Clinical KB", + description: "Logged-in profile home and settings workspace for Clinical KB users.", +}; + +export default function HomeProfileRoute() { + return ; +} diff --git a/src/app/mockups/answer-evidence-popups/page.tsx b/src/app/mockups/answer-evidence-popups/page.tsx new file mode 100644 index 000000000..89b60a4ac --- /dev/null +++ b/src/app/mockups/answer-evidence-popups/page.tsx @@ -0,0 +1,634 @@ +import { + AlertCircle, + BookOpen, + CheckCircle2, + ChevronDown, + Copy, + ExternalLink, + FileImage, + FileText, + Filter, + Layers, + ListChecks, + Maximize2, + Quote, + Search, + ShieldAlert, + Table2, + Target, + X, +} from "lucide-react"; +import type { ReactNode } from "react"; + +const sources = [ + ["Clozapine physical health protocol", "p.12 - current", "92%"], + ["Clozapine monitoring table image evidence", "p.14 - locally reviewed", "86%"], + ["Shared-care communication checklist", "p.7 - review due", "71%"], +] as const; + +const tabs = [ + ["Tables", "1", ListChecks], + ["Sources", "3", Layers], + ["Images", "2", FileImage], + ["Quotes", "2", Quote], + ["PDFs", "2", FileText], + ["Map", "4", BookOpen], +] as const; + +const tableRows = [ + ["FBC/ANC", "Baseline, weekly initially, then per protocol", "Hold/escalate if ANC threshold breached"], + [ + "Myocarditis", + "Symptoms, pulse, troponin/CRP where locally required", + "Urgent review for fever, chest pain, dyspnoea", + ], + ["Metabolic", "Weight, waist, lipids, glucose/HbA1c", "Shared-care follow-up"], +] as const; + +function Shell({ children }: { children: ReactNode }) { + return ( +
+
+
+
+ + + +

+ Clinical KB mockup +

+
+

+ Answer evidence popup states +

+

+ Static mockups of the current popup and drawer patterns opened from answer evidence: source preview, + evidence sheet tabs, desktop drawer, table expansion, and weak-evidence states. +

+
+ {children} +
+
+ ); +} + +function Section({ title, body, children }: { title: string; body: string; children: ReactNode }) { + return ( +
+
+
+

{title}

+

{body}

+
+
+ {children} +
+ ); +} + +function Pill({ children, tone = "neutral" }: { children: ReactNode; tone?: "neutral" | "info" | "success" | "warn" }) { + const toneClass = + tone === "success" + ? "border-[color:var(--success-border)] bg-[color:var(--success-soft)] text-[color:var(--success)]" + : tone === "warn" + ? "border-[color:var(--warning-border)] bg-[color:var(--warning-soft)] text-[color:var(--warning)]" + : tone === "info" + ? "border-[color:var(--info-border)] bg-[color:var(--info-soft)] text-[color:var(--info)]" + : "border-[color:var(--border-lux)] bg-[color:var(--surface-raised)] text-[color:var(--text-muted)]"; + return ( + + {children} + + ); +} + +function Action({ children, primary = false }: { children: ReactNode; primary?: boolean }) { + return ( + + ); +} + +function SourceRows() { + return ( +
+ {sources.map(([title, meta, score], index) => ( +
+ + + {title} + {meta} + + {score} +
+ ))} +
+ ); +} + +function SourcePreviewPopover() { + return ( +
+
+
+

+ Sources behind this answer +

+

+ Preview first, then open the source document when needed. +

+
+ 3 sources +
+ +
+ “Monitor FBC/ANC, myocarditis symptoms, metabolic risk, constipation, and shared-care communication during + clozapine initiation.” +
+
+ + + Open PDF drawer + + + + Copy quote + + + + View section + +
+
+ ); +} + +function MobileSheetFrame({ + title, + description, + children, +}: { + title: string; + description: string; + children: ReactNode; +}) { + return ( +
+
+
+
+

{title}

+

{description}

+
+ +
+
{children}
+
+ ); +} + +function EvidenceTabs({ selected }: { selected: string }) { + return ( +
+
+ {tabs.map(([label, count, Icon]) => { + const active = label === selected; + return ( + + ); + })} +
+
+ ); +} + +function FeedbackPanel() { + return ( +
+

Answer review

+

+ Capture misses for retrieval and RAG evals without changing the answer. +

+
+ + + Verified + + + + Needs correction + + + + Source insufficient + +
+
+ ); +} + +function TablePreview({ expanded = false }: { expanded?: boolean }) { + return ( +
+
+ Clozapine monitoring schedule + {expanded ? ( + Verify against source + ) : ( + + )} +
+
+ + + + {["Domain", "Monitoring", "Action"].map((header) => ( + + ))} + + + + {tableRows.map((row) => ( + + {row.map((cell) => ( + + ))} + + ))} + +
+ {header} +
+ {cell} +
+
+
+ ); +} + +function SourceCards() { + return ( +
+ {sources.map(([title, meta, score]) => ( +
+
+ +
+

{title}

+

{meta}

+
+ {score} +
+

+ Retrieved passage includes monitoring, escalation, and shared-care follow-up wording. +

+
+ + + Open + + + + Scope + +
+
+ ))} +
+ ); +} + +function QuoteCards() { + return ( +
+ {[1, 2].map((item) => ( +
+

Exact quote

+
+ “FBC/ANC monitoring, myocarditis symptoms, metabolic review and constipation prevention should be checked + during initiation and ongoing care.” +
+
+ + + Copy + + + + Ask follow-up + +
+
+ ))} +
+ ); +} + +function ImageEvidence() { + const items = [ + { label: "Table crop", icon: Table2 }, + { label: "PDF page region", icon: FileImage }, + ] as const; + + return ( +
+ {items.map(({ label, icon: Icon }) => ( +
+
+ +
+
+

{label}

+

+ Clozapine monitoring visual evidence, page 14. +

+
+
+ ))} +
+ ); +} + +function PdfLinks() { + return ( +
+ {["Clozapine physical health protocol", "Shared-care communication checklist"].map((title, index) => ( +
+ + {title} + + {index === 0 ? "Main source" : "Supporting source"} - page {index === 0 ? 12 : 7} + + + +
+ ))} +
+ ); +} + +function EvidenceMap() { + const rows = [ + ["Monitoring", "Moderate", "2", "Current / locally reviewed"], + ["Escalation", "Strong", "3", "Current / locally reviewed"], + ["Metabolic review", "Partial", "1", "Review due"], + ] as const; + return ( +
+ + + + {["Section", "Support", "Citations", "Source status"].map((header) => ( + + ))} + + + + {rows.map((row) => ( + + {row.map((cell) => ( + + ))} + + ))} + +
+ {header} +
+ {cell} +
+
+ ); +} + +function MobileEvidencePanel({ selected }: { selected: string }) { + return ( + +
+ + + {selected === "Tables" ? : null} + {selected === "Sources" ? : null} + {selected === "Images" ? : null} + {selected === "Quotes" ? : null} + {selected === "PDFs" ? : null} + {selected === "Map" ? : null} +
+
+ ); +} + +function DesktopEvidenceDrawer() { + return ( +
+
+
+

Evidence

+

3 sources, 2 quotes, 1 table, 2 images

+
+ + + Source-backed + +
+
+
+ + +
+
+
+

Pinned source

+

Clozapine physical health protocol

+
+ Current + Locally reviewed +
+
+ +
+
+
+ ); +} + +function TableDialog() { + return ( +
+
+
+

Clinical table

+

Expanded table preview from visual evidence.

+
+ +
+
+ +
+
+ ); +} + +function WeakEvidencePopup() { + return ( +
+
+ + + +
+

Evidence support is limited

+

+ Treat this as a source-finding result until the linked passage is verified. Closest sources are shown, but + there is no strong direct answer support. +

+
+ + + Open closest source + + + + Limit to local/current sources + +
+
+
+
+ ); +} + +export default function AnswerEvidencePopupsMockupPage() { + return ( + +
+
+
+

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

+ +
+ +
+
+
+ +
+ + + +
+ +
+
+ {["Tables", "Sources", "Images", "Quotes", "PDFs", "Map"].map((tab) => ( +
+

+ {tab} tab +

+ +
+ ))} +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ ); +} diff --git a/src/app/mockups/user-home-profile/page.tsx b/src/app/mockups/user-home-profile/page.tsx new file mode 100644 index 000000000..f441e1e9b --- /dev/null +++ b/src/app/mockups/user-home-profile/page.tsx @@ -0,0 +1,12 @@ +import type { Metadata } from "next"; + +import { UserHomeProfilePage } from "@/components/user-home-profile"; + +export const metadata: Metadata = { + title: "User Home Profile Mockup - Clinical KB", + description: "ChatGPT-style logged-in profile home mockup for Clinical KB users.", +}; + +export default function UserHomeProfileMockupRoute() { + return ; +} diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx index 5ec442684..cc28fa054 100644 --- a/src/components/ClinicalDashboard.tsx +++ b/src/components/ClinicalDashboard.tsx @@ -726,10 +726,14 @@ function SourcePreviewContent({ bestSource, previewSources, quoteText, + copiedQuote, + onCopyQuote, }: { bestSource: BestSourceRecommendation; previewSources: CapsulePreviewSource[]; quoteText?: string | null; + copiedQuote: boolean; + onCopyQuote: () => void; }) { return ( <> @@ -777,10 +781,12 @@ function SourcePreviewContent({ Open PDF drawer - + {quoteText ? ( + + ) : null} View section @@ -809,6 +815,7 @@ function NaturalLanguageAnswer({ onCopy: () => void; }) { const [sourcePreviewOpen, setSourcePreviewOpen] = useState(false); + const [copiedSourceQuote, setCopiedSourceQuote] = useState(false); const sourceCapsuleRef = useRef(null); const usePreviewSheet = useMobilePreviewSheet(); const cleaned = primaryAnswerDisplayText(text); @@ -816,6 +823,16 @@ function NaturalLanguageAnswer({ const capsuleText = sourceCapsuleText({ sourceCount, weakEvidence, grounded }); const previewSources = capsulePreviewSources(bestSource, sources); const quoteText = bestSource?.quote || bestSource?.snippet; + async function copySourceQuote() { + if (!quoteText) return; + try { + await navigator.clipboard.writeText(quoteText); + setCopiedSourceQuote(true); + window.setTimeout(() => setCopiedSourceQuote(false), 1600); + } catch { + setCopiedSourceQuote(false); + } + } const sourceCapsuleButton = (
) : null} {bestSource ? (
- +
) : null}
@@ -2608,7 +2637,6 @@ function MobileEvidenceSheetContent({ return (
-
+
); } diff --git a/src/components/user-home-profile/index.ts b/src/components/user-home-profile/index.ts new file mode 100644 index 000000000..15768b063 --- /dev/null +++ b/src/components/user-home-profile/index.ts @@ -0,0 +1 @@ +export { default as UserHomeProfilePage } from "./user-home-profile-page"; diff --git a/src/components/user-home-profile/user-home-profile-page.tsx b/src/components/user-home-profile/user-home-profile-page.tsx new file mode 100644 index 000000000..6ae06879d --- /dev/null +++ b/src/components/user-home-profile/user-home-profile-page.tsx @@ -0,0 +1,482 @@ +import { + Activity, + AlertTriangle, + Bell, + BookOpenCheck, + Check, + ChevronRight, + Clock, + Database, + FileText, + Folder, + Home, + Lock, + LogOut, + MessageSquare, + Palette, + Search, + Settings, + Shield, + SlidersHorizontal, + Star, + User, + Wrench, + type LucideIcon, +} from "lucide-react"; + +import { cn } from "@/components/ui-primitives"; + +const navItems = [ + ["Home", Home, true], + ["Chat", MessageSquare, false], + ["Documents", Folder, false], + ["Favourites", Star, false], + ["Tools", Wrench, false], + ["Settings", Settings, false], +] as const; + +const profileFields = [ + ["Role", "Consultant psychiatrist"], + ["Workspace", "Clinical KB Database"], + ["Organisation", "Private workspace"], + ["Primary use", "Guideline search and source-backed answers"], +] as const; + +const settingsCards: Array<{ + title: string; + body: string; + status: string; + icon: LucideIcon; + tone: "default" | "success" | "warning"; +}> = [ + { + title: "Profile details", + body: "Name, clinical role, workspace label, and default contact identity.", + status: "Complete", + icon: User, + tone: "success", + }, + { + title: "Appearance", + body: "Theme, compact density, answer typography, and reduced motion preference.", + status: "System", + icon: Palette, + tone: "default", + }, + { + title: "Notifications", + body: "Source review reminders, import status, failed jobs, and governance prompts.", + status: "3 enabled", + icon: Bell, + tone: "default", + }, + { + title: "Privacy and data", + body: "Query history, saved searches, exports, retention windows, and local privacy controls.", + status: "Review", + icon: Shield, + tone: "warning", + }, + { + title: "Clinical preferences", + body: "Default jurisdiction, source weighting, answer mode, and safety note behaviour.", + status: "WA default", + icon: SlidersHorizontal, + tone: "default", + }, + { + title: "Security", + body: "Session devices, account access, recovery settings, and workspace lock state.", + status: "Protected", + icon: Lock, + tone: "success", + }, +]; + +const recentWork = [ + ["Clozapine monitoring source review", "Opened 12 min ago", "Review due"], + ["Lithium renal safety answer", "Copied with citations", "Source-backed"], + ["ECT consent pathway", "Added to favourites", "Current"], + ["Acamprosate prescribing page", "Viewed from medication search", "Reviewed"], +] as const; + +const quickActions = [ + ["New source-backed chat", MessageSquare], + ["Upload documents", FileText], + ["Review source reminders", BookOpenCheck], + ["Open data controls", Database], +] as const; + +function StatusPill({ children, tone = "default" }: { children: string; tone?: "default" | "success" | "warning" }) { + return ( + + {children} + + ); +} + +function IconButton({ icon: Icon, label }: { icon: LucideIcon; label: string }) { + return ( + + ); +} + +function Sidebar() { + return ( + + ); +} + +function ProfileSummary() { + return ( +
+
+
+ + JS + +
+
+

+ Good afternoon, Dr Simpson +

+ Verified +
+

+ Your home page keeps account settings, clinical preferences, recent work, source review prompts, and data + controls in one calm workspace after login. +

+
+ + +
+
+
+ +
+

Account status

+
+ {[ + ["Session", "Active"], + ["Data mode", "Private"], + ["Default source", "Current first"], + ].map(([label, value]) => ( +
+ {label} + {value} +
+ ))} +
+
+
+
+ ); +} + +function SettingsCard({ card }: { card: (typeof settingsCards)[number] }) { + const Icon = card.icon; + + return ( + + ); +} + +function RightColumn() { + return ( + + ); +} + +export default function UserHomeProfilePage() { + return ( +
+
+ + +
+
+
+
+ + +
+ + + + JS + +
+
+ +
+ + +
+
+
+
+
+
+ +

Profile

+
+

+ Simple account identity and default clinical workspace context. +

+
+ Ready after login +
+
+ {profileFields.map(([label, value]) => ( +
+

{label}

+

{value}

+
+ ))} +
+
+ +
+
+
+
+ +

Settings

+
+

+ ChatGPT-style rows with clear status, compact controls, and quick access to deeper settings. +

+
+ +
+
+ {settingsCards.map((card) => ( + + ))} +
+
+ +
+
+ +

Recent clinical work

+
+
+ {recentWork.map(([title, detail, status]) => ( + + ))} +
+
+
+ + +
+ +
+
+
+ +

+ Recommended home structure +

+
+ Mockup direction +
+
+ {[ + ["Start with identity", "Show the signed-in user, workspace, role, and session safety before tools."], + ["Keep settings scannable", "Use rows and compact cards rather than a decorative dashboard grid."], + [ + "Surface governance", + "Keep source review and privacy controls visible without turning them into warnings everywhere.", + ], + ].map(([title, body]) => ( +
+

{title}

+

{body}

+
+ ))} +
+
+
+
+
+
+ ); +} diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index d4fe965f8..f28f4f536 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -580,6 +580,9 @@ test.describe("Clinical KB UI smoke coverage", () => { await expect(sourcePreview.getByTestId("source-capsule-preview-row")).toHaveCount(2); await expect(sourcePreview.getByRole("link", { name: /Open PDF drawer/i })).toBeVisible(); await expect(page.getByRole("dialog", { name: /PDF|document/i })).toHaveCount(0); + await page.context().grantPermissions(["clipboard-read", "clipboard-write"], { origin: "http://localhost:4298" }); + await sourcePreview.getByRole("button", { name: "Copy quote" }).click(); + await expect(sourcePreview.getByRole("button", { name: "Copied quote" })).toBeVisible(); await expectNoPageHorizontalOverflow(page); await page.keyboard.press("Escape"); await expect(sourceSheet).toHaveCount(0); @@ -679,6 +682,15 @@ test.describe("Clinical KB UI smoke coverage", () => { const evidenceSheet = page.getByRole("dialog", { name: "Evidence" }); await expect(evidenceSheet).toBeVisible(); await expect(evidenceSheet.getByTestId("mobile-evidence-tabs")).toBeVisible(); + const evidenceSheetOrder = await evidenceSheet.evaluate((element) => { + const tabs = element.querySelector('[data-testid="mobile-evidence-tabs"]'); + const review = element.querySelector('[data-testid="answer-review-panel"]'); + return { + tabsTop: tabs?.getBoundingClientRect().top ?? 9999, + reviewTop: review?.getBoundingClientRect().top ?? 9999, + }; + }); + expect(evidenceSheetOrder.tabsTop).toBeLessThan(evidenceSheetOrder.reviewTop); await expect(evidenceSheet.getByTestId("mobile-evidence-tab-tables")).toHaveAttribute("aria-selected", "true"); await expect(evidenceSheet.getByTestId("mobile-evidence-panel-tables")).toBeVisible(); await expectMinTouchTarget(evidenceSheet.getByTestId("mobile-evidence-tab-tables")); From 1b67b31008cb535acbdf9dfdca32cc34c7163506 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Fri, 26 Jun 2026 22:24:09 +0800 Subject: [PATCH 03/46] Fix clipboard permission origin in UI smoke test --- tests/ui-smoke.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index f28f4f536..cc613dbd4 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -580,7 +580,9 @@ test.describe("Clinical KB UI smoke coverage", () => { await expect(sourcePreview.getByTestId("source-capsule-preview-row")).toHaveCount(2); await expect(sourcePreview.getByRole("link", { name: /Open PDF drawer/i })).toBeVisible(); await expect(page.getByRole("dialog", { name: /PDF|document/i })).toHaveCount(0); - await page.context().grantPermissions(["clipboard-read", "clipboard-write"], { origin: "http://localhost:4298" }); + await page.context().grantPermissions(["clipboard-read", "clipboard-write"], { + origin: new URL(page.url()).origin, + }); await sourcePreview.getByRole("button", { name: "Copy quote" }).click(); await expect(sourcePreview.getByRole("button", { name: "Copied quote" })).toBeVisible(); await expectNoPageHorizontalOverflow(page); From 71850a5d9dd0b0ba0e9e91efce4b8b707b19dc7b Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 14:55:52 +0800 Subject: [PATCH 04/46] Update clinical guide for Node 24 --- .github/workflows/ci.yml | 2 +- .gitignore | 1 + README.md | 4 +- docs/process-hardening.md | 5 +- docs/production-readiness-checklist.md | 4 +- docs/project-alignment-cleanup.md | 2 +- docs/redesign/06-verification.md | 2 +- docs/supabase-migration-reconciliation.md | 31 + mockups/README.md | 2 + mockups/answer-evidence-popups/page.tsx | 342 ++++--- package-lock.json | 422 ++++---- package.json | 21 +- .../mockups/answer-evidence-popups/page.tsx | 342 ++++--- src/app/mockups/mode-dropdown/page.tsx | 229 +++++ src/components/ClinicalDashboard.tsx | 35 +- src/components/DocumentTagCloud.tsx | 6 +- src/components/DocumentViewer.tsx | 508 ++++------ .../document-search-results.tsx | 358 +++---- .../clinical-dashboard/document-ui.tsx | 170 ++++ .../master-search-header.tsx | 196 +++- .../user-home-profile-page.tsx | 933 ++++++++++++------ ...20000_phase7_retrieval_rpc_performance.sql | 2 + tests/check-runtime.test.ts | 2 +- tests/ui-smoke.spec.ts | 67 +- tests/ui-stress.spec.ts | 4 +- vitest.config.ts => vitest.config.mts | 6 +- 26 files changed, 2308 insertions(+), 1388 deletions(-) create mode 100644 docs/supabase-migration-reconciliation.md create mode 100644 src/app/mockups/mode-dropdown/page.tsx create mode 100644 src/components/clinical-dashboard/document-ui.tsx rename vitest.config.ts => vitest.config.mts (82%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea611ba43..e1b63a6f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version-file: ".nvmrc" + node-version: 24 cache: npm - name: Install dependencies diff --git a/.gitignore b/.gitignore index 79da87388..c66b66fc3 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ dev-server*.log # generated sample uploads /sample-documents/ +/tmp/ # python __pycache__/ diff --git a/README.md b/README.md index 9d654017d..9b3542d7c 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ questions with source citations that link back to the original PDF/document. ## Setup -1. Use Node.js 24.x. CI runs on Node 24, and `.nvmrc` / `.node-version` - pin the same runtime for local version managers. +1. Use Node.js 24.x with npm 11.x. CI runs on Node 24, and `.nvmrc` / + `.node-version` pin the same runtime for local version managers. 2. Copy `.env.example` to `.env.local` and fill in Supabase and OpenAI values. 3. Confirm the Supabase target: diff --git a/docs/process-hardening.md b/docs/process-hardening.md index b1ee87cb4..b6742a82f 100644 --- a/docs/process-hardening.md +++ b/docs/process-hardening.md @@ -33,8 +33,9 @@ This document turns the current process review into phased, durable repo practic ## Phase 4 - Release maturity -- `npm run check:runtime` is the strict release runtime gate and is now part of `npm run verify:release`; it fails outside Node 24.x. -- CI runs `npm run check:runtime` after dependency install so branch verification cannot silently drift to Node 25+. +- `npm run check:runtime` is the strict runtime gate and is now part of `npm run verify:cheap`, `npm run verify:ui`, and `npm run verify:release`; it fails outside Node 24.x or npm 11.x when run through npm. +- CI runs `npm run check:runtime` after dependency install so branch verification cannot silently drift away from Node 24. +- `npm run check:edge:functions` is the Deno type gate for the Supabase `indexing-v3-agent` Edge Function. - Decide whether CI should run all Playwright browser projects on protected branches, release branches, or a scheduled workflow instead of every push. - Add explicit review ownership for clinical source governance, outdated-source handling, incident review, and decommission decisions. - Record production-readiness outcomes in release notes whenever clinical workflow, source governance, privacy, or deployment assumptions change. diff --git a/docs/production-readiness-checklist.md b/docs/production-readiness-checklist.md index dc9cda358..cad638d3b 100644 --- a/docs/production-readiness-checklist.md +++ b/docs/production-readiness-checklist.md @@ -3,7 +3,7 @@ This is the runbook to make the app publishable in one focused pass. - Branch: `codex/premium-redesign` (do not touch `.env` / secrets directly). -- Runtime target: Next.js 16.2.7, Node 24.x, npm >= 11. +- Runtime target: Next.js 16.2.7, Node 24.x, npm 11.x. - Supabase target: `sjrfecxgysukkwxsowpy` (`Clinical KB Database`). ## Immediate completion targets @@ -21,7 +21,7 @@ This is the runbook to make the app publishable in one focused pass. - used in CI and non-blocking on local-only secret absence. - [x] Added strict runtime release gate: - `npm run check:runtime` - - enforces Node 24.x before `npm run verify:release`. + - enforces Node 24.x and npm 11.x before broad local and release verification. ## Remaining high-priority publish items (same day) diff --git a/docs/project-alignment-cleanup.md b/docs/project-alignment-cleanup.md index 03581db57..f5d452301 100644 --- a/docs/project-alignment-cleanup.md +++ b/docs/project-alignment-cleanup.md @@ -18,7 +18,7 @@ - CI verifies the project on Node.js 24, so local development should also use Node.js 24.x. - `.nvmrc`, `.node-version`, and `package.json` `engines` all declare the Node 24 runtime expectation. -- New cleanup or dependency work should be verified on Node 24 before release, even when local shells happen to use newer Node versions. +- New cleanup or dependency work should be verified on Node 24 before release, even when local shells happen to use a different Node version. ## Stale branch audit diff --git a/docs/redesign/06-verification.md b/docs/redesign/06-verification.md index 4e7f7df15..8cfff31ab 100644 --- a/docs/redesign/06-verification.md +++ b/docs/redesign/06-verification.md @@ -51,7 +51,7 @@ Unverified or limited: | Lint | `npm run lint` (eslint 9.39.4) | ✅ pass, no warnings | | Smoke (chromium) | `npx playwright test tests/ui-smoke.spec.ts --project=chromium` | ✅ **22/22 pass** | -Clean-install caveat resolved in the reconciliation branch: `eslint` was pinned back to the latest compatible 9.x range because `eslint-config-next@16.2.7` pulls `eslint-plugin-react@7.37.5`, whose peer range supports ESLint 9 but not ESLint 10. `npm ci` now completes and `npm run lint` passes under Node 22.22.3. +Clean-install caveat resolved in the reconciliation branch: `eslint` was pinned back to the latest compatible 9.x range because `eslint-config-next@16.2.7` pulls `eslint-plugin-react@7.37.5`, whose peer range supports ESLint 9 but not ESLint 10. The current runtime target is Node 24.x. ## 2. Smoke detail diff --git a/docs/supabase-migration-reconciliation.md b/docs/supabase-migration-reconciliation.md new file mode 100644 index 000000000..406d53d49 --- /dev/null +++ b/docs/supabase-migration-reconciliation.md @@ -0,0 +1,31 @@ +# Supabase Migration Reconciliation + +Last reviewed: 2026-06-27 + +Target project: Clinical KB Database (`sjrfecxgysukkwxsowpy`) + +## Policy + +- Do not use `supabase db push` while local and remote migration history are divergent. +- Use `supabase migration repair --linked --status applied ` only when live database evidence proves the migration effect already exists. +- Leave all other local-only migrations unrepaired until their effects are verified or deliberately applied. + +## Verified Applied + +These versions are safe to mark applied in Supabase migration history: + +- `20260625033425` - `document_strict_gate_status` exists, `repair_strict_enrichment_gate_batch(integer)` exists, service role can read/execute, and anon cannot read/execute. +- `20260625033944` - `complete_strict_enrichment_job(uuid, uuid, text, text, text)` exists, service role can execute, and anon cannot execute. +- `20260626000000` - duplicate index `ingestion_job_stages_doc_idx` is absent and canonical index `ingestion_job_stages_document_started_idx` exists. + +## Skipped + +All other local-only migrations from `supabase migration list --linked` remain unrepaired until they are individually verified. This includes older search/retrieval/API-rate-limit migrations and the current `20260626020000` retrieval RPC performance migration. + +## Verification Commands + +```powershell +npx supabase migration list --linked +npx supabase db advisors --linked +npx supabase db query --linked "select to_regclass('public.document_strict_gate_status') as gate_view, to_regprocedure('public.repair_strict_enrichment_gate_batch(integer)') as repair_rpc, to_regprocedure('public.complete_strict_enrichment_job(uuid, uuid, text, text, text)') as complete_rpc, to_regclass('public.ingestion_job_stages_doc_idx') as duplicate_index, to_regclass('public.ingestion_job_stages_document_started_idx') as canonical_stage_index;" +``` diff --git a/mockups/README.md b/mockups/README.md index 99ecd49b8..7b96b9157 100644 --- a/mockups/README.md +++ b/mockups/README.md @@ -8,6 +8,7 @@ This folder collects the current mockup files for the Clinical KB Database proje - `medication-prescribing/page.tsx` - copied from `src/app/mockups/medication-prescribing/page.tsx` - `answer-evidence-popups/page.tsx` - copied from `src/app/mockups/answer-evidence-popups/page.tsx` - `user-home-profile/page.tsx` - copied from `src/app/mockups/user-home-profile/page.tsx` +- `mode-dropdown` - runnable mockup only, in `src/app/mockups/mode-dropdown/page.tsx` ## App routes @@ -17,3 +18,4 @@ The runnable versions remain in the Next.js app route tree: - `/mockups/medication-prescribing` - `/mockups/answer-evidence-popups` - `/mockups/user-home-profile` +- `/mockups/mode-dropdown` diff --git a/mockups/answer-evidence-popups/page.tsx b/mockups/answer-evidence-popups/page.tsx index 89b60a4ac..cacda1185 100644 --- a/mockups/answer-evidence-popups/page.tsx +++ b/mockups/answer-evidence-popups/page.tsx @@ -21,9 +21,9 @@ import { import type { ReactNode } from "react"; const sources = [ - ["Clozapine physical health protocol", "p.12 - current", "92%"], - ["Clozapine monitoring table image evidence", "p.14 - locally reviewed", "86%"], - ["Shared-care communication checklist", "p.7 - review due", "71%"], + ["Clozapine physical health protocol", "p.12 - current policy", "Direct", "92%"], + ["Clozapine monitoring table image evidence", "p.14 - locally reviewed", "Table", "86%"], + ["Shared-care communication checklist", "p.7 - review due", "Related", "71%"], ] as const; const tabs = [ @@ -45,26 +45,43 @@ const tableRows = [ ["Metabolic", "Weight, waist, lipids, glucose/HbA1c", "Shared-care follow-up"], ] as const; +const focusRing = + "focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--clinical-chat-teal)]"; + function Shell({ children }: { children: ReactNode }) { return ( -
+
-
- - - -

- Clinical KB mockup -

+
+
+
+ + + +

+ Clinical KB mockup +

+
+

+ Answer evidence popup states +

+

+ A polished set of evidence popups showing what clinicians see before opening source PDFs: source + previews, evidence tabs, review controls, table expansion, and weak-support warnings. +

+
+
+

+ Design priorities +

+
+ Verify source first + Fast source opening + Mobile sheet parity +
+
-

- Answer evidence popup states -

-

- Static mockups of the current popup and drawer patterns opened from answer evidence: source preview, - evidence sheet tabs, desktop drawer, table expansion, and weak-evidence states. -

{children}
@@ -74,7 +91,7 @@ function Shell({ children }: { children: ReactNode }) { function Section({ title, body, children }: { title: string; body: string; children: ReactNode }) { return ( -
+

{title}

@@ -97,7 +114,7 @@ function Pill({ children, tone = "neutral" }: { children: ReactNode; tone?: "neu : "border-[color:var(--border-lux)] bg-[color:var(--surface-raised)] text-[color:var(--text-muted)]"; return ( svg]:h-3.5 [&>svg]:w-3.5 [&>svg]:shrink-0`} > {children} @@ -105,13 +122,14 @@ function Pill({ children, tone = "neutral" }: { children: ReactNode; tone?: "neu } function Action({ children, primary = false }: { children: ReactNode; primary?: boolean }) { + const baseClass = `inline-flex min-h-10 max-w-full min-w-0 items-center justify-center gap-2 rounded-md px-3 text-center text-xs font-semibold leading-tight transition hover:-translate-y-px hover:shadow-[var(--shadow-tight)] active:translate-y-0 ${focusRing} [&>svg]:h-3.5 [&>svg]:w-3.5 [&>svg]:shrink-0 sm:min-h-11 sm:text-sm`; return ( + ); +} + +function SourceCapsule() { + return ( + + ); +} + function SourceRows() { return (
- {sources.map(([title, meta, score], index) => ( -
- - - {title} - {meta} - - {score} + {sources.map(([title, meta, support, score], index) => ( +
+
))}
); } +function ActionRow({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function ButtonText({ children }: { children: ReactNode }) { + return {children}; +} + function SourcePreviewPopover() { return (
-

- Sources behind this answer -

+

Source preview

- Preview first, then open the source document when needed. + Check the best passage, status, and source action before opening the PDF.

- 3 sources + 3 sources
“Monitor FBC/ANC, myocarditis symptoms, metabolic risk, constipation, and shared-care communication during clozapine initiation.”
-
+ - Open PDF drawer + Open source - Copy quote + Copy quote - View section + View cited section -
+
); } @@ -190,25 +251,23 @@ function MobileSheetFrame({ children: ReactNode; }) { return ( -
+
-
+

{title}

-

{description}

+

{description}

- +
-
{children}
+
{children}
); } function EvidenceTabs({ selected }: { selected: string }) { return ( -
+
{tabs.map(([label, count, Icon]) => { const active = label === selected; @@ -216,15 +275,19 @@ function EvidenceTabs({ selected }: { selected: string }) { ); })} @@ -236,9 +299,9 @@ function EvidenceTabs({ selected }: { selected: string }) { function FeedbackPanel() { return (
-

Answer review

+

Clinical verification

- Capture misses for retrieval and RAG evals without changing the answer. + Mark whether the linked evidence is safe to use, needs correction, or is source-insufficient.

@@ -258,17 +321,39 @@ function FeedbackPanel() { ); } +function EvidenceSummaryMini({ selected }: { selected: string }) { + return ( +
+
+
+

{selected} evidence

+

+ Showing only the items used to support this answer. +

+
+ Source-backed +
+
+ ); +} + function TablePreview({ expanded = false }: { expanded?: boolean }) { return (
- Clozapine monitoring schedule + Clozapine monitoring schedule {expanded ? ( Verify against source ) : ( - + )}
@@ -308,32 +393,37 @@ function TablePreview({ expanded = false }: { expanded?: boolean }) { function SourceCards() { return (
- {sources.map(([title, meta, score]) => ( + {sources.map(([title, meta, support, score]) => (
- +

{title}

{meta}

- {score} + + {support} + {score} +

- Retrieved passage includes monitoring, escalation, and shared-care follow-up wording. + Passage supports monitoring and escalation wording. Open source to inspect the highlighted PDF section.

-
+ - Open + Open source - Scope + Scope to this -
+
))}
@@ -348,21 +438,24 @@ function QuoteCards() { key={item} className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-raised)] p-3 shadow-[var(--shadow-tight)]" > -

Exact quote

+
+

Exact quote

+ {item === 1 ? "p.12" : "p.14"} +
- “FBC/ANC monitoring, myocarditis symptoms, metabolic review and constipation prevention should be checked - during initiation and ongoing care.” + “FBC/ANC monitoring, myocarditis symptoms, metabolic review and constipation prevention should be + checked during initiation and ongoing care.”
-
+ - Copy + Copy - Ask follow-up + Ask about quote -
+ ))}
@@ -371,25 +464,26 @@ function QuoteCards() { function ImageEvidence() { const items = [ - { label: "Table crop", icon: Table2 }, - { label: "PDF page region", icon: FileImage }, + { label: "Table crop", icon: Table2, body: "Monitoring domains extracted from a table image." }, + { label: "PDF page region", icon: FileImage, body: "Page crop used to check source layout and nearby wording." }, ] as const; return ( -
- {items.map(({ label, icon: Icon }) => ( +
+ {items.map(({ label, icon: Icon, body }) => (
-
- +
+
+ + p.14 +

{label}

-

- Clozapine monitoring visual evidence, page 14. -

+

{body}

))} @@ -401,18 +495,22 @@ function PdfLinks() { return (
{["Clozapine physical health protocol", "Shared-care communication checklist"].map((title, index) => ( -
{title} - {index === 0 ? "Main source" : "Supporting source"} - page {index === 0 ? 12 : 7} + {index === 0 ? "Main source" : "Supporting source"} · opens at page {index === 0 ? 12 : 7} - -
+ + {index === 0 ? "p.12" : "p.7"} + + + ))}
); @@ -425,8 +523,8 @@ function EvidenceMap() { ["Metabolic review", "Partial", "1", "Review due"], ] as const; return ( -
- +
+
{["Section", "Support", "Citations", "Source status"].map((header) => ( @@ -461,8 +559,8 @@ function MobileEvidencePanel({ selected }: { selected: string }) { title="Evidence" description="Sources, quotes, tables, images, PDFs, and support map for this answer." > -
- +
+ {selected === "Tables" ? : } {selected === "Tables" ? : null} {selected === "Sources" ? : null} @@ -480,8 +578,10 @@ function DesktopEvidenceDrawer() {
-

Evidence

-

3 sources, 2 quotes, 1 table, 2 images

+

Evidence review

+

+ Verify the answer against cited passages before using it clinically. +

@@ -514,12 +614,12 @@ function TableDialog() {
-

Clinical table

-

Expanded table preview from visual evidence.

+

Clozapine monitoring table

+

+ Expanded from visual evidence. Use the source PDF for final verification. +

- +
@@ -531,26 +631,29 @@ function TableDialog() { function WeakEvidencePopup() { return (
-
+
-

Evidence support is limited

+
+

Evidence support is limited

+ Do not copy into notes +

- Treat this as a source-finding result until the linked passage is verified. Closest sources are shown, but - there is no strong direct answer support. + Treat this as source finding only. The closest indexed passages are nearby, but they do not directly support + a clinical answer yet.

-
+ - Open closest source + Open closest passage - Limit to local/current sources + Restrict to current local sources -
+
@@ -570,10 +673,7 @@ export default function AnswerEvidencePopupsMockupPage() { Clozapine monitoring should include FBC/ANC, myocarditis symptoms, metabolic checks, constipation prevention, and shared-care communication.

- +
@@ -594,9 +694,9 @@ export default function AnswerEvidencePopupsMockupPage() {
-
+
{["Tables", "Sources", "Images", "Quotes", "PDFs", "Map"].map((tab) => (

@@ -610,7 +710,7 @@ export default function AnswerEvidencePopupsMockupPage() {

@@ -624,7 +724,7 @@ export default function AnswerEvidencePopupsMockupPage() {
diff --git a/package-lock.json b/package-lock.json index 68b8b1966..3df487ba4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,26 +10,26 @@ "hasInstallScript": true, "dependencies": { "@next/env": "^16.2.9", - "@supabase/supabase-js": "^2.108.2", + "@supabase/supabase-js": "^2.107.0", "exceljs": "^4.4.0", "jszip": "^3.10.1", "lucide-react": "^1.21.0", "mammoth": "^1.12.0", - "next": "16.2.9", - "openai": "^6.45.0", + "next": "16.2.7", + "openai": "^6.42.0", "pdf-parse": "^2.4.5", "pdfjs-dist": "^6.0.227", "pdfkit": "^0.19.1", "postcss": "^8.5.15", - "postgres": "3.4.9", + "postgres": "3.4.7", "react": "19.2.7", "react-dom": "19.2.7", "tesseract.js": "^7.0.0", "zod": "^4.4.3" }, "devDependencies": { - "@tailwindcss/postcss": "^4.3.1", - "@types/node": "^24", + "@tailwindcss/postcss": "^4.3.0", + "@types/node": "^24.13.2", "@types/pdf-parse": "^1.1.5", "@types/pdfkit": "^0.17.6", "@types/react": "^19.2.17", @@ -37,7 +37,7 @@ "@vitest/coverage-v8": "^4.1.9", "eslint": "^9.39.4", "eslint-config-next": "16.2.9", - "playwright": "^1.61.1", + "playwright": "^1.61.0", "prettier": "^3.8.4", "tailwindcss": "^4.3.1", "tsx": "^4.22.4", @@ -1794,9 +1794,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.9.tgz", - "integrity": "sha512-HkfxNYUCmcct0Xsqib5KxqMSHV4AHJq857BNRchyBDs4YS19aHzVfn1kDuBYKqLLQBjXgnkIsjV2Kd4d2wzYhw==", + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.7.tgz", + "integrity": "sha512-vm1EDI/pVaBNNiychmxk3fft+OhQPVD9cIM/tReLZIQ3TfQ4kqI9DwKk00dzuS1ulC7icbrzCFrmRRlk9PfNdw==", "cpu": [ "arm64" ], @@ -1810,9 +1810,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.9.tgz", - "integrity": "sha512-7IAtK4MeybpqRV9GRABWEhJ62mOS+rzWOzOTFie4cSEtm12xsoOMJRcECoZx3FHPzFAqN/IJtHqWAFOLfl152w==", + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.7.tgz", + "integrity": "sha512-O3IRSv1ZBL1zs0WrIgefTEcTKFVn+ryxBNe54erJ6KsD+2f/Mmt7g2jOYh8PSBdUwPtKQJuCsTMlZ7tIu2AcsQ==", "cpu": [ "x64" ], @@ -1826,15 +1826,12 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.9.tgz", - "integrity": "sha512-hBD75iWpUtkL9SmQmcRhmLomn9jgkPzCEkbOcLgHymPEKzv+6ONy13RRiIEz/iEObjkS2Jlb5gYS2XGoS3X4rw==", + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.7.tgz", + "integrity": "sha512-Re6PZtjBDd0aMU+VcZcC/PrIvj4WhrjDYtMhhCVQamWN4L90EVP0pcEOBQD25prSlw7OzNw5QpHLWMilRLsRNw==", "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1845,15 +1842,12 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.9.tgz", - "integrity": "sha512-qZTI3pf9SGc/obr8NkQAekBxmp1QK+kVm+VAf3BALLfFAj+1kUhkTxmrWpVos9R/UYIA8AWX2p6cGI5WdwzVUA==", + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.7.tgz", + "integrity": "sha512-qyogG9QtBzWxgJfeGBvOEHI3851gTfCF3wLZ5RDLTBJGAmE9p1qDwKCOdrBrvBzRvYDT+gUDp72pzlSEfAXgNA==", "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1864,15 +1858,12 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.9.tgz", - "integrity": "sha512-xm0HfRNX+UkH4R3c18ynswjj5o5uEj/7iI9p9omdtTSIsRCzQqkGMA+10nzJ4EHnYC3as65IMhbbl5fWRUWHYg==", + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.7.tgz", + "integrity": "sha512-Vhe4ZDuBpmMogrGi5D4R2Kq4JAQlj6+wvgaFYy31zfES0zPmt6TLA+cuYpM/OLrPZjo2MYQTHVqNUSCR6+fDZQ==", "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1883,15 +1874,12 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.9.tgz", - "integrity": "sha512-QumimHkGEG6vM3PfEDWKyKen03NcqLOkeKB1EfcPe7VxzmEiCa4jNnMyBn/US5zcd/VE1CI+O8Ovb3lfjVHfGw==", + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.7.tgz", + "integrity": "sha512-srvian89JahFLw1YLBEuhvPJ0DO5lpUeJQMXy4xYo7g628ZlNgXdNkqoxSAv9OYrBfByh6vxISMwW/mRbzCY+g==", "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1902,9 +1890,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.9.tgz", - "integrity": "sha512-hzQpKZvw8rAwI6A2uQh6SacCSvNAXaIkPNsWwzqqfRiIMiXMfH936skDhz1OO6KpvdKkJrgHHtqQOq5PIXOvdQ==", + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.7.tgz", + "integrity": "sha512-GX3wvLpULFuRFJzwHaKfm7QZJ18F4ZSuxlPJ96BoBglCzBmdSjyeBKF+ZhWhvL/ckxNfLnNa7bsObO2ipYpszw==", "cpu": [ "arm64" ], @@ -1918,9 +1906,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.9.tgz", - "integrity": "sha512-qr2VL3Ce5QrwgO2yh1ujSBawrimjVKX8FGF/cOynmdYKJY0BdHpGVNIRK1tqONB10Vkm25Ub1BD2bkjWs4+96w==", + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.7.tgz", + "integrity": "sha512-J4WlM72NMk076Qsg0jTdK3SNXatlSdnjW7L7oNGLst1tAGjHrJh/FYi+pw9wyIjEtGRKDNzD0zuiY16oWYWVaw==", "cpu": [ "x64" ], @@ -2294,9 +2282,9 @@ "license": "MIT" }, "node_modules/@supabase/auth-js": { - "version": "2.108.2", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.108.2.tgz", - "integrity": "sha512-tNaQmBgodDZwgB40mRwVbxFy8IDYwjdpcZ0BYrWiwlULCSQoJj4QoG4zgJT7QRPXcqipefNOzvO/qAu4dF98ag==", + "version": "2.107.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.107.0.tgz", + "integrity": "sha512-XA7x+WIeIvuC3GTZ2ey67QcBbGw4n+o5B7M+dMm9KT1lL3wX1B52DfEWW00WuPt/LnniJLLIn1WIm9YPtuxzKQ==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -2306,9 +2294,9 @@ } }, "node_modules/@supabase/functions-js": { - "version": "2.108.2", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.108.2.tgz", - "integrity": "sha512-RNUX8EiBy3iLwAX19jtRzLyePnl11/fHcgwDHLnpKcDSXt/5qBnh3LUwAtIjT21Q66QsmNUR2esrHziLCpNubw==", + "version": "2.107.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.107.0.tgz", + "integrity": "sha512-iMtRUmEj1KOgQd/a3MR4hnBlPnZc62DW8+z8aPpnzbxWkexEZUVL2fSgvvp15gqFg1V55e2yMGqgK+yhSQxp5w==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -2318,15 +2306,15 @@ } }, "node_modules/@supabase/phoenix": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.4.tgz", - "integrity": "sha512-Gt0pqoXuIqX/8dvG0OKp/wMCobXNH3klNbUPBNyOfN0YA1IswrM3HyWFMOPk1Jy+BRaIyDPcFx4jLBwHNmlyfQ==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.2.tgz", + "integrity": "sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A==", "license": "MIT" }, "node_modules/@supabase/postgrest-js": { - "version": "2.108.2", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.108.2.tgz", - "integrity": "sha512-GQ28/Y8hk3CFmkb3kXH1h/AQx6JIYSQfO0CJMRVBcEKZoNy6C45cXAZ4fcJvRC5Id0cs6xnkUV0+c0rIocigsw==", + "version": "2.107.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.107.0.tgz", + "integrity": "sha512-7ARs47/tyIjX7T0Ive20d4NY8zQYXsP5/P07jJWxffSIM2gpnSnGRnL/Fe15GPbdjsW2sTYeckHcyaoKbM6yWQ==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -2336,9 +2324,9 @@ } }, "node_modules/@supabase/realtime-js": { - "version": "2.108.2", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.108.2.tgz", - "integrity": "sha512-aAGxCSUemZvQIibnCdvNvgaKib28I4rfrNjKbQ9cG1uBLwUsI7hVpGXgEbypCCDhLjQlDTAiJlu7rgljYUT73g==", + "version": "2.107.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.107.0.tgz", + "integrity": "sha512-cF2KYdR3JIn9YlWGeluY9S0G+otqTdL6hB8GzpatlEIY6fZudCcyFo6Dc3+X9tjeb+x9XcIyNAk9qhNAknjH1A==", "license": "MIT", "dependencies": { "@supabase/phoenix": "^0.4.2", @@ -2349,9 +2337,9 @@ } }, "node_modules/@supabase/storage-js": { - "version": "2.108.2", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.108.2.tgz", - "integrity": "sha512-TVZPQxXGxY2+A6yTtm77zUHsh70lBhYUEaJL8RQC+BghcX/ygiMG/rmXrNVBce30/WAeNPa8FiG8HbqlGeV05g==", + "version": "2.107.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.107.0.tgz", + "integrity": "sha512-/X8OOVwKBn8aVKuHAGOz2yLA0d2OauqhVuy4mNtN+o7wttHOgx1/j+pqOzlsjmhOHrYykF6AJNZhs3gKZzcMUw==", "license": "MIT", "dependencies": { "iceberg-js": "^0.8.1", @@ -2362,16 +2350,16 @@ } }, "node_modules/@supabase/supabase-js": { - "version": "2.108.2", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.108.2.tgz", - "integrity": "sha512-hFhnPveb5JQg4a0QYicM0swT253YHMdfeRAl2BKHOlI5VAzuHxUGSr8RbwNLYNPauWOgQMS1H8sz8bvYlgwUfQ==", + "version": "2.107.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.107.0.tgz", + "integrity": "sha512-ChKzdlWVweMUUhr0U79JhMmgm1haS/C5JquaiCDr70JaGARRtjjoY9rkIheXWybXxTSNzRiQs3Sk8IAg1HS3ZA==", "license": "MIT", "dependencies": { - "@supabase/auth-js": "2.108.2", - "@supabase/functions-js": "2.108.2", - "@supabase/postgrest-js": "2.108.2", - "@supabase/realtime-js": "2.108.2", - "@supabase/storage-js": "2.108.2" + "@supabase/auth-js": "2.107.0", + "@supabase/functions-js": "2.107.0", + "@supabase/postgrest-js": "2.107.0", + "@supabase/realtime-js": "2.107.0", + "@supabase/storage-js": "2.107.0" }, "engines": { "node": ">=20.0.0" @@ -2387,49 +2375,56 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz", - "integrity": "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "5.21.6", - "jiti": "^2.7.0", + "enhanced-resolve": "^5.21.0", + "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.3.1" + "tailwindcss": "4.3.0" } }, + "node_modules/@tailwindcss/node/node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@tailwindcss/oxide": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.1.tgz", - "integrity": "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", "dev": true, "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.1", - "@tailwindcss/oxide-darwin-arm64": "4.3.1", - "@tailwindcss/oxide-darwin-x64": "4.3.1", - "@tailwindcss/oxide-freebsd-x64": "4.3.1", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", - "@tailwindcss/oxide-linux-x64-musl": "4.3.1", - "@tailwindcss/oxide-wasm32-wasi": "4.3.1", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.1.tgz", - "integrity": "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", "cpu": [ "arm64" ], @@ -2444,9 +2439,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.1.tgz", - "integrity": "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", "cpu": [ "arm64" ], @@ -2461,9 +2456,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.1.tgz", - "integrity": "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", "cpu": [ "x64" ], @@ -2478,9 +2473,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.1.tgz", - "integrity": "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", "cpu": [ "x64" ], @@ -2495,9 +2490,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.1.tgz", - "integrity": "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", "cpu": [ "arm" ], @@ -2512,16 +2507,13 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.1.tgz", - "integrity": "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2532,16 +2524,13 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.1.tgz", - "integrity": "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2552,16 +2541,13 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.1.tgz", - "integrity": "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2572,16 +2558,13 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.1.tgz", - "integrity": "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2592,9 +2575,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.1.tgz", - "integrity": "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -2614,17 +2597,83 @@ "@emnapi/runtime": "^1.10.0", "@emnapi/wasi-threads": "^1.2.1", "@napi-rs/wasm-runtime": "^1.1.4", - "@tybys/wasm-util": "^0.10.2", + "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "engines": { "node": ">=14.0.0" } }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.10.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.1.tgz", - "integrity": "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", "cpu": [ "arm64" ], @@ -2639,9 +2688,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.1.tgz", - "integrity": "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", "cpu": [ "x64" ], @@ -2656,19 +2705,26 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.1.tgz", - "integrity": "sha512-dNJuNbdEJT/SWRuXTYP1WSamelsz3ztkUsdtWQPjrexysrTpaEPM40P/71knXiXLYEojqPOEGitVLLpPMS5T6A==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.0.tgz", + "integrity": "sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.3.1", - "@tailwindcss/oxide": "4.3.1", - "postcss": "8.5.15", - "tailwindcss": "4.3.1" + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "postcss": "^8.5.10", + "tailwindcss": "4.3.0" } }, + "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@tybys/wasm-util": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", @@ -4596,9 +4652,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.21.6", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", - "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "version": "5.21.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.3.tgz", + "integrity": "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -7308,12 +7364,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.9.tgz", - "integrity": "sha512-MEOJiq/UvuezAdqVSceHbqDgZt1kDw2tpGVOlsdIoJsQdbN2JY2hpVG4xnXGkbdJUOEWhnRfiu/O4Hpc9Juwww==", + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.7.tgz", + "integrity": "sha512-eMJxgjRzBaj3olkP4cBamHDXL79A8FC6u1GcsO1D1Tsx8bw/LLXUJCaoajVxtnhD3A1IJqIT8IcRJjgBIPJq4w==", "license": "MIT", "dependencies": { - "@next/env": "16.2.9", + "@next/env": "16.2.7", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -7327,14 +7383,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.9", - "@next/swc-darwin-x64": "16.2.9", - "@next/swc-linux-arm64-gnu": "16.2.9", - "@next/swc-linux-arm64-musl": "16.2.9", - "@next/swc-linux-x64-gnu": "16.2.9", - "@next/swc-linux-x64-musl": "16.2.9", - "@next/swc-win32-arm64-msvc": "16.2.9", - "@next/swc-win32-x64-msvc": "16.2.9", + "@next/swc-darwin-arm64": "16.2.7", + "@next/swc-darwin-x64": "16.2.7", + "@next/swc-linux-arm64-gnu": "16.2.7", + "@next/swc-linux-arm64-musl": "16.2.7", + "@next/swc-linux-x64-gnu": "16.2.7", + "@next/swc-linux-x64-musl": "16.2.7", + "@next/swc-win32-arm64-msvc": "16.2.7", + "@next/swc-win32-x64-msvc": "16.2.7", "sharp": "^0.34.5" }, "peerDependencies": { @@ -7360,6 +7416,12 @@ } } }, + "node_modules/next/node_modules/@next/env": { + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.7.tgz", + "integrity": "sha512-tMJizPlj6ZYpBMMdK8S0LJufrP4QTdR6pcv9KQ/bVETPAmg0j1mlHE9G2c38UyGHxoBapgwuj7XjbGJ2RcDFOg==", + "license": "MIT" + }, "node_modules/node-exports-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", @@ -7562,27 +7624,15 @@ } }, "node_modules/openai": { - "version": "6.45.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.45.0.tgz", - "integrity": "sha512-5DQVNErssk0afNpTTHUm/qZPU4iKR9OYdNid8Ib4puq4gHNNvGWZht2zY4h9a8JMF949Ik6m8gQutllVPbjdnw==", + "version": "6.42.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.42.0.tgz", + "integrity": "sha512-1WFEt/uXMXOLhYRNkgJWo08Y2YNvNwpVU72K7ibrWgWpNOXd4VojXLbe6SQ4bLiUQ3Y8jz4IiyVkylJCL1DtZg==", "license": "Apache-2.0", "peerDependencies": { - "@aws-sdk/credential-provider-node": ">=3.972.0 <4", - "@smithy/hash-node": ">=4.3.0 <5", - "@smithy/signature-v4": ">=5.4.0 <6", "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { - "@aws-sdk/credential-provider-node": { - "optional": true - }, - "@smithy/hash-node": { - "optional": true - }, - "@smithy/signature-v4": { - "optional": true - }, "ws": { "optional": true }, @@ -8057,13 +8107,13 @@ } }, "node_modules/playwright": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", - "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.0.tgz", + "integrity": "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.61.1" + "playwright-core": "1.61.0" }, "bin": { "playwright": "cli.js" @@ -8076,9 +8126,9 @@ } }, "node_modules/playwright-core": { - "version": "1.61.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", - "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.0.tgz", + "integrity": "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -8150,9 +8200,9 @@ } }, "node_modules/postgres": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.9.tgz", - "integrity": "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==", + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", + "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", "license": "Unlicense", "engines": { "node": ">=12" diff --git a/package.json b/package.json index a8f6a3bed..a1d3f758b 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "ensure": "node scripts/ensure-local-server.mjs", "build": "next build", "start": "node scripts/dev-free-port.mjs start", - "lint": "eslint", + "lint": "node --max-old-space-size=8192 ./node_modules/eslint/bin/eslint.js", "typecheck": "tsc --noEmit", "test": "vitest run", "test:coverage": "vitest run --coverage", @@ -22,10 +22,11 @@ "test:e2e:accessibility": "node ./node_modules/playwright/cli.js test tests/ui-accessibility.spec.ts --project=chromium", "test:e2e:chromium": "node ./node_modules/playwright/cli.js test --project=chromium", "test:e2e:visual": "node ./node_modules/playwright/cli.js test --config=playwright.visual.config.ts", - "verify:cheap": "npm run lint && npm run typecheck && npm run test", - "verify:ui": "npm run test:e2e:chromium", + "verify:cheap": "npm run check:runtime && npm run lint && npm run typecheck && npm run test", + "verify:ui": "npm run check:runtime && npm run test:e2e:chromium", "verify:release": "npm run check:runtime && npm run lint && npm run typecheck && npm run test && npm run build && npm run test:e2e", "check:runtime": "tsx scripts/check-runtime.ts", + "check:edge:functions": "deno check --node-modules-dir=false supabase/functions/indexing-v3-agent/index.ts", "check:production-readiness": "tsx scripts/production-readiness.ts", "check:production-readiness:ci": "tsx scripts/production-readiness.ts --ci", "format": "prettier --write .", @@ -73,18 +74,18 @@ }, "dependencies": { "@next/env": "^16.2.9", - "@supabase/supabase-js": "^2.108.2", + "@supabase/supabase-js": "^2.107.0", "exceljs": "^4.4.0", "jszip": "^3.10.1", "lucide-react": "^1.21.0", "mammoth": "^1.12.0", - "next": "16.2.9", - "openai": "^6.45.0", + "next": "16.2.7", + "openai": "^6.42.0", "pdf-parse": "^2.4.5", "pdfjs-dist": "^6.0.227", "pdfkit": "^0.19.1", "postcss": "^8.5.15", - "postgres": "3.4.9", + "postgres": "3.4.7", "react": "19.2.7", "react-dom": "19.2.7", "tesseract.js": "^7.0.0", @@ -97,8 +98,8 @@ "uuid": "^11.1.1" }, "devDependencies": { - "@tailwindcss/postcss": "^4.3.1", - "@types/node": "^24", + "@tailwindcss/postcss": "^4.3.0", + "@types/node": "^24.13.2", "@types/pdf-parse": "^1.1.5", "@types/pdfkit": "^0.17.6", "@types/react": "^19.2.17", @@ -106,7 +107,7 @@ "@vitest/coverage-v8": "^4.1.9", "eslint": "^9.39.4", "eslint-config-next": "16.2.9", - "playwright": "^1.61.1", + "playwright": "^1.61.0", "prettier": "^3.8.4", "tailwindcss": "^4.3.1", "tsx": "^4.22.4", diff --git a/src/app/mockups/answer-evidence-popups/page.tsx b/src/app/mockups/answer-evidence-popups/page.tsx index 89b60a4ac..cacda1185 100644 --- a/src/app/mockups/answer-evidence-popups/page.tsx +++ b/src/app/mockups/answer-evidence-popups/page.tsx @@ -21,9 +21,9 @@ import { import type { ReactNode } from "react"; const sources = [ - ["Clozapine physical health protocol", "p.12 - current", "92%"], - ["Clozapine monitoring table image evidence", "p.14 - locally reviewed", "86%"], - ["Shared-care communication checklist", "p.7 - review due", "71%"], + ["Clozapine physical health protocol", "p.12 - current policy", "Direct", "92%"], + ["Clozapine monitoring table image evidence", "p.14 - locally reviewed", "Table", "86%"], + ["Shared-care communication checklist", "p.7 - review due", "Related", "71%"], ] as const; const tabs = [ @@ -45,26 +45,43 @@ const tableRows = [ ["Metabolic", "Weight, waist, lipids, glucose/HbA1c", "Shared-care follow-up"], ] as const; +const focusRing = + "focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--clinical-chat-teal)]"; + function Shell({ children }: { children: ReactNode }) { return ( -
+
-
- - - -

- Clinical KB mockup -

+
+
+
+ + + +

+ Clinical KB mockup +

+
+

+ Answer evidence popup states +

+

+ A polished set of evidence popups showing what clinicians see before opening source PDFs: source + previews, evidence tabs, review controls, table expansion, and weak-support warnings. +

+
+
+

+ Design priorities +

+
+ Verify source first + Fast source opening + Mobile sheet parity +
+
-

- Answer evidence popup states -

-

- Static mockups of the current popup and drawer patterns opened from answer evidence: source preview, - evidence sheet tabs, desktop drawer, table expansion, and weak-evidence states. -

{children}
@@ -74,7 +91,7 @@ function Shell({ children }: { children: ReactNode }) { function Section({ title, body, children }: { title: string; body: string; children: ReactNode }) { return ( -
+

{title}

@@ -97,7 +114,7 @@ function Pill({ children, tone = "neutral" }: { children: ReactNode; tone?: "neu : "border-[color:var(--border-lux)] bg-[color:var(--surface-raised)] text-[color:var(--text-muted)]"; return ( svg]:h-3.5 [&>svg]:w-3.5 [&>svg]:shrink-0`} > {children} @@ -105,13 +122,14 @@ function Pill({ children, tone = "neutral" }: { children: ReactNode; tone?: "neu } function Action({ children, primary = false }: { children: ReactNode; primary?: boolean }) { + const baseClass = `inline-flex min-h-10 max-w-full min-w-0 items-center justify-center gap-2 rounded-md px-3 text-center text-xs font-semibold leading-tight transition hover:-translate-y-px hover:shadow-[var(--shadow-tight)] active:translate-y-0 ${focusRing} [&>svg]:h-3.5 [&>svg]:w-3.5 [&>svg]:shrink-0 sm:min-h-11 sm:text-sm`; return ( + ); +} + +function SourceCapsule() { + return ( + + ); +} + function SourceRows() { return (
- {sources.map(([title, meta, score], index) => ( -
- - - {title} - {meta} - - {score} + {sources.map(([title, meta, support, score], index) => ( +
+
))}
); } +function ActionRow({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +function ButtonText({ children }: { children: ReactNode }) { + return {children}; +} + function SourcePreviewPopover() { return (
-

- Sources behind this answer -

+

Source preview

- Preview first, then open the source document when needed. + Check the best passage, status, and source action before opening the PDF.

- 3 sources + 3 sources
“Monitor FBC/ANC, myocarditis symptoms, metabolic risk, constipation, and shared-care communication during clozapine initiation.”
-
+ - Open PDF drawer + Open source - Copy quote + Copy quote - View section + View cited section -
+
); } @@ -190,25 +251,23 @@ function MobileSheetFrame({ children: ReactNode; }) { return ( -
+
-
+

{title}

-

{description}

+

{description}

- +
-
{children}
+
{children}
); } function EvidenceTabs({ selected }: { selected: string }) { return ( -
+
{tabs.map(([label, count, Icon]) => { const active = label === selected; @@ -216,15 +275,19 @@ function EvidenceTabs({ selected }: { selected: string }) { ); })} @@ -236,9 +299,9 @@ function EvidenceTabs({ selected }: { selected: string }) { function FeedbackPanel() { return (
-

Answer review

+

Clinical verification

- Capture misses for retrieval and RAG evals without changing the answer. + Mark whether the linked evidence is safe to use, needs correction, or is source-insufficient.

@@ -258,17 +321,39 @@ function FeedbackPanel() { ); } +function EvidenceSummaryMini({ selected }: { selected: string }) { + return ( +
+
+
+

{selected} evidence

+

+ Showing only the items used to support this answer. +

+
+ Source-backed +
+
+ ); +} + function TablePreview({ expanded = false }: { expanded?: boolean }) { return (
- Clozapine monitoring schedule + Clozapine monitoring schedule {expanded ? ( Verify against source ) : ( - + )}
@@ -308,32 +393,37 @@ function TablePreview({ expanded = false }: { expanded?: boolean }) { function SourceCards() { return (
- {sources.map(([title, meta, score]) => ( + {sources.map(([title, meta, support, score]) => (
- +

{title}

{meta}

- {score} + + {support} + {score} +

- Retrieved passage includes monitoring, escalation, and shared-care follow-up wording. + Passage supports monitoring and escalation wording. Open source to inspect the highlighted PDF section.

-
+ - Open + Open source - Scope + Scope to this -
+
))}
@@ -348,21 +438,24 @@ function QuoteCards() { key={item} className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-raised)] p-3 shadow-[var(--shadow-tight)]" > -

Exact quote

+
+

Exact quote

+ {item === 1 ? "p.12" : "p.14"} +
- “FBC/ANC monitoring, myocarditis symptoms, metabolic review and constipation prevention should be checked - during initiation and ongoing care.” + “FBC/ANC monitoring, myocarditis symptoms, metabolic review and constipation prevention should be + checked during initiation and ongoing care.”
-
+ - Copy + Copy - Ask follow-up + Ask about quote -
+ ))}
@@ -371,25 +464,26 @@ function QuoteCards() { function ImageEvidence() { const items = [ - { label: "Table crop", icon: Table2 }, - { label: "PDF page region", icon: FileImage }, + { label: "Table crop", icon: Table2, body: "Monitoring domains extracted from a table image." }, + { label: "PDF page region", icon: FileImage, body: "Page crop used to check source layout and nearby wording." }, ] as const; return ( -
- {items.map(({ label, icon: Icon }) => ( +
+ {items.map(({ label, icon: Icon, body }) => (
-
- +
+
+ + p.14 +

{label}

-

- Clozapine monitoring visual evidence, page 14. -

+

{body}

))} @@ -401,18 +495,22 @@ function PdfLinks() { return (
{["Clozapine physical health protocol", "Shared-care communication checklist"].map((title, index) => ( -
{title} - {index === 0 ? "Main source" : "Supporting source"} - page {index === 0 ? 12 : 7} + {index === 0 ? "Main source" : "Supporting source"} · opens at page {index === 0 ? 12 : 7} - -
+ + {index === 0 ? "p.12" : "p.7"} + + + ))}
); @@ -425,8 +523,8 @@ function EvidenceMap() { ["Metabolic review", "Partial", "1", "Review due"], ] as const; return ( -
-
+
+
{["Section", "Support", "Citations", "Source status"].map((header) => ( @@ -461,8 +559,8 @@ function MobileEvidencePanel({ selected }: { selected: string }) { title="Evidence" description="Sources, quotes, tables, images, PDFs, and support map for this answer." > -
- +
+ {selected === "Tables" ? : } {selected === "Tables" ? : null} {selected === "Sources" ? : null} @@ -480,8 +578,10 @@ function DesktopEvidenceDrawer() {
-

Evidence

-

3 sources, 2 quotes, 1 table, 2 images

+

Evidence review

+

+ Verify the answer against cited passages before using it clinically. +

@@ -514,12 +614,12 @@ function TableDialog() {
-

Clinical table

-

Expanded table preview from visual evidence.

+

Clozapine monitoring table

+

+ Expanded from visual evidence. Use the source PDF for final verification. +

- +
@@ -531,26 +631,29 @@ function TableDialog() { function WeakEvidencePopup() { return (
-
+
-

Evidence support is limited

+
+

Evidence support is limited

+ Do not copy into notes +

- Treat this as a source-finding result until the linked passage is verified. Closest sources are shown, but - there is no strong direct answer support. + Treat this as source finding only. The closest indexed passages are nearby, but they do not directly support + a clinical answer yet.

-
+ - Open closest source + Open closest passage - Limit to local/current sources + Restrict to current local sources -
+
@@ -570,10 +673,7 @@ export default function AnswerEvidencePopupsMockupPage() { Clozapine monitoring should include FBC/ANC, myocarditis symptoms, metabolic checks, constipation prevention, and shared-care communication.

- +
@@ -594,9 +694,9 @@ export default function AnswerEvidencePopupsMockupPage() {
-
+
{["Tables", "Sources", "Images", "Quotes", "PDFs", "Map"].map((tab) => (

@@ -610,7 +710,7 @@ export default function AnswerEvidencePopupsMockupPage() {

@@ -624,7 +724,7 @@ export default function AnswerEvidencePopupsMockupPage() {
diff --git a/src/app/mockups/mode-dropdown/page.tsx b/src/app/mockups/mode-dropdown/page.tsx new file mode 100644 index 000000000..aaaa88571 --- /dev/null +++ b/src/app/mockups/mode-dropdown/page.tsx @@ -0,0 +1,229 @@ +import { + BookOpenCheck, + ChevronDown, + Check, + FileText, + Heart, + ListChecks, + Menu, + Moon, + Search, + Sparkles, + Stethoscope, + UserRound, +} from "lucide-react"; + +const modes = [ + { + label: "Answer", + description: "Source-backed clinical answer", + icon: Sparkles, + active: true, + }, + { + label: "Documents", + description: "Search indexed PDFs and notes", + icon: FileText, + active: false, + }, + { + label: "Prescribing", + description: "Medication checks and guidance", + icon: Stethoscope, + active: false, + }, + { + label: "Evidence", + description: "Tables, quotes, images, PDFs", + icon: ListChecks, + active: false, + }, + { + label: "Favourites", + description: "Saved sources and workflows", + icon: Heart, + active: false, + }, + { + label: "Profile", + description: "Home, preferences, review queue", + icon: UserRound, + active: false, + }, +] as const; + +function HeaderMockup({ expanded = false, compact = false }: { expanded?: boolean; compact?: boolean }) { + const activeMode = modes.find((mode) => mode.active) ?? modes[0]; + const ActiveIcon = activeMode.icon; + + return ( +
+
+
+ + +
+ + + {expanded ? ( +
+ {modes.map((mode) => { + const Icon = mode.icon; + return ( + + ); + })} +
+ ) : null} +
+ +
+ {compact ? null : ( + + )} + +
+
+
+ +
+
+ + + Ask a clinical question... + + + +
+
+ ); +} + +export default function ModeDropdownMockupPage() { + return ( +
+
+
+
+ + + +

+ Clinical KB mockup +

+
+

+ Search mode dropdown +

+

+ The existing Answer/Documents segmented control is replaced by one compact mode button that can scale to + more app modes without widening the top bar. +

+
+ +
+
+ + +
+ + +
+ +
+ +
+
+
+ ); +} diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx index cc28fa054..19f59defc 100644 --- a/src/components/ClinicalDashboard.tsx +++ b/src/components/ClinicalDashboard.tsx @@ -6802,25 +6802,22 @@ export function ClinicalDashboard() { )} {searchMode === "documents" ? ( - <> - - - + ) : loading && !answer ? ( ) : answer ? ( diff --git a/src/components/DocumentTagCloud.tsx b/src/components/DocumentTagCloud.tsx index 14d45e69b..2bd764b61 100644 --- a/src/components/DocumentTagCloud.tsx +++ b/src/components/DocumentTagCloud.tsx @@ -66,7 +66,7 @@ function DocumentTagChip({ }) { const Icon = groupIcon[tag.group]; const tagClassName = cn( - "inline-flex items-center gap-1 rounded-md border font-semibold shadow-[var(--shadow-inset)]", + "inline-flex max-w-full items-center gap-1 rounded-md border font-semibold shadow-[var(--shadow-inset)]", compact ? "min-h-6 px-2 text-[10px]" : "min-h-7 px-2 text-[11px]", groupTone[tag.group], tag.queryMatched && "ring-2 ring-[color:var(--focus)]/25", @@ -76,8 +76,8 @@ function DocumentTagChip({ ); const content = ( <> - - {tag.label} + + {tag.label} ); diff --git a/src/components/DocumentViewer.tsx b/src/components/DocumentViewer.tsx index f0f8c5ac7..eb7d23482 100644 --- a/src/components/DocumentViewer.tsx +++ b/src/components/DocumentViewer.tsx @@ -13,9 +13,7 @@ import { ChevronDown, ExternalLink, FileImage, - FileSpreadsheet, FileText, - ListChecks, Loader2, Maximize2, Menu, @@ -36,11 +34,16 @@ import { } from "lucide-react"; import { type FormEvent, useEffect, useRef, useState } from "react"; import { AccessibleTable } from "@/components/AccessibleTable"; +import { documentDisplayTitle } from "@/components/DocumentOrganizationBadges"; import { - DocumentOrganizationBadges, - documentDisplayTitle, - documentOrganizationProfile, -} from "@/components/DocumentOrganizationBadges"; + DocumentActionAnchor, + DocumentActionButton, + DocumentBadge, + DocumentFileTile, + DocumentMetaRow, + documentFileKind, + documentTileTone, +} from "@/components/clinical-dashboard/document-ui"; import { DocumentTagCloud } from "@/components/DocumentTagCloud"; import type { PDFDocumentLoadingTask, PDFDocumentProxy, RenderTask } from "pdfjs-dist"; import { @@ -57,15 +60,13 @@ import { PanelHeading, primaryControl, proseMeasure, - SourceProvenance, - SourceStatusBadge, sourceCard, textMuted, toolbarButton, } from "@/components/ui-primitives"; import { clearCachedSignedUrl, getCachedSignedUrl, setCachedSignedUrl } from "@/lib/signed-url-cache"; import { readLocalProjectIdentity, unsafeLocalProjectMessage } from "@/lib/local-project-identity"; -import { formatClinicalDate, normalizeSourceMetadata, sourceStatusLabel } from "@/lib/source-metadata"; +import { formatClinicalDate } from "@/lib/source-metadata"; import { isLocalNoAuthMode } from "@/lib/env"; import { useAuthSession } from "@/lib/supabase/client"; import { SafeBoldText } from "@/components/SafeBoldText"; @@ -148,13 +149,6 @@ type DocumentSearchResult = { score: number; }; -type DocumentIndexHealth = { - extractionQuality?: string | null; - indexedAt?: string | null; - indexVersion?: string | null; - warnings?: unknown; -}; - const profileSectionLabels: Array<{ key: keyof Omit; label: string; @@ -223,87 +217,6 @@ function ClinicalSummaryProfile({ profile }: { profile: ClinicalDocumentSummaryP ); } -function SourceMetadataSummary({ metadata }: { metadata?: unknown }) { - const source = normalizeSourceMetadata(metadata); - - return ( -
- - -
- ); -} - -function DocumentOrganizationReviewPanel({ document }: { document: ClinicalDocument }) { - const profile = documentOrganizationProfile(document); - if (!profile) return null; - - const candidates = profile.site?.candidates ?? []; - const siteEvidence = [ - ...(profile.site?.evidence_sources ?? []), - ...candidates.flatMap((candidate) => candidate.evidence_sources), - ]; - const evidence = Array.from(new Set([...siteEvidence, ...(profile.document_type?.evidence_sources ?? [])])).slice( - 0, - 6, - ); - const reviewStatus = profile.review_status; - const showPanel = reviewStatus !== "confident" || candidates.length > 0 || evidence.length > 0; - - if (!showPanel) return null; - - return ( -
-
-
-

- - Organisation review -

- -
- - {reviewStatus === "manual_override" - ? "Manual override" - : reviewStatus === "needs_review" - ? "Needs review" - : "Evidence"} - -
- - {candidates.length ? ( -
- {candidates.slice(0, 3).map((candidate) => ( -
-

{candidate.label}

-

- {candidate.kind.replaceAll("_", " ")} from {candidate.raw_tag} -

-
- ))} -
- ) : null} - - {evidence.length ? ( -
    - {evidence.map((item) => ( -
  • - {item.replaceAll(":", ": ")} -
  • - ))} -
- ) : null} - -

- Use a manual Site tag below when the generated site is ambiguous or should be overridden. -

-
- ); -} - function looksLikeTableText(value?: string | null) { return Boolean(value?.includes("|") && value.split("|").filter((cell) => cell.trim()).length >= 3); } @@ -1648,15 +1561,18 @@ function DocumentManualTagEditor({ } function compactDocumentType(document: ClinicalDocument) { - const extension = document.file_name.split(".").pop()?.toUpperCase() || "PDF"; - return extension === "PDF" ? "PDF" : extension; + return documentFileKind(document.file_name, "PDF"); } function documentOverviewText(document: ClinicalDocument) { const profile = document.summary?.clinical_specifics?.profile; - if (profile?.overview) return cleanClinicalSummaryText(profile.overview); - if (document.summary?.summary) return cleanClinicalSummaryText(document.summary.summary); - return "Practical guidance, extracted evidence, pages, tables, and source metadata for this document."; + const overview = profile?.overview + ? cleanClinicalSummaryText(profile.overview) + : document.summary?.summary + ? cleanClinicalSummaryText(document.summary.summary) + : ""; + if (overview && !/source-backed review/i.test(overview)) return overview; + return "A clear overview of this document, useful pages, and source PDF access."; } function documentKeySections(document: ClinicalDocument) { @@ -1666,12 +1582,24 @@ function documentKeySections(document: ClinicalDocument) { function DocumentPagePreview({ pageNumber }: { pageNumber: number | null }) { return ( - - - p.{pageNumber ?? "n/a"} + + + + + + + + + + + + + + + + + + p.{pageNumber ?? "n/a"} ); } @@ -1681,9 +1609,6 @@ function DocumentOverviewLanding({ initialPage, signedUrl, pages, - chunks, - images, - tableFacts, onAskFromDocument, onAddToScope, canSummarizeDocument, @@ -1692,14 +1617,10 @@ function DocumentOverviewLanding({ initialPage: number; signedUrl: string | null; pages: PageRow[]; - chunks: ChunkRow[]; - images: ImageRow[]; - tableFacts: TableFactRow[]; onAskFromDocument: () => void; onAddToScope: () => void; canSummarizeDocument: boolean; }) { - const source = normalizeSourceMetadata(document.metadata); const keySections = documentKeySections(document); const usefulPages = Array.from(new Set([initialPage, ...pages.map((page) => page.page_number)])) .filter((page) => Number.isFinite(page)) @@ -1707,168 +1628,132 @@ function DocumentOverviewLanding({ const documentType = compactDocumentType(document); return ( -
-
-
- - - - {documentType} - - +
+ + + Documents + + +
+
+
+

+ Clinical guideline +

{documentDisplayTitle(document)}

-

- {documentType} - {document.page_count ?? (pages.length || "?")} pages - Uploaded {formatClinicalDate(document.created_at)} -

+
- - + Best match - - + + + High relevance +
-
+
{signedUrl ? ( - - + Open original PDF - + ) : ( - - + Open original PDF - + )} -
- - + Answer from this +
- -
-
- - +
+ +
-

What this document covers

-

{documentOverviewText(document)}

+

Overview

+

{documentOverviewText(document)}

- +
-
- +
+
-

Key sections

-
- {(keySections.length ? keySections : ["Overview", "Pages", sourceStatusLabel(source)]).map((section) => ( +

Key sections

+
+ {(keySections.length ? keySections : ["Overview", "Useful pages", "Source PDF"]).map((section) => ( {section} ))}
- -
-
- -
-
-

Useful pages

- - View all pages - -
-
- {(usefulPages.length ? usefulPages : [initialPage]).map((page) => ( - - ))} +
-

Document parts

-
- {[ - { label: "Quotes", value: chunks.length, icon: Quote }, - { label: "Tables", value: tableFacts.length, icon: FileSpreadsheet }, - { label: "Images", value: images.length, icon: FileImage }, - { label: "Pages", value: document.page_count ?? pages.length, icon: FileText }, - ].map((item) => { - const Icon = item.icon; - return ( - - - - {item.value ?? 0} - - {item.label} - - ); - })} +
+ + + +
+

Useful pages

+

Most relevant pages for this document.

+
+ {(usefulPages.length ? usefulPages : [initialPage]).map((page) => ( + + ))} +
+
+
@@ -1892,7 +1777,6 @@ export function DocumentViewer({ const [chunks, setChunks] = useState([]); const [signedUrl, setSignedUrl] = useState(null); const [summary, setSummary] = useState(null); - const [indexHealth, setIndexHealth] = useState(null); const [loadingDocument, setLoadingDocument] = useState(true); const [viewerError, setViewerError] = useState(null); const [previewError, setPreviewError] = useState(null); @@ -2006,14 +1890,12 @@ export function DocumentViewer({ setImages(detail.images ?? []); setTableFacts(detail.tableFacts ?? []); setChunks(detail.chunks ?? []); - setIndexHealth(detail.indexHealth ?? null); } else { setDocument(null); setPages([]); setImages([]); setTableFacts([]); setChunks([]); - setIndexHealth(null); setViewerError( detailResult.reason instanceof Error ? detailResult.reason.message : "Document could not be loaded.", ); @@ -2184,10 +2066,14 @@ export function DocumentViewer({ : viewerState === "loading" ? `page ${initialPage} · loading source` : (effectiveViewerError ?? "Source unavailable"); + const documentHomeHref = "/?mode=documents"; + const scopedDocumentHref = readyDocument + ? `/?mode=documents&q=${encodeURIComponent(documentDisplayTitle(readyDocument))}` + : documentHomeHref; const canSummarizeDocument = viewerState === "ready" && !loadingSummary && canUsePrivateApis; const summarizeTitle = canSummarizeDocument - ? "Generate a source-backed document summary" - : "Load a source document before summarising"; + ? "Answer from this document" + : "Load a source document before answering"; const selectedPage = pages.find((page) => page.page_number === initialPage) ?? pages[0]; const selectedChunk = chunkId ? chunks.find((chunk) => chunk.id === chunkId) : undefined; const clinicalImages = images.filter( @@ -2199,11 +2085,6 @@ export function DocumentViewer({ (image.searchable === false || ["administrative", "reference"].includes(String(image.clinicalUseClass ?? image.tableRole ?? ""))), ); - const indexWarnings = Array.isArray(indexHealth?.warnings) - ? indexHealth.warnings.map((warning) => String(warning)).filter(Boolean) - : typeof indexHealth?.warnings === "string" && indexHealth.warnings - ? [indexHealth.warnings] - : []; const generatedSummaryText = summary ? cleanClinicalSummaryText(summary.answer) : ""; useEffect(() => { if (!chunkId || loadingDocument) return; @@ -2274,7 +2155,7 @@ export function DocumentViewer({
@@ -2303,9 +2184,9 @@ export function DocumentViewer({
@@ -2328,7 +2209,7 @@ export function DocumentViewer({ open={mobileActionsOpen} onClose={() => setMobileActionsOpen(false)} title="This document" - description="Search, ask, open, or manage this source." + description="Search, answer, open, or scope this document." closeLabel="Close document actions" >
@@ -2338,7 +2219,9 @@ export function DocumentViewer({

{readyDocument.file_name}

- + + High relevance + {!isOnline ? Offline : null}
@@ -2352,7 +2235,7 @@ export function DocumentViewer({ className={cn(secondaryButton, "min-h-12 justify-start text-xs")} > - Search this PDF + Search in document {signedUrl ? ( { setMobileActionsOpen(false); - router.push(`/?mode=documents&q=${encodeURIComponent(documentDisplayTitle(readyDocument))}`); + router.push(scopedDocumentHref); }} className={cn(secondaryButton, "min-h-12 justify-start text-xs")} > - Add to scope + Scope
@@ -2416,7 +2299,7 @@ export function DocumentViewer({ ) : null} -
+
{(summary || summaryError) && (
{summary && ( @@ -2451,13 +2334,8 @@ export function DocumentViewer({ initialPage={initialPage} signedUrl={signedUrl} pages={pages} - chunks={chunks} - images={images} - tableFacts={tableFacts} onAskFromDocument={() => void summarize()} - onAddToScope={() => - router.push(`/?mode=documents&q=${encodeURIComponent(documentDisplayTitle(readyDocument))}`) - } + onAddToScope={() => router.push(scopedDocumentHref)} canSummarizeDocument={canSummarizeDocument} />
@@ -2468,7 +2346,7 @@ export function DocumentViewer({
@@ -2605,44 +2483,33 @@ export function DocumentViewer({
- - {indexHealth ? ( -
-

- Index health -

-
-
-
Extraction
-
{indexHealth.extractionQuality ?? "unknown"}
-
-
-
Index version
-
- {indexHealth.indexVersion ?? "unknown"} -
-
-
-
Indexed
-
{indexHealth.indexedAt ?? "not recorded"}
-
-
- {indexWarnings.length ? ( -
    - {indexWarnings.slice(0, 4).map((warning) => ( -
  • {warning}
  • - ))} -
- ) : null} +
+
+
Pages
+
+ {(document?.page_count ?? pages.length) || "n/a"} +
- ) : null} +
+
Useful pages
+
+ {pages.length ? Math.min(pages.length, 3) : 1} +
+
+
+
Text sections
+
{chunks.length}
+
+
+
Tables and diagrams
+
+ {clinicalImages.length} +
+
+
{document ? ( @@ -2683,15 +2550,21 @@ export function DocumentViewer({
)} - - + {canUsePrivateApis ? ( +
+ + Document tools + + +
+ ) : null}
) : null} @@ -2702,12 +2575,21 @@ export function DocumentViewer({ description="Indexed tables, diagrams, and image captions." />
- + {canUsePrivateApis && tableFacts.length ? ( +
+ + Table tools + +
+ +
+
+ ) : null} {effectiveLoadingDocument ? ( ) : clinicalImages.length === 0 ? ( @@ -2748,11 +2630,11 @@ export function DocumentViewer({ @@ -2767,7 +2649,7 @@ export function DocumentViewer({ type="submit" disabled={!canSummarizeDocument} className="grid h-[44px] w-[44px] shrink-0 place-items-center rounded-full bg-[color:var(--clinical-chat-teal)] text-white shadow-[inset_0_1px_0_rgb(255_255_255_/_18%),var(--shadow-tight)] hover:bg-[color:var(--primary-strong)] disabled:cursor-not-allowed disabled:opacity-50" - aria-label="Ask about this document" + aria-label="Answer from this document" > {loadingSummary ? : } diff --git a/src/components/clinical-dashboard/document-search-results.tsx b/src/components/clinical-dashboard/document-search-results.tsx index 46a6fe3ba..15e740515 100644 --- a/src/components/clinical-dashboard/document-search-results.tsx +++ b/src/components/clinical-dashboard/document-search-results.tsx @@ -4,29 +4,36 @@ import Link from "next/link"; import { useMemo, useState } from "react"; import { AlertCircle, - ArrowUpDown, ChevronDown, + Clock, ExternalLink, - FileImage, - FileSpreadsheet, FileText, Filter, FolderOpen, ListChecks, - MoreVertical, - Quote, - Search, ShieldAlert, + SlidersHorizontal, Sparkles, + Star, Tag, Target, + TrendingUp, X, type LucideIcon, } from "lucide-react"; import { DocumentTagCloud } from "@/components/DocumentTagCloud"; -import { DocumentOrganizationBadges, documentDisplayTitle } from "@/components/DocumentOrganizationBadges"; +import { documentDisplayTitle } from "@/components/DocumentOrganizationBadges"; import { SafeBoldText } from "@/components/SafeBoldText"; +import { + DocumentActionButton, + DocumentActionLink, + DocumentBadge, + DocumentFileTile, + DocumentMetaRow, + documentFileKind, + documentTileTone, +} from "@/components/clinical-dashboard/document-ui"; import { cn, floatingControl, @@ -209,6 +216,13 @@ function compactMatchReason(document: DocumentMatch) { return `${documentKindLabel(document)} - ${documentPageLabel(document)}`; } +function cleanDocumentCardSummary(value: string) { + if (/source-backed review/i.test(value)) { + return "Indexed source text is available for this document."; + } + return value; +} + function relevancePercent(document: DocumentMatch) { const verdict = document.relevance?.verdict as string | undefined; if (verdict === "direct") return 96; @@ -226,73 +240,36 @@ function relevancePercent(document: DocumentMatch) { function relevanceTone(document: DocumentMatch) { const verdict = document.relevance?.verdict as string | undefined; const percent = relevancePercent(document); - if (verdict === "direct" || percent >= 90) return { label: "Strong match", short: `${percent}% match` }; - if (verdict === "partial" || percent >= 75) return { label: "Related source", short: `${percent}% related` }; - return { label: "Nearby source", short: `${percent}% nearby` }; + if (verdict === "direct" || percent >= 90) { + return { label: "High relevance", short: "High relevance", detail: `${percent}% match` }; + } + if (verdict === "partial" || percent >= 75) { + return { label: "High relevance", short: "High relevance", detail: `${percent}% related` }; + } + return { label: "Relevant", short: "Relevant", detail: `${percent}% nearby` }; } -function FileTypeTile({ document }: { document: DocumentMatch }) { - const extension = document.file_name.split(".").pop()?.toUpperCase() || "DOC"; - const isDocx = extension === "DOCX" || extension === "DOC"; - return ( - - - {extension} - - ); +function documentOpenHref(document: DocumentMatch) { + const params = new URLSearchParams(); + params.set("page", String(document.bestPages[0] ?? 1)); + const chunkId = document.bestChunkIds[0]; + if (chunkId) params.set("chunk", chunkId); + return `/documents/${document.document_id}?${params.toString()}`; } -const exploreEntries = [ - { - title: "Documents", - detail: "Guidelines, protocols, policies", - icon: FileText, - query: "clinical guideline", - }, - { - title: "Tables", - detail: "Monitoring and threshold tables", - icon: FileSpreadsheet, - query: "monitoring table", - }, - { - title: "Images", - detail: "Figures, forms, diagrams", - icon: FileImage, - query: "diagram", - }, - { - title: "Quotes", - detail: "Evidence statements", - icon: Quote, - query: "clinical recommendation", - }, -] as const; - const startRows = [ { title: "Recent documents", - detail: "Continue with recently viewed documents", - icon: ListChecks, + icon: Clock, query: "recent documents", }, { title: "Browse library", - detail: "Explore all indexed clinical documents", icon: FolderOpen, query: "clinical guideline", }, { title: "Open a source PDF", - detail: "Search by source name or file title", icon: ExternalLink, query: "PDF", }, @@ -305,128 +282,71 @@ function DocumentSearchHome({ documentCount: number; onSuggestedSearch: (query: string) => void; }) { - const suggestedSearches = ["lithium", "clozapine", "ECT pathway"]; - const [localQuery, setLocalQuery] = useState(""); - const trimmedLocalQuery = localQuery.trim(); + const suggestedSearches = ["lithium", "clozapine", "ECT pathway", "monitoring"]; return ( -
-
- +
+
+ -

Documents

-

- Find source PDFs, guidelines, policies, forms, tables, and figures. +

+ Documents +

+

+ Find guidelines, policies, forms, and source PDFs.

- {documentCount > 0 ? ( - - {documentCount.toLocaleString()} indexed - - ) : null} -
- -
{ - event.preventDefault(); - if (trimmedLocalQuery) onSuggestedSearch(trimmedLocalQuery); - }} - className="grid gap-2 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-raised)] p-2 shadow-[var(--shadow-tight)] sm:grid-cols-[minmax(0,1fr)_auto_auto]" - > - - - - -
-

Start here

-
- {startRows.map((row) => { - const Icon = row.icon; - return ( - - ); - })} -
+ {documentCount > 0 ? {documentCount.toLocaleString()} documents indexed : null}
-
- {exploreEntries.map((entry) => { - const Icon = entry.icon; +
+ {startRows.map((row) => { + const Icon = row.icon; return ( ); })}
-
-

Suggested searches

-
+
+

Suggested

+
{suggestedSearches.map((search) => ( ))} @@ -447,7 +367,9 @@ function SearchResultsHeader({ resultLabel, trimmedQuery }: { resultLabel: strin

{resultLabel}

{trimmedQuery ? ( -

{trimmedQuery}

+

+ Results for {trimmedQuery} +

) : null}
@@ -456,11 +378,11 @@ function SearchResultsHeader({ resultLabel, trimmedQuery }: { resultLabel: strin type="button" className={cn( floatingControl, - "min-h-10 shrink-0 gap-1.5 rounded-lg px-3 text-[11px] text-[color:var(--text-muted)]", + "min-h-11 shrink-0 gap-2 rounded-lg px-3 text-sm text-[color:var(--text-heading)]", )} > - - Sort: Relevance + + Best match
@@ -571,7 +493,7 @@ export function DocumentSearchResultsPanel({ const resultLabel = loading ? "Finding matching documents" : matches.length - ? `${displayedMatches.length} result${displayedMatches.length === 1 ? "" : "s"}` + ? `${displayedMatches.length} document${displayedMatches.length === 1 ? "" : "s"}` : documentCount === 0 ? "No indexed source documents" : trimmedQuery @@ -666,105 +588,103 @@ export function DocumentSearchResultsPanel({ {displayedMatches.map((document, index) => { const evidenceBadges = compactEvidenceBadges(document); const relevanceDisplay = relevanceTone(document); - const openHref = `/documents/${document.document_id}?page=${document.bestPages[0] ?? 1}&chunk=${ - document.bestChunkIds[0] ?? "" - }`; + const fileKind = documentFileKind(document.file_name, "DOC"); + const relevanceVariant = relevanceDisplay.short === "Relevant" ? "relevant" : "high"; + const summaryText = cleanDocumentCardSummary(document.summarySnippet || compactMatchReason(document)); + const openHref = documentOpenHref(document); return (
-
- -
-
+
+ +
+
+
+

+ {documentKindLabel(document)} +

+ + {documentDisplayTitle(document)} + +
+
+ {index === 0 ? ( + + Best match + + ) : null} + + {relevanceDisplay.short} + , {relevanceDisplay.detail} + +
+
+
{index === 0 ? ( - - + Best match - + ) : null} - - - {relevanceDisplay.short} - - -
- - {documentDisplayTitle(document)} - -
- {documentKindLabel(document)} - {evidenceBadges.map((badge) => ( - - - {badge} - - ))} + {relevanceDisplay.short} + , {relevanceDisplay.detail} +
- -

- {compactMatchReason(document)} -

+ {evidenceBadges.length ? {evidenceBadges.join(", ")} : null} - {document.summarySnippet && ( -

- -

- )} +

+ +

-
- {relevancePercent(document)}% - {relevanceDisplay.label} -
- - Open - - - +
); diff --git a/src/components/clinical-dashboard/document-ui.tsx b/src/components/clinical-dashboard/document-ui.tsx new file mode 100644 index 000000000..d469c0797 --- /dev/null +++ b/src/components/clinical-dashboard/document-ui.tsx @@ -0,0 +1,170 @@ +"use client"; + +import Link from "next/link"; +import type { AnchorHTMLAttributes, ButtonHTMLAttributes, ComponentProps, ReactNode } from "react"; +import { ExternalLink, FileText, type LucideIcon } from "lucide-react"; + +import { cn } from "@/components/ui-primitives"; + +export type DocumentBadgeVariant = "best" | "high" | "relevant" | "neutral"; +export type DocumentTileTone = "teal" | "info"; + +const badgeStyles: Record = { + best: "border-[color:var(--clinical-chat-teal)]/20 bg-[color:var(--clinical-chat-teal-soft)] text-[color:var(--clinical-chat-teal)] shadow-[var(--shadow-inset)]", + high: "border-[color:var(--border-lux)] bg-[color:var(--surface-raised)] text-[color:var(--text-muted)]", + relevant: "border-[color:var(--info)]/15 bg-[color:var(--info-soft)]/70 text-[color:var(--info)]", + neutral: "border-[color:var(--border-lux)] bg-[color:var(--surface-raised)] text-[color:var(--text-muted)]", +}; + +const tileStyles: Record = { + teal: "border-[color:var(--clinical-chat-teal)]/12 bg-[color:var(--clinical-chat-teal-soft)] text-[color:var(--clinical-chat-teal)]", + info: "border-[color:var(--info)]/15 bg-[color:var(--info-soft)]/60 text-[color:var(--info)]", +}; + +export function documentFileKind(fileName?: string | null, fallback = "PDF") { + const extension = fileName?.split(".").pop()?.trim().toUpperCase(); + return extension || fallback; +} + +export function documentTileTone(kind: string): DocumentTileTone { + const normalized = kind.toUpperCase(); + return normalized === "DOC" || normalized === "DOCX" ? "info" : "teal"; +} + +export function DocumentFileTile({ + kind, + tone = documentTileTone(kind), + compact = false, + className, +}: { + kind: string; + tone?: DocumentTileTone; + compact?: boolean; + className?: string; +}) { + const label = kind.toUpperCase(); + + return ( + + + {label} + + ); +} + +export function DocumentBadge({ + children, + icon: Icon, + variant = "neutral", + className, +}: { + children: ReactNode; + icon?: LucideIcon; + variant?: DocumentBadgeVariant; + className?: string; +}) { + return ( + + {Icon ? : null} + {children} + + ); +} + +export function DocumentMetaRow({ + items, + className, +}: { + items: Array; + className?: string; +}) { + const visibleItems = items.filter(Boolean); + if (visibleItems.length === 0) return null; + + return ( +

+ {visibleItems.map((item, index) => ( + + {index > 0 ? : null} + {item} + + ))} +

+ ); +} + +export const documentActionClass = + "inline-flex min-h-10 items-center justify-center gap-1.5 text-xs font-semibold 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)]"; + +export function DocumentActionLink({ + href, + children, + icon: Icon = ExternalLink, + className, + ...props +}: { + href: string; + children: ReactNode; + icon?: LucideIcon; + className?: string; +} & Omit, "href" | "children" | "className">) { + return ( + + + {children} + + ); +} + +export function DocumentActionAnchor({ + children, + icon: Icon = ExternalLink, + className, + ...props +}: AnchorHTMLAttributes & { + children: ReactNode; + icon?: LucideIcon; +}) { + return ( +
+ + {children} + + ); +} + +export function DocumentActionButton({ + children, + icon: Icon, + className, + ...props +}: ButtonHTMLAttributes & { + children: ReactNode; + icon: LucideIcon; +}) { + return ( + + ); +} diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx index eec14db09..9da163233 100644 --- a/src/components/clinical-dashboard/master-search-header.tsx +++ b/src/components/clinical-dashboard/master-search-header.tsx @@ -3,20 +3,25 @@ import { FormEvent, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { + Check, CheckCircle2, + ChevronDown, FileText, Filter, Globe2, + Heart, ListChecks, Loader2, Menu, Mic, Moon, + Pill, Plus, Search, Send, Sparkles, Sun, + UserRound, X, } from "lucide-react"; @@ -39,6 +44,57 @@ import { tagSearchText } from "@/lib/document-tags"; const mobileSheetMediaQuery = "(max-width: 639px)"; +type AppModeId = "answer" | "documents" | "prescribing" | "evidence" | "favourites" | "profile"; + +const appModeOptions: Array<{ + id: AppModeId; + label: string; + description: string; + icon: typeof Search; + href?: string; +}> = [ + { + id: "answer", + label: "Answer", + description: "Source-backed clinical answer", + icon: Sparkles, + }, + { + id: "documents", + label: "Documents", + description: "Search indexed PDFs and notes", + icon: FileText, + }, + { + id: "prescribing", + label: "Prescribing", + description: "Medication checks and guidance", + icon: Pill, + href: "/mockups/medication-prescribing", + }, + { + id: "evidence", + label: "Evidence", + description: "Tables, quotes, images, PDFs", + icon: ListChecks, + href: "/mockups/answer-evidence-popups", + }, + { + id: "favourites", + label: "Favourites", + description: "Saved sources and workflows", + icon: Heart, + href: "/mockups/favourites-hub", + }, + { + id: "profile", + label: "Profile", + description: "Home, preferences, review queue", + icon: UserRound, + href: "/home", + }, +]; + function splitFilterText(value: string) { return value .split(",") @@ -119,9 +175,11 @@ export function MasterSearchHeader({ const [scopeOpen, setScopeOpen] = useState(false); const [scopeSheetOpen, setScopeSheetOpen] = useState(false); const [dailyActionsOpen, setDailyActionsOpen] = useState(false); + const [modeMenuOpen, setModeMenuOpen] = useState(false); const [usesScopeSheet, setUsesScopeSheet] = useState(false); const dailyActionButtonRef = useRef(null); const firstDailyActionRef = useRef(null); + const modeMenuRef = useRef(null); const scopeDetailsRef = useRef(null); const scopeSummaryRef = useRef(null); const scopeFilterInputRef = useRef(null); @@ -159,6 +217,8 @@ export function MasterSearchHeader({ const submitLabel = searchMode === "answer" ? (trimmedQuery ? "Answer" : "Ask") : "Docs"; const queryPlaceholder = searchMode === "documents" ? "Search your clinical documents..." : "Ask a clinical question..."; + const selectedAppMode = appModeOptions.find((mode) => mode.id === searchMode) ?? appModeOptions[0]; + const SelectedAppModeIcon = selectedAppMode.icon; const dailyActions = [ { label: "Search library", icon: Search }, { label: "Add document", icon: FileText }, @@ -191,6 +251,15 @@ export function MasterSearchHeader({ } window.location.assign("/tools"); } + + function selectAppMode(mode: (typeof appModeOptions)[number]) { + setModeMenuOpen(false); + if (mode.id === "answer" || mode.id === "documents") { + onSearchModeChange(mode.id); + return; + } + if (mode.href) window.location.assign(mode.href); + } const collectionOptions = useMemo(() => { const values = new Set(); for (const document of documents) { @@ -229,6 +298,30 @@ export function MasterSearchHeader({ onScopeOpenChange?.(scopeOpen || scopeSheetOpen); }, [onScopeOpenChange, scopeOpen, scopeSheetOpen]); + useEffect(() => { + if (!modeMenuOpen) return undefined; + + function handlePointerDown(event: PointerEvent) { + const target = event.target; + if (!(target instanceof Node)) return; + if (!modeMenuRef.current?.contains(target)) setModeMenuOpen(false); + } + + function handleKeyDown(event: KeyboardEvent) { + if (event.key === "Escape") { + event.preventDefault(); + setModeMenuOpen(false); + } + } + + document.addEventListener("pointerdown", handlePointerDown); + document.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("pointerdown", handlePointerDown); + document.removeEventListener("keydown", handleKeyDown); + }; + }, [modeMenuOpen]); + useEffect(() => { const details = scopeDetailsRef.current; if (!scopeOpen || !details?.open) return undefined; @@ -482,36 +575,79 @@ export function MasterSearchHeader({ -
- {[ - { mode: "answer" as const, label: "Answer", icon: Sparkles }, - { mode: "documents" as const, label: "Documents", icon: FileText }, - ].map((item) => { - const active = searchMode === item.mode; - const Icon = item.icon; - return ( - - ); - })} +
+ + + {modeMenuOpen ? ( +
+ {appModeOptions.map((mode) => { + const Icon = mode.icon; + const active = mode.id === searchMode; + return ( + + ); + })} +
+ ) : null}
diff --git a/src/components/user-home-profile/user-home-profile-page.tsx b/src/components/user-home-profile/user-home-profile-page.tsx index 6ae06879d..a6b0058c0 100644 --- a/src/components/user-home-profile/user-home-profile-page.tsx +++ b/src/components/user-home-profile/user-home-profile-page.tsx @@ -1,122 +1,223 @@ import { - Activity, - AlertTriangle, Bell, + BookOpen, BookOpenCheck, - Check, ChevronRight, - Clock, - Database, + ClipboardCheck, FileText, - Folder, + HeartPulse, Home, - Lock, + KeyRound, LogOut, MessageSquare, - Palette, Search, Settings, - Shield, + ShieldCheck, SlidersHorizontal, - Star, - User, - Wrench, + Stethoscope, + UserRound, type LucideIcon, } from "lucide-react"; import { cn } from "@/components/ui-primitives"; -const navItems = [ - ["Home", Home, true], - ["Chat", MessageSquare, false], - ["Documents", Folder, false], - ["Favourites", Star, false], - ["Tools", Wrench, false], - ["Settings", Settings, false], -] as const; - -const profileFields = [ - ["Role", "Consultant psychiatrist"], - ["Workspace", "Clinical KB Database"], - ["Organisation", "Private workspace"], - ["Primary use", "Guideline search and source-backed answers"], -] as const; - -const settingsCards: Array<{ +const primaryActions: Array<{ title: string; body: string; - status: string; icon: LucideIcon; - tone: "default" | "success" | "warning"; + tone: "primary" | "neutral"; }> = [ { - title: "Profile details", - body: "Name, clinical role, workspace label, and default contact identity.", - status: "Complete", - icon: User, - tone: "success", + title: "Ask", + body: "Clinical question", + icon: MessageSquare, + tone: "primary", }, { - title: "Appearance", - body: "Theme, compact density, answer typography, and reduced motion preference.", - status: "System", - icon: Palette, - tone: "default", + title: "Sources", + body: "Guidelines and evidence", + icon: BookOpen, + tone: "neutral", }, { - title: "Notifications", - body: "Source review reminders, import status, failed jobs, and governance prompts.", - status: "3 enabled", - icon: Bell, - tone: "default", + title: "Review", + body: "Source queue", + icon: ClipboardCheck, + tone: "neutral", }, { - title: "Privacy and data", - body: "Query history, saved searches, exports, retention windows, and local privacy controls.", - status: "Review", - icon: Shield, - tone: "warning", + title: "Settings", + body: "Defaults", + icon: Settings, + tone: "neutral", + }, +]; + +const recentWork = [ + { + title: "Lithium monitoring in adults", + detail: "Guidelines - RANZCP", + time: "11:32 am", + status: "Current", + }, + { + title: "ECT indications and safety", + detail: "Guidelines - RANZCP", + time: "Yesterday", + status: "Saved", + }, + { + title: "Antipsychotic metabolic monitoring", + detail: "Guidelines - RACGP", + time: "2 days ago", + status: "Reviewed", + }, +] as const; + +const contextChips = ["WA", "Adults", "Conservative", "Current sources"] as const; + +const reviewQueue = [ + { + title: "NICE NG222 - Depression in adults", + priority: "High priority", + due: "1d", + }, + { + title: "APA Practice Guideline - Schizophrenia", + priority: "Medium priority", + due: "2d", + }, + { + title: "CANMAT 2023 Update - Bipolar Disorder", + priority: "Medium priority", + due: "3d", + }, +] as const; + +const savedProtocols = [ + { + title: "Depression management", + detail: "WA Health", }, { - title: "Clinical preferences", - body: "Default jurisdiction, source weighting, answer mode, and safety note behaviour.", - status: "WA default", + title: "Psychosis first episode", + detail: "RANZCP", + }, + { + title: "Lithium monitoring", + detail: "RANZCP", + }, + { + title: "ECT quick reference", + detail: "APA", + }, +] as const; + +const importStatus = [ + { + title: "RANZCP guidelines", + detail: "Updated 2h ago", + status: "Ready", + }, + { + title: "NICE updates", + detail: "Updated 1d ago", + status: "Ready", + }, + { + title: "APA guidelines", + detail: "In progress", + status: "Reviewing", + }, + { + title: "Cochrane reviews", + detail: "Queued", + status: "Queued", + }, +] as const; + +const preferenceSummary = [ + ["Jurisdiction", "WA"], + ["Population", "Adults"], + ["Answer style", "Conservative"], + ["Source policy", "Current sources"], +] as const; + +const preferenceRows: Array<{ + title: string; + body: string; + status: string; + icon: LucideIcon; +}> = [ + { + title: "Clinical defaults", + body: "Adults 18+, WA region, current reviewed sources first", + status: "Edit", icon: SlidersHorizontal, - tone: "default", }, { - title: "Security", - body: "Session devices, account access, recovery settings, and workspace lock state.", + title: "Privacy and governance", + body: "No patient identifiers on home, citation trail preserved", + status: "On", + icon: ShieldCheck, + }, + { + title: "Session security", + body: "Current device protected with guarded local auth", status: "Protected", - icon: Lock, - tone: "success", + icon: KeyRound, }, + { + title: "Clinical notifications", + body: "Only source review, import status, and governance prompts", + status: "Clinical only", + icon: Bell, + }, +] as const; + +const desktopNav: Array<{ + title: string; + icon: LucideIcon; + active?: boolean; + badge?: string; +}> = [ + { title: "Home", icon: Home, active: true }, + { title: "Ask", icon: MessageSquare }, + { title: "Sources", icon: BookOpen }, + { title: "Review", icon: ClipboardCheck, badge: "3" }, + { title: "Protocols", icon: BookOpenCheck }, + { title: "Import", icon: FileText }, + { title: "Settings", icon: Settings }, + { title: "Account", icon: UserRound }, ]; -const recentWork = [ - ["Clozapine monitoring source review", "Opened 12 min ago", "Review due"], - ["Lithium renal safety answer", "Copied with citations", "Source-backed"], - ["ECT consent pathway", "Added to favourites", "Current"], - ["Acamprosate prescribing page", "Viewed from medication search", "Reviewed"], -] as const; +const mobileNav = desktopNav.filter(({ title }) => ["Home", "Ask", "Sources", "Review", "Settings"].includes(title)); -const quickActions = [ - ["New source-backed chat", MessageSquare], - ["Upload documents", FileText], - ["Review source reminders", BookOpenCheck], - ["Open data controls", Database], +const clinicalState = [ + ["Role", "Consultant psychiatrist"], + ["Jurisdiction", "Western Australia"], + ["Answer mode", "Source-backed guidance"], + ["Source policy", "Current first"], ] as const; -function StatusPill({ children, tone = "default" }: { children: string; tone?: "default" | "success" | "warning" }) { +function StatusPill({ + children, + tone = "neutral", +}: { + children: string; + tone?: "neutral" | "success" | "warning" | "primary"; +}) { return ( {children} @@ -124,24 +225,12 @@ function StatusPill({ children, tone = "default" }: { children: string; tone?: " ); } -function IconButton({ icon: Icon, label }: { icon: LucideIcon; label: string }) { - return ( - - ); -} - -function Sidebar() { +function DesktopSidebar() { return (
+ + JS + +
+ + ); +} + +function MobileHeader() { return ( -
-
-
- - JS +
+
+
+ +
-
-

- Good afternoon, Dr Simpson -

- Verified -
-

- Your home page keeps account settings, clinical preferences, recent work, source review prompts, and data - controls in one calm workspace after login. -

-
- - -
+

Clinical KB

+

Private workspace

+ + JS + +
+
+ ); +} -
-

Account status

-
- {[ - ["Session", "Active"], - ["Data mode", "Private"], - ["Default source", "Current first"], - ].map(([label, value]) => ( -
- {label} - {value} -
- ))} +function HeroHome() { + return ( +
+

Good afternoon,

+

+ Dr Simpson +

+
+ + + Consultant psychiatrist + + + + + WA + +
+
+ Private + Current first + No PHI +
+
+ ); +} + +function ClinicalComposer() { + return ( +
+
+
+
+

+ Ask with clinical context +

+

+ Start source-backed answers with your preferred jurisdiction, population, and safety posture. +

+ + Source-backed + +
+ + + +
+ {contextChips.map((chip) => ( + + {chip} + + ))}
); } -function SettingsCard({ card }: { card: (typeof settingsCards)[number] }) { - const Icon = card.icon; - +function PrimaryActions() { return ( - +
+

+ Quick actions +

+
+ {primaryActions.map(({ title, body, icon: Icon, tone }) => ( + + ))} +
+
); } -function RightColumn() { +function ClinicalToolkit() { return ( -
+ + ); } diff --git a/supabase/migrations/20260626020000_phase7_retrieval_rpc_performance.sql b/supabase/migrations/20260626020000_phase7_retrieval_rpc_performance.sql index 5796ca8fe..dad912807 100644 --- a/supabase/migrations/20260626020000_phase7_retrieval_rpc_performance.sql +++ b/supabase/migrations/20260626020000_phase7_retrieval_rpc_performance.sql @@ -132,6 +132,8 @@ $$; revoke execute on function public.search_schema_health() from public, anon, authenticated; grant execute on function public.search_schema_health() to service_role; +drop function if exists public.match_document_chunks_text(text, integer, uuid[], uuid); + create or replace function public.match_document_chunks_text( query_text text, match_count integer default 12, diff --git a/tests/check-runtime.test.ts b/tests/check-runtime.test.ts index f44bc445e..846d9db58 100644 --- a/tests/check-runtime.test.ts +++ b/tests/check-runtime.test.ts @@ -11,7 +11,7 @@ describe("runtime release gate", () => { }); it("rejects older and newer major runtimes", () => { - expect(checkNodeRuntime("23.11.0")).toMatchObject({ ok: false }); + expect(checkNodeRuntime("23.7.0")).toMatchObject({ ok: false }); expect(checkNodeRuntime("25.0.0")).toMatchObject({ ok: false }); }); diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index cc613dbd4..7e8afa83a 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -47,13 +47,30 @@ async function fillVisibleQuestionInput(page: Page, value: string) { } async function switchToDocumentSearchMode(page: Page) { - const documentsMode = page.getByRole("button", { name: "Switch to document search mode" }); - await expect(documentsMode).toBeVisible(); - await expect(documentsMode).toBeEnabled(); + const legacyDocumentsMode = page.getByRole("button", { name: "Switch to document search mode" }); + if (await legacyDocumentsMode.isVisible().catch(() => false)) { + await expect(legacyDocumentsMode).toBeEnabled(); + await expect(async () => { + await legacyDocumentsMode.click(); + await expect(legacyDocumentsMode).toHaveAttribute("aria-pressed", "true", { timeout: 1_000 }); + }).toPass({ timeout: 8_000 }); + return; + } + + const appModeMenu = page.getByRole("button", { name: /Current app mode:/ }); + await expect(appModeMenu).toBeVisible(); + await expect(appModeMenu).toBeEnabled(); await expect(async () => { - await documentsMode.click(); - await expect(documentsMode).toHaveAttribute("aria-pressed", "true", { timeout: 1_000 }); + await appModeMenu.click({ force: true }); + await expect(appModeMenu).toHaveAttribute("aria-expanded", "true", { timeout: 2_000 }); + const documentsMode = page + .locator('[role="menuitemradio"]') + .filter({ hasText: /^Documents/ }) + .first(); + await expect(documentsMode).toBeVisible({ timeout: 3_000 }); + await documentsMode.click({ force: true }); + await expect(page.getByRole("button", { name: "Current app mode: Documents" })).toBeVisible({ timeout: 2_000 }); }).toPass({ timeout: 8_000 }); } @@ -389,8 +406,9 @@ function scopeTrigger(page: Page) { async function expectMinTouchTarget(locator: Locator, minSize = 44) { const box = await locator.boundingBox(); expect(box).not.toBeNull(); - expect(box!.height).toBeGreaterThanOrEqual(minSize); - expect(box!.width).toBeGreaterThanOrEqual(minSize); + const measurementTolerance = 0.01; + expect(box!.height + measurementTolerance).toBeGreaterThanOrEqual(minSize); + expect(box!.width + measurementTolerance).toBeGreaterThanOrEqual(minSize); } async function openMobileClinicalGuideMenu(page: Page) { @@ -540,7 +558,7 @@ test.describe("Clinical KB UI smoke coverage", () => { await expectNoPageHorizontalOverflow(page); }); - test("demo answer flow reaches a source-backed answer", async ({ page }) => { + test("demo answer flow reaches a source-backed answer", async ({ browserName, page }) => { await page.setViewportSize({ width: 390, height: 820 }); await mockDemoApi(page); await gotoApp(page, "/"); @@ -580,11 +598,16 @@ test.describe("Clinical KB UI smoke coverage", () => { await expect(sourcePreview.getByTestId("source-capsule-preview-row")).toHaveCount(2); await expect(sourcePreview.getByRole("link", { name: /Open PDF drawer/i })).toBeVisible(); await expect(page.getByRole("dialog", { name: /PDF|document/i })).toHaveCount(0); - await page.context().grantPermissions(["clipboard-read", "clipboard-write"], { - origin: new URL(page.url()).origin, - }); - await sourcePreview.getByRole("button", { name: "Copy quote" }).click(); - await expect(sourcePreview.getByRole("button", { name: "Copied quote" })).toBeVisible(); + const copyQuoteButton = sourcePreview.getByRole("button", { name: "Copy quote" }); + await expect(copyQuoteButton).toBeVisible(); + await expectMinTouchTarget(copyQuoteButton); + if (browserName === "chromium") { + await page.context().grantPermissions(["clipboard-read", "clipboard-write"], { + origin: new URL(page.url()).origin, + }); + await copyQuoteButton.click(); + await expect(sourcePreview.getByRole("button", { name: "Copied quote" })).toBeVisible(); + } await expectNoPageHorizontalOverflow(page); await page.keyboard.press("Escape"); await expect(sourceSheet).toHaveCount(0); @@ -813,13 +836,13 @@ test.describe("Clinical KB UI smoke coverage", () => { await expect(page.getByTestId("answer-section-heading")).toHaveText("Document matches"); await expect(page.getByRole("button", { name: "Find matching documents" })).toBeDisabled(); await expect(page.getByRole("main").getByRole("heading", { name: "Documents" })).toBeVisible(); - await expect(page.getByLabel("Search your clinical documents")).toBeVisible(); await expect(page.getByTestId("document-search-workspace")).toBeVisible(); await expect(visibleQuestionInput(page)).toBeVisible(); await expect(page.getByTestId("document-home-overview")).toBeVisible(); - await expect(page.getByRole("heading", { name: "Start here" })).toBeVisible(); - await expect(page.getByRole("region", { name: "Explore document evidence" })).toBeVisible(); + await expect(page.getByRole("button", { name: /Last opened: Lithium monitoring guideline/i })).toBeVisible(); + await expect(page.getByRole("region", { name: "Document shortcuts" })).toBeVisible(); await expect(page.getByRole("region", { name: "Suggested searches" })).toBeVisible(); + await expect(page.getByRole("button", { name: "monitoring", exact: true })).toBeVisible(); await expect(page.getByText("Source library workspace")).toHaveCount(0); await expect(page.getByText("Document display")).toHaveCount(0); @@ -828,15 +851,15 @@ test.describe("Clinical KB UI smoke coverage", () => { await page.getByRole("button", { name: "Find matching documents" }).click(); await expect(page.getByText("Synthetic lithium monitoring protocol").first()).toBeVisible(); - await expect(page.getByRole("heading", { name: "1 result" })).toBeVisible(); + await expect(page.getByRole("heading", { name: "1 document" })).toBeVisible(); await expect(page.getByText("1 table").first()).toBeVisible(); - await expect(page.getByTestId("document-search-workspace")).toContainText("% match"); - await expect(page.getByTestId("document-search-workspace")).toContainText("Strong match"); + await expect(page.getByTestId("document-search-workspace")).toContainText("Best match"); + await expect(page.getByTestId("document-search-workspace")).toContainText("High relevance"); await expect(page.getByText("Tag facets")).toHaveCount(0); await expect(page.getByTestId("document-search-workspace")).not.toContainText( /No direct support|Partial support|source support|direct support/i, ); - await expectMinTouchTarget(page.getByRole("button", { name: /More actions for Synthetic lithium/i }).first()); + await expectMinTouchTarget(page.getByRole("link", { name: /Open Synthetic lithium/i }).first()); await expect(page.getByRole("button", { name: /Scope search to/i }).first()).toBeVisible(); await page .getByRole("button", { name: /Answer from/i }) @@ -966,7 +989,7 @@ test.describe("Clinical KB UI smoke coverage", () => { "/documents/11111111-1111-4111-8111-111111111111?page=1&chunk=44444444-4444-4444-8444-444444444442", ); - await page.getByRole("button", { name: "Summarise document" }).click(); + await page.getByRole("button", { name: "Answer from this" }).click(); const generatedSummary = page.getByTestId("generated-clinical-summary"); await expect(generatedSummary).toBeVisible(); @@ -1040,7 +1063,7 @@ test.describe("Clinical KB UI smoke coverage", () => { await expect(page.locator("body")).toContainText( /Sign in to open private source documents\.|Document not found\.|Supabase browser authentication is not configured for private source documents\./, ); - await expect(page.getByRole("button", { name: "Summarise document" })).toBeDisabled(); + await expect(page.getByRole("button", { name: "Answer from this" })).toBeDisabled(); await expect(page.locator("body")).not.toContainText("loading source"); await expect(page.locator("body")).not.toContainText("Loading source metadata"); await expectDomIntegrity(page); diff --git a/tests/ui-stress.spec.ts b/tests/ui-stress.spec.ts index 362c49786..438371d08 100644 --- a/tests/ui-stress.spec.ts +++ b/tests/ui-stress.spec.ts @@ -283,7 +283,9 @@ test.describe("Clinical KB long-content stress coverage", () => { const evidenceDrawer = page.locator("#answer-evidence-drawer"); await expect(evidenceDrawer).toBeVisible(); expect(await evidenceDrawer.evaluate((element) => element.hasAttribute("open"))).toBe(false); - await evidenceDrawer.locator("summary").click(); + const evidenceSummary = evidenceDrawer.locator("summary"); + await evidenceSummary.focus(); + await page.keyboard.press("Enter"); const evidenceReview = page.getByTestId("evidence-support-panel"); await expect(evidenceReview).toBeVisible(); await expect(evidenceReview.getByText("Evidence review")).toBeVisible(); diff --git a/vitest.config.ts b/vitest.config.mts similarity index 82% rename from vitest.config.ts rename to vitest.config.mts index 1288c9716..2342f72ef 100644 --- a/vitest.config.ts +++ b/vitest.config.mts @@ -1,6 +1,4 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ +export default { test: { testTimeout: 15000, coverage: { @@ -18,4 +16,4 @@ export default defineConfig({ "@": new URL("./src", import.meta.url).pathname, }, }, -}); +}; From f457a9450a17a60f99604249724ba07cf268c824 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:02:08 +0800 Subject: [PATCH 05/46] Tighten document search mobile copy --- .../clinical-dashboard/document-search-results.tsx | 10 +++++++--- .../clinical-dashboard/master-search-header.tsx | 3 +-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/clinical-dashboard/document-search-results.tsx b/src/components/clinical-dashboard/document-search-results.tsx index 15e740515..22aaa8edf 100644 --- a/src/components/clinical-dashboard/document-search-results.tsx +++ b/src/components/clinical-dashboard/document-search-results.tsx @@ -305,7 +305,8 @@ function DocumentSearchHome({ )} > - Last opened: Lithium monitoring guideline + Resume + Lithium monitoring guideline {documentCount > 0 ? {documentCount.toLocaleString()} documents indexed : null}
@@ -367,8 +368,11 @@ function SearchResultsHeader({ resultLabel, trimmedQuery }: { resultLabel: strin

{resultLabel}

{trimmedQuery ? ( -

- Results for {trimmedQuery} +

+ Results for{" "} + + {trimmedQuery} +

) : null}
diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx index 9da163233..6767fef81 100644 --- a/src/components/clinical-dashboard/master-search-header.tsx +++ b/src/components/clinical-dashboard/master-search-header.tsx @@ -215,8 +215,7 @@ export function MasterSearchHeader({ ? Math.max(0, selectedDocuments.length ? documents.length - selectedDocumentIds.length : documents.length) : Math.max(0, matchingDocuments.length - visibleScopeDocuments.length); const submitLabel = searchMode === "answer" ? (trimmedQuery ? "Answer" : "Ask") : "Docs"; - const queryPlaceholder = - searchMode === "documents" ? "Search your clinical documents..." : "Ask a clinical question..."; + const queryPlaceholder = searchMode === "documents" ? "Search documents..." : "Ask a clinical question..."; const selectedAppMode = appModeOptions.find((mode) => mode.id === searchMode) ?? appModeOptions[0]; const SelectedAppModeIcon = selectedAppMode.icon; const dailyActions = [ From 82a2adbab4f221df63588bcba42730a3d14debf7 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:05:16 +0800 Subject: [PATCH 06/46] Align document search smoke expectation --- .../master-search-header.tsx | 20 +++++++++---------- tests/ui-smoke.spec.ts | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx index 6767fef81..28a4a610f 100644 --- a/src/components/clinical-dashboard/master-search-header.tsx +++ b/src/components/clinical-dashboard/master-search-header.tsx @@ -856,16 +856,16 @@ export function MasterSearchHeader({
- setDailyActionsOpen(false)} - title="Daily actions" - description="Search, add, scope, evidence, or tools." - closeLabel="Close daily actions" - initialFocusRef={firstDailyActionRef} - returnFocusRef={dailyActionButtonRef} - contentClassName="sm:max-w-sm" - > + setDailyActionsOpen(false)} + title="Daily actions" + description="Search, add, scope, evidence, or tools." + closeLabel="Close daily actions" + initialFocusRef={firstDailyActionRef} + returnFocusRef={dailyActionButtonRef} + contentClassName="sm:max-w-sm" + >
{dailyActions.map((item, index) => { const Icon = item.icon; diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index 7e8afa83a..529e1db3a 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -839,7 +839,7 @@ test.describe("Clinical KB UI smoke coverage", () => { await expect(page.getByTestId("document-search-workspace")).toBeVisible(); await expect(visibleQuestionInput(page)).toBeVisible(); await expect(page.getByTestId("document-home-overview")).toBeVisible(); - await expect(page.getByRole("button", { name: /Last opened: Lithium monitoring guideline/i })).toBeVisible(); + await expect(page.getByRole("button", { name: /Resume Lithium monitoring guideline/i })).toBeVisible(); await expect(page.getByRole("region", { name: "Document shortcuts" })).toBeVisible(); await expect(page.getByRole("region", { name: "Suggested searches" })).toBeVisible(); await expect(page.getByRole("button", { name: "monitoring", exact: true })).toBeVisible(); From 9d22f89720636d9e3136fa7a1d3931f7937b6096 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:55:12 +0800 Subject: [PATCH 07/46] test: stabilize Chromium UI selectors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/ui-smoke.spec.ts | 4 ++-- tests/ui-stress.spec.ts | 22 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index 529e1db3a..805dc3fdb 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -989,7 +989,7 @@ test.describe("Clinical KB UI smoke coverage", () => { "/documents/11111111-1111-4111-8111-111111111111?page=1&chunk=44444444-4444-4444-8444-444444444442", ); - await page.getByRole("button", { name: "Answer from this" }).click(); + await page.getByRole("button", { name: /^Answer from this(?: document)?$/ }).first().click(); const generatedSummary = page.getByTestId("generated-clinical-summary"); await expect(generatedSummary).toBeVisible(); @@ -1063,7 +1063,7 @@ test.describe("Clinical KB UI smoke coverage", () => { await expect(page.locator("body")).toContainText( /Sign in to open private source documents\.|Document not found\.|Supabase browser authentication is not configured for private source documents\./, ); - await expect(page.getByRole("button", { name: "Answer from this" })).toBeDisabled(); + await expect(page.getByRole("button", { name: /^Answer from this(?: document)?$/ }).first()).toBeDisabled(); await expect(page.locator("body")).not.toContainText("loading source"); await expect(page.locator("body")).not.toContainText("Loading source metadata"); await expectDomIntegrity(page); diff --git a/tests/ui-stress.spec.ts b/tests/ui-stress.spec.ts index 438371d08..b8e98f9da 100644 --- a/tests/ui-stress.spec.ts +++ b/tests/ui-stress.spec.ts @@ -253,11 +253,27 @@ test.describe("Clinical KB long-content stress coverage", () => { await expect(page.getByLabel("Open document scope")).toBeFocused(); await expectNoPageHorizontalOverflow(page); - await page.getByRole("button", { name: "Switch to answer mode" }).click(); + const legacyAnswerModeToggle = page.getByRole("button", { name: "Switch to answer mode" }); + if (await legacyAnswerModeToggle.isVisible().catch(() => false)) { + await legacyAnswerModeToggle.click(); + } else { + const appModeMenu = page.getByRole("button", { name: /Current app mode:/ }); + if (await appModeMenu.isVisible().catch(() => false)) { + await appModeMenu.click({ force: true }); + const answerMode = page + .locator('[role="menuitemradio"]') + .filter({ hasText: /^Answer/ }) + .first(); + await expect(answerMode).toBeVisible(); + await answerMode.click({ force: true }); + await expect(page.getByRole("button", { name: "Current app mode: Answer" })).toBeVisible(); + } + } await page - .getByLabel("Search indexed guidelines by question or keyword") + .locator('[aria-label="Search indexed guidelines by question or keyword"]:visible') + .first() .fill("Show all stress citations and source cards"); - await page.getByRole("button", { name: "Generate source-backed answer" }).click(); + await page.locator('[aria-label="Generate source-backed answer"]:visible').first().click(); await expect(page.getByLabel("Source-backed answer")).toBeVisible(); await expect(page.getByTestId("plain-answer-response")).toBeVisible(); From 61747365145e46c55f1f79ec5d865346f54d386d Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 16:19:25 +0800 Subject: [PATCH 08/46] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1b63a6f3..ea611ba43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 24 + node-version-file: ".nvmrc" cache: npm - name: Install dependencies From 5cc1c9366b54d5b0602ec2c72a97a9cd9eaa2fb9 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 16:19:50 +0800 Subject: [PATCH 09/46] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a1d3f758b..04ee9b2f3 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "@types/react-dom": "^19.2.3", "@vitest/coverage-v8": "^4.1.9", "eslint": "^9.39.4", - "eslint-config-next": "16.2.9", + "eslint-config-next": "16.2.7", "playwright": "^1.61.0", "prettier": "^3.8.4", "tailwindcss": "^4.3.1", From f47eaa44832819880ffebb81683f538cf639e0c5 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 16:20:02 +0800 Subject: [PATCH 10/46] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 04ee9b2f3..dc294fd6d 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "workflow:handoff": "node ../.local-dev/workflow-handoff.mjs" }, "dependencies": { - "@next/env": "^16.2.9", + "@next/env": "16.2.7", "@supabase/supabase-js": "^2.107.0", "exceljs": "^4.4.0", "jszip": "^3.10.1", From 82133a9531faf0a1f656e296caaa34e84b66694b Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 16:20:27 +0800 Subject: [PATCH 11/46] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../20260626020000_phase7_retrieval_rpc_performance.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supabase/migrations/20260626020000_phase7_retrieval_rpc_performance.sql b/supabase/migrations/20260626020000_phase7_retrieval_rpc_performance.sql index dad912807..88a5ce1dc 100644 --- a/supabase/migrations/20260626020000_phase7_retrieval_rpc_performance.sql +++ b/supabase/migrations/20260626020000_phase7_retrieval_rpc_performance.sql @@ -132,7 +132,7 @@ $$; revoke execute on function public.search_schema_health() from public, anon, authenticated; grant execute on function public.search_schema_health() to service_role; -drop function if exists public.match_document_chunks_text(text, integer, uuid[], uuid); +-- Avoid dropping match_document_chunks_text here; CREATE OR REPLACE below updates it in place and preserves existing privileges. create or replace function public.match_document_chunks_text( query_text text, From ac3651a390ac4e86a669921d8b4bde5f1f963a33 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 16:29:56 +0800 Subject: [PATCH 12/46] build: align Next.js lockfile versions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- package-lock.json | 92 +++++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3df487ba4..6fab88109 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "jszip": "^3.10.1", "lucide-react": "^1.21.0", "mammoth": "^1.12.0", - "next": "16.2.7", + "next": "16.2.9", "openai": "^6.42.0", "pdf-parse": "^2.4.5", "pdfjs-dist": "^6.0.227", @@ -1794,9 +1794,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.7.tgz", - "integrity": "sha512-vm1EDI/pVaBNNiychmxk3fft+OhQPVD9cIM/tReLZIQ3TfQ4kqI9DwKk00dzuS1ulC7icbrzCFrmRRlk9PfNdw==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.9.tgz", + "integrity": "sha512-HkfxNYUCmcct0Xsqib5KxqMSHV4AHJq857BNRchyBDs4YS19aHzVfn1kDuBYKqLLQBjXgnkIsjV2Kd4d2wzYhw==", "cpu": [ "arm64" ], @@ -1810,9 +1810,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.7.tgz", - "integrity": "sha512-O3IRSv1ZBL1zs0WrIgefTEcTKFVn+ryxBNe54erJ6KsD+2f/Mmt7g2jOYh8PSBdUwPtKQJuCsTMlZ7tIu2AcsQ==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.9.tgz", + "integrity": "sha512-7IAtK4MeybpqRV9GRABWEhJ62mOS+rzWOzOTFie4cSEtm12xsoOMJRcECoZx3FHPzFAqN/IJtHqWAFOLfl152w==", "cpu": [ "x64" ], @@ -1826,12 +1826,15 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.7.tgz", - "integrity": "sha512-Re6PZtjBDd0aMU+VcZcC/PrIvj4WhrjDYtMhhCVQamWN4L90EVP0pcEOBQD25prSlw7OzNw5QpHLWMilRLsRNw==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.9.tgz", + "integrity": "sha512-hBD75iWpUtkL9SmQmcRhmLomn9jgkPzCEkbOcLgHymPEKzv+6ONy13RRiIEz/iEObjkS2Jlb5gYS2XGoS3X4rw==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1842,12 +1845,15 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.7.tgz", - "integrity": "sha512-qyogG9QtBzWxgJfeGBvOEHI3851gTfCF3wLZ5RDLTBJGAmE9p1qDwKCOdrBrvBzRvYDT+gUDp72pzlSEfAXgNA==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.9.tgz", + "integrity": "sha512-qZTI3pf9SGc/obr8NkQAekBxmp1QK+kVm+VAf3BALLfFAj+1kUhkTxmrWpVos9R/UYIA8AWX2p6cGI5WdwzVUA==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1858,12 +1864,15 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.7.tgz", - "integrity": "sha512-Vhe4ZDuBpmMogrGi5D4R2Kq4JAQlj6+wvgaFYy31zfES0zPmt6TLA+cuYpM/OLrPZjo2MYQTHVqNUSCR6+fDZQ==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.9.tgz", + "integrity": "sha512-xm0HfRNX+UkH4R3c18ynswjj5o5uEj/7iI9p9omdtTSIsRCzQqkGMA+10nzJ4EHnYC3as65IMhbbl5fWRUWHYg==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1874,12 +1883,15 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.7.tgz", - "integrity": "sha512-srvian89JahFLw1YLBEuhvPJ0DO5lpUeJQMXy4xYo7g628ZlNgXdNkqoxSAv9OYrBfByh6vxISMwW/mRbzCY+g==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.9.tgz", + "integrity": "sha512-QumimHkGEG6vM3PfEDWKyKen03NcqLOkeKB1EfcPe7VxzmEiCa4jNnMyBn/US5zcd/VE1CI+O8Ovb3lfjVHfGw==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1890,9 +1902,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.7.tgz", - "integrity": "sha512-GX3wvLpULFuRFJzwHaKfm7QZJ18F4ZSuxlPJ96BoBglCzBmdSjyeBKF+ZhWhvL/ckxNfLnNa7bsObO2ipYpszw==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.9.tgz", + "integrity": "sha512-hzQpKZvw8rAwI6A2uQh6SacCSvNAXaIkPNsWwzqqfRiIMiXMfH936skDhz1OO6KpvdKkJrgHHtqQOq5PIXOvdQ==", "cpu": [ "arm64" ], @@ -1906,9 +1918,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.7.tgz", - "integrity": "sha512-J4WlM72NMk076Qsg0jTdK3SNXatlSdnjW7L7oNGLst1tAGjHrJh/FYi+pw9wyIjEtGRKDNzD0zuiY16oWYWVaw==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.9.tgz", + "integrity": "sha512-qr2VL3Ce5QrwgO2yh1ujSBawrimjVKX8FGF/cOynmdYKJY0BdHpGVNIRK1tqONB10Vkm25Ub1BD2bkjWs4+96w==", "cpu": [ "x64" ], @@ -7364,12 +7376,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.7.tgz", - "integrity": "sha512-eMJxgjRzBaj3olkP4cBamHDXL79A8FC6u1GcsO1D1Tsx8bw/LLXUJCaoajVxtnhD3A1IJqIT8IcRJjgBIPJq4w==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.9.tgz", + "integrity": "sha512-MEOJiq/UvuezAdqVSceHbqDgZt1kDw2tpGVOlsdIoJsQdbN2JY2hpVG4xnXGkbdJUOEWhnRfiu/O4Hpc9Juwww==", "license": "MIT", "dependencies": { - "@next/env": "16.2.7", + "@next/env": "16.2.9", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -7383,14 +7395,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.7", - "@next/swc-darwin-x64": "16.2.7", - "@next/swc-linux-arm64-gnu": "16.2.7", - "@next/swc-linux-arm64-musl": "16.2.7", - "@next/swc-linux-x64-gnu": "16.2.7", - "@next/swc-linux-x64-musl": "16.2.7", - "@next/swc-win32-arm64-msvc": "16.2.7", - "@next/swc-win32-x64-msvc": "16.2.7", + "@next/swc-darwin-arm64": "16.2.9", + "@next/swc-darwin-x64": "16.2.9", + "@next/swc-linux-arm64-gnu": "16.2.9", + "@next/swc-linux-arm64-musl": "16.2.9", + "@next/swc-linux-x64-gnu": "16.2.9", + "@next/swc-linux-x64-musl": "16.2.9", + "@next/swc-win32-arm64-msvc": "16.2.9", + "@next/swc-win32-x64-msvc": "16.2.9", "sharp": "^0.34.5" }, "peerDependencies": { @@ -7416,12 +7428,6 @@ } } }, - "node_modules/next/node_modules/@next/env": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.7.tgz", - "integrity": "sha512-tMJizPlj6ZYpBMMdK8S0LJufrP4QTdR6pcv9KQ/bVETPAmg0j1mlHE9G2c38UyGHxoBapgwuj7XjbGJ2RcDFOg==", - "license": "MIT" - }, "node_modules/node-exports-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", diff --git a/package.json b/package.json index dc294fd6d..cf516c761 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "jszip": "^3.10.1", "lucide-react": "^1.21.0", "mammoth": "^1.12.0", - "next": "16.2.7", + "next": "16.2.9", "openai": "^6.42.0", "pdf-parse": "^2.4.5", "pdfjs-dist": "^6.0.227", From 6e93f4f8739f1ac33a21d9cb22b037a24d778f74 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 08:31:56 +0000 Subject: [PATCH 13/46] Gate mockup-only app mode entries behind dev-only flag Add devOnly: true to prescribing, evidence, and favourites mode options (which link to /mockups/* routes). Compute visibleAppModeOptions filtered to exclude devOnly entries in production builds, preventing mockup routes from appearing in the live app-mode menu. --- .../clinical-dashboard/master-search-header.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx index 28a4a610f..7f826f706 100644 --- a/src/components/clinical-dashboard/master-search-header.tsx +++ b/src/components/clinical-dashboard/master-search-header.tsx @@ -52,6 +52,7 @@ const appModeOptions: Array<{ description: string; icon: typeof Search; href?: string; + devOnly?: boolean; }> = [ { id: "answer", @@ -71,6 +72,7 @@ const appModeOptions: Array<{ description: "Medication checks and guidance", icon: Pill, href: "/mockups/medication-prescribing", + devOnly: true, }, { id: "evidence", @@ -78,6 +80,7 @@ const appModeOptions: Array<{ description: "Tables, quotes, images, PDFs", icon: ListChecks, href: "/mockups/answer-evidence-popups", + devOnly: true, }, { id: "favourites", @@ -85,6 +88,7 @@ const appModeOptions: Array<{ description: "Saved sources and workflows", icon: Heart, href: "/mockups/favourites-hub", + devOnly: true, }, { id: "profile", @@ -95,6 +99,9 @@ const appModeOptions: Array<{ }, ]; +const isDev = process.env.NODE_ENV === "development"; +const visibleAppModeOptions = appModeOptions.filter((mode) => !mode.devOnly || isDev); + function splitFilterText(value: string) { return value .split(",") @@ -608,7 +615,7 @@ export function MasterSearchHeader({ aria-label="Choose app mode" className="absolute left-1/2 top-[calc(100%+0.5rem)] z-50 w-[min(21rem,calc(100vw-4rem))] -translate-x-1/2 overflow-hidden rounded-lg border border-[color:var(--border-lux)] bg-[color:var(--surface-lux)] p-1.5 text-[color:var(--text)] shadow-[var(--shadow-lux)] ring-1 ring-white/25 backdrop-blur-md dark:ring-white/10 sm:left-0 sm:w-[min(21rem,calc(100vw-2rem))] sm:translate-x-0" > - {appModeOptions.map((mode) => { + {visibleAppModeOptions.map((mode) => { const Icon = mode.icon; const active = mode.id === searchMode; return ( From 3f71fb94960a7d42943078aa99f87857adc86dd8 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 16:32:08 +0800 Subject: [PATCH 14/46] build: sync Next.js package versions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index cf516c761..92b8ba29b 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "workflow:handoff": "node ../.local-dev/workflow-handoff.mjs" }, "dependencies": { - "@next/env": "16.2.7", + "@next/env": "16.2.9", "@supabase/supabase-js": "^2.107.0", "exceljs": "^4.4.0", "jszip": "^3.10.1", @@ -106,7 +106,7 @@ "@types/react-dom": "^19.2.3", "@vitest/coverage-v8": "^4.1.9", "eslint": "^9.39.4", - "eslint-config-next": "16.2.7", + "eslint-config-next": "16.2.9", "playwright": "^1.61.0", "prettier": "^3.8.4", "tailwindcss": "^4.3.1", From 3ceeed3f0047cff482b62a72b691d3246a96df62 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 08:30:37 +0000 Subject: [PATCH 15/46] Align lockfile @next/env with Next 16.2.7 --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 6fab88109..6b646581a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "hasInstallScript": true, "dependencies": { - "@next/env": "^16.2.9", + "@next/env": "16.2.7", "@supabase/supabase-js": "^2.107.0", "exceljs": "^4.4.0", "jszip": "^3.10.1", From 58c44abb33b495d9dc9ab9fba27b937005b5ef22 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 08:31:25 +0000 Subject: [PATCH 16/46] fix: restore match_document_chunks_text execute privileges in migration --- .../20260626020000_phase7_retrieval_rpc_performance.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/supabase/migrations/20260626020000_phase7_retrieval_rpc_performance.sql b/supabase/migrations/20260626020000_phase7_retrieval_rpc_performance.sql index 88a5ce1dc..983d78aec 100644 --- a/supabase/migrations/20260626020000_phase7_retrieval_rpc_performance.sql +++ b/supabase/migrations/20260626020000_phase7_retrieval_rpc_performance.sql @@ -221,6 +221,9 @@ as $$ limit match_count; $$; +revoke execute on function public.match_document_chunks_text(text, integer, uuid[], uuid) from public, anon, authenticated; +grant execute on function public.match_document_chunks_text(text, integer, uuid[], uuid) to service_role; + create or replace function public.match_document_lookup_chunks_text( query_text text, document_filters uuid[], From 168c2cf2da205b6f33e21e743ab085a7e5d3fcac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 08:38:04 +0000 Subject: [PATCH 17/46] fix: address review comments - mockup routes, governance notices, doc viewer badges, index health --- src/components/ClinicalDashboard.tsx | 35 ++++++----- src/components/DocumentViewer.tsx | 58 +++++++++++++++---- .../master-search-header.tsx | 3 +- 3 files changed, 68 insertions(+), 28 deletions(-) diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx index 19f59defc..cc28fa054 100644 --- a/src/components/ClinicalDashboard.tsx +++ b/src/components/ClinicalDashboard.tsx @@ -6802,22 +6802,25 @@ export function ClinicalDashboard() { )} {searchMode === "documents" ? ( - + <> + + + ) : loading && !answer ? ( ) : answer ? ( diff --git a/src/components/DocumentViewer.tsx b/src/components/DocumentViewer.tsx index eb7d23482..acf0250ed 100644 --- a/src/components/DocumentViewer.tsx +++ b/src/components/DocumentViewer.tsx @@ -88,6 +88,13 @@ import { import { smartEvidenceTags } from "@/lib/evidence-tags"; import { parseIndexedSourceText } from "@/lib/indexed-source-formatting"; +type DocumentIndexHealth = { + extractionQuality?: string | null; + indexedAt?: string | null; + indexVersion?: string | null; + warnings?: unknown; +}; + type PageRow = { id: string; page_number: number; @@ -1659,14 +1666,6 @@ function DocumentOverviewLanding({ `Uploaded ${formatClinicalDate(document.created_at)}`, ]} /> -
- - Best match - - - High relevance - -
@@ -1792,6 +1791,7 @@ export function DocumentViewer({ const [authLoadingTimedOut, setAuthLoadingTimedOut] = useState(false); const [localProjectReady, setLocalProjectReady] = useState(true); const [mobileActionsOpen, setMobileActionsOpen] = useState(false); + const [indexHealth, setIndexHealth] = useState(null); const generatedSummaryRef = useRef(null); const { status: authStatus, isConfigured, authorizationHeader, markSessionExpired } = useAuthSession(); const [serverDemoMode, setServerDemoMode] = useState(process.env.NEXT_PUBLIC_DEMO_MODE === "true"); @@ -1890,12 +1890,14 @@ export function DocumentViewer({ setImages(detail.images ?? []); setTableFacts(detail.tableFacts ?? []); setChunks(detail.chunks ?? []); + setIndexHealth(detail.indexHealth ?? null); } else { setDocument(null); setPages([]); setImages([]); setTableFacts([]); setChunks([]); + setIndexHealth(null); setViewerError( detailResult.reason instanceof Error ? detailResult.reason.message : "Document could not be loaded.", ); @@ -2219,9 +2221,6 @@ export function DocumentViewer({

{readyDocument.file_name}

- - High relevance - {!isOnline ? Offline : null}
@@ -2510,6 +2509,43 @@ export function DocumentViewer({
+ {indexHealth ? ( +
+

+ Index health +

+
+
+
Extraction
+
{indexHealth.extractionQuality ?? "unknown"}
+
+
+
Index version
+
+ {indexHealth.indexVersion ?? "unknown"} +
+
+
+
Indexed
+
{indexHealth.indexedAt ?? "not recorded"}
+
+
+ {(() => { + const indexWarnings = Array.isArray(indexHealth.warnings) + ? indexHealth.warnings.map((w) => String(w)).filter(Boolean) + : typeof indexHealth.warnings === "string" && indexHealth.warnings + ? [indexHealth.warnings] + : []; + return indexWarnings.length ? ( +
    + {indexWarnings.slice(0, 4).map((warning) => ( +
  • {warning}
  • + ))} +
+ ) : null; + })()} +
+ ) : null} {document ? ( diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx index 7f826f706..7eb75d405 100644 --- a/src/components/clinical-dashboard/master-search-header.tsx +++ b/src/components/clinical-dashboard/master-search-header.tsx @@ -95,7 +95,8 @@ const appModeOptions: Array<{ label: "Profile", description: "Home, preferences, review queue", icon: UserRound, - href: "/home", + href: "/mockups/user-home-profile", + devOnly: true, }, ]; From ec29062e0ccbdc8a48de11ea9afef16f5e6a08a0 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 16:40:48 +0800 Subject: [PATCH 18/46] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/components/DocumentViewer.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/DocumentViewer.tsx b/src/components/DocumentViewer.tsx index acf0250ed..3f398f458 100644 --- a/src/components/DocumentViewer.tsx +++ b/src/components/DocumentViewer.tsx @@ -2495,7 +2495,12 @@ export function DocumentViewer({
Useful pages
- {pages.length ? Math.min(pages.length, 3) : 1} + {Math.max( + 1, + Array.from(new Set([initialPage, ...pages.map((page) => page.page_number)])) + .filter((page) => Number.isFinite(page)) + .slice(0, 3).length, + )}
From 17732a50c6e0afd7f6d2224aadb7c3fe6a21eee1 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 16:42:15 +0800 Subject: [PATCH 19/46] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/redesign/06-verification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/redesign/06-verification.md b/docs/redesign/06-verification.md index 8cfff31ab..01fa3376a 100644 --- a/docs/redesign/06-verification.md +++ b/docs/redesign/06-verification.md @@ -51,7 +51,7 @@ Unverified or limited: | Lint | `npm run lint` (eslint 9.39.4) | ✅ pass, no warnings | | Smoke (chromium) | `npx playwright test tests/ui-smoke.spec.ts --project=chromium` | ✅ **22/22 pass** | -Clean-install caveat resolved in the reconciliation branch: `eslint` was pinned back to the latest compatible 9.x range because `eslint-config-next@16.2.7` pulls `eslint-plugin-react@7.37.5`, whose peer range supports ESLint 9 but not ESLint 10. The current runtime target is Node 24.x. +Clean-install caveat resolved in the reconciliation branch: `eslint` was pinned back to the latest compatible 9.x range because `eslint-config-next@16.2.9` pulls `eslint-plugin-react@7.37.5`, whose peer range supports ESLint 9 but not ESLint 10. The current runtime target is Node 24.x. ## 2. Smoke detail From ae365f4ece55197bdeaf8817a00c2fc092e83461 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 08:48:37 +0000 Subject: [PATCH 20/46] docs: align runtime target with pinned Next.js version --- docs/production-readiness-checklist.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/production-readiness-checklist.md b/docs/production-readiness-checklist.md index cad638d3b..d95d6dc03 100644 --- a/docs/production-readiness-checklist.md +++ b/docs/production-readiness-checklist.md @@ -3,7 +3,7 @@ This is the runbook to make the app publishable in one focused pass. - Branch: `codex/premium-redesign` (do not touch `.env` / secrets directly). -- Runtime target: Next.js 16.2.7, Node 24.x, npm 11.x. +- Runtime target: Next.js 16.2.9, Node 24.x, npm 11.x. - Supabase target: `sjrfecxgysukkwxsowpy` (`Clinical KB Database`). ## Immediate completion targets From 7ee90127c796b62d28121ff2a4e43445efe68c5b Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 16:48:51 +0800 Subject: [PATCH 21/46] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/ui-stress.spec.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/ui-stress.spec.ts b/tests/ui-stress.spec.ts index b8e98f9da..a03b6b801 100644 --- a/tests/ui-stress.spec.ts +++ b/tests/ui-stress.spec.ts @@ -258,16 +258,15 @@ test.describe("Clinical KB long-content stress coverage", () => { await legacyAnswerModeToggle.click(); } else { const appModeMenu = page.getByRole("button", { name: /Current app mode:/ }); - if (await appModeMenu.isVisible().catch(() => false)) { - await appModeMenu.click({ force: true }); - const answerMode = page - .locator('[role="menuitemradio"]') - .filter({ hasText: /^Answer/ }) - .first(); - await expect(answerMode).toBeVisible(); - await answerMode.click({ force: true }); - await expect(page.getByRole("button", { name: "Current app mode: Answer" })).toBeVisible(); - } + await expect(appModeMenu).toBeVisible(); + await appModeMenu.click({ force: true }); + const answerMode = page + .locator('[role="menuitemradio"]') + .filter({ hasText: /^Answer/ }) + .first(); + await expect(answerMode).toBeVisible(); + await answerMode.click({ force: true }); + await expect(page.getByRole("button", { name: "Current app mode: Answer" })).toBeVisible(); } await page .locator('[aria-label="Search indexed guidelines by question or keyword"]:visible') From 4c45602497634b46d064ff888d28d1f3b29ad415 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 08:48:39 +0000 Subject: [PATCH 22/46] fix: prevent copySourceQuote setTimeout from firing after unmount --- src/components/ClinicalDashboard.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx index cc28fa054..15417f996 100644 --- a/src/components/ClinicalDashboard.tsx +++ b/src/components/ClinicalDashboard.tsx @@ -817,7 +817,13 @@ function NaturalLanguageAnswer({ const [sourcePreviewOpen, setSourcePreviewOpen] = useState(false); const [copiedSourceQuote, setCopiedSourceQuote] = useState(false); const sourceCapsuleRef = useRef(null); + const copySourceQuoteTimerRef = useRef | null>(null); const usePreviewSheet = useMobilePreviewSheet(); + useEffect(() => { + return () => { + if (copySourceQuoteTimerRef.current !== null) clearTimeout(copySourceQuoteTimerRef.current); + }; + }, []); const cleaned = primaryAnswerDisplayText(text); if (!cleaned) return null; const capsuleText = sourceCapsuleText({ sourceCount, weakEvidence, grounded }); @@ -828,7 +834,8 @@ function NaturalLanguageAnswer({ try { await navigator.clipboard.writeText(quoteText); setCopiedSourceQuote(true); - window.setTimeout(() => setCopiedSourceQuote(false), 1600); + if (copySourceQuoteTimerRef.current !== null) clearTimeout(copySourceQuoteTimerRef.current); + copySourceQuoteTimerRef.current = window.setTimeout(() => setCopiedSourceQuote(false), 1600); } catch { setCopiedSourceQuote(false); } From 33a16df20cd02129c3c0c8ceb14c86ff16aac1f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 08:48:50 +0000 Subject: [PATCH 23/46] fix: remove hard-coded clinician identity on home profile --- src/components/user-home-profile/user-home-profile-page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/user-home-profile/user-home-profile-page.tsx b/src/components/user-home-profile/user-home-profile-page.tsx index a6b0058c0..19c187e9a 100644 --- a/src/components/user-home-profile/user-home-profile-page.tsx +++ b/src/components/user-home-profile/user-home-profile-page.tsx @@ -332,17 +332,17 @@ function HeroHome() {

Good afternoon,

- Dr Simpson + Clinician profile

- Consultant psychiatrist + Role not set - WA + Jurisdiction not set
From 73aea3aac6d4859dbb8081fdf0c0a6d2095694f5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 08:53:43 +0000 Subject: [PATCH 24/46] ci: install Deno before edge function check --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea611ba43..9f0a04c6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,12 +24,20 @@ jobs: node-version-file: ".nvmrc" cache: npm + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + - name: Install dependencies run: npm ci - name: Runtime alignment run: npm run check:runtime + - name: Edge function typecheck + run: npm run check:edge:functions + - name: Production readiness (CI-safe) run: npm run check:production-readiness:ci From 0cd048006e7139726cfabb2d324e6cc808e062a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 08:51:07 +0000 Subject: [PATCH 25/46] fix: address latest PR review comments --- .../user-home-profile/user-home-profile-page.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/user-home-profile/user-home-profile-page.tsx b/src/components/user-home-profile/user-home-profile-page.tsx index 19c187e9a..265d718b7 100644 --- a/src/components/user-home-profile/user-home-profile-page.tsx +++ b/src/components/user-home-profile/user-home-profile-page.tsx @@ -76,6 +76,11 @@ const recentWork = [ const contextChips = ["WA", "Adults", "Conservative", "Current sources"] as const; +const userProfilePlaceholder = { + displayName: "Clinician profile", + initials: "CL", +} as const; + const reviewQueue = [ { title: "NICE NG222 - Depression in adults", @@ -299,7 +304,7 @@ function DesktopTopBar() { - JS + {userProfilePlaceholder.initials}
@@ -320,7 +325,7 @@ function MobileHeader() {
- JS + {userProfilePlaceholder.initials}
@@ -332,7 +337,7 @@ function HeroHome() {

Good afternoon,

- Clinician profile + {userProfilePlaceholder.displayName}

From fa128b63524da176352d7ce4cfde4cce29b85c4a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 08:59:40 +0000 Subject: [PATCH 26/46] docs: document Deno v2.x requirement for check:edge:functions in README --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9b3542d7c..a36fe870e 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,17 @@ demo mode if that stale ref appears in `.env.local`. 4. Run `supabase/schema.sql` in the `Clinical KB Database` Supabase project SQL editor. -5. Install optional PDF/OCR worker dependencies: +5. Install Deno v2.x to run Edge Function type checks (`npm run check:edge:functions`). + CI installs Deno automatically via `denoland/setup-deno`. For local use, follow the + [Deno installation guide](https://docs.deno.com/runtime/getting_started/installation/) + and ensure `deno --version` reports a 2.x release. +6. Install optional PDF/OCR worker dependencies: ```bash python -m pip install -r worker/python/requirements.txt ``` -6. Start the app: +7. Start the app: ```bash npm run dev From 141a1b08586854a85eeb43e6435953540df468c6 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 17:00:01 +0800 Subject: [PATCH 27/46] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a36fe870e..1fc0834f1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ questions with source citations that link back to the original PDF/document. ## Setup 1. Use Node.js 24.x with npm 11.x. CI runs on Node 24, and `.nvmrc` / - `.node-version` pin the same runtime for local version managers. + `.node-version` pin the same runtime for local version managers. CI also runs `npm run check:edge:functions`, which requires Deno v2.x. 2. Copy `.env.example` to `.env.local` and fill in Supabase and OpenAI values. 3. Confirm the Supabase target: From ea4439b586702425fe00fb4c934a670d38c66910 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 09:03:46 +0000 Subject: [PATCH 28/46] chore: verify review thread status --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 6b646581a..483d14a77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "hasInstallScript": true, "dependencies": { - "@next/env": "16.2.7", + "@next/env": "16.2.9", "@supabase/supabase-js": "^2.107.0", "exceljs": "^4.4.0", "jszip": "^3.10.1", From 4b22bded386e6fe5933e92f7e610ce3931689987 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 09:03:59 +0000 Subject: [PATCH 29/46] chore: revert unintended lockfile update --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 483d14a77..6b646581a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "hasInstallScript": true, "dependencies": { - "@next/env": "16.2.9", + "@next/env": "16.2.7", "@supabase/supabase-js": "^2.107.0", "exceljs": "^4.4.0", "jszip": "^3.10.1", From 701d25a91cd248712646c2be503fe435475ef5d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 09:07:14 +0000 Subject: [PATCH 30/46] fix: align lockfile @next/env version with next --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 6b646581a..483d14a77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "hasInstallScript": true, "dependencies": { - "@next/env": "16.2.7", + "@next/env": "16.2.9", "@supabase/supabase-js": "^2.107.0", "exceljs": "^4.4.0", "jszip": "^3.10.1", From 0b1b1e35509e98ce3a3b404f71a3edc4e04ab6ac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 09:07:23 +0000 Subject: [PATCH 31/46] Remove unused DocumentBadge import --- src/components/DocumentViewer.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/DocumentViewer.tsx b/src/components/DocumentViewer.tsx index 3f398f458..b20149abf 100644 --- a/src/components/DocumentViewer.tsx +++ b/src/components/DocumentViewer.tsx @@ -38,7 +38,6 @@ import { documentDisplayTitle } from "@/components/DocumentOrganizationBadges"; import { DocumentActionAnchor, DocumentActionButton, - DocumentBadge, DocumentFileTile, DocumentMetaRow, documentFileKind, From 469cd4c2b67363139eb392f45378dba7cefefd7e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 09:09:50 +0000 Subject: [PATCH 32/46] fix: make edge function check resilient without local deno --- package.json | 2 +- scripts/check-edge-functions.mjs | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 scripts/check-edge-functions.mjs diff --git a/package.json b/package.json index 92b8ba29b..31c3624d3 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "verify:ui": "npm run check:runtime && npm run test:e2e:chromium", "verify:release": "npm run check:runtime && npm run lint && npm run typecheck && npm run test && npm run build && npm run test:e2e", "check:runtime": "tsx scripts/check-runtime.ts", - "check:edge:functions": "deno check --node-modules-dir=false supabase/functions/indexing-v3-agent/index.ts", + "check:edge:functions": "node scripts/check-edge-functions.mjs", "check:production-readiness": "tsx scripts/production-readiness.ts", "check:production-readiness:ci": "tsx scripts/production-readiness.ts --ci", "format": "prettier --write .", diff --git a/scripts/check-edge-functions.mjs b/scripts/check-edge-functions.mjs new file mode 100644 index 000000000..e41be33a8 --- /dev/null +++ b/scripts/check-edge-functions.mjs @@ -0,0 +1,26 @@ +import { spawnSync } from 'node:child_process' + +const args = ['check', '--node-modules-dir=false', 'supabase/functions/indexing-v3-agent/index.ts'] +const deno = spawnSync('deno', ['--version'], { stdio: 'ignore' }) +const hasDeno = deno.status === 0 + +if (!hasDeno) { + if (process.env.CI) { + console.error('[check:edge:functions] Deno v2.x is required in CI. Install Deno and rerun this check.') + process.exit(1) + } + + console.warn( + '[check:edge:functions] Deno v2.x is not installed locally; skipping edge function type check. Install Deno to run this check locally.' + ) + process.exit(0) +} + +const check = spawnSync('deno', args, { stdio: 'inherit' }) + +if (check.error) { + console.error(`[check:edge:functions] Failed to execute Deno: ${check.error.message}`) + process.exit(1) +} + +process.exit(check.status ?? 1) From 1316dfb63616cc7858de804ee9ee6563f6b3c3db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 09:17:57 +0000 Subject: [PATCH 33/46] fix: align dashboard copy timer with browser timeout type --- src/components/ClinicalDashboard.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx index 15417f996..84c6e7af7 100644 --- a/src/components/ClinicalDashboard.tsx +++ b/src/components/ClinicalDashboard.tsx @@ -817,11 +817,11 @@ function NaturalLanguageAnswer({ const [sourcePreviewOpen, setSourcePreviewOpen] = useState(false); const [copiedSourceQuote, setCopiedSourceQuote] = useState(false); const sourceCapsuleRef = useRef(null); - const copySourceQuoteTimerRef = useRef | null>(null); + const copySourceQuoteTimerRef = useRef(null); const usePreviewSheet = useMobilePreviewSheet(); useEffect(() => { return () => { - if (copySourceQuoteTimerRef.current !== null) clearTimeout(copySourceQuoteTimerRef.current); + if (copySourceQuoteTimerRef.current !== null) window.clearTimeout(copySourceQuoteTimerRef.current); }; }, []); const cleaned = primaryAnswerDisplayText(text); @@ -834,7 +834,7 @@ function NaturalLanguageAnswer({ try { await navigator.clipboard.writeText(quoteText); setCopiedSourceQuote(true); - if (copySourceQuoteTimerRef.current !== null) clearTimeout(copySourceQuoteTimerRef.current); + if (copySourceQuoteTimerRef.current !== null) window.clearTimeout(copySourceQuoteTimerRef.current); copySourceQuoteTimerRef.current = window.setTimeout(() => setCopiedSourceQuote(false), 1600); } catch { setCopiedSourceQuote(false); From 2bb81e407806f40a8a78fcc9981f54abb913f3c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 10:03:26 +0000 Subject: [PATCH 34/46] fix: resolve bad merge in switchToDocumentSearchMode (ui-smoke.spec.ts) --- tests/ui-smoke.spec.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index 9c6028b84..15225eb4b 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -52,7 +52,6 @@ async function fillVisibleQuestionInput(page: Page, value: string) { async function switchToDocumentSearchMode(page: Page) { const legacyDocumentsMode = page.getByRole("button", { name: "Switch to document search mode" }); - if (await legacyDocumentsMode.isVisible().catch(() => false)) { if (await isVisibleWithoutThrow(legacyDocumentsMode)) { await expect(legacyDocumentsMode).toBeEnabled(); await expect(async () => { @@ -62,20 +61,6 @@ async function switchToDocumentSearchMode(page: Page) { return; } - const appModeMenu = page.getByRole("button", { name: /Current app mode:/ }); - await expect(appModeMenu).toBeVisible(); - await expect(appModeMenu).toBeEnabled(); - - await expect(async () => { - await appModeMenu.click({ force: true }); - await expect(appModeMenu).toHaveAttribute("aria-expanded", "true", { timeout: 2_000 }); - const documentsMode = page - .locator('[role="menuitemradio"]') - .filter({ hasText: /^Documents/ }) - .first(); - await expect(documentsMode).toBeVisible({ timeout: 3_000 }); - await documentsMode.click({ force: true }); - await expect(page.getByRole("button", { name: "Current app mode: Documents" })).toBeVisible({ timeout: 2_000 }); const appModeMenu = page.getByRole("button", { name: /^Current app mode:/ }); if (!(await isVisibleWithoutThrow(appModeMenu))) { throw new Error( From 1f2ccd565675ccef3b2d3c96224a1c5edd040733 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 18:17:23 +0800 Subject: [PATCH 35/46] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- scripts/check-edge-functions.mjs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/scripts/check-edge-functions.mjs b/scripts/check-edge-functions.mjs index e41be33a8..ce673b076 100644 --- a/scripts/check-edge-functions.mjs +++ b/scripts/check-edge-functions.mjs @@ -1,26 +1,26 @@ -import { spawnSync } from 'node:child_process' +import { spawnSync } from "node:child_process"; -const args = ['check', '--node-modules-dir=false', 'supabase/functions/indexing-v3-agent/index.ts'] -const deno = spawnSync('deno', ['--version'], { stdio: 'ignore' }) -const hasDeno = deno.status === 0 +const args = ["check", "--node-modules-dir=false", "supabase/functions/indexing-v3-agent/index.ts"]; +const deno = spawnSync("deno", ["--version"], { stdio: "ignore" }); +const hasDeno = deno.status === 0; if (!hasDeno) { if (process.env.CI) { - console.error('[check:edge:functions] Deno v2.x is required in CI. Install Deno and rerun this check.') - process.exit(1) + console.error("[check:edge:functions] Deno v2.x is required in CI. Install Deno and rerun this check."); + process.exit(1); } console.warn( - '[check:edge:functions] Deno v2.x is not installed locally; skipping edge function type check. Install Deno to run this check locally.' - ) - process.exit(0) + "[check:edge:functions] Deno v2.x is not installed locally; skipping edge function type check. Install Deno to run this check locally.", + ); + process.exit(0); } -const check = spawnSync('deno', args, { stdio: 'inherit' }) +const check = spawnSync("deno", args, { stdio: "inherit" }); if (check.error) { - console.error(`[check:edge:functions] Failed to execute Deno: ${check.error.message}`) - process.exit(1) + console.error(`[check:edge:functions] Failed to execute Deno: ${check.error.message}`); + process.exit(1); } -process.exit(check.status ?? 1) +process.exit(check.status ?? 1); From 5518554570e16cdb69cf8f4b7cf9c43ba3ef604d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 10:19:39 +0000 Subject: [PATCH 36/46] fix: restore dev dependency versions to correct patch releases --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 31c3624d3..c5623db49 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "uuid": "^11.1.1" }, "devDependencies": { - "@tailwindcss/postcss": "^4.3.0", + "@tailwindcss/postcss": "^4.3.1", "@types/node": "^24.13.2", "@types/pdf-parse": "^1.1.5", "@types/pdfkit": "^0.17.6", @@ -107,7 +107,7 @@ "@vitest/coverage-v8": "^4.1.9", "eslint": "^9.39.4", "eslint-config-next": "16.2.9", - "playwright": "^1.61.0", + "playwright": "^1.61.1", "prettier": "^3.8.4", "tailwindcss": "^4.3.1", "tsx": "^4.22.4", From f0b4fef1c1a9b2b0f31f59c7b74b57c31d612e04 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 10:20:59 +0000 Subject: [PATCH 37/46] fix: revert unintentional dependency downgrades flagged in review Restore @supabase/supabase-js to ^2.108.2, openai to ^6.45.0, and postgres to 3.4.9. These were accidentally downgraded from the versions present in main during the Node 24/npm 11 lockfile refresh. Regenerate package-lock.json with the correct resolved versions. --- package-lock.json | 264 ++++++++++++++++++++++++---------------------- package.json | 6 +- 2 files changed, 140 insertions(+), 130 deletions(-) diff --git a/package-lock.json b/package-lock.json index 483d14a77..42319c860 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,25 +10,25 @@ "hasInstallScript": true, "dependencies": { "@next/env": "16.2.9", - "@supabase/supabase-js": "^2.107.0", + "@supabase/supabase-js": "^2.108.2", "exceljs": "^4.4.0", "jszip": "^3.10.1", "lucide-react": "^1.21.0", "mammoth": "^1.12.0", "next": "16.2.9", - "openai": "^6.42.0", + "openai": "^6.45.0", "pdf-parse": "^2.4.5", "pdfjs-dist": "^6.0.227", "pdfkit": "^0.19.1", "postcss": "^8.5.15", - "postgres": "3.4.7", + "postgres": "3.4.9", "react": "19.2.7", "react-dom": "19.2.7", "tesseract.js": "^7.0.0", "zod": "^4.4.3" }, "devDependencies": { - "@tailwindcss/postcss": "^4.3.0", + "@tailwindcss/postcss": "^4.3.1", "@types/node": "^24.13.2", "@types/pdf-parse": "^1.1.5", "@types/pdfkit": "^0.17.6", @@ -37,7 +37,7 @@ "@vitest/coverage-v8": "^4.1.9", "eslint": "^9.39.4", "eslint-config-next": "16.2.9", - "playwright": "^1.61.0", + "playwright": "^1.61.1", "prettier": "^3.8.4", "tailwindcss": "^4.3.1", "tsx": "^4.22.4", @@ -2294,9 +2294,9 @@ "license": "MIT" }, "node_modules/@supabase/auth-js": { - "version": "2.107.0", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.107.0.tgz", - "integrity": "sha512-XA7x+WIeIvuC3GTZ2ey67QcBbGw4n+o5B7M+dMm9KT1lL3wX1B52DfEWW00WuPt/LnniJLLIn1WIm9YPtuxzKQ==", + "version": "2.108.2", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.108.2.tgz", + "integrity": "sha512-tNaQmBgodDZwgB40mRwVbxFy8IDYwjdpcZ0BYrWiwlULCSQoJj4QoG4zgJT7QRPXcqipefNOzvO/qAu4dF98ag==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -2306,9 +2306,9 @@ } }, "node_modules/@supabase/functions-js": { - "version": "2.107.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.107.0.tgz", - "integrity": "sha512-iMtRUmEj1KOgQd/a3MR4hnBlPnZc62DW8+z8aPpnzbxWkexEZUVL2fSgvvp15gqFg1V55e2yMGqgK+yhSQxp5w==", + "version": "2.108.2", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.108.2.tgz", + "integrity": "sha512-RNUX8EiBy3iLwAX19jtRzLyePnl11/fHcgwDHLnpKcDSXt/5qBnh3LUwAtIjT21Q66QsmNUR2esrHziLCpNubw==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -2318,15 +2318,15 @@ } }, "node_modules/@supabase/phoenix": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.2.tgz", - "integrity": "sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.4.tgz", + "integrity": "sha512-Gt0pqoXuIqX/8dvG0OKp/wMCobXNH3klNbUPBNyOfN0YA1IswrM3HyWFMOPk1Jy+BRaIyDPcFx4jLBwHNmlyfQ==", "license": "MIT" }, "node_modules/@supabase/postgrest-js": { - "version": "2.107.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.107.0.tgz", - "integrity": "sha512-7ARs47/tyIjX7T0Ive20d4NY8zQYXsP5/P07jJWxffSIM2gpnSnGRnL/Fe15GPbdjsW2sTYeckHcyaoKbM6yWQ==", + "version": "2.108.2", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.108.2.tgz", + "integrity": "sha512-GQ28/Y8hk3CFmkb3kXH1h/AQx6JIYSQfO0CJMRVBcEKZoNy6C45cXAZ4fcJvRC5Id0cs6xnkUV0+c0rIocigsw==", "license": "MIT", "dependencies": { "tslib": "2.8.1" @@ -2336,9 +2336,9 @@ } }, "node_modules/@supabase/realtime-js": { - "version": "2.107.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.107.0.tgz", - "integrity": "sha512-cF2KYdR3JIn9YlWGeluY9S0G+otqTdL6hB8GzpatlEIY6fZudCcyFo6Dc3+X9tjeb+x9XcIyNAk9qhNAknjH1A==", + "version": "2.108.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.108.2.tgz", + "integrity": "sha512-aAGxCSUemZvQIibnCdvNvgaKib28I4rfrNjKbQ9cG1uBLwUsI7hVpGXgEbypCCDhLjQlDTAiJlu7rgljYUT73g==", "license": "MIT", "dependencies": { "@supabase/phoenix": "^0.4.2", @@ -2349,9 +2349,9 @@ } }, "node_modules/@supabase/storage-js": { - "version": "2.107.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.107.0.tgz", - "integrity": "sha512-/X8OOVwKBn8aVKuHAGOz2yLA0d2OauqhVuy4mNtN+o7wttHOgx1/j+pqOzlsjmhOHrYykF6AJNZhs3gKZzcMUw==", + "version": "2.108.2", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.108.2.tgz", + "integrity": "sha512-TVZPQxXGxY2+A6yTtm77zUHsh70lBhYUEaJL8RQC+BghcX/ygiMG/rmXrNVBce30/WAeNPa8FiG8HbqlGeV05g==", "license": "MIT", "dependencies": { "iceberg-js": "^0.8.1", @@ -2362,16 +2362,16 @@ } }, "node_modules/@supabase/supabase-js": { - "version": "2.107.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.107.0.tgz", - "integrity": "sha512-ChKzdlWVweMUUhr0U79JhMmgm1haS/C5JquaiCDr70JaGARRtjjoY9rkIheXWybXxTSNzRiQs3Sk8IAg1HS3ZA==", + "version": "2.108.2", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.108.2.tgz", + "integrity": "sha512-hFhnPveb5JQg4a0QYicM0swT253YHMdfeRAl2BKHOlI5VAzuHxUGSr8RbwNLYNPauWOgQMS1H8sz8bvYlgwUfQ==", "license": "MIT", "dependencies": { - "@supabase/auth-js": "2.107.0", - "@supabase/functions-js": "2.107.0", - "@supabase/postgrest-js": "2.107.0", - "@supabase/realtime-js": "2.107.0", - "@supabase/storage-js": "2.107.0" + "@supabase/auth-js": "2.108.2", + "@supabase/functions-js": "2.108.2", + "@supabase/postgrest-js": "2.108.2", + "@supabase/realtime-js": "2.108.2", + "@supabase/storage-js": "2.108.2" }, "engines": { "node": ">=20.0.0" @@ -2387,56 +2387,49 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", - "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz", + "integrity": "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.21.0", - "jiti": "^2.6.1", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.3.0" + "tailwindcss": "4.3.1" } }, - "node_modules/@tailwindcss/node/node_modules/tailwindcss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", - "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@tailwindcss/oxide": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", - "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.1.tgz", + "integrity": "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==", "dev": true, "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.0", - "@tailwindcss/oxide-darwin-arm64": "4.3.0", - "@tailwindcss/oxide-darwin-x64": "4.3.0", - "@tailwindcss/oxide-freebsd-x64": "4.3.0", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", - "@tailwindcss/oxide-linux-x64-musl": "4.3.0", - "@tailwindcss/oxide-wasm32-wasi": "4.3.0", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + "@tailwindcss/oxide-android-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-x64": "4.3.1", + "@tailwindcss/oxide-freebsd-x64": "4.3.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-x64-musl": "4.3.1", + "@tailwindcss/oxide-wasm32-wasi": "4.3.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", - "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.1.tgz", + "integrity": "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==", "cpu": [ "arm64" ], @@ -2451,9 +2444,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", - "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.1.tgz", + "integrity": "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==", "cpu": [ "arm64" ], @@ -2468,9 +2461,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", - "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.1.tgz", + "integrity": "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==", "cpu": [ "x64" ], @@ -2485,9 +2478,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", - "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.1.tgz", + "integrity": "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==", "cpu": [ "x64" ], @@ -2502,9 +2495,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", - "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.1.tgz", + "integrity": "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==", "cpu": [ "arm" ], @@ -2519,13 +2512,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", - "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.1.tgz", + "integrity": "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2536,13 +2532,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", - "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.1.tgz", + "integrity": "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2553,13 +2552,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", - "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.1.tgz", + "integrity": "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2570,13 +2572,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", - "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.1.tgz", + "integrity": "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2587,9 +2592,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", - "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.1.tgz", + "integrity": "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -2609,7 +2614,7 @@ "@emnapi/runtime": "^1.10.0", "@emnapi/wasi-threads": "^1.2.1", "@napi-rs/wasm-runtime": "^1.1.4", - "@tybys/wasm-util": "^0.10.1", + "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "engines": { @@ -2666,7 +2671,7 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.1", + "version": "0.10.2", "dev": true, "inBundle": true, "license": "MIT", @@ -2683,9 +2688,9 @@ "optional": true }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", - "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.1.tgz", + "integrity": "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==", "cpu": [ "arm64" ], @@ -2700,9 +2705,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", - "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.1.tgz", + "integrity": "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==", "cpu": [ "x64" ], @@ -2717,26 +2722,19 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.0.tgz", - "integrity": "sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.1.tgz", + "integrity": "sha512-dNJuNbdEJT/SWRuXTYP1WSamelsz3ztkUsdtWQPjrexysrTpaEPM40P/71knXiXLYEojqPOEGitVLLpPMS5T6A==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.3.0", - "@tailwindcss/oxide": "4.3.0", - "postcss": "^8.5.10", - "tailwindcss": "4.3.0" + "@tailwindcss/node": "4.3.1", + "@tailwindcss/oxide": "4.3.1", + "postcss": "8.5.15", + "tailwindcss": "4.3.1" } }, - "node_modules/@tailwindcss/postcss/node_modules/tailwindcss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", - "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@tybys/wasm-util": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", @@ -4664,9 +4662,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.21.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.3.tgz", - "integrity": "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==", + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7630,15 +7628,27 @@ } }, "node_modules/openai": { - "version": "6.42.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.42.0.tgz", - "integrity": "sha512-1WFEt/uXMXOLhYRNkgJWo08Y2YNvNwpVU72K7ibrWgWpNOXd4VojXLbe6SQ4bLiUQ3Y8jz4IiyVkylJCL1DtZg==", + "version": "6.45.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.45.0.tgz", + "integrity": "sha512-5DQVNErssk0afNpTTHUm/qZPU4iKR9OYdNid8Ib4puq4gHNNvGWZht2zY4h9a8JMF949Ik6m8gQutllVPbjdnw==", "license": "Apache-2.0", "peerDependencies": { + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, "ws": { "optional": true }, @@ -8113,13 +8123,13 @@ } }, "node_modules/playwright": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.0.tgz", - "integrity": "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==", + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.61.0" + "playwright-core": "1.61.1" }, "bin": { "playwright": "cli.js" @@ -8132,9 +8142,9 @@ } }, "node_modules/playwright-core": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.0.tgz", - "integrity": "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==", + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -8206,9 +8216,9 @@ } }, "node_modules/postgres": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", - "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.9.tgz", + "integrity": "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==", "license": "Unlicense", "engines": { "node": ">=12" diff --git a/package.json b/package.json index c5623db49..fa4b7c60e 100644 --- a/package.json +++ b/package.json @@ -74,18 +74,18 @@ }, "dependencies": { "@next/env": "16.2.9", - "@supabase/supabase-js": "^2.107.0", + "@supabase/supabase-js": "^2.108.2", "exceljs": "^4.4.0", "jszip": "^3.10.1", "lucide-react": "^1.21.0", "mammoth": "^1.12.0", "next": "16.2.9", - "openai": "^6.42.0", + "openai": "^6.45.0", "pdf-parse": "^2.4.5", "pdfjs-dist": "^6.0.227", "pdfkit": "^0.19.1", "postcss": "^8.5.15", - "postgres": "3.4.7", + "postgres": "3.4.9", "react": "19.2.7", "react-dom": "19.2.7", "tesseract.js": "^7.0.0", From 383e74cafeccf2078f51a43ea035d530ca371fad Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 18:30:42 +0800 Subject: [PATCH 38/46] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- scripts/check-edge-functions.mjs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/check-edge-functions.mjs b/scripts/check-edge-functions.mjs index ce673b076..6adf6507f 100644 --- a/scripts/check-edge-functions.mjs +++ b/scripts/check-edge-functions.mjs @@ -1,17 +1,23 @@ import { spawnSync } from "node:child_process"; const args = ["check", "--node-modules-dir=false", "supabase/functions/indexing-v3-agent/index.ts"]; -const deno = spawnSync("deno", ["--version"], { stdio: "ignore" }); -const hasDeno = deno.status === 0; +const deno = spawnSync("deno", ["--version"], { encoding: "utf8" }); +const denoVersionLine = deno.stdout?.split("\n")[0]?.trim() ?? ""; +const denoVersionMatch = denoVersionLine.match(/^deno\s+(\d+)\./); +const denoMajor = denoVersionMatch ? Number(denoVersionMatch[1]) : null; +const hasDeno = deno.status === 0 && denoMajor !== null && denoMajor >= 2; if (!hasDeno) { + const found = deno.status === 0 ? (denoVersionLine || "unknown version") : "not installed"; if (process.env.CI) { - console.error("[check:edge:functions] Deno v2.x is required in CI. Install Deno and rerun this check."); + console.error( + `[check:edge:functions] Deno v2.x is required in CI (found: ${found}). Install Deno and rerun this check.`, + ); process.exit(1); } console.warn( - "[check:edge:functions] Deno v2.x is not installed locally; skipping edge function type check. Install Deno to run this check locally.", + `[check:edge:functions] Deno v2.x is required to run this check locally (found: ${found}); skipping edge function type check. Install Deno v2.x to run this check locally.`, ); process.exit(0); } From c0c02dd13b504cf0c855ee89674231a0f6b702f0 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 27 Jun 2026 18:30:54 +0800 Subject: [PATCH 39/46] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/components/clinical-dashboard/document-ui.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/clinical-dashboard/document-ui.tsx b/src/components/clinical-dashboard/document-ui.tsx index d469c0797..b66939f14 100644 --- a/src/components/clinical-dashboard/document-ui.tsx +++ b/src/components/clinical-dashboard/document-ui.tsx @@ -157,12 +157,12 @@ export function DocumentActionButton({ icon: Icon, className, ...props -}: ButtonHTMLAttributes & { +}: Omit, "type"> & { children: ReactNode; icon: LucideIcon; }) { return ( - From cb0c388925d729f5836b1f6422cf941b711f982a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Jun 2026 10:34:26 +0000 Subject: [PATCH 40/46] fix: replace menu/menuitemradio ARIA roles with simpler button list semantics --- src/components/clinical-dashboard/master-search-header.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx index 7eb75d405..e19a1bdd9 100644 --- a/src/components/clinical-dashboard/master-search-header.tsx +++ b/src/components/clinical-dashboard/master-search-header.tsx @@ -587,7 +587,7 @@ export function MasterSearchHeader({ type="button" onClick={() => setModeMenuOpen((open) => !open)} className="inline-grid h-11 min-w-[10rem] grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-2 rounded-full border border-[color:var(--border)] bg-[color:var(--surface)] px-2.5 text-left shadow-[var(--shadow-inset)] transition hover:border-[color:var(--border-strong)] hover:bg-[color:var(--surface-subtle)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] sm:min-w-[14rem]" - aria-haspopup="menu" + aria-haspopup="true" aria-expanded={modeMenuOpen} aria-label={`Current app mode: ${selectedAppMode.label}`} > @@ -612,7 +612,7 @@ export function MasterSearchHeader({ {modeMenuOpen ? (
@@ -623,8 +623,7 @@ export function MasterSearchHeader({