From b7673c6ff24a22249bf942b5f5a8c476676e5117 Mon Sep 17 00:00:00 2001 From: kev1n77 Date: Sat, 10 Oct 2026 13:17:01 +0800 Subject: [PATCH] fix(flow-chat): accept directories from the context picker on Enter and click Enter, Tab, and a row click in the '@' reference picker now add the highlighted directory or file as context instead of descending into a directory, so accepting a row never changes the browsing level. Entering a directory stays on the explicit affordances: the arrow keys and a click on the trailing chevron, which now carries a localized tooltip. Adds one locale string per locale for that tooltip. --- .../components/ChatContextPicker.tsx | 31 +++-- .../ChatContextPickerOverlay.test.tsx | 121 +++++++++++++++++- src/web-ui/src/locales/en-US/flow-chat.json | 1 + src/web-ui/src/locales/zh-CN/flow-chat.json | 1 + src/web-ui/src/locales/zh-TW/flow-chat.json | 1 + 5 files changed, 144 insertions(+), 11 deletions(-) diff --git a/src/web-ui/src/flow_chat/components/ChatContextPicker.tsx b/src/web-ui/src/flow_chat/components/ChatContextPicker.tsx index f47e6e1da8..f3ce6ad847 100644 --- a/src/web-ui/src/flow_chat/components/ChatContextPicker.tsx +++ b/src/web-ui/src/flow_chat/components/ChatContextPicker.tsx @@ -670,8 +670,13 @@ export const ChatContextPicker: React.FC = ({ onClose(); }, [onAddImage, onClose, onRetrySkills, onSelectContext, onSelectSkill, onSelectMcp, openSource]); - const handleItemClick = useCallback((selection: ContextPickerItem) => { - if (selection.kind === 'file' && selection.item.isDirectory && !isSearchMode) { + const handleItemClick = useCallback((selection: ContextPickerItem, event: React.MouseEvent) => { + // Browsing keeps its own affordance: the trailing chevron opens a directory, + // while a row click accepts the row exactly like Enter. Both input devices + // therefore select a directory without leaving the current level. + const clickedEnterAffordance = event.target instanceof Element + && Boolean(event.target.closest('[data-openbitfun-part="indicator"]')); + if (selection.kind === 'file' && selection.item.isDirectory && !isSearchMode && clickedEnterAffordance) { enterDirectory(selection.item); return; } @@ -712,13 +717,13 @@ export const ChatContextPicker: React.FC = ({ } case 'Enter': case 'Tab': { + // Accepting a row always selects it, including a directory. Browsing a + // directory stays on the explicitly navigational keys (ArrowRight in, + // ArrowLeft out) so acceptance and navigation never share one key. event.preventDefault(); event.stopPropagation(); const selected = displayItems[selectedIndex]; - if (selected) { - if (event.key === 'Tab' && selected.kind !== 'source') handleSelect(selected); - else handleItemClick(selected); - } + if (selected) handleSelect(selected); break; } case 'Escape': @@ -727,7 +732,7 @@ export const ChatContextPicker: React.FC = ({ onClose(); break; } - }, [canNavigateBack, displayItems, enterDirectory, goBack, handleItemClick, handleSelect, isOpen, isSearchMode, onClose, openSource, selectedIndex]); + }, [canNavigateBack, displayItems, enterDirectory, goBack, handleSelect, isOpen, isSearchMode, onClose, openSource, selectedIndex]); useEffect(() => { if (!isOpen) return; @@ -893,6 +898,7 @@ export const ChatContextPicker: React.FC = ({ : 'chatInput.boostSkillsLoadFailed') : session?.sessionName ?? skill?.name ?? mcp?.label ?? file?.name; const skillDescription = skill?.description?.trim() || undefined; + const isEnterableDirectory = Boolean(file?.isDirectory) && !isSearchMode; return ( = ({ data-openbitfun-context-kind={selection.kind === 'source' || selection.kind === 'action' ? selection.id : selection.kind === 'mcp' ? `mcp-${selection.item.kind}` : selection.kind} - indicator={selection.kind === 'source' || (file?.isDirectory && !isSearchMode) - ?