Conversation
…olbar layout Highlights rendered opaque: inline `background: <hex>` overrode the CSS rgba(...,0.35). Now convert def.color via cssColorToRgb and force alpha 0.35. Added a toHaveCSS regression check in pdf-annotations.spec.ts. Pinch-in while inline (wheel ctrlKey deltaY<0 or two-finger spread >1.15x) now enters fullscreen. Pinch-out in fullscreen past 0.9x of fit-scale, when already at/below fit, exits to inline and clears userHasZoomed so refit sizes the inline view. previewScaleRaw tracks the unclamped intent so the exit fires even when fit ~= ZOOM_MIN. A modeTransitionInFlight latch (held 250ms post-toggle) keeps the gesture tail from re-toggling or immediately zooming the new view. Fullscreen toolbar: flex-wrap: nowrap and tighter 0.25rem vertical padding (min-height 40px + safe-top instead of 48px + wrap). Search bar top/right now follow --safe-top/--safe-right so it sits flush below the toolbar instead of overlapping it. Base .canvas-container gets touch-action: pan-x pan-y so the inline pinch is capturable on iOS.
Hosts wrap fullscreen in their own header (title + close), which already clears the top safe-area. Adding --safe-top to our toolbar padding-top double-dipped, leaving a visible gap between the host header and our toolbar. Keep --safe-left/right (host header doesn't cover the sides).
@modelcontextprotocol/ext-apps
@modelcontextprotocol/server-basic-preact
@modelcontextprotocol/server-basic-react
@modelcontextprotocol/server-basic-solid
@modelcontextprotocol/server-basic-svelte
@modelcontextprotocol/server-basic-vanillajs
@modelcontextprotocol/server-basic-vue
@modelcontextprotocol/server-budget-allocator
@modelcontextprotocol/server-cohort-heatmap
@modelcontextprotocol/server-customer-segmentation
@modelcontextprotocol/server-debug
@modelcontextprotocol/server-map
@modelcontextprotocol/server-pdf
@modelcontextprotocol/server-scenario-modeler
@modelcontextprotocol/server-shadertoy
@modelcontextprotocol/server-sheet-music
@modelcontextprotocol/server-system-monitor
@modelcontextprotocol/server-threejs
@modelcontextprotocol/server-transcript
@modelcontextprotocol/server-video-resource
@modelcontextprotocol/server-wiki-explorer
commit: |
…idth Previously gated on scale <= 1.0, which blocked page-nav in fullscreen where fit-scale is often >100%. Now gate on actual horizontal overflow (scrollWidth > clientWidth) so swipe works at any fit-to-width scale and still defers to native panning once you zoom past it.
handleHostContextChanged calls refitScale() before the iframe has actually shrunk, and the ResizeObserver's inline branch only refits on width *growth* (to avoid a requestFitToContent shrink-loop). So the fullscreen->inline shrink never triggered a refit and the page stayed at the fullscreen scale. Add a one-shot forceNextResizeRefit flag, set on fullscreen->inline (both the pinch-out path and handleHostContextChanged), consumed by the ResizeObserver on the next size change. One-shot keeps the shrink-loop guard intact for ordinary inline resizes.
In fullscreen, pinch-out and the zoom-out button now floor at the fit-to-page scale instead of ZOOM_MIN, so the page never shrinks below fully-visible (no dead margin around it). previewScaleRaw stays unclamped so a continued pinch-out past fit still triggers exit-to-inline.
The fit-floor pinned previewScale at fit, but the wheel handler multiplied the *clamped* previewScale, so it could never accumulate below fit*0.9 to trigger exit. Drive the wheel accumulator off previewScaleRaw instead, and bound previewScaleRaw to [floor*0.7, ZOOM_MAX] so it can cross the 0.9 exit threshold without drifting unboundedly (which would make direction reversal feel sticky). Touch path was unaffected (absolute ratio).
userHasZoomed stayed true after Escape/button/host-× exits, so refitScale() bailed even though forceNextResizeRefit let the ResizeObserver call it. Clear userHasZoomed in handleHostContextChanged whenever we land inline — fullscreen zoom level is meaningless there. Dropped the now-dead requestFitToContent fallback in the same block.
…lamp The previewScaleRaw side-channel made the gesture feel dead (page pinned at fit, no feedback) and the wheel accumulator interaction was fragile. New model: previewScale is the only tracked value. In fullscreen it may overshoot down to 0.75*fit so the user *sees* the page pull away as they pinch out. On commit: - started near fit (<=1.05*fit) AND preview <0.9*fit -> exit to inline - otherwise clamp committed scale to >=fit (overshoot snaps back) beginPinch seeds fitScaleAtPinchStart synchronously from when !userHasZoomed (the common enter-fullscreen-at-fit case) so the first frame already has the right floor; the async computeFitScale refines it.
'trackpad pinch is ignored outside fullscreen' is no longer the contract. Replace with two tests: pinch-in inline -> .main.fullscreen appears; pinch-out inline -> zoom unchanged, no fullscreen. The two pdf-annotations.spec.ts failures in the previous run were flaky (passed on retry).
ochafik
added a commit
that referenced
this pull request
Apr 2, 2026
Some hosts overlay UI on the iframe (chat composer, etc.) without reporting it in safeAreaInsets.bottom, so computeFitScale's 'fit' fills the iframe but the page bottom sits under the overlay. Flooring the zoom-out button at fit removed the only escape hatch. Pinch keeps the fit floor + rubber-band (it needs a defined snap point for the exit-to-inline gesture). The button now goes to ZOOM_MIN as before #587.
ochafik
added a commit
that referenced
this pull request
Apr 2, 2026
…r) (#589) Some hosts overlay UI on the iframe (chat composer, etc.) without reporting it in safeAreaInsets.bottom, so computeFitScale's 'fit' fills the iframe but the page bottom sits under the overlay. Flooring the zoom-out button at fit removed the only escape hatch. Pinch keeps the fit floor + rubber-band (it needs a defined snap point for the exit-to-inline gesture). The button now goes to ZOOM_MIN as before #587.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
background: <hex>overrode the CSSrgba(…,0.35). Now rundef.colorthroughcssColorToRgband force0.35alpha so text underneath stays readable.ctrlKey && deltaY<0or two-finger spread>1.15×triggerstoggleFullscreen(). AmodeTransitionInFlightlatch (250 ms) swallows the gesture tail.previewScalemay visibly overshoot down to0.75×fitso the page pulls away as you pinch. On release: started near fit (≤1.05×) and preview <0.9×fit→ exit to inline; otherwise committed scale clamps to ≥fit (snap-back).−button floors at fit — never letterboxes.handleHostContextChangedclearsuserHasZoomedand arms a one-shotforceNextResizeRefitso the ResizeObserver refits on the iframe shrink (its inline branch normally only refits on growth, to avoid arequestFitToContentloop). Covers pinch, Escape, button, host ×.flex-wrap: nowrap+0.25remvertical padding → flat 40px. Dropped--safe-topfrom toolbar padding (host's own header already clears it). Side/bottom safe-area insets stay.top: 39pxin fullscreen,rightfollows--safe-right.scrollWidth > clientWidth) instead ofscale ≤ 1.0, so it works in fullscreen where fit is often >100%..canvas-containergetstouch-action: pan-x pan-yso the inline pinch is capturable on iOS.Test Plan
npm run --workspace examples/pdf-server build— type-checks cleanpdf-annotations.spec.ts— addedtoHaveCSS('background-color', /0\.35/)regression checkpdf-viewer-zoom.spec.ts— replaced obsolete "pinch ignored inline" with "pinch-in →.main.fullscreen" + "pinch-out → no-op"