Habit Rabbit
A habit tracker.
- Next.js
- React
- Zustand
- Ant Design
- Tailwind
- Recharts
- Firebase
- COMMITS
- 97
- FIRST
- Mar 2026
- LAST
- Sep 2026
- ACTIVE MONTHS
- 4
Habit Rabbit
Deployed at health-n-habit: https://health-n-habit.netlify.app/
Habit tracker with sections (habits), updates (log entries), and multiple views: list, frequency chart, and calendar heatmap. Built with Next.js (App Router), Zustand, Ant Design, Tailwind CSS, and Recharts.
Product overview (user journeys, fitness vs sections, data flow, key files): docs/APP_OVERVIEW.md.
What this app does
- Sections – Each section is a habit (e.g. "Exercise", "Reading"). You can add, edit, and reorder them.
- Updates – Each section has updates: short log entries with optional timestamps. Add, edit, or soft-delete (undo window) updates.
- Views – Switch between:
- List – Sections as cards with grouped updates (by day).
- Frequency – Bar chart of update counts over a chosen range (1m, 3m, 6m, 1y).
- Calendar – Heatmap of activity by day (week, month, last 7, last 30).
- Search & sort – Filter sections by search; sort by most updates (all-time or today), recently updated, or name A–Z / Z–A.
- Layout – Horizontal cards or grid. Settings (view, layout, calendar range, frequency range, sort) live in the navbar and persist in the store.
- API – All data goes through Next.js API routes (
/api/sections,/api/sections/[id]/updates,/api/fitness, etc.). With Firebase configured, APIs require authentication and data is stored in Firestore per user. Without Firebase env vars, the app falls back to file-based or in-memory data.
Tech stack
| Area | Choice |
|---|---|
| Framework | Next.js 16 (App Router), React 19 |
| State | Zustand (src/store/useSectionsStore.ts) |
| UI | Ant Design 5, Tailwind CSS 4 |
| Charts | Recharts |
| Date logic | date-fns |
| Utils / checks | lodash (e.g. isNil, isEmpty, get) |
| Tests | Vitest, React Testing Library, jsdom |
| Lint / format | ESLint (Next + Prettier), Prettier |
| Git hooks | Husky 9, lint-staged |
Project structure
habit-rabbit/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── layout.tsx # Root layout (fonts, AntdRegistry, globals)
│ │ ├── page.tsx # Main home page (sections, views, FAB)
│ │ ├── globals.css # Tailwind + fluid typography
│ │ └── api/ # API routes
│ │ └── sections/ # CRUD for sections and updates
│ ├── components/ # React components
│ │ ├── Navbar.tsx # Search, settings menu, view/layout/calendar/freq toggles
│ │ ├── SectionCard.tsx # One section (title, updates, add/edit/delete)
│ │ ├── FreqChart.tsx # Recharts frequency bar chart
│ │ ├── CalendarGrid.tsx # Calendar heatmap
│ │ ├── SettingsMenu.tsx # View, layout, calendar range, freq range, sort
│ │ ├── AddHabitFab.tsx # FAB to add new section
│ │ ├── DeleteToast.tsx # Undo for soft-delete
│ │ └── ... # UpdateItem, AddUpdateForm, toggles, etc.
│ ├── store/
│ │ └── useSectionsStore.ts # Global state (sections, UI prefs, API calls)
│ ├── lib/ # Pure utils + API client
│ │ ├── api.ts # fetch wrappers for /api/sections
│ │ ├── sectionsFilterSort.ts
│ │ ├── groupUpdatesByDay.ts
│ │ ├── dateRange.ts, calendarHeatmap.ts, formatIndianDate.ts, utils.ts
│ │ └── store.ts # Optional persisted store helpers
│ ├── constants/ # Shared constants
│ │ ├── colors.ts # Pastel palette, getPastelStyle
│ │ ├── sortOptions.ts, viewOptions.ts
│ │ ├── antdTheme.ts
│ │ └── ...
│ ├── types/
│ │ └── index.ts # Section, Update, LayoutChildren
│ └── test/
│ ├── setup.ts # RTL + jest-dom
│ └── vitest-setup.d.ts # Vitest + jest-dom types
├── .husky/ # Git hooks (Husky 9)
│ ├── pre-commit # lint-staged + format:check + lint
│ └── _/ # Husky internals (hooksPath)
├── .cursor/
│ ├── practices.md # Code conventions (no default export, lodash, etc.)
│ └── rules/ # Cursor AI rules
│ ├── pre-commit-agentic.mdc # "Fix lint/format" → run fix then check
│ └── github-issue-agent.mdc # "Implement issue #N" → implement + PR
├── .github/
│ ├── workflows/
│ │ ├── ci.yml # Check + build on push/PR
│ │ └── agentic-issue.yml # On label "agent": create branch + draft PR
│ └── AGENTIC_ISSUES.md # How to have an agent pick up issues and deploy
├── check.sh # Pre-commit check script (format, lint, lint-staged)
├── fix.sh # Format + lint:fix
├── vitest.config.ts
├── eslint.config.mjs
├── .prettierrc / .prettierignore
└── package.jsonScripts
| Command | What it does |
|---|---|
pnpm dev | Next dev (webpack). |
pnpm build | Production build. |
pnpm start | Run production server. |
pnpm run check | Format check + lint (no fix). |
pnpm run lint | ESLint (includes no-console). |
pnpm run lint:fix | ESLint with auto-fix. |
pnpm run format | Prettier write. |
pnpm run format:check | Prettier check only. |
pnpm run fix:all | Runs fix.sh: format + lint:fix. |
pnpm run check:precommit | Runs full pre-commit check (same as Husky). |
./check.sh | Human-friendly check (format, lint, lint-staged) with fix hints. |
./fix.sh | Format + lint:fix. |
pnpm run test | Vitest watch. |
pnpm run test:run | Vitest single run. |
pnpm run prepare | Husky install (run after clone; sets core.hooksPath). |
Code conventions (and how I work)
These are the rules and behaviors I use in this repo. Anyone (or any agent) editing the codebase should follow them.
General
- No comments unless necessary. Prefer clear names over comments.
- Lint before commit. Use
pnpm run checkor the pre-commit flow (see below). I usenpx eslint . --quietto verify.
Exports and imports
- No default exports (except where the framework requires it, e.g. Next.js
layout.tsx/page.tsx). Use named exports and import likeimport { X } from "@/...". - Path alias:
@/points tosrc/. Useimport { x } from "@/components/...",@/lib/...,@/constants/...,@/store/...,@/types.
Types and checks
- Null/undefined: Don’t use
value !== null && value !== undefined. UseisNilfrom lodash:if (!isNil(data)). - Loose equality: Don’t use
!=. Use explicit checks (if (data)) or lodash (if (!isNil(data))). - Type checks: Don’t use
typeof x !== 'object'or similar. Use lodash:isObject,isString, etc.
React and UI
- No
<style>tags. Use CSS modules or inline stylesstyle={{ ... }}. - Component file order:
- Imports
- Small constants (or put in
src/constants/) - Main component (named after the file; named export)
- Supporting components
- Utils (small ones here; bigger ones in
lib/orutils/)
Dates
- Use date-fns for date math. No raw millisecond math. Use helpers like
differenceInCalendarDays,subDays,eachDayOfInterval,startOfWeek,endOfMonth.
Organization
- Constants in
src/constants/(e.g.sortOptions.ts,colors.ts,viewOptions.ts,antdTheme.ts). Avoid big inline objects in components. - Utils in
src/lib/(orsrc/utils/). Pure helpers; components import from@/lib/...or@/constants/....
Performance
- useMemo for derived data (filtered/sorted lists, computed arrays) that depend on props/state.
- useCallback for stable callbacks passed to children or used in effect deps.
Testing
- Vitest + React Testing Library. Tests live next to source (
*.test.ts,*.test.tsx). Run:pnpm run testorpnpm run test:run. - Setup:
src/test/setup.tswires@testing-library/jest-dom. UsegetByTestIdwhen multiple elements match (e.g. StrictMode); otherwisegetAllBy*+ index orfireEventas needed.
Responsive typography
- Root font size is fluid in
globals.css(clamp onhtml). Use rem-based Tailwind so type scales. For very small labels, use.text-fluid-xs.
Lint rules I care about
- no-console: ESLint errors on
console.log/console.*. No debug logs in prod; remove them or use a proper logger.
Pre-commit and “agentic” check flow
I want every commit to pass format and lint (including no-console). I also want an AI agent to be able to fix and re-check in one go.
What runs on commit (Husky)
- Pre-commit hook (
.husky/pre-commit): runs lint-staged (Prettier + ESLint --fix on staged files), then format:check, then lint. If any step fails, the commit is blocked. - Lint-staged (in
package.json): on*.{js,jsx,ts,tsx,json,css,md}, runsprettier --writeandeslint --fix.
Note:.github/workflows/*.ymlare in.prettierignoreso Prettier doesn’t parse them (avoids YAML parse issues).
Scripts for checking and fixing
./check.sh– Runs format:check, lint, and lint-staged with readable output and fix hints (e.g. “Prettier wants a word” →pnpm run format)../fix.sh– Runspnpm run formatandpnpm run lint:fix. Use this when the agent (or you) needs to auto-fix before re-checking.- Agentic flow: When I say “pre-commit failed”, “fix lint”, or “make checks pass”, I want the agent to run the fix script, then the check script, and if something still fails (e.g.
console.log), fix the code and re-run until the check passes. The Cursor rule pre-commit-agentic describes this; the agent should runpnpm run fix:allthen./check.shand address any remaining issues.
How I want agents to handle GitHub issues (“agentic” issues)
I want to create an issue and have an agent implement it and open a PR so I can merge and deploy (e.g. Vercel on main).
Cursor agent (me asking in chat)
- When I say “Implement issue #N”, “Pick up my latest issue”, or “I just created a new issue”, the agent should:
- Resolve the issue (by number or “latest” via
gh issue view/gh issue list). I have GitHub CLI installed and logged in. - Implement the work in the repo.
- Run the check flow (
pnpm run fix:allthen./check.sh) and fix any failures (including removingconsole.*). - Create a branch (e.g.
issue-5-short-slug), commit, push, and open a PR that closes the issue (body: “Closes #5”). The agent should not merge; I merge the PR so that deploy (e.g. Vercel) runs.
- Resolve the issue (by number or “latest” via
The Cursor rule github-issue-agent encodes this. I don’t want the agent to suggest I run commands manually when I’ve asked it to implement an issue; it should run the flow and fix code as needed.
Optional: GitHub Action for label “agent”
- If I add the label
agentto an issue, the workflow agentic-issue.yml creates a branch and a draft PR and comments on the issue. Then I (or the agent in Cursor) can implement on that branch; merging the PR is the deploy step. - Details: .github/AGENTIC_ISSUES.md.
Setup (for me or a new clone)
- Node: >= 20. pnpm: >= 9 (see
packageManagerinpackage.json). - Install:
pnpm install.
This runsprepare, which runs Husky and setsgit config core.hooksPathto.husky/_so the pre-commit hook runs. - Env (production): Copy
.env.exampleto.env.localand fill in:- Firebase (client):
NEXT_PUBLIC_FIREBASE_API_KEY,NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,NEXT_PUBLIC_FIREBASE_PROJECT_ID, and optionallyNEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,NEXT_PUBLIC_FIREBASE_APP_ID(from Firebase Console → Project settings → Your apps). - Firebase Admin (server):
FIREBASE_SERVICE_ACCOUNT_KEY(JSON string) orGOOGLE_APPLICATION_CREDENTIALS(path to service account JSON). From Firebase Console → Project settings → Service accounts → Generate key. Without these, login and API auth are disabled and the app uses file-based or in-memory data only. Firestore rules: In Firebase Console → Firestore → Rules, restrictusers/{userId}/data/*to the signed-in user:request.auth != null && request.auth.uid == userId.
Service account IAM: The Firebase Admin SDK service account (e.g.firebase-adminsdk-xxx@your-project.iam.gserviceaccount.com) must have the Cloud Datastore User role so the server can read/write Firestore. In Google Cloud Console → IAM & Admin → IAM, find the service account and add the role if you see "Missing or insufficient permissions" from the API. - AI assistant (server, optional):
ANTHROPIC_API_KEYpowers the in-app "Ask AI" chat (/api/assistant) on the fitness page — ask for your metrics, edit set/rep schemes, rename exercises, or paste a markdown workout to rebuild a section. Server-side only; never prefix withNEXT_PUBLIC. On Netlify add it under Site settings → Environment variables. Optionally setASSISTANT_MODEL(defaults toclaude-haiku-4-5, the lightest/cheapest model). The assistant applies safe edits immediately and asks for confirmation before destructive/bulk changes (history-affecting renames, routine imports). It is gated to privileged accounts, same as the rest of the fitness features.
- Firebase (client):
- Dev:
pnpm dev.
Build:pnpm build. Run prod:pnpm start. - GitHub CLI (for agentic issues):
gh auth loginso the agent can read issues and create PRs.
CI/CD
- GitHub Actions (ci.yml): On push/PR to
main(ormaster): check (format + lint) and build. No deploy in the workflow; I connect the repo to Vercel (or similar) so that push tomaintriggers deployment. - Vitest: Used only locally / in tests;
next buildtype-checks the app (includingtsconfig).vitest.config.tsis excluded from anything that would break the Next build (e.g.resolveis only at top level so TypeScript is happy).
Summary of my behaviors and needs (for agents and humans)
- Conventions: Named exports,
@/imports, lodash for null/type checks, no!=, no<style>, noconsole.*, date-fns for dates, useMemo/useCallback where it matters, tests next to source with Vitest + RTL. - Pre-commit: Must pass format + lint (including no-console). Use
check.shfor a friendly report andfix.sh(orpnpm run fix:all) to fix; agent should re-run until the check passes. - Issues: When I ask to “implement issue #N” or “pick up my latest issue”, implement it, run the check flow, open a PR that closes the issue, and don’t merge (I merge to deploy).
- Docs: Create docs for files when making changes and update them with new changes. this allows infinite context gathering. allows agents to work with changes and learn
HEAD · 500ffe139 FOLDERS · 190 FILES
- .cursorredirects, logins, and themingMar 27
- .githubfixes yamlMar 19
- .huskyfailure fixSep 16
- .vscodelinting and stuffMar 27
- docsredesignSep 16
- publicInitial commit: Habit RabbitMar 15
- srcUse lightest model (claude-haiku-4-5) for the assistant backendSep 26
- .gitignoreignore debug logsApr 9
- .npmrccleanupMar 19
- .prettierignoreagent docs, and build fixMar 15
- .prettierrcInitial commit: Habit RabbitMar 15
- eslint.config.mjsspace between imports and codeMar 27
- firestore.rulesadds google auth, and databaseMar 19
- fix.shhusky checksMar 15
- netlify.tomlfixes pnpm versions, adds netlify tomlMar 15
- next.config.tsnew redirectsSep 19
- package.jsonAdd in-app AI assistant for metrics and routine editingSep 26
- pnpm-lock.yamlAdd in-app AI assistant for metrics and routine editingSep 26
- postcss.config.mjsInitial commit: Habit RabbitMar 15
- README.mdUpdate README.mdSep 28
- tsconfig.jsonfixesMar 15
- vitest.config.tsfixes fonts and spacing on small screen runs linterMar 27
Showing the top two levels. Files aren't browsable: this is a portfolio, not a mirror.
97 COMMITS · 2 AUTHORS
MON, SEP 28 20262 COMMITS
- 500ffe1Update README.mdEYSep 28Sep 28
- bb91a1bUpdate README.mdEYSep 28Sep 28
SAT, SEP 26 20262 COMMITS
- 23d38d6Use lightest model (claude-haiku-4-5) for the assistant backendClaudeSep 26Sep 26
- 670935cAdd in-app AI assistant for metrics and routine editingClaudeSep 26Sep 26
SAT, SEP 19 20267 COMMITS
- 0a4a37anavigation fixesEYSep 19Sep 19
- a1bdb2anull checksEYSep 19Sep 19
- 50223ecnew redirectsEYSep 19Sep 19
- e2a1efe75 d windowsEYSep 19Sep 19
- 947e27dsecurity and mark the startEYSep 19Sep 19
- 2cfa54cfixes behaviorsEYSep 19Sep 19
- 7cea9d8fitness v2, fuck the bro splitEYSep 19Sep 19
WED, SEP 16 202610 COMMITS
- e9fe021checkable excercisesEYSep 16Sep 16
- a2f2bc0menu heightEYSep 16Sep 16
- 09e252dfat buttonsEYSep 16Sep 16
- 0f9b933failure fixEYSep 16Sep 16
- 6261de0buttonsEYSep 16Sep 16
- 4513dearedesignEYSep 16Sep 16
- 743a431rename biceps tricepsEYSep 16Sep 16
- 5a644d9start selected groupsEYSep 16Sep 16
- 32f052badd quick startEYSep 16Sep 16
- 364fad8add exercise exportEYSep 16Sep 16
TUE, JUL 7 20267 COMMITS
- 5ff744dlintEYJul 7Jul 7
- 3e505fffinal touchesEYJul 7Jul 7
- 08c5252fixesEYJul 7Jul 7
- 795f320use last n days selector inside chart range tabEYJul 7Jul 7
- 9c4c78badd chart range tiles with last n weeks and allEYJul 7Jul 7
- 73fc84eadd ate shit checkbox to wellbeingEYJul 7Jul 7
- 13448cfgroup abs and biceps into one sectionEYJul 7Jul 7
MON, APR 27 20269 COMMITS
- 1ccdae8removes testingEYApr 27Apr 27
- 3a065d6better progress labelsEYApr 27Apr 27
- 71ef38fprogress barsEYApr 27Apr 27
- c9d99d7better progress markersEYApr 27Apr 27
- b8f02adprogress barEYApr 27Apr 27
- f94d9aebetter uiEYApr 27Apr 27
- 72561d4fixes congrats behaviorEYApr 27Apr 27
- cc5de71adds congrats modalEYApr 27Apr 27
- 4345905adds milestonesEYApr 27Apr 27
MON, APR 13 202613 COMMITS
- 5219d07fixes scaleEYApr 13Apr 13
- e7ba714fixes elapsed streak formattingEYApr 13Apr 13
- 9312674fixes time functionsEYApr 13Apr 13
- 5094d40fixes constantsEYApr 13Apr 13
- a9fd8b5better toastEYApr 13Apr 13
- c1b9350debounced settingsEYApr 13Apr 13
- d90beb6stale date alert, reduced paddingEYApr 13Apr 13
- 8a88bf6fixes nf to start saving streaksEYApr 13Apr 13
- e4b0ae0better hoverEYApr 13Apr 13
- 72bcfc9major refactor into smaller modulesEYApr 13Apr 13
- b9b9525better charts, and scalesEYApr 13Apr 13
- e032dd4better colors and chartsEYApr 13Apr 13
- a1b921aremoves redundant stringEYApr 13Apr 13