Project

General

Profile

Actions

Feature #1511

closed
CD

Replace ticket view with Kanban board for CTO and assigned developers

Feature #1511: Replace ticket view with Kanban board for CTO and assigned developers

Added by Client Dashboard 5 days ago. Updated 1 day ago.

Status:
Shipped
Priority:
Normal
Assignee:
-
Start date:
08/03/2026
Due date:
% Done:

100%

Estimated time:
(Total: 0:00 h)
spec_ref:

docs/superpowers/specs/2026-08-03-kanban-board-design.md

build_status:
build_number:
ci_run_url:
scan_status:
scan_report_url:
deploy_status:
preprod_url:
deployed_at:
branch:
pr_url:
security_key:
severity:
paused:

Description

### Problem
The current ticketing view is a static list/table, which lacks visual tracking and real-time collaboration for the CTO and assigned developers. Users need a more intuitive way to manage tickets by status, with drag-and-drop functionality and real-time updates.

### Context
- The Kanban board will **replace** the existing ticket view (not a new tab or overlay).
- It will be **visible only to the CTO and the assigned developer** (if any).
- The board must support **real-time updates** (with smooth animations) and **manual refresh**.
- User preferences (e.g., hidden columns) must be saved per user (cached).
- The design phase must precede implementation to align on UI/UX.

### Proposed Behaviour
- **Columns**: Default to all ticket statuses (e.g., "Submitted," "Spec," "In Development"), with a filter to hide/show columns. "Shipped" is hidden by default.
- **Cards**: Display the ticket title, assignee name, priority, and parent ticket (if applicable).
- **Drag-and-drop**: Enabled—moving a ticket to a new column updates its status automatically.
- **Real-time updates**: Tickets move instantly when their status changes (e.g., via API or another user’s action).
- **User preferences**: Hidden/shown columns are saved per user (e.g., via browser cache or backend storage).

## Acceptance criteria
['- [ ] The Kanban board replaces the existing ticket view (no overlay or new tab).', '- [ ] The board is visible **only** to the CTO and the assigned developer (if any).', '- [ ] Columns default to all ticket statuses, with "Shipped" hidden by default.', '- [ ] A filter allows users to hide/show columns (saved per user).', '- [ ] Cards display: ticket title, assignee name, priority, and parent ticket (if applicable).', '- [ ] Drag-and-drop updates the ticket status automatically (with error handling).', '- [ ] Real-time updates move tickets instantly (with smooth animations).', '- [ ] A manual refresh option is available.', '- [ ] User preferences (e.g., hidden columns) persist across sessions.', '- [ ] Design mockups and behavior specs are reviewed and approved before implementation.']

## Classification
- feature

## Complexity
- 7/10 — Requires UI/UX design, real-time updates, drag-and-drop functionality, and user preference persistence, but leverages existing ticket data and statuses.


Subtasks 8 (0 open8 closed)

Feature #1541: Aggregator: emit assignee and priority on ticket_listShipped08/03/2026

Actions
Feature #1542: API: assignee, priority and parent on the task list payloadShipped08/03/2026

Actions
Feature #1543: API: per-viewer visibility matrix and per-card drag capabilityShipped08/03/2026

Actions
Feature #1544: API: per-user board column preferencesShipped08/03/2026

Actions
Feature #1545: Design: Kanban board section in the design systemShipped08/03/2026

Actions
Feature #1546: Frontend: Kanban board replaces the ticketing list viewShipped08/03/2026

Actions
Feature #1547: Frontend: board polling, manual refresh and card-move animationShipped08/03/2026

Actions
Feature #1554: API: per-category counts on the board payload, so the tab pills stop undercountingShipped08/03/2026

Actions

CD Updated by Client Dashboard 1 day ago Actions #1

  • Status changed from Backlog to Submitted

RA Updated by Redmine Admin 1 day ago Actions #2

  • Status changed from Submitted to Spec

RA Updated by Redmine Admin 1 day ago Actions #3

  • Subtask #1532 added

RA Updated by Redmine Admin 1 day ago Actions #4

  • Subtask #1533 added

RA Updated by Redmine Admin 1 day ago Actions #5

  • Subtask #1534 added

RA Updated by Redmine Admin 1 day ago Actions #6

  • Subtask #1535 added

RA Updated by Redmine Admin 1 day ago Actions #7

  • Subtask #1536 added

RA Updated by Redmine Admin 1 day ago Actions #8

  • Subtask #1537 added

RA Updated by Redmine Admin 1 day ago Actions #9

  • Subtask #1538 added

RA Updated by Redmine Admin 1 day ago Actions #10

  • spec_ref updated (diff)

## Spec + decomposition

Specced and decomposed into **7 children (#1532–#1538)**. Carrying spec: `docs/superpowers/specs/2026-08-03-kanban-board-design.md`.

### Product-owner decisions (these override the ticket text)

**D1 — The board replaces the list, for everyone.** In place, no overlay, no new tab, no toggle. Drag inert for ordinary clients. This **supersedes acceptance criterion #2** ("visible only to the CTO and the assigned developer") — that AC is withdrawn and replaced by the D2 matrix. QA tests against the matrix, not AC #2.

**D2 — Visibility matrix.**

| Viewer | Cards visible | Drag |
|---|---|---|
| Ordinary client | all tickets in their project | none — read-only |
| Developer | **only** tickets assigned to them | their single legal forward step |
| CTO / admin | all tickets | full allowed set, backward included |

A developer deliberately sees **fewer** cards than the client does — the client owns the project view, a developer's board is their work queue. Not a bug; do not widen it in review.

**D3 — "Real-time" means polling.** `refetchInterval` + refetch-on-focus. This **narrows** the "tickets move instantly" AC to "within one poll interval, animated, plus manual refresh". SSE is out of scope and becomes a follow-up ticket.

**D4 — Design gate.** No frontend code before the design-system section is written and approved (the ticket's own AC #10).

### Children

| # | Child | Surface | Depends on |
|---|---|---|---|
| #1532 | Aggregator emits assignee + priority on `ticket_list` | `../aggregator` | — |
| #1533 | API: assignee, priority, parent + board fetch cap + pre-change resilience | `api/` | — (soft: #1532) |
| #1534 | API: per-viewer visibility matrix + per-card drag capability | `api/` | #1533 |
| #1535 | API: per-user board column preferences | `api/` | — |
| #1536 | Design: Kanban section in the design system | docs only | — |
| #1537 | Frontend: the board replaces the list view | `src/`+`app/` | #1534, #1535, #1536 |
| #1538 | Frontend: polling, manual refresh, card-move animation | `src/` | #1537 |

Display data (#1533) and authorization (#1534) are split on purpose: a mistake in the former shows a wrong name, a mistake in the latter leaks another developer's or another tenant's tickets.

### Three things the review caught that the original scoping missed

1. **Board data is aggregator-owned** — the list comes from the aggregator's `ticket_list`, not Redmine, so assignee and priority need a sibling-repo change first. Priority does not exist anywhere in pipeliner-client today.
2. **Cross-repo delivery risk** — the aggregator ships on its own pipeline, so the dashboard will run against a version without the new fields. #1533 carries an explicit test feeding a pre-change payload (schema-valid 200, null fields, no 500 — gotcha #2). That demotes #1532 from release blocker to data-quality follow-through.
3. **Board scale was unaddressed** — the replaced view is paginated; a board loads whole columns. Now an explicit per-status cap (proposed 50) with a "+N more" affordance, asserted server- and client-side.

### Open questions

1. **#1535** — server-side preference store, or localStorage? The ticket permits either. Not on the critical path.
2. **Board cap** (proposed 50/column) and **poll interval** (proposed 30s).

### Billing

Complexity stays pinned at **7/10** as charged at filing. No child carries or re-derives a complexity or price field.

RA Updated by Redmine Admin 1 day ago Actions #11

  • Subtask deleted (#1532)

RA Updated by Redmine Admin 1 day ago Actions #12

  • Subtask deleted (#1533)

RA Updated by Redmine Admin 1 day ago Actions #13

  • Subtask deleted (#1534)

RA Updated by Redmine Admin 1 day ago Actions #14

  • Subtask deleted (#1535)

RA Updated by Redmine Admin 1 day ago Actions #15

  • Subtask deleted (#1537)

RA Updated by Redmine Admin 1 day ago Actions #16

  • Subtask deleted (#1538)

RA Updated by Redmine Admin 1 day ago Actions #17

  • Subtask deleted (#1536)

RA Updated by Redmine Admin 1 day ago Actions #18

  • Subtask #1541 added

RA Updated by Redmine Admin 1 day ago Actions #19

  • Subtask #1542 added

RA Updated by Redmine Admin 1 day ago Actions #20

  • Subtask #1543 added

RA Updated by Redmine Admin 1 day ago Actions #21

  • Subtask #1544 added

RA Updated by Redmine Admin 1 day ago Actions #22

  • Subtask #1545 added

RA Updated by Redmine Admin 1 day ago Actions #23

  • Subtask #1546 added

RA Updated by Redmine Admin 1 day ago Actions #24

  • Subtask #1547 added

RA Updated by Redmine Admin 1 day ago Actions #25

## Correction — child ticket ids

The children first created as #1532–#1538 landed in **Backlog**, because Backlog was the only permitted initial status for the Feature tracker. Rather than walk them through the credit-gate edge (Backlog → Submitted → Spec), Spec was added as a permitted initial status in the Redmine workflow and the children were **deleted and recreated directly at Spec**.

**#1532–#1538 no longer exist.** The current children are:

| # | Child | Surface | Depends on |
|---|---|---|---|
| #1541 | Aggregator emits assignee + priority on `ticket_list` | `../aggregator` | — |
| #1542 | API: assignee, priority, parent + board fetch cap + pre-change resilience | `api/` | — (soft: #1541) |
| #1543 | API: per-viewer visibility matrix + per-card drag capability | `api/` | #1542 |
| #1544 | API: per-user board column preferences | `api/` | — |
| #1545 | Design: Kanban section in the design system | docs only | — |
| #1546 | Frontend: the board replaces the list view | `src/`+`app/` | #1543, #1544, #1545 |
| #1547 | Frontend: polling, manual refresh, card-move animation | `src/` | #1546 |

All seven are at **Spec** with `spec_ref` set. Everything else in the previous comment — D1–D4, the visibility matrix, the three review findings, the open questions — stands unchanged.

The credit gate was never crossed: no child was ever moved out of Backlog, and the transition graph is untouched. The workflow change affects creation only.

RA Updated by Redmine Admin 1 day ago Actions #26

  • Subtask #1554 added

RA Updated by Redmine Admin 1 day ago Actions #27

  • Status changed from Spec to Design

RA Updated by Redmine Admin 1 day ago Actions #28

## Code-complete — `dev` and `preprod` are green, awaiting the production gate

All eight children are merged. `dev` is **22 commits** ahead of `master`, single migration head `0021_board_column_prefs`.

| Build | Result |
|---|---|
| `dev` #121 | ✅ green on the real merged tree |
| `preprod` #108 | ✅ green, `:preprod` images built |

`master` has **not** been pushed. It needs a human at the "Approve PRODUCTION deploy" input, which times out in ~1h.

### Children

| # | Surface | |
|---|---|---|
| #1541 | aggregator | `assignee_ref` / `priority_ref` on `ticket_list` |
| #1542 | `api/` | payload enrichment, board fetch cap, pre-change resilience |
| #1543 | `api/` | visibility matrix, per-card capability, filer scope |
| #1544 | `api/` | per-user column preferences |
| #1545 | design | §KB.0–KB.11 |
| #1546 | `src/` | the board replaces the list |
| #1547 | `src/` | polling, manual refresh, card-move animation |
| #1554 | both | per-category counts |

### What lands for a client

The ticketing list becomes a Kanban board for **every** viewer, assignee names become visible to clients (a deliberate reversal of #1528, name only — every capability stays internal-only), and an internal developer's view narrows to tickets assigned to *or filed by* them.

### Decisions that overrode the ticket text
**D1** the board is for everyone with drag inert for clients, superseding AC #2 · **D2** the visibility matrix, in which a developer deliberately sees fewer cards than the client · **D3** "real-time" means polling; SSE is out of scope · **D4** design approved before code.

### Carried forward, not shipped
**#1548** (Backlog, unbilled) — durable ticket authorship, so a filer keeps access after submit; `ticket_charges` is cleared at submit, so filer-scope currently lasts only while a ticket sits in Backlog. §KB.8.3's enter/exit fades are unimplemented — the exit half needs mount retention and enter-only would cause the teleport §KB.8.3 exists to prevent. The priority scale assumes the stock five-entry Redmine enumeration. The RAG corpus is scoped by project only, so a developer's own chat can be grounded in a colleague's ticket text — predates this epic.

### Process note
Every one of the six implementation children failed its first review and passed the next. Three shipped green gates with tests that asserted the right *intent* against the wrong *thing* — a drag-negative that could never start a drag, a `writes.called` on a respx route that could never match, a `staleTime` override on a QueryClient that never had the app's default. The reviews caught four dead user paths, two existence leaks and a board that blanked on any failed poll.

**Complexity remains pinned at 7/10 as charged at filing.** No child carries or re-derives a score.

RA Updated by Redmine Admin 1 day ago Actions #29

  • Status changed from Design to Shipped

## Shipped

Live in production, master build **#109** (`c784f95`). QA passed on prod; proof attached to #1546. All eight children Shipped.

### What the client gets
The ticketing screen at `/projects/{id}/ticketing` is now a Kanban board for **every** viewer — columns in canonical stage order with Shipped hidden by default, cards carrying title, assignee, priority and parent, drag-to-transition where the server permits it, a column filter that persists per user and per project, 30s polling with a manual refresh and a freshness line, and an animated card move when work advances elsewhere. Fully bilingual.

### The four decisions that overrode the ticket as written
**D1** — the board is for everyone with drag inert for clients, superseding AC #2 ("visible only to the CTO and the assigned developer"), which was withdrawn rather than left to fail at QA. **D2** — the visibility matrix, in which a developer deliberately sees *fewer* cards than the client, since the client owns the project view and a developer's board is their work queue. **D3** — "real-time" means polling; SSE is out of scope. **D4** — design approved before code.

### Known and carried forward, none blocking
- **#1548** (Backlog, unbilled) — durable ticket authorship. `ticket_charges` is cleared at submit, so a developer's filer-scope lasts only while a ticket sits in Backlog; after submitting, an unassigned filer loses sight of their own ticket. Fails closed.
- **§KB.8.3's enter/exit fades** are unimplemented — the exit half needs mount retention, and enter-only would cause the teleport the section exists to prevent.
- **The priority scale assumes the stock five-entry Redmine enumeration** — `position` is a rank within whatever that instance configured. Wrong-but-calm on a four- or six-entry instance; commented in place.
- **Drag was never exercised against the live API.** Doing so on prod would have meant moving a real client's ticket, so QA stopped rather than mutate live work. Drag, the zero-network assertion on locked columns, and the real 403/409 rollback rest on unit tests plus the prod-commit suite. Closing that needs a disposable ticket on a throwaway project.
- **Two freshness controls now share the screen** — the project header's "Updated … | Refresh data" and the board toolbar's "Refresh | Updated just now". They report different things but read as redundant.
- Pre-existing, surfaced during review: the **RAG corpus** is scoped by project only, so a developer's own chat can be grounded in a colleague's ticket text.

### Process note, for the next epic of this size
Every one of the six implementation children failed its first review and passed the next. Three shipped fully green gates with tests that asserted the right *intent* against the wrong *thing* — a drag-negative that could never start a drag because jsdom has no `PointerEvent`; an `assert not writes.called` on a respx route registered second and therefore unreachable; a `staleTime` override checked against a bare QueryClient that never carried the app's default. Between them the reviews caught two existence leaks, four dead user paths, and a board that blanked on any failed poll.

Green gates were not evidence. Mutation-testing each guard — breaking the thing it protects and confirming the test goes red — is what actually caught these, and it is the practice worth keeping.

**Complexity stayed pinned at 7/10 as charged at filing.** No child carried or re-derived a score, and the one regression this epic introduced (#1554) was absorbed as a child rather than billed again.

Actions

Also available in: PDF Atom