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.