From f6c9b2b75225a8339f1944d15eb58f6128676326 Mon Sep 17 00:00:00 2001 From: Andrei Kirkouski Date: Mon, 29 Jun 2026 15:46:02 +0200 Subject: [PATCH 1/3] feat: theme-persistence login examples + bump to 1.0.41 - Static login (static/login.html) and Jet login (src/app/api/login.jet + login_jet.lua, served at /login via a public `web` router) examples, each with a segmented icon theme switcher that persists via window.wippyThemePersist. The Jet login renders the theme server-side from the cookie (no flash). login_path -> /login. - Opt in to facade theme_persist=cookie (key @wippy-theme-mode) in src/app/deps; wippy.lock `replacements:` points wippy/facade at the local checkout for testing. - Playwright e2e (e2e/theme-persistence.spec.ts): theme-persist.js endpoint + config, cookie SSR for shell + Jet login, and switcher persistence (6 cases). - Bump @wippy-fe/* frontend deps -> ^0.0.41 and CDN refs (login stylesheets + app.html dev-proxy) -> 1.0.41. Lockfiles regenerate on the next clean rebuild. --- e2e/theme-persistence.spec.ts | 97 +++++++++++++ frontend/applications/iframe-demo/app.html | 2 +- .../applications/iframe-demo/package.json | 8 +- frontend/applications/main/app.html | 2 +- frontend/applications/main/package.json | 12 +- .../web-components/chart-circle/package.json | 12 +- .../counter-persist/package.json | 14 +- frontend/web-components/markdown/package.json | 12 +- frontend/web-components/mermaid/package.json | 12 +- .../web-components/model-gallery/package.json | 12 +- .../web-components/reaction-bar/package.json | 12 +- .../web-components/websocket-log/package.json | 12 +- src/app/_index.yaml | 7 + src/app/api/_index.yaml | 50 +++++++ src/app/api/login.jet | 131 ++++++++++++++++++ src/app/api/login_jet.lua | 80 +++++++++++ src/app/deps/_index.yaml | 14 +- static/login.html | 102 +++++++++++++- wippy.lock | 3 + 19 files changed, 533 insertions(+), 61 deletions(-) create mode 100644 e2e/theme-persistence.spec.ts create mode 100644 src/app/api/login.jet create mode 100644 src/app/api/login_jet.lua diff --git a/e2e/theme-persistence.spec.ts b/e2e/theme-persistence.spec.ts new file mode 100644 index 0000000..facd69e --- /dev/null +++ b/e2e/theme-persistence.spec.ts @@ -0,0 +1,97 @@ +import { expect, test } from '@playwright/test' + +/** + * Theme persistence e2e (facade theme_persist = "cookie", key "@wippy-theme-mode"). + * + * Covers the parts that don't need the Wippy host bundle: + * - the generated /facade/theme-persist.js script (key + mode baked in), + * - /facade/config exposing themePersist / themeStorageKey, + * - server-side cookie rendering (Jet shell "/" + Jet login) — no-flash class on , + * - the static + Jet login theme switchers persisting the choice across reload. + * + * The host-switcher -> themeChanged -> persist path needs the local gen-2-chat + * dev server (:5173); it's covered by the gen-2-chat unit/lint and the shared + * cookie below (a choice made on login is read by the host on next load). + * + * Prereq: wippy backend running with the LOCAL facade replacement and + * theme_persist: cookie (see src/app/deps/_index.yaml + wippy.lock). + */ + +const KEY = '@wippy-theme-mode' + +test.describe('facade theme-persist endpoint + config', () => { + test('theme-persist.js is served with the configured key and mode', async ({ request }) => { + const res = await request.get('/api/public/facade/theme-persist.js') + expect(res.status()).toBe(200) + expect(res.headers()['content-type']).toContain('application/javascript') + const body = await res.text() + expect(body).toContain(`var KEY = "${KEY}"`) + expect(body).toContain('MODE = "cookie"') + expect(body).toContain('window.wippyThemePersist') + }) + + test('config exposes themePersist + themeStorageKey', async ({ request }) => { + const res = await request.get('/api/public/facade/config') + expect(res.status()).toBe(200) + const cfg = await res.json() + expect(cfg.themePersist).toBe('cookie') + expect(cfg.themeStorageKey).toBe(KEY) + }) +}) + +test.describe('server-side cookie rendering (no flash)', () => { + test('shell "/" bakes the theme class onto from the cookie', async ({ request }) => { + const dark = await (await request.get('/', { headers: { Cookie: `${KEY}=dark` } })).text() + expect(dark).toMatch(/]*class="w-theme-dark"[^>]*style="color-scheme: dark;"/) + + const light = await (await request.get('/', { headers: { Cookie: `${KEY}=light` } })).text() + expect(light).toMatch(/]*class="w-theme-light"/) + + const none = await (await request.get('/')).text() + expect(none).not.toContain('w-theme-dark') + expect(none).not.toContain('w-theme-light') + }) + + test('Jet login (/login) bakes the theme class onto from the cookie', async ({ request }) => { + // Use the canonical /login/ (GET /login 307-redirects to it); the redirect + // would otherwise drop the manually-set Cookie header in the request API. + const dark = await (await request.get('/login/', { headers: { Cookie: `${KEY}=dark` } })).text() + expect(dark).toMatch(/]*class="w-theme-dark"/) + expect(dark).toContain('id="login-form"') // the Jet login page, not the shell + + // No cookie → plain tag (the switcher JS mentions w-theme-* strings, + // so assert the opening tag specifically rather than the whole document). + const none = await (await request.get('/login/')).text() + expect(none).toContain('') + }) +}) + +test.describe('login theme switchers persist the choice', () => { + test('static login: switch to dark, reload, theme persists', async ({ page }) => { + await page.goto('/app/login.html') + await page.getByRole('button', { name: 'Dark', exact: true }).click() + + await expect(page.locator('html')).toHaveClass(/w-theme-dark/) + const cookies = await page.context().cookies() + expect(cookies.find(c => c.name === KEY)?.value).toBe('dark') + + await page.reload() + // Applied before paint by theme-persist.js reading the cookie. + await expect(page.locator('html')).toHaveClass(/w-theme-dark/) + + // switch back so the shared cookie doesn't leak into other specs + await page.getByRole('button', { name: 'Auto', exact: true }).click() + }) + + test('Jet login: switch to light, reload, server renders it', async ({ page }) => { + await page.goto('/login') + await page.getByRole('button', { name: 'Light', exact: true }).click() + await expect(page.locator('html')).toHaveClass(/w-theme-light/) + + await page.reload() + // On reload the server reads the cookie and ships class="w-theme-light". + await expect(page.locator('html')).toHaveClass(/w-theme-light/) + + await page.getByRole('button', { name: 'Auto', exact: true }).click() + }) +}) diff --git a/frontend/applications/iframe-demo/app.html b/frontend/applications/iframe-demo/app.html index 41b543e..18a648f 100644 --- a/frontend/applications/iframe-demo/app.html +++ b/frontend/applications/iframe-demo/app.html @@ -15,7 +15,7 @@ } diff --git a/frontend/applications/iframe-demo/package.json b/frontend/applications/iframe-demo/package.json index e4fda05..5cd393d 100644 --- a/frontend/applications/iframe-demo/package.json +++ b/frontend/applications/iframe-demo/package.json @@ -44,12 +44,12 @@ "lint:fix": "eslint src --ext .ts,.vue --fix" }, "dependencies": { - "@wippy-fe/theme": "^0.0.40" + "@wippy-fe/theme": "^0.0.41" }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.0", - "@wippy-fe/vite-plugin": "^0.0.40", - "@wippy-fe/types-global-proxy": "^0.0.40", + "@wippy-fe/vite-plugin": "^0.0.41", + "@wippy-fe/types-global-proxy": "^0.0.41", "autoprefixer": "^10.4.0", "postcss": "^8.4.0", "tailwindcss": "3", @@ -61,7 +61,7 @@ }, "peerDependencies": { "@iconify/vue": "^5.0.0", - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/proxy": "^0.0.41", "axios": "^1.0.0", "vue": "^3.5.0", "vue-router": "^4.0.0" diff --git a/frontend/applications/main/app.html b/frontend/applications/main/app.html index 48b3d5e..15dc58b 100644 --- a/frontend/applications/main/app.html +++ b/frontend/applications/main/app.html @@ -18,7 +18,7 @@ } diff --git a/frontend/applications/main/package.json b/frontend/applications/main/package.json index 8f5fab2..b0d12b6 100644 --- a/frontend/applications/main/package.json +++ b/frontend/applications/main/package.json @@ -48,7 +48,7 @@ "lint:fix": "eslint src --ext .ts,.vue --fix" }, "dependencies": { - "@wippy-fe/theme": "^0.0.40" + "@wippy-fe/theme": "^0.0.41" }, "devDependencies": { "@tanstack/query-core": "^5.90.20", @@ -56,8 +56,8 @@ "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "@vitejs/plugin-vue": "^5.0.0", - "@wippy-fe/vite-plugin": "^0.0.40", - "@wippy-fe/types-global-proxy": "^0.0.40", + "@wippy-fe/vite-plugin": "^0.0.41", + "@wippy-fe/types-global-proxy": "^0.0.41", "autoprefixer": "^10.4.0", "eslint": "^8.57.0", "eslint-plugin-vue": "^9.0.0", @@ -74,9 +74,9 @@ "peerDependencies": { "@iconify/vue": "^5.0.0", "@tanstack/vue-query": "^5.69.0", - "@wippy-fe/pinia-persist": "^0.0.40", - "@wippy-fe/proxy": "^0.0.40", - "@wippy-fe/router": "^0.0.40", + "@wippy-fe/pinia-persist": "^0.0.41", + "@wippy-fe/proxy": "^0.0.41", + "@wippy-fe/router": "^0.0.41", "axios": "^1.0.0", "luxon": "^3.5.0", "pinia": "^2.1.0", diff --git a/frontend/web-components/chart-circle/package.json b/frontend/web-components/chart-circle/package.json index b61c03e..b6339bb 100644 --- a/frontend/web-components/chart-circle/package.json +++ b/frontend/web-components/chart-circle/package.json @@ -11,17 +11,17 @@ "package.json" ], "dependencies": { - "@wippy-fe/theme": "^0.0.40", - "@wippy-fe/webcomponent-core": "^0.0.40", - "@wippy-fe/webcomponent-vue": "^0.0.40", + "@wippy-fe/theme": "^0.0.41", + "@wippy-fe/webcomponent-core": "^0.0.41", + "@wippy-fe/webcomponent-vue": "^0.0.41", "chart.js": "^4.4.0" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "@vitejs/plugin-vue": "^5.0.0", - "@wippy-fe/vite-plugin": "^0.0.40", - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/vite-plugin": "^0.0.41", + "@wippy-fe/proxy": "^0.0.41", "eslint": "^8.57.0", "eslint-plugin-vue": "^9.0.0", "typescript": "^5.0.0", @@ -31,7 +31,7 @@ "vue-tsc": "^2.0.0" }, "peerDependencies": { - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/proxy": "^0.0.41", "vue": "^3.5.0" }, "wippy": { diff --git a/frontend/web-components/counter-persist/package.json b/frontend/web-components/counter-persist/package.json index a179226..8949d97 100644 --- a/frontend/web-components/counter-persist/package.json +++ b/frontend/web-components/counter-persist/package.json @@ -11,18 +11,18 @@ "package.json" ], "dependencies": { - "@wippy-fe/theme": "^0.0.40", - "@wippy-fe/webcomponent-core": "^0.0.40", - "@wippy-fe/webcomponent-vue": "^0.0.40", + "@wippy-fe/theme": "^0.0.41", + "@wippy-fe/webcomponent-core": "^0.0.41", + "@wippy-fe/webcomponent-vue": "^0.0.41", "primevue": "^4.3.3" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "@vitejs/plugin-vue": "^5.0.0", - "@wippy-fe/vite-plugin": "^0.0.40", - "@wippy-fe/pinia-persist": "^0.0.40", - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/vite-plugin": "^0.0.41", + "@wippy-fe/pinia-persist": "^0.0.41", + "@wippy-fe/proxy": "^0.0.41", "pinia": "^2.1.0", "autoprefixer": "^10.4.0", "eslint": "^8.57.0", @@ -36,7 +36,7 @@ "vue-tsc": "^2.0.0" }, "peerDependencies": { - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/proxy": "^0.0.41", "pinia": "^2.1.0", "vue": "^3.5.0" }, diff --git a/frontend/web-components/markdown/package.json b/frontend/web-components/markdown/package.json index 63bec92..df50774 100644 --- a/frontend/web-components/markdown/package.json +++ b/frontend/web-components/markdown/package.json @@ -11,9 +11,9 @@ "package.json" ], "dependencies": { - "@wippy-fe/theme": "^0.0.40", - "@wippy-fe/webcomponent-core": "^0.0.40", - "@wippy-fe/webcomponent-vue": "^0.0.40", + "@wippy-fe/theme": "^0.0.41", + "@wippy-fe/webcomponent-core": "^0.0.41", + "@wippy-fe/webcomponent-vue": "^0.0.41", "markdown-it": "^14.1.0", "sanitize-html": "^2.14.0" }, @@ -23,8 +23,8 @@ "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "@vitejs/plugin-vue": "^5.0.0", - "@wippy-fe/vite-plugin": "^0.0.40", - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/vite-plugin": "^0.0.41", + "@wippy-fe/proxy": "^0.0.41", "eslint": "^8.57.0", "eslint-plugin-vue": "^9.0.0", "typescript": "^5.0.0", @@ -34,7 +34,7 @@ "vue-tsc": "^2.0.0" }, "peerDependencies": { - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/proxy": "^0.0.41", "vue": "^3.5.0" }, "wippy": { diff --git a/frontend/web-components/mermaid/package.json b/frontend/web-components/mermaid/package.json index eb253aa..d8208ec 100644 --- a/frontend/web-components/mermaid/package.json +++ b/frontend/web-components/mermaid/package.json @@ -11,9 +11,9 @@ "package.json" ], "dependencies": { - "@wippy-fe/theme": "^0.0.40", - "@wippy-fe/webcomponent-core": "^0.0.40", - "@wippy-fe/webcomponent-vue": "^0.0.40", + "@wippy-fe/theme": "^0.0.41", + "@wippy-fe/webcomponent-core": "^0.0.41", + "@wippy-fe/webcomponent-vue": "^0.0.41", "beautiful-mermaid": "latest", "mermaid": "^11" }, @@ -21,8 +21,8 @@ "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "@vitejs/plugin-vue": "^5.0.0", - "@wippy-fe/vite-plugin": "^0.0.40", - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/vite-plugin": "^0.0.41", + "@wippy-fe/proxy": "^0.0.41", "eslint": "^8.57.0", "eslint-plugin-vue": "^9.0.0", "typescript": "^5.0.0", @@ -32,7 +32,7 @@ "vue-tsc": "^2.0.0" }, "peerDependencies": { - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/proxy": "^0.0.41", "vue": "^3.5.0" }, "wippy": { diff --git a/frontend/web-components/model-gallery/package.json b/frontend/web-components/model-gallery/package.json index ec5de93..c5a3fd5 100644 --- a/frontend/web-components/model-gallery/package.json +++ b/frontend/web-components/model-gallery/package.json @@ -11,17 +11,17 @@ "package.json" ], "dependencies": { - "@wippy-fe/theme": "^0.0.40", - "@wippy-fe/webcomponent-core": "^0.0.40", - "@wippy-fe/webcomponent-vue": "^0.0.40", + "@wippy-fe/theme": "^0.0.41", + "@wippy-fe/webcomponent-core": "^0.0.41", + "@wippy-fe/webcomponent-vue": "^0.0.41", "primevue": "^4.3.3" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "@vitejs/plugin-vue": "^5.0.0", - "@wippy-fe/vite-plugin": "^0.0.40", - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/vite-plugin": "^0.0.41", + "@wippy-fe/proxy": "^0.0.41", "autoprefixer": "^10.4.0", "eslint": "^8.57.0", "eslint-plugin-vue": "^9.0.0", @@ -34,7 +34,7 @@ "vue-tsc": "^2.0.0" }, "peerDependencies": { - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/proxy": "^0.0.41", "vue": "^3.5.0" }, "wippy": { diff --git a/frontend/web-components/reaction-bar/package.json b/frontend/web-components/reaction-bar/package.json index 8793b52..78387d2 100644 --- a/frontend/web-components/reaction-bar/package.json +++ b/frontend/web-components/reaction-bar/package.json @@ -11,17 +11,17 @@ "package.json" ], "dependencies": { - "@wippy-fe/theme": "^0.0.40", - "@wippy-fe/webcomponent-core": "^0.0.40", - "@wippy-fe/webcomponent-vue": "^0.0.40", + "@wippy-fe/theme": "^0.0.41", + "@wippy-fe/webcomponent-core": "^0.0.41", + "@wippy-fe/webcomponent-vue": "^0.0.41", "primevue": "^4.3.3" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "@vitejs/plugin-vue": "^5.0.0", - "@wippy-fe/vite-plugin": "^0.0.40", - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/vite-plugin": "^0.0.41", + "@wippy-fe/proxy": "^0.0.41", "autoprefixer": "^10.4.0", "eslint": "^8.57.0", "eslint-plugin-vue": "^9.0.0", @@ -34,7 +34,7 @@ "vue-tsc": "^2.0.0" }, "peerDependencies": { - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/proxy": "^0.0.41", "vue": "^3.5.0" }, "wippy": { diff --git a/frontend/web-components/websocket-log/package.json b/frontend/web-components/websocket-log/package.json index 70c2c4c..f5b1da6 100644 --- a/frontend/web-components/websocket-log/package.json +++ b/frontend/web-components/websocket-log/package.json @@ -11,16 +11,16 @@ "package.json" ], "dependencies": { - "@wippy-fe/theme": "^0.0.40", - "@wippy-fe/webcomponent-core": "^0.0.40", - "@wippy-fe/webcomponent-vue": "^0.0.40" + "@wippy-fe/theme": "^0.0.41", + "@wippy-fe/webcomponent-core": "^0.0.41", + "@wippy-fe/webcomponent-vue": "^0.0.41" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "@vitejs/plugin-vue": "^5.0.0", - "@wippy-fe/vite-plugin": "^0.0.40", - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/vite-plugin": "^0.0.41", + "@wippy-fe/proxy": "^0.0.41", "eslint": "^8.57.0", "eslint-plugin-vue": "^9.0.0", "typescript": "^5.0.0", @@ -30,7 +30,7 @@ "vue-tsc": "^2.0.0" }, "peerDependencies": { - "@wippy-fe/proxy": "^0.0.40", + "@wippy-fe/proxy": "^0.0.41", "vue": "^3.5.0" }, "wippy": { diff --git a/src/app/_index.yaml b/src/app/_index.yaml index d2cc2fc..e6b40eb 100644 --- a/src/app/_index.yaml +++ b/src/app/_index.yaml @@ -49,6 +49,13 @@ entries: - endpoint_firewall prefix: /api/v1/ + # Public, unauthenticated login page. Own prefix (the facade owns prefix "/"). + - name: web + kind: http.router + meta: + server: gateway + prefix: /login + - name: db kind: db.sql.sqlite file: ./.wippy/app.db diff --git a/src/app/api/_index.yaml b/src/app/api/_index.yaml index cb6086d..520e9d8 100644 --- a/src/app/api/_index.yaml +++ b/src/app/api/_index.yaml @@ -35,3 +35,53 @@ entries: method: GET func: login_page path: /auth/login + + # Jet login (default login page) — server-side cookie theme rendering (zero + # flash). Served at the site root /login via the public `web` router. The + # static static/login.html (at /app/login.html) is the alternative example. + - name: login_templates + kind: template.set + meta: + comment: Login page Jet template set + + - name: login + kind: template.jet + meta: + content_type: text/html + comment: Jet login page — bakes the theme class onto from the cookie + source: file://login.jet + set: app.api:login_templates + + - name: theme_read + kind: security.policy + meta: + comment: Lets the Jet login read the facade theme key/mode and its own template set + policy: + resources: + - wippy.facade:* + - app.api:* + actions: + - registry.get + effect: allow + + - name: login_jet + kind: function.lua + source: file://login_jet.lua + method: handler + modules: + - http + - registry + - templates + security: + actor: + id: app.api:login_jet + policies: + - app.api:theme_read + + - name: login_jet.endpoint + kind: http.endpoint + meta: + router: app:web + method: GET + func: login_jet + path: /{$} diff --git a/src/app/api/login.jet b/src/app/api/login.jet new file mode 100644 index 0000000..2d4c3fc --- /dev/null +++ b/src/app/api/login.jet @@ -0,0 +1,131 @@ + + + + + + + Wippy - Sign In + + + + + + + + + + + + + + + diff --git a/src/app/api/login_jet.lua b/src/app/api/login_jet.lua new file mode 100644 index 0000000..92b0ada --- /dev/null +++ b/src/app/api/login_jet.lua @@ -0,0 +1,80 @@ +local http = require("http") +local registry = require("registry") +local templates = require("templates") + +-- Jet-rendered login example. Demonstrates server-side cookie theme rendering: +-- the chosen theme (persisted by window.wippyThemePersist as a cookie) is read +-- from the request and baked onto before the response is sent, so the +-- login page paints in the right theme with no flash — the same technique the +-- facade's Jet shell uses. Compare with the static static/login.html example. + +local TEMPLATE_SET = "app.api:login_templates" + +-- Read a facade requirement's default (the configured theme key / mode). +local function facade_req(name: string): string + local entry, _ = registry.get("wippy.facade:" .. name) + if entry and entry.data then + return entry.data.default or "" + end + return "" +end + +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 + +local function handler() + local req = http.request() + local res = http.response() + + local persist = facade_req("theme_persist") + local key = facade_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("login", { + hasTheme = has_theme, + themeClass = theme_class, + colorScheme = color_scheme, + }) + if err then + res:set_status(http.STATUS.INTERNAL_ERROR) + res:set_content_type("text/html") + res:write("Failed to render login page") + return nil, err + end + + res:set_content_type("text/html") + res:set_header("Cache-Control", "no-store") + res:set_status(http.STATUS.OK) + res:write(html) +end + +return { handler = handler } diff --git a/src/app/deps/_index.yaml b/src/app/deps/_index.yaml index b46f1fd..2cc74d5 100644 --- a/src/app/deps/_index.yaml +++ b/src/app/deps/_index.yaml @@ -211,8 +211,11 @@ entries: value: "false" - name: hide_nav_bar value: "true" + # Default login is the Jet page at /login (server-side cookie theme + # rendering). The static static/login.html (/app/login.html) is the + # alternative example. - name: login_path - value: /app/login.html + value: /login - name: session_type value: non-persistent - name: history_mode @@ -221,6 +224,15 @@ entries: # regardless of OS; the sidebar switch can still override at runtime. - name: theme_mode value: auto + # Theme persistence is OPT-IN: the facade defaults theme_persist to "none" + # (no persistence). This app opts in with "cookie" so the Jet shell + Jet + # login apply the saved theme server-side (no flash); "localStorage" is the + # client-only alternative. The facade serves + # /api/public/facade/theme-persist.js with this key baked in. + - name: theme_persist + value: cookie + - name: theme_storage_key + value: "@wippy-theme-mode" # Theming files live in ./static alongside login.html (served at /app), so # content_fs resolves them from the same folder a static page could . # `fs://` (not `file://`) keeps the value literal: the wippy loader reserves diff --git a/static/login.html b/static/login.html index 68be3cd..0bbf03e 100644 --- a/static/login.html +++ b/static/login.html @@ -5,10 +5,16 @@ Wippy - Sign In - - - - + + + + + + +
+ + + +
@@ -158,6 +212,44 @@

Wippy App