Skip to content

fix(core): install sub-extension plugins when registering at runtime - #3178

Draft
adarshsm wants to merge 1 commit into
TypeCellOS:mainfrom
adarshsm:fix/3175-runtime-sub-extension-plugins
Draft

adarshsm wants to merge 1 commit into
TypeCellOS:mainfrom
adarshsm:fix/3175-runtime-sub-extension-plugins

Conversation

@adarshsm

Copy link
Copy Markdown
Contributor

Summary

Closes #3175.

editor.registerExtension(CollaborationExtension(...)) on an editor that already exists installed no Yjs plugins, so local edits never reached the Y.XmlFragment. This fixes runtime registration (registerExtension / replaceExtension) to install the plugins of the sub-extensions an extension declares in blockNoteExtensions.

Rationale

replaceExtension built pluginsToAdd from the return values of addExtension. addExtension registers the parent and then recursively registers its blockNoteExtensions into this.extensions, but it returns only the parent. So the sub-extensions were recorded as registered while their ProseMirror plugins were never installed.

CollaborationExtension has no plugins of its own. YSync, YCursor, YUndo, ForkYDoc and the others all come from blockNoteExtensions, so registering it at runtime installed nothing. When the extension is passed to BlockNoteEditor.create it works, because getTiptapExtensions walks all of this.extensions.

Changes

  • replaceExtension now collects every extension the call added (the parent plus its sub-extensions) by diffing this.extensions before and after the add phase.
  • It orders those extensions by the same dependency priority getTiptapExtensions uses, so sub-extensions come before their parent and runsBefore is respected, just as at editor creation. The priority computation moved into a small private getExtensionPriority() that both paths share.

The ordering is required. y-prosemirror's cursor plugin reads the sync plugin's state in its init. Collecting the sub-extensions in declaration order (YCursor is declared before YSync) makes registerExtension throw from cursor-plugin.js init. With the priority order, the plugins go in as y-sync, yjs-cursor, y-undo, the same order as at editor creation.

Impact

  • Only extensions registered after the editor is created are affected. The init path is unchanged apart from calling the extracted helper.
  • For flat lists without blockNoteExtensions, such as the ones ForkYDoc passes to replaceExtension, the set of plugins added is the same as before. They're now sorted by priority, with ties kept in the given order since Array.prototype.sort is stable. That's the order they'd get at editor creation.

Testing

  • ExtensionManager.test.ts gets a new runtime-registration test. A parent with a plugin-bearing sub-extension is registered via editor.registerExtension, and the test asserts that the sub's plugin is installed and comes before the parent's.
  • New yjs/extensions/index.test.ts. It creates an editor without collaboration, registers CollaborationExtension (with an awareness provider, so the cursor plugin is included), edits, and asserts the fragment contains the edit.
  • Both tests fail on main (expected -1 not to be -1, and expected '' to contain 'hello') and pass with this change.
  • Full @blocknote/core suite: 885 passed, 9 skipped. vp lint and vp fmt --check are clean on the touched files.

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 (n/a)

Additional Notes

This PR doesn't change one related gap: extensions registered at runtime never get their mount() called, because mounting only happens in the editor.onMount handler. For collaboration this only affects YCursor's "show the label on activity" listener. Cursors and sync work without it, and flat registration (the workaround in the issue) has the same limitation. I can follow up separately if you'd like runtime registration to mount extensions on an already-mounted editor.

🤖 Generated with Claude Code

`replaceExtension` (behind `registerExtension`) collected ProseMirror
plugins only from the instances `addExtension` returned. `addExtension`
also registers the extensions declared in `blockNoteExtensions`, but it
returns just the parent, so their plugins were never installed.
`CollaborationExtension` keeps all of its plugins (YSync, YCursor, YUndo,
...) in sub-extensions, so registering it on an existing editor installed
nothing and edits never reached the Y.XmlFragment.

Collect every extension added in the call and order them by the same
dependency priority used when the editor is created. The order matters:
y-prosemirror's cursor plugin reads the sync plugin's state in `init`,
so registration throws if YCursor ends up before YSync.

Closes TypeCellOS#3175
@vercel

vercel Bot commented Oct 10, 2026

Copy link
Copy Markdown

@adarshsm is attempting to deploy a commit to the TypeCell Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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 Oct 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

@blocknote/ariakit

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

@blocknote/code-block

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

@blocknote/core

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

@blocknote/diagram-block

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

@blocknote/mantine

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

@blocknote/math-block

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

@blocknote/react

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

@blocknote/server-util

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

@blocknote/shadcn

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

@blocknote/xl-ai

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

@blocknote/xl-docx-exporter

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

@blocknote/xl-email-exporter

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

@blocknote/xl-multi-column

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

@blocknote/xl-odt-exporter

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

@blocknote/xl-pdf-exporter

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

@blocknote/xl-typst-exporter

npm i https://pkg.pr.new/@blocknote/xl-typst-exporter@3178

commit: 1080b38

This branch has not been deployed

No deployments
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.

CollaborationExtension registered at runtime installs no sync plugin, so edits never reach the Y.Doc

1 participant