diff --git a/packages/ui/src/__tests__/chat-turn-answer-identity.test.tsx b/packages/ui/src/__tests__/chat-turn-answer-identity.test.tsx index f0ac7cec5b..7255c315f6 100644 --- a/packages/ui/src/__tests__/chat-turn-answer-identity.test.tsx +++ b/packages/ui/src/__tests__/chat-turn-answer-identity.test.tsx @@ -229,6 +229,27 @@ test('uses human conversation context instead of raw ids in action names', async ); }); +test('keeps Astryx auto formatting live for user-message timestamps', async (context) => { + const now = Date.UTC(2026, 7, 27, 12); + context.mock.timers.enable({ apis: ['Date', 'setInterval'], now }); + const { container, root } = domRoot(); + const twoHoursAgo = now - 2 * 60 * 60 * 1_000; + const turn = { + ...turnWith([{ ...ANSWER, live: false }]), + user: { id: 'ask', role: 'user' as const, text: 'ask', ts: twoHoursAgo }, + }; + + await renderTurn(root, turn); + + const timestamp = container.querySelector('.maka-message-time-inline time'); + assert.ok(timestamp, 'Astryx Timestamp renders the semantic time element'); + assert.match(timestamp.textContent ?? '', /2 hours ago/); + assert.equal(timestamp.getAttribute('tabindex'), '0', 'the absolute-time hover card is keyboard reachable'); + + await act(() => context.mock.timers.tick(60 * 60 * 1_000)); + assert.match(timestamp.textContent ?? '', /3 hours ago/); +}); + /** * The live handoff announces itself exactly once, when the answer enters its * settled phase. A bubble replayed from history mounts already past the diff --git a/packages/ui/src/chat-turn.tsx b/packages/ui/src/chat-turn.tsx index e0e6f27383..f17bdb24fa 100644 --- a/packages/ui/src/chat-turn.tsx +++ b/packages/ui/src/chat-turn.tsx @@ -193,7 +193,7 @@ const UserMessageBody = memo(function UserMessageBody(props: { /* `value` takes ms directly: Timestamp's own parseValue reads anything past 1e12 as milliseconds (2001-09-09 onward), and a chat message never predates that. */ - () + () ) : undefined } footer={