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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

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

opened this

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, readSort and sortRows, and fleetTable. Each header links to ?q=…&sort=<column>; on the sorted column the link flips to -<column> and the header carries aria-sort. It works without JavaScript and uses the existing table / data-dense classes, so the design system gains no new component. Every cell goes through html, 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 /fleet is 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.md gets the page's entry, and docs/APPS.md gets 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.ts caps a selection at MAX_ROWS = 500 and 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.ts is unchanged: the gate now includes every test file that EXCLUDED doesn'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

Closes #565

commented

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, readSort and sortRows, and fleetTable. Each header links to ?q=…&sort=<column>; on the sorted column the link flips to -<column> and the header carries aria-sort. It works without JavaScript and uses the existing table / data-dense classes, so the design system gains no new component. Every cell goes through html, 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 /fleet is 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.md gets the page's entry, and docs/APPS.md gets 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.ts caps a selection at MAX_ROWS = 500 and 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.ts is unchanged: the gate now includes every test file that EXCLUDED doesn'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

commented

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, readSort and sortRows, and fleetTable. Each header links to ?q=…&sort=<column>; on the sorted column the link flips to -<column> and the header carries aria-sort. It works without JavaScript and uses the existing table / data-dense classes, so the design system gains no new component. Every cell goes through html, 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 /fleet is 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.md gets the page's entry, and docs/APPS.md gets 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: and Try: 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.ts caps a selection at MAX_ROWS = 500 and 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.ts is unchanged: the gate now includes every test file that EXCLUDED doesn'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

commented

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, readSort and sortRows, and fleetTable. Each header links to ?q=…&sort=<column>; on the sorted column the link flips to -<column> and the header carries aria-sort. It works without JavaScript and uses the existing table / data-dense classes, so the design system gains no new component. Every cell goes through html, 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 reports session and render in Server-Timing, as every GET page must.
  • src/http/server.ts: GET /fleet is 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.md gets the page's entry, and docs/APPS.md gets a paragraph on using it.
  • No preview screenshot: forge.json looks is capped at 24 paths and all are taken. /fleet goes in NOT_A_PAGE in test/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.ts caps a selection at MAX_ROWS = 500 and 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.ts is unchanged: the gate now includes every test file that EXCLUDED doesn'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

Changes requested — before the latest push

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.

  1. 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.
  2. 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.
  3. 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.

commented

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 through json_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.ts holds 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: selectRows now takes viewer: User | undefined. A visitor is undefined and sees only public repositories; this replaces the as unknown as User cast. 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 existing table / data-dense classes. Each header links to ?sort=<column>, the sorted one flips to -<column> and carries aria-sort, and it works without JavaScript.
  • Wiring: the route is next to /fleet/graph in src/http/server.ts and reports session and render in Server-Timing. The fleet graph links to it as "Rows". It has an entry in docs/design/audit.md.
  • No preview screenshot: forge.json looks is capped at 24 paths and all are taken, so /fleet goes in the not-a-page list in test/forge-looks.test.ts, with that reason.

Terminal

  • src/tui/screens/fleet.ts, registered next to packs in forgeCommands, is open to everyone because visibility is the selection's rule.
  • Keys: / writes a new query line, s sorts by the next column, r reverses, j/k scroll, esc goes 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, s and r, 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.ts needs 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.ts refuses 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-graph
  • forge-looks, server-timing-sweep, hollow, design-audit
  • palette-roster, palette-entries, features-empty, readme-cap
  • flake, 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

Approved

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.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs