diff --git a/specs/staples-settings-redesign.html b/specs/staples-settings-redesign.html new file mode 100644 index 0000000..4a39324 --- /dev/null +++ b/specs/staples-settings-redesign.html @@ -0,0 +1,1334 @@ + + + + + + Vorräte Settings — 5 Redesign Concepts + + + + +

Vorräte Settings — Redesign Concepts

+

Atlas · 2026-04-10 · Mealplan Design System · 5 Concepts

+ +
+

Diagnosed Problems (current A3/D3)

+ +
+ + +
+
+ Concept 01 + Accordion Categories +
+

+ Categories are collapsed by default. A count badge shows selected/total at a glance. + Tap to expand any category — all others stay collapsed. One category in focus at a time. +

+ +
+ + +
+
Mobile — 320px
+
+ ← Einstellungen +
Vorräte
+

Wähle deine regelmäßig vorhandenen Zutaten.

+ + +
+ + +
+
+
+ Gemüse + 7 / 18 +
+ +
+
+ +
+ + +
+
+ Möhren + Zwiebeln + Knoblauch + Zucchini + Aubergine + Paprika + Spinat + Tomaten + Lauch + Brokkoli + Kohl + Sellerie + Gurke +
+
+
+ +
+
+
+ Milchprodukte + 4 / 12 +
+ +
+
+ +
+
+
+ Gewürze + 0 / 24 +
+ +
+
+ +
+
+
+ Öle & Fette + 2 / 6 +
+ +
+
+ +
+
+
+ Getreide & Körner + 0 / 9 +
+ +
+
+ +
+ +

Automatisch gespeichert.

+
+
+ + +
+
Desktop — 640px (settings column)
+
+ ← Einstellungen +
Vorräte
+

Wähle deine regelmäßig vorhandenen Zutaten. Automatisch gespeichert.

+ +
+ +
+
+
+ Gemüse + 7 / 18 +
+
+ + + +
+
+
+
+ Möhren + Zwiebeln + Knoblauch + Zucchini + Aubergine + Paprika + Spinat + Tomaten + Lauch + Brokkoli + Kohl + Sellerie +
+
+
+ +
+
+
+ Milchprodukte + 4 / 12 +
+ +
+
+ +
+
+
+ Gewürze + 0 / 24 +
+ +
+
+ +
+
+
+ Öle & Fette + 2 / 6 +
+ +
+
+ +
+
+
+
+ +

+ Trade-off: Solves visual overload immediately — only one category is in view at a time. + The Alle/Keine actions eliminate bulk-select friction. Count badges answer "where do I still need to configure?" + at a glance. Risk: accordion interaction adds a tap per category visit; power users who want + a full overview lose it. Works best when categories are <8. Keyboard nav via Enter/Space on row headers. +

+
+ + +
+
+ Concept 02 + Category Sidebar +
+

+ Left panel: sticky category list with count badges — acts as a mini-nav. + Right panel: the selected category's full chip cloud. One category in focus, full list visible. + Mobile collapses to top tabs (scrollable). +

+ +
+ + +
+
Mobile — horizontal tab strip
+
+
+ ← Einstellungen +
Vorräte
+
+ + +
+
+ Gemüse + 7 +
+
+ Milch + 4 +
+
+ Gewürze + 0 +
+
+ Öle + 2 +
+
+ + +
+
+ Gemüse — 7 gewählt +
+ + +
+
+
+ Möhren + Zwiebeln + Knoblauch + Zucchini + Aubergine + Paprika + Spinat + Tomaten + Lauch + Brokkoli + Kohl + Sellerie + Gurke + Blumenkohl +
+
+
+
+ + +
+
Desktop — sidebar + content panel
+
+ + +
+
+ Kategorien +
+ + +
+ Gemüse + 7 +
+
+ Milchprodukte + 4 +
+
+ Gewürze + 0 +
+
+ Öle & Fette + 2 +
+
+ Getreide + 0 +
+
+ Fleisch & Fisch + 3 +
+
+ Backwaren + 1 +
+
+ + +
+
+ Gemüse — 7 von 18 gewählt +
+ + +
+
+
+ Möhren + Zwiebeln + Knoblauch + Zucchini + Aubergine + Paprika + Spinat + Tomaten + Lauch + Brokkoli + Kohl + Sellerie + Gurke + Blumenkohl + Kohlrabi + Rote Bete + Fenchel + Erbsen +
+
+
+
+ Automatisch gespeichert · 23 Vorräte insgesamt +
+
+
+ +

+ Trade-off: Best scalability — the sidebar grows gracefully to 15+ categories without + visual breakdown. The "23 Vorräte insgesamt" footer answers the comprehension question for the whole household. + Active/inactive category coloring (green-tint bg + green-dark text) matches the existing nav convention exactly. + Risk: Two-panel layouts can feel over-engineered for simple tasks. Mobile tab strip + truncates long category names — needs abbreviation strategy. +

+
+ + +
+
+ Concept 03 + Search-First +
+

+ Search bar dominates the top. Below it: a single scrollable list with sticky category headers (like iOS Contacts). + Searching filters in real-time — category headers collapse when their section is empty. + For power users and anyone who knows what they want. +

+ +
+ + +
+
Mobile — with active search
+
+ ← Einstellungen +
Vorräte
+ + +
+ + + + Par + + +
+ + +
+ + +
+ Käse & Milchprodukte +
+ + +
+ Parmesan +
+ +
+
+
+ Parmesan gerieben +
+
+ +
+ Gewürze +
+
+ Paprika edelsüß +
+
+ +
+ 3 Treffer für „Par" +
+ +
+ +

Automatisch gespeichert.

+
+
+ + +
+
Desktop — idle state (no search)
+
+ ← Einstellungen +
+
Vorräte
+ 23 gewählt +
+

Automatisch gespeichert. Gilt ab der nächsten Einkaufsliste.

+ + +
+ + + + Zutat suchen … +
+ + +
+ +
+ Gemüse +
+
+ Möhren + Zwiebeln + Knoblauch + Zucchini + Aubergine + Paprika + Spinat + Tomaten +
+ +
+ Milchprodukte +
+
+ Butter + Milch + Joghurt + Sahne + Parmesan + Mozzarella +
+ +
+ Gewürze +
+
+ Salz + Pfeffer + Kreuzkümmel + Paprika edelsüß + Kurkuma +
+ +
+
+
+
+ +

+ Trade-off: Fastest path for users who know what they want. Search cuts through all category + hierarchy instantly. The checklist row style in search results (vs chips) is intentional — it's denser and + keyboard-navigable (arrow keys + Space). The idle state preserves the chip cloud so casual browsing stays familiar. + Risk: Two different interaction patterns (chips vs checklist rows in search) add cognitive load. + The sticky-header list can be hard to test cross-browser. +

+
+ + +
+
+ Concept 04 + Checklist Table +
+

+ Abandon chips entirely. A single scrollable table: category, ingredient name, checkbox. + Sorted by category. Extremely keyboard-friendly, screen-reader native, and + far denser — 40 ingredients fit in the same space as 10 chips. +

+ +
+ + +
+
Mobile — 320px
+
+ ← Einstellungen +
Vorräte
+

23 gewählt · automatisch gespeichert

+ + +
+ + Suchen … +
+ + +
+ + +
+ Gemüse + 7 / 18 +
+ +
+ Möhren + +
+
+ Zwiebeln + +
+
+ Aubergine + +
+
+ Spinat + +
+ + +
+ Milchprodukte + 4 / 12 +
+
+ Butter + +
+
+ Joghurt + +
+ +
+
+
+ + +
+
Desktop — two-column table
+
+ ← Einstellungen +
+
Vorräte
+ 23 gewählt +
+

Automatisch gespeichert. Gilt ab der nächsten Einkaufsliste.

+ + +
+
+ + Zutat suchen … +
+ +
+ + +
+ + +
+
+ Gemüse + 7 / 18 +
+
+ Möhren + +
+
+ Zwiebeln + +
+
+ Aubergine + +
+
+ Spinat + +
+
+ Zucchini + +
+
+ + +
+
+ Milchprodukte + 4 / 12 +
+
+ Butter + +
+
+ Milch + +
+
+ Joghurt + +
+
+ Sahne + +
+
+ +
+ +
+
+
+ +

+ Trade-off: Highest information density. Excellent keyboard navigation (tab to checkbox, space to toggle). + Screen readers get a native checklist — no ARIA juggling needed. The two-column desktop layout keeps categories + side by side without the chip-cloud imbalance problem. + Risk: Loses the "selection feels satisfying" quality of chip toggling — a checkbox is functional, + not delightful. Row highlight (green-tint bg) on checked rows restores some of that signal. + May feel clinical for a consumer cooking app. +

+
+ + +
+
+ Concept 05 + Enhanced Current +
+

+ Keep the chip paradigm — it works. Add three targeted fixes: + (1) category count badges + Alle/Keine micro-actions inline, + (2) a collapsible "overflow" per category beyond 8 chips, + (3) a global count in the header. Zero new navigation patterns to learn. +

+ +
+ + +
+
Mobile — current feel, fixed overload
+
+ ← Einstellungen +
+
Vorräte
+ 23 gewählt +
+

Automatisch gespeichert.

+ + +
+
+
+ Gemüse + 7 / 18 +
+
+ + +
+
+ +
+ Möhren + Zwiebeln + Knoblauch + Paprika + Tomaten + Brokkoli + Zucchini + Aubergine + + +
+
+ + +
+
+
+ Gewürze + 0 / 24 +
+
+ + +
+
+
+ Salz + Pfeffer + Kreuzkümmel + Kurkuma + Zimt + Paprika + Koriander + Thymian + +
+
+ + +
+
+
+ Öle & Fette + 2 / 6 +
+
+ + +
+
+
+ Olivenöl + Rapsöl + Kokosöl + Butter + Ghee + Sesamöl +
+
+
+
+ + +
+
Desktop — 3-col grid, enhanced
+
+ ← Einstellungen +
+
Vorräte
+ 23 Vorräte gewählt +
+

Automatisch gespeichert. Gilt ab der nächsten Einkaufsliste.

+ +
+ + +
+
+
+ Gemüse + 7/18 +
+
+ + +
+
+
+ Möhren + Zwiebeln + Knoblauch + Paprika + Tomaten + Brokkoli + Zucchini + Aubergine + +
+
+ + +
+
+
+ Gewürze + 0/24 +
+
+ + +
+
+
+ Salz + Pfeffer + Kreuzkümmel + Kurkuma + Zimt + Paprika + Thymian + Oregano + +
+
+ + +
+
+
+ Öle & Fette + 2/6 +
+
+ + +
+
+
+ Olivenöl + Rapsöl + Kokosöl + Butter + Ghee + Sesamöl +
+
+ +
+
+
+
+ +

+ Trade-off: Lowest risk, smallest diff — the three components (CategorySection, StapleChip, StaplesManager) + need only minor additions. No new navigation paradigm to learn. Count badges on category headers answer the + comprehension question. The "+N weitere…" overflow trigger caps visual height per category. + Alle/Keine bulk actions land where the eye already is (next to the label). + This is the recommendation for a first iteration — it fixes the acute pain without a redesign. + Concepts 01 or 02 are step-2 if the dataset grows substantially larger. +

+
+ + + + + + diff --git a/specs/staples-settings-tile.html b/specs/staples-settings-tile.html new file mode 100644 index 0000000..5bb9ae5 --- /dev/null +++ b/specs/staples-settings-tile.html @@ -0,0 +1,1456 @@ + + + + + + A3/D3 Vorräte Settings — Tile Redesign + + + + + +
+ + +
+
+

A3 / D3 — Vorräte Settings

+

Tile redesign · unified with Settings + Members page language

+
+
+ route /household/staples
+ context settings
+ Atlas · 2026-04-10 +
+
+ + +
Analysis
+
+

Why the current page looks out of place

+
+
+

Current — plain divs

+
    +
  • No surface background, no border, no shadow — floats naked on the page
  • +
  • Feels like a different app than /settings and /members
  • +
  • 3-col grid has no visual container to balance uneven category sizes
  • +
  • No count feedback on category level
  • +
  • Chip overflow creates page-length sprawl for large categories
  • +
+
+
+

Tile approach — matches SettingsCard shell

+
    +
  • Each category in a tile: radius-xl, shadow-card, surface bg — identical to SettingsCard
  • +
  • Tile acts as a visual container that naturally caps chip overflow
  • +
  • Count badge + Alle/Keine in tile header replaces raw category label
  • +
  • Grid: grid-cols-1 sm:grid-cols-2 gap-4 max-w-[820px] — identical to /settings
  • +
  • Zero new patterns — reuses the language the user already trusts
  • +
+
+
+
+ + +
Tile Anatomy
+
+
CategoryTile — token mapping
+
+ + +
+
+
+
+ Gemüse + 7 / 18 +
+
+ + +
+
+
+ Möhren + Zwiebeln + Knoblauch + Paprika + Zucchini + Aubergine + Tomaten + Spinat + +
+
+
+ + +
+
--radius-xlborder-radius (same as SettingsCard)
+
--color-surfacetile background
+
--color-borderborder (rest state)
+
--color-border-hoverborder on hover
+
--shadow-cardelevation (rest)
+
--shadow-raisedelevation (hover)
+
28pxpadding (all sides, same as SettingsCard)
+
16px / 500category title (same as SettingsCard)
+
--green-tint / --green-darkbadge when selected > 0
+
--color-subtle / mutedbadge when selected === 0
+
+

+ Tile does not have a link/href — it is an interactive container, not a navigation element. + Hover state is retained for visual feedback (user can see the tile is a unit), but no cursor:pointer on the tile itself. +

+
+
+
+
+ + +
Tile States
+
+ + +
+
Empty (0 selected)
+
+
+
+ Gewürze + 0 / 24 +
+
+ + +
+
+
+ Salz + Pfeffer + Kurkuma + Zimt + Thymian + Oregano + Paprika + Koriander + +
+
+
+ + +
+
Partial
+
+
+
+ Öle & Fette + 2 / 6 +
+
+ + +
+
+
+ Olivenöl + Rapsöl + Kokosöl + Butter + Ghee + Sesamöl +
+
+
+ + +
+
All selected
+
+
+
+ Milchprodukte + 4 / 4 +
+
+ + +
+
+
+ Butter + Milch + Joghurt + Parmesan +
+
+
+ +
+ + +
Page Previews
+ +
+ + +
+
Desktop — /settings page header + 2-col tile grid
+
+
+
+ + + ← Einstellungen + + +
+

Vorräte

+ 23 gewählt +
+

Automatisch gespeichert. Gilt ab der nächsten Einkaufsliste.

+ + +
+ + +
+
+
+ Gemüse + 7 / 18 +
+
+ + +
+
+
+ Möhren + Zwiebeln + Knoblauch + Paprika + Tomaten + Zucchini + Aubergine + Brokkoli + +
+
+ + +
+
+
+ Gewürze + 0 / 24 +
+
+ + +
+
+
+ Salz + Pfeffer + Kurkuma + Zimt + Thymian + Oregano + Paprika + Koriander + +
+
+ + +
+
+
+ Öle & Fette + 2 / 6 +
+
+ + +
+
+
+ Olivenöl + Rapsöl + Kokosöl + Butter + Ghee + Sesamöl +
+
+ + +
+
+
+ Milchprodukte + 4 / 4 +
+
+ + +
+
+
+ Butter + Milch + Joghurt + Parmesan +
+
+ + +
+
+
+ Getreide & Körner + 0 / 9 +
+
+ + +
+
+
+ Reis + Nudeln + Quinoa + Haferflocken + Linsen + Kichererbsen + +
+
+ + +
+
+
+ Fleisch & Fisch + 3 / 11 +
+
+ + +
+
+
+ Hühnerbrust + Hackfleisch + Lachs + Thunfisch + Eier + Speck + +
+
+ +
+
+
+
+
+ + +
+
Mobile — single col
+
+
+
+ + ← Einstellungen + +
+

Vorräte

+ 23 gewählt +
+

Automatisch gespeichert.

+ + +
+ +
+
+
+ Gemüse + 7/18 +
+
+ + +
+
+
+ Möhren + Zwiebeln + Knoblauch + Zucchini + Paprika + Aubergine + Tomaten + Spinat + +
+
+ +
+
+
+ Gewürze + 0/24 +
+
+ + +
+
+
+ Salz + Pfeffer + Kurkuma + Thymian + Oregano + Zimt + Paprika + Koriander + +
+
+ +
+
+
+ Öle & Fette + 2/6 +
+
+ + +
+
+
+ Olivenöl + Rapsöl + Kokosöl + Butter + Sesamöl +
+
+ +
+
+
+
+
+ +
+ + +
Context — how /settings and /household/staples sit side by side
+
+
+ /settings — same tile shell, consistent visual weight +
+
+

Einstellungen

+
+ + + + Vorräte +

23

+ Vorräte bearbeiten → +
+ + + + Haushalt +

3 Mitglieder

+ Mitglieder anzeigen → +
+ + + + Profil +

Max Mustermann

+ Profil bearbeiten → +
+ +
+
+
+ /household/staples — same tile shell applied to CategorySection +
+
+
+

Vorräte

+ 23 gewählt +
+
+ +
+
+
+ Gemüse + 7 / 18 +
+
+ + +
+
+
+ Möhren + Zwiebeln + Knoblauch + Zucchini + Paprika + Tomaten + Aubergine + Brokkoli + +
+
+ +
+
+
+ Gewürze + 0 / 24 +
+
+ + +
+
+
+ Salz + Pfeffer + Kurkuma + Thymian + Oregano + Paprika + Koriander + Zimt + +
+
+ +
+
+
+ + +
Seed Flow — Haushalt ohne Zutaten
+
+

Wann und wie wird der Katalog befüllt?

+
+
+

Backend — automatisch beim Erstellen des Haushalts

+
    +
  • Flyway-Migration oder HouseholdService seed bei POST /v1/households
  • +
  • Seed-Daten: ~100 typisch deutsche Grundzutaten in 8 Kategorien
  • +
  • Alle Zutaten mit isStaple = false — Nutzer entscheidet selbst
  • +
  • Kategorien werden mitgeneriert (gleicher Haushalt-Scope)
  • +
  • Danach normal editierbar: toggle, add, PATCH name/kategorie
  • +
+
+
+

Seed-Katalog — 8 Kategorien (Auswahl)

+
    +
  • Gemüse: Möhren, Zwiebeln, Knoblauch, Kartoffeln, Tomaten, Paprika, Spinat, Brokkoli, Zucchini, Lauch, Sellerie, Kohl, Blumenkohl, Kürbis, Süßkartoffeln …
  • +
  • Gewürze: Salz, Pfeffer, Kurkuma, Kreuzkümmel, Paprikapulver, Zimt, Thymian, Oregano, Rosmarin, Lorbeer, Muskat, Curry, Chili …
  • +
  • Öle & Essig: Olivenöl, Rapsöl, Sesamöl, Kokosöl, Apfelessig, Balsamico …
  • +
  • Milch & Käse: Butter, Milch, Sahne, Joghurt, Parmesan, Mozzarella, Quark …
  • +
  • Getreide & Hülsenfrüchte: Reis, Nudeln, Linsen, Kichererbsen, Haferflocken, Quinoa, Couscous …
  • +
  • Fleisch & Fisch: Hähnchenbrust, Hackfleisch, Lachs, Thunfisch (Dose), Eier, Speck …
  • +
  • Backzutaten: Mehl, Zucker, Backpulver, Hefe, Speisestärke, Vanille …
  • +
  • Saucen & Würzmittel: Tomatenmark, Sojasoße, Senf, Ketchup, Brühe, Kokosmilch …
  • +
+
+
+
+ + +
Add-Ingredient Flow — Inline pro Tile
+

+ Jedes Category-Tile hat am unteren Ende einen stillen „+ Zutat hinzufügen" Trigger. + Der Flow bleibt inline — kein Modal, kein Sheet. Neue Zutat erscheint sofort als ausgewählter Chip. +

+ + +
+ + +
+
1 — Rest (Trigger sichtbar)
+
+
+
+ Gewürze + 0 / 24 +
+
+ + +
+
+
+ Salz + Pfeffer + Kurkuma + Thymian + +
+ +
+ +
+
+
+ + +
+
2 — Eingabe aktiv
+
+
+
+ Gewürze + 0 / 24 +
+
+ + +
+
+
+ Salz + Pfeffer + Kurkuma + Thymian + +
+ +
+
+ + + +
+

Enter zum Speichern · Esc zum Abbrechen

+
+
+
+ + +
+
3 — Zutat hinzugefügt (optimistic)
+
+
+
+ Gewürze + 1 / 25 +
+
+ + +
+
+
+ Salz + Pfeffer + Kurkuma + Thymian + + Safran + +
+
+ +
+
+
+ + +
+
4 — Duplikat-Fehler
+
+
+
+ Gewürze + 0 / 24 +
+
+ + +
+
+
+ Salz + + Pfeffer + Kurkuma + Thymian + +
+
+
+ + + +
+

„Pfeffer" existiert bereits in dieser Kategorie.

+
+
+
+ +
+ + +
+
+
Desktop — Tile mit aktiver Eingabe (Gemüse-Tile, Eingabe offen)
+
+
+
+
+

Vorräte

+ 23 gewählt +
+

Automatisch gespeichert.

+ +
+ + +
+
+
+ Gemüse + 7 / 18 +
+
+ + +
+
+
+ Möhren + Zwiebeln + Knoblauch + Paprika + Zucchini + Tomaten + Aubergine + Brokkoli + +
+ +
+
+ + + +
+
+
+ + +
+
+
+ Gewürze + 0 / 24 +
+
+ + +
+
+
+ Salz + Pfeffer + Kurkuma + Thymian + Oregano + Paprika + +
+
+ +
+
+ +
+
+
+
+
+ +
+
Mobile — Tile mit Eingabe
+
+
+
+
+

Vorräte

+ 23 gewählt +
+
+
+
+
+ Gemüse + 7/18 +
+
+ + +
+
+
+ Möhren + Zwiebeln + Zucchini + Paprika + +
+
+
+ + + +
+
+
+
+
+
+ Gewürze + 0/24 +
+
+ + +
+
+
+ Salz + Pfeffer + Kurkuma + +
+
+ +
+
+
+
+
+
+
+
+ + + + + ───────────────────────────────────────────────────────────────── + + ## 2. StaplesManager.svelte — grid + selectAll/deselectAll wiring + + GRID CLASS CHANGE: + BEFORE: "grid grid-cols-1 gap-[24px_32px] md:grid-cols-3" + AFTER: "grid grid-cols-1 sm:grid-cols-2 gap-4 max-w-[820px]" + // matches /settings page exactly + + TOTAL COUNT COMPUTATION (new): + totalSelected = $derived(Object.values(stapleState).filter(Boolean).length) + + SELECT ALL HANDLER (new): + function handleSelectAll(categoryId: string) { + const category = categories.find(c => c.id === categoryId); + if (!category) return; + for (const ing of category.ingredients) { + if (!stapleState[ing.id]) { + stapleState[ing.id] = true; + getPatcher(ing.id)(ing.id, true); + } + } + } + + DESELECT ALL HANDLER (new): + function handleDeselectAll(categoryId: string) { + const category = categories.find(c => c.id === categoryId); + if (!category) return; + for (const ing of category.ingredients) { + if (stapleState[ing.id]) { + stapleState[ing.id] = false; + getPatcher(ing.id)(ing.id, false); + } + } + } + + PASS TO CategorySection: + onSelectAll={() => handleSelectAll(category.id)} + onDeselectAll={() => handleDeselectAll(category.id)} + + EXPOSE totalSelected as prop or slot for +page.svelte to render in header. + + ───────────────────────────────────────────────────────────────── + + ## 3. +page.svelte (settings context) — h1 row + + CHANGE header to match /settings and /members exactly: + + ← Einstellungen +
+

Vorräte

+ + {totalSelected} gewählt + +
+

+ Automatisch gespeichert. Gilt ab der nächsten Einkaufsliste. +

+ + NOTE: mb-8 (32px) before grid matches /settings page spacing. + + ───────────────────────────────────────────────────────────────── + + ## 4. Token reference table + + | Token | Value | Applied to | + |--------------------------|----------|-------------------------------| + | --radius-xl | 16px | tile border-radius | + | --color-surface | #F5F4EE | tile background | + | --color-border | #D8D7D0 | tile border (rest) | + | --color-border-hover | #C0BFB8 | tile border (hover) | + | --shadow-card | ... | tile elevation (rest) | + | --shadow-raised | ... | tile elevation (hover) | + | 28px | — | tile padding (desktop) | + | 20px | — | tile padding (mobile, md:28px)| + | --green-tint/dark | — | badge when selected > 0 | + | --color-subtle/muted | — | badge when selected === 0 | + | --color-text-muted | #6B6A63 | action btn text, overflow btn | + | --color-border | #D8D7D0 | action btn border | + | 16px / 500 / --font-sans | — | category title (= SettingsCard title) | + | gap-4 (16px) | — | grid gap (= /settings grid) | + | sm:grid-cols-2 | — | grid breakpoint | + | max-w-[820px] | — | grid max-width | + + ## 5. No-op: StapleChip.svelte + No changes needed. Chip tokens and hover/focus styles remain identical. + + ## 6. Mobile adjustment + On mobile (< sm), tile padding reduces to 20px: + class="p-[20px] md:p-[28px]" + Title font-size stays 16px/500 (no mobile reduction — tile is smaller but label stays legible). + + ═══════════════════════════════════════════════════════════════════ + SEED FLOW + ═══════════════════════════════════════════════════════════════════ + + ## 7. Backend — Household seed on creation + + TRIGGER: after successful household creation (HouseholdService or Flyway data migration) + SCOPE: per-household (all seeded rows carry the new household's ID) + DEFAULT: isStaple = false for all seeded ingredients + + CATEGORIES TO SEED (with sortOrder): + 1 Gemüse + 2 Gewürze & Kräuter + 3 Öle & Essig + 4 Milch & Käse + 5 Getreide & Hülsenfrüchte + 6 Fleisch & Fisch + 7 Backzutaten + 8 Saucen & Würzmittel + + SEED INGREDIENTS (representative list — full list in seed SQL/Java constant): + Gemüse: Möhren, Zwiebeln, Knoblauch, Kartoffeln, Tomaten, Paprika (rot), + Paprika (gelb), Spinat, Brokkoli, Zucchini, Lauch, Sellerie, + Blumenkohl, Kohl, Kürbis, Süßkartoffeln, Aubergine, Gurke, + Erbsen (TK), Mais (Dose) + Gewürze & Kräuter: Salz, Pfeffer (schwarz), Kurkuma, Kreuzkümmel, Paprikapulver (edelsüß), + Paprikapulver (scharf), Zimt, Thymian, Oregano, Rosmarin, Lorbeerblätter, + Muskatnuss, Currypulver, Chilliflocken, Koriander (gemahlen), + Petersilie, Schnittlauch, Basilikum + Öle & Essig: Olivenöl, Rapsöl, Sesamöl, Kokosöl, Apfelessig, Balsamico-Essig, + Weinessig + Milch & Käse: Butter, Milch (3,5%), Sahne, Schmand, Joghurt (natur), Parmesan, + Mozzarella, Gouda, Quark, Frischkäse, Feta + Getreide & Hülsenfrüchte: Reis (Langkorn), Basmati-Reis, Nudeln, Spaghetti, + Linsen (rote), Linsen (grüne), Kichererbsen (Dose), Kidneybohnen (Dose), + Haferflocken, Quinoa, Couscous, Bulgur, Polenta + Fleisch & Fisch: Hähnchenbrust, Hähnchenkeule, Hackfleisch (gemischt), Rinderhack, + Lachs (Filet), Thunfisch (Dose), Garnelen (TK), Speck, Eier + Backzutaten: Mehl (Typ 405), Mehl (Typ 550), Zucker, Brauner Zucker, + Backpulver, Natron, Hefe (trocken), Speisestärke, Vanillezucker, + Puderzucker, Kakaopulver, Schokolade (Zartbitter) + Saucen & Würzmittel: Tomatenmark, Passierte Tomaten, Tomaten (Dose, ganz), + Sojasoße, Senf (mittelscharf), Ketchup, Mayonnaise, + Gemüsebrühe, Hühnerbrühe, Kokosmilch, Worcestershire-Sauce, + Tabasco, Honig, Ahornsirup + + IMPLEMENTATION NOTE: + Option A (preferred): Java constant in HouseholdSeedService.java, called from + HouseholdService.createHousehold() after persisting the household entity. + Option B: Flyway repeatable migration R__seed_ingredients.sql — only if global + seed makes sense (it doesn't here: ingredients are household-scoped). + Deduplication: before inserting, check if category/ingredient with same name + (citext) already exists for the household — skip if found. This makes the + seed idempotent (safe to re-run in dev). + + ═══════════════════════════════════════════════════════════════════ + ADD INGREDIENT FLOW + ═══════════════════════════════════════════════════════════════════ + + ## 8. Backend — new POST /v1/ingredients endpoint + + REQUEST: POST /v1/ingredients + Content-Type: application/json + { "name": "Fenchel", "categoryId": "uuid-of-category" } + categoryId is optional — if omitted, ingredient has no category + + RESPONSE: 201 Created + { "id": "new-uuid", "name": "Fenchel", "isStaple": true, "categoryId": "..." } + NOTE: newly user-created ingredients default to isStaple = true + (user explicitly added it — reasonable to assume they want it tracked) + + ERROR 409 Conflict: + { "error": "DUPLICATE_INGREDIENT", "message": "..." } + Backend uses citext so "fenchel" == "Fenchel" == "FENCHEL" + + AUTHORIZATION: planner role (same as PATCH) + + ## 9. Frontend — CategorySection.svelte additions + + NEW PROP: + onAddIngredient: (name: string) => Promise<{ id: string; name: string } | 'duplicate' | 'error'> + + NEW STATE: + addMode: boolean = false + addInput: string = '' + addError: '' | 'duplicate' | 'error' = '' + addHighlightId: string | null = null // ID of duplicate chip to highlight briefly + + TILE FOOTER (below chip cloud, always rendered): +
+ + {#if !addMode} + + + {:else} +
+ { + if (e.key === 'Enter') handleAdd(); + if (e.key === 'Escape') { addMode = false; addError = ''; } + }} + class="flex-1 min-w-0 + font-[var(--font-sans)] text-[13px] text-[var(--color-text)] + bg-[var(--color-page)] + border rounded-[var(--radius-md)] px-[10px] py-[6px] + {addError ? 'border-[var(--color-error)]' : 'border-[var(--green-light)]'} + focus:outline-none" + /> + + +
+ + {#if addError === 'duplicate'} +

+ „{addInput}" existiert bereits in dieser Kategorie. +

+ {:else if addError === 'error'} +

+ Konnte nicht gespeichert werden. +

+ {/if} + {/if} + +
+ + ADD HANDLER: + async function handleAdd() { + const name = addInput.trim(); + if (!name) return; + addError = ''; + const result = await onAddIngredient(name); + if (result === 'duplicate') { + addError = 'duplicate'; + // find matching ingredient by name (citext: case-insensitive compare) + const existing = ingredients.find(i => i.name.toLowerCase() === name.toLowerCase()); + if (existing) { + addHighlightId = existing.id; + setTimeout(() => { addHighlightId = null; }, 2000); + } + } else if (result === 'error') { + addError = 'error'; + } else { + // success — optimistically add chip (selected = true, from POST response) + // parent (StaplesManager) handles state update + addMode = false; + addInput = ''; + addError = ''; + } + } + + DUPLICATE CHIP HIGHLIGHT: + In StapleChip.svelte, accept optional highlight prop: + let { ..., highlight = false }: { ...; highlight?: boolean } = $props(); + add class: highlight ? 'outline-2 outline-offset-2 outline-[var(--green-light)]' : '' + + ## 10. StaplesManager.svelte — onAddIngredient handler + + async function handleAddIngredient(categoryId: string, name: string) { + const res = await fetch('/household/staples/ingredients', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, categoryId }) + }); + + if (res.status === 409) return 'duplicate'; + if (!res.ok) return 'error'; + + const created: { id: string; name: string; isStaple: boolean } = await res.json(); + + // Optimistically add to categories state + categories = categories.map(cat => + cat.id === categoryId + ? { ...cat, ingredients: [...cat.ingredients, created] } + : cat + ); + // Mark as staple (POST returns isStaple: true) + stapleState[created.id] = true; + + return created; + } + + PASS TO CategorySection: + onAddIngredient={(name) => handleAddIngredient(category.id, name)} + + ## 11. New SvelteKit API route: +server.ts at /household/staples/ingredients + + File: frontend/src/routes/(app)/household/staples/ingredients/+server.ts + + export const POST: RequestHandler = async ({ request, locals }) => { + const { name, categoryId } = await request.json(); + const res = await fetch(`${BACKEND}/v1/ingredients`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: ... }, + body: JSON.stringify({ name, categoryId }) + }); + return new Response(await res.text(), { status: res.status, + headers: { 'Content-Type': 'application/json' } }); + }; + --> +
+ + + +