The terminal was the last surface in the chat tool panel still wearing someone else's design system, and it had two defects underneath that. The font stack named no Nerd Font, so the private-use glyphs a default zsh prompt draws its separators and branch marks from rendered as notdef boxes. The ~ and the path after it drew fine, which is what made it read as a font bug rather than a missing-glyph one. Both components also carried their own copy of the palette -- four themes in one, fifteen in the other, drifting, defaulting to a near-black that appears nowhere in the token set. Changing --code-bg did nothing to the terminal because the terminal never read a token. They now share one module built from the tokens, so an error in the terminal is the same red as an error anywhere else. TerminalView reconnected on a bare setTimeout with no attempt cap and no error surface, retrying invisibly every three seconds forever against an unreachable backend. It now shares the panel's cap of three and renders the same retry affordance, counting down again once a session is created. Also drops a dead ws.onopen assignment and a 21px padding that was on no spacing scale, and records what the chat page still owes the prototype.
78 lines
4.1 KiB
Markdown
78 lines
4.1 KiB
Markdown
---
|
|
date: 2026-10-19
|
|
pr: pending
|
|
feature: Terminal CYBER theme, Nerd Font fallback, and bounded reconnect
|
|
impact: The chat tool panel's Terminal tab now paints from the CYBER token set instead of a hardcoded One Dark palette, resolves its font through a Nerd Font stack so shell prompts stop rendering as tofu boxes, and stops reconnecting after three failed attempts with an inline retry instead of looping forever. No WebSocket message names, payload shapes, or session lifecycle behavior changed.
|
|
---
|
|
|
|
# Terminal brought onto the CYBER token set
|
|
|
|
The terminal was the last surface in the chat tool panel still wearing someone
|
|
else's design system. Three defects, in order of how visible they were.
|
|
|
|
## Tofu boxes in the prompt
|
|
|
|
The font stack was `Menlo, Monaco, "Courier New", monospace`. A default zsh
|
|
prompt (powerlevel10k, oh-my-zsh) draws its separators, arrows and branch marks
|
|
from the Unicode private use area — `U+E0A0` through `U+E0D4`. None of those
|
|
fonts carry those glyphs, so the browser fell back to drawing notdef boxes for
|
|
the entire prompt line. The `~` and the path after it rendered fine, which is
|
|
what made it read as a font bug rather than a missing-glyph bug.
|
|
|
|
The stack now names Nerd Font variants explicitly, in
|
|
`variables.scss` as `--font-mono`. It is a CSS custom property rather than only
|
|
an SCSS variable because xterm reads it from JS at `Terminal` construction time.
|
|
|
|
## Colors came from a different product
|
|
|
|
Both terminal components carried their own copy of the palette — four themes in
|
|
`TerminalPanel.vue`, fifteen in `TerminalView.vue`, drifting from each other,
|
|
all of them third-party themes with a `#1a1a2e` default that appears nowhere in
|
|
the token set. Changing `--code-bg` did nothing to the terminal, because the
|
|
terminal never read a token.
|
|
|
|
Both now import `utils/hermes/terminal-theme.ts`, which holds a CYBER theme
|
|
built from the tokens plus two user-selectable alternates. The mapping is
|
|
recorded in that file: `--bg-input` for the background, `--text-secondary` for
|
|
foreground, `--accent-primary` for the cursor, and `--error` / `--success` /
|
|
`--warning` / `--accent-info` mapped onto red / green / yellow / blue so an
|
|
error in the terminal is the same red as an error anywhere else.
|
|
|
|
xterm's `ITheme` only accepts hex, so the OKLCH values were converted rather
|
|
than interpolated. The conversion is documented per-color; changing a token
|
|
means updating that file too, which is a real coupling and the reason the
|
|
comment says so.
|
|
|
|
Note the tradeoff on ANSI `cyan`: it is deliberately *not* the accent cyan.
|
|
`--accent-primary` at full strength reads as UI chrome, and a terminal where
|
|
every cyan prompt fragment glows competes with the actual UI. The accent is
|
|
spent on the cursor, and `brightCyan` carries it for text.
|
|
|
|
## Reconnect looped forever
|
|
|
|
`TerminalView.vue` reconnected on a bare `setTimeout` with no attempt cap and no
|
|
error surface, so an unreachable backend produced an invisible retry every three
|
|
seconds indefinitely. `TerminalPanel.vue` already capped at three attempts and
|
|
rendered a retry affordance; `TerminalView.vue` now does the same, sharing the
|
|
same cap and the same `connectionFailed` / `connectionClosed` / `connectionError`
|
|
strings. The counter resets when a session is successfully created, so a
|
|
transient blip does not permanently exhaust the budget.
|
|
|
|
Two smaller items came out of the same pass: `ws.onopen` was assigned twice in
|
|
`TerminalView.vue` with the first assignment dead, and the header used a `21px`
|
|
vertical padding that was on no spacing scale. Both are gone.
|
|
|
|
## Not changed
|
|
|
|
The session list, theme picker, and new-terminal button stay. The static
|
|
prototype has no equivalent of any of them, and deleting working features to
|
|
match a mock is the inverse of the problem worth solving.
|
|
|
|
## Verification
|
|
|
|
`npm run harness:check`, `npm run test` and `npm run build` pass. The existing
|
|
`tests/e2e/terminal.spec.ts` still passes unchanged — it asserts the WebSocket
|
|
URL carries the token, that `create` is sent, that keyboard input is forwarded,
|
|
and that a `resize` with non-zero cols/rows arrives. Those four are exactly the
|
|
behaviors this change had to preserve.
|