- kernel/lifecycle.js: ctx.loadScripts now loads in array order (sequential),
so a feature can list a base file before files that augment it. Strictly
safer than the previous parallel load.
- Extract the module-level schema/retention data (the BACKUP_* maps + the
retention-preset detector, 83 lines) out of backup-page.js into a new
backup-schema.js, loaded first. Verbatim move — no logic change. First slice
of the backup decomposition (god-file: 2553 -> 2470 lines).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
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>
Introduces the kernel lifecycle and migrates the first real page to the
feature-module contract:
- kernel/lifecycle.js: MountContext (loadScripts/loadFragment/setContent
+ an AbortController/unsub teardown ledger so mounts can't leak
listeners or live streams).
- features/backup/index.js: Backup Center as a self-contained module
(LP.features.register with mount/unmount); heavy backup-page.js stays
lazy-loaded on first mount.
- spa.js: routes whose feature has a registered mount() are driven
through the kernel; everything else still uses its legacy handleX().
navigate() unmounts the current feature first. Both fall back to the
legacy handler if a module is missing or mount throws.
Strangler step: /backup now flows manifest -> registry -> mount/unmount.
The other pages are untouched. handleBackup remains as the fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Adds the foundation of the feature-module architecture
(docs/frontend-modularization.md) as inert, additive code:
- kernel/feature-registry.js: window.LP.features — runtime register(),
manifest loader, route-table + nav builders.
- features/manifest.dev.json: hand-committed manifest mirroring spa.js
setupRoutes() exactly (route -> handler + navId).
- index.html loads the kernel before spa.js.
Zero behaviour change: nothing consults the registry yet. Phase 0b flips
routing to be registry-driven with the spa.js Map as fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>