style(tui): apply npm run fix

Run the TUI lint autofix and formatter on the PR branch after the sticky prompt and paste recovery changes.
This commit is contained in:
Brooklyn Nicholson 2026-04-28 22:18:26 -05:00
parent d7ae8dfd0a
commit f542d17b00
20 changed files with 60 additions and 52 deletions

View file

@ -714,9 +714,7 @@ describe('createGatewayEventHandler', () => {
} as any)
// Pre-interrupt todos should land in turn state.
expect(getTurnState().todos).toEqual([
{ content: 'pre-interrupt', id: 'todo-1', status: 'pending' }
])
expect(getTurnState().todos).toEqual([{ content: 'pre-interrupt', id: 'todo-1', status: 'pending' }])
turnController.interruptTurn({
appendMessage: (msg: Msg) => appended.push(msg),

View file

@ -141,6 +141,7 @@ describe('configureTerminalKeybindings', () => {
// it overlaps any context, including our terminal scope. We must NOT
// silently add a terminal-scoped cmd+c that would shadow it.
const mkdir = vi.fn().mockResolvedValue(undefined)
const readFile = vi.fn().mockResolvedValue(
JSON.stringify([
{
@ -149,6 +150,7 @@ describe('configureTerminalKeybindings', () => {
}
])
)
const writeFile = vi.fn().mockResolvedValue(undefined)
const copyFile = vi.fn().mockResolvedValue(undefined)
@ -170,6 +172,7 @@ describe('configureTerminalKeybindings', () => {
// would shadow ours. Treat as a conflict even though the strings
// aren't identical.
const mkdir = vi.fn().mockResolvedValue(undefined)
const readFile = vi.fn().mockResolvedValue(
JSON.stringify([
{
@ -179,6 +182,7 @@ describe('configureTerminalKeybindings', () => {
}
])
)
const writeFile = vi.fn().mockResolvedValue(undefined)
const copyFile = vi.fn().mockResolvedValue(undefined)
@ -198,6 +202,7 @@ describe('configureTerminalKeybindings', () => {
// logically disjoint from our copy-forwarding binding, which requires
// terminalTextSelected.
const mkdir = vi.fn().mockResolvedValue(undefined)
const readFile = vi.fn().mockResolvedValue(
JSON.stringify([
{
@ -208,6 +213,7 @@ describe('configureTerminalKeybindings', () => {
}
])
)
const writeFile = vi.fn().mockResolvedValue(undefined)
const copyFile = vi.fn().mockResolvedValue(undefined)
@ -226,6 +232,7 @@ describe('configureTerminalKeybindings', () => {
// clauses don't overlap. A user's pre-existing cmd+c binding scoped to
// editor focus should NOT block our terminal-scoped cmd+c binding.
const mkdir = vi.fn().mockResolvedValue(undefined)
const readFile = vi.fn().mockResolvedValue(
JSON.stringify([
{
@ -235,6 +242,7 @@ describe('configureTerminalKeybindings', () => {
}
])
)
const writeFile = vi.fn().mockResolvedValue(undefined)
const copyFile = vi.fn().mockResolvedValue(undefined)

View file

@ -16,14 +16,16 @@ const RELEVANT_ENV = [
'HERMES_TUI_THEME',
'HERMES_TUI_BACKGROUND',
'COLORFGBG',
'TERM_PROGRAM',
'TERM_PROGRAM'
] as const
async function importThemeWithCleanEnv() {
for (const key of RELEVANT_ENV) {
vi.stubEnv(key, '')
}
vi.resetModules()
return import('../theme.js')
}
@ -165,9 +167,7 @@ describe('detectLightMode', () => {
expect(detectLightMode({ TERM_PROGRAM: 'Apple_Terminal' }, allowList)).toBe(true)
// Dark COLORFGBG must beat the allow-list.
expect(
detectLightMode({ COLORFGBG: '15;0', TERM_PROGRAM: 'Apple_Terminal' }, allowList),
).toBe(false)
expect(detectLightMode({ COLORFGBG: '15;0', TERM_PROGRAM: 'Apple_Terminal' }, allowList)).toBe(false)
})
})

View file

@ -373,6 +373,7 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev:
// 120-char clip used for `gateway.stderr` activity entries.
const STDERR_LINE_CAP = 120
const STDERR_LINES_MAX = 8
const tailLines = (stderrTail ?? '')
.split('\n')
.map(l => l.trim())

View file

@ -290,21 +290,19 @@ export const sessionCommands: SlashCommand[] = [
return ctx.transcript.sys(`usage: /indicator [${INDICATOR_STYLES.join('|')}]`)
}
ctx.gateway
.rpc<ConfigSetResponse>('config.set', { key: 'indicator', value })
.then(
ctx.guarded<ConfigSetResponse>(r => {
if (!r.value) {
return
}
ctx.gateway.rpc<ConfigSetResponse>('config.set', { key: 'indicator', value }).then(
ctx.guarded<ConfigSetResponse>(r => {
if (!r.value) {
return
}
// Hot-swap the running TUI immediately so the next render
// uses the new style without waiting for the 5s mtime poll
// to re-apply config.full.
patchUiState({ indicatorStyle: value as IndicatorStyle })
ctx.transcript.sys(`indicator → ${r.value}`)
})
)
// Hot-swap the running TUI immediately so the next render
// uses the new style without waiting for the 5s mtime poll
// to re-apply config.full.
patchUiState({ indicatorStyle: value as IndicatorStyle })
ctx.transcript.sys(`indicator → ${r.value}`)
})
)
}
},

View file

@ -11,11 +11,11 @@ import type {
import { asRpcResult } from '../lib/rpc.js'
import {
type BusyInputMode,
DEFAULT_INDICATOR_STYLE,
INDICATOR_STYLES,
type BusyInputMode,
type IndicatorStyle,
type StatusBarMode,
type StatusBarMode
} from './interfaces.js'
import { turnController } from './turnController.js'
import { patchUiState } from './uiStore.js'

View file

@ -366,6 +366,7 @@ export function useInputHandlers(ctx: InputHandlerContext): InputHandlerResult {
if (isCtrl(key, ch, 'x') && cState.queueEditIdx !== null) {
cActions.removeQueue(cState.queueEditIdx)
return cActions.clearIn()
}
@ -393,6 +394,7 @@ export function useInputHandlers(ctx: InputHandlerContext): InputHandlerResult {
if (isAction(key, ch, 'l')) {
clearSelection()
forceRedraw(terminal.stdout ?? process.stdout)
return
}

View file

@ -227,6 +227,7 @@ export function useSubmission(opts: UseSubmissionOptions) {
(full: string, opts: { fallbackToFront?: boolean } = {}) => {
const live = getUiState()
const mode = live.busyInputMode
const fallback = (note: string) => {
if (opts.fallbackToFront) {
composerRefs.queueRef.current.unshift(full)
@ -234,6 +235,7 @@ export function useSubmission(opts: UseSubmissionOptions) {
} else {
composerActions.enqueue(full)
}
sys(note)
}
@ -350,17 +352,7 @@ export function useSubmission(opts: UseSubmissionOptions) {
send(full)
},
[
appendMessage,
composerActions,
composerRefs,
handleBusyInput,
interpolate,
send,
sendQueued,
shellExec,
slashRef
]
[appendMessage, composerActions, composerRefs, handleBusyInput, interpolate, send, sendQueued, shellExec, slashRef]
)
const submit = useCallback(

View file

@ -671,9 +671,7 @@ function DiffView({
<Text color={t.color.text}>
{diffMetricLine('duration', aTotals.totalDuration, bTotals.totalDuration, n => `${n.toFixed(1)}s`)}
</Text>
<Text color={t.color.text}>
{diffMetricLine('tokens', sumTokens(aTotals), sumTokens(bTotals), fmtTokens)}
</Text>
<Text color={t.color.text}>{diffMetricLine('tokens', sumTokens(aTotals), sumTokens(bTotals), fmtTokens)}</Text>
<Text color={t.color.text}>{diffMetricLine('cost', aTotals.costUsd, bTotals.costUsd, dollars)}</Text>
</Box>
</Box>

View file

@ -5,8 +5,8 @@ import unicodeSpinners from 'unicode-animations'
import { $delegationState } from '../app/delegationStore.js'
import type { IndicatorStyle } from '../app/interfaces.js'
import { $uiState } from '../app/uiStore.js'
import { useTurnSelector } from '../app/turnStore.js'
import { $uiState } from '../app/uiStore.js'
import { FACES } from '../content/faces.js'
import { VERBS } from '../content/verbs.js'
import { fmtDuration } from '../domain/messages.js'

View file

@ -224,7 +224,12 @@ const ComposerPane = memo(function ComposerPane({
</Box>
))}
<Box onMouseDown={captureInputDrag} onMouseDrag={dragFromPromptRow} onMouseUp={endInputDrag} position="relative">
<Box
onMouseDown={captureInputDrag}
onMouseDrag={dragFromPromptRow}
onMouseUp={endInputDrag}
position="relative"
>
<Box width={promptWidth}>
{sh ? (
<Text color={ui.theme.color.shellDollar}>{promptLabel}</Text>

View file

@ -133,7 +133,12 @@ export function SessionPicker({ gw, onCancel, onSelect, t }: SessionPickerProps)
</Text>
</Box>
<Text bold={selected} color={selected ? t.color.accent : t.color.muted} inverse={selected} wrap="truncate-end">
<Text
bold={selected}
color={selected ? t.color.accent : t.color.muted}
inverse={selected}
wrap="truncate-end"
>
{s.title || s.preview || '(untitled)'}
</Text>
</Box>

View file

@ -1,5 +1,5 @@
import { Box, NoSelect, Text } from '@hermes/ink'
import { memo, useEffect, useMemo, useState, type ReactNode } from 'react'
import { memo, type ReactNode, useEffect, useMemo, useState } from 'react'
import spinners, { type BrailleSpinnerName } from 'unicode-animations'
import { THINKING_COT_MAX } from '../config/limits.js'

View file

@ -21,14 +21,11 @@
* no effect.
*/
if (
process.env.HERMES_TUI_TRUECOLOR !== '0' &&
!process.env.NO_COLOR &&
!process.env.FORCE_COLOR
) {
if (process.env.HERMES_TUI_TRUECOLOR !== '0' && !process.env.NO_COLOR && !process.env.FORCE_COLOR) {
if (!process.env.COLORTERM) {
process.env.COLORTERM = 'truecolor'
}
process.env.FORCE_COLOR = '3'
}

View file

@ -336,6 +336,7 @@ export async function configureTerminalKeybindings(
}
const targets = targetBindings(platform)
const conflicts = targets.filter(target =>
keybindings.some(existing => isKeybinding(existing) && bindingsConflict(existing, target))
)

View file

@ -219,6 +219,7 @@ function backgroundLuminance(raw: string): null | number {
}
const hex = v.startsWith('#') ? v.slice(1) : v
const rgb = HEX_6_RE.test(hex)
? [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)]
: HEX_3_RE.test(hex)
@ -254,7 +255,7 @@ export function detectLightMode(
env: NodeJS.ProcessEnv = process.env,
// Injectable so tests can prove the COLORFGBG-over-TERM_PROGRAM
// precedence rule even though the production allow-list is empty.
lightDefaultTermPrograms: ReadonlySet<string> = LIGHT_DEFAULT_TERM_PROGRAMS,
lightDefaultTermPrograms: ReadonlySet<string> = LIGHT_DEFAULT_TERM_PROGRAMS
): boolean {
const lightFlag = (env.HERMES_TUI_LIGHT ?? '').trim().toLowerCase()