tasks: phases get a room of their own — 56, 57, 58, 59
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.
This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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*.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user