From 57f0ef8df2d9ef15d3143a09e08ddd2013783aa5 Mon Sep 17 00:00:00 2001 From: Void Date: Wed, 11 Nov 2026 18:12:02 +0000 Subject: [PATCH 1/3] Desk: raised_at on the ForkCard (required key, instant or nil); the card shows its age only when there is one Co-Authored-By: Claude Sonnet 5.5 --- src/app/features/desk/DeskView.test.tsx | 30 +++++++++++++++++++++++++ src/app/features/desk/DeskView.tsx | 12 +++++++--- src/app/features/desk/age.ts | 17 ++++++++++++++ src/app/features/desk/buildDesk.ts | 3 +++ src/app/features/desk/fixtures.ts | 1 + src/app/features/desk/forkContract.ts | 6 +++++ src/preview-demo/deskForks.test.ts | 1 + src/preview-demo/deskForks.ts | 2 ++ 8 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 src/app/features/desk/age.ts diff --git a/src/app/features/desk/DeskView.test.tsx b/src/app/features/desk/DeskView.test.tsx index ca10ea66..19be8aff 100644 --- a/src/app/features/desk/DeskView.test.tsx +++ b/src/app/features/desk/DeskView.test.tsx @@ -5,6 +5,7 @@ import { afterEach, describe, expect, it } from 'vitest'; import { ScreenSize, ScreenSizeProvider } from '../../hooks/useScreenSize'; import { DeskEntry } from './DeskEntry'; import { DeskView, SIGN_IN_NOTE } from './DeskView'; +import { ForkList } from './forkContract'; import { STATES } from './fixtures'; import { setDeskSource } from './source'; @@ -121,6 +122,35 @@ describe('DeskView', () => { }); }); +describe("a question's age", () => { + const NOW = Date.parse('2026-11-11T12:00:00Z'); + const listRaised = (raisedAt: string | null): ForkList => { + const entry = STATES.noAnswers.forks[0]; + return { + ...STATES.noAnswers, + forks: [{ ...entry, card: { ...entry.card, raised_at: raisedAt } }], + }; + }; + const html = (raisedAt: string | null) => + renderToStaticMarkup( + + + + ); + it('reads days since the card was first admitted', () => { + expect(html('2026-11-06T12:00:00Z')).toContain('raised 5d ago'); + }); + it('reads hours inside a day, and just now inside an hour', () => { + expect(html('2026-11-11T07:30:00Z')).toContain('raised 4h ago'); + expect(html('2026-11-11T11:40:00Z')).toContain('raised just now'); + }); + it('shows no age when the card carries none, or an instant that does not parse or is ahead of the clock', () => { + ['null', 'yesterday', '2027-01-01T00:00:00Z'].forEach((v) => { + expect(html(v === 'null' ? null : v)).not.toContain('raised '); + }); + }); +}); + describe('DeskEntry', () => { afterEach(() => setDeskSource(() => undefined)); const entry = (mind: string) => diff --git a/src/app/features/desk/DeskView.tsx b/src/app/features/desk/DeskView.tsx index e57bfa70..ceb42fd9 100644 --- a/src/app/features/desk/DeskView.tsx +++ b/src/app/features/desk/DeskView.tsx @@ -6,6 +6,7 @@ import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { AnswerRow, Desk, buildDesk, DeskRow } from './buildDesk'; import { ForkList } from './forkContract'; import { deskMood } from './deskMood'; +import { raisedAge } from './age'; import * as css from './DeskView.css'; /** Slice 1 has no session: every viewer is signed out, and the note says why it cannot be used. */ @@ -45,7 +46,7 @@ function Answers({ answers }: { answers: AnswerRow[] }) { ); } -function Row({ row }: { row: DeskRow }) { +function Row({ row, now }: { row: DeskRow; now: number }) { const phone = useScreenSizeContext() === ScreenSize.Mobile; // Phones list titles; a tap opens one. Wide screens show every row open. const [open, setOpen] = useState(!phone); @@ -77,6 +78,9 @@ function Row({ row }: { row: DeskRow }) { {open && ( <> + {raisedAge(row.raisedAt, now) && ( +

{raisedAge(row.raisedAt, now)}

+ )}

{row.context}