From eb4b71cafa3b49abc5be5f4d1a1eb60588392375 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Wed, 8 Apr 2026 16:09:42 -0400 Subject: [PATCH] docs(plan): add optional web ui server plan --- .../2026-04-08-optional-web-ui-server-plan.md | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 docs/plans/2026-04-08-optional-web-ui-server-plan.md diff --git a/docs/plans/2026-04-08-optional-web-ui-server-plan.md b/docs/plans/2026-04-08-optional-web-ui-server-plan.md new file mode 100644 index 000000000..1d97e9c7e --- /dev/null +++ b/docs/plans/2026-04-08-optional-web-ui-server-plan.md @@ -0,0 +1,97 @@ +# Optional Web UI for `fabro server` Plan + +## Summary +Make the Fabro server able to run in two surfaces: + +- API-only +- API + web UI + +The always-on surface is `/health` plus the machine API under `/api/v1`. The web surface includes the embedded SPA fallback, browser auth routes under `/auth/*`, and the browser-session/setup/demo helper endpoints that currently live under `/api/v1` in `web_auth::api_routes()`: + +- `/api/v1/auth/me` +- `/api/v1/setup/register` +- `/api/v1/setup/status` +- `/api/v1/demo/toggle` + +When the web UI is disabled, all of those web-surface routes return `404`, even when they live under `/api/v1`. API-only mode is a routing change only; it does not remove the embedded SPA from the binary. + +Operator control should use both config and CLI: + +- Add `web.enabled` to server settings +- Add `--web` / `--no-web` startup overrides +- Precedence: CLI override > config > default +- Default: web UI enabled + +## Key Changes +### Config and CLI surface +- Extend the existing `[web]` config section with `enabled`. +- Add `enabled: Option` to the config model and `enabled: bool` to resolved settings, defaulting to `true`. +- Add mutually exclusive `--web` and `--no-web` flags to server startup args. +- Define merge behavior explicitly: combine `WebConfig` sources first with last-non-`None` wins for `enabled`, then materialize resolved `WebSettings.enabled`, then apply the CLI override last. +- Apply the flags in the same resolved-settings pass that already handles other serve-time overrides. +- Update help text and docs to describe the new toggle and its precedence. + +### Router composition +- Refactor server router construction so the machine API, web surface, and health surface are composed separately. +- Keep these always mounted: + - machine API routes under `/api/v1`, excluding the routes currently provided by `web_auth::api_routes()` + - `/health` +- Treat these as web-only routes, even when they live under `/api/v1`: + - `/api/v1/auth/me` + - `/api/v1/setup/register` + - `/api/v1/setup/status` + - `/api/v1/demo/toggle` + - `/auth/*` + - SPA/static fallback for non-API `GET`/`HEAD` +- The current SPA is served from the fallback closure, not a mounted router. In API-only mode, that fallback closure must return `404` for all non-API, non-health routes instead of serving the SPA. +- In API-only mode, disable the browser-oriented demo/session behavior as well: + - `/api/v1/demo/toggle` returns `404` + - cookie-driven and `X-Fabro-Demo` header demo dispatch do not route requests into the auth-disabled demo router + - requests use only the normal machine API router plus `/health` +- When the web UI is enabled, preserve current browser session, setup, and demo-cookie behavior. + +### Internal interface changes +- Thread a resolved `web_enabled` boolean into router construction. +- Prefer making this explicit in the server boundary, for example by extending `build_router(...)` with a surface/options argument, rather than re-reading settings inside the router. +- `build_router(state, auth_mode)` has a broad test blast radius. Minimize churn by introducing a small `RouterOptions`-style parameter or helper wrapper so tests that do not care about web surface toggling can keep using the default-enabled path. +- Keep the decision centralized in startup/resolution code so tests can build routers deterministically. + +## Test Plan +- Router tests: + - web enabled: `GET /` serves SPA + - web enabled: `/auth/*` remains available + - web enabled: `/api/v1/auth/me`, `/api/v1/setup/status`, and `/api/v1/demo/toggle` keep their current behavior + - web disabled: `GET /` returns `404` + - web disabled: client routes like `/runs/abc` return `404` + - web disabled: `/auth/*` returns `404` + - web disabled: `/api/v1/auth/me`, `/api/v1/setup/register`, `/api/v1/setup/status`, and `/api/v1/demo/toggle` return `404` + - web disabled: representative machine API endpoints still work + - web disabled: cookie-driven or `X-Fabro-Demo` requests do not enter auth-disabled demo dispatch + - `/health` still works in both modes +- CLI/config tests: + - default startup serves web UI + - config `web.enabled = false` disables the UI + - `--web` overrides config-disabled to enable + - `--no-web` overrides config-enabled/default to disable + - help snapshots include `--web` and `--no-web` +- Regression coverage: + - existing source-map/static routing tests still pass in enabled mode + - existing server lifecycle tests still pass with default behavior unchanged + +## Docs and User-Facing Behavior +- Update CLI docs for `fabro server start` to describe `--web` / `--no-web`. +- Update server configuration docs to document `[web].enabled`. +- Update deploy/architecture docs so “server mode” no longer implies the web UI is always present. +- Call out that “web UI disabled” means: + - no `/auth/*` + - no web-session/setup/demo helper endpoints under `/api/v1` + - no SPA fallback at `/` or client routes + - machine API and `/health` only + - embedded SPA assets are still compiled into the binary; this is not a build-time exclusion + +## Assumptions and Defaults +- Default remains web UI enabled. +- CLI flags are `--web` and `--no-web`. +- CLI override precedence is standard: CLI > config > default. +- Disabling the web UI is strictly an HTTP-surface change; it does not disable workflow execution, machine API behavior, or server-owned background services. +- No OpenAPI/API schema changes are needed, since this only changes route availability and classification within the existing server.