Skip to content

UI polish overhaul: mantis CLI output - #112

Merged
adamXbot merged 11 commits into
mainfrom
feat/tui-polish-rating-c70fe9
Nov 10, 2026
Merged

adamXbot merged 11 commits into
mainfrom
feat/tui-polish-rating-c70fe9

Conversation

@adamXbot

@adamXbot adamXbot commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Scored polish hill climb of the mantis CLI's human-readable output, run with the ui-polish method: eight real screens captured in light and dark at 100 columns against a fixture API with a frozen clock, judged per category by three independent reviewer models against GitHub CLI and Wrangler, fixed where the judges agreed, re-captured and re-judged. The target, 9.0 in every category from every judge in the same round, was never shown to the judges. It cleared in round 8. Round 9 then applied changes the maintainer chose after a comparison against twelve more CLIs and a resize test; Opus and Sonnet re-cleared it, and Fable could not be run that round (usage limit).

--json, --id-only, --url-only, flags and exit codes are unchanged. Every change is to text, layout, glyphs and ANSI attributes. 37 test files, 275 tests pass; four tests were added in round 9 for the middle cut and the error-hint arrow, and two assertions were updated to the new wording (a since cell without "ago", the escaped host rather than the source in a table row).

Score history (overall per judge)

round Fable Opus Sonnet lowest category
0 6.0 6.0 6.3 5.0
1 8.2 7.5 7.8 7.5
2 8.6 8.1 8.4 8.0
3 8.9 8.3 8.5 8.0
4 9.0 8.6 8.7 8.0
5 9.2 8.8 8.8 8.5
6 9.2 8.9 8.8 8.5
7 9.3 9.0 9.0 8.5
8 9.5 9.1 9.2 9.0
9 not run 9.2 9.2 9.0

Round 8 per category: Fable 9.5 in all eight; Opus 9.0 in all eight; Sonnet 9.0 to 9.5. Round 9 per category: Opus and Sonnet 9.0 to 9.5 in all eight, no regressions. References on the same scale, same judges: GitHub CLI 8.0 to 8.5, Wrangler 5.5 to 6.5.

Round 9: the maintainer's picks and the resize fixes

After round 8 the same judges scored mantis beside Homebrew, Ansible, nala, AWS CLI, Azure CLI, fzf, witr, Docker, kubectl, cargo, npm and uv (mantis ranked first at 8.85, ahead of cargo 8.2, uv 8.2 and fzf 8.1). The maintainer picked from what the judges agreed on, and a resize test added three fixes:

  • Error hints print as an indented → line, the same as empty states (formatError).
  • URLs and file paths are underlined at the text colour (c.link); cyan is kept for commands.
  • A failed delivery keeps a red ✗ in its own column at the left edge (the maintainer's call, given to the judges as a constraint), with times right-aligned after it; the ssh arrow sits in a gutter with the ip header over the addresses; since right-aligns like age.
  • Failure reasons hang under their row behind a dim └ (childLines).
  • The new-key wizard reads as a form: questions, a dim default column, every answer bold and starting at column 44; choices and rejections sit in the answer column; inline fallback below 70 columns.
  • Bug fix: the masked webhook redraw moved up one row only, so a real-length Discord URL (about 121 characters) or Slack URL (about 77) that wrapped left the prompt twice and the start of the token on screen. It now clears every row the answer used.
  • Narrow windows: memos and identities cut in the middle on word edges (cutMiddle), headlines fit the window, and times, addresses and delivery are never cut.
  • status --watch redraws 150 ms after a resize instead of waiting for its next tick.

Changes by category

Hierarchy (cli/src/lib/out.ts: heading, kv, tripped; status.ts, hits.ts, doctor.ts)

  • Verdict lines open status (● 2 of 6 monitored keys tripped), doctor (⚠ 5 passed, 1 skipped, 3 warnings), hits and list.
  • Tripped rows bold and first; failed-delivery rows carry ✗ in the when cell; bot, duplicate and disabled rows dim their identity cells.
  • One heading (bold memo, dim short id) and one label block (kv, 12-column label gutter) on show, status detail, hit detail and the wizard summary; hit detail leads with who, where and when, then delivery.
  • The home screen is a 12-row launcher instead of a 119-row command dump; --help is grouped.

Typography (out.ts: formatStamp, formatAge, formatAgo, wrapProse, proseWidth, cap, memoWidth)

  • One timestamp format, 2026-11-06 02:26 UTC, time only for today, seconds only in hit detail; one relative form (4m, 3m40s, 2h, in prose with "ago").
  • Prose and failure reasons wrap on word boundaries with a hanging indent; nothing is cut mid-token; memo columns size from the terminal (38 to 60).
  • Agents parsed to one form (Chrome 141 · Windows, aws-cli 2.31.4 · linux, Googlebot 2.1).

Spacing and layout (out.ts: table, fitMiddle, maskSecretUrl; hit-format.ts, destination-format.ts)

  • Tables right-align ages and counts; headers dim and underlined; the ssh client address sits in the ip column so the who column is the identity alone.
  • Notify and delivery rows share one shape (mark, channel, target, result) with results aligned and the failure reason on its own line without the repeated address.
  • Webhook tokens masked to https://host/…tail on a terminal (piped and --json output keep the whole target).

Colour and contrast (out.ts: c, mark, hintLines, formatError)

  • Strict roles: red for tripped and failed, green for ok, yellow for warning and pending (bold, so it survives light mode), cyan for anything to run or open, dim for labels, ages, ids and legends.
  • Instruction lines, hints and state words at full weight; the mark carries the colour, the word carries the meaning.

Iconography and controls (out.ts: mark, arrow, dot, glyph; wizard.ts, prompt.ts)

  • One mark set, ✓ ✗ ⚠ … ● –, with ASCII fallbacks + x ! ~ ! -; tripped has its own dot so ⚠ means warning only; legends explain every mark on screen.
  • Wizard: defaults dim in brackets, typed answers bold, accepted defaults echoed dim, choices on their own line before every prompt, a rejected URL redrawn dim, an accepted webhook redrawn masked, phase breaks, summary drawn by the same renderer as show.

Consistency (hit-format.ts, destination-format.ts, hostOnly, withoutRepeat)

  • One renderer each for hits, deliveries and destinations, shared by list, show, status, hits, watch and the wizard; the destinations label became notify, matching --notify.
  • Hosts named for identification drop their scheme (doctor, home); the next command in every footer uses the resolved short id.

Clarity (runner.ts: networkReason; resolve.ts; doctor.ts)

  • Errors are error: plus the message plus one hint line; network errors say which server and why (connection refused, host not found); the echoed prefix is bold.
  • Doctor marks unconfigured checks as skipped and counts them apart, names causes in words, names the older side and the upgrade path for the install in use, and scopes the shared hint.
  • Delivery states in words: delivered on attempt 1, failed after 5 attempts, suppressed, no destinations; Latest N hits when --limit cut the page.

Character

  • The green wordmark on home and in the wizard title; the keychain notice is one line that is erased once the read returns, so every screen opens with its own content.

Beyond polish, for the maintainer to decide

  • Tripped state in mantis list. All three judges asked for it every round. The list endpoint carries no monitor state and reading it costs one request per monitored key (what mantis status does), so showing it in list is a behaviour change. The footer points at mantis status.
  • Latest 2 of 3 hits under --limit. The hits endpoint reports no total.
  • Collapsing duplicate hits under --verbose to one line naming the original (all three judges in round 8, Opus and Sonnet again in round 9).
  • One footer grammar (count line, then an indented → pointer) and relative-first timestamp pairs on show: both round 9 judges' top shared findings, left for a later round.
  • Doctor warning names at default weight and a ⚠ on the warning count itself (single-judge, low).

Judges, references, rounds

  • Judges: Claude Opus, Claude Fable and Claude Sonnet, each a fresh subagent that saw only the brief and the screenshots, run in parallel, never each other's reviews, never the target. Fable shares the fixing model but none of its context.
  • References: GitHub CLI 2.x (gh pr --help, gh run list, gh pr list, gh pr view, gh pr checks) and Cloudflare Wrangler (--help, kv namespace --help), captured through the same harness and palette.
  • Nine rounds after the baseline (round 9 judged by Opus and Sonnet only: the account's Fable limit was reached; the session model was Opus 5.5 for round 9); one commit per round named with that round's scores. Judge disagreements and single-judge findings not taken are recorded per round in the run notes (kept out of git with the captures).
  • Output was also checked with colour off and an ASCII-only locale: every mark has a text fallback.

🤖 Generated with Claude Code

adamXbot and others added 8 commits November 7, 2026 22:14
Shared terminal tokens for the mantis CLI's human output: one status mark
set with ASCII fallbacks, one timestamp format with a relative age, one
label block and one detail heading, verdict lines on status, doctor, hits
and list, errors as a message plus a dim hint, grouped --help, a short
home launcher, restyled wizard prompts, and a keychain notice that is
erased once the read returns. --json, --id-only, --url-only, flags and
exit codes are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Dim underlined table headers; webhook tokens masked to host plus tail on a
terminal (piped and --json output keep the whole target); failure reasons
word-wrapped under the target instead of cut; one "nothing" glyph; delivery
words that say what happened; agents parsed to one form; doctor marks
unconfigured checks as skipped, names network causes and the older side;
the ip column no longer repeated in the who cell; x-mantis headers not
repeated in hit detail; two-line empty states everywhere.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Tripped gets its own red dot so the warning sign means warning only; one
target formatter drops the scheme everywhere and masks only token-bearing
paths; instruction lines and state words at full weight with commands in
cyan; failure reasons no longer repeat the target named above them;
destinations labelled notify, ordered right after status, with the wizard
summary drawn by the same renderer; failed-delivery rows flagged in the
when cell; a legend entry for the ssh client arrow; honest "Latest N hits"
when --limit cut the page; doctor hosts without schemes and a masked key.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Hit detail leads with when, where and whether anyone was told; the typed
webhook is redrawn masked; delivery words return to the table so the dash
means only an unknown address; stamps from today show the time only and
relative ages under ten minutes carry seconds; show marks status and dims
identifiers; the no-destination count carries a warning mark; doctor and
home name servers without a scheme and hints fit beside the check name;
error hints read at full weight; the memo prompt says what it wants.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
One headline grammar on every screen (plain count, bold memo, dim id); the
memo column capped on a terminal; the hit source as a tag so the who cell
stays short; failed-delivery rows marked at the left edge; the doctor
summary colours only the counts that need acting on; wizard choices on
their own line before every prompt and a rejected answer redrawn dim; the
quiet configuration rows in show dim as one block; one example key name.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The hit id as a dim suffix on each hit headline instead of a UUID row; the
echoed prefix in "no key matches prefix" in bold through the SGR-keeping
escape; seconds in table ages under ten minutes and the since column in
the same form as when; warning and pending marks in bold yellow; the
accepted masked webhook redrawn bold; the doctor upgrade hint names the
installer in use.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Hit detail: delivery right under the time, a duplicate's headline dim; the
fixture now serves hits newest first like the server; show folds both ids
onto one dim line; doctor probe rows fit 80 columns and the shared hint
says it covers both checks; empty-state hints carry an arrow like the
other hints; the wizard echo keeps the https the validation asked for;
the wordmark in the wizard title; "hits in the window" without repeating
the window length.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The ssh client address in the ip column of hit tables, marked with the
arrow the legend explains, so the who column is the identity alone; one
spelling for ages under ten minutes on every surface; memo columns sized
from the terminal; notify and delivery results aligned; "no destinations"
spelled out; masked targets keep their scheme so the wizard echo and the
summary match; list status cells carry the same glyphs as show; quiet rows
keep their delivery cell at full weight; the upgrade hint names both paths.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
adamXbot and others added 3 commits November 8, 2026 15:53
Round 9 applies the items the user approved from the market comparison
and the fixes the resize tests found. Fable was not scored this round:
the account hit its Fable usage limit.

- Error hints print as an indented → line, like empty states.
- URLs and file paths are underlined at the text colour; cyan is kept
  for commands.
- Hits and status tables: a failed delivery keeps a red ✗ in its own
  column at the left edge, times right-align after it, the ssh arrow
  sits in a gutter with the ip header over the addresses, and since
  right-aligns like age.
- Failure reasons hang under their row behind a dim └.
- The new-key wizard reads as a form: defaults in a dim column, every
  answer bold and starting in one column, with an inline fallback
  below 70 columns.
- The masked webhook redraw clears every row a wrapped URL used, so no
  part of a real-length Discord or Slack token stays on screen.
- Narrow windows: memos and identities are cut in the middle, the hits
  headline fits, and times, addresses and delivery are never cut.
- status --watch redraws as soon as the window is resized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adamXbot
adamXbot marked this pull request as ready for review November 10, 2026 06:07
@adamXbot
adamXbot merged commit c9a6ec5 into main Nov 10, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant