Multi-Tenant Two-Player Habit Parity, Wearable Telemetry & Accountability Engine
Live Production App: https://do-it-plum-seven.vercel.app
Do It is a public, multi-tenant two-player habit competition and accountability platform engineered for high-frequency daily execution. Originating as a calibrated two-player parity engine, the platform enables any pair of users to register accounts, pair via a cryptographic one-use invitation link, and compete in an isolated head-to-head duel.
Unlike conventional habit trackers that treat points as spendable currency or require identical routines, Do It enforces asymmetric habit parity:
- Calibrated Equivalent Habits: Players select from a curated onboarding catalog or define custom habits across 10 domain categories (Foundation, Gym, Cardio, Intellect, Mind, Deep Work, Skills, Nutrition, Environment, Language) calibrated to a balanced 240 Daily Par ceiling and flexible weekly session targets (e.g., Basketball 3x/wk vs. Running 3x/wk, Swedish vs. Polish).
- Decoupled Tiered Leaderboards & Lifetime Karma: Competitive tallies reset automatically on Weekly (Sunday 00:00), Monthly, and Yearly boundaries to drive recurring head-to-head stakes, while Lifetime Karma accumulates permanently as an immutable consistency ledger (ADR-0001).
- Mutual Real-World Stakes: Players wager custom Weekly and Monthly stakes tracked in a persistent Hall of Champions ledger without penalizing leaderboard balances.
flowchart LR
subgraph Client["Client PWA (Next.js 16 / React 19)"]
UI["Linear Dark UI (#08090a)\n12-Week Heatmap & Web Audio"]
Store["Hybrid State Engine\nLocalStorage + Realtime Sync"]
end
subgraph Edge["Next.js 16 App Router API & Cron"]
AuthAPI["OAuth 2.0 Flows\n/api/auth/{google,strava,disconnect}"]
SyncAPI["Wearable Sync Pipeline\n/api/sync/{google-health,apple-health,strava,hevy}"]
CronAPI["Scheduled Telemetry Worker\n/api/cron/sync-wearables"]
Crypto["AES-256-GCM Token Vault\n(HEALTH_TOKEN_ENCRYPTION_KEY)"]
end
subgraph Providers["External Wearable & Fitness Telemetry"]
GHealth["Google Health / Health Connect"]
AHealth["Apple Health"]
Strava["Strava API"]
Hevy["Hevy Workouts"]
end
subgraph DB["Supabase PostgreSQL (Multi-Tenant RLS)"]
Duels["duels / duel_habits / duel_check_ins\n(Row-Level Security + Slot Policies)"]
Extras["duel_reactions / duel_rest_days / duel_stakes\n(Supabase Realtime Publication)"]
Vault["health_connections\n(Service-Role Only / Revoked Public Grants)"]
end
UI --> Store
Store <-->|"Authenticated JWT + Realtime"| Duels
Store <-->|"Reactions & Rest Days"| Extras
UI -->|"Bearer JWT"| AuthAPI
UI -->|"Manual / On-Demand Sync"| SyncAPI
CronAPI -->|"CRON_SECRET Bearer"| SyncAPI
AuthAPI --> Crypto
SyncAPI <--> Crypto
Crypto <-->|"Service Role Key"| Vault
SyncAPI <-->|"Reconciled Rollups & Workouts"| Providers
SyncAPI -->|"Idempotent Upsert + Weekly Rebalance"| Duels
- Strict Tenant Boundaries (
supabase/duels.sql,supabase/duels-extras.sql): Every competitive entity (duels,duel_habits,duel_check_ins,duel_stakes,duel_reactions,duel_rest_days) is partitioned byduel_idwith PostgreSQL Row-Level Security enabled. - Slot-Level Write Enforcement: Both participants (
owner_idandguest_id) holdSELECTvisibility across their shared duel to power live head-to-head analytics, whileINSERT,UPDATE, andDELETEpolicies verifyauth.uid()against the specificplayer_slot(ownervs.guest) so neither player can mutate their opponent's habits or check-ins. - Cryptographic One-Use Invitation Handshake and Profile RPCs: Duels are provisioned and updated through
SECURITY DEFINERPostgreSQL functions (public.create_duel,public.accept_duel,public.update_duel_player_name,public.set_player_color) withsearch_path = ''; the color RPC validates the caller's slot and Karma threshold before saving. - Database-Enforced Idempotency: A composite unique expression index
duel_check_ins_one_per_habit_dayon(duel_id, habit_id, (data->>'date'))prevents rapid repeat taps, network retries, or concurrent wearable sync jobs from awarding duplicate daily points.
- Unified Telemetry Routes: Server-side synchronization endpoints (
/api/sync/google-health,/api/sync/apple-health,/api/sync/strava,/api/sync/hevy) and OAuth 2.0 handshakes (/api/auth/google,/api/auth/strava,/api/auth/disconnect) reconcile daily step rollups, sleep intervals, strength workouts, and cardio sessions against active habit automation rules (src/lib/health-sync.ts,src/lib/health-data.ts). - AES-256-GCM Server-Side Token Encryption (
src/lib/health-server.ts): OAuth refresh tokens are never exposed to the browser. Before persistence inpublic.health_connections, tokens are encrypted using Node'scryptomodule (aes-256-gcm) with a 12-byte random IV, authenticated tag verification, and a 32-byte server-only key (HEALTH_TOKEN_ENCRYPTION_KEY). Furthermore,supabase/health.sqlrevokes allpublic,anon, andauthenticatedgrants onhealth_connections, restricting access strictly toservice_role. - Timezone-Aware Cron Synchronization (
/api/cron/sync-wearables): A scheduled worker authenticated viaCRON_SECRETevaluates each connected user's IANA timezone (Intl.DateTimeFormat), reconciles bothyesterdayandtodaycivil windows to capture delayed wearable uploads, preserves manual check-in precedence, and automatically rebalances weekly target habits (rebalanceWeeklyHabitCheckIns).
- Dual-Mode Storage Seam (
src/lib/duel-sync.ts,src/lib/store.tsx): Operates seamlessly as a zero-config offline-first LocalStorage app (ADR-0002) and upgrades transparently to a multi-tenant Supabase Realtime duel when authenticated. Paginated batch fetchers (fetchDuelRows) and optimistic state reconciliation keep UI interactions instantaneous. - Idempotent Migration & Backup Validation (
src/lib/legacy-import.ts,src/lib/backup.ts): Includes a deterministic migration planner (planLegacyReplacement,prepareLegacyImport) that consolidates duplicate historical check-ins by comparing proof attachments, points earned, quantitative progress, and completion timestamps, alongside full JSON backup export/restore with schema validation.
- Linear-Inspired Dark Interface: Built on a
#08090acanvas with#27272ahairline borders, responsive desktop sidebar / mobile bottom navigation, and full keyboard navigation (1/2/3tab switching,?shortcut modal,Escfocus-trapped dialogs). - Full-Width App Layout: Today, Duel, Progress, and Settings fill the available app frame while preserving desktop sidebar space and mobile gutters. Duel player names occupy balanced columns above the timeframe controls, with long names truncated rather than pushing controls off-screen.
- Visual Analytics & Social Proof: Real-time tug-of-war differential bar, Monday–Sunday daily battle charts, 10-category dominance matrix, 12-week GitHub/Linear-style consistency heatmaps (
HabitHeatmap.tsx), 12 dynamically evaluated milestone trophies (TrophyCabinet.tsx), client-side compressed photo proofs (image-utils.ts), and 1-tap cheer reactions (⚡,💪,🍕,☕). - Indexed Streaks & Calendar Rendering: Daily streaks use date sets, and weekly streaks index distinct completion dates by ISO week in one history pass before checking consecutive weeks. Rest-day protection and partial-current-week behavior are unchanged. Calendar month labels use static English names, and date labels reuse cached formatters instead of creating one per calendar cell.
- Synthesized Web Audio & PWA Support: Zero-asset Web Audio API synthesizer (
sound-utils.ts) generating tactile check-in clicks and completion fanfare chimes, paired with dynamic PWA manifest generation and URL deep-linking (?action=checkin&habit=...) for iOS Shortcuts automation.
# Install dependencies
npm install
# Start the Next.js 16 (Turbopack) development server
npm run devOpen http://localhost:3000 in your browser. Without Supabase environment variables configured, the application runs in local offline-first mode.
- In your Supabase project's SQL Editor, execute the schema migrations in order:
supabase/duels.sql— Core multi-tenant tables (duels,duel_habits,duel_check_ins,duel_stakes), RLS policies, andSECURITY DEFINERinvitation RPCs.- Existing deployments: If you have already executed
duels.sql, applysupabase/replace-solo-duel.sqlto enable switching from an unpaired solo duel to an invitation.
- Existing deployments: If you have already executed
supabase/duels-extras.sql— Realtime reactions, rest-day streak protection, and the idempotent daily check-in unique index.supabase/player-colors.sql— adds per-player colors to existing and new duels, with an authenticated RPC that enforces lifetime-point unlock thresholds. Apply once afterduels.sql; existing deployments must run it before players can save colors.supabase/health.sql— Isolatedhealth_connectionsvault restricted toservice_role.
- Copy
.env.exampleto.env.localand configure your environment variables:NEXT_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-public-key-here # Optional: Wearable OAuth & Encrypted Token Vault GOOGLE_HEALTH_ENABLED=true NEXT_PUBLIC_APP_URL=http://localhost:3000 GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=your-google-client-secret SUPABASE_SERVICE_ROLE_KEY=your-supabase-service-role-key HEALTH_TOKEN_ENCRYPTION_KEY=your-base64-encoded-32-byte-key CRON_SECRET=your-secure-cron-secret
- Enable Email/Password authentication in Supabase Auth. The first player signs up, creates a duel, and shares the generated one-use invite link from the Duel tab; the second player signs in and accepts the invitation to pair accounts.
- Database Migration: After signing in and completing habit onboarding, navigate to Vault → Settings → Bring back previous progress to merge historical habits and check-ins into your active duel (
src/lib/legacy-import.ts). - JSON Backup Sovereignty: Export or restore full duel snapshots at any time via Vault → Settings. Duplicate check-ins for the same habit and civil date are automatically consolidated.
Run the full quality gate (TypeScript strict typechecking, ESLint, and the automated unit/regression test suite covering backup validation, habit catalog parity, health telemetry parsing, image utilities, and idempotent legacy imports) with:
npm run checkIndividual commands:
npm run typecheck—tsc --noEmitnpm run lint— ESLint 9 static analysisnpm test— Compilestsconfig.test.jsonand executes the test suite via the Node.js test runner
- Domain Model & Ubiquitous Language:
CONTEXT.md - ADR-0001 — Couples Parity & Tiered Leaderboards with Non-Spendable Karma:
docs/adr/0001-couples-parity-and-tiered-leaderboards.md - ADR-0002 — Zero-Password Device-Profile Identity Model:
docs/adr/0002-zero-password-device-profile-identity.md - Wearable & Health Launch Checklist:
docs/health-launch.md - Low-Friction UX Specification:
docs/low-friction-ux.md - Agent Issue-to-PR Workflow:
docs/agent-workflow.md