# Frontend Facade _Path: en/frontend/web-host/entry-point_ ## Table of Contents - Facade Entry Point ## Content # Facade Entry Point The `wippy/facade` backend module is the entry point that delivers the Web Host to users. It serves an HTML page that loads the Web Host JS module, handles authentication redirects, exposes a `/facade/config` endpoint, and bridges deployment-specific configuration into the CDN-hosted frontend bundle. No configuration is baked into the bundle itself — every deployment provides its own config through this mechanism. ![Facade entry point](../diagrams/facade-entry-point.svg) ## The HTML Page When a user navigates to a Wippy application, `wippy/facade` serves an HTML page. This page is thin: it loads a Web Host JS module from the CDN and initializes the host with the configuration returned from `/facade/config`. The module takes over the entire page — including its browser history — so the host runs as the whole application rather than inside an iframe. The facade loads one of two JS-module entries depending on the configured `fe_mode`: - **`module.js`** — the **compat** shell (default): the standard nav-sidebar + page-area + chat-right-panel layout. - **`managed-layout.js`** — the **managed** shell (opt-in, early access): the declarative multi-panel layout. A simplified version of the page looks like this: ```html My App
``` The page fetches its configuration and hands it to the module's init function. The host mounts into the page, takes over routing and browser history, and proceeds with full initialization. > **Note on the fetch path.** `/facade/config` is the path the facade registers on the public router; the actual URL your page fetches includes that router's prefix. With the example prefix `/api/public`, it is `/api/public/facade/config` — exactly what the shipped facade page fetches. The inline `fetch('/facade/config')` snippets here are shortened for readability. ## The Config Flow The config flow has two steps: 1. The page's inline JavaScript calls `GET /facade/config` on the same origin as the page. This endpoint is registered by `wippy/facade` on the public router. 2. On response, the page passes the full config object to the loaded JS module's init function (`window.initWippyApp(config, rootContainer?)`). The Web Host extracts the `AppConfig` payload from the config object and proceeds with full initialization. From this point forward the page script is passive — all user interaction happens inside the mounted host. This pattern means the CDN-hosted bundle never contains deployment-specific URLs, tokens, or branding. The bundle is identical for every deployment. Only the config payload differs. > **Shell fields vs child `AppConfig`.** The `/facade/config` response carries both. Fields like `facade_url`, `iframe_origin`, `iframe_url`, and `login_path` are **shell-level** fields consumed by the embedding page to build itself — they are not part of the child `AppConfig`. The `AppConfig` the host actually initializes with is `auth`, `env`, `theming`, `hostConfig`, `context`, and the other fields documented below. ## The `/facade/config` Response The config endpoint returns a JSON object carrying both the shell-level fields and the child `AppConfig`. The facade page passes it to the host module's init function; a manual iframe embedding instead delivers the `AppConfig` portion over PostMessage (see below). All fields are assembled by `wippy/facade` from its module parameters and the running environment: ```json { "$schema": "wippy-context-2.0", "facade_url": "https://web-host.wippy.ai/", "iframe_origin": "https://web-host.wippy.ai", "iframe_url": "https://web-host.wippy.ai//iframe.html?waitForCustomConfig", "login_path": "/login.html", "auth": { "token": "eyJ...", "expiresAt": "2026-06-01T12:00:00Z" }, "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", "axiosDefaults": {}, "apiRoutes": {}, "tanstack": { "lists": { "refetchOnWindowFocus": true } }, "theming": { "global": { "customCSS": "@import url('https://fonts.googleapis.com/...');", "cssVariables": { "--p-primary": "#6366f1" }, "iconSets": {} }, "host": { "customCSS": ".wippy-host-app .chat-container { background: var(--p-surface-100); }", "cssVariables": {}, "iconSets": {}, "i18n": { "app": { "title": "My App", "icon": "wippy:logo", "appName": "My Application" } } }, "children": { "customCSS": "", "cssVariables": {} } }, "hostConfig": { // example values — defaults shown in table below "session": { "type": "non-persistent" }, "history": "hash", "showAdmin": true, "allowSelectModel": false, "startNavOpen": false, "hideNavBar": false, "disableRightPanel": false, "hideSessionSelector": false, "additionalNavItems": [], "stateCache": {}, "allowAdditionalTags": [], "chat": {} }, "context": { "resourceId": "root", "resourceType": "page", "route": "/" } } ``` ### Field Reference **Shell-level fields** — consumed by the embedding page to build itself; not part of the child `AppConfig`: | Field | Description | |-------|-------------| | `facade_url` | Base CDN URL for the Web Host bundle. Used to resolve the module entry and vendor scripts. | | `iframe_origin` | `Origin` header value of the CDN. Used as the `targetOrigin` for PostMessage in manual iframe embeddings (see below). | | `iframe_url` | Full iframe `src` including `?waitForCustomConfig`. Used only by manual, facade-less iframe embeddings (see below). | | `login_path` | Path on the page's origin to redirect unauthenticated users to. | **Child `AppConfig` fields** — passed to the host's init function and consumed by the running host: | Field | Description | |-------|-------------| | `$schema` | Config contract version (`"wippy-context-2.0"`). | | `auth` | Runtime bearer token and expiry injected as `AppConfig.auth`. | | `env` | Runtime URLs injected as top-level `AppConfig.env`. | | `routePrefix` | API URL prefix forwarded to child apps. | | `axiosDefaults` | Axios instance defaults forwarded to child apps. | | `apiRoutes` | Override individual API endpoint paths (top-level `AppConfig` field). | | `tanstack` | TanStack Query defaults — global + per role-based category (`content`/`lists`); top-level `AppConfig` field. Host default is `refetchOnWindowFocus:false`. | | `theming` | CSS customization split into three scopes. | | `hostConfig` | Web Host feature flags and UI configuration. | | `context` | Initial page or artifact context for the host. | **`env` fields:** | Field | Source | Description | |-------|--------|-------------| | `APP_API_URL` | `PUBLIC_API_URL` env var | Base URL for all backend HTTP calls | | `APP_AUTH_API_URL` | Same as `APP_API_URL` | Auth endpoint URL (may differ in custom setups) | | `APP_WEBSOCKET_URL` | Derived from `APP_API_URL` | `http://` → `ws://`, `https://` → `wss://` | **`theming` scopes:** | Scope | Applied to | |-------|-----------| | `global` | Both the host chrome and all child iframes | | `host` | Host chrome only. Also carries `i18n.app` for the app title, icon, and name shown in the sidebar. | | `children` | Child iframes only (injected by the proxy script) | **`hostConfig` fields:** | Field | Type | Default | Description | |-------|------|---------|-------------| | `session.type` | `"non-persistent"` \| `"cookie"` | `"non-persistent"` | Token storage mode | | `history` | `"hash"` \| `"browser"` | `"hash"` | Vue Router history mode | | `showAdmin` | boolean | `true` | Show admin features in UI | | `allowSelectModel` | boolean | `false` | Show LLM model picker | | `startNavOpen` | boolean | `false` | Expand nav sidebar on load | | `hideNavBar` | boolean | `false` | Hide left navigation sidebar entirely | | `disableRightPanel` | boolean | `false` | Disable right artifact panel | | `hideSessionSelector` | boolean | `false` | Hide the chat session picker | | `additionalNavItems` | array | `[]` | Extra items injected into the sidebar | | `stateCache` | object | `{}` | LRU cache config for child iframe state | | `allowAdditionalTags` | object | `{}` | HTML sanitizer tag whitelist (`Record`, tag → allowed attributes) | | `chat` | object | `{}` | Chat UI overrides (paste-to-file behavior, etc.) | ## Authentication Flow If the user is not authenticated when they load the page, `wippy/facade` redirects to `login_path` before serving the HTML page. After login succeeds, the user is returned to the original URL. No authentication state is passed through the Web Host config itself — the Web Host trusts the auth token embedded in `auth`/`env` by the authenticated page response. Because the config endpoint is served by the same authenticated session that served the HTML page, `APP_API_URL` and the derived WebSocket URL automatically reflect the correct backend for that user. ## The Module Init Function The JS-module entry registers `window.initWippyApp` on the page. The facade page calls it with the config object fetched from `/facade/config`. `fe_mode` selects which module the facade loads — `module.js` for **compat**, `managed-layout.js` for **managed** — and both expose the same `initWippyApp` entry function. The choice of module is about which shell renders; it is independent of the embedding style (JS-module page vs manual iframe). `initWippyApp(config, rootContainer?)` returns a simple event emitter: ```javascript const events = window.initWippyApp(config, '#app') events.on('ready', () => console.log('Wippy loaded')) events.on('error', err => console.error('Failed to load:', err)) ``` When called without a root container, the host mounts into a default element. The host takes over the page and its browser history from this point forward. ## Manual (facade-less) iframe embedding The JS-module page above is the standard, recommended path and the one the current facade uses. There is also a second embedding mechanism for cases where you want to run the full host **inside an iframe** — for example to occupy only part of a page with stronger isolation from the surrounding application. In this mode you embed the host yourself; the facade does not produce this page. ![Manual iframe embedding](../diagrams/manual-iframe-embedding.svg) You can still reuse the facade's `/facade/config` endpoint to obtain the URLs and config: its `iframe_url` (the host's `iframe.html` entry with `?waitForCustomConfig` already appended) and `iframe_origin` (the `targetOrigin` for PostMessage) exist for exactly this path. You then create the iframe yourself and complete the config handshake. Unlike the JS-module path, the host inside the iframe **requests** its config: it boots and posts a `get-config` message to the parent, and the parent replies with `set-config`. So the parent **listens** for the request rather than pushing config blindly on `load`: ```html My App ``` The `?waitForCustomConfig` query parameter (already present in `iframe_url`) is the key signal. It tells the Web Host to pause initialization — the app mounts but deliberately does not attempt to resolve authentication or load routes until it receives a `set-config` message. Without it the Web Host would try to read auth tokens from URL parameters or defaults, which is not appropriate for embedded deployments. The handshake uses the `@gen2-chat` PostMessage protocol: 1. The parent fetches `GET /facade/config` (or supplies an equivalent `AppConfig` payload itself) and creates the iframe pointing at `iframe_url`. 2. The booting iframe posts `{ type: '@gen2-chat', action: 'get-config' }` to the parent. 3. The parent's `message` listener responds with `{ type: '@gen2-chat', action: 'set-config', ...config }`, targeted at `iframe_origin`. The Web Host extracts the `AppConfig` payload and proceeds with full initialization. For the full message protocol (`@gen2-chat` envelope and the `IFrameMessageType` enum), see [Proxy & Isolation](./proxy-isolation.md). This `SetConfig` handshake is specific to manual, facade-less embedding; the `wippy/facade` module loads the Web Host as a JS module instead. ## Configuring the Facade Module The `wippy/facade` parameters that produce the config response above are set in your `_index.yaml`. A real example from `app-template`: ```yaml - name: facade kind: ns.dependency component: wippy/facade version: '>=v0.5.37' parameters: - name: server value: app:gateway - name: router value: app:api.public - name: app_title value: Wippy App - name: app_name value: Wippy App - name: app_icon value: "wippy:logo" - name: show_admin value: "false" - name: hide_nav_bar value: "true" - name: login_path value: /app/login.html - name: session_type value: non-persistent - name: history_mode value: browser - name: custom_css value: "@import url('https://fonts.googleapis.com/css2?family=Poppins...'); body { font-family: 'Poppins', sans-serif; }" - name: css_variables value: '{"--p-primary":"#6366f1"}' - name: host_custom_css value: ".wippy-host-app .chat-container { background: var(--p-surface-100); }" - name: tanstack value: '{"lists":{"refetchOnWindowFocus":true}}' ``` For the full list of available parameters and their defaults, see the [Facade module reference](../../framework/facade.md). ## Navigation Previous: Overview (frontend/web-host/overview) Next: Bootstrap Sequence (frontend/web-host/bootstrap)