Repository navigation
fix: name the trip on a shopping row bought only on a later trip - #74
Merged
Merged
Conversation
A product that only a later shopping trip buys showed a plain "Buy 2 pieces", which reads as "buy it now". The user then bought a perishable too early, for example a lettuce that a week 3 meal uses. The per-trip list now keeps a single purchase when it is not the first trip, and the row adds that trip's label (the same shoppingTripLabel that the per-trip lines use). A product that only the first trip buys still shows the plain total. The single line keeps the under-buy count: it shows packsToBuy minus one, not the purchase's full round-up, so the warning chip and the count agree. Closes #73 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fix for #73 adds 4 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
guplem
commented
Oct 2, 2026
guplem
left a comment
Owner
Author
There was a problem hiding this comment.
Good, focused fix. The single later-trip line reuses shoppingTripLabel through the new _tripWhen helper, so the row, the 2+ trip lines, the copy and the PDF name the trip the same way. The under-buy case is handled, and each new test asserts the full text and was proven by a mutation.
Two gaps keep the fix from being complete:
- [Required] Equivalent-product groups still show a plain total (
menu_management/lib/shopping/shopping_ingredient.dart:487,tripPurchases: isCombinedGroup ? const [] : _tripPurchasesForProduct(product),). Two pizza flavors of one size form such a group. When only a later trip buys the ingredient, each member row still reads "Buy 1 pack", which is the #73 symptom. The old reason for the empty list (a 2+ split would show wrong solo counts) does not apply to a label that only names the trip. Fix: when every purchase of the group falls on one later trip, pass a one-entry list to each visible member, and add a widget test. - [Required] Rounding can name a trip that is too late (inline comment on line 292).
Smaller items:
- [Suggestion]
textContaining("week")intest/shopping_ingredient_test.dart:232,:249and:348can never fail, because the labels write "Week" with a capital W. These tests now guard the "first trip alone" rule. Replace them with exactfind.text(...)checks of the wrong outputs. - [Suggestion] Add ingredient-level tests for: a start date (real date label through
_tripPurchasesForProduct), owned stock that lowers the single later-trip count, and parity with the copied list's trip section. - [Suggestion]
adr/0014-multi-trip-shopping-planner.md:77says the on-screen list shows no trip feedback besides the banner. That is out of date since #25 and more so now. - Follow-up candidate: an ingredient with no product shows only "Need: X" and never names a trip.
The largest-remainder split can round a small early need down to 0 and move the whole buy to a later trip. The row then sent the user to that later trip, although a recipe before it still needs the product. A row that one trip buys now names the earliest trip that holds a trip item of the ingredient. When that trip is the first trip of the plan, the row shows the plain total, the same as a one-trip plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rows of an equivalent group (one-of-each cycle) always got an empty trip list, so they showed a plain total even when only a later trip buys the ingredient. The user then read "buy it now" for food that the plan buys on a later trip. When one later trip buys the whole cover of the group, each visible member row now gets one trip entry with its own cycled share, so it prints its count and the trip label. A 2+ trip split still gives the member rows the plain share, because a per-trip split of the solo count would contradict the one-of-each share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The checks for a missing trip label searched for "week", but labels write "Week", so they could never fail. They now name the exact wrong rows, such as "Buy 6 packs now" and "Buy 6 packs Week 2". New tests cover the real-date label of a single later trip, a count that owned stock lowers, and parity with the copied list: the row names the same trip that the copy puts the product under. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ADR 0014 still said that the banner and the copy are the only trip feedback. Product rows now show a per-trip split or the label of a single later trip, so the ADR says that, and why the label names the earliest trip that holds the ingredient. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 tasks
guplem
commented
Oct 2, 2026
guplem
left a comment
Owner
Author
There was a problem hiding this comment.
Round 2. Both Required items from round 1 are fixed: equivalent-product rows name a single later trip, and the single-trip row names the earliest trip that holds the ingredient. The weak textContaining("week") checks are now exact, and ADR 0014 records the per-row trip label.
- [Suggestion] Equivalent group bought only on 2+ later trips still reads "buy now" (
shopping_ingredient.dart,groupTripPurchases.length == 1 ? groupTripPurchases.single : null). Trips week 0 (rice only), week 1 and week 2 (pizza): each member row shows a plain "Buy 1 pack". Fix: name the earliest trip that holds the ingredient whenever it is not the first trip. - [Suggestion] ADR 0014 says the copy always equals the on-screen list (line 75). In the rounding case the row names an earlier trip than the copy section, on purpose. Say so, so nobody "fixes" one side back. The split itself is tracked in #75.
- [Nitpick]
if (purchases.length != 1) return purchases; // The split weighs ...: move the comment above thefirstWherethat it explains. - [Nitpick]
isFirstTrip: groupSingleTrip.isFirstTripcan only be false; writeisFirstTrip: falseas the standalone branch does. - [Nitpick]
groupSingleTrip != null && packsToBuy > 0: the row shows "Covered" for 0 packs before it readstripPurchases, so the check has no effect. - [Nitpick] The owned-stock test uses
remainingGrams: 300and a trip item of 300, so a count from the raw trip amount also passes. Use a different trip amount (for example 900).
The single later-trip label needs the earliest trip that holds the ingredient. Equivalent-group rows need the same lookup next, so it moves into its own helper instead of a second copy. The comment that explained the lookup sat at the end of the early-return line. It now sits on the helper, and the comment above the early return says what 0 and 2+ purchases give. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An equivalent group got a trip label only when exactly one later trip bought its cover. When the first trip holds only other ingredients and 2+ later trips buy this one, each member row showed a plain "Buy 1 pack", which reads as "buy now" although no recipe needs it before the later trip. Each member row now names the earliest trip that holds the ingredient whenever that trip is not the first trip of the plan, with its own cycled share. The isFirstTrip flag is now a plain false, since a later trip is never the first one, and the packsToBuy > 0 guard goes away, because the row shows "Covered" for 0 packs before it reads the trip purchases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The trip item held 300 grams, the same as the remaining after owned stock, so a row that counted packs from the raw trip amount also passed. The trip item now holds 900 grams, so only a count from the 300 grams remaining gives "Buy 3 packs Week 2". The comment now names the grams that the test uses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ADR said the copied per-trip amounts always equal the on-screen list. In the rounding case the row names an earlier trip than the copied section, on purpose, so the ADR now says so and points to issue #75 for the split. This keeps a later change from "fixing" one side back to match the other. It also records that equivalent-group rows use the same earliest-trip label, also when 2+ later trips buy the ingredient. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #73
A product that only later shopping trips buy now names the trip on the shopping page, for example "Buy 2 pieces Saturday 17 Oct" (the shopping day) instead of a plain "Buy 2 pieces". Before, the trip appeared only in the copied list and the PDF, so the user could buy a perishable product too early.
_tripPurchasesForProduct,shopping_ingredient.dart): when one trip buys the product, the row names the earliest trip that holds the ingredient. When that is the first trip of the plan, the row stays a plain "Buy X". The 2+ trip split is unchanged.ShoppingProductRow): the single-total line adds the trip label fromshoppingTripLabel, the same function that the 2+ lines, the copy and the PDF use. The under-buy reduction still applies.AGENTS.mdtest count (1066 tests).Test plan
textContainingchecks replaced by exact checksflutter analyze, full suite (1066 tests) pass locally🤖 Generated with Claude Code