- 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
- Restore the if($bringEnabled) block that was accidentally removed in fa0442e
- Check is skipped entirely when SHOPPING_MODE != bring or credentials not set
- Missing token file = first launch, auto-created on next shopping open → ok:true
- Warning shown only if token file exists but access_token field is missing (corrupt)
- Expired tokens are OK (refreshed automatically)
Fixes spurious 'Token Bring!' warning on installs without Bring! configured
- PHP (all 3 recipe endpoints): conf products with weight/volume package_unit
(e.g., 300g/conf) now keep qty_number in sub-units (grams/ml) instead of
converting to fractional conf. Post-normalisation block converts any AI-
returned fractional conf values to grams automatically.
- JS submitRecipeUse: vacuum state now read from actual inventory item at the
used location (_recipeUseContext.items), not from recipe ingredient data.
- JS showRecipeMoveModal: now uses _prefMoveLocCache preference system —
after 2 consistent choices the modal is skipped automatically. 'Stay' button
records the choice. Added _recipeMoveCancelStay() helper.
- JS confirmRecipeMove: records move choice via _recordMoveLocChoice();
accepts optional forcedVacuum param for preference-triggered auto-moves.
Closes#108 (duplicate of #107 — data dir permissions)
- getSettings() no longer reads from localStorage; uses _settingsCache only
- saveSettingsToStorage() no longer writes to localStorage
- _applySyncedSettings() no longer writes to localStorage
- syncSettingsFromDB() meal_plan/tts_voice blocks no longer write to localStorage
- loadSettingsUI() server-merge block no longer writes to localStorage
- flipCamera() saves camera_facing directly to server via _saveSettingToServer()
- New helper _saveSettingToServer(data): calls save_settings API for partial updates
- onShoppingEnabledChange() / onShoppingModeChange() now immediately persist to
server .env via _saveSettingToServer() — no wait for Save button
- Early-theme IIFE: reads dedicated evershelf_dark_mode key (falls back to old
evershelf_settings for backward compat) — only dark_mode kept in localStorage
as a technical necessity for pre-render theme application
- New shopping_list SQLite table (migration in migrateDB)
- shoppingGetList/Add/Remove — delegates to Bring! or internal DB
based on SHOPPING_MODE env var (default: internal)
- isShoppingBringMode() guard: requires mode=bring + BRING credentials
- bringQuickSyncProduct updated to support both modes
- All bring_* JS calls replaced with shopping_* (bring_migrate_names kept)
- New settings tab 'Lista spesa' (tab-bring) with:
- Enable/disable shopping list toggle
- Provider radio: internal vs Bring!
- Bring! sub-section (shown only when mode=bring)
- AI smart suggestions toggle
- Forecast toggle
- Auto-add threshold (qty slider)
- Price estimation section
- _applyShoppingSettingsUI, onShoppingEnabledChange, onShoppingModeChange
- SHOPPING_* env vars documented in .env.example
- cron_smart_shopping respects SHOPPING_MODE and SHOPPING_SMART_SUGGESTIONS
- Translations: 12 new keys in all 5 languages (it/en/de/fr/es)
- DB busy_timeout=5000ms + WAL pragma in getDB() (fixes#95)
- New '🤝 Contributing' section with translation table, skill matrix,
and links to good first issues and help wanted labels
- New '💬 Community' section linking to GitHub Discussions
- Visible call-to-action for translators pointing to issue #93
- Switch redirect URI from server IP to http://localhost (works everywhere)
- Add manual code exchange flow: user copies URL from browser, pastes in app
- New PHP action gdrive_oauth_exchange to exchange auth code for refresh token
- Fix null bug in gdrive_oauth_exchange (was read before initialization)
- Add #gdrive-code-section UI with input + submit button in index.html
- Update _gdriveAuthorize() to show code section and store redirect_uri
- Add _gdriveSubmitCode() JS function for manual code submission
- Update setup wizard and backup tab to show http://localhost as redirect URI
- Add 5 new translation keys (gdrive_redirect_uri_hint, gdrive_code_title,
gdrive_code_hint, gdrive_code_submit, gdrive_code_empty) in all 5 languages
- Update gdrive_oauth_steps in all translations to reflect new flow
- Document Google Drive OAuth setup in README.md
- Dark mode: comprehensive fix for 30+ components with hardcoded light colors