Production PWA for personal discipline — Next.js 15, Supabase, Trigger.dev scheduled jobs, VAPID push notifications. AI layer in active development.
| Layer | Technology |
|---|---|
| Framework | Next.js 15 (App Router) |
| Auth + DB | Supabase (Postgres + RLS) |
| Styling | Tailwind CSS v3 |
| Push | web-push (VAPID) |
| Jobs | Trigger.dev v3 |
| Deploy | Vercel |
- Focus Rail — Now / Next / Later block view with one-tap start/complete/skip
- Plan Builder — Morning ritual: set intention + schedule time blocks by category
- Progress Log — Log job apps, LeetCode sessions, skills, gym (with live timer)
- Stats — 7-day heatmap, streaks per domain, weekly totals
- Push Notifications — VAPID-based, per-device; EOD reports, gym nudges
- Recovery / Replan — If the day goes sideways, repack remaining blocks (minimal / catchup / reset mode)
- PWA — Installable, offline shell via Service Worker
- Node.js 20+
- A Supabase project
- A Trigger.dev project (optional for local dev)
git clone <repo>
cd LifeOS
npm installCopy .env.local.example to .env.local and fill in:
cp .env.local.example .env.local| Variable | Where to find it |
|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Supabase project → Settings → API |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase project → Settings → API |
SUPABASE_SERVICE_ROLE_KEY |
Supabase project → Settings → API (secret) |
NEXT_PUBLIC_VAPID_PUBLIC_KEY |
npx web-push generate-vapid-keys |
VAPID_PRIVATE_KEY |
same command |
VAPID_EMAIL |
your email |
TRIGGER_SECRET_KEY |
Trigger.dev dashboard |
TRIGGER_PROJECT_ID |
Trigger.dev dashboard |
Run the migration in the Supabase SQL editor (or with supabase db push if using the CLI):
# with supabase CLI
supabase db pushThe migration is at supabase/migrations/001_initial.sql.
Note: The
get_streaksRPC is referenced by the/api/plan/todayroute. If you haven't created it yet, the streaks will default to{ job: 0, leetcode: 0, gym: 0, skills: 0 }. See below for the SQL.
get_streaks SQL function
CREATE OR REPLACE FUNCTION get_streaks(p_user_id UUID)
RETURNS JSON AS $$
DECLARE
job_streak INT := 0;
lc_streak INT := 0;
gym_streak INT := 0;
sk_streak INT := 0;
d DATE;
BEGIN
-- job streak
d := CURRENT_DATE;
LOOP
EXIT WHEN NOT EXISTS (SELECT 1 FROM job_applications WHERE user_id = p_user_id AND date = d);
job_streak := job_streak + 1;
d := d - 1;
END LOOP;
-- lc streak
d := CURRENT_DATE;
LOOP
EXIT WHEN NOT EXISTS (SELECT 1 FROM leetcode_sessions WHERE user_id = p_user_id AND date = d);
lc_streak := lc_streak + 1;
d := d - 1;
END LOOP;
-- gym streak
d := CURRENT_DATE;
LOOP
EXIT WHEN NOT EXISTS (SELECT 1 FROM gym_sessions WHERE user_id = p_user_id AND date = d AND end_ts IS NOT NULL);
gym_streak := gym_streak + 1;
d := d - 1;
END LOOP;
-- skills streak
d := CURRENT_DATE;
LOOP
EXIT WHEN NOT EXISTS (SELECT 1 FROM learning_logs WHERE user_id = p_user_id AND date = d);
sk_streak := sk_streak + 1;
d := d - 1;
END LOOP;
RETURN json_build_object('job', job_streak, 'leetcode', lc_streak, 'gym', gym_streak, 'skills', sk_streak);
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;npm run devOpen http://localhost:3000.
npm run trigger:devLifeOS/
├── app/
│ ├── (auth)/ # Login, register pages
│ ├── (app)/ # Protected app shell
│ │ ├── page.tsx # Focus Rail
│ │ ├── plan/ # Plan builder
│ │ ├── log/ # Progress logging
│ │ ├── stats/ # Stats & streaks
│ │ └── settings/ # Targets, push, account
│ ├── api/ # API routes
│ │ ├── plan/
│ │ ├── block/
│ │ ├── recovery/
│ │ ├── progress/
│ │ └── push/
│ ├── globals.css
│ └── layout.tsx
├── components/ui/ # Card, Button, Badge, BottomNav
├── lib/
│ ├── types.ts
│ ├── supabase/ # client.ts, server.ts
│ ├── push/send.ts # VAPID push wrapper
│ ├── recovery/replan.ts # Rule-based replan logic
│ └── ai/stubs.ts # Phase 2 AI placeholders
├── trigger/ # Trigger.dev scheduled jobs
├── public/
│ ├── sw.js # Service worker
│ ├── manifest.json
│ └── icons/ # PWA icons (add manually)
├── supabase/migrations/
├── docs/alexa.md
└── middleware.ts
vercel --prodSet all environment variables in the Vercel dashboard before deploying.
You'll need to place icon PNGs in public/icons/:
icon-72.png icon-96.png icon-128.png icon-144.png
icon-152.png icon-192.png icon-384.png icon-512.png
badge-72.png
A quick way to generate them from a single SVG:
npx pwa-asset-generator logo.svg public/icons --manifest public/manifest.json- Real AI insights via Anthropic Claude API (
lib/ai/stubs.ts→ real calls) - Alexa voice skill (
docs/alexa.md) - Offline-first queue with Background Sync
- Calendar import/export (iCal)
- Multi-user / team mode