Public: anyone can read this; the crew can change it.
The admin Billing page shows the last Stripe event received and when, and how many signatures were refused #11291
Check · Review · Land · Live done — Landed and live in production.
The admin Billing page now has one line under Provider with the last Stripe event and the day's refused signatures. gate, `tsc`, `design-lint` and `test/billing.test.ts` plus `test/stripe-hook.test.ts` all pass here. The forge's full check hasn't run yet.
shipped production is serving it deploy 9328ecc
2 earlier comments · 2 events
The admin Billing page now has one line under Provider with the last Stripe event and the day's refused signatures. gate, tsc, design-lint and test/billing.test.ts plus test/stripe-hook.test.ts all pass here. The forge's full check hasn't run yet.
The line reads "Last event: invoice.paid at 2026-11-11 10:00 UTC · 1 signature refused today (UTC)". With nothing received it reads "No event yet · 0 signatures refused today (UTC)". It always shows, whether or not Stripe is connected.
- Code: a new
hookSummary(app)insrc/http/stripe_hook.tsreads the neweststripe_eventrow and today'sstripe_hook_refusedrows.src/web/pages/billing.tsrenders it, and the page is still admin-only. - Test: a new test in
test/billing.test.tsrenders both states, empty and with an event plus a refusal.
I left out:
- Rolling window: the refused count is the current UTC day, not a rolling 24 hours, because refusals are stored per day. The line says "today (UTC)" so it doesn't claim more than it counts.
- Past events: there is no list of earlier events.
To try it, sign in as an admin and open /settings/billing.
The first push was refused until the branch had a commit with a "Try:" line, so I added an empty one.
The page line works and is escaped, but it misses the both-doors rule and DESIGN principle 7. Fix in one round:
- Both doors: add the same fact to the terminal, a screen in
src/tui/with its entry where AGENTS.md says, admin-gated exactly like the page (a guest sees nothing), with a test. - Add
docs/features/<name>.mdsaying how to read the last Stripe event and refused signatures on both doors. Do not put it in README.md. - Render the facts as
dlrows (status-rows/status-row, as the Stripe block below already does), not a prose paragraph (principle 7). - Add a test that a non-admin gets 404 and sees no event data; the TUI test should assert the same gating.
The design-lint check is reported passing by the builder; I did not re-run it.
All four review points are addressed and pushed. tsc, design-lint, scripts/gate.ts and the billing, billing-TUI and stripe-hook tests (28) pass here. The forge's full check hasn't run yet.
- Page rows: the Provider block now has a
status-rowslist with twostatus-rows, "Last event" and "Refused today". With nothing received, "Last event" reads "No event yet". I dropped the "(UTC)" parenthetical from the label; the event time still ends in "UTC". - Terminal: the existing Billing screen shows the same facts. The line is "Last event: invoice.paid 2026-11-11 10:00 UTC. Refused today: 2", or "No event yet", and it also appears in the empty state. I put the facts into
readBilling(asevents) rather than adding a new screen. Page and terminal share the admin-only gate, so no new palette entry was needed. - Doc:
docs/features/stripe-events-line.mdcovers both doors. I left README.md alone. - Tests:
- test/billing.test.ts now covers the page's two states and a non-admin getting 404 with no event data.
- test/billing-tui.test.ts shows an admin the facts and a non-admin nothing.
The count is still the current UTC day rather than a rolling 24 hours, because refusals are stored per day. The doc says so.
This head does not contain the change the pull is titled for. The diff is a GraphQL schema renderer (src/web/render/graphql.ts, kinds.ts, renderers.ts, a TUI table in workspace.ts, docs/features/file-graphql.md and tests). It has no change to src/web/pages/billing.ts, no hookSummary, no Stripe event line, no refused-signature count, and no billing test. The builder's and sentinel's notes (status-rows, billing TUI, docs/features/stripe-events-line.md) are not in this head either.
Fix first: restore the Billing work on this branch (page rows for Last event and Refused today with the 'No event yet' state, the same facts in the existing Billing TUI screen, admin-only on both, docs/features/stripe-events-line.md, tests for both states plus a non-admin 404 and TUI gating) and drop the unrelated GraphQL renderer into its own pull. Direction: small single-purpose pulls. Then run bun scripts/design-lint.ts and the billing tests again.
The GraphQL code itself is outside this issue, so I did not judge it further.
On /settings/billing, admins now see two rows under Provider: Last event and Refused today. Last event is the type and time in UTC of the newest Stripe event, or "No event yet". Refused today counts the bad or stale signatures on the current UTC day. The terminal Billing screen shows the same two facts in a line under the Stripe status.
Both the page and the terminal screen read them through readBilling (events), so only an admin sees them. Anyone else gets the not-found page, and the terminal screen shows nothing.
- Code:
hookSummary(app)insrc/http/stripe_hook.tsreads the neweststripe_eventrow and today'sstripe_hook_refusedcount.src/web/pages/billing.tsrenders the rows using the existingstatus-rowslist and adds no new styles.src/tui/screens/billing.tsadds the line to the Billing screen. - Tests:
test/billing.test.tscovers both page states (no event yet, then an event and a refusal) and that a non-admin gets a 404 with none of the data.test/billing-tui.test.tschecks the admin sees the line on the terminal screen and a non-admin doesn't. - Doc:
docs/features/stripe-events-line.md.
The review saw a GraphQL schema renderer in this pull, but that code isn't in this branch or in main. The head it was looking at (6f07dc252) already held only the Billing change, so I removed nothing. I merged current main so the diff is now just the seven Billing files. I also added an empty commit with a Direction: line, because the gate asks for one when a feature changes nothing in the pipeline.
The gate, bun x tsc --noEmit and bun scripts/design-lint.ts pass here. The billing tests pass too: test/billing.test.ts, test/billing-tui.test.ts and test/stripe-hook.test.ts, 33 tests, 0 failures. The forge's full check runs on this push.
I left out:
- Rolling window: the refused count is for the current UTC day, not a rolling 24 hours, because refusals are stored per day. The row says "today" to match.
- Past events: there is no list of earlier events, only the newest one.
To try it, sign in as an admin and open /settings/billing, or open the Billing screen from the terminal palette. Signed in as a non-admin, the page gives a 404.
🤖 Generated with Claude Code
The head is now the seven Billing files and nothing else. The earlier asks are met: Provider shows two status-rows (Last event, Refused today) using existing classes, the terminal Billing screen shows the same facts through readBilling and so stays admin-only, docs/features/stripe-events-line.md is added and README is untouched, and tests cover both page states, the non-admin 404 and TUI gating. The queries are fixed SQL with bound parameters and output goes through html. Non-blocking: test/billing.test.ts asserts "<dt>Refused today</dt>" twice in the second test. I could not diff against a591c13d762e because the commit is not in my clone, so I judged the change as shown. The preview answers 200 on / and redirects /settings/billing for an unauthenticated request.
The admin Billing page now has one line under Provider with the last Stripe event and the day's refused signatures. gate,
tsc,design-lintandtest/billing.test.tsplustest/stripe-hook.test.tsall pass here. The forge's full check hasn't run yet.The line reads "Last event:
invoice.paidat 2026-11-11 10:00 UTC · 1 signature refused today (UTC)". With nothing received it reads "No event yet · 0 signatures refused today (UTC)". It always shows, whether or not Stripe is connected.hookSummary(app)insrc/http/stripe_hook.tsreads the neweststripe_eventrow and today'sstripe_hook_refusedrows.src/web/pages/billing.tsrenders it, and the page is still admin-only.test/billing.test.tsrenders both states, empty and with an event plus a refusal.I left out:
To try it, sign in as an admin and open
/settings/billing.The first push was refused until the branch had a commit with a "Try:" line, so I added an empty one.
Closes #11248