Repository navigation
0.2.0 API review: canvas sizing and contexts, gauge styling, colorScheme, disposal, default actions - #145
Merged
Conversation
getContext("termdom-cellgrid") is typed to return a
CanvasCellGridContext, in TermDOM's own canvas and, through a global
declaration, in lib.dom's, so no caller casts. The context's canvas is
an HTMLCanvasElement, and its settings read back as their own values.
columns and rows are gone: canvas.width and canvas.height are the
grid's size, as a 2d context reads its canvas's.
textDecoration takes line-through as well as underline, alone or
together, and getCell reports dim, strikethrough and inverse.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
libuild 0.2.26 builds a src/<name>.worker.ts to dist and leaves it out of exports, which is what a module loaded by file path from new URL(..., import.meta.url) needs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… aliases A page styles a progress or meter's track and fill through ::slider-track and ::slider-fill, and a meter's level through :optimal-value, :low-value and :high-value, as CSS Form Control Styling specifies. WebKit's ::-webkit-progress-* and ::-webkit-meter-* and Firefox's ::-moz-progress-bar, ::-moz-meter-bar and :-moz-meter-* pseudo-classes are aliases, as its section 4.10 asks. The groove takes the track's color, so ::slider-track colors it. accent-color no longer colors a progress bar's fill. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cell grid gains the 2d context's direction: ltr, rtl, or inherit, the default, which takes the canvas element's direction as the text is drawn. Right-to-left text is put in display order, as the layout does for a page's text, and start and end follow it. textAlign defaults to start, as a 2d context's does. The 2d context's inherit was read as ltr and its text was written in logical order; it now resolves and orders the same way. getCell rounds its coordinates, as every drawing method does, so a fractional coordinate reads the cell it was drawn to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- fillStyle and strokeStyle read back serialized as a 2d context's do. currentcolor is the canvas element's color as it is set, and the defaults are CanvasText, the terminal's own text color; Canvas is its background. A translucent fill blends over the cell, and text under half opaque draws nothing, as a 2d context's text does. - font is a CSS font shorthand, parsed, serialized and ignored when invalid, and its weight and style make text bold, dim or italic. The 2d context's font validates the same way. - save(), restore() and isContextLost(). - maxWidth of zero, less or NaN draws nothing. - lineJoin takes bevel, drawn as a miter, and lineStyle takes none. - drawImage normalizes a negative destination size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
images.ts reads a file: URL with node:fs's readFileSync, which the site's sandbox provides, and its broken image is described as the alt text it shows. 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 <noreply@anthropic.com>
A canvas's width and height are unitless, and HTML lays the canvas out at that many CSS pixels. Under the unit cell a CSS pixel is a cell, so there they are cells, for a 2d canvas as for a cell grid, and with cellSize set they are pixels. Asking for a context no longer changes a canvas's size: a 2d canvas under the unit cell was measured against the terminal's cell, as an image is, and a cell grid switched to counting its attributes as cells. Under the unit cell, width and height default to 38 and 9, the cells HTML's 300 by 150 pixels fill at 8 by 16 pixels a cell, so a bare canvas is not 300 columns wide. A cell grid holds the cells its canvas's box holds, reported as cols and rows, so CSS sizes it exactly in any mode. When the box changes size, the cells that still fit keep what was drawn, as a terminal keeps its screen when its window is resized. Images keep measuring their pixels against the terminal's cell under the unit cell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A page's ::part() does not reach inside a built-in control, as in a browser, and the demo does not need the colors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d make canvas contexts only through getContext - colorScheme, what the program knows of the terminal's background, is an option of TermDOM beside colorDepth, not a member of TerminalTransport or an option of transportFromProcess and transportFromSSH. - A TermDOM disposes through Symbol.asyncDispose, so await using waits for the restores as dispose() does. Symbol.dispose is gone: a disposal that writes to a terminal cannot promise it is done when it returns. - new CanvasRenderingContext2D() and new CanvasCellGridContext() throw Illegal constructor, as a browser's contexts do, so no internal type is in their published signatures. A 2d context's pixels a cell before layout follow the canvas's size, not an image's. - A delayed transition's test waits half a second, not 60 ms, so a frame that runs late on Bun no longer ends the delay early. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
attach() no longer takes a transport to swap in before the first attach. The constructor's transport does nothing until attach(), and a transport that is not ready yet resolves ready when it is, so binding late needed nothing more, and the rebind path served only this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It stays as it is for now, undocumented, until document.open(), write() and close() are worked out for the scrollback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It made getContext drift from lib.dom's. Importing the package declares the overload on lib.dom's HTMLCanvasElement, which is where callers' types come from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
window.close() closes with 1 once the page has let an exception escape, and 0 otherwise; Ctrl-C's close is an interrupt, 130. A SIGINT, SIGHUP or SIGTERM sent to the process ends it with 128 plus the signal's number, where it ended with 0. The hint that a blocked load prints is not an error and does not change the status. The API guide said a keydown listener could keep Ctrl-C from closing; Ctrl-C never reaches keydown, and a beforeunload listener is what keeps the session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… group with the arrows A keydown nothing canceled scrolls, as in a browser: the arrows a row, PageUp, PageDown and Space a page, keeping one row of the last in view, and Home and End to an edge. The nearest scroller around the focus that can go that way takes the key, and the document otherwise. Keys a field, a select or editable content takes, and Space on a button, do not scroll. The arrows move to the previous or next radio of a group, checking it and wrapping at the ends. The git-log, tree, hacker-news, markdown and solitaire examples cancel the keys they handle, as they would in a browser. Part of #84. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ctrl+Z hands the terminal back, stops the program, and takes the terminal again when the shell continues it. A document in flow is written into the scrollback first, so the shell's lines land below it and the document starts again under them; a fullscreen one leaves the alternate screen and comes back to it. The document is hidden while suspended, with visibilitychange on each edge, as a backgrounded tab reports. Like Ctrl+C, Ctrl+Z is not a key and cannot be canceled. A transport suspends through an optional suspend(). The process transport leaves raw mode and sends itself SIGTSTP, which raw mode keeps the terminal from sending, and goes on at SIGCONT. Where a transport has no suspend(), over SSH and on Windows, Ctrl+Z is an ordinary key. Part of #84. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
window.setTimeout(), setInterval() and queueMicrotask() hand the runtime a callback that reports an exception the way a listener's is reported, through the window's error event and then the transport's log, and the page goes on, as in a browser. They were the runtime's own, whose exceptions ended the process. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… rejections while installed installGlobals() puts each of the window's names on globalThis, the runtime's included, so the process reads as the window does: a bare setTimeout() reports what it lets escape, and navigator, localStorage and, on Bun and Deno, addEventListener and alert are the window's. Uninstalling puts back each name's own descriptor. The runtime's event classes stay its own: on Bun and Deno its event targets accept only its own events, and the document accepts them as well. While installed, a promise rejected with no handler fires unhandledrejection on the window, as a PromiseRejectionEvent, and one no listener cancels is reported as an uncaught exception, where the runtime would end the process. Deno fires its own event at the window first; a promise the window has heard about is not fired again. The window's timers call the runtime's own, kept from when the module loaded, so they do not call themselves once installed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A browser's setTimeout, queueMicrotask and the rest throw Illegal invocation when called on anything but the global, and the window's wrappers called them on the object that kept them, so the site's sandbox broke for the canvas and CodeMirror examples. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ls() Where the runtime defines them, they stay its own, as its event classes do: Deno's, and Node's given --localstorage-file, keep what is stored between runs, where the window's keep it in memory. On Bun and Node 20, which have none, the window's are installed. Only whether the name is there is asked, since reading Node's when it has no storage file prints an ExperimentalWarning, which installing the globals did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
A pass over the public API before 0.2.0, so that 0.2.0 can mark general stability. Canvases, images and most of what changes here are new in 0.2.0, so only the items under Breaking change anything 0.1.x shipped.
Breaking
colorSchemeis aTermDOMoption, besidecolorDepth:new TermDOM({colorScheme: "dark"}). It is gone fromTerminalTransportand from the options oftransportFromProcessandtransportFromSSH. It holds until the terminal reports its background.TermDOMhasSymbol.asyncDispose, soawait using term = new TermDOM()waits for the restores asawait term.dispose()does.Symbol.disposeis gone: a disposal that writes to a terminal cannot promise it is done when it returns.attach()takes no transport. The transport comes only from the constructor,new TermDOM({transport}). Swapping one in at attach time only duplicated the constructor option: the default transport does nothing untilattach(), and a transport that isn't ready yet resolvesreadywhen it is.keydownnothing canceled, moving the nearest scrollable box around the focus and the document otherwise. A program that handles those keys itself needspreventDefault(); the git-log, tree, hacker-news, markdown and solitaire examples now call it.window.close()exits 1 if the page let an exception escape, 0 otherwise;SIGINT,SIGHUPandSIGTERMexit 130, 129 and 143. All exited 0 before.::part()does not reach inside built-in controls, as in browsers (this landed in 0.2.0: images and canvas follow-up, and a DOM that matches lib.dom #141; the launch post's demo no longer uses it).Canvas sizing
A canvas's
widthandheightare unitless, and HTML lays the canvas out at that many CSS pixels. TermDOM now does exactly that, whichever context the canvas gets, so asking for a context never changes a canvas's size.cellSize: "unit"a CSS pixel is a cell, so<canvas width="40" height="10">is 40×10 cells. WithcellSizeset they are pixels."unit",widthandheightdefault to 38 and 9, HTML's 300×150 pixels over an 8×16 cell, so a bare canvas is not 300 columns wide."unit".The cell grid context
colsandrows. CSSwidth: 40ch; height: 10chsizes it exactly in any mode. When the box changes size, the cells that still fit keep what was drawn.getContext("termdom-cellgrid")as returning aCanvasCellGridContext, so no caller casts.canvasis anHTMLCanvasElement, and settings read back as their own types.fillStyleandstrokeStyleserialize as a 2d context's.currentcoloris the canvas's color when set, andCanvasTextandCanvas, the defaults, are the terminal's own. A translucent fill blends.fontis a parsed CSS font shorthand.textAlign(defaultstart) anddirection(defaultinherit); right-to-left text draws in display order.save(),restore(),isContextLost().getCellincluded.maxWidth,lineJoin: "bevel",lineStyle: "none", and negativedrawImagesizes as a 2d context treats them.textDecorationtakesline-through;getCellreportsdim,strikethroughandinverse.getContextmakes a context.new CanvasCellGridContext()andnew CanvasRenderingContext2D()throwIllegal constructor, as in browsers.The 2d context gained the same fixes where it fell short:
direction: "inherit"takes the canvas's direction, its right-to-left text draws in display order, andfontrejects values the shorthand grammar rejects.Gauges
A page styles
<progress>and<meter>through CSS Form Control Styling (css-forms-1):::slider-trackand::slider-fillmeter:optimal-value,:low-valueand:high-valuefor a meter's levels::-webkit-progress-*and::-webkit-meter-*and Firefox's::-moz-progress-bar,::-moz-meter-barand:-moz-meter-*as aliases, as its §4.10 asksaccent-colorno longer colors a progress bar's fill.Default actions (#84)
fg; a document in flow is written to the scrollback and starts again below the shell's lines, and a fullscreen one leaves and re-enters the alternate screen. The document is hidden meanwhile, withvisibilitychangeon each edge. Transports opt in with an optionalsuspend(); the process transport leaves raw mode and sends itselfSIGTSTPuntilSIGCONT, except on Windows. Over SSH, Ctrl+Z is an ordinary key. Checked by hand in zsh under tmux on Node, Bun and Deno.Packaging
src/image-decode.worker.ts, with libuild 0.2.26, and is no longer inexports. The built package starts it and decodes on Bun, Deno and Node 26 with--experimental-web-worker.ColorDepth(24 | 8 | 4 | "rgb" | "256" | "ansi") is exported.Docs and site
The images guide, the API guide and the styling guide match. The canvas and images examples run on the site.
Tests
New tests cover each change above.
tests/transitions.test.ts. That file fails intermittently on Bun onmaintoo (about 5 in 15 runs). This PR fixes the delayed-transition case, which allowed only 60 ms; the rest are a pre-existing timing flake left for later.document.close()keeps its scrollback behavior for now but is no longer documented, untildocument.open(),write()andclose()are worked out for the scrollback.window.screenTopstays the row the document's region starts at, the terminal's version of how far down the screen the window is.🤖 Generated with Claude Code