What it solves
A board is good at being a board. It is bad at being asked questions.
The columns are a fixed way of looking at the work, and anything cutting across them — which open items have no assignee and slip past this sprint, what does this person actually carry, what has dates and what is guesswork — either isn’t expressible or means scrolling and counting. There is no timeline. There is no export. And because reading Projects v2 requires a token, the view you worked out cannot be handed to anyone; you can describe it, or screenshot it, but you can’t send it.
Two things change that. The board is mirrored to public JSON by a scheduled job that holds the token, so no viewer needs one. And the whole view — query plus which of the three slices you’re on — lives in the URL, so “the thing I’m looking at” becomes a link. The query language is deliberately GitHub’s own vocabulary rather than an invented one, made clickable: every chip you press rewrites the same query string, which stays the single source of truth.
The board isn’t replaced — it’s mirrored to public JSON so no viewer needs a token, and the query plus the active view live in the URL. That is what turns “the thing I’m looking at” into something you can send someone.
What it does
Makes a Projects v2 board answerable. Three views over the same filtered set:
- Table — the dense grid; sort by any column, choose your columns (remembered in
localStorage). - By assignee — one lane per person plus an Unassigned bucket.
- Timeline — date-axis bars grouped by any field.
The interesting part is the query language: GitHub’s own filter vocabulary, made clickable.
assignee: / author: / label: / milestone: / repo: / type: / reason: plus any board
field by name, is:open|closed|merged|issue|pr|draft, no: / has:, - negation, comma-OR,
repeated-qualifier-AND, quoting, and date comparisons like updated:>@today-7d. Clicking any chip
anywhere rewrites the one query string — a single source of truth, no parallel filter object.
Also: / to focus search (like GitHub), a copyable link that encodes the full view state in the URL hash, and CSV export of exactly the filtered rows with every board field.
Where the data comes from
board-explorer.yml mirrors the board to JSON on the board-explorer/data branch every 6 hours
(and on demand), using PROJECT_TOKEN_FOR_BOARD_READS. The app then only ever reads public JSON:
commit-SHA raw URL → branch path → bundled public/data/ snapshot.
The generator talks GraphQL directly rather than using gh project item-list, which cannot return
state, merged, label colours, avatars, milestone, timestamps or sub-issue progress. It is
field-agnostic — fields are discovered via fields(first: 60), so adding a board column needs no
code change in either the generator or the SPA.
Run it locally
cd board-explorer
npm install
npm run dev
# Offline: regenerate from a canned GraphQL bundle, and exercise the query grammar.
python3 board-explorer/scripts/test_generate.py
node board-explorer/scripts/test_search.ts