From 4d00858b8a814a2a67a68b7e150964e863926c84 Mon Sep 17 00:00:00 2001 From: Maurice Date: Sat, 24 Oct 2026 01:21:28 +0000 Subject: [PATCH] fix(assistant): the window's menu and trip list keep the keyboard in reach The home chat's trip list opened before its trips had loaded, so the focus stayed on its button and no trip could be reached by keyboard; the first trip now takes the focus once the list is there. The header's menu says whether it is open, and closed by Escape or a pick it hands the focus back to its button, so the next Escape closes the window again. The open chat in the history list is announced as the current one. --- .../components/Assistant/AssistantHeader.tsx | 16 ++++++++-- .../Assistant/AssistantTripPicker.tsx | 12 ++++++-- .../Assistant/AssistantWindow.test.tsx | 30 ++++++++++++++++++- .../Packing/PackingPopover.test.tsx | 11 ++++++- .../src/components/Packing/PackingPopover.tsx | 5 +++- 5 files changed, 67 insertions(+), 7 deletions(-) diff --git a/client/src/components/Assistant/AssistantHeader.tsx b/client/src/components/Assistant/AssistantHeader.tsx index 7cc74950a..5f79e954b 100644 --- a/client/src/components/Assistant/AssistantHeader.tsx +++ b/client/src/components/Assistant/AssistantHeader.tsx @@ -11,7 +11,7 @@ import { Trash2, X, } from 'lucide-react'; -import { type HTMLAttributes, type MouseEvent, useState } from 'react'; +import { type HTMLAttributes, type MouseEvent, useRef, useState } from 'react'; import { useNavigate } from 'react-router'; import { useTranslation } from '../../i18n'; @@ -79,6 +79,7 @@ export default function AssistantHeader({ const [memoryOpen, setMemoryOpen] = useState(false); const navigate = useNavigate(); const menu = useContextMenu(); + const moreRef = useRef(null); const [confirming, setConfirming] = useState(false); const threads = thread.threads ?? []; const history = usePopoverList(threads.findIndex((th) => th.id === thread.threadId) + 1); @@ -95,6 +96,14 @@ export default function AssistantHeader({ run(); }; + // Closed by Escape or a pick while it holds the focus, the menu hands it back to its button, so the keys + // go on reaching the window; a click or a scroll elsewhere leaves the focus where it went. + const closeMenu = () => { + const fromMenu = document.activeElement?.closest('[role="menu"]') != null; + menu.close(); + if (fromMenu) moreRef.current?.focus(); + }; + const openMenu = (e: MouseEvent) => menu.open( e, @@ -187,10 +196,12 @@ export default function AssistantHeader({