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
16 changes: 16 additions & 0 deletions docs/project-alignment-cleanup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Project Alignment Cleanup

## Dependency decisions

- Updated direct runtime and development dependencies to their current compatible releases as part of the June 2026 alignment pass.
- Kept `eslint` on `^9.39.4` because `eslint-config-next@16.2.7` still depends on plugins that peer against ESLint 9. ESLint 10 is intentionally deferred until the Next lint stack supports it without peer overrides.
- Added transitive overrides for `postcss`, `tmp`, and `uuid` to remove audit findings from Next and ExcelJS dependency paths while preserving the existing spreadsheet import/export code.
- Verified ExcelJS still writes and reads an XLSX buffer with the `uuid` override in place.

## Stale branch audit

- `temporary` and `codex/spark` contain the same four unique commits covering upload workflow notes, Supabase project checks, and database cleanup hardening.
- `codex/database-local` contains the design-system experiment history and a design-system baseline document.
- `codex/design-system-baseline-20260524` is the earlier design baseline snapshot from the same design-system line.

No stale branch was merged directly. Current `main` already contains the accepted upload workflow, Supabase project safety guidance, cleanup scripts, RAG hardening, and dashboard/document-viewer design work. The remaining branch diffs are either superseded by current tracked files or predate the accepted design implementation, so no code was ported.
1,122 changes: 627 additions & 495 deletions package-lock.json

Large diffs are not rendered by default.

40 changes: 21 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,41 +31,43 @@
"samples:check": "tsx scripts/check-sample-extraction.ts"
},
"dependencies": {
"@next/env": "^16.2.6",
"@supabase/supabase-js": "^2.106.1",
"@next/env": "^16.2.7",
"@supabase/supabase-js": "^2.107.0",
"exceljs": "^4.4.0",
"jszip": "^3.10.1",
"lucide-react": "^1.17.0",
"mammoth": "^1.12.0",
"next": "16.2.6",
"openai": "^6.38.0",
"next": "16.2.7",
"openai": "^6.41.0",
"pdf-parse": "^2.4.5",
"pdfjs-dist": "^5.7.284",
"pdfjs-dist": "^6.0.227",
"pdfkit": "^0.18.0",
"postcss": "^8.5.10",
"postcss": "^8.5.15",
"react": "19.2.7",
"react-dom": "19.2.4",
"react-dom": "19.2.7",
"tesseract.js": "^7.0.0",
"zod": "^4.4.3"
},
"overrides": {
"postcss": "^8.5.10"
"postcss": "^8.5.15",
"tmp": "^0.2.7",
"uuid": "^11.1.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^25",
"@tailwindcss/postcss": "^4.3.0",
"@types/node": "^25.9.1",
"@types/pdf-parse": "^1.1.5",
"@types/pdfkit": "^0.17.6",
"@types/react": "^19",
"@types/react-dom": "^19",
"@vitest/coverage-v8": "^4.1.6",
"eslint": "^9",
"eslint-config-next": "16.2.6",
"@types/react": "^19.2.16",
"@types/react-dom": "^19.2.3",
"@vitest/coverage-v8": "^4.1.8",
"eslint": "^9.39.4",
"eslint-config-next": "16.2.7",
"playwright": "^1.60.0",
"prettier": "^3.8.3",
"tailwindcss": "^4",
"tsx": "^4.22.0",
"typescript": "^5",
"vitest": "^4.1.7"
"tailwindcss": "^4.3.0",
"tsx": "^4.22.4",
"typescript": "^6.0.3",
"vitest": "^4.1.8"
}
}
19 changes: 15 additions & 4 deletions scripts/audit-tables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ type Args = {
};

function parseArgs(): Args {
const args: Args = { ownerEmail: process.env.RAG_EVAL_OWNER_EMAIL, allOwners: !process.env.RAG_EVAL_OWNER_EMAIL, limit: 200, failOnMissed: false };
const args: Args = {
ownerEmail: process.env.RAG_EVAL_OWNER_EMAIL,
allOwners: !process.env.RAG_EVAL_OWNER_EMAIL,
limit: 200,
failOnMissed: false,
};
const tokens = process.argv.slice(2);
for (let index = 0; index < tokens.length; index += 1) {
const token = tokens[index];
Expand Down Expand Up @@ -85,7 +90,10 @@ async function main() {
if (imagesResult.error) throw new Error(imagesResult.error.message);
if (pagesResult.error) throw new Error(pagesResult.error.message);

const counts = new Map<string, { tables: number; clinicalTables: number; adminTables: number; searchableAdminTables: number; images: number }>();
const counts = new Map<
string,
{ tables: number; clinicalTables: number; adminTables: number; searchableAdminTables: number; images: number }
>();
for (const documentId of documentIds) {
counts.set(documentId, { tables: 0, clinicalTables: 0, adminTables: 0, searchableAdminTables: 0, images: 0 });
}
Expand All @@ -98,7 +106,8 @@ async function main() {
searchableAdminTables: 0,
images: 0,
};
const metadata = image.metadata && typeof image.metadata === "object" ? (image.metadata as Record<string, unknown>) : {};
const metadata =
image.metadata && typeof image.metadata === "object" ? (image.metadata as Record<string, unknown>) : {};
const useClass = String(
metadata.clinical_use_class ??
assessClinicalImageUse({
Expand Down Expand Up @@ -153,7 +162,9 @@ async function main() {
possibleMisses.push(`${document.file_name}: table-like text on pages ${markerPages.slice(0, 8).join(", ")}`);
}
if (documentCounts.searchableAdminTables > 0) {
searchableAdminIssues.push(`${document.file_name}: searchable admin/reference tables=${documentCounts.searchableAdminTables}`);
searchableAdminIssues.push(
`${document.file_name}: searchable admin/reference tables=${documentCounts.searchableAdminTables}`,
);
}
console.log(
[
Expand Down
16 changes: 12 additions & 4 deletions scripts/check-indexing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ function hasCurrentMemoryVersion(metadata: unknown, expectedVersion: string) {
}

function strictEnrichmentVersionRequired() {
return process.argv.includes("--strict-enrichment-version") || process.env.RAG_REQUIRE_CURRENT_ENRICHMENT_VERSION === "1";
return (
process.argv.includes("--strict-enrichment-version") || process.env.RAG_REQUIRE_CURRENT_ENRICHMENT_VERSION === "1"
);
}

async function loadEnrichmentRows(supabase: SupabaseLike, documentIds: string[]) {
Expand Down Expand Up @@ -144,7 +146,10 @@ async function main() {
labelRowsByDocument.set(label.document_id, [...(labelRowsByDocument.get(label.document_id) ?? []), label]);
}
for (const section of deepMemoryRows.sections) {
sectionRowsByDocument.set(section.document_id, [...(sectionRowsByDocument.get(section.document_id) ?? []), section]);
sectionRowsByDocument.set(section.document_id, [
...(sectionRowsByDocument.get(section.document_id) ?? []),
section,
]);
}
for (const card of deepMemoryRows.memoryCards) {
memoryRowsByDocument.set(card.document_id, [...(memoryRowsByDocument.get(card.document_id) ?? []), card]);
Expand Down Expand Up @@ -173,7 +178,9 @@ async function main() {
);
const documentsWithCurrentEnrichmentVersion = indexedDocuments.filter((document) => {
const summary = summariesByDocument.get(document.id);
const generatedLabels = (labelRowsByDocument.get(document.id) ?? []).filter((label) => label.source === "generated");
const generatedLabels = (labelRowsByDocument.get(document.id) ?? []).filter(
(label) => label.source === "generated",
);
return (
hasCurrentEnrichmentVersion(document.metadata, ragEnrichmentVersion) &&
hasCurrentEnrichmentVersion(summary?.metadata, ragEnrichmentVersion) &&
Expand Down Expand Up @@ -257,7 +264,8 @@ async function main() {
issues.push(`indexed documents missing summaries: ${documentsMissingSummaries.length}`);
if (documentsMissingGeneratedLabels.length > 0)
issues.push(`indexed documents missing generated labels: ${documentsMissingGeneratedLabels.length}`);
if (documentsMissingSections.length > 0) issues.push(`indexed documents missing sections: ${documentsMissingSections.length}`);
if (documentsMissingSections.length > 0)
issues.push(`indexed documents missing sections: ${documentsMissingSections.length}`);
if (documentsMissingMemoryCards.length > 0)
issues.push(`indexed documents missing memory cards: ${documentsMissingMemoryCards.length}`);
if (requireCurrentEnrichmentVersion && documentsMissingCurrentEnrichmentVersion > 0) {
Expand Down
3 changes: 2 additions & 1 deletion scripts/enrich-documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ function parseArgs(argv: string[]): EnrichArgs {
const args: EnrichArgs = {
ownerEmail: process.env.RAG_EVAL_OWNER_EMAIL,
ownerId: process.env.RAG_EVAL_OWNER_ID ?? process.env.LOCAL_NO_AUTH_OWNER_ID,
allOwners: !process.env.RAG_EVAL_OWNER_EMAIL && !process.env.RAG_EVAL_OWNER_ID && !process.env.LOCAL_NO_AUTH_OWNER_ID,
allOwners:
!process.env.RAG_EVAL_OWNER_EMAIL && !process.env.RAG_EVAL_OWNER_ID && !process.env.LOCAL_NO_AUTH_OWNER_ID,
mode: "summaries-labels-images",
limit: 25,
includeCurrent: false,
Expand Down
3 changes: 1 addition & 2 deletions scripts/eval-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,7 @@ async function main() {

if (!args.json) {
const failureSuffix = result.failures.length ? ` FAIL=${result.failures.join("; ")}` : "";
const expectedCoverage =
result.expectedFileCount > 1 ? ` allExpectedTop5=${result.expectedAllHitTop5}` : "";
const expectedCoverage = result.expectedFileCount > 1 ? ` allExpectedTop5=${result.expectedAllHitTop5}` : "";
console.log(
`SEARCH ${result.latencyMs}ms strategy=${result.retrievalStrategy ?? "none"} skippedEmbedding=${result.embeddingSkipped} expectedHit=${result.expectedHitTop3}${expectedCoverage} topScore=${result.topScore.toFixed(3)}${failureSuffix}`,
);
Expand Down
3 changes: 2 additions & 1 deletion scripts/import-documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ async function main() {
throw new Error("Provide --owner-id, set LOCAL_NO_AUTH_OWNER_ID, or provide --owner-email for a new import batch.");
}

const requestedOwnerId = configuredOwnerId ?? (args.ownerEmail ? await findOwnerIdByEmail(supabase, args.ownerEmail) : undefined);
const requestedOwnerId =
configuredOwnerId ?? (args.ownerEmail ? await findOwnerIdByEmail(supabase, args.ownerEmail) : undefined);
const batch = await loadOrCreateBatch({
supabase,
ownerId: requestedOwnerId,
Expand Down
9 changes: 7 additions & 2 deletions src/components/AccessibleTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ export function AccessibleTable({
<div className="overflow-x-auto rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)]">
<table className="min-w-full border-collapse text-left text-sm">
{caption ? (
<caption className={cn("caption-top px-3 py-2 text-left text-xs font-semibold", textMuted)}>{caption}</caption>
<caption className={cn("caption-top px-3 py-2 text-left text-xs font-semibold", textMuted)}>
{caption}
</caption>
) : null}
<thead>
<tr className="bg-[color:var(--surface-subtle)]">
Expand All @@ -66,7 +68,10 @@ export function AccessibleTable({
return (
<tr key={`${rowIndex}:${row.join("|")}`} className="border-t border-[color:var(--border)]/70">
{row.map((cell, cellIndex) => (
<td key={`${rowIndex}:${cellIndex}`} className="px-3 py-2 align-top leading-5 text-[color:var(--text)]">
<td
key={`${rowIndex}:${cellIndex}`}
className="px-3 py-2 align-top leading-5 text-[color:var(--text)]"
>
{cell || <span className={textMuted}>-</span>}
</td>
))}
Expand Down
57 changes: 38 additions & 19 deletions src/components/ClinicalDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -751,15 +751,21 @@ function SectionHeading({
);
}

function relevanceChipLabel(relevance: EvidenceRelevance | SourceEvidenceRelevance | null | undefined, grounded = false) {
function relevanceChipLabel(
relevance: EvidenceRelevance | SourceEvidenceRelevance | null | undefined,
grounded = false,
) {
if (!relevance) return grounded ? "Source-backed" : "No direct support";
if (relevance.verdict === "direct") return "Source-backed";
if (relevance.verdict === "partial") return "Partial support";
if (relevance.verdict === "nearby") return "Nearby only";
return "No direct support";
}

function relevanceChipClasses(relevance: EvidenceRelevance | SourceEvidenceRelevance | null | undefined, grounded = false) {
function relevanceChipClasses(
relevance: EvidenceRelevance | SourceEvidenceRelevance | null | undefined,
grounded = false,
) {
const verdict = relevance?.verdict ?? (grounded ? "direct" : "none");
if (verdict === "direct") {
return "border-[color:var(--success)]/20 bg-[color:var(--success-soft)]/45 text-[color:var(--success)]";
Expand All @@ -770,7 +776,10 @@ function relevanceChipClasses(relevance: EvidenceRelevance | SourceEvidenceRelev
return "border-[color:var(--warning)]/25 bg-[color:var(--warning-soft)]/45 text-[color:var(--warning)]";
}

function hasStrongRelevanceIcon(relevance: EvidenceRelevance | SourceEvidenceRelevance | null | undefined, grounded = false) {
function hasStrongRelevanceIcon(
relevance: EvidenceRelevance | SourceEvidenceRelevance | null | undefined,
grounded = false,
) {
const verdict = relevance?.verdict ?? (grounded ? "direct" : "none");
return verdict === "direct" || verdict === "partial";
}
Expand Down Expand Up @@ -905,7 +914,9 @@ function MasterSearchHeader({
const trimmedQuery = query.trim();
const canAsk = trimmedQuery.length >= 1 && !loading && realDataReady;
const compactMobile = hasAnswer;
const selectedDocuments = selectedDocumentIds.map((id) => documents.find((document) => document.id === id)).filter(Boolean);
const selectedDocuments = selectedDocumentIds
.map((id) => documents.find((document) => document.id === id))
.filter(Boolean);
const scopeSummary = selectedDocumentIds.length === 0 ? "All documents" : `${selectedDocumentIds.length} scoped`;
const scopePreview = selectedDocuments
.slice(0, 2)
Expand Down Expand Up @@ -1066,9 +1077,7 @@ function MasterSearchHeader({
: "text-slate-200 hover:bg-white/10 hover:text-white",
)}
aria-pressed={active}
aria-label={
item.mode === "answer" ? "Switch to answer mode" : "Switch to document search mode"
}
aria-label={item.mode === "answer" ? "Switch to answer mode" : "Switch to document search mode"}
>
<Icon className="h-4 w-4" />
{item.label}
Expand Down Expand Up @@ -1167,7 +1176,9 @@ function MasterSearchHeader({
<Filter className="h-4 w-4 shrink-0" />
<span className="shrink-0">Scope & prompts</span>
<span className="rounded-md bg-white/10 px-2 py-0.5 text-[11px] text-slate-300">{scopeSummary}</span>
{scopePreview ? <span className="truncate text-xs font-medium text-slate-400">{scopePreview}</span> : null}
{scopePreview ? (
<span className="truncate text-xs font-medium text-slate-400">{scopePreview}</span>
) : null}
</span>
<span className="inline-flex shrink-0 items-center gap-2 text-xs text-slate-400">
<span>{documents.length} documents</span>
Expand Down Expand Up @@ -1635,7 +1646,9 @@ function EvidenceGapPanel({
<p className={cn("mt-2 text-[15px] leading-6", textMuted)}>{found}</p>
</article>
<article className={cn(sourceCard, "p-3")}>
<p className="text-xs font-bold uppercase tracking-[0.08em] text-[color:var(--text-soft)]">What was not found</p>
<p className="text-xs font-bold uppercase tracking-[0.08em] text-[color:var(--text-soft)]">
What was not found
</p>
<p className={cn("mt-2 text-[15px] leading-6", textMuted)}>{missing}</p>
</article>
<article className={cn(sourceCard, "p-3 md:col-span-2")}>
Expand Down Expand Up @@ -1803,7 +1816,9 @@ function ClinicalOutputPanel({
icon={ListChecks}
title="Clinical action view"
description="Structured from retrieved answer text, exact quotes, citations, and extracted tables."
action={<CopyButton label="Copy clinical draft" shortLabel="Draft" copied={copiedWardNote} onClick={onCopyWardNote} />}
action={
<CopyButton label="Copy clinical draft" shortLabel="Draft" copied={copiedWardNote} onClick={onCopyWardNote} />
}
hideDescriptionOnMobile
compactMobile
/>
Expand All @@ -1823,11 +1838,7 @@ function ClinicalOutputPanel({
>
<div className="flex items-start gap-2">
<span className={cn(iconTilePremium, "h-8 w-8")}>
{section.id === "verify-source" ? (
<Target className="h-4 w-4" />
) : (
<CheckCircle2 className="h-4 w-4" />
)}
{section.id === "verify-source" ? <Target className="h-4 w-4" /> : <CheckCircle2 className="h-4 w-4" />}
</span>
<div className="min-w-0">
<h3 className="text-sm font-semibold text-[color:var(--text)]">{section.title}</h3>
Expand Down Expand Up @@ -4037,7 +4048,9 @@ export function ClinicalDashboard() {
<AnswerHeaderActions
bestSource={bestSource}
grounded={
answer.grounded && answer.confidence !== "unsupported" && currentRelevance?.isSourceBacked !== false
answer.grounded &&
answer.confidence !== "unsupported" &&
currentRelevance?.isSourceBacked !== false
}
relevance={currentRelevance}
/>
Expand Down Expand Up @@ -4098,7 +4111,9 @@ export function ClinicalDashboard() {
<FileText className="h-4 w-4" />
</span>
<span className="min-w-0">
<span className="block text-sm font-semibold text-[color:var(--text)]">Source narrative</span>
<span className="block text-sm font-semibold text-[color:var(--text)]">
Source narrative
</span>
<span className={cn("block truncate text-xs", textMuted)}>
Full source-linked answer text and section citations
</span>
Expand All @@ -4117,7 +4132,9 @@ export function ClinicalDashboard() {
<VerificationActionStrip
source={bestSource}
grounded={
answer.grounded && answer.confidence !== "unsupported" && currentRelevance?.isSourceBacked !== false
answer.grounded &&
answer.confidence !== "unsupported" &&
currentRelevance?.isSourceBacked !== false
}
citationCount={answer.citations.length}
quoteCount={answer.quoteCards?.length ?? 0}
Expand Down Expand Up @@ -4177,7 +4194,9 @@ export function ClinicalDashboard() {
onScopeDocument={scopeOnlyDocument}
/>
)}
{searchMode === "answer" && answer && <VisualEvidenceStrip evidence={visualEvidence} collapsed={weakEvidence} />}
{searchMode === "answer" && answer && (
<VisualEvidenceStrip evidence={visualEvidence} collapsed={weakEvidence} />
)}
{searchMode === "answer" && answer && (
<RelatedDocumentsPanel documents={relatedDocuments} onScopeDocument={scopeOnlyDocument} />
)}
Expand Down
Loading