wip: phase 05 plan-phase paused at revision verify (checker v2 pending)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 09:54:43 +02:00
parent c8852d2014
commit e60d34dc28
6 changed files with 823 additions and 59 deletions
@@ -2,28 +2,57 @@
phase: 05-dashboard-calendar
plan: 04
type: execute
wave: 4
wave: 3
depends_on: ["05-01", "05-02", "05-03"]
files_modified: []
autonomous: false
requirements: [DASH-01, DASH-02, DASH-03, DASH-04, DASH-05, DASH-06, DASH-07, CAL-01, CAL-02, CAL-03]
files_modified:
- apps/web/src/components/dashboard/widgets/calendar-widget.tsx
- apps/web/src/components/dashboard/widget-registry.ts
- apps/web/src/lib/calendar-api.ts
- apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx
- apps/web/src/app/(portal)/settings/dashboard/calendar/page.tsx
- apps/web/src/components/settings/calendar-settings-panel.tsx
- apps/web/src/components/settings/calendar-source-form.tsx
- apps/web/src/app/(portal)/settings/dashboard/calendar/calendar-settings.test.tsx
autonomous: true
requirements: [DASH-05, CAL-02, CAL-03]
must_haves:
truths:
- "Human confirms the dashboard grid, all four widgets, settings, and calendar integration work end-to-end"
artifacts: []
key_links: []
- "User can configure CalDAV, Exchange, and ICS calendar sources in Settings > Dashboard > Kalender"
- "User can toggle which calendar sources are visible in the widget"
- "Calendar widget shows upcoming events aggregated from selected sources"
artifacts:
- path: "apps/web/src/components/dashboard/widgets/calendar-widget.tsx"
provides: "Upcoming-events list widget"
- path: "apps/web/src/app/(portal)/settings/dashboard/calendar/page.tsx"
provides: "Calendar source management UI"
- path: "apps/web/src/lib/calendar-api.ts"
provides: "Calendar API client (sources CRUD + events)"
key_links:
- from: "apps/web/src/components/dashboard/widgets/calendar-widget.tsx"
to: "/api/calendar/events"
via: "fetch in effect"
pattern: "calendar/events"
- from: "apps/web/src/components/settings/calendar-settings-panel.tsx"
to: "/api/calendar/sources"
via: "updateSource visibility toggle"
pattern: "isVisible"
---
<objective>
Final human verification of the complete Phase 05 dashboard & calendar experience. All implementation is automated in plans 05-01 through 05-03; this plan pauses for the user to visually and functionally confirm the full flow before the phase closes.
Deliver the calendar frontend slice: a user manages CalDAV/Exchange/ICS calendar sources in Settings > Dashboard > Kalender, toggles which sources are visible, and the Calendar widget on the dashboard shows upcoming events aggregated and normalized across all visible sources (read-only). All data flows through the calendar backend built in 05-03 — the browser never fetches external calendars directly.
Purpose: Catch visual/interaction regressions that automated tests cannot (drag feel, theme correctness, real calendar fetch). Closes the phase against ROADMAP success criteria 1-5.
Purpose: Implements DASH-05 (calendar widget), CAL-02 (visibility selection), and CAL-03 (event previews for selected sources). The matching CAL-01 source integration + event aggregation backend ships in 05-03; this plan consumes it.
Output: Human sign-off (or a gap list to feed `/gsd-plan-phase --gaps`).
Output: A working calendar widget plus a source-management settings page wired to the 05-03 backend.
</objective>
<artifacts_this_phase_produces>
No new symbols — verification-only plan.
Symbols created by THIS plan (exclude from drift verification — they are new):
**Frontend components:** `CalendarWidget`, `CalendarSettingsPanel`, `CalendarSourceForm`
**Frontend modules:** `calendar-api.ts` (fetchSources/addSource/updateSource/deleteSource/testSource/fetchEvents); registry wiring of real `CalendarWidget`
Consumed (built in 05-03, do NOT redefine): `GET /api/calendar/events`, `GET|POST|PATCH|DELETE /api/calendar/sources`, `POST /api/calendar/sources/:id/test`, the `CalendarEvent` shape.
</artifacts_this_phase_produces>
<execution_context>
@@ -32,7 +61,12 @@ No new symbols — verification-only plan.
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/05-dashboard-calendar/05-CONTEXT.md
@.planning/phases/05-dashboard-calendar/05-RESEARCH.md
@.planning/phases/05-dashboard-calendar/05-PATTERNS.md
@.planning/phases/05-dashboard-calendar/05-UI-SPEC.md
@.planning/phases/05-dashboard-calendar/05-01-SUMMARY.md
@.planning/phases/05-dashboard-calendar/05-02-SUMMARY.md
@@ -41,60 +75,78 @@ No new symbols — verification-only plan.
<tasks>
<task type="auto">
<name>Task 1: Pre-flight — start stack and run full test suite</name>
<files></files>
<task type="auto" tdd="true">
<name>Task 1: Calendar API client + calendar widget</name>
<files>apps/web/src/lib/calendar-api.ts, apps/web/src/components/dashboard/widgets/calendar-widget.tsx, apps/web/src/components/dashboard/widget-registry.ts, apps/web/src/components/dashboard/widgets/calendar-widget.test.tsx</files>
<read_first>
- .planning/phases/05-dashboard-calendar/05-01-SUMMARY.md
- .planning/phases/05-dashboard-calendar/05-02-SUMMARY.md
- .planning/phases/05-dashboard-calendar/05-03-SUMMARY.md
- apps/web/src/components/dashboard/widget-registry.ts (replace calendar placeholder with real CalendarWidget)
- apps/web/src/components/dashboard/widgets/clock-widget.tsx (WidgetProps pattern)
- .planning/phases/05-dashboard-calendar/05-03-SUMMARY.md (CalendarEvent shape + GET /api/calendar/events contract from the backend plan)
- .planning/phases/05-dashboard-calendar/05-RESEARCH.md lines 287-310 (CalendarEvent interface), lines 364-412 (anti-pattern: never fetch calendars in the browser)
- .planning/phases/05-dashboard-calendar/05-UI-SPEC.md lines 110, 206-209 (calendar widget spec: time/title/source color dot; empty-state copy)
</read_first>
<behavior>
- Test (calendar-widget.test.tsx): given a mocked `/api/calendar/events` response with two events, the widget renders both titles, their times, and a source color dot per event
- given an empty events response, renders t('widgets.calendarEmptyNoEvents')
- given a no-sources response, renders t('widgets.calendarEmptyNoSources')
</behavior>
<action>
Confirm the full Docker Compose stack is running (web + api + postgres). Run the complete web test suite and the api type-check to confirm the phase is green before asking the human to verify. If anything fails, report it and do NOT proceed to the human checkpoint. Ensure CALENDAR_ENCRYPTION_KEY is set in the api environment so calendar endpoints respond.
Create `calendar-api.ts`: fetchSources, addSource, updateSource (incl. isVisible toggle), deleteSource, testSource, fetchEvents — all `credentials:'include'`. Type the returns against the `CalendarEvent` and source shapes documented in 05-03-SUMMARY (do not redefine the backend contract — mirror it).
Create `calendar-widget.tsx` (DASH-05, D-10 read-only): `'use client'`. On mount fetch /api/calendar/events via calendar-api.fetchEvents. Render an upcoming-events list: each row shows time (locale-formatted), title, and an 8px source color dot (UI-SPEC color palette). Three states per UI-SPEC copy: no sources configured → t('widgets.calendarEmptyNoSources'); sources but no events → t('widgets.calendarEmptyNoEvents'); events → list. Loading state while fetching. Refresh periodically (every 5 min to match the backend cache TTL). Never fetch external calendars directly from the browser (RESEARCH anti-pattern — always via /api/calendar/events).
Update `widget-registry.ts`: replace the calendar placeholder component with the real CalendarWidget. Keep WIDGET_CONSTRAINTS.
Write calendar-widget.test.tsx per <behavior> with mocked fetch.
</action>
<verify>
<automated>cd apps/web && pnpm test && cd ../api && npx tsc --noEmit</automated>
<automated>cd apps/web && pnpm vitest run src/components/dashboard/widgets/calendar-widget.test.tsx && pnpm exec tsc --noEmit</automated>
</verify>
<acceptance_criteria>
- `cd apps/web && pnpm test` exits 0 (full suite green)
- `cd apps/api && npx tsc --noEmit` exits 0
- Docker stack reachable (web responds, api /health responds)
- calendar-api.ts exports fetchSources/addSource/updateSource/deleteSource/testSource/fetchEvents, all using `credentials:'include'`
- calendar-widget.tsx fetches `/api/calendar/events` and renders three distinct empty/list states
- calendar-widget.tsx renders a source color dot per event row
- widget-registry.ts references the real `CalendarWidget`
- calendar-widget.test.tsx exits 0; `pnpm exec tsc --noEmit` exits 0
</acceptance_criteria>
<done>Full automated suite green and stack running; ready for human verification.</done>
<done>Calendar widget shows aggregated upcoming events from visible sources, cached refresh, read-only, with correct empty states.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>
Complete Phase 05 dashboard & calendar: a configurable drag-and-drop widget grid as the portal start page, four widgets (Clock, Search, Notes, Calendar), per-user layout persistence, a Settings page (via avatar menu) with widget config and calendar source management, and multi-protocol calendar integration (CalDAV / Exchange / ICS).
</what-built>
<how-to-verify>
Open the portal in a browser (logged in as a normal user).
<task type="auto" tdd="true">
<name>Task 2: Calendar settings page (source management + visibility)</name>
<files>apps/web/src/app/(portal)/settings/dashboard/calendar/page.tsx, apps/web/src/components/settings/calendar-settings-panel.tsx, apps/web/src/components/settings/calendar-source-form.tsx, apps/web/src/app/(portal)/settings/dashboard/calendar/calendar-settings.test.tsx</files>
<read_first>
- apps/web/src/app/(portal)/settings/layout.tsx (from 05-01 — nested settings layout)
- apps/web/src/components/settings/settings-sidebar.tsx (from 05-01 — this page is the "Kalender" sub-item target)
- apps/web/src/lib/calendar-api.ts (from Task 1 of this plan)
- .planning/phases/05-dashboard-calendar/05-UI-SPEC.md lines 120, 161-166, 204-209, 216-220, 256-261 (calendar settings panel spec, source form fields, connection test, delete confirmation, copy)
</read_first>
<behavior>
- Test (calendar-settings.test.tsx): rendering CalendarSettingsPanel with two mocked sources lists both with name + type badge + visibility toggle
- toggling a source calls updateSource with the new isVisible (CAL-02)
- the add form requires name+type+url before enabling submit
</behavior>
<action>
Create `settings/dashboard/calendar/page.tsx` (D-11): `'use client'`, renders CalendarSettingsPanel. Title t('settings.categoryCalendar').
Dashboard grid + persistence (DASH-01/02/07):
1. Confirm the start page shows the empty-state ("Keine Widgets aktiv") with a visible pencil edit button.
2. Click the pencil (top-right) — grid lines appear, "Widget hinzufuegen" button appears.
3. Add each widget type from the catalog modal (Clock, Search, Notes, Calendar). Confirm all four appear.
4. In edit mode, drag a widget to a new position and resize it. Confirm snapping + reflow.
5. Click the checkmark to exit edit mode. Reload the page — confirm the layout persists exactly (DASH-07).
Create `calendar-settings-panel.tsx`: fetch sources (calendar-api.fetchSources). Render a source list — each row: color dot, name, type badge (CalDAV/Exchange/ICS), visibility toggle switch (CAL-02 — calls updateSource({isVisible})), connection-status indicator (green check / orange warning from lastSyncError per UI-SPEC), edit + delete actions. Delete uses a confirmation dialog (UI-SPEC destructive: heading + body + "Quelle loeschen"/"Abbrechen"). "Quelle hinzufuegen" button reveals CalendarSourceForm. Empty state: t('settings.calendarEmpty') (UI-SPEC copy).
Clock (DASH-03): confirm it ticks. In Settings > Dashboard > Widgets, set a timezone and toggle date — confirm the widget updates.
Create `calendar-source-form.tsx`: fields Name (required), Type (select CalDAV/Exchange/ICS, required), when Exchange show an Exchange-mode select (Exchange Online=graph / Exchange Server=ews, RESEARCH open question 2), URL (required, https validation client-side), Username (optional, hidden for ICS), Password (password input, optional, hidden for ICS), Color (from the 8-color palette in UI-SPEC). On save call addSource (or updateSource when editing); then auto-run testSource and show connection-success/error toast (UI-SPEC copy). Validate URL is https before submit.
Search (DASH-04): pick a provider (Google/Bing/DuckDuckGo), type a query, press Enter/click — confirm a new browser tab opens the correct search. Add a custom provider in Settings and confirm it appears in the dropdown.
Notes (DASH-06): type Markdown (bold, checkbox list). Confirm live rendering + toolbar. Wait ~1s, reload — confirm content persisted (autosave). Set a custom title in Settings.
Settings (D-19/D-20): open via the avatar menu (NOT the sidebar). Confirm the sub-sidebar with Dashboard > Widgets / Kalender and the "Zurueck zum Dashboard" link.
Calendar (CAL-01/02/03, DASH-05): in Settings > Dashboard > Kalender add a real ICS source (e.g. a public .ics URL). Confirm connection success. Confirm the Calendar widget lists upcoming events with source color dots. Toggle the source's visibility off — confirm its events disappear from the widget. (CalDAV/Exchange: test if you have credentials; ICS is the minimum.)
Cross-cutting:
- Toggle dark/light theme — confirm all widgets (esp. Notes Markdown editor) render correctly in both.
- Switch DE/EN — confirm all dashboard/settings strings translate (no raw keys).
- Resize the browser narrow (<768px) — confirm widgets stack vertically (D-22).
Report any visual or functional issue; otherwise approve.
</how-to-verify>
<resume-signal>Type "approved" if everything works, or describe each issue found.</resume-signal>
Write calendar-settings.test.tsx per <behavior> with mocked calendar-api.
</action>
<verify>
<automated>cd apps/web && pnpm vitest run "src/app/(portal)/settings/dashboard/calendar/calendar-settings.test.tsx" && pnpm exec tsc --noEmit</automated>
</verify>
<acceptance_criteria>
- settings/dashboard/calendar/page.tsx renders CalendarSettingsPanel
- calendar-settings-panel.tsx has a visibility toggle calling updateSource with isVisible
- calendar-source-form.tsx shows an Exchange-mode select only when type is exchange, and hides username/password for ICS
- calendar-source-form.tsx validates https before submit
- delete uses a confirmation dialog (contains the "Quelle loeschen"/"Delete source" CTA)
- calendar-settings.test.tsx exits 0; `pnpm exec tsc --noEmit` exits 0
</acceptance_criteria>
<done>Users manage CalDAV/Exchange/ICS sources, toggle widget visibility, test connections, and delete with confirmation.</done>
</task>
</tasks>
@@ -102,26 +154,32 @@ No new symbols — verification-only plan.
<threat_model>
## Trust Boundaries
No new trust boundaries — verification-only plan; all enforcement was implemented and threat-modeled in plans 05-01 through 05-03.
| Boundary | Description |
|----------|-------------|
| Browser → Calendar API | Frontend submits source config (incl. credentials) to the 05-03 backend; never fetches external calendars directly |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-05-V1 | Information Disclosure | manual calendar source test | accept | Human uses own test credentials in a dev stack; no production data |
| T-05-14 | Tampering / SSRF | calendar-source-form URL field | mitigate | Client-side https-only validation before submit; authoritative SSRF + https enforcement lives in the 05-03 backend DTO |
| T-05-15 | Information Disclosure | calendar widget render | mitigate | Widget consumes /api/calendar/events only; passwords never reach the browser (backend GET excludes encryptedPassword) |
</threat_model>
<verification>
- Full web test suite green (Task 1)
- API type-check green (Task 1)
- Human confirms all ROADMAP Phase 05 success criteria 1-5
- `cd apps/web && pnpm exec tsc --noEmit` exits 0
- `cd apps/web && pnpm vitest run src/components/dashboard/widgets/calendar-widget.test.tsx "src/app/(portal)/settings/dashboard/calendar/calendar-settings.test.tsx"` green
</verification>
<success_criteria>
- Human approves the complete dashboard + calendar experience, OR
- A concrete gap list is produced for `/gsd-plan-phase 05 --gaps`
- User manages CalDAV, ICS, and Exchange sources in Settings > Dashboard > Kalender
- User toggles source visibility; only visible sources feed the widget (CAL-02/CAL-03)
- Calendar widget shows aggregated upcoming events with source color dots, read-only (DASH-05)
- Browser never fetches external calendars directly — all data via /api/calendar/events
</success_criteria>
<output>
Create `.planning/phases/05-dashboard-calendar/05-04-SUMMARY.md` when done
</output>
</content>
</invoke>