diff --git a/containers/libreportal/frontend/components/apps/core/css/apps.css b/containers/libreportal/frontend/components/apps/core/css/apps.css index c20b42a..9fbf4aa 100644 --- a/containers/libreportal/frontend/components/apps/core/css/apps.css +++ b/containers/libreportal/frontend/components/apps/core/css/apps.css @@ -190,6 +190,18 @@ line-height: 1; } +/* Improvements (hotfix) chip — amber, links to Updates & Improvements. Only + shown when signed, app-scoped, applicable hotfixes exist for this app. */ +.app-tag.improvements-chip { + background: rgba(var(--status-warning-rgb, 245, 158, 11), 0.30); + color: #fcd34d; + border-color: rgba(var(--status-warning-rgb, 245, 158, 11), 0.65); + transform: translateY(-2px); +} +.app-tag.improvements-chip:hover { + background: rgba(var(--status-warning-rgb, 245, 158, 11), 0.45); +} + /* Not Installed tags - Gray with a leading ✕ glyph for symmetry with the ✓ on the installed pill. */ .app-tag.not-installed-tag { diff --git a/containers/libreportal/frontend/components/apps/core/js/apps-manager.js b/containers/libreportal/frontend/components/apps/core/js/apps-manager.js index b8fbe33..f097ab8 100755 --- a/containers/libreportal/frontend/components/apps/core/js/apps-manager.js +++ b/containers/libreportal/frontend/components/apps/core/js/apps-manager.js @@ -472,6 +472,32 @@ class AppsManager { + // Secondary surface for the hotfix channel (fork 3 locality): show a small chip + // on an installed app's detail header when signed, app-scoped hotfixes are + // available for it. The canonical home is the Updates & Improvements page; this + // just links there. Read-only, best-effort, never throws into the render path. + async _populateImprovementsChip(appName) { + try { + const chip = document.getElementById('app-improvements-chip'); + if (!chip) return; + const r = await fetch('/data/updater/generated/artifacts_available.json', { cache: 'no-store' }); + if (!r.ok) return; + const data = await r.json(); + const list = (data && Array.isArray(data.artifacts)) ? data.artifacts : []; + const n = list.filter(a => a && a.app === appName && a.applicable && !a.applied).length; + if (n <= 0) return; + chip.textContent = `⚡ ${n} improvement${n > 1 ? 's' : ''}`; + chip.title = 'Signed hotfixes are available for this app — open Updates & Improvements'; + chip.style.cursor = 'pointer'; + chip.style.display = ''; + chip.onclick = () => { + if (typeof window.navigateToRoute === 'function') window.navigateToRoute('/updater/improvements'); + else if (typeof window.spaClean === 'function') window.spaClean('/updater/improvements'); + else window.location.href = '/updater/improvements'; + }; + } catch (_) { /* best-effort */ } + } + async renderAppDetail(appName, preferredCategory = null, appChanged = true, opts = {}) { //// // console.log(`🎯 renderAppDetail called with: "${appName}", appChanged: ${appChanged}`); //// // console.log(`🎯 Available apps:`, window.apps?.map(a => ({ name: a.name, command: a.command }))); @@ -588,6 +614,7 @@ class AppsManager {
@@ -612,6 +639,7 @@ class AppsManager { } this.wireShowWhenListeners(); this.wireConfigDirtyTracking(cleanAppName); + if (app.installed) this._populateImprovementsChip(cleanAppName); // Only update service buttons if app has changed or installed status changed if (shouldRenderHeader) { this.updateServiceButtonsSidebar(app, cleanAppName); diff --git a/containers/libreportal/frontend/components/tasks/js/tasks-format.js b/containers/libreportal/frontend/components/tasks/js/tasks-format.js index ae9a8ef..06e9d58 100644 --- a/containers/libreportal/frontend/components/tasks/js/tasks-format.js +++ b/containers/libreportal/frontend/components/tasks/js/tasks-format.js @@ -29,6 +29,8 @@ Object.assign(TasksManager.prototype, { { match: /^libreportal updater apply-all\b/, title: 'Apps - Update All' }, { match: /^libreportal updater apply (\S+)/, title: (m) => `${displayName(m[1])} - Update` }, { match: /^libreportal updater rollback (\S+)/, title: (m) => `${displayName(m[1])} - Roll Back` }, + { match: /^libreportal artifact apply (\S+)/, title: (m) => `Hotfix ${m[1]} - Apply` }, + { match: /^libreportal artifact revert (\S+)/, title: (m) => `Hotfix ${m[1]} - Revert` }, // -- Peers ------------------------------------------------------------- { match: /^libreportal peer add\b/, title: 'LibrePortal - Add Peer' }, @@ -138,6 +140,7 @@ Object.assign(TasksManager.prototype, { 'system_image_rm': 'Remove Images', 'verify': 'Verify System', 'updater_check': 'Check for Updates', 'updater_apply': 'Update', 'updater_apply_all': 'Update All', 'updater_rollback': 'Roll Back', + 'artifact_apply': 'Apply Hotfix', 'artifact_revert': 'Revert Hotfix', 'setup-config': 'Apply Configuration', 'setup-finalize': 'Finalize Setup' }; diff --git a/containers/libreportal/frontend/components/tasks/js/tasks-list-render.js b/containers/libreportal/frontend/components/tasks/js/tasks-list-render.js index 1fde72a..e995833 100644 --- a/containers/libreportal/frontend/components/tasks/js/tasks-list-render.js +++ b/containers/libreportal/frontend/components/tasks/js/tasks-list-render.js @@ -313,6 +313,8 @@ Object.assign(TasksManager.prototype, { 'updater_apply': { icon: '⬆️', class: 'update' }, 'updater_apply_all': { icon: '⬆️', class: 'update' }, 'updater_rollback': { icon: '↩️', class: 'restore' }, + 'artifact_apply': { icon: '⚡', class: 'update' }, + 'artifact_revert': { icon: '↩️', class: 'restore' }, 'custom': { icon: '⚙️', class: 'custom' } }; diff --git a/containers/libreportal/frontend/components/updater/html/updater-content.html b/containers/libreportal/frontend/components/updater/html/updater-content.html index 0f1d228..87a9889 100644 --- a/containers/libreportal/frontend/components/updater/html/updater-content.html +++ b/containers/libreportal/frontend/components/updater/html/updater-content.html @@ -20,6 +20,12 @@ Updates +