Build MealWise
YESreplaces $7.99/mosaves $95.88/yrback to the verdict
A one-page recipe generator whose hard problems are refusing to render a half-parsed model response, doing the cost arithmetic itself from prices you maintain, and taking every safety-relevant number from a published chart rather than from generated text.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the application and its tests.
Get it nodejs.org, or your version manager. open ↗
Verify
node --version prints v22 or higher - API keypay per request; a few recipes a week is negligible
Why The recipe itself is one call, and structured output is what makes the costing and safety matching possible at all.
Get it Create a key with your chosen provider and set a spend limit in their console.
Verify
One request returns a response that validates against the schema - have readyfree
Why This is the one part of the output that must not be generated, and copying it yourself with the date is the whole safeguard.
Get it Open the chart published by the authority for your country, copy the figures into the table file, and record the URL and today's date in it. open ↗
Verify
The table file contains the figures, the URL and the copy date, and the build fails without it - have readyfree
Why The cost panel is arithmetic over this table, and with an empty table every recipe is entirely unpriced, which is honest but useless.
Get it Write down the price and unit for the staples in your kitchen from a recent receipt.
Verify
A generated recipe prices most of its ingredients and counts the rest
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- recipes(id, created_at, request_text, model_id, prompt_version, schema_version, raw_response) - ingredients(recipe_id, ordinal, name, quantity, unit, matched_price_key, unit_price_minor, est_cost_minor) - steps(recipe_id, ordinal, text, temp_key, flagged) · temp_key points into the safety table or is null - safety_temps(key, food, celsius, fahrenheit, rest_minutes, source_url, copied_on) · checked into the repository, never generated - pantry_prices(key, name, unit, unit_price_minor, updated_at) · your prices, because the model does not know what a tin costs where you shop - flags(recipe_id, kind, detail) · kind is allergen_match, unconfirmed_temperature or missing_price - There is no field anywhere holding a claim that a recipe is safe for a person, and no field holding a cost the model produced.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
MODEL_API_KEYsecret | required | sk-...redacted | Held in the route handler only. A test asserts it is absent from the client bundle. |
MODEL_ID | required | a text model identifier | Stored with every recipe, because a different model writes different steps. |
SAFETY_TABLE_PATH | required | ./data/safety-temps.json | The checked-in temperature table. A missing file fails the build. |
CURRENCY | required | GBP | Used for every cost figure and for the smallest-unit arithmetic. |
EXPORT_PROMPT_AFTER_SAVES | required | 10 | How many saves before the app asks you to export. Browser storage is not a backup. |
REQUESTS_PER_SESSION | required | 30 | Cap on model calls from one session, so a stuck retry loop is not a bill. |
The build, in order
One request, one schema, no half-parse
A meal request produces a validated structured recipe or a clear failure, never something in between.
Everything downstream needs a number and a unit.
Files
src/lib/schema.tssrc/app/api/recipe/route.tsA partial recipe is worse than no recipe.
Files
src/lib/generate.tstests/parse.test.tsSo an odd recipe can be explained rather than argued about.
Files
src/lib/store.ts
done when · tick each as it passeswatch out- Falling back to rendering the model's prose when the structured response fails to parse gives you a recipe that reads perfectly and has no machine-readable quantities, so every cost is missing and no safety step can be matched, and the user cannot tell the difference. Render nothing on a parse failure: show what came back, offer a retry, and keep the failure visible.
The costs are yours, not the model's
Every figure in the cost panel is arithmetic the application did over prices you entered.
The model does not know what a tin costs where you shop.
Files
src/lib/schema.tsA number a test can check is a number you can argue with.
Files
src/lib/cost.tstests/cost.test.tsA missing price is not a free ingredient.
Files
src/components/CostPanel.tsx
done when · tick each as it passeswatch out- Letting the model return a cost per ingredient gives you a total that changes between two runs of the same shopping list, cannot be reconciled against a receipt, and is presented with the same confidence as the cooking time. Ask for quantities and units only, look the prices up yourself, and make the arithmetic something a test can check to the penny.
- Converting between weight and volume for an ingredient silently assumes a density and produces a price that is wrong by a factor rather than a percentage. Convert only within a measurement family, and leave anything else unpriced with a visible reason.
The safety layer
Cooking temperatures come from the checked-in table and allergen handling never claims safety.
The figures change and a stale table is worse than none.
Files
data/safety-temps.json'Until the juices run clear' is not a measurement.
Files
src/lib/safety.tstests/safety.test.tsA string test is the only way this stays true.
Files
tests/no-safety-claims.test.ts
done when · tick each as it passeswatch out- A generated step saying to cook chicken until the juices run clear is a sentence models write constantly and it is not a temperature; a reader following it has no way to know the difference between that and the step that gives a number. Match the food against your table, show the published figure and rest time beside the step, and flag the vague step rather than letting it stand alone.
- Writing that a recipe is safe for someone with an allergy, in any wording, is a claim about a dish nobody has cooked, produced from a sentence, with no knowledge of cross-contamination in the kitchen it will be made in. The interface reports matches against a list the user typed and says exactly that, and there is no string in the codebase asserting safety.
Saving, exporting and the honest READMEproduct builder
Saved recipes can leave the browser, the key stays server side, and the README states what the numbers are.
Browser storage disappears without warning anybody.
Files
src/lib/export.tssrc/components/ExportPrompt.tsxA key in the bundle is a key on the internet.
Files
src/app/api/recipe/route.tstests/no-key-in-bundle.test.tsThe limitations are the product here, not a footnote.
Files
README.md
done when · tick each as it passeswatch out- Browser storage is deleted when somebody clears site data and can be evicted by the browser itself when the disk is under pressure, so a library somebody built over a year vanishes without an error message. Offer a one-action export, prompt for it after a stated number of saves, and say plainly in the interface and the README that the saved library is not a backup.
- Putting the model key in client code because the fetch is simpler there publishes it to everyone who opens the page, and the bill arrives before the discovery does. Keep every model call behind a route handler, and add a test that fails if the key name appears in the client bundle.
That is the whole plan for MealWise. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Live grocery prices.
- Nutrition figures.
- Voice input and a native application.
- Accounts, quotas and cloud sync.
- native iPhone interaction and voice dictation
- server-side authentication, quota enforcement and abuse protection
- optional cross-device sync
- recipe refinement and regeneration polish
- ongoing AI-quality, food-safety and operational maintenance
- A shopping list that merges several recipes and subtracts what the pantry table says you have
- Recipes you cook often, stored once and re-costed against current prices without another model call
- A second safety table for a different country, selected by a setting rather than by editing the file
Need the files? The project pack on the verdict page hands your agent the whole brief · more home.