▍ humdrum codex / soft

Personal Dashboard

A local, single-user dashboard inspired by firstlight: weather, news, on-this-day history, sports, calendar, todos, recent Mastodon/Bluesky replies, GitHub & Vercel activity, and recently edited Obsidian notes — on one screen, plus a printable daily newspaper PDF.

Runs only on your Mac at http://localhost:4317. No accounts, no cloud, no deploy.

Built on the 37 Apps kit (themes, fonts, layout, PWA), with the cloud bits swapped out: no Clerk/Stripe/Vercel/Neon — local SQLite + a double-click launcher instead.


Run it (no terminal)

bash scripts/make-app.sh

This creates Personal Dashboard.app in the project folder. Double-click it (or drag it to your Dock). First launch installs deps + builds (a minute or two), then opens your browser. Subsequent launches are instant. The server keeps running until you quit it.

You can also double-click scripts/launch.command directly — same thing, but it shows a Terminal window with logs.

Run it (terminal, for development)

pnpm install
cp .env.example .env.local      # fill in optional source keys
pnpm db:push && pnpm db:seed    # create + seed the local SQLite db
pnpm dev                        # http://localhost:4317
Command What
pnpm dev Dev server (Turbopack, hot reload) on :4317
pnpm build Production build (webpack — required by the PWA service worker)
pnpm start Run the production build on :4317
pnpm typecheck tsc --noEmit
pnpm db:push Apply the schema to data/dashboard.db
pnpm db:seed Add any missing default sources (idempotent)
pnpm icons Regenerate PWA icons from public/icons/source.svg

Sources

Each source is a self-contained module in lib/sources/modules/. Keyless ones work out of the box; credentialed ones show "needs config" until you add their keys to .env.local and enable them in Settings.

Source Needs Env keys
Weather nothing (Open-Meteo) set a location in Settings (or WEATHER_LAT/WEATHER_LON)
On This Day nothing (Wikipedia)
News nothing (RSS) edit feed list in Settings
Obsidian nothing (reads vault, read-only) OBSIDIAN_VAULT (default /Users/kortum/Humdrum)
Scores nothing (ESPN) pick leagues in Settings config (e.g. basketball/nba)
GitHub token GITHUB_TOKEN, optional GITHUB_USER
Vercel token VERCEL_TOKEN, optional VERCEL_TEAM_ID
Mastodon instance + token MASTODON_INSTANCE, MASTODON_TOKEN
Bluesky handle + app password BLUESKY_HANDLE, BLUESKY_APP_PASSWORD
Calendar local Calendar.app (pick which calendars in Settings) or ICS URLs — / CALENDAR_ICS_URLS
Today local Things 3 (pulls the Today list)
Links nothing — a list of label + URL (your other web apps, bookmarks)

Toggle, reorder, resize (bento tiles: small → large), and configure sources in Settings. Each card refreshes on its own interval (cached in SQLite); hit ↻ on a card or Refresh all to force it. Weather takes a place name (geocoded) and defaults to Napa, CA. News merges your own RSS with a curated set.

Add more cards: Settings → Add source lets you add another card of any kind — e.g. a second Links card for bookmarks, or a second News card for personal feeds. Delete any card with its ✕.

How Scores works

The Scores card has two independent settings:

So leagues basketball/nba + those teams = all NBA scores plus just the Cubs, Bears, and Blackhawks — and you never see other MLB/NFL/NHL games. Common ESPN paths: basketball/nba, baseball/mlb, football/nfl, hockey/nhl, soccer/eng.1. Games are grouped by league on the card. (ESPN's public scoreboard is keyless.)

macOS permission: Calendar and Things use AppleScript. The first time the app reads them, macOS asks to allow automation — click OK. (Grant it to the launcher app / your terminal.)

Add a new source

  1. lib/schemas/sources/<kind>.ts — Zod Config + Payload.
  2. lib/sources/modules/<kind>.ts — implement SourceModule (fetch, isConfigured).
  3. Register it in lib/sources/modules/index.ts.
  4. Add a render branch in components/cards/CardBodies.tsx.
  5. Add it to scripts/seed.ts and run pnpm db:seed.

The Paper

The Paper tab generates a dated newspaper PDF from your current dashboard data (/print is the live HTML version). PDFs are saved to editions/ in the project folder and listed in the app. Generation refreshes stale sources first, then renders /print to PDF via headless Chrome.


Architecture

Data is portable: Settings → Export/Import JSON (/api/export, /api/import).

See BRIEF.md and PLAN.md for the original intent and build plan.