A modern, full-featured habit tracking and productivity application built with React, TypeScript, and Vite. Includes a multi-mode timer (stopwatch/countdown/intervals), achievements, AI insights, analytics, exports, social/messaging, integrations with Google/Notion/Slack/Spotify/Fit, and email + TOTP MFA authentication. Ships as a Vite web app, a Capacitor Android wrapper, and a static landing page. Backend is Supabase (Postgres + Edge Functions) with Cloudflare Turnstile bot protection.
We follow a simplified two-branch flow:
mainβ Production-ready code (protected)docs/*,feat/*,chore/*,fix/*β short-lived branches offmain, squash-merged via PR
π See CONTRIBUTING.md for the workflow details.
- βοΈ React 18 β Concurrent rendering
- π TypeScript (strict) β Type safety end-to-end
- β‘ Vite 6 β Fast HMR and builds
- π» Zustand 5 β Lightweight state with persistence (8 global stores + 8 domain stores)
- ποΈ Supabase β Postgres + Auth + Storage + Edge Functions (the only data layer)
- π React Router 7 β Declarative routing
- π React Hook Form β Performant, flexible forms
- β Zod 3 β TypeScript-first schema validation
- π¨ Tailwind CSS 3 β Utility-first, with dark mode
- π¬ Framer Motion β Animations
- π§± dnd-kit β Accessible drag-and-drop
- π Recharts β Charts and analytics
- π react-hot-toast β Notifications
- π html2canvas + jspdf β Premium exports
- π Supabase Auth β Email/password + JWT sessions
- π‘οΈ Custom TOTP MFA β In
src/lib/auth/mfa.ts - π€ Cloudflare Turnstile β Bot protection on auth gateway
- πͺ΅ Sentry 10 β Error monitoring (opt-in via
VITE_ENABLE_SENTRY)
- π§ͺ Vitest 4 β Unit + component tests (jsdom)
- π§© React Testing Library β Component tests
- βΏ jest-axe β Accessibility assertions
- π Playwright β E2E tests (Firefox)
- π ESLint 9 β Flat config, with
@ts-nocheckdebt awareness - β¨ Prettier 3 β Formatter with Tailwind plugin
- πΆ Husky 9 β Pre-commit hook (installed via
npm run prepare)
- π date-fns β Date utilities
- π§ Custom Hooks β
useDebounce,useLocalStorage, etc. - π Path Aliases β
@/maps tosrc/
Feature-sliced architecture β domain code lives in src/features/<domain>/,
cross-domain primitives in src/shared/ and the classic shared layers. See
docs/ARCHITECTURE.md for the full architecture guide,
dependency rules, and testing policy.
.
βββ src/
β βββ features/ # Domain features (timer, social, tasks, habits,
β β # categories, today, integrations, auth,
β β # accessibility, onboarding)
β βββ shared/ # Cross-domain UI primitives + layout shell
β βββ lib/ # Framework-free logic (auth, storage, security, env, errors)
β βββ store/ # Global persisted Zustand stores
β βββ pages/ # Routing composition
β βββ hooks/ utils/ schemas/ types/ constants/
β βββ __tests__/ # Cross-cutting tests only
β βββ App.tsx # Main App component
β βββ main.tsx # Application entry point
βββ e2e/ # Playwright specs (Firefox)
βββ scripts/ # Codegen tooling (leagues β constants)
βββ public/ # Static assets
βββ index.html # HTML template
βββ vite.config.ts # Vite configuration
- Node.js v20+
- npm v10+
- A Supabase project (for the backend)
- Cloudflare Turnstile site key (for auth bot protection)
# Install dependencies
npm ci
# Copy env template and fill in your values
cp .env.example .env
# Start the dev server (validates env on boot)
npm run dev
# Typecheck + production build
npm run build
# Preview the production build locally
npm run preview| Command | What it does |
|---|---|
npm run dev |
Vite dev server (port 3000) |
npm run build |
Typecheck then Vite build |
npm run typecheck |
tsc --noEmit over the strict tsconfig |
npm run lint |
ESLint flat config |
npm run lint:strict |
ESLint with --max-warnings 0 (gated on debt burn-down) |
npm run lint:debt |
ESLint in strict mode over @ts-nocheck files |
npm run lint:fix |
ESLint with --fix |
npm run format |
Prettier write |
npm run format:check |
Prettier check (CI gate) |
npm test |
Vitest watch |
npm run test:coverage |
Vitest with v8 coverage |
npm run test:e2e |
Playwright (Firefox) |
npm run audit |
npm audit --audit-level=high |
All VITE_* variables are validated at module load time by
src/lib/env.ts. Required vars throw in production;
optional vars warn and fall through.
| Var | Required | Notes |
|---|---|---|
VITE_SUPABASE_URL |
yes | Supabase project URL |
VITE_SUPABASE_ANON_KEY |
yes | Supabase anon JWT |
VITE_API_URL |
no | Backend base URL (unused in current build) |
VITE_APP_NAME |
no | Display name (default: HabitFlow) |
VITE_APP_VERSION |
no | Display version (default: unknown) |
VITE_SENTRY_DSN |
no | Sentry DSN for error reporting |
VITE_ENABLE_SENTRY |
no | true to enable Sentry init |
VITE_ENABLE_ANALYTICS |
no | true to enable analytics |
VITE_TURNSTILE_SITE_KEY |
no | Cloudflare Turnstile site key |
VITE_TURNSTILE_DISABLED |
no | true to bypass Turnstile in dev |
VITE_GOOGLE_CLIENT_ID / _SECRET / _REDIRECT_URI |
no | Google integration |
VITE_NOTION_CLIENT_ID / _SECRET / _REDIRECT_URI |
no | Notion integration |
VITE_SLACK_CLIENT_ID / _SECRET / _REDIRECT_URI |
no | Slack integration |
VITE_SPOTIFY_CLIENT_ID / _SECRET / _REDIRECT_URI |
no | Spotify integration |
VITE_GOOGLE_FIT_REDIRECT_URI |
no | Google Fit integration |
In production, missing required vars throw on import β the build fails fast. In development, missing required vars log to the console and fall back to placeholders so the dev server can boot.
Imports use the @/ alias (mapped to src/ in both tsconfig and vite.config.ts):
// Instead of
import Component from '../../../components/Component'
// You can use
import Component from '@/components/Component'npm test # Vitest in watch mode
npm run test:coverage # Vitest with v8 coverage report
npm run test:e2e # Playwright (Firefox; install via `npx playwright install firefox`)Tests are colocated with their subjects in __tests__/ directories. Two
cross-cutting suites live in src/__tests__/. See
docs/ARCHITECTURE.md for the testing
policy and known-debt caveats.
npm run buildThe build runs tsc --noEmit first, then Vite. Output is in dist/,
ready to be deployed to any static hosting service.
The app is configured for dual-target deployment β both kept intentionally:
- Vercel β
vercel.jsonat the repo root - Netlify β
netlify.tomlat the repo root
Both build the SPA statically; the Supabase backend is a separate concern managed in the Supabase dashboard.
See CONTRIBUTING.md for setup, branching strategy, commit conventions, and the PR process.
MIT