Vibecode Crouton
track this build4 phases, 12 steps, beginner friendly0%The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Crouton, organize recipes, meal plans, timers, and a grocery list locally. The hard boundary is native apple apps, recipe capture, household sharing, sync, and cooking polish, plus sync, integrations, and household adoption.
You are building a lean indie version of Crouton. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Crouton · indie build 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. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A local-first web application installed to the home screen, served on your own network | the device in the kitchen is a phone or a tablet with wet hands | | Storage | SQLite with images on disk, everything exportable as plain files | a recipe box should outlive the software that holds it | | Import | Read the publisher's embedded structured data on a page you opened, one page at a time | the data is published to be read by software, and guessing from markup is both worse and ruder | | Quantities | Parsed into a number, a unit and an item, with the original string kept | '2 to 3 tablespoons, plus more for the pan' has to survive the round trip | | Timers | Attached to steps, running in the background and surviving a reload | a kitchen timer that dies when the screen locks is not a timer | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the application and the import and export commands. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v22 or higher - [ ] **The device you will actually cook with, on your network** · free - 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 - [ ] **A certificate for the local address** · free - 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 - [ ] **Saved copies of a few recipe pages you use** · free - 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 - [ ] **Which unit conversions you will trust** · free - 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 ## Quick start ```sh npm run export -- --dir=./recipe-box npm run import -- --dir=./recipe-box ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - 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 If one of those is essential to you, that is the reason to keep paying for Crouton, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Crouton The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a recipe box that scales, times and shops, and imports without helping itself to somebody's writing to replace Crouton. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Importing other people's recipes is what makes this useful and is also the part that can be done badly. Two things decide the design. First, what you are allowed to keep. The United States Copyright Office states that a mere listing of ingredients is not protected by copyright, and that where a recipe is accompanied by substantial literary expression in the form of an explanation or directions, or appears as part of a collection such as a cookbook, there may be a basis for protection. So import the quantities and the working steps, store the source link and the author on every imported recipe and show them, and do not copy the headnote, the story or the photograph into your box. Other countries word it differently and the cautious version is the same everywhere: cook from it, do not republish it. Second, how you read the page. Recipe publishers embed machine readable structured data precisely so software can read the recipe without guessing. Read that, from a page you opened yourself, one at a time. If it is not there, hand the user an editor rather than picking the markup apart. No bulk crawling, no public index of other people's recipes, no sharing beyond your own household. ### Stack (fixed, do not substitute) - Runtime: A local-first web application installed to the home screen, served on your own network · the device in the kitchen is a phone or a tablet with wet hands - Storage: SQLite with images on disk, everything exportable as plain files · a recipe box should outlive the software that holds it - Import: Read the publisher's embedded structured data on a page you opened, one page at a time · the data is published to be read by software, and guessing from markup is both worse and ruder - Quantities: Parsed into a number, a unit and an item, with the original string kept · '2 to 3 tablespoons, plus more for the pan' has to survive the round trip - Timers: Attached to steps, running in the background and surviving a reload · a kitchen timer that dies when the screen locks is not a timer ### Data model (create this before Phase 1) - 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. ### Phase 1 · A recipe that is structured, not a wall of text Build: Enter a recipe by hand: title, servings, ingredient lines and steps. Parse each ingredient line into quantity, unit and item, handling ranges, fractions and trailing notes, and keep the original line. Mark ingredients that should not be scaled. Attach an optional timer and temperature to any step. Trap: 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. Done when: A set of awkward ingredient lines parses into quantity, unit, item and note without losing any words, tested as a table including ranges, mixed fractions, 'to taste' and 'plus more for the pan'. The original line is stored for every ingredient and displayed when the parse is marked uncertain. Steps carry an optional timer and temperature. A recipe entered by hand round-trips through export and import unchanged. Do not build yet: No import, scaling, cooking mode or shopping list yet. ### Phase 2 · Import what the publisher published Build: Given a URL you opened, read the embedded structured recipe object and map its name, yield, ingredient list, instruction steps and durations, which are published in the standard duration format. Store the source URL and author and show them on the recipe. Copy no prose beyond the steps. When there is no structured data, open the editor with a paste box instead of guessing. Trap: 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. Done when: A page with embedded structured recipe data imports its name, yield, ingredients, steps and durations, tested against saved fixtures from several sites. Durations in the published format are converted correctly, including hours and minutes together, tested. Every imported recipe stores and displays its source URL and author. A page without structured data opens the manual editor with a clear message and imports nothing, tested. Do not build yet: No scaling, cooking mode, plan or shopping list yet. ### Phase 3 · Scaling that refuses to lie Build: Scale quantities by the ratio of wanted to original servings, rounding to a sensible kitchen amount and showing the exact value on demand. Leave ingredients marked unscalable at their original value with a note. Never scale a timer or an oven temperature. Show the original servings and quantities alongside the scaled ones. Trap: 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. Done when: Scaling to 2.5 servings rounds quantities to measurable amounts and shows the exact value on request, tested as a table. Ingredients marked unscalable keep their original quantity and carry a visible note. Timers and temperatures are identical at every scale, asserted in the suite. The original servings and quantities remain visible next to the scaled ones. Do not build yet: No cooking mode, plan or shopping list yet. ### Phase 4 · Cooking mode and timers that survive Build: A large-type cooking view with one step in focus, ingredients for that step listed beside it, and next and previous as big targets. Request a screen wake lock and release it on exit. Timers start from a step, run against stored start times rather than a counter in memory, and several can run at once with a visible label each. Trap: 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. Done when: A timer started and then followed by a full page reload resumes with the correct remaining time, tested. Several timers run at once, each labelled with its step and recipe, and each alarms separately. The screen wake lock is requested on entering cooking mode and released on leaving, verified by observation. Every control in cooking mode is usable one-handed at arm's length, checked by using it while actually cooking. Do not build yet: No weekly plan, shopping list or export yet. ### Phase 5 · The week, the list, and taking it all with you Build: Assign recipes to days with the servings you want. Generate one list, merging identical items whose units convert and listing the rest separately with their recipes named. Group by aisle, allow manual items, and keep checked state. Export the whole box as one folder of markdown recipes plus images and a manifest, and import that folder back. Trap: 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. Done when: Two recipes asking for the same item in convertible units produce one merged line with the correct total, tested. Two recipes asking for the same item in units that do not convert produce two lines, each naming its recipe, tested. Exporting produces a folder of markdown recipes and images that is readable without this application and reimports into an empty database unchanged, tested. Checked items survive a reload and reset when a new week is planned. Do not build yet: Nothing after this. ### Out of scope (and why) - A public index or shared library of imported recipes, which is republishing other people's work and is exactly what rule zero refuses. - Retailer ordering and product catalogues, which need a commercial relationship and a maintained product database rather than a weekend. - Nutrition figures per serving, which need a food composition database, a licence for it, and a portion model, and which would be guesswork otherwise. - Household accounts with permissions, since two people editing the same list needs identity and conflict rules and the paper alternative works fine. ### README must contain - That imports take the quantities and steps plus the source link and author, and deliberately leave the publisher's prose and photograph where they are - That importing reads the page's embedded structured recipe data on a page you opened, and falls back to a manual editor rather than picking apart the markup - That quantities are scaled but times and temperatures never are, and which ingredients are marked unscalable - That timers run from stored start times, so a reload or a locked screen does not lose one - Where the recipes and images live, what the export folder contains, and how to reimport it ===== AGENTS.md ===== # Agent instructions · Crouton indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A local-first web application installed to the home screen, served on your own network, SQLite with images on disk, everything exportable as plain files, Read the publisher's embedded structured data on a page you opened, one page at a time, Parsed into a number, a unit and an item, with the original string kept, Attached to steps, running in the background and surviving a reload. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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. ===== BUILD_PLAN.md ===== # Build plan · Crouton 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A recipe that is structured, not a wall of text Ingredients and steps are rows with their original wording kept beside the parsed values. ### Steps 1. Parse into fields and keep the original line The parse is a convenience; the line is the recipe. Files: `src/parse/ingredient.ts`, `tests/ingredient-cases.test.ts` 2. Mark what must not be scaled Salt, a tin and an egg do not multiply by 2.5. Files: `src/parse/scaling.ts` 3. Attach timers and temperatures to steps The step is where the cook is looking when they need it. Files: `src/recipes/steps.ts` ### Done when - [ ] Awkward ingredient lines parse without losing words - [ ] The original line is stored and shown when uncertain - [ ] Steps carry optional timer and temperature - [ ] Hand-entered recipes round-trip through export ### Watch 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. ## Phase 2 · Import what the publisher published Importing reads the page's embedded structured recipe data and lands in the editor when it cannot. ### Steps 1. Read the structured data, map the fields you need It is published for exactly this and it is the honest path. Files: `src/import/structured.ts`, `tests/import-fixtures.test.ts` 2. Store and display the source and the author Attribution costs nothing and settles most of the question. Files: `src/import/source.ts` 3. Fail into the editor, never into the markup A half-scraped recipe is worse than typing it out. Files: `src/import/fallback.ts` ### Done when - [ ] Structured data imports fields and durations correctly - [ ] Source URL and author are stored and displayed - [ ] No prose beyond the steps is copied - [ ] A page without structured data opens the editor ### Watch 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. ## Phase 3 · Scaling that refuses to lie Changing the servings scales what scales, leaves alone what does not, and never touches times or temperatures. ### Steps 1. Round to something a kitchen can measure Nobody owns a scale that reads three and a half eggs. Files: `src/scale/round.ts`, `tests/scale.test.ts` 2. Leave marked ingredients and all times alone This is the part that ruins the dish rather than the arithmetic. Files: `src/scale/apply.ts` 3. Keep the original visible beside the scaled value The cook is the one who decides whether the scale is sensible. Files: `src/app/recipe/[id]/page.tsx` ### Done when - [ ] Scaled quantities round to measurable amounts - [ ] Unscalable ingredients keep their value with a note - [ ] Timers and temperatures never change with scale - [ ] Original values stay visible ### Watch 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. ## Phase 4 · 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. ### Steps 1. Store timer start and duration, compute the rest A counter in memory is one tab switch from gone. Files: `src/timers/store.ts`, `tests/timer-reload.test.ts` 2. Hold a wake lock only while cooking And release it, because a screen that never sleeps is a flat battery. Files: `src/cook/wakelock.ts` 3. Make the targets big and test them wet Precision tapping is not available in this context. Files: `src/app/cook/page.tsx` ### Done when - [ ] Timers resume correctly after a reload - [ ] Multiple labelled timers run and alarm separately - [ ] The wake lock is held only during cooking - [ ] Controls are usable one-handed ### Watch 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. ## Not in this build - A public or shared library of imported recipes. - Retailer ordering and product catalogues. - Per-serving nutrition figures. - Household accounts with permissions. ## After v1, if you want it - 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 ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Recipes, plans, lists and timers. DATABASE_PATH=./data/recipes.db # Required. Your own photographs. Imported recipes keep a link rather than a copy of the publisher's image. IMAGE_DIR=./data/images # Required. Markdown recipes plus images and a manifest. Readable without this application. EXPORT_DIR=./recipe-box # Required. What the plan assumes unless a meal says otherwise. DEFAULT_SERVINGS=2 # Required. When the shopping list resets and checked items clear. LIST_WEEK_START=monday
You are building a lean indie version of Crouton. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Crouton · indie build 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. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A local-first web application installed to the home screen, served on your own network | the device in the kitchen is a phone or a tablet with wet hands | | Storage | SQLite with images on disk, everything exportable as plain files | a recipe box should outlive the software that holds it | | Import | Read the publisher's embedded structured data on a page you opened, one page at a time | the data is published to be read by software, and guessing from markup is both worse and ruder | | Quantities | Parsed into a number, a unit and an item, with the original string kept | '2 to 3 tablespoons, plus more for the pan' has to survive the round trip | | Timers | Attached to steps, running in the background and surviving a reload | a kitchen timer that dies when the screen locks is not a timer | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the application and the import and export commands. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v22 or higher - [ ] **The device you will actually cook with, on your network** · free - 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 - [ ] **A certificate for the local address** · free - 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 - [ ] **Saved copies of a few recipe pages you use** · free - 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 - [ ] **Which unit conversions you will trust** · free - 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 ## Quick start ```sh npm run export -- --dir=./recipe-box npm run import -- --dir=./recipe-box ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - 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 If one of those is essential to you, that is the reason to keep paying for Crouton, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Crouton The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a recipe box that scales, times and shops, and imports without helping itself to somebody's writing to replace Crouton. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Importing other people's recipes is what makes this useful and is also the part that can be done badly. Two things decide the design. First, what you are allowed to keep. The United States Copyright Office states that a mere listing of ingredients is not protected by copyright, and that where a recipe is accompanied by substantial literary expression in the form of an explanation or directions, or appears as part of a collection such as a cookbook, there may be a basis for protection. So import the quantities and the working steps, store the source link and the author on every imported recipe and show them, and do not copy the headnote, the story or the photograph into your box. Other countries word it differently and the cautious version is the same everywhere: cook from it, do not republish it. Second, how you read the page. Recipe publishers embed machine readable structured data precisely so software can read the recipe without guessing. Read that, from a page you opened yourself, one at a time. If it is not there, hand the user an editor rather than picking the markup apart. No bulk crawling, no public index of other people's recipes, no sharing beyond your own household. ### Stack (fixed, do not substitute) - Runtime: A local-first web application installed to the home screen, served on your own network · the device in the kitchen is a phone or a tablet with wet hands - Storage: SQLite with images on disk, everything exportable as plain files · a recipe box should outlive the software that holds it - Import: Read the publisher's embedded structured data on a page you opened, one page at a time · the data is published to be read by software, and guessing from markup is both worse and ruder - Quantities: Parsed into a number, a unit and an item, with the original string kept · '2 to 3 tablespoons, plus more for the pan' has to survive the round trip - Timers: Attached to steps, running in the background and surviving a reload · a kitchen timer that dies when the screen locks is not a timer ### Data model (create this before Phase 1) - 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. ### Phase 1 · A recipe that is structured, not a wall of text Build: Enter a recipe by hand: title, servings, ingredient lines and steps. Parse each ingredient line into quantity, unit and item, handling ranges, fractions and trailing notes, and keep the original line. Mark ingredients that should not be scaled. Attach an optional timer and temperature to any step. Trap: 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. Done when: A set of awkward ingredient lines parses into quantity, unit, item and note without losing any words, tested as a table including ranges, mixed fractions, 'to taste' and 'plus more for the pan'. The original line is stored for every ingredient and displayed when the parse is marked uncertain. Steps carry an optional timer and temperature. A recipe entered by hand round-trips through export and import unchanged. Do not build yet: No import, scaling, cooking mode or shopping list yet. ### Phase 2 · Import what the publisher published Build: Given a URL you opened, read the embedded structured recipe object and map its name, yield, ingredient list, instruction steps and durations, which are published in the standard duration format. Store the source URL and author and show them on the recipe. Copy no prose beyond the steps. When there is no structured data, open the editor with a paste box instead of guessing. Trap: 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. Done when: A page with embedded structured recipe data imports its name, yield, ingredients, steps and durations, tested against saved fixtures from several sites. Durations in the published format are converted correctly, including hours and minutes together, tested. Every imported recipe stores and displays its source URL and author. A page without structured data opens the manual editor with a clear message and imports nothing, tested. Do not build yet: No scaling, cooking mode, plan or shopping list yet. ### Phase 3 · Scaling that refuses to lie Build: Scale quantities by the ratio of wanted to original servings, rounding to a sensible kitchen amount and showing the exact value on demand. Leave ingredients marked unscalable at their original value with a note. Never scale a timer or an oven temperature. Show the original servings and quantities alongside the scaled ones. Trap: 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. Done when: Scaling to 2.5 servings rounds quantities to measurable amounts and shows the exact value on request, tested as a table. Ingredients marked unscalable keep their original quantity and carry a visible note. Timers and temperatures are identical at every scale, asserted in the suite. The original servings and quantities remain visible next to the scaled ones. Do not build yet: No cooking mode, plan or shopping list yet. ### Phase 4 · Cooking mode and timers that survive Build: A large-type cooking view with one step in focus, ingredients for that step listed beside it, and next and previous as big targets. Request a screen wake lock and release it on exit. Timers start from a step, run against stored start times rather than a counter in memory, and several can run at once with a visible label each. Trap: 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. Done when: A timer started and then followed by a full page reload resumes with the correct remaining time, tested. Several timers run at once, each labelled with its step and recipe, and each alarms separately. The screen wake lock is requested on entering cooking mode and released on leaving, verified by observation. Every control in cooking mode is usable one-handed at arm's length, checked by using it while actually cooking. Do not build yet: No weekly plan, shopping list or export yet. ### Phase 5 · The week, the list, and taking it all with you Build: Assign recipes to days with the servings you want. Generate one list, merging identical items whose units convert and listing the rest separately with their recipes named. Group by aisle, allow manual items, and keep checked state. Export the whole box as one folder of markdown recipes plus images and a manifest, and import that folder back. Trap: 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. Done when: Two recipes asking for the same item in convertible units produce one merged line with the correct total, tested. Two recipes asking for the same item in units that do not convert produce two lines, each naming its recipe, tested. Exporting produces a folder of markdown recipes and images that is readable without this application and reimports into an empty database unchanged, tested. Checked items survive a reload and reset when a new week is planned. Do not build yet: Nothing after this. ### Out of scope (and why) - A public index or shared library of imported recipes, which is republishing other people's work and is exactly what rule zero refuses. - Retailer ordering and product catalogues, which need a commercial relationship and a maintained product database rather than a weekend. - Nutrition figures per serving, which need a food composition database, a licence for it, and a portion model, and which would be guesswork otherwise. - Household accounts with permissions, since two people editing the same list needs identity and conflict rules and the paper alternative works fine. ### README must contain - That imports take the quantities and steps plus the source link and author, and deliberately leave the publisher's prose and photograph where they are - That importing reads the page's embedded structured recipe data on a page you opened, and falls back to a manual editor rather than picking apart the markup - That quantities are scaled but times and temperatures never are, and which ingredients are marked unscalable - That timers run from stored start times, so a reload or a locked screen does not lose one - Where the recipes and images live, what the export folder contains, and how to reimport it ===== AGENTS.md ===== # Agent instructions · Crouton indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A local-first web application installed to the home screen, served on your own network, SQLite with images on disk, everything exportable as plain files, Read the publisher's embedded structured data on a page you opened, one page at a time, Parsed into a number, a unit and an item, with the original string kept, Attached to steps, running in the background and surviving a reload. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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. ===== BUILD_PLAN.md ===== # Build plan · Crouton 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A recipe that is structured, not a wall of text Ingredients and steps are rows with their original wording kept beside the parsed values. ### Steps 1. Parse into fields and keep the original line The parse is a convenience; the line is the recipe. Files: `src/parse/ingredient.ts`, `tests/ingredient-cases.test.ts` 2. Mark what must not be scaled Salt, a tin and an egg do not multiply by 2.5. Files: `src/parse/scaling.ts` 3. Attach timers and temperatures to steps The step is where the cook is looking when they need it. Files: `src/recipes/steps.ts` ### Done when - [ ] Awkward ingredient lines parse without losing words - [ ] The original line is stored and shown when uncertain - [ ] Steps carry optional timer and temperature - [ ] Hand-entered recipes round-trip through export ### Watch 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. ## Phase 2 · Import what the publisher published Importing reads the page's embedded structured recipe data and lands in the editor when it cannot. ### Steps 1. Read the structured data, map the fields you need It is published for exactly this and it is the honest path. Files: `src/import/structured.ts`, `tests/import-fixtures.test.ts` 2. Store and display the source and the author Attribution costs nothing and settles most of the question. Files: `src/import/source.ts` 3. Fail into the editor, never into the markup A half-scraped recipe is worse than typing it out. Files: `src/import/fallback.ts` ### Done when - [ ] Structured data imports fields and durations correctly - [ ] Source URL and author are stored and displayed - [ ] No prose beyond the steps is copied - [ ] A page without structured data opens the editor ### Watch 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. ## Phase 3 · Scaling that refuses to lie Changing the servings scales what scales, leaves alone what does not, and never touches times or temperatures. ### Steps 1. Round to something a kitchen can measure Nobody owns a scale that reads three and a half eggs. Files: `src/scale/round.ts`, `tests/scale.test.ts` 2. Leave marked ingredients and all times alone This is the part that ruins the dish rather than the arithmetic. Files: `src/scale/apply.ts` 3. Keep the original visible beside the scaled value The cook is the one who decides whether the scale is sensible. Files: `src/app/recipe/[id]/page.tsx` ### Done when - [ ] Scaled quantities round to measurable amounts - [ ] Unscalable ingredients keep their value with a note - [ ] Timers and temperatures never change with scale - [ ] Original values stay visible ### Watch 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. ## Phase 4 · 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. ### Steps 1. Store timer start and duration, compute the rest A counter in memory is one tab switch from gone. Files: `src/timers/store.ts`, `tests/timer-reload.test.ts` 2. Hold a wake lock only while cooking And release it, because a screen that never sleeps is a flat battery. Files: `src/cook/wakelock.ts` 3. Make the targets big and test them wet Precision tapping is not available in this context. Files: `src/app/cook/page.tsx` ### Done when - [ ] Timers resume correctly after a reload - [ ] Multiple labelled timers run and alarm separately - [ ] The wake lock is held only during cooking - [ ] Controls are usable one-handed ### Watch 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. ## Not in this build - A public or shared library of imported recipes. - Retailer ordering and product catalogues. - Per-serving nutrition figures. - Household accounts with permissions. ## After v1, if you want it - 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 ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Recipes, plans, lists and timers. DATABASE_PATH=./data/recipes.db # Required. Your own photographs. Imported recipes keep a link rather than a copy of the publisher's image. IMAGE_DIR=./data/images # Required. Markdown recipes plus images and a manifest. Readable without this application. EXPORT_DIR=./recipe-box # Required. What the plan assumes unless a meal says otherwise. DEFAULT_SERVINGS=2 # Required. When the shopping list resets and checked items clear. LIST_WEEK_START=monday
You are building a production product version of Crouton. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Crouton · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Crouton, organize recipes, meal plans, timers, and a grocery list locally. The hard boundary is native apple apps, recipe capture, household sharing, sync, and cooking polish, plus sync, integrations, and household adoption. ## Product outcome A recipe box that imports honestly from what publishers made machine readable, scales quantities without touching times, keeps a timer through a locked screen, and merges a week into a shopping list you can trust. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - modern browser - local or self-hosted storage - optional email notifications ## Explicit non-goals for v1 - 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 ## Success criteria - Awkward ingredient lines are confirmed to parse without losing words - Import is confirmed to take no prose beyond the steps and to store the source - Times and temperatures are confirmed never to change with scale - A timer is confirmed to resume correctly after a page reload - Cross-family unit conversions are confirmed refused in the shopping list ===== BRIEF.md ===== # Build brief · Crouton The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a recipe box that scales, times and shops, and imports without helping itself to somebody's writing to replace Crouton. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Importing other people's recipes is what makes this useful and is also the part that can be done badly. Two things decide the design. First, what you are allowed to keep. The United States Copyright Office states that a mere listing of ingredients is not protected by copyright, and that where a recipe is accompanied by substantial literary expression in the form of an explanation or directions, or appears as part of a collection such as a cookbook, there may be a basis for protection. So import the quantities and the working steps, store the source link and the author on every imported recipe and show them, and do not copy the headnote, the story or the photograph into your box. Other countries word it differently and the cautious version is the same everywhere: cook from it, do not republish it. Second, how you read the page. Recipe publishers embed machine readable structured data precisely so software can read the recipe without guessing. Read that, from a page you opened yourself, one at a time. If it is not there, hand the user an editor rather than picking the markup apart. No bulk crawling, no public index of other people's recipes, no sharing beyond your own household. ### Stack (fixed, do not substitute) - Runtime: A local-first web application installed to the home screen, served on your own network · the device in the kitchen is a phone or a tablet with wet hands - Storage: SQLite with images on disk, everything exportable as plain files · a recipe box should outlive the software that holds it - Import: Read the publisher's embedded structured data on a page you opened, one page at a time · the data is published to be read by software, and guessing from markup is both worse and ruder - Quantities: Parsed into a number, a unit and an item, with the original string kept · '2 to 3 tablespoons, plus more for the pan' has to survive the round trip - Timers: Attached to steps, running in the background and surviving a reload · a kitchen timer that dies when the screen locks is not a timer ### Data model (create this before Phase 1) - 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. ### Phase 1 · A recipe that is structured, not a wall of text Build: Enter a recipe by hand: title, servings, ingredient lines and steps. Parse each ingredient line into quantity, unit and item, handling ranges, fractions and trailing notes, and keep the original line. Mark ingredients that should not be scaled. Attach an optional timer and temperature to any step. Trap: 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. Done when: A set of awkward ingredient lines parses into quantity, unit, item and note without losing any words, tested as a table including ranges, mixed fractions, 'to taste' and 'plus more for the pan'. The original line is stored for every ingredient and displayed when the parse is marked uncertain. Steps carry an optional timer and temperature. A recipe entered by hand round-trips through export and import unchanged. Do not build yet: No import, scaling, cooking mode or shopping list yet. ### Phase 2 · Import what the publisher published Build: Given a URL you opened, read the embedded structured recipe object and map its name, yield, ingredient list, instruction steps and durations, which are published in the standard duration format. Store the source URL and author and show them on the recipe. Copy no prose beyond the steps. When there is no structured data, open the editor with a paste box instead of guessing. Trap: 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. Done when: A page with embedded structured recipe data imports its name, yield, ingredients, steps and durations, tested against saved fixtures from several sites. Durations in the published format are converted correctly, including hours and minutes together, tested. Every imported recipe stores and displays its source URL and author. A page without structured data opens the manual editor with a clear message and imports nothing, tested. Do not build yet: No scaling, cooking mode, plan or shopping list yet. ### Phase 3 · Scaling that refuses to lie Build: Scale quantities by the ratio of wanted to original servings, rounding to a sensible kitchen amount and showing the exact value on demand. Leave ingredients marked unscalable at their original value with a note. Never scale a timer or an oven temperature. Show the original servings and quantities alongside the scaled ones. Trap: 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. Done when: Scaling to 2.5 servings rounds quantities to measurable amounts and shows the exact value on request, tested as a table. Ingredients marked unscalable keep their original quantity and carry a visible note. Timers and temperatures are identical at every scale, asserted in the suite. The original servings and quantities remain visible next to the scaled ones. Do not build yet: No cooking mode, plan or shopping list yet. ### Phase 4 · Cooking mode and timers that survive Build: A large-type cooking view with one step in focus, ingredients for that step listed beside it, and next and previous as big targets. Request a screen wake lock and release it on exit. Timers start from a step, run against stored start times rather than a counter in memory, and several can run at once with a visible label each. Trap: 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. Done when: A timer started and then followed by a full page reload resumes with the correct remaining time, tested. Several timers run at once, each labelled with its step and recipe, and each alarms separately. The screen wake lock is requested on entering cooking mode and released on leaving, verified by observation. Every control in cooking mode is usable one-handed at arm's length, checked by using it while actually cooking. Do not build yet: No weekly plan, shopping list or export yet. ### Phase 5 · The week, the list, and taking it all with you Build: Assign recipes to days with the servings you want. Generate one list, merging identical items whose units convert and listing the rest separately with their recipes named. Group by aisle, allow manual items, and keep checked state. Export the whole box as one folder of markdown recipes plus images and a manifest, and import that folder back. Trap: 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. Done when: Two recipes asking for the same item in convertible units produce one merged line with the correct total, tested. Two recipes asking for the same item in units that do not convert produce two lines, each naming its recipe, tested. Exporting produces a folder of markdown recipes and images that is readable without this application and reimports into an empty database unchanged, tested. Checked items survive a reload and reset when a new week is planned. Do not build yet: Nothing after this. ### Out of scope (and why) - A public index or shared library of imported recipes, which is republishing other people's work and is exactly what rule zero refuses. - Retailer ordering and product catalogues, which need a commercial relationship and a maintained product database rather than a weekend. - Nutrition figures per serving, which need a food composition database, a licence for it, and a portion model, and which would be guesswork otherwise. - Household accounts with permissions, since two people editing the same list needs identity and conflict rules and the paper alternative works fine. ### README must contain - That imports take the quantities and steps plus the source link and author, and deliberately leave the publisher's prose and photograph where they are - That importing reads the page's embedded structured recipe data on a page you opened, and falls back to a manual editor rather than picking apart the markup - That quantities are scaled but times and temperatures never are, and which ingredients are marked unscalable - That timers run from stored start times, so a reload or a locked screen does not lose one - Where the recipes and images live, what the export folder contains, and how to reimport it ===== ARCHITECTURE.md ===== # Architecture · Crouton ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A local-first web application installed to the home screen, served on your own network | the device in the kitchen is a phone or a tablet with wet hands | | Storage | SQLite with images on disk, everything exportable as plain files | a recipe box should outlive the software that holds it | | Import | Read the publisher's embedded structured data on a page you opened, one page at a time | the data is published to be read by software, and guessing from markup is both worse and ruder | | Quantities | Parsed into a number, a unit and an item, with the original string kept | '2 to 3 tablespoons, plus more for the pan' has to survive the round trip | | Timers | Attached to steps, running in the background and surviving a reload | a kitchen timer that dies when the screen locks is not a timer | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Recipes | Parsed ingredients with original text and steps. | Any store, provided the original line survives. | | Import | Structured data reading and attribution. | Not swappable for scraping the markup. | | Scaling | Rounding and the unscalable rule. | Any rounding, provided times and temperatures never scale. | | Cooking | Step view, wake lock and persisted timers. | Any interface, provided timers run from stored times. | | List | Plan merging inside unit families. | Any merge, provided cross-family conversions are refused. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_PATH` · required · Recipes, plans, lists and timers. - `IMAGE_DIR` · required · Your own photographs. Imported recipes keep a link rather than a copy of the publisher's image. - `EXPORT_DIR` · required · Markdown recipes plus images and a manifest. Readable without this application. - `DEFAULT_SERVINGS` · required · What the plan assumes unless a meal says otherwise. - `LIST_WEEK_START` · required · When the shopping list resets and checked items clear. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Crouton product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A local-first web application installed to the home screen, served on your own network, SQLite with images on disk, everything exportable as plain files, Read the publisher's embedded structured data on a page you opened, one page at a time, Parsed into a number, a unit and an item, with the original string kept, Attached to steps, running in the background and surviving a reload. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - 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. - 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. - 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. - 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. - 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. ===== MILESTONES.md ===== # Delivery milestones · Crouton Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A recipe that is structured, not a wall of text Ingredients and steps are rows with their original wording kept beside the parsed values. ### Steps 1. Parse into fields and keep the original line The parse is a convenience; the line is the recipe. Files: `src/parse/ingredient.ts`, `tests/ingredient-cases.test.ts` 2. Mark what must not be scaled Salt, a tin and an egg do not multiply by 2.5. Files: `src/parse/scaling.ts` 3. Attach timers and temperatures to steps The step is where the cook is looking when they need it. Files: `src/recipes/steps.ts` ### Done when - [ ] Awkward ingredient lines parse without losing words - [ ] The original line is stored and shown when uncertain - [ ] Steps carry optional timer and temperature - [ ] Hand-entered recipes round-trip through export ### Watch 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. ## M2 · Import what the publisher published Importing reads the page's embedded structured recipe data and lands in the editor when it cannot. ### Steps 1. Read the structured data, map the fields you need It is published for exactly this and it is the honest path. Files: `src/import/structured.ts`, `tests/import-fixtures.test.ts` 2. Store and display the source and the author Attribution costs nothing and settles most of the question. Files: `src/import/source.ts` 3. Fail into the editor, never into the markup A half-scraped recipe is worse than typing it out. Files: `src/import/fallback.ts` ### Done when - [ ] Structured data imports fields and durations correctly - [ ] Source URL and author are stored and displayed - [ ] No prose beyond the steps is copied - [ ] A page without structured data opens the editor ### Watch 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. ## M3 · Scaling that refuses to lie Changing the servings scales what scales, leaves alone what does not, and never touches times or temperatures. ### Steps 1. Round to something a kitchen can measure Nobody owns a scale that reads three and a half eggs. Files: `src/scale/round.ts`, `tests/scale.test.ts` 2. Leave marked ingredients and all times alone This is the part that ruins the dish rather than the arithmetic. Files: `src/scale/apply.ts` 3. Keep the original visible beside the scaled value The cook is the one who decides whether the scale is sensible. Files: `src/app/recipe/[id]/page.tsx` ### Done when - [ ] Scaled quantities round to measurable amounts - [ ] Unscalable ingredients keep their value with a note - [ ] Timers and temperatures never change with scale - [ ] Original values stay visible ### Watch 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. ## M4 · 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. ### Steps 1. Store timer start and duration, compute the rest A counter in memory is one tab switch from gone. Files: `src/timers/store.ts`, `tests/timer-reload.test.ts` 2. Hold a wake lock only while cooking And release it, because a screen that never sleeps is a flat battery. Files: `src/cook/wakelock.ts` 3. Make the targets big and test them wet Precision tapping is not available in this context. Files: `src/app/cook/page.tsx` ### Done when - [ ] Timers resume correctly after a reload - [ ] Multiple labelled timers run and alarm separately - [ ] The wake lock is held only during cooking - [ ] Controls are usable one-handed ### Watch 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. ## M5 · The week, the list, and taking it all with you (production only) A plan produces one shopping list with quantities merged where they can be, and everything exports as plain files. ### Steps 1. Merge only inside a convertible unit family A wrong total once is a list nobody reads again. Files: `src/list/merge.ts`, `tests/merge.test.ts` 2. Keep unmergeable lines separate and attributed Two honest lines beat one invented number. Files: `src/list/build.ts` 3. Export as markdown files and images, then reimport The box should survive this application being abandoned. Files: `src/export/folder.ts`, `tests/roundtrip.test.ts` ```sh npm run export -- --dir=./recipe-box npm run import -- --dir=./recipe-box ``` ### Done when - [ ] Convertible units merge into one correct line - [ ] Unconvertible units stay separate and attributed - [ ] Export is readable without this application - [ ] Checked state survives a reload ### Watch 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. ===== OPERATIONS.md ===== # Operations · Crouton ## Backup The database and the image directory together, plus a periodic run of the folder export, which is the copy that will still be readable in ten years when this application is not. ## Restore Restore the database and images, or reimport the exported folder into an empty database, and confirm the recipe count and that one imported recipe still shows its source and author. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a household application the checks live in the suite: the ingredient parse table, the duration conversions, timers resuming after a reload, and the merge refusing cross-family conversions. Watch the image directory, which grows faster than anything else here. ## Incident checklist The realistic failures are a lost timer and a wrong shopping total, and both are recoverable but corrosive: people stop trusting the tool. Keep the parse cases and the merge cases as tests you add to whenever something comes out wrong in the kitchen. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Awkward ingredient lines are confirmed to parse without losing words - [ ] Import is confirmed to take no prose beyond the steps and to store the source - [ ] Times and temperatures are confirmed never to change with scale - [ ] A timer is confirmed to resume correctly after a page reload - [ ] Cross-family unit conversions are confirmed refused in the shopping list ## Launch constraint Do not market omitted Crouton capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Recipes, plans, lists and timers. DATABASE_PATH=./data/recipes.db # Required. Your own photographs. Imported recipes keep a link rather than a copy of the publisher's image. IMAGE_DIR=./data/images # Required. Markdown recipes plus images and a manifest. Readable without this application. EXPORT_DIR=./recipe-box # Required. What the plan assumes unless a meal says otherwise. DEFAULT_SERVINGS=2 # Required. When the shopping list resets and checked items clear. LIST_WEEK_START=monday
# Crouton · indie build 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. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A local-first web application installed to the home screen, served on your own network | the device in the kitchen is a phone or a tablet with wet hands | | Storage | SQLite with images on disk, everything exportable as plain files | a recipe box should outlive the software that holds it | | Import | Read the publisher's embedded structured data on a page you opened, one page at a time | the data is published to be read by software, and guessing from markup is both worse and ruder | | Quantities | Parsed into a number, a unit and an item, with the original string kept | '2 to 3 tablespoons, plus more for the pan' has to survive the round trip | | Timers | Attached to steps, running in the background and surviving a reload | a kitchen timer that dies when the screen locks is not a timer | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the application and the import and export commands. - Get it: nodejs.org, or your version manager. - Verify: node --version prints v22 or higher - [ ] **The device you will actually cook with, on your network** · free - 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 - [ ] **A certificate for the local address** · free - 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 - [ ] **Saved copies of a few recipe pages you use** · free - 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 - [ ] **Which unit conversions you will trust** · free - 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 ## Quick start ```sh npm run export -- --dir=./recipe-box npm run import -- --dir=./recipe-box ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - 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 If one of those is essential to you, that is the reason to keep paying for Crouton, and the README should say so rather than pretend.
# Build brief · Crouton The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a recipe box that scales, times and shops, and imports without helping itself to somebody's writing to replace Crouton. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Importing other people's recipes is what makes this useful and is also the part that can be done badly. Two things decide the design. First, what you are allowed to keep. The United States Copyright Office states that a mere listing of ingredients is not protected by copyright, and that where a recipe is accompanied by substantial literary expression in the form of an explanation or directions, or appears as part of a collection such as a cookbook, there may be a basis for protection. So import the quantities and the working steps, store the source link and the author on every imported recipe and show them, and do not copy the headnote, the story or the photograph into your box. Other countries word it differently and the cautious version is the same everywhere: cook from it, do not republish it. Second, how you read the page. Recipe publishers embed machine readable structured data precisely so software can read the recipe without guessing. Read that, from a page you opened yourself, one at a time. If it is not there, hand the user an editor rather than picking the markup apart. No bulk crawling, no public index of other people's recipes, no sharing beyond your own household. ### Stack (fixed, do not substitute) - Runtime: A local-first web application installed to the home screen, served on your own network · the device in the kitchen is a phone or a tablet with wet hands - Storage: SQLite with images on disk, everything exportable as plain files · a recipe box should outlive the software that holds it - Import: Read the publisher's embedded structured data on a page you opened, one page at a time · the data is published to be read by software, and guessing from markup is both worse and ruder - Quantities: Parsed into a number, a unit and an item, with the original string kept · '2 to 3 tablespoons, plus more for the pan' has to survive the round trip - Timers: Attached to steps, running in the background and surviving a reload · a kitchen timer that dies when the screen locks is not a timer ### Data model (create this before Phase 1) - 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. ### Phase 1 · A recipe that is structured, not a wall of text Build: Enter a recipe by hand: title, servings, ingredient lines and steps. Parse each ingredient line into quantity, unit and item, handling ranges, fractions and trailing notes, and keep the original line. Mark ingredients that should not be scaled. Attach an optional timer and temperature to any step. Trap: 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. Done when: A set of awkward ingredient lines parses into quantity, unit, item and note without losing any words, tested as a table including ranges, mixed fractions, 'to taste' and 'plus more for the pan'. The original line is stored for every ingredient and displayed when the parse is marked uncertain. Steps carry an optional timer and temperature. A recipe entered by hand round-trips through export and import unchanged. Do not build yet: No import, scaling, cooking mode or shopping list yet. ### Phase 2 · Import what the publisher published Build: Given a URL you opened, read the embedded structured recipe object and map its name, yield, ingredient list, instruction steps and durations, which are published in the standard duration format. Store the source URL and author and show them on the recipe. Copy no prose beyond the steps. When there is no structured data, open the editor with a paste box instead of guessing. Trap: 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. Done when: A page with embedded structured recipe data imports its name, yield, ingredients, steps and durations, tested against saved fixtures from several sites. Durations in the published format are converted correctly, including hours and minutes together, tested. Every imported recipe stores and displays its source URL and author. A page without structured data opens the manual editor with a clear message and imports nothing, tested. Do not build yet: No scaling, cooking mode, plan or shopping list yet. ### Phase 3 · Scaling that refuses to lie Build: Scale quantities by the ratio of wanted to original servings, rounding to a sensible kitchen amount and showing the exact value on demand. Leave ingredients marked unscalable at their original value with a note. Never scale a timer or an oven temperature. Show the original servings and quantities alongside the scaled ones. Trap: 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. Done when: Scaling to 2.5 servings rounds quantities to measurable amounts and shows the exact value on request, tested as a table. Ingredients marked unscalable keep their original quantity and carry a visible note. Timers and temperatures are identical at every scale, asserted in the suite. The original servings and quantities remain visible next to the scaled ones. Do not build yet: No cooking mode, plan or shopping list yet. ### Phase 4 · Cooking mode and timers that survive Build: A large-type cooking view with one step in focus, ingredients for that step listed beside it, and next and previous as big targets. Request a screen wake lock and release it on exit. Timers start from a step, run against stored start times rather than a counter in memory, and several can run at once with a visible label each. Trap: 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. Done when: A timer started and then followed by a full page reload resumes with the correct remaining time, tested. Several timers run at once, each labelled with its step and recipe, and each alarms separately. The screen wake lock is requested on entering cooking mode and released on leaving, verified by observation. Every control in cooking mode is usable one-handed at arm's length, checked by using it while actually cooking. Do not build yet: No weekly plan, shopping list or export yet. ### Phase 5 · The week, the list, and taking it all with you Build: Assign recipes to days with the servings you want. Generate one list, merging identical items whose units convert and listing the rest separately with their recipes named. Group by aisle, allow manual items, and keep checked state. Export the whole box as one folder of markdown recipes plus images and a manifest, and import that folder back. Trap: 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. Done when: Two recipes asking for the same item in convertible units produce one merged line with the correct total, tested. Two recipes asking for the same item in units that do not convert produce two lines, each naming its recipe, tested. Exporting produces a folder of markdown recipes and images that is readable without this application and reimports into an empty database unchanged, tested. Checked items survive a reload and reset when a new week is planned. Do not build yet: Nothing after this. ### Out of scope (and why) - A public index or shared library of imported recipes, which is republishing other people's work and is exactly what rule zero refuses. - Retailer ordering and product catalogues, which need a commercial relationship and a maintained product database rather than a weekend. - Nutrition figures per serving, which need a food composition database, a licence for it, and a portion model, and which would be guesswork otherwise. - Household accounts with permissions, since two people editing the same list needs identity and conflict rules and the paper alternative works fine. ### README must contain - That imports take the quantities and steps plus the source link and author, and deliberately leave the publisher's prose and photograph where they are - That importing reads the page's embedded structured recipe data on a page you opened, and falls back to a manual editor rather than picking apart the markup - That quantities are scaled but times and temperatures never are, and which ingredients are marked unscalable - That timers run from stored start times, so a reload or a locked screen does not lose one - Where the recipes and images live, what the export folder contains, and how to reimport it
# Agent instructions · Crouton indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A local-first web application installed to the home screen, served on your own network, SQLite with images on disk, everything exportable as plain files, Read the publisher's embedded structured data on a page you opened, one page at a time, Parsed into a number, a unit and an item, with the original string kept, Attached to steps, running in the background and surviving a reload. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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.
# Build plan · Crouton 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A recipe that is structured, not a wall of text Ingredients and steps are rows with their original wording kept beside the parsed values. ### Steps 1. Parse into fields and keep the original line The parse is a convenience; the line is the recipe. Files: `src/parse/ingredient.ts`, `tests/ingredient-cases.test.ts` 2. Mark what must not be scaled Salt, a tin and an egg do not multiply by 2.5. Files: `src/parse/scaling.ts` 3. Attach timers and temperatures to steps The step is where the cook is looking when they need it. Files: `src/recipes/steps.ts` ### Done when - [ ] Awkward ingredient lines parse without losing words - [ ] The original line is stored and shown when uncertain - [ ] Steps carry optional timer and temperature - [ ] Hand-entered recipes round-trip through export ### Watch 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. ## Phase 2 · Import what the publisher published Importing reads the page's embedded structured recipe data and lands in the editor when it cannot. ### Steps 1. Read the structured data, map the fields you need It is published for exactly this and it is the honest path. Files: `src/import/structured.ts`, `tests/import-fixtures.test.ts` 2. Store and display the source and the author Attribution costs nothing and settles most of the question. Files: `src/import/source.ts` 3. Fail into the editor, never into the markup A half-scraped recipe is worse than typing it out. Files: `src/import/fallback.ts` ### Done when - [ ] Structured data imports fields and durations correctly - [ ] Source URL and author are stored and displayed - [ ] No prose beyond the steps is copied - [ ] A page without structured data opens the editor ### Watch 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. ## Phase 3 · Scaling that refuses to lie Changing the servings scales what scales, leaves alone what does not, and never touches times or temperatures. ### Steps 1. Round to something a kitchen can measure Nobody owns a scale that reads three and a half eggs. Files: `src/scale/round.ts`, `tests/scale.test.ts` 2. Leave marked ingredients and all times alone This is the part that ruins the dish rather than the arithmetic. Files: `src/scale/apply.ts` 3. Keep the original visible beside the scaled value The cook is the one who decides whether the scale is sensible. Files: `src/app/recipe/[id]/page.tsx` ### Done when - [ ] Scaled quantities round to measurable amounts - [ ] Unscalable ingredients keep their value with a note - [ ] Timers and temperatures never change with scale - [ ] Original values stay visible ### Watch 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. ## Phase 4 · 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. ### Steps 1. Store timer start and duration, compute the rest A counter in memory is one tab switch from gone. Files: `src/timers/store.ts`, `tests/timer-reload.test.ts` 2. Hold a wake lock only while cooking And release it, because a screen that never sleeps is a flat battery. Files: `src/cook/wakelock.ts` 3. Make the targets big and test them wet Precision tapping is not available in this context. Files: `src/app/cook/page.tsx` ### Done when - [ ] Timers resume correctly after a reload - [ ] Multiple labelled timers run and alarm separately - [ ] The wake lock is held only during cooking - [ ] Controls are usable one-handed ### Watch 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. ## Not in this build - A public or shared library of imported recipes. - Retailer ordering and product catalogues. - Per-serving nutrition figures. - Household accounts with permissions. ## After v1, if you want it - 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
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Recipes, plans, lists and timers. DATABASE_PATH=./data/recipes.db # Required. Your own photographs. Imported recipes keep a link rather than a copy of the publisher's image. IMAGE_DIR=./data/images # Required. Markdown recipes plus images and a manifest. Readable without this application. EXPORT_DIR=./recipe-box # Required. What the plan assumes unless a meal says otherwise. DEFAULT_SERVINGS=2 # Required. When the shopping list resets and checked items clear. LIST_WEEK_START=monday
# Crouton · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Crouton, organize recipes, meal plans, timers, and a grocery list locally. The hard boundary is native apple apps, recipe capture, household sharing, sync, and cooking polish, plus sync, integrations, and household adoption. ## Product outcome A recipe box that imports honestly from what publishers made machine readable, scales quantities without touching times, keeps a timer through a locked screen, and merges a week into a shopping list you can trust. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - modern browser - local or self-hosted storage - optional email notifications ## Explicit non-goals for v1 - 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 ## Success criteria - Awkward ingredient lines are confirmed to parse without losing words - Import is confirmed to take no prose beyond the steps and to store the source - Times and temperatures are confirmed never to change with scale - A timer is confirmed to resume correctly after a page reload - Cross-family unit conversions are confirmed refused in the shopping list
# Build brief · Crouton The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a recipe box that scales, times and shops, and imports without helping itself to somebody's writing to replace Crouton. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Importing other people's recipes is what makes this useful and is also the part that can be done badly. Two things decide the design. First, what you are allowed to keep. The United States Copyright Office states that a mere listing of ingredients is not protected by copyright, and that where a recipe is accompanied by substantial literary expression in the form of an explanation or directions, or appears as part of a collection such as a cookbook, there may be a basis for protection. So import the quantities and the working steps, store the source link and the author on every imported recipe and show them, and do not copy the headnote, the story or the photograph into your box. Other countries word it differently and the cautious version is the same everywhere: cook from it, do not republish it. Second, how you read the page. Recipe publishers embed machine readable structured data precisely so software can read the recipe without guessing. Read that, from a page you opened yourself, one at a time. If it is not there, hand the user an editor rather than picking the markup apart. No bulk crawling, no public index of other people's recipes, no sharing beyond your own household. ### Stack (fixed, do not substitute) - Runtime: A local-first web application installed to the home screen, served on your own network · the device in the kitchen is a phone or a tablet with wet hands - Storage: SQLite with images on disk, everything exportable as plain files · a recipe box should outlive the software that holds it - Import: Read the publisher's embedded structured data on a page you opened, one page at a time · the data is published to be read by software, and guessing from markup is both worse and ruder - Quantities: Parsed into a number, a unit and an item, with the original string kept · '2 to 3 tablespoons, plus more for the pan' has to survive the round trip - Timers: Attached to steps, running in the background and surviving a reload · a kitchen timer that dies when the screen locks is not a timer ### Data model (create this before Phase 1) - 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. ### Phase 1 · A recipe that is structured, not a wall of text Build: Enter a recipe by hand: title, servings, ingredient lines and steps. Parse each ingredient line into quantity, unit and item, handling ranges, fractions and trailing notes, and keep the original line. Mark ingredients that should not be scaled. Attach an optional timer and temperature to any step. Trap: 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. Done when: A set of awkward ingredient lines parses into quantity, unit, item and note without losing any words, tested as a table including ranges, mixed fractions, 'to taste' and 'plus more for the pan'. The original line is stored for every ingredient and displayed when the parse is marked uncertain. Steps carry an optional timer and temperature. A recipe entered by hand round-trips through export and import unchanged. Do not build yet: No import, scaling, cooking mode or shopping list yet. ### Phase 2 · Import what the publisher published Build: Given a URL you opened, read the embedded structured recipe object and map its name, yield, ingredient list, instruction steps and durations, which are published in the standard duration format. Store the source URL and author and show them on the recipe. Copy no prose beyond the steps. When there is no structured data, open the editor with a paste box instead of guessing. Trap: 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. Done when: A page with embedded structured recipe data imports its name, yield, ingredients, steps and durations, tested against saved fixtures from several sites. Durations in the published format are converted correctly, including hours and minutes together, tested. Every imported recipe stores and displays its source URL and author. A page without structured data opens the manual editor with a clear message and imports nothing, tested. Do not build yet: No scaling, cooking mode, plan or shopping list yet. ### Phase 3 · Scaling that refuses to lie Build: Scale quantities by the ratio of wanted to original servings, rounding to a sensible kitchen amount and showing the exact value on demand. Leave ingredients marked unscalable at their original value with a note. Never scale a timer or an oven temperature. Show the original servings and quantities alongside the scaled ones. Trap: 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. Done when: Scaling to 2.5 servings rounds quantities to measurable amounts and shows the exact value on request, tested as a table. Ingredients marked unscalable keep their original quantity and carry a visible note. Timers and temperatures are identical at every scale, asserted in the suite. The original servings and quantities remain visible next to the scaled ones. Do not build yet: No cooking mode, plan or shopping list yet. ### Phase 4 · Cooking mode and timers that survive Build: A large-type cooking view with one step in focus, ingredients for that step listed beside it, and next and previous as big targets. Request a screen wake lock and release it on exit. Timers start from a step, run against stored start times rather than a counter in memory, and several can run at once with a visible label each. Trap: 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. Done when: A timer started and then followed by a full page reload resumes with the correct remaining time, tested. Several timers run at once, each labelled with its step and recipe, and each alarms separately. The screen wake lock is requested on entering cooking mode and released on leaving, verified by observation. Every control in cooking mode is usable one-handed at arm's length, checked by using it while actually cooking. Do not build yet: No weekly plan, shopping list or export yet. ### Phase 5 · The week, the list, and taking it all with you Build: Assign recipes to days with the servings you want. Generate one list, merging identical items whose units convert and listing the rest separately with their recipes named. Group by aisle, allow manual items, and keep checked state. Export the whole box as one folder of markdown recipes plus images and a manifest, and import that folder back. Trap: 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. Done when: Two recipes asking for the same item in convertible units produce one merged line with the correct total, tested. Two recipes asking for the same item in units that do not convert produce two lines, each naming its recipe, tested. Exporting produces a folder of markdown recipes and images that is readable without this application and reimports into an empty database unchanged, tested. Checked items survive a reload and reset when a new week is planned. Do not build yet: Nothing after this. ### Out of scope (and why) - A public index or shared library of imported recipes, which is republishing other people's work and is exactly what rule zero refuses. - Retailer ordering and product catalogues, which need a commercial relationship and a maintained product database rather than a weekend. - Nutrition figures per serving, which need a food composition database, a licence for it, and a portion model, and which would be guesswork otherwise. - Household accounts with permissions, since two people editing the same list needs identity and conflict rules and the paper alternative works fine. ### README must contain - That imports take the quantities and steps plus the source link and author, and deliberately leave the publisher's prose and photograph where they are - That importing reads the page's embedded structured recipe data on a page you opened, and falls back to a manual editor rather than picking apart the markup - That quantities are scaled but times and temperatures never are, and which ingredients are marked unscalable - That timers run from stored start times, so a reload or a locked screen does not lose one - Where the recipes and images live, what the export folder contains, and how to reimport it
# Architecture · Crouton ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A local-first web application installed to the home screen, served on your own network | the device in the kitchen is a phone or a tablet with wet hands | | Storage | SQLite with images on disk, everything exportable as plain files | a recipe box should outlive the software that holds it | | Import | Read the publisher's embedded structured data on a page you opened, one page at a time | the data is published to be read by software, and guessing from markup is both worse and ruder | | Quantities | Parsed into a number, a unit and an item, with the original string kept | '2 to 3 tablespoons, plus more for the pan' has to survive the round trip | | Timers | Attached to steps, running in the background and surviving a reload | a kitchen timer that dies when the screen locks is not a timer | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Recipes | Parsed ingredients with original text and steps. | Any store, provided the original line survives. | | Import | Structured data reading and attribution. | Not swappable for scraping the markup. | | Scaling | Rounding and the unscalable rule. | Any rounding, provided times and temperatures never scale. | | Cooking | Step view, wake lock and persisted timers. | Any interface, provided timers run from stored times. | | List | Plan merging inside unit families. | Any merge, provided cross-family conversions are refused. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_PATH` · required · Recipes, plans, lists and timers. - `IMAGE_DIR` · required · Your own photographs. Imported recipes keep a link rather than a copy of the publisher's image. - `EXPORT_DIR` · required · Markdown recipes plus images and a manifest. Readable without this application. - `DEFAULT_SERVINGS` · required · What the plan assumes unless a meal says otherwise. - `LIST_WEEK_START` · required · When the shopping list resets and checked items clear. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Crouton product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A local-first web application installed to the home screen, served on your own network, SQLite with images on disk, everything exportable as plain files, Read the publisher's embedded structured data on a page you opened, one page at a time, Parsed into a number, a unit and an item, with the original string kept, Attached to steps, running in the background and surviving a reload. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - 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. - 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. - 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. - 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. - 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.
# Delivery milestones · Crouton Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A recipe that is structured, not a wall of text Ingredients and steps are rows with their original wording kept beside the parsed values. ### Steps 1. Parse into fields and keep the original line The parse is a convenience; the line is the recipe. Files: `src/parse/ingredient.ts`, `tests/ingredient-cases.test.ts` 2. Mark what must not be scaled Salt, a tin and an egg do not multiply by 2.5. Files: `src/parse/scaling.ts` 3. Attach timers and temperatures to steps The step is where the cook is looking when they need it. Files: `src/recipes/steps.ts` ### Done when - [ ] Awkward ingredient lines parse without losing words - [ ] The original line is stored and shown when uncertain - [ ] Steps carry optional timer and temperature - [ ] Hand-entered recipes round-trip through export ### Watch 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. ## M2 · Import what the publisher published Importing reads the page's embedded structured recipe data and lands in the editor when it cannot. ### Steps 1. Read the structured data, map the fields you need It is published for exactly this and it is the honest path. Files: `src/import/structured.ts`, `tests/import-fixtures.test.ts` 2. Store and display the source and the author Attribution costs nothing and settles most of the question. Files: `src/import/source.ts` 3. Fail into the editor, never into the markup A half-scraped recipe is worse than typing it out. Files: `src/import/fallback.ts` ### Done when - [ ] Structured data imports fields and durations correctly - [ ] Source URL and author are stored and displayed - [ ] No prose beyond the steps is copied - [ ] A page without structured data opens the editor ### Watch 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. ## M3 · Scaling that refuses to lie Changing the servings scales what scales, leaves alone what does not, and never touches times or temperatures. ### Steps 1. Round to something a kitchen can measure Nobody owns a scale that reads three and a half eggs. Files: `src/scale/round.ts`, `tests/scale.test.ts` 2. Leave marked ingredients and all times alone This is the part that ruins the dish rather than the arithmetic. Files: `src/scale/apply.ts` 3. Keep the original visible beside the scaled value The cook is the one who decides whether the scale is sensible. Files: `src/app/recipe/[id]/page.tsx` ### Done when - [ ] Scaled quantities round to measurable amounts - [ ] Unscalable ingredients keep their value with a note - [ ] Timers and temperatures never change with scale - [ ] Original values stay visible ### Watch 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. ## M4 · 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. ### Steps 1. Store timer start and duration, compute the rest A counter in memory is one tab switch from gone. Files: `src/timers/store.ts`, `tests/timer-reload.test.ts` 2. Hold a wake lock only while cooking And release it, because a screen that never sleeps is a flat battery. Files: `src/cook/wakelock.ts` 3. Make the targets big and test them wet Precision tapping is not available in this context. Files: `src/app/cook/page.tsx` ### Done when - [ ] Timers resume correctly after a reload - [ ] Multiple labelled timers run and alarm separately - [ ] The wake lock is held only during cooking - [ ] Controls are usable one-handed ### Watch 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. ## M5 · The week, the list, and taking it all with you (production only) A plan produces one shopping list with quantities merged where they can be, and everything exports as plain files. ### Steps 1. Merge only inside a convertible unit family A wrong total once is a list nobody reads again. Files: `src/list/merge.ts`, `tests/merge.test.ts` 2. Keep unmergeable lines separate and attributed Two honest lines beat one invented number. Files: `src/list/build.ts` 3. Export as markdown files and images, then reimport The box should survive this application being abandoned. Files: `src/export/folder.ts`, `tests/roundtrip.test.ts` ```sh npm run export -- --dir=./recipe-box npm run import -- --dir=./recipe-box ``` ### Done when - [ ] Convertible units merge into one correct line - [ ] Unconvertible units stay separate and attributed - [ ] Export is readable without this application - [ ] Checked state survives a reload ### Watch 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.
# Operations · Crouton ## Backup The database and the image directory together, plus a periodic run of the folder export, which is the copy that will still be readable in ten years when this application is not. ## Restore Restore the database and images, or reimport the exported folder into an empty database, and confirm the recipe count and that one imported recipe still shows its source and author. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a household application the checks live in the suite: the ingredient parse table, the duration conversions, timers resuming after a reload, and the merge refusing cross-family conversions. Watch the image directory, which grows faster than anything else here. ## Incident checklist The realistic failures are a lost timer and a wrong shopping total, and both are recoverable but corrosive: people stop trusting the tool. Keep the parse cases and the merge cases as tests you add to whenever something comes out wrong in the kitchen. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Awkward ingredient lines are confirmed to parse without losing words - [ ] Import is confirmed to take no prose beyond the steps and to store the source - [ ] Times and temperatures are confirmed never to change with scale - [ ] A timer is confirmed to resume correctly after a page reload - [ ] Cross-family unit conversions are confirmed refused in the shopping list ## Launch constraint Do not market omitted Crouton capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Recipes, plans, lists and timers. DATABASE_PATH=./data/recipes.db # Required. Your own photographs. Imported recipes keep a link rather than a copy of the publisher's image. IMAGE_DIR=./data/images # Required. Markdown recipes plus images and a manifest. Readable without this application. EXPORT_DIR=./recipe-box # Required. What the plan assumes unless a meal says otherwise. DEFAULT_SERVINGS=2 # Required. When the shopping list resets and checked items clear. LIST_WEEK_START=monday
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Crouton because household apps survive when every family member actually uses them and the data stays current across devices. The recurring cost buys multi-user conflicts, reminders, notifications, imports, barcode data, smart-home APIs, backups, and long-term maintenance, not just the visible interface.
xnative Apple apps, recipe capture, household sharing, sync, and cooking polish
xfrictionless family sync
xretailer and smart-home integrations
xlarge recipe or product database
xmobile polish
Crouton pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| basic | $0 | $0 | Store and organize up to 5 recipes; meal planning, grocery lists, cooking mode, timers, iCloud sync, conversion, scaling, guided weighing. |
| crouton plus | custom | — | Unlimited recipes and family sharing; no AI import, enhanced scanning or recipe feeds. |
| discover monthly | $1.99 | — | Unlimited recipes, family sharing, AI recipe import from photos/text, enhanced cookbook scanning, recipe feeds/RSS. |
| discover yearly | — | $1.25 | Unlimited recipes, family sharing, AI recipe import from photos/text, enhanced cookbook scanning, recipe feeds/RSS. |
free tier$0; store and organize up to 5 recipes; core meal planning/grocery/cooking/timer/sync features included.
billingfree + one-time Plus + monthly/yearly Discover subscription
hidden costsAI import, enhanced scanning and recipe feeds require Discover; unlimited recipe storage/family sharing require Plus or Discover.
verified 2026-08-10 · source ↗
Vibecode Crouton
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Crouton replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Crouton cost?
Crouton costs about $1.99/month (Crouton Plus, checked 2026-07-31), which is $23.88 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing Crouton?
Honestly: 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. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Crouton?
Yes: Mealie (Active open-source recipe manager and meal planner for households.). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.