feat: @y/prosemirror v2 + @y/y v14 integration#2739
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 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 |
776ca34 to
5fae30a
Compare
5fae30a to
d70df64
Compare
@blocknote/ariakit
@blocknote/code-block
@blocknote/core
@blocknote/mantine
@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
commit: |
00569fc to
38d3e13
Compare
38d3e13 to
a56bb0d
Compare
a9f7067 to
2ad90dc
Compare
a56bb0d to
6b9a6f6
Compare
e44394d to
17ab49f
Compare
6b9a6f6 to
d13fcac
Compare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The docs package postinstall runs fumadocs-mdx, which fails under
esbuild 0.27.x ('source.config.ts' cannot be marked as external).
docs is not needed for e2e (the suite resolves @blocknote/* to src),
so guard the postinstall behind SKIP_DOCS_POSTINSTALL and set it for
both the image build install and the runtime vp test deps re-install.
Also bump the Dockerfile pnpm to 11.8.0 to match packageManager.
Broaden the e2e include glob from y-prosemirror/** to all end-to-end/** so the complete suite runs in the Docker (linux) container. Generate the missing linux reference screenshots and remove the stale darwin baselines, since CI/e2e only runs on linux. Also reorder a tables.concurrent snapshot to match the produced row order.
…inux baselines Re-enable the firefox and webkit browser instances in the e2e browser config so the CI matrix's firefox/webkit shards have a matching instance (they were failing instantly with an empty browser.instances array). Add a global toMatchScreenshot tolerance (allowedMismatchedPixelRatio 0.02) so minor anti-aliasing / sub-pixel font diffs in the shared Docker container don't fail exact pixel comparisons. Generate the missing firefox/webkit linux screenshot baselines (now 84 per browser) for the full end-to-end suite.
…on merges Pin Y.Doc clientIDs (base=1, A=2, B=3, merged=4) in the concurrent suggestion fixture so CRDT tiebreaking is deterministic across runs, making the merged results stable to snapshot. Update the affected inline snapshot column ordering accordingly.
…ing tooltips Render the insert/delete/modification suggestion marks through a shared ProseMirror mark view instead of CSS-only tooltips. The author tooltip is now portaled to <body> and positioned as a fixed element, so it is no longer clipped by an ancestor's overflow (e.g. a table cell). - The mark wrapper is always `display: contents` so it never disturbs layout (notably table structure for block-level suggestions). - Inline marks (over text) show a highlight plus a hover tooltip; block-level marks (over a node) show a node background highlight only, no tooltip. - Fold the modification mark into the shared view, rendering a <span> inline or a <div> over a block to match its parseDOM rules. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move suggestion-mark hover tooltips out of the per-mark-view code and module-global state into a new SuggestionMarksExtension. The extension installs a single delegated mouseover listener on the editor root and reads attribution straight from the mark wrappers' data-* attributes, so the mark view stays purely presentational and tooltip state is scoped per editor instead of global. Tooltip resolution now picks the innermost attributed mark under the pointer (children win over parents) and also covers block-level marks, not just inline ones. Nested identical tooltips are de-duplicated: when an enclosing mark would show the same author, the tooltip anchors on the outermost same-text ancestor so one stable tooltip covers the whole region; the child only keeps its own tooltip when an enclosing mark has different content. The tooltip is portaled to the editor's portalElement rather than document.body. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add v14 (@y/y / @y/prosemirror) equivalents of the yjs conversion helpers (blocksToYDoc/yDocToBlocks/blocksToYType/yfragmentToBlocks) and mirror the v13 utils test suite for them. Read-back uses deltaToPNode so empty documents normalize to a single empty paragraph instead of throwing on an empty blockGroup. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ibution marks Add a `suggestion_changes` dictionary section (formatting_change, deleted) and use it to prefix modification-mark tooltips with a localized label of which formatting changed, and to render a localized "Deleted" badge on deleted blocks. Flip the tooltip to anchor above the mark, and soften the ins/del/mark backgrounds with rounded corners. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add snapshotBuilder for building named Yjs snapshots of a document's history by diffing successive ProseMirror docs into deltas (without the y-prosemirror sync plugin), plus a seed utility to push those snapshots to a YHub document. Includes tests and re-exports. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the snapshot builder and YHub seeding into the yjs14 versioning example so it loads with a realistic multi-author document history, with accompanying styling tweaks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move user resolution/caching out of the Comments feature into its own `UserExtension` so it can be reused by other features. The extension owns a BlockNote store, caches resolved users, de-dupes in-flight requests, and exposes `loadUsers`/`refetchUsers`/`getUser`. It is generic over the user type returned by `resolveUsers`. CommentsExtension now declares the dependency via `blockNoteExtensions` (still accepting `resolveUsers`). React `useUser`/`useUsers` are reimplemented over `useExtensionState` + an effect (dropping the useSyncExternalStore boilerplate) and moved to `hooks/`. Fixes the infinite load loop when a user can't be resolved (#1548) by only emitting a store update when users were actually resolved and by no longer re-triggering loads on every store update. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
De-duplication: when an extension with a given key is already registered, skip registering another one with the same key (the first registration wins). This lets an extension declare a dependency on another via `blockNoteExtensions` without conflicting when that same extension is also registered directly by the user. Ordering: a sub-extension declared via `blockNoteExtensions` is a dependency of the extension that declares it, so it now runs before its parent. The dependency is recorded as the sub is resolved (before the de-duplication check), so it holds even when multiple parents declare the same sub-extension and when a parent has a higher base priority via `runsBefore`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace whole-node replacements with minimal-step helpers (setNodeMarkupMinimal, replaceContentMinimal) so updateBlock only touches the part of a block that actually changed, keeping collaborative diffs small. Extract the shared setNodeMarkupMinimalAndRemap helper to de-duplicate the post-markup position remapping, and cover the behavior with minimal-step regression tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add Sidebar and Snapshot versioning components for the ariakit, mantine, and shadcn skins, with shared styles and a VersioningSidebarContext. Wire them through ComponentsContext and the react Versioning components. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Turning off comparison (or otherwise leaving a version preview) could leave the editor stuck showing the diff, with a thrown error: "Node should be a bnBlock, but is instead: bulletListItem". Root cause is in the preview teardown, not the sidebar UI. `configureYProsemirror` replaces the whole document in a single transaction (it does not yet apply a minimal diff). When the incoming document is smaller than the current one -- e.g. tearing down a diff whose "old" side contained deleted blocks -- ProseMirror reuses existing node view descs while reconciling. During that reconciliation a reused desc reports a transiently stale position: `updateNextNode` calls `next.update()` before `destroyBetween` removes the intervening deleted descs, so `posAtStart` is inflated by the size of the not-yet-destroyed deleted content. BlockNote's block node views resolve their block eagerly at construction via `getPos()` (getBlockFromPos), so they get handed that stale position, resolve it against the correct (smaller) new doc, land inside the wrong node, and throw -- aborting the render and leaving the old diff decorations on screen. Fix: empty the document before each `configureYProsemirror` refill so ProseMirror tears down every node view first. The refill then only *creates* views (each with a correct position) and never reuses a moved one. Yjs sync is paused first (`pauseSync`) so the clear stays local and never reaches the Y.Doc -- the real document is untouched. This is a workaround for the wholesale replace. It can be removed once `configureYProsemirror` applies a minimal diff, since unchanged blocks would then keep their node views and positions and no stale-position reuse could occur. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comparison is a property of the PreviewController (whether enterPreview can render a diff), so surface it from core instead of a React prop. Add PreviewController.supportsComparison and expose it as the extension's canCompareVersions capability; the Yjs v13 adapter reports it off since it can only fork to a single snapshot. The sidebar reads the capability from the extension and hides the comparison toggle and "Compare with…" actions when unsupported. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clicking a version's title in the sidebar now selects/views the version on the first click and only starts editing on a second click, once the version is the one being viewed. The title cursor reflects this: a pointer when the click will select, a text caret when it's editable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…badges The suggestion-mode linux screenshot baselines predated 496521c (localized change labels and deleted badges on attribution marks), which renders a "Deleted" badge on deleted blocks and adds vertical height. Every deletion-involving suggestion test was failing on a screenshot dimension mismatch. Regenerate the affected baselines across chromium/firefox/webkit; full suite now passes (508 passed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
YousefED
left a comment
There was a problem hiding this comment.
Great. not a lot of major feedback atm.
Still need to review the examples + React components
| const posInfo = getNearestBlockPos(tr.doc, pos.pos); | ||
| const blockInfo = getBlockInfoAt(tr, pos.pos); | ||
| const id = getNodeId(blockInfo.bnBlock.node, tr.doc); | ||
| // TODO are these safe? |
There was a problem hiding this comment.
I think the data-id in the editor doesn't have the deleted index suffix. I think the preferred fix would be to make sure that it has (there are a few other spots, e.g.: floating ui), that depend on the queryselector
| const out: Record<string, unknown> = { ...format }; | ||
|
|
||
| if (attribution.insert) { | ||
| const colors = colorsForUserIds(attribution.insert); |
There was a problem hiding this comment.
Two (related) questions:
- is this the right place for setting colors? We have the userIds on the marks, can't we in the rendering layer map these user ids to colors?
- it's currently hashed by userId, but I think we want to wire them up to collaboration user settings right? (
CollaborationUser.color)
| * @param fragment XML fragment name | ||
| * @returns BlockNote document (BlockNote style JSON of all blocks) | ||
| */ | ||
| export function yDocToBlocks< |
There was a problem hiding this comment.
we have similar functions in server-util. Should we remove / update those?
| @@ -0,0 +1,450 @@ | |||
| import * as Y from "@y/y"; | |||
There was a problem hiding this comment.
Let's discuss with Kevin tomorrow:
- How to do "granularity config" for activity items
- If / how to do manual snapshot support. The version attribution seems too hacky imo
|
|
||
| /* ---- Versioning sidebar -------------------------------------------------- */ | ||
|
|
||
| .bn-versioning-sidebar { |
There was a problem hiding this comment.
@matthewlipski could you review the UX components in this PR? Do they conform to existing patterns? Are they similar to how you would set them up? etc (for all 3 implementations)
The v13 versioning example synced the live doc only over the broadcast channel, so a refresh started from an empty document. Restore the doc from localStorage before creating the editor and persist full state on each update, keeping edits across refreshes. Snapshots remain stored separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an optional `deleteSnapshot` endpoint to the versioning contract, surfaced through the extension as `canDeleteSnapshot`/`deleteSnapshot`. Deleting exits preview first if the removed snapshot is being previewed or is the diff baseline, then optimistically drops it and reconciles. The React sidebar shows a "Delete" item in each snapshot's context menu when the backend provides the endpoint. Implemented for the in-memory and localStorage reference endpoints, with tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Adds support for the new
@y/prosemirrorv2 and@y/yv14 (Yjs 14) packages alongside the existing Yjs 13 (yjs,y-prosemirror) integration. This includes new collaboration bindings, versioning extensions, forked document support, and suggestion tracking built on the v14 APIs.Rationale
The
@y/yv14 ecosystem introduces significant improvements over Yjs 13, including better TypeScript support, a new protocol layer, and improved ProseMirror bindings. This PR adds first-class support for v14 while keeping full backward compatibility with Yjs 13.Changes
New
@blocknote/core/yentry point@y/prosemirrorv2 with TipTap/BlockNote@y/prosemirrorv2@y/yv14 APIsVersioning system (
@blocknote/coreextensions)yjspackageVersioningSidebar,Snapshot,CurrentSnapshotEditor improvements
y-attributed-deletemark) to avoid corrupting suggestionsgetBlockInfoFromPosnow handles nodes with marks (suggested changes)moveBlocks,replaceBlocks,splitBlock,nestBlock) to handle marked/attributed nodesPatches
@y/prosemirror@2.0.0-2— extensive patch for BlockNote compatibility@y/y@14.0.0-rc.16— minor patchlib0@1.0.0-rc.13— compatibility patchExamples
Tests
Impact
@y/packages are optional peer dependencies@blocknote/core/yexport for v14 users, parallel to existing@blocknote/core/yjsScreenshots/Video
Live examples:
Checklist