mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-13 09:01:54 +00:00
fix(tui): make busy status-bar reservation /indicator-style aware
The left-content reservation used a flat constant for the busy face, but its width varies by /indicator style: kaomoji is a wide glyph plus a rotating verb, while unicode is a bare 1-col braille spinner with no verb. Reserve the real width via busyIndicatorWidth(style, hasDuration) so the model stays on-screen across styles without over-reserving the unbounded elapsed-time tail.
This commit is contained in:
parent
e59b815c04
commit
1d7a1c00b4
3 changed files with 51 additions and 4 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { statusBarSegments, statusRuleWidths } from '../components/appChrome.js'
|
||||
import { busyIndicatorWidth, statusBarSegments, statusRuleWidths } from '../components/appChrome.js'
|
||||
|
||||
describe('statusRuleWidths', () => {
|
||||
it('keeps the status rule within the terminal width', () => {
|
||||
|
|
@ -103,3 +103,18 @@ describe('statusBarSegments', () => {
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('busyIndicatorWidth', () => {
|
||||
it('reserves a bare spinner for the verb-less unicode style', () => {
|
||||
// unicode is a 1-col braille spinner with no verb; far slimmer than the
|
||||
// kaomoji face which carries a wide glyph + rotating verb.
|
||||
expect(busyIndicatorWidth('unicode', false)).toBeLessThan(busyIndicatorWidth('kaomoji', false))
|
||||
expect(busyIndicatorWidth('unicode', false)).toBe(1)
|
||||
})
|
||||
|
||||
it('reserves room for the elapsed-time tail only when a turn is timed', () => {
|
||||
for (const style of ['kaomoji', 'emoji', 'ascii', 'unicode'] as const) {
|
||||
expect(busyIndicatorWidth(style, true)).toBeGreaterThan(busyIndicatorWidth(style, false))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -75,6 +75,34 @@ const renderIndicator = (style: IndicatorStyle, tick: number): IndicatorRender =
|
|||
return { frame, intervalMs: Math.max(SPINNER_TICK_MS, spinner.interval), showVerb: false }
|
||||
}
|
||||
|
||||
const indicatorFrameWidth = (style: IndicatorStyle): number => {
|
||||
if (style === 'kaomoji') {
|
||||
return FACES.reduce((max, f) => Math.max(max, stringWidth(f)), 1)
|
||||
}
|
||||
|
||||
if (style === 'emoji') {
|
||||
return EMOJI_FRAMES.reduce((max, f) => Math.max(max, stringWidth(f)), 1)
|
||||
}
|
||||
|
||||
// 'ascii' and 'unicode' are single-column glyphs.
|
||||
return 1
|
||||
}
|
||||
|
||||
// Display width to reserve for the busy indicator so its verb + elapsed-time
|
||||
// tail can't shove the model off-screen on narrow terminals. Style-aware:
|
||||
// `unicode` is a bare 1-col braille spinner with no verb, while kaomoji/emoji/
|
||||
// ascii add a fixed-width verb; any style adds a bounded elapsed-time tail.
|
||||
// Mirrors FaceTicker's `frame + verbSegment + durationSegment` layout.
|
||||
export const busyIndicatorWidth = (style: IndicatorStyle, hasDuration: boolean): number => {
|
||||
const { showVerb } = renderIndicator(style, 0)
|
||||
const verb = showVerb ? 1 + VERB_PAD_LEN : 0
|
||||
// ` · ` plus a bounded clock (e.g. `59m59s`); long-running durations let the
|
||||
// tail clip rather than reserving unbounded width.
|
||||
const duration = hasDuration ? stringWidth(' · ') + 6 : 0
|
||||
|
||||
return indicatorFrameWidth(style) + verb + duration
|
||||
}
|
||||
|
||||
function FaceTicker({ color, startedAt }: { color: string; startedAt?: null | number }) {
|
||||
const ui = useStore($uiState)
|
||||
const style = ui.indicatorStyle
|
||||
|
|
@ -337,6 +365,7 @@ export function StatusRule({
|
|||
model,
|
||||
modelFast,
|
||||
modelReasoningEffort,
|
||||
indicatorStyle = 'kaomoji',
|
||||
usage,
|
||||
bgCount,
|
||||
liveSessionCount,
|
||||
|
|
@ -369,9 +398,10 @@ export function StatusRule({
|
|||
// grow with its verb/duration tail, but only the glyph itself is essential.
|
||||
const minLeftContent =
|
||||
stringWidth('─ ') +
|
||||
// The busy face carries a verb + elapsed-time tail; reserve enough that it
|
||||
// can't shove the model off-screen, but not the whole (growing) duration.
|
||||
(busy ? 10 : stringWidth(status)) +
|
||||
// The busy face width depends on the active /indicator style (kaomoji is
|
||||
// wide with a verb; unicode is a bare 1-col spinner) — reserve accordingly
|
||||
// so the model survives, without reserving the unbounded duration tail.
|
||||
(busy ? busyIndicatorWidth(indicatorStyle, turnStartedAt != null) : stringWidth(status)) +
|
||||
stringWidth(' │ ') +
|
||||
stringWidth(modelText) +
|
||||
(ctxLabel ? stringWidth(' │ ') + stringWidth(ctxLabel) : 0)
|
||||
|
|
@ -586,6 +616,7 @@ interface StatusRuleProps {
|
|||
model: string
|
||||
modelFast?: boolean
|
||||
modelReasoningEffort?: string
|
||||
indicatorStyle?: IndicatorStyle
|
||||
sessionStartedAt?: null | number
|
||||
showCost: boolean
|
||||
status: string
|
||||
|
|
|
|||
|
|
@ -358,6 +358,7 @@ const StatusRulePane = memo(function StatusRulePane({
|
|||
busy={ui.busy}
|
||||
cols={composer.cols}
|
||||
cwdLabel={status.cwdLabel}
|
||||
indicatorStyle={ui.indicatorStyle}
|
||||
liveSessionCount={ui.liveSessionCount}
|
||||
model={ui.info?.model ?? ''}
|
||||
modelFast={ui.info?.fast || ui.info?.service_tier === 'priority'}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue