Adds Thumbnailator-based ImageCompressor that resizes uploaded images
to a 400px-wide JPEG preview stored in hero_image_preview. The recipe
list uses the preview instead of the full image URL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Store hero image as base64 data URI in text column (V023 migration)
- Add file upload UI to RecipeForm with FileReader preview
- Remove isChildFriendly from RecipeCreateRequest (no form field)
- Fix 500 on save: effort values now lowercase, serves/cookTimeMin changed
from primitive short to nullable Integer to survive omitted fields
- Fix empty categories panel: removed stale tagType=category filter
- Group category tags by type with German headings in recipe form
- Split SuggestionResponse.SuggestionRecipe (no image) from SlotRecipe
- Seed 11 HelloFresh recipes with ingredients, steps and tags (V101)
- Add frontend e2e scaffold, specs and dev yml
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
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>
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>
- 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>
Returns currentScore, projectedScore, and scoreDelta when a recipe
would be added on a given date. Used by C6 desktop day picker.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PATCH, DELETE, and POST slot endpoints now return 403 Forbidden
when called by a household member.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- generateFromPlan removes stale generated items
- sourceRecipes deduplicates when same recipe appears in two slots
- checkItem throws ResourceNotFoundException on household mismatch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renamed endpoint to /v1/shopping-list to avoid Springdoc path conflict.
Added @RequiresHouseholdRole("planner") on generate. Regenerated
frontend OpenAPI schema with all new shopping list endpoints.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New week-based lookup endpoint with optional weekStart param (defaults
to current week). Generate endpoint now enforced with @RequiresHouseholdRole.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a shopping list already exists for the week plan, regeneration
now merges: custom items and check states are preserved, existing
generated items are updated, removed recipes' items are deleted,
and new ingredients are added.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Returns the shopping list for a given week, defaulting to the current
week's Monday when no weekStart is provided. Returns null when no
list exists.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shopping list response now includes generatedAt timestamp, count of
filtered staples, and recipe names (not just UUIDs) in source references.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reusable annotation for planner-only endpoints. Uses a
HandlerInterceptor that resolves the household role from the
authenticated user and throws 403 if the role doesn't match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each domain had a single-implementation interface (e.g. AdminService
interface + AdminServiceImpl). Merged implementation into the service
class and deleted the redundant interfaces per KISS principle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shopping lists no longer go through a draft → published lifecycle.
They are immediately usable upon generation from a week plan.
Removed: status/published_at columns (V021 migration), publish endpoint,
PublishResponse DTO, delete-item guard, and 4 related tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add VarietyScoreConfig entity, repository, and V020 migration for
per-household scoring weights and configurable tag types
- Rewrite getVarietyScore: tag-type repeats on consecutive days,
non-staple ingredient overlaps, cooking log history, plan duplicates
- Rewrite getSuggestions: simulate variety score for each candidate,
add tag filter (AND, case-insensitive) and configurable topN param
- Update SuggestionResponse to return simulatedScore instead of
fitReasons/warnings, update VarietyScoreResponse to new shape
- Seed default VarietyScoreConfig on household creation
- Extend test suite across all domains (+270 tests, all passing)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Controller (5 tests): create household, get mine, get members,
create invite, accept invite.
Service (10 tests): household creation with planner role + seed
data (categories, tags, staple ingredients), conflict when already
in household, invite code generation with 48h expiry, accept invite
with expired/used/conflict validation.
Also includes:
- Household, HouseholdMember, HouseholdInvite JPA entities
- HouseholdInvite repository with findByInviteCode
- Ingredient, IngredientCategory, Tag entities + repositories
(created early for seed data, will be extended in recipe domain)
- Fixed BackendApplicationTests to use AbstractIntegrationTest
Total: 38 tests passing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Testcontainers 2.0.4 (PostgreSQL) for repository integration tests
- AbstractIntegrationTest base class with shared Postgres container
- application-test.yml for test profile
- Common module: ApiResponse/ApiError envelopes, GlobalExceptionHandler,
ResourceNotFoundException, ConflictException, ValidationException,
HouseholdContext record
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>