diff --git a/docs/archive/operator-decisions-2026-07-04.md b/docs/archive/operator-decisions-2026-07-04.md new file mode 100644 index 0000000000..b6f7615808 --- /dev/null +++ b/docs/archive/operator-decisions-2026-07-04.md @@ -0,0 +1,46 @@ +# Operator decisions — 2026-07-04 + +Historical snapshot of manual follow-ups deferred during documentation and verification gate recovery work. **No live actions were taken from this checklist.** + +## Publishable key rotation + +**Context:** `docs/multi-user-auth-setup.md` previously contained a live Supabase publishable (anon) key. The doc was scrubbed in commit `31543a926`, but the key may still exist in Git history. + +**Decision:** Document only — rotate in Supabase when convenient; do not block doc/CI recovery on rotation. + +**Operator steps (when approved):** + +1. Supabase Dashboard → Project `Clinical KB Database` (`sjrfecxgysukkwxsowpy`) → Settings → API. +2. Rotate the **anon/public** key. +3. Update deployment secrets and local `.env.local` for all environments that use the old key. +4. Run `npm run check:supabase-project` after env updates. +5. Optionally audit Git history exposure; consider `git filter-repo` only if policy requires history rewrite. + +## Pending live migrations + +**Context:** Local `supabase/migrations/` includes July 2026 changes not yet verified as applied on the linked project. See [`docs/supabase-migration-reconciliation.md`](../supabase-migration-reconciliation.md). + +**Decision:** Document only — no `supabase db push`, dashboard SQL, or `migration repair` from automation. + +**Pre-apply checklist (when approved):** + +```bash +npx supabase migration list --linked +npm run supabase:recovery-status +npx supabase db advisors --linked +``` + +**Notable pending migration (verify before apply):** + +- `20260703030000_reconcile_storage_cleanup_jobs_indexes` — reconcile storage cleanup job indexes; confirm live index state and maintenance window before applying. + +**After apply:** + +- Re-run the verification queries in `docs/supabase-migration-reconciliation.md`. +- Update the "Current Status" section with applied versions and evidence. + +## Verification gate notes + +- `tsconfig.json` excludes `.next/dev/types` — dev server artifacts must not be typechecked. +- Run `npm run sitemap:update` immediately before `npm run verify:cheap` when routes change. +- Clear stale `.eslintcache` if lint reports ENOENT on removed temp scripts. diff --git a/docs/supabase-migration-reconciliation.md b/docs/supabase-migration-reconciliation.md index cddf96f423..4c16613608 100644 --- a/docs/supabase-migration-reconciliation.md +++ b/docs/supabase-migration-reconciliation.md @@ -52,3 +52,7 @@ npx supabase db query --linked "select to_regclass('public.document_strict_gate_ npx supabase db query --linked "select to_regprocedure('public.commit_document_index_generation(uuid, uuid, text, integer, integer, integer, jsonb, jsonb, jsonb)') as commit_generation_rpc, has_function_privilege('anon', 'public.invoke_indexing_v3_agent(integer)', 'execute') as anon_can_invoke_indexing_v3_agent, has_function_privilege('service_role', 'public.invoke_indexing_v3_agent(integer)', 'execute') as service_role_can_invoke_indexing_v3_agent;" npm run check:indexing ``` + +## Operator follow-ups + +Manual key rotation and live migration apply decisions are recorded in [`docs/archive/operator-decisions-2026-07-04.md`](archive/operator-decisions-2026-07-04.md). Do not execute those steps from CI or agent automation without explicit operator approval. diff --git a/src/app/documents/documents-layout-client.tsx b/src/app/documents/documents-layout-client.tsx index 72fbf7c902..4876ba5fdd 100644 --- a/src/app/documents/documents-layout-client.tsx +++ b/src/app/documents/documents-layout-client.tsx @@ -8,8 +8,7 @@ import { GlobalSearchShell } from "@/components/clinical-dashboard/global-search export function DocumentsLayoutClient({ children }: { children: ReactNode }) { const pathname = usePathname(); const isDocumentSearchRoute = pathname === "/documents/search"; - const documentViewerOwnsMobileChrome = pathname.startsWith("/documents/") && pathname !== "/documents/search"; - const documentFlowOwnsMobileChrome = pathname.startsWith("/documents/source") || documentViewerOwnsMobileChrome; + const documentFlowOwnsMobileChrome = pathname.startsWith("/documents/source"); return ( { if (!dialogOpen) return; @@ -401,6 +401,7 @@ export function AccessibleTable({ function openDialog(trigger: HTMLElement) { if (!canExpand) return; + trigger.scrollIntoView({ block: "center", inline: "nearest" }); restoreFocusRef.current = trigger; setOpen(true); } @@ -428,7 +429,7 @@ export function AccessibleTable({ className={cn( "min-w-0", canExpand && - "cursor-zoom-in rounded-lg outline-none ring-offset-2 ring-offset-[color:var(--surface)] transition focus-within:ring-4 focus-within:ring-[color:var(--focus)]/25", + "relative z-50 cursor-zoom-in rounded-lg outline-none ring-offset-2 ring-offset-[color:var(--surface)] transition focus-within:ring-4 focus-within:ring-[color:var(--focus)]/25", )} > {table} @@ -444,7 +445,7 @@ export function AccessibleTable({ event.stopPropagation(); openDialog(event.currentTarget); }} - className="mt-2 inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-lg border border-[color:var(--border-lux)] bg-[color:var(--surface-raised)] px-3 text-xs font-semibold text-[color:var(--text)] shadow-[var(--shadow-tight)] transition hover:border-[color:var(--border-strong)] focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-[color:var(--focus)]/25" + className="relative z-50 mt-2 inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-lg border border-[color:var(--border-lux)] bg-[color:var(--surface-raised)] px-3 text-xs font-semibold text-[color:var(--text)] shadow-[var(--shadow-tight)] transition hover:border-[color:var(--border-strong)] focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-[color:var(--focus)]/25" > Expand table diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx index 67c84fd257..feca1291d9 100644 --- a/src/components/ClinicalDashboard.tsx +++ b/src/components/ClinicalDashboard.tsx @@ -12,7 +12,6 @@ import { ChevronRight, CircleUserRound, Clock3, - ClipboardCheck, Copy, ExternalLink, FileImage, @@ -40,14 +39,22 @@ import { SlidersHorizontal, Sparkles, Stethoscope, - Tag, UploadCloud, UserRound, WifiOff, Wrench, X, } from "lucide-react"; -import { type CSSProperties, type FormEvent, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { + type CSSProperties, + type FormEvent, + type RefObject, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { AccessibleTable } from "@/components/AccessibleTable"; import { DocumentOrganizationBadges, @@ -79,6 +86,7 @@ import { SourceProvenance, SourceStatusBadge, sourceCard, + subtleStatusPill, tableCard, tableCardHeader, tableMicroActionRow, @@ -95,6 +103,7 @@ import { Sheet } from "@/components/ui/sheet"; import { AccountSetupDialog } from "@/components/clinical-dashboard/account-setup-dialog"; import { StagedAnswerResultSurface } from "@/components/clinical-dashboard/answer-result-surface"; import { RelatedDocumentsPanel } from "@/components/clinical-dashboard/document-results"; +import { AnswerFollowUpSuggestions } from "@/components/clinical-dashboard/answer-follow-up-suggestions"; 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"; @@ -138,6 +147,13 @@ import { AnswerEmptyState, AnswerSkeleton } from "@/components/clinical-dashboar import { AnswerFeedbackPanel, AnswerSafetyNotice, + AnswerSupportSummaryCard, + answerHasCentralTable, + answerSupportPriority, + ClinicalNotesChecklistPanel, + clinicalNotesCount, + clinicalNotesDisplayCountForAnswer, + compactEvidenceSummary, type EvidenceTabName, simpleClinicalTableProps, evidenceMapRowsFromRenderModel, @@ -146,11 +162,18 @@ import { formatQuoteCardsForClipboard, primaryVisualTable, QuoteCards, + SafetyFindingsListContent, } from "@/components/clinical-dashboard/evidence-panels"; import { MasterSearchHeader } from "@/components/clinical-dashboard/master-search-header"; import { SearchCommandProvider } from "@/components/clinical-dashboard/search-command-context"; import { emptyStates, errorCopy } from "@/lib/ui-copy"; import { applicationsLauncherItemCount } from "@/components/applications-launcher-page"; +import { + DrawerGroupLabel, + type DocumentDrawerMode, + type DocumentDrawerStatusFilter, + type LabelReviewMutationBody, +} from "@/components/clinical-dashboard/document-admin"; const DifferentialsHome = dynamic( () => import("@/components/clinical-dashboard/differentials-home").then((m) => m.DifferentialsHome), @@ -171,6 +194,11 @@ export const ApplicationsLauncherWorkspace = dynamic( () => import("@/components/applications-launcher-page").then((m) => m.ApplicationsLauncherWorkspace), { ssr: false }, ); +const DocumentDrawer = dynamic( + () => import("@/components/clinical-dashboard/document-admin").then((m) => m.DocumentDrawer), + { ssr: false }, +); + import { DocumentSearchResultsPanel, type SearchFacets } from "@/components/clinical-dashboard/document-search-results"; import { isWeakRelevance, QueryCoverageChips } from "@/components/clinical-dashboard/relevance"; import { @@ -219,11 +247,6 @@ import { } from "@/lib/source-governance"; import { smartEvidenceTags } from "@/lib/evidence-tags"; import { - documentLabelReviewStatus, - documentLabelTier, - formatDocumentLabelDisplay, - normalizeDocumentLabelForStorage, - reviewDocumentTagQuality, tagSearchText, type SmartDocumentTag, type SmartDocumentTagFacet, @@ -1133,1084 +1156,10 @@ function PriorAnswerTurnSurface({ ); } -const tagQualityTone: Record = { - noisy: toneDanger, - duplicate: toneWarning, - low_confidence: toneInfo, - overused: toneNeutral, -}; - -const labelTierTone: Record = { - primary: toneSuccess, - secondary: toneNeutral, - ranking: toneInfo, -}; - -const documentLabelTypeOptions: Array<{ value: DocumentLabelType; label: string }> = [ - { value: "site", label: "Site" }, - { value: "topic", label: "Topic" }, - { value: "document_type", label: "Document type" }, - { value: "medication", label: "Medication" }, - { value: "risk", label: "Risk" }, - { value: "setting", label: "Setting" }, - { value: "workflow", label: "Workflow" }, - { value: "population", label: "Population" }, - { value: "service", label: "Service" }, - { value: "clinical_action", label: "Clinical action" }, - { value: "care_phase", label: "Care phase" }, - { value: "document_intent", label: "Document intent" }, - { value: "content_feature", label: "Content feature" }, - { value: "custom", label: "Manual" }, -]; - -function tagQualityLabel(kind: SmartDocumentTagQualityIssueKind) { - if (kind === "low_confidence") return "low confidence"; - return kind; -} - -function normalizedLabelReviewRow(label: DocumentLabel) { - const normalized = normalizeDocumentLabelForStorage(label); - const fallbackLabelType = documentLabelTypeOptions.some((option) => option.value === label.label_type) - ? label.label_type - : "custom"; - const labelType = normalized?.label_type ?? fallbackLabelType; - const labelText = normalized?.label ?? label.label?.trim() ?? ""; - const tier: SmartDocumentTagTier = normalized - ? documentLabelTier(normalized.label, normalized.label_type) - : "secondary"; - const reviewStatus = documentLabelReviewStatus(label); - return { - id: label.id, - label: labelText, - displayLabel: labelText ? formatDocumentLabelDisplay(labelText, labelType) : "Unreviewed label", - labelType, - tier, - reviewStatus, - source: label.source, - confidence: normalized?.confidence ?? label.confidence ?? 0, - }; -} - -function labelTypeDisplay(value: DocumentLabelType) { - return documentLabelTypeOptions.find((option) => option.value === value)?.label ?? value.replaceAll("_", " "); -} - -type LabelReviewMutationBody = - { labelId: string; action: "approve" | "hide" | "restore" } | { label: string; label_type: DocumentLabelType }; - -function DocumentLabelReviewPanel({ - documents, - canManage, - onMutateLabel, -}: { - documents: ClinicalDocument[]; - canManage: boolean; - onMutateLabel: (documentId: string, method: "POST" | "PATCH", body: LabelReviewMutationBody) => Promise; -}) { - const [busyAction, setBusyAction] = useState(null); - const [overrideDrafts, setOverrideDrafts] = useState>( - {}, - ); - - const items = useMemo(() => { - return documents - .map((document) => { - const rows = (document.labels ?? []) - .map((label) => normalizedLabelReviewRow(label)) - .filter((row): row is NonNullable> => Boolean(row)); - const visible = rows.filter((row) => row.reviewStatus !== "hidden" && row.tier !== "ranking"); - const ranking = rows.filter((row) => row.reviewStatus !== "hidden" && row.tier === "ranking"); - const hidden = rows.filter((row) => row.reviewStatus === "hidden"); - const needsReview = rows.some((row) => row.reviewStatus === "new" && row.source === "generated"); - return { document, rows, visible, ranking, hidden, needsReview }; - }) - .filter((item) => item.rows.length) - .sort((a, b) => Number(b.needsReview) - Number(a.needsReview) || b.ranking.length - a.ranking.length) - .slice(0, 8); - }, [documents]); - - if (!items.length) return null; - - async function mutate(documentId: string, method: "POST" | "PATCH", body: LabelReviewMutationBody, actionId: string) { - setBusyAction(actionId); - try { - return await onMutateLabel(documentId, method, body); - } finally { - setBusyAction(null); - } - } - - function draftFor(documentId: string) { - return overrideDrafts[documentId] ?? { label: "", labelType: "topic" as DocumentLabelType }; - } - - function setDraft(documentId: string, next: { label: string; labelType: DocumentLabelType }) { - setOverrideDrafts((current) => ({ ...current, [documentId]: next })); - } - - return ( -
- - - - - - - Label review - - Visible labels, ranking labels, hidden labels, confidence, and manual overrides - - - - - -
- {items.map((item) => { - const draft = draftFor(item.document.id); - return ( -
-
-
- - {documentDisplayTitle(item.document)} - -

- {item.visible.length} visible · {item.ranking.length} ranking · {item.hidden.length} hidden -

-
- {item.needsReview ? ( - Needs review - ) : ( - Reviewed - )} -
- - {( - [ - { title: "Visible", rows: item.visible }, - { title: "Ranking", rows: item.ranking }, - { title: "Hidden", rows: item.hidden }, - ] satisfies Array<{ title: string; rows: typeof item.rows }> - ).map(({ title, rows: labelRows }) => { - if (!labelRows.length) return null; - return ( -
-

- {title} -

-
- {labelRows.slice(0, 8).map((label) => ( -
-
-
- - {label.displayLabel} - - - {label.tier} - - - {labelTypeDisplay(label.labelType)} - -
-

- {label.source} · {Math.round(label.confidence * 100)}% · {label.reviewStatus} -

-
-
- {label.reviewStatus === "hidden" ? ( - - ) : ( - <> - - - - )} -
-
- ))} -
-
- ); - })} - -
{ - event.preventDefault(); - const trimmed = draft.label.trim(); - if (!trimmed) return; - void mutate( - item.document.id, - "POST", - { label: trimmed, label_type: draft.labelType }, - `override:${item.document.id}`, - ).then((ok) => { - if (ok) setDraft(item.document.id, { label: "", labelType: draft.labelType }); - }); - }} - > - setDraft(item.document.id, { ...draft, label: event.target.value })} - disabled={!canManage || busyAction !== null} - placeholder="Manual override label" - aria-label="Manual override label" - className={fieldControlPlain} - /> - - -
-
- ); - })} -
-
- ); -} - -function DocumentTagQualityPanel({ documents }: { documents: ClinicalDocument[] }) { - const issues = useMemo(() => reviewDocumentTagQuality(documents), [documents]); - const counts = issues.reduce>( - (current, issue) => ({ ...current, [issue.kind]: current[issue.kind] + 1 }), - { noisy: 0, duplicate: 0, low_confidence: 0, overused: 0 }, - ); - - return ( -
- - - - - - - Tag quality review - - {issues.length - ? `${issues.length} issue${issues.length === 1 ? "" : "s"} across loaded documents` - : "No obvious tag cleanup issues in loaded documents"} - - - - - -
-
- {(Object.keys(counts) as SmartDocumentTagQualityIssueKind[]).map((kind) => ( - - {tagQualityLabel(kind)}: {counts[kind]} - - ))} -
- {issues.length ? ( -
- {issues.slice(0, 12).map((issue, index) => ( -
-
- - {tagQualityLabel(issue.kind)} - -

{issue.label}

- {issue.count > 1 ? ( - {issue.count} hits - ) : null} -
-

{issue.reason}

- {issue.examples.length || issue.documentTitles.length ? ( -

- {[ - issue.examples.length ? `examples: ${issue.examples.join(", ")}` : "", - issue.documentTitles.length ? `docs: ${issue.documentTitles.join(", ")}` : "", - ] - .filter(Boolean) - .join(" · ")} -

- ) : null} -
- ))} -
- ) : ( -

Loaded tags are clean enough for the current smart-tag rules.

- )} -
-
- ); -} - -function DocumentIndexRepairPanel({ documents }: { documents: ClinicalDocument[] }) { - const items = useMemo(() => { - return documents - .map((document) => { - const metadata = document.metadata && typeof document.metadata === "object" ? document.metadata : {}; - const score = Number((metadata as Record).index_quality_score ?? 1); - const issues = Array.isArray((metadata as Record).index_quality_issues) - ? ((metadata as Record).index_quality_issues as unknown[]).map(String) - : []; - const sectionCount = Number((metadata as Record).section_count ?? 0); - const memoryCardCount = Number((metadata as Record).memory_card_count ?? 0); - const extractionQuality = String((metadata as Record).extraction_quality ?? "unknown"); - const needsRepair = - score < 0.72 || - issues.length > 0 || - sectionCount === 0 || - memoryCardCount === 0 || - extractionQuality === "poor" || - extractionQuality === "partial"; - return { document, score, issues, sectionCount, memoryCardCount, extractionQuality, needsRepair }; - }) - .filter((item) => item.needsRepair) - .sort((a, b) => a.score - b.score || b.issues.length - a.issues.length) - .slice(0, 10); - }, [documents]); - - if (!items.length) return null; - - return ( -
- - - - - - - Index repair queue - - {items.length} loaded document{items.length === 1 ? "" : "s"} need quality review or reindexing - - - - - -
- {items.map((item) => ( -
-
-

{item.document.title}

- - index {Number.isFinite(item.score) ? item.score.toFixed(2) : "n/a"} - -
-
- extraction:{item.extractionQuality} - sections:{item.sectionCount} - memory:{item.memoryCardCount} - {item.issues.slice(0, 4).map((issue) => ( - - {issue} - - ))} -
-
- ))} -
-
- ); -} - -function DocumentDrawer({ - documents, - pagination, - loadingMoreDocuments, - mode, - selectedDocumentIds, - statusFilter, - onToggleScope, - onLoadMoreDocuments, - onDocumentRenamed, - onDocumentDeleted, - onBulkReindex, - onBulkAssignCollection, - onBulkMetadataUpdate, - bulkActionStatus, - bulkActionBusy, - canManageDocuments, - onTagSearch, - onMutateLabel, -}: { - documents: ClinicalDocument[]; - pagination: DocumentPagination | null; - loadingMoreDocuments: boolean; - mode: DocumentDrawerMode; - selectedDocumentIds: string[]; - statusFilter: DocumentDrawerStatusFilter; - onToggleScope: (documentId: string) => void; - onLoadMoreDocuments: () => void; - onDocumentRenamed: (document: ClinicalDocument) => void; - onDocumentDeleted: (result: DocumentDeleteResult) => void; - onBulkReindex: (mode: "enrichment" | "full" | "retry_failed") => void; - onBulkAssignCollection: (collection: string) => void; - onBulkMetadataUpdate: (metadata: Record) => void; - bulkActionStatus: string | null; - bulkActionBusy: boolean; - canManageDocuments: boolean; - onTagSearch: (tag: SmartDocumentTag) => void; - onMutateLabel: (documentId: string, method: "POST" | "PATCH", body: LabelReviewMutationBody) => Promise; -}) { - const [filter, setFilter] = useState(""); - const [selectedType, setSelectedType] = useState("all"); - const [selectedSite, setSelectedSite] = useState("all"); - const [selectedTopic, setSelectedTopic] = useState("all"); - const [selectedPopulation, setSelectedPopulation] = useState("all"); - const [showNeedsReviewOnly, setShowNeedsReviewOnly] = useState(false); - - const [collectionDraft, setCollectionDraft] = useState(""); - const [metadataDraft, setMetadataDraft] = useState({ - sourceStatus: "", - validationStatus: "", - extractionQuality: "", - reviewDate: "", - publicationDate: "", - jurisdiction: "", - sourceType: "", - category: "", - }); - - const allTypes = useMemo(() => { - const types = new Set(); - for (const doc of documents) { - const typeLabel = doc.labels?.find((l) => l.label_type === "document_type" && l.confidence >= 0.5)?.label; - if (typeLabel) types.add(typeLabel); - const profile = documentOrganizationProfile(doc); - if (profile?.document_type?.label && profile.document_type.label !== "unknown") { - types.add(profile.document_type.label); - } - } - return Array.from(types).sort(); - }, [documents]); - - const allSites = useMemo(() => { - const sites = new Set(); - for (const doc of documents) { - const siteLabels = doc.labels?.filter((l) => l.label_type === "site" && l.confidence >= 0.5) ?? []; - for (const l of siteLabels) sites.add(l.label); - const profile = documentOrganizationProfile(doc); - if (profile?.site?.label) sites.add(profile.site.label); - } - return Array.from(sites).sort(); - }, [documents]); - - const allTopics = useMemo(() => { - const topics = new Set(); - for (const doc of documents) { - const topicLabels = - doc.labels?.filter((l) => (l.label_type === "topic" || l.label_type === "custom") && l.confidence >= 0.5) ?? []; - for (const l of topicLabels) topics.add(l.label); - const profile = documentOrganizationProfile(doc); - if (profile?.secondary_facets?.topic) { - for (const t of profile.secondary_facets.topic) topics.add(t); - } - } - return Array.from(topics).sort(); - }, [documents]); - - const allPopulations = useMemo(() => { - const populations = new Set(); - for (const doc of documents) { - const popLabels = doc.labels?.filter((l) => l.label_type === "population" && l.confidence >= 0.5) ?? []; - for (const l of popLabels) populations.add(l.label); - const profile = documentOrganizationProfile(doc); - if (profile?.secondary_facets?.population) { - for (const p of profile.secondary_facets.population) populations.add(p); - } - } - return Array.from(populations).sort(); - }, [documents]); - - const isAdminMode = mode === "admin" && canManageDocuments; - const filterValue = filter.toLowerCase(); - const sourcePdfCount = useMemo( - () => - documents.filter((document) => { - const typeText = `${document.file_type} ${document.file_name}`.toLowerCase(); - return documentStatusMatchesFilter(document, statusFilter) && typeText.includes("pdf"); - }).length, - [documents, statusFilter], - ); - - const filtered = documents - .filter((document) => { - if (!documentStatusMatchesFilter(document, statusFilter)) return false; - if (mode === "source") { - const typeText = `${document.file_type} ${document.file_name}`.toLowerCase(); - if (!typeText.includes("pdf")) return false; - } - - // Filter by Type - if (selectedType !== "all") { - const typeLabel = document.labels?.find((l) => l.label_type === "document_type" && l.confidence >= 0.5)?.label; - const profile = documentOrganizationProfile(document); - const hasTypeMatch = typeLabel === selectedType || profile?.document_type?.label === selectedType; - if (!hasTypeMatch) return false; - } - - // Filter by Site - if (selectedSite !== "all") { - const siteLabels = document.labels?.filter((l) => l.label_type === "site" && l.confidence >= 0.5) ?? []; - const profile = documentOrganizationProfile(document); - const hasSiteMatch = siteLabels.some((l) => l.label === selectedSite) || profile?.site?.label === selectedSite; - if (!hasSiteMatch) return false; - } - - // Filter by Topic - if (selectedTopic !== "all") { - const topicLabels = - document.labels?.filter( - (l) => (l.label_type === "topic" || l.label_type === "custom") && l.confidence >= 0.5, - ) ?? []; - const profile = documentOrganizationProfile(document); - const hasTopicMatch = - topicLabels.some((l) => l.label === selectedTopic) || - profile?.secondary_facets?.topic?.includes(selectedTopic); - if (!hasTopicMatch) return false; - } - - // Filter by Population - if (selectedPopulation !== "all") { - const popLabels = document.labels?.filter((l) => l.label_type === "population" && l.confidence >= 0.5) ?? []; - const profile = documentOrganizationProfile(document); - const hasPopMatch = - popLabels.some((l) => l.label === selectedPopulation) || - profile?.secondary_facets?.population?.includes(selectedPopulation); - if (!hasPopMatch) return false; - } - - // Filter by Needs Review - if (showNeedsReviewOnly) { - const profile = documentOrganizationProfile(document); - if (profile?.review_status !== "needs_review") return false; - } - - const labelText = tagSearchText(document); - const summaryText = document.summary?.summary ?? ""; - const haystack = `${document.title} ${document.file_name} ${labelText} ${summaryText}`.toLowerCase(); - return haystack.includes(filterValue); - }) - .sort((left, right) => { - if (mode !== "recent") return 0; - return new Date(right.updated_at).getTime() - new Date(left.updated_at).getTime(); - }); - const availableDocumentCount = mode === "source" ? sourcePdfCount : (pagination?.total ?? documents.length); - const statusTitle = - mode === "recent" - ? `${availableDocumentCount.toLocaleString()} recent source${availableDocumentCount === 1 ? "" : "s"}` - : mode === "source" - ? `${availableDocumentCount.toLocaleString()} source PDF${availableDocumentCount === 1 ? "" : "s"}` - : isAdminMode - ? `${statusFilterLabel(statusFilter)}: ${filtered.length.toLocaleString()} shown` - : `${availableDocumentCount.toLocaleString()} indexed source${availableDocumentCount === 1 ? "" : "s"}`; - const statusHelper = - availableDocumentCount === 0 - ? mode === "recent" - ? "Recent source rows will appear here after indexing." - : mode === "source" - ? "Indexed PDF source rows will appear below." - : "Indexed source rows will appear below." - : mode === "recent" - ? "Continue reading from the most recently updated sources." - : mode === "source" - ? "Open original PDF source documents." - : "Search and filter to open indexed clinical sources."; - - return ( -
-
- - -
-

{statusTitle}

-

{statusHelper}

-
- - {filtered.length.toLocaleString()} shown - -
- - - {/* Dynamic Browse Library Filters */} -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - {/* Admin Queue Toggle */} - {isAdminMode ? ( -
- setShowNeedsReviewOnly(e.target.checked)} - className="rounded border-[color:var(--border)] text-[color:var(--clinical-accent)] focus:ring-[color:var(--focus)] h-4 w-4" - /> - -
- ) : null} - {pagination && pagination.total > documents.length ? ( -

- Showing {documents.length} of {pagination.total} documents. Load more to manage older files. -

- ) : null} - {isAdminMode ? ( - - ) : null} - {isAdminMode ? : null} - {isAdminMode ? : null} - {isAdminMode && selectedDocumentIds.length ? ( -
-
-
-

- {selectedDocumentIds.length} selected document{selectedDocumentIds.length === 1 ? "" : "s"} -

-

Bulk actions apply only to explicitly selected documents.

-
-
- - - -
-
-
- setCollectionDraft(event.target.value)} - placeholder="Collection name for selected documents" - aria-label="Collection name for selected documents" - className={fieldControlPlain} - /> - -
-
- - Bulk metadata editor - -
- - - - setMetadataDraft((current) => ({ ...current, reviewDate: event.target.value }))} - className={fieldControlPlain} - aria-label="Bulk review date" - /> - - setMetadataDraft((current) => ({ ...current, publicationDate: event.target.value })) - } - className={fieldControlPlain} - aria-label="Bulk publication date" - /> - setMetadataDraft((current) => ({ ...current, jurisdiction: event.target.value }))} - placeholder="Jurisdiction/locality" - aria-label="Bulk edit jurisdiction/locality" - className={fieldControlPlain} - /> - setMetadataDraft((current) => ({ ...current, sourceType: event.target.value }))} - placeholder="Source type" - aria-label="Bulk edit source type" - className={fieldControlPlain} - /> - setMetadataDraft((current) => ({ ...current, category: event.target.value }))} - placeholder="Category" - aria-label="Bulk edit category" - className={fieldControlPlain} - /> -
- -
- {bulkActionStatus ?

{bulkActionStatus}

: null} -
- ) : null} -
- {filtered.length === 0 ? ( - - ) : ( - filtered.slice(0, 12).map((document) => { - const selected = selectedDocumentIds.includes(document.id); - return ( -
-
- - {documentDisplayTitle(document)} - - - -

- {document.page_count} pages · {document.chunk_count} chunks · {document.image_count} images -

- {document.summary?.summary && ( -

- -

- )} - - -
-
- - - {isAdminMode ? ( - - ) : null} - -
-
- ); - }) - )} -
- {pagination?.hasMore ? ( - - ) : null} -
- ); -} - type LibraryHealthTarget = "documents" | "setup" | "indexing" | "failures"; -type DocumentDrawerMode = "recent" | "library" | "source" | "admin"; -type DocumentDrawerStatusFilter = "all" | "indexed" | "indexing" | "failed"; type IndexingMonitorFilter = "all" | "active" | "failed"; type UploadIndexingTab = "setup" | "upload" | "jobs" | "quality"; -function documentStatusMatchesFilter(document: ClinicalDocument, filter: DocumentDrawerStatusFilter) { - if (filter === "all") return true; - if (filter === "indexed") return document.status === "indexed"; - if (filter === "indexing") return document.status === "queued" || document.status === "processing"; - return document.status === "failed"; -} - -function statusFilterLabel(filter: DocumentDrawerStatusFilter) { - if (filter === "indexed") return "Indexed documents"; - if (filter === "indexing") return "Indexing documents"; - if (filter === "failed") return "Failed documents"; - return "All documents"; -} - -function DrawerGroupLabel({ title }: { title: string }) { - return ( -

{title}

- ); -} - export function SettingsDialog({ open, onClose, @@ -5693,7 +4642,7 @@ export function ClinicalDashboard({ loading={loading} documentCount={indexedDocumentTotal} recentDocuments={documents} - realDataReady={canRunSearch} + realDataReady={searchMode === "services" || searchMode === "forms" ? true : canRunSearch} authUnavailable={false} apiUnavailable={apiUnavailable} setupWarning={setupWarning} diff --git a/src/components/applications-launcher-page.tsx b/src/components/applications-launcher-page.tsx index 6c6f7f5367..49549d059d 100644 --- a/src/components/applications-launcher-page.tsx +++ b/src/components/applications-launcher-page.tsx @@ -835,14 +835,15 @@ function DetailDialog({ app, open, onClose }: { app: LauncherApp; open: boolean; { icon: ClipboardList, label: "Needed input" }, { icon: Waves, label: "Output" }, ].map(({ icon: Icon, label }) => ( -
{label} - Soon -
+ + ))} @@ -1018,9 +1019,7 @@ export function ApplicationsLauncherWorkspace({
Sort by A to Z - - Soon - +
diff --git a/src/components/clinical-dashboard/ClinicalSidebar.tsx b/src/components/clinical-dashboard/ClinicalSidebar.tsx index d9ec1a2229..d9172af478 100644 --- a/src/components/clinical-dashboard/ClinicalSidebar.tsx +++ b/src/components/clinical-dashboard/ClinicalSidebar.tsx @@ -368,7 +368,9 @@ function ClinicalCollapsedRail({ ) : ( <> - + {/* Tablet: the expanded panel does not exist below lg, so show a + static brand mark instead of a dead expand control. */} + )} diff --git a/src/components/clinical-dashboard/differentials-home.tsx b/src/components/clinical-dashboard/differentials-home.tsx index 6fcbb62264..29d868eedc 100644 --- a/src/components/clinical-dashboard/differentials-home.tsx +++ b/src/components/clinical-dashboard/differentials-home.tsx @@ -690,15 +690,6 @@ function SearchResultsView({ data-testid="differentials-search-results" className="mx-auto grid w-full max-w-[86rem] gap-4 overflow-x-hidden px-3 pb-[calc(9rem+env(safe-area-inset-bottom))] sm:px-4 lg:px-0 lg:pb-0" > -
- -
diff --git a/src/components/clinical-dashboard/document-search-results.tsx b/src/components/clinical-dashboard/document-search-results.tsx index 6809fb1d14..5efffa0ad0 100644 --- a/src/components/clinical-dashboard/document-search-results.tsx +++ b/src/components/clinical-dashboard/document-search-results.tsx @@ -44,8 +44,6 @@ import { sourceCard, textMuted, } from "@/components/ui-primitives"; - -const disabledControlClass = "cursor-not-allowed opacity-60"; import { buildSmartDocumentTagFacetIndex, filterDocumentsBySmartTagFacetIndex, @@ -377,12 +375,9 @@ function SearchResultsHeader({ resultLabel, trimmedQuery }: { resultLabel: strin ); })} -
+
{onSortChange ? (