From 887fa43e36d8c1927d20e7dc198e2fc77e826687 Mon Sep 17 00:00:00 2001 From: istos Date: Sat, 1 Aug 2026 17:28:20 +0200 Subject: [PATCH 1/5] board: 53 phase log (istos) --- tasks/in-progress/53-the-board-stops-fighting-you.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks/in-progress/53-the-board-stops-fighting-you.md b/tasks/in-progress/53-the-board-stops-fighting-you.md index 478a960..ae7f264 100644 --- a/tasks/in-progress/53-the-board-stops-fighting-you.md +++ b/tasks/in-progress/53-the-board-stops-fighting-you.md @@ -65,4 +65,5 @@ review or one too many. - 2026-08-01 17:00 · 52 started - 2026-08-01 17:15 · 52 merged into phase/53-the-board-stops-fighting-you - 2026-08-01 17:15 · 47 started +- 2026-08-01 17:28 · halted at 47 — its run ended without reaching review/ From b6091809fecf79955badccfee327ba2f801e1a25 Mon Sep 17 00:00:00 2001 From: istos Date: Sat, 1 Aug 2026 19:57:50 +0200 Subject: [PATCH 2/5] =?UTF-8?q?tasks:=20card=2055=20=E2=80=94=20a=20headle?= =?UTF-8?q?ss=20run=20gets=20one=20turn,=20and=20nothing=20says=20so?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Card 47's agent ended its turn with 'the suite is still running; I'll wait for the monitor', which in a one-shot claude -p run is the end of the run — 314 lines of finished, passing work staged and never committed. The prompt never states that the run is a single turn or that uncommitted work dies with it. --- ...eadless-run-is-one-turn-and-must-say-so.md | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 tasks/backlog/55-a-headless-run-is-one-turn-and-must-say-so.md diff --git a/tasks/backlog/55-a-headless-run-is-one-turn-and-must-say-so.md b/tasks/backlog/55-a-headless-run-is-one-turn-and-must-say-so.md new file mode 100644 index 0000000..de805d9 --- /dev/null +++ b/tasks/backlog/55-a-headless-run-is-one-turn-and-must-say-so.md @@ -0,0 +1,101 @@ +# 55 — A headless run gets one turn, and nothing tells the agent that + +**Status:** Backlog +**Priority:** High — it costs a whole run and everything in it, and the +condition that triggers it is getting more likely every week +**Type:** Bug + +A work agent finished its turn with the words *"The suite is still +running; I'll wait for the monitor rather than poll further."* There is no +monitor. A headless `claude -p` run ends when the model stops producing +output, so ending a turn to wait ends the run — and this one ended with +314 lines of finished, passing work staged and never committed. + +## Context + +What actually happened, on card 47 inside phase 53: + +- The agent staged changes across `AGENTS.md`, `manager/core/board.html`, + `manager/core/httpd.py`, a new `tests/test_archive_chip.py` and an + edited `tests/test_card_actions.py` — then started the test suite and + ended its turn to wait for it. +- The process exited 0 with no commits on `task/47-…`. The log is 80 + bytes: that one sentence. +- Everything downstream behaved correctly. The board refused to move the + card to `review/` — an empty branch reaching review is a broken launch + hiding — and the phase halted at 47 rather than skipping it, saying so + in its `## Phase log`. +- The work was fine. Run by hand afterwards: 37 tests in the two files + it touched, then **875 tests, all passing**, in that worktree. + +Why waiting looked reasonable to the agent: **that suite takes four +minutes.** 875 tests, 239 seconds. This morning it was 631 tests in about +105 seconds. At four minutes, backgrounding a run and waiting for it is a +sensible-looking strategy for an agent that believes it will be resumed. + +And nothing tells it otherwise. `manager/core/prompts/work.md` says to +run the checks until they pass and to commit in clear, reviewable +commits. It never says the run is a single non-interactive turn, that +there is nobody to hand back to, or that uncommitted work dies with the +process. The one place finality is stated is the `NOT READY` path — "end +immediately with a reply whose FIRST line is exactly…" — which is about +declining, not about the shape of the run. + +**Affected areas:** `manager/core/prompts/work.md` first, and the same +paragraph is owed to `act-pr.md` and `review.md`, which run the same way. + +## What to build + +- **Say what a run is, at the top of the prompt.** One short paragraph: + this is a single non-interactive turn; there is no human and no second + chance; when the reply ends the process exits; anything not committed + is lost, and the board judges the run by what is on the branch. +- **Turn that into an instruction, not a warning.** Commit before + anything long-running, and commit again after it. A commit is cheap and + a lost run is not — an agent that commits early can always improve on + it in the same turn. +- **Name the trap by name.** Do not background a command and end the turn + to wait for it; do not promise to come back to something. If a check + takes minutes, run it in the foreground and wait inside the turn. +- **The same paragraph in the other headless prompts.** `act-pr` pushes + and `review` posts a verdict; both lose everything the same way if the + turn ends early. +- **Check the marker contract still reads clearly** once the paragraph is + added — `NOT READY:` and the closing report are parsed from the same + output, and the new text must not muddy where the first line goes. + +**Out of scope** — tempting neighbours left alone: + +- Making the runner resume a stalled agent, or giving an agent a second + turn. One-shot is the design; the fix is telling it so. +- Rescuing uncommitted work automatically. Tempting, and wrong: the + board judging a run by its commits is exactly what caught this. +- Changing what the definition-of-done checks are — see Notes. + +## Acceptance + +- [ ] `prompts/work.md` states, before the task body, that the run is one + non-interactive turn and that uncommitted work is lost when it ends. +- [ ] It instructs the agent to commit before long-running commands, and + not to end a turn waiting on one. +- [ ] `act-pr.md` and `review.md` carry the same paragraph. +- [ ] `tests/test_prompt_report_contract.py` still passes — the four + templates keep their identical closing-report block and their + marker lines still parse. +- [ ] Edge case: the added text does not change where the first line of a + `NOT READY:` reply must sit. + +## Notes + +The recovered work from that run is committed on +`task/47-an-archive-button-on-the-card` as `661dd89`, by hand, unchanged +from what the run left staged. + +**The four-minute suite is the other half of this**, and it deserves its +own card rather than a fix here. `BOARD_AGENT_COMMANDS` is +`python3 -m unittest`, so every work agent runs everything — 875 tests +now, and the number only goes up. A definition-of-done slow enough to +change how an agent behaves is a problem that gets worse quietly: the +prompt fix stops the agent walking away from it, but it does not make the +wait shorter, and the next agent will still spend four minutes of its run +watching a suite that mostly tests things it did not touch. From c256d80efc7e9c2237ae2b01c9b7f355001dde5e Mon Sep 17 00:00:00 2001 From: istos Date: Sun, 2 Aug 2026 07:15:00 +0200 Subject: [PATCH 3/5] =?UTF-8?q?tasks:=20phases=20get=20a=20room=20of=20the?= =?UTF-8?q?ir=20own=20=E2=80=94=2056,=2057,=2058,=2059?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 56 takes phase members off the Board view (counts describe what is visible; membership is the only thing hiding them, so done, archived or an edited list gives them back). 57 draws them instead: a Phases view, one swimlane per phase, the phase log under it, run-again where the halt is. 58 refuses to move a phase card while a member has a live agent — 'hold the phase first' — and covers archive, which is the path that would really have hurt. 59 sweeps merged members to done/ when the phase merges, so an ending reads as an ending. --- .../56-phase-members-leave-the-main-board.md | 103 +++++++++++++++++ .../57-the-phases-view-a-swimlane-each.md | 107 ++++++++++++++++++ ...-card-will-not-move-while-its-work-runs.md | 93 +++++++++++++++ ...59-finishing-a-phase-finishes-its-cards.md | 99 ++++++++++++++++ 4 files changed, 402 insertions(+) create mode 100644 tasks/backlog/56-phase-members-leave-the-main-board.md create mode 100644 tasks/backlog/57-the-phases-view-a-swimlane-each.md create mode 100644 tasks/backlog/58-a-phase-card-will-not-move-while-its-work-runs.md create mode 100644 tasks/backlog/59-finishing-a-phase-finishes-its-cards.md diff --git a/tasks/backlog/56-phase-members-leave-the-main-board.md b/tasks/backlog/56-phase-members-leave-the-main-board.md new file mode 100644 index 0000000..fe0d7a0 --- /dev/null +++ b/tasks/backlog/56-phase-members-leave-the-main-board.md @@ -0,0 +1,103 @@ +# 56 — Phase members leave the main board + +**Status:** Backlog +**Priority:** High — the main board is the thing phases currently spoil, +and every other phase-UI card is downstream of this one +**Type:** Feature + +A phase's members stop appearing on the Board view. The phase card stays — +one card, in whatever stage the phase is in — and its members are drawn +somewhere else (card 57). The Board goes back to meaning one thing: the +work you are personally holding, in five columns that fit. + +## Context + +- Membership is already derived, not stored: `taskfiles.weave_phases()` + (`manager/core/taskfiles.py:163`) resolves every phase card's `## Cards` + list against the board and gives each member a + `phase: {file, number, title, index, total}`. Everything this card + needs is on the task by the time `collect()` returns. +- `httpd.state_payload()` already ships `phases: phases.public_state()` + (`:42`) beside the board, and `board.html` reads it in three places, so + the client can tell a phase card from an ordinary one without asking + again. +- `renderBoard()` (`board.html:945`) builds each column from + `stage.tasks` and prints `stage.tasks.length` in the header, with a note + beside it (`STAGE_NOTE[stage.slug]`, or "N agents" when agents are + working there). +- The design exploration that led here, including the two mockups and the + controls split: + https://claude.ai/code/artifact/727a3b64-1354-4fb0-a73d-b700bdfc2b19 + +**Affected areas:** `manager/core/board.html` — the Board view's +rendering and its column headers. No server change: the state payload +already says everything needed. + +## What to build + +- **A member is not drawn on the Board view.** It keeps its stage, its + file, its agent and its actions — it is simply not in this view. The + card is not deleted, moved, or marked; the Board just stops listing it. +- **Counts describe what is visible.** A column's number is the number of + cards you can see in it. This is the rule that keeps the header honest: + the count is true by construction rather than true-with-a-footnote. +- **A note where the work went**, beside the count and in the register the + existing note uses — `+2 in phases`. It is a signpost to the other + view, not a correction to the number, and it only appears where members + are actually hidden. +- **Membership is the only thing that hides a card.** So when a phase + reaches `done/`, is archived, or has its `## Cards` list edited, its + former members reappear on the Board in whatever stage they are + genuinely in — with no sweep, no migration and no second rule. Removing + membership *is* the un-hiding. +- **The phase card carries the summary it now owes.** It is the only + thing standing for that work on this view, so it shows progress + (`1 of 2 merged`), the member in flight, and a way through to the + Phases view. +- **Only the Board view hides.** Sessions and Focus are about runs and + sessions, not stages, and a phase member's agent is an agent like any + other. + +**Out of scope** — tempting neighbours left alone: + +- Where the members are drawn instead — card 57. Until it lands they are + reachable through the phase card and through Sessions, which is thin + but not a dead end. +- Refusing to move a phase card — card 58. +- What happens to members' stages when the phase merges — card 59. +- Hiding anything that is not a phase member. + +## Acceptance + +- [ ] Given a phase with two members, when the Board view renders, then + the members are absent and the phase card is present. +- [ ] The column that held a member counts one fewer, and its header says + `+1 in phases`. +- [ ] A column with no hidden members shows no such note. +- [ ] Given the phase card reaches `done/`, when the board redraws, then + its former members appear in the columns matching their actual + stages. +- [ ] The same is true when the phase card is archived, and when a number + is removed from its `## Cards` list. +- [ ] The header's live-agents chip still counts an agent working on a + hidden member — hiding cards must not hide activity. +- [ ] Edge case: a card listed by a phase card that does not resolve + (`phaseDrift`) is **not** hidden — an authoring mistake must not + make a card vanish. +- [ ] Edge case: with no phases on the board at all, the Board view is + byte-for-byte what it is today. + +## Notes + +The risk this card takes is stated plainly and should stay stated: **a +board that does not show everything is a board that can surprise you**, +and "you can see everything the agents are doing" is most of what bench +is for. Three things pay that back — the count note, the phase card's own +progress line, and the halt reaching the header whether or not you are on +the Phases view (57). If those three are not in place, this card makes +the product worse rather than better. + +Worth watching for once it lands: a phase with a member in `review/` +means `review/` says `0` and yet something genuinely is waiting on you. +The note covers it, but "your move" as a column note is now a claim about +visible cards only. diff --git a/tasks/backlog/57-the-phases-view-a-swimlane-each.md b/tasks/backlog/57-the-phases-view-a-swimlane-each.md new file mode 100644 index 0000000..0598298 --- /dev/null +++ b/tasks/backlog/57-the-phases-view-a-swimlane-each.md @@ -0,0 +1,107 @@ +# 57 — The Phases view: a swimlane each + +**Status:** Backlog +**Priority:** High — card 56 takes the members off the Board; this is +where they go +**Type:** Feature +**Depends on:** 56 — the members have to have left before they need a room + +A fourth view beside Board, Sessions and Focus. One swimlane per phase, +each running the same five stages left to right, holding every card that +phase owns. It is the room where horizontal work gets horizontal space, +which the five columns could never spare. + +## Context + +- The exploration and both mockups, including the table of which control + belongs to which view: + https://claude.ai/code/artifact/727a3b64-1354-4fb0-a73d-b700bdfc2b19 +- Views already exist as a switcher: `setView()` (`board.html:1005`), + `VIEW_TITLES` (`:860`), `S.view`, and the `#views button` elements. A + fourth is an addition to a pattern, not a new one. +- Everything to draw a lane is already in the state payload: + `phases.public_state()` gives the runner's last pass per phase card — + progress, the member in flight, whether it halted and why — and + `weave_phases` has already told each member its phase and position. +- The phase card keeps its own record in a `## Phase log` section, one + line per decision the runner made. It is the thing you read when + something went sideways, and it has nowhere to be shown today. +- `stop_phase()` (`phases.py:662`) holds the phase *and* the agent it has + in flight; `start_phase()` runs it again and clears a halt. + +**Affected areas:** `manager/core/board.html` — a new view and its +rendering. The API is already sufficient. + +## What to build + +- **A Phases view** in the switcher, carrying a count of running phases + and, when one has halted, a mark that says so from the Board view + without switching to find out. +- **One lane per phase**, ordered so a halted phase is not below the fold. + A lane has a head — the phase, its progress, the member in flight — and + five stage columns holding its cards. +- **The cards are the cards.** Full fidelity: the live agent line, the CI + and PR chips, the ordinary hover actions. Nothing is shrunk to a token, + which is the advantage a whole view buys. +- **The last column is the phase's own**, not `done/`: a member merged + into the phase branch is finished as far as the phase is concerned and + is not in `main` yet. Name it for what it is. +- **The phase log under the lane**, the runner's decisions in order. It is + the only thing that can distinguish "not reached yet" from "started and + ended badly". +- **A halted lane says so at the top of itself** — the reason, the card it + stopped on — and offers **▸ run again**. That action lives here and not + on the Board, because clearing a halt should mean having read what + caused it. +- **Per-phase controls in the lane head**: hold, the phase branch, the + phase card. Per-member controls are the card's own and need no + special-casing. +- **Nothing here that ends the phase.** Merging is a board move on the + phase card, and there should be exactly one place where work leaves the + board. + +**Out of scope** — tempting neighbours left alone: + +- Starting a phase, which stays on the phase card in the Board view: the + commitment is the card reaching `in-progress/`. +- **Merge & clean up**, for the same reason. +- Editing a phase's list — reordering, adding, removing — which is the + file's business and card 51's action. +- A per-phase Focus view. Focus is a heads-up for one session; this is a + different thing wearing similar words. + +## Acceptance + +- [ ] With at least one phase in `in-progress/`, the switcher offers + Phases and the view draws one lane per phase. +- [ ] A lane shows every card the phase lists, in the stage each is + actually in, with its position in the run. +- [ ] A member's card in the lane offers the same actions it would offer + on the Board. +- [ ] A halted phase names the reason and the card, offers **▸ run + again**, and running it again clears the halt and continues. +- [ ] **‖ hold** in the lane head stops the phase and the agent it has in + flight, and unwinds nothing. +- [ ] The phase log is readable under the lane, in the order the runner + wrote it. +- [ ] Given a phase halts while you are on the Board view, you learn it + there — the switcher marks it, and the existing toast and ticker + still fire. +- [ ] With no phases at all, the view says so plainly rather than + rendering an empty grid. +- [ ] Edge case: a phase whose members are all merged, waiting on its own + PR, still draws a lane rather than disappearing before you have + merged it. + +## Notes + +The reason this is a view and not a panel: five columns cannot hold two +boards at once, and every attempt to make them — a rail, a band, a thread +— either fights the geometry or hides the order. Given a room of its own, +the swimlane is the obvious drawing, and it was the obvious drawing all +along. + +The thing to get right is not the lane, it is the **crossing**: a person +on the Board must learn that a phase halted without being on this view, +and a person here must be able to get back to the card that owns it. Two +signposts, both cheap, and the feature is untrustworthy without either. diff --git a/tasks/backlog/58-a-phase-card-will-not-move-while-its-work-runs.md b/tasks/backlog/58-a-phase-card-will-not-move-while-its-work-runs.md new file mode 100644 index 0000000..3c6b468 --- /dev/null +++ b/tasks/backlog/58-a-phase-card-will-not-move-while-its-work-runs.md @@ -0,0 +1,93 @@ +# 58 — A phase card will not move while its work is running + +**Status:** Backlog +**Priority:** Medium — the hole is narrow but what falls through it is a +phase whose card and branch disagree about what is happening +**Type:** Feature + +Dragging a phase card between stages while one of its members has an +agent in it is a move nobody can mean. Refuse it, name what to do — **‖ +hold** stops the phase and the agent it has in flight — and let the move +through the moment nothing is running. + +## Context + +- Today a phase card is an ordinary card to `move_task()`. Nothing + consults the phase, so a phase card can be walked back to `to-do/` (or + archived, which is a move) while its second member is mid-run, leaving + a card in one place and a live agent, a worktree and a phase branch in + another. +- The stopping half already exists and does exactly the right thing. + `stop_phase()` (`manager/core/phases.py:662`): *"the agent the phase has + in flight is held exactly as its own card's hold would hold it"*, and + the branch, the merges and the worktrees are left as they were. So the + fix for a refusal is one action the person already has. +- A **halted** phase has nothing running by construction, so it moves + freely — which is exactly when you would want to walk it back. +- The board has almost no refusals on drag. `move_task` raises for a bad + stage or an existing file; the drag-to-`done/` sheet *intercepts* rather + than refuses. This adds the first real "no", which is why the wording + matters more than the check. + +**Affected areas:** `manager/core/taskfiles.py` or +`manager/core/phases.py` for the check, `manager/core/httpd.py` where +`/api/move` and `/api/archive` are served, and `manager/core/board.html` +for how a refused drag looks. + +## What to build + +- **Refuse the move while any member has a live agent.** Not "while the + phase is running" — a phase between members has nothing to lose, and + refusing then would be a rule people learn to resent. +- **Say the whole thing in the refusal**: which member is working, and + that **‖ hold** stops the phase and its agent without unwinding + anything. A refusal that only says no makes the person guess, and the + guess is usually to force it. +- **Archiving is a move.** `archive_task()` takes the same guard, or the + rule has a hole shaped exactly like the tidiest thing to do with a + phase you have lost interest in. +- **The card returns to where it was**, cleanly, with the reason in the + toast — a drag that snaps back with no explanation is worse than one + that is not allowed to start. +- **Guard the server, not just the drag.** The board can be stale, and + `/api/move` is reachable regardless — the same lesson card 54 wrote + down about `▸ start work` on a phase card. + +**Out of scope** — tempting neighbours left alone: + +- Stopping members individually. `‖ hold` on the phase covers it, and a + per-member stop is card 57's lane doing ordinary card things. +- Refusing to move a *member* card. That is a person overriding the + runner deliberately, and the phase halting on it afterwards is the + honest outcome. +- Anything about what the move does once allowed. + +## Acceptance + +- [ ] Given a phase with an agent running on a member, when its card is + dragged to another stage, then the move is refused, the card stays, + and the message names the working member and **‖ hold**. +- [ ] The same phase card cannot be archived while that agent runs. +- [ ] Given the phase is held, the card moves — and archives — normally. +- [ ] Given the phase has halted, the card moves normally: nothing is + running. +- [ ] Given a phase between members, with nothing launched, the card + moves normally. +- [ ] `POST /api/move` on that card is refused with the same reason, from + a stale page or by hand. +- [ ] An ordinary card is unaffected in every case. +- [ ] Edge case: a member's agent that has died but not been reaped does + not lock the phase card forever — the check reads what is actually + running, not what was once started. + +## Notes + +The rule earns its keep on the archive path more than the drag: a phase +you have given up on is precisely the one you would archive, and doing it +mid-run would leave a branch, a worktree and a running agent belonging to +a card that is no longer on the board. + +Worth phrasing the refusal as an instruction rather than a prohibition — +"hold the phase first, then move it" reads as help, and "cannot be moved +while running" reads as a wall. The board's other refusals already do +this: work starts from `in-progress/` *— move the card there first*. diff --git a/tasks/backlog/59-finishing-a-phase-finishes-its-cards.md b/tasks/backlog/59-finishing-a-phase-finishes-its-cards.md new file mode 100644 index 0000000..139a0bd --- /dev/null +++ b/tasks/backlog/59-finishing-a-phase-finishes-its-cards.md @@ -0,0 +1,99 @@ +# 59 — Finishing a phase finishes its cards + +**Status:** Backlog +**Priority:** Medium — without it a phase ends by handing you a pile of +cards you have already judged +**Type:** Feature + +When a phase's PR merges into `main`, its members' work is in `main` too — +but their cards are sitting in `review/`, where the board says they are +waiting on you. **Merge & clean up** on a phase card should move its +members to `done/` in the same operation that moves the phase. + +## Context + +- A member stops at `review/`. The runner merges its branch into the + phase branch and records it, and deliberately does not move the card: + `done/` has always meant *merged into `main`*, and merged into a phase + branch is not that. That reasoning is right and should survive this + card. +- What makes it wrong at the end is card 56: members are hidden from the + Board while the phase holds them, and reappear when it lets go. A phase + reaching `done/` therefore returns three or five cards to `review/` in + one redraw — all of them merged, none of them needing anything, and all + of them in the column whose note is "your move". +- The operation to extend already exists and already does careful + multi-step git work: `github.complete_task()` (`:387`) parks the drive, + merges, cleans up the worktree and branch, then calls `move_task` for + the one card. It narrates each step and aborts cleanly on a conflict. +- Board-made moves commit themselves under `BOARD_COMMIT_MOVES` and, in + team mode, push — so a sweep of five cards is five commits or one, and + which it is deserves a decision rather than an accident. + +**Affected areas:** `manager/core/github.py` (`complete_task`), and +whatever narrates the result. + +## What to build + +- **The sweep, inside the merge.** When a phase card is completed, every + member the phase lists moves to `done/` as part of the same operation — + after the merge into `main` has actually succeeded, never before. +- **Only what the phase merged.** A member that never reached the phase + branch — halted, held, walked back — is not swept. It stays where it + is, and it is the reason a person will look at the phase afterwards. +- **Narrate it as one thing.** The ticker should say a phase finished and + how many cards went with it, not five separate moves scrolling past. A + person watching should see one event, because one thing happened. +- **Decide the commit shape deliberately.** One commit for the sweep + reads better in `git log` than five `board: NN → done` lines in a row, + but the message must still say what moved; and in team mode it has to + reach the other boards either way. +- **Abort together.** If the merge fails, nothing moves — the existing + behaviour, extended to the members. A half-swept phase is worse than an + unswept one. +- **The other endings do not sweep.** Archiving a phase card, or removing + a member from its list, releases the members to the board in whatever + stage they are actually in (card 56). Only *merging* means the work is + in `main`, and only merging may say `done/`. + +**Out of scope** — tempting neighbours left alone: + +- Moving members while the phase runs. They stop at `review/` on purpose. +- Sweeping on any path other than **merge & clean up** — including "just + move the card", which explicitly leaves the work alone. +- Closing the members' own PRs. Those were opened against the phase + branch and are closed by their own merges. + +## Acceptance + +- [ ] Given a phase whose members are all merged, when **merge & clean + up** succeeds, then the phase card and every merged member are in + `done/`. +- [ ] The ticker reports it as one ending, naming the phase and the + number of cards. +- [ ] Given the merge fails or conflicts, nothing moves — not the phase + card, not one member. +- [ ] Given a member that never reached the phase branch, it is left + exactly where it is. +- [ ] Given "just move the card" instead, no member moves. +- [ ] Given the phase card is archived rather than merged, its members + reappear on the board in their own stages and none of them is + marked done. +- [ ] With `BOARD_COMMIT_MOVES` on, the sweep is committed and, in team + mode, published — a sweep that never leaves one working tree is not + a sweep. +- [ ] Edge case: a member already in `done/` — moved by hand — is not + moved again and does not fail the operation. + +## Notes + +This is the card that makes the ending feel like an ending. Everything +else about phases is about the middle: running, halting, resuming. The +last thing a person does is drag one card to `done/`, and what should +happen is that the whole phase goes quiet — not that five cards they have +already reviewed reappear asking for attention. + +The reason it is separate from card 56 rather than folded in: 56 is about +what the *Board view draws*, and this is about what the *board does*. One +is a rendering rule, the other moves files and commits them. Keeping them +apart also keeps 56 shippable on its own. From a6291ec7ba0c1b535dbf277922640c86add3a8f7 Mon Sep 17 00:00:00 2001 From: istos Date: Sun, 2 Aug 2026 07:17:14 +0200 Subject: [PATCH 4/5] =?UTF-8?q?board:=2047=20=E2=86=92=20review=20(istos)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{in-progress => review}/47-an-archive-button-on-the-card.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tasks/{in-progress => review}/47-an-archive-button-on-the-card.md (99%) diff --git a/tasks/in-progress/47-an-archive-button-on-the-card.md b/tasks/review/47-an-archive-button-on-the-card.md similarity index 99% rename from tasks/in-progress/47-an-archive-button-on-the-card.md rename to tasks/review/47-an-archive-button-on-the-card.md index c4309fe..04ce378 100644 --- a/tasks/in-progress/47-an-archive-button-on-the-card.md +++ b/tasks/review/47-an-archive-button-on-the-card.md @@ -1,6 +1,6 @@ # 47 — An archive button on the card, where the card is -**Status:** In Progress +**Status:** Review **Assignee:** istos **Priority:** Medium — archiving works, but only by dragging a card the length of the board to a tray at the far corner From 4ce8b11d9b741d40930ef50ee79f0b1144d3c24b Mon Sep 17 00:00:00 2001 From: istos Date: Sun, 2 Aug 2026 07:17:20 +0200 Subject: [PATCH 5/5] board: 47 PR opened (istos) --- tasks/review/47-an-archive-button-on-the-card.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks/review/47-an-archive-button-on-the-card.md b/tasks/review/47-an-archive-button-on-the-card.md index 04ce378..715849d 100644 --- a/tasks/review/47-an-archive-button-on-the-card.md +++ b/tasks/review/47-an-archive-button-on-the-card.md @@ -1,6 +1,7 @@ # 47 — An archive button on the card, where the card is **Status:** Review +**PR:** https://github.com/12vectors/bench/pull/45 **Assignee:** istos **Priority:** Medium — archiving works, but only by dragging a card the length of the board to a tray at the far corner