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 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Themes are already modular via folder discovery (GET /api/themes/list scans
themes/<name>/). This brings the SAME model to pages:
- backend/routes/features.js: public GET /api/features/list scans
frontend/features/<id>/feature.json and returns the page manifest. The
Node process reads its own bind-mounted /app/frontend — no runFileOp /
regen / source-array plumbing needed (sidesteps the shell-generator gotchas).
- features/<id>/feature.json: each page now self-describes (id, routes,
module, handler, navId, nav, order). 6 real features + 3 redirect-only
(config/peers/ssh) so behaviour is preserved exactly.
- kernel loadManifest() prefers /api/features/list, falls back to the static
features/manifest.dev.json when the endpoint isn't up yet.
Result: dropping a features/<id>/ folder registers a page; deleting it
removes it — zero central edits, exactly like dropping a theme folder.
(Backend route needs a Node restart to activate; the static-manifest
fallback keeps everything working until then.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
- features/apps/index.js (/apps*) and features/app-detail/index.js (/app*)
as two features; /apps* registered first so wildcard precedence holds.
Both drive the system-loader-pre-initialized singletons (appsManager /
appTabbedManager) via .initialize(), mirroring the legacy handlers exactly
(incl. app-detail's legacy ?app=/?=name parsing + ?tab=/?config= rewrite).
- kernel/lifecycle.js: ctx.nav(path, addToHistory) so app-detail's empty-name
redirect matches navigate('/apps', false) exactly.
unmount is a no-op for both (shared singletons); the app-tabbed-manager
listener-rebind leak is pre-existing and handled in a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>