import { useCallback, useEffect, useMemo, useState, type ReactNode, } from 'react'; import type { ActionSet, RegisteredAction } from './types'; import type { ActionRegistry } from './registry'; import type { HotkeyManager } from './hotkey-manager'; import type { HotkeyStore } from './hotkeyStore'; import { ActionContext } from './ActionContext'; import { CommandPalette } from './CommandPalette'; import { getRendererPlatform } from '../lib/platform'; interface ActionProviderProps { children: ReactNode; /** ActionSets to register — each adapts a component or subsystem. */ actionSets: ActionSet[]; /** The action registry (created and owned by AppShell). */ registry: ActionRegistry; /** The hotkey manager (created and owned by AppShell). */ hotkeyManager: HotkeyManager; /** The hotkey store (created and owned by AppShell). */ hotkeyStore: HotkeyStore; } /** * Provides the action registry, hotkey manager, and command palette to the app. * * Accepts ActionSets and core services via dependency injection. * AppShell creates the registry and hotkey manager; ActionProvider only * registers/unregisters action sets and renders the command palette. */ export function ActionProvider({ children, actionSets, registry, hotkeyManager, hotkeyStore, }: ActionProviderProps) { const isMac = getRendererPlatform() === 'darwin'; // Command palette open state const [paletteOpen, setPaletteOpen] = useState(false); // Build the full list of action sets including the internal palette set const allActionSets = useMemo( () => [ ...actionSets, { id: 'palette', getActions: () => [ { id: 'open', name: 'Open command palette', description: 'Search and execute commands', hotkeys: [{ modifiers: ['Mod'], key: 'p' }], execute: () => setPaletteOpen(true), }, ], }, ], [actionSets], ); /** * Register all actions from all sets and wire their effective hotkeys. */ const registerAll = useCallback(() => { for (const set of allActionSets) { for (const def of set.getActions()) { const registered = registry.register(set.id, def); for (const hotkey of hotkeyStore.getEffectiveHotkeys(registered)) { hotkeyManager.registerHotkey(registered.id, hotkey); } } } }, [allActionSets, registry, hotkeyManager, hotkeyStore]); /** * Unregister all actions and clear their hotkeys. */ const unregisterAll = useCallback(() => { for (const set of allActionSets) { registry.unregisterAll(set.id); hotkeyManager.removeSourceHotkeys(set.id); } }, [allActionSets, registry, hotkeyManager]); // Register on mount / when action sets change. // Re-register hotkeys when the user edits an override. useEffect(() => { registerAll(); return unregisterAll; }, [registerAll, unregisterAll]); // Re-register hotkeys immediately when the user edits an override, // without waiting for a React state cycle. useEffect(() => { return hotkeyStore.onChange(() => { unregisterAll(); registerAll(); }); }, [hotkeyStore, registerAll, unregisterAll]); const handlePaletteExecute = useCallback( (action: RegisteredAction) => { void registry.execute(action.id); }, [registry], ); const availableActions = useMemo( () => registry.getAvailable(), [registry, paletteOpen], ); const value = useMemo( () => ({ registry, hotkeyStore }), [registry, hotkeyStore], ); return ( {children} setPaletteOpen(false)} onExecute={handlePaletteExecute} actions={availableActions} hotkeyStore={hotkeyStore} isMac={isMac} /> ); }