# 20 — Dashboard composition ## Goal The `/` landing page composes widgets contributed by installed modules. No hardcoded widget list. ## Depends on - 10, 11, 12 ## Scope - `DashboardWidget` type already in `_core` (from task 04). Each widget exports `{ id, priority, render }`. - `/` reads `getRegistry().dashboardWidgets`, sorts by priority desc, renders them in a responsive grid (1 col mobile, 2-3 cols desktop). - Widgets implemented: - **calendar.upcoming** — next 3 days of events - **lists.shopping** — top 5 unchecked items in default shopping list - **lists.tasks** — my open tasks (assigned to me, due soon first) - **notes.pinned** — pinned notes - **core.activity** — recent activity feed (depends on task 22; until then render a stub) - Skeleton loaders while widgets load (each widget is a server component with `loading.tsx`-equivalent Suspense boundary). - "+" floating action button opens the quick-add menu (task 21). ## Out of scope - User-configurable widget order or hide/show (defer; pin a sane default order). - Drag-rearrange. ## Acceptance criteria - [ ] 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 layout (375px) is usable without horizontal scroll.