Repository navigation
0.2.0: images and canvas follow-up, and a DOM that matches lib.dom - #141
Merged
Merged
Conversation
The 8 by 16 guess at a cell's pixels was defined in the framebuffer, re-exported from cssom so index.ts's old import still worked, chosen again in index.ts for a transport that is not a terminal, though that framebuffer never leaves the guess anyway, and used a third time as cssom's own fallback for image sizes, read through a callback index.ts registered with cssom. The framebuffer now holds the guess privately and the cell it adopts from the terminal. dom.ts reads that cell from a document's framebuffer with getCellPixels, image sizing reads it there, and "auto" reads the framebuffer directly. The re-export, the second choice and the callback registry are gone. A document no TermDOM draws draws no image, so its image cell is the page's. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The per-document hover listener count was kept in one WeakMap, wrapped by watchHoverListeners in a closure that read it, and that closure kept in a second WeakMap for hoverListenerCount to call. hoverListenerCount now reads the count, and attachDocument sets the count's onChange itself. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The cellSize option became a closure in index.ts, registered with cssom for getCellSize to call, while index.ts decided "auto" a second time in its own flag. The engine behind renderANSI then had its closure written over after it was built, and two cell sizes were told apart by object identity. attachDocument now keeps the option, checked, on the document, and getCellSize reads it there: the unit cell, the framebuffer's cell for "auto", or the size given. TermDOM reads "auto" from the same place, and a cell measured anew changes the page exactly when the setting is "auto". The renderANSI engine is built with its parent's cell, and cells are compared by their sizes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Whether the terminal is light or dark was resolved in index.ts, from the exchange's answer, then the transport's, then light, and handed to cssom through a callback registered per document, where cssom fell back to light a second time. The exchange already has both the answer and the transport, so its colorScheme now resolves all three, and cssom reads it through the document's exchange. The callback registry and index.ts's copy are gone. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The document's loader and the signal that ends its loads went to resources.ts through setResourceLoader, kept in a registry there for dom.ts to look up, while everything else a TermDOM hands its document goes through attachDocument. attachDocument now takes them with the rest, as one object of named parts in place of eight positional arguments, and the image load reads them from the attached document. The registry is gone. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
replaced.ts re-exported isReplacedElement from images.ts for layout.ts and painter.ts, which now import it from images.ts. cssom.ts and cssselectors.ts each imported dom.ts in two statements, and cssvalues.ts imported cssselectors.ts's types in a second one; each is one statement now. getNestedRules was used nowhere and is gone. Twenty-five functions, constants and types that only their own module uses are no longer exported. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
dom.ts called its isTextControl the single definition of a text control while editing.ts had another isTextControl testing something else: whether an element is an input, textarea or select, which keeps its keys. That one is isFormControl now, and dom.ts's comment no longer claims to be the only one. A layout comment named a bidi.ts that does not exist. Comments that told of past bugs or past code now say what the code does, and the list gutter's explanation is kept in one place. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
isControlByte was written out in both the framebuffer and the exchange; it lives in text.ts now. The 80 by 24 a silent terminal is taken to be was written five times, and the second a question to the terminal waits eight times; each is a named constant in exchange.ts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
dom.ts wrote the trim of HTML's ASCII whitespace out nine times and had its own split, cssselectors.ts had a second split, hints.ts a third trim, and the CSP parser trimmed with String.prototype.trim, which also strips Unicode spaces that CSP3 does not. text.ts, which already owns toASCIILowercase, now has trimASCIIWhitespace and splitOnASCIIWhitespace, and each of them uses those. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
HTML's rendering rules (§15.4.2) keep an image that is not showing a box only when it has a size and may still load, has no alt text, or is in a quirks-mode document. Otherwise its alt text is laid out as inline text. The text lives in the image's UA shadow tree, as Chromium keeps it, and layout and painting ask one function whether an element is replaced. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The drift script now builds a TermDOM and walks every interface object on its window, and the objects a page reaches without a constructor (navigator, screen, the 2D context, a computed style), failing on any member lib.dom does not declare and on any prototype in a chain that is not a standard interface. Members a current standard defines that this lib.dom lacks are listed with where they are defined. The members it found are gone: the live lists' compute, namedProperties and getChangedMembers, colorAt on CanvasGradient and CanvasPattern, the CSS rules' prelude, atKeyword and static atRule, a computed style's getComputedValue and declaredCustomProperties, and innerText on MathMLElement, which the DOM puts on HTMLElement only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
A page could reach classes the engine made for itself through Object.getPrototypeOf: LiveList and HTMLCollectionBase under every collection, LayoutObserver under both observers, the CSS rules' shared bases, ComputedStyleDeclaration under getComputedStyle(), and MatchingCollection for getElementsBy*. DOMRectList inherited every Array method, and ResizeObserver had IntersectionObserver's takeRecords(). LayoutObserver is gone: each observer keeps its own state and calls three functions. The CSS rules call init functions and keep their own style and cssText, and @counter-style, @Property and @font-palette-values lose the style attribute their spec does not give them. getComputedStyle() returns a plain CSSStyleProperties reading through to the engine's computed declaration. getElementsBy* return a plain HTMLCollection that carries its test. LiveList's methods are copied onto each list interface. DOMRectList is its own interface. The drift script now also checks that what a page gets from a call is an instance of the interface itself. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The renderer read two custom properties, in the author's own namespace, to pick ASCII or box-drawing pieces for stretched operators and radicals, and the Mathematical Alphanumeric block for mathvariant. Both are gone with the tables and branches only they reached. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
… them No browser lets a page's ::part() into a built-in control. A page now styles one through ::field-content, ::slider-track and ::slider-fill from the CSS Form Control Styling draft, the customizable select's ::picker(select) and ::picker-icon, and a meter's level through ::-termdom-meter-optimum-value and its two siblings, after WebKit's prefixed ones. The UA sheet uses the same names. The other parts, a button's brackets, a gauge's groove and a picker's rows, are the UA sheet's alone, and the data-* flags on them are gone. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
::field-content, ::slider-track and ::slider-fill are in a draft no browser ships, and the meter's level pseudo-elements were TermDOM's own. A page styles a control through ::placeholder, ::details-content, ::picker(select) and ::picker-icon, and the rest stays the UA sheet's, as it does in a browser. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
…indow The context type is TermDOM's own, so it carries TermDOM's prefix, as "moz-webgl" and "webkit-3d" carried theirs. Its interface, CanvasCharGridContext, is no longer a global; the package exports it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
css-ui-4 has a <progress> draw in its accent-color, as browsers do, so a page can color the fill without reaching into the control. With auto the fill keeps the UA sheet's blue. A style change reached a shadow tree only through element.shadowRoot, which is null for a closed or UA tree, and never the shadow trees of the element's descendants. A control's insides kept what they had inherited, so setting color on a form's container left its fields' text as it was. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
true, the default, draws pixels the best way the terminal has, which is
cells until a pixel protocol is negotiated. "cells" forces cells. false
draws none: an <img> ends unavailable without loading and shows its alt
text, as HTML's "update the image data" does with images disabled, and
getContext("2d") returns null, so a canvas shows its fallback content.
The chargrid context draws text and stays.
The images guide leads with the three csp recipes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The transport is the connection: input, output, size and close. How TermDOM draws is its own option, asked of the terminal when the program leaves it out, beside graphics and cellSize. The values stay "rgb", "256" and "ansi". A transport, or transportFromProcess's options, that still names a colorDepth is refused with where it went. Tests that check the exact bytes, or render before attaching, name "rgb" as a program that knows its terminal would. MockProcess answers the 24-bit color question as a terminal that has it does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
TermDOM ignores a property it does not know, on its options and on a transport alike, and TypeScript rejects one written in place. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
graphics takes "auto", "cells" or "none", and later the pixel protocols, so the one that means detect is not true beside a list of strings. colorDepth takes "auto" too, so detection can be written out, as cellSize's "auto" already is. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
24, 8 and 3 are what a page reads back from screen.colorDepth, so a program can hand one TermDOM's depth to another. "rgb", "256" and "ansi" name the same three, and any other value is refused. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Each cell it holds carries a glyph and its styling, so it is a grid of cells, as TermDOM calls them everywhere else, not of characters. Its interface is CanvasCellGridContext. COMPATIBILITY.md is regenerated, which also catches up line-height. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The compatibility table says which standards TermDOM implements. The cell grid context is TermDOM's own, documented in the images and canvas guide, so it has no row there. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
A 2d canvas and a cell grid each clip to a scroller and paint the rows scrolled into view, a cell grid narrower than its columns is cut at its content box, and a 2d canvas wider than a box that clips it paints nothing past the box. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
A scroller taller than the rows it moves is shifted under scroll margins and only the rows scrolled in are written. A 2d canvas's pixels and a cell grid's text land on the right rows after the shift. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
A color goes to the nearest of xterm's 16 default colors, so bright forms use SGR 90-97 and 100-107. screen.colorDepth reports 4, the colorDepth option takes 4 in place of 3, and color-index is 16. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The progress examples use accent-color, the website names its color depth on TermDOM, a page cannot construct a DOMRectList, part names split on ASCII whitespace, and the guides cover color-index 16. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Co-Authored-By: Claude Opus 5.5 (1M context) <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.
The follow-up to the images and canvas work in #136, and the changes for 0.2.0: what a program configures, what a page can reach, and how unloaded images, control styling and the cell canvas behave. It also carries a cleanup of code that had grown roundabout. Every commit passes the full suite on Node, Bun and Deno.
Breaking changes
colorDepthis an option ofTermDOM, not of its transport.new TermDOM({colorDepth: 24})replacestransport.colorDepthandtransportFromProcess(proc, {colorDepth}). It takes the bitsscreen.colorDepthreports,24,8or4, or"auto", the default, which asks the terminal."rgb","256"and"ansi"still name the same three. Any other value throws."ansi"draws with all 16 SGR colors and reports 4 bits. A color goes to the nearest of xterm's 16 default colors, so bright colors go out as SGR 90–97 and 100–107.screen.colorDepthis4and(color-index: 16)matches.getContext("termdom-cellgrid"). It is TermDOM's own, so it carries TermDOM's prefix, as"moz-webgl"and"webkit-3d"carried theirs, and each cell carries a glyph and its styling. Its interface,CanvasCellGridContext, is exported from the package and is no longer onwindow. (The old name never shipped in a release.)::part()no longer reaches into the built-in controls, as in every browser. A page styles them through the pseudo-elements browsers ship:::placeholder,::details-content,::picker(select)and::picker-icon. A<progress>draws its fill in itsaccent-color. The rest of a control's insides, such as a meter's level colors and a button's brackets, are the UA sheet's alone. Thedata-level,data-highlighted,data-disabledanddata-groupedattributes on control parts are gone.--math-glyphsand--math-variant-glyphsare gone. They were custom properties the MathML renderer read from the author's own namespace. MathML always draws with the Unicode pieces and terminal italics it used by default.DOMRectListis its own interface, no longer anArray, withlength,item(), indexed properties and the standard iterator. As in browsers, a page cannot construct one.colorAtonCanvasGradientandCanvasPattern, the live collections'compute,namedPropertiesandgetChangedMembers, the CSS rules'prelude,atKeywordand staticatRule, a computed style'sgetComputedValue,innerTextonMathMLElement, andtakeRecordsonResizeObserver.The version is 0.2.0.
New options
graphics: how a document draws pixels for<img>and a canvas's"2d"context."auto", the default, uses the best way the terminal has, which today is always cells."cells"forces half blocks."none"draws no pixels: an<img>loads nothing and shows its alt text, as HTML's "images are disabled" does, andgetContext("2d")returns null, so a canvas shows its fallback content.colorDepth, above.The images guide now opens its loading section with the three
csprecipes: images from the web, a mail client'scid:attachments, and""for no policy.An image that won't load is its alt text
HTML's rendering rules (§15.4.2) keep an image that isn't showing a box only when it has a size and may still load, has no alt text, or is in a quirks-mode document. Otherwise its alt text is ordinary inline text. TermDOM drew every such image as a bordered box three rows tall, which made a mail client's link lists with blocked thumbnails much longer. The alt text lives in the image's UA shadow tree, as Chromium keeps it.
The DOM a page sees matches lib.dom
The drift script builds a TermDOM and walks every interface on its
window, the objects a page reaches without a constructor (navigator,screen, the 2D context, a computed style), and what calls such asgetElementsByClassName()return. It fails on any member lib.dom doesn't declare and on any prototype in a chain that isn't a standard interface. Members a current standard defines that this lib.dom lacks are listed with where they're defined. The members it found are removed, listed above.The engine's own classes are out of every prototype chain a page can see.
LayoutObserveris gone: each observer keeps its own state and calls three functions. The CSS rules' shared bases are init functions.getComputedStyle()returns a plainCSSStylePropertiesthat reads through to the engine's computed declaration.getElementsBy*()return a plainHTMLCollection.LiveList's methods are copied onto each list interface.Fixes
coloron a form's container now recolors its fields' text.Cleanup
These commits change no behavior, except the whitespace fix noted under Duplication.
Per-document values now live on the attached document.
attachDocumenttakes one object of named parts, instead of eight positional arguments plus side registries.getCellPixels. ThecellSizeoption is stored on the document, andgetCellSizereads it there. Gone: theFALLBACK_CELLre-export from cssom, the second "not interactive" choice in index.ts, thesetScreenCellSourceandsetCellSizeSourcecallback registries, and the separatekMeasuresCellflag. TherenderANSIengine is built with its parent's cell, and cells are compared by size, not by object identity.Exchange.colorScheme: the terminal's answer, then the transport's, then light. cssom reads it through the document's exchange. ThesetColorSchemeSourceregistry and index.ts's copy are gone.attachDocument, in place of resources.ts'ssetResourceLoaderregistry.Imports and exports.
isReplacedElementfrom images.ts, where it lives, not through replaced.ts's re-export.getNestedRuleswas used nowhere and is deleted.Duplication.
isControlBytelives in text.ts, not in both framebuffer.ts and exchange.ts.String.prototype.trim, which also strips Unicode spaces.Comments and names.
isTextControlis the only definition. editing.ts's different test (an input, textarea or select, which keeps its keys) is renamedisFormControl.bidi.ts.Looked at and left as is:
HTML_NAMESPACE, which keeps a leaf module from importing dom.ts.isShadowRootfunctions, which agree in practice.domExceptionhelpers, which build exceptions in different realms.🤖 Generated with Claude Code
https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D