From 14bcc6e261cd59d04d280a8ed5d1fd0c40ee639d Mon Sep 17 00:00:00 2001 From: gogamid <36050790+gogamid@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:13:51 +0200 Subject: [PATCH] feat(extensions): add setTheme method to extension API --- src/extension-api/types.ts | 8 ++++++++ src/extensions/runExtension.ts | 6 ++++++ src/extensions/types.ts | 2 ++ src/ui/App.tsx | 5 +++++ 4 files changed, 21 insertions(+) diff --git a/src/extension-api/types.ts b/src/extension-api/types.ts index cdc10dd9b..6df8f117f 100644 --- a/src/extension-api/types.ts +++ b/src/extension-api/types.ts @@ -1073,6 +1073,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 36255fa4a..a856ac7c6 100644 --- a/src/extensions/runExtension.ts +++ b/src/extensions/runExtension.ts @@ -426,6 +426,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 8047b893c..c857a1ad3 100644 --- a/src/extensions/types.ts +++ b/src/extensions/types.ts @@ -161,6 +161,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 3771a4262..c65535172 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -1042,6 +1042,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(() => {