Always in stock
Essentials: lines the kitchen always has in; the list keeps them off the shop.
From DOMAIN.md, Vocabulary
One of the aisles is the kitchen’s, not the shop’s: Essentials, "always in stock." The owner: "I’d like to have an ‘essentials’ category that’s user-set for things that we always have in stock. salt/pepper/oil and stuff."
From SCREENS.md, 1d · Lists
Vocabulary
DOMAIN.md, the whole section
- Thing: something on the bench, an ingredient or a preparation.
- Ingredient: an item as the recipe declared it, before anything is done to it.
- Preparation: anything an operation produced: a prepped ingredient, a mixture, a component, the final dish, a reservation, a discard.
- Operation: an action. Consumes things, produces things. A cook reads it as a step, and the
code says step far oftener than operation; the two are one thing. A source step is not one: it is
a line of the page the recipe came from (
sourceStepIds), and one step can answer several. - Station: where the food is while a step runs, as the graph declares it (
OperationNode.station, optional, never inferred from a temperature or a pan's name): the hob (the value; a cook reads stove, and the cooking screen's code saysStove*, the ruling beingeditor-words.ts's), the oven, the fridge (and freezer), the bath (sous vide) or the bench (counter, board, bowl: off the heat and off the cold). The import asks the model for it asplace, in four plain words (counter, stove, oven, fridge). Why it is declared: "Where a step happens is declared, not inferred", below. - Vessel: what the food is in while a step runs (pan, pot, tray, bowl, jar). Not a field of its
own: the graph keeps free-text
equipmenton the step, and the technique table reads the word in the sentence to place the step. Where a sentence can say "the pan" or "the bowl" instead, it should. - Allocation: a portion of an ingredient. Every ingredient has at least one; a divided ingredient
has several. AMOUNTS calls it a share, and so does the code
beside it;
sharealso names a published link, a divide's part and a line the importer reads, which are not this. - Role: what a share is for: fat, acid, bulk, seasoning, and the rest of a fixed vocabulary.
ingredientRoleSchema(libs/model/src/recipe/recipe-graph.model.ts) is its one home, carrying a one-line definition per member; the count is deliberately not restated here, since growing it is the point. Declared on the allocation, optional, and never guessed. - Workstream: a run of the graph you can run beside another, the sauce while the pasta boils.
The count is the recipe's honest difficulty and nothing caps it; the ring squishes to fit however
many there are (DESIGN.md). One word for the thing (owner, 24
October 2026: "for the same thing being referenced with 5 different words, why wouldn't unifying to
a single term be worth it?"):
workstreamis the thing, in code, CSS and docs, andarcis kept only for the shape the ring draws for one (ringShapeOf, the label placement), the way a row is not the record it shows.strand,trackandlaneare retired for it (laneHoldsOfisworkstreamHoldsOf,--lane-1is--workstream-1);lanekeeps only an agent on a ticket (below). The storedarc-takenevent and itsarcfield keep the name, because a stored value is renamed only with a migration.one-word-for-workstream.test.tsholds it. A cook reads parts (the owner, 25 October 2026, over "components" of 14 August: "I am good with parts instead of components"), and the words gate holds that line (repo-word-strand). A part's name is the page's heading, else the product without its participle (owner, same day: "I agree heading else product"): the one source heading every headed step of the workstream sits under and no other workstream claims, else the one heading of a list headed more than once that every ingredient only it draws on was listed under ("Seafood Stuffing:"), else what it makes with the leading words that say what was done dropped, "the chilled tenderloin" read as "the tenderloin" (partNamesOf,Workstream.partName;namestays the product). Two parts that would read alike keep their products in full. A single bench step is one only as a beginning: it keeps its arc when it meets the dish at its first join (the dry bowl, a marinade), and is a bead where it is used when it meets it later (the yogurt, the parsley, the oven). - Twist: a version kept beside the one it came from: the pancetta Amatriciana next to the guanciale one, both cookable, neither superseding the other. The cook says which they meant at the end of an edit.
- Tweak: a version that takes over the line it came from, still the guanciale recipe, changed. The one before it stays readable and cookable, because nothing here is ever deleted.
- Session: one cook of one recipe version, recorded as an append-only event log. The cook's word for it is the evening, below.
- Bench: the things that currently exist and still have a use ahead of them. What the cooking view shows.
- Kitchen: who a row belongs to: a named place with members, a hob, a plan, a shopping list and a shelf of recipes. An account creates or joins one at onboarding; a household shares one (KITCHENS).
- Cook: a person, as a session records them. What a kitchen has members of, and what an event is
stamped with. Two names deviate and are left:
cooksPerRecipeandcookIdcount and key occasions (sessions), not people. - Library: a kitchen's recipes. A word for the collection, not an entity: there is no library that is not some kitchen's, and nothing should ever create a table of them.
How hard a recipe reads is its arcs first, then the work in your hands, and never the clock.
Owner ruling, 5 October 2026: complexity weighs the arc count over the total time, "or at least use
time hands on not passive total time". complexityOf (libs/core/src/recipes/recipe-complexity.ts)
is the one function, derived at read: ten points an arc and a point a quarter hour of the low end of
handsOnTime, the second capped under an arc, so one more arc outscores any amount of work in hand.
It is cut into five levels, not ten (ten claims a precision a score of arcs and quarter hours
has not): a level is the arc count, plus one where half an hour or more is in your hands, never above
five — one arc is a 1, or a 2 with a long stand at the stove, and each further arc is a level up.
The four cuts are one number (30 minutes) on the score, each a row with a why in LEVEL_CUTS,
measured on the shipped recipes and the owner's exports. It is drawn as a position and never as a
fill (owner, 22 October 2026, after the lemons read as "low stars"): five rising steps with only the
recipe's own inked, Easy and Hard named at their ends, so it cannot be read as a rating or as a
fraction of the clock. The recipe page and the share page say it as one tap, the strand dashes and "2
parts" (the count the ring draws, workstreamsOf), and the panel it opens, RECIPE DIFFICULTY, is the one
place the level is drawn: what it is cut from (the hands-on span said as a time squeezy summed is, where
the steps nobody timed do not outnumber the rest; the steps; the things to prep, prepListOf) with the five steps beside it, and, where it is not the part count, how many
steps run at once (underWayAtOnce: "At the busiest, three parallel tasks"). It does not explain the cut.
complexityOf carries the arcs, the hands-on low end it cuts the level from and the hands-on span
the panel may say, so the panel cannot disagree with it. Its accessible name says the level in words; the words come from
recipe-complexity-words.ts. Beginnings are arcs, finishes are beads (owner rulings,
16 and 17 October 2026): one hands-on step from ingredients alone keeps its arc when it meets the
dish at its first join, as the cookies' dry bowl does beside the wet mixture, so they are two arcs;
met later, like the yogurt or the parsley, it is a bead and adds nothing here. The shelf, the brief
and the ring all read the one count (workstreamsOf). A wait, a chill and an
oven are the cook's time given back and are not read; a step nothing answers adds nothing, so a
recipe of blanks reads lighter rather than being guessed a number. Only the Recipe difficulty panel draws the level: the shelf row says it by its dashes and the week not at all (owner, 22 October 2026; SCREENS §1b).
Hands-on time is a low end and a high end, because a step nobody timed is still work. Owner
ruling, 6 October 2026, on an Amatriciana row that read "3 min+" while its prep (dice, grate, crush)
stated no time: "3min+ does bother me." handsOnTime (libs/core/src/scheduling/hands-on.ts)
answers each attended step with stepFigureOf, the reading the dish's total shares (COOKING,
"The total counts the guessed times too"): this kitchen's own median for it, the figure the recipe
carries, then our assumed range for the step's verb and words (assumedRangeFor, the one call the
importer settles a condition with). A single figure adds to both ends; a range, the recipe's or ours,
adds its minimum to the low and its maximum to the high. So it reads "27 min" where every part is
a single figure and a range where any part is not, never a point standing for one: said as a summed time is (expectedTimeFor and
expectedTimeWords, the head's own), "40 min", "30 to 40 min", or "up to 10 min" where the ends are
far apart, and never the low end alone. A summary is cached per version, so it folds the recipe's
and our figures; this kitchen's step times are the fold's argument for a caller that holds them.
It is said only where the steps that answered outnumber the steps nothing answers
(handsOnStandsForTheDish; complexityOf carries the span, handsOn, or none). Owner, 23 October
2026, on the Seafood Salmon Roulade: "having a 5-minute hands-on makes no sense whatsoever." Two of its
19 attended steps had a window and 17 (skin, peel, mince, roll, tie) had none, so two minutes of
stirring rounded to "5 min" for a dish of hands. Two ends over a few answered steps are a floor
dressed as a total, as totalMs is absent for the same reason, and a window for knife and hand work
would be an invented figure, since an act's time depends on what it is done to. The panel then says
nothing of the time and still says the steps and the things to prep. The level is cut from the low end whether
or not the time is said: the hands-on term lifts a level by one at most.
It is parked on every row (owner, 6 October 2026: a hands-on low end of 13 minutes "made no sense" on a row). No row says hands-on time; a row's time is this kitchen's own whole-dish time where it has saved any, else the recipe's. Only the Recipe difficulty panel says it.
The words the screens, the tools and the tickets use, in plain terms. This is their one home: the owner's question, 19 September 2026, was "the taster?? what the heck is that", and a name that needs this list to be understood is a name to reconsider before it spreads.
- Visitor: a person using squeezy with no account (the word was "taster" until 19 September). Cooks the built-in Amatriciana; nothing is kept between visits.
- The brief: a recipe's own page: the ring, the ingredients, the steps, and the button that starts cooking.
- The shelf: the list of a kitchen's recipes.
- The finder: the dinner picker on home: a few questions, then a dish.
- The week: the planning screen: seven days, a dish on each, and the shopping it implies.
- The evening: one cook of one dish, from Start cooking to the summary, and the screen between the brief and the stove that asks how tonight is cooked. It is a session in the model.
- The route, the screen, the cook's word:
/todayis home;/dinneris the finder (the code also says picker, funnel and easy-peasy);/mealsis the week (the tab says Plan; the code saysWeek*andMealPlan);/recipesis the shelf and/liststhe shopping. Add a screen's route here when it gets a second name. - The ring, an arc, a bead: the map above the steps: the recipe drawn as concentric arcs, one arc per workstream, a bead per step done.
- The deck (cooking): the scrolling column of step cards under the ring.
- A reading: one view of the same data, switched between with a tap or a swipe: the brief's
ingredients and steps, a list's by-aisle and by-recipe. Three other things carry the word and are not
that: the types a view computes at read (
IngredientReading,StepReading,AxisReading), an entry in the import library (below), and a unit choice (AMOUNTS). The words gate bans "readings" on a screen (repo-word-reading). - A standing: the state a screen is in and answers from: home "with a pot on", the welcome "at the door".
- A receipt: what a row shows after a tap so you know it landed: "On the list".
- A grip: the handle a swipe starts from on a row.
- A door: an entrance, and the word is said of several. Import's: one of the ways to add a recipe
(a link, pasted text, a photograph). Also a door: the front door (below), a sign-in method, closed
beta (
CLOSED_BETA), a cross-kitchen copy path, the finder's entrance and a settings entry. Name which. - The walk: the importer reading a recipe's steps one by one into the graph; also a typed recipe's save doing the same.
- Re-import: doing an import again for a recipe already held, the cook's word (the owner, 20
October 2026). The code and the docs say read again, read all and reread, all the same
act; a screen says Re-import and Re-import all (
re-import-not-read-again). - Settle rules: the importer's corrections to a reply, the
settling/folder (IMPORT-PIPELINE). No cook reads "settle" (repo-word-settle). - The import library: the store, shared across kitchens, of what the model read from each page, so
a page is paid for once (IMPORT-PIPELINE). It lives in Postgres
as
import_replies, and the code keeps that name andReplyCache. The docs say library and not cache because a cache is cleared when it fills and this is kept (owner, 25 October 2026: "is cache the right term? Cache almost implies it would be cleared"). A sweep relabels its rows and never deletes one; the only thing that drops any is the reaper's prune of entries read under prompt words older than the newest two versions.reply_cache, the Grafana view over it, keeps its name too. No screen says any of this, and none says whether a page was read before. - A reading (in the library): one page's entry, the model's reply as it arrived, found by a hash of
the page's words with the prompt's words, the schema, the model and the effort. A reused reading
is an import that replayed one and called no model (
reused_reply). The owner, 25 October 2026: "Readings is what we cache, maybe the import library is more accurate." It is the reply and never the recipe: the recipe is rebuilt from it by today's rules. - Second senses: words that mean two things here, so a bare use is ambiguous. Component: a
stored
PreparationKind; a swap's part; until 25 October 2026, a workstream as a cook read it (now a part, above). Library: a kitchen's recipes, squeezy's own curated tenant,kitchen_squeezy, and the import library. Role: an ingredient's job (above) and a member's permission (KITCHENS). Draft: a typedRecipeDraft, the model's reply, the walk'sAuthoredDraft. Walk and standing are above and are also said of other things. Say which where it can be mistaken. - The front door: the server's sign-in check: which requests may pass with no account.
- Essentials: lines the kitchen always has in; the list keeps them off the shop. Running out (a swipe on the cook screen) is offered on these and on every spice and pantry line.
- MyTime: the kitchen's own recorded times for a recipe's steps, which replace the recipe's figures once they exist.
- A sweep: a one-time pass over stored rows when something about their shape had to change; it may relabel a row and never delete one.
- The gate, the drive, a deck (design):
pnpm verify,node tools/drive.mjs, and a reference drawing of a screen indesign/; CLAUDE.md says what each does. - A lane: one agent working one ticket in its own worktree. In the app's code
laneis a workstream (above), so a cook-facing line never says it. - The reference recipes: the recipes an import is scored against: a graph written in our own sentences, one per recipe. A comparison set, not a truth set.
- A scoring run:
node tools/score-imports.mjs: reads each reference recipe's source the way an import does, or replays a reply recorded from it, and compares what comes out with the reference, in the graph and in what a cook sees.
Dependency depth is not simultaneity. Two operations at the same depth are merely both unblocked; that says nothing about whether a cook can do them at once. Chopping mushrooms, searing a fillet and peeling potatoes share a depth and are three separate jobs for one pair of hands. What is genuinely simultaneous is everything already cooking without you, plus the one job in your hands — see SCREENS.md.
1d · Lists
SCREENS.md, the whole section
Question: what is still to buy, and what have I just put in the basket?
Implements: apps/web/src/app/lists/[listId]/page.tsx,
components/list/{list-shop,list-top,list-title,list-picker,list-namer,list-readings,list-lines,list-line,list-clear,list-clear-purchased,list-write}.tsx,
views/{list-score,list-words,shopping-dishes,shopping-filter,shopping-score,shopping-sort}.ts,
hooks/use-shop-reading.ts, lib/open-list.ts, lib/leaving-motion.ts. The reference is
design/lists.mjs.
State: no session. The plan's lists (MealPlan), synced with the kitchen.
The rules of the shopping list. The tab itself, which opens a list and draws nothing, is §1b; which screen draws a given control is said where it matters.
- The grocery list is a list. Items with a status, stored — see AMOUNTS.md. Adding a dish from its brief copies its ingredients on; typing on the page adds anything else, because half of what goes on a shopping list is milk and bin bags. Each item ticks off, each can be taken off on its own, Clear purchased takes off what is ticked, and Clear all deletes the lot once you have confirmed it. Planning a dinner does not write a list, and clearing one does not cancel a dinner.
- A list summarised is one sentence, and it is said in the picker. Owner's ruling, 31 August
2026: "The grocery list card is also way too busy. We have to condense the heck out of this."
Two screens drew one component, so the summary had inherited a shop's entire surface — which list,
an add field, three filter pills, a sort — five rows of controls above the first thing you actually
need, on a screen where nobody is holding a basket. What survives that condensing is
views/list-score.ts: what is still to buy, or that everything is in the basket, or that the list is empty. Home no longer asks the shopping anything at all (§1), and the Lists tab opens a list rather than an index of them, so the one place a list you are not looking at is described is the picker on the title.- What it says is what is missing, because that is the question you ask about a list you are not standing in. It counts the lines the list's own page shows and not the stored rows, so the two can never disagree about how long the same list is.
- The three answers are different facts. "Empty" and "9 in the basket" are not the same list, and a picker that said one for both would send a cook to a shop they had already done.
- Everything here is
/lists/[listId]'s./listsresolves to a list and draws nothing (§1b), so this page is where a list lives: switching between them, making one, renaming, deleting, filtering, sorting, adding, ticking, notes and clearing are all reachable from it. - The list is the page, and there is no card on it. Owner's ruling, 6 September 2026: "you shouldn't see the weird cards like that … otherwise they are full screen." A card is a container for one thing among several and there is only ever one list on screen, so the container is gone and the lines are ruled edge to edge — which is what a shopping list looks like everywhere one has ever been written.
- ROWS PER SCREEN IS THE MEASURE OF THIS SCREEN AND THERE IS NO SECOND ONE. Owner's ruling, 7
September 2026: "the list view is way too spacy and inefficient for something that must be
absolutely maximum utility", and "not sure about the font either" — one complaint, not two. A
real shop list is twenty to thirty lines and a 390×844 phone was showing six of them, so the
screen showed a fifth of its own subject and a cook scrolled four times to read one list,
one-handed, in a shop. Where something must give it is not the count of items visible. Three
things were paying for that and all three went: the hand came off everything but the list's own
name (DESIGN's third face carries why — a
script face has to be set larger to be legible and a row was tall enough to hold it), a row is
--taprather than--tap-major, and the line you add on is the foot band instead of costing a band above the lines.design/lists.mjscounts the rows, because this is exactly the property a redraw loses with every other check still green.- The count the deck holds is nine whole lines at 390×844 and eight at 360×800. Owner, 2 October 2026: the dish's own line under each name (below) was worth a row; 6 October 2026: the two clearing words at the title row's right (below) cost a third of a row and some title room. The deck's fifteen-line fixture shows nine at 390×844 and seven at 360×740, four rows in five exactly at the floor, and the ceiling a row is measured against is the floor plus the two things that may wrap under a long name, the dish and the amount. The deck and the drive measure the title row asking and at rest, require the same height and lines on screen either way, and hold the clearing stack to a third of a row at most.
- The floor did not move, and it is what stops the number going higher. Every control is
reachable at
--tap: the tick is the whole row at--tap, both marks are--tapsquare, and the two marks stay at the far edge where a thumb reaches for them by position. A row cannot be shorter than that without a ruling that lowers the floor itself. - No name is ever cut to buy it. Density is not a second reason for the defect below.
- It is one viewport, and the lines are what run past a bottom edge. The construction is the one
the card had, one level out: the title row, the readings and the foot band are furniture and
.travelstakes the shortfall. Nothing in it is a measured height, so it holds at any window, the pills a thumb taps on every aisle are on screen at row one and at row twenty-one alike, and a short list is its own height rather than a page stretched to the window. - The unit the amounts read in sits on a row of its own under the title, and it is the one screen
that offers the lemon. Owner, 19 October 2026: "21px, but let's only offer the lemon on grocery
lists", then, asked whether the list's lemon is an on/off or a full switcher, "List gets all
three". So the row is the recipe page's switcher (
MeasureSystems, the brief's bare-word dress, at the left where the brief keeps it) with three buttons: the app's wordmark lemon, Cups and Grams. The lemon is the grocery-store reading (GS), drawn as the app's own lemon (BrandMark, never a new drawing) at 1.45em of the pill's type, about 21px, in full colour whether chosen or not, the yellow wash behind it saying which; it has no letters, so the button is named "Grocery store" ("Épicerie") to a screen reader. The row is its own because the readings row under it is full at 360. The choice is the phone's one stored choice, so Cups or Grams here is the same on every recipe, and a phone left on the lemon reads Cups on a screen that does not offer it (AMOUNTS). The deck holds the row on one line at 390 and 360 with the lemon at 20 to 22px, and the drive walks the three buttons, the one stored choice and the 360 fit. - The order of the page is how often you do the thing, and then it is packed: the title row (the
name and the two clearing words), the unit row, the readings, the lines, and a foot band carrying
the line you add on and nothing else. The add line is in the foot band: it costs no row, is
outside
.travelsso it is never scrolled past, and is nearer a thumb than a band above the lines was. One quiet word, Add, saves the line beside the keyboard's own return — dimmed until something is typed. There was none, on the reading that one field makes Enter the whole of it; the owner, on a phone, 17 September 2026: "Adding something to a list needs a button to save as well as the normal keyboard." Renaming and deleting a list are not on the page at all: they are the rarest acts there are and they are about the list rather than about what is on it, so they hang off the control that names it, on the list's own row in the menu it opens. - A dropdown on the title picks a list, and makes one. Owner's ruling, same day: "there's a
dropdown next to the title for selecting or creating new lists." The title is the list's name,
so the control belongs on it: the whole title row is the button, at
--tap-major, because choosing which list you are shopping from is one of the two things this screen is for. What opens is every list with how much of each is still to buy, the one you are on ticked and saying so inaria-current, a pencil and a bin at the edge of every row, and one foot band: New list, the shared outlined button across the whole width. Owner's ruling, 21 September 2026: "Make the new list button full width and move the rename to be an edit icon next to the list names." The pencil opens the namer for that list and the bin confirms first, each in the row it was pressed on; either acts on the list whose row it is, whichever list you are standing on (grocery-ops.tstakes the id). The two are the line's own marks (.list__mark), quiet and wordless to the eye — the two exceptions the owner ruled — and each names its list to a screen reader: "Rename Weekly shop", "Delete Weekly shop". The bin's confirmation is the ask without its question (DESIGN,ConfirmAsk), every part the house's own and no question in it (owner, 13 October 2026: "the buttons state their actions", and on all three screens that use it, "Everywhere"): the act the outlined button a tap high, naming the list it deletes (Delete Weekly shop, Supprimer Weekly shop: owner, 13 October 2026, the button states exactly what it does), the same words to a screen reader, wrapping onto a second line for a long name and never truncated, and Keep the grey word, so no act in the menu is smaller than its neighbours. Nothing in it takes the button's yellow: the one loud thing on a screen is whatever you should do right now, and that is never a menu you opened to get past; the open list is ticked in the gutter every pick-from-a-list reserves, and New list is the outlined act. This is the whole of how a cook moves between lists, the tab opening one rather than an index of them (§1b), so what an index would have said is said here or nowhere.- It is the app's own menu, the one the versions and the language card use. The owner's
approved picture of 12 October 2026 (one app), in the old dress he put back on 15 October
(DESIGN.md, the pick menu): a white box with its soft edge, each list's name in the
serif over its score as a subline, the open list ticked in the gutter and
aria-currentwith no wash, the pencil and the bin at a row's end, and New list in a foot band of its own (components/list/list-picker.tsxcomposingcomponents/pick-menu.tsx, whose rowend, rowinsteadandfootslots carry the marks, the namer or the delete question, and the button). One thing is open at a time: a namer or the delete question takes the row it was opened from, and the new list's namer takes the foot. The deck (design/lists.mjs) draws it frompick-menu.cssandlist.cssrather than restating them, and holds each property; so does the drive. - The aisle select wears the app's caret, not the phone's. The aisle in a line's sheet
carries the title's own caret over the closed control, the native one off (
.list__select), and the opened sheet is the phone's and stays. The order beside the readings is not a select now: it is the same menu as the title's (below). It offers the aisles the kitchen made (§9) after the built-ins, in the order this phone walks them and in the cook's own words, and a line filed there reads under that aisle's heading on the list and in a recipe's shopping reading alike; an aisle removed from Settings sends its lines back to the guess. - Grocery list always exists and cannot be deleted. Owner's ruling, 7 September 2026:
"Default is always grocery list, the single undeletable list." Which list that is has one home,
defaultListOfinlibs/model— the id a kitchen's first list is always made under, falling back to the first list on a plan written before the rule, so the answer is never nothing while a kitchen holds something. Always exists is the resolver's: arriving at a kitchen with no lists makes it. Cannot be deleted is two things and both are wanted — the picker draws no bin on its row, whichever list you are standing on, andgrocery-ops.tsrefuses the act anyway, a control merely hidden not being a rule. Its pencil stays: renaming keeps the id, so a list called anything is still the one a kitchen may not be left without. - The grocery list is always the top row. Owner's ruling, 3 October 2026: "Grocery list
should always be the top list." Whichever list is open and whichever was stored first, the
picker shows the grocery list on top and every other list after it in stored order.
listsInShowingOrderinlibs/modelis the one home, besidedefaultListOfand keyed through it, so a renamed grocery list keeps its place and a plan with none made under the default id shows its first list first, as it always did. It is a reading, not a rewrite: the stored order is untouched, and which list the Lists tab opens is still the one you last had open (§1b).buildHomeViewapplies it where it makes the picker's rows, and that menu is the only place a kitchen's lists are offered — add to list and a ran-out write to the grocery list with no chooser, and home asks the shopping nothing — so a screen that ever offers a choice of lists maps the same function, neverplan.lists. The deck and the drive hold it, the drive with a list stored first.
- It is the app's own menu, the one the versions and the language card use. The owner's
approved picture of 12 October 2026 (one app), in the old dress he put back on 15 October
(DESIGN.md, the pick menu): a white box with its soft edge, each list's name in the
serif over its score as a subline, the open list ticked in the gutter and
- Three readings, and a clear. All / To buy / Bought filter what is shown; clearing deletes.
They are different acts and are not substitutes: filtering is what you want while shopping, and
it was offered in place of clearing for exactly one commit before the gap showed. A filter that
leaves the page empty says so in words, because the control that emptied it must stay reachable.
- Each reading says what it would show, inside its own pill — its word, and beside it the count of what it would leave. Owner's ruling, same day: "The filters need to be way more sleek." It is home's chip rather than a second idiom, and it absorbs the score that used to sit above the lines: three counts in a row you were already reading cost nothing, where three bare pills and a score beside them cost a row. This is not a count that moves with the filter, which is the thing that would be a count of nothing — each pill states its own partition and goes on stating it whichever reading you are in, and All is the whole list.
- The readings and the order share one row, and take different controls. The filter stays pills
because it is switched all the way round the shop; the order is one menu because it is set once
and left. Two rows of pills read as one six-way choice when they are two independent ones, and put
seven controls above the first thing you are actually buying. Which control a choice wears is
DESIGN.md's rule, under "the pill and the tick". The closed
control is the chosen order drawn as a small grey word with its caret, and it opens the app's
own menu (
PickMenu, as the title's does), its box standing at the row's right end under the word rather than off the phone's edge. It was a native select lying unseen over that word at the field's floor (PLATFORMS.md, zoom on focus), and opened the phone's sheet; the menu has no field to zoom to. The readings are the larger of the two, the pills at 0.9rem and the order at 0.75rem, since the pills are the thing switched all round the shop.- Read by recipe, the join stops at the dish. Owner, 13 September 2026: every line had come
off a recipe, and By recipe filed the shared ones under Anything else — the join across the
list had dropped the dish two sources disagreed over. Under a dish's heading a line is that
dish's, with that dish's own figure: garlic two dinners want is one line in every other order
and a line under each of them here (
joinGroceryItems'sdishscope), and the pills count the lines that reading draws. Anything else is what no dish put there, and nothing more. - Both are sticky. Owner's ruling, 11 September 2026: "the grocery list filters and ordering
need to be sticky." The reading you were in and the order you set are where you left them on
the next open and after a reload — a phone goes in a pocket halfway round the shop and comes
back out in the same reading, and an order set once is not asked for again. They are the
phone's own (
hooks/use-shop-reading.ts), never synced, the same call as which list is open: two phones in one house shop the same list in different readings on the same evening.
- Read by recipe, the join stops at the dish. Owner, 13 September 2026: every line had come
off a recipe, and By recipe filed the shared ones under Anything else — the join across the
list had dropped the dish two sources disagreed over. Under a dish's heading a line is that
dish's, with that dish's own figure: garlic two dinners want is one line in every other order
and a line under each of them here (
- Nothing moves when a line is ticked. Owner's ruling, 6 September 2026: "why does the order
change as things are crossed on and off?" Crossing something off is the one act a cook does with
their hands full, at the moment they are least able to re-find their place, so the order is a
function of the list and never of what is in the basket: a ticked line is struck through where
it stands, and no line around it stirs. A ticked line used to sink to the foot of its group; that
is what the To buy reading is for now, and asking for it beats having it happen under a thumb.
- To buy is the one reading a ticked line leaves, and that is its definition rather than an exception: you asked to be shown what is left, and a line you have just found is not. All and Bought never move at all.
- But it leaves slowly: struck through, fading, and gone a few seconds later. Owner's ask,
27 September 2026 — "cross out + delayed removal + fade + undo". A line that vanished on the
tap gave a cook nothing to check it against, and nothing to take back, at the one moment their
hands are full. So the reading lets go of it and the screen keeps it for
LEAVES_AFTER_MS(lib/leaving-motion.ts, two seconds;hooks/use-leaving.tsholds), drawn where it stood, at full ink for the first beat and never faded to nothing. The undo is the row itself: it is still there and the tick is still the whole row, so tapping it again puts it back, fading in from wherever the fade had reached — there is no Undo button, no toast and no snackbar, this screen having no room for furniture that says what the row already says. This is the app's one leaving motion (owner, 19 October 2026):.leavesand.leaves--goinginglobals.css, drawn across--leaves-after, which the document carries from that same figure so the animation and the timer cannot disagree, and--ease-leavesintokens.css. Everything that offers Undo and takes something off the screen uses it: today that is this tick and Abandon on home's rows (§1). Changing reading drops every hold: a different partition is a fresh question, and a line fading in it would be answering the last one. - A merged line still restates its own figure (
joinGroceryItems): two recipes' butter reads as what is outstanding, so ticking part of it changes the amount that line prints. That is the line answering for itself and not the list re-sorting — nothing above or below it moves.
- A line you ran out of mid-cook reads on its own, ahead of every dish, whatever dish caused it.
Grouping it under the dish does not merely blur why it is there, it merges two different facts: the
pecorino you chose to buy on Sunday and the pecorino that ran out on Thursday would land in one
group, indistinguishable — and merge again the next time you cook it. A planned line is a decision
and a ran-out is a surprise, and the surprise is the part you need at the shop. So it is marked as
one (
ranOut), the mark outranks the dish when the list is read by recipe, and it is never deduped against a planned line of the same ingredient: two lines is the correct answer. Where it is put there is §3. - Every line says which dish put it there. Owner's ask, 27 September 2026. A list is a list of
decisions somebody took days ago, and "why is this on here" was a question the screen could not
answer at all outside the by-recipe reading — a cook standing in an aisle looking at "pecorino
100 g" with no way to know whether it is tonight's or Thursday's.
- A subline of its own at the foot of the row, under the cook's own note, and worded as the
dish rather than as a sentence about it (
.list__from,views/shopping-dishes.ts). It was drawn beside the name first, to spend no row on it, and read "for Sunday roast"; the owner ruled on seeing it — "The recipe subline need to be their own line, none of this 'for x' cuteness." What it costs is measured above, and the deck's counts still hold: a row with a note already had a second line, so the ones that grew are the plain ones. - Every dish, not the one the stored row happens to name. A line two recipes want is one line at
the shop (
joinGroceryItems) and both of them are why it is there, so the names come from the stored rows the fold gathered rather than from the folded row's ownfromRecipeId, which the fold drops when they disagree. Each is a line of its own, up to three, as below. - Nothing at all on a line nobody's recipe asked for, which is half a list — milk, bin bags — and nothing in the by-recipe reading either, where the heading above the row already says it. A recipe deleted with its lines still on the list still reads as a dish rather than as something you typed.
- A recipe's line reads as it does on the brief and the cook screen: the variety beside the
food, no "canned" beside a tin amount, "optional" after the line. It is read from the recipe at
draw time and stored on no line;
namestays what the aisle and the sheet read, and a line the cook renamed keeps the cook's words. The same ingredient is one row, and a named variety is a different ingredient: San Marzano tomatoes never join plain ones ("We need to make sure that San Marzano tomatoes are not combined with regular tomatoes", owner, 11 October 2026). Every count folds the same way. A line several dishes share is one grey line per dish (owner, 19 October 2026: "every recipe share should be its own line"): the dish's title at the left and its own amount and form at the right ("Rigatoni with grated parmesan ... 50-75 g, grated"), each amount drawn byAmountTextas the total beside the name is, and a merged row some of its dishes leave out says which with "(optional)" after that dish's title. The form is the ingredient'spreparation, read at draw time as the variety is (ListedIngredient) and stored on no line, so the shopper who sees one wedge for two dishes sees which is grated and which shaved; a dish whose recipe states none shows its amount alone. At most three of those lines, then one more grey line counting the rest ("+2 more",listWords'dishes; owner, 19 October 2026: "cap at 3 then +N"), the first three in the order the dishes come; the total and the wedge still cover every recipe, drawn or not. Where the food is sold in one piece the total leads with what you buy ("parmesan ... a wedge, at least 4 1/2 oz", AMOUNTS.md has the rule), and each dish then says its recipe's own measure, read from the recipe and not this phone's unit. For any other joined food the total beside the name shows only where the shares add, and not at all where they do not (a cup and some grams,figuresApart): each dish's line says its own, and the sheet still starts from the figures side by side.
- A subline of its own at the foot of the row, under the cook's own note, and worded as the
dish rather than as a sentence about it (
- Every line takes a note of its own, and a line without one costs nothing. A shopping note is
what you tell yourself at the shop — get the ripe ones, two if they are ripe, the brand at the
back. None of it is about the dish, so none of it goes near the recipe.
- It is a field on the item, not a fourth kind of note (
GroceryItem.note). A cooking note is about an evening and is folded from that session's log, which is the rule at the foot of this file; a grocery line has no session and no log — it is a stored row besideboughtandfromRecipeId, and what you wrote on it is that same kind of fact. Sharing the word is not a reason to share a home. Being on the plan is also how it travels: the plan syncs as one document, so a note reaches the other phone in the house the wayboughtdoes. - An empty note is no note. The field is absent rather than
'', the writer trims and drops the key, and clearing the box and saving is how a note is taken back. There is no second control for unsaying something. - Read on the row, in every reading. The note is painted under the name, whatever the filter and whatever the sort, because reading it is the whole point at the shop and a note you must switch a control to see is a note you will not read. A line that has none draws no second line and no mark of its own: no affordance goes on every row for something most rows will never carry, which is the rule that deleted the always-visible "Ran out" pill (§13).
- A note is written where the line is edited, which is the row's own edit rather than a mode the whole page goes into. The card used to open every box at once from its foot; one line is what a cook actually has something to say about, and the boxes cost every other row a field.
- It is a field on the item, not a fourth kind of note (
- A row's resting state is the line, one mark and a grip. Owner's ruling, 6 September 2026:
"why are there so many buttons for items … an edit icon next to the name and a settings icon.
Those are standard enough, no labels needed." It was two icons until 23 September, when the same
complaint came back about the pair: "move the aisle setting to the bottom of the other edit stuff
so the icons are less busy" — so the gear went, its sheet folded into the pencil's, and what sits
in that seat now is the grip, a hairline handle rather than a mark that says anything. Both are
quiet, unlabelled and identical on every row, which is what makes them furniture a thumb stops
seeing — the rule about an affordance most rows will never carry is about a mark that says
something, and marks that are the same on all twenty rows say nothing about any of them. Each
still carries an accessible name: unlabelled is a fact about the paint, never the announcement.
- The mark opens everything about the line — its name, its amount, its note, and at the foot
where it lives. It is the first time the product lets a cook fix a name: "4 shallot",
copied off a recipe, has never been correctable. There is no Save: the owner, on the same
day, "we don't need a save button, it should save on change immediately", so the sheet writes
after a pause and again on the way out — the recipe draft's own bargain and the same 800ms
(
hooks/use-recipe-draft.ts), since a write per keystroke is a phone writing hundreds of times a minute and walking away mid-word is not losing it. A line called nothing is never written: that is a mistake rather than a rename, andeditItemrefuses it too. - The aisle is a fact about the line rather than a word on it, which is why it comes last. It
is how a cook teaches squeezy where something goes; the correction is kept and keyed to the enum
so the list does not re-sort when the language changes, and it is offered in every reading rather
than only while sorted by aisle. It left the row's resting state long before it left its own
sheet, and it keeps that capability wherever it is drawn.
- One of the aisles is the kitchen's, not the shop's: Essentials, "always in stock." The
owner: "I'd like to have an 'essentials' category that's user-set for things that we always
have in stock. salt/pepper/oil and stuff." It is set here, on the line, since the list is
where a cook notices they need never have bought it, and it is kept exactly as an aisle
correction is — by name, on the plan, the kitchen's, synced — so it reaches every screen
that reads corrections. The table never guesses it (
aisles.ts,essentials: []). On a list a line set essential reads under its own heading, last but for Other, because a line that reached a list is one the cook chose to buy; the recipe's ingredients reading groups the kitchen's essentials in a block of their own at the foot and its Add to list starts the choice without them (§2). The drive sets one and reads it back on the list and the brief. - And it is a tick, not the last option of the aisle select. Owner, 27 September 2026:
"Setting something to an essential is very inefficient." It was — open the line, find the
select, open it, read past eleven shop aisles — where what a cook is doing is answering one
yes-or-no about their own kitchen. So it is
Tick, the app's own checkbox (DESIGN's pill and tick), reading "Always in stock" with what saying so does underneath it, and the two acts left are opening the line and saying so. The shop aisle is then not asked at all, because an essential is not going on a list to be found in an aisle, and the select no longer offers Essentials: the kitchen's own fact has one control and the shop's has the other. They still share one stored field, so unticking hands the line back to the aisle the table guesses for it rather than leaving it nowhere.
- One of the aisles is the kitchen's, not the shop's: Essentials, "always in stock." The
owner: "I'd like to have an 'essentials' category that's user-set for things that we always
have in stock. salt/pepper/oil and stuff." It is set here, on the line, since the list is
where a cook notices they need never have bought it, and it is kept exactly as an aisle
correction is — by name, on the plan, the kitchen's, synced — so it reaches every screen
that reads corrections. The table never guesses it (
- The tick is still the whole row, edge to edge, and the name is still the first thing you read — until the line is open, when the square it starts with becomes the way off the list. The owner's ruling, having cooked with the alternative for an afternoon: "scrap the swipe action. It sucks. Instead, when you click the edit button, change the checkbox on the left to a delete icon." So the act stands where the thumb already is, on the one line a cook has just said they are working on, and no row at rest offers it at all — which is the same bargain the aisle strikes one line up. The words beside it are still the tick, in a button of their own, since one cannot sit inside another; the square is the box's own width, so nothing moves when it changes what it is. It is in the receding ink rather than the error red: an act at rest is not something that has gone wrong.
- Opening a line scrolls it into view. The owner, in the same breath: "when you click Edit,
make sure the screen scrolls so that you see all of the fields." A line near the foot of the
region put its last boxes under the band the page ends on, so a cook typing a note could not see
the box they were typing in. The sheet brings itself into view on open, by the least that works
(
block: 'nearest') rather than by jumping the list to a fixed place. - The name wins the width, and the amount is what gives. The name is what a cook is standing in the aisle to find and the amount is what they re-read at the hob, so the name takes its width first. The three are one wrapping box now and the box order is the priority: the name is laid out at its own width, and where the two will not fit together the amount drops to a second line — right-aligned where it lands, so amounts still read as a column on every row with room for one. Nothing is ellipsised. A name longer than the whole row wraps within itself, which is the last resort and is still readable; a truncation never is. The two marks do not move.
- The mark opens everything about the line — its name, its amount, its note, and at the foot
where it lives. It is the first time the product lets a cook fix a name: "4 shallot",
copied off a recipe, has never been correctable. There is no Save: the owner, on the same
day, "we don't need a save button, it should save on change immediately", so the sheet writes
after a pause and again on the way out — the recipe draft's own bargain and the same 800ms
(
- Under the list's name, how long ago this phone last heard from the server. Owner,
4 October 2026: "can we have like an updated x seconds ago in super small underneath the list
name? So we know exactly that we're getting updated." One line in the smallest type the screen
uses (the micro label's size, in sentence case), the receding ink, and not a control: Updated
just now, Updated 40 seconds ago, Updated 3 minutes ago, ticking while the screen is open.
Offline it says so and says where the ticks are: Offline. Ticks are kept on this phone until
it reconnects. On reconnecting it is the last real time again until the exchange lands. Signed
out it is not drawn, there being no server to have heard from. The time is the sync's own last
successful exchange, never stored as a reading of its own.
- It costs no row, which is the whole constraint: it stands inside the title row's own height, on its floor under the name and the two clearing words, the name making room for it, and the question that takes the row takes the line with it. The deck measures the row, the title and the lines shown against the frame without it, at both widths.
- What it reads, and when it says nothing.
updatedLinefolds the sync's own two records throughsyncStateOf— the last attempt and the journal's stamp for the last exchange that landed — and the connection as it is now, so offline is said the moment the browser says it and not at the next attempt. A phone online that cannot reach the server keeps counting, which is how a cook sees it has stopped updating. Nothing is said signed out, for an account with no kitchen yet, or on a phone that has never heard from a server: each has no figure to give, and none is invented. The words aresync-words.ts's (updated,offlineTicks), under ten seconds "just now" and past a minute the coarsest unit, never rounded up. - The row ticks and the page does not.
ListTopis the title row and owns the clock (useUpdatedLine, once a second while the screen is open), so a render a second is a title and two words and never every line of the list. It is a sentence and not a live region: a reader is not spoken to every second.
- Finishing a shop is two words, one over the other, and neither is behind anything. Owner,
5 October 2026: "on lists the clear purchased, clear all need to be way more clear and separate.
Not hidden." And on 6 October 2026, with Clear purchased at the foot's left: "Clear purchased
can't be bottom left, bottom is all adding an ingredient. How do we combine it with clear all as
an option so they are both visible but not cluttered." Clear all is the word on top at the
title row's right and asks first; Clear purchased is the word under it, dimmed until a
line is ticked, and does not ask; and the foot is the add line alone. They are the
owner's own words (the reading that shows the same lines is still called Bought), and neither is
a pill or the yellow: the readings are a set you switch between, these are one-off acts on the
list itself, and the All pill and Add stay the coloured things. Both are
--taptargets in the screen's quiet type (list-clear.tsx,list-clear-purchased.tsx).- The pick's frames are the list's own, and a round for a pick is drawn again the same way: frames marked proposed, the caption opening with the option's letter, and the deck failing while any is left.
- It costs a third of a row and some title room, and the deck holds both. The stack is two taps tall less what the pair's margins take back, 74px where the name alone made the row 56. It is as wide as Clear purchased, so a list's name has room for about twenty letters on one line at 390 and sixteen at 360 (twenty-four and twenty-one beside Clear all alone), in every language the app reads, which the deck measures by swapping each language's two words into the frame. A longer name wraps into the row. The row is the same height asking and at rest, so nothing under it moves while they decide; a list with nothing on it draws the same stack at the same 74px, both words dimmed (below).
- Clear all is top right, asks first, and is dimmed when there is nothing to lose. Owner's ruling, 7 September 2026: "move clear list to the top right where it's harder to click accidentally." It lived inline with the filters a thumb taps all the way round the shop, one mis-tap from deleting a shop you are halfway round, and then in the foot band, which is the add line's now. Top right is out of a one-handed thumb's arc and nowhere near the pills, and being on top it is where it already stood. It is the page's own ink, the firmer of the two, and the ask stays: a list emptied by a sweep once cost the owner his list.
- The confirming row stands where the two words stood, the list's name stays beside it, and it
asks no question. The ask's row shape (
ConfirmAsk) is that the row replaces the control that opened it; here that control is the stack at the title row's right. With no sentence on the row, the name is what says which list is being emptied, so it stays. Owner, 13 October 2026: "What if we just removed the can we text? That's pointless, the buttons state their actions", and asked whether that held on every screen that confirms: "Everywhere". So the row is the act in the same words as the control that opened it, and Keep — "Delete all 15?" is gone — and the act's accessible name says which list ("Clear all from Weekly shop"). It confirms one thing and has no second answer, and Clear purchased is hidden while it is open, coming back with Clear all on Keep: it rode on the old question from 13 September ("we should have clear bought option in addition to clear list") and that is exactly why a cook never found it — to reach the gentle act they had to open the one that deletes everything. The act and Keep stand one over the other in the stack's own footprint, the act where Clear all stood and Keep where Clear purchased stood: side by side they were a third wider than the stack, the name's box narrowed while asking, and any name near a wrap gained a line and walked everything under it down. So the name keeps its width and the row keeps its height, for every name. - Clear purchased is under it, the quieter, and takes no question. What it takes is what is
already in the basket, and it leaves the rest as next time's list; asking before every
end-of-shop sweep would put a question on the act a cook does most. It is the receding ink and
Clear all is the page's own, so the weight says which is which, and the stack puts Clear all
where a thumb reaches least.
- It is on every list, dimmed until a line is ticked, and the first tick brings nothing in.
Dimmed, it is
disabled— not tappable, out of the tab order, announced as unavailable — the way Add is dimmed until something is typed, and the tick lights a word that was already there: the two words and the add line stand in the same places with a line ticked or none, which the deck measures at both widths and the drive walks across a real tick. Asking before it deletes is not taken.
- It is on every list, dimmed until a line is ticked, and the first tick brings nothing in.
Dimmed, it is
- The foot is the add line alone. One row, the height and the start of an empty list's, with a line on the list or not: the bottom is for adding, and no act that deletes stands beside the field. The field keeps room for what it advertises beside Add, which the deck measures at both widths and in every language the app reads (the French hint is "Autre chose", cut short when the field shared its row with an act).
- The add line is the app's one-line field in its list-line dress, not a bare line.
.fieldon warm paper (DESIGN.md) as.field--line: square, with a 1px--outlinerule under it only, the plus at its left and Add inside it at its right; the field takes the focus ring, not the line inside it. Owner, 13 October 2026: every other one-line field is the boxed pill, and this one is the ruled line because it is typed into in passing, on a list you write down. It had been a bare line with no field around it, which read as no place to type. - The line under the name stands on the row's floor, under both words. Its longest sentence, the offline one, is as wide as the name and then some, and at the name's own depth it ran over Clear purchased; so the name comes down to meet it, level with the middle of the stack, and the deck holds the line clear of both words on every frame that has one.
- An empty list changes nothing above its lines. Owner, 13 October 2026: "The grocery list
filters and clear buttons need to always be there, even if the list is empty", and the rule is
wider than those controls: the header, the title, the line under it, the readings, both clearing
words and the add line are the same elements in the same places empty or full, and only the
lines differ, so the screen does not change shape as the last line goes or the first arrives. A
control that can take nothing says so the way Add does: Clear all and Clear purchased are
dimmed (
disabled, the outline ink), and the three readings stand live at nought, each one still the reading the list opens in once there is a line. What an empty list adds is the sentence saying which kind of empty it is.
On the recipe, off the shop
what was crossed out starts left out, and so does what the kitchen always has in — its essentials (§1d), which this reading groups in a block of their own at the foot, so the salt is still on the recipe and never on the shop; a tap on a row takes it out or puts it back.
From SCREENS.md, Ingredients
On a list a line set essential reads under its own heading, last but for Other, because a line that reached a list is one the cook chose to buy
From SCREENS.md, 1d · Lists
Running out (a swipe on the cook screen) is offered on these and on every spice and pantry line.
From DOMAIN.md, Vocabulary
Ingredients
SCREENS.md, the whole section
- The ingredients reading is the shopping list, and the way onto the grocery list is an action
on the list itself — an action, and never a state. "Add to grocery is an action right on the
ingredient list", then the owner's second ruling: "Add to list is an action, not a state. You
click it, you see all ingredients, can de-select what you already have, and it gets added to
list. You could re-add immediately from the same button again if you want, there's no checked
state of yes it's on the list." So the list's own
BasketButton, on the list's own row with the headcount stepper and the systems, says "Add to list", and the tap opens a choice — the owner: "add to list is supposed to give me the option of removing anything from the add action. In case I want to add most things but know I have pasta". Every row wears the app's tick box (.box, the one the shelf's Filter and the grocery list wear: the app picks several one way), ticked where the row is going on; what was crossed out starts left out, and so does what the kitchen always has in — its essentials (§1d), which this reading groups in a block of their own at the foot, so the salt is still on the recipe and never on the shop; a tap on a row takes it out or puts it back. The screen's yellow becomes the confirming tap — "Add 8 to list", alone on the bar, in place of Start cooking (components/brief/brief-actions.tsx) — the owner: "the add button that gets spawned shouldn't go to the left side … Otherwise, I think I added it, and I didn't notice that there's a new action to complete. Maybe when I click Add to list, the Start cooking button actually needs to change to be Add to list." So the one loud thing on the screen says the act still to finish, and the way out stands in Add to list's exact spot, in its dress but the receding ink, the row keeping the headcount and the systems and its shape. The confirming tap writes exactly those rows, says what it did for a moment ("Added 8"), and the button offers the act again in the same words; Cancel writes nothing. It never reads "On list" and never takes a dish back off; adding again replaces what the list already holds under the same lines, unbought, and doubles nothing. It names the list only where it is not the one every kitchen has; a cook with one list needs no telling which.hooks/use-add-to-list.tscomposes the button, the choice (use-add-choice.ts) and the write; the count the confirming tap says is counted off the very lines the write will put there.- And what the recipe makes for itself is not a line on it at all. The owner's pasta alla vodka
lists "2-4oz pasta water", so his shop asked him to buy pasta water; the graph already held the
answer, a step draining the pasta and leaving reserved pasta water behind, which makes the line
and the preparation the same water with only one of them shopping (
boughtIngredients). Two things have to hold and each was a false positive first: the preparation must bereserved, or "sliced shallots" takes the Wellington's shallots off the shop, and it must not be made out of the ingredient, or a recipe reserving half the stock it told you to buy stops telling you. Derived at read, so a recipe already in a kitchen is right the next time its list is drawn, and the step still says to add it — it is the basket it does not belong in. The count the week's row and the brief print reads the same way, so the number and the rows cannot disagree. Water from the tap, at any temperature, is not a line either. A batch of another recipe ("1 batch pizza dough") stays a row, in a block of its own at the foot headed "From another recipe", because it is the one place before its step that says a second recipe is needed; it is not counted, the choice starts without it as it does the essentials, and the week's add leaves it off. - The rows group by aisle or by part, one switch on a second line. Owner, 25 October 2026:
"the switcher for grouping ingredients by aisle or part on recipe screens", after a tester
(1 October) saw the carrot cake's For the cake and For the frosting flattened into aisles ("If
I prebake cake a week in advance I might buy frosting stuff later"), and ruled on the drawn frames on 26 October:
"Build: pills on 2nd line, ring order". By aisle · By part are two words in Cups ·
Grams's own pill dress (the one in use on the wash of the yellow, never toggling off) on a line
under the headcount and the systems, at the row's left, Add to list at that line's right end
(the stepper, both switches and the button do not fit one 360px line); while the choice is
open the way out stands there instead and the switch stays, so a part can be left off. By
aisle is today's blocks. By part heads each block with the name the overview and the ring
give the part, in the ring's order so the list and the map agree, and puts under it what
its steps take (
ingredientsByPart,views/by-part.ts). A line two parts take (the roulade's salt, tarragon) stands under each at that part's share where the recipe stated every taker's, in the unit its words used (the sauce's tarragon is 1 tbsp); with no share for every taker it stands once, under the first, whole. What no part takes, and the pan seasoning after the parts meet, stands under The rest; the essentials and a batch of another recipe keep their foot blocks either way. In the choice every row is its own tick, so a line is written if any of its rows is ticked, at the ticked shares. The grocery list itself stays by aisle, and a share page draws the rows by aisle with no switch. The choice is a device preference (squeezy.ingredient-grouping, By aisle until chosen), kept like the units. - A tap on a row crosses it out, for this visit only. The owner: "Clicking should just
temporarily cross out." The row is the hob's own checkbox (§13's rule through an ingredient
in the pan), meaning "I have this" here: name and amount struck through in the receding ink,
said as checked to a screen reader, taken back by a second tap, and stored nowhere —
hooks/use-crossed-out.tsis the set, and it is where your eye has got to, not a fact about the recipe. What is crossed out is what Add to list leaves behind. No name wears an underline: the dotted rule that said "tap me for what else works" went with the ladder (below). - No row wears a grip, and nothing opens under a row. Owner, 21 October 2026: "remove the grip
and swipe action on ingredients in recipe ingredient screen. It doesn't work and adds no value.
Will refine editing later." The grip's one act was Note, the cook's own word on an ingredient;
the amount's sheet (next) is where one is written now. A note
(
Recipe.ingredientNotes, never a version: DOMAIN, the note kinds) stands after the name, quieter than it, here and on the cook card. The amounts keep the aisle heading's line with no padding held for a grip, and a sideways swipe on the list is the readings' (§2's "make sure I can swipe between the tabs"). - A tap on an amount opens a small sheet that changes the line. Owner, 21 October 2026:
"Editing needs to be easier for quick ingredient changes"; ruled 22 October, door 1 of the
three drawn: "1 tap the amount", and a tap on the name still crosses the line out. The
amount wears the cooking screen's dotted rule and is a button. The sheet stands over the lower
screen and takes no room from the list (a scrim above it, no row moves: the Amatriciana list is
499px with the door and without, at 390 and 360). It holds the line's name as a field (in the
recipe's English, as the editor's is), the amount with the editor's own unit menu
(
IngredientLine, 44px fields), and the cook's note, over one seat that says what it will do: Done where nothing changed; Save note where only the note did, which writes no version and asks nothing, since a note lives on the recipe row outside every version; Save version where the name, figure or unit did, which is the editor's own save (useSaveVersion) and asks its one question, Twist or Tweak, in the sheet's place, the note written after the version. It edits the recipe as written: where the page reads grams or is scaled, a line under the amount says so, since the figure in the field is not the one the page shows. A version kept becomes the one showing and says "Kept. Version 2 is what this recipe is now." above the yellow. Backing out of the question (Escape, the scrim or the close mark) lands on the fields as they were, the note included, with nothing written. Where the editor holds typing nobody saved (typingAtRisk), Save version asks the replacing-typing question first, since a version takes the recipe's one draft with it. While a cook is on, the sheet is the name and the one sentence the name's menu says ("Stop cooking to edit."). A visitor, another recipe's batch and the list choice (choosing) have no menu. The target is 24px tall (the row's own pitch, a thumb short of 44 by the pick), measured withelementFromPointat 390 and 360. A rename or a new figure reaches every step (owner, 22 October 2026): the version it saves has the new name and figure in the steps' own sentences, in English and French, every id and the line's note kept (AMOUNTS, "A step's own words read as its rows do", is the home). Under the note the sheet lists the steps left as written before anything is saved: "One step still says guanciale." or "…the old amount.", their titles, and "Left as written. Change it in the editor." A name another line has is refused, one sentence under the field and the seat held. Words:views/ingredient-sheet-words.ts. - The name drawn is the one a cook shops for, here and on the step cards alike: the variety an
import keeps apart from the name back beside the food, past the words for how it is bought
("Russet potatoes", "whole peeled San Marzano tomatoes"), a "canned" the amount already says
dropped ("1 (8-oz) can crushed pineapple"), a fill ("packed", "heaping") the amount already
says dropped ("¾ cup packed light brown sugar", never "packed packed"), and "optional" after a line the recipe
lets the cook leave out, quieter than the name.
shownName(libs/core/src/recipes/shown-name.ts) composes it at read, and every word said about the row — its labels, the ladder's question — names the food the same way. The stored name stays the key: the aisle, the list's join (which also reads the variety, section 1), what an act writes, and what Add to list copies. The grocery list draws the same name. - A line that offers a choice says so after its name, in the same quiet type as "optional":
"Ground beef or ground lamb", "Broccoli or carrots or bell pepper". The options are the recipe's
instead, completed at read where the page elided a word (alternativesShown), never stored rewritten, and the name above them is still the one food the row buys and the question the ladder asks. The step cards and the grocery list say it the same way; a merged list row says only the options every recipe behind it offers. - A stored line naming two foods is two rows here ("salt and pepper" as salt and pepper), read
at read and stored nowhere (
twoFoodsAtRead; IMPORT-PIPELINE is the home), so the rows ticked for Add to list are exactly the lines it writes and each food joins its namesake in another dish. The step cards, the cook screen and the editor keep the line as stored. - The rows are blocks by aisle — the owner's pick, lane E of
design/board-ingredients.html, after two rounds of ruled lines ("still not a fan"): each aisle is its own soft block on the container tone, its label at the head and its rows inside with no rule anywhere; the gap between blocks is the aisle break, and nothing else has to say it. No card stands around any of it. The rulings on the rows still hold — "ingredient spacing is too big, can't see anything", "so much spacing … it can be significantly condensed", "spacing between ingredients should be tighter than spacing between ingredient and aisle label" — so a line is under a quarter of a row of air:.brief__aisleBlock,.brief__aisleand.brief__listinglobals.cssare the one home. A row is one line only when its name and its amount both are: an amount such as "1 oz + more for serving" keeps "1 oz" at the amounts' size and sets "+ more for serving" back as a phrase (DESIGN), wrapping in the shared column to stand two lines high beside a one-line name, which is a second line and never a cut. A stated container size is the exception to wrapping ("1 (28-oz; 794 g) can"): a break inside its brackets read as two amounts, and holding it whole in the column would push every name below it right, so it takes the row's first line across both tracks and the name stands under it, still where every other name begins. The deck and the drive both measure the gap between blocks against the air between two rows, the blocks' tone against the page's, and that no rule is drawn in or between them. - The amount is a column at the left, and the aisle's heading has some flair — the owner's
pick, lane B of
design/board-amounts.html: "moving the ingredients amounts to the left side. They should all be aligned so they are easy to read. I think aisle titles need a bit more flair." So every row is the hob's own order, amount then name, the amounts one left-aligned column so a run of figures starts on the same line, the figure set firm and its unit lighter beside it the way a scale reads (ingredient-amount.tsxsplits them into.cook__figureand.cook__unit; a phrase like "to taste" stays one span, in the receding ink), and the name after. An aisle's heading is an italic word in the serif, in the accent ink, with how many things are in it after in the small capitals. The deck and the drive both measure the column's one starting line, the figure's weight against the unit's, and the heading's face, ink and count. - The row's controls wear the strip's dress, and none of them is a white pill. The owner,
on the stepper, the systems and Add to list: "match the styling … no harsh whites." So the
scale is two bare marks in the accent ink either side of "×1"; the systems are two
words in the pill type (Inter 600, mixed case: "Cups Grams", owner, 19 October 2026, the
stove's too; GS is the grocery list's alone, below), the one in use on a wash of the yellow (
--y-100) in the ramp's dark amber rather than the yellow itself; and Add to list is the act's own dress — the accent ink, the mark, no box — and stays in it, there being no state for it to turn another ink for. The deck and the drive both measure that no control on the row carries a rule or the white fill. - The system chosen here reaches the stove. The owner: "why is the unit selection on
ingredients not carried into the recipe itself?" GS, Cups or Grams is a device preference
(
useMeasureSystem), and the cook screen reads every ingredient in it unless a tap on an amount there said otherwise for that one (readingsAtTheStoveinhooks/use-ingredient-units.ts) — and draws the same two words at the ring's corner, so it can be changed without leaving the stove (§3).
- And what the recipe makes for itself is not a line on it at all. The owner's pasta alla vodka
lists "2-4oz pasta water", so his shop asked him to buy pasta water; the graph already held the
answer, a step draining the pasta and leaving reserved pasta water behind, which makes the line
and the preparation the same water with only one of them shopping (
Set with one tick
It is set here, on the line, since the list is where a cook notices they need never have bought it, and it is kept exactly as an aisle correction is — by name, on the plan, the kitchen’s, synced — so it reaches every screen that reads corrections.
From SCREENS.md, 1d · Lists
Owner, 27 September 2026: "Setting something to an essential is very inefficient."
From SCREENS.md, 1d · Lists
The shop aisle is then not asked at all, because an essential is not going on a list to be found in an aisle
From SCREENS.md, 1d · Lists