feat(coding-agent): add automatic theme mode (#5874)

This commit is contained in:
Armin Ronacher
2026-06-18 17:13:47 +02:00
committed by GitHub
parent 908be616f2
commit d0b46764b1
16 changed files with 620 additions and 80 deletions

View File

@@ -151,6 +151,13 @@ describe("InteractiveMode.createExtensionUIContext setTheme", () => {
const fakeThis: any = {
session: { settingsManager },
settingsManager,
themeController: {
setThemeInstance: vi.fn(() => ({ success: true })),
setThemeName: vi.fn(() => {
fakeThis.ui.requestRender();
return { success: true };
}),
},
ui: { requestRender: vi.fn() },
};
@@ -158,6 +165,7 @@ describe("InteractiveMode.createExtensionUIContext setTheme", () => {
const result = uiContext.setTheme("light");
expect(result.success).toBe(true);
expect(fakeThis.themeController.setThemeName).toHaveBeenCalledWith("light");
expect(settingsManager.setTheme).toHaveBeenCalledWith("light");
expect(currentTheme).toBe("light");
expect(fakeThis.ui.requestRender).toHaveBeenCalledTimes(1);
@@ -173,6 +181,10 @@ describe("InteractiveMode.createExtensionUIContext setTheme", () => {
const fakeThis: any = {
session: { settingsManager },
settingsManager,
themeController: {
setThemeInstance: vi.fn(() => ({ success: true })),
setThemeName: vi.fn(() => ({ success: false, error: "Theme not found" })),
},
ui: { requestRender: vi.fn() },
};
@@ -180,6 +192,7 @@ describe("InteractiveMode.createExtensionUIContext setTheme", () => {
const result = uiContext.setTheme("__missing_theme__");
expect(result.success).toBe(false);
expect(fakeThis.themeController.setThemeName).toHaveBeenCalledWith("__missing_theme__");
expect(settingsManager.setTheme).not.toHaveBeenCalled();
expect(fakeThis.ui.requestRender).not.toHaveBeenCalled();
});

View File

@@ -198,6 +198,24 @@ describe("SettingsManager", () => {
});
});
describe("theme setting", () => {
it("stores slash-separated automatic theme settings separately from fixed theme names", async () => {
const settingsPath = join(agentDir, "settings.json");
writeFileSync(settingsPath, JSON.stringify({ theme: "light/dark" }));
const manager = SettingsManager.create(projectDir, agentDir);
expect(manager.getTheme()).toBeUndefined();
expect(manager.getThemeSetting()).toBe("light/dark");
manager.setTheme("solarized-light/tokyo-night");
await manager.flush();
const savedSettings = JSON.parse(readFileSync(settingsPath, "utf-8"));
expect(savedSettings.theme).toBe("solarized-light/tokyo-night");
});
});
describe("error tracking", () => {
it("should collect and clear load errors via drainErrors", () => {
const globalSettingsPath = join(agentDir, "settings.json");

View File

@@ -21,6 +21,7 @@ type ShutdownThis = {
unregisterSignalHandlers: () => void;
runtimeHost: { dispose: () => Promise<void> };
ui: { terminal: { drainInput: (ms: number) => Promise<void> } };
themeController: { disableAutoSync: () => void };
stop: () => void;
sessionManager: SessionManager;
};
@@ -81,6 +82,7 @@ function createContext(order: string[], sessionManager = createSessionManager())
}),
},
},
themeController: { disableAutoSync: vi.fn() },
stop: vi.fn(() => {
order.push("stop");
}),

View File

@@ -13,6 +13,7 @@ type ShutdownThis = {
unregisterSignalHandlers: () => void;
runtimeHost: { dispose: () => Promise<void> };
ui: { terminal: { drainInput: (ms: number) => Promise<void> } };
themeController: { disableAutoSync: () => void };
stop: () => void;
};
@@ -73,6 +74,7 @@ describe("InteractiveMode SIGTERM shutdown with signal-exit (#5724)", () => {
}),
},
},
themeController: { disableAutoSync: vi.fn() },
stop: vi.fn(() => {
order.push("stop");
}),

View File

@@ -5,6 +5,8 @@ import {
detectTerminalBackgroundTheme,
getThemeByName,
getThemeForRgbColor,
parseAutoThemeSetting,
resolveThemeSetting,
} from "../src/modes/interactive/theme/theme.ts";
afterEach(() => {
@@ -119,3 +121,13 @@ describe("theme detection from RGB", () => {
expect(getThemeForRgbColor({ r: 250, g: 250, b: 250 })).toBe("light");
});
});
describe("theme setting helpers", () => {
it("parses and resolves automatic theme settings", () => {
expect(parseAutoThemeSetting("light/dark")).toEqual({ lightTheme: "light", darkTheme: "dark" });
expect(resolveThemeSetting("dark", "light")).toBe("dark");
expect(resolveThemeSetting("light/dark", "light")).toBe("light");
expect(resolveThemeSetting("light/dark", "dark")).toBe("dark");
expect(resolveThemeSetting("light/dark/extra", "dark")).toBeUndefined();
});
});