Calendars as entities, uniform widget contract, multi-dashboard tasks
Architecture refinements before module work begins: - Calendars become first-class entities (mirror of lists). Each calendar has its own visibility (private | household) and is independently shareable. Updated CLAUDE.md data model and task 10. - Drop the singleton/parameterized split for dashboard widgets. Every widget declares a configSchema + defaultConfig + resolveConfigOptions and every placement is an independent instance — same widgetId can appear multiple times on one dashboard pointed at different things. Updated task 04 contract; tasks 11/12 widget sections aligned. - Add tasks 25 (multiple dashboards per user) and 26 (customizable layout + widget configuration). Replaces the previously-considered three-task plan with a cleaner two-task split that the uniform contract enables. Phase 3 index updated; STATUS records the rationale. - Task 20 reframed as a single-dashboard MVP that exercises the new contract end-to-end before the customization layer lands.
This commit is contained in:
+64
-15
@@ -1,33 +1,82 @@
|
||||
# 20 — Dashboard composition
|
||||
# 20 — Dashboard composition (single-dashboard MVP)
|
||||
|
||||
## Goal
|
||||
|
||||
The `/` landing page composes widgets contributed by installed modules. No hardcoded widget list.
|
||||
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, 11, 12
|
||||
- 10 (calendar), 11 (lists), 12 (notes)
|
||||
|
||||
## 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).
|
||||
### 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
|
||||
|
||||
- User-configurable widget order or hide/show (defer; pin a sane default order).
|
||||
- Drag-rearrange.
|
||||
- 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 layout (375px) is usable without horizontal scroll.
|
||||
- [ ] 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).
|
||||
|
||||
Reference in New Issue
Block a user