From 1a3d3584c0eed9f02fb7dac175dfceb197118da6 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:15:30 -0700 Subject: [PATCH 1/2] terminal: test VT replay of blank cells after a styled cell A styled cell followed by skipped default cells replays the blanks with the previous cell's background. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/terminal/formatter.zig | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/terminal/formatter.zig b/src/terminal/formatter.zig index 8b87f2b50a7..5a2ca3252a5 100644 --- a/src/terminal/formatter.zig +++ b/src/terminal/formatter.zig @@ -6724,6 +6724,41 @@ test "Page VT background color on trailing blank cells" { try testing.expect(has_red_bg_line1); } +test "Page VT unstyled blank cells do not inherit the previous background" { + // Claude Code draws its mascot with a black background, then moves the + // cursor past untouched cells with CHA before resetting SGR. The skipped + // cells are default-styled, so the replay must not paint them black. + const testing = std.testing; + const alloc = testing.allocator; + const io = testing.io; + + var builder: std.Io.Writer.Allocating = .init(alloc); + defer builder.deinit(); + + var t = try Terminal.init(io, alloc, .{ + .cols = 20, + .rows = 5, + }); + defer t.deinit(alloc); + + var s = t.vtStream(); + defer s.deinit(); + + s.nextSlice("\x1b[48;2;0;0;0mAB\x1b[6G\x1b[49mC"); + + const pages = &t.screens.active.pages; + const page = pages.pages.last.?.page(); + + var formatter: PageFormatter = .init(page, .vt); + try formatter.format(&builder.writer); + const output = builder.writer.buffered(); + + try testing.expectEqualStrings( + "\x1b[0m\x1b[48;2;0;0;0mAB\x1b[0m C", + output, + ); +} + test "Page VT preserves a fully styled blank row" { const testing = std.testing; const alloc = testing.allocator; From 55974027991e4230210712796cd87c7b3a2018ee Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 30 Sep 2026 01:15:37 -0700 Subject: [PATCH 2/2] terminal: reset style before VT replay of blank cells The formatter wrote accumulated blank cells as spaces before it switched to the next cell's style, so the spaces took the previous cell's colors. Claude Code's mascot sets a black background and skips three cells with CHA, which a cmux Cloud replay painted as a black box. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/terminal/formatter.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/terminal/formatter.zig b/src/terminal/formatter.zig index 5a2ca3252a5..f52c49fd70e 100644 --- a/src/terminal/formatter.zig +++ b/src/terminal/formatter.zig @@ -1261,6 +1261,14 @@ pub const PageFormatter = struct { // This cell is not blank. If we have accumulated blank cells // then we want to emit them now. if (blank_cells > 0) { + // Blank cells have the default style. Close any open + // style first so the spaces do not take the previous + // cell's colors, as the row-break path above does. + if (formatStyled(self.opts.emit) and !style.default()) { + try self.formatStyleClose(writer); + style = .{}; + } + try writer.splatByteAll(' ', blank_cells); if (self.point_map) |*map| {