From e25b2a6e187e346c28f02ee981dc38e92a7faba1 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 1 Jun 2026 20:49:51 -0500 Subject: [PATCH] fix(tui): address Copilot review on status-bar tail disclosure - Render SpawnHud last in the tail so its un-budgeted (dynamic) width can only truncate itself, never push budgeted segments past leftWidth. - Precompute kaomoji/emoji frame widths once at module load instead of rescanning FACES/EMOJI_FRAMES on every status render. - Correct the tail-priority comment to match the actual fits() order (bar, duration, compressions, voice, session count, bg, cost). --- ui-tui/src/components/appChrome.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/ui-tui/src/components/appChrome.tsx b/ui-tui/src/components/appChrome.tsx index 7808b1cba..a42e3e0e9 100644 --- a/ui-tui/src/components/appChrome.tsx +++ b/ui-tui/src/components/appChrome.tsx @@ -75,13 +75,18 @@ const renderIndicator = (style: IndicatorStyle, tick: number): IndicatorRender = return { frame, intervalMs: Math.max(SPINNER_TICK_MS, spinner.interval), showVerb: false } } +// `FACES` / `EMOJI_FRAMES` are static, so measure their widest glyph once at +// module load instead of rescanning on every status render. +const KAOMOJI_FRAME_WIDTH = FACES.reduce((max, f) => Math.max(max, stringWidth(f)), 1) +const EMOJI_FRAME_WIDTH = EMOJI_FRAMES.reduce((max, f) => Math.max(max, stringWidth(f)), 1) + const indicatorFrameWidth = (style: IndicatorStyle): number => { if (style === 'kaomoji') { - return FACES.reduce((max, f) => Math.max(max, stringWidth(f)), 1) + return KAOMOJI_FRAME_WIDTH } if (style === 'emoji') { - return EMOJI_FRAMES.reduce((max, f) => Math.max(max, stringWidth(f)), 1) + return EMOJI_FRAME_WIDTH } // 'ascii' and 'unicode' are single-column glyphs. @@ -416,8 +421,9 @@ export function StatusRule({ // Whole-segment progressive disclosure for the tail: a segment renders only // if it fits in the space left after the pinned essentials, evaluated in - // priority order. No mid-segment truncation, and the low-value tail (incl. - // the session count) drops first instead of crushing status/model/context. + // descending priority order — bar, duration, compressions, voice, session + // count, bg, cost. Lower-priority segments drop first and nothing truncates + // mid-segment, so status/model/context are never crushed. const SEP = stringWidth(' │ ') let tailBudget = Math.max(0, leftWidth - essentialWidth) const fits = (w: number) => { @@ -499,7 +505,6 @@ export function StatusRule({ ) : null} - {showVoice ? ( ) : null} + {/* SpawnHud isn't part of the tail budget (its width is dynamic), so it + renders last — any overflow truncates the HUD itself rather than the + budgeted segments before it. It self-hides when no delegation runs. */} + {rightWidth > 0 ? (