Shopping Lists and Shop a List
Audience: User, Org Admin, Developer, Support Related: Meal Planner · Kitchen Inventory · Household and External Shopping · Data Retention
Shopping Lists can be created from recipes, selected meal-plan scope, or manual items. Ingredient scaling, normalization, source provenance, recipe multipliers, departments, inventory filtering, ownership, and list lifecycle are preserved through editing and Shop a List.
Shop a List provides recipe, department, and flat grouping, quick manual additions, undo behavior, and completion handling. Completed lists are retained or purged according to the configured policy.
This document captures the stabilized shopping-list architecture and UI split after the Shop a List, Edit Shopping List, Kitchen Inventory, and department-management improvements.
Workflow split
Blackcap Pi intentionally separates shopping-list work into three surfaces.
| Surface | Purpose | Primary users |
|---|---|---|
| Shopping Lists | Manage saved lists, active/completed state, print, reopen, and entry points. | Admin users. |
| Edit Shopping List | Compose/repair a list: recipes, multipliers, manual additions, preview, save, rebuild. | Admin users. |
| Shop a List | Execute the list while shopping: checkoff, inventory filter, add item, department reassignment, complete. | Admin and Mobile users. |
The Mobile execution page should be called Shop a List, not simply Shopping.
Shopping-list lifecycle
- Lists can be created from the Recipe Cart or manually.
- Recipe-created lists receive consumer-friendly smart names based on list date and recipes.
- Empty manual lists require a user-provided name.
- One list can be active at a time for an organization.
- Completed lists are excluded from Shop a List and Edit Shopping List selectors.
- Completing an active list clears the active-list state.
- Completed lists can be reopened from Shopping Lists.
- Completed lists cannot be made active until reopened.
Meal Planner as a shopping-list source
Meal Planner is another composition source for Shopping Lists. It does not create a parallel shopping system.
When a shopping list is created from Meal Planner:
- recipe-backed meal entries contribute ingredients using the meal-plan recipe multiplier;
- manual meal items contribute ingredient-style candidates when marked for shopping inclusion;
- uncanonicalized manual meal items remain reviewable just like unresolved recipe ingredients;
- the resulting list is still edited in Edit Shopping List and executed in Shop a List;
- Kitchen Inventory / On Hand behavior remains in the shopping-list execution layer.
If no specific days/slots are selected in Meal Planner, Create Shopping List defaults to the current configured/working week rather than a whole month view.
Smart list names
Smart names should be friendly for household users rather than developer-style timestamps.
Preferred format:
May 22, 2026 · Cheeseburger Pie +1 Recipe
If the same date/recipe/count combination already exists, append a suffix:
May 22, 2026 · Cheeseburger Pie +1 Recipe -1
Custom/manual names should be preserved. Generic placeholders such as Shopping List should not be used for newly created blank lists.
Edit Shopping List rules
Edit Shopping List is a composition page.
Supported actions:
- select an editable non-completed list;
- adjust recipe multipliers using fraction controls;
- remove recipes from the list;
- add/remove manual shopping items;
- save changes;
- rebuild recipe-derived rows when permitted;
- print or cancel back to the previous page.
Important boundaries:
- Recipe-derived ingredient rows are preview-only here.
- Checkoff happens in Shop a List.
- Manual-verification or recipe-derived loose rows should not appear as removable manual items.
- Save should rebuild recipe-derived quantities from the current recipe multipliers.
- Manual additions should be preserved across save/rebuild.
- Unsaved-change warnings should appear before switching lists or leaving the page.
- Completed lists should be reopened from Shopping Lists before editing.
Data Admin, Org Admin, and System Admin users can use Rebuild. The Rebuild action is intentionally role-restricted because it rewrites generated list rows from source recipes/multipliers/manual items.
Shop a List rules
Shop a List is the execution page.
Supported actions:
- check/uncheck items;
- filter All items / On hand / Need to shop;
- group by Recipe, Department, or None;
- add a manual item without leaving the page;
- remove manual items with undo;
- change department assignments;
- complete the list.
Completing a list marks only checked items as on hand in Kitchen Inventory. Unchecked items are skipped.
When grouped by Recipe or Department, Admin and Mobile Shop a List render each group as a collapsible section. Group headers use real buttons with aria-expanded, show shopped/total counts, and preserve item state when collapsed. Admin shows one intelligent control that says Collapse All when at least one group is expanded and Expand All only when every group is collapsed. Mobile adds a sticky horizontal group strip for Recipe/Department grouping; tapping a chip expands that group if needed, marks the chip active, and scrolls to the group header. The chip/header counts use the intuitive shopped/total direction, such as 3/7 shopped.
Rapid checkoff persistence
Rapid Shop a List checkoffs use an optimistic, single-flight queue. The browser keeps the latest desired checked state per item and coalesces additional taps while a save is already in flight. Checkoff targets are stable shopping-list item_id values, not the visible row index; section/index are retained only as a legacy fallback. This matters because checked rows can move after sorting, grouping, household-list inclusion, or duplicate consolidation.
The server applies the payload with the shopping-list DB service atomic mutation helper, resolves refs by item_id first, reloads the affected refs, verifies that the requested state persisted, and retries once before returning an error. If a coalesced save still fails, the browser retries still-current rows individually and only reverts rows that actually fail.
This pattern avoids launching many overlapping full-list writes, and it avoids targeting the wrong row after normalization/sorting. The route and browser code stay database-backend neutral; SQLite and PostgreSQL locking differences belong behind the DB service boundary.
Shop With extension behavior
The Chrome extension Shop With helper uses the same organization-scoped shopping-list state as Admin/Mobile Shop a List. It can run an assisted session for enabled providers such as Shipt, Target/Walmart-style providers, and Amazon.
Session rules:
- provider and store selection are scoped to the extension token owner’s organization;
- providers can expose store modes or a free-form store text field;
- Shipt supports free-form store text with common-store suggestions, and a blank store falls back to the provider default such as
stores; - the selected provider/store stays with the active Shop With session until the session is completed, reset, or changed by the user;
- shopping credentials are not stored by Blackcap; assisted shopping relies on the user’s active browser session with the retailer.
Execution rows are consolidated before they are sent to the extension. Duplicate shopping-list rows with the same normalized shopping/search text are grouped into one assisted item where safe. Matching quantities with the same unit are summed; mixed or uncertain duplicates stay grouped with a count label rather than risking a wrong quantity. The consolidated row keeps source references to all backing shopping-list item rows, so Got It, Already Have It, Review uncheck, and Reshop update every backing row consistently.
Completion behavior is intentionally different from Return behavior. Complete finishes the session, clears the extension shopping state, and closes the side shopping panel without navigating back to Blackcap. Return is the explicit action that brings the user back to Blackcap’s Shop a List page.
Department assignment behavior
Department reassignment should improve both the current list and future lists.
- Admin: dragging a shopping row into a grocery department changes the visible group and updates the canonical department mapping.
- Mobile: long-pressing an ingredient opens a department modal. The user can select a department, add a new department, save, or cancel.
- Department resolution uses item override first, then organization canonical mapping, then platform/default mapping, then
Other / Uncategorized.
Undo behavior
Manual item deletion should not require confirmation.
Instead, supported pages show an undo toast:
- Admin Edit Shopping List.
- Admin Shop a List.
- Mobile Shop a List.
This keeps shopping fast while still protecting against accidental taps/clicks.
Persistence boundary
Shopping-list headers are handled by the shopping-list DB service/repository flow. Shopping-list item rows and item-source rows are written together through:
inky_admin/repositories/shopping_list_item_write_repository.py
That repository helper is responsible for:
- resolving the effective organization id from the sync context or parent shopping list;
- writing
shopping_list_items.organization_id; - writing
shopping_list_item_sources.organization_id; - deleting and replacing item/source rows in one place;
- dropping stale
source_recipe_ingredient_idvalues that no longer exist, avoiding foreign-key failures during legacy payload rewrites.
Routes and templates should not write directly to shopping_list_items or shopping_list_item_sources.
Admin UI layout expectations
Advanced shopping pages use the newer split-scroll pattern:
- top/header panels can scroll fully up and out of view;
- primary content panels fill the visible viewport after the headers clear;
- internal panel scrollbars stay fully visible, including the bottom/down arrow;
- panel scroll reaches the top/bottom and hands off smoothly to the page scroll;
- avoid large blank bottom whitespace by dynamically adjusting scroll range;
- treat
top: 0as valid, not as a missing value.
This pattern applies to Shop a List, Edit Shopping List, Database Admin-style pages, and similar dual-panel admin workflows.
Smoke coverage
tests/test_shopping_list_item_write_repository.py recreates the failure shape that appeared on the Pi:
- org-scoped
shopping_list_items.organization_id NOT NULL; - org-scoped
shopping_list_item_sources.organization_id NOT NULL; - foreign keys enabled;
- source attribution payloads that do not carry organization id;
- stale recipe ingredient references that should not be written.
Run the targeted smoke test with:
python3 -m unittest tests/test_shopping_list_item_write_repository.py
More information
- Household List and External Synchronization — Maintain the persistent Household List and synchronize supported external shopping-list providers.