From 42c4deca75527d718fc81b56f82ea7d922a37cf2 Mon Sep 17 00:00:00 2001 From: Craig Date: Sun, 2 Aug 2026 18:46:23 +0100 Subject: [PATCH] Tested and working, added ability to use vite --- .pi/agents/be-implementer.md | 4 +- .pi/agents/fe-implementer.md | 4 +- HANDOFF.md | 19 +++++-- README.md | 5 +- dev.sh | 39 +++++++++++++ frontend/package-lock.json | 14 +++++ frontend/package.json | 2 + frontend/src/App.svelte | 15 ++++- frontend/src/components/BarcodeScanner.svelte | 3 + frontend/src/components/FoodEditor.svelte | 4 +- frontend/src/lib/scanner.js | 56 +++++++++++++++---- frontend/vite.config.js | 3 +- 12 files changed, 143 insertions(+), 25 deletions(-) create mode 100755 dev.sh diff --git a/.pi/agents/be-implementer.md b/.pi/agents/be-implementer.md index 3c5d3e3..d7b6e59 100644 --- a/.pi/agents/be-implementer.md +++ b/.pi/agents/be-implementer.md @@ -2,8 +2,8 @@ name: be-implementer description: Backend implementer for FastAPI + SQLAlchemy + SQLite tools: read, write, edit, grep, find, ls, bash -model: deepseek/deepseek-v4-pro -thinking: xhigh +model: deepseek/deepseek-v4-flash +thinking: high --- You are a backend implementer. Write code, run tests, iterate until green. diff --git a/.pi/agents/fe-implementer.md b/.pi/agents/fe-implementer.md index a2aae4c..616b589 100644 --- a/.pi/agents/fe-implementer.md +++ b/.pi/agents/fe-implementer.md @@ -2,8 +2,8 @@ name: fe-implementer description: Frontend implementer for Svelte 5 + Vite tools: read, write, edit, grep, find, ls, bash -model: deepseek/deepseek-v4-pro -thinking: xhigh +model: deepseek/deepseek-v4-flash +thinking: high --- You are a frontend implementer. Write code, run tests, iterate until green. diff --git a/HANDOFF.md b/HANDOFF.md index 4c71610..cc2b817 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -155,15 +155,26 @@ detection in `services/nutrition.py`). Read the TICKET-007 section of the camera+decode loop (native BarcodeDetector + lazy zxing-wasm, ~4fps, teardown in `stop()`). `BarcodeScanner.svelte` stops on first detect + on destroy (§8.2 rule 6). -- **§8.4 phone checklist PENDING USER TESTING** (native BarcodeDetector on - Android Chrome, zxing-wasm on iOS Safari, EAN-13/UPC-A decode). Requires - Caddy HTTPS. The manual-barcode fallback + OFF search flows are - playwright-verified; do not regress them. +- **§8.4 phone checklist — tested** (2026-07-26): + - ✅ Desktop webcam (native BarcodeDetector on Chrome): works with good + lighting. Scanner requests 640×480 min resolution. Added `[scanner]`/ + `[BarcodeScanner]`/`[App]` console logging; set + `localStorage.debugScanner = 'true'` for per-frame verbose logs. + - ✅ Phone camera (native BarcodeDetector on Android Chrome): **works**. + Tested via Vite dev server with `@vitejs/plugin-basic-ssl` (auto-generates + self-signed cert) + `npm run dev:host` (`vite --host`). Phone accepts the + browser security warning and camera works. Backend proxy unchanged. + - ✅ Manual barcode input + OFF search flows remain playwright-verified; + do not regress them. - **Dev-only Vite proxy caching** observed by QA: empty OFF search responses were cached within a Vite dev session; a fresh Vite restart cleared it. Not a code defect (production build unaffected). If it recurs, check the vite proxy config (`changeOrigin`, cache headers). +- Fixed macro grid on FoodEditor and target form (App.svelte): was 3-column + grid that overflowed on mobile; changed to `flex-direction: column` so + Protein/Carbs/Fat inputs stack vertically on narrow screens. + ## Loose ends / chores - `.playwright-cli/` artifacts are polluting the repo (some were even tracked diff --git a/README.md b/README.md index a4bbdc9..97229d5 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Two terminals: ```bash # Terminal 1 — backend on http://localhost:8000 -cd backend +cd backend uv run uvicorn main:app --reload # Terminal 2 — frontend on http://localhost:5173 (proxies /api → :8000) @@ -59,3 +59,6 @@ cd frontend && npm test # frontend suite (vitest) ```bash cd frontend && npm run build # production build to frontend/dist/ ``` + +# Agentic dev +Defined subagents, idea is big strong agent tells the little ones what to do. I maybe have overkilled on the "small" agents, v4 pro is till pretty powerful. Play around with it and see what we can get away with. \ No newline at end of file diff --git a/dev.sh b/dev.sh new file mode 100755 index 0000000..8b8c0bf --- /dev/null +++ b/dev.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")" && pwd)" +BACKEND_DIR="$ROOT_DIR/backend" +FRONTEND_DIR="$ROOT_DIR/frontend" + +echo "=== Starting CalCount dev servers ===" + +# Kill background processes on exit +cleanup() { + echo "" + echo "Shutting down..." + kill "$BACKEND_PID" "$FRONTEND_PID" 2>/dev/null || true + wait "$BACKEND_PID" "$FRONTEND_PID" 2>/dev/null || true + echo "Done." +} +trap cleanup EXIT SIGINT SIGTERM + +# Start backend (FastAPI via uvicorn) +echo "[backend] uvicorn main:app --reload --host 0.0.0.0" +cd "$BACKEND_DIR" +uv run uvicorn main:app --reload --host 0.0.0.0 & +BACKEND_PID=$! + +# Start frontend (Vite dev server with --host) +echo "[frontend] vite --host" +cd "$FRONTEND_DIR" +npm run dev:host & +FRONTEND_PID=$! + +echo "" +echo " Backend: http://localhost:8000" +echo " Frontend: http://localhost:5173" +echo " Press Ctrl+C to stop both." +echo "" + +# Wait for either process to exit +wait \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cf43149..e76b606 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,6 +12,7 @@ }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@vitejs/plugin-basic-ssl": "^2.3.0", "svelte": "^5.56.4", "vite": "^7.3.6", "vitest": "^4.1.10" @@ -953,6 +954,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.3.0.tgz", + "integrity": "sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/@vitest/expect": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1397036..cc7f86f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,12 +5,14 @@ "type": "module", "scripts": { "dev": "vite", + "dev:host": "vite --host", "build": "vite build", "preview": "vite preview", "test": "vitest run" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@vitejs/plugin-basic-ssl": "^2.3.0", "svelte": "^5.56.4", "vite": "^7.3.6", "vitest": "^4.1.10" diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 03ece95..39b084e 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -107,37 +107,48 @@ } async function handleBarcode(barcode) { + console.log('[App] handleBarcode:', barcode) resetScanFlow() scanBarcode = barcode scanLoading = true // Step 1: Check local DB by barcode try { + console.log('[App] searching local DB for barcode:', barcode) const results = await api.searchFoodsByBarcode(barcode) + console.log('[App] local search results:', results?.length ?? 0) if (results && results.length > 0) { localFood = results[0] scanLogQuantity = defaultQuantity(localFood) scanLoading = false scanPhase = 'localFound' + console.log('[App] local food found:', localFood.name) return } + console.log('[App] not found locally, trying OpenFoodFacts…') } catch (e) { + console.warn('[App] local search failed:', e) // Local lookup failed — try OFF anyway } // Step 2: Not found locally → try OFF try { + console.log('[App] fetching OFF product:', barcode) offFood = await api.offProduct(barcode) scanLoading = false scanPhase = 'offFound' + console.log('[App] OFF product found:', offFood?.product_name ?? offFood?.name) } catch (e) { + console.warn('[App] OFF lookup failed:', e.message) // OFF miss (404) or network error scanLoading = false if (e.message?.includes('404') || e.message?.includes('not found')) { scanPhase = 'notFound' + console.log('[App] OFF product not found (404)') } else { scanError = e.message scanPhase = 'error' + console.error('[App] OFF lookup error:', e.message) } } } @@ -572,8 +583,8 @@ padding: 0 0.3rem; } .macro-grid { - display: grid; - grid-template-columns: 1fr 1fr 1fr; + display: flex; + flex-direction: column; gap: 0.5rem; } .form-actions { diff --git a/frontend/src/components/BarcodeScanner.svelte b/frontend/src/components/BarcodeScanner.svelte index eaf5d9b..f13b458 100644 --- a/frontend/src/components/BarcodeScanner.svelte +++ b/frontend/src/components/BarcodeScanner.svelte @@ -19,6 +19,7 @@ let stopHandle = null function handleDetected(barcode) { + console.log('[BarcodeScanner] 🎯 barcode detected:', barcode) // Stop scanning once we have a barcode if (stopHandle) { stopHandle.stop() @@ -28,6 +29,7 @@ } function handleError(err) { + console.error('[BarcodeScanner] camera error:', err) loading = false if (isPermissionDeniedError(err)) { cameraUnavailable = true @@ -45,6 +47,7 @@ e.preventDefault() const code = manualBarcode.trim() if (!code) return + console.log('[BarcodeScanner] manual barcode submitted:', code) handleDetected(code) } diff --git a/frontend/src/components/FoodEditor.svelte b/frontend/src/components/FoodEditor.svelte index b89db27..e9d3a95 100644 --- a/frontend/src/components/FoodEditor.svelte +++ b/frontend/src/components/FoodEditor.svelte @@ -538,8 +538,8 @@ } .macro-grid { - display: grid; - grid-template-columns: 1fr 1fr 1fr; + display: flex; + flex-direction: column; gap: 0.5rem; } diff --git a/frontend/src/lib/scanner.js b/frontend/src/lib/scanner.js index 4e446cb..7adaa80 100644 --- a/frontend/src/lib/scanner.js +++ b/frontend/src/lib/scanner.js @@ -8,8 +8,13 @@ * * NOTE: getUserMedia requires a secure context — HTTPS via the Caddy * reverse proxy must be in place before phone testing (spec §5). + * + * DEBUGGING: Open browser DevTools (F12) → Console to see scanner logs. + * Set localStorage.debugScanner = 'true' for verbose per-frame logging. */ +const DEBUG = typeof globalThis !== 'undefined' && globalThis.localStorage?.getItem('debugScanner') === 'true' + // zxing-wasm is the fallback decoder; imported lazily so Chromium users // on the native path never pay the WASM download cost. // The module exposes readBarcodesFromImageData for ImageData input. @@ -82,57 +87,80 @@ export function startScanner(videoEl, { onDetect, onError }) { // Extract the current video frame into our canvas const vw = videoEl.videoWidth const vh = videoEl.videoHeight - if (vw === 0 || vh === 0) return // not playing yet + if (vw === 0 || vh === 0) { + if (DEBUG) console.warn('[scanner] video dimensions are 0 — not playing yet?') + return + } canvas.width = vw canvas.height = vh ctx.drawImage(videoEl, 0, 0, vw, vh) + if (DEBUG) console.log('[scanner] frame extracted', vw, '×', vh) + let barcode = null if (detector) { // Native BarcodeDetector path try { const detections = await detector.detect(canvas) + if (DEBUG) console.log('[scanner] BarcodeDetector returned', detections.length, 'results') if (detections.length > 0 && !stopped) { barcode = detections[0].rawValue + if (DEBUG) console.log('[scanner] 🎯 detected via BarcodeDetector:', barcode) } - } catch { - // Native detector can throw on some frames; ignore and try next + } catch (e) { + console.warn('[scanner] BarcodeDetector error on frame:', e) } } else { // zxing-wasm fallback — load once if (!zxingReader) { - const mod = await import('zxing-wasm') - // zxing-wasm v3 exports readBarcodesFromImageData - zxingReader = mod.readBarcodesFromImageData + try { + if (DEBUG) console.log('[scanner] loading zxing-wasm…') + const mod = await import('zxing-wasm') + // zxing-wasm v3 exports readBarcodesFromImageData + zxingReader = mod.readBarcodesFromImageData + if (DEBUG) console.log('[scanner] zxing-wasm loaded') + } catch (e) { + console.error('[scanner] failed to load zxing-wasm:', e) + return + } } try { const imageData = ctx.getImageData(0, 0, vw, vh) const results = await zxingReader(imageData, { formats: BARCODE_FORMATS, }) + if (DEBUG) console.log('[scanner] zxing returned', results.length, 'results') if (results.length > 0 && !stopped) { barcode = results[0].text + if (DEBUG) console.log('[scanner] 🎯 detected via zxing:', barcode) } - } catch { - // zxing decode errors on non-barcode frames — ignore + } catch (e) { + console.warn('[scanner] zxing decode error on frame:', e) } } if (barcode && !stopped) { onDetect(barcode) } - } catch { - // Frame extraction can fail if video isn't ready yet — ignore + } catch (e) { + console.warn('[scanner] frame extraction error:', e) } } // ── Start camera ────────────────────────────────────────────────────── ;(async () => { try { + // Request a minimum resolution for better barcode detection. + // Desktop webcams default to very low res (e.g. 320×240) which makes + // fine barcode lines hard to read. 640×480 is a good baseline. stream = await navigator.mediaDevices.getUserMedia({ - video: { facingMode: 'environment' }, + video: { + facingMode: 'environment', + width: { min: 640, ideal: 1280 }, + height: { min: 480, ideal: 720 }, + }, audio: false, }) if (stopped) { @@ -143,9 +171,15 @@ export function startScanner(videoEl, { onDetect, onError }) { videoEl.srcObject = stream await videoEl.play() + if (DEBUG) { + console.log('[scanner] camera started, dimensions:', videoEl.videoWidth, '×', videoEl.videoHeight) + console.log('[scanner] decoder:', detector ? 'native BarcodeDetector' : 'zxing-wasm') + } + // Start decode loop at ~4 fps timer = setInterval(decodeFrame, DECODE_INTERVAL_MS) } catch (err) { + console.error('[scanner] getUserMedia failed:', err) if (!stopped) { onError(err instanceof Error ? err : new Error(String(err))) } diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 718d187..6677eda 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,9 +1,10 @@ import { defineConfig } from 'vite' import { svelte } from '@sveltejs/vite-plugin-svelte' +import basicSsl from '@vitejs/plugin-basic-ssl' // https://vite.dev/config/ export default defineConfig({ - plugins: [svelte()], + plugins: [svelte(), basicSsl()], server: { // Dev proxy so the frontend can call /api without CORS in production-style setups proxy: {