Build Crouton
YESreplaces $1.99/mosaves $23.88/yrback to the verdict
A household recipe box whose hard problems are importing from other people's sites without taking their writing, scaling quantities without pretending times and temperatures scale too, and merging a week of recipes into one shopping list when the units disagree.
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 the import and export commands.
Get it nodejs.org, or your version manager. open ↗
Verify
node --version prints v22 or higher - have readyfree
Why Every decision in the cooking view is about wet hands, arm's length and a screen that wants to lock, and none of it is testable on a laptop.
Get it Open the application on that device and add it to the home screen.
Verify
A recipe can be followed end to end on it without touching a keyboard - have readyfree
Why The screen wake lock and installing to the home screen both need a secure origin, and this is where kitchen projects stall on day one.
Get it A local certificate authority for your network, or a hostname with a real certificate pointing at the local address.
Verify
The device opens the application with no certificate warning and the wake lock is granted - have readyfree
Why Import is the feature most likely to break, and testing it by fetching live pages is both flaky and impolite.
Get it Save the pages you actually cook from, including one with no structured data at all.
Verify
The import test suite runs offline against the saved copies - decidefree
Why The shopping list merge is only as good as this list, and an over-eager conversion table is what produces a total nobody believes.
Get it Write down the unit families you will convert within and explicitly refuse cross-family conversions such as volume to weight.
Verify
docs/units.md lists them and the merge tests cover a refusal case
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- recipes(id, title, source_url, source_author, licence_note, servings, total_time_iso, created_at, imported_at) - ingredients(id, recipe_id, position, quantity_low, quantity_high, unit, item, note, original_text, scales_with_servings) · original_text is what was written, the rest is what you parsed - steps(id, recipe_id, position, text, timer_seconds, temperature_c) · a timer belongs to the step that needs it - plans(id, date, meal, recipe_id, servings_wanted) · the week, and what you actually intend to cook - list_items(id, plan_week, item, unit, quantity, from_recipes_json, checked_at, manual) · merged where units agree, kept separate where they do not - timers(id, recipe_id, step_id, started_at, seconds, state) · persisted, so a reload does not lose the bread - Every ingredient keeps the text it arrived as. Parsing is a convenience that can be wrong, and the cook needs to see what the recipe actually said.
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 |
|---|---|---|---|
DATABASE_PATH | required | ./data/recipes.db | Recipes, plans, lists and timers. |
IMAGE_DIR | required | ./data/images | Your own photographs. Imported recipes keep a link rather than a copy of the publisher's image. |
EXPORT_DIR | required | ./recipe-box | Markdown recipes plus images and a manifest. Readable without this application. |
DEFAULT_SERVINGS | required | 2 | What the plan assumes unless a meal says otherwise. |
LIST_WEEK_START | required | monday | When the shopping list resets and checked items clear. |
The build, in order
A recipe that is structured, not a wall of text
Ingredients and steps are rows with their original wording kept beside the parsed values.
The parse is a convenience; the line is the recipe.
Files
src/parse/ingredient.tstests/ingredient-cases.test.tsSalt, a tin and an egg do not multiply by 2.5.
Files
src/parse/scaling.tsThe step is where the cook is looking when they need it.
Files
src/recipes/steps.ts
done when · tick each as it passeswatch out- Storing ingredients as one text blob makes scaling and shopping lists impossible later, and storing only the parsed values makes '1 tin of tomatoes, drained' come back as '1 tin tomatoes' with the instruction that mattered removed. Parse into fields and keep the original line, and show the original whenever the parse is uncertain.
Import what the publisher published
Importing reads the page's embedded structured recipe data and lands in the editor when it cannot.
It is published for exactly this and it is the honest path.
Files
src/import/structured.tstests/import-fixtures.test.tsAttribution costs nothing and settles most of the question.
Files
src/import/source.tsA half-scraped recipe is worse than typing it out.
Files
src/import/fallback.ts
done when · tick each as it passeswatch out- Falling back to pulling text out of the page's markup when the structured data is missing produces a recipe made of advertisement captions and newsletter prompts, and it also means you are taking the article rather than the recipe. Import from the published structured data or not at all, and make the manual path pleasant enough that failing is fine.
Scaling that refuses to lie
Changing the servings scales what scales, leaves alone what does not, and never touches times or temperatures.
Nobody owns a scale that reads three and a half eggs.
Files
src/scale/round.tstests/scale.test.tsThis is the part that ruins the dish rather than the arithmetic.
Files
src/scale/apply.tsThe cook is the one who decides whether the scale is sensible.
Files
src/app/recipe/[id]/page.tsx
done when · tick each as it passeswatch out- Multiplying everything by the scale factor gives you three and a half eggs, two and a half tins, and a cake that is still baked for the time written for one tin, which is the part that actually ruins dinner. Round to what a kitchen can measure, leave the marked ingredients alone, and display times and temperatures untouched with a note that they were written for the original quantity.
Cooking mode and timers that survive
The kitchen view shows one step at a time, keeps the screen awake, and its timers survive a reload.
A counter in memory is one tab switch from gone.
Files
src/timers/store.tstests/timer-reload.test.tsAnd release it, because a screen that never sleeps is a flat battery.
Files
src/cook/wakelock.tsPrecision tapping is not available in this context.
Files
src/app/cook/page.tsx
done when · tick each as it passeswatch out- Running a timer as a counter in a page variable means locking the screen, switching to a message or the browser reclaiming the tab silently loses it, and the first time you find out is the smell. Store the start time and the duration, compute the remaining time on every render, and fire the alarm from the stored values so a reload resumes exactly where it was.
The week, the list, and taking it all with youproduct builder
A plan produces one shopping list with quantities merged where they can be, and everything exports as plain files.
A wrong total once is a list nobody reads again.
Files
src/list/merge.tstests/merge.test.tsTwo honest lines beat one invented number.
Files
src/list/build.tsThe box should survive this application being abandoned.
Files
src/export/folder.tstests/roundtrip.test.tsterminalnpm run export -- --dir=./recipe-box npm run import -- --dir=./recipe-box
done when · tick each as it passeswatch out- Merging '200 g tomatoes' with '1 tin of tomatoes' into '201 tomatoes' is the classic failure here, and once the list is wrong once nobody trusts it again. Merge only within a unit family you can convert, keep everything else as separate lines with the recipe that asked for it, and show the cook both lines rather than inventing a total.
That is the whole plan for Crouton. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- A public or shared library of imported recipes.
- Retailer ordering and product catalogues.
- Per-serving nutrition figures.
- Household accounts with permissions.
- native Apple apps, recipe capture, household sharing, sync, and cooking polish
- frictionless family sync
- retailer and smart-home integrations
- large recipe or product database
- mobile polish
- A printable weekly plan and shopping list, which is the format that actually survives a supermarket
- Notes per cook, recording what you changed and whether it worked, attached to the recipe rather than the step
- A pantry list checked against the shopping list, so the things you already have stop being bought twice
Need the files? The project pack on the verdict page hands your agent the whole brief · more home.