···3131- `aria-label` describing action; `aria-pressed` if toggle
3232- SVG decorative: `aria-hidden`
33333434+## Icons
3535+3636+**Reference:** `src/icons/index.tsx`
3737+3838+All shared SVG icons live in `src/icons/index.tsx`. They accept `className` for Tailwind sizing/coloring and use `currentColor` for stroke so they inherit the parent's text color. Import them from `../icons` (or `@/icons` if aliased).
3939+3440## Primary / secondary buttons
35413642**Primary CTA:** `bg-primary text-primary-foreground`, adequate padding (`px-3 py-2`), optional `hover:opacity-90`, `disabled:opacity-50`.
···11import { createContext, useContext } from 'react';
22import type { ActionRegistry } from './registry';
33+import type { HotkeyStore } from './hotkeyStore';
3445/**
55- * React context providing the action registry.
66- * Used by components that need to register, search, or execute actions.
66+ * React context providing the action registry and hotkey store.
77+ * Used by components that need to register, search, execute, or edit actions.
78 */
89export interface ActionProviderValue {
910 /** The action registry for registering and looking up actions */
1011 registry: ActionRegistry;
1212+ /** The hotkey store for reading and editing user overrides */
1313+ hotkeyStore: HotkeyStore;
1114}
12151316export const ActionContext = createContext<ActionProviderValue | null>(null);
14171518/**
1616- * Hook to access the action registry.
1919+ * Hook to access the action registry and hotkey store.
1720 * Must be used within an ActionProvider.
1821 */
1922export function useActions(): ActionProviderValue {
···3134export function useActionRegistry(): ActionRegistry {
3235 return useActions().registry;
3336}
3737+3838+/**
3939+ * Hook to access the hotkey store.
4040+ * Must be used within an ActionProvider.
4141+ */
4242+export function useHotkeyStore(): HotkeyStore {
4343+ return useActions().hotkeyStore;
4444+}
+40-13
src/actions/ActionProvider.tsx
···88import type { ActionSet, RegisteredAction } from './types';
99import type { ActionRegistry } from './registry';
1010import type { HotkeyManager } from './hotkey-manager';
1111+import type { HotkeyStore } from './hotkeyStore';
1112import { ActionContext } from './ActionContext';
1213import { CommandPalette } from './CommandPalette';
1314import { getRendererPlatform } from '../lib/platform';
···2021 registry: ActionRegistry;
2122 /** The hotkey manager (created and owned by AppShell). */
2223 hotkeyManager: HotkeyManager;
2424+ /** The hotkey store (created and owned by AppShell). */
2525+ hotkeyStore: HotkeyStore;
2326}
24272528/**
···3437 actionSets,
3538 registry,
3639 hotkeyManager,
4040+ hotkeyStore,
3741}: ActionProviderProps) {
3842 const isMac = getRendererPlatform() === 'darwin';
3943···6064 [actionSets],
6165 );
62666363- // Register/unregister all actions from all sets
6464- useEffect(() => {
6767+ /**
6868+ * Register all actions from all sets and wire their effective hotkeys.
6969+ */
7070+ const registerAll = useCallback(() => {
6571 for (const set of allActionSets) {
6672 for (const def of set.getActions()) {
6773 const registered = registry.register(set.id, def);
6868- if (registered.hotkeys) {
6969- for (const hotkey of registered.hotkeys) {
7070- hotkeyManager.registerHotkey(registered.id, hotkey);
7171- }
7474+ for (const hotkey of hotkeyStore.getEffectiveHotkeys(registered)) {
7575+ hotkeyManager.registerHotkey(registered.id, hotkey);
7276 }
7377 }
7478 }
7979+ }, [allActionSets, registry, hotkeyManager, hotkeyStore]);
75807676- return () => {
7777- for (const set of allActionSets) {
7878- registry.unregisterAll(set.id);
7979- hotkeyManager.removeSourceHotkeys(set.id);
8080- }
8181- };
8181+ /**
8282+ * Unregister all actions and clear their hotkeys.
8383+ */
8484+ const unregisterAll = useCallback(() => {
8585+ for (const set of allActionSets) {
8686+ registry.unregisterAll(set.id);
8787+ hotkeyManager.removeSourceHotkeys(set.id);
8888+ }
8289 }, [allActionSets, registry, hotkeyManager]);
83909191+ // Register on mount / when action sets change.
9292+ // Re-register hotkeys when the user edits an override.
9393+ useEffect(() => {
9494+ registerAll();
9595+ return unregisterAll;
9696+ }, [registerAll, unregisterAll]);
9797+9898+ // Re-register hotkeys immediately when the user edits an override,
9999+ // without waiting for a React state cycle.
100100+ useEffect(() => {
101101+ return hotkeyStore.onChange(() => {
102102+ unregisterAll();
103103+ registerAll();
104104+ });
105105+ }, [hotkeyStore, registerAll, unregisterAll]);
106106+84107 const handlePaletteExecute = useCallback(
85108 (action: RegisteredAction) => {
86109 void registry.execute(action.id);
···93116 [registry, paletteOpen],
94117 );
951189696- const value = useMemo(() => ({ registry }), [registry]);
119119+ const value = useMemo(
120120+ () => ({ registry, hotkeyStore }),
121121+ [registry, hotkeyStore],
122122+ );
9712398124 return (
99125 <ActionContext.Provider value={value}>
···103129 onClose={() => setPaletteOpen(false)}
104130 onExecute={handlePaletteExecute}
105131 actions={availableActions}
132132+ hotkeyStore={hotkeyStore}
106133 isMac={isMac}
107134 />
108135 </ActionContext.Provider>