Build better habits, one day at a time.
HabitFlow is a personal habit tracking and journaling web application designed to help people build consistent routines, track progress over time, and reflect on their growth. Live at tryhabitflow.com.
- Habit tracking with daily completion, streaks, and progress visualization
- Customizable habits with frequency, time slots, weekly goals, and color coding
- Push notifications via Firebase Cloud Messaging for habit reminders
- Pomodoro timer for focused work sessions linked to habits
- Journal for daily reflections and notes
- Milestones that celebrate streak achievements automatically
- Dashboard with state-aware "up next" card, today's habits, and weekly heatmap
- Calendar view with monthly heatmap, day-detail panel, and statistics
- Analytics page with completion charts, streak summaries, and habit comparisons
- Timeline of habit history and milestone unlocks
- Secure authentication via Clerk (email + Google OAuth)
- Data export — download all your data as JSON anytime
- Account deletion — full data removal across Convex and Clerk
- Privacy Policy and Terms of Service built into the app
- Production-ready monitoring via Sentry for error tracking
| Layer | Technology |
|---|---|
| Frontend | Next.js 16 (App Router), React 19, TypeScript |
| Styling | Tailwind CSS, shadcn/ui components |
| Backend | Convex (database + serverless functions + real-time sync) |
| Auth | Clerk (with custom JWT integration to Convex) |
| Notifications | Firebase Cloud Messaging |
| Monitoring | Sentry (error tracking + session replay) |
| Hosting | Vercel (frontend), Convex Cloud (backend) |
| Domain & DNS | Cloudflare |
| Testing | Playwright (e2e), 33+ tests covering auth, habits, calendar, journal, pomodoro |
┌─────────────────────┐
│ Next.js Client │ React UI, server components, MDX legal pages
│ (Vercel) │
└──────────┬──────────┘
│ WebSocket (real-time)
▼
┌─────────────────────┐
│ Convex Backend │ Serverless functions, document DB, real-time sync
│ (Convex Cloud) │
└──────────┬──────────┘
│ JWT validation
▼
┌─────────────────────┐
│ Clerk Auth │ User management, JWT issuance, OAuth providers
└─────────────────────┘
┌─────────────────────┐
│ Firebase FCM │ Push notifications (called from Convex cron)
└─────────────────────┘
┌─────────────────────┐
│ Sentry │ Error tracking + session replay
└─────────────────────┘
- User clicks the completion button in the UI
- React component calls a Convex mutation via WebSocket
- Convex validates the user's Clerk JWT
- Mutation writes a row to
habitCompletionsand updates streak inuserMilestones - Convex pushes the update to all connected clients in real-time
- UI updates instantly without a full page reload
Convex tables (defined in convex/schema.ts):
users— Clerk user mapping with profile datahabits— habit definitions (title, frequency, color, time slots)habitCompletions— completion records per habit per dateuserMilestones— streak achievements and best streaksdailyCheckIns— daily reflection promptspomodoroSessions— pomodoro timer records linked to habitsjournalEntries— daily journal textpushTokens— FCM device tokensreminderLog— audit log for push notifications (idempotency)
All tables are scoped per-user via indexes (by_user, by_user_date, etc.) — data isolation is enforced at the query layer, not just in application logic.
- Node.js 20+
- npm or pnpm
- A Clerk account (free tier)
- A Convex account (free tier)
- A Firebase project (for push notifications, optional for basic dev)
# Clone and install
git clone https://github.com/<your-username>/habit-tracker.git
cd habit-tracker
npm install
# Set up Convex (creates dev deployment)
npx convex dev
# Set up environment variables
cp .env.local.example .env.local
# Fill in your Clerk and Convex keys
# Run the dev server
npm run devOpen http://localhost:3000.
# Convex
NEXT_PUBLIC_CONVEX_URL=
# Clerk
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
# Sentry (optional in dev)
NEXT_PUBLIC_SENTRY_DSN=Server-side env vars (set via npx convex env set):
CLERK_JWT_ISSUER_DOMAIN= # Clerk Frontend API URL
FIREBASE_PROJECT_ID=
FIREBASE_CLIENT_EMAIL=
FIREBASE_PRIVATE_KEY=Playwright e2e suite covers auth flows, habit lifecycle, calendar, journal, pomodoro, and account management.
# Run all e2e tests
npm run test:e2e
# Run a specific spec
npx playwright test e2e/habit-lifecycle.spec.ts
# Open the Playwright UI
npx playwright test --uiTests use a dedicated Clerk test user and real Convex dev deployment. Test data is auto-cleaned between runs via a dev-only cleanup endpoint.
- Frontend auto-deploys to Vercel from the
mainbranch - Backend deploys via
npx convex deploy(one-time push to production Convex) - Custom domain routed through Cloudflare DNS to Vercel
# Deploy Convex production functions
npx convex deploy
# Vercel auto-deploys on `main` push
git push origin mainhabit-tracker/
├── app/ # Next.js App Router
│ ├── (auth)/ # Sign-in/sign-up pages
│ ├── (dashboard)/ # Authenticated app pages
│ ├── legal/ # Public privacy/terms pages
│ └── api/ # Server routes (FCM, dev cleanup)
├── components/ # React components
│ ├── dashboard/ # Dashboard UI (up-next, today's habits)
│ ├── calendar/ # Calendar grid, day panel, stats
│ ├── habits/ # Habit forms, list, completion UI
│ ├── legal/ # Legal page renderer (MDX)
│ ├── layout/ # App shell, footer, nav
│ └── ui/ # shadcn/ui primitives
├── convex/ # Convex backend
│ ├── schema.ts # Database schema
│ ├── habits.ts # Habit mutations and queries
│ ├── notifications.ts # FCM cron logic
│ ├── deleteAccount.ts # Account deletion flow
│ ├── export.ts # Data export HTTP action
│ └── auth.config.ts # Clerk JWT configuration
├── content/legal/ # Privacy/terms MDX content
├── e2e/ # Playwright tests
├── lib/ # Shared utilities
└── public/ # Static assets
HabitFlow is a Progressive Web App. Users can install it to their home screen for a native-like experience.
Android (Chrome):
- Open tryhabitflow.com in Chrome
- Tap the install banner, or Menu → Install app
iOS (Safari):
- Open tryhabitflow.com in Safari
- Tap Share → Add to Home Screen
Features:
- Offline support — cached pages load without internet via service worker (
public/sw.js) - Home screen icon (generate real icons with
npx pwa-asset-generator public/logo.svg public/icons) - Fullscreen standalone mode (no browser chrome)
- iOS splash screens — placeholder paths in
<head>; generate images withnpx pwa-asset-generatoror appsco.pe/developer/splash-screens
Lighthouse PWA checklist:
- Web app manifest (
public/manifest.json) - Service worker with offline fallback (
public/sw.js) -
theme-colormeta tag -
apple-mobile-web-app-capablemeta tag - Real 192×192 and 512×512 PNG icons (placeholders in
public/icons/) - Real iOS splash screen images (placeholders in
public/splash/)
- AI habit coach with personalized insights based on user data
- Stripe billing for Pro tier (unlimited habits, AI features)
- Capacitor wrapper for App Store and Play Store presence
This project is currently private. Licensing terms TBD.
Built by Arun Kumar Kulkarni — arunkulkarni2000@gmail.com
For questions about HabitFlow as a product, see the Privacy Policy and Terms of Service.
This is a Next.js project bootstrapped with create-next-app.
First, run the development server:
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun devOpen http://localhost:3000 with your browser to see the result.
You can start editing the page by modifying app/page.tsx. The page auto-updates as you edit the file.
This project uses next/font to automatically optimize and load Geist, a new font family for Vercel.
To learn more about Next.js, take a look at the following resources:
- Next.js Documentation - learn about Next.js features and API.
- Learn Next.js - an interactive Next.js tutorial.
You can check out the Next.js GitHub repository - your feedback and contributions are welcome!
CI not yet configured — run
npm run test:e2elocally before pushing changes that touch core flows (auth, habit CRUD, navigation, pomodoro, journal).
- Clerk test instance — create a dedicated test user in your Clerk test environment (never the production instance).
- Environment variables — copy
.env.localand add:
# Clerk test instance keys (use sk_test_… / pk_test_…)
CLERK_SECRET_KEY=sk_test_...
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
# Dedicated e2e test user (must exist in your Clerk test instance)
# No password needed — Clerk Backend API creates a sign-in token from the email alone
E2E_CLERK_USER_EMAIL=testuser@gmail.com
# Convex dev deployment URL
NEXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud# Run the full suite (headless, reuses existing dev server if running)
npm run test:e2e
# Interactive UI mode — great for debugging, step-through, timeline view
npm run test:e2e:ui
# Headed mode — watch Chromium execute the tests
npm run test:e2e:headed| File | Hyperedge protected | Critical god nodes |
|---|---|---|
e2e/auth.spec.ts |
Authentication flow | DashboardPage, Clerk |
e2e/habit-lifecycle.spec.ts |
Habit CRUD Flow + Optimistic completion loop | HabitCard, useHabits, useOptimisticCompletion |
e2e/navigation.spec.ts |
Dashboard data layer, Global provider stack | DashboardPage, useHabits |
e2e/pomodoro.spec.ts |
Pomodoro UI system | usePomodoro |
e2e/journal.spec.ts |
Journal Entry module | upsertEntry, listEntries |
e2e/admin-reminders.spec.ts |
FCM cron hardening (idempotency, error isolation) | reminderLog, devTriggerReminders |
# View the HTML report after a run (opens in browser)
npx playwright show-report
# Re-run a single file with trace recording enabled
npx playwright test e2e/habit-lifecycle.spec.ts --trace on
# Open a trace zip (shows screenshots + network + DOM snapshots)
npx playwright show-trace test-results/<test-name>/trace.zipTests that create habits prefix their names with E2E_. After each test,
cleanupTestHabits() calls /api/dev/cleanup-test-data which runs the
deleteTestHabits Convex mutation (hard-blocked in production) to bulk-delete
all E2E_ prefixed habits in under 100ms.
Set the env vars above as CI secrets and add a workflow step:
- run: npm run test:e2e
env:
PLAYWRIGHT_BASE_URL: http://localhost:3000
CI: true
# ... all vars listed in playwright.config.ts headerAccessible from Settings → Danger Zone (/settings).
A single JSON file (habitflow-export-YYYY-MM-DD.json) with schema version 1:
{
"_exportVersion": 1,
"_exportedAt": "<ISO timestamp>",
"_userId": "<Clerk user ID>",
"data": {
"user": { ... },
"habits": [ ... ],
"habitCompletions": [ ... ],
"userMilestones": [ ... ],
"dailyCheckIns": [ ... ],
"pomodoroSessions": [ ... ],
"pushTokens": [ ... ],
"journalEntries": [ ... ],
"reminderLog": [ ... ]
}
}The export is generated on demand by the Convex HTTP action GET /export-user-data
(registered in convex/http.ts). No file is stored — the response streams directly
to the browser.
Account deletion is permanent and immediate. There is no recovery window. The following are hard-deleted in this order:
reminderLogrowspushTokensrowspomodoroSessionsrowsdailyCheckInsrowsjournalEntriesrowsuserMilestonesrowshabitCompletionsrowshabitsrows- Convex
_storageblob (profile image, if one was uploaded) usersrow- Clerk user record (via Clerk Admin API)
If the Clerk user deletion fails (network error, API outage), the Convex data is already gone. The error is logged to Sentry but not surfaced to the user. Their next sign-in attempt would create a fresh Convex user row.
Account deletion calls the Clerk Admin REST API. Set CLERK_SECRET_KEY in
Convex's environment (separate from .env.local):
npx convex env set CLERK_SECRET_KEY sk_test_...The destructive deletion test is skipped by default. To run it manually with an ephemeral Clerk test user:
RUN_DESTRUCTIVE_TESTS=1 npm run test:e2e -- e2e/account-deletion-destructive.spec.tsSee e2e/account-deletion-destructive.spec.ts for full prerequisites and setup.
The habit reminder cron (sendHabitReminders, fires every minute) is hardened
with idempotency, error isolation, and a dev-only manual trigger.
Navigate to http://localhost:3000/admin/reminders in development. Two independent guards protect this page:
- Next.js page returns 404 when
NODE_ENV === "production"— the page never renders in a production build. - Convex actions (
devTriggerReminders,triggerRemindersNow) throw unlessALLOW_DEV_TRIGGERS === "true"is set in the Convex deployment's env vars. Convex always runs withNODE_ENV=productioninternally, so the Next.js env is not a reliable gate at the Convex layer — this explicit flag is.
Three buttons are available:
| Button | What it does |
|---|---|
| Dry Run | Walks through the scan logic and logs what would be sent, but makes no FCM calls and writes no reminderLog entries. |
| Mock Send | Skips FCM entirely and treats every send as successful. Does write reminderLog entries — use this to test idempotency without real Firebase credentials. |
| Real Send | Calls FCM for real. Requires FIREBASE_PROJECT_ID, FIREBASE_CLIENT_EMAIL, and FIREBASE_PRIVATE_KEY to be set in Convex env vars (see below). |
The bottom of the page shows the last 20 reminderLog entries so you can
confirm what fired and what was skipped.
- Click Mock Send once — entries appear in the log table.
- Click Mock Send again immediately — the row count must not increase.
The idempotency check (
wasReminderSent) finds the existingsent/stale_tokenrow inreminderLogand skips the re-send.
All of these must be set in Convex (not .env.local) because Convex actions
run in an isolated runtime that only sees Convex env vars.
Enable the manual trigger in dev:
npx convex env set ALLOW_DEV_TRIGGERS trueDo not set
ALLOW_DEV_TRIGGERSin your production Convex deployment. Even if the flag were accidentally set, the Next.js page additionally hides itself whenNODE_ENV=production, so the UI would never expose the buttons.
Firebase credentials (required for Real Send and the production cron):
npx convex env set FIREBASE_PROJECT_ID your-project-id
npx convex env set FIREBASE_CLIENT_EMAIL service-account@your-project.iam.gserviceaccount.com
npx convex env set FIREBASE_PRIVATE_KEY "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"The private key must use literal \n escape sequences (not actual newlines)
when stored via npx convex env set. The cron unescapes them at runtime.
| Field | Purpose |
|---|---|
habitId + date + timeSlot + pushTokenId |
Idempotency key (index: by_habit_date_slot_token) |
outcome |
sent / stale_token / error — only sent and stale_token block re-sends |
sentAt |
Timestamp for the admin UI |
Rows are cleaned up automatically at 3 AM UTC daily (7-day retention) via
the cleanup-reminder-logs cron registered in convex/crons.ts.
The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.
Check out our Next.js deployment documentation for more details.