Build Crouton

YESreplaces $1.99/mosaves $23.88/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

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 weekendthe files for this build are in the project pack

RuntimeA local-first web application installed to the home screen, served on your own networkStorageSQLite with images on disk, everything exportable as plain filesImportRead the publisher's embedded structured data on a page you opened, one page at a timeQuantitiesParsed into a number, a unit and an item, with the original string keptTimersAttached to steps, running in the background and surviving a reload

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the application and the import and export commands.

    Get it nodejs.org, or your version manager. open ↗

    Verify node --version prints v22 or higher

  2. have readyfree

    Why Every decision in the cooking view is about wet hands, arm's length and a screen that wants to lock, and none of it is testable on a laptop.

    Get it Open the application on that device and add it to the home screen.

    Verify A recipe can be followed end to end on it without touching a keyboard

  3. have readyfree

    Why The screen wake lock and installing to the home screen both need a secure origin, and this is where kitchen projects stall on day one.

    Get it A local certificate authority for your network, or a hostname with a real certificate pointing at the local address.

    Verify The device opens the application with no certificate warning and the wake lock is granted

  4. have readyfree

    Why Import is the feature most likely to break, and testing it by fetching live pages is both flaky and impolite.

    Get it Save the pages you actually cook from, including one with no structured data at all.

    Verify The import test suite runs offline against the saved copies

  5. decidefree

    Why The shopping list merge is only as good as this list, and an over-eager conversion table is what produces a total nobody believes.

    Get it Write down the unit families you will convert within and explicitly refuse cross-family conversions such as volume to weight.

    Verify docs/units.md lists them and the merge tests cover a refusal case

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- recipes(id, title, source_url, source_author, licence_note, servings, total_time_iso, created_at, imported_at)
- ingredients(id, recipe_id, position, quantity_low, quantity_high, unit, item, note, original_text, scales_with_servings) · original_text is what was written, the rest is what you parsed
- steps(id, recipe_id, position, text, timer_seconds, temperature_c) · a timer belongs to the step that needs it
- plans(id, date, meal, recipe_id, servings_wanted) · the week, and what you actually intend to cook
- list_items(id, plan_week, item, unit, quantity, from_recipes_json, checked_at, manual) · merged where units agree, kept separate where they do not
- timers(id, recipe_id, step_id, started_at, seconds, state) · persisted, so a reload does not lose the bread
- Every ingredient keeps the text it arrived as. Parsing is a convenience that can be wrong, and the cook needs to see what the recipe actually said.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_PATHrequired./data/recipes.dbRecipes, plans, lists and timers.
IMAGE_DIRrequired./data/imagesYour own photographs. Imported recipes keep a link rather than a copy of the publisher's image.
EXPORT_DIRrequired./recipe-boxMarkdown recipes plus images and a manifest. Readable without this application.
DEFAULT_SERVINGSrequired2What the plan assumes unless a meal says otherwise.
LIST_WEEK_STARTrequiredmondayWhen the shopping list resets and checked items clear.

The build, in order

  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.

    1. The parse is a convenience; the line is the recipe.

      Files src/parse/ingredient.tstests/ingredient-cases.test.ts

    2. Salt, a tin and an egg do not multiply by 2.5.

      Files src/parse/scaling.ts

    3. The step is where the cook is looking when they need it.

      Files src/recipes/steps.ts

    done when · tick each as it passes
    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.
  2. Import what the publisher published

    Importing reads the page's embedded structured recipe data and lands in the editor when it cannot.

    1. It is published for exactly this and it is the honest path.

      Files src/import/structured.tstests/import-fixtures.test.ts

    2. Attribution costs nothing and settles most of the question.

      Files src/import/source.ts

    3. A half-scraped recipe is worse than typing it out.

      Files src/import/fallback.ts

    done when · tick each as it passes
    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.
  3. Scaling that refuses to lie

    Changing the servings scales what scales, leaves alone what does not, and never touches times or temperatures.

    1. Nobody owns a scale that reads three and a half eggs.

      Files src/scale/round.tstests/scale.test.ts

    2. This is the part that ruins the dish rather than the arithmetic.

      Files src/scale/apply.ts

    3. The cook is the one who decides whether the scale is sensible.

      Files src/app/recipe/[id]/page.tsx

    done when · tick each as it passes
    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.
  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.

    1. A counter in memory is one tab switch from gone.

      Files src/timers/store.tstests/timer-reload.test.ts

    2. And release it, because a screen that never sleeps is a flat battery.

      Files src/cook/wakelock.ts

    3. Precision tapping is not available in this context.

      Files src/app/cook/page.tsx

    done when · tick each as it passes
    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.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more home.