Skip to content

feat(tui): pluggable busy-indicator styles (#13610)#17150

Merged
OutThisLife merged 8 commits into
mainfrom
bb/tui-status-indicator-style
Apr 28, 2026
Merged

feat(tui): pluggable busy-indicator styles (#13610)#17150
OutThisLife merged 8 commits into
mainfrom
bb/tui-status-indicator-style

Conversation

@OutThisLife

Copy link
Copy Markdown
Collaborator

Summary

The status-bar FaceTicker rotated through wide-and-variable kaomoji glyphs ((。•́︿•̀。), ( ͡° ͜ʖ ͡°), …) every 2.5s — display widths range from ~5 to ~16 columns, so the rest of the bar (cwd, ctx %, voice, bg counter) shifted on every cycle.

Padding the verb alone (#17116) helped a little but didn't address the dominant jitter source — the glyph itself. Closer fix: let the user pick a stable-width indicator style.

What this adds

Four busy-indicator styles, default keeps current vibe:

  • kaomoji (default) — preserves the existing experience.
  • emoji — single 2-col emoji frame: ⚕ 🌀 🤔 ✨ 🍵 🔮.
  • unicodeunicode-animations braille spinner (1-col, smooth).
  • ascii| / - \ (1-col, max compatibility).

Wires

  • display.tui_status_indicator in DEFAULT_CONFIG (default kaomoji).
  • New config.set / config.get handler for key=indicator with a narrow allow-list.
  • applyDisplay reads the field into UiState.indicatorStyle; the existing 5s mtime poll picks up ~/.hermes/config.yaml edits without restart.
  • /indicator [style] slash command. Bare form shows current; setter fires config.set and optimistically patchUiState so the live TUI swaps immediately (matching /skin UX). Subcommand completion: kaomoji|emoji|unicode|ascii.
  • CommandDef("indicator", ..., subcommands=...) so classic CLI autocomplete + TUI complete.slash both surface it.
  • FaceTicker decouples spinner cadence from verb cadence — glyph runs at the spinner's authored interval (or FACE_TICK_MS for kaomoji), verb stays on the original 2.5s cycle, both re-arm cleanly when style changes.

Test plan

  • npm run type-check — clean
  • npm test -- --run398/398 pass
  • scripts/run_tests.sh tests/test_tui_gateway_server.py tests/hermes_cli/test_commands.py220/220 pass
  • New tests:
    • normalizeIndicatorStyle rejects unknown / non-string
    • applyDisplay → tui_status_indicator covers fan-out + fallback
    • /indicator <style> hot-swaps UiState.indicatorStyle after config.set
    • /indicator sparkle rejects with usage hint, never hits gateway
    • Slash-parity matrix gets '/indicator'config.get

Refs

Supersedes #17116 (bb/tui-status-ticker-width) — that one padded the verb to the longest catalogue entry, which addressed only a small share of the actual width churn. Closes #13610 by giving users a way out instead of pretending kaomoji widths are fixable.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a user-configurable busy-indicator style for the TUI status bar to reduce layout jitter, wiring it through config, gateway RPC, and slash-command UX.

Changes:

  • Introduces display.tui_status_indicator (default kaomoji) and threads it into UiState.indicatorStyle via config sync.
  • Adds /indicator [style] slash command (+ completion wiring) and gateway config.get/config.set support for key=indicator.
  • Updates FaceTicker to render different indicator styles and decouple glyph tick cadence from verb tick cadence; adds targeted tests.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
ui-tui/src/gatewayTypes.ts Adds config field typing for tui_status_indicator; small event union formatting change.
ui-tui/src/components/appChrome.tsx Implements pluggable indicator rendering in FaceTicker with per-style cadence.
ui-tui/src/app/useConfigSync.ts Normalizes indicator style and applies it into UI state from config.get full.
ui-tui/src/app/uiStore.ts Adds indicatorStyle default to the UI store.
ui-tui/src/app/slash/commands/session.ts Adds /indicator slash command with validation + hot-swap via patchUiState.
ui-tui/src/app/interfaces.ts Introduces IndicatorStyle type and adds it to UiState.
ui-tui/src/tests/useConfigSync.test.ts Adds unit tests for indicator normalization and config fan-out.
ui-tui/src/tests/createSlashHandler.test.ts Adds routing + behavior tests for /indicator.
tui_gateway/server.py Adds config.set/get handling for key=indicator.
hermes_cli/config.py Adds default config display.tui_status_indicator.
hermes_cli/commands.py Adds indicator CommandDef for completion/catalog exposure.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ui-tui/src/components/appChrome.tsx Outdated
Comment thread ui-tui/src/app/slash/commands/session.ts
Comment thread tui_gateway/server.py Outdated
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/cli CLI entry point, hermes_cli/, setup wizard labels Apr 28, 2026
@OutThisLife OutThisLife requested a review from Copilot April 28, 2026 21:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ui-tui/src/gatewayTypes.ts Outdated
Comment thread ui-tui/src/components/appChrome.tsx

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tui_gateway/server.py Outdated
The status-bar `FaceTicker` rotated through wide-and-variable kaomoji
glyphs (`(。•́︿•̀。)`, `( ͡° ͜ʖ ͡°)`, …) every 2.5s.  Real display widths range
from ~5 to ~16 columns, so the rest of the bar (cwd, ctx %, voice,
bg counter) shifted on every cycle.  Padding the verb alone (#17116)
helped but didn't address the dominant jitter source — the glyph
itself.

Add four indicator styles, configurable + hot-swappable:

* `kaomoji` (default — preserves the existing vibe; verb is now
  pad-stable so the only width churn left is the kaomoji itself).
* `emoji`  — single 2-col emoji frame (`⚕ 🌀 🤔 ✨ 🍵 🔮`).
* `unicode` — `unicode-animations` braille spinner (1-col, smooth).
* `ascii`  — `| / - \` (1-col, max compat).

Wires:

* `display.tui_status_indicator` in `DEFAULT_CONFIG` (default
  `kaomoji`).
* New JSON-RPC `config.set/get indicator` keys, narrow allow-list.
* `applyDisplay` reads the field and patches `UiState.indicatorStyle`,
  so the existing `mtime` poll picks up `~/.hermes/config.yaml` edits
  within ~5s without a TUI restart.
* `/indicator [style]` slash command (alias `/indicator-style`,
  subcommand completion `kaomoji|emoji|unicode|ascii`).  Bare form
  shows the current style; setter fires `config.set` and
  optimistically `patchUiState({ indicatorStyle })` so the live TUI
  swaps immediately, matching the `/skin` UX.
* `CommandDef("indicator", ..., subcommands=...)` so classic CLI
  autocomplete + TUI `complete.slash` both surface it.
* `FaceTicker` decouples spinner cadence from verb cadence — the
  glyph runs at the spinner's authored interval (or `FACE_TICK_MS`
  for kaomoji), the verb stays on the original 2.5s cycle, and both
  re-arm cleanly when style changes.

Tests:

* `normalizeIndicatorStyle` rejects unknown / non-string input.
* `applyDisplay → tui_status_indicator` covers fan-out + fallback.
* `/indicator <style>` hot-swaps `UiState.indicatorStyle` after a
  successful `config.set`.
* `/indicator sparkle` rejects with the usage hint and never hits
  the gateway.
* Slash-parity matrix gets `'/indicator'` → `config.get`.

Validation:
  cd ui-tui && npm run type-check — clean; npm test --run — 398/398.
  scripts/run_tests.sh tests/test_tui_gateway_server.py
  tests/hermes_cli/test_commands.py — 220/220.
…rror format

Round 1 Copilot review on PR #17150:

- Exported `INDICATOR_STYLES` const tuple from `interfaces.ts`;
  `IndicatorStyle` union type is derived from it. `useConfigSync`
  builds its validation Set from the tuple, and `session.ts` uses it
  for both the usage hint and the runtime allow-list — adding/removing
  a style now touches one line.
- Backend `config.set indicator` error message: switched
  `sorted(allowed)` list repr to `pick one of ascii|emoji|kaomoji|unicode`
  (matches the TUI usage hint), and reports the normalized `raw`
  instead of the original `value`. Backend allowed tuple now has a
  comment pointing back at `INDICATOR_STYLES` so the two stay aligned.

Note: kept the verb portion unpadded per design intent — fixed-width
padding was the exact UX the `/indicator` command was added to remove.
Stable width comes from the glyph; verbs cycling is part of the kawaii
aesthetic. Reply on the verb thread will explain.
Round 2 Copilot review on PR #17150:

- `tui_status_indicator?: 'ascii' | ... | string` collapses to `string`
  in TS — consumers got no narrowing. Documented as plain `string` with
  a comment about runtime validation via `normalizeIndicatorStyle`.
- `FaceTicker` always started a 2.5s verb interval, even for the
  `unicode` style which hides the verb entirely. Now gated on
  `showVerb` from `renderIndicator` — `unicode` stays calm.

Pre-emptive self-review (avoid round 3):
- Three call sites duplicated the literal `'kaomoji'` default
  (uiStore, normalizeIndicatorStyle, slash command). Added
  `DEFAULT_INDICATOR_STYLE` to interfaces.ts and threaded it through
  so changing the default touches one line.
@OutThisLife OutThisLife force-pushed the bb/tui-status-indicator-style branch from db3f220 to 112495a Compare April 28, 2026 22:58
…render

Round 4 Copilot review on PR #17150: `config.get` for `indicator`
returned the raw `display.tui_status_indicator` value without
validation, so a hand-edited config.yaml with stray casing or an
unknown style would leave `/indicator` printing one thing while
the TUI rendered the kaomoji default (frontend's
`normalizeIndicatorStyle` does this normalization on receive).

Lifted the allow-list to module scope as `_INDICATOR_STYLES` /
`_INDICATOR_DEFAULT`, reused by both `config.set` and `config.get`.
Comment notes the alignment with `INDICATOR_STYLES` /
`DEFAULT_INDICATOR_STYLE` in interfaces.ts so adding/removing a
style is a one-line change on each end.

Tests cover: known value verbatim, casing/whitespace normalize,
unknown→default, unset→default.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tui_gateway/server.py
…cator error

Round 5 Copilot review on PR #17150: `raw = str(value or "").strip().lower()`
collapsed any falsy non-string (`0`, `False`, `[]`) to empty string,
so the error message read `unknown indicator: ` with nothing after —
losing the original input.

Switched to `("" if value is None else str(value)).strip().lower()`
so only `None` (the genuine 'no value' case) becomes blank.  Used
`{raw!r}` in the error so the diagnostic is unambiguous (`'0'` vs `0`).

Tests:
- known-value happy path (`'EMOJI'` → `'emoji'`)
- falsy non-string inputs (`0` / `False` / `[]`) surface meaningfully
- `None` keeps the blank-repr error
@OutThisLife OutThisLife merged commit 7d81d76 into main Apr 28, 2026
@OutThisLife OutThisLife deleted the bb/tui-status-indicator-style branch April 28, 2026 23:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TUI status bar width fluctuates when ticker cycles progress names

3 participants