← work

cadence

2026·activerepo ↗

Cadence — the rhythm you keep. Which is the entire point of a habit.

What even is this?

Cadence is a personal habit tracker: a .NET REST API that owns your habits and completions, and a CLI called cx that lets you log the whole thing without leaving the terminal. cx done gym and you're back to work. cx week gym prints an 8-week heatmap right there in your shell.

No app to open, no dashboard to load, no streak flexing at you. Just a quiet record of whether I kept the beat today.

Why I built it

Habit apps are a crowded market, and I tried a lot of them. Two things kept killing it:

  1. None of them matched my vibe. They wanted me to live in their app, tap through their onboarding, and care about their idea of a "streak."
  2. The one feature I actually needed was always behind a paywall. Want more than three habits? Pay. Want the heatmap? Pay. Want to export your own data? Pay.

So I built the version I wanted: it lives in the terminal I already have open all day, it does exactly the tracking I need, and the only thing behind a paywall is my own laziness.

How it works

There are two halves, and the split is the whole design:

  1. The API is the source of truth — habits, daily completions, and stats all live in Postgres behind it.
  2. The cx CLI is just a thin client. It sends what you did and renders what comes back. Every command — today, done, week, habit add — is one HTTP call in a nice coat.

Inside the API I went with vertical slices: each feature is its own file — CreateHabit, GetTodayHabits, PutCompletion — instead of the cathedral of Domain/Application/Infrastructure layers I've built before. For a solo tool, "the whole feature is one file I can hold in my head" beat the ceremony.

What I learned

Timezones are the entire ballgame for a habit tracker. The hardest question in the whole app is deceptively simple: did I do it today? — because "today" is a lie without a timezone. A completion at 11pm in Jakarta is a different day than the server's UTC clock thinks it is. So every request carries an X-Timezone header, and a dedicated UserClock decides what "today" means for you, not for my VPS. Get this wrong and the app quietly lies about your streak, which is worse than not tracking at all.

I tested exactly one thing, and it was the right thing. I'm not religious about unit tests on a personal project — but the streak math got tests, no exceptions. A tracker that miscounts your streak isn't a minor bug; it undermines the one number you actually trust it for. So StreakCalculator and StatsRecalculator are the two pieces with tests around them, and everything else earns its trust in production.

A CLI was the correct interface, not a lazy one. I could've built a web front-end. But the friction of opening the app is the exact thing that kills habit tracking for me. cx done is muscle memory, sub-second, and never once nagged me to upgrade. Matching a tool to how you already work beats matching it to how tools are "supposed" to look.

Stack

  • Backend — C#, .NET, minimal APIs, vertical-slice features, API-key auth
  • CLI — a .NET console client (cx) with a terminal heatmap renderer
  • Data — PostgreSQL via EF Core, migrations applied on startup
  • Infra — Docker Compose behind Caddy