Fix Cmd+K focus restore and focus input after agent switch

This commit is contained in:
Mohamed Boudra
2026-02-04 11:32:28 +07:00
parent bceac42ec9
commit 1be891e21c
4 changed files with 46 additions and 16 deletions
@@ -374,9 +374,11 @@ export function AgentInputArea({
} }
lastHandledFocusRequestIdRef.current = focusChatInputRequest.id; lastHandledFocusRequestIdRef.current = focusChatInputRequest.id;
setTimeout(() => { requestAnimationFrame(() => {
messageInputRef.current?.focus(); requestAnimationFrame(() => {
}, 0); messageInputRef.current?.focus();
});
});
clearFocusChatInputRequest(focusChatInputRequest.id); clearFocusChatInputRequest(focusChatInputRequest.id);
}, [ }, [
agentId, agentId,
+14 -12
View File
@@ -49,8 +49,8 @@ export function CommandCenter() {
const open = useKeyboardNavStore((s) => s.commandCenterOpen); const open = useKeyboardNavStore((s) => s.commandCenterOpen);
const setOpen = useKeyboardNavStore((s) => s.setCommandCenterOpen); const setOpen = useKeyboardNavStore((s) => s.setCommandCenterOpen);
const requestFocusChatInput = useKeyboardNavStore((s) => s.requestFocusChatInput); const requestFocusChatInput = useKeyboardNavStore((s) => s.requestFocusChatInput);
const takeFocusRestoreElement = useKeyboardNavStore((s) => s.takeFocusRestoreElement);
const inputRef = useRef<TextInput>(null); const inputRef = useRef<TextInput>(null);
const previouslyFocusedRef = useRef<HTMLElement | null>(null);
const didNavigateRef = useRef(false); const didNavigateRef = useRef(false);
const prevOpenRef = useRef(open); const prevOpenRef = useRef(open);
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
@@ -71,16 +71,18 @@ export function CommandCenter() {
setActiveIndex(0); setActiveIndex(0);
if (prevOpen && !didNavigateRef.current) { if (prevOpen && !didNavigateRef.current) {
const el = previouslyFocusedRef.current; const el = takeFocusRestoreElement();
if (el && el.isConnected) { if (el && el.isConnected) {
// Modal unmount can steal focus; restore on next tick. // Modal unmount can steal focus; restore on next tick.
setTimeout(() => { requestAnimationFrame(() => {
try { requestAnimationFrame(() => {
el.focus(); try {
} catch { el.focus();
// ignore } catch {
} // ignore
}, 0); }
});
});
} }
} }
@@ -88,8 +90,6 @@ export function CommandCenter() {
} }
didNavigateRef.current = false; didNavigateRef.current = false;
previouslyFocusedRef.current =
typeof document !== "undefined" ? (document.activeElement as HTMLElement | null) : null;
const id = setTimeout(() => { const id = setTimeout(() => {
inputRef.current?.focus(); inputRef.current?.focus();
@@ -118,10 +118,12 @@ export function CommandCenter() {
const navigate = shouldReplace ? router.replace : router.push; const navigate = shouldReplace ? router.replace : router.push;
requestFocusChatInput(`${agent.serverId}:${agent.id}`); requestFocusChatInput(`${agent.serverId}:${agent.id}`);
// Don't restore focus back to the prior element after we navigate.
takeFocusRestoreElement();
setOpen(false); setOpen(false);
navigate(`/agent/${agent.serverId}/${agent.id}` as any); navigate(`/agent/${agent.serverId}/${agent.id}` as any);
}, },
[pathname, requestFocusChatInput, setOpen] [pathname, requestFocusChatInput, setOpen, takeFocusRestoreElement]
); );
useEffect(() => { useEffect(() => {
@@ -127,7 +127,19 @@ export function useGlobalKeyboardNav({
if ((event.metaKey || event.ctrlKey) && lowerKey === "k") { if ((event.metaKey || event.ctrlKey) && lowerKey === "k") {
event.preventDefault(); event.preventDefault();
const s = useKeyboardNavStore.getState(); const s = useKeyboardNavStore.getState();
s.setCommandCenterOpen(!s.commandCenterOpen); if (!s.commandCenterOpen) {
const target = event.target as unknown;
const el =
typeof HTMLElement !== "undefined" && target instanceof HTMLElement
? target
: (typeof document !== "undefined"
? (document.activeElement as HTMLElement | null)
: null);
s.setFocusRestoreElement(el);
s.setCommandCenterOpen(true);
} else {
s.setCommandCenterOpen(false);
}
return; return;
} }
@@ -6,12 +6,16 @@ interface KeyboardNavState {
cmdOrCtrlDown: boolean; cmdOrCtrlDown: boolean;
/** Sidebar-visible agent keys (up to 9), in top-to-bottom visual order. */ /** Sidebar-visible agent keys (up to 9), in top-to-bottom visual order. */
sidebarShortcutAgentKeys: string[]; sidebarShortcutAgentKeys: string[];
/** Captured focus target when opening the command center (web only). */
focusRestoreElement: HTMLElement | null;
focusChatInputRequest: { id: number; agentKey: string | null } | null; focusChatInputRequest: { id: number; agentKey: string | null } | null;
setCommandCenterOpen: (open: boolean) => void; setCommandCenterOpen: (open: boolean) => void;
setAltDown: (down: boolean) => void; setAltDown: (down: boolean) => void;
setCmdOrCtrlDown: (down: boolean) => void; setCmdOrCtrlDown: (down: boolean) => void;
setSidebarShortcutAgentKeys: (keys: string[]) => void; setSidebarShortcutAgentKeys: (keys: string[]) => void;
setFocusRestoreElement: (el: HTMLElement | null) => void;
takeFocusRestoreElement: () => HTMLElement | null;
requestFocusChatInput: (agentKey: string | null) => void; requestFocusChatInput: (agentKey: string | null) => void;
clearFocusChatInputRequest: (id: number) => void; clearFocusChatInputRequest: (id: number) => void;
resetModifiers: () => void; resetModifiers: () => void;
@@ -22,12 +26,22 @@ export const useKeyboardNavStore = create<KeyboardNavState>((set) => ({
altDown: false, altDown: false,
cmdOrCtrlDown: false, cmdOrCtrlDown: false,
sidebarShortcutAgentKeys: [], sidebarShortcutAgentKeys: [],
focusRestoreElement: null,
focusChatInputRequest: null, focusChatInputRequest: null,
setCommandCenterOpen: (open) => set({ commandCenterOpen: open }), setCommandCenterOpen: (open) => set({ commandCenterOpen: open }),
setAltDown: (down) => set({ altDown: down }), setAltDown: (down) => set({ altDown: down }),
setCmdOrCtrlDown: (down) => set({ cmdOrCtrlDown: down }), setCmdOrCtrlDown: (down) => set({ cmdOrCtrlDown: down }),
setSidebarShortcutAgentKeys: (keys) => set({ sidebarShortcutAgentKeys: keys }), setSidebarShortcutAgentKeys: (keys) => set({ sidebarShortcutAgentKeys: keys }),
setFocusRestoreElement: (el) => set({ focusRestoreElement: el }),
takeFocusRestoreElement: () => {
let value: HTMLElement | null = null;
set((state) => {
value = state.focusRestoreElement;
return { focusRestoreElement: null };
});
return value;
},
requestFocusChatInput: (agentKey) => requestFocusChatInput: (agentKey) =>
set((state) => ({ set((state) => ({
focusChatInputRequest: { focusChatInputRequest: {