Add keyboard shortcut rebinding with string-based format and sectioned settings UI

- Parse/format shortcut strings ("Cmd+Shift+O") bidirectionally via shortcut-string.ts
- Convert all 60+ bindings from structured KeyCombo objects to declarative strings
- Add keyboard shortcut overrides storage (AsyncStorage + React Query)
- Wire overrides into the keyboard shortcut resolution pipeline
- Add sectioned settings layout with sidebar on desktop, single scroll on mobile
- Add keyboard shortcuts settings section with rebind/reset UI and key capture
- Create useShortcutKeys hook so tooltip shortcut displays update on rebind
- Replace hardcoded shortcut displays in agent-input-area, left-sidebar, command center
This commit is contained in:
Mohamed Boudra
2026-03-21 23:14:59 +07:00
parent 68ab7b81a1
commit ad1cb4c200
10 changed files with 1607 additions and 612 deletions
+25 -4
View File
@@ -13,6 +13,11 @@ import {
} from "@/utils/command-center-focus-restore";
import { buildHostSettingsRoute, parseServerIdFromPathname } from "@/utils/host-routes";
import type { ShortcutKey } from "@/utils/format-shortcut";
import { comboStringToShortcutKeys } from "@/keyboard/shortcut-string";
import { getBindingIdForAction, getDefaultKeysForAction } from "@/keyboard/keyboard-shortcuts";
import { useKeyboardShortcutOverrides } from "@/hooks/use-keyboard-shortcut-overrides";
import { getShortcutOs } from "@/utils/shortcut-platform";
import { getIsDesktop } from "@/constants/layout";
import { prepareWorkspaceTab } from "@/utils/workspace-navigation";
import { focusWithRetries } from "@/utils/web-focus";
@@ -48,7 +53,7 @@ type CommandCenterActionDefinition = {
id: string;
title: string;
icon?: "plus" | "settings";
shortcutKeys?: ShortcutKey[];
actionId?: string;
keywords: string[];
routeKind: "settings" | "none";
};
@@ -58,7 +63,7 @@ const COMMAND_CENTER_ACTIONS: readonly CommandCenterActionDefinition[] = [
id: "new-agent",
title: "Open project",
icon: "plus",
shortcutKeys: ["mod", "shift", "O"],
actionId: "new-agent",
keywords: ["open", "project", "folder", "workspace", "repo"],
routeKind: "none",
},
@@ -99,9 +104,25 @@ export type CommandCenterItem =
agent: AggregatedAgent;
};
function resolveActionShortcutKeys(
actionId: string | undefined,
overrides: Record<string, string>,
): ShortcutKey[] | undefined {
if (!actionId) return undefined;
const isMac = getShortcutOs() === "mac";
const isDesktop = getIsDesktop();
const platform = { isMac, isDesktop };
const bindingId = getBindingIdForAction(actionId, platform);
if (!bindingId) return undefined;
const override = overrides[bindingId];
if (override) return comboStringToShortcutKeys(override);
return getDefaultKeysForAction(actionId, platform) ?? undefined;
}
export function useCommandCenter() {
const pathname = usePathname();
const daemons = useHosts();
const { overrides } = useKeyboardShortcutOverrides();
const open = useKeyboardShortcutsStore((s) => s.commandCenterOpen);
const setOpen = useKeyboardShortcutsStore((s) => s.setCommandCenterOpen);
const inputRef = useRef<TextInput>(null);
@@ -158,9 +179,9 @@ export function useCommandCenter() {
title: action.title,
icon: action.icon,
route: action.routeKind === "settings" ? settingsRoute : undefined,
shortcutKeys: action.shortcutKeys,
shortcutKeys: resolveActionShortcutKeys(action.actionId, overrides),
}));
}, [open, query, settingsRoute]);
}, [open, query, settingsRoute, overrides]);
const items = useMemo(() => {
if (!open) {