Let agents see and test the TUI the way a user uses it #2

Merged
cyplo merged 6 commits from ai-agent/agent-testing into main 2026-11-08 21:58:33 +00:00 AGit
Collaborator

Agents kept getting conflicts with several copies wrong. Fixtures could only say "keep the conflict", which always meant the first copy, and they set state by calling app methods, so the keys themselves were never tested.

Fixtures now press keys through the terminal's own event loop, name the winning copy (keep_conflict_b) and snapshot their final screen. A screen example renders any directory for an agent to look at, and three tests run the real binary in a pseudo-terminal. The one behaviour change is that conflicts are now listed in a stable order. This should land before #1.

I checked a three-way conflict by viewing the screen example's output as a PNG.

Logic flow

flowchart TD
  A[scan directory] --> B[sort groups by path, copies by name]
  B --> C[draw first frame]
  C --> D[read key]
  D --> E[update app]
  E --> F{quit?}
  F -- no --> G[draw frame]
  G --> D
  F -- yes --> H[restore terminal]
  classDef pr fill:#ffd966,stroke:#b8860b,color:#000
  class B pr
Agents kept getting conflicts with several copies wrong. Fixtures could only say "keep the conflict", which always meant the first copy, and they set state by calling app methods, so the keys themselves were never tested. Fixtures now press keys through the terminal's own event loop, name the winning copy (`keep_conflict_b`) and snapshot their final screen. A `screen` example renders any directory for an agent to look at, and three tests run the real binary in a pseudo-terminal. The one behaviour change is that conflicts are now listed in a stable order. This should land before #1. I checked a three-way conflict by viewing the `screen` example's output as a PNG. ## Logic flow ```mermaid flowchart TD A[scan directory] --> B[sort groups by path, copies by name] B --> C[draw first frame] C --> D[read key] D --> E[update app] E --> F{quit?} F -- no --> G[draw frame] G --> D F -- yes --> H[restore terminal] classDef pr fill:#ffd966,stroke:#b8860b,color:#000 class B pr ```
Runs deescalate under portable-pty and reads its screen through vt100, to
cover what in-process tests cannot: the first frame appearing without
input, a key showing up in the next frame, and quitting restoring the
terminal. TMUX and TERM_PROGRAM are removed from the child's environment
so it can never reach a tmux server the tests happen to run in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Groups were listed in hash-map order, so the list reshuffled on every
launch and fixtures that select by position were flaky. Groups are now
sorted by original path and copies by file name, which embeds the
Syncthing date, so [a] is always the oldest copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixtures now say which keys a user presses (keys = ["b", "c", "y"])
instead of calling app methods, and run through the same event loop as
the real terminal, now in the library as tui::run_event_loop. That
exercises the keymap itself and lets a fixture name which of several
conflict copies wins (keep_conflict_b). Fixture tables reject unknown
fields, so an outdated or misspelt field fails instead of being ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each fixture now also compares its last frame, text plus styles in
ratatui's buffer format, with screen.snap next to its expected.toml, so
a reviewer or agent can read exactly what the user sees and any change
to it shows up as a diff. Locally a changed screen is written to
screen.snap.new for review with cargo insta; CI never writes snapshots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cargo run --example screen -- DIR --keys "j b" prints the frame a user
would see after those keys, as text or as SVG for resvg to turn into a
PNG. It runs through the same event loop as the real terminal, on a
temporary copy of DIR with modification times kept, so confirming keys
never touch the real files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md now tells agents to render the screen instead of guessing it,
to write behaviour tests as key-driven fixtures, how to name the winning
copy among several, and how to handle snapshots. The spec's testing
strategy, the fixture schema, README and PLAN match the new harness.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ai-agent force-pushed ai-agent/agent-testing from ea3a59de57 to 3af1f0704a
All checks were successful
CI / Lint (pull_request) Successful in 18s
CI / Run tests (pull_request) Successful in 35s
CI / Nix checks and package build (pull_request) Successful in 1m11s
CI / Lint (push) Successful in 8s
CI / Nix checks and package build (push) Successful in 9s
CI / Run tests (push) Successful in 17s
2026-11-08 21:56:18 +00:00
Compare
cyplo merged commit 3af1f0704a into main 2026-11-08 21:58:33 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
cyplo/deescalate!2
No description provided.