fix(themes): apply a runtime switch back to default on the desktop

ingestBackendSkin returned early for name === 'default' even when
apply=true, so a real runtime switch to the default skin (/skin default
on CLI/TUI, or config.set display.skin=default) emitted skin.changed but
never repainted the desktop. 'default' is no-opinion on the PALETTE (the
desktop keeps its own nous default, so we still never register a converted
theme under it), but it IS a valid apply TARGET: setTheme normalizes
'default' -> nous, so switching back repaints to the desktop default.
Skip only the registry step for 'default' and let it flow through the
apply guard. Addresses Copilot review.
This commit is contained in:
Brooklyn Nicholson 2026-07-21 20:58:00 -05:00
parent 50170fdd2d
commit 300a0f1530
2 changed files with 20 additions and 11 deletions

View file

@ -45,13 +45,25 @@ describe('ingestBackendSkin', () => {
expect($pendingSkinApply.get()).toBe('forest')
})
it('treats default as no-opinion: never registers or applies it', () => {
it('never registers default in the backend store (desktop keeps its own palette)', () => {
ingestBackendSkin(skin('default'), { apply: true })
expect($pendingSkinApply.get()).toBeNull()
expect($backendThemes.get().default).toBeUndefined()
})
it('does not apply default on the connect-time seed', () => {
ingestBackendSkin(skin('default'), { apply: false })
expect($pendingSkinApply.get()).toBeNull()
})
it('applies a runtime switch back to default (repaints the desktop to its own default)', () => {
ingestBackendSkin(skin('neon'), { apply: false }) // gateway.ready seed on some skin
ingestBackendSkin(skin('default'), { apply: true }) // Hermes switched back to default
expect($pendingSkinApply.get()).toBe('default')
})
it('does not shadow a built-in name but can still apply it', () => {
ingestBackendSkin(skin('mono'), { apply: true })

View file

@ -53,17 +53,14 @@ export function ingestBackendSkin(skin: HermesSkin | undefined | null, { apply }
return
}
// `default` is "no opinion" — the desktop keeps its own default (nous). Record
// it as the baseline so a real skin authored later reads as a change.
if (name === 'default') {
lastSynced = 'default'
return
}
// `default` is "no opinion" on the PALETTE — the desktop keeps its own default
// (nous), so we never register a converted theme under `default`. It is still a
// valid apply TARGET though: a runtime switch back to `default` must repaint the
// desktop to its own default (setTheme normalizes `default` → nous). So we only
// skip the registry step here and let it flow through the apply logic below.
// Built-in names (mono/slate/…) already have a hand-tuned desktop palette — we
// never shadow it, but the name is still a valid apply target.
if (!BUILTIN_THEMES[name]) {
if (name !== 'default' && !BUILTIN_THEMES[name]) {
const theme = skinToDesktopTheme(skin as HermesSkin)
if (!theme) {