The hover actions were easy to miss and mute when hit: a ~20px target that slid in on hover and reshaped when arming swapped the label, then zero feedback between the second click and the SSE redraw seconds later. board.html, one state machine for every action button (hover actions and $-command chips alike), its truth in S.acts so SSE re-renders mid-window redraw the same picture: - Stable geometry: rest/confirm/busy labels stack in one grid cell so the button is born at its widest state's width; the slot fades in place (opacity only); buttons are 24px tall and the toprow reserves that height, so nothing moves or grows under the pointer. - Visible armed window: alarm colour plus a draining underline over the disarm timer, lengthened to 5s; a rebuilt button rejoins the drain partway via a negative animation delay. - Instant busy state: firing disables the button, breathes the glyph and shows a participle (starting…, holding…) until the redraw replaces the card, the runner reports failure, or a 15s timeout restores it with a toast naming the action — never a silent revert. - Misses fall harmlessly: the slot pads its hitbox (padding + negative margin) and swallows clicks itself, so gap clicks neither fire an action nor open the card sheet. tests/test_card_actions.py holds the source-level invariants; the full suite (119 tests) passes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bench
A live kanban for coding-agent work: task files in stage directories are the only source of truth; a stdlib-only board narrates everything that happens to them — agents working in git worktrees, PRs opening on review, CI and Copilot state on the cards, drives of the app from a task's own branch, and an archive that is never a delete.
Install into a repo
git clone <this repo> .task-manager && rm -rf .task-manager/.git
./.task-manager/start.sh # wires the project (idempotent) and serves
The first start.sh clears the distribution's own cards from tasks/,
plans/ and reference/ (printing each removal), so a fresh install
starts with an empty board.
Commit .task-manager/ into the host repo — core is vendored on purpose,
so clones work offline and updates show up in the host's own diffs.
The workflow brief ships as .task-manager/AGENTS.md — the cross-vendor
name coding agents read natively — with CLAUDE.md beside it as a one-line
compatibility pointer. Both live inside .task-manager/, so a host repo's
own root AGENTS.md is never touched.
Update
# in manager/local/.env: BENCH_SOURCE=<this repo's git url>
./.task-manager/update.sh # latest
BENCH_REF=v1 ./.task-manager/update.sh # a specific release tag
Updates replace manager/core/ and the top-level scripts wholesale and
touch nothing else — tasks, plans, reference, and everything under
manager/local/ (your driver, commands, prompt overrides, settings,
state) survive every update. Then python3 .task-manager/install.py and
restart the board.
The three-layer law
Core knows about tasks, worktrees, PRs and events. It knows nothing about
any particular app (drivers do: local/driver/start), agent vendor
(adapters do: core/adapters/), or project (local/ does). Full docs in
AGENTS.md; the adapter contract in manager/core/adapters/README.md.
License
MIT.