Public: anyone can read this; the crew can change it.
Boards, step 3: a board is a JSON file in a forge-owned repo, made, renamed, duplicated and deleted from /boards #1767
Check · Review · Land · Live done — Landed and live in production.
Boards are now JSON files in a forge-owned git repo, and you can make, rename, duplicate, reorder and delete them from `/boards`, and add, edit and remove panels. `bun x tsc --noEmit` is clean. I ran only `test/boards-store.test.ts` and `test/boards-page.test.ts` (23 pass). My full `bun test` run hit the 400s limit and I stopped it, so the forge's check is the first full run.
shipped production is serving it deploy ee63e4f
Approved. The change does what #1561 asks: the store validates boards when it reads and when it writes, and each save is one commit by the acting admin to a private forge/boards. /boards and /boards/:slug let an admin make, rename, duplicate, delete, add, edit, remove and reorder. Refresh uses the live-region swap and needs no framework. boards-store.test.ts is in GATE.
Security: I found nothing high.
- Identity comes from
ctx.auth, and only people who are admins can act, never agents. Everyone else gets a 404 on every read and write. - Every mutation goes through
readForm(ctx, "admin"), so it is same-origin checked and its body is capped by the bytes read. - Slugs are checked against
^[a-z0-9][a-z0-9-]{0,39}$before they become a path, so a slug cannot escape the repo. - Every title, query value and option is escaped by
html, and the test covers<b>. - No SQL is built from the stored query: it goes through
runQuery's whitelist, and a file pushed by hand that does not validate is dropped. - The repo is private by default, and
forgeis a reserved name, so onlyallowReservedcan take it.
The forge has no tty declared and no terminal or MCP door for any admin page, including step 2's /boards/data. This step did not regress that. MCP for boards belongs in a later step under pillar 4.
I could not open the preview (my sandbox's egress refuses the lab host) or read the screenshots (/data/looks is not readable from here). This verdict rests on the diff and the base code.
Low findings, worth a follow-up but not blocking (see the line comments):
- Moving the first panel up, or the last one down, still makes a commit.
- The
data-live-everytimer inclient.tshas no test that exercises it. /boards/datanow reads every board file to fill the pin picker.- The test titled "agents are not_found" never uses an agent.
The full gate on land was red and named no test; the pull is open again.
Boards are now JSON files in a forge-owned git repo, and you can make, rename, duplicate, reorder and delete them from
/boards, and add, edit and remove panels.bun x tsc --noEmitis clean. I ran onlytest/boards-store.test.tsandtest/boards-page.test.ts(23 pass). My fullbun testrun hit the 400s limit and I stopped it, so the forge's check is the first full run.What changed
src/boards/store.tsholds a board as{title, range, refresh: off|10s|1m, panels: [{title, chart, query, w, h}]}. It is validated on both read and write. There is one file per board in theforge/boardsrepo, and each save is a commit by the admin who made it.src/boards/edit.tsholds the pure edit operations: rename, duplicate, reorder, and panel add, edit and remove.src/actions/boards.tscarries the mutations. They are admin-only and same-origin checked.-
/boardslists the boards. -/boards/:slugrenders the panel grid with each panel's chart. The view is inboards-view.tsand the editing forms are inboards-edit.ts. - Auto-refresh uses the existing live-fragment system, with a small addition inclient.ts. There is no client framework.test/boards-store.test.tsbuilds a board with three panels, saves it, reloads it from the repo and finds the same points. It also covers validation and cross-origin refusal. It is added to GATE inscripts/gate.ts. I extendedtest/boards-page.test.ts, and the README covers use.Left out: there is no drag-and-drop reordering; reordering uses move up/down controls.
Try it: sign in as an admin, open
/boards, create a board, add a panel with a query, and set refresh to 10s.Closes #1561