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.
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.
| File | Purpose |
|---|---|
index.ts | Hono HTTP val (entry point). HTML shell at /, server-compiled client JS at /*.js, all /api/* routes. |
app.tsx | App shell: one-round-trip bootstrap fetch, loading/error states, device frame + Tweaks wiring. |
harbour.tsx | All UI: theme tokens, atoms, sections, the four screens, and every data handler (optimistic + error-safe). |
db/init.ts | SQLite schema (idempotent CREATE TABLE IF NOT EXISTS, runs at cold start). |
db/api.ts | All SQLite reads/writes, including the metrics read-models (streaks, energy history, One Thing history). |
db/notion.ts | Notion Home List: cached fetch of unchecked to-dos + check-off write-back. |
ios-frame.tsx | iOS device frame chrome (status bar, dynamic island, home indicator). |
tweaks-panel.tsx | Floating Tweaks UI: light/dark, device frame on/off, Editorial vs Classic type, four accent colors. |
- No bundler, no client Babel. The
.tsxclient 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=28assembles 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.tspages 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 aPATCHon the to-do block, which also invalidates the cache. Requires theNOTION_TOKENenv var: 👉 Add NOTION_TOKEN here: https://www.val.town/x/brenwildt/Harbour/environment-variables?key=NOTION_TOKEN
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 viaactivehabit_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)
GET /api/bootstrap— everything Today needs, one callGET/POST/PUT/DELETE /api/checklist/items[/:id],GET/PUT /api/checklist/completions[/:itemId]GET/POST /api/logsGET/POST/PUT/DELETE /api/habits[/:id]GET /api/habit-completions/:habitId?days=&today=,PUT /api/habit-completions/:habitId/:dateGET /api/metrics?days=&today=→{days, energy, habits, oneThing}GET/POST /api/one-thing,PUT /api/one-thing/:id/completeGET/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.
- 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.