Skip to content

fix: prevent dashboard and TUI startup theme flash - #34248

Open
sgtworkman wants to merge 8 commits into
NousResearch:mainfrom
sgtworkman:fix/chat-pty-startup-resize-flash
Open

sgtworkman wants to merge 8 commits into
NousResearch:mainfrom
sgtworkman:fix/chat-pty-startup-resize-flash

Conversation

@sgtworkman

Copy link
Copy Markdown
Contributor

Summary

  • Inject the active dashboard theme into the served SPA HTML so custom user themes paint before React mounts, even on first-ever load with empty localStorage.
  • Cache user dashboard theme definitions and apply theme changes with useLayoutEffect to prevent default-palette flashes on reload/theme hydration.
  • Send the TUI skin before MCP discovery and gate the first Ink paint until skin readiness so the TUI does not briefly render in the default skin.
  • Boot the dashboard-embedded TUI with the browser-reported terminal size to avoid initial resize/flicker churn.

Validation

  • env -u NODE_ENV npm --prefix web ci --include=dev — passed
  • env -u NODE_ENV npm --prefix web run build — passed
  • npm --prefix ui-tui run build — passed
  • venv/bin/python -m py_compile hermes_cli/web_server.py tui_gateway/entry.py tui_gateway/server.py — passed
  • env -u NODE_ENV npx --prefix ui-tui vitest run --root ui-tui src/__tests__/createGatewayEventHandler.test.ts — passed (45 tests)
  • scripts/run_tests.sh tests/hermes_cli/test_web_server.py tests/test_tui_gateway_server.py tests/hermes_cli/test_tui_bundled.py — 338 passed, 1 unrelated existing browser-launch assertion failed in tests/test_tui_gateway_server.py::test_browser_manage_connect_default_local_reports_launch_hint

Manual QA

  • Verified by hard-refreshing Glen's dashboard on the GUNNY-COMMAND theme after v0.15.1: no initial default-theme flash.
  • Smoke-checked dashboard /api/status reports v0.15.1 and served root HTML includes __HERMES_THEME_BOOTSTRAP__ plus embedded chat bootstrap.

Safety

  • Source/dashboard/TUI changes only.
  • No merge, deploy, DNS, production data, or live service mutation in this PR.

sgtworkman and others added 5 commits May 28, 2026 21:41
…up flicker

The /chat tab spawned the full-screen TUI behind a PTY at the default
80x24, then let the browser's startup burst of RESIZE escapes (onopen,
post-layout refit, ResizeObserver) drive it to the real size. Because the
TUI clears and repaints on every resize, users saw it flash through
several full repaints before settling.

Hold the spawn until the browser reports its size, coalescing the rapid
follow-up resizes within a short settle window, then boot the child once
at the final size. Clients that never send a RESIZE fall back to 80x24
after a bounded wait; non-resize bytes that arrive first are buffered and
replayed so no keystrokes are lost.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Ink TUI renders with its built-in DEFAULT_THEME until it receives the
configured skin, which previously rode only on gateway.ready. That event
is emitted after discover_mcp_tools() in the gateway entry point. For
users with MCP servers configured, discovery connects to (often remote)
servers and can take seconds, leaving the TUI in the default theme the
whole time before it visibly snapped to the user's theme.

Emit a cheap, side-effect-free skin.changed event up front, before MCP
discovery, so the theme applies on the first event the TUI processes.
gateway.ready still carries the skin (applySkin is idempotent), so nothing
is lost for old clients or a stale stdout pipe.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
entry.tsx clears the screen and renders App immediately, so the first
frame painted in DEFAULT_THEME and visibly snapped to the user's theme
once the skin event was read off the gateway stdio. Add a $skinReady
atom that App waits on before its first paint: it renders nothing (the
screen is already cleared) until the first skin.changed/gateway.ready
applies the theme, then paints once in the user's colors. A 1.5s
timeout fallback releases the gate so an old gateway that never emits a
skin can't strand the UI on a blank screen.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The dashboard restored the active theme NAME from localStorage but
userThemeDefs started empty, so resolveTheme() fell back to defaultTheme
for custom (user-YAML) themes like GUNNY-COMMAND until
/api/dashboard/themes responded — a visible default-palette flash on
every load. Built-ins ship in the bundle; user themes only exist
server-side, so the name alone can't repaint them.

Cache the full user-theme definitions map in localStorage after the API
returns, seed userThemeDefs from that cache so the first render resolves
the real definition, and pre-apply the cached theme's CSS vars at module
load (before React mounts/paints). The apply effect also moves to
useLayoutEffect so later theme switches commit before paint too. First
render of a known/cached custom theme is now flash-free; only a
first-ever load with no cache still falls back to default.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@alt-glitch alt-glitch added type/bug Something isn't working comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have labels May 29, 2026
@alt-glitch

Copy link
Copy Markdown

Supersedes #24739 — broader fix covering dashboard theme first-paint flash AND TUI skin flash AND dashboard-embedded TUI initial sizing. #24739 only addressed dashboard custom theme flash.

…resize-flash

# Conflicts:
#	tui_gateway/entry.py
#	web/src/themes/context.tsx
@alt-glitch alt-glitch added comp/dashboard Web dashboard / control panel UI (dashboard/, landing) and removed comp/cli CLI entry point, hermes_cli/, setup wizard labels Jun 26, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for covering the dashboard, Ink TUI, and embedded-chat startup paths. The dashboard and TUI premises remain present on current main: web/src/themes/context.tsx:435-496 initializes custom definitions only after the API response, while ui-tui/src/app.tsx:9-24 renders before gateway skin events are applied.

Problems

  • The PTY sizing hunk is based on a superseded lifecycle. Current main creates attach-token sessions through PTY_REGISTRY.attach_or_spawn in hermes_cli/web_server.py:15545-15596, rather than the direct bridge pump the PR modifies. This needs a registry-aware reimplementation.
  • PR hermes_cli/web_server.py:8839 passes raw browser dimensions to PtyBridge.spawn; later resize handling clamps those values in hermes_cli/pty_bridge.py:218-239.

Suggested changes

  • Salvage the dashboard/TUI theme work, then move initial-size negotiation to the current registry spawn boundary and clamp the initial dimensions. Cover both fresh and attach-token PTY paths.

Automated hermes-sweeper review.

Comment thread hermes_cli/web_server.py
@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 13, 2026
…-fixes

# Conflicts:
#	hermes_cli/web_server.py
#	ui-tui/src/app/createGatewayEventHandler.ts
@sgtworkman

Copy link
Copy Markdown
Contributor Author

Addressed on current main in 91b804c.

  • Preserved the dashboard custom-theme hydration and Ink first-paint skin gate.
  • Moved initial terminal-size negotiation ahead of the current PTY_REGISTRY.attach_or_spawn boundary.
  • Both direct and attach-token spawns now receive the coalesced browser size.
  • Initial dimensions are clamped before spawn, and both POSIX and Windows bridges clamp defensively at the spawn boundary too.
  • Buffered input is replayed after spawn. Existing attach-token sessions receive the consumed initial resize.

Proof: PTY startup/resize tests pass 3/3; bridge suites pass 23 with 19 platform skips; TUI typecheck passes and the full TUI suite passes 1125 with 1 skip; dashboard typecheck passes and dashboard tests pass 72/72.

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) comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants