Skip to content

fix(dashboard): inline critical-CSS bootstrap for user themes (#36024 salvage) - #65695

Merged
teknium1 merged 4 commits into
mainfrom
salvage/36024-dashboard-theme-flash
Jul 16, 2026
Merged

fix(dashboard): inline critical-CSS bootstrap for user themes (#36024 salvage)#65695
teknium1 merged 4 commits into
mainfrom
salvage/36024-dashboard-theme-flash

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Dashboard user themes no longer flash the default teal on first paint: _serve_index() injects a <style id="hermes-theme-bootstrap"> critical-CSS block when the active theme is a user YAML theme.

Salvage of #36024 by @nnnet with all three review fixes on top (authorship preserved):

  1. Real bundle variable names (verified against web/src/themes/context.tsx + index.css): kept --background-base/--midground-base, replaced --font-sans--theme-font-sans and --font-base-size--theme-base-size, dropped the two nonexistent --color-* vars.
  2. Stale-rule bug fixed via the variables approach: the injected html,body rule now references var(--background-base) etc. instead of literal values — applyTheme() writes those vars inline on documentElement, so live theme switches re-resolve the rule with zero frontend changes and nothing to remove.
  3. Tests added: 11 server-side tests (user theme → block present with correct vars; built-in theme → no block; malformed theme → no crash).

Changes

  • hermes_cli/web_server.py (+81), tests/hermes_cli/test_web_server.py (+183), scripts/release.py AUTHOR_MAP.

Validation

Before After
user YAML theme first paint teal flash until themes API round-trip correct palette from frame one
theme switch in picker stale font/canvas until reload (original PR's approach) re-resolves live via CSS vars
built-in theme n/a no injection (bundled in presets.ts)

scripts/run_tests.sh tests/hermes_cli/test_web_server.py402 passed, 0 failed. Ruff clean. No web/src changes.

Infographic

infographic

nnnet and others added 4 commits July 16, 2026 06:32
…gate flash

User themes (`~/.hermes/dashboard-themes/*.yaml`) reach the SPA only
after `/api/dashboard/themes` resolves at React mount.  The bundle paints
the first frame with the default Hermes Teal canvas — the
`<link rel="stylesheet">` carries `:root{--background-base:#041c1c}`,
the bundled `presets.ts` defines the same surfaces in JS — and then
`ThemeProvider.applyTheme(<user theme>)` flips the inline CSS variables
on `documentElement` once the API response lands.  Visible to the user
as a green canvas behind the loading SPA on every reload when the active
theme is non-default.

Built-in themes do not suffer the same effect because their full
definitions ship inside the bundle, so the SPA already has the palette
before first paint.

This patch closes the gap on the backend side: `_serve_index()` injects
a `<style id="hermes-theme-bootstrap">` block inside `<head>` with the
six critical CSS variables (`--background-base`, `--color-background`,
`--midground-base`, `--color-midground`, `--font-sans`,
`--font-base-size`) plus an `html, body` rule painting the body in the
target palette.  Because the inline `<style>` follows the bundle's
`<link>` in DOM order and matches the same `:root` specificity, the
later declaration wins the cascade — the static canvas behind the SPA is
already the right colour before any JavaScript runs.

`_render_active_theme_bootstrap_css()` looks up the active theme through
the existing `_discover_user_themes()` helper.  No-op for built-in
active themes (empty string returned, no `<style>` injected).  No new
API endpoints, no config flags, no frontend changes.

After `ThemeProvider` mounts and `applyTheme()` writes the same
variables as inline styles on `documentElement`, the values match what
the bootstrap block set, so there is no second-paint discrepancy on the
critical CSS variables.
…le flows through vars

Review fixes for the inline critical-CSS bootstrap (PR #36024):

1. Variable names now match what the bundle actually consumes.
   --color-background, --color-midground, --font-sans and
   --font-base-size appear nowhere in web/src; the real tokens are:
     --background-base / --midground-base  (layerVars(), context.tsx)
     --theme-font-sans / --theme-base-size (typographyVars(), and
       index.css html{font-family:var(--theme-font-sans);
       font-size:var(--theme-base-size)})

2. Stale-rule bug: the injected html,body rule previously baked in
   literal hex/font values. Because the <style> block sits after the
   bundle's <link> at equal specificity and is never removed, switching
   themes in the picker left the old canvas/font until reload. The rule
   now references the same CSS variables instead of literals —
   applyTheme() writes those vars as inline styles on documentElement,
   which outrank this block in the cascade, so runtime theme switches
   re-resolve the rule automatically. No frontend change needed.
…ction

Server-side coverage for the critical-CSS shim (PR #36024 salvage):

- user theme → style block emitted with ONLY real bundle variable names
  (--background-base/--midground-base from layerVars(),
  --theme-font-sans/--theme-base-size from typographyVars()/index.css),
  and an html,body rule expressed via those vars so runtime theme
  switches never leave a stale canvas/font
- built-in / unknown / non-string active theme → no block
- malformed theme YAML and load_config() exceptions → no crash, index
  still serves
- </style> breakout attempt in a theme value stays escaped
- mount_spa integration: block present in <head> for user themes,
  absent for built-ins
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/dashboard Web dashboard / control panel UI (dashboard/, landing) labels Jul 16, 2026

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Comment

Overview

  • Dashboard CSS fix: inline critical-CSS bootstrap for user themes
  • 265 additions

Assessment

  • CSS inlining for theme flash prevention is a reasonable approach
  • No obvious security concerns
  • Pure frontend/style change

Reviewed by Hermes Agent

@teknium1
teknium1 merged commit 7edaaf4 into main Jul 16, 2026
33 checks passed
@teknium1
teknium1 deleted the salvage/36024-dashboard-theme-flash branch July 16, 2026 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants