refactor(tui): move last-msg elapsed from status bar to prompt right-edge

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.
This commit is contained in:
Brooklyn Nicholson
2026-04-20 11:23:58 -05:00
parent 1e7de177e8
commit 9910681b85
2 changed files with 19 additions and 9 deletions

View File

@@ -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 <Text color={t.color.dim}>{fmtDuration(now - lastUserAt)} </Text>
}
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 ? (
<Text color={t.color.dim}>
{' │ '}
<SessionDuration lastUserAt={lastUserAt} startedAt={sessionStartedAt} />
<SessionDuration startedAt={sessionStartedAt} />
</Text>
) : null}
{voiceLabel ? <Text color={t.color.dim}> {voiceLabel}</Text> : null}
@@ -290,7 +300,6 @@ interface StatusRuleProps {
busy: boolean
cols: number
cwdLabel: string
lastUserAt?: null | number
model: string
sessionStartedAt?: null | number
showCost: boolean

View File

@@ -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}
/>
<Box position="absolute" right={0}>
<Box flexDirection="row" position="absolute" right={0}>
{!ui.busy && status.lastUserAt ? <IdleSinceLastMsg lastUserAt={status.lastUserAt} t={ui.theme} /> : null}
<GoodVibesHeart t={ui.theme} tick={status.goodVibesTick} />
</Box>
</Box>