A personal meal-prep app: structured recipes and ingredients instead of manually re-deriving a shopping list every week, with Picnic as the delivery layer.
Built for two people (me + my household), as a portfolio piece and a hands-on lab for AI PM judgment and agentic dev practices (RPI/QRSPI).
Not a production app — no public access, no accounts, no reliability guarantees on the Picnic integration (unofficial API).
Phase 1 complete. Recipes/ingredients as structured data, a meal-plan
week view, a shopping-list page generated from that plan, and a real push to
the Picnic basket — all through the app's own UI, no scripts involved.
Confirmed end-to-end against a real account: a matched item ("Harry Sammys
Super Sandwich") landed in the real Picnic basket via the app's push button.
See docs/phases.md for what shipped when.
Design system (Apple-inspired, light/dark) is applied throughout. Picnic
auth uses a stored PICNIC_AUTH_KEY — see "Security notes" below before
treating that as a solved problem.
Source of truth for scope and direction lives in Obsidian, not here:
04 After the Algorithm Lab/06 Prototypes & Apps/Meal Prep App/Vision.md
One language end-to-end, chosen for a laptop-only, two-person app with no auth and no scale requirements:
- Next.js 16 (App Router, TypeScript) — pages are server components that
read the database directly; mutations go through Server Actions. Next 16
changed several APIs since prior training data (async
params, Server Actions patterns) — seeAGENTS.md/node_modules/next/dist/docsbefore assuming older Next.js conventions. - Prisma 7 + SQLite (
better-sqlite3driver adapter) — one file database (dev.db, gitignored), good enough for two users and no need for a server-hosted DB. Prisma 7 changed generator config significantly (prisma-clientgenerator, driver adapters mandatory,prisma.config.tsfor connection config) — seeprisma.config.tsandprisma/schema.prisma. - Tailwind CSS — utility styling, no component library yet.
- picnic-api (unofficial Node
wrapper) — used in
src/lib/picnic.tsfor product search and cart pushes. Auth works via a storedPICNIC_AUTH_KEYin.env, obtained once viascripts/picnic-login.ts(+scripts/picnic-verify-2fa.tsif Picnic requires 2FA, which it did on first login — can't be completed from a server request, only from that standalone script). - Anthropic SDK — not added yet; planned for Phase 3 (chat-assisted planning).
prisma/schema.prisma Data model: Recipe, Ingredient, RecipeIngredient,
MealPlan, MealPlanItem, List, ListItem
src/lib/db.ts Prisma client singleton
src/lib/picnic.ts Picnic client wrapper (search, cart)
src/lib/shopping-list.ts Aggregates ingredient quantities across recipes,
scaled from each recipe's base portions
src/app/recipes/ Recipe list + create form
src/app/plan/ Meal-plan week view — assign recipes to days
src/app/shopping-list/ Generates from the plan, Picnic matching + push
src/app/lists/ Generic lists (household staples, etc.)
src/lib/week.ts Week math shared by /plan and /shopping-list
scripts/picnic-*.ts One-off spikes: login/2FA, then search+add-to-cart.
Not part of the app; run with `npx tsx scripts/...`
Phases were renumbered 2026-07-23 — Phase 1 shipped thinner than the vision doc assumed, and refinement moved up from a vague Phase 4 into a concrete Phase 2, ahead of the chat feature. See Vision.md for the full note.
- Phase 1 (done): recipes/ingredients, meal planning, shopping-list
generation, ingredient→Picnic product matching (manual confirm, no
auto-accept), and the real basket push. See
docs/phases.mdfor what shipped when this pass (bulk auto-matching, editing items already in the Picnic cart, out-of-stock handling). - Phase 2 (in progress) — Refinement: CRUD completeness (edit/delete
across recipes, lists, weekly plan), correct unit-aware quantity
aggregation, package-size-aware shopping quantities, Picnic match pricing
- images, a "Weekly Plan" rename, and a design-polish pass. See
docs/phases.md.
- images, a "Weekly Plan" rename, and a design-polish pass. See
- Phase 3 — Chat-assisted planning (previously Phase 2): not started. Open question: in-app chat vs. an MCP server exposing this app to external clients — deliberately undecided until Phase 2 is done.
- Phase 4 — Learning & healthier suggestions (previously Phase 3): not started, deferred until there's real usage data. See the vision doc.
npm install
npm run devCopy .env.example to .env and fill in Picnic credentials if you want the
basket integration (not required for recipes/lists).
The landing page shows this week's training load and can have Claude plan
next week's dinners around it — see docs/phases.md.
# 1. Add a Claude API key (console.anthropic.com)
echo 'ANTHROPIC_API_KEY=sk-ant-...' >> .env
# 2. Seed the starter recipe library (idempotent)
npx tsx scripts/seed-recipes.ts
# 3. Restart the dev server, then press "Plan next week with Claude"
npm run devTraining data is a seeded demo week (src/lib/training.ts) so demos never
depend on live OAuth; Strava slots in behind the same interface. Fuelling
targets are computed deterministically in src/lib/fueling.ts — the model
selects meals against them and never does the arithmetic — and every proposed
plan is validated against real recipe ids before it is written
(src/lib/plan-validation.ts).
PICNIC_AUTH_KEY is a full-access bearer session token for a real Picnic
account — not scoped to search/cart, not read-only. Anyone who obtains it
can act as you against Picnic's API directly (no app login, password, or
2FA needed): browse, mutate the cart, and place a real order
(cart.confirmOrder) charged to your account. Treat it as equivalent to an
account password.
- Keep it off any synced storage. iCloud Drive's "Desktop & Documents
Folders" sync does not respect
.gitignore— if this repo ever lives under a synced folder,.envleaves the laptop regardless of git config. Verify sync is off for this project's location (System Settings → Apple ID → iCloud → iCloud Drive), or move the repo out of a synced folder. (Resolved 2026-07-23 — repo moved from~/Documents/Projectsto~/Developer, which iCloud Drive doesn't sync by default. Re-check this if the repo ever moves again.) chmod 600 .env— owner-read/write only, not world-readable. (Already applied; re-apply if the file is ever recreated.)- Never run this app reachable off your laptop.
next dev/next startbind to all interfaces by default — fine onlocalhostonly. Don't expose this over LAN/Tailscale or deploy it anywhere until there's real auth in front of it. This matters more once any RPi/self-hosted deployment is on the table. - No automatable rotation. Getting a fresh key requires re-running the
2FA login flow (
scripts/picnic-login.ts+scripts/picnic-verify-2fa.ts) by hand — there's no way to rotate this on a schedule. Only re-run it if you actually suspect the key leaked. - Real revocation lives on Picnic's side, not in this repo. This API wrapper has no "revoke this key" call. If you ever suspect a leak, log out other sessions or change your Picnic account password from the official app — that's what actually invalidates the old key.
- See
docs/BACKLOG.mdfor planned hardening (moving the key out of plaintext.envinto macOS Keychain).