com um clique
hom-terminal-integration
// Use when working on terminal emulation, the TerminalBackend trait, libghostty integration, or PTY management
// Use when working on terminal emulation, the TerminalBackend trait, libghostty integration, or PTY management
Use this skill when building, reviewing, or refactoring Rust code that requires strong maintainability discipline: SRP, DRY, OCP, explicit dependency injection, TDD/ATDD workflow, architecture review, and clean project structure. Complements the project's Rust CLAUDE.md with process rigor.
Use when adding a new harness adapter or modifying an existing one in hom-adapters
Use when modifying TUI rendering, input routing, layout, command bar, or pane management
Use when creating, modifying, or debugging YAML workflow definitions or the workflow engine
Use before making meaningful code changes in HOM to load repository architecture, workflow rules, crate boundaries, feature flags, and verification requirements
Use when building, reviewing, or refactoring Rust code in HOM that requires strong maintainability, testing discipline, and explicit dependency boundaries
| name | hom-terminal-integration |
| description | Use when working on terminal emulation, the TerminalBackend trait, libghostty integration, or PTY management |
Invoke this skill when:
GhosttyBackend in crates/hom-terminal/src/ghostty.rsVt100Backend in crates/hom-terminal/src/fallback_vt100.rsTerminalBackend trait in crates/hom-core/src/traits.rscrates/hom-pty/src/manager.rscrates/hom-pty/src/async_reader.rsghostty-backend feature flagThis is the hardest part of HOM. Each pane runs a REAL terminal emulator:
Harness Process (e.g., claude)
↓ writes ANSI escape sequences to stdout
PTY slave → PTY master
↓ raw bytes
AsyncPtyReader (tokio channel)
↓ Vec<u8> chunks
terminal.process(bytes) ← TerminalBackend implementation
↓ updates internal screen state
terminal.screen_snapshot() → ScreenSnapshot { rows: Vec<Vec<Cell>> }
↓ cell-by-cell mapping
pane_render.rs → ratatui Buffer
↓
ratatui renders to real terminal
Every byte from the harness flows through this pipeline. A bug anywhere means garbled output.
Defined in crates/hom-core/src/traits.rs. Any terminal emulator must implement:
| Method | What it does | Failure mode |
|---|---|---|
new(cols, rows, scrollback) | Create terminal state | — |
process(bytes) | Feed PTY output into VT state machine | Garbled screen |
resize(cols, rows) | Update terminal dimensions | Content clipped or wrapped wrong |
screen_snapshot() | Return all cells with colors and attributes | Wrong rendering |
cursor() | Return cursor position and visibility | Cursor in wrong place |
title() | Return window title set by child process | Cosmetic only |
ghostty-backend, DEFAULT)Current status: Fully implemented in ghostty.rs. This is the default backend.
To build (default):
cargo build # ghostty-backend is the default feature
Requires Zig ≥0.15.x at build time. Install Zig: brew install zig or download from ziglang.org.
Critical risk: libghostty-rs is v0.1.1, pre-1.0. Pin commits. The API WILL change.
vt100-backend)The opt-in fallback in fallback_vt100.rs. Uses the vt100 crate (v0.16).
To build with vt100 fallback (no Zig required):
cargo build --no-default-features --features vt100-backend
Known limitations:
title() always returns None (vt100 0.16 doesn't expose it)screen_mut().set_size() (not parser.set_size())These limitations are acceptable for the current implementation. Most harness TUIs use basic text, colors, and cursor positioning.
crates/hom-terminal/src/color_map.rs maps TermColor → ratatui::style::Color.
When adding a new backend, you must map its color representation to TermColor. The 16 named colors (Black through BrightWhite) plus Indexed(0-255) and Rgb(r,g,b) must all be handled.
crates/hom-pty/src/manager.rs uses portable-pty to:
Critical timing issue: After spawning, the harness needs time to initialize before accepting input. Don't send prompts immediately — wait for detect_completion() == WaitingForInput.
#[test]
fn test_process_simple_text() {
let mut term = Vt100Backend::new(80, 24, 0).unwrap();
term.process(b"Hello, World!");
let snap = term.screen_snapshot();
let first_row: String = snap.rows[0].iter().map(|c| c.character).collect();
assert!(first_row.starts_with("Hello, World!"));
}
#[test]
fn test_process_color_escape() {
let mut term = Vt100Backend::new(80, 24, 0).unwrap();
term.process(b"\x1b[31mRed text\x1b[0m");
let snap = term.screen_snapshot();
assert!(matches!(snap.rows[0][0].fg, TermColor::Red));
}
#[test]
fn test_cursor_movement() {
let mut term = Vt100Backend::new(80, 24, 0).unwrap();
term.process(b"Line 1\nLine 2");
let cursor = term.cursor();
assert_eq!(cursor.row, 1);
}
#[test]
fn test_resize() {
let mut term = Vt100Backend::new(80, 24, 0).unwrap();
term.resize(40, 12);
let snap = term.screen_snapshot();
assert_eq!(snap.cols, 40);
assert_eq!(snap.num_rows, 12);
}
#[cfg(feature = "ghostty-backend")] must never reference vt100 and vice versa.AsyncPtyReader. Blocking the tokio runtime freezes the entire TUI.cargo check passes with default features (ghostty)cargo check --no-default-features --features vt100-backend passes (vt100 fallback path)cargo test -p hom-terminalcargo test -p hom-ptyTermColor variants