feat(PBI-63): meerdere sprints per product + EXCLUDED + sprint-switcher (#161)

- Sprint lifecycle: ACTIVE→OPEN, COMPLETED→CLOSED, +ARCHIVED (FAILED behouden)
- TaskStatus: +EXCLUDED (overgeslagen door agent-loop via bestaande TO_DO filter)
- Cookie-gebaseerde actieve sprint per product (lib/active-sprint.ts)
- Route splitsen: /products/[id]/sprint/[sprintId] + /sprint redirect-page
- NavBar: gestapelde product/sprint dropdowns + BUILDING-badge derivatie
- Backlog selectie-modus + nieuwe-sprint-dialog (createSprintWithPbisAction)
- Migratie 20260507210000_sprint_lifecycle: ALTER TYPE RENAME (geen data-rewrite)
- Version bump 1.0.0 → 1.2.0

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-08 00:15:04 +02:00 committed by GitHub
parent d68aa1e5e6
commit 4a9db57e94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 966 additions and 290 deletions

View file

@ -0,0 +1,19 @@
-- PBI-63: Sprint-lifecycle migratie
--
-- 1. Hernoem SprintStatus ACTIVE → OPEN, COMPLETED → CLOSED (bestaande data behouden, geen rewrite)
-- 2. Voeg ARCHIVED toe (FAILED blijft)
-- 3. Pas Sprint.status default aan naar OPEN
-- 4. Voeg EXCLUDED toe aan TaskStatus
--
-- ALTER TYPE ... RENAME VALUE werkt vanaf PostgreSQL 10 zonder data-rewrite.
-- ALTER TYPE ... ADD VALUE moet buiten een transaction-block uitgevoerd worden;
-- Prisma migrate runt elk SQL-bestand zonder impliciete BEGIN/COMMIT, dus
-- losse statements zijn voldoende.
ALTER TYPE "SprintStatus" RENAME VALUE 'ACTIVE' TO 'OPEN';
ALTER TYPE "SprintStatus" RENAME VALUE 'COMPLETED' TO 'CLOSED';
ALTER TYPE "SprintStatus" ADD VALUE 'ARCHIVED';
ALTER TABLE "sprints" ALTER COLUMN "status" SET DEFAULT 'OPEN';
ALTER TYPE "TaskStatus" ADD VALUE 'EXCLUDED';

View file

@ -61,6 +61,7 @@ enum TaskStatus {
REVIEW
DONE
FAILED
EXCLUDED
}
enum LogType {
@ -75,8 +76,9 @@ enum TestStatus {
}
enum SprintStatus {
ACTIVE
COMPLETED
OPEN
CLOSED
ARCHIVED
FAILED
}
@ -302,7 +304,7 @@ model Sprint {
product_id String
code String @db.VarChar(30)
sprint_goal String
status SprintStatus @default(ACTIVE)
status SprintStatus @default(OPEN)
start_date DateTime? @db.Date
end_date DateTime? @db.Date
created_at DateTime @default(now())

View file

@ -21,7 +21,7 @@ export type ParsedMilestone = {
title: string
goal: string
priority: 1 | 2 | 3 | 4
sprint_status: 'ACTIVE' | 'COMPLETED'
sprint_status: 'OPEN' | 'CLOSED'
sort_order: number
stories: ParsedStory[]
}
@ -66,19 +66,19 @@ const MILESTONE_GOAL: Record<string, string> = {
}
const MILESTONE_SPRINT_STATUS: Record<string, ParsedMilestone['sprint_status']> = {
M0: 'COMPLETED',
M1: 'COMPLETED',
M2: 'COMPLETED',
M3: 'COMPLETED',
'M3.5': 'COMPLETED',
M4: 'COMPLETED',
M5: 'COMPLETED',
M6: 'COMPLETED',
M7: 'COMPLETED',
M8: 'COMPLETED',
M9: 'COMPLETED',
M10: 'COMPLETED',
M11: 'COMPLETED',
M0: 'CLOSED',
M1: 'CLOSED',
M2: 'CLOSED',
M3: 'CLOSED',
'M3.5': 'CLOSED',
M4: 'CLOSED',
M5: 'CLOSED',
M6: 'CLOSED',
M7: 'CLOSED',
M8: 'CLOSED',
M9: 'CLOSED',
M10: 'CLOSED',
M11: 'CLOSED',
}
const MILESTONE_KEY = /^(?:M[\d.]+|PBI-\d+)$/
@ -154,7 +154,7 @@ export async function loadBacklog(
title,
goal: MILESTONE_GOAL[key] ?? title,
priority: MILESTONE_PRIORITY[key] ?? 4,
sprint_status: MILESTONE_SPRINT_STATUS[key] ?? 'COMPLETED',
sprint_status: MILESTONE_SPRINT_STATUS[key] ?? 'CLOSED',
sort_order: milestones.length + 1,
stories: [],
}

View file

@ -155,7 +155,7 @@ async function main() {
const forceOpen = ms.key === 'M3.5'
for (const s of ms.stories) {
const isActive = ms.sprint_status === 'ACTIVE'
const isActive = ms.sprint_status === 'OPEN'
const effectivelyDone = !forceOpen && s.status === 'DONE'
const inSprint = isActive || effectivelyDone
const storyStatus = effectivelyDone ? 'DONE' : isActive ? 'IN_SPRINT' : 'OPEN'