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

A card someone moves appears for everyone within a second: one in-process bus and server-sent events #12

shipped production is serving it opened this has it
opened this

Make a board live: every change publishes on one in-process bus, and each open board page streams those changes over server-sent events and applies them to the DOM without a reload.

Done looks like

  • [ ] src/bus.ts — one concept, no dependency: publish(boardId, event) and subscribe(boardId, fn): () => void, plain data in and out, and unsubscribing leaves no listener behind.
  • [ ] Every mutation in src/routes.ts publishes exactly one event after the write succeeds: kind (card.added, card.moved, card.edited, card.deleted, column.added, column.renamed, column.moved, column.deleted, board.renamed, undo), the board id, who did it (from who()), and the ids the page needs to apply it.
  • [ ] GET /b/<id>/events streams text/event-stream for a board that exists (404 otherwise), with cache-control: no-store, x-accel-buffering: no, a comment heartbeat about every 25 s, and a clean teardown that unsubscribes when the client goes away. It carries the same security floor as every other response.
  • [ ] The board page applies events: a card another person moves, adds, edits or deletes lands in the right place within a second, the column counts stay right, and focus and the filter box are not disturbed.
  • [ ] My own change does not flicker or double-apply — mutations carry a per-tab id that the stream echoes back and the page ignores.
  • [ ] A dropped stream reconnects and the page reconciles rather than drifting (re-read the board on reconnect is fine; say so in the README).
  • [ ] Tests: two subscribers on one board both receive a publish and a subscriber on another board receives nothing; unsubscribe drops the listener; the endpoint answers text/event-stream and emits a frame when a card is moved by another request; a closed client leaves no subscriber behind.
  • [ ] No new dependency, no layout shift, bun test passes.

Files

src/bus.ts (new), src/bus.test.ts (new), src/routes.ts, static/board.<new hash>.js, src/pages/board.ts (the BOARD_JS hash), src/server.test.ts, README.md.

Risks

Subscribers that are never removed are a memory leak and, eventually, the app; an event applied by the page that triggers another POST is an echo loop; a stream held open per tab needs a sane cap per board; publish before the write commits shows a change that did not happen.

Suggested brief: scout (instant feedback, small and correct).

commented

Correction to this issue's last line: the briefs I named (ledger, torque) are not on this crew — I invented them in the run that opened this. The crew is atlas (planning), scout (builds), mason (maintains and refactors), sentinel (security review) and juniper (experience review). Read the suggestion here as scout to build, with sentinel reviewing the pull where the issue asks for a security review and juniper where the change is visible on the page. A person still assigns.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs