From f542d17b0040cf28e388a3e935d36e14de9801a0 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 28 Apr 2026 22:18:26 -0500 Subject: [PATCH] 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. --- ui-tui/packages/hermes-ink/index.d.ts | 2 +- .../packages/hermes-ink/src/entry-exports.ts | 2 +- .../hermes-ink/src/ink/components/App.tsx | 6 ++--- ui-tui/packages/hermes-ink/src/ink/root.ts | 2 ++ .../createGatewayEventHandler.test.ts | 4 +-- ui-tui/src/__tests__/terminalSetup.test.ts | 8 ++++++ ui-tui/src/__tests__/theme.test.ts | 8 +++--- ui-tui/src/app/createGatewayEventHandler.ts | 1 + ui-tui/src/app/slash/commands/session.ts | 26 +++++++++---------- ui-tui/src/app/useConfigSync.ts | 4 +-- ui-tui/src/app/useInputHandlers.ts | 2 ++ ui-tui/src/app/useSubmission.ts | 14 +++------- ui-tui/src/components/agentsOverlay.tsx | 4 +-- ui-tui/src/components/appChrome.tsx | 2 +- ui-tui/src/components/appLayout.tsx | 7 ++++- ui-tui/src/components/sessionPicker.tsx | 7 ++++- ui-tui/src/components/thinking.tsx | 2 +- ui-tui/src/lib/forceTruecolor.ts | 7 ++--- ui-tui/src/lib/terminalSetup.ts | 1 + ui-tui/src/theme.ts | 3 ++- 20 files changed, 60 insertions(+), 52 deletions(-) diff --git a/ui-tui/packages/hermes-ink/index.d.ts b/ui-tui/packages/hermes-ink/index.d.ts index 92aeb0a4f2..637c4bb43b 100644 --- a/ui-tui/packages/hermes-ink/index.d.ts +++ b/ui-tui/packages/hermes-ink/index.d.ts @@ -30,7 +30,7 @@ export { useTerminalFocus } from './src/ink/hooks/use-terminal-focus.ts' export { useTerminalTitle } from './src/ink/hooks/use-terminal-title.ts' export { useTerminalViewport } from './src/ink/hooks/use-terminal-viewport.ts' export { default as measureElement } from './src/ink/measure-element.ts' -export { createRoot, default as render, forceRedraw, renderSync } from './src/ink/root.ts' +export { createRoot, forceRedraw, default as render, renderSync } from './src/ink/root.ts' export type { Instance, RenderOptions, Root } from './src/ink/root.ts' export { stringWidth } from './src/ink/stringWidth.ts' export { default as TextInput, UncontrolledTextInput } from 'ink-text-input' diff --git a/ui-tui/packages/hermes-ink/src/entry-exports.ts b/ui-tui/packages/hermes-ink/src/entry-exports.ts index 6bfb0e4955..355faa16f9 100644 --- a/ui-tui/packages/hermes-ink/src/entry-exports.ts +++ b/ui-tui/packages/hermes-ink/src/entry-exports.ts @@ -23,7 +23,7 @@ export { useTerminalTitle } from './ink/hooks/use-terminal-title.js' export { useTerminalViewport } from './ink/hooks/use-terminal-viewport.js' export { default as measureElement } from './ink/measure-element.js' export { scrollFastPathStats, type ScrollFastPathStats } from './ink/render-node-to-output.js' -export { createRoot, default as render, forceRedraw, renderSync } from './ink/root.js' +export { createRoot, forceRedraw, default as render, renderSync } from './ink/root.js' export { stringWidth } from './ink/stringWidth.js' export { isXtermJs } from './ink/terminal.js' export { default as TextInput, UncontrolledTextInput } from 'ink-text-input' diff --git a/ui-tui/packages/hermes-ink/src/ink/components/App.tsx b/ui-tui/packages/hermes-ink/src/ink/components/App.tsx index 1d238b40f7..a8984e13ca 100644 --- a/ui-tui/packages/hermes-ink/src/ink/components/App.tsx +++ b/ui-tui/packages/hermes-ink/src/ink/components/App.tsx @@ -11,13 +11,13 @@ import { InputEvent } from '../events/input-event.js' import { TerminalFocusEvent } from '../events/terminal-focus-event.js' import { INITIAL_STATE, - parseMultipleKeypresses, type ParsedInput, type ParsedKey, - type ParsedMouse + type ParsedMouse, + parseMultipleKeypresses } from '../parse-keypress.js' import reconciler from '../reconciler.js' -import { finishSelection, hasSelection, startSelection, type SelectionState } from '../selection.js' +import { finishSelection, hasSelection, type SelectionState, startSelection } from '../selection.js' import { getTerminalFocused, setTerminalFocused } from '../terminal-focus-state.js' import { TerminalQuerier, xtversion } from '../terminal-querier.js' import { isXtermJs, setXtversionName, supportsExtendedKeys } from '../terminal.js' diff --git a/ui-tui/packages/hermes-ink/src/ink/root.ts b/ui-tui/packages/hermes-ink/src/ink/root.ts index fdfb97a6e7..1d7af3803b 100644 --- a/ui-tui/packages/hermes-ink/src/ink/root.ts +++ b/ui-tui/packages/hermes-ink/src/ink/root.ts @@ -75,11 +75,13 @@ export type Root = { export const forceRedraw = (stdout: NodeJS.WriteStream = process.stdout): boolean => { const instance = instances.get(stdout) + if (!instance) { return false } instance.forceRedraw() + return true } diff --git a/ui-tui/src/__tests__/createGatewayEventHandler.test.ts b/ui-tui/src/__tests__/createGatewayEventHandler.test.ts index f2326dc5ac..441caf6076 100644 --- a/ui-tui/src/__tests__/createGatewayEventHandler.test.ts +++ b/ui-tui/src/__tests__/createGatewayEventHandler.test.ts @@ -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), diff --git a/ui-tui/src/__tests__/terminalSetup.test.ts b/ui-tui/src/__tests__/terminalSetup.test.ts index 5508e94ce1..59e725e988 100644 --- a/ui-tui/src/__tests__/terminalSetup.test.ts +++ b/ui-tui/src/__tests__/terminalSetup.test.ts @@ -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) diff --git a/ui-tui/src/__tests__/theme.test.ts b/ui-tui/src/__tests__/theme.test.ts index e3e3cea889..888bd9142a 100644 --- a/ui-tui/src/__tests__/theme.test.ts +++ b/ui-tui/src/__tests__/theme.test.ts @@ -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) }) }) diff --git a/ui-tui/src/app/createGatewayEventHandler.ts b/ui-tui/src/app/createGatewayEventHandler.ts index 3abfc18561..8018623631 100644 --- a/ui-tui/src/app/createGatewayEventHandler.ts +++ b/ui-tui/src/app/createGatewayEventHandler.ts @@ -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()) diff --git a/ui-tui/src/app/slash/commands/session.ts b/ui-tui/src/app/slash/commands/session.ts index 5acd417de2..ecd1b7866f 100644 --- a/ui-tui/src/app/slash/commands/session.ts +++ b/ui-tui/src/app/slash/commands/session.ts @@ -290,21 +290,19 @@ export const sessionCommands: SlashCommand[] = [ return ctx.transcript.sys(`usage: /indicator [${INDICATOR_STYLES.join('|')}]`) } - ctx.gateway - .rpc('config.set', { key: 'indicator', value }) - .then( - ctx.guarded(r => { - if (!r.value) { - return - } + ctx.gateway.rpc('config.set', { key: 'indicator', value }).then( + ctx.guarded(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}`) + }) + ) } }, diff --git a/ui-tui/src/app/useConfigSync.ts b/ui-tui/src/app/useConfigSync.ts index 1b9d930b53..8695855759 100644 --- a/ui-tui/src/app/useConfigSync.ts +++ b/ui-tui/src/app/useConfigSync.ts @@ -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' diff --git a/ui-tui/src/app/useInputHandlers.ts b/ui-tui/src/app/useInputHandlers.ts index 84978b98c4..6fdd04ea61 100644 --- a/ui-tui/src/app/useInputHandlers.ts +++ b/ui-tui/src/app/useInputHandlers.ts @@ -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 } diff --git a/ui-tui/src/app/useSubmission.ts b/ui-tui/src/app/useSubmission.ts index 2c2c6d48d9..df6acfadbe 100644 --- a/ui-tui/src/app/useSubmission.ts +++ b/ui-tui/src/app/useSubmission.ts @@ -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( diff --git a/ui-tui/src/components/agentsOverlay.tsx b/ui-tui/src/components/agentsOverlay.tsx index a3a075afb9..a1b349827c 100644 --- a/ui-tui/src/components/agentsOverlay.tsx +++ b/ui-tui/src/components/agentsOverlay.tsx @@ -671,9 +671,7 @@ function DiffView({ {diffMetricLine('duration', aTotals.totalDuration, bTotals.totalDuration, n => `${n.toFixed(1)}s`)} - - {diffMetricLine('tokens', sumTokens(aTotals), sumTokens(bTotals), fmtTokens)} - + {diffMetricLine('tokens', sumTokens(aTotals), sumTokens(bTotals), fmtTokens)} {diffMetricLine('cost', aTotals.costUsd, bTotals.costUsd, dollars)} diff --git a/ui-tui/src/components/appChrome.tsx b/ui-tui/src/components/appChrome.tsx index e85a0fb718..cf8328bc8f 100644 --- a/ui-tui/src/components/appChrome.tsx +++ b/ui-tui/src/components/appChrome.tsx @@ -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' diff --git a/ui-tui/src/components/appLayout.tsx b/ui-tui/src/components/appLayout.tsx index 84470c4ccf..16d96f390b 100644 --- a/ui-tui/src/components/appLayout.tsx +++ b/ui-tui/src/components/appLayout.tsx @@ -224,7 +224,12 @@ const ComposerPane = memo(function ComposerPane({ ))} - + {sh ? ( {promptLabel} diff --git a/ui-tui/src/components/sessionPicker.tsx b/ui-tui/src/components/sessionPicker.tsx index c5696a068a..fd29d9e7ec 100644 --- a/ui-tui/src/components/sessionPicker.tsx +++ b/ui-tui/src/components/sessionPicker.tsx @@ -133,7 +133,12 @@ export function SessionPicker({ gw, onCancel, onSelect, t }: SessionPickerProps) - + {s.title || s.preview || '(untitled)'} diff --git a/ui-tui/src/components/thinking.tsx b/ui-tui/src/components/thinking.tsx index eb75858741..4204ff56a0 100644 --- a/ui-tui/src/components/thinking.tsx +++ b/ui-tui/src/components/thinking.tsx @@ -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' diff --git a/ui-tui/src/lib/forceTruecolor.ts b/ui-tui/src/lib/forceTruecolor.ts index 78ff5bd719..3e99b6b184 100644 --- a/ui-tui/src/lib/forceTruecolor.ts +++ b/ui-tui/src/lib/forceTruecolor.ts @@ -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' } diff --git a/ui-tui/src/lib/terminalSetup.ts b/ui-tui/src/lib/terminalSetup.ts index dc00512b6b..7d387797d0 100644 --- a/ui-tui/src/lib/terminalSetup.ts +++ b/ui-tui/src/lib/terminalSetup.ts @@ -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)) ) diff --git a/ui-tui/src/theme.ts b/ui-tui/src/theme.ts index e87e6eeb61..e14b8d2a52 100644 --- a/ui-tui/src/theme.ts +++ b/ui-tui/src/theme.ts @@ -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 = LIGHT_DEFAULT_TERM_PROGRAMS, + lightDefaultTermPrograms: ReadonlySet = LIGHT_DEFAULT_TERM_PROGRAMS ): boolean { const lightFlag = (env.HERMES_TUI_LIGHT ?? '').trim().toLowerCase()