@@ -19,6 +19,7 @@ import {
1919 type SettingsSnapshot ,
2020} from "./command-surfaces" ;
2121import type { KeyEvent } from "@opentui/core" ;
22+ import type { ThemeSetting } from "./theme-detect.js" ;
2223
2324import { focusOwner } from "./focus/index.js" ;
2425import { BUNDLED_PLUGIN_MARKER as BUNDLED_MARKER } from "../plugins/origin-marker.js" ;
@@ -43,6 +44,7 @@ function baseSnapshot(): SettingsSnapshot {
4344 waitForApproval : true ,
4445 telemetryEnabled : false ,
4546 showPromptCost : false ,
47+ theme : "auto" ,
4648 } ;
4749}
4850
@@ -133,13 +135,15 @@ function settingsDeps(overrides?: Partial<SettingsSnapshot>): {
133135 waitForApproval : boolean [ ] ;
134136 telemetry : boolean [ ] ;
135137 showPromptCost : boolean [ ] ;
138+ theme : ThemeSetting [ ] ;
136139 } ;
137140} {
138141 let state : SettingsSnapshot = { ...baseSnapshot ( ) , ...overrides } ;
139142 const calls = {
140143 waitForApproval : [ ] as boolean [ ] ,
141144 telemetry : [ ] as boolean [ ] ,
142145 showPromptCost : [ ] as boolean [ ] ,
146+ theme : [ ] as ThemeSetting [ ] ,
143147 } ;
144148 const deps : CommandSurfaceDeps = {
145149 notify : ( ) => undefined ,
@@ -157,6 +161,10 @@ function settingsDeps(overrides?: Partial<SettingsSnapshot>): {
157161 calls . showPromptCost . push ( value ) ;
158162 state = { ...state , showPromptCost : value } ;
159163 } ,
164+ setTheme : ( value ) => {
165+ calls . theme . push ( value ) ;
166+ state = { ...state , theme : value } ;
167+ } ,
160168 } ,
161169 } ;
162170 return { deps, snapshot : ( ) => state , calls } ;
@@ -217,6 +225,42 @@ describe("settings surface", () => {
217225 } ) ;
218226 } ) ;
219227
228+ test ( "left/right cycles the theme pin through auto, dark, light" , async ( ) => {
229+ await withShell ( async ( shell ) => {
230+ const { deps, calls, snapshot } = settingsDeps ( ) ;
231+ openCommandSurface ( shell , "settings" , deps ) ;
232+ await flushSurface ( ) ;
233+
234+ // approval wait, telemetry, show cost, theme
235+ moveOverlaySelection ( shell , 3 ) ;
236+ cycleOverlaySelection ( shell , 1 ) ;
237+ await flushSurface ( ) ;
238+ expect ( calls . theme ) . toEqual ( [ "dark" ] ) ;
239+ expect ( snapshot ( ) . theme ) . toBe ( "dark" ) ;
240+ expect ( shell . overlayItems . some ( ( l ) => l . includes ( "‹ dark ›" ) ) ) . toBe ( true ) ;
241+
242+ cycleOverlaySelection ( shell , 1 ) ;
243+ await flushSurface ( ) ;
244+ expect ( calls . theme ) . toEqual ( [ "dark" , "light" ] ) ;
245+ expect ( snapshot ( ) . theme ) . toBe ( "light" ) ;
246+ expect ( shell . overlayItems . some ( ( l ) => l . includes ( "‹ light ›" ) ) ) . toBe (
247+ true ,
248+ ) ;
249+
250+ // Wraps around: past light comes auto again.
251+ cycleOverlaySelection ( shell , 1 ) ;
252+ await flushSurface ( ) ;
253+ expect ( calls . theme ) . toEqual ( [ "dark" , "light" , "auto" ] ) ;
254+ expect ( snapshot ( ) . theme ) . toBe ( "auto" ) ;
255+
256+ // And stepping back from auto wraps the other way, to light.
257+ cycleOverlaySelection ( shell , - 1 ) ;
258+ await flushSurface ( ) ;
259+ expect ( calls . theme ) . toEqual ( [ "dark" , "light" , "auto" , "light" ] ) ;
260+ expect ( snapshot ( ) . theme ) . toBe ( "light" ) ;
261+ } ) ;
262+ } ) ;
263+
220264 test ( "arrow navigation still moves the cursor in a non-cycling overlay" , async ( ) => {
221265 await withShell ( async ( shell ) => {
222266 const deps : CommandSurfaceDeps = {
0 commit comments