▍ humdrum codex / soft
5.7 KB raw

Personal Dashboard — Plan

Mode: greenfield _Generated from BRIEF.md + shared-app-kit/APP_KIT.md.

Snapshot

Kit deviations (local-only) — build must honor

Keep kit look + structure (tokens, fonts, layout, themes, eink, API-first Zod, export/import, PWA shell, design primitives). Swap infra:

Data model (lib/schemas/, Drizzle in db/schema.ts)

Source   { id, kind, label, enabled, config: json, refreshSeconds, position }
         kind  weather|news|onthisday|sports|calendar|mastodon|bluesky|github|vercel|obsidian|todos
Snapshot { id, sourceIdSource, payload: json, fetchedAt, ok, error|null }   // cache
Todo     { id, title, done, due|null, createdAt }
Edition  { id, date, pdfPath, generatedAt }
Setting  { key (pk), value: json }                                            // location, units, layout

Per-source payloads get their own Zod schema in lib/schemas/sources/<kind>.ts.

Source module pattern (the core abstraction)

Each source = lib/sources/<kind>.ts exporting: { kind, label, keyless, configSchema, payloadSchema, fetch(config) }, registered in lib/sources/registry.ts. A source has: a fetcher, a typed payload, a dashboard card (components/cards/<Kind>Card.tsx), and an edition section (components/edition/<Kind>Section.tsx). Snapshots cached in SQLite; refreshed on load when older than refreshSeconds, or via manual refresh. Missing creds → card shows "needs config", never crashes.

API routes

Method Path Notes
GET /api/health liveness
GET/POST /api/sources list configured + latest snapshot / create
GET/PATCH/DELETE /api/sources/[id] read (cached or fresh) / update config / remove
POST /api/sources/[id]/refresh force fetch, store Snapshot
GET/POST /api/todos · PATCH/DELETE /api/todos/[id] local todos
GET /api/editions · POST /api/edition list / generate dated PDF
GET /api/export · POST /api/import kit JSON contract (single-user id)
GET /api/settings · PUT /api/settings location, units, card layout

Build tasks (ordered)

  1. Copy scaffold → repo (preserve .git/BRIEF/PLAN). Rename package.jsonpersonal-dashboard.
  2. Strip cloud per "Kit deviations": remove Clerk/Stripe/Neon deps + files, swap to SQLite, add new deps, write single-user lib/auth.ts, delete vercel.json. pnpm install.
  3. Zod schemas: core (source, snapshot, todo, edition, setting) + lib/sources/registry.ts skeleton.
  4. db/schema.ts (sqlite tables) + db/index.ts (better-sqlite3). pnpm db:push. Seed default Sources (the keyless four + todos enabled).
  5. Stub all /api/* routes → 501, so contract is visible. Verify with curl.
  6. Implement core handlers: settings, todos, sources CRUD, snapshot refresh + cache TTL, export/import.
  7. Keyless sources end-to-end: weather (Open-Meteo + geocode), onthisday (Wikipedia), news (rss-parser), obsidian (scan /Users/kortum/Humdrum mtimes, read-only). Each: fetch + payload schema + card.
  8. Dashboard UI: responsive card grid reading /api/sources; per-card loading/error/needs-config states; manual refresh. Todos card (add/toggle). Mount kit ShortcutsRoot + SettingsDrawer.
  9. Settings page: location/units, RSS feed list, per-source enable + ordering, vault path.
  10. PDF edition: /print newspaper route (columns, Awke display, forced mono/light, uses same source data); POST /api/edition drives puppeteer → save to editions/ → insert row; "Generate edition" button + editions list.
  11. Credentialed sources (same pattern, stub-friendly): calendar (ICS via node-ical), github (PAT), vercel (token), mastodon (instance+token), bluesky (handle+app-pw), sports (ESPN public JSON). Wire as creds land; each independent.
  12. PWA: public/manifest.json (name/icons/theme), icons via scripts/generate-icons.mjs, service worker shell + network-first API.
  13. E-ink pass: dashboard + /print legible mono, no motion, run §Eink rubric.
  14. Launcher: scripts/launch.command (build-if-stale → next start on :4317 → open browser) + scripts/make-app.sh (osacompile a double-clickable Personal Dashboard.app that runs it without a Terminal window).
  15. README: setup, .env.local keys per source, launcher install, edition output path.

Open questions (not blockers)

Out of scope (v0)