chore: documentatie naar docs/, iconen bijgewerkt, theme.css verplaatst

- scrum4me-*.md en MD3_Color_Scheme_Documentation.md verplaatst naar docs/
- Srum4MeIcons.html verplaatst naar docs/icons.html
- theme.css verplaatst van root naar app/styles/theme.css
- Import in globals.css bijgewerkt
- Alle app-iconen vervangen door nieuw logo (icon-master-light.svg)
- AppIcon component bijgewerkt met nieuw SVG
- CLAUDE.md verwijzingen bijgewerkt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-04-24 22:51:42 +02:00
parent 8e0513e07c
commit b4371f5afb
17 changed files with 53 additions and 37 deletions

View file

@ -0,0 +1,941 @@
# Material Design 3 Color Scheme Documentation
## Project Management Application for Scrum Teams
**Version:** 1.0
**Date:** April 22, 2026
**Application:** Desktop-first fullstack web application for solo developers and small Scrum Teams
---
## Table of Contents
1. [Color Philosophy](#color-philosophy)
2. [Complete Color Palette](#complete-color-palette)
3. [Surface Elevation System](#surface-elevation-system)
4. [Color Roles](#color-roles)
5. [Semantic States](#semantic-states)
6. [Project Management Colors](#project-management-colors)
7. [Usage Examples](#usage-examples)
8. [Implementation Guide](#implementation-guide)
9. [Accessibility](#accessibility)
10. [Best Practices](#best-practices)
---
## Color Philosophy
This color scheme follows Material Design 3 principles with three main color roles optimized for productivity software:
- **Primary (Blue)** - Represents productivity, trust, and professionalism. Used for main actions and navigation.
- **Secondary (Purple)** - Represents planning and organization. Used for supporting UI elements.
- **Tertiary (Teal)** - Represents progress and data visualization. Used for highlights and metrics.
The system uses **tonal elevation** instead of shadows to create depth, providing a modern, clean interface suitable for extended desktop work sessions.
---
## Complete Color Palette
### Light Theme Colors
#### Primary Colors (Blue - Productivity)
| Color Token | Hex Code | Usage |
|-------------|----------|-------|
| `--primary` | `#0061a4` | Primary buttons, key actions |
| `--primary-foreground` | `#ffffff` | Text on primary background |
| `--primary-container` | `#d1e4ff` | Low emphasis primary elements |
| `--primary-container-foreground` | `#001d36` | Text on primary container |
#### Secondary Colors (Purple - Planning)
| Color Token | Hex Code | Usage |
|-------------|----------|-------|
| `--secondary` | `#5b5e71` | Secondary buttons, less critical actions |
| `--secondary-foreground` | `#ffffff` | Text on secondary background |
| `--secondary-container` | `#dfe1f9` | Low emphasis secondary elements |
| `--secondary-container-foreground` | `#181b2c` | Text on secondary container |
#### Tertiary Colors (Teal - Progress)
| Color Token | Hex Code | Usage |
|-------------|----------|-------|
| `--tertiary` | `#006874` | Highlights, progress indicators |
| `--tertiary-foreground` | `#ffffff` | Text on tertiary background |
| `--tertiary-container` | `#97f0ff` | Low emphasis tertiary elements |
| `--tertiary-container-foreground` | `#001f24` | Text on tertiary container |
#### Surface Colors (Elevation System)
| Color Token | Hex Code | Usage |
|-------------|----------|-------|
| `--background` | `#fdfcff` | Main app background |
| `--foreground` | `#1b1b1f` | Main text color |
| `--surface-container-lowest` | `#ffffff` | Highest elevation (dialogs) |
| `--surface-container-low` | `#f7f5fc` | Cards, elevated containers |
| `--surface-container` | `#f1eff6` | Default container background |
| `--surface-container-high` | `#ebeaf0` | Nested containers |
| `--surface-container-highest` | `#e6e3ea` | Lowest elevation |
#### Semantic State Colors
| Color Token | Hex Code | Usage |
|-------------|----------|-------|
| `--success` | `#006e1c` | Success states, completed items |
| `--success-foreground` | `#ffffff` | Text on success background |
| `--success-container` | `#92f894` | Low emphasis success |
| `--warning` | `#735b00` | Warning states, attention needed |
| `--warning-foreground` | `#ffffff` | Text on warning background |
| `--warning-container` | `#ffdf9d` | Low emphasis warning |
| `--error` | `#ba1a1a` | Error states, failures |
| `--error-foreground` | `#ffffff` | Text on error background |
| `--error-container` | `#ffdad6` | Low emphasis error |
| `--info` | `#006493` | Information, neutral alerts |
| `--info-foreground` | `#ffffff` | Text on info background |
| `--info-container` | `#c9e6ff` | Low emphasis info |
#### Project Management Specific Colors
**Work Item Status**
| Color Token | Hex Code | Status |
|-------------|----------|--------|
| `--status-todo` | `#6750a4` | Not started (Purple) |
| `--status-in-progress` | `#0061a4` | Active work (Blue) |
| `--status-done` | `#006e1c` | Completed (Green) |
| `--status-blocked` | `#ba1a1a` | Blocked/Issues (Red) |
**Priority Levels**
| Color Token | Hex Code | Priority |
|-------------|----------|----------|
| `--priority-critical` | `#ba1a1a` | Critical (Red) |
| `--priority-high` | `#c75300` | High (Orange) |
| `--priority-medium` | `#735b00` | Medium (Yellow) |
| `--priority-low` | `#006874` | Low (Teal) |
---
### Dark Theme Colors
#### Primary Colors (Blue - Productivity)
| Color Token | Hex Code | Usage |
|-------------|----------|-------|
| `--primary` | `#9fcbfa` | Primary buttons, key actions |
| `--primary-foreground` | `#003257` | Text on primary background |
| `--primary-container` | `#00497b` | Low emphasis primary elements |
| `--primary-container-foreground` | `#d1e4ff` | Text on primary container |
#### Secondary Colors (Purple - Planning)
| Color Token | Hex Code | Usage |
|-------------|----------|-------|
| `--secondary` | `#c3c4dd` | Secondary buttons |
| `--secondary-foreground` | `#2c2f42` | Text on secondary background |
| `--secondary-container` | `#434659` | Low emphasis secondary elements |
| `--secondary-container-foreground` | `#dfe1f9` | Text on secondary container |
#### Tertiary Colors (Teal - Progress)
| Color Token | Hex Code | Usage |
|-------------|----------|-------|
| `--tertiary` | `#4fd8eb` | Highlights, progress indicators |
| `--tertiary-foreground` | `#00363d` | Text on tertiary background |
| `--tertiary-container` | `#004f58` | Low emphasis tertiary elements |
| `--tertiary-container-foreground` | `#97f0ff` | Text on tertiary container |
#### Surface Colors (Elevation System)
| Color Token | Hex Code | Usage |
|-------------|----------|-------|
| `--background` | `#1b1b1f` | Main app background |
| `--foreground` | `#e4e1e9` | Main text color |
| `--surface-container-lowest` | `#0f0e13` | Highest elevation (dialogs) |
| `--surface-container-low` | `#232227` | Cards, elevated containers |
| `--surface-container` | `#27262b` | Default container background |
| `--surface-container-high` | `#322f36` | Nested containers |
| `--surface-container-highest` | `#3d3a41` | Lowest elevation |
#### Semantic State Colors
| Color Token | Hex Code | Usage |
|-------------|----------|-------|
| `--success` | `#77db77` | Success states |
| `--success-container` | `#005313` | Low emphasis success |
| `--warning` | `#efc047` | Warning states |
| `--warning-container` | `#574400` | Low emphasis warning |
| `--error` | `#ffb4ab` | Error states |
| `--error-container` | `#93000a` | Low emphasis error |
| `--info` | `#87ceff` | Information |
| `--info-container` | `#004c6d` | Low emphasis info |
#### Project Management Specific Colors (Dark)
**Work Item Status**
| Color Token | Hex Code | Status |
|-------------|----------|--------|
| `--status-todo` | `#cfbdfe` | Not started (Purple) |
| `--status-in-progress` | `#9fcbfa` | Active work (Blue) |
| `--status-done` | `#77db77` | Completed (Green) |
| `--status-blocked` | `#ffb4ab` | Blocked/Issues (Red) |
**Priority Levels**
| Color Token | Hex Code | Priority |
|-------------|----------|----------|
| `--priority-critical` | `#ffb4ab` | Critical (Red) |
| `--priority-high` | `#ffb68d` | High (Orange) |
| `--priority-medium` | `#efc047` | Medium (Yellow) |
| `--priority-low` | `#4fd8eb` | Low (Teal) |
---
## Surface Elevation System
Material Design 3 uses **tonal elevation** to create depth. Higher surfaces are lighter (in light mode) or darker (in dark mode).
### Hierarchy (from highest to lowest elevation)
1. **surface-container-lowest** - Dialogs, modals, popovers
2. **surface-container-low** - Cards, panels, elevated containers
3. **surface-container** - Default container background
4. **surface-container-high** - Nested containers, grouped elements
5. **surface-container-highest** - Background-level containers
### Tailwind Usage
```tsx
bg-surface-container-lowest
bg-surface-container-low
bg-surface-container
bg-surface-container-high
bg-surface-container-highest
```
---
## Color Roles
### Primary (Productivity & Trust)
Use for:
- Main action buttons
- Navigation highlights
- Active states
- Key interactive elements
**Examples:**
```tsx
// Filled button
<button className="bg-primary text-primary-foreground">
Create Sprint
</button>
// Outline button (less emphasis)
<button className="border-2 border-primary text-primary">
View Details
</button>
// Container variant (even less emphasis)
<div className="bg-primary-container text-primary-container-foreground">
Selected item
</div>
```
### Secondary (Planning & Support)
Use for:
- Secondary actions
- Supporting UI elements
- Less critical interactive elements
- Toolbar items
**Examples:**
```tsx
// Secondary action
<button className="bg-secondary text-secondary-foreground">
Filter
</button>
// Subtle highlight
<div className="bg-secondary-container text-secondary-container-foreground">
Planning phase
</div>
```
### Tertiary (Progress & Highlights)
Use for:
- Progress indicators
- Data visualization accents
- Highlights and callouts
- Metrics
**Examples:**
```tsx
// Progress indicator
<div className="bg-tertiary text-tertiary-foreground">
75% Complete
</div>
// Metric badge
<span className="bg-tertiary-container text-tertiary-container-foreground">
+12 stories
</span>
```
---
## Semantic States
### Success (Completed, Positive)
Use for:
- Completed tasks
- Success messages
- Positive confirmations
- Achievement indicators
```tsx
<div className="bg-success text-success-foreground">
✓ Sprint completed successfully
</div>
<div className="bg-success-container text-success-container-foreground">
8 stories completed this week
</div>
```
### Warning (Attention Needed)
Use for:
- Warnings
- Potential issues
- Items needing attention
- Approaching deadlines
```tsx
<div className="bg-warning text-warning-foreground">
⚠ Sprint deadline in 2 days
</div>
<div className="bg-warning-container text-warning-container-foreground">
3 stories at risk
</div>
```
### Error (Failures, Critical Issues)
Use for:
- Errors
- Failed operations
- Blocked items
- Critical alerts
```tsx
<div className="bg-error text-error-foreground">
✗ Failed to save changes
</div>
<div className="bg-error-container text-error-container-foreground">
1 story blocked
</div>
```
### Info (Neutral Information)
Use for:
- Informational messages
- Tips and hints
- Neutral notifications
- Help text
```tsx
<div className="bg-info text-info-foreground">
New feature available
</div>
<div className="bg-info-container text-info-container-foreground">
Tip: Use drag-and-drop to reorder
</div>
```
---
## Project Management Colors
### Work Item Status Colors
These colors are specifically designed for Scrum workflow states:
#### To Do (Purple - #6750a4 / #cfbdfe)
- Not started items
- Backlog items
- Planned work
```tsx
<span className="bg-status-todo text-white px-3 py-1 rounded-full">
To Do
</span>
```
#### In Progress (Blue - #0061a4 / #9fcbfa)
- Active development
- Current sprint items
- Work in flight
```tsx
<span className="bg-status-in-progress text-white px-3 py-1 rounded-full">
In Progress
</span>
```
#### Done (Green - #006e1c / #77db77)
- Completed items
- Accepted work
- Delivered features
```tsx
<span className="bg-status-done text-white px-3 py-1 rounded-full">
Done
</span>
```
#### Blocked (Red - #ba1a1a / #ffb4ab)
- Blocked items
- Impediments
- Issues preventing progress
```tsx
<span className="bg-status-blocked text-white px-3 py-1 rounded-full">
Blocked
</span>
```
### Priority Level Colors
#### Critical (Red - #ba1a1a / #ffb4ab)
- Production issues
- Show-stopper bugs
- Immediate action required
```tsx
<span className="bg-priority-critical text-white px-2 py-1 rounded text-sm">
Critical
</span>
```
#### High (Orange - #c75300 / #ffb68d)
- Important features
- Significant bugs
- Near-term priorities
```tsx
<span className="bg-priority-high text-white px-2 py-1 rounded text-sm">
High
</span>
```
#### Medium (Yellow - #735b00 / #efc047)
- Standard priority
- Regular backlog items
- Scheduled work
```tsx
<span className="bg-priority-medium text-white px-2 py-1 rounded text-sm">
Medium
</span>
```
#### Low (Teal - #006874 / #4fd8eb)
- Nice-to-have features
- Minor improvements
- Future considerations
```tsx
<span className="bg-priority-low text-white px-2 py-1 rounded text-sm">
Low
</span>
```
---
## Usage Examples
### Product Backlog Item Card
```tsx
<div className="bg-card text-card-foreground p-6 rounded-lg border border-border">
<div className="flex items-start justify-between mb-3">
<h3 className="text-lg">Implement OAuth2 Authentication</h3>
<span className="bg-priority-high text-white px-3 py-1 rounded-md text-sm">
High
</span>
</div>
<p className="text-muted-foreground mb-4">
Add OAuth2 support for Google and GitHub authentication providers
with proper session management and refresh token handling.
</p>
<div className="flex items-center justify-between">
<div className="flex gap-2">
<span className="bg-status-in-progress text-white px-3 py-1 rounded-full text-sm">
In Progress
</span>
<span className="text-muted-foreground text-sm">
Story Points: 8
</span>
</div>
<div className="text-sm text-muted-foreground">
Assigned to: Sarah
</div>
</div>
</div>
```
### Sprint Dashboard Card
```tsx
<div className="bg-surface-container-low p-6 rounded-xl">
<h2 className="mb-4">Sprint 12 Progress</h2>
<div className="bg-surface-container p-4 rounded-lg mb-4">
<div className="space-y-3">
<div className="flex justify-between items-center">
<span className="text-muted-foreground">Total Stories</span>
<span className="text-xl">15</span>
</div>
<div className="flex justify-between items-center">
<span className="text-muted-foreground">Completed</span>
<span className="text-xl text-success">10</span>
</div>
<div className="flex justify-between items-center">
<span className="text-muted-foreground">In Progress</span>
<span className="text-xl text-info">4</span>
</div>
<div className="flex justify-between items-center">
<span className="text-muted-foreground">Blocked</span>
<span className="text-xl text-error">1</span>
</div>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Progress</span>
<span>67%</span>
</div>
<div className="w-full bg-surface-container-highest rounded-full h-3">
<div
className="bg-success h-3 rounded-full transition-all"
style={{ width: '67%' }}
/>
</div>
</div>
</div>
```
### Kanban Board Column
```tsx
<div className="bg-surface-container p-4 rounded-lg min-w-80">
<div className="flex items-center justify-between mb-4">
<h3 className="flex items-center gap-2">
<span className="w-3 h-3 rounded-full bg-status-in-progress" />
In Progress
</h3>
<span className="bg-surface-container-high text-muted-foreground px-2 py-1 rounded text-sm">
4
</span>
</div>
<div className="space-y-3">
{/* Story card */}
<div className="bg-surface-container-low p-3 rounded-lg border border-border hover:border-primary cursor-pointer transition-colors">
<div className="flex items-start justify-between mb-2">
<h4 className="text-sm">Fix login redirect issue</h4>
<span className="bg-priority-critical text-white px-2 py-0.5 rounded text-xs">
Critical
</span>
</div>
<p className="text-xs text-muted-foreground mb-3">
Users are redirected to wrong page after login
</p>
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">SP: 3</span>
<div className="w-6 h-6 rounded-full bg-primary text-primary-foreground flex items-center justify-center text-xs">
JD
</div>
</div>
</div>
</div>
</div>
```
### Action Buttons
```tsx
{/* Primary action */}
<button className="px-6 py-3 bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity">
Start Sprint
</button>
{/* Secondary action */}
<button className="px-6 py-3 bg-secondary text-secondary-foreground rounded-lg hover:opacity-90 transition-opacity">
Save as Draft
</button>
{/* Outlined action */}
<button className="px-6 py-3 border-2 border-primary text-primary rounded-lg hover:bg-primary-container transition-colors">
View Backlog
</button>
{/* Tertiary/text action */}
<button className="px-6 py-3 text-primary hover:bg-primary-container rounded-lg transition-colors">
Cancel
</button>
{/* Destructive action */}
<button className="px-6 py-3 bg-error text-error-foreground rounded-lg hover:opacity-90 transition-opacity">
Delete Sprint
</button>
```
### Status Badges
```tsx
{/* Pill-shaped status badges */}
<div className="flex gap-2">
<span className="inline-flex items-center gap-1 px-3 py-1 bg-status-todo text-white rounded-full text-sm">
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 8 8">
<circle cx="4" cy="4" r="3" />
</svg>
To Do
</span>
<span className="inline-flex items-center gap-1 px-3 py-1 bg-status-in-progress text-white rounded-full text-sm">
<svg className="w-3 h-3 animate-spin" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
In Progress
</span>
<span className="inline-flex items-center gap-1 px-3 py-1 bg-status-done text-white rounded-full text-sm">
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</svg>
Done
</span>
<span className="inline-flex items-center gap-1 px-3 py-1 bg-status-blocked text-white rounded-full text-sm">
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M13.477 14.89A6 6 0 015.11 6.524l8.367 8.368zm1.414-1.414L6.524 5.11a6 6 0 018.367 8.367zM18 10a8 8 0 11-16 0 8 8 0 0116 0z" clipRule="evenodd" />
</svg>
Blocked
</span>
</div>
```
---
## Implementation Guide
### Setting Up the Color System
The color system is defined in `/src/styles/theme.css`. All colors use CSS custom properties (variables) for easy theming.
### Tailwind CSS Configuration
All color tokens are automatically available as Tailwind classes through the `@theme inline` directive:
```css
/* Colors are available as: */
bg-primary
text-primary-foreground
border-primary-container
bg-surface-container-low
bg-status-in-progress
bg-priority-high
/* etc. */
```
### Dark Mode Toggle
Toggle dark mode with JavaScript:
```tsx
// Toggle dark mode
document.documentElement.classList.toggle('dark');
// Set dark mode
document.documentElement.classList.add('dark');
// Set light mode
document.documentElement.classList.remove('dark');
// React component example
function DarkModeToggle() {
const [isDark, setIsDark] = useState(false);
const toggleDark = () => {
setIsDark(!isDark);
document.documentElement.classList.toggle('dark');
};
return (
<button onClick={toggleDark}>
{isDark ? '☀️ Light Mode' : '🌙 Dark Mode'}
</button>
);
}
```
### Using with shadcn Components
All shadcn/ui components work seamlessly with this color system. The legacy color tokens (card, popover, muted, accent, destructive) are mapped to appropriate MD3 colors.
```tsx
// shadcn Button component automatically uses the color system
<Button>Primary Action</Button>
<Button variant="secondary">Secondary Action</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline">Outlined</Button>
```
---
## Accessibility
### Contrast Ratios
All color combinations meet **WCAG AA** standards:
- **Normal text**: Minimum 4.5:1 contrast ratio
- **Large text**: Minimum 3:1 contrast ratio
- **UI components**: Minimum 3:1 contrast ratio
### Color Blindness Considerations
The color system is designed to be distinguishable for users with common types of color blindness:
1. **Status colors** use distinct hues that remain distinguishable in deuteranopia, protanopia, and tritanopia
2. **Priority levels** use a progression from cool (low) to warm (high) colors
3. **All status indicators** should include text labels, not just color
### Best Practices for Accessibility
```tsx
// ✅ Good: Color + Icon + Text
<span className="bg-status-done text-white px-3 py-1 rounded-full">
<CheckIcon className="w-4 h-4 inline" />
Done
</span>
// ❌ Bad: Color only
<span className="w-4 h-4 bg-status-done rounded-full" />
// ✅ Good: Semantic HTML + ARIA
<button
className="bg-primary text-primary-foreground"
aria-label="Start new sprint"
>
Start Sprint
</button>
// ✅ Good: Status with aria-label
<div
className="bg-status-blocked"
role="status"
aria-label="This item is blocked"
>
Blocked
</div>
```
---
## Best Practices
### 1. Use Surface Elevation for Depth
❌ **Don't use shadows for elevation**
```tsx
<div className="shadow-lg">Card</div>
```
✅ **Do use surface tones**
```tsx
<div className="bg-surface-container-low">Card</div>
```
### 2. Use Container Variants for Lower Emphasis
❌ **Don't reduce opacity**
```tsx
<button className="bg-primary opacity-50">Secondary Action</button>
```
✅ **Do use container variants**
```tsx
<button className="bg-primary-container text-primary-container-foreground">
Secondary Action
</button>
```
### 3. Consistent Status Colors
❌ **Don't use arbitrary colors**
```tsx
<span className="bg-green-500">Complete</span>
<span className="bg-emerald-600">Finished</span>
```
✅ **Do use semantic status colors**
```tsx
<span className="bg-status-done">Complete</span>
<span className="bg-status-done">Finished</span>
```
### 4. Semantic Color Usage
❌ **Don't use colors for decoration only**
```tsx
<div className="bg-error">This is important information</div>
```
✅ **Do use colors for their semantic meaning**
```tsx
<div className="bg-error text-error-foreground">
Error: Failed to save changes
</div>
<div className="bg-info text-info-foreground">
This is important information
</div>
```
### 5. Proper Text Contrast
❌ **Don't forget foreground colors**
```tsx
<button className="bg-primary">Click me</button>
```
✅ **Do include foreground colors**
```tsx
<button className="bg-primary text-primary-foreground">Click me</button>
```
### 6. Layering and Nesting
When nesting containers, move down the elevation scale:
```tsx
<div className="bg-surface-container-low p-6">
{/* Outer container */}
<div className="bg-surface-container p-4">
{/* Inner container - lower elevation */}
<div className="bg-surface-container-high p-3">
{/* Deepest container - lowest elevation */}
</div>
</div>
</div>
```
### 7. Interactive States
```tsx
// Buttons with hover states
<button className="bg-primary text-primary-foreground hover:opacity-90 transition-opacity">
Click me
</button>
// Cards with hover states
<div className="bg-card border border-border hover:border-primary transition-colors cursor-pointer">
Interactive card
</div>
// Focus states (automatically handled by theme)
<input className="border border-border focus:ring-2 focus:ring-primary" />
```
---
## Quick Reference
### Most Common Combinations
```tsx
// Primary button
bg-primary text-primary-foreground
// Secondary button
bg-secondary text-secondary-foreground
// Card
bg-card text-card-foreground border border-border
// Success message
bg-success-container text-success-container-foreground
// Warning banner
bg-warning text-warning-foreground
// Error alert
bg-error text-error-foreground
// Info panel
bg-info-container text-info-container-foreground
// Status badge - To Do
bg-status-todo text-white
// Status badge - In Progress
bg-status-in-progress text-white
// Status badge - Done
bg-status-done text-white
// Priority badge - High
bg-priority-high text-white
// Muted text
text-muted-foreground
// Border
border-border
```
---
## Additional Resources
### Color Tools
- **Material Theme Builder**: https://m3.material.io/theme-builder
- **Contrast Checker**: https://webaim.org/resources/contrastchecker/
- **Color Blind Simulator**: https://www.color-blindness.com/coblis-color-blindness-simulator/
### Documentation
- **Material Design 3**: https://m3.material.io
- **Tailwind CSS**: https://tailwindcss.com
- **shadcn/ui**: https://ui.shadcn.com
---
## Version History
### Version 1.0 (April 22, 2026)
- Initial color scheme based on Material Design 3
- Complete light and dark theme support
- Project management specific colors (status, priority)
- Full shadcn/ui compatibility
- Accessibility WCAG AA compliant
---
## Support
For questions or issues with the color system:
- Review the live demo in the application
- Check `COLOR_SYSTEM.md` for additional examples
- Refer to `/src/styles/theme.css` for color definitions
---
**End of Documentation**

384
docs/icons.html Normal file
View file

@ -0,0 +1,384 @@
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Scrum4Me — Icoon Concepten</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Syne:wght@700;800&display=swap');
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0f1117;
--surface: #181c27;
--border: #252a38;
--accent: #4f6ef7;
--accent2: #7c3aed;
--accent3: #06b6d4;
--text: #e8eaf0;
--muted: #6b7280;
}
body {
background: var(--bg);
color: var(--text);
font-family: 'DM Mono', monospace;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 24px;
gap: 64px;
}
header {
text-align: center;
}
header h1 {
font-family: 'Syne', sans-serif;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--muted);
}
header p {
font-size: 11px;
color: var(--muted);
margin-top: 8px;
letter-spacing: 0.05em;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 40px;
max-width: 900px;
width: 100%;
}
.concept {
display: flex;
flex-direction: column;
align-items: center;
gap: 32px;
}
.label {
font-size: 11px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--muted);
text-align: center;
}
.label strong {
display: block;
font-family: 'Syne', sans-serif;
font-size: 13px;
color: var(--text);
letter-spacing: 0.05em;
margin-bottom: 4px;
}
/* Icon containers — three sizes */
.sizes {
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
}
.icon-wrap {
display: flex;
align-items: center;
justify-content: center;
border-radius: 22%;
background: var(--surface);
border: 1px solid var(--border);
flex-shrink: 0;
transition: transform 0.2s ease, box-shadow 0.2s ease;
cursor: default;
}
.icon-wrap:hover {
transform: translateY(-3px);
box-shadow: 0 12px 40px rgba(79, 110, 247, 0.2);
}
.icon-wrap.xl { width: 120px; height: 120px; border-radius: 26px; }
.icon-wrap.md { width: 64px; height: 64px; border-radius: 14px; }
.icon-wrap.sm { width: 32px; height: 32px; border-radius: 7px; }
.size-row {
display: flex;
align-items: center;
gap: 16px;
}
/* =====================
CONCEPT 2 — S4M Lettermerk
===================== */
.s4m-xl { background: linear-gradient(135deg, #1a1f35 0%, #0f1117 100%); }
/* =====================
CONCEPT 3 — Iteratielus
===================== */
.loop-xl { background: linear-gradient(135deg, #0d1f2d 0%, #0f1117 100%); }
/* =====================
CONCEPT 5 — Raket
===================== */
.rocket-xl { background: linear-gradient(135deg, #1a1028 0%, #0f1117 100%); }
/* Divider */
.divider {
width: 1px;
height: 40px;
background: var(--border);
}
footer {
font-size: 11px;
color: var(--muted);
letter-spacing: 0.1em;
}
</style>
</head>
<body>
<header>
<h1>Scrum4Me — Icoon Concepten</h1>
<p>2 · S4M Lettermerk &nbsp;·&nbsp; 3 · Iteratielus &nbsp;·&nbsp; 5 · Raket</p>
</header>
<div class="grid">
<!-- ======================== CONCEPT 2: S4M LETTERMERK ======================== -->
<div class="concept">
<div class="sizes">
<!-- XL -->
<div class="icon-wrap xl s4m-xl">
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g2a" x1="0" y1="0" x2="80" y2="80" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#7c9fff"/>
<stop offset="100%" stop-color="#4f6ef7"/>
</linearGradient>
</defs>
<!-- S -->
<path d="M14 26C14 22 17 19 22 19H32C36 19 39 21.5 39 25.5C39 29 36.5 31 33 32L22 35C18 36.5 15 39 15 43.5C15 48 18.5 51 23 51H34C38.5 51 42 48 42 44"
stroke="url(#g2a)" stroke-width="4.5" stroke-linecap="round" fill="none"/>
<!-- 4 -->
<path d="M52 19L44 37H62" stroke="url(#g2a)" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<line x1="55" y1="30" x2="55" y2="51" stroke="url(#g2a)" stroke-width="4.5" stroke-linecap="round"/>
<!-- M -->
<path d="M14 59L14 73M14 59L21 68L28 59M28 59L28 73" stroke="url(#g2a)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<!-- dot accent -->
<circle cx="65" cy="61" r="4" fill="#4f6ef7" opacity="0.6"/>
<circle cx="65" cy="61" r="2" fill="#7c9fff"/>
</svg>
</div>
<!-- MD + SM -->
<div class="size-row">
<div class="icon-wrap md s4m-xl">
<svg width="42" height="42" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g2b" x1="0" y1="0" x2="80" y2="80" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#7c9fff"/>
<stop offset="100%" stop-color="#4f6ef7"/>
</linearGradient>
</defs>
<path d="M14 26C14 22 17 19 22 19H32C36 19 39 21.5 39 25.5C39 29 36.5 31 33 32L22 35C18 36.5 15 39 15 43.5C15 48 18.5 51 23 51H34C38.5 51 42 48 42 44"
stroke="url(#g2b)" stroke-width="4.5" stroke-linecap="round" fill="none"/>
<path d="M52 19L44 37H62" stroke="url(#g2b)" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<line x1="55" y1="30" x2="55" y2="51" stroke="url(#g2b)" stroke-width="4.5" stroke-linecap="round"/>
<path d="M14 59L14 73M14 59L21 68L28 59M28 59L28 73" stroke="url(#g2b)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<circle cx="65" cy="61" r="4" fill="#4f6ef7" opacity="0.6"/>
<circle cx="65" cy="61" r="2" fill="#7c9fff"/>
</svg>
</div>
<div class="icon-wrap sm s4m-xl">
<svg width="20" height="20" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 26C14 22 17 19 22 19H32C36 19 39 21.5 39 25.5C39 29 36.5 31 33 32L22 35C18 36.5 15 39 15 43.5C15 48 18.5 51 23 51H34C38.5 51 42 48 42 44"
stroke="#4f6ef7" stroke-width="5" stroke-linecap="round" fill="none"/>
<path d="M52 19L44 37H62" stroke="#4f6ef7" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<line x1="55" y1="30" x2="55" y2="51" stroke="#4f6ef7" stroke-width="5" stroke-linecap="round"/>
</svg>
</div>
</div>
</div>
<div class="label">
<strong>02 — S4M Lettermerk</strong>
S · 4 · M als geïntegreerd monogram<br>accent-dot als leesteken
</div>
</div>
<!-- ======================== CONCEPT 3: ITERATIELUS ======================== -->
<div class="concept">
<div class="sizes">
<!-- XL -->
<div class="icon-wrap xl loop-xl">
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g3a" x1="0" y1="40" x2="80" y2="40" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#06b6d4"/>
<stop offset="60%" stop-color="#4f6ef7"/>
<stop offset="100%" stop-color="#7c3aed"/>
</linearGradient>
</defs>
<!-- Open arc — 300 degrees, gap at top-right -->
<path d="M40 12
A28 28 0 1 1 62.2 26"
stroke="url(#g3a)"
stroke-width="6"
stroke-linecap="round"
fill="none"/>
<!-- Arrowhead at the open end -->
<path d="M62.2 26 L74 20 M62.2 26 L68 38"
stroke="#7c3aed"
stroke-width="5.5"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"/>
<!-- Centre dot -->
<circle cx="40" cy="40" r="5" fill="url(#g3a)" opacity="0.5"/>
<circle cx="40" cy="40" r="2.5" fill="#06b6d4"/>
</svg>
</div>
<!-- MD + SM -->
<div class="size-row">
<div class="icon-wrap md loop-xl">
<svg width="42" height="42" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g3b" x1="0" y1="40" x2="80" y2="40" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#06b6d4"/>
<stop offset="60%" stop-color="#4f6ef7"/>
<stop offset="100%" stop-color="#7c3aed"/>
</linearGradient>
</defs>
<path d="M40 12 A28 28 0 1 1 62.2 26" stroke="url(#g3b)" stroke-width="6" stroke-linecap="round" fill="none"/>
<path d="M62.2 26 L74 20 M62.2 26 L68 38" stroke="#7c3aed" stroke-width="5.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<circle cx="40" cy="40" r="5" fill="url(#g3b)" opacity="0.5"/>
<circle cx="40" cy="40" r="2.5" fill="#06b6d4"/>
</svg>
</div>
<div class="icon-wrap sm loop-xl">
<svg width="20" height="20" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M40 12 A28 28 0 1 1 62.2 26" stroke="#06b6d4" stroke-width="7" stroke-linecap="round" fill="none"/>
<path d="M62.2 26 L74 20 M62.2 26 L68 38" stroke="#7c3aed" stroke-width="6.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>
</div>
</div>
</div>
<div class="label">
<strong>03 — Iteratielus</strong>
Open boog · gradient cyan→indigo→violet<br>pijlpunt markeert de opening
</div>
</div>
<!-- ======================== CONCEPT 5: RAKET ======================== -->
<div class="concept">
<div class="sizes">
<!-- XL -->
<div class="icon-wrap xl rocket-xl">
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g5a" x1="40" y1="8" x2="40" y2="72" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#a78bfa"/>
<stop offset="100%" stop-color="#7c3aed"/>
</linearGradient>
<linearGradient id="g5b" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#4f6ef7" stop-opacity="0.6"/>
<stop offset="100%" stop-color="#7c3aed" stop-opacity="0.6"/>
</linearGradient>
</defs>
<!-- Block 1 (PBI) — bottom -->
<rect x="28" y="58" width="24" height="10" rx="3" fill="#4f6ef7" opacity="0.5"/>
<rect x="28" y="58" width="24" height="10" rx="3" stroke="#4f6ef7" stroke-width="1" fill="none"/>
<!-- Block 2 (Story) — middle -->
<rect x="22" y="44" width="36" height="12" rx="3" fill="#6366f1" opacity="0.65"/>
<rect x="22" y="44" width="36" height="12" rx="3" stroke="#818cf8" stroke-width="1" fill="none"/>
<!-- Block 3 (Task) — upper body -->
<rect x="26" y="30" width="28" height="12" rx="3" fill="#7c3aed" opacity="0.8"/>
<rect x="26" y="30" width="28" height="12" rx="3" stroke="#a78bfa" stroke-width="1" fill="none"/>
<!-- Rocket nose -->
<path d="M40 8 C34 8 26 18 26 30 H54 C54 18 46 8 40 8Z"
fill="url(#g5a)" opacity="0.9"/>
<!-- Window -->
<circle cx="40" cy="22" r="5" fill="#0f1117" opacity="0.6"/>
<circle cx="40" cy="22" r="3" fill="#e0e7ff" opacity="0.9"/>
<!-- Fins -->
<path d="M26 52 L18 62 L26 62 Z" fill="#4f6ef7" opacity="0.5"/>
<path d="M54 52 L62 62 L54 62 Z" fill="#4f6ef7" opacity="0.5"/>
<!-- Exhaust flame -->
<path d="M33 68 Q37 76 40 72 Q43 76 47 68" stroke="#f59e0b" stroke-width="2.5" stroke-linecap="round" fill="none" opacity="0.8"/>
<path d="M36 68 Q40 73 44 68" stroke="#fbbf24" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.6"/>
</svg>
</div>
<!-- MD + SM -->
<div class="size-row">
<div class="icon-wrap md rocket-xl">
<svg width="42" height="42" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g5c" x1="40" y1="8" x2="40" y2="60" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#a78bfa"/>
<stop offset="100%" stop-color="#7c3aed"/>
</linearGradient>
</defs>
<rect x="28" y="58" width="24" height="10" rx="3" fill="#4f6ef7" opacity="0.5"/>
<rect x="22" y="44" width="36" height="12" rx="3" fill="#6366f1" opacity="0.65"/>
<rect x="26" y="30" width="28" height="12" rx="3" fill="#7c3aed" opacity="0.8"/>
<path d="M40 8 C34 8 26 18 26 30 H54 C54 18 46 8 40 8Z" fill="url(#g5c)" opacity="0.9"/>
<circle cx="40" cy="22" r="5" fill="#0f1117" opacity="0.6"/>
<circle cx="40" cy="22" r="3" fill="#e0e7ff" opacity="0.9"/>
<path d="M26 52 L18 62 L26 62 Z" fill="#4f6ef7" opacity="0.5"/>
<path d="M54 52 L62 62 L54 62 Z" fill="#4f6ef7" opacity="0.5"/>
<path d="M33 68 Q37 76 40 72 Q43 76 47 68" stroke="#f59e0b" stroke-width="2.5" stroke-linecap="round" fill="none" opacity="0.8"/>
</svg>
</div>
<div class="icon-wrap sm rocket-xl">
<svg width="20" height="20" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="28" y="56" width="24" height="12" rx="3" fill="#4f6ef7" opacity="0.6"/>
<rect x="22" y="40" width="36" height="14" rx="3" fill="#6366f1" opacity="0.75"/>
<rect x="26" y="24" width="28" height="14" rx="3" fill="#7c3aed" opacity="0.9"/>
<path d="M40 6 C34 6 26 16 26 24 H54 C54 16 46 6 40 6Z" fill="#a78bfa"/>
</svg>
</div>
</div>
</div>
<div class="label">
<strong>05 — De Raket</strong>
3 blokken (PBI · story · taak) die opstijgen<br>neus · vinnen · uitlaatpit
</div>
</div>
</div>
<footer>Scrum4Me · Icoon Concepten v0.1 · april 2026</footer>
</body>
</html>

View file

@ -0,0 +1,726 @@
# Scrum4Me — Technische Architectuur
**Versie:** 0.1 — april 2026
**Volgt op:** Functionele Specificatie v0.2
---
## Architectuursamenvatting
Scrum4Me is een desktop-first Next.js 15 webapplicatie die server-side wordt gerenderd en gedeployed op Vercel. De database is PostgreSQL via Neon (cloud) of SQLite (lokaal), aangestuurd via Prisma v7. Authenticatie is custom username/password via iron-session — geen externe auth-provider, geen e-mail. De REST API voor Claude Code-integratie loopt via Next.js Route Handlers, beveiligd met API-tokens. Drag-and-drop in de planningsschermen wordt afgehandeld door dnd-kit. De volledige app draait ook lokaal zonder externe accounts.
---
## Stack
| Laag | Technologie | Rationale |
|---|---|---|
| Frontend framework | Next.js 15 (App Router) | Stabiel, wijdverbreid, naadloze Vercel-deployment; SSR vereist voor auth-cookie-management |
| UI runtime | React 19 | Standaard bij Next.js 15; brengt `useActionState`, `useFormStatus` en de React Compiler (experimenteel) mee — minder boilerplate bij Server Actions |
| Taal | TypeScript (strict) | Type-veiligheid is essentieel voor een solo developer zonder reviewlaag; vangt datamodel-mismatches vroeg |
| Client state | Zustand | Minimale boilerplate voor ephemere UI-staat (selectie, optimistische drag-and-drop volgorde); leeft naast Server Components zonder conflict; eenvoudig uitbreidbaar naar v2 teamgebruik |
| Styling | Tailwind CSS + shadcn/ui | Snelle iteratie; toegankelijke componentprimitieven; desktop-first layouts goed ondersteund |
| Database (cloud) | PostgreSQL via Neon | Serverless Postgres, gratis tier voldoende voor MVP; native PostgreSQL zonder vendor lock-in |
| Database (lokaal) | SQLite via Prisma SQLite-adapter | Geen externe accounts nodig voor lokale ontwikkeling; `prisma db push` initialiseert schema |
| ORM | Prisma v7 | Type-safe queries; ondersteunt zowel PostgreSQL als SQLite via adapter; migraties zijn deterministisch |
| Authenticatie | Custom — iron-session + bcrypt | Username/password zonder e-mail vereist geen externe auth-provider; iron-session beheert versleutelde cookies server-side |
| Drag-and-drop | dnd-kit | Actief onderhouden, React-native hooks, 60fps bij grote lijsten, ondersteuning voor meerdere containers |
| REST API | Next.js Route Handlers (`/app/api/`) | Naast Server Actions nodig voor Claude Code-integratie; Route Handlers zijn volledig HTTP-compatibel |
| Hosting | Vercel | Zero-config Next.js deployment; preview-URLs per PR; gratis tier voldoende voor v1 |
| CI/CD | GitHub Actions | Lint + typecheck + build op elke PR; Vercel handelt de daadwerkelijke deploy af |
---
## Wat we NIET gebruiken (en waarom)
| Technologie | Afgewezen omdat |
|---|---|
| Supabase Auth | Username/password zonder e-mail past niet in Supabase Auth's flow; onnodige afhankelijkheid voor wat iron-session zelf afhandelt |
| NextAuth / Auth.js | Overkill voor username/password zonder providers; voegt complexiteit toe zonder voordeel bij deze auth-vereisten |
| Redux Toolkit | Te veel boilerplate (actions, reducers, slices, selectors, provider) voor deze schaal; Zustand doet hetzelfde met een kwart van de code |
| Jotai / Recoil | Atom-gebaseerd model is te granulaar voor de gecorreleerde state in de gesplitste schermen; Zustand stores zijn explicieter en beter uitbreidbaar |
| React Query / SWR | Server Components + Server Actions dekken de datalaag; client-side server-state caching introduceert een sync-probleem dat we bewust vermijden |
| Context API (React) | Veroorzaakt onnodige re-renders bij drag-and-drop updates; Zustand's selector-gebaseerde subscriptions zijn granulairder |
| WebSockets / real-time | Geen real-time vereisten in v1; polling of page-refresh volstaat |
| Redis | Geen caching- of queuerequirements op deze schaal |
| Docker (lokale dev) | Prisma + SQLite maakt lokale setup trivial zonder containers |
| Supabase (als database) | Neon geeft directe PostgreSQL-toegang zonder Supabase-specifieke abstractielagen; past beter bij Prisma-first aanpak |
| tRPC | REST API is vereist voor Claude Code-integratie; tRPC werkt alleen vanuit TypeScript-clients |
---
## Datamodel
### `users`
| Kolom | Type | Constraints | Noten |
|---|---|---|---|
| id | String (cuid) | PK | Gegenereerd door Prisma |
| username | String | unique, not null, min 3 | Inlognaam |
| password_hash | String | not null | bcrypt hash (cost factor 12) |
| is_demo | Boolean | default false | Demo-gebruiker heeft read-only rechten |
| created_at | DateTime | default now() | |
| updated_at | DateTime | auto-update | |
**Indexes:** `username` (unique lookup bij inloggen)
---
### `user_roles`
| Kolom | Type | Constraints | Noten |
|---|---|---|---|
| id | String (cuid) | PK | |
| user_id | String | FK → users, not null | |
| role | Enum | PRODUCT_OWNER \| SCRUM_MASTER \| DEVELOPER | |
**Indexes:** `(user_id)` — meerdere rollen per gebruiker
**Constraint:** unique `(user_id, role)`
---
### `api_tokens`
| Kolom | Type | Constraints | Noten |
|---|---|---|---|
| id | String (cuid) | PK | |
| user_id | String | FK → users, not null | |
| token_hash | String | not null | SHA-256 hash van het token |
| label | String | nullable | Bijv. "Claude Code — laptop" |
| created_at | DateTime | default now() | |
| revoked_at | DateTime | nullable | Null = actief |
**Indexes:** `token_hash` (lookup bij elke API-aanroep — moet snel zijn)
---
### `products`
| Kolom | Type | Constraints | Noten |
|---|---|---|---|
| id | String (cuid) | PK | |
| user_id | String | FK → users, not null | |
| name | String | not null, max 200 | Uniek per gebruiker |
| description | String | nullable, max 1000 | |
| repo_url | String | nullable | Gevalideerde URL |
| definition_of_done | String | not null, max 500 | Vaste instelling per product |
| archived | Boolean | default false | |
| created_at | DateTime | default now() | |
| updated_at | DateTime | auto-update | |
**Indexes:** `(user_id, archived)` — standaard query filtert op actieve producten
**Constraint:** unique `(user_id, name)`
---
### `pbis`
| Kolom | Type | Constraints | Noten |
|---|---|---|---|
| id | String (cuid) | PK | |
| product_id | String | FK → products (cascade delete) | |
| title | String | not null, max 200 | |
| description | String | nullable, max 2000 | |
| priority | Int | 14, not null | 1 = Kritiek, 4 = Laag |
| sort_order | Float | not null | Float voor volgorde tussen items zonder renummering |
| created_at | DateTime | default now() | |
| updated_at | DateTime | auto-update | |
**Indexes:** `(product_id, priority, sort_order)` — standaard query voor het gesplitste scherm
---
### `stories`
| Kolom | Type | Constraints | Noten |
|---|---|---|---|
| id | String (cuid) | PK | |
| pbi_id | String | FK → pbis (cascade delete) | |
| product_id | String | FK → products | Denormalisatie voor snellere queries |
| sprint_id | String | FK → sprints, nullable | Null = in Product Backlog |
| title | String | not null, max 200 | |
| description | String | nullable, max 2000 | |
| acceptance_criteria | String | nullable, max 2000 | |
| priority | Int | 14, not null | |
| sort_order | Float | not null | |
| status | Enum | OPEN \| IN_SPRINT \| DONE | |
| created_at | DateTime | default now() | |
| updated_at | DateTime | auto-update | |
**Indexes:** `(pbi_id, priority, sort_order)`, `(sprint_id, sort_order)`, `(product_id, status)`
---
### `story_logs`
| Kolom | Type | Constraints | Noten |
|---|---|---|---|
| id | String (cuid) | PK | |
| story_id | String | FK → stories (cascade delete) | |
| type | Enum | IMPLEMENTATION_PLAN \| TEST_RESULT \| COMMIT | |
| content | String | not null | Tekst van plan of testuitvoer |
| status | Enum | PASSED \| FAILED, nullable | Alleen bij type TEST_RESULT |
| commit_hash | String | nullable | Alleen bij type COMMIT |
| commit_message | String | nullable | Alleen bij type COMMIT |
| created_at | DateTime | default now() | |
**Indexes:** `(story_id, created_at)` — chronologische weergave in de UI
---
### `sprints`
| Kolom | Type | Constraints | Noten |
|---|---|---|---|
| id | String (cuid) | PK | |
| product_id | String | FK → products (cascade delete) | |
| sprint_goal | String | not null, max 500 | |
| status | Enum | ACTIVE \| COMPLETED | |
| created_at | DateTime | default now() | |
| completed_at | DateTime | nullable | |
**Indexes:** `(product_id, status)` — query voor actieve Sprint per product
**Constraint:** Max. 1 actieve Sprint per product (gehandhaafd in applicatielaag)
---
### `tasks`
| Kolom | Type | Constraints | Noten |
|---|---|---|---|
| id | String (cuid) | PK | |
| story_id | String | FK → stories (cascade delete) | |
| sprint_id | String | FK → sprints, nullable | Denormalisatie voor snellere queries |
| title | String | not null, max 200 | |
| description | String | nullable, max 1000 | |
| priority | Int | 14, not null | |
| sort_order | Float | not null | |
| status | Enum | TO_DO \| IN_PROGRESS \| DONE | |
| created_at | DateTime | default now() | |
| updated_at | DateTime | auto-update | |
**Indexes:** `(story_id, priority, sort_order)`, `(sprint_id, status)`
---
### `todos`
| Kolom | Type | Constraints | Noten |
|---|---|---|---|
| id | String (cuid) | PK | |
| user_id | String | FK → users, not null | |
| title | String | not null | |
| done | Boolean | default false | |
| archived | Boolean | default false | |
| created_at | DateTime | default now() | |
| updated_at | DateTime | auto-update | |
**Indexes:** `(user_id, done, archived)` — standaard weergave filtert op actieve todo's
---
## Prisma Schema (excerpt)
```prisma
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
// Database wordt bepaald via prisma.config.ts — niet hier
enum Role {
PRODUCT_OWNER
SCRUM_MASTER
DEVELOPER
}
enum StoryStatus {
OPEN
IN_SPRINT
DONE
}
enum TaskStatus {
TO_DO
IN_PROGRESS
DONE
}
enum LogType {
IMPLEMENTATION_PLAN
TEST_RESULT
COMMIT
}
enum TestStatus {
PASSED
FAILED
}
enum SprintStatus {
ACTIVE
COMPLETED
}
model User {
id String @id @default(cuid())
username String @unique
password_hash String
is_demo Boolean @default(false)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
roles UserRole[]
api_tokens ApiToken[]
products Product[]
todos Todo[]
}
model UserRole {
id String @id @default(cuid())
user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
user_id String
role Role
@@unique([user_id, role])
}
model ApiToken {
id String @id @default(cuid())
user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
user_id String
token_hash String @unique
label String?
created_at DateTime @default(now())
revoked_at DateTime?
@@index([token_hash])
}
model Product {
id String @id @default(cuid())
user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
user_id String
name String
description String?
repo_url String?
definition_of_done String
archived Boolean @default(false)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
pbis Pbi[]
sprints Sprint[]
stories Story[]
@@unique([user_id, name])
@@index([user_id, archived])
}
model Pbi {
id String @id @default(cuid())
product Product @relation(fields: [product_id], references: [id], onDelete: Cascade)
product_id String
title String
description String?
priority Int
sort_order Float
created_at DateTime @default(now())
updated_at DateTime @updatedAt
stories Story[]
@@index([product_id, priority, sort_order])
}
model Story {
id String @id @default(cuid())
pbi Pbi @relation(fields: [pbi_id], references: [id], onDelete: Cascade)
pbi_id String
product Product @relation(fields: [product_id], references: [id])
product_id String
sprint Sprint? @relation(fields: [sprint_id], references: [id])
sprint_id String?
title String
description String?
acceptance_criteria String?
priority Int
sort_order Float
status StoryStatus @default(OPEN)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
logs StoryLog[]
tasks Task[]
@@index([pbi_id, priority, sort_order])
@@index([sprint_id, sort_order])
@@index([product_id, status])
}
model StoryLog {
id String @id @default(cuid())
story Story @relation(fields: [story_id], references: [id], onDelete: Cascade)
story_id String
type LogType
content String
status TestStatus?
commit_hash String?
commit_message String?
created_at DateTime @default(now())
@@index([story_id, created_at])
}
model Sprint {
id String @id @default(cuid())
product Product @relation(fields: [product_id], references: [id], onDelete: Cascade)
product_id String
sprint_goal String
status SprintStatus @default(ACTIVE)
created_at DateTime @default(now())
completed_at DateTime?
stories Story[]
tasks Task[]
@@index([product_id, status])
}
model Task {
id String @id @default(cuid())
story Story @relation(fields: [story_id], references: [id], onDelete: Cascade)
story_id String
sprint Sprint? @relation(fields: [sprint_id], references: [id])
sprint_id String?
title String
description String?
priority Int
sort_order Float
status TaskStatus @default(TO_DO)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
@@index([story_id, priority, sort_order])
@@index([sprint_id, status])
}
model Todo {
id String @id @default(cuid())
user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
user_id String
title String
done Boolean @default(false)
archived Boolean @default(false)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
@@index([user_id, done, archived])
}
```
---
## Authenticatieflow
```
Registratie:
POST /register → valideer username/wachtwoord → bcrypt hash → opslaan in DB
→ iron-session cookie zetten → redirect /dashboard
Inloggen:
POST /login → gebruiker ophalen op username → bcrypt vergelijken
→ bij match: iron-session cookie zetten → redirect /dashboard
→ bij mismatch: generieke foutmelding (geen onderscheid)
Sessie per request:
middleware.ts → iron-session cookie uitlezen → user_id + is_demo in request
→ beschermde routes: redirect /login als geen geldige sessie
API-aanroepen (Claude Code):
Authorization: Bearer <token> header → SHA-256 hash → opzoeken in api_tokens
→ revoked_at null check → user_id ophalen → is_demo check voor schrijfrechten
Uitloggen:
Server Action → iron-session vernietigen → redirect /login
```
---
## Projectstructuur
```
scrum4me/
├── app/
│ ├── (auth)/
│ │ ├── login/page.tsx
│ │ └── register/page.tsx
│ ├── (app)/ # Beschermde routes
│ │ ├── layout.tsx # Auth-check + navigatie
│ │ ├── dashboard/page.tsx # Productenlijst
│ │ ├── products/
│ │ │ ├── new/page.tsx
│ │ │ └── [id]/
│ │ │ ├── page.tsx # Product Backlog (gesplitst scherm)
│ │ │ ├── sprint/
│ │ │ │ ├── page.tsx # Sprint Backlog (gesplitst scherm)
│ │ │ │ └── planning/page.tsx # Sprint Planning (gesplitst scherm)
│ │ ├── todos/page.tsx
│ │ └── settings/
│ │ ├── page.tsx
│ │ └── tokens/page.tsx
│ └── api/ # REST API voor Claude Code
│ ├── products/
│ │ └── [id]/
│ │ └── next-story/route.ts
│ ├── sprints/
│ │ └── [id]/
│ │ └── tasks/route.ts
│ ├── stories/
│ │ └── [id]/
│ │ ├── log/route.ts
│ │ └── tasks/reorder/route.ts
│ ├── tasks/
│ │ └── [id]/route.ts
│ └── todos/route.ts
├── components/
│ ├── ui/ # shadcn/ui primitieven
│ ├── split-pane/ # Gesplitst scherm component
│ ├── backlog/ # PBI- en story-componenten
│ ├── sprint/ # Sprint-componenten
│ └── dnd/ # dnd-kit wrappers
├── lib/
│ ├── prisma.ts # Prisma Client singleton
│ ├── session.ts # iron-session configuratie
│ ├── auth.ts # login/register/token helpers
│ ├── api-auth.ts # Bearer token middleware voor API
│ └── env.ts # Zod-gevalideerde env vars
├── stores/ # Zustand stores
│ ├── planner-store.ts # Optimistische drag-and-drop volgorde
│ ├── selection-store.ts # Geselecteerd PBI / story
│ └── sprint-store.ts # Sprint Backlog interacties
│ ├── products.ts
│ ├── pbis.ts
│ ├── stories.ts
│ ├── sprints.ts
│ ├── tasks.ts
│ └── todos.ts
├── prisma/
│ ├── schema.prisma
│ ├── migrations/
│ └── seed.ts # Testdata uit Product Backlog document
├── middleware.ts # Sessiecheck op beschermde routes
├── prisma.config.ts # Prisma v7 config (DATABASE_URL)
└── .env.example
```
---
## Sleutelarchitectuurbeslissingen
### Beslissing: iron-session in plaats van Auth.js / Supabase Auth
**Keuze:** iron-session voor versleutelde server-side sessiecookies
**Rationale:** Scrum4Me gebruikt username/wachtwoord zonder e-mail — een flow die Auth.js/NextAuth met Credentials Provider ondersteunt, maar met onnodige complexiteit (JWT-callbacks, adapter-configuratie). iron-session is minimaal: sla een gesigneerde, versleutelde cookie op met `{ userId, isDemo }` en klaar. Geen externe afhankelijkheid, geen database-adapter voor sessies.
**Trade-off:** Geen ingebouwde OAuth of magic links. Dat is bewust — v1 heeft die niet nodig.
### Beslissing: Route Handlers naast Server Actions
**Keuze:** Server Actions voor UI-mutaties; Route Handlers voor de Claude Code REST API
**Rationale:** Server Actions zijn ideaal voor form-submits en UI-interacties (CSRF-bescherming, progressive enhancement). Maar Claude Code heeft echte HTTP-endpoints nodig — Bearer token, JSON body, programmatisch aanroepbaar. Die twee aanpakken leven naast elkaar zonder conflict.
**Trade-off:** Duplicatie in validatie-logica. Opgelost door gedeelde service-functies in `lib/` die beide aanroepen.
### Beslissing: Float voor sort_order
**Keuze:** `Float` in plaats van `Int` voor volgorde van PBI's, stories en taken
**Rationale:** Bij drag-and-drop tussenvoeging kan de nieuwe positie worden berekend als het gemiddelde van de buurwaarden (bijv. `(1.0 + 2.0) / 2 = 1.5`). Hierdoor is nooit een herindexering van alle items nodig. Herindexering is alleen nodig als de float-precisie opraakt (in de praktijk na duizenden bewegingen).
**Trade-off:** Kleine kans op precisieverlies bij extreme fragmentatie. Opgelost door periodieke herindexering als de minimale afstand onder een drempelwaarde valt.
### Beslissing: Denormalisatie van `product_id` op `stories` en `sprint_id` op `tasks`
**Keuze:** `product_id` opslaan op zowel `pbis` als `stories`; `sprint_id` op zowel `stories` als `tasks`
**Rationale:** Veel queries in de gesplitste schermen filteren op product of Sprint zonder de volledige hiërarchie te doorlopen. Directe foreign keys voorkomen onnodige joins en N+1-risico's.
**Trade-off:** Redundante data vereist consistente updates. Gehandhaafd via Prisma-transacties in de service-laag.
### Beslissing: Zustand voor client-side state management
**Keuze:** Drie Zustand-stores naast Server Components
**Rationale:** De gesplitste schermen met dnd-kit vereisen client-side staat die twee panelen tegelijk aanstuurt. `useState` per component leidt tot prop drilling; Context API veroorzaakt onnodige re-renders bij 60fps drag-events. Zustand's selector-gebaseerde subscriptions updaten alleen de componenten die de gewijzigde slice observeren. De gouden regel: Zustand beheert uitsluitend ephemere UI-staat — nooit server-data. Server-data blijft in Server Components en wordt opgehaald via Prisma.
**Trade-off:** Extra abstractielaag die geïnitialiseerd moet worden vanuit server-data. Opgelost via een patroon waarbij het Server Component de initiële ids doorgeeft aan een Client Component dat de store hydrateert.
---
## Zustand stores
### `usePlannerStore` — optimistische drag-and-drop volgorde
Beheert de lokale volgorde van PBI's, stories en taken tijdens en na drag-and-drop, voordat de server bevestigt. Houdt de UI vloeiend op 60fps ongeacht netwerklatency.
```ts
// stores/planner-store.ts
import { create } from 'zustand'
interface PlannerStore {
// Optimistische volgorde per container (id-arrays)
pbiOrder: Record<string, string[]> // productId → pbi-ids
storyOrder: Record<string, string[]> // pbiId → story-ids
taskOrder: Record<string, string[]> // storyId → taak-ids
// Initialiseren vanuit server-data (bij mount)
initPbis: (productId: string, ids: string[]) => void
initStories: (pbiId: string, ids: string[]) => void
initTasks: (storyId: string, ids: string[]) => void
// Optimistisch updaten (vóór server-bevestiging)
reorderPbis: (productId: string, newOrder: string[]) => void
reorderStories: (pbiId: string, newOrder: string[]) => void
reorderTasks: (storyId: string, newOrder: string[]) => void
// Terugdraaien bij server-fout
rollbackPbis: (productId: string, prevOrder: string[]) => void
rollbackStories: (pbiId: string, prevOrder: string[]) => void
rollbackTasks: (storyId: string, prevOrder: string[]) => void
}
```
**Gebruikspatroon:**
```ts
// 1. Server Component geeft ids door
// app/(app)/products/[id]/page.tsx
const pbis = await prisma.pbi.findMany({ where: { product_id: id }, orderBy: [...] })
return <BacklogPanel productId={id} initialPbiIds={pbis.map(p => p.id)} pbis={pbis} />
// 2. Client Component hydrateert store
// components/backlog/backlog-panel.tsx
'use client'
const { initPbis, reorderPbis, rollbackPbis } = usePlannerStore()
useEffect(() => { initPbis(productId, initialPbiIds) }, [])
// 3. dnd-kit onDragEnd → optimistisch updaten + Server Action
const prevOrder = usePlannerStore(s => s.pbiOrder[productId])
reorderPbis(productId, newOrder)
const result = await reorderPbisAction(productId, newOrder)
if (!result.success) rollbackPbis(productId, prevOrder)
```
---
### `useSelectionStore` — navigatieselectie
Beheert welk PBI of story geselecteerd is in het linkerpaneel, zodat beide panelen en de navigatiebar synchroon reageren zonder prop drilling.
```ts
// stores/selection-store.ts
interface SelectionStore {
selectedPbiId: string | null
selectedStoryId: string | null
setSelectedPbi: (id: string | null) => void
setSelectedStory: (id: string | null) => void
clearSelection: () => void
}
```
---
### `useSprintStore` — Sprint Backlog interacties
Beheert optimistische toevoegingen en verwijderingen van stories aan de Sprint Backlog tijdens drag-and-drop tussen de twee panelen.
```ts
// stores/sprint-store.ts
interface SprintStore {
// Stories per Sprint (optimistisch, op volgorde)
sprintStoryIds: Record<string, string[]> // sprintId → story-ids
initSprint: (sprintId: string, ids: string[]) => void
addStoryToSprint: (sprintId: string, storyId: string, atIndex: number) => void
removeStoryFromSprint: (sprintId: string, storyId: string) => void
reorderSprintStories: (sprintId: string, newOrder: string[]) => void
rollbackSprint: (sprintId: string, prevIds: string[]) => void
}
```
---
## Data flow architectuur
```
┌─────────────────────────────────────────┐
│ Server Component (page.tsx) │
│ Prisma query → initiële data + ids │
│ → props naar Client Component │
└──────────────────┬──────────────────────┘
│ initialIds, initialData
┌─────────────────────────────────────────┐
│ Client Component (panel.tsx) │
│ useEffect → store.init(ids) │
│ dnd-kit drag → store.reorder() │
│ → Server Action (async) │
│ → bij fout: store.rollback()│
└──────────────────┬──────────────────────┘
│ selecteert state via selector
┌─────────────────────────────────────────┐
│ Zustand Stores │
│ usePlannerStore useSelectionStore │
│ useSprintStore │
│ │
│ Alleen ephemere UI-staat │
│ Nooit server-data of business logic │
└─────────────────────────────────────────┘
```
**Keuze:** API-tokens opgeslagen als SHA-256 hashes in de `api_tokens` tabel
**Rationale:** Het token zelf wordt eenmalig getoond aan de gebruiker en nooit opgeslagen. De hash is voldoende voor lookup en verificatie. Redis of een aparte token-store zou overkill zijn voor v1-schaal.
**Trade-off:** Tokens kunnen niet worden verlengd of geroteerd zonder een nieuw token aan te maken.
---
## Environment variables
| Variabele | Doel | Waar te vinden |
|---|---|---|
| `DATABASE_URL` | Prisma database-verbinding | Neon dashboard (cloud) of lokaal `file:./dev.db` (SQLite) |
| `DIRECT_URL` | Directe verbinding voor migraties (Neon) | Neon dashboard → Connection string (unpooled) |
| `SESSION_SECRET` | Versleutelingssleutel voor iron-session | Genereer met `openssl rand -base64 32` |
| `NODE_ENV` | Omgevingsmodus | Automatisch gezet door Vercel / Node |
`.env.example`:
```bash
# Database
DATABASE_URL="postgresql://user:password@host/dbname?sslmode=require"
DIRECT_URL="postgresql://user:password@host/dbname?sslmode=require"
# Lokaal (SQLite): DATABASE_URL="file:./dev.db"
# Sessie
SESSION_SECRET="vervang-dit-met-openssl-rand-base64-32-output"
# Optioneel
NODE_ENV="development"
```
---
## Deployment
**Hosting:** Vercel (Hobby — gratis voor v1)
**CI/CD:** GitHub Actions → lint + typecheck + `prisma validate` op elke PR; Vercel deploy automatisch bij merge naar `main`
**Database (cloud):** Neon — migraties via `prisma migrate deploy` in de Vercel build-stap
**Database (lokaal):** SQLite — `npx prisma db push` initialiseert schema
**Seeding:** `npx prisma db seed` laadt de testdata uit het Product Backlog document
### Deployment checklist (pre-launch)
- [ ] `DATABASE_URL` en `DIRECT_URL` gezet in Vercel dashboard (Neon connection strings)
- [ ] `SESSION_SECRET` gezet in Vercel dashboard (min. 32 tekens)
- [ ] `prisma migrate deploy` uitgevoerd op productiedatabase
- [ ] Demo-gebruiker aangemaakt via seed of handmatig
- [ ] API-token aangemaakt en getest met `curl`-aanroep naar `/api/products`
- [ ] Vercel preview-deployments getest op een PR
- [ ] `next build` lokaal geslaagd zonder TypeScript-fouten
---
## Kostenscattting
| Service | Plan | Maandelijkse kosten |
|---|---|---|
| Vercel | Hobby | Gratis |
| Neon | Free tier (0.5 GB, 190 compute-uren) | Gratis |
| GitHub | Free | Gratis |
| Domein | Eigen domein (optioneel) | ~€12/maand |
| **Totaal** | | **€02/maand** |
> Bij groei naar meerdere gebruikers (v2): Neon Launch plan (~$19/maand) en Vercel Pro (~$20/maand) zijn de eerste stappen omhoog.

356
docs/scrum4me-backlog.md Normal file
View file

@ -0,0 +1,356 @@
# Scrum4Me — Implementatie Backlog
**Versie:** 0.1 — april 2026
**Volgt op:** Functionele Specificatie v0.2, Architectuur v0.1
---
## MVP-definitie
De MVP is klaar wanneer Lars — de primaire persona — de volledige cyclus kan doorlopen: een product aanmaken, een Product Backlog opbouwen met PBI's en stories, een Sprint plannen, taken aanmaken, en Claude Code de volgende story laten ophalen, implementeren en vastleggen — allemaal zonder hulp of handleiding. De app draait stabiel op Vercel en is volledig lokaal opzetbaar via één README.
---
## Milestone-overzicht
| Milestone | Doel | Tasks |
|---|---|---|
| M0: Foundation | Project, database, auth, navigatieshell | ST-001 ST-008 |
| M1: Producten & Product Backlog | Producten, PBI's, gesplitst scherm | ST-101 ST-110 |
| M2: Stories & Drag-and-drop | Stories als blokken, dnd-kit, Zustand | ST-201 ST-210 |
| M3: Sprint Backlog & Sprint Planning | Sprint aanmaken, stories slepen, taken | ST-301 ST-312 |
| M4: Claude Code REST API | Alle endpoints, tokenbeheer | ST-401 ST-410 |
| M5: Todo-lijst | Todo CRUD, promotie naar PBI/story | ST-501 ST-506 |
| M6: Polish & Launch-ready | Foutafhandeling, toegankelijkheid, CI/CD, beveiliging | ST-601 ST-612 |
---
## Backlog
### M0: Foundation
- [ ] **ST-001** Project scaffolding
- `create-next-app` met TypeScript strict, Tailwind CSS, App Router; installeer shadcn/ui, Zustand, dnd-kit, iron-session, bcrypt, Zod; configureer path aliases (`@/`)
- Done when: `npm run dev` start zonder fouten; `npm run lint` geeft geen errors; shadcn `Button` rendert op een testpagina
- [ ] **ST-002** Prisma v7 setup + `prisma.config.ts`
- Installeer Prisma v7 + `@prisma/adapter-pg`; schrijf `prisma.config.ts` met `DATABASE_URL` via Zod-gevalideerde env; configureer SQLite-adapter voor lokale modus; schrijf `lib/prisma.ts` singleton
- Done when: `npx prisma db push` slaagt met SQLite lokaal; Prisma Client importeerbaar in een testbestand zonder fouten
- [ ] **ST-003** Database schema migratie (volledige initiële migratie)
- Schrijf het volledige `schema.prisma` op basis van het architectuurdocument: `User`, `UserRole`, `ApiToken`, `Product`, `Pbi`, `Story`, `StoryLog`, `Sprint`, `Task`, `Todo`; alle enums, indexes, cascade deletes
- Done when: `npx prisma migrate dev --name init` slaagt; alle tabellen zichtbaar in DB-client; `npx prisma validate` geeft geen fouten
- [ ] **ST-004** Seed met testdata
- Schrijf `prisma/seed.ts` op basis van het Product Backlog document (devplanner-product-backlog.md); seed één gebruiker, één product (Scrum4Me zelf), alle PBI's en stories als testdata; voeg demo-gebruiker toe
- Done when: `npx prisma db seed` slaagt; DB bevat alle PBI's en stories uit het backlog-document; demo-gebruiker aanwezig
- [ ] **ST-005** Environment variabelen + `lib/env.ts`
- Schrijf Zod-schema voor alle env vars (`DATABASE_URL`, `DIRECT_URL`, `SESSION_SECRET`, `NODE_ENV`); exporteer gevalideerd `env` object; schrijf `.env.example` met instructies
- Done when: app gooit een begrijpelijke fout bij ontbrekende env var; `.env.example` volledig gedocumenteerd
- [ ] **ST-006** Authenticatie — registratie en inloggen
- Schrijf `lib/auth.ts` (registreer met bcrypt hash, verifieer bij inloggen); schrijf `lib/session.ts` (iron-session config); implementeer `/register` en `/login` pagina's met Server Actions; sla `{ userId, isDemo }` op in sessiecookie
- Done when: registreren → ingelogde sessie → redirect `/dashboard`; inloggen met verkeerde credentials geeft generieke foutmelding; sessie blijft actief na paginaverversing
- [ ] **ST-007** Route-beveiliging via `middleware.ts`
- Schrijf `middleware.ts` die iron-session cookie uitleest; redirect naar `/login` bij alle `/dashboard`, `/products/*`, `/todos`, `/settings/*` routes zonder geldige sessie; authenticated users worden van `/login` en `/register` doorgestuurd naar `/dashboard`
- Done when: directe navigatie naar `/dashboard` zonder sessie redirect naar `/login`; ingelogde gebruiker op `/login` redirect naar `/dashboard`
- [ ] **ST-008** Navigatieshell + dashboard-layout
- Schrijf `app/(app)/layout.tsx` met navigatiebalk (logo, productenlink, todolink, instellingen, uitlogknop); implementeer uitlog Server Action; implementeer `/dashboard` als lege productenlijstpagina met "Maak je eerste product aan" lege staat; zet demo-badge zichtbaar als `isDemo === true`
- Done when: volledige auth-flow (register → login → dashboard → logout → login) werkt end-to-end; demo-gebruiker ziet badge in navigatie
---
### M1: Producten & Product Backlog
- [ ] **ST-101** Product aanmaken
- `/products/new` pagina met formulier (naam, beschrijving, repo URL, definition of done); `createProduct` Server Action met Zod-validatie; uniekheidscontrole op naam per gebruiker; redirect naar `/products/[id]` na aanmaken
- Done when: product aangemaakt en zichtbaar op dashboard; dubbele naam geeft inline validatiefout; lege naam blokkeert submit
- [ ] **ST-102** Productenlijst op dashboard
- Haal actieve producten op via Prisma Server Component; toon naam, beschrijving (ingekort 80 tekens), repo-link; lege staat met CTA; klikken opent Product Backlog
- Done when: twee producten zichtbaar na aanmaken; gearchiveerd product niet zichtbaar in standaardlijst
- [ ] **ST-103** Product bewerken en archiveren
- Bewerkformulier (naam, beschrijving, repo URL, DoD) via Server Action; archiveerknop met bevestigingsdialoog; hersteloptie voor gearchiveerde producten; "toon gearchiveerd"-filter op dashboard
- Done when: naam bijwerken persisteert; archiveren verbergt product; herstel maakt het weer zichtbaar
- [ ] **ST-104** Gesplitst scherm layout component (`SplitPane`)
- Bouw herbruikbaar `<SplitPane>` Client Component met versleepbare horizontale splitter; sla splitter-positie op in `localStorage` per sleutel; standaard 40/60 verhouding; minimale panelbreedte 200px; responsive fallback naar tabs op < 1024px
- Done when: splitter versleepbaar en positie behouden na paginaverversing; tabs getoond op smal scherm
- [ ] **ST-105** Navigatiebar-component per paneel
- Bouw herbruikbaar `<PanelNavBar>` component met slots voor knoppen (aanmaken, filter, verwijderen); consistent design voor linker- en rechterpaneel
- Done when: navigatiebar herbruikt in minimaal twee gesplitste schermen zonder duplicatie
- [ ] **ST-106** PBI aanmaken en weergeven
- Linkerpaneel van `/products/[id]`: haal PBI's op gegroepeerd op prioriteit en sort_order; "PBI aanmaken" knop opent inline formulier (titel, prioriteit); `createPbi` Server Action; nieuw PBI verschijnt onderaan de juiste prioriteitsgroep
- Done when: PBI aangemaakt en zichtbaar in juiste prioriteitsgroep; lege staat toont prompt
- [ ] **ST-107** PBI prioriteitsgroepen met visuele scheiding
- Render PBI's gegroepeerd per prioriteit (14) met gelabelde scheidingslijn per groep (bijv. "Kritiek", "Hoog"); lege groepen zijn niet zichtbaar; prioriteitsbadge per PBI
- Done when: vier prioriteitsgroepen correct gerenderd met labels; PBI met prioriteit 1 staat boven prioriteit 4
- [ ] **ST-108** PBI bewerken en verwijderen
- Inline bewerkingsmodus via dubbelklik of contextmenu (titel, omschrijving, prioriteit); `updatePbi` Server Action; verwijderen met bevestigingsdialoog inclusief waarschuwing cascade; `deletePbi` Server Action
- Done when: titelbewering opgeslagen zonder paginaverversing; verwijderen cascade-verwijdert stories (verifieerbaar in DB)
- [ ] **ST-109** PBI selecteren → stories laden
- Klikken op PBI in linkerpaneel toont bijbehorende stories rechts via `useSelectionStore`; geselecteerd PBI visueel gemarkeerd; lege staat rechts als geen stories
- Done when: klikken op PBI A toont stories van A rechts; klikken op PBI B schakelt direct over
- [ ] **ST-110** PBI filter
- Filterknop in linkerpaneel navigatiebar; dropdown voor prioriteit (14, alle); filter werkt realtime op gerenderde lijst; actief filter zichtbaar als badge; wissen via ×-knop
- Done when: filter op prioriteit 1 verbergt alle andere PBI's; wissen herstelt volledige lijst
---
### M2: Stories & Drag-and-drop
- [ ] **ST-201** `usePlannerStore` Zustand-store
- Schrijf `stores/planner-store.ts` met `pbiOrder`, `storyOrder`, `taskOrder`; `init*`, `reorder*`, `rollback*` actions; TypeScript strict types
- Done when: store importeerbaar in een Client Component; `initPbis` vult order; `reorderPbis` muteert order; `rollbackPbis` herstelt vorige staat
- [ ] **ST-202** `useSelectionStore` Zustand-store
- Schrijf `stores/selection-store.ts` met `selectedPbiId`, `selectedStoryId`, setters en `clearSelection`
- Done when: selectie in linkerpaneel via store zichtbaar in rechterpaneel zonder prop drilling
- [ ] **ST-203** dnd-kit setup + PBI drag-and-drop
- Installeer dnd-kit; wrap linkerpaneel in `DndContext` + `SortableContext`; implementeer `useSortable` per PBI-rij; `onDragEnd`: bereken nieuwe `sort_order` via float-gemiddelde; optimistisch updaten via `usePlannerStore`; `reorderPbisAction` Server Action; rollback bij fout
- Done when: PBI versleepbaar binnen prioriteitsgroep; volgorde opgeslagen na loslaten; UI rollback bij gesimuleerde server-fout
- [ ] **ST-204** PBI drag-and-drop over prioriteitsgrens
- Uitbreiding ST-203: slepen over een prioriteitsgrens wijzigt `priority` van het PBI; `sort_order` wordt onderaan de doelgroep geplaatst; `updatePbiPriority` Server Action
- Done when: PBI naar prioriteit 2 slepen vanuit prioriteit 3 wijzigt zowel prioriteit als volgorde
- [ ] **ST-205** Story aanmaken en weergeven als blokken
- Rechterpaneel van Product Backlog: haal stories op voor geselecteerd PBI; render als blokken (~10% schermbreedte, horizontaal); elk blok toont titel (ingekort), prioriteitsbadge, statusbadge; "Story aanmaken" knop; `createStory` Server Action
- Done when: drie stories zichtbaar als blokken; nieuw blok verschijnt in juiste prioriteitsgroep
- [ ] **ST-206** Story prioriteitsgroepen met visuele scheiding
- Groepeer story-blokken per prioriteit; gekleurde band of scheidingslijn per groep; blokken horizontaal gerangschikt per rij; nieuwe rij bij overloop
- Done when: stories van vier prioriteiten correct gescheiden weergegeven
- [ ] **ST-207** Story drag-and-drop (horizontaal, binnen en tussen groepen)
- dnd-kit horizontale `SortableContext` per prioriteitsgroep; `onDragEnd`: herrangschikking via float-gemiddelde in `storyOrder`; slepen naar andere groep wijzigt prioriteit; optimistisch via `usePlannerStore`; `reorderStoriesAction` Server Action; rollback bij fout
- Done when: story versleepbaar binnen groep en naar andere groep; volgorde en prioriteit persistent na loslaten
- [ ] **ST-208** Story detail-modal / slide-over
- Klikken op storyblok opent slide-over of modal met titel, omschrijving, acceptatiecriteria, statusbadge, activiteitenlog (leeg bij nieuwe story); bewerkformulier voor titel/omschrijving/acceptatiecriteria; `updateStory` Server Action
- Done when: klikken op blok opent detail; bewerken persisteert; sluiten keert terug naar backlog
- [ ] **ST-209** Story verwijderen
- Verwijderknop in story-detail of contextmenu; bevestigingsdialoog met waarschuwing cascade (taken); `deleteStory` Server Action; blok verdwijnt optimistisch uit het rechterpaneel
- Done when: story verwijderd incl. cascade-taken (verifieerbaar in DB); blok direct verdwenen uit UI
- [ ] **ST-210** Story filter in rechterpaneel
- Filterknop in rechterpaneel navigatiebar; filter op status (OPEN, IN_SPRINT, DONE) en prioriteit; realtime; actief filter als badge; wissbaar
- Done when: filter op OPEN verbergt IN_SPRINT stories
---
### M3: Sprint Backlog & Sprint Planning
- [ ] **ST-301** `useSprintStore` Zustand-store
- Schrijf `stores/sprint-store.ts`; `initSprint`, `addStoryToSprint`, `removeStoryFromSprint`, `reorderSprintStories`, `rollbackSprint`
- Done when: store beheert sprint-story-volgorde onafhankelijk van planner-store
- [ ] **ST-302** Sprint aanmaken
- "Sprint starten" knop op productpagina (zichtbaar als geen actieve Sprint); modal met Sprint Goal invoerveld; `createSprint` Server Action; max. 1 actieve Sprint per product afgedwongen in service-laag
- Done when: Sprint aangemaakt met Goal; tweede sprint aanmaken terwijl eerste actief is geeft foutmelding
- [ ] **ST-303** Sprint Backlog scherm — layout
- `/products/[id]/sprint` pagina; `SplitPane` met Sprint Backlog links (stories in Sprint op volgorde) en rechts de Product Backlog stories gegroepeerd per PBI (inklapbaar); Sprint Goal zichtbaar bovenaan; lege staat links met instructie
- Done when: pagina rendert correct; Sprint Goal zichtbaar; beide panelen tonen juiste data
- [ ] **ST-304** Story vanuit Product Backlog naar Sprint slepen
- dnd-kit drag vanuit rechterpaneel naar linkerpaneel; `onDragEnd`: `addStoryToSprint` in store; story krijgt badge "In Sprint" in Product Backlog; `addStoryToSprintAction` Server Action (zet `sprint_id` + status `IN_SPRINT`); rollback bij fout
- Done when: story gesleept naar Sprint verschijnt links en toont "In Sprint" badge rechts; persistent na herlaad
- [ ] **ST-305** Sprint Backlog story volgorde aanpassen
- dnd-kit verticale `SortableContext` in linkerpaneel; herrangschikking via float-gemiddelde in `useSprintStore`; `reorderSprintStoriesAction` Server Action
- Done when: volgorde in Sprint Backlog persistent na loslaten en na paginaverversing
- [ ] **ST-306** Story uit Sprint verwijderen
- Verwijderknop per story in Sprint Backlog; `removeStoryFromSprintAction` Server Action (wist `sprint_id`, zet status terug op `OPEN`); story verdwijnt links en badge verdwijnt rechts
- Done when: verwijderen persistent; story beschikbaar in Product Backlog rechterpaneel
- [ ] **ST-307** Sprint Planning scherm — layout
- `/products/[id]/sprint/planning` pagina; `SplitPane` met Sprint Backlog stories links (op volgorde) en taken van geselecteerde story rechts; Sprint Goal zichtbaar; lege staat rechts als geen story geselecteerd
- Done when: pagina rendert; story selecteren links toont taken rechts
- [ ] **ST-308** Taak aanmaken
- "Taak aanmaken" knop in rechterpaneel navigatiebar; inline formulier (titel, omschrijving, prioriteit); `createTask` Server Action; voortgangsindicator per story (bijv. "0/0 Done")
- Done when: taak aangemaakt en zichtbaar in takenlijst; voortgangsindicator toont "0/1 Done"
- [ ] **ST-309** Taak drag-and-drop (verticaal)
- dnd-kit verticale `SortableContext` in rechterpaneel; herrangschikking via float-gemiddelde in `usePlannerStore.taskOrder`; `reorderTasksAction` Server Action
- Done when: taken versleepbaar; volgorde persistent na loslaten
- [ ] **ST-310** Taakstatus bijhouden
- Status-toggle per taak (TO_DO → IN_PROGRESS → DONE) via klikbare badge of dropdown; `updateTaskStatus` Server Action; voortgangsindicator op story updatet optimistisch
- Done when: taak op DONE zetten verhoogt teller in voortgangsindicator; persistent na herlaad
- [ ] **ST-311** Taak bewerken en verwijderen
- Inline bewerken van titel, omschrijving en prioriteit; `updateTask` Server Action; verwijderen met bevestiging; `deleteTask` Server Action
- Done when: titelwijziging persisteert; verwijderde taak verdwijnt uit lijst
- [ ] **ST-312** Sprint afronden
- "Sprint afronden" knop op Sprint-pagina; dialoog toont per story de status en vraagt: "Markeer als Done of terug naar Backlog?"; `completeSprint` Server Action zet Sprint op COMPLETED, verwerkt keuzes per story
- Done when: Sprint afgerond; stories correct verplaatst naar DONE of OPEN; nieuwe Sprint aanmaakbaar
---
### M4: Claude Code REST API
- [ ] **ST-401** API-token infrastructuur
- Schrijf `lib/api-auth.ts`: parseer `Authorization: Bearer` header; bereken SHA-256 hash; zoek op in `api_tokens`; controleer `revoked_at`; retourneer `userId` of 401; retourneer 403 als `is_demo`
- Done when: geldige token geeft userId terug; ongeldige token geeft 401; ingetrokken token geeft 401; demo-token op schrijf-endpoint geeft 403
- [ ] **ST-402** API-tokenbeheer UI
- `/settings/tokens` pagina; token aanmaken (label optioneel); token eenmalig getoond in kopieerbaar veld na aanmaken; tokenoverzicht (label, datum, actief/ingetrokken); intrekken via Server Action; max. 10 actieve tokens
- Done when: token aangemaakt en waarde zichtbaar; na sluiten dialoog niet meer te zien; intrekken maakt token onbruikbaar (getest via curl)
- [ ] **ST-403** `GET /api/products` — productenlijst
- Route Handler; authenticatie via `api-auth.ts`; retourneer actieve producten `[{ id, name, repo_url }]` als JSON
- Done when: `curl -H "Authorization: Bearer <token>" /api/products` retourneert correct JSON; 401 zonder token
- [ ] **ST-404** `GET /api/products/:id/next-story` — volgende story ophalen
- Route Handler; haal hoogst geprioriteerde OPEN story op van actieve Sprint van het product (priority ASC, sort_order ASC); retourneer `{ id, title, description, acceptance_criteria, tasks[] }`; 404 als geen open stories
- Done when: endpoint retourneert eerste story van Sprint; 404 als Sprint leeg; 404 als geen actieve Sprint
- [ ] **ST-405** `GET /api/sprints/:id/tasks` — taken ophalen
- Route Handler met `?limit=N` query param (default 10, max 50); retourneer taken van actieve Sprint op `(story.sort_order, task.priority, task.sort_order)` volgorde; retourneer `{ id, title, story_id, priority, sort_order, status }`
- Done when: endpoint retourneert max N taken in juiste volgorde; `?limit=5` retourneert max 5
- [ ] **ST-406** `PATCH /api/stories/:id/tasks/reorder` — taakvolgorde aanpassen
- Route Handler; body: `{ task_ids: string[] }`; valideer alle IDs behoren tot de story; update `sort_order` via float-verdeling; retourneer `{ success: true }`
- Done when: volgorde in DB veranderd na PATCH; gewijzigde volgorde zichtbaar in Sprint Planning UI na herlaad; ongeldige task_id geeft 400
- [ ] **ST-407** `POST /api/stories/:id/log` — activiteit vastleggen
- Route Handler; body: `{ type, content, status?, commit_hash?, commit_message? }`; Zod-validatie per type; schrijf naar `story_logs`; retourneer `{ id, created_at }`
- Done when: drie typen werken (IMPLEMENTATION_PLAN, TEST_RESULT, COMMIT); log-entry zichtbaar in story-detail UI na aanmaken via API; ontbrekend verplicht veld geeft 400
- [ ] **ST-408** `PATCH /api/tasks/:id` — taakstatus bijwerken
- Route Handler; body: `{ status: "TO_DO" | "IN_PROGRESS" | "DONE" }`; valideer dat taak aan requester's product behoort; update status; retourneer `{ id, status }`
- Done when: status update via API zichtbaar in Sprint Planning UI; ongeldige status geeft 400; andere gebruikers taak geeft 403
- [ ] **ST-409** `POST /api/todos` — todo aanmaken
- Route Handler; body: `{ title: string }`; schrijf naar `todos` voor de geverifieerde gebruiker; retourneer `{ id, title, created_at }`
- Done when: todo aangemaakt via API verschijnt in todo-lijst UI; lege titel geeft 400
- [ ] **ST-410** Story-activiteitenlog UI
- Activiteitenlog sectie in story-detail slide-over; haal `story_logs` op via Server Component; render chronologisch; visuele stijl per type (IMPLEMENTATION_PLAN = blauw, TEST_RESULT passed = groen, failed = rood, COMMIT = paars); commit-hash klikbaar als `repo_url` ingesteld; lege staat
- Done when: drie log-entries (plan, test, commit) correct gestyled; commit-hash link opent in nieuw tabblad
---
### M5: Todo-lijst
- [ ] **ST-501** Todo-lijst pagina
- `/todos` pagina; haal actieve (niet-gearchiveerde) todos op; snel-invoerveld bovenaan (Enter om op te slaan); `createTodo` Server Action; lege staat met instructie
- Done when: todo aanmaken via Enter persisteert en verschijnt in lijst; lege staat zichtbaar bij geen todos
- [ ] **ST-502** Todo afvinken
- Checkbox per todo; `toggleTodo` Server Action; afgevinkte todos visueel doorgestreept; afgevinkte todos blijven zichtbaar onderaan de lijst
- Done when: afvinken persistent na herlaad; visuele doorstreping correct
- [ ] **ST-503** Afgevinkte todos archiveren
- "Archiveer afgeronde items" knop; `archiveCompletedTodos` Server Action; gearchiveerde todos verdwijnen uit standaardweergave
- Done when: archiveren verbergt alle afgevinkte todos; telling correct
- [ ] **ST-504** Todo promoveren naar PBI
- "Promoveer naar PBI" contextmenu of knop per todo; dialoog: product dropdown (actieve producten), prioriteit dropdown; titel vooringevuld (bewerkbaar); bevestigingswaarschuwing; `promoteTodeToPbi` Server Action (maak PBI aan, verwijder todo)
- Done when: gepromoveerde todo verdwijnt; PBI zichtbaar in juist product met juiste prioriteit
- [ ] **ST-505** Todo promoveren naar story
- "Promoveer naar story" knop per todo; dialoog: product dropdown → PBI dropdown (gefilterd op product), prioriteit; titel vooringevuld; `promoteTodoToStory` Server Action (maak story aan, verwijder todo)
- Done when: gepromoveerde todo verdwijnt; story zichtbaar in juist PBI met juiste prioriteit
- [ ] **ST-506** Rolbeheer in instellingen
- `/settings` pagina met roltoewijzing (checkbox per rol: Product Owner, Scrum Master, Developer); minimaal één rol verplicht; `updateRoles` Server Action; geselecteerde rollen zichtbaar in profielbalk
- Done when: rollen bijwerken persisterend; profielbalk toont gekozen rollen; uitvinken van alle rollen geeft validatiefout
---
### M6: Polish & Launch-ready
- [ ] **ST-601** Loading states en skeletons
- `loading.tsx` voor alle zware routes (`/products/[id]`, `/sprint`, `/sprint/planning`); skeletoncomponenten voor PBI-lijst, story-blokken en takenlijst; pending states op alle form-submit-knoppen via `useFormStatus`
- Done when: navigeren naar een trage route toont skeleton; submit-knoppen disablen tijdens Server Action
- [ ] **ST-602** Error boundaries
- `error.tsx` voor alle beschermde routes; toon gebruiksvriendelijke foutmelding met "Probeer opnieuw" knop; log fout naar console (Sentry in M6)
- Done when: gesimuleerde Server Action-fout toont error boundary zonder witte pagina
- [ ] **ST-603** Toast-notificaties (Sonner)
- Installeer Sonner; success-toast na aanmaken/bewerken/verwijderen van producten, PBI's, stories, taken, todos; error-toast bij mislukte Server Actions; toast niet bij drag-and-drop (te frequent)
- Done when: aanmaken van PBI toont success-toast; gesimuleerde netwerk-fout toont error-toast
- [ ] **ST-604** Demo-gebruiker write-protection in UI
- Alle aanmaak-, bewerk- en verwijderknoppen disabled + tooltip "Niet beschikbaar in demo-modus" voor demo-sessies; gebaseerd op `isDemo` in sessie
- Done when: demo-gebruiker ziet alle knoppen maar kan niets wijzigen; tooltip zichtbaar bij hover
- [ ] **ST-605** Keyboard-navigatie
- Tab-volgorde logisch in alle formulieren; Enter submits formulieren; Escape sluit modals/slide-overs; dnd-kit keyboard-drag (Space om te pakken, pijltjestoetsen, Space om te laten vallen)
- Done when: volledige PBI aanmaken-flow keyboard-only uitvoerbaar; dnd-kit drag via keyboard werkt
- [ ] **ST-606** Desktop-first UI-review
- Test alle flows op 1280px, 1440px en 1920px breedte; fix overflow, uitlijning en proportie-issues; controleer minimum schermbreedte 1024px (toon melding bij smaller)
- Done when: alle M0M5 flows correct op drie schermbreedtes; melding bij < 1024px
- [ ] **ST-607** Toegankelijkheid (WCAG AA)
- Kleurcontrast-check op alle tekst en badges; aria-labels op icon-only knoppen; focus-ring zichtbaar op alle interactieve elementen; `role` en `aria-selected` op geselecteerde PBI in linkerpaneel
- Done when: geen WCAG AA contrastfouten op primaire flows; alle knoppen hebben toegankelijke labels
- [ ] **ST-608** Ratelimiting op auth-endpoints
- Max. 10 inlogpogingen per IP per minuut; max. 5 registraties per IP per uur; implementeer via in-memory counter (v1) of Vercel Edge middleware
- Done when: 11 snelle inlogpogingen leiden tot 429-respons met duidelijke melding
- [ ] **ST-609** Beveiligingsreview API-endpoints
- Controleer alle Route Handlers: elke schrijfoperatie valideert dat de resource bij de geverifieerde gebruiker hoort; cross-user reads zijn onmogelijk; voeg integratietests toe die cross-user toegang testen
- Done when: poging om andere gebruikers's product op te halen via API geeft 403; getest met twee test-gebruikers
- [ ] **ST-610** CI/CD via GitHub Actions
- Workflow: `lint` (ESLint), `typecheck` (tsc --noEmit), `prisma validate`, `build` (next build) op elke PR en push naar main; Vercel auto-deploy op main
- Done when: een TypeScript-fout in een PR blokkeert merge; succesvolle merge triggert Vercel-deploy
- [ ] **ST-611** README en lokale setup-documentatie
- Schrijf `README.md` met: wat is Scrum4Me, quickstart lokaal (clone → env → prisma push → seed → dev), cloud deployment (Vercel + Neon stappenplan), API-documentatie (alle 7 endpoints met voorbeelden), Claude Code-integratie uitleg
- Done when: iemand zonder context de app lokaal kan draaien op basis van alleen de README
- [ ] **ST-612** End-to-end acceptatietest
- Voer handmatig de volledige Lars-flow uit: product aanmaken → PBI's en stories aanmaken → Sprint starten → stories slepen → taken aanmaken → API-token aanmaken → curl `next-story` → curl `log` (plan, test, commit) → activiteitenlog controleren in UI
- Done when: volledige flow werkt zonder fouten of onverwacht gedrag; alle API-responses correct JSON
---
## v2 Backlog (na MVP)
- [ ] Meerdere gebruikers per Scrum Team — uitgebreide auth, rol-gebaseerde permissies, uitnodigingsflow
- [ ] Daily Scrum scherm — voortgang per story bijhouden tijdens Sprint
- [ ] Sprint Review scherm — demo en feedback vastleggen per story
- [ ] Sprint Retrospective scherm — reflectie per Sprint
- [ ] Automatische story-statusupdate na commit via API
- [ ] Velocity tracking — statistieken over meerdere Sprints
- [ ] Definition of Done per product configureerbaar (nu vaste instelling)
- [ ] Notificaties / reminders
- [ ] Timeline / kalenderweergave per Sprint
- [ ] Integratie GitHub Issues / Linear
- [ ] Mobiele app — uitsluitend taken afvinken
- [ ] Export van Product Backlog en Sprint als markdown of CSV
---
## Definition of MVP Done
- [ ] Alle M0M6 tasks afgerond
- [ ] Volledige Lars-flow succesvol doorlopen (ST-612)
- [ ] Alle 7 API-endpoints getest via curl (ST-403 t/m ST-409)
- [ ] Demo-gebruiker kan inloggen en heeft geen schrijfrechten (ST-604)
- [ ] App lokaal opzetbaar via README zonder extra hulp (ST-611)
- [ ] CI/CD actief — falende build blokkeert merge (ST-610)
- [ ] Beveiligingsreview API geslaagd (ST-609)
- [ ] Geen bekende blocker-bugs

View file

@ -0,0 +1,479 @@
# Scrum4Me — Functionele Specificatie
**Versie:** 0.2 — april 2026
**Volgt op:** Brainstorm v0.3, Personas v0.1
---
## MVP-scopeverklaring
v1 is een single-user desktop-first fullstack webapplicatie waarmee een solo developer of klein Scrum Team meerdere softwareprojecten hiërarchisch kan plannen (product → PBI → story → taak), Sprints kan beheren via gesplitste schermen met drag-and-drop, en Claude Code kan integreren voor geautomatiseerde implementatieflows waarbij elk resultaat wordt vastgelegd in de story. De app is deployable op Vercel + Neon én volledig lokaal draaibaar.
## Expliciet buiten scope voor v1
- Meerdere gebruikers per Scrum Team — datastructuur is voorbereid, UI en permissies komen in v2
- E-mailverificatie bij registratie — gebruikersnaam/wachtwoord volstaat voor v1
- Daily Scrum, Sprint Review en Sprint Retrospective schermen — v2 zodra teamgebruik mogelijk is
- Automatische statusupdate van stories na commit — handmatige update via UI in v1
- Tijdregistratie, urenverantwoording en burndown-charts — buiten positionering
- Integratie met externe tools (GitHub Issues, Linear, Jira) — v2
- Notificaties en reminders — v2
- Native mobiele app — web-first; een toekomstige mobiele variant richt zich uitsluitend op taken afvinken
- Responsive layout voor schermen smaller dan 1024px — desktop-first in v1
---
## Feature-specificaties
### F-01: Authenticatie
**Prioriteit:** v1 — Kritiek
**Persona:** Lars, Dina, Remi
**Omschrijving:**
Gebruikers kunnen een account aanmaken en inloggen met gebruikersnaam en wachtwoord. Er is een ingebouwde demo-gebruiker met alleen leesrechten. Geen e-mailverificatie vereist in v1.
**Acceptatiecriteria:**
- [ ] Registratie vereist gebruikersnaam (uniek, min. 3 tekens) en wachtwoord (min. 8 tekens)
- [ ] Dubbele gebruikersnaam geeft duidelijke foutmelding bij registratie
- [ ] Inloggen met incorrecte combinatie geeft generieke foutmelding (geen onderscheid naam/wachtwoord)
- [ ] Na inloggen wordt de gebruiker doorgestuurd naar het dashboard
- [ ] Sessie blijft actief totdat de gebruiker uitlogt
- [ ] Demo-gebruiker kan inloggen met vaste credentials (zichtbaar op de loginpagina)
- [ ] Demo-gebruiker kan niets aanmaken, aanpassen of verwijderen
- [ ] Alle schrijfknoppen zijn zichtbaar maar uitgeschakeld voor de demo-gebruiker, met tooltip "Niet beschikbaar in demo-modus"
- [ ] Uitlogknop is altijd zichtbaar in de navigatie
**Randgevallen:**
- Gebruiker probeert beschermde route te bezoeken zonder sessie → redirect naar /login
- Demo-gebruiker probeert via de API te schrijven → 403 Forbidden
**Data:**
- Opgeslagen: `users` (id, username, password_hash, role[], is_demo, created_at)
- Sessie: JWT of server-side sessie met user_id en is_demo vlag
---
### F-02: Roltoewijzing
**Prioriteit:** v1 — Fundament voor v2
**Persona:** Remi (v2), Lars en Dina (impliciet)
**Omschrijving:**
Een gebruiker kan bij registratie of in instellingen één of meerdere Scrum-rollen aannemen: Product Owner, Scrum Master, Developer. In v1 heeft dit geen effect op zichtbare functionaliteit maar de datastructuur ondersteunt rolscheiding voor v2-teamgebruik.
**Acceptatiecriteria:**
- [ ] Gebruiker kan bij registratie of achteraf in instellingen rollen selecteren
- [ ] Minimaal één rol is verplicht
- [ ] Alle drie de rollen tegelijk zijn toegestaan
- [ ] Geselecteerde rollen zijn zichtbaar in de profielbalk
- [ ] Rolkeuze heeft in v1 geen effect op zichtbare functionaliteit
- [ ] Demo-gebruiker heeft een vaste rol (Developer) die niet gewijzigd kan worden
**Data:**
- Opgeslagen: `user_roles[]` als array op het gebruikersobject (enum: PRODUCT_OWNER, SCRUM_MASTER, DEVELOPER)
---
### F-03: Productbeheer
**Prioriteit:** v1 — Kritiek
**Persona:** Lars (meerdere eigen projecten), Dina (per klant), Remi (per team-product)
**Omschrijving:**
Gebruikers kunnen producten aanmaken, bewerken en archiveren. Een product is het hoogste niveau in de hiërarchie en bevat een naam, beschrijving, git-repo URL en de Definition of Done. Alle andere entiteiten (PBI's, stories, taken) horen bij een product.
**Acceptatiecriteria:**
- [ ] Product aanmaken vereist een naam (uniek per gebruiker, verplicht)
- [ ] Beschrijving is optioneel (vrije tekst, max. 1000 tekens)
- [ ] Git-repo URL is optioneel; wordt gevalideerd als geldige URL bij invullen
- [ ] Definition of Done is een vaste tekst, instelbaar per product (verplicht bij aanmaken, max. 500 tekens)
- [ ] Product verschijnt direct in de productenlijst na aanmaken
- [ ] Naam en alle andere velden zijn bewerkbaar na aanmaken
- [ ] Archiveren is omkeerbaar; gearchiveerde producten zijn standaard verborgen
- [ ] Productenlijst toont: naam, beschrijving (ingekort tot 80 tekens), git-repo link (indien aanwezig)
- [ ] Klikken op een product opent de Product Backlog van dat product
- [ ] Lege staat toont een duidelijke prompt om een eerste product aan te maken
**Randgevallen:**
- Gebruiker probeert naam leeg te maken bij bewerken → validatiefout, opslaan geblokkeerd
- Git-repo URL zonder `https://` → validatiefout met suggestie
**Data:**
- Opgeslagen: `products` (id, user_id, name, description, repo_url, definition_of_done, archived, created_at, updated_at)
---
### F-04: Product Backlog — gesplitst scherm
**Prioriteit:** v1 — Kritiek
**Persona:** Lars, Dina, Remi
**Omschrijving:**
De Product Backlog wordt weergegeven als een gesplitst scherm: links de PBI's gerangschikt op prioriteit en volgorde, rechts de stories van het geselecteerde PBI. De splitter is horizontaal versleepbaar. Elk paneel heeft een eigen navigatiebar met acties (aanmaken, filteren, verwijderen).
**Acceptatiecriteria:**
- [ ] Standaard splitverhouding is 40/60 (PBI's / stories)
- [ ] Splitter is versleepbaar; positie wordt lokaal opgeslagen (localStorage)
- [ ] Selecteren van een PBI links toont de bijbehorende stories rechts
- [ ] Geselecteerd PBI is visueel gemarkeerd (achtergrondkleur of rand)
- [ ] Linkerpaneel navigatiebar bevat: [+ PBI aanmaken], [filter], [verwijderen]
- [ ] Rechterpaneel navigatiebar bevat: [+ Story aanmaken], [filter], [verwijderen]
- [ ] Lege staat links: prompt om eerste PBI aan te maken
- [ ] Lege staat rechts (geen PBI geselecteerd): instructie om een PBI te selecteren
- [ ] Lege staat rechts (PBI geselecteerd, geen stories): prompt om eerste story aan te maken
**Randgevallen:**
- Scherm smaller dan 768px → gesplitst scherm schakelt over naar tabbladen (PBI's / Stories)
---
### F-05: PBI-beheer
**Prioriteit:** v1 — Kritiek
**Persona:** Lars, Dina, Remi
**Omschrijving:**
Product Backlog Items kunnen worden aangemaakt, bewerkt, geprioriteerd (14) en gerangschikt via drag-and-drop binnen dezelfde prioriteitsgroep. PBI's worden gegroepeerd per prioriteit met een visuele scheiding.
**Acceptatiecriteria:**
- [ ] PBI aanmaken vereist een titel (verplicht, max. 200 tekens)
- [ ] Omschrijving is optioneel (vrije tekst, max. 2000 tekens)
- [ ] Prioriteit is verplicht (1 = Kritiek, 2 = Hoog, 3 = Middel, 4 = Laag)
- [ ] PBI's worden gegroepeerd per prioriteit; elke groep heeft een visueel label en scheidingslijn
- [ ] Binnen een prioriteitsgroep is de volgorde instelbaar via drag-and-drop (dnd-kit)
- [ ] Slepen over een prioriteitsgrens wijzigt de prioriteit van het PBI
- [ ] Volgorde en prioriteit worden direct opgeslagen na loslaten
- [ ] PBI bewerken (titel, omschrijving, prioriteit) via inline bewerkingsmodus
- [ ] PBI verwijderen vereist bevestiging; cascade-verwijdering van gekoppelde stories en taken
- [ ] Bevestigingsdialoog vermeldt expliciet dat stories en taken ook verwijderd worden
- [ ] Filter op prioriteit werkt realtime; actief filter is visueel zichtbaar en eenvoudig te wissen
- [ ] Drag-and-drop placeholder toont de doelpositie tijdens het slepen
**Randgevallen:**
- Prioriteitsgroep is leeg na verplaatsing → groep verdwijnt uit de weergave
- PBI met stories verwijderen → bevestigingsdialoog toont het aantal gekoppelde stories
**Data:**
- Opgeslagen: `pbis` (id, product_id, title, description, priority (14), sort_order, created_at, updated_at)
---
### F-06: Story-beheer
**Prioriteit:** v1 — Kritiek
**Persona:** Lars, Dina, Remi
**Omschrijving:**
Stories worden weergegeven als compacte blokken (~10% schermbreedte) in het rechterpaneel van de Product Backlog, gerangschikt op prioriteit. Ze kunnen worden aangemaakt, bewerkt, geprioriteerd en gerangschikt via drag-and-drop.
**Acceptatiecriteria:**
- [ ] Story aanmaken vereist een titel (verplicht, max. 200 tekens)
- [ ] Omschrijving is optioneel (vrije tekst, max. 2000 tekens)
- [ ] Acceptatiecriteria zijn optioneel (vrije tekst, max. 2000 tekens)
- [ ] Prioriteit is verplicht (14, zelfde schaal als PBI's)
- [ ] Stories worden weergegeven als blokken van ~10% schermbreedte, horizontaal gerangschikt
- [ ] Blokken tonen: storytitel (ingekort), prioriteit-badge, status-badge
- [ ] Elke prioriteitsgroep heeft een visuele scheiding (gekleurde band of scheidingslijn)
- [ ] Volgorde binnen een prioriteitsgroep is instelbaar via drag-and-drop (dnd-kit)
- [ ] Slepen over een prioriteitsgrens wijzigt de prioriteit van de story
- [ ] Klikken op een storyblok opent de story-detailweergave (slide-over of modal)
- [ ] Story verwijderen vereist bevestiging; cascade-verwijdering van gekoppelde taken
- [ ] Stories die aan een Sprint gekoppeld zijn tonen een badge "In Sprint [naam]"
**Randgevallen:**
- Storytitel is langer dan past in het blok → afgekapt met ellipsis, volledige titel zichtbaar bij hover
**Data:**
- Opgeslagen: `stories` (id, pbi_id, product_id, title, description, acceptance_criteria, priority (14), sort_order, status (OPEN | IN_SPRINT | DONE), sprint_id?, created_at, updated_at)
---
### F-07: Story-activiteitenlog
**Prioriteit:** v1 — Kern van Claude Code-integratie
**Persona:** Lars
**Omschrijving:**
Elke story heeft een activiteitenlog die alle door Claude Code vastgelegde stappen toont: implementatieplannen, testresultaten en commits. De log is zichtbaar in de story-detailweergave en is read-only vanuit de UI.
**Acceptatiecriteria:**
- [ ] Log toont alle entries in chronologische volgorde (oudste eerst)
- [ ] Elk entry-type heeft een eigen visuele stijl:
- `implementation_plan`: blauw, icoon document
- `test_result (passed)`: groen, icoon vinkje
- `test_result (failed)`: rood, icoon kruis
- `commit`: paars, icoon git-branch
- [ ] Commit-entries tonen de hash (ingekort, bijv. `a1b2c3d`) en commit-bericht
- [ ] Commit-hash is klikbaar als de git-repo URL is ingesteld op het product; opent in nieuw tabblad
- [ ] Log is leeg bij nieuwe stories → lege staat toont "Nog geen activiteit vastgelegd"
- [ ] Log is niet bewerkbaar via de UI
**Data:**
- Opgeslagen: `story_logs` (id, story_id, type (IMPLEMENTATION_PLAN | TEST_RESULT | COMMIT), content, status? (PASSED | FAILED), commit_hash?, commit_message?, created_at)
---
### F-08: Todo-lijst
**Prioriteit:** v1 — Hoog
**Persona:** Lars (snelle vastlegging), Dina (losse klantnotities)
**Omschrijving:**
Een snelle todo-lijst voor losse taken die (nog) niet aan een product of Sprint gekoppeld zijn. Todo-items kunnen worden afgevinkt en gepromoveerd naar een PBI of story in een bestaand product.
**Acceptatiecriteria:**
- [ ] Todo aanmaken via snel-invoerveld (Enter om op te slaan); alleen titel verplicht
- [ ] Todo aanmaken ook mogelijk via REST API: `POST /api/todos` (body: `{ "title": string }`) — zodat Claude Code losse bevindingen kan vastleggen zonder de planningsflow te onderbreken
- [ ] Todo-lijst is zichtbaar als apart scherm of persistent zijpaneel
- [ ] Todo afvinken markeert het als afgerond (visueel doorgestreept)
- [ ] Afgevinkte todo's blijven zichtbaar; kunnen worden gearchiveerd via "Archiveer afgeronde items"
- [ ] Todo promoveren naar PBI: dialoog vraagt om product en prioriteit; todo verdwijnt na promotie
- [ ] Todo promoveren naar story: dialoog vraagt om product, PBI en prioriteit; todo verdwijnt na promotie
- [ ] Titel van het todo-item is vooringevuld in de promotiedialoog (bewerkbaar)
- [ ] Promotie is niet ongedaan te maken; dialoog waarschuwt hiervoor
**Randgevallen:**
- Geen producten aangemaakt → promotie-dialoog toont melding "Maak eerst een product aan"
- Promoveren naar story zonder PBI's in het product → dialoog toont melding "Maak eerst een PBI aan"
**Data:**
- Opgeslagen: `todos` (id, user_id, title, done, archived, created_at, updated_at)
---
### F-09: Sprint aanmaken en beheren
**Prioriteit:** v1 — Hoog
**Persona:** Lars, Dina, Remi
**Omschrijving:**
Het Scrum Team kan een Sprint aanmaken met een Sprint Goal. Per product kan er één actieve Sprint zijn. Stories worden via een gesplitst scherm vanuit de Product Backlog naar de Sprint Backlog gesleept.
**Acceptatiecriteria:**
- [ ] Sprint aanmaken vereist een Sprint Goal (verplicht, max. 500 tekens)
- [ ] Sprint is gekoppeld aan een product
- [ ] Er kan maar één actieve Sprint per product tegelijk zijn
- [ ] Sprint Backlog scherm is gesplitst: Sprint Backlog links, stories per PBI rechts
- [ ] Rechterpaneel toont alle PBI's inklapbaar, met hun stories eronder
- [ ] Stories die al in de Sprint zitten zijn visueel gemarkeerd en niet opnieuw sleepbaar
- [ ] Story naar Sprint slepen via drag-and-drop (dnd-kit) van rechts naar links
- [ ] Story in de Sprint Backlog is herrangschikbaar via drag-and-drop
- [ ] Story uit Sprint verwijderen via contextmenu of verwijderknop → story keert terug in Product Backlog
- [ ] Sprint Goal is bewerkbaar na aanmaken
- [ ] Sprint afronden zet alle stories op DONE of terug op OPEN (keuze per story in afronden-dialoog)
**Randgevallen:**
- Gebruiker probeert tweede Sprint aan te maken terwijl er al een actieve Sprint is → foutmelding met link naar actieve Sprint
- Story wordt uit Sprint verwijderd terwijl er taken aan hangen → taken blijven bestaan maar worden losgekoppeld van de Sprint
**Data:**
- Opgeslagen: `sprints` (id, product_id, sprint_goal, status (ACTIVE | COMPLETED), created_at, completed_at?)
---
### F-10: Sprint Planning — taken aanmaken
**Prioriteit:** v1 — Hoog
**Persona:** Lars, Remi
**Omschrijving:**
In het Sprint Planning scherm worden stories uit de Sprint Backlog opgedeeld in taken. Het scherm is gesplitst: stories links, taken van de geselecteerde story rechts. Taken kunnen worden geprioriteerd en gerangschikt via drag-and-drop.
**Acceptatiecriteria:**
- [ ] Sprint Planning scherm toont Sprint Backlog stories links in volgorde
- [ ] Selecteren van een story links toont de bijbehorende taken rechts
- [ ] Taak aanmaken vereist een titel (verplicht, max. 200 tekens)
- [ ] Omschrijving is optioneel (max. 1000 tekens)
- [ ] Prioriteit is verplicht (14)
- [ ] Taken zijn gerangschikt op prioriteit en volgorde; volgorde instelbaar via drag-and-drop (dnd-kit)
- [ ] Taakstatus is instelbaar via de UI: TO_DO | IN_PROGRESS | DONE
- [ ] Story toont een voortgangsindicator (bijv. "2/5 taken Done")
- [ ] Taak verwijderen vereist bevestiging
**Randgevallen:**
- Story heeft geen taken → lege staat rechts met prompt om eerste taak aan te maken
- Alle taken van een story zijn Done → story-voortgang toont 100% maar story-status wijzigt niet automatisch
**Data:**
- Opgeslagen: `tasks` (id, story_id, sprint_id, title, description, priority (14), sort_order, status (TO_DO | IN_PROGRESS | DONE), created_at, updated_at)
---
### F-11: Claude Code REST API
**Prioriteit:** v1 — Kern-differentiator
**Persona:** Lars
**Omschrijving:**
Een REST API waarmee Claude Code stories en taken kan ophalen, de taakvolgorde kan beoordelen en aanpassen, en implementatieplannen, testresultaten en commits kan vastleggen. Alle endpoints zijn beveiligd via een API-token.
**Acceptatiecriteria:**
**Endpoints:**
- [ ] `GET /api/products` — lijst van actieve producten
- [ ] `GET /api/products/:id/next-story` — hoogst geprioriteerde open story van de actieve Sprint
- [ ] `GET /api/sprints/:id/tasks?limit=10` — eerste N taken in huidige volgorde
- [ ] `PATCH /api/stories/:id/tasks/reorder` — accepteert geordende lijst van taak-id's
- [ ] `POST /api/stories/:id/log` — vastleggen van implementatieplan, testresultaat of commit
- [ ] `PATCH /api/tasks/:id` — status bijwerken (TO_DO → IN_PROGRESS → DONE)
- [ ] `POST /api/todos` — todo aanmaken vanuit Claude Code (body: `{ "title": string }`)
**Authenticatie:**
- [ ] Alle endpoints vereisen `Authorization: Bearer <token>` header
- [ ] Ontbrekend of ongeldig token geeft 401 Unauthorized
- [ ] Demo-gebruiker API-token geeft 403 op alle schrijf-endpoints
**Responsformaat:**
- [ ] Alle responses zijn JSON
- [ ] Foutresponses bevatten `{ "error": "omschrijving" }`
- [ ] `GET /api/products/:id/next-story` geeft 404 als er geen open stories zijn
**Data:**
- Opgeslagen: `api_tokens` (id, user_id, token_hash, label, created_at, revoked_at?)
---
### F-12: API-tokenbeheer
**Prioriteit:** v1 — Vereiste voor Claude Code-integratie
**Persona:** Lars
**Omschrijving:**
Gebruikers kunnen API-tokens aanmaken, labelen en intrekken via de instellingenpagina. Een token wordt eenmalig in klare tekst getoond en daarna niet meer.
**Acceptatiecriteria:**
- [ ] Gebruiker kan een token aanmaken met een optioneel label (bijv. "Claude Code — laptop")
- [ ] Token wordt eenmalig getoond na aanmaken in een kopieerbaar veld
- [ ] Na sluiten van het dialoog is de tokennwaarde niet meer opvraagbaar
- [ ] Tokenoverzicht toont: label, aanmaakdatum, status (actief / ingetrokken)
- [ ] Gebruiker kan een token intrekken; ingetrokken tokens werken direct niet meer
- [ ] Maximaal 10 actieve tokens per gebruiker
**Randgevallen:**
- Gebruiker sluit aanmaakmeldingsdialoog per ongeluk → token bestaat al, maar waarde is verloren; gebruiker moet een nieuw token aanmaken
---
### F-13: Lokale en cloud deployment
**Prioriteit:** v1 — Kritiek
**Persona:** Lars (lokaal, geen vendor lock-in), Dina (cloud)
**Omschrijving:**
De app is deployable op Vercel + Neon PostgreSQL én volledig lokaal draaibaar met SQLite of lokale PostgreSQL. Eén codebase, database-adapter als abstractielaag via Prisma.
**Acceptatiecriteria:**
- [ ] `npm run dev` start de app lokaal zonder externe accounts
- [ ] Lokale modus gebruikt SQLite als standaard database (via Prisma)
- [ ] `DATABASE_URL` in `.env.local` schakelt tussen lokale en cloud database
- [ ] `npx prisma db push` initialiseert het schema in beide modi
- [ ] `next build` slaagt voor Vercel-deployment zonder aanpassingen
- [ ] `.env.example` documenteert alle vereiste en optionele environment variables
- [ ] README bevat stap-voor-stap instructies voor zowel lokale als cloud setup
---
## Navigatiestructuur
```
/ (redirect naar /login als niet ingelogd)
/login
/register
/dashboard (productenlijst)
/products/new (product aanmaken)
/products/:id (Product Backlog — gesplitst scherm)
/products/:id/sprint (Sprint Backlog — gesplitst scherm)
/products/:id/sprint/planning (Sprint Planning — gesplitst scherm)
/todos (todo-lijst)
/settings (profiel, rollen, API-tokens)
/settings/tokens (API-tokenbeheer)
```
---
## Datamodel (schets)
| Entiteit | Sleutelvelden | Relaties / opmerkingen |
|---|---|---|
| `users` | id, username, password_hash, is_demo, created_at | Basis authenticatie-entiteit |
| `user_roles` | id, user_id, role (enum) | Meervoudige rollen per gebruiker |
| `api_tokens` | id, user_id, token_hash, label, revoked_at | Max. 10 actief per gebruiker |
| `products` | id, user_id, name, description, repo_url, definition_of_done, archived | Hoogste niveau in de hiërarchie |
| `pbis` | id, product_id, title, description, priority (14), sort_order | Geordend binnen prioriteitsgroep |
| `stories` | id, pbi_id, product_id, title, description, acceptance_criteria, priority, sort_order, status, sprint_id? | Status: OPEN / IN_SPRINT / DONE |
| `story_logs` | id, story_id, type, content, status?, commit_hash?, commit_message?, created_at | Aangemaakt via API; read-only in UI |
| `sprints` | id, product_id, sprint_goal, status (ACTIVE / COMPLETED), created_at, completed_at? | Max. 1 actieve Sprint per product |
| `tasks` | id, story_id, sprint_id, title, description, priority, sort_order, status | Status: TO_DO / IN_PROGRESS / DONE |
| `todos` | id, user_id, title, done, archived, created_at | Ontkoppeld van producthiërarchie |
---
## Niet-functionele vereisten
| Vereiste | Waarde |
|---|---|
| Platform | Desktop-first web (minimale breedte 1024px); toekomstige mobiele variant beperkt tot taken afvinken |
| Authenticatie | Gebruikersnaam + wachtwoord; geen e-mailverificatie in v1 |
| Drag-and-drop | dnd-kit (useDraggable / useDroppable); 60fps vereist bij lijsten tot 100 items |
| API-beveiliging | Bearer token op alle /api/* routes |
| Offline support | Niet vereist voor v1 |
| Taal | Nederlands voor UI-teksten; Engels voor code, API en technische documentatie |
| Toegankelijkheid | Keyboard-navigatie voor alle primaire acties; WCAG AA voor de sprint planning flow |
| Database | Prisma ORM; PostgreSQL (cloud) of SQLite (lokaal) |
| Deployment | Vercel (cloud) of lokaal via `npm run dev` |
| Sessiebeheer | Server-side sessie of JWT; geen opslag van gevoelige data in localStorage |
---
## Sleutel-user-flows
### Flow 1: Lars start zijn avond
**Startpunt:** Dashboard (na inloggen)
1. Lars ziet zijn productenlijst — vier producten, elk met naam en beschrijving
2. Hij klikt op "Factuur-tool"
3. Product Backlog opent — PBI's links, stories rechts
4. Hij ziet drie open PBI's; klikt op "Betalingsverwerking"
5. Vijf stories verschijnen rechts als blokken; twee hebben badge "In Sprint"
6. Hij navigeert naar Sprint Planning
7. Selecteert een story links, ziet de bijbehorende taken rechts
8. Maakt een nieuwe taak aan: "Stripe webhook valideren" → prioriteit 1
9. Opent terminal, start Claude Code
10. Claude haalt de taak op via API, stelt implementatieplan op
11. Plan verschijnt direct in de story-activiteitenlog in de app
**Resultaat:** Lars heeft in minder dan vijf minuten overzicht en Claude Code is aan het werk.
---
### Flow 2: Claude Code volledige cyclus
**Startpunt:** Claude Code in terminal
1. `claude devplanner next` — haalt hoogst geprioriteerde open story op via `GET /api/products/:id/next-story`
2. Claude toont storytitel en acceptatiecriteria
3. Claude haalt eerste 10 taken op via `GET /api/sprints/:id/tasks?limit=10`
4. Claude beoordeelt volgorde en past aan via `PATCH /api/stories/:id/tasks/reorder`
5. Claude stelt implementatieplan op → `POST /api/stories/:id/log` (type: IMPLEMENTATION_PLAN)
6. Claude voert implementatie uit
7. Claude draait tests → `POST /api/stories/:id/log` (type: TEST_RESULT, status: PASSED)
8. Claude maakt commit → `POST /api/stories/:id/log` (type: COMMIT, hash, message)
9. Lars opent de story in de app — ziet plan, testresultaat en commit-hash in de activiteitenlog
10. Lars zet de story handmatig op DONE via de UI
**Resultaat:** Volledige traceerbaarheid van beslissing tot commit, zonder extra handmatige invoer.
---
### Flow 3: Todo promoveren naar story
**Startpunt:** Todo-lijst
1. Lars heeft een todo: "Voeg rate limiting toe aan de API"
2. Hij klikt op "Promoveren → Story"
3. Dialoog opent: product (vooringevuld met laatste product), PBI (dropdown), prioriteit
4. Hij kiest product "Factuur-tool", PBI "Beveiliging", prioriteit 2
5. Bevestigen → todo verdwijnt, story is aangemaakt
6. Lars navigeert naar de Product Backlog → story staat in de juiste prioriteitsgroep
**Resultaat:** Losse gedachte is in drie stappen onderdeel van de formele Product Backlog.

137
docs/scrum4me-personas.md Normal file
View file

@ -0,0 +1,137 @@
# DevPlanner — User Personas
**Versie:** 0.1 — april 2026
**Volgt op:** Concept & Feature Brainstorm v0.3
---
## Persona-overzicht
| Naam | Leeftijd | Situatie | Primaire behoefte |
|---|---|---|---|
| Lars | 34 | Solo developer, 4 eigen SaaS-projecten naast zijn dagtaak | Overzicht houden zonder planningssysteem te worden |
| Dina | 28 | Freelance developer, werkt voor 3 klanten tegelijk | Klantprojecten gescheiden houden en voortgang aantonen |
| Remi | 41 | Lead van een klein team (3 personen), geen dedicated PM | Scrum licht toepassen zonder Jira-overhead |
---
## Lars
**Leeftijd:** 34 — **Situatie:** Solo developer met 4 actieve side projects naast een fulltime baan
### Achtergrond
Lars werkt overdag als backend developer bij een middelgroot bedrijf en bouwt 's avonds en in het weekend aan zijn eigen projecten: een SaaS-tool voor factuurverwerking, een open-source CLI-utility, een persoonlijk dashboard en een experimenteel AI-project. Hij werkt alleen, heeft geen teamleden en geen deadlines die van buitenaf worden opgelegd. Alles loopt via zijn hoofd of verspreide markdown-bestanden in de repositories zelf.
### Een typische slechte avond
Hij opent zijn laptop na het werk en weet niet meer waar hij gebleven was. In welk project zat hij? Wat stond er open? Hij herinnert zich dat er vorige week een bug was gerapporteerd in de factuur-tool, maar hij weet niet meer of hij die heeft gefixt of alleen heeft opgeschreven. Hij besteedt twintig minuten aan het doorzoeken van commit-logs en README's voordat hij kan beginnen. Als Claude Code dan eindelijk een taak oppakt, is de context al kwijt.
### Hoe hij het nu oplost
Elke repository heeft een `TODO.md` die hij bijhoudt zolang een project actief is. Als hij een week niet heeft gekeken, is het bestand achterhaald. Prioritering is impliciet — hij werkt aan wat op dat moment het interessantst voelt, niet aan wat het belangrijkst is. Jira heeft hij één keer geprobeerd voor zijn side projects: duurde twee uur om in te richten en stopte na drie weken.
### Doelen met deze app
- Elke avond binnen één minuut weten welke taak het meest urgent is per project
- Claude Code laten oppakken wat open staat zonder zelf de context te hoeven herstellen
- Achteraf kunnen zien wat er gedaan is en hoe (implementatieplan, commit, testresultaat)
### Frustraties om te vermijden
- Verplichte velden en formulieren die langer duren dan de taak zelf
- Systemen die vragen om updates die hij toch niet bijhoudt (daily standup-achtige inputs)
- Alles wat aanvoelt als "project management voor grote teams" — hij is de enige gebruiker
### Relatie met technologie
Power user. Leeft in de terminal, gebruikt Claude Code dagelijks, bouwt zijn eigen tooling als iets hem niet bevalt. Wil een app die hij ook via een API kan aansturen.
---
## Dina
**Leeftijd:** 28 — **Situatie:** Freelance full-stack developer, werkt parallel voor drie klanten
### Achtergrond
Dina werkt als zelfstandige en heeft op dit moment drie lopende klantprojecten: een e-commerceplatform in onderhoudsfase, een nieuw admin-dashboard voor een logistiek bedrijf en een kleine MVP voor een startup. Elk project heeft zijn eigen ritme, zijn eigen repo en zijn eigen verwachtingen. Ze werkt alleen maar heeft per klant een contactpersoon die wil weten wat er gedaan is.
### Een typische slechte dag
Ze heeft drie context-switches voor de lunch. Het logistiek-dashboard heeft een bugmelding binnengekomen, de startup wil een update, en de e-commerce klant heeft een vraag over iets wat twee Sprints geleden geleverd is. Ze heeft geen centraal overzicht — ze zoekt in Slack-threads, e-mails en commit-logs om te reconstrueren wat er wanneer gedaan is. Aan het eind van de dag heeft ze productief werk gedaan maar kan ze niet meer zeggen wat precies.
### Hoe ze het nu oplost
Elk klantproject heeft een Notion-pagina met een ruwe takenlijst. Notion is flexible maar heeft geen structuur die Scrum-begrippen begrijpt. Ze heeft geprobeerd Trello te gebruiken maar het mist de hiërarchie die ze nodig heeft (project → feature → taak). Ze voelt dat ze iets robuusters nodig heeft nu het derde project erbij is gekomen.
### Doelen met deze app
- Per klant een gestructureerde Product Backlog bijhouden zonder overlap
- Snel kunnen antwoorden op "wat heb je de afgelopen week gedaan?" met concrete verwijzingen naar commits en stories
- Claude Code gebruiken voor routinetaken zodat zij zich kan richten op complexere beslissingen
### Frustraties om te vermijden
- Systemen waarbij klantdata vermengd raakt (ze wil strikte projectscheiding)
- Dashboards die statistieken tonen die ze niet nodig heeft (velocity, burndown)
- Alles wat ze verplicht dagelijks bij te houden — ze heeft soms een dag vrij of ziek
### Relatie met technologie
Comfortabel maar niet fanatiek. Gebruikt VS Code, GitHub en een handvol SaaS-tools. Geen terminal-purist zoals Lars — ze gebruikt liever een goede UI dan een CLI als beide beschikbaar zijn.
---
## Remi
**Leeftijd:** 41 — **Situatie:** Lead developer van een team van drie, zonder dedicated projectmanager
### Achtergrond
Remi werkt bij een klein softwarebedrijf met in totaal acht mensen. Zijn team bestaat uit hemzelf en twee juniordevelopers. Ze bouwen interne tools voor twee afdelingen en onderhouden drie legacy-systemen. Er is geen Scrum Master, geen Product Owner en geen projectmanager — Remi doet dat er allemaal bij. Hij heeft Scrum gelezen en wil het toepassen, maar Jira is te zwaar en te duur voor hun schaal. Ze werken nu met een gedeeld Excel-bestand dat niemand consequent bijhoudt.
### Een typische slechte week
Ze beginnen maandagochtend zonder duidelijk Sprint-doel. Iedereen werkt aan wat binnenkomt. Woensdag vraagt zijn manager naar de status van het rapportage-dashboard. Remi weet dat er aan gewerkt is maar niet hoe ver het is. Hij moet twee junioren ondervragen en drie feature-branches bekijken om een antwoord te kunnen geven. Vrijdagmiddag hebben ze een "retrospective" die eigenlijk een statusmeeting is.
### Hoe hij het nu oplost
Excel voor taakregistratie, Teams-kanalen per project voor communicatie, GitHub Issues voor bugs. Drie systemen die niet met elkaar praten. Hij heeft Linear geprobeerd maar de leercurve was te groot voor de junioren. Hij zoekt iets dat hij in een middag kan inrichten en dat zijn teamleden zonder training kunnen gebruiken.
### Doelen met deze app
- Elke Sprint starten met een duidelijk Sprint Goal dat iedereen ziet
- Wekelijks in één scherm kunnen zien wat open staat, wat in progress is en wat klaar is
- In de toekomst rollen kunnen toewijzen aan teamleden zodat de Product Owner (hijzelf) en de Developers (de junioren) gescheiden rechten hebben
### Frustraties om te vermijden
- Alles wat de junioren extra werk geeft zonder directe waarde voor hen
- Rapportages en grafieken die hij niet nodig heeft
- Systemen waarbij hij afhankelijk is van een externe SaaS die duur wordt bij groei
### Relatie met technologie
Ervaren developer, maar kiest bewust voor eenvoud. Wil een tool die hij zelf kan hosten als dat goedkoper uitpakt. Waardeert open source maar heeft geen tijd om een tool te onderhouden die hij zelf gebouwd heeft.
---
## Persona-spanningen
| Spanning | Lars wil | Dina wil | Remi wil | Oplossing |
|---|---|---|---|---|
| API vs. UI | Alles via API en Claude Code | Goede UI, API is bonus | UI eerst, team moet het kunnen gebruiken | UI is primair; API is volwaardige burgerklasse, niet bijzaak |
| Eén vs. meerdere gebruikers | Altijd solo | Solo maar met klantcontext | Team van 3 met rolscheiding | v1 is solo; rolbeheer is v1-fundament voor v2-teamuitbreiding |
| Scrum-striktheid | Licht, pragmatisch | Structuur helpt maar geen dogma | Wil Scrum leren toepassen | Scrum-terminologie consistent; events zijn optioneel, niet verplicht |
| Zichtbaarheid voortgang | Eigen overzicht, geen rapportages | Klantverantwoording via commits | Teamstatus in één scherm | Activiteitenlog per story volstaat voor alle drie; aparte rapportagelaag is v2 |
---
## Primaire persona
**Lars** is de primaire designtarget voor v1.
Rationale: Lars vertegenwoordigt de meest veeleisende gebruiker in termen van API-integratie en Claude Code-koppeling — de kern-differentiator van DevPlanner. Als de app goed werkt voor Lars (solo, API-gedreven, meerdere projecten, minimale overhead), werkt het ook voor Dina (zelfde gebruik, lichtere techvoorkeur). Remi's behoeften — teamgebruik en rolscheiding — zijn bewust naar v2 verschoven; het fundament (rolmodel in de datastructuur) wordt in v1 al gelegd.
Een feature die Lars zou doen stoppen — verplichte velden, trage UI, geen API — wordt niet gebouwd, ook niet als Remi er baat bij zou hebben.

View file

@ -0,0 +1,454 @@
# DevPlanner — Product Backlog
**Versie:** 0.1 — april 2026
**Product:** DevPlanner
**Beschrijving:** Een lichtgewicht Scrum-gebaseerde projectplanner voor solo developers en kleine Scrum Teams die meerdere softwareprojecten parallel beheren. De app organiseert werk hiërarchisch (product → PBI → story → taak), biedt een visuele planningslaag en integreert met Claude Code voor geautomatiseerde implementatieflows.
**Git repo:** https://github.com/devplanner/devplanner
**Definition of Done:** Feature is geïmplementeerd, getest (unit + integratie), gedocumenteerd in code, en gedeployed naar de staging-omgeving zonder regressies.
---
## Prioriteiten
| Prioriteit | Betekenis |
|---|---|
| 1 — Kritiek | Blokkeert alle andere functionaliteit. Moet eerst. |
| 2 — Hoog | Core waarde van het product. MVP vereiste. |
| 3 — Middel | Verhoogt bruikbaarheid significant. v1 wenselijk. |
| 4 — Laag | Waardevol maar niet blokkerend. v2 kandidaat. |
---
## PBI-01 — Authenticatie & gebruikersbeheer
**Prioriteit:** 1 — Kritiek
**Omschrijving:** Het Scrum Team kan een account aanmaken en inloggen met gebruikersnaam en wachtwoord. Een demo-gebruiker heeft alleen leesrechten. Gebruikers kunnen één of meerdere Scrum-rollen aannemen.
### Stories
**S-01-01: Account aanmaken**
Als bezoeker wil ik een account aanmaken met gebruikersnaam en wachtwoord, zodat ik toegang krijg tot de app.
Acceptatiecriteria:
- Gebruikersnaam en wachtwoord zijn verplicht
- Gebruikersnaam is uniek; dubbele aanmelding geeft foutmelding
- Wachtwoord heeft minimaal 8 tekens
- Na aanmaken wordt de gebruiker direct ingelogd
- Geen e-mailverificatie vereist in v1
**S-01-02: Inloggen**
Als geregistreerde gebruiker wil ik inloggen met gebruikersnaam en wachtwoord, zodat ik mijn projecten kan beheren.
Acceptatiecriteria:
- Incorrecte combinatie geeft generieke foutmelding (geen onderscheid gebruikersnaam/wachtwoord)
- Na inloggen wordt de gebruiker doorgestuurd naar het dashboard
- Sessie blijft actief totdat de gebruiker uitlogt
**S-01-03: Uitloggen**
Als ingelogde gebruiker wil ik kunnen uitloggen, zodat mijn sessie veilig afgesloten wordt.
Acceptatiecriteria:
- Uitlogknop altijd zichtbaar in de navigatie
- Na uitloggen wordt de gebruiker naar de loginpagina gestuurd
- Sessiedata wordt gewist
**S-01-04: Demo-gebruiker (read-only)**
Als bezoeker wil ik kunnen inloggen als demo-gebruiker, zodat ik de app kan verkennen zonder een account aan te maken.
Acceptatiecriteria:
- Vaste inloggegevens voor de demo-gebruiker zijn beschikbaar op de loginpagina
- Demo-gebruiker ziet alle data maar kan niets aanmaken, aanpassen of verwijderen
- Alle actieknoppen (aanmaken, bewerken, verwijderen) zijn zichtbaar maar uitgeschakeld met tooltip "Niet beschikbaar in demo-modus"
- Demo-gebruiker kan niet van rol wisselen
**S-01-05: Roltoewijzing**
Als gebruiker wil ik één of meerdere Scrum-rollen kunnen aannemen (Product Owner, Scrum Master, Developer), zodat de app weet in welke context ik werk.
Acceptatiecriteria:
- Gebruiker kan bij registratie of in instellingen rollen selecteren
- Minimaal één rol is verplicht
- Alle drie de rollen tegelijk zijn toegestaan
- Rolkeuze is zichtbaar in de navigatie/profielbalk
- Rolkeuze heeft in v1 geen effect op zichtbare functionaliteit (voorbereiding op v2)
---
## PBI-02 — Productbeheer
**Prioriteit:** 1 — Kritiek
**Omschrijving:** Het Scrum Team kan producten aanmaken, bekijken, bewerken en archiveren. Een product heeft een naam, beschrijving en koppeling naar een git-repository.
### Stories
**S-02-01: Product aanmaken**
Als Product Owner wil ik een nieuw product aanmaken met naam, beschrijving en git-repo URL, zodat ik een werkruimte heb voor de Product Backlog.
Acceptatiecriteria:
- Naam is verplicht en uniek per gebruiker
- Beschrijving is optioneel (vrije tekst)
- Git-repo URL is optioneel maar wordt gevalideerd als geldige URL
- Product is direct zichtbaar in de productenlijst na aanmaken
**S-02-02: Product bewerken**
Als Product Owner wil ik de naam, beschrijving en git-repo URL van een product kunnen aanpassen, zodat de informatie actueel blijft.
Acceptatiecriteria:
- Alle velden zijn bewerkbaar
- Wijzigingen worden opgeslagen zonder de pagina te verlaten
- Lege naam geeft validatiefout
**S-02-03: Product archiveren**
Als Product Owner wil ik een product kunnen archiveren, zodat het niet meer in het overzicht verschijnt maar de data bewaard blijft.
Acceptatiecriteria:
- Gearchiveerde producten verschijnen niet in de standaardlijst
- Er is een optie om gearchiveerde producten te tonen
- Archiveren is omkeerbaar (product kan worden hersteld)
**S-02-04: Productenlijst bekijken**
Als gebruiker wil ik een overzicht zien van alle actieve producten, zodat ik snel naar het juiste product kan navigeren.
Acceptatiecriteria:
- Lijst toont naam, beschrijving (ingekort) en git-repo link
- Klikken op een product opent de Product Backlog van dat product
- Lege staat toont een prompt om een product aan te maken
---
## PBI-03 — Product Backlog
**Prioriteit:** 1 — Kritiek
**Omschrijving:** Het Scrum Team kan de Product Backlog beheren via een gesplitst scherm: links de PBI's, rechts de bijbehorende stories. Items kunnen aangemaakt, bewerkt, geprioriteerd en gerangschikt worden via drag-and-drop (dnd-kit).
### Stories
**S-03-01: PBI aanmaken**
Als Product Owner wil ik een PBI aanmaken in de Product Backlog, zodat ik nieuwe functionaliteit kan definiëren.
Acceptatiecriteria:
- PBI heeft een titel (verplicht) en omschrijving (optioneel)
- PBI krijgt een prioriteit (1 t/m 4)
- Nieuw PBI verschijnt onderaan de lijst voor de gekozen prioriteit
- Aanmaken via knop in de navigatiebar van het linkerpaneel
**S-03-02: PBI bewerken**
Als Product Owner wil ik de titel, omschrijving en prioriteit van een PBI kunnen aanpassen.
Acceptatiecriteria:
- Dubbelklikken of via contextmenu opent bewerkingsmodus
- Alle velden zijn inline bewerkbaar
- Prioriteitswijziging herplaatst het PBI visueel
**S-03-03: PBI verwijderen**
Als Product Owner wil ik een PBI kunnen verwijderen, zodat irrelevante items de backlog niet vervuilen.
Acceptatiecriteria:
- Verwijderen vereist bevestiging
- Verwijderen van een PBI verwijdert ook alle bijbehorende stories (cascade)
- Actie is niet ongedaan te maken; bevestigingsdialoog waarschuwt hiervoor
**S-03-04: PBI prioriteit instellen**
Als Product Owner wil ik per PBI een prioriteit kunnen instellen (1 t/m 4), zodat de volgorde van de backlog de businesswaarde weerspiegelt.
Acceptatiecriteria:
- Prioriteit is instelbaar via dropdown of inline label
- PBI's worden gegroepeerd per prioriteit in de lijst
- Visuele scheiding per prioriteitsgroep (kleurband of scheidingslijn)
**S-03-05: PBI volgorde aanpassen via drag-and-drop**
Als Product Owner wil ik de volgorde van PBI's binnen dezelfde prioriteit kunnen aanpassen via drag-and-drop, zodat ik fijnmazige prioritering kan doen.
Acceptatiecriteria:
- Drag-and-drop werkt vloeiend (60fps) via dnd-kit
- Volgorde wordt direct opgeslagen na loslaten
- Drag over prioriteitsgrens wisselt de prioriteit van het PBI
- Visuele placeholder toont de doelpositie tijdens het slepen
**S-03-06: PBI filteren**
Als gebruiker wil ik PBI's kunnen filteren op prioriteit of status, zodat ik me kan focussen op het relevante werk.
Acceptatiecriteria:
- Filteropties beschikbaar in de navigatiebar van het linkerpaneel
- Filter werkt realtime (geen herlaadactie)
- Actief filter is duidelijk zichtbaar; eenvoudig te wissen
**S-03-07: Gesplitst scherm Product Backlog**
Als gebruiker wil ik de Product Backlog bekijken als gesplitst scherm (PBI's links, stories rechts), zodat ik snel kan navigeren tussen PBI's en hun stories.
Acceptatiecriteria:
- Scherm is standaard 50/50 verdeeld
- De splitter is horizontaal versleepbaar
- Elk paneel heeft een eigen navigatiebar met acties
- Selecteren van een PBI links toont de bijbehorende stories rechts
- Geselecteerde PBI is visueel gemarkeerd
---
## PBI-04 — Story-beheer
**Prioriteit:** 1 — Kritiek
**Omschrijving:** Stories kunnen worden aangemaakt, bewerkt, geprioriteerd en gerangschikt binnen een PBI. Stories worden weergegeven als blokken van circa 10% schermbreedte, gerangschikt op prioriteit.
### Stories
**S-04-01: Story aanmaken**
Als Product Owner wil ik een story aanmaken binnen een PBI, zodat ik de functionaliteit kan uitwerken in uitvoerbare eenheden.
Acceptatiecriteria:
- Story heeft een titel (verplicht), omschrijving (optioneel) en prioriteit
- Aanmaken via navigatiebar van het rechterpaneel
- Nieuwe story verschijnt als blok rechts, in de juiste prioriteitsgroep
**S-04-02: Story weergave als blokken**
Als gebruiker wil ik stories zien als compacte blokken (~10% schermbreedte), zodat ik snel een overzicht heb van alle stories per PBI.
Acceptatiecriteria:
- Elk blok toont: storytitel, prioriteit, status
- Blokken zijn gerangschikt op prioriteit (hoog naar laag, links naar rechts)
- Elke nieuwe prioriteitsgroep heeft een visuele scheiding (kleurband of lijn)
- Blokken zijn klikbaar voor detail/bewerking
**S-04-03: Story prioriteit instellen**
Als Product Owner wil ik per story een prioriteit instellen, zodat de Developer weet wat als eerste opgepakt moet worden.
Acceptatiecriteria:
- Prioriteit instelbaar via het storyblok (dropdown of label)
- Prioriteitswijziging herplaatst het blok in de juiste groep
**S-04-04: Story volgorde aanpassen via drag-and-drop**
Als Product Owner wil ik de volgorde van stories binnen dezelfde prioriteit aanpassen via drag-and-drop, zodat ik de uitvoeringsvolgorde kan finetunen.
Acceptatiecriteria:
- Drag-and-drop werkt via dnd-kit tussen en binnen prioriteitsgroepen
- Volgorde wordt direct opgeslagen
- Slepen over een prioriteitsgrens wijzigt de prioriteit
**S-04-05: Story bewerken**
Als Product Owner wil ik de titel, omschrijving en prioriteit van een story kunnen aanpassen.
Acceptatiecriteria:
- Bewerkbaar via klikken op het storyblok
- Wijzigingen opgeslagen zonder paginaverversing
**S-04-06: Story verwijderen**
Als Product Owner wil ik een story kunnen verwijderen.
Acceptatiecriteria:
- Verwijderen vereist bevestiging
- Cascade verwijdering van gekoppelde taken
- Niet ongedaan te maken; waarschuwing in dialoog
---
## PBI-05 — Todo-lijst
**Prioriteit:** 2 — Hoog
**Omschrijving:** Gebruikers kunnen een snelle todo-lijst bijhouden voor ongeplande of kortstondige taken. Een todo-item kan worden gepromoveerd naar een PBI of story.
### Stories
**S-05-01: Todo-item aanmaken**
Als gebruiker wil ik snel een todo-item aanmaken zonder het aan een product te koppelen, zodat ik losse gedachten kan vastleggen zonder de planningsflow te onderbreken.
Acceptatiecriteria:
- Todo heeft alleen een titel (verplicht)
- Aanmaken via een snel-invoerveld (Enter om op te slaan)
- Todo's zijn zichtbaar in een aparte todo-sectie of zijpaneel
**S-05-02: Todo-item afvinken**
Als gebruiker wil ik een todo-item kunnen afvinken, zodat ik bij kan houden wat klaar is.
Acceptatiecriteria:
- Afgevinkte items zijn visueel doorgestreept
- Afgevinkte items blijven zichtbaar maar kunnen worden gearchiveerd
**S-05-03: Todo promoveren naar PBI**
Als Product Owner wil ik een todo-item promoveren naar een PBI in een bestaand product, zodat losse ideeën in de formele backlog terechtkomen.
Acceptatiecriteria:
- Promoten opent een dialoog om product en prioriteit te kiezen
- Het todo-item wordt omgezet naar een PBI en verdwijnt uit de todo-lijst
- De PBI-titel is gelijk aan de todo-titel (bewerkbaar in dialoog)
**S-05-04: Todo promoveren naar story**
Als Product Owner wil ik een todo-item promoveren naar een story binnen een bestaand PBI, zodat ik snel nieuwe stories kan toevoegen vanuit losse notities.
Acceptatiecriteria:
- Promoten opent een dialoog om product, PBI en prioriteit te kiezen
- Todo wordt omgezet naar een story en verdwijnt uit de todo-lijst
---
## PBI-06 — Sprint Backlog & Sprint Planning
**Prioriteit:** 2 — Hoog
**Omschrijving:** Het Scrum Team kan een Sprint aanmaken met een Sprint Goal, stories uit de Product Backlog naar de Sprint Backlog slepen, en de volgorde bepalen.
### Stories
**S-06-01: Sprint aanmaken**
Als Scrum Master wil ik een nieuwe Sprint aanmaken met een Sprint Goal, zodat het Scrum Team een duidelijk doel heeft voor de komende Sprint.
Acceptatiecriteria:
- Sprint heeft een Sprint Goal (verplicht, vrije tekst)
- Sprint is gekoppeld aan een product
- Er kan maar één actieve Sprint per product zijn
**S-06-02: Sprint Backlog scherm (gesplitst)**
Als gebruiker wil ik de Sprint Backlog kunnen beheren via een gesplitst scherm (Sprint Backlog links, stories per PBI rechts), zodat ik snel stories kan toevoegen aan de Sprint.
Acceptatiecriteria:
- Links: Sprint Backlog met geselecteerde stories in volgorde
- Rechts: stories uit de Product Backlog, gegroepeerd per PBI
- Splitter is horizontaal versleepbaar
- Elk paneel heeft eigen navigatiebar
**S-06-03: Story naar Sprint slepen**
Als Developer wil ik een story vanuit de Product Backlog naar de Sprint Backlog kunnen slepen, zodat ik bepaal wat we deze Sprint gaan oppakken.
Acceptatiecriteria:
- Drag-and-drop werkt via dnd-kit tussen rechterpaneel en linkerpaneel
- Story verschijnt in de Sprint Backlog op de gesleepte positie
- Story in de Product Backlog krijgt visuele markering "In Sprint"
- Een story kan maar aan één actieve Sprint gekoppeld zijn
**S-06-04: Volgorde stories in Sprint bepalen**
Als Developer wil ik de volgorde van stories in de Sprint Backlog kunnen aanpassen via drag-and-drop, zodat ik de uitvoeringsvolgorde bepaal.
Acceptatiecriteria:
- Drag-and-drop werkt binnen de Sprint Backlog
- Volgorde wordt direct opgeslagen
- Volgorde is onafhankelijk van de prioriteit in de Product Backlog
**S-06-05: Story uit Sprint verwijderen**
Als Developer wil ik een story uit de Sprint Backlog kunnen verwijderen (terugplaatsen in de Product Backlog), zodat we de Sprint scope kunnen aanpassen.
Acceptatiecriteria:
- Story verdwijnt uit de Sprint Backlog
- Story is weer beschikbaar in de Product Backlog
- Actie vereist geen bevestiging (is niet destructief)
---
## PBI-07 — Sprint Planning (taken per story)
**Prioriteit:** 2 — Hoog
**Omschrijving:** Tijdens Sprint Planning worden stories opgedeeld in taken. Hetzelfde gesplitste scherm wordt gebruikt: stories links, taken rechts. Taken kunnen geprioriteerd en gerangschikt worden.
### Stories
**S-07-01: Sprint Planning scherm**
Als Developer wil ik een Sprint Planning scherm zien met stories links en taken rechts, zodat ik per story taken kan aanmaken en rangschikken.
Acceptatiecriteria:
- Links: stories in de Sprint Backlog in volgorde
- Rechts: taken van de geselecteerde story
- Selecteren van een story links toont de bijbehorende taken rechts
- Gesplitst scherm is horizontaal versleepbaar
**S-07-02: Taak aanmaken**
Als Developer wil ik een taak aanmaken onder een story, zodat ik het uitvoerbare werk kan definiëren.
Acceptatiecriteria:
- Taak heeft een titel (verplicht), omschrijving (optioneel) en prioriteit
- Aanmaken via navigatiebar van het rechterpaneel
- Nieuwe taak verschijnt onderaan de takenlijst van de story
**S-07-03: Taak prioriteit instellen**
Als Developer wil ik per taak een prioriteit instellen, zodat de uitvoeringsvolgorde duidelijk is.
Acceptatiecriteria:
- Prioriteit instelbaar via taakregel (dropdown of label)
- Taken gegroepeerd en gerangschikt op prioriteit
**S-07-04: Taak volgorde aanpassen via drag-and-drop**
Als Developer wil ik de volgorde van taken binnen een story kunnen aanpassen via drag-and-drop, zodat de uitvoeringsvolgorde precies klopt.
Acceptatiecriteria:
- Drag-and-drop via dnd-kit binnen de takenlijst
- Volgorde direct opgeslagen na loslaten
**S-07-05: Taakstatus bijhouden**
Als Developer wil ik de status van een taak kunnen bijhouden (To Do, In Progress, Done), zodat de voortgang van de Sprint zichtbaar is.
Acceptatiecriteria:
- Status is instelbaar via de UI (dropdown of knoppen)
- Statuswijziging is direct zichtbaar in het Sprint Planning scherm
- Story toont een voortgangsindicator op basis van taakstatussen
---
## PBI-08 — Claude Code integratie
**Prioriteit:** 2 — Hoog
**Omschrijving:** Claude Code kan via een REST API (en later MCP) stories en taken ophalen, de volgorde beoordelen, een implementatieplan opstellen, tests uitvoeren en committen. Elk resultaat wordt vastgelegd in de story.
### Stories
**S-08-01: REST API — story ophalen**
Als Developer (via Claude Code) wil ik de hoogst geprioriteerde open story van een product kunnen ophalen via een API-endpoint, zodat Claude Code weet wat er gedaan moet worden.
Acceptatiecriteria:
- Endpoint: `GET /api/products/:id/next-story`
- Retourneert: story-id, titel, omschrijving, acceptatiecriteria, gekoppelde taken
- Authentiseerd via API-token
- Geeft 404 als er geen open stories zijn
**S-08-02: REST API — eerste 10 taken ophalen**
Als Developer (via Claude Code) wil ik de eerste 10 taken van de Sprint Backlog kunnen ophalen, zodat Claude Code de volgorde kan beoordelen en zo nodig aanpassen.
Acceptatiecriteria:
- Endpoint: `GET /api/sprints/:id/tasks?limit=10`
- Retourneert taken in huidige volgorde met id, titel, prioriteit, status
- Claude Code kan de volgorde aanpassen via een apart endpoint
**S-08-03: REST API — taakvolgorde aanpassen**
Als Developer (via Claude Code) wil ik de volgorde van taken kunnen aanpassen via de API, zodat Claude Code een optimale uitvoeringsvolgorde kan bepalen.
Acceptatiecriteria:
- Endpoint: `PATCH /api/stories/:id/tasks/reorder`
- Accepteert een geordende lijst van taak-id's
- Volgorde wordt direct weerspiegeld in de UI
**S-08-04: Implementatieplan vastleggen**
Als Developer (via Claude Code) wil ik een implementatieplan kunnen schrijven naar een story, zodat de ontwerpbeslissingen traceerbaar zijn.
Acceptatiecriteria:
- Endpoint: `POST /api/stories/:id/log`
- Veld: `type: "implementation_plan"`, `content: string`
- Log-entry verschijnt in de story-activiteitenlog in de UI
**S-08-05: Teststatus vastleggen**
Als Developer (via Claude Code) wil ik de uitkomst van testruns kunnen vastleggen in een story, zodat kwaliteitsbewijs per story bewaard blijft.
Acceptatiecriteria:
- Endpoint: `POST /api/stories/:id/log`
- Veld: `type: "test_result"`, `content: string`, `status: "passed" | "failed"`
- Teststatus zichtbaar in de story-activiteitenlog
**S-08-06: Commit-hash vastleggen**
Als Developer (via Claude Code) wil ik de commit-hash na een succesvolle commit kunnen vastleggen in een story, zodat code en planning direct gekoppeld zijn.
Acceptatiecriteria:
- Endpoint: `POST /api/stories/:id/log`
- Veld: `type: "commit"`, `hash: string`, `message: string`
- Commit-hash is klikbaar en linkt naar de git-repo (indien geconfigureerd)
**S-08-07: Story activiteitenlog in UI**
Als gebruiker wil ik per story een activiteitenlog zien met alle vastgelegde implementatieplannen, testresultaten en commits, zodat ik de volledige uitvoeringsgeschiedenis op één plek heb.
Acceptatiecriteria:
- Log toont alle entries in chronologische volgorde
- Elk type entry heeft een eigen visuele stijl (plan, test, commit)
- Log is zichtbaar in de story-detailweergave
- Log is read-only in de UI (schrijven gebeurt via API)
---
## PBI-09 — Infrastructuur & deployment
**Prioriteit:** 1 — Kritiek
**Omschrijving:** De app is deployable op Vercel + Neon (cloud) én volledig lokaal draaibaar zonder externe afhankelijkheden.
### Stories
**S-09-01: Cloud deployment (Vercel + Neon)**
Als Developer wil ik de app deployen op Vercel met een Neon PostgreSQL-database, zodat de app beschikbaar is via een URL.
Acceptatiecriteria:
- `next build` slaagt zonder fouten
- Database-migraties worden uitgevoerd via Prisma
- Environment variables zijn gedocumenteerd in `.env.example`
**S-09-02: Lokale modus**
Als Developer wil ik de app volledig lokaal kunnen draaien met een lokale SQLite- of PostgreSQL-database, zodat er geen externe afhankelijkheid nodig is.
Acceptatiecriteria:
- `npm run dev` start de app lokaal zonder Vercel of Neon account
- Database wordt aangemaakt via `prisma db push`
- README bevat stap-voor-stap instructies voor lokale setup
**S-09-03: API-token authenticatie**
Als Developer wil ik een API-token kunnen genereren in de app, zodat Claude Code veilig kan communiceren met de REST API.
Acceptatiecriteria:
- Gebruiker kan een API-token aanmaken in de instellingenpagina
- Token wordt eenmalig getoond en daarna niet meer zichtbaar
- Token kan worden ingetrokken
- Alle API-endpoints vereisen een geldig token via `Authorization: Bearer`
---
## Backlog — v2 kandidaten (niet in v1)
| PBI | Omschrijving |
|---|---|
| Daily Scrum scherm | Voortgang per story bijhouden tijdens de Sprint |
| Sprint Review scherm | Demo en feedback vastleggen per story |
| Sprint Retrospective scherm | Reflectie vastleggen per Sprint |
| Meerdere gebruikers per Scrum Team | Uitgebreide auth met rol-gebaseerde permissies |
| Automatische statusupdate na commit | Story op Done zetten via API-aanroep |
| Velocity tracking | Statistieken over meerdere Sprints |
| Notificaties / reminders | Push of e-mailmeldingen |
| Timeline / kalenderweergave | Sprint-planning op kalender |
| Definition of Done per product configureerbaar | Nu vaste instelling; later flexibel |
| Integratie GitHub Issues / Linear | Import/export van PBI's en stories |
---
*Dit document dient als testdata voor de eerste implementatie van de datastructuur.*
*Versie 0.1 — te updaten na Sprint 1 Review.*

664
docs/scrum4me-styling.md Normal file
View file

@ -0,0 +1,664 @@
# Scrum4Me — Styling & Design System
**Versie:** 0.1 — april 2026
**Onderdeel van:** CLAUDE.md context-set
---
## Overzicht
Scrum4Me gebruikt **Material Design 3 (MD3)** als kleurfilosofie, geïmplementeerd via CSS custom properties in `theme.css` en direct bruikbaar als Tailwind utility classes. **shadcn/ui** levert alle UI-primitieven (Button, Dialog, Sheet, Badge, etc.) en is volledig compatibel met het MD3-kleurensysteem via de legacy-token-mapping.
Lees dit document voordat je een component schrijft. Gebruik **nooit** willekeurige Tailwind-kleuren zoals `bg-blue-500` of `bg-green-600` — gebruik altijd de semantische tokens uit dit systeem.
---
## Setup
### 1. theme.css plaatsen
Kopieer het meegeleverde `theme.css` bestand naar:
```
app/globals.css ← importeer theme.css hier, of plak de inhoud direct
```
Of als apart bestand:
```
styles/theme.css
```
Importeer in `app/globals.css`:
```css
@import './styles/theme.css';
```
### 2. shadcn/ui initialiseren
```bash
npx shadcn@latest init
```
Kies bij de setup:
- Style: **Default**
- Base color: **Slate** (wordt overschreven door theme.css)
- CSS variables: **Yes**
De `theme.css` overschrijft alle shadcn default-kleuren via CSS custom properties. Geen extra configuratie nodig.
### 3. Tailwind configuratie
`theme.css` registreert alle tokens via `@theme inline` — ze zijn direct beschikbaar als Tailwind utility classes:
```tsx
// Werkt direct:
className="bg-primary text-primary-foreground"
className="bg-surface-container-low"
className="bg-status-done"
className="bg-priority-critical"
```
### 4. Dark mode
Dark mode werkt via de `.dark` class op `<html>`:
```tsx
// components/theme-toggle.tsx
'use client'
import { useState, useEffect } from 'react'
export function ThemeToggle() {
const [isDark, setIsDark] = useState(false)
useEffect(() => {
const stored = localStorage.getItem('theme')
if (stored === 'dark') {
document.documentElement.classList.add('dark')
setIsDark(true)
}
}, [])
const toggle = () => {
document.documentElement.classList.toggle('dark')
const next = !isDark
setIsDark(next)
localStorage.setItem('theme', next ? 'dark' : 'light')
}
return (
<button onClick={toggle} className="text-muted-foreground hover:text-foreground">
{isDark ? '☀️' : '🌙'}
</button>
)
}
```
---
## Kleurfilosofie
Drie hoofdrollen, elk met een semantische betekenis voor een Scrum-planner:
| Rol | Kleur | Betekenis | Gebruik in Scrum4Me |
|---|---|---|---|
| **Primary** | Blauw `#0061a4` | Productiviteit, vertrouwen | Primaire knoppen, actieve navigatie, Sprint Goal |
| **Secondary** | Paars `#5b5e71` | Planning, organisatie | Secundaire acties, filters, toolbar-items |
| **Tertiary** | Teal `#006874` | Voortgang, data | Voortgangsindicatoren, story-tellers, metrics |
Diepte wordt gecreëerd via **tonal elevation** (lichtere/donkerdere oppervlakken), niet via schaduwen.
---
## Surface Elevation System
Gebruik deze hiërarchie consequent — nooit `shadow-lg` voor diepte:
```
HOOGSTE ELEVATIE (voorgrond)
surface-container-lowest → dialogs, modals, popovers
surface-container-low → kaarten, panelen
surface-container → standaard container
surface-container-high → geneste containers
surface-container-highest → achtergrondcontainers
LAAGSTE ELEVATIE (achtergrond)
background → app-achtergrond
```
### In Scrum4Me specifiek
| Element | Surface token |
|---|---|
| App achtergrond | `bg-background` |
| Navigatiebalk | `bg-surface-container-low` |
| Gesplitst scherm (elk paneel) | `bg-surface-container-low` |
| PBI-rij | `bg-surface-container` |
| Geselecteerde PBI-rij | `bg-primary-container` |
| Story-blok | `bg-surface-container-low border border-border` |
| Story-blok (geselecteerd) | `bg-primary-container border border-primary` |
| Taakregel | `bg-surface-container` |
| Dialogs / modals | `bg-surface-container-lowest` |
| Slide-over (story detail) | `bg-surface-container-lowest` |
| Todo-item | `bg-surface-container` |
| Navigatiebar per paneel | `bg-surface-container-highest` |
---
## Statuskleur mapping
### Story- en taakstatus
Gebruik **altijd** icoon + tekst naast kleur (toegankelijkheid):
```tsx
// Status badge component
const statusConfig = {
OPEN: {
label: 'Open',
className: 'bg-status-todo text-white',
},
IN_SPRINT: {
label: 'In Sprint',
className: 'bg-status-in-progress text-white',
},
DONE: {
label: 'Done',
className: 'bg-status-done text-white',
},
}
// Taakstatus
const taskStatusConfig = {
TO_DO: {
label: 'To Do',
className: 'bg-status-todo text-white',
},
IN_PROGRESS: {
label: 'In Progress',
className: 'bg-status-in-progress text-white',
},
DONE: {
label: 'Done',
className: 'bg-status-done text-white',
},
}
```
### Prioriteitskleur mapping
```tsx
const priorityConfig = {
1: {
label: 'Kritiek',
className: 'bg-priority-critical text-white',
borderClassName: 'border-l-4 border-priority-critical',
},
2: {
label: 'Hoog',
className: 'bg-priority-high text-white',
borderClassName: 'border-l-4 border-priority-high',
},
3: {
label: 'Middel',
className: 'bg-priority-medium text-white',
borderClassName: 'border-l-4 border-priority-medium',
},
4: {
label: 'Laag',
className: 'bg-priority-low text-white',
borderClassName: 'border-l-4 border-priority-low',
},
}
```
### Story-activiteitenlog
```tsx
const logTypeConfig = {
IMPLEMENTATION_PLAN: {
label: 'Implementatieplan',
className: 'bg-info-container text-info-container-foreground border-l-4 border-info',
},
TEST_RESULT: {
PASSED: {
label: 'Tests geslaagd',
className: 'bg-success-container text-success-container-foreground border-l-4 border-success',
},
FAILED: {
label: 'Tests mislukt',
className: 'bg-error-container text-error-container-foreground border-l-4 border-error',
},
},
COMMIT: {
label: 'Commit',
className: 'bg-secondary-container text-secondary-container-foreground border-l-4 border-secondary',
},
}
```
---
## shadcn/ui componenten — gebruik in Scrum4Me
Alle shadcn-componenten gebruiken automatisch het MD3-kleurensysteem. Hieronder de aanbevolen varianten per context.
### Button
```tsx
import { Button } from '@/components/ui/button'
// Primaire actie (Sprint starten, PBI aanmaken, Opslaan)
<Button>Sprint starten</Button>
// Secundaire actie (Annuleren, Filters, Exporteren)
<Button variant="secondary">Annuleren</Button>
// Destructieve actie (Verwijderen, Archiveren)
<Button variant="destructive">Verwijderen</Button>
// Ghost (icon-knoppen in navigatiebar)
<Button variant="ghost" size="icon">
<PlusIcon className="h-4 w-4" />
</Button>
// Outline (minder urgente acties)
<Button variant="outline">Details bekijken</Button>
```
### Badge (status en prioriteit)
```tsx
import { Badge } from '@/components/ui/badge'
// Gebruik custom className voor MD3-kleuren
// shadcn Badge variant="secondary" is ook bruikbaar voor neutrale badges
<Badge className="bg-status-done text-white">Done</Badge>
<Badge className="bg-priority-critical text-white">Kritiek</Badge>
<Badge className="bg-status-in-progress text-white">In Sprint</Badge>
// Neutrale info badge (bijv. "3 taken")
<Badge variant="secondary">3 taken</Badge>
```
### Dialog (bevestigingsdialogen)
```tsx
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
// Standaard bevestigingsdialoog voor verwijderacties
<AlertDialogContent className="bg-surface-container-lowest">
<AlertDialogHeader>
<AlertDialogTitle>PBI verwijderen?</AlertDialogTitle>
<AlertDialogDescription>
Dit verwijdert ook alle gekoppelde stories en taken. Deze actie is niet ongedaan te maken.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Annuleren</AlertDialogCancel>
<AlertDialogAction className="bg-destructive text-destructive-foreground">
Verwijderen
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
```
### Sheet (story detail slide-over)
```tsx
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'
<Sheet>
<SheetContent
side="right"
className="w-[480px] bg-surface-container-lowest border-l border-border"
>
<SheetHeader>
<SheetTitle className="text-foreground">{story.title}</SheetTitle>
</SheetHeader>
{/* story detail inhoud */}
</SheetContent>
</Sheet>
```
### Input en Textarea
```tsx
import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea'
// shadcn Input gebruikt --input-background automatisch uit theme.css
<Input
placeholder="PBI titel"
className="bg-input-background border-border focus:ring-primary"
/>
<Textarea
placeholder="Omschrijving (optioneel)"
className="bg-input-background border-border focus:ring-primary resize-none"
/>
```
### Select (prioriteit dropdown)
```tsx
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
<Select>
<SelectTrigger className="bg-input-background border-border">
<SelectValue placeholder="Prioriteit" />
</SelectTrigger>
<SelectContent className="bg-surface-container-lowest border-border">
<SelectItem value="1">
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-priority-critical" />
Kritiek
</span>
</SelectItem>
<SelectItem value="2">
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-priority-high" />
Hoog
</span>
</SelectItem>
<SelectItem value="3">
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-priority-medium" />
Middel
</span>
</SelectItem>
<SelectItem value="4">
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-priority-low" />
Laag
</span>
</SelectItem>
</SelectContent>
</Select>
```
### Skeleton (loading states)
```tsx
import { Skeleton } from '@/components/ui/skeleton'
// PBI lijst skeleton
function PbiListSkeleton() {
return (
<div className="space-y-2 p-4">
{Array.from({ length: 5 }).map((_, i) => (
<Skeleton key={i} className="h-12 w-full bg-surface-container-high" />
))}
</div>
)
}
// Story blokken skeleton
function StoryGridSkeleton() {
return (
<div className="flex flex-wrap gap-3 p-4">
{Array.from({ length: 6 }).map((_, i) => (
<Skeleton key={i} className="h-24 w-[10%] min-w-[100px] bg-surface-container-high" />
))}
</div>
)
}
```
### Tooltip (demo-gebruiker write-protection)
```tsx
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
// Gebruik voor alle uitgeschakelde knoppen bij demo-gebruiker
function DemoProtectedButton({ children, isDemo, onClick, ...props }) {
if (!isDemo) {
return <Button onClick={onClick} {...props}>{children}</Button>
}
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span>
<Button disabled {...props}>{children}</Button>
</span>
</TooltipTrigger>
<TooltipContent className="bg-surface-container-lowest border-border">
<p className="text-sm text-muted-foreground">Niet beschikbaar in demo-modus</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)
}
```
---
## Scrum4Me component patronen
### PBI-rij
```tsx
// Geselecteerd PBI heeft primary-container achtergrond
<div
className={cn(
"flex items-center gap-3 px-4 py-3 rounded-lg cursor-pointer transition-colors border-l-4",
priorityConfig[pbi.priority].borderClassName,
isSelected
? "bg-primary-container text-primary-container-foreground"
: "bg-surface-container hover:bg-surface-container-high"
)}
onClick={() => setSelectedPbi(pbi.id)}
>
<span className="flex-1 text-sm font-medium truncate">{pbi.title}</span>
<Badge className={priorityConfig[pbi.priority].className}>
{priorityConfig[pbi.priority].label}
</Badge>
</div>
```
### Story-blok
```tsx
// ~10% schermbreedte, compacte weergave
<div
className={cn(
"relative flex flex-col gap-1 p-3 rounded-lg border cursor-pointer",
"min-w-[100px] w-[10%] h-24 text-xs",
"transition-colors hover:border-primary",
"bg-surface-container-low border-border"
)}
>
<span className="font-medium leading-tight line-clamp-2">{story.title}</span>
<div className="mt-auto flex items-center justify-between">
<span className={cn("px-1.5 py-0.5 rounded text-[10px] font-medium", statusConfig[story.status].className)}>
{statusConfig[story.status].label}
</span>
<span className={cn("w-2 h-2 rounded-full", `bg-priority-${priorityLabel}`)}>
</span>
</div>
</div>
```
### Prioriteitsgroep scheidingslijn
```tsx
// Visuele scheiding per prioriteitsgroep in PBI-lijst en story-grid
<div className="mt-4 mb-2">
<div className="flex items-center gap-2">
<span className={cn(
"text-xs font-semibold uppercase tracking-wider",
priority === 1 && "text-priority-critical",
priority === 2 && "text-priority-high",
priority === 3 && "text-priority-medium",
priority === 4 && "text-priority-low",
)}>
{priorityConfig[priority].label}
</span>
<div className={cn(
"flex-1 h-px",
priority === 1 && "bg-priority-critical/30",
priority === 2 && "bg-priority-high/30",
priority === 3 && "bg-priority-medium/30",
priority === 4 && "bg-priority-low/30",
)} />
<span className="text-xs text-muted-foreground">{count}</span>
</div>
</div>
```
### Voortgangsindicator (story → taken)
```tsx
// Gebruikt tertiary kleur voor voortgang
function StoryProgress({ done, total }: { done: number; total: number }) {
const pct = total === 0 ? 0 : Math.round((done / total) * 100)
return (
<div className="flex items-center gap-2 text-xs">
<div className="flex-1 h-1.5 bg-surface-container-highest rounded-full overflow-hidden">
<div
className="h-full bg-tertiary rounded-full transition-all"
style={{ width: `${pct}%` }}
/>
</div>
<span className="text-muted-foreground tabular-nums">
{done}/{total}
</span>
</div>
)
}
```
### Activiteitenlog entry
```tsx
function LogEntry({ entry }: { entry: StoryLog }) {
const config = entry.type === 'TEST_RESULT'
? logTypeConfig.TEST_RESULT[entry.status ?? 'PASSED']
: logTypeConfig[entry.type]
return (
<div className={cn("rounded-lg p-3 text-sm", config.className)}>
<div className="flex items-center justify-between mb-1">
<span className="font-medium text-xs uppercase tracking-wide">
{config.label}
</span>
<span className="text-xs opacity-70">
{formatDate(entry.created_at)}
</span>
</div>
<p className="text-sm leading-relaxed whitespace-pre-wrap">
{entry.content}
</p>
{entry.commit_hash && (
<a
href={`${repoUrl}/commit/${entry.commit_hash}`}
target="_blank"
rel="noopener noreferrer"
className="mt-1 inline-flex items-center gap-1 text-xs font-mono opacity-80 hover:opacity-100 underline"
>
{entry.commit_hash.slice(0, 7)} — {entry.commit_message}
</a>
)}
</div>
)
}
```
### Sprint Goal banner
```tsx
// Prominent bovenaan Sprint-schermen
<div className="bg-primary-container text-primary-container-foreground rounded-lg px-4 py-3 mb-4">
<span className="text-xs font-semibold uppercase tracking-wider opacity-70">
Sprint Goal
</span>
<p className="mt-0.5 font-medium">{sprint.sprint_goal}</p>
</div>
```
### Toast notificaties (Sonner)
```tsx
import { toast } from 'sonner'
// Success (aanmaken, opslaan)
toast.success('PBI aangemaakt')
toast.success('Story toegevoegd aan Sprint')
// Error (mislukte Server Action)
toast.error('Opslaan mislukt. Probeer opnieuw.')
// Info (neutrale melding)
toast.info('Sprint afgerond')
// Geen toast bij drag-and-drop (te frequent)
```
---
## Regels (nooit overtreden)
```
❌ bg-blue-500, bg-green-600, bg-red-400 → gebruik semantische tokens
❌ shadow-lg, shadow-md → gebruik surface elevation
❌ opacity-50 op een primary button → gebruik -container variant
❌ Kleur alleen voor status (geen tekst) → altijd tekst + kleur
❌ Hardcoded hex-waarden in className → altijd via CSS token
❌ bg-white of bg-black → bg-background of bg-foreground
✅ bg-primary text-primary-foreground
✅ bg-surface-container-low
✅ bg-status-done + tekst "Done"
✅ bg-error-container text-error-container-foreground
✅ border-l-4 border-priority-critical
```
---
## Bestandslocaties
```
styles/
theme.css ← bronbestand, niet aanpassen
app/
globals.css ← importeert theme.css
components/
ui/ ← shadcn/ui (auto-gegenereerd, niet aanpassen)
shared/
status-badge.tsx ← herbruikbare status badge
priority-badge.tsx ← herbruikbare prioriteit badge
demo-button.tsx ← Button met demo-protection tooltip
story-log.tsx ← activiteitenlog entries
story-progress.tsx ← voortgangsindicator
priority-group.tsx ← prioriteitsgroep scheidingslijn
```
---
## Toegankelijkheid
- Alle kleurcombinaties voldoen aan **WCAG AA** (contrast ratio ≥ 4.5:1 voor normale tekst)
- Gebruik **altijd** tekst + kleur voor statusindicatoren, nooit kleur alleen
- Alle interactieve elementen hebben een zichtbare `focus:ring-primary`
- Dark mode is volledig ondersteund via de `.dark` class
---
*Bijlage bij CLAUDE.md — lees beide voor je begint met bouwen.*