262 Commits

Author SHA1 Message Date
librelad
25dc51d63e fix(webui): make Overview sub-tab areas match the app-detail layout
Migrate and Backups detached their sub-tab strip from the content (16px gap)
and Backups had no top-level header and a transparent (card-less) body — so
both read as different from the per-app Config sub-tabs.

- Migrate: nest .tabs-content inside the same .tabs-wrapper as .tabs-list (the
  canonical app-detail structure) and drop the gap, so the strip joins the
  content card with no space and clean corners.
- Backups: inject the shared "Backups" .config-title header above the embedded
  BackupPage; drop the sub-tab strip's bottom gap; give the content (.main) the
  connected .tabs-content surface (card bg + rounded bottom) and round the
  strip's outer top corners — so it reads as one tabs-within-tabs unit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 00:23:10 +01:00
librelad
4d54d6a9b0 feat(webui): unify Overview tab headers as in-content, app-detail style
Drop the floating fleet header; every Overview tab now renders its heading
inside the pane, under the tab strip, in the per-app detail .config-title
format (emoji + title + description) — matching the Migrate tab.

Introduces a small modular system so the area can't drift:
- OV_TAB_META is the single source of truth for each tab's icon/title/blurb.
- renderHeader(id) is the only thing that turns it into markup; renderTab()
  prepends it for the string tabs and mountMigrate() injects it once for the
  static Migrate pane. Body renderers now only ever produce the body.

Retires the now-dead floating-header plumbing: updateHeader(), the
ov-backups-active/ov-migrate-active hide toggles + CSS, and the .overview-header
rules. The tabbed interface owns the top padding the header used to provide.

Backups is the documented exception — it embeds the full BackupPage, which
supplies its own header.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 23:29:16 +01:00
librelad
f6f29bf68b feat(webui): match Migrate tab to app-detail tab design
Give the fleet Overview › Migrate tab the same in-content header + sub-tab
styling the per-app detail tabs use:

- Add a .config-title header (icon + title + description) inside the Migrate
  pane, above the Restore/Peers sub-tabs, and hide the generic floating fleet
  header for Migrate (mirrors how Backups already supplies its own heading).
- Make the .tab-button icon↔label gap explicit (flex + 6px gap) so sub-tabs
  render identically whether or not the markup has whitespace between the
  emoji and name spans; align the Backups sub-tab strip gap to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 23:04:44 +01:00
librelad
9986a8a814 fix(webui): stop rapid clicks selecting text on setup level cards
The Beginner/Advanced experience cards are tap targets, but had no
user-select guard. Rapid clicking — notably the Advanced card's 10-tap
dev-mode unlock — selected the card title/description text as a side
effect. Add user-select: none to .setup-level-card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-06-02 16:21:39 +01:00
librelad
f1e1330cd8 feat(webui): network-drift notifier (topbar badge + dashboard banner)
Surfaces network_status.json in the WebUI, attention-only: a rose badge
+ dashboard banner appear ONLY when conflicts_found, with a details panel
listing the stranded apps and a 'Heal now' button that runs the heal
through the task pipeline (libreportal system network heal). Re-reads on
task completion via taskRefresh.

Cloned from update-notifier; the badge anchors just after the update
badge so the two coexist in a stable order. New --page-network hue.
Wired into system-loader scripts[], topbar onTopbarReady, and index.html.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 16:07:54 +01:00
librelad
164f782a95 fix(webui): address Migrate-refactor review findings
- Restore empty-state 'Open Locations' now deep-links to the backup center's
  Locations sub-tab: the embedded center honors /overview/backups/<sub> and
  switchTab()s to it after mount (was landing on Dashboard).
- PeersPage.notify + MigratePage.notify use the real window.notificationSystem
  (were calling a never-defined window.showNotification → console-only).
- Remove the now-dead Admin config-manager peers branch (Peers left Admin).
- Trim the dead migrate.json/peers fetch + hostnameToPeerName from BackupPage
  (no consumer after the migrate removal).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 11:12:11 +01:00
librelad
69bb5532b7 refactor(webui): move Peers out of Admin; harmonize Backups sub-tab strip
- Remove the Admin sidebar Peers entry; /peers and /admin/tools/peers now
  redirect to /overview/migrate/peers (its new home next to cross-host Restore).
- Re-skin the embedded Backups center's sub-tab strip from pills to the per-app
  Config .tabs-list/.tab-button segmented look (full-width bar, accent underline)
  so every nested sub-tab row is consistent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 10:46:30 +01:00
librelad
25e25230fd fix(webui): drop deleted backup-migrate.js from the embedded center asset list
The embedded backup center's lazy bundle still listed backup-migrate.js, which
was just removed — its 404 failed the whole load chain. Drop it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 10:39:36 +01:00
librelad
c449641b9c refactor(webui): remove Migrate from the backup center (moved to Overview)
Migrate now lives at Overview › Migrate › Restore (standalone MigratePage). Strip
it out of BackupPage: drop the migrate sidebar item/panel/modal from the
fragment, the 'migrate' tab from the allowed set / titleFor / subtitleFor /
iconFor, the renderMigrate() call, and the migrate-host/app/confirm click
handlers; delete the now-orphaned backup-migrate.js. The backup center is now
Dashboard/Backups/Locations/Configuration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 10:37:46 +01:00
librelad
ebdae15838 feat(webui): deep-link the Migrate sub-tabs (/overview/migrate/{restore,peers})
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 10:28:58 +01:00
librelad
4a964c42a2 feat(webui): add Migrate fleet tab (Restore + Peers sub-tabs)
New 5th Overview tab 'Migrate' with a nested segmented sub-tab row reusing the
per-app Config-tab .tabs-list/.tab-button design:
- Restore: a standalone MigratePage (cross-host migrate moved out of BackupPage
  into its own controller + fragment + modal; own data fetch + task dispatch).
- Peers: reuses the existing PeersPage (container-parameterized) + its template.
Both lazy-loaded on first open and disposed on apps-feature unmount. Additive —
migrate is still in the backup center and Peers still in Admin until the next
commits remove the duplicates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 10:24:54 +01:00
librelad
efdbed8e0c fix(webui): embedded backup-center review fixes
- BackupPage task-refresh run-guard is instance-relative now (window.backupPage
  OR window.overviewBackupPage), so the embedded center's own auto-refresh works
  instead of relying on OverviewManager's overlapping coverage.
- _ensureBackupAssets no longer memoizes a rejected promise — a transient
  script-load failure no longer bricks the backup center until reload; the next
  open retries.
- spaClean.loadScript removes the failed <script> element on error so the
  getElementById dedupe can't make a retry resolve without re-fetching.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 01:55:09 +01:00
librelad
f1f0cf7516 fix(webui): scope embedded backup-center mount check to the pane
The 'already mounted?' guard checked #backup-section, but the per-app Backups
tab also defines that id — detect the prior mount via the pane's own
.backup-layout instead so the check is correct, not coincidental.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 01:40:29 +01:00
librelad
c508a20605 feat(webui): embed the full backup center in the Overview Backups tab
Instead of a glance + 'Open backup center' button, the Backups tab now mounts
the real BackupPage (dashboard/snapshots/locations/migrate/configuration) inline,
with its sidebar restyled as a nested tab strip and its own header taking over.

- BackupPage gains an embedded mode (opts.embedded): no /backup URL coupling, so
  sub-tabs switch in-page under /overview/backups. Backward compatible.
- OverviewManager lazy-loads the backup bundle + fragment on first open, news a
  BackupPage({embedded:true}), and disposes it on apps-feature unmount. Colliding
  ids (#sidebar/#mobile-overlay) are stripped on inject.
- Revert the Admin backup-config surface — the embedded center (incl.
  Configuration) is now the single home for backup settings.
- The updater needs no equivalent: its sections were already unpacked into the
  Overview/Updates/Improvements tabs + the per-row expander.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 01:36:25 +01:00
librelad
d5e2375f38 fix(webui): address review findings on the fleet Overview build
- HIGH: renderAppDetail gated the Roll back button on last_snapshot* fields no
  generator emits, so it never rendered. Derive recoverability from update
  history (updater_apply always snapshots first) so the affordance is reachable.
- MED: per-app Updates tab now repaints on update/rollback/check/hotfix task
  completion (mirrors the backups card) instead of going stale until re-click.
- MED: in-page tab switches now sync spaClean.currentRoute, so the sidebar
  Overview entry no longer no-ops after switching tabs.
- LOW: keyboard activation (Enter/Space) for the role=button expander heads,
  backup tiles, and sidebar Overview entry.
- LOW: preserve ALL expanded Updates rows across a background repaint, not just
  the single ?app= deep-link (split toggle into _openDetail/_closeDetail).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 00:37:59 +01:00
librelad
dfd4ffa268 feat(webui): drop Updates & Backups top-nav items; fold into App Center
The fleet Overview area (Overview · Updates · Improvements · Backups) and the
backup center now live under App Center, so the standalone top-nav items are
redundant. Top nav is now Dashboard · App Center · Admin · Tasks.

- Remove the Backups and Updates anchors from topbar.html.
- Remove the nav{} blocks from updater/backup feature.json + manifest (so they
  don't resurface when the nav kernel lands).
- Highlight App Center for /overview and /backup; drop the dead /updater branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 00:10:30 +01:00
librelad
c7ae1414b9 feat(webui): redirect /updater into Overview; surface backup config in Admin
- _legacyRedirect: /updater[/tab] -> /overview[/tab] (security/recovery/history
  fold into the Updates expander -> /overview/updates). /backup is intentionally
  NOT redirected — it stays the operational backup center (locations/migrate/
  snapshots), reached from Overview › Backups.
- Re-point the per-app hotfix chip to /overview/improvements.
- Unhide the existing backup config category in the Admin sidebar so
  engine/schedule/retention config lives under Admin (same generated category
  the backup center binds, so edits stay in sync).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 00:01:59 +01:00
librelad
1460acb941 feat(webui): add per-app Updates tab (version/CVEs/recovery/history)
New 'Updates' tab in the app detail page, beside Backups. Reuses the headless
UpdaterPage + renderAppDetail() scoped to the single app, so the per-app and
fleet views share one data/render path. UpdaterPage is added to the apps script
bundle so it's available on app pages; the tab is disabled while a task runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 23:54:21 +01:00
librelad
dbc5e64505 feat(webui): deep-link auto-expand for Overview Updates rows
Open the per-app row named by ?app=<name> on load/repaint and write it back on
toggle, so an expanded Updates row is a shareable URL — mirrors the Tasks page's
?task=<id> pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 23:46:34 +01:00
librelad
8acf2d02c3 feat(webui): add fleet Overview area (Overview/Updates/Improvements/Backups tabs)
Introduce a per-fleet Overview area inside the apps shell, reachable from a new
'Overview' entry pinned above the apps sidebar search. Selecting it renders a
top-tabbed view in the main pane — Overview · Updates · Improvements · Backups —
mirroring the per-app tabbed layout, with the apps sidebar persistent.

- TabController: generic root-scoped show/hide tab host (core/ui-state).
- OverviewManager: drives the 4 tabs. Reuses a headless UpdaterPage for all
  update/CVE/improvement data + rendering (its renderX() are pure HTML
  producers) and reads backup/dashboard.json directly for backup health.
- Overview tab: combined update + backup health cards.
- Updates tab: per-app expander table (CVEs/recovery/history inline via the new
  UpdaterPage.renderAppDetail) + All/Updates/Security filter chips.
- Improvements tab: reuses the updater's signed-hotfix renderer.
- Backups tab: fleet backup-health tiles; actions deep-link per app.
- Additive only: /overview* routes on the apps feature; old /updater and
  /backup pages untouched. Cleanup (redirects, nav, Admin config move) is next.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 23:37:35 +01:00
librelad
79d2a4750d feat(webui): Phase 4 — Improvements (hotfix) stream + per-app chip
Surfaces the hotfix channel in the WebUI. Primary home is the Updates &
Improvements page (the updater component) — its own "Improvements" tab — with a
secondary chip on the App detail page (fork 3 locality = both).

Updater component (components/updater):
- New "Improvements" sidebar tab + panel; renderImprovements() reads the host-
  generated artifacts_available.json (severity badge, scope chip, applied/auto/
  not-applicable badges, plain-English why). Apply/Revert buttons dispatch
  artifact_apply / artifact_revert through the TASK system (services.tasks.route)
  — no mutating API. Apply is disabled when the index is UNSIGNED.
- Overview gains an "Improvements" stat card; task-refresh now also repaints on
  artifact_* task completion; URL tab routing + dispose teardown extended.

Task plumbing (core/tasks): artifactApply/artifactRevert action methods (id is
charset-guarded before it enters the command string) + artifact_apply/
artifact_revert routeAction cases. Task list/format gain icons + friendly labels.

Apps component: an amber " N improvements" chip on an installed app's detail
header (populated async from artifacts_available.json filtered by app, applicable
& not-applied), linking to /updater/improvements. Best-effort, never throws.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 21:07:01 +01:00
librelad
306e6223c0 fix(webui): release leaked listeners/intervals/streams on unmount (all modules)
The teardown audit found the backup-stacking leak class across 4 more feature
modules (12 confirmed leaks); unmount() left document/window listeners, intervals,
and SSE subscriptions firing on stale controllers after navigation:

- admin: overview/ssh/peers/system each leaked a document click listener ->
  AbortController + dispose() per page; admin unmount() aborts each.
- dashboard: the 1 Hz update-countdown interval + the LiveSystem view sub ->
  stopUpdateCountdown()/detachDashboardLive(), registered via ctx.sub().
- tasks: constructor-started global live-log poller (discarded handle) -> stored
  + idempotent + cleared on unmount + re-armed on mount; per-task monitorTask
  window listeners + interval -> tracked in a map, released on unmount.
- apps: app-tabbed reconcile setTimeout loop + watchdog window/document listeners
  + popstate -> per-instance AbortController + dispose() that clears the timer,
  resets the guards, and unloads the active tab's Services intervals + log SSE.

All mirror the kernel's MountContext teardown discipline. 12 files, all pass
node --check. Backup (fixed earlier) re-confirmed clean by the audit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 15:27:29 +01:00
librelad
7709b0d97b fix(backup): dispose document listeners on unmount (sidebar stacking bug)
The original report: clicking a backup sidebar tab loaded content on top of
the old content. Root cause (flagged in the unmount comment as deferred):
BackupPage.bindEvents() attaches document-level click/input/change listeners
guarded only by the instance-level this.eventBound, and unmount() nulled
window.backupPage WITHOUT removing them. Each revisit added another full set of
listeners bound to a stale BackupPage, all firing on every click and mutating
the live DOM (double tab-switches, double modal opens, stale-instance renders).

Fix (mirrors the kernel's MountContext pattern): give BackupPage an
AbortController, bind the three document listeners to its signal, add dispose()
that aborts them (+ drops the task-refresh reg + clears the timer), and call it
from the feature module's unmount(). Revisits now start clean — one live
instance, one set of listeners.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 14:48:54 +01:00
librelad
4e18a6ff42 fix(webui): render App-Updater tasks as standard tasks in the panel
updater_check/apply/apply_all/rollback tasks fell through every per-type
branch of the Tasks panel, so they showed the generic custom gear icon, a
raw/truncated command title, and (for the app:'updater' sentinel) a broken
hidden app icon. Wired them in like every other task type:

- tasks-format.js formatCommandForUser PATTERNS: added the 'libreportal updater'
  command rows (Apps - Check for Updates / Update All / <App> - Update /
  <App> - Roll Back) — only the *self*-update 'libreportal update' was mapped.
- tasks-format.js formatActionTitle: added the updater_* short labels.
- tasks-list-render.js getTaskTypeIcon: updater_check 🔍 / apply ⬆️ /
  apply_all ⬆️ / rollback ↩️ (reusing existing verify/update/restore classes).
- tasks-list-render.js renderTaskIcons: treat app:'updater' as a sentinel like
  app:'system' so updater_check/apply_all fall back to the LibrePortal logo
  instead of a 404'd /core/icons/apps/updater.svg (apply/rollback keep their
  real app icon).

node --check clean.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 02:53:57 +01:00
librelad
bb0d750b55 chore(webui): remove two orphan icons
Verified-dead assets from the feng-shui audit, zero consumers:
- core/icons/categories/utils.svg — no 'utils' app category exists (the only
  'utils' refs are unrelated system health-check names); category icons are
  requested as /core/icons/categories/<id>.svg and no id is 'utils'.
- core/icons/apps/portainer.svg — Portainer was retired to
  scripts/unused/OLD_CONTAINERS/; no live containers/portainer/, and apps.json
  is generated only from live containers, so the icon is never requested.

Both git-recoverable if a portainer app / utils category is ever (re)added.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 01:34:51 +01:00
librelad
c22b0ac60d chore(webui): strip ~665 commented-out console.* debug lines
The shipped frontend carried ~600 muted '// console.…' debug statements (and
their multi-line commented continuation lines) left over from development —
clutter across 30 files. Removed them with a guarded pass that ONLY ever deletes
lines starting with // (so it can never alter behaviour), consuming each
commented console opener plus its continuation comment lines until the
string-stripped parens balance.

665 lines removed, 30 files; 0 insertions. Verified every deleted line is a //
comment (no code touched), real prose comments preserved, full node --check
sweep clean.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 01:25:41 +01:00
librelad
c920ca2dc9 refactor(webui): align page-controller names with the -page convention
From the feng-shui audit naming findings:
- admin/overview/js/admin-overview.js -> overview-page.js (class AdminOverview ->
  OverviewPage, window globals + the 'admin-overview' task-refresh id ->
  overview-page, lazy-load path + typeof/new in config-manager.js).
- admin/system/js/admin-system.js -> system-page.js (class AdminSystem ->
  SystemPage; now sits beside its -page sub-views system-metric-page.js /
  system-storage-page.js).
- tasks/js/tasks-logs-modal.js -> tasks-log-modal.js (singular 'log' to match its
  sibling tasks-log-stream.js; single path ref in system-loader.js).

These were the only page controllers breaking the dominant <thing>-page.js /
<Thing>Page convention (ssh-page/peers-page/backup-page/updater-page/
system-metric-page/system-storage-page). Pure renames; node --check clean.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 01:21:07 +01:00
librelad
afe0ef1c7e chore: drop duplicate doc files + fix wrong/stale comments
- docs: remove the docs/README.md index and docs/CONTRIBUTING.md pointer
  (duplicate filenames); the canonical contributing guide stays at
  docs/contributing/contributing.md. Clean tree, no name collisions.
- scripts/system/*: 6 helper headers + host_access.sh said the helpers
  install to /usr/local/sbin, but init.sh installs all of them to
  /usr/local/lib/libreportal/ (verified via initRootHelpers + the sudoers
  Cmnd_Alias). Corrected. The only remaining /usr/local/sbin is the legit
  PATH export in the task processor.
- frontend kernel: drop migration-era comments that are now false post-
  modularization (feature-registry 'passive/phase 0/unused', lifecycle
  'ctx.services lands with Phase 2', manifest 'scan generator lands') —
  describe current behaviour instead.

Comment-only edits to scripts/system/* — no footprint_version bump (no
behavioural change; bumping would force needless reinstalls).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 01:05:16 +01:00
librelad
30612a0d87 docs: organize docs/ into purpose folders with consistent naming
Sort docs/ into guide/ contributing/ architecture/ roadmap/ and rename
to consistent kebab-case (USER->guide/install-and-use, FOOTPRINT->
architecture/system-footprint, frontend-modularization->architecture/
webui-architecture, etc.). Add a docs/README.md index and a docs/
CONTRIBUTING.md pointer so the forge still surfaces the contributing
guide. Fix every reference (README, init.sh comments, frontend code
comments, and the USER<->DEVELOPMENT cross-links). History preserved
via git mv. Root stays README.md + CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 00:48:38 +01:00
librelad
164606dc7c docs(webui): refresh stale features/ path comments after the components/ rename
Comment-only tidy from the feng-shui audit — no code behavior changes. The
features/ directory was renamed to components/ during modularization, but
several header banners and inline comments still named the old path:

- 6 component module headers (admin/tasks/backup/dashboard/updater/index.js +
  updater/js/updater-page.js) now name their real components/<id>/… path
- core/kernel/js/spa.js + core/tasks/js/task-router.js comments
- backend/routes/features.js doc-banner (drop a components/<id>/ folder …)
- core/update-notifier/css/update-notifier.css header (js/update-notifier.js)

Guarded the rewrite so the LIVE /api/features/list endpoint name (feature-
registry.js sources + backend route) is untouched — only stale 'features/<path>'
directory references were updated.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 00:41:16 +01:00
librelad
123f04b03e Merge claude/2 2026-05-31 00:39:40 +01:00
librelad
19909b91e0 docs: rewrite frontend-modularization as a lean as-built reference
The modularization shipped (2026-05-30), so the design doc was stale and
internally contradictory: it described a features/ tree (real tree is
components/), a shell-generator/Node route that were never built, and a
'partially implemented' status. Replace the 59KB design exploration with
a short, accurate description of the component-module system as it exists
(components/<id> pages, core/ subsystems, the kernel: feature-registry/
services/lifecycle/spa, static manifest discovery, mount/unmount contract,
eager global CSS). Fix one stale features/ path in a spa.js comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 00:39:12 +01:00
librelad
8d86855098 fix(webui): category-icon fallback + flatten forms/ + manifest order
From the feng-shui audit (all adversarially verified):

- BUG (high): apps-grid.js category tiles used onerror fallback
  /core/icons/categories/default.svg, which doesn't exist (the dir has
  misc.svg as its generic icon, which data-loader.js already uses). Any
  category missing its named SVG showed a broken-image glyph. Repointed to
  /core/icons/categories/misc.svg.
- TIDY: core/forms was the lone depth-3 nesting — JS at forms/controls/js/
  while its CSS sat at forms/css/ and every other core subsystem uses
  <name>/js/. 'controls/' grouped nothing (just the 2 custom-* widgets), so
  flattened to core/forms/js/ (+ updated index.html). forms is now symmetric.
- CONSISTENCY: components/manifest.dev.json entries carried nav.order but not
  the top-level 'order' that each feature.json has; added it so the API-down
  fallback matches the live /api/features/list scan.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 00:39:08 +01:00
librelad
5cf5b88b16 fix(webui): flatten redundant components/admin/config/icons/config/ + repair engine logos
The config-category icons sat at admin/config/icons/CONFIG/ — the inner config/
duplicates the subsystem name; they belong in the icons root. Moved all 6
(backup, features, general, network, security, webui) up to
components/admin/config/icons/ and updated the two consumers (config-manager.js
header icon, config-sidebar.js category icons).

Also fixed the backup-engine logos: scripts/backup/engines/{restic,kopia,borg}
.json pointed 'logo' at /icons/config/backup.svg — a path that 404'd on two
counts (missing the components/admin/config prefix AND the now-removed config/
nesting), so the engine-details modal logo silently hid. Repointed to the real
served path /components/admin/config/icons/backup.svg.

(Left the meaningful icon groupings alone — admin/system/icons/{cpu,os} and
apps/core/icons/vpn are vendor/OS/provider logo sets, not redundant nesting.
The backup engines borrowing an admin-config icon is a minor smell; a dedicated
backup-engine icon could replace it later if wanted.)

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 00:07:49 +01:00
librelad
fe5cc18827 refactor(webui): give every core/ subsystem the js/css/html convention
Brings core/ in line with components/ — each subsystem now sorts its files into
js/ css/ html/ subfolders (and the nested auth/ + controls/ groups keep theirs):

  core/topbar/{js/{topbar,mobile-menu}.js, css/{topbar,sidebar}.css, html/topbar.html}
  core/theme/{js/theme-registry.js, css/{tokens,themes,base,aurora-background}.css}
  core/forms/{css/{forms,config}.css, controls/js/{custom-number,custom-select}.js}
  core/boot/{js/{system-loader,system-orchestrator}.js, auth/{js/auth-manager.js,css/login.css}}
  core/{config,tasks,kernel}/js/…  core/overlays/{js,css}/…  core/setup/{js,css}/…
  core/{app-meta,backup-card,data-loader,dom,live,notifications,ui-mode,ui-state}/js/…
  core/{loading,update-notifier}/{js,css}/…

50 files relocated (pure git mv). All path literals rewritten from a generated
old→new map across index.html, system-loader.js bundles, topbar.js's internal
fetch (/core/topbar/html/topbar.html), and the three backup-app-card loaders. No
OLD path contained a js/css/html segment, so no double-prefixing was possible.
core/icons/ left as-is (shared asset tree). All 50 /core asset refs verified to
resolve; full node --check sweep clean.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 00:00:52 +01:00
librelad
5351da5b4c refactor(webui): regroup core/ui + core/boot into named subsystems
Final structural phase — the catch-all core/ui/ is gone and core/boot/ keeps
only the genuine bootstrap pipeline:

  core/ui/eo-modal.js, confirmation-dialog.js   -> core/overlays/
  core/ui/notifications.js                       -> core/notifications/
  core/ui/topbar.{js,html}, mobile-menu.js       -> core/topbar/
  core/ui/update-notifier.js                     -> core/update-notifier/
  core/ui/backup-app-card.js                     -> core/backup-card/  (shared widget)
  core/boot/controls/                            -> core/forms/controls/
  core/boot/setup/                               -> core/setup/
  core/boot/loading-ui.js                        -> core/loading/
  core/boot/auth-manager.js                      -> core/boot/auth/

Each subsystem now owns its JS + CSS together (topbar.js beside topbar.css,
auth-manager beside login.css, etc.). Path-only moves; rewrote literals in
index.html, system-loader.js (confirmation/notifications/topbar/mobile-menu/
update-notifier/apps bundles), topbar.js's internal fetch('/core/topbar/
topbar.html'), and all THREE backup-app-card loaders (system-loader, backup/
index.js, spa.js). core/boot now = system-loader, system-orchestrator, auth/.
All 24 referenced paths resolve; full node --check sweep clean.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 21:34:18 +01:00
librelad
2da0b22719 refactor(webui): dissolve core/css — co-locate each sheet with its owner
The generic core/css/{base,components,screens} buckets are gone; every shared
stylesheet now lives beside the subsystem that owns it:

  base/tokens.css, base/themes.css, components/aurora-background.css -> core/theme/
  base/style.css                                  -> core/theme/base.css (carve deferred)
  components/modal.css                            -> core/overlays/
  components/topbar.css, components/sidebar.css   -> core/topbar/
  components/forms.css, components/config.css     -> core/forms/   (config.css under forms)
  components/update-notifier.css                  -> core/update-notifier/
  screens/login.css                               -> core/boot/auth/
  screens/loading-screen.css                      -> core/loading/
  screens/setup-wizard.css                        -> core/setup/

href-only rewrites in index.html; the <link> line ORDER is unchanged, so the
cascade is preserved (no @import anywhere). All 13 /core css hrefs verified to
resolve. (The JS for overlays/topbar/forms/update-notifier/loading/setup/auth
co-locates in the next phase.)

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 21:27:51 +01:00
librelad
461dfe1bdc fix(webui): rename core/data → core/data-loader (deploy excludes 'data/')
The Phase-2 rename put DataLoader in core/data/, but update.sh's deploy rsync
uses --exclude 'data/' (to protect the runtime frontend/data/ dir the backend
serves auth-gated under /data). rsync's pattern matches a 'data' dir ANYWHERE in
the tree, so core/data/ was silently excluded from the served copy — the file
404'd and the dashboard showed 0 apps / Loading… while every sibling subsystem
deployed fine. Renamed the folder to core/data-loader/ (segment 'data-loader' ≠
'data') so it ships. No code/class change.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 21:22:01 +01:00
librelad
de25262595 refactor(webui): kill core/lib — promote to named subsystems
The generic core/lib/ wrapper (and its task/config/util sub-buckets) is gone.
Each child is now a named core subsystem describing what it IS:

  core/lib/task/            -> core/tasks/      (task kernel: bus, refresh,
                                                 manager, router, actions,
                                                 commands, parameter-preserve)
  core/lib/config/          -> core/config/     (config-shared.js→field-factory.js,
                                                 config-options.js→options.js;
                                                 options-before-factory order kept)
  core/lib/util/system-live -> core/live/live-system.js
  core/lib/util/lp-ui       -> core/ui-mode/lp-ui.js   (stays FIRST eager — no FOUC)
  core/lib/util/data-loader -> core/data/data-loader.js
  core/lib/util/dom-helpers -> core/dom/dom-helpers.js
  core/lib/util/ui-helpers  -> core/app-meta/app-helpers.js  (getAppIcon survivor)
  core/lib/util/dismissible -> core/ui-state/dismissible.js  (generic+eager, stays
                                                 core — NOT a backup widget)
  core/boot/theme-registry  -> core/theme/theme-registry.js  (theming, not bootstrap)

Path-only moves (git mv) + literal rewrites in index.html, system-loader.js
(config/task/apps bundles) and apps-manager ensureTaskScripts. Class/global
names unchanged (ConfigShared/ConfigOptions/LiveSystem/getAppIcon) so consumers
are untouched. All 16 referenced paths verified to resolve; full node --check
sweep clean.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 21:10:27 +01:00
librelad
afb44c2f78 refactor(webui): remove dead code from core + fix two latent bugs
Verified-dead removals (zero consumers, confirmed by adversarial dependency
audit):
- core/lib/util/router.js — legacy class Router superseded by kernel/spa.js;
  self-instantiated, never exposed, and added a SECOND competing popstate
  listener. Dropped the file + its eager index.html tag.
- core/lib/task/task-global-functions.js — wired window.installApp/uninstallApp/
  etc. that nothing calls (real calls go through class methods / the task
  router). Dropped the file + its task-system scripts[] entry + the
  setupTaskGlobalFunctions() block in system-loader.js.
- TopbarComponent.createNavigationHighlighting + clearAllNavigationHighlighting —
  dead statics; window.topbarNavigationHighlighting was never set.
- ui-helpers.js: getAppStatus/formatAppName/getAppShortName (dead), the stale
  setupMobileMenu/closeMobileMenu (superseded by core/ui/mobile-menu.js's
  #mobile-drawer impl), setupActiveNavigation + the safe* helpers (verbatim dups
  of dom-helpers). Only getAppIcon remains. dom-helpers loses dead
  setupActiveNavigation + waitForElement; it is now the sole safe* source.

Bug fixes surfaced during the audit:
- system-orchestrator.js called this._wireLogout() which is defined nowhere —
  threw on the 'Continue Anyway' boot path. Removed the dangling call (logout is
  wired in topbar.setupLogout()).
- active-nav highlighting never updated on SPA navigation (it depended on the
  never-set global). spa.js now calls the live window.topbar?.setActiveNav?.()
  after each route handler.

No structural moves yet; full node --check sweep clean.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 21:04:09 +01:00
librelad
1a4de624d0 refactor(webui): separate core/css into base/components/screens
core/css held 13 flat stylesheets. Grouped by role, matching the JS sub-system
split:

  core/css/base/        foundation (3): tokens, style, themes
  core/css/components/  chrome + widgets (7): topbar, sidebar, modal, forms,
                        config, update-notifier, aurora-background
  core/css/screens/     full-page screens (3): login, loading-screen,
                        setup-wizard

Path-only move (git mv) + href rewrite in index.html. No @import anywhere and
the <link> line order is unchanged, so the cascade is preserved; all 13 paths
verified to resolve.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 19:39:14 +01:00
librelad
aa563f1fed refactor(webui): separate core/lib + core/boot into sub-system folders
core/lib held 18 loose .js; core/boot held 10. Grouped by responsibility,
mirroring the components/* sub-system layout:

  core/lib/task/    task kernel (8): event-bus, commands, actions, router,
                    global-functions, manager, refresh-coordinator,
                    parameter-preserve
  core/lib/config/  config helpers (2): config-shared, config-options
  core/lib/util/    generic utils (7): data-loader, dom-helpers, ui-helpers,
                    lp-ui, router, system-live, dismissible
  core/boot/setup/  first-run wizard (3): setup-detector, setup-wizard,
                    setup-completion-watcher
  core/boot/controls/ form widgets (2): custom-number, custom-select

core/lib is now purely subfolders. backup-app-card.js was misfiled in lib (it's
a UI card) — moved to core/ui/. core/boot keeps the genuine bootstrap singletons
at root (system-loader, system-orchestrator, auth-manager, loading-ui,
theme-registry).

Path-only moves (git mv); all 35 referenced core paths verified to resolve,
full node --check sweep of 100 frontend .js files clean.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 19:32:09 +01:00
librelad
939f0223fb refactor(webui): separate backup component into sub-system folders
backup/ held 15 loose .js plus css/html at the component root. Split into
per-tab sub-systems mirroring the admin/apps layout:

  backup/core/{js,css,html}/   schema, base BackupPage, fetch-client,
                               cron-schedule, backup.css, backup-content.html
  backup/dashboard/js/         backup-dashboard
  backup/snapshots/js/         backup-snapshots, backup-snapshot-actions
  backup/locations/js/         backup-locations, backup-location-fields,
                               backup-location-modal, backup-ssh-key
  backup/migrate/js/           backup-migrate
  backup/configuration/js/     backup-configuration, backup-retention-presets,
                               backup-engine-details

Updated the scripts[] array + loadFragment in index.js, the backup.css href in
index.html, and the handleBackup() fallback paths in spa.js. No behaviour
change — files moved verbatim (prototype-augment clusters), all 17 referenced
paths verified to resolve, node --check clean.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 19:18:43 +01:00
librelad
f896df6006 refactor(admin): move bare css/ into admin/core/ for sub-system consistency
admin had sub-system folders (config/overview/system/ssh/peers) AND a bare
root css/ (admin.css) — inconsistent. A component's shared base belongs in a
core/ sub-folder once it has sub-systems (matching apps/core/), so admin.css
moves to admin/core/css/admin.css. Audit confirms all components are now
consistent: sub-system components (apps, admin) use core/ + sub-systems;
single-page components (backup/dashboard/tasks/updater) stay flat js/css/html
(no sub-systems, so no core/ needed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 18:26:31 +01:00
librelad
e30c20fde6 refactor(webui): remove leftover redirect components; one working kernel redirect
config-redirect/, peers/, ssh/ were redirect-only shim components (just a
feature.json each) whose handlers re-entered navigate() and were silently
no-op'd by the isLoading guard — i.e. the legacy /config /peers /ssh URLs were
broken, and nothing in the UI links to them. Replace all three with a single
_legacyRedirect() at the top of navigate() (before the guard, so it actually
works) that rewrites them to the canonical /admin/* path. Removed the 3
folders, their manifest entries, the 3 dead spa.js handlers, and their
setupRoutes() lines. components/ is now exactly the 6 real pages; the real
SSH/Peers pages live (as before) under admin/ssh and admin/peers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 15:47:37 +01:00
librelad
25e3570616 refactor(webui): fold app-detail into the apps feature
app-detail was a separate sibling component but is really the apps feature's
detail view (shares the apps controllers + apps-unified-layout). Merge it in:
the apps feature now owns /apps*, /app, /app* and its mount() dispatches grid
vs detail by path (checks /apps first for wildcard precedence). Removed the
components/app-detail/ folder + its manifest entry. (The 1-level feature scan
means a feature must be a direct components/<id>/ child — folding the routes
into apps is the correct way to express the relationship.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 15:27:36 +01:00
librelad
df75059330 refactor(apps): decompose apps-manager god-file into 7 responsibility files
Brace-aware split of apps-manager.js (4154->2015 base) into apps-grid,
config-form, port-manager-integration, gluetun-vpn, config-dirty-guard,
install-console, service-buttons-sidebar — augmenting AppsManager.prototype.
Removed the dead duplicate initializeSimpleTabs (kept the live later def).
Used a self-checking extractor (node --check per cluster, auto-revert on
failure): install-dispatch contains a regex literal (/^\d{16}$/) that trips
brace-bounding, so its methods were safely LEFT in the base rather than risk a
bad split. ServiceButtons class + expandServiceLinks + bootstrap also remain
inline. Verified: all 117 methods preserved (none lost), all files
node --check clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 15:06:42 +01:00
librelad
1b0040dbf1 refactor(tasks): decompose tasks-manager god-file into 8 responsibility files
Faithful brace-aware split of tasks-manager.js (2664->491 line base) into
list-render, data-load, log-stream, row-expand, actions, modals, logs-modal,
format — augmenting TasksManager.prototype. First removed 4 provably-dead
duplicate defs (the earlier init/setupAutoRefresh/startLogStreaming/loadTaskLogs
that the later defs already overrode — behavior-preserving). Methods relocated
verbatim via a brace-aware Node extractor (handles strings/templates/comments/
regex, fixing the line-heuristic over-capture). Verified: all 60 (deduped)
methods present exactly once, no dups, all 9 files node --check clean. Wired
after the base in the task-system loader (async=false-ordered).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 14:53:19 +01:00
librelad
82989069e2 refactor(backup): decompose backup-page god-file into 13 responsibility files
Faithful prototype-augment split of backup-page.js (2353->753 line base) into
fetch-client, dashboard, snapshots, locations, location-fields, ssh-key,
retention-presets, configuration, engine-details, location-modal,
snapshot-actions, migrate (+ the earlier cron-schedule). Methods relocated
verbatim (mechanical sed/awk extraction, no logic change); all augment
BackupPage.prototype and load after the base via the ordered kernel loader.
Verified: all 99 original methods present exactly once across base+clusters,
no duplicates, all 14 files node --check clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 14:02:45 +01:00
librelad
3ad44a62f2 refactor(backup): extract cron-schedule cluster from backup-page god-file
First god-file decomposition slice: lift the standalone cron-next utility
(nextCronFireTime/_cronFieldSet/formatRelativeFuture/formatScheduleClock) out
of backup-page.js into backup-cron-schedule.js, augmenting BackupPage.prototype.
Extracted verbatim via sed (no logic change); loaded after backup-page.js in
the feature's ordered scripts array. backup-page.js 2470 -> ~2353 lines.
Proves the faithful prototype-split pattern on the verify-confirmed-safe loader.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 13:02:13 +01:00