From 259b54aa39209668f04fb9eca42808ac7b530eda Mon Sep 17 00:00:00 2001 From: Jacob Leksan Date: Wed, 22 Apr 2026 13:29:33 -0400 Subject: [PATCH] perf: defer RichTextInput formatting toolbar invalidation to rAF Defer toolbar Svelte invalidation to requestAnimationFrame when rich text and the formatting toolbar are enabled, so reconciliation does not run in the same turn as ProseMirror DOM updates. Cancel a pending frame when the toolbar is disabled or on destroy, avoiding stale callbacks and unnecessary invalidation after toggling settings. Made-with: Cursor --- .../components/common/RichTextInput.svelte | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/lib/components/common/RichTextInput.svelte b/src/lib/components/common/RichTextInput.svelte index 8c4006d280..9787920651 100644 --- a/src/lib/components/common/RichTextInput.svelte +++ b/src/lib/components/common/RichTextInput.svelte @@ -302,6 +302,25 @@ let bubbleMenuElement: Element | null = null; let element: Element | null = null; + /** Defer FormattingButtons refresh to after layout so we don't interleave Svelte DOM work with ProseMirror's updateStateInner (reduces forced reflow). */ + let toolbarInvalidateRaf = 0; + + const scheduleToolbarInvalidation = () => { + if (!richText || !showFormattingToolbar) { + if (toolbarInvalidateRaf) { + cancelAnimationFrame(toolbarInvalidateRaf); + toolbarInvalidateRaf = 0; + } + return; + } + if (toolbarInvalidateRaf) cancelAnimationFrame(toolbarInvalidateRaf); + toolbarInvalidateRaf = requestAnimationFrame(() => { + toolbarInvalidateRaf = 0; + if (!editor || editor.isDestroyed) return; + editor = editor; + }); + }; + const options = { throwOnError: false }; @@ -855,8 +874,7 @@ content: collaboration ? undefined : content, autofocus: messageInput ? true : false, onTransaction: () => { - // force re-render so `editor.isActive` works as expected - editor = editor; + scheduleToolbarInvalidation(); if (!editor) return; htmlValue = editor.getHTML(); @@ -1223,6 +1241,11 @@ }); onDestroy(() => { + if (toolbarInvalidateRaf) { + cancelAnimationFrame(toolbarInvalidateRaf); + toolbarInvalidateRaf = 0; + } + if (provider) { provider.destroy(); }