Files
solopm-server/plans/07-issues-epics.md

47 lines
3.7 KiB
Markdown

# 07 — Issues & Epics
The two work-item levels. Schema in [02-data-model.md](02-data-model.md); routes in [04-api.md](04-api.md); board/list UI in [13-frontend.md](13-frontend.md).
## Issues
- [ ] Create: title required; mints `project_issue_id` via the counter UPDATE inside the insert tx (concurrent creates never duplicate or skip visibly); initial `board_rank` = top of its status column
- [ ] Fields editable via PATCH: title, description, status, priority, estimate, assignee_id, epic_id, parent_issue_id, start_date, target_date
- [ ] Status automation:
- [ ]`done` sets `completed_at = now()`; leaving `done` clears it
- [ ] `duplicate` and `canceled` count as "closed" (with `done`) for epic progress and UI dimming
- [ ] Assignment: single assignee; assigning emits `issue.assigned` (notification + auto-watch); unassigning emits `issue.unassigned`
- [ ] Estimate: story points ∈ {1, 2, 3, 5, 8, 13}, nullable; displayed as a badge; summed on epics
- [ ] Sub-issues: `parent_issue_id`, same project only; UI nests one level (data allows deeper); parent detail lists children with status; deleting a parent does **not** delete children (they keep the dangling parent until cull nulls it)
- [ ] Blockers: `issue_blockers` with same-project constraint and DFS cycle detection (409); blocked issues show a blocked indicator in list/board/detail
- [ ] Labels: apply/remove from the project's label set; emit `issue.label_added/removed`
- [ ] Soft delete; emits `issue.deleted`
## Kanban ordering (board_rank)
- [ ] `board_rank` is a **fractional lexicographic rank** (LexoRank-style base-36 strings, e.g. `"hzzz"`, midpoint insertion `between(a, b)`); implement `rank.Between/Before/After` helpers with unit tests
- [ ] Move = PATCH with `{status?, after_rank?}`: server computes the new rank between neighbors; ties never occur because ranks are always unique strings per column (append-suffix on exhaustion)
- [ ] `?view=board` returns issues grouped by status, each group ordered by board_rank; the 7 fixed statuses are the columns
- [ ] Periodic rebalance is NOT needed in v1 (rank strings grow slowly at this scale); note as future work if strings exceed 64 chars
- [ ] Rank-only moves emit `issue.moved` (audited, hidden from activity feeds, still broadcast over SSE for live board sync)
## List view semantics
- [ ] Filters (combinable): status, assignee, label, priority, epic, parent, free-text `q` over title
- [ ] Sort: project_issue_id (default desc), title, status, priority, estimate, assignee, target_date, updated_at
- [ ] Pagination per API conventions; the board view is not paginated (whole project board ≤ a few hundred issues at target scale)
## Epics
- [ ] Fields: title, description (markdown), status (`backlog…canceled`), color (palette hex)
- [ ] Status automation mirrors projects (`completed``completed_at` is not stored for epics — progress is computed, keep it simple)
- [ ] Issue membership: `issues.epic_id` + `epic_rank` orders issues within the epic
- [ ] `PUT /epics/{epicId}/issues/{num}` attaches (or moves, with `after_rank`); `DELETE` detaches (epic_id NULL)
- [ ] An issue belongs to at most one epic; same project only
- [ ] Progress (computed in the list/detail queries, no denormalized counters):
- [ ] issue counts: total, closed (`done`+`canceled`+`duplicate`), open
- [ ] story points: total estimated, closed estimated (unestimated issues counted separately as "unestimated: N")
- [ ] percent = closed/total issues; UI progress bar shows both counts and points
- [ ] Epic list sortable by status/title/progress; filter by status
- [ ] Soft delete: epic disappears, issues keep `epic_id` until cull nulls it; UI treats a soft-deleted epic reference as none
- [ ] Emit `epic.created/updated/status_changed/deleted`