diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 3ba9c1926..6f0df5e2c 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -16304,6 +16304,64 @@ def _normalise_prefix(raw: Optional[str]) -> str: return normalise_prefix(raw) +def _render_active_theme_bootstrap_css() -> str: + """Critical-CSS shim for the active user theme. + + Returns a ```` escape — current values are well-known + # hex/font strings, but this keeps the helper safe if it is + # later extended to ship user-authored CSS literals. + def _esc(s: str) -> str: + return str(s).replace("' + ":root{" + f"--background-base:{_esc(bg_hex)};" + f"--color-background:{_esc(bg_hex)};" + f"--midground-base:{_esc(mg_hex)};" + f"--color-midground:{_esc(mg_hex)};" + f"--font-sans:{_esc(font_sans)};" + f"--font-base-size:{_esc(base_size)};" + "}" + f"html,body{{background-color:{_esc(bg_hex)};color:{_esc(mg_hex)};" + f"font-family:{_esc(font_sans)};font-size:{_esc(base_size)};}}" + "" + ) + return "" + except Exception: + _log.debug("theme bootstrap render failed", exc_info=True) + return "" + + def mount_spa(application: FastAPI): """Mount the built SPA. Falls back to index.html for client-side routing. @@ -16378,6 +16436,16 @@ def mount_spa(application: FastAPI): html = html.replace('href="/fonts/', f'href="{prefix}/fonts/') html = html.replace('href="/ds-assets/', f'href="{prefix}/ds-assets/') html = html.replace('src="/ds-assets/', f'src="{prefix}/ds-assets/') + # Theme flash mitigation: when the active theme is a user theme + # (``HERMES_HOME/dashboard-themes/.yaml``), inject a minimal + # critical-CSS block so the first paint uses the target palette. + # Without this the SPA paints the default Hermes Teal canvas, then + # ``ThemeProvider`` flips the CSS variables once + # ``/api/dashboard/themes`` resolves. Built-in themes are already + # in the bundle's ``presets.ts`` so no shim is needed for them. + theme_bootstrap = _render_active_theme_bootstrap_css() + if theme_bootstrap: + html = html.replace("", f"{theme_bootstrap}", 1) html = html.replace("", f"{bootstrap_script}", 1) return HTMLResponse( html,