Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

Harbour

A personal daily-tracking app in a James Clear–inspired editorial style: Newsreader serif + Geist sans, warm cream paper, hairline rules instead of cards, tally marks for habit streaks, accent color used as punctuation.

Four screens, fully persistent, phone-first (rendered inside an iOS device frame on desktop):

  • Today — the daily ritual. This week's One Thing (pickable from the Notion Home List), the evening check-in (energy 1–5, "what mattered today", optional gratitude + let-go prompts), an evidence log of small good things, morning/evening checklists, and a 7-day habit strip with tap-to-toggle for today.
  • Habits — the fuller surface: a 4-week heatmap per habit (today tappable), plus add / rename / note / delete. Deletes are soft, so history survives.
  • Metrics — the long view: a 30-day energy sparkline (points positioned by actual date, so gaps read as gaps), per-habit consistency (current streak / best streak / days out of 30), and completed One Thing history with a set-vs-done rate.
  • Home list — the Notion Home List grouped by section. Checking an item off writes back to the real Notion page; "focus →" promotes an item to this week's One Thing and jumps to Today.

Files

FilePurpose
index.tsHono HTTP val (entry point). HTML shell at /, server-compiled client JS at /*.js, all /api/* routes.
app.tsxApp shell: one-round-trip bootstrap fetch, loading/error states, device frame + Tweaks wiring.
harbour.tsxAll UI: theme tokens, atoms, sections, the four screens, and every data handler (optimistic + error-safe).
db/init.tsSQLite schema (idempotent CREATE TABLE IF NOT EXISTS, runs at cold start).
db/api.tsAll SQLite reads/writes, including the metrics read-models (streaks, energy history, One Thing history).
db/notion.tsNotion Home List: cached fetch of unchecked to-dos + check-off write-back.
ios-frame.tsxiOS device frame chrome (status bar, dynamic island, home indicator).
tweaks-panel.tsxFloating Tweaks UI: light/dark, device frame on/off, Editorial vs Classic type, four accent colors.

Architecture notes

  • No bundler, no client Babel. The .tsx client modules are transpiled server-side with @babel/standalone — once per isolate, cached in memory, invalidated automatically on deploy — and served as plain JS to production React UMD builds. First paint is fast even on a phone.
  • One-round-trip boot. GET /api/bootstrap?date=&weekStart=&days=28 assembles everything the Today screen needs (checklists + completions, logs, habits + 28 days of completions, One Thing, evidence) in a single response.
  • Local-date correctness. All dates are the client's local calendar date (YYYY-MM-DD), passed to the server, which anchors every range query and streak calculation on it. The app's day never flips early because a server somewhere is on UTC. Week starts Sunday.
  • Optimistic but honest. Every write goes through a shared api() helper that throws on non-2xx; failures surface as a transient banner instead of silently pretending the save worked.
  • Notion. db/notion.ts pages through the Home List page's blocks (section headings tracked across pages), returns unchecked to-dos with their block ids, and caches results for 60s per isolate. Check-off is a PATCH on the to-do block, which also invalidates the cache. Requires the NOTION_TOKEN env var: 👉 Add NOTION_TOKEN here: https://www.val.town/x/brenwildt/Harbour/environment-variables?key=NOTION_TOKEN

Data model (SQLite)

  • checklist_items(id, session, section, title, detail, sort_order)
  • checklist_completions(item_id, date, done) — PK (item_id, date)
  • daily_logs(date, session, energy, note, gratitude, let_go, saved_at) — PK (date, session)
  • habits(id, name, note, sort_order, active) — soft delete via active
  • habit_completions(habit_id, date, done) — PK (habit_id, date)
  • one_thing(id, title, category, week_start, created_at, completed, completed_at)
  • evidence(id, date, title, detail, logged_at, sort_order)

API

  • GET /api/bootstrap — everything Today needs, one call
  • GET/POST/PUT/DELETE /api/checklist/items[/:id], GET/PUT /api/checklist/completions[/:itemId]
  • GET/POST /api/logs
  • GET/POST/PUT/DELETE /api/habits[/:id]
  • GET /api/habit-completions/:habitId?days=&today=, PUT /api/habit-completions/:habitId/:date
  • GET /api/metrics?days=&today={days, energy, habits, oneThing}
  • GET/POST /api/one-thing, PUT /api/one-thing/:id/complete
  • GET/POST/PUT/DELETE /api/evidence[/:id]
  • GET /api/notion/home-list, POST /api/notion/home-list/:blockId/check

Range endpoints accept a today=YYYY-MM-DD anchor from the client; without it they fall back to server UTC.

Notes on the design

  • Type: Newsreader (editorial serif, headings + reflective copy) · Geist (sans, UI + body) · JetBrains Mono (times, day counts, energy 3/5)
  • Palette: warm cream paper, deep ink, single muted amber accent used as punctuation. Dark mode is a toggle away.
  • Voice: small caps section labels, italic serif for moments of reflection, tabular numerals for data
  • Treatment: check marks as crossing strokes, streaks as a row of small tally slashes (filled = done, hollow = missed), the "one thing" gets a quiet pull-quote treatment instead of a bordered card

The accent color is intentionally restrained — it appears in roughly one zone per screen (a section divider, the active energy segments, the filled streak strokes) and stays out of the way otherwise.