GY

Meal App

At home · public repo · Next.js, tRPC, Postgres

An AI meal-planning app for finding recipes, planning the week and making a matching grocery list.

Meal App plan screen

Why it exists

Deciding what to cook, finding recipes, planning the week and making a matching grocery list were separate chores. Every app I tried handled part of that process. My rule is to get from "I have no idea what to cook" to a ready grocery list in under ten minutes. I cut features that don't serve that target.

I built it on my own time, with Claude Code building and Codex reviewing. I'm calling it Meal App for now. I used it to test an interaction I care about at work: AI proposing a structured plan someone adjusts instead of a chat box.

What it does

Recipes come in three ways: paste a recipe URL, describe what you want, or change an existing recipe in conversation. You can ask for a dairy-free version or substitute an ingredient you don't have.

The planner streams meals with its reasoning visible as they arrive. You adjust by talking to it or dragging meals between days, with a ring around rows it changes. It reuses ingredients on purpose, planning one meal to leave leftovers for a later one.

The grocery list combines ingredients across planned meals: "2 cloves garlic" and "1 head garlic" become one line. It tracks staples separately and works offline. Preferences, dietary restrictions, and meal feedback persist across weeks. I model allergies and hard restrictions separately from taste, with narrower code paths for changing them.

The week's plan, with the planner's reasoning under each meal The grocery list, grouped by aisle The recipe library

Onboarding

Onboarding is an interview. You can skip any question, or answer with a tap, typing, or the microphone. In the screenshot, someone types "I love Thai food" while answering a dietary-framework question. The planner catches the preference and shows what it understood.

Before saving anything, the planner plays back what it heard. Allergies have a separate card in a different color because they need different treatment from preferences.

A diet question with tappable chips, a free-text field, and the planner catching a preference in typed text The end of onboarding, where the planner plays back everything it learned

How it's built

It's an installable, mobile-first web app using Next.js, React, tRPC, Drizzle and Postgres on Supabase. I route all business logic through tRPC procedures so a native client can reuse it. Components never call the database directly, and mutations never use server actions.

Model calls return data. Application code validates it against schemas before displaying or saving it.

I verified row-level security against the real database. It protects every table through the public API, and the app's pooled connection bypasses it, so tRPC guards that connection instead.

URL import checks protocols and resolved hosts, rejects private addresses, and repeats validation at every redirect. Production AI logs contain metadata only, with no dietary or health content.

While the planner generates meals, the app shows what's happening. I set the timeout before the hosting platform's deadline so it can display its own failure.

I test procedures, schemas and the AI pipeline, use snapshots to detect prompt changes, and run browser tests against a deterministic mock provider. The screenshots here come from a capture layer that checks rendered pixels against a design rubric. I run accessibility checks on the rendered tree. Real-model evals cover the AI behavior the mock cannot test.

I grade raw model output before validators repair it, so a repair cannot hide a failure. I use no pass-rate threshold because of sampling noise. I rerun a failing safety check and fail the build if it fails again. I commit eval results with the changes that produced them, and an offline check fails the unit run if prompts have changed since those results.

What building it taught me

I learned a model can follow an injected instruction even when the untrusted text stays inside its delimiters. I traced how imported content reaches a recipe title, a grocery item, durable memory, and a conversational call that can delete an allergy record. In evals, an instruction in a free-text field deleted an allergy every time it was tried, and planned a week of pork for a vegetarian. I keep those failing cases committed. The fix belongs in the code that permits the operation.

I also changed session replay to mask everything by default, with explicit exceptions. Replay tooling recorded DOM attributes verbatim, so user content in an aria-label remained readable despite text masking.

Status

Meal App has a feature-complete version in private beta with its first household. I publish the code in the repository, without a license for reuse. I want to add a pantry inferred from purchases and cooking, planning with what's already in the house, a shared grocery list, and recipe capture from photos or shared posts. I'll build native iOS and Android clients when the app needs push, a share extension, or offline capabilities beyond the browser.