Every recipe page buries a twelve-line recipe under ads and a nine-hundred-word story about a farmhouse kitchen. Stripping that out is table stakes and a dozen tools already do it. The interesting problem is the one underneath: even a perfectly clean recipe is a flat numbered list, and a flat list hides the two things a cook actually needs.
A Recipe Has Two Structures, and a List Shows Neither
What combines with what. “Cut the butter into the dry ingredients” assumes you know which of the seven ingredients above are the dry ones, and that they were already combined. That is topology, and it renders as a merge chart: ingredients as rows, each operation spanning the contiguous block of rows that feed it.
When to start. Every step is written as though you begin it the instant the previous one ends, which is false. Bread rises for an hour while you do nothing. The oven needs fifteen minutes of preheat that the recipe mentions once, at step one, and never accounts for again. That is scheduling, and it renders as a true Gantt: clock time across the top, bars sized by duration, your two hands treated as a resource of capacity one.
Neither structure is derivable from the other. The merge chart has no clock, so it cannot tell you when to preheat. The schedule only knows that four things must precede a fifth, not that those four combine into one substance. That is why views are a registry rather than two hardcoded components: adding one costs a file and a registry entry.
The Scheduler Is the Product
Set a serve time and the plan is computed backward from it. Placement is as-late-as-possible, because food should not sit, which is also why slack renders to the left of a bar: the only room to move is toward the past. Slack itself is measured against a separate forward pass, because under pure backward placement every step sits at its latest start and slack would be zero by construction, making the critical path meaningless.
On the first real test fixture the critical path came out as the oven preheat, not the dough. The dough chain runs 870 seconds; the 900 second preheat has to finish before the final step begins, so it reaches further back. Every dough step carries 210 seconds of slack and the preheat carries none. The binding constraint was the thing the recipe mentions once and never accounts for, which is the entire thesis holding up on the first try.
Both engines are pure functions. The clock is an argument, never Date.now(), which is the single decision that makes a scheduler testable at all.
It Refuses to Guess
A schedule that lies is worse than no schedule, because the chart looks complete and the meal is still late. So a step with no stated time renders as a zero-width marker with a visible “duration unknown” affordance, and it is never filled in. When a source states a total time but no per-step times, the importer says so explicitly and refuses to divide it up.
The same honesty governs import. Structured data only: schema.org JSON-LD, then microdata, then the h-recipe microformat, then stop. There is deliberately no fallback that scrapes body text, because a site publishing no machine-readable recipe has not offered one, and “paste it yourself” is the honest answer. Only the functional recipe is ever stored, never the headnote or the story, and every saved recipe carries its source domain, author and a link back, on screen, in print and in any export.
Some large publishers refuse automated requests at the network edge. Measured against one of them, a mainstream browser User-Agent receives a byte-identical refusal, so this is not about identity and there is nothing to fix in the headers. The app takes them at their word: it says which site said no, offers a paste-the-page path that runs the same parser over a page you opened yourself, and points at a directory of sites measured as working.
What Do I Cook This Week
Scheduling one recipe assumes you already know which recipe. The other half of this app works one step earlier, starting from what’s already in the house rather than a recipe you’ve already picked.
/fridge is a record of what got bought this week, kept after the fact rather than as a shopping list. Each item derives a rough “stretches to how many meals” estimate from a small portions table, shown as a draft and always correctable, and once corrected by hand it stays that way rather than getting quietly re-guessed on the next edit. A week plan then builds from the fridge in the order each item keeps worst first, so nothing bought to spoil sits behind something shelf-stable.
The fridge itself is drawn, above the list you edit it from. A list of eleven rows of number inputs is the wrong density for remembering what’s in the house, so items sit on shelves grouped by category, each a small picture over its name. Categories group the shelves, never how fast something spoils, because labeling a shelf “cook first” would put a claim about food safety right next to the food, which this app refuses to do anywhere. Adding what you bought used to be a hundred-item dropdown; it’s a search box now, and an empty one shows only what’s been starred, since the ingredients bought most weeks are the ones that deserve to be a tap away.
Built for Flour-Covered Hands
Read at arm’s length, on a tablet, by someone who cannot easily touch the screen. Base type is one step larger than a desktop app would use, touch targets are 44px minimum, and numerals are tabular everywhere a time appears, because a clock column whose digits shift width is unreadable at a glance.
Colour is semantic on four orthogonal channels: fill encodes attention, outline encodes the critical path, an end-of-bar glyph encodes status, and hue encodes equipment. Warm means hands-on, cool means walk away, and that mapping never inverts. Resource hues are separated by lightness as well as hue, so the chart survives colour blindness and a greyscale printer. A printed timeline taped to a cupboard is a real use, so both charts print to one page with the attribution in the footer.
A kitchen tablet at night is a real case too, so the app now offers an explicit light or dark theme on top of a system default that still wins with no interaction. Building the override surfaced a bug nobody had ever hit: forcing dark on a browser that reported light used to move nothing but the native scrollbars, because the dark palette had only ever been wired to the system’s own preference. Fixed, with a test that fails if the two palettes needed to support both paths ever drift apart.
Local, and Staying That Way
The library is IndexedDB on the device. No accounts, no sync, no server holding anyone’s recipes. Whole-library export writes a JSON file you keep, and import merges it back, keeping the newer copy of anything that arrives from a second device and never letting an older copy overwrite a local edit.
The app ships with a 300-recipe starter library, built once from Wikibooks Cookbook’s CC BY-SA corpus and committed rather than fetched at runtime. 111 of those recipes carry a photograph: sixty-nine reviewed by hand off a contact sheet of candidates and credited to their photographer, the rest a drawn tint and initial rather than a guessed or borrowed picture, because a wrong photo of the wrong dish is worse than an honest placeholder.
1,049 tests. Two of the more useful ones assert what the app gets wrong on purpose, so the known limits of a best-effort ingredient parser are visible in the suite rather than discovered in a kitchen.

