Commit Graph

210 Commits

Author SHA1 Message Date
dadaloop82 be8dfe9e1e fix: shopping price calc — null for unconvertible /kg items, resolved qty in response/badge 2026-05-08 06:02:40 +00:00
dadaloop82 7cb1dfe285 fix: no HTML in use-all confirm dialog; rework disambiguation UX flow 2026-05-08 05:48:00 +00:00
dadaloop82 f4dbd151a8 fix: getAllShoppingPrices TypeError on null estimated_total for /kg items; fallback to 1-unit price 2026-05-07 20:56:03 +00:00
dadaloop82 4f98a63414 fix: refresh btn busts only total cache (fast); fix _pricesFetching button lock 2026-05-07 20:43:26 +00:00
dadaloop82 d2e5eea05b fix: extend PHP timeout for batch price fetch; refresh btn forces recompute 2026-05-07 20:39:44 +00:00
dadaloop82 b412cc0ebe fix: server is single source of truth for prices — no sessionStorage preload 2026-05-07 20:35:37 +00:00
dadaloop82 c02a2fc632 feat: screensaver shopping panel with item count and estimated total 2026-05-07 19:29:53 +00:00
dadaloop82 dfbdbc6efb fix: use timestamp-based price cache validity; invalidate stale per-item cache 2026-05-07 18:58:11 +00:00
dadaloop82 6c342a412b fix: centralize price totals server-side; batch API call; 5-min total cache 2026-05-07 18:55:37 +00:00
dadaloop82 a01ca583ea fix: skip price fetch on first render when smart data empty; bump header to v1.7.4 2026-05-07 18:50:04 +00:00
dadaloop82 13b55104a1 chore: release v1.7.4 — AI price estimation, dashboard badge, real-time total 2026-05-07 17:55:05 +00:00
dadaloop82 bcd7580729 fix: show price total on dashboard via sessionStorage fallback 2026-05-07 17:48:18 +00:00
dadaloop82 1584d402e4 fix: replace stat-urgent with green price total badge top-right 2026-05-07 17:46:20 +00:00
dadaloop82 20192f902d fix: prices cached on tab switch; background price fetch every 2min; stat-price-total bigger 2026-05-07 17:44:56 +00:00
dadaloop82 b9082eae52 fix: log.title emoji; add price estimate total to shopping stat card 2026-05-07 17:41:41 +00:00
dadaloop82 3a9f0ccf79 fix: bump asset versions to force cache bust; price rate limit own bucket
- app.js and style.css versioned to 20260507a so browsers load new code
- get_shopping_price / get_all_shopping_prices moved to dedicated 'price'
  rate-limit bucket (60 req/min) separate from general (120 req/min)
  to avoid false 429s during sequential per-item price loading
2026-05-07 17:34:04 +00:00
dadaloop82 5f510c0451 feat: AI price estimation for shopping list with per-item real-time display
- 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
2026-05-07 17:31:23 +00:00
dadaloop82 ffb0341eb6 feat: screensaver configurable timeout + fix gitignore (exclude kiosk build artifacts) 2026-05-06 15:00:04 +00:00
dadaloop82 1c890c66ea feat: kiosk manual update check + install from settings
KioskActivity.kt:
- checkForUpdates(forceCheck, jsCallback): accepts force flag + optional
  JS callback to deliver result as JSON to the WebView
- New @JavascriptInterface checkForUpdates(): bypasses 6h throttle,
  calls back window._kioskUpdateResult({has_update, current, latest, apk_url})
- New @JavascriptInterface installUpdate(apkUrl): triggers APK download+install
- Improved error handling: HTTP status check + network error JSON response

app.js:
- window._kioskUpdateResult(): callback receives update check JSON,
  shows green/amber status box with version info, shows install button
- _kioskCheckForUpdates(): triggers native check, shows spinner while waiting
- _kioskInstallUpdate(): passes apk_url to native installUpdate()
- loadSettings(): shows #kiosk-update-panel when in kiosk WebView

index.html:
- #kiosk-update-panel: version label, 'Cerca aggiornamenti' button,
  status box, 'Installa aggiornamento' button (hidden until update found)
- CSS cache bump ?v=20260506e

build.gradle.kts: version 1.6.0 → 1.7.0 (versionCode 10 → 11)
2026-05-06 14:17:31 +00:00
dadaloop82 891733aa8c fix: scale dot white+glow, kiosk reconfigure fallback, live weight in settings
style.css:
- Connected dot: white fill + green border/glow (was green-on-green, invisible)

app.js:
- _kioskReconfigureScale(): show #kiosk-needs-update-notice + toast when
  kiosk APK is too old and reconfigureScale() method is missing
- _scaleUpdateStatus(): show/hide #scale-live-diag panel, update device + battery
- _scaleOnMessage weight: update #scale-diag-weight in real time
- _scaleOnMessage status: update #scale-diag-proto with BLE protocol

index.html:
- #kiosk-needs-update-notice: amber warning + download link inside kiosk panel
- #scale-live-diag: device name, battery, live weight readout, reconnect info
- CSS cache bump ?v=20260506d
2026-05-06 14:07:29 +00:00
dadaloop82 e002cc4483 feat: nutrition analysis section + screensaver animated pie charts
app.js:
- _buildNutritionData(): category distribution, health/variety/freshness scores
- _renderNutritionSection(): animated 3D conic-gradient pie + legend + score bars
- _startInsightAlternation(): waste <-> nutrition fade-swap every hour
- _startScreensaverRotation(): facts and nutrition panel alternate every 5 min
- _renderScreensaverNutrition(): 3D animated pie + donut ring scores on screensaver
- _ssDonut(): CSS-only ring donut helper
- Removed two generic filler screensaver facts
- Cleaned up time-of-day screensaver facts (content-aware, no empty greetings)

index.html:
- Wrap waste/nutrition sections in #dashboard-insight-wrap
- Add #screensaver-nutrition slot in screensaver overlay
- Bump CSS cache ?v=20260506c

style.css:
- .ss-pie3d: 3D perspective + cubic-bezier spring + continuous slow spin
- .ss-donut-ring: CSS conic-gradient donut with bobbing animation
- .nutr-card, .nutr-pie-3d: dashboard nutrition card with 3D pie spin
- Score bars with fill transition
2026-05-06 14:00:13 +00:00
dadaloop82 115c966322 fix: scale dot contrast + kiosk scale config panel + download banner in kiosk
style.css:
- Scale connected dot: bright #4ade80 fill + white border + double shadow
  so it pops on the dark green header (was white on green = invisible)

index.html:
- Scale settings tab: add kiosk panel with 'Riconfigura bilancia BLE'
  button (hidden in browser, shown in kiosk mode)
- Wrap gateway download section and WebSocket URL section with IDs
  so JS can hide them in kiosk mode
- CSS cache bust ?v=20260506b

app.js:
- syncSettingsFromDB: in kiosk mode hide scale gateway download section,
  WebSocket URL section and test button; show kiosk BLE panel instead;
  auto-set URL to ws://localhost:8765
- Add _kioskReconfigureScale() helper that calls _kioskBridge.reconfigureScale()

KioskActivity.kt:
- Add reconfigureScale() @JavascriptInterface: stops GatewayService,
  clears saved scale device prefs, launches SetupActivity at step 4
- Import GatewayService
2026-05-06 13:44:50 +00:00
dadaloop82 f48fb02589 fix: center header title + bump to v1.7.3 for update detection
- CSS: header-title-wrap uses position:absolute with left:0/right:0
  + justify-content:center so title is truly centered regardless of
  button count on each side (kiosk has 2 left / 3 right)
- header-actions gets margin-left:auto to push right; both sides z-index:1
- Bump version 1.7.2 → 1.7.3 so already-open kiosk tabs will see
  the update badge once the new version is deployed to the server
- CSS cache bust: ?v=20260506a
2026-05-06 05:19:37 +00:00
dadaloop82 af9bae3093 fix: remove skeleton shimmer from dashboard stat cards 2026-05-06 05:15:44 +00:00
dadaloop82 14a7bbccbe fix: bust CSS cache (v20260505b) + bump webapp to v1.7.2
style.css was still served with ?v=20260421a so all CSS changes since
April 21 (scale indicator redesign, banner close button fix, etc.) were
invisible in the kiosk WebView — it used the cached old file.

Bumping manifest.json version 1.7.1→1.7.2 causes the auto-update
detector (_checkWebappUpdate) to fire on the kiosk: it compares the
loaded page version (1.7.1) with the server version (1.7.2), detects
a change, and shows the '⬆️ Aggiorna' badge so the user can reload.
2026-05-05 18:39:35 +00:00
dadaloop82 7ea5505a0d fix: scale indicator, logo crop, gateway LAN IP, setup spacing
webapp:
- Scale indicator: replace plain green dot with ⚖️ emoji + colored
  status badge (green/amber/grey/red); icon fades out when disconnected;
  tap shows a toast with device name + battery level
- Logo images: crop excess transparent padding from logo.png and
  logo_icon.png so content fills the frame at small display sizes
- style.css: reworked .scale-status-indicator CSS for emoji+badge

kiosk:
- SetupActivity: use device's real LAN IP for scale_gateway_url
  (was hardcoded 127.0.0.1 — only worked if server and kiosk run on
  the same machine); added getDeviceLanIp() helper (prefers wlan/eth)
- activity_setup.xml: reduce welcome step padding/margins so step 1
  fits on screen without scrolling; text sizes slightly reduced
- activity_setup.xml: fix feature bullet 'Bilancia Bluetooth via
  Gateway app' → 'Bilancia BLE integrata (nessuna app esterna)'
- strings.xml (en + it): rewrite all wizard_gateway_* strings to
  reflect integrated BLE service instead of external gateway APK
- ic_logo.png: regenerated at all densities from cropped source
2026-05-05 17:47:54 +00:00
dadaloop82 9cb29de1f0 kiosk: integrate BLE scale gateway + fix logo/branding
- Kiosk v1.6.0 (versionCode 10)
  - Integrate BLE scale gateway directly into kiosk app (no external app needed)
    - New scale/ package: BleScaleManager, GatewayWebSocketServer, ScaleProtocol, GatewayService
    - GatewayService: foreground service, runs BLE scan + WebSocket :8765 server
    - Auto-reconnect on BLE disconnect; protocol compatible with old gateway app
  - Setup step 4: replace gateway install flow with BLE device scan + selection (mandatory)
  - Permissions: added BLUETOOTH_SCAN, BLUETOOTH_CONNECT, ACCESS_FINE_LOCATION (pre-S),
    FOREGROUND_SERVICE, FOREGROUND_SERVICE_CONNECTED_DEVICE
  - KioskActivity: replace launchGatewayInBackground() with startGatewayService()
  - checkForUpdates: remove gateway APK check (gateway is now internal)
  - Remove GATEWAY_PACKAGE / GATEWAY_DOWNLOAD_URL constants

- Logo / branding
  - logo.png + logo_icon.png: transparent background (no more black)
  - ic_logo.png regenerated in all densities
  - Removed house emoji (🏠) from web UI: favicon, bottom nav, setup wizard header
  - Removed 🏠 prefix from all translations (it/en/de) and manifest
  - Setup wizard: logo shown in language + welcome steps
  - Setup wizard: footer with credits ('Creato da Stimpfl Daniel • Open Source')
  - CSS: .nav-logo-icon for bottom nav logo sizing

- Scale Gateway v2.1.1 (versionCode 8)
  - Fix false update notification: replace == comparison with proper semverNewer()
    (was reporting 'update available' whenever tag != current, e.g. v2.1.0 != 2.1.0)
2026-05-05 17:24:24 +00:00
dadaloop82 30aa2db0b9 branding: add EverShelf logo to kiosk splash + web header/preloader
- assets/img/logo/logo.png: trimmed full logo (icon + text, transparent bg)
- assets/img/logo/logo_icon.png: icon-only crop (no text, for header)
- drawable-*/ic_logo.png: multi-density PNGs for Android splash (mdpi→xxxhdpi)
- activity_kiosk.xml: replace ic_launcher_foreground with ic_logo at 260dp,
  remove redundant 'EverShelf' text row (already in logo image)
- index.html: add logo_icon.png in header title, logo.png in preloader
- style.css: add .app-preloader-logo and .header-logo-icon rules
2026-05-05 16:37:57 +00:00
dadaloop82 d02e48543f chore: release v1.7.1
- Bump header-version in index.html to v1.7.1
- Bump manifest.json version to 1.7.1
- Update CHANGELOG with v1.7.1 release notes

Includes:
- Destructive confirm modal with 5s auto-countdown (throwAll, submitUseAll)
- Undo button visibility fix in history log
- undoTransactionEntry() uses custom modal instead of native confirm()
2026-05-04 19:50:39 +00:00
dadaloop82 2e46090adc fix(webapp): confirmation dialog + undo button visibility
- Add _showDestructiveConfirm() helper: shows modal with 5-second
  auto-confirm countdown bar; user can confirm early or cancel
- throwAll(): now shows confirmation before discarding all stock
- submitUseAll(): same confirmation before marking all as used
- undoTransactionEntry(): replace native confirm() with modal
- Rename core logic to _doSubmitUseAll() / _doUndoTransaction()
- btn-log-undo: more visible (red tint + larger font) so user can
  easily undo accidental operations from the history log
- Bump app.js version to v=20260505a
2026-05-04 18:30:30 +00:00
dadaloop82 bff22d43b4 fix(i18n): translate antiwaste title in IT/DE + use quantity validation
- translations: antiwaste.title = 'Rapporto Anti-Spreco' (IT), 'Anti-Verschwendungs-Bericht' (DE)
- translations: add use.error_exceeds_stock in IT/EN/DE
- submitUse(): block submit if qty > available at selected location + shake animation
- adjustUseQty(): cap '+' button at max available at selected location (incl. sub-unit mode)
- style.css: add @keyframes inputShake + .input-shake class

Bump app.js cache buster to v=20260504c
2026-05-04 16:56:37 +00:00
dadaloop82 874ae90afa fix(settings): fix screensaver toggle (add toggle-slider, correct layout)
- 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
2026-05-04 16:52:22 +00:00
dadaloop82 8d56d4a221 fix(settings): use correct toggle-switch markup for screensaver checkbox 2026-05-04 16:40:47 +00:00
dadaloop82 ab647f38d6 fix(cache): bump app.js version to force browser cache refresh 2026-05-04 16:38:54 +00:00
dadaloop82 108f3ef283 feat(settings): add screensaver toggle in Language tab (default off)
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.
2026-05-04 16:17:36 +00:00
dadaloop82 bc0beea090 fix(header): full redesign — left-aligned title, uniform buttons, update badge
- Title always left-aligned (was centered via 3-col flex trick)
- In kiosk mode: exit/refresh buttons appear left of title via header-left
- All action buttons unified as .header-btn (42×42px, consistent style)
- Scan button: 48×48px + pulse animation to stand out from others
- Gemini button: no longer misuses header-scan-btn class; own indigo tint
- Scale status: same 42×42px .header-btn shape with colored .scale-dot
  inside instead of a tiny 22px standalone circle
- Update notification: uses #header-update-badge beside the title instead
  of replacing title innerHTML (title never disappears anymore)
- Fixed _scaleUpdateStatus() to preserve header-btn class on className reset
2026-05-04 16:12:43 +00:00
dadaloop82 f625e55526 fix: header 3-col layout, shopping_name migration, demo mode UI, kiosk buttons left
Header layout:
- Redesign header-content as 3-column flex (left / center / right)
- Add #header-left div: dedicated slot for kiosk buttons (empty by default)
- header-title: flex-shrink auto, no more position:absolute centering hack
- header-actions: flex:1 1 0 + justify-content:flex-end (right)
- header-left: flex:1 1 0 (left) — equal width balances the title visually

Kiosk exit/refresh buttons:
- _injectKioskOverlay() now appends to #header-left instead of
  insertBefore(firstChild) — buttons appear on LEFT, not mixed with center

DB migration:
- Add shopping_name TEXT DEFAULT '' to CREATE TABLE products schema
- Add ALTER TABLE migration in migrateDB() for existing databases
- Avoids repeated ALTER TABLE in seed code on every request

Demo mode UI:
- _applyDemoModeUI(): hides ⚙️ settings nav button in demo mode
- Suppresses first-run setup wizard when _demoMode === true
- Shows a small DEMO badge in header-left
- Called from both syncSettingsFromDB() and _initApp()
2026-05-04 12:52:29 +00:00
dadaloop82 bf27469228 security: fix 3 critical vulnerabilities
1. Remove raw API key from get_settings response
   - getServerSettings() no longer returns gemini_key in plain text
   - Only gemini_key_set (boolean) and settings_token_set (boolean)
   - JS updated to only check gemini_key_set (removes stale gemini_key fallback)

2. Protect save_settings with SETTINGS_TOKEN
   - If SETTINGS_TOKEN is set in .env, all save_settings calls must
     include matching X-Settings-Token header (uses hash_equals)
   - Empty token = no protection (backwards-compatible default)
   - Settings UI (Security tab) has a token input field
   - Wrong/missing token returns HTTP 403 with error 'unauthorized'
   - JS shows '🔒 Token non valido o mancante' on 403

3. DEMO_MODE native blocking in PHP
   - DEMO_MODE=false added to .env (default off)
   - When DEMO_MODE=true, all write actions return HTTP 403 before routing
   - Blocked: save_settings, product_save/delete/merge, inventory_add/use/update/remove,
     dismiss_anomaly, bring_add/remove/sync
   - demo_mode flag exposed via get_settings so JS can adapt UI
2026-05-04 06:20:23 +00:00
dadaloop82 6756b16ecb fix(ui): center header title on tablet + add skeleton loader to spesa stat card
Header title centering:
- .header-content: remove max-width:600px, use position:relative + justify-content:center
- .header-title: position:absolute; left:50%; transform:translateX(-50%)
  so the title is always at the exact center of the header regardless of
  screen width or how many action buttons are on the right
- Added max-width:calc(100% - 200px) to prevent overlap with action buttons
  on narrow screens

Spesa skeleton preloader:
- index.html: add stat-loading class to stat-spesa (was missing, other 3 had it)
- app.js showPage('dashboard'): add 'spesa' to the skeleton init array
- app.js loadShoppingCount(): remove stat-loading class after data loads
  (like loadDashboard() does for the other 3 locations)
2026-05-04 05:26:38 +00:00
dadaloop82 9e4a8323c3 chore: bump versions + update CHANGELOG/README for v1.6.0
Webapp:    v1.5.0 → v1.6.0
Kiosk:     v1.3.0 → v1.4.0 (versionCode 4→5)
Scale GW:  v2.0.0 → v2.1.0 (versionCode 6→7)

CI: build-scale-gateway.yml now also triggers on develop branch
    (was main-only, causing APK builds to not run on feature branches)

CHANGELOG: added [1.6.0] entry covering PackageInstaller OTA fixes,
  dashboard skeleton, update banners, cooking mode z-index, XOR token
README: updated 'Recent Updates' section with 1.6.0 highlights
2026-05-03 18:00:46 +00:00
dadaloop82 73fbb73974 fix: APK install conflict (PackageInstaller) + dashboard stat skeleton
APK install conflict:
- Replace ACTION_VIEW-based install with PackageInstaller.Session API (API 21+)
- PackageInstaller gives us the actual install status via BroadcastReceiver:
  STATUS_PENDING_USER_ACTION → launch system confirmation dialog automatically
  STATUS_SUCCESS → success toast
  STATUS_FAILURE_CONFLICT/INCOMPATIBLE → show AlertDialog offering to
    uninstall the old version (ACTION_DELETE) so user can re-download and install
- FileProvider no longer needed for install (still kept for other uses)
- Kiosk: derive target package from filename (gateway vs kiosk self-update)

Dashboard 0-flash:
- Replace hardcoded 0 in HTML stat-value spans with ... placeholder
- Add .stat-loading CSS class: shimmer skeleton animation (gradient sweep)
- showPage(dashboard): set ... + stat-loading before API call
- loadDashboard: remove stat-loading class and set real count after data arrives
2026-05-03 17:51:18 +00:00
dadaloop82 58e69625bd fix: preloader + update notification robustness
- Add full-screen CSS preloader to webapp (fades out when _initApp completes)
- Defer _checkWebappUpdate() to 6s after app init so it does not compete
  with startup API calls (fixes perceived slowness on first load)
- Switch update-check throttle from sessionStorage to localStorage (6h TTL);
  use release published_at instead of version string for comparison, so the
  banner correctly appears when a new release is published regardless of whether
  the tag is a semver or the rolling "latest" tag
- PHP _isLatestVersion(): return true (do not suppress error reports) when
  tag_name is non-semver (e.g. "latest") — was incorrectly blocking ALL reports
- Kiosk checkForUpdates(): show banner only when the release asset actually
  contains an APK for the component; handle non-semver tag by treating it
  as always-update (prevents silent no-op with rolling "latest" tag)
- Scale gateway checkForUpdates(): same non-semver fix; apkUrl now defaults
  to empty and bails out if no matching APK found in assets (prevents 404 install)
2026-05-03 17:46:42 +00:00
dadaloop82 a6c2fb93cf feat: offline OCR (Tesseract) + embedding category classifier (@xenova/transformers)
Tesseract OCR (PHP, server-side):
- Dockerfile: adds tesseract-ocr + tesseract-ocr-ita + libgd-dev (gd extension)
- api/index.php: new tesseractReadExpiry() — decodes base64 image, pre-processes with GD (2× upscale, greyscale, auto-contrast, sharpen), runs tesseract CLI with ita+eng PSM-6, extracts date with multi-pattern regex (DD/MM/YYYY, MM/YYYY, ISO, named-month), returns YYYY-MM-DD + confidence
- geminiReadExpiry() now: (1) tries Tesseract first; (2) falls back to Gemini Vision if OCR returns null or no date found; (3) passes source ('ocr'|'gemini') in response

@xenova/transformers embedding classifier (browser-side):
- index.html: ES-module bootstrap that lazy-loads 'Xenova/all-MiniLM-L6-v2' quantized (~23 MB, cached in browser) via window._getCategoryPipeline(); pre-warms on first scan page visit
- assets/js/app.js: classifyCategoryByEmbedding(name) — embeds product name + 16 category anchor descriptions, cosine similarity, threshold 0.30; results cached in _embeddingCache Map
- autoDetectCategory(): after keyword map misses, fires classifyCategoryByEmbedding async and updates select when resolved (respects manuallySet flag)
- createQuickProduct(): if regex returned 'altro', silently patches category with embedding result via a background api call
2026-05-03 13:17:14 +00:00
dadaloop82 e71ef3aba3 Dashboard: move waste-chart above expiring; fix opened-items conf split, expiry cache, AI validation, MAX_SHOWN 20; remove DupliClick from README 2026-04-29 17:02:10 +00:00
dadaloop82 3c9fe7dfea Remove all Dupliclick/Spesa integration; merge annual waste info into status line 2026-04-29 16:52:36 +00:00
dadaloop82 ee2c280167 Redesign anti-waste section: report card with grade, comparison vs national avg, savings badges and trend chart
- 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
2026-04-29 05:54:17 +00:00
dadaloop82 8558db1925 Complete i18n pass for recipes and meal plan labels 2026-04-28 17:28:54 +00:00
dadaloop82 cb75558581 fix: auto-migrate Bring! names to generic on every list load (throttled 10min)
- bringGetList() now runs bringMigrateNamesInternal() silently after
  returning the response, max once per 10 minutes (bring_migrate_ts.json flag)
- Refactored migration into bringMigrateNamesInternal() reusable function
- Removed manual migrate button from UI (not needed, it's automatic now)
- One-shot migration already executed via curl: 16 items updated in Bring!
2026-04-27 17:33:49 +00:00
dadaloop82 8258591e44 feat: migrate existing Bring! items to generic shopping names
- New API action bring_migrate_names: reads current Bring! list, matches
  items against products DB, replaces specific names with shopping_name
  (e.g. 'Mortadella IGP' → 'Affettato' with spec 'Mortadella IGP · Brand')
- New button in Bring! settings: 'Generalizza nomi lista Bring!'
  with live status feedback (migrated / skipped / errors count)
- Auto-refreshes shopping list view after migration
2026-04-27 17:29:55 +00:00
dadaloop82 37299e60c9 fix: TTS voices — retry 10s, message on failure, refresh button
- 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
2026-04-26 17:28:05 +00:00