A background task like the updater check surfaces its result in the page it was
launched from (the Updates/Overview tab repaints itself), yet it still fired the
full "View Task" pair — "Check task started!" then "Check task completed!" —
on top of the launcher's own small "Checking apps for updates…" line. Three
notifications for something you have no reason to open the Tasks page for.
Add a small, generic classifier (LP_BACKGROUND_TASKS) keyed by task action/type
(with a command regex that also catches the backend `updater check auto` run).
For a classified task:
- executeTask() skips the standard "task started!" toast and, when the run is
hand-launched, records its id as pending.
- the taskCompleted handler skips the standard "task completed!" toast; if the
id was pending (user launched it) it shows one small plain result line
("Apps checked for updates & vulnerabilities."), otherwise stays silent so
the periodic auto-scan makes no noise at all.
Non-background tasks are unaffected — the standard task-notification style still
applies. Extend byAction to quiet another self-surfacing action.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
In the config grid (e.g. Backups > Configuration > Engine) toggle pills sat
~6px below and 2px taller than the text/select controls beside them, so they
read as misaligned. Two causes, both scoped to .config-fields:
- .field-group carried a vestigial margin-bottom:6px (the grid's own 16px gap
already spaces rows). It inflated the grid track, so the align-self:end
toggle landed 6px below the input controls. Zeroed inside the grid.
- The .checkbox-label pill's 24px switch + 10px vertical padding rendered 46px
vs the inputs' 44px. Trimmed to 9px padding so the pill is 44px.
Toggles now share the exact vertical band (top + height) as the inputs in
their row. Applies to every config-grid toggle, not just the backup engine.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On /admin/config/general the Basic section's fields sit in the darker inset
".config-fields" box, but the Mail Enabled toggle and Installation Mode select
did not — their control sits in its own .mail-master-toggle / .git-master-toggle
wrapper, a sibling of .config-fields, which the boxing rule never covered. So a
gated section's enable row read as loose, unboxed content above the (hidden)
options.
Extend the boxed-panel rule to the master-toggle wrappers (mail / install-mode /
generic / backup-remote). Element-qualified to the wrapper <div> because
renderToggleSection stamps the same class on the inner .checkbox-label pill too,
which must not get its own nested box.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
When the official catalog is hidden, its "Re-enable" action rendered as a
flat, greyed-out ~27px pill pinned to the top-left of the row — shorter than
the 44px URL field and not obviously clickable (the generic .btn-secondary
loses its nebula-theme polish outside .config-actions, so it fell back to an
unstyled base button).
- The action column now align-self:stretch, so its control matches the URL
field's height and sits inline (also vertically centres the compact × in the
enabled state).
- The Re-enable button gets a dedicated .catalog-reenable-btn treatment: accent
fill + accent border + hover state + an eye icon, filling the row height so it
lines up with the input and clearly reads as a button.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Backups tab's Configuration sub-tab showed only the warning banner and
an orphaned divider -- the actual config form (retention presets, engine,
keep-policies, Save/Reset) never appeared.
Root cause: a duplicate id. The Fleet Overview, the app grid and per-app
detail all render the shared apps-unified-layout, which keeps a hidden
#config-tab > #config-section in the DOM. The Backups Configuration sub-tab
created its OWN #config-section, and configManager.renderConfig() resolved
the target with getElementById('config-section') -- which returns the FIRST
(hidden) node in document order. So the 21KB form painted into the invisible
per-app container while the backup tab's container stayed empty. Because the
/backup routes now redirect into /apps/overview/backups, this fired every
time -- the form had effectively never been visible.
Fix:
- renderConfig(category, target) now accepts an explicit container (element
or id) and falls back to #config-section; the error-retry button re-targets
via configSection.id.
- The Backups Configuration sub-tab uses a unique id (#backup-config-section)
and passes it to renderConfig; retention-preset and engine-details
selectors are scoped to it.
Also: the Refresh button now spins + locks while refreshAll()+render() run,
so the click has visible feedback instead of appearing to do nothing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The global Backups tab rendered its snapshot list (and the "No backups
yet." empty state) as a bare flex column directly on the tab panel, so
the list section had no visual container -- unlike the Dashboard tab's
cards and the per-app Backups tab's recessed panel. Wrap it in a
.backup-snapshot-panel box (translucent fill + hairline border, matching
.backup-card) so the section reads as a bounded surface.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The embedded backup center deep-links *in* fine (mountBackupCenter parses the
sub-segment), but clicking a sub-tab never wrote the URL: BackupPage disables
pushTabToUrl() when embedded and nothing on the fleet side stepped in, so the
address bar stayed stale and the sub-tabs weren't shareable/deep-linkable —
unlike the sibling Migrate sub-tabs.
Give BackupPage an onTabChange hook fired on every switch; the OverviewManager
supplies it to replaceState /apps/overview/backups/<sub> (replaceState, matching
Migrate, so the on-entry deep-link re-sync can't stack a duplicate history
entry). Standalone /backup keeps using pushTabToUrl() unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The badge centres on the input box (top:50%), but the single-line input renders
its text a hair low, so the pill read as slightly above the URL. +2px lands it
optically inline.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Feedback: the removal warning was an overwhelming paragraph and made it look like
updates might break. Keep the official catalog removable, but present the effects
as a clear two-part list instead of prose:
✓ Still works: system updates & hotfixes, installed apps, re-enabling any time
✕ What changes: official apps hidden from the App Center "Available" list
Backed by a new optional messageHtml arg on the shared confirmation dialog (dev-
controlled HTML body) + styles for the effect groups. Required tickbox kept.
Signed-off-by: librelad <librelad@digitalangels.vip>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
formatSubcategoryName title-cased naively, so section headers read "Ssh",
"Dns", "Libreportal". Add a whole-word acronym/brand map so they render
professionally as "SSH", "DNS", "LibrePortal" (also covers VPN/API/IP/UI/URL/
HTTP(S)/TLS/SSL/WebUI for future files). Composes with stripCategoryPrefix,
which runs after and is case-insensitive.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
- Info banner: drop the borrowed traefik warning banner (icon sat above the
text, amber-warning styling) for a .catalog-note — icon inline with the text,
neutral boxed look matching the content sections.
- Official row (#1): render the release-host URL in a read-only input (same
element/height as the third-party rows) with the "✓ Official" badge overlaid,
instead of short text — so it shows the URL and its box height matches the
others. URL is read from the loaded config (CFG_RELEASE_BASE_URL).
Signed-off-by: librelad <librelad@digitalangels.vip>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Config sections (Basic, Mail, DNS, Firewall, …) rendered as a bare name. Add a
small data-driven icon before each: config files now carry a `# @icon <emoji>`
directive in their header, the config generator surfaces it as the subcategory's
`icon`, and the config editor prepends it to the section <h3> (covers every
render path — regular/master/toggle/domains/catalogs/whitelist — since they all
drop displaySubcategory straight into the heading).
- webui_generate_configs.sh: parse `# @icon` (first 10 lines) → emit "icon".
- config-manager.js: prepend subcategoryData.icon to the section title.
- configs/**: added an @icon to all 23 base config files (backup/general/network/
security/webui). The live install's config files get the same directive + a
configs.json regen so existing installs show the icons too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
- Catalog block now shows the official catalog as a pinned row #1 (verified
badge + its URL from the generated data). Removing it opens the shared
confirmation dialog with a REQUIRED tickbox (generalized to accept custom
checkbox text) — it only hides official apps from browsing; system updates are
unaffected. Backed by a new CFG_CATALOG_OFFICIAL_ENABLED flag the scan honors.
- Trimmed the info-banner wording per feedback.
- Config option sections: the body (after the divider, before the buttons) now
sits in a darker boxed panel (bg + border), matching the content-box look used
elsewhere. Applies to regular grids + the block editors; scoped to the main
config editor.
Signed-off-by: librelad <librelad@digitalangels.vip>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the Catalog Sources block to the config editor (General → App Catalogs),
mirroring the domains block UI: add/remove up to 9 catalog URL slots, saved by
the generic config-form (any CFG_* input) → configUpdateBatch, which rebuilds
registry_catalog.json. Official catalog stays source #1 (not editable here);
third-party rows carry an "unverified" note.
- new catalog-manager.js (lean CatalogManager; reuses the domains block CSS,
URL inputs, no DNS checks) + window.addCatalog/deleteCatalog globals.
- config-manager.js routes the general_catalogs subcategory to it.
- system-loader.js loads catalog-manager.js before config-manager.js.
Signed-off-by: librelad <librelad@digitalangels.vip>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On the app-details Config tab, switching main tabs re-renders the config form
(showAppDetail → displayConfigForm runs on every tab switch). A re-render race
let the enhancer wrap a <select> that was already enhanced, nesting a second
.custom-select inside the first — so the dropdown showed twice, one on top of
the other (reproduced: CFG_<APP>_BACKUP_STRATEGY / _COMPOSE_FILE ended up with
two nested wrappers). The per-element ENHANCED symbol didn't catch it.
Harden the guard with two DOM-based checks (which survive any symbol loss/race):
skip a <select> that already carries the .custom-select-native class or already
sits inside a .custom-select wrapper — in both shouldEnhance() and the
constructor. Port-manager selects were unaffected (they regenerate their
container each render); this fixes the plain form-select fields.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Switching to the fleet Backups tab blanked the whole pane to a bare, headerless
lpLoadingBox() while the (script-heavy) backup center loaded, so it read as the
entire section reloading with no indication of where you'd landed — then the
header + content appeared at once when the fetch resolved.
Render the shared .ov-tab-header (💾 Backups + description + divider) up front and
drop the spinner into an .ov-tab-body beneath it, matching the per-app detail
tabs' header-first idiom (and the recent Admin board fix). The header stays put
and only the content area swaps in once BackupPage mounts.
The Migrate tab already does this — mountMigrate() injects its header and the
sub-tab strip is static, so its spinner is already confined to the Restore/Peers
sub-panel — so no change was needed there.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Removes the zero-app confirmation dialog added earlier today — it wasn't
needed. Zero-app installs proceed straight through again. The other setup
fixes (finalize rolls up per-app results, watcher gates the hand-off on
whole-group success, dev-mode auto-enable skipped during setup handoff)
are unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Three coupled defects in the first-run wizard flow, all surfacing as
"it said complete but nothing was set up":
1. Zero-app installs sailed through. With no apps ticked, setup was just
config+finalize, finished in seconds having installed nothing, and
fast-forwarded to an empty App Center. Add a self-contained in-wizard
confirm ("Install with no apps?") before submitting. Can't reuse the
shared confirmation-dialog component — it isn't loaded this early in
boot — so the dialog is rendered by the wizard itself and mounted on
<body> to escape the aurora surface's FX-stacking rule.
2. finalize declared success unconditionally. It never inspected the
per-app install tasks, so a failed app still yielded "your install is
ready" + a redirect. Pass the setup group id to `setup finalize`; it
now rolls up the group's app-install task results and logs a clear
partial/failed verdict. The WebUI completion watcher gates the welcome
toast + App Center hand-off on the whole group succeeding, not just on
finalize completing — a failed app now keeps the user on the tasks page
with an error toast instead of a false all-clear.
3. Setup task count was confusing (banner "of 2" while the page listed 3).
On dev/git installs the topbar's dev-mode auto-enable raced the wizard's
own CFG_DEV_MODE write and spawned a second, un-grouped config-update
task mid-setup. Skip that auto-enable while a setup handoff is active
(the wizard already persists CFG_DEV_MODE); it runs on the next load if
still needed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The Admin Dashboard (OverviewPage) and Admin System (SystemPage) boards each
blanked the whole content pane to a bare headerless lpLoadingBox() on init(),
then rendered the .page-header + panel only once data arrived. So clicking
either tab flashed a full-pane spinner with no indication of which page you'd
landed on, then everything appeared at once.
Extract the page header into a _headerHtml() helper on each page and render it
up front in both the loading state and render(). Now navigating to a board
paints its header (breadcrumb + title + description) instantly and the spinner
is confined to the .admin-panel beneath it — the header stays put and only the
content swaps in when the fetch resolves.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The setup-in-progress banner floats over arbitrary page content (the aurora
gradient, apps grid, task logs) but filled at rgba(--bg-rgb, 0.45) with no
backdrop blur, so the page bled through and it looked washed-out. Mirror the
.notification treatment already used for floating toasts: a near-solid panel
(0.85) over a frosted backdrop, plus a lift shadow. Stays theme-aware via
--bg-rgb (near-black on dark themes, near-white on light).
Signed-off-by: librelad <librelad@digitalangels.vip>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the extra on-card marketplace tags (the separate Official trust
badge) with a richer, more elegant flow: a registry card keeps a single
'Available' pill, and clicking Add (or the card) opens a modal outlining the
app — full description, publisher + trust, version, the add command, and a
'View full page on the marketplace ↗' link to the app's page on the source
it came from — with the actual add as the confirm. Gives a beat to review a
community app before pulling it in.
- webui_registry_scan.sh re-emits source{base,channel}; loadApps stashes it
(window.registryCatalogSource) + carries per-app version.
- openRegistryDetails() builds the eo-modal; addRegistryApp() is now just the
task dispatch (the modal's confirm / a fallback).
- The marketplace website supports a #<slug> deep-link (focus one app, with a
'Show all' bar), so the modal's link lands on that app's page.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Reverts a4e65df. The maintainer prefers the more fluid model where registry
apps flow into the App Center grid as 'Available — Add' cards alongside
installed/local apps, rather than a separate /apps/marketplace destination.
Restores the Stage-4 merged-grid behaviour (loadApps merges
registry_catalog.json; createAppCard renders registry cards; addRegistryApp
dispatches app_add). The make_app.sh featured passthrough + the generator's
featured/source fields go with it (unused by the merged grid).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Splits the catalog out of the main grid into its own destination, the
WordPress 'Add Plugins' vs 'Installed Plugins' model: the grid is now
purely 'your apps' (local definitions), and the new Marketplace section is
'get more apps' (the remote signed catalog).
- New MarketplacePage (components/apps/marketplace/) mounts at
/apps/marketplace inside the apps feature (same sub-dispatch pattern as
/apps/overview — no new top-level component). Pinned sidebar entry with a
live 'available to add' count badge.
- Status strip: signed/unsigned, available + catalog counts, serial, source
base+channel, freshness, and a Refresh that re-runs the host-side registry
scan via updater_check.
- Publisher-curated Featured shelf (meta.featured, set at publish time — no
tracking/popularity), category chips + search, per-app detail modal
(long description, publisher/trust/version, add command), and the chained
Add & set-up flow: dispatch app_add, and when the definition lands, hand
off to the app's config/install page.
- State-aware cards: Available (Add) / Added (Set up →) / Installed (Open).
- Backend: make_app.sh passes through meta.featured; webui_registry_scan.sh
emits featured + source{base,channel} in registry_catalog.json.
- Removed the grid's registry-merge + registry card path + its CSS (moved to
the namespaced marketplace surface); app_add task wiring + completion
handler retained and reused.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
- .tabs-list now rounds its own top corners (12px, matching .tab-navigation);
the first/last .tab-button radii were invisible against the bar's square
background on the Backups/Migrate/Config sub-tab strips.
- All five fleet tabs share one rhythm under the header divider: a single
16px gap, no extra inset — .ov-tab-body drops its recessed margin/padding
box, and the Backups/Migrate sub-tab strips gain the matching gap.
- The Peers list keeps its recessed panel via .peers-body (in-card recipe),
no longer riding the flattened .ov-tab-body.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The embedded backup center's fragment kept its standalone-page skeleton
(.container + .sidebar restyled by a pile of overview.css overrides).
.container's fixed viewport height inflated the Backups tab pane to
~100vh, stretching the pane surface far past the content and under the
footer buttons, and the restyled strip never matched the app-detail
Config sub-tabs.
Rebuild backup-content.html on the canonical sub-tab idiom instead —
.tabs-wrapper > .tabs-list (emoji tab-buttons) + .tabs-content card,
with a .backup-actions footer below the card mirroring .config-actions.
The bespoke overview.css restyle block, the nebula special-cases, the
embed's id-stripping and BackupPage's dead page-header updater all fall
away; the export menu now opens upward from the footer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
containers/marketplace — nginx:alpine app (standard drop-in contract:
config + tagged compose + icon + install hook) whose docroot serves BOTH
halves of the marketplace: the signed catalog channel tree (index.json /
payloads, published into data/<channel>/ by the release tools) and a
self-contained client-rendered browse site over the same file (search,
category chips, trust badges, copyable 'libreportal app add <slug>' —
no third-party assets, no backend, no build step). The official
marketplace is an instance of this app; self-hosting one = installing it
and pointing CFG_RELEASE_BASE_URL at it. Boxes only ever trust the
minisign signature on the catalog, never the website.
New generic gating convention: CFG_<APP>_DEV_ONLY=true keeps an app out
of the App Center grid unless Developer Mode is on (CFG_DEV_MODE, the
same flag the **DEV** config-field filter uses); an installed dev-only
app always stays visible. The marketplace app is the first user.
Cache policy: catalog/channel manifests no-cache; payloads short
revalidating cache (same-id re-publish); version-pinned release
artifacts immutable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The grid now also reads apps/generated/registry_catalog.json (optional
data: any failure = no registry cards, never a broken grid) and renders
signed-catalog apps that have no local definition as "Available — Add"
cards: indigo Available pill, teal Official trust badge, and an Add button
that dispatches the app_add task (task-actions/router/format wired). Local
definitions always win on a slug collision; registry cards sort last and
are excluded from the Installed view. No detail page until Add lands the
definition — then the card becomes a normal Install card, repainted by the
app_add branch in the task-completion handler without a manual refresh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Prepend a boxed app icon (tasks-notification style) to each row of the
System page's Per-app usage table. Multi-instance slugs (<type>_<id>)
resolve to the base type's icon; missing icons fall back to default.svg.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Pages and panels showed inconsistent loading states: the Backup center
and several admin pages (System, SSH, admin Overview), the Overview
Migrate/Peers panels, the per-app updater section and the backup engine
details modal rendered a bare 'Loading…' text line (updater-empty /
backup-empty-state) with no spinner, while Services/Config/Tasks used a
boxed card + spinner.
Add one shared loader — window.lpLoadingBox(message) + .lp-loading CSS in
the core/loading subsystem (the boxed card + accent spinner the good tabs
already use) — and route those bare-text loaders through it. The system
metric graph keeps its absolute overlay but gains the same spinner.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
'Checking for improvements… this usually takes a minute.' implied an active
scan finishing imminently. The scan is periodic and runs in the background
(CFG_UPDATER_SCAN_INTERVAL, default 30m), so reword to reflect that. Kept the
cadence generic since the interval is admin-configurable and not exposed here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
'No installed apps to track.' was terse and gave no next step. Point the user
to the App Center so the empty Updates tab is actionable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The custom-select enhancer only matched select.form-control /
select.theme-selector, so dropdowns rendered by the per-app config form
(config-form.js uses form-select / form-input / config-input), the app
tools form, the port-manager grid (port-* classes) and the instance
domain picker stayed as plain native OS dropdowns. The LibrePortal app's
Theme option is one of these.
Add those classes to ENHANCE_CLASSES, give the classless instance domain
select a form-control class, and add a compact button override so the
themed dropdown matches the dense port-manager input metrics.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
'No hotfix data yet — the automatic scan fetches the signed improvements index
within a couple of minutes.' leaked jargon (hotfix data, signed index) onto the
interface. Replace with a short, plain 'Checking for improvements…' line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Empty-state 'no data yet' messages rendered at rgba(text, 0.55) (and one at
text-muted), which is hard to read on the dark recessed panels. Switch them to
the theme's --text-secondary muted token so they stay de-emphasized but legible.
Covers .updater-empty, .updater-detail-empty, .sys-detail-empty, .eo-modal-empty.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The last task tile's 10px bottom margin stacked on the list's 16px bottom
padding, leaving a larger gap below the last row than above the first.
Zero the last tile's bottom margin so the dark panel reads symmetric.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Clicking Reclaim space fired two notifications: routeAction → executeTask
already shows the rich 'Reclaim Space task started!' toast (icon + bold
LibrePortal + task link), then _reclaim added a second, plain, iconless
'Reclaiming space…' info toast on top of it. The image-removal path doesn't
double-notify like this. Drop the redundant one — the start toast plus the
completion toast give clean feedback on their own.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
.sys-detail-loading and .sys-detail-empty are absolutely-positioned overlays
that JS shows/hides via the `hidden` attribute. Their .sys-detail-* rule sets
`display: flex`, an author declaration that overrides the UA
`[hidden] { display: none }` — so `el.hidden = true` never actually hid them.
Both the 'Loading history…' and 'No samples in this range yet' overlays stayed
painted on top of a fully-populated chart (overlapping into garbled text).
Add `.sys-detail-loading[hidden], .sys-detail-empty[hidden] { display: none }`
(higher specificity than the bare class) so the hidden attribute wins.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The donut legend collided into the stat cards and the image-row details
were squished on tablet widths — the 220px sidebar leaves the content
cramped while the old breakpoints assumed full-viewport width.
- Headline collapses to a single column at <=1024px (was 800px), the two
stat cards reflow side-by-side, and on phones the donut stacks above its
legend with one stat per row. Legend labels now ellipsis instead of
overflowing into the stats.
- Image rows group the name+pill and the size/shared/age metadata so the
metadata drops onto its own line under the name at <=1024px instead of
competing for width; on phones the Delete button collapses to an icon.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The Images list on /admin/system/storage now has an All / In use / Unused
segmented filter (with live per-group counts), and the default All view
sorts in-use images to the top — the ones you can't reclaim lead, the
reclaimable ones follow. Select all / Clear All act on the visible rows,
so they honour the active filter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
App-less system tasks (verify, regen, …) resolved to an empty displayName
and null icon in _taskNotificationDescriptor, so their completion toast
rendered an empty <strong></strong><br> — a blank bold line that showed as
a random gap above the message — and had no icon, unlike every other
notification. Treat any task with no app slug as a system task so it gets
the 'LibrePortal' subject and libreportal.svg icon.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The .sys-tasklist panel spaces rows with a flex gap, but each .task-item
also carries margin-bottom: 10px from the shared tasks styles. That margin
only stacks on the last row, so the list had ~8px above the first row and
~18px below the last row, looking lopsided. Zero the row margin inside the
list so spacing is symmetric.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The Services tab's loading placeholder (.services-loading) was a bare
centered row with no container chrome, unlike the Config and Tasks tab
loading cards. Give it the same boxed look (semi-opaque black fill,
hairline border, rounded corners, margin and min-height) so it reads as
a deliberate loading panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>