Build and develop the UI
The Gateway serves static files fromdist/control-ui:
gateway.controlUi.root builds do not use this cache.
Bundled public assets (themes, fonts, icons, and artwork) use ?v=<build-id> URLs with a one-year immutable HTTP cache. The ID includes a digest of the public files, so rebuilding changed files at the same commit also changes their URLs. The Gateway snapshots this identity at startup; restart it after rebuilding an in-place installation. Unversioned requests, stale IDs, documents, sw.js, and custom gateway.controlUi.root installs keep Cache-Control: no-cache. The service worker keeps its network-first policy for public assets, allowing the browser’s HTTP cache to satisfy matching versioned requests.
Non-index static assets use Last-Modified for conditional GET and HEAD requests. If-None-Match takes precedence over If-Modified-Since: * matches an existing asset, while other values receive the normal 200 response because static assets do not emit ETags. Date-only revalidation still returns 304 for unchanged assets. If no available content encoding is acceptable, the Gateway returns 406 before evaluating either condition.
All three HTTP-date formats are interpreted as UTC. Invalid or repeated If-Modified-Since fields are ignored, so they cannot suppress the current asset bytes. A leap-second validator remains earlier than the following second.
Static asset URLs support percent-encoded filenames. Contained symlinks retain the requested asset’s MIME type, and a symlinked index.html receives the same base-path and document preparation as other entry routes.
Optional absolute base (fixed asset URLs):
ws://127.0.0.1:18789).
For a standalone preview with synthetic data, use:
--fixture attachments for media examples; the
printed board fixture URL is also available.
The mock preview selects its own origin for Gateway resources, including
avatars, before application startup. It supplies synthetic WebSocket responses
and confines native resource requests to the serving origin and local data/blob
fixtures, including frames, while preserving same-origin Vite HMR and terminal
WebAssembly. Unimplemented HTTP API routes return a local JSON 404; external
fetches are rejected with a standalone-mock diagnostic. New workers, Talk WebRTC,
popups, and external link/navigation actions are disabled in the mock app.
External iframe URL assignments are rejected before Chromium can speculatively
connect. Add a local fixture when a demo needs another response. Each invocation
owns a separate Vite cache and removes it on graceful shutdown, so concurrent
previews and attachment fixtures do not invalidate one another.
This is a trusted-fixture development boundary, not a sandbox for hostile HTML,
browser extensions, or an already-controlling service worker. Browser-level
navigation outside the app is outside its control. Production connection settings
and pnpm ui:dev behavior are unchanged; use that command when you intentionally
need a real Gateway or external integration.
Debugging/testing: dev server + remote Gateway
The Control UI is static files; the WebSocket target is configurable and can differ from the HTTP origin. This is handy when you want the Vite dev server locally but the Gateway runs elsewhere.1
Start the UI dev server
2
Connect the remote Gateway
Follow the remote Gateway URL handoff
reference for the encoded Gateway URL and optional one-time credentials.
Origin security notes
Origin security notes
- Public non-loopback Control UI deployments must set
gateway.controlUi.allowedOriginsexplicitly (full origins). Private same-origin LAN/Tailnet loads from loopback, RFC1918/link-local,.local,.ts.net, or Tailscale CGNAT hosts are accepted without enabling Host-header fallback. - Gateway startup may seed local origins such as
http://localhost:<port>andhttp://127.0.0.1:<port>from the effective runtime bind and port, but remote browser origins still need explicit entries. - Do not use
gateway.controlUi.allowedOrigins: ["*"]except for tightly controlled local testing; it means allow any browser origin, not “match whatever host I am using.” gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=trueenables Host-header origin fallback mode, but it is a dangerous security mode.