feat(timeline): add pulse animation for in-flight drag mutations

Allocation bars that have active optimistic overrides (post-drag,
awaiting server confirmation) now pulse subtly via animate-pulse.
The pending set is derived from the existing optimisticAllocations
map keys, requiring no additional state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 13:28:46 +02:00
parent 7a5e98e2e9
commit 1df208dbcc
386 changed files with 657 additions and 81650 deletions
+92 -70
View File
@@ -1,80 +1,102 @@
# Plan: Scenario Regression Depth + Housekeeping
# Plan: Navigation Route Smoke Test (Ticket #50)
Stand: 2026-04-02
---
# Workstream E: Scenario Regression Depth
**Stand: 2026-04-03**
## Anforderungsanalyse
Vier Szenario-Module haben **keine direkten Unit-Tests**:
**Untersuchungsergebnis:** Die in #50 gemeldeten 404s sind kein echter Code-Defekt.
- `analytics/skills` → HTTP 200 (in Docker-Logs bestätigt)
- `reports/chargeability` → HTTP 200 (in Docker-Logs bestätigt)
- `analytics/insights` → Seite existiert, wurde schlicht noch nicht kompiliert (Next.js dev kompiliert Routen on-demand beim ersten Besuch)
- Alle 35 Nav-Hrefs wurden gegen existierende `page.tsx`-Dateien geprüft: **keine toten Links**
| Datei | Inhalt | Lücke |
|-------|--------|-------|
| `scenario-shared.ts` | Pure helpers: `roundToTenths`, `getScenarioAvailability`, `collectScenarioSkillSet`, `calculateScenarioEntryHours` | Keine Tests |
| `scenario-baseline.ts` | `readProjectScenarioBaseline` — lädt Assignments/Demands, berechnet Kosten/Stunden | Keine Tests |
| `scenario-apply.ts` | `applyProjectScenario` — CANCELLED/update/create Branches, appliedCount | Keine Tests |
| `scenario-simulation.ts` | `simulateProjectScenario` — Baseline vs. Szenario-Delta, Warnungen, Skill-Coverage | Keine Tests |
**Was gebaut wird:** Ein Playwright-Smoke-Test, der bei jedem Lauf alle sichtbaren Sidebar-Destinations (navSections + adminNavEntries) für einen eingeloggten Admin-User überprüft. Verhindert, dass künftige Regressions (nav-Link zu nicht-existierender Route) erst von Usern entdeckt werden.
Bestehende Coverage: `scenario-router.test.ts` (Auth-Guards), `scenario-procedure-support.test.ts` (Delegation), `assistant-tools-scenarios.test.ts` (1 Integration-Test) — alles Delegation, keine Business-Logik.
## Betroffene Pakete & Dateien
| Paket | Datei | Art |
|-------|-------|-----|
| `packages/api` | `src/__tests__/scenario-shared.test.ts` | create |
| `packages/api` | `src/__tests__/scenario-apply.test.ts` | create |
| `packages/api` | `src/__tests__/scenario-baseline.test.ts` | create |
| `packages/api` | `src/__tests__/scenario-simulation.test.ts` | create |
## Task-Liste
- [x] **E-1a:** `scenario-shared.test.ts` — Pure-Helper-Tests (keine Mocks außer resource-capacity)
- `roundToTenths`: 0.15 → 0.2, 0.34 → 0.3, ganzer Integer bleibt
- `getScenarioAvailability`: null → DEFAULT_AVAILABILITY; valides Objekt wird durchgereicht
- `collectScenarioSkillSet`: null → leeres Set; leeres Array → leeres Set; doppelte Skills dedupliziert; lowercase-Normalisierung; leere Strings gefiltert; Mixed case dedupliziert
- `calculateScenarioEntryHours`: ohne resourceId → delegiert an `calculateAllocation`; mit resourceId → delegiert an `calculateEffectiveBookedHours`
- Mock: `@capakraken/engine/allocation` und `../lib/resource-capacity.js`
- [x] **E-1b:** `scenario-apply.test.ts` — CRUD-Branch-Tests
- NOT_FOUND wenn `project.findUnique` null zurückgibt
- `remove: true` + `assignmentId``assignment.update` mit `status: "CANCELLED"`, appliedCount = 0 (cancel trifft `continue` vor `created.push`)
- `assignmentId` ohne remove → `assignment.update` mit neuen Daten, appliedCount = 1
- kein `assignmentId`, kein `resourceId` → Zeile wird übersprungen, appliedCount = 0
- kein `assignmentId`, hat `resourceId``assignment.create` mit korrektem `dailyCostCents`, appliedCount = 1
- Mehrere Changes → korrekter appliedCount summiert
- [x] **E-1c:** `scenario-baseline.test.ts` — Baseline-Lade-Tests
- NOT_FOUND wenn Projekt nicht gefunden
- Leeres Projekt (keine Assignments, keine Demands) → `totalCostCents: 0`, `totalHours: 0`, `assignments: []`, `demands: []`
- Assignment mit bekanntem `lcrCents` und `hoursPerDay``costCents` korrekt berechnet
- CANCELLED Assignments werden herausgefiltert (kommen nicht in `baselineAllocations`)
- Demands werden korrekt gemappt (kein `costCents`, hat `headcount`, `roleName` aus roleEntity)
- `totalCostCents` ist Summe aller Assignment-`costCents`
- [x] **E-1d:** `scenario-simulation.test.ts` — Simulations-Logik-Tests
- NOT_FOUND wenn Projekt nicht gefunden
- remove-Change → Assignment fehlt im Scenario-headcount (`delta.headcount < 0`)
- Neues Assignment hinzufügen → `delta.headcount > 0`
- Budget-Warnung wenn `scenarioCostCents > budgetCents` → warnings enthält "exceeds budget"
- Skill-Coverage: Szenario mit mehr Skills → `delta.skillCoveragePct > 100`
- Szenario ohne Änderungen aber mit bestehenden Assignments → `delta.costCents = 0`, `delta.hours = 0`
## Abhängigkeiten
- E-1a und E-1b können **parallel** geschrieben werden (separate Dateien)
- E-1c und E-1d können **parallel** geschrieben werden
- Keine Abhängigkeiten zwischen allen vier
## Akzeptanzkriterien
- [x] `pnpm test:unit` läuft grün
- [x] Alle 4 neuen Test-Dateien existieren mit ≥ 4 Tests jeweils (4 Dateien, 31 Tests, alle grün)
**Betroffenes Paket:** `apps/web` (nur Testinfrastruktur, kein Produktionscode)
---
# Workstream F: .env.example + Docs Housekeeping
## Betroffene Pakete & Dateien
- [x] **F-1:** `.env.example` committen (commit 1ec56aa — erweitert auf ~85 Zeilen mit vollständiger Dokumentation)
- [x] **F-2:** `plan.md` nach Abschluss mit erledigten Tasks aktualisieren
| Paket | Dateien | Art der Änderung |
|-------|---------|-----------------|
| `apps/web` | `e2e/dev-system/nav-smoke.spec.ts` | create |
| `apps/web` | `e2e/navigation.spec.ts` | edit — 9 bisher ungetestete Routen hinzufügen |
---
## Task-Liste
- [ ] **Task 1: `e2e/dev-system/nav-smoke.spec.ts` erstellen**
Neues Spec in der `dev-system`-Suite (läuft gegen Live-Dev-Server auf Port 3100).
Nutzt gespeicherten Auth-State (`storageState: .auth/admin.json`) — kein manueller
Login nötig, vermeidet Rate-Limiter. Admin-User hat Zugriff auf alle Routen.
Alle Hrefs als eigenständige Konstante definieren — **NICHT AppShell importieren**
(würde Next.js-Infrastruktur in die Test-Runtime einziehen).
Pro Route: `page.goto(href, { waitUntil: "commit" })` + zwei Assertions:
1. `response.status() !== 404`
2. `"This page could not be found"` ist nicht sichtbar (max 15 s warten)
Timeout für die Seite auf `60_000 ms` setzen wegen JIT-Compile-Verzögerung bei
noch nicht kompilierten Routen (z.B. `analytics/insights` beim ersten Besuch).
**Hrefs (Stand AppShell.tsx 2026-04-03):**
- Planning: `/dashboard`, `/timeline`, `/allocations`, `/staffing`, `/notifications`
- Estimating: `/estimates`, `/admin/rate-cards`, `/admin/effort-rules`, `/admin/experience-multipliers`
- Resources: `/resources`, `/projects`, `/roles`
- Analytics: `/analytics/skills`, `/reports/chargeability`, `/reports/builder`, `/analytics/computation-graph`, `/analytics/insights`
- Time Off: `/vacations/my`, `/vacations`
- Account: `/account/security`
- Admin: `/admin/blueprints`, `/admin/clients`, `/admin/countries`, `/admin/org-units`, `/admin/utilization-categories`, `/admin/management-levels`, `/admin/imports`, `/admin/calculation-rules`, `/admin/vacations`, `/admin/users`, `/admin/system-roles`, `/admin/settings`, `/admin/notifications`, `/admin/webhooks`, `/admin/activity-log`
→ Datei: `apps/web/e2e/dev-system/nav-smoke.spec.ts`
- [ ] **Task 2: `e2e/navigation.spec.ts` erweitern**
Die bestehende `navigation.spec.ts` im Standard-Test-Suite prüft nur 5 Routen via
Sidebar-Click. Einen **separaten Test-Block** `"all nav routes resolve (no 404)"`
hinzufügen, der die restlichen regulären Routen (ohne Admin-Routen, da Role-Gate
im isolierten Test-Server-Seed unbekannt) per `page.goto` prüft.
Neue Routen zusätzlich zum bestehenden 5er-Set:
`/estimates`, `/roles`, `/analytics/skills`, `/reports/chargeability`,
`/reports/builder`, `/analytics/computation-graph`, `/analytics/insights`,
`/vacations/my`, `/vacations`, `/account/security`
Der bestehende Click-Test bleibt unverändert.
→ Datei: `apps/web/e2e/navigation.spec.ts`
---
## Abhängigkeiten
- Task 1 und Task 2 sind **vollständig unabhängig** — können parallel implementiert werden.
- Task 1 setzt gespeicherte Auth-State-Dateien voraus (`e2e/dev-system/.auth/admin.json`).
Diese existieren bereits (von vorherigen Runs). Falls nicht: `playwright test --config
playwright.dev.config.ts` ohne Spec-Filter — global-setup läuft zuerst.
---
## Akzeptanzkriterien
- [ ] `pnpm --filter @capakraken/web exec playwright test --config playwright.dev.config.ts e2e/dev-system/nav-smoke.spec.ts` → alle 35 Tests grün gegen Live-Dev-Server auf Port 3100
- [ ] `pnpm --filter @capakraken/web exec playwright test e2e/navigation.spec.ts` → grün im Standard-Test-Suite
- [ ] `pnpm test:unit` → unverändert grün (kein Produktionscode geändert)
- [ ] `pnpm --filter @capakraken/web exec tsc --noEmit` → keine neuen Errors
- [ ] Gitea #50 mit Analyseergebnis kommentieren und schließen
---
## Risiken & offene Fragen
1. **Slow first-compile:** `analytics/insights` wurde im Dev-Server noch nie kompiliert. Der erste Goto kann 1015 s dauern (JIT + Batch-Query). Test-Timeout auf `60_000 ms` setzen.
2. **`/vacations/my` für Admin ohne Resource:** Admin-User `admin@planarchy.dev` hat möglicherweise kein verlinktes Resource-Record. Die Seite rendert dann ein Amber-Warning-Banner — kein 404. Smoke-Test besteht trotzdem, da nur HTTP-Status und "page not found"-Text geprüft werden.
3. **Role-Gates in Standard-Suite (Task 2):** Der Test-Server-Seed-User `admin@capakraken.dev` muss systemRole ADMIN haben damit Role-gated Routen erreichbar sind. Prüfen via `test-server.mjs` bevor Implementierung.
4. **Credentials nicht mischen:** `navigation.spec.ts` (Standard-Suite) → `admin@capakraken.dev`. `nav-smoke.spec.ts` (dev-system) → `admin@planarchy.dev` via storageState. Nie kreuzen.