Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/facade/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Run `make sync` after every Wippy Web Host version bump to pull fresh copies.

# Web Host CDN base — update version when Wippy Web Host releases
WEB_HOST_CDN = https://web-host.wippy.ai/webcomponents-1.0.39
WEB_HOST_CDN = https://web-host.wippy.ai/webcomponents-1.0.41

# Files to sync from CDN into public/@wippy-fe/
CDN_FILES = loading.js
Expand Down
12 changes: 9 additions & 3 deletions src/facade/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ These fields are NOT configurable via requirements — they are computed at runt

| Requirement | Default | Description |
|---|---|---|
| `fe_facade_url` | `https://web-host.wippy.ai/webcomponents-1.0.39` | CDN base URL for the Web Host frontend bundle |
| `fe_facade_url` | `https://web-host.wippy.ai/webcomponents-1.0.41` | CDN base URL for the Web Host frontend bundle |
| `fe_entry_path` | `/iframe.html` | Iframe HTML entry point path (appended to `fe_facade_url`) |
| `fe_mode` | `compat` | `compat` (default — loads `module.js`) or `managed` (loads `managed-layout.js` for declarative multi-panel apps). See [Modes](#modes) above |

Expand Down Expand Up @@ -94,6 +94,11 @@ Host-only UI flags — NOT sent to child iframes.
| `hide_session_selector` | `false` | bool (`== "true"`) | Hide the chat session selector dropdown |
| `session_type` | `non-persistent` | string | Chat session persistence (`non-persistent` or `cookie`) |
| `history_mode` | `hash` | string | Browser history mode (`hash` or `browser`) |
| `theme_mode` | `auto` | string | Forced theme: `auto` (follow OS), `light`, or `dark` |
| `theme_persist` | `none` | string | Persist the user's chosen theme across reloads: `none`, `cookie`, or `localStorage`. `cookie` is read server-side by the Jet shell so the first paint is already themed (no flash). |
| `theme_storage_key` | `@wippy-theme-mode` | string | Cookie / localStorage key the theme is stored under. Returned in `/facade/config` as `themeStorageKey` and baked into `/api/public/facade/theme-persist.js`. |

> **Theme persistence:** when `theme_persist` is `cookie` or `localStorage`, include the facade-generated script — `<script src="/api/public/facade/theme-persist.js"></script>` — in the `<head>` of any page that should share the theme (the host shell already does). It applies the stored theme before paint and exposes `window.wippyThemePersist` (`read`, `write`, `apply`). The Web Host emits a `themeChanged` event the shell uses to persist changes.

> **Boolean parsing:** `show_admin` defaults to `true` (any value except `"false"` is truthy). All other boolean flags default to `false` (only `"true"` is truthy).

Expand All @@ -109,6 +114,7 @@ These accept JSON strings for complex configuration:
| `allow_additional_tags` | `{}` | `hostConfig.allowAdditionalTags` | HTML sanitizer tag whitelist (e.g. `{"w-chart":["data","type"]}`) |
| `chat` | `{}` | `hostConfig.chat` | Chat config (e.g. `{"convertPasteToFile":{"enabled":true,"minFileSize":1024,"allowHtml":false}}`) |
| `axios_defaults` | `{}` | `axiosDefaults` | HTTP client defaults (e.g. `{"timeout":30000}`) — top-level, not under hostConfig |
| `tanstack` | `{}` | `tanstack` | TanStack Query defaults — top-level, not under hostConfig. `{ default?, content?, lists? }`: `default` applies to all queries, `content` to single-resource renders, `lists` to navigation/index queries. Host default is `refetchOnWindowFocus:false` (e.g. `{"lists":{"refetchOnWindowFocus":true}}`) |
| `extra_scripts` | `[]` | `extraScripts` | External `<script>` tags injected into `index.html` before the Web Host bundle loads. See [Extra scripts](#extra-scripts). |

### Auth
Expand Down Expand Up @@ -371,9 +377,9 @@ Returns an empty body (200 OK) when no variables are configured. Response has `C

```json
{
"facade_url": "https://web-host.wippy.ai/webcomponents-1.0.39",
"facade_url": "https://web-host.wippy.ai/webcomponents-1.0.41",
"iframe_origin": "https://web-host.wippy.ai",
"iframe_url": "https://web-host.wippy.ai/webcomponents-1.0.39/iframe.html?waitForCustomConfig",
"iframe_url": "https://web-host.wippy.ai/webcomponents-1.0.41/iframe.html?waitForCustomConfig",
"login_path": "/login.html",
"login_redirect_param": null,
"env": {
Expand Down
134 changes: 131 additions & 3 deletions src/facade/_index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,20 @@ entries:
targets:
- entry: wippy.facade:static
path: .meta.server
- entry: wippy.facade:web_router
path: .meta.server

- name: router
kind: ns.requirement
meta:
description: Public API router for config and CSS variable endpoints
description: Public API router for config, CSS variable, and theme-persist endpoints
targets:
- entry: wippy.facade:config_endpoint
path: .meta.router
- entry: wippy.facade:css_vars_endpoint
path: .meta.router
- entry: wippy.facade:theme_persist_endpoint
path: .meta.router

- name: fe_facade_url
kind: ns.requirement
Expand All @@ -35,7 +39,7 @@ entries:
targets:
- entry: wippy.facade:fe_facade_url
path: .default
default: https://web-host.wippy.ai/webcomponents-1.0.39
default: https://web-host.wippy.ai/webcomponents-1.0.41

- name: fe_entry_path
kind: ns.requirement
Expand Down Expand Up @@ -163,6 +167,36 @@ entries:
path: .default
default: auto

- name: theme_persist
kind: ns.requirement
meta:
description: |
Where the user's chosen theme mode is persisted across reloads.
"none" (default) keeps the current behavior (theme always comes from
theme_mode). "cookie" stores it in a cookie — the Jet shell reads it
server-side and renders the w-theme-* class before paint (no flash).
"localStorage" stores it client-side (a brief flash is unavoidable; the
server can't read it). The store name is theme_storage_key. The facade
serves a ready-made script at /facade/theme-persist.js that applies and
writes the value; the host shell and login/non-host pages just include it.
targets:
- entry: wippy.facade:theme_persist
path: .default
default: none

- name: theme_storage_key
kind: ns.requirement
meta:
description: |
Cookie / localStorage key used to persist the theme mode when
theme_persist is "cookie" or "localStorage". Returned in /facade/config
as themeStorageKey and baked into /facade/theme-persist.js so non-Wippy
pages share the same key.
targets:
- entry: wippy.facade:theme_storage_key
path: .default
default: "@wippy-theme-mode"

# Auth
- name: login_path
kind: ns.requirement
Expand Down Expand Up @@ -354,6 +388,15 @@ entries:
path: .default
default: "{}"

- name: tanstack
kind: ns.requirement
meta:
description: "TanStack Query default options as JSON: { default?, content?, lists? }. `default` applies to every query; `content` targets single-resource renders (page/artifact/session/entry/model/upload), `lists` targets navigation/index queries. Each is a JSON-safe subset of query options (refetchOnWindowFocus, refetchOnReconnect, refetchOnMount, staleTime, gcTime, retry, refetchInterval). Default behavior (no config) is refetchOnWindowFocus:false. e.g. {\"lists\":{\"refetchOnWindowFocus\":true}}"
targets:
- entry: wippy.facade:tanstack
path: .default
default: "{}"

- name: extra_scripts
kind: ns.requirement
meta:
Expand All @@ -371,10 +414,14 @@ entries:
directory: ./public/
mode: "0755"

# Serves facade static assets (@wippy-fe/loading.js) and is the SPA fallback
# for history-mode deep links. The Jet shell endpoints below match only the
# exact "/" and "/index.html" (Go 1.22 "{$}" = exact match), a strict subset
# of this catch-all, so they win for those paths without shadowing assets.
- name: static
kind: http.static
meta:
comment: Serves facade index.html
comment: Serves facade assets (@wippy-fe/*) + SPA fallback
server: server
fs: public_files
directory: /
Expand All @@ -383,6 +430,62 @@ entries:
index: index.html
spa: true

# Jet-rendered shell (index.html) — server-side theme cookie application
# Root router for the Jet shell. Endpoints use exact-match paths ("/{$}" and
# "/index.html") so they only intercept those two routes; the /api/public API
# router and the "/" static catch-all handle everything else.
- name: web_router
kind: http.router
meta:
comment: Root router for the Jet-rendered facade shell
server: server
prefix: /

- name: templates
kind: template.set
meta:
comment: Facade Jet template set (shell)

- name: index
kind: template.jet
meta:
content_type: text/html
comment: Facade shell template — conditionally bakes the theme class onto <html>
source: file://index.jet
set: wippy.facade:templates

- name: index_handler
kind: function.lua
meta:
comment: Renders the facade shell; applies the persisted theme from the request cookie (cookie mode)
source: file://index_handler.lua
method: handler
modules:
- http
- registry
- templates
security:
actor:
id: wippy.facade:index_handler
policies:
- wippy.facade:config_read_runtime

- name: index_endpoint
kind: http.endpoint
meta:
router: web_router
method: GET
path: /{$}
func: index_handler

- name: index_html_endpoint
kind: http.endpoint
meta:
router: web_router
method: GET
path: /index.html
func: index_handler

# Shared helpers: file:// resolution and CSS variable generation
- name: theming_helpers
kind: library.lua
Expand Down Expand Up @@ -451,6 +554,31 @@ entries:
path: /facade/variables.css
func: css_vars_handler

# Theme-persistence script endpoint
- name: theme_persist_handler
kind: function.lua
meta:
comment: Serves /facade/theme-persist.js — early-apply + window.wippyThemePersist with key/mode baked in
source: file://theme_persist_handler.lua
method: handler
modules:
- http
- registry
- json
security:
actor:
id: wippy.facade:theme_persist_handler
policies:
- wippy.facade:config_read_runtime

- name: theme_persist_endpoint
kind: http.endpoint
meta:
router: router
method: GET
path: /facade/theme-persist.js
func: theme_persist_handler

- name: config_read_runtime
kind: security.policy
meta:
Expand Down
18 changes: 18 additions & 0 deletions src/facade/config_handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ local function handler()

local axios_defaults = non_empty_map_or_nil(get_req_json_any("axios_defaults"))
local extra_scripts = non_empty_array_or_nil(get_req_json_any("extra_scripts"))
-- TanStack Query defaults: { default?, content?, lists? }. Passed through
-- verbatim; the host validates/whitelists the option keys client-side.
local tanstack = non_empty_map_or_nil(get_req_json_any("tanstack"))

-- Clamp theme_mode to the valid enum; anything else (typo/misconfig) → auto,
-- so a bad value can't ship a silently-ignored class to the client.
Expand All @@ -189,6 +192,18 @@ local function handler()
theme_mode = "auto"
end

-- Persistence of the chosen theme mode. Clamp to the valid enum (bad value →
-- "none" = no persistence). theme_storage_key falls back to the documented
-- default so a blank requirement still produces a usable key.
local theme_persist = get_req("theme_persist")
if theme_persist ~= "cookie" and theme_persist ~= "localStorage" then
theme_persist = "none"
end
local theme_storage_key = get_req("theme_storage_key")
if theme_storage_key == "" then
theme_storage_key = "@wippy-theme-mode"
end

local config = {
facade_url = facade_url,
iframe_origin = iframe_origin,
Expand All @@ -205,8 +220,11 @@ local function handler()
},
routePrefix = non_empty_or_nil(api_url),
themeMode = theme_mode,
themePersist = theme_persist,
themeStorageKey = theme_storage_key,
apiRoutes = api_routes,
axiosDefaults = axios_defaults,
tanstack = tanstack,
extraScripts = extra_scripts,
theming = {
global = global_scope,
Expand Down
Loading
Loading