# "Facade" _Path: en/framework/facade_ > "Serve and configure the Wippy Web Host from a CDN with authentication, navigation, theming, and deployment settings." ## Table of Contents - Facade ## Content # Facade The `wippy/facade` module serves a page that loads and configures the Wippy Web Host from a CDN. The page loads `module.js` for the default compatibility shell or `managed-layout.js` for managed mode, handles authentication, and passes backend configuration to the frontend. The loaded module controls the page and its browser history. For isolated or partial-page integrations, the host can still be embedded manually through `iframe.html` and a `SetConfig` postMessage handshake. The facade itself does not use this delivery mode. This page is a partial deployment recipe and configuration reference. The setup block can be adapted to an existing Wippy project, while the theming, config-response, navigation, and publishing blocks are independent reference snippets. Provide any login page, filesystem entries, static assets, and frontend view entries that an adapted snippet names. For a complete runnable facade project, follow [Serve the Web Host with Facade](../tutorials/facade.md). ## Setup Add the module to your project: ```bash wippy add wippy/facade wippy install ``` Declare the dependency: ```yaml version: "1.0" namespace: app entries: - name: gateway kind: http.service addr: :8090 lifecycle: auto_start: true - name: api kind: http.router meta: server: app:gateway prefix: /api/public - name: dep.facade kind: ns.dependency component: wippy/facade version: "*" parameters: - name: server value: app:gateway - name: router value: app:api ``` ### Configuration Parameters | Parameter | Required | Default | Description | |-----------|----------|---------|-------------| | `server` | yes | — | HTTP server for static and page serving | | `router` | yes | — | Public API router for config endpoint | | `fe_facade_url` | no | `https://web-host.wippy.ai/webcomponents-1.0.56` | Base CDN URL for the frontend bundle | | `fe_entry_path` | no | `/iframe.html` | Path to the **iframe** entry on the bundle, used by the iframe embedding mode. The current facade's page loads the JS-module entry (`module.js`/`managed-layout.js`) instead; this iframe path remains available for manual, facade-less iframe embeddings. | | `fe_mode` | no | `compat` | Which shell the facade page loads: `compat` loads `module.js` (the default chat shell); `managed` loads `managed-layout.js` (opt-in declarative multi-panel layout). Surfaced on `/facade/config` as `mode`/`module_file`. | | `host_config_layout` | no | `{}` | JSON layout config emitted as `hostConfig.layout`; consumed by the **managed** shell only. | | `render_engine` | no | `iframe` | Page render engine, emitted as `hostConfig.renderEngine`. See [Render engine](#render-engine). | | `login_path` | no | `/login.html` | Path on the page's origin to redirect unauthenticated users to; works with `login_redirect_param`. | | `login_redirect_param` | no | `""` (off) | Query-parameter name to append the post-login return URL to when redirecting to `login_path`. Empty disables the return-URL append. | | `extra_scripts` | no | `[]` | JSON array of extra script URLs the facade page loads; emitted on `/facade/config` as `extraScripts`. | ### Render Engine `render_engine` selects the [page render engine](../frontend/web-host/render-engines.md) for the whole deployment. It is emitted as `hostConfig.renderEngine` and read by the Web Host at its single page-render fork. | Value | Effect | |-------|--------| | `iframe` _(default)_ | Pages render as srcdoc iframes — the main (default) engine. | | `fragment` | Pages render as [Web Fragments](../frontend/web-host/render-engines.md) (a `reframed` realm reflected into a shadow root). | Only the exact string `fragment` opts in; **any other value — including a typo like `fragmnet` — is clamped to `iframe`** (fail-safe, but silent). Enabling the fragment engine also requires the [`/@fragment` gateway](./views.md#web-fragments-gateway), which is self-provided by `wippy/views` (≥ 0.5.9) — no consumer wiring. A page can override the deployment default per-page with [`wippy.renderEngine`](../frontend/frontend-registry/view-page.md#render-engine). ### App Identity | Parameter | Default | Description | |-----------|---------|-------------| | `app_title` | `Wippy` | Title shown in sidebar | | `app_name` | `Wippy AI` | Full application name | | `app_icon` | `wippy:logo` | Iconify icon reference | ### Feature Flags | Parameter | Default | Description | |-----------|---------|-------------| | `hide_nav_bar` | `false` | Hide the left navigation sidebar | | `disable_right_panel` | `false` | Disable the right sidebar panel | | `start_nav_open` | `false` | Navigation drawer open by default | | `show_admin` | `true` | Show admin panel toggle | | `allow_select_model` | `false` | Allow user to select LLM model | | `session_type` | `non-persistent` | Web Host session policy: `cookie` stores a secondary token cookie; any other value is normalized to `non-persistent` and does not use that cookie. | | `history_mode` | `hash` | Browser history mode: `hash` or `browser`. The Web Host treats any value other than `browser` as `hash`. | | `hide_session_selector` | `false` | Hide the session picker UI | The facade shell's bootstrap token is separate from `session_type`. The shell always reads `localStorage["@wippy_token_info"]`, parses its JSON `token` field, and redirects to `login_path` when the value is missing or invalid. It passes that token to the Web Host. In `cookie` mode the Web Host also stores the token in its `@wippy-gen2/token` cookie; in `non-persistent` mode it does not use that secondary cookie. ### Theming Three scopes apply: **global** (everywhere), **host** (the Web Host chrome — sidebar, chat, page area), and **children** (child `view.page` render contexts and `view.component` web components). For which surface each knob reaches, see the [CSS Delivery Matrix](../frontend/web-host/css-injection.md#css-delivery-matrix). | Parameter | Scope | Default | Description | |-----------|-------|---------|-------------| | `custom_css` | global | Google Fonts import | Global CSS — reaches host chrome, `view.page` render contexts, and `view.component` shadow roots (1.0.43+). | | `css_variables` | global | `{}` | JSON map of arbitrary CSS custom properties; compiled for Auto and forced modes and bridged into component shadow roots. | | `icon_sets` | global | `{}` | Iconify icon sets keyed by prefix (inline JSON only — no `fs://`) | | `host_custom_css` | host | `""` | CSS for the host chrome only — not children. Scope class-based rules to `.wippy-host-app`. | | `host_css_variables` | host | `{}` | CSS custom properties for the host chrome only | | `host_icon_sets` | host | `{}` | Icon sets keyed by prefix for host only (inline JSON only) | | `children_custom_css` | children | `""` | CSS for children only — injected into `view.page` render contexts and `view.component` shadow roots (1.0.43+), not host chrome | | `children_css_variables` | children | `{}` | CSS custom properties for children only | Put shared brand styling in the global `custom_css` and `css_variables` parameters so it reaches every surface. Use `host_custom_css` and `host_css_variables` for host-only elements such as the sidebar, chat panel, and splitters. A `view.component` can opt out of shadow-root `*_custom_css` with `customCss: false`. #### Theme Mode and Persistence | Parameter | Default | Description | |-----------|---------|-------------| | `theme_mode` | `auto` | Forced theme for host + children: `auto` (follow OS), `light`, or `dark`. Emitted on `/facade/config` as `themeMode`. | | `theme_persist` | `none` | Persist the user's chosen theme across reloads: `none`, `cookie`, or `localStorage`. In `cookie` mode the Jet-rendered shell reads the cookie server-side and applies the `w-theme-*` class before the first paint (no flash). Emitted as `themePersist`. | | `theme_storage_key` | `@wippy-theme-mode` | Cookie / localStorage key the mode is stored under. Emitted as `themeStorageKey` and baked into the generated `/facade/theme-persist.js`. | Theme persistence is **opt-in**: `theme_persist` defaults to `none`, so nothing is stored until a deployment sets it to `cookie` or `localStorage`. When enabled the facade serves a ready-made script at **`GET /facade/theme-persist.js`** with the key and mode baked in; include it on any page that should share the theme. See [Theme Persistence](../frontend/web-host/theme-persistence.md) for the full model, the `themeChanged` host event, and non-Wippy-page integration. #### Reusing Facade Theming on Non-Web-Host Pages A page served outside the Web Host, such as `login.html`, an error page, or an email confirmation page, can reuse the facade theme. This keeps brand tokens and custom rules in one place. First, keep `custom_css` and `css_variables` in standalone files rather than inlining them, and point the parameters at those files with `fs://` plus a `content_fs` filesystem: ```yaml custom_css: fs://custom-css.facade.css css_variables: fs://css-variables.facade.json content_fs: app:app_fs ``` Use `fs://` (resolved by `content_fs` at runtime), **not** `file://` — `file://` is inlined by the wippy loader relative to the YAML at load time. Keep the files in the same static folder your `login_path` page is served from (in `app`, `static/` served at `/app`). `fs://` resolution applies to exactly the **six theming parameters** — `custom_css`, `css_variables`, `host_custom_css`, `host_css_variables`, `children_custom_css`, `children_css_variables` (CSS strings are read verbatim; JSON `*_css_variables` files are parsed as the variable map). `icon_sets` / `host_icon_sets` and every other JSON parameter (`api_routes`, `chat`, `tanstack`, …) are **inline-only**; `fs://` is not resolved there. A standalone page then links both: - **`custom_css`** — already a `.css` file, so link it directly from where it is served. - **`css_variables`** — JSON, so it is not linkable as-is. The facade renders it at **`GET /facade/variables.css`** as base plus effective Auto-light, Auto-dark, forced Light, and forced Dark blocks. Top-level values apply everywhere; `@light` / `@dark` replace selected names. The sheet is cached for 1h and registered on the same public router as `/facade/config`, so it carries the router prefix. ```html ``` To also share the **theme mode** (so a `login.html` honours and persists the same light/dark choice as the host), add the generated theme-persist script and call its `write()` from your switcher: ```html ``` See [Theme Persistence → Non-Wippy-hosted pages](../frontend/web-host/theme-persistence.md) for a complete switcher example. ### Optional JSON Parameters Each of the following is a JSON-encoded string parameter; defaults are empty (`{}` or `[]`). These four are surfaced verbatim under `hostConfig` for the frontend: | Parameter | Default | Description | |-----------|---------|-------------| | `additional_nav_items` | `[]` | Extra sidebar entries | | `state_cache` | `{}` | Frontend state cache configuration | | `allow_additional_tags` | `{}` | HTML sanitizer tag whitelist (`Record`, tag → allowed attributes) | | `chat` | `{}` | Chat UI overrides | These three are emitted as **top-level** `AppConfig` fields (siblings of `hostConfig`), not under `hostConfig`: | Parameter | Emitted as | Default | Description | |-----------|------------|---------|-------------| | `api_routes` | `apiRoutes` | `{}` | Route overrides for the frontend | | `axios_defaults` | `axiosDefaults` | `{}` | Frontend axios HTTP client defaults | | `tanstack` | `tanstack` | `{}` | TanStack Query defaults: `{ default?, content?, lists? }`. `default` applies to all queries; `content` targets single-resource renders, `lists` targets navigation/index queries. Host default is `refetchOnWindowFocus:false` | ## Config Endpoint The facade registers `GET /facade/config` on the configured public router, so the effective URL includes that router's prefix. With the `/api/public` prefix from [Setup](#setup), the page fetches `/api/public/facade/config`. The same router exposes `GET /facade/variables.css`, which renders `css_variables` as a `text/css` stylesheet for pages outside the Web Host. See [Reusing Facade Theming on Non-Web-Host Pages](#reusing-facade-theming-on-non-web-host-pages). The frontend fetches the configuration on load: ```json { "facade_url": "https://web-host.wippy.ai/webcomponents-1.0.56", "iframe_origin": "https://web-host.wippy.ai", "iframe_url": "https://web-host.wippy.ai/webcomponents-1.0.56/iframe.html?waitForCustomConfig", "login_path": "/login.html", "login_redirect_param": null, "mode": "compat", "module_file": "/module.js", "extraScripts": null, "env": { "APP_API_URL": "https://api.example.com", "APP_AUTH_API_URL": "https://api.example.com", "APP_WEBSOCKET_URL": "wss://api.example.com" }, "routePrefix": "https://api.example.com", "themeMode": "auto", "themePersist": "none", "themeStorageKey": "@wippy-theme-mode", "apiRoutes": { "...": "..." }, "axiosDefaults": { "...": "..." }, "tanstack": { "lists": { "refetchOnWindowFocus": true } }, "theming": { "global": { "customCSS": "...", "cssVariables": {}, "iconSets": {} }, "host": { "customCSS": "...", "cssVariables": {}, "iconSets": {}, "i18n": { "app": { "title": "Wippy", "icon": "wippy:logo", "appName": "Wippy AI" } } }, "children": { "customCSS": "...", "cssVariables": {} } }, "hostConfig": { "session": { "type": "non-persistent" }, "history": "hash", "renderEngine": "iframe", "showAdmin": true, "allowSelectModel": false, "startNavOpen": false, "hideNavBar": false, "disableRightPanel": false, "hideSessionSelector": false, "additionalNavItems": [], "stateCache": { "...": "..." }, "allowAdditionalTags": { "w-chart": ["data", "type"] }, "chat": { "...": "..." } } } ``` The API URL is read from the `PUBLIC_API_URL` environment variable; `APP_WEBSOCKET_URL` is derived by replacing `http://` with `ws://` or `https://` with `wss://`. Theming has three scopes (`global`, `host`, `children`) — `host.i18n` carries app branding. `hostConfig` keys are camelCased and assembled from facade parameters: `session_type`, `history_mode`, `render_engine`, `show_admin`, `allow_select_model`, `start_nav_open`, `hide_nav_bar`, `disable_right_panel`, `hide_session_selector`, plus optional `additional_nav_items`, `state_cache`, `allow_additional_tags`, and `chat`. `render_engine` becomes `renderEngine` (see [Render engine](#render-engine)). The `api_routes`, `axios_defaults`, and `tanstack` parameters are emitted as top-level `AppConfig` fields (`apiRoutes`, `axiosDefaults`, `tanstack`), siblings of `hostConfig`, not inside it. The `facade_url`, `iframe_origin`, `iframe_url`, `login_path`, `mode`, and `module_file` fields are **shell-level** fields used by the embedding page to build itself — they are not part of the child `AppConfig` that the host initializes with. The `iframe_origin`/`iframe_url` fields are consumed only by manual, facade-less iframe embeddings (see [Facade Entry Point](../frontend/web-host/entry-point.md)). The `mode` field is the normalized `fe_mode` (`compat` or `managed`), and `module_file` is the JS-module entry the facade page loads — `/module.js` for compat, `/managed-layout.js` for managed. ## Navigation Sidebar Pages registered via `wippy/views` appear in the sidebar automatically based on their metadata: ```yaml entries: - name: dashboard kind: registry.entry meta: type: view.page name: dashboard title: Dashboard icon: tabler:chart-bar group: Analytics group_icon: tabler:chart-dots group_order: 10 order: 1 announced: true secure: true url: https://cdn.example.com/dashboard/ ``` ### Sidebar Groups Pages with the same `group` value are collected into collapsible sections. Groups are sorted by `group_order` (lower first), pages within groups by `order`. | Field | Description | |-------|-------------| | `group` | Category name displayed in sidebar | | `group_icon` | Icon for the category header | | `group_order` | Sort position of the group (lower = higher) | | `group_placement` | `"sidebar"` (in sidebar) or `"default"` (main area only) | Pages without a `group` appear as top-level items. ### Controlling Visibility | Field | Effect | |-------|--------| | `announced: true` | Page appears in sidebar navigation | | `announced: false` | Page hidden from navigation but still accessible via URL | | `inline: true` | Internal page, hidden from all UI listings | | `hide_nav_bar: true` | Facade parameter — hides the entire left sidebar | ## Publishing with Embedded Assets When publishing a component that includes static files (like the facade's `public/` directory), use `--embed` to include `fs.directory` entries in the package: ```bash wippy publish --embed facade:public_files ``` Without `--embed`, `fs.directory` entries are excluded from the published package. The `--embed` flag accepts entry IDs or names matching `fs.directory` entries. ## See Also - [Views](./views.md) — Page and component system - [HTTP Server](../http/server.md) — HTTP service configuration - [Framework Overview](./overview.md) — Framework module usage - [Facade Entry Point](../frontend/web-host/entry-point.md) — How the facade starts the Web Host - [CSS Injection](../frontend/web-host/css-injection.md) — How facade theming reaches child iframes - [Render Engines](../frontend/web-host/render-engines.md) — Iframe and Web Fragment page rendering ## Navigation Previous: "Views" (framework/views) Next: "Embeddings" (framework/embeddings)