Commit Graph

414 Commits

Author SHA1 Message Date
github-actions[bot] 9b83acdef6 chore: auto-merge develop → main
Triggered by: 1fb00d4 fix(shopping): prevent cleanup from removing user-manually-added items
2026-05-04 16:36:26 +00:00
dadaloop82 1fb00d48a9 fix(shopping): prevent cleanup from removing user-manually-added items
- cleanupObsoleteBringItems now protects items the user explicitly added
  from the suggestions panel via a '_userPinnedBring' localStorage set
  (30-day TTL, cleared on force-sync)
- cleanup now protects ALL smart-predicted items (any urgency), not only
  critical/high — if the algorithm still flags it, it should stay in list
- autoAddCriticalItems: bypass purchased-blocklist for depleted items
  (current_qty=0) so products that ran out are always re-added to Bring
- forceSyncBring also clears _userPinnedBring for a full reset
2026-05-04 16:34:34 +00:00
github-actions[bot] 922e633ec9 chore: auto-merge develop → main
Triggered by: 108f3ef feat(settings): add screensaver toggle in Language tab (default off)
2026-05-04 16:19:21 +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
github-actions[bot] d4d1aca774 chore: auto-merge develop → main
Triggered by: bc0beea fix(header): full redesign — left-aligned title, uniform buttons, update badge
2026-05-04 16:14:28 +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
github-actions[bot] 56584e07df chore: auto-merge develop → main
Triggered by: 04cba79 chore: update GitHub issue reporter token
2026-05-04 16:08:18 +00:00
dadaloop82 04cba79519 chore: update GitHub issue reporter token 2026-05-04 16:06:12 +00:00
github-actions[bot] 5cb1799d1d chore: auto-merge develop → main
Triggered by: e68d11a fix(pwa): handle orientation.lock() promise rejection silently
2026-05-04 15:48:11 +00:00
dadaloop82 e68d11a7fc fix(pwa): handle orientation.lock() promise rejection silently
screen.orientation.lock() returns a Promise; the old try/catch only
caught synchronous errors, leaving the rejection unhandled and triggering
the auto-reporter (issue #8). Added .catch(()=>{}) to suppress it.
Also fixed CI: add sleep+retry around gh release create to avoid 502
race condition after delete.

Closes #8
2026-05-04 15:46:17 +00:00
github-actions[bot] 74a60f5bbf chore: auto-merge develop → main
Triggered by: 3649be8 feat(kiosk): add screensaver toggle in settings (default off)
2026-05-04 15:44:27 +00:00
dadaloop82 3649be848a feat(kiosk): add screensaver toggle in settings (default off) 2026-05-04 15:42:51 +00:00
github-actions[bot] 644fa2b94f chore: auto-merge develop → main
Triggered by: fa9c52e feat(kiosk): complete setup wizard overhaul
2026-05-04 15:42:31 +00:00
dadaloop82 fa9c52e997 feat(kiosk): complete setup wizard overhaul
- New SetupActivity (5 steps, NOT kiosk-locked, always has Exit button)
  - Step 0: Welcome — logo, tagline, privacy/offline notice, feature list
  - Step 1: Permissions — explain camera/mic/install before requesting
  - Step 2: Server — URL input + LAN auto-discovery + fixed API validation
  - Step 3: Scale — YES/NO question first; gateway info + install only after YES
  - Step 4: Done — summary + Launch button
- KioskActivity simplified: removed all wizard code (~700 lines)
  - kiosk lock only enabled AFTER setup completes
  - starts SetupActivity on first run; launches WebView on return
- activity_kiosk.xml simplified: removed wizard ScrollView
- AndroidManifest: added SetupActivity declaration
- Fixed testConnection(): validates /api/index.php?action=get_settings JSON
- Fixed scale freeze: scale YES/NO asked before any connection check
- Fixed gateway install status=1: same PackageInstaller flow + signature conflict dialog
- Auto-discovery: parallel scan of LAN (30 threads, ports 80/8080, common subnets)
- i18n: new setup strings in en/it/de
2026-05-04 15:40:45 +00:00
github-actions[bot] b06beb23be chore: auto-merge develop → main
Triggered by: f625e55 fix: header 3-col layout, shopping_name migration, demo mode UI, kiosk buttons left
2026-05-04 12:54:08 +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
github-actions[bot] 04efbe29b3 chore: auto-merge develop → main
Triggered by: 77c2bd5 docs: update README with AI features, security hardening, new .env keys
2026-05-04 06:24:59 +00:00
dadaloop82 77c2bd59a7 docs: update README with AI features, security hardening, new .env keys 2026-05-04 06:23:18 +00:00
github-actions[bot] 13e88bc5b8 chore: auto-merge develop → main
Triggered by: bf27469 security: fix 3 critical vulnerabilities
2026-05-04 06:22:04 +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
github-actions[bot] 60c1f406cc chore: auto-merge develop → main
Triggered by: 529c09f feat(ai): 3 new AI features — product storage hint, shopping tips, anomaly explain
2026-05-04 06:03:40 +00:00
dadaloop82 529c09fda3 feat(ai): 3 new AI features — product storage hint, shopping tips, anomaly explain
Feature 1: AI product storage/shelf-life hint
- New API: gemini_product_hint → {location, expiry_days, reason}
- After opening the add form, Gemini suggests optimal storage and expiry
- Shown inline next to expiry estimate as a subtle AI badge with tooltip
- Also updates location buttons if AI suggests a different location
- Cached permanently in food_facts_cache.json (per name+lang)

Feature 2: AI-enriched shopping suggestions
- New API: gemini_shopping_enrich → adds tip field to each suggestion
- After bring_suggest renders, Gemini adds practical buying/storing tips
- Tips shown inline under each suggestion item in indigo italic text
- Cached per item list + lang in food_facts_cache.json

Feature 3: AI anomaly explanation
- New API: gemini_anomaly_explain → plain-language explanation
- '🤖 Spiega' button added to anomaly banners (when Gemini available)
- Explains in 2-3 conversational sentences why the discrepancy likely happened
- Replaces technical banner detail text with friendly explanation
- No caching (anomaly context is always specific)
2026-05-04 06:01:44 +00:00
github-actions[bot] 1b7fe58769 chore: auto-merge develop → main
Triggered by: a85390b feat(ai): guard all Gemini features when API key is not configured
2026-05-04 05:52:14 +00:00
dadaloop82 a85390b498 feat(ai): guard all Gemini features when API key is not configured
Added _geminiAvailable global flag (false by default):
- Set in _initApp() from serverSettings.gemini_key_set after app loads
- Updated in syncSettingsFromDB() so it stays current if key is added later

Added _requireGemini() helper:
- Returns true if Gemini key is configured → proceed normally
- Returns false + shows a warning toast if key is missing → abort

Added _updateGeminiButtonState():
- Adds .header-btn-no-ai CSS class to Gemini button when key is missing:
  greyed out, slight grayscale filter, amber dot badge in corner
- Updates button tooltip to explain what to do
- Removes class/restores normal appearance when key is present

All 6 AI entry points now call _requireGemini() as first line:
  captureForAI()          — AI product identification from scan page
  captureForAIFormFill()  — AI product fill in manual add form
  scanExpiryWithAI()      — AI expiry date reader
  openRecipeDialog()      — recipe generation dialog
  generateRecipe()        — recipe generation (direct call path)
  quickRecipeSuggestion() — quick expiring-products recipe (→ chat)
  showPage('chat')        — Gemini chat page

Previously: user would click the button, camera would open, API call
would fail, and only THEN see an error message deep in the flow.
Now: blocked immediately at the entry point with a clear toast.
2026-05-04 05:50:30 +00:00
github-actions[bot] 9e078c9930 chore: auto-merge develop → main
Triggered by: d635635 fix(ui): update notification inline in header title area, no full-page banner
2026-05-04 05:43:20 +00:00
dadaloop82 d635635577 fix(ui): update notification inline in header title area, no full-page banner
Instead of a fixed banner that covers the top of the page, the update
notification now replaces only the header title area (the centered title):
- .header-title content is swapped in-place with an animated pill:
    ⬆️ v1.x.x  [Aggiorna]  ✕
- Pulsing animation (header-update-pulse) draws attention without being
  intrusive; camera and Gemini buttons stay exactly where they are
- [Aggiorna] button does window.location.reload()
- [✕] dismisses: for a release update stores publishedAt so it won't reappear;
  for a server deploy simply restores title (reappears next 5-min check)
- Auto-restores after 60 s without marking as seen
- Removed the old fixed position:fixed banner entirely
2026-05-04 05:41:38 +00:00
github-actions[bot] bb77395a3a chore: auto-merge develop → main
Triggered by: 968e26c fix(ui): header title always centered, actions to right, real-time update detection
2026-05-04 05:34:37 +00:00
dadaloop82 968e26cc6a fix(ui): header title always centered, actions to right, real-time update detection
CSS header fixes:
- .header-content: justify-content:flex-end so .header-actions (camera, Gemini)
  naturally stays at the right edge as a flex child
- .header-title: removed overflow:hidden and text-overflow:ellipsis that were
  clipping the version number; title stays absolutely centered
- Cleaned up unused max-width:none and margin:0 from previous broken attempt

Real-time webapp update detection:
- Added module-level _loadedVersion captured at page load (version in HTML header)
- _checkWebappUpdate() now has two checks:
  1. webapp_version from server vs _loadedVersion: if different, the server was
     updated since this page was loaded → show '🔄 Nuova versione disponibile' banner
  2. GitHub latest release vs _loadedVersion (existing behaviour)
  Different banner messages: deploy-changed shows simple reload prompt;
  release-newer shows version + changelog link (same as before)
- TTL reduced from 6h to 5 min so updates are detected quickly
- _checkWebappUpdate() now also fires on visibilitychange so the user sees
  the banner as soon as they return to the tab after a deploy
2026-05-04 05:32:57 +00:00
github-actions[bot] 84229a4345 chore: auto-merge develop → main
Triggered by: 6756b16 fix(ui): center header title on tablet + add skeleton loader to spesa stat card
2026-05-04 05:28:11 +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
github-actions[bot] e12a1ebde1 chore: auto-merge develop → main
Triggered by: c6e3d13 fix(api): bringAddItems() missing $input/$items decode — always returned 0
2026-05-04 05:22:36 +00:00
dadaloop82 c6e3d13e8c fix(api): bringAddItems() missing $input/$items decode — always returned 0
bringAddItems() used $input and $items without ever decoding the request
body. $items was undefined (null) so the foreach never ran, every call
returned added=0 skipped=0 regardless of what was sent.

Added:
  $input = json_decode(file_get_contents('php://input'), true) ?? [];
  $items = $input['items'] ?? [];
Also added the missing $auth guard (consistent with all other Bring functions).
2026-05-04 05:21:07 +00:00
github-actions[bot] 03bd0bb321 chore: auto-merge develop → main
Triggered by: 7c61ae6 fix(kiosk): fix ByteArray type inference error in APK magic-byte check
2026-05-03 20:15:57 +00:00
dadaloop82 7c61ae61bb fix(kiosk): fix ByteArray type inference error in APK magic-byte check
The try expression had a spurious 'true' result in one branch which
made Kotlin infer the type as Any? instead of ByteArray?.
Simplified to a single try block with explicit type annotation
ByteArray? to eliminate the ambiguity.
2026-05-03 20:14:19 +00:00
github-actions[bot] 653746e913 chore: auto-merge develop → main
Triggered by: 22e506b fix(kiosk): 4 bug fix — uninstall loop, PHP check, APK validation, ErrorReporter init
2026-05-03 20:12:17 +00:00
dadaloop82 22e506bd66 fix(kiosk): 4 bug fix — uninstall loop, PHP check, APK validation, ErrorReporter init
Bug 1 — Uninstall loop (kiosk lock task blocks system uninstall UI):
  startActivityForResult(ACTION_DELETE) was called while lock task was
  active. The system uninstall activity is not in the lock task whitelist
  so it either silently fails or creates an unresolvable loop.
  Fix: call disableKioskLock() immediately before every ACTION_DELETE
  intent (3 call sites). Call enableKioskLock() at the start of
  onActivityResult(UNINSTALL_REQUEST) before retrying install.
  Added 600 ms delay after uninstall so PackageManager finishes cleanup.

Bug 2 — Step 2 only checks HTTP connectivity, not PHP API:
  testConnection() was checking the root URL only. A generic web server
  could pass while the EverShelf PHP API was absent.
  Fix: after HTTP 200-399 on the root URL, do a second GET to
  /api/?action=check_update and check the response body contains
  'latest_tag'|'webapp_version'|'ok'. Shows:
     Server EverShelf trovato e API attiva!
    ⚠  Server raggiungibile ma API PHP non trovata (codice N)

Bug 3 — STATUS_FAILURE=1 even after uninstall (invalid APK file):
  GitHub DownloadManager follows redirects; if the release asset does
  not exist yet, GitHub returns a 404 HTML page but DownloadManager
  still reports STATUS_SUCCESSFUL. PackageInstaller then tries to parse
  HTML as an APK and returns STATUS_FAILURE=1.
  Fix: validate APK magic bytes (0x504B = 'PK') before calling
  installWithPackageInstaller. If invalid: show error, delete corrupt
  file, send ErrorReporter event, re-enable retry button.
  Also renamed install error string to install_error_install (separate
  from install_error_download) for clarity.

Bug 4 — ErrorReporter.serverBaseUrl empty during wizard install:
  ErrorReporter.init() is called in onCreate() with the saved URL.
  On first setup the URL is typed in step 2 and saved to prefs, but
  ErrorReporter still has serverBaseUrl='' for the rest of that session.
  Any install error in step 3 silently failed to POST.
  Fix: call ErrorReporter.init(this, url) in btnStep2Next immediately
  after prefs.edit().putString(KEY_URL, url) so step 3 has a live URL.
2026-05-03 20:10:40 +00:00
github-actions[bot] 7d134f561a chore: auto-merge develop → main
Triggered by: 38eb66c feat(kiosk): server reachability check in step 3 + uninstall-on-generic-failure
2026-05-03 19:37:18 +00:00
dadaloop82 38eb66cfbf feat(kiosk): server reachability check in step 3 + uninstall-on-generic-failure
Server check (wizard step 3):
- New horizontal card above the scale question always shows server status
  as soon as step 3 is entered:  checking →  reachable / ⚠️ not reachable
- Pings GET $serverUrl/api/?action=check_update (5 s timeout)
- If reachable: 'Error reporting active — failures sent to GitHub Issues'
- If not reachable: 'Check the URL in step 2' warning
- checkServerReachability() called every time goToStep(3) runs
- Strings added in EN / IT / DE

Signature-conflict fallback (else branch in installWithPackageInstaller):
- When PackageInstaller returns a generic STATUS_FAILURE and the target
  package is already installed, a signature conflict is the most likely
  cause (CONFLICT/INCOMPATIBLE are caught separately earlier)
- New AlertDialog: 'Disinstalla e riprova' → startActivityForResult
  ACTION_DELETE → UNINSTALL_REQUEST → auto-retries install on return
- Only shown when all else has already failed
2026-05-03 19:35:20 +00:00
github-actions[bot] a2b1a6f2cf chore: auto-merge develop → main
Triggered by: 15e1dfb fix(kiosk): STATUS_FAILURE=1 (wrong package) + issue version-guard bypass
2026-05-03 19:30:40 +00:00
dadaloop82 15e1dfbd69 fix(kiosk): STATUS_FAILURE=1 (wrong package) + issue version-guard bypass
Bug 1 — Root cause of PackageInstaller STATUS_FAILURE=1:
  The dest file is always named 'evershelf-update.apk'. installApk()
  was trying to detect 'gateway' in the filename — always false.
  So setAppPackageName() was always passed 'it.dadaloop.evershelf.kiosk'
  even when installing the gateway APK (package scalegate).
  PackageInstaller rejects the mismatch with STATUS_FAILURE=1.

  Fix: save apkUrl into pendingApkDownloadUrl at the TOP of
  triggerApkDownload() (not only in the permission branch), then derive
  targetPkg from the URL (which does contain 'gateway'/'scale') instead
  of the filename.

Bug 2 — Install errors not reaching GitHub Issues:
  PHP reportError() has a version guard: if the client version is not
  the latest release, it silently skips GitHub issue creation.
  A device that is FAILING TO INSTALL an update is by definition on an
  old version, so every install error was silently dropped.

  Fix: bypass the version guard for types install_download_failed,
  install_failure, install_packager_exception.
2026-05-03 19:29:04 +00:00
github-actions[bot] f26b9c291d chore: auto-merge develop → main
Triggered by: 645162f fix(kiosk,gateway): use RECEIVER_EXPORTED for DownloadManager broadcast
2026-05-03 19:21:21 +00:00
dadaloop82 645162f063 fix(kiosk,gateway): use RECEIVER_EXPORTED for DownloadManager broadcast
Root cause of 'stuck on downloading' bug (Android 13+):

DownloadManager.ACTION_DOWNLOAD_COMPLETE is sent by the system process,
which is external to our app. Registering the receiver with
RECEIVER_NOT_EXPORTED silently drops the broadcast — the BroadcastReceiver
never fires, the install never starts, and the UI stays frozen at
whatever progress percentage the poller last saw.

Fix: use RECEIVER_EXPORTED for the DownloadManager completion receiver in
both kiosk and scale-gateway apps.

The PackageInstaller result receiver (internal PendingIntent broadcast,
same package) correctly keeps RECEIVER_NOT_EXPORTED — that one is
intentionally app-private.
2026-05-03 19:19:44 +00:00
github-actions[bot] 7cb39d94b6 chore: auto-merge develop → main
Triggered by: 0dac10d fix(kiosk): fix compile error — ErrorReporter.report() → reportMessage()
2026-05-03 19:14:11 +00:00
dadaloop82 0dac10d05e fix(kiosk): fix compile error — ErrorReporter.report() → reportMessage()
ErrorReporter.report() takes a Throwable as first argument.
The three new calls added in fe633c9 incorrectly passed 'this'
(Context) instead, causing compileDebugKotlin to fail.

Replace with ErrorReporter.reportMessage(type, message) which
is the correct overload for non-exception error events.
2026-05-03 19:12:42 +00:00
github-actions[bot] 8865131557 chore: auto-merge develop → main
Triggered by: fe633c9 fix(kiosk): real-time download progress bar + ErrorReporter on failures
2026-05-03 19:08:59 +00:00
dadaloop82 fe633c97cb fix(kiosk): real-time download progress bar + ErrorReporter on failures
Problem: tapping 'Aggiorna Scale Gateway' gave no visible feedback after
the button was pressed — user could not tell if the download was
happening, stuck, or had silently failed.

Changes:
- layout: add horizontal ProgressBar (determinate) + percentage TextView
  inside the wizard step-3 status card
- layout: add thin ProgressBar (4 dp) at the bottom of the update banner
  (banner changed to vertical orientation to accommodate it)
- startDownloadProgressPoll(downloadId): polls DownloadManager every
  500 ms, reads COLUMN_BYTES_DOWNLOADED_SO_FAR and COLUMN_TOTAL_SIZE_BYTES,
  updates status card + banner with 'Download: 45% (18.2 MB / 40.5 MB)'
- setInstallUI(): new 'progress' parameter (-2 = hide, -1 = indeterminate,
  0-100 = determinate) and 'progressText' for the label under the bar
  — both bars updated in sync
- Status transitions now visible:
     Download: 45% [====----] 18.2 MB / 40.5 MB
     Installazione in corso… [~~~~] (indeterminate)
     + 'Conferma nel dialog…'
     Installato! → bar hidden, gateway status re-checked after 3 s
     + error detail → bar hidden, button re-enabled as '↩ Riprova'
- All error paths (download fail, PackageInstaller exception, installer
  failure status) now call ErrorReporter.report() → GitHub Issue created
  automatically so failures are tracked without user intervention
- Dismiss button also cancels the progress poll + hides the bar
2026-05-03 19:07:19 +00:00
github-actions[bot] fe7aca43ab chore: auto-merge develop → main
Triggered by: 7d8132a fix(kiosk): persistent install progress in UI — no more silent Toasts
2026-05-03 18:56:14 +00:00
dadaloop82 7d8132a743 fix(kiosk): persistent install progress in UI — no more silent Toasts
Problem: tapping 'Aggiorna ora' showed a fleeting 'Download avviato'
Toast and then nothing — no feedback on download progress, installer
state, success or failure.

Solution — setInstallUI() central helper:
- Updates the wizard step-3 status card (icon + title + detail line)
  OR the update banner (tvUpdateMessage) depending on which is visible
- Always updates and enables/disables the button that triggered the flow

States shown (status card + button text):
   Scaricamento in corso…  (download started)
   Installazione in corso… (download done, PackageInstaller running)
   Installazione in corso… + 'Conferma nel dialog…' (user action needed)
   Installato con successo! (onActivityResult RESULT_OK or STATUS_SUCCESS)
     → after 3 s auto-refreshes gateway status + closes banner
   Download fallito / Installazione non riuscita
     → button re-enabled as '↩ Riprova'

Strings added (EN default + IT + DE):
  install_downloading, install_downloading_detail
  install_installing, install_confirm_detail
  install_success, install_success_detail
  install_error_download, install_error_download_detail
  install_perm_detail, install_btn_retry
2026-05-03 18:54:27 +00:00
github-actions[bot] 4b27f249e3 chore: auto-merge develop → main
Triggered by: 03f201c ci: auto-merge develop → main after all checks pass
2026-05-03 18:49:58 +00:00
dadaloop82 03f201c651 ci: auto-merge develop → main after all checks pass
New job 'auto-merge-to-main' in ci.yml:
- needs: lint-php, lint-js, docker-build, validate-translations
- only runs when github.ref == refs/heads/develop
- uses git merge --no-ff so history is preserved
- push to develop → CI passes → main updated automatically
2026-05-03 18:48:20 +00:00