···11+# Data Object Flow
22+33+This document describes the lifecycle of generation data in `internal/recipes`, from query args to regeneration.
44+55+## 1) `params` is created from query args (generation starts here)
66+77+Entry point:
88+- `GET /recipes?location=...` without `h` query arg
99+- Handler: `internal/recipes/server.go` `handleRecipes`
1010+1111+Flow:
1212+1. `handleRecipes` calls `ParseQueryArgs(...)`.
1313+2. `ParseQueryArgs` (`internal/recipes/params.go`) builds `generatorParams` from URL query args:
1414+ - `location` (required)
1515+ - `date` (optional, defaulted by store timezone/day boundary)
1616+ - `instructions` (optional)
1717+ - `conversation_id` (optional)
1818+3. `handleRecipes` persists that object with `SaveParams(...)` under `params/<params_hash>`.
1919+4. This saved `params` object is the start signal for generation. `kickgeneration(...)` is launched immediately after.
2020+2121+## 2) `shoppingList` + `recipes` are generated from `params`
2222+2323+Async generation path:
2424+1. `kickgeneration(...)` calls `generator.GenerateRecipes(ctx, params)`.
2525+2. The generator returns an `ai.ShoppingList` containing `Recipes` (and `ConversationID`).
2626+3. `SaveShoppingList(...)` persists:
2727+ - `shoppinglist/<params_hash>` -> full `ai.ShoppingList`
2828+ - `recipe/<recipe_hash>` -> each recipe object (with `OriginHash = params_hash`)
2929+3030+At this point, `/recipes?h=<params_hash>` can render the generated list.
3131+3232+## 3) Optional `selection` state is created to hold user choices
3333+3434+After the list exists, a signed-in user can save/dismiss recipes:
3535+- `POST /recipe/{hash}/save`
3636+- `POST /recipe/{hash}/dismiss`
3737+3838+Both handlers (`handleSaveRecipe`, `handleDismissRecipe`) update `recipeSelection` (`internal/recipes/selection.go`):
3939+- `SavedHashes []string`
4040+- `DismissedHashes []string`
4141+- `UpdatedAt time.Time`
4242+4343+Storage key:
4444+- `recipe_selection/<user_id>/<origin_hash>`
4545+4646+This object is optional and exists only when the user starts interacting with save/dismiss actions.
4747+4848+## 4) Regeneration creates a new `params` from old `params` + `selection`
4949+5050+Regeneration entry:
5151+- `POST /recipes/{hash}/regenerate`
5252+- Handler: `handleRegenerate`
5353+5454+`handleRegenerate` calls `paramsForAction(...)`, which:
5555+1. Loads old `params` from `params/<hash>`.
5656+2. Loads current `shoppingList` from `shoppinglist/<hash>`.
5757+3. Loads `recipeSelection` for `(user_id, hash)`.
5858+4. Merges selection state into params (`mergeParamsWithSelection`), applies new instructions, and carries conversation id when needed.
5959+5. Computes a new hash from the updated params.
6060+6161+Then:
6262+1. New params is saved at `params/<new_hash>`.
6363+2. `kickgeneration(...)` runs again with that new params.
6464+6565+Result:
6666+- `selection` holds transient decision state for a given origin hash.
6767+- A new generation cycle begins when a new `params` object is created and saved.