La interfaz está localizada; las descripciones y documentos de las Skills se traducen automáticamente con IA a su idioma. El código, los comandos, rutas y términos técnicos se mantienen en el original, y prevalece el documento del autor.
Explorar Skills
Desarrollo · SKILL.md

tui-engineering

tui-engineering

Build production-grade, phone-friendly terminal UIs (full-screen TUI dashboards) with zero terminal libraries — pure-core architecture, ANSI-safe layout math, line-diffed painting, SGR mouse + touch gestures, hit-testing, narrow-width responsive layouts, and a headless test harness. Distilled from the axfleet fleet-monitoring TUI. MUST trigger when the user…

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentación de la Skill

GitHub

name: tui-engineering description: > Build production-grade, phone-friendly terminal UIs (full-screen TUI dashboards) with zero terminal libraries — pure-core architecture, ANSI-safe layout math, line-diffed painting, SGR mouse + touch gestures, hit-testing, narrow-width responsive layouts, and a headless test harness. Distilled from the axfleet fleet-monitoring TUI. MUST trigger when the user says: "build a TUI", "terminal dashboard", "terminal UI", "full-screen CLI", "ncurses-style", "做个 TUI", "终端面板", "终端界面", "命令行 dashboard", "TUI 适配手机/Moshi", "mouse support in terminal", or asks to add tabs/mouse/touch/responsive behavior to an existing terminal program. Cross-platform: works on Claude Code, OpenAI Codex, OpenCode, and OpenClaw.

TUI Engineering — phone-friendly terminal dashboards from scratch

Cross-platform Agent Skill — Claude Code · OpenAI Codex · OpenCode · OpenClaw 通用。

Build a full-screen terminal UI the way axfleet is built: no ncurses, no blessed, no ink — the terminal layer is ~500 lines you own and can test. Every rule in this skill states the failure it prevents; every mechanism has a runnable reference implementation at xingfanxia/axfleet-ui-public (fixture-driven demo — bun install && bun run tui). The reference stack is Bun + TypeScript; the architecture is stack-agnostic.

When to use / not use

  • ✅ Full-screen dashboards, monitors, pickers, multi-tab tools; anything that must stay readable at phone width (Moshi ≈45 cols) over mosh/SSH.
  • ✅ Retrofitting mouse/touch/narrow-mode onto an existing raw-ANSI TUI.
  • ❌ Line-oriented CLI output (just print), one-shot prompts (use readline), or apps already committed to a TUI framework (follow that framework).

Architecture (non-negotiable shape)

contracts/  typed data model — the single shape everything renders from
state.ts    pure transitions: (state, event) → new state (never mutate)
render/     pure renderers: (state, cols, rows) → string[]  (one per tab)
term.ts     THE ONLY module touching stdin/stdout
api.ts      data source behind fetch/stream fns returning {ok,data}|{ok,error}
index.ts    impure event loop wiring term → state → render → paint

Everything except term.ts/index.ts is a pure function — that's what makes layout, input decoding, and gestures unit-testable without a TTY. Transitions return the same reference when nothing changed; the loop skips redraws on identity. Detail: references/architecture.md.

Build order (follow this sequence)

  1. contracts/ — the typed data model. Fixtures and renderers both compile against it, so tsc proves demo data is shape-complete.
  2. ansi.ts + tests — visibleWidth / truncate / padEnd / wrapPlain. All layout math flows through here. CJK/emoji = 2 cells. Never String.length. → architecture.md §2
  3. term.ts — alt screen, raw mode, line-diffed paints, crash restore, pure decodeEvents with split-chunk carry-over + escape timeout. → architecture.md §3, input.md
  4. state.ts + tests — tabs, selection, scroll, data application.
  5. render/frame.ts — chrome + first tab; add the layout-contract test NOW (every tab × sizes: exactly rows lines, each ≤ cols). Future tabs inherit the guarantee by joining the loop. → testing.md
  6. Remaining tabs — one pure renderer each.
  7. Fixtures + demo api — deterministic noise, pure fn of (key, time). The TUI now runs headless; every later change is eyeball-verifiable. → testing.md §fixtures
  8. Mouse/touch — SGR decode → direction-locked gesture machine → hit ranges. Tests before wiring. → input.md
  9. Narrow-mode pass — render every tab at ~45 cols with fixtures, catalog information loss, fix by stacking/compressing, pin with survival tests. → responsive.md
  10. Footer hints + README — document keys; keep single-key next/prev aliases bindable from phone-terminal shortcut slots.

Invariants checklist (verify before calling it done)

  • Frame is exactly rows lines, every line visibleWidth ≤ cols, at ALL sizes (45×30 / 76×24 / 120×40 minimum) — soft-wrap corrupts diff paints
  • Crash path restores the terminal (uncaughtException → term.exit())
  • Unknown CSI consumed through final byte (F5 must not leak "1","5")
  • Trailing escape fragments carried across chunks + escape timeout (~40ms); NEVER eagerly treat chunk-final ESC as the ESC key if ESC quits
  • Mouse modes disabled on exit in reverse order of enable
  • Selectable list rows are ONE line each, list at top of pane (pane line == item index — hit-testing and follow-selection depend on it)
  • Narrow mode stacks/wraps instead of truncating; survival tests assert full message text present at 45 cols
  • Fixtures typed against contracts/; day-spaced noise periods don't divide a day evenly (aliasing)
  • One command runs typecheck + all tests headless (bun run verify)

Phone terminals — what actually arrives (hard-won facts)

Phone actionWhat the TUI receives (Moshi, measured 2026-07)
tapSGR press + release
vertical panwheel-up/down burst
horizontal swipenothing by default — armed only by moshi-hook's live env read of $TMUX_PANE/$ZELLIJ/$HERDR_ENV (that precedence); on detection swipe sends the mux's prefix chord (Ctrl-B n/p). Two verified recipes: tmux conditional binding forwards the chord into a pane titled via OSC 2 (input.md §6.5 — the default-setup path), or bare-shell HERDR_ENV=1 impersonation + speak the chord (§6.6)
Mouse-Mode dragpress/drag/release forwarded (gesture recognizer applies)

Consequences: tap-the-tab-label is primary phone navigation, never make swipe the only path, map D-pad ←/→ to next/prev, keep single-key aliases (n/p) for custom shortcut slots. Full table + gesture state machine: references/input.md.

References

Skills guardadas