feat(meal-plan) open recipes at the planned servings and return to the plan #41

Merged
araemer merged 2 commits from open-recipe-with-plan-servings into stage 2026-09-26 08:50:01 +00:00
Owner

Closes #31

What

Opening a recipe from the meal plan now:

  • starts at the servings configured for that dish — ingredients are pre-scaled (e.g. a recipe for 4 planned for 8 opens at "Für 8 Personen" with doubled amounts), and the servings stepper keeps working from there;
  • returns to the meal plan via the back button (same plan and week, labelled "Zurück zum Essensplan") instead of the recipe list.

Opening a recipe from the recipe list or via a plain URL behaves exactly as before.

How

  • getRecipeDetailUrl(id, servings?) appends the new RECIPE_DETAIL_SERVINGS_PARAM (?servings=8). A URL param rather than router state, so the servings survive a reload and a copied link.
  • RecipeDetailPage pre-scales the working copy when the param is valid; invalid values (0, 2.5, abc, …) fall back to the recipe's own servings. Back target and label come from the new typed RecipeDetailLocationState in routes.ts.
  • New MealPlanRecipeLink component, shared by the list view (MealPlanDishCard) and the week overview (MealPlanCalendar), so both links behave identically.
  • Ingredient scaling extracted into src/utils/servingsUtils.ts (scaleRecipeToServings, parseServingsParam) with tests. Side effect: a recipe with 0 base servings is no longer scaled to Infinity.
  • docs/architecture.md mentions typed router navigation state in the routes.ts section.

Verification

  • npm run build ✓, npm run lint (0 errors; 2 pre-existing warnings elsewhere), npm run test ✓ 204/204
  • /verify-ui as user on all five viewports: list view and week overview both open the recipe at 8 servings / 1000 g, stepper recalculates (7 → 875 g), back returns to the same plan and week; recipe list path and ?servings=abc unchanged at 4 / 500 g; no console errors.

Notes

  • Touches the same lines of MealPlanDishCard.tsx as the #32 branch (meal-plan-dish-card-wrap) — whichever merges second needs a small conflict resolution.
  • The meal plan's view mode (overview vs. list) isn't in the URL, so on tablet/desktop "back" returns to the overview even when the user came from the list view. Possible follow-up.

🤖 Generated with Claude Code

Closes #31 ## What Opening a recipe from the meal plan now: - **starts at the servings configured for that dish** — ingredients are pre-scaled (e.g. a recipe for 4 planned for 8 opens at "Für 8 Personen" with doubled amounts), and the servings stepper keeps working from there; - **returns to the meal plan** via the back button (same plan and week, labelled "Zurück zum Essensplan") instead of the recipe list. Opening a recipe from the recipe list or via a plain URL behaves exactly as before. ## How - `getRecipeDetailUrl(id, servings?)` appends the new `RECIPE_DETAIL_SERVINGS_PARAM` (`?servings=8`). A URL param rather than router state, so the servings survive a reload and a copied link. - `RecipeDetailPage` pre-scales the working copy when the param is valid; invalid values (`0`, `2.5`, `abc`, …) fall back to the recipe's own servings. Back target and label come from the new typed `RecipeDetailLocationState` in `routes.ts`. - New `MealPlanRecipeLink` component, shared by the list view (`MealPlanDishCard`) and the week overview (`MealPlanCalendar`), so both links behave identically. - Ingredient scaling extracted into `src/utils/servingsUtils.ts` (`scaleRecipeToServings`, `parseServingsParam`) with tests. Side effect: a recipe with 0 base servings is no longer scaled to `Infinity`. - `docs/architecture.md` mentions typed router navigation state in the `routes.ts` section. ## Verification - `npm run build` ✓, `npm run lint` (0 errors; 2 pre-existing warnings elsewhere), `npm run test` ✓ 204/204 - `/verify-ui` as `user` on all five viewports: list view and week overview both open the recipe at 8 servings / 1000 g, stepper recalculates (7 → 875 g), back returns to the same plan and week; recipe list path and `?servings=abc` unchanged at 4 / 500 g; no console errors. ## Notes - Touches the same lines of `MealPlanDishCard.tsx` as the #32 branch (`meal-plan-dish-card-wrap`) — whichever merges second needs a small conflict resolution. - The meal plan's view mode (overview vs. list) isn't in the URL, so on tablet/desktop "back" returns to the overview even when the user came from the list view. Possible follow-up. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(meal-plan) open recipes at the planned servings and return to the plan
All checks were successful
/ test (pull_request) Successful in 59s
bf776c951f
Recipes opened from the meal plan now start at the servings configured for
that dish instead of the recipe's own servings, and their back button leads
back to the meal plan (same plan and week) instead of the recipe list.

- getRecipeDetailUrl takes optional servings, carried as the new
  RECIPE_DETAIL_SERVINGS_PARAM query param
- RecipeDetailPage pre-scales the ingredients to a valid servings param and
  reads the back target and label from the typed RecipeDetailLocationState
- New MealPlanRecipeLink shared by the list view (MealPlanDishCard) and the
  week overview (MealPlanCalendar)
- Ingredient scaling moved into servingsUtils (with parseServingsParam), both
  covered by tests; recipes with no usable base servings are no longer scaled
  to Infinity

Closes #31

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/routes.ts Outdated
@ -125,0 +143,4 @@
* several places (recipe list, meal plan) and the URL alone doesn't say which.
* Absent when the page is opened directly (bookmark, reload of a fresh tab).
*/
export interface RecipeDetailLocationState {
Author
Owner

should this live here or would it be better off in its own file?

should this live here or would it be better off in its own file?
araemer marked this conversation as resolved
refactor(recipes) move RecipeDetailLocationState into its own file
All checks were successful
/ test (pull_request) Successful in 1m0s
87fbe6bd30
Addresses review feedback on #41: routes.ts is the single source of truth
for URL information, and router navigation state is deliberately not part
of the URL. The interface now lives next to RecipeDetailPage, which owns and
reads it.

RecipeListItem, the other producer of this state, now uses the interface
too instead of an untyped { backTo } object, so both callers that set the
recipe detail page's back target are type-checked. architecture.md updated
accordingly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
araemer merged commit 9a5450982a into stage 2026-09-26 08:50:01 +00:00
Author
Owner

Re review comment on src/routes.ts ("should this live here or would it be better off in its own file?"):

Moved in 87fbe6b. RecipeDetailLocationState now lives in its own file, src/components/recipes/RecipeDetailLocationState.ts, next to RecipeDetailPage, which owns and reads it. routes.ts is back to URL information only, since router state is deliberately not part of the URL. RecipeListItem, the other caller that sets this state, now uses the interface instead of an untyped { backTo } object, and docs/architecture.md is updated to match.

Posted by Claude Code.

Re review comment on `src/routes.ts` (*"should this live here or would it be better off in its own file?"*): Moved in 87fbe6b. `RecipeDetailLocationState` now lives in its own file, `src/components/recipes/RecipeDetailLocationState.ts`, next to `RecipeDetailPage`, which owns and reads it. `routes.ts` is back to URL information only, since router state is deliberately not part of the URL. `RecipeListItem`, the other caller that sets this state, now uses the interface instead of an untyped `{ backTo }` object, and `docs/architecture.md` is updated to match. _Posted by Claude Code._
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!41
No description provided.