--- phase: 05-dashboard-calendar plan: 01 subsystem: dashboard tags: [dashboard, widgets, grid, settings, prisma, nestjs, react-grid-layout] dependency_graph: requires: [] provides: [DashboardModule, DashboardGrid, ClockWidget, WidgetRegistry, SettingsLayout] affects: [apps/api/prisma/schema.prisma, apps/api/src/app.module.ts, apps/web/src/app/(portal)/page.tsx, apps/web/src/components/layout/header.tsx] tech_stack: added: [react-grid-layout@2.2.3, react-resizable] patterns: [Zustand store without persist, react-grid-layout v2 Responsive API, widget-registry pattern, dragConfig/resizeConfig v2 API] key_files: created: - apps/api/src/dashboard/dashboard.module.ts - apps/api/src/dashboard/dashboard.controller.ts - apps/api/src/dashboard/dashboard.service.ts - apps/api/src/dashboard/dto/save-layout.dto.ts - apps/api/src/dashboard/dto/create-widget.dto.ts - apps/api/src/dashboard/dto/update-widget-config.dto.ts - apps/web/src/components/dashboard/dashboard-grid.tsx - apps/web/src/components/dashboard/edit-mode-toggle.tsx - apps/web/src/components/dashboard/widget-catalog-modal.tsx - apps/web/src/components/dashboard/widget-registry.tsx - apps/web/src/components/dashboard/widgets/clock-widget.tsx - apps/web/src/components/dashboard/widgets/widget-wrapper.tsx - apps/web/src/lib/dashboard-api.ts - apps/web/src/lib/stores/dashboard-store.ts - apps/web/src/app/(portal)/settings/layout.tsx - apps/web/src/app/(portal)/settings/page.tsx - apps/web/src/components/settings/settings-sidebar.tsx - apps/web/src/components/dashboard/dashboard-grid.test.tsx - apps/web/src/components/dashboard/widgets/clock-widget.test.tsx modified: - apps/api/prisma/schema.prisma - apps/api/src/app.module.ts - apps/web/package.json - apps/web/src/app/(portal)/page.tsx - apps/web/src/components/layout/header.tsx - apps/web/src/messages/de.json - apps/web/src/messages/en.json - apps/web/vitest.config.ts - apps/web/src/test/setup.ts decisions: - "react-grid-layout v2 uses dragConfig/resizeConfig instead of isDraggable/isResizable" - "react-resizable installed as direct dependency (pnpm strict isolation requires it)" - "widget-registry.tsx uses wireClockWidget() deferred wiring to avoid circular deps" - "ResizeObserver polyfill added to test setup for jsdom compatibility" - "Prisma JSON fields cast via unknown to InputJsonValue for type safety" metrics: duration: 14min completed: 2026-06-24T09:25:00Z tasks: 4 files_created: 19 files_modified: 9 --- # Phase 05 Plan 01: Dashboard Grid + Clock Widget + Settings Shell Summary Full vertical stack for dashboard: Prisma models (DashboardLayout, WidgetInstance), NestJS CRUD API with userId ownership verification, react-grid-layout v2 Responsive grid with 4-widget registry, clock widget using Intl.DateTimeFormat, Zustand store without persist (D-05), settings page shell with sub-sidebar, and all phase i18n keys. ## Tasks Completed | Task | Name | Commit | Key Files | |------|------|--------|-----------| | 1 | Dashboard backend | 950eebb | schema.prisma, dashboard.controller.ts, dashboard.service.ts | | 2 | Dashboard grid + clock widget + store | d5e1c42 | dashboard-grid.tsx, clock-widget.tsx, widget-registry.tsx, dashboard-store.ts | | 2 (RED) | Failing tests | 792b766 | dashboard-grid.test.tsx, clock-widget.test.tsx | | 3 | Settings shell + header link + i18n | 8a9c1ae | settings/layout.tsx, settings-sidebar.tsx, de.json, en.json | | 4 | Prisma schema push | (no code change) | Database tables created | ## Verification Results - `prisma validate`: PASS - `tsc --noEmit` (apps/api): PASS - `tsc --noEmit` (apps/web): PASS - `vitest run src/components/dashboard`: 5/5 PASS - `vitest run` (all tests): 31/31 PASS - JSON parse (de.json, en.json): PASS - `prisma db push`: "database is already in sync" ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 3 - Blocking] react-grid-layout v2 API changes** - **Found during:** Task 2 - **Issue:** react-grid-layout v2 uses `dragConfig`/`resizeConfig` objects instead of `isDraggable`/`isResizable` props. The type `Layouts` is renamed to `ResponsiveLayouts`. - **Fix:** Rewrote grid component to use v2 API: `dragConfig: { enabled, handle }` and `resizeConfig: { enabled }`. - **Files modified:** apps/web/src/components/dashboard/dashboard-grid.tsx **2. [Rule 3 - Blocking] pnpm strict isolation for react-resizable CSS** - **Found during:** Task 2 - **Issue:** `react-resizable/css/styles.css` import failed in tests because react-resizable is a transitive dep of react-grid-layout, not directly installed. pnpm strict isolation blocks resolution. - **Fix:** Added `react-resizable` as direct dependency in apps/web/package.json. - **Files modified:** apps/web/package.json **3. [Rule 3 - Blocking] ResizeObserver not available in jsdom** - **Found during:** Task 2 - **Issue:** DashboardGrid uses ResizeObserver for container width measurement, which jsdom doesn't provide. - **Fix:** Added ResizeObserver polyfill to apps/web/src/test/setup.ts that fires immediately with mock dimensions. - **Files modified:** apps/web/src/test/setup.ts **4. [Rule 3 - Blocking] widget-registry.ts JSX in .ts file** - **Found during:** Task 2 - **Issue:** PlaceholderWidget component uses JSX but file was `.ts` extension. - **Fix:** Renamed to `widget-registry.tsx`. - **Files modified:** apps/web/src/components/dashboard/widget-registry.tsx **5. [Rule 1 - Bug] Prisma JSON type mismatch** - **Found during:** Task 1 - **Issue:** `Record` not assignable to Prisma's `InputJsonValue` type. - **Fix:** Cast JSON fields via `as unknown as Prisma.InputJsonValue`. - **Files modified:** apps/api/src/dashboard/dashboard.service.ts ## Decisions Made | Decision | Context | Outcome | |----------|---------|---------| | react-grid-layout v2 Responsive API | v2 is a complete TypeScript rewrite with different prop names | Used `dragConfig`/`resizeConfig` objects instead of boolean props | | react-resizable as direct dep | pnpm strict isolation prevents resolving transitive CSS imports | Added as explicit dependency in apps/web | | wireClockWidget() pattern | Avoid circular deps between widget-registry and clock-widget | Deferred wiring function called from page.tsx at mount time | | Prisma db push via docker exec | DB port not exposed to host | Copied schema into API container and ran prisma db push from there | ## Known Stubs | File | Line | Stub | Reason | |------|------|------|--------| | widget-registry.tsx | search/calendar/note entries | PlaceholderWidget component | Implemented in plans 05-02 (search/note) and 05-03 (calendar) | | settings-sidebar.tsx | /settings/dashboard, /settings/dashboard/calendar links | Links to pages not yet created | Created in plans 05-02 and 05-03 | ## Self-Check: PASSED All 19 created files verified present. All 4 commit hashes found in git log.