Skip to content

Placeholder tiles are content: printing over the cell removes the tile - #156

Merged
tomlm merged 4 commits into
mainfrom
tomlm/placeholder-tiles-are-content
Sep 6, 2026
Merged

Placeholder tiles are content: printing over the cell removes the tile#156
tomlm merged 4 commits into
mainfrom
tomlm/placeholder-tiles-are-content

Conversation

@tomlm

@tomlm tomlm commented Sep 5, 2026

Copy link
Copy Markdown
Owner

A Unicode-placeholder tile exists because its cell holds the placeholder character; the protocol has no command to remove one tile, because overwriting the cell IS the removal. Tiles were stored as classic Kitty overlay runs, which text never modifies, so anything drawn across a placeholder picture (a dialog, a new screen) kept the picture on top of it permanently.

  • New PlacementKind.Placeholder with content semantics: the split that printing performs on Sixel runs now takes placeholder runs too (a one-column run simply goes). Classic placements keep overlay behaviour and z-ordering untouched, pinned by a new test.
  • Writing a placeholder cell also clears any stale tile another picture left at that cell, since the cell write itself now splits.
  • The test asserting overlay behaviour for placeholders encoded the implementation rather than the protocol (kitty itself removes the tile; image.nvim depends on it) and now asserts the removal, with the reasoning in its remarks.

Perf: the only hot-path-adjacent change is the kind test inside SplitPlacementsAt, which flipped from != Sixel to == Kitty — same single compare, and the method only runs on lines that hold placements.

Found from the consumer side: Consolonia's kitty renderer overwrites occluded placeholder cells exactly as the spec prescribes, and the pictures stayed on top of its dialogs.

BufferLine.CopyFrom carries placements and images

Clone already copies them ("the runs are the picture"); CopyFrom silently dropped them. A host that double-buffers frames through pooled line copies (Iciclecreek.Avalonia.Terminal's FrameCapture) therefore could not represent image rows and had to read them live — which, for a screen drawn entirely as kitty Unicode placeholders, was the remaining paint-mid-write tear (a black band sweeping the picture). Replace semantics, like every other field there: a recycled line that once held a picture must not keep showing it. The lists are copied and the images shared — a stored picture is never mutated in place (a retransmission builds a new one), so holders of a copy are safe against later writes.

Consumed by tomlm/Iciclecreek.Avalonia.Terminal#166, which captures image rows only when this fix is present and falls back to live reads against older emulators.

Suite: 2216 passed / 0 failed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CxS1BnzZ6BhuGovMLNtRQH
https://claude.ai/code/session_01XmKfhMXUAy4n3uLDSUBHo8

A Unicode-placeholder tile exists because its cell holds the placeholder
character; the protocol offers no command to remove one tile, because
overwriting the cell IS the removal. The tiles were stored as classic
Kitty overlay runs, which text never modifies, so an application that
drew a dialog across a picture kept the picture on top of it permanently
and navigating away could not erase it.

A third PlacementKind, Placeholder, carries the content semantics: the
split that printing performs on Sixel runs now takes placeholder runs
too (a one-column run simply goes), while classic Kitty placements keep
their overlay behaviour and z-ordering untouched. Writing a placeholder
cell also clears any stale tile another picture left there, since the
cell write itself now splits.

The test asserting the old behaviour encoded the bug rather than the
protocol and now asserts the removal; classic-overlay survival under
text is pinned by its own new test so the two semantics cannot be
conflated again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CxS1BnzZ6BhuGovMLNtRQH
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Perf comparison — this change, against its base

3 run(s) of each side, alternating on one machine. Allocation is a count and is gated exactly. Time is a measurement, so its gate is derived from the spread this job just observed in itself rather than fixed in advance.

corpus bytes/char gen0/Mchar ns/char Δ time noise gate
scroll-ascii 0.00 → 0.00 0.00 → 0.00 3.49 → 3.48 -0.3% ±1% 4%
sgr-churn 0.00 → 0.00 0.00 → 0.00 9.33 → 9.31 -0.3% ±0% 4%
truecolor 0.00 → 0.00 0.00 → 0.00 9.87 → 9.88 +0.0% ±1% 4%
alt-redraw 0.00 → 0.00 0.00 → 0.00 14.74 → 14.74 +0.0% ±2% 7%
unicode 7.66 → 7.66 0.45 → 0.45 36.52 → 35.32 -3.3% ±1% 4%
flood 0.00 → 0.00 0.00 → 0.00 105.12 → 104.78 -0.3% ±0% 4%

Each corpus is gated at max(4%, 3 × its own noise). A wide noise column means this runner was busy and the timing half of the table should be read as advisory; the allocation half is exact either way.

assemblies measured
  • base: XTerm.NET 2.0.2.0 mvid:c3048032-d19f-486a-a7b4-d178e47b981e
  • head: XTerm.NET 2.0.2.0 mvid:5d73322b-acc3-4edd-b593-0b33f51dc3ca

Perf comparison — cumulative, everything since 2.0.0

3 run(s) of each side, alternating on one machine. Allocation is a count and is gated exactly. Time is a measurement, so its gate is derived from the spread this job just observed in itself rather than fixed in advance.

corpus bytes/char gen0/Mchar ns/char Δ time noise gate
scroll-ascii 0.00 → 0.00 0.00 → 0.00 3.48 → 3.48 +0.1% ±1% 5%
sgr-churn 0.00 → 0.00 0.00 → 0.00 9.26 → 9.31 +0.5% ±1% 5%
truecolor 0.00 → 0.00 0.00 → 0.00 9.86 → 9.88 +0.2% ±1% 5%
alt-redraw 0.00 → 0.00 0.00 → 0.00 14.93 → 14.74 -1.3% ±4% 12%
unicode 7.66 → 7.66 0.45 → 0.45 35.53 → 35.32 -0.6% ±2% 7%
flood 0.00 → 0.00 0.00 → 0.00 105.09 → 104.78 -0.3% ±0% 5%

Each corpus is gated at max(5%, 3 × its own noise). A wide noise column means this runner was busy and the timing half of the table should be read as advisory; the allocation half is exact either way.

assemblies measured
  • base: XTerm.NET 2.0.0.0 mvid:a26a50e5-3343-4b31-bada-ce2a03923fe4
  • head: XTerm.NET 2.0.2.0 mvid:5d73322b-acc3-4edd-b593-0b33f51dc3ca

tomlm and others added 2 commits September 6, 2026 11:09
Clone already copies them ("the runs are the picture"); CopyFrom silently
dropped them, so a host capturing frames through pooled line copies could
not represent image rows and had to read them live -- the remaining tear
for screens drawn entirely as kitty Unicode placeholders. Replace
semantics, like every other field: a recycled line that once held a
picture must not keep showing it. The lists are copied, the images shared;
a stored picture is never mutated in place, so copies stay safe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XmKfhMXUAy4n3uLDSUBHo8

Copilot AI 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.

🟡 Changes recommended

There are a couple of newly introduced comments that contradict the actual implementation (and a missing regression test for the new CopyFrom image/placement behavior), which should be corrected before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR aligns Kitty Unicode placeholder tiles with “content” semantics so that overwriting a placeholder cell removes the corresponding tile (matching kitty/image.nvim behavior), while preserving classic Kitty placement overlay/z-index behavior. It also fixes BufferLine.CopyFrom to carry image placement state so line copies don’t silently drop images.

Changes:

  • Introduce PlacementKind.Placeholder and treat placeholder-tile placements as content (split/removed on text writes), while leaving classic Kitty placements as overlays.
  • Update placeholder writing to emit PlacementKind.Placeholder so buffer write paths can clear/replace tiles correctly.
  • Fix BufferLine.CopyFrom to copy placements/images and add/adjust tests covering the intended semantics.
File summaries
File Description
src/XTerm.NET/InputHandler.KittyGraphics.cs Marks placeholder-tile placements as Placeholder so overwrites clear tiles.
src/XTerm.NET/Graphics/LinePlacement.cs Adds PlacementKind.Placeholder with documentation for content semantics.
src/XTerm.NET/Buffer/BufferLine.cs Updates placement-splitting logic for placeholder tiles; fixes CopyFrom to copy placement/image lists.
src/XTerm.NET.Tests/Graphics/KittyZIndexTests.cs Adds regression test ensuring classic Kitty placements remain overlays under text.
src/XTerm.NET.Tests/Graphics/KittyPlaceholderTests.cs Updates placeholder behavior test to assert tile removal on overwrite; adds replacement test.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +1111 to +1116
_placements = line._placements is null
? null
: new List<Graphics.LinePlacement>(line._placements);
_images = line._images is null
? null
: new List<Graphics.TerminalImage>(line._images);
Comment on lines +1106 to +1110
// The runs are the picture, exactly as in Clone above -- a copy that skipped them would
// silently lose it. Replaced rather than merged, like every field here: a recycled line
// that once held a picture must not keep showing it. The lists are copied but the images
// they name are shared; a stored picture is never mutated in place (a retransmission
// builds a new one), so a reader holding this copy is safe against later writes.
Comment thread src/XTerm.NET/InputHandler.KittyGraphics.cs
@tomlm
tomlm merged commit b2d78f8 into main Sep 6, 2026
5 checks passed
@tomlm
tomlm deleted the tomlm/placeholder-tiles-are-content branch September 6, 2026 21:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants