Skip to content
9 changes: 5 additions & 4 deletions docs/process-hardening.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -34,13 +34,14 @@ This document turns the current process review into phased, durable repo practic
- **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 `<ClinicalOutputPanel/>` (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.

- **2026-07-03:** extracted `output-panel.tsx` — `ClinicalOutputPanel` (block 487–688, moved verbatim). Monolith 6084 → 5882 lines. Clean move: empty danger set (no monolith-internal deps) and empty reverse set (the monolith never rendered it — its only consumer is `evidence-panels`'s `ClinicalNotesChecklistPanel`). This **replaced** the evidence-panels↔monolith cycle with an evidence-panels↔output-panel one: `evidence-panels` now imports `ClinicalOutputPanel` from `clinical-dashboard/output-panel` (still imports `clinicalQueryModeOptions` + `type AnswerFeedbackType` from the monolith); `output-panel` imports the detail-helper family + `AnswerViewModeControl`/`EvidenceMapTable` from `evidence-panels`. Retargeted the AST pin (`ClinicalOutputPanel` now resolves via the `scannedFiles` array's `output-panel.tsx` entry) and added `output-panel.tsx` to the `rendered-text-formatting.test.ts` corpus. Stripped 8 now-orphaned monolith imports.

#### Remaining decomposition — hand-off (do on a stable `main`, one module per commit)

The approved move map (`docs/redesign/04-deferred.md` §2) has 3 modules left. Recommended order:
The approved move map (`docs/redesign/04-deferred.md` §2) has 2 modules left. Recommended order:

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`.
1. `visual-evidence.tsx` — `VisualEvidenceStrip`, `InlineTableCard`, `MobileEvidenceSheetContent`, `MobileEvidenceTabPanel`, `UnifiedEvidenceDrawerContent` (+ `supportDotClass`/`supportLabel`/`EvidenceClaimsList`/`EvidenceGapsPanel`/`compactClinicalTableCaption`/`visualEvidenceHeader` helpers, currently just after `WhyThisMatchedPanel`).
2. `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.

Expand Down
211 changes: 0 additions & 211 deletions src/components/ClinicalDashboard.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -62,9 +62,9 @@
answerSurface,
chatMicroAction,
clinicalDivider,
clinicalNotesRow,

Check warning on line 65 in src/components/ClinicalDashboard.tsx

View workflow job for this annotation

GitHub Actions/ verify

'clinicalNotesRow' is defined but never used
cn,
evidenceRow,

Check warning on line 67 in src/components/ClinicalDashboard.tsx

View workflow job for this annotation

GitHub Actions/ verify

'evidenceRow' is defined but never used
EmptyState,
fieldControlPlain,
fieldControlWithIcon,
Expand DownExpand Up@@ -126,7 +126,6 @@
} from "@/components/clinical-dashboard/display-text";
import {
NaturalLanguageAnswer,
plainAnswerText,
ScopeAndGovernanceNotice,
SourceImage,
UserQuestionBubble,
Expand All@@ -135,18 +134,12 @@
AnswerFeedbackPanel,
AnswerSafetyNotice,
AnswerSupportSummaryCard,
AnswerViewModeControl,
answerHasCentralTable,
answerSupportPriority,
ClinicalNotesChecklistPanel,
clinicalDetailContentCount,
clinicalDetailMeta,
clinicalDetailSummaryItems,
clinicalNotesCount,
clinicalNotesDisplayCountForAnswer,
compactEvidenceSummary,
displayItemsForClinicalDetailSection,
EvidenceMapTable,
type EvidenceTabName,
simpleClinicalTableProps,
evidenceMapRowsFromRenderModel,
Expand All@@ -156,7 +149,6 @@
primaryVisualTable,
QuoteCards,
SafetyFindingsPanel,
sortClinicalDetailSections,
VerificationWorkspace,
} from "@/components/clinical-dashboard/evidence-panels";
import { useMobilePreviewSheet } from "@/components/clinical-dashboard/use-mobile-preview-sheet";
Expand DownExpand Up@@ -262,9 +254,9 @@
import {
type AnswerEvidenceMapRow,
type AnswerViewMode,
buildAnswerEvidenceMap,

Check warning on line 257 in src/components/ClinicalDashboard.tsx

View workflow job for this annotation

GitHub Actions/ verify

'buildAnswerEvidenceMap' is defined but never used
buildClinicalOutputSections,

Check warning on line 258 in src/components/ClinicalDashboard.tsx

View workflow job for this annotation

GitHub Actions/ verify

'buildClinicalOutputSections' is defined but never used
buildHighYieldClinicalOutputSections,

Check warning on line 259 in src/components/ClinicalDashboard.tsx

View workflow job for this annotation

GitHub Actions/ verify

'buildHighYieldClinicalOutputSections' is defined but never used
shouldPollForUpdates,
} from "@/lib/ward-output";

Expand DownExpand Up@@ -484,210 +476,7 @@
return navigationHashes.includes(hash as (typeof navigationHashes)[number]) ? hash : "#search";
}

export function ClinicalOutputPanel({
answer,
collapsed = false,
showLead = true,
viewMode = "standard",
onViewModeChange,
evidenceMapRows,
}: {
answer: RagAnswer;
collapsed?: boolean;
showLead?: boolean;
viewMode?: AnswerViewMode;
onViewModeChange?: (mode: AnswerViewMode) => void;
evidenceMapRows?: AnswerEvidenceMapRow[];
}) {
const sections =
viewMode === "high_yield" ? buildHighYieldClinicalOutputSections(answer) : buildClinicalOutputSections(answer);
const rows = evidenceMapRows ?? buildAnswerEvidenceMap(answer);
if (sections.length === 0 && (viewMode !== "evidence_map" || rows.length === 0)) return null;
const leadSection = sections.find((section) => section.id === "bottom-line") ?? sections[0];
const primaryAnswer = plainAnswerText(answer.answer);
const detailSections = sections
.filter((section) => section.id !== "verify-source")
.filter((section) => (showLead ? section.id !== leadSection?.id : section.id !== "bottom-line"))
.map((section) => ({
...section,
items: displayItemsForClinicalDetailSection(section, primaryAnswer, showLead),
}))
.filter((section) => section.items.length > 0 || Boolean(section.tables?.length));
const orderedDetailSections = sortClinicalDetailSections(detailSections);
const summaryItems = clinicalDetailSummaryItems(orderedDetailSections);
const title =
viewMode === "evidence_map"
? "Evidence map"
: viewMode === "high_yield"
? "High-yield clinical details"
: showLead
? "Clinical answer"
: "Structured clinical details";
const description =
viewMode === "evidence_map"
? "Mapped answer sections to linked source support and source status."
: viewMode === "high_yield"
? "Actions, thresholds, cautions, escalation triggers, monitoring, and dose details."
: showLead
? "Dense source-backed structure for review."
: "Adaptive source-backed support below the concise answer.";

const content = (
<section data-testid="clinical-action-view" className={cn(panelSubtle, "p-3 sm:p-4")}>
<div className="flex flex-wrap items-center justify-between gap-3">
<SectionHeading
icon={ListChecks}
title={title}
description={description}
action={onViewModeChange ? <AnswerViewModeControl value={viewMode} onChange={onViewModeChange} /> : undefined}
hideDescriptionOnMobile
compactMobile
/>
</div>
{summaryItems.length ? (
<div
data-testid="clinical-detail-summary"
className="mt-3 grid grid-cols-2 gap-2 sm:flex sm:flex-wrap sm:items-center"
aria-label="High-yield clinical detail summary"
>
{summaryItems.map((item) => (
<span
key={item.label}
className={cn(
subtleStatusPill,
"min-h-12 min-w-0 justify-between gap-2 rounded-lg px-3 py-2 text-left sm:min-h-9",
)}
>
<span className="min-w-0 truncate text-[11px] uppercase tracking-[0.06em]">{item.label}</span>
<span className="shrink-0 text-sm font-bold text-[color:var(--text-heading)]">{item.value}</span>
</span>
))}
</div>
) : null}
{showLead && leadSection ? (
<div className="mt-3 rounded-md border border-[color:var(--primary)]/15 bg-[color:var(--surface-raised)] p-3 shadow-[var(--shadow-inset)]">
<div className="flex items-start gap-2.5">
<span className={cn(iconTilePremium, "h-8 w-8 text-[color:var(--primary)]")}>
<CheckCircle2 className="h-4 w-4" />
</span>
<div className="min-w-0">
<p className="text-xs font-bold uppercase tracking-[0.08em] text-[color:var(--primary)]">
{leadSection.title}
</p>
<p className="mt-1 text-[15px] font-semibold leading-6 text-[color:var(--text-heading)]">
<SafeBoldText text={leadSection.items[0] ?? "Review the source-backed answer and citations."} />
</p>
</div>
</div>
</div>
) : null}
{viewMode === "evidence_map" ? (
<div className="mt-3">
<EvidenceMapTable rows={rows} />
</div>
) : orderedDetailSections.length ? (
<div
className={cn(
"mt-3 grid gap-2 md:grid-cols-2 xl:grid-cols-3",
showLead && "border-t border-[color:var(--border)] pt-3",
)}
>
{orderedDetailSections.map((section) => {
const isWide = section.id === "thresholds" || Boolean(section.tables?.length);
const itemCount = clinicalDetailContentCount(section);
const meta = clinicalDetailMeta(section);
const Icon = meta.icon;
return (
<article
key={section.id}
data-testid="clinical-detail-card"
className={cn(
"min-w-0 rounded-lg border border-[color:var(--border)]/80 bg-[color:var(--surface)] p-3 shadow-[var(--shadow-inset)]",
isWide && "md:col-span-2 xl:col-span-3",
)}
>
<div className="flex min-w-0 items-start justify-between gap-2">
<div className="flex min-w-0 items-start gap-2.5">
<span
className={cn(
"grid h-9 w-9 shrink-0 place-items-center rounded-lg border shadow-[var(--shadow-inset)]",
meta.toneClassName,
)}
aria-hidden="true"
>
<Icon className="h-4 w-4" />
</span>
<div className="min-w-0">
<p className="truncate text-[11px] font-bold uppercase tracking-[0.06em] text-[color:var(--text-soft)]">
{meta.eyebrow}
</p>
<h3 className="truncate text-sm font-semibold text-[color:var(--text-heading)]">
{section.title}
</h3>
</div>
</div>
<span className={cn(metadataPill, "min-h-7 shrink-0 px-2 text-[10px]")}>{itemCount}</span>
</div>
{section.tables?.length ? (
<div className="mt-3 grid gap-3">
{section.tables.map((table) => (
<div key={table.id} data-testid="clinical-detail-table" className="min-w-0 space-y-2">
<AccessibleTable
caption={table.caption}
markdown={table.markdown}
rows={table.rows}
columns={table.columns}
{...simpleClinicalTableProps}
clinicalOnly
dialogTitle={table.caption || "Clinical table"}
/>
</div>
))}
</div>
) : null}
{section.items.length ? (
<ul className="mt-3 grid gap-2 text-[15px] leading-6 text-[color:var(--text)]">
{section.items.map((item, index) => (
<li
key={`${section.id}:${index}:${item.slice(0, 48)}`}
className="grid min-h-10 min-w-0 grid-cols-[auto_minmax(0,1fr)] gap-2 rounded-md border border-[color:var(--border)]/70 bg-[color:var(--surface-raised)] px-3 py-2 shadow-[var(--shadow-inset)] sm:min-h-9"
>
<span
className={cn("mt-1 h-4 w-1 shrink-0 rounded-full", meta.accentClassName)}
aria-hidden="true"
/>
<span className="min-w-0 break-words">
<SafeBoldText text={item} />
</span>
</li>
))}
</ul>
) : null}
</article>
);
})}
</div>
) : null}
</section>
);

if (collapsed) {
return (
<UtilityDrawer
icon={ListChecks}
title="Clinical answer"
summary="Collapsed because direct source support was not found."
mobileSummary="Clinical formats"
>
{content}
</UtilityDrawer>
);
}

return content;
}

function WhyThisMatchedPanel({ sources }: { sources: SearchResult[] }) {

Check warning on line 479 in src/components/ClinicalDashboard.tsx

View workflow job for this annotation

GitHub Actions/ verify

'WhyThisMatchedPanel' is defined but never used
const visibleSources = sources.slice(0, 3);
if (visibleSources.length === 0) return null;

Expand DownExpand Up@@ -1265,7 +1054,7 @@
function MobileEvidenceTabPanel({
tab,
renderModel,
query,

Check warning on line 1057 in src/components/ClinicalDashboard.tsx

View workflow job for this annotation

GitHub Actions/ verify

'query' is defined but never used
visualEvidence,
answerEvidenceMapRows,
copiedQuotes,
Expand DownExpand Up@@ -1338,7 +1127,7 @@
return <EvidenceGapsPanel warnings={renderModel.warnings} />;
}

function UnifiedEvidenceDrawerContent({

Check warning on line 1130 in src/components/ClinicalDashboard.tsx

View workflow job for this annotation

GitHub Actions/ verify

'UnifiedEvidenceDrawerContent' is defined but never used
answer,
renderModel,
query,
Expand DownExpand Up@@ -1531,8 +1320,8 @@
query,
safeAnswerText,
bestSource,
currentRelevance,

Check warning on line 1323 in src/components/ClinicalDashboard.tsx

View workflow job for this annotation

GitHub Actions/ verify

'currentRelevance' is defined but never used
queryMode,

Check warning on line 1324 in src/components/ClinicalDashboard.tsx

View workflow job for this annotation

GitHub Actions/ verify

'queryMode' is defined but never used
sourceGovernanceWarnings,
sourceSummary,
renderModel,
Expand Down
3 changes: 2 additions & 1 deletion src/components/clinical-dashboard/evidence-panels.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,7 +28,8 @@ import {
} from "lucide-react";

import { AccessibleTable } from "@/components/AccessibleTable";
import { ClinicalOutputPanel, clinicalQueryModeOptions, type AnswerFeedbackType } from "@/components/ClinicalDashboard";
import { clinicalQueryModeOptions, type AnswerFeedbackType } from "@/components/ClinicalDashboard";
import { ClinicalOutputPanel } from "@/components/clinical-dashboard/output-panel";
import {
keyClinicalItemsFromSections,
keyClinicalItemsFromTable,
Expand Down
Loading
Loading