One day of events told as prose a person reads in ten seconds — computed on read, deterministic, and the same words at every door.
Done looks like
[ ] src/digest.ts exports digest(db, who, { day }) returning { day, sentence, counts, top }. sentence is one paragraph assembled from the counts: the busiest sources first, each with its kinds ("bazaar placed 12 orders and shipped 9"), at most four clauses with a "and three other apps" tail, no clause for a zero, and one fixed sentence when the day is empty.
[ ] day is validated ^\d{4}-\d{2}-\d{2}$, refused in the future, and defaults to yesterday in UTC; a malformed day is invalid, not an empty paragraph pretending nothing happened.
[ ] When the caller has subscriptions the digest covers what they match and the paragraph says so in its first words; with none it is the whole forge.
[ ] Doors: GET /api/digest?day=, a server-rendered /digest page linking each clause back into the timeline filtered for that day, an MCP tool daily_digest, and D in the terminal. All four print the same sentence.
[ ] src/digest.test.ts inserts a fixed set of rows and asserts the exact sentence, character for character — the empty day, the single-source day, the tail past four sources, and a subscribed person's narrower paragraph.
[ ] No cron and no mail: the README says the digest is computed when it is read, and what delivering one would take.
Risks: prose assembled from data goes wrong at the edges — one event, zero events, a source with a hyphen, a kind that is a prefix of another. Pluralisation is a rule, not an s. The query is a GROUP BY over one day and must use the indexes from #4 rather than scanning.
Suggested: Torque — an exact result stated in words, tested against a hand-written expectation.
One day of events told as prose a person reads in ten seconds — computed on read, deterministic, and the same words at every door.
Done looks like
src/digest.tsexportsdigest(db, who, { day })returning{ day, sentence, counts, top }.sentenceis one paragraph assembled from the counts: the busiest sources first, each with its kinds ("bazaar placed 12 orders and shipped 9"), at most four clauses with a "and three other apps" tail, no clause for a zero, and one fixed sentence when the day is empty.dayis validated^\d{4}-\d{2}-\d{2}$, refused in the future, and defaults to yesterday in UTC; a malformed day isinvalid, not an empty paragraph pretending nothing happened.GET /api/digest?day=, a server-rendered/digestpage linking each clause back into the timeline filtered for that day, an MCP tooldaily_digest, andDin the terminal. All four print the same sentence.src/digest.test.tsinserts a fixed set of rows and asserts the exact sentence, character for character — the empty day, the single-source day, the tail past four sources, and a subscribed person's narrower paragraph.Files:
src/digest.ts,src/digest.test.ts,src/routes.ts,src/page.ts,src/mcp.ts,src/tty.ts,README.mdRisks: prose assembled from data goes wrong at the edges — one event, zero events, a source with a hyphen, a kind that is a prefix of another. Pluralisation is a rule, not an
s. The query is aGROUP BYover one day and must use the indexes from #4 rather than scanning.Suggested: Torque — an exact result stated in words, tested against a hand-written expectation.