From 7bb7dc843cbb79a47fdc5ed5980fbf4e5aca8f91 Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Thu, 20 Aug 2026 06:33:42 +0200 Subject: [PATCH] refactor: the event row takes its clock instead of reading one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every other surface here is handed `now` — the headline, the board, the dailies strip, the detail sheet — for the reason the parsers are: a function that reads the clock cannot be rendered against a fixed instant, so nothing about it can be asserted. The list row was the one exception, calling the wall clock twice for its window caption and its "starts in". Two things follow from fixing it. Those numbers were counted from a different instant than the `clock` sitting beside them in the same row, which is a disagreement nobody would ever notice and nobody could ever prove. And the countdown is now testable: rendering one row at two instants gives the two answers the injected clock implies, which is what the new test pins. Co-Authored-By: Claude Opus 5 (1M context) --- src/client/App.tsx | 1 + src/client/components/EventRow.tsx | 16 ++++++++++-- test/custom-ui.test.tsx | 39 +++++++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/client/App.tsx b/src/client/App.tsx index fa5bbb3..b569773 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -348,6 +348,7 @@ export function App() { { test("marks the reader's own event as theirs", () => { const html = render(
    - {}} /> + {}} + />
, ); expect(html).toContain("yours"); @@ -170,6 +175,7 @@ describe("EventRow provenance", () => {
    {}} /> @@ -177,6 +183,37 @@ describe("EventRow provenance", () => { ); expect(html).not.toContain(">yours<"); }); + + test("counts from the instant it is handed, not from the wall clock", () => { + // The row used to ask `Date.now()` for its caption and its "starts in", + // which is why neither could be asserted at all: the numbers moved with + // whenever the suite happened to run. Rendering the same row at two instants + // has to produce two different countdowns, and both have to be the ones the + // injected clock implies. + const event = { ...asDisplayEvent(OWN), id: OWN.id }; + const at = Date.parse(AT); + // A window opening in two days, so the row takes its "starts in" branch. + const upcoming = { + ...event, + startsAt: new Date(at + 2 * 24 * 3_600_000).toISOString(), + }; + const at2 = (ms: number) => + render( +
      + {}} + /> +
    , + ); + + expect(at2(at)).toContain("starts in 2d"); + // A day later the same row says one day, with nothing about the real clock + // involved in either answer. + expect(at2(at + 24 * 3_600_000)).toContain("starts in 1d"); + }); }); describe("Colophon freshness notice (PRD F7)", () => {