feat(shopping-list) Fill a shopping list from a meal plan (spec 004) #36

Open
araemer wants to merge 15 commits from 004-fill-list-from-meal-plan into stage
Owner

Implements spec 004 (fill a shopping list from a meal plan) in recipe-backend.

  • Plan: docs/plans/004-fill-list-from-meal-plan.md
  • Spec: ../recipe-platform/docs/features/004-fill-list-from-meal-plan.md
  • Contract pin: contract.ref = cf101fc4b28dbd3665fd296324b1a8be2316e60a (tip of the unmerged platform branch 004-fill-list-from-meal-plan-contract)

What was implemented, by plan step

  • 0 Contract pinned to cf101fc.
  • 1–2 Migration 1780012800014-AddMealPlanReasonsAndItemContentEdit: the meal_plan enum value; the reason's meal plan columns with FKs ON DELETE SET NULL, the check constraint and two indexes; content_edited_at / content_edited_by on items. Entities updated to match.
  • 3 isValidIsoDate, itemUnitsMatch, and the pure src/utils/shoppingListFillUtils.ts: scaling, normalization, rounding, spelling, grouping into connected components plus split, lines, keys, token (fp1. + sha256), and the write plan. Unit-tested without mocks, one test per spec rule and acceptance criterion.
  • 4 SOURCE_MEAL_PLAN_* codes added to the 403/404 code unions.
  • 5 MealPlanPermissionHelper (pure, extracted from MealPlanHandler), MealPlanTransactionHelper, meal plan and recipe repositories in ShoppingListTransactionRepositories, and the new MealPlanRepository locks and reads.
  • 6 MealPlanHandler.moveDish, which locks plan, then days, then the dish. Day create takes the plan lock first; day saves reject dish ids of other days. New MealPlanDishRestResource (POST /meal-plan-dish/:dishId/move).
  • 7 ShoppingListItemRepository: an edit marker set by a CASE … IS DISTINCT FROM in the edit statement, a coverage query using the dish identity rule, the fill's conditional writes, reason inserts and deletes, and deleteMealPlanItemsOnList. Plus RecipeRepository.lockForKeyShare.
  • 8 editItem sets the edit marker; removeTicked now takes the list lock (resolves recipe-backend#27); new removeMealPlanItems.
  • 9 ShoppingListFillHandler: preview takes no lock. fill runs in one transaction in the order list row → KEY SHARE on plan, dishes and recipes → items (ascending id) → reasons → change row last.
  • 10 The mealPlan reason block in ShoppingListItemDtoMapper, and a new ShoppingListFillReviewMapper.
  • 11 Service validators and the routes POST /shopping-list/:id/fill-preview, /fill and /remove-meal-plan-items; factories wired.
  • 12 Bruno: fillPreview, fill, removeMealPlanItems, and the new MealPlanDishRestResource/move. Folder seqs renumbered.
  • 13 Integration tests: new ShoppingListFill.integration.test.ts and shared mealPlanFixtureHelper.ts. The change-time file is extended: remove-ticked moved to the list-locked paths; fill and remove-meal-plan-items were added to change-row-last, recorded/not-recorded and the concurrency matrix.
  • 15 docs/howto-locking.md and docs/architecture.md updated.

Review fixes (review gate)

  1. Explicit type annotations. Added at ShoppingListItemDtoMapper (dish) and MealPlanRepository.insertDay (InsertResult). Also added at three more spots of mine: the line sort entry, now a named FillLineSortEntry, and the reason insert mapping.
  2. AC 20 integration test for remove-meal-plan-items, on one mixed list:
    • filled items that are removed: unedited open, ticked, and edited;
    • items that stay: an open and a ticked hand-added item, and an edited filled item that became hand-added;
    • a second list, with a filled and a hand-added item, that stays untouched;
    • a repeat call is a no-op.
  3. Spellings are stored per reason (decided by the human). New migration 1780012800015-AddReasonNameSpelling adds shopping_list_item_reason.name_spelling. Its check constraint requires the spelling on meal_plan rows and forbids it on manual rows; existing meal plan reasons are backfilled with their item's name. The unit spelling was already stored as share_unit.
    • An unedited filled item's name and unit are now chosen by the spelling rule from all its remaining reasons' spellings plus the new contributions. That applies when a fill adds onto it, and after "no longer planned" removes shares. An edited item keeps the user's spelling.
    • ADR: docs/adr/2026-10-24-meal-plan-reason-name-spelling.md. It partially supersedes docs/adr/2026-10-24-shopping-list-fill-from-meal-plan.md, but only its list of reason columns and the check constraint. It also records two accepted known gaps:
      • (a) only one spelling is stored per reason;
      • (b) case- and space-only ties follow reason order, not plan order. Accepted by the human as cosmetic until the ingredient catalog (spec C of E01); also recorded in the plan's Deviations section.
    • New unit tests: the two-step Zwiebel/Zwiebeln case and the Dose/Dosen unit case, each equal to filling at once; re-spelling after removal; edited items; a reason without a spelling. An integration assertion checks that the spelling is persisted. No contract change.

Deviations (recorded in the plan's Deviations section)

  • Step 6.2: the day save checks dish ids inside the locked update. The check now runs as a callback in MealPlanRepository.updateDay(), after the day row is locked and its version checked. The plan had it in the handler before updateDay(), using a new findDishIdsOfDay, which was not added. In that position, saving the source day with its pre-move version got 400 for the moved dish id instead of the 409 that the contract and the move ADR promise. The plan's own comment there expected 409. Found by integration test 8.

  • Steps 1, 2 and 3.12 d/f: each meal plan reason stores its dish's name spelling (review fix 3). Decided by the human at the review gate. The choice of "required, with a backfill" over "nullable for old rows" is mine: the feature is unreleased, and the constraint stays without a special case.

Non-obvious judgment calls

  1. A dish whose recipe has no usable ingredients never counts as covered, and it does not trigger the ticked-items question either (tickedCoverage). This follows the plan's rule that such a dish "can never be covered".
  2. Half-up rounding gets a 1e-9 nudge (an unexported constant), so 9.95 g rounds to 10 g even though 9.95 is stored in binary as 9.9499…
  3. The write plan recomputes the add-onto result with the same function as the review. With removeNoLongerPlanned it therefore gives exactly the reviewed result; without it, the gone shares stay included.
  4. moveDishToDay and incrementDayVersion are raw SQL. They set update_date explicitly. The dish's own version is not bumped; the ADR allows that either way, since day saves do not check dish versions.
  5. In the coverage and dish-range queries, column names (meal_plan_day_id, recipe_id, meal_plan_id) appear in raw where clauses. This avoids relying on TypeORM's relation-name resolution in string conditions.
  6. The new routes sit after remove-ticked, before reorder, in ShoppingListRestResource.
  7. Unused stubs for the meal plan and recipe repositories were added to the existing ShoppingList handler tests, for the extended transaction repositories.
  8. A docs fix outside the plan: docs/architecture.md §5 said meal_plan_day_dish → recipe does not cascade. The migration has ON DELETE CASCADE, which matters for reasons: deleting a recipe deletes its dishes. I corrected it.
  9. One spelling per reason: the name of the dish's first contribution to the line. If one recipe lists both "Ei" and "Eier" in one line, the second spelling is not stored, so two-step and at-once can still differ in that rare case. The unit uses the existing share_unit, the dish's first unit with an amount. A reason without a spelling stands in with the item's name.
  10. Per the plan (judgment call 8), the reasons of one fill are ordered by reason id, not plan order. The integration test compares them order-independently.

Review fixes (commit 619e941)

  • Inline German texts moved into tested builders in src/utils/fillReviewUtils.ts: line checkbox label, "kommt zu „…“ auf der Liste → …", "Du hast … auf … geändert." (fallback "–"), the edited-item radio legend "Bearbeiteten Eintrag „…“", the "Dazu addieren" hint "→ …", the join toggle label and text, skipped dish "… – auf …", "…: keine Zutaten", gone dish "für … (…)", and the no-longer-planned entry details (quantity, "(von dir geändert)", outcome). The create-list dialog's texts in FillReviewInputs became module constants.
  • Recipe link back state kept and recorded as a plan deviation.
  • Picker blocking during a fill (D3): SelectField gains an optional native disabled, which the list picker now uses (blocked for keyboard too). The date pickers already used native disabled.
  • moveDish: comment stating the accepted residual risk (a note save firing during the move hits the 409 dialog; practically unreachable).
  • Verification after the fixes: build ✅, tests ✅ (43 files, 531 tests), lint ✅ (0 errors, the 2 pre-existing warnings). /verify-ui is arranged by the platform session.

Verification

  • npm run build: passes.

  • npm test: 62 files, 807 tests, all pass.

  • npm run test:integration: 82 tests, all pass in three runs in a row. This includes the deterministic lock tests, change-row-last for fill and remove-meal-plan-items, and 20 rounds of each concurrent pair. CI does not run these tests.

  • Migration 1780012800015 on the local dev DB: up, down and up again all work. The backfill was checked once with a temporary meal plan reason, which got its item's name; the check data was removed.

  • npm run migration on the local dev DB (1780012800014): applied. Schema checked (columns, FKs SET NULL, check constraint, indexes, enum {manual,meal_plan}). Then down(), then up() again: both work.

  • A manual API scenario against the running backend passed in full, with two throwaway users: A owns the plan and the list; B has READONLY on the plan and READ_WRITE on the list. It covered:

    • preview, fill, and reasons with day, servings and share;
    • skipped dishes on a second preview; tickedCoverage; clearTicked;
    • the move keeping the dish id, with the reason showing the new day;
    • an old day version giving 409, and a foreign dish id giving 400;
    • noLongerPlanned and its removal;
    • a malformed token giving 400 and a stale token giving 409;
    • remove-meal-plan-items keeping the hand-added item, and the list coming first in the overview;
    • a READONLY move giving 403.

    The test data was removed afterwards. Bruno itself was not used (its environment credentials are secret); the same requests were sent with curl and locally signed tokens.

  • contract_pin CI check: expected red until the platform contract PR is merged and the check is re-run. Judge this PR by its build and test checks.

🤖 Generated with Claude Code

Implements spec 004 (fill a shopping list from a meal plan) in recipe-backend. - Plan: `docs/plans/004-fill-list-from-meal-plan.md` - Spec: `../recipe-platform/docs/features/004-fill-list-from-meal-plan.md` - Contract pin: `contract.ref` = `cf101fc4b28dbd3665fd296324b1a8be2316e60a` (tip of the unmerged platform branch `004-fill-list-from-meal-plan-contract`) ## What was implemented, by plan step - **0** Contract pinned to cf101fc. - **1–2** Migration `1780012800014-AddMealPlanReasonsAndItemContentEdit`: the `meal_plan` enum value; the reason's meal plan columns with FKs `ON DELETE SET NULL`, the check constraint and two indexes; `content_edited_at` / `content_edited_by` on items. Entities updated to match. - **3** `isValidIsoDate`, `itemUnitsMatch`, and the pure `src/utils/shoppingListFillUtils.ts`: scaling, normalization, rounding, spelling, grouping into connected components plus split, lines, keys, token (`fp1.` + sha256), and the write plan. Unit-tested without mocks, one test per spec rule and acceptance criterion. - **4** `SOURCE_MEAL_PLAN_*` codes added to the 403/404 code unions. - **5** `MealPlanPermissionHelper` (pure, extracted from `MealPlanHandler`), `MealPlanTransactionHelper`, meal plan and recipe repositories in `ShoppingListTransactionRepositories`, and the new `MealPlanRepository` locks and reads. - **6** `MealPlanHandler.moveDish`, which locks plan, then days, then the dish. Day create takes the plan lock first; day saves reject dish ids of other days. New `MealPlanDishRestResource` (`POST /meal-plan-dish/:dishId/move`). - **7** `ShoppingListItemRepository`: an edit marker set by a `CASE … IS DISTINCT FROM` in the edit statement, a coverage query using the dish identity rule, the fill's conditional writes, reason inserts and deletes, and `deleteMealPlanItemsOnList`. Plus `RecipeRepository.lockForKeyShare`. - **8** `editItem` sets the edit marker; `removeTicked` now takes the list lock (resolves recipe-backend#27); new `removeMealPlanItems`. - **9** `ShoppingListFillHandler`: `preview` takes no lock. `fill` runs in one transaction in the order list row → KEY SHARE on plan, dishes and recipes → items (ascending id) → reasons → change row last. - **10** The `mealPlan` reason block in `ShoppingListItemDtoMapper`, and a new `ShoppingListFillReviewMapper`. - **11** Service validators and the routes `POST /shopping-list/:id/fill-preview`, `/fill` and `/remove-meal-plan-items`; factories wired. - **12** Bruno: `fillPreview`, `fill`, `removeMealPlanItems`, and the new `MealPlanDishRestResource/move`. Folder seqs renumbered. - **13** Integration tests: new `ShoppingListFill.integration.test.ts` and shared `mealPlanFixtureHelper.ts`. The change-time file is extended: remove-ticked moved to the list-locked paths; fill and remove-meal-plan-items were added to change-row-last, recorded/not-recorded and the concurrency matrix. - **15** `docs/howto-locking.md` and `docs/architecture.md` updated. ## Review fixes (review gate) 1. **Explicit type annotations.** Added at `ShoppingListItemDtoMapper` (`dish`) and `MealPlanRepository.insertDay` (`InsertResult`). Also added at three more spots of mine: the line sort entry, now a named `FillLineSortEntry`, and the reason insert mapping. 2. **AC 20 integration test** for remove-meal-plan-items, on one mixed list: - filled items that are removed: unedited open, ticked, and edited; - items that stay: an open and a ticked hand-added item, and an edited filled item that became hand-added; - a second list, with a filled and a hand-added item, that stays untouched; - a repeat call is a no-op. 3. **Spellings are stored per reason** (decided by the human). New migration `1780012800015-AddReasonNameSpelling` adds `shopping_list_item_reason.name_spelling`. Its check constraint requires the spelling on `meal_plan` rows and forbids it on `manual` rows; existing meal plan reasons are backfilled with their item's name. The unit spelling was already stored as `share_unit`. - An unedited filled item's name and unit are now chosen by the spelling rule from all its remaining reasons' spellings plus the new contributions. That applies when a fill adds onto it, and after "no longer planned" removes shares. An edited item keeps the user's spelling. - ADR: `docs/adr/2026-10-24-meal-plan-reason-name-spelling.md`. It partially supersedes `docs/adr/2026-10-24-shopping-list-fill-from-meal-plan.md`, but only its list of reason columns and the check constraint. It also records two accepted known gaps: - (a) only one spelling is stored per reason; - (b) case- and space-only ties follow reason order, not plan order. Accepted by the human as cosmetic until the ingredient catalog (spec C of E01); also recorded in the plan's Deviations section. - New unit tests: the two-step Zwiebel/Zwiebeln case and the Dose/Dosen unit case, each equal to filling at once; re-spelling after removal; edited items; a reason without a spelling. An integration assertion checks that the spelling is persisted. No contract change. ## Deviations (recorded in the plan's Deviations section) - **Step 6.2: the day save checks dish ids inside the locked update.** The check now runs as a callback in `MealPlanRepository.updateDay()`, after the day row is locked and its version checked. The plan had it in the handler before `updateDay()`, using a new `findDishIdsOfDay`, which was not added. In that position, saving the source day with its pre-move version got 400 for the moved dish id instead of the 409 that the contract and the move ADR promise. The plan's own comment there expected 409. Found by integration test 8. - **Steps 1, 2 and 3.12 d/f: each meal plan reason stores its dish's name spelling** (review fix 3). Decided by the human at the review gate. The choice of "required, with a backfill" over "nullable for old rows" is mine: the feature is unreleased, and the constraint stays without a special case. ## Non-obvious judgment calls 1. **A dish whose recipe has no usable ingredients never counts as covered**, and it does not trigger the ticked-items question either (`tickedCoverage`). This follows the plan's rule that such a dish "can never be covered". 2. **Half-up rounding gets a 1e-9 nudge** (an unexported constant), so 9.95 g rounds to 10 g even though 9.95 is stored in binary as 9.9499… 3. **The write plan recomputes the add-onto result with the same function as the review.** With `removeNoLongerPlanned` it therefore gives exactly the reviewed result; without it, the gone shares stay included. 4. **`moveDishToDay` and `incrementDayVersion` are raw SQL.** They set `update_date` explicitly. The dish's own `version` is not bumped; the ADR allows that either way, since day saves do not check dish versions. 5. **In the coverage and dish-range queries, column names (`meal_plan_day_id`, `recipe_id`, `meal_plan_id`) appear in raw where clauses.** This avoids relying on TypeORM's relation-name resolution in string conditions. 6. **The new routes sit after `remove-ticked`, before `reorder`**, in `ShoppingListRestResource`. 7. **Unused stubs for the meal plan and recipe repositories** were added to the existing ShoppingList handler tests, for the extended transaction repositories. 8. **A docs fix outside the plan:** `docs/architecture.md` §5 said `meal_plan_day_dish → recipe` does not cascade. The migration has `ON DELETE CASCADE`, which matters for reasons: deleting a recipe deletes its dishes. I corrected it. 10. **One spelling per reason:** the name of the dish's first contribution to the line. If one recipe lists both "Ei" and "Eier" in one line, the second spelling is not stored, so two-step and at-once can still differ in that rare case. The unit uses the existing `share_unit`, the dish's first unit with an amount. A reason without a spelling stands in with the item's name. 9. **Per the plan (judgment call 8), the reasons of one fill are ordered by reason id, not plan order.** The integration test compares them order-independently. ## Review fixes (commit 619e941) - **Inline German texts** moved into tested builders in `src/utils/fillReviewUtils.ts`: line checkbox label, "kommt zu „…“ auf der Liste → …", "Du hast … auf … geändert." (fallback "–"), the edited-item radio legend "Bearbeiteten Eintrag „…“", the "Dazu addieren" hint "→ …", the join toggle label and text, skipped dish "… – auf …", "…: keine Zutaten", gone dish "für … (…)", and the no-longer-planned entry details (quantity, "(von dir geändert)", outcome). The create-list dialog's texts in `FillReviewInputs` became module constants. - **Recipe link back state** kept and recorded as a plan deviation. - **Picker blocking during a fill (D3)**: `SelectField` gains an optional native `disabled`, which the list picker now uses (blocked for keyboard too). The date pickers already used native `disabled`. - **`moveDish`**: comment stating the accepted residual risk (a note save firing during the move hits the 409 dialog; practically unreachable). - Verification after the fixes: build ✅, tests ✅ (43 files, 531 tests), lint ✅ (0 errors, the 2 pre-existing warnings). `/verify-ui` is arranged by the platform session. ## Verification - `npm run build`: passes. - `npm test`: 62 files, 807 tests, all pass. - **`npm run test:integration`: 82 tests, all pass in three runs in a row.** This includes the deterministic lock tests, change-row-last for fill and remove-meal-plan-items, and 20 rounds of each concurrent pair. CI does not run these tests. - Migration `1780012800015` on the local dev DB: up, down and up again all work. The backfill was checked once with a temporary meal plan reason, which got its item's name; the check data was removed. - `npm run migration` on the local dev DB (`1780012800014`): applied. Schema checked (columns, FKs `SET NULL`, check constraint, indexes, enum `{manual,meal_plan}`). Then `down()`, then `up()` again: both work. - **A manual API scenario against the running backend passed in full**, with two throwaway users: A owns the plan and the list; B has READONLY on the plan and READ_WRITE on the list. It covered: - preview, fill, and reasons with day, servings and share; - skipped dishes on a second preview; `tickedCoverage`; `clearTicked`; - the move keeping the dish id, with the reason showing the new day; - an old day version giving 409, and a foreign dish id giving 400; - `noLongerPlanned` and its removal; - a malformed token giving 400 and a stale token giving 409; - remove-meal-plan-items keeping the hand-added item, and the list coming first in the overview; - a READONLY move giving 403. The test data was removed afterwards. Bruno itself was not used (its environment credentials are secret); the same requests were sent with curl and locally signed tokens. - `contract_pin` CI check: **expected red** until the platform contract PR is merged and the check is re-run. Judge this PR by its build and test checks. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
docs(adr) Add the fill-from-meal-plan and dish-move ADRs for spec 004
All checks were successful
/ test (pull_request) Successful in 1m16s
/ contract_pin (pull_request) Successful in 3s
60f52e813f
Approved at GATE B of spec 004.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(plan) Add the implementation plan for spec 004
All checks were successful
/ test (pull_request) Successful in 1m11s
/ contract_pin (pull_request) Successful in 3s
f148440a41
Approved at GATE C of spec 004 (on the user's delegation).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chore(contract) Pin the contract for spec 004
Some checks failed
/ test (pull_request) Successful in 1m10s
/ contract_pin (pull_request) Failing after 3s
5b19c918bf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(shoppingList) Add meal plan reason columns and the item edit marker
Some checks failed
/ test (pull_request) Successful in 1m11s
/ contract_pin (pull_request) Failing after 3s
69487bc1f4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(shoppingList) Compute a fill review from a meal plan
Some checks failed
/ test (pull_request) Successful in 1m11s
/ contract_pin (pull_request) Failing after 2s
98134507d1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(mealPlan) Move a dish keeping its id and reject foreign dish ids on day saves
Some checks failed
/ test (pull_request) Successful in 1m12s
/ contract_pin (pull_request) Failing after 2s
77c2049284
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(shoppingList) Fill a list from a meal plan and remove its items again
Some checks failed
/ contract_pin (pull_request) Has been cancelled
/ test (pull_request) Has been cancelled
42fa281c5c
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(bruno) Add the fill and dish move requests
Some checks failed
/ test (pull_request) Successful in 1m15s
/ contract_pin (pull_request) Failing after 2s
6ef255d5d6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(integration) Cover the fill, the dish move and the new lock order
Some checks failed
/ test (pull_request) Successful in 1m14s
/ contract_pin (pull_request) Failing after 3s
519528c232
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(architecture) Document filling a list from a meal plan
Some checks failed
/ test (pull_request) Successful in 1m13s
/ contract_pin (pull_request) Failing after 2s
1e2ccd2272
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
araemer changed title from WIP: feat(shopping-list) Fill a shopping list from a meal plan (spec 004) to feat(shopping-list) Fill a shopping list from a meal plan (spec 004) 2026-10-24 10:53:14 +00:00
refactor(shoppingList) Add the missing explicit type annotations
Some checks failed
/ test (pull_request) Successful in 1m15s
/ contract_pin (pull_request) Failing after 3s
cd427d08d7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(shoppingList) Store each meal plan reason's name spelling and spell filled items from all of them
Some checks failed
/ test (pull_request) Successful in 1m14s
/ contract_pin (pull_request) Failing after 3s
0d3f357e7a
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(integration) Cover removing the items from meal plan on a mixed list
Some checks failed
/ test (pull_request) Successful in 1m16s
/ contract_pin (pull_request) Failing after 3s
4f47c30e6d
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(adr) Record storing the meal plan reason's name spelling and its known gaps
Some checks failed
/ test (pull_request) Successful in 1m16s
/ contract_pin (pull_request) Failing after 3s
f8301b423e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some checks failed
/ test (pull_request) Successful in 1m16s
/ contract_pin (pull_request) Failing after 3s
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin 004-fill-list-from-meal-plan:004-fill-list-from-meal-plan
git switch 004-fill-list-from-meal-plan

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch stage
git merge --no-ff 004-fill-list-from-meal-plan
git switch 004-fill-list-from-meal-plan
git rebase stage
git switch stage
git merge --ff-only 004-fill-list-from-meal-plan
git switch 004-fill-list-from-meal-plan
git rebase stage
git switch stage
git merge --no-ff 004-fill-list-from-meal-plan
git switch stage
git merge --squash 004-fill-list-from-meal-plan
git switch stage
git merge --ff-only 004-fill-list-from-meal-plan
git switch stage
git merge 004-fill-list-from-meal-plan
git push origin stage
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-backend!36
No description provided.