fix(virtualization): correct scroll mapping and viewport sizing at non-100% zoom#2171
Conversation
…n-100% zoom Fix blank pages and excess scroll space when zoomed out in long documents. Three interrelated bugs caused the virtualization system to break at zoom != 100%: 1. updateVirtualWindow() used getBoundingClientRect().top (screen-space, affected by CSS transform: scale) but compared against virtualOffsets (layout-space, unscaled). Divide by zoom factor to fix. 2. PresentationEditor used 24px page gap for viewport height calculation while DomPainter virtualization defaulted to 72px. Normalize the virtualization gap to match the effective page gap. 3. CSS transform: scale() does not change layout box dimensions. At zoom < 1, painterHost's unscaled CSS box overflowed viewportHost, inflating the scroll range. Use explicit height + overflow: hidden on viewportHost to clip the CSS box.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 556f6bdbd9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
packages/super-editor/src/core/presentation-editor/PresentationEditor.ts
Outdated
Show resolved
Hide resolved
Blank documents created by the store lacked an `id`, causing the PresentationEditor instance to not register in the static `#instances` map. When `setGlobalZoom()` iterated the map it found nothing, so zoom CSS transforms were never applied. Two fixes: - Add `id: uuidv4()` to the blank document config in superdoc-store - Always register PresentationEditor instances with a fallback key when `documentId` is not provided, ensuring `setGlobalZoom` works regardless of document configuration
caio-pizzol
left a comment
There was a problem hiding this comment.
good start!
the core fix (zoom × virtualization) isn't covered by tests - each side is tested separately but the interaction isn't.
a unit test in virtualization.test.ts that calls setZoom(0.75) + triggers scroll and checks the virtual window would catch regressions here.
packages/super-editor/src/core/presentation-editor/PresentationEditor.ts
Outdated
Show resolved
Hide resolved
packages/super-editor/src/core/presentation-editor/PresentationEditor.ts
Outdated
Show resolved
Hide resolved
…om-scroll-mismatch
…ng, zoom test - Replace options.documentId mutation with #registryKey private field - Replace overflow:hidden with negative margin-bottom on painterHost to avoid clipping collaboration cursor labels - Add zoom × virtualization interaction test for non-scrollable container
caio-pizzol
left a comment
There was a problem hiding this comment.
@tupizz registryKey, overflow clipping, and zoom test all addressed. left one inline comment — the test assertions are loose enough to pass on the old buggy behavior too, so they don't guard the regression yet. not blocking.
|
@tupizz a behavior test that loads a long doc at 75% zoom, scrolls to mid-document, and checks content is visible (not blank) would go a long way — this bug is easy to regress when virtualization or zoom code changes. |
Tighten the zoom × virtualization unit test to assert exact page indices [7, 8, 9] instead of loose range checks that pass on buggy behavior. Add a Playwright behavior test that generates a long document, sets 75% zoom, scrolls to mid-document, and verifies content is visible (not blank). This guards against regressions when virtualization or zoom code changes.
|
🎉 This PR is included in superdoc-cli v0.2.0-next.53 The release is available on GitHub release |
|
🎉 This PR is included in superdoc v1.17.0-next.59 The release is available on GitHub release |
# [1.17.0](v1.16.0...v1.17.0) (2026-03-03) ### Bug Fixes * active track change ([#2163](#2163)) ([108c14d](108c14d)) * add currentTotalPages getter and pagination-update event ([#2202](#2202)) ([95b4579](95b4579)), closes [#958](#958) * always call resolveComment after custom TC bubble handlers (SD-2049) ([#2204](#2204)) ([34fb4e0](34fb4e0)) * backward replace insert text ([#2172](#2172)) ([66f0849](66f0849)) * before paragraph spacing inside table cells ([#1842](#1842)) ([c7efa85](c7efa85)) * **collaboration:** deduplicate updateYdocDocxData during replaceFile (SD-1920) ([#2162](#2162)) ([52962fc](52962fc)) * **comments:** cross-page collision avoidance for floating comment bubbles (SD-1998) ([#2180](#2180)) ([6cfbeca](6cfbeca)) * **comments:** emit empty comment positions so undo clears orphan bubbles ([#2235](#2235)) ([12ba727](12ba727)) * **comments:** improve multiline comment input styling ([#2242](#2242)) ([e6a0dab](e6a0dab)) * **comments:** prevent comment mark from extending to adjacent typed text ([#2241](#2241)) ([07fecd8](07fecd8)) * **comments:** reduce sidebar jitter when clicking comments (SD-2034) ([#2250](#2250)) ([c3568d2](c3568d2)) * **comments:** remove synchronous dispatch from plugin apply() (SD-1940) ([#2157](#2157)) ([887175b](887175b)) * **css:** scope ProseMirror CSS to prevent bleeding into host apps (SD-1850) ([#2134](#2134)) ([b9d98fa](b9d98fa)) * document-api improvements, plan mode, query.match, mutations ([6221580](6221580)) * **document-api:** delete table cell fix ([#2209](#2209)) ([5e5c43f](5e5c43f)) * **document-api:** distribute columns command fixes ([#2207](#2207)) ([8f4eaf7](8f4eaf7)) * **document-api:** fix cell shading in document api ([#2215](#2215)) ([456f60e](456f60e)) * **document-api:** insert table cell ([#2210](#2210)) ([357ee90](357ee90)) * **document-api:** plan-engine reliability fixes and error diagnostics ([#2185](#2185)) ([abfd81b](abfd81b)) * **document-api:** split table cell command ([#2217](#2217)) ([0b3e2b4](0b3e2b4)) * **document-api:** split table command ([#2214](#2214)) ([ec31699](ec31699)) * **editor:** render styles applied inside SDT fields (SD-2011) ([#2188](#2188)) ([9c34be3](9c34be3)) * **editor:** selection highlight flickers when dragging across mark boundaries (SD-2024) ([#2205](#2205)) ([ba03e76](ba03e76)) * extract duplicate block identity normalization from docxImporter ([7f7ff93](7f7ff93)) * improve backspace behavior near run boundaries for tracked changes ([#2175](#2175)) ([6c9c7a3](6c9c7a3)) * **layout:** per-section footer constraints for multi-section docs (SD-1837) ([#2022](#2022)) ([e11acc5](e11acc5)) * markdown block-separator blank lines and heading split style-mark normalization ([e988adc](e988adc)) * normalize review namespace into trackChanges, harden input validation ([33e907b](33e907b)) * outside click for toolbar dropdown ([#2174](#2174)) ([5f859c7](5f859c7)) * prefer full decoration range ([#2239](#2239)) ([ac15e31](ac15e31)), closes [#collectDesiredState](https://github.com/superdoc-dev/superdoc/issues/collectDesiredState) [#resolveEffectiveRanges](https://github.com/superdoc-dev/superdoc/issues/resolveEffectiveRanges) [#setPreviousRanges](https://github.com/superdoc-dev/superdoc/issues/setPreviousRanges) * preserve line spacing and indentation on Google Docs paste ([#2183](#2183)) ([b9a7357](b9a7357)), closes [#2151](#2151) * preserve text-align on paste from Google Docs ([#2208](#2208)) ([762231b](762231b)) * rollback comments colors / ui ([#2216](#2216)) ([a99b5ab](a99b5ab)) * **scroll:** wait for virtualized page mount and center text element ([#2221](#2221)) ([95f634e](95f634e)) * **shapes:** render grouped DrawingML shapes with custom geometry (SD-1877) ❇️ ([#2105](#2105)) ([14985a5](14985a5)) * splitting run with header adds empty row ([#2229](#2229)) ([e1965fc](e1965fc)) * **super-converter:** handle empty pic:spPr in image import ([#2254](#2254)) ([2b8dbce](2b8dbce)) * **super-editor:** backspace across run boundaries without splitting list items ([#2258](#2258)) ([27ccb64](27ccb64)) * support cell spacing ([#1879](#1879)) ([1639967](1639967)) * **tables:** defaultTableStyle support, cell fixes ([#2246](#2246)) ([74fca9c](74fca9c)) * **tables:** expand auto-width tables to fill available page width ([#2109](#2109)) ([15f36bc](15f36bc)) * **tables:** preserve TableGrid defaults and style-driven spacing/bor… ([#2230](#2230)) ([b0a482f](b0a482f)) * text highlight on export ([#2189](#2189)) ([9cbd022](9cbd022)) * track highlight changes ([#2192](#2192)) ([e164625](e164625)) * **track-changes:** correct format change description for already-formatted text (SD-2077) ([#2253](#2253)) ([b2ffc0d](b2ffc0d)) * **track-changes:** handle ReplaceAroundStep in tracked changes mode (SD-2061) ([#2225](#2225)) ([8f3cbe4](8f3cbe4)) * **track-changes:** remove ghost TrackFormat on multi-node format cancel ([#2233](#2233)) ([e925ef9](e925ef9)) * undo/redo actions ([#2161](#2161)) ([495e92f](495e92f)) * **virtualization:** correct scroll mapping and viewport sizing at non-100% zoom ([#2171](#2171)) ([84af4c0](84af4c0)), closes [#registryKey](https://github.com/superdoc-dev/superdoc/issues/registryKey) ### Features * allow custom accept/reject handlers for TC bubbles ([#1921](#1921)) ([e30abf6](e30abf6)) * **comments:** improve floating comments ui ([#2195](#2195)) ([e870cfb](e870cfb)) * **document-api:** add format operations font size alignment color font family ([#2179](#2179)) ([f19c688](f19c688)) * **document-api:** add get markdown to sdks ([e42b56d](e42b56d)) * **document-api:** add plan-based mutation engine with query.match and style capture ([#2160](#2160)) ([365293a](365293a)) * **document-api:** default table style setting ([#2248](#2248)) ([3ad4e9f](3ad4e9f)) * **document-api:** default target-less insert to document end ([#2244](#2244)) ([c717e2b](c717e2b)) * **document-api:** doc default initial styles ([#2184](#2184)) ([f25e41f](f25e41f)) * **document-api:** format.paragraph for w:pPr formatting ([#2218](#2218)) ([32c9991](32c9991)) * **document-api:** history name space ([#2219](#2219)) ([41dea37](41dea37)) * **document-api:** include anchored text in comments list response ([#2177](#2177)) ([b3a2912](b3a2912)) * **document-api:** inline formatting parity core end-to-end ([#2197](#2197)) ([b405b03](b405b03)) * **document-api:** inline formatting rpr parity ([#2198](#2198)) ([41ab771](41ab771)) * **document-api:** lists namespace ([#2223](#2223)) ([09ebfcb](09ebfcb)) * **document-api:** section commands ([#2199](#2199)) ([ec4abe3](ec4abe3)) * **document-api:** support deleting entire block nodes not only text ([#2181](#2181)) ([2897246](2897246)) * **document-api:** table of contents commands ([#2200](#2200)) ([baa72c4](baa72c4)) * **document-api:** tables namespace and commands ([#2182](#2182)) ([b80ee31](b80ee31)) * **document-api:** toc commands ([#2220](#2220)) ([767e010](767e010)) * **images:** allow drag-and-drop for images in editor ([#2227](#2227)) ([4b36780](4b36780)) * **layout-engine:** render table headers, tblLook support ([#2256](#2256)) ([db6a2ff](db6a2ff)) * **link-popover:** custom link popovers ([#2222](#2222)) ([070190f](070190f)) * **markdown:** add markdown override to sdk, improve conversion ([#2196](#2196)) ([04a1c71](04a1c71)) * preserve w:view setting through DOCX round-trip ([#2190](#2190)) ([48b4210](48b4210)), closes [#2070](#2070) * real time collab in python sdk ([#2243](#2243)) ([dc3b4fd](dc3b4fd)) * **tables:** allow resizing table rows ([#2226](#2226)) ([2c6da10](2c6da10)) * **tables:** improve cell color application (context), column dragging, table pasting ([#2228](#2228)) ([066b9eb](066b9eb)) * **table:** toggle header row sets both cell types and repeatHeader atomically ([#2245](#2245)) ([2f5899d](2f5899d)) * **track-changes:** clear comment bubbles when bulk accept or reject TCs ([#2159](#2159)) ([27fbe8e](27fbe8e)) ### Performance Improvements * **comments:** batch tracked change comment creation on load ([#2166](#2166)) ([0c2eca5](0c2eca5)) * **comments:** batch tracked change creation and virtualize floating bubbles (SD-1997) ([#2168](#2168)) ([70fd7d9](70fd7d9))
|
🎉 This PR is included in superdoc v1.17.0 The release is available on GitHub release |
Summary
Fixes blank pages and excess scroll space when zoomed out (e.g., 75%) in long documents with virtualization enabled.
Relates to: IT-579 (cursor accelerates to bottom in long documents), IT-581 (scroll jumps on long documents)
Root Cause
Three interrelated bugs caused the virtualization system to break at zoom != 100%:
1. Virtual window scroll calculation ignores CSS transform scale
DomPainter.updateVirtualWindow()usesgetBoundingClientRect().topto determine scroll position. This returns screen-space coordinates (affected bytransform: scale(0.75)), butvirtualOffsetsare in layout space (unscaled). The mismatch grows linearly with scroll position — at scrollTop=15000 with 75% zoom, the error is ~5000px, causing the virtualization to mount pages above the actual viewport (blank pages).2. Gap mismatch between PresentationEditor and DomPainter
PresentationEditor calculated viewport height using 24px page gaps (
DEFAULT_PAGE_GAP), but DomPainter's virtualization defaulted to 72px (DEFAULT_VIRTUALIZED_PAGE_GAP). This made the actual rendered content taller than the viewport expected, causing the spacer math to be inconsistent.3. CSS box overflow inflating scroll range
CSS
transform: scale()does not change an element's CSS box dimensions. At zoom < 1, painterHost's CSS box (full unscaled height, e.g., 74304px) overflowed viewportHost'sminHeight(scaled, e.g., 53442px), making the scroll container see ~30% extra unusable scrollable space at the bottom.Changes
painters/dom/src/renderer.tszoomFactorproperty +setZoom()method; fixedupdateVirtualWindow()to dividerect.topby zoom to convert screen-space → layout-spacepainters/dom/src/index.tssetZoom()through thecreateDomPainterfacadePresentationEditor.tssetZoom()in bothsetZoom()and#ensurePainter(); normalize virtualization gap to match effective page gap; use explicitheight+overflow: hiddenon viewportHostBefore / After (75% zoom, scrollTop=15000)
minHeight: 53442px(overridden by child CSS box)height: 53442px+overflow: hiddenTest plan