- Add .github/workflows/build-scale-gateway.yml
Triggers on push to main (evershelf-scale-gateway/** path filter)
Builds debug APK with Gradle/JDK 17, renames to evershelf-scale-gateway.apk
Creates/updates 'latest' GitHub Release so the direct download URL resolves
- Bump web app version v1.2.0 -> v1.3.0 (index.html)
- Bump Android versionCode 1->2, versionName 1.0.0->1.3.0 (app/build.gradle.kts)
- Add evershelf-scale-gateway/ Android app (Kotlin):
- BLE scanning and GATT connection to smart scales
- Supports BT SIG Weight Scale (0x181D), Body Composition (0x181B), and generic heuristic parser
- WebSocket server on port 8765 (local LAN)
- Real-time weight broadcasting to EverShelf browser client
- Add scale status indicator in header (green/orange/grey dot)
- Add Settings tab for scale configuration (URL, enable toggle, test, APK download link)
- Add 'Read from scale' button in Add/Use forms when unit is g or ml
- Add scale WebSocket client logic in app.js with auto-reconnect
- Fix recipe suggestion: expiry-prioritized ingredients now only injected into
AI prompt when user explicitly selects 'Priorità Scadenze' or 'Zero Sprechi'
- Update README with smart scale section and website link
- Update all translations (it, en, de) with scale strings
- Add 9 curated screenshots to assets/img/screenshots/ covering all main features
- Update README with 3x3 screenshot table with Italian descriptions
- Anchor /screenshots/ gitignore rule so assets/img/screenshots/ is tracked
- Screenshots cover: dashboard, inventory, barcode scanner, recipe detail,
recipes list, cooking mode, AI chat, shopping list, smart predictions
Merging all changes from develop into main for v1.2.0 release:
- Project renamed from Dispensa Manager to EverShelf
- Contact email: evershelfproject@gmail.com
- All localStorage keys migrated: dispensa_* -> evershelf_*
- SQLite DB renamed: dispensa.db -> evershelf.db
- Docker/Apache resources renamed to evershelf
- Version badge added to app header
- App name updated in all translations (it, en, de)
- Asset version strings bumped to bust browser cache
- JS file truncation fix (safe Python-based replacements)
- CHANGELOG, manifest.json, OpenAPI spec updated for v1.2.0
- Add v1.2.0 version badge to app header
- Update CHANGELOG with v1.2.0 entries
- Bump manifest.json version to 1.2.0
- Bump OpenAPI spec version to 1.2.0
- Recover app.js from pre-rebrand commit and re-apply all substitutions safely
- Fix all localStorage keys: dispensa_* -> evershelf_* (settings, setup, lang)
- Fix TTS test strings: 'Dispensa Manager' -> 'EverShelf'
- Set nav.title to EverShelf in it/en/de translations and HTML fallback
- Fix JS syntax error (Unexpected end of input) that broke the site
- CI JavaScript Lint should now pass
Add Web Speech API as alternative TTS engine (fully offline, no config needed).
- Engine selector in settings: 'browser' (offline) or 'server' (HTTP endpoint)
- Voice picker populated from speechSynthesis.getVoices(), Italian voices first
- Auto-selects Paola voice on macOS/iOS if available
- Rate and pitch sliders (0.5x-2x, 0-2)
- testTTS() and speakCookingStep() branch on selected engine
- Existing users with tts_url keep 'server' as default engine
Bring! and Gemini keys stored in .env are now fetched from the server
before deciding which wizard steps to show. This prevents the wizard
from prompting for credentials that are already configured server-side.
The wizard now detects which specific settings are missing and shows
only those steps. Existing configurations are preserved. If a future
feature adds a new required setting, it will automatically prompt for
just that one.
- Nel form USA, se il prodotto ha >=2 slot in inventario con scadenze
diverse (es. 2 lotti dispensa, o frigo+dispensa), mostra un banner
giallo: '⚠️ Usa prima quella in Frigo — scade il 12/04 (tra 3 giorni)!'
- Logica: filtra item con expiry_date, ordina per scadenza ASC,
mostra hint solo se ci sono almeno 2 scadenze diverse O 2 location diverse
- Nessun hint se tutto ha la stessa scadenza (inutile)
Ogni chiamata a showProductAction() creava un nuovo div e lo appendeva
con .after() senza rimuovere il precedente -> link moltiplicato.
Fix: il div ha id='catalog-edit-link', viene riutilizzato se esiste;
se il prodotto non è in inventario il link viene rimosso.
- Intervallo pagina corrente: 5 minuti (era 10) -> dashboard, inventario,
scadenze aperte, ricette, log si aggiornano automaticamente
- Intervallo lista spesa: 2 minuti in background anche se non sei sulla
pagina Shopping -> badge/contatore aggiornato, e se sei sulla pagina
Shopping vedi i nuovi prodotti aggiunti da Bring! da un altro device
- refreshCurrentPage() ora copre anche 'recipe' e 'log'
- visibilitychange: refresh immediato su qualsiasi pagina (già presente)
- setInterval ogni 10 minuti ricarica la pagina corrente (dashboard,
inventario, spesa) silenziosamente, senza reload — i contatori di
scadenza restano sempre aggiornati anche se la scheda è aperta da ore
- visibilitychange ora aggiorna SEMPRE la pagina corrente, non solo
la lista spesa, quando la tab torna in foreground
PHP getStats() opened section:
- Primary detection: opened_at IS NOT NULL (reliable, set by useFromInventory)
Fallback: fractional-qty pattern (legacy items)
- Per-item compute opened_expiry = min(opened_at + estimateOpenedExpiryDaysPHP, original_expiry)
→ vacuum_sealed items get 1.5× multiplier
→ always take sooner of 'opened shelf life' vs 'original sealed expiry'
- Add days_to_expiry, opened_expiry, is_edible, has_opened_at to each item
- Filter legacy items (no opened_at) with expiry > 14 days (too much noise)
- Sort by days_to_expiry ASC (soonest/spoiled first) instead of updated_at DESC
JS dashboard opened render:
- Expiry badge: ⛔ Scaduto / ⚠️ Scade oggi / ⏰ Xgg (urgent≤2, soon≤5, ok>5)
- 🔒 icon added when vacuum_sealed=1
- Spoiled items shown with strikethrough name + muted styling (.alert-item-spoiled)
- Cap display at 10 items; 'e altri N prodotti aperti...' note if more
- Sort comes from server (removed JS openedFraction sort)
CSS:
- .opened-expiry-{ok,soon,urgent,today,spoiled} badge classes
- .alert-item-spoiled strikethrough styling
- .alert-more-note
Root cause: autoAddCriticalItems used stale in-memory cache (old critical items)
and re-added items to Bring right after manual removal, because on_bring was
now false but urgency was still 'critical' in the old cache.
PHP smartShopping():
- Rename stockByFirstToken → stockByAnyToken (indexes ALL significant tokens)
- 'Passata di pomodoro' depleted + 'Polpa di pomodoro' in stock → share token
'pomodoro' → passata no longer flagged as critical (COVERS: passata/polpa/pelato
and any future tomato product variant)
- Same logic: 'aglio'/'aglio rosso', 'latte'/'latte di montagna', etc.
JS loadSmartShopping():
- When critical item set changes (items added OR removed), immediately reset
_autoAddedCriticalTs and _bringCleanupTs so next shopping load uses fresh data
instead of debounced old data
JS cleanupObsoleteBringItems():
- Use any-token matching (like PHP) for both stockByAnyToken and urgentSmartByToken
→ 'Passata di pomodoro' in Bring, 'polpa' in stock → share 'pomodoro' → removed
1. Use form pz step bug (banana non scalabile):
- min=0.25 + step=0.5 → solo 0.75, 1.25, 1.75... validi per browser
- 1 intero (default) era INVALIDO → form bloccato silenziosamente
- Fix: step='any', min=0.01 (il passo logico resta in adjustUseQty)
2. cleanupObsoleteBringItems mai eseguita:
- Usava products_list che non ha campo quantity → (qty||0)<=0 sempre vero → skip sempre
- Fix: usa inventory_list che ha le qty reali per location
3. cleanupObsoleteBringItems troppo rara:
- sessionStorage → una sola volta per sessione
- Fix: localStorage con TTL 30 minuti
- Ora rimuove da Bring qualsiasi item che ha scorte in inventario
e NON è flaggato come critical/high dalla spesa intelligente