Project

General

Profile

Actions

Feature #1544

closed
RA

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

API: per-user board column preferences

Feature #1544: API: per-user board column preferences

Added by Redmine Admin 1 day ago. Updated 1 day ago.

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

0%

Estimated time:
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:
feat/1544-board-column-prefs
pr_url:
https://github.com/omdev-tech/PipeLiner-Client/pull/95
security_key:
severity:
paused:

Description

**Surface:** backend (`api/`)
**Depends on:** nothing
**Blocks:** "Frontend: Kanban board replaces the ticketing list view"

The board's column show/hide filter must persist per user (epic AC #4). No per-user preference store exists in this repo today — the only server-persisted per-user setting is `clients.language` via `PATCH /auth/me/language`. This ticket creates the first real one, deliberately scoped to board columns rather than built as a general-purpose settings service.

Add an Alembic migration for a `(user, project)`-keyed table holding the hidden column status ids, plus read and write endpoints under the authenticated user's namespace. Validate incoming ids against the canonical `STAGE_ORDER` (`domain/tasks/value_objects.py:92`) — do not re-derive stage order.

**Default when no row exists:** Shipped (13) hidden, every other status column shown.

> **Open PO question, resolve before building:** the epic permits *"browser cache **or** backend storage"*. This ticket specifies the server-side store, which additionally gives cross-device persistence. A localStorage-only implementation would satisfy the same user-visible AC for a fraction of the work (precedent: `src/features/claude/fallbackPreference.ts`), at the cost of per-device preferences and this whole child. Confirm before starting.

## Acceptance criteria
- [ ] Alembic migration creates the table with a unique constraint on `(user_id, project_id)`; `alembic upgrade head` then `downgrade` both run clean against an ephemeral `pgvector/pgvector:pg16`.
- [ ] A GET endpoint returns the caller's hidden column ids for a project; with **no stored row** it returns the documented default (exactly the Shipped status id) with 200 — not a 404.
- [ ] A write endpoint persists the set and is idempotent: a repeated identical write leaves exactly one row.
- [ ] A status id absent from `STAGE_ORDER` is rejected with 422 and nothing is persisted.
- [ ] A stored preference referencing a status id later removed from `STAGE_ORDER` is ignored gracefully — 200 with that entry dropped, never a 500.
- [ ] **Isolation:** user A's write leaves user B's read unchanged for the same project, and a write on project 1 does not affect project 2.
- [ ] A caller cannot read or write preferences for another user, nor for a project they are not a member of — same not-found shape used elsewhere, no existence leak.
- [ ] **Preferences cannot widen visibility:** hiding or showing columns leaves the set of ticket ids returned by the board endpoint identical (test). Column preferences are presentation-only and must never interact with the D2 matrix.
- [ ] Hiding every column is either rejected or documented as legal and rendered as an explicit empty-board state; whichever is chosen is asserted by a test.
- [ ] Gates green: `python -m pytest -q` (**run serially**), `ruff check .`, `mypy src`.

Actions

Also available in: PDF Atom