feat(cli): light-mode color remap covers all skin reads (Rich Panel borders, etc)
Three changes that together make the response Panel readable in light Terminal.app mode: 1. Hook Skin.get_color() at module load so EVERY skin color read goes through _maybe_remap_for_light_mode(). Previously only _hex_to_ansi() and pt's style strings were remapped — Rich Panel borders and body text bypassed the remap and stayed as #FFF8DC (cornsilk on cream). 2. Prime the light-mode detection cache at import time when stdin is a tty. Ensures OSC 11 query happens before any banner/Panel render. 3. Drop status-bar fg colors (#C0C0C0 silver, #888888, #555555, #8B8682) from the remap table — those are paired with a dark navy bg, so remapping them to dark gray would make them invisible the OTHER direction (dark on dark).
This commit is contained in:
@@ -1423,6 +1423,11 @@ def _detect_light_mode() -> bool:
|
||||
# Light-mode equivalents of skin colors that are unreadable on cream
|
||||
# Terminal.app backgrounds. Used by _SkinAwareAnsi to remap colors
|
||||
# at resolution time when light mode is detected.
|
||||
#
|
||||
# IMPORTANT: only remap colors that are used as STANDALONE foregrounds
|
||||
# on the terminal's background. Don't remap colors that are paired
|
||||
# with a dark bg (e.g. status bar text on bg:#1a1a2e) — those would
|
||||
# become invisible the OTHER direction (dark gray on dark navy).
|
||||
_LIGHT_MODE_REMAP: dict[str, str] = {
|
||||
# Original (dark-mode) -> Light-mode replacement (darker, readable)
|
||||
"#FFF8DC": "#1A1A1A", # cornsilk -> near-black
|
||||
@@ -1436,11 +1441,10 @@ _LIGHT_MODE_REMAP: dict[str, str] = {
|
||||
"#F5F5F5": "#1A1A1A",
|
||||
"#FFF0D4": "#1A1A1A",
|
||||
"#CD7F32": "#8A4F1A", # bronze -> darker bronze
|
||||
"#C0C0C0": "#3A3A3A", # silver -> dark gray
|
||||
"#888888": "#444444",
|
||||
"#555555": "#444444",
|
||||
"#8B8682": "#444444",
|
||||
"#FFEFB5": "#3A2A00",
|
||||
# NOTE: skipping #C0C0C0/#888888/#555555/#8B8682 — those are
|
||||
# status-bar foregrounds paired with dark navy bg, where dark
|
||||
# remap values would become invisible.
|
||||
}
|
||||
|
||||
|
||||
@@ -1462,6 +1466,41 @@ def _maybe_remap_for_light_mode(hex_color: str) -> str:
|
||||
_LIGHT_MODE_REMAP_UPPER = {k.upper(): v for k, v in _LIGHT_MODE_REMAP.items()}
|
||||
|
||||
|
||||
def _install_skin_light_mode_hook() -> None:
|
||||
"""Wrap Skin.get_color at import time so EVERY skin color read goes
|
||||
through the light-mode remap. Idempotent."""
|
||||
try:
|
||||
from hermes_cli.skin_engine import Skin # type: ignore[import]
|
||||
except Exception:
|
||||
return
|
||||
if getattr(Skin, "_hermes_light_mode_hook_installed", False):
|
||||
return
|
||||
_orig_get_color = Skin.get_color
|
||||
|
||||
def _wrapped_get_color(self, key, fallback=""):
|
||||
value = _orig_get_color(self, key, fallback)
|
||||
try:
|
||||
return _maybe_remap_for_light_mode(value)
|
||||
except Exception:
|
||||
return value
|
||||
|
||||
Skin.get_color = _wrapped_get_color # type: ignore[method-assign]
|
||||
Skin._hermes_light_mode_hook_installed = True # type: ignore[attr-defined]
|
||||
|
||||
|
||||
_install_skin_light_mode_hook()
|
||||
|
||||
|
||||
# Prime the light-mode detection cache early (at module load) when
|
||||
# we're running interactively so OSC 11 happens before pt grabs the
|
||||
# tty. Skip for non-tty contexts (subagents, gateway, tests).
|
||||
try:
|
||||
if sys.stdin.isatty() and sys.stdout.isatty():
|
||||
_detect_light_mode()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class _SkinAwareAnsi:
|
||||
"""Lazy ANSI escape that resolves from the skin engine on first use.
|
||||
@@ -8128,8 +8167,8 @@ class HermesCLI:
|
||||
from hermes_cli.skin_engine import get_active_skin
|
||||
_skin = get_active_skin()
|
||||
label = _skin.get_branding("response_label", "⚕ Hermes")
|
||||
_resp_color = _skin.get_color("response_border", "#CD7F32")
|
||||
_resp_text = _skin.get_color("banner_text", "#FFF8DC")
|
||||
_resp_color = _maybe_remap_for_light_mode(_skin.get_color("response_border", "#CD7F32"))
|
||||
_resp_text = _maybe_remap_for_light_mode(_skin.get_color("banner_text", "#FFF8DC"))
|
||||
except Exception:
|
||||
label = "⚕ Hermes"
|
||||
_resp_color = "#CD7F32"
|
||||
@@ -11110,12 +11149,12 @@ class HermesCLI:
|
||||
from hermes_cli.skin_engine import get_active_skin
|
||||
_skin = get_active_skin()
|
||||
label = _skin.get_branding("response_label", "⚕ Hermes")
|
||||
_resp_color = _skin.get_color("response_border", "#CD7F32")
|
||||
_resp_text = _skin.get_color("banner_text", "#FFF8DC")
|
||||
_resp_color = _maybe_remap_for_light_mode(_skin.get_color("response_border", "#CD7F32"))
|
||||
_resp_text = _maybe_remap_for_light_mode(_skin.get_color("banner_text", "#FFF8DC"))
|
||||
except Exception:
|
||||
label = "⚕ Hermes"
|
||||
_resp_color = "#CD7F32"
|
||||
_resp_text = "#FFF8DC"
|
||||
_resp_color = _maybe_remap_for_light_mode("#CD7F32")
|
||||
_resp_text = _maybe_remap_for_light_mode("#FFF8DC")
|
||||
|
||||
is_error_response = result and (result.get("failed") or result.get("partial"))
|
||||
already_streamed = self._stream_started and self._stream_box_opened and not is_error_response
|
||||
|
||||
Reference in New Issue
Block a user