Habits, made visible. A desktop-first habit-tracker web app with a matte editorial aesthetic, a forgiving streak/momentum model (no streak-anxiety), and insights that explain why you succeed — not just whether. Brand: nitor is Latin for brightness / radiance / striving. Tagline: Show up. Glow.
Phase 2 — identity and persistence are live. Auth is real Supabase GoTrue (no stub sign-in), and authenticated habits and logs persist to Postgres behind row-level security. The signed-out landing demo still uses the seeded in-memory repository behind the same
HabitRepositoryseam.The Nix companion is deferred, not cancelled:
/petis an honest "Coming soon" page and the marketing surfaces no longer show the creature. The component, store and page are kept for a future phase.
npm install
npm run dev # http://localhost:3000
npm test # Vitest unit/component suite
npm run build # production buildRequires Node ≥ 20.9. The app lives at the repository root (Next.js 16, App Router).
Auth and persistence need a Supabase project. Copy the values into .env.local:
NEXT_PUBLIC_SUPABASE_URL=https://<project-ref>.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=<publishable key>
SUPABASE_SECRET_KEY=<service-role key> # server-only — never NEXT_PUBLIC_SUPABASE_SECRET_KEY bypasses row-level security. It is read only inside server-side route
handlers (src/lib/supabase/admin.ts constructs its client per call, never at module scope) and
must never reach a client bundle, a NEXT_PUBLIC_ variable, or a log line.
Schema lives in supabase/*.sql and is applied by hand (SQL editor or apply_migration); each
file is written to be re-runnable. Verify tenancy against the live project with:
npx tsx --env-file=.env.local scripts/verify-rls.tsReal Supabase auth — email/password with an emailed confirmation link. There is no stub sign-in
and no OAuth provider configured (the live project reports external.google: false; its absence
is deliberate).
- Import the repo in Vercel. Root Directory:
./(the app is at the repo root). - Framework preset: Next.js.
- Set the three environment variables above.
SUPABASE_SECRET_KEYmust be server-scoped. - Deploy.
- Marketing landing (
/) — hero + GSAP scroll story + kinetic NITOЯ footer. - App — Today (checklist), Habits (5-type builder), Stats (GitHub-style heatmap), Insights
(worded correlations, streak-risk, stacking, monthly recap), Settings, and a deferred
/pet. - Auth — login / signup / forgot-password + a 3-step onboarding, gated at authentication.
- Account — permanent account deletion via
DELETE /api/account, which reads the subject from verified claims only and confirms the delete actually happened before reporting success. - Design system — see
DESIGN.md: matte tokens, the NITOЯ wordmark (mirrored Я), Space Grotesk / Geist / JetBrains Mono, one amber accent, restrained glitch (4 sanctioned uses).
- Domain logic (
src/domain/) — types, dates, forgiving streak/momentum engine, insights, stats — is framework-free and unit-tested. - All data flows through
HabitRepository(src/data/repository.ts). Signed-in users getSupabaseHabitRepository; the signed-out demo getsMockHabitRepository. Selection lives insrc/data/repositorySelection.ts. - Tenancy is structural, not app-enforced.
habitsandlogsare both keyed(user_id, id), andlogsreferences habits by a composite FK(user_id, habit_id) → habits(user_id, id). Postgres validates FKs with RLS bypassed, so a single-column reference was a cross-tenant hole. The client never sendsuser_id; it defaults toauth.uid()and is pinned by the RLS insert with-check. Browser-side filters are not a security boundary. - Authorization always uses
getClaims()(orgetUser()), nevergetSession()— that reads cookie storage shared with the client and is not revalidated. - Middleware is
src/proxy.ts(a Next 16 file convention, not the repo root) and matches public paths against an exactSet, not a prefix. - The 3D pet is a procedural placeholder (
src/components/pet/NixCreature.tsx) with a clean slot for a Spline scene / rigged.glb. Deferred — kept, not wired into marketing.
Project status and roadmap: PROGRESS.md.