Refine CLAUDE.md content and task instructions
Removed references to automatic logging of implementation plans, test results, and commits in stories. Updated task instructions and conventions for clarity.
This commit is contained in:
parent
aea278eed7
commit
990fca792f
1 changed files with 56 additions and 536 deletions
592
CLAUDE.md
592
CLAUDE.md
|
|
@ -6,7 +6,7 @@ Dit is het centrale instructiedocument voor Claude Code. Lees dit volledig voord
|
|||
|
||||
## Wat is Scrum4Me?
|
||||
|
||||
Een desktop-first fullstack webapplicatie voor solo developers en kleine Scrum Teams die meerdere softwareprojecten parallel beheren. De app organiseert werk hiërarchisch (product → PBI → story → taak), biedt gesplitste planningsschermen met drag-and-drop, en integreert met Claude Code via een REST API zodat implementatieplannen, testresultaten en commits automatisch vastgelegd worden in stories.
|
||||
Een desktop-first fullstack webapplicatie voor solo developers en kleine Scrum Teams die meerdere softwareprojecten parallel beheren. De app organiseert werk hiërarchisch (product → PBI → story → taak), biedt gesplitste planningsschermen met drag-and-drop, en integreert met Claude Code via een REST API.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -16,14 +16,12 @@ Lees het relevante document voordat je aan een feature begint. Nooit gokken over
|
|||
|
||||
| Document | Gebruik voor |
|
||||
|---|---|
|
||||
| `scrum4me-functional-spec.md` | Acceptatiecriteria, randgevallen, user flows per feature |
|
||||
| `scrum4me-architecture.md` | Stack, datamodel, Prisma schema, Zustand stores, projectstructuur |
|
||||
| `scrum4me-backlog.md` | Welke task bouwen, in welke volgorde, "done when"-criteria |
|
||||
| `scrum4me-personas.md` | Lars (primaire gebruiker), Dina, Remi — gebruik bij UI-beslissingen |
|
||||
| `scrum4me-product-backlog.md` | Testdata voor de seed — PBI's en stories van Scrum4Me zelf |
|
||||
| `scrum4me-styling.md` | **Lees dit voor elk component** — MD3-kleuren, shadcn gebruik, component-patronen |
|
||||
| `theme.css` | Bronbestand — kopieer naar `styles/theme.css`, importeer in `app/globals.css` |
|
||||
| `MD3_Color_Scheme_Documentation.md` | Volledige MD3-kleurendocumentatie als referentie |
|
||||
| `scrum4me-functional-spec.md` | Acceptatiecriteria, randgevallen, user flows |
|
||||
| `scrum4me-architecture.md` | Stack, datamodel, Prisma schema, Zustand stores |
|
||||
| `scrum4me-backlog.md` | Welke task bouwen, volgorde, "done when"-criteria |
|
||||
| `scrum4me-personas.md` | Lars (primair), Dina, Remi — gebruik bij UI-beslissingen |
|
||||
| `scrum4me-product-backlog.md` | Testdata voor de seed |
|
||||
| `scrum4me-styling.md` | **Lees dit voor elk component** — MD3-kleuren, shadcn patronen |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -39,570 +37,92 @@ M0 (ST-001–008) → M1 (ST-101–110) → M2 (ST-201–210)
|
|||
|
||||
Per task:
|
||||
1. Lees de task in `scrum4me-backlog.md`
|
||||
2. Zoek de bijbehorende feature-spec op in `scrum4me-functional-spec.md`
|
||||
3. Bouw — test — verifieer de "Done when"-criteria
|
||||
4. Commit met de task-ID in het commit-bericht: `feat: ST-001 project scaffolding`
|
||||
2. Zoek de bijbehorende feature-spec in `scrum4me-functional-spec.md`
|
||||
3. Lees het relevante patroon in `docs/patterns/` als dat van toepassing is
|
||||
4. Bouw — test — verifieer de "Done when"-criteria
|
||||
5. vraag of code juiste is
|
||||
6. Commit: `feat: ST-001 project scaffolding`
|
||||
7. vraag of volgende taak moet worden gedaan
|
||||
|
||||
---
|
||||
|
||||
## Tech stack (samenvatting)
|
||||
## Tech stack
|
||||
|
||||
```
|
||||
Next.js 15 (App Router) + React 19
|
||||
Next.js 16 (App Router) + React 19
|
||||
TypeScript strict
|
||||
Tailwind CSS + shadcn/ui ← UI-primitieven (Button, Dialog, Sheet, Badge, etc.)
|
||||
MD3 kleurensysteem via theme.css ← semantische tokens, nooit willekeurige Tailwind-kleuren
|
||||
Tailwind CSS + shadcn/ui
|
||||
MD3 kleurensysteem via theme.css
|
||||
Zustand (client state)
|
||||
dnd-kit (drag-and-drop)
|
||||
Prisma v7 (ORM)
|
||||
PostgreSQL via Neon (cloud) | SQLite (lokaal)
|
||||
Prisma v7 + PostgreSQL (Neon) | SQLite (lokaal)
|
||||
iron-session (auth cookies)
|
||||
bcrypt (wachtwoord hashing)
|
||||
Zod (validatie)
|
||||
Sonner (toasts)
|
||||
bcryptjs + Zod + Sonner
|
||||
```
|
||||
|
||||
> **Stylingregel:** Gebruik **nooit** `bg-blue-500`, `bg-green-600` of andere willekeurige Tailwind-kleuren.
|
||||
> Gebruik altijd semantische MD3-tokens: `bg-primary`, `bg-status-done`, `bg-priority-critical`, etc.
|
||||
> Zie `scrum4me-styling.md` voor alle patronen en regels.
|
||||
> ⚠️ **Stylingregel:** Gebruik **nooit** `bg-blue-500` of willekeurige Tailwind-kleuren.
|
||||
> Gebruik altijd semantische MD3-tokens: `bg-primary`, `bg-status-done`, `bg-priority-critical`.
|
||||
> Zie `scrum4me-styling.md` voor alle patronen.
|
||||
|
||||
> ⚠️ **Next.js-versie:** Lees `node_modules/next/dist/docs/` bij twijfel — API's kunnen afwijken van trainingsdata.
|
||||
|
||||
---
|
||||
|
||||
## Exacte dependencies (package.json)
|
||||
## Implementatiepatronen
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"next": "^15.0.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"zustand": "^5.0.0",
|
||||
"@dnd-kit/core": "^6.3.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.0",
|
||||
"prisma": "^7.0.0",
|
||||
"@prisma/client": "^7.0.0",
|
||||
"@prisma/adapter-pg": "^7.0.0",
|
||||
"iron-session": "^8.0.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"zod": "^3.22.0",
|
||||
"sonner": "^1.5.0",
|
||||
"pg": "^8.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/pg": "^8.11.0",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"eslint": "^8.0.0",
|
||||
"eslint-config-next": "^15.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
Lees het relevante patroon vóór je begint. Nooit uit het hoofd schrijven.
|
||||
|
||||
---
|
||||
|
||||
## theme.css installeren
|
||||
|
||||
```bash
|
||||
# Kopieer theme.css naar de project root of styles map
|
||||
cp theme.css app/styles/theme.css
|
||||
|
||||
# Importeer bovenaan app/globals.css:
|
||||
# @import './styles/theme.css';
|
||||
```
|
||||
|
||||
Dark mode werkt via `.dark` class op `<html>`. Zie `scrum4me-styling.md` voor het ThemeToggle component.
|
||||
|
||||
## shadcn/ui componenten om te installeren
|
||||
|
||||
Voer deze uit na `npx shadcn@latest init`:
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add button
|
||||
npx shadcn@latest add input
|
||||
npx shadcn@latest add textarea
|
||||
npx shadcn@latest add dialog
|
||||
npx shadcn@latest add dropdown-menu
|
||||
npx shadcn@latest add badge
|
||||
npx shadcn@latest add tooltip
|
||||
npx shadcn@latest add separator
|
||||
npx shadcn@latest add sheet # voor story slide-over
|
||||
npx shadcn@latest add select
|
||||
npx shadcn@latest add alert-dialog # voor bevestigingsdialogen
|
||||
npx shadcn@latest add skeleton
|
||||
npx shadcn@latest add toast
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Projectstructuur
|
||||
|
||||
```
|
||||
scrum4me/
|
||||
├── app/
|
||||
│ ├── (auth)/
|
||||
│ │ ├── login/page.tsx
|
||||
│ │ └── register/page.tsx
|
||||
│ ├── (app)/
|
||||
│ │ ├── layout.tsx # Auth-check + navigatie
|
||||
│ │ ├── dashboard/page.tsx
|
||||
│ │ ├── products/
|
||||
│ │ │ ├── new/page.tsx
|
||||
│ │ │ └── [id]/
|
||||
│ │ │ ├── page.tsx # Product Backlog
|
||||
│ │ │ └── sprint/
|
||||
│ │ │ ├── page.tsx # Sprint Backlog
|
||||
│ │ │ └── planning/page.tsx
|
||||
│ │ ├── todos/page.tsx
|
||||
│ │ └── settings/
|
||||
│ │ ├── page.tsx
|
||||
│ │ └── tokens/page.tsx
|
||||
│ └── api/
|
||||
│ ├── 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 (auto-gegenereerd)
|
||||
│ ├── split-pane/
|
||||
│ │ └── split-pane.tsx
|
||||
│ ├── backlog/
|
||||
│ │ ├── pbi-list.tsx
|
||||
│ │ ├── pbi-item.tsx
|
||||
│ │ ├── story-grid.tsx
|
||||
│ │ └── story-block.tsx
|
||||
│ ├── sprint/
|
||||
│ │ ├── sprint-backlog.tsx
|
||||
│ │ └── sprint-story-item.tsx
|
||||
│ ├── planning/
|
||||
│ │ ├── task-list.tsx
|
||||
│ │ └── task-item.tsx
|
||||
│ └── shared/
|
||||
│ ├── panel-nav-bar.tsx
|
||||
│ ├── confirm-dialog.tsx
|
||||
│ └── story-log.tsx
|
||||
├── stores/
|
||||
│ ├── planner-store.ts
|
||||
│ ├── selection-store.ts
|
||||
│ └── sprint-store.ts
|
||||
├── lib/
|
||||
│ ├── prisma.ts
|
||||
│ ├── session.ts
|
||||
│ ├── auth.ts
|
||||
│ ├── api-auth.ts
|
||||
│ └── env.ts
|
||||
├── actions/
|
||||
│ ├── products.ts
|
||||
│ ├── pbis.ts
|
||||
│ ├── stories.ts
|
||||
│ ├── sprints.ts
|
||||
│ ├── tasks.ts
|
||||
│ └── todos.ts
|
||||
├── prisma/
|
||||
│ ├── schema.prisma
|
||||
│ ├── migrations/
|
||||
│ └── seed.ts
|
||||
├── middleware.ts
|
||||
├── prisma.config.ts
|
||||
└── .env.example
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Kritieke implementatiepatronen
|
||||
|
||||
### 1. iron-session configuratie
|
||||
|
||||
```ts
|
||||
// lib/session.ts
|
||||
import { SessionOptions } from 'iron-session'
|
||||
|
||||
export interface SessionData {
|
||||
userId: string
|
||||
isDemo: boolean
|
||||
}
|
||||
|
||||
export const sessionOptions: SessionOptions = {
|
||||
password: process.env.SESSION_SECRET!,
|
||||
cookieName: 'scrum4me-session',
|
||||
cookieOptions: {
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// Gebruik in Server Action of Route Handler:
|
||||
import { getIronSession } from 'iron-session'
|
||||
import { cookies } from 'next/headers'
|
||||
import { SessionData, sessionOptions } from '@/lib/session'
|
||||
|
||||
const session = await getIronSession<SessionData>(await cookies(), sessionOptions)
|
||||
if (!session.userId) redirect('/login')
|
||||
```
|
||||
|
||||
### 2. Prisma Client singleton
|
||||
|
||||
```ts
|
||||
// lib/prisma.ts
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined }
|
||||
|
||||
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
|
||||
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||
})
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
||||
```
|
||||
|
||||
### 3. prisma.config.ts (Prisma v7 vereiste)
|
||||
|
||||
```ts
|
||||
// prisma.config.ts
|
||||
import 'dotenv/config'
|
||||
import { defineConfig } from 'prisma/config'
|
||||
|
||||
export default defineConfig({
|
||||
schema: 'prisma/schema.prisma',
|
||||
migrations: { path: 'prisma/migrations' },
|
||||
})
|
||||
```
|
||||
|
||||
### 4. API Bearer token authenticatie
|
||||
|
||||
```ts
|
||||
// lib/api-auth.ts
|
||||
import { createHash } from 'crypto'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function authenticateApiRequest(request: Request) {
|
||||
const authHeader = request.headers.get('Authorization')
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return { error: 'Unauthorized', status: 401 }
|
||||
}
|
||||
|
||||
const token = authHeader.slice(7)
|
||||
const tokenHash = createHash('sha256').update(token).digest('hex')
|
||||
|
||||
const apiToken = await prisma.apiToken.findUnique({
|
||||
where: { token_hash: tokenHash },
|
||||
include: { user: true },
|
||||
})
|
||||
|
||||
if (!apiToken || apiToken.revoked_at) {
|
||||
return { error: 'Unauthorized', status: 401 }
|
||||
}
|
||||
|
||||
return { userId: apiToken.user_id, isDemo: apiToken.user.is_demo }
|
||||
}
|
||||
|
||||
// Gebruik in Route Handler:
|
||||
export async function GET(request: Request) {
|
||||
const auth = await authenticateApiRequest(request)
|
||||
if ('error' in auth) {
|
||||
return Response.json({ error: auth.error }, { status: auth.status })
|
||||
}
|
||||
// auth.userId beschikbaar
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Float sort_order — drag-and-drop volgorde
|
||||
|
||||
```ts
|
||||
// Bereken nieuwe sort_order bij tussenvoeging:
|
||||
function getSortOrder(before: number | null, after: number | null): number {
|
||||
if (before === null && after === null) return 1.0
|
||||
if (before === null) return after! / 2
|
||||
if (after === null) return before + 1.0
|
||||
return (before + after) / 2
|
||||
}
|
||||
|
||||
// Herindexeer als precisie opraakt (< 0.001 verschil):
|
||||
async function reindexIfNeeded(items: { id: string; sort_order: number }[]) {
|
||||
const minGap = Math.min(...items.slice(1).map((item, i) =>
|
||||
item.sort_order - items[i].sort_order
|
||||
))
|
||||
if (minGap < 0.001) {
|
||||
// Herindexeer: 1.0, 2.0, 3.0, ...
|
||||
await Promise.all(items.map((item, i) =>
|
||||
prisma.pbi.update({ where: { id: item.id }, data: { sort_order: i + 1.0 } })
|
||||
))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Zustand store patroon (optimistische update + rollback)
|
||||
|
||||
```ts
|
||||
// Gebruik in dnd-kit onDragEnd:
|
||||
const { pbiOrder, reorderPbis, rollbackPbis } = usePlannerStore()
|
||||
|
||||
async function handleDragEnd(event: DragEndEvent) {
|
||||
const { active, over } = event
|
||||
if (!over || active.id === over.id) return
|
||||
|
||||
const prevOrder = [...pbiOrder[productId]]
|
||||
const newOrder = arrayMove(prevOrder, oldIndex, newIndex)
|
||||
|
||||
// 1. Optimistisch updaten
|
||||
reorderPbis(productId, newOrder)
|
||||
|
||||
// 2. Server Action aanroepen
|
||||
const result = await reorderPbisAction(productId, newOrder)
|
||||
|
||||
// 3. Rollback bij fout
|
||||
if (!result.success) {
|
||||
rollbackPbis(productId, prevOrder)
|
||||
toast.error('Volgorde opslaan mislukt')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Server Action patroon
|
||||
|
||||
```ts
|
||||
// actions/pbis.ts
|
||||
'use server'
|
||||
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import { getIronSession } from 'iron-session'
|
||||
import { cookies } from 'next/headers'
|
||||
import { z } from 'zod'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { SessionData, sessionOptions } from '@/lib/session'
|
||||
|
||||
const createPbiSchema = z.object({
|
||||
productId: z.string().cuid(),
|
||||
title: z.string().min(1).max(200),
|
||||
priority: z.number().int().min(1).max(4),
|
||||
})
|
||||
|
||||
export async function createPbi(formData: FormData) {
|
||||
const session = await getIronSession<SessionData>(await cookies(), sessionOptions)
|
||||
if (!session.userId) return { error: 'Niet ingelogd' }
|
||||
if (session.isDemo) return { error: 'Niet beschikbaar in demo-modus' }
|
||||
|
||||
const parsed = createPbiSchema.safeParse({
|
||||
productId: formData.get('productId'),
|
||||
title: formData.get('title'),
|
||||
priority: Number(formData.get('priority')),
|
||||
})
|
||||
if (!parsed.success) return { error: parsed.error.flatten().fieldErrors }
|
||||
|
||||
// Valideer eigenaarschap
|
||||
const product = await prisma.product.findFirst({
|
||||
where: { id: parsed.data.productId, user_id: session.userId }
|
||||
})
|
||||
if (!product) return { error: 'Product niet gevonden' }
|
||||
|
||||
// Bepaal sort_order (onderaan de prioriteitsgroep)
|
||||
const last = await prisma.pbi.findFirst({
|
||||
where: { product_id: parsed.data.productId, priority: parsed.data.priority },
|
||||
orderBy: { sort_order: 'desc' },
|
||||
})
|
||||
const sort_order = (last?.sort_order ?? 0) + 1.0
|
||||
|
||||
const pbi = await prisma.pbi.create({
|
||||
data: { ...parsed.data, product_id: parsed.data.productId, sort_order },
|
||||
})
|
||||
|
||||
revalidatePath(`/products/${parsed.data.productId}`)
|
||||
return { success: true, pbi }
|
||||
}
|
||||
```
|
||||
|
||||
### 8. Route Handler patroon (REST API)
|
||||
|
||||
```ts
|
||||
// app/api/products/[id]/next-story/route.ts
|
||||
import { authenticateApiRequest } from '@/lib/api-auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const auth = await authenticateApiRequest(request)
|
||||
if ('error' in auth) {
|
||||
return Response.json({ error: auth.error }, { status: auth.status })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
|
||||
// Valideer eigenaarschap
|
||||
const sprint = await prisma.sprint.findFirst({
|
||||
where: { product_id: id, status: 'ACTIVE', product: { user_id: auth.userId } },
|
||||
})
|
||||
if (!sprint) {
|
||||
return Response.json({ error: 'Geen actieve Sprint gevonden' }, { status: 404 })
|
||||
}
|
||||
|
||||
const story = await prisma.story.findFirst({
|
||||
where: { sprint_id: sprint.id, status: 'IN_SPRINT' },
|
||||
orderBy: [{ priority: 'asc' }, { sort_order: 'asc' }],
|
||||
include: { tasks: { orderBy: [{ priority: 'asc' }, { sort_order: 'asc' }] } },
|
||||
})
|
||||
|
||||
if (!story) {
|
||||
return Response.json({ error: 'Geen open stories in de Sprint' }, { status: 404 })
|
||||
}
|
||||
|
||||
return Response.json(story)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Middleware patroon
|
||||
|
||||
```ts
|
||||
// middleware.ts
|
||||
import { NextResponse } from 'next/server'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { getIronSession } from 'iron-session'
|
||||
import { SessionData, sessionOptions } from '@/lib/session'
|
||||
|
||||
const protectedRoutes = ['/dashboard', '/products', '/todos', '/settings']
|
||||
const authRoutes = ['/login', '/register']
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
const response = NextResponse.next()
|
||||
const session = await getIronSession<SessionData>(request.cookies, sessionOptions)
|
||||
|
||||
const isProtected = protectedRoutes.some(r => request.nextUrl.pathname.startsWith(r))
|
||||
const isAuthRoute = authRoutes.some(r => request.nextUrl.pathname.startsWith(r))
|
||||
|
||||
if (isProtected && !session.userId) {
|
||||
return NextResponse.redirect(new URL('/login', request.url))
|
||||
}
|
||||
if (isAuthRoute && session.userId) {
|
||||
return NextResponse.redirect(new URL('/dashboard', request.url))
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scrum-terminologie (gebruik consistent)
|
||||
|
||||
| Correct | Niet gebruiken |
|
||||
| Patroon | Bestand |
|
||||
|---|---|
|
||||
| Product Backlog Item (PBI) | Feature, Epic, Issue |
|
||||
| Story | User Story, Ticket |
|
||||
| Sprint Goal | Sprint Objective |
|
||||
| Sprint Planning | Sprint Meeting |
|
||||
| Scrum Team | Team |
|
||||
| Definition of Done | DoD criteria |
|
||||
| iron-session (auth cookies) | `docs/patterns/iron-session.md` |
|
||||
| Prisma Client singleton | `docs/patterns/prisma-client.md` |
|
||||
| Server Action (met auth + Zod) | `docs/patterns/server-action.md` |
|
||||
| Route Handler (REST API) | `docs/patterns/route-handler.md` |
|
||||
| Zustand optimistische update + rollback | `docs/patterns/zustand-optimistic.md` |
|
||||
| Float sort_order drag-and-drop | `docs/patterns/sort-order.md` |
|
||||
| Middleware (route protection) | `docs/patterns/middleware.md` |
|
||||
|
||||
---
|
||||
|
||||
## Env vars
|
||||
|
||||
```bash
|
||||
# .env.local
|
||||
DATABASE_URL="postgresql://user:password@host/dbname?sslmode=require"
|
||||
DIRECT_URL="postgresql://user:password@host/dbname?sslmode=require"
|
||||
SESSION_SECRET="genereer-met-openssl-rand-base64-32"
|
||||
|
||||
# Lokaal (SQLite):
|
||||
# DATABASE_URL="file:./dev.db"
|
||||
# DIRECT_URL niet nodig bij SQLite
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Lokale setup (quickstart)
|
||||
|
||||
```bash
|
||||
git clone <repo>
|
||||
cd scrum4me
|
||||
npm install
|
||||
cp .env.example .env.local
|
||||
# Vul SESSION_SECRET in .env.local
|
||||
|
||||
# SQLite lokaal:
|
||||
npx prisma db push
|
||||
npx prisma db seed
|
||||
npm run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Demo-gebruiker credentials
|
||||
|
||||
Na seeding:
|
||||
- **Gebruikersnaam:** `demo`
|
||||
- **Wachtwoord:** `demo1234`
|
||||
|
||||
De demo-gebruiker heeft read-only rechten. Alle schrijfacties geven een 403 of zijn uitgeschakeld in de UI.
|
||||
|
||||
---
|
||||
|
||||
## REST API — alle endpoints
|
||||
|
||||
| Methode | Endpoint | Doel |
|
||||
|---|---|---|
|
||||
| GET | `/api/products` | Actieve producten ophalen |
|
||||
| GET | `/api/products/:id/next-story` | Hoogst geprioriteerde open story |
|
||||
| GET | `/api/sprints/:id/tasks?limit=10` | Eerste N taken van de Sprint |
|
||||
| PATCH | `/api/stories/:id/tasks/reorder` | Taakvolgorde aanpassen |
|
||||
| POST | `/api/stories/:id/log` | Plan / testresultaat / commit vastleggen |
|
||||
| PATCH | `/api/tasks/:id` | Taakstatus bijwerken |
|
||||
| POST | `/api/todos` | Todo aanmaken |
|
||||
|
||||
Alle endpoints vereisen: `Authorization: Bearer <token>`
|
||||
|
||||
### POST /api/stories/:id/log — body schema
|
||||
|
||||
```json
|
||||
// Implementatieplan:
|
||||
{ "type": "IMPLEMENTATION_PLAN", "content": "string" }
|
||||
|
||||
// Testresultaat:
|
||||
{ "type": "TEST_RESULT", "content": "string", "status": "PASSED" | "FAILED" }
|
||||
|
||||
// Commit:
|
||||
{ "type": "COMMIT", "content": "string", "commit_hash": "string", "commit_message": "string" }
|
||||
DATABASE_URL="" # postgresql://... of file:./dev.db
|
||||
DIRECT_URL="" # alleen bij Neon/cloud
|
||||
SESSION_SECRET="" # openssl rand -base64 32
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conventies
|
||||
|
||||
- **Commit-berichten:** `feat: ST-XXX beschrijving` / `fix: ST-XXX beschrijving`
|
||||
- **Branch-namen:** `feat/ST-001-scaffolding`
|
||||
- **Commits:** `feat: ST-XXX beschrijving` / `fix: ST-XXX beschrijving`
|
||||
- **Branches:** `feat/ST-001-scaffolding`
|
||||
- **Server Actions:** altijd in `actions/[domein].ts`, nooit inline in page.tsx
|
||||
- **Validatie:** altijd Zod, nooit handmatige checks
|
||||
- **Eigenaarschap:** elke Server Action en Route Handler controleert dat de resource bij de geverifieerde gebruiker hoort
|
||||
- **Eigenaarschap:** elke Server Action en Route Handler controleert dat de resource bij de ingelogde gebruiker hoort
|
||||
- **Demo-check:** elke Server Action controleert `session.isDemo` vóór schrijven
|
||||
- **Foutberichten:** altijd in het Nederlands voor eindgebruikers
|
||||
- **Comments in code:** Engels
|
||||
- **Foutberichten:** Nederlands voor eindgebruikers — comments in code: Engels
|
||||
|
||||
---
|
||||
|
||||
## Definitie of Done (project)
|
||||
## Scrum-terminologie
|
||||
|
||||
De MVP is klaar wanneer:
|
||||
- [ ] Alle 62 tasks (ST-001 t/m ST-612) zijn afgerond
|
||||
- [ ] Volledige Lars-flow doorlopen zonder fouten (ST-612)
|
||||
| Correct | Niet gebruiken |
|
||||
|---|---|
|
||||
| Product Backlog Item (PBI) | Feature, Epic, Issue |
|
||||
| Story | User Story, Ticket |
|
||||
| Sprint Goal | Sprint Objective |
|
||||
| Scrum Team | Team |
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [ ] Alle 62 tasks (ST-001 t/m ST-612) afgerond
|
||||
- [ ] Volledige Lars-flow zonder fouten (ST-612)
|
||||
- [ ] Alle 7 API-endpoints werken via curl
|
||||
- [ ] Demo-gebruiker heeft geen schrijfrechten
|
||||
- [ ] App lokaal opzetbaar via README zonder extra hulp
|
||||
- [ ] App opzetbaar via README zonder extra hulp
|
||||
- [ ] CI/CD actief — falende build blokkeert merge
|
||||
- [ ] Beveiligingsreview API geslaagd (cross-user toegang onmogelijk)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue