Public: anyone can read this; the crew can change it.
Remove on a board panel goes through the confirm page, which names the panel, and refuses if the board changed underneath #8037
Check · Review · Land · Live done — Landed and live in production.
The Remove button on each board panel now goes through the confirm page instead of posting on one click. The panel's index and title are in the confirm target, `/boards/<slug>/remove?index=<i>&title=<title>`. If the board was reordered between the two clicks, nothing is removed, so the wrong panel can't go.
shipped production is serving it deploy b070674
The Remove button on each board panel now goes through the confirm page instead of posting on one click. The panel's index and title are in the confirm target, /boards/<slug>/remove?index=<i>&title=<title>. If the board was reordered between the two clicks, nothing is removed, so the wrong panel can't go.
src/boards/edit.ts:removetakes an optionaltitle. If it doesn't match the panel atindex,applyEditrefuses with "that panel moved; nothing was removed". Without a title it works as before, so the terminal'sxis unchanged.src/web/pages/boards-view.ts: the per-panel form is nowconfirmAction({ action: …, verb: "Remove", size: "sm" }). That drops the old per-panel screen-reader label "Remove <title>", because the issue gives the exact call.src/web/pages/boards-edit.ts:editOfreadsindexandtitlefrom the form, or fromctx.url.searchParamswhen the form has noindex.src/web/pages/search.ts: one newDESTRUCTIVEentry, directly after/boards/…/delete. Its page title is "Remove the panel <title> from <slug>?", and the title is escaped by the template. The names are decoded with a smalldecodedOrthat keeps the raw text if the encoding is malformed, so a hand-typed bad%can't make the page fail with a 500.- The pin line in the detail is true today: when home reads its pins, it drops any whose panel no longer exists (
resolvedinsrc/actions/board-pins.ts). So the pin goes with the panel, as the detail says. - Tests:
- test/danger-buttons.test.ts no longer lists the panel Remove as an exception.
- The new test/confirm-board-panel.test.ts checks that the board page links to the confirm page, and that the confirm page answers 200 with the escaped title (using a panel called "Runs & kinds"). It also checks that a POST removes the right panel, that a POST whose title no longer matches removes nothing and shows the message, and that remove without a title works as before. All 5 fail with the source change stashed.
- test/boards-store.test.ts was wrong after this change: it still expected the old one-click form. It now expects the confirm link.
Checks run: scripts/gate.ts, tsc --noEmit and design-lint are clean. scripts/fast.ts named 1000 files (over its budget), so I ran the covering board, confirm, pin and danger-button suites myself instead: all pass. My first push still had the old boards-store test failing; the second commit on the branch fixes it, and the tip passes.
To try it: open /boards/<slug> as an admin, click Remove on a panel, read the confirm page, then confirm.
🤖 Generated with Claude Code
The flow works and is tested: Remove links to the confirm page, the page names the panel (escaped), a stale title refuses, and the terminal's x is unchanged. One thing to fix first: the per-panel Remove links lost their accessible name. A board with N panels now has N links that all read just "Remove" to a screen reader, while Up and Down on the same row still say which panel they act on. Fix: give confirmAction an optional label that it passes to btn (btn already supports label), pass Remove ${p.title} from boards-view, and assert aria-label in the board-page test. Smaller point: the stale guard compares titles only, so two panels with the same title swapped still remove the wrong one. Say so in the README or compare the query as well.
Remove on a board panel now asks first, on the confirm page, which names the panel and its board (escaped) and offers one Remove button. The confirm target is /boards/<slug>/remove?index=<i>&title=<title>. If a different panel sits at that index when the POST arrives, nothing is removed and the page says "that panel moved; nothing was removed". Without a title, removal by index works as before, so the terminal's x is unchanged.
src/boards/edit.ts: theremoveedit takes an optionaltitleand refuses when it doesn't match the panel at that index.src/web/pages/boards-edit.ts:editOfreadsindexandtitlefrom the form, or from the URL query when the form has noindex.src/web/pages/boards-view.ts: each panel's Remove is aconfirmActionlink.- Remove link names (from review): every Remove link is named for its own panel ("Remove <title>"), as Up and Down already are.
confirmActioninsrc/web/ui.tstakes an optionallabeland passes it tobtn. src/web/pages/search.ts: one newDESTRUCTIVEentry, directly after the board delete one.
- Its detail says a pin of the panel on home goes with it. That is true today: home drops pins whose panel is gone (resolved in src/actions/board-pins.ts).
- Names are decoded with a small decodedOr, so a malformed % shows as typed instead of making the page fail with a 500.
- Not done: duplicate titles. The check compares titles only. If two panels have the same title and are swapped between the two clicks, the one now at that index is removed. Board titles may repeat, but home pins already tell panels apart by title alone. The README says this and suggests distinct titles. Comparing the query as well would mean a longer confirm URL and a different pattern from the one the issue gives.
- Tests:
- test/danger-buttons.test.ts no longer lists the panel Remove as an exception.
- The new test/confirm-board-panel.test.ts checks:
- the board page links Remove to the confirm page, with a per-panel aria-label;
- the confirm page answers 200 with the escaped title;
- the POST removes that panel;
- a POST with a stale title removes nothing and says so;
- removal by index alone still works.
- All five failed with the source change stashed; I checked that before adding the aria-label assertions.
- test/boards-store.test.ts was wrong after this change: it expected the old one-click form, and it now expects the confirm link.
- Checks:
scripts/gate.ts,tsc --noEmitanddesign-lintare clean, and the board, confirm, pin and danger-button suites pass.scripts/fast.tsnamed 1000 files, too many to run here, so I chose these suites myself.
To try it: open /boards/<slug> as an admin, click Remove on a panel, read the confirm page, and confirm.
🤖 Generated with Claude Code
Earlier asks: (1) each Remove link named for its own panel, met: confirmAction takes a label and the board page passes Remove <title>, and the test asserts the aria-labels. (2) The duplicate-title gap, met: the README states it and tells people to use distinct titles. I could not diff against 9fb4456ba932 because the clone lacks that commit, so I read the change as it stands. It is a plain link to the confirm page, so it works without JavaScript. The confirm page escapes the title. The stale-title refusal is tested, and the preview answers 200.
Two things I would do next:
- Put an index-plus-title check on the duplicate-title case, for example a per-panel id, so swapped same-title panels cannot remove the wrong one.
- Make the terminal
xremove ask first, so it matches the page. It still removes by index with no title.
The Remove button on each board panel now goes through the confirm page instead of posting on one click. The panel's index and title are in the confirm target,
/boards/<slug>/remove?index=<i>&title=<title>. If the board was reordered between the two clicks, nothing is removed, so the wrong panel can't go.src/boards/edit.ts:removetakes an optionaltitle. If it doesn't match the panel atindex,applyEditrefuses with "that panel moved; nothing was removed". Without a title it works as before, so the terminal'sxis unchanged.src/web/pages/boards-view.ts: the per-panel form is nowconfirmAction({ action: …, verb: "Remove", size: "sm" }). That drops the old per-panel screen-reader label "Remove <title>", because the issue gives the exact call.src/web/pages/boards-edit.ts:editOfreadsindexandtitlefrom the form, or fromctx.url.searchParamswhen the form has noindex.src/web/pages/search.ts: one newDESTRUCTIVEentry, directly after/boards/…/delete. Its page title is "Remove the panel <title> from <slug>?", and the title is escaped by the template. The names are decoded with a smalldecodedOrthat keeps the raw text if the encoding is malformed, so a hand-typed bad%can't make the page fail with a 500.resolvedinsrc/actions/board-pins.ts). So the pin goes with the panel, as the detail says.-
test/danger-buttons.test.tsno longer lists the panel Remove as an exception. - The newtest/confirm-board-panel.test.tschecks that the board page links to the confirm page, and that the confirm page answers 200 with the escaped title (using a panel called "Runs & kinds"). It also checks that a POST removes the right panel, that a POST whose title no longer matches removes nothing and shows the message, and that remove without a title works as before. All 5 fail with the source change stashed. -test/boards-store.test.tswas wrong after this change: it still expected the old one-click form. It now expects the confirm link.Checks run:
scripts/gate.ts,tsc --noEmitanddesign-lintare clean.scripts/fast.tsnamed 1000 files (over its budget), so I ran the covering board, confirm, pin and danger-button suites myself instead: all pass. My first push still had the oldboards-storetest failing; the second commit on the branch fixes it, and the tip passes.To try it: open
/boards/<slug>as an admin, click Remove on a panel, read the confirm page, then confirm.🤖 Generated with Claude Code
Closes #8030