From 08320d5ab0d9e6d1e65da54ce8ebd18f9831c73a Mon Sep 17 00:00:00 2001 From: Waleed Date: Fri, 10 Jul 2026 10:55:34 -0700 Subject: [PATCH] fix(sidebar): fix rename input losing its selection on open (#5563) * fix(sidebar): fix rename input losing its selection on open Radix's FocusScope defers close-time focus teardown to a setTimeout(0), which can occasionally run after the rename input's own focus()/select() and clobber the selection. Focus the input from onCloseAutoFocus instead, which runs inside that same deferred teardown and always wins the race. Affects workflow, folder, and workspace rename (all route through the shared sidebar ContextMenu component). * fix(sidebar): only refocus the rename input when Rename triggered the close onCloseAutoFocus fires on every menu close, not just after selecting Rename. Gate the refocus behind a ref set only when the Rename item was selected, so closing the menu for an unrelated action (Delete, Duplicate, ...) while an earlier rename is still live doesn't steal focus back into it and delay its blur-save. --- .../components/context-menu/context-menu.tsx | 33 ++++++++++++++++++- .../components/folder-item/folder-item.tsx | 1 + .../workflow-item/workflow-item.tsx | 1 + .../workspace-header/workspace-header.tsx | 3 ++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx index ac6cd52bac..13e4dc6ab1 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu.tsx @@ -1,5 +1,6 @@ 'use client' +import { useRef } from 'react' import { DropdownMenu, DropdownMenuContent, @@ -34,6 +35,18 @@ interface ContextMenuProps { onMarkAsUnread?: () => void onTogglePin?: () => void onRename?: () => void + /** + * Ref to the rename input rendered by the "Rename" action, if any. Radix's + * FocusScope defers its close-time focus teardown to a `setTimeout(0)`, which + * can run after the rename input's own mount-time `focus()`/`select()` and + * clobber the selection (the "rename deselects the text" bug). Focusing from + * `onCloseAutoFocus` runs synchronously inside that same deferred teardown, so + * it always wins the race regardless of scheduler timing. Only applied when + * this specific close was caused by selecting "Rename" (see + * `justSelectedRenameRef`) — an unrelated action closing the menu while an + * earlier rename is still live must not steal focus back into it. + */ + renameInputRef?: React.RefObject onCreate?: () => void onCreateFolder?: () => void onDuplicate?: () => void @@ -84,6 +97,7 @@ export function ContextMenu({ onMarkAsUnread, onTogglePin, onRename, + renameInputRef, onCreate, onCreateFolder, onDuplicate, @@ -132,6 +146,13 @@ export function ContextMenu({ (showUploadLogo && onUploadLogo) const hasCopySection = (showDuplicate && onDuplicate) || (showExport && onExport) + /** + * Only the "Rename" item should trigger the `onCloseAutoFocus` refocus below — + * an unrelated action (Delete, Duplicate, ...) closing this menu while a rename + * from an earlier interaction is still live must not steal focus back into it. + */ + const justSelectedRenameRef = useRef(false) + return ( !open && onClose()} modal={false}> @@ -152,7 +173,16 @@ export function ContextMenu({ side='bottom' sideOffset={4} className='max-h-[var(--radix-dropdown-menu-content-available-height,400px)]' - onCloseAutoFocus={(e) => e.preventDefault()} + onCloseAutoFocus={(e) => { + e.preventDefault() + const shouldFocusRenameInput = justSelectedRenameRef.current + justSelectedRenameRef.current = false + const input = shouldFocusRenameInput ? renameInputRef?.current : null + if (input) { + input.focus() + input.select() + } + }} > {showOpenInNewTab && onOpenInNewTab && ( { + justSelectedRenameRef.current = true onRename() onClose() }} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx index 377e562ead..cc29b05011 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/folder-item/folder-item.tsx @@ -575,6 +575,7 @@ export const FolderItem = memo(function FolderItem({ workspaceId, folder }: Fold menuRef={menuRef} onClose={closeMenu} onRename={handleStartEdit} + renameInputRef={inputRef} onCreate={handleCreateWorkflowInFolder} onCreateFolder={handleCreateFolderInFolder} onDuplicate={handleDuplicate} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx index d0c4b104b4..fcc7d8a28b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/workflow-item/workflow-item.tsx @@ -487,6 +487,7 @@ export const WorkflowItem = memo(function WorkflowItem({ onClose={closeMenu} onOpenInNewTab={handleOpenInNewTab} onRename={handleStartEdit} + renameInputRef={inputRef} onDuplicate={handleDuplicate} onExport={handleExport} onDelete={handleOpenDeleteModal} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx index 711d96af8d..dcdfefbacb 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx @@ -113,6 +113,7 @@ function WorkspaceHeaderImpl({ const isContextMenuOpeningRef = useRef(false) const contextMenuClosedRef = useRef(true) const hasInputFocusedRef = useRef(false) + const renameInputRef = useRef(null) const [isMounted, setIsMounted] = useState(false) useEffect(() => { @@ -408,6 +409,7 @@ function WorkspaceHeaderImpl({ )} { + renameInputRef.current = el if (el && !hasInputFocusedRef.current) { hasInputFocusedRef.current = true el.focus() @@ -643,6 +645,7 @@ function WorkspaceHeaderImpl({ menuRef={contextMenuRef} onClose={closeContextMenu} onRename={handleRenameAction} + renameInputRef={renameInputRef} onDelete={handleDeleteAction} onLeave={handleLeaveAction} onUploadLogo={handleUploadLogoAction}