diff --git a/mobile/src/source-control/MobileGitHistoryList.test.tsx b/mobile/src/source-control/MobileGitHistoryList.test.tsx index b19a069c80f..c6d7a9895a5 100644 --- a/mobile/src/source-control/MobileGitHistoryList.test.tsx +++ b/mobile/src/source-control/MobileGitHistoryList.test.tsx @@ -27,11 +27,24 @@ vi.mock('react-native', () => ({ vi.mock('lucide-react-native', () => ({ ChevronDown: 'ChevronDown', ChevronRight: 'ChevronRight' })) vi.mock('../transport/client-context', () => ({ useForceReconnect: () => vi.fn() })) +// Captured at module scope: the list renders rows against Date.now() a few ms later, +// so a 3h offset stays inside the '3h' relative-time bucket. +const RENDER_NOW = Date.now() + function historyResponse(subject: string) { return { ok: true, result: { - items: [{ id: 'commit-1', displayId: 'c0mm1t1', subject, author: 'Ada', parentIds: [] }] + items: [ + { + id: 'commit-1', + displayId: 'c0mm1t1', + subject, + author: 'Ada', + parentIds: [], + timestamp: RENDER_NOW - 3 * 3_600_000 + } + ] } } } @@ -92,6 +105,9 @@ describe('MobileGitHistoryList', () => { await render(client, 'connected') expect(tree()).toContain('first load') + // Rows format the RPC timestamp (epoch ms); a regression to seconds-scaling + // renders every commit as 'just now' instead. + expect(tree()).toContain('3h') await update(client, 'reconnecting') expect(tree()).toContain('first load') diff --git a/mobile/src/source-control/mobile-git-history.test.ts b/mobile/src/source-control/mobile-git-history.test.ts index 7357f76aeff..7660d3f72d8 100644 --- a/mobile/src/source-control/mobile-git-history.test.ts +++ b/mobile/src/source-control/mobile-git-history.test.ts @@ -11,20 +11,21 @@ function item(overrides: Partial = {}): GitHistoryItem { subject: 'feat: thing', message: 'feat: thing\n\nbody', author: 'Jane', - timestamp: NOW / 1000 - 3600, + timestamp: NOW - 3_600_000, ...overrides } } describe('formatCommitTime', () => { - it('formats across thresholds', () => { - const s = NOW / 1000 - expect(formatCommitTime(s - 30, NOW)).toBe('just now') - expect(formatCommitTime(s - 5 * 60, NOW)).toBe('5m') - expect(formatCommitTime(s - 3 * 3600, NOW)).toBe('3h') - expect(formatCommitTime(s - 2 * 86400, NOW)).toBe('2d') - expect(formatCommitTime(s - 60 * 86400, NOW)).toBe('2mo') - expect(formatCommitTime(s - 800 * 86400, NOW)).toBe('2y') + it('formats across thresholds from epoch-millisecond timestamps', () => { + // GitHistoryItem.timestamp is epoch ms (git-history-log-parser scales git %at by 1000). + const ms = { min: 60_000, hour: 3_600_000, day: 86_400_000 } + expect(formatCommitTime(NOW - 3 * ms.hour, NOW)).toBe('3h') + expect(formatCommitTime(NOW - 30_000, NOW)).toBe('just now') + expect(formatCommitTime(NOW - 5 * ms.min, NOW)).toBe('5m') + expect(formatCommitTime(NOW - 2 * ms.day, NOW)).toBe('2d') + expect(formatCommitTime(NOW - 60 * ms.day, NOW)).toBe('2mo') + expect(formatCommitTime(NOW - 800 * ms.day, NOW)).toBe('2y') }) it('returns empty for missing timestamp', () => { diff --git a/mobile/src/source-control/mobile-git-history.ts b/mobile/src/source-control/mobile-git-history.ts index 416b761d214..d0d42929ade 100644 --- a/mobile/src/source-control/mobile-git-history.ts +++ b/mobile/src/source-control/mobile-git-history.ts @@ -12,12 +12,13 @@ export type MobileCommitRow = { } // Short relative time for a commit list (just now / Xm / Xh / Xd / Xmo / Xy). -export function formatCommitTime(timestampSeconds: number | undefined, nowMs: number): string { +// `timestampMs` is epoch ms, the unit GitHistoryItem.timestamp already carries. +export function formatCommitTime(timestampMs: number | undefined, nowMs: number): string { // Nullish — not falsy — so a real epoch-0 timestamp still formats. - if (timestampSeconds == null) { + if (timestampMs == null) { return '' } - const delta = nowMs - timestampSeconds * 1000 + const delta = nowMs - timestampMs if (delta < 60_000) { return 'just now' } diff --git a/src/shared/git-history-types.ts b/src/shared/git-history-types.ts index 4e99d4b2eb2..ede5ba19fac 100644 --- a/src/shared/git-history-types.ts +++ b/src/shared/git-history-types.ts @@ -48,6 +48,7 @@ export type GitHistoryItem = { displayId?: string author?: string authorEmail?: string + /** Epoch milliseconds (git %at seconds × 1000). */ timestamp?: number statistics?: GitHistoryItemStatistics references?: GitHistoryItemRef[] diff --git a/src/shared/git-history.test.ts b/src/shared/git-history.test.ts index 617fa33c2ef..38f7cdd2bd5 100644 --- a/src/shared/git-history.test.ts +++ b/src/shared/git-history.test.ts @@ -115,7 +115,9 @@ describe('git history parsing', () => { message: 'feat: add graph\n\nbody line', author: 'Ada Lovelace', authorEmail: 'ada@example.com', - displayId: HEAD_OID.slice(0, 7) + displayId: HEAD_OID.slice(0, 7), + // The format feeds %at seconds; consumers get epoch milliseconds. + timestamp: 1_700_000_000_000 }) expect(item?.references?.map((ref) => [ref.id, ref.name, ref.category])).toEqual([ ['refs/heads/feature', 'feature', 'branches'],