diff --git a/src/extension-api/types.ts b/src/extension-api/types.ts index e0bebe63..ecab2f15 100644 --- a/src/extension-api/types.ts +++ b/src/extension-api/types.ts @@ -1489,6 +1489,14 @@ export interface HunkExtensionAPI { readonly config: Record; /** Record a diagnostic line; collected per extension instead of written to the terminal. */ log(message: string): void; + /** + * Switch the active theme programmatically. + * + * Valid at any time — during the factory, from a lifecycle handler, or from + * a command handler. The theme id must match a built-in, config-defined, or + * extension-registered theme. + */ + setTheme(themeId: string): void; } /** Default export every extension entry file must provide. */ diff --git a/src/extensions/runExtension.ts b/src/extensions/runExtension.ts index 3a20c7be..d3f01df5 100644 --- a/src/extensions/runExtension.ts +++ b/src/extensions/runExtension.ts @@ -448,6 +448,12 @@ export function createExtensionApi( // Logs are collected rather than printed: the TUI owns the terminal. registry.logs.push({ extensionId: metadata.id, message: String(message) }); }, + setTheme(themeId: string) { + // Runtime action, not a registration — no assertOpen guard. + // The callback is bound after the UI mounts; before that, calls are a + // silent no-op since there is nothing to switch yet. + registry.setTheme?.(themeId); + }, }; return { diff --git a/src/extensions/types.ts b/src/extensions/types.ts index 648f823d..fe37e1d2 100644 --- a/src/extensions/types.ts +++ b/src/extensions/types.ts @@ -187,6 +187,8 @@ export interface ExtensionRegistry { eventBusPhase: "loading" | "ready" | "closed"; /** Bound after loading so hunk.events.emit can dispatch at runtime. */ emitCustomEvent?: (event: string, payload: unknown) => void; + /** Bound after the UI mounts so hunk.setTheme can switch themes at runtime. */ + setTheme?: (themeId: string) => void; logs: ExtensionLogEntry[]; } diff --git a/src/ui/App.tsx b/src/ui/App.tsx index d8d549d3..4771dcbb 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -1182,6 +1182,11 @@ export function App({ }, [showTransientNotice, themeOptions], ); + // Wire hunk.setTheme into the React theme state so extensions can switch + // themes programmatically at runtime. + if (extensions) { + extensions.registry.setTheme = selectTheme; + } /** Open the keyboard-driven theme selector with the current theme highlighted. */ const openThemeSelector = useCallback(() => {