From cc7969ac00ec7131676bfa7774ea6990566f0f56 Mon Sep 17 00:00:00 2001 From: youfch <97601975+youfch@users.noreply.github.com> Date: Tue, 26 May 2026 16:50:16 +0800 Subject: [PATCH] fix: session rename exits immediately due to focus race (#1429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: session rename exits immediately due to focus race Replace fragile onBlur handler with document mousedown listener for click-outside detection. Defer dropdown-initiated rename to onOpenChangeComplete (fires after CSS transition + cleanup). Use finalFocus instead of dead onCloseAutoFocus (never forwarded to Base UI). * chore: remove dead editCancelledRef after onBlur removal onBlur was replaced by document mousedown listener. editCancelledRef was only read in the old onBlur handler — its write in the Escape key handler was the sole remaining use. --- .../session/sidebar/SessionNodeItem.tsx | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/packages/ui/src/components/session/sidebar/SessionNodeItem.tsx b/packages/ui/src/components/session/sidebar/SessionNodeItem.tsx index 06431912..deff5e75 100644 --- a/packages/ui/src/components/session/sidebar/SessionNodeItem.tsx +++ b/packages/ui/src/components/session/sidebar/SessionNodeItem.tsx @@ -284,8 +284,13 @@ function SessionNodeItemComponent(props: Props): React.ReactNode { : (showQuickArchiveAction ? 'group-hover:pr-12 group-focus-within:pr-12' : 'group-hover:pr-5 group-focus-within:pr-5')); const alwaysActionPaddingClass = showQuickArchiveAction ? 'pr-13' : 'pr-7'; const suppressNextSelectRef = React.useRef(false); - const editCancelledRef = React.useRef(false); const [isTouchPressed, setIsTouchPressed] = React.useState(false); + const editingIdRef = React.useRef(editingId); + editingIdRef.current = editingId; + const pendingRenameRef = React.useRef<{ id: string; title: string } | null>(null); + const handleSaveEditRef = React.useRef(handleSaveEdit); + handleSaveEditRef.current = handleSaveEdit; + const formRef = React.useRef(null); const session = node.session; const liveSession = useSession(session.id); @@ -447,6 +452,18 @@ function SessionNodeItemComponent(props: Props): React.ReactNode { }); }, [session.id, sessionDirectory]); + // Capture outside-clicks to save edits — immune to focus-race with onBlur. + React.useEffect(() => { + if (editingId !== session.id) return; + const handleDocMouseDown = (e: MouseEvent) => { + if (formRef.current && !formRef.current.contains(e.target as Node)) { + handleSaveEditRef.current(); + } + }; + document.addEventListener('mousedown', handleDocMouseDown); + return () => document.removeEventListener('mousedown', handleDocMouseDown); + }, [editingId, session.id]); + if (editingId === session.id) { return (
{ event.preventDefault(); handleSaveEdit(); @@ -471,7 +488,6 @@ function SessionNodeItemComponent(props: Props): React.ReactNode { onKeyDown={(event) => { if (event.key === 'Escape') { event.stopPropagation(); - editCancelledRef.current = true; handleCancelEdit(); return; } @@ -479,13 +495,6 @@ function SessionNodeItemComponent(props: Props): React.ReactNode { event.stopPropagation(); } }} - onBlur={() => { - if (editCancelledRef.current) { - editCancelledRef.current = false; - return; - } - handleSaveEdit(); - }} />