An event that cannot be linked to is not a hub's event: give each one a short page, and make the timeline the index of them.
Today /api/events/:id returns JSON and nothing renders a single event, so the only way to point someone at "the one where bazaar shipped order 42" is to send them the whole timeline and hope. The digest (#13), both integrations (#15, #16) and any agent quoting an event all want an address.
Done looks like
[ ] GET /e/:id renders one event through getEvent and the shared layout: subject, summary, the source and kind as links into the filtered timeline, the event's own link when present with rel="noopener noreferrer", occurred_at as <time datetime> showing stamp() and ago() from src/when.ts, and received_at shown only when it differs from occurred_at.
[ ] The path stays short because it is meant to be pasted — a comment in src/routes.ts says that is why it is /e/:id and not /events/:id.
[ ] The document title is the event's subject, escaped, so a shared tab says what it is; the page's first paint is server-rendered and carries no new JavaScript.
[ ] A missing row is a 404 that is still a page — layout, a sentence, a link back to / — not the bare text("not found", 404) the router falls through to; an id that is not a whole number is the same invalid notice the bad-filter path already renders, with status 422.
[ ] Each row's <time> on / becomes the link to that event's address, server-rendered in src/page.ts and client-built the same way in static/live.js, at least 44px on a side with no layout shift.
[ ] AGENTS.md's rule for the second page is followed: src/page.ts becomes src/pages/layout.ts, src/pages/home.ts and src/pages/event.ts, one layout shared. Do the move as its own commit that only moves and re-exports — nothing else changed in it — so the branches in flight rebase across a rename cleanly.
[ ] Tests in src/server.test.ts: a subject containing <script> renders as text on both / and /e/:id; an unknown id is 404 with the security floor on the response; /e/abc is 422; the timeline's rows link to their addresses; HEAD /e/:id answers like GET.
Risks: the rename is the whole danger — five open issues touch src/page.ts (#12, #13, #41, #42, #43), so a move mixed into a behaviour change is how a pull rots the way #22 did; keep the move pure and land it fast. Everything on this page came from another app: subject, summary and link are escaped on the way out, and the link is rendered only when it parses as http(s), exactly as static/live.js already does. This issue waits on #45, which is rewriting the day grouping in the same file.
Suggested: Scout — one small page, done fully, with the move kept honest.
An event that cannot be linked to is not a hub's event: give each one a short page, and make the timeline the index of them.
Today
/api/events/:idreturns JSON and nothing renders a single event, so the only way to point someone at "the one where bazaar shipped order 42" is to send them the whole timeline and hope. The digest (#13), both integrations (#15, #16) and any agent quoting an event all want an address.Done looks like
GET /e/:idrenders one event throughgetEventand the sharedlayout: subject, summary, the source and kind as links into the filtered timeline, the event's ownlinkwhen present withrel="noopener noreferrer",occurred_atas<time datetime>showingstamp()andago()fromsrc/when.ts, andreceived_atshown only when it differs fromoccurred_at.src/routes.tssays that is why it is/e/:idand not/events/:id.layout, a sentence, a link back to/— not the baretext("not found", 404)the router falls through to; an id that is not a whole number is the sameinvalidnotice the bad-filter path already renders, with status 422.<time>on/becomes the link to that event's address, server-rendered insrc/page.tsand client-built the same way instatic/live.js, at least 44px on a side with no layout shift.src/page.tsbecomessrc/pages/layout.ts,src/pages/home.tsandsrc/pages/event.ts, onelayoutshared. Do the move as its own commit that only moves and re-exports — nothing else changed in it — so the branches in flight rebase across a rename cleanly.src/server.test.ts: a subject containing<script>renders as text on both/and/e/:id; an unknown id is 404 with the security floor on the response;/e/abcis 422; the timeline's rows link to their addresses;HEAD /e/:idanswers likeGET.Files:
src/routes.ts,src/pages/layout.ts,src/pages/home.ts,src/pages/event.ts(fromsrc/page.ts),static/live.js,src/server.test.ts,README.mdRisks: the rename is the whole danger — five open issues touch
src/page.ts(#12, #13, #41, #42, #43), so a move mixed into a behaviour change is how a pull rots the way #22 did; keep the move pure and land it fast. Everything on this page came from another app: subject, summary and link are escaped on the way out, and the link is rendered only when it parses ashttp(s), exactly asstatic/live.jsalready does. This issue waits on #45, which is rewriting the day grouping in the same file.Suggested: Scout — one small page, done fully, with the move kept honest.