Vibecode AnyList Complete
track this build5 phases, 15 steps, beginner friendly0%The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For AnyList Complete, manage shared grocery lists, recipes, and meal plans with a simple household sync server. The hard boundary is excellent mobile capture, retailer integrations, voice assistants, and polished sharing, plus sync, integrations, and household adoption.
You are building a lean indie version of AnyList Complete. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # AnyList Complete · indie build Shared grocery lists that work in a shop with no signal and merge sensibly when two people add items at once, recipes with ingredients that generate a list, a weekly meal plan, and a printable shopping list for the person who would rather have paper. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with SQLite | a household is a handful of people and a few thousand rows | | Client | An installable progressive web application | shops have bad signal and app stores are not worth it for four people | | Offline | IndexedDB with an operation queue | the whole point is that it works in the shop, where the signal is worst | | Merge | Per-item operations rather than whole-list writes | two people editing one list at once is the normal case, not an edge case | | Access | One household, invite links, no public signup | a family application does not need accounts for strangers | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the sync server and the build. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Two phones in the household** · free - Why: Phase 2's merge behaviour needs two real devices, and the offline case needs a real shop with real signal problems. - Get it: Your own and one other household member's. Ten minutes of their time, twice. - Verify: Two devices are installed and can both edit a list - [ ] **An HTTPS URL for development** · free - Why: Service workers and installation both require a secure context, so a local address will not work on a phone. - Get it: A tunnelling tool such as cloudflared, which is free. - Verify: The phone loads it over HTTPS and the service worker registers - [ ] **A small server or a machine at home** (optional) · 5 to 10 USD a month, or free on hardware you own - Why: Product phase only. Sync needs somewhere both phones can reach. - Get it: Any small virtual server, or a machine at home behind a tunnel. This workload is tiny. - Verify: Both phones reach it over HTTPS - [ ] **Five recipes from different sites** · free - Why: Phase 3's import needs variety: some sites publish structured data and some do not, and you need both cases. - Get it: Pick five you actually cook, from five different sites. Keep the URLs as fixtures. - Verify: At least one imports structurally and at least one falls back to paste ## Quick start ```sh npm create vite@latest . -- --template vanilla-ts npm install -D vite-plugin-pwa ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Barcode scanning, voice entry and mobile capture polish. - Retailer integrations that place an order. - Voice assistant integration. - Setup-free sharing for a household member who will not tolerate a step. - excellent mobile capture, retailer integrations, voice assistants, and polished sharing - frictionless family sync - retailer and smart-home integrations - large recipe or product database - mobile polish If one of those is essential to you, that is the reason to keep paying for AnyList Complete, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · AnyList Complete The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a shared household list, recipe and meal plan app on a small server your family actually uses to replace AnyList Complete. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node 22 with SQLite · a household is a handful of people and a few thousand rows - Client: An installable progressive web application · shops have bad signal and app stores are not worth it for four people - Offline: IndexedDB with an operation queue · the whole point is that it works in the shop, where the signal is worst - Merge: Per-item operations rather than whole-list writes · two people editing one list at once is the normal case, not an edge case - Access: One household, invite links, no public signup · a family application does not need accounts for strangers ### Data model (create this before Phase 1) - households(id, name, created_at), members(id, household_id, name, invite_token, joined_at) - lists(id, household_id, name, position) - items(id, list_id, text, quantity, category, checked_at, checked_by, created_at, updated_at, deleted_at) · deleted_at is a tombstone, never a removed row - recipes(id, household_id, title, source_url, servings, instructions), recipe_ingredients(recipe_id, text, quantity, unit, category) - plans(id, household_id, week_start), plan_entries(plan_id, day, meal, recipe_id, note) - operations(id, member_id, entity, entity_id, op, payload, at) · the sync log, which is what makes concurrent editing work - Deletions are tombstones. A deleted item that is simply absent will be resurrected by the next device to sync. ### Phase 1 · A list that works with no signal Build: An installable progressive web application reading from and writing to IndexedDB. Every change is an operation queued locally. The interface never waits on the network. Install to the home screen and test in aeroplane mode. Trap: Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. Done when: With the network disabled, items can be added and checked off and they survive a reload. The application installs to the home screen. The interface never blocks on a network request. A queued operation persists across an application restart. Do not build yet: No sync, no recipes, no plans. ### Phase 2 · Sync that merges rather than clobbers Build: A small server storing operations per household. Clients push their queue and pull operations since a watermark. Merge per item rather than per list. Deletions travel as tombstones. Trap: Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. Done when: Two devices adding different items offline both end up with all items after sync. Two devices checking off the same item converge without duplicating it. An item deleted on one device stays deleted after the other syncs. A device offline for a day catches up without losing anything. Do not build yet: No recipes or plans yet. ### Phase 3 · Recipes, and getting them in without typing Build: Recipes with ingredients, servings and instructions. Import by pasting a URL and reading the structured recipe data most cooking sites publish, falling back to pasting the text. Scale ingredient quantities by servings. Trap: Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. Done when: A recipe URL from a site publishing structured data imports with ingredients and instructions. A site without it falls back to pasting, which works. Doubling the servings doubles numeric quantities and leaves quantities like a pinch alone. Adding a recipe to a list creates one item per ingredient. Do not build yet: No meal plan yet. ### Phase 4 · Meal plans and a list that makes sense in a shop Build: A weekly plan of recipes by day and meal. Generating a list combines ingredients across recipes, merging duplicates and summing quantities where units match. Group the list by shop section so it follows the route rather than the recipes. Trap: Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. Done when: A week of five recipes generates one list. Ingredients appearing in two recipes with the same unit are merged with quantities summed. Ingredients with incompatible units appear as separate lines rather than being wrongly combined. The list is grouped by shop section. Do not build yet: No sharing or printing yet. ### Phase 5 · The household, and paper Build: Invite links joining a household, with no public signup. A print view producing a clean one-page list. Show who checked off what, gently, because in a shared list that is the information people want. Trap: A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done. Done when: An invite link adds someone to the household and nothing else. There is no public signup path. The print view fits a normal shopping list on one page with no interface furniture, verified by printing it. Checked items show who checked them without making it a leaderboard. Do not build yet: Operations come last. ### Phase 6 · Hosting, backups and handing it over Build: Deploy behind TLS. Back up the database off the machine. Export everything as JSON and as readable Markdown recipes. Write a note the household can follow if it stops working while you are away. Trap: A household application that only you can fix becomes a problem the week you are away, and the recipes people rely on are suddenly unavailable. The export and the note are what make that recoverable by someone else, and both need to exist before they are needed. Done when: The application serves over HTTPS and the household can reach it. Nightly backups land off the machine. Export produces JSON and readable Markdown recipes. A restore onto a clean server reproduces lists, recipes and plans, and the date is recorded. A household note explains what to do if it stops working. Do not build yet: Nothing after this. ### Out of scope (and why) - The mobile capture polish of the commercial application: barcode scanning, voice entry and the widget that makes adding an item take two seconds. - Retailer integrations that turn a list into an order, which are per-retailer partnerships rather than features. - Voice assistant integration, which needs a reviewed relationship with each platform. - The frictionless sharing that makes it work for a family member who will not tolerate a setup step. ### README must contain - That the application works offline and sync merges per item rather than per list - That deletions are tombstones, because an absent row comes back - That recipe import reads structured data first and falls back to pasting - That ingredients merge only where units are compatible - The backup schedule, the export formats, and the household note for when you are away ===== AGENTS.md ===== # Agent instructions · AnyList Complete indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22 with SQLite, An installable progressive web application, IndexedDB with an operation queue, Per-item operations rather than whole-list writes, One household, invite links, no public signup. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. - Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. - Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. - Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. - A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done. ===== BUILD_PLAN.md ===== # Build plan · AnyList Complete Shared grocery lists that work in a shop with no signal and merge sensibly when two people add items at once, recipes with ingredients that generate a list, a weekly meal plan, and a printable shopping list for the person who would rather have paper. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A list that works with no signal You can add and check off items in a shop with the phone offline, and nothing is lost. ### Steps 1. Build the offline shell Service worker precaching the shell, a manifest with icons, and IndexedDB for data. Files: `vite.config.ts` ```sh npm create vite@latest . -- --template vanilla-ts npm install -D vite-plugin-pwa ``` 2. Queue every change as an operation Local write first, queue second, network never on the critical path. Files: `src/ops.ts` 3. Test in aeroplane mode on a real phone In an actual shop if you can. Desktop testing hides everything that matters here. ### Done when - [ ] Items can be added and checked with networking disabled - [ ] Changes survive a reload and an application restart - [ ] The application installs to the home screen - [ ] The interface never blocks on the network ### Watch out - Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. ## Phase 2 · Sync that merges rather than clobbers Two people editing the same list at the same time both keep their changes. ### Steps 1. Push and pull operations Per household, since a watermark. Small payloads and simple ordering. Files: `server/sync.ts` 2. Merge per item Additions merge, checks are last-write-wins per item, deletions are tombstones. Test each case. Files: `src/merge.ts` 3. Test the offline divergence case Both devices offline, both editing, then both reconnect. This is the case that matters and the one that is skipped. Files: `test/merge.test.ts` ### Done when - [ ] Two devices adding offline both keep their items - [ ] Checking the same item converges without duplication - [ ] A deletion survives sync rather than reappearing - [ ] A day offline catches up without loss ### Watch out - Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. ## Phase 3 · Recipes, and getting them in without typing A recipe can be added from a page in a few seconds, and its ingredients become list items. ### Steps 1. Read structured recipe data first Most cooking sites publish it. It is far more reliable than parsing the visible page. Files: `src/import/structured.ts` 2. Fall back to paste A text box with a light parser for quantities. Honest and adequate, unlike a fragile scraper. Files: `src/import/paste.ts` 3. Scale quantities carefully Numbers scale; a pinch does not. Test with a recipe containing both. Files: `src/scale.ts` ### Done when - [ ] A structured recipe URL imports with ingredients and instructions - [ ] A site without it falls back to paste - [ ] Doubling servings scales numbers and leaves non-numeric amounts alone - [ ] Adding a recipe creates one list item per ingredient ### Watch out - Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. ## Phase 4 · Meal plans and a list that makes sense in a shop A week of meals produces one sensible shopping list. ### Steps 1. Build the weekly plan Days and meals, with recipes assigned and free-text notes for the nights nobody is cooking. Files: `src/plan.ts` 2. Combine ingredients conservatively Merge on name and compatible unit only. Anything else stays separate. Files: `src/combine.ts` 3. Group by shop section Categories assigned per ingredient, ordered to match a real shop. This is what makes the list usable. Files: `src/categories.ts` ### Done when - [ ] Five recipes generate one combined list - [ ] Same-unit duplicates merge with summed quantities - [ ] Incompatible units stay as separate lines - [ ] The list is grouped by shop section ### Watch out - Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. ## Phase 5 · The household, and paper Everyone in the house can use it, including the person who wants it printed. ### Steps 1. Add invite links only A token that joins a household. No public signup, because a family application does not need one. Files: `server/invite.ts` 2. Write a real print stylesheet Items, quantities and sections. No navigation, no colours, nothing cut off. Print it on paper. Files: `src/print.css` 3. Attribute gently Who checked an item, shown quietly. Useful in a shared list, and not a scoreboard. Files: `src/items.ts` ### Done when - [ ] An invite link joins a household and grants nothing else - [ ] There is no public signup path - [ ] The print view fits one page cleanly on real paper - [ ] Checked items show who checked them without ranking anyone ### Watch out - A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done. ## Not in this build - Barcode scanning, voice entry and mobile capture polish. - Retailer integrations that place an order. - Voice assistant integration. - Setup-free sharing for a household member who will not tolerate a step. ## After v1, if you want it - A pantry list so the shopping list can subtract what you already have - Recurring items that reappear weekly without being added - A simple nutrition note per recipe, entered by hand rather than looked up ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Lists, recipes, plans and the operation log. DB_PATH=./data/household.db # Required. Used in invite links. Must be HTTPS or installation and offline support will not work. PUBLIC_URL=https://lists.example.com # Required · secret. Signs invite tokens so a household cannot be joined by guessing. INVITE_SECRET=generate-a-long-random-value # Required. How often a foregrounded client pulls. Frequent enough to feel shared, infrequent enough to be polite. SYNC_PULL_INTERVAL_SECONDS=30 # Required. How long the sync log is kept. Long enough for a device that has been off for a while. OPERATION_RETENTION_DAYS=60 # Optional. Product phase only. Where nightly backups go. BACKUP_DEST=s3://your-bucket/household
You are building a lean indie version of AnyList Complete. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # AnyList Complete · indie build Shared grocery lists that work in a shop with no signal and merge sensibly when two people add items at once, recipes with ingredients that generate a list, a weekly meal plan, and a printable shopping list for the person who would rather have paper. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with SQLite | a household is a handful of people and a few thousand rows | | Client | An installable progressive web application | shops have bad signal and app stores are not worth it for four people | | Offline | IndexedDB with an operation queue | the whole point is that it works in the shop, where the signal is worst | | Merge | Per-item operations rather than whole-list writes | two people editing one list at once is the normal case, not an edge case | | Access | One household, invite links, no public signup | a family application does not need accounts for strangers | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the sync server and the build. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Two phones in the household** · free - Why: Phase 2's merge behaviour needs two real devices, and the offline case needs a real shop with real signal problems. - Get it: Your own and one other household member's. Ten minutes of their time, twice. - Verify: Two devices are installed and can both edit a list - [ ] **An HTTPS URL for development** · free - Why: Service workers and installation both require a secure context, so a local address will not work on a phone. - Get it: A tunnelling tool such as cloudflared, which is free. - Verify: The phone loads it over HTTPS and the service worker registers - [ ] **A small server or a machine at home** (optional) · 5 to 10 USD a month, or free on hardware you own - Why: Product phase only. Sync needs somewhere both phones can reach. - Get it: Any small virtual server, or a machine at home behind a tunnel. This workload is tiny. - Verify: Both phones reach it over HTTPS - [ ] **Five recipes from different sites** · free - Why: Phase 3's import needs variety: some sites publish structured data and some do not, and you need both cases. - Get it: Pick five you actually cook, from five different sites. Keep the URLs as fixtures. - Verify: At least one imports structurally and at least one falls back to paste ## Quick start ```sh npm create vite@latest . -- --template vanilla-ts npm install -D vite-plugin-pwa ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Barcode scanning, voice entry and mobile capture polish. - Retailer integrations that place an order. - Voice assistant integration. - Setup-free sharing for a household member who will not tolerate a step. - excellent mobile capture, retailer integrations, voice assistants, and polished sharing - frictionless family sync - retailer and smart-home integrations - large recipe or product database - mobile polish If one of those is essential to you, that is the reason to keep paying for AnyList Complete, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · AnyList Complete The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a shared household list, recipe and meal plan app on a small server your family actually uses to replace AnyList Complete. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node 22 with SQLite · a household is a handful of people and a few thousand rows - Client: An installable progressive web application · shops have bad signal and app stores are not worth it for four people - Offline: IndexedDB with an operation queue · the whole point is that it works in the shop, where the signal is worst - Merge: Per-item operations rather than whole-list writes · two people editing one list at once is the normal case, not an edge case - Access: One household, invite links, no public signup · a family application does not need accounts for strangers ### Data model (create this before Phase 1) - households(id, name, created_at), members(id, household_id, name, invite_token, joined_at) - lists(id, household_id, name, position) - items(id, list_id, text, quantity, category, checked_at, checked_by, created_at, updated_at, deleted_at) · deleted_at is a tombstone, never a removed row - recipes(id, household_id, title, source_url, servings, instructions), recipe_ingredients(recipe_id, text, quantity, unit, category) - plans(id, household_id, week_start), plan_entries(plan_id, day, meal, recipe_id, note) - operations(id, member_id, entity, entity_id, op, payload, at) · the sync log, which is what makes concurrent editing work - Deletions are tombstones. A deleted item that is simply absent will be resurrected by the next device to sync. ### Phase 1 · A list that works with no signal Build: An installable progressive web application reading from and writing to IndexedDB. Every change is an operation queued locally. The interface never waits on the network. Install to the home screen and test in aeroplane mode. Trap: Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. Done when: With the network disabled, items can be added and checked off and they survive a reload. The application installs to the home screen. The interface never blocks on a network request. A queued operation persists across an application restart. Do not build yet: No sync, no recipes, no plans. ### Phase 2 · Sync that merges rather than clobbers Build: A small server storing operations per household. Clients push their queue and pull operations since a watermark. Merge per item rather than per list. Deletions travel as tombstones. Trap: Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. Done when: Two devices adding different items offline both end up with all items after sync. Two devices checking off the same item converge without duplicating it. An item deleted on one device stays deleted after the other syncs. A device offline for a day catches up without losing anything. Do not build yet: No recipes or plans yet. ### Phase 3 · Recipes, and getting them in without typing Build: Recipes with ingredients, servings and instructions. Import by pasting a URL and reading the structured recipe data most cooking sites publish, falling back to pasting the text. Scale ingredient quantities by servings. Trap: Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. Done when: A recipe URL from a site publishing structured data imports with ingredients and instructions. A site without it falls back to pasting, which works. Doubling the servings doubles numeric quantities and leaves quantities like a pinch alone. Adding a recipe to a list creates one item per ingredient. Do not build yet: No meal plan yet. ### Phase 4 · Meal plans and a list that makes sense in a shop Build: A weekly plan of recipes by day and meal. Generating a list combines ingredients across recipes, merging duplicates and summing quantities where units match. Group the list by shop section so it follows the route rather than the recipes. Trap: Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. Done when: A week of five recipes generates one list. Ingredients appearing in two recipes with the same unit are merged with quantities summed. Ingredients with incompatible units appear as separate lines rather than being wrongly combined. The list is grouped by shop section. Do not build yet: No sharing or printing yet. ### Phase 5 · The household, and paper Build: Invite links joining a household, with no public signup. A print view producing a clean one-page list. Show who checked off what, gently, because in a shared list that is the information people want. Trap: A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done. Done when: An invite link adds someone to the household and nothing else. There is no public signup path. The print view fits a normal shopping list on one page with no interface furniture, verified by printing it. Checked items show who checked them without making it a leaderboard. Do not build yet: Operations come last. ### Phase 6 · Hosting, backups and handing it over Build: Deploy behind TLS. Back up the database off the machine. Export everything as JSON and as readable Markdown recipes. Write a note the household can follow if it stops working while you are away. Trap: A household application that only you can fix becomes a problem the week you are away, and the recipes people rely on are suddenly unavailable. The export and the note are what make that recoverable by someone else, and both need to exist before they are needed. Done when: The application serves over HTTPS and the household can reach it. Nightly backups land off the machine. Export produces JSON and readable Markdown recipes. A restore onto a clean server reproduces lists, recipes and plans, and the date is recorded. A household note explains what to do if it stops working. Do not build yet: Nothing after this. ### Out of scope (and why) - The mobile capture polish of the commercial application: barcode scanning, voice entry and the widget that makes adding an item take two seconds. - Retailer integrations that turn a list into an order, which are per-retailer partnerships rather than features. - Voice assistant integration, which needs a reviewed relationship with each platform. - The frictionless sharing that makes it work for a family member who will not tolerate a setup step. ### README must contain - That the application works offline and sync merges per item rather than per list - That deletions are tombstones, because an absent row comes back - That recipe import reads structured data first and falls back to pasting - That ingredients merge only where units are compatible - The backup schedule, the export formats, and the household note for when you are away ===== AGENTS.md ===== # Agent instructions · AnyList Complete indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22 with SQLite, An installable progressive web application, IndexedDB with an operation queue, Per-item operations rather than whole-list writes, One household, invite links, no public signup. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. - Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. - Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. - Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. - A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done. ===== BUILD_PLAN.md ===== # Build plan · AnyList Complete Shared grocery lists that work in a shop with no signal and merge sensibly when two people add items at once, recipes with ingredients that generate a list, a weekly meal plan, and a printable shopping list for the person who would rather have paper. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A list that works with no signal You can add and check off items in a shop with the phone offline, and nothing is lost. ### Steps 1. Build the offline shell Service worker precaching the shell, a manifest with icons, and IndexedDB for data. Files: `vite.config.ts` ```sh npm create vite@latest . -- --template vanilla-ts npm install -D vite-plugin-pwa ``` 2. Queue every change as an operation Local write first, queue second, network never on the critical path. Files: `src/ops.ts` 3. Test in aeroplane mode on a real phone In an actual shop if you can. Desktop testing hides everything that matters here. ### Done when - [ ] Items can be added and checked with networking disabled - [ ] Changes survive a reload and an application restart - [ ] The application installs to the home screen - [ ] The interface never blocks on the network ### Watch out - Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. ## Phase 2 · Sync that merges rather than clobbers Two people editing the same list at the same time both keep their changes. ### Steps 1. Push and pull operations Per household, since a watermark. Small payloads and simple ordering. Files: `server/sync.ts` 2. Merge per item Additions merge, checks are last-write-wins per item, deletions are tombstones. Test each case. Files: `src/merge.ts` 3. Test the offline divergence case Both devices offline, both editing, then both reconnect. This is the case that matters and the one that is skipped. Files: `test/merge.test.ts` ### Done when - [ ] Two devices adding offline both keep their items - [ ] Checking the same item converges without duplication - [ ] A deletion survives sync rather than reappearing - [ ] A day offline catches up without loss ### Watch out - Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. ## Phase 3 · Recipes, and getting them in without typing A recipe can be added from a page in a few seconds, and its ingredients become list items. ### Steps 1. Read structured recipe data first Most cooking sites publish it. It is far more reliable than parsing the visible page. Files: `src/import/structured.ts` 2. Fall back to paste A text box with a light parser for quantities. Honest and adequate, unlike a fragile scraper. Files: `src/import/paste.ts` 3. Scale quantities carefully Numbers scale; a pinch does not. Test with a recipe containing both. Files: `src/scale.ts` ### Done when - [ ] A structured recipe URL imports with ingredients and instructions - [ ] A site without it falls back to paste - [ ] Doubling servings scales numbers and leaves non-numeric amounts alone - [ ] Adding a recipe creates one list item per ingredient ### Watch out - Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. ## Phase 4 · Meal plans and a list that makes sense in a shop A week of meals produces one sensible shopping list. ### Steps 1. Build the weekly plan Days and meals, with recipes assigned and free-text notes for the nights nobody is cooking. Files: `src/plan.ts` 2. Combine ingredients conservatively Merge on name and compatible unit only. Anything else stays separate. Files: `src/combine.ts` 3. Group by shop section Categories assigned per ingredient, ordered to match a real shop. This is what makes the list usable. Files: `src/categories.ts` ### Done when - [ ] Five recipes generate one combined list - [ ] Same-unit duplicates merge with summed quantities - [ ] Incompatible units stay as separate lines - [ ] The list is grouped by shop section ### Watch out - Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. ## Phase 5 · The household, and paper Everyone in the house can use it, including the person who wants it printed. ### Steps 1. Add invite links only A token that joins a household. No public signup, because a family application does not need one. Files: `server/invite.ts` 2. Write a real print stylesheet Items, quantities and sections. No navigation, no colours, nothing cut off. Print it on paper. Files: `src/print.css` 3. Attribute gently Who checked an item, shown quietly. Useful in a shared list, and not a scoreboard. Files: `src/items.ts` ### Done when - [ ] An invite link joins a household and grants nothing else - [ ] There is no public signup path - [ ] The print view fits one page cleanly on real paper - [ ] Checked items show who checked them without ranking anyone ### Watch out - A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done. ## Not in this build - Barcode scanning, voice entry and mobile capture polish. - Retailer integrations that place an order. - Voice assistant integration. - Setup-free sharing for a household member who will not tolerate a step. ## After v1, if you want it - A pantry list so the shopping list can subtract what you already have - Recurring items that reappear weekly without being added - A simple nutrition note per recipe, entered by hand rather than looked up ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Lists, recipes, plans and the operation log. DB_PATH=./data/household.db # Required. Used in invite links. Must be HTTPS or installation and offline support will not work. PUBLIC_URL=https://lists.example.com # Required · secret. Signs invite tokens so a household cannot be joined by guessing. INVITE_SECRET=generate-a-long-random-value # Required. How often a foregrounded client pulls. Frequent enough to feel shared, infrequent enough to be polite. SYNC_PULL_INTERVAL_SECONDS=30 # Required. How long the sync log is kept. Long enough for a device that has been off for a while. OPERATION_RETENTION_DAYS=60 # Optional. Product phase only. Where nightly backups go. BACKUP_DEST=s3://your-bucket/household
You are building a production product version of AnyList Complete. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # AnyList Complete · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For AnyList Complete, manage shared grocery lists, recipes, and meal plans with a simple household sync server. The hard boundary is excellent mobile capture, retailer integrations, voice assistants, and polished sharing, plus sync, integrations, and household adoption. ## Product outcome A household application that works in a shop with no signal, merges two people's changes instead of losing one, imports recipes without typing, prints a clean list, and can be recovered by someone else if you are away. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - modern browser - local or self-hosted storage - optional email notifications ## Explicit non-goals for v1 - Barcode scanning, voice entry and mobile capture polish. - Retailer integrations that place an order. - Voice assistant integration. - Setup-free sharing for a household member who will not tolerate a step. - excellent mobile capture, retailer integrations, voice assistants, and polished sharing - frictionless family sync - retailer and smart-home integrations - large recipe or product database - mobile polish ## Success criteria - Offline editing on two devices is confirmed to merge without loss - A deletion is confirmed to survive sync - Ingredient combining is confirmed not to merge incompatible units - The print view is checked on real paper - A restore has been performed and dated, and the household note exists ===== BRIEF.md ===== # Build brief · AnyList Complete The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a shared household list, recipe and meal plan app on a small server your family actually uses to replace AnyList Complete. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node 22 with SQLite · a household is a handful of people and a few thousand rows - Client: An installable progressive web application · shops have bad signal and app stores are not worth it for four people - Offline: IndexedDB with an operation queue · the whole point is that it works in the shop, where the signal is worst - Merge: Per-item operations rather than whole-list writes · two people editing one list at once is the normal case, not an edge case - Access: One household, invite links, no public signup · a family application does not need accounts for strangers ### Data model (create this before Phase 1) - households(id, name, created_at), members(id, household_id, name, invite_token, joined_at) - lists(id, household_id, name, position) - items(id, list_id, text, quantity, category, checked_at, checked_by, created_at, updated_at, deleted_at) · deleted_at is a tombstone, never a removed row - recipes(id, household_id, title, source_url, servings, instructions), recipe_ingredients(recipe_id, text, quantity, unit, category) - plans(id, household_id, week_start), plan_entries(plan_id, day, meal, recipe_id, note) - operations(id, member_id, entity, entity_id, op, payload, at) · the sync log, which is what makes concurrent editing work - Deletions are tombstones. A deleted item that is simply absent will be resurrected by the next device to sync. ### Phase 1 · A list that works with no signal Build: An installable progressive web application reading from and writing to IndexedDB. Every change is an operation queued locally. The interface never waits on the network. Install to the home screen and test in aeroplane mode. Trap: Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. Done when: With the network disabled, items can be added and checked off and they survive a reload. The application installs to the home screen. The interface never blocks on a network request. A queued operation persists across an application restart. Do not build yet: No sync, no recipes, no plans. ### Phase 2 · Sync that merges rather than clobbers Build: A small server storing operations per household. Clients push their queue and pull operations since a watermark. Merge per item rather than per list. Deletions travel as tombstones. Trap: Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. Done when: Two devices adding different items offline both end up with all items after sync. Two devices checking off the same item converge without duplicating it. An item deleted on one device stays deleted after the other syncs. A device offline for a day catches up without losing anything. Do not build yet: No recipes or plans yet. ### Phase 3 · Recipes, and getting them in without typing Build: Recipes with ingredients, servings and instructions. Import by pasting a URL and reading the structured recipe data most cooking sites publish, falling back to pasting the text. Scale ingredient quantities by servings. Trap: Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. Done when: A recipe URL from a site publishing structured data imports with ingredients and instructions. A site without it falls back to pasting, which works. Doubling the servings doubles numeric quantities and leaves quantities like a pinch alone. Adding a recipe to a list creates one item per ingredient. Do not build yet: No meal plan yet. ### Phase 4 · Meal plans and a list that makes sense in a shop Build: A weekly plan of recipes by day and meal. Generating a list combines ingredients across recipes, merging duplicates and summing quantities where units match. Group the list by shop section so it follows the route rather than the recipes. Trap: Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. Done when: A week of five recipes generates one list. Ingredients appearing in two recipes with the same unit are merged with quantities summed. Ingredients with incompatible units appear as separate lines rather than being wrongly combined. The list is grouped by shop section. Do not build yet: No sharing or printing yet. ### Phase 5 · The household, and paper Build: Invite links joining a household, with no public signup. A print view producing a clean one-page list. Show who checked off what, gently, because in a shared list that is the information people want. Trap: A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done. Done when: An invite link adds someone to the household and nothing else. There is no public signup path. The print view fits a normal shopping list on one page with no interface furniture, verified by printing it. Checked items show who checked them without making it a leaderboard. Do not build yet: Operations come last. ### Phase 6 · Hosting, backups and handing it over Build: Deploy behind TLS. Back up the database off the machine. Export everything as JSON and as readable Markdown recipes. Write a note the household can follow if it stops working while you are away. Trap: A household application that only you can fix becomes a problem the week you are away, and the recipes people rely on are suddenly unavailable. The export and the note are what make that recoverable by someone else, and both need to exist before they are needed. Done when: The application serves over HTTPS and the household can reach it. Nightly backups land off the machine. Export produces JSON and readable Markdown recipes. A restore onto a clean server reproduces lists, recipes and plans, and the date is recorded. A household note explains what to do if it stops working. Do not build yet: Nothing after this. ### Out of scope (and why) - The mobile capture polish of the commercial application: barcode scanning, voice entry and the widget that makes adding an item take two seconds. - Retailer integrations that turn a list into an order, which are per-retailer partnerships rather than features. - Voice assistant integration, which needs a reviewed relationship with each platform. - The frictionless sharing that makes it work for a family member who will not tolerate a setup step. ### README must contain - That the application works offline and sync merges per item rather than per list - That deletions are tombstones, because an absent row comes back - That recipe import reads structured data first and falls back to pasting - That ingredients merge only where units are compatible - The backup schedule, the export formats, and the household note for when you are away ===== ARCHITECTURE.md ===== # Architecture · AnyList Complete ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with SQLite | a household is a handful of people and a few thousand rows | | Client | An installable progressive web application | shops have bad signal and app stores are not worth it for four people | | Offline | IndexedDB with an operation queue | the whole point is that it works in the shop, where the signal is worst | | Merge | Per-item operations rather than whole-list writes | two people editing one list at once is the normal case, not an edge case | | Access | One household, invite links, no public signup | a family application does not need accounts for strangers | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Offline store | Local data and the operation queue. | The rule that the interface never waits on the network is the product. Any store that preserves it is fine. | | Sync | Pushing and pulling operations, merging per item. | Per-item operations rather than document writes is the decision that makes concurrent editing work, and tombstones are what make deletion work. | | Recipes | Import, scaling and ingredients. | Structured data first, paste as fallback. Building a scraper is the trap to avoid. | | Combine | Turning a plan into a shopping list. | Conservative merging is correct. Guessing at unit conversion produces a list nobody trusts. | | Print | Paper output. | A stylesheet, and it matters more than it sounds for the household member who wanted paper. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Lists, recipes, plans and the operation log. - `PUBLIC_URL` · required · Used in invite links. Must be HTTPS or installation and offline support will not work. - `INVITE_SECRET` · required, secret · Signs invite tokens so a household cannot be joined by guessing. - `SYNC_PULL_INTERVAL_SECONDS` · required · How often a foregrounded client pulls. Frequent enough to feel shared, infrequent enough to be polite. - `OPERATION_RETENTION_DAYS` · required · How long the sync log is kept. Long enough for a device that has been off for a while. - `BACKUP_DEST` · optional · Product phase only. Where nightly backups go. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · AnyList Complete product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22 with SQLite, An installable progressive web application, IndexedDB with an operation queue, Per-item operations rather than whole-list writes, One household, invite links, no public signup. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. - Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. - Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. - Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. - A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done. - A household application that only you can fix becomes a problem the week you are away, and the recipes people rely on are suddenly unavailable. The export and the note are what make that recoverable by someone else, and both need to exist before they are needed. ===== MILESTONES.md ===== # Delivery milestones · AnyList Complete Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A list that works with no signal You can add and check off items in a shop with the phone offline, and nothing is lost. ### Steps 1. Build the offline shell Service worker precaching the shell, a manifest with icons, and IndexedDB for data. Files: `vite.config.ts` ```sh npm create vite@latest . -- --template vanilla-ts npm install -D vite-plugin-pwa ``` 2. Queue every change as an operation Local write first, queue second, network never on the critical path. Files: `src/ops.ts` 3. Test in aeroplane mode on a real phone In an actual shop if you can. Desktop testing hides everything that matters here. ### Done when - [ ] Items can be added and checked with networking disabled - [ ] Changes survive a reload and an application restart - [ ] The application installs to the home screen - [ ] The interface never blocks on the network ### Watch out - Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. ## M2 · Sync that merges rather than clobbers Two people editing the same list at the same time both keep their changes. ### Steps 1. Push and pull operations Per household, since a watermark. Small payloads and simple ordering. Files: `server/sync.ts` 2. Merge per item Additions merge, checks are last-write-wins per item, deletions are tombstones. Test each case. Files: `src/merge.ts` 3. Test the offline divergence case Both devices offline, both editing, then both reconnect. This is the case that matters and the one that is skipped. Files: `test/merge.test.ts` ### Done when - [ ] Two devices adding offline both keep their items - [ ] Checking the same item converges without duplication - [ ] A deletion survives sync rather than reappearing - [ ] A day offline catches up without loss ### Watch out - Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. ## M3 · Recipes, and getting them in without typing A recipe can be added from a page in a few seconds, and its ingredients become list items. ### Steps 1. Read structured recipe data first Most cooking sites publish it. It is far more reliable than parsing the visible page. Files: `src/import/structured.ts` 2. Fall back to paste A text box with a light parser for quantities. Honest and adequate, unlike a fragile scraper. Files: `src/import/paste.ts` 3. Scale quantities carefully Numbers scale; a pinch does not. Test with a recipe containing both. Files: `src/scale.ts` ### Done when - [ ] A structured recipe URL imports with ingredients and instructions - [ ] A site without it falls back to paste - [ ] Doubling servings scales numbers and leaves non-numeric amounts alone - [ ] Adding a recipe creates one list item per ingredient ### Watch out - Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. ## M4 · Meal plans and a list that makes sense in a shop A week of meals produces one sensible shopping list. ### Steps 1. Build the weekly plan Days and meals, with recipes assigned and free-text notes for the nights nobody is cooking. Files: `src/plan.ts` 2. Combine ingredients conservatively Merge on name and compatible unit only. Anything else stays separate. Files: `src/combine.ts` 3. Group by shop section Categories assigned per ingredient, ordered to match a real shop. This is what makes the list usable. Files: `src/categories.ts` ### Done when - [ ] Five recipes generate one combined list - [ ] Same-unit duplicates merge with summed quantities - [ ] Incompatible units stay as separate lines - [ ] The list is grouped by shop section ### Watch out - Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. ## M5 · The household, and paper Everyone in the house can use it, including the person who wants it printed. ### Steps 1. Add invite links only A token that joins a household. No public signup, because a family application does not need one. Files: `server/invite.ts` 2. Write a real print stylesheet Items, quantities and sections. No navigation, no colours, nothing cut off. Print it on paper. Files: `src/print.css` 3. Attribute gently Who checked an item, shown quietly. Useful in a shared list, and not a scoreboard. Files: `src/items.ts` ### Done when - [ ] An invite link joins a household and grants nothing else - [ ] There is no public signup path - [ ] The print view fits one page cleanly on real paper - [ ] Checked items show who checked them without ranking anyone ### Watch out - A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done. ## M6 · Hosting, backups and handing it over (production only) It runs somewhere the household can reach, and it survives you being unavailable. ### Steps 1. Deploy behind TLS A small server or a machine at home behind a tunnel. Certificates, because a warning page loses the household immediately. Files: `Caddyfile` 2. Back up and export Nightly database copies off the machine, plus a Markdown recipe export anyone can read without the application. Files: `scripts/backup.sh`, `src/export.ts` 3. Write the household note Where it runs, how to reach the export, and who to contact. Somewhere findable without the application working. Files: `docs/household.md` ### Done when - [ ] The application serves over HTTPS to the household - [ ] Nightly backups land off the machine - [ ] A restore reproduces lists, recipes and plans, and is dated - [ ] The household note exists and is findable without the application ### Watch out - A household application that only you can fix becomes a problem the week you are away, and the recipes people rely on are suddenly unavailable. The export and the note are what make that recoverable by someone else, and both need to exist before they are needed. ===== OPERATIONS.md ===== # Operations · AnyList Complete ## Backup Nightly database copies off the machine, plus a Markdown recipe export. The recipes are the part the household would genuinely miss, and Markdown means they survive without this application. ## Restore Restore the database onto a server, point the clients at it, and confirm a list syncs between two devices. Record the date. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Sync failure rate and operation log growth. A client that has silently stopped syncing looks fine to its user until they arrive at the shop without the items someone else added. ## Incident checklist If sync breaks, clients keep working offline, which is the property that makes this recoverable rather than urgent. If the server is gone entirely, the Markdown recipe export is what the household actually needs, which is why it exists before it is needed. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Offline editing on two devices is confirmed to merge without loss - [ ] A deletion is confirmed to survive sync - [ ] Ingredient combining is confirmed not to merge incompatible units - [ ] The print view is checked on real paper - [ ] A restore has been performed and dated, and the household note exists ## Launch constraint Do not market omitted AnyList Complete capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Lists, recipes, plans and the operation log. DB_PATH=./data/household.db # Required. Used in invite links. Must be HTTPS or installation and offline support will not work. PUBLIC_URL=https://lists.example.com # Required · secret. Signs invite tokens so a household cannot be joined by guessing. INVITE_SECRET=generate-a-long-random-value # Required. How often a foregrounded client pulls. Frequent enough to feel shared, infrequent enough to be polite. SYNC_PULL_INTERVAL_SECONDS=30 # Required. How long the sync log is kept. Long enough for a device that has been off for a while. OPERATION_RETENTION_DAYS=60 # Optional. Product phase only. Where nightly backups go. BACKUP_DEST=s3://your-bucket/household
# AnyList Complete · indie build Shared grocery lists that work in a shop with no signal and merge sensibly when two people add items at once, recipes with ingredients that generate a list, a weekly meal plan, and a printable shopping list for the person who would rather have paper. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with SQLite | a household is a handful of people and a few thousand rows | | Client | An installable progressive web application | shops have bad signal and app stores are not worth it for four people | | Offline | IndexedDB with an operation queue | the whole point is that it works in the shop, where the signal is worst | | Merge | Per-item operations rather than whole-list writes | two people editing one list at once is the normal case, not an edge case | | Access | One household, invite links, no public signup | a family application does not need accounts for strangers | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the sync server and the build. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Two phones in the household** · free - Why: Phase 2's merge behaviour needs two real devices, and the offline case needs a real shop with real signal problems. - Get it: Your own and one other household member's. Ten minutes of their time, twice. - Verify: Two devices are installed and can both edit a list - [ ] **An HTTPS URL for development** · free - Why: Service workers and installation both require a secure context, so a local address will not work on a phone. - Get it: A tunnelling tool such as cloudflared, which is free. - Verify: The phone loads it over HTTPS and the service worker registers - [ ] **A small server or a machine at home** (optional) · 5 to 10 USD a month, or free on hardware you own - Why: Product phase only. Sync needs somewhere both phones can reach. - Get it: Any small virtual server, or a machine at home behind a tunnel. This workload is tiny. - Verify: Both phones reach it over HTTPS - [ ] **Five recipes from different sites** · free - Why: Phase 3's import needs variety: some sites publish structured data and some do not, and you need both cases. - Get it: Pick five you actually cook, from five different sites. Keep the URLs as fixtures. - Verify: At least one imports structurally and at least one falls back to paste ## Quick start ```sh npm create vite@latest . -- --template vanilla-ts npm install -D vite-plugin-pwa ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Barcode scanning, voice entry and mobile capture polish. - Retailer integrations that place an order. - Voice assistant integration. - Setup-free sharing for a household member who will not tolerate a step. - excellent mobile capture, retailer integrations, voice assistants, and polished sharing - frictionless family sync - retailer and smart-home integrations - large recipe or product database - mobile polish If one of those is essential to you, that is the reason to keep paying for AnyList Complete, and the README should say so rather than pretend.
# Build brief · AnyList Complete The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a shared household list, recipe and meal plan app on a small server your family actually uses to replace AnyList Complete. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node 22 with SQLite · a household is a handful of people and a few thousand rows - Client: An installable progressive web application · shops have bad signal and app stores are not worth it for four people - Offline: IndexedDB with an operation queue · the whole point is that it works in the shop, where the signal is worst - Merge: Per-item operations rather than whole-list writes · two people editing one list at once is the normal case, not an edge case - Access: One household, invite links, no public signup · a family application does not need accounts for strangers ### Data model (create this before Phase 1) - households(id, name, created_at), members(id, household_id, name, invite_token, joined_at) - lists(id, household_id, name, position) - items(id, list_id, text, quantity, category, checked_at, checked_by, created_at, updated_at, deleted_at) · deleted_at is a tombstone, never a removed row - recipes(id, household_id, title, source_url, servings, instructions), recipe_ingredients(recipe_id, text, quantity, unit, category) - plans(id, household_id, week_start), plan_entries(plan_id, day, meal, recipe_id, note) - operations(id, member_id, entity, entity_id, op, payload, at) · the sync log, which is what makes concurrent editing work - Deletions are tombstones. A deleted item that is simply absent will be resurrected by the next device to sync. ### Phase 1 · A list that works with no signal Build: An installable progressive web application reading from and writing to IndexedDB. Every change is an operation queued locally. The interface never waits on the network. Install to the home screen and test in aeroplane mode. Trap: Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. Done when: With the network disabled, items can be added and checked off and they survive a reload. The application installs to the home screen. The interface never blocks on a network request. A queued operation persists across an application restart. Do not build yet: No sync, no recipes, no plans. ### Phase 2 · Sync that merges rather than clobbers Build: A small server storing operations per household. Clients push their queue and pull operations since a watermark. Merge per item rather than per list. Deletions travel as tombstones. Trap: Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. Done when: Two devices adding different items offline both end up with all items after sync. Two devices checking off the same item converge without duplicating it. An item deleted on one device stays deleted after the other syncs. A device offline for a day catches up without losing anything. Do not build yet: No recipes or plans yet. ### Phase 3 · Recipes, and getting them in without typing Build: Recipes with ingredients, servings and instructions. Import by pasting a URL and reading the structured recipe data most cooking sites publish, falling back to pasting the text. Scale ingredient quantities by servings. Trap: Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. Done when: A recipe URL from a site publishing structured data imports with ingredients and instructions. A site without it falls back to pasting, which works. Doubling the servings doubles numeric quantities and leaves quantities like a pinch alone. Adding a recipe to a list creates one item per ingredient. Do not build yet: No meal plan yet. ### Phase 4 · Meal plans and a list that makes sense in a shop Build: A weekly plan of recipes by day and meal. Generating a list combines ingredients across recipes, merging duplicates and summing quantities where units match. Group the list by shop section so it follows the route rather than the recipes. Trap: Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. Done when: A week of five recipes generates one list. Ingredients appearing in two recipes with the same unit are merged with quantities summed. Ingredients with incompatible units appear as separate lines rather than being wrongly combined. The list is grouped by shop section. Do not build yet: No sharing or printing yet. ### Phase 5 · The household, and paper Build: Invite links joining a household, with no public signup. A print view producing a clean one-page list. Show who checked off what, gently, because in a shared list that is the information people want. Trap: A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done. Done when: An invite link adds someone to the household and nothing else. There is no public signup path. The print view fits a normal shopping list on one page with no interface furniture, verified by printing it. Checked items show who checked them without making it a leaderboard. Do not build yet: Operations come last. ### Phase 6 · Hosting, backups and handing it over Build: Deploy behind TLS. Back up the database off the machine. Export everything as JSON and as readable Markdown recipes. Write a note the household can follow if it stops working while you are away. Trap: A household application that only you can fix becomes a problem the week you are away, and the recipes people rely on are suddenly unavailable. The export and the note are what make that recoverable by someone else, and both need to exist before they are needed. Done when: The application serves over HTTPS and the household can reach it. Nightly backups land off the machine. Export produces JSON and readable Markdown recipes. A restore onto a clean server reproduces lists, recipes and plans, and the date is recorded. A household note explains what to do if it stops working. Do not build yet: Nothing after this. ### Out of scope (and why) - The mobile capture polish of the commercial application: barcode scanning, voice entry and the widget that makes adding an item take two seconds. - Retailer integrations that turn a list into an order, which are per-retailer partnerships rather than features. - Voice assistant integration, which needs a reviewed relationship with each platform. - The frictionless sharing that makes it work for a family member who will not tolerate a setup step. ### README must contain - That the application works offline and sync merges per item rather than per list - That deletions are tombstones, because an absent row comes back - That recipe import reads structured data first and falls back to pasting - That ingredients merge only where units are compatible - The backup schedule, the export formats, and the household note for when you are away
# Agent instructions · AnyList Complete indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22 with SQLite, An installable progressive web application, IndexedDB with an operation queue, Per-item operations rather than whole-list writes, One household, invite links, no public signup. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. - Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. - Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. - Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. - A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done.
# Build plan · AnyList Complete Shared grocery lists that work in a shop with no signal and merge sensibly when two people add items at once, recipes with ingredients that generate a list, a weekly meal plan, and a printable shopping list for the person who would rather have paper. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A list that works with no signal You can add and check off items in a shop with the phone offline, and nothing is lost. ### Steps 1. Build the offline shell Service worker precaching the shell, a manifest with icons, and IndexedDB for data. Files: `vite.config.ts` ```sh npm create vite@latest . -- --template vanilla-ts npm install -D vite-plugin-pwa ``` 2. Queue every change as an operation Local write first, queue second, network never on the critical path. Files: `src/ops.ts` 3. Test in aeroplane mode on a real phone In an actual shop if you can. Desktop testing hides everything that matters here. ### Done when - [ ] Items can be added and checked with networking disabled - [ ] Changes survive a reload and an application restart - [ ] The application installs to the home screen - [ ] The interface never blocks on the network ### Watch out - Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. ## Phase 2 · Sync that merges rather than clobbers Two people editing the same list at the same time both keep their changes. ### Steps 1. Push and pull operations Per household, since a watermark. Small payloads and simple ordering. Files: `server/sync.ts` 2. Merge per item Additions merge, checks are last-write-wins per item, deletions are tombstones. Test each case. Files: `src/merge.ts` 3. Test the offline divergence case Both devices offline, both editing, then both reconnect. This is the case that matters and the one that is skipped. Files: `test/merge.test.ts` ### Done when - [ ] Two devices adding offline both keep their items - [ ] Checking the same item converges without duplication - [ ] A deletion survives sync rather than reappearing - [ ] A day offline catches up without loss ### Watch out - Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. ## Phase 3 · Recipes, and getting them in without typing A recipe can be added from a page in a few seconds, and its ingredients become list items. ### Steps 1. Read structured recipe data first Most cooking sites publish it. It is far more reliable than parsing the visible page. Files: `src/import/structured.ts` 2. Fall back to paste A text box with a light parser for quantities. Honest and adequate, unlike a fragile scraper. Files: `src/import/paste.ts` 3. Scale quantities carefully Numbers scale; a pinch does not. Test with a recipe containing both. Files: `src/scale.ts` ### Done when - [ ] A structured recipe URL imports with ingredients and instructions - [ ] A site without it falls back to paste - [ ] Doubling servings scales numbers and leaves non-numeric amounts alone - [ ] Adding a recipe creates one list item per ingredient ### Watch out - Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. ## Phase 4 · Meal plans and a list that makes sense in a shop A week of meals produces one sensible shopping list. ### Steps 1. Build the weekly plan Days and meals, with recipes assigned and free-text notes for the nights nobody is cooking. Files: `src/plan.ts` 2. Combine ingredients conservatively Merge on name and compatible unit only. Anything else stays separate. Files: `src/combine.ts` 3. Group by shop section Categories assigned per ingredient, ordered to match a real shop. This is what makes the list usable. Files: `src/categories.ts` ### Done when - [ ] Five recipes generate one combined list - [ ] Same-unit duplicates merge with summed quantities - [ ] Incompatible units stay as separate lines - [ ] The list is grouped by shop section ### Watch out - Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. ## Phase 5 · The household, and paper Everyone in the house can use it, including the person who wants it printed. ### Steps 1. Add invite links only A token that joins a household. No public signup, because a family application does not need one. Files: `server/invite.ts` 2. Write a real print stylesheet Items, quantities and sections. No navigation, no colours, nothing cut off. Print it on paper. Files: `src/print.css` 3. Attribute gently Who checked an item, shown quietly. Useful in a shared list, and not a scoreboard. Files: `src/items.ts` ### Done when - [ ] An invite link joins a household and grants nothing else - [ ] There is no public signup path - [ ] The print view fits one page cleanly on real paper - [ ] Checked items show who checked them without ranking anyone ### Watch out - A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done. ## Not in this build - Barcode scanning, voice entry and mobile capture polish. - Retailer integrations that place an order. - Voice assistant integration. - Setup-free sharing for a household member who will not tolerate a step. ## After v1, if you want it - A pantry list so the shopping list can subtract what you already have - Recurring items that reappear weekly without being added - A simple nutrition note per recipe, entered by hand rather than looked up
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Lists, recipes, plans and the operation log. DB_PATH=./data/household.db # Required. Used in invite links. Must be HTTPS or installation and offline support will not work. PUBLIC_URL=https://lists.example.com # Required · secret. Signs invite tokens so a household cannot be joined by guessing. INVITE_SECRET=generate-a-long-random-value # Required. How often a foregrounded client pulls. Frequent enough to feel shared, infrequent enough to be polite. SYNC_PULL_INTERVAL_SECONDS=30 # Required. How long the sync log is kept. Long enough for a device that has been off for a while. OPERATION_RETENTION_DAYS=60 # Optional. Product phase only. Where nightly backups go. BACKUP_DEST=s3://your-bucket/household
# AnyList Complete · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For AnyList Complete, manage shared grocery lists, recipes, and meal plans with a simple household sync server. The hard boundary is excellent mobile capture, retailer integrations, voice assistants, and polished sharing, plus sync, integrations, and household adoption. ## Product outcome A household application that works in a shop with no signal, merges two people's changes instead of losing one, imports recipes without typing, prints a clean list, and can be recovered by someone else if you are away. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - modern browser - local or self-hosted storage - optional email notifications ## Explicit non-goals for v1 - Barcode scanning, voice entry and mobile capture polish. - Retailer integrations that place an order. - Voice assistant integration. - Setup-free sharing for a household member who will not tolerate a step. - excellent mobile capture, retailer integrations, voice assistants, and polished sharing - frictionless family sync - retailer and smart-home integrations - large recipe or product database - mobile polish ## Success criteria - Offline editing on two devices is confirmed to merge without loss - A deletion is confirmed to survive sync - Ingredient combining is confirmed not to merge incompatible units - The print view is checked on real paper - A restore has been performed and dated, and the household note exists
# Build brief · AnyList Complete The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a shared household list, recipe and meal plan app on a small server your family actually uses to replace AnyList Complete. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node 22 with SQLite · a household is a handful of people and a few thousand rows - Client: An installable progressive web application · shops have bad signal and app stores are not worth it for four people - Offline: IndexedDB with an operation queue · the whole point is that it works in the shop, where the signal is worst - Merge: Per-item operations rather than whole-list writes · two people editing one list at once is the normal case, not an edge case - Access: One household, invite links, no public signup · a family application does not need accounts for strangers ### Data model (create this before Phase 1) - households(id, name, created_at), members(id, household_id, name, invite_token, joined_at) - lists(id, household_id, name, position) - items(id, list_id, text, quantity, category, checked_at, checked_by, created_at, updated_at, deleted_at) · deleted_at is a tombstone, never a removed row - recipes(id, household_id, title, source_url, servings, instructions), recipe_ingredients(recipe_id, text, quantity, unit, category) - plans(id, household_id, week_start), plan_entries(plan_id, day, meal, recipe_id, note) - operations(id, member_id, entity, entity_id, op, payload, at) · the sync log, which is what makes concurrent editing work - Deletions are tombstones. A deleted item that is simply absent will be resurrected by the next device to sync. ### Phase 1 · A list that works with no signal Build: An installable progressive web application reading from and writing to IndexedDB. Every change is an operation queued locally. The interface never waits on the network. Install to the home screen and test in aeroplane mode. Trap: Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. Done when: With the network disabled, items can be added and checked off and they survive a reload. The application installs to the home screen. The interface never blocks on a network request. A queued operation persists across an application restart. Do not build yet: No sync, no recipes, no plans. ### Phase 2 · Sync that merges rather than clobbers Build: A small server storing operations per household. Clients push their queue and pull operations since a watermark. Merge per item rather than per list. Deletions travel as tombstones. Trap: Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. Done when: Two devices adding different items offline both end up with all items after sync. Two devices checking off the same item converge without duplicating it. An item deleted on one device stays deleted after the other syncs. A device offline for a day catches up without losing anything. Do not build yet: No recipes or plans yet. ### Phase 3 · Recipes, and getting them in without typing Build: Recipes with ingredients, servings and instructions. Import by pasting a URL and reading the structured recipe data most cooking sites publish, falling back to pasting the text. Scale ingredient quantities by servings. Trap: Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. Done when: A recipe URL from a site publishing structured data imports with ingredients and instructions. A site without it falls back to pasting, which works. Doubling the servings doubles numeric quantities and leaves quantities like a pinch alone. Adding a recipe to a list creates one item per ingredient. Do not build yet: No meal plan yet. ### Phase 4 · Meal plans and a list that makes sense in a shop Build: A weekly plan of recipes by day and meal. Generating a list combines ingredients across recipes, merging duplicates and summing quantities where units match. Group the list by shop section so it follows the route rather than the recipes. Trap: Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. Done when: A week of five recipes generates one list. Ingredients appearing in two recipes with the same unit are merged with quantities summed. Ingredients with incompatible units appear as separate lines rather than being wrongly combined. The list is grouped by shop section. Do not build yet: No sharing or printing yet. ### Phase 5 · The household, and paper Build: Invite links joining a household, with no public signup. A print view producing a clean one-page list. Show who checked off what, gently, because in a shared list that is the information people want. Trap: A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done. Done when: An invite link adds someone to the household and nothing else. There is no public signup path. The print view fits a normal shopping list on one page with no interface furniture, verified by printing it. Checked items show who checked them without making it a leaderboard. Do not build yet: Operations come last. ### Phase 6 · Hosting, backups and handing it over Build: Deploy behind TLS. Back up the database off the machine. Export everything as JSON and as readable Markdown recipes. Write a note the household can follow if it stops working while you are away. Trap: A household application that only you can fix becomes a problem the week you are away, and the recipes people rely on are suddenly unavailable. The export and the note are what make that recoverable by someone else, and both need to exist before they are needed. Done when: The application serves over HTTPS and the household can reach it. Nightly backups land off the machine. Export produces JSON and readable Markdown recipes. A restore onto a clean server reproduces lists, recipes and plans, and the date is recorded. A household note explains what to do if it stops working. Do not build yet: Nothing after this. ### Out of scope (and why) - The mobile capture polish of the commercial application: barcode scanning, voice entry and the widget that makes adding an item take two seconds. - Retailer integrations that turn a list into an order, which are per-retailer partnerships rather than features. - Voice assistant integration, which needs a reviewed relationship with each platform. - The frictionless sharing that makes it work for a family member who will not tolerate a setup step. ### README must contain - That the application works offline and sync merges per item rather than per list - That deletions are tombstones, because an absent row comes back - That recipe import reads structured data first and falls back to pasting - That ingredients merge only where units are compatible - The backup schedule, the export formats, and the household note for when you are away
# Architecture · AnyList Complete ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with SQLite | a household is a handful of people and a few thousand rows | | Client | An installable progressive web application | shops have bad signal and app stores are not worth it for four people | | Offline | IndexedDB with an operation queue | the whole point is that it works in the shop, where the signal is worst | | Merge | Per-item operations rather than whole-list writes | two people editing one list at once is the normal case, not an edge case | | Access | One household, invite links, no public signup | a family application does not need accounts for strangers | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Offline store | Local data and the operation queue. | The rule that the interface never waits on the network is the product. Any store that preserves it is fine. | | Sync | Pushing and pulling operations, merging per item. | Per-item operations rather than document writes is the decision that makes concurrent editing work, and tombstones are what make deletion work. | | Recipes | Import, scaling and ingredients. | Structured data first, paste as fallback. Building a scraper is the trap to avoid. | | Combine | Turning a plan into a shopping list. | Conservative merging is correct. Guessing at unit conversion produces a list nobody trusts. | | Print | Paper output. | A stylesheet, and it matters more than it sounds for the household member who wanted paper. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Lists, recipes, plans and the operation log. - `PUBLIC_URL` · required · Used in invite links. Must be HTTPS or installation and offline support will not work. - `INVITE_SECRET` · required, secret · Signs invite tokens so a household cannot be joined by guessing. - `SYNC_PULL_INTERVAL_SECONDS` · required · How often a foregrounded client pulls. Frequent enough to feel shared, infrequent enough to be polite. - `OPERATION_RETENTION_DAYS` · required · How long the sync log is kept. Long enough for a device that has been off for a while. - `BACKUP_DEST` · optional · Product phase only. Where nightly backups go. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · AnyList Complete product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22 with SQLite, An installable progressive web application, IndexedDB with an operation queue, Per-item operations rather than whole-list writes, One household, invite links, no public signup. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. - Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. - Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. - Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. - A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done. - A household application that only you can fix becomes a problem the week you are away, and the recipes people rely on are suddenly unavailable. The export and the note are what make that recoverable by someone else, and both need to exist before they are needed.
# Delivery milestones · AnyList Complete Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A list that works with no signal You can add and check off items in a shop with the phone offline, and nothing is lost. ### Steps 1. Build the offline shell Service worker precaching the shell, a manifest with icons, and IndexedDB for data. Files: `vite.config.ts` ```sh npm create vite@latest . -- --template vanilla-ts npm install -D vite-plugin-pwa ``` 2. Queue every change as an operation Local write first, queue second, network never on the critical path. Files: `src/ops.ts` 3. Test in aeroplane mode on a real phone In an actual shop if you can. Desktop testing hides everything that matters here. ### Done when - [ ] Items can be added and checked with networking disabled - [ ] Changes survive a reload and an application restart - [ ] The application installs to the home screen - [ ] The interface never blocks on the network ### Watch out - Browser storage can be evicted, and on some platforms it is far more aggressive for sites that are not installed to the home screen. Prompt for installation, and make an export available, because losing a household's recipe collection to a storage eviction is a genuinely bad afternoon. ## M2 · Sync that merges rather than clobbers Two people editing the same list at the same time both keep their changes. ### Steps 1. Push and pull operations Per household, since a watermark. Small payloads and simple ordering. Files: `server/sync.ts` 2. Merge per item Additions merge, checks are last-write-wins per item, deletions are tombstones. Test each case. Files: `src/merge.ts` 3. Test the offline divergence case Both devices offline, both editing, then both reconnect. This is the case that matters and the one that is skipped. Files: `test/merge.test.ts` ### Done when - [ ] Two devices adding offline both keep their items - [ ] Checking the same item converges without duplication - [ ] A deletion survives sync rather than reappearing - [ ] A day offline catches up without loss ### Watch out - Syncing the whole list as one document means the last device to save wins and the other person's additions vanish silently, which in a shop means someone comes home without the milk. Sync per-item operations so two additions merge naturally. ## M3 · Recipes, and getting them in without typing A recipe can be added from a page in a few seconds, and its ingredients become list items. ### Steps 1. Read structured recipe data first Most cooking sites publish it. It is far more reliable than parsing the visible page. Files: `src/import/structured.ts` 2. Fall back to paste A text box with a light parser for quantities. Honest and adequate, unlike a fragile scraper. Files: `src/import/paste.ts` 3. Scale quantities carefully Numbers scale; a pinch does not. Test with a recipe containing both. Files: `src/scale.ts` ### Done when - [ ] A structured recipe URL imports with ingredients and instructions - [ ] A site without it falls back to paste - [ ] Doubling servings scales numbers and leaves non-numeric amounts alone - [ ] Adding a recipe creates one list item per ingredient ### Watch out - Parsing a recipe out of page prose is unreliable and always will be. Most cooking sites publish structured recipe metadata specifically so applications can read it, so use that first and fall back to a paste box rather than to a fragile parser you will maintain forever. ## M4 · Meal plans and a list that makes sense in a shop A week of meals produces one sensible shopping list. ### Steps 1. Build the weekly plan Days and meals, with recipes assigned and free-text notes for the nights nobody is cooking. Files: `src/plan.ts` 2. Combine ingredients conservatively Merge on name and compatible unit only. Anything else stays separate. Files: `src/combine.ts` 3. Group by shop section Categories assigned per ingredient, ordered to match a real shop. This is what makes the list usable. Files: `src/categories.ts` ### Done when - [ ] Five recipes generate one combined list - [ ] Same-unit duplicates merge with summed quantities - [ ] Incompatible units stay as separate lines - [ ] The list is grouped by shop section ### Watch out - Combining ingredients across recipes without reconciling units produces a list saying two hundred grams of onion and one onion as separate lines, which is worse than not merging at all. Merge only where units are compatible, and leave the rest as separate lines rather than guessing. ## M5 · The household, and paper Everyone in the house can use it, including the person who wants it printed. ### Steps 1. Add invite links only A token that joins a household. No public signup, because a family application does not need one. Files: `server/invite.ts` 2. Write a real print stylesheet Items, quantities and sections. No navigation, no colours, nothing cut off. Print it on paper. Files: `src/print.css` 3. Attribute gently Who checked an item, shown quietly. Useful in a shared list, and not a scoreboard. Files: `src/items.ts` ### Done when - [ ] An invite link joins a household and grants nothing else - [ ] There is no public signup path - [ ] The print view fits one page cleanly on real paper - [ ] Checked items show who checked them without ranking anyone ### Watch out - A default web print of an application interface produces a page of navigation and cut-off text, and the person who wanted paper will not ask twice. Write a print stylesheet and check it on actual paper before calling it done. ## M6 · Hosting, backups and handing it over (production only) It runs somewhere the household can reach, and it survives you being unavailable. ### Steps 1. Deploy behind TLS A small server or a machine at home behind a tunnel. Certificates, because a warning page loses the household immediately. Files: `Caddyfile` 2. Back up and export Nightly database copies off the machine, plus a Markdown recipe export anyone can read without the application. Files: `scripts/backup.sh`, `src/export.ts` 3. Write the household note Where it runs, how to reach the export, and who to contact. Somewhere findable without the application working. Files: `docs/household.md` ### Done when - [ ] The application serves over HTTPS to the household - [ ] Nightly backups land off the machine - [ ] A restore reproduces lists, recipes and plans, and is dated - [ ] The household note exists and is findable without the application ### Watch out - A household application that only you can fix becomes a problem the week you are away, and the recipes people rely on are suddenly unavailable. The export and the note are what make that recoverable by someone else, and both need to exist before they are needed.
# Operations · AnyList Complete ## Backup Nightly database copies off the machine, plus a Markdown recipe export. The recipes are the part the household would genuinely miss, and Markdown means they survive without this application. ## Restore Restore the database onto a server, point the clients at it, and confirm a list syncs between two devices. Record the date. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Sync failure rate and operation log growth. A client that has silently stopped syncing looks fine to its user until they arrive at the shop without the items someone else added. ## Incident checklist If sync breaks, clients keep working offline, which is the property that makes this recoverable rather than urgent. If the server is gone entirely, the Markdown recipe export is what the household actually needs, which is why it exists before it is needed. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Offline editing on two devices is confirmed to merge without loss - [ ] A deletion is confirmed to survive sync - [ ] Ingredient combining is confirmed not to merge incompatible units - [ ] The print view is checked on real paper - [ ] A restore has been performed and dated, and the household note exists ## Launch constraint Do not market omitted AnyList Complete capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Lists, recipes, plans and the operation log. DB_PATH=./data/household.db # Required. Used in invite links. Must be HTTPS or installation and offline support will not work. PUBLIC_URL=https://lists.example.com # Required · secret. Signs invite tokens so a household cannot be joined by guessing. INVITE_SECRET=generate-a-long-random-value # Required. How often a foregrounded client pulls. Frequent enough to feel shared, infrequent enough to be polite. SYNC_PULL_INTERVAL_SECONDS=30 # Required. How long the sync log is kept. Long enough for a device that has been off for a while. OPERATION_RETENTION_DAYS=60 # Optional. Product phase only. Where nightly backups go. BACKUP_DEST=s3://your-bucket/household
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for AnyList Complete because household apps survive when every family member actually uses them and the data stays current across devices. The recurring cost buys multi-user conflicts, reminders, notifications, imports, barcode data, smart-home APIs, backups, and long-term maintenance, not just the visible interface.
xexcellent mobile capture, retailer integrations, voice assistants, and polished sharing
xfrictionless family sync
xretailer and smart-home integrations
xlarge recipe or product database
xmobile polish
Don't feel like building it? These folks already made it free.
all 5 free alternatives to AnyList Complete →· no votes, no pay-to-list · just what's real
AnyList Complete pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| anylist free | $0 | $0 | Free list/recipe app; exact numeric caps not disclosed on pricing page. |
| anylist complete individual | — | $0.83/user | Unlocks web/Mac access, recipe import, meal planning calendar, Apple Watch, item photos, location reminders, passcode lock, priority support and photo backup. |
| anylist complete household | — | $1.25 | Household plan for the same Complete features shared by the household. |
free tierfree plan exists; official page did not disclose numeric caps.
billingannual only on public page; no monthly plan published
hidden costsIndividual and household annual subscriptions are separate; no monthly alternative shown.
verified 2026-08-10 · source ↗
Vibecode AnyList Complete
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal AnyList Complete replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does AnyList Complete cost?
AnyList Complete costs about $0.83/month (Complete, checked 2026-07-31), which is $9.959999999999999 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing AnyList Complete?
Honestly: excellent mobile capture, retailer integrations, voice assistants, and polished sharing; frictionless family sync; retailer and smart-home integrations; large recipe or product database; mobile polish. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to AnyList Complete?
Yes: Mealie (A household recipe server with meal plans and grocery lists; you supply the server and patience.) Grocy (Meal plans and groceries run through a tiny home ERP, because apparently the fridge needed one.) RecipeSage (Shared recipes, meal plans, and shopping lists with no recipe cap; the polish budget is smaller.) All 5 curated free alternatives are at vibecodeit.com/anylist-complete/alternatives. The prompt is for when you want it exactly your way.