# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## What this is A **local-only, single-user** personal dashboard (weather, news, history, sports, calendar, todos, Mastodon/Bluesky, GitHub/Vercel, Obsidian) plus a printable daily newspaper PDF. Runs only at `http://localhost:4317`. See `README.md` for usage, `BRIEF.md`/`PLAN.md` for intent. It descends from the **37 Apps kit** (`../_shared-app-kit/`) but **deliberately drops the cloud stack**: no Clerk, Stripe, Vercel, or Neon. Don't "restore kit parity" by adding them back — local-only is the point. ## Commands ```bash pnpm dev # dev server on :4317 (Turbopack) pnpm build # MUST be --webpack (set in script) — see gotchas pnpm start # run the production build on :4317 pnpm typecheck # tsc --noEmit — run this to verify changes pnpm db:push # apply db/schema.ts to data/dashboard.db pnpm db:seed # idempotently add missing default sources pnpm icons # regenerate PWA icons from public/icons/source.svg ``` No test suite. Verify with `pnpm typecheck` + a manual run (or puppeteer screenshot against the dev server, which is how the UI was validated during the build). ## Architecture (the parts that span files) **API-first, single data path.** Every surface reads `/api/*`. Only `lib/store.ts` touches the DB (Drizzle over local SQLite, `better-sqlite3`). Route handlers wrap logic in `withErrors()` from `lib/api.ts` and call `requireUser()` from `lib/auth.ts` — a **no-op single-user shim** (returns `"local"`). If real auth is ever needed, that one file is the seam. **The source module pattern is the core abstraction.** A "source" (weather, news, github…) is defined once in `lib/sources/modules/.ts` as a `SourceModule`: `fetch()`, a Zod `configSchema` + `payloadSchema`, and `isConfigured()`. Modules register in `lib/sources/modules/index.ts`. The flow: - `db.sources` rows hold per-source config/enabled/refreshSeconds/position. - `lib/refresh.ts` runs a module's `fetch`, validates the payload, and stores a **`snapshot` row = the cache**. `ensureFresh()` refreshes when forced or stale; `currentState()` reads cache only (instant paint). - The dashboard (`components/Dashboard.tsx`) renders one card per source; `components/cards/CardBodies.tsx` switches on `kind` to render each payload. - Credentialed sources with missing keys return `configured:false` → card shows "needs config" instead of erroring. **To add a source:** schema in `lib/schemas/sources/.ts` → module in `lib/sources/modules/.ts` → register in the barrel → add a branch in `CardBodies.tsx` → add to `scripts/seed.ts` and run `pnpm db:seed`. **macOS-native sources** (Calendar.app, Things 3) run AppleScript through `lib/mac.ts` (`runOsa` + FS/RS-delimited output parsed by `parseRows`). The host process needs Automation permission (macOS prompts once). Calendar lists names via `/api/integrations/calendars`; Things completes a task via `/api/integrations/things/complete`. The `"todos"` kind is the **Things Today** source, rendered by `components/cards/ThingsCard.tsx` (special-cased in the Dashboard for its complete-checkbox). A vestigial local `/api/todos` + `todos` table remain for export compatibility but aren't shown. **Bento layout:** each source stores explicit `cols`/`rows` grid spans (`lib/layout.ts` `spanStyle`); the dashboard is a dense `grid-auto-flow` with fixed `gridAutoRows`, and cards fill their tile and scroll internally. The dashboard's **"Edit layout"** mode (`components/Dashboard.tsx`) makes cards dnd-kit drag sources for reordering (renumbers `position`) and adds a bottom-right corner handle that resizes by whole grid cells (writes `cols`/`rows`); both persist via `PATCH /api/sources/[id]`. The old `size` enum column + `TILE_SPANS` are **vestigial** (kept for export/back-compat, no longer drive layout). **The newspaper.** `/print` is a server component rendering current snapshots (`lib/edition-data.ts`) with print CSS in `app/globals.css` (`.paper`, `@media print` hides the nav). `POST /api/edition` (`lib/pdf.ts`) refreshes sources, then drives **puppeteer** to render `/print` to a PDF saved under `editions/`, recorded in the `editions` table and served by `/api/editions/[id]/file`. ## Gotchas - **`pnpm build` is `next build --webpack`.** `@serwist/next` (the PWA service worker) is a webpack plugin and breaks under Next 16's default Turbopack build. Don't change it back to plain `next build`. Dev stays on Turbopack (SW is disabled in dev). - **Native deps must stay in `serverExternalPackages`** (`next.config.ts`): `better-sqlite3`, `puppeteer`. And in pnpm's `onlyBuiltDependencies` allowlist (`package.json`) so their postinstall build scripts run. - **`typedRoutes` is intentionally off** — nav is config-driven (`lib/nav.ts`) with string hrefs, which fights route-literal typing. - **Obsidian is read-only.** The obsidian source only stats/lists vault files. Never write to the vault from here (a global hook also blocks writes outside `Humdrum/Claude/`). - `data/` (SQLite), `editions/` (PDFs), and `Personal Dashboard.app` are gitignored local artifacts. - Theme tokens come from `app/tokens.css` (synced from the kit). Components use `var(--token)` inline styles, never hard-coded colors. The e-ink theme exists but is unused (desktop-only).