Backend (api/index.php):
- callGemini() now extracts usageMetadata (tokens_in/tokens_out) from response
- _recordAiUsage() persists monthly token data to data/ai_usage.json
- callGeminiWithFallback() accepts $usageAction param; all 15 call sites labeled
- gemini_usage endpoint: returns token stats, cost estimate, log info, DB size
- smartShopping(): rolling 90-day EWMA (70% last-30d / 30% days-31-90)
with fallback to all-time rate when <14 days of history
Frontend (index.html + app.js):
- New Info tab (ℹ️) in Settings with Gemini usage and System cards
- _loadInfoTab() / _renderInfoTab(): loads on click, auto-refreshes every 30s
- switchSettingsTab() stops auto-refresh when leaving Info tab
Translations (it/en/de): settings.info.* keys
- health_check: use evershelf.db (not dispensa.db); auto-migrate if needed
- removed dispensa.db (legacy, obsolete)
- backups check: verify files exist (not dir writability, cron writes as root)
- bring_token: read data/bring_token.json (not env var)
- warning popup: 5s countdown bar with label+hint per warning, auto-closes
- error popup: blocking panel with title + hint per critical failure
- db_legacy check: warns if old dispensa.db still present
- 32 total checks (added db_legacy, tts_url, scale_gateway)
- hint messages on every check explaining cause and fix
- translations: added check_db_legacy, check_tts, check_scale,
critical_error_intro, error_network_detail in it/en/de
- Replace banner checklist with real-time progress bar + per-check label
Bar fills smoothly (0→100%) as each check runs; label shows current check.
On success: bar stays green briefly then fades. On warnings: amber badges
shown for 2.2s. On critical error: bar turns red + error block + Retry.
- Extend health_check to 29 comprehensive checks:
PHP 8.0+ version, 4 critical extensions (pdo_sqlite/curl/json/mbstring),
4 optional extensions (openssl/fileinfo/zip/intl), PHP memory/timeout/upload,
data/ writable, rate_limits/ dir, backups/ dir, actual file-write test,
free disk space, SQLite connect, required tables, PRAGMA quick_check integrity,
WAL mode, DB file size, inventory row count, .env file, Gemini AI key,
Bring! credentials + token, cURL SSL version, internet reachability (Gemini API)
- Fresh-install detection: if dispensa.db not found + data/ writable → OK (auto-create)
- Translations: startup.* expanded to 28 keys in IT, EN, DE, FR, ES
- CSS: new .preloader-progress-wrap, .preloader-bar-track, .preloader-bar,
.preloader-check-label, .preloader-warn-badge; removed old .preloader-checks
- Version: v1.7.21, assets v=20260520b
- Add ?action=health_check PHP endpoint (early-exit, before rate-limiter)
Checks: PHP version, required extensions, data/ writability, SQLite DB
connection + table integrity, .env file, Gemini AI key, Bring! token
- Display animated checklist in splash screen with per-item icons
(ok/warn/error); critical failures block app launch with clear error
message and Retry button; optional warnings shown but don't block
- New JS: _runStartupCheck(), _startupRetry(); called first in _initApp()
- New HTML elements in #app-preloader: #preloader-checks, #preloader-error-msg,
#preloader-retry-btn (hidden until startup check completes)
- New CSS: .preloader-checks, .preloader-check-row, .preloader-error-msg,
.preloader-retry-btn with state colors (ok=green, warn=amber, error=red)
- Translations: startup.* keys (10 per language) in IT, EN, DE, FR, ES
- Asset version bump: v=20260520a
- Complete fr.json (1049 keys, 52 sections)
- Complete es.json (1049 keys, 52 sections)
- Language selector updated with Francais and Espanol
- Setup wizard localized for fr/es
- Default fallback language changed from 'it' to 'en'
- Version bump to 1.7.17
- Complete i18n audit: 25+ new translation keys (en/it/de) — vacuum toast,
TTS voices, timer steps, product notes, error prefixes, form placeholders,
barcode hints, recipe/cooking ingredient labels, unit variants
- pz/conf unit labels now use t('units.pz') / t('units.conf') throughout
- Splash screen: minimum 3-second display (_splashStart recorded at parse
time, fade delayed by remaining ms if app loads faster)
- Quantity decimal precision: qtyNum in recipe/cooking buttons and conf
fallback display capped to 1 decimal (was showing 7+ from raw AI output)
- Recipe/cooking buttons: removed Italian fallback strings from t() calls
- README: translated remaining Italian phrases; added demo.gif to Screenshots
- CHANGELOG: updated 1.7.15 entry with all session changes
- assets/img/demo.gif: EverShelf.gif processed at 2x speed (~36s)
- kiosk: add gear button (⚙) to the left overlay (between ✕ and ↻)
so settings are reachable from within kiosk mode without a native
Android button. The web button calls showPage('settings').
- kiosk: permanently hide the native Android settings button via
setNativeSettingsVisible(false) after overlay injection. Removes the
touch bleed-through that caused the camera button tap to open kiosk
settings instead of the scan page.
- kiosk: closeModal() no longer restores native settings visibility
(native button is replaced, must stay hidden)
- dashboard opened-items panel: items expired by opened shelf-life but
classified as safe by getExpiredSafety (level='ok', e.g. jam,
condiments) now show a gentler amber 'Check soon' badge instead of
the red ⛔ 'Scaduto!' that was misleading users. Red ⛔ is now
reserved for warning/danger safety levels only, consistent with the
top banner which already filtered out safe-level expired items.
- header: version label corrected to v1.7.13
- translations: added expiry.badge_check_soon (it/en/de)
When inventory item has opened_at set, the expired banner now shows:
- Title: '[Nome] — Aperto da troppo tempo!' (instead of '— Scaduto!')
- Detail: 'Aperto da N giorni in [icon] [location] · hai ancora X'
Also removed hardcoded Italian 'scade il' string from non-opened expired detail.
- Category badge on every inventory item (icon + label); 'altro' items
refined asynchronously via new guess_category Gemini endpoint
(data/category_ai_cache.json) — no AI call when key not configured
- Category search: inventory search now matches by macro-category key
and translated label (e.g. 'biscotti' finds all cookie items)
- Brand fast-path in guessCategoryFromName (Oreo, Barilla, Lavazza…)
- Fix: duplicate banner alerts — _bannerLoading guard + _queuedItemIds Set
- Fix: mapToLocalCategory with en:dairies (dairi stem added)
- Fix: mapToLocalCategory no longer blocks on 'altro' — falls back to
guessCategoryFromName(productName) before returning 'altro'
- Fix: 'Tonno all'olio' was resolving to condimenti — moved tonno\b
check before olio\b in conserve regex block
- AI guards: _refineCategoryBadgesAsync and fetchAllPrices now check
_geminiAvailable (JS); getShoppingPrice returns no_api_key (PHP)
when GEMINI_API_KEY is not set — all AI functions are now explicit
- Smart shopping: aggiungi family-coverage check per prodotti 'quasi finiti'.
Se il shopping_name family ha scorte da altri prodotti (es. Burro conf)
con unità diff (g/ml vs conf), l'alert 'sta finendo' viene soppresso.
- Corretto bug traduzioni: sezione 'action' duplicata in de/en/it.json
causava JSONDecodeError in CI/CD (line 944 column 2).
- DB: allineamento inventario burro — rimossi 30g residui (usati),
pulito opened_at da pacco nuovo Burro conf (comprato 2026-05-08).
- Bottone 'Apri la ricetta': il transfer btn si trasforma direttamente in
'📖 Apri la ricetta' dopo il successo (invece di aggiungere un elemento DOM separato)
- meal null: chatToRecipe e recipe_from_ingredient non auto-categorizzano il pasto;
renderRecipe mostra il tag meal solo se presente
- Nuovo endpoint recipe_from_ingredient: genera una ricetta con l'ingrediente
selezionato come protagonista, stessa pipeline di chatToRecipe (Gemini + fuzzy-match)
- Bottone '👨🍳 Crea una ricetta con questo' nel pannello azione degli alimenti
(span-2 sotto la griglia 2x2), apre overlay Ricette in loading state
Fixes parse_error on complex recipes (JSON was truncated at 2048 tokens).
After successful transfer, shows 'Apri la ricetta' button inline in chat
alongside the '✅ Aggiunta alle Ricette!' button.
Closes#27
- Sostituisce 'Usa ingredienti' inline con 'Trasferisci a Ricette'
- Nuovo endpoint chat_to_recipe: Gemini restituisce JSON completo
(title, meal, servings, ingredients, steps, nutrition_note),
PHP arricchisce tutti gli ingredienti con product_id/location
via fuzzy-match identico a generateRecipe
- La ricetta viene salvata in archivio e si apre nell'overlay Ricette
con tutti i pulsanti Usa, modalità cottura, salvataggio intatto
- Rimossi: chatExtractIngredients, _buildChatIngredientPanelHTML,
_chatRecipeTitle, chat_extract_recipe, chat-recipe-panel CSS
- Nuovo endpoint chat_extract_recipe: Gemini estrae solo nomi+quantità
con prompt minimo (nessun inventario nel prompt → niente troncamento),
poi PHP fuzzy-match contro l'inventario completo identico a generateRecipe
- Frontend: _looksLikeRecipe() rileva risposte chat con ricetta;
bottone '🥄 Usa ingredienti' appare sotto la bubble, chiama chatExtractIngredients()
che mostra pannello inline con pulsanti '📦 Usa' per ogni ingrediente in dispensa
- useRecipeIngredient() riusato 1:1 con fallback _chatRecipeTitle per le note
- Stili CSS: btn-chat-use-recipe, chat-recipe-panel, chat-recipe-panel-container
- Chiavi i18n: use_ingredients_btn, recipe_ingredients_from_pantry (it/en/de)
- Add get_shopping_price / get_all_shopping_prices API endpoints
- AI (Gemini) estimates retail price per natural unit (pack, piece, bunch)
instead of always per-kg — avoids absurd totals like €1609
- _calcEstimatedTotal: proper g/ml→package conversion using defQty + regex
on unit_label; only 'kg'/'l' labels trigger weight/volume math
- Cache key bumped to v2 to invalidate old per-kg cached entries
- Suggested quantity cap lowered from 20 to 10 conf/pz
- Unit mismatch guard: if totalUsed >> buyCount*5 for unit=conf, use
purchase frequency instead of raw consumption rate
- JS _buildPricePayload: use smartShoppingItems for qty/unit (not Bring! spec)
- JS _cachedPrices: persist in sessionStorage (survives navigation);
validated by _qty/_unit metadata so stale totals auto-invalidate
- Price display redesigned: right-side column per row (price-col-main +
price-col-unit) instead of small inline badge
- fetchAllPrices: buttons disabled immediately before guard check;
running total uses only current shoppingItems (not Object.values cache)
- Background refresh: always silent (removed 90s interaction condition)
- visibilitychange: sets _bgCall=true for shopping before refreshCurrentPage
- .gitignore: add runtime data files (bring_migrate_ts, shopping_price_cache,
anomaly_dismissed, opened_shelf_cache, shopping_name_cache)
- Remove bring_catalog.json and bring_migrate_ts.json from tracking
- .env: split 'Bimby/Moulinex Cookeo' into 'Bimby' and 'Moulinex Cookeo'
- translations (it/en/de): add use_all_confirm_*, throw_all_confirm_*,
confirm.cancel, confirm.proceed, location.dispensa/frigo/freezer
- app.js: when ANY opened package exists, always show disambiguation
dialog ('solo questa confezione?' vs 'tutto?') instead of silently
finishing one — was previously showing dialog only for 2+ opened packages
- Replace checkbox-label with toggle-row pattern matching other toggles
- Add missing <span class="toggle-slider"></span> inside toggle-switch
- Add data-i18n attributes to card title and hint
- Add screensaver.card_title / card_hint translations in all 3 locales
feat(demo): full demo mode implementation
- _applyDemoModeUI(): set _geminiAvailable=true + call _updateGeminiButtonState()
- api(): no-op all bring_add/bring_remove/bring_set_spec calls in demo mode
- api(): return in-memory shoppingItems for bring_list in demo mode
- loadShoppingList(): show placeholder list in demo mode, skip all Bring! calls
fix(shopping): graceful Bring! missing credentials handling
- Show friendly message with link to settings instead of raw PHP error
- Add shopping.bring_not_configured i18n key in IT/EN/DE
Bump app.js cache buster to v=20260504b
Toggle appears in the Language settings tab, below the language selector.
Default: disabled. When disabled, initInactivityWatcher() exits early so
the screensaver never activates. i18n added for it/en/de.
- submitUseAll() now detects opened packages: if current location has an opened pack, finishes only that; if exactly one opened pack exists elsewhere, uses it automatically; multiple opened packs → disambiguation modal
- quickUse() resets scale baseline on page open so stale weight doesn't immediately trigger auto-fill
- Expired alerts (dashboard + banner) now filter out freezer items within their safety window (level='ok')
- Review banner: conf unit quantity displayed as sub-unit total (e.g. 800g) instead of raw pack count; high-qty threshold evaluated on sub-unit volume to prevent '400 confezioni' nonsense
- Low-stock review banner gains 'È finito tutto' button → new bannerFinishAll() handler
- New _submitUseAllAt() helper and _showUseAllDisambiguation() modal
- New translation keys: toast_opened_finished, disambiguation_hint, disambiguation_all, banner_review_action_finish (it/en/de)
- Replace simple bar chart with full Anti-Waste Report Card
- Grade system (A+ to D) based on user's waste rate
- Dual comparison bars: user waste rate vs national average (IT/DE/US)
- Estimated monthly savings in money, meals saved, CO2 avoided
- 3-month trend mini chart with colour-coded bars
- Backend: getStats() now returns 3×30d buckets (used_30d, used_prev_30d, used_prev_60d, etc.)
- Real-world benchmarks: IT 22%/5.4kg/mo (REDUCE), DE 20%/6.5kg/mo (Eurostat), US 30%/9.2kg/mo (USDA)
- All labels fully i18n: 18 new antiwaste.* keys in it/en/de translation files
- Section is fully JS-rendered; HTML now just an empty container
- Convert LOCATIONS labels to use t('locations.*')
- Convert SHOPPING_SECTIONS labels to use t('shopping_sections.*')
- Convert CATEGORY_LABELS to use t('categories.*')
- Convert MEAL_PLAN_TYPES to use t('meal_plan_types.*')
- Convert WEEK_DAYS_SHORT to use t('days.*_short')
- Convert MEAL_TYPES to use t('meal_types.*')
- Convert MEAL_SUB_TYPES to use t('meal_sub.*')
- Convert meal-plan column headers to use translated meal_types
- Replace inline locLabels/LOC_LABELS with translated LOCATIONS object
- Fix shopping action buttons: bring_add_n, bring_add_selected, bring_adding, bring_added_*
- Fix recipe archive empty state
- Fix meal plan reset success toast
- Fix meal plan suggestion hint and screensaver display
- Fix settings save status messages (saved, saved_local, saved_local_error)
- Fix product edit form title
- Fix kiosk session phrases for screensaver counter
- Add cooking.expires_chip translation for expiry date format
- Add meal_plan section (reset_success, suggested_by)
- Add error.select_items for Bring shopping validation
- All strings now properly internationalized for EN/DE languages
- Banner: detect expired opened-products via effective shelf-life (opened_at +
estimateOpenedExpiryDays), not just raw expiry_date — fixes Fagioli/Panna case
- Banner: expired items show safety tip inline; danger-level items (fridge dairy,
meat, fish) get red banner + 'L'ho buttato' as primary button, 'Usa comunque'
demoted to grey; safety-ok/warning items keep original button order
- Banner: anomaly dismiss button now shows current inventory qty ('La quantità è
giusta (2 pz)') so the action is unambiguous
- AI: add callGeminiWithFallback() helper — tries gemini-2.5-flash first (separate
quota), falls back to gemini-2.0-flash; applied to all endpoints (expiry, chat,
identify, recipe non-streaming, shopping name classifier)
- AI: show friendly 'Quota AI esaurita' message instead of raw Gemini error string
- Cooking TTS: fix auto-speak broken since 'auto-speak removed' comment — each step
is now read automatically on navigate and on first step when entering cooking mode
- Cooking TTS: remove incorrect s.tts_enabled gate — _cookingTTS toggle is the only
gate; browser Web Speech API used by default without requiring Settings config
- Cooking TTS: timer fires '10 secondi rimanenti' warning at T-10s
- Cooking TTS: announce recipe completion ('Buon appetito!') on last step confirm
- i18n: add timer_warning_tts, recipe_done_tts, error.ai_quota keys (IT/EN/DE)
- CSS: add banner-expired-danger, banner-safety-* styles for unsafe expired items