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
2 changes: 2 additions & 0 deletions docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -575,5 +575,7 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie
| 2026-07-31 | claude/top-search-design-mockups-w53znc | c09eb64918ae2956968d35f3253c405ed9b915d5 | therapy-compass filter sheet + documents review fixes (C2) | Therapy Compass's phone Topics/availability selects were faking multi-select (value pinned to "", literal check-prefix in option text, a Clear-filters action among the options); replaced with one sheet of aria-pressed toggles matching the wide viewport, in the tc- idiom. Also lands two CodeRabbit findings from merged PR #1542: sheet footer primary action to sm:min-h-12, and the dialog test now pins aria-expanded/aria-controls. Third finding (unmount skips focus restore) dispositioned no-change with a test pinning the scroll lock is not leaked. | verify:cheap exit 0 (458 files / 4785 tests); ui-accessibility.spec.ts 15 passed chromium incl. the rewritten Therapy Compass case; ui-smoke documents journey 1 passed chromium |
| 2026-08-01 | claude/top-search-design-mockups-w53znc | 4f4440fd6776742f5de203ee15295f372205321d | search results bar: scope-system deletion, filter shelf, bar anatomy | Handoff for PR #1555. Deleted the inert command-scope system (voided props, six modes' scope config, three matchers, four no-op call sites, the original shelf) — behaviour-preserving because every matcher early-returned true on a permanently-empty array. Rebuilt the applied-filter shelf prop-driven on live facet data, scoped to documents and therapy-compass. Landed the bar anatomy: tile spinner and funnel states, Filter to the right edge, Sort inboard. Study step 6 (remove the library button) deliberately declined — the nav route clears the query via onSearchModeChange. Ledger #182 closed. | verify:pr-local exit 0 (460 files / 4796 tests, production build, client-bundle secret scan, RAG fixtures 36 cases / 23 suites); ui-tools 87 passed; ui-smoke + ui-accessibility 108 passed 1 failed (pre-existing PDF-canvas test, fails identically stashed, Chromium 1194 vs pinned 1228); mutation-tested the shelf's survives-loading guard |
| 2026-08-01 | claude/sentry-agent-monitoring-eri94v | 86983f344b45e42310e9f167a5adb0a56e46ddb5 | pr-1551 | merge-ready-pending-ci: merged origin/main; fixed outstanding-issues blank-line/#183 orphan + renumbered npm row to #204; kept worker+wizard error-tracking sections; qodo claim-spam thread already fixed on prior tip and resolved | check:outstanding-issues pass; merge-tree clean vs origin/main; prior tip Static PR failed on outstanding-issues; push 86983f344 |
| 2026-08-01 | work | d4a4463172671cff3fd333feb685fa397e4f95d0 | latency, lazy loading, React and Next.js frontend performance | P2 eager document administration bundle boundary fixed; no P0/P1 findings | lint; typecheck; 463 files/4811 tests; production build; bundle budget |
| 2026-08-01 | codex/cloud-connected-profile-boundary | 6fddcfc780b237b6b2cd524dbebd7b1de70d9701 | Cloud connected profile credential boundary | No high-confidence issues after least-privilege MCP hardening and portable Git fixture fix | Cloud static PASS; focused Vitest 15/15; full format PASS; Bash syntax PASS; PR-local dry-run |
| 2026-08-01 | codex/cloud-connected-profile-boundary | 55b08496a5ee3495eed8a7436e8f69ae7b6612d8 | Cloud connected profile credential boundary | Reviewer findings fixed: cross-tenant service-role credential scrubbed and duplicate Supabase MCP parameters rejected | Cloud static PASS; focused Vitest 15/15; Bash syntax PASS; targeted Prettier PASS |
| 2026-08-01 | codex/review-latency-and-lazy-loading-optimizations | 5f069a7fec4e6ada47a0074aa5f2ea2c9dc97830 | pr-1562 unblock | unblocked: merge-tree was clean behind-by-4; merged origin/main; no unresolved threads; prior tip CI green including Production UI + PR required | merge-tree clean vs origin/main; gh mergeable was CONFLICTING/DIRTY (staleness); unresolved threads 0; auto-merge off |
4 changes: 2 additions & 2 deletions src/components/ClinicalDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ import {
ClinicalMobileSidebar,
} from "@/components/clinical-dashboard/ClinicalSidebar";
import {
LibraryHealthStrip,
fallbackSetupChecks,
hasReadyRequiredPublicSearchConfig,
hasReadyPublicSearchSetup,
type SetupCheck,
type IngestionQualityReviewItem,
} from "@/components/clinical-dashboard/DocumentManagerPanel";
} from "@/components/clinical-dashboard/document-manager-contracts";
import { LibraryHealthStrip } from "@/components/clinical-dashboard/library-health-strip";
import { GuideDialog, GuideTrigger, UtilityDrawer } from "@/components/clinical-dashboard/dashboard-shell";
import { SystemNotice, DegradedNotice } from "@/components/clinical-dashboard/dashboard-notices";
import { sanitizeAnswerDisplayText, sanitizeDisplayText } from "@/components/clinical-dashboard/display-text";
Expand Down
204 changes: 20 additions & 184 deletions src/components/clinical-dashboard/DocumentManagerPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,100 +22,33 @@ import { emptyStates, errorCopy } from "@/lib/ui-copy";
import { exceedsClientUploadSize, getClientMaxUploadMb, uploadSizeLimitMessage } from "@/lib/upload-limits";
import { StatusBadge } from "@/components/clinical-dashboard/badges";
import { PrivacyInputNotice } from "@/components/privacy-input-notice";
import type { ClinicalDocument, IngestionJob, ImportBatch } from "@/lib/types";
import type { IngestionJob, ImportBatch } from "@/lib/types";
import {
fallbackSetupChecks,
type IngestionQualityReviewItem,
type IngestionQualityReviewType,
type SetupCheck,
type SetupCheckStatus,
} from "@/components/clinical-dashboard/document-manager-contracts";

export {
fallbackSetupChecks,
hasReadyPublicSearchSetup,
hasReadyRequiredPublicSearchConfig,
} from "@/components/clinical-dashboard/document-manager-contracts";
export type {
IngestionQualityReviewItem,
IngestionQualityReviewType,
SetupCheck,
SetupCheckStatus,
} from "@/components/clinical-dashboard/document-manager-contracts";

// Setup and quality types
export type SetupCheckStatus = "ready" | "needs_setup" | "unknown";
export type SetupCheck = {
id: "env" | "project" | "schema" | "search" | "openai" | "worker";
label: string;
status: SetupCheckStatus;
detail: string;
};

const demoUploadReadOnlyMessage =
"Demo mode is read-only. Configure Supabase, OpenAI, and the local worker before uploading private guideline files.";

export type LibraryHealthTarget = "documents" | "setup" | "indexing" | "failures";
export type IndexingMonitorFilter = "all" | "active" | "failed";

export type IngestionQualityReviewType =
"failed_ocr" | "low_extraction_confidence" | "missing_tables" | "image_only_pages" | "failed_job" | "manual_review";

export type IngestionQualityReviewItem = {
id: string;
type: IngestionQualityReviewType;
severity: "danger" | "warning" | "info";
title: string;
detail: string;
documentId: string;
documentTitle: string;
fileName: string;
jobId: string | null;
qualityScore: number | null;
extractionQuality: string | null;
reasons: string[];
metrics: Record<string, unknown>;
updatedAt: string | null;
};

export const fallbackSetupChecks: SetupCheck[] = [
{
id: "env",
label: ".env.local configured",
status: "unknown",
detail: "Setup status has not loaded yet.",
},
{
id: "project",
label: "Clinical KB Database target",
status: "unknown",
detail: "Setup status has not loaded yet.",
},
{
id: "schema",
label: "supabase/schema.sql applied",
status: "unknown",
detail: "Setup status has not loaded yet.",
},
{
id: "search",
label: "Search RPC and vector indexes",
status: "unknown",
detail: "Setup status has not loaded yet.",
},
{
id: "openai",
label: "OpenAI API key available",
status: "unknown",
detail: "Setup status has not loaded yet.",
},
{
id: "worker",
label: "npm run worker running",
status: "unknown",
detail: "Setup status has not loaded yet.",
},
];

// OpenAI is intentionally excluded from both gates: browse/search only needs Supabase.
// The answer path validates OPENAI_API_KEY at request time (requireOpenAIEnv), so a
// missing key surfaces as a real API error there rather than blocking every mode here.
const publicSearchSetupCheckIds = new Set<SetupCheck["id"]>(["env", "project", "schema", "search"]);
const requiredPublicSearchConfigCheckIds = new Set<SetupCheck["id"]>(["env", "project", "schema"]);

export function hasReadyPublicSearchSetup(checks: SetupCheck[]) {
return Array.from(publicSearchSetupCheckIds).every(
(id) => checks.find((check) => check.id === id)?.status === "ready",
);
}

export function hasReadyRequiredPublicSearchConfig(checks: SetupCheck[]) {
return Array.from(requiredPublicSearchConfigCheckIds).every(
(id) => checks.find((check) => check.id === id)?.status === "ready",
);
}

function setupBadgeClasses(status: SetupCheckStatus) {
if (status === "ready") {
return toneSuccess;
Expand Down Expand Up @@ -790,103 +723,6 @@ export function IngestionQualityConsole({
);
}

export function LibraryHealthStrip({
documents,
jobs,
batches,
checks,
loading,
onSelectTarget,
}: {
documents: ClinicalDocument[];
jobs: IngestionJob[];
batches: ImportBatch[];
checks: SetupCheck[];
loading: boolean;
onSelectTarget?: (target: LibraryHealthTarget) => void;
}) {
const readyChecks = checks.filter((check) => check.status === "ready").length;
const indexedDocuments = documents.filter((document) => document.status === "indexed").length;
const activeJobs = jobs.filter((job) => job.status === "pending" || job.status === "processing").length;
const activeBatches = batches.filter((batch) => batch.status === "queued" || batch.status === "processing").length;
const failedWork =
jobs.filter((job) => job.status === "failed").length + batches.filter((batch) => batch.status === "failed").length;
const items = [
{
target: "documents" as const,
label: "Documents",
value: loading ? "" : `${indexedDocuments} indexed`,
tone: loading ? toneNeutral : indexedDocuments ? toneSuccess : toneWarning,
actionLabel: "Show indexed document files",
},
{
target: "setup" as const,
label: "Setup",
value: loading ? "" : `${readyChecks}/${checks.length || fallbackSetupChecks.length} ready`,
tone: loading
? toneNeutral
: readyChecks === (checks.length || fallbackSetupChecks.length)
? toneSuccess
: toneWarning,
actionLabel: "Show setup checks",
},
{
target: "indexing" as const,
label: "Indexing",
value: loading ? "" : activeJobs + activeBatches ? `${activeJobs + activeBatches} active` : "Idle",
tone: loading ? toneNeutral : activeJobs + activeBatches ? toneInfo : toneNeutral,
actionLabel: "Show indexing progress",
},
{
target: "failures" as const,
label: "Failures",
value: loading ? "" : failedWork ? `${failedWork} needs review` : "None",
tone: loading ? toneNeutral : failedWork ? toneDanger : toneNeutral,
actionLabel: "Show failed indexing work",
},
];

return (
<section
data-testid="library-health-strip"
className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-3 shadow-[var(--shadow-inset)]"
aria-label="Library health"
style={{ containIntrinsicSize: "auto 76px", contentVisibility: "auto" }}
>
<div className="mb-2 flex min-h-7 items-center justify-between gap-2">
<p className="text-xs font-bold uppercase tracking-eyebrow text-[color:var(--text-muted)]">Library health</p>
<span className={cn("text-2xs font-semibold", textMuted)}>Read-only status</span>
</div>
<div className="grid gap-2 sm:grid-cols-4">
{items.map((item) => (
<button
key={item.label}
type="button"
onClick={() => onSelectTarget?.(item.target)}
className={cn(
"rounded-md border px-2.5 py-2 text-left transition hover:-translate-y-px hover:shadow-[var(--shadow-soft)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] active:translate-y-0",
item.tone,
)}
aria-label={item.actionLabel}
aria-busy={loading || undefined}
style={{ containIntrinsicSize: "auto 48px", contentVisibility: "auto" }}
>
<p className="text-2xs font-bold uppercase tracking-[0.06em]">{item.label}</p>
{loading ? (
<div
className="mt-1 h-4 w-16 animate-skeleton-shimmer rounded bg-[color:var(--surface-inset)]"
data-testid="library-health-skeleton"
/>
) : (
<p className="mt-1 text-xs font-semibold">{item.value}</p>
)}
</button>
))}
</div>
</section>
);
}

function indexingWorkMatchesFilter(item: Pick<IngestionJob | ImportBatch, "status">, filter: IndexingMonitorFilter) {
if (filter === "all") return true;
if (filter === "active") return item.status === "pending" || item.status === "processing" || item.status === "queued";
Expand Down
63 changes: 63 additions & 0 deletions src/components/clinical-dashboard/document-manager-contracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Kept in a dependency-free module so the initial dashboard does not pull the
// complete document administration UI into its eager client chunk.
export type SetupCheckStatus = "ready" | "needs_setup" | "unknown";
export type SetupCheck = {
id: "env" | "project" | "schema" | "search" | "openai" | "worker";
label: string;
status: SetupCheckStatus;
detail: string;
};

export type IngestionQualityReviewType =
"failed_ocr" | "low_extraction_confidence" | "missing_tables" | "image_only_pages" | "failed_job" | "manual_review";

export type IngestionQualityReviewItem = {
id: string;
type: IngestionQualityReviewType;
severity: "danger" | "warning" | "info";
title: string;
detail: string;
documentId: string;
documentTitle: string;
fileName: string;
jobId: string | null;
qualityScore: number | null;
extractionQuality: string | null;
reasons: string[];
metrics: Record<string, unknown>;
updatedAt: string | null;
};

export const fallbackSetupChecks: SetupCheck[] = [
{ id: "env", label: ".env.local configured", status: "unknown", detail: "Setup status has not loaded yet." },
{
id: "project",
label: "Clinical KB Database target",
status: "unknown",
detail: "Setup status has not loaded yet.",
},
{ id: "schema", label: "supabase/schema.sql applied", status: "unknown", detail: "Setup status has not loaded yet." },
{
id: "search",
label: "Search RPC and vector indexes",
status: "unknown",
detail: "Setup status has not loaded yet.",
},
{ id: "openai", label: "OpenAI API key available", status: "unknown", detail: "Setup status has not loaded yet." },
{ id: "worker", label: "npm run worker running", status: "unknown", detail: "Setup status has not loaded yet." },
];

const publicSearchSetupCheckIds = new Set<SetupCheck["id"]>(["env", "project", "schema", "search"]);
const requiredPublicSearchConfigCheckIds = new Set<SetupCheck["id"]>(["env", "project", "schema"]);

export function hasReadyPublicSearchSetup(checks: SetupCheck[]) {
return Array.from(publicSearchSetupCheckIds).every(
(id) => checks.find((check) => check.id === id)?.status === "ready",
);
}

export function hasReadyRequiredPublicSearchConfig(checks: SetupCheck[]) {
return Array.from(requiredPublicSearchConfigCheckIds).every(
(id) => checks.find((check) => check.id === id)?.status === "ready",
);
}
Loading
Loading