feat(meal-plan) Essensplan feature #10

Merged
araemer merged 42 commits from meal-plan into stage 2026-06-14 06:40:48 +00:00
Owner

Summary

  • Adds a full meal plan feature ("Essensplan") behind a new /meal-plan route, accessible from the settings menu for all authenticated users
  • Plans can be created, renamed, and shared with family members at three permission levels (readonly, read_write, full)
  • Calendar view shows the current week (7 columns on md+, single column on mobile); dish lists auto-save on every change with debounced note saving and immediate structural saves
  • Optimistic locking throughout; 409 conflicts surface as an error banner prompting a reload

What's included

Layer Files
ADR docs/adr/2026-05-31-meal-plan.md
Enum MealPlanPermission
DTOs (7) MealPlanDto, MealPlanDayDto, MealPlanDayDishDto, MealPlanShareDto, MealPlanSummaryDto, MealPlanListResponseDto, MealPlanLoadRequestDto
Models (5) MealPlanModel, MealPlanDayModel, MealPlanDayDishModel, MealPlanShareModel, MealPlanSummaryModel
Mappers (5) MealPlanDayDishMapper, MealPlanShareMapper, MealPlanSummaryMapper, MealPlanDayMapper, MealPlanMapper
API resource MealPlanRestResource (8 endpoints)
Hook useMealPlanDayAutoSave
Components (10) MealPlanDishCard, MealPlanDayNoteEditor, MealPlanCreateDialog, MealPlanRenameDialog, MealPlanAddDishPopup, MealPlanShareDialog, MealPlanDayColumn, MealPlanCalendar, MealPlanHeader, MealPlanPage
Routing getMealPlanUrl, getMealPlanUrlDefinition, MEAL_PLAN_PLAN_PARAM, MEAL_PLAN_WEEK_PARAM in routes.ts
Nav "Essensplan" entry in SettingsMenu, route wired in App.tsx
Utils getMondayOfWeek, formatIsoDate, parseIsoDate, getWeekDates in dateUtils.ts

Test plan

  • "Essensplan" appears in the settings menu and navigates to /meal-plan
  • Empty state shows a create button when no plans exist
  • Creating a plan sets its title and selects it; URL updates to include ?plan=<uuid>
  • Week navigation updates the ?week= param and reloads the day window
  • Adding a dish to an empty day creates the day entity and shows the recipe card
  • Servings +/– auto-saves immediately; note edits debounce 800 ms
  • Removing a dish saves the updated (shorter) dish list
  • Renaming a plan updates the header title and plan switcher
  • Sharing a plan with readonly permission hides all edit controls for that user
  • URL is bookmarkable: reload preserves selected plan and week

🤖 Generated with Claude Code

## Summary - Adds a full meal plan feature ("Essensplan") behind a new `/meal-plan` route, accessible from the settings menu for all authenticated users - Plans can be created, renamed, and shared with family members at three permission levels (readonly, read_write, full) - Calendar view shows the current week (7 columns on md+, single column on mobile); dish lists auto-save on every change with debounced note saving and immediate structural saves - Optimistic locking throughout; 409 conflicts surface as an error banner prompting a reload ## What's included | Layer | Files | |---|---| | ADR | `docs/adr/2026-05-31-meal-plan.md` | | Enum | `MealPlanPermission` | | DTOs (7) | `MealPlanDto`, `MealPlanDayDto`, `MealPlanDayDishDto`, `MealPlanShareDto`, `MealPlanSummaryDto`, `MealPlanListResponseDto`, `MealPlanLoadRequestDto` | | Models (5) | `MealPlanModel`, `MealPlanDayModel`, `MealPlanDayDishModel`, `MealPlanShareModel`, `MealPlanSummaryModel` | | Mappers (5) | `MealPlanDayDishMapper`, `MealPlanShareMapper`, `MealPlanSummaryMapper`, `MealPlanDayMapper`, `MealPlanMapper` | | API resource | `MealPlanRestResource` (8 endpoints) | | Hook | `useMealPlanDayAutoSave` | | Components (10) | `MealPlanDishCard`, `MealPlanDayNoteEditor`, `MealPlanCreateDialog`, `MealPlanRenameDialog`, `MealPlanAddDishPopup`, `MealPlanShareDialog`, `MealPlanDayColumn`, `MealPlanCalendar`, `MealPlanHeader`, `MealPlanPage` | | Routing | `getMealPlanUrl`, `getMealPlanUrlDefinition`, `MEAL_PLAN_PLAN_PARAM`, `MEAL_PLAN_WEEK_PARAM` in `routes.ts` | | Nav | "Essensplan" entry in `SettingsMenu`, route wired in `App.tsx` | | Utils | `getMondayOfWeek`, `formatIsoDate`, `parseIsoDate`, `getWeekDates` in `dateUtils.ts` | ## Test plan - [x] "Essensplan" appears in the settings menu and navigates to `/meal-plan` - [x] Empty state shows a create button when no plans exist - [ ] Creating a plan sets its title and selects it; URL updates to include `?plan=<uuid>` - [ ] Week navigation updates the `?week=` param and reloads the day window - [ ] Adding a dish to an empty day creates the day entity and shows the recipe card - [ ] Servings +/– auto-saves immediately; note edits debounce 800 ms - [ ] Removing a dish saves the updated (shorter) dish list - [ ] Renaming a plan updates the header title and plan switcher - [ ] Sharing a plan with readonly permission hides all edit controls for that user - [ ] URL is bookmarkable: reload preserves selected plan and week 🤖 Generated with [Claude Code](https://claude.ai/claude-code)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ -0,0 +20,4 @@
recipeTitle?: string;
/** Number of portions needed for this dish on this day. Must be a positive integer. Required. */
servings: number;
Author
Owner

If we wish to retain a consistent order here or allow the user to reorder the dishes in some way, the backend will have to add a sortOrder field.

If we wish to retain a consistent order here or allow the user to reorder the dishes in some way, the backend will have to add a sortOrder field.
araemer marked this conversation as resolved
@ -0,0 +6,4 @@
* For week navigation, set `dateFrom` to the Monday and `dateTo` to the Sunday of the
* displayed week. The backend returns only days whose `date` falls within the inclusive range.
*/
export interface MealPlanLoadRequestDto {
Author
Owner

Should be MealPlanFilterRequest - Backend renamed. Request and Response should never end on Dto

Should be MealPlanFilterRequest - Backend renamed. Request and Response should never end on Dto
araemer marked this conversation as resolved
@ -0,0 +5,4 @@
* a named field so the response shape can be extended with pagination metadata in the future
* without breaking existing consumers.
*/
export interface MealPlanListResponseDto {
Author
Owner

Naming should be consistent with backend! Request and Response should never end with Dto as they are no Dtos...

Naming should be consistent with backend! Request and Response should never end with Dto as they are no Dtos...
araemer marked this conversation as resolved
@ -0,0 +9,4 @@
* existing share and create a new one. `sharedWithUserDisplayName` is a read-only convenience
* field; do NOT send it on write requests.
*/
export interface MealPlanShareDto extends AbstractDto {
Author
Owner

This is out dated. Backend updated DTO. It's now
export class MealPlanShareDto extends AbstractDto {
/**
* UUID of the user this plan is shared with.
* Required on create; identifies the recipient of the access grant.
*/
sharedWithUserId!: string;

/**
 * Full user object for the shared-with user.
 * Populated by the backend in responses — not required when sending a request.
 */
sharedWithUser?: UserDto;

/**
 * The access level granted to the shared user.
 * @see MealPlanPermission
 */
permission!: MealPlanPermission;

}

This is out dated. Backend updated DTO. It's now export class MealPlanShareDto extends AbstractDto { /** * UUID of the user this plan is shared with. * Required on create; identifies the recipient of the access grant. */ sharedWithUserId!: string; /** * Full user object for the shared-with user. * Populated by the backend in responses — not required when sending a request. */ sharedWithUser?: UserDto; /** * The access level granted to the shared user. * @see MealPlanPermission */ permission!: MealPlanPermission; }
araemer marked this conversation as resolved
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ -0,0 +89,4 @@
const goToPrevWeek = () => {
const prev = new Date(weekStart);
prev.setDate(prev.getDate() - 7);
Author
Owner

I'd prefer to have a named constant for 7 instead of a magic number. E.g., const daysOfWeek = 7

I'd prefer to have a named constant for 7 instead of a magic number. E.g., const daysOfWeek = 7
araemer marked this conversation as resolved
@ -0,0 +108,4 @@
onClick={goToPrevWeek}
title="Vorherige Woche"
variant={IconButtonVariant.ghost}
size={20}
Author
Owner

Why the repeated magic 20? We should at least have a constant for this page instead of repeating the magic value. Probably even some global constants to keep sizes consistent on different pages / in different components.

Why the repeated magic 20? We should at least have a constant for this page instead of repeating the magic value. Probably even some global constants to keep sizes consistent on different pages / in different components.
araemer marked this conversation as resolved
@ -0,0 +120,4 @@
onClick={goToNextWeek}
title="Nächste Woche"
variant={IconButtonVariant.ghost}
size={20}
Author
Owner

See comment on magic numbers above.

See comment on magic numbers above.
araemer marked this conversation as resolved
@ -0,0 +47,4 @@
};
// Allow submitting via the Enter key in the text input for convenience.
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
Author
Owner

It's nice to have this feature, but why introduce it here? I'd like to have it, but then we need to add it to all our dialogs. Otherwise, we'll be inconsistent. Ideally, it's even ensured globally by a shared basic dialog component.

It's nice to have this feature, but why introduce it here? I'd like to have it, but then we need to add it to all our dialogs. Otherwise, we'll be inconsistent. Ideally, it's even ensured globally by a shared basic dialog component.
araemer marked this conversation as resolved
@ -0,0 +128,4 @@
onClick={enterEditMode}
title="Notiz bearbeiten"
variant={IconButtonVariant.ghost}
size={14}
Author
Owner

Here we have another magic size. Is this type of component used somewhere else as well? is this something that we should define in a central place? Or maybe add size-options to the IconButton?

Here we have another magic size. Is this type of component used somewhere else as well? is this something that we should define in a central place? Or maybe add size-options to the IconButton?
araemer marked this conversation as resolved
Three deviations corrected:

- MealPlanDayDishDto/Model: add sortOrder field (backend requires it;
  day mapper now sorts by sortOrder on load and passes array index on save)
- MealPlanShareDto: replace sharedWithUserDisplayName with sharedWithUser (UserDto);
  share mapper derives the display name from firstName/lastName/userName
- MealPlanSummaryDto/Model: make createdAt optional to match backend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename MealPlanLoadRequestDto → MealPlanFilterRequest and
  MealPlanListResponseDto → MealPlanListResponse to match backend naming
  convention (Request/Response types do not get a Dto suffix)
- Add BasicDialog shared component: backdrop, card, consistent heading,
  optional X close button, Escape-key handling, and backdrop-click-to-close —
  ensuring all dialogs behave consistently without each one reimplementing
  the close mechanics
- Refactor all 7 application dialogs (DeleteModal, MealPlanCreateDialog,
  MealPlanRenameDialog, MealPlanShareDialog, AiImportDialog,
  ChangePasswordModal, DeleteTagModal) to use BasicDialog
- Add DAYS_OF_WEEK constant to MealPlanCalendar to replace the magic 7

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduce IconButtonSize = 12 | 14 | 16 | 18 | 20 as the allowed values
for the size prop. Change the default from 13 to 14 and round the two
outliers: 15 → 16 in MealPlanDishCard and MealPlanShareDialog.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ -0,0 +46,4 @@
};
// Enter submits the form; Escape is handled globally by BasicDialog.
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
Author
Owner

check whether it's possible to have a general solution for confirm as well.

check whether it's possible to have a general solution for confirm as well.
araemer marked this conversation as resolved
@ -0,0 +68,4 @@
function pickDefaultPlan(plans: MealPlanSummaryModel[]): MealPlanSummaryModel | null {
if (plans.length === 0) return null;
// First plan where the user is the owner is the natural default.
const ownedPlan = plans.find(p => p.effectivePermission === "owner");
Author
Owner

Must owner be referenced by string.... Is there no less magic way to reference an enum value? What about a util method? I guess we'll need this check in several places.

Must owner be referenced by string.... Is there no less magic way to reference an enum value? What about a util method? I guess we'll need this check in several places.
araemer marked this conversation as resolved
@ -14,0 +20,4 @@
export function getMondayOfWeek(date: Date): Date {
const d = new Date(date);
// getDay() returns 0=Sunday … 6=Saturday. Shift so Monday=0, Sunday=6.
const dayOfWeek = (d.getDay() + 6) % 7;
Author
Owner

magic 7s again. Use constant for days of week in dateUtil. Export that constant here and use in calendar as well

magic 7s again. Use constant for days of week in dateUtil. Export that constant here and use in calendar as well
araemer marked this conversation as resolved
@ -109,0 +117,4 @@
if (planId) params.set(MEAL_PLAN_PLAN_PARAM, planId)
if (weekStart) params.set(MEAL_PLAN_WEEK_PARAM, weekStart)
const qs = params.toString()
return qs ? `/meal-plan?${qs}` : "/meal-plan"
Author
Owner

Why not use getMealPlanUrlDefinition here?

Why not use getMealPlanUrlDefinition here?
araemer marked this conversation as resolved
Replace the ad-hoc sharedWithUserDisplayName string field with a proper
sharedWithUser UserModel, consistent with how CommentMapper handles
createdByUser. The mapper now delegates to userMapper.mapDtoToModel()
and the dialog uses getUserDisplayName() from userUtils.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ -0,0 +116,4 @@
// Map each dish to its DTO shape, omitting id/version for brand-new dishes.
// Pass the array index as sortOrder so the backend persists the display order.
const dishDtos: MealPlanDayDishDto[] = (current?.dishes ?? []).map((dish, index): MealPlanDayDishDto => {
Author
Owner

shouldn't all this mapping be part of the mapper?

shouldn't all this mapping be part of the mapper?
araemer marked this conversation as resolved
@ -0,0 +226,4 @@
setSaveState("error");
} else {
// Any other network or server error — show generic error state.
setSaveState("error");
Author
Owner

Can't we reference all these state by enum values instead of magic strings.

Can't we reference all these state by enum values instead of magic strings.
araemer marked this conversation as resolved
- Remove inline DTO construction from buildSaveDto; delegate to
  mealPlanDayMapper.mapModelToDto which already handles conditional
  id/version omission and sortOrder assignment correctly
- Replace SaveState string union with an as-const object so consumers
  use SaveState.IDLE/SAVING/SAVED/ERROR instead of bare string literals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a named const for EffectiveMealPlanPermission (same as-const pattern as
MealPlanPermission) so "owner" and "readonly" comparisons in MealPlanPage
use EffectiveMealPlanPermission.OWNER / .READONLY instead of string literals.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ -0,0 +165,4 @@
// Compute the ISO bounds for the week window sent to the backend.
const weekDates = getWeekDates(week);
const dateFrom = formatIsoDate(weekDates[0]); // Monday
const dateTo = formatIsoDate(weekDates[6]); // Sunday
Author
Owner

Use days of week constant?

Use days of week constant?
araemer marked this conversation as resolved
dateUtils already uses the value in getWeekDates — it is the natural home
for this constant. MealPlanCalendar now imports it from there.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Date formatting data belongs with the other date utilities, not in the
calendar component. MealPlanCalendar now imports it from dateUtils.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the inline getWeekDates + magic index 6 pattern in MealPlanPage
with a dedicated getWeekIsoRange utility in dateUtils that returns the
Monday and Sunday ISO date strings for a given week.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Also move DAYS_OF_WEEK and SHORT_WEEKDAY_NAMES to the top of the file so
they are declared before any function that references them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract MEAL_PLAN_BASE constant so getMealPlanUrlDefinition and
getMealPlanUrl share a single source of truth for the base path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add onConfirm prop to BasicDialog with a window-level keydown listener
(guarded against textarea/button/select focus). All concrete dialogs
pass their confirm handler there instead of attaching onKeyDown to
individual inputs.

Side-effect: removes the pre-existing bug in ChangePasswordModal where
handleSave fired on every keystroke via PasswordField.onKeyDown.
Also makes PasswordField.onKeyDown optional since callers no longer need
to supply it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Overview: mention meal planning alongside recipes
- §3 repo structure: add meal-plan/ under components
- §4.2 pages: add MealPlanPage
- §4.3 basics: add BasicDialog and CookingSpinner
- §4.3 meal-plan: new subsection with all 10 components
- §4.5 mappers: add all 5 meal plan mappers; clarify sortOrder
  parameter purpose on AbstractModelDtoMapper
- §4.5 legacy note: update to reflect that RecipeImportPage and
  RecipeEditPage are the only remaining exceptions, and explain why
- §4.6 hooks: add useMealPlanDayAutoSave
- §5 design decisions: add BasicDialog pattern, routes.ts convention,
  and the as-const enum pattern

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously only the first 24 results were fetched (page 0), making recipes
beyond the first page unreachable. Now tracks totalCount and currentPage;
a "Weitere laden (N verbleibend)" button inside the list appends the next
page without replacing the current results.

Also migrates the popup to BasicDialog for consistent keyboard handling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ -0,0 +104,4 @@
setIsLoadingUsers(true);
fetchAllUsers()
.then(users => {
setAllUsers(users);
Author
Owner

A user can currently share with himself...

A user can currently share with himself...
araemer marked this conversation as resolved
The button was inside the overflow-y-auto container, so after loading 24+
recipes the user had to scroll to the very bottom of the list to find it.
Moving it below the container makes it always visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
addDish, removeDish, and updateServings called setLocalDay then
immediately called executeSave. Because setLocalDay is asynchronous,
localDayRef.current had not yet been updated by a render when
buildSaveDto read it, so the save was sent with the pre-change state.

Fix: compute the next state from the ref directly, write it back to the
ref before calling executeSave, then call setLocalDay for UI sync.
The debounced updateNote path is unaffected — 800ms is more than enough
time for React to render and sync the ref.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CircularIconButton gains a size prop ("md" default, "sm" for tight layouts).
NumberStepControl gains a compact prop that uses size="sm" circular buttons,
gap-1, and a w-10 input. MealPlanDishCard uses compact, removes the
"Portionen" label, and centres the control horizontally in the card.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add isSaturday / isSunday helpers to dateUtils. MealPlanCalendar uses
them to tint Saturday (bg-accent/10) and Sunday (bg-accent/15). Day type
takes priority over today so the two shades are always distinct; today on
a weekday gets a subtle bg-accent/5 wash. Weekend column headers use
border-accent/30 so the divider line remains visible on the tinted
background. All day columns get shadow-md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DishCard: restore relative/absolute trash button (top-1.5 right-1.5,
  size 14), pr-6 on title, hyphens-auto for German syllable breaks
- index.html: lang="en" → lang="de" (enables browser German hyphenation)
- NumberStepControl: py-0.5 on compact input to reduce height
- Button: add iconSize prop so callers can override the default 20px icon
- MealPlanDayColumn: "Rezept hinzufügen" → "Rezept" with iconSize={14}
  to match the text-xs font size

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves the plan-switcher dropdown title out of MealPlanHeader into a reusable
basics/HeaderSwitchTitle component that accepts any items with id+title. Restores
doc comments on MealPlanHeaderProps fields and MealPlanHeader that were lost in
the previous commit. PageHeader.title now accepts ReactNode in addition to string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ -0,0 +86,4 @@
* specific view via the address bar and the browser's back/forward navigation
* moves between weeks naturally.
*/
export default function MealPlanPage() {
Author
Owner

We need some way to delete a meal plan.

We need some way to delete a meal plan.
araemer marked this conversation as resolved
Adds a two-mode calendar: a compact 7-column overview grid (dish titles
with terracotta left bars, no edit controls) toggled via grid/list icons
in the nav row, and a full-width stacked list view with all edit controls.
The toggle is hidden on mobile where list view is always used. Dish cards
now use a single inline flex row (title + servings + delete) with an
off-white background for contrast against day columns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a Löschen icon button to the meal plan header (owner-only) that
opens a DeleteModal for confirmation before permanently removing the plan
and all its days and dishes. Navigates to the next available plan or the
empty state after deletion. Secondary header actions (Löschen, Teilen,
Umbenennen) are now icon-only to reduce header crowding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Empty day tiles now show a minimum height so a sparse week isn't squashed.
Empty days show a placeholder: read-only users see "Noch keine Rezepte";
editors see "Rezept hinzufügen" which switches to list view and scrolls
directly to that day's section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PageHeader now uses LogoHeading for string titles. HeaderSwitchTitle
gets the bee on both its plain h1 and dropdown button variants, with
items-end alignment on the button so the chevron sits at the text
baseline and clears the bee above it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both action buttons now share a single flex row at the bottom of the
share dialog instead of stacking in separate divs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BasicDialog now owns the cancel button (auto-rendered from onClose as
"Abbrechen") and renders confirm actions from a new actions prop. Each
dialog declares only its confirm action; cancel is never disabled.
DeleteTagModal migrated from raw <button> elements to the shared system.
ChangePasswordModal button order fixed (cancel left, confirm right).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- honour _ prefix convention for intentionally unused args in ESLint config
- remove now-redundant eslint-disable comments from meal plan mappers
- rename `amount` to `_amount` in IngredientListEditor destructuring
- type handleDragEnd with DragEndEvent instead of any
- add currentUser?.id to MealPlanShareDialog mount effect deps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves the save lifecycle enum out of useMealPlanDayAutoSave so it can be
reused by future auto-save hooks without coupling them to the meal plan hook.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Typing handleDragEnd with DragEndEvent (instead of any) revealed that dnd-kit
sets over to null when an item is dropped outside a valid target. Add an early
return for that case to prevent a runtime crash.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
araemer merged commit 9130c75b13 into stage 2026-06-14 06:40:48 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
araemer/recipe-app!10
No description provided.