# 20 — Dashboard composition (single-dashboard MVP) ## Goal Each user sees a single default dashboard with widgets pre-seeded from the registry. Every widget is rendered with a `config` (per the task 04 contract). No customization yet — task 25/26 add multiple dashboards and the editor. ## Why Lands a working dashboard quickly so the app is usable while the customization layer is built. Validates the configurable widget contract end-to-end. The schema introduced here (one row per user) gets generalized in task 25 to support many dashboards per user. ## Depends on - 10 (calendar), 11 (lists), 12 (notes) ## Scope ### Schema (interim, generalized in task 25) Add a `default_dashboard_layout` jsonb column on `users`. Shape: ```jsonc { "version": 1, "widgets": [ { "widgetId": "calendar.upcoming", "config": { "calendarIds": "all", "days": 3 }, "x": 0, "y": 0, "w": 6, "h": 3, }, { "widgetId": "lists.list", "config": { "listIds": "all", "showCompleted": false }, "x": 6, "y": 0, "w": 6, "h": 3, }, ], } ``` If null, fall back to the registry's default seed (sorted by `defaultPriority`, packed by `defaultSize`). ### Layout - 12-col responsive grid using basic CSS Grid (no drag library yet — task 26 introduces it). - Mobile: single column ordered by `y` then `x`. - Each widget is a server component that receives `config` and renders inside a card. Suspense boundary per widget so they load in parallel. ### Seeded widgets the registry must contribute These are widget definitions, not hardcoded slots: - `calendar.upcoming` (calendar module) - `calendar.month` (calendar module) - `lists.list` (lists module) - `notes.filtered` (notes module) - `core.activity` (\_core; depends on task 22 — until then render a placeholder card) ### Header - Page title "Dashboard". - "+" floating action button opens the quick-add menu (task 21). - _No_ edit-mode toggle yet — that lands in task 26. ## Out of scope - Multiple dashboards (task 25). - Drag, resize, picker, configure UI (task 26). - User-configurable widget order. Defaults are taken from `defaultPriority`. ## Acceptance criteria - [ ] First-time user lands on a populated dashboard with widgets seeded from the registry's `defaultPriority` + `defaultSize`. - [ ] Every widget receives a typed `config` and renders without `if (widgetId === ...)` branches in the dashboard code. - [ ] Removing a module from `src/modules/index.ts` cleanly removes its widgets — no errors, no blank slot. - [ ] Widgets load in parallel (verify in network tab — no waterfall). - [ ] Mobile (375px) is usable without horizontal scroll. - [ ] The schema column added here has a clean migration path to the `dashboards` table introduced in task 25 (a one-line copy-into-default-dashboard migration is enough).