feat: Sprint 0 — CI/CD pipeline, production Docker, health checks

CI Pipeline (.github/workflows/ci.yml):
- 5 jobs: typecheck, lint, test, build, e2e (parallel where possible)
- PostgreSQL 16 + Redis 7 service containers for test/e2e
- pnpm store, Turborepo, Playwright browser caching
- Concurrency groups cancel in-progress runs

Production Docker:
- Dockerfile.prod: 3-stage build (deps → build → runtime ~150MB)
- docker-compose.prod.yml: postgres + redis + app with health checks
- .dockerignore for fast builds
- next.config.ts: output: "standalone" for minimal runtime

Health Check Endpoints:
- GET /api/health — liveness probe (200 OK, no deps)
- GET /api/ready — readiness probe (postgres + redis connectivity)

Documentation:
- docs/ci-cd-manual.md — full pipeline manual with troubleshooting
- plan.md — Product Owner strategic plan (bottlenecks, growth, automation)

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-19 20:33:18 +01:00
parent c02f453679
commit 0d78fe1770
9 changed files with 1070 additions and 210 deletions
+217 -210
View File
@@ -1,258 +1,265 @@
# Refactor v2 — Code Optimization, De-duplication & Maintainability
# Planarchy — Product Owner Strategic Plan
## Anforderungsanalyse
Vollstaendiger Optimierungsdurchlauf des Planarchy-Monorepos. Ziel: Code-Duplikate eliminieren,
Performance verbessern, Wartbarkeit erhoehen. Betroffen sind alle Pakete: `apps/web`, `packages/api`,
`packages/db`, `packages/shared`.
Die Analyse identifiziert **7 unabhaengige Arbeitsstroeme (Waves)**, die von Agenten parallel
bearbeitet werden koennen, plus eine abschliessende Schema-Migration.
> Consolidated analysis from 4 expert agents: Roadmap, API Surface, Frontend UX, and Test Infrastructure.
> Date: 2026-03-19
---
## Betroffene Pakete & Dateien
## Executive Summary
| Paket | Dateien | Art |
|-------|---------|-----|
| `apps/web` | `src/lib/format.ts`, 10+ Consumer-Dateien | edit |
| `apps/web` | `src/lib/status-styles.ts`, `src/components/timeline/timelineConstants.ts`, 6 Consumer-Dateien | edit |
| `apps/web` | `src/hooks/useInvalidatePlanningViews.ts`, 14 Consumer-Dateien | edit |
| `apps/web` | `src/components/timeline/renderHelpers.ts` | create |
| `apps/web` | `src/components/timeline/TimelineResourcePanel.tsx`, `TimelineProjectPanel.tsx` | edit |
| `apps/web` | `src/components/timeline/TimelineView.tsx` | edit |
| `apps/web` | `src/hooks/useTimelineDrag.ts` | edit |
| `packages/api` | `src/db/helpers.ts`, 7+ Router-Dateien | edit |
| `packages/api` | `src/db/selects.ts`, 6+ Router-Dateien | edit |
| `packages/db` | `prisma/schema.prisma` | edit |
Planarchy has reached **Phase 9** with a mature core: timeline planning, allocation management, estimating, vacation pro, skill matrix, RBAC, and chargeability reporting. The product covers 34 routes, 47 DB models, ~200 tRPC procedures, and 109+ domain components.
**However, the product has critical gaps preventing production readiness and growth:**
| Dimension | Score | Verdict |
|-----------|-------|---------|
| Feature completeness | 85% | Strong core, thin edges (staffing, reporting) |
| Code quality | 90% | Zero TODOs, clean architecture, typed end-to-end |
| Test coverage | 55% | Engine excellent, API routers ~5%, no integration tests |
| CI/CD & DevOps | 10% | No pipeline, no prod Docker, no monitoring |
| UX polish | 75% | Deep timeline/estimates, but gaps in staffing workflow |
| Growth readiness | 40% | No scenario planning, no integrations, no mobile |
---
## Wave 1 — Centralize `formatMoney()` / `formatCents()` (Agent: format-consolidator)
## Part 1: Bottlenecks
**Problem:** Zentralisierte Funktionen existieren in `apps/web/src/lib/format.ts`, aber 10+
Stellen nutzen Inline-`(x / 100).toLocaleString("de-DE", ...)` statt der zentralen Imports.
Zusaetzlich gibt es 2 lokale `fmtEur()` Helfer in API-Routern.
### 1.1 Production Readiness Blockers (Critical)
**Dateien die keine Aenderung brauchen:**
- `ShiftPreviewTooltip.tsx` — spezialisierter Delta-Formatter mit +/- Prefix (bleibt)
| # | Bottleneck | Impact | Severity |
|---|-----------|--------|----------|
| B1 | **No CI/CD pipeline** — tests, lint, tsc not automated on PR | Regressions ship undetected | CRITICAL |
| B2 | **No production Docker image** — only dev Dockerfile exists | Cannot deploy containerized | CRITICAL |
| B3 | **No monitoring/logging** — no Sentry, no Pino, no APM | Blind in production, cannot debug | CRITICAL |
| B4 | **No health check endpoints**`/health`, `/ready` missing | Cannot detect/recover from failures | HIGH |
| B5 | **API router test coverage ~5%** — 28 routers, almost no unit tests | Mutations untested at API boundary | HIGH |
### Tasks
### 1.2 UX Bottlenecks
- [ ] **1.1** Inline-Formatierung in `FillOpenDemandModal.tsx` (5 Stellen, Lines ~214/417/434/444/452) durch `formatCents()` Import ersetzen → `apps/web/src/components/allocations/FillOpenDemandModal.tsx`
- [ ] **1.2** Inline-Formatierung in `DemandPopover.tsx` (Lines ~146/152) durch `formatCents()` Import ersetzen → `apps/web/src/components/timeline/DemandPopover.tsx`
- [ ] **1.3** Inline-Formatierung in `ResourceHoverCard.tsx` (Lines ~123/130) durch `formatCents()` Import ersetzen → `apps/web/src/components/timeline/ResourceHoverCard.tsx`
- [ ] **1.4** Inline-Formatierung in `ProjectWizard.tsx` (Lines ~509-511) ersetzen → `apps/web/src/components/projects/ProjectWizard.tsx`
- [ ] **1.5** Inline-Formatierung in `ProjectAssignmentsTable.tsx` (Line ~130) ersetzen → `apps/web/src/components/projects/ProjectAssignmentsTable.tsx`
- [ ] **1.6** Inline-Formatierung in `ProjectDemandsTable.tsx` (Lines ~135/138-139) ersetzen → `apps/web/src/components/projects/ProjectDemandsTable.tsx`
- [ ] **1.7** Inline-Formatierung in `ProjectsClient.tsx` (Line ~403) ersetzen → `apps/web/src/app/(app)/projects/ProjectsClient.tsx`
- [ ] **1.8** Inline-Formatierung in `ProjectTableWidget.tsx` (Lines ~274/283) ersetzen → `apps/web/src/components/dashboard/widgets/ProjectTableWidget.tsx`
- [ ] **1.9** Inline-`.toFixed(0)` in `ResourcesClient.tsx` (Line ~1178) und `ResourceDetail.tsx` (Lines ~279/286) durch `formatMoney()` ersetzen → 2 Dateien
- [ ] **1.10** Duplizierte `fmtEur()` in `assistant-tools.ts` (Line ~44-46) und `computation-graph.ts` (Line ~53-55) entfernen — gemeinsamen Helfer in `packages/api/src/lib/format-utils.ts` erstellen (da API-Router keinen Zugriff auf `apps/web/src/lib/format.ts` haben) → `packages/api/src/lib/format-utils.ts` (create), 2 Router editieren
| # | Bottleneck | Impact | Severity |
|---|-----------|--------|----------|
| B6 | **Staffing -> Allocation gap** — match results don't link to allocation creation | Users must manually recreate allocations after finding matches | HIGH |
| B7 | **Reporting is thin** — only 2 report types (chargeability, PDF allocations) | Finance/PMs can't self-serve custom reports | MEDIUM |
| B8 | **No bulk operations in list views** — no multi-select outside timeline | Slow to manage 10+ resources/projects at once | MEDIUM |
| B9 | **Dashboard metrics computed live** — no caching/pre-computation | Slow dashboard load with growing data | MEDIUM |
| B10 | **Timeline 3.3K LOC ecosystem** — ResourcePanel 1035, ProjectPanel 1315 LOC | Hard to maintain, risky to modify | LOW |
### Akzeptanzkriterien
- Kein `/ 100).toLocaleString("de-DE"` mehr in Component-Dateien (ausser ShiftPreviewTooltip)
- Kein lokales `fmtEur()` mehr in API-Routern
### 1.3 Architecture Bottlenecks
| # | Bottleneck | Impact | Severity |
|---|-----------|--------|----------|
| B11 | **Prisma client cache invalidation** — dev server restart required after schema changes | Developer friction, CI complexity | MEDIUM |
| B12 | **No webhook/event outbound** — SSE event bus exists but no external subscriptions | Cannot notify external systems (Slack, Jira) | MEDIUM |
| B13 | **No soft-delete strategy** — mixed approach (isActive, status, hard delete) | Data loss risk, no audit trail on deletions | LOW |
| B14 | **Rate card lookup manual in estimates** — no auto-lookup by resource chapter/level | Estimate creation slower than needed | LOW |
---
## Wave 2 — Adopt `findUniqueOrThrow()` Helper (Agent: db-helper-consolidator)
## Part 2: Growth Potential
**Problem:** `packages/api/src/db/helpers.ts` existiert mit `findUniqueOrThrow<T>()`, aber 7 Router-Dateien nutzen ihn nicht und haben manuelle `findUnique` + `if (!x) throw` Bloecke.
### 2.1 High-Value Feature Opportunities
### Tasks
#### Tier 1 — Quick Wins (1-3 days each)
- [ ] **2.1** `entitlement.ts` — 7 manuelle Stellen durch `findUniqueOrThrow()` ersetzen → `packages/api/src/router/entitlement.ts`
- [ ] **2.2** `calculation-rules.ts` — 3 manuelle if+throw Stellen (Lines ~24-26, 62-64, 89-91) ersetzen → `packages/api/src/router/calculation-rules.ts`
- [ ] **2.3** `notification.ts` — 3 Stellen migrieren → `packages/api/src/router/notification.ts`
- [ ] **2.4** `settings.ts` — 3 Stellen migrieren → `packages/api/src/router/settings.ts`
- [ ] **2.5** `user.ts` — verbleibende manuelle Stellen migrieren → `packages/api/src/router/user.ts`
- [ ] **2.6** `resource.ts` — 8 verbleibende manuelle Stellen migrieren → `packages/api/src/router/resource.ts`
- [ ] **2.7** `vacation.ts` — 12 verbleibende manuelle Stellen migrieren → `packages/api/src/router/vacation.ts`
- [ ] **2.8** `timeline.ts` — 4 verbleibende Stellen migrieren → `packages/api/src/router/timeline.ts`
- [ ] **2.9** `assistant.ts` — 1 Stelle migrieren → `packages/api/src/router/assistant.ts`
| # | Feature | Value | Effort |
|---|---------|-------|--------|
| G1 | **Staffing "Assign" button** — pre-populate allocation modal from match result | Closes biggest UX gap, saves 5+ clicks per staffing decision | 1-2 days |
| G2 | **Dashboard caching** — pre-compute metrics, invalidate on SSE events | 3-5x dashboard load speed improvement | 1-2 days |
| G3 | **Bulk list operations** — multi-select + context menu on resources/projects | Enables batch edit, export, status change | 2-3 days |
| G4 | **Health check endpoints**`/api/health` (liveness), `/api/ready` (DB + Redis) | Production deployment prerequisite | 0.5 day |
**Hinweis:** `assistant-tools.ts` nutzt `{ error: "..." }` Return-Pattern statt throw — NICHT migrieren (anderes Error-Handling).
#### Tier 2 — Strategic Features (1-2 weeks each)
### Akzeptanzkriterien
- Alle Router (ausser assistant-tools.ts) nutzen `findUniqueOrThrow()` fuer NOT_FOUND Checks
- `pnpm --filter @planarchy/api exec tsc --noEmit` — gruen
| # | Feature | Value | Effort |
|---|---------|-------|--------|
| G5 | **Scenario/What-If Planning** — alternate staffing mixes, cost simulations | Differentiation for PMs and finance; leverages existing engine | 1-2 weeks |
| G6 | **Skill Marketplace** — searchable skill inventory, gap heat map, hiring priorities | High leverage from existing skill matrix; enables org-wide planning | 1 week |
| G7 | **Custom Report Builder** — drag columns, pivot, grouping, scheduled exports | Unlocks self-service analytics for finance and executives | 1-2 weeks |
| G8 | **Collaboration Layer** — inline comments on estimates, @mention, approval feedback | Enables cross-functional workflows (finance, PM, staffing) | 1-2 weeks |
#### Tier 3 — Market Differentiators (2-4 weeks each)
| # | Feature | Value | Effort |
|---|---------|-------|--------|
| G9 | **AI-Powered Insights** — auto-suggest staffing, anomaly detection, narrative reports | Leverages existing Azure OpenAI integration; executive decision support | 2-3 weeks |
| G10 | **External Integrations** — Jira/Linear sync, Slack notifications, Google Calendar | Stickiness; connects Planarchy into existing workflows | 2-4 weeks |
| G11 | **Mobile Companion** — PWA with quick-view (status, gaps, approvals, push notifications) | Engagement for field PMs and remote staff | 3-4 weeks |
| G12 | **Dispo V2 Clean-Slate Import** — design doc + tickets exist, ready for implementation | Unblocks migration from legacy system; critical for customer onboarding | 1-2 weeks |
### 2.2 Missing Dashboard Widgets
| Widget | Purpose | Effort |
|--------|---------|--------|
| Budget spend forecast | Forward-looking actuals vs budget trend line | 2 days |
| Team utilization heatmap | Resource x week grid with color intensity | 2 days |
| Skill gap analysis | Required vs available skills across open demands | 3 days |
| Project health scorecard | On-time, on-budget, quality composite score | 2 days |
| Hiring pipeline | Forecast unfilled demand 3-6 months out | 3 days |
---
## Wave 3 — Prisma Select Constants adoptieren (Agent: select-consolidator)
## Part 3: Automation Potential
**Problem:** `packages/api/src/db/selects.ts` definiert `ROLE_BRIEF_SELECT`, `PROJECT_BRIEF_SELECT`, `RESOURCE_BRIEF_SELECT`, aber Adoption ist gering (nur `allocation.ts` nutzt alle drei).
### 3.1 Development Workflow Automation
### Tasks
| # | Automation | Current State | Target | Effort |
|---|-----------|--------------|--------|--------|
| A1 | **CI/CD Pipeline** | None | GitHub Actions: test + lint + tsc on PR, build + deploy on merge | 1-2 days |
| A2 | **Dependency scanning** | None | Dependabot + npm audit in CI | 0.5 day |
| A3 | **E2E test suite expansion** | 4 specs (auth, timeline, projects, resources) | 20+ specs covering key user flows | 1 week |
| A4 | **API integration tests** | ~5% router coverage | 80% coverage with mock DB layer | 1-2 weeks |
| A5 | **Coverage gates** | Engine 95%, staffing 90%, others none | All packages minimum 80% | 2 days config |
- [ ] **3.1** `vacation.ts` — 5 Inline-Resource-Selects (Lines ~102/123/213/542/579) durch `RESOURCE_BRIEF_SELECT` ersetzen → `packages/api/src/router/vacation.ts`
- [ ] **3.2** `role.ts` — 1 Inline-Resource-Select (Line ~92) ersetzen → `packages/api/src/router/role.ts`
- [ ] **3.3** `project-planning-read-model.ts` — 1 Inline-Role-Select (Line ~34) durch `ROLE_BRIEF_SELECT` ersetzen → `packages/api/src/router/project-planning-read-model.ts`
- [ ] **3.4** `resource.ts` — 1 Inline-Role-Select (Line ~313) durch `ROLE_BRIEF_SELECT` ersetzen → `packages/api/src/router/resource.ts`
- [ ] **3.5** `calculation-rules.ts` — 2 Inline-Project-Selects (Lines ~13/22) durch `PROJECT_BRIEF_SELECT` ersetzen → `packages/api/src/router/calculation-rules.ts`
- [ ] **3.6** `entitlement.ts` — 1 Inline-Resource-Select (Line ~269) durch `RESOURCE_BRIEF_SELECT` (+ spread `chapter: true`) ersetzen → `packages/api/src/router/entitlement.ts`
### 3.2 Business Process Automation
### Akzeptanzkriterien
- Keine `{ id: true, name: true, color: true }` Inline-Selects mehr in Routern (ausser dort wo erweitert)
- `pnpm --filter @planarchy/api exec tsc --noEmit` — gruen
| # | Automation | Current Manual Process | Automated Process | Effort |
|---|-----------|----------------------|-------------------|--------|
| A6 | **Auto-staffing suggestions** | PM manually searches for resources per demand | System proposes top-3 matches when demand is created | 3 days |
| A7 | **Vacation conflict alerts** | Manager manually checks team calendar before approving | Auto-detect overlap > threshold, flag in approval flow | 2 days |
| A8 | **Budget overrun notifications** | Finance checks dashboards manually | SSE-triggered notification when project hits 80%/100% budget | 1 day |
| A9 | **Estimate approval reminders** | Verbal follow-up | Scheduled notification after N days in SUBMITTED status | 1 day |
| A10 | **Chargeability alerts** | Monthly manual review | Weekly auto-email when resource chargeability drops below target | 2 days |
| A11 | **Rate card auto-apply** | Manual rate lookup when creating estimate demand lines | Auto-fill LCR/UCR from rate card by resource chapter + level + client | 2 days |
| A12 | **Public holiday auto-import** | Admin manually batch-creates per year | Auto-generate on year rollover based on country/state config | 1 day |
### 3.3 Monitoring & Observability Automation
| # | Automation | Target | Effort |
|---|-----------|--------|--------|
| A13 | **Structured logging** (Pino) | All API requests logged with correlation ID | 2 days |
| A14 | **Error tracking** (Sentry) | Unhandled exceptions captured with context | 1 day |
| A15 | **Performance monitoring** | Slow query detection, API response time tracking | 2 days |
| A16 | **Uptime monitoring** | External health check probe, alerting | 0.5 day |
---
## Wave 4 — Status Badge & Vacation Constant Consolidation (Agent: style-consolidator)
## Part 4: Prioritized Roadmap
**Problem:** Mehrere duplizierte Konstanten-Maps fuer Status-Badges und Vacation-Type-Farben:
- `VACATION_TYPE_LABELS` dupliziert in `VacationModal.tsx` (Line ~13-18)
- `TYPE_COLORS` + `TYPE_BORDER` + `TYPE_LABELS_SHORT` identisch in `TimelineResourcePanel.tsx` (Lines ~563-580) und `TimelineProjectPanel.tsx` (Lines ~1299-1316)
- `TYPE_COLOR` fuer Kalender dupliziert in `VacationCalendar.tsx` (Line ~21) und `TeamCalendar.tsx` (Line ~8) — mit Inkonsistenz bei PUBLIC_HOLIDAY
### Sprint 0: Production Foundation (Week 1)
### Tasks
**Goal:** Unblock production deployment.
- [ ] **4.1** Vacation-Timeline-Konstanten (`VACATION_TIMELINE_COLORS`, `VACATION_TIMELINE_BORDER`, `VACATION_TYPE_LABELS_SHORT`) in `status-styles.ts` als Exports hinzufuegen → `apps/web/src/lib/status-styles.ts`
- [ ] **4.2** Vacation-Kalender-Konstanten (`VACATION_CALENDAR_COLORS`) in `status-styles.ts` hinzufuegen, PUBLIC_HOLIDAY-Inkonsistenz auf `emerald-500` vereinheitlichen → `apps/web/src/lib/status-styles.ts`
- [ ] **4.3** `TimelineResourcePanel.tsx` — lokale `TYPE_COLORS/TYPE_BORDER/TYPE_LABELS_SHORT` (Lines ~563-580) entfernen, Import aus `status-styles.ts``apps/web/src/components/timeline/TimelineResourcePanel.tsx`
- [ ] **4.4** `TimelineProjectPanel.tsx` — lokale `TYPE_COLORS/TYPE_BORDER/TYPE_LABELS_SHORT` (Lines ~1299-1316) entfernen, Import aus `status-styles.ts``apps/web/src/components/timeline/TimelineProjectPanel.tsx`
- [ ] **4.5** `VacationModal.tsx` — lokales `VACATION_TYPE_LABELS` (Lines ~13-18) entfernen, Import aus `status-styles.ts``apps/web/src/components/vacations/VacationModal.tsx`
- [ ] **4.6** `VacationCalendar.tsx` — lokales `TYPE_COLOR` (Line ~21) ersetzen durch Import → `apps/web/src/components/vacations/VacationCalendar.tsx`
- [ ] **4.7** `TeamCalendar.tsx` — lokales `TYPE_COLOR` (Line ~8) ersetzen durch Import → `apps/web/src/components/vacations/TeamCalendar.tsx`
- [ ] **A1** — GitHub Actions CI pipeline (test + lint + tsc + build)
- [ ] **G4** — Health check endpoints (`/api/health`, `/api/ready`)
- [ ] **A14** — Sentry error tracking integration
- [ ] **A13** — Pino structured logging in API layer
- [ ] Production Dockerfile (multi-stage, distroless base)
- [ ] docker-compose.prod.yml with env-based config
- [ ] Database backup strategy (pg_dump cron + S3)
### Akzeptanzkriterien
- Keine duplizierten Vacation/Status-Konstanten mehr in Komponenten
- `VacationCalendar` und `TeamCalendar` nutzen identische Farben
**Acceptance:** `main` branch has green CI, production image builds, errors are captured.
### Sprint 1: Quick Wins (Week 2)
**Goal:** Close the biggest UX gaps and improve daily workflows.
- [ ] **G1** — Staffing "Assign" button (match -> allocation in 1 click)
- [ ] **G2** — Dashboard metric caching (Redis-backed, SSE-invalidated)
- [ ] **G3** — Bulk operations on resource/project lists
- [ ] **A8** — Budget overrun notifications (80% + 100% thresholds)
- [ ] **A9** — Estimate approval reminders (auto-notify after 3 days)
**Acceptance:** Staffing-to-allocation is 1 click, dashboard loads <500ms, bulk select works.
### Sprint 2: Test Coverage & Stability (Week 3)
**Goal:** Harden the codebase for confident iteration.
- [ ] **A4** — API router integration tests (target 15 most-used routers)
- [ ] **A5** — Coverage gates: api + application packages at 80%
- [ ] **A3** — E2E expansion: 10 new specs (estimate lifecycle, vacation flow, bulk ops, filters)
- [ ] **A2** — Dependabot + npm audit in CI
**Acceptance:** `pnpm test:unit` covers all routers, E2E suite runs in CI, zero high-severity vulnerabilities.
### Sprint 3: Automation & Intelligence (Week 4-5)
**Goal:** Automate repetitive decisions, surface insights proactively.
- [ ] **A6** — Auto-staffing suggestions on demand creation
- [ ] **A7** — Vacation conflict detection in approval flow
- [ ] **A10** — Weekly chargeability alerts
- [ ] **A11** — Rate card auto-apply in estimate demand lines
- [ ] **A12** — Public holiday auto-import on year rollover
- [ ] **G6** — Skill marketplace MVP (searchable inventory + gap heat map)
**Acceptance:** Demands auto-suggest resources, vacation conflicts auto-flagged, rate cards auto-filled.
### Sprint 4: Strategic Features (Week 6-8)
**Goal:** Build differentiation features that create competitive moat.
- [ ] **G5** — Scenario/what-if planning (staffing mix simulator)
- [ ] **G7** — Custom report builder MVP (column picker, filters, export)
- [ ] **G8** — Collaboration layer (comments on estimates, @mention)
- [ ] **G12** — Dispo V2 clean-slate import (leverage existing design docs + tickets)
- [ ] Dashboard new widgets: budget forecast, skill gap, project health scorecard
**Acceptance:** PMs can simulate staffing scenarios, finance can build custom reports, Dispo import onboards first customer.
### Sprint 5: Market Expansion (Week 9-12)
**Goal:** Expand the platform beyond core planning.
- [ ] **G9** — AI insights: auto-staffing, anomaly detection, narrative summaries
- [ ] **G10** — Jira/Linear integration + Slack notifications
- [ ] **G11** — Mobile PWA companion
- [ ] **A15** — Performance monitoring + load testing baseline
- [ ] Advanced: multi-tenant architecture planning
**Acceptance:** AI suggestions active, Jira sync live, mobile app installable.
---
## Wave 5 — Adopt `useInvalidatePlanningViews()` Hook (Agent: invalidation-consolidator)
## Part 5: Risk Register
**Problem:** Hook existiert in `apps/web/src/hooks/useInvalidatePlanningViews.ts` mit 8 Queries,
wird aber von KEINER Mutation genutzt. 14+ Stellen kopieren die 4-Query-Timeline-Invalidierung manuell.
Ausserdem fehlt `getProjectContext` in `useTimelineDrag.ts` (Line ~238).
### Tasks
- [ ] **5.1** `useInvalidatePlanningViews` in eine `useInvalidateTimeline()` (4 Timeline-Queries) und `useInvalidateAllAllocViews()` (alle 8) aufspalten, da manche Stellen nur Timeline invalidieren → `apps/web/src/hooks/useInvalidatePlanningViews.ts` (edit)
- [ ] **5.2** `TimelineView.tsx` — 2 manuelle Invalidierungsbloecke (Lines ~73-76, ~333-336) durch Hook ersetzen → `apps/web/src/components/timeline/TimelineView.tsx`
- [ ] **5.3** `AllocationPopover.tsx` — Invalidierungsblock (Lines ~58-62) durch Hook ersetzen → `apps/web/src/components/timeline/AllocationPopover.tsx`
- [ ] **5.4** `NewAllocationPopover.tsx` — Invalidierungsblock (Lines ~63-66) ersetzen → `apps/web/src/components/timeline/NewAllocationPopover.tsx`
- [ ] **5.5** `BatchAssignPopover.tsx` — Invalidierungsblock (Lines ~54-57) ersetzen → `apps/web/src/components/timeline/BatchAssignPopover.tsx`
- [ ] **5.6** `ProjectPanel.tsx` — 3 Invalidierungsbloecke (Lines ~106-109, 115-118, 124-127) ersetzen → `apps/web/src/components/timeline/ProjectPanel.tsx`
- [ ] **5.7** `useAllocationHistory.ts` — 3 Invalidierungsbloecke (Lines ~31-34, 40-43, 62-65) ersetzen → `apps/web/src/hooks/useAllocationHistory.ts`
- [ ] **5.8** `useTimelineDrag.ts` — 2 Bloecke (Lines ~238-241, 254-257) ersetzen + fehlende `getProjectContext` Invalidierung fixen → `apps/web/src/hooks/useTimelineDrag.ts`
- [ ] **5.9** `FillOpenDemandModal.tsx` — Invalidierungsblock (Lines ~76-79) ersetzen → `apps/web/src/components/allocations/FillOpenDemandModal.tsx`
### Akzeptanzkriterien
- Keine manuellen 4-Query-Timeline-Invalidierungsbloecke mehr
- `useTimelineDrag.ts` invalidiert alle 4 Timeline-Queries (inkl. `getProjectContext`)
| # | Risk | Probability | Impact | Mitigation |
|---|------|-------------|--------|------------|
| R1 | Production deployment without CI catches regressions | HIGH | CRITICAL | Sprint 0 is mandatory before any feature work |
| R2 | Timeline 3.3K LOC becomes unmaintainable | MEDIUM | HIGH | Decompose into sub-hook modules when next touching timeline |
| R3 | Dashboard performance degrades with data growth | MEDIUM | MEDIUM | G2 (caching) in Sprint 1; monitor query times |
| R4 | Prisma schema changes break dev workflow | HIGH | LOW | Automate restart in dev scripts (already documented) |
| R5 | Skill matrix AI costs grow with usage | LOW | MEDIUM | Add token budget tracking in SystemSettings |
| R6 | No data backup strategy | MEDIUM | CRITICAL | Add pg_dump cron + S3 upload in Sprint 0 |
| R7 | Single-point-of-failure (1 dev, 1 server) | HIGH | CRITICAL | Document architecture, automate deployment, enable team onboarding |
---
## Wave 6 — Timeline Render-Helpers & React.memo (Agent: timeline-optimizer)
## Part 6: Key Metrics to Track
**Problem:** 3 identische Render-Funktionen in beiden Panel-Komponenten (Vacation-Blocks, Range-Overlay, Overbooking-Blink). Keine `React.memo` auf den grossen Panel-Komponenten. `useTimelineDrag.ts` ist 883 Zeilen Monolith.
### Product Metrics
- **Time-to-staff**: Minutes from demand creation to resource assignment
- **Estimate turnaround**: Days from estimate creation to approval
- **Vacation approval latency**: Hours from request to decision
- **Dashboard load time**: P95 response time for dashboard page
- **Chargeability accuracy**: Forecast vs actual deviation %
### Tasks
- [ ] **6.1** `renderHelpers.ts` erstellen mit extrahierten Shared-Funktionen: `renderVacationBlocks()`, `renderRangeOverlay()`, `renderOverbookingBlink()``apps/web/src/components/timeline/renderHelpers.ts` (create)
- [ ] **6.2** `TimelineResourcePanel.tsx` — lokale `renderVacationBlocksForRow`, `renderRangeOverlay`, `renderOverbookingBlink` (Lines ~582-636, 895-930) durch Imports aus `renderHelpers.ts` ersetzen → edit
- [ ] **6.3** `TimelineProjectPanel.tsx` — lokale `renderVacationBlocksForProjectRow`, `renderRangeOverlayProject`, `renderOverbookingBlinkProject` durch Imports ersetzen → edit
- [ ] **6.4** `React.memo()` auf `TimelineResourcePanel` wrappen → `TimelineResourcePanel.tsx`
- [ ] **6.5** `React.memo()` auf `TimelineProjectPanel` wrappen → `TimelineProjectPanel.tsx`
- [ ] **6.6** Multi-Select-Intersection-Logic (Lines ~573-634 in `TimelineView.tsx`) in eigenen Hook `useMultiSelectIntersection.ts` extrahieren → `apps/web/src/hooks/useMultiSelectIntersection.ts` (create), `TimelineView.tsx` (edit)
- [ ] **6.7** `useTimelineDrag.ts` — Drag-Math-Utilities (`pixelsToDays`, `constrainToGrid`, `clampDate`) in `dragMath.ts` extrahieren → `apps/web/src/components/timeline/dragMath.ts` (create), `useTimelineDrag.ts` (edit)
### Akzeptanzkriterien
- Keine duplizierten Render-Funktionen zwischen den Panel-Komponenten
- Beide Panels als `React.memo()` exportiert
- `useTimelineDrag.ts` unter 800 Zeilen
- Timeline rendert korrekt in beiden Views + Overbooking-Blink funktioniert
### Engineering Metrics
- **Test coverage**: % by package (target: all >=80%)
- **CI green rate**: % of PRs passing all gates
- **Build time**: Minutes for full `next build`
- **Error rate**: Sentry exceptions per hour
- **API latency**: P95 tRPC procedure response time
---
## Wave 7 — Composite Database Indexes (Agent: db-index-optimizer)
## Appendix: Current State Snapshot
**Problem:** Mehrere haeufig abgefragte Modelle haben keine optimalen Composite-Indexes fuer
die kombinierten WHERE-Bedingungen der tRPC-Router.
### Tasks
- [ ] **7.1** `DemandRequirement``@@index([projectId, status, startDate, endDate])` hinzufuegen (ersetzt/ergaenzt bestehenden 3-Feld-Index) → `packages/db/prisma/schema.prisma`
- [ ] **7.2** `Resource``@@index([isActive, orgUnitId])` hinzufuegen fuer "aktive Ressourcen pro OrgUnit" Queries → `packages/db/prisma/schema.prisma`
- [ ] **7.3** `Project``@@index([status, startDate, endDate])` hinzufuegen fuer Timeline-Filterung → `packages/db/prisma/schema.prisma`
- [ ] **7.4** `Estimate``@@index([projectId, status])` hinzufuegen → `packages/db/prisma/schema.prisma`
- [ ] **7.5** `pnpm db:push` ausfuehren, `.next/` Cache loeschen, Dev-Server neu starten
**Bereits optimal:**
- `Vacation` — hat `@@index([resourceId, status, startDate, endDate])`
- `Assignment` — hat `@@index([resourceId, status, startDate])` + `@@index([projectId, startDate, endDate])`
### Akzeptanzkriterien
- Neue Indexes in Schema sichtbar
- `pnpm db:push` erfolgreich
- Dev-Server startet sauber
---
## Abhaengigkeiten & Parallelisierung
```
Wave 1 (format) ─┐
Wave 2 (findUniqueOrThrow) ─┤
Wave 3 (selects) ─┤── Alle parallel ausfuehrbar (verschiedene Dateien/Domains)
Wave 4 (status-styles) ─┤
Wave 5 (invalidation) ─┤
Wave 6 (timeline render) ─┘
Wave 7 (DB indexes) ── Sequentiell NACH Wave 1-6 (erfordert db:push + Restart)
```
**Innerhalb der Waves:**
- Wave 1: Task 1.10 (API format-utils) kann parallel zu Tasks 1.1-1.9 (web components)
- Wave 2: Alle Tasks unabhaengig (verschiedene Router-Dateien)
- Wave 4: Task 4.1-4.2 ZUERST (erstellt Exports), dann Tasks 4.3-4.7 parallel
- Wave 5: Task 5.1 ZUERST (Hook-Refactoring), dann Tasks 5.2-5.9 parallel
- Wave 6: Task 6.1 ZUERST (erstellt renderHelpers.ts), dann Tasks 6.2-6.7 parallel
**Dateikonflikte vermeiden:**
- Wave 2 und Wave 3 editieren teilweise gleiche Router-Dateien (`vacation.ts`, `entitlement.ts`, `calculation-rules.ts`) — diese Tasks SEQUENTIELL innerhalb eines Agents ausfuehren
- Wave 4 und Wave 6 editieren beide Panel-Dateien — unterschiedliche Abschnitte, koennen aber sicherer sequentiell sein
---
## Akzeptanzkriterien (Gesamt)
- [ ] `pnpm --filter @planarchy/web exec tsc --noEmit` — null Errors
- [ ] `pnpm --filter @planarchy/api exec tsc --noEmit` — null Errors
- [ ] `pnpm --filter @planarchy/engine exec vitest run` — alle Tests gruen
- [ ] `pnpm --filter @planarchy/staffing exec vitest run` — alle Tests gruen
- [ ] Dev-Server startet, Timeline rendert in beiden Views
- [ ] Overbooking-Blink funktioniert
- [ ] Demand-Popover und Resource-Hover-Card funktionieren
- [ ] Keine duplizierten `formatMoney/formatCents/fmtEur` Funktionsdefinitionen
- [ ] Keine manuellen `findUnique` + throw Bloecke in Routern (ausser assistant-tools.ts)
- [ ] Keine duplizierten Vacation-Type/Status-Konstanten
- [ ] Keine manuellen 4-Query Timeline-Invalidierungsbloecke
---
## Risiken & Offene Fragen
1. **Wave 2+3 Dateikonflikt:** `vacation.ts`, `entitlement.ts`, `calculation-rules.ts` werden in Wave 2 UND Wave 3 editiert. Loesung: Ein Agent bearbeitet beide Waves sequentiell fuer diese Dateien.
2. **Wave 5 Type-Cast:** `useInvalidatePlanningViews` hat einen TypeScript-Cast fuer `allocation.listView`. Wenn der Cast nach Refactoring bricht, muss der tRPC-Output-Type geprueft werden.
3. **Wave 6 memo-Props:** `React.memo()` auf Panels erfordert stabile Prop-Referenzen. Wenn Inline-Callbacks als Props uebergeben werden, muss der Parent `useCallback` nutzen — pruefe `TimelineViewContent`.
4. **Wave 7 DB-Migration:** `db:push` auf Produktions-DB erfordert Maintenance-Window fuer Index-Erstellung. Auf Dev-DB unproblematisch.
5. **assistant-tools.ts:** 45 `findUnique` Stellen mit `{ error: "..." }` Return-Pattern — NICHT auf `findUniqueOrThrow` migrieren, da das Error-Handling grundlegend anders ist (Return vs Throw).
---
## Metriken (erwartet)
| Metrik | Vorher | Nachher |
|--------|--------|---------|
| Duplizierte Format-Funktionen | 12+ Inline | 0 (1 zentrale Lib + 1 API-Helfer) |
| Manuelle findUnique+throw | ~35 Stellen | 0 (alle via Helper) |
| Inline Prisma-Selects | ~20 Duplikate | 0 (via Shared Constants) |
| Duplizierte Status-Konstanten | 7 Stellen | 0 (1 zentrale Datei) |
| Manuelle Invalidierungsbloecke | 14+ Stellen | 0 (via Hooks) |
| Duplizierte Render-Funktionen | 3 Paare (6 total) | 3 Shared (renderHelpers.ts) |
| useTimelineDrag.ts Zeilen | 883 | ~800 |
| Fehlende DB-Composite-Indexes | 4 | 0 |
| Dimension | Count |
|-----------|-------|
| Database models | 47 |
| tRPC routers | 28 |
| tRPC procedures | ~200 (120Q + 80M) |
| Frontend routes | 34 |
| Domain components | 109+ |
| Shared UI components | 20+ |
| Unit test files | 62 |
| E2E test specs | 4 |
| Engine test coverage | 95% (gated) |
| Staffing test coverage | 90% (gated) |
| API router test coverage | ~5% (not gated) |
| CI/CD pipeline | None |
| Production Docker | None |
| Monitoring/APM | None |
| Completed phases | 9 |
| Known pain points | 24 (documented in LEARNINGS.md) |