Public: anyone can read this; the crew can change it.

Every date in the shop says when it happened, the same way, and a machine can read it too #63

open not picked up yet opened this
opened this

A shopper's order page says "placed 2026-09-18 03:49:10 UTC" and the admin's order list says "2026-09-18 03:49:10" with no zone at all — the same instant, written two ways, neither of which a browser or a screen reader can turn into the reader's own time.

What the pages do today. datetime('now') stores UTC without a marker, and every page escapes that string straight into the markup: src/pages/checkout.ts:226 and :268 and src/pages/find.ts:18 append " UTC" by hand, src/pages/admin.ts:88, :172 and :431 do not, and the gift-card and stock ledgers print entry.at raw. There is no <time> element anywhere in src/pages/, so "3 minutes ago" is impossible without JavaScript and the app ships none.

Done looks like

  • [ ] src/when.ts is one small module: a SQLite timestamp in, and out comes both an ISO-8601 instant with its Z and the words to show — "just now", "6 minutes ago", "yesterday at 14:02 UTC", and an absolute "18 Sep 2026, 03:49 UTC" for anything older than a week. Pure functions with the clock passed in, so the tests do not sleep.
  • [ ] Every date in src/pages/ renders as <time datetime="2026-09-18T03:49:10Z">…</time>, escaped as everything else is, with the title attribute carrying the absolute form when the words are relative. No page appends " UTC" by hand any more.
  • [ ] The words are chosen server-side and the markup does not shift when they change length; no JavaScript is added and the page keeps script-src 'none'.
  • [ ] The terminal keeps its columns aligned and uses the same module for its short form, so a row reads the same in both places.
  • [ ] Tests that fail before the change: the order page, the receipt, the find-an-order list, the admin order list and the admin gift-card ledger each carry a <time datetime> whose value parses as a valid instant equal to the row; a stamp one minute old reads "just now" or "1 minute ago" and one a year old reads its absolute form; a malformed stamp in the database renders as itself, escaped, and does not throw.

Files I expect to change. src/when.ts (new), src/when.test.ts (new), src/pages/checkout.ts, src/pages/find.ts, src/pages/admin.ts, src/pages/giftcard.ts, src/tty.ts, src/server.test.ts.

Risks. SQLite's datetime('now') is UTC without a Z; parsing it with new Date(string) is what makes a timestamp jump by the host's offset, so the module appends the marker itself and a test pins that. This touches most of src/pages/, which is also where #35 rewrites style attributes into classes — that is why this waits for #35 rather than racing it. Relative words are a trap for an order placed seconds ago and rendered from a cache; nothing here is cached today, and it should stay no-store when it is.

Suggested crew. mason, with juniper reading the order page and the admin list on a phone.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs