Example app spec: a habit tracker

THE WHOLE IDEA WE TYPED

“A habit tracker with daily streaks”

Generated by SpecWise with the Balanced model (3 credits) · 2,514 words · unedited

Want one for your idea?

Describe your idea in a sentence and get a spec like this in about a minute.

Generate my spec

7-day free trial · no card needed

Streak Habits - Product Requirements Document

1) Summary

Streak Habits is a phone-first web app for one person who wants to build daily habits. The user creates up to 12 habits, taps once per habit each day to mark it done, and sees a current streak (consecutive local calendar days completed) and a best streak beside each habit. All data lives in the browser on the user's own device; there are no accounts and no server.

  • Assumption: Storage is local only (IndexedDB, single user, single device). Clearing browser data erases habits; there is no sync or export.
  • Assumption: "Daily streak" means the number of consecutive local calendar days, ending today or yesterday, on which the habit was completed, not 24-hour windows from the last tap.
  • Assumption: A day runs midnight to midnight in the device's local time zone; dates are stored as YYYY-MM-DD strings.
  • Assumption: Caps: name 1-40 characters, at most 12 active habits, completions stored for the last 730 days per habit.
  • Assumption: Core formula: the streak counts back from today if today is done, otherwise from yesterday; see 4 for pseudo-code.
  • Assumption: Today is never a broken day. A habit not yet done today keeps its streak until midnight passes without a tap.
  • Assumption: Platform is a browser on phone and desktop, installable as a PWA so it opens like an app and works offline.
  • Assumption: Nothing is automatic: no reminders or notifications, no backfilling the future. Past days within 7 days can be corrected by hand.

2) Tech Stack

  • Framework → React 18 + Vite → fast static build, no server needed
  • Language → TypeScript → typed habit and completion records
  • Storage → IndexedDB via idb-keyval → simple persistent key-value store
  • Auth → None → single user, local data
  • Styling → Tailwind CSS → quick mobile-first layout
  • Dates → date-fns → formatting and local calendar math
  • PWA → vite-plugin-pwa → installable and offline
  • Deployment → Netlify static hosting → one route, no fallback rule needed

3) Users & Screens

Single user on one device; no accounts.

Screens:

ScreenReached fromShowsEmpty / loading / errorBack / close goes to
TodayApp launchDate heading, progress line, habit rows with check button, streak, bestEmpty: No habits yet. Add your first one. with Add habit button. Loading: blank surface under 300 ms, no spinner. Error: see 6.1None (home)
Add/Edit Habit sheet+ Add habit button, row EditName field, color swatches, Save, Delete (edit only)Validation errors inline, see 6.1Today
Habit DetailTap habit nameStreaks, 30-day grid, 7-day correction rowEmpty grid cells for days before creation: mutedToday
Delete confirm modalDelete in edit sheetWarning and two buttonsNoneEdit sheet

Navigation: single screen app; Today is home, and the others open as sheets or a pushed detail view with a ← Back button.

4) Data Model & Logic

Habit

  • id: string (UUID, example "a1f3…")
  • name: string (1-40 chars, trimmed, example "Read 20 pages")
  • color: string (hex from palette, default #2563EB)
  • createdOn: string (YYYY-MM-DD, local date of creation)
  • order: number (0-based position, default append)

Completion

  • habitId: string
  • date: string (YYYY-MM-DD)
  • Stored as a set of date strings per habit under key done:{habitId}.

Uniqueness, sort and retention: one completion per habit per date; habits sort by order ascending; completions older than 730 days are pruned on app open.

Logic:

today = localDate(now)
isDone(h, d) = done[h].has(d)

currentStreak(h):
  start = isDone(h, today) ? today : addDays(today, -1)
  n = 0
  d = start
  while d >= h.createdOn and isDone(h, d):
    n += 1; d = addDays(d, -1)
  return n

bestStreak(h):
  walk sorted completion dates; run = 1 while
  daysBetween(prev, cur) == 1, else run resets to 1;
  return max(run) (0 if no completions)

daysBetween(a, b) = round((Date.UTC(b.y,b.m,b.d) − Date.UTC(a.y,a.m,a.d)) / 86400000)

Worked example: Habit "Read" created 2025-03-01. Completions: Mar 1, 2, 3, 4, 6, 7. Today is Mar 7 (done today).

  • currentStreak: start Mar 7 done, Mar 6 done, Mar 5 not done → stop. Streak = 2.
  • bestStreak: Mar 1-4 run is 4, then Mar 6-7 run is 2 → best 4.
  • Midnight crossing: at 23:59 on Mar 7 the habit "Meditate" has completions Mar 5, 6 only and today not done. currentStreak starts at yesterday (Mar 6): Mar 6 done, Mar 5 done, Mar 4 not → 2, still alive. At 00:01 on Mar 8 with no tap on Mar 7, start = Mar 7, not done → 0: the streak broke at midnight.
  • Third record, "Stretch": completions Mar 7 only, created Mar 7 → current 1, best 1.

Status transitions: each habit's day state:

FromEventToSide effect
pending (today, not done)Tap checkdoneAdd date, recompute streaks, pulse animation
doneTap check againpendingRemove date, recompute (undo)
pendingMidnight passesmissedNone stored; streak recomputes to 0
missed (past day, within 7 days)Tap in correction rowdoneAdd date, recompute
done (past day, within 7 days)Tap in correction rowmissedRemove date, recompute

missed older than 7 days is terminal (read-only).

Edits and edge cases: renaming never touches completions. Deleting a habit removes its completions. Editing a past day recomputes both streaks immediately. A habit created today starts at 0 with today pending. Device clock moved backward: dates after today are ignored in streak math. At 12 habits the Add habit button is disabled.

5) External Services

None. All data is local and no third-party API is called.

6) Core Features

6.1 Add and Edit Habits

User Flow:

  1. On Today, user taps + Add habit; the sheet opens with the heading New habit.
  2. User types a name and taps one of 8 color swatches (default first swatch).
  3. User taps Save; the habit is stored with createdOn = today and appears at the bottom of the list.
  4. To edit, user taps Edit on a row; the sheet opens with heading Edit habit and the Delete button.
  5. Delete opens the confirm modal: Delete "{name}" and its history? This cannot be undone. with Cancel and Delete.

UI Copy & Colors:

  • Add button: + Add habit, text #FFFFFF on #2563EB
  • Name placeholder: e.g. Read 20 pages, text #64748B
  • Save: Save, #FFFFFF on #2563EB; Delete: Delete, #FFFFFF on #DC2626

Error & Empty States:

  • Name empty → Give your habit a name. Recovery: type a name.
  • Name over 40 chars → input stops accepting characters and counter shows 40/40.
  • Duplicate name (case-insensitive) → You already track "{name}". Recovery: change the name.
  • 12 habits exist → button disabled, helper You can track up to 12 habits. Recovery: delete one.
  • Storage write fails → banner Couldn't save. Check browser storage and try again. Recovery: retry Save.

Acceptance Checks:

  • ✓ Name Run → Save → row Run appears with streak 0 and best 0.
  • ✓ 41 typed characters → field holds 40.
  • ✓ Name run when Run exists → You already track "run".
  • ✓ Delete confirmed → habit and its completions disappear; Today shows one fewer row.

6.2 Mark Done Today

User Flow:

  1. Today shows each habit row: color dot, name, a 44×44 px circular check button, and 🔥 {n} current streak with Best {m} below.
  2. User taps the check button; it fills with the habit color and shows ✓.
  3. The streak number updates instantly; if it equals or passes best, best updates too.
  4. Tapping a filled button un-marks today (undo).
  5. The header progress line reads {done} of {total} done today; at all done it reads All done today. Nice work!

UI Copy & Colors:

  • Done button: ✓ #FFFFFF on habit color; pending: border #CBD5E1, fill #FFFFFF
  • Streak: 🔥 {n} text #0F172A; zero streak shows 🔥 0 in #64748B
  • Progress line: text #64748B; all-done text #16A34A

Error & Empty States:

  • Write fails → button reverts and toast Couldn't save that tap. Recovery: tap again.
  • No habits → empty state per section 3.

Acceptance Checks:

  • ✓ Streak 2 (Mar 6, 7 pending on Mar 8 → shown as 2 before tapping) → tap on Mar 8 → 3.
  • ✓ Tap then tap again on the same habit → returns to pending, streak back to prior value.
  • ✓ 3 habits, 2 done → 2 of 3 done today.
  • ✓ All 3 done → All done today. Nice work!

6.3 Habit Detail and Corrections

User Flow:

  1. User taps a habit name; Habit Detail opens with ← Back.
  2. The top shows Current streak and Best streak as large numbers.
  3. A 30-day grid (6 columns × 5 rows, oldest top-left, today bottom-right) fills done days with the habit color and missed days with #E2E8F0; days before createdOn are #F1F5F9 and not tappable.
  4. Below, the correction row Last 7 days shows 7 labelled day chips (Mon 3); tapping a chip toggles done/missed for that day.
  5. Totals line: {n} days completed since {createdOn formatted d MMM yyyy}.

UI Copy & Colors:

  • Labels: Current streak, Best streak, text #64748B; numbers #0F172A at 32 px
  • Done chip: #FFFFFF on habit color; missed chip: #0F172A on #E2E8F0

Error & Empty States:

  • New habit with no history → grid all muted, totals 0 days completed since {date}.
  • Tapping a day before createdOn → nothing happens; chip is dimmed with aria-disabled.

Acceptance Checks:

  • ✓ Completions Mar 1-4, 6, 7 viewed on Mar 7 → current 2, best 4.
  • ✓ Toggle Mar 5 to done → current 7, best 7.
  • ✓ Toggle Mar 5 back → current 2, best 4.
  • ✓ Habit created today → 6 grid cells before today are #F1F5F9.

6.4 Midnight Rollover and Offline Use

User Flow:

  1. On visibilitychange to visible and every 60 s while open, the app recomputes today.
  2. If the date changed, all check buttons reset to pending and streaks recompute (see 4).
  3. When offline, the app loads from the service worker cache and works unchanged.

UI Copy & Colors:

  • Date heading: EEEE, d MMMM (e.g. Saturday, 8 March), text #0F172A

Error & Empty States:

  • IndexedDB unavailable (private mode) → full-screen Your browser is blocking local storage, so habits can't be saved. Open this page in a normal window. Recovery: reopen outside private mode.

Acceptance Checks:

  • ✓ Open at 23:59 with all done, return at 00:01 → heading shows the new date and 0 of {n} done today.
  • ✓ Habit with Mar 6 done, nothing Mar 7: at 00:01 Mar 8 shows 🔥 0; at 23:00 on Mar 7 still shows 🔥 1.
  • ✓ Airplane mode after first load → app opens and taps still save.

7) UI/UX

Palette:

TokenHexUsed for
primary#2563EBButtons, default habit color
success#16A34AAll-done message
warning#F59E0BBanner background tint
danger#DC2626Delete
text#0F172AHeadings, numbers
muted#64748BCaptions, zero streak
surface#F8FAFCPage background
border#CBD5E1Pending check ring
missed#E2E8F0Missed grid cells

Habit swatches: #2563EB, #16A34A, #F59E0B, #DC2626, #9333EA, #0891B2, #DB2777, #475569.

Typography: Inter, 16 px base; page heading 24 px bold; streak and detail numbers 32 px bold; captions 13 px.

Layout: single column, mobile first, max width 480 px centred; the habit list is the hero, with the progress line pinned under the date heading.

Motion:

InteractionDurationEasing
Check fill150 msease-out
Streak number change200 msease-in-out
Sheet open/close250 msease-out
Toast in/out200 msease

Timestamp display: createdOn shown as d MMM yyyy; heading as EEEE, d MMMM; day chips as EEE d.

Accessibility: text contrast ≥ 4.5:1, tap targets ≥ 44×44 px, visible focus ring #2563EB 2 px, check buttons have aria-label="Mark {name} done" and aria-pressed.

8) Infrastructure Notes

Environment variables: None.

Client vs server: everything runs in the browser; there is no server code.

Platform limits:

LimitPlatforms affectedDesign answer
Browser storage can be cleared, and Safari may evict data of sites unused for 7 daysSafari iOS/macOSCall navigator.storage.persist() on first save; recommend installing to Home Screen in the empty state footer text Tip: add to Home Screen to keep your data safe.
No background timers while closedAllMidnight handled by recompute on open/visible, see 6.4
Private mode may block IndexedDBSafari, FirefoxBlocking message in 6.4

9) Out of Scope (V1)

  1. Reminders and push notifications - tempting, but they need a service worker push setup and a server.
  2. Weekly or custom schedules (e.g. 3x per week) - tempting, but streak logic changes entirely; V1 is daily only.
  3. Cloud sync and accounts - requires a backend and auth, against the local-only design.
  4. Statistics and charts - monthly trends and completion rates go beyond the 30-day grid.
  5. Streak freezes or skip days - changes the streak rule defined in 4.
  6. Data export and import - useful, but not needed for a first version.
  7. Dark mode and themes - extra palette work not required by the pitch.

10) Success Criteria

  • ✓ Adding a habit named Read shows a row with 🔥 0 and Best 0.
  • ✓ Tapping the check button fills it and updates the streak to 1 on a new habit.
  • ✓ Tapping the filled button again returns it to pending and streak to 0.
  • ✓ A habit with Mar 6 and Mar 7 done shows streak 2 on Mar 7 and Mar 8 (before a tap on Mar 8).
  • ✓ A missed full day resets the streak to 0 at the next midnight.
  • ✓ Best streak never decreases when a streak breaks.
  • ✓ A 13th habit cannot be added and You can track up to 12 habits. appears.
  • ✓ Deleting a habit asks for confirmation and removes its history.
  • ✓ Toggling a day within the last 7 days in Habit Detail recomputes current and best streaks immediately.
  • ✓ No console errors during adding a habit, marking it done, and reopening the app after a reload.

Spec your own app

Describe your idea in a sentence and get a spec like this in about a minute.

Generate my spec

7-day free trial · no card needed

More examples