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
52 changes: 35 additions & 17 deletions src/components/DocumentViewer.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -474,32 +474,39 @@ function TableReviewPanel({

function DocumentViewerAnchors({
evidenceHref,
textHref,
className,
}: {
evidenceHref: "#source-evidence" | "#source-evidence-rail";
textHref: "#source-text-mobile" | "#source-text-desktop";
className?: string;
}) {
const anchors = [
{ label: "Evidence", href: evidenceHref },
{ label: "PDF", href: "#pdf-preview-section" },
{ label: "Summary", href: "#source-summary" },
{ label: "Images", href: "#source-images" },
{ label: "Evidence", href: evidenceHref, icon: Quote },
{ label: "PDF", href: "#pdf-preview-section", icon: FileText },
{ label: "Text", href: textHref, icon: Search },
{ label: "Summary", href: "#source-summary", icon: Sparkles },
{ label: "Images", href: "#source-images", icon: FileImage },
];

return (
<nav
aria-label="Document viewer sections"
className={cn("flex gap-2 overflow-x-auto pb-1 polished-scroll", className)}
>
{anchors.map((anchor) => (
<a
key={anchor.href}
href={anchor.href}
className="inline-flex min-h-9 shrink-0 items-center rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] px-3 text-xs font-semibold text-[color:var(--primary)] shadow-[var(--shadow-tight)]"
>
{anchor.label}
</a>
))}
{anchors.map((anchor) => {
const Icon = anchor.icon;
return (
<a
key={anchor.href}
href={anchor.href}
className="inline-flex min-h-10 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(--primary)] shadow-[var(--shadow-tight)] hover:bg-[color:var(--surface-subtle)]"
>
<Icon className="h-3.5 w-3.5" />
{anchor.label}
</a>
);
})}
</nav>
);
}
Expand DownExpand Up@@ -744,6 +751,7 @@ function IndexedTextPanel({
searchingDocument,
documentSearchError,
idPrefix,
sectionId,
selectedChunkId,
onSearchChange,
}: {
Expand All@@ -755,6 +763,7 @@ function IndexedTextPanel({
searchingDocument: boolean;
documentSearchError: string | null;
idPrefix: string;
sectionId?: "source-text-mobile" | "source-text-desktop";
selectedChunkId?: string;
onSearchChange: (value: string) => void;
}) {
Expand DownExpand Up@@ -804,7 +813,11 @@ function IndexedTextPanel({
}

return (
<section data-testid={`${idPrefix}-indexed-text-panel`} className={cn(panel, "p-5 source-print")}>
<section
id={sectionId}
data-testid={`${idPrefix}-indexed-text-panel`}
className={cn(panel, "scroll-mt-24 p-5 source-print")}
>
<PanelHeading
icon={FileText}
title="Indexed source text"
Expand DownExpand Up@@ -2366,7 +2379,7 @@ export function DocumentViewer({

<div className="min-w-0 space-y-4 sm:space-y-5">
<div className="lg:hidden">
<DocumentViewerAnchors evidenceHref="#source-evidence" className="mb-3" />
<DocumentViewerAnchors evidenceHref="#source-evidence" textHref="#source-text-mobile" className="mb-3" />
<PinnedSourceEvidence
loading={effectiveLoadingDocument}
chunk={selectedChunk}
Expand All@@ -2375,7 +2388,7 @@ export function DocumentViewer({
/>
</div>

<details className={cn("group lg:hidden", panel)}>
<details id="source-text-mobile" className={cn("group scroll-mt-24 lg:hidden", panel)}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Open the mobile text panel when jumping to Text

When viewed on mobile, the new Text nav item points at this closed <details> element. Fragment navigation scrolls to the disclosure but does not set open, so the indexed text/search panel remains hidden; the smoke assertion only sees the summary label. Users jumping back from the PDF still need an extra, non-obvious tap to reveal the source text, so please open the disclosure for #source-text-mobile or target an always-open element.

Useful? React with 👍 / 👎.

<summary className="flex min-h-[56px] cursor-pointer list-none items-center justify-between gap-3 px-4 py-3">
<span className="inline-flex min-w-0 items-center gap-3">
<span className="grid h-9 w-9 shrink-0 place-items-center rounded-lg border border-[color:var(--primary)]/20 bg-[color:var(--primary-soft)] text-[color:var(--primary)] shadow-[var(--shadow-inset)]">
Expand DownExpand Up@@ -2475,6 +2488,7 @@ export function DocumentViewer({
searchingDocument={searchingDocument}
documentSearchError={documentSearchError}
idPrefix="desktop-chunk"
sectionId="source-text-desktop"
selectedChunkId={chunkId}
onSearchChange={setSourceSearch}
/>
Expand All@@ -2483,7 +2497,11 @@ export function DocumentViewer({

<aside className="min-w-0 space-y-4 sm:space-y-5">
<div className="hidden lg:block">
<DocumentViewerAnchors evidenceHref="#source-evidence-rail" className="mb-3" />
<DocumentViewerAnchors
evidenceHref="#source-evidence-rail"
textHref="#source-text-desktop"
className="mb-3"
/>
<PinnedSourceEvidence
loading={effectiveLoadingDocument}
chunk={selectedChunk}
Expand Down
5 changes: 5 additions & 0 deletions tests/ui-smoke.spec.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -1137,6 +1137,7 @@ test.describe("Clinical KB UI smoke coverage", () => {
await expect(evidence.getByText("Highlighted source passage")).toBeVisible();
await expect(viewerNav.getByRole("link", { name: "Evidence" })).toBeVisible();
await expect(viewerNav.getByRole("link", { name: "PDF" })).toBeVisible();
await expect(viewerNav.getByRole("link", { name: "Text" })).toBeVisible();
await expect(page.getByRole("heading", { level: 1, name: "Synthetic lithium monitoring protocol" })).toBeVisible();
await expect(preview).toBeVisible();
await expect(toolbar).toBeVisible({ timeout: 30000 });
Expand DownExpand Up@@ -1164,6 +1165,10 @@ test.describe("Clinical KB UI smoke coverage", () => {
expect(expandedEvidenceBox?.height ?? 0).toBeGreaterThan(evidenceBox!.height);
await viewerNav.getByRole("link", { name: "PDF" }).click();
await expect(preview).toBeInViewport();
await viewerNav.getByRole("link", { name: "Text" }).click();
await expect(page.getByText("Indexed page text", { exact: true })).toBeInViewport();
await viewerNav.getByRole("link", { name: "PDF" }).click();
await expect(preview).toBeInViewport();

const mobilePdfStyles = await toolbar.evaluate((element) => ({
position: window.getComputedStyle(element).position,
Expand Down