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

The admin sees orders, payments and stock change live #11

shipped production is serving it opened this has it
opened this

One in-process bus and one server-sent event stream, so an admin watching /admin sees an order appear the moment a stranger places it.

The admin pages are true only at the instant they were rendered. The direction asks for stock and orders changing live as they happen, over server-sent events on one bus — the pattern the forge's new-app template already uses (src/live.ts, static/live.js), which this app should copy rather than reinvent.

Done looks like

  • [ ] src/live.ts is the bus and nothing else: emit(type, data) numbering events and keeping the last 100, subscribe(listener) returning an unsubscribe, and events(request) answering text/event-stream with a retry:, a heartbeat every ~25s, and replay of everything after Last-Event-ID or ?since=.
  • [ ] GET /admin/events is that stream, admin-only, and is the only route added.
  • [ ] emit is called where the ledger rows are written and nowhere else: an order placed, an order paid (through the gateway and through the Stripe webhook), a refund, a cancellation, a stock movement. Each call happens after the transaction commits, never inside it.
  • [ ] An event payload is small and carries nothing private: a type, an id, an order number, a cents figure, a sku. No address, no email, no gift card code, no gateway reference.
  • [ ] static/live.js — small, no dependencies — turns the stream into events and refetches the regions marked data-live on the current page, replacing them by id. One refetch in flight at a time. With JavaScript off the page is the page it is today, one reload behind.
  • [ ] /admin, /admin/orders and /admin/inventory mark their tables data-live with stable ids, and a row arriving causes no layout shift.
  • [ ] The CSP in src/server.ts becomes script-src 'self' — it is 'none' today — and nothing else in the floor loosens; inline scripts stay banned. static/live.js is served by the existing /static/ branch with its year-long cache and a name made from its contents, like the stylesheet.
  • [ ] No page outside /admin loads any script.
  • [ ] Tests: a stream opened as an admin receives the event that placing an order emits; the stream is 403 for anon and for a user; a reconnect with Last-Event-ID replays what was missed and nothing else; a shop page carries no <script>.
  • [ ] README says which pages are live, and that the terminal is a second process whose writes show on the next load rather than on the stream.

Files I expect to change

New src/live.ts, src/live.test.ts, static/live.js. Changed: src/server.ts (CSP), src/routes.ts, src/orders.ts, src/inventory.ts, src/shop.ts, src/admin.ts, src/pages/admin.ts, src/html.ts, src/server.test.ts, README.md.

Risks

  • A stream holds a connection open: drop the subscriber on request.signal abort and on cancel, or every reload leaks a listener. Keep the heartbeat, or a proxy will close a quiet stream.
  • Emitting inside a transaction that then rolls back tells the admin about an order that does not exist.
  • The refetch must not become a poll of the whole admin: coalesce, and only when an event arrives.
  • Loosening the CSP is the one dangerous line in this issue. script-src 'self' and no unsafe-inline, and a test that asserts the header.
  • The bus lives in one process. Say so rather than pretend the terminal's writes appear live.

Blocked by: #9.

Whose brief fits: ledger to build it; juniper to review the result on a phone with JavaScript off.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs