Repository navigation
fix(tui): plain top-left screens; raw-mode rows back to column 0; rows cut to width - #234
Conversation
…ws cut to width Co-authored-by: Duyet Le <me@duyet.net> Co-authored-by: duyetbot <bot@duyet.net> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Duyet Le <me@duyet.net> Co-authored-by: duyetbot <bot@duyet.net> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
duyetbot
left a comment
There was a problem hiding this comment.
Re-ran tests/tty_render (5) and tests/status (5): all pass; fish skipped locally (not installed), runs in CI.
Prod pty (80x50, smoke key, temp HOME): auth status has no bare LF after raw mode starts; rows account, credits $53.08, plan free; no Network Error; picker rows aligned.
Launcher raw output has no box chars and no logo glyph; no key... text; filter, credits refresh and configure rows present.
Diff reviewed: mouse/key handling in tui/live.rs untouched, no secrets, no orphaned code found.
Verdict: approve once CI is green.
Co-authored-by: Duyet Le <me@duyet.net> Co-authored-by: duyetbot <bot@duyet.net>
anyr size and startupStartup is wall time for a cold Size budget:
Budget check: ok raw timings[
{
"asset": "anyr-darwin-arm64",
"kind": "native",
"path": "/Users/runner/work/cli/cli/anyr-darwin-arm64",
"bytes": 2537272,
"size": "2.4 MiB",
"version": "0.1.34 (built 2026-10-27 18:38:00)",
"target": "aarch64-apple-darwin",
"os": "macos-latest",
"startup_version": {
"n": 21,
"min_ms": 5.37,
"median_ms": 9.08,
"p95_ms": 31.23,
"mean_ms": 12.64
},
"startup_help": {
"n": 21,
"min_ms": 4.43,
"median_ms": 8.44,
"p95_ms": 17.02,
"mean_ms": 11.01
}
},
{
"asset": "anyr-darwin-x86_64",
"kind": "native",
"path": "/Users/runner/work/cli/cli/anyr-darwin-x86_64",
"bytes": 3037648,
"size": "2.9 MiB",
"version": "0.1.34 (built 2026-10-27 18:37:45)",
"target": "x86_64-apple-darwin",
"os": "macos-latest",
"startup_version": {
"n": 21,
"min_ms": 35.17,
"median_ms": 45.93,
"p95_ms": 58.97,
"mean_ms": 45.96
},
"startup_help": {
"n": 21,
"min_ms": 40.07,
"median_ms": 45.53,
"p95_ms": 57.06,
"mean_ms": 48.44
}
},
{
"asset": "anyr-linux-arm64",
"kind": "native",
"path": "/home/runner/work/cli/cli/anyr-linux-arm64",
"bytes": 3020048,
"size": "2.9 MiB",
"version": "0.1.34 (built 2026-10-27 18:37:12)",
"target": "aarch64-unknown-linux-gnu",
"os": "ubuntu-24.04-arm",
"startup_version": {
"n": 21,
"min_ms": 0.71,
"median_ms": 0.77,
"p95_ms": 0.86,
"mean_ms": 0.78
},
"startup_help": {
"n": 21,
"min_ms": 0.76,
"median_ms": 0.8,
"p95_ms": 0.88,
"mean_ms": 0.81
}
},
{
"asset": "anyr-linux-x86_64",
"kind": "native",
"path": "/home/runner/work/cli/cli/anyr-linux-x86_64",
"bytes": 3520712,
"size": "3.4 MiB",
"version": "0.1.34 (built 2026-10-27 18:39:47)",
"target": "x86_64-unknown-linux-gnu",
"os": "ubuntu-latest",
"startup_version": {
"n": 21,
"min_ms": 0.82,
"median_ms": 0.84,
"p95_ms": 0.88,
"mean_ms": 0.85
},
"startup_help": {
"n": 21,
"min_ms": 0.81,
"median_ms": 0.85,
"p95_ms": 0.9,
"mean_ms": 0.85
}
},
{
"asset": "anyr-windows-x86_64.exe",
"kind": "native",
"path": "D:\\a\\cli\\cli\\anyr-windows-x86_64.exe",
"bytes": 2994688,
"size": "2.9 MiB",
"version": "0.1.34 (built 2026-10-27T18:38:18Z)",
"target": "x86_64-pc-windows-msvc",
"os": "windows-latest",
"startup_version": {
"n": 21,
"min_ms": 9.88,
"median_ms": 11.0,
"p95_ms": 13.69,
"mean_ms": 11.37
},
"startup_help": {
"n": 21,
"min_ms": 10.05,
"median_ms": 11.02,
"p95_ms": 13.06,
"mean_ms": 11.26
}
},
{
"asset": "anyr.wasm",
"kind": "wasm",
"path": "/home/runner/work/cli/cli/target/wasm-pkg/anyr_cli_bg.wasm",
"bytes": 258065,
"size": "252.0 KiB",
"version": "wasm",
"target": "wasm32-unknown-unknown",
"os": ""
}
] |
The 0.1.34
anyr auth statuspicker drifted right on each row, a long scopes error wrapped across lines, and a paid account showedplan —. This PR fixes all three. It also redraws the launcher as plain top-left text, and adds terminal-render tests that run the real binary in a pseudo-terminal.Root causes
Staircase.
term::pick_inline_rawwrote each frame withwriteln!while raw mode was on. Raw mode turns off the terminal's\n→\r\ntranslation, so each row began in the column where the last one ended. The checkbox picker already handled this; the inline picker didn't. Both now call one writer,term::write_raw_frame, which ends every line in\r\nand cuts it to the terminal width. A wrapped line also threw off theMoveUpcount on the next redraw. The relay live view draws through the same function (term::raw_frame_bytes). No other screen enables raw mode: the ratatui screens use the alternate screen, andprompt_secretonly turns echo off.Long rows.
term::label_rowsnow sits on a pure layout core,term::label_lines(groups, width). It cuts each value to the width with…, measured withunicode-width. The divider is capped at the width too, so it no longer wraps at 40 columns. A failed lookup now prints a short reason, e.g.unavailable (timed out). The full error stays in--json(key_error,identity_error), andauth status --verboseprints it on stderr."Network Error: Network Error". This was not the CLI wrapping the error twice. ureq 2 nests an I/O error inside its own
Network Errorkind, so itsDisplayrepeats the kind. A newhttp::unreachable()names the cause once:Could not reach AnyRouter: <url>: timed out,could not connect, orDNS lookup failed.Timeout. The API is not slow. 20 sequential calls to
GET /api/v1/keywith the smoke key took min 0.23 s, p50 0.26 s and max 0.53 s; the first cold call took 0.98 s./v1/mep50 was about 0.22 s. These numbers are from this host, not the owner's Mac, so no API issue was filed. The 3 s client budget left little room for a slow first DNS lookup and TLS handshake. It is now 4 s. An existing test pins thatstatusanswers within 5 s when the gateway hangs, so 4 s is the limit.Plan row. The dash appeared because
/keytimed out, not because a field was missing. The plan now comes from/me'splanwhen the server sends one (duyet/anyrouter#6653), then fromis_free_tier(freeorpaid). When neither answered, the row is hidden. The CLI never prints a bare dash for the plan.Launcher
The orange frame, backdrop, pixel logo and centered card are gone. The launcher draws from row 0, column 0:
term::label_linesEvery capability stays: fuzzy filter, mouse hits and the background credits refresh.
key…is gone (#232). The menu, settings and picker screens follow the same rule: a title row, status rows, one line and the list, with no frame. WithNO_COLORorTERM=dumb, the ratatui theme keeps only bold.TERM=dumbalso uses the typed numbered prompt instead of cursor moves.Tests
tests/tty_render.rs(unix only) runs the real binary in a pty (libc::openpty, with the pty as the controlling terminal). It feeds the output into avt100emulator and asserts on the rendered screen. The binary runs as<shell> -c "exec anyr …"underbash,zsh,fishandsh, at 40, 80 and 200 columns. The cases use a temp HOME, fake agents on PATH and a local scripted API.vt100::Screen::row_wrapped). No frame characters appear.…. The plan row sayspaid.NO_COLOR, no SGR codes appear. WithTERM=dumb, there are no escape bytes at all, and the typed prompt is used.auth statusandmenuoutput has no escape bytes on stdout or stderr.A missing shell prints
SKIP <shell>: not installed on this host. CI setsANYR_PTY_REQUIRE_SHELLS=1, so a missing shell fails there instead of skipping. CI installszshandfishwith apt on Linux (both the coverage job and the build matrix) and installsfishwith brew on macOS.ANYR_TTY_BINpoints the cases at another build, such as a release binary.This host has no fish, so it ran bash, zsh and sh locally. fish runs in CI.
Proof it fails on 0.1.34. These are the same cases with
ANYR_TTY_BINpointing at a 0.1.34 build. Four of five fail. The piped case already passed.Unit tests cover:
raw_frame_bytes: every line ends in\r\n, and the cut keeps color codes whole.label_lines: no row is wider than 40, 80 or 200 columns.http::unreachableagainst a server that never answers: the error readstimed outonce.short_reason, run on the exact 0.1.34 error string./meplan, thenis_free_tier, then hidden.Before / after (VT100 screen dumps from the tests)
anyr auth status, 80 columns, 0.1.34:After:
Launcher, 40 columns, 0.1.34:
Launcher, 80 columns, after:
Checks
cargo fmt --check,cargo clippy --locked --all-targets -- -D warnings,cargo check --lib --no-default-featuresandcargo test --locked --all-targetsall pass locally (linux x86_64).🤖 Generated with Claude Code