Adds a two-level browse tree (domain → category → subcategory) to the
recipe browser, plus an "All" unfiltered option at the top of every
domain.
browser_domains.py:
- Category values now support list[str] (flat) or dict with "keywords"
and "subcategories" keys — backward compatible with all existing flat
categories
- Added subcategories to: Italian (Sicilian, Neapolitan, Tuscan, Roman,
Venetian, Ligurian), Mexican (Oaxacan, Yucatecan, Veracruz, Street
Food, Mole), Asian (Korean, Japanese, Chinese, Thai, Vietnamese,
Filipino, Indonesian), Indian (North, South, Bengali, Gujarati),
Mediterranean (Greek, Turkish, Moroccan, Lebanese, Israeli), American
(Southern, Cajun/Creole, BBQ, Tex-Mex, New England), European
(French, Spanish, German, British/Irish, Scandinavian), Latin American
(Peruvian, Brazilian, Colombian, Cuban, Caribbean), Dinner, Lunch,
Breakfast, Snack, Dessert, Chicken, Beef, Pork, Fish, Vegetables
- New helpers: category_has_subcategories, get_subcategory_names,
get_keywords_for_subcategory
store.py:
- get_browser_categories now accepts has_subcategories_by_category and
includes has_subcategories: bool in each result row
- New get_browser_subcategories method for subcategory count queries
recipes.py endpoints:
- GET /browse/{domain}/{category}/subcategories — returns subcategory
list with recipe counts (registered before /{subcategory} to avoid
path collision)
- GET /browse/{domain}/{category} gains optional ?subcategory=X param
to narrow results within a category
- GET /browse/{domain}/{category}/_all — unfiltered paginated browse
(landed in previous commit)
api.ts: BrowserCategory adds has_subcategories; new BrowserSubcategory
type; listSubcategories() call; browse() gains subcategory param
RecipeBrowserPanel.vue:
- Category pills show a › indicator when subcategories exist
- Selecting such a category fetches subcategories in the background
(non-blocking — recipes load immediately at the category level)
- Subcategory row appears below the category list with an
"All [Category]" pill + one pill per subcategory with count
- Active subcategory highlighted; clicking "All [Category]" resets
to the full category view
- Settings: add unit_system key (metric | imperial, default metric)
- Recipe LLM prompts: inject unit instruction into L3 and L4 prompts
so generated recipes use the user's preferred units throughout
- Frontend: new utils/units.ts converter (mirrors Python units.py)
- Inventory list: display quantities converted to preferred units
- Settings view: metric/imperial toggle with save button
- Settings store: load/save unit_system alongside cooking_equipment
Closes#81
- Persist built recipes to recipes table on /build so they get real DB IDs
and can be bookmarked via saved_recipes (FK was pointing at negative IDs)
- Populate missing_ingredients in build_from_selection() from role_overrides
vs pantry diff -- backend now owns shopping list computation
- Remove client-side cartItems tracking; shopping list derived from
builtRecipe.missing_ingredients instead
- Fix saved_recipes 422: mount saved_recipes router before recipes router in
routes.py so /recipes/saved isn't captured by /recipes/{recipe_id}
- Bump SaveRecipeModal z-index to 500 (above detail-overlay at 400)
- Replace "Add to pantry" primary action with "Grocery list" clipboard copy;
"Add to pantry" demoted to compact secondary button
Wires the three Build Your Own API routes into the recipes router,
registered before the catch-all /{recipe_id} route to avoid shadowing.
Adds 5 endpoint tests covering template list count/shape, candidate
response structure, successful recipe build, and 404 on unknown template.