Take it one hop at a time.
A calm, private habit tracker for a small circle of friends — part of the 4dl Apps family. Live at rhabbit.4dl.ca.
- One-tap logging on a mobile-first Today screen, with undo everywhere
- Forgiving progress — skips, pauses, and comebacks instead of guilt
- Boolean, numeric, duration, and avoidance habits with flexible schedules (daily, chosen weekdays, or N times a week)
- Spreadsheet import (.xlsx/.xls/.csv) with layout detection, preview, and one-tap undo — plus JSON/CSV export
- Calendar history with backfill, per-habit heatmaps, plain-language insights
- Installable PWA with offline support, dark and light themes
React 19 · TypeScript · Vite · Firebase (Google auth + Firestore with offline persistence) · SheetJS · vite-plugin-pwa · Netlify
cp .env.example .env # fill in Firebase web config
npm install
npm run devnpm run build type-checks and produces dist/. Deploys run automatically
from main via Netlify: a GitHub push webhook fires a Netlify build hook,
and the buildbot clones over SSH with a read-only deploy key. If a deploy is
ever needed by hand: npm run build && netlify deploy --prod --dir dist --no-build (local .env supplies the Firebase config).
npm test # unit tests for dates, schedules, and stats
npm run test:rules # Firestore security rules, against the local emulator
npm run ci:verify # everything CI runs: typecheck, both suites, buildtest:rules boots the Firestore emulator, which needs a Java runtime on
PATH. Homebrew's openjdk is keg-only, so it needs to be added explicitly:
brew install openjdk
export PATH="/opt/homebrew/opt/openjdk/bin:$PATH" # add to ~/.zshrc to persistThe rules suite asserts denials as well as grants — cross-user reads,
non-allowlisted accounts, unverified emails, and self-granting an allowlist
entry. Changing firestore.rules without updating it should turn CI red.
Production uses rhabbit.4dl.ca as Firebase's auth domain and Netlify proxies
/__/auth/* to the Firebase Hosting auth helpers. Keep rhabbit.4dl.ca in
Firebase Authentication's authorized domains and keep
https://rhabbit.4dl.ca/__/auth/handler in the Google OAuth web client's
authorized redirect URIs. Local development continues to use the
VITE_FIREBASE_AUTH_DOMAIN value from .env.
Rhabbit is open to Adil's close friends. If you know him personally, reach out
to ask for access. Server-side Firestore rules (firestore.rules) only admit
Google accounts whose verified email exists in the allowlist collection —
add or remove emails in the Firebase console (doc ID = email, contents can be
empty). Each person's habit data remains private to their account.
users/{uid} profile: displayName, timezone, weekStartsOn
users/{uid}/habits/{id} name, type, target, schedule, timeOfDay, …
users/{uid}/entries/{habitId_date} status, value, note — one doc per day
users/{uid}/importBatches/{id} filename, counts — enables import undo
allowlist/{email} presence = access (console-managed)
Entries key on a local date string (2026-07-19), so history never
shifts when the device timezone changes.