diff --git a/packages/app/e2e/regression/preview-baseline-contract.spec.ts b/packages/app/e2e/regression/preview-baseline-contract.spec.ts
index f3981dc58..b160a65ca 100644
--- a/packages/app/e2e/regression/preview-baseline-contract.spec.ts
+++ b/packages/app/e2e/regression/preview-baseline-contract.spec.ts
@@ -236,6 +236,11 @@ test("splits a Preview leaf at its right edge without recreating the moved rende
await page.mouse.down()
await page.mouse.move(leafBox.x + leafBox.width - 2, leafBox.y + leafBox.height / 2, { steps: 8 })
await expect(panel.locator('[data-preview-dragging="notes/second.md"]')).toBeVisible()
+ const dragProxy = panel.locator("[data-preview-tab-drag-proxy]")
+ await expect(dragProxy).toBeVisible()
+ await expect(dragProxy).toContainText("second.md")
+ await expect(dragProxy).toHaveCSS("pointer-events", "none")
+ expect((await dragProxy.boundingBox())?.x).toBeGreaterThan(leafBox.x)
const preview = panel.locator('[data-preview-drop-preview="right"]')
await expect(preview).toBeVisible()
await expect(preview).toHaveCSS("pointer-events", "none")
@@ -245,6 +250,7 @@ test("splits a Preview leaf at its right edge without recreating the moved rende
await page.mouse.up()
await page.waitForTimeout(250)
await expect(preview).toHaveCount(0)
+ await expect(dragProxy).toHaveCount(0)
const destinationLeaf = panel.locator('[data-preview-leaf="pane-1"]')
await expect(panel.locator("[data-preview-leaf]")).toHaveCount(2)
@@ -330,6 +336,125 @@ test("retains an unsaved editor draft through a Preview edge split", async ({ pa
expect(await host.evaluate((element) => element.isConnected)).toBe(true)
})
+test("keeps Preview zoom independent in split panes", async ({ page }) => {
+ await openPreview(page)
+ await openPreviewFile(page, secondMarkdownFile)
+
+ const panel = page.locator("#review-panel")
+ const source = panel.getByRole("tab", { name: "second.md" })
+ const rootLeaf = panel.locator('[data-preview-leaf="root"]')
+ const sourceBox = await source.boundingBox()
+ const rootBox = await rootLeaf.boundingBox()
+ if (!sourceBox || !rootBox) throw new Error("Preview tab and leaf must be measurable before splitting")
+
+ await page.mouse.move(sourceBox.x + sourceBox.width / 2, sourceBox.y + sourceBox.height / 2)
+ await page.mouse.down()
+ await page.mouse.move(rootBox.x + rootBox.width - 2, rootBox.y + rootBox.height / 2, { steps: 8 })
+ await page.mouse.up()
+
+ const destinationLeaf = panel.locator('[data-preview-leaf="pane-1"]')
+ const rootZoom = rootLeaf.locator('input[type="text"]:visible')
+ const destinationZoom = destinationLeaf.locator('input[type="text"]')
+ await expect(rootZoom).toHaveValue("100%")
+ await expect(destinationZoom).toHaveValue("100%")
+
+ await rootZoom.fill("130")
+ await rootZoom.press("Enter")
+
+ await expect(rootZoom).toHaveValue("130%")
+ await expect(destinationZoom).toHaveValue("100%")
+})
+
+test("keeps each pane's zoom control inside its minimum-width leaf", async ({ page }) => {
+ await openPreview(page)
+ await openPreviewFile(page, secondMarkdownFile)
+
+ const panel = page.locator("#review-panel")
+ const source = panel.getByRole("tab", { name: "second.md" })
+ const rootLeaf = panel.locator('[data-preview-leaf="root"]')
+ const sourceBox = await source.boundingBox()
+ const rootBox = await rootLeaf.boundingBox()
+ if (!sourceBox || !rootBox) throw new Error("Preview tab and leaf must be measurable before splitting")
+
+ await page.mouse.move(sourceBox.x + sourceBox.width / 2, sourceBox.y + sourceBox.height / 2)
+ await page.mouse.down()
+ await page.mouse.move(rootBox.x + rootBox.width - 2, rootBox.y + rootBox.height / 2, { steps: 8 })
+ await page.mouse.up()
+
+ const destinationLeaf = panel.locator('[data-preview-leaf="pane-1"]')
+ for (const leaf of [rootLeaf, destinationLeaf]) {
+ const leafBox = await leaf.boundingBox()
+ const inputBox = await leaf.locator('input[type="text"]:visible').boundingBox()
+ if (!leafBox || !inputBox) throw new Error("Preview leaf and zoom input must be measurable")
+ expect(inputBox.x).toBeGreaterThanOrEqual(leafBox.x)
+ expect(inputBox.x + inputBox.width).toBeLessThanOrEqual(leafBox.x + leafBox.width)
+ }
+})
+
+test("inherits source zoom and focuses the new Preview leaf after a split", async ({ page }) => {
+ await openPreview(page)
+ await openPreviewFile(page, secondMarkdownFile)
+
+ const panel = page.locator("#review-panel")
+ const rootLeaf = panel.locator('[data-preview-leaf="root"]')
+ const rootZoom = rootLeaf.locator('[data-preview-host="notes/second.md"] input[type="text"]')
+ await rootZoom.fill("130")
+ await rootZoom.press("Enter")
+ await expect(rootZoom).toHaveValue("130%")
+
+ const source = panel.getByRole("tab", { name: "second.md" })
+ const sourceBox = await source.boundingBox()
+ const rootBox = await rootLeaf.boundingBox()
+ if (!sourceBox || !rootBox) throw new Error("Preview tab and leaf must be measurable before splitting")
+
+ await page.mouse.move(sourceBox.x + sourceBox.width / 2, sourceBox.y + sourceBox.height / 2)
+ await page.mouse.down()
+ await page.mouse.move(rootBox.x + rootBox.width - 2, rootBox.y + rootBox.height / 2, { steps: 8 })
+ await page.mouse.up()
+
+ const destinationLeaf = panel.locator('[data-preview-leaf="pane-1"]')
+ await expect(destinationLeaf).toHaveAttribute("data-focused", "true")
+ await expect(destinationLeaf.locator('input[type="text"]')).toHaveValue("130%")
+})
+
+test("adopts destination pane zoom when transferring a Preview renderer", async ({ page }) => {
+ await openPreview(page)
+ await openPreviewFile(page, secondMarkdownFile)
+
+ const panel = page.locator("#review-panel")
+ const source = panel.getByRole("tab", { name: "second.md" })
+ const rootLeaf = panel.locator('[data-preview-leaf="root"]')
+ const sourceBox = await source.boundingBox()
+ const rootBox = await rootLeaf.boundingBox()
+ if (!sourceBox || !rootBox) throw new Error("Preview tab and leaf must be measurable before splitting")
+
+ await page.mouse.move(sourceBox.x + sourceBox.width / 2, sourceBox.y + sourceBox.height / 2)
+ await page.mouse.down()
+ await page.mouse.move(rootBox.x + rootBox.width - 2, rootBox.y + rootBox.height / 2, { steps: 8 })
+ await page.mouse.up()
+
+ const rootZoom = rootLeaf.locator('input[type="text"]')
+ await rootZoom.fill("130")
+ await rootZoom.press("Enter")
+ await expect(rootZoom).toHaveValue("130%")
+
+ const sourceLeaf = panel.locator('[data-preview-leaf="pane-1"]')
+ const movedHost = sourceLeaf.locator('[data-preview-host="notes/second.md"]')
+ const host = await movedHost.elementHandle()
+ const transferTab = sourceLeaf.getByRole("tab", { name: "second.md" })
+ const transferBox = await transferTab.boundingBox()
+ const transferTarget = await rootLeaf.boundingBox()
+ if (!host || !transferBox || !transferTarget) throw new Error("Preview leaves must be measurable before transferring")
+
+ await page.mouse.move(transferBox.x + transferBox.width / 2, transferBox.y + transferBox.height / 2)
+ await page.mouse.down()
+ await page.mouse.move(transferTarget.x + transferTarget.width / 2, transferTarget.y + transferTarget.height / 2, { steps: 8 })
+ await page.mouse.up()
+
+ await expect(rootLeaf.locator('[data-preview-host="notes/second.md"] input[type="text"]')).toHaveValue("130%")
+ expect(await host.evaluate((element) => element.isConnected)).toBe(true)
+})
+
test("keeps both Preview leaves at their minimum width while resizing a divider", async ({ page }) => {
await openPreview(page)
await openPreviewFile(page, secondMarkdownFile)
@@ -431,10 +556,15 @@ test("clears the prospective pane preview when a Preview drag is cancelled", asy
await page.mouse.move(sourceBox.x + sourceBox.width / 2, sourceBox.y + sourceBox.height / 2)
await page.mouse.down()
await page.mouse.move(leafBox.x + leafBox.width - 2, leafBox.y + leafBox.height / 2, { steps: 8 })
+ const dragProxy = panel.locator("[data-preview-tab-drag-proxy]")
+ await expect(dragProxy).toBeVisible()
await expect(panel.locator('[data-preview-drop-preview="right"]')).toBeVisible()
await page.mouse.move(leafBox.x - 20, leafBox.y + leafBox.height / 2, { steps: 4 })
await expect(panel.locator("[data-preview-drop-preview]")).toHaveCount(0)
await page.mouse.up()
+ await expect(dragProxy).toHaveAttribute("data-leaving", "true")
+ await page.waitForTimeout(200)
+ await expect(dragProxy).toHaveCount(0)
await expect(panel.locator("[data-preview-leaf]")).toHaveCount(1)
await expect(leaf.getByRole("tab", { name: "second.md" })).toHaveAttribute("aria-selected", "true")
diff --git a/packages/app/src/components/session/preview-file-view.tsx b/packages/app/src/components/session/preview-file-view.tsx
index 6f8216edf..3d26bd5ea 100644
--- a/packages/app/src/components/session/preview-file-view.tsx
+++ b/packages/app/src/components/session/preview-file-view.tsx
@@ -360,15 +360,13 @@ export function PreviewFileView(props: {
>
{/* Floating controls — top-right overlay */}
}) {
const [dirtyPaths, setDirtyPaths] = createStore
>({})
const [saveRequests, setSaveRequests] = createStore>({})
- const [zoom, setZoom] = createSignal(100)
const [workspace, setWorkspace] = createSignal(createPreviewWorkspace())
const [capacityMessage, setCapacityMessage] = createSignal(null)
const [closingPath, setClosingPath] = createSignal(null)
const [previewDrag, setPreviewDrag] = createSignal(null)
+ const [dragProxy, setDragProxy] = createSignal<{ path: string; x: number; y: number; leaving: boolean } | null>(null)
const leafElements = new Map()
const [hostMounts, setHostMounts] = createStore>({})
let stopPreviewDrag: (() => void) | undefined
+ let dragProxyTimer: ReturnType | undefined
const openedPaths = () => previewLeaves(workspace().tree).flatMap((leaf) => leaf.tabs)
@@ -84,11 +88,16 @@ export function SessionPreviewTab(props: { previewFile: Accessor
window.addEventListener("message", handlePreviewFile)
onCleanup(() => window.removeEventListener("message", handlePreviewFile))
})
- onCleanup(() => stopPreviewDrag?.())
+ onCleanup(() => {
+ stopPreviewDrag?.()
+ if (dragProxyTimer) clearTimeout(dragProxyTimer)
+ })
- const zoomIn = () => setZoom((value) => Math.min(value + 10, 500))
- const zoomOut = () => setZoom((value) => Math.max(value - 10, 50))
- const onZoomChange = (value: number) => setZoom(Math.round(Math.min(Math.max(value, 50), 500)))
+ const zoomForPath = (path: string) => previewLeafContaining(workspace().tree, path)?.zoom ?? 100
+ const setZoomForPath = (path: string, value: number) => {
+ const leaf = previewLeafContaining(workspace().tree, path)
+ if (leaf) setWorkspace((current) => setPreviewLeafZoom(current, leaf.id, value))
+ }
const removePath = (path: string) => {
setWorkspace((current) => removePreviewPath(current, path))
@@ -144,13 +153,30 @@ export function SessionPreviewTab(props: { previewFile: Accessor
const source = event.currentTarget as HTMLElement
source.setPointerCapture?.(event.pointerId)
- const stop = () => {
+ const clearDragProxy = (fade: boolean) => {
+ const proxy = dragProxy()
+ if (!proxy) return
+ if (!fade) {
+ if (dragProxyTimer) clearTimeout(dragProxyTimer)
+ dragProxyTimer = undefined
+ setDragProxy(null)
+ return
+ }
+ setDragProxy({ ...proxy, leaving: true })
+ if (dragProxyTimer) clearTimeout(dragProxyTimer)
+ dragProxyTimer = setTimeout(() => {
+ setDragProxy(null)
+ dragProxyTimer = undefined
+ }, 160)
+ }
+ const stop = (fadeProxy = false) => {
window.removeEventListener("pointermove", onMove)
window.removeEventListener("pointerup", onUp)
window.removeEventListener("pointercancel", onCancel)
if (source.hasPointerCapture?.(event.pointerId)) source.releasePointerCapture(event.pointerId)
stopPreviewDrag = undefined
setPreviewDrag(null)
+ clearDragProxy(fadeProxy)
}
const onMove = (moveEvent: PointerEvent) => {
if (!active) {
@@ -158,15 +184,25 @@ export function SessionPreviewTab(props: { previewFile: Accessor
active = true
}
moveEvent.preventDefault()
- setPreviewDrag({ path, sourceLeafID, drop: dropTargetAt(moveEvent.clientX, moveEvent.clientY, path, sourceLeafID) })
+ if (dragProxyTimer) clearTimeout(dragProxyTimer)
+ dragProxyTimer = undefined
+ const next = {
+ path,
+ sourceLeafID,
+ x: moveEvent.clientX,
+ y: moveEvent.clientY,
+ drop: dropTargetAt(moveEvent.clientX, moveEvent.clientY, path, sourceLeafID),
+ }
+ setPreviewDrag(next)
+ setDragProxy({ path, x: next.x, y: next.y, leaving: false })
}
const onUp = (upEvent: PointerEvent) => {
const drop = active ? dropTargetAt(upEvent.clientX, upEvent.clientY, path, sourceLeafID) : undefined
- stop()
+ stop(!drop)
if (!drop) return
setWorkspace((current) => movePreviewTab(current, { path, targetLeafID: drop.leafID, position: drop.position, targetIndex: drop.targetIndex }))
}
- const onCancel = () => stop()
+ const onCancel = () => stop(true)
stopPreviewDrag?.()
stopPreviewDrag = stop
@@ -303,6 +339,22 @@ export function SessionPreviewTab(props: { previewFile: Accessor
return (
+
+ {(proxy) => (
+
+
+
+ )}
+
{capacityMessage()}
@@ -371,10 +423,10 @@ export function SessionPreviewTab(props: { previewFile: Accessor
removePath(path)
setClosingPath(null)
}}
- zoom={zoom}
- zoomIn={zoomIn}
- zoomOut={zoomOut}
- onZoomChange={onZoomChange}
+ zoom={() => zoomForPath(path)}
+ zoomIn={() => setZoomForPath(path, zoomForPath(path) + 10)}
+ zoomOut={() => setZoomForPath(path, zoomForPath(path) - 10)}
+ onZoomChange={(value) => setZoomForPath(path, value)}
/>
diff --git a/packages/app/src/components/session/session-preview-tree.test.ts b/packages/app/src/components/session/session-preview-tree.test.ts
index 1154cd7c1..fb20f5b95 100644
--- a/packages/app/src/components/session/session-preview-tree.test.ts
+++ b/packages/app/src/components/session/session-preview-tree.test.ts
@@ -1,5 +1,13 @@
import { describe, expect, test } from "bun:test"
-import { createPreviewWorkspace, movePreviewTab, openPreviewPath, previewLeaves, previewMinimumExtent } from "./session-preview-tree"
+import {
+ createPreviewWorkspace,
+ movePreviewTab,
+ openPreviewPath,
+ previewLeafByID,
+ previewLeaves,
+ previewMinimumExtent,
+ setPreviewLeafZoom,
+} from "./session-preview-tree"
describe("Preview workspace pane tree", () => {
test("moves a tab into a focused right sibling leaf", () => {
@@ -11,9 +19,9 @@ describe("Preview workspace pane tree", () => {
position: "right",
})
- expect(previewLeaves(moved.tree).map(({ id, tabs, selectedPath }) => ({ id, tabs, selectedPath }))).toEqual([
- { id: "root", tabs: ["notes/baseline.md"], selectedPath: "notes/baseline.md" },
- { id: "pane-1", tabs: ["notes/second.md"], selectedPath: "notes/second.md" },
+ expect(previewLeaves(moved.tree).map(({ id, tabs, selectedPath, zoom }) => ({ id, tabs, selectedPath, zoom }))).toEqual([
+ { id: "root", tabs: ["notes/baseline.md"], selectedPath: "notes/baseline.md", zoom: 100 },
+ { id: "pane-1", tabs: ["notes/second.md"], selectedPath: "notes/second.md", zoom: 100 },
])
expect(moved.focusedLeafID).toBe("pane-1")
})
@@ -46,4 +54,23 @@ describe("Preview workspace pane tree", () => {
expect(previewMinimumExtent(nested.tree, "horizontal")).toBe(450)
expect(previewMinimumExtent(nested.tree, "vertical")).toBe(150)
})
+
+ test("inherits source zoom on split and destination zoom on transfer", () => {
+ const sourceZoomed = setPreviewLeafZoom(createPreviewWorkspace(["notes/one.md", "notes/two.md"]), "root", 130)
+ const split = movePreviewTab(sourceZoomed, {
+ path: "notes/two.md",
+ targetLeafID: "root",
+ position: "right",
+ })
+
+ expect(previewLeafByID(split.tree, "pane-1")?.zoom).toBe(130)
+ const destinationZoomed = setPreviewLeafZoom(split, "root", 90)
+ const transferred = movePreviewTab(destinationZoomed, {
+ path: "notes/two.md",
+ targetLeafID: "root",
+ position: "center",
+ })
+
+ expect(previewLeafByID(transferred.tree, "root")?.zoom).toBe(90)
+ })
})
diff --git a/packages/app/src/components/session/session-preview-tree.ts b/packages/app/src/components/session/session-preview-tree.ts
index 79c310a15..b94624b56 100644
--- a/packages/app/src/components/session/session-preview-tree.ts
+++ b/packages/app/src/components/session/session-preview-tree.ts
@@ -3,6 +3,7 @@ export type PreviewLeaf = {
id: string
tabs: string[]
selectedPath: string | null
+ zoom: number
}
export type PreviewSplit = {
@@ -32,6 +33,7 @@ export const createPreviewWorkspace = (paths: readonly string[] = []): PreviewWo
id: "root",
tabs: [...paths],
selectedPath: paths.at(-1) ?? null,
+ zoom: 100,
},
focusedLeafID: "root",
nextPaneID: 1,
@@ -139,6 +141,11 @@ export const selectPreviewPath = (workspace: PreviewWorkspace, leafID: string, p
}
}
+export const setPreviewLeafZoom = (workspace: PreviewWorkspace, leafID: string, zoom: number): PreviewWorkspace => ({
+ ...workspace,
+ tree: mapLeaf(workspace.tree, leafID, (leaf) => ({ ...leaf, zoom: Math.round(Math.min(Math.max(zoom, 50), 500)) })),
+})
+
export const removePreviewPath = (workspace: PreviewWorkspace, path: string): PreviewWorkspace => {
const source = previewLeafContaining(workspace.tree, path)
if (!source) return workspace
@@ -195,6 +202,7 @@ export const movePreviewTab = (
id: `pane-${workspace.nextPaneID}`,
tabs: [input.path],
selectedPath: input.path,
+ zoom: source.zoom,
}
const direction = input.position === "left" || input.position === "right" ? "horizontal" : "vertical"
const newPaneBeforeTarget = input.position === "left" || input.position === "top"
diff --git a/packages/app/src/design-polish.css b/packages/app/src/design-polish.css
index 012c0ccba..452ceff39 100644
--- a/packages/app/src/design-polish.css
+++ b/packages/app/src/design-polish.css
@@ -171,12 +171,25 @@ body[data-new-layout] #review-panel [data-component="tabs"].preview-tab-strip[da
background: var(--v2-background-bg-base);
}
#review-panel .preview-pane-leaf[data-focused] {
- box-shadow: inset 0 0 0 var(--border-width) var(--v2-border-border-strong);
+ box-shadow: inset 0 0 0 var(--border-width) var(--accent-edge);
}
#review-panel .preview-pane-leaf > [data-component="tabs"].preview-tab-strip {
flex: 0 0 auto;
height: auto;
}
+#review-panel [data-preview-controls] {
+ top: var(--space-2);
+ right: var(--space-3);
+ gap: var(--space-2);
+}
+#review-panel .preview-pane-leaf [data-preview-controls] {
+ top: var(--space-1);
+ right: var(--space-1);
+ gap: var(--space-1);
+ max-width: calc(100% - var(--space-2));
+ flex-wrap: wrap;
+ justify-content: flex-end;
+}
#review-panel .preview-pane-content {
position: relative;
display: flex;
@@ -225,6 +238,25 @@ body[data-new-layout] #review-panel [data-component="tabs"].preview-tab-strip[da
}
#review-panel .preview-pane-drop-preview[data-preview-drop-preview="top"] { top: 0; }
#review-panel .preview-pane-drop-preview[data-preview-drop-preview="bottom"] { bottom: 0; }
+#review-panel .preview-tab-drag-proxy {
+ position: fixed;
+ z-index: 60;
+ top: 0;
+ left: 0;
+ display: flex;
+ align-items: center;
+ max-width: min-content;
+ padding: var(--space-1) var(--space-2);
+ border: var(--border-width) solid var(--v2-border-border-base);
+ border-radius: var(--radius-md);
+ background: var(--v2-background-bg-layer-01);
+ box-shadow: var(--elev-float);
+ opacity: 0.92;
+ pointer-events: none;
+ transform: translate3d(calc(var(--preview-drag-x) + var(--space-2)), calc(var(--preview-drag-y) + var(--space-2)), 0);
+ transition: opacity 0.16s ease;
+}
+#review-panel .preview-tab-drag-proxy[data-leaving] { opacity: 0; }
/* ── session status dots ──
Four states: green (done, unread), grey (done, seen), yellow (running),
diff --git a/packages/session-ui/src/v2/components/preview-file-routing.test.ts b/packages/session-ui/src/v2/components/preview-file-routing.test.ts
index 780eec377..d2ef75087 100644
--- a/packages/session-ui/src/v2/components/preview-file-routing.test.ts
+++ b/packages/session-ui/src/v2/components/preview-file-routing.test.ts
@@ -226,6 +226,10 @@ describe("toolbar layout (#934)", () => {
path.resolve(__dirname, "../../../../app/src/components/session/preview-file-view.tsx"),
"utf8",
)
+ const previewTreeSrc = fs.readFileSync(
+ path.resolve(__dirname, "../../../../app/src/components/session/session-preview-tree.ts"),
+ "utf8",
+ )
const pdfCanvasViewSrc = fs.readFileSync(
path.resolve(__dirname, "../../../../app/src/components/session/pdf-canvas-view.tsx"),
"utf8",
@@ -235,8 +239,8 @@ describe("toolbar layout (#934)", () => {
// SessionPreviewTab must NOT render the zoom buttons/input (aria-label)
expect(previewTabSrc).not.toContain("Zoom out")
expect(previewTabSrc).not.toContain("Zoom in")
- // But it must define and pass zoom/zoomIn/zoomOut as props
- expect(previewTabSrc).toContain("zoom={zoom}")
+ // It routes the pane-local zoom accessor to each renderer host.
+ expect(previewTabSrc).toContain("zoom={() => zoomForPath(path)}")
// PreviewFileView MUST render the zoom widget
expect(fileViewSrc).toContain("Zoom out")
@@ -256,8 +260,8 @@ describe("toolbar layout (#934)", () => {
})
test("SessionPreviewTab passes zoomIn and zoomOut to PreviewFileView", () => {
- expect(previewTabSrc).toContain("zoomIn={zoomIn}")
- expect(previewTabSrc).toContain("zoomOut={zoomOut}")
+ expect(previewTabSrc).toContain("zoomIn={() => setZoomForPath(path, zoomForPath(path) + 10)}")
+ expect(previewTabSrc).toContain("zoomOut={() => setZoomForPath(path, zoomForPath(path) - 10)}")
})
test("image zoom uses direct CSS sizing, not transform: scale (#934)", () => {
@@ -371,8 +375,8 @@ describe("toolbar layout (#934)", () => {
})
test("zoom floor is category-aware: 100% for image/pdf, 50% for markdown/text (#934)", () => {
- // The parent (SessionPreviewTab) keeps the global floor at 50%
- expect(previewTabSrc).toMatch(/Math\.max\(\w+ - 10, 50\)/)
+ // The parent routes existing renderer controls to the owning leaf pane.
+ expect(previewTabSrc).toContain("setZoomForPath")
// PreviewFileView applies a higher floor for image/pdf in its zoom-out handler
expect(fileViewSrc).toContain("zoomFloor")
})
@@ -380,8 +384,8 @@ describe("toolbar layout (#934)", () => {
test("pinch/wheel zoom: SessionPreviewTab exposes onZoomChange to PreviewFileView (#934)", () => {
// Parent must define a handler that accepts an arbitrary zoom value
expect(previewTabSrc).toContain("onZoomChange")
- // Must clamp to [50, 500] — same bounds as the +/- buttons
- expect(previewTabSrc).toContain("500")
+ // The leaf model clamps to the same [50, 500] range.
+ expect(previewTreeSrc).toContain("Math.min(Math.max(zoom, 50), 500)")
})
test("pinch/wheel zoom: PreviewFileView has a wheel handler for ctrlKey/shiftKey (#934)", () => {
@@ -639,7 +643,7 @@ describe("dirty dot indicator (#937)", () => {
)
test("shows an unsaved dot based on workspace dirty state", () => {
- // The workspace owns only a dirty flag; PreviewFileView owns draft text.
+ // The workspace owns only the dirty flag; the renderer owns draft text.
expect(previewTabSrc).toContain("dirtyPaths")
})