- @tanstack/react-query with QueryClientProvider in providers/index.tsx - Typed api-client.ts fetch wrapper with ApiError class + apiDownload - Service modules: members, distributions, stock, reports, dashboard, portal, staff - Offline banner component (onlineManager subscription) - API error boundary with retry button - Loading skeleton components (card, table, chart, form, dashboard) - i18n for error/loading states (de/en)
22 KiB
CannaManage Sprint 5 Plan — Multi-Persona Review Panel
Date: 2026-06-12
Reviewed Document: docs/sprint-5/cannamanage-sprint5-plan.md (v2, ~840 lines)
Review Method: 6-persona stakeholder simulation, scoring on 4 dimensions (0–100%)
Iteration: 1
v2 Decisions Incorporated
The Sprint 5 plan v2 incorporates all Q&A decisions from the planning session:
- ✅ @tanstack/react-query — caching, refetch, optimistic updates (Q1)
- ✅ Per-component loading — independent card/table loading, no full-page blocking (Q2)
- ✅ Stale-while-revalidate + "Offline" banner — graceful degradation (Q3)
- ✅ Full CRUD staff management — list, invite, edit perms, revoke (Q4)
- ✅ Dual seed data strategy — SQL for dev/test + API-driven for system E2E (Q5)
- ✅ Next.js 15.2.8 → 15.5.18 upgrade — addresses 8+ Snyk CVEs in Phase 1 (Bonus)
1. 👤 Club Member (End User)
"I'm a regular member of a cannabis social club. I want to see my quota, pick up my cannabis, and check my history — now with real data instead of fake numbers."
Findings
| # | Type | Observation |
|---|---|---|
| 1 | ✅ Positive | Real-time quota data — my portal dashboard now shows actual usage from the database, not mock numbers. I can trust the "28g remaining" display because it's coming from the real backend with server-authoritative calculations. |
| 2 | ✅ Positive | Stale-while-revalidate — if my phone briefly loses signal in the club, I still see my last-known quota instead of a blank page. The "Offline" banner is unobtrusive. |
| 3 | ✅ Positive | Per-component loading — when I open the portal, I see my quota radial load first (fast), then history loads separately. No frozen full-page spinner. |
| 4 | ✅ Positive | Distribution history is live — I can see a distribution appear in my history immediately after the staff records it. No manual refresh needed. |
| 5 | ⚠️ Minor | No push notification for distributions — I still won't get notified when a distribution is recorded against my account. I have to check manually. (Acknowledged as Sprint 6+ scope.) |
| 6 | ✅ Positive | Portal auth is explicitly planned — Phase 5 specifically calls out wiring the portal login flow with session-based auth. My separate login experience is preserved. |
Scores
| Dimension | Score | Rationale |
|---|---|---|
| Precision | 92% | Portal integration is explicitly scoped in Phase 5 with specific endpoint mappings (/portal/dashboard, /portal/history). Loading patterns and offline handling are precisely defined. |
| Correctness | 94% | React Query's stale-while-revalidate behavior correctly serves cached data during network issues. Quota calculations remain server-authoritative. Session-based portal auth is appropriate for member-facing UI. |
| Usability | 88% | Per-component loading and offline resilience significantly improve my experience. Only gap: no proactive notifications when distributions are recorded or quotas reset. |
| Usefulness | 90% | The transition from mock to real data is exactly what I need — my portal becomes trustworthy and useful for tracking my actual consumption. |
Composite Score: 91%
Remaining Gaps (minor, Sprint 6+)
- Push/email notifications for distributions and quota resets
- PWA manifest for mobile home-screen shortcut
2. 🏢 Club Owner / Vorstand (Business Owner)
"I run the Anbauvereinigung. I need staff management, real reports for the Behörde, and confidence that the system works end-to-end."
Findings
| # | Type | Observation |
|---|---|---|
| 1 | ✅ Positive | Full CRUD staff management — Phase 6 covers list, invite, edit permissions, revoke. All 8 granular permissions are specified. This is exactly what I need to delegate work to my team without giving everyone full access. |
| 2 | ✅ Positive | Real report downloads — Phase 5 wires PDF/CSV report generation from actual database data. Monthly reports for the Behörde will contain real distribution records, not mock data. |
| 3 | ✅ Positive | System test harness — Phase 7 proves the full stack works end-to-end with a deterministic test flow. This gives me confidence that deployments won't break critical workflows. |
| 4 | ✅ Positive | Docker Compose full stack — I can run the entire system locally with one command for demos to my Vorstand or to show the Behörde during audits. |
| 5 | ✅ Positive | Seed data for dev/test — the dual strategy (SQL + API-driven) means I can quickly spin up a realistic environment for training new staff. |
| 6 | ⚠️ Minor | Staff activity log deferred — I can't yet see what actions my staff performed (who recorded which distribution). Deferred to Sprint 6. |
| 7 | ⚠️ Minor | Club settings UI still pending — email whitelist, prevention officer limits, and other club-level configuration isn't in Sprint 5 scope. |
| 8 | ✅ Positive | Permission chips are color-coded — the plan specifies visual badges per permission, making it easy to scan who has what access at a glance. |
Scores
| Dimension | Score | Rationale |
|---|---|---|
| Precision | 93% | Staff management is thoroughly specified: 8 named permissions, invite flow, pending status handling, revoke confirmation. API endpoints are concrete. |
| Correctness | 91% | Permission model matches the existing StaffPermission enum in the backend. RBAC enforcement (403 for unauthorized) is explicitly tested. Report generation uses established PDF/CSV generation code. |
| Usability | 85% | Full CRUD staff management covers my daily needs. Missing activity log means I can't audit staff behavior yet. Club settings require direct backend config. |
| Usefulness | 92% | This sprint delivers the #1 missing piece: staff management. Combined with real reports and system tests, I can now operate the club with confidence. |
Composite Score: 90%
Remaining Gaps (Sprint 6)
- Staff activity log (who did what, when)
- Club settings UI (email whitelist, prevention officer limits)
3. 💻 Developer (Technical Implementer)
"I'm the one building this. Is the integration architecture sound? Are dependencies clear? Is the testing strategy robust?"
Findings
| # | Type | Observation |
|---|---|---|
| 1 | ✅ Positive | React Query architecture is well-designed — two-layer client (server-side apiServer() + client-side apiFetch()) with Next.js rewrite proxy keeps auth tokens server-side. Clean separation of concerns. |
| 2 | ✅ Positive | Service hooks pattern is consistent — every domain gets the same structure: useQuery for reads, useMutation with invalidateQueries for writes. Easy to replicate across 7 service files. |
| 3 | ✅ Positive | Next.js upgrade in Phase 1 — doing the security upgrade before integration work prevents debugging whether issues come from the upgrade or new code. Smart sequencing. |
| 4 | ✅ Positive | Docker Compose is production-like — multi-stage Maven build, PostgreSQL health checks, proper service dependencies. Build cache optimization mentioned for developer experience. |
| 5 | ✅ Positive | Error handling is comprehensive — API error → status-specific German toast message mapping is precisely defined (401/403/409/500). Offline detection returns "Verbindungsfehler" toast. |
| 6 | ✅ Positive | Dual test strategy preserved — mock E2E tests (<30s) for rapid iteration, system tests (minutes) for full-stack confidence. Both have clear value and separate configs. |
| 7 | ⚠️ Minor | No API versioning strategy discussed — if backend DTOs change, there's no contract test or OpenAPI schema validation to catch frontend/backend drift. Currently relies on manual verification during Phase 3. |
| 8 | ✅ Positive | Risk assessment is realistic — acknowledges Docker build slowness, CORS container issues, Playwright startup flakiness, and provides concrete mitigations for each. |
| 9 | ✅ Positive | Seed data with ON CONFLICT DO NOTHING — idempotent SQL ensures repeatable migrations don't fail on re-runs. Correct Flyway pattern. |
| 10 | ⚠️ Minor | Optimistic update complexity — Phase 4 mentions optimistic updates for distributions, but doesn't detail rollback behavior when the backend rejects (e.g., quota exceeded after optimistic decrement). React Query's onMutate/onError rollback pattern should be specified. |
Scores
| Dimension | Score | Rationale |
|---|---|---|
| Precision | 91% | Code examples for every pattern (React Query, CORS, Docker, error handling, seeds). File paths are explicit. Only missing: optimistic update rollback detail. |
| Correctness | 93% | Architecture decisions are technically sound. React Query + Next.js rewrite proxy is the established pattern. Docker multi-stage build is correct. Flyway repeatable migration is appropriate for seed data. |
| Usability | 88% | Developer experience is considered: DevTools in dev, skeleton components pre-built, one-command Docker startup. Phase sequencing is logical. Slightly high effort estimate (10.5 days single worker) may compress. |
| Usefulness | 92% | This plan is immediately actionable. Every phase has acceptance criteria checkboxes. Directory structure shows exact file placement. Minimal ambiguity in implementation order. |
Composite Score: 91%
Remaining Gaps (nice-to-have)
- OpenAPI contract tests or schema validation between frontend types and backend DTOs
- Explicit optimistic update rollback pattern documentation
4. 🛡️ Compliance Officer (CanKG / BfArM Regulatory)
"I ensure the system meets CanKG regulatory requirements. Distribution records must be immutable, quotas enforced, and audit trails available."
Findings
| # | Type | Observation |
|---|---|---|
| 1 | ✅ Positive | Quota enforcement is server-authoritative — the plan explicitly routes quota checks through the backend (GET /compliance/quota/{memberId}), not client-side calculations. The 409 response for exceeded limits prevents frontend manipulation. |
| 2 | ✅ Positive | Under-21 differentiation preserved — Phase 4 acceptance criteria explicitly states "Under-21 members see 30g monthly limit enforced." The reduced quota is backend-enforced. |
| 3 | ✅ Positive | System test validates compliance flow — the test harness (Phase 7) includes a distribution → quota update verification step, proving the enforcement pipeline works end-to-end. |
| 4 | ✅ Positive | Real PDF reports for Behörde — the monthly report is generated from actual database records via the established PdfReportGenerator. No more mock data in regulatory documents. |
| 5 | ✅ Positive | Staff permission model controls access — only users with RECORD_DISTRIBUTIONS permission can create distributions. MANAGE_COMPLIANCE controls compliance dashboard access. Least-privilege principle applied. |
| 6 | ⚠️ Minor | No mention of distribution record immutability in Sprint 5 — Sprint 4 established audit trail and 🔒 indicators. Sprint 5 should confirm these survive the integration (i.e., the real backend enforces immutability, not just the frontend). |
| 7 | ✅ Positive | Seed data includes realistic compliance scenarios — 5 members with varying ages (including one born 2005 = under-21), 3 batches with different strains. Good for testing age-based quota differentiation. |
Scores
| Dimension | Score | Rationale |
|---|---|---|
| Precision | 88% | Quota enforcement endpoints and error codes are precisely specified. Under-21 handling is explicit. Minor gap: immutability guarantee from backend not re-confirmed for Sprint 5. |
| Correctness | 92% | Server-authoritative quota checks with 409 response is the correct enforcement pattern. Staff permissions align with CanKG operational requirements. Age calculation from date_of_birth in seed data is testable. |
| Usability | 86% | Compliance officers benefit from real reports and system tests proving the workflow. Missing: no dedicated compliance dashboard enhancements in Sprint 5 (existing dashboard from Sprint 3 carries forward). |
| Usefulness | 90% | The transition to real data makes compliance reporting meaningful. System tests provide regulatory confidence. Staff permissions enable proper access control documentation for Behörde audits. |
Composite Score: 89%
Remaining Gaps (Sprint 6)
- Explicit immutability confirmation at the API level (backend
@PreAuthorize+ DB trigger or soft-delete pattern) - Monthly report auto-sealing (cryptographic timestamp)
- Compliance dashboard enhancements (violation alerts, trend charts)
5. 🔒 Security Auditor
"I review the system for security vulnerabilities. Authentication, authorization, data protection, and secure communication are my focus."
Findings
| # | Type | Observation |
|---|---|---|
| 1 | ✅ Positive | Next.js upgrade 15.2.8 → 15.5.18 — proactively addresses 8+ known CVEs including SSRF, authentication bypass, and resource exhaustion. Doing this in Phase 1 before integration work is the correct priority. |
| 2 | ✅ Positive | JWT stays server-side — the proxy architecture (/api/backend/* rewrite) means JWT tokens never touch the browser. Client-side fetches use credentials: "include" with session cookies only. Reduced token theft surface. |
| 3 | ✅ Positive | CORS configuration is restrictive — only localhost:3000 and frontend:3000 (Docker) are whitelisted. allowCredentials: true with explicit origins (not *). maxAge: 3600L limits preflight cache. |
| 4 | ✅ Positive | Permission-based authorization — 8 granular permissions with enforcement at API level (403 for insufficient permissions). Only ADMIN + MANAGE_STAFF holders can modify staff. Defense in depth. |
| 5 | ✅ Positive | Error messages don't leak internals — the useApiErrorHandler maps status codes to generic German messages. No stack traces, no internal paths, no SQL errors exposed to the client. |
| 6 | ⚠️ Minor | Seed data contains plaintext password placeholders — $2a$10$...bcrypt... in the SQL is obviously a placeholder, but the plan should note that real bcrypt hashes must be generated during implementation. Password admin123 is acceptable for test-seed profile only. |
| 7 | ✅ Positive | Docker secrets in environment variables — JWT_SECRET and POSTGRES_PASSWORD are in docker-compose with clear "change-in-prod" suffixes. The plan acknowledges these are dev-only values. |
| 8 | ⚠️ Minor | No rate limiting mentioned — the API endpoints (especially /auth/login, /staff/invite) should have rate limiting to prevent brute-force attacks. Not Sprint 5 scope but worth noting. |
| 9 | ✅ Positive | Portal uses session auth (not JWT) — member portal correctly uses a separate auth mechanism (session cookies + CSRF) appropriate for public-facing user interfaces. |
| 10 | ✅ Positive | System test validates auth flows — the test harness logs in as both admin and member, confirming both auth paths work correctly and are isolated from each other. |
Scores
| Dimension | Score | Rationale |
|---|---|---|
| Precision | 90% | CORS config is exact (origins, methods, headers, maxAge). Auth architecture is clearly layered (server-side JWT, client-side session). Permission model is enumerated. |
| Correctness | 92% | Next.js upgrade addresses real CVEs. Server-side token handling is the security best practice. CORS with explicit origins + credentials is correctly restrictive. bcrypt for passwords is appropriate. |
| Usability | 87% | Security measures don't impede development (CORS allows localhost, DevTools in dev mode, test-seed profile for easy setup). Good security/DX balance. |
| Usefulness | 91% | Proactive CVE remediation, server-side token architecture, and granular permissions provide a solid security foundation. Rate limiting and 2FA are reasonable Sprint 6+ deferrals. |
Composite Score: 90%
Remaining Gaps (Sprint 6+)
- Rate limiting on auth endpoints
- 2FA (TOTP) for admin accounts
- CSRF token handling for portal session auth (not explicitly mentioned)
- Content Security Policy headers (carried forward from Sprint 4)
6. 🎨 UX Designer
"I focus on user experience, interaction design, accessibility, and visual consistency across the application."
Findings
| # | Type | Observation |
|---|---|---|
| 1 | ✅ Positive | Per-component skeleton loaders — the plan includes specific skeleton components (skeleton-card.tsx, skeleton-table.tsx) that show shimmer during loading. This is better than a full-page spinner — users see progressive content appear. |
| 2 | ✅ Positive | Offline banner is non-blocking — "Stale-while-revalidate + banner" means users can still interact with cached data while the banner communicates status. No modal or blocking overlay. |
| 3 | ✅ Positive | Error states have retry actions — error-state.tsx component includes a retry button. Users aren't stuck — they can attempt recovery without navigating away. |
| 4 | ✅ Positive | Permission chips are color-coded — staff permissions displayed as visual badges make the permission grid scannable at a glance. Pattern is consistent with existing UI components (shadcn Badge). |
| 5 | ✅ Positive | i18n for staff management — Phase 6 explicitly includes adding German + English strings. Language consistency maintained across the new feature. |
| 6 | ⚠️ Minor | No loading animation specification — skeleton components are mentioned but no design tokens for shimmer timing, color, or animation duration. Should match existing shadcn skeleton defaults. |
| 7 | ⚠️ Minor | Optimistic update UX not detailed — when a distribution is recorded optimistically, what does the user see? Immediate list update with a subtle "saving..." indicator? Or just instant appearance? The undo pattern for failed optimistic updates needs visual design. |
| 8 | ✅ Positive | Toast messages are in German — error toasts use natural German ("Sitzung abgelaufen", "Kontingent überschritten", "Verbindungsfehler"). Consistent with the i18n-first approach from Sprint 4. |
| 9 | ✅ Positive | Staff invite dialog with checkboxes — the permission editor uses checkbox grid (8 permissions), which is the appropriate control for multi-select binary options. Familiar pattern. |
| 10 | ⚠️ Minor | No empty state illustrations — empty states get messages but no visual illustrations. Sprint 4 defined empty state messages; Sprint 5 should carry them forward with real API responses. |
Scores
| Dimension | Score | Rationale |
|---|---|---|
| Precision | 87% | Loading patterns (skeletons, error state, retry) are specified as components. Toast messages have exact copy. Minor gap: no animation timing or optimistic update visual feedback details. |
| Correctness | 90% | Per-component loading is the UX best practice for data-heavy dashboards. Stale-while-revalidate preserves user context during connectivity issues. Checkbox grid for permissions is appropriate. |
| Usability | 86% | Progressive loading, offline resilience, and German error messages create a polished experience. Gaps: optimistic update visual feedback and empty state illustrations not specified. |
| Usefulness | 88% | The UX improvements make the transition from mock to real data transparent to users. Loading and error states prevent confusion. Staff management UI follows established patterns. |
Composite Score: 88%
Remaining Gaps (nice-to-have)
- Optimistic update visual feedback pattern (saving indicator, rollback animation)
- Empty state illustrations (consistent with shadcn/ui style)
- Loading animation design tokens (shimmer timing, colors)
- Accessibility audit for new staff management components (focus management in dialogs)
Summary & Composite Scores
| # | Persona | Composite Score | Top Concern |
|---|---|---|---|
| 1 | 👤 Club Member | 91% | No push notifications for distributions |
| 2 | 🏢 Club Owner | 90% | Staff activity log deferred |
| 3 | 💻 Developer | 91% | Optimistic update rollback pattern unspecified |
| 4 | 🛡️ Compliance Officer | 89% | Immutability re-confirmation at API level |
| 5 | 🔒 Security Auditor | 90% | Rate limiting on auth endpoints |
| 6 | 🎨 UX Designer | 88% | Optimistic update visual feedback unspecified |
Overall Score: 90% ✅
Verdict: APPROVED — exceeds 85% threshold on first pass.
Dimension Averages
| Dimension | Average | Min | Max |
|---|---|---|---|
| Precision | 90% | 87% (UX) | 93% (Owner) |
| Correctness | 92% | 90% (UX) | 94% (Member) |
| Usability | 87% | 85% (Owner) | 88% (Dev/Member) |
| Usefulness | 91% | 88% (UX) | 92% (Dev/Owner) |
Consolidated Improvement Suggestions (Non-Blocking)
These are recommendations for implementation-time refinement, not plan revisions:
| # | Category | Suggestion | Priority |
|---|---|---|---|
| 1 | Technical | Add explicit optimistic update rollback pattern (React Query onMutate/onError) |
Medium |
| 2 | Security | Add rate limiting to /auth/login and /staff/invite endpoints |
Medium |
| 3 | Compliance | Re-confirm distribution immutability at API level (not just UI lock icons) | Medium |
| 4 | UX | Define visual feedback for optimistic updates (saving indicator, error rollback) | Low |
| 5 | Technical | Consider OpenAPI schema validation for frontend/backend DTO contract | Low |
| 6 | UX | Carry forward empty state illustrations from Sprint 4 design system | Low |
| 7 | Security | Document CSRF token handling for portal session auth | Low |
Comparison with Sprint 4 Review
| Metric | Sprint 4 (Iteration 1) | Sprint 4 (Iteration 2) | Sprint 5 (This Review) |
|---|---|---|---|
| Overall Score | 78% | 88% | 90% |
| Iterations Needed | 2 | — | 1 ✅ |
| Blocking Gaps | 8 | 0 | 0 |
| Non-Blocking Gaps | 5 | 3 | 7 (minor) |
Sprint 5 plan achieves a higher first-pass score than Sprint 4's final iteration, demonstrating that learnings from the Sprint 4 review process were incorporated into the planning methodology. All non-blocking gaps are implementation-time refinements rather than plan-level deficiencies.