93e2b94c47
- 3/3 tasks complete, 7/7 tests pass, tsc clean - Worktree fast-forward deviation documented
116 lines
5.6 KiB
Markdown
116 lines
5.6 KiB
Markdown
---
|
|
phase: 08-dashboard-widgets-vollimplementierung
|
|
plan: "02"
|
|
subsystem: dashboard-widgets
|
|
tags: [stopwatch, widget, tdd, dashboard, persistence, i18n]
|
|
status: complete
|
|
|
|
dependency_graph:
|
|
requires:
|
|
- 08-01 (wireStopwatchWidget, WIDGET_CONSTRAINTS.stopwatch, i18n keys)
|
|
provides:
|
|
- StopwatchWidget component (start/stop/reset/lap, reload-safe persistence)
|
|
- stopwatch-widget.tsx wired into page.tsx via wireStopwatchWidget()
|
|
- Reload reconstruction via startedAt + elapsed config shape
|
|
affects:
|
|
- apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx
|
|
- apps/web/src/components/dashboard/widgets/stopwatch-widget.test.tsx
|
|
- apps/web/src/app/(portal)/page.tsx
|
|
|
|
tech_stack:
|
|
added: []
|
|
patterns:
|
|
- TDD Red/Green cycle (stopwatch behavioral contract)
|
|
- setInterval ticker cleared on unmount and when not running (T-08-04)
|
|
- Reload reconstruction: Date.now() - new Date(startedAt).getTime() + elapsed
|
|
- updateWidgetConfig best-effort persist (AbortController, swallow errors)
|
|
- wireXWidget() lazy-loading pattern (same as Plan 01)
|
|
- Tailwind-only styling (no CSS modules)
|
|
|
|
key_files:
|
|
created:
|
|
- apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx
|
|
- apps/web/src/components/dashboard/widgets/stopwatch-widget.test.tsx
|
|
modified:
|
|
- apps/web/src/app/(portal)/page.tsx
|
|
|
|
decisions:
|
|
- "Lap times stored newest-first (per RESEARCH recommendation) — simplest rendering, no reverse needed"
|
|
- "Persist errors swallowed best-effort (same pattern as note-widget) — UI never blocked by API errors"
|
|
- "Interval tick rate 100ms — smooth display without excessive re-renders"
|
|
- "AbortController per-persist-call (same as note-widget) — cancels in-flight request before new one"
|
|
|
|
metrics:
|
|
duration: "~3 minutes"
|
|
completed: "2026-07-01"
|
|
tasks_completed: 3
|
|
tests_added: 7
|
|
files_created: 2
|
|
files_modified: 1
|
|
---
|
|
|
|
# Phase 08 Plan 02: Stopwatch Widget Summary
|
|
|
|
Stoppuhr-Widget (DASH-10) via TDD Red/Green-Zyklus: Start/Stop/Pause/Reset/Runde mit persistiertem Zustand in WidgetInstance.config — laufende Stoppuhr ueberleb Seiten-Reload korrekt.
|
|
|
|
## What Was Built
|
|
|
|
- **Stopwatch Widget** (`stopwatch-widget.tsx`): Voll funktionsfaehige Stoppuhr. Start startet den Timer und setzt `startedAt` auf `new Date().toISOString()`. Stop/Pause friert den angezeigten Wert ein und akkumuliert `elapsed`. Reset setzt alles auf 0. Lap haengt den aktuellen Live-Elapsed vorne an `laps[]` an.
|
|
- **Reload-Rekonstruktion**: Beim Rendern mit `config.state === 'running'` wird elapsed als `Date.now() - new Date(startedAt).getTime() + config.elapsed` berechnet — die Stoppuhr zeigt damit sofort den korrekten Wert, auch nach Seiten-Reload (Pitfall 2 aus RESEARCH korrekt umgesetzt).
|
|
- **Persistenz**: Jede Aktion (Start/Stop/Reset/Lap) ruft `updateWidgetConfig(instanceId, {...})` auf. Best-effort-Muster (Fehler werden verschluckt, UI wird nie blockiert). AbortController cancelt laufende Anfragen vor neuen.
|
|
- **Timer-Safety** (T-08-04): `setInterval` (100ms) wird beim Unmount und immer wenn `state !== 'running'` gecleart. Kein unbegrenzter Timer.
|
|
- **Wiring**: `page.tsx` importiert `StopwatchWidget` und ruft `wireStopwatchWidget(StopwatchWidget)` auf.
|
|
|
|
## Tasks
|
|
|
|
| Task | Description | Status | Commit |
|
|
|------|-------------|--------|--------|
|
|
| 1 | RED: Failing tests fuer Stopwatch-Verhalten und Reload-Rekonstruktion | Complete | d8d008b |
|
|
| 2 | GREEN: StopwatchWidget-Implementierung + page.tsx-Wiring | Complete | c1c7bff |
|
|
| 3 | Full Suite + TypeCheck Gate | Complete | — (no new changes needed) |
|
|
|
|
## Test Results
|
|
|
|
- **stopwatch-widget.test.tsx**: 7 Tests — initial display 00:00, Start persists state=running + startedAt, display advances after 3s, Stop persists state=paused, Reset returns 00:00, Lap appends to laps, reload reconstruction shows ~7s from 5s-ago startedAt + 2000ms elapsed
|
|
- **Full Web Suite**: 74/74 Tests bestehen (16 Test-Dateien)
|
|
- **Web TypeScript**: sauber (0 Fehler)
|
|
|
|
## Deviations from Plan
|
|
|
|
### Worktree Fast-Forward (auto-fix, Rule 3)
|
|
|
|
**Found during:** Pre-task setup
|
|
|
|
**Issue:** Das Worktree-Branch `worktree-agent-a1442bedc5b6b918d` war bei `88db54f` (Quick-Task-Commits) — HINTER dem erwarteten Basis-Commit `2fa5193f` (Main nach Plan-01-Merge). `wireStopwatchWidget` und die i18n-Schluesseln aus Plan 01 fehlten.
|
|
|
|
**Fix:** `git merge main --ff-only` — Fast-Forward auf `2fa5193f`. Keine Konflikte. Anschliessend node_modules-Symlinks neu erstellt.
|
|
|
|
**Impact:** Keiner. Alle Plan-01-Artefakte standen danach vollstaendig zur Verfuegung.
|
|
|
|
### Task 3: Kein separater Commit
|
|
|
|
Task 3 (Full Suite + TypeCheck) loeste keine Fehler aus — Full Suite und tsc waren nach Task 2 bereits sauber. Gemaess Protokoll: Kein Commit bei keinen Aenderungen.
|
|
|
|
## Known Stubs
|
|
|
|
Keine Stubs. StopwatchWidget ist vollstaendig implementiert. Favoriten-Widget (Plan 03) und Link-Widget (Plan 04) bleiben als PlaceholderWidget in der Registry — das ist erwartet und wird in den Nachfolgeplaenen umgesetzt.
|
|
|
|
## Threat Surface Scan
|
|
|
|
Keine neuen Sicherheitsoberflaechen eingefuehrt.
|
|
|
|
- **T-08-03 (Tampering)**: Kein neuer API-Endpunkt. Stopwatch-Config nutzt denselben `PATCH /dashboard/widgets/:id/config`-Pfad — Eigentuemerspruefung in DashboardService gilt.
|
|
- **T-08-04 (DoS via setInterval)**: Interval wird im useEffect-Cleanup und beim Uebergang zu nicht-laufendem Zustand gecleart. Mitigiert.
|
|
|
|
## Self-Check: PASSED
|
|
|
|
| Item | Status |
|
|
|------|--------|
|
|
| stopwatch-widget.tsx | FOUND |
|
|
| stopwatch-widget.test.tsx | FOUND |
|
|
| page.tsx: wireStopwatchWidget(StopwatchWidget) | FOUND |
|
|
| Commit d8d008b (RED) | FOUND |
|
|
| Commit c1c7bff (GREEN) | FOUND |
|
|
| Full suite 74/74 | PASSED |
|
|
| TypeScript: 0 errors | PASSED |
|