diff --git a/ui-tui/src/__tests__/loaders.test.ts b/ui-tui/src/__tests__/loaders.test.ts
index cc3d838a260..cab44b8d2b5 100644
--- a/ui-tui/src/__tests__/loaders.test.ts
+++ b/ui-tui/src/__tests__/loaders.test.ts
@@ -9,7 +9,11 @@ describe('ShimmerRows leniency (agent-authored calls)', () => {
const { createElement } = await import('react')
const { screen, height } = renderToScreen(
- createElement(ShimmerRows, { rows: 3, width: 20, t: { color: { completionBg: '#1a1a2e', label: '#DAA520', muted: '#B8860B' } } }),
+ createElement(ShimmerRows, {
+ rows: 3,
+ width: 20,
+ t: { color: { completionBg: '#1a1a2e', label: '#DAA520', muted: '#B8860B' } }
+ }),
30
)
diff --git a/ui-tui/src/__tests__/theme.test.ts b/ui-tui/src/__tests__/theme.test.ts
index c7a00c19632..98bfb060639 100644
--- a/ui-tui/src/__tests__/theme.test.ts
+++ b/ui-tui/src/__tests__/theme.test.ts
@@ -303,7 +303,10 @@ describe('fromSkin', () => {
})
it('keeps truecolor light Apple Terminal in truecolor (adapting, not ansi256-bucketing)', async () => {
- const { contrastRatio, fromSkin } = await importThemeWithEnv({ COLORTERM: 'truecolor', TERM_PROGRAM: 'Apple_Terminal' })
+ const { contrastRatio, fromSkin } = await importThemeWithEnv({
+ COLORTERM: 'truecolor',
+ TERM_PROGRAM: 'Apple_Terminal'
+ })
const theme = fromSkin({ banner_text: '#FFF8DC' }, {})
// No ansi256 bucketing on truecolor terminals — a truly invisible cream
@@ -418,10 +421,7 @@ describe('derived tone ladder', () => {
const { fromSkin } = await importThemeWithCleanEnv()
// A seeds-only skin (no dim/label/menu keys authored at all).
- const { color } = fromSkin(
- { banner_accent: '#DD4A3A', banner_text: '#F1E6CF', banner_title: '#C7A96B' },
- {}
- )
+ const { color } = fromSkin({ banner_accent: '#DD4A3A', banner_text: '#F1E6CF', banner_title: '#C7A96B' }, {})
// Muted recedes from THIS skin's text toward the background with an
// accent tint — a red-family derivative, never another skin's gold.
diff --git a/ui-tui/src/__tests__/widgetGrid.test.ts b/ui-tui/src/__tests__/widgetGrid.test.ts
index 3fed524b6e3..fb99e7c90f6 100644
--- a/ui-tui/src/__tests__/widgetGrid.test.ts
+++ b/ui-tui/src/__tests__/widgetGrid.test.ts
@@ -193,11 +193,7 @@ describe('layoutGridAreas', () => {
columns: 2,
gap: 0,
height: 12,
- items: [
- { col: 1, id: 'pinned', row: 2 },
- { id: 'auto-a' },
- { id: 'auto-b' }
- ],
+ items: [{ col: 1, id: 'pinned', row: 2 }, { id: 'auto-a' }, { id: 'auto-b' }],
rowGap: 0,
rows: 2,
width: 10
diff --git a/ui-tui/src/app/createGatewayEventHandler.ts b/ui-tui/src/app/createGatewayEventHandler.ts
index 1be2ea41598..320d7441064 100644
--- a/ui-tui/src/app/createGatewayEventHandler.ts
+++ b/ui-tui/src/app/createGatewayEventHandler.ts
@@ -47,10 +47,16 @@ const themeForSkin = (s: GatewaySkin) => {
// still works — it just overrides every key it lists.
const paired = detectLightMode() ? s.light_colors : s.dark_colors
- const colors =
- paired && Object.keys(paired).length ? { ...(s.colors ?? {}), ...paired } : (s.colors ?? {})
+ const colors = paired && Object.keys(paired).length ? { ...(s.colors ?? {}), ...paired } : (s.colors ?? {})
- return fromSkin(colors, s.branding ?? {}, s.banner_logo ?? '', s.banner_hero ?? '', s.tool_prefix ?? '', s.help_header ?? '')
+ return fromSkin(
+ colors,
+ s.branding ?? {},
+ s.banner_logo ?? '',
+ s.banner_hero ?? '',
+ s.tool_prefix ?? '',
+ s.help_header ?? ''
+ )
}
// Patch the live theme AND persist it for the next launch's first frame
@@ -95,7 +101,12 @@ const themesEqual = (a: Theme, b: Theme) => {
}
}
- return a.brand.name === b.brand.name && a.brand.prompt === b.brand.prompt && a.bannerLogo === b.bannerLogo && a.bannerHero === b.bannerHero
+ return (
+ a.brand.name === b.brand.name &&
+ a.brand.prompt === b.brand.prompt &&
+ a.bannerLogo === b.bannerLogo &&
+ a.bannerHero === b.bannerHero
+ )
}
const applySkin = (s: GatewaySkin) => {
diff --git a/ui-tui/src/components/appChrome.tsx b/ui-tui/src/components/appChrome.tsx
index 556770394d3..97994e82722 100644
--- a/ui-tui/src/components/appChrome.tsx
+++ b/ui-tui/src/components/appChrome.tsx
@@ -799,7 +799,9 @@ export function TranscriptScrollbar({ scrollRef, t }: TranscriptScrollbarProps)
{`${'┃\n'.repeat(Math.max(0, thumb - 1))}${thumb > 0 ? '┃' : ''}`}
) : null}
{vp - thumbTop - thumb > 0 ? (
- {`${'│\n'.repeat(Math.max(0, vp - thumbTop - thumb - 1))}${vp - thumbTop - thumb > 0 ? '│' : ''}`}
+ {`${'│\n'.repeat(Math.max(0, vp - thumbTop - thumb - 1))}${vp - thumbTop - thumb > 0 ? '│' : ''}`}
) : null}
>
)}
diff --git a/ui-tui/src/components/branding.tsx b/ui-tui/src/components/branding.tsx
index e8495f717e4..fcc5f6f130d 100644
--- a/ui-tui/src/components/branding.tsx
+++ b/ui-tui/src/components/branding.tsx
@@ -401,117 +401,115 @@ export function SessionPanel({ info, maxWidth, sid, t }: SessionPanelProps) {
const infoColumn = (
- {wide ? (
-
-
- {t.brand.name}
- {info.version ? ` v${info.version}` : ''}
- {info.release_date ? ` (${info.release_date})` : ''}
-
-
- ) : (
- // Narrow layout hides the hero column; surface model/cwd/session
- // here so they aren't lost.
-
-
- {info.model.split('/').pop()}
- · Nous Research
-
-
- {info.cwd || process.cwd()}
-
- {sid && (
-
- Session:
- {sid}
-
- )}
-
- )}
-
- {/* ── Tools (expanded by default) ── */}
-
- setToolsOpen(v => !v)} open={toolsOpen} t={t} title="Available Tools" />
- {toolsOpen && toolsBody()}
+ {wide ? (
+
+
+ {t.brand.name}
+ {info.version ? ` v${info.version}` : ''}
+ {info.release_date ? ` (${info.release_date})` : ''}
+
+ ) : (
+ // Narrow layout hides the hero column; surface model/cwd/session
+ // here so they aren't lost.
+
+
+ {info.model.split('/').pop()}
+ · Nous Research
+
+
+ {info.cwd || process.cwd()}
+
+ {sid && (
+
+ Session:
+ {sid}
+
+ )}
+
+ )}
- {/* ── Skills (collapsed by default) ── */}
+ {/* ── Tools (expanded by default) ── */}
+
+ setToolsOpen(v => !v)} open={toolsOpen} t={t} title="Available Tools" />
+ {toolsOpen && toolsBody()}
+
+
+ {/* ── Skills (collapsed by default) ── */}
+
+ setSkillsOpen(v => !v)}
+ open={skillsOpen}
+ suffix={skillsCatCount > 0 ? `in ${skillsCatCount} categor${skillsCatCount === 1 ? 'y' : 'ies'}` : undefined}
+ t={t}
+ title="Available Skills"
+ />
+ {skillsOpen && skillsBody()}
+
+
+ {/* ── System Prompt (collapsed by default) ── */}
+ {sysPromptLen > 0 && (
setSkillsOpen(v => !v)}
- open={skillsOpen}
- suffix={
- skillsCatCount > 0 ? `in ${skillsCatCount} categor${skillsCatCount === 1 ? 'y' : 'ies'}` : undefined
- }
+ onToggle={() => setSystemOpen(v => !v)}
+ open={systemOpen}
+ suffix={`— ${sysPromptLen.toLocaleString()} chars`}
t={t}
- title="Available Skills"
+ title="System Prompt"
/>
- {skillsOpen && skillsBody()}
+ {systemOpen && systemBody()}
+ )}
- {/* ── System Prompt (collapsed by default) ── */}
- {sysPromptLen > 0 && (
-
- setSystemOpen(v => !v)}
- open={systemOpen}
- suffix={`— ${sysPromptLen.toLocaleString()} chars`}
- t={t}
- title="System Prompt"
- />
- {systemOpen && systemBody()}
-
- )}
+ {/* ── MCP Servers (collapsed by default) ── */}
+ {mcpServers.length > 0 && (
+
+ setMcpOpen(v => !v)}
+ open={mcpOpen}
+ suffix="connected"
+ t={t}
+ title="MCP Servers"
+ />
+ {mcpOpen && mcpBody()}
+
+ )}
- {/* ── MCP Servers (collapsed by default) ── */}
- {mcpServers.length > 0 && (
-
- setMcpOpen(v => !v)}
- open={mcpOpen}
- suffix="connected"
- t={t}
- title="MCP Servers"
- />
- {mcpOpen && mcpBody()}
-
- )}
+
-
+
+ {/* Lazy boot: never print "0 tools · 0 skills" while counts load. */}
+ {info.lazy && !toolsTotal ? '… ' : `${toolsTotal} `}tools{' · '}
+ {info.lazy && !skillsTotal ? '… ' : `${skillsTotal} `}skills
+ {mcpConnected ? ` · ${mcpConnected} MCP` : ''}
+ {' · '}
+ /help for commands
+
-
- {/* Lazy boot: never print "0 tools · 0 skills" while counts load. */}
- {info.lazy && !toolsTotal ? '… ' : `${toolsTotal} `}tools{' · '}
- {info.lazy && !skillsTotal ? '… ' : `${skillsTotal} `}skills
- {mcpConnected ? ` · ${mcpConnected} MCP` : ''}
- {' · '}
- /help for commands
-
-
- {typeof info.update_behind === 'number' && info.update_behind > 0 && (
+ {typeof info.update_behind === 'number' && info.update_behind > 0 && (
+
+ ! {info.update_behind} {info.update_behind === 1 ? 'commit' : 'commits'} behind
+
+ {' '}
+ - run{' '}
+
- ! {info.update_behind} {info.update_behind === 1 ? 'commit' : 'commits'} behind
-
- {' '}
- - run{' '}
-
-
- {info.update_command || 'hermes update'}
-
-
- {' '}
- to update
-
+ {info.update_command || 'hermes update'}
- )}
+
+ {' '}
+ to update
+
+
+ )}
- {info.install_warning && (
-
- ! {info.install_warning}
-
- )}
+ {info.install_warning && (
+
+ ! {info.install_warning}
+
+ )}
)
diff --git a/ui-tui/src/components/gridStreamsDemo.tsx b/ui-tui/src/components/gridStreamsDemo.tsx
index a838666e4c3..ce07acb48a1 100644
--- a/ui-tui/src/components/gridStreamsDemo.tsx
+++ b/ui-tui/src/components/gridStreamsDemo.tsx
@@ -90,12 +90,13 @@ const sparkRows = (history: number[], width: number, rows: number): string[] =>
// ── stream panels ───────────────────────────────────────────────────────────
-const TOKEN_WORDS =
- (`Hermes streams tokens into the promoted cell while the grid reshapes around it. ` +
- `Cells are keyed by id, so promotion never resets a panel — history, cursors and ` +
- `tickers all survive the relayout. Row and column tracks re-solve to integer ` +
- `terminal cells on every change, spans bridge the gaps they cross, and dense ` +
- `auto-placement backfills the holes the promoted panel leaves behind. `).split(' ')
+const TOKEN_WORDS = (
+ `Hermes streams tokens into the promoted cell while the grid reshapes around it. ` +
+ `Cells are keyed by id, so promotion never resets a panel — history, cursors and ` +
+ `tickers all survive the relayout. Row and column tracks re-solve to integer ` +
+ `terminal cells on every change, spans bridge the gaps they cross, and dense ` +
+ `auto-placement backfills the holes the promoted panel leaves behind. `
+).split(' ')
function TokenStream({ height, t, width }: { height: number; t: Theme; width: number }) {
const tick = useTick(90)
diff --git a/ui-tui/src/components/gridTestOverlay.tsx b/ui-tui/src/components/gridTestOverlay.tsx
index b53ed031f67..9edab4af848 100644
--- a/ui-tui/src/components/gridTestOverlay.tsx
+++ b/ui-tui/src/components/gridTestOverlay.tsx
@@ -114,7 +114,7 @@ function AreasDemo({ cols, state, t }: { cols: number; state: GridTestState; t:
const rowSpanFirst = state.rows >= 2 ? 2 : 1
const colSpanSecond = state.cols >= 2 ? 2 : 1
- const extraSlots = (rowSpanFirst - 1) + (colSpanSecond - 1)
+ const extraSlots = rowSpanFirst - 1 + (colSpanSecond - 1)
const itemCount = Math.max(1, state.rows * state.cols - extraSlots)
const cursorInside = (cell: GridAreaCell) =>
@@ -126,7 +126,9 @@ function AreasDemo({ cols, state, t }: { cols: number; state: GridTestState; t:
const widgets: GridAreaWidget[] = Array.from({ length: itemCount }, (_, idx) => ({
colSpan: idx === 1 ? colSpanSecond : 1,
id: `area-c${idx + 1}`,
- render: (cell: GridAreaCell) => ,
+ render: (cell: GridAreaCell) => (
+
+ ),
rowSpan: idx === 0 ? rowSpanFirst : 1
}))
diff --git a/ui-tui/src/components/overlayPrimitives.tsx b/ui-tui/src/components/overlayPrimitives.tsx
index f86787f4ae4..b7cb7b60d98 100644
--- a/ui-tui/src/components/overlayPrimitives.tsx
+++ b/ui-tui/src/components/overlayPrimitives.tsx
@@ -116,7 +116,11 @@ export function MenuRow({ active, index, label, t }: { active: boolean; index: n
return (
-
+
{active ? '▸ ' : ' '}
{index}. {label}
diff --git a/ui-tui/src/components/textInput.tsx b/ui-tui/src/components/textInput.tsx
index c049ce078ff..c65fc8c5441 100644
--- a/ui-tui/src/components/textInput.tsx
+++ b/ui-tui/src/components/textInput.tsx
@@ -667,7 +667,9 @@ export function TextInput({
}
if (!display && placeholder) {
- return hintCursorCell(placeholder[0] ?? ' ', placeholderColor) + colorizeHint(placeholder.slice(1), placeholderColor)
+ return (
+ hintCursorCell(placeholder[0] ?? ' ', placeholderColor) + colorizeHint(placeholder.slice(1), placeholderColor)
+ )
}
if (selected) {
diff --git a/ui-tui/src/components/widgetGrid.tsx b/ui-tui/src/components/widgetGrid.tsx
index 6867210d697..0c34c791534 100644
--- a/ui-tui/src/components/widgetGrid.tsx
+++ b/ui-tui/src/components/widgetGrid.tsx
@@ -255,17 +255,12 @@ export const GridAreas = memo(function GridAreas({
const AreaCell = memo(function AreaCell({ cell, widget }: { cell: GridAreaCell; widget?: GridAreaWidget }) {
const node =
- widget?.render?.(cell) ?? (typeof widget?.children === 'function' ? widget.children(cell) : widget?.children) ?? null
+ widget?.render?.(cell) ??
+ (typeof widget?.children === 'function' ? widget.children(cell) : widget?.children) ??
+ null
return (
-
+
{node}
)
diff --git a/ui-tui/src/lib/color.ts b/ui-tui/src/lib/color.ts
index 3d7bd080844..d051934cfd8 100644
--- a/ui-tui/src/lib/color.ts
+++ b/ui-tui/src/lib/color.ts
@@ -46,8 +46,7 @@ export function parseColor(input: string): Rgb | null {
return null
}
-export const toHex = (rgb: Rgb): string =>
- '#' + rgb.map(c => clampChannel(c).toString(16).padStart(2, '0')).join('')
+export const toHex = (rgb: Rgb): string => '#' + rgb.map(c => clampChannel(c).toString(16).padStart(2, '0')).join('')
/** sRGB lerp `a → b` by `t` in [0,1]. Unparseable inputs return `a` unchanged. */
export function mix(a: string, b: string, t: number): string {
@@ -71,7 +70,9 @@ function channelLuminance(value: number): number {
export function relativeLuminance(color: string): null | number {
const rgb = parseColor(color)
- return rgb ? 0.2126 * channelLuminance(rgb[0]) + 0.7152 * channelLuminance(rgb[1]) + 0.0722 * channelLuminance(rgb[2]) : null
+ return rgb
+ ? 0.2126 * channelLuminance(rgb[0]) + 0.7152 * channelLuminance(rgb[1]) + 0.0722 * channelLuminance(rgb[2])
+ : null
}
/** WCAG contrast ratio between two colors (1–21). Null when unparseable. */
diff --git a/ui-tui/src/lib/widgetGrid.ts b/ui-tui/src/lib/widgetGrid.ts
index ae891eb857f..ee4fb09edad 100644
--- a/ui-tui/src/lib/widgetGrid.ts
+++ b/ui-tui/src/lib/widgetGrid.ts
@@ -52,7 +52,11 @@ const columnCountForWidth = (width: number, minColumnWidth: number, gap: number,
}
const buildColumnWidths = (width: number, columnCount: number, gap: number) =>
- resolveGridTracks(width, gap, Array.from({ length: Math.max(1, toInt(columnCount, 1)) }, () => ({ fr: 1 })))
+ resolveGridTracks(
+ width,
+ gap,
+ Array.from({ length: Math.max(1, toInt(columnCount, 1)) }, () => ({ fr: 1 }))
+ )
const spanWidth = (columns: number[], colStart: number, span: number, gap: number) => {
const end = Math.min(columns.length, colStart + span)
@@ -229,7 +233,10 @@ export function resolveGridTracks(total: number, gap: number, tracks: GridTrackS
})
while (unpinned.length) {
- const shares = distributeByWeight(Math.max(0, remaining), unpinned.map(idx => trackFr(tracks[idx]!)))
+ const shares = distributeByWeight(
+ Math.max(0, remaining),
+ unpinned.map(idx => trackFr(tracks[idx]!))
+ )
const violating = unpinned.filter((idx, i) => shares[i]! < trackMin(tracks[idx]!))
if (!violating.length) {
@@ -467,10 +474,7 @@ export function layoutGridAreas({
const explicitRowTracks = rows === undefined ? [] : normalizeTracks(rows, 1)
const rowCount = Math.max(1, explicitRowTracks.length, placedRowCount)
- const rowTracks: GridTrackSize[] = Array.from(
- { length: rowCount },
- (_, idx) => explicitRowTracks[idx] ?? { fr: 1 }
- )
+ const rowTracks: GridTrackSize[] = Array.from({ length: rowCount }, (_, idx) => explicitRowTracks[idx] ?? { fr: 1 })
const columnSizes = resolveGridTracks(safeWidth, safeGap, columnTracks)
const rowSizes = resolveGridTracks(safeHeight, safeRowGap, rowTracks)
diff --git a/ui-tui/src/theme.ts b/ui-tui/src/theme.ts
index 28ce5c0f14d..fa28d8ffd5b 100644
--- a/ui-tui/src/theme.ts
+++ b/ui-tui/src/theme.ts
@@ -580,8 +580,7 @@ export function deriveTones(seeds: {
statusFg: grayOf(mix(text, bg, isLight ? 0.3 : 0.24)),
surface,
activeRow: mix(surface, accent, 0.25),
- selection:
- isLight && seeds.shellDollar ? mix(bg, seeds.shellDollar, 0.2) : mix(surface, accent, 0.28),
+ selection: isLight && seeds.shellDollar ? mix(bg, seeds.shellDollar, 0.2) : mix(surface, accent, 0.28),
border: mix(accent, bg, 0.25)
}
}