B2 — Planner board: group-by pivot (agent | stage | repo) #410

Closed
opened 2026-04-27 00:01:54 +00:00 by claude-desktop · 0 comments
Collaborator

As an operator,
I want to switch the column axis between agent type, pipeline stage, and repo,
so that I can audit "what is every reviewer doing" or "what does the spec column look like across repos" without leaving the route.

Today /planner/board columns are always per agent type. A ?repo= filter exists but no native pivot. This story adds a Linear-style group-by toggle that re-projects the same card set against three axes.

Acceptance criteria

Frontend

  • Toolbar segmented control Group: [agent | stage | repo], persisted in URL as ?group=, default agent.
  • group=agent (default) — current column projection, unchanged.
  • group=stage — columns from the pipeline stage enum (triage, spec, impl, review, merged); each card still shows its agent on the card face.
  • group=repo — one column per repo with at least one open card; cards still show their stage badge.
  • Filter chips (repo, milestone, label, unassigned) work identically across pivots. (repo chip becomes a no-op when group=repo — show it disabled with a tooltip.)
  • Empty columns are still rendered (with an empty-state placeholder) so the operator sees the full axis.

Drag semantics per pivot

  • group=agent — drag triggers assignCard (existing behaviour). Dropping onto a different agent column reassigns; B1 covers the Unassigned target.
  • group=stage — drag triggers a new restageCard mutation (see backend). Dropping onto a different stage updates the stage label + cancels the running task on the previous stage's agent, mirroring /board/reroute semantics. Source-and-target-equal drops are no-ops.
  • group=repo — drag is disabled. Cards render with cursor-default and no drag handle. Cross-repo move is not a meaningful op.

Backend — new endpoint POST /board/restage

  • Body: { repo: string, issue_number: number, new_stage: "triage" | "spec" | "impl" | "review" | "merged" }.
  • Reject illegal transitions (e.g. merged → triage); return 400 { error: "illegal_stage_transition", from, to }.
  • Update the stage label on the issue (remove old stage:* label, add new). Use mcp__forgejo__add_issue_labels and remove_issue_labels server-side helpers in apps/server/src/infrastructure/forge/.
  • Cancel any running task on the previous stage's agent.
  • Post an audit comment: Stage moved from @{from} to @{to} by @{actor} via board.
  • Return { ok: true, repo, issue_number, new_stage }.

Tests

  • board.test.tsx: pivot toggle updates URL, re-projects columns, preserves card identity (same key → same DOM node where possible).
  • board.test.tsx: drag in group=stagerestageCard call.
  • board.test.tsx: drag in group=repo is a no-op (no dragstart or visibly suppressed).
  • Server test for /board/restage: happy path, illegal transition, running-task cancellation, audit comment.

Out of scope

  • Group-by custom field (we have no custom fields).
  • Saved-view multiplicity (one operator, one URL).
  • Per-instance grouping (deferred with the rest of the per-instance work).

References

  • Spec: docs/specs/board-rework.md §5 B2 + §6.
  • Pipeline stage enum: packages/shared/src/pipeline.ts (or wherever the enum lives — check apps/server/src/domain/views/pipeline.ts).
  • Existing restage-equivalent: POST /board/reroute in apps/server/src/domain/views/board.ts.
  • Stage label conventions: docs/label-routing.md.

Dependencies

  • Pairs well with B5 (card face must show stage when grouped by agent, and agent when grouped by stage). Land B5 first if both are open.

Suggested first commit

feat(board): group-by pivot — agent | stage | repo

**As an** operator, **I want** to switch the column axis between *agent type*, *pipeline stage*, and *repo*, **so that** I can audit "what is every reviewer doing" or "what does the spec column look like across repos" without leaving the route. Today `/planner/board` columns are always per agent type. A `?repo=` filter exists but no native pivot. This story adds a Linear-style group-by toggle that re-projects the same card set against three axes. ## Acceptance criteria ### Frontend - [ ] Toolbar segmented control `Group: [agent | stage | repo]`, persisted in URL as `?group=`, default `agent`. - [ ] `group=agent` (default) — current column projection, unchanged. - [ ] `group=stage` — columns from the pipeline stage enum (`triage`, `spec`, `impl`, `review`, `merged`); each card still shows its agent on the card face. - [ ] `group=repo` — one column per repo with at least one open card; cards still show their stage badge. - [ ] Filter chips (`repo`, `milestone`, `label`, `unassigned`) work identically across pivots. (`repo` chip becomes a no-op when `group=repo` — show it disabled with a tooltip.) - [ ] Empty columns are still rendered (with an empty-state placeholder) so the operator sees the full axis. ### Drag semantics per pivot - [ ] `group=agent` — drag triggers `assignCard` (existing behaviour). Dropping onto a different agent column reassigns; B1 covers the Unassigned target. - [ ] `group=stage` — drag triggers a new `restageCard` mutation (see backend). Dropping onto a different stage updates the stage label + cancels the running task on the previous stage's agent, mirroring `/board/reroute` semantics. Source-and-target-equal drops are no-ops. - [ ] `group=repo` — drag is **disabled**. Cards render with `cursor-default` and no drag handle. Cross-repo move is not a meaningful op. ### Backend — new endpoint `POST /board/restage` - [ ] Body: `{ repo: string, issue_number: number, new_stage: "triage" | "spec" | "impl" | "review" | "merged" }`. - [ ] Reject illegal transitions (e.g. `merged → triage`); return `400 { error: "illegal_stage_transition", from, to }`. - [ ] Update the stage label on the issue (remove old `stage:*` label, add new). Use `mcp__forgejo__add_issue_labels` and `remove_issue_labels` server-side helpers in `apps/server/src/infrastructure/forge/`. - [ ] Cancel any running task on the previous stage's agent. - [ ] Post an audit comment: `Stage moved from @{from} to @{to} by @{actor} via board.` - [ ] Return `{ ok: true, repo, issue_number, new_stage }`. ### Tests - [ ] `board.test.tsx`: pivot toggle updates URL, re-projects columns, preserves card identity (same key → same DOM node where possible). - [ ] `board.test.tsx`: drag in `group=stage` → `restageCard` call. - [ ] `board.test.tsx`: drag in `group=repo` is a no-op (no `dragstart` or visibly suppressed). - [ ] Server test for `/board/restage`: happy path, illegal transition, running-task cancellation, audit comment. ## Out of scope - Group-by custom field (we have no custom fields). - Saved-view multiplicity (one operator, one URL). - Per-instance grouping (deferred with the rest of the per-instance work). ## References - Spec: `docs/specs/board-rework.md` §5 B2 + §6. - Pipeline stage enum: `packages/shared/src/pipeline.ts` (or wherever the enum lives — check `apps/server/src/domain/views/pipeline.ts`). - Existing restage-equivalent: `POST /board/reroute` in `apps/server/src/domain/views/board.ts`. - Stage label conventions: `docs/label-routing.md`. ## Dependencies - Pairs well with **B5** (card face must show stage when grouped by agent, and agent when grouped by stage). Land B5 first if both are open. ## Suggested first commit `feat(board): group-by pivot — agent | stage | repo`
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#410
No description provided.