Recipe to Grocery List
A local-first iOS app that turns recipes into one grocery list. Share a link, paste text, or photograph a page; confirm the ingredients; pick what you're cooking; shop from a single merged, checkable list.
No account. No servers. No tracking. No cloud AI. Recipes, ingredients, and your list live in on-device SQLite. The app makes exactly one kind of network request: when you give it a link, the app downloads that page, once, and nothing else.
Free, no in-app purchases. Product page and support: lukefwalton.com/recipe-to-grocery-list · privacy policy.
Same family as A New Word Every Day and Private Workout Logger (withdrawn October 2026): SwiftUI, on-device, shared design system, XcodeGen, one write path.
What it does
- Capture a recipe from a URL (Share Extension or paste, via schema.org
RecipeJSON-LD with a microdata fallback), pasted text, or a photo (Vision OCR). Every path becomes the same thing: a list of raw ingredient lines. - Confirm the parse. The app proposes quantity, unit, name, note and a canonical ingredient for each line; you fix what's wrong and save. Lines it can't place are shown as New ingredient — create one, match it to one you have, or keep it as text. Nothing is ever dropped.
- Select any saved recipes as "cooking this".
- Shop from one list grouped by ingredient and store aisle, with the lines from each recipe underneath. Amounts are summed only when the units match; nothing is ever converted. Check state lives on the ingredient and survives adding or removing recipes. Done shopping clears it in one tap.
How it's built
- Storage — raw
sqlite3, forward-only migrations, a singleRecipeDraft → validate → transaction → savewrite path (RecipeStore.save), and a CI guard that no view touches the database and the Share Extension never opens it. Inherited from the workout app. - Ingredient identity — stable slugs, a hand-authored CC0 seed built from
scripts/seed_source.json, an alias table with one owner per alias, and a deterministic-first resolution stack: exact → alias → loose → fuzzy (Jaro–Winkler, symmetric tokens) → on-device embeddings → on-device LLM. Every layer proposes; the user confirms; the store writes. - Parsing — a regex splitter that runs everywhere, refined per recipe by Apple Foundation Models on iOS 26 devices that have it (gated at compile time and at runtime), mapped back per row so one unreadable line never costs the rest. Measured against a golden corpus whose pass rate CI prints and asserts.
- The list — a pure function of
(selected recipes, manual items, check state); no persisted list table. - Import — one
httpsGET, five same-scheme redirects at most, no cookies, no JavaScript, no sub-resources, 15 s, 3 MB; the page is parsed once and discarded. The Share Extension writes a one-way inbox file in the App Group and does nothing else. - Design —
lfwdesignsystem(vendored from the word app): Forest theme by default, Fraunces for display, Inter for chrome, the palette picker; the typeface picker offers only the two bundled faces.
Documentation
This README is the overview. docs/SPEC.md is the normative design document; when the two disagree, the spec wins and the README gets fixed.
- docs/SPEC.md — the v1 design: principles, inheritance, data model, parse pipeline, units policy, list behavior, privacy, testing, the PR-gate table the build followed, open decisions.
- docs/privacy.md — a mirror of the privacy policy. The canonical copy is lukefwalton.com/recipe-to-grocery-list/privacy; the in-app screen and the App Store label use the same words, and an edit starts on the site.
- docs/prior-art-and-licensing.md — what was reused, what was deliberately not, and why the seed is our own.
- docs/app-store-metadata.md and docs/app-store-review-notes.md — listing copy (no price words), review notes, the 1.0 checklist.
- docs/learnings/ — decision write-ups, one per gate, 001–012: scaffold and inheritance, the seed and normalizer, fuzzy thresholds calibrated on the corpus, per-row model fallback, the computed list, URL import and the inbox, photo import, the device without Apple Intelligence, App Store readiness, the CI guards that stand in for a compiler, the first read-through, and release prep. docs/publishing-source.md is the going-public checklist.
- .github/STANDARDS.md — the doctrine PRs are reviewed against. NOTICE.md — the reuse log.
Status
1.0 (build 1) submitted to App Review, October 2026. Every gate in SPEC §12 landed in order; the real app icon shipped with the archive; the Support and Privacy pages are live. Pricing is decided (free) and so is the license (MIT), both in 012. The App Store link lands here and on the product page once the app is approved.
Building
brew install xcodegen
python3 -m pip install pyyaml # generate.sh merges YAML with PyYAML
cp project.local.yml.example project.local.yml # set DEVELOPMENT_TEAM (gitignored)
bash scripts/fetch_fonts.sh # Fraunces + Inter, OFL, not committed — BEFORE generate
bash scripts/generate.sh
bash scripts/run_tests.sh # or open RecipeToGroceryList.xcodeproj and ⌘U
Register the App Group (group.com.lukewalton.recipetogrocerylist) for your team;
the Share Extension needs it. CI: .github/workflows/ios-tests.yml (unit tests +
a Foundation Models compile guard) and checks.yml (design-system checksum, App
Group sync, font list, store boundary, seed regeneration, golden-corpus fixture,
the SQL schema executed and diffed against the spec, and a source check that every
file is compiled and imports what it uses).
License
Code: MIT, see LICENSE, including the vendored lfwdesignsystem/.
Bundled fonts are SIL OFL 1.1 and the ingredient seed is CC0.
NOTICE.md logs third-party references (fonts, the seed vocabulary,
build tooling). Going public? See docs/publishing-source.md.
