fix(collab): prevent stale view when remote Y.js changes bypass sdBlockRev increment#2099
Conversation
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
There was a problem hiding this comment.
Pull request overview
This PR fixes a subtle collaboration bug where remote Y.js changes could result in stale cached content being displayed. The root cause stems from the FlowBlockCache's O(1) fast path relying on sdBlockRev for cache invalidation, while the blockNodePlugin intentionally skips incrementing sdBlockRev for Y.js-origin transactions to prevent infinite sync loops. The fix adds a setHasExternalChanges() flag to the cache that forces JSON comparison when external changes occur, ensuring correctness while preserving O(1) performance for local edits.
Changes:
- Added
setHasExternalChanges()mechanism to FlowBlockCache with dual-strategy cache validation (fast revision check for local edits, JSON comparison when Y.js changes detected) - Wired PresentationEditor to detect Y.js-origin transactions and signal the cache accordingly
- Fixed file upload race condition in collaboration mode by using
replaceFile()instead of destroying and recreating the editor
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
packages/layout-engine/pm-adapter/src/cache.ts |
Adds #hasExternalChanges flag and dual-strategy validation logic that falls through to JSON comparison when Y.js changes bypass rev increment |
packages/layout-engine/pm-adapter/src/cache.d.ts |
Updates type declaration with new setHasExternalChanges() method signature |
packages/layout-engine/pm-adapter/src/cache.test.ts |
Adds 10 comprehensive tests including 4 specifically for the externalChanges feature and a regression test demonstrating the original bug |
packages/super-editor/src/core/presentation-editor/PresentationEditor.ts |
Detects Y.js-origin transactions via ySyncPluginKey metadata and signals cache to use JSON comparison for that render cycle |
packages/superdoc/src/dev/components/SuperdocDev.vue |
Switches to replaceFile() in collab mode to avoid Y.js race condition where empty room state could overwrite new document content |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ckRev increment FlowBlockCache uses sdBlockRev for O(1) cache invalidation, but the collaboration plugin skips incrementing sdBlockRev for Y.js-origin transactions to avoid an infinite sync loop. This created a window where remote content changes arrive but the cache returns stale blocks because the rev appears unchanged. Fix: add setHasExternalChanges() to FlowBlockCache. When a Y.js-origin transaction is detected in PresentationEditor, the cache falls back to JSON comparison for that cycle instead of trusting the matching rev. The flag auto-clears on commit() so normal editing remains O(1). Also fix file upload in collab mode: use replaceFile() on the existing editor rather than destroying and recreating SuperDoc, which caused a race condition where the empty Y.js room state overwrote the new document content.
0bc463f to
aa9cb89
Compare
# [1.15.0](v1.14.0...v1.15.0) (2026-02-20) ### Bug Fixes * **ai-actions:** preserve html/markdown insertion and prevent repeated formatted replacement ([#2117](#2117)) ([9f685e9](9f685e9)) * **ai:** support headless mode in EditorAdapter.applyPatch ([#1859](#1859)) ([cf9275d](cf9275d)) * **collaboration:** memory leaks, Vue stack overflow, and Liveblocks stability (SD-1924) ([#2030](#2030)) ([a6827fd](a6827fd)), closes [#prepareDocumentForExport](https://github.com/superdoc-dev/superdoc/issues/prepareDocumentForExport) * **collab:** prevent stale view when remote Y.js changes bypass sdBlockRev increment ([#2099](#2099)) ([0895a93](0895a93)) * **converter:** handle null list lvlText and always clear numbering cache ([#2113](#2113)) ([336958c](336958c)) * **document-api:** remove search match cap and validate moveComment bounds ([6d3de67](6d3de67)) * export docx blobs with docx mime type ([#1849](#1849)) ([1bc466d](1bc466d)) * **export:** prevent DOCX corruption from entity encoding and orphaned delInstrText (SD-1943) ([#2102](#2102)) ([56e917f](56e917f)), closes [#replaceSpecialCharacters](https://github.com/superdoc-dev/superdoc/issues/replaceSpecialCharacters) [#1988](#1988) * **layout-bridge:** correct cell selection for tables with rowspan ([#1839](#1839)) ([0b782be](0b782be)) * **layout,converter:** text box rendering and page-relative anchor positioning (SD-1331, SD-1838) ([#2034](#2034)) ([3947f39](3947f39)) * **layout:** route list text-start calculations through resolveListTextStartPx ([02b14b8](02b14b8)) * **painter-dom:** use absolute page Y for page-relative anchors in header/footer decorations ([0b9bc72](0b9bc72)) * preserve selection highlight when opening toolbar dropdowns ([#2097](#2097)) ([a33568e](a33568e)) * structured content renders correct on hover and select ([#1843](#1843)) ([dab3f04](dab3f04)) * **super-editor:** add unsupported-content reporting across HTML/Markdown import paths ([#2115](#2115)) ([84880b7](84880b7)) * **super-editor:** handle partial comment file-sets and clean up stale parts on export ([#2123](#2123)) ([f63ae0a](f63ae0a)) * **super-editor:** restore <hr> contentBlock parsing and harden VML HR export fallback ([#2118](#2118)) ([da51b1f](da51b1f)) * table headers are incorrectly imported from html ([#2112](#2112)) ([e8d1480](e8d1480)) * table resizing regression ([#2091](#2091)) ([20ed24e](20ed24e)) * table resizing regression ([#2091](#2091)) ([9a07f1c](9a07f1c)) * **tables:** align tableHeader attrs with tableCell to fix oversized DOCX export widths ([#2114](#2114)) ([38f0430](38f0430)) * **tables:** fix autofit column scaling, cell width overflow, and page break splitting ([#1987](#1987)) ([61a3f6f](61a3f6f)) * **tables:** prevent tblInd double-shrink when using tblGrid widths (SD-1494) ([8750ece](8750ece)) * track changes comment text for formatting changes ([#2013](#2013)) ([b2a43ff](b2a43ff)) * wire DocumentApi to Editor.doc with lifecycle-safe caching ([57326ea](57326ea)) ### Features * cropped images ([#1940](#1940)) ([3767a49](3767a49)) * extend document-api with format, examples, create.heading ([#2092](#2092)) ([fdf8c7c](fdf8c7c)) * **lists:** support hidden list indicators via w:vanish ([#2069](#2069)) ([#2080](#2080)) ([0bed0fd](0bed0fd)) * the document API limited alpha ([#2087](#2087)) ([091c24c](091c24c))
|
🎉 This PR is included in superdoc v1.15.0 The release is available on GitHub release |
Summary
FlowBlockCacheusessdBlockRevfor O(1) invalidation, but the collab plugin skips incrementingsdBlockRevfor Y.js-origin transactions (to prevent an infinite sync loop). This created a window where remote content changes arrive but the cache returns stale blocks because the rev appears unchanged — only reproducible in production with real network latency.setHasExternalChanges()toFlowBlockCache. WhenPresentationEditordetects a Y.js-origin transaction, it flags the cache to fall back to JSON comparison for that render cycle instead of trusting the matching rev. The flag auto-clears oncommit()so normal local editing stays O(1).replaceFile(), causing a race condition where the empty Y.js room state could overwrite the new document content.Changes
pm-adapter/src/cache.tssetHasExternalChanges(flag)— forces JSON fallback when Y.js changes bypass rev incrementpm-adapter/src/cache.d.tspm-adapter/src/cache.test.tsPresentationEditor.tsySyncPluginKeymeta and signal cacheSuperdocDev.vuereplaceFile()on existing editor in collab mode instead of full reinitTest plan
Closes SD-1852