Skip to content

fix(terminal): serialize only the visible width after a column shrink - #22586

Open
Jinwoo-H wants to merge 14 commits into
mainfrom
fix/terminal-serialize-alt-screen-width
Open

Jinwoo-H wants to merge 14 commits into
mainfrom
fix/terminal-serialize-alt-screen-width

Conversation

@Jinwoo-H

@Jinwoo-H Jinwoo-H commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 15 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1053 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1053
Prod 9 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1219 $\color{#cf222e}{\Huge{\mathbf{−}}}$​42 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1177

ELI5

When OpenCode's pane gets narrower, xterm still keeps the old, wider rows in memory past the new right edge. When Orca saved a copy of the screen, it saved those hidden leftovers too. Coming back to the worktree redraws the pane from that copy, so the leftovers wrap onto their own rows and the screen looks shredded. OpenCode only redraws cells it thinks changed, so it never cleans them up. This change saves only what fits the current width.

What Changed

Before: open the right sidebar while OpenCode is writing an answer, switch to another worktree, switch back. The pane shows every other row filled with pieces of the old wide screen. It stays like that until the pane gets wider again.

After: the pane comes back exactly as OpenCode drew it.

Mechanism:

  • xterm only trims rows on a column shrink when it reflows, and the alternate screen used by full-screen apps (OpenCode, vim, htop) has no scrollback, so it never reflows. Each row keeps its old length.
  • @xterm/addon-serialize wrote every non-final row out to the row's own length, not to terminal.cols.
  • Every Orca screen snapshot goes through that add-on: the daemon's HeadlessEmulator.getSnapshot, main's model serializer, and the renderer serializer. So a snapshot taken after any narrowing replays stale tails that wrap into junk rows.
  • The fix is in Orca's existing source patch for the add-on. Every line.length read is clamped to cols. A wide (CJK or emoji) glyph cut in half by the shrink is written as a blank so the row still spans exactly cols. Bundles, maps and the lockfile hash are regenerated per docs/reference/xterm-patch-regeneration.md; --check reports them in sync.

Why

  • Fixing it in the serializer covers every place a snapshot is made and replayed (daemon, main, renderer, SSH), with one change.
  • Alternative considered: trim rows inside the emulator on shrink, which is what real terminals do. That needs a patch to xterm's core buffer rather than the add-on. It would additionally cover a narrow-then-widen corner case: a restored pane won't show the stale cells a live xterm would re-show after widening. Real terminals drop them too, so this change's output matches real terminals there. It is left as a possible follow-up.
  • A client-side guard for snapshots from older hosts isn't practical: the client would have to re-emulate at the capture width, which it often doesn't know.

Linked Issue

Fixes #18178

Visual Proof

N/A for screenshots: in the background-launched harness the WebGL renderer stopped drawing glyphs, so captures show only bands of color. The proof is cell-level instead (see Testing).

Minimal reproduction without Orca (xterm headless + serialize add-on): paint a 135-col alt-screen frame, resize to 48, repaint at 48 without clearing, serialize, replay at 48. On main 4 of 6 rows come back as wrapped ....; with this patch all 6 are correct.

Testing

Recipe (by hand, local Mac): close the right sidebar; give OpenCode a prompt with a long answer; while it streams, open the right sidebar; switch to another worktree for ~3 s; switch back.

Build Result Pane vs host model read directly
main 3/3 garbled (6/6 incl. earlier runs) 789–874 text cells wrong
this branch 5/5 clean 0

On main the host model itself matched OpenCode's byte stream exactly (checked against an independent @xterm/headless fed a PTY tap). Only the serialized-and-replayed image was wrong. Controls stayed clean on main: OpenCode idle while away (no restore happens), and the sidebar opened after leaving.

  • I manually tested these changes locally (macOS; background-launched dev build driven over CDP)

  • Automated tests added: src/main/daemon/headless-emulator-shrink-snapshot.test.ts. It covers:

    • the alt-screen shrink round trip;
    • a normal buffer without reflow (old Windows ConPTY);
    • a CJK or emoji glyph straddling the new edge, with a control where it ends exactly at the edge.

    All fail on main's patch and pass here. pnpm tc and check:code-quality:changed are clean, and the xterm contract tests pass. The only failures in the wider daemon/shared/terminal-pane run are the repro-13767 real-PTY timeout tests, which fail the same way on main.

AI Disclosure

Review

Adversarial review found no regressions. The one hole it confirmed (the wide glyph straddling the edge) is fixed in ecfdea0.

Agent skill upstream boundary

  • Not applicable

Notes

  • Cross-platform: the change is platform-neutral. It also fixes the normal buffer on Windows ConPTY builds without reflow.
  • SSH: fixed too, because the SSH restore uses main's model serializer.
  • Mobile and web: clients replay what the host serializes, so they are fixed once the host updates.
  • Remote wire: no field or opcode changes. A new host publishes a strict subset of the old snapshot content, and old clients replay it as ordinary ANSI. A new client paired with an old host still sees the old garble until that host updates.
  • Companion PR fix(terminal): stop dropping visible alt-screen output tagged as hidden-resize repaint #22587: fixes a separate path where visible alt-screen output was dropped after a hidden resize.
  • Performance: the serializer now walks fewer cells, never more.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • Before/after: N/A with reason above
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (typecheck and targeted tests local; CI will cover the rest)

xterm does not reflow the alternate buffer (or a normal buffer under
pre-21376 ConPTY), so after a shrink each line keeps its old length.
SerializeAddon walked every non-final row to line.length, so any snapshot
taken after a shrink carried stale right-hand cells that wrapped into extra
rows on replay; restores repainted that garbage and a differential TUI such
as OpenCode never cleared it.

Clamp the row walk and the wrap-boundary lookups to the terminal's columns
in Orca's addon-serialize source patch, and regenerate the bundles, maps and
lockfile hash per docs/reference/xterm-patch-regeneration.md.
Drops the private-terminal casts the casting gate flags; the normal-buffer case
now drives a plain pre-21376 ConPTY terminal and its SerializeAddon directly.
…ializing

After a non-reflowing shrink a width-2 glyph can have its lead half in the last
column and its trailing half past the grid. Serializing the lead half makes the
replay wrap it to the next row and shift every row below, so serialize that
cell as a blank and keep the row exactly the grid's width. A glyph ending
exactly at the edge is unchanged.
@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 2514d8b2-0a81-4988-ba67-ad3a55bb1a38

📥 Commits

Reviewing files that changed from the base of the PR and between 4eff4a5 and ccfc5ad.

📒 Files selected for processing (1)
  • src/main/daemon/serialize-grid-roundtrip.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

The xterm serializer patch changes visible-width cell handling, cursor and row tracking, style output, inverse-styled empty cells, and OSC 8 links. New tests cover snapshots after column shrink, clipped wide glyphs, and trailing background rows. The change also adds a round-trip oracle, differential fuzz tools and tests, a script to build serializer output at a selected git ref, and PTY transcript fixtures with replay validation.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to ccfc5

The serializer change is mergeable after normal checks; no actionable issue remains established.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning For [#18178], this PR implements visible-width serialization after column shrink. It blanks clipped wide glyphs and adds alternate-buffer, normal-buffer, edge-case, replay, differential-fuzz, and tran… Implement and test atomic damage tracking and full repaint invalidation for streaming redraws and pane or focus switches, or keep [#18178] open for those remaining symptoms.
Docstring Coverage ⚠️ Warning Docstring coverage is 17.91% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 67 functions across 11 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, specific, and accurately describes the primary change: limiting serialization to the visible terminal width after a column shrink.
Description check ✅ Passed The description follows the required template and provides clear ELI5, change, rationale, linked issue, visual-proof explanation, testing details, review notes, compatibility considerations, and check…
Out of Scope Changes check ✅ Passed The serializer patch, shrink and replay tests, differential-fuzz harness, build helper, PTY fixtures, and screen-reader regression test all support the visible-width serialization and replay behavior …
Full details: Linked Issues check

Explanation

For [#18178], this PR implements visible-width serialization after column shrink. It blanks clipped wide glyphs and adds alternate-buffer, normal-buffer, edge-case, replay, differential-fuzz, and transcript tests. The issue also requires clean frames during streaming redraws and pane or focus switches, with atomic damage tracking and full repaint invalidation. The reviewed changes do not implement or test those behaviors.

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 176066a5-4b8b-4399-a28b-7a2d2d8ed0dc

📥 Commits

Reviewing files that changed from the base of the PR and between 4064653 and ecfdea0.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • config/patches/@xterm__addon-serialize@0.15.0-beta.300.patch
  • config/patches/xterm-src/@xterm__addon-serialize@0.15.0-beta.300.src.patch
  • src/main/daemon/headless-emulator-shrink-snapshot.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 3 remain after this review.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — one coverage gap worth a look.

Reviewed changes

This PR clamps every buffer-line length read in Orca's @xterm/addon-serialize source patch to the terminal's current width, so a snapshot taken after a non-reflowing column shrink (the alternate screen always; the normal buffer on pre-21376 ConPTY) describes only the visible grid.

  • Width-clamped serialization — SerializeAddon.ts gains visibleCellCount(line, cols) and isClippedWideCell(...); the base serialize loop's per-row end column, _rowEnd's last/second-last cell and _nullCellCount cursor math, and both handler constructors now use the terminal's cols.
  • Clipped wide glyph — a width-2 cell whose trailing half fell past the grid is serialized as a blank that keeps the row exactly cols wide.
  • Patch bookkeeping — regenerated compiled bundle/maps and the pnpm-lock.yaml patch hash.
  • Test — headless-emulator-shrink-snapshot.test.ts pins the alt-screen shrink round trip, the normal-buffer non-reflow case, and CJK/emoji cells that straddle the edge versus end exactly at it.

I verified against the installed patched addon that the new suite fails 6/6 on the pre-PR patch (stale . tails wrap into extra rows) and passes 6/6 on this one, that node config/scripts/regenerate-xterm-patches.mjs --check reports the tree in sync, and that a wrapped no-reflow normal buffer round-trips correctly.

ℹ️ Wrapped-line clamp in _rowEnd has no automated guard

The added tests position every row with \x1b[<n>;1H, so nextLine.isWrapped is always false and _rowEnd takes its \r\n branch. The clamp this PR adds to the wrapped branch (thisRowLastChar read at currentLineCells - 1, the _backgroundCell substitution, and the contentCellCount guard on the C/D cursor math) therefore ships untested.

Technical details
# Untested wrapped-line clamp in `_rowEnd`

## Affected sites
- `config/patches/xterm-src/@xterm__addon-serialize@0.15.0-beta.300.src.patch` — `_rowEnd` wrapped branch: `currentLineCells`/`thisRowLastChar` clamp and `contentCellCount`.
- `src/main/daemon/headless-emulator-shrink-snapshot.test.ts` — cases 1 and 2 drive rows with explicit cursor positioning, so `nextLine.isWrapped` is false.

## Required outcome
- A regression guard for the wrapped-line path: a no-reflow shrink of a normal buffer whose content actually wrapped at the old width, serialized and replayed at the narrow width.
- This is the only branch where `currentLine.length - _nullCellCount` could exceed `cols` (the bug the `contentCellCount > 0` guard exists for); nothing currently pins it.

## Open questions for the human
- Is the wrapped branch reachable for the target hosts (pre-21376 ConPTY), or is it considered dead enough to leave uncovered?

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

… modules

#22452 added src/shared/main-agent-status.ts and src/shared/agent-turn-outcome.ts,
which agent-status-types.ts imports, so the session route's closure grew by two
local modules (4218 -> 4220). That change was src/shared-only, so its own CI never
ran this suite; main has been at 4220 since, and any PR that fires the mobile web
app job fails on the stale pin. Measured on 4064653 and on origin/main 3ea15dd.
… replay

Seeded VT streams (text, CJK/emoji/combining, SGR, cursor/edit ops, scroll
regions, DECAWM/IRM, alt-screen variants, DECSC, and shrink-heavy resizes)
drive a source terminal in three modes: reflowing normal buffer, alternate
buffer, and a non-reflowing pre-21376 ConPTY normal buffer. At each checkpoint
every SerializeAddon build under test serializes it, and each output is
replayed into a fresh terminal of the same size and compared cell by cell,
plus cursor, active buffer and modes.

CI runs 25 seeds per mode against a pinned list of pre-existing divergences,
and replays the committed PTY transcripts (the existing agent fixtures plus new
vim, less, pico, and OpenCode captures) under four resize schedules. Point
ORCA_OLD_SERIALIZE_ADDON at a previous patched build to also check byte
identity when no line is wider than the grid, and that no checkpoint regresses.
…t ref

config/scripts/build-serialize-addon-at-ref.mjs reverse-applies the patch that
produced the installed @xterm/addon-serialize dist, applies the ref's patch,
and verifies each step against the patches' blob hashes, so the fuzz can use
origin/main (or any fix commit) as its baseline without a second install.
ORCA_NEW_SERIALIZE_ADDON swaps in a built dist for the build under test, and a
seed-pinned test replays the nine I3 regressions found against origin/main.
The stand-in for a wide glyph clipped by a column shrink came from getNullCell(),
whose width is 0. _nextCell skipped it as a wide trailer, and the row-end wrap
check counted the width-0 _backgroundCell as content, so a soft wrap after the
clipped column was taken as natural and replayed one column early
(conpty seed 1149: `abcdefghi中WRAPPED` at 12 -> 10 cols replayed as
`abcdefghiR`/`APPED`). Blank the cell in place instead: width 1, no codepoint,
its own attributes, so it counts as one empty column and forces the wrap.

Differential sweep vs origin/main, 7000 cases per mode: I1 0 byte diffs, seed
1149 fixed; the remaining I3 regressions are the trailing background-row seeds.
…ment

The OpenCode transcript carried this machine's lane paths in its footer; replace
them with same-length neutral paths so the recorded cursor layout is unchanged.
Without scrollback, _serializeString trims rows after the last content cursor.
A row made only of background-colored blanks emits its erase in _rowEnd but
never moved that cursor, so two or more such rows at the bottom were dropped
(4x3 `r1\r\n\e[48;5;157m\e[J\e[0m\e[3;1H` replayed with the last row blank).
Track the erase separately and extend the kept rows to it, except when the
cursor is wrap-pending: relative moves back from those rows cannot re-create
that state, and doing so regressed normal 508, alt 1425/6647, conpty 4699.

Harness: I1 now exempts checkpoints with a background row after the last text
row, the one place this fix changes bytes on purpose (scope helpers move to
serialize-grid-variant-scope.ts); conpty seed 5 leaves the pinned pre-existing
list. Sweep vs origin/main, 7000 cases per mode: I1 0, I3 regressions 0;
fixed/both-fail normal 967/3914, alt 2628/8316, conpty 6657/2937 (was
323/4558, 2296/8648, 5466/4128).
… background rows

Trailing background-only rows change bytes only when the serialized range has no
scrollback (the trimming path) and the cursor is not wrap-pending; checkpoints with
scrollback or a wrap-pending cursor are held to byte identity again. The 7000-per-mode
sweep against origin/main stays at I1 0 and I3 0.
The serializer now keeps trailing background-only rows, so a painted TUI's screen
read as text ends in \r\n\x1b[NX rows. Serialize the same frame with and without
them and check the Claude option scrape, the empty-prompt check and the fork
transcript read the same thing.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 34b3b79d-23cb-41ae-8b1e-7bfdac5bd9ae

📥 Commits

Reviewing files that changed from the base of the PR and between 0624e75 and 15feb5c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (20)
  • config/patches/@xterm__addon-serialize@0.15.0-beta.300.patch
  • config/patches/xterm-src/@xterm__addon-serialize@0.15.0-beta.300.src.patch
  • config/scripts/build-serialize-addon-at-ref.mjs
  • src/main/daemon/__fixtures__/pty-transcripts/less.meta.json
  • src/main/daemon/__fixtures__/pty-transcripts/less.txt
  • src/main/daemon/__fixtures__/pty-transcripts/nano.meta.json
  • src/main/daemon/__fixtures__/pty-transcripts/nano.txt
  • src/main/daemon/__fixtures__/pty-transcripts/opencode-run.meta.json
  • src/main/daemon/__fixtures__/pty-transcripts/opencode-run.txt
  • src/main/daemon/__fixtures__/pty-transcripts/opencode.meta.json
  • src/main/daemon/__fixtures__/pty-transcripts/opencode.txt
  • src/main/daemon/__fixtures__/pty-transcripts/vim.meta.json
  • src/main/daemon/__fixtures__/pty-transcripts/vim.txt
  • src/main/daemon/serialize-addon-edge-cases.test.ts
  • src/main/daemon/serialize-grid-cell-descriptors.ts
  • src/main/daemon/serialize-grid-fuzz-stream.ts
  • src/main/daemon/serialize-grid-roundtrip.ts
  • src/main/daemon/serialize-grid-transcript-replay.test.ts
  • src/main/daemon/serialize-grid-variant-scope.ts
  • src/main/daemon/serialize-grid.differential.fuzz.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.

Comment thread src/main/daemon/serialize-grid-transcript-replay.test.ts

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

This run re-reviewed the commits since the prior pullfrog review (ecfdea0a94): two serializer fixes, the wrapping differential test infrastructure, and test-only bookkeeping. I read the full diff (all 23 files), verified the fixes against xterm's CellData/Content layout, and ran the two always-on focused suites — serialize-addon-edge-cases.test.ts (3 tests) and headless-emulator-shrink-snapshot.test.ts (6 tests) — both green.

  • Clipped wide glyph now blanks to width 1 — blankClippedWideCell writes content = 1 << 22 (width 1, empty codepoint) instead of reusing the buffer's null cell. I confirmed at runtime that getNullCell().getWidth() is 0 in this xterm build and that WIDTH_SHIFT = 22, so the earlier revision's blank was skipped as a placeholder / read as a wide trailer; the fix is correct.
  • Trailing background-only rows are no longer trimmed — the serializer records the last \x1b[<n>X background erase and, in the no-scrollback fixup, advances the content cursor past it unless the cursor is wrap-pending (cursorX < cols). The added regression replays pin the previously-trimmed output on both normal and alternate buffers.
  • Differential serialize oracle added — seeded VT fuzz (serialize-grid-fuzz-stream.ts) with an I1/I2/I3 verdict oracle (serialize-grid-roundtrip.ts), cell descriptors, variant scope, a step-drop minimizer, and captured PTY transcript replay under resize schedules.
  • Baseline builder — config/scripts/build-serialize-addon-at-ref.mjs reverse-applies the installed patch and applies any git ref's patch with blob-hash checks, so I1/I3 can compare two builds without reinstalling node_modules.
  • Wrapped-line clamp guard — serialize-addon-edge-cases.test.ts replays the seed-1149 soft wrap, covering the _rowEnd wrapped branch the prior review flagged as untested. That feedback is now addressed.
  • Test bookkeeping — PTY fixtures are scrubbed (no /Users/, emails, or tokens; cwd neutralized) and the mobile session-closure pin moved 4218 → 4220.

ℹ️ The differential oracle does not run automatically

The strongest check here — I1 byte identity and I3 "no checkpoint regresses" against a prior build — is gated behind it.skipIf(!OLD_ADDON_PATH), and nothing in CI sets ORCA_OLD_SERIALIZE_ADDON. The always-on coverage is the fixed-seed I2 pin (seeds 1-25, installed build only) plus the two edge-case suites, so a regression outside those seeds and not reducible to the pinned cases would land uncaught.

Technical details
# Differential oracle is opt-in

## Affected sites
- `src/main/daemon/serialize-grid.differential.fuzz.test.ts` — the two I1/I3 tests are `it.skipIf(!OLD_ADDON_PATH)`, so they are skipped unless `ORCA_OLD_SERIALIZE_ADDON` is set.
- `config/scripts/build-serialize-addon-at-ref.mjs` — the only documented way to produce that env's value.
- `.github/workflows/*` — no step sets `ORCA_OLD_SERIALIZE_ADDON`.

## Required outcome
- Decide whether the I1/I3 sweep should run in CI (building the `origin/main` addon per run) or stay an on-demand investigative tool.

## Open questions for the human
- Is the fixed-seed I2 pin plus the edge-case replays considered sufficient always-on coverage, with the differential sweep reserved for local/adversarial use?

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Takes main's session closure pin (4219: main already re-pinned past #22452 and the
#18790 revert).

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

This run re-reviewed the two commits pushed after the previous incremental review (15feb5c0b3). Both are test-only refinements of the coverage around the trailing-background-row fix; no production code changed.

  • I1 carve-out narrowed — hasTrailingBackgroundRow now takes the source terminal and returns early unless the serializer's keep gate holds (buffer.length - start <= rows and cursorX < cols), so the differential oracle asserts byte identity against the old build in every case where the serializer still trims. I verified the gate matches the _rowEnd condition exactly (_firstRow ≡ start, cursorX < cols).
  • Reader-agnostic guard added — a renderer test replays a Claude-Code-like alt screen with and without painted background rows and asserts readClaudeSessionOptionsFromTerminalScreen, agentInputLineCleared, and buildBoundedSessionTranscript return identical results. I ran it: 1 test, pass.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

static analysis is red on this head: the repo's anti-slop/no-reflect-get rule rejects the two Reflect.get(terminal, '_core') reads in serialize-grid-roundtrip.ts. This delta is otherwise a clean origin/main merge, so the failure is code the PR added earlier and it is a mechanical fix.

Reviewed changes

This run re-reviewed the commits since the prior pullfrog review (c5ba3a28): a single merge of origin/main, which absorbs main's session-closure pin. Verifying the head surfaced the red check above plus one cross-platform fixture gap.

  • Merged origin/main into the branch — no product-code change; the mobile-web-app-session-terminal-closure.test.mjs pin now matches main and drops out of the PR diff.

⚠️ New PTY fixtures are not pinned against line-ending normalization

The five src/main/daemon/__fixtures__/pty-transcripts/*.txt captures carry real CR and CRLF bytes (verified: less.txt, vim.txt, and opencode-run.txt all contain CRLF), and serialize-grid-transcript-replay.test.ts:48 reads them as utf8. A Windows checkout with core.autocrlf=true rewrites those bytes (LF → CRLF, and an existing CRLF → CRCRLF), so the replay tests would diverge from the committed evidence. The repo already guards the older captures against exactly this at .gitattributes:39-42 (/src/main/runtime/__fixtures__/*.txt -text), but the new pty-transcripts/ path is not covered.

Technical details
# PTY transcript fixtures unprotected from CRLF normalization

## Affected sites
- `src/main/daemon/__fixtures__/pty-transcripts/{less,opencode-run,vim}.txt` — hold CR/CRLF exactly as the terminal emitted them
- `src/main/daemon/serialize-grid-transcript-replay.test.ts:48` — `readFileSync(join(dir, file), 'utf8')`, replayed and compared byte-for-byte
- `.gitattributes:39-42` — the existing `-text` pin for `/src/main/runtime/__fixtures__/*.txt`, same rationale

## Required outcome
- A Windows (`core.autocrlf=true`) checkout preserves the transcript bytes, so the replay fixtures stay reproducible across platforms.

## Suggested approach
- Add `/src/main/daemon/__fixtures__/pty-transcripts/*.txt -text` next to the existing transcript rule.

ℹ️ Git compatibility failed on an npm-registry ECONNRESET

The other red check, Git compatibility, died in pnpm/setup before any test ran: Error: Could not reach https://registry.npmjs.org/pnpm: fetch failed with ECONNRESET. Environment noise, unrelated to this PR — a re-run should clear it.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/main/daemon/serialize-grid-roundtrip.ts Outdated
…ot Reflect.get

The fuzz oracle reached xterm's private _core with Reflect.get, which the
low-evidence gate rejects. Narrow _core, writeSync and the DECSTBM bounds with
in/typeof checks into one named XtermCoreInternals shape instead.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

This run re-reviewed the commit pushed after the prior pullfrog review (4eff4a5a4a), which resolved the red static analysis check.

  • Reflect.get replaced with a checked parser — the two Reflect.get(terminal, '_core') reads in src/main/daemon/serialize-grid-roundtrip.ts are now served by readXtermCore/readRegion, which narrow _core via in and validate writeSync before use. No casts, and the private core is no longer reached through reflection.

Verified locally on this head: anti-slop/no-reflect-get reports 0 errors for the file, pnpm tc:node is clean, and serialize-grid.differential.fuzz.test.ts (the consumer of the refactored helper) passes. The prior Reflect.get thread is resolved.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

This run re-reviewed the single commit pushed after the prior pullfrog review (ccfc5ad580). It closes the last open finding from the 4eff4a5a4a review, with no production-code change.

  • PTY transcript fixtures pinned against line-ending normalization — .gitattributes gains /src/main/daemon/__fixtures__/pty-transcripts/*.txt -text, mirroring the existing /src/main/runtime/__fixtures__/*.txt -text rule so a Windows core.autocrlf=true checkout preserves the captured CR/CRLF bytes the replay suites compare against. Verified git check-attr text reports unset for the new fixtures.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

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

1 participant