@@ -45,6 +45,12 @@ interface RichMarkdownEditorProps {
4545 saveRef ?: React . MutableRefObject < ( ( ) => Promise < void > ) | null >
4646 streamingContent ?: string
4747 isAgentEditing ?: boolean
48+ /**
49+ * True when the stream delivers complete full-file snapshots (an `append`/`patch` edit built on the
50+ * existing file) rather than a from-scratch rebuild (`create`/`update`). Incremental snapshots are
51+ * applied live; a rebuild is only revealed while it extends what's shown (see the streaming tick).
52+ */
53+ streamIsIncremental ?: boolean
4854 disableStreamingAutoScroll ?: boolean
4955 previewContextKey ?: string
5056}
@@ -60,6 +66,7 @@ export const RichMarkdownEditor = memo(function RichMarkdownEditor({
6066 saveRef,
6167 streamingContent,
6268 isAgentEditing,
69+ streamIsIncremental,
6370 disableStreamingAutoScroll = false ,
6471 previewContextKey,
6572} : RichMarkdownEditorProps ) {
@@ -101,6 +108,7 @@ export const RichMarkdownEditor = memo(function RichMarkdownEditor({
101108 isStreaming = { isStreamInteractionLocked }
102109 canEdit = { canEdit }
103110 autoFocus = { autoFocus }
111+ streamIsIncremental = { streamIsIncremental }
104112 disableStreamingAutoScroll = { disableStreamingAutoScroll }
105113 onChange = { setDraftContent }
106114 onSaveShortcut = { saveImmediately }
@@ -117,6 +125,8 @@ interface LoadedRichMarkdownEditorProps {
117125 isStreaming : boolean
118126 canEdit : boolean
119127 autoFocus ?: boolean
128+ /** See {@link RichMarkdownEditorProps.streamIsIncremental}. */
129+ streamIsIncremental ?: boolean
120130 disableStreamingAutoScroll ?: boolean
121131 onChange : ( markdown : string ) => void
122132 onSaveShortcut : ( ) => Promise < void >
@@ -140,6 +150,7 @@ export function LoadedRichMarkdownEditor({
140150 isStreaming,
141151 canEdit,
142152 autoFocus,
153+ streamIsIncremental,
143154 disableStreamingAutoScroll,
144155 onChange,
145156 onSaveShortcut,
@@ -160,8 +171,9 @@ export function LoadedRichMarkdownEditor({
160171 )
161172 /**
162173 * The body currently shown in the editor: seeded from a settled mount, updated on local edits (via
163- * onUpdate) and on each streamed sync. The streaming tick reveals a chunk only when it extends this,
164- * so an agent rewrite holds the current content instead of collapsing to a partial result.
174+ * onUpdate) and on each streamed sync. Incremental edits (append/patch) stream complete snapshots and
175+ * always apply; a from-scratch rebuild (create/update) only applies while it still extends this, so a
176+ * rewrite holds the current content instead of collapsing to a partial result.
165177 */
166178 const lastSyncedBodyRef = useRef < string | null > (
167179 streamingAtMountRef . current ? null : splitFrontmatter ( content ) . body
@@ -170,6 +182,10 @@ export function LoadedRichMarkdownEditor({
170182 onChangeRef . current = onChange
171183 const onSaveShortcutRef = useRef ( onSaveShortcut )
172184 onSaveShortcutRef . current = onSaveShortcut
185+ // Read in the RAF tick so an already-scheduled tick still sees the latest edit kind (it can change
186+ // between sessions within one turn, e.g. an append followed by a rewrite).
187+ const streamIsIncrementalRef = useRef ( streamIsIncremental )
188+ streamIsIncrementalRef . current = streamIsIncremental
173189 const router = useRouter ( )
174190 const routerRef = useRef ( router )
175191 routerRef . current = router
@@ -300,7 +316,11 @@ export function LoadedRichMarkdownEditor({
300316 }
301317 const shownBody = lastSyncedBodyRef . current
302318 const extendsShown = shownBody === null || pending . startsWith ( shownBody )
303- if ( ! extendsShown ) {
319+ // Incremental edits (append/patch) arrive as complete full-file snapshots, so each is applied
320+ // live — ProseMirror diffs the localized change in place (mid-doc rewrite, insertion, delete).
321+ // A rebuild (create/update streamed from scratch) only extends while revealing from empty; once
322+ // a chunk would collapse the established document it is held until settle, avoiding the flicker.
323+ if ( ! streamIsIncrementalRef . current && ! extendsShown ) {
304324 streamRafRef . current = null
305325 return
306326 }
0 commit comments