mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-26 11:12:03 +00:00
Imported VS Code themes now carry their integrated-terminal ANSI palette (`terminal.ansi*`), keyed to the painted variant (terminal / darkTerminal). The terminal adopts it when the full base-8 set is present and keeps its VS Code defaults otherwise; withSurface still owns the background, so the pane stays translucent. Pull the command palette and session switcher into a shared top-center HUD (`floating-hud.ts`): no dim/blur backdrop, one compact text + item-padding size, sidebar-label-style section headers (brand-tinted, uppercase), and the themed portal scrollbar.
171 lines
6 KiB
TypeScript
171 lines
6 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { contrastRatio } from './color'
|
|
import { convertVscodeColorTheme, parseVscodeTheme, vscodeThemeSlug } from './vscode'
|
|
|
|
describe('vscodeThemeSlug', () => {
|
|
it('namespaces, lowercases, and dashes', () => {
|
|
expect(vscodeThemeSlug('Dracula Soft')).toBe('vsc-dracula-soft')
|
|
expect(vscodeThemeSlug(' One Dark Pro!! ')).toBe('vsc-one-dark-pro')
|
|
})
|
|
|
|
it('falls back when the name has no usable characters', () => {
|
|
expect(vscodeThemeSlug('—')).toBe('vsc-theme')
|
|
})
|
|
})
|
|
|
|
describe('parseVscodeTheme (JSONC tolerance)', () => {
|
|
it('strips comments and trailing commas', () => {
|
|
const text = `{
|
|
// a line comment
|
|
"name": "Demo",
|
|
/* block comment */
|
|
"type": "dark",
|
|
"colors": {
|
|
"editor.background": "#1e1e2e", // inline
|
|
},
|
|
}`
|
|
|
|
const parsed = parseVscodeTheme(text)
|
|
expect(parsed.name).toBe('Demo')
|
|
expect(parsed.colors?.['editor.background']).toBe('#1e1e2e')
|
|
})
|
|
|
|
it('throws on a non-object', () => {
|
|
expect(() => parseVscodeTheme('42')).toThrow()
|
|
})
|
|
})
|
|
|
|
describe('convertVscodeColorTheme', () => {
|
|
const dracula = {
|
|
name: 'Dracula',
|
|
type: 'dark',
|
|
colors: {
|
|
'editor.background': '#282a36',
|
|
'editor.foreground': '#f8f8f2',
|
|
focusBorder: '#6272a4',
|
|
'editorWidget.background': '#21222c',
|
|
'sideBar.background': '#21222c',
|
|
errorForeground: '#ff5555',
|
|
// 8-digit hex (alpha) — must flatten over the background.
|
|
'panel.border': '#bd93f900'
|
|
}
|
|
}
|
|
|
|
it('maps the load-bearing tokens onto the palette', () => {
|
|
const { theme } = convertVscodeColorTheme(dracula, { source: 'dracula-theme.theme-dracula' })
|
|
|
|
expect(theme.name).toBe('vsc-dracula')
|
|
expect(theme.label).toBe('Dracula')
|
|
expect(theme.description).toContain('dracula-theme.theme-dracula')
|
|
expect(theme.colors.background).toBe('#282a36')
|
|
expect(theme.colors.foreground).toBe('#f8f8f2')
|
|
// One accent drives primary + ring + midground together...
|
|
expect(theme.colors.ring).toBe(theme.colors.primary)
|
|
expect(theme.colors.midground).toBe(theme.colors.primary)
|
|
// ...and it's nudged until it reads on the sidebar it labels (the dim
|
|
// focusBorder #6272a4 sits below AA, so it's lifted).
|
|
expect(contrastRatio(theme.colors.primary, theme.colors.sidebarBackground!)).toBeGreaterThanOrEqual(4.5)
|
|
expect(theme.colors.popover).toBe('#21222c')
|
|
expect(theme.colors.sidebarBackground).toBe('#21222c')
|
|
expect(theme.colors.destructive).toBe('#ff5555')
|
|
})
|
|
|
|
it('flattens alpha hex over the background (no #rrggbbaa leaks)', () => {
|
|
const { theme } = convertVscodeColorTheme(dracula)
|
|
expect(theme.colors.border).toMatch(/^#[0-9a-f]{6}$/)
|
|
// 00 alpha over the bg means the border collapses to the background.
|
|
expect(theme.colors.border).toBe('#282a36')
|
|
})
|
|
|
|
it('renders identically in both modes (single palette in both slots)', () => {
|
|
const { theme } = convertVscodeColorTheme(dracula)
|
|
expect(theme.darkColors).toBe(theme.colors)
|
|
})
|
|
|
|
it('records derived fallbacks for omitted tokens', () => {
|
|
const { derived } = convertVscodeColorTheme({
|
|
name: 'Sparse',
|
|
type: 'dark',
|
|
colors: { 'editor.background': '#101010', 'editor.foreground': '#fafafa' }
|
|
})
|
|
|
|
// No accent/elevated/sidebar/error tokens → all derived. The accent records
|
|
// its first candidate (button.background) when none of the family is present.
|
|
expect(derived).toContain('button.background')
|
|
expect(derived).toContain('editorWidget.background')
|
|
expect(derived).toContain('editorError.foreground')
|
|
})
|
|
|
|
it('buckets light vs dark from background luminance when type is absent', () => {
|
|
const light = convertVscodeColorTheme({
|
|
name: 'Bright',
|
|
colors: { 'editor.background': '#ffffff', 'editor.foreground': '#1a1a1a' }
|
|
}).theme
|
|
|
|
// A light background should keep a near-white background, not synth dark.
|
|
expect(light.colors.background).toBe('#ffffff')
|
|
})
|
|
|
|
it('throws when there is no colors map', () => {
|
|
expect(() => convertVscodeColorTheme({ name: 'Empty' })).toThrow(/colors/)
|
|
})
|
|
|
|
const fullAnsi = {
|
|
'terminal.ansiBlack': '#073642',
|
|
'terminal.ansiRed': '#dc322f',
|
|
'terminal.ansiGreen': '#859900',
|
|
'terminal.ansiYellow': '#b58900',
|
|
'terminal.ansiBlue': '#268bd2',
|
|
'terminal.ansiMagenta': '#d33682',
|
|
'terminal.ansiCyan': '#2aa198',
|
|
'terminal.ansiWhite': '#eee8d5',
|
|
'terminal.ansiBrightBlack': '#002b36',
|
|
'terminal.ansiBrightRed': '#cb4b16',
|
|
'terminal.ansiBrightGreen': '#586e75',
|
|
'terminal.ansiBrightYellow': '#657b83',
|
|
'terminal.ansiBrightBlue': '#839496',
|
|
'terminal.ansiBrightMagenta': '#6c71c4',
|
|
'terminal.ansiBrightCyan': '#93a1a1',
|
|
'terminal.ansiBrightWhite': '#fdf6e3'
|
|
}
|
|
|
|
it('lifts the ANSI palette when the full base-8 set is present', () => {
|
|
const { theme } = convertVscodeColorTheme({
|
|
name: 'Solarized Dark',
|
|
type: 'dark',
|
|
colors: {
|
|
'editor.background': '#002b36',
|
|
'editor.foreground': '#93a1a1',
|
|
'terminal.foreground': '#839496',
|
|
'terminalCursor.foreground': '#93a1a1',
|
|
// Alpha selection must survive un-flattened — xterm blends it.
|
|
'terminal.selectionBackground': '#073642aa',
|
|
...fullAnsi
|
|
}
|
|
})
|
|
|
|
expect(theme.terminal?.red).toBe('#dc322f')
|
|
expect(theme.terminal?.brightWhite).toBe('#fdf6e3')
|
|
expect(theme.terminal?.foreground).toBe('#839496')
|
|
expect(theme.terminal?.cursor).toBe('#93a1a1')
|
|
expect(theme.terminal?.selectionBackground).toBe('#073642aa')
|
|
// No background slot — the pane keeps the live surface (transparency).
|
|
expect('background' in (theme.terminal ?? {})).toBe(false)
|
|
})
|
|
|
|
it('keeps the default palette (no terminal slot) when the ANSI set is partial', () => {
|
|
const { theme } = convertVscodeColorTheme({
|
|
name: 'Half',
|
|
type: 'dark',
|
|
colors: {
|
|
'editor.background': '#101010',
|
|
'editor.foreground': '#fafafa',
|
|
'terminal.ansiRed': '#ff0000',
|
|
'terminal.ansiGreen': '#00ff00'
|
|
}
|
|
})
|
|
|
|
expect(theme.terminal).toBeUndefined()
|
|
})
|
|
})
|