diff --git a/specs/frontend/j2-add-meal.html b/specs/frontend/j2-add-meal.html new file mode 100644 index 0000000..6358019 --- /dev/null +++ b/specs/frontend/j2-add-meal.html @@ -0,0 +1,913 @@ + + +
+ + +Supplemental spec · Bottom sheet recipe picker · Recipe quick actions · Day picker
+Three missing flows: picking a recipe for an empty slot (C4), quick actions on recipe cards (C5), and the day picker when the recipe is already known (C6).
+User taps "+" or an empty slot in C1. The day is known; the recipe isn't. A bottom sheet slides up over the dimmed planner, showing variety-ranked suggestions and a search-filtered full library. On desktop the empty tile highlights and the detail panel transforms to a recipe picker — the calendar grid stays fully visible. No full-page navigation; same pattern as J4 swap.
+User is browsing recipes and taps "Zur Woche +". The recipe is known; the day isn't. A compact day-picker sheet shows the week's slots. Empty slots have a dashed green border to invite selection. Selecting a filled slot shows an inline replace warning — no modal dialog. "Jetzt kochen" on the same card navigates directly to J3 cook mode.
+J4 Swap starts from a filled slot and shows system-suggested replacements. C4/C6 add starts from an empty slot or a chosen recipe. Different intent — do not reuse the swap sheet component.
+No full-page navigation for either entry point. The planner or recipe list context stays visible behind the sheet at 40% opacity.
+The 216px detail panel switches between states. No modal overlay. The calendar grid or recipe grid remains fully interactive beside the picker.
+Drag handle → title row (day date in muted subtitle) → close × → search input → two sections: "Empfohlen" (2–4 variety-ranked, sorted by variety_delta DESC) + "Alle Rezepte" (full library, filtered by search). Max height ~75vh — 3 suggestions visible without scrolling.
+Green "↑ +N Punkte" when adding this recipe improves the variety score. Yellow "⚠ [reason]" when it creates a conflict but is still selectable. No badge in the "Alle Rezepte" section.
+Weekly planner dims to 30% opacity behind the sheet. The week strip is still legible — the empty Sa chip is visible with its dashed border, providing context for which slot is being filled.
+Tapping "+ Wählen" writes to the slot immediately and dismisses the sheet. An undo toast appears for 4 seconds. No confirmation dialog — same pattern as J4.
+Clicking an empty slot tile (or "+" in the toolbar without a day) opens the recipe picker in the right detail panel. The calendar grid remains fully visible and interactive. The empty tile highlights with a solid green border and "Wählen…" label to indicate which slot is being edited.
+Clicked empty slot: solid green border (replaces dashed), green-tint bg, green-dark "+" icon and "Wählen…" label. Provides clear visual anchor for which slot the panel is operating on.
"Sa, 5. April" in Fraunces 14px / "Rezept wählen" in 9px muted below. Close × returns panel to idle state. If a filled day was previously selected, × returns to that day's detail view.
Immediate PATCH to slot. Panel switches to the day-detail view for the newly filled slot. No undo toast needed on desktop — the panel immediately shows the result with a "Swap" option if the user wants to change it.
/* TWO ENTRY POINTS:
+ * 1. "+" button in C1 nav → pre-selects next empty day in week (Mon–Sun scan).
+ * 2. Empty day slot chip (mobile) / empty tile (desktop) → pre-selects that date.
+ *
+ * MOBILE: bottom sheet slides up, planner dims to 30% opacity behind.
+ * Sheet: drag handle + {day label} header + × + search input + two sections.
+ * "Empfohlen" section: GET /api/suggestions?week={id}&day={date}
+ * → sorted by variety_delta DESC (unlike J4 which sorts by effort ASC).
+ * → 2–4 items. Badge: green "↑ +N Punkte" if delta > 0, yellow "⚠ {reason}" if ≤ 0.
+ * "Alle Rezepte": GET /api/recipes?sort=name — no badge.
+ * Search input: client-side filter of visible list. Does not re-sort.
+ * "+ Wählen" tap: PATCH /api/week-plan/{weekId}/slots/{date} {recipe_id}
+ * → dismiss sheet → undo toast 4s with "Rückgängig" link.
+ *
+ * DESKTOP: tap empty tile → detail panel enters recipe-picker state.
+ * Empty tile visual: solid green border, green-tint bg, "Wählen…" label.
+ * Panel state machine: idle | day-detail | recipe-picker | day-picker
+ * Clicking panel recipe row: PATCH → panel transitions to day-detail for that date.
+ * No undo toast on desktop (panel shows result immediately with Swap option). */
+ | Element | Value | Notes |
|---|---|---|
| Mobile sheet | ||
| Sheet max-height | 75vh | Drag to dismiss |
| Dim opacity | rgba(28,28,24,.4) | Same as J4 |
| Suggestion sort | variety_delta DESC | Different from J4 (effort ASC) |
| Suggestion badge | 8px, green-tint or yellow-tint | Only in Empfohlen section |
| Write action | PATCH + undo toast 4s | No confirmation dialog |
| Desktop panel | ||
| Active empty tile | solid green border, green-tint bg | Replaces dashed border |
| Panel width | 216px (unchanged) | Same panel, different content state |
| Panel recipe click | PATCH → transition to day-detail | No toast needed |
"Jetzt kochen" is filled green (primary). "Zur Woche +" is green-tint bg / green-dark text / green-light border (secondary). Same green family — clearly related, but different priority. Both are 10px mobile / 11px desktop, weight 500, radius-md.
+"Jetzt kochen" → navigate to /cook/{recipeId} (J3, no sheet or confirmation). "Zur Woche +" → open C6 day picker (bottom sheet mobile, panel transformation desktop). The Recipes tab stays active.
+/* Add two buttons to every recipe card in B1 (Recipes tab).
+ * Position: below the tags row, above any description/ingredients.
+ * "Jetzt kochen": navigate to /cook/{recipeId}. No confirmation. Primary style.
+ * "Zur Woche +": open C6 day-picker component. Secondary style.
+ * Both always visible — not hover-gated. Touch-capable desktops need always-visible actions.
+ * Mobile: equal-width flex row, font-size 10px, padding 5px 6px.
+ * Desktop grid: equal-width flex row, font-size 11px, padding 7px. */
+ | Token | Cook btn | Plan btn |
|---|---|---|
| Background | var(--green) | var(--green-tint) |
| Text color | #fff | var(--green-dark) |
| Border | none | 1px var(--green-light) |
| Font size mobile | 10px | 10px |
| Font size desktop | 11px | 11px |
| Radius | var(--radius-md) | var(--radius-md) |
Empty: dashed green-light border + green-tint bg (invites selection). Filled: solid color-border + surface bg + green dot. Today: yellow-tint border. Selected empty: 2px green-dark. Selected filled: 2px orange-dark + orange-tint → shows replace warning.
+The replace warning appears inline below the week strip. The confirm button turns orange. One tap replaces. An undo toast appears for 4 seconds. No modal dialog is shown — consistent with J4 swap.
+‹ › buttons allow browsing to next/prev week if the current week has no empty slots. Default: shows current week.
+The card whose day picker is open: 2px green border, green-tint bg, "Zur Woche +" button becomes green filled "Tag wählen…". Other cards at 45% opacity to keep focus on the active recipe.
Below the confirm button the panel shows the projected score change and reason: "↑ Score: 8 → 9 · Neues Protein". Omitted on mobile to keep the sheet compact. Calls GET /api/variety/preview?add={recipeId}&date={date}.
Same V2 pattern: clicking a filled day chip turns the confirm button orange and shows inline warning "Ersetzt [recipe] am [day]." Panel width is enough to show both elements without overlap.
/* Entry: tap "Zur Woche +" on recipe card. Recipe ID is known; date is unknown.
+ * Mobile: compact bottom sheet (~55vh). Background: recipe list at 28% opacity.
+ * Sheet: drag handle + recipe name header + week label + ‹ › week nav + 7-day strip + confirm.
+ *
+ * DAY CHIP STATES (7 chips, current week):
+ * .empty → dashed green-light border, green-tint bg (invite selection)
+ * .filled → solid color-border, surface bg, green dot below date number
+ * .today → solid yellow border, yellow-tint bg, yellow-text label
+ * .sel-empty → 2px green-dark border, green-tint bg → confirm btn = green
+ * .sel-filled → 2px orange-dark border, orange-tint bg → show dp-warn → confirm btn = orange
+ *
+ * Confirm empty: PATCH /api/week-plan/{weekId}/slots/{date} {recipe_id}
+ * → dismiss sheet → undo toast 4s "Rückgängig"
+ * Confirm filled: same PATCH (server replaces existing recipe_id)
+ * → dismiss sheet → undo toast 4s "Rückgängig"
+ *
+ * Desktop panel state: 'day-picker'
+ * Active recipe card: 2px green border, green-tint bg, button text "Tag wählen…" (green filled)
+ * Other cards: opacity 0.45
+ * Panel adds variety-preview section below confirm: GET /api/variety/preview?add={id}&date={date}
+ * Confirm → panel transitions to day-detail for newly filled date.
+ *
+ * Week navigation: ‹ › loads prev/next week's slots from GET /api/week-plan/{weekId±1}
+ * Default: current week. If current week fully filled, auto-advance to next week. */
+ | State | Border | Background | Confirm btn |
|---|---|---|---|
| empty | dashed green-light | green-tint | — |
| filled | solid color-border | color-surface | — |
| today | solid yellow | yellow-tint | — |
| sel-empty | 2px green-dark | green-tint | green |
| sel-filled | 2px orange-dark | orange-tint | orange + dp-warn |
C4: User knows the day, not the recipe. Entry: "+" or empty slot tap in C1. C5: Recipe card in B1 gains two quick action buttons. C6: User knows the recipe, not the day. Entry: "Zur Woche +" on C5 card. All three flows share the same backend write: PATCH /api/week-plan/{weekId}/slots/{date} { recipe_id }.
Both use a bottom sheet over dimmed content. The difference: J4 swap suggestions are sorted effort ASC (easiest first, because mid-week changes happen under stress). C4 suggestions are sorted variety_delta DESC (best variety impact first, because this is deliberate weekly planning).
+ +The 216px right panel can be in four states: idle (no selection) → day-detail (filled day selected in C1) → recipe-picker (C4 mode) → day-picker (C6 mode). The "×" close button always returns to the previous state. Successful pick transitions to day-detail for the affected date.
+ +All flows use DELETE /api/week-plan/{weekId}/slots/{date} when "Rückgängig" is tapped in the 4-second toast. Toast is dismissed on navigation. On desktop, no toast for C4 (panel shows result immediately with Swap option in the day-detail view).
Navigating to /cook/{recipeId} from the recipe list is a standard route push. The recipe list is available via the back button or bottom nav. Do not open cook mode in a sheet or modal — it is a full-screen experience per J3 spec.