Project · ★★ · Intermediate
Habits
A habit tracker that lives on your phone and your laptop and keeps them in step. Tick off reading, practice, water, whatever you are working on. Watch the streaks grow and the month fill in. It works with no signal, and catches up when you are back online. There is no account to make: you type a short sync code on your second device and your habits are there. This is your first app with a real database behind it.
You need
- Set up your computer, so you have a terminal, git and Node.
- Claude Code or Codex, set up and logged in: Get Claude Code or Get Codex.
- How to work with an agent. Read it once. It saves hours.
- Keep data, especially the parts on localStorage and D1.
- A free Cloudflare account (dash.cloudflare.com/sign-up). The free plan covers everything here.
- A free GitHub account, if you want to publish it: Save it on GitHub.
What you'll learn
- Offline first: write to the device, update the screen at once, sync later.
- A real SQL database (D1): tables, primary keys and prepared statements.
- Sync and merge: what happens when two devices change the same thing, and the "last write wins" rule.
- Login-free identity: a random sync code, and why it has to be treated like a password.
- A Worker as your own small server, built and deployed with wrangler.
What this shows the agent can do
- Design a small database and the sync rules from a few plain sentences.
- Write a test page for the fiddly parts (streaks, merging) and run it before the screen depends on them.
- Guide you through creating a database on Cloudflare one command at a time, explaining each one.
The brief
Make a new folder for the project. Copy the brief below into a file called BRIEF.md in that folder, or download BRIEF.md. Open the agent in the folder and say:
Read BRIEF.md and start with milestone 1. Stop after each milestone so I can check it.
# Habits — a tracker with a real database (★★)
Build a habit tracker that works on my phone and my laptop and keeps them in step, with no login. I add habits (read 20 minutes, practise guitar, drink water), tick them each day, and see my streaks and a month grid. Everything works offline first, saved in the browser. A small Cloudflare Worker with a D1 database keeps a copy. To sync another device, I type a short sync code instead of making an account. I can export everything as a CSV file.
## Done looks like
- Add, rename, reorder and archive habits. Each habit has a name and a colour.
- Today's view: one big tick button per habit. A tap toggles it.
- A month grid per habit shows done days as filled squares. Arrows move between months.
- Current streak and best streak per habit. A streak counts back from today, or from yesterday if today is not ticked yet.
- The app works with Wi-Fi off. Ticks made offline sync when the connection comes back.
- A sync code like `K7PQ-M2XD-9R` appears in Settings. Typing it on a second device brings over every habit and tick within a few seconds. After that, changes on either device show up on the other after a reload.
- A status line says "Saved on this device" or "Synced 1 minute ago" or "Offline, will sync later".
- "Export CSV" downloads `habits.csv` with columns `habit,date,done`.
- Live at the address `npx wrangler deploy` prints. No console errors.
## Constraints
- One Cloudflare Worker with static assets: it serves the front end and the `/api/` routes. Use wrangler. Free plan only.
- Front end: plain HTML, CSS and JavaScript, no frameworks, no build step for the front end.
- Offline first: every change is written to localStorage first and the screen updates at once. Sync runs on load, two seconds after the last change, and on the `online` event.
- Sync code: 10 characters from an alphabet with no lookalikes (no 0, O, 1, I or L), made with `crypto.getRandomValues`, shown in groups. Treat it like a password: anyone with it can see and change the data. Say so in Settings.
- D1 tables: `habits(code, id, name, colour, sort, archived, updated_at)` and `checks(code, habit_id, day, done, updated_at)` with primary key `(code, habit_id, day)`. Days are local dates as `YYYY-MM-DD` text.
- Merge rule: last write wins per row by `updated_at`. Never delete rows: un-ticking sets `done = 0`, deleting a habit sets `archived = 1`, so the change syncs.
- API: `GET /api/sync/:code?since=<ms>` returns rows changed since then. `POST /api/sync/:code` takes changed rows and keeps each one only if it is newer than what is stored. The Worker checks the code format, caps a request at 500 rows and habit names at 60 characters, and uses prepared statements (never builds SQL from strings).
- No accounts, no email, no tracking, no cookies.
- Phone works: big tap targets, readable on a 360 px screen.
## Milestones
1. **Local tracker.** Add habits, tick today, all in localStorage. No server yet.
Check: add three habits, tick two, reload: everything is still there, and it works with Wi-Fi switched off.
2. **Month grid and streaks.** The month grid and a `streaks(days)` function with a small test page. Test cases: ticks on the last 5 days gives current 5; a gap yesterday with today ticked gives current 1; today not ticked but the 3 days before ticked gives current 3.
Check: the test page shows every case passing and the grid matches the ticks you made.
> Milestones 1 and 2 are a complete tracker for one device. Use it for a few days before you build the sync. You will know better what you want from it.
3. **A Worker on your computer.** Set up the Worker project with wrangler, serving the existing files from static assets, plus `GET /api/health` returning `ok`. Run it locally.
Check: the app opens from the local wrangler address and `/api/health` shows `ok`.
4. **D1 and the sync API.** Create the D1 database and the two tables with a migration file. Write the two sync routes with the merge rule.
Check: a small script posts two rows, then gets them back with `since=0`; posting an older copy of a row does not overwrite the newer one.
5. **Sync codes.** Make a code on first sync and show it in Settings. An "Enter a code" box on another device pulls everything for that code and switches to it.
Check: in a second browser, type the code: all habits and ticks appear.
6. **Offline first sync.** Queue changes made offline, send them when back online, show the status line.
Check: turn Wi-Fi off on the phone, tick a habit, turn it back on: within a few seconds the status says "Synced" and the laptop shows the tick after a reload.
7. **Export and go live.** "Export CSV" with a `Blob` download. Deploy with `npx wrangler deploy`.
Check: the live address works on your phone over mobile data, and the CSV opens in a spreadsheet with one row per tick.
## Stretch
- Install it to the phone home screen with a web app manifest and a service worker, so it opens offline from an icon.
- Weekly goals ("3 times a week") with their own kind of streak.
- A year view heatmap, one small square per day.
- Sync codes as four short words instead of characters, from a word list built into the app.
- A "rotate code" button that copies the data to a new code and deletes the old one.
## Leave out
- Accounts, email, passwords, social login.
- Push notifications from a server.
- Charts libraries, frameworks, analytics.
Start by reading WRITING.md and the guide chapter on data if you need storage.Milestones
The same milestones as the brief. Tick each one off when its check passes. Do one per sitting if you like. The dot in the list marks the point where you have something that works on its own. Everything after it makes it remember things or work for more than one person. Stop at the dot and use what you built before you go on.
- Local tracker. Add habits, tick today, all in localStorage. No server yet.
Check: add three habits, tick two, reload: everything is still there, and it works with Wi-Fi switched off.
- Month grid and streaks. The month grid and a
streaks(days)function with a small test page. Test cases: ticks on the last 5 days gives current 5; a gap yesterday with today ticked gives current 1; today not ticked but the 3 days before ticked gives current 3.Check: the test page shows every case passing and the grid matches the ticks you made.
- Milestones 1 and 2 are a complete tracker for one device. Use it for a few days before you build the sync. You will know better what you want from it.
- A Worker on your computer. Set up the Worker project with wrangler, serving the existing files from static assets, plus
GET /api/healthreturningok. Run it locally.Check: the app opens from the local wrangler address and
/api/healthshowsok. - D1 and the sync API. Create the D1 database and the two tables with a migration file. Write the two sync routes with the merge rule.
Check: a small script posts two rows, then gets them back with
since=0; posting an older copy of a row does not overwrite the newer one. - Sync codes. Make a code on first sync and show it in Settings. An "Enter a code" box on another device pulls everything for that code and switches to it.
Check: in a second browser, type the code: all habits and ticks appear.
- Offline first sync. Queue changes made offline, send them when back online, show the status line.
Check: turn Wi-Fi off on the phone, tick a habit, turn it back on: within a few seconds the status says "Synced" and the laptop shows the tick after a reload.
- Export and go live. "Export CSV" with a
Blobdownload. Deploy withnpx wrangler deploy.Check: the live address works on your phone over mobile data, and the CSV opens in a spreadsheet with one row per tick.
Make it yours
Once it works, pick one of these and ask for it as a new milestone.
- Install it to the phone home screen with a web app manifest and a service worker, so it opens offline from an icon.
- Weekly goals ("3 times a week") with their own kind of streak.
- A year view heatmap, one small square per day.
- Sync codes as four short words instead of characters, from a word list built into the app.
- A "rotate code" button that copies the data to a new code and deletes the old one.
If it goes wrong
Ticks made on the phone do not show up on the laptop.
Usually the sync is sending rows but the "since" time is wrong, or the device is on a different code. Say this to the agent:
Ticks from my phone do not reach my laptop. Add a sync log panel that shows the code, the last since time and how many rows went up and came back, then find where they get lost.
Un-ticking a day on one device brings it back after a sync.
The un-tick was a delete, so the other device sent the old tick back. Say this to the agent:
Un-ticked days come back after sync. Never delete check rows: set done to 0 with a new updated_at, and keep only newer rows on the server.
Streak shows 0 in the morning even though you did it every day.
The streak counts from today, and today is not ticked yet. Say this to the agent:
My streak resets every morning. If today is not ticked, count back from yesterday, and add that case to the streak tests.