+
+
+
{
+ focusDocumentSearchAfterSheetCloseRef.current = true;
setMobileActionsOpen(false);
- setSourceSearch(documentDisplayTitle(readyDocument));
}}
className={cn(secondaryButton, "min-h-12 justify-start text-xs")}
>
@@ -1330,7 +1356,7 @@ export function DocumentViewer({
) : null}
-
+
@@ -1491,7 +1517,7 @@ export function DocumentViewer({
) : null}
-
+
{
event.preventDefault();
- if (canSummarizeDocument) void summarize();
+ if (!canSubmitDocumentSearch) return;
+ window.document
+ .getElementById("source-text")
+ ?.scrollIntoView({ block: "start", behavior: resolveScrollBehavior() });
}}
+ role="search"
+ aria-label="Search within this document"
data-scroll-hidden={composerScrollHidden ? "true" : undefined}
onFocusCapture={() => setComposerChromeFocused(true)}
onBlurCapture={(event) => {
@@ -1707,25 +1738,27 @@ export function DocumentViewer({
- Search or answer from this document
+ Search within this document
setSourceSearch(event.target.value)}
- placeholder="Search or answer from this document..."
+ placeholder="Search within this document..."
className="min-h-tap min-w-0 flex-1 bg-transparent px-2 text-base font-medium text-[color:var(--text)] outline-none placeholder:text-[color:var(--text-soft)]"
/>
- {loadingSummary ? (
-
- ) : (
-
- )}
+
) : null}
diff --git a/src/components/document-top-navigation-mockups.tsx b/src/components/document-top-navigation-mockups.tsx
new file mode 100644
index 0000000000..ef164b870f
--- /dev/null
+++ b/src/components/document-top-navigation-mockups.tsx
@@ -0,0 +1,511 @@
+"use client";
+
+import Link from "next/link";
+import {
+ ArrowLeft,
+ FileImage,
+ FileText,
+ MoreHorizontal,
+ Plus,
+ Quote,
+ Search,
+ Sparkles,
+ Target,
+ type LucideIcon,
+} from "lucide-react";
+import { useId, useRef, useState, type RefObject } from "react";
+
+import { appModeHomeHref } from "@/lib/app-modes";
+import { cn } from "@/components/ui-primitives";
+
+type SectionId = "summary" | "pdf" | "evidence" | "images";
+type Direction = "continuous" | "folder" | "index";
+type Device = "desktop" | "phone";
+
+const documentTitle = "Clinical practice guideline: schizophrenia care";
+const documentHomeHref = appModeHomeHref("documents");
+
+const sections: Array<{
+ id: SectionId;
+ label: string;
+ description: string;
+ icon: LucideIcon;
+}> = [
+ {
+ id: "summary",
+ label: "Summary",
+ description: "Scan the document overview, clinical points, and source-grounded notes.",
+ icon: Sparkles,
+ },
+ {
+ id: "pdf",
+ label: "PDF",
+ description: "Read the original source and move through its pages without leaving the viewer.",
+ icon: FileText,
+ },
+ {
+ id: "evidence",
+ label: "Evidence",
+ description: "Review the selected source passage alongside the page it came from.",
+ icon: Quote,
+ },
+ {
+ id: "images",
+ label: "Images",
+ description: "Browse extracted tables and figures with their page references intact.",
+ icon: FileImage,
+ },
+];
+
+const directionCopy: Record = {
+ continuous: {
+ title: "Continuous rail (recommended)",
+ summary: "The safest evolution: one calm header surface, one seam, and a familiar active underline.",
+ },
+ folder: {
+ title: "Document folder",
+ summary: "The active section lifts out of the navigation strip and joins directly to the page below.",
+ },
+ index: {
+ title: "Section index",
+ summary: "A more editorial direction that makes position and document structure visible at a glance.",
+ },
+};
+
+function HeaderActions({
+ compact,
+ searchInputId,
+ onSearch,
+}: {
+ compact: boolean;
+ searchInputId: string;
+ onSearch: () => void;
+}) {
+ const [scoped, setScoped] = useState(false);
+ const [menuOpen, setMenuOpen] = useState(false);
+ const menuId = useId();
+
+ return (
+
+
+
+
+
setScoped((current) => !current)}
+ aria-label={scoped ? "Remove this document from scope" : "Add this document to scope"}
+ aria-pressed={scoped}
+ title={scoped ? "Added to scope" : "Add this document to scope"}
+ className={cn(
+ "grid h-11 w-11 place-items-center rounded-full transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]",
+ scoped
+ ? "bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)]"
+ : "text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text)]",
+ compact && "hidden min-[350px]:grid",
+ )}
+ >
+
+
+
setMenuOpen((current) => !current)}
+ aria-label="Open document actions"
+ aria-expanded={menuOpen}
+ aria-controls={menuId}
+ className="grid h-11 w-11 place-items-center rounded-full text-[color:var(--text-muted)] transition hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]"
+ >
+ {compact ? (
+
+ ) : (
+
+ )}
+
+
+ {menuOpen ? (
+
+ ) : null}
+
+ );
+}
+
+function TitleRow({
+ compact,
+ searchInputId,
+ onSearch,
+}: {
+ compact: boolean;
+ searchInputId: string;
+ onSearch: () => void;
+}) {
+ return (
+
+
+
+ {compact ? null :
Documents }
+
+
+
+ {documentTitle}
+
+
+
+
+ );
+}
+
+function SectionNavigation({
+ direction,
+ compact,
+ active,
+ onChange,
+}: {
+ direction: Direction;
+ compact: boolean;
+ active: SectionId;
+ onChange: (section: SectionId) => void;
+}) {
+ if (direction === "folder") {
+ return (
+
+ {sections.map((section) => {
+ const selected = active === section.id;
+ const Icon = section.icon;
+ return (
+ onChange(section.id)}
+ aria-current={selected ? "page" : undefined}
+ className={cn(
+ "inline-flex min-h-11 shrink-0 items-center gap-1.5 rounded-t-lg border px-3 text-xs font-semibold transition focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[color:var(--focus)]",
+ selected
+ ? "relative -mb-px border-[color:var(--border)] border-b-[color:var(--surface)] bg-[color:var(--surface)] text-[color:var(--clinical-accent)] shadow-[0_-3px_10px_rgb(15_23_42/0.04)]"
+ : "border-transparent text-[color:var(--text-muted)] hover:bg-[color:var(--surface)]/70 hover:text-[color:var(--text)]",
+ )}
+ >
+
+ {section.label}
+
+ );
+ })}
+
+ );
+ }
+
+ if (direction === "index") {
+ return (
+
+ {sections.map((section, index) => {
+ const selected = active === section.id;
+ return (
+ onChange(section.id)}
+ aria-current={selected ? "page" : undefined}
+ className={cn(
+ "group relative flex min-h-[3.25rem] min-w-[7.25rem] flex-1 shrink-0 items-center gap-2 border-r border-[color:var(--border)] px-3 text-left transition last:border-r-0 focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[color:var(--focus)]",
+ selected
+ ? "bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)]"
+ : "text-[color:var(--text-muted)] hover:bg-[color:var(--surface-subtle)] hover:text-[color:var(--text)]",
+ compact && "min-w-[6.75rem]",
+ )}
+ >
+
+ {String(index + 1).padStart(2, "0")}
+
+ {section.label}
+ {selected ? (
+
+ ) : null}
+
+ );
+ })}
+
+ );
+ }
+
+ return (
+
+ {sections.map((section) => {
+ const selected = active === section.id;
+ const Icon = section.icon;
+ return (
+ onChange(section.id)}
+ aria-current={selected ? "page" : undefined}
+ className={cn(
+ "relative inline-flex min-h-[3.25rem] shrink-0 items-center gap-1.5 px-3 text-xs font-semibold transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-3px] focus-visible:outline-[color:var(--focus)]",
+ selected
+ ? "text-[color:var(--clinical-accent)]"
+ : "text-[color:var(--text-muted)] hover:text-[color:var(--text)]",
+ )}
+ >
+
+ {section.label}
+ {selected ? (
+
+ ) : null}
+
+ );
+ })}
+
+ );
+}
+
+function SectionPreview({
+ direction,
+ compact,
+ sectionId,
+}: {
+ direction: Direction;
+ compact: boolean;
+ sectionId: SectionId;
+}) {
+ const section = sections.find((candidate) => candidate.id === sectionId) ?? sections[0];
+ const Icon = section.icon;
+
+ return (
+
+
+
+
+
+
+
+
+ {section.label}
+
+
{section.description}
+
+
+
+
+
+
Document
+
84-page clinical guideline
+
+
+
+ Current view
+
+
{section.label} section
+
+
+
+
+ {compact ? null : (
+
+ )}
+
+ );
+}
+
+function PreviewSearchBar({
+ compact,
+ inputId,
+ inputRef,
+}: {
+ compact: boolean;
+ inputId: string;
+ inputRef: RefObject;
+}) {
+ const [query, setQuery] = useState("");
+ const [submittedQuery, setSubmittedQuery] = useState("");
+ const canSearch = query.trim().length >= 2;
+
+ return (
+
+
+
+ {submittedQuery ? `Showing document text matches for “${submittedQuery}”.` : "Type two or more characters."}
+
+
+ );
+}
+
+function NavigationPreview({ direction, device }: { direction: Direction; device: Device }) {
+ const [active, setActive] = useState("summary");
+ const compact = device === "phone";
+ const searchInputId = useId();
+ const searchInputRef = useRef(null);
+ const focusSearch = () => {
+ searchInputRef.current?.focus({ preventScroll: true });
+ searchInputRef.current?.select();
+ };
+
+ return (
+
+
+ {compact ? "Phone · 390 px" : "Desktop"}
+
+
+
+
+
+
+
+ );
+}
+
+function ConceptSection({ direction, number }: { direction: Direction; number: number }) {
+ const copy = directionCopy[direction];
+
+ return (
+
+
+
+ {number}. {copy.title}
+
+
{copy.summary}
+
+
+
+
+
+
+ );
+}
+
+export function DocumentTopNavigationMockups() {
+ return (
+
+
+
+
+ Document title bar + attached page navigation
+
+
+ Three directions built from the current document viewer. In every option the back control, current title,
+ actions, and live section navigation read as one connected top bar.
+
+
+ Try the section labels in any preview; the content below updates immediately.
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/document-viewer/source-panels.tsx b/src/components/document-viewer/source-panels.tsx
index 3d28041465..bf04688202 100644
--- a/src/components/document-viewer/source-panels.tsx
+++ b/src/components/document-viewer/source-panels.tsx
@@ -11,7 +11,6 @@ import {
FileText,
Loader2,
Quote,
- Search,
Sparkles,
Target,
type LucideIcon,
@@ -375,18 +374,15 @@ export function TableReviewPanel({
export function DocumentViewerAnchors({
evidenceHref,
- textHref,
className,
}: {
evidenceHref: "#source-evidence" | "#source-evidence-rail";
- textHref: "#source-text";
className?: string;
}) {
const anchors = [
+ { label: "Summary", href: "#source-summary", icon: Sparkles },
{ label: "PDF", href: "#pdf-preview-section", icon: FileText },
{ label: "Evidence", href: evidenceHref, icon: Quote },
- { label: "Text", href: textHref, icon: Search },
- { label: "Summary", href: "#source-summary", icon: Sparkles },
{ label: "Images", href: "#source-images", icon: FileImage },
];
diff --git a/tests/ui-document-top-navigation-mockup.spec.ts b/tests/ui-document-top-navigation-mockup.spec.ts
new file mode 100644
index 0000000000..97398e5fee
--- /dev/null
+++ b/tests/ui-document-top-navigation-mockup.spec.ts
@@ -0,0 +1,73 @@
+import { expect, test, type Locator, type Page } from "playwright/test";
+
+const path = "/mockups/document-top-navigation";
+const expectedSections = ["Summary", "PDF", "Evidence", "Images"];
+
+async function gotoMockup(page: Page) {
+ await page.goto(path, { waitUntil: "domcontentloaded" });
+ await expect(
+ page.getByRole("heading", { level: 1, name: "Document title bar + attached page navigation" }),
+ ).toBeVisible({
+ timeout: 15_000,
+ });
+}
+
+async function expectNoHorizontalOverflow(page: Page) {
+ const overflow = await page.evaluate(() => {
+ const documentWidth = Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0);
+ return documentWidth - document.documentElement.clientWidth;
+ });
+ expect(overflow).toBeLessThanOrEqual(2);
+}
+
+async function expectNavigationContract(preview: Locator) {
+ const navigation = preview.getByRole("navigation", { name: "Document page sections" });
+ await expect(navigation.getByRole("button")).toHaveText(expectedSections);
+ await expect(navigation.getByRole("button", { name: "Overview" })).toHaveCount(0);
+ await expect(navigation.getByRole("button", { name: "Text" })).toHaveCount(0);
+ await expect(navigation.getByRole("button", { name: "Summary" })).toHaveAttribute("aria-current", "page");
+}
+
+test.describe("Document top navigation mockups @mockup", () => {
+ test.describe.configure({ timeout: 60_000 });
+
+ test("keeps all three desktop and phone concepts on the perfected navigation contract", async ({ page }) => {
+ await page.setViewportSize({ width: 1440, height: 1000 });
+ await gotoMockup(page);
+
+ const previews = page.locator("article[data-direction][data-device]");
+ await expect(previews).toHaveCount(6);
+ for (let index = 0; index < 6; index += 1) {
+ await expectNavigationContract(previews.nth(index));
+ }
+ await expectNoHorizontalOverflow(page);
+ });
+
+ test("focuses and operates the bottom document search from the title bar", async ({ page }) => {
+ await page.setViewportSize({ width: 390, height: 844 });
+ await gotoMockup(page);
+
+ const preview = page.locator('article[data-direction="continuous"][data-device="phone"]');
+ const searchInput = preview.getByRole("searchbox", { name: "Search within this document" });
+ await preview.locator("header").getByRole("button", { name: "Go to document search" }).click();
+ await expect(searchInput).toBeFocused();
+
+ await searchInput.fill("clozapine");
+ await preview
+ .getByRole("search", { name: "Search within this document" })
+ .getByRole("button", {
+ name: "Search document text",
+ })
+ .click();
+ await expect(preview.getByText("Showing document text matches for “clozapine”.")).toBeVisible();
+ });
+
+ test("does not overflow across the required responsive widths", async ({ page }) => {
+ await gotoMockup(page);
+ for (const width of [320, 390, 639, 768, 1440, 1920]) {
+ await page.setViewportSize({ width, height: 900 });
+ await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => resolve())));
+ await expectNoHorizontalOverflow(page);
+ }
+ });
+});
diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts
index 22d1b6997d..31b89aab08 100644
--- a/tests/ui-smoke.spec.ts
+++ b/tests/ui-smoke.spec.ts
@@ -3251,9 +3251,9 @@ test.describe("Clinical KB UI smoke coverage", () => {
await expect(
page.getByTestId("source-chunk-indexed-text-panel").getByTestId("highlighted-indexed-source-chunk"),
).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(viewerNav.getByRole("link")).toHaveText(["Summary", "PDF", "Evidence", "Images"]);
+ await expect(viewerNav.getByRole("link", { name: "Overview" })).toHaveCount(0);
+ await expect(viewerNav.getByRole("link", { name: "Text" })).toHaveCount(0);
await expect(page.getByRole("heading", { level: 1, name: "Synthetic lithium monitoring protocol" })).toBeVisible();
await expect(preview).toBeVisible();
const switchToCanvasMode = page.getByRole("button", { name: "Switch to canvas zoom mode" });
@@ -3298,8 +3298,9 @@ test.describe("Clinical KB UI smoke coverage", () => {
expect(expandedEvidenceBox?.height ?? 0).toBeGreaterThan(evidenceBox!.height);
await activateFocusedControl(page, viewerNav.getByRole("link", { name: "PDF" }));
await expect(preview).toBeInViewport();
- await activateFocusedControl(page, viewerNav.getByRole("link", { name: "Text" }));
- await expect(indexedTextHeading).toBeInViewport();
+ await activateFocusedControl(page, viewerNav.getByRole("link", { name: "Summary" }));
+ await expect(page.getByTestId("high-yield-summary")).toHaveJSProperty("open", true);
+ await expect(page.getByTestId("high-yield-summary")).toBeInViewport();
await activateFocusedControl(page, viewerNav.getByRole("link", { name: "PDF" }));
await expect(preview).toBeInViewport();
@@ -3390,7 +3391,7 @@ test.describe("Clinical KB UI smoke coverage", () => {
const images = page.locator("#source-images");
const indexingDetails = page.getByTestId("indexing-details");
const viewerNav = page.getByRole("navigation", { name: "Document viewer sections" }).first();
- const clickViewerNav = async (name: "Images" | "Summary" | "Text") => {
+ const clickViewerNav = async (name: "Images" | "Summary") => {
const link = viewerNav.getByRole("link", { name });
await waitForReactEventHandler(link, "onClick");
await activateFocusedControl(page, link);
@@ -3413,12 +3414,9 @@ test.describe("Clinical KB UI smoke coverage", () => {
await expect(summaryContent).toBeHidden();
await expect(images).toHaveJSProperty("open", true);
- await clickViewerNav("Text");
- await expect(indexedText).toBeInViewport();
- await expect(images).toHaveJSProperty("open", false);
-
await clickViewerNav("Summary");
await expect(summary).toHaveJSProperty("open", true);
+ await expect(images).toHaveJSProperty("open", false);
await expect(indexedText).toBeVisible();
await clickViewerNav("Images");
@@ -3702,11 +3700,13 @@ test.describe("Clinical KB UI smoke coverage", () => {
.toBeGreaterThan(250);
await expect.poll(async () => readMobileComposerReservePx(main)).toBeLessThanOrEqual(13);
- // Keyboard focus inside the composer reveals it while hidden.
+ // The title-bar Search action reveals the hidden composer and transfers
+ // focus directly to this document's search field.
await scrollPrimarySurface(page, 240);
await expect(composer).toHaveAttribute("data-scroll-hidden", "true");
- await composer.locator("input").focus();
+ await page.locator("header").getByRole("button", { name: "Go to document search" }).click();
await expect(composer).not.toHaveAttribute("data-scroll-hidden", "true");
+ await expect(composer.getByRole("searchbox", { name: "Search within this document" })).toBeFocused();
await expect
.poll(async () =>
viewerContent.evaluate((node) => Number.parseFloat(window.getComputedStyle(node).paddingBottom)),
@@ -3715,14 +3715,18 @@ test.describe("Clinical KB UI smoke coverage", () => {
await expect.poll(async () => readMobileComposerReservePx(main)).toBeLessThanOrEqual(13);
});
- test("document questions use the shared answer stream with progress and cleaned bold formatting", async ({
- page,
- }) => {
+ test("document search stays text-only and the separate answer action remains summary-scoped", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 820 });
+ const documentId = "11111111-1111-4111-8111-111111111111";
const answerRequests: Array<{ query: string; documentId?: string; summaryMode?: boolean }> = [];
+ const documentSearchRequests: string[] = [];
+ const globalSearchRequests: string[] = [];
let legacySummaryRequestCount = 0;
page.on("request", (request) => {
- if (/\/api\/documents\/[^/]+\/summarize$/.test(new URL(request.url()).pathname)) {
+ const url = new URL(request.url());
+ if (url.pathname === `/api/documents/${documentId}/search`) documentSearchRequests.push(request.url());
+ if (url.pathname === "/api/search") globalSearchRequests.push(request.url());
+ if (/\/api\/documents\/[^/]+\/summarize$/.test(url.pathname)) {
legacySummaryRequestCount += 1;
}
});
@@ -3735,16 +3739,35 @@ test.describe("Clinical KB UI smoke coverage", () => {
"Key practical points: **clozapine** monitoring requires regular FBC/ANC checks and review of constipation, myocarditis symptoms, metabolic risk, and missed-dose restart rules.",
}),
});
- await gotoApp(
- page,
- "/documents/11111111-1111-4111-8111-111111111111?page=1&chunk=44444444-4444-4444-8444-444444444442",
- );
+ await gotoApp(page, `/documents/${documentId}?page=1&chunk=44444444-4444-4444-8444-444444444442`);
const composer = page.locator("form.document-viewer-composer");
- await composer
- .getByRole("textbox", { name: "Search or answer from this document" })
- .fill("How is clozapine monitored?");
- await activateFocusedControl(page, composer.getByRole("button", { name: "Answer from this document" }));
+ const documentSearch = composer.getByRole("searchbox", { name: "Search within this document" });
+ const indexedTextSearch = page.getByLabel("Search within indexed source text").last();
+ await page.locator("header").getByRole("button", { name: "Open document actions" }).click();
+ const searchActions = page.getByRole("dialog", { name: "This document" });
+ await activateFocusedControl(page, searchActions.getByRole("button", { name: "Search in document" }));
+ await expect(searchActions).toHaveCount(0);
+ await expect(documentSearch).toBeFocused();
+
+ await documentSearch.fill("safety plan include");
+ await expect(indexedTextSearch).toHaveValue("safety plan include");
+ await expect.poll(() => documentSearchRequests.length).toBe(1);
+ const documentSearchUrl = new URL(documentSearchRequests[0]);
+ expect(documentSearchUrl.pathname).toBe(`/api/documents/${documentId}/search`);
+ expect(documentSearchUrl.searchParams.get("q")).toBe("safety plan include");
+ expect(globalSearchRequests).toEqual([]);
+ expect(answerRequests).toEqual([]);
+
+ await documentSearch.press("Enter");
+ await expect(page.getByTestId("source-chunk-indexed-text-panel")).toBeInViewport();
+ await expect(page.getByTestId("source-chunk-indexed-text-panel").getByText("Hit 1 of 2").first()).toBeVisible();
+ expect(answerRequests).toEqual([]);
+
+ await page.locator("header").getByRole("button", { name: "Open document actions" }).click();
+ const answerActions = page.getByRole("dialog", { name: "This document" });
+ await expect(answerActions).toBeVisible();
+ await activateFocusedControl(page, answerActions.getByRole("button", { name: "Answer from this" }));
const generatedSummary = page.getByTestId("generated-clinical-summary");
await expect(generatedSummary).toBeVisible();
@@ -3762,25 +3785,13 @@ test.describe("Clinical KB UI smoke coverage", () => {
expect(summaryBox!.y).toBeLessThan(previewBox!.y);
expect(answerRequests).toEqual([
{
- query: "How is clozapine monitored?",
- documentId: "11111111-1111-4111-8111-111111111111",
- summaryMode: undefined,
+ query: documentSummaryQuestion,
+ documentId,
+ summaryMode: true,
},
]);
expect(legacySummaryRequestCount).toBe(0);
-
- await composer.getByRole("textbox", { name: "Search or answer from this document" }).fill("");
- // The generated answer intentionally smooth-scrolls into view. WebKit can
- // move the fixed pointer target during that animation, so exercise the
- // native submit control by keyboard for this immediate follow-up action.
- await activateFocusedControl(page, composer.getByRole("button", { name: "Answer from this document" }));
- await expect.poll(() => answerRequests.length).toBe(2);
- expect(answerRequests[1]).toEqual({
- query: documentSummaryQuestion,
- documentId: "11111111-1111-4111-8111-111111111111",
- summaryMode: true,
- });
- expect(legacySummaryRequestCount).toBe(0);
+ expect(globalSearchRequests).toEqual([]);
await expectNoPageHorizontalOverflow(page);
});