6.2 KiB
TipTap Migration Notes
Migrated from Lexical to TipTap for the markdown rich-text editor
(MarkdownRichTextViewer). This file tracks known trade-offs and
follow-up work items.
Why we migrated
The Lexical implementation had two lossy coordinate translations in series:
- Lexical tree → Lexical-normalised markdown (soft-break collapsing, character escaping, table pipes) introduced systematic drift from the raw file content stored on the server.
- Markdown offset → raw file offset via a ±200-char window search that failed whenever drift exceeded the window.
The result: comment highlights silently disappeared or anchored to the wrong range.
TipTap's approach with ProseMirror Decorations fixes this:
- Decorations never touch the document → markdown serialisation is clean.
doc.textBetween(0, size, "\n")is much closer to the raw file than Lexical's normalised markdown.- ProseMirror positions are stable integers that remap automatically
through
tr.mapping, no bespoke inverse-offset code needed.
Known downsides / follow-up items
1. @tiptap/markdown is in beta
Status: @tiptap/markdown is the official TipTap markdown extension
(part of the ueberdosis/tiptap monorepo, same version cadence as all
other @tiptap/* packages we use). It is marked beta by the team.
Known gaps called out in the docs:
- HTML comments are not supported
- Table cells allow only one child node per cell
Mitigation: Track their GitHub issues; it's the path-of-least-resistance to patch since it's in the monorepo.
2. Markdown round-trip fidelity is imperfect
tiptap-markdown uses markdown-it for parsing and a custom serialiser
for export. It does not guarantee perfect idempotency:
- Setext-style headings (
Heading\n======) → ATX (# Heading) - Tight vs loose list spacing may normalise on first save
- HTML blocks embedded in markdown are stripped by default (
htmldefaults tofalsein@tiptap/markdown) - Thematic breaks (
***,- - -) always serialise as---
Impact: First save after opening a file may produce minor whitespace
or syntax normalisation even without user edits. The baseline check
(markdown === baselineRef.current) prevents spurious dirty-flag triggers,
but a user who opens a file and immediately saves will write a normalised
version.
Follow-up: Test round-trip fidelity against real agent-generated markdown files. Add a post-save diff warning if normalisation occurred.
3. Comment anchor search can still miss on duplicate content
The new implementation searches for anchor_content in the PM text
content near a scaled start_index hint (±500 chars window). If the
same text appears multiple times and the hint doesn't discriminate,
the first match is used.
This is better than the old ±200-char window but not immune.
Follow-up (tracked): Add a Tier-3 "context anchoring" strategy that hashes surrounding context (e.g., previous paragraph heading) to disambiguate identical phrases.
4. Table editing UX is limited
@tiptap/extension-table requires explicit row/cell add/delete commands
via the toolbar. The old Lexical implementation had the same limitation.
Follow-up: Add table toolbar controls (insert row, insert column, delete row, merge cells).
5. Active comment highlight matched by offsets, not id
buildDecorations identifies the active comment by comparing
activeSelection.start_index / end_index against each comment's
stored offsets. Two comments on the same range would both receive
md-comment-active.
Follow-up: Add an optional id field to ActiveSelection and
populate it when activating a saved comment. The extension can then
prefer id matching when available, falling back to offset matching
for pending (unsaved) selections.
Files removed
| File | Reason |
|---|---|
MarkdownEditorHelpers.tsx |
Lexical-specific offset math ($invertMarkdownOffset, computeLexicalMarkdownPointOffset, normalizeSoftBreaks, custom table walker) |
MarkdownEditorHelpers.test.ts |
Tests for the above |
MarkdownTableTransformer.ts |
Custom Lexical table node + transformer |
MarkdownTableTransformer.test.ts |
Tests for the above |
MarkdownTheme.ts |
Lexical CSS class theme — replaced by index.css prose rules |
Files added
| File | Purpose |
|---|---|
TipTapEditorHelpers.ts |
findPmRangeForComment, computeSelectionData — text-content ↔ PM position mapping |
TipTapCommentExtension.ts |
ProseMirror Plugin + TipTap Extension for Decoration-based comment highlights |
Files rewritten
| File | Changes |
|---|---|
MarkdownCommentPlugin.tsx |
No Lexical; uses TipTap Editor, dispatches rebuild transactions |
MarkdownEditorToolbar.tsx |
Replaced useLexicalComposerContext + dispatch commands with editor.chain() |
MarkdownRichTextViewer.tsx |
Replaced LexicalComposer with useEditor / EditorContent |
MarkdownRichTextViewer.test.tsx |
Mocks TipTap modules instead of Lexical modules |