feat(examples): add drop-in-assessment app — TipTap → SuperDoc migration harness (SD-2671)#2963
Conversation
…ion harness (SD-2671)
A working reference app that drives the same custom React UI (toolbar +
activity sidebar) against TipTap and SuperDoc through a shared
EditorAdapter contract. Toggling the editor in the header swaps the
adapter; the rest of the UI stays put. Every workaround the SuperDoc
adapter takes is annotated `FRICTION:` or `ESCAPE HATCH:` and rolled up
in `FRICTION.md` as a drop-in DX gap.
What this exercises:
- Comments: range selection → `editor.doc.comments.create` with the
multi-segment `TextTarget` from `editor.doc.selection.current()`,
list / patch / delete, sidebar ↔ inline highlight sync against both
TipTap's `data-comment-id` mark and SuperDoc's `data-comment-ids`
decorator.
- Tracked changes (SuperDoc only): imported from a Word-authored DOCX,
rendered alongside comments in one chronological feed, accept/reject
via `trackChanges.decide`, sidebar-card click scrolls to anchor via
`editor.doc.ranges.scrollIntoView({ target: EntityAddress })`.
- Selection: `selection.current()` for read, `selection.onChange` for
live updates — no PM reach-in.
- Toolbar: SuperDoc's `createHeadlessToolbar` 45-command closed union;
H1/H2/highlight buttons disabled with FRICTION notes for the gaps.
Honest omissions:
- TipTap ships no built-in tracked changes; the TipTap view shows an
empty TC panel rather than mock parity. The missing capability is
the finding.
- HTML/Markdown persistence: out of scope. SuperDoc is DOCX-first.
- `addComment` returns `null` on engine failure rather than fabricating
a placeholder comment — a harness should surface failures, not hide
them.
- Telemetry explicitly opted out (`telemetry: { enabled: false }`) so
consumers copying this config don't ship to ingest.superdoc.dev by
default.
Sample content: `public/sample-review.docx` is a two-page Word memo with
3 comments + 3 tracked insertions, two distinct authors, exercises
multi-page scroll and multi-author attribution.
Verified end-to-end against published SD-2668 + SD-2670 APIs:
- TipTap → SuperDoc → TipTap toggle leaves no leaked state.
- `selection.current()` returns multi-segment TextTarget for cross-block
selections.
- `comments.create({ target: TextTarget })` round-trips (3 → 4 items).
- `ranges.scrollIntoView({ target: EntityAddress })` returns
`{ success: true }` on the new comment id.
- Single Vue warn on unmount is a SuperDoc internal cleanup race
(filed as SD-2760), not a regression of the example.
Part of the SD-2667 drop-in assessment umbrella. FRICTION.md captures
the remaining DX gaps; SD-2750 (virtualized non-body entity scroll)
and SD-2760 (Vue unmount warn) tracked as separate follow-ups.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 89fa947b2f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| superdoc.on?.('commentsUpdate', onCommentsUpdate); | ||
| this.unsubscribes.push(() => superdoc.off?.('commentsUpdate', onCommentsUpdate)); |
There was a problem hiding this comment.
Subscribe to SuperDoc's emitted comments event name
This listener is wired to 'commentsUpdate', but the SuperDoc instance emits 'comments-update' (kebab-case) from the comments store (packages/superdoc/src/stores/comments-store.js). Because of the name mismatch, refreshCommentsCache() is never triggered for external updates (for example collaboration updates or other engine-side comment changes), so the sidebar can drift out of sync unless the change was initiated through this adapter's own methods.
Useful? React with 👍 / 👎.
| superdoc.on?.('trackedChangesUpdate', onTrackedChangesUpdate); | ||
| superdoc.on?.('trackChangesLoaded', onTrackedChangesUpdate); |
There was a problem hiding this comment.
Hook tracked-change cache to an event that actually fires
The adapter subscribes to 'trackedChangesUpdate' and 'trackChangesLoaded', but neither event is emitted anywhere in this repo (and trackChangesLoaded appears only in this file). As a result, trackedChangesCache will not refresh when tracked changes are updated outside acceptChange/rejectChange, so the activity feed can become stale during normal editing/collaboration flows.
Useful? React with 👍 / 👎.
A working reference app that drives the same custom React UI (toolbar + activity sidebar) against TipTap and SuperDoc through a shared
EditorAdaptercontract. Toggling the editor in the header swaps the adapter; the rest of the UI stays put.Why this exists. Selling SuperDoc as a drop-in for TipTap / CKEditor / tinyMCE only works if a consumer with their own custom UI can swap the engine cleanly. This app proves they can — and surfaces the DX gaps that still hurt while doing it. Every workaround the SuperDoc adapter takes is annotated
FRICTION:orESCAPE HATCH:and rolled up in FRICTION.md.Trade-offs:
addCommentreturnsnullon engine failure rather than fabricating a placeholder. A harness should surface failures, not hide them.telemetry: { enabled: false }) so consumers copying this config don't ship to ingest.superdoc.dev by default.DocRange { from, to }as an opaque token in a couple of places. Removing that PM-shaped seam belongs in a follow-up (SD-2671b) so this PR can ship a working harness today.Verified end-to-end against the published SD-2668 + SD-2670 APIs (no workspace shims):
selection.current()returns multi-segmentTextTargetfor cross-block selections.comments.create({ target: TextTarget })round-trips: list 3 → 4, receiptsuccess: true, new id reachable viacomments.list().ranges.scrollIntoView({ target: EntityAddress })returns{ success: true }on the new comment id.Known noise filed separately:
{ success: false }when the page is unmounted.[Vue warn]: Cannot unmount an app that is not mountedon every React unmount. Cleanup race in the wrapper, not a regression of this example.Part of the SD-2667 drop-in assessment umbrella. Sample DOCX in
public/sample-review.docxis a two-page Word memo with 3 comments + 3 tracked insertions, two authors, exercises multi-page scroll.To run: