diff --git a/docs/process-hardening.md b/docs/process-hardening.md
index a26a65fb0..a4a678c7d 100644
--- a/docs/process-hardening.md
+++ b/docs/process-hardening.md
@@ -31,16 +31,16 @@ This document turns the current process review into phased, durable repo practic
- `src/app/page.tsx` now imports `ClinicalDashboard` from the module path (`@/components/clinical-dashboard`) while preserving
the legacy source declaration file for AST and merge-guard compatibility.
- **2026-07-03:** extracted `AuthPanel` (+ its solely-consumed auth-email snapshot helpers) into `clinical-dashboard/auth-panel.tsx`. Monolith 7924 → 7800 lines. Per-module gate established: `npm run typecheck` + `npx vitest run tests/clinical-dashboard-merge-artifacts.test.ts tests/rendered-text-formatting.test.ts` + a `data-testid`/`aria-label` sha1 checksum over `ClinicalDashboard.tsx` + `clinical-dashboard/*.tsx` (must be byte-identical before/after each move) + lint + prettier.
+- **2026-07-03:** extracted `answer-content.tsx` — `SourceImage`, `ScopeAndGovernanceNotice`, the answer/source formatters, `SourcePreviewContent`, `NaturalLanguageAnswer`, `UserQuestionBubble`, `KeyClinicalItems` (block 510–1249 against the post-drift monolith), moved verbatim (block diff empty; testid/aria checksum byte-identical). Two shared helpers went to clean homes instead of a monolith↔module cycle: `useMobilePreviewSheet` (+ its media-query snapshot helpers) → new `clinical-dashboard/use-mobile-preview-sheet.ts`, and `comparableAnswerText` → `clinical-dashboard/display-text.ts`. Retargeted the `NaturalLanguageAnswer` AST pin to scan `answer-content.tsx`, and widened `rendered-text-formatting.test.ts` negative scans to the monolith+module corpus (both strengthened, not weakened). Reusable finding: the answer/evidence families' helpers already live in `@/lib/*` and extracted sibling modules, so extractions re-import rather than needing wide monolith exports; only `comparableAnswerText`/`useMobilePreviewSheet` were monolith-internal.
+- **2026-07-03:** extracted `evidence-panels.tsx` — the clinical-detail/notes helper family + `AnswerSupportSummaryCard`, `ClinicalNotesChecklistPanel`, `SafetyFindingsPanel`, `EvidenceGapPanel`, `EvidenceCounts`, `AnswerSourceStatus`, `EvidenceSummaryCard`, `AnswerInsightBar`, `EvidenceVerificationStrip`, `AnswerFeedbackPanel`, `RenderModelSourceList`, `VerificationWorkspace`, `AnswerViewModeControl`, `EvidenceMapTable`, `AnswerSafetyNotice`, `QuoteCards` (contiguous block 484–2311, moved verbatim). Monolith 7909 → 6084 lines. **This module needs a monolith↔module cycle** — `ClinicalNotesChecklistPanel` renders ` ` (staying in the monolith as B3), so evidence-panels imports `ClinicalOutputPanel` + `clinicalQueryModeOptions` + `type AnswerFeedbackType` back from `@/components/ClinicalDashboard`. This is the SAME pattern already used by `global-mockup-search-shell.tsx` (imports `SettingsDialog` back); the repo has no `import/no-cycle` rule and the refs are render/runtime-time so init is safe (the `ui-smoke` build is the definitive cycle check). The monolith imports 26 symbols back (incl. the exported detail-helper family for output-panel). Robust dependency finding: the danger/reverse regex missed `const X: Type =` and `const X = {…}` forms (caught `clinicalQueryModeOptions` + `simpleClinicalTableProps` only via typecheck) — use a name-only regex `^(export )?(async )?(function|const|type) NAME` next time.
#### Remaining decomposition — hand-off (do on a stable `main`, one module per commit)
-The approved move map (`docs/redesign/04-deferred.md` §2) has 5 modules left. Unlike `auth-panel`, these are **interdependent** — they share a clinical-detail/notes helper family, so order matters and cross-module `export`s are required. Recommended order and the key dependency to resolve first:
+The approved move map (`docs/redesign/04-deferred.md` §2) has 3 modules left. Recommended order:
-1. `answer-content.tsx` — `SourceImage`, `ScopeAndGovernanceNotice`, `SourcePreviewContent`, `NaturalLanguageAnswer` (**AST-pinned** — retarget `tests/clinical-dashboard-merge-artifacts.test.ts` to scan this file for `NaturalLanguageAnswer`), `UserQuestionBubble`, `KeyClinicalItems` + answer formatters. Widen `tests/rendered-text-formatting.test.ts` to also scan this file.
-2. `evidence-panels.tsx` — the clinical-detail/notes helper family (`displayItemsForClinicalDetailSection`, `sortClinicalDetailSections`, `clinicalDetailSummaryItems`, and siblings) **plus** `ClinicalNotesChecklistPanel`, `SafetyFindingsPanel`, `EvidenceGapPanel`, `EvidenceCounts`, `AnswerSourceStatus`, `EvidenceSummaryCard`, `AnswerInsightBar`, `EvidenceVerificationStrip`, `AnswerFeedbackPanel`, `VerificationWorkspace`, `AnswerViewModeControl`, `EvidenceMapTable`, `AnswerSafetyNotice`, `QuoteCards`. **Export the helper family** so output-panel can import it. Must land before output-panel.
-3. `output-panel.tsx` — `ClinicalOutputPanel` (**AST-pinned** — retarget `dashboardPath` in `tests/clinical-dashboard-merge-artifacts.test.ts` to resolve declarations across the monolith + this file). Imports the detail helpers from `evidence-panels`.
-4. `visual-evidence.tsx` — `VisualEvidenceStrip`, `InlineTableCard`, `MobileEvidenceSheetContent`, `MobileEvidenceTabPanel`, `UnifiedEvidenceDrawerContent`.
-5. `document-results.tsx` — `WhyThisMatchedPanel`, `RelatedDocumentsPanel`, `StagedAnswerResultSurface`.
+1. `output-panel.tsx` — `ClinicalOutputPanel` (**AST-pinned** — retarget `dashboardPath` in `tests/clinical-dashboard-merge-artifacts.test.ts`; the test now scans a `scannedFiles` array — add this file to it). It already imports the detail helpers from `evidence-panels`; when it moves out of the monolith, update `evidence-panels`'s back-import of `ClinicalOutputPanel` to point at `output-panel.tsx` instead of `@/components/ClinicalDashboard` (and move `clinicalQueryModeOptions`/`AnswerFeedbackType` to a shared home or keep the back-import).
+2. `visual-evidence.tsx` — `VisualEvidenceStrip`, `InlineTableCard`, `MobileEvidenceSheetContent`, `MobileEvidenceTabPanel`, `UnifiedEvidenceDrawerContent` (+ `supportDotClass`/`supportLabel`/`EvidenceClaimsList`/`EvidenceGapsPanel` helpers, currently just after `ClinicalOutputPanel`).
+3. `document-results.tsx` — `WhyThisMatchedPanel`, `RelatedDocumentsPanel`, `StagedAnswerResultSurface`.
For each: trace which module-scope helpers/icons/types it uses; move solely-consumed ones with it, import shared ones; strip newly-orphaned monolith imports (lint flags them); run the per-module gate above; commit immediately. Keep the main `ClinicalDashboard` export in `ClinicalDashboard.tsx` (the barrel/bridge stays). Admin surfaces (`DocumentDrawer`, `SettingsDialog`, `ToolsHub`, `MobileSectionFab`) are out of the approved map — a later pass.
diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx
index f5722ea76..e2f0295a4 100644
--- a/src/components/ClinicalDashboard.tsx
+++ b/src/components/ClinicalDashboard.tsx
@@ -1,12 +1,9 @@
"use client";
-/* eslint-disable @next/next/no-img-element */
-
import Link from "next/link";
import { useRouter } from "next/navigation";
import dynamic from "next/dynamic";
import {
- Activity,
AlertCircle,
Bell,
BookOpen,
@@ -18,7 +15,6 @@ import {
ExternalLink,
FileImage,
FileText,
- Filter,
Globe2,
HelpCircle,
Heart,
@@ -40,26 +36,14 @@ import {
SlidersHorizontal,
Sparkles,
Stethoscope,
- Table2,
Tag,
- Target,
UploadCloud,
UserRound,
WifiOff,
Wrench,
X,
} from "lucide-react";
-import {
- type CSSProperties,
- memo,
- type RefObject,
- useCallback,
- useEffect,
- useMemo,
- useRef,
- useState,
- useSyncExternalStore,
-} from "react";
+import { type CSSProperties, type RefObject, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { AccessibleTable } from "@/components/AccessibleTable";
import {
DocumentOrganizationBadges,
@@ -69,24 +53,18 @@ import {
import { DocumentTagCloud } from "@/components/DocumentTagCloud";
import { DocumentManagementActions, type DocumentDeleteResult } from "@/components/DocumentManagementActions";
import { useDismissableLayer } from "@/components/use-dismissable-layer";
-import { documentCitationHref, formatCompactCitationLabel, formatCitationLabel } from "@/lib/citations";
-import { extractSafetyFindings, formatSafetyFindingLabel } from "@/lib/clinical-safety";
-import { clearCachedSignedUrl, getCachedSignedUrl, setCachedSignedUrl } from "@/lib/signed-url-cache";
+import { formatCompactCitationLabel } from "@/lib/citations";
+import { extractSafetyFindings } from "@/lib/clinical-safety";
import { readLocalProjectIdentity, unsafeLocalProjectMessage } from "@/lib/local-project-identity";
import { isLocalNoAuthMode } from "@/lib/env";
-import { normalizeSourceMetadata, sourceStatusLabel, validationStatusLabel } from "@/lib/source-metadata";
import {
appBackdrop,
answerSurface,
- chatActionRow,
- chatAnswerText,
chatMicroAction,
- codeText,
clinicalDivider,
clinicalNotesRow,
cn,
evidenceRow,
- evidenceSurface,
EmptyState,
fieldControlPlain,
fieldControlWithIcon,
@@ -96,15 +74,9 @@ import {
metadataPill,
panelSubtle,
primaryControl,
- proseMeasure,
- raisedCard,
SourceProvenance,
SourceStatusBadge,
sourceCard,
- sourceCapsule,
- statusDotMuted,
- statusDotReady,
- statusDotReview,
subtleStatusPill,
tableCard,
tableCardHeader,
@@ -119,7 +91,7 @@ import {
import { useAuthSession } from "@/lib/supabase/client";
import { SafeBoldText } from "@/components/SafeBoldText";
import { Sheet } from "@/components/ui/sheet";
-import { AnswerEmptyState, AnswerSkeleton, CopyButton } from "@/components/clinical-dashboard/answer-status";
+import { AnswerEmptyState, AnswerSkeleton } from "@/components/clinical-dashboard/answer-status";
import { AuthPanel } from "@/components/clinical-dashboard/auth-panel";
import { useSidebarCollapsed } from "@/components/clinical-dashboard/use-sidebar-collapsed";
import { useTheme } from "@/components/clinical-dashboard/use-theme";
@@ -149,10 +121,45 @@ import {
} from "@/components/clinical-dashboard/dashboard-shell";
import {
cleanDisplayTitle,
- compactSourceSnippet,
sanitizeAnswerDisplayText,
sanitizeDisplayText,
} from "@/components/clinical-dashboard/display-text";
+import {
+ NaturalLanguageAnswer,
+ plainAnswerText,
+ ScopeAndGovernanceNotice,
+ SourceImage,
+ UserQuestionBubble,
+} from "@/components/clinical-dashboard/answer-content";
+import {
+ AnswerFeedbackPanel,
+ AnswerSafetyNotice,
+ AnswerSupportSummaryCard,
+ AnswerViewModeControl,
+ answerHasCentralTable,
+ answerSupportPriority,
+ ClinicalNotesChecklistPanel,
+ clinicalDetailContentCount,
+ clinicalDetailMeta,
+ clinicalDetailSummaryItems,
+ clinicalNotesCount,
+ clinicalNotesDisplayCountForAnswer,
+ compactEvidenceSummary,
+ displayItemsForClinicalDetailSection,
+ EvidenceMapTable,
+ type EvidenceTabName,
+ simpleClinicalTableProps,
+ evidenceMapRowsFromRenderModel,
+ evidenceTabCount,
+ evidenceTabOrder,
+ formatQuoteCardsForClipboard,
+ primaryVisualTable,
+ QuoteCards,
+ SafetyFindingsPanel,
+ sortClinicalDetailSections,
+ VerificationWorkspace,
+} from "@/components/clinical-dashboard/evidence-panels";
+import { useMobilePreviewSheet } from "@/components/clinical-dashboard/use-mobile-preview-sheet";
import { MasterSearchHeader } from "@/components/clinical-dashboard/master-search-header";
import { emptyStates, errorCopy } from "@/lib/ui-copy";
import { applicationsLauncherItemCount } from "@/components/applications-launcher-page";
@@ -181,13 +188,7 @@ import {
MatchExplanationChips,
type SearchFacets,
} from "@/components/clinical-dashboard/document-search-results";
-import {
- hasStrongRelevanceIcon,
- isWeakRelevance,
- QueryCoverageChips,
- relevanceChipLabel,
- RelevanceBadge,
-} from "@/components/clinical-dashboard/relevance";
+import { isWeakRelevance, QueryCoverageChips, RelevanceBadge } from "@/components/clinical-dashboard/relevance";
import {
answerPayloadIsUsable,
isRetryableError,
@@ -216,14 +217,8 @@ import {
} from "@/lib/app-modes";
import { searchFormRecords } from "@/lib/forms";
import { searchServiceRecords } from "@/lib/services";
-import { buildAnswerRenderModel, type AnswerRenderModel, type SourceLink } from "@/lib/answer-render-policy";
-import { SourceActionRow, sourceResultHref } from "@/components/clinical-dashboard/source-actions";
-import {
- clinicalProseUsefulness,
- normalizeExtractedGlyphs,
- sourceTextForCompactDisplay,
- sourceTextForVerbatimQuote,
-} from "@/lib/source-text-sanitizer";
+import { buildAnswerRenderModel, type AnswerRenderModel } from "@/lib/answer-render-policy";
+import { sourceTextForCompactDisplay } from "@/lib/source-text-sanitizer";
import {
frontendSourceGovernanceWarnings,
groupSourceGovernanceWarnings,
@@ -252,7 +247,6 @@ import type {
QuoteCard,
RagAnswer,
AnswerSection,
- AnswerSectionKind,
ConflictOrGap,
RelatedDocument,
EvidenceSummary,
@@ -276,23 +270,6 @@ import {
const navigationHashes = ["#search", "#quotes", "#images", "#sources"] as const;
const mobileSectionFabMediaQuery = "(max-width: 768px), ((max-width: 1023px) and (hover: none) and (pointer: coarse))";
-const sourcePreviewSheetMediaQuery = "(max-width: 1023px)";
-
-function subscribeToMobilePreviewMedia(callback: () => void) {
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return () => undefined;
- const media = window.matchMedia(sourcePreviewSheetMediaQuery);
- media.addEventListener("change", callback);
- return () => media.removeEventListener("change", callback);
-}
-
-function getMobilePreviewSnapshot() {
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
- return window.matchMedia(sourcePreviewSheetMediaQuery).matches;
-}
-
-function useMobilePreviewSheet() {
- return useSyncExternalStore(subscribeToMobilePreviewMedia, getMobilePreviewSnapshot, () => false);
-}
export const recentQueryStorageKey = "clinical-kb-recent-queries";
const documentPageSize = 150;
@@ -346,7 +323,7 @@ type BatchesPayload = {
hasActiveBatches?: boolean;
pollAfterMs?: number | null;
};
-type AnswerFeedbackType =
+export type AnswerFeedbackType =
| "verified"
| "needs_correction"
| "source_insufficient"
@@ -360,7 +337,7 @@ type IngestionQualityPayload = {
demoMode?: boolean;
};
-const clinicalQueryModeOptions: Array<{ value: ClinicalQueryMode; label: string }> = [
+export const clinicalQueryModeOptions: Array<{ value: ClinicalQueryMode; label: string }> = [
{ value: "auto", label: "Auto" },
{ value: "monitoring_schedule", label: "Monitoring" },
{ value: "dose_threshold_lookup", label: "Dose / thresholds" },
@@ -507,2585 +484,7 @@ function normalizeNavigationHash(hash: string) {
return navigationHashes.includes(hash as (typeof navigationHashes)[number]) ? hash : "#search";
}
-const SourceImage = memo(function SourceImage({
- endpoint,
- caption,
- className = "max-h-52",
-}: {
- endpoint: string;
- caption: string;
- className?: string;
-}) {
- const [url, setUrl] = useState(() => getCachedSignedUrl(endpoint)?.url ?? null);
- const [failed, setFailed] = useState(false);
- const [attempt, setAttempt] = useState(0);
- const { authorizationHeader, markSessionExpired } = useAuthSession();
-
- useEffect(() => {
- const cached = getCachedSignedUrl(endpoint);
- if (cached) return () => undefined;
-
- let active = true;
- fetch(endpoint, { headers: authorizationHeader })
- .then((response) => {
- if (response.status === 401) markSessionExpired();
- return response.ok ? response.json() : null;
- })
- .then((data) => {
- if (active && data?.url) {
- setCachedSignedUrl(endpoint, data);
- setUrl(data.url);
- setFailed(false);
- } else if (active) {
- setFailed(true);
- }
- })
- .catch(() => {
- if (active) setFailed(true);
- });
- return () => {
- active = false;
- };
- }, [attempt, authorizationHeader, endpoint, markSessionExpired]);
-
- function retryImage() {
- clearCachedSignedUrl(endpoint);
- setUrl(null);
- setFailed(false);
- setAttempt((current) => current + 1);
- }
-
- function handleImageError() {
- clearCachedSignedUrl(endpoint);
- setFailed(true);
- }
-
- if (failed) {
- return (
-
-
-
- Image preview could not load.
-
- Retry image
-
-
-
- );
- }
-
- if (!url) {
- return (
-
-
- Loading image
-
- );
- }
-
- return (
-
- );
-});
-
-function ScopeAndGovernanceNotice({
- scope,
- warnings,
-}: {
- scope: SearchScopeSummary | null;
- warnings: SourceGovernanceWarning[];
-}) {
- const groupedWarnings = groupSourceGovernanceWarnings(frontendSourceGovernanceWarnings(warnings)).slice(0, 4);
- const showScope =
- Boolean(scope && scope.activeFilterCount > 0) ||
- Boolean(scope?.warnings?.length) ||
- scope?.matchedDocumentCount === 0;
- if (!showScope && groupedWarnings.length === 0) return null;
- return (
-
- {showScope && scope ? (
-
- Scope: {scope.summary}
- {scope.queryMode && scope.queryMode !== "auto" ? ` · ${scope.queryMode.replaceAll("_", " ")}` : ""}
-
- ) : null}
- {scope?.warnings?.length ? (
-
- {scope.warnings.slice(0, 3).map((warning) => (
- {warning}
- ))}
-
- ) : null}
- {groupedWarnings.length ? (
-
- {groupedWarnings.map((warning) => (
-
- {warning.message}
- {warning.titles.length ? (
-
- Sources affected
- {warning.titles.slice(0, 5).join(", ")}
-
- ) : null}
-
- ))}
-
- ) : null}
-
- );
-}
-
-function plainAnswerText(value: string) {
- const useful = clinicalProseUsefulness(value);
- return sanitizeAnswerDisplayText(useful.text || value, { minLength: 8, minTokens: 2 })
- .replace(/(?:\s*\n\s*)?Synthetic demo only:.*$/i, "")
- .trim();
-}
-
-function primaryAnswerDisplayText(value: string) {
- const cleaned = plainAnswerText(value);
- const fragments = cleaned
- .split(/\r?\n+/)
- .flatMap((line: string) =>
- line.split(/(?<=[.!?])\s+(?=(?:[A-Z]|\*\*|If\b|When\b|Do\b|Use\b|Monitor\b|Escalate\b|Document\b))/),
- )
- .map((fragment: string) =>
- fragment
- .replace(/^(?:[-*•]|\d+[.)])\s+/, "")
- .replace(
- /^(?:\*\*)?(?:answer|summary|bottom line|direct answer|clinical point|key point|required actions?|monitoring(?:\/timing)?|thresholds?|dose detail|medication(?:\/dose details?)?|escalation(?:\/risk)?|risk|safety|documentation(?:\/forms)?|source gaps?)(?:\*\*)?:\s+/i,
- "",
- )
- .trim(),
- )
- .map((fragment: string) => clinicalProseUsefulness(fragment).text || fragment)
- .filter((fragment: string) => {
- if (!fragment) return false;
- const useful = clinicalProseUsefulness(fragment);
- return useful.useful || fragment.split(/\s+/).length >= 8;
- });
- const uniqueFragments = Array.from(new Set(fragments));
- const selected = uniqueFragments.slice(0, 3).join(" ");
- const words = selected.split(/\s+/).filter(Boolean);
- if (words.length <= 85) return selected || cleaned;
- return `${words
- .slice(0, 85)
- .join(" ")
- .replace(/[;,:-]\s*$/, "")}...`;
-}
-
-function sourceCapsuleText({
- sourceCount,
- weakEvidence,
- grounded,
-}: {
- sourceCount: number;
- weakEvidence: boolean;
- grounded: boolean;
-}) {
- if (sourceCount <= 0) return "No direct source found";
- if (!grounded) return "Review nearby sources";
- if (weakEvidence) return "Review sources";
- return `${sourceCount} source${sourceCount === 1 ? "" : "s"}`;
-}
-
-function sourceStatusDotClass(metadata: ReturnType | null | undefined) {
- if (!metadata) return statusDotMuted;
- if (metadata.document_status === "current") return statusDotReady;
- if (metadata.document_status === "review_due" || metadata.document_status === "outdated") return statusDotReview;
- return statusDotMuted;
-}
-
-type CapsulePreviewSource = {
- id: string;
- title: string;
- pageNumber: number | null;
- metadata: ReturnType;
- score: number;
- href: string;
- snippet?: string;
- sourceStrength?:
- SourceLink["sourceStrength"] | BestSourceRecommendation["source_strength"] | SearchResult["source_strength"];
-};
-
-function sourceBadgeLabel(index: number) {
- return `S${index + 1}`;
-}
-
-function sourceBadgeToneClass(metadata: ReturnType, index: number) {
- if (metadata.document_status === "review_due" || metadata.document_status === "outdated") {
- return "border-[color:var(--warning-border)] bg-[color:var(--warning-soft)] text-[color:var(--warning)]";
- }
- if (index === 0) {
- return "border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent)] text-[color:var(--clinical-accent-contrast)]";
- }
- return "border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)]";
-}
-
-function sourceSupportLabel(source: CapsulePreviewSource, index: number) {
- if (!source.sourceStrength || source.sourceStrength === "none") return "Unsupported";
- if (source.sourceStrength === "limited") return "Partial";
- if (source.sourceStrength === "moderate") return "Partial";
- if (index === 0 || source.sourceStrength === "strong") return "Direct";
- return "Partial";
-}
-
-function sourceStatusShortLabel(metadata: ReturnType) {
- if (metadata.document_status === "review_due") return "Review due";
- if (metadata.document_status === "outdated") return "Outdated";
- if (metadata.document_status === "current") return "Current";
- return sourceStatusLabel(metadata);
-}
-
-function sourcePreviewPageCountLabel(previewSources: CapsulePreviewSource[]) {
- const uniquePages = new Set(previewSources.map((source) => source.pageNumber).filter((page) => page !== null));
- const count = uniquePages.size || previewSources.length;
- return `${count} page${count === 1 ? "" : "s"}`;
-}
-
-function capsulePreviewSources(
- bestSource: BestSourceRecommendation | null,
- sources: SearchResult[],
- sourceLinks: SourceLink[] = [],
-) {
- const rows: CapsulePreviewSource[] = [];
- const seen = new Set();
- const pushRow = (row: CapsulePreviewSource) => {
- const key = `${row.id}:${row.title}:${row.pageNumber ?? "n/a"}`;
- if (seen.has(key)) return;
- seen.add(key);
- rows.push(row);
- };
-
- sourceLinks.slice(0, 5).forEach((source) => {
- pushRow({
- id: source.chunk_id,
- title: source.title || source.file_name || "Source",
- pageNumber: source.page_number,
- metadata: normalizeSourceMetadata(source.sourceMetadata),
- score: source.score ?? 0,
- href: source.href,
- snippet: source.snippet,
- sourceStrength: source.sourceStrength,
- });
- });
-
- if (bestSource) {
- pushRow({
- id: bestSource.chunk_id,
- title: bestSource.title || bestSource.file_name || "Source",
- pageNumber: bestSource.page_number,
- metadata: normalizeSourceMetadata(bestSource.source_metadata),
- score: bestSource.score,
- href: bestSource.viewer_href,
- sourceStrength: bestSource.source_strength,
- });
- }
-
- sources.slice(0, 5).forEach((source) => {
- pushRow({
- id: source.id,
- title: source.title || source.file_name || "Source",
- pageNumber: source.page_number,
- metadata: normalizeSourceMetadata(source.source_metadata),
- score: source.hybrid_score ?? source.similarity ?? source.lexical_score ?? 0,
- href: sourceResultHref(source),
- sourceStrength: source.source_strength,
- });
- });
-
- return rows.slice(0, 4);
-}
-
-function SourcePreviewContent({
- previewSources,
- quoteText,
- copiedQuote,
- onCopyQuote,
- showHeader = true,
-}: {
- previewSources: CapsulePreviewSource[];
- quoteText?: string | null;
- copiedQuote: boolean;
- onCopyQuote: () => void;
- showHeader?: boolean;
-}) {
- const primaryPreviewSource = previewSources[0] ?? null;
- const reviewDueSource = previewSources.find(
- (source) => source.metadata.document_status === "review_due" || source.metadata.document_status === "outdated",
- );
-
- return (
- <>
- {showHeader ? (
-
-
-
-
Sources
-
- {sourcePreviewPageCountLabel(previewSources)}
-
-
-
Open the original PDF page.
-
-
- ) : null}
-
- {previewSources.map((source, index) => (
-
- {index === 0 ? (
-
-
- Best match
-
- ) : index === 1 ? (
-
Also used
- ) : null}
-
-
- {sourceBadgeLabel(index)}
-
-
-
- {cleanDisplayTitle(source.title)}
-
-
- p. {source.pageNumber ?? "n/a"}
- ·
- {sourceSupportLabel(source, index)}
-
-
- {sourceStatusShortLabel(source.metadata)}
-
-
-
-
-
- {index === 0 ? Open : null}
-
-
-
- ))}
-
- {quoteText ? (
-
- “{quoteText}”
-
- ) : null}
-
- {primaryPreviewSource ? (
-
-
- Open source page
-
- ) : null}
- {quoteText ? (
-
-
- {copiedQuote ? "Copied quote" : "Copy quote"}
-
- ) : null}
-
-
-
- {reviewDueSource ? : }
- {reviewDueSource
- ? `${sourceBadgeLabel(previewSources.indexOf(reviewDueSource))} review due`
- : "Sources current"}
-
- {primaryPreviewSource ? (
-
- Evidence details
-
-
- ) : null}
-
- >
- );
-}
-
-function NaturalLanguageAnswer({
- text,
- sourceCount,
- weakEvidence,
- grounded,
- sourceOnly,
- bestSource,
- sources,
- sourceLinks,
- copied,
- onCopy,
-}: {
- text: string;
- sourceCount: number;
- weakEvidence: boolean;
- grounded: boolean;
- sourceOnly: boolean;
- bestSource: BestSourceRecommendation | null;
- sources: SearchResult[];
- sourceLinks: SourceLink[];
- copied: boolean;
- onCopy: () => void;
-}) {
- const [sourcePreviewOpen, setSourcePreviewOpen] = useState(false);
- const [copiedSourceQuote, setCopiedSourceQuote] = useState(false);
- const sourceCapsuleRef = useRef(null);
- const copySourceQuoteTimerRef = useRef(null);
- const usePreviewSheet = useMobilePreviewSheet();
- useEffect(() => {
- return () => {
- if (copySourceQuoteTimerRef.current !== null) window.clearTimeout(copySourceQuoteTimerRef.current);
- };
- }, []);
- const cleaned = primaryAnswerDisplayText(text);
- if (!cleaned) return null;
- const capsuleText = sourceCapsuleText({ sourceCount, weakEvidence, grounded });
- const previewSources = capsulePreviewSources(bestSource, sources, sourceLinks);
- const quoteText = sourceLinks.find((source) => source.snippet)?.snippet || bestSource?.quote || bestSource?.snippet;
- const canOpenSourcePreview = previewSources.length > 0;
- async function copySourceQuote() {
- if (!quoteText) return;
- try {
- await navigator.clipboard.writeText(quoteText);
- setCopiedSourceQuote(true);
- if (copySourceQuoteTimerRef.current !== null) window.clearTimeout(copySourceQuoteTimerRef.current);
- copySourceQuoteTimerRef.current = window.setTimeout(() => setCopiedSourceQuote(false), 1600);
- } catch {
- setCopiedSourceQuote(false);
- }
- }
- const sourceCapsuleButton = (
- {
- if (canOpenSourcePreview) setSourcePreviewOpen((current) => !current);
- }}
- >
- {sourceCount > 0 ? (
- <>
-
- {sourceCount} source{sourceCount === 1 ? "" : "s"}
-
- {capsuleText}
- >
- ) : (
- capsuleText
- )}
- {canOpenSourcePreview ? : null}
-
- );
-
- return (
-
-
-
-
-
-
-
-
-
-
- {sourceOnly ? (
-
- Source-only answer — assembled from your documents without the AI model, so it may be less complete. Verify
- it against the cited passages below.
-
- ) : null}
- {sourceCapsuleButton}
- {sourcePreviewOpen && canOpenSourcePreview && !usePreviewSheet ? (
-
-
-
- ) : null}
-
setSourcePreviewOpen(false)}
- title="Sources"
- description="Open the original PDF page."
- titleAccessory={
-
- {sourcePreviewPageCountLabel(previewSources)}
-
- }
- closeLabel="Close answer sources"
- contentClassName="sm:max-w-xl"
- returnFocusRef={sourceCapsuleRef}
- portal
- >
-
-
-
-
-
-
-
- {copied ? "Copied with sources" : "Copy with sources"}
-
-
-
-
- );
-}
-
-function UserQuestionBubble({ query }: { query: string }) {
- const cleaned = query.trim();
- if (!cleaned) return null;
-
- return (
-
- );
-}
-
-type KeyClinicalItem = {
- id: string;
- label?: string;
- detail: string;
-};
-
-function keyClinicalItemFromText(item: string): KeyClinicalItem | null {
- const cleaned = item.replace(/^[-*•]\s*/, "").trim();
- if (cleaned.length < 24) return null;
- const [labelCandidate, ...detailParts] = cleaned.split(/\s+(?:—|-)\s+/);
- const label = labelCandidate?.trim();
- const detail = detailParts.join(" — ").trim();
- const id = comparableAnswerText(cleaned);
- if (label && detail && label.length <= 64) return { id, label, detail };
- return { id, detail: cleaned };
-}
-
-function keyClinicalItemsFromSections(
- sections: Array,
-): KeyClinicalItem[] {
- const usefulKinds = new Set([
- "required_actions",
- "monitoring_timing",
- "medication_dose",
- "thresholds",
- "escalation_risk",
- "contraindications_cautions",
- "comparison",
- ]);
- return sections
- .filter((section) => usefulKinds.has(section.kind))
- .flatMap((section) =>
- section.body
- .split(/\n+|(?<=\.)\s+(?=(?:Monitor|Check|Use|Avoid|Escalate|Withhold|Review|Document|Repeat|Consider)\b)/)
- .map((item) => keyClinicalItemFromText(item))
- .filter((item): item is KeyClinicalItem => Boolean(item)),
- )
- .filter((item, index, items) => items.findIndex((candidate) => candidate.id === item.id) === index)
- .slice(0, 5);
-}
-
-function keyClinicalItemsFromTable(item: VisualEvidenceCard | null): KeyClinicalItem[] {
- const rows = item?.tableRows?.filter((row) => row.some((cell) => cell.trim())) ?? [];
- if (rows.length < 2) return [];
-
- return rows
- .slice(0, 3)
- .map((row): KeyClinicalItem | null => {
- const [domain, baseline] = row.map((cell) => cell.trim()).filter(Boolean);
- if (!domain || !baseline) return null;
- const detail = baseline;
- return {
- id: comparableAnswerText([domain, detail].join(" ")),
- label: domain,
- detail,
- };
- })
- .filter((value): value is KeyClinicalItem => value !== null)
- .slice(0, 5);
-}
-
-function KeyClinicalItems({
- sections,
- table,
-}: {
- sections: Array;
- table: VisualEvidenceCard | null;
-}) {
- const sectionItems = keyClinicalItemsFromSections(sections);
- const tableItems = keyClinicalItemsFromTable(table);
- const items = sectionItems.length >= 2 ? sectionItems : tableItems;
- if (items.length < 2) return null;
-
- return (
-
- Key monitoring items
-
- {items.map((item) => (
-
- {item.label ? (
- <>
- {item.label}
- —
-
- >
- ) : (
-
- )}
-
- ))}
-
-
- );
-}
-
-type AnswerSupportPriority = {
- title: string;
- detail: string;
- sourceLabel?: string;
- tone: "priority" | "caution";
-};
-
-function answerSupportPriority(
- answer: RagAnswer,
- sections: Array,
- table: VisualEvidenceCard | null,
- safetyFindings: ReturnType,
- options: { grounded: boolean; weakEvidence: boolean },
-): AnswerSupportPriority | null {
- const firstSafetyFinding = safetyFindings[0];
- if (firstSafetyFinding) {
- return {
- title: "Priority",
- detail: formatSafetyFindingLabel(firstSafetyFinding),
- sourceLabel: "S1",
- tone: "caution",
- };
- }
-
- if (answer.answerQualityTier === "source_only" || !options.grounded || options.weakEvidence) {
- return {
- title: "Review source match",
- detail:
- "Verify cited passages before using clinical numbers, monitoring, dose, route, timing, or risk decisions.",
- sourceLabel: "Review",
- tone: "caution",
- };
- }
-
- const sectionItems = keyClinicalItemsFromSections(sections);
- const tableItems = keyClinicalItemsFromTable(table);
- const item = sectionItems[0] ?? tableItems[0] ?? null;
- if (!item) return null;
-
- return {
- title: item.label ?? "Priority",
- detail: item.detail,
- sourceLabel: "S1",
- tone: "priority",
- };
-}
-
-function AnswerSupportSummaryCard({
- priority,
- clinicalCount,
- evidenceSummary,
- clinicalAvailable,
- evidenceAvailable,
- clinicalTriggerRef,
- evidenceTriggerRef,
- onOpenClinicalNotes,
- onOpenEvidence,
-}: {
- priority: AnswerSupportPriority | null;
- clinicalCount: number;
- evidenceSummary: string;
- clinicalAvailable: boolean;
- evidenceAvailable: boolean;
- clinicalTriggerRef?: RefObject;
- evidenceTriggerRef?: RefObject;
- onOpenClinicalNotes: () => void;
- onOpenEvidence: () => void;
-}) {
- const supportRowCount = Number(clinicalAvailable) + Number(evidenceAvailable);
- const supportButtonClass =
- "grid min-h-[72px] grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-3 py-3 text-left transition hover:bg-[color:var(--surface-subtle)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[color:var(--focus)]";
-
- return (
-
- {priority ? (
-
-
- {priority.tone === "caution" ? : }
-
-
-
{priority.title}
-
{priority.detail}
-
- {priority.sourceLabel ? (
-
{priority.sourceLabel}
- ) : null}
-
- ) : null}
-
- {supportRowCount > 0 ? (
-
- {clinicalAvailable ? (
-
-
-
- Clinical notes
-
- {clinicalCount} note{clinicalCount === 1 ? "" : "s"}
-
-
-
-
- ) : null}
- {evidenceAvailable ? (
-
-
-
- Evidence
- {evidenceSummary}
-
-
-
- ) : null}
-
- ) : null}
-
- );
-}
-
-function comparableAnswerText(value: string) {
- return value
- .replace(/\*\*/g, "")
- .replace(/\.\.\.$/, "")
- .toLowerCase()
- .replace(/[^a-z0-9]+/g, " ")
- .trim();
-}
-
-function isRedundantStructuredItem(item: string, primaryAnswer: string) {
- const itemText = comparableAnswerText(item);
- const answerText = comparableAnswerText(primaryAnswer);
- if (!itemText || !answerText) return false;
- if (answerText.includes(itemText) || itemText.includes(answerText)) return true;
- if (itemText.length < 40) return false;
- const answerWords = new Set(answerText.split(" ").filter((word) => word.length > 3));
- const itemWords = itemText.split(" ").filter((word) => word.length > 3);
- if (itemWords.length < 6) return false;
- const sharedWords = itemWords.filter((word) => answerWords.has(word)).length;
- if (sharedWords / itemWords.length >= 0.82) return true;
- return answerText.includes(itemText.slice(0, Math.min(160, itemText.length)));
-}
-
-type ClinicalDetailSection = ReturnType[number];
-
-function displayItemsForClinicalDetailSection(
- section: ClinicalDetailSection,
- primaryAnswer: string,
- showLead: boolean,
-) {
- if (showLead) return section.items;
- const nonRedundantItems = section.items.filter((item) => !isRedundantStructuredItem(item, primaryAnswer));
- return nonRedundantItems.length > 0 || section.items.length === 0 ? nonRedundantItems : section.items;
-}
-
-const clinicalDetailPriority: Record = {
- action: 10,
- escalation: 20,
- thresholds: 30,
- cautions: 40,
- monitoring: 50,
- medication: 60,
- documentation: 70,
- comparison: 80,
- "support-map": 90,
- "source-gap": 100,
-};
-
-function clinicalDetailContentCount(section: ClinicalDetailSection) {
- if (section.items.length > 0) return section.items.length;
- const tableRows =
- section.tables?.reduce((total, table) => total + (table.rows?.length ?? (table.markdown ? 1 : 0)), 0) ?? 0;
- return tableRows || section.tables?.length || 0;
-}
-
-function sortClinicalDetailSections(sections: ClinicalDetailSection[]) {
- return [...sections].sort((left, right) => {
- const leftPriority = clinicalDetailPriority[left.id] ?? 75;
- const rightPriority = clinicalDetailPriority[right.id] ?? 75;
- if (leftPriority !== rightPriority) return leftPriority - rightPriority;
- return left.title.localeCompare(right.title);
- });
-}
-
-function clinicalDetailMeta(section: ClinicalDetailSection): {
- icon: typeof Search;
- eyebrow: string;
- toneClassName: string;
- accentClassName: string;
-} {
- if (section.id === "thresholds") {
- return {
- icon: Target,
- eyebrow: "Thresholds",
- toneClassName: toneWarning,
- accentClassName: "bg-[color:var(--warning)]",
- };
- }
- if (section.id === "escalation" || section.id === "cautions" || section.id === "source-gap") {
- return {
- icon: ShieldAlert,
- eyebrow: section.id === "source-gap" ? "Source gap" : "Risk",
- toneClassName: toneDanger,
- accentClassName: "bg-[color:var(--danger)]",
- };
- }
- if (section.id === "monitoring" || section.id === "medication") {
- return {
- icon: ClipboardCheck,
- eyebrow: section.id === "monitoring" ? "Monitoring" : "Medication",
- toneClassName: toneWarning,
- accentClassName: "bg-[color:var(--warning)]",
- };
- }
- if (section.id === "support-map" || section.id === "comparison") {
- return {
- icon: BookOpen,
- eyebrow: section.id === "support-map" ? "Evidence support" : "Comparison",
- toneClassName: toneInfo,
- accentClassName: "bg-[color:var(--info)]",
- };
- }
- if (section.id === "documentation") {
- return {
- icon: FileText,
- eyebrow: "Documentation",
- toneClassName: toneNeutral,
- accentClassName: "bg-[color:var(--border-strong)]",
- };
- }
- return {
- icon: ListChecks,
- eyebrow: "Clinical action",
- toneClassName: toneSuccess,
- accentClassName: "bg-[color:var(--success)]",
- };
-}
-
-function clinicalDetailSummaryItems(sections: ClinicalDetailSection[]) {
- const countById = (ids: string[]) =>
- sections
- .filter((section) => ids.includes(section.id))
- .reduce((total, section) => total + clinicalDetailContentCount(section), 0);
- const tableCount = sections.reduce((total, section) => total + (section.tables?.length ?? 0), 0);
- const items = [
- { label: "Actions", value: countById(["action", "escalation", "documentation"]) },
- { label: "Monitoring", value: countById(["monitoring", "medication"]) },
- { label: "Tables", value: tableCount },
- { label: "Cautions", value: countById(["cautions", "source-gap"]) },
- { label: "Evidence", value: countById(["support-map", "comparison"]) },
- ];
- return items.filter((item) => item.value > 0);
-}
-
-type ClinicalNotesTabId = "essentials" | "actions" | "safety";
-
-type ClinicalNotesRow = {
- id: string;
- title: string;
- detail: string;
- sourceIndex: number;
- tone: "safe" | "warn";
-};
-
-const clinicalNotesTabMeta: Record<
- ClinicalNotesTabId,
- { label: string; icon: typeof ShieldCheck; sectionIds: string[] }
-> = {
- essentials: {
- label: "Essentials",
- icon: ClipboardCheck,
- sectionIds: ["thresholds", "monitoring", "medication", "support-map", "comparison"],
- },
- actions: {
- label: "Actions",
- icon: Activity,
- sectionIds: ["action", "documentation", "monitoring", "medication"],
- },
- safety: {
- label: "Safety",
- icon: ShieldCheck,
- sectionIds: ["escalation", "cautions", "source-gap", "thresholds"],
- },
-};
-
-function compactClinicalNoteText(value: string) {
- return normalizeExtractedGlyphs(value)
- .replace(/\*\*/g, "")
- .replace(/\s*\[\d+(?:,\s*\d+)*\]\s*/g, " ")
- .replace(/\s+/g, " ")
- .trim();
-}
-
-function stripClinicalNoteLeadIn(value: string) {
- let text = compactClinicalNoteText(value);
- let previous = "";
- while (text !== previous) {
- previous = text;
- text = text
- .replace(/^(the\s+same\s+)?synthetic\s+source\s+says\s+/i, "")
- .replace(/^the\s+(indexed\s+)?source\s+says\s+/i, "")
- .replace(/^source\s+text\s+says\s+/i, "")
- .replace(/^according\s+to\s+[^,]+,\s*/i, "")
- .trim();
- }
- return text;
-}
-
-function titleCaseClinicalNote(value: string) {
- return value
- .replace(/\b\w[\w/-]*/g, (word) => {
- if (/[A-Z]{2,}|\/|\d/.test(word)) return word;
- return `${word.charAt(0).toUpperCase()}${word.slice(1).toLowerCase()}`;
- })
- .replace(/\bAnd\b/g, "and")
- .replace(/\bOr\b/g, "or")
- .replace(/\bTo\b/g, "to");
-}
-
-function sentenceCaseClinicalNoteDetail(value: string) {
- const text = stripClinicalNoteLeadIn(value);
- return text ? `${text.charAt(0).toUpperCase()}${text.slice(1)}` : text;
-}
-
-function clinicalNoteHeuristicTitle(value: string) {
- const text = stripClinicalNoteLeadIn(value);
- const lower = text.toLowerCase();
-
- if (/\bbaseline checklist\b/.test(lower) && /\bconfirm indication\b/.test(lower)) return "Indication";
- if (/\b(vomiting|diarrhoea|diarrhea|dehydration|acute kidney injury|tremor|confusion|ataxia)\b/.test(lower)) {
- return "Toxicity review triggers";
- }
- if (
- /\b(escalate|urgent review|urgent|red flag|seizures?|severe constipation|chest pain|dyspnoea|tachycardia)\b/.test(
- lower,
- )
- ) {
- return "Escalation triggers";
- }
- if (/\blithium levels?\b/.test(lower) && /\b(5\s*(?:to|-|–)\s*7|dose change|stable|days?)\b/.test(lower)) {
- return "Lithium level timing";
- }
- if (/\b(lithium level|serum lithium|trough level)\b/.test(lower)) return "Lithium level check";
- if (/\b(fbc|anc)\b/.test(lower)) return "FBC/ANC monitoring";
- if (/\bmyocarditis\b/.test(lower)) return "Myocarditis screening";
- if (/\b(metabolic|weight|lipids?|glucose|hba1c|waist)\b/.test(lower)) return "Metabolic monitoring";
- if (/\b(constipation|bowel)\b/.test(lower)) return "Constipation prevention";
- if (/\b(shared-care|shared care|communication|handover)\b/.test(lower)) return "Shared-care communication";
- if (/\b(renal|kidney|creatinine|egfr)\b/.test(lower)) return "Renal function";
- if (/\b(thyroid|tsh)\b/.test(lower)) return "Thyroid monitoring";
- if (/\bcalcium\b/.test(lower)) return "Calcium monitoring";
- if (/\b(nsaid|ace inhibitor|diuretic|interacting medicine|medicine reconciliation)\b/.test(lower)) {
- return "Interacting medicines";
- }
-
- return null;
-}
-
-function clinicalNoteTitleFromItem(item: string, section: ClinicalDetailSection, index: number) {
- const text = stripClinicalNoteLeadIn(item);
- const heuristicTitle = clinicalNoteHeuristicTitle(text);
- if (heuristicTitle) return heuristicTitle;
- const colonIndex = text.indexOf(":");
- if (colonIndex > 8 && colonIndex < 54) {
- const title = text.slice(0, colonIndex).trim();
- const detailStart = text
- .slice(colonIndex + 1)
- .split(/[,;]/)[0]
- ?.trim();
- if (/\b(checklist|checkpoint|points?)\b/i.test(title) && detailStart) {
- return clinicalNoteTitleFromFragment(detailStart);
- }
- return title;
- }
- const dashIndex = text.search(/\s[-–]\s/);
- if (dashIndex > 8 && dashIndex < 54) return text.slice(0, dashIndex).trim();
- if (section.items.length === 1 && section.title.length <= 42) return section.title;
- const words = text
- .replace(/^(confirm|check|review|record|document)\s+/i, "")
- .split(" ")
- .filter(Boolean);
- return words.slice(0, Math.min(words.length, index === 0 ? 5 : 4)).join(" ") || section.title;
-}
-
-function clinicalNoteDetailFromItem(item: string, title: string) {
- const text = stripClinicalNoteLeadIn(item);
- const normalizedTitle = title.toLowerCase();
- const lowerText = text.toLowerCase();
- const colonIndex = text.indexOf(":");
- if (colonIndex > 8 && colonIndex < 64) {
- const beforeColon = text.slice(0, colonIndex);
- const afterColon = text.slice(colonIndex + 1).trim();
- if (/\b(checklist|checkpoint|points?)\b/i.test(beforeColon) && afterColon) {
- return sentenceCaseClinicalNoteDetail(afterColon);
- }
- }
- if (lowerText.startsWith(`${normalizedTitle}:`)) {
- return sentenceCaseClinicalNoteDetail(text.slice(title.length + 1).trim());
- }
- if (lowerText.startsWith(`${normalizedTitle} -`) || lowerText.startsWith(`${normalizedTitle} –`)) {
- return sentenceCaseClinicalNoteDetail(text.slice(title.length + 2).trim());
- }
- if (text === title) return "Review linked source context before using this note.";
- return sentenceCaseClinicalNoteDetail(text);
-}
-
-function clinicalNoteTitleFromFragment(fragment: string) {
- const text = stripClinicalNoteLeadIn(fragment)
- .replace(/^(and|or)\s+/i, "")
- .replace(/^(confirm|check|review|record|document)\s+/i, "")
- .replace(/[.;:,]+$/g, "");
- if (!text) return "Clinical note";
- return clinicalNoteHeuristicTitle(text) ?? titleCaseClinicalNote(text);
-}
-
-function splitClinicalNoteFragments(item: string, section: ClinicalDetailSection, title: string) {
- const detail = clinicalNoteDetailFromItem(item, title);
- const titleLooksGeneric = /\b(checkpoint|checklist|item|point|monitoring|safety)\b/i.test(title);
- const itemLooksGeneric = /\b(checkpoint|checklist|item|point|monitoring|safety)\b/i.test(
- stripClinicalNoteLeadIn(item),
- );
- if (!titleLooksGeneric && !itemLooksGeneric && section.items.length > 1) return null;
-
- const fragments = detail
- .replace(/\band\s+/gi, "")
- .split(/[,;]\s+/)
- .map((fragment) => compactClinicalNoteText(fragment).replace(/[.;:,]+$/g, ""))
- .filter((fragment) => fragment.length > 5);
-
- return fragments.length >= 3 ? fragments.slice(0, 5) : null;
-}
-
-function clinicalNoteToneForText(text: string, fallback: ClinicalNotesRow["tone"]) {
- if (/\b(toxicity|toxic|warning|caution|urgent|red flag|adverse|confusion|ataxia|tremor)\b/i.test(text)) {
- return "warn";
- }
- return fallback;
-}
-
-function clinicalNoteHasDistinctDetail(row: ClinicalNotesRow) {
- const title = compactClinicalNoteText(row.title).toLowerCase();
- const detail = compactClinicalNoteText(row.detail).toLowerCase();
- return Boolean(detail) && detail !== title;
-}
-
-function clinicalNoteDetailLabel(row: ClinicalNotesRow) {
- const text = `${row.title} ${row.detail}`.toLowerCase();
- if (/\b(timing|level|schedule|dose change|stable|days?)\b/.test(text)) return "Timing";
- if (/\b(escalation|escalate|urgent|toxicity|trigger|vomiting|confusion|ataxia|tremor)\b/.test(text)) {
- return "Escalate";
- }
- if (/\b(baseline|confirm|record|document|check|review)\b/.test(text)) return "Action";
- return "Note";
-}
-
-function ClinicalNoteDetailCard({ row }: { row: ClinicalNotesRow }) {
- const detail = sentenceCaseClinicalNoteDetail(row.detail);
- return (
-
-
- {clinicalNoteDetailLabel(row)}:
- {detail}
-
-
- );
-}
-
-function clinicalNotesTableEvidenceCount(answer: RagAnswer) {
- return (answer.visualEvidence ?? answer.smartPanel?.visualEvidence ?? []).filter(
- (item) => item.accessibleTableMarkdown || item.tableRows?.length,
- ).length;
-}
-
-function clinicalNotesRowsForTab(sections: ClinicalDetailSection[], tab: ClinicalNotesTabId) {
- const meta = clinicalNotesTabMeta[tab];
- const rows: ClinicalNotesRow[] = [];
- let sourceIndex = 1;
-
- for (const section of sections) {
- const sectionText = `${section.title} ${section.items.join(" ")}`.toLowerCase();
- const hasMonitoringText =
- (tab === "actions" || tab === "essentials") &&
- /\b(monitor|screen|level|fbc|anc|metabolic|renal|thyroid|function)\b/i.test(sectionText);
- const hasSafetyText =
- tab === "safety" &&
- /\b(toxicity|toxic|urgent|caution|contraindication|red flag|escalat|warning|review due)\b/i.test(sectionText);
- if (!meta.sectionIds.includes(section.id) && !hasMonitoringText) {
- if (!hasSafetyText) continue;
- }
- if (tab === "essentials" && section.id === "action" && rows.length >= 2) {
- continue;
- }
- const tone: ClinicalNotesRow["tone"] = section.id === "escalation" || section.id === "cautions" ? "warn" : "safe";
-
- for (const item of section.items.slice(0, 4)) {
- if (section.tables?.length && /\b(table|showing domains|table showing)\b/i.test(item)) continue;
- const title = clinicalNoteTitleFromItem(item, section, rows.length);
- const fragments = splitClinicalNoteFragments(item, section, title);
- if (fragments) {
- for (const fragment of fragments) {
- const fragmentTitle = clinicalNoteTitleFromFragment(fragment);
- rows.push({
- id: `${tab}:${section.id}:${rows.length}:${fragmentTitle}`,
- title: fragmentTitle,
- detail: fragment,
- sourceIndex: sourceIndex++,
- tone: clinicalNoteToneForText(fragment, tone),
- });
- }
- } else {
- rows.push({
- id: `${tab}:${section.id}:${rows.length}:${title}`,
- title,
- detail: clinicalNoteDetailFromItem(item, title),
- sourceIndex: sourceIndex++,
- tone: clinicalNoteToneForText(item, tone),
- });
- }
- }
- }
-
- return rows.slice(0, 6);
-}
-
-function clinicalNotesAvailableTabs(sections: ClinicalDetailSection[]) {
- return (Object.keys(clinicalNotesTabMeta) as ClinicalNotesTabId[])
- .map((id) => ({ id, ...clinicalNotesTabMeta[id], count: clinicalNotesRowsForTab(sections, id).length }))
- .filter((tab) => tab.count > 0);
-}
-
-function clinicalNotesDetailSectionsForAnswer(answer: RagAnswer, viewMode: AnswerViewMode) {
- const sections =
- viewMode === "high_yield" ? buildHighYieldClinicalOutputSections(answer) : buildClinicalOutputSections(answer);
- const primaryAnswer = plainAnswerText(answer.answer);
- return sortClinicalDetailSections(
- sections
- .filter((section) => section.id !== "verify-source" && section.id !== "bottom-line")
- .map((section) => ({
- ...section,
- items: displayItemsForClinicalDetailSection(section, primaryAnswer, false),
- }))
- .filter((section) => section.items.length > 0),
- );
-}
-
-function clinicalNotesDisplayCountForAnswer(answer: RagAnswer, viewMode: AnswerViewMode, fallback: number) {
- const tabs = clinicalNotesAvailableTabs(clinicalNotesDetailSectionsForAnswer(answer, viewMode));
- const largestTabCount = tabs.reduce((largest, tab) => Math.max(largest, tab.count), 0);
- return Math.max(1, largestTabCount || fallback);
-}
-
-function ClinicalNotesChecklistPanel({
- answer,
- viewMode,
- evidenceMapRows,
- bestSource,
- copied,
- onCopy,
- onOpenTables,
-}: {
- answer: RagAnswer;
- viewMode: AnswerViewMode;
- evidenceMapRows: AnswerEvidenceMapRow[];
- bestSource: BestSourceRecommendation | null;
- copied: boolean;
- onCopy: () => void;
- onOpenTables?: () => void;
-}) {
- const detailSections = clinicalNotesDetailSectionsForAnswer(answer, viewMode);
- const tabs = clinicalNotesAvailableTabs(detailSections);
- const defaultTab = tabs.find((tab) => tab.id === "actions")?.id ?? tabs[0]?.id ?? "actions";
- const [requestedTab, setRequestedTab] = useState(defaultTab);
- const activeTab = tabs.some((tab) => tab.id === requestedTab) ? requestedTab : defaultTab;
- const rows = clinicalNotesRowsForTab(detailSections, activeTab);
- const tableEvidenceCount = clinicalNotesTableEvidenceCount(answer);
- const [added, setAdded] = useState(false);
- const warningRows = clinicalNotesRowsForTab(detailSections, "safety");
- const warningCount = warningRows.filter((row) => row.tone === "warn").length || warningRows.length;
-
- if (!tabs.length || rows.length === 0) {
- return (
-
- );
- }
-
- const activeMeta = clinicalNotesTabMeta[activeTab];
-
- return (
-
-
-
- {tabs.map((tab) => {
- const selected = tab.id === activeTab;
- return (
- setRequestedTab(tab.id)}
- className={cn(
- "inline-flex min-h-11 min-w-0 items-center justify-center gap-1.5 rounded-md px-2 text-xs font-semibold leading-none transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]",
- selected
- ? "bg-[color:var(--clinical-accent)] text-[color:var(--clinical-accent-contrast)] shadow-[var(--shadow-tight)]"
- : "text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text)]",
- )}
- >
- {tab.label}
-
- {tab.count}
-
-
- );
- })}
-
-
-
-
-
- {activeMeta.label} ({rows.length})
-
- {tableEvidenceCount > 0 && onOpenTables ? (
-
-
- Tables
-
- ) : null}
-
-
-
- {rows.map((row) => {
- const hasDistinctDetail = clinicalNoteHasDistinctDetail(row);
- const RowIcon = row.tone === "warn" ? AlertCircle : activeTab === "actions" ? Activity : CheckCircle2;
- return (
-
-
-
-
-
-
-
- {row.title}
-
-
- {row.tone === "warn" ? "Review" : activeTab === "actions" ? "Action" : "Source"}
-
-
- {hasDistinctDetail ? (
-
{row.detail}
- ) : null}
-
-
-
- S{row.sourceIndex}
-
-
-
-
- );
- })}
-
-
- {warningCount > 0 && activeTab !== "safety" ? (
- setRequestedTab("safety")}
- className="mt-3 grid min-h-[58px] grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 rounded-lg border border-[color:var(--warning-border)] bg-[color:var(--warning-soft)]/45 px-3 py-2 text-left text-[color:var(--warning)] shadow-[var(--shadow-inset)] transition hover:bg-[color:var(--warning-soft)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]"
- >
-
-
- Safety preview ({warningCount})
- Review toxicity symptoms
-
- S1
-
- ) : null}
-
-
-
- {bestSource ? (
-
-
- Source
-
- ) : (
-
-
- Source
-
- )}
-
-
- {copied ? "Copied" : "Copy"}
-
-
setAdded(true)}
- className="inline-flex min-h-11 items-center justify-center gap-1.5 px-2 text-[11px] font-semibold text-[color:var(--primary)]"
- >
-
- {added ? "Added" : "Add"}
-
-
-
-
- );
-}
-
-function SafetyFindingsPanel({ findings }: { findings: ReturnType }) {
- if (findings.length === 0) return null;
-
- return (
-
-
-
- {findings.map((finding, index) => (
-
-
-
- {finding.label}
-
-
-
- Source
-
-
- {finding.text}
-
- {formatCitationLabel(finding.citation)}
-
-
- ))}
-
-
- );
-}
-
-function EvidenceGapPanel({
- relevance,
- sources,
- query,
-}: {
- relevance?: EvidenceRelevance | null;
- sources: SearchResult[];
- query: string;
-}) {
- if (!relevance || relevance.isSourceBacked) return null;
- const closestSources = sources.slice(0, 3);
- const found = relevance.matchedTerms.length
- ? relevance.matchedTerms.slice(0, 6).join(", ")
- : "Only weak neighboring passages were retrieved.";
- const missing = relevance.missingTerms.length
- ? relevance.missingTerms.slice(0, 6).join(", ")
- : "No direct indexed passage covered the full question.";
-
- return (
-
- }
- />
-
-
- What was found
- {found}
-
-
-
- What was not found
-
- {missing}
-
-
- Closest sources
- {closestSources.length ? (
-
- {closestSources.map((source) => (
-
-
- {cleanDisplayTitle(source.title)}
-
- ))}
-
- ) : (
- No nearby indexed sources were returned.
- )}
-
-
-
- Suggested next search/upload
-
-
- Try a narrower query using the missing terms, scope to a likely document, or upload/index the guideline that
- directly covers "{query.trim()}".
-
-
-
-
- );
-}
-
-function EvidenceCounts({
- answer,
- sourceSummary,
- sourceCount,
-}: {
- answer: RagAnswer;
- sourceSummary?: EvidenceSummary;
- sourceCount: number;
-}) {
- const counts = [
- {
- label: "Citations",
- value: answer.citations.length,
- },
- {
- label: "Quotes",
- value: answer.quoteCards?.length ?? sourceSummary?.quote_count ?? 0,
- },
- {
- label: "Images",
- value:
- answer.visualEvidence?.length ?? answer.smartPanel?.visualEvidence?.length ?? sourceSummary?.image_count ?? 0,
- },
- {
- label: "Passages",
- value: sourceCount || sourceSummary?.total_sources || 0,
- },
- ];
-
- return (
-
- {counts.map((item) => (
-
-
{item.value}
-
{item.label}
-
- ))}
-
- );
-}
-
-function AnswerSourceStatus({
- source,
- weakEvidence,
-}: {
- source: BestSourceRecommendation | null | undefined;
- weakEvidence: boolean;
-}) {
- const metadata = source?.source_metadata;
- return (
-
-
-
- {weakEvidence ? (
-
- Evidence support is limited. Treat this as a source-finding result until the linked passage is verified.
-
- ) : null}
-
- );
-}
-
-function EvidenceSummaryCard({
- answer,
- bestSource,
- grounded,
- relevance,
- sourceSummary,
- weakEvidence,
- sources,
- gaps,
- onScopeDocument,
- compact = false,
- supporting = false,
-}: {
- answer: RagAnswer;
- bestSource: BestSourceRecommendation | null;
- grounded: boolean;
- relevance?: EvidenceRelevance | null;
- sourceSummary?: EvidenceSummary;
- weakEvidence: boolean;
- sources: SearchResult[];
- gaps: ConflictOrGap[];
- onScopeDocument: (documentId: string) => void;
- compact?: boolean;
- supporting?: boolean;
-}) {
- const sourceLabel = relevance && !relevance.isSourceBacked ? "Closest source" : "Top source";
- const supportLabel = relevanceChipLabel(relevance, grounded);
- const sourceStrength = bestSource?.source_strength ?? sourceSummary?.source_strength ?? "none";
- const gapMessage =
- gaps[0]?.message ??
- (!relevance?.isSourceBacked && relevance?.supportReason
- ? relevance.supportReason
- : (sourceSummary?.summary ?? null));
-
- return (
-
-
-
-
Evidence review
-
- {weakEvidence
- ? "Verify before relying on this answer."
- : "Source status and retrieved evidence at a glance."}
-
-
-
-
-
-
-
-
-
- {bestSource ? (
-
-
-
-
- {sourceLabel}
-
-
- {bestSource.title}
-
-
- page {bestSource.page_number ?? "n/a"} · {sourceStrength} support
-
-
-
- {Math.round(Math.max(0, Math.min(1, bestSource.score)) * 100)}% match
-
-
-
- “{bestSource.snippet}”
-
-
-
- Open source
-
-
- onScopeDocument(bestSource.document_id)}
- className={cn(floatingControl, "min-h-[44px] px-3 text-xs")}
- aria-label={`Search only ${bestSource.title}`}
- >
-
- Add scope
-
-
-
- ) : (
-
- )}
-
- {gapMessage ? (
-
-
Coverage note
-
{gapMessage}
-
- ) : null}
-
-
-
- {supportLabel}. Verify source before copying, including any clinical draft text.
-
-
- );
-}
-
-function compactEvidenceSummary(
- answer: RagAnswer,
- sources: SearchResult[],
- sourceSummary?: EvidenceSummary,
- renderModel?: AnswerRenderModel,
-) {
- const support =
- renderModel?.trust === "high"
- ? "Strong support"
- : renderModel?.trust === "medium"
- ? "Supported"
- : renderModel?.trust === "low"
- ? "Limited support"
- : "Review support";
- const claimCount = renderModel?.evidenceRows.length || answer.answerSections?.length || answer.citations.length;
- const quoteCount = renderModel?.quoteCards.length ?? answer.quoteCards?.length ?? sourceSummary?.quote_count ?? 0;
- const tableCount = (renderModel?.visualEvidence ?? answer.visualEvidence ?? []).filter(
- (item) => item.accessibleTableMarkdown || item.tableRows?.length,
- ).length;
- const sourceCount = renderModel?.primarySources.length || sourceSummary?.total_sources || sources.length;
- const countParts = [
- claimCount > 0 ? `${claimCount} claim${claimCount === 1 ? "" : "s"}` : null,
- quoteCount > 0 ? `${quoteCount} quote${quoteCount === 1 ? "" : "s"}` : null,
- tableCount > 0 ? `${tableCount} table${tableCount === 1 ? "" : "s"}` : null,
- ].filter((part): part is string => Boolean(part));
-
- if (countParts.length === 0 && sourceCount > 0) {
- countParts.push(`${sourceCount} source${sourceCount === 1 ? "" : "s"}`);
- }
-
- return [support, ...countParts].join(" · ");
-}
-
-type EvidenceTabName = "Claims" | "Quotes" | "Tables" | "Images" | "Gaps";
-
-function renderModelAllows(renderModel: AnswerRenderModel, block: AnswerRenderModel["allowedBlocks"][number]) {
- return renderModel.allowedBlocks.includes(block);
-}
-
-function evidenceTabOrder(_answer: RagAnswer, renderModel: AnswerRenderModel): EvidenceTabName[] {
- const order: EvidenceTabName[] = ["Claims", "Quotes", "Tables", "Images", "Gaps"];
- return order.filter((tab) => {
- if (tab === "Tables") {
- return (
- renderModelAllows(renderModel, "visualEvidence") &&
- renderModel.visualEvidence.some((item) => item.accessibleTableMarkdown || item.tableRows?.length)
- );
- }
- if (tab === "Images") return renderModelAllows(renderModel, "visualEvidence");
- if (tab === "Quotes") return renderModelAllows(renderModel, "quoteCards");
- if (tab === "Gaps") return renderModel.warnings.length > 0;
- return renderModelAllows(renderModel, "evidenceMap") || renderModelAllows(renderModel, "reviewSources");
- });
-}
-
-function evidenceTabCount({
- tab,
- sources,
- visualEvidence,
- answerEvidenceMapRows,
- renderModel,
-}: {
- tab: EvidenceTabName;
- sources: SearchResult[];
- visualEvidence: VisualEvidenceCard[];
- answerEvidenceMapRows: AnswerEvidenceMapRow[];
- renderModel: AnswerRenderModel;
-}) {
- if (tab === "Tables") {
- return visualEvidence.filter((item) => item.accessibleTableMarkdown || item.tableRows?.length).length;
- }
- if (tab === "Claims")
- return (
- answerEvidenceMapRows.length ||
- renderModel.evidenceRows.length ||
- sources.length ||
- renderModel.primarySources.length
- );
- if (tab === "Images") return visualEvidence.length;
- if (tab === "Quotes") return renderModel.quoteCards.length;
- return renderModel.warnings.length;
-}
-
-function clinicalNotesCount(answer: RagAnswer) {
- return buildHighYieldClinicalOutputSections(answer).filter((section) =>
- ["action", "escalation", "thresholds", "cautions", "monitoring", "medication", "source-gap"].includes(section.id),
- ).length;
-}
-
-function answerHasCentralTable(answer: RagAnswer) {
- return (
- answer.queryClass === "table_threshold" ||
- answer.responseMode === "threshold_table" ||
- Boolean(answer.visualEvidence?.some((item) => item.accessibleTableMarkdown || item.tableRows?.length))
- );
-}
-
-function primaryVisualTable(answer: RagAnswer) {
- return answer.visualEvidence?.find((item) => item.accessibleTableMarkdown || item.tableRows?.length) ?? null;
-}
-
-type RenderModelPdfSource = {
- document_id: string;
- title: string;
- file_name: string;
- page_number: number | null;
- chunk_id: string | null;
-};
-
-function uniquePdfSourcesForRenderModel(renderModel: AnswerRenderModel): RenderModelPdfSource[] {
- return renderModel.primarySources.map((source) => ({
- document_id: source.document_id,
- title: source.title,
- file_name: source.file_name,
- page_number: source.page_number,
- chunk_id: source.chunk_id,
- }));
-}
-
-function queryModeLabel(mode: ClinicalQueryMode) {
- return clinicalQueryModeOptions.find((option) => option.value === mode)?.label ?? mode.replaceAll("_", " ");
-}
-
-function AnswerInsightBar({
- answer,
- bestSource,
- relevance,
- queryMode,
- sourceGovernanceWarnings,
-}: {
- answer: RagAnswer;
- bestSource: BestSourceRecommendation | null;
- relevance?: EvidenceRelevance | null;
- queryMode: ClinicalQueryMode;
- sourceGovernanceWarnings: SourceGovernanceWarning[];
-}) {
- const frontendGovernanceWarnings = frontendSourceGovernanceWarnings(sourceGovernanceWarnings);
- const metadata = normalizeSourceMetadata(
- bestSource?.source_metadata ?? answer.sources?.[0]?.source_metadata ?? answer.citations?.[0]?.source_metadata,
- );
- const modeLabel =
- answer.smartApiPlan?.displayMode?.replaceAll("_", " ") ??
- answer.responseMode?.replaceAll("_", " ") ??
- queryModeLabel(queryMode);
- const sourceCount = answer.evidenceSummary?.total_sources ?? answer.sources?.length ?? answer.citations.length;
- const support = relevanceChipLabel(relevance ?? answer.relevance, answer.grounded);
- const sourceStatus = frontendGovernanceWarnings.length
- ? `${frontendGovernanceWarnings.length} source status note${frontendGovernanceWarnings.length === 1 ? "" : "s"}`
- : sourceStatusLabel(metadata);
- const retrievalGate = answer.retrievalDiagnostics?.gateStatus;
- const items = [
- { label: "Mode", value: modeLabel, icon: SlidersHorizontal },
- {
- label: "Support",
- value: support,
- icon: hasStrongRelevanceIcon(relevance ?? answer.relevance, answer.grounded) ? CheckCircle2 : AlertCircle,
- },
- { label: "Sources", value: String(sourceCount), icon: FileText },
- { label: "Confidence", value: answer.confidence, icon: Target },
- {
- label: "Retrieval",
- value: retrievalGate ? `${retrievalGate} gate` : "Not logged",
- icon: retrievalGate === "blocked" ? ShieldAlert : CheckCircle2,
- },
- { label: "Status", value: `${sourceStatus} / ${validationStatusLabel(metadata)}`, icon: BookOpen },
- ];
-
- return (
-
- {items.map((item) => {
- const Icon = item.icon;
- return (
-
-
-
- {item.label}
-
- {item.value}
-
- );
- })}
-
- );
-}
-
-function EvidenceVerificationStrip({
- answer,
- bestSource,
- sourceSummary,
- weakEvidence,
- governanceWarningCount,
-}: {
- answer: RagAnswer;
- bestSource: BestSourceRecommendation | null;
- sourceSummary?: EvidenceSummary | null;
- weakEvidence: boolean;
- governanceWarningCount: number;
-}) {
- const metadata = normalizeSourceMetadata(
- bestSource?.source_metadata ?? answer.sources?.[0]?.source_metadata ?? answer.citations?.[0]?.source_metadata,
- );
- const sourceCount = sourceSummary?.total_sources ?? answer.sources?.length ?? answer.citations.length;
- const citationCount = answer.citations.length;
- const gapCount = answer.conflictsOrGaps?.length ?? answer.smartPanel?.conflictsOrGaps?.length ?? 0;
- const retrievalGateBlocked = answer.retrievalDiagnostics?.gateStatus === "blocked";
- const checks = [
- {
- label: "Citations",
- value: citationCount ? `${citationCount} citation${citationCount === 1 ? "" : "s"}` : "None",
- ready: citationCount > 0,
- },
- {
- label: "Sources",
- value: `${sourceCount} source${sourceCount === 1 ? "" : "s"}`,
- ready: sourceCount > 0,
- },
- {
- label: "Source status",
- value: sourceStatusLabel(metadata),
- ready: metadata.document_status === "current" && !governanceWarningCount,
- },
- {
- label: "Retrieval gate",
- value: retrievalGateBlocked ? "Blocked for low signal" : answer.retrievalDiagnostics ? "Passed" : "Not available",
- ready: !retrievalGateBlocked,
- },
- {
- label: "Gaps",
- value: governanceWarningCount
- ? `${governanceWarningCount} status note${governanceWarningCount === 1 ? "" : "s"}`
- : gapCount
- ? `${gapCount} gap${gapCount === 1 ? "" : "s"}`
- : "None",
- ready: !weakEvidence && !gapCount && !governanceWarningCount,
- },
- ];
-
- return (
-
-
- {checks.map((check) => (
-
-
- {check.ready ? (
-
- ) : (
-
- )}
-
- {check.label}
-
-
-
{check.value}
-
- ))}
-
-
-
- Pinned source
-
- {bestSource ? (
- <>
- {bestSource.title}
-
- >
- ) : (
- No pinned source yet
- )}
-
-
- );
-}
-
-const answerFeedbackOptions: Array<{
- type: AnswerFeedbackType;
- label: string;
- icon: typeof CheckCircle2;
- tone: "success" | "warning" | "danger" | "neutral";
-}> = [
- { type: "verified", label: "Verified", icon: CheckCircle2, tone: "success" },
- { type: "needs_correction", label: "Needs correction", icon: AlertCircle, tone: "warning" },
- { type: "source_insufficient", label: "Source insufficient", icon: ShieldAlert, tone: "warning" },
- { type: "wrong_source", label: "Wrong source", icon: FileText, tone: "danger" },
- { type: "missing_source", label: "Missing source", icon: Search, tone: "warning" },
- { type: "unsupported_answer", label: "Unsupported answer", icon: ShieldAlert, tone: "danger" },
- { type: "numeric_error", label: "Numeric error", icon: Target, tone: "danger" },
- { type: "outdated_guidance", label: "Outdated guidance", icon: RefreshCw, tone: "warning" },
-];
-
-function feedbackToneClass(tone: "success" | "warning" | "danger" | "neutral") {
- if (tone === "success") return toneSuccess;
- if (tone === "warning") return toneWarning;
- if (tone === "danger") return toneDanger;
- return toneNeutral;
-}
-
-function AnswerFeedbackPanel({
- pending,
- onSubmit,
-}: {
- pending: AnswerFeedbackType | null;
- onSubmit: (feedbackType: AnswerFeedbackType) => void;
-}) {
- return (
-
-
-
-
Answer review
-
- Capture misses for retrieval and RAG evals without changing the answer.
-
-
- {pending ? (
-
-
- Saving
-
- ) : null}
-
-
- {answerFeedbackOptions.map((item) => {
- const Icon = item.icon;
- return (
- onSubmit(item.type)}
- className={cn(
- "inline-flex min-h-[40px] items-center gap-1.5 rounded-lg border px-2.5 text-xs font-semibold transition disabled:cursor-not-allowed disabled:opacity-60",
- feedbackToneClass(item.tone),
- )}
- >
- {pending === item.type ? : }
- {item.label}
-
- );
- })}
-
-
- );
-}
-
-function RenderModelSourceList({
- sources,
- query,
- onScopeDocument,
-}: {
- sources: SourceLink[];
- query: string;
- onScopeDocument: (documentId: string) => void;
-}) {
- if (sources.length === 0) {
- return (
-
- );
- }
-
- return (
-
- {sources.map((source, index) => {
- const metadata = normalizeSourceMetadata(source.sourceMetadata);
- const snippet = compactSourceSnippet(source.snippet ?? "", { dropTitle: source.title });
- const openLabel = `Open source ${index + 1}: ${cleanDisplayTitle(source.title)}${query ? ` for ${query}` : ""}`;
- return (
-
-
-
-
-
-
- {cleanDisplayTitle(source.title)}
-
-
- p.{source.page_number ?? "n/a"} · {sourceStatusLabel(metadata)} · {source.sourceStrength} support
-
-
-
-
- {snippet ? {snippet}
: null}
-
-
- onScopeDocument(source.document_id)} className={chatMicroAction}>
-
- Scope document
-
-
-
- );
- })}
-
- );
-}
-
-function VerificationWorkspace({
- renderModel,
- query,
- answerEvidenceMapRows,
- pendingFeedback,
- onSubmitFeedback,
- onScopeDocument,
-}: {
- renderModel: AnswerRenderModel;
- query: string;
- answerEvidenceMapRows: AnswerEvidenceMapRow[];
- pendingFeedback: AnswerFeedbackType | null;
- onSubmitFeedback: (feedbackType: AnswerFeedbackType) => void;
- onScopeDocument: (documentId: string) => void;
-}) {
- const verificationSources = renderModel.primarySources.slice(0, renderModel.trust === "unsupported" ? 3 : 6);
- return (
-
-
-
-
-
Section support map
-
- Each answer section should resolve back to a linked cited passage before clinical use.
-
-
-
-
-
-
-
-
-
Cited source excerpts
-
- Open the document to inspect the PDF page and highlighted indexed passage.
-
-
-
-
-
- );
-}
-
-function AnswerViewModeControl({
- value,
- onChange,
-}: {
- value: AnswerViewMode;
- onChange: (mode: AnswerViewMode) => void;
-}) {
- const modes: Array<{ value: AnswerViewMode; label: string; shortLabel: string; icon: typeof Search }> = [
- { value: "standard", label: "Standard", shortLabel: "All", icon: ListChecks },
- { value: "high_yield", label: "High-yield", shortLabel: "Key", icon: Target },
- { value: "evidence_map", label: "Evidence map", shortLabel: "Map", icon: BookOpen },
- ];
-
- return (
-
- {modes.map((mode) => {
- const Icon = mode.icon;
- const active = value === mode.value;
- return (
- onChange(mode.value)}
- aria-pressed={active}
- aria-label={`Show ${mode.label.toLowerCase()} answer view`}
- title={mode.label}
- className={cn(
- "inline-flex min-h-9 min-w-0 flex-1 basis-[4.75rem] items-center justify-center gap-1.5 rounded-md px-2 text-xs font-semibold transition sm:flex-none sm:basis-auto sm:px-2.5",
- active
- ? "bg-[color:var(--primary)] text-[color:var(--primary-contrast)] shadow-sm"
- : "text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text)]",
- )}
- >
-
- {mode.shortLabel}
- {mode.label}
-
- );
- })}
-
- );
-}
-
-const simpleClinicalTableProps = {
- compact: false,
- expandOnMobile: true,
-} as const;
-
-function compactEvidenceCell(value: string | null | undefined, max = 140) {
- const text = value ? value.replace(/\s+/g, " ").trim() : "";
- return text.length > max ? `${text.slice(0, max - 1).trim()}…` : text;
-}
-
-function evidenceMapRowsFromRenderModel(renderModel: AnswerRenderModel): AnswerEvidenceMapRow[] {
- return renderModel.evidenceRows.map((row, index) => ({
- id: row.id || `${row.source.chunk_id}:${index}`,
- section: row.section || "Source evidence",
- detail:
- sourceTextForCompactDisplay(row.quote || row.source.snippet || row.source.reason || "") ||
- cleanDisplayTitle(row.source.title),
- supportLevel: row.supportLevel || row.source.sourceStrength,
- citationCount: 1,
- sourceStatus:
- row.source.sourceStrength === "none" ? "Source requires review" : `${row.source.sourceStrength} source support`,
- bestSourceLabel: row.source.label,
- bestLinkedPassage: row.quote || row.source.snippet || row.source.reason,
- href: row.source.href,
- }));
-}
-
-function EvidenceMapTable({ rows }: { rows: AnswerEvidenceMapRow[] }) {
- if (rows.length === 0) {
- return ;
- }
-
- const tableRows = rows.map((row) => [
- compactEvidenceCell(row.section),
- row.supportLevel,
- String(row.citationCount),
- compactEvidenceCell(row.sourceStatus),
- compactEvidenceCell(row.bestSourceLabel, 72),
- row.bestLinkedPassage || "Open source passage.",
- ]);
- const linkedRows = rows.filter((row) => row.href);
-
- return (
-
-
- {linkedRows.length ? (
-
- {linkedRows.map((row) => (
-
-
- {row.section}
- {row.bestSourceLabel}
-
-
- Open source
-
-
-
- ))}
-
- ) : null}
-
- );
-}
-
-function AnswerSafetyNotice({
- demoMode,
- weakEvidence = false,
- retrievalDiagnostics,
-}: {
- demoMode: boolean;
- weakEvidence?: boolean;
- retrievalDiagnostics?: RagAnswer["retrievalDiagnostics"];
-}) {
- const retrievalGateBlocked = retrievalDiagnostics?.gateStatus === "blocked";
- return (
-
-
- {weakEvidence
- ? "Weak source support; verify the linked source before relying on this answer."
- : "Draft only; verify source first before pasting into the medical record."}
-
- {retrievalGateBlocked ? (
-
- Retrieval confidence gate was triggered (low-confidence retrieval signal). Expand evidence details before
- using this result.
-
- ) : null}
- {demoMode ? (
-
- Synthetic demo only: this is not clinical guidance.
-
- ) : null}
-
- );
-}
-
-function QuoteCards({
- quotes,
- copiedQuotes,
- onCopyQuotes,
- onFollowUp,
- onScopeDocument,
-}: {
- quotes: QuoteCard[];
- copiedQuotes: boolean;
- onCopyQuotes: () => void;
- onFollowUp?: (quote: QuoteCard) => void;
- onScopeDocument: (documentId: string) => void;
-}) {
- return (
-
- 0 ? (
-
- ) : null
- }
- />
- {quotes.length === 0 ? (
-
- ) : (
-
- {quotes.map((quote, index) => {
- const quoteText = sourceTextForVerbatimQuote(quote.quote);
- const quoteTitle = cleanDisplayTitle(quote.title);
- return (
-
-
-
- {index + 1}
-
-
-
-
- “{quoteText}”
-
-
-
- {formatCompactCitationLabel(quote)}
-
-
- {quoteTitle}, page {quote.page_number ?? "n/a"}
-
-
- onFollowUp(quote) : undefined}
- divider={false}
- />
-
-
-
- );
- })}
-
- )}
-
- );
-}
-
-function formatQuoteCardsForClipboard(quotes: QuoteCard[]) {
- return quotes
- .map((quote, index) =>
- [
- // Clean the copied text the same way the card displays it, so clipboard
- // output never contains internal image-data blocks or glyph artifacts.
- `${index + 1}. "${sourceTextForVerbatimQuote(quote.quote)}"`,
- `Source: ${formatCitationLabel(quote)}`,
- `Link: ${documentCitationHref(quote)}`,
- ].join("\n"),
- )
- .join("\n\n");
-}
-
-function ClinicalOutputPanel({
+export function ClinicalOutputPanel({
answer,
collapsed = false,
showLead = true,
diff --git a/src/components/clinical-dashboard/answer-content.tsx b/src/components/clinical-dashboard/answer-content.tsx
new file mode 100644
index 000000000..9140030b2
--- /dev/null
+++ b/src/components/clinical-dashboard/answer-content.tsx
@@ -0,0 +1,797 @@
+"use client";
+
+/* eslint-disable @next/next/no-img-element */
+
+import Link from "next/link";
+import { memo, useEffect, useRef, useState } from "react";
+import {
+ AlertCircle,
+ CheckCircle2,
+ ChevronDown,
+ Copy,
+ ExternalLink,
+ Loader2,
+ ShieldCheck,
+ Sparkles,
+} from "lucide-react";
+
+import { SafeBoldText } from "@/components/SafeBoldText";
+import { Sheet } from "@/components/ui/sheet";
+import {
+ chatActionRow,
+ chatAnswerText,
+ chatMicroAction,
+ cn,
+ sourceCapsule,
+ statusDotMuted,
+ statusDotReady,
+ statusDotReview,
+ subtleStatusPill,
+ textMuted,
+} from "@/components/ui-primitives";
+import { sourceResultHref } from "@/components/clinical-dashboard/source-actions";
+import {
+ cleanDisplayTitle,
+ comparableAnswerText,
+ sanitizeAnswerDisplayText,
+} from "@/components/clinical-dashboard/display-text";
+import { useMobilePreviewSheet } from "@/components/clinical-dashboard/use-mobile-preview-sheet";
+import { clearCachedSignedUrl, getCachedSignedUrl, setCachedSignedUrl } from "@/lib/signed-url-cache";
+import { normalizeSourceMetadata, sourceStatusLabel } from "@/lib/source-metadata";
+import { clinicalProseUsefulness } from "@/lib/source-text-sanitizer";
+import {
+ frontendSourceGovernanceWarnings,
+ groupSourceGovernanceWarnings,
+ type SourceGovernanceWarning,
+} from "@/lib/source-governance";
+import { type SourceLink } from "@/lib/answer-render-policy";
+import { useAuthSession } from "@/lib/supabase/client";
+import type {
+ AnswerSection,
+ AnswerSectionKind,
+ BestSourceRecommendation,
+ SearchResult,
+ SearchScopeSummary,
+ VisualEvidenceCard,
+} from "@/lib/types";
+
+export const SourceImage = memo(function SourceImage({
+ endpoint,
+ caption,
+ className = "max-h-52",
+}: {
+ endpoint: string;
+ caption: string;
+ className?: string;
+}) {
+ const [url, setUrl] = useState(() => getCachedSignedUrl(endpoint)?.url ?? null);
+ const [failed, setFailed] = useState(false);
+ const [attempt, setAttempt] = useState(0);
+ const { authorizationHeader, markSessionExpired } = useAuthSession();
+
+ useEffect(() => {
+ const cached = getCachedSignedUrl(endpoint);
+ if (cached) return () => undefined;
+
+ let active = true;
+ fetch(endpoint, { headers: authorizationHeader })
+ .then((response) => {
+ if (response.status === 401) markSessionExpired();
+ return response.ok ? response.json() : null;
+ })
+ .then((data) => {
+ if (active && data?.url) {
+ setCachedSignedUrl(endpoint, data);
+ setUrl(data.url);
+ setFailed(false);
+ } else if (active) {
+ setFailed(true);
+ }
+ })
+ .catch(() => {
+ if (active) setFailed(true);
+ });
+ return () => {
+ active = false;
+ };
+ }, [attempt, authorizationHeader, endpoint, markSessionExpired]);
+
+ function retryImage() {
+ clearCachedSignedUrl(endpoint);
+ setUrl(null);
+ setFailed(false);
+ setAttempt((current) => current + 1);
+ }
+
+ function handleImageError() {
+ clearCachedSignedUrl(endpoint);
+ setFailed(true);
+ }
+
+ if (failed) {
+ return (
+
+
+
+ Image preview could not load.
+
+ Retry image
+
+
+
+ );
+ }
+
+ if (!url) {
+ return (
+
+
+ Loading image
+
+ );
+ }
+
+ return (
+
+ );
+});
+
+export function ScopeAndGovernanceNotice({
+ scope,
+ warnings,
+}: {
+ scope: SearchScopeSummary | null;
+ warnings: SourceGovernanceWarning[];
+}) {
+ const groupedWarnings = groupSourceGovernanceWarnings(frontendSourceGovernanceWarnings(warnings)).slice(0, 4);
+ const showScope =
+ Boolean(scope && scope.activeFilterCount > 0) ||
+ Boolean(scope?.warnings?.length) ||
+ scope?.matchedDocumentCount === 0;
+ if (!showScope && groupedWarnings.length === 0) return null;
+ return (
+
+ {showScope && scope ? (
+
+ Scope: {scope.summary}
+ {scope.queryMode && scope.queryMode !== "auto" ? ` · ${scope.queryMode.replaceAll("_", " ")}` : ""}
+
+ ) : null}
+ {scope?.warnings?.length ? (
+
+ {scope.warnings.slice(0, 3).map((warning) => (
+ {warning}
+ ))}
+
+ ) : null}
+ {groupedWarnings.length ? (
+
+ {groupedWarnings.map((warning) => (
+
+ {warning.message}
+ {warning.titles.length ? (
+
+ Sources affected
+ {warning.titles.slice(0, 5).join(", ")}
+
+ ) : null}
+
+ ))}
+
+ ) : null}
+
+ );
+}
+
+export function plainAnswerText(value: string) {
+ const useful = clinicalProseUsefulness(value);
+ return sanitizeAnswerDisplayText(useful.text || value, { minLength: 8, minTokens: 2 })
+ .replace(/(?:\s*\n\s*)?Synthetic demo only:.*$/i, "")
+ .trim();
+}
+
+function primaryAnswerDisplayText(value: string) {
+ const cleaned = plainAnswerText(value);
+ const fragments = cleaned
+ .split(/\r?\n+/)
+ .flatMap((line: string) =>
+ line.split(/(?<=[.!?])\s+(?=(?:[A-Z]|\*\*|If\b|When\b|Do\b|Use\b|Monitor\b|Escalate\b|Document\b))/),
+ )
+ .map((fragment: string) =>
+ fragment
+ .replace(/^(?:[-*•]|\d+[.)])\s+/, "")
+ .replace(
+ /^(?:\*\*)?(?:answer|summary|bottom line|direct answer|clinical point|key point|required actions?|monitoring(?:\/timing)?|thresholds?|dose detail|medication(?:\/dose details?)?|escalation(?:\/risk)?|risk|safety|documentation(?:\/forms)?|source gaps?)(?:\*\*)?:\s+/i,
+ "",
+ )
+ .trim(),
+ )
+ .map((fragment: string) => clinicalProseUsefulness(fragment).text || fragment)
+ .filter((fragment: string) => {
+ if (!fragment) return false;
+ const useful = clinicalProseUsefulness(fragment);
+ return useful.useful || fragment.split(/\s+/).length >= 8;
+ });
+ const uniqueFragments = Array.from(new Set(fragments));
+ const selected = uniqueFragments.slice(0, 3).join(" ");
+ const words = selected.split(/\s+/).filter(Boolean);
+ if (words.length <= 85) return selected || cleaned;
+ return `${words
+ .slice(0, 85)
+ .join(" ")
+ .replace(/[;,:-]\s*$/, "")}...`;
+}
+
+function sourceCapsuleText({
+ sourceCount,
+ weakEvidence,
+ grounded,
+}: {
+ sourceCount: number;
+ weakEvidence: boolean;
+ grounded: boolean;
+}) {
+ if (sourceCount <= 0) return "No direct source found";
+ if (!grounded) return "Review nearby sources";
+ if (weakEvidence) return "Review sources";
+ return `${sourceCount} source${sourceCount === 1 ? "" : "s"}`;
+}
+
+export function sourceStatusDotClass(metadata: ReturnType | null | undefined) {
+ if (!metadata) return statusDotMuted;
+ if (metadata.document_status === "current") return statusDotReady;
+ if (metadata.document_status === "review_due" || metadata.document_status === "outdated") return statusDotReview;
+ return statusDotMuted;
+}
+
+type CapsulePreviewSource = {
+ id: string;
+ title: string;
+ pageNumber: number | null;
+ metadata: ReturnType;
+ score: number;
+ href: string;
+ snippet?: string;
+ sourceStrength?:
+ SourceLink["sourceStrength"] | BestSourceRecommendation["source_strength"] | SearchResult["source_strength"];
+};
+
+function sourceBadgeLabel(index: number) {
+ return `S${index + 1}`;
+}
+
+function sourceBadgeToneClass(metadata: ReturnType, index: number) {
+ if (metadata.document_status === "review_due" || metadata.document_status === "outdated") {
+ return "border-[color:var(--warning-border)] bg-[color:var(--warning-soft)] text-[color:var(--warning)]";
+ }
+ if (index === 0) {
+ return "border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent)] text-[color:var(--clinical-accent-contrast)]";
+ }
+ return "border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)]";
+}
+
+function sourceSupportLabel(source: CapsulePreviewSource, index: number) {
+ if (!source.sourceStrength || source.sourceStrength === "none") return "Unsupported";
+ if (source.sourceStrength === "limited") return "Partial";
+ if (source.sourceStrength === "moderate") return "Partial";
+ if (index === 0 || source.sourceStrength === "strong") return "Direct";
+ return "Partial";
+}
+
+function sourceStatusShortLabel(metadata: ReturnType) {
+ if (metadata.document_status === "review_due") return "Review due";
+ if (metadata.document_status === "outdated") return "Outdated";
+ if (metadata.document_status === "current") return "Current";
+ return sourceStatusLabel(metadata);
+}
+
+function sourcePreviewPageCountLabel(previewSources: CapsulePreviewSource[]) {
+ const uniquePages = new Set(previewSources.map((source) => source.pageNumber).filter((page) => page !== null));
+ const count = uniquePages.size || previewSources.length;
+ return `${count} page${count === 1 ? "" : "s"}`;
+}
+
+function capsulePreviewSources(
+ bestSource: BestSourceRecommendation | null,
+ sources: SearchResult[],
+ sourceLinks: SourceLink[] = [],
+) {
+ const rows: CapsulePreviewSource[] = [];
+ const seen = new Set();
+ const pushRow = (row: CapsulePreviewSource) => {
+ const key = `${row.id}:${row.title}:${row.pageNumber ?? "n/a"}`;
+ if (seen.has(key)) return;
+ seen.add(key);
+ rows.push(row);
+ };
+
+ sourceLinks.slice(0, 5).forEach((source) => {
+ pushRow({
+ id: source.chunk_id,
+ title: source.title || source.file_name || "Source",
+ pageNumber: source.page_number,
+ metadata: normalizeSourceMetadata(source.sourceMetadata),
+ score: source.score ?? 0,
+ href: source.href,
+ snippet: source.snippet,
+ sourceStrength: source.sourceStrength,
+ });
+ });
+
+ if (bestSource) {
+ pushRow({
+ id: bestSource.chunk_id,
+ title: bestSource.title || bestSource.file_name || "Source",
+ pageNumber: bestSource.page_number,
+ metadata: normalizeSourceMetadata(bestSource.source_metadata),
+ score: bestSource.score,
+ href: bestSource.viewer_href,
+ sourceStrength: bestSource.source_strength,
+ });
+ }
+
+ sources.slice(0, 5).forEach((source) => {
+ pushRow({
+ id: source.id,
+ title: source.title || source.file_name || "Source",
+ pageNumber: source.page_number,
+ metadata: normalizeSourceMetadata(source.source_metadata),
+ score: source.hybrid_score ?? source.similarity ?? source.lexical_score ?? 0,
+ href: sourceResultHref(source),
+ sourceStrength: source.source_strength,
+ });
+ });
+
+ return rows.slice(0, 4);
+}
+
+function SourcePreviewContent({
+ previewSources,
+ quoteText,
+ copiedQuote,
+ onCopyQuote,
+ showHeader = true,
+}: {
+ previewSources: CapsulePreviewSource[];
+ quoteText?: string | null;
+ copiedQuote: boolean;
+ onCopyQuote: () => void;
+ showHeader?: boolean;
+}) {
+ const primaryPreviewSource = previewSources[0] ?? null;
+ const reviewDueSource = previewSources.find(
+ (source) => source.metadata.document_status === "review_due" || source.metadata.document_status === "outdated",
+ );
+
+ return (
+ <>
+ {showHeader ? (
+
+
+
+
Sources
+
+ {sourcePreviewPageCountLabel(previewSources)}
+
+
+
Open the original PDF page.
+
+
+ ) : null}
+
+ {previewSources.map((source, index) => (
+
+ {index === 0 ? (
+
+
+ Best match
+
+ ) : index === 1 ? (
+
Also used
+ ) : null}
+
+
+ {sourceBadgeLabel(index)}
+
+
+
+ {cleanDisplayTitle(source.title)}
+
+
+ p. {source.pageNumber ?? "n/a"}
+ ·
+ {sourceSupportLabel(source, index)}
+
+
+ {sourceStatusShortLabel(source.metadata)}
+
+
+
+
+
+ {index === 0 ? Open : null}
+
+
+
+ ))}
+
+ {quoteText ? (
+
+ “{quoteText}”
+
+ ) : null}
+
+ {primaryPreviewSource ? (
+
+
+ Open source page
+
+ ) : null}
+ {quoteText ? (
+
+
+ {copiedQuote ? "Copied quote" : "Copy quote"}
+
+ ) : null}
+
+
+
+ {reviewDueSource ? : }
+ {reviewDueSource
+ ? `${sourceBadgeLabel(previewSources.indexOf(reviewDueSource))} review due`
+ : "Sources current"}
+
+ {primaryPreviewSource ? (
+
+ Evidence details
+
+
+ ) : null}
+
+ >
+ );
+}
+
+export function NaturalLanguageAnswer({
+ text,
+ sourceCount,
+ weakEvidence,
+ grounded,
+ sourceOnly,
+ bestSource,
+ sources,
+ sourceLinks,
+ copied,
+ onCopy,
+}: {
+ text: string;
+ sourceCount: number;
+ weakEvidence: boolean;
+ grounded: boolean;
+ sourceOnly: boolean;
+ bestSource: BestSourceRecommendation | null;
+ sources: SearchResult[];
+ sourceLinks: SourceLink[];
+ copied: boolean;
+ onCopy: () => void;
+}) {
+ const [sourcePreviewOpen, setSourcePreviewOpen] = useState(false);
+ const [copiedSourceQuote, setCopiedSourceQuote] = useState(false);
+ const sourceCapsuleRef = useRef(null);
+ const copySourceQuoteTimerRef = useRef(null);
+ const usePreviewSheet = useMobilePreviewSheet();
+ useEffect(() => {
+ return () => {
+ if (copySourceQuoteTimerRef.current !== null) window.clearTimeout(copySourceQuoteTimerRef.current);
+ };
+ }, []);
+ const cleaned = primaryAnswerDisplayText(text);
+ if (!cleaned) return null;
+ const capsuleText = sourceCapsuleText({ sourceCount, weakEvidence, grounded });
+ const previewSources = capsulePreviewSources(bestSource, sources, sourceLinks);
+ const quoteText = sourceLinks.find((source) => source.snippet)?.snippet || bestSource?.quote || bestSource?.snippet;
+ const canOpenSourcePreview = previewSources.length > 0;
+ async function copySourceQuote() {
+ if (!quoteText) return;
+ try {
+ await navigator.clipboard.writeText(quoteText);
+ setCopiedSourceQuote(true);
+ if (copySourceQuoteTimerRef.current !== null) window.clearTimeout(copySourceQuoteTimerRef.current);
+ copySourceQuoteTimerRef.current = window.setTimeout(() => setCopiedSourceQuote(false), 1600);
+ } catch {
+ setCopiedSourceQuote(false);
+ }
+ }
+ const sourceCapsuleButton = (
+ {
+ if (canOpenSourcePreview) setSourcePreviewOpen((current) => !current);
+ }}
+ >
+ {sourceCount > 0 ? (
+ <>
+
+ {sourceCount} source{sourceCount === 1 ? "" : "s"}
+
+ {capsuleText}
+ >
+ ) : (
+ capsuleText
+ )}
+ {canOpenSourcePreview ? : null}
+
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {sourceOnly ? (
+
+ Source-only answer — assembled from your documents without the AI model, so it may be less complete. Verify
+ it against the cited passages below.
+
+ ) : null}
+ {sourceCapsuleButton}
+ {sourcePreviewOpen && canOpenSourcePreview && !usePreviewSheet ? (
+
+
+
+ ) : null}
+
setSourcePreviewOpen(false)}
+ title="Sources"
+ description="Open the original PDF page."
+ titleAccessory={
+
+ {sourcePreviewPageCountLabel(previewSources)}
+
+ }
+ closeLabel="Close answer sources"
+ contentClassName="sm:max-w-xl"
+ returnFocusRef={sourceCapsuleRef}
+ portal
+ >
+
+
+
+
+
+
+
+ {copied ? "Copied with sources" : "Copy with sources"}
+
+
+
+
+ );
+}
+
+export function UserQuestionBubble({ query }: { query: string }) {
+ const cleaned = query.trim();
+ if (!cleaned) return null;
+
+ return (
+
+ );
+}
+
+type KeyClinicalItem = {
+ id: string;
+ label?: string;
+ detail: string;
+};
+
+function keyClinicalItemFromText(item: string): KeyClinicalItem | null {
+ const cleaned = item.replace(/^[-*•]\s*/, "").trim();
+ if (cleaned.length < 24) return null;
+ const [labelCandidate, ...detailParts] = cleaned.split(/\s+(?:—|-)\s+/);
+ const label = labelCandidate?.trim();
+ const detail = detailParts.join(" — ").trim();
+ const id = comparableAnswerText(cleaned);
+ if (label && detail && label.length <= 64) return { id, label, detail };
+ return { id, detail: cleaned };
+}
+
+export function keyClinicalItemsFromSections(
+ sections: Array,
+): KeyClinicalItem[] {
+ const usefulKinds = new Set([
+ "required_actions",
+ "monitoring_timing",
+ "medication_dose",
+ "thresholds",
+ "escalation_risk",
+ "contraindications_cautions",
+ "comparison",
+ ]);
+ return sections
+ .filter((section) => usefulKinds.has(section.kind))
+ .flatMap((section) =>
+ section.body
+ .split(/\n+|(?<=\.)\s+(?=(?:Monitor|Check|Use|Avoid|Escalate|Withhold|Review|Document|Repeat|Consider)\b)/)
+ .map((item) => keyClinicalItemFromText(item))
+ .filter((item): item is KeyClinicalItem => Boolean(item)),
+ )
+ .filter((item, index, items) => items.findIndex((candidate) => candidate.id === item.id) === index)
+ .slice(0, 5);
+}
+
+export function keyClinicalItemsFromTable(item: VisualEvidenceCard | null): KeyClinicalItem[] {
+ const rows = item?.tableRows?.filter((row) => row.some((cell) => cell.trim())) ?? [];
+ if (rows.length < 2) return [];
+
+ return rows
+ .slice(0, 3)
+ .map((row): KeyClinicalItem | null => {
+ const [domain, baseline] = row.map((cell) => cell.trim()).filter(Boolean);
+ if (!domain || !baseline) return null;
+ const detail = baseline;
+ return {
+ id: comparableAnswerText([domain, detail].join(" ")),
+ label: domain,
+ detail,
+ };
+ })
+ .filter((value): value is KeyClinicalItem => value !== null)
+ .slice(0, 5);
+}
+
+function KeyClinicalItems({
+ sections,
+ table,
+}: {
+ sections: Array;
+ table: VisualEvidenceCard | null;
+}) {
+ const sectionItems = keyClinicalItemsFromSections(sections);
+ const tableItems = keyClinicalItemsFromTable(table);
+ const items = sectionItems.length >= 2 ? sectionItems : tableItems;
+ if (items.length < 2) return null;
+
+ return (
+
+ Key monitoring items
+
+ {items.map((item) => (
+
+ {item.label ? (
+ <>
+ {item.label}
+ —
+
+ >
+ ) : (
+
+ )}
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/clinical-dashboard/display-text.ts b/src/components/clinical-dashboard/display-text.ts
index c599b295b..a21b08f88 100644
--- a/src/components/clinical-dashboard/display-text.ts
+++ b/src/components/clinical-dashboard/display-text.ts
@@ -241,3 +241,12 @@ export function sourceDisplayMeta(source: SearchResult, title: string) {
Boolean(fileBase) && fileBaseNormalized !== titleBase && !fileBaseNormalized.startsWith(titleBase);
return [includeFile ? source.file_name : null, `page ${source.page_number ?? "n/a"}`].filter(Boolean).join(" · ");
}
+
+export function comparableAnswerText(value: string) {
+ return value
+ .replace(/\*\*/g, "")
+ .replace(/\.\.\.$/, "")
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, " ")
+ .trim();
+}
diff --git a/src/components/clinical-dashboard/evidence-panels.tsx b/src/components/clinical-dashboard/evidence-panels.tsx
new file mode 100644
index 000000000..88e1138aa
--- /dev/null
+++ b/src/components/clinical-dashboard/evidence-panels.tsx
@@ -0,0 +1,1935 @@
+"use client";
+
+import Link from "next/link";
+import { type RefObject, useState } from "react";
+import {
+ Activity,
+ AlertCircle,
+ BookOpen,
+ CheckCircle2,
+ ChevronDown,
+ ClipboardCheck,
+ Copy,
+ ExternalLink,
+ FileText,
+ Filter,
+ Layers,
+ ListChecks,
+ Loader2,
+ Plus,
+ Quote,
+ RefreshCw,
+ Search,
+ ShieldAlert,
+ ShieldCheck,
+ SlidersHorizontal,
+ Table2,
+ Target,
+} from "lucide-react";
+
+import { AccessibleTable } from "@/components/AccessibleTable";
+import { ClinicalOutputPanel, clinicalQueryModeOptions, type AnswerFeedbackType } from "@/components/ClinicalDashboard";
+import {
+ keyClinicalItemsFromSections,
+ keyClinicalItemsFromTable,
+ plainAnswerText,
+ sourceStatusDotClass,
+} from "@/components/clinical-dashboard/answer-content";
+import { CopyButton } from "@/components/clinical-dashboard/answer-status";
+import { StrengthBadge } from "@/components/clinical-dashboard/badges";
+import { SectionHeading } from "@/components/clinical-dashboard/dashboard-shell";
+import {
+ cleanDisplayTitle,
+ compactSourceSnippet,
+ comparableAnswerText,
+} from "@/components/clinical-dashboard/display-text";
+import {
+ hasStrongRelevanceIcon,
+ QueryCoverageChips,
+ relevanceChipLabel,
+ RelevanceBadge,
+} from "@/components/clinical-dashboard/relevance";
+import { SourceActionRow, sourceResultHref } from "@/components/clinical-dashboard/source-actions";
+import {
+ chatMicroAction,
+ clinicalDivider,
+ cn,
+ codeText,
+ EmptyState,
+ evidenceSurface,
+ floatingControl,
+ iconTilePremium,
+ metadataPill,
+ panelSubtle,
+ primaryControl,
+ proseMeasure,
+ raisedCard,
+ sourceCard,
+ SourceProvenance,
+ SourceStatusBadge,
+ subtleStatusPill,
+ tableMicroActionRow,
+ textMuted,
+ toneDanger,
+ toneInfo,
+ toneNeutral,
+ toneSuccess,
+ toneWarning,
+} from "@/components/ui-primitives";
+import { type AnswerRenderModel, type SourceLink } from "@/lib/answer-render-policy";
+import { documentCitationHref, formatCitationLabel, formatCompactCitationLabel } from "@/lib/citations";
+import { extractSafetyFindings, formatSafetyFindingLabel } from "@/lib/clinical-safety";
+import { frontendSourceGovernanceWarnings, type SourceGovernanceWarning } from "@/lib/source-governance";
+import { normalizeSourceMetadata, sourceStatusLabel, validationStatusLabel } from "@/lib/source-metadata";
+import {
+ normalizeExtractedGlyphs,
+ sourceTextForCompactDisplay,
+ sourceTextForVerbatimQuote,
+} from "@/lib/source-text-sanitizer";
+import type {
+ AnswerSection,
+ BestSourceRecommendation,
+ ClinicalQueryMode,
+ ConflictOrGap,
+ EvidenceRelevance,
+ EvidenceSummary,
+ QuoteCard,
+ RagAnswer,
+ SearchResult,
+ VisualEvidenceCard,
+} from "@/lib/types";
+import { emptyStates } from "@/lib/ui-copy";
+import {
+ type AnswerEvidenceMapRow,
+ type AnswerViewMode,
+ buildClinicalOutputSections,
+ buildHighYieldClinicalOutputSections,
+} from "@/lib/ward-output";
+
+type AnswerSupportPriority = {
+ title: string;
+ detail: string;
+ sourceLabel?: string;
+ tone: "priority" | "caution";
+};
+
+export function answerSupportPriority(
+ answer: RagAnswer,
+ sections: Array,
+ table: VisualEvidenceCard | null,
+ safetyFindings: ReturnType,
+ options: { grounded: boolean; weakEvidence: boolean },
+): AnswerSupportPriority | null {
+ const firstSafetyFinding = safetyFindings[0];
+ if (firstSafetyFinding) {
+ return {
+ title: "Priority",
+ detail: formatSafetyFindingLabel(firstSafetyFinding),
+ sourceLabel: "S1",
+ tone: "caution",
+ };
+ }
+
+ if (answer.answerQualityTier === "source_only" || !options.grounded || options.weakEvidence) {
+ return {
+ title: "Review source match",
+ detail:
+ "Verify cited passages before using clinical numbers, monitoring, dose, route, timing, or risk decisions.",
+ sourceLabel: "Review",
+ tone: "caution",
+ };
+ }
+
+ const sectionItems = keyClinicalItemsFromSections(sections);
+ const tableItems = keyClinicalItemsFromTable(table);
+ const item = sectionItems[0] ?? tableItems[0] ?? null;
+ if (!item) return null;
+
+ return {
+ title: item.label ?? "Priority",
+ detail: item.detail,
+ sourceLabel: "S1",
+ tone: "priority",
+ };
+}
+
+export function AnswerSupportSummaryCard({
+ priority,
+ clinicalCount,
+ evidenceSummary,
+ clinicalAvailable,
+ evidenceAvailable,
+ clinicalTriggerRef,
+ evidenceTriggerRef,
+ onOpenClinicalNotes,
+ onOpenEvidence,
+}: {
+ priority: AnswerSupportPriority | null;
+ clinicalCount: number;
+ evidenceSummary: string;
+ clinicalAvailable: boolean;
+ evidenceAvailable: boolean;
+ clinicalTriggerRef?: RefObject;
+ evidenceTriggerRef?: RefObject;
+ onOpenClinicalNotes: () => void;
+ onOpenEvidence: () => void;
+}) {
+ const supportRowCount = Number(clinicalAvailable) + Number(evidenceAvailable);
+ const supportButtonClass =
+ "grid min-h-[72px] grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-3 py-3 text-left transition hover:bg-[color:var(--surface-subtle)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[color:var(--focus)]";
+
+ return (
+
+ {priority ? (
+
+
+ {priority.tone === "caution" ? : }
+
+
+
{priority.title}
+
{priority.detail}
+
+ {priority.sourceLabel ? (
+
{priority.sourceLabel}
+ ) : null}
+
+ ) : null}
+
+ {supportRowCount > 0 ? (
+
+ {clinicalAvailable ? (
+
+
+
+ Clinical notes
+
+ {clinicalCount} note{clinicalCount === 1 ? "" : "s"}
+
+
+
+
+ ) : null}
+ {evidenceAvailable ? (
+
+
+
+ Evidence
+ {evidenceSummary}
+
+
+
+ ) : null}
+
+ ) : null}
+
+ );
+}
+
+function isRedundantStructuredItem(item: string, primaryAnswer: string) {
+ const itemText = comparableAnswerText(item);
+ const answerText = comparableAnswerText(primaryAnswer);
+ if (!itemText || !answerText) return false;
+ if (answerText.includes(itemText) || itemText.includes(answerText)) return true;
+ if (itemText.length < 40) return false;
+ const answerWords = new Set(answerText.split(" ").filter((word) => word.length > 3));
+ const itemWords = itemText.split(" ").filter((word) => word.length > 3);
+ if (itemWords.length < 6) return false;
+ const sharedWords = itemWords.filter((word) => answerWords.has(word)).length;
+ if (sharedWords / itemWords.length >= 0.82) return true;
+ return answerText.includes(itemText.slice(0, Math.min(160, itemText.length)));
+}
+
+type ClinicalDetailSection = ReturnType[number];
+
+export function displayItemsForClinicalDetailSection(
+ section: ClinicalDetailSection,
+ primaryAnswer: string,
+ showLead: boolean,
+) {
+ if (showLead) return section.items;
+ const nonRedundantItems = section.items.filter((item) => !isRedundantStructuredItem(item, primaryAnswer));
+ return nonRedundantItems.length > 0 || section.items.length === 0 ? nonRedundantItems : section.items;
+}
+
+const clinicalDetailPriority: Record = {
+ action: 10,
+ escalation: 20,
+ thresholds: 30,
+ cautions: 40,
+ monitoring: 50,
+ medication: 60,
+ documentation: 70,
+ comparison: 80,
+ "support-map": 90,
+ "source-gap": 100,
+};
+
+export function clinicalDetailContentCount(section: ClinicalDetailSection) {
+ if (section.items.length > 0) return section.items.length;
+ const tableRows =
+ section.tables?.reduce((total, table) => total + (table.rows?.length ?? (table.markdown ? 1 : 0)), 0) ?? 0;
+ return tableRows || section.tables?.length || 0;
+}
+
+export function sortClinicalDetailSections(sections: ClinicalDetailSection[]) {
+ return [...sections].sort((left, right) => {
+ const leftPriority = clinicalDetailPriority[left.id] ?? 75;
+ const rightPriority = clinicalDetailPriority[right.id] ?? 75;
+ if (leftPriority !== rightPriority) return leftPriority - rightPriority;
+ return left.title.localeCompare(right.title);
+ });
+}
+
+export function clinicalDetailMeta(section: ClinicalDetailSection): {
+ icon: typeof Search;
+ eyebrow: string;
+ toneClassName: string;
+ accentClassName: string;
+} {
+ if (section.id === "thresholds") {
+ return {
+ icon: Target,
+ eyebrow: "Thresholds",
+ toneClassName: toneWarning,
+ accentClassName: "bg-[color:var(--warning)]",
+ };
+ }
+ if (section.id === "escalation" || section.id === "cautions" || section.id === "source-gap") {
+ return {
+ icon: ShieldAlert,
+ eyebrow: section.id === "source-gap" ? "Source gap" : "Risk",
+ toneClassName: toneDanger,
+ accentClassName: "bg-[color:var(--danger)]",
+ };
+ }
+ if (section.id === "monitoring" || section.id === "medication") {
+ return {
+ icon: ClipboardCheck,
+ eyebrow: section.id === "monitoring" ? "Monitoring" : "Medication",
+ toneClassName: toneWarning,
+ accentClassName: "bg-[color:var(--warning)]",
+ };
+ }
+ if (section.id === "support-map" || section.id === "comparison") {
+ return {
+ icon: BookOpen,
+ eyebrow: section.id === "support-map" ? "Evidence support" : "Comparison",
+ toneClassName: toneInfo,
+ accentClassName: "bg-[color:var(--info)]",
+ };
+ }
+ if (section.id === "documentation") {
+ return {
+ icon: FileText,
+ eyebrow: "Documentation",
+ toneClassName: toneNeutral,
+ accentClassName: "bg-[color:var(--border-strong)]",
+ };
+ }
+ return {
+ icon: ListChecks,
+ eyebrow: "Clinical action",
+ toneClassName: toneSuccess,
+ accentClassName: "bg-[color:var(--success)]",
+ };
+}
+
+export function clinicalDetailSummaryItems(sections: ClinicalDetailSection[]) {
+ const countById = (ids: string[]) =>
+ sections
+ .filter((section) => ids.includes(section.id))
+ .reduce((total, section) => total + clinicalDetailContentCount(section), 0);
+ const tableCount = sections.reduce((total, section) => total + (section.tables?.length ?? 0), 0);
+ const items = [
+ { label: "Actions", value: countById(["action", "escalation", "documentation"]) },
+ { label: "Monitoring", value: countById(["monitoring", "medication"]) },
+ { label: "Tables", value: tableCount },
+ { label: "Cautions", value: countById(["cautions", "source-gap"]) },
+ { label: "Evidence", value: countById(["support-map", "comparison"]) },
+ ];
+ return items.filter((item) => item.value > 0);
+}
+
+type ClinicalNotesTabId = "essentials" | "actions" | "safety";
+
+type ClinicalNotesRow = {
+ id: string;
+ title: string;
+ detail: string;
+ sourceIndex: number;
+ tone: "safe" | "warn";
+};
+
+const clinicalNotesTabMeta: Record<
+ ClinicalNotesTabId,
+ { label: string; icon: typeof ShieldCheck; sectionIds: string[] }
+> = {
+ essentials: {
+ label: "Essentials",
+ icon: ClipboardCheck,
+ sectionIds: ["thresholds", "monitoring", "medication", "support-map", "comparison"],
+ },
+ actions: {
+ label: "Actions",
+ icon: Activity,
+ sectionIds: ["action", "documentation", "monitoring", "medication"],
+ },
+ safety: {
+ label: "Safety",
+ icon: ShieldCheck,
+ sectionIds: ["escalation", "cautions", "source-gap", "thresholds"],
+ },
+};
+
+function compactClinicalNoteText(value: string) {
+ return normalizeExtractedGlyphs(value)
+ .replace(/\*\*/g, "")
+ .replace(/\s*\[\d+(?:,\s*\d+)*\]\s*/g, " ")
+ .replace(/\s+/g, " ")
+ .trim();
+}
+
+function stripClinicalNoteLeadIn(value: string) {
+ let text = compactClinicalNoteText(value);
+ let previous = "";
+ while (text !== previous) {
+ previous = text;
+ text = text
+ .replace(/^(the\s+same\s+)?synthetic\s+source\s+says\s+/i, "")
+ .replace(/^the\s+(indexed\s+)?source\s+says\s+/i, "")
+ .replace(/^source\s+text\s+says\s+/i, "")
+ .replace(/^according\s+to\s+[^,]+,\s*/i, "")
+ .trim();
+ }
+ return text;
+}
+
+function titleCaseClinicalNote(value: string) {
+ return value
+ .replace(/\b\w[\w/-]*/g, (word) => {
+ if (/[A-Z]{2,}|\/|\d/.test(word)) return word;
+ return `${word.charAt(0).toUpperCase()}${word.slice(1).toLowerCase()}`;
+ })
+ .replace(/\bAnd\b/g, "and")
+ .replace(/\bOr\b/g, "or")
+ .replace(/\bTo\b/g, "to");
+}
+
+function sentenceCaseClinicalNoteDetail(value: string) {
+ const text = stripClinicalNoteLeadIn(value);
+ return text ? `${text.charAt(0).toUpperCase()}${text.slice(1)}` : text;
+}
+
+function clinicalNoteHeuristicTitle(value: string) {
+ const text = stripClinicalNoteLeadIn(value);
+ const lower = text.toLowerCase();
+
+ if (/\bbaseline checklist\b/.test(lower) && /\bconfirm indication\b/.test(lower)) return "Indication";
+ if (/\b(vomiting|diarrhoea|diarrhea|dehydration|acute kidney injury|tremor|confusion|ataxia)\b/.test(lower)) {
+ return "Toxicity review triggers";
+ }
+ if (
+ /\b(escalate|urgent review|urgent|red flag|seizures?|severe constipation|chest pain|dyspnoea|tachycardia)\b/.test(
+ lower,
+ )
+ ) {
+ return "Escalation triggers";
+ }
+ if (/\blithium levels?\b/.test(lower) && /\b(5\s*(?:to|-|–)\s*7|dose change|stable|days?)\b/.test(lower)) {
+ return "Lithium level timing";
+ }
+ if (/\b(lithium level|serum lithium|trough level)\b/.test(lower)) return "Lithium level check";
+ if (/\b(fbc|anc)\b/.test(lower)) return "FBC/ANC monitoring";
+ if (/\bmyocarditis\b/.test(lower)) return "Myocarditis screening";
+ if (/\b(metabolic|weight|lipids?|glucose|hba1c|waist)\b/.test(lower)) return "Metabolic monitoring";
+ if (/\b(constipation|bowel)\b/.test(lower)) return "Constipation prevention";
+ if (/\b(shared-care|shared care|communication|handover)\b/.test(lower)) return "Shared-care communication";
+ if (/\b(renal|kidney|creatinine|egfr)\b/.test(lower)) return "Renal function";
+ if (/\b(thyroid|tsh)\b/.test(lower)) return "Thyroid monitoring";
+ if (/\bcalcium\b/.test(lower)) return "Calcium monitoring";
+ if (/\b(nsaid|ace inhibitor|diuretic|interacting medicine|medicine reconciliation)\b/.test(lower)) {
+ return "Interacting medicines";
+ }
+
+ return null;
+}
+
+function clinicalNoteTitleFromItem(item: string, section: ClinicalDetailSection, index: number) {
+ const text = stripClinicalNoteLeadIn(item);
+ const heuristicTitle = clinicalNoteHeuristicTitle(text);
+ if (heuristicTitle) return heuristicTitle;
+ const colonIndex = text.indexOf(":");
+ if (colonIndex > 8 && colonIndex < 54) {
+ const title = text.slice(0, colonIndex).trim();
+ const detailStart = text
+ .slice(colonIndex + 1)
+ .split(/[,;]/)[0]
+ ?.trim();
+ if (/\b(checklist|checkpoint|points?)\b/i.test(title) && detailStart) {
+ return clinicalNoteTitleFromFragment(detailStart);
+ }
+ return title;
+ }
+ const dashIndex = text.search(/\s[-–]\s/);
+ if (dashIndex > 8 && dashIndex < 54) return text.slice(0, dashIndex).trim();
+ if (section.items.length === 1 && section.title.length <= 42) return section.title;
+ const words = text
+ .replace(/^(confirm|check|review|record|document)\s+/i, "")
+ .split(" ")
+ .filter(Boolean);
+ return words.slice(0, Math.min(words.length, index === 0 ? 5 : 4)).join(" ") || section.title;
+}
+
+function clinicalNoteDetailFromItem(item: string, title: string) {
+ const text = stripClinicalNoteLeadIn(item);
+ const normalizedTitle = title.toLowerCase();
+ const lowerText = text.toLowerCase();
+ const colonIndex = text.indexOf(":");
+ if (colonIndex > 8 && colonIndex < 64) {
+ const beforeColon = text.slice(0, colonIndex);
+ const afterColon = text.slice(colonIndex + 1).trim();
+ if (/\b(checklist|checkpoint|points?)\b/i.test(beforeColon) && afterColon) {
+ return sentenceCaseClinicalNoteDetail(afterColon);
+ }
+ }
+ if (lowerText.startsWith(`${normalizedTitle}:`)) {
+ return sentenceCaseClinicalNoteDetail(text.slice(title.length + 1).trim());
+ }
+ if (lowerText.startsWith(`${normalizedTitle} -`) || lowerText.startsWith(`${normalizedTitle} –`)) {
+ return sentenceCaseClinicalNoteDetail(text.slice(title.length + 2).trim());
+ }
+ if (text === title) return "Review linked source context before using this note.";
+ return sentenceCaseClinicalNoteDetail(text);
+}
+
+function clinicalNoteTitleFromFragment(fragment: string) {
+ const text = stripClinicalNoteLeadIn(fragment)
+ .replace(/^(and|or)\s+/i, "")
+ .replace(/^(confirm|check|review|record|document)\s+/i, "")
+ .replace(/[.;:,]+$/g, "");
+ if (!text) return "Clinical note";
+ return clinicalNoteHeuristicTitle(text) ?? titleCaseClinicalNote(text);
+}
+
+function splitClinicalNoteFragments(item: string, section: ClinicalDetailSection, title: string) {
+ const detail = clinicalNoteDetailFromItem(item, title);
+ const titleLooksGeneric = /\b(checkpoint|checklist|item|point|monitoring|safety)\b/i.test(title);
+ const itemLooksGeneric = /\b(checkpoint|checklist|item|point|monitoring|safety)\b/i.test(
+ stripClinicalNoteLeadIn(item),
+ );
+ if (!titleLooksGeneric && !itemLooksGeneric && section.items.length > 1) return null;
+
+ const fragments = detail
+ .replace(/\band\s+/gi, "")
+ .split(/[,;]\s+/)
+ .map((fragment) => compactClinicalNoteText(fragment).replace(/[.;:,]+$/g, ""))
+ .filter((fragment) => fragment.length > 5);
+
+ return fragments.length >= 3 ? fragments.slice(0, 5) : null;
+}
+
+function clinicalNoteToneForText(text: string, fallback: ClinicalNotesRow["tone"]) {
+ if (/\b(toxicity|toxic|warning|caution|urgent|red flag|adverse|confusion|ataxia|tremor)\b/i.test(text)) {
+ return "warn";
+ }
+ return fallback;
+}
+
+function clinicalNoteHasDistinctDetail(row: ClinicalNotesRow) {
+ const title = compactClinicalNoteText(row.title).toLowerCase();
+ const detail = compactClinicalNoteText(row.detail).toLowerCase();
+ return Boolean(detail) && detail !== title;
+}
+
+function clinicalNoteDetailLabel(row: ClinicalNotesRow) {
+ const text = `${row.title} ${row.detail}`.toLowerCase();
+ if (/\b(timing|level|schedule|dose change|stable|days?)\b/.test(text)) return "Timing";
+ if (/\b(escalation|escalate|urgent|toxicity|trigger|vomiting|confusion|ataxia|tremor)\b/.test(text)) {
+ return "Escalate";
+ }
+ if (/\b(baseline|confirm|record|document|check|review)\b/.test(text)) return "Action";
+ return "Note";
+}
+
+function ClinicalNoteDetailCard({ row }: { row: ClinicalNotesRow }) {
+ const detail = sentenceCaseClinicalNoteDetail(row.detail);
+ return (
+
+
+ {clinicalNoteDetailLabel(row)}:
+ {detail}
+
+
+ );
+}
+
+function clinicalNotesTableEvidenceCount(answer: RagAnswer) {
+ return (answer.visualEvidence ?? answer.smartPanel?.visualEvidence ?? []).filter(
+ (item) => item.accessibleTableMarkdown || item.tableRows?.length,
+ ).length;
+}
+
+function clinicalNotesRowsForTab(sections: ClinicalDetailSection[], tab: ClinicalNotesTabId) {
+ const meta = clinicalNotesTabMeta[tab];
+ const rows: ClinicalNotesRow[] = [];
+ let sourceIndex = 1;
+
+ for (const section of sections) {
+ const sectionText = `${section.title} ${section.items.join(" ")}`.toLowerCase();
+ const hasMonitoringText =
+ (tab === "actions" || tab === "essentials") &&
+ /\b(monitor|screen|level|fbc|anc|metabolic|renal|thyroid|function)\b/i.test(sectionText);
+ const hasSafetyText =
+ tab === "safety" &&
+ /\b(toxicity|toxic|urgent|caution|contraindication|red flag|escalat|warning|review due)\b/i.test(sectionText);
+ if (!meta.sectionIds.includes(section.id) && !hasMonitoringText) {
+ if (!hasSafetyText) continue;
+ }
+ if (tab === "essentials" && section.id === "action" && rows.length >= 2) {
+ continue;
+ }
+ const tone: ClinicalNotesRow["tone"] = section.id === "escalation" || section.id === "cautions" ? "warn" : "safe";
+
+ for (const item of section.items.slice(0, 4)) {
+ if (section.tables?.length && /\b(table|showing domains|table showing)\b/i.test(item)) continue;
+ const title = clinicalNoteTitleFromItem(item, section, rows.length);
+ const fragments = splitClinicalNoteFragments(item, section, title);
+ if (fragments) {
+ for (const fragment of fragments) {
+ const fragmentTitle = clinicalNoteTitleFromFragment(fragment);
+ rows.push({
+ id: `${tab}:${section.id}:${rows.length}:${fragmentTitle}`,
+ title: fragmentTitle,
+ detail: fragment,
+ sourceIndex: sourceIndex++,
+ tone: clinicalNoteToneForText(fragment, tone),
+ });
+ }
+ } else {
+ rows.push({
+ id: `${tab}:${section.id}:${rows.length}:${title}`,
+ title,
+ detail: clinicalNoteDetailFromItem(item, title),
+ sourceIndex: sourceIndex++,
+ tone: clinicalNoteToneForText(item, tone),
+ });
+ }
+ }
+ }
+
+ return rows.slice(0, 6);
+}
+
+function clinicalNotesAvailableTabs(sections: ClinicalDetailSection[]) {
+ return (Object.keys(clinicalNotesTabMeta) as ClinicalNotesTabId[])
+ .map((id) => ({ id, ...clinicalNotesTabMeta[id], count: clinicalNotesRowsForTab(sections, id).length }))
+ .filter((tab) => tab.count > 0);
+}
+
+function clinicalNotesDetailSectionsForAnswer(answer: RagAnswer, viewMode: AnswerViewMode) {
+ const sections =
+ viewMode === "high_yield" ? buildHighYieldClinicalOutputSections(answer) : buildClinicalOutputSections(answer);
+ const primaryAnswer = plainAnswerText(answer.answer);
+ return sortClinicalDetailSections(
+ sections
+ .filter((section) => section.id !== "verify-source" && section.id !== "bottom-line")
+ .map((section) => ({
+ ...section,
+ items: displayItemsForClinicalDetailSection(section, primaryAnswer, false),
+ }))
+ .filter((section) => section.items.length > 0),
+ );
+}
+
+export function clinicalNotesDisplayCountForAnswer(answer: RagAnswer, viewMode: AnswerViewMode, fallback: number) {
+ const tabs = clinicalNotesAvailableTabs(clinicalNotesDetailSectionsForAnswer(answer, viewMode));
+ const largestTabCount = tabs.reduce((largest, tab) => Math.max(largest, tab.count), 0);
+ return Math.max(1, largestTabCount || fallback);
+}
+
+export function ClinicalNotesChecklistPanel({
+ answer,
+ viewMode,
+ evidenceMapRows,
+ bestSource,
+ copied,
+ onCopy,
+ onOpenTables,
+}: {
+ answer: RagAnswer;
+ viewMode: AnswerViewMode;
+ evidenceMapRows: AnswerEvidenceMapRow[];
+ bestSource: BestSourceRecommendation | null;
+ copied: boolean;
+ onCopy: () => void;
+ onOpenTables?: () => void;
+}) {
+ const detailSections = clinicalNotesDetailSectionsForAnswer(answer, viewMode);
+ const tabs = clinicalNotesAvailableTabs(detailSections);
+ const defaultTab = tabs.find((tab) => tab.id === "actions")?.id ?? tabs[0]?.id ?? "actions";
+ const [requestedTab, setRequestedTab] = useState(defaultTab);
+ const activeTab = tabs.some((tab) => tab.id === requestedTab) ? requestedTab : defaultTab;
+ const rows = clinicalNotesRowsForTab(detailSections, activeTab);
+ const tableEvidenceCount = clinicalNotesTableEvidenceCount(answer);
+ const [added, setAdded] = useState(false);
+ const warningRows = clinicalNotesRowsForTab(detailSections, "safety");
+ const warningCount = warningRows.filter((row) => row.tone === "warn").length || warningRows.length;
+
+ if (!tabs.length || rows.length === 0) {
+ return (
+
+ );
+ }
+
+ const activeMeta = clinicalNotesTabMeta[activeTab];
+
+ return (
+
+
+
+ {tabs.map((tab) => {
+ const selected = tab.id === activeTab;
+ return (
+ setRequestedTab(tab.id)}
+ className={cn(
+ "inline-flex min-h-11 min-w-0 items-center justify-center gap-1.5 rounded-md px-2 text-xs font-semibold leading-none transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]",
+ selected
+ ? "bg-[color:var(--clinical-accent)] text-[color:var(--clinical-accent-contrast)] shadow-[var(--shadow-tight)]"
+ : "text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text)]",
+ )}
+ >
+ {tab.label}
+
+ {tab.count}
+
+
+ );
+ })}
+
+
+
+
+
+ {activeMeta.label} ({rows.length})
+
+ {tableEvidenceCount > 0 && onOpenTables ? (
+
+
+ Tables
+
+ ) : null}
+
+
+
+ {rows.map((row) => {
+ const hasDistinctDetail = clinicalNoteHasDistinctDetail(row);
+ const RowIcon = row.tone === "warn" ? AlertCircle : activeTab === "actions" ? Activity : CheckCircle2;
+ return (
+
+
+
+
+
+
+
+ {row.title}
+
+
+ {row.tone === "warn" ? "Review" : activeTab === "actions" ? "Action" : "Source"}
+
+
+ {hasDistinctDetail ? (
+
{row.detail}
+ ) : null}
+
+
+
+ S{row.sourceIndex}
+
+
+
+
+ );
+ })}
+
+
+ {warningCount > 0 && activeTab !== "safety" ? (
+ setRequestedTab("safety")}
+ className="mt-3 grid min-h-[58px] grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 rounded-lg border border-[color:var(--warning-border)] bg-[color:var(--warning-soft)]/45 px-3 py-2 text-left text-[color:var(--warning)] shadow-[var(--shadow-inset)] transition hover:bg-[color:var(--warning-soft)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]"
+ >
+
+
+ Safety preview ({warningCount})
+ Review toxicity symptoms
+
+ S1
+
+ ) : null}
+
+
+
+ {bestSource ? (
+
+
+ Source
+
+ ) : (
+
+
+ Source
+
+ )}
+
+
+ {copied ? "Copied" : "Copy"}
+
+
setAdded(true)}
+ className="inline-flex min-h-11 items-center justify-center gap-1.5 px-2 text-[11px] font-semibold text-[color:var(--primary)]"
+ >
+
+ {added ? "Added" : "Add"}
+
+
+
+
+ );
+}
+
+export function SafetyFindingsPanel({ findings }: { findings: ReturnType }) {
+ if (findings.length === 0) return null;
+
+ return (
+
+
+
+ {findings.map((finding, index) => (
+
+
+
+ {finding.label}
+
+
+
+ Source
+
+
+ {finding.text}
+
+ {formatCitationLabel(finding.citation)}
+
+
+ ))}
+
+
+ );
+}
+
+function EvidenceGapPanel({
+ relevance,
+ sources,
+ query,
+}: {
+ relevance?: EvidenceRelevance | null;
+ sources: SearchResult[];
+ query: string;
+}) {
+ if (!relevance || relevance.isSourceBacked) return null;
+ const closestSources = sources.slice(0, 3);
+ const found = relevance.matchedTerms.length
+ ? relevance.matchedTerms.slice(0, 6).join(", ")
+ : "Only weak neighboring passages were retrieved.";
+ const missing = relevance.missingTerms.length
+ ? relevance.missingTerms.slice(0, 6).join(", ")
+ : "No direct indexed passage covered the full question.";
+
+ return (
+
+ }
+ />
+
+
+ What was found
+ {found}
+
+
+
+ What was not found
+
+ {missing}
+
+
+ Closest sources
+ {closestSources.length ? (
+
+ {closestSources.map((source) => (
+
+
+ {cleanDisplayTitle(source.title)}
+
+ ))}
+
+ ) : (
+ No nearby indexed sources were returned.
+ )}
+
+
+
+ Suggested next search/upload
+
+
+ Try a narrower query using the missing terms, scope to a likely document, or upload/index the guideline that
+ directly covers "{query.trim()}".
+
+
+
+
+ );
+}
+
+function EvidenceCounts({
+ answer,
+ sourceSummary,
+ sourceCount,
+}: {
+ answer: RagAnswer;
+ sourceSummary?: EvidenceSummary;
+ sourceCount: number;
+}) {
+ const counts = [
+ {
+ label: "Citations",
+ value: answer.citations.length,
+ },
+ {
+ label: "Quotes",
+ value: answer.quoteCards?.length ?? sourceSummary?.quote_count ?? 0,
+ },
+ {
+ label: "Images",
+ value:
+ answer.visualEvidence?.length ?? answer.smartPanel?.visualEvidence?.length ?? sourceSummary?.image_count ?? 0,
+ },
+ {
+ label: "Passages",
+ value: sourceCount || sourceSummary?.total_sources || 0,
+ },
+ ];
+
+ return (
+
+ {counts.map((item) => (
+
+
{item.value}
+
{item.label}
+
+ ))}
+
+ );
+}
+
+function AnswerSourceStatus({
+ source,
+ weakEvidence,
+}: {
+ source: BestSourceRecommendation | null | undefined;
+ weakEvidence: boolean;
+}) {
+ const metadata = source?.source_metadata;
+ return (
+
+
+
+ {weakEvidence ? (
+
+ Evidence support is limited. Treat this as a source-finding result until the linked passage is verified.
+
+ ) : null}
+
+ );
+}
+
+function EvidenceSummaryCard({
+ answer,
+ bestSource,
+ grounded,
+ relevance,
+ sourceSummary,
+ weakEvidence,
+ sources,
+ gaps,
+ onScopeDocument,
+ compact = false,
+ supporting = false,
+}: {
+ answer: RagAnswer;
+ bestSource: BestSourceRecommendation | null;
+ grounded: boolean;
+ relevance?: EvidenceRelevance | null;
+ sourceSummary?: EvidenceSummary;
+ weakEvidence: boolean;
+ sources: SearchResult[];
+ gaps: ConflictOrGap[];
+ onScopeDocument: (documentId: string) => void;
+ compact?: boolean;
+ supporting?: boolean;
+}) {
+ const sourceLabel = relevance && !relevance.isSourceBacked ? "Closest source" : "Top source";
+ const supportLabel = relevanceChipLabel(relevance, grounded);
+ const sourceStrength = bestSource?.source_strength ?? sourceSummary?.source_strength ?? "none";
+ const gapMessage =
+ gaps[0]?.message ??
+ (!relevance?.isSourceBacked && relevance?.supportReason
+ ? relevance.supportReason
+ : (sourceSummary?.summary ?? null));
+
+ return (
+
+
+
+
Evidence review
+
+ {weakEvidence
+ ? "Verify before relying on this answer."
+ : "Source status and retrieved evidence at a glance."}
+
+
+
+
+
+
+
+
+
+ {bestSource ? (
+
+
+
+
+ {sourceLabel}
+
+
+ {bestSource.title}
+
+
+ page {bestSource.page_number ?? "n/a"} · {sourceStrength} support
+
+
+
+ {Math.round(Math.max(0, Math.min(1, bestSource.score)) * 100)}% match
+
+
+
+ “{bestSource.snippet}”
+
+
+
+ Open source
+
+
+ onScopeDocument(bestSource.document_id)}
+ className={cn(floatingControl, "min-h-[44px] px-3 text-xs")}
+ aria-label={`Search only ${bestSource.title}`}
+ >
+
+ Add scope
+
+
+
+ ) : (
+
+ )}
+
+ {gapMessage ? (
+
+
Coverage note
+
{gapMessage}
+
+ ) : null}
+
+
+
+ {supportLabel}. Verify source before copying, including any clinical draft text.
+
+
+ );
+}
+
+export function compactEvidenceSummary(
+ answer: RagAnswer,
+ sources: SearchResult[],
+ sourceSummary?: EvidenceSummary,
+ renderModel?: AnswerRenderModel,
+) {
+ const support =
+ renderModel?.trust === "high"
+ ? "Strong support"
+ : renderModel?.trust === "medium"
+ ? "Supported"
+ : renderModel?.trust === "low"
+ ? "Limited support"
+ : "Review support";
+ const claimCount = renderModel?.evidenceRows.length || answer.answerSections?.length || answer.citations.length;
+ const quoteCount = renderModel?.quoteCards.length ?? answer.quoteCards?.length ?? sourceSummary?.quote_count ?? 0;
+ const tableCount = (renderModel?.visualEvidence ?? answer.visualEvidence ?? []).filter(
+ (item) => item.accessibleTableMarkdown || item.tableRows?.length,
+ ).length;
+ const sourceCount = renderModel?.primarySources.length || sourceSummary?.total_sources || sources.length;
+ const countParts = [
+ claimCount > 0 ? `${claimCount} claim${claimCount === 1 ? "" : "s"}` : null,
+ quoteCount > 0 ? `${quoteCount} quote${quoteCount === 1 ? "" : "s"}` : null,
+ tableCount > 0 ? `${tableCount} table${tableCount === 1 ? "" : "s"}` : null,
+ ].filter((part): part is string => Boolean(part));
+
+ if (countParts.length === 0 && sourceCount > 0) {
+ countParts.push(`${sourceCount} source${sourceCount === 1 ? "" : "s"}`);
+ }
+
+ return [support, ...countParts].join(" · ");
+}
+
+export type EvidenceTabName = "Claims" | "Quotes" | "Tables" | "Images" | "Gaps";
+
+function renderModelAllows(renderModel: AnswerRenderModel, block: AnswerRenderModel["allowedBlocks"][number]) {
+ return renderModel.allowedBlocks.includes(block);
+}
+
+export function evidenceTabOrder(_answer: RagAnswer, renderModel: AnswerRenderModel): EvidenceTabName[] {
+ const order: EvidenceTabName[] = ["Claims", "Quotes", "Tables", "Images", "Gaps"];
+ return order.filter((tab) => {
+ if (tab === "Tables") {
+ return (
+ renderModelAllows(renderModel, "visualEvidence") &&
+ renderModel.visualEvidence.some((item) => item.accessibleTableMarkdown || item.tableRows?.length)
+ );
+ }
+ if (tab === "Images") return renderModelAllows(renderModel, "visualEvidence");
+ if (tab === "Quotes") return renderModelAllows(renderModel, "quoteCards");
+ if (tab === "Gaps") return renderModel.warnings.length > 0;
+ return renderModelAllows(renderModel, "evidenceMap") || renderModelAllows(renderModel, "reviewSources");
+ });
+}
+
+export function evidenceTabCount({
+ tab,
+ sources,
+ visualEvidence,
+ answerEvidenceMapRows,
+ renderModel,
+}: {
+ tab: EvidenceTabName;
+ sources: SearchResult[];
+ visualEvidence: VisualEvidenceCard[];
+ answerEvidenceMapRows: AnswerEvidenceMapRow[];
+ renderModel: AnswerRenderModel;
+}) {
+ if (tab === "Tables") {
+ return visualEvidence.filter((item) => item.accessibleTableMarkdown || item.tableRows?.length).length;
+ }
+ if (tab === "Claims")
+ return (
+ answerEvidenceMapRows.length ||
+ renderModel.evidenceRows.length ||
+ sources.length ||
+ renderModel.primarySources.length
+ );
+ if (tab === "Images") return visualEvidence.length;
+ if (tab === "Quotes") return renderModel.quoteCards.length;
+ return renderModel.warnings.length;
+}
+
+export function clinicalNotesCount(answer: RagAnswer) {
+ return buildHighYieldClinicalOutputSections(answer).filter((section) =>
+ ["action", "escalation", "thresholds", "cautions", "monitoring", "medication", "source-gap"].includes(section.id),
+ ).length;
+}
+
+export function answerHasCentralTable(answer: RagAnswer) {
+ return (
+ answer.queryClass === "table_threshold" ||
+ answer.responseMode === "threshold_table" ||
+ Boolean(answer.visualEvidence?.some((item) => item.accessibleTableMarkdown || item.tableRows?.length))
+ );
+}
+
+export function primaryVisualTable(answer: RagAnswer) {
+ return answer.visualEvidence?.find((item) => item.accessibleTableMarkdown || item.tableRows?.length) ?? null;
+}
+
+type RenderModelPdfSource = {
+ document_id: string;
+ title: string;
+ file_name: string;
+ page_number: number | null;
+ chunk_id: string | null;
+};
+
+function uniquePdfSourcesForRenderModel(renderModel: AnswerRenderModel): RenderModelPdfSource[] {
+ return renderModel.primarySources.map((source) => ({
+ document_id: source.document_id,
+ title: source.title,
+ file_name: source.file_name,
+ page_number: source.page_number,
+ chunk_id: source.chunk_id,
+ }));
+}
+
+function queryModeLabel(mode: ClinicalQueryMode) {
+ return clinicalQueryModeOptions.find((option) => option.value === mode)?.label ?? mode.replaceAll("_", " ");
+}
+
+function AnswerInsightBar({
+ answer,
+ bestSource,
+ relevance,
+ queryMode,
+ sourceGovernanceWarnings,
+}: {
+ answer: RagAnswer;
+ bestSource: BestSourceRecommendation | null;
+ relevance?: EvidenceRelevance | null;
+ queryMode: ClinicalQueryMode;
+ sourceGovernanceWarnings: SourceGovernanceWarning[];
+}) {
+ const frontendGovernanceWarnings = frontendSourceGovernanceWarnings(sourceGovernanceWarnings);
+ const metadata = normalizeSourceMetadata(
+ bestSource?.source_metadata ?? answer.sources?.[0]?.source_metadata ?? answer.citations?.[0]?.source_metadata,
+ );
+ const modeLabel =
+ answer.smartApiPlan?.displayMode?.replaceAll("_", " ") ??
+ answer.responseMode?.replaceAll("_", " ") ??
+ queryModeLabel(queryMode);
+ const sourceCount = answer.evidenceSummary?.total_sources ?? answer.sources?.length ?? answer.citations.length;
+ const support = relevanceChipLabel(relevance ?? answer.relevance, answer.grounded);
+ const sourceStatus = frontendGovernanceWarnings.length
+ ? `${frontendGovernanceWarnings.length} source status note${frontendGovernanceWarnings.length === 1 ? "" : "s"}`
+ : sourceStatusLabel(metadata);
+ const retrievalGate = answer.retrievalDiagnostics?.gateStatus;
+ const items = [
+ { label: "Mode", value: modeLabel, icon: SlidersHorizontal },
+ {
+ label: "Support",
+ value: support,
+ icon: hasStrongRelevanceIcon(relevance ?? answer.relevance, answer.grounded) ? CheckCircle2 : AlertCircle,
+ },
+ { label: "Sources", value: String(sourceCount), icon: FileText },
+ { label: "Confidence", value: answer.confidence, icon: Target },
+ {
+ label: "Retrieval",
+ value: retrievalGate ? `${retrievalGate} gate` : "Not logged",
+ icon: retrievalGate === "blocked" ? ShieldAlert : CheckCircle2,
+ },
+ { label: "Status", value: `${sourceStatus} / ${validationStatusLabel(metadata)}`, icon: BookOpen },
+ ];
+
+ return (
+
+ {items.map((item) => {
+ const Icon = item.icon;
+ return (
+
+
+
+ {item.label}
+
+ {item.value}
+
+ );
+ })}
+
+ );
+}
+
+function EvidenceVerificationStrip({
+ answer,
+ bestSource,
+ sourceSummary,
+ weakEvidence,
+ governanceWarningCount,
+}: {
+ answer: RagAnswer;
+ bestSource: BestSourceRecommendation | null;
+ sourceSummary?: EvidenceSummary | null;
+ weakEvidence: boolean;
+ governanceWarningCount: number;
+}) {
+ const metadata = normalizeSourceMetadata(
+ bestSource?.source_metadata ?? answer.sources?.[0]?.source_metadata ?? answer.citations?.[0]?.source_metadata,
+ );
+ const sourceCount = sourceSummary?.total_sources ?? answer.sources?.length ?? answer.citations.length;
+ const citationCount = answer.citations.length;
+ const gapCount = answer.conflictsOrGaps?.length ?? answer.smartPanel?.conflictsOrGaps?.length ?? 0;
+ const retrievalGateBlocked = answer.retrievalDiagnostics?.gateStatus === "blocked";
+ const checks = [
+ {
+ label: "Citations",
+ value: citationCount ? `${citationCount} citation${citationCount === 1 ? "" : "s"}` : "None",
+ ready: citationCount > 0,
+ },
+ {
+ label: "Sources",
+ value: `${sourceCount} source${sourceCount === 1 ? "" : "s"}`,
+ ready: sourceCount > 0,
+ },
+ {
+ label: "Source status",
+ value: sourceStatusLabel(metadata),
+ ready: metadata.document_status === "current" && !governanceWarningCount,
+ },
+ {
+ label: "Retrieval gate",
+ value: retrievalGateBlocked ? "Blocked for low signal" : answer.retrievalDiagnostics ? "Passed" : "Not available",
+ ready: !retrievalGateBlocked,
+ },
+ {
+ label: "Gaps",
+ value: governanceWarningCount
+ ? `${governanceWarningCount} status note${governanceWarningCount === 1 ? "" : "s"}`
+ : gapCount
+ ? `${gapCount} gap${gapCount === 1 ? "" : "s"}`
+ : "None",
+ ready: !weakEvidence && !gapCount && !governanceWarningCount,
+ },
+ ];
+
+ return (
+
+
+ {checks.map((check) => (
+
+
+ {check.ready ? (
+
+ ) : (
+
+ )}
+
+ {check.label}
+
+
+
{check.value}
+
+ ))}
+
+
+
+ Pinned source
+
+ {bestSource ? (
+ <>
+ {bestSource.title}
+
+ >
+ ) : (
+ No pinned source yet
+ )}
+
+
+ );
+}
+
+const answerFeedbackOptions: Array<{
+ type: AnswerFeedbackType;
+ label: string;
+ icon: typeof CheckCircle2;
+ tone: "success" | "warning" | "danger" | "neutral";
+}> = [
+ { type: "verified", label: "Verified", icon: CheckCircle2, tone: "success" },
+ { type: "needs_correction", label: "Needs correction", icon: AlertCircle, tone: "warning" },
+ { type: "source_insufficient", label: "Source insufficient", icon: ShieldAlert, tone: "warning" },
+ { type: "wrong_source", label: "Wrong source", icon: FileText, tone: "danger" },
+ { type: "missing_source", label: "Missing source", icon: Search, tone: "warning" },
+ { type: "unsupported_answer", label: "Unsupported answer", icon: ShieldAlert, tone: "danger" },
+ { type: "numeric_error", label: "Numeric error", icon: Target, tone: "danger" },
+ { type: "outdated_guidance", label: "Outdated guidance", icon: RefreshCw, tone: "warning" },
+];
+
+function feedbackToneClass(tone: "success" | "warning" | "danger" | "neutral") {
+ if (tone === "success") return toneSuccess;
+ if (tone === "warning") return toneWarning;
+ if (tone === "danger") return toneDanger;
+ return toneNeutral;
+}
+
+export function AnswerFeedbackPanel({
+ pending,
+ onSubmit,
+}: {
+ pending: AnswerFeedbackType | null;
+ onSubmit: (feedbackType: AnswerFeedbackType) => void;
+}) {
+ return (
+
+
+
+
Answer review
+
+ Capture misses for retrieval and RAG evals without changing the answer.
+
+
+ {pending ? (
+
+
+ Saving
+
+ ) : null}
+
+
+ {answerFeedbackOptions.map((item) => {
+ const Icon = item.icon;
+ return (
+ onSubmit(item.type)}
+ className={cn(
+ "inline-flex min-h-[40px] items-center gap-1.5 rounded-lg border px-2.5 text-xs font-semibold transition disabled:cursor-not-allowed disabled:opacity-60",
+ feedbackToneClass(item.tone),
+ )}
+ >
+ {pending === item.type ? : }
+ {item.label}
+
+ );
+ })}
+
+
+ );
+}
+
+function RenderModelSourceList({
+ sources,
+ query,
+ onScopeDocument,
+}: {
+ sources: SourceLink[];
+ query: string;
+ onScopeDocument: (documentId: string) => void;
+}) {
+ if (sources.length === 0) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {sources.map((source, index) => {
+ const metadata = normalizeSourceMetadata(source.sourceMetadata);
+ const snippet = compactSourceSnippet(source.snippet ?? "", { dropTitle: source.title });
+ const openLabel = `Open source ${index + 1}: ${cleanDisplayTitle(source.title)}${query ? ` for ${query}` : ""}`;
+ return (
+
+
+
+
+
+
+ {cleanDisplayTitle(source.title)}
+
+
+ p.{source.page_number ?? "n/a"} · {sourceStatusLabel(metadata)} · {source.sourceStrength} support
+
+
+
+
+ {snippet ? {snippet}
: null}
+
+
+ onScopeDocument(source.document_id)} className={chatMicroAction}>
+
+ Scope document
+
+
+
+ );
+ })}
+
+ );
+}
+
+export function VerificationWorkspace({
+ renderModel,
+ query,
+ answerEvidenceMapRows,
+ pendingFeedback,
+ onSubmitFeedback,
+ onScopeDocument,
+}: {
+ renderModel: AnswerRenderModel;
+ query: string;
+ answerEvidenceMapRows: AnswerEvidenceMapRow[];
+ pendingFeedback: AnswerFeedbackType | null;
+ onSubmitFeedback: (feedbackType: AnswerFeedbackType) => void;
+ onScopeDocument: (documentId: string) => void;
+}) {
+ const verificationSources = renderModel.primarySources.slice(0, renderModel.trust === "unsupported" ? 3 : 6);
+ return (
+
+
+
+
+
Section support map
+
+ Each answer section should resolve back to a linked cited passage before clinical use.
+
+
+
+
+
+
+
+
+
Cited source excerpts
+
+ Open the document to inspect the PDF page and highlighted indexed passage.
+
+
+
+
+
+ );
+}
+
+export function AnswerViewModeControl({
+ value,
+ onChange,
+}: {
+ value: AnswerViewMode;
+ onChange: (mode: AnswerViewMode) => void;
+}) {
+ const modes: Array<{ value: AnswerViewMode; label: string; shortLabel: string; icon: typeof Search }> = [
+ { value: "standard", label: "Standard", shortLabel: "All", icon: ListChecks },
+ { value: "high_yield", label: "High-yield", shortLabel: "Key", icon: Target },
+ { value: "evidence_map", label: "Evidence map", shortLabel: "Map", icon: BookOpen },
+ ];
+
+ return (
+
+ {modes.map((mode) => {
+ const Icon = mode.icon;
+ const active = value === mode.value;
+ return (
+ onChange(mode.value)}
+ aria-pressed={active}
+ aria-label={`Show ${mode.label.toLowerCase()} answer view`}
+ title={mode.label}
+ className={cn(
+ "inline-flex min-h-9 min-w-0 flex-1 basis-[4.75rem] items-center justify-center gap-1.5 rounded-md px-2 text-xs font-semibold transition sm:flex-none sm:basis-auto sm:px-2.5",
+ active
+ ? "bg-[color:var(--primary)] text-[color:var(--primary-contrast)] shadow-sm"
+ : "text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text)]",
+ )}
+ >
+
+ {mode.shortLabel}
+ {mode.label}
+
+ );
+ })}
+
+ );
+}
+
+export const simpleClinicalTableProps = {
+ compact: false,
+ expandOnMobile: true,
+} as const;
+
+function compactEvidenceCell(value: string | null | undefined, max = 140) {
+ const text = value ? value.replace(/\s+/g, " ").trim() : "";
+ return text.length > max ? `${text.slice(0, max - 1).trim()}…` : text;
+}
+
+export function evidenceMapRowsFromRenderModel(renderModel: AnswerRenderModel): AnswerEvidenceMapRow[] {
+ return renderModel.evidenceRows.map((row, index) => ({
+ id: row.id || `${row.source.chunk_id}:${index}`,
+ section: row.section || "Source evidence",
+ detail:
+ sourceTextForCompactDisplay(row.quote || row.source.snippet || row.source.reason || "") ||
+ cleanDisplayTitle(row.source.title),
+ supportLevel: row.supportLevel || row.source.sourceStrength,
+ citationCount: 1,
+ sourceStatus:
+ row.source.sourceStrength === "none" ? "Source requires review" : `${row.source.sourceStrength} source support`,
+ bestSourceLabel: row.source.label,
+ bestLinkedPassage: row.quote || row.source.snippet || row.source.reason,
+ href: row.source.href,
+ }));
+}
+
+export function EvidenceMapTable({ rows }: { rows: AnswerEvidenceMapRow[] }) {
+ if (rows.length === 0) {
+ return ;
+ }
+
+ const tableRows = rows.map((row) => [
+ compactEvidenceCell(row.section),
+ row.supportLevel,
+ String(row.citationCount),
+ compactEvidenceCell(row.sourceStatus),
+ compactEvidenceCell(row.bestSourceLabel, 72),
+ row.bestLinkedPassage || "Open source passage.",
+ ]);
+ const linkedRows = rows.filter((row) => row.href);
+
+ return (
+
+
+ {linkedRows.length ? (
+
+ {linkedRows.map((row) => (
+
+
+ {row.section}
+ {row.bestSourceLabel}
+
+
+ Open source
+
+
+
+ ))}
+
+ ) : null}
+
+ );
+}
+
+export function AnswerSafetyNotice({
+ demoMode,
+ weakEvidence = false,
+ retrievalDiagnostics,
+}: {
+ demoMode: boolean;
+ weakEvidence?: boolean;
+ retrievalDiagnostics?: RagAnswer["retrievalDiagnostics"];
+}) {
+ const retrievalGateBlocked = retrievalDiagnostics?.gateStatus === "blocked";
+ return (
+
+
+ {weakEvidence
+ ? "Weak source support; verify the linked source before relying on this answer."
+ : "Draft only; verify source first before pasting into the medical record."}
+
+ {retrievalGateBlocked ? (
+
+ Retrieval confidence gate was triggered (low-confidence retrieval signal). Expand evidence details before
+ using this result.
+
+ ) : null}
+ {demoMode ? (
+
+ Synthetic demo only: this is not clinical guidance.
+
+ ) : null}
+
+ );
+}
+
+export function QuoteCards({
+ quotes,
+ copiedQuotes,
+ onCopyQuotes,
+ onFollowUp,
+ onScopeDocument,
+}: {
+ quotes: QuoteCard[];
+ copiedQuotes: boolean;
+ onCopyQuotes: () => void;
+ onFollowUp?: (quote: QuoteCard) => void;
+ onScopeDocument: (documentId: string) => void;
+}) {
+ return (
+
+ 0 ? (
+
+ ) : null
+ }
+ />
+ {quotes.length === 0 ? (
+
+ ) : (
+
+ {quotes.map((quote, index) => {
+ const quoteText = sourceTextForVerbatimQuote(quote.quote);
+ const quoteTitle = cleanDisplayTitle(quote.title);
+ return (
+
+
+
+ {index + 1}
+
+
+
+
+ “{quoteText}”
+
+
+
+ {formatCompactCitationLabel(quote)}
+
+
+ {quoteTitle}, page {quote.page_number ?? "n/a"}
+
+
+ onFollowUp(quote) : undefined}
+ divider={false}
+ />
+
+
+
+ );
+ })}
+
+ )}
+
+ );
+}
+
+export function formatQuoteCardsForClipboard(quotes: QuoteCard[]) {
+ return quotes
+ .map((quote, index) =>
+ [
+ // Clean the copied text the same way the card displays it, so clipboard
+ // output never contains internal image-data blocks or glyph artifacts.
+ `${index + 1}. "${sourceTextForVerbatimQuote(quote.quote)}"`,
+ `Source: ${formatCitationLabel(quote)}`,
+ `Link: ${documentCitationHref(quote)}`,
+ ].join("\n"),
+ )
+ .join("\n\n");
+}
diff --git a/src/components/clinical-dashboard/use-mobile-preview-sheet.ts b/src/components/clinical-dashboard/use-mobile-preview-sheet.ts
new file mode 100644
index 000000000..88976d083
--- /dev/null
+++ b/src/components/clinical-dashboard/use-mobile-preview-sheet.ts
@@ -0,0 +1,21 @@
+"use client";
+
+import { useSyncExternalStore } from "react";
+
+const sourcePreviewSheetMediaQuery = "(max-width: 1023px)";
+
+function subscribeToMobilePreviewMedia(callback: () => void) {
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return () => undefined;
+ const media = window.matchMedia(sourcePreviewSheetMediaQuery);
+ media.addEventListener("change", callback);
+ return () => media.removeEventListener("change", callback);
+}
+
+function getMobilePreviewSnapshot() {
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
+ return window.matchMedia(sourcePreviewSheetMediaQuery).matches;
+}
+
+export function useMobilePreviewSheet() {
+ return useSyncExternalStore(subscribeToMobilePreviewMedia, getMobilePreviewSnapshot, () => false);
+}
diff --git a/tests/clinical-dashboard-merge-artifacts.test.ts b/tests/clinical-dashboard-merge-artifacts.test.ts
index 08308d664..a9e0ed411 100644
--- a/tests/clinical-dashboard-merge-artifacts.test.ts
+++ b/tests/clinical-dashboard-merge-artifacts.test.ts
@@ -3,29 +3,42 @@ import { resolve } from "node:path";
import * as ts from "typescript";
import { describe, expect, it } from "vitest";
-const dashboardPath = resolve(process.cwd(), "src/components/ClinicalDashboard.tsx");
-const dashboardSource = readFileSync(dashboardPath, "utf8");
-const dashboardAst = ts.createSourceFile(
- dashboardPath,
- dashboardSource,
- ts.ScriptTarget.Latest,
- true,
- ts.ScriptKind.TSX,
-);
+// The dashboard render surfaces are progressively being extracted from the
+// monolith into src/components/clinical-dashboard/*. Scan every file that now
+// owns a pinned declaration so the guards travel with the code and the
+// absence checks strengthen across the whole set.
+const scannedFiles = [
+ "src/components/ClinicalDashboard.tsx",
+ "src/components/clinical-dashboard/answer-content.tsx",
+].map((relativePath) => {
+ const path = resolve(process.cwd(), relativePath);
+ const source = readFileSync(path, "utf8");
+ return {
+ path,
+ source,
+ ast: ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX),
+ };
+});
+
+type FoundDeclaration = { node: ts.FunctionDeclaration; ast: ts.SourceFile };
-function findFunctionDeclaration(name: string): ts.FunctionDeclaration | null {
- let found: ts.FunctionDeclaration | null = null;
+function findFunctionDeclaration(name: string): FoundDeclaration | null {
+ for (const file of scannedFiles) {
+ let found: ts.FunctionDeclaration | null = null;
- function visit(node: ts.Node) {
- if (ts.isFunctionDeclaration(node) && node.name?.text === name) {
- found = node;
- return;
+ function visit(node: ts.Node) {
+ if (found) return;
+ if (ts.isFunctionDeclaration(node) && node.name?.text === name) {
+ found = node;
+ return;
+ }
+ ts.forEachChild(node, visit);
}
- ts.forEachChild(node, visit);
- }
- visit(dashboardAst);
- return found;
+ visit(file.ast);
+ if (found) return { node: found, ast: file.ast };
+ }
+ return null;
}
function descendantIdentifiers(node: ts.Node) {
@@ -50,8 +63,8 @@ describe("ClinicalDashboard merge-artifact guards", () => {
expect(panel, "ClinicalOutputPanel should remain a local function declaration").not.toBeNull();
if (!panel) throw new Error("ClinicalOutputPanel should remain a local function declaration");
- const panelSource = panel.getText(dashboardAst);
- const panelIdentifiers = descendantIdentifiers(panel);
+ const panelSource = panel.node.getText(panel.ast);
+ const panelIdentifiers = descendantIdentifiers(panel.node);
expect(panelIdentifiers.has("copiedWardNote")).toBe(false);
expect(panelIdentifiers.has("onCopyWardNote")).toBe(false);
@@ -67,7 +80,7 @@ describe("ClinicalDashboard merge-artifact guards", () => {
expect(answer, "NaturalLanguageAnswer should remain a local function declaration").not.toBeNull();
if (!answer) throw new Error("NaturalLanguageAnswer should remain a local function declaration");
- const answerSource = answer.getText(dashboardAst);
+ const answerSource = answer.node.getText(answer.ast);
expect(answerSource).toContain("plain-answer-prose");
expect(answerSource).not.toContain("parseAnswerDisplayContent");
expect(answerSource).not.toContain("AnswerSymbolTile");
diff --git a/tests/rendered-text-formatting.test.ts b/tests/rendered-text-formatting.test.ts
index f123d6d87..7b1779c85 100644
--- a/tests/rendered-text-formatting.test.ts
+++ b/tests/rendered-text-formatting.test.ts
@@ -14,31 +14,38 @@ function componentSource(relativePath: string) {
describe("document-derived text must route through a formatter", () => {
const dashboard = componentSource("ClinicalDashboard.tsx");
const documentViewer = componentSource("DocumentViewer.tsx");
+ // Answer/evidence render surfaces (SourceImage, SourcePreviewContent,
+ // NaturalLanguageAnswer, QuoteCards, EvidenceMapTable, …) now live in extracted
+ // modules. Scan them alongside the monolith so the raw-render guards travel with
+ // the code as it moves out — assertions check the combined dashboard surfaces.
+ const answerContent = componentSource("clinical-dashboard/answer-content.tsx");
+ const evidenceContent = componentSource("clinical-dashboard/evidence-panels.tsx");
+ const dashboardSurfaces = `${dashboard}\n${answerContent}\n${evidenceContent}`;
it("renders exact quotes through the verbatim cleaner, never raw", () => {
// Allow `${quote.quote}` inside template literals (React keys, clipboard text);
// only a bare JSX child `{quote.quote}` is a raw-render regression.
- expect(dashboard).not.toMatch(/(? {
- expect(dashboard).not.toMatch(/\{source\.title\}/);
- expect(dashboard).toContain("cleanDisplayTitle(");
+ expect(dashboardSurfaces).not.toMatch(/\{source\.title\}/);
+ expect(dashboardSurfaces).toContain("cleanDisplayTitle(");
});
it("renders extracted table snippets through a compact formatter, never raw", () => {
- expect(dashboard).not.toMatch(/>\s*\{item\.tableTextSnippet\}\s*);
- expect(dashboard).toContain("sourceTextForCompactDisplay(item.tableTextSnippet)");
+ expect(dashboardSurfaces).not.toMatch(/>\s*\{item\.tableTextSnippet\}\s*);
+ expect(dashboardSurfaces).toContain("sourceTextForCompactDisplay(item.tableTextSnippet)");
});
it("renders source-card snippets through compactSourceSnippet with the card title deduped", () => {
- expect(dashboard).not.toMatch(/(? {
- expect(dashboard).toContain("sourceTextForCompactDisplay(row.quote || row.source.snippet");
+ expect(dashboardSurfaces).toContain("sourceTextForCompactDisplay(row.quote || row.source.snippet");
});
it("renders document-viewer image captions through a formatter, never raw", () => {
@@ -47,13 +54,13 @@ describe("document-derived text must route through a formatter", () => {
});
it("renders visual-evidence titles and alt text through formatters, never raw", () => {
- expect(dashboard).not.toMatch(/(? {
// One call for the rendered blockquote, one for the copy-to-clipboard text.
- const cleanerCalls = dashboard.match(/sourceTextForVerbatimQuote\(quote\.quote\)/g) ?? [];
+ const cleanerCalls = dashboardSurfaces.match(/sourceTextForVerbatimQuote\(quote\.quote\)/g) ?? [];
expect(cleanerCalls.length).toBeGreaterThanOrEqual(2);
});
});