Intent. A table runs for a day and you open it six times; every time, the only question you have is what changed since the last time — and today the act log answers it by making you remember.
Done looks like
[ ] Migration: seat.last_seen_act INTEGER (nullable). The mark belongs to a seat, not to a browser or a cookie: nothing about a viewer is stored anywhere else.
[ ] tableState returns new_acts (a count) and since_act (the id the mark stood at) for the caller's own seat; a guest, a non-seated viewer and an anonymous reader get 0 and null, and no row is written for them.
[ ] The mark advances only when the seat's holder reads that table, never on the lobby list, never backwards, and never past the act the reader was actually shown.
[ ] One divider in src/pages/table.ts — "3 acts since you last looked" — placed in the act log at since_act, shown once, gone on the next read; and the lobby marks a table with unread acts. Both read the action; neither counts anything itself.
[ ] The mark leaks nothing: it says how many acts, never what they were, and a test asserts a guest's rendered table is byte-identical whether or not any seat has a mark.
[ ] Tests: two acts arrive and the holder's next read shows the divider once; an immediate second read shows none; another seat's read does not move your mark; a seat that never read sees the whole log with no divider rather than "7 acts since you last looked" on a table it has never opened.
Filessrc/db.ts (append a migration), src/actions.ts, src/pages/table.ts, src/pages/style.ts (the divider in the fixture), src/page.ts, src/actions.test.ts, src/server.test.ts.
Risks A read that writes: keep the write off the response path (after the answer is built), keep it out of any query the lobby runs per table, and remember the table view is already private, no-store so nothing caches a per-seat count. Do not let this become a second turn signal — #16 owns "it is your move", and this owns "what I missed"; if the two ever disagree on the page, one of them is wrong.
Who @mason to build; @juniper on the wording and where the divider sits.
Intent. A table runs for a day and you open it six times; every time, the only question you have is what changed since the last time — and today the act log answers it by making you remember.
Done looks like
seat.last_seen_act INTEGER(nullable). The mark belongs to a seat, not to a browser or a cookie: nothing about a viewer is stored anywhere else.tableStatereturnsnew_acts(a count) andsince_act(the id the mark stood at) for the caller's own seat; a guest, a non-seated viewer and an anonymous reader get0andnull, and no row is written for them.src/pages/table.ts— "3 acts since you last looked" — placed in the act log atsince_act, shown once, gone on the next read; and the lobby marks a table with unread acts. Both read the action; neither counts anything itself.Files
src/db.ts(append a migration),src/actions.ts,src/pages/table.ts,src/pages/style.ts(the divider in the fixture),src/page.ts,src/actions.test.ts,src/server.test.ts.Risks A read that writes: keep the write off the response path (after the answer is built), keep it out of any query the lobby runs per table, and remember the table view is already
private, no-storeso nothing caches a per-seat count. Do not let this become a second turn signal — #16 owns "it is your move", and this owns "what I missed"; if the two ever disagree on the page, one of them is wrong.Who @mason to build; @juniper on the wording and where the divider sits.