Two bundled UI fixes. 1. Tools page Run / destructive buttons — the base recipe in tools.css (rgba green/red 0.12 bg + 0.30 border + full-saturation text) reads muddy against Nebula's cosmic gradient, same readability problem .install-btn / .uninstall-btn had before the nebula overrides bumped them to 0.35/0.65 with --text-primary text. .tool-run-btn and its .destructive variant now ride those same overrides so Run pops as green-tint and the dangerous variant pops as red-tint, both with neutral text against the gradient. 2. Services tab row — the "Logs" button now reads "Details" because that's what it actually toggles (meta + rich detail + log toggle). The data-action moves from toggle-logs to toggle-details, and the expanded panel no longer auto-opens a log stream. A small footer "Show logs" / "Hide logs" toggle at the bottom of the open panel explicitly opts in to tailing, kicking off the existing SSE stream on click (auto-updates while shown). Closing the parent details panel also resets the log block back to its hidden state so the next reopen starts clean. app-tabbed-manager's task-running button disable was taught about the new actions so they stay clickable while a long task is running. Signed-off-by: librelad <librelad@digitalangels.vip>
Custom Themes
Drop a folder here named after your theme and refresh the browser — the topbar dropdown will pick it up automatically.
Minimum
frontend/themes/<your-name>/
theme.css (required) — palette inside a [data-theme="<your-name>"] block
meta.json (optional) — { "displayName": "Pretty Name", "author": "you" }
The folder name (<your-name>) becomes the data-theme value. Use
lowercase + dashes — no spaces, no slashes.
Tokens
Copy frontend/themes/example/theme.css as a starting point. Every CSS
variable defined there is what style.css and friends consume — change
the values, keep the names.
The visible-essentials list (override these at minimum):
| Token | What it controls |
|---|---|
--surface-bg |
Body background (gradient or solid) |
--text-primary |
Default text colour |
--accent |
Theme's signature colour (manage btn) |
--accent-rgb |
Same colour as r,g,b for rgba() mixes |
--text-rgb |
255,255,255 (dark) or 0,0,0 (light) |
--bg-rgb |
The opposite of --text-rgb |
Status colours (--status-success, --status-danger, --status-warning,
--status-info) are usually safe to leave as the defaults — they're
brand-stable across themes.
How discovery works
On page load the frontend calls GET /api/themes/list. The backend walks
this directory, returns one entry per folder containing a theme.css, and
the frontend (js/system/theme-registry.js) injects each entry's CSS
into <head> and adds it to the topbar dropdown. The currently-saved
theme is <link>-loaded synchronously by the inline bootstrap in
index.html so first paint has the right palette — no flash.
The built-in themes (nebula, dark-blue, light) live in this folder
too — their meta.json files set "builtin": true so a future UI can
distinguish them (e.g. a "reset to built-ins" affordance). If you delete
a built-in folder you'll remove it from the dropdown; the app survives
but the deleted theme is gone until you put it back.
The dropdown orders themes by:
nebula,dark-blue,light(built-ins, in that fixed order)- Everything else, sorted by display name.