From 5633fa19b879459119c0d19fd433c452e6ef26a4 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Tue, 7 Jul 2026 17:17:51 -0700 Subject: [PATCH] fix(dashboard): advertise truecolor to the embedded chat TUI (#60576) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Headless/hosted deploys run the dashboard server without COLORTERM in the process environment, so chalk inside the PTY-spawned TUI child downgraded every skin hex color to the xterm 256 palette — the default skin's bronze banner border (#CD7F32) snapped to palette 173 (#D7875F, salmon red) and the gold caduceus rendered red/yellow on fresh cloud instances. Local launches never reproduced it because the operator's interactive terminal leaks COLORTERM=truecolor into the server env. xterm.js always renders 24-bit RGB, so the dashboard PTY child should always advertise truecolor: backfill COLORTERM=truecolor in _resolve_chat_argv via setdefault (an explicit operator value wins). Verified with a clean-env PTY probe of the real TUI binary: no COLORTERM -> 0 truecolor SGRs / 165 palette-256 (salmon 38;5;173); with the backfill -> 166 truecolor SGRs, exact bronze 38;2;205;127;50. --- hermes_cli/web_server.py | 11 ++++++++++ tests/hermes_cli/test_web_server.py | 33 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 4ae8c3251d2..3a854033195 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -13087,6 +13087,17 @@ def _resolve_chat_argv( # the dashboard PTY path. env.setdefault("HERMES_TUI_DISABLE_MOUSE", "1") env.setdefault("HERMES_TUI_INLINE", "1") + # The dashboard terminal is xterm.js, which always renders 24-bit RGB. + # But chalk inside the TUI child decides its color depth from the + # SERVER process env — and hosted/cloud deploys run the dashboard under + # a process manager (container init, systemd) with no COLORTERM, so + # chalk downgrades every hex color to the xterm 256 palette. The skin's + # bronze border #CD7F32 snaps to palette 173 (#D7875F, salmon-red) and + # the banner reads red/yellow instead of gold. Local launches dodge + # this only because the operator's interactive terminal leaks + # COLORTERM=truecolor into os.environ. Backfill it for the PTY child; + # setdefault so an explicit operator value still wins. + env.setdefault("COLORTERM", "truecolor") env["HERMES_TUI_DASHBOARD"] = "1" if profile_dir is not None: diff --git a/tests/hermes_cli/test_web_server.py b/tests/hermes_cli/test_web_server.py index 18f5facbb6b..dc1bfcccf79 100644 --- a/tests/hermes_cli/test_web_server.py +++ b/tests/hermes_cli/test_web_server.py @@ -5919,6 +5919,39 @@ class TestPtyWebSocket: assert env["HERMES_TUI_INLINE"] == "1" assert env["HERMES_TUI_DISABLE_MOUSE"] == "1" + def test_resolve_chat_argv_backfills_colorterm_truecolor(self, monkeypatch): + """Headless servers (cloud/systemd) have no COLORTERM, which made + chalk in the TUI child degrade skin hex colors to the xterm 256 + palette (gold banner rendered salmon-red). xterm.js always supports + 24-bit color, so the PTY env must advertise truecolor.""" + import hermes_cli.main as main_mod + + monkeypatch.setattr( + main_mod, + "_make_tui_argv", + lambda project_root, tui_dev=False: (["node", "dist/entry.js"], "/tmp/ui-tui"), + ) + monkeypatch.delenv("COLORTERM", raising=False) + + _argv, _cwd, env = self.ws_module._resolve_chat_argv() + + assert env["COLORTERM"] == "truecolor" + + def test_resolve_chat_argv_keeps_operator_colorterm(self, monkeypatch): + """An explicit operator COLORTERM wins over the backfill.""" + import hermes_cli.main as main_mod + + monkeypatch.setattr( + main_mod, + "_make_tui_argv", + lambda project_root, tui_dev=False: (["node", "dist/entry.js"], "/tmp/ui-tui"), + ) + monkeypatch.setenv("COLORTERM", "24bit") + + _argv, _cwd, env = self.ws_module._resolve_chat_argv() + + assert env["COLORTERM"] == "24bit" + def test_resolve_chat_argv_applies_terminal_backend_config( self, monkeypatch, _isolate_hermes_home ):