From 154e693dd1a3379291e0757cbbb6e4c0c07fb0fc Mon Sep 17 00:00:00 2001 From: Vyncint Ng <115854244+vyncint@users.noreply.github.com> Date: Wed, 7 Oct 2026 06:36:42 +0700 Subject: [PATCH] =?UTF-8?q?docs(skill):=20rule=209=20gives=20the=20true=20?= =?UTF-8?q?reason,=20and=20=C2=A76=20reads=20the=20Debug=20form=20too?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rule 9 said that returning `termlens::Result<()>` and using `?` makes a failing wait print the grid "instead of `called unwrap() on Err`". It does not: a test that returns `Err` is reported through the harness's `Termination` impl, which prints the error's `Debug` form, and `unwrap()` prints the same `Debug` form. Measured on a timeout carrying a query note: Timeout { waiting_for: "the screen predicate to hold — note: …", timeout: 200ms, screen: Screen(size: 40x4 cursor: 0,5 READY …) } Both show the description, the note and the grid. The rule keeps its advice and now gives the reasons that hold: steps and helpers chain without an `.unwrap()` each, nothing is lost on failure, and `err.to_string()` is the readable `Display` form for a test asserting on an error it expects (#547). The same mismatch was in §6, whose table is keyed on the `Display` form's first line ("timed out after 5s while waiting for …"), which a failing `?` test never prints. §6 now says how that line reads in the `Debug` form (`Timeout { waiting_for: "…", timeout: …, … }`, `Eof { waiting_for: … }`) so the table matches what an agent actually sees. The unanswered-probe row pointed at the README's "Known limitations", which does not exist; it links to the list in docs/LIMITATIONS.md, with an absolute URL because SKILL.md is copied out of the repository (#539). Closes #539 Closes #547 Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com> --- skills/termlens/SKILL.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/skills/termlens/SKILL.md b/skills/termlens/SKILL.md index d5ac39d..443d0f2 100644 --- a/skills/termlens/SKILL.md +++ b/skills/termlens/SKILL.md @@ -128,9 +128,14 @@ your test ── send(Key) · click · paste · resize ──▶ PTY └─ `EndSynchronizedUpdate` — then a `wait_frame` timeout also shows the diff from the last frame it returned to the live screen. -9. **Return `termlens::Result<()>` from the test and use `?`.** The - `Display` of every error carries the screen, so a failing wait prints - the grid the application was showing instead of `called unwrap() on Err`. +9. **Return `termlens::Result<()>` from the test and use `?`.** Steps and + helpers then chain without an `.unwrap()` each, and a failed wait still + prints everything: the test harness shows the error's `Debug` form, + `Error: Timeout { waiting_for: "…", timeout: …, screen: Screen(…) }`, + with the description, any note and the grid. That is the same content + `unwrap()` would print, without the panic. The readable `Display` form + that §6 quotes is `err.to_string()`, for a test that asserts on an + error it expects. 10. **Snapshot the `Screen`, not its text.** `insta::assert_snapshot!(screen)` records the header (`size: 80x24 cursor: 3,5` or `cursor: hidden`) and @@ -396,13 +401,18 @@ fn snapshot_diff_and_mask() -> termlens::Result<()> { Every error's `Display` ends with the screen, under a header that says which screen it is (`--- screen at timeout ---`, `--- final screen ---`). -Read the first line for the cause: +Read the first line for the cause. A test that fails through `?` or +`unwrap()` prints the `Debug` form instead, which carries the same text in +another shape: `timed out after 5s while waiting for X` is +`Timeout { waiting_for: "X", timeout: 5s, screen: Screen(…) }`, notes +included in `X`, and `terminal closed (EOF) while waiting for X` is +`Eof { waiting_for: "X", … }`. The grid follows inside `Screen(…)`: | First line says | Meaning | Do | |---|---|---| | `timed out after 5s while waiting for the screen predicate to hold` | the predicate never became true | look at the embedded grid; the text is usually spelled differently, on another row, or scrolled off (the note says how many rows scrolled) | | `… note: N rows have scrolled off the top` | the text went into history | assert with `full_text()` / `scrollback_text()` | -| `… note: the application queried the terminal (^[[?u …) and received no answer` | the app is blocked on a probe termlens deliberately does not answer | the app needs a fallback; see the termlens README's Known limitations | +| `… note: the application queried the terminal (^[[?u …) and received no answer` | the app is blocked on a probe termlens deliberately does not answer | the app needs a fallback; see [the questions termlens leaves unanswered](https://github.com/vyncint/termlens/blob/main/docs/LIMITATIONS.md#waits-frames-and-queries) | | `terminal closed (EOF) while waiting for …` | the app exited before the predicate held | check `wait_exit()` first, or the app crashed — the final screen shows why | | ``failed to send Char('x') to `…` (the child is gone (exit code 7) and the terminal is closed)`` | input was sent after the child exited | check `wait_exit()` first and inspect the screen embedded under `--- screen at the failed write ---` | | ``failed to send … to `…` (the application is not reading its input, and the PTY buffer is full — no progress in 5s)`` | the child is alive but has stopped draining terminal input | wait for the application to draw the state that is ready for input before typing |