Web apps

Habit tracker with weekly digest and group cheers

React and Supabase habit tracker with a weekly email digest and cheer groups of up to 5 members.

reactvitetypescriptsupabaseresend

Built 1 time, downloaded 0 times.

Install from your terminal

Run this in your project folder. It signs you in, shows what changes and asks before it overwrites a file.

npx claude-lab add habit-tracker-with-weekly-digest-and-group-cheers-12525c
CLAUDE.md
# the app

the app is a personal habit tracker. Users check in on daily habits, see their streaks, and get a weekly email digest on Sunday evening in their own timezone. Users can also join small cheer groups of up to 5 friends, where members see each other's check-in counts and send short cheers. Habit names stay private unless the user opts in.

The spec lives in SPEC.md and the build plan in PLAN.md. Read them before large changes.

## Commands

- `npm run dev`: start the Vite dev server
- `npm run typecheck`: TypeScript check, no emit
- `npm run lint`: ESLint over the project
- `npm test -- --run`: Vitest unit tests, single run
- `npx playwright test`: end-to-end tests (run when UI files change)
- `npm run build`: production build
- `npm run digest:preview`: render the weekly digest to an HTML file for review

## Architecture

- `src/features/habits`: habit CRUD, daily check-ins, streak calculation
- `src/features/groups`: groups, invite codes, membership, cheers
- `src/features/digest`: pure digest generator (no network calls)
- `src/lib/supabase.ts`: the single Supabase client setup
- `supabase/migrations`: schema, RLS policies, triggers
- `supabase/functions/weekly-digest`: scheduled function that sends email through Resend

Keep feature code inside its feature folder. Shared helpers go in `src/lib` only when two features need them.

## Conventions

- TypeScript strict mode is on. Do not use `any`. Use `unknown` and narrow it.
- Function components only. No class components.
- Server state goes through TanStack Query. Do not fetch in components directly.
- Validate every input boundary with Zod: form input, API responses, webhook or function payloads.
- Tests sit next to the code they cover (`*.test.ts`, `*.test.tsx`). Playwright tests live in `e2e/`.
- Use Intl for date and time work. Never hard-code offsets.

## Gotchas

- The 5-member group cap is enforced in two places: a database trigger on `group_members` (the real guard) and the UI (for clear feedback). A UI-only check is a bug.
- Every table needs row level security enabled, with policies written in the same migration that creates the table.
- Store each user's timezone on `profiles`. Compute the digest send time (Sunday 18:00) and the week boundaries in that timezone, including DST weeks.
- Cheers are visible to group members. The digest and the UI must say so.
- The local `/code-review` reads CLAUDE.md but not REVIEW.md. The import below makes it read the review checklist too.
- `DRY_RUN` must be respected by the digest sender. Tests never send email.

@REVIEW.md

## Secrets

- Never commit `.env` or `.env.local`. Both must be listed in `.gitignore`.
- Use only the placeholder names `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and `RESEND_API_KEY`. Real values go in `.env.local` only.
- The Supabase service role key never goes into client code or the Vite bundle.
- Do not read `.env` or `.env.local` into the conversation. Refer to variable names only.

## Ask before

- Changing migrations on tables that already exist
- Changing the digest schedule or its timing rules
- Adding a new dependency (state the reason in the PR or commit message)

## Definition of done

Before saying a task is done, run the `verify` skill. It runs typecheck, lint, unit tests, and build, and runs Playwright when UI files changed. Report only the failures. Do not claim done while a step fails.

## Working style

- Explore, plan, code, then verify. Give yourself a way to check each change.
- Keep changes scoped. No unrelated refactors in the same change.
- Use the `test-runner` subagent for long test output so logs stay out of the main context.
- Use `/clear` between unrelated tasks and `/compact` at natural breaks.
Use for my project