- 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
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.
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.
- 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
- 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
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.
- 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
- 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
- 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)
- 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!
_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
- 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
- .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
- 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
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
- 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
- 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
- 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.
- 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)
- 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
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.
- 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
- 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
- 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
- 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
- Triple-tap exit zone now covers full header height (was 6px, untappable)
- Uses touchend event instead of click for reliable tablet interaction
- JS bridge registered once before loadUrl (not on every page load)
- Update banner auto-dismisses after 3 seconds
- CRITICAL: _finishSetup() no longer sends empty strings to save_settings
→ was overwriting .env values (Gemini key, Bring credentials) with blanks
→ now only sends non-empty values to the API
- Screen pinning (startLockTask) blocks home/recent buttons
- Gateway launches in background, kiosk returns to front after 1.5s
- Injected thin green bar at top of WebView for triple-tap exit
- JavaScript bridge for kiosk exit from WebView context
- Update check via GitHub releases API (every 6h)
- Shows banner in WebView when kiosk/gateway updates available
- Setup wizard no longer re-appears after completion/skip (evershelf_setup_done flag)
- REORDER_TASKS permission for moveTaskToFront
- singleTask launch mode for proper kiosk behavior
- Version bumped to 1.2.0 (versionCode 3)
- Fix 404: test base URL directly instead of appending /api/
- Fix gateway not detected: add <queries> block for Android 11+ package visibility
- Add splash screen with icon + loading spinner (1.5s)
- Add triple-tap on wizard title to exit kiosk mode
- Accept 3xx redirects as valid responses
- New house/shelf vector icon for launcher
- Replace emoji icon with drawable on welcome screen
- Version 1.1.0
- Remove BLE/scale code (BleScaleManager, ScaleProtocol, GatewayWebSocketServer, ScaleGatewayService)
- Kiosk is now a pure WebView wrapper — scale handled by standalone gateway app
- Fix SSL certificate error: accept self-signed certs for local servers (WebView + connection test)
- Add gateway APK detection: check if it.dadaloop.evershelf.scalegate is installed
- If gateway installed: show green status, auto-launch on finish
- If not installed: show download link to GitHub releases
- Remove BLE/foreground service permissions from manifest
- Remove java-websocket dependency
- Bump version to 1.1.0
- 3-step wizard: Welcome → Server URL → Scale Setup → Launch
- Connection test with live feedback
- BLE scale status during wizard
- Dark theme with modern UI (slate/purple palette)
- Settings page with URL edit, connection test, scale info, wizard reset
- Skip scale option for users without BLE scales
- Error page with retry button when server unreachable
- All UI in English
- Adaptive icon XMLs (API 26+) with vector foreground + green background
- PNG fallbacks for all density buckets (mdpi through xxxhdpi)
- Added ic_launcher_background color to colors.xml
- Green-themed banner between settings panels and Save button
- Direct download link to kiosk-latest release APK
- Auto-hidden when running inside Android WebView (kiosk mode)
- i18n: translations added for it/en/de