From 49f63791bced2b1764a11ab723aea94b45b026b6 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:12:20 +0800 Subject: [PATCH 1/3] fix(documents): default viewer sections closed --- src/app/globals.css | 8 + src/components/DocumentViewer.tsx | 248 ++++++++++++++++++++---------- tests/ui-smoke.spec.ts | 54 +++++++ 3 files changed, 230 insertions(+), 80 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 2880729549..ab2b420f31 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2163,6 +2163,14 @@ summary::-webkit-details-marker { box-shadow: none !important; } + details.source-print > summary { + display: none !important; + } + + details.source-print > :not(summary) { + display: block !important; + } + a { color: #000; text-decoration: underline; diff --git a/src/components/DocumentViewer.tsx b/src/components/DocumentViewer.tsx index 220171a76d..0c1d2fd3a7 100644 --- a/src/components/DocumentViewer.tsx +++ b/src/components/DocumentViewer.tsx @@ -25,6 +25,7 @@ import { Pencil, Trash2, X, + type LucideIcon, } from "lucide-react"; import { type FormEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { AccessibleTable, hasRenderableAccessibleTable } from "@/components/AccessibleTable"; @@ -540,7 +541,7 @@ function TableReviewPanel({ }) { if (!tableFacts.length) return null; return ( -
+
Table review queue ({tableFacts.length}) @@ -623,6 +624,10 @@ function DocumentViewerAnchors({ { + const target = window.document.querySelector(anchor.href); + if (target instanceof HTMLDetailsElement) target.open = true; + }} className="inline-flex min-h-tap shrink-0 items-center gap-1.5 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] px-3 text-xs font-semibold text-[color:var(--clinical-accent)] shadow-[var(--shadow-tight)] transition hover:bg-[color:var(--surface-subtle)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]" > @@ -634,6 +639,40 @@ function DocumentViewerAnchors({ ); } +function DocumentSectionSummary({ + icon: Icon, + title, + description, +}: { + icon: LucideIcon; + title: string; + description: string; +}) { + return ( + + + + + + + {title} + + {description} + + + + ); +} + function PinnedSourceEvidence({ loading, chunk, @@ -1567,6 +1606,29 @@ export function DocumentViewer({ chunkId?: string; }) { const router = useRouter(); + useEffect(() => { + const previousOpenStates = new Map(); + const expandPrintableDisclosures = () => { + previousOpenStates.clear(); + window.document.querySelectorAll("details.source-print").forEach((disclosure) => { + previousOpenStates.set(disclosure, disclosure.open); + disclosure.open = true; + }); + }; + const restorePrintableDisclosures = () => { + previousOpenStates.forEach((wasOpen, disclosure) => { + if (disclosure.isConnected) disclosure.open = wasOpen; + }); + previousOpenStates.clear(); + }; + window.addEventListener("beforeprint", expandPrintableDisclosures); + window.addEventListener("afterprint", restorePrintableDisclosures); + return () => { + restorePrintableDisclosures(); + window.removeEventListener("beforeprint", expandPrintableDisclosures); + window.removeEventListener("afterprint", restorePrintableDisclosures); + }; + }, []); const [document, setDocument] = useState(null); const [pages, setPages] = useState([]); const [images, setImages] = useState([]); @@ -2641,7 +2703,11 @@ export function DocumentViewer({ compact sectionId="source-evidence" /> -
+
@@ -2722,91 +2788,98 @@ export function DocumentViewer({ {document ? ( -
- - - {document.summary?.clinical_specifics?.profile ? ( - - ) : ( - - )} - {!document.summary?.clinical_specifics?.profile && document.summary?.clinical_specifics && ( -
- {Object.entries(document.summary.clinical_specifics) - .filter(([key, items]) => key !== "profile" && Array.isArray(items) && items.length > 0) - .slice(0, 6) - .map(([key, items]) => ( -
-

- {key.replaceAll("_", " ")} -

-
    - {(items as string[]).slice(0, 5).map((item, index) => ( -
  • -
  • - ))} -
-
- ))} -
- )} - {document.labels?.length ? ( -
-

Browse by tag

- -
- ) : null} - {canUsePrivateApis ? ( -
- - Document tools - - + + {document.summary?.clinical_specifics?.profile ? ( + + ) : ( + -
- ) : null} -
+ )} + {!document.summary?.clinical_specifics?.profile && document.summary?.clinical_specifics && ( +
+ {Object.entries(document.summary.clinical_specifics) + .filter(([key, items]) => key !== "profile" && Array.isArray(items) && items.length > 0) + .slice(0, 6) + .map(([key, items]) => ( +
+

+ {key.replaceAll("_", " ")} +

+
    + {(items as string[]).slice(0, 5).map((item, index) => ( +
  • +
  • + ))} +
+
+ ))} +
+ )} + {document.labels?.length ? ( +
+

Browse by tag

+ +
+ ) : null} + {canUsePrivateApis ? ( +
+ + Document tools + + +
+ ) : null} + +
) : null} -
- + -
+
{canUsePrivateApis && tableFacts.length ? (
@@ -2853,12 +2926,27 @@ export function DocumentViewer({
) : null}
-
+
{indexHealth ? ( -
- Indexing details -
+
+ + Indexing details + +
Extraction
{indexHealth.extractionQuality ?? "unknown"}
diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index ce50eeff9c..fcb53ec723 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -2927,8 +2927,14 @@ test.describe("Clinical KB UI smoke coverage", () => { await mockDemoApi(page); await gotoApp(page, "/documents/11111111-1111-4111-8111-111111111111?page=1"); + await expect(page.getByRole("heading", { level: 1, name: "Synthetic lithium monitoring protocol" })).toBeVisible({ + timeout: 30_000, + }); const summaryCard = page.getByTestId("high-yield-summary"); await expect(summaryCard).toBeVisible(); + await expect(summaryCard).toHaveJSProperty("open", false); + await summaryCard.getByText("High-yield summary", { exact: true }).click(); + await expect(summaryCard).toHaveJSProperty("open", true); // Smart summary: badge cluster from labels + detected phrases, structured // sections, and no document-header boilerplate leaking through. await expect(summaryCard.getByText("Narrow therapeutic index", { exact: true })).toBeVisible(); @@ -2949,6 +2955,54 @@ test.describe("Clinical KB UI smoke coverage", () => { await expectNoPageHorizontalOverflow(page); }); + test("document viewer content disclosures are naturally closed and mutually exclusive by default", async ({ + page, + }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await mockDemoApi(page); + await gotoApp(page, "/documents/11111111-1111-4111-8111-111111111111?page=1"); + + await expect(page.getByRole("heading", { level: 1, name: "Synthetic lithium monitoring protocol" })).toBeVisible({ + timeout: 30_000, + }); + const indexedText = page.locator("#source-text-mobile"); + const summary = page.getByTestId("high-yield-summary"); + const images = page.locator("#source-images"); + const indexingDetails = page.getByTestId("indexing-details"); + const viewerNav = page.getByRole("navigation", { name: "Document viewer sections" }).first(); + + for (const disclosure of [indexedText, summary, images, indexingDetails]) { + await expect(disclosure).toHaveJSProperty("open", false); + } + + const summaryContent = summary.getByTestId("formatted-high-yield-summary"); + await expect(summaryContent).toBeHidden(); + await page.evaluate(() => window.dispatchEvent(new Event("beforeprint"))); + await page.emulateMedia({ media: "print" }); + await expect(summaryContent).toBeVisible(); + await page.emulateMedia({ media: "screen" }); + await page.evaluate(() => window.dispatchEvent(new Event("afterprint"))); + await expect(summaryContent).toBeHidden(); + + await viewerNav.getByRole("link", { name: "Text" }).click(); + await expect(indexedText).toHaveJSProperty("open", true); + + await viewerNav.getByRole("link", { name: "Summary" }).click(); + await expect(summary).toHaveJSProperty("open", true); + await expect(indexedText).toHaveJSProperty("open", false); + + await viewerNav.getByRole("link", { name: "Images" }).click(); + await expect(images).toHaveJSProperty("open", true); + await expect(summary).toHaveJSProperty("open", false); + + await indexingDetails.getByText("Indexing details", { exact: true }).click(); + await expect(indexingDetails).toHaveJSProperty("open", true); + await expect(images).toHaveJSProperty("open", false); + + await expectDomIntegrity(page); + await expectNoPageHorizontalOverflow(page); + }); + test("answer glass header overlays main and fully hides while scrolling on phones", async ({ page }) => { await page.setViewportSize({ width: 390, height: 844 }); await gotoApp(page, "/?mode=answer"); From eec68d2ee7d2ffbdea6c6106a489e2fdd7bfa63f Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:13:26 +0800 Subject: [PATCH 2/3] docs(review): record document disclosure audit --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 66f527cc42..1983cc6e5d 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -557,3 +557,4 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD | 2026-07-15 | PR #679 / claude/therapy-compass-pages-rz0m5l | f5ca25f8b6b14f41e63f708933fe4bb311994795 | Therapy Compass production promotion and review-followup | All five review findings are fixed on the reviewed head: run-enabled shared-composer links stay on the Therapy Compass route, production data loads outside `/mockups`, deep links seed the in-tool search, and same-route query changes remount the provider. No additional high-confidence defect remains in the changed scope. | GitHub review-thread inventory; exact-head hosted build, critical UI smoke, UI regression, unit coverage, static, security, and image checks green; focused Vitest 28/28; TypeScript; `git diff --check`. No live Supabase/OpenAI checks run. | | 2026-07-15 | PR #680 / claude/rag-scalability-review-x0s55l | 32e242ab7fc386ea82b19c7cfc2112aa41f06f9a | privacy, public-catalog throttling, ingestion-recovery, and merge-readiness review | Audit remediation wave 1 plus review follow-ups. Confirmed and fixed: mixed-owner document list/detail responses exposed nested summary internals and free-form document metadata for public rows; anonymous catalog rate limiting skipped known-slug detail routes; and ingestion recovery could retry a failed row without seeing a legitimate pending/fresh-processing sibling. Ownership-specific projections/redaction now cover list and detail responses, every catalog detail path is throttled, and both recovery scripts pass every open sibling to the planner. No remaining unresolved review thread or high-confidence defect. | GitHub exact-head review-thread inspection (0 unresolved); hosted required CI, UI regression, migration replay, build, coverage, static, and security checks green; local focused route/recovery Vitest 163/163; TypeScript; earlier full `verify:cheap`; Prettier; `git diff --check`. No live Supabase/OpenAI/provider checks run. | | 2026-07-15 | PR #682 / claude/codex-builder-perf-flakiness-ck5yv7 | dc942dddb87c8a1fa8d84d88052b3d50dd883a43 + reviewed follow-up diff | CI performance, cache correctness, browser-lane routing, and merge-readiness review | Confirmed and fixed two P2 CI safety gaps: the cached `node_modules` key ignored `package.json` and `.npmrc`, allowing install-contract changes to reuse stale modules and skip `npm ci`; and removing the advisory quarantine job made any newly tagged UI test run nowhere. The cache now covers every install input and the quarantine lane performs only a cheap source scan when empty, preserving the speed improvement without losing coverage. No remaining unresolved review thread or high-confidence defect. | GitHub exact-head checks green before final main sync; focused Vitest 6/6; GitHub Actions pin check; CI-scope self-test; TypeScript; Prettier; `git diff --check`. Final hosted exact-head checks required after push. No live Supabase/OpenAI checks run. | +| 2026-07-15 | codex/documents-closed-default | 49f63791bced2b1764a11ab723aea94b45b026b6 | documents viewer disclosure defaults and related defect hunt | Fixed the inconsistent default-open document viewer sections by making indexed text, high-yield summary, tables/diagrams, and indexing details a native mutually exclusive closed disclosure group. The section navigation opens its requested disclosure and deep-linked evidence still reveals its target. The hunt also removed the explicitly open nested table-review queue, preserved printable summary content through the browser print lifecycle, and added cold-server readiness guards to the affected viewer tests. No other high-confidence default-open defect remains in the live Documents scope. | `npm run verify:cheap`; TypeScript; focused ESLint/Prettier; clean-worktree mocked Chromium coverage for deep-linked evidence, structured summary, closed/mutually-exclusive disclosures, navigation opening, and print state restore; `git diff --check`. Turbopack could not run through the local external `node_modules` junction, so clean browser verification used Next's supported Webpack dev mode. No Supabase/OpenAI/live-provider checks run. | From e037e19c0cf76f272ec037978f17674c56bc49e4 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 16 Jul 2026 01:34:13 +0800 Subject: [PATCH 3/3] fix(documents): restore disclosure state after print --- src/components/DocumentViewer.tsx | 18 ++++++++++++++---- tests/ui-smoke.spec.ts | 4 ++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/components/DocumentViewer.tsx b/src/components/DocumentViewer.tsx index 0c1d2fd3a7..2ac217e8c9 100644 --- a/src/components/DocumentViewer.tsx +++ b/src/components/DocumentViewer.tsx @@ -1609,15 +1609,25 @@ export function DocumentViewer({ useEffect(() => { const previousOpenStates = new Map(); const expandPrintableDisclosures = () => { + if (previousOpenStates.size) return; previousOpenStates.clear(); - window.document.querySelectorAll("details.source-print").forEach((disclosure) => { - previousOpenStates.set(disclosure, disclosure.open); + const printable = window.document.querySelectorAll("details.source-print"); + window.document + .querySelectorAll('details.source-print, details[name="document-viewer-section"]') + .forEach((disclosure) => { + previousOpenStates.set(disclosure, disclosure.open); + }); + printable.forEach((disclosure) => { disclosure.open = true; }); }; const restorePrintableDisclosures = () => { - previousOpenStates.forEach((wasOpen, disclosure) => { - if (disclosure.isConnected) disclosure.open = wasOpen; + const connected = [...previousOpenStates].filter(([disclosure]) => disclosure.isConnected); + connected.forEach(([disclosure]) => { + disclosure.open = false; + }); + connected.forEach(([disclosure, wasOpen]) => { + if (wasOpen) disclosure.open = true; }); previousOpenStates.clear(); }; diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts index fcb53ec723..ca264d7e95 100644 --- a/tests/ui-smoke.spec.ts +++ b/tests/ui-smoke.spec.ts @@ -2977,15 +2977,19 @@ test.describe("Clinical KB UI smoke coverage", () => { const summaryContent = summary.getByTestId("formatted-high-yield-summary"); await expect(summaryContent).toBeHidden(); + await viewerNav.getByRole("link", { name: "Images" }).click(); + await expect(images).toHaveJSProperty("open", true); await page.evaluate(() => window.dispatchEvent(new Event("beforeprint"))); await page.emulateMedia({ media: "print" }); await expect(summaryContent).toBeVisible(); await page.emulateMedia({ media: "screen" }); await page.evaluate(() => window.dispatchEvent(new Event("afterprint"))); await expect(summaryContent).toBeHidden(); + await expect(images).toHaveJSProperty("open", true); await viewerNav.getByRole("link", { name: "Text" }).click(); await expect(indexedText).toHaveJSProperty("open", true); + await expect(images).toHaveJSProperty("open", false); await viewerNav.getByRole("link", { name: "Summary" }).click(); await expect(summary).toHaveJSProperty("open", true);