Products used ≥ 4x/month (daily staples: latte, pane, uova) now appear in
the smart shopping list when stock will deplete within 28 days (instead of 14).
Products used ≥ 2x/month (weekly staples: yogurt, frutta, carne) appear
within 21 days. The existing ≥1.5x/month rule at 14 days is unchanged.
No manual pin or UI needed: frequency is detected automatically from
transaction history. justRestocked guard prevents noise for freshly
bought items.
- app.js: TTS kiosk timeout 4s → 10s; fires interactive 'Hai sentito?' YES/NO
prompt instead of showing error (TTS can take 6-8s; UtteranceProgressListener
may not fire on all firmware); YES → success, NO → troubleshooting steps
- translations: add heard_question/heard_yes/heard_no/test_ok_kiosk/test_fail_steps
to all 5 languages (it/en/de/fr/es) under settings.tts
- api/index.php: fix end() PHP 8.0+ reference error in _offFetchProduct()
(categories_hierarchy stored in temp var before calling end()) (fixes#130)
- api/database.php: migrateDB() now checks sqlite_master for 'products' table;
if missing, calls initializeDB() and returns — no ALTER on nonexistent table
(fixes#133, covers #131)
- api/index.php: health_check db_row_count query guarded against missing
inventory table (fixes#131)
SetupActivity:
- btnTestRetry click handler: add step3NextButtons.visibility = VISIBLE
so the Indietro/Avanti buttons reappear after pressing No-retry
(previously they stayed hidden → user was stuck with no way to go back)
- onDisconnected(): always re-enable btnTestRetry so user is never stuck
when scale drops unexpectedly before a weight reading arrives
SettingsActivity / activity_settings.xml:
- Add 'IMPOSTAZIONI AVANZATE' section explaining that HA, Gemini AI,
- Add '← Torna all'app per le impostazioni avanzate' button (finish())
Android TTS was silently failing because engine.speak() used the default
audio stream, which may be muted while the media stream is not.
Changes:
- KioskActivity: speak() now passes Bundle with KEY_PARAM_STREAM=STREAM_MUSIC
so TTS plays on the same channel as beep/media audio
- KioskActivity: add UtteranceProgressListener that calls window._kioskTtsDone()
or window._kioskTtsError(uid, code) back into the WebView on completion/failure
- app.js testTTS(): kiosk path now shows real result from Android callbacks
(success/error) with a 4s fallback timeout showing actionable troubleshooting hints
Add FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true env var to ci.yml,
security.yml and build-kiosk.yml to prevent action failures when
GitHub removes Node 20 runner support on 2026-06-02.
Also re-triggers CI to bypass transient 403 infrastructure failure
that hit the previous run (GitHub CDN outage).
- Add testSound() function: plays AudioContext beep independently of TTS
- Add '🔔 Esegui Test Suono' button in TTS settings (above voice test btn)
- Add translations: settings.tts.test_sound_btn (it/en/de)
- Remove hasOtherLocs guard in showMoveAfterUseModal: always show
location-choice modal when opening a package, regardless of whether
the product already exists in other locations (e.g. peas only in pantry
→ user can now choose to move opened portion to fridge)
_speakBrowser: voice fallback now prefers localService=true (offline)
voices over cloud voices. On Android Chrome, cloud voices fail silently
when no internet. New priority: local Italian → any Italian → local
any-lang → first available → lang-only.
testTTS (browser engine):
- Plays _playCookingTimerSound('done') beep FIRST (AudioContext, always
works) so user can confirm audio hardware/volume is working
- Checks if only cloud voices exist → shows actionable error
- Builds utterance with onerror → shows error code on failure
- Adds onstart → shows actual voice name/lang/online status on success
- 2s watchdog: if onstart never fires → 'nessuna risposta' warning
- Shows '🔊 Beep + TTS in corso...' during the test
_speakBrowser: Chrome sets speechSynthesis.paused=true after tab is
backgrounded/minimized. Calling cancel() does NOT clear this state.
Adding resume() before speak() inside the setTimeout fixes silent
failure when user returns to the tab (e.g. switch app → back to browser).
testTTS: add diagnostic messages for:
- Android kiosk: check isTtsReady() and show actionable error if
the native TTS engine hasn't initialized yet
- Web browser: check getVoices().length before speaking; if empty,
show error asking user to install a voice pack in OS settings
(instead of always showing '✅ Riproduzione in corso' even when silent)
_speakBrowser() was silently failing in two scenarios:
1. Chrome cancel+speak bug: calling speechSynthesis.speak() immediately
after cancel() is silently dropped in Chrome. Fixed by adding a 50ms
setTimeout between cancel() and speak().
2. No voice fallback: when tts_voice preference is empty (never saved)
and the browser has no default 'it-IT' voice, speech fails silently.
Now tries: preferred by name → first Italian voice → first any voice
→ lang-only as last resort.
3. Async voice loading: Chrome/Android load voices asynchronously.
When getVoices() returns empty, now waits for onvoiceschanged (with
a 500ms safety timeout) before speaking.
CATEGORY_LABELS was built at parse time before translations were
available, causing raw keys like 'categories.verdura' to appear
in all category labels, dropdowns and inventory filters.
_applyI18nToLabels() now also:
- Rebuilds each CATEGORY_LABELS entry using the loaded i18n strings
and the corresponding CATEGORY_ICONS icon prefix
- Regenerates the static #pf-category <select> options so the
product-add form also shows translated category names
- speakCookingStep: respect tts_engine='browser' explicitly; if user
chose browser engine, always use _speakBrowser regardless of HA.
Also add per-engine try/catch so HA or server TTS failures fall back
to browser TTS instead of silently doing nothing.
- index.html: fix data-i18n='settings.tts.ha_hint' → 'settings.ha.ha_hint'
(key lived in settings.ha not settings.tts)
When user clicks 'Generate another', show a choice:
- Replace (discard current, generate new) — former behavior
- Save to archive & generate new — saves current recipe first
All 5 languages (it/en/de/fr/es) with regen_choice_title,
regen_replace, regen_save_new keys.
- Replaces the old 'New' callout with a proper dedicated section at the top of Features
- Shows all 16 sensors, 6 binary sensors, 5 buttons, todo, calendar, text, 6 services
- HACS and config_flow_start my.home-assistant.io badge buttons
Also: fix dark mode not persisting — _setThemeMode now saves immediately to server .env
- PHP generateRecipeStream: normalize recipe.steps to plain strings after
parsing Gemini JSON (handles [{text:'...'}, ...] objects gracefully)
- JS: add _stepStr(s) helper near cooking mode — safely extracts text from
a step regardless of type (string or object {text/description/step key})
and strips leading 'Passo N:' prefix in one place
- JS: replace all 7 manual step.replace(/^Passo.../) calls with _stepStr()
across renderRecipe, renderCookingStep, startCookingMode, replayCookingTTS,
toggleCookingTTS, navigateCookingStep — no more crash if Gemini schema drifts
- PHP generateRecipeStream: wrap entire body in try/catch(\Throwable) to catch
any PHP fatal/exception mid-stream and send it as a proper SSE error event
- PHP: curl timeout raised 60s→90s; capture curl errno/errmsg on failure
- PHP: HTTP error messages now include a human-readable status label
(e.g. 'Quota API esaurita (429)', 'Nessuna risposta da Gemini (cURL: ...)')
- JS catch block: show err.message alongside error.connection so the actual
JS network error (NetworkError, AbortError, etc.) is visible
- JS no-recipe+no-error path: show recipes.stream_interrupted instead of
generic error.connection
- Translation: added recipes.stream_interrupted in it/en/de
- Shopping list: each item now shows 'Hai già Xg in dispensa' for same-family inventory stock
- Lazy-loads inventory once per shopping page visit (_getShoppingInventoryCache)
- Matches by first significant token (same logic as related-stock on action page)
- Green hint below item badges, dark-mode aware (.shopping-pantry-hint)
- Barcode lookup: added Open Products Facts + Open Beauty Facts as step 3;
Gemini AI (_barcodeLookupGemini) as final step 4 fallback
- Added stockForName PHP endpoint (stock_for_name action) for future use
- Restored missing function signatures for _offFetchProduct() and saveProduct()
that were accidentally lost when stockForName was added in a previous session
- Translation: added shopping.pantry_hint in it/en/de