Files
ginnoir 09d6adbf18 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.
2026-05-06 01:14:49 -05:00

2.8 KiB

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:

{
  "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).