Skip to content

Stop docs cursor movement from re-measuring the whole document#444

Merged
hackerwins merged 5 commits into
mainfrom
perf/docs-cursor-relayout
Jul 5, 2026
Merged

Stop docs cursor movement from re-measuring the whole document#444
hackerwins merged 5 commits into
mainfrom
perf/docs-cursor-relayout

Conversation

@hackerwins

@hackerwins hackerwins commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

As the docs body grows, cursor movement and scrolling got progressively slower; profiling a large document showed measureText/measureWidth dominating (~57%). The cost was text measurement at layout time, which viewport/partial rendering can't help — paint is already viewport-culled, but layout measures all blocks regardless of what's visible. Root causes and fixes:

  1. Caret moves forced a full re-layout. Arrow keys / Home / End routed through render() → recomputeLayout() with dirtyBlockIds undefined, disabling the incremental cache and re-measuring every block. Now pure caret navigation repaints from the cached layout via a paint-only renderCursorMove (requestCursorRender). Table navigation stays on the full render because its table-exit branch can call ensureBlockAfter (a mutation). → ArrowRight on a 40-block doc: 1991 → <40 measureText calls.

  2. Full re-layouts re-measured char offsets uncached. Word widths were already cached, but computeCharOffsets measured every character prefix on every full re-layout (still fired by Enter/paste, remote edits, undo/redo, resize). Now memoised per (measurer, font, text). → Full recompute of a warm layout: 63 → 0 measureText calls.

  3. Caret/selection resolvers re-measured per frame. They re-measured run.text.slice(0, n) each paint instead of reading the precomputed LayoutRun.charOffsets. Now read via a shared caretOffsetX() helper (measurer kept only as a defensive fallback, so no API churn).

  4. Font-load staleness (from code review). The caches key by (font, text) with no load-state key, so layout/caret stayed on fallback-font metrics after an async web-font load — a pre-existing docs gap that caching char offsets made reach the caret. initialize() now subscribes to document.fonts loadingdoneclearMeasureCache() + invalidateLayout() + render() (mirrors slides), removed on dispose().

Net: the measureText hotspot now only fires on the first layout of content that actually changed.

Known limitation

The char-offset cache shares the exact lifecycle of the pre-existing word-width cache, whose unbounded growth docs-rendering-optimization.md explicitly deferred (LRU "if needed"); the new font-load handler drains both. Adding LRU to one but not its twin would be inconsistent — deferred to a joint follow-up if profiling shows pressure.

Test plan

  • New tests:
    • cursor-move-no-relayout.test.ts — ArrowRight/Down don't re-measure the body.
    • char-offsets-cache.test.ts — warm full re-layout does zero measurement.
    • caret-offset-x.test.ts — helper reads charOffsets, falls back correctly.
    • font-load-invalidation.test.tsloadingdone re-measures; listener removed on dispose.
  • pnpm --filter @wafflebase/docs test — 1071 pass, typecheck clean.
  • pnpm verify:fast — green (pre-commit hook on every commit).
  • Manual smoke in pnpm dev: cursor movement, shift-selection, and cross-page scrolling on a long document confirmed by the author.
  • Design doc updated: docs/design/docs/docs-rendering-optimization.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_015EPywatWADMR2iYrwauVNL

Summary by CodeRabbit

  • New Features

    • Improved cursor movement performance by reducing unnecessary full-document re-layouts.
    • Added better handling for web font loading so text positions update correctly when fonts finish loading.
  • Bug Fixes

    • Fixed caret and selection placement in headers, footers, and table cells by reusing cached text offsets.
    • Reduced repeated text measurement during editing, which can improve responsiveness on larger documents.

Arrow keys and Home/End routed through renderWithScroll -> render() ->
recomputeLayout(), which re-laid-out and re-measured every block because
dirtyBlockIds is undefined on a pure caret move (disabling the incremental
layout cache). On a large body this made cursor movement scale with the
document length; measureText/measureWidth dominated profiles.

Pure caret navigation changes no document content, so it can repaint from
the cached layout. Add a paint-only renderCursorMove (sharing the cursor
side effects via afterCursorRender) wired as requestCursorRender, and route
the non-mutating paths (non-table arrows, Home/End, Doc start/end) through
it. Table navigation stays on the full render because its table-exit branch
can call ensureBlockAfter, which mutates the document.

A single ArrowRight on a 40-block document drops from 1991 measureText
calls to under 40.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

Claude-Session: https://claude.ai/code/session_015EPywatWADMR2iYrwauVNL
Word widths were already cached (measureSegments -> cachedMeasureText),
but computeCharOffsets re-measured every character prefix uncached on every
full re-layout. Full re-layouts still fire on structural edits (Enter, paste,
multi-block delete), remote collaborative edits, undo/redo, and resize, so
this was the remaining measureText hotspot as the body grows.

Memoise the whole offsets array per (measurer, font, text), keyed like
cachedMeasureText and drained by the same clearMeasureCache path. Offsets
depend only on font and text, not layout width, so the cache also survives
resizes. Re-laying-out unchanged content now costs zero canvas measurements;
a full recompute of a warm two-paragraph layout drops from 63 measureWidth
calls to 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

Claude-Session: https://claude.ai/code/session_015EPywatWADMR2iYrwauVNL
The caret- and selection-pixel resolvers re-measured run.text.slice(0, n)
on every paint frame, even though each run already carries charOffsets
(charOffsets[i] = width of the first i+1 chars). During scroll and cursor
blink this measured on every frame and re-resolved the font, which can drift
from the layout's own seg.font.

Add caretOffsetX(run, localOffset, measurer): it reads charOffsets and only
consults the measurer on a fallback path (a run lacking a matching offset),
preserving prior correctness. Route all eight offset->x callsites through it
(body + table caret in peer-cursor, body selection in selection, and the
header/footer/table caret and selection-rect sites in editor). Image runs
keep their own display-width handling; measurer stays in the signatures for
the fallback, so no API churn.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

Claude-Session: https://claude.ai/code/session_015EPywatWADMR2iYrwauVNL
The width and char-offset caches key by (font, text) with no load-state
key, so the initial layout of a not-yet-loaded web font pins run positions
and caret offsets to the fallback face's metrics. Docs, unlike slides, never
cleared the cache when the real face arrived, so layout and caret drifted
from the painted glyphs until a reload. Caching computeCharOffsets made the
existing word-width staleness reach the caret too.

Subscribe initialize() to document.fonts 'loadingdone' and clear the cache,
invalidate layout, and re-render on fire (mirrors the slides editor); remove
the listener on dispose. clearMeasureCache already drains the offset cache via
the shared knownCaches, so one wiring fixes both the word-width and
char-offset staleness.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

Claude-Session: https://claude.ai/code/session_015EPywatWADMR2iYrwauVNL
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@hackerwins, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f6b3b93e-2062-43db-855b-095546c5f968

📥 Commits

Reviewing files that changed from the base of the PR and between e7a421d and 0ab5168.

📒 Files selected for processing (7)
  • docs/design/docs/docs-rendering-optimization.md
  • docs/tasks/active/20260705-docs-cursor-move-relayout-todo.md
  • packages/docs/src/index.ts
  • packages/docs/src/view/editor.ts
  • packages/docs/src/view/layout.ts
  • packages/docs/test/view/font-load-invalidation.test.ts
  • packages/docs/test/view/measure-cache.test.ts
📝 Walkthrough

Walkthrough

Adds a per-measurer character-offset cache and caretOffsetX helper to the docs layout module, wires caret/selection resolvers (editor, peer-cursor, selection) to reuse cached offsets, introduces a paint-only cursor render path via requestCursorRender/requestCaretRender, adds font-load cache invalidation, and documents/tests these changes.

Changes

Caret rendering performance changes

Layer / File(s) Summary
Per-measurer offset cache and caretOffsetX helper
packages/docs/src/view/layout.ts
Adds perMeasurerOffsetCache, offsetCacheFor, memoizes computeCharOffsets, registers the cache in knownCaches, and adds exported caretOffsetX with fallback measurement.
Wiring caretOffsetX into caret/selection resolvers
packages/docs/src/view/editor.ts, packages/docs/src/view/peer-cursor.ts, packages/docs/src/view/selection.ts, packages/docs/test/view/caret-offset-x.test.ts
Replaces measurer.measureWidth/resolveInlineFont calls with caretOffsetX across header/footer caret/selection paths, table-cell resolution, and peer-cursor/selection resolvers; adds tests for cached-offset and fallback behavior.
Paint-only cursor render path
packages/docs/src/view/editor.ts, packages/docs/src/view/text-editor.ts, packages/docs/test/view/cursor-move-no-relayout.test.ts
Introduces afterCursorRender/renderCursorMove in editor.ts and requestCursorRender/requestCaretRender in text-editor.ts so non-mutating caret navigation (arrows, Home/End, doc start/end) repaints without full re-layout; adds a perf test asserting reduced measureText calls.
Font-load cache invalidation
packages/docs/src/view/editor.ts, packages/docs/test/view/font-load-invalidation.test.ts
Adds a document.fonts loadingdone listener clearing the measure cache and re-rendering, removed on dispose; tests confirm re-measurement on load and no measurement post-dispose.
Char-offset caching tests
packages/docs/test/view/char-offsets-cache.test.ts
Adds coverage for offset memoization, zero re-measurement on unchanged relayout, and cache miss on text change.
Design docs and task tracking
docs/design/docs/docs-rendering-optimization.md, docs/tasks/active/20260705-docs-cursor-move-relayout-todo.md
Updates the caching design doc and adds a task document describing the staged fixes and measured results.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant TextEditor
  participant Editor
  participant Canvas

  User->>TextEditor: ArrowRight keydown
  TextEditor->>TextEditor: requestCaretRender()
  TextEditor->>Editor: requestCursorRender()
  Editor->>Editor: renderCursorMove()
  Editor->>Canvas: renderPaintOnly()
  Editor->>Editor: afterCursorRender()
Loading
sequenceDiagram
  participant FontsAPI as document.fonts
  participant Editor
  participant MeasureCache
  participant Canvas

  FontsAPI->>Editor: loadingdone event
  Editor->>MeasureCache: clearMeasureCache()
  Editor->>Editor: invalidate layout
  Editor->>Canvas: re-render
Loading

Possibly related PRs

  • wafflebase/wafflebase#62: Extends the same layout-layer text-measure caching/invalidation infrastructure in packages/docs/src/view/layout.ts and clearMeasureCache().
  • wafflebase/wafflebase#86: Modifies the same caret/selection X-coordinate measurement path in layout.ts, peer-cursor.ts, and selection.ts.
  • wafflebase/wafflebase#106: Reworks the same LayoutRun.charOffsets/computeCharOffsets pipeline used for cursor hit-testing.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main performance change: docs cursor movement no longer triggers whole-document re-measurement.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/docs-cursor-relayout

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 325.5s

Lane Status Duration
tokens:build ✅ pass 2.2s
sheets:build ✅ pass 13.5s
docs:build ✅ pass 13.9s
slides:build ✅ pass 19.8s
verify:fast ✅ pass 227.7s
frontend:build ✅ pass 19.9s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 5.2s
cli:build ✅ pass 2.2s
verify:entropy ✅ pass 20.6s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented Jul 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.55556% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/docs/src/view/text-editor.ts 16.66% 4 Missing and 1 partial ⚠️
packages/docs/src/view/editor.ts 96.29% 0 Missing and 1 partial ⚠️
packages/docs/src/view/peer-cursor.ts 50.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/docs/src/view/layout.ts (1)

37-61: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

knownCaches should not hold dead per-measurer Maps forever

perMeasurerCache/perMeasurerOffsetCache are weakly keyed, but both created Maps are also added to the module-level knownCaches Set. clearMeasureCache() only empties those maps; it never removes them from knownCaches, and editor.dispose() doesn’t release them either. That keeps each disposed editor’s cache objects alive for the lifetime of the module.

Consider adding a way to unregister a measurer’s caches on dispose, or switching to a cache registry that doesn’t retain strong references.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/docs/src/view/layout.ts` around lines 37 - 61, The per-measurer
caches created in cacheFor and offsetCacheFor are being kept alive by the
module-level knownCaches Set even after the associated editor is disposed.
Update the layout cache lifecycle so each measurer’s Map is unregistered from
knownCaches when it is no longer needed, ideally from editor.dispose or a
dedicated cleanup path, and ensure clearMeasureCache still works without
retaining dead cache objects.
🧹 Nitpick comments (2)
packages/docs/test/view/font-load-invalidation.test.ts (1)

21-56: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the shared canvas/RAF test setup installCanvasShim and the RAF-to-microtask shim are repeated across these docs view tests; moving that shared setup into packages/docs/test/view/test-utils.ts would reduce copy/paste drift. Keep the document.fonts stub local to this font-load case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/docs/test/view/font-load-invalidation.test.ts` around lines 21 - 56,
The shared canvas and RAF test setup is duplicated in this docs view test, so
move the common shim logic from installCanvasShim and the RAF-to-microtask setup
into packages/docs/test/view/test-utils.ts and reuse it from the view tests.
Keep the document.fonts stub specific to the font-load invalidation case, and
update the existing test helpers/imports so installCanvasShim remains only the
local font-loading customization point.
packages/docs/src/view/layout.ts (1)

416-472: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Solid caching design; consider extracting the repeated image/text branch into a shared helper.

computeCharOffsets/caretOffsetX are well-documented and correctly memoized/fallback-tested. However, the pattern run.imageHeight !== undefined ? (n > 0 ? run.width : 0) : caretOffsetX(run, n, measurer) is duplicated verbatim across editor.ts (4 sites), peer-cursor.ts (2 sites), and selection.ts (1 site). Exporting a single runOffsetX(run, localOffset, measurer) helper from this module (mirroring the local runOffsetX already inlined in editor.ts's computeHFTableCellSelectionRects) would remove ~7 duplicated conditionals and reduce the risk of future divergence.

♻️ Proposed shared helper
+export function runOffsetX(
+  run: LayoutRun,
+  localOffset: number,
+  measurer: TextMeasurer,
+): number {
+  if (run.imageHeight !== undefined) {
+    return localOffset > 0 ? run.width : 0;
+  }
+  return caretOffsetX(run, localOffset, measurer);
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/docs/src/view/layout.ts` around lines 416 - 472, The duplicated
image/text offset branching should be centralized: add a shared helper in
layout.ts (for example, a runOffsetX-style wrapper around caretOffsetX) that
handles `run.imageHeight !== undefined ? (localOffset > 0 ? run.width : 0) :
caretOffsetX(...)`, then update the repeated call sites in editor.ts,
peer-cursor.ts, and selection.ts to use it. Keep the existing
`computeCharOffsets` and `caretOffsetX` behavior unchanged, and preserve the
special-case handling for image runs versus text runs in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/tasks/active/20260705-docs-cursor-move-relayout-todo.md`:
- Around line 145-149: Remove the stale “Still open” entry for `#2` in the task
log, since it contradicts the earlier completed-fix section. Update the markdown
in this document so the `computeCharOffsets` / `cachedMeasureText` item is no
longer listed under `Still open (separate fixes)`, leaving only the genuinely
unresolved item(s) such as the remote-edit / undo-redo / resize full re-layout
note.

In `@packages/docs/src/view/editor.ts`:
- Around line 2436-2449: Add a document.fonts.ready fallback in the editor
font-loading logic because loadingdone may not fire reliably on WebKit. Update
the font re-layout path around the fontsTarget/handleFontsLoaded setup in
editor.ts so the same clearMeasureCache, invalidateLayout, and render sequence
also runs when document.fonts.ready resolves, while keeping the existing
loadingdone listener for browsers that support it.

---

Outside diff comments:
In `@packages/docs/src/view/layout.ts`:
- Around line 37-61: The per-measurer caches created in cacheFor and
offsetCacheFor are being kept alive by the module-level knownCaches Set even
after the associated editor is disposed. Update the layout cache lifecycle so
each measurer’s Map is unregistered from knownCaches when it is no longer
needed, ideally from editor.dispose or a dedicated cleanup path, and ensure
clearMeasureCache still works without retaining dead cache objects.

---

Nitpick comments:
In `@packages/docs/src/view/layout.ts`:
- Around line 416-472: The duplicated image/text offset branching should be
centralized: add a shared helper in layout.ts (for example, a runOffsetX-style
wrapper around caretOffsetX) that handles `run.imageHeight !== undefined ?
(localOffset > 0 ? run.width : 0) : caretOffsetX(...)`, then update the repeated
call sites in editor.ts, peer-cursor.ts, and selection.ts to use it. Keep the
existing `computeCharOffsets` and `caretOffsetX` behavior unchanged, and
preserve the special-case handling for image runs versus text runs in one place.

In `@packages/docs/test/view/font-load-invalidation.test.ts`:
- Around line 21-56: The shared canvas and RAF test setup is duplicated in this
docs view test, so move the common shim logic from installCanvasShim and the
RAF-to-microtask setup into packages/docs/test/view/test-utils.ts and reuse it
from the view tests. Keep the document.fonts stub specific to the font-load
invalidation case, and update the existing test helpers/imports so
installCanvasShim remains only the local font-loading customization point.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8bd13745-6363-4cb3-a020-c7cbf5a57e5d

📥 Commits

Reviewing files that changed from the base of the PR and between d53e289 and e7a421d.

📒 Files selected for processing (11)
  • docs/design/docs/docs-rendering-optimization.md
  • docs/tasks/active/20260705-docs-cursor-move-relayout-todo.md
  • packages/docs/src/view/editor.ts
  • packages/docs/src/view/layout.ts
  • packages/docs/src/view/peer-cursor.ts
  • packages/docs/src/view/selection.ts
  • packages/docs/src/view/text-editor.ts
  • packages/docs/test/view/caret-offset-x.test.ts
  • packages/docs/test/view/char-offsets-cache.test.ts
  • packages/docs/test/view/cursor-move-no-relayout.test.ts
  • packages/docs/test/view/font-load-invalidation.test.ts

Comment thread docs/tasks/active/20260705-docs-cursor-move-relayout-todo.md Outdated
Comment thread packages/docs/src/view/editor.ts
Three findings from the PR review:

- knownCaches strongly retained every per-measurer cache map for the
  module lifetime (one leaked pair per disposed editor), defeating the
  WeakMaps. Add disposeMeasureCache(measurer) and call it from
  editor.dispose(); export it from the package index.

- loadingdone is unreliable on WebKit/Safari, so also settle via the
  Promise-based document.fonts.ready when fonts are still loading at mount
  (guarded on dispose). Mid-session Safari picks still rely on loadingdone.

- Remove a stale 'Still open' task-log entry for the already-completed #2.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

Claude-Session: https://claude.ai/code/session_015EPywatWADMR2iYrwauVNL
@hackerwins

Copy link
Copy Markdown
Collaborator Author

Addressed the remaining review items in 0ab5168 (no inline anchor for these):

  • knownCaches retains dead maps (Major, outside-diff). Real leak — the drain Set strongly held every per-measurer cache map for the module lifetime, defeating the WeakMaps (one leaked pair per disposed editor; pre-existing for the width cache, doubled by the new offset cache). Added disposeMeasureCache(measurer) to layout.ts, called from editor.dispose(), and exported from the package index. Covered by a new measure-cache.test.ts case.
  • Extract shared installCanvasShim into test-utils.ts (Trivial, "Low value"). Declining to keep this PR focused — the shims carry per-test variations (the font-load case needs a document.fonts stub) and the duplication is contained in test setup. Happy to do a follow-up test-utils sweep if preferred.

@hackerwins
hackerwins merged commit 134899f into main Jul 5, 2026
4 checks passed
@hackerwins
hackerwins deleted the perf/docs-cursor-relayout branch July 5, 2026 12:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant