From f8e5fe1ed038d15f050fd9467f94352ccb8d966a Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Mon, 12 Jan 2026 17:51:41 +0100 Subject: [PATCH] Use globalThis for theme to share across module loaders When running with tsx in dev mode, jiti loads extensions in a separate module cache. This caused the theme to be undefined in jiti's copy of theme.ts since initTheme() was only called on tsx's copy. Using globalThis with Symbol.for() ensures all module instances share the same theme, regardless of which loader created them. --- .../src/modes/interactive/theme/theme.ts | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/coding-agent/src/modes/interactive/theme/theme.ts b/packages/coding-agent/src/modes/interactive/theme/theme.ts index ae99ed40..97a7a691 100644 --- a/packages/coding-agent/src/modes/interactive/theme/theme.ts +++ b/packages/coding-agent/src/modes/interactive/theme/theme.ts @@ -593,7 +593,23 @@ function getDefaultTheme(): string { // Global Theme Instance // ============================================================================ -export let theme: Theme; +// Use globalThis to share theme across module loaders (tsx + jiti in dev mode) +const THEME_KEY = Symbol.for("@mariozechner/pi-coding-agent:theme"); + +// Export theme as a getter that reads from globalThis +// This ensures all module instances (tsx, jiti) see the same theme +export const theme: Theme = new Proxy({} as Theme, { + get(_target, prop) { + const t = (globalThis as Record)[THEME_KEY]; + if (!t) throw new Error("Theme not initialized. Call initTheme() first."); + return (t as unknown as Record)[prop]; + }, +}); + +function setGlobalTheme(t: Theme): void { + (globalThis as Record)[THEME_KEY] = t; +} + let currentThemeName: string | undefined; let themeWatcher: fs.FSWatcher | undefined; let onThemeChangeCallback: (() => void) | undefined; @@ -602,14 +618,14 @@ export function initTheme(themeName?: string, enableWatcher: boolean = false): v const name = themeName ?? getDefaultTheme(); currentThemeName = name; try { - theme = loadTheme(name); + setGlobalTheme(loadTheme(name)); if (enableWatcher) { startThemeWatcher(); } } catch (_error) { // Theme is invalid - fall back to dark theme silently currentThemeName = "dark"; - theme = loadTheme("dark"); + setGlobalTheme(loadTheme("dark")); // Don't start watcher for fallback theme } } @@ -617,7 +633,7 @@ export function initTheme(themeName?: string, enableWatcher: boolean = false): v export function setTheme(name: string, enableWatcher: boolean = false): { success: boolean; error?: string } { currentThemeName = name; try { - theme = loadTheme(name); + setGlobalTheme(loadTheme(name)); if (enableWatcher) { startThemeWatcher(); } @@ -628,7 +644,7 @@ export function setTheme(name: string, enableWatcher: boolean = false): { succes } catch (error) { // Theme is invalid - fall back to dark theme currentThemeName = "dark"; - theme = loadTheme("dark"); + setGlobalTheme(loadTheme("dark")); // Don't start watcher for fallback theme return { success: false, @@ -638,7 +654,7 @@ export function setTheme(name: string, enableWatcher: boolean = false): { succes } export function setThemeInstance(themeInstance: Theme): void { - theme = themeInstance; + setGlobalTheme(themeInstance); currentThemeName = ""; stopThemeWatcher(); // Can't watch a direct instance if (onThemeChangeCallback) { @@ -677,7 +693,7 @@ function startThemeWatcher(): void { setTimeout(() => { try { // Reload the theme - theme = loadTheme(currentThemeName!); + setGlobalTheme(loadTheme(currentThemeName!)); // Notify callback (to invalidate UI) if (onThemeChangeCallback) { onThemeChangeCallback(); @@ -691,7 +707,7 @@ function startThemeWatcher(): void { setTimeout(() => { if (!fs.existsSync(themeFile)) { currentThemeName = "dark"; - theme = loadTheme("dark"); + setGlobalTheme(loadTheme("dark")); if (themeWatcher) { themeWatcher.close(); themeWatcher = undefined;