Skip to content

Commit a19cda2

Browse files
committed
fix(pdf-server): no blank flash on pinch commit; clamp Ctrl+mousewheel; basic-host observer cleanup
Three review follow-ups: 1) Snap on pinch release: setting canvasEl.width clears the backing store, so between the resize and page.render() completing the page was blank. Snapshot the old bitmap first and drawImage it scaled as a placeholder; the real render paints over it. Gated on a transform being present so page-nav renders aren't affected. 2) Ctrl + physical mouse wheel sent deltaY≈±100 → exp(±1)≈2.7× per notch, slamming straight to ZOOM_MIN/MAX. Clamp per-event delta to ±25 (≈±28% per notch); trackpad pinch deltas are ~±1-10 so the clamp is a no-op there. 3) basic-host's iframe ResizeObserver had no disposal path. Chained disconnect() into appBridge.onclose (Protocol base class hook).
1 parent 6c910a4 commit a19cda2

2 files changed

Lines changed: 31 additions & 7 deletions

File tree

examples/basic-host/src/implementation.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,12 @@ export function newAppBridge(
321321
}
322322
});
323323
iframeResizeObserver.observe(iframe);
324+
// AppBridge inherits Protocol's onclose hook — chain disposal there.
325+
const prevOnclose = appBridge.onclose;
326+
appBridge.onclose = () => {
327+
iframeResizeObserver.disconnect();
328+
prevOnclose?.();
329+
};
324330

325331
// Register all handlers before calling connect(). The view can start
326332
// sending requests immediately after the initialization handshake, so any

examples/pdf-server/src/mcp-app.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3041,22 +3041,37 @@ async function renderPage() {
30413041
const dpr = window.devicePixelRatio || 1;
30423042
const ctx = canvasEl.getContext("2d")!;
30433043

3044-
// Set canvas size in pixels (scaled for retina)
3044+
// If we're committing a pinch (transform still on .page-wrapper),
3045+
// snapshot the current bitmap so we can paint a scaled placeholder
3046+
// while page.render() runs. Without this, setting canvasEl.width
3047+
// below clears the backing store → blank flash → user sees a "snap".
3048+
let snap: HTMLCanvasElement | null = null;
3049+
if (pageWrapperEl.style.transform && canvasEl.width > 0) {
3050+
snap = document.createElement("canvas");
3051+
snap.width = canvasEl.width;
3052+
snap.height = canvasEl.height;
3053+
snap.getContext("2d")!.drawImage(canvasEl, 0, 0);
3054+
}
3055+
3056+
// Set canvas size in pixels (scaled for retina) — clears the bitmap
30453057
canvasEl.width = viewport.width * dpr;
30463058
canvasEl.height = viewport.height * dpr;
30473059

30483060
// Set display size in CSS pixels
30493061
canvasEl.style.width = `${viewport.width}px`;
30503062
canvasEl.style.height = `${viewport.height}px`;
3051-
// If a pinch preview transform is on .page-wrapper, drop it in the same
3052-
// frame as the canvas resize. Clearing earlier (in commitPinch, before
3053-
// the await getPage() above) snaps the page back to the old size for
3054-
// one frame; clearing later double-applies the scale until render ends.
3063+
// Drop the pinch preview transform in the same frame as the canvas
3064+
// resize so the size handoff is atomic.
30553065
pageWrapperEl.style.transform = "";
30563066

30573067
// Scale context for retina
30583068
ctx.scale(dpr, dpr);
30593069

3070+
if (snap) {
3071+
// Stretched-but-correct-size placeholder until page.render replaces it.
3072+
ctx.drawImage(snap, 0, 0, viewport.width, viewport.height);
3073+
}
3074+
30603075
// Clear and setup text layer
30613076
textLayerEl.innerHTML = "";
30623077
textLayerEl.style.width = `${viewport.width}px`;
@@ -3812,8 +3827,11 @@ canvasContainerEl.addEventListener(
38123827
e.preventDefault();
38133828
if (pinchSettleTimer === null) beginPinch();
38143829
// exp(-deltaY * k) makes equal-magnitude in/out deltas inverse —
3815-
// pinch out then back lands where you started.
3816-
updatePinch(previewScale * Math.exp(-e.deltaY * 0.01));
3830+
// pinch out then back lands where you started. Clamp per event so a
3831+
// physical mouse wheel (deltaY ≈ ±100/notch) doesn't slam to the
3832+
// limit; trackpad pinch deltas are ~±1-10 so the clamp is a no-op.
3833+
const d = Math.max(-25, Math.min(25, e.deltaY));
3834+
updatePinch(previewScale * Math.exp(-d * 0.01));
38173835
if (pinchSettleTimer) clearTimeout(pinchSettleTimer);
38183836
// 200ms — slow trackpad pinches can leave >150ms gaps between wheel
38193837
// events, which would commit-then-restart and feel steppy.

0 commit comments

Comments
 (0)