From 64bdbd7f3cefc1c198d3a97d5ce0d6c12075db32 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 28 Jul 2026 20:33:25 -0500 Subject: [PATCH] fix(desktop): stop message age falling back to the 1970 epoch ("20663d ago") --- apps/desktop/src/lib/chat-runtime.test.ts | 22 ++++++++++++++++++++++ apps/desktop/src/lib/chat-runtime.ts | 17 ++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/lib/chat-runtime.test.ts b/apps/desktop/src/lib/chat-runtime.test.ts index 46ce1da92198..7ec2aa4cc4d8 100644 --- a/apps/desktop/src/lib/chat-runtime.test.ts +++ b/apps/desktop/src/lib/chat-runtime.test.ts @@ -6,6 +6,7 @@ import { attachmentDisplayText, attachmentId, coerceThinkingText, + messageCreatedAt, optimisticAttachmentRef, parseCommandDispatch, parseSlashCommand @@ -178,3 +179,24 @@ describe('attachmentId', () => { expect(attachmentId('url', 'https://example.com/a')).not.toBe(attachmentId('url', 'https://example.com/b')) }) }) + +describe('messageCreatedAt', () => { + const NOW = Date.UTC(2026, 6, 28, 18, 0, 0) + + it('reads the authoritative Unix-seconds timestamp (not ms)', () => { + // 1785282262s → July 2026, not the 1970 epoch a *1000-less read would give. + expect(messageCreatedAt({ timestamp: 1785282262 }, NOW).getFullYear()).toBe(2026) + }) + + it('falls back to now — never digs digits out of the id → "20663d ago" (1970)', () => { + // The old fallback did `new Date(Number(id.match(/\d+/)))`, so a session-style + // id like 20260728_184420_05e697 parsed to 20260728 *ms* = Jan 1970, showing + // as an absurd 20663-day age. A timestamp-less message is freshly created. + expect(messageCreatedAt({ timestamp: undefined }, NOW).getTime()).toBe(NOW) + }) + + it('treats a zero / non-finite timestamp as absent', () => { + expect(messageCreatedAt({ timestamp: 0 }, NOW).getTime()).toBe(NOW) + expect(messageCreatedAt({ timestamp: Number.NaN }, NOW).getTime()).toBe(NOW) + }) +}) diff --git a/apps/desktop/src/lib/chat-runtime.ts b/apps/desktop/src/lib/chat-runtime.ts index 65965c41b67d..0aab5277a703 100644 --- a/apps/desktop/src/lib/chat-runtime.ts +++ b/apps/desktop/src/lib/chat-runtime.ts @@ -368,13 +368,24 @@ export function quickModelOptions( return options.slice(0, 8) } +// A message's display time. `timestamp` (Unix seconds) is authoritative when +// present. Without it we fall back to *now* rather than digging digits out of +// the id: message ids come in incompatible shapes — `assistant-`, +// `--`, session-style `20260728_184420_…` — and feeding any +// of them to `new Date()` (which reads ms) lands on the 1970 epoch, rendering +// as an absurd "20663d ago". A timestamp-less message is a freshly created +// optimistic/streaming one, so *now* is the right age anyway. +export function messageCreatedAt(message: Pick, nowMs = Date.now()): Date { + return typeof message.timestamp === 'number' && Number.isFinite(message.timestamp) && message.timestamp > 0 + ? new Date(message.timestamp * 1000) + : new Date(nowMs) +} + export function toRuntimeMessage(message: ChatMessage): ThreadMessage { const role = message.role === 'user' || message.role === 'assistant' || message.role === 'system' ? message.role : 'assistant' - const createdAt = message.timestamp - ? new Date(message.timestamp * 1000) - : new Date(Number(message.id.match(/\d+/)?.[0]) || Date.now()) + const createdAt = messageCreatedAt(message) if (role === 'user') { return {