diff --git a/src/facade/Makefile b/src/facade/Makefile index 294b3c5..d0c15dd 100644 --- a/src/facade/Makefile +++ b/src/facade/Makefile @@ -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 diff --git a/src/facade/README.md b/src/facade/README.md index 25bfb96..c463e07 100644 --- a/src/facade/README.md +++ b/src/facade/README.md @@ -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 | @@ -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 — `` — in the `` 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). @@ -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 ` + Wippy + + + + +
+
+ +
+ + + diff --git a/src/facade/index_handler.lua b/src/facade/index_handler.lua new file mode 100644 index 0000000..53a26bd --- /dev/null +++ b/src/facade/index_handler.lua @@ -0,0 +1,86 @@ +local http = require("http") +local registry = require("registry") +local templates = require("templates") + +local NS = "wippy.facade:" +local TEMPLATE_SET = NS .. "templates" + +local function get_req(name: string): string + local entry, _ = registry.get(NS .. name) + if entry and entry.data then + return entry.data.default or "" + end + return "" +end + +-- Pull a single cookie value out of the raw Cookie header +-- ("a=1; @wippy-theme-mode=dark; b=2" → "dark"). Plain string match — no +-- pattern metachars from `name` reach gsub (we compare with ==). +local function cookie_value(header: string?, name: string): string? + if not header or header == "" then + return nil + end + for pair in header:gmatch("[^;]+") do + local k, v = pair:match("^%s*(.-)%s*=%s*(.*)$") + if k == name then + return v + end + end + return nil +end + +-- Renders the facade shell (index.jet). The only server-side dynamic part is the +-- theme: in "cookie" persistence mode the stored mode is read from the request +-- cookie and the matching w-theme-* class is baked onto so the very first +-- paint is already themed (no flash). "localStorage"/"none" render no class — the +-- served theme-persist.js handles those client-side. +local function handler() + local req = http.request() + local res = http.response() + if not res then + return nil, "no HTTP context" + end + + local persist = get_req("theme_persist") + local key = get_req("theme_storage_key") + if key == "" then + key = "@wippy-theme-mode" + end + + local has_theme = false + local theme_class = "" + local color_scheme = "" + if persist == "cookie" and req then + local stored = cookie_value(req:header("Cookie"), key) + if stored == "dark" then + has_theme = true + theme_class = "w-theme-dark" + color_scheme = "dark" + elseif stored == "light" then + has_theme = true + theme_class = "w-theme-light" + color_scheme = "light" + end + end + + local set = templates.get(TEMPLATE_SET) + local html, err = set:render("index", { + hasTheme = has_theme, + themeClass = theme_class, + colorScheme = color_scheme, + }) + if err then + res:set_status(http.STATUS.INTERNAL_SERVER_ERROR) + res:set_content_type("text/html") + res:write("WippyFailed to render shell.") + return nil, err + end + + res:set_content_type("text/html") + -- The shell embeds a per-user theme from a cookie; never cache across users. + res:set_header("Cache-Control", "no-store") + res:set_status(http.STATUS.OK) + res:write(html) +end + +return { handler = handler } diff --git a/src/facade/public/index.html b/src/facade/public/index.html index 3e4357c..7462282 100644 --- a/src/facade/public/index.html +++ b/src/facade/public/index.html @@ -4,6 +4,11 @@ + + Wippy