Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/components/SessionPicker.client.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,16 @@ describe('SessionPicker', () => {

describe('relativeTime', () => {
const now = Date.parse('2026-09-17T12:00:00Z');
// The assertions match English wording.
const ago = (iso: string) => relativeTime(Date.parse(iso), now, 'en');

it('picks the largest fitting unit', () => {
expect(relativeTime(Date.parse('2026-09-15T12:00:00Z'), now)).toMatch(/day/);
expect(relativeTime(Date.parse('2026-09-17T09:00:00Z'), now)).toMatch(/hour/);
expect(relativeTime(Date.parse('2026-09-17T11:30:00Z'), now)).toMatch(/minute/);
expect(ago('2026-09-15T12:00:00Z')).toMatch(/day/);
expect(ago('2026-09-17T09:00:00Z')).toMatch(/hour/);
expect(ago('2026-09-17T11:30:00Z')).toMatch(/minute/);
});

it('falls through to seconds for a very recent session', () => {
expect(relativeTime(Date.parse('2026-09-17T11:59:50Z'), now)).toMatch(/second/);
expect(ago('2026-09-17T11:59:50Z')).toMatch(/second/);
});
});
7 changes: 4 additions & 3 deletions src/components/SessionPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import { canResumeSessionId } from '../../electron/shared/session-resume';
import type { SessionRecord } from '../../electron/shared/session-record';

/** Relative age of a session, via the platform formatter rather than a table
* of thresholds. Exported for its test. */
export function relativeTime(epochMs: number, now = Date.now()): string {
const format = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' });
* of thresholds. Exported for its test, which pins `locale` so the wording it
* checks does not depend on the host's default. */
export function relativeTime(epochMs: number, now = Date.now(), locale?: string): string {
const format = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
const seconds = Math.round((epochMs - now) / 1000);
const units: [Intl.RelativeTimeFormatUnit, number][] = [
['day', 86_400],
Expand Down
9 changes: 6 additions & 3 deletions src/components/chat/ContextMeter.client.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ afterEach(() => {
container.remove();
});

// Exact counts are formatted in the host's locale, so the expected text is too.
const n = (value: number) => value.toLocaleString();

it('shows how full the context is, with exact figures and over-limit usage on hover', () => {
expect(meter()).toBeNull();
setState({ ...base, contextUsage: { usedTokens: 50000, maxTokens: 200000 } });
// A ring alone until the window runs short; the figures wait on hover.
expect(meter()?.textContent).toBe('');
expect(meter()?.title).toContain('50,000 of 200,000 tokens used, 150K left');
expect(meter()?.title).toContain(`${n(50_000)} of ${n(200_000)} tokens used, 150K left`);
expect(meter()?.getAttribute('aria-valuenow')).toBe('50000');
setState({ ...base, contextUsage: { usedTokens: 190000, maxTokens: 200000 } });
expect(meter()?.dataset.level).toBe('high');
Expand All @@ -36,7 +39,7 @@ it('shows how full the context is, with exact figures and over-limit usage on ho
expect(meter()?.textContent).toBe('105%');
expect(meter()?.dataset.level).toBe('full');
expect(meter()?.getAttribute('aria-valuenow')).toBe('200000');
expect(meter()?.getAttribute('aria-valuetext')).toContain('210,000 of 200,000');
expect(meter()?.getAttribute('aria-valuetext')).toContain(`${n(210_000)} of ${n(200_000)}`);
setState(base);
expect(meter()).toBeNull();
});
Expand All @@ -55,7 +58,7 @@ it('adds the session token breakdown to the hover text', () => {
scope: 'conversation',
},
});
expect(meter()?.title).toContain('Session: 12,500 tokens · 12,000 input');
expect(meter()?.title).toContain(`Session: ${n(12_500)} tokens · ${n(12_000)} input`);
expect(meter()?.title).toContain('for this conversation');
setState({
...base,
Expand Down
Loading