mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-22 16:25:58 +00:00
fix(desktop): prevent timers from shifting as they count (#68131)
LiveDuration returned a bare string with proportional digits, so the statusbar reflowed every second as the timer ticked. Extract a shared StableText component that renders each character in its own 1ch-wide cell, preventing any digit from shifting the layout — works with the proportional sans font, no need for font-mono. Both LiveDuration (statusbar session/running timers) and ActivityTimerText (tool activity timers) now use StableText, dropping the font-mono + tabular-nums workaround from the latter. Also renames statusbar.ts → statusbar.tsx since LiveDuration now returns JSX.
This commit is contained in:
parent
9403b4f8ba
commit
470c7e2a60
4 changed files with 36 additions and 7 deletions
|
|
@ -36,7 +36,7 @@ describe('ResponseLoadingIndicator timer', () => {
|
|||
const sessionA = renderIndicator()
|
||||
|
||||
act(() => vi.advanceTimersByTime(5_000))
|
||||
expect(screen.getByText('5s')).toBeTruthy()
|
||||
expect(screen.getAllByText((_, node) => node?.textContent === '5s').length).toBeGreaterThan(0)
|
||||
sessionA.unmount()
|
||||
|
||||
$activeSessionId.set('session-b')
|
||||
|
|
@ -44,13 +44,13 @@ describe('ResponseLoadingIndicator timer', () => {
|
|||
const sessionB = renderIndicator()
|
||||
|
||||
act(() => vi.advanceTimersByTime(3_000))
|
||||
expect(screen.getByText('3s')).toBeTruthy()
|
||||
expect(screen.getAllByText((_, node) => node?.textContent === '3s').length).toBeGreaterThan(0)
|
||||
sessionB.unmount()
|
||||
|
||||
$activeSessionId.set('session-a')
|
||||
$turnStartedAt.set(new Date('2026-01-01T00:00:00.000Z').getTime())
|
||||
renderIndicator()
|
||||
|
||||
expect(screen.getByText('8s')).toBeTruthy()
|
||||
expect(screen.getAllByText((_, node) => node?.textContent === '8s').length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
|
||||
import { formatElapsed } from './activity-timer'
|
||||
import { StableText } from './stable-text'
|
||||
|
||||
interface ActivityTimerTextProps {
|
||||
seconds: number
|
||||
|
|
@ -9,16 +10,16 @@ interface ActivityTimerTextProps {
|
|||
|
||||
export function ActivityTimerText({ seconds, className }: ActivityTimerTextProps) {
|
||||
return (
|
||||
<span
|
||||
<StableText
|
||||
className={cn(
|
||||
// Tinted with --dt-midground (very low alpha) so the timer reads
|
||||
// as part of the same "live signal" cluster as the dither block /
|
||||
// arc-border / working-session dot, instead of being neutral chrome.
|
||||
'shrink-0 font-mono text-[0.56rem] leading-none tracking-[0.02em] text-midground/55 tabular-nums',
|
||||
'shrink-0 text-[0.56rem] leading-none tracking-[0.02em] text-midground/55',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{formatElapsed(seconds)}
|
||||
</span>
|
||||
</StableText>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
23
apps/desktop/src/components/chat/stable-text.tsx
Normal file
23
apps/desktop/src/components/chat/stable-text.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface StableTextProps {
|
||||
children: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders text as a row of 1ch-wide cells so individual characters can't
|
||||
* shift the layout as they change (e.g. digits in a ticking timer).
|
||||
* Works with any proportional font — no need for font-mono.
|
||||
*/
|
||||
export function StableText({ children, className }: StableTextProps) {
|
||||
return (
|
||||
<span className={cn('inline-flex', className)}>
|
||||
{children.split('').map((char, i) => (
|
||||
<span className="inline-block w-[1ch] text-center" key={i}>
|
||||
{char}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { StableText } from '@/components/chat/stable-text'
|
||||
import { compactNumber } from '@/lib/format'
|
||||
import type { UsageStats } from '@/types/hermes'
|
||||
|
||||
|
|
@ -72,5 +73,9 @@ export function LiveDuration({ since }: { since: number | null | undefined }) {
|
|||
return () => window.clearInterval(timer)
|
||||
}, [since])
|
||||
|
||||
return since ? formatDuration(now - since) : null
|
||||
if (!since) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <StableText>{formatDuration(now - since)}</StableText>
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue