ターミナル描画の作り直し (v1.2.0) - #2
Merged
Merged
Conversation
Every terminal fixture so far came from a Linux host, so what network gear sends was guesswork - and the second-opinion review twice asked whether the full-screen notice could fire on a pager or a terminal reset. Captured 7058 bytes from the lab Catalyst 8000v over the same vt100 shell the app opens: show version, show ip route, three screens of show interfaces through the pager, and a typo rubbed out with backspace. It contains no escape sequences at all. Not one. The pager erases --More-- with backspace-space-backspace and line editing does the same, so the whole device-facing path rests on \r \n \b. That answers the false-positive question by measurement, and it says what the rewrite must not break: this is the path a network engineer is on all day. Two artefacts of real capture are worth keeping. The pager ate the first character of the next command, so the device answered with % Invalid input and a ^ under the offending column - a column-alignment invariant that comes free. And exit arrived as xit for the same reason. Addresses, MAC addresses and the hostname are replaced with documentation values; the control bytes are untouched, verified by comparing their positions before and after. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First piece of the v1.2.0 terminal rewrite. The old widget interprets control codes with regexes over a text buffer and patches receive boundaries by hand (_pending_escape); that seam produced most of the terminal bugs this release cycle. Replace the approach at the root: implement Paul Williams' DEC-compatible parser state machine (vt100.net/emu/dec_ansi_parser), which carries its state across feeds, so a sequence split anywhere on the wire parses identically. This commit covers ground, escape, escape intermediate, OSC and SOS/PM/APC; the CSI and DCS states follow in the next commits, so for now ESC [ falls through to esc_dispatch and ESC P is consumed like SOS. Nothing imports the module yet. It knows nothing about Qt. Deliberate deviations from the diagram are listed in the module docstring (BEL-terminated OSC as xterm does, decoded-str input instead of bytes, aborted strings are discarded). The tests pin the property that motivated the rewrite: every split point of a stream yields the same command list - including the 7058 byte Catalyst 8000v capture, which must come out as Print/Ctrl only and rebuild byte-for-byte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the four CSI states from the Williams diagram (entry, param, intermediate, ignore). The parser stays deliberately dumb: params come out as numbers with omitted positions kept as None, private markers (< = > ?) ride in their own field, and unknown finals still come through - deciding what to honour is the screen's job, not the parser's. Two poison paths follow the diagram rather than modern practice: a colon (ECMA-48 sub-parameters, e.g. SGR 38:5:1) sends the whole sequence to csi_ignore, and a param string over MAX_PARAMS does the same instead of growing without bound. Neither form appears in any capture; revisit the colon if 256-colour SGR ever shows up. The Linux capture tests assert measured values, not guesses: nano under TERM=vt100 builds its screen with DECSTBM (ESC[1;24r) and CUP, bash toggles bracketed paste (?2004), clear is ESC[H ESC[J, and no capture contains a single OSC. Every split point of all three streams parses identically. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the five DCS states (entry, param, intermediate, passthrough, ignore) from the Williams diagram. The hook head - private markers, params, intermediates, final - is captured when passthrough starts, and the whole thing is delivered as one Dcs command on ST. A CAN/SUB discards head and body together, matching how the parser already treats aborted OSC strings. No capture contains a DCS and the screen will ignore them, but consuming them by the diagram instead of a shortcut means a device that does send one (DECRQSS replies, XTGETTCAP) can never desync the stream or leak its payload into the visible text. With this the parser implements every state in the diagram. The module is still imported by nothing but its tests; the screen model comes next. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Value object for what ECMA-48 calls select graphic rendition: colour, bold, underline, reverse. Cells will hold these as-is. Colours stay numbers (0-7 basic, 8-15 bright, 16-255 palette, an (r,g,b) tuple for direct colour) - mapping a number to an actual pixel colour is the renderer's decision, because it depends on the light/dark theme, and core code knows nothing about themes. SGR is the most frequent sequence in the captures (72 times), all of it the basic codes, but the extended 38/48 forms cost little and vim or ls --color will send them the moment someone opens a Linux shell. Unknown codes are skipped silently; a malformed 38/48 drops the rest of the parameter list rather than misreading colour components as attribute codes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A grid of rows x cols cells, each holding (char, Attr). This is the concept the old widget never had: nano says "row 23 column 1" and now there is somewhere for that to land. This commit covers printing with deferred wrap (the VT100 right-edge quirk - a CR after an exactly 80-column line must not mint a phantom blank line), C0 controls, scrolling with history capture, cursor movement and SGR. Erasing, escape dispatch, margins, the alternate screen and modes follow in the next commits. Lines scrolled off the top of a full-screen region go to history, which is how the session record survives the grid being only 24 rows. The Catalyst capture already renders correctly on the model alone, no Qt: --More-- fully erased, the backspace-corrected typo clean, the ^ error marker in column 10, 12 indented legend lines - all equal to what the old widget shows today. Walking every 7th split point of the 7058 bytes takes ~2 s here versus ~4 min through the widget, which is the testability the rewrite was after. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ED/EL erasing, ECH, line and character insertion/deletion (IL DL ICH DCH), explicit scrolls (SU SD) and DECSTBM margins. Erased cells drop their attributes, edited lines keep their exact width, and margins outside the screen are refused like a real VT does. Two behaviours carry policy, not just spec: erasing the display never touches history (clear keeps the session record - the v1.1.1 decision), and a partial scroll region never feeds history, because what scrolls inside an application's pinned region (nano's editing area between its title and shortcut bars) is repainting, not session output. DECSTBM also homes the cursor, which matters because nano's ESC[1;24r is the very first thing it sends and the marker the full-screen notice keys on today. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The rest of the screen model. ESC dispatch (save/restore cursor, index, reverse index, full reset), ISO 2022 charset designation with the DEC line-drawing set, the xterm private modes that actually occur in captures (?1049/1047/47 alternate screen, ?7 autowrap, ?25 cursor, ?1 cursor keys, ?2004 bracketed paste - the last two only tracked and exposed for the input side), OSC window titles, DSR/DA answered through a response queue the connection can drain, and set_size for window-change support. Two findings from measuring the real clear capture: it is not ESC[2J but ESC[H ESC[J - a mode 0 erase from home - followed by 216 NUL padding bytes (the vt100 terminfo $<50> delay), so the keep-the- session-record rule now triggers on any erase that covers the whole screen, not just mode 2. And one Python trap fixed on the way: the charset branch tested `intermediate in "()"`, which is a substring check that is true for the empty string, so every plain ESC dispatch was being eaten as a charset designation until the tests caught it. The nano capture now assembles the full 24x80 layout on the model: title bar reverse-video on row 0, shortcut bars on rows 22-23, cursor parked in the editing area. The old widget flattened all of this into one line; that difference is the whole point of v1.2.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The widget now feeds received text to the parser, applies the commands to the Screen, and mirrors the result into the QTextEdit. The document becomes [settled record] + [live screen]: lines pushed off the top join the record and are never touched again, and the live region is re-rendered per batch - but only the changed span (common prefix/suffix trimmed), because wiping the whole region would destroy a selection every time the device emits a log line. The cursor row keeps its blanks up to the cursor column so a prompt's trailing space still shows and the caret sits where the device put it. DSR and DA queries are answered back through the key_pressed path. nano now draws its real 24x80 layout in the widget - title bar, shortcut bars, cursor in the editing area - where v1.1.1 could only print an apology notice. Reconnection swaps in a fresh parser and screen; the old screen's rendering stays in the record. tests/test_terminal_fullscreen.py pinned that notice and the regex fragment carry-over, both of which this change removes on purpose; its surviving properties (split invariance, no title leaks, records outliving clear and nano, reconnect isolation, selections surviving output) move to tests/test_terminal_widget_render.py. The old escape machinery in the widget is now dead code; the next commits remove it. The widget-level Cisco split walk drops from ~4 min to ~5 s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dead since the widget switched to the parser + screen model. This was the 195-line loop that read escape sequences with regexes over the QTextEdit document, carried split fragments in _pending_escape, and edited text around a persistent render cursor - the seam most of the v1.1.x terminal bugs lived in. Its helpers and constants go in the next commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The notice ("full-screen apps are not supported") existed because the
widget could not honour cursor addressing; now it can, and nano
actually renders. Gone with it: the DECSTBM/CUP heuristics that
guessed when a full-screen app was starting, the alt-screen mode
list, the incomplete-escape regex and its carry limit, the visible
line estimate, and the per-tab render cursor - all machinery of the
old interpreter. The device capture test keeps proving the heuristics
are not missed: a network device sends no escape sequences at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Colour, bold, underline and reverse video now reach the glyphs. Numbers map to pixels only here in the renderer (xterm's default 16, the 6x6x6 cube, the grayscale ramp, direct RGB); the core model keeps numbers, as designed. Reverse video swaps the terminal's configured palette pair, so --More-- highlights and nano's title bar band come out right on any colour scheme - and attribute-carrying blanks are no longer trimmed at line ends, because that band IS trailing spaces. Painting is a format-only pass over rows the screen marked dirty: setCharFormat moves no characters, so selections survive it, and default-attribute text gets an empty format on purpose - pinning an explicit colour would freeze old output when the user changes the palette in settings. Under TERM=vt100 devices send no colour today; what this buys now is reverse and bold, and the rest is ready the day the terminal type setting learns xterm-256color. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The grid was pinned at 80x24 since the first release, so show output wrapped at 80 columns no matter how wide the window was. Now the widget measures how many rows and columns fit (font metrics x viewport), resizes the screen model, and announces the change: SSHConnection.set_terminal_size sends an RFC 4254 6.7 window-change on a live channel and feeds the 6.2 pty-req when called before connecting - which MainWindow now does, so a session starts at the real window size instead of 80x24. Resize storms while dragging are coalesced through a 200 ms timer, an unchanged size is not re-announced, and a dead channel swallows the notify without taking the session down. Telnet has its own standard for this (RFC 1073 NAWS) but our client does not negotiate options, so non-SSH connections are silently left at their fixed size. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Arrow keys now follow DECCKM: while an application holds ESC[?1h - nano does - they go out as SS3 (ESC O A) instead of CSI, which is the form such applications actually decode. And pasted or dropped text is wrapped in xterm bracketed-paste markers, but only while the far end has ESC[?2004h raised; bash then treats the paste as one edit instead of executing each line on sight. Network devices raise neither mode, so nothing changes on the primary path - both switches live in the screen model the device itself controls. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The old widget implemented erase-to-start-of-line by deleting the text and pulling the rest left, and this test pinned that. ECMA-48 says EL blanks the span - including the cursor position - and shifts nothing; shifting is exactly what breaks column-aligned output. The screen model does it by the book (test_terminal_screen already pins " def" for the same operation), so the widget-level expectation moves to the standard behaviour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five fixes from the review round and from testing the build on real gear. Shrinking the window destroyed text: set_size truncated every line at the new width, so narrowing the window while reading a long line - an authorized_keys entry, a wide show output - permanently ate its tail, and widening again did not bring it back. It now reflows: overflow wraps onto following lines, surplus rows go to history, and blank bottom rows are dropped before any written line is. The buffer parked behind an alternate screen is reflowed too, so opening vi and then resizing no longer silently drops the shell's bottom rows. Repeat counts are now clamped to the screen. CSI parameters may carry 256 digits, so ESC[999999999S spun _scroll_up for tens of minutes on the GUI thread while _new_history grew past a million rows, with no way out but killing the app. Line commands clamp to rows, character commands to columns; cursor moves are left alone because _move already clamps them. ED 3 erases saved lines, not the display - it was clearing the visible screen and, backwards, growing the history instead. NetBelt does not discard the session record, so it now does nothing. And clearing DECAWM now also drops a pending wrap, which otherwise let exactly one more character spill onto the next line. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Testing the build on real gear turned up three visible faults, all from the same root: the live screen was drawn only as tall as its last non-blank row. clear looked like it did nothing - the region collapsed, so scrollback rose to fill the viewport. nano drew half off-screen, because ensureCursorVisible scrolled to the caret up in the editing area rather than to the screen's bottom. The screen is now always drawn its full height and the view is pinned to the bottom, which is what a terminal does: the last N lines of the document are exactly what the device has on screen. Selections no longer die when the screen scrolls. A line scrolling off shifted every row, so the diff found no common prefix and replaced the whole region, taking any selection with it. The scrolled-off line is already in the document, so instead of writing it again the boundary between record and live screen just moves past it - nothing is edited, so nothing is lost. Re-inserted text also carries an explicit empty format now, and rows covered by the replacement are repainted, so colours no longer bleed from whatever sat at the insertion point. Notices the app writes itself (disconnect banner, errors, serial messages) used bare LF. A terminal's LF moves down without returning to column 0, so with a prompt on screen the disconnect banner came out in a staircase. They go through show_notice, which converts to CRLF; device output keeps its LF meaning untouched. Session logs also keep tabs again - dropping them ran table columns together. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The thread-safety check matched the failure notice as an exact source literal, so switching it to CRLF for the terminal broke a test that has nothing to say about line endings. It now matches the call shape instead: three emits through the signal, no direct widget call. Both mutations - routing one through the widget, and dropping one of the three - are still caught. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Resizing narrow and back left output permanently chopped up: ls /etc/ came back with its columns split mid-line, and because the chopped rows had already scrolled into the record, later commands inherited the mess. The screen split long lines on the way down but had no way to tell a wrap from a line break the device actually sent, so widening could not put them back together. Each row now carries a flag saying whether it continues onto the next, set only where autowrap actually carried a character over. Reflow joins each run of continued rows into one logical line before splitting it at the new width, so narrowing and widening is a round trip. The flags ride along through scrolls, insertions, erases and the alternate-screen swap, and a row cleared to the end of line stops being a continuation. The renderer uses the same flag: a continued row goes into the document without a newline after it, so a line that scrolled off while the window was narrow is one long line in the record and re-wraps freely at whatever width the window has later. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Testing on real gear showed output being eaten one piece per resize cycle: shrink to the minimum and restore, and the tail of a wrapped line was gone; do it again and more went, until only the first 20 characters - one minimum-width row - were left. The cause was reflow itself. Recomputing every line break on each resize means the document the renderer had already settled no longer lines up with what the model hands over, and each pass shaved a bit more off. Chasing that with a smarter renderer only moved the seam. So the model no longer re-splits. A resize widens short rows to the new width and never truncates or re-wraps anything already written - which is what real terminals do, and what this app did before v1.2.0. Rows may now be longer than the screen is wide; erasing uses each row's own length, and the view wraps long lines for display. Lines that must leave a shrinking screen still go to history rather than being dropped, including the main screen parked behind vi. Verified against the lab box: five shrink-to-minimum-and-back cycles leave the document byte-identical, where before the first cycle already lost the end of the key. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Working out why output vanished on resize took several rounds partly because nobody could say how wide the window had been at the time. The size the app reports to the device is now visible while you work. It sits in the status bar's permanent area, so it never competes with the transient showMessage notices, and it follows the visible tab rather than whichever terminal happened to resize last. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Requested while testing. The tool-area tabs already work this way; terminal tabs did not. Nothing had to change underneath: a tab is matched to its device by name and widget, and every index in this file is read at the moment of the event it belongs to, never stored. Tests cover what a reorder could plausibly break - output reaching the right terminal, closing the tab you actually clicked, the current-tab name, and reconnection reusing the same tab from its new position. All three mutations (disabling the move, closing by a fixed index, matching a tab without checking its name) are caught. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Widening the window put a block of spaces in the middle of a key: the two halves of the wrapped line rejoined with the gap between them, because widening had padded the first row out to the new width and the padding was carried into the join. A wrapped row's length is the record of where it wrapped, so a resize now leaves it alone and only pads rows that end on their own. Rows can therefore be shorter than the screen is wide, and printing past the end of one grows it on demand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Requested while testing. The tool area could already be collapsed by its splitter handle, hidden from its tab bar's context menu, and toggled from the View menu; the device list had none of that. It now has the same three ways in and out. Every device-tree context menu carries the entry - on a device, on a group, and on empty space - so it does not matter where the click lands. The console group's menu used to return early and show nothing at all; it now shows at least this. Bringing it back gives it a width again: a list restored after being dragged to zero would otherwise come back invisible and read as "it won't come back". The terminal stays non-collapsible, being the part of the window there is no point in hiding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two release blockers found by the pre-release audit. Erasing to the start of a line, or deleting characters, crashed the app outright. Since b8409cf a wrapped row is left at its wrap width instead of being padded, so a row can be narrower than the screen - while the cursor may still sit anywhere up to the last column. EL 1, ED 1 and DCH indexed the row by the cursor column and raised IndexError, which takes the whole app down. Every place that touches a row now bounds itself by that row's own length. A sweep of 8316 combinations of sequence, parameter, cursor column and row/width mismatch now completes without an exception. The Catalyst capture was also being stored wrong. With no .gitattributes and core.autocrlf on, git decided the file was text - it contains only CR, LF and BS - and stripped all 147 CRs on the way into the index: 7058 bytes in the working tree, 6911 in the commit. Windows checkouts put the CRs back, so nothing showed locally, but a Linux clone and the Source code archive attached to each release - the GPL corresponding source - carried the broken file, where test_terminal_cisco fails on the column of the ^ marker. Verified by cloning with core.autocrlf=input: 6911 bytes and a failing test before, 7058 bytes and 7 passing after. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The update notice showed the release body, which held only the install guide and licence notice - nothing about what had changed. Releases now put the CHANGELOG section for that version at the top of the body, with the standing notice below it (moved to .github/release-body.md so the workflow can compose the two). The licence text and the GPL source offer stay exactly as they were. The app asks for the release list rather than just the newest one, so someone who skipped a few versions sees each of them in order, newest first, capped at five with a line saying how many were left out. It is the same single request as before, so the unauthenticated rate limit is unaffected; drafts and prereleases are filtered out here rather than by the endpoint, and the list is re-sorted by version instead of trusting its order. One trap found by running the workflow's PowerShell locally: -like reads [ and ] as a character class, so "## [1.2.0]*" matches nothing and the release would have failed at the tag. It uses StartsWith. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Splitter positions are remembered across restarts, so a list collapsed to nothing and left that way came back the same on the next launch - visible as far as Qt was concerned, invisible to the person looking at it. The View menu then appeared dead, because the first click hid a list that was already unseen and only the second brought it back. A pane with no width now counts as hidden. Also corrects the changelog. Everything previously listed under "fixed" was a fault introduced during this rewrite and caught before release, not something a v1.1.1 user ever met - v1.1.1 has no screen model at all. Listing them tells people they were suffering from bugs they never had, so the section now names only what was genuinely broken for them: full-screen apps collapsing to one line, and output wrapping at 80 columns however wide the window. Three claims that overstated the work are corrected too, and the device queries the app now answers on its own (cursor position, device attributes), the pager's page size following the window height, and full-width characters counting as one column are all written down. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The notes box printed the release body verbatim, so a changelog arrived as a wall of #, ### and - with the text buried in it. Now that the body carries the changelog rather than an install guide, that markup is most of what is on screen. It renders as markdown, falling back to plain text if that ever fails. Checked end to end for a v1.1.0 user finding v1.3.0: all three skipped versions arrive in the dialog, in order, with the version already installed left out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The step that assembles the release body only ran on a tag, so the first time it executed was the release itself - and it had already been caught failing once locally (PowerShell reads [ ] in -like as a character class, so the changelog heading never matched). Publishing is still tag-only, but the assembly now runs on a manual dispatch too, prints the first lines it produced, and fails if a template placeholder survived. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gomanizm
added a commit
that referenced
this pull request
Sep 12, 2026
…oning it Closing the update dialog mid-download went three different ways, and all three were wrong. Esc reaches QDialog.reject(), which never runs closeEvent, so the download was neither confirmed nor cancelled and the thread kept running as a child of the dialog. The cancel button and the window's close button called wait(10000) on the GUI thread, freezing the window for ten seconds. And in every case the still-running QThread stayed parented to the dialog, so destroying the main window at exit destroyed a running QThread and Qt aborted the process. Measured (inspector, release #2): after Esc, `dialog visible: False result: 0 thread running: True _cancelled flag: False`; the cancel path took `cancel path took 10.0s`; both ended in `QThread: Destroyed while thread '' is still running` and `EXIT: -1073740791 (hex 0xC0000409)`. Fix: Esc, the cancel button and closeEvent now share one path that asks once, then hands the thread over: signals disconnected, parent cleared, a cancel requested, and the object kept in a module-level set until its finished signal arrives (with a bounded wait at aboutToQuit). The GUI never waits. Cancelling also closes the HTTP response (VersionManager.abort()), so a stalled read stops at once instead of sitting until the 60 second timeout. Tests: tests/test_update_dialog_cancel.py -- Esc and close release the thread promptly, "No" keeps the download, a child process survives having its parent widget destroyed (it exited 3221226505 before this change), and abort() ends a stalled read. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
ターミナル描画の作り直し(v1.2.0)。受信文字を書き足すだけだった実装を、
行×桁の「画面」を持つ形へ置き換えた。あわせて UI の追加が 4 件。
仕様から自作している。既存ターミナル実装のソースは読んでいない
(派生物とみなされる余地を残さないため)。参照したのは Paul Williams の
DEC 互換パーサ状態遷移図、ECMA-48(= ISO/IEC 6429)、ISO/IEC 2022、
RFC 4254、標準に無い部分は XTerm Control Sequences。
主な変更
src/core/terminal/を新設(parser / screen / attrs)。UI に依存しないterminal_widget.pyの解釈処理(正規表現・約 300 行)を削除して差し替え接続先リストの非表示、更新通知に変更内容を出す
検証
機器の 7058 バイトにはエスケープが 1 つも含まれず、この経路を
壊さないことを最優先に据えた
監査で見つけて直したもの
タグを打つ前に止められた 3 件:
ESC[1K/ESC[PでIndexError。折り返し行を埋めない変更の副作用。8316 通りの総当たりで 0 件に
cisco_ios_vt100.binだけテキスト判定され CR を 147 個剥がされていた。Linux clone と
リリース添付のソースアーカイブでテストが落ちる。
.gitattributesで解決不具合だった(v1.1.1 に
core/terminal自体が無い)。実際に v1.1.1 で壊れていた 2 項目に絞った。代替画面の記述も誤りだったので訂正
既知の制限
(実端末と同じ。記録へ流れた行は 1 本の行として保存され、
ウィンドウ幅に追従する)
vt100固定のため、nano/viの画面は記録に残る🤖 Generated with Claude Code