Public Access
docs: Sprint 12 — F8 Spoonacular search across all 6 running docs
Sprint 12 (commit 11b4595) wires the "Search the web" toggle on
/recipes to Spoonacular complexSearch, with a per-result Import
button that pulls the full recipe info (1 point) and writes a
local Recipe row. No pre-existing WIP files touched.
This commit updates the 6 running docs that track the sprint:
- .agent/plan.md — Sprint 12 section (S12.1-S12.5) added.
- .agent/context.md — Sprint 12 (D1-D9, Q1-Q3) added; file:line
references; key takeaways.
- Review/sprint12-verification.md — new file: 4-step browser
smoke + 2 API curls + quota test + a11y check + 5-risk table
+ future work section.
- Review/ui-nielsen-audit.md — Sprint 12 status block (T6.1-T6.5)
at the top, after the Sprint 11 block.
- fix-ui-audit.md — Sprint 12 section (T6.1-T6.6) added after
the Sprint 11 section, including the D-fix for the 5
pre-existing tsc errors.
- Review/handoff-ui-audit.md — Batch H added to the deploy
instructions; Sprint 12 section added after Sprint 11; TL;DR
table row 12 added; Last-updated footer updated.
- docs/HANDOFF.md — Sprint 12 section added after the Sprint 11
section, with a D-fix paragraph and a path-forward paragraph
for F9.
All 6 docs now reflect Sprint 12. §Future backlog remaining: F9
(Ollama local LLM) — a full backend proposal that plugs into the
same handleGenerateFirstPlan (Sprint 11) + recipe_search.import
(Sprint 12) seams.
This commit is contained in:
@@ -646,3 +646,52 @@ User direction 2026-06-05: "Proceed." Selected from the question menu as the sma
|
||||
### T5.5 · `Review/sprint11-verification.md` (NEW)
|
||||
|
||||
- Deploy + 4-step browser smoke + race test + 2 API curls + a11y check + risks. Source of truth for the operator deploy + smoke flow. The CTA is the single seam for future F8 (Spoonacular) + F9 (Ollama) work — they only need to swap the `fillEmptySlots` call in `handleGenerateFirstPlan`.
|
||||
|
||||
---
|
||||
|
||||
## Sprint 12 — F8 Spoonacular search — ✅ COMPLETE, awaiting deploy
|
||||
|
||||
User direction 2026-06-05: "Proceed." Selected from the question menu as the smallest remaining §Future item with a clear UI scope. F1 (Sprint 9) shipped, the dead CTA (Sprint 11) shipped, and F8 (Spoonacular) was the last piece. F9 (Ollama) remains a separate full backend proposal.
|
||||
|
||||
**Status (2026-06-05):** ✅ Code complete. `npm run build` green (tsc 0 errors, vite 0 errors). Bundle: 496.48 → 500.28 kB. Backend AST clean. Backend pytest skipped (venv broken on host; known pre-existing issue). Awaiting user commit + deploy. **No new dependencies, no migration, no pre-existing WIP files touched.**
|
||||
|
||||
### T6.1 · Backend — `backend/app/api/recipe_search.py` (NEW, ~270 lines)
|
||||
|
||||
- 2 endpoints:
|
||||
- `GET /api/recipes/search?q=&limit=` (public, `require_session`) — calls Spoonacular `complexSearch` with `addRecipeInformation=true, fillIngredients=true, instructionsRequired=true`. Returns normalized `RecipeSearchHit[]`. **No info endpoint call** (saves 1 pt per result; the pre-existing `_search_spoonacular` calls the info endpoint for every result, which would burn the whole daily quota on a 10-result search).
|
||||
- `POST /api/recipes/import` (public, `require_session`) — body `{external_id, external_source: "spoonacular"}`. Fetches `/recipes/{id}/information` (1 pt), normalizes, upserts ingredients via the existing idempotent `_upsert_ingredient` helper (mirrors the public `POST /api/ingredients` logic without the HTTP roundtrip), creates a local `Recipe` with `external_source="spoonacular"` + `external_id` + `is_manually_added=True`, returns the new recipe id.
|
||||
- Process-wide `_points_used` counter (module-level singleton + `threading.Lock`). 503 with `detail: "spoonacular daily quota reached; try again tomorrow"` when over 140 (10-pt safety margin under the 150-pt free tier). Resets on process restart.
|
||||
- 503 with `detail: "SPOONACULAR_API_KEY not configured; set it in the backend env"` when env var unset. Logged once.
|
||||
- Idempotent import: 409 with `detail: "recipe already imported: <id>"` if a row with the same `(external_source, external_id)` already exists.
|
||||
|
||||
### T6.2 · Backend — config + schemas + main.py wiring
|
||||
|
||||
- **File:** `backend/app/config.py` — added `SPOONACULAR_API_KEY: Optional[str] = None` to `Settings`. Was previously read via `getattr` because `extra="ignore"` silently accepts unknown env vars. The schema declaration surfaces it in `.env.example` and tools; runtime behavior is unchanged.
|
||||
- **File:** `backend/app/schemas/__init__.py` — added `RecipeSearchHit` (Pydantic mirror of the `ExternalRecipe` dataclass at `recipe_discovery.py:28-44`) and `RecipeImportRequest` (just `external_id` + `external_source`).
|
||||
- **File:** `backend/app/main.py:62-63` — registered `recipe_search_api.router` at the `/api/recipes` prefix. No collision with the pre-existing WIP `recipes.py` (which registers `GET /api/recipes`, `GET /api/recipes/recommended`, `GET /api/recipes/{id}`).
|
||||
|
||||
### T6.3 · Frontend — `Recipes.tsx` toggle + panel + mutation
|
||||
|
||||
- **File:** `frontend/src/pages/Recipes.tsx` — added the "Search the web" toggle button (with `aria-pressed={searchWeb}`) to the header. Toggle defaults to OFF so the existing UX is preserved. When ON, a `<div role="region" aria-label="Web recipe search" aria-busy={webLoading}>` panel renders above the local list. The panel reuses the existing `q` + `handleSearch` (line 77-81, 300ms debounce) so the local search bar drives both. The `useQuery` for the web search is `enabled: searchWeb && debouncedQ.length >= 2` to avoid burning quota on idle toggling.
|
||||
- `importMutation` (useMutation) calls `mealPlannerApi.recipes.importRecipe`; on success, marks the hit as imported (local `Set<string>` of external_ids) + invalidates `['recipes']` + shows a success toast. On error, uses `showApiError` (Sprint 4 F7).
|
||||
- The "Import" button state machine: "Import" (Sparkles icon) → "Importing…" (Loader2 spin) → "Imported" (Check, disabled). Communicates state via label + icon.
|
||||
- **File:** `frontend/src/api/index.ts` — added `recipes.search(q, limit)` + `recipes.importRecipe(data)` + 3 stub methods (`recommended`, `listIngredients`, `createIngredient`) for pre-existing call sites.
|
||||
|
||||
### T6.4 · D-fix — pre-existing tsc errors exposed by the API surface expansion
|
||||
|
||||
- Adding 5 new methods to `mealPlannerApi.recipes` (search, importRecipe, recommended, listIngredients, createIngredient) caused TypeScript to evaluate the recipes object as a closed type, exposing 5 latent errors in Pantry.tsx / MealDetail.tsx / Recommended.tsx (calls to non-existent `listIngredients` / `createIngredient` / `recommended` + 2 missing fields on `RecipeIngredient`).
|
||||
- **User decision:** add stub methods + fix the `RecipeIngredient` type. 7 lines of fixes total; no pre-existing WIP touched.
|
||||
- **File:** `frontend/src/types/index.ts` — added optional `ingredient: { id: string; name: string }` + `is_optional: boolean` to `RecipeIngredient`. The backend JSONB column can carry arbitrary keys; we surface the most common ones as optional.
|
||||
|
||||
### T6.5 · Sprint 12 verification gate
|
||||
|
||||
- [x] `npm run build` green (tsc 0 errors, vite 0 errors). Bundle: 496.48 → 500.28 kB.
|
||||
- [x] Backend AST clean on all 4 changed files (recipe_search.py, config.py, schemas/__init__.py, main.py).
|
||||
- [ ] Backend pytest skipped — venv on docker-willester is broken (pre-existing, not caused by Sprint 12). Pytest is part of the operator's deploy checklist; the 4 tests in `backend/tests/test_recipe_search.py` would cover: search happy path, search empty query (422), import happy path, import duplicate (409). The endpoint code follows the same patterns as the existing `never_suggest.py` and `meals.py` routers.
|
||||
- [ ] Browser smoke (4 steps) on `http://100.108.208.56:8082/recipes` per `Review/sprint12-verification.md`.
|
||||
- [ ] Quota test: 50 searches in a row, 51st returns 503.
|
||||
- [ ] No regression in Sprints 1-11.
|
||||
|
||||
### T6.6 · `Review/sprint12-verification.md` (NEW)
|
||||
|
||||
- Deploy + 4-step browser smoke + 2 API curls + quota test + a11y check + 5-risk table + future work section. Source of truth for the operator deploy + smoke flow.
|
||||
|
||||
Reference in New Issue
Block a user