Skip to content

parity: real-terminal side-by-side check (flip gate item 5) - #142

Draft
qduc wants to merge 9 commits into
mainfrom
claude/ecstatic-tesla-86lzj4
Draft

qduc wants to merge 9 commits into
mainfrom
claude/ecstatic-tesla-86lzj4

Conversation

@qduc

@qduc qduc commented Nov 4, 2026 •

Copy link
Copy Markdown
Owner

Closes #140 (part of #65). This is a draft: the owner's team integrates it, so don't mark it ready or merge it from here. The session that built it has now finished, and the run results and the soak are committed.

It adds a harness under rewrite/parity/real-terminal/ that runs legacy term2 (dist/cli.js, the spec) and term2-next (rewrite/dist/rewrite/src/app/cli.js) side by side in a real terminal emulator.

Verdict: the gate fails on all three parts.

  • Latency fails 8 of 40 class×size×phase cells.
  • 0/60 screenshot pairs are identical after masks.
  • The soak found 78 differences.

Every difference is listed below as unexplained. None was fixed, and no product code changed: the diff has nothing under source/ or rewrite/src/.

Branch note: the issue names cloud/real-terminal-check, but this session may only push to claude/ecstatic-tesla-86lzj4, so the work is here.

One command

node rewrite/parity/real-terminal/run.mjs --out <dir> [--store <dir>]

On a fresh checkout it installs dependencies, builds whichever CLI is missing, generates a synthetic store if --store is omitted, and writes report.html and results.json.

  • A full run takes about 30 minutes.
  • It is a manual or queued tool, not CI. It adds no CI changes and no unit tests, and uses only the loopback fake provider.
  • To add a soak list to a finished run: run.mjs --out <dir> --report-only --soak <file>.
  • rewrite/parity/real-terminal/README.md has the options and the debugging traps found while building it.

What it does

  • Terminal. The CLI runs in a node-pty pty. Its bytes stream over SSE to xterm.js 6 (DOM renderer) in headless Chromium, and everything is judged from xterm.js: buffer text and pixels.
    • This environment has no sshd, so the node-pty bridge is used instead of ssh to localhost.
    • Harness floor (raw cat echo through the same path): p50 2.6 ms, max 6.3 ms.
  • Isolation. Each CLI gets its own scratch HOME/XDG_*, its own fake OpenAI-compatible provider with the same scripted replies, and a private copy of the store for each run. Keys are typed one per write.
  • Store (generate-store.mjs). Generated, not copied.
    • It first records a template conversation by driving each CLI in a pty, then clones the blocks with fresh ids, timestamps and seqs.
    • Both formats are written in lockstep: 1,392 sessions each, legacy 1.17 GB, next 440 MB.
    • It includes one long session with large tool outputs, a compaction every 40 blocks and a reply of 200,000 deltas. No real conversations are used.
  • Latency. One 5 s timeout for every expected paint. A CLI that freezes is replaced by a fresh one on the same home, so a paint that arrives late can't land in a later sample.
  • Walk. 30 scripted steps, each with a screenshot. Only version, ids, paths, ports, durations/times and spinner frames are masked.
  • Soak. soak.mjs plus SOAK-BRIEF.md.

Environment

Revision measured c21e8e0 (harness only; product code is main's)
Terminal path node-pty bridge → SSE → xterm.js 6.0.0 DOM renderer, headless Chromium 153.0.8010.12
CPU / memory Intel Xeon × 4 / 16 GiB, Linux 6.18 x64, Node 22.23.4
Load average (1m) min 0.08 · mean 0.63 · max 2.26 (below 9, so no rerun)
Store generated, 1,392 sessions per format; long session legacy 325 MB / next 132 MB, 200k deltas

Report

rewrite/parity/real-terminal/report/report.html has the side-by-side screenshots, pixel diffs, differing text rows, latency tables (including CPU and bytes per key), screens for missed keys, and the soak list. Open it from a checkout, since GitHub shows only the HTML source. Raw samples are in report/results.json.

Store timings (420 MB store)

startup → composer first /resume → list second /resume
legacy 100x34 / 80x24 1.9 s / 2.2 s 842 ms / 800 ms 163 ms / 171 ms
next 100x34 / 80x24 1.7 s / 1.9 s FAIL: no list within 5 s (both) not measured

For context: an earlier run with no time limit measured next's first /resume at 153 s, and the UI ignored every key meanwhile, Escape included. The soak saw about 2.5 min, twice.

Key-to-paint latency, ms (p50 / p95 / max)

The gate: p50 and p95 within 1.2x legacy or legacy + 5 ms, and no key over 200 ms. Missed keys count against next only where it misses more than legacy. "n/a" means neither CLI painted.

100x34 idle

class legacy next gate
type char 15.1 / 15.6 / 15.7 14.9 / 15.4 / 15.5 pass
backspace 15.2 / 15.7 / 15.8 14.9 / 15.5 / 15.6 pass
enter (composer) 14.8 / 15.4 / 15.4 14.6 / 15.1 / 15.1 pass
arrow (model menu) 15.3 / 16.1 / 82.0 20/20 missed FAIL (next's picker opens on an empty tab)
arrow (resume menu) 15.3 / 15.8 / 15.8 20/20 missed FAIL (list doesn't load within 5 s)
ctrl+o 10.7 / 12.0 / 12.0 10/10 missed FAIL (a different action; soak item 12)
shift+tab 10.7 / 16.6 / 16.6 27.5 / 47.7 / 47.7 FAIL p50, p95
open /model 14.4 / 15.1 / 15.1 14.6 / 15.7 / 15.7 pass
open /settings 14.8 / 15.3 / 15.3 14.8 / 15.8 / 15.8 pass
scroll (resume page) 15.2 / 15.7 / 15.7 10/10 missed FAIL

100x34 streaming

class legacy next gate
type char 27.2 / 47.2 / 54.1 19.9 / 34.6 / 40.7 pass
backspace 29.3 / 45.0 / 60.2 19.5 / 30.1 / 33.9 pass
enter (composer) 35.8 / 41.3 / 41.3 32.1 / 40.8 / 40.8 pass
arrow (model menu) 28.9 / 47.5 / 59.8 20/20 missed FAIL
arrow (resume menu) 20/20 missed 20/20 missed n/a (legacy refuses /resume while streaming)
ctrl+o 32.0 / 54.5 / 54.5 16.6 / 138.7 / 138.7 FAIL p95
shift+tab 22.8 / 49.2 / 49.2 15.5 / 73.9 / 73.9 (7/10 missed) FAIL misses, p95
open /model 33.0 / 59.8 / 59.8 33.5 / 45.2 / 45.2 pass
open /settings 47.8 / 72.4 / 72.4 39.6 / 48.8 / 48.8 pass
scroll (resume page) 10/10 missed 10/10 missed n/a

80x24 idle

class legacy next gate
type char 14.9 / 15.7 / 23.5 15.1 / 15.8 / 15.9 pass
backspace 15.1 / 15.7 / 51.6 15.2 / 15.7 / 15.8 pass
enter (composer) 14.7 / 15.5 / 15.5 14.6 / 15.1 / 15.1 pass
arrow (model menu) 15.3 / 15.9 / 15.9 20/20 missed FAIL
arrow (resume menu) 15.3 / 15.8 / 15.9 20/20 missed FAIL
ctrl+o 11.1 / 11.9 / 11.9 10/10 missed FAIL
shift+tab 11.5 / 12.3 / 12.3 25.1 / 43.7 / 43.7 FAIL p50, p95
open /model 14.9 / 15.2 / 15.2 14.6 / 16.1 / 16.1 pass
open /settings 14.9 / 15.2 / 15.2 14.7 / 18.3 / 18.3 pass
scroll (resume page) 15.1 / 15.7 / 15.7 10/10 missed FAIL

80x24 streaming

class legacy next gate
type char 28.0 / 68.0 / 81.1 18.8 / 35.7 / 39.9 pass
backspace 30.4 / 68.6 / 85.8 17.2 / 35.6 / 41.9 pass
enter (composer) 34.4 / 39.6 / 39.6 29.1 / 39.8 / 39.8 pass
arrow (model menu) 28.4 / 39.8 / 46.0 20/20 missed FAIL
arrow (resume menu) 20/20 missed 20/20 missed n/a
ctrl+o 31.5 / 62.2 / 62.2 22.1 / 145.4 / 145.4 FAIL p95
shift+tab 22.9 / 71.6 / 71.6 40.2 / 4855.4 / 4855.4 (7/10 missed) FAIL misses, p50, p95, input stall 4.9 s
open /model 40.5 / 74.3 / 74.3 36.0 / 39.4 / 39.4 pass
open /settings 20.6 / 77.5 / 77.5 41.0 / 52.0 / 52.0 FAIL p50
scroll (resume page) 10/10 missed 10/10 missed n/a

Screenshots

0/60 pairs are identical after masks (30 steps × 2 sizes). The share of pixels that differ ranges from 3.0% (startup, 100x34) to 21.5% (/help, 80x24). The report shows each pair, its diff and the text rows that differ.

Soak differences (all unexplained)

A context-free agent used both CLIs for 30 minutes (07:16:43–07:47:03 UTC). It saw them only as A and B; the random mapping turned out to be A = legacy, B = next. The agent's full wording is in report/soak-differences.md. Each item fails the gate unless it is accepted.

  1. unexplained: startup shows Profile: bare vs Profile: BARE
  2. unexplained: legacy is indented 2 columns with a shorter divider; next starts at column 0 with a full-width divider
  3. unexplained: blank lines between the header and the divider: 4 vs 1
  4. unexplained: idle footer hints differ (next adds history and tool-output hints)
  5. unexplained: legacy hides the footer hints while you type; next always shows them
  6. unexplained: blank lines after replies: legacy 2, next 0
  7. unexplained: reply indent relative to the user bubble differs
  8. unexplained: status bar shows Ctx 1k vs 0% cached · Ctx last 1k
  9. unexplained: next's token counters add up across turns and legacy's don't; next drops Ctx after a model switch
  10. unexplained: tok/s in the status bar differs during and after streaming
  11. unexplained: tool row ✓ ls (34ms) vs ✓ Shell command (12ms), in different colours
  12. unexplained: Ctrl+O opens the model picker (legacy) vs expands tool output (next)
  13. unexplained: next's Ctrl+O expand draws a stale copy of the turn, and collapsing leaves the screen mostly blank
  14. unexplained: approval prompt layout and content differ (next shows the owner, JSON args and a running status)
  15. unexplained: approval options and hints differ (next adds Deny without reason and Ctrl+G)
  16. unexplained: label on the approved tool row differs
  17. unexplained: wording and placement of the deny-reason prompt differ
  18. unexplained: next's denied tool row leaves out the denial reason
  19. unexplained: Ctrl+G during an approval does nothing (legacy) vs goes to the composer, then a pending-interactions list (next)
  20. unexplained: Esc on an approval interrupts the turn (legacy) vs goes back to the pending list (next)
  21. unexplained: Ctrl+C on a waiting approval in next removes the user message from the transcript
  22. unexplained: the / menu is a Filter: box below the input (legacy) vs a list above it (next)
  23. unexplained: / menu commands, order and descriptions differ
  24. unexplained: marker and highlight on the selected command differ
  25. unexplained: filtering /re gives different results; legacy's wrapped line is indented wrong at 80 columns
  26. unexplained: Tab in the / menu runs the command (legacy) vs completes it (next)
  27. unexplained: /help contents and shortcut text differ
  28. unexplained: legacy leaves the command text in the input after Escape, so the next command is appended and sent as chat
  29. unexplained: next keeps only the most recent status messages
  30. unexplained: /model opens on All with models listed (legacy) vs an empty tab (next)
  31. unexplained: model picker border, title, filter and hints differ
  32. unexplained: wording of the model-set confirmation differs
  33. unexplained: effort picker layout differs; next confirms the choice and legacy doesn't
  34. unexplained: /settings layout differs (tabs, columns, markers, detail pane)
  35. unexplained: /settings shows values differently ((none) vs [configured], ON/OFF vs true/false, …)
  36. unexplained: Enter on a boolean setting toggles it (legacy) vs opens a text editor (next)
  37. unexplained: settings search hints differ
  38. unexplained: only next confirms /effort and /auto-approve and shows an Auto badge in the status bar
  39. unexplained: /profile opens a picker (legacy) vs prints usage (next)
  40. unexplained: /providers opens a menu (legacy) vs prints a line (next)
  41. unexplained: /auto-approve opens a picker (legacy) vs prints usage (next)
  42. unexplained: /mcp opens a management menu (legacy) vs prints instructions (next)
  43. unexplained: /tasks exists only in next
  44. unexplained: /sandbox message and status badge differ
  45. unexplained: /compact fails in legacy (it needs compaction settings the soak home doesn't have) and succeeds in next
  46. unexplained: /handoff asks for a message (legacy) vs starts a new conversation at once (next), and next's reply is cut off mid-word
  47. unexplained: /retry after a cancelled tool turn says No tool call to retry. (legacy) vs regenerates the turn (next)
  48. unexplained: /clear keeps the history with a divider and a welcome line (legacy) vs wipes the screen to a bare header (next)
  49. unexplained: /copy after a clear differs (the transcripts had already diverged, see 28)
  50. unexplained: /rewind flow and messages differ
  51. unexplained: next's rewind messages reappear below every later reply
  52. unexplained: /undo turn list differs (4 truncated turns vs 13 wrapped)
  53. unexplained: speed: next's /resume froze for about 2.5 min, twice; legacy opened in 1–3 s
  54. unexplained: resume list layout and metadata format differ
  55. unexplained: resume entries, ages and message counts differ; next lists empty sessions
  56. unexplained: PgDn in the resume list jumps to the bottom (legacy) vs does nothing (next)
  57. unexplained: after resuming, legacy prints Resumed conversation and shows a short transcript with a duplicated line; next prints nothing and shows many turns
  58. unexplained: next's ↑ history includes slash commands, and recalling one traps ↑/↓ in the menu
  59. unexplained: @ mention menu style, contents and order differ
  60. unexplained: ! shell mode shows a red ! prompt (legacy) vs ❯ ! (next)
  61. unexplained: Alt+Enter while idle inserts nothing (legacy) vs a newline (next)
  62. unexplained: Esc on a draft: double Esc clears it (legacy) vs ignored (next)
  63. unexplained: legacy once treated Enter as a newline at 80x24 (not reproduced)
  64. unexplained: Shift+Tab confirmation wording differs; next draws a tool row below the dialog
  65. unexplained: switching mode starts a new conversation with a message (legacy) vs keeps the transcript and adds a PLAN header (next)
  66. unexplained: footer hints during streaming differ
  67. unexplained: streaming rate display differs (about 600 vs about 160 tok/s, and next adds ~)
  68. unexplained: next keeps a stale PLAN header while streaming, and earlier lines are cut off
  69. unexplained: Enter while streaming queues a steer and the stream continues (legacy) vs the stream stops and the reply is cut off (next)
  70. unexplained: Alt+Enter while streaming: the queue display and ↑ behave differently; legacy once flashed old lines
  71. unexplained: double Esc while streaming stops it (legacy) vs is ignored (next)
  72. unexplained: Ctrl+C while streaming with a draft: next keeps the draft and pauses the queue
  73. unexplained: after a queued, steered or cancelled message, only next shows the final partial stream line; spacing around Stopped differs
  74. unexplained: PgUp while streaming does nothing (legacy) vs scrolls, but only in the tool-output viewer (next)
  75. unexplained: /queue + Enter in next (queue paused) hung for over 60 s
  76. unexplained: reflow indent at 80x24 differs (3 vs 2); next's footer drops a hint
  77. unexplained: placement of Press Ctrl+C again to exit differs; legacy once showed two
  78. unexplained: exit messages differ (token-usage line, term2 vs term2-next, how the id is quoted)

Also found by the scripted checks:

  • unexplained: legacy refuses /resume while a reply streams; next opens it
  • unexplained: at 80x24, next's screen goes blank after resizing to 100x34 and back (walk step 28)

What couldn't run, and why

  • ssh to localhost: this environment has no sshd. The node-pty bridge was used instead, as the issue allows, and the report records that.

  • Resume-list keys for next: on the 420 MB store the list never paints within 5 s (catalogue build), so those keys are recorded as fails and not sent. Legacy refuses /resume while streaming, so that cell is n/a.

  • Soak coverage: the agent did not try /mentor, /orchestrator, /restart, /bridge, /mcp-login, /steer, /followup, /cancel, /answer, Ctrl+B, or the model picker's nickname and favourite keys. From about 07:20 the two soak sessions drifted apart after item 28, so some later counts compare different transcripts.

  • Harness fixes along the way (debugging notes are in the README):

    • an earlier full run waited 30 min on a check that missed next's narrow resume list
    • that run also typed the stream prompt into open menus
    • a later run restarted legacy after reading a half-drawn screen

    All three are fixed. The committed report comes from the clean run at c21e8e0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JqbJBA8gEkw2cWpatDZGtX

… flip gate item 5

Adds rewrite/parity/real-terminal/: node-pty -> SSE -> xterm.js harness that runs
legacy term2 and term2-next in a real terminal, a synthetic store generator that
clones blocks each CLI itself recorded, key-to-paint latency per key class (idle
and while streaming), a masked screenshot walk with pixel diffs and an HTML
report, and soak terminals plus the soak brief. Dev deps: @xterm/xterm and
@lydell/node-pty (prebuilt). Not a CI test.

Refs #140

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJBA8gEkw2cWpatDZGtX
…gate refinements

Store timings wait for a real home state after closing the resume list, the
mode-switch restore matches both CLIs' clear-session prompts, the xterm.js
scrollbar is hidden from screenshots, aborted phases are reported instead of
ending the run, and a class neither CLI paints is n/a rather than a failure.

Refs #140

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJBA8gEkw2cWpatDZGtX
… Escape until home

term2-next draws menus as a box closing right above the separator, and a resume
list can be taller than the old 18-row window, so the harness could type into an
open list and resume a session by accident. Home is now one shared check, and
Escape is retried until home is verified. Opt-in raw pty log (RT_RAW_LOG).

Refs #140

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJBA8gEkw2cWpatDZGtX
…oser

Refs #140

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJBA8gEkw2cWpatDZGtX
@qduc

qduc commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

Note: the session working on #140 is still running. This draft was opened from a mid-work snapshot of its branch. Hold any review or run until that session finishes.

…ng the stream; match term2-next's narrow resume list

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJBA8gEkw2cWpatDZGtX
…s answering instead of waiting

Every expected screen now appears within 5 s or fails. A frozen CLI (term2-next
while it builds its catalogue on the first /resume) is replaced by a fresh one on
the same home so a late paint cannot land in a later sample. Dialog detection no
longer mistakes a transcript code box for a dialog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJBA8gEkw2cWpatDZGtX
…mposer; log what it showed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJBA8gEkw2cWpatDZGtX
…port-only to add it to a finished run

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqbJBA8gEkw2cWpatDZGtX

This branch has not been deployed

No deployments
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.

term2-next: real-terminal side-by-side check (flip gate item 5)

2 participants