Public: anyone can read this; the crew can change it.
/fleet: a server-rendered table of what today's selection grammar selects, with the query line in the URL #10071
Check · Review · Land · Live done — Landed and live in production.
The new `GET /fleet?q=<query line>&sort=<column>` page shows the rows `src/fleet/selection.ts` selects as a server-rendered table, so an operator can see a fleet operation's rows before acting on them. `selection.ts` is unchanged: it supplies the grammar, the visibility rule (a repository you cannot read is absent) and the row limit.
shipped production is serving it deploy 4cb4bc0
The new GET /fleet?q=<query line>&sort=<column> page shows the rows src/fleet/selection.ts selects as a server-rendered table, so an operator can see a fleet operation's rows before acting on them. selection.ts is unchanged: it supplies the grammar, the visibility rule (a repository you cannot read is absent) and the row limit.
src/fleet/rows.ts(new): takes the selected ids and returns plain records in the same order. One query per kind:
- repo: repository, autonomy, visibility, app (its access, or none)
- run: started, repo, kind, agent, state, cost
- issue: repo, number, title, state, assignee
src/web/fleet-table.ts(new, pure): the columns per kind,readSortandsortRows, andfleetTable. Each header links to?q=…&sort=<column>; on the sorted column the link flips to-<column>and the header carriesaria-sort. It works without JavaScript and uses the existingtable/data-denseclasses, so the design system gains no new component. Every cell goes throughhtml, which escapes it.src/web/pages/fleet.ts(new): the form with the query line, a row count, and the table. A line that does not parse, or selects too many rows, shows its error beside the field with status 200 and nothing selected. A signed-out visitor reads as an id no account has, so only public repositories with owners in good standing appear.src/http/server.ts:GET /fleetis registered next to/fleet/graph.- Reachability and docs: the fleet graph's head gets a "Rows" button linking to
/fleet; the hollow-work test required a link from a page people can reach.docs/design/audit.mdgets the page's entry, anddocs/APPS.mdgets a paragraph on using it. test/fleet-table.test.ts(new) covers:
- render with the row count and the security headers - the empty query - a parse error shown beside the line with status 200 - sorting both ways, with ties keeping the selection's order - a private repository absent for a visitor and present for its owner - escaping of every cell - a 1,000-row table rendering in under 100 ms
What to look at, and what I left out:
- Row limit: the page says "at most 500", not 1,000.
selection.tscaps a selection atMAX_ROWS = 500and refuses a larger one rather than cutting it, and this issue says not to change it. So the 1,000-row timing test runs on the pure renderer. scripts/gate.tsis unchanged: the gate now includes every test file thatEXCLUDEDdoesn't name, so the new test is gated without an entry.- Not done: no paging, no API or terminal version of the page, and no new grammar.
- Not done: the issue asked me to keep a plan on it with
keep_plan, but this run had no such tool, so there is no plan on the issue.
Checks: bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts pass. scripts/fast.ts could not narrow the run because server.ts changed (it named 1,531 files). So I ran the tests that cover the change instead, and all pass: fleet-table, fleet-graph, fleet-ops, router, hollow, design-audit, cookie-flags, openapi, settings-routes, forge-webspec.
To try it: open /fleet?q=repo%20autonomy:assisted, click a header to sort, then try repo spent:4 to see the error beside the line.
🤖 Generated with Claude Code
The new GET /fleet?q=<query line>&sort=<column> page shows the rows src/fleet/selection.ts selects as a server-rendered table, so an operator can see a fleet operation's rows before acting on them. selection.ts is unchanged: it supplies the grammar, the visibility rule (a repository you cannot read is absent) and the row limit.
src/fleet/rows.ts(new): takes the selected ids and returns plain records in the same order. One query per kind:
- repo: repository, autonomy, visibility, app (its access, or none)
- run: started, repo, kind, agent, state, cost
- issue: repo, number, title, state, assignee
src/web/fleet-table.ts(new, pure): the columns per kind,readSortandsortRows, andfleetTable. Each header links to?q=…&sort=<column>; on the sorted column the link flips to-<column>and the header carriesaria-sort. It works without JavaScript and uses the existingtable/data-denseclasses, so the design system gains no new component. Every cell goes throughhtml, which escapes it.src/web/pages/fleet.ts(new): the form with the query line, a row count, and the table. A line that does not parse, or selects too many rows, shows its error beside the field with status 200 and nothing selected. A signed-out visitor reads as an id no account has, so only public repositories with owners in good standing appear.src/http/server.ts:GET /fleetis registered next to/fleet/graph.- Reachability and docs: the fleet graph's head gets a "Rows" button linking to
/fleet; the hollow-work test required a link from a page people can reach.docs/design/audit.mdgets the page's entry, anddocs/APPS.mdgets a paragraph on using it. test/fleet-table.test.ts(new) covers:
- render with the row count and the security headers - the empty query - a parse error shown beside the line with status 200 - sorting both ways, with ties keeping the selection's order - a private repository absent for a visitor and present for its owner - escaping of every cell - a 1,000-row table rendering in under 100 ms
- Second commit (empty): adds the
Pipeline:,Why now:andTry:lines the check requires, after the first push failed only on those two gate rules.
What to look at, and what I left out:
- Row limit: the page says "at most 500", not 1,000.
selection.tscaps a selection atMAX_ROWS = 500and refuses a larger one rather than cutting it, and this issue says not to change it. So the 1,000-row timing test runs on the pure renderer. scripts/gate.tsis unchanged: the gate now includes every test file thatEXCLUDEDdoesn't name, so the new test is gated without an entry.- Not done: no paging, no API or terminal version of the page, and no new grammar.
- Not done: the issue asked me to keep a plan on it with
keep_plan, but this run had no such tool, so there is no plan on the issue.
Checks: bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts pass. scripts/fast.ts could not narrow the run because server.ts changed (it named 1,531 files). So I ran the tests that cover the change instead, and all pass: fleet-table, fleet-graph, fleet-ops, router, hollow, design-audit, cookie-flags, openapi, settings-routes, forge-webspec. Nothing was re-run for the second commit because it changes no files.
To try it on the preview: open /fleet?q=repo%20owner:trevato, click the "autonomy" header twice to sort both ways, then open /fleet?q=repo%20spent:4 to see the error beside the line.
🤖 Generated with Claude Code
The new GET /fleet?q=<query line>&sort=<column> page shows the rows src/fleet/selection.ts selects as a server-rendered table, so an operator can see a fleet operation's rows before acting on them. selection.ts is unchanged: it supplies the grammar, the visibility rule (a repository you cannot read is absent) and the row limit.
src/fleet/rows.ts(new): takes the selected ids and returns plain records in the same order. One query per kind:
- repo: repository, autonomy, visibility, app (its access, or none)
- run: started, repo, kind, agent, state, cost
- issue: repo, number, title, state, assignee
src/web/fleet-table.ts(new, pure): the columns per kind,readSortandsortRows, andfleetTable. Each header links to?q=…&sort=<column>; on the sorted column the link flips to-<column>and the header carriesaria-sort. It works without JavaScript and uses the existingtable/data-denseclasses, so the design system gains no new component. Every cell goes throughhtml, which escapes it.src/web/pages/fleet.ts(new): the form with the query line, a row count, and the table. A line that does not parse, or selects too many rows, shows its error beside the field with status 200 and nothing selected. A signed-out visitor reads as an id no account has, so only public repositories with owners in good standing appear. The page reportssessionandrenderin Server-Timing, as every GET page must.src/http/server.ts:GET /fleetis registered next to/fleet/graph.- Reachability and docs: the fleet graph's head gets a "Rows" button linking to
/fleet; the hollow-work test required a link from a page people can reach.docs/design/audit.mdgets the page's entry, anddocs/APPS.mdgets a paragraph on using it. - No preview screenshot:
forge.jsonlooksis capped at 24 paths and all are taken./fleetgoes inNOT_A_PAGEintest/forge-looks.test.ts, with that reason. test/fleet-table.test.ts(new) covers:
- render with the row count and the security headers - the empty query - a parse error shown beside the line with status 200 - sorting both ways, with ties keeping the selection's order - a private repository absent for a visitor and present for its owner - escaping of every cell - a 1,000-row table rendering every row in sorted order - the page's SQL statement count not growing with its row count
What to look at, and what I left out:
- Timing test replaced: the issue asked for a test proving a 1,000-row selection renders in under 100 ms. The repository bans stopwatch bounds in tests (#7824), so that test is gone. The bound is now counted work: a three-row page may cost no more statements than a one-row page.
- Row limit: the page says "at most 500", not 1,000.
selection.tscaps a selection atMAX_ROWS = 500and refuses a larger one rather than cutting it, and this issue says not to change it. So the 1,000-row test runs on the pure renderer. scripts/gate.tsis unchanged: the gate now includes every test file thatEXCLUDEDdoesn't name, so the new test is gated without an entry.- Not done: no paging, no API or terminal version of the page, and no new grammar.
- Not done: the issue asked me to keep a plan on it with
keep_plan, but this run had no such tool, so there is no plan on the issue.
Checks: bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts pass. These test files pass: fleet-table, forge-looks, server-timing-sweep, hollow, design-audit, fleet-graph, router, flake, rule-tests-always. The full suite was not run here; the forge runs it on this push. On the first push, fleet-ops, cookie-flags, openapi, settings-routes and forge-webspec also passed.
To try it on the preview: open /fleet?q=repo%20owner:trevato, click the "autonomy" header twice to sort both ways, then open /fleet?q=repo%20spent:4 to see the error beside the line.
🤖 Generated with Claude Code
The security side is sound: the selection does the visibility check, cells are escaped, the headers are on the response, and the SQL binds ids through json_each. Three things need fixing in one round.
- Both doors (fix first). There is no terminal screen in src/tui/ and no entry for it where AGENTS.md says. The pull says outright that it adds none. There is also no docs/features/fleet.md. The how-to went into docs/APPS.md, but the checklist wants docs/features/<name>.md. Add a TUI screen that reads the same query line and shows the same rows, gated by the same selection and visibility rule, with the same sort. Write docs/features/fleet.md, and keep README.md untouched.
- Design principle 1: the page explains itself twice. It has an intro paragraph, then a placeholder example in the field, then a second Try paragraph repeating the examples. Keep one confident sentence and drop the rest.
- Box 6: the 1,000-row under-100 ms test was replaced with a statement-count test. I accept the reason (#7824). The issue text still asks for the timing test, so either say on the issue that the box changed or keep the box open.
Smaller: the VISITOR constant in fleet.ts is built with as unknown as User. Give selectRows an explicit viewer type for a visitor instead of faking a User.
GET /fleet?q=<query line>&sort=<column> shows the rows a fleet query line selects, as a server-rendered table, so an operator can see them before an operation acts. The same rows are in the terminal as "Fleet rows" in the palette. The grammar and the rule that only rows the viewer may read appear both come from src/fleet/selection.ts.
Shared by both doors
- Reading the rows:
src/fleet/rows.ts(viewFleet) parses the line, selects the ids for the viewer, then reads their rows by primary key throughjson_each. A line that does not parse, or selects more than 500 rows, comes back as an error with nothing selected. - Columns and sort:
src/fleet/columns.tsholds them. Each kind has its own columns:
- repo: repository, autonomy, visibility, app - run: started, repository, kind, agent, state, cost - issue: repository, number, title, state, assignee
The table sorts by one column, either direction, and ties keep the selection's order.
- Signed-out visitors:
selectRowsnow takesviewer: User | undefined. A visitor isundefinedand sees only public repositories; this replaces theas unknown as Usercast. The grammar itself is unchanged.
Web
- The page (
src/web/pages/fleet.ts) has one sentence of purpose, the query field with a single placeholder example, a row count, and the table. A parse error shows beside the field with status 200. - The table (
src/web/fleet-table.ts) is pure: rows in, escaped HTML out, using the existingtable/data-denseclasses. Each header links to?sort=<column>, the sorted one flips to-<column>and carriesaria-sort, and it works without JavaScript. - Wiring: the route is next to
/fleet/graphinsrc/http/server.tsand reportssessionandrenderin Server-Timing. The fleet graph links to it as "Rows". It has an entry indocs/design/audit.md. - No preview screenshot:
forge.jsonlooksis capped at 24 paths and all are taken, so/fleetgoes in the not-a-page list intest/forge-looks.test.ts, with that reason.
Terminal
src/tui/screens/fleet.ts, registered next to packs inforgeCommands, is open to everyone because visibility is the selection's rule.- Keys:
/writes a new query line,ssorts by the next column,rreverses,j/kscroll,escgoes back. - A line that does not parse keeps the prompt open with its reason.
Docs: docs/features/fleet.md covers both doors and the grammar. README.md is untouched.
Tests
test/fleet-table.test.ts:
- render with the row count and the security headers - a parse error at status 200 - sorting both ways, through the header links - a private repository absent for a visitor and present for its owner - escaping of every cell - a 1,000-row table rendered in full - the page's SQL statement count not growing with its rows
test/tui-fleet.test.ts: the guest and owner views, the parse error in the prompt,sandr, and the line layout.test/palette-roster.test.ts: lists the new palette entry. That test was not wrong; the roster grew.- The gate takes every test file not excluded, so
scripts/gate.tsneeds no entry.
Left out, and what to look at
- Timing box: the issue's "1,000 rows in under 100 ms" test is a stopwatch, which the repository refuses in tests (#7824). I replaced it with the counted-work tests above and said so in a comment on #565.
- Row cap:
selection.tsrefuses more than 500 rows rather than cutting the list, so the page says "at most 500", not 1,000. - Not done: no paging, no API endpoint, no new grammar.
- No plan on the issue: the issue asked me to keep a plan with
keep_plan, but this run had no such tool.
Checks: bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts pass. scripts/fast.ts could not narrow the run because server.ts changed, so I ran the tests that cover the change, and they pass:
fleet-table,tui-fleet,fleet-ops,fleet-graphforge-looks,server-timing-sweep,hollow,design-auditpalette-roster,palette-entries,features-empty,readme-capflake,rule-tests-always- the terminal suites that name palette commands
The full suite was not run here; the forge runs it on this push.
Try it on the preview: open /fleet?q=repo%20owner:trevato, signed out and then signed in, and click a column header twice. Then try /fleet?q=repo%20spent:4 to see the error beside the field. In the preview's terminal, open the palette, choose "Fleet rows", type repo owner:trevato, then press s and r.
🤖 Generated with Claude Code
Both earlier asks are met: selectRows now takes viewer: User | undefined with no cast, and the page is one purpose sentence, one placeholder and no repeated hint. The new terminal screen reads through the same viewFleet, so a guest sees public rows only and the owner sees all, and test/tui-fleet.test.ts proves it. The how-to is in docs/features/fleet.md and README is untouched. Cells are escaped and the input is capped. Non-blocking: the stopwatch test is replaced by a counted-work test and a full 1,000-row render, which fits the repo rule against clocks in tests.
The new
GET /fleet?q=<query line>&sort=<column>page shows the rowssrc/fleet/selection.tsselects as a server-rendered table, so an operator can see a fleet operation's rows before acting on them.selection.tsis unchanged: it supplies the grammar, the visibility rule (a repository you cannot read is absent) and the row limit.src/fleet/rows.ts(new): takes the selected ids and returns plain records in the same order. One query per kind:- repo: repository, autonomy, visibility, app (its access, or
none) - run: started, repo, kind, agent, state, cost - issue: repo, number, title, state, assigneesrc/web/fleet-table.ts(new, pure): the columns per kind,readSortandsortRows, andfleetTable. Each header links to?q=…&sort=<column>; on the sorted column the link flips to-<column>and the header carriesaria-sort. It works without JavaScript and uses the existingtable/data-denseclasses, so the design system gains no new component. Every cell goes throughhtml, which escapes it.src/web/pages/fleet.ts(new): the form with the query line, a row count, and the table. A line that does not parse, or selects too many rows, shows its error beside the field with status 200 and nothing selected. A signed-out visitor reads as an id no account has, so only public repositories with owners in good standing appear.src/http/server.ts:GET /fleetis registered next to/fleet/graph./fleet; the hollow-work test required a link from a page people can reach.docs/design/audit.mdgets the page's entry, anddocs/APPS.mdgets a paragraph on using it.test/fleet-table.test.ts(new) covers:- render with the row count and the security headers - the empty query - a parse error shown beside the line with status 200 - sorting both ways, with ties keeping the selection's order - a private repository absent for a visitor and present for its owner - escaping of every cell - a 1,000-row table rendering in under 100 ms
What to look at, and what I left out:
selection.tscaps a selection atMAX_ROWS = 500and refuses a larger one rather than cutting it, and this issue says not to change it. So the 1,000-row timing test runs on the pure renderer.scripts/gate.tsis unchanged: the gate now includes every test file thatEXCLUDEDdoesn't name, so the new test is gated without an entry.keep_plan, but this run had no such tool, so there is no plan on the issue.Checks:
bun scripts/gate.ts,bun x tsc --noEmitandbun scripts/design-lint.tspass.scripts/fast.tscould not narrow the run becauseserver.tschanged (it named 1,531 files). So I ran the tests that cover the change instead, and all pass:fleet-table,fleet-graph,fleet-ops,router,hollow,design-audit,cookie-flags,openapi,settings-routes,forge-webspec.To try it: open
/fleet?q=repo%20autonomy:assisted, click a header to sort, then tryrepo spent:4to see the error beside the line.🤖 Generated with Claude Code
Closes #565