Build Pestle
YESreplaces $2.99/mosaves $35.88/yrback to the verdict
A household recipe box whose hard problems are a cooking mode that keeps working when the browser throttles it, a shopping list two people can tick offline without losing an item, and keeping the whole box portable enough to leave.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the household server and the build.
Get it nodejs.org or your version manager. open ↗
Verify
node --version prints v22 or higher - have readyfree
Why Cooking mode, the wake lock and the timer reconciliation can only be judged on the device that will be on a worktop with wet hands.
Get it Open the app on the phone over the local network and add it to the home screen.
Verify
A timer started and backgrounded reports correctly on return - have readyfree on hardware you own, or a few pounds a month
Why The household needs one place to agree with, and the wake lock and installability both want a secure origin.
Get it Any small machine or home server with a local certificate, or a cheap instance with a real one.
Verify
Two devices reach the same household over a valid certificate - have readyfree
Why The list merge is the part most likely to lose data and it cannot be tested with one device.
Get it Any second phone, tablet or browser profile that can be put into aeroplane mode.
Verify
Both devices edit offline and reconcile without loss
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_name, servings, notes, created_by, updated_at) - ingredients(recipe_id, position, quantity, unit, item, note) · structured rather than a free text line, because the list depends on it - steps(recipe_id, position, text, timer_seconds) · a step may carry a suggested duration that becomes a timer - cooks(id, recipe_id, started_at, current_step, finished_at) · one run through a recipe, so you can leave and come back - timers(id, cook_id, label, ends_at, acknowledged_at, cancelled_at) · an end time, never a remaining count - list_items(id, household_id, text, quantity, unit, source_recipe_id, state, state_changed_at, changed_by) · state is needed, in trolley or done - members(id, household_id, name, joined_at) and households(id, name, invite_code, created_at)
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_PATH | required | ./data/household.db | Recipes, lists, cooks and timers. Back this up; recipes are typed once. |
IMAGE_DIR | required | ./data/images | Recipe photos. Included in every export as files. |
PUBLIC_BASE_URL | required | https://kitchen.local | Used in invite links and needed for a secure origin. |
INVITE_CODE_TTL_HOURS | required | 48 | How long a household invite code stays valid. |
MAX_IMAGE_BYTES | required | 2097152 | Per photo, after the device resizes it. |
BACKUP_DIR | required | ./backups | Scheduled archives of the database and images together. |
The build, in order
A box that opens in a kitchen
Recipes are structured, editable and readable on a phone with no signal.
Everything downstream depends on quantity and unit existing.
Files
src/db/schema.tssrc/lib/ingredients.tsA stale copy you know about is fine; one you do not is not.
Files
src/lib/offline.tsDoubling a cake does not double the bake.
Files
src/lib/scale.tstests/scale.test.ts
done when · tick each as it passeswatch out- Storing ingredients as one text line per row is quick and makes every later feature impossible, because a shopping list cannot merge two hundred grams of butter with a knob of butter when neither has a quantity or a unit. Store quantity, unit and item separately from the start and accept a fiddlier entry form.
Cooking mode, and clocks you can trust
The screen stays on when it can, and every timer is correct when you come back to it.
The clock is the only thing that keeps running.
Files
src/lib/timers.tstests/timers.test.tsThe lock is dropped when the page goes away and does not come back by itself.
Files
src/lib/wakelock.tsOne honest sentence beats a silent failure over a hot pan.
Files
src/app/cook/page.tsx
done when · tick each as it passeswatch out- Building the timer as a countdown in a repeating callback means that when the phone locks or the tab goes to the background the callback is throttled or stopped, and the user returns to a timer showing four minutes left that should have rung twenty minutes ago. Store the end time, compute from the clock, and reconcile on every wake.
A list two people can tick at once
Two members editing the list offline both keep their changes.
This is the difference between a household tool and an argument.
Files
src/lib/list-sync.tstests/list-merge.test.tsA stale device will otherwise bring the milk back.
Files
src/lib/list.tsSo removing a meal from the plan can remove its items.
Files
src/lib/list.ts
done when · tick each as it passeswatch out- Syncing the list as one document and taking whichever copy was saved most recently means the person in the shop who ticked off eight things overwrites the person at home who added three, and nobody finds out until dinner. Merge per item on the time its own state changed, and keep deletions as tombstones so a slow device cannot resurrect them.
A household, and the door outproduct builder
People can join and leave cleanly, and the whole box can be exported and reimported.
They are the part nobody can recreate.
Files
scripts/export.tstests/export.test.tsPeople leave households; their recipes should not.
Files
src/lib/members.tsThe first restore should not be the one that matters.
Files
tests/restore.test.ts
done when · tick each as it passeswatch out- Exporting recipes as JSON while leaving the photos on the server means the export looks complete and is missing the only part nobody can retype, and people discover this when the server is already gone. Export images as files alongside the JSON, reference them by relative path, and test the import into an empty instance rather than assuming it.
That is the whole plan for Pestle. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Scraping recipes from other people's sites.
- Retailer ordering and barcode product data.
- Smart home and appliance integrations.
- Nutrition figures.
- native Apple polish, recipe import, hands-free mode, sync, and family sharing
- frictionless family sync
- retailer and smart-home integrations
- large recipe or product database
- mobile polish
- A weekly plan that feeds the list and can be undone as one action when the week changes
- A printable list grouped by shop aisle, which is the feature people actually use in a supermarket
- A pantry with rough quantities, which only becomes useful once the list merge has been trusted for a while
Need the files? The project pack on the verdict page hands your agent the whole brief · more home.