From 98d950ba4459010bebd41dbc380ce47c353a77bf Mon Sep 17 00:00:00 2001 From: librelad Date: Sat, 30 May 2026 00:46:57 +0100 Subject: [PATCH] =?UTF-8?q?feat(webui):=20phase=202=20=E2=80=94=20DI=20ser?= =?UTF-8?q?vice=20container=20(ctx.services)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces kernel/services.js (window.LP.services): an additive, typed, LAZY view onto the existing cross-cutting singletons — tasks{bus,refresh, route}, live, auth, data, notify, theme, modal, router. It constructs nothing (pure getters onto the live globals), so there's no double-init and the globals stay authoritative. MountContext now injects it as ctx.services. Slot names/globals were verified against the real code (workflow map): the design doc's §4 list was wrong in several places — no window.taskManager (client slot dropped), tasks.route lives on tasksManager.router, auth has no status(), DataLoader isn't a window prop (lexical fallback), modal/router are split surfaces (grouped/bound objects). Migrated the 4 cross-cutting refs in the feature modules onto ctx.services (admin: router.adminCategoryFromPath + tasks.refresh; backup: tasks.refresh; app-detail: router.appPath). Page-owned controllers stay feature-globals. Co-Authored-By: Claude Opus 4.8 Signed-off-by: librelad --- .../frontend/features/admin/index.js | 6 +- .../frontend/features/app-detail/index.js | 2 +- .../frontend/features/backup/index.js | 4 +- containers/libreportal/frontend/index.html | 1 + .../libreportal/frontend/kernel/lifecycle.js | 3 + .../libreportal/frontend/kernel/services.js | 62 +++++++++++++++++++ 6 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 containers/libreportal/frontend/kernel/services.js diff --git a/containers/libreportal/frontend/features/admin/index.js b/containers/libreportal/frontend/features/admin/index.js index 21b76a3..eb627b4 100644 --- a/containers/libreportal/frontend/features/admin/index.js +++ b/containers/libreportal/frontend/features/admin/index.js @@ -14,7 +14,7 @@ LP.features.register({ routes: ['/admin', '/admin*'], async mount(ctx) { - window.configCategory = window.adminCategoryFromPath(window.location.pathname); + window.configCategory = ctx.services.router.adminCategoryFromPath(window.location.pathname); const html = await ctx.loadFragment('/html/config-content.html'); ctx.setContent(html, 'Admin'); @@ -30,7 +30,7 @@ LP.features.register({ } }, - async unmount() { + async unmount(ctx) { // Release only this view's leaks; never destroy the configManager singleton. // The sub-controllers renderConfig() spawns are re-created on each visit, so // null them; AdminSystem additionally holds a live SSE sub + a 30s interval @@ -48,7 +48,7 @@ LP.features.register({ } catch (_) {} // Drop AdminOverview's task-refresh registration so a finished verify/update // task doesn't repaint a torn-down board. - try { window.taskRefresh && window.taskRefresh.unregister && window.taskRefresh.unregister('admin-overview'); } catch (_) {} + try { ctx.services.tasks.refresh && ctx.services.tasks.refresh.unregister('admin-overview'); } catch (_) {} window.adminOverview = null; window.adminSystem = null; window.sshPage = null; diff --git a/containers/libreportal/frontend/features/app-detail/index.js b/containers/libreportal/frontend/features/app-detail/index.js index d32f9a4..197d220 100644 --- a/containers/libreportal/frontend/features/app-detail/index.js +++ b/containers/libreportal/frontend/features/app-detail/index.js @@ -36,7 +36,7 @@ LP.features.register({ const tab = legacyTab === 'logs' ? 'tasks' : (legacyTab || 'config'); const sub = (tab === 'config') ? legacyConfig : null; const taskId = url.searchParams.get('task'); - const canonical = window.appPath(appName, tab, sub, taskId); + const canonical = ctx.services.router.appPath(appName, tab, sub, taskId); if (canonical !== url.pathname + url.search) { window.history.replaceState({ route: canonical }, '', canonical); } diff --git a/containers/libreportal/frontend/features/backup/index.js b/containers/libreportal/frontend/features/backup/index.js index b80f39b..81e663e 100644 --- a/containers/libreportal/frontend/features/backup/index.js +++ b/containers/libreportal/frontend/features/backup/index.js @@ -29,13 +29,13 @@ LP.features.register({ await window.backupPage.init(); }, - async unmount() { + async unmount(ctx) { // Best-effort teardown. BackupPage self-guards stale work via // (window.backupPage === this), so nulling the global neutralises any // pending task-refresh repaint; we also drop its coordinator registration. // A proper dispose() (removing the leaked document listeners) lands with // the Phase 5 backup decomposition. - try { window.taskRefresh && window.taskRefresh.unregister && window.taskRefresh.unregister('backups'); } catch (_) {} + try { ctx.services.tasks.refresh && ctx.services.tasks.refresh.unregister('backups'); } catch (_) {} window.backupPage = null; }, }); diff --git a/containers/libreportal/frontend/index.html b/containers/libreportal/frontend/index.html index cc306be..ef54dd6 100755 --- a/containers/libreportal/frontend/index.html +++ b/containers/libreportal/frontend/index.html @@ -108,6 +108,7 @@ loads the page manifest; spa.js consults it for routing. See docs/frontend-modularization.md. --> +