Skip to content

feat(examples): add drop-in-assessment app — TipTap → SuperDoc migration harness (SD-2671)#2963

Closed
caio-pizzol wants to merge 1 commit into
mainfrom
caio/sd-2671-drop-in-assessment-example-app-adapter-cleanups-frictionmd
Closed

feat(examples): add drop-in-assessment app — TipTap → SuperDoc migration harness (SD-2671)#2963
caio-pizzol wants to merge 1 commit into
mainfrom
caio/sd-2671-drop-in-assessment-example-app-adapter-cleanups-frictionmd

Conversation

@caio-pizzol
Copy link
Copy Markdown
Contributor

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.

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: or ESCAPE HATCH: and rolled up in FRICTION.md.

Trade-offs:

  • 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, not something to paper over.
  • addComment returns null on engine failure rather than fabricating a placeholder. 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.
  • Adapter contract still uses 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):

  • TipTap → SuperDoc → TipTap toggle leaves no leaked state. Activity counts go 3 → 6 → 3 → 6.
  • selection.current() returns multi-segment TextTarget for cross-block selections.
  • comments.create({ target: TextTarget }) round-trips: list 3 → 4, receipt success: true, new id reachable via comments.list().
  • ranges.scrollIntoView({ target: EntityAddress }) returns { success: true } on the new comment id.
  • DOCX import attributes the right author (Caio Pizzol on imports, not the AUTHORS fallback) and the real timestamps (08:16 PM not "now").

Known noise filed separately:

  • SD-2750: virtualized non-body tracked-change navigation returns { success: false } when the page is unmounted.
  • SD-2760: SuperDoc emits [Vue warn]: Cannot unmount an app that is not mounted on 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.docx is a two-page Word memo with 3 comments + 3 tracked insertions, two authors, exercises multi-page scroll.

To run:

pnpm install
pnpm --filter dropin-assessment dev
# http://localhost:5188

…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.
@linear
Copy link
Copy Markdown

linear Bot commented Apr 27, 2026

@caio-pizzol caio-pizzol deleted the caio/sd-2671-drop-in-assessment-example-app-adapter-cleanups-frictionmd branch April 27, 2026 15:14
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +98 to +99
superdoc.on?.('commentsUpdate', onCommentsUpdate);
this.unsubscribes.push(() => superdoc.off?.('commentsUpdate', onCommentsUpdate));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +106 to +107
superdoc.on?.('trackedChangesUpdate', onTrackedChangesUpdate);
superdoc.on?.('trackChangesLoaded', onTrackedChangesUpdate);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants