fix(rich-markdown-editor): defer non-collab settle/stream mutations off the render phase (flushSync) - #6073
Conversation
…ff the render phase (flushSync) The non-collaborative streaming/settle effect called editor.setContent / setEditable / setTextSelection / focus directly in the effect body. setContent mounts the custom node views synchronously through the @tiptap/react flushSync path (tiptap#3764), so when this effect runs while React is mid-render it throws "flushSync was called from inside a lifecycle method." This is the second flushSync source (the collab editability effect was the first, fixed separately); it fires on the agent-streaming-into-a-non-collab-editor surface. Defer the effect-body view mutations to a microtask via a small runOffRender helper (runs right after the current commit, before paint; no-ops if the editor was torn down). The settle block is deferred as ONE microtask so setContent -> collapse selection -> setEditable -> focus keep their order. The streaming rAF tick is left untouched — it already runs off-render, so it keeps writing content directly. queueMicrotask is TipTap's own documented remedy for this warning. 497 rich-markdown-editor tests (incl. stream-settle-selection) pass; tsc + lint + api-validation + boundary + prune green. Needs a live check: stream an agent into a non-collab markdown file and confirm it still renders smoothly.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryLow Risk Overview
On settle, one ordered microtask still does sync body → collapse selection → re-apply editability → optional focus. Reviewed by Cursor Bugbot for commit a40a0e0. Configure here. |
Greptile SummaryDefers non-collab rich-markdown settle/stream editor mutations off the render phase and invalidates superseded microtasks.
Confidence Score: 5/5This PR appears safe to merge; the prior microtask-staleness issue is addressed and no remaining blocking failure is evident. The settle/stream path defers TipTap view mutations off render, drops superseded microtasks with a run sequence token, and carries selection-collapse debt so a dropped settle cannot leave a stale select-all decoration without a later collapse. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-editor.tsx | Non-collab reconcile effect defers view mutations via run-token-guarded microtasks and preserves settle selection-collapse across superseded runs; streaming rAF path unchanged. |
Reviews (4): Last reviewed commit: "fix(rich-markdown-editor): never drop th..." | Re-trigger Greptile
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
1 issue from previous review remains unresolved.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit e01aeb6. Configure here.
…ia a run token runOffRender previously only guarded editor.isDestroyed, so if React ran the next reconcile pass (a newer stream or settle) before a queued microtask flushed, the stale microtask could still apply setContent/setEditable/setTextSelection over the newer state. Tag each effect run with an incrementing token; a deferred mutation applies only when its run is still the latest (and the editor is alive). A run token fits this effect's several early-return exits better than a per-exit cleanup flag. Addresses Greptile/Cursor review.
|
@cursor review |
…nder a superseded run The run token drops a superseded settle's microtask, but the settle had already flipped its state flags synchronously — so a pre-empting steady-sync run took the non-settle path and never collapsed the selection, leaving a post-stream select-all painting the leaf-in-selection decoration. Track the collapse as a debt (pendingCollapseRef): whichever deferred run ultimately applies — settle or the steady-sync path — clears it, so the collapse runs exactly once on the latest content. Addresses the Cursor review finding.
|
@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 a40a0e0. Configure here.

Summary
Follow-up to #6070. Fixes the second
flushSyncsource on the rich-markdown editor — the non-collaborative streaming/settle path (#6070 fixed the collab editability effect).The non-collab settle/stream effect called
editor.setContent/setEditable/setTextSelection/focusdirectly in the effect body.setContentmounts the custom node views synchronously through@tiptap/react'sflushSync(tiptap#3764), so when the effect runs while React is mid-render it throws "flushSync was called from inside a lifecycle method." Surfaces on the agent-streaming-into-a-non-collab-editor path.Fix
runOffRenderhelper defers the effect-body view mutations to aqueueMicrotask(runs right after the current commit, before paint; no-ops if the editor was torn down).setContent → collapse selection → setEditable → focuskeep their exact order.requestAnimationFrame), so it keeps writing content directly with no added latency.queueMicrotaskis TipTap's own documented remedy for this warning (and what they use internally — PR #3188).Type of Change
Testing
stream-settle-selection); tsc + lint + api-validation + boundary + prune green.Checklist