Commit Graph

199 Commits

Author SHA1 Message Date
ba79cff4e7 feat(planner): show variety score in swap menu via RecipePicker
Replace SwapSuggestionList with RecipePicker in both mobile and desktop
swap contexts. RecipePicker now accepts excludeRecipeId, replacingRecipe,
and isDisabled props. Mobile swap sheet also triggers suggestion fetch
via activePickerDate so green/yellow/red score badges appear during swap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
55285e7d5d feat(planner): show score badges for all recipes in RecipePicker
- +server.ts: pass topN=100 so all recipes are scored in one request
- RecipePicker: Empfohlen keeps top 5 with scoreDelta > 0; builds a
  scoreMap from all suggestions; shows green/yellow/red delta badge on
  every recipe in Alle Rezepte that has a score entry
- Extracted scoreBadge snippet to avoid duplication between sections

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
055ae11fa3 feat(planner): show yellow neutral badge for scoreDelta = 0 in RecipePicker
Neutral suggestions (no variety impact) now show "= 0.0 Punkte" in yellow
instead of no badge, making the three states explicit: green (improves),
yellow (neutral), red (worsens).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
bf18f2bd84 fix(planner): format variety score to one decimal place
Avoids floating-point display like 6.199999999999999 by using
score.toFixed(1) in VarietyScoreCard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
da21a12222 feat(planner): replace Variationskonflikt with red delta badge
Shows the actual score delta (e.g. "↓ -1.5 Punkte") in red instead of a
generic ⚠ Variationskonflikt label, letting users compare the cost of each
recipe to make an informed swap decision.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
e9dc04b2a5 feat(planner): add remove meal with undo; fix RecipePicker badge for neutral delta
- MealActionSheet: new onremove prop + Entfernen button (guarded by #if)
- +page.svelte: handleRemoveMeal submits delete form, shows undo bar;
  undo re-adds via addSlot form; refactored handleUndo to undoCallback
  pattern; desktop day-detail panel also gets Entfernen button
- RecipePicker: only show green +delta badge when scoreDelta > 0;
  neutral (scoreDelta = 0) shows no badge instead of ⚠ Variationskonflikt
- Tests: page.test.ts remove-meal describe, RecipePicker neutral badge test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
8dfc3df06b fix(planning): hasConflict only when scoreDelta strictly negative
Neutral suggestions (scoreDelta = 0) are not conflicts — they simply
don't improve variety. Changing scoreDelta <= 0 to scoreDelta < 0
lets empty-plan additions and quality-neutral swaps show without a
misleading ⚠ Variationskonflikt warning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
ea070b4760 fix(planning): replace existing slot in simulation instead of appending
simulateVarietyScore was adding the candidate recipe on top of the
existing slot for slotDate, keeping the old recipe's tag-repeat penalty
in the score. Now the existing slot is excluded before simulating, so
swapping a recipe for one with better variety correctly shows positive
scoreDelta and hasConflict=false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
aecdf249d6 feat(planner): add onremove prop and Entfernen button to MealActionSheet
Button only renders when onremove callback is provided, keeping the
component usable in read-only contexts without the destructive action.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
e4345350ad fix(planner): RecipePicker UI polish from review
- Badge font-size 8px → 9px (WCAG minimum)
- Score badge toFixed(1) to avoid misleading "+0 Punkte"
- Self-contain @keyframes pulse in component <style> block
- Wählen buttons use var(--green-dark) per design system

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
56decf155d test(planner): clarify server.test.ts error-branch test name
"when backend returns error" → "when data is undefined (error response
without data)" — documents that the guard is data?.suggestions ?? [],
not error field inspection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
1de4b15e34 refactor(planner): extract Suggestion type to $lib/planner/types.ts
Removes the inline interface from RecipePicker.svelte and replaces
any[] in +page.svelte with Suggestion[] — compile-time safety.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
ccec0baa99 feat(planner): add AbortController to suggestion fetch $effect
Cancels the inflight request when activePickerDate changes or picker
closes, preventing stale responses from overwriting suggestions.
Adds page.test.ts covering fetch trigger, suggestion rendering,
and AbortSignal presence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
9928591b48 refactor(planner): extract computeCurrentScore helper in PlanningService
Eliminates duplicated currentSlots→score pattern that appeared in both
getSuggestions and getVarietyPreview.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
89a549a1c8 test(planner): assert hasConflict=true for neutral scoreDelta on empty plan
Documents the surprising-but-correct behavior: recipes on an empty plan
get scoreDelta=0.0, which satisfies scoreDelta<=0, so hasConflict=true.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
c24281dd4c test(planner): cover topN=0 and topN=-1 boundary in SuggestionsTest
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
8051fcbe22 refactor(planner): extract MAX_VARIETY_SCORE constant in PlanningService
Replaces magic literal 10.0 with a named constant in all four
scoring sites: getSuggestions, getVarietyPreview, scoreFromSimulatedSlots,
and getVarietyScore.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
b45ab0fd46 fix(planner): guard scoreDelta against undefined in RecipePicker badge
Defensive null-coalescing prevents crash when suggestion data arrives
without scoreDelta (e.g. stale backend or mismatched schema).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
2bbc3762e2 feat(planner): lazy-fetch variety suggestions in RecipePicker for empty slots
Derives activePickerDate from mobile pickerOpen/selectedDay and desktop
recipe-picker panel state, then uses $effect to fetch /planner?planId&date
on demand — wires suggestions and isLoading into both RecipePicker instances.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
a751b0758a feat(planner): add server.test.ts for GET /planner, fix sort + add error handling
- Sort uses scoreDelta instead of removed simulatedScore
- try/catch degrades gracefully to suggestions=[] on backend errors
- 6 tests cover: missing params, success, backend error, network throw, empty result

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
8234c2f162 feat(planner): RecipePicker uses scoreDelta/hasConflict, drop currentVarietyScore, add isLoading
- Suggestion interface: { recipe, scoreDelta, hasConflict } (no simulatedScore)
- Badge renders from hasConflict directly — no client-side delta computation needed
- New isLoading prop shows skeleton rows while suggestions fetch is in flight
- currentVarietyScore prop removed from component and both call sites follow in next commit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
257808016d chore(api): update SuggestionItem schema — scoreDelta + hasConflict replace simulatedScore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
cd7f4a1ea0 chore(planner): delete orphaned SuggestionCard component and test
Unused since the suggestions route was removed (commit 4333dc0).
RecipePicker.test.ts is the active coverage for suggestion rendering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
b673a466e9 feat(planner): replace simulatedScore with scoreDelta + hasConflict in SuggestionItem
SuggestionItem now exposes scoreDelta (simulatedScore − currentScore) and
hasConflict (scoreDelta ≤ 0) so the frontend can render badges without
needing to pass currentVarietyScore as a separate prop.

PlanningService.getSuggestions() computes currentScore once per request
and derives scoreDelta + hasConflict per candidate. Sorting is unchanged
(scoreDelta desc = simulatedScore desc since currentScore is constant).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:33:12 +02:00
e3066ec3e5 docs(specs): add C3 variety page rework mockups and V1 implementation spec
Three mockup variations (c3-variety-rework.html) for /planner/variety page,
plus detailed implementation spec for the chosen V1 "Erweiterte Karten" approach:
recipe names + swap links inside warning cards, minimal layout changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:31:14 +02:00
bd1604fc1d docs(specs): add detailed implementation spec for E4 variety settings (V2 Kontext-Preset)
5 states: S0 E1 hub update, S1 default, S2 preset selection + score simulation,
S3 advanced settings + Individuell chip, S4 reset confirmation dialog.
Includes API contract, preset mappings, weight multipliers, and LLM agent region.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:13:17 +02:00
c297403506 docs(specs): add 3 mockup variations for E4 variety settings screen
V1: Structured sections (toggles + segmented weight controls, low effort)
V2: Context preset chips (Omnivor/Vegetarisch/Vegan) with live score preview — recommended
V3: Rule cards with inline examples showing exact penalty impact

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 16:00:02 +02:00
fa4a4c9ef7 docs(specs): add J9 variety score config user journey and variety page rework spec
- Adds J9 (Configure variety score) to userjourneys.html — new journey for
  tuning the algorithm per household dietary context (e.g. disabling protein
  penalties for vegetarian households); introduces screen E4 (Variety settings)
- Adds specs/frontend/variety-page-rework.html with 3 design variations for
  the /planner/variety page rework: recipe-name pills, action rows (recommended),
  and week-grid with side panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 15:51:26 +02:00
6dd0b7ac93 docs(specs): add final frontend specs for members and settings Kachel views
Finalised implementation specs for /members (E2) and /settings (E1)
pages using the chosen Kachel (card grid) variation. Members spec
covers 6 states including role-change inline control and remove
confirmation dialog; notes backend gaps (DELETE/PATCH member
endpoints). Settings spec covers hub layout, D3 staples sub-page,
hover and empty states.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 15:06:11 +02:00
f0bbb3b009 fix(planner): exclude current recipe from swap suggestions
Adds excludeRecipeId prop to SwapSuggestionList so the meal being
replaced is not offered as a swap candidate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:38:30 +02:00
b4fa3ca23e feat(planner): add isLoading prop to SwapSuggestionList — disables Pick buttons during PATCH
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:32:59 +02:00
9482ecbf36 fix(planner): add truncation and title attribute to replacing-name span
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:31:13 +02:00
278fda7d90 fix(planner): translate MealActionSheet button labels to German
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:30:39 +02:00
8e3256d960 fix(planner): translate SwapSuggestionList copy to German
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:29:54 +02:00
30722d9bcc refactor(planner): extract shared recipe info markup into DayMealCard snippet
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:28:56 +02:00
dd9a86d4e9 feat(planner): wire J4 swap flow — mobile action sheet + desktop inline panel
Mobile: DayMealCard tap opens MealActionSheet; Swap → SwapSuggestionsSheet
(BottomSheet + SwapSuggestionList, easiest-first). Empty slots still open
RecipePicker directly.

Desktop: recipe-picker panel detects swap context (slot has recipe) and
renders SwapSuggestionList; empty slots continue to show RecipePicker.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:13:45 +02:00
c8c2605f31 feat(planner): add SwapSuggestionList component for J4 swap context
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:10:44 +02:00
1b2a02881d feat(planner): add MealActionSheet component for mobile swap trigger
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:07:46 +02:00
8756bf93d9 feat(planner): add sortEasiestFirst utility for J4 swap context
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:03:47 +02:00
dac83c70ea feat(planner): DayMealCard gains onactionsheet prop for full-card mobile tap target
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:02:47 +02:00
5b8d336d21 fix(planner): map backend role 'planner' to 'planer' and enlarge nav buttons to 40px touch targets
- hooks.server.ts: replace type-cast with actual mapping so isPlanner works
- planner page: set min-h/min-w 40px on prev/next/heute week buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 09:51:32 +02:00
e5d96cd85a fix(frontend): address all PR review concerns
- Fix 7px → 11px font-size on section headers in RecipePicker
- Extract shared slotActions.ts with UUID validation for planId/slotId/recipeId
- Load full recipe list in planner page load (was placeholder current-week slots)
- Update planner/+page.svelte to pass data.recipes as allRecipes to RecipePicker
- Update planner and recipes page.server.ts to use shared slot action helpers
- Fix planner page.server tests: add recipes mock for parallel GET load
- Update action tests to use valid UUIDs (were 'plan-1'/'r1' style strings)
- Add validation-path tests for blank/invalid input on all slot actions
- Add tests for recipes/+server.ts GET endpoint (DayPicker week navigation)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 08:19:37 +02:00
ea7113ec53 fix(backend): add role guard to variety-preview and extract shared scoring method
- Add @RequiresHouseholdRole("member") to GET /{planId}/variety-preview endpoint
  to require household membership (was accessible to any authenticated user)
- Extract scoreFromSimulatedSlots() private method eliminating duplicate logic
  between simulateVarietyScore() and the old computeCurrentScore()
- Fix loose variety preview test assertions (isBetween → exact assertEquals)
- Add test verifying negative scoreDelta when candidate is a duplicate recipe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 08:11:45 +02:00
4333dc0d84 refactor(planner): remove C2 suggestions route, replace with callback-based DayMealCard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 23:25:35 +02:00
cbafe783e9 feat(planner): integrate C4 RecipePicker with PanelState machine + slot actions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 23:23:26 +02:00
178c888635 feat(recipes): add C6 day-picker flow — week plan load + slot actions + DayPicker sheet
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 23:09:40 +02:00
f5adc051e8 feat(recipes): add C5 quick action buttons to RecipeCard
Always-visible "Jetzt kochen" and "Zur Woche +" buttons shown
when onplan prop is provided. Restructured card to avoid nested
interactive elements.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 22:58:50 +02:00
90c9ea1894 feat(planner): add UndoBar component with 4s auto-dismiss
Shows undo notification after slot add/replace. Rückgängig button
calls onundo, auto-dismisses after 4s via ondismiss callback.
Also patches test-setup for userEvent + fake timers compatibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 22:57:05 +02:00
ba41f6984b feat(planner): add DayPicker component (C6)
7-chip week strip with 5 slot states, inline replace warning,
confirm button, and prev/next week navigation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 22:44:29 +02:00
25c575c167 feat(planner): add RecipePicker component (C4) and suggestions API endpoint
C4 sheet content: Empfohlen section with variety delta badges,
Alle Rezepte with client-side search filter. GET /planner endpoint
proxies suggestions to backend for lazy client-side loading.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 22:42:10 +02:00