From 4a95b4c41e9fefc64b9b74be6b56c633d888d570 Mon Sep 17 00:00:00 2001 From: librelad Date: Tue, 18 Aug 2026 21:00:45 +0100 Subject: [PATCH] fix(webui): make the app detail category tag clickable and its icon legible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The category pill on the app detail header was rendered inert — no click handler at all — while the identical pill on the app cards navigated to that category's filter view. Both now come from one AppsManager.renderCategoryTag(), so the detail pill behaves like the card pill and the two can't drift again. The pill's glyph was an of a category SVG, and those SVGs hardcode #1e90ff. That only ever matched the dark-blue theme; on nebula (the default, accent #00d4ff) and any other theme the icon read as a dark smudge next to its own label. It's now painted as a CSS mask filled with currentColor, so it always matches the pill's text on every theme. Co-Authored-By: Claude Opus 5 --- .../components/apps/core/css/apps.css | 14 +++++++++++++ .../components/apps/core/js/apps-grid.js | 6 +----- .../components/apps/core/js/apps-manager.js | 21 ++++++++++++++----- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/containers/libreportal/frontend/components/apps/core/css/apps.css b/containers/libreportal/frontend/components/apps/core/css/apps.css index 394fde8..f8f1dd7 100644 --- a/containers/libreportal/frontend/components/apps/core/css/apps.css +++ b/containers/libreportal/frontend/components/apps/core/css/apps.css @@ -163,6 +163,20 @@ transform: translateY(-1px); } +/* The category glyph is drawn as a mask filled with the pill's own text + colour. The category SVGs ship a hardcoded #1e90ff, which disappears into + the pill on any theme whose accent isn't that blue — masking keeps icon and + label the same colour on every theme. --cat-icon is set inline per tag. */ +.category-tag-icon { + width: 12px; + height: 12px; + flex-shrink: 0; + display: inline-block; + background-color: currentColor; + -webkit-mask: var(--cat-icon) center / contain no-repeat; + mask: var(--cat-icon) center / contain no-repeat; +} + /* Description tags - White to match title */ .app-tag.description-tag { background: rgba(var(--text-rgb), 0.1); diff --git a/containers/libreportal/frontend/components/apps/core/js/apps-grid.js b/containers/libreportal/frontend/components/apps/core/js/apps-grid.js index 4502f78..ab09c95 100644 --- a/containers/libreportal/frontend/components/apps/core/js/apps-grid.js +++ b/containers/libreportal/frontend/components/apps/core/js/apps-grid.js @@ -181,13 +181,9 @@ Object.assign(AppsManager.prototype, { const status = app.installed ? 'Installed' : 'Not Installed'; - // Get category icon and name - const categoryIcon = this.getCategoryIcon(app.category); - const categoryName = this.getCategoryName(app.category); - // Create rich tags like original const descriptionTag = app.description ? ` ${app.description}` : ''; - const categoryTag = ` ${categoryName}`; + const categoryTag = this.renderCategoryTag(app.category); // Format long description with period if missing let formattedLongDescription = ''; 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 2329184..e381e23 100755 --- a/containers/libreportal/frontend/components/apps/core/js/apps-manager.js +++ b/containers/libreportal/frontend/components/apps/core/js/apps-manager.js @@ -678,14 +678,12 @@ class AppsManager { } const status = app.installed ? 'Installed' : 'Not Installed'; - const categoryName = this.getCategoryName(app.category); - const categoryIcon = this.getCategoryIcon(app.category); - + // Create tags matching app center style const installedTag = app.installed ? `${status}` : `${status}`; - const categoryTag = ` ${categoryName}`; + const categoryTag = this.renderCategoryTag(app.category); // Render app header section (always define, but only update DOM if app changed) const headerHTML = `
@@ -879,7 +877,20 @@ class AppsManager { return category ? category.name : categoryId; } - + // The category pill (icon + name), shared by the grid cards and the app + // detail header so both click through to that category's filter view. + // The icon is painted as a CSS mask filled with currentColor rather than a + // plain : the source SVGs hardcode #1e90ff, which reads as a dark + // smudge inside the pill on any theme whose accent isn't that blue. As a + // mask it always matches the pill's own text colour. + renderCategoryTag(categoryId) { + const name = this.getCategoryName(categoryId); + const iconPath = this.getCategoryIcon(categoryId); + const icon = iconPath + ? `` + : ''; + return `${icon}${name}`; + } // Check if a service is installed checkServiceInstalled(serviceName) {