fix(collaboration): sync numbering, styles, and headers in real-time (SD-2062, SD-2040)#2314
fix(collaboration): sync numbering, styles, and headers in real-time (SD-2062, SD-2040)#2314
Conversation
…(SD-2062, SD-2040) Add real-time sync for converter metadata that previously only reached other clients via the 30-second debounced DOCX export. - Numbering definitions (list indent/spacing) - Style definitions (document defaults) - Header/footer content and section-type mappings (headerIds/footerIds)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0e3bf7cd2c
ℹ️ 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".
| const editorEventHandlers = { | ||
| 'list-definitions-change': () => pushConverterMetadata(this.editor, 'numbering'), | ||
| stylesDefaultsChanged: () => pushConverterMetadata(this.editor, 'styles'), | ||
| }; |
There was a problem hiding this comment.
Add a push trigger for headerFooterIds updates
The new converter metadata wiring never republishes headerFooterIds after initialization: only 'list-definitions-change' and 'stylesDefaultsChanged' call pushConverterMetadata, so converterMeta['headerFooterIds'] can go stale while the editor keeps mutating header/footer IDs (for example updateGlobalTitlePageFlag in sections-adapter.ts updates converter.headerIds.titlePg/footerIds.titlePg). This stale key is then replayed for joiners in the same module, which can override fresher header/footer mapping and produce incorrect first/default header rendering in collaboration sessions.
Useful? React with 👍 / 👎.
Demo
CleanShot.2026-03-05.at.19.24.40.mp4
Problem
In collaboration mode, three types of document metadata only sync via the 30-second debounced DOCX export. This means other clients see stale or missing data for up to 30 seconds:
Numbering definitions (SD-2062) — list indent/spacing comes from numbering level definitions stored in
editor.converter.numbering. Without them, the receiving client renders lists with wrong marker spacing (34.6px vs 10.6px).Style definitions (SD-2040) — document default styles live in
editor.converter.translatedLinkedStyles. Thestyles.applyAPI was blocked entirely during collaboration because there was no sync mechanism.Header/footer content (SD-2040) — header content pushes to the
headerFooterJsonY.js map on edit, butheaderIds(the mapping that tells the layout pipeline which header to render for each section type) was never synced. Joining clients had header content but no way to resolve it.How collaboration sync works now
SuperDoc uses two real-time sync channels:
XmlFragment('supereditor')via y-prosemirrorMap('media')Map('headerFooterJson')Map('converterMeta')list-definitions-change,stylesDefaultsChangedeventsEverything else (full DOCX XML) syncs via the debounced 30s export to
Map('meta').docx.What changed
1. Unified converter metadata sync (
converterMetaY.js map)One map, one observer, one anti-ping-pong flag for all converter metadata. Adding a new type is: add a key to
CONVERTER_META_KEYS, add push/apply logic, add a trigger.Files:
collaboration-helpers.js,collaboration.js2. Header/footer atomicity fix
headerIds/footerIdsare now bundled into everyheaderFooterJsonentry alongside the content. Before, they lived in a separate Y.js map (converterMeta) and could arrive in a different network message than the content. The receiver would have header content butheaderIds.default === undefined, so the layout pipeline couldn't resolve which header to render.Files:
collaboration-helpers.js(pushHeaderFooterToYjs, applyRemoteHeaderFooterChanges)3. Initial state sync on join
Y.js map observers only fire for future changes. Data that arrived via Y.js sync before the observer was attached is missed. Fixed by reading existing map entries after attaching each observer.
Also push all initial metadata during
initializeMetaMap(first client) so joining clients get headers, numbering, and styles immediately.Files:
collaboration.js4.
numberingPlugincollaboration hardeningThe numbering plugin's
appendTransactionclearslistRenderingwhen definitions are missing. In collaboration, definitions may arrive slightly after the document change (different Y.js types). The plugin now preserves syncedlistRenderingwhenydocis present and the value already exists.Files:
numberingPlugin.js5. Removed
styles.applycollaboration gatestyles.applywas blocked during collaboration with "Stylesheet mutations cannot be synced via Yjs." Now that styles sync via theconverterMetamap, the gate is removed.Files:
styles-adapter.ts,capabilities-adapter.tsTest plan
?collab=1&room=<name>, create a numbered list on tab A, verify correct spacing on tab B