fix(collab-doc): stream every external file write into open editors, not just edit_content - #6070
Conversation
…not just edit_content A copilot/mothership edit to an open markdown file did not appear live in another user's editor: the live-doc merge bridge (mergeEditIntoLiveFileDoc) was wired into the edit_content tool ONLY. Every other server-side write — the file tool (/api/tools/file/manage), function_execute (/api/function/execute via writeWorkspaceFileByPath), create_file overwrite, and the PUT /content route — went straight to updateWorkspaceFileContent and skipped the merge, so the durable file changed but the open editor never updated. Confirmed from live logs (the mothership 'Prepend sentence' ran read + file + function_execute — zero apply-edit calls) and Redis (the prepended text was absent from the doc stream). Centralize the merge at the one chokepoint every external writer shares: - updateWorkspaceFileContent gains an opt-out "syncLiveDoc" (default on) and, after the durable write, merges markdown writes into any open collaborative doc (best-effort; no-op when nobody has it open). Any current OR future writer is covered automatically. - persist.ts opts out (syncLiveDoc:false) — it IS the doc→markdown projection, so merging it back would be a persist→merge→persist self-loop. - create_file opts its empty shell out (real content arrives via a later write) so an open editor never flickers to empty on overwrite; threaded through writeWorkspaceFileByPath. - edit_content drops its now-redundant explicit merge call (the chokepoint handles it). - binary writers (image/video/audio/ffmpeg/download) are naturally excluded — the merge is gated to markdown, the only format the collaborative editor renders. Also bump the api-validation route baseline 994→996 to match the true route count already on this branch (pre-existing ratchet drift from an earlier merge; NOT added by this PR).
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview
Opt-outs: relay Editor: collaborative Adds unit tests for merge vs opt-out vs non-markdown; bumps API route validation baseline 994 → 996 (pre-existing drift). Reviewed by Cursor Bugbot for commit 299842e. Configure here. |
Greptile SummaryCentralizes live collaborative-doc merge on every durable workspace file write so open markdown editors receive external updates.
Confidence Score: 5/5This PR appears safe to merge; no blocking failures remain from prior or current review. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/lib/uploads/contexts/workspace/workspace-file-manager.ts | Adds default-on markdown live-doc merge after successful durable update; opt-out via syncLiveDoc false. |
| apps/sim/lib/collab-doc/persist.ts | Opts persist out of live merge to avoid persist→merge→persist loops. |
| apps/sim/lib/copilot/tools/server/files/edit-content.ts | Removes redundant per-tool merge; relies on shared chokepoint. |
| apps/sim/lib/copilot/tools/server/files/create-file.ts | Opts empty-shell create out of merge to avoid blanking open editors. |
| apps/sim/lib/copilot/vfs/resource-writer.ts | Forwards optional syncLiveDoc on overwrite path. |
| apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-editor.tsx | Defers setEditable to a cancellable microtask to avoid flushSync during React render. |
| apps/sim/lib/uploads/contexts/workspace/workspace-file-storage-accounting.test.ts | Covers default merge, syncLiveDoc false, and non-markdown skip. |
| scripts/check-api-validation-contracts.ts | Ratchets route baseline to 996 for pre-existing drift on the base branch. |
Sequence Diagram
sequenceDiagram
participant Writer as External writer<br/>(file tool / function_execute / PUT content / edit_content)
participant UWC as updateWorkspaceFileContent
participant Storage as Durable storage + DB
participant Merge as mergeEditIntoLiveFileDoc
participant Editor as Open collab markdown editor
Writer->>UWC: content Buffer (+ optional syncLiveDoc)
UWC->>Storage: upload + finalize metadata
alt "syncLiveDoc !== false and markdown"
UWC->>Merge: fileId + utf-8 markdown
Merge->>Editor: CRDT merge (best-effort)
else syncLiveDoc false or non-markdown
Note over UWC: skip live merge
end
Reviews (2): Last reviewed commit: "fix(collab-doc): defer setEditable out o..." | Re-trigger Greptile
… warning) The collab editability-reapply effect called editor.setEditable synchronously. In collab mode isEditable flips from readiness (synced + seeded), which is driven by a Yjs config.observe firing synchronously inside Y.applyUpdate — so the effect can run while React is mid-render. TipTap's React binding commits setEditable's transaction with flushSync, which throws "flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering." Defer the setEditable to a microtask (runs right after the current commit, before paint), guarding against a destroyed editor or a stale value before it fires. Only the collab path (this effect) hit the warning; the streaming/settle effect's setEditable calls run on the non-collab path where isEditable isn't driven by a mid-render Yjs observer.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 299842e. Configure here.
Summary
Fixes a real bug found via live testing: a copilot/mothership edit to a markdown file that another user has open did not stream into their editor.
Root-caused from the running server logs + local Redis:
read+file+function_execute— zeroapply-editcalls.The live-doc merge bridge (
mergeEditIntoLiveFileDoc) was wired into theedit_contenttool only. Every other server-side write — thefiletool (/api/tools/file/manage),function_execute(/api/function/executeviawriteWorkspaceFileByPath),create_fileoverwrite, and the PUT/contentroute — went straight toupdateWorkspaceFileContentand skipped the merge. The durable file changed (list refreshed) but the open editor never updated.Fix — centralize at the one chokepoint every external writer shares
updateWorkspaceFileContentgains an opt-out{ syncLiveDoc }(default on); after the durable write it merges markdown writes into any open collaborative doc (best-effort, no-op when nobody has it open). Any current or future writer is covered automatically.persistopts out — it IS the doc→markdown projection, so merging back would be a persist→merge→persist self-loop.create_fileopts its empty shell out (real content arrives via a later write) so an open editor never flickers to empty on overwrite.edit_contentdrops its now-redundant explicit merge call.Note
Bumps the api-validation route baseline
994 → 996to match the true route count already onrealtime-rooms(pre-existing ratchet drift from an earlier merge — not routes added by this PR; verified against a clean tree).Type of Change
Testing
syncLiveDoc:falseand non-markdown writes do not.Checklist