Imported from shsu23cs/iter (
AGENTS.md). Install upstream withnpx skills add shsu23cs/iter. Copyright stays with the author.
AGENTS.md — Iter (Minimalist Habit Tracker)
This file instructs an AI coding agent (Google Antigravity) on how to build this project from scratch. Follow it as the source of truth for scope, stack, data model, and the production-grade bar this project must hit. Do not add features outside this scope without flagging them first (see §8 Non-Goals).
1. Project Summary
Build Iter, a minimalist, single-purpose habit tracker: users create a small number of daily habits, check them off once a day, and see consistency over time as a GitHub-style heatmap with streak counters. No social features, no gamification, no AI coaching. The bar for "done" is production-grade — authenticated, secure, tested, observable, and deployable — not a prototype.
2. Tech Stack (do not substitute without asking)
| Layer | Choice |
|---|---|
| Framework | Next.js, App Router, React Server Components |
| Styling | Tailwind CSS |
| Database & Auth | Supabase (Postgres + Auth + Row Level Security) |
| SSR/session handling | @supabase/ssr (cookie-based, Next.js middleware compatible) |
| Validation | Zod — shared schemas between client forms and server actions |
| Hosting | Vercel |
| Testing | Vitest (unit) + Playwright (E2E) |
| Error tracking | Sentry (client + server) |
Use Server Components for data-heavy pages (dashboard, heatmap) and Client Components only for interactive elements (check-in buttons, popovers, forms).
3. Setup Steps
- Scaffold a Next.js (App Router, TypeScript) project.
- Install and configure Tailwind CSS.
- Set up a Supabase project via the Supabase CLI; configure local dev with Docker.
- Install
@supabase/ssr,zod,vitest,@playwright/test, and@sentry/nextjs. - Create three separate Supabase projects/environments: local, staging, production. Never point local development at the production project.
- Store all secrets (Supabase service role key, etc.) as server-side environment variables only — never expose them in the client bundle. Add a lint/CI check that fails the build if a secret-looking key is referenced from a Client Component.
- Set up GitHub Actions for CI: lint, type-check, unit tests, E2E tests on every PR. Wire Vercel preview deployments per PR, with manual promotion to production.
4. Data Model
Implement this schema via version-controlled Supabase CLI migrations. Never hand-edit the schema directly in production.
-- Habits
create table habits (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users(id) on delete cascade,
name text not null,
icon text,
color text not null default '#6366f1',
frequency text not null default 'daily', -- extensible for future 'weekly'/'custom'
sort_order int not null default 0,
archived boolean not null default false,
created_at timestamptz not null default now()
);
-- Daily check-ins (one row per habit per date)
create table habit_logs (
id uuid primary key default gen_random_uuid(),
habit_id uuid not null references habits(id) on delete cascade,
user_id uuid not null references auth.users(id) on delete cascade,
log_date date not null,
completed boolean not null default true,
created_at timestamptz not null default now(),
unique (habit_id, log_date)
);
-- Cached streak stats (denormalized for read performance)
create table habit_streaks (
habit_id uuid primary key references habits(id) on delete cascade,
current_streak int not null default 0,
longest_streak int not null default 0,
last_computed_date date,
updated_at timestamptz not null default now()
);
-- Row Level Security
alter table habits enable row level security;
alter table habit_logs enable row level security;
alter table habit_streaks enable row level security;
create policy "Users manage own habits" on habits
for all using (auth.uid() = user_id);
create policy "Users manage own logs" on habit_logs
for all using (auth.uid() = user_id);
create policy "Users read own streaks" on habit_streaks
for select using (
auth.uid() = (select user_id from habits where habits.id = habit_id)
);
Rules to follow when implementing this:
habit_logswrites must be upserts on(habit_id, log_date), never plain inserts — this prevents duplicate entries from double-taps or retries.habit_streaksis a cache, not the source of truth. Update it via a Postgres trigger or a scheduled Supabase Edge Function wheneverhabit_logschanges — do not recompute streaks from full history on every page load.- Store dates as
date(no time component). All streak logic must operate in the user's stored timezone preference, converted at the query boundary. This is the highest-risk logic in the app — see §7 Testing. - RLS must be enforced on every table, at the database layer. Never rely on application-level checks alone.
5. Features to Implement
5.1 Authentication
- Email/password and magic-link sign-in via Supabase Auth.
- Google OAuth is a stretch goal — do not block MVP on it.
- Session persistence via
@supabase/ssr, cookie-based, working through Next.js middleware.
5.2 Habit Management (CRUD)
- Create: name, optional emoji/icon, color tag, frequency (daily only for v1; "specific days of week" is a fast-follow, not v1 scope).
- Edit: name, icon, color, archive/unarchive.
- Delete: soft delete by default (
archivedflag) to preserve historical logs; hard delete is a separate, explicitly confirmed destructive action. - Reorder via drag-and-drop, persisted to
sort_order.
5.3 Daily Check-In
- One-tap toggle for today's status, respecting the user's local timezone.
- Allow back-filling/editing a past date's entry, within a configurable window (default 7 days).
- Optimistic UI updates with rollback on failure.
5.4 Streaks
- Current streak: consecutive days completed up to today, or yesterday if today isn't logged yet. A streak is not "broken" until the day is fully over in the user's timezone.
- Longest streak: historical max, read from the
habit_streakscache, not recomputed per load. - Flame icon + count as the visual streak indicator per habit.
5.5 Heatmap Calendar
- GitHub-contribution-style grid, one cell per day.
- Per-habit view and an "all habits" aggregate view (cell = % of habits completed that day).
- Range toggle: 3 / 6 / 12 months.
- Click a cell to open a popover to view/edit that date's entries.
- Single aggregated query per render — never N+1 per-day fetches.
- Fully keyboard-navigable and screen-reader labeled (see §6.4 in the accompanying design brief, or replicate the
role="grid"pattern described there).
5.6 Dashboard
- Today's habit list with one-tap check-in.
- Current streak per habit, at a glance.
- Aggregate heatmap for roughly the last 12 weeks.
5.7 Settings
- Timezone: auto-detected, user-overridable.
- Week start day: Sunday/Monday.
- Theme: light/system.
- Account: change email/password, export data (JSON/CSV), delete account.
5.8 Server Actions (API Surface)
Implement these as Next.js Server Actions:
createHabit(input)updateHabit(id, input)archiveHabit(id)toggleCheckIn(habitId, date)— upsert, recalculates streakgetHeatmapData(habitId | 'all', range)— single aggregated querygetDashboardData()— habits + today's status + current streaks in one call
Validate every mutation's input with a Zod schema shared between the client form and the server action.
6. Non-Functional Requirements ("Production-Grade" Bar)
- Performance: Lighthouse Performance ≥ 90, Accessibility ≥ 95, Best Practices ≥ 95, SEO ≥ 90 on marketing/login pages. Dashboard time-to-interactive < 2s on 4G/mid-tier mobile.
- Security: RLS on every table; secrets server-side only; Zod validation on client and server; rate limiting on auth endpoints; CSRF protection via the SSR cookie flow;
npm audit/Dependabot in CI. - Reliability: Version-controlled migrations only; automated daily backups (Supabase PITR on paid tier, or scheduled
pg_dumpon free tier); idempotent check-in writes. - Accessibility (WCAG 2.1 AA): All interactive elements keyboard-operable with visible focus; heatmap cells expose date + completion status via
aria-label, not color alone; contrast-checked color choices; respectprefers-reduced-motion. - Observability: Sentry on client and server; structured logging on API routes/server actions; uptime monitoring on the deployed domain.
7. Testing Requirements
- Unit tests: streak-calculation logic — this is the highest-risk business logic. Cover timezone edge cases, DST transitions, and "is the streak broken yet" boundary conditions explicitly.
- Integration tests: Supabase RLS policies. Explicitly assert that a logged-in user cannot read or write another user's rows — do not assume RLS works from configuration alone.
- E2E tests (Playwright): sign-up → create habit → check in → see heatmap update.
- CI: every PR runs lint, type-check, unit tests, and E2E tests before merge.
8. Non-Goals (v1) — do not build these unless asked
- Social features (following, sharing, leaderboards).
- Habit "coaching" or AI suggestions.
- Native mobile apps (PWA-friendly web only for v1).
- Quantity-based habits (e.g. "drink 8 glasses of water") — v1 is binary done/not-done only.
- Weekly/custom-day habit frequency — daily only for v1.
9. Definition of Done (apply to every feature before marking it complete)
- Passing unit/integration/E2E tests
- RLS policy coverage verified
- Accessibility check (keyboard + screen reader pass)
- Error states handled (network failure, validation failure)
- Loading/optimistic UI states implemented
- Checked against §8 Non-Goals to avoid scope creep
10. UI / Design System
Visual design (colors, typography, components, accessibility patterns) is specified separately in the (DESIGN.md) brief for this project. Match that spec exactly for anything user-facing — do not invent colors, fonts, or spacing values outside of it.
11. Current Frontend Implementation Analysis
We analyzed the current implementation in the frontend directory. The project has been bootstrapped using TanStack Start with React 19, TypeScript, and Tailwind CSS v4.
Here is the breakdown of what has been created:
11.1 Dependencies & Stack
- Framework: TanStack Start (
@tanstack/react-start,@tanstack/react-router,@tanstack/react-query) with React 19. - Styling: Tailwind CSS v4 (
@tailwindcss/vite&tailwindcss). - Icons:
lucide-react. - UI Components: shadcn/ui primitives built on Radix UI (
accordion,alert-dialog,avatar,calendar,dialog,dropdown-menu,popover,sheet,sidebar,tabs,tooltip, etc.). - Validation & State:
zodandreact-hook-form.
11.2 Routing Structure (src/routes)
The pages use TanStack Router:
__root.tsx: App shell layout, sidebar integration, auth wrapper, and global navigation.index.tsx: Landing/marketing page.auth.tsx: Authentication page (Sign In / Sign Up toggles).dashboard.tsx: Main application dashboard listing active habits, completion toggles, and summary heatmap.habits.$id.tsx: Detail page for an individual habit (allowing CRUD operations, detailed heatmap history, and streak views).settings.tsx: User settings interface (timezone customization, week start preferences, account management).
11.3 Iter Custom Components (src/components/iter)
AppHeader.tsx: Header with logo, page title, and theme/user actions.Button.tsx: Reusable button wrappers.Card.tsx: Reusable card container.HabitRow.tsx: Layout for a habit row on the dashboard including check-in checkbox/button, name, color pill, and current streak.Heatmap.tsx: SVG-based or grid-based habit completion heatmap (resembling the GitHub contribution grid) showing completion status for habits.StreakBadge.tsx: Visual indicator showing current habit streak.
11.4 Mocking & Libraries (src/lib)
iter/mock.ts: Simulated client-side local storage and state for habits, daily logs, and streaks to enable offline/local-first behavior or prototyping before integrating with a live Supabase backend.error-capture.ts/lovable-error-reporting.ts: Basic error boundaries and logging.
Here's the build order for the backend, sequenced by dependency — each phase should compile/run before you move to the next.
Phase 0 — Foundation
backend/package.json+tsconfig.json— set up TypeScript, install your framework (Express/Fastify/Hono — whicheverapp.tsimplies), Zod, a Postgres client (pgor Supabase client depending on whether you're still using Supabase for storage), and dev tooling (nodemon/tsx, Vitest).src/core/config.ts— environment variable loading and validation (DB connection string, JWT/auth secret, port, etc.). Fail fast on startup if a required var is missing.src/core/db.ts— database connection/pool setup. If you're keeping Supabase as the Postgres provider, this is your typed client init; otherwise yourpg.Poolor ORM instance.
Phase 1 — Cross-cutting middleware
src/core/middlewares/— build these before any module, since every route depends on them:- Auth middleware (verify session/JWT, attach
user_idto the request) — this is your RLS-equivalent enforcement point now that you don't have Supabase RLS doing it at the DB layer, so every query in every module must filter byuser_idexplicitly. - Request validation middleware (wraps Zod schemas per-route).
- Error-handling middleware (consistent error shape, logged, no leaking stack traces to the client).
- Rate limiting on auth-adjacent routes.
- Auth middleware (verify session/JWT, attach
Phase 2 — Modules, in dependency order
Build habits first — logs and streaks both reference habit_id.
-
modules/habits/- Schema/model (matches the
habitstable from the PRD: name, icon, color, frequency, sort_order, archived). - Zod input schemas (create/update).
- Routes: create, update, archive, reorder, list.
- Every query scoped to
req.user_idfrom the auth middleware.
- Schema/model (matches the
-
modules/logs/- Schema/model matching
habit_logs(unique onhabit_id + log_date). toggleCheckInroute as an upsert, not insert — idempotency matters here per the PRD.- Back-fill/edit route with the 7-day window rule enforced server-side, not just in the UI.
- Heatmap data route — single aggregated query per range (3/6/12 months), not per-day fetches.
- Schema/model matching
-
modules/streaks/- Schema/model matching
habit_streaks(denormalized cache). - Recalculation logic triggered from
toggleCheckIn(either inline after the upsert, or a queued job) — this is the timezone/DST-sensitive logic the PRD flags as highest-risk, so isolate it in a pure, unit-testable function. - Read route for current/longest streak, served from the cache, not recomputed live.
- Schema/model matching
Phase 3 — Wiring
src/app.ts— assemble the framework instance: mount middleware, mount each module's router, global error handler.src/index.ts— entrypoint: load config, connect DB, start the server, graceful shutdown handling.
Phase 4 — Harden
- Unit tests for streak calculation (timezone/DST edge cases) — do this before wiring the frontend, since it's the module most likely to have subtle bugs.
- Integration tests asserting a user cannot read/write another user's habits/logs (your RLS-equivalent check, now enforced in code — test it explicitly).
- Connect
frontend/to these routes and confirm the E2E flow end-to-end: sign in → create habit → check in → heatmap updates.