fmt(js): npm run fix on merge (#68976)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
hermes-seaeye[bot] 2026-07-21 22:59:05 +00:00 committed by GitHub
parent e0e15436ee
commit 96ba7ec529
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 162 additions and 143 deletions

View file

@ -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
)

View file

@ -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.

View file

@ -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

View file

@ -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) => {

View file

@ -799,7 +799,9 @@ export function TranscriptScrollbar({ scrollRef, t }: TranscriptScrollbarProps)
<Text color={thumbColor}>{`${'┃\n'.repeat(Math.max(0, thumb - 1))}${thumb > 0 ? '┃' : ''}`}</Text>
) : null}
{vp - thumbTop - thumb > 0 ? (
<Text color={trackColor}>{`${'│\n'.repeat(Math.max(0, vp - thumbTop - thumb - 1))}${vp - thumbTop - thumb > 0 ? '│' : ''}`}</Text>
<Text
color={trackColor}
>{`${'│\n'.repeat(Math.max(0, vp - thumbTop - thumb - 1))}${vp - thumbTop - thumb > 0 ? '│' : ''}`}</Text>
) : null}
</>
)}

View file

@ -401,117 +401,115 @@ export function SessionPanel({ info, maxWidth, sid, t }: SessionPanelProps) {
const infoColumn = (
<Box flexDirection="column" width="100%">
{wide ? (
<Box justifyContent="center" marginBottom={1}>
<Text bold color={t.color.primary}>
{t.brand.name}
{info.version ? ` v${info.version}` : ''}
{info.release_date ? ` (${info.release_date})` : ''}
</Text>
</Box>
) : (
// Narrow layout hides the hero column; surface model/cwd/session
// here so they aren't lost.
<Box flexDirection="column" marginBottom={1}>
<Text color={t.color.accent} wrap="truncate-end">
{info.model.split('/').pop()}
<Text color={t.color.muted}> · Nous Research</Text>
</Text>
<Text color={t.color.muted} wrap="truncate-end">
{info.cwd || process.cwd()}
</Text>
{sid && (
<Text wrap="truncate-end">
<Text color={t.color.sessionLabel}>Session: </Text>
<Text color={t.color.sessionBorder}>{sid}</Text>
</Text>
)}
</Box>
)}
{/* ── Tools (expanded by default) ── */}
<Box flexDirection="column" marginTop={1}>
<CollapseToggle onToggle={() => setToolsOpen(v => !v)} open={toolsOpen} t={t} title="Available Tools" />
{toolsOpen && toolsBody()}
{wide ? (
<Box justifyContent="center" marginBottom={1}>
<Text bold color={t.color.primary}>
{t.brand.name}
{info.version ? ` v${info.version}` : ''}
{info.release_date ? ` (${info.release_date})` : ''}
</Text>
</Box>
) : (
// Narrow layout hides the hero column; surface model/cwd/session
// here so they aren't lost.
<Box flexDirection="column" marginBottom={1}>
<Text color={t.color.accent} wrap="truncate-end">
{info.model.split('/').pop()}
<Text color={t.color.muted}> · Nous Research</Text>
</Text>
<Text color={t.color.muted} wrap="truncate-end">
{info.cwd || process.cwd()}
</Text>
{sid && (
<Text wrap="truncate-end">
<Text color={t.color.sessionLabel}>Session: </Text>
<Text color={t.color.sessionBorder}>{sid}</Text>
</Text>
)}
</Box>
)}
{/* ── Skills (collapsed by default) ── */}
{/* ── Tools (expanded by default) ── */}
<Box flexDirection="column" marginTop={1}>
<CollapseToggle onToggle={() => setToolsOpen(v => !v)} open={toolsOpen} t={t} title="Available Tools" />
{toolsOpen && toolsBody()}
</Box>
{/* ── Skills (collapsed by default) ── */}
<Box flexDirection="column" marginTop={1}>
<CollapseToggle
count={skillsTotal}
onToggle={() => setSkillsOpen(v => !v)}
open={skillsOpen}
suffix={skillsCatCount > 0 ? `in ${skillsCatCount} categor${skillsCatCount === 1 ? 'y' : 'ies'}` : undefined}
t={t}
title="Available Skills"
/>
{skillsOpen && skillsBody()}
</Box>
{/* ── System Prompt (collapsed by default) ── */}
{sysPromptLen > 0 && (
<Box flexDirection="column" marginTop={1}>
<CollapseToggle
count={skillsTotal}
onToggle={() => 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()}
</Box>
)}
{/* ── System Prompt (collapsed by default) ── */}
{sysPromptLen > 0 && (
<Box flexDirection="column" marginTop={1}>
<CollapseToggle
onToggle={() => setSystemOpen(v => !v)}
open={systemOpen}
suffix={`${sysPromptLen.toLocaleString()} chars`}
t={t}
title="System Prompt"
/>
{systemOpen && systemBody()}
</Box>
)}
{/* ── MCP Servers (collapsed by default) ── */}
{mcpServers.length > 0 && (
<Box flexDirection="column" marginTop={1}>
<CollapseToggle
count={mcpConnected}
onToggle={() => setMcpOpen(v => !v)}
open={mcpOpen}
suffix="connected"
t={t}
title="MCP Servers"
/>
{mcpOpen && mcpBody()}
</Box>
)}
{/* ── MCP Servers (collapsed by default) ── */}
{mcpServers.length > 0 && (
<Box flexDirection="column" marginTop={1}>
<CollapseToggle
count={mcpConnected}
onToggle={() => setMcpOpen(v => !v)}
open={mcpOpen}
suffix="connected"
t={t}
title="MCP Servers"
/>
{mcpOpen && mcpBody()}
</Box>
)}
<Text />
<Text />
<Text color={t.color.text}>
{/* Lazy boot: never print "0 tools · 0 skills" while counts load. */}
{info.lazy && !toolsTotal ? '… ' : `${toolsTotal} `}tools{' · '}
{info.lazy && !skillsTotal ? '… ' : `${skillsTotal} `}skills
{mcpConnected ? ` · ${mcpConnected} MCP` : ''}
{' · '}
<Text color={t.color.muted}>/help for commands</Text>
</Text>
<Text color={t.color.text}>
{/* Lazy boot: never print "0 tools · 0 skills" while counts load. */}
{info.lazy && !toolsTotal ? '… ' : `${toolsTotal} `}tools{' · '}
{info.lazy && !skillsTotal ? '… ' : `${skillsTotal} `}skills
{mcpConnected ? ` · ${mcpConnected} MCP` : ''}
{' · '}
<Text color={t.color.muted}>/help for commands</Text>
</Text>
{typeof info.update_behind === 'number' && info.update_behind > 0 && (
{typeof info.update_behind === 'number' && info.update_behind > 0 && (
<Text bold color={t.color.warn}>
! {info.update_behind} {info.update_behind === 1 ? 'commit' : 'commits'} behind
<Text bold={false} color={t.color.warn} dimColor>
{' '}
- run{' '}
</Text>
<Text bold color={t.color.warn}>
! {info.update_behind} {info.update_behind === 1 ? 'commit' : 'commits'} behind
<Text bold={false} color={t.color.warn} dimColor>
{' '}
- run{' '}
</Text>
<Text bold color={t.color.warn}>
{info.update_command || 'hermes update'}
</Text>
<Text bold={false} color={t.color.warn} dimColor>
{' '}
to update
</Text>
{info.update_command || 'hermes update'}
</Text>
)}
<Text bold={false} color={t.color.warn} dimColor>
{' '}
to update
</Text>
</Text>
)}
{info.install_warning && (
<Text bold color={t.color.warn} wrap="wrap">
! {info.install_warning}
</Text>
)}
{info.install_warning && (
<Text bold color={t.color.warn} wrap="wrap">
! {info.install_warning}
</Text>
)}
</Box>
)

View file

@ -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)

View file

@ -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) => <AreaDemoCell active={cursorInside(cell)} cell={cell} label={`c${idx + 1}`} t={t} />,
render: (cell: GridAreaCell) => (
<AreaDemoCell active={cursorInside(cell)} cell={cell} label={`c${idx + 1}`} t={t} />
),
rowSpan: idx === 0 ? rowSpanFirst : 1
}))

View file

@ -116,7 +116,11 @@ export function MenuRow({ active, index, label, t }: { active: boolean; index: n
return (
<Text>
<Text backgroundColor={row.backgroundColor} bold={active} color={active ? (row.color ?? t.color.label) : t.color.muted}>
<Text
backgroundColor={row.backgroundColor}
bold={active}
color={active ? (row.color ?? t.color.label) : t.color.muted}
>
{active ? '▸ ' : ' '}
{index}. {label}
</Text>

View file

@ -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) {

View file

@ -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 (
<Box
height={cell.height}
left={cell.x}
overflow="hidden"
position="absolute"
top={cell.y}
width={cell.width}
>
<Box height={cell.height} left={cell.x} overflow="hidden" position="absolute" top={cell.y} width={cell.width}>
{node}
</Box>
)

View file

@ -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 (121). Null when unparseable. */

View file

@ -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)

View file

@ -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)
}
}