Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/process-hardening.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,19 @@ This document turns the current process review into phased, durable repo practic
- Added `src/components/clinical-dashboard/` as the module boundary.
- `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.

#### 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:

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`.

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.

## Phase 4 - Release maturity

Expand Down
128 changes: 2 additions & 126 deletions src/components/ClinicalDashboard.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,10 +26,8 @@
Layers,
ListChecks,
Loader2,
LogIn,
LogOut,
LockKeyhole,
Mail,
Palette,
PanelTop,
Plus,
Expand All@@ -53,7 +51,6 @@
} from "lucide-react";
import {
type CSSProperties,
FormEvent,
memo,
type RefObject,
useCallback,
Expand DownExpand Up@@ -86,9 +83,9 @@
chatMicroAction,
codeText,
clinicalDivider,
clinicalNotesRow,

Check warning on line 86 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 88 in src/components/ClinicalDashboard.tsx

View workflow job for this annotation

GitHub Actions/ verify

'evidenceRow' is defined but never used
evidenceSurface,
EmptyState,
fieldControlPlain,
Expand All@@ -96,7 +93,6 @@
fieldIcon,
floatingControl,
iconTilePremium,
fieldLabel,
metadataPill,
panelSubtle,
primaryControl,
Expand All@@ -120,10 +116,11 @@
toneSuccess,
toneWarning,
} from "@/components/ui-primitives";
import { AUTH_EMAIL_STORAGE_KEY, useAuthSession } from "@/lib/supabase/client";
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 { AuthPanel } from "@/components/clinical-dashboard/auth-panel";
import { useSidebarCollapsed } from "@/components/clinical-dashboard/use-sidebar-collapsed";
import { useTheme } from "@/components/clinical-dashboard/use-theme";
import { StatusBadge, StrengthBadge } from "@/components/clinical-dashboard/badges";
Expand DownExpand Up@@ -297,7 +294,6 @@
return useSyncExternalStore(subscribeToMobilePreviewMedia, getMobilePreviewSnapshot, () => false);
}

const authEmailChangeEvent = "clinical-kb-auth-email-change";
export const recentQueryStorageKey = "clinical-kb-recent-queries";
const documentPageSize = 150;
const activeIndexingPollFallbackMs = 5_000;
Expand DownExpand Up@@ -511,32 +507,6 @@
return navigationHashes.includes(hash as (typeof navigationHashes)[number]) ? hash : "#search";
}

function getAuthEmailSnapshot() {
if (typeof window === "undefined") return "";
try {
return window.localStorage.getItem(AUTH_EMAIL_STORAGE_KEY) ?? "";
} catch {
return "";
}
}

function getServerAuthEmailSnapshot() {
return "";
}

function subscribeAuthEmail(onStoreChange: () => void) {
if (typeof window === "undefined") return () => undefined;
const notify = () => onStoreChange();

window.addEventListener("storage", notify);
window.addEventListener(authEmailChangeEvent, notify);

return () => {
window.removeEventListener("storage", notify);
window.removeEventListener(authEmailChangeEvent, notify);
};
}

const SourceImage = memo(function SourceImage({
endpoint,
caption,
Expand DownExpand Up@@ -1244,7 +1214,7 @@
.slice(0, 5);
}

function KeyClinicalItems({

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

View workflow job for this annotation

GitHub Actions/ verify

'KeyClinicalItems' is defined but never used
sections,
table,
}: {
Expand DownExpand Up@@ -1765,7 +1735,7 @@
return "Note";
}

function ClinicalNoteDetailCard({ row }: { row: ClinicalNotesRow }) {

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

View workflow job for this annotation

GitHub Actions/ verify

'ClinicalNoteDetailCard' is defined but never used
const detail = sentenceCaseClinicalNoteDetail(row.detail);
return (
<div className="mt-2 rounded-md border border-[color:var(--border)] bg-[color:var(--surface-subtle)] px-2.5 py-2 shadow-[var(--shadow-inset)]">
Expand DownExpand Up@@ -2108,7 +2078,7 @@
);
}

function EvidenceGapPanel({

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

View workflow job for this annotation

GitHub Actions/ verify

'EvidenceGapPanel' is defined but never used
relevance,
sources,
query,
Expand DownExpand Up@@ -2263,7 +2233,7 @@
);
}

function EvidenceSummaryCard({

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

View workflow job for this annotation

GitHub Actions/ verify

'EvidenceSummaryCard' is defined but never used
answer,
bestSource,
grounded,
Expand DownExpand Up@@ -2503,7 +2473,7 @@
chunk_id: string | null;
};

function uniquePdfSourcesForRenderModel(renderModel: AnswerRenderModel): RenderModelPdfSource[] {

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

View workflow job for this annotation

GitHub Actions/ verify

'uniquePdfSourcesForRenderModel' is defined but never used
return renderModel.primarySources.map((source) => ({
document_id: source.document_id,
title: source.title,
Expand All@@ -2517,7 +2487,7 @@
return clinicalQueryModeOptions.find((option) => option.value === mode)?.label ?? mode.replaceAll("_", " ");
}

function AnswerInsightBar({

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

View workflow job for this annotation

GitHub Actions/ verify

'AnswerInsightBar' is defined but never used
answer,
bestSource,
relevance,
Expand DownExpand Up@@ -2586,7 +2556,7 @@
);
}

function EvidenceVerificationStrip({

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

View workflow job for this annotation

GitHub Actions/ verify

'EvidenceVerificationStrip' is defined but never used
answer,
bestSource,
sourceSummary,
Expand DownExpand Up@@ -3318,7 +3288,7 @@
return content;
}

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

Check warning on line 3291 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@@ -4525,100 +4495,6 @@
);
}

function AuthPanel() {
const { status, error, isConfigured, signInWithEmail, signOut, session } = useAuthSession();
const savedEmail = useSyncExternalStore(subscribeAuthEmail, getAuthEmailSnapshot, getServerAuthEmailSnapshot);
const [draftEmail, setDraftEmail] = useState<string | null>(null);
const email = draftEmail ?? savedEmail;
const busy = status === "loading";
const isExpired = status === "expired";

async function submit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!email.trim()) return;
await signInWithEmail(email.trim());
}

if (!isConfigured) {
return (
<div className={cn(panelSubtle, "p-3")}>
<div className="flex items-start gap-3">
<ShieldAlert className="mt-0.5 h-5 w-5 shrink-0 text-[color:var(--warning)]" />
<div>
<p className="text-sm font-semibold text-[color:var(--text)]">Real-data sign-in unavailable</p>
<p className={cn("mt-1 text-[15px] leading-6", textMuted)}>
Configure the Supabase public URL and publishable key before using private documents.
</p>
</div>
</div>
</div>
);
}

if (status === "authenticated") {
return (
<div className={cn(panelSubtle, "p-3")}>
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<p className="text-sm font-semibold text-[color:var(--text)]">Signed in for private documents</p>
<p className={cn("mt-1 text-xs leading-5", textMuted)}>{session?.user.email ?? "Authenticated session"}</p>
</div>
<button type="button" onClick={signOut} className={cn(floatingControl, "px-3 text-xs")}>
<LogOut className="h-4 w-4" />
Sign out
</button>
</div>
</div>
);
}

return (
<form onSubmit={submit} className={cn(panelSubtle, "space-y-3 p-3")}>
<div className="flex items-start gap-3">
<LogIn className="mt-0.5 h-5 w-5 shrink-0 text-[color:var(--primary)]" />
<div>
<p className="text-sm font-semibold text-[color:var(--text)]">
{isExpired ? "Sign-in link expired" : "Sign in for private documents"}
</p>
<p className={cn("mt-1 text-[15px] leading-6", textMuted)}>
{isExpired
? "Send a fresh link if this one failed or already timed out."
: "Real-data search, upload, and source previews require a Supabase Auth session."}
</p>
</div>
</div>
<label className="block">
<span className={fieldLabel}>Email address</span>
<div className="relative">
<Mail className={fieldIcon} />
<input
type="email"
value={email}
onChange={(event) => setDraftEmail(event.target.value)}
placeholder="you@example.com"
className={fieldControlWithIcon}
/>
</div>
</label>
<button type="submit" disabled={busy || !email.trim()} className={cn(primaryControl, "w-full")}>
{busy ? <Loader2 className="h-4 w-4 animate-spin" /> : <Mail className="h-4 w-4" />}
Send sign-in link
</button>
{error && (
<p
role="alert"
className={cn(
"rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-inset)] p-3 text-xs",
textMuted,
)}
>
{error}
</p>
)}
</form>
);
}

const tagQualityTone: Record<SmartDocumentTagQualityIssueKind, string> = {
noisy: toneDanger,
duplicate: toneWarning,
Expand Down
138 changes: 138 additions & 0 deletions src/components/clinical-dashboard/auth-panel.tsx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
"use client";

import { type FormEvent, useState, useSyncExternalStore } from "react";
import { Loader2, LogIn, LogOut, Mail, ShieldAlert } from "lucide-react";

import { AUTH_EMAIL_STORAGE_KEY, useAuthSession } from "@/lib/supabase/client";
import {
cn,
fieldControlWithIcon,
fieldIcon,
fieldLabel,
floatingControl,
panelSubtle,
primaryControl,
textMuted,
} from "@/components/ui-primitives";

const authEmailChangeEvent = "clinical-kb-auth-email-change";

function getAuthEmailSnapshot() {
if (typeof window === "undefined") return "";
try {
return window.localStorage.getItem(AUTH_EMAIL_STORAGE_KEY) ?? "";
} catch {
return "";
}
}

function getServerAuthEmailSnapshot() {
return "";
}

function subscribeAuthEmail(onStoreChange: () => void) {
if (typeof window === "undefined") return () => undefined;
const notify = () => onStoreChange();

window.addEventListener("storage", notify);
window.addEventListener(authEmailChangeEvent, notify);

return () => {
window.removeEventListener("storage", notify);
window.removeEventListener(authEmailChangeEvent, notify);
};
}

export function AuthPanel() {
const { status, error, isConfigured, signInWithEmail, signOut, session } = useAuthSession();
const savedEmail = useSyncExternalStore(subscribeAuthEmail, getAuthEmailSnapshot, getServerAuthEmailSnapshot);
const [draftEmail, setDraftEmail] = useState<string | null>(null);
const email = draftEmail ?? savedEmail;
const busy = status === "loading";
const isExpired = status === "expired";

async function submit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!email.trim()) return;
await signInWithEmail(email.trim());
}

if (!isConfigured) {
return (
<div className={cn(panelSubtle, "p-3")}>
<div className="flex items-start gap-3">
<ShieldAlert className="mt-0.5 h-5 w-5 shrink-0 text-[color:var(--warning)]" />
<div>
<p className="text-sm font-semibold text-[color:var(--text)]">Real-data sign-in unavailable</p>
<p className={cn("mt-1 text-[15px] leading-6", textMuted)}>
Configure the Supabase public URL and publishable key before using private documents.
</p>
</div>
</div>
</div>
);
}

if (status === "authenticated") {
return (
<div className={cn(panelSubtle, "p-3")}>
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<p className="text-sm font-semibold text-[color:var(--text)]">Signed in for private documents</p>
<p className={cn("mt-1 text-xs leading-5", textMuted)}>{session?.user.email ?? "Authenticated session"}</p>
</div>
<button type="button" onClick={signOut} className={cn(floatingControl, "px-3 text-xs")}>
<LogOut className="h-4 w-4" />
Sign out
</button>
</div>
</div>
);
}

return (
<form onSubmit={submit} className={cn(panelSubtle, "space-y-3 p-3")}>
<div className="flex items-start gap-3">
<LogIn className="mt-0.5 h-5 w-5 shrink-0 text-[color:var(--primary)]" />
<div>
<p className="text-sm font-semibold text-[color:var(--text)]">
{isExpired ? "Sign-in link expired" : "Sign in for private documents"}
</p>
<p className={cn("mt-1 text-[15px] leading-6", textMuted)}>
{isExpired
? "Send a fresh link if this one failed or already timed out."
: "Real-data search, upload, and source previews require a Supabase Auth session."}
</p>
</div>
</div>
<label className="block">
<span className={fieldLabel}>Email address</span>
<div className="relative">
<Mail className={fieldIcon} />
<input
type="email"
value={email}
onChange={(event) => setDraftEmail(event.target.value)}
placeholder="you@example.com"
className={fieldControlWithIcon}
/>
</div>
</label>
<button type="submit" disabled={busy || !email.trim()} className={cn(primaryControl, "w-full")}>
{busy ? <Loader2 className="h-4 w-4 animate-spin" /> : <Mail className="h-4 w-4" />}
Send sign-in link
</button>
{error && (
<p
role="alert"
className={cn(
"rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-inset)] p-3 text-xs",
textMuted,
)}
>
{error}
</p>
)}
</form>
);
}
Loading