Skip to content

fix(rich-markdown-editor): defer non-collab settle/stream mutations off the render phase (flushSync) - #6073

Merged
waleedlatif1 merged 4 commits into
realtime-roomsfrom
fix-noncollab-flushsync
Jul 30, 2026
Merged

fix(rich-markdown-editor): defer non-collab settle/stream mutations off the render phase (flushSync)#6073
waleedlatif1 merged 4 commits into
realtime-roomsfrom
fix-noncollab-flushsync

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #6070. Fixes the second flushSync source 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 / focus directly in the effect body. setContent mounts the custom node views synchronously through @tiptap/react's flushSync (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

  • Small runOffRender helper defers the effect-body view mutations to a queueMicrotask (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 exact order.
  • The streaming rAF tick is untouched — it already runs off-render (inside requestAnimationFrame), so it keeps writing content directly with no added latency.
  • queueMicrotask is TipTap's own documented remedy for this warning (and what they use internally — PR #3188).

Type of Change

  • Bug fix

Testing

  • 497 rich-markdown-editor tests pass (incl. stream-settle-selection); tsc + lint + api-validation + boundary + prune green.
  • Needs a live check: stream an agent into a non-collab markdown file and confirm it renders smoothly (this path is client rendering timing that can't be verified headlessly).

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…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.
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 30, 2026 12:53am

Request Review

@cursor

cursor Bot commented Jul 30, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Client-only TipTap/React timing fix on the non-collab agent-streaming editor path; no auth, persistence, or API changes, with behavior guarded by existing tests including stream-settle selection.

Overview
Fixes React "flushSync was called from inside a lifecycle method" on the non-collaborative agent streaming/settle path in rich-markdown-editor.tsx (follow-up to collab editability deferral in #6070).

setContent, setEditable, setTextSelection, and focus no longer run synchronously in the streaming/settle effect. They go through a runOffRender helper that **queueMicrotask**s work after commit, with a run-sequence token so a superseded effect pass cannot apply stale editor updates. The rAF streaming tick is unchanged and still updates content directly.

On settle, one ordered microtask still does sync body → collapse selection → re-apply editability → optional focus. pendingCollapseRef records when a settle owed a selection collapse; if that microtask is dropped because a newer run superseded it, the steady-sync path still collapses selection so post-stream select-all cannot leave rich-leaf-in-selection decorations stuck on dividers/images.

Reviewed by Cursor Bugbot for commit a40a0e0. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Defers non-collab rich-markdown settle/stream editor mutations off the render phase and invalidates superseded microtasks.

  • Adds runOffRender (queueMicrotask) so setContent / setEditable / selection / focus are not called directly inside the reconcile effect (avoids TipTap/@tiptap/react flushSync during render).
  • Tags each effect run with settleRunSeqRef so deferred work runs only for the latest run and a live editor.
  • Tracks pendingCollapseRef so a selection collapse owed by a dropped settle still runs on a later settle or steady-sync path.
  • Leaves the streaming requestAnimationFrame content path writing directly (already off-render).

Confidence Score: 5/5

This 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.

Important Files Changed

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

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

1 issue from previous review remains unresolved.

Fix All in Cursor

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.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@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.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.

@waleedlatif1
waleedlatif1 merged commit caa0a73 into realtime-rooms Jul 30, 2026
5 checks passed
@waleedlatif1
waleedlatif1 deleted the fix-noncollab-flushsync branch July 30, 2026 01:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant