diff --git a/.claude/settings.json b/.claude/settings.json index ea2f793..c1dd4cb 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -32,7 +32,11 @@ "Bash(curl -s http://127.0.0.1:5187/css/site.css)", "PowerShell(\"done\")", "Bash(grep -n \"^### \\\\`CW-STORY\\\\|^## \\\\`CW-EPIC\" docs/scrum-backlog.md)", - "Bash(cd \"g:/development/C Sharp AI/CartWise\" && rm -f verify3.db* && dotnet ef database update --project src/CartWise.Infrastructure/CartWise.Infrastructure.csproj --startup-project src/CartWise.Web/CartWise.Web.csproj --connection \"Data Source=verify3.db\" 2>&1 | tail -15 && rm -f verify3.db*)" + "Bash(cd \"g:/development/C Sharp AI/CartWise\" && rm -f verify3.db* && dotnet ef database update --project src/CartWise.Infrastructure/CartWise.Infrastructure.csproj --startup-project src/CartWise.Web/CartWise.Web.csproj --connection \"Data Source=verify3.db\" 2>&1 | tail -15 && rm -f verify3.db*)", + "Bash(curl -s -m 8 \"https://world.openfoodfacts.org/api/v2/product/3017620422003.json\")", + "Bash(curl -s -b /tmp/cw5.txt -m 15 http://127.0.0.1:5187/api/products/barcode/3017620422003)", + "Bash(curl -s -b /tmp/cw5.txt http://127.0.0.1:5187/products/0ce5df7a-6c8d-489d-ba9f-8d41699f0705 -o /tmp/product.html -w \"status=%{http_code}\\\\n\")", + "Bash(sed -n '/

/,/Add to my list/p' /tmp/product.html)" ], "additionalDirectories": [ "G:\\development\\C Sharp AI\\CartWise" diff --git a/README.md b/README.md index e20f5ba..48b19cb 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,15 @@ Progress so far (see `docs/scrum-backlog.md` for the authoritative task-level st - [x] `CW-STORY-03.4` Toggle purchased/skip/delete with optimistic concurrency handling and jQuery progressive enhancement - [x] `CW-STORY-03.5` Mobile-first/touch-friendly polish pass (not visually verified in a browser — no browser-automation tool available in this environment; recommend a quick manual check) -Next up: `CW-EPIC-05` Stores, Purchases, and Price Intelligence (`CW-EPIC-04` is post-MVP). +`CW-EPIC-04` Product Catalog and Barcode Resolution is complete — built ahead of the MVP-tagged epics per `DEC-010` (explicit founder direction to work epics in strict numeric order rather than MVP-first): + +- [x] `CW-STORY-04.1` Brand/Product/ProductIdentifier/HouseholdProductPreference entities, EF configuration, migration +- [x] `CW-STORY-04.2` Local product search and details pages +- [x] `CW-STORY-04.3` Local-first barcode lookup flow with provider fallback (`IProductDataProvider`) +- [x] `CW-STORY-04.4` Real `OpenFoodFactsProductDataProvider` (typed `HttpClient`, no API key required) +- [x] `CW-STORY-04.5` Camera-based scan page (native `BarcodeDetector` API, manual-entry fallback) with add-to-list — verified live against the real Open Food Facts API + +Next up: `CW-EPIC-05` Stores, Purchases, and Price Intelligence. Next up: `CW-EPIC-03` Smart Shopping List. diff --git a/docs/scrum-backlog.md b/docs/scrum-backlog.md index 415b192..0d42035 100644 --- a/docs/scrum-backlog.md +++ b/docs/scrum-backlog.md @@ -520,11 +520,19 @@ Two more real bugs found by tests (bringing the running total for `CW-EPIC-03` t - User can add scanned product to the list or record a price **Tasks** -- [ ] Create `ScanController` -- [ ] Build `Views/Scan/Index.cshtml` -- [ ] Add camera permission flow -- [ ] Add barcode JS integration -- [ ] Add add-to-list and record-price actions +- [x] Create `ScanController` +- [x] Build `Views/Scan/Index.cshtml` +- [x] Add camera permission flow +- [x] Add barcode JS integration +- [x] Add add-to-list and record-price actions + +**Status:** Done, with one deliberate partial: "record-price" isn't wired up because `CW-EPIC-05` (Purchases/Price Intelligence) hasn't been built yet — a direct consequence of `DEC-010`'s strict-numeric-order choice reaching `04.5` before `05` exists. "Add to list" is fully wired instead, which satisfies the acceptance criterion's "add ... to the list **or** record a purchase/price." Will revisit once `CW-EPIC-05`'s `PriceController` exists. + +Also added `GET /products/barcode/{barcode}` and `GET /api/products/barcode/{barcode}` to `ProductController` (per AGENTS.md §11 — these were specified but not yet built in `CW-STORY-04.2`/`04.3`, since neither of those stories' task lists mentioned a controller route; the scan page is what actually needed them). The JSON route is the "actual client-side need" AGENTS.md §11 requires before introducing an `/api` route — the scan page's JS calls it via `fetch`/`$.getJSON` to resolve a detected barcode without a full page reload. + +No barcode-scanning JS library is vendored — this project has no bundler/npm step (`wwwroot` is plain static files, matching the existing jQuery/Bootstrap vendoring pattern), and pulling in a third-party scanning library's browser build wasn't practical to do well without one. Instead, `wwwroot/js/pages/scan.js` uses the native browser `BarcodeDetector` API (Chrome/Edge/Android WebView) with graceful degradation: browsers lacking it get a clear "not supported" message and a manual barcode-entry text field instead, which also doubles as the non-JS fallback path per AGENTS.md §14. + +Verified live end-to-end with a **real** barcode against the **real** Open Food Facts API (not mocked): `GET /api/products/barcode/3017620422003` (Nutella) → provider fallback fires → product persists locally with the correct name/brand/identifier → `/products/{id}` renders it → "Add to my list" persists it onto the household's shopping list. Also confirmed a genuinely-unknown barcode correctly 404s (HTML route) / returns `{"found":false}` (JSON route) after the provider fallback misses too. --- diff --git a/src/CartWise.Web/Controllers/ProductController.cs b/src/CartWise.Web/Controllers/ProductController.cs index 6068101..8c4b121 100644 --- a/src/CartWise.Web/Controllers/ProductController.cs +++ b/src/CartWise.Web/Controllers/ProductController.cs @@ -60,4 +60,33 @@ public class ProductController : Controller return View(viewModel); } + + [HttpGet("products/barcode/{barcode}")] + public async Task Barcode(string barcode) + { + var result = await _productService.ResolveBarcodeAsync(barcode); + if (!result.IsSuccess) + { + return NotFound(result.Error); + } + + return RedirectToAction(nameof(Details), new { id = result.Value!.ProductId }); + } + + [HttpGet("api/products/barcode/{barcode}")] + public async Task BarcodeJson(string barcode) + { + var result = await _productService.ResolveBarcodeAsync(barcode); + if (!result.IsSuccess) + { + return Ok(new { found = false, error = result.Error }); + } + + return Ok(new + { + found = true, + productId = result.Value!.ProductId, + redirectUrl = Url.Action(nameof(Details), new { id = result.Value.ProductId }) + }); + } } diff --git a/src/CartWise.Web/Controllers/ScanController.cs b/src/CartWise.Web/Controllers/ScanController.cs new file mode 100644 index 0000000..3b2ad1a --- /dev/null +++ b/src/CartWise.Web/Controllers/ScanController.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace CartWise.Web.Controllers; + +[Authorize] +[Route("scan")] +public class ScanController : Controller +{ + [HttpGet("")] + public IActionResult Index() => View(); +} diff --git a/src/CartWise.Web/Views/Product/Details.cshtml b/src/CartWise.Web/Views/Product/Details.cshtml index 81bc92c..c4eb10e 100644 --- a/src/CartWise.Web/Views/Product/Details.cshtml +++ b/src/CartWise.Web/Views/Product/Details.cshtml @@ -29,4 +29,9 @@ } -

Price history and household insights will appear here once purchase tracking is available.

+
+ + +
+ +

Price history and recording a purchase will appear here once purchase tracking is available.

diff --git a/src/CartWise.Web/Views/Scan/Index.cshtml b/src/CartWise.Web/Views/Scan/Index.cshtml new file mode 100644 index 0000000..356fd94 --- /dev/null +++ b/src/CartWise.Web/Views/Scan/Index.cshtml @@ -0,0 +1,31 @@ +@{ + ViewData["Title"] = "Scan a barcode"; +} + +

Scan a barcode

+ + + +
+ +
+ + + +
+
+ + +
+
+ +
+
+ +
+ +@section Scripts { + +} diff --git a/src/CartWise.Web/Views/Shared/_Layout.cshtml b/src/CartWise.Web/Views/Shared/_Layout.cshtml index 2a726ca..3ef92ce 100644 --- a/src/CartWise.Web/Views/Shared/_Layout.cshtml +++ b/src/CartWise.Web/Views/Shared/_Layout.cshtml @@ -31,6 +31,9 @@ + }