Bureau of Almost-Happened Events
A counterfactual story archive where every docket is a branching document graph — and the editors build that graph on a live investigation board instead of a CMS form.
Links
| Live archive | https://bureau-events.pages.dev |
| Repository | https://github.com/mukeshkbj/bureau-events |
| Studio | https://bureau-events.sanity.studio |
| Bureau Board (App SDK app) | https://www.sanity.io/@o041d79wc/application/iv082hu12r39wvgimogfoxhc |
| Sanity project |
9n512feo · dataset production (public read) |
| Stack | Astro 7 · Studio v6 · App SDK (React 19) · Functions + Blueprints · Cloudflare Pages |
What it is
The Bureau files incident reports for events that almost happened. A visitor
opens a docket — say, The Town That Arrived One Minute Late — and reads the
first incident report. Each report ends in labeled choices; each choice is a
door into another incident. Evidence (stopwatches, memos, photographs) arrives
taped into the reports. Every path terminates in a stamped designation:
REPRIEVE, ANOMALY, or CATASTROPHE. The Bureau does not explain the
difference.
The unusual part is the authoring surface. The archive's stories are stored as
a graph of documents, so the editorial tool is a live investigation board
— nodes pinned to a desk, edges drawn between them, diagnostics stamped on
broken graphs, all updating in real time over the Sanity App SDK.
The schema is the product
Four document types, no plumbing:
-
caseFile— a docket: title, slug, premise, filing code (BAH-####), sigil, and a review status that gates publication. -
incident— one node in the graph.order == 0marks the entry point.choices[]are objects holding a label, a Bureau-voiced "consequence note", and anextreference to a sibling incident. An incident with no choices must declareending.isEnding— enforced by document-level validation. -
artifact— evidence attached to an incident (evidence → artifact), rendered in the reader as taped-on registry cards. -
review— an append-only approval trail. Each round carries the author's note, then the curator's decision.
Because the story is the reference graph, the public reader is a single GROQ
projection: one query returns the docket plus every incident with its resolved
choices (choices[]{ "nextId": next._ref }), ending, and dereferenced
evidence. The frontend walks it as a pure client-side graph traversal with
#i=<incident-id> deep links.
Past the Studio: the Bureau Board (App SDK)
The board is a standalone Sanity app, not a Studio plugin. It shows the same
documents — live — but as the shape an editor actually thinks in:
- A case rail listing dockets with their review status pills.
- A graph canvas: incidents as pinned cards, SVG edges between choices, layered by BFS distance from the entry. Endings, entry points, and flagged nodes each get their own look.
- Live diagnostics — the same validator the tests run, projected on the board: unreachable nodes, choices pointing at missing targets, non-endings with no way out.
-
Full authoring: file a new docket (auto-assigns the next
BAH-####and a slug), add incidents straight onto the board, grow branches with "+ New incident linked from here", edit reports/endings in a drawer, attach or create artifacts as evidence. - Review panel: submit a round with an author's note; curators approve or request changes in place.
- Activity toasts for document events — open the board in two windows and watch edits land.
Everything the board writes goes through useCreateDocument /
useEditDocument / useApplyDocumentActions — real documents, real
persistence, real-time. The Studio keeps its own job: schema validation plus
two custom document actions, Submit for review and Publish case graph
(the latter publishes the docket and every child draft in one transaction).
Workflows: review as data, a Function as the messenger
The challenge asked to see a process modeled next to the content. Here it is:
- An author files a
reviewdocument (decision: pending, round number, author's note) from the board or Studio. - A curator edits that same document —
decision,decisionNote,reviewer— and publishes it. - A Sanity Function (
sync-review-decision, deployed via a Blueprint) listens for review events, unions the event payload with the queryable reviews so the firing document is authoritative, and patchescaseFile.reviewStatus→approved/changesRequested. - Only
reviewStatus == "approved"dockets are picked up by the public build — the approval literally turns the site on.
Round-tripping works: request changes → author resubmits round N+1 → approved.
(The review panel sits in the same board — the pasted screenshot above shows it collapsed; the Function's own log records each decision patch it applies.)
The build story (the honest part)
A few things broke in instructive ways:
-
Imagined SDK APIs. First pass of the board was written against the App
SDK hooks I assumed existed (
useQueryreturning pages, separate draft saves). The real@sanity/sdk-reactsurface is different —useDocumentsfor type queries,useQuery({query, params})for GROQ,useEditDocumentwith whole-document updaters. The fix was reading the type declarations and rewriting five components. -
Dotted
_ids are anonymous-invisible. The seed used IDs likeincident.clk-00. The dataset was public, authenticated queries worked — but anonymous reads returnedomitted: permission. Only a rawcurlrevealed that.in an_idnamespaces the document away from public access. Everything was re-seeded with hyphenated IDs. -
A Function race that looked like a no-op. The Function queried "latest
review" after receiving the event — but the event can fire before the new
document is visible to the query index. Round 3 silently computed against
stale data and the idempotency check skipped the patch permanently. The fix
folds
event.datainto the decision set so the firing document is always authoritative. Logs confirmed the patch landing on round 4. -
Layering a cyclic graph. The first
computeLayersused longest-path relaxation — which diverges on cycles. Replaced with shortest-path BFS.
Stack
Astro 7 (static) · Sanity Studio v6 · App SDK (React 19) · Sanity Functions +
Blueprints · Cloudflare Pages · pnpm monorepo with a shared
@bureau/content-model package (schemas, GROQ, graph validator + tests, seed).
Sanity Project Details
Project name Bureau of Almost-Happened Events
Live site https://bureau-events.pages.dev
Repository https://github.com/mukeshkbj/bureau-events
Studio https://bureau-events.sanity.studio
App (Bureau Board) https://www.sanity.io/@o041d79wc/application/iv082hu12r39wvgimogfoxhc
Organization o041d79wc
Try it
- Read the archive — https://bureau-events.pages.dev — no login needed; take a path to an ending.
- The editorial surfaces (Bureau Board app + Studio) are gated by Sanity org membership — the Board link is above, and the screenshots, walkthrough videos, and the repo's README show the full authoring loop end to end.
-
Judges — Sanity project ID
9n512feo, public datasetproduction:https://9n512feo.api.sanity.io/v2026-09-01/data/query/production?query=*%5B_type%20%3D%3D%20%22caseFile%22%5Dreturns the docket graph anonymously. - Repo README has setup, seeding (token or CLI-session import), deploy steps, and the docket-authoring walkthrough.
Demo walkthrough — index → dossier → choices → evidence → ending (62s, docs/media/archive-walkthrough.webm; upload to DEV as a video embed):
https://github.com/mukeshkbj/bureau-events/blob/master/docs/media/archive-walkthrough.webm
Editorial round-trip — Bureau Board files docket BAH-0007, submits it for
review, a curator approves, and the sync-review-decision Function lands the
APPROVED decision on the review trail — live, unedited (46s,
docs/media/editorial-review-roundtrip.webm):
https://github.com/mukeshkbj/bureau-events/blob/master/docs/media/editorial-review-roundtrip.webm






Top comments (0)