feat(editor): migrate to Tiptap 3 - #18
Merged
Merged
Conversation
Packages 2-7 of the migration plan, landed together because they have to
be: every @tiptap/* package peer-depends on the exact string 3.31.3, and
the binding swap, caret replacement, StarterKit reconfiguration and anchor
repoint are one dependency graph.
The real content of this upgrade is not Tiptap. It is a Yjs binding swap
-- Collaboration now installs @tiptap/y-tiptap's ySyncPlugin rather than
y-prosemirror's -- and a collaboration-cursor package that has no v3
release. anchor.ts is repointed at @tiptap/y-tiptap accordingly; importing
ySyncPluginKey from the other package would compile, run, and silently
return undefined for every anchor, because both call new
PluginKey("y-sync") and ProseMirror uniquifies the second.
Dependencies: 20 packages to ^3.31.3. extension-collaboration-cursor ->
extension-collaboration-caret. The three list and three table
sub-packages collapse into @tiptap/extension-list and
@tiptap/extension-table, whose aggregate has no default export. Adds
@tiptap/y-tiptap (a peer of both collaboration packages that nothing
declares) and @floating-ui/dom (a new runtime peer of suggestion). Drops
y-prosemirror. The prosemirror-* pins are carried unchanged -- one
variable at a time -- and pnpm still resolves a single copy of each.
StarterKit v3 turns on four things v2 did not. link and underline are
disabled because both are registered explicitly here and the duplicate
loses the argument fight over openOnClick. trailingNode is disabled
because appending a paragraph to a document that does not end in one is,
in this app, a write to a shared CRDT that every peer receives and that
reaches the markdown export -- triggered by opening a document rather
than editing it. listKeymap is disabled as inert against snake_case
nodes. history is now undoRedo.
Three new extensions arrive that cannot be turned off: textDirection and
delete from core, and a branching-delete keymap ListItem registers for
itself. All three were read before being accepted -- textDirection adds
no attribute unless configured, delete only emits events and skips
y-sync transactions -- and are now pinned in schema.test.ts with that
reasoning.
v3's Image adds width and height. Nothing here sets them, and declaring
attributes tools/schema.json does not know puts content in the CRDT that
to_markdown cannot emit, so they are dropped to keep the stored schema
identical to what every existing document was written against.
One genuine regression, found by the e2e suite and fixed here: switching
documents via the command palette took the whole route down with
"Unexpected Application Error!". Tiptap 3 empties extensionStorage on
destroy, and the comment-highlight effect runs twice against the
outgoing editor before its replacement exists -- verified by
instrumenting it: destroyed true, storage keys 0. In v2 that write landed
on a dead object and did nothing. Worth noting that the Storage interface
augmentation this migration required told tsc the property was always
defined, so the type system actively concealed it.
Caret classes follow upstream (collaboration-carets__*) rather than being
pinned to the old names with a custom render; prose.css, docText and the
converge spec move with them.
tsc, eslint --max-warnings 0 and 153 unit tests green. e2e green on a
clean stack apart from tree-reorder's "nest", the known macOS-only
failure that passes in CI.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Packages 2–7 of the migration plan, landed together because they have to be: every
@tiptap/*package peer-depends on the exact string3.31.3, and the binding swap, caret replacement, StarterKit reconfiguration and anchor repoint are one dependency graph.The real content of this upgrade is not Tiptap. It's a Yjs binding swap — Collaboration now installs
@tiptap/y-tiptap'sySyncPlugininstead ofy-prosemirror's — plus acollaboration-cursorpackage that has no v3 release.anchor.tsis repointed accordingly. ImportingySyncPluginKeyfrom the old package would compile, run, and silently returnundefinedfor every anchor: both packages callnew PluginKey("y-sync")and ProseMirror uniquifies the second.Dependencies
20 packages to
^3.31.3.extension-collaboration-cursor→extension-collaboration-caret. The three list and three table sub-packages collapse into@tiptap/extension-listand@tiptap/extension-table— whose aggregate has no default export. Adds@tiptap/y-tiptap(a peer of both collaboration packages that nothing declares transitively) and@floating-ui/dom(a new runtime peer ofsuggestion). Dropsy-prosemirror.The
prosemirror-*pins are carried unchanged — one variable at a time — and pnpm still resolves a single copy of each. Lifting them is the follow-up (WP-8).StarterKit v3 turns on four things v2 did not
link,underlineopenOnClicktrailingNodelistKeymaphistoryis nowundoRedo.Three new extensions arrive that cannot be turned off:
textDirectionanddeletefrom core, and a branching-delete keymapListItemregisters for itself. All three were read before being accepted —textDirectionadds no attribute unless configured,deleteonly emits events and skipsy-synctransactions — and are pinned inschema.test.tswith that reasoning.v3's
Imageaddswidth/height. Nothing here sets them, and declaring attributestools/schema.jsondoesn't know puts content in the CRDT thatto_markdowncan't emit, so they're dropped — keeping the stored schema identical to what every existing document was written against.One genuine regression, found and fixed
Switching documents via the command palette took the whole route down with "Unexpected Application Error!".
Tiptap 3 empties
extensionStorageondestroy(), and the comment-highlight effect runs twice against the outgoing editor before its replacement exists — verified by instrumenting it (destroyed: true, keys: 0). In v2 that write landed on a dead object and did nothing.Worth noting: the
Storageinterface augmentation this migration required toldtscthe property was always defined, so the type system actively concealed it.search.spec.tsis the regression gate — a sidebar link click does not reproduce it, and there's a comment saying so.Caret classes
Follow upstream (
collaboration-carets__*) rather than pinning the old names via a customrender.prose.css,docTextand the converge spec move with them.tsc,eslint --max-warnings 0and 153 unit tests green. e2e green on a clean stack apart fromtree-reorder's "nest" — the known macOS-only dnd-kit failure that passes in CI.Unrelated issue found along the way, worth filing separately: the collab room's
doc_updatesread leaves backendsidle in transaction (aborted). Enough of them and the collab WebSocket stops reachingconnectedwhile plain HTTP still answers — which is what made local e2e runs flaky here, and is very likely the "leaked transaction" I misattributed earlier.🤖 Generated with Claude Code