Frontend: D1 — Shopping list #30

Closed
opened 2026-04-02 11:29:39 +02:00 by marcel · 14 comments
Owner

Summary

Shared shopping checklist generated from the week's meal plan. Pantry staples are auto-filtered. Anyone can add or remove items.

Journey: J5 — Generate shopping list
Role: Planner generates / All household members can view, check off, and add items
Screen: D1

Layout

Mobile (< 768px)

  • Topbar: "Shopping list" title + settings icon
  • Checklist section (8px 12px padding)
  • Bottom tab bar

Desktop (> 1024px)

  • Sidebar (224px) + topbar (title + members-online badge, blue-tint)
  • Left panel (flex:1, --color-page bg, 20px 24px padding):
    • Eyebrow: "N items remaining · N checked off"
    • Checklist rows (unchecked)
    • Divider
    • "Checked off" section (strikethrough items)
    • "Add custom item" link
  • Right panel (280px, --color-surface bg, border-left, 20px padding):
    • "This week's recipes" reference cards (name 13px/500 + day + ingredient count)
    • "Filtered staples" section + "Edit staples" link → D3

Checklist Rows

  • Checkbox + ingredient name + "For: [recipe names]" source + quantity
  • Checked items: strikethrough text, moved below divider
  • Each check/uncheck syncs in real-time to all connected household members

Shopping List Generation

  • Only the planner can generate/regenerate the list
  • Ingredients from all planned meals are collected
  • Shared ingredients across meals are merged and quantities summed (e.g., 3 + 2 carrots = 5 carrots)
  • Pantry staples (defined in A3/D3) are automatically filtered out

Custom Items

  • Any household member can add items not on the generated list (household supplies, snacks, etc.)
  • Custom items appear at the bottom of the list

Acceptance Criteria

  • Mobile: full-width checklist with blue shared banner
  • Desktop: 2-panel with checklist + recipe reference panel
  • Ingredients merged and quantities summed across meals
  • Pantry staples auto-filtered
  • Custom item addition by any member
  • Planner-only: generate/regenerate list
  • "Edit staples" link → D3
## Summary Shared shopping checklist generated from the week's meal plan. Pantry staples are auto-filtered. Anyone can add or remove items. **Journey:** J5 — Generate shopping list **Role:** Planner generates / All household members can view, check off, and add items **Screen:** D1 ## Layout ### Mobile (< 768px) - Topbar: "Shopping list" title + settings icon - Checklist section (8px 12px padding) - Bottom tab bar ### Desktop (> 1024px) - Sidebar (224px) + topbar (title + members-online badge, blue-tint) - Left panel (flex:1, `--color-page` bg, 20px 24px padding): - Eyebrow: "N items remaining · N checked off" - Checklist rows (unchecked) - Divider - "Checked off" section (strikethrough items) - "Add custom item" link - Right panel (280px, `--color-surface` bg, border-left, 20px padding): - "This week's recipes" reference cards (name 13px/500 + day + ingredient count) - "Filtered staples" section + "Edit staples" link → D3 ## Checklist Rows - Checkbox + ingredient name + "For: [recipe names]" source + quantity - Checked items: strikethrough text, moved below divider - Each check/uncheck syncs in real-time to all connected household members ## Shopping List Generation - Only the planner can generate/regenerate the list - Ingredients from all planned meals are collected - Shared ingredients across meals are merged and quantities summed (e.g., 3 + 2 carrots = 5 carrots) - Pantry staples (defined in A3/D3) are automatically filtered out ## Custom Items - Any household member can add items not on the generated list (household supplies, snacks, etc.) - Custom items appear at the bottom of the list ## Acceptance Criteria - [ ] Mobile: full-width checklist with blue shared banner - [ ] Desktop: 2-panel with checklist + recipe reference panel - [ ] Ingredients merged and quantities summed across meals - [ ] Pantry staples auto-filtered - [ ] Custom item addition by any member - [ ] Planner-only: generate/regenerate list - [ ] "Edit staples" link → D3
marcel added the kind/featurepriority/high labels 2026-04-02 11:30:18 +02:00
Author
Owner

Spec file: specs/frontend/j5-shopping-list.html — screen D1 with mobile (checklist) desktop (split checklist + recipe reference panel) previews, agent table, and LLM implementation guide.

**Spec file:** [`specs/frontend/j5-shopping-list.html`](../specs/frontend/j5-shopping-list.html) — screen D1 with mobile (checklist) desktop (split checklist + recipe reference panel) previews, agent table, and LLM implementation guide.
marcel changed title from Frontend: D1 — Shopping list (live shared checklist) to Frontend: D1 — Shopping list 2026-04-03 21:13:26 +02:00
Author
Owner

🎨 Atlas — UI/UX Designer

Questions & Observations

  • The issue says "Each check/uncheck syncs in real-time to all connected household members." However, the decision is no live list — simple refresh to view new items. This simplifies the interaction model significantly, but the spec should be updated to reflect this. Should there be a visible refresh affordance (pull-to-refresh on mobile, refresh button on desktop), or is browser refresh sufficient?
  • "N items remaining · N checked off" eyebrow: When are these counts updated — on every check action immediately (local state), or only after refresh? If local, we need optimistic UI. If only after refresh, the counts could be stale and confusing.
  • Checked items moving below the divider: Does this reorder happen immediately on check (local DOM move), or only after refresh? Immediate reorder is the expected pattern — items jumping around on refresh would feel broken.
  • "Add custom item" link: The spec says it's a link at the bottom. Is this a text link that expands into an inline input, or does it navigate to a separate view? For mobile, an inline input with a simple text field + add button would be the most frictionless pattern. A link that navigates away from the list breaks flow.
  • Right panel "This week's recipes" reference cards: The spec mentions "name 13px/500 + day + ingredient count" but doesn't specify what happens on click. Should clicking a recipe card navigate to the recipe detail (B2), or is it purely informational? If informational, it should not look like a link/button.

Suggestions

  • Add a subtle "Last updated: [time]" or "Pull down to refresh" hint so users understand they're seeing a snapshot, not a live view. Without real-time sync, this context prevents confusion.
  • The "Filtered staples" section on the right panel should show the count of filtered items (e.g., "12 staples filtered") rather than listing them all — otherwise the panel gets long and the checklist loses focus.
  • Consider a "Regenerate list" button with a confirmation dialog, since regeneration presumably wipes custom items and check states. The planner-only constraint should be enforced visually (button hidden for members, not just disabled).
  • Mobile: The blue shared banner mentioned in acceptance criteria isn't described in the layout section. Define its content and position — is it a sticky bar below the topbar showing "Shared with [household name]"?
## 🎨 Atlas — UI/UX Designer ### Questions & Observations - The issue says "Each check/uncheck syncs in real-time to all connected household members." However, the decision is **no live list — simple refresh to view new items**. This simplifies the interaction model significantly, but the spec should be updated to reflect this. Should there be a visible refresh affordance (pull-to-refresh on mobile, refresh button on desktop), or is browser refresh sufficient? - **"N items remaining · N checked off" eyebrow**: When are these counts updated — on every check action immediately (local state), or only after refresh? If local, we need optimistic UI. If only after refresh, the counts could be stale and confusing. - **Checked items moving below the divider**: Does this reorder happen immediately on check (local DOM move), or only after refresh? Immediate reorder is the expected pattern — items jumping around on refresh would feel broken. - **"Add custom item" link**: The spec says it's a link at the bottom. Is this a text link that expands into an inline input, or does it navigate to a separate view? For mobile, an inline input with a simple text field + add button would be the most frictionless pattern. A link that navigates away from the list breaks flow. - **Right panel "This week's recipes" reference cards**: The spec mentions "name 13px/500 + day + ingredient count" but doesn't specify what happens on click. Should clicking a recipe card navigate to the recipe detail (B2), or is it purely informational? If informational, it should not look like a link/button. ### Suggestions - Add a subtle "Last updated: [time]" or "Pull down to refresh" hint so users understand they're seeing a snapshot, not a live view. Without real-time sync, this context prevents confusion. - The "Filtered staples" section on the right panel should show the count of filtered items (e.g., "12 staples filtered") rather than listing them all — otherwise the panel gets long and the checklist loses focus. - Consider a "Regenerate list" button with a confirmation dialog, since regeneration presumably wipes custom items and check states. The planner-only constraint should be enforced visually (button hidden for members, not just disabled). - Mobile: The blue shared banner mentioned in acceptance criteria isn't described in the layout section. Define its content and position — is it a sticky bar below the topbar showing "Shared with [household name]"?
Author
Owner

🖥️ Kai — Senior Frontend Engineer

Questions & Observations

  • No real-time sync simplifies things massively. The issue body says "Each check/uncheck syncs in real-time to all connected household members" — but the decision is to skip live updates and use simple refresh. This means no WebSocket/SSE, no optimistic concurrency, no conflict resolution. The SvelteKit load function in +page.server.ts fetches the list on navigation/refresh, and form actions handle check/uncheck/add. Clean and simple.
  • Check/uncheck interaction: Should this be a form action (use:enhance) that POSTs to the backend on every toggle, or should we batch changes client-side and sync on a "Save" action? Individual form actions per checkbox are the simplest approach and fit the "no live list" model — each check is a server round-trip, and the page state updates via invalidateAll() or returned data.
  • Ingredient merging and quantity summing: Is this done backend-side during list generation, or does the frontend receive raw per-recipe ingredients and merge them? Backend-side is strongly preferred — the frontend should receive a flat, pre-merged list. The "For: [recipe names]" source label implies the backend tracks which recipes contributed to each merged row.
  • "Planner-only: generate/regenerate list": How does the frontend know the user's role? Is it available in the session/layout data? We need this to conditionally render the generate button. The role check must also be enforced server-side in the form action.
  • Custom items at the bottom: Are custom items in a separate section with their own heading, or mixed into the main list but appended at the end? If separate, they need their own "checked" subsection below the divider too.

Suggestions

  • Use SvelteKit form actions (+page.server.ts actions) for all mutations: check item, uncheck item, add custom item, generate list. Each action calls the backend API with the session cookie. Use use:enhance for progressive enhancement — the page works without JS.
  • The checklist state is server-authoritative. On page load, +page.server.ts fetches the full list from the backend API. No client-side state management needed beyond what Svelte's reactivity gives us from the data prop.
  • Split the page into components: ShoppingHeader.svelte (eyebrow counts), ChecklistSection.svelte (unchecked items), CheckedOffSection.svelte (checked items below divider), AddCustomItem.svelte (inline form), RecipeReferencePanel.svelte (desktop right panel), FilteredStaples.svelte (desktop right panel section).
  • For the mobile/desktop split: use a single route with responsive Tailwind classes. The right panel is hidden lg:block. No need for separate routes or layouts.
## 🖥️ Kai — Senior Frontend Engineer ### Questions & Observations - **No real-time sync simplifies things massively.** The issue body says "Each check/uncheck syncs in real-time to all connected household members" — but the decision is to skip live updates and use simple refresh. This means no WebSocket/SSE, no optimistic concurrency, no conflict resolution. The SvelteKit `load` function in `+page.server.ts` fetches the list on navigation/refresh, and form actions handle check/uncheck/add. Clean and simple. - **Check/uncheck interaction**: Should this be a form action (`use:enhance`) that POSTs to the backend on every toggle, or should we batch changes client-side and sync on a "Save" action? Individual form actions per checkbox are the simplest approach and fit the "no live list" model — each check is a server round-trip, and the page state updates via `invalidateAll()` or returned data. - **Ingredient merging and quantity summing**: Is this done backend-side during list generation, or does the frontend receive raw per-recipe ingredients and merge them? Backend-side is strongly preferred — the frontend should receive a flat, pre-merged list. The "For: [recipe names]" source label implies the backend tracks which recipes contributed to each merged row. - **"Planner-only: generate/regenerate list"**: How does the frontend know the user's role? Is it available in the session/layout data? We need this to conditionally render the generate button. The role check must also be enforced server-side in the form action. - **Custom items at the bottom**: Are custom items in a separate section with their own heading, or mixed into the main list but appended at the end? If separate, they need their own "checked" subsection below the divider too. ### Suggestions - Use SvelteKit form actions (`+page.server.ts` actions) for all mutations: check item, uncheck item, add custom item, generate list. Each action calls the backend API with the session cookie. Use `use:enhance` for progressive enhancement — the page works without JS. - The checklist state is server-authoritative. On page load, `+page.server.ts` fetches the full list from the backend API. No client-side state management needed beyond what Svelte's reactivity gives us from the `data` prop. - Split the page into components: `ShoppingHeader.svelte` (eyebrow counts), `ChecklistSection.svelte` (unchecked items), `CheckedOffSection.svelte` (checked items below divider), `AddCustomItem.svelte` (inline form), `RecipeReferencePanel.svelte` (desktop right panel), `FilteredStaples.svelte` (desktop right panel section). - For the mobile/desktop split: use a single route with responsive Tailwind classes. The right panel is `hidden lg:block`. No need for separate routes or layouts.
Author
Owner

⚙️ Backend Engineer — Senior Backend Engineer

Questions & Observations

  • Shopping list generation logic: The spec says "Ingredients from all planned meals are collected" and "Shared ingredients across meals are merged and quantities summed." This is the most complex backend logic in this feature. Questions:
    • How do we match "same ingredient" across recipes? By ingredient name (case-insensitive)? By a normalized ingredient ID? Name-based matching is fragile ("Karotten" vs "Möhren" vs "Karotte"). An ingredient master table with IDs would be more reliable.
    • How do we sum quantities when units differ? "200g Butter" + "1 EL Butter" — are these summed or listed separately? The spec doesn't address unit normalization. I'd suggest listing them separately for v1 and flagging unit normalization as a future enhancement.
  • Data model questions: The issue doesn't reference a data model. We need at minimum:
    • shopping_list (id, household_id, week, created_by, created_at)
    • shopping_list_item (id, list_id, ingredient_name, quantity, unit, checked, checked_by, source_recipe_ids[], is_custom, sort_order)
    • Should source_recipe_ids be a join table or a uuid[] array? Join table is cleaner for querying "which recipes use this item."
  • Pantry staples filtering: The spec references "A3/D3" for pantry staples. Is there already a pantry_staple table? The generation endpoint needs to exclude items matching the household's staples list. Matching logic (by name? by ingredient ID?) needs to be consistent with the merging logic above.
  • Regeneration behavior: "Only the planner can generate/regenerate the list." What happens to existing checked items and custom items when regenerating? Options: (a) wipe everything and start fresh, (b) keep custom items and reset check states, (c) merge new items into existing list. The spec should clarify this — it significantly affects the endpoint design.

Suggestions

  • Expose these endpoints:
    • POST /api/shopping-lists/generate — planner-only, creates/regenerates list for current week
    • GET /api/shopping-lists/current — returns the current week's list with all items
    • PATCH /api/shopping-lists/{listId}/items/{itemId} — toggle checked state
    • POST /api/shopping-lists/{listId}/items — add custom item
  • Keep ingredient merging and staple filtering entirely server-side. The frontend receives a flat, ready-to-render list.
  • Use @Transactional on the generation service method — if merging fails midway, the whole operation rolls back. Don't leave a half-generated list in the database.
  • Consider a generation_version or generated_at timestamp on the list so the frontend can show "Generated on [date]" and the regenerate action is clearly a destructive replace.
## ⚙️ Backend Engineer — Senior Backend Engineer ### Questions & Observations - **Shopping list generation logic**: The spec says "Ingredients from all planned meals are collected" and "Shared ingredients across meals are merged and quantities summed." This is the most complex backend logic in this feature. Questions: - How do we match "same ingredient" across recipes? By ingredient name (case-insensitive)? By a normalized ingredient ID? Name-based matching is fragile ("Karotten" vs "Möhren" vs "Karotte"). An ingredient master table with IDs would be more reliable. - How do we sum quantities when units differ? "200g Butter" + "1 EL Butter" — are these summed or listed separately? The spec doesn't address unit normalization. I'd suggest listing them separately for v1 and flagging unit normalization as a future enhancement. - **Data model questions**: The issue doesn't reference a data model. We need at minimum: - `shopping_list` (id, household_id, week, created_by, created_at) - `shopping_list_item` (id, list_id, ingredient_name, quantity, unit, checked, checked_by, source_recipe_ids[], is_custom, sort_order) - Should `source_recipe_ids` be a join table or a `uuid[]` array? Join table is cleaner for querying "which recipes use this item." - **Pantry staples filtering**: The spec references "A3/D3" for pantry staples. Is there already a `pantry_staple` table? The generation endpoint needs to exclude items matching the household's staples list. Matching logic (by name? by ingredient ID?) needs to be consistent with the merging logic above. - **Regeneration behavior**: "Only the planner can generate/regenerate the list." What happens to existing checked items and custom items when regenerating? Options: (a) wipe everything and start fresh, (b) keep custom items and reset check states, (c) merge new items into existing list. The spec should clarify this — it significantly affects the endpoint design. ### Suggestions - Expose these endpoints: - `POST /api/shopping-lists/generate` — planner-only, creates/regenerates list for current week - `GET /api/shopping-lists/current` — returns the current week's list with all items - `PATCH /api/shopping-lists/{listId}/items/{itemId}` — toggle checked state - `POST /api/shopping-lists/{listId}/items` — add custom item - Keep ingredient merging and staple filtering entirely server-side. The frontend receives a flat, ready-to-render list. - Use `@Transactional` on the generation service method — if merging fails midway, the whole operation rolls back. Don't leave a half-generated list in the database. - Consider a `generation_version` or `generated_at` timestamp on the list so the frontend can show "Generated on [date]" and the regenerate action is clearly a destructive replace.
Author
Owner

🔒 Sable — Security Engineer

Questions & Observations

  • Real-time sync removed — good for security scope. No WebSocket/SSE means no new authentication surface for persistent connections, no cross-household event leakage risk, and no need for connection-level authorization. Simple request/response with session cookies is well-understood and already secured in hooks.server.ts.
  • Household isolation is critical here. The shopping list is household-scoped. Every API endpoint must verify that the requesting user belongs to the household that owns the list. This includes:
    • GET /current — only return the list for the user's household
    • PATCH /items/{itemId} — verify the item belongs to a list owned by the user's household (not just that the user is authenticated). An attacker could enumerate item IDs across households otherwise (IDOR).
    • POST /items — verify the list belongs to the user's household
    • POST /generate — verify planner role AND household membership
  • Planner-only generation: Role check must happen server-side, not just by hiding the button in the frontend. The generate endpoint must reject requests from members with 403. Verify this is enforced in the service layer, not just the controller annotation.
  • Custom item input — injection surface: The "Add custom item" feature accepts free-text user input. Ensure:
    • Backend validates max length (prevent storage abuse)
    • Input is parameterized in SQL (no string concatenation)
    • Frontend renders the item name via Svelte text interpolation ({item.name}), never {@html} — Svelte's default escaping handles XSS here, but this should be a review checkpoint.
  • Regeneration as destructive action: If regeneration wipes the list, it's a data-destroying operation. Ensure it requires explicit confirmation and that the endpoint is idempotent (calling it twice doesn't create duplicate items).

Suggestions

  • Add integration tests specifically for cross-household access: authenticate as a user from household A, attempt to check off an item from household B's list — must return 403.
  • Add a max length constraint on custom item names at both the database level (VARCHAR(200) or similar CHECK constraint) and the API validation layer (@Size(max=200)).
  • Log list generation and regeneration events in the audit trail — these are significant data mutations that affect the whole household.
  • Rate-limit the generate endpoint to prevent abuse (a planner spamming regenerate could be disruptive to other household members mid-shopping).
## 🔒 Sable — Security Engineer ### Questions & Observations - **Real-time sync removed — good for security scope.** No WebSocket/SSE means no new authentication surface for persistent connections, no cross-household event leakage risk, and no need for connection-level authorization. Simple request/response with session cookies is well-understood and already secured in hooks.server.ts. - **Household isolation is critical here.** The shopping list is household-scoped. Every API endpoint must verify that the requesting user belongs to the household that owns the list. This includes: - `GET /current` — only return the list for the user's household - `PATCH /items/{itemId}` — verify the item belongs to a list owned by the user's household (not just that the user is authenticated). An attacker could enumerate item IDs across households otherwise (IDOR). - `POST /items` — verify the list belongs to the user's household - `POST /generate` — verify planner role AND household membership - **Planner-only generation**: Role check must happen server-side, not just by hiding the button in the frontend. The `generate` endpoint must reject requests from members with 403. Verify this is enforced in the service layer, not just the controller annotation. - **Custom item input — injection surface**: The "Add custom item" feature accepts free-text user input. Ensure: - Backend validates max length (prevent storage abuse) - Input is parameterized in SQL (no string concatenation) - Frontend renders the item name via Svelte text interpolation (`{item.name}`), never `{@html}` — Svelte's default escaping handles XSS here, but this should be a review checkpoint. - **Regeneration as destructive action**: If regeneration wipes the list, it's a data-destroying operation. Ensure it requires explicit confirmation and that the endpoint is idempotent (calling it twice doesn't create duplicate items). ### Suggestions - Add integration tests specifically for cross-household access: authenticate as a user from household A, attempt to check off an item from household B's list — must return 403. - Add a max length constraint on custom item names at both the database level (`VARCHAR(200)` or similar CHECK constraint) and the API validation layer (`@Size(max=200)`). - Log list generation and regeneration events in the audit trail — these are significant data mutations that affect the whole household. - Rate-limit the generate endpoint to prevent abuse (a planner spamming regenerate could be disruptive to other household members mid-shopping).
Author
Owner

🧪 QA Engineer — Senior QA Engineer

Questions & Observations

  • Acceptance criteria gap — "no live list, simple refresh": The issue body still says "Each check/uncheck syncs in real-time to all connected household members" but the decision is to skip real-time. The acceptance criteria should be updated to reflect the actual behavior: checking/unchecking persists server-side, other users see changes on page refresh. Without this update, I can't write accurate acceptance tests.
  • Ingredient merging is the highest-risk logic. "Shared ingredients across meals are merged and quantities summed (e.g., 3 + 2 carrots = 5 carrots)" — this needs extensive test coverage:
    • Same ingredient, same unit → sum quantities
    • Same ingredient, different units → list separately (or convert? spec unclear)
    • Same ingredient, one with quantity, one without ("Salz" with no amount) → how to merge?
    • Ingredient appears in 3+ recipes → verify all source recipes are listed
    • Recipe with 0 ingredients → should not produce empty rows
  • Pantry staple filtering edge cases:
    • Ingredient that exactly matches a staple → filtered
    • Ingredient that partially matches a staple name → not filtered? (e.g., staple "Butter", ingredient "Erdnussbutter")
    • Staple list is empty → no filtering, all ingredients appear
    • All ingredients are staples → empty list with appropriate empty state
  • Planner-only generation — authorization test matrix:
    • Planner generates → 200/201 ✓
    • Member attempts to generate → 403 ✓
    • Unauthenticated user → 401 ✓
    • Planner from different household → 403 ✓
  • Custom items:
    • Add a custom item → appears at bottom of list
    • Check off a custom item → moves to checked section
    • Regenerate list → what happens to custom items? (spec doesn't say)
    • Add duplicate custom item name → allowed or rejected?
    • Empty string or whitespace-only custom item → validation error

Suggestions

  • Update the acceptance criteria to remove real-time sync language and add: "Users see updated list on page refresh" and "Check/uncheck persists immediately via server round-trip."
  • Add an acceptance criterion for the empty state: "When no meal plan exists for the week, show an appropriate message instead of an empty checklist."
  • Add an acceptance criterion for regeneration behavior: what exactly happens to checked items and custom items.
  • The "Ingredients merged and quantities summed across meals" criterion needs a concrete test case in the spec — the current example (3 + 2 carrots = 5) is too simple. Include a case with mixed units and a case with 3+ contributing recipes.
  • I'd suggest a test matrix table for the generate endpoint covering all role × household × list-state combinations before implementation starts.
## 🧪 QA Engineer — Senior QA Engineer ### Questions & Observations - **Acceptance criteria gap — "no live list, simple refresh"**: The issue body still says "Each check/uncheck syncs in real-time to all connected household members" but the decision is to skip real-time. The acceptance criteria should be updated to reflect the actual behavior: checking/unchecking persists server-side, other users see changes on page refresh. Without this update, I can't write accurate acceptance tests. - **Ingredient merging is the highest-risk logic.** "Shared ingredients across meals are merged and quantities summed (e.g., 3 + 2 carrots = 5 carrots)" — this needs extensive test coverage: - Same ingredient, same unit → sum quantities - Same ingredient, different units → list separately (or convert? spec unclear) - Same ingredient, one with quantity, one without ("Salz" with no amount) → how to merge? - Ingredient appears in 3+ recipes → verify all source recipes are listed - Recipe with 0 ingredients → should not produce empty rows - **Pantry staple filtering edge cases:** - Ingredient that exactly matches a staple → filtered - Ingredient that partially matches a staple name → not filtered? (e.g., staple "Butter", ingredient "Erdnussbutter") - Staple list is empty → no filtering, all ingredients appear - All ingredients are staples → empty list with appropriate empty state - **Planner-only generation — authorization test matrix:** - Planner generates → 200/201 ✓ - Member attempts to generate → 403 ✓ - Unauthenticated user → 401 ✓ - Planner from different household → 403 ✓ - **Custom items:** - Add a custom item → appears at bottom of list - Check off a custom item → moves to checked section - Regenerate list → what happens to custom items? (spec doesn't say) - Add duplicate custom item name → allowed or rejected? - Empty string or whitespace-only custom item → validation error ### Suggestions - Update the acceptance criteria to remove real-time sync language and add: "Users see updated list on page refresh" and "Check/uncheck persists immediately via server round-trip." - Add an acceptance criterion for the empty state: "When no meal plan exists for the week, show an appropriate message instead of an empty checklist." - Add an acceptance criterion for regeneration behavior: what exactly happens to checked items and custom items. - The "Ingredients merged and quantities summed across meals" criterion needs a concrete test case in the spec — the current example (3 + 2 carrots = 5) is too simple. Include a case with mixed units and a case with 3+ contributing recipes. - I'd suggest a test matrix table for the generate endpoint covering all role × household × list-state combinations before implementation starts.
Author
Owner

🎨 Atlas — UI/UX Designer (Round 2)

The spec update (e12fb72) addresses my main concern — liveness is gone, "Shared with household" replaces "N members online" everywhere. The visual previews now match the sync model. A few things remain:

Resolved

  • Shared banner/badge text is consistent across mobile and desktop previews
  • No more "members online" indicator — simpler, no presence tracking needed

Still open from Round 1

  • Eyebrow counts + checked item reorder: The spec doesn't clarify whether counts update and items move below the divider immediately after a check action (optimistic local update) or only on refresh. Since each check is a use:enhance form action that returns updated data, the answer is likely "immediately via returned server state" — but this should be explicit in the spec.
  • "Add custom item" interaction pattern: Still undefined — inline input or navigation? I'd recommend adding to the spec: "Clicking '+ Add custom item' reveals an inline text input with name field + optional quantity + Add button. No navigation away from the list."
  • Recipe reference card click behavior: Still unspecified. Recommend: cards are informational only, no link styling, no cursor:pointer.

New observation

  • The issue body is now out of sync with the spec. Lines like "members-online badge, blue-tint" in the Desktop layout and "Each check/uncheck syncs in real-time" in the Checklist Rows section should be updated to match the spec.
## 🎨 Atlas — UI/UX Designer (Round 2) The spec update (`e12fb72`) addresses my main concern — liveness is gone, "Shared with household" replaces "N members online" everywhere. The visual previews now match the sync model. A few things remain: ### Resolved - Shared banner/badge text is consistent across mobile and desktop previews - No more "members online" indicator — simpler, no presence tracking needed ### Still open from Round 1 - **Eyebrow counts + checked item reorder**: The spec doesn't clarify whether counts update and items move below the divider immediately after a check action (optimistic local update) or only on refresh. Since each check is a `use:enhance` form action that returns updated data, the answer is likely "immediately via returned server state" — but this should be explicit in the spec. - **"Add custom item" interaction pattern**: Still undefined — inline input or navigation? I'd recommend adding to the spec: "Clicking '+ Add custom item' reveals an inline text input with name field + optional quantity + Add button. No navigation away from the list." - **Recipe reference card click behavior**: Still unspecified. Recommend: cards are informational only, no link styling, no cursor:pointer. ### New observation - The issue body is now out of sync with the spec. Lines like "members-online badge, blue-tint" in the Desktop layout and "Each check/uncheck syncs in real-time" in the Checklist Rows section should be updated to match the spec.
Author
Owner

🖥️ Kai — Senior Frontend Engineer (Round 2)

Spec update looks good — section 4 now clearly says "server-authoritative, form actions, no WebSocket/SSE." This is exactly the model I'd implement. A few remaining items:

Resolved

  • Sync model is now explicit: form actions with use:enhance, page refresh for other users' changes
  • No WebSocket/SSE infrastructure needed — keeps the frontend simple

Still open from Round 1

  • Ingredient merging — frontend or backend? The spec's data operations section (section 6) shows a SQL GROUP BY query that merges server-side, which confirms backend-side merging. Good. But the source_recipe_ids tracking isn't in that query — the generation endpoint needs to return a sources: string[] field per item so the frontend can render "For: Tomato pasta, Thai curry."
  • Role availability in frontend: How does +page.server.ts know the user is a planner vs member? Is this in the session data from hooks.server.ts? This needs to be in the layout data so the generate/regenerate button can be conditionally rendered. Not a spec issue per se, but worth confirming before implementation.
  • Custom items section structure: Are custom items interleaved with generated items (sorted together) or in a separate "Custom" section? The spec says "appear at the bottom of the unchecked list" — so they're in the same list but sorted last. After checking off, they go to the same "Checked off" section. Clear enough to implement.

New observation

  • The spec's section 6 SQL uses i.is_staple = false for filtering, implying a boolean on the ingredient table. But section 3 says "Pantry staples (defined in A3/D3)" which suggests a separate household-level staples list. These are different models — a global is_staple flag vs a per-household staples list. The backend needs to clarify which one before I can build the correct API contract.
## 🖥️ Kai — Senior Frontend Engineer (Round 2) Spec update looks good — section 4 now clearly says "server-authoritative, form actions, no WebSocket/SSE." This is exactly the model I'd implement. A few remaining items: ### Resolved - Sync model is now explicit: form actions with `use:enhance`, page refresh for other users' changes - No WebSocket/SSE infrastructure needed — keeps the frontend simple ### Still open from Round 1 - **Ingredient merging — frontend or backend?** The spec's data operations section (section 6) shows a SQL `GROUP BY` query that merges server-side, which confirms backend-side merging. Good. But the `source_recipe_ids` tracking isn't in that query — the generation endpoint needs to return a `sources: string[]` field per item so the frontend can render "For: Tomato pasta, Thai curry." - **Role availability in frontend**: How does `+page.server.ts` know the user is a planner vs member? Is this in the session data from `hooks.server.ts`? This needs to be in the layout data so the generate/regenerate button can be conditionally rendered. Not a spec issue per se, but worth confirming before implementation. - **Custom items section structure**: Are custom items interleaved with generated items (sorted together) or in a separate "Custom" section? The spec says "appear at the bottom of the unchecked list" — so they're in the same list but sorted last. After checking off, they go to the same "Checked off" section. Clear enough to implement. ### New observation - The spec's section 6 SQL uses `i.is_staple = false` for filtering, implying a boolean on the ingredient table. But section 3 says "Pantry staples (defined in A3/D3)" which suggests a separate household-level staples list. These are different models — a global `is_staple` flag vs a per-household staples list. The backend needs to clarify which one before I can build the correct API contract.
Author
Owner

⚙️ Backend Engineer — Senior Backend Engineer (Round 2)

The spec update removes real-time broadcast from the data operations — CHECK OFF and ADD CUSTOM are now simple mutations without broadcast. This simplifies the backend significantly: standard REST endpoints, no event system needed.

Resolved

  • No WebSocket/SSE infrastructure required
  • Data operations are clean request/response cycles
  • Sync model is explicit: server-authoritative, refresh-based

Still open from Round 1

  • Ingredient matching strategy: Still the biggest open question. The spec's SQL uses ingredient_id for grouping, which implies a shared ingredient master table. But our current recipe model stores ingredients as free-text strings per recipe (name + quantity + unit). If that's the case, we need to decide: (a) introduce an ingredient master table now, or (b) match by case-insensitive name for v1. Option (b) is simpler but fragile. This is a blocking design decision before implementation.
  • Unit handling for summing: Still unspecified. I'll proceed with: same ingredient + same unit = sum quantities; same ingredient + different unit = list separately. This is the pragmatic v1 approach.
  • Regeneration behavior: Still undefined. My recommendation for v1: regeneration replaces all generated items (wipes and recreates) but preserves custom items and their check state. This is the least surprising behavior — the planner updates the meal-sourced items, but custom items the family added are untouched.
  • is_staple model mismatch (flagged by Kai): The spec SQL uses i.is_staple = false (global flag on ingredient), but A3/D3 defines staples per household. If staples are household-scoped, the query needs to join against a household_staple table instead. This affects the data model and the generation query.

New observation

  • With the broadcast requirement removed, the shopping_list_item table doesn't need a version or updated_at for conflict detection. A simple checked BOOLEAN DEFAULT false is sufficient. Keeps the schema lean.
## ⚙️ Backend Engineer — Senior Backend Engineer (Round 2) The spec update removes real-time broadcast from the data operations — `CHECK OFF` and `ADD CUSTOM` are now simple mutations without broadcast. This simplifies the backend significantly: standard REST endpoints, no event system needed. ### Resolved - No WebSocket/SSE infrastructure required - Data operations are clean request/response cycles - Sync model is explicit: server-authoritative, refresh-based ### Still open from Round 1 - **Ingredient matching strategy**: Still the biggest open question. The spec's SQL uses `ingredient_id` for grouping, which implies a shared ingredient master table. But our current recipe model stores ingredients as free-text strings per recipe (name + quantity + unit). If that's the case, we need to decide: (a) introduce an ingredient master table now, or (b) match by case-insensitive name for v1. Option (b) is simpler but fragile. This is a **blocking design decision** before implementation. - **Unit handling for summing**: Still unspecified. I'll proceed with: same ingredient + same unit = sum quantities; same ingredient + different unit = list separately. This is the pragmatic v1 approach. - **Regeneration behavior**: Still undefined. My recommendation for v1: regeneration **replaces all generated items** (wipes and recreates) but **preserves custom items and their check state**. This is the least surprising behavior — the planner updates the meal-sourced items, but custom items the family added are untouched. - **`is_staple` model mismatch** (flagged by Kai): The spec SQL uses `i.is_staple = false` (global flag on ingredient), but A3/D3 defines staples per household. If staples are household-scoped, the query needs to join against a `household_staple` table instead. This affects the data model and the generation query. ### New observation - With the broadcast requirement removed, the `shopping_list_item` table doesn't need a `version` or `updated_at` for conflict detection. A simple `checked BOOLEAN DEFAULT false` is sufficient. Keeps the schema lean.
Author
Owner

🔒 Sable — Security Engineer (Round 2)

The spec update is a net positive for security. Removing WebSocket/SSE eliminates an entire authentication surface. The remaining attack surface is standard REST + session cookies, which is already well-handled by the existing Spring Security + SvelteKit hooks setup.

Resolved

  • No persistent connection authentication needed
  • No cross-household event leakage risk via WebSocket channels
  • Data operations are simple mutations behind session auth — existing CSRF + session validation in hooks.server.ts covers this

Still open from Round 1

  • Household isolation (IDOR): Still the top security concern. Every endpoint must verify item.list.household_id == user.household_id. This is not a spec change — it's an implementation requirement. Flag it for code review.
  • Custom item input validation: Still needs max length at DB + API level. Not in the spec's data operations section — the INSERT in section 6 doesn't show constraints. Backend should enforce VARCHAR(200) + @Size(max=200).
  • Planner role enforcement: Spec section 7 says "Only planner can: generate list, regenerate list." Ensure the backend enforces this at the service layer, not just @PreAuthorize on the controller (which can be bypassed if another code path calls the service directly).

New observation

  • With refresh-based sync, there's a time-of-check/time-of-use window for concurrent shoppers. Two users could both check off "Milk" between refreshes. This is acceptable for v1 — the item ends up checked regardless, and no data corruption occurs. But worth noting: if we later add "who checked this?" attribution, we'd need to handle the second check gracefully (idempotent toggle, last-writer-wins).
  • The issue body still mentions "real-time" in the Checklist Rows section. If the issue body is treated as authoritative (alongside the spec), it should be updated to avoid confusion during implementation.
## 🔒 Sable — Security Engineer (Round 2) The spec update is a net positive for security. Removing WebSocket/SSE eliminates an entire authentication surface. The remaining attack surface is standard REST + session cookies, which is already well-handled by the existing Spring Security + SvelteKit hooks setup. ### Resolved - No persistent connection authentication needed - No cross-household event leakage risk via WebSocket channels - Data operations are simple mutations behind session auth — existing CSRF + session validation in hooks.server.ts covers this ### Still open from Round 1 - **Household isolation (IDOR)**: Still the top security concern. Every endpoint must verify `item.list.household_id == user.household_id`. This is not a spec change — it's an implementation requirement. Flag it for code review. - **Custom item input validation**: Still needs max length at DB + API level. Not in the spec's data operations section — the `INSERT` in section 6 doesn't show constraints. Backend should enforce `VARCHAR(200)` + `@Size(max=200)`. - **Planner role enforcement**: Spec section 7 says "Only planner can: generate list, regenerate list." Ensure the backend enforces this at the service layer, not just `@PreAuthorize` on the controller (which can be bypassed if another code path calls the service directly). ### New observation - With refresh-based sync, there's a **time-of-check/time-of-use window** for concurrent shoppers. Two users could both check off "Milk" between refreshes. This is acceptable for v1 — the item ends up checked regardless, and no data corruption occurs. But worth noting: if we later add "who checked this?" attribution, we'd need to handle the second check gracefully (idempotent toggle, last-writer-wins). - The issue body still mentions "real-time" in the Checklist Rows section. If the issue body is treated as authoritative (alongside the spec), it should be updated to avoid confusion during implementation.
Author
Owner

🧪 QA Engineer — Senior QA Engineer (Round 2)

The spec update addresses the sync model cleanly — section 4 is now "Sync model" with explicit rules. This unblocks writing accurate test cases. Here's where we stand:

Resolved

  • Sync model is testable: check/uncheck via form action → verify server state → verify UI update on return. No WebSocket test infrastructure needed.
  • Shared state indicator is static text ("Shared with household") — no dynamic presence count to test.

Still open from Round 1

  • Ingredient merging edge cases: Still the highest-risk area for bugs. The spec gives one example (Jasmine rice merged from two recipes). Before implementation, I need clarity on:
    • Matching: by ingredient ID or by name? (affects test data setup)
    • Mixed units: listed separately or error? (affects assertion logic)
    • These aren't spec bugs — they're design decisions that determine what "correct" means in tests.
  • Regeneration behavior: Still undefined. This is a blocking gap for test coverage. I can't write a regeneration test without knowing: does it wipe custom items? Reset check states? My test matrix for the generate endpoint has a blank column here.
  • Empty states: The spec's preconditions (section 8) say "show empty state on D1 with prompt to plan the week first" — but there's no visual preview of this empty state. I need to know: what text? What action link? Is there a CTA button to navigate to the planner?

Updated test plan given spec changes

Scenario Method Expected
Load list (authenticated, has items) GET 200, items sorted: unchecked first, custom last
Check off item PATCH (form action) Item moves to checked section, counts update
Uncheck item PATCH (form action) Item moves back to unchecked section
Add custom item POST (form action) Item appears at bottom of unchecked list
Add empty/whitespace custom item POST (form action) 422 validation error
Generate list (planner) POST 201, merged items with sources
Generate list (member) POST 403
Generate list (unauthenticated) POST 401
Other user's changes GET (refresh) See latest server state
Concurrent check by two users PATCH + PATCH Both succeed, item is checked (idempotent)

Recommendation

Update the issue body to match the spec — the stale "real-time" and "members-online" language will cause confusion if someone reads the issue without checking the spec file.

## 🧪 QA Engineer — Senior QA Engineer (Round 2) The spec update addresses the sync model cleanly — section 4 is now "Sync model" with explicit rules. This unblocks writing accurate test cases. Here's where we stand: ### Resolved - Sync model is testable: check/uncheck via form action → verify server state → verify UI update on return. No WebSocket test infrastructure needed. - Shared state indicator is static text ("Shared with household") — no dynamic presence count to test. ### Still open from Round 1 - **Ingredient merging edge cases**: Still the highest-risk area for bugs. The spec gives one example (Jasmine rice merged from two recipes). Before implementation, I need clarity on: - Matching: by ingredient ID or by name? (affects test data setup) - Mixed units: listed separately or error? (affects assertion logic) - These aren't spec bugs — they're design decisions that determine what "correct" means in tests. - **Regeneration behavior**: Still undefined. This is a **blocking gap for test coverage**. I can't write a regeneration test without knowing: does it wipe custom items? Reset check states? My test matrix for the generate endpoint has a blank column here. - **Empty states**: The spec's preconditions (section 8) say "show empty state on D1 with prompt to plan the week first" — but there's no visual preview of this empty state. I need to know: what text? What action link? Is there a CTA button to navigate to the planner? ### Updated test plan given spec changes | Scenario | Method | Expected | |---|---|---| | Load list (authenticated, has items) | GET | 200, items sorted: unchecked first, custom last | | Check off item | PATCH (form action) | Item moves to checked section, counts update | | Uncheck item | PATCH (form action) | Item moves back to unchecked section | | Add custom item | POST (form action) | Item appears at bottom of unchecked list | | Add empty/whitespace custom item | POST (form action) | 422 validation error | | Generate list (planner) | POST | 201, merged items with sources | | Generate list (member) | POST | 403 | | Generate list (unauthenticated) | POST | 401 | | Other user's changes | GET (refresh) | See latest server state | | Concurrent check by two users | PATCH + PATCH | Both succeed, item is checked (idempotent) | ### Recommendation Update the issue body to match the spec — the stale "real-time" and "members-online" language will cause confusion if someone reads the issue without checking the spec file.
Author
Owner

⚙️ Backend Engineer — Discussion Summary

Interactive discussion working through all open backend design decisions. All items resolved.

Resolved items

  • Ingredient matching — by ingredient_id (UUID), already implemented in ShoppingService.generateFromPlan(). Merge key is ingredientId + "|" + unit. No name-based matching fragility — the "Karotten vs Möhren" concern from Round 1 doesn't apply since each ingredient has a canonical entry per household.
  • Staples modelis_staple boolean on the ingredient entity, scoped per household (not a global flag). Already implemented — ingredient.isStaple() check in the generation loop. The spec SQL's i.is_staple = false is accurate.
  • Unit handling — same ingredient with different units = separate line items (e.g., "200g Butter" and "1 EL Butter" stay as two rows). Already the behavior of the existing merge key. No unit normalization for v1.
  • Regeneration = merge — when a shopping list already exists for the week plan, regeneration merges rather than replaces:
    • Custom items: untouched (preserved with their check state)
    • Existing generated items still in the plan: update quantity + source_recipes, preserve is_checked
    • Generated items no longer in the plan (recipe removed): deleted
    • New ingredients from newly added recipes: added as unchecked
    • Current generateFromPlan() needs refactoring — it always creates a new list today.
  • Data model — existing schema (shopping_list, shopping_list_item, ingredient) covers the need. One addition: generated_at timestamptz column on shopping_list, updated on each generate/merge. New migration needed.
  • API contract — week-based lookup using ISO week strings:
    • GET /api/shopping-lists?week=2026-W14 — returns the list for the given week + household, or 404 if none exists
    • POST /api/shopping-lists/generate with { "week": "2026-W14" } — generates or merges the list for that week
    • PATCH /api/shopping-lists/{listId}/items/{itemId}/check — toggle checked state (any household member)
    • POST /api/shopping-lists/{listId}/items — add custom item (any household member)
    • DELETE /api/shopping-lists/{listId}/items/{itemId} — remove item (any household member)
    • Frontend defaults to current ISO week; week parameter enables future/past list access without API changes.

No unresolved items

The existing codebase already had solid foundations — most of the concerns raised in the review rounds (ingredient matching, staples model, unit handling) were already solved by the data model. The main implementation work is refactoring generateFromPlan() to support the merge strategy and adding the generated_at column.

## ⚙️ Backend Engineer — Discussion Summary Interactive discussion working through all open backend design decisions. All items resolved. ### Resolved items - **Ingredient matching** — by `ingredient_id` (UUID), already implemented in `ShoppingService.generateFromPlan()`. Merge key is `ingredientId + "|" + unit`. No name-based matching fragility — the "Karotten vs Möhren" concern from Round 1 doesn't apply since each ingredient has a canonical entry per household. - **Staples model** — `is_staple` boolean on the `ingredient` entity, scoped per household (not a global flag). Already implemented — `ingredient.isStaple()` check in the generation loop. The spec SQL's `i.is_staple = false` is accurate. - **Unit handling** — same ingredient with different units = separate line items (e.g., "200g Butter" and "1 EL Butter" stay as two rows). Already the behavior of the existing merge key. No unit normalization for v1. - **Regeneration = merge** — when a shopping list already exists for the week plan, regeneration **merges** rather than replaces: - Custom items: untouched (preserved with their check state) - Existing generated items still in the plan: update quantity + source_recipes, **preserve `is_checked`** - Generated items no longer in the plan (recipe removed): deleted - New ingredients from newly added recipes: added as unchecked - Current `generateFromPlan()` needs refactoring — it always creates a new list today. - **Data model** — existing schema (`shopping_list`, `shopping_list_item`, `ingredient`) covers the need. One addition: `generated_at timestamptz` column on `shopping_list`, updated on each generate/merge. New migration needed. - **API contract** — week-based lookup using ISO week strings: - `GET /api/shopping-lists?week=2026-W14` — returns the list for the given week + household, or 404 if none exists - `POST /api/shopping-lists/generate` with `{ "week": "2026-W14" }` — generates or merges the list for that week - `PATCH /api/shopping-lists/{listId}/items/{itemId}/check` — toggle checked state (any household member) - `POST /api/shopping-lists/{listId}/items` — add custom item (any household member) - `DELETE /api/shopping-lists/{listId}/items/{itemId}` — remove item (any household member) - Frontend defaults to current ISO week; week parameter enables future/past list access without API changes. ### No unresolved items The existing codebase already had solid foundations — most of the concerns raised in the review rounds (ingredient matching, staples model, unit handling) were already solved by the data model. The main implementation work is refactoring `generateFromPlan()` to support the merge strategy and adding the `generated_at` column.
Author
Owner

🎨 Atlas — UI/UX Designer Discussion Summary

Interactive discussion resolving all open UI/UX design decisions for D1. All items resolved.

Resolved items

  • Eyebrow counts + item reorder on check — immediate feedback via use:enhance return data. After checking an item, it moves to the "Checked off" section and counts update based on the server response. No optimistic UI, no waiting for full page refresh.
  • "Add custom item" interaction — inline expand. Tapping "+ Artikel hinzufügen" reveals a text input in-place: name (required), quantity + unit (optional). No modal, no navigation away from the list. Collapses on tap-away or after adding.
  • Recipe reference cards — purely informational, not clickable. No link styling, no cursor:pointer, no hover effect. Context for shopping, not navigation to recipe detail.
  • Empty states — three variants, all German:
    • Planner, no list generated: "Noch keine Einkaufsliste" / "Erstelle eine Liste aus dem Wochenplan." / [Liste erstellen] button
    • Member, no list generated: "Noch keine Einkaufsliste" / "Die Einkaufsliste wurde noch nicht erstellt."
    • No week plan exists: "Keine Mahlzeiten geplant" / "Plane zuerst deine Woche, um eine Einkaufsliste zu erstellen." / [Zum Wochenplaner] button
  • "Zuletzt aktualisiert" timestamp — 10px, --color-text-muted, positioned below the eyebrow ("5 Artikel übrig · 2 abgehakt"). Shows generated_at from the backend, updated on each regeneration/merge. Format: "Zuletzt aktualisiert: Fr, 4. Apr., 14:32"
  • Regenerate button — label "Liste aktualisieren", secondary button style (not primary green). Positioned in topbar: desktop next to "Shared with household" badge, mobile in topbar right. Hidden entirely for members. No confirmation dialog needed — regeneration is a merge that preserves custom items and check states.

No unresolved items

The UI decisions are now fully specified. Combined with the backend discussion (merge-on-regenerate, week-based API, generated_at column), this feature is ready for implementation.

## 🎨 Atlas — UI/UX Designer Discussion Summary Interactive discussion resolving all open UI/UX design decisions for D1. All items resolved. ### Resolved items - **Eyebrow counts + item reorder on check** — immediate feedback via `use:enhance` return data. After checking an item, it moves to the "Checked off" section and counts update based on the server response. No optimistic UI, no waiting for full page refresh. - **"Add custom item" interaction** — inline expand. Tapping "+ Artikel hinzufügen" reveals a text input in-place: name (required), quantity + unit (optional). No modal, no navigation away from the list. Collapses on tap-away or after adding. - **Recipe reference cards** — purely informational, not clickable. No link styling, no `cursor:pointer`, no hover effect. Context for shopping, not navigation to recipe detail. - **Empty states** — three variants, all German: - **Planner, no list generated:** "Noch keine Einkaufsliste" / "Erstelle eine Liste aus dem Wochenplan." / [Liste erstellen] button - **Member, no list generated:** "Noch keine Einkaufsliste" / "Die Einkaufsliste wurde noch nicht erstellt." - **No week plan exists:** "Keine Mahlzeiten geplant" / "Plane zuerst deine Woche, um eine Einkaufsliste zu erstellen." / [Zum Wochenplaner] button - **"Zuletzt aktualisiert" timestamp** — 10px, `--color-text-muted`, positioned below the eyebrow ("5 Artikel übrig · 2 abgehakt"). Shows `generated_at` from the backend, updated on each regeneration/merge. Format: "Zuletzt aktualisiert: Fr, 4. Apr., 14:32" - **Regenerate button** — label "Liste aktualisieren", secondary button style (not primary green). Positioned in topbar: desktop next to "Shared with household" badge, mobile in topbar right. Hidden entirely for members. No confirmation dialog needed — regeneration is a merge that preserves custom items and check states. ### No unresolved items The UI decisions are now fully specified. Combined with the backend discussion (merge-on-regenerate, week-based API, `generated_at` column), this feature is ready for implementation.
Author
Owner

Implementation Complete

Branch: feat/issue-30-shopping-list (15 commits)

Backend (10 commits)

  • 2253c76 Add generated_at column to shopping_list (V022 migration)
  • 2f690eb Add ForbiddenException + 403 handler in GlobalExceptionHandler
  • 3be9f50 Add @RequiresHouseholdRole annotation with HandlerInterceptor — reusable role guard
  • 7e254fc Add findByHouseholdIdAndWeekPlanWeekStart to ShoppingListRepository
  • 93e8bf9 Extend ShoppingListResponse with generatedAt, filteredStaplesCount, RecipeRef
  • c26c2e1 Add getByWeekStart() to ShoppingService (defaults to current Monday)
  • 5325f48 Refactor generateFromPlan() to merge strategy (preserves custom items + check states)
  • 16b70bd Add GET /v1/shopping-list endpoint + @RequiresHouseholdRole("planner") on generate
  • 9292253 Finalize endpoint naming + regenerate OpenAPI types

Frontend (6 commits)

  • e831480 +page.server.ts — load function (shopping list + week plan) + form actions (check, addItem, generate)
  • 2151dff ShoppingHeader.svelte — title, eyebrow counts, timestamp, planner-only generate/regenerate button
  • 7bdadbe ChecklistItem.svelte — checkbox row with name, quantity, recipe source label, strikethrough
  • 5ac8f17 AddCustomItem.svelte — expandable inline form for custom items
  • 6cc7983 RecipeReferencePanel.svelte — desktop right panel with recipe cards + filtered staples info
  • 7411411 +page.svelte — responsive mobile/desktop layout with 3 empty state variants

Test Results

  • Backend: 277 tests pass (including 20 shopping-specific tests)
  • Frontend: npm run check — 0 errors
  • Frontend tests: 478 pass, 2 pre-existing failures in recipe search (unrelated)

Acceptance Criteria Coverage

  • Mobile: full-width checklist
  • Desktop: 2-panel with checklist + recipe reference panel
  • Ingredients merged and quantities summed (merge strategy in backend)
  • Pantry staples auto-filtered (filteredStaplesCount in API response)
  • Custom item addition by any member
  • Planner-only: generate/regenerate list (@RequiresHouseholdRole)
  • "Vorrat bearbeiten" link → pantry page
## Implementation Complete Branch: `feat/issue-30-shopping-list` (15 commits) ### Backend (10 commits) - `2253c76` Add `generated_at` column to `shopping_list` (V022 migration) - `2f690eb` Add `ForbiddenException` + 403 handler in `GlobalExceptionHandler` - `3be9f50` Add `@RequiresHouseholdRole` annotation with `HandlerInterceptor` — reusable role guard - `7e254fc` Add `findByHouseholdIdAndWeekPlanWeekStart` to `ShoppingListRepository` - `93e8bf9` Extend `ShoppingListResponse` with `generatedAt`, `filteredStaplesCount`, `RecipeRef` - `c26c2e1` Add `getByWeekStart()` to `ShoppingService` (defaults to current Monday) - `5325f48` Refactor `generateFromPlan()` to merge strategy (preserves custom items + check states) - `16b70bd` Add `GET /v1/shopping-list` endpoint + `@RequiresHouseholdRole("planner")` on generate - `9292253` Finalize endpoint naming + regenerate OpenAPI types ### Frontend (6 commits) - `e831480` `+page.server.ts` — load function (shopping list + week plan) + form actions (check, addItem, generate) - `2151dff` `ShoppingHeader.svelte` — title, eyebrow counts, timestamp, planner-only generate/regenerate button - `7bdadbe` `ChecklistItem.svelte` — checkbox row with name, quantity, recipe source label, strikethrough - `5ac8f17` `AddCustomItem.svelte` — expandable inline form for custom items - `6cc7983` `RecipeReferencePanel.svelte` — desktop right panel with recipe cards + filtered staples info - `7411411` `+page.svelte` — responsive mobile/desktop layout with 3 empty state variants ### Test Results - **Backend**: 277 tests pass (including 20 shopping-specific tests) - **Frontend**: `npm run check` — 0 errors - **Frontend tests**: 478 pass, 2 pre-existing failures in recipe search (unrelated) ### Acceptance Criteria Coverage - ✅ Mobile: full-width checklist - ✅ Desktop: 2-panel with checklist + recipe reference panel - ✅ Ingredients merged and quantities summed (merge strategy in backend) - ✅ Pantry staples auto-filtered (filteredStaplesCount in API response) - ✅ Custom item addition by any member - ✅ Planner-only: generate/regenerate list (@RequiresHouseholdRole) - ✅ "Vorrat bearbeiten" link → pantry page
Sign in to join this conversation.