Skip to content

perf(layout-bridge): cache footnote convergence to reduce typing latency#3707

Open
mattConnHarbour wants to merge 3 commits into
mainfrom
matthew/sd-3418-optimize-footnote-convergence-loop-with-caching
Open

perf(layout-bridge): cache footnote convergence to reduce typing latency#3707
mattConnHarbour wants to merge 3 commits into
mainfrom
matthew/sd-3418-optimize-footnote-convergence-loop-with-caching

Conversation

@mattConnHarbour

Copy link
Copy Markdown
Contributor

Summary

  • Cache footnote convergence loop results to skip expensive multi-pass layout on subsequent keystrokes
  • Reduce typing latency from ~170ms to ~30ms in documents with footnotes

Problem

Typing was slow in documents with footnotes because the footnote reserve convergence loop ran 4 passes per keystroke, each re-paginating the entire document (~30-40ms per pass).

Footnotes create a circular dependency: reserving space at page bottom pushes body text to the next page, which may move footnote references to different pages, requiring re-convergence.

Solution

Added FootnoteConvergenceCache class that caches converged reserves keyed by footnote ID signature:

Aspect Details
Cache key Sorted footnote IDs (stable across keystrokes)
Cache value Converged reserves array + separator spacing
Cache hit Skip Pass 1 and convergence loop entirely
Invalidation When footnotes are added/removed

Performance

Scenario Before After
Keystroke (with footnotes) ~170ms ~30ms
Convergence passes 4 0 (cache hit)

Test plan

  • Type in document with footnotes - verify cache HIT logs appear
  • Add/remove footnote - verify cache MISS and re-convergence
  • Verify footnotes render correctly on all pages

Closes SD-3418

🤖 Generated with Claude Code

mattConnHarbour and others added 3 commits June 10, 2026 16:08
Add a cache for footnote reserve convergence to skip the expensive
multi-pass loop on subsequent keystrokes. The cache key is the set of
footnote IDs (stable across edits), not positions (which shift on every
keystroke).

On cache hit:
- Skip the 4-pass convergence loop (saves ~140ms)
- Skip grow/tighten post-processing loops
- Skip widow/orphan absorb and preferred reserve trials

Cache is invalidated when footnotes are added or removed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Further optimize footnote convergence by skipping the initial Pass 1
(measure + compute plan) when we have a cache hit. Previously we ran
Pass 1 before checking the cache, then used cached reserves anyway.

Now on cache hit:
- Skip Pass 1 entirely
- Use cached reserves directly
- Do single relayout with cached reserves

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move footnote convergence cache logic into a dedicated class with
clear methods: checkHit(), getReserves(), getSeparatorSpacingBefore(),
update(), and clear().

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mattConnHarbour mattConnHarbour requested a review from a team as a code owner June 10, 2026 20:11
@linear-code

linear-code Bot commented Jun 10, 2026

Copy link
Copy Markdown

SD-3418

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 966e25e129

ℹ️ 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".

Comment on lines +2720 to +2724
// Cache hit: skip grow/tighten loops entirely — reserves are already optimal
if (usedCachedReserves) {
console.log('[layout] Skipping grow/tighten loops (using cached reserves)');
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Validate cached footnote reserves before skipping correction

When the same footnote IDs remain but their anchors move to different pages/columns (for example, typing above a footnote or opening another document with the same generated IDs), the cached page-index reserve vector can no longer match finalPlan.reserves. The code recomputes finalPlan after applying the cache, but this early return prevents the existing grow/tighten safety loop from fixing under-reserved or dead-reserved pages, so injectFragments can place footnotes into a page that was not reserved for them and overflow the band. Let the finalPlan vs reservesAppliedToLayout checks run on cache hits, or make the cache key include the page/column assignment and other layout inputs that affect reserves.

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown
Contributor

📦 Preview published: superdoc@1.39.0-pr.3707.1781122652

npm install superdoc@pr-3707

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions

Copy link
Copy Markdown
Contributor

📦 Preview published: superdoc@1.39.0-pr.3707.1781131640

npm install superdoc@pr-3707

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants