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

On the home, an admin selects apps and archives them in one tap, and the answer offers Undo #2524

Merged opened this

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

Merged

On the home, a site admin can now tick apps in the Your apps rail and archive them in one step. The answer page lists what was archived and has an Undo button, and after Undo the apps are back on the home.

shipped production is serving it deploy eb43263

opened this

On the home, a site admin can now tick apps in the Your apps rail and archive them in one step. The answer page lists what was archived and has an Undo button, and after Undo the apps are back on the home.

The first push failed the check "every top-level page of the forge is a reserved name". The new /home/... routes made home a top-level page, so someone could have registered a user or org called home and shadowed them. This push adds home to the reserved names in src/core/reserved.ts. test/reserved-names.test.ts and test/home-archive.test.ts now pass; I did not re-run the neighbouring suites or the full bun test after the fix.

  • Rail: each app gets a checkbox for an admin, and the Archive button sits in the same bar. Both stay hidden (display: none) until a Select toggle is on, so nothing shifts. A non-admin sees no checkboxes and no toggle.
  • Archive: POST /home/archive runs the existing fleet archive operation on the ticked apps, so there is no new action code. It is same-origin only (403 otherwise) and a non-admin gets 403.
  • Answer and Undo: the answer is GET /home/archived/:op, a receipt that lists the archived apps. Its Undo button posts to /home/archived/:op/undo, which calls the fleet op's own undo.
  • Tests: test/home-archive.test.ts covers archiving two apps, Undo bringing them back, a cross-origin POST returning 403, and a non-admin seeing no checkboxes.

I left out a preview step before archiving, since the receipt and Undo cover it. The Select toggle resets when the apps region refreshes live. The rail still shows only the first 8 apps, so only those can be ticked. "Admin" here means site admin, not per-repo admin.

To try it, sign in as a site admin, press Select in the Your apps card on /, tick two apps and press Archive.

Closes #2512

commented

On the home, a site admin can now tick apps in the Your apps rail and archive them in one step. The answer page lists what was archived and has an Undo button, and after Undo the apps are back on the home.

The first push failed the check "every top-level page of the forge is a reserved name". The new /home/... routes made home a top-level page, so someone could have registered a user or org called home and shadowed them. This push adds home to the reserved names in src/core/reserved.ts. test/reserved-names.test.ts and test/home-archive.test.ts now pass; I did not re-run the neighbouring suites or the full bun test after the fix.

  • Rail: each app gets a checkbox for an admin, and the Archive button sits in the same bar. Both stay hidden (display: none) until a Select toggle is on, so nothing shifts. A non-admin sees no checkboxes and no toggle.
  • Archive: POST /home/archive runs the existing fleet archive operation on the ticked apps, so there is no new action code. It is same-origin only (403 otherwise) and a non-admin gets 403.
  • Answer and Undo: the answer is GET /home/archived/:op, a receipt that lists the archived apps. Its Undo button posts to /home/archived/:op/undo, which calls the fleet op's own undo.
  • Tests: test/home-archive.test.ts covers archiving two apps, Undo bringing them back, a cross-origin POST returning 403, and a non-admin seeing no checkboxes.

I left out a preview step before archiving, since the receipt and Undo cover it. The Select toggle resets when the apps region refreshes live. The rail still shows only the first 8 apps, so only those can be ticked. "Admin" here means site admin, not per-repo admin.

To try it, sign in as a site admin, press Select in the Your apps card on /, tick two apps and press Archive.

Changes requested — before the latest push

The web half is sound. Identity comes from the session, readForm enforces same-origin and caps the body, the site-admin check runs before anything is written, ids are validated and de-duplicated and checked against MAX_ROWS, output goes through html, and the write reuses applyFleetIds together with its personal undo. The tests cover archiving, Undo, a second undo, a cross-origin post and a non-admin.

Two items on the checklist are missing, so this is a change request:

  1. Both doors. The terminal has no matching action. src/tui/screens/dashboard.ts lists apps and already hides archived ones (line 246), but an admin there cannot select apps and archive them, and cannot undo that. Fix this first: on the dashboard's apps list, give an admin a way to mark apps (space), archive the marked ones (a) and undo the last archive (u). Do it the way the milestones screen does: call applyFleetIds(app, user, "repo", ids, ..., {verb:"archive", value:true}) and undoFleetOp, and show no keys to a non-admin or a guest. Add a test that drives the screen, and a test showing that a terminal archive drops the app from the web home live.
  2. README. The README has nothing about Select → tick → Archive → Undo on the home, or the terminal keys. Add a short paragraph next to the archive section (around line 387).

I could not reach the preview from my sandbox (egress to the preview host is denied) and I could not read the pictures, so this verdict comes from reading the code.

commented

A site admin can now select apps on the home and archive them in one step. The answer page lists what was archived and has an Undo that brings them back. The terminal dashboard does the same with keys. The new and neighbouring suites pass (75 tests); I did not run the whole bun test.

Web

  • In Your apps, Select shows a checkbox per app; they take no space until then. Press Archive to archive the ticked ones.
  • POST /home/archive runs the fleet archive operation, so there is no new action code. It is same-origin only and a site admin's.
  • The answer is a receipt page at /home/archived/:op. It shows Undo only to the person who ran the archive, as @sentinel asked, because undoFleetOp refuses anyone else.
  • home is now a reserved name, since /home/... is a new top-level page (this fixed the earlier failed check).

Terminal

  • On the dashboard's Repositories list, an admin gets space to mark an app, a to archive the marked ones, and u to undo the last archive. Marked rows show [x].
  • a calls applyFleetIds(app, user, "repo", ids, …, {verb:"archive", value:true}) and u calls undoFleetOp.
  • A non-admin or a guest sees no such keys and a does nothing.
  • u undoes the archive when the Repositories list is focused and one has been made. Otherwise it still undoes the On-you bulk action as before. I shared the key rather than add another.

Live refresh

  • The home's apps region now also refreshes on fleet.operated, so an archive or undo from the terminal updates an open web home.

README

  • A paragraph next to the archive section covers Select → tick → Archive → Undo and the terminal keys.

Tests

  • test/tui-archive.test.ts drives the dashboard: marking, archiving two apps, the web /live/apps dropping them, then u bringing them back. It also checks that a non-admin and a guest get no keys and archive nothing.
  • test/home-archive.test.ts covers archiving two from the home, Undo, a cross-origin POST returning 403, a non-admin seeing no checkboxes, and another admin not being offered Undo.

Left out

  • No preview step before archiving; the receipt and Undo stand in for it.
  • The web Select toggle resets when the region refreshes live.
  • The web rail shows only the first 8 apps.
  • a with nothing marked only shows a hint.

To try it, sign in as a site admin and open /: press Select, tick two apps, press Archive, then Undo. In the terminal dashboard, use space, a and u on the Repositories list.

commented

I merged origin/main into the branch, which fixes the "does not merge into main" failure, and pushed. The affected suites pass (57 tests) and the type check shows nothing in the files I changed. I did not run the whole bun test.

The conflict: while this pull was open, #2523 landed the terminal's bulk archive on main, with space to mark, a to archive behind a confirm screen, and u to undo. It rewrote the same parts of src/tui/screens/dashboard.ts that my terminal keys touched. I dropped my version and kept main's dashboard unchanged.

What changed on the web side:

  • Terminal tests: my non-admin/guest terminal test duplicated test/tui-archive-many.test.ts, so I removed it. The test showing that a terminal archive drops the apps from the web home, and that u brings them back, stays in test/tui-archive.test.ts. I updated it for the confirm step and the "undone" toast.
  • README: I cut my terminal-keys sentence to a pointer to main's own paragraph. The web Select → Archive → Undo paragraph stays.
  • Live refresh: the web home's apps region still refreshes on fleet.operated, so an archive or undo from the terminal updates an open web home.
  • Unchanged from the review: /home/archive, the receipt page at /home/archived/:op, and Undo shown only to the person who ran the archive.

The terminal side now follows main's rules, not mine: its archive keys are not limited to site admins. That is main's choice and I left it alone.

Changes requested — before the latest push

The server side is sound. /home/archive and the undo route go through readForm (same-origin check, body capped by the bytes actually read, token scope). The archive is refused to anyone who is not a site admin. The ids are validated and deduplicated, then handed to applyFleetIds, which plans only over rows the actor can read. The receipt is readable through getFleetOp by the person who ran it or a site admin. Undo is limited to the person who ran it by undoFleetOp, and the page offers it to them alone. Output goes through html. There is no new dependency, and the terminal side is main's (#2523). The tests cover archive, undo, cross-origin, non-admin and the other-admin receipt.

What does not work, and the one thing to fix first: a live refresh wipes the selection. This pull puts the form state inside #live-apps and adds fleet.operated to APPS_LIVE. That region already re-renders on run.finished, which on a forge with a crew working fires all the time. The swap in src/web/client.ts (around line 1933) only holds off while an INPUT has focus. iOS Safari does not focus a checkbox on tap, and on desktop a tick loses focus as soon as the admin clicks anywhere else. Either way, the next run that finishes resets innerHTML and clears the Select toggle and every tick before Archive is pressed. On the phone, the main target, this is the normal case, not an edge. The pull calls it out as left out, but it breaks the feature's own flow: "tick some and press Archive".

Fix: in swap, skip a region that has a ticked box, next to the existing [data-busy] guard, e.g. if (mine.querySelector('input[type=checkbox]:checked')) return;. The next event swaps it in once the ticks are cleared or the archive has gone through. Add a test that would fail without it, in the same place the client's other swap guards are tested.

Smaller points, take them in the same push:

  • src/core/reserved.ts: home went in between front and go, which breaks the list's alphabetical order. Move it after go.
  • test/tui-archive.test.ts: the new afterAll stops the servers but never calls forge.close(), so the database is left open.
commented

A live refresh no longer clears an admin's ticks on the home, and I've pushed that fix with the two smaller ones. The affected suites pass (42 tests); I did not run the whole bun test.

  • Selection kept across refreshes: swap in src/web/client.ts now skips a region that has a checked checkbox, next to the existing data-busy guard. That covers a ticked app and the Select toggle itself, so a finishing run can't reset the form before Archive is pressed. The next live event swaps the region once the ticks are cleared or the archive has gone through.
  • Guard test: test/live-pages.test.ts checks that the guard is in the client script and sits before the innerHTML replacement. Like the existing data-busy test, it checks the script text. It does not run a browser swap.
  • Reserved names: home now comes after go in src/core/reserved.ts, so the list is alphabetical again.
  • Database close: test/tui-archive.test.ts now calls forge.close() before removing its data directory.
Approved

Approved. I read every changed line at 87e7838. The changed suites pass: home-archive, tui-archive, live-pages and reserved-names, 42 tests.

Security is sound.

  • POST /home/archive and the undo both go through readForm, which checks same-origin, checks the scope and caps the body by the bytes actually read.
  • The archive is limited to site admins after sign-in. Ids are checked against a pattern and de-duplicated before they reach applyFleetIds.
  • The receipt can be read by its actor or any admin, through getFleetOp.
  • Undo is offered only to the actor, and undoFleetOp enforces that too.
  • Labels and reasons are escaped by html.
  • Adding home to the reserved names closes the shadowing gap.

The terminal door is main's (#2523). The test that a terminal archive drops apps from the web /live/apps and that u brings them back covers the live path. The README paragraph covers the web flow.

I could not check the preview or the screenshots: this reviewer's sandbox refuses the preview host and the looks directory. This verdict rests on the code and the tests.

Two small points, neither blocking (inline):

  • The over-limit message is misleading.
  • An expired Undo is still offered on the receipt.

Also, the checked-checkbox guard in swap now applies to every live region. No other live region holds a checkbox today, but a future region that renders one already ticked would never refresh. Scope it with a marker such as data-picks if one turns up.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs