Conversation
brainkim
marked this pull request as draft
September 13, 2026 22:27
brainkim
force-pushed
the
images
branch
3 times, most recently
from
September 16, 2026 05:56
7038d95 to
4cfb203
Compare
An image element now builds a UA shadow tree holding its alt text, loads a PNG from a data: URL, a file: URL or a path, and reads the intrinsic size out of the IHDR chunk. The box is the width and height attributes or CSS, else the image over the terminal's cell size, else the alt text. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Two more startup questions: XTWINOPS 16 for what a cell measures in pixels, and the kitty graphics protocol's own query, which needs the wire reader to bound an APC reply the way it already bounds the clipboard's OSC. iTerm2 has no query, so its environment answers for it, and only where the kitty question went unanswered. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
A placement is a cell rect the painter blanks and the screen writes after the rows, with the cursor moved to its corner first: kitty gets one transmission per image and a placement per frame it stays visible, and a deletion when it goes; iTerm2 gets the whole file each frame, since it has no placement ids. Detaching releases every placement the terminal still holds, so none is left over the shell. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Every image landed at its own pixel size wherever the cursor stood, and the placement that was meant to show it went in after that stray copy. The transmission asked kitty to display as well as store: a=T. Now it only stores, and a placement leaves the cursor where it found it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
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.
Behavior
<img>renders. Before, it was an element with reflected attributes and no box: nothing on screen, not even its alt text. After:widthandheightattributes or CSS width and height in cells; else the PNG's pixel size divided by the terminal's cell size, rounded up; else the size of its alt text.<img alt="">with no image is an empty box, no border or placeholder glyph.srcmay be adata:URL, afile:URL, or a path relative to the working directory. PNG only in this release.loadanderrorfire;naturalWidth,naturalHeight,complete,currentSrc,x,yanddecode()behave as in a browser. Changingsrcreloads.bun examples/images.tsfrom the repository root shows a PNG the script builds and hands over as adata:URL,docs/solitaire.pngfrom a path, and a missing source falling back to its alt text.Design
Two questions go to the terminal at startup, after the mode probes and their echo erase:
CSI 16 tfor the cell's pixel size (default 8 by 16 when unanswered) and the kitty graphics protocol's own query, which kitty, Ghostty and WezTerm answer. The wire reader learns to bound an APC reply the way it bounds the clipboard's OSC. iTerm2 has no query, soTERM_PROGRAMorLC_TERMINALanswers for it, only when the kitty question went unanswered. Until an answer lands, images are their alt text; the answer triggers a repaint.A placement is a cell rect. The painter blanks the cells under it so text never paints over the pixels, and the screen writes the image sequence after the frame's rows with the cursor moved to the placement's corner first. Kitty gets one transmission per distinct image, in 4096-byte chunks, and a placement each frame it stays visible, and a deletion when it leaves or the document detaches, so no placement is left over the shell. iTerm2 has no placement ids and gets the whole file each frame. The intrinsic size comes from the PNG's IHDR chunk, no image library.
Change
dom.ts:HTMLImageElementbuilds a UA shadow tree holding the alt text, loads throughnode:fs/promisesimported dynamically so the browser bundle still builds (onlydata:works there), and gets the members above with lib.dom's types.exchange.ts: the two questions, the APC reply bounding, andnegotiateImages, sent afterscrubProbeEchoso the erase still follows the mode probes.screen.tsandpainter.ts:drawImageon the cell context, per-frame placements, the kitty and iTerm2 sequence writers, and a release on detach.layout.ts: the image's box from attributes, natural size over the cell size, or alt text.examples/images.ts, registered in the playground; an Images section in the layout guide.Tests
tests/images.test.ts, 11 tests: the alt-text box, attribute sizing, a valid PNG built in the test firingloadwith the right natural size and the box scaling with a scripted cell-size reply,erroron a missing file, kitty transmission and placement when the mock answers the query, no sequence and alt text when it does not, the iTerm2 form under its environment, and placement deletion on removal. Full suite 1636, drift exact, all 38 examples verified.Conflicts with #92 in
painter.ts; whichever merges second gets rebased.🤖 Generated with Claude Code
https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D