Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Alacritty Terminal Integration

TUICommander uses alacritty_terminal 0.26.0 as its terminal emulation backend. We maintain a local patch at src-tauri/patches/alacritty_terminal/ referenced via [patch.crates-io] in Cargo.toml.

Why a local patch

alacritty_terminal is designed for the Alacritty GUI app. Several methods and fields needed by an embedded terminal backend are private. Rather than forking the entire repo, we patch the crate locally — minimal changes, easy to audit, easy to rebase on upstream updates.

Our patches

The patch includes semantic changes listed below as well as rustfmt-only changes in other files. term/search.rs now has a semantic change: it searches retained zero-width scalars in addition to the base scalar. Do not classify it as formatting-only when rebasing. The crate is a workspace member with no local rustfmt.toml, so workspace formatting also rewrites upstream style; review semantic changes separately from that reflow.

FileChangeWhy
src/term/mod.rsZero-width input damages the actual base cellSeparately received combining marks reach both parse-side changed rows and render frames, including marks attached to a wide base cell.
src/term/search.rsFeed base and zero-width scalars to the regex search automaton in both directionsMatch exact stored Unicode sequences while returning cell coordinates; TUIC converts buffer-search ranges to UTF-16 for JavaScript consumers.
src/term/mod.rspub fn resize_reflow(size, reflow: crate::grid::ReflowMode)Choose the reflow policy on resize. Ink/Claude Code uses CUU cursor positioning that breaks when reflow merges/splits screen lines.
src/term/mod.rspub fn mark_fully_damaged() (was fn)Lets us force full-frame damage directly instead of maintaining a parallel flag.
src/term/mod.rsParse-side damage: TermParseDamage enum, TermDamageState.parse_lines/parse_full, pub fn parse_damage()/reset_parse_damage(), damage recorded in write_at_cursorA SECOND, independent damage view for TUIC’s PTY parse path (TerminalGrid::process → ChangedRow), read+reset separately from the render damage so the two consumers never steal each other’s damage. Lets process() diff only changed rows instead of rebuilding+diffing the whole screen per PTY chunk. write_at_cursor now damages the written cell (upstream reconstructs input damage lazily at damage() time from cursor deltas, which left the parse consumer blind to typed text); this is at worst a safe over-damage for the render consumer. Correctness pinned by the process_damage_matches_full_diff differential test.
src/term/mod.rsfn osc7770(&mut self, verb, payload)OSC 7770 TUIC protocol handler. Fires Event::Tuic { verb, payload } for in-band state/suggest/intent signalling.
src/term/color.rspub fn named_color_to_index(NamedColor) -> Option<u8>Maps named colors to xterm-256 indices. Eliminates 30-line match duplication in our serializer.
src/event.rsEvent::Osc133 { command, params, line }, Event::Osc7(String), Event::Tuic { verb, payload, line } variantsCarry parsed OSC 133 / OSC 7 / OSC 7770 events from VTE to the application layer. All three carry the grid line where the marker landed.
src/term/mod.rsConfig.alt_scrolling_history + alt-grid history in Term::new/set_options, era reset in swap_altUser-visible parity with iTerm2’s optional alternate-screen scrollback, implemented with Alacritty’s separate grids rather than iTerm2’s shared persistent line buffer. Upstream gives the alternate grid capacity 0 (XTerm semantics), so an app printing more than a screenful (gh run watch, less, man) loses whatever scrolls off. The field defaults to 0, preserving upstream behavior for consumers that do not opt in; TUICommander uses the primary cap. Each enter/exit starts a fresh alternate era, so sessions never inherit one another and no alternate lines remain logically retained after exit. Oversized repeated redraws remain repeated because the emulator is byte-faithful, not a semantic snapshot deduplicator.
src/term/mod.rspub fn primary_history_size()Returns primary-grid history even while the alternate grid is active. Durable-log resize synchronization must stay in this coordinate space; using active alternate history can suppress the first normal-shell lines after exit.
src/grid/row.rs, src/grid/mod.rs, src/grid/resize.rs, src/term/mod.rsRow::copy_origin_unknownPer-row copy provenance: predecessor eviction/purge makes the oldest retained origin unknown; full row reset/erase restores it. Selection cleanup consults this flag without changing absolute row counters.
src/grid/mod.rspub fn reset_history_era()clear_history() keeps lines_scrolled monotonic because absolute row ids must be stable for the life of a physical line. The alternate screen is a separate content universe wiped on every enter/exit, so it gets a fresh era instead: history and counter reset. Frame-protocol keyboard_flags bit 5 marks the transition; the frontend then atomically invalidates row, scroll, selection, search, and link state.
src/grid/mod.rslines_scrolled field + pub fn total_scrolled()Monotonic count of lines ever scrolled into history (incremented in scroll_up). total_scrolled() - history_size() gives lines evicted from the top, the base for an eviction-stable absolute row coordinate. Excluded from PartialEq; serde(default) so old ref fixtures still load.
src/grid/mod.rspub enum ScrollSource + pub fn scroll_up_with(region, positions, source)Says why lines move up, because the region cannot: a linefeed past the bottom margin and a DL at the top row both present a region that starts at line 0. Overflow feeds history, advances lines_scrolled and shifts a scrolled-back viewport; Control swaps within the region and moves no counter. scroll_up still delegates as Overflow, so resize.rs and clear_viewport are untouched (#834-1878).
src/grid/resize.rspub enum ReflowMode { None, All, HistoryOnly }; Grid::resize and grow_columns/shrink_columns take it instead of boolHistoryOnly is the mode TUIC runs: scrollback stays readable across resize cycles while visible screen rows are truncated/padded, so a cursor-addressed TUI is not re-wrapped under itself. The screen/history boundary is derived from the post-rezero() storage layout (i < self.lines is screen).
src/grid/row.rsRow.reflow_wrap: bool (serde(default))Marks a wrap produced by this shrink. History rows only merge when it is set, so a stale natural wrap from an earlier width is never joined. Reset by Row::reset, propagated when a row is absorbed.
src/grid/storage.rsdebug_assert_eq!(size_of::<Row<T>>(), size_of::<usize>() * 5) (was * 4) and the matching cache loop boundConsequence of the extra Row field — the assertion is upstream’s guard against an accidentally fat Row.
src/term/cell.rspub enum Osc133CellType { None, Prompt, Input, Output } + Cell.cell_type (serde(default))Semantic cell tagging from OSC 133 A/B/C/D, written by Term::osc133 and read by TerminalGrid to emit prompt/input/output zones. Replaces the regex pre-parser TUIC used to run over raw output.
src/grid/tests.rsUpstream resize tests migrated to ReflowMode; new shrink_reflow_history_only caseKeeps upstream’s reflow coverage green after the signature change and pins the new mode.
src/term/cell.rsCellExtra.zerowidth: ArrayVec<char, MAX_ZEROWIDTH_CHARS> (was Vec<char>), push_zerowidth uses try_push, clear_wide assigns ArrayVec::new(); direct arrayvec depBackport of upstream ede2ac14 (2026-08-26, master only — 0.26.0 predates it, so this is not yet available from crates.io). The unbounded Vec let a single cell absorb combining marks forever (echo -en a; while true; do echo -en '\xcc\x81'; done), a memory-exhaustion vector any PTY child can reach. Overflow now drops the character instead of allocating. Bound is 9, upstream’s value — no glyph cluster we render needs more, and zerowidth() still hands out a &[char] so no caller changed. This row exists to stop the next rebase silently reverting the fix: delete it only once the version we pin actually contains ede2ac14. arrayvec was already in the lock via vte, so the dep costs no new crate.
src/grid/row.rsSafe Row::new using iter::repeat_with(T::default).take(columns).collect()Backport of upstream d692748d (2026-08-31, master only; 0.26.0 predates it). Removes the unsafe initializer and its hidden nonzero-column requirement. A zero-column row is now empty. Keep this backport until the pinned upstream release includes it.
src/grid/row.rsgrow reserves exactly the requested additional columns; shrink returns allocation slack in both the retained row and its nonempty tailWidth changes no longer double row capacity or retain the previous wide allocation after shrinking. The cell values and reflow contract are unchanged.
src/grid/storage.rsMAX_CACHE_SIZE = 32 (upstream: 1,000)Bounds spare allocated rows while retaining batched allocation during scrollback growth. At 220 columns and 24 bytes per cell, the spare-cell budget falls from 5,280,000 to 168,960 bytes per grid, before allocator rounding. See the scrollback memory measurement below.
src/tty/unix.rsShellUser::from_env calls getpwuid_r only when USER/HOME/SHELL is missingUpstream resolves the passwd entry unconditionally on every PTY spawn. TUIC spawns many PTYs; the lookup is skipped when the environment already answers.
src/term/mod.rsIL/DL reset the cursor column; ICH/DCH/ECH clear the pending wrap; ED0 spares the cell behind a pending wrapThree inherited divergences from the DEC contract, found by the ANSI differential harness (tests/terminal-stress/INTEGRITY_FINDINGS.md). They are described one row below; each is pinned by a term::tests case that names the operation.
src/term/mod.rsfn scroll_up_overflow(), separate from Handler::scroll_up; DL and SU scroll as ScrollSource::ControlDL and SU remove lines that never reached the bottom, so TUIC gained scrollback rows the agent never printed — an agent TUI that repaints with DL manufactured history. wrapline and advance_line called the same Handler::scroll_up the SU control dispatches to, so routing that one method would have stopped a linefeed feeding history as well; the overflow path gets its own entry point instead. Replaying the retained ANSI captures moves the row-count-grew residual bucket from 205 to 0 (#834-1878).

The pending wrap and the edit operations

A character written into the last column leaves the cursor there and arms input_needs_wrap instead of moving past the margin. Which operations resolve that pending state is not a matter of taste — it decides where the next character lands and whether the last visible cell survives. Upstream Alacritty answers three of them differently from DEC and from xterm:

OperationContractWhat upstream did
IL (CSI Ps L), DL (CSI Ps M)return the cursor to the left margin, but only when the line is inside the scroll region; clear the pending wrap either wayleft the column stranded mid-row, so the rest of the line painted at the wrong offset
ICH (CSI Ps @), DCH (CSI Ps P), ECH (CSI Ps X)edit the cell under the cursor, therefore resolve the pending wrapcarried the wrap past the edit, so the next character jumped to the following row
ED0 (CSI 0 J)the erase origin is past the right margin, so the current line keeps its last cell; lines below still clearerased that cell, dropping a character the user had already seen

ED1 pending-wrap behavior, EL0, EL1 and EL2 already agreed with the reference and are deliberately unchanged — clear_line has carried the LineClearMode::Right guard all along. Do not “fix” them alongside the three rows above; term::tests pins their current behaviour for exactly that reason.

VTE patch (src-tauri/patches/vte/)

We also patch the vte crate (0.15.0) to extend the Handler trait:

MethodPurpose
fn osc133(&mut self, command: char, params: &str)Shell integration markers (A/B/C/D). Routes OSC 133;X from osc_dispatch.
fn osc7(&mut self, url: &str)Current working directory. Routes OSC 7;url from osc_dispatch.
fn osc7770(&mut self, verb: &str, payload: &str)TUIC protocol. Routes OSC 7770;verb=payload from osc_dispatch.

OSC 7770 — TUIC Protocol

In-band signalling via the PTY stream. Never written to the grid (consumed by VTE before rendering).

Format: ESC ] 7770 ; verb=payload BEL or ESC ] 7770 ; verb=payload ST

Verbs:

VerbPayloadEffect
stateidle, busy, or awaitingidle/busy: immediate shell state transition (bypasses silence timer). awaiting: emits a confident Question (sets awaiting_input); busy also clears a prior awaiting. Driven by native agent hooks (see AI Agents → Native Hook Instrumentation). Unknown payloads are ignored.
suggestA|B|C (pipe-separated)Emits ParsedEvent::Suggest — never hits the grid, no conceal needed.
intenttext or text (Title)Emits ParsedEvent::Intent with optional tab title.

Advantages over text-based detection:

  • Zero cross-chunk issues (OSC has delimiter-based framing in VTE)
  • Zero conceal (never written to grid cells)
  • Zero regex (structured parse in VTE dispatcher)
  • Zero stale rescan (not in visible buffer)

Upstream API we use directly (no patch needed)

APIUsage
Term::new(config, dimensions, event_proxy)Create terminal grid
Processor::advance(&mut term, data)Feed PTY bytes
term.grid() / term.grid_mut()Read cell grid, cursor, history
term.damage() / term.reset_damage()Dirty-row tracking for incremental serialization
term.scroll_display(Scroll::Delta)Viewport scrolling
term.mode()Check TermMode flags (ALT_SCREEN, SHOW_CURSOR, kitty keyboard)
term.cursor_style()Cursor shape (block/beam/underline)
term.colors()Dynamic color palette (OSC 4/10/11/12 overrides)
term.selection / term.selection_to_string()Native selection API
RegexSearch::new(query) + term.regex_search_right()Native DFA regex search across grid + scrollback
EventListener traitCapture bell, title, clipboard, PTY write-back events

Canonical HTTP text snapshots read a single absolute range from this grid. They must not rebuild a snapshot by appending a separately retained log to the screen: increasing terminal rows can move history back into the viewport and make the two representations overlap.

Clipboard selections use the same absolute grid coordinates. TUICommander joins rows marked with WRAPLINE, trims terminal padding, and then removes only coherent multi-line Claude space/NBSP ▎ visual gutters. Claude composer selections remove one leading ❯ and two continuation-margin columns only when the ordered selection starts at grid column zero with the marker in the first two cells and the preceding row has no WRAPLINE. Partial body selections and VT soft-wrap continuation origins remain literal. At every retained row, cleanup requires known row provenance. A single copy_origin_unknown flag prevents cleanup even when RI/IL moves the physical row. Eviction or purge of nonblank predecessor content sets it on the surviving boundary row, including a blank survivor; removing only blank rows does not set it. Cap trimming, reprint-tail removal and reflow truncation use the same content-loss rule. There is no separate latent flag, blank-history exemption or resize promotion. Full row replacements (ECH, DCH, ICH, EL or ED covering every column from column zero, and DECALN alignment-screen replacement) share Row::reset, which clears the flag without changing absolute row counters. Partial edits preserve it. A composer redrawn on a blank loss boundary can retain its ❯ in copied text until the row is fully erased. This cosmetic limitation is intentional: literal content takes priority over speculative cleanup of an ambiguous origin. ED1 resets every row above the cursor, including row zero when the cursor is on row one; upstream skips that row with its cursor.line > 1 guard. The cursor row is erased only through its current column, and rows below remain unchanged. ED2 resets all visible rows after scrolling occupied content into history. This clears loss flags that overflow can attach to a fresh blank row with zero scrollback, while retained history keeps its provenance. ED3 does not erase live content. DECALN fills live rows with default-background E cells after resetting provenance; it does not inherit the active erase background. Ordinary printing, including insert-mode shifts and a sequence that overwrites every cell, preserves unknown provenance because it has no explicit whole-row replacement boundary. RI/IL retains the moved row’s flag and literal copy behavior at its new position. Unknown content stays literal; the same width-evidence rule rejoins wraps while preserving short typed lines and deeper content indentation. Pasted prompt glyphs remain content. This normalization is outside the Alacritty fork and is shared by desktop IPC and HTTP clients.

Atomic MCP agent-submission receipts require no Alacritty patch. The receipt boundary is the raw child-output ring offset captured before Enter; after that offset moves, the current agent screen adapter may classify the already-built grid as working, ready, or interrupted. Neither grid mutation nor local input bookkeeping is itself an acknowledgement.

Notable forks and patches (external)

Zed Editor (zed-industries/alacritty)

Zed maintains branches on their fork with patches not yet upstream:

BranchWhatRelevance
osc-133Semantic cell tagging — cells get Osc133CellType (Prompt/Input/Output) from OSC 133 sequences. Fires Event::Osc133. Requires Zed’s VTE fork (osc-133-2 branch).None — already implemented in our own patch (term/cell.rs + Term::osc133 + our VTE osc133 hook). The regex pre-parser this was meant to replace no longer exists.
v0.16-child-exit-patchUses exit_status.into_raw() for ChildExit. Removed from fork (confirmed 2026-05-04).Story 1553 needs re-evaluation — implement independently if needed.
use-zed-vtePins to Zed’s VTE fork with Serialize/Deserialize on parser state. Removed from fork (confirmed 2026-05-04).Was prerequisite for OSC 133; check if osc-133 branch still depends on it.
grid-mutMakes grid_mut() public (removes #[cfg(test)]).Low — we already expose grid access via our own patches.
click-linksURL detection + click-to-open in grid. Ancient branch (pre-0.26 API).None — we handle link detection in our Canvas renderer.
cursor-blinkCursor blink timer via mio::Timer. WIP with debug prints.None — we handle blink in Canvas/JS.
cursor-configRestructures cursor config into cursor.style/hide_when_typing/custom_colors.None — we don’t use alacritty’s config system.
scrollbackAdded scrollback buffer — already merged into upstream alacritty.None (already upstream).
scroll/fix-alt-grid-sizeAlt screen gets zero scrollback — already merged upstream.None (already upstream).

Other projects

  • Rio Terminal — built on alacritty_terminal but maintains its own fork with rendering changes (not relevant to us since we do our own Canvas2D rendering).
  • Ghostty — uses its own terminal emulation written in Zig, not alacritty_terminal.
  • Warp — uses vte + forked alacritty grid internally, tightly coupled to their warpui framework. Not extractable.

Update procedure

Checking for upstream updates

# Check latest version on crates.io
cargo search alacritty_terminal

# Compare with our pinned version
grep "alacritty_terminal" src-tauri/Cargo.toml

Rebasing our patch on a new upstream version

  1. Download the new version:

    cargo download alacritty_terminal@<new_version> -o /tmp/alacritty_new
    

    Or copy from ~/.cargo/registry/src/ after adding the new version to Cargo.toml.

  2. Diff our patches against the old upstream:

    diff -ru ~/.cargo/registry/src/*/alacritty_terminal-0.26.0/src/term/mod.rs \
             src-tauri/patches/alacritty_terminal/src/term/mod.rs
    
  3. Apply patches to the new version. Our changes are small and isolated:

    • resize_reflow in term/mod.rs — add method, modify resize() to call it
    • mark_fully_damaged visibility in term/mod.rs — fn → pub fn
    • named_color_to_index in term/color.rs — new function, no existing code modified
  4. Update Cargo.toml version and the patches/ directory.

  5. Run tests: cargo test terminal_grid && cargo test vt_log

Checking Zed’s fork for new patches

# List branches on Zed's fork
gh api repos/zed-industries/alacritty/branches --jq '.[].name'

# Compare a specific branch
# https://github.com/zed-industries/alacritty/compare/master...<branch>

Periodic review cadence

Driven by the alacritty-upstream entry in .claude/scheduled-checks.json (every 20 days). Each run:

  • Check crates.io for new alacritty_terminal releases (cargo search alacritty_terminal).
  • Review Zed fork branches for new patches relevant to our embedded backend.
  • On major issues: If we hit terminal emulation bugs, check if upstream or Zed has a fix before writing our own.

Deliberately not ported (re-evaluate only if the reason changes):

UpstreamWhatWhy we skipped it
d692748d (2026-08-31)Replaces the unsafe hand-rolled Vec fill in grid/row.rs Row::new with iter::repeat_with, dropping the undocumented columns >= 1 limit.Pure cleanup with no user-visible defect, and it collides with our reflow_wrap patch: the hunk’s closing context is the Row { inner, occ: 0 } literal, which in our fork carries a third field. TUIC never builds a zero-column row, so the unsoundness it fixes is unreachable here. Take it for free at the next version bump instead of hand-merging it now.

Planned patches (stories)

StoryPriorityDescriptionStatus
1552-02ffP2Port Zed OSC 133 semantic cell tagging (requires VTE fork)Done — cell_type tagging + VTE osc133/osc7 handlers implemented
1550-64b1P3Move OSC 133 extraction into VTE handler (blocked by 1552)Done — VTE routes OSC 133 directly to Handler::osc133()
—P2OSC 7770 TUIC protocol (state/suggest/intent)Done — full pipeline from VTE→Event→PTY→ParsedEvent
—P3Use cell_type for idle detection (OSC 133 shells)Pending — next step after TUIC protocol
1553-5e8cP3Port Zed child-exit raw waitpid statusPending

Stored terminal marker coordinates

OSC 133 and OSC 7770 event rows use grid.total_scrolled() + cursor row. Capture this origin inside the OSC handler, before later bytes in the same chunk can evict history. Retained history size is not an absolute origin.

Scrollback memory and the single-grid contract

VtLogBuffer owns the session’s only TerminalGrid. That grid backs desktop frames, HTTP scrollback, copy and search, as well as log extraction. Its 10,000-row history is user-visible history; it is not a duplicate scratch grid that can be capped independently. The separate 10,000-line LogLine deque serves mobile pagination. Reducing its grid history would delete terminal scrollback.

The 2026-10-07 six-session measurement uses 220 columns and full history, including a resize cycle to expose retained row capacity. The workload and native RSS, vmmap, footprint, heap, and diagnostics measurements are recorded in story 1575-2c7b. Row cache sizing retains a 32-row allocation batch instead of allocating one row at a time. This patch changes allocation retention, not the history limit or transport behavior.

Metric (six sessions after the width cycle)BeforeAfter
RSS (KiB)977,584966,176
Physical footprint (bytes, diagnostics)1,118,438,912596,067,624
Malloc bytes in use (diagnostics)822,958,480454,159,264
Heap bytes (heap -s)822,623,440453,913,968
LogLine bytes (diagnostics estimate)14,638,75214,638,752

The requested 50% RSS reduction was not reached: RSS fell by 1.17%, while malloc bytes in use fell by 44.81% and physical footprint by 46.71%. The after footprint report also classified 355 MB of Malloc Small as reclaimable; RSS alone does not show the live-allocation reduction. Heap row allocations changed from approximately 60,294 12 KiB blocks to 60,295 6 KiB blocks. At that measured 6 KiB per 220-column row, the spare-row bound is 6,144,000 bytes per grid before and 196,608 bytes after. This is a bound calculation, not a separate measurement of the cache’s contribution to the total reduction.

The largest remaining payload is the retained terminal grid: (10,000 + 24) × 220 × 24 = 52,926,720 nominal cell bytes per session, before inactive-screen rows, spare rows and allocator rounding. Compact cells or file-backed history need a separate decision; neither is included in this patch.

Resize repaint reconciliation

Every primary-screen resize records the source/target column widths, grid sequence, original viewport-top row index and the viewport’s own row snapshot (#1407-1ab2). Live VtLogBuffer width changes use full reflow so visible logical lines retain their complete content and soft-wrap continuity. History-only reflow truncates visible cells on shrink and leaves natural wrap flags at the old column on growth, breaking session-output redaction. The cursor-addressed Ink redraw remains a separate replacement domain for the reconciliation below.

The first explicit full viewport erase can replace an old displaced row only when that complete physical row occurs in the new replacement. A two-row viewport anchor does not license removing other old rows.

Full reflow can move the original viewport prefix into history even when the height is unchanged. A unique complete two-row source prefix locates that owned range in the reflowed coordinates; incomplete or ambiguous matches retain the rows. Each old row still needs complete replacement proof.

Blank screen-prefix rows created by proven suppression retain explicit provenance. A write, erase, scroll or alternate-screen switch touching them invalidates that provenance, even if the visible text remains blank. Before the next full reflow, only still-owned synthetic blanks are removed without adding history. Program-authored separators are never included.

A fresh redraw prefix is suppressed only when its entire text equals a contiguous suffix of an immutable history snapshot ending immediately above the owned viewport. The snapshot includes at most four original viewport heights, capped at 256 rows. Only its first row may start inside a source logical line, with a fragment of at least 16 visible characters; all following content must match exactly through the anchor. Short fragments, non-suffix matches, gaps or missing anchors preserve the entire prefix. Trailing historical blank separators remain in the comparison: a redraw omitting that gap provides no replay proof. Leading blank rows owned by the viewport remain in its replacement domain, so the prefix boundary excludes their replacement. This path never deletes old history. A genuinely new prefix identical to that complete retained suffix is indistinguishable from a replay: its older copy remains, but event multiplicity is lost. This bounded residual is explicit and regression-tested.

Height growth records the exact history coordinates pulled into the new viewport, so their new redraw is retained. Matching preserves intra-line whitespace and uses the shared greedy-width rule for Ink’s hard-line word wrapping. The bounded snapshot retains recorded source widths for unchanged hard rows across subsequent width changes; native VT soft wraps are joined before comparison, and blank paragraph separators remain explicit. Fresh visible duplicates are blanked without shifting child row coordinates. Alternate screens and ordinary edits provide no replacement authority. The recorded streaming and idle PTY captures and resize byte timelines live in crates/tuic-terminal/src/fixtures/claude-resize-1407/.

Resize-owned physical fragments retain their complete logical-record identity from the post-resize WRAPLINE boundaries, before a redraw can erase a continuation still on screen. Replacement proof must match that complete record; a substring in a different replaced record never licenses deleting an omitted history record.