Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/components/Editor/SourceEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,14 @@ export function SourceEditor({ hidden = false, readOnly = false }: SourceEditorP
const setCursorInfoRef = useRef(setCursorInfo);
const setSelectedTextRef = useRef(setSelectedText);
const cursorInfoRef = useRef(cursorInfo);
// Sync "latest value" refs after commit (read only from the CodeMirror listener/effects) — concurrent-safe (#1063).
useEffect(() => {
hiddenRef.current = hidden;
setContentRef.current = setContent;
setCursorInfoRef.current = setCursorInfo;
setSelectedTextRef.current = setSelectedText;
cursorInfoRef.current = cursorInfo;
});
// Latest-value refs synced during render: read by CodeMirror's update listener, a delayed focus/restore setTimeout, and an interval poll — all of which can fire before a passive effect would flush, so they need pre-commit freshness (#1063).
/* eslint-disable react-hooks/refs */
hiddenRef.current = hidden;
setContentRef.current = setContent;
setCursorInfoRef.current = setCursorInfo;
setSelectedTextRef.current = setSelectedText;
cursorInfoRef.current = cursorInfo;
/* eslint-enable react-hooks/refs */

// Use editor store for global settings
const wordWrap = useUIStore((state) => state.wordWrap);
Expand Down
18 changes: 9 additions & 9 deletions src/components/Editor/TiptapEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,15 +180,15 @@ export function TiptapEditorInner({ hidden = false, readOnly = false, preview =
const contentRef = useRef(content);
const editorRef = useRef<TiptapEditor | null>(null);
const flushToStoreRef = useRef<((editor: TiptapEditor) => void) | null>(null);
// Sync "latest value" refs after commit (read only from callbacks/effects) — concurrent-safe (#1063).
useEffect(() => {
cursorInfoRef.current = cursorInfo;
preserveLineBreaksRef.current = preserveLineBreaks;
hardBreakStyleOnSaveRef.current = hardBreakStyleOnSave;
hiddenRef.current = hidden;
previewRef.current = preview;
contentRef.current = content;
});
// Latest-value refs synced during render: a deferred init parse (setTimeout) + the unmount-flush read these and need the latest committed value before effects run (#1063).
/* eslint-disable react-hooks/refs */
cursorInfoRef.current = cursorInfo;
preserveLineBreaksRef.current = preserveLineBreaks;
hardBreakStyleOnSaveRef.current = hardBreakStyleOnSave;
hiddenRef.current = hidden;
previewRef.current = preview;
contentRef.current = content;
/* eslint-enable react-hooks/refs */

const extensions = useMemo(
() => createTiptapExtensions({ tabId: activeTabId, lintEnabled }),
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useGenieInvocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ export function useGenieInvocation() {
[runGenie]
);

// eslint-disable-next-line react-hooks/refs -- latest-value ref read only by the async MCP bridge listener (#1063)
// eslint-disable-next-line react-hooks/refs -- render-synced so the synchronous MCP bridge handler sees the latest invokeGenie before passive effects run (#1063)
invokeGenieRef.current = invokeGenie;

const invokeFreeform = useCallback(
Expand Down
16 changes: 8 additions & 8 deletions src/hooks/useTabDragOut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* @module hooks/useTabDragOut
*/

import { useCallback, useEffect, useRef, useState, type PointerEvent as ReactPointerEvent, type RefObject } from "react";
import { useCallback, useRef, useState, type PointerEvent as ReactPointerEvent, type RefObject } from "react";

/** Vertical distance (px) outside the tab bar to trigger drag-out. */
const DRAG_OUT_THRESHOLD = 40;
Expand Down Expand Up @@ -150,17 +150,17 @@ export function useTabDragOut({ tabBarRef, onDragOut, onReorder, onDragMove }: U
}
}, []);

// Latest-value refs read only from the document drag listeners; synced after commit (#1063).
// Latest-value refs read by synchronous document pointer listeners during a drag, so they must be render-synced (fresh before a pointer event fires), not passive (#1063).
const onDragOutRef = useRef(onDragOut);
const onReorderRef = useRef(onReorder);
const onDragMoveRef = useRef(onDragMove);
const stableBarRef = useRef(tabBarRef);
useEffect(() => {
onDragOutRef.current = onDragOut;
onReorderRef.current = onReorder;
onDragMoveRef.current = onDragMove;
stableBarRef.current = tabBarRef;
});
/* eslint-disable react-hooks/refs */
onDragOutRef.current = onDragOut;
onReorderRef.current = onReorder;
onDragMoveRef.current = onDragMove;
stableBarRef.current = tabBarRef;
/* eslint-enable react-hooks/refs */

// Detach document listeners and reset state
/* v8 ignore start -- @preserve reason: cleanupRef empty-function initializer and reset are uncovered; drag cleanup not triggered in unit tests */
Expand Down
Loading