Maestro task f098e8f5-238f-4334-b86e-76fac149f777 — "Shopping list generation from the plan" (Feature 17, task 1 of 7).
Done-condition
internal/shopping Generate(weekStart) builds a shopping_lists row with one group per plan day (dinner_id, day, position by date) and one item per normalized recipe ingredient (name, qty+unit display, needed=true, position), skipping days whose dinner has no ingredients and reporting them. Regenerating after a plan change keeps the needed flag of items whose (dinner_id, name) is unchanged, adds new ones as needed and removes ones no longer present. Generation is deterministic and makes no LLM call. Unit tests cover a 7-day plan, a manual-ingredient dinner, a dinner with no ingredients, and regeneration after a swap preserving a "have it" flag.
Scope
The generator and its unit tests. No HTTP endpoints or item toggles (task d076480c), no archive_title skill (fa4b94c9), no freeze/archive API (7ac36526), no UI, and no push-hook wiring (task d076480c's line).
Points for review
Migration 0016_shopping_list_untitled.sql rebuilds all three shopping tables (SQLite cannot drop a NOT NULL): title becomes nullable and a group keeps its plan day. It is modelled on the existing 0012 rebuild and documents that a group whose dinner is not on its list's plan has no date and fails the migration. Since no code wrote to the shopping tables before this feature, they should be empty in production, which would make this trivially safe — please verify that claim (that nothing earlier ever inserted a shopping row) rather than taking it on trust, and say plainly if the migration is not safe for the live database.
Generation correctness: one group per plan day ordered by date, one item per ingredient ordered by the ingredient's position, the needed flag defaulting true, and qty as a display string that is null (not empty) when unknown.
The regeneration semantics: how a "have it" item is matched across a regeneration; that a changed order does not lose the flag; that stale items are removed and genuinely new ones inserted as needed; and that there is exactly one generator entry point (no separate first-generation path).
That days without ingredients are skipped and reported rather than producing an empty group silently, and that a frozen/archived list is refused.
internal/dinners/grounding.go gains an exported IsManualRecipe(sourceURL): confirm it wraps the existing mealquest:manual: detection rather than duplicating it, and that exporting it is justified (no second way to detect a manual recipe).
The test rows in internal/store/*_test.go were updated (groups now carry a date): confirm that did not weaken any existing assertion.
No LLM call anywhere in this task; no new dependency; generated sqlc code committed and not hand-edited.
go test -race -count=1 ./... — 27 packages ok, 0 failures (server 182s)
CGO_ENABLED=0 go build ./... — ok
make sqlc-check SQLC=$(go env GOPATH)/bin/sqlc — no drift
cd web && npm run typecheck && npm test && npm run build — PASS, 46 files / 804 tests (no web change)
migrations/0016_shopping_list_untitled.sql present and in order after 0015
The developer ran 10 deliberate mutations one at a time (needed not kept; no matching of old items; empty recipe not skipped; skip not reported; manual recipe not detected; stale items not removed; items matched by group instead of dinner; quantity joined without a space; frozen list not refused) and reports each made at least one test fail.
## Task
Maestro task `f098e8f5-238f-4334-b86e-76fac149f777` — "Shopping list generation from the plan" (Feature 17, task 1 of 7).
## Done-condition
`internal/shopping Generate(weekStart)` builds a `shopping_lists` row with one group per plan day (dinner_id, day, position by date) and one item per normalized recipe ingredient (name, qty+unit display, needed=true, position), skipping days whose dinner has no ingredients and reporting them. Regenerating after a plan change keeps the `needed` flag of items whose `(dinner_id, name)` is unchanged, adds new ones as needed and removes ones no longer present. Generation is deterministic and makes no LLM call. Unit tests cover a 7-day plan, a manual-ingredient dinner, a dinner with no ingredients, and regeneration after a swap preserving a "have it" flag.
## Scope
The generator and its unit tests. No HTTP endpoints or item toggles (task `d076480c`), no `archive_title` skill (`fa4b94c9`), no freeze/archive API (`7ac36526`), no UI, and no push-hook wiring (task `d076480c`'s line).
## Points for review
- **Migration `0016_shopping_list_untitled.sql`** rebuilds all three shopping tables (SQLite cannot drop a NOT NULL): `title` becomes nullable and a group keeps its plan day. It is modelled on the existing 0012 rebuild and documents that a group whose dinner is not on its list's plan has no date and fails the migration. Since **no code wrote to the shopping tables before this feature**, they should be empty in production, which would make this trivially safe — please verify that claim (that nothing earlier ever inserted a shopping row) rather than taking it on trust, and say plainly if the migration is not safe for the live database.
- Generation correctness: one group per plan day ordered by date, one item per ingredient ordered by the ingredient's position, the `needed` flag defaulting true, and `qty` as a display string that is null (not empty) when unknown.
- The regeneration semantics: how a "have it" item is matched across a regeneration; that a changed order does not lose the flag; that stale items are removed and genuinely new ones inserted as needed; and that there is exactly **one** generator entry point (no separate first-generation path).
- That days without ingredients are skipped **and reported** rather than producing an empty group silently, and that a frozen/archived list is refused.
- `internal/dinners/grounding.go` gains an exported `IsManualRecipe(sourceURL)`: confirm it wraps the existing `mealquest:manual:` detection rather than duplicating it, and that exporting it is justified (no second way to detect a manual recipe).
- The test rows in `internal/store/*_test.go` were updated (groups now carry a date): confirm that did not weaken any existing assertion.
- No LLM call anywhere in this task; no new dependency; generated sqlc code committed and not hand-edited.
## Evidence (orchestrator-run gate at 1ca66931bb1abb020bf71f9685fae554ab1e05c1)
- `gofmt -l .` — empty; `go vet ./...` — clean
- `go test -race -count=1 ./...` — **27 packages ok, 0 failures** (server 182s)
- `CGO_ENABLED=0 go build ./...` — ok
- `make sqlc-check SQLC=$(go env GOPATH)/bin/sqlc` — no drift
- `cd web && npm run typecheck && npm test && npm run build` — PASS, 46 files / 804 tests (no web change)
- `migrations/0016_shopping_list_untitled.sql` present and in order after 0015
The developer ran 10 deliberate mutations one at a time (needed not kept; no matching of old items; empty recipe not skipped; skip not reported; manual recipe not detected; stale items not removed; items matched by group instead of dinner; quantity joined without a space; frozen list not refused) and reports each made at least one test fail.
Implementation commit: `1ca66931bb1abb020bf71f9685fae554ab1e05c1`
Add internal/shopping with Generate(weekStart), one entry point for the
first generation and every regeneration. It builds one group per plan
day (dinner_id, date, position by date) and one item per ingredient of
the dinner's recipe, in ingredient order, reusing the values recipe
grounding stored: a fetched recipe's normalized name with qty and unit
joined for display, or a manual recipe's line as typed with no qty. A
day whose dinner has no recipe or no ingredients gets no group and is
reported in Skipped. Regenerating keeps the id and needed flag of each
item whose (dinner_id, name) is unchanged, matching repeated names in
order; new items are needed and items no longer planned are removed. It
is deterministic and makes no model call. A frozen list is refused.
Migration 0016 makes shopping_lists.title nullable (never empty) so a
list exists untitled until the AI titles it, and adds shopping_groups.date.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Task
Maestro task
f098e8f5-238f-4334-b86e-76fac149f777— "Shopping list generation from the plan" (Feature 17, task 1 of 7).Done-condition
internal/shopping Generate(weekStart)builds ashopping_listsrow with one group per plan day (dinner_id, day, position by date) and one item per normalized recipe ingredient (name, qty+unit display, needed=true, position), skipping days whose dinner has no ingredients and reporting them. Regenerating after a plan change keeps theneededflag of items whose(dinner_id, name)is unchanged, adds new ones as needed and removes ones no longer present. Generation is deterministic and makes no LLM call. Unit tests cover a 7-day plan, a manual-ingredient dinner, a dinner with no ingredients, and regeneration after a swap preserving a "have it" flag.Scope
The generator and its unit tests. No HTTP endpoints or item toggles (task
d076480c), noarchive_titleskill (fa4b94c9), no freeze/archive API (7ac36526), no UI, and no push-hook wiring (taskd076480c's line).Points for review
0016_shopping_list_untitled.sqlrebuilds all three shopping tables (SQLite cannot drop a NOT NULL):titlebecomes nullable and a group keeps its plan day. It is modelled on the existing 0012 rebuild and documents that a group whose dinner is not on its list's plan has no date and fails the migration. Since no code wrote to the shopping tables before this feature, they should be empty in production, which would make this trivially safe — please verify that claim (that nothing earlier ever inserted a shopping row) rather than taking it on trust, and say plainly if the migration is not safe for the live database.neededflag defaulting true, andqtyas a display string that is null (not empty) when unknown.internal/dinners/grounding.gogains an exportedIsManualRecipe(sourceURL): confirm it wraps the existingmealquest:manual:detection rather than duplicating it, and that exporting it is justified (no second way to detect a manual recipe).internal/store/*_test.gowere updated (groups now carry a date): confirm that did not weaken any existing assertion.Evidence (orchestrator-run gate at
1ca66931bb)gofmt -l .— empty;go vet ./...— cleango test -race -count=1 ./...— 27 packages ok, 0 failures (server 182s)CGO_ENABLED=0 go build ./...— okmake sqlc-check SQLC=$(go env GOPATH)/bin/sqlc— no driftcd web && npm run typecheck && npm test && npm run build— PASS, 46 files / 804 tests (no web change)migrations/0016_shopping_list_untitled.sqlpresent and in order after 0015The developer ran 10 deliberate mutations one at a time (needed not kept; no matching of old items; empty recipe not skipped; skip not reported; manual recipe not detected; stale items not removed; items matched by group instead of dinner; quantity joined without a space; frozen list not refused) and reports each made at least one test fail.
Implementation commit:
1ca66931bb1abb020bf71f9685fae554ab1e05c1Independent Codex gpt-6-sol review: APPROVED for this exact commit.