Repository navigation
perf(latex): stop scroll re-layout and sibling :has() restyles in Visual LaTeX - #810
Merged
Merged
Conversation
Scrolling Visual LaTeX kept the main thread ~85% busy at 4x CPU. Every content-visibility reveal started a paged-layout pass, and every pass republished hundreds of decorations and rewrote every block's intrinsic size, so the reading anchor's next read forced a full style and layout. - A reveal starts a pass only when the block's rendered size differs from its skipped size. Chromium reports that resize before the reveal event. - A recomputed spacer or flow offset within 1 px of the published one keeps the published value. Read-back positions drift by fractions of a pixel, so passes never settled and replaced ~400 decorations each time. - Intrinsic sizes are written in layout units and compared numerically. The string comparison never matched the six-digit serialization, so each pass wrote ~1,000 inline styles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pass that turns the page plan into spacers read each block's position in work slices across frames. A block revealed between slices moved the blocks read after it, so the pass published page gaps and flow offsets hundreds of pixels off. The next reveal flipped them back, moving visible text and forcing another full layout. Reading positions is cheap once layout is clean, so the reading loop no longer yields. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to claude/scroll-perf-20261109
…() fix Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Five sibling :has(+ …) rules made Chromium restyle every top-level paragraph, heading and display on each block insertion. A ProseMirror plugin now marks a paragraph before a display (and whether the paged layout set that display short) and a run-in heading before its paragraph. The marks map through edits and are rebuilt only around the change. Showing the 61-page manuscript's tab at 4x: long frames 5.7-6.3 s -> 2.7-3.9 s. A paragraph holding a footnote is now raised above a short display like any other line; the note band between them in the DOM used to hide the display. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A heading level change through setNodeAttribute maps no positions, so the plugin missed it and an h3 turned h4 kept no run-in mark. Attribute steps now widen the changed range. The sibling :has() guard also catches a relative selector later in a :has() list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
yaacovcorcos
enabled auto-merge
November 9, 2026 17:00
Contributor
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Nov 9, 2026
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.
Platforms: measured and tested in headless Chromium on macOS (Apple Silicon). Electron 44 uses the same engine on Windows and Linux, but neither was run.
What Changed
Follow-up to #801 and #806. Scrolling a Visual LaTeX document no longer re-lays out the paged document, and showing the LaTeX tab costs about half as much. All changes are in Scient files; no T3 file changes.
Scrolling (
latexPagedPresentation.ts):content-visibilityreveal now schedules a pass only when the block's size actually changed (reported by the sharedobserveResize). Unknown blocks keep the old path.getBoundingClientRectdrifted by up to 0.55 px from the published values, so about 400 of 768 decorations were replaced on every pass. A gap or offset within 1 px of the published one now keeps it.Showing the tab (new
latexBlockNeighbours.ts)::has(+ …)selectors made Chromium restyle every top-level block on each block the loader inserted.data-latex-before-display,"short"when the paged layout set the display short) and a run-in heading before its paragraph (data-latex-run-in). The CSS uses those attributes.universalSiblingSelectors.test.tsnow also rejects sibling:has()undersrc/scient.One visible difference: a paragraph holding a footnote, followed by a short display, is now raised above the display like every other such line. Before, the note's band between them in the DOM hid the match, and the formula took clicks on that line's lower half. Computed styles of every paragraph, heading and display were otherwise identical across 11 editor states.
Why
61-page Visual LaTeX document, production build:
:has()invalidation records per tab showPDF scrolling needed nothing more: #806 already removed its cost.
What remains of tab show is mostly the real style and layout of the content. Keeping the editor mounted would remove it, but it needs T3 panel changes and 0.3–0.4 GB per thesis-sized document, so it is not done here.
Verification
vp run check:changed origin/mainpasses: format, lint, knip, typecheck, 7 affected test files and the React Compiler check.latexBlockNeighbours.test.tschecks the plugin against a fresh one after an explicit sequence and 400 seeded random edits, undo/redo and layout publications. Deliberate breaks to the plugin make it fail, including the attribute-step case below.latexPagedPresentation.browser.test.tsxcases cover the reveal gate and the single-layout read.setNodeAttributemaps no positions, so the plugin missed it;:has()list.Documentation Impact
Documentation impact: Updated —
docs/internals/scient-latex-visual-performance.md(scroll causes and the remaining tab-show cost).Checklist
Implementation: Claude Code (Claude Opus 5.5). Review: Codex (gpt-6.1-sol).
🤖 Generated with Claude Code