Skip to content

perf(core): resolve block changes from changed range only - #2981

Open
nperez0111 wants to merge 1 commit into
mainfrom
feat/warsaw
Open

perf(core): resolve block changes from changed range only#2981
nperez0111 wants to merge 1 commit into
mainfrom
feat/warsaw

Conversation

@nperez0111

@nperez0111 nperez0111 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Resolve block changes from only the range a transaction touched, instead of snapshotting the entire document on every transaction.

Rationale

getBlocksChangedByTransaction (behind editor.getChanges()) walked the whole document and ran a nodeToBlock() conversion for every block, twice, per transaction. Because apps typically read getChanges() inside onChange on every keystroke, this made typing lag in large documents (O(document-size) per keypress). The existing #2595 perf test never subscribed to onChange, so this path was uncovered.

Changes

  • getBlocksChangedByTransaction now diffs only the blocks overlapping the changed range (mapped into both the old and new document) rather than snapshotting every block.
  • Added a shared getChangedRange() helper: a drop-in for ProseMirror's changedRange() that also covers attribute-only steps (AttrStep) and mark steps, at the same O(steps) cost.
  • PreviousBlockType now uses getChangedRange(), fixing a latent bug where attribute-only changes (e.g. a heading's level, a numbered list index) were silently missed by its ranged diff — breaking those CSS transitions.

Impact

Behavior of getChanges() is unchanged (all 25 snapshot tests pass as-is); only the cost model changes, from O(document-size) to O(changed-range) per transaction. PreviousBlockType now correctly animates attribute-only block changes it previously dropped.

Testing

vp run test (full @blocknote/core suite, 748 passing) plus lint/type-check pass. Added a getChanges()-in-onChange performance regression test (previously-uncovered path) and a PreviousBlockType test for the AttrStep (heading-level) case that fails without the fix.

Screenshots/Video

N/A

Checklist

  • Code follows the project's coding standards.
  • Unit tests covering the new feature have been added.
  • All existing tests pass.
  • The documentation has been updated to reflect the new feature

Additional Notes

Relates to #2595 — the earlier plugin optimizations closed that issue, but the getChanges()/onChange path (which apps hit on every keystroke) still snapshotted the whole document. This PR completes that fix and adds the regression test the original lacked.

The PreviousBlockType change is a correctness fix (not just perf) surfaced while investigating the same optimization; it is isolated to that file and its test.

Summary by CodeRabbit

  • Bug Fixes

    • Improved detection of document changes, including heading-level and other block attribute updates.
    • More accurately limits change tracking to affected document ranges.
    • Unchanged transactions are now handled without unnecessary document processing.
  • Performance

    • Improved efficiency when detecting changes in large documents, helping maintain responsive editing as document size grows.
  • Tests

    • Added coverage for attribute-only updates and change-detection performance across small and large documents.

@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blocknote Ready Ready Preview Aug 19, 2026 8:37am
blocknote-website Ready Ready Preview Aug 19, 2026 8:37am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a443cfec-917f-4675-bfb6-7e76e446f237

📥 Commits

Reviewing files that changed from the base of the PR and between df2090f and 1c74355.

📒 Files selected for processing (4)
  • packages/core/src/api/getBlocksChangedByTransaction.ts
  • packages/core/src/api/getChangedRange.ts
  • packages/core/src/editor/performance.test.ts
  • packages/core/src/extensions/PreviousBlockType/PreviousBlockType.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Changes

Transaction change detection

Layer / File(s) Summary
Changed-range computation
packages/core/src/api/getChangedRange.ts
Adds getChangedRange to combine mapped step ranges with attribute-only and mark-only changes.
Range-scoped block snapshots
packages/core/src/api/getBlocksChangedByTransaction.ts, packages/core/src/editor/performance.test.ts
Limits snapshots to affected old and new document ranges, includes overlapping ancestors, skips unchanged transactions, and tests scaling during onChange.
Attribute-change integration and regression coverage
packages/core/src/extensions/PreviousBlockType/PreviousBlockType.ts, packages/core/src/extensions/PreviousBlockType/PreviousBlockType.test.ts
Uses getChangedRange for previous block type tracking and tests heading-level updates.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to 1c743

The change narrows transaction processing to the affected document range and adds coverage for the related correctness fix; no actionable merge-blocking risk remains after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant Editor
  participant getBlocksChangedByTransaction
  participant getChangedRange
  participant Document
  Editor->>getBlocksChangedByTransaction: process transaction
  getBlocksChangedByTransaction->>getChangedRange: compute affected range
  getChangedRange-->>getBlocksChangedByTransaction: return range or null
  getBlocksChangedByTransaction->>Document: map range to old document
  getBlocksChangedByTransaction->>Document: collect overlapping block snapshots
  Document-->>getBlocksChangedByTransaction: return changed blocks
Loading

Possibly related PRs

Poem

A rabbit maps each changed span,
From old document to new it ran.
Marks and attributes join the trace,
While distant blocks remain in place.
“Less to scan!” it hops with cheer.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary performance change: resolving block changes only within the transaction's changed range.
Description check ✅ Passed The description covers the required sections, explains the rationale and impact, and documents testing; documentation remains unchecked but is non-critical.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/warsaw

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.

@pkg-pr-new

pkg-pr-new Bot commented Aug 18, 2026

Copy link
Copy Markdown

Open in StackBlitz

@blocknote/ariakit

npm i https://pkg.pr.new/@blocknote/ariakit@2981

@blocknote/code-block

npm i https://pkg.pr.new/@blocknote/code-block@2981

@blocknote/core

npm i https://pkg.pr.new/@blocknote/core@2981

@blocknote/diagram-block

npm i https://pkg.pr.new/@blocknote/diagram-block@2981

@blocknote/mantine

npm i https://pkg.pr.new/@blocknote/mantine@2981

@blocknote/math-block

npm i https://pkg.pr.new/@blocknote/math-block@2981

@blocknote/react

npm i https://pkg.pr.new/@blocknote/react@2981

@blocknote/server-util

npm i https://pkg.pr.new/@blocknote/server-util@2981

@blocknote/shadcn

npm i https://pkg.pr.new/@blocknote/shadcn@2981

@blocknote/xl-ai

npm i https://pkg.pr.new/@blocknote/xl-ai@2981

@blocknote/xl-docx-exporter

npm i https://pkg.pr.new/@blocknote/xl-docx-exporter@2981

@blocknote/xl-email-exporter

npm i https://pkg.pr.new/@blocknote/xl-email-exporter@2981

@blocknote/xl-multi-column

npm i https://pkg.pr.new/@blocknote/xl-multi-column@2981

@blocknote/xl-odt-exporter

npm i https://pkg.pr.new/@blocknote/xl-odt-exporter@2981

@blocknote/xl-pdf-exporter

npm i https://pkg.pr.new/@blocknote/xl-pdf-exporter@2981

commit: 1c74355

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://TypeCellOS.github.io/BlockNote/pr-preview/pr-2981/

Built to branch gh-pages at 2026-08-19 08:46 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

getBlocksChangedByTransaction snapshotted the entire document (a nodeToBlock
conversion of every block, twice) on every transaction. Since apps read
getChanges() on each keystroke, typing lagged in large documents. It now diffs
only the range the transaction touched.

Extract a shared getChangedRange() helper that, unlike ProseMirror's
changedRange(), also covers attribute-only steps (AttrStep) and mark steps, at
the same O(steps) cost. PreviousBlockType now uses it too, fixing a latent bug
where attribute-only changes (e.g. a heading's level) were silently missed by
its ranged diff.
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