cli-router 0.1.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. cli_router-0.3.0/.claude/skills/tui-design/SKILL.md +314 -0
  2. cli_router-0.3.0/.claude/skills/tui-design/references/cli-basics.md +493 -0
  3. cli_router-0.3.0/.claude/skills/tui-design/references/ecosystem-go.md +482 -0
  4. cli_router-0.3.0/.claude/skills/tui-design/references/ecosystem-python.md +414 -0
  5. cli_router-0.3.0/.claude/skills/tui-design/references/ecosystem-rust.md +403 -0
  6. cli_router-0.3.0/.claude/skills/tui-design/references/ecosystem-typescript.md +420 -0
  7. cli_router-0.3.0/.claude/skills/tui-design/references/exemplar-apps.md +475 -0
  8. cli_router-0.3.0/.claude/skills/tui-design/references/interaction-patterns.md +535 -0
  9. cli_router-0.3.0/.claude/skills/tui-design/references/visual-patterns.md +676 -0
  10. {cli_router-0.1.0 → cli_router-0.3.0}/.github/workflows/publish.yml +6 -6
  11. {cli_router-0.1.0 → cli_router-0.3.0}/.github/workflows/test.yml +2 -2
  12. {cli_router-0.1.0 → cli_router-0.3.0}/.gitignore +1 -1
  13. cli_router-0.3.0/AGENTS.md +287 -0
  14. cli_router-0.3.0/CHANGELOG.md +71 -0
  15. cli_router-0.3.0/PKG-INFO +313 -0
  16. cli_router-0.3.0/README.md +286 -0
  17. {cli_router-0.1.0 → cli_router-0.3.0}/cli_router/__init__.py +1 -1
  18. cli_router-0.3.0/cli_router/cli.py +254 -0
  19. {cli_router-0.1.0 → cli_router-0.3.0}/cli_router/config.py +39 -6
  20. cli_router-0.3.0/cli_router/doctor.py +425 -0
  21. {cli_router-0.1.0 → cli_router-0.3.0}/cli_router/failures.py +38 -3
  22. cli_router-0.3.0/cli_router/logs.py +107 -0
  23. cli_router-0.3.0/cli_router/modelcache.py +69 -0
  24. cli_router-0.3.0/cli_router/models.py +246 -0
  25. cli_router-0.3.0/cli_router/presets/generic.yaml +120 -0
  26. cli_router-0.3.0/cli_router/presets/grok.yaml +9 -0
  27. cli_router-0.3.0/cli_router/runner.py +209 -0
  28. cli_router-0.3.0/cli_router/runs.py +119 -0
  29. cli_router-0.3.0/cli_router/streamfmt.py +179 -0
  30. cli_router-0.3.0/cli_router/tui.py +1996 -0
  31. cli_router-0.3.0/cli_router/workflows.py +481 -0
  32. {cli_router-0.1.0 → cli_router-0.3.0}/examples/cli-router.yaml +20 -0
  33. {cli_router-0.1.0 → cli_router-0.3.0}/pyproject.toml +2 -1
  34. cli_router-0.3.0/tests/test_cli.py +218 -0
  35. cli_router-0.3.0/tests/test_config.py +144 -0
  36. cli_router-0.3.0/tests/test_doctor.py +237 -0
  37. {cli_router-0.1.0 → cli_router-0.3.0}/tests/test_failures.py +17 -0
  38. cli_router-0.3.0/tests/test_logs.py +45 -0
  39. cli_router-0.3.0/tests/test_modelcache.py +35 -0
  40. cli_router-0.3.0/tests/test_models.py +195 -0
  41. cli_router-0.3.0/tests/test_runner.py +175 -0
  42. cli_router-0.3.0/tests/test_runs.py +98 -0
  43. cli_router-0.3.0/tests/test_streamfmt.py +96 -0
  44. cli_router-0.3.0/tests/test_tui.py +1151 -0
  45. cli_router-0.3.0/tests/test_workflows.py +459 -0
  46. cli_router-0.1.0/AGENTS.md +0 -204
  47. cli_router-0.1.0/CHANGELOG.md +0 -5
  48. cli_router-0.1.0/PKG-INFO +0 -172
  49. cli_router-0.1.0/README.md +0 -145
  50. cli_router-0.1.0/cli_router/cli.py +0 -96
  51. cli_router-0.1.0/cli_router/presets/generic.yaml +0 -60
  52. cli_router-0.1.0/cli_router/runner.py +0 -75
  53. cli_router-0.1.0/cli_router/workflows.py +0 -176
  54. cli_router-0.1.0/tests/test_cli.py +0 -78
  55. cli_router-0.1.0/tests/test_config.py +0 -49
  56. cli_router-0.1.0/tests/test_runner.py +0 -42
  57. cli_router-0.1.0/tests/test_workflows.py +0 -155
  58. {cli_router-0.1.0 → cli_router-0.3.0}/LICENSE +0 -0
  59. {cli_router-0.1.0 → cli_router-0.3.0}/PRD.md +0 -0
  60. {cli_router-0.1.0 → cli_router-0.3.0}/cli_router/artifacts.py +0 -0
  61. {cli_router-0.1.0 → cli_router-0.3.0}/cli_router/extractors.py +0 -0
  62. {cli_router-0.1.0 → cli_router-0.3.0}/cli_router/presets/__init__.py +0 -0
  63. {cli_router-0.1.0 → cli_router-0.3.0}/cli_router/presets/claude.yaml +0 -0
  64. {cli_router-0.1.0 → cli_router-0.3.0}/cli_router/presets/codex.yaml +0 -0
  65. {cli_router-0.1.0 → cli_router-0.3.0}/cli_router/presets/hermes.yaml +0 -0
  66. {cli_router-0.1.0 → cli_router-0.3.0}/cli_router/tools.py +0 -0
  67. {cli_router-0.1.0 → cli_router-0.3.0}/examples/claude_codex.yaml +0 -0
  68. {cli_router-0.1.0 → cli_router-0.3.0}/examples/local_models.yaml +0 -0
  69. {cli_router-0.1.0 → cli_router-0.3.0}/tests/test_extractors.py +0 -0
@@ -0,0 +1,314 @@
1
+ ---
2
+ name: tui-design
3
+ description: Design and build clean, professional, minimal terminal UI (TUI) applications and command-line tools. Use this skill whenever the user is building, designing, refactoring, reviewing, or asking about terminal interfaces — full-screen TUIs (file managers, dashboards, monitors, git/k8s tools, REPLs), interactive CLI prompts, or simple command-line utilities. Use it for library questions ("Bubble Tea vs Ratatui vs Textual vs Ink"), design questions ("how should I lay out this dashboard"), and concrete build requests ("build me a TUI for X"), even when the user doesn't say "TUI" explicitly — phrases like "terminal app", "ncurses-style", "interactive shell tool", "CLI dashboard", "fzf-like picker", or naming a known TUI app (lazygit, k9s, btop, helix, yazi) all qualify.
4
+ ---
5
+
6
+ # TUI & CLI Design
7
+
8
+ Build terminal applications that feel professional — the way `lazygit`, `k9s`, `btop`, `helix`, `fzf`, and `yazi` feel. The terminal is enjoying a renaissance: Charm (Go), Ratatui (Rust), Textual (Python), and Ink (TypeScript) have each crystallized a mature philosophy. This skill teaches the universal patterns that make TUIs feel good plus per-ecosystem deep-dives in `references/`.
9
+
10
+ ## When to read which reference
11
+
12
+ Use this skill's body for the **universal principles** below. Then load reference files on demand:
13
+
14
+ | Situation | Read |
15
+ |---|---|
16
+ | User picked Go / mentioned Bubble Tea, Charm, Lipgloss, tview, gocui | `references/ecosystem-go.md` |
17
+ | User picked Rust / mentioned Ratatui, crossterm, tui-rs, Cursive | `references/ecosystem-rust.md` |
18
+ | User picked Python / mentioned Textual, Rich, prompt_toolkit, urwid | `references/ecosystem-python.md` |
19
+ | User picked TS/JS / mentioned Ink, blessed, OpenTUI, Clack, Inquirer | `references/ecosystem-typescript.md` |
20
+ | Building a non-interactive CLI (no full-screen UI) | `references/cli-basics.md` |
21
+ | Designing layout, borders, color, typography, density | `references/visual-patterns.md` |
22
+ | Designing keybindings, focus, navigation, modal vs modeless | `references/interaction-patterns.md` |
23
+ | Studying what makes specific apps great (lazygit, k9s, fzf, btop, helix, yazi, atuin) | `references/exemplar-apps.md` |
24
+ | Testing or debugging a TUI | that ecosystem's `references/ecosystem-*.md` (Testing / Debugging sections) |
25
+ | Inline vs full-screen; clipboard, hyperlinks, notifications (OSC) | `references/visual-patterns.md` → *Inline, alt-screen, or overlay*; `references/interaction-patterns.md` → *Talking to the terminal emulator* |
26
+
27
+ If the user hasn't named a language, ask which ecosystem before diving into framework specifics. The universal principles below apply regardless.
28
+
29
+ ---
30
+
31
+ ## The terminal is a constrained design medium
32
+
33
+ Every cell is the same width. Type size doesn't change. You have ~80×24 characters at the small end, maybe 200×60 if you're lucky. You can't draw arbitrary pixels; you compose grids of characters with foreground/background colors and a handful of attributes (bold, dim, italic, underline, reverse). These constraints are the **point** — they force clarity. When something feels cramped or noisy in a TUI, the answer is almost never "add more"; it's usually "remove something or use whitespace."
34
+
35
+ Three observations that drive everything else:
36
+
37
+ 1. **Spatial memory is the navigation.** Users learn where things live: the file list is left, the diff is right, the status bar is bottom. Once that's established, panels must never move without explicit action. Reordering panels on focus is among the worst sins a TUI can commit.
38
+ 2. **Color encodes meaning, not appearance.** Treat colors as semantic tokens (`status.error`, `text.muted`, `accent.primary`), not raw hex codes. The app should be *usable in monochrome* — color is enhancement, never the only signal. ~8% of males have red-green CVD; pair color with letters or symbols.
39
+ 3. **Keyboard is primary; mouse is augmentation.** Every action must be reachable from the keyboard. Mouse can speed things up but never gates functionality. Vim motions (`hjkl`, `/`, `?`, `Esc`, `q`, `gg`, `G`) are the lingua franca even for non-vim users — supporting them is a courtesy that costs nothing.
40
+
41
+ ## The seven canonical layouts
42
+
43
+ Most successful TUIs use one of these. Choose by workflow shape, not by aesthetics:
44
+
45
+ - **Persistent multi-panel** — All panels visible in fixed positions, focus shifts between them. Numeric keys (`1`–`5`) jump directly. Used by **lazygit, btop, htop**. Best for at-a-glance observation and switching between views of related state.
46
+ - **Miller columns** — Three (or N) columns: parent → current → preview. `h`/`l` ascend/descend. Used by **yazi, ranger, broot**. Best for hierarchies (filesystems, JSON, K8s resources). Degrades poorly on narrow terminals — provide a single-pane fallback.
47
+ - **Drill-down stack** — Browser-style: navigate deeper with a back-stack, `Esc` returns. Often paired with command-mode navigation (`:pods`, `:nodes`). Used by **k9s, lazydocker**. Best when there are many resource types and the user needs to pivot between them.
48
+ - **Widget dashboard** — Independent widgets in a grid, each owning its data lifecycle. Layout configurable via TOML/YAML. Used by **bottom, btop, glances**. Best for monitoring/observability where users want to compose their own view.
49
+ - **IDE three-panel** — Sidebar → main content → detail/output, often with tabs in the main panel. Used by **Posting, Harlequin, helix**. Best for editor-like workflows.
50
+ - **Overlay / popup** — Appears over the shell, does one thing, exits. Used by **fzf, atuin, zoxide+fzf**. Best for "summon → choose → output" interactions. Render either full-screen on the alternate screen or inline with a bounded height (fzf's `--height`); either way, clean up on exit and print the result to stdout. See `references/visual-patterns.md` → *Inline, alt-screen, or overlay*.
51
+ - **Tabbed within panel** — Tab bars cycled with `[`/`]`. Used inside larger layouts (lazygit's Local/Remotes/Tags, lazydocker's Logs/Stats/Env tabs). Best when one panel needs multiple personalities without changing the global layout.
52
+
53
+ The universal rule: **panels never move without explicit user action.**
54
+
55
+ ## Visual hierarchy without varying type size
56
+
57
+ Since you can't change font size, hierarchy comes from:
58
+
59
+ - **Position** — top/left reads first; status bar at bottom; headers at top.
60
+ - **Color and weight** — bold + accent color for titles and focused panel borders; dim for metadata, timestamps, disabled items; default weight for primary text.
61
+ - **Reverse video** — universally available since VT100; the canonical way to mark current selection. Works on every terminal.
62
+ - **Indentation and connectors** — `├─ └─` for trees; consistent indent units (2 cells is standard).
63
+ - **Whitespace and bullets** — `▶` expandable, `▼` expanded, `●` active, `○` inactive, `•` static bullet.
64
+ - **Borders for focus** — border *color* change is the strongest focus indicator. Lipgloss, Ratatui, Textual, and Ink all support per-side border styling.
65
+
66
+ Use **bold** for titles, selection labels, and primary content. Use **dim** for metadata and disabled items. Use **italic** sparingly (poorly supported on many terminals — never the only signal). Use **underline** for hyperlinks (OSC 8) and shortcut hints. Use **reverse video** for the cursor row and current selection. Avoid blink (disabled in most modern terminals; accessibility hazard) and strikethrough (limited support).
67
+
68
+ ## Color as a semantic system
69
+
70
+ Design in three tiers:
71
+
72
+ 1. **Monochrome** — does the app work with `NO_COLOR=1`? If layout, weight, and reverse video carry the meaning, yes.
73
+ 2. **16 ANSI** — does it look right with the user's theme (Solarized, Gruvbox, whatever)? You don't control these; theme-coherent palettes do.
74
+ 3. **256 / truecolor** — fine-grained palette for designed themes (Catppuccin, Dracula, Nord). Detect via `$COLORTERM=truecolor`.
75
+
76
+ **Always respect `NO_COLOR`** (no-color.org). `ripgrep`, `bat`, `eza`, `delta`, `fd` all do.
77
+
78
+ Conventional meanings have crystallized:
79
+ - **Green** → success, added, online
80
+ - **Red** → error, deleted, danger
81
+ - **Yellow** → warning, modified, pending
82
+ - **Cyan / Blue** → info, paths, links
83
+ - **Magenta** → special, highlights
84
+ - **Dim / gray** → secondary, disabled
85
+
86
+ Define semantic tokens (`status.error`, `git.staged`, `text.muted`) and theme them. Lipgloss's `LightDark` (v2; `AdaptiveColor` in v1/compat), Textual's CSS variables, and Ratatui's palette pipelines all implement this indirection. Scattering hex codes through code is a phase you grow out of.
87
+
88
+ **Never use color alone.** Pair with letters (lazygit's file status: `M` modified, `A` added, `D` deleted, `??` untracked) or symbols (delta's `+`/`-` line prefixes). Safe color pairs for CVD: blue+orange, blue+yellow, black+white.
89
+
90
+ ## Borders, density, and whitespace
91
+
92
+ Use single-line borders (`─ │ ┌ ┐ └ ┘`) by default. Rounded (`╭ ╮ ╰ ╯`) is the modern Charm aesthetic — fine, slightly softer. Heavy (`━ ┃ ┏`) for emphasis sparingly. **Avoid double-line** (`═ ║ ╔`) — it reads as "DOS." Always provide ASCII fallback (`+`, `-`, `|`) for legacy SSH and `TERM=dumb`.
93
+
94
+ When to use borders vs whitespace:
95
+ - **Borders** — when the pane has dynamic content needing a visible boundary, when focus state must be shown, when adjacent panels need clear separation.
96
+ - **Whitespace alone** — when content is static (htop has no internal borders) or density matters more than structure. A single blank row often beats a heavy border.
97
+
98
+ Density choices:
99
+ - **Pack** when data is scanned at a glance, updates in real time, or is read horizontally across rows (htop, btop, k9s).
100
+ - **Pad** when reading prose, filling forms, or making single decisions (gum/huh forms, Glow markdown, Posting).
101
+
102
+ Don't decorate. Borders that exist purely for "looks polished" usually make the app feel busier without adding meaning.
103
+
104
+ ## Two reflexes to apply unprompted
105
+
106
+ These are the two things the default instinct misses most, because users rarely ask for them by name — and a strong base model will answer the literal question without raising either. Apply both to **any** layout you design or review, even when the user asked about something else entirely (a color choice, a keybinding, "why does this feel busy"). This is where most of the value is.
107
+
108
+ **1. Run a clutter audit — make "feels busy" countable.** Never answer "it feels noisy" with "simplify it." Count the offenders and name the specific cuts: border-nesting depth (more than *one* border between the terminal edge and the content is too many; an outer full-screen frame is almost always redundant), how many separate signals encode the same state (`[PASS]` + green + `✅` + a row marker is four), markers that sit on every row (a glyph on 100% of rows marks nothing), and the ratio of cells spent on chrome — borders, labels, repeated boilerplate like a full datestamp on every log line — versus actual data. The full method is in `references/visual-patterns.md` → *The clutter audit*.
109
+
110
+ **2. Pressure-test the floor.** A layout designed at the author's own window size is unfinished — they never see it break because they only ever see their own terminal. State concretely what happens at **80×24 and a 60-column tmux split**: what collapses to a single pane, what hides, what truncates, and the "terminal too small" message below the minimum. Multi-column layouts (Miller columns, 2×N grids) must have a single-pane fallback. **Raise this in every layout review even when size was never mentioned** — it is the single most-missed issue in TUI design, and "it looks great on my screen" is exactly the blind spot it addresses. Breakpoint ladder in `references/visual-patterns.md` → *Responsive design*.
111
+
112
+ ## Tables and lists
113
+
114
+ Always:
115
+ - **Align numerics right, text left, dates as fixed-width ISO-8601.**
116
+ - **Truncate, don't wrap, in cells.** Tail truncation (`/usr/local/share/...`) for paths in lists. Middle truncation (`/usr/.../file.txt`) when the basename matters. Reserve a cell for the ellipsis.
117
+ - **Show a count** (`123/45678` like fzf does) when filtering.
118
+ - **Sort indicator** (`▲`/`▼`) on the active column.
119
+ - **Detail-on-Enter** as the universal escape hatch — pressing Enter on a row reveals all fields in a side panel or modal. This lets you hide low-priority columns at narrow widths without losing access to the data.
120
+ - **Virtualize** any list that might exceed a few hundred items. k9s renders thousands of pods, Toolong tails multi-GB logs — both virtualize. Built into Textual `DataTable`, Ratatui `Table`+`TableState`, Bubbles `list`, Ink with `<Static>`.
121
+
122
+ ## Status bars, headers, footers
123
+
124
+ The convention that has converged across nearly every modern TUI:
125
+
126
+ - **Header (top)** — persistent context: what app, what dataset, what mode. htop's CPU/mem meters; k9s's cluster/context/namespace; lazygit's branch and repo.
127
+ - **Main area (middle)** — the panels. This is where the work happens.
128
+ - **Status / mode line** — ephemeral feedback ("Saved", "3 files changed") with auto-fade. Vim-style mode indicators (NORMAL/INSERT/SELECT) with distinct cursor shapes.
129
+ - **Footer hint bar (bottom)** — 3–5 most-useful shortcuts always visible, full reference behind `?`.
130
+
131
+ The footer hint bar is the single most important discoverability tool. htop's F1–F10 strip; lazygit's per-pane hints; Bubble Tea's `bubbles/help` auto-generates from the keymap; Textual's `Footer` widget renders bindings declared via `BINDINGS`. **Don't make users read docs to discover basic actions.**
132
+
133
+ ## Keys: discoverability and conventions
134
+
135
+ **Cross-app conventions** that have crystallized — use these unless you have a strong reason not to:
136
+
137
+ | Key | Action |
138
+ |---|---|
139
+ | `q` | quit |
140
+ | `?` | help |
141
+ | `/` | search |
142
+ | `n` / `N` | next / prev match |
143
+ | `Esc` | cancel / back |
144
+ | `Enter` | confirm / drill in |
145
+ | `Space` | toggle / mark for multi-select |
146
+ | `:` | command mode |
147
+ | `gg` / `G` | top / bottom |
148
+ | `Tab` / `Shift+Tab` | switch focus |
149
+ | `r` | refresh |
150
+ | `1`–`9` | jump to panel / numbered tab |
151
+ | `hjkl` *and* arrows | move (support both) |
152
+
153
+ **Never bind these — they belong to the terminal:**
154
+ - `Ctrl+C` (SIGINT — should always quit cleanly)
155
+ - `Ctrl+Z` (SIGTSTP — suspend; you must restore terminal state on resume)
156
+ - `Ctrl+\` (SIGQUIT)
157
+ - `Ctrl+S` / `Ctrl+Q` (XON/XOFF flow control on legacy terminals)
158
+
159
+ **Discoverability is layered:**
160
+
161
+ 1. Always-visible footer hints (3–5 most useful keys)
162
+ 2. `?` opens a help screen with all bindings
163
+ 3. Leader-key prefixes show a which-key popup (helix's `Space-` menu is the gold standard)
164
+ 4. Command palette (`Ctrl+P`) — every action with a binding should also be a palette command
165
+ 5. Documentation as the last resort, not the first
166
+
167
+ **Modal vs modeless** is a real choice. Modal apps (vim, helix, k9s ex-mode) get denser keybindings and need persistent mode indicators (status-bar color or label) plus distinct cursor shapes. Modeless apps (Textual, Bubble Tea, btop) lean on widget focus. Both are valid; pick one paradigm and stick with it.
168
+
169
+ **Mouse support** is contested. The pragmatic answer: support mouse where it's natural (clicking a tab, scrolling a list, focusing a pane) but require nothing of it. Every mouse-reachable target needs a keyboard equivalent. Note that mouse capture disables terminal text-selection — most emulators bypass with Shift.
170
+
171
+ ## The non-negotiables (terminal hygiene)
172
+
173
+ These four are the difference between an app that feels professional and one that doesn't:
174
+
175
+ 1. **Use the alternate screen for full-screen TUIs.** Don't pollute the user's scrollback. On exit, the terminal returns to where it was.
176
+ 2. **Always restore terminal state on exit — even on panic.** Install panic/atexit handlers that disable raw mode, leave alt screen, and restore the cursor *before* printing the trace. A panicking TUI that leaves raw mode + alt screen is the worst possible UX. Ratatui's `color_eyre` integration, Bubble Tea's `defer p.RestoreTerminal()`, Textual's exception cleanup, Ink's `unmount()` all do this.
177
+ 3. **Handle resize (`SIGWINCH`).** Re-layout on every resize event; debounce rapid resizes. Define a minimum size (typically 80×24) and render a clear "terminal too small" message rather than crash. Use percentages, `fr` units, `min`/`max`, and ratios — never absolute positions.
178
+ 4. **Handle suspend (`Ctrl+Z` / `SIGTSTP`).** On suspend: disable raw mode, leave alt screen, restore cursor, then `kill(0, SIGTSTP)`. On `SIGCONT`: re-enter alt screen and force a full redraw. Windows lacks SIGTSTP; that's fine.
179
+
180
+ Other essentials:
181
+
182
+ - **Never block the UI thread on I/O.** All network/disk/subprocess work happens in goroutines/tasks/promises; results flow back via messages/channels/events.
183
+ - **Don't redraw on a fixed timer.** Redraw on events. Most apps idle at 0 fps until something happens. Cap animations at 30–60 fps.
184
+ - **Logging can't go to stdout.** Alt-screen + raw mode would corrupt the UI. Log to a file (`tea.LogToFile`, `~/.cache/myapp/log`), use a separate console (Textual's `textual console`), or render an in-app log pane (lazygit, k9s).
185
+ - **Cell width ≠ string length.** CJK ideographs are width 2; emoji should be width 2 (legacy `wcwidth` lies). Use `unicode-segmentation` (Rust), `golang.org/x/text` + `mattn/go-runewidth` (Go), `wcwidth` (Python), `string-width` (JS — Ink uses this) — never `len()` or `.length`.
186
+ - **Clipboard, hyperlinks, and desktop notifications go through OSC escapes** (52 / 8 / 9) — the *local* emulator interprets them, so they work over SSH where shelling out to `pbcopy`/`xclip` can't. Support matrices and tmux caveats: `references/interaction-patterns.md` → *Talking to the terminal emulator*.
187
+
188
+ ## Testing and debugging
189
+
190
+ TUIs are testable; teams that skip tests usually just don't know the shape. Three layers, bottom-heavy:
191
+
192
+ 1. **Unit-test the update/event layer as pure functions.** Every modern framework separates state change from rendering — feed a synthetic key event in, assert on state out. Cheapest, least flaky, and catches the "Tab silently stopped working" class of regression. Even Charm's own apps lean on this layer over harnesses.
193
+ 2. **Golden/snapshot the rendered frame** at a *pinned terminal size and color profile* — unpinned size or profile is the #1 cause of snapshot tests that flap in CI. Harnesses: teatest/v2 golden files (Go), `TestBackend` + insta (Rust), Pilot + pytest-textual-snapshot (Python), ink-testing-library frame assertions (TS).
194
+ 3. **PTY end-to-end sparingly** — one or two smoke flows at most; it's slow and the tooling is thin in every ecosystem.
195
+
196
+ Debugging follows one rule: **never write debug output to the terminal the TUI owns** — raw mode + alt screen turn `print` into screen corruption. Log to a file and `tail -f` it in a second terminal, or use the framework's dev console. Exact APIs live in each ecosystem reference's Testing / Debugging sections.
197
+
198
+ ## Performance and compatibility
199
+
200
+ **Truecolor is now safe to assume** in 2026. Detect via `$COLORTERM=truecolor`; fall back to 256 then 16 then monochrome. The Kitty keyboard protocol (CSI u) is supported by kitty, foot, WezTerm, Alacritty, iTerm2, Ghostty, Rio, and Windows Terminal — opt-in for advanced bindings (Ctrl+I distinct from Tab, Shift+Enter distinct from Enter), always with legacy fallback.
201
+
202
+ **SSH and tmux** strip features unless explicitly enabled. For tmux:
203
+ ```
204
+ set -ga terminal-overrides ",*:Tc" # truecolor passthrough
205
+ set -g extended-keys on # CSI u
206
+ set -g extended-keys-format csi-u
207
+ set -g allow-passthrough on # kitty graphics
208
+ set -g mouse on # mouse forwarding
209
+ set -g set-clipboard on # OSC 52 clipboard
210
+ ```
211
+
212
+ **Image protocols are fragmented**: kitty graphics (best quality) → Sixel (broadest compat) → iTerm2 inline. yazi auto-detects and supports all three.
213
+
214
+ ## Accessibility — the honest take
215
+
216
+ TUIs are inherently inaccessible to screen readers. NVDA, JAWS, VoiceOver, and Orca read the visible buffer like a textbox, with no concept of widgets or focus. Best current practices when accessibility matters:
217
+
218
+ - Linear left-to-right, top-to-bottom layouts where possible.
219
+ - Never color-alone signals; pair with words (`[ERROR]`, `[OK]`, `[!]`).
220
+ - Full keyboard parity — every action reachable via keyboard.
221
+ - Provide a `--no-tui` plain mode that just prints output linearly.
222
+ - For Python/Textual specifically, `textual serve` → HTML is currently the best a11y route — same code runs in a browser, where real accessibility tooling exists.
223
+
224
+ If a11y matters seriously, ship a web alternative or a plain-CLI mode alongside the TUI. Don't pretend the TUI alone is accessible.
225
+
226
+ ## Theming
227
+
228
+ Most production TUIs support themes via TOML/YAML config (lazygit, bottom, btop, helix, delta, bat, fzf), TCSS files (Textual), or composable styles (Lipgloss). Light/dark detection via OSC `]11;?` query or `$COLORFGBG`; Lipgloss's `LightDark` and Textual's runtime theme switching are the cleanest implementations.
229
+
230
+ Community palettes you should be able to support: Catppuccin (Latte/Frappé/Macchiato/Mocha), Dracula, Nord, Gruvbox, Tokyo Night, Rose Pine, Solarized, base16. Build your theme via semantic tokens, then map tokens → palette colors. Adding a new theme should be one config file, not a code change.
231
+
232
+ ## Patterns worth naming
233
+
234
+ Refer users to these by name when you spot them:
235
+
236
+ - **The fzf pattern** — instant fuzzy filter as core interaction. Filter must be sub-100ms; show result count; offer `--exact`; `--preview` pane; `Tab` for multi-select. Used by fzf, skim, telescope.nvim, atuin, zoxide, helix, Textual command palette.
237
+ - **The lazygit pattern** — multi-pane with numeric tab navigation. 5+ panels, `1-5` jumps, `Tab` cycles, single letters trigger panel-specific actions, context-sensitive footer. Trade-off: cognitive load — `c` does different things in each panel.
238
+ - **The k9s pattern** — command-driven via vim-style ex-commands (`:pods`, `:nodes`, `:svc`) with tab-completion and aliases. Fast for power users; demands tab-completion or aliases listing for discovery.
239
+ - **The helix pattern** — selection-first modal editing (select-then-act vs vim's act-on-motion); multi-cursor as primary; Tree-sitter integration; `Space` opens which-key popup.
240
+ - **The miller-columns pattern** — three columns (parent / current / preview), `h` ascend, `l` descend. ranger, lf, nnn, yazi, broot.
241
+ - **The command palette pattern** — `Ctrl+P` modal with fuzzy-matched action list. Every action that has a binding should also be a palette command; show keybinding next to command name.
242
+ - **Dual product** — ship CLI + TUI from the same core. helix, atuin, posting, gh all do this. The CLI handles scripts; the TUI handles exploration.
243
+
244
+ For deeper coverage of any of these patterns and the specific apps that exemplify them, read `references/exemplar-apps.md`.
245
+
246
+ ## Common pitfalls
247
+
248
+ Ranked by real-world complaint frequency:
249
+
250
+ 1. **Hardcoded colors clashing with user themes.** Use semantic tokens.
251
+ 2. **Crash on resize.** Subscribe to `SIGWINCH`; debounce; never assume fixed dimensions.
252
+ 3. **Blocking the UI thread on I/O.** Async everything.
253
+ 4. **Color-only signaling.** Add letters or symbols.
254
+ 5. **Unicode glyphs failing on minimal SSH or Windows conhost.** Provide ASCII fallback.
255
+ 6. **Polluting scrollback.** Use the alternate screen.
256
+ 7. **Binding terminal-reserved keys** (Ctrl+C, Ctrl+Z, Ctrl+S/Q).
257
+ 8. **Wall-of-shortcuts with no progressive disclosure.** Footer → `?` → palette.
258
+ 9. **Inconsistent spatial layout** (panels reordering on focus). Don't.
259
+ 10. **Misaligned tables** when text contains CJK or emoji. Use cell-width libraries.
260
+
261
+ ## Decision flow for new TUI/CLI projects
262
+
263
+ When the user asks you to build something:
264
+
265
+ 1. **Is this a one-shot command, a summon-choose-exit tool, or a full-screen app?**
266
+ - One-shot CLI (no UI, exits when done) → load `references/cli-basics.md`. Apply argparse + color + maybe a spinner and you're done.
267
+ - Summon–choose–exit tool (fzf-class picker, prompt, wizard, live progress) → render **inline**, not on the alt screen: bounded height, machine-readable result to stdout, a one-line receipt left in scrollback. See `references/visual-patterns.md` → *Inline, alt-screen, or overlay*.
268
+ - Full-screen interactive (a session you live in) → alt screen; continue.
269
+
270
+ 2. **What ecosystem?**
271
+ - Already chosen → load that ecosystem's reference.
272
+ - Not chosen → ask. Quick guide: Go for compiled binaries with great styling (Bubble Tea); Rust for performance and reliability (Ratatui); Python for rapid development with web-deploy option (Textual); TS/JS for npm distribution and React-familiar teams (Ink).
273
+
274
+ 3. **What's the workflow shape?** Match to one of the seven canonical layouts above before writing any code. Sketch the panels in ASCII first.
275
+
276
+ 4. **What are the 5–8 most common actions?** Those become the always-visible footer hints. Everything else lives behind `?` or the command palette.
277
+
278
+ 5. **What's the data model?** Lists, trees, tables, forms, free-text? This determines which widgets you need and whether to virtualize.
279
+
280
+ 6. **What's the responsive plan across sizes?** Don't design for one window. Walk the breakpoint ladder (wide >120 / standard 80–120 / narrow 60–80 / too-small below) and decide what gets hidden, collapsed, or stacked at each — and the "terminal too small" message below the 80×24 floor. A fixed grid that can't fold to a single pane is a design smell; drill-down degrades more gracefully. See `references/visual-patterns.md` → *Responsive design*.
281
+
282
+ Then, with the ecosystem reference loaded, write the code. The non-negotiables (alt screen, terminal restoration, resize, suspend, async I/O, no UI-thread blocking) apply regardless of language.
283
+
284
+ ## When reviewing or refactoring an existing TUI
285
+
286
+ Walk through this checklist:
287
+
288
+ - Does it use the alternate screen? Does it restore terminal state on panic?
289
+ - Does it handle resize and suspend?
290
+ - Are colors semantic tokens, or hardcoded? Is `NO_COLOR` honored?
291
+ - Is the app usable in monochrome (color removed, layout still readable)?
292
+ - Are there always-visible footer hints? Does `?` show full help?
293
+ - Is every action keyboard-reachable? Are `q` and `Esc` consistent?
294
+ - Are panels in fixed positions? Or do they jump around on focus?
295
+ - **Clutter audit** — border-nesting depth (>1 inside a panel?), duplicate signals encoding one state, markers on every row, chrome-vs-data ratio. Name specific cuts, not "simplify."
296
+ - **Pressure-test the floor** — what does this do at 80×24 and a 60-col tmux split? Is there a degradation plan (what hides / collapses / stacks) and a "too small" message? Flag this even if the user didn't ask about size.
297
+ - Are tables aligned correctly? Do they handle CJK / emoji width?
298
+ - Are long lists virtualized?
299
+ - Does I/O block the UI thread anywhere?
300
+ - Are reserved keys (Ctrl+C/Z/S/Q) bound to anything?
301
+ - Does it ship with at least one popular community theme support (Catppuccin, Gruvbox, etc.) or a way to define one?
302
+ - Is the update/event layer unit-testable as pure functions? Are frame snapshots (if any) pinned to a size and color profile?
303
+
304
+ Most existing TUIs fail 3–5 of these. Calling them out specifically gives the user a concrete improvement path.
305
+
306
+ ---
307
+
308
+ ## Style of help to give
309
+
310
+ When the user asks "should I do X or Y?" — give a recommendation. The terminal renaissance has produced enough convergent design that many questions have a clear best answer (use the alternate screen, support `hjkl`+arrows, honor `NO_COLOR`, use semantic color tokens). Don't hedge on settled questions. Hedge on real tradeoffs (modal vs modeless, mouse support, single-key destructive actions vs always-confirm).
311
+
312
+ When showing code, prefer the idiom of the chosen ecosystem — don't translate Bubble Tea's MVU into Ratatui's immediate-mode and call it good. Each ecosystem has converged on a style; meet it where it is. The reference files document each one in detail.
313
+
314
+ When the user is stuck on a design decision, point at an exemplar app that solved the same problem (`references/exemplar-apps.md`) — concrete examples beat abstract principles for design questions.