Merge pull request #89 from madhura68/chore/before-launch-changelog

docs+fix: Before-launch checklist (CHANGELOG, demo-policy, privacy, smoke-test)
This commit is contained in:
Janpeter Visser 2026-05-04 14:21:26 +02:00 committed by GitHub
commit d02434a1e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 221 additions and 10 deletions

85
CHANGELOG.md Normal file
View file

@ -0,0 +1,85 @@
# Changelog
All notable changes to **Scrum4Me** are documented in this file.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [Unreleased]
### Added
- A11y: rate-limit `enforceUserRateLimit(scope, userId)` helper applied to all
high-value mutation paths (PBI/Story/Task/Todo/Sprint/Product/Token create,
Claude job enqueue, answerQuestion, story-log POST, avatar upload).
([#86](https://github.com/madhura68/Scrum4Me/pull/86))
- Sentry error-monitoring scaffolding (`@sentry/nextjs`) with no-op fallback
when DSN is not configured. Activate via `NEXT_PUBLIC_SENTRY_DSN` in Vercel
env-vars. ([#85](https://github.com/madhura68/Scrum4Me/pull/85))
### Changed
- A11y: `aria-selected` on PBI-cards replaced with `aria-pressed` (correct
ARIA role-attribute pairing). ([#88](https://github.com/madhura68/Scrum4Me/pull/88))
- A11y: form-label associations (`htmlFor` + `id`) on all happy-path dialogs
(Story/Task + Promote-PBI/Story); auth pages get `<main>` landmark.
([#87](https://github.com/madhura68/Scrum4Me/pull/87))
- A11y: tap-targets ≥28×28 px on hover-icon-buttons (PBI ✎ + ×, Story ✎,
dashboard product ✎). ([#88](https://github.com/madhura68/Scrum4Me/pull/88))
---
## [0.9.0] — 2026-05-04
[GitHub Release](https://github.com/madhura68/Scrum4Me/releases/tag/v0.9.0)
### Added
- **PBI-11: Mobile-shell met landscape-lock** ([#81](https://github.com/madhura68/Scrum4Me/pull/81)):
- Aparte route group `app/(mobile)/m/{settings,pair,products}/...` met eigen
layout (zonder NavBar/StatusBar/MinWidthBanner)
- `LandscapeGuard` (rotate-overlay in portrait), `MobileTabBar` (3 lucide-iconen)
- PWA-manifest met `"orientation": "landscape"`
- UA-redirect bij login: telefoons (`Mobi`-substring) → `/m/products/[active]/solo`,
tablets en desktop → `/dashboard`
- Gedeelde `lib/auth-guard.ts` `requireSession()` helper, hergebruikt door beide layouts
- Mobile-fullscreen voor entity-dialogen via gedeelde `entityDialogContentClasses`
- Sprint Product-Backlog kolom: filter-popover (prioriteit + status) en
edit-iconen op PBI/story/task-rijen. ([#79](https://github.com/madhura68/Scrum4Me/pull/79))
- Edit-icoon op product-card in dashboard (consistent met PBI/story/task-pattern).
([#83](https://github.com/madhura68/Scrum4Me/pull/83))
- v1.0 readiness checklist in `docs/plans/v1-readiness.md`.
([#82](https://github.com/madhura68/Scrum4Me/pull/82))
### Changed
- Refactor `app/(app)/layout.tsx` om gedeelde `requireSession()` te gebruiken
(gedrag onveranderd). ([#81](https://github.com/madhura68/Scrum4Me/pull/81))
- `/m/pair` filesystem-verhuisd uit `(app)/` naar `(mobile)/` — URL onveranderd.
([#81](https://github.com/madhura68/Scrum4Me/pull/81))
---
## [0.4.0] — eerder
### Added
- M9 — Actief Product Backlog: persistente actieve PB-keuze, gesplitste
navigatie, disabled-states bij geen actief product
---
## [0.3.1] — eerder
Initiële stabilisatie-release.
---
## Pre-0.3.x
Foundation-werk (M0 t/m M8) is niet retroactief in dit changelog opgenomen.
Voor de volledige milestone-historie zie [docs/backlog/index.md](./docs/backlog/index.md).
---
[Unreleased]: https://github.com/madhura68/Scrum4Me/compare/v0.9.0...HEAD
[0.9.0]: https://github.com/madhura68/Scrum4Me/releases/tag/v0.9.0
[0.4.0]: https://github.com/madhura68/Scrum4Me/commit/615f0c8
[0.3.1]: https://github.com/madhura68/Scrum4Me/commit/ecc05dd

View file

@ -49,6 +49,7 @@ Scrum4Me biedt een lichtgewicht, web-based oplossing voor het beheren van sprint
## Documentation ## Documentation
- [CHANGELOG.md](CHANGELOG.md) — release-historie (Keep a Changelog)
- [docs/INDEX.md](docs/INDEX.md) — generated index of all docs (front-matter driven) - [docs/INDEX.md](docs/INDEX.md) — generated index of all docs (front-matter driven)
- [docs/glossary.md](docs/glossary.md) — domain terms (PBI, Story, MCP-job, etc.) - [docs/glossary.md](docs/glossary.md) — domain terms (PBI, Story, MCP-job, etc.)
- [CLAUDE.md](CLAUDE.md) / [AGENTS.md](AGENTS.md) — agent instructions - [CLAUDE.md](CLAUDE.md) / [AGENTS.md](AGENTS.md) — agent instructions
@ -152,7 +153,7 @@ npm run dev
npm test npm test
``` ```
Verwacht: alle 69 tests slagen, 0 failures. Verwacht: alle 445 tests slagen, 0 failures.
**API curl-tests (vereist lopende dev server + API token):** **API curl-tests (vereist lopende dev server + API token):**
@ -175,7 +176,7 @@ Handmatige generatie:
npm run db:erd npm run db:erd
``` ```
Tijdens lokale development draait `npm run dev` naast Next.js ook `npm run db:erd:watch`. Bij wijzigingen in `prisma/schema.prisma` wordt `docs/assets/erd.svg` automatisch opnieuw gegenereerd. Optioneel: `npm run db:erd:watch` parallel aan `npm run dev` om bij wijzigingen in `prisma/schema.prisma` `docs/assets/erd.svg` automatisch opnieuw te genereren.
Gebruik `npx prisma db push` alleen om het schema naar de database te synchroniseren. Gebruik `npm run db:erd` om lokaal Prisma Client en de ERD te genereren. Gebruik in CI uitsluitend `npx prisma generate --generator client`. Gebruik `npx prisma db push` alleen om het schema naar de database te synchroniseren. Gebruik `npm run db:erd` om lokaal Prisma Client en de ERD te genereren. Gebruik in CI uitsluitend `npx prisma generate --generator client`.
@ -198,8 +199,11 @@ Zie [.env.example](.env.example).
| Variabele | Verplicht | Doel | | Variabele | Verplicht | Doel |
|---|---:|---| |---|---:|---|
| `DATABASE_URL` | Ja | PostgreSQL connection string voor Prisma | | `DATABASE_URL` | Ja | PostgreSQL connection string voor Prisma |
| `DIRECT_URL` | Nee | Directe Neon connection string voor migraties | | `DIRECT_URL` | Nee | Directe Neon connection string voor migraties (Prisma `directUrl`) |
| `SESSION_SECRET` | Ja | Minimaal 32 tekens; gebruikt door iron-session | | `SESSION_SECRET` | Ja | Minimaal 32 tekens; gebruikt door iron-session |
| `CRON_SECRET` | Productie | Bearer-secret voor `/api/cron/*` routes — required als crons aan staan |
| `NEXT_PUBLIC_SENTRY_DSN` | Nee | Sentry DSN — zonder is de SDK een no-op |
| `SENTRY_ORG` / `SENTRY_PROJECT` / `SENTRY_AUTH_TOKEN` | Nee | Source-map upload tijdens build |
| `NODE_ENV` | Nee | Wordt door Node/Vercel gezet | | `NODE_ENV` | Nee | Wordt door Node/Vercel gezet |
Vercel Analytics gebruikt geen project-specifieke environment variabele in deze app; de component staat in `app/layout.tsx`. Vercel Analytics gebruikt geen project-specifieke environment variabele in deze app; de component staat in `app/layout.tsx`.

View file

@ -366,6 +366,7 @@ export async function removeProductMemberAction(productId: string, memberId: str
export async function leaveProductAction(productId: string) { export async function leaveProductAction(productId: string) {
const session = await getSession() const session = await getSession()
if (!session.userId) return { error: 'Niet ingelogd' } if (!session.userId) return { error: 'Niet ingelogd' }
if (session.isDemo) return { error: 'Niet beschikbaar in demo-modus' }
await prisma.$transaction([ await prisma.$transaction([
prisma.user.updateMany({ prisma.user.updateMany({

View file

@ -47,6 +47,7 @@ export async function createTodoAction(_prevState: unknown, formData: FormData)
export async function toggleTodoAction(id: string, done: boolean) { export async function toggleTodoAction(id: string, done: boolean) {
const session = await getSession() const session = await getSession()
if (!session.userId) return { error: 'Niet ingelogd' } if (!session.userId) return { error: 'Niet ingelogd' }
if (session.isDemo) return { error: 'Niet beschikbaar in demo-modus' }
const todo = await prisma.todo.findFirst({ where: { id, user_id: session.userId } }) const todo = await prisma.todo.findFirst({ where: { id, user_id: session.userId } })
if (!todo) return { error: 'Todo niet gevonden' } if (!todo) return { error: 'Todo niet gevonden' }
@ -59,6 +60,7 @@ export async function toggleTodoAction(id: string, done: boolean) {
export async function archiveCompletedTodosAction() { export async function archiveCompletedTodosAction() {
const session = await getSession() const session = await getSession()
if (!session.userId) return { error: 'Niet ingelogd' } if (!session.userId) return { error: 'Niet ingelogd' }
if (session.isDemo) return { error: 'Niet beschikbaar in demo-modus' }
await prisma.todo.updateMany({ await prisma.todo.updateMany({
where: { user_id: session.userId, done: true, archived: false }, where: { user_id: session.userId, done: true, archived: false },

View file

@ -12,6 +12,11 @@ export const dynamic = 'force-dynamic'
const CHANNEL = 'scrum4me_changes' const CHANNEL = 'scrum4me_changes'
export async function POST(request: Request) { export async function POST(request: Request) {
// Productie-guard: anonieme test-emit op pg_notify is niet voor productie.
if (process.env.NODE_ENV === 'production') {
return new Response('Not found', { status: 404 })
}
const directUrl = process.env.DIRECT_URL ?? process.env.DATABASE_URL const directUrl = process.env.DIRECT_URL ?? process.env.DATABASE_URL
if (!directUrl) { if (!directUrl) {
return Response.json({ error: 'DIRECT_URL/DATABASE_URL niet gezet' }, { status: 500 }) return Response.json({ error: 'DIRECT_URL/DATABASE_URL niet gezet' }, { status: 500 })

View file

@ -16,6 +16,11 @@ export const maxDuration = 300
const CHANNEL = 'scrum4me_changes' const CHANNEL = 'scrum4me_changes'
export async function GET(request: NextRequest) { export async function GET(request: NextRequest) {
// Productie-guard: deze debug-stream lekt rauw alle pg_notify-events.
if (process.env.NODE_ENV === 'production') {
return new Response('Not found', { status: 404 })
}
const directUrl = process.env.DIRECT_URL ?? process.env.DATABASE_URL const directUrl = process.env.DIRECT_URL ?? process.env.DATABASE_URL
if (!directUrl) { if (!directUrl) {
return Response.json({ error: 'DIRECT_URL/DATABASE_URL niet gezet' }, { status: 500 }) return Response.json({ error: 'DIRECT_URL/DATABASE_URL niet gezet' }, { status: 500 })

View file

@ -5,6 +5,7 @@
// VERWIJDEREN zodra env-config op Vercel bevestigd is. // VERWIJDEREN zodra env-config op Vercel bevestigd is.
import { headers } from 'next/headers' import { headers } from 'next/headers'
import { notFound } from 'next/navigation'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
export const runtime = 'nodejs' export const runtime = 'nodejs'
@ -45,6 +46,9 @@ function inspectSecret(name: string, raw: string | undefined): VarStatus {
} }
export default async function DebugEnvPage() { export default async function DebugEnvPage() {
// Productie-guard: lekt env-var-metadata (hostnames, lengtes, pooled-flag).
if (process.env.NODE_ENV === 'production') notFound()
// Force dynamic so each visit reads runtime env (niet build-time gecached) // Force dynamic so each visit reads runtime env (niet build-time gecached)
await headers() await headers()

View file

@ -5,11 +5,15 @@
// //
// VERWIJDEREN VOOR M8 OUT-OF-DRAFT. // VERWIJDEREN VOOR M8 OUT-OF-DRAFT.
import { notFound } from 'next/navigation'
import { DebugRealtimeClient } from './client' import { DebugRealtimeClient } from './client'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
export default function DebugRealtimePage() { export default function DebugRealtimePage() {
// Productie-guard: deze pagina toont rauwe pg_notify-events zonder auth.
if (process.env.NODE_ENV === 'production') notFound()
return ( return (
<div style={{ fontFamily: 'monospace', padding: 16 }}> <div style={{ fontFamily: 'monospace', padding: 16 }}>
<h1 style={{ fontSize: 18, fontWeight: 'bold' }}>Realtime debug scrum4me_changes</h1> <h1 style={{ fontSize: 18, fontWeight: 'bold' }}>Realtime debug scrum4me_changes</h1>

View file

@ -110,6 +110,7 @@ Auto-generated on 2026-05-04 from front-matter and headings.
| [Branch, PR & Commit Strategy](./runbooks/branch-and-commit.md) | `runbooks/branch-and-commit.md` | active | 2026-05-03 | | [Branch, PR & Commit Strategy](./runbooks/branch-and-commit.md) | `runbooks/branch-and-commit.md` | active | 2026-05-03 |
| [Vercel Deployment](./runbooks/deploy-vercel.md) | `runbooks/deploy-vercel.md` | active | 2026-05-03 | | [Vercel Deployment](./runbooks/deploy-vercel.md) | `runbooks/deploy-vercel.md` | active | 2026-05-03 |
| [MCP Integration — Scrum4Me Tools](./runbooks/mcp-integration.md) | `runbooks/mcp-integration.md` | active | 2026-05-03 | | [MCP Integration — Scrum4Me Tools](./runbooks/mcp-integration.md) | `runbooks/mcp-integration.md` | active | 2026-05-03 |
| [v1.0 Smoke Test Checklist](./runbooks/v1-smoke-test.md) | `runbooks/v1-smoke-test.md` | active | 2026-05-04 |
| [StoryDialog Profiel](./story-dialog.md) | `story-dialog.md` | active | 2026-05-03 | | [StoryDialog Profiel](./story-dialog.md) | `story-dialog.md` | active | 2026-05-03 |
| [TaskDialog Profiel](./task-dialog.md) | `task-dialog.md` | active | 2026-05-03 | | [TaskDialog Profiel](./task-dialog.md) | `task-dialog.md` | active | 2026-05-03 |
| [Scrum4Me — API Test Plan](./test-plan.md) | `test-plan.md` | active | 2026-05-03 | | [Scrum4Me — API Test Plan](./test-plan.md) | `test-plan.md` | active | 2026-05-03 |

View file

@ -93,13 +93,13 @@ Twee verwante todo's over de todo-feature uitbreiden. Past bij de strategische r
Must-do voor publieke aankondiging, maar mag pas vlak vóór v1.0-tag. Must-do voor publieke aankondiging, maar mag pas vlak vóór v1.0-tag.
- [ ] **Smoke-test productie** na merge: registreren → product → PBI → story → sprint → task → solo → completion. Ook mobile flow (DevTools UA-spoof of echt toestel). 12-min-job. - [ ] **Smoke-test productie** — checklist klaar in [docs/runbooks/v1-smoke-test.md](../runbooks/v1-smoke-test.md), 11 secties, ~15 min
- [ ] **PWA-installatie test** op echt mobiel (Android + iOS) — bevestig manifest landscape, controleer iOS-fallback via CSS-overlay. - [ ] **PWA-installatie test** op echt mobiel (Android + iOS) — bevestig manifest landscape, controleer iOS-fallback via CSS-overlay
- [ ] **Demo-policy regression-pass:** demo-user mag niets schrijven via UI/API/MCP. Drie-laags-demo-block. - [x] **Demo-policy regression-pass** — code-side gefixt: 3 gaps gedicht (toggleTodo, archiveCompletedTodos, leaveProduct). Drielaags-block geverifieerd voor alle mutation-actions
- [ ] **Privacy review:** zit er PII in logs? Sentry-events strippen? Avatar-upload-paden? - [x] **Privacy review** — Sentry sendDefaultPii=false; geen PII in logs; 4 debug-routes nu NODE_ENV-guarded (404 in productie)
- [ ] **README + Quick start verifiëren** op een schone clone — kan iemand binnen 10 min `npm run dev` draaien? - [x] **README + Quick start verifiëren** — test-count 69 → 445 gecorrigeerd, env-vars-tabel uitgebreid (CRON_SECRET, Sentry vars), CHANGELOG-link toegevoegd
- [ ] **Bump naar v1.0.0** + GitHub release met release-notes (mirror van wat in v0.9.0 zit, met v1-claim "stabiele MVP") - [x] **CHANGELOG.md** aangemaakt (Keep a Changelog formaat met [Unreleased] + [0.9.0])
- [ ] **CHANGELOG.md** aanmaken (ontbreekt nu) — een vlakke `Keep-a-Changelog`-formaat is genoeg - [ ] **Bump naar v1.0.0** + GitHub release met release-notes
--- ---

View file

@ -0,0 +1,100 @@
---
title: "v1.0 Smoke Test Checklist"
status: active
audience: [maintainer]
language: nl
last_updated: 2026-05-04
---
# v1.0 Smoke Test Checklist
Loop deze checklist door **vóór** je v1.0.0 tagt. Alleen handmatig — geen automation.
Time-budget: ~15 min.
**Productie-URL:** https://scrum4me.jp-visser.nl
---
## 1. Auth + dashboard (3 min)
- [ ] **Demo-login:** `demo` / `demo1234` → dashboard rendert, alle write-acties geven 403
- [ ] **Logout** vanuit user-menu → redirect naar `/login`
- [ ] **Register** met nieuwe gebruikersnaam → succesvol, redirect naar `/dashboard`
- [ ] **Login fout-flow:** verkeerd wachtwoord → generieke fout, geen leak
## 2. Mobile UA-redirect (2 min)
- [ ] DevTools mobile-emulatie iPhone 12 (UA-spoof) → log in → automatisch naar `/m/products/[id]/solo` (of `/m/settings` zonder actief product)
- [ ] Tablet-UA (iPad) → blijft op `/dashboard`
- [ ] Desktop blijft `/dashboard`
## 3. Product → PBI → Story → Sprint → Task happy-path (5 min)
- [ ] **Product aanmaken** (eigen account) → naam, code, DoD ingevuld
- [ ] **PBI aanmaken** in Product Backlog kolom → priority + status
- [ ] **Story aanmaken** onder PBI → titel + acceptatiecriteria
- [ ] **Sprint starten** met sprint-goal
- [ ] **Story slepen** vanuit Product Backlog naar Sprint Backlog
- [ ] **Task aanmaken** in Sprint → titel + implementation_plan
- [ ] **Task drag-and-drop** in Solo Paneel: To Do → Bezig → Klaar
- [ ] **Story-status auto-promotie:** alle taken DONE → story status DONE
## 4. Mobile shell (2 min — op echte phone of DevTools landscape iPhone 12)
- [ ] `/m/products/[id]` rendert in tab-mode (3 tabs onderaan: Backlog/Solo/Settings)
- [ ] Portrait-orientatie → rotate-overlay
- [ ] `/m/products/[id]/solo` toont 3-koloms kanban met horizontal scroll
- [ ] Task-detail dialog opent full-screen (`<640px`) — sticky header + footer bereikbaar
- [ ] `/m/settings` toont username + actieve product + logout-knop met bevestiging
- [ ] `/m/pair` toont QR-pairing-confirmation (M10 intact)
- [ ] **Geen** NavBar / AppIcon / Scrum4Me-tekst zichtbaar op `/m/*`
## 5. Edit-flows (1 min)
- [ ] **Pencil-icon op product-card** (dashboard hover) → ProductDialog opent
- [ ] **PBI ✎-icoon** (hover) → PbiDialog opent en saved
- [ ] **Story ✎-icoon** (sprint screen Sprint Backlog) → StoryDialog opent
- [ ] **Task ✎-icoon** (Taken-kolom) → TaskDialog opent
## 6. Demo-policy (1 min)
Inloggen als demo-gebruiker:
- [ ] PBI/Story/Task create-knoppen disabled met DemoTooltip
- [ ] Edit-iconen disabled
- [ ] Logout-knop bereikbaar (demo mag uitloggen)
- [ ] Productselector gaat door op view, maar Activeer-acties geven 403
## 7. Rate-limiting (steekproef, 1 min)
- [ ] Probeer 31 PBIs in <60s aan te maken via UI 31e geeft toast "Te veel acties achter elkaar"
- [ ] (Optioneel) `bash scripts/test-api.sh` → alle endpoints groen
## 8. Realtime (1 min)
- [ ] Open `/products/[id]/solo` in twee browsers (één als owner, één als teamlid)
- [ ] Status-toggle in browser A → ziet binnen 1s in browser B (SSE-pipe)
- [ ] Claude-vraag binnenkrijgen → bell-badge verschijnt zonder refresh
## 9. Debug-routes (productie afgeschermd)
- [ ] `https://scrum4me.jp-visser.nl/debug-env`**404** (NODE_ENV-guard)
- [ ] `https://scrum4me.jp-visser.nl/debug-realtime` → **404**
- [ ] `https://scrum4me.jp-visser.nl/api/debug/realtime-stream` → **404**
## 10. Lighthouse op happy-path
- [ ] `/login` — a11y ≥95
- [ ] `/dashboard` — a11y ≥95
- [ ] `/products/[id]` — a11y ≥95 (was 86 vóór PR #88)
- [ ] `/products/[id]/sprint` — a11y ≥95
- [ ] `/products/[id]/solo` — a11y ≥95
> Performance score in dev-mode is misleidend (dev-bundles, Chrome-extensies).
> Test op productie of `npm run build && npm run start` voor betrouwbare cijfers.
## 11. Rollback-trigger
Als één van bovenstaande faalt:
- Vercel dashboard → Deployments → vorige Ready deploy → "Promote to Production"
- Issue-titel: "v1.0 smoke-test failure: \<korte beschrijving\>"
- Tag v1.0.0 NIET totdat alles groen is