From 9910681b859fecf85f37a76d02ae6e88dd148ccc Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 20 Apr 2026 11:23:58 -0500 Subject: [PATCH] refactor(tui): move last-msg elapsed from status bar to prompt right-edge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Status bar ticker was too hot in peripheral vision. The moment the elapsed value matters is when the prompt returns — so surface it there. Dim `fmtDuration` next to the GoodVibesHeart, idle-only (hidden while busy), so quick turns and active streaming stay quiet. --- ui-tui/src/components/appChrome.tsx | 21 +++++++++++++++------ ui-tui/src/components/appLayout.tsx | 7 ++++--- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/ui-tui/src/components/appChrome.tsx b/ui-tui/src/components/appChrome.tsx index 23c4a4e8e..4c4faef0e 100644 --- a/ui-tui/src/components/appChrome.tsx +++ b/ui-tui/src/components/appChrome.tsx @@ -55,7 +55,7 @@ function ctxBar(pct: number | undefined, w = 10) { return '█'.repeat(filled) + '░'.repeat(w - filled) } -function SessionDuration({ lastUserAt, startedAt }: { lastUserAt?: null | number; startedAt: number }) { +function SessionDuration({ startedAt }: { startedAt: number }) { const [now, setNow] = useState(() => Date.now()) useEffect(() => { @@ -65,9 +65,20 @@ function SessionDuration({ lastUserAt, startedAt }: { lastUserAt?: null | number return () => clearInterval(id) }, [startedAt]) - const total = fmtDuration(now - startedAt) + return fmtDuration(now - startedAt) +} - return lastUserAt ? `${fmtDuration(now - lastUserAt)}/${total}` : total +export function IdleSinceLastMsg({ lastUserAt, t }: { lastUserAt: number; t: Theme }) { + const [now, setNow] = useState(() => Date.now()) + + useEffect(() => { + setNow(Date.now()) + const id = setInterval(() => setNow(Date.now()), 1000) + + return () => clearInterval(id) + }, [lastUserAt]) + + return {fmtDuration(now - lastUserAt)} } export function GoodVibesHeart({ tick, t }: { tick: number; t: Theme }) { @@ -100,7 +111,6 @@ export function StatusRule({ model, usage, bgCount, - lastUserAt, sessionStartedAt, showCost, voiceLabel, @@ -135,7 +145,7 @@ export function StatusRule({ {sessionStartedAt ? ( {' │ '} - + ) : null} {voiceLabel ? │ {voiceLabel} : null} @@ -290,7 +300,6 @@ interface StatusRuleProps { busy: boolean cols: number cwdLabel: string - lastUserAt?: null | number model: string sessionStartedAt?: null | number showCost: boolean diff --git a/ui-tui/src/components/appLayout.tsx b/ui-tui/src/components/appLayout.tsx index d711edca8..7b5b25ae8 100644 --- a/ui-tui/src/components/appLayout.tsx +++ b/ui-tui/src/components/appLayout.tsx @@ -9,7 +9,7 @@ import { PLACEHOLDER } from '../content/placeholders.js' import type { Theme } from '../theme.js' import type { DetailsMode } from '../types.js' -import { GoodVibesHeart, StatusRule, StickyPromptTracker, TranscriptScrollbar } from './appChrome.js' +import { GoodVibesHeart, IdleSinceLastMsg, StatusRule, StickyPromptTracker, TranscriptScrollbar } from './appChrome.js' import { FloatingOverlays, PromptZone } from './appOverlays.js' import { Banner, Panel, SessionPanel } from './branding.js' import { MessageLine } from './messageLine.js' @@ -188,7 +188,6 @@ const ComposerPane = memo(function ComposerPane({ busy={ui.busy} cols={composer.cols} cwdLabel={status.cwdLabel} - lastUserAt={status.lastUserAt} model={ui.info?.model?.split('/').pop() ?? ''} sessionStartedAt={status.sessionStartedAt} showCost={ui.showCost} @@ -243,7 +242,9 @@ const ComposerPane = memo(function ComposerPane({ value={composer.input} /> - + + {!ui.busy && status.lastUserAt ? : null} +