feat(webui): phase 0b — route from the feature manifest

LibrePortalSPAClean now builds its route table from window.LP.features
(features/manifest.dev.json) instead of the hardcoded setupRoutes() Map.
Manifest order is preserved so findRouteHandler()'s wildcard precedence
(/apps* before /app*) is unchanged. All-or-nothing fallback to the
built-in table if the manifest is missing/empty or names an unknown
handler, so routing is never left half-wired.

Rendering is unchanged — handlers still do the work; only the routing
source moved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
librelad 2026-05-29 22:32:42 +01:00
parent 76138ddcdd
commit 7e051be196

View File

@ -13,9 +13,9 @@ class LibrePortalSPAClean {
async init() {
//console.log('🚀 Clean SPA: Initializing...');
// Setup routes immediately
this.setupRoutes();
// Setup routes from the feature manifest (falls back to the built-in table)
await this.setupRoutesFromManifest();
// Wait for DOM to be ready
if (document.readyState === 'loading') {
await new Promise(resolve => {
@ -85,6 +85,47 @@ class LibrePortalSPAClean {
//console.log('📍 Routes registered:', Array.from(this.routes.keys()));
}
// Build the route table from the feature manifest (window.LP.features) so
// "what pages exist" lives in one declarative place (features/manifest.dev.json)
// instead of being hardcoded here. Route insertion order is preserved from the
// manifest, which keeps the wildcard precedence findRouteHandler() relies on
// (e.g. '/apps*' must be inserted before '/app*'). Falls back to the built-in
// setupRoutes() table if the manifest is missing, empty, or names a handler
// this class doesn't define — routing must never be left half-wired.
async setupRoutesFromManifest() {
try {
const manifest = (window.LP && window.LP.features)
? await window.LP.features.loadManifest()
: null;
const entries = manifest && manifest.features;
if (!entries || !entries.length) {
this.setupRoutes();
return;
}
// All-or-nothing: a single missing handler means we don't trust the
// manifest enough to route from it — use the known-good built-in table.
for (const f of entries) {
if (typeof this[f.handler] !== 'function') {
console.warn(`[spa] manifest handler "${f.handler}" (feature "${f.id}") not found — using built-in routes`);
this.setupRoutes();
return;
}
}
this.routes.clear();
for (const f of entries) {
const handler = () => this[f.handler]();
for (const route of (f.routes || [])) {
this.routes.set(route, handler);
}
}
this._routesFromManifest = true;
//console.log('📍 Routes registered from manifest:', Array.from(this.routes.keys()));
} catch (err) {
console.error('[spa] manifest routing failed, using built-in routes:', err);
this.setupRoutes();
}
}
async loadCoreData() {
//console.log('📊 Loading core data...');