diff --git a/apps/desktop/src/components/assistant-ui/thread/status.test.tsx b/apps/desktop/src/components/assistant-ui/thread/status.test.tsx
index b4920e1ec483..f6edaee0d7e3 100644
--- a/apps/desktop/src/components/assistant-ui/thread/status.test.tsx
+++ b/apps/desktop/src/components/assistant-ui/thread/status.test.tsx
@@ -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)
})
})
diff --git a/apps/desktop/src/components/chat/activity-timer-text.tsx b/apps/desktop/src/components/chat/activity-timer-text.tsx
index aa439eb247d5..d9ca1c292e29 100644
--- a/apps/desktop/src/components/chat/activity-timer-text.tsx
+++ b/apps/desktop/src/components/chat/activity-timer-text.tsx
@@ -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 (
-
{formatElapsed(seconds)}
-
+
)
}
diff --git a/apps/desktop/src/components/chat/stable-text.tsx b/apps/desktop/src/components/chat/stable-text.tsx
new file mode 100644
index 000000000000..1e7e03815cd6
--- /dev/null
+++ b/apps/desktop/src/components/chat/stable-text.tsx
@@ -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 (
+
+ {children.split('').map((char, i) => (
+
+ {char}
+
+ ))}
+
+ )
+}
diff --git a/apps/desktop/src/lib/statusbar.ts b/apps/desktop/src/lib/statusbar.tsx
similarity index 92%
rename from apps/desktop/src/lib/statusbar.ts
rename to apps/desktop/src/lib/statusbar.tsx
index b06f9cc0d60e..0c0d22599c58 100644
--- a/apps/desktop/src/lib/statusbar.ts
+++ b/apps/desktop/src/lib/statusbar.tsx
@@ -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 {formatDuration(now - since)}
}