Commit Graph

250 Commits

Author SHA1 Message Date
dadaloop82 965a672abe feat: full Home Assistant integration
- PHP: _fireHaWebhook(), _sendHaNotify(), haInventorySensor(), haTestConnection()
- PHP: ha_sensor + ha_test routing actions
- PHP: getServerSettings() exposes ha_token (consistent with tts_token)
- PHP: saveSettings() handles all HA_* env keys (url, token, tts_entity, webhook_id, events, notify_service, expiry_days)
- PHP: bringAddItems(), shoppingAdd(), updateInventory() fire shopping_add / stock_update webhooks
- Cron: daily HA expiry/expired webhook + push notify with flag-file guard
- HTML: 🏠 Settings tab button + full HA panel (connection, TTS, webhook, notify, sensor cards)
- JS: serverKeys + loadSettingsUI extended with HA fields
- JS: _applyHaSettingsUI(), _loadHaTab(), _renderHaSensorYaml()
- JS: onHaEnabledChange(), testHaConnection(), applyHaTtsPreset()
- JS: saveHaSettings(), copyHaSensorYaml(), showHaWebhookHelp()
- JS: _buildHaTtsRequest() for HA media_player TTS
- JS: speakCookingStep() now supports HA TTS as first-priority path
- JS: onTtsEngineChange() fixed to show server section for both 'server' and 'custom'
- Translations: settings.ha.* (52 keys) in all 5 languages (it/en/de/fr/es)
- .env.example: HA_ENABLED/URL/TOKEN/TTS_ENTITY/WEBHOOK_ID/EVENTS/NOTIFY_SERVICE/EXPIRY_DAYS
- docs/wiki/Home-Assistant.md: new wiki page (REST sensors, webhooks, TTS, push notify, troubleshooting)
- README: HA integration highlighted as first feature block
2026-05-23 12:28:09 +00:00
dadaloop82 3989d11094 fix: step.replace is not a function when Gemini returns steps as objects
- 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
2026-05-23 11:55:55 +00:00
dadaloop82 cc0fa09219 fix: recipe errors now show specific cause instead of generic 'connection error'
- 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
2026-05-23 11:45:26 +00:00
dadaloop82 6a41b53174 feat: shopping list pantry hints, barcode multi-API fallback (OPF/beauty/Gemini), README disclaimer
- 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
2026-05-23 09:53:17 +00:00
dadaloop82 6320b575e0 v1.7.25 — partial throw from banner, barcode fallback, related stock, Bring! re-add fix
- Fix: Bring! items re-appearing after manual removal (missing _markBringPurchased call in removeBringItem / confirmShoppingItemFound; autoAddCriticalItems now respects blocklist for qty=0 items)
- Fix: barcode false 'not found' — new _offFetchProduct() helper tries UPC-A↔EAN-13 candidates × 2 locales with auto-retry; UPCItemDB fallback also iterates candidates
- Fix: bannerThrowAway() now opens partial-throw modal (location + qty input + throw-all button) instead of immediately discarding everything
- Add: related stock card on action page — shows same-family inventory items when scanning a branded product
2026-05-23 08:17:20 +00:00
dadaloop82 98426bf861 fix: dark_mode persisted in server .env (not localStorage) — add to saveSettings, getServerSettings, applySyncedSettings 2026-05-21 18:41:12 +00:00
dadaloop82 66f5a03503 fix: wrap updateInventory DB writes in a transaction to prevent concurrent lock errors (#109 #110) 2026-05-20 15:38:34 +00:00
dadaloop82 149621651d fix: depleted items urgency based on usesPerMonth only (not recency) 2026-05-20 13:46:00 +00:00
dadaloop82 ccc2f8907d fix: depleted items urgency — use buyCount/useCount from internal history to assign medium/low 2026-05-20 13:41:04 +00:00
dadaloop82 7b60f1dbe3 fix: 0.5 conf use page (default conf mode + fraction btns); depleted items always in shopping; conf decimals in history log 2026-05-20 13:35:27 +00:00
dadaloop82 ac8b5acc0c fix: restore Bring! health check; token warning only when truly invalid
- 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
2026-05-19 17:26:06 +00:00
dadaloop82 87eac171bf fix: recipe quantities for conf+weight; move modal remembers location
- 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)
2026-05-19 16:51:37 +00:00
dadaloop82 fa0442e2f6 feat: native shopping list — decouple from Bring! (#105)
- 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)
2026-05-19 16:05:49 +00:00
dadaloop82 7364e75881 feat: Google Drive OAuth via http://localhost redirect (no public domain required)
- 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
2026-05-18 18:41:56 +00:00
dadaloop82 4515ff7246 i18n: replace all hardcoded Italian strings with English
- api/index.php: health check hints (disk space, DB, Gemini, TTS, scale,
  internet) translated to English; Bring! error strings (credentials,
  list not found, fetch error, missing params) translated; Gemini chat
  and identify_product error strings translated; transaction note
  [Correzione manuale] -> [Manual correction]
- assets/js/app.js: expiry scanner result strings now use t() keys
  (scanner.expiry_found, scanner.expiry_read_fail, scanner.expiry_raw_label);
  removed Italian fallback from kiosk native_update_hint toast
- translations/{it,en,de}.json: added scanner.expiry_found,
  scanner.expiry_read_fail, scanner.expiry_raw_label keys
- README.md: 'Generali' tab label -> 'General' (2 occurrences)
2026-05-18 07:32:41 +00:00
dadaloop82 bd5d4bcac6 fix: dispensa.db auto-delete, zerowaste save, vacuum expiry, DB retention
- api/index.php: auto-delete legacy dispensa.db when evershelf.db exists
  and dispensa.db is empty (<1KB); vacuum-sealed items only show as
  expired after VACUUM_EXPIRY_EXTENSION_DAYS (default 30) past printed
  date; add dbCleanup() function; add recipe/tx/vacuum params to
  getServerSettings + saveSettings intMap; add 'db_cleanup' action
- api/cron_smart_shopping.php: run dbCleanup() each cron cycle
- app.js: add zerowaste_tips_enabled + screensaver_timeout + retention
  days to saveSettings POST payload (were missing, causing reset on sync);
  asset version bumped to v=20260518b
- .env: added ZEROWASTE_TIPS_ENABLED, RECIPE_RETENTION_DAYS=7,
  TRANSACTION_RETENTION_DAYS=7, VACUUM_EXPIRY_EXTENSION_DAYS=30
2026-05-18 07:16:26 +00:00
dadaloop82 c9a859463c feat: Generali tab, time-based auto theme, AI cost from real data
- index.html: new Generali tab (first, active) with Language/Currency/
  Theme/Screensaver/ZeroWaste/Export; old tab-language removed;
  screensaver timeout select uses form-input style; asset v=20260518a
- app.js: auto theme = time-based (20:00-07:00 dark, not system pref);
  removed matchMedia listener; added 5min setInterval for auto re-check;
  removed Bring! token row from Info tab (internal implementation detail)
- api/index.php: gemini_usage - removed all cache-estimation code;
  month/year_stats from ai_usage.json only
- data/ai_usage.json: data-driven baseline estimate for 2026-05:
  ~4.4M in + ~1.3M out from 8374 inferred historical calls (102 recipes,
  555 price lookups, getStats loop pre-fix, smart cron runs, etc.)
  = ~EUR 1.32 at 2.5-flash rates; new calls tracked precisely from now
- translations: settings.tab_general added; theme.auto updated to
  'Automatico (orario)' / 'Automatic (time of day)' / 'Automatisch (Tageszeit)'
2026-05-18 07:07:47 +00:00
dadaloop82 56e68b72f8 feat: Info tab v3 — clean month/year stats, currency to Info tab, Gemini costs from .env
- .env: GEMINI_COST_* rates configurable (4 new vars, defaults to current Google pricing)
- api/index.php: GEMINI_COST defines read from env() with fallback; added SHOPPING_NAME_CACHE_PATH
- api/index.php: gemini_usage output — clean month_stats/year_stats (no tracked/retro split)
  updated token estimates: price 700/250, shelf 650/120, cat 280/40, shopping_name 250/40
  added 'pricing' key to response (current rates); removed food_facts from estimate
- index.html: currency selector moved from tab-api to tab-info as first card (global setting)
- app.js: _renderInfoTab() rewritten — just month + year sections, no retro framing
  cost displayed in user's currency (price_currency) with expanded multi-currency conversion
- translations: settings.info.currency_title/hint/year_label added; retro/tracked keys removed
2026-05-18 06:45:56 +00:00
dadaloop82 cc0d9763ed feat: Info tab enriched — retroactive AI estimate, annual totals, inventory & activity stats
api/index.php:
- gemini_usage: retroactive AI call estimate from cache files (price/shelf/category)
  with per-entry token estimates (price ~475tok, shelf ~580tok, category ~230tok)
- yearly totals: sum tracked months + retro estimate for full 2026 view
- DB activity stats: products, inventory, transactions, expired, expiring_soon
- cache stats: price (255), shelf (30), category (7), foodfacts (10)
- system info: last backup timestamp+size, Bring! token expiry
- new constants: SHELF_CACHE_PATH, FOODFACTS_CACHE_PATH, BRING_TOKEN_PATH

assets/js/app.js:
- _renderInfoTab(): full rewrite — 4 cards (AI, Inventory, Activity, System)
- month displayed as localized name via Intl.DateTimeFormat (es. 'maggio 2026')
- tracked section shown when calls > 0; retro estimate always shown if gap exists
- year section: tracked + retro combined total
- pill() helper for consistent stat display

index.html: 4 cards with ids info-ai-content, info-inv-content, info-act-content, info-system-content

translations: updated settings.info.* keys in it/en/de (overview subtitle, retro labels, inv/act/system keys)
2026-05-18 06:33:59 +00:00
dadaloop82 9f554c6e22 feat: Gemini token usage counter (#82) + smarter qty suggestions 90-day EWMA (#70)
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
2026-05-18 06:23:42 +00:00
dadaloop82 dc3cefefd0 feat(logging): complete EverLog coverage in index.php
- Entry log (INFO/DEBUG) added to all public API functions
- EverLog::warn/error added before every uncovered http_response_code(4xx/5xx)
- Total EverLog calls: 117 across all request paths, error paths, and AI flows
- Only pure helper functions excluded (no I/O, no side-effects)
2026-05-18 05:55:14 +00:00
dadaloop82 30f4bf4a1b chore: sposta cartella log in logs/ alla root del progetto
- api/logger.php: path aggiornato da data/logs/ a logs/
- logs/README.md: placeholder con documentazione formato e configurazione
- .gitignore: aggiunto logs/*.log (file ignorati, cartella tracciata)
2026-05-18 05:48:20 +00:00
dadaloop82 2806cb0903 feat: sistema di log rotante 4 livelli (EverLog + LoggingPDO)
- api/logger.php: EverLog static class con 4 livelli (DEBUG/INFO/WARN/ERROR)
  - Rotazione oraria/giornaliera configurabile via LOG_ROTATE_HOURS
  - Max file configurabile via LOG_MAX_FILES (default 14)
  - Request ID unico per tracciare ogni chiamata API
  - EverLog::query(), aiCall(), aiResponse(), cache(), slowOp(), exception()
  - Endpoint get_logs per inspection remota (protetto da SETTINGS_TOKEN)
  - LoggingPDO + LoggingPDOStatement: auto-log di OGNI query SQLite
- api/database.php: getDB() restituisce LoggingPDO (drop-in, retrocompat.)
- api/index.php: EverLog integrato in ~82 punti
  - Entry log in ogni funzione API
  - callGemini/callGeminiWithFallback: timing AI + aiCall/aiResponse
  - Rate limiter, unknown action, errori globali, DB connect fail

Livello default: INFO (query DB a DEBUG, solo se LOG_LEVEL=DEBUG)
2026-05-18 05:45:46 +00:00
dadaloop82 da4aa5a1ae fix: shelf life formaggio, banner vacuum/modifica, preloader redesign, ricetta da ingrediente, porzioni, modal ricetta testo tradotto, use_btn semplificato 2026-05-17 18:19:13 +00:00
dadaloop82 9541e3a385 feat: preloader smooth fade ticker; fix asiago shelf life; kiosk 5-lang wizard (ES/FR + Gemini/Bring steps)
Preloader:
- Replace 3D wheel with smooth fade-in ticker queue
- Bigger text (clamp 1.1–1.35rem), green/amber/red per check state
- Previous items fade upward at decreasing opacity
- Wider container (min(96vw,860px)) — no more awkward line-wrapping
- JS already used ticker-item/state-ok/warn/error classes (CSS was missing)

Shelf life — Asiago sottovuoto fix:
- estimateSealedExpiryDaysPHP() and estimateExpiryDays() JS:
  asiago/fontina/emmental/gruyere/scamorza now grouped with hard cheeses (60d base)
  vacuum sealed: 60 × 2.5 = 150 days — correct for fridge + sottovuoto
- Cleared stale opened_shelf_cache entry for 'Formaggio Asiago fresco'

Kiosk wizard:
- 5 languages: values-es/ and values-fr/ created (97 strings each)
- values/, values-it/, values-de/: complete rewrite with new keys
  (ble_connecting, ble_connecting_to, summary_scale_ok/warn, Gemini/Bring step strings)
  stepDone hardcoded Italian → @string refs; screensaver nav → @string/setup_step_back/next
- SetupActivity.kt: steps 0-8 fully implemented; ES/FR language selection;
  auto-skip Gemini/Bring if already configured; buildSummary() localised;
  finishSetup() sends gemini_api_key + bring_email/password; BLE connecting
  strings localised; scale summary lines use R.string
2026-05-17 16:23:22 +00:00
dadaloop82 47ce849311 fix(ux): banner aperto senza 'Usa comunque'/'Ignora'; preloader ruota 3D; config default non bloccante
Banner prodotti aperti:
- Rimosse le opzioni 'Usa comunque' e 'Ignora' (non hanno senso
  se il prodotto è solo aperto — rimangono solo 'L\''ho finito!',
  'L\''ho buttato', 'Correggi data')
- Per prodotti scaduti non aperti il comportamento rimane invariato

Preloader startup check:
- Sostituito il mini-label monospace con una ruota 3D (stile cooking wheel)
- Testo grande, colorato: VERDE=ok, ARANCIONE=warning, ROSSO=errore
- Il check precedente sale in cima (rotateX tilt, dimmed) mentre il
  nuovo entra dal basso con animazione 3D
- setProgress() ora guida la ruota; slowAnim() aggiorna solo la barra

Defaults / non-bloccante:
- Gemini API key non impostata → ok:true 'non configurata' (verde)
- Bring! token non ancora generato → ok:true (verde, auto-generato al 1° accesso)
- La configurazione mancante mostra  informativo, non ⚠️ warning
2026-05-17 15:47:57 +00:00
dadaloop82 8360f5a0a0 feat(kiosk-wizard): step Features con screensaver, prezzi, piano pasti, zero-waste tips
Step 5 del wizard ora mostra 4 toggle (pre-compilati se già configurati):
  - Salvaschermo orologio (screensaver_enabled)
  - Prezzi lista spesa (price_enabled)
  - Piano pasti (meal_plan_enabled)
  - Suggerimenti zero-waste durante cottura (zerowaste_tips_enabled)

Solo i toggle NON ancora impostati in prefs partono da false (fresh install).
Tutti e 4 vengono salvati in SharedPreferences e inviati al server via
save_settings al completamento del wizard.

PHP/JS: zerowaste_tips_enabled aggiunto come impostazione server-side
(ZEROWASTE_TIPS_ENABLED in .env), sincronizzata nel WebView via
_applySyncedSettings() al caricamento.
2026-05-17 15:40:01 +00:00
dadaloop82 92048c9eba fix: price calc conf+weight label → convert g to packs, not qty×price (v1.7.22)
When unit='conf' and we know the package weight (default_quantity + package_unit)
and the AI price label contains a weight (e.g. 'pacco 500g'), convert:
  total_grams = qty × defQty
  packs = ceil(total_grams / label_grams)
  cost = packs × price_per_unit

Examples:
  Noci 7 conf×170g at €3.20/pacco 500g → 3 packs × €3.20 = €9.60 (was €22.40)
  Fragole 3 conf×250g at €3.29/conf 500g → 2 packs × €3.29 = €6.58 (was €9.87)
  Ceci 2 conf×250g at €2.00/pacco 500g → 1 pack × €2.00 = €2.00 (was €4.00)
2026-05-17 15:07:13 +00:00
dadaloop82 a690d2e7cf fix: conditional checks, evershelf.db fix, warning popup 5s, error modal (v1.7.22)
- 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
2026-05-17 10:00:38 +00:00
dadaloop82 78f499205c feat: progress bar startup check with 29 diagnostics (v1.7.21)
- 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
2026-05-17 09:50:42 +00:00
dadaloop82 d3b119c7fe feat: startup health check during splash screen (v1.7.20)
- 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
2026-05-17 09:40:11 +00:00
dadaloop82 a602726531 feat: zero-waste tips during cooking mode (#76) 2026-05-17 09:16:48 +00:00
dadaloop82 0a6e653692 feat: dark mode (Off/On/Auto) + export inventory CSV/PDF (#78, #64) 2026-05-17 08:59:40 +00:00
dadaloop82 47c26ffdc8 v1.7.15 — centralize all settings to server (.env + SQLite)
- TTS: tts_engine, tts_rate, tts_pitch, tts_auth_header_name, tts_auth_header_value,
  tts_extra_fields now stored in .env and synced across devices via get_settings/save_settings
- meal_plan: persisted to SQLite app_settings table on every edit (selectMealPlanType,
  resetMealPlan) and restored on startup via syncSettingsFromDB — all devices stay in sync
- tts_voice: also synced to SQLite for best-effort cross-device restore
- saveSettings() sends meal_plan + tts_voice to app_settings_save after env write
- Remove deprecated SPESA_PROVIDER and SPESA_AI_PROMPT from .env
- .env.example: full rewrite documenting all 30+ keys in labelled sections
  (AI, Shopping, TTS, Preferences, Appliances, Scale, Meal Plan, Screensaver, Prices,
  Security, Developer)
2026-05-16 16:09:49 +00:00
dadaloop82 473d3f59a4 v1.7.15 — i18n audit, splash min 3s, decimal precision, demo GIF, README fixes
- 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)
2026-05-16 15:36:31 +00:00
dadaloop82 5f4c29bd5a feat: in-app bug report form (replaces GitHub link) 2026-05-16 13:25:51 +00:00
dadaloop82 68693e7168 fix(expiry): sealed potatoes shelf life 14→30 days (aligns with JS) 2026-05-16 12:31:26 +00:00
dadaloop82 8928c75a9d feat(recipes): add tools_needed field — appliances shown as chips above ingredients 2026-05-16 09:56:10 +00:00
dadaloop82 3065b80370 fix(expiry): potato shelf life 14→30 days in pantry; add explicit rules for onion/garlic/carrot 2026-05-16 09:25:04 +00:00
dadaloop82 6b982b6730 fix: migration crash 'no such column: undone' on old DBs
When migrateDB() upgraded the transactions table to add the 'waste'
CHECK constraint, the new table was created WITHOUT the 'undone' column.
The migration then tried to build idx_transactions_pid_type_undone, which
references 'undone' → PDOException SQLSTATE[HY000].

Fix:
- Add undone INTEGER DEFAULT 0 to the migration CREATE TABLE
- Replace 'INSERT INTO transactions SELECT * FROM transactions_old'
  with explicit column list (transactions_old may predate undone column)

Fixes: #56
2026-05-16 07:15:03 +00:00
dadaloop82 f121b8804c fix: jam/confiture opened shelf life in fridge 60→180 days
Both PHP and JS rules for opened confettura/marmellata in
section G (fridge condiments) were returning 60 days — too short.
An opened jar of jam lasts ~6 months in the fridge.

Also: update README roadmap with comprehensive, grouped view
matching the internal memory roadmap (high/medium/low/completed).

Fixes: database.php line ~412, app.js line ~1707
2026-05-16 06:38:18 +00:00
dadaloop82 da4bd635db feat: professional repo cleanup + community infrastructure
- README: remove Recent Updates section, clean roadmap (pending only),
  replace Screenshots with demo link, add 6 new badges (stars, last
  commit, contributors, discussions, CI), invite GIF contributions
- CHANGELOG: translate all Italian entries to English, add v1.7.13
  (DB fresh-install crash fix)
- database.php: add missing 'undone' column to transactions schema;
  wrap ALTER TABLE calls in try/catch to prevent race-condition errors
  on concurrent first requests
- Wiki: Android-Kiosk v1.5.0 → v1.6.0, Step 5 rewritten (BLE scan,
  no external APK), removed gateway troubleshooting section
- Wiki: Scale-Gateway promoted to deprecated with redirect banner
- Wiki: Home What's New updated to v1.7.12 / v1.7.13
- Wiki: Features.md, kiosk README Italian UI strings translated
- .github: add bug_report.yml, feature_request.yml issue templates,
  config.yml (blank issues off, links to FAQ/Discussions/Security),
  PULL_REQUEST_TEMPLATE.md with checklist
- .github: FUNDING.yml (Ko-fi), dependabot.yml (monthly action updates)
- .github/workflows/security.yml: Trivy docker + fs scan, SARIF upload
- .github/workflows/build-scale-gateway.yml: disabled (deprecated)
- SECURITY.md: responsible disclosure policy, supported versions, scope
- CODE_OF_CONDUCT.md: Contributor Covenant 2.1
- Settings UI: About section with version display, Report Bug button,
  Changelog and GitHub links; reportBugManual() + _loadAboutSection()
- Translations: added 'about' key group (9 keys × 3 languages)
2026-05-16 06:02:18 +00:00
dadaloop82 850c5047b8 Fix noisy consumption alerts and make predictions adaptive 2026-05-15 11:41:29 +00:00
dadaloop82 2d70e7a688 chore: release v1.7.12 — banner aperti, fix ricette pz, fix usa-tutto 2026-05-13 11:40:05 +00:00
dadaloop82 696a9c6d11 feat: scan page redesign — fixed 2x zoom, torch, camera flip, tabs, recents, AI number OCR
- Always-on 2x hardware zoom (CSS scale fallback)
- Torch button with toggle + visual feedback
- Camera flip (front/back) with settings persistence
- 3-tab input panel: Barcode / Name / AI
- Recent products chips (last 6 scanned, from localStorage)
- Live barcode code overlay during partial detection
- Confirm overlay (checkmark + name) on successful scan
- AI number OCR (Gemini reads barcode digits from image, shown after 4s)
- Guide corners frame in viewport
- PHP: gemini_number_ocr action + rate-limited
- Translations: new scan.* keys in it/en/de
2026-05-12 14:55:14 +00:00
dadaloop82 27ba41700f fix: consumption predictions require >=5 txns, 7-day spread, and >=15% predicted consumption ratio 2026-05-12 14:31:24 +00:00
dadaloop82 2c34387592 fix: remove 'untracked' anomaly direction — incomplete purchase history is normal, not an anomaly 2026-05-12 05:52:09 +00:00
dadaloop82 d056a6a116 fix: expired section hides items with quantity=0
Query was missing AND i.quantity > 0, so thrown-away items (qty=0)
with a past expiry_date kept appearing in the expired list.
Also cleaned up the orphan row for Aglio in the DB.
2026-05-11 17:35:53 +00:00
dadaloop82 cb39b63997 fix: drastically reduce false-positive consumption anomaly banners
Two changes:
1. Skip prediction when expected_qty=0 — model says 'should be finished'
   but user simply restocked or consumed less. Not actionable.
2. Raise 'more than expected' threshold to 400% (was 30%).
   Having more than expected almost always means a restock the model
   doesn't know about yet — only truly extreme cases (>4x) are flagged.
   'Less than expected' stays at 30% (still actionable: unregistered use).
2026-05-11 17:31:41 +00:00
dadaloop82 5b401f8d5f fix: consumption predictions false positives after restocking
Root cause: baseline was 'restockQty' (only the new items added) but
actualQty = pre-existing stock + new items → always looked like 'more than expected'.

New approach: baseline = current_qty + consumed_since_restock.
This correctly reflects the true starting point regardless of pre-existing stock,
eliminating all false positives after shopping trips.
2026-05-11 17:24:44 +00:00