# LibrePortal WebUI — Feature-Module Architecture (Design Doc) **Status:** Partially implemented (core architecture shipped 2026-05-29/30) · **Audience:** implementing engineer · **Scope:** `containers/libreportal/frontend/` (no-build vanilla-JS SPA) --- ## 0. Implementation status (2026-05-30) **Shipped + verified live (via `lp-shot`):** - **Kernel + manifest routing** — `kernel/feature-registry.js`, `kernel/lifecycle.js` (MountContext + AbortController/unsub teardown), `kernel/services.js` (DI container). `spa.js` routes from the manifest; legacy `handleX()` kept as fallbacks. - **All pages are feature modules** — `features/{dashboard,apps,app-detail,admin,backup,tasks}/index.js` with `mount`/`unmount`; admin owns all `/admin/*` sub-routes. - **Folder auto-discovery** — `GET /api/features/list` (`backend/routes/features.js`) scans `features//feature.json`, mirroring `/api/themes/list`. **This replaces the §3 shell-regen generator** — the Node backend reads its own bind-mounted `/app/frontend`, so the `runFileOp`/regen-staleness/source-array gotchas are moot. Drop a folder → page appears; delete it → gone. (`features/manifest.dev.json` is the static fallback.) - **DI seam** — `ctx.services` (tasks/live/auth/data/notify/theme/modal/router) injected into every feature; cross-cutting refs in feature modules migrated onto it. - **Shared token layer** — `shared/css/tokens.css` (`--font-mono`, hoisted `--page-*`). - Three of the four central registries eliminated (spa.js route Map, index.html script list, manual manifest). Themes were already modular and are untouched. **Not yet done (large internal refactors; do NOT assume these are present):** - **§7 god-file decomposition** (`apps-manager.js` 176KB, `tasks-manager.js` 109KB, `config-shared.js` 62KB, `backup-page.js` 129KB, `system-loader.js` 47KB). The ~80 raw `window.*` globals + the last two registries (system-loader component map, config-manager if-chain) live inside these and retire as part of this work. - **§5 `base.css`/`shared-ui` extraction + per-feature CSS split.** Note: needs per-theme verification; the screenshot helper only easily renders the default theme. - **§3.7 esbuild chunker** (was always optional/unproven). --- ## 1. Executive summary We turn the frontend into a **scan-and-manifest feature system** that mirrors the backend's container scan: each page becomes a self-contained folder (`features//`) carrying its own HTML fragment, scoped CSS, controller, and a `feature.json` manifest. A single shell generator — hung off the existing `webuiLibrePortalUpdate`/`libreportal regen` pass that already emits the apps-tools artifact — walks those folders and writes one read-only `/data/webui/generated/features.json`. A small **navigation kernel** consumes that manifest to build the route table, the topbar nav, and the per-feature CSS links, replacing the four hand-maintained registries (`index.html`, `spa.js` `routes` Map, `system-loader.js` component registry, `config-manager.js` `renderConfig()` if-chain). Every feature implements **one uniform lifecycle** (`mount(ctx)` / `unmount(ctx)`) and receives shared services (taskBus, liveSystem, auth, dataLoader, router, notifications) via an injected `ctx` instead of ~80 `window.*` globals. We stay **no-build by default** (plain `