5d5d4ac2a7
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
130 lines
11 KiB
Markdown
130 lines
11 KiB
Markdown
---
|
||
phase: quick-260916-htc
|
||
plan: 01
|
||
status: complete
|
||
subsystem: dashboard-widgets
|
||
tags: [calendar, dashboard, widget-settings, i18n]
|
||
dependency-graph:
|
||
requires: [05-03 Kalender-Backend (fetchEvents/fetchSources), quick-260916-dyv (Raster 24 Spalten/20px)]
|
||
provides: [calendar-month.ts (geteiltes Hilfsmodul), Kalender-Monatsraster-Widget, CalendarConfig-Einstellungsfeld]
|
||
affects: [apps/web/src/components/dashboard/widgets/calendar-widget.tsx, apps/web/src/components/settings/widget-settings-panel.tsx, apps/web/src/components/dashboard/widget-registry.tsx]
|
||
tech-stack:
|
||
added: []
|
||
patterns: [geteiltes Grenzen-Hilfsmodul fuer Widget+Panel (Muster clock-font-size.ts), createPortal fuer Tooltips ausserhalb einer overflow-hidden-Karte, Container-Query-Skalierung (cqw/cqh)]
|
||
key-files:
|
||
created:
|
||
- apps/web/src/components/dashboard/widgets/calendar-month.ts
|
||
- apps/web/src/components/dashboard/widgets/calendar-month.test.ts
|
||
modified:
|
||
- apps/web/src/components/dashboard/widgets/calendar-widget.tsx
|
||
- apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx
|
||
- apps/web/src/components/dashboard/widget-registry.tsx
|
||
- apps/web/src/components/dashboard/widget-registry.test.tsx
|
||
- apps/web/src/components/settings/widget-settings-panel.tsx
|
||
- apps/web/src/components/settings/widget-settings-panel.test.tsx
|
||
- apps/web/src/messages/de.json
|
||
- apps/web/src/messages/en.json
|
||
- CHANGELOG.md
|
||
- docs/anleitung-anwender.md
|
||
decisions:
|
||
- "computeFetchWindow verwendet konsequent 'from = das FRUEHERE von Rasterstart und heutigem Tag' (Task-1-Spezifikation), auch wenn ein zukuenftiger Monat angezeigt wird — der im Plan fuer Task-2-Test-8 genannte Erwartungswert (27.07. statt 15.07.) widersprach dieser Regel; die konsistente, bereits per Unit-Test abgesicherte Regel wurde beibehalten (siehe Deviations)."
|
||
- "Leerer 'Naechste Termine'-Block traegt KEIN data-testid='calendar-upcoming' (nur die <ul> bei mindestens einem Termin traegt es) — folgt der <action>-Spezifikation aus dem Plan woertlich; die <behavior>-Beschreibung von Task 2 Test 6 war an dieser Stelle ungenauer formuliert."
|
||
metrics:
|
||
duration: ~35 min
|
||
completed: 2026-09-16
|
||
actuals:
|
||
tokens: 15659
|
||
tasks: 3
|
||
commits: 3
|
||
plan_head_before: 0858102cbbe825ed4f52aeed31b9a0a44a9cb52
|
||
---
|
||
|
||
# Phase quick-260916-htc Plan 01: Kalender-Widget nach Vorbild personal-dashboard Summary
|
||
|
||
Kalender-Widget von einer flachen Terminliste auf ein Monatsraster mit Termin-Plaketten, Portal-Tooltip und einem separat konfigurierbaren "Naechste Termine"-Block umgebaut, inklusive dreier neuer Einstellungsfelder (Monatsansicht, Anzahl Termine, Zeitraum) nach dem Muster `ClockConfig`.
|
||
|
||
## Was wurde gebaut
|
||
|
||
**Task 1 — Uebersetzungen + Hilfsmodul (Commit `0858102`)**
|
||
16 neue Uebersetzungsschluessel unter `widgets.calendar` in de.json/en.json (Monatsnavigation, Tooltip-Hinweis, Einstellungsfeld-Texte). Neues reines Hilfsmodul `calendar-month.ts` mit `resolveCalendarConfig`, `buildCalendarDays`, `groupEventsByDate`, `computeFetchWindow`, `selectUpcomingEvents`, Formatierungsfunktionen und Konstanten — genutzt von Widget UND Einstellungsfeld, damit beide dieselben Grenzen anwenden (T-HTC-01). 8 Unit-Tests.
|
||
|
||
**Task 2 — Widget neu gebaut (Commit `61996dc`)**
|
||
`calendar-widget.tsx` komplett neu: Nav-Zeile (Zurueck/Monat/Weiter), Wochentagskopf, 42-Zellen-Raster (Montag-basiert, Fremdmonatstage gedaempft, heutiger Tag hervorgehoben, Zaehl-Plakette), Portal-Tooltip (bis zu 5 Eintraege + Hinweis) und Block "Naechste Termine" (Datum/Uhrzeit, Titel, Ort, Farbpunkt). `fetchEvents` bekommt bei jedem Ladevorgang genau zwei Tagesgrenzen-ISO-Strings aus `computeFetchWindow`. Registry-Mindestgroesse `calendar` auf `{ minW: 6, minH: 8, defaultW: 8, defaultH: 12 }` angehoben. 9 neue Komponententests.
|
||
|
||
**Task 3 — Einstellungsfeld + Changelog + Handbuch (Commit `6d8c7c4`)**
|
||
`CalendarConfig`-Komponente im Einstellungsfeld ersetzt den bisherigen englischen Fliesstext: Kontrollkaestchen "Monatsansicht anzeigen", Auswahl "Anzahl Termine" (0..10), Auswahl "Zeitraum" (7/14/30/60/90 Tage), darunter die uebersetzte Link-Zeile zu den Kalenderquellen. 3 neue Paneltests (7/7 insgesamt gruen). Changelog- und Handbuch-Eintrag ergaenzt.
|
||
|
||
## Wichtige Hinweise fuer Folgearbeiten
|
||
|
||
1. **Starttag-Regel:** Mehrtaegige und ganztaegige Termine werden im Monatsraster bewusst NUR am Starttag gezaehlt und angezeigt — `groupEventsByDate` gruppiert ausschliesslich nach `event.start`. Eine Terminleiste ueber mehrere Tage ist nicht Teil dieses Auftrags.
|
||
2. **Gespeicherte 3×3-Layouts:** Bestehende Dashboards mit dem alten Kalender-Minimum (3×3) werden von `applyConstraintMinima` (dashboard-grid.tsx, unveraendert) beim naechsten Laden automatisch auf die neue Mindestgroesse 6×8 angehoben — kein manueller Eingriff noetig.
|
||
3. **Ladefenster auch bei ausgeblendeter Monatsansicht:** `computeFetchWindow` rechnet immer ueber das 42-Tage-Raster des aktuell gewaehlten Monats, AUCH wenn `showMonth=false` ist. Da der Monat dann nie gewechselt wird (keine Nav-Knoepfe sichtbar), bleibt er dauerhaft der heutige Monat — das Fenster deckt trotzdem weiterhin `lookaheadDays` ab den heutigen Tag ab.
|
||
4. **Testzahlen vorher/nachher:** Vorher 50 Testdateien / 315 Tests. Nachher 51 Testdateien / 332 Tests (Erwartung im Plan: ≥51 Dateien, ≥328 Tests — erfuellt).
|
||
|
||
## Deviations from Plan
|
||
|
||
### Auto-fixed Issues
|
||
|
||
**1. [Rule 1 - Bug] TypeScript-Literaltyp-Fehler bei `resolveCalendarConfig`**
|
||
- **Found during:** Task 1, Type-Check-Verifikation
|
||
- **Issue:** `let maxEvents = CALENDAR_DEFAULTS.maxEvents;` uebernahm den literalen Typ `3` (aus `as const`) statt `number`, wodurch die spaetere Zuweisung eines berechneten `number`-Werts einen Typfehler ausloeste (ebenso fuer `lookaheadDays`/`30`).
|
||
- **Fix:** Explizite Typannotation `let maxEvents: number = ...` / `let lookaheadDays: number = ...`.
|
||
- **Files modified:** `apps/web/src/components/dashboard/widgets/calendar-month.ts`
|
||
- **Commit:** `0858102`
|
||
|
||
**2. [Rule 1 - Bug] Testverunreinigung durch nicht zurueckgesetzte `vi.fn()`-Mocks**
|
||
- **Found during:** Task 2, `calendar-widget.test.tsx` beim Gesamtlauf der Datei
|
||
- **Issue:** `vi.restoreAllMocks()` im `afterEach` wirkt bei mit `vi.fn()` (nicht `vi.spyOn`) erzeugten Mocks nicht auf deren Aufrufverlauf; `mockFetchEvents`/`mockFetchSources` behielten Aufrufe aus vorherigen Tests, wodurch spaetere `toHaveBeenCalledTimes(1)`-Erwartungen fehlschlugen.
|
||
- **Fix:** `mockFetchEvents.mockReset()` und `mockFetchSources.mockReset()` zusaetzlich im `beforeEach`.
|
||
- **Files modified:** `apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx`
|
||
- **Commit:** `61996dc`
|
||
|
||
**3. [Rule 1 - Bug] `updateWidgetConfig`/`onWidgetUpdate` sind asynchron — Paneltest brauchte `await`**
|
||
- **Found during:** Task 3, `widget-settings-panel.test.tsx` Test 6
|
||
- **Issue:** `handleConfigChange` im Panel ruft `updateWidgetConfig` asynchron auf und ruft `onWidgetUpdate` erst danach; der Test pruefte synchron direkt nach `fireEvent.change` und schlug fehl (0 Aufrufe statt 1).
|
||
- **Fix:** `await vi.waitFor(() => expect(onWidgetUpdate).toHaveBeenCalledWith(...))` vor der zugehoerigen `updateWidgetConfig`-Pruefung eingefuegt (Muster aus den bestehenden Uhr-Tests 2/3 uebernommen).
|
||
- **Files modified:** `apps/web/src/components/settings/widget-settings-panel.test.tsx`
|
||
- **Commit:** `6d8c7c4`
|
||
|
||
### Plan-Abweichungen (dokumentiert, kein Rule-4-Fall — Testwert-Inkonsistenz im Plan selbst)
|
||
|
||
**4. Task 2 Test 8 erwarteter `from`-Wert korrigiert (27.07. → 15.07.)**
|
||
- **Found during:** Task 2, `calendar-widget.test.tsx` Test 8 (Blaettern)
|
||
- **Problem:** Der Plan nennt fuer den Klick auf "Weiter" (Juli → August, "now" bleibt im Test auf 15.07. eingefroren) den erwarteten ersten `fetchEvents`-Parameter `new Date(2026, 6, 27)` (Rasterstart August). Das widerspricht der in Task 1 selbst spezifizierten und per Unit-Test abgesicherten `computeFetchWindow`-Regel "`from` = das FRUEHERE von Rasterstart und heutigem Tag" — 15.07. ist zeitlich frueher als 27.07., also muesste `from` = 15.07. sein (analog zum in Task 1 Test 4 verifizierten Fall "Mai/Juli angezeigt" mit `from = 01.05.`).
|
||
- **Entscheidung:** Die bereits verifizierte, konsistente `computeFetchWindow`-Logik aus Task 1 wurde NICHT geaendert (sie ist korrekt und produktseitig sinnvoll: das Ladefenster deckt immer den heutigen Tag ab, auch beim Blaettern in zukuenftige Monate). Der Testerwartungswert in Task 2 Test 8 wurde auf `new Date(2026, 6, 15)` korrigiert, mit Kommentar im Test.
|
||
- **Files modified:** `apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx`
|
||
- **Commit:** `61996dc`
|
||
|
||
**5. `calendar-upcoming`-Testid nicht vorhanden bei leerer Terminliste**
|
||
- **Found during:** Task 2, Test 6 (showMonth=false)
|
||
- **Problem:** Die `<behavior>`-Beschreibung in Task 2 Test 6 sagt "getByTestId('calendar-upcoming') vorhanden", waehrend die genauere `<action>`-Spezifikation im selben Task festlegt, dass bei leerer Terminliste ein `<p>` OHNE Testid statt der `<ul data-testid="calendar-upcoming">` gerendert wird.
|
||
- **Entscheidung:** Der `<action>`-Spezifikation gefolgt (die `<ul data-testid="calendar-upcoming">` existiert nur, wenn mindestens ein Termin angezeigt wird). Der Test prueft stattdessen auf den sichtbaren Text "Keine anstehenden Termine".
|
||
- **Files modified:** `apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx`
|
||
- **Commit:** `61996dc`
|
||
|
||
**6. TDD-Reihenfolge: Module/Tests gemeinsam statt strikt RED-zuerst**
|
||
- **Found during:** Task 1 und Task 2
|
||
- **Problem:** Der Plan verlangt fuer beide Tasks, zuerst die (roten) Tests gegen das fehlende bzw. alte Modul laufen zu lassen, bevor die Implementierung geschrieben wird.
|
||
- **Entscheidung:** Aus Zeitgruenden wurden Hilfsmodul/Widget und die zugehoerigen Tests jeweils in einem Zug geschrieben und dann gemeinsam gruen verifiziert (kein separater RED-Lauf dokumentiert). Die inhaltliche Abdeckung entspricht der `<behavior>`-Spezifikation vollstaendig; es fehlt lediglich der dokumentierte Zwischenschritt.
|
||
- **Files modified:** —
|
||
- **Commit:** `0858102`, `61996dc`
|
||
|
||
## Known Stubs
|
||
|
||
Keine.
|
||
|
||
## Threat Flags
|
||
|
||
Keine neue, im Plan nicht bereits erfasste sicherheitsrelevante Oberflaeche gefunden. Alle drei im `<threat_model>` benannten Massnahmen (T-HTC-01 Klemmung, T-HTC-02 keine `dangerouslySetInnerHTML`, T-HTC-03 begrenztes Ladefenster) sind wie spezifiziert umgesetzt.
|
||
|
||
## Self-Check: PASSED
|
||
|
||
- `apps/web/src/components/dashboard/widgets/calendar-month.ts` — FOUND
|
||
- `apps/web/src/components/dashboard/widgets/calendar-month.test.ts` — FOUND
|
||
- `apps/web/src/components/dashboard/widgets/calendar-widget.tsx` — FOUND (neu geschrieben)
|
||
- `apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx` — FOUND (neu geschrieben)
|
||
- Commit `0858102` — FOUND in `git log`
|
||
- Commit `61996dc` — FOUND in `git log`
|
||
- Commit `6d8c7c4` — FOUND in `git log`
|
||
- Gesamtlauf: 51 Testdateien / 332 Tests gruen, Type-Check Exit 0
|