Public: anyone can read this; the crew can change it.
A test fails when a new danger button posts straight from a page instead of through the confirm page #7989
Check · Review · Land · Live done — Landed and live in production.
This adds `test/danger-buttons.test.ts`. It does a plain text scan of every `.ts` file under `src/web/` and looks for `variant: "danger"` buttons that sit inside an open `<form method="post">`. When it finds one that isn't allowed, it fails with `file:line "Label" posts without asking: use confirmAction from src/web/ui.ts`.
shipped production is serving it deploy 2e641a3
This adds test/danger-buttons.test.ts. It does a plain text scan of every .ts file under src/web/ and looks for variant: "danger" buttons that sit inside an open <form method="post">. When it finds one that isn't allowed, it fails with file:line "Label" posts without asking: use confirmAction from src/web/ui.ts.
What it lets through without a confirm:
- buttons whose
btn(...)options sethref, which coversconfirmAction; - forms that contain a
name="confirm"field (orname: "confirm", theformFieldform). That coversconfirmBodyinsearch.ts, the chain "Sever" form, and the accounts suspend/retire forms.
Today's other cases are listed in the test, one line each, keyed by file and button label so a new button in the same file is still caught:
- board panel "Remove" (the panel index is in the body)
- board delete
- team member "Remove" in
org.ts - the edit page's "Delete this file" (the page is the confirmation)
- the detach page
- "Land past the gate" (asks for a typed reason)
- owner-note ban in
studio/notes.ts(undone by unban)
A listed case that no longer exists only prints a hint telling you to drop it, so the sibling confirm pulls can land in any order without touching this file.
Things to look at:
- Scan rules: these are deliberately narrow. The button must be a
btn(/iconBtn(call within 400 characters of the danger option, with a<formbefore it that isn't closed yet and whose tag saysmethod="post". Anything less certain is skipped, not failed. - App Stop: the issue names it, but no
variant: "danger"button for it exists undersrc/web/, so it isn't listed. If it's built some other way, this scan doesn't see it.
Two tests check the scanner itself: the issue's Wipe example is caught, and links, confirm fields, GET forms, buttons after a closed form, and non-button variant objects are not. I also added the Wipe form to detach.ts temporarily: the test failed naming src/web/pages/detach.ts:89, and I reverted the file. The file runs in about 120 ms. bun scripts/gate.ts and bun x tsc --noEmit pass. No app code changed.
Try it: bun test test/danger-buttons.test.ts, then add a danger button inside a post form to any page.
🤖 Generated with Claude Code
Test-only change, no app code touched, no new dependency, no secrets or I/O beyond reading src/web. The scanner is narrow by design, the allowlist is keyed by file and label so a new button in a listed file is still caught, and stale entries only print a hint so sibling confirm pulls can land in any order. Two scanner tests cover the positive and negative cases. Note: App Stop is not covered because no danger button for it exists under src/web, as the description says. I could not run the file here (the branch checkout did not contain it), so I judged it from the diff.
This adds
test/danger-buttons.test.ts. It does a plain text scan of every.tsfile undersrc/web/and looks forvariant: "danger"buttons that sit inside an open<form method="post">. When it finds one that isn't allowed, it fails withfile:line "Label" posts without asking: use confirmAction from src/web/ui.ts.What it lets through without a confirm:
btn(...)options sethref, which coversconfirmAction;name="confirm"field (orname: "confirm", theformFieldform). That coversconfirmBodyinsearch.ts, the chain "Sever" form, and the accounts suspend/retire forms.Today's other cases are listed in the test, one line each, keyed by file and button label so a new button in the same file is still caught:
org.tsstudio/notes.ts(undone by unban)A listed case that no longer exists only prints a hint telling you to drop it, so the sibling confirm pulls can land in any order without touching this file.
Things to look at:
btn(/iconBtn(call within 400 characters of the danger option, with a<formbefore it that isn't closed yet and whose tag saysmethod="post". Anything less certain is skipped, not failed.variant: "danger"button for it exists undersrc/web/, so it isn't listed. If it's built some other way, this scan doesn't see it.Two tests check the scanner itself: the issue's
Wipeexample is caught, and links, confirm fields, GET forms, buttons after a closed form, and non-buttonvariantobjects are not. I also added theWipeform todetach.tstemporarily: the test failed namingsrc/web/pages/detach.ts:89, and I reverted the file. The file runs in about 120 ms.bun scripts/gate.tsandbun x tsc --noEmitpass. No app code changed.Try it:
bun test test/danger-buttons.test.ts, then add a danger button inside a post form to any page.🤖 Generated with Claude Code
Closes #7984