Public: anyone can read this; the crew can change it.

`/fleet` applies one verb to the rows it selects, with a preview first and Undo after #10147

Merged opened this

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

Merged

`/fleet` now applies one verb to the rows it selects, with a preview first and Undo afterwards. No JavaScript is needed.

shipped production is serving it deploy 9e8c29c

opened this

/fleet now applies one verb to the rows it selects, with a preview first and Undo afterwards. No JavaScript is needed.

What changed

  • Form: under the table, a signed-in person gets a verb picker (the verbs for the kind) and a Value field. A signed-out visitor and an agent are not offered it, and a POST from either is refused (signed-out gets the sign-in page, an agent gets not-found).
  • Preview: a same-origin POST /fleet with step=preview calls previewFleetOp. It shows each row's before → after, and whether it will change, is unchanged or is refused. Refusals and verb errors appear as muted lines. Nothing is written.
  • Apply: step=apply calls applyFleetOp and redirects to GET /fleet/ops/:id, a result page with the same table.
  • Undo: POST /fleet/ops/:id/undo calls undoFleetOp. The Undo button shows only while the operation is reversible, not yet undone, and within UNDO_WINDOW_MS.
  • Same-origin check: every POST goes through readForm, so a cross-origin one gets 403.
  • Code: the handlers are in src/web/pages/fleet-ops.ts and the HTML is in src/web/fleet-act.ts. fleetPage in src/web/pages/fleet.ts now delegates to renderFleet, which draws the preview or receipt under the form. The routes are in src/http/server.ts. No new dependency and no new component class.
  • Tests: test/fleet-act.test.ts covers the reader seeing no form, the signed-out and cross-origin refusals, a label preview over two issues that writes nothing, apply, undo, and a cross-origin undo.
  • Docs: docs/features/fleet.md describes the new flow.

Checks

  • bun x tsc --noEmit, bun scripts/gate.ts and bun scripts/design-lint.ts pass.
  • I ran 634 tests (the fleet, route, origin, docs and no-JS suites) and all pass. scripts/fast.ts named 1549 files because the change touches server.ts, so I did not run them all; the forge's check runs the full suite.
  • One test run printed a denied network call to parent.test:443; all tests still passed.
  • I could not call keep_plan, so there is no plan on the issue.

Left out

  • The Value field is free text and is read as a flag, a number, or text, whichever the verb accepts. There is no per-verb typed input.
  • The Undo button disappears after a day for every verb. The API still undoes non-asks verbs later.
  • There is no operations history list on the web.
  • No terminal change.

Try it: sign in, open /fleet?q=issue repo:you/repo state:open, choose label, enter a label name, then Preview, Apply and Undo.

🤖 Generated with Claude Code

Closes #10133

commented

/fleet now applies one verb to the rows it selects, with a preview first and Undo afterwards. No JavaScript is needed.

What changed

  • Form: under the table, a signed-in person gets a verb picker (the verbs for the kind) and a Value field. A signed-out visitor and an agent are not offered it, and a POST from either is refused (signed-out gets the sign-in page, an agent gets not-found).
  • Preview: a same-origin POST /fleet with step=preview calls previewFleetOp. It shows each row's before → after, and whether it will change, is unchanged or is refused. Refusals and verb errors appear as muted lines. Nothing is written.
  • Apply: step=apply calls applyFleetOp and redirects to GET /fleet/ops/:id, a result page with the same table.
  • Undo: POST /fleet/ops/:id/undo calls undoFleetOp. The Undo button shows only while the operation is reversible, not yet undone, and within UNDO_WINDOW_MS.
  • Same-origin check: every POST goes through readForm, so a cross-origin one gets 403.
  • Code: the handlers are in src/web/pages/fleet-ops.ts and the HTML is in src/web/fleet-act.ts. fleetPage in src/web/pages/fleet.ts now delegates to renderFleet, which draws the preview or receipt under the form. The routes are in src/http/server.ts. No new dependency and no new component class.
  • Tests: test/fleet-act.test.ts covers the reader seeing no form, the signed-out and cross-origin refusals, a label preview over two issues that writes nothing, apply, undo, and a cross-origin undo.
  • Docs: docs/features/fleet.md describes the new flow.

Checks

  • bun x tsc --noEmit, bun scripts/gate.ts and bun scripts/design-lint.ts pass.
  • I ran 634 tests (the fleet, route, origin, docs and no-JS suites) and all pass. scripts/fast.ts named 1549 files because the change touches server.ts, so I did not run them all; the forge's check runs the full suite.
  • One test run printed a denied network call to parent.test:443; all tests still passed.
  • I could not call keep_plan, so there is no plan on the issue.

Left out

  • The Value field is free text and is read as a flag, a number, or text, whichever the verb accepts. There is no per-verb typed input.
  • The Undo button disappears after a day for every verb. The API still undoes non-asks verbs later.
  • There is no operations history list on the web.
  • No terminal change.

Try it: sign in, open /fleet?q=issue repo:you/repo state:open, choose label, enter a label name, then Preview, Apply and Undo.

🤖 Generated with Claude Code

commented

The check failed on two gate rules, and I've pushed a fix for both. The rules need Why now: and Try: lines in a commit message, and my first commit had neither. The new commit is empty and adds only those two lines. No code changed, so the tests are unaffected.

bun scripts/gate.ts now passes locally. I haven't seen the forge's check run on the new push yet.

Try on the preview: sign in, open /fleet?q=issue+state:open, pick label, enter a label name, then Preview, Apply and Undo.

🤖 Generated with Claude Code

commented

The failing test was test/security-floor-routes.test.ts. It wants every route the seed can't fill to be named, and the new GET /fleet/ops/:id receipt route wasn't, because it needs an operation id the seed never creates. I added it to the UNSEEDED list in that test, reusing the existing FLEET reason, and pushed.

That test file now passes locally (3 pass, 0 fail). I haven't seen the forge's full check run on the new push yet.

Try on the preview: sign in, open /fleet?q=issue+state:open, apply a verb, and open the result page.

🤖 Generated with Claude Code

Changes requested — before the latest push

Web flow works and is tested, but it misses the 'both doors' rule and has two correctness gaps.

Fix first: add the terminal screen. The PR says 'No terminal change'; what a person can do on /fleet (preview, apply, undo a verb) must also be doable in src/tui/screens/fleet.ts (verb + value prompt, preview, apply, undo), gated like the page (guest/agent get no write), with its entry where AGENTS.md says.

Also:

  1. Apply is not bound to what was previewed. The apply form only carries q/verb/value, and applyFleetOp re-selects rows at apply time, so rows that changed between preview and Apply are written without having been shown. Carry a digest of the previewed row ids (or the id list, validated server-side) and refuse with a muted line if the selection differs.
  2. Add tests: an agent/ non-acting user POST is refused (canAct branch), a refused row in apply, and the terminal screen's write path.
  3. Design: the preview/receipt tables put three columns where principle 7 wants rows; the result line 'N changed, N unchanged, ...' repeats what the table says (principle 1). Use a single muted sentence and drop the duplicate count in the receipt.
  4. docs/features/fleet.md lists 'Tested in ... bun test pass' as a claim; keep the how-to and add the terminal section once the screen exists.
commented

/fleet can now preview, apply and undo a verb from the terminal as well as the web, and Apply is tied to the rows that were previewed. bun x tsc --noEmit, bun scripts/gate.ts and bun scripts/design-lint.ts pass, and the fleet tests pass, 40 in all. I did not run the full suite: scripts/fast.ts named 1579 of 2350 files, so I ran the fleet tests plus test/security-floor-routes.test.ts instead.

What changed

  • Apply is bound to the preview.

- The preview carries a digest of each row's id, outcome and new value (src/fleet/digest.ts). - The new applyPreviewed in src/actions/fleet.ts plans again and writes nothing if the digest differs. The page then shows a muted line asking for a new preview. - A missing digest is refused the same way. A preview with nothing to change has no Apply button.

  • Terminal screen.

- In src/tui/screens/fleet.ts, v asks for a verb and value (label urgent) and shows each row's change in a dialog. Apply then writes through applyPreviewed with the digest of what the dialog showed. - u undoes the last result. The keys exist only for a signed-in person who is not an agent, so a guest or agent gets no write path. - The palette entry "Fleet rows" already sits where AGENTS.md says, so it needed no change. Both doors now read the typed value through src/fleet/reading.ts, which I moved out of the web page.

  • Design.

- The preview and receipt are ul.list rows (label, change, outcome) instead of a three-column table. - The preview says one muted sentence, "Nothing is written until you apply." The count appears only on the Apply button. - The receipt no longer repeats the counts.

  • Docs. docs/features/fleet.md drops the "Tested in … pass" claim, describes the digest, and gains the terminal how-to.

Tests added

  • test/fleet-act.test.ts:

- An agent's POST is refused with a 404, and the form is not shown to it. - A row the actor does not administer shows as refused in the receipt and stays unlabelled, while their own row is labelled. - Apply after a new issue appears is refused and nothing is written, as is Apply with no digest. A fresh preview then applies.

  • test/tui-fleet.test.ts:

- Preview, Apply and u undo work, and the labels change and revert. - A guest is not offered v.

Left out

  • The terminal has no key to refresh the preview after a stale refusal. You press v again.
  • The terminal dialog shows at most 12 rows, then "and N more".
Approved

All four earlier asks are met. Apply is bound to the preview by a digest and refuses if the rows moved, with a test. The preview says its one sentence once. The receipt no longer repeats counts. The terminal has v and u, and docs/features/fleet.md has the how-to. Writes check who, POSTs are same-origin, and agents and guests get no form or write key. Non-blocking: the terminal has no key to refresh a stale preview.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs