Give the admin role something to do: one read-only page of numbers, so whoever runs this app can see how it is doing without opening the database file.
What it is like today.admin is a role the forge signs into every request and this app's only use of it is a badge next to a name (src/pages/board.ts:144 prints · admin). Nothing anywhere answers how many boards exist, how big the database has grown, how many tabs are on the stream, or how often a limit is refusing people — the information exists (src/db.ts's tables, bus.subscriberCount, the buckets in src/limits.ts) and has no reader. When a board fills up, when the disk grows, or when #78's refusal lines start appearing, there is no page to look at.
Done looks like
[ ] GET /admin renders for who.role === "admin" only. Everyone else — anon, ordinary user, an unsigned request — gets the same 404 the app gives any unknown path, so the page's existence is not advertised. There is no link to it from anywhere.
[ ] It shows numbers, never contents: counts of boards, columns, cards and action rows; the database file's size on disk; how many boards have an open event stream and how many connections in total; the process's uptime and FORGE_APP_ENV; and, if #129 has landed, the newest snapshot's timestamp. No board names, no card titles, no owner names, no ids — an operator gets scale, not other people's work, and the pull says that is the rule.
[ ] Every count is one indexed SQL aggregate, computed on the request, with the whole page answering in single-digit milliseconds on a database at this app's caps. It is a page, not an endpoint: server-rendered HTML with the existing shell() and the security floor, cache-control: private, no-store.
[ ] It is read-only. No button deletes a board, resets a limit or touches data — if an operator needs that, it is a separate issue with its own review.
[ ] Tests that fail without the change: an admin identity gets 200 and the counts match the fixture; a user identity gets 404; an anonymous request gets 404; a forged identity header (bad signature) gets 404; the rendered page contains no board name or card title from the fixture.
[ ] The README says the page exists, who can see it, and what it deliberately does not show.
Files I expect to change
src/routes.ts (one path), src/pages/admin.ts (new, one concept: the numbers page), src/db.ts (a small counts() read), src/server.test.ts or a new src/admin.test.ts, README.md.
Risks
The temptation is to show the newest boards, "who is on what", or a link into a board to investigate. That turns an operator page into a way to read every private board with one role flip. Keep it to aggregates, and make the test that greps for fixture titles the thing that holds the line.
Counting rows on a large database inside a request is fine here because the caps bound it — but write the aggregates against the indexes that exist (see #96) rather than COUNT(*) over a scan, and say which in the pull.
Give the
adminrole something to do: one read-only page of numbers, so whoever runs this app can see how it is doing without opening the database file.What it is like today.
adminis a role the forge signs into every request and this app's only use of it is a badge next to a name (src/pages/board.ts:144prints· admin). Nothing anywhere answers how many boards exist, how big the database has grown, how many tabs are on the stream, or how often a limit is refusing people — the information exists (src/db.ts's tables,bus.subscriberCount, the buckets insrc/limits.ts) and has no reader. When a board fills up, when the disk grows, or when #78's refusal lines start appearing, there is no page to look at.Done looks like
GET /adminrenders forwho.role === "admin"only. Everyone else — anon, ordinary user, an unsigned request — gets the same 404 the app gives any unknown path, so the page's existence is not advertised. There is no link to it from anywhere.FORGE_APP_ENV; and, if #129 has landed, the newest snapshot's timestamp. No board names, no card titles, no owner names, no ids — an operator gets scale, not other people's work, and the pull says that is the rule.shell()and the security floor,cache-control: private, no-store.useridentity gets 404; an anonymous request gets 404; a forged identity header (bad signature) gets 404; the rendered page contains no board name or card title from the fixture.Files I expect to change
src/routes.ts(one path),src/pages/admin.ts(new, one concept: the numbers page),src/db.ts(a smallcounts()read),src/server.test.tsor a newsrc/admin.test.ts,README.md.Risks
COUNT(*)over a scan, and say which in the pull.Suggested: scout, reviewed by sentinel.