What happened?
Collaborative tracked-change suggestions render the redline but not the comment cards
Summary
In collaborative mode (Yjs + Hocuspocus), tracked-change "suggestion" cards (the side cards with author/accept/reject) fail to render in several situations even though the redline marks render
correctly:
- After a page reload, the redline is present but the comment cards are gone — on all clients.
- When a user makes a tracked change, the card shows up on the remote peer but not on the author who made the edit.
- When the shared document is populated from the Y.Doc rather than by a live local edit (page reload, late join, or programmatically seeding the shared doc with
seedEditorStateToYDoc), the redline shows
but the cards are missing and the change cannot be accepted/rejected.
The common thread: the tracked-change marks live in the editor fragment (ydoc.getXmlFragment("supereditor")) and are synced/persisted correctly, but the comment cards are only materialized from live
tracked-changes-changed transactions and are never rebuilt from marks that already exist in a loaded document.
Environment
@harbour-enterprises/superdoc 1.38.0
- SuperDoc configured with:
new SuperDoc({
comments: { visible: true },
trackChanges: { visible: true },
modules: { collaboration: { provider, ydoc } }, // Hocuspocus provider + Y.Doc
// ...
})
- Two browser clients connected to the same Hocuspocus room.
Steps to reproduce
Repro A — reload drops the cards
- Open a document collaboratively with at least one tracked change (suggestion) present.
- Confirm the redline and the side comment card render.
- Reload the page.
- ❌ The redline renders, but the comment card is gone.
Repro B — author doesn't see their own suggestion
- Two clients (A and B) in the same room, suggesting mode.
- A makes a tracked change by editing the body.
- ❌ B sees the comment card; A (the author) never does.
- Reload either client → card gone on both (see Repro A).
Expected behavior
Tracked-change comment cards should render for every tracked change present in the document, regardless of whether the change arrived via a live transaction, a remote collaboration update, a page reload, or
a programmatic seed of the shared Y.Doc — and the author should see their own change's card.
Root cause
Two issues in the comments/collaboration layer (symbol names as they appear in the distributed build):
1. Cards are only built from live transactions, never rebuilt from a loaded document.
syncTrackedChangeComments({ superdoc, editor }) (which enumerates getTrackChanges(editor.state) and calls createCommentForTrackChanges(...)) is the routine that builds the cards. In collaborative mode
it is only invoked from the live editor.on("tracked-changes-changed", ...) handler. When a document is loaded from the Y.Doc (reload / late join / seedEditorStateToYDoc), the tracked-change marks are
applied as the editor's initial state, not as a tracked-changes-changed event, so syncTrackedChangeComments never runs and no cards are created. The non-collaborative import path does bootstrap
them (processLoadedDocxComments → bootstrapImportedTrackedChangeComments), but that path is gated by editor.options.shouldLoadComments || replacedFile and doesn't run for content that arrives through
the synced Y.Doc fragment.
2. The author's own edits are skipped.
The tracked-changes-changed handler short-circuits on the author's own body edits:
editor.on?.("tracked-changes-changed", ({ editor: sourceEditor, source }) => {
if (source === "body-edit") return; // <-- author's own edit never builds a card
if (!shouldRenderCommentsInViewing.value) { /* ... */ return; }
syncTrackedChangeComments({ superdoc: proxy.$superdoc, editor: sourceEditor ?? editor });
});
The remote peer receives the same change as a non-body-edit collaboration update and does build the card, which is why it shows on the peer but not on the author.
Suggested fix
Two changes, both reusing the existing syncTrackedChangeComments so no new comment logic is introduced. Deriving cards locally (broadcastChanges: false) keeps them a pure function of the synced marks
and avoids writing duplicates into the shared comments array.
1. Rebuild tracked-change cards when a collaborative editor is set up / synced, so loaded/seeded documents get their cards:
// after the collaborative editor is ready
if (superdoc?.isCollaborative) {
const rebuild = () => {
try {
syncTrackedChangeComments({ superdoc, editor, broadcastChanges: false });
} catch {}
};
// run once content is applied; a small retry covers the sync race
setTimeout(rebuild, 0);
setTimeout(rebuild, 300);
}
2. Don't drop the author's own body-edit changes — schedule a debounced local rebuild instead of returning:
editor.on?.("tracked-changes-changed", ({ editor: sourceEditor, source }) => {
if (source === "body-edit") {
if (shouldRenderCommentsInViewing.value) {
clearTimeout(bodyEditTimer);
bodyEditTimer = setTimeout(
() => syncTrackedChangeComments({ superdoc, editor: sourceEditor ?? editor, broadcastChanges: false }),
400
);
}
return;
}
// ...existing path unchanged...
});
We applied both changes as a local patch against 1.38.0 and they resolve all three symptoms: cards now persist across reload, the author sees their own suggestion, and seeded/generated content renders cards
that can be accepted/rejected.
Possible alternative
It would also help to expose a public API to rebuild tracked-change comments for the current document (e.g. a superdoc.refreshTrackedChangeComments() or having the collaboration sync path run it on
provider "synced"), so integrators don't have to patch the bundle for loaded/seeded documents.
Steps to reproduce
No response
SuperDoc version
No response
Browser
None
Additional context
No response
What happened?
Collaborative tracked-change suggestions render the redline but not the comment cards
Summary
In collaborative mode (Yjs + Hocuspocus), tracked-change "suggestion" cards (the side cards with author/accept/reject) fail to render in several situations even though the redline marks render
correctly:
seedEditorStateToYDoc), the redline showsbut the cards are missing and the change cannot be accepted/rejected.
The common thread: the tracked-change marks live in the editor fragment (
ydoc.getXmlFragment("supereditor")) and are synced/persisted correctly, but the comment cards are only materialized from livetracked-changes-changedtransactions and are never rebuilt from marks that already exist in a loaded document.Environment
@harbour-enterprises/superdoc1.38.0Steps to reproduce
Repro A — reload drops the cards
Repro B — author doesn't see their own suggestion
Expected behavior
Tracked-change comment cards should render for every tracked change present in the document, regardless of whether the change arrived via a live transaction, a remote collaboration update, a page reload, or
a programmatic seed of the shared Y.Doc — and the author should see their own change's card.
Root cause
Two issues in the comments/collaboration layer (symbol names as they appear in the distributed build):
1. Cards are only built from live transactions, never rebuilt from a loaded document.
syncTrackedChangeComments({ superdoc, editor })(which enumeratesgetTrackChanges(editor.state)and callscreateCommentForTrackChanges(...)) is the routine that builds the cards. In collaborative modeit is only invoked from the live
editor.on("tracked-changes-changed", ...)handler. When a document is loaded from the Y.Doc (reload / late join /seedEditorStateToYDoc), the tracked-change marks areapplied as the editor's initial state, not as a
tracked-changes-changedevent, sosyncTrackedChangeCommentsnever runs and no cards are created. The non-collaborative import path does bootstrapthem (
processLoadedDocxComments→bootstrapImportedTrackedChangeComments), but that path is gated byeditor.options.shouldLoadComments || replacedFileand doesn't run for content that arrives throughthe synced Y.Doc fragment.
2. The author's own edits are skipped.
The
tracked-changes-changedhandler short-circuits on the author's own body edits:The remote peer receives the same change as a non-
body-editcollaboration update and does build the card, which is why it shows on the peer but not on the author.Suggested fix
Two changes, both reusing the existing
syncTrackedChangeCommentsso no new comment logic is introduced. Deriving cards locally (broadcastChanges: false) keeps them a pure function of the synced marksand avoids writing duplicates into the shared
commentsarray.1. Rebuild tracked-change cards when a collaborative editor is set up / synced, so loaded/seeded documents get their cards:
2. Don't drop the author's own
body-editchanges — schedule a debounced local rebuild instead of returning:We applied both changes as a local patch against 1.38.0 and they resolve all three symptoms: cards now persist across reload, the author sees their own suggestion, and seeded/generated content renders cards
that can be accepted/rejected.
Possible alternative
It would also help to expose a public API to rebuild tracked-change comments for the current document (e.g. a
superdoc.refreshTrackedChangeComments()or having the collaboration sync path run it onprovider"synced"), so integrators don't have to patch the bundle for loaded/seeded documents.Steps to reproduce
No response
SuperDoc version
No response
Browser
None
Additional context
No response