diff --git a/specs/frontend/variety-page-rework.html b/specs/frontend/variety-page-rework.html new file mode 100644 index 0000000..d128314 --- /dev/null +++ b/specs/frontend/variety-page-rework.html @@ -0,0 +1,841 @@ + + +
+ + +3 Design-Variationen · Route: /planner/variety
+ Zwei Kernprobleme werden adressiert: (1) Warnungen zeigen aktuell Wochentag-Kürzel ("MON, WED")
+ statt Rezeptnamen — rein frontend-seitig lösbar über weekPlan.slots-Mapping.
+ (2) Es gibt keine Swap-Aktion direkt aus den Warnungen heraus. Das Protein-Score-Problem
+ für vegetarische Haushalte ist ein Backend-Thema und separat zu behandeln.
+
+ Die aktuelle Formel proteinDiversity = 10 − repeats × 2 bestraft vegetarische
+ Proteinquellen (Tofu, Linsen, Ei) stärker als in omnivoren Haushalten üblich.
+ Frontend-seitig ändert sich das Label "Protein-Vielfalt" ggf. zu "Quellen-Vielfalt" sobald
+ das Backend die Score-Gewichtung anpasst. Bis dahin: keine Änderung an ScoreBreakdownList.
+
tagRepeat.days[] → weekPlan.slots.find(s => s.dayOfWeek === day) → recipe.name/planner?week={weekStart}&swap={slotId} — öffnet RecipePicker für den betreffenden SlotdayOfWeek-MappingVarietyWarningCards.svelte + variety.ts anpassen; Rest der Seite bleibt<details>-Element — zugänglich, kein JavaScript nötigVarietyWarningCards grundlegend neu strukturieren+page.svelte Struktur und alle beteiligten Komponenten müssen neu aufgebaut werdenGilt für alle drei Variationen. Implementierungs-Details werden nach Variantenwahl konkretisiert.
+ +
+/* spec:rules — Variety Page Rework (alle Variationen)
+ *
+ * RECIPE NAME MAPPING (frontend, no backend change)
+ * Source: weekPlan.slots[] → { dayOfWeek: "MON"|"TUE"|..., recipe: { id, name } }
+ * tagRepeats[].days[] contains dayOfWeek keys (e.g. "MON")
+ * slotsByDay = Object.fromEntries(weekPlan.slots.map(s => [s.dayOfWeek, s]))
+ * recipeName = slotsByDay[day]?.recipe?.name ?? day
+ * slotId = slotsByDay[day]?.id
+ *
+ * SWAP NAVIGATION
+ * "Tauschen" button href: /planner?week={weekStart}&swap={slotId}
+ * weekStart available in page data
+ * slotId from weekPlan.slots mapping above
+ * Opens RecipePicker for that slot (existing functionality in planner page)
+ *
+ * DAY LABEL MAPPING (for display)
+ * MON → "Montag" TUE → "Dienstag" WED → "Mittwoch" THU → "Donnerstag"
+ * FRI → "Freitag" SAT → "Samstag" SUN → "Sonntag"
+ * Short: Mo, Di, Mi, Do, Fr, Sa, So
+ *
+ * EMPTY SLOT HANDLING
+ * If slotsByDay[day] is undefined: show day key only, no swap button
+ * This can happen if slot was deleted since varietyScore was computed
+ *
+ * PROTEIN SCORE — VEGETARIAN NOTE
+ * Label "Protein-Vielfalt" in ScoreBreakdownList may change to "Quellen-Vielfalt"
+ * pending backend decision on scoring weight adjustment.
+ * No frontend change required until backend ships the updated score.
+ *
+ * VARIATION-SPECIFIC
+ * V1: Modify VarietyWarningCards + Warning type (add slots: { day, recipeName, slotId }[])
+ * computeWarnings() now returns slots[] instead of string days[]
+ * V2: Restructure VarietyWarningCards to ActionRows; VarietyScoreHero → compact variant
+ * for sub-scores (no JS needed)
+ * V3: Replace protein grid with full week grid (recipe names); add side panel component
+ * Mobile: tab switcher (Übersicht | Hinweise) using $state activeTab
+ */
+
+
+ | Property | Value | Notes |
|---|---|---|
| Shared: Recipe Mapping | ||
| data-source | weekPlan.slots[].dayOfWeek + recipe | already in page data |
| swap-url | /planner?week={weekStart}&swap={slotId} | RecipePicker pre-selects slot |
| day-long | MON→Montag, TUE→Dienstag… | for V2 display |
| day-short | MON→Mo, TUE→Di… | for V1 pills + V3 grid |
| V1 Recipe Pills | ||
| pill-padding | 5px 10px 5px 12px | left more for text |
| swap-btn-size | 22×22px, border-radius 50% | within pill |
| pill-bg | white, border --yellow-light | on yellow-tint card |
| V2 Action Rows | ||
| score-compact-height | ~64px | replaces 180px hero |
| details-summary | native <details>, no JS | sub-scores hidden by default |
| recipe-row-bg | --color-subtle | within white action card |
| V3 Week Grid | ||
| slot-height | 52px min | enough for 2-line recipe name |
| warn-slot-ring | 2px solid --yellow + yellow-tint bg | problem indicator |
| selected-slot-ring | 2px solid --green-dark | active selection |
| panel-width | 280px | fixed, right side |
| mobile-tab-active-bg | --green-dark | selected tab button |
Recipe app · Eight core journeys · Planner & household member roles
+The app serves two user roles. The planner (you) has full access — adding recipes, building the weekly plan, generating the shopping list, and managing the household. The household member (your partner, other household members) has read-only access to the plan and collaborative access to the shopping list. Journeys J7 and J8 cover post-onboarding household management: adding or removing members and keeping the pantry staples configuration current. J9 covers tuning the variety score algorithm to match the household's dietary context (e.g. disabling meat-centric protein penalties for a vegetarian household).
+ +| Step | +What happens | +Screen | +Notes | +
|---|---|---|---|
| Open library | +Planner taps Recipes in nav or the + button anywhere in the app. | +B1 | +Entry from nav or from the planner (C1) when a day has no meal assigned. | +
| Add recipe form | +Planner enters recipe name, serves count, and adds ingredients one by one with name and quantity. | +B3 | +B3 is a single form used for both add and edit states. Form is prefilled when editing. | +
| Add cooking steps | +Planner adds numbered steps in free text. Steps are used in cook mode (B4). | +B3 | +Steps are optional at save time — a recipe without steps can still be planned and cooked from ingredient list only. | +
| Tag the recipe | +Planner selects tags: effort level (easy / medium / hard), child-friendly (yes/no), primary protein or category (chicken, fish, vegetarian, pasta, etc.). | +B3 | +Tags are used by the suggestion engine in J2 to avoid repetition. Minimum: effort + one category tag required to save. | +
| Save | +Recipe is saved and appears in the library (B1). Planner is returned to B1 or to wherever they came from. | +B1 | +If entered from a day slot in the planner, the recipe is optionally offered for that day immediately after saving. | +
| Step | What happens | Screen | Notes |
|---|---|---|---|
| Open planner | +Planner opens the app. The weekly planner is the default home screen showing the current week with any already-planned meals. | +C1 | +Today's slot is always highlighted in yellow. Empty slots show a dashed + prompt. | +
| Get suggestions | +Planner taps an empty day slot or the suggestions button. The app shows recipes filtered to avoid ingredients used in the past 3 days and the same protein as adjacent days. | +C2 | +Suggestions also balance effort — if the previous two days were hard meals, easy meals are surfaced first. | +
| Fill day slots | +Planner picks a suggestion or manually selects a recipe from the library for each day. | +C1 | +Saturday is optional — some weeks have no planned meal on Saturday. Empty slots are valid. | +
| Review variety score | +The variety score (0–10) updates live as meals are added. It reflects ingredient overlap across the week. Warnings surface for specific repeated ingredients. | +C3 C1 | +Score is visible at all times on C1 (variety banner on mobile/tablet, sidebar widget on desktop). C3 shows the full breakdown. | +
| Swap a meal (optional) | +If the variety score is low or a specific warning appears, the planner swaps one meal for an alternative suggestion. | +C2 | +Swap uses the same suggestion engine as the initial fill. Swapping updates the score immediately. | +
| Confirm the week | +Planner confirms the plan. The week is locked and visible (read-only) to household members. This also triggers the option to generate a shopping list (J5). | +C1 | +Confirming does not prevent future edits — the planner can still swap meals mid-week via J4. | +
| Step | What happens | Screen | Notes |
|---|---|---|---|
| Check today | +Planner opens the app. Today's slot is highlighted in yellow on the planner. The meal name is visible without any tap. | +C1 | +The today highlight is the most important element on C1. It must be visible on first render with zero interaction. | +
| Open recipe | +Planner taps the today slot. The recipe detail screen shows the full ingredient list and step count. | +B2 | +B2 shows ingredients scaled to the saved serving count. No serving adjustment is required in this journey. | +
| Enter cook mode | +Planner taps "Cook now". The screen switches to full-screen cook mode with one step visible at a time in large, readable text. Screen stays awake. | +B4 | +B4 is designed for kitchen use: 16px body text, 1.75 line height, single step per screen, tap-anywhere to advance. Screen wake lock is requested. | +
| Mark as cooked | +On the final step, the planner taps "Done". The meal is logged to cooking history with today's date. | +B4 → C1 | +The cooking log is the data source for the variety algorithm. Meals cooked more recently are weighted more heavily in the repetition filter. | +
| Step | What happens | Screen | Notes |
|---|---|---|---|
| Trigger swap | +Planner taps a meal slot and selects "Swap meal". The original meal is not yet removed — the swap is a preview. | +C1 | +The "Swap" button is visible inline in the upcoming list on mobile and in the detail panel on desktop — one tap away at all times. | +
| View quick alternatives | +The suggestion screen shows 3–5 low-effort alternatives that avoid the ingredients already used that week. Sorted: easiest first. | +C2 | +When the swap is triggered mid-week (today or a past day), effort filter defaults to Easy. The variety filter still applies. | +
| Confirm new meal | +Planner picks an alternative. The day slot updates immediately. Variety score recalculates. | +C1 | +If no suitable suggestion exists, the planner can manually select any recipe from the library. | +
| History logged | +The swap is logged — both the original meal (not cooked) and the replacement. This is used to ensure the original recipe isn't over-suppressed in future suggestions. | +— | +The original uncooked meal remains in the library and can be planned for a future week. | +
| Step | What happens | Screen | Role |
|---|---|---|---|
| Collect ingredients | +All ingredients from all planned meals are gathered. Shared ingredients across meals are merged and quantities summed (e.g. 3 + 2 carrots = 5 carrots). | +D1 | +Planner | +
| Smart filter | +Pantry staples (olive oil, salt, pasta, rice, etc.) defined by the planner in settings are automatically removed from the list. | +D3 | +Planner | +
| List goes live | +The generated list is immediately live and visible to all household members. No approval step needed. | +D1 | +Both roles | +
| Shop collaboratively | +Any household member can check off items while shopping. Checked items stay checked for all members — no double buying. | +D1 | +Household member | +
| Add items | +Household members can add items not on the generated list (e.g. household supplies, snacks). These appear at the bottom of the list. | +D1 | +Both roles | +
| Step | What happens | Screen | Role |
|---|---|---|---|
| Create account | +Planner signs up with email and password. The account is created with the Planner role automatically assigned. | +A1 | +Planner | +
| Name the household | +Planner gives the household a name (e.g. "Smith family"). This name appears in the sidebar on desktop and in the app header. | +A2 | +Planner | +
| Define pantry staples | +Planner selects which ingredients their household always has on hand. These are excluded from generated shopping lists. A default list of common staples is pre-selected and can be adjusted. | +A3 | +Planner | +
| Invite household members | +Planner sends an invite link or code to household members (e.g. spouse). One invite per member. The invite grants Household Member role on acceptance. | +A2 | +Planner | +
| Member accepts invite | +The invited person opens the link, creates an account, and is automatically joined to the household with the Member role. | +A4 | +Household member | +
| Access granted | +The member can now see the meal plan (read-only) and the shopping list (view, check off, add items). The planner retains full access. | +C1 D1 | +Both roles | +
| Step | What happens | Screen | Role |
|---|---|---|---|
| Open members page | +Planner navigates to the Members page from the settings nav. The page lists all current household members with their name, role (Planner / Member), and join date. Pending invites are shown separately with their expiry status. | +E2 | +Planner | +
| Invite a new member | +Planner taps "Mitglied einladen". An invite link or short code is generated and displayed. The planner copies it and sends it via any messaging app (WhatsApp, SMS, email). No email delivery system is required — the link is the mechanism. | +E2 | +Planner | +
| Member accepts invite | +The invited person opens the link on their device, creates an account if they don't have one, and is automatically added to the household with the Household Member role. The planner sees the new member appear in the roster without refreshing. | +A4 | +Household member | +
| Access granted | +The new member can now see the weekly meal plan (read-only) and the shopping list (view, check off, add items). Their name appears in the member list on E2. | +C1 D1 | +Both roles | +
| Remove a member | +Planner taps a member's row and selects "Zugang entziehen". After an explicit confirmation prompt showing the member's name, the member is removed. They lose all access immediately. Their account is not deleted — they simply leave the household. | +E2 | +Planner | +
| Step | What happens | Screen | Notes |
|---|---|---|---|
| Open settings | +Planner navigates to the Settings page (E1) from the settings nav. The settings hub provides access to profile information and pantry staples configuration. | +E1 | +E1 is the entry hub for settings-area screens. The Staples section is the most common destination from here and should be prominently placed. | +
| Open staples manager | +Planner taps the Staples section. The same StaplesManager component from onboarding (A3) renders in settings context — no sidebar, no "Weiter" navigation, just the ingredient category list. | +D3 | +D3 = A3. One component, two render contexts (onboarding and settings). Context is passed as a prop — no duplicate component needed. | +
| Browse and toggle | +Planner browses ingredient categories. Checked items are staples and will be excluded from shopping lists. Unchecked items will appear on the next generated list. Changes are saved automatically on each toggle — no save button required. | +D3 | +Auto-save on toggle is required. The planner must never lose a change because they forgot to tap a save button mid-browsing. | +
| Changes applied | +The updated staple configuration is persisted immediately. The next time a shopping list is generated (J5), the new staple set is used to filter out always-on-hand ingredients. | +— | +Changes do not retroactively update an already-generated shopping list. If the current list should reflect the change, the planner must regenerate it via J5. | +
| Step | What happens | Screen | Notes |
|---|---|---|---|
| Open settings | +Planner navigates to Settings (E1). The settings hub now shows a third card: "Vielfalt-Einstellungen". Current score is shown as a stat number on the card so the planner knows what they are about to tune. | +E1 | +Planner role only. Household members cannot see the Vielfalt-Einstellungen card. The card is added to the E1 settings hub grid alongside Vorräte and Haushalt cards. | +
| Open variety settings | +Planner taps the card. Screen E4 shows the full algorithm config: which tag types trigger repeat warnings (default: Protein, Küche) and the penalty weights for each violation type. | +E4 | +E4 shows the current household config. If no custom config has been saved, defaults are displayed: Protein ✓, Küche ✓; all weights at "Mittel". A "Zurücksetzen auf Standard" link is always visible. | +
| Toggle tag type checks | +Planner toggles "Protein" off. For a vegetarian household, removing the protein check means tofu, eggs, and legumes appearing on consecutive days no longer reduces the variety score. The change auto-saves immediately. | +E4 | +Each tag type in the household's recipe library can be checked or unchecked. Only types that actually appear in tagged recipes are listed — empty types are hidden. At least one type must remain checked; the toggle is disabled if it would leave zero checked types. | +
| Adjust penalty weights (optional) | +Planner uses segmented controls (Niedrig / Mittel / Hoch) to tune how severely each violation type reduces the score. For example, reducing ingredient overlap weight to "Niedrig" for households that cook mostly single-ingredient dishes. | +E4 | +Four weights are tunable: Tag-Wiederholung (default Mittel = 1.5 pts), Zutaten-Überschneidung (default Niedrig = 0.3 pts), Letzte Wochen (default Mittel = 1.0 pts), Doppelte Rezepte (default Hoch = 2.0 pts). Changes auto-save on interaction — no save button. | +
| Review updated score | +Planner navigates back to the variety review page (C3). The score and all warning cards now reflect the updated config. Warnings for the disabled tag type no longer appear. | +C3 | +The variety score shown on C1 (the planner home screen) also updates the next time the plan is loaded or a recipe is swapped. No full page reload is required on C3 — the server recalculates using the persisted config on each request. | +
| Reset to defaults (optional exit) | +If the planner wants to undo all changes, they tap "Zurücksetzen auf Standard" at the bottom of E4. A confirmation prompt names the specific values that will be reset. On confirm, the household config is deleted and the system defaults are restored. | +E4 | +Reset is a destructive action — it deletes the household's custom VarietyScoreConfig row. Confirmation dialog is required. Unlike most auto-save interactions, this one needs an explicit confirm because it discards all customisations at once. | +
This section is the authoritative journey reference for all agentic LLM tasks. Use it to understand which pages are involved in each journey, who performs each step, and what the key constraints are before building any screen or component.
+ +/* Journey rules for agents + * 1. Two roles exist: PLANNER (full access) and HOUSEHOLD_MEMBER (limited access). + * 2. PLANNER can access: all screens (A1–E4). + * 3. HOUSEHOLD_MEMBER can access: C1 (read-only) and D1 (view + check off + add items). + * 4. The variety score must be visible on C1 at all times — it is not a secondary feature. + * 5. J1 and J2 are preconditions for J5 — a shopping list requires planned meals with tagged recipes. + * 6. J6 is a precondition for J5 shared list — household members must be invited before the list is shared. + * 7. J3 "mark as cooked" feeds variety history which filters J2 suggestions. This feedback loop is critical. + * 8. J4 swap must complete in ≤ 3 taps from "Swap" to updated plan. + * 9. A3 and D3 are the same component (staples) — design and build once, reference from two entry points. + * 10. B3 and B4 (add + edit recipe) are the same form — build once with two initial states (empty vs prefilled). + * 11. The shopping list (D1) is real-time shared — checked items update for ALL household members instantly. + * 12. CalDAV export is future scope (E3) — do not build in v1. + * 13. Child accounts are future scope — do not design for in v1. + * 14. J7 is the post-setup continuation of J6 invite step — same A4 acceptance mechanism, different entry point (E2 not A2). The invite component is shared. + * 15. J8 entry is E1 (settings hub) → D3 (staples). D3 = A3: same component, settings context prop removes onboarding chrome (sidebar + nav footer). + * 16. Staple changes (J8) do not retroactively update an already-generated shopping list — planner must regenerate via J5 if the current list should reflect the change. + * 17. PLANNER cannot remove themselves from the household. Household role transfer is future scope, not v1. + * 18. J9 variety config is per-household and persisted in VarietyScoreConfig. Auto-save on toggle/weight change; reset-to-defaults requires a confirmation dialog. HOUSEHOLD_MEMBER cannot access E4. + * 19. J9 "Protein" tag-type toggle is the primary use case. Vegetarian households should disable it so tofu/eggs/legumes are not penalised for consecutive-day repetition. + * 20. E4 weight presets map to: Niedrig = ×0.5, Mittel = ×1.0 (default), Hoch = ×1.5 of the backend default weight. + */+ +
| Journey | +Title | +Actor(s) | +Screens touched (in order) | +Key constraint | +
|---|---|---|---|---|
| J1 | +Add a recipe | +PLANNER | +B1 → B3 → B1 | +Minimum tags required: effort + 1 category. Tags power J2 suggestions. | +
| J2 | +Plan the week | +PLANNER | +C1 → C2 → C1 → C3 → C1 (→ C2 if swap needed) | +Variety score must be visible throughout. Suggestions filter on: last 3 days ingredients, same-protein consecutive days, effort balance. | +
| J3 | +Cook tonight | +PLANNER | +C1 → B2 → B4 → C1 | +Cook mode (B4): 16px body text, 1.75 line-height, one step per screen, screen wake lock, tap-anywhere to advance. | +
| J4 | +Adapt on the fly | +PLANNER | +C1 → C2 → C1 | +Max 3 taps from Swap button to plan updated. Suggestions sorted by effort (easiest first) when triggered mid-week. | +
| J5 | +Generate shopping list | +PLANNER (generate) + HOUSEHOLD_MEMBER (shop) | +C1 → D1 (always live) | +Ingredients merged + summed across meals. Staples filtered (D3 config). Planner generates. List is always live — all members can view, check off, add, and remove items. Checked state persists for all users. | +
| J6 | +Household setup | +PLANNER (creates) + HOUSEHOLD_MEMBER (joins) | +A1 → A2 → A3 → A2 → [invite] → A4 | +One-time journey. A3 = D3 (same component). Invite via link/code (no email system). Member role grants: C1 read-only + D1 collaborative. | +
| J7 | +Manage household members | +PLANNER (manages) + HOUSEHOLD_MEMBER (accepts) | +E2 → [invite link] → A4 → E2 | +Post-onboarding companion to J6 invite step. One-tap re-generation for expired invite links. Member removal requires explicit confirmation with member name. PLANNER cannot remove themselves. | +
| J8 | +Edit pantry staples | +PLANNER | +E1 → D3 | +Auto-save on toggle — no save button. Changes apply to next J5 shopping list generation only, not retroactively. D3 = A3 (same component, settings context prop). | +
| J9 | +Configure variety score | +PLANNER | +E1 → E4 → C3 | +Auto-save on every toggle/weight change. Reset-to-defaults requires explicit confirmation. Protein tag-type toggle is the primary use case for vegetarian households. Config is stored per household — changes take effect on next variety score request, no plan changes required. | +
| Screen | +Name | +Journeys | +Planner access | +Member access | +
|---|---|---|---|---|
| A — Onboarding & auth | ||||
| A1 | Welcome / sign up | J6 | Full | Full (joining only) |
| A2 | Household setup + invite | J6 | Full | No access |
| A3 | Staples setup (= D3) | J6, J5 | Full | No access |
| A4 | Join household (accept invite) | J6 | N/A | Full |
| B — Recipe library | ||||
| B1 | Recipe library | J1, J2 | Full | No access |
| B2 | Recipe detail | J3 | Full | No access |
| B3 | Add / edit recipe (one form, two states) | J1 | Full | No access |
| B4 | Cook mode | J3 | Full | No access |
| C — Meal planning | ||||
| C1 | Weekly planner | J2, J3, J4, J5 | Full | Read-only |
| C2 | Meal suggestions | J2, J4 | Full | No access |
| C3 | Variety review | J2 | Full | No access |
| D — Shopping list | ||||
| D1 | Shopping list (live shared) | J5 | Full | View + check off + add items |
| D3 | Staples manager (= A3) | J5, J6, J8 | Full | No access |
| E — Settings | ||||
| E1 | Settings hub | J8, J9 | Full | No access |
| E2 | Household (members + roles) | J6, J7 | Full | View-only |
| E3 | Integrations (CalDAV — future) | — | Full | No access |
| E4 | Variety settings (tag types + weights) | J9 | Full | No access |