fix(mobile): stop double-scaling commit timestamps in history rows (#17731)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
This commit is contained in:
blade035
2026-09-06 23:27:13 -07:00
committed by GitHub
co-authored by Claude Jinwoo-H
parent 1ae7aa8bb4
commit c300913f90
5 changed files with 35 additions and 14 deletions
@@ -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')
@@ -11,20 +11,21 @@ function item(overrides: Partial<GitHistoryItem> = {}): 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', () => {
@@ -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'
}
+1
View File
@@ -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[]
+3 -1
View File
@@ -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'],