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("", "<\\/")
+ return (
+ '"
+ )
+ 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,