Habits
A minimal daily habit tracker — seven dots per habit, a streak that counts the way you'd count it, and nothing that leaves the browser.
- Section
- productivity
- Year
- 2026
- Role
- Solo project
- Stack
- React, TypeScript, Vite, localStorage
Summary
A daily habit tracker that’s one screen. Each habit is a row of seven dots — the
last week, today on the right. Tap a dot to toggle the day. Everything lives in
localStorage with JSON export and import, and nothing leaves the browser.
Story
Two decisions about what the dots mean did most of the work. Any of the seven
can be toggled, so a forgotten day is fixable the next morning, but days before
the habit existed stay locked — history is correctable, not backfillable. And a
streak counts the unbroken run ending today, falling back to yesterday if today
isn’t marked yet; otherwise a real streak reads 0 all morning. The bug that
cost the most was toISOString(): it returns UTC, so marking a habit at 9pm
filed it under tomorrow and broke a streak I hadn’t missed. Day keys come from
local date components now. The other afternoon went to hosting it at
/habits — setting Vite’s base only changes the asset URLs the build
prints, so the deploy advertised a prefix it didn’t answer on and the bare
path 404’d on production only.
Lessons
- A base path is not a route. Four lines of
vercel.jsonmap the prefix back to root; it became row three of the hosting runbook. - Date handling is calendar days or instants, never both —
toISOString()is an instant wearing a day’s clothes. - Local-only storage needs a versioned envelope that degrades instead of throwing. An error screen from one malformed key loses the user permanently.