diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index c25a143f2..59dc68acd 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -24,7 +24,7 @@ ### Changed -- Changed selectors in `/thinking`, `/model`, `/scoped-models`, `/trust`, and per-model thinking settings to keep active options marked while browsing. `/scoped-models` now uses consistent per-item toggles and strikes through unavailable models ([#8900](https://github.com/earendil-works/pi/pull/8900)). +- Changed selectors in `/thinking`, `/model`, `/scoped-models`, `/trust`, per-model thinking settings, and theme settings to keep active options marked while browsing. `/scoped-models` now uses consistent per-item toggles and strikes through unavailable models ([#8900](https://github.com/earendil-works/pi/pull/8900)). ### Fixed diff --git a/packages/coding-agent/src/modes/interactive/components/settings-selector.ts b/packages/coding-agent/src/modes/interactive/components/settings-selector.ts index eea44e881..cd0e8e869 100644 --- a/packages/coding-agent/src/modes/interactive/components/settings-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/settings-selector.ts @@ -191,20 +191,23 @@ function modelItemLabel(model: Model): string { return `${model.id} ${theme.fg("muted", `[${model.provider}]`)}`; } -function themeItems(availableThemes: string[]): SelectItem[] { - return availableThemes.map((name) => ({ value: name, label: name })); +function themeItems(availableThemes: string[], currentTheme: string): SelectItem[] { + return availableThemes.map((name) => ({ + value: name, + label: `${name === currentTheme ? "✓ " : " "}${name}`, + })); } const AUTOMATIC_THEME_VALUE = "/"; -function singleModeThemeItems(availableThemes: string[]): SelectItem[] { +function singleModeThemeItems(availableThemes: string[], currentTheme: string): SelectItem[] { return [ { value: AUTOMATIC_THEME_VALUE, - label: "Automatic", + label: " Automatic", description: "Use separate themes for light and dark terminal appearance", }, - ...themeItems(availableThemes), + ...themeItems(availableThemes, currentTheme), ]; } @@ -285,7 +288,7 @@ class ThemeSubmenu extends Container { const menu = new SelectSubmenu( "Theme", "Select a theme, or choose Automatic to follow terminal appearance.", - singleModeThemeItems(this.availableThemes), + singleModeThemeItems(this.availableThemes, this.singleTheme), this.singleTheme, (value) => { if (value === AUTOMATIC_THEME_VALUE) { @@ -401,7 +404,7 @@ class ThemeSubmenu extends Container { return new SelectSubmenu( title, description, - themeItems(this.availableThemes), + themeItems(this.availableThemes, currentValue), currentValue, onSelect, () => { diff --git a/packages/coding-agent/test/settings-selector.test.ts b/packages/coding-agent/test/settings-selector.test.ts index 0fb64693b..ffe201d88 100644 --- a/packages/coding-agent/test/settings-selector.test.ts +++ b/packages/coding-agent/test/settings-selector.test.ts @@ -57,6 +57,56 @@ describe("SettingsSelectorComponent", () => { expect(onCopyOnSelectChange.mock.calls.flat()).toEqual([false, true]); }); + it("keeps the configured fixed theme marked while browsing", () => { + const config = { + defaultModel: "not set", + availableDefaultModels: [], + modelThinkingLevels: {}, + currentTheme: "dark", + terminalTheme: "dark", + availableThemes: ["dark", "light"], + warnings: {}, + } as unknown as SettingsConfig; + const callbacks = { onThemePreview: vi.fn(), onCancel: () => {} } as unknown as SettingsCallbacks; + const list = new SettingsSelectorComponent(config, callbacks).getSettingsList(); + + list.selectItem("theme"); + list.handleInput("\r"); + let output = stripAnsi(list.render(120).join("\n")); + expect(output).toContain(" Automatic"); + expect(output).toContain("→ ✓ dark"); + + list.handleInput("\x1b[B"); + output = stripAnsi(list.render(120).join("\n")); + expect(output).toContain(" ✓ dark"); + expect(output).toContain("→ light"); + }); + + it("keeps a configured automatic theme marked while browsing", () => { + const config = { + defaultModel: "not set", + availableDefaultModels: [], + modelThinkingLevels: {}, + currentTheme: "light/dark", + terminalTheme: "dark", + availableThemes: ["dark", "light", "other"], + warnings: {}, + } as unknown as SettingsConfig; + const callbacks = { onThemePreview: vi.fn(), onCancel: () => {} } as unknown as SettingsCallbacks; + const list = new SettingsSelectorComponent(config, callbacks).getSettingsList(); + + list.selectItem("theme"); + list.handleInput("\r"); + list.handleInput("\r"); + let output = stripAnsi(list.render(120).join("\n")); + expect(output).toContain("→ ✓ light"); + + list.handleInput("\x1b[B"); + output = stripAnsi(list.render(120).join("\n")); + expect(output).toContain(" ✓ light"); + expect(output).toContain("→ other"); + }); + it("keeps the configured per-model thinking level marked while browsing", async () => { harness = await createHarness({ models: [{ id: "thinking-model", reasoning: true }],