mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 01:47:24 +00:00
* feat(themes): make Match Terminal a real app theme painted at :root Match Terminal used to be an inline override on three left-column elements, so the tab bar, right sidebar, status bar, titlebar, menus and editors kept the built-in palette, and its fixed color mixes became unreadable on low-contrast palettes. It is now a light/dark app theme slot value synthesized from the active terminal palette (read through resolveTerminalPalette, so each mode's color overrides apply) and painted through the plugin theme pipeline (derivation, contrast gate, startup cache, Monaco theme). The document mode follows the terminal background, including onboarding's restore. The legacy leftSidebarAppearanceMode='match-terminal' value fills the empty slots on load and on writes (desktop and web), and it is kept mirrored while a slot matches the terminal so older Orca still matches its sidebar. The pickers mark the option with a muted "(dynamic)" label. * fix(onboarding): sync the painted-theme ref in an effect, not during render * feat(themes): use a lightning icon for Match Terminal's dynamic label * perf(themes): reuse the Match Terminal theme when its content is unchanged
168 lines
5.8 KiB
TypeScript
168 lines
5.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import type { GlobalSettings } from './global-settings-types'
|
|
import {
|
|
hasLegacyMatchTerminalSidebar,
|
|
legacyMatchTerminalSidebarWrite,
|
|
syncLegacyMatchTerminalSidebar,
|
|
normalizePluginThemeSettings,
|
|
pluginThemeSettingsAfterRemoval
|
|
} from './plugin-theme-settings'
|
|
|
|
const colors = { background: '#1b1430', foreground: '#e8e0ff', red: '#ff6b8b' }
|
|
|
|
describe('plugin theme settings', () => {
|
|
it('bounds what a client may write', () => {
|
|
expect(
|
|
normalizePluginThemeSettings({
|
|
pluginAppTheme: 'plugin:samples.dusk/dusk',
|
|
themeBeforePlugin: 'system',
|
|
terminalPluginPalettes: [
|
|
{ id: 'plugin:samples.dusk/dusk', colors },
|
|
{ id: 'plugin:samples.dusk/dusk', colors },
|
|
{ id: 'not-a-plugin', colors },
|
|
{ id: 'plugin:samples.other/a', colors: { background: 'red;' } },
|
|
{ id: 'plugin:samples.b/b', colors },
|
|
{ id: 'plugin:samples.c/c', colors }
|
|
]
|
|
})
|
|
).toEqual({
|
|
pluginAppTheme: 'plugin:samples.dusk/dusk',
|
|
themeBeforePlugin: 'system',
|
|
terminalPluginPalettes: [
|
|
{ id: 'plugin:samples.dusk/dusk', colors },
|
|
{ id: 'plugin:samples.b/b', colors }
|
|
]
|
|
})
|
|
expect(
|
|
normalizePluginThemeSettings({
|
|
pluginAppTheme: 'plugin:../x',
|
|
pluginAppThemeLight: 'plugin:samples.dawn/dawn',
|
|
pluginAppThemeDark: 'match-terminal',
|
|
pluginIconTheme: 'x'.repeat(10_000),
|
|
themeBeforePlugin: 'neon',
|
|
editorThemeLight: 'dark',
|
|
editorThemeDark: 'vs-dark'
|
|
})
|
|
).toEqual({
|
|
pluginAppTheme: null,
|
|
pluginAppThemeLight: 'plugin:samples.dawn/dawn',
|
|
pluginAppThemeDark: 'match-terminal',
|
|
editorThemeLight: 'dark',
|
|
editorThemeDark: null,
|
|
pluginIconTheme: null,
|
|
themeBeforePlugin: null
|
|
})
|
|
const unrelated: Partial<GlobalSettings> = { editorAutoSave: true }
|
|
expect(normalizePluginThemeSettings(unrelated)).toEqual({})
|
|
})
|
|
|
|
it('forgets an uninstalled plugin and restores the earlier Theme choice', () => {
|
|
expect(
|
|
pluginThemeSettingsAfterRemoval(
|
|
{
|
|
theme: 'dark',
|
|
themeBeforePlugin: 'system',
|
|
pluginAppTheme: 'plugin:samples.dusk/dusk',
|
|
pluginIconTheme: 'plugin:samples.dusk/icons',
|
|
terminalThemeDark: 'plugin:samples.dusk/dusk',
|
|
terminalThemeLight: 'Builtin Tango Light',
|
|
terminalPluginPalettes: [{ id: 'plugin:samples.dusk/dusk', colors }]
|
|
},
|
|
'samples.dusk'
|
|
)
|
|
).toEqual({
|
|
pluginAppTheme: null,
|
|
themeBeforePlugin: null,
|
|
theme: 'system',
|
|
pluginIconTheme: null,
|
|
terminalThemeDark: '',
|
|
terminalPluginPalettes: []
|
|
})
|
|
expect(
|
|
pluginThemeSettingsAfterRemoval(
|
|
{
|
|
theme: 'light',
|
|
pluginAppTheme: 'plugin:samples.dusk-two/dusk',
|
|
terminalThemeDark: '',
|
|
terminalThemeLight: ''
|
|
},
|
|
'samples.dusk'
|
|
)
|
|
).toEqual({})
|
|
})
|
|
|
|
it("clears only the removed plugin's light or dark slot", () => {
|
|
expect(
|
|
pluginThemeSettingsAfterRemoval(
|
|
{
|
|
theme: 'system',
|
|
pluginAppThemeLight: 'plugin:other.dawn/dawn',
|
|
pluginAppThemeDark: 'plugin:samples.dusk/dusk',
|
|
editorThemeLight: 'plugin:samples.dusk/dusk',
|
|
editorThemeDark: 'light',
|
|
terminalThemeDark: '',
|
|
terminalThemeLight: ''
|
|
},
|
|
'samples.dusk'
|
|
)
|
|
).toEqual({ pluginAppThemeDark: null, editorThemeLight: null })
|
|
})
|
|
|
|
it('moves the legacy Match Terminal sidebar into the empty app theme slots', () => {
|
|
const legacy = {
|
|
leftSidebarAppearanceMode: 'match-terminal' as const,
|
|
pluginAppThemeLight: null,
|
|
pluginAppThemeDark: 'plugin:samples.dusk/dusk' as const
|
|
}
|
|
expect(hasLegacyMatchTerminalSidebar(legacy)).toBe(true)
|
|
expect(syncLegacyMatchTerminalSidebar(legacy, true)).toEqual({
|
|
leftSidebarAppearanceMode: 'match-terminal',
|
|
pluginAppThemeLight: 'match-terminal',
|
|
pluginAppThemeDark: 'plugin:samples.dusk/dusk'
|
|
})
|
|
})
|
|
|
|
it('keeps the sidebar value in step with the slots so older Orca still matches the terminal', () => {
|
|
const current = {
|
|
leftSidebarAppearanceMode: 'match-terminal' as const,
|
|
pluginAppThemeLight: 'match-terminal' as const,
|
|
pluginAppThemeDark: 'match-terminal' as const
|
|
}
|
|
// Choosing Built-in for one mode keeps the mirror; for both, it clears it without refilling.
|
|
expect(legacyMatchTerminalSidebarWrite(current, { pluginAppThemeLight: null })).toMatchObject({
|
|
leftSidebarAppearanceMode: 'match-terminal',
|
|
pluginAppThemeLight: null
|
|
})
|
|
const builtIn = legacyMatchTerminalSidebarWrite(current, {
|
|
pluginAppThemeLight: null,
|
|
pluginAppThemeDark: null
|
|
})
|
|
expect(builtIn.leftSidebarAppearanceMode).toBe('default')
|
|
expect(hasLegacyMatchTerminalSidebar({ ...current, ...builtIn })).toBe(false)
|
|
// A full-settings write keeps the mirror beside the cleared slots; that is not legacy intent.
|
|
expect(
|
|
legacyMatchTerminalSidebarWrite(current, {
|
|
...current,
|
|
pluginAppThemeLight: null,
|
|
pluginAppThemeDark: null
|
|
})
|
|
).toMatchObject({
|
|
pluginAppThemeLight: null,
|
|
pluginAppThemeDark: null,
|
|
leftSidebarAppearanceMode: 'default'
|
|
})
|
|
expect(
|
|
legacyMatchTerminalSidebarWrite(
|
|
{ ...current, pluginAppThemeLight: null, pluginAppThemeDark: null },
|
|
{ leftSidebarAppearanceMode: 'match-terminal' }
|
|
)
|
|
).toMatchObject({ pluginAppThemeLight: 'match-terminal', pluginAppThemeDark: 'match-terminal' })
|
|
expect(
|
|
legacyMatchTerminalSidebarWrite(current, { leftSidebarAppearanceMode: 'tinted' })
|
|
).toMatchObject({
|
|
leftSidebarAppearanceMode: 'tinted'
|
|
})
|
|
expect(legacyMatchTerminalSidebarWrite(current, { editorAutoSave: true })).toEqual({})
|
|
})
|
|
})
|