Skip to content
appsbynate
← productivity

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.json map 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.