fix(core): stop schema.extend() mutating the shared default specs - #3039
Conversation
`CustomBlockNoteSchema.extend()` merged the added specs into the existing spec objects with `Object.assign`. `BlockNoteSchema.create()` passes the module-level `defaultBlockSpecs` / `defaultInlineContentSpecs` / `defaultStyleSpecs` by reference, so `BlockNoteSchema.create().extend(...)` (the documented pattern, used by `withMultiColumn` and `withPageBreak`) wrote the added specs into the shared defaults. Every editor created afterwards without an explicit schema silently inherited the extra block types and the editor extensions they register - e.g. in the playground, visiting the multi-column example and then the basic example let you drop blocks side by side in the basic editor (no drop indicator, since the `dropCursor` option isn't part of the schema). `extend()` now merges into fresh objects and reassigns `this.opts`, which also stops it from mutating a caller-owned spec object passed to `create`. Adds a regression test covering the shared defaults, caller-owned spec objects, and the end-to-end symptom (a default-schema editor created after an extended one must have neither the block nor its extension).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review. 📝 WalkthroughWalkthrough
ChangesSchema extension isolation
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to The PR prevents schema extensions from mutating shared defaults or caller-owned specifications, eliminating order-dependent behavior between editors. No actionable merge-blocking risk remains after normal checks and review. Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Description checkExplanation The description is detailed and covers the symptom, root cause, fix, impact, regression tests, and verification. It does not use every template heading or include a completed checklist, but it provides the required technical context. Full details: Linked Issues checkExplanation The changes address issue [
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
@blocknote/ariakit
@blocknote/code-block
@blocknote/core
@blocknote/diagram-block
@blocknote/mantine
@blocknote/math-block
@blocknote/react
@blocknote/server-util
@blocknote/shadcn
@blocknote/xl-ai
@blocknote/xl-docx-exporter
@blocknote/xl-email-exporter
@blocknote/xl-multi-column
@blocknote/xl-odt-exporter
@blocknote/xl-pdf-exporter
@blocknote/xl-typst-exporter
commit: |
|
nperez0111
left a comment
There was a problem hiding this comment.
I had found this separately on the nested blocks branch, but let's go with this first
Fixes #2968
Symptom
In the playground, open the multi-column example, then the basic example: you can now drop blocks side by side in the basic editor (no vertical drop indicator is shown, but columns get created).
Root cause
Global state mutation, not editor cleanup.
CustomBlockNoteSchema.extend()merged the added specs into the existing spec objects:BlockNoteSchema.create()with no arguments passes the module-leveldefaultBlockSpecs/defaultInlineContentSpecs/defaultStyleSpecsby reference, sowithMultiColumn(BlockNoteSchema.create())wrotecolumnandcolumnListinto the shared defaults. Every editor created afterwards without an explicit schema (e.g. the basic example'suseCreateBlockNote()) then inherited:column/columnListnode types, andmultiColumnDropHandler,columnResize), which theExtensionManagercollects fromschema.blockSpecs.It did not inherit the
dropCursor: multiColumnDropCursoreditor option, which is why the drop works but no side indicator is shown.This affects more than the playground:
BlockNoteSchema.create().extend({...})is the pattern the docs recommend, andwithPageBreakuses it too, so any app that creates one extended editor and later a default-schema editor got a polluted default schema.Fix
extend()merges into fresh objects and reassignsthis.optsinstead of writing into the inputs. This also stops it from mutating a caller-owned spec object passed tocreate({ blockSpecs }). The schema instance's builder semantics are unchanged (extendstill mutates and returnsthis).Tests
New
packages/core/src/schema/schema.test.tswith three cases — the shared defaults stay untouched, a caller-owned spec object stays untouched, and the end-to-end symptom (a default-schema editor created after an extended one has neither the block nor the extension it registers). All three fail on those assertions before the fix and pass after.Verification
vp lint --type-awareandvp fmtclean.["column","columnList"]and tiptap extensions["multiColumnDropHandler","columnResize","column","columnList"]; with the fix all are empty.🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests