diff --git a/src/renderer/src/components/editor/combined-diff/load-sections/use-combined-diff-section-loader.ts b/src/renderer/src/components/editor/combined-diff/load-sections/use-combined-diff-section-loader.ts index 65cc57fae9a..582eb25907d 100644 --- a/src/renderer/src/components/editor/combined-diff/load-sections/use-combined-diff-section-loader.ts +++ b/src/renderer/src/components/editor/combined-diff/load-sections/use-combined-diff-section-loader.ts @@ -151,10 +151,6 @@ export function useCombinedDiffSectionLoader({ ) { return } - if (wasShowingContent) { - // Why: content really changed, so the old Monaco height no longer describes this row. - setSectionHeights((prev) => removeDiffSectionMeasuredHeight(prev, index)) - } // `dirty` flips back to false once a save is acknowledged, so it cannot tell a stale payload // from a fresh one. If the draft moved while this fetch was in flight, only commit when the // payload actually agrees with that draft — otherwise this reverts the user's saved text on @@ -163,6 +159,11 @@ export function useCombinedDiffSectionLoader({ if (liveDraft !== draftAtFetchStart && storedContent.modifiedContent !== liveDraft) { return } + if (wasShowingContent) { + // Why: content really changed, so the old measured height no longer describes this row. + // Must follow the skip above, or a rejected payload resizes the row being typed in. + setSectionHeights((prev) => removeDiffSectionMeasuredHeight(prev, index)) + } setSections((prev) => { return prev.map((s, i) => i === index && !s.dirty diff --git a/src/renderer/src/components/editor/pierre-diff/use-pierre-diff-native-view.ts b/src/renderer/src/components/editor/pierre-diff/use-pierre-diff-native-view.ts index 0d926a67333..910a3e2da59 100644 --- a/src/renderer/src/components/editor/pierre-diff/use-pierre-diff-native-view.ts +++ b/src/renderer/src/components/editor/pierre-diff/use-pierre-diff-native-view.ts @@ -14,7 +14,12 @@ import { // The editor reasserts its own selection on attach, which can land well after ours. Budget by // wall clock, not frames: under load a frame count expires long before the editor settles. // User interaction still cancels the pending restore, so this only bounds the quiet case. +// The editor reasserts its own selection well after ours, and convergence needs a later Pierre +// render, so the window extends on each render while a restore is still pending. RESTORE_CEILING +// is the hard bound: past it no further extension is granted, so a row that renders forever +// without converging cannot spin forever. User interaction cancels the restore sooner still. const RESTORE_DEADLINE_MS = 2_000 +const RESTORE_CEILING_MS = 15_000 export function usePierreDiffNativeView( key: string | undefined, @@ -34,6 +39,7 @@ export function usePierreDiffNativeView( const frame = useRef(null) // Armed on attach; 0 until then so a stale ref can never keep a restore alive. const deadline = useRef(0) + const ceiling = useRef(0) const lastSnapshot = useRef(undefined) const schedule = useCallback(() => { if (frame.current !== null || !pending.current || Date.now() > deadline.current) { @@ -103,7 +109,9 @@ export function usePierreDiffNativeView( }) }, [editorRef]) useLayoutEffect(() => { - deadline.current = Date.now() + RESTORE_DEADLINE_MS + const now = Date.now() + ceiling.current = now + RESTORE_CEILING_MS + deadline.current = now + RESTORE_DEADLINE_MS schedule() }, [activeGroupId, schedule]) useLayoutEffect(() => { @@ -174,8 +182,14 @@ export function usePierreDiffNativeView( (host: HTMLElement, phase: PostRenderPhase, instance: PierreDiffInstance) => { if (phase !== 'unmount') { view.current = { host, instance } - // Re-arm on attach: the remount is the point a restore becomes possible. - deadline.current = Date.now() + RESTORE_DEADLINE_MS + const now = Date.now() + if (phase === 'mount') { + ceiling.current = now + RESTORE_CEILING_MS + } + // Extend while a restore is still pending, but never past the ceiling. + if (pending.current && now < ceiling.current) { + deadline.current = Math.min(now + RESTORE_DEADLINE_MS, ceiling.current) + } schedule() } },