Commit Graph

438 Commits

Author SHA1 Message Date
dadaloop82 8258591e44 feat: migrate existing Bring! items to generic shopping names
- New API action bring_migrate_names: reads current Bring! list, matches
  items against products DB, replaces specific names with shopping_name
  (e.g. 'Mortadella IGP' → 'Affettato' with spec 'Mortadella IGP · Brand')
- New button in Bring! settings: 'Generalizza nomi lista Bring!'
  with live status feedback (migrated / skipped / errors count)
- Auto-refreshes shopping list view after migration
2026-04-27 17:29:55 +00:00
dadaloop82 28a8c938bd fix: prevent scale double-deduction (duplicate inventory_use)
Root cause: after scale auto-confirm fires submitUse(), the old code called
_cancelScaleAutoConfirm(false) which reset _scaleLastConfirmedGrams to null.
This allowed the scale (still showing the same reading) to start a new 10-second
stability+confirm cycle and trigger a second identical deduction.

JS fix:
- submitUse() now calls _cancelScaleTimersOnly() instead of
  _cancelScaleAutoConfirm(false), preserving _scaleLastConfirmedGrams so the
  same weight is rejected until the product is removed from the plate.
- _scaleStabilityVal reset to null so a genuinely new weight starts fresh.
- Duplicate API response (result.duplicate) silently ignored in the UI.

PHP fix (server-side safety net):
- useFromInventory() rejects a second 'out' transaction for the same product
  within 12 seconds with { success: false, duplicate: true }.
  This catches any client-side edge cases regardless of scale timing.
2026-04-27 17:01:11 +00:00
dadaloop82 d269f919b9 ci: trigger kiosk rebuild — include native TTS bridge (95389eb) 2026-04-27 14:49:53 +00:00
dadaloop82 679b3f16a8 ci: force kiosk APK rebuild with TTS bridge fix 2026-04-27 14:49:30 +00:00
dadaloop82 97f6681e24 ci: trigger kiosk APK build on develop branch too 2026-04-27 14:49:19 +00:00
dadaloop82 a5a6e80b31 fix: use product_shopping_name in all Bring! add paths from low-stock flow
- inventory_use API now returns product_shopping_name in response
- showLowStockBringPrompt: uses generic shopping name (e.g. Affettato) as
  Bring! item name, specific product name + brand as specification field
- addLowStockToBring: reads from window._lowStockName instead of arg
- Auto-add on depletion JS fallback: same generic-name pattern
- Deduplication check now tries both shoppingName and raw name
2026-04-27 13:45:10 +00:00
dadaloop82 fd5ff00d82 fix: comprehensive shopping name audit + README update
Shopping name system hardening (api/index.php):
- phraseMap: added sugar subtypes (zucchero a velo), passato di verdure/patate
  → Verdure, aroma phrases → Ingredienti Spezie, farina 00 → Farina,
  explicit brodo subtypes; added ordering comment on 'farina integrale'
- keywordMap: added 'lievito' → Lievito, 'aroma' → Ingredienti Spezie
  single-token fallbacks

DB migration (sqlite3 direct):
- farina integrale / Farina integrale di grano tenero → Farina integrale (3 rows)
- Prodotto sconosciuto (Belbake, it:farina-integrale) → Farina integrale
- Zucchero di canna → Zucchero di canna
- Passato di patate e carote → Verdure (it's a blended veg purée)
- Aroma mandorla per dolci → Ingredienti Spezie (consistent with other aromas)
Total: 11 products re-classified

README.md:
- Shopping List: added generic shopping name feature + auto-add on depletion
- Cooking Mode: updated TTS description (browser / native Android / REST)
- Kiosk: added native TTS bridge bullet
- Roadmap: checked off 3 new completed items
2026-04-27 12:17:11 +00:00
dadaloop82 1a73ed91dd fix: compound shopping names + auto-Bring on depletion + panna da cucina
1. shopping_name compound-phrase map (computeShoppingName)
   Add phraseMap checked against the full product name BEFORE the single-token
   keyword loop.  Prevents 'pane grattugiato' → 'Pane', 'panna da cucina' → 'Panna', etc.
   Key new phrases:
   - pane/pan grattugiato → Pangrattato
   - panna da cucina / panna cucina / panna chef → Panna da cucina
   - fette biscottate → Fette biscottate
   - aceto balsamico / glassa balsamico → Aceto balsamico
   - latte condensato/evaporato/vegetale/di soia/mandorla/avena/riso/cocco → specific
   - prosciutto cotto → Prosciutto cotto
   - farina di riso/mais/integrale → specific
   - pasta fresca, zucchero di canna, acqua minerale/frizzante/gassata, brodo, …
   Also added single-token safety-net entries: 'grattugiato'/'grattato'/'pangrattato'
   → 'Pangrattato', 'biscottate' → 'Fette biscottate'.

2. DB migration (sqlite3 UPDATE)
   Re-classified 10 products that had wrong shopping_name:
   Pane grattugiato → Pangrattato
   Panna da cucina (×4) → Panna da cucina
   Fette biscottate (×2) → Fette biscottate
   Aceto balsamico (×3) → Aceto balsamico
   Cleared 2 stale Gemini cache entries.

3. showLowStockBringPrompt (app.js)
   When totalRemaining <= 0 (product fully depleted), skip the modal entirely.
   The backend already auto-adds to Bring! on depletion; the JS only asks as a
   fallback if that failed (fire-and-forget async, never blocks the UI).
   The afterCallback (e.g. move-remainder modal, navigate to dashboard) is called
   immediately without user interaction.
2026-04-27 12:04:48 +00:00
dadaloop82 95389ebe87 fix: native Android TTS bridge in kiosk — bypass Web Speech API voice issues
On Android WebView, window.speechSynthesis.getVoices() often returns empty
because the Web Speech API cannot enumerate the device's TTS voices.
This caused the kiosk to show 'nessuna voce offline è supportata'.

Changes:
- KioskActivity.kt: initialise Android TextToSpeech engine on startup;
  expose speak(text, rate, pitch), stopSpeech() and isTtsReady() via
  the existing _kioskBridge JavascriptInterface; release TTS in onDestroy.
- app.js (_speakBrowser): when _kioskBridge.speak is available, delegate
  to it instead of using speechSynthesis — works even without offline voice
  packs installed.
- app.js (_initBrowserTtsVoices): show 'Voce nativa Android (kiosk)'
  in the voice dropdown when running inside the kiosk WebView.
- app.js (testTTS): use the bridge path when testing TTS inside the kiosk.
2026-04-27 11:52:30 +00:00
dadaloop82 8b5985dc80 feat: improve computeShoppingName — expanded keyword map + Gemini AI fallback
- Extended keyword map: 100+ entries covering bread (bauletto->Pane),
  cheese (bel->Formaggio, casatella->Formaggio), wine (vesoletto/trebbiano->Vino),
  foreign brand names (kaffee->Caffe, risofrolle->Cracker, zuppalatte->Biscotti,
  inchusa->Birra, apfelsaft->Succo, kartoffelpüree->Purè, ciobar->Cioccolata calda,
  ovomaltine->Bevande), desserts (gelato->Gelato), herbs (camomilla->Camomilla),
  liquors (sambuca->Liquore), sugar variants (zuccheri->Zucchero), foreign words
  (jus/zumo/arome->Italian equivalents)
- Add _geminiClassifyProduct(): calls Gemini 2.0 Flash for ambiguous product names,
  with persistent cache in data/shopping_name_cache.json (never re-queries same product)
- computeShoppingName() now calls Gemini when keyword map and Bring! catalog both fail
  and the product name is multi-token or has a brand/category hint
- saveProduct() auto-computes shopping_name on every create/update (already in prev commit)
- DB migration: all 210 products re-classified with new rules
- shopping list: 38->33 groups (Formaggio +4v, Affettato +5v, Biscotti +1v, Pane +1v...)
- Final SQL fixes for edge cases: Gelato, Camomilla, brand name single tokens
2026-04-27 11:40:14 +00:00
dadaloop82 430f9e7854 feat: generic shopping names — group products by shopping_name
- Add shopping_name column to products table
- Add computeShoppingName() PHP auto-assign function:
  * Curated keyword map: all salumi/cold cuts → 'Affettato'
  * Bring! catalog back-translation: 'Latte di Montagna' → 'Latte'
  * Fallback: first significant token capitalized
- Migrate all 210 existing products with auto-computed shopping_name
- saveProduct() auto-computes shopping_name on every create/update
- smartShopping() groups items by shopping_name: most urgent item is
  representative, others listed as variants (e.g. 'Affettato' shows
  Mortadella, Speck, Nduja, Salame, Prosciutto, Schinkenspeck as one row)
- _productOnBring() also checks shopping_name for Bring! match detection
- addToInventory auto-remove: uses shopping_name-based Bring! key
- useFromInventory auto-add: sends shopping_name to Bring! (not raw name),
  specific product name goes into specification field
- Frontend renderSmartItem: shows shopping_name as title, specific
  product name(s) in italic subtitle line below
- _syncOnBringFlags: matches on both name and shopping_name
2026-04-27 08:16:44 +00:00
dadaloop82 61e94db0d3 style: barcode suffix in banner title smaller + monospace 2026-04-27 05:54:28 +00:00
dadaloop82 76c4344720 feat: show last 3 barcode digits in finished-product banner title 2026-04-27 05:50:22 +00:00
dadaloop82 61e7d7d4bf fix: finished banner only fires when transaction balance is suspicious
getFinishedItems now:
- Computes total_in - total_out for every qty=0 row
- If balance <= unit threshold (e.g. <20g, <0.1 conf): product was
  legitimately used up → silently DELETE, no banner shown
- Only if balance > threshold (unexpected zero): return to frontend
  so banner asks user to verify
Banner detail now shows the expected residual qty so user understands
why the alert fired.
2026-04-27 05:47:11 +00:00
dadaloop82 36f6fcd232 fix: reduce finished-banner to only products with unexpected zero balance
Of the 75 qty=0 rows restored in previous commit, delete the 66 where
total_in - total_out <= unit threshold (legitimately finished by user).
Keep only 9 products where transaction math says there should still be
stock but inventory hit 0 (likely system/scale error):
- Latte di Montagna (0.41 conf)
- Passata di pomodoro (692g)
- Carote (80g), mele (6pz), Uova biologiche (1pz)
- Cipolla dorata (496pz), Panna da cucina (0.6 conf)
- Gran bauletto integrale e noci (448 conf), il tuo muesli mountain (331g)

These 9 will appear in the banner asking user to verify.
2026-04-27 05:43:59 +00:00
dadaloop82 5df0be1661 feat: keep qty=0 instead of auto-delete, ask user to confirm via banner
- useFromInventory: replace DELETE with UPDATE qty=0 when stock hits 0
  (both normal path and use-all-locations path)
- listInventory: add WHERE quantity > 0 so qty=0 rows are invisible in
  the regular inventory list
- New API actions: inventory_finished_items (query) and
  inventory_confirm_finished (delete after user confirms)
- Banner: new 'finished' type (priority 600, above anomalies)
  Shows: '{name} — è finito?' with two buttons
  'Sì, è finito' → permanently deletes the qty=0 row
  'No, ne ho ancora' → navigates to add-inventory form
- i18n: banner_finished_* and toast.product_finished_confirmed (it/en/de)
- DB migration: restored 75 auto-deleted products (last 30 days) as
  qty=0 inventory rows so they appear in the banner queue
2026-04-27 05:41:38 +00:00
dadaloop82 37299e60c9 fix: TTS voices — retry 10s, message on failure, refresh button
- Retry loop extended to 10s (50×200ms) for slow Android WebViews
- Show 'Nessuna voce disponibile' after timeout instead of infinite loader
- Show 'Voce non supportata dal browser' if speechSynthesis missing
- Reset to loading state on each settings open (fixes stale empty select)
- Add refresh button ↺ to force-reload voices manually
2026-04-26 17:28:05 +00:00
dadaloop82 f57ad4b330 fix: TTS voice selector stuck on 'Caricamento voci' in Chrome
Chrome loads voices async — getVoices() returns [] on first call
and onvoiceschanged may fire before the handler is assigned (race).
Add fallback retry loop: poll every 200ms for up to 4s.
2026-04-26 17:23:39 +00:00
dadaloop82 fe0221e6d4 fix: banner buttons — no icons, qty in confirm, full i18n 2026-04-26 17:17:05 +00:00
dadaloop82 4a780f2743 feat: smarter alert banners — expired only, explanatory messages
- Scadenze: rimuove 'in scadenza' dal banner (solo prodotti già scaduti)
- Consumo anomalo: spiega la media giornaliera, giorni dall'ultimo rifornimento
  e direzione della discrepanza (più/meno del previsto) con contesto
- Quantità sospetta: messaggio specifico per caso (bassa/alta/conf insolita)
- Anomalia inventario: linguaggio naturale invece di jargon tecnico
  phantom='hai più scorte del previsto', missing='hai meno scorte del previsto'
- API prediction: aggiunge days_since_restock, direction, tx_count
- confirmBannerPrediction: toast con info su ricalcolo previsioni
2026-04-26 16:57:09 +00:00
dadaloop82 546d4afd59 feat: SSE streaming recipe generation with live agent feedback
- Add generateRecipeStream() endpoint with real-time SSE status events
- Frontend generateRecipe() uses ReadableStream for live step updates
- Fix gemini-2.5-flash thinking model: disable thinkingBudget, raise maxOutputTokens to 4096
- Passo 2 is pure PHP heuristic (zero extra AI calls)
- Retry logic with live countdown on 429, fallback chain: 2.5-flash → 2.0-flash
- Pass all ingredients when meal plan is active (no limits)
- Add recipe-loading-msg element with CSS transition
2026-04-23 15:16:50 +00:00
dadaloop82 db033844d4 Gemini: centralizza chiamate API in callGemini() con backoff intelligente
- Aggiunto helper callGemini($url, $payload, $timeout):
  * Fino a 4 tentativi su 429 / 503
  * Legge Retry-After header dalla risposta HTTP di Google
  * Legge retryDelay dal corpo JSON di errore (es. '10s', '30s')
  * Backoff default: 2s, 4s, 6s (sovrascitto da Google se specificato)
- geminiReadExpiry(), geminiChat(), geminiIdentifyProduct(): rimosso curl
  diretto senza retry, ora usano callGemini()
- generateRecipe(): rimosso vecchio loop manuale (3 tentativi, 2s/4s fissi),
  ora usa callGemini() che rispetta i delay suggeriti da Google
- In caso di 429 finale restituisce il messaggio di errore da Google (non generico)
2026-04-22 11:38:47 +00:00
dadaloop82 f4a62ef496 feat: anomaly detection banner - notifica incongruenze inventario/transazioni
- New API endpoint 'inventory_anomalies': detects items where stored qty
  differs from tx history by >20% AND >50 units (phantom qty or missing qty)
- New API endpoint 'dismiss_anomaly': persists dismissal in anomaly_dismissed.json
- Banner system: new 'anomaly' type shown in dashboard alert banner
  with 'Correggi' (opens edit) and 'Ok, ignora' (dismisses) buttons
- CSS: banner-anomaly style (orange gradient)
- Fix: lo zucchero azzerato (175g fantasma rimossi), aggiunto a Bring!
2026-04-21 12:34:54 +00:00
dadaloop82 234cae14bc perf: remove Gemini from bringSuggest and product selection - pure offline logic
- bringSuggestItems: now uses smart_shopping cache (already computed offline)
  instead of calling Gemini with full inventory prompt
- aiSelectBestProduct: replaced Gemini call with token-scoring algorithm;
  scores by token overlap, first-anchor bonus, spec-variant matching,
  category mismatch penalty — 0ms vs ~1s per product search
- Only truly necessary AI calls remain: photo expiry, photo identify,
  chat assistant, recipe generation
2026-04-21 12:12:04 +00:00
dadaloop82 03142e2f7f fix: retry Gemini 429 with backoff, add recipe rate limit bucket (5/min) 2026-04-21 12:03:16 +00:00
dadaloop82 56c269d616 feat: tutte le operazioni Bring! ora completamente autonome in background
_backgroundBringSync() riscritto completamente:
- Aggiorna SEMPRE smartShoppingItems + shoppingItems con dati freschi
- Aggiunge item high/critical mancanti su Bring
- Aggiorna spec urgenza su item già presenti (anche downgrade)
- Rimuove automaticamente item auto-aggiunti quando non più urgenti
- Se la pagina shopping è aperta, re-renderizza i dati freschi
- Gira ogni 5 min via setInterval, non dipende da nessuna navigazione
- Aggiorna il badge urgenza dashboard in background
2026-04-21 05:33:52 +00:00
dadaloop82 5bbedc8a3b fix: aggiorna urgenza lista spesa live e più frequentemente
- Cache smart_shopping: 10min → 3min (urgenza fresca)
- backgroundBringSync: ogni 10min → ogni 5min + setInterval continuo
- backgroundBringSync: aggiunge anche 'high' (non solo 'critical') a Bring
- autoSyncUrgencySpecs: aggiorna spec anche se il livello di urgenza sale/scende
- Risultato: Latte/prodotti urgenti compaiono su Bring in max ~5min
2026-04-21 05:32:06 +00:00
dadaloop82 cd4fd55006 fix: sposta bottone settings kiosk in basso-sinistra (non copre più camera/Gemini) 2026-04-20 17:40:19 +00:00
dadaloop82 4e4c1867bf fix: abilita mod_headers nel Dockerfile (richiesto da .htaccess no-cache) 2026-04-20 17:37:03 +00:00
dadaloop82 517a615d11 fix: forza no-cache per JS/CSS + WebView LOAD_NO_CACHE
- .htaccess: aggiunge Cache-Control no-cache/no-store per .js/.css
- Kiosk Kotlin: aggiunge cacheMode=LOAD_NO_CACHE al WebView
- Il pulsante refresh del kiosk ora carica sempre l'ultima versione
2026-04-20 17:34:23 +00:00
dadaloop82 5e01c0656c fix: bump cache-buster JS/CSS (v=20260420a) 2026-04-20 17:29:56 +00:00
dadaloop82 abd8ab1829 fix: sposta pulsanti kiosk dentro l'header, prima del titolo
I pulsanti ora sono inseriti nel flusso del header-content come primo figlio, non più come overlay fixed che copriva il titolo.
2026-04-20 17:27:54 +00:00
dadaloop82 076593c564 fix: sposta pulsanti kiosk (X/refresh) a sinistra, prima del titolo 2026-04-20 17:18:32 +00:00
dadaloop82 63b721cf09 fix: ripristina pulsanti kiosk (X/refresh) e riduce attesa stabilità bilancia a 5s
- Aggiunge iniezione overlay kiosk lato web (X chiudi + ↻ refresh) quando _kioskBridge è disponibile, più affidabile dell'iniezione Android-side
- Riduce timer stabilità bilancia da 10s a 5s
2026-04-20 14:48:57 +00:00
dadaloop82 4db8882dbd feat: ottimizza prompt Gemini ricette (riduzione ~60% token) e migliora stabilità bilancia
Prompt ricette:
- Lista ingredienti compatta: skip staples, no brand, flag brevi (🔴3gg)
- Cap gruppo 4 a 40 items, gruppo 6 a 20
- Regole condensate da 10 verbose a 6 concise
- Testi condizionali (varietà, regen, opzioni) abbreviati
- Aggiunto detail errore API Gemini nel toast

Bilancia:
- Ignora oscillazioni sub-grammo (jitter 0.5g)
- Confronto integer-gram prima di dichiarare instabile
2026-04-20 14:43:05 +00:00
dadaloop82 cedd97fd73 fix(bring): keep weekly depletion items auto-synced despite local purchased blocklist
- autoAddCriticalItems now bypasses _isBringPurchased for imminent items
  (days_left <= 7 and uses_per_month >= 3)
- prevents missing Bring additions for items predicted to finish within a week
  after temporary local blocklist entries
2026-04-19 09:10:39 +00:00
dadaloop82 c115f83879 fix(bring): avoid ambiguous fallback match on generic words like 'dolce'
- italianToBring(): pass-2 whole-word fallback now ignores generic qualifiers
  (dolce, light, classico, originale, etc.)
- when multiple single-word matches exist, choose the longest/specific token
  instead of first catalog iteration hit
- prevents wrong mappings like 'Pancetta Dolce' being interpreted via generic
  adjective rather than the core product token
2026-04-19 09:07:44 +00:00
dadaloop82 d778817fd8 fix: auto-add to Bring! items running out within 7 days
- autoAddCriticalItems now adds:
  - critical: always (unchanged)
  - high: always (removed sub-conditions — PHP already gates high urgency strictly)
  - medium + days_left <= 7 + uses_per_month >= 3 (NEW: weekly depletion rule)
- Reduce auto-add guard from 10 min to 5 min for faster detection
- Covers: Latte di Montagna (high, 3gg), items running out mid-week (medium, 4-7gg)
2026-04-19 06:52:16 +00:00
dadaloop82 1021f04735 fix: smarter proactive shopping list urgency
- PHP: predictive urgency block now scales by imminence:
  round(days_left) <= 3 → high, <= 7 → medium, <= 14 → low
  (was flat 'low' for any days_left <= 14)
- PHP: also upgrades existing 'low' urgency to 'high' when
  imminent depletion detected (round(days_left) <= 3, isFrequent)
- JS: autoAddCriticalItems now also adds:
  - high urgency items with pct_left < 20% (nearly empty)
  - high urgency items with days_left <= 3 (imminent)
  - any item with days_left <= 2 and uses_per_month >= 5

Result: Latte di Montagna (27.8x/mo, 3 days left) now appears
on shopping list before running out, as do Lenticchie/Riso
Basmati at 1% stock and Sandwich at 1 day left.
2026-04-19 06:06:18 +00:00
dadaloop82 3901113e76 fix: Storico nav icon, recipe title in transaction notes and log display
- index.html: replace broken byte with 📋 emoji in Storico nav button
- submitRecipeUse: pass 'Ricetta: <title>' as notes to inventory_use API
  so every ingredient use is linked to the recipe in the DB
- loadLog: render recipe note as small italic line 🍳 below log-detail row
- style.css: add .log-recipe-note style (0.75rem, muted, italic)
2026-04-18 19:12:07 +00:00
dadaloop82 2edd5a6ebd fix(kiosk): skip debug keystore config when file does not exist (CI fix) 2026-04-18 18:56:27 +00:00
dadaloop82 07bdfe6b87 fix: kiosk overlay, preferred use-location, scale reconnect, Bring! translation, smart cache invalidation
- Kiosk: replace header-inject overlay with position:fixed div appended to <html>
  so buttons appear regardless of SPA init timing
- Kiosk: bump versionCode 3→4, versionName 1.2.0→1.3.0
- Kiosk: add explicit signingConfigs block (debug keystore) to avoid signature
  mismatch on updates; update banner now shows uninstall instruction + 12s timeout
- Web: v1.4.0 → v1.5.0
- Preferred use-location: remember last N location choices per product; after 3+
  consistent picks auto-select and collapse location picker (with 'cambia' link)
- Scale: call updateScaleReadButtons() on every status change so live-box and
  read button appear instantly on reconnect without manual refresh
- Smart shopping cache: invalidate JSON cache file on every inventory_add and
  inventory_use so next shopping-page load always sees current stock
- isLowStock: conf threshold changed <= 1 → < 1 (1 full pack is not low stock)
- italianToBring: replace substring matching with whole-word matching (min 4 chars)
  to prevent 'gin' matching 'original', 'rum' matching 'crumble', etc.
  Philadelphia original was silently mapped to Gin and skipped as duplicate
- Storico: add undo support (transaction_undo endpoint, undone column, JS undo btn)
- LOG → Storico rename in UI, nav, translations
- Bring! sync: urgency-aware purchased blocklist TTL (critical 30m, high 90m, others 4h)
- forceSyncBring() button to clear all guards and re-sync from scratch
- Scale live-box: position:fixed CSS class, 1.6rem/800 value, direct ml display
- Recipe use modal: scale live-box with 10s stability + 5s auto-confirm countdown
- Recipe use modal: show recipe quantity as highlighted row in Usa popup
2026-04-18 18:50:15 +00:00
dadaloop82 9e2a24def4 feat: v1.3.0 — banner notifications, quick-access, swipe navigation, bug fixes
Added:
- Expired/expiring product banner alerts with use, throw, edit, dismiss actions
- Priority-sorted notifications (expired > expiring > suspicious qty > predictions)
- Touch swipe navigation for banner with dot indicators and arrow buttons
- Quick-access buttons on inventory (4 recent + 8 popular products)
- Auto-refresh banner every 5 min on dashboard
- Edit expiry dates directly from expired/expiring notifications

Fixed:
- Ignore negative BLE scale readings
- Banner re-appearing after edit (confirmation now persisted)
- False consumption predictions when inventory was manually edited
- Kiosk overlay no longer blocks web app header
2026-04-18 05:37:03 +00:00
dadaloop82 52cfbba663 docs: update all READMEs with new features
- README.md: add anomaly banner, AI local matching, scale 10g threshold,
  ml conversion hint, auto-fill improvements, Android kiosk section,
  updated roadmap with completed items, architecture tree with kiosk
- evershelf-kiosk/README.md: complete rewrite — now reflects pure WebView
  wrapper architecture (no BLE), setup wizard, kiosk lock, exit/refresh
  buttons, permissions, gateway auto-launch, update notifications
- evershelf-scale-gateway/README.md: update architecture diagram to show
  SSE relay path, add kiosk integration note
2026-04-17 06:14:33 +00:00
dadaloop82 32e2833b27 feat: AI scan shows existing products, scale ml hint, 10g threshold
1. AI photo scan: searches local DB for matching products and shows
   'Già in dispensa' section before OFF matches. User can tap an
   existing product directly. 'Non è nessuno di questi' button for
   new products.

2. Scale live box: when product unit is ml, shows hint
   'Peso in grammi → verrà convertito in ml' so user knows the
   gram reading will be converted.

3. Scale auto-fill: ignores stable weight if it differs less than
   10g from the last confirmed reading. Prevents re-triggering the
   same weight when switching between products on the scale.
   _scaleLastConfirmedGrams tracks the last auto-confirmed weight
   and resets on page navigation.
2026-04-17 05:42:48 +00:00
dadaloop82 ccd59269d4 feat(kiosk): move exit button left of title, add hard-refresh button
- Exit ✕ and Refresh ↻ buttons now appear left of the title
- Refresh clears WebView cache and reloads (picks up web app updates)
- Uses native bridge hardReload() for true cache-busting reload
- Banner alerts reload automatically when dashboard is shown
2026-04-17 05:24:22 +00:00
dadaloop82 d37b43003c feat: show brand in anomaly banner title
Example: 'Anomalia: Latte (Granarolo)'
2026-04-16 19:22:27 +00:00
dadaloop82 9083e25f37 feat(kiosk): add camera, microphone, storage permissions
- Manifest: CAMERA, RECORD_AUDIO, READ_EXTERNAL_STORAGE, READ_MEDIA_IMAGES
- Runtime: requests all permissions on startup (requestAllPermissions)
- WebView: onPermissionRequest checks runtime grants, requests if needed
- onRequestPermissionsResult grants pending WebView permission after user allows
- Camera and mic now work inside the kiosk WebView
2026-04-16 19:07:30 +00:00
dadaloop82 bc70f330f8 fix(kiosk): replace header overlay with exit button next to Gemini
- Removed invisible overlay that was blocking camera/Gemini buttons
- Added a visible ✕ button in the header-actions bar
- Tap shows confirm dialog 'Uscire dalla modalità kiosk?'
- All header buttons (camera, Gemini, scale) work normally again
2026-04-16 18:59:53 +00:00
dadaloop82 4250a37f0d fix: dismiss banner item after editing anomaly from banner
- Added _bannerEditPending flag set when edit/weigh triggered from banner
- submitEditInventory now calls dismissBannerItem() after save
- Next banner item shows automatically after correction
- Flag reset on modal close (cancel) to prevent stale state
2026-04-16 18:58:06 +00:00