3.7 KiB
3.7 KiB
07 — Issues & Epics
The two work-item levels. Schema in 02-data-model.md; routes in 04-api.md; board/list UI in 13-frontend.md.
Issues
- Create: title required; mints
project_issue_idvia the counter UPDATE inside the insert tx (concurrent creates never duplicate or skip visibly); initialboard_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:
- →
donesetscompleted_at = now(); leavingdoneclears it duplicateandcanceledcount as "closed" (withdone) for epic progress and UI dimming
- →
- Assignment: single assignee; assigning emits
issue.assigned(notification + auto-watch); unassigning emitsissue.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_blockerswith 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_rankis a fractional lexicographic rank (LexoRank-style base-36 strings, e.g."hzzz", midpoint insertionbetween(a, b)); implementrank.Between/Before/Afterhelpers 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=boardreturns 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
qover 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_atis not stored for epics — progress is computed, keep it simple) - Issue membership:
issues.epic_id+epic_rankorders issues within the epicPUT /epics/{epicId}/issues/{num}attaches (or moves, withafter_rank);DELETEdetaches (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
- issue counts: total, closed (
- Epic list sortable by status/title/progress; filter by status
- Soft delete: epic disappears, issues keep
epic_iduntil cull nulls it; UI treats a soft-deleted epic reference as none - Emit
epic.created/updated/status_changed/deleted