Commits
Completes the queryTermInfo probe window: response bytes are fed
through a probe-private input parser over the handle's shared memory,
so replies fold into the capability struct as they arrive and the DA1
device-attributes report — which every terminal answers — resolves the
probe immediately instead of waiting out the timeout. The parser is
abandoned after the window; the consumer's createInput({ terminfo })
parser takes over for the session.
Adds terminfo-spec 10.3 attachment tests (at most one Input and one
Term per handle) and exports the public capability surface from mod.ts
(queryTermInfo, TermInfo, Capabilities, Rgb, probe stream types,
MAX_TERMINFO), deliberately omitting the internals() attachment
helper.
Gates the renderer's output on the shared capability struct:
- Color encoding ladder: emit_attr routes colors through emit_color,
which picks truecolor (38;2), 256-color (38;5, nearest 6x6x6 cube or
24-step grayscale ramp, tmux/xterm colour_find_rgb approach), or
16-color (nearest of the xterm default ANSI palette, 30-37/90-97)
from the frame's capabilities. With no evidence the baseline emits
256-color SGR — this deliberately supersedes the historical
unconditional truecolor output per the progressive-enhancement
invariant (TINV-5); truecolor now requires terminfo, COLORTERM, or a
probe reply.
- Generation invalidation: reduce() compares the struct generation
against the last emitted frame and invalidates the front buffer on
change, so a mid-session capability upgrade (e.g. an XTGETTCAP reply
confirming truecolor) produces a complete redraw with no stale cells.
- Synchronized output: when mode 2026 is probe-confirmed, non-empty
cursor-update frames are wrapped in CSI ?2026h/l within the same
output buffer. Empty frames drop the wrap entirely and line-mode
output is never wrapped. The output buffer gets explicit headroom so
a saturated frame cannot truncate the closing wrap.
- init() takes a TermInfo pointer (NULL = private baseline struct);
createTerm({ terminfo }) attaches to a handle's shared memory, with
at most one Term per handle (terminfo-spec 10.3).
Attached consumers now reuse the handle's WASM instance instead of
re-instantiating the module over the shared memory: a fresh
instantiation rewrites the module's data segments, which clobbered
Clay's already-initialized static context (caught by the
generation-redraw test as an indirect call through zeroed state).
Existing tests that asserted truecolor SGR against handle-less terms
now attach COLORTERM evidence via test/caps.ts helpers, and the
baseline expectation is covered by new 256-color tests.
Implements input-spec section 6, the input parser's two roles in the
capability layer:
Key sequences (6.1): input_init gains terminfo bytes and a TermInfo
pointer. key_* string capabilities (arrows, kf1-kf12, khome/kend/
kich1/kdch1/kpp/knp, kcbt — indices verified empirically against tic
output, including the alphabetical kf10=67 quirk) seed the sequence
trie before the xterm defaults; trie_add is first-writer-wins, so
terminfo sequences take precedence while defaults stay registered for
anything the entry omits.
Query responses (6.2): a new parse_response path in the scan loop
recognizes and silently consumes capability replies, writing them into
the shared struct with one generation bump per logical update:
- OSC 10/11/12 theme color reports (rgb:/# forms, 1-4 hex digits per
channel, BEL or ST terminated)
- OSC 21 kitty color reports (key=value; fills theme, sets kittyColor)
- OSC 22 kitty pointer shape reports
- XTGETTCAP DCS replies (1+r naming RGB/Tc confirms truecolor; 0+r
denies it — probe evidence outranks the terminfo entry)
- DECRPM reports (mode 2026 grants/denies syncOutput; other modes are
consumed silently)
- kitty keyboard flag reports (CSI ? u)
- kitty graphics APC replies (;OK grants, error payloads deny)
- DA1 device attributes reports, which also set the TERMINFO_DA1
fence marker in confirmed for the queryTermInfo probe window
Responses are recognized only behind tight prefixes (OSC number in
{10,11,12,21,22}, DCS [01]+r, APC G, CSI ?) so Alt+]/Alt+P/Alt+_
keystrokes keep their existing behavior, and unterminated responses
are abandoned after 1KB. Responses interleave with user input and
buffer across scan calls without leaking bytes into adjacent events.
A parser with no handle consumes responses into a private struct so
stray replies never corrupt the event stream.
createInput({ terminfo }) now takes the TermInfo handle (the raw
Uint8Array form moved to queryTermInfo({ terminfo: bytes })); the
parser instantiates against the handle's shared memory and allocates
its arena from the handle's bump allocator. A handle can be attached
to at most one Input (terminfo-spec 10.3).
Implements the terminfo-spec capability core:
- src/terminfo.{c,h}: the TermInfo capability struct (generation,
colors, flags, confirmed, theme group), the xterm-256color baseline
init, and terminfo_parse for both storage formats (legacy 0432 and
extended-number 01036) including the extended capability table
(RGB/Tc/Su/Smulx). Parsing is bounds-checked everywhere and
all-or-nothing: malformed input returns a nonzero code without
touching the struct (TINV-3). A successful parse replaces the
standard capabilities the entry owns — absent booleans clear, colors
becomes the entry's max_colors — matching ncurses semantics where an
entry fully describes its terminal. terminfo_grant applies
creation-time evidence (COLORTERM) and bumps the generation only on
actual change.
- terminfo.ts: queryTermInfo() as the single blessed entry point.
Locates entries via the ncurses search path (TERMINFO, ~/.terminfo,
TERMINFO_DIRS, compiled-in defaults; letter and hex directory
layouts; traversal-safe, magic-validated — ported from the
bombshell-dev/ui feat/terminfo spike), parses into a fresh shared
WebAssembly.Memory with a bump allocator (the renderer and input
parser will attach to the same memory in follow-ups), applies
COLORTERM evidence, and runs the sans-IO probe batch (OSC 10/11/12,
kitty OSC 21/22, XTGETTCAP RGB;Tc, DECRQM 2026, kitty keyboard and
graphics, DA1 fence) over injectable streams. It never rejects on
environmental grounds: missing entries, non-TTY streams, timeouts,
and aborts all resolve with whatever evidence was gathered. Raw mode
is saved and restored around the probe window.
- test/terminfo.test.ts + embedded fixtures (tasks/gen-fixtures.ts):
real xterm-256color, tic-compiled extended-caps entry, hand-built
01036 entry (macOS ships ncurses 6.0, which predates that format),
16-color downgrade entry. The extended-block layout (name offsets
relative to the names sub-table) was verified against tic output
byte-by-byte.
The probe window currently closes on timeout only; DA1 fence
recognition lands with the input parser integration.
Adds specs/terminfo-spec.md defining the shared capability layer:
- One shared WebAssembly.Memory with three tenants (terminfo region,
renderer heap, input heap); both instances receive explicit region
pointers plus a capability struct pointer. Split-module builds must
link with disjoint static footprints (TINV-7).
- A fixed-layout TermInfo struct: generation counter, max_colors,
capability flag bits, probe-confirmation bits, and a theme group
(OSC 10/11/12 foreground/background/cursor).
- Progressive enhancement (TINV-5): a conservative xterm-256color
baseline owned by the terminfo module, raised only by positive
evidence with precedence baseline < terminfo entry < environment
(COLORTERM) < probe response. This deliberately supersedes the
renderer's historical unconditional truecolor emission.
- Sans-IO probe (TINV-6): a query batch (OSC 10/11/12, kitty OSC 21
color, kitty OSC 22 pointer shape, XTGETTCAP RGB;Tc, DECRQM 2026,
kitty keyboard, kitty graphics) fenced by DA1, with responses
recognized by the input parser during normal scans. queryTermInfo()
is the single blessed entry point; every environmental dependency
(env, terminfo bytes, streams, timeout) is injectable.
Updates input-spec.md: the terminfo option becomes the TermInfo handle;
new normative section 6 covers key_* trie loading and query-response
recognition (responses are consumed silently and never leak into
events); terminfo parsing leaves the deferred list.
Updates renderer-spec.md: new section 7.6 (capability-gated emission)
specifies the color-encoding ladder (truecolor / 256 / 16), bce-gated
erase, the mode-2026 synchronized-output frame wrap, and
generation-triggered full redraw; INV-7 and section 11.2 are amended so
the shared capability layer and the frame-scoped sync wrap cannot be
read as violating renderer/input independence or the no-terminal-state
boundary.
References: OSC 8 spec (egmontkob gist), kitty color-stack and
pointer-shapes protocols, Ghostty synchronized-output guidance.
Bumps the github-actions group with 2 updates: [actions/cache](https://github.com/actions/cache) and [CodSpeedHQ/action](https://github.com/codspeedhq/action).
Updates `actions/cache` from 6.0.0 to 6.1.0
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/2c8a9bd7457de244a408f35966fab2fb45fda9c8...55cc8345863c7cc4c66a329aec7e433d2d1c52a9)
Updates `CodSpeedHQ/action` from 4.17.6 to 4.18.1
- [Release notes](https://github.com/codspeedhq/action/releases)
- [Changelog](https://github.com/CodSpeedHQ/action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codspeedhq/action/compare/63f3e98b61959fe67f146a3ff022e4136fe9bb9c...a4a36bb07c0638b0b4ca52bf1f3dad1b4289e52f)
---
updated-dependencies:
- dependency-name: actions/cache
dependency-version: 6.1.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: github-actions
- dependency-name: CodSpeedHQ/action
dependency-version: 4.18.1
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: github-actions
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(docs): BUILD mentions lld for macOS
* Update BUILD.md
🔧 ci: pin Deno to v2.8.3 to work around v2.9.0 JSR warm-cache regression
v2.9.0 fails JSR integrity checks on warm cache for files without a
manifest checksum (denoland/deno#35529). Size Report builds twice on
the same runner, so the base build hits the cache and errors on
`@ts-morph/common@0.27.0`'s dynamic `require("./crypto")`.
Restore `v2.x` once v2.9.1 ships.
* 📝 add transitions design specification
Design spec for clayterm transitions: frame-snapshot-compatible interpolation
of element position, size, and color properties. Defines the deltaTime
convention, the animating signal on RenderResult, declarative enter/exit
semantics that replace Clay's function-pointer callbacks, and cancellation
as a structural consequence of re-describing state. Implementation is
gated on bumping the Clay submodule past the upstream transition commit.
* ⬆️ bump Clay submodule to latest main (transitions API)
* 🔧 adapt clayterm to new Clay signatures (OpenTextElement, EndLayout)
* ⬆️ pin Clay to 938967a (work around upstream CLAY_WASM_EXPORT typo)
* ✨ add deltaTime parameter to reduce()
* 🔧 add deltaTime to Native.reduce signature
* ✨ track deltaTime on Term, accept deltaTime override
* ✨ add animating_count to Clayterm context
* 🔧 expose animating() via Native binding
* ✨ surface animating: boolean on RenderResult
* 📝 rewrite transitions spec for v1 (Clay-supported subset)
Scope v1 to what Clay currently supports without userData on transition
callbacks: one duration and one easing per element, applied to all listed
properties. Drop per-property longhand, enter/exit deltas, cubicBezier,
and corner radius — each with an explicit "Deferred Until Upstream Clay"
entry in §13 referencing nicbarker/clay#603 and the forthcoming exit-flag
work. Easings are plain string literals ("linear" | "easeIn" | "easeOut"
| "easeInOut") since v1 has no parametric easings.
* ✨ add transition property names, bitmask helpers, and Easing
* ✨ add transition field type to OpenElement
* ✨ encode transition block in pack()
* ✨ register Clay handlers, interpolate on property change
Co-Authored-By:
* ✨ reset deltaTime to 0 after idle (preserve transitions across long gaps)
* ✅ verify color transitions work in line mode
* 🎨 apply deno fmt and clang-format
* ✨ add transitions demo (collapsing sidebar)
* 📝 reference transitions-spec from renderer-spec
* ✨ rewrite transitions demo as interactive full-screen menu
* ✨ add clay-transitions demo port (v1-compatible subset)
Ports the spirit of the raylib-transitions demo to clayterm: a 4×4 grid
of colored boxes that animate position, size, and bg color. Shuffle (s)
animates positions via Clay's transition system; recolor (c) toggles
between two palettes with animated bg interpolation; hover tints each
box by blending its bg toward white (overlay-color field is not yet in
the v1 command buffer, so lighten-on-hover substitutes). Full mouse
tracking is wired via mouseTracking() + pointer state from input events.
* 🎨 let clay-transitions demo rows fill available height
* 🎨 remove modeline from clay-transitions demo
* 📝 note ct_active_context is a workaround for Clay userData PR
* 🎨 use border-only boxes in clay-transitions demo
* 🎨 prevent menu text from wrapping during sidebar transition
* 🔥 drop unused grow import in transitions-run test
* Update transitions branch with upstream changes (#53)
* ✨ add snapshot() for pre-packing directive subtrees
Introduces a new `snapshot(ops)` constructor that pre-packs a directive
array into its transfer encoding. The returned opaque `Op` can be
spliced into any directive array, and during packing its bytes are
copied directly into the command buffer without re-encoding.
This enables higher-level frameworks to implement dirty tracking:
unchanged component subtrees can reuse a cached snapshot, skipping
the per-frame packing cost entirely.
* docs: add maintainer build guide
* 💄 format build docs
* 🐛 improve pack string overflow errors
* os matrix test in ci (#36)
* 🧼 optimize build
* 🧼 compress bundled wasm
* 🧼 optimize wcwidth.c size
* ⚙️ update npm settings
* 🐛 install wasm-opt in ci
* ⚡ use brotli-11 + z85 wasm encoding
* 📌 pin @types/node to v22
* 🧼 apply @ghostdevv review suggestions from PR #35
Co-Authored-By: ghostdevv <git@willow.sh>
* 🔨 add type to bundle-wasm
* 💌 signed, sealed, delivered
* chore: use hashes for versions
* chore: don't save git credentials
* chore: use array syntax
for some reason the schema for the actions wants it to be an array
* perf: set concurrency limits to reduce cost and improve dx
Without this it means that, for example, if I push a change to a PR then
shortly push again this workflow will be running twice. This change will
cancel the old run before starting the new one, which reduces the
overall actions cost and DX as you don't have extra runs
* chore: use hashes for versions
* chore: update node version
* perf: set concurrency limits to reduce cost and improve dx
Without this it means that, for example, if I push a change to a PR then
shortly push again this workflow will be running twice. This change will
cancel the old run before starting the new one, which reduces the
overall actions cost and DX as you don't have extra runs
* chore: don't save git credentials
* chore: mitigate potential template injection
See https://docs.zizmor.sh/audits/#template-injection
* chore: update ::set-output command to new syntax
https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
* chore: use hashes for versions
* chore: don't save git credentials
* chore: limit id-token permission to the publishing steps
* chore: explicitly disable npm cache to mitigate cache poisoning attacks
* chore: mitigate potential template injection
See https://docs.zizmor.sh/audits/#template-injection
* chore: use oidc
This should be using OIDC for publishing
* chore: update ::set-output command to new syntax
https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
* 🙅 revert aggressive optimization experiments pending benchmark
Reverts three changes that need benchmarks before landing:
- -Oz / wasm-opt
- brotli+Z85 wasm compression
- wcwidth.c rewrite
* Add CodSpeed performance benchmarks
* 🔨 use deno
* 🧼 deno fmt
* ⚙️ vitest -> tinybench
* 🧼 remove codspeed assets
* 🔨 fix ci
* add type module
* fmt
* chore: update github url (#38)
* downgrade to tinybench@5
* move to examples folder with readme
* 🔧 export animating from wasm build
* 🧪 cover transitions in snapshots and validation
* ✅ enforce nonnegative transition duration
---------
Co-authored-by: Charles Lowell <cowboyd@frontside.com>
Co-authored-by: Jacob Bolda <me@jacobbolda.com>
Co-authored-by: Nate Moore <nate@natemoo.re>
Co-authored-by: ghostdevv <git@willow.sh>
Co-authored-by: Nate Moore <git@natemoo.re>
Co-authored-by: codspeed-hq[bot] <117304815+codspeed-hq[bot]@users.noreply.github.com>
Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
* remove uncecessary note
* clean up and consolidate
* re-organize transitions examples
* 🔥remove redundant cast
---------
Co-authored-by: Ryan Rauh <rauhryan@users.noreply.github.com>
Co-authored-by: Jacob Bolda <me@jacobbolda.com>
Co-authored-by: Nate Moore <nate@natemoo.re>
Co-authored-by: ghostdevv <git@willow.sh>
Co-authored-by: Nate Moore <git@natemoo.re>
Co-authored-by: codspeed-hq[bot] <117304815+codspeed-hq[bot]@users.noreply.github.com>
Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
Co-authored-by: Ryan Rauh <rauh.ryan@gmail.com>
✨ per-side border support
Bumps the github-actions group with 4 updates in the / directory: [actions/checkout](https://github.com/actions/checkout), [actions/cache](https://github.com/actions/cache), [CodSpeedHQ/action](https://github.com/codspeedhq/action) and [actions/github-script](https://github.com/actions/github-script).
Updates `actions/checkout` from 6.0.2 to 7.0.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6.0.2...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)
Updates `actions/cache` from 5.0.5 to 6.0.0
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/27d5ce7f107fe9357f9df03efb73ab90386fccae...2c8a9bd7457de244a408f35966fab2fb45fda9c8)
Updates `CodSpeedHQ/action` from 4.17.0 to 4.17.6
- [Release notes](https://github.com/codspeedhq/action/releases)
- [Changelog](https://github.com/CodSpeedHQ/action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codspeedhq/action/compare/9d332c4d90b43981c3e55ae8e38e68709996240f...63f3e98b61959fe67f146a3ff022e4136fe9bb9c)
Updates `actions/github-script` from 7.1.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/f28e40c7f34bde8b3046d885e986cb6290c5673b...3a2844b7e9c422d3c10d287c895573f7108da1b3)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-version: 7.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions
- dependency-name: actions/cache
dependency-version: 6.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions
- dependency-name: CodSpeedHQ/action
dependency-version: 4.17.6
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: github-actions
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* test: failing repro for nested-clip
* fix: stack clip regions so nested clips restore the parent rect
Clip state was a single rect, so closing an inner clip turned
clipping off entirely and later siblings leaked past the outer
bounds. Replace it with a fixed-depth stack: SCISSOR_START pushes
intersect(parent, child) and SCISSOR_END pops, restoring the parent
rect while the stack is non-empty. The active top mirrors into the
existing clipx/clipy/clipw/cliph scalars, so setcell is unchanged.
Fixes #77
* test: move nested-clip regression into test/clip.test.ts
* 🌆 Use visual language for clipping tests
* 📝 Update render spec to include clipping behavior
* fix: honor clip depth limit
---------
Co-authored-by: Charles Lowell <cowboyd@frontside.com>
♻️ rename clayterm -> @bomb.sh/tty
* test: add input event-loop integration test
Drives createInput as a consumer would (read chunk, scan, flush a pending ESC, dispatch) and asserts the same event stream whether input is fed whole, byte-by-byte, or split mid-sequence. Covers the lone-ESC flush path.
* bench: add macro throughput WallTime bench
In-process bench over a large mixed corpus, measured in WallTime where real work dominates placement/JIT/alloc noise (the Simulation micro-benches sit on codegen cliffs). Feeds the corpus in small reads: a single scan() drains at most 256 events (the wasm event-buffer cap), so small reads keep every call under the cap and process the whole corpus.
* bench: remove input micro-suite (codegen-cliff artifacts)
The Simulation input micro-benches (long input burst, printable ASCII single char) move by 50-90% on unrelated changes, even a test-only rename, because their simulated cost snaps to a different value when the combined wasm shifts. Input perf is now gated by the throughput WallTime bench; correctness by the event-loop integration test.
* ci(bench): pin build runner and drop wasm cache
Pin ubuntu-24.04 so the wasm toolchain is stable and drop the wasm cache so main and PRs always rebuild identically. The cache froze main's baseline on a stale build, so every PR compared a fresh build against a stale baseline and produced phantom regressions.
* fix(ci): return a promise from throughput bench for CodSpeed walltime
CodSpeed's walltime tinybench plugin only populates result.latency on its async path; a sync task fn leaves it undefined and crashes (Cannot destructure 'min' of result.latency). startup.bench works because its tasks return a promise (spawnFixture). Return Promise.resolve() from the throughput task so the plugin takes the async path — a bare async fn with no await would trip deno's require-await lint. The walltime job runs startup and throughput as separate node processes.
* bench: move render/ops to WallTime macro benches, drop the simulation job
CodSpeed Simulation (Valgrind) is unviable for CI here: flaky measurements (dashboard layout swung 20x, diff render 17% on changes that touch no render code) and unpredictable runtime — the same commit's simulation job finished in ~2 min one run and hung past 30 min the next. Convert render/ops to ms-scale WallTime macro benches (looped, promise-returning, ~7-11ms at <1% variance) run as separate node processes in the walltime job, and drop the simulation job entirely. mod.ts is now a local aggregator for deno task bench.
* ref(test): use built-in
* ref(bench): centralize withCodSpeed workaround
* ref(test): use semantic util functions
fix(ci): rebuild from clean in size-report so build-config changes are measured
As we add features to the wire format, this makes is simplier to adjust
the memory allocation of the wasm
For example, the PR can add 24 new bytes of memory to an element
operation that defines border properties
probably not a big deal, but its nice to keep it in mind
expands per-side border attributes to allow for fg, width, bg properties
for each side of the border
* ✨ Add border-cell backgrounds
* format the spec file
* fix merge conflicts
* Adds code comment and validation for bg
* Updated the tests as suggests by paul
* feat: expand floating parameters
* ♻️ use string literals for floating options
* Remove any casts from validation tests
refactor: alignment to use string literals instead of magic numbers
* ✨ Add glyph-cell text backgrounds
Packs a bg color into the pack string
after decoding the bg color, its pass through to the render text method
properly
* chore: update the render-spec
* Add a very usage of text bg in the examples
* Adds a demo that looks like a code diff to show all the different colors features in action
* adds test to verify the bg color is reset have the directive
* move our tests into color.test.ts
rename test for generic color
The size report builds head then base without cleaning between them. For PRs that change only build config (Makefile, patches/, flags) and not src/*.c, make sees clayterm.wasm as newer than the unchanged sources and skips the base rebuild, so base re-measures head's artifact, delta is 0, and no size comment is posted. Run make clean && make for both builds so each is from scratch.
fix: leave terminal default foreground for uncolored text
Just testing text attributes, shouldn't need an enclosing box.
Text with no explicit color was packed as concrete white
(0xFFFFFFFF), forcing white on light-background terminals. Encode
absent color as ATTR_DEFAULT (0x80 in the attrs byte) so the C path
skips the foreground SGR entirely, mirroring how an unset background
is already left alone.
Fixes #62
🏃 add input + combined startup benchmarks
build(deps): bump actions/checkout from 6.0.2 to 6.0.3 in the github-actions group
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).
Updates `actions/checkout` from 6.0.2 to 6.0.3
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-version: 6.0.3
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: github-actions
...
Signed-off-by: dependabot[bot] <support@github.com>
🧼 compress bundled wasm with brotli + z85 encoding
bench: add WASM init and startup timing benchmarks
Completes the queryTermInfo probe window: response bytes are fed
through a probe-private input parser over the handle's shared memory,
so replies fold into the capability struct as they arrive and the DA1
device-attributes report — which every terminal answers — resolves the
probe immediately instead of waiting out the timeout. The parser is
abandoned after the window; the consumer's createInput({ terminfo })
parser takes over for the session.
Adds terminfo-spec 10.3 attachment tests (at most one Input and one
Term per handle) and exports the public capability surface from mod.ts
(queryTermInfo, TermInfo, Capabilities, Rgb, probe stream types,
MAX_TERMINFO), deliberately omitting the internals() attachment
helper.
Gates the renderer's output on the shared capability struct:
- Color encoding ladder: emit_attr routes colors through emit_color,
which picks truecolor (38;2), 256-color (38;5, nearest 6x6x6 cube or
24-step grayscale ramp, tmux/xterm colour_find_rgb approach), or
16-color (nearest of the xterm default ANSI palette, 30-37/90-97)
from the frame's capabilities. With no evidence the baseline emits
256-color SGR — this deliberately supersedes the historical
unconditional truecolor output per the progressive-enhancement
invariant (TINV-5); truecolor now requires terminfo, COLORTERM, or a
probe reply.
- Generation invalidation: reduce() compares the struct generation
against the last emitted frame and invalidates the front buffer on
change, so a mid-session capability upgrade (e.g. an XTGETTCAP reply
confirming truecolor) produces a complete redraw with no stale cells.
- Synchronized output: when mode 2026 is probe-confirmed, non-empty
cursor-update frames are wrapped in CSI ?2026h/l within the same
output buffer. Empty frames drop the wrap entirely and line-mode
output is never wrapped. The output buffer gets explicit headroom so
a saturated frame cannot truncate the closing wrap.
- init() takes a TermInfo pointer (NULL = private baseline struct);
createTerm({ terminfo }) attaches to a handle's shared memory, with
at most one Term per handle (terminfo-spec 10.3).
Attached consumers now reuse the handle's WASM instance instead of
re-instantiating the module over the shared memory: a fresh
instantiation rewrites the module's data segments, which clobbered
Clay's already-initialized static context (caught by the
generation-redraw test as an indirect call through zeroed state).
Existing tests that asserted truecolor SGR against handle-less terms
now attach COLORTERM evidence via test/caps.ts helpers, and the
baseline expectation is covered by new 256-color tests.
Implements input-spec section 6, the input parser's two roles in the
capability layer:
Key sequences (6.1): input_init gains terminfo bytes and a TermInfo
pointer. key_* string capabilities (arrows, kf1-kf12, khome/kend/
kich1/kdch1/kpp/knp, kcbt — indices verified empirically against tic
output, including the alphabetical kf10=67 quirk) seed the sequence
trie before the xterm defaults; trie_add is first-writer-wins, so
terminfo sequences take precedence while defaults stay registered for
anything the entry omits.
Query responses (6.2): a new parse_response path in the scan loop
recognizes and silently consumes capability replies, writing them into
the shared struct with one generation bump per logical update:
- OSC 10/11/12 theme color reports (rgb:/# forms, 1-4 hex digits per
channel, BEL or ST terminated)
- OSC 21 kitty color reports (key=value; fills theme, sets kittyColor)
- OSC 22 kitty pointer shape reports
- XTGETTCAP DCS replies (1+r naming RGB/Tc confirms truecolor; 0+r
denies it — probe evidence outranks the terminfo entry)
- DECRPM reports (mode 2026 grants/denies syncOutput; other modes are
consumed silently)
- kitty keyboard flag reports (CSI ? u)
- kitty graphics APC replies (;OK grants, error payloads deny)
- DA1 device attributes reports, which also set the TERMINFO_DA1
fence marker in confirmed for the queryTermInfo probe window
Responses are recognized only behind tight prefixes (OSC number in
{10,11,12,21,22}, DCS [01]+r, APC G, CSI ?) so Alt+]/Alt+P/Alt+_
keystrokes keep their existing behavior, and unterminated responses
are abandoned after 1KB. Responses interleave with user input and
buffer across scan calls without leaking bytes into adjacent events.
A parser with no handle consumes responses into a private struct so
stray replies never corrupt the event stream.
createInput({ terminfo }) now takes the TermInfo handle (the raw
Uint8Array form moved to queryTermInfo({ terminfo: bytes })); the
parser instantiates against the handle's shared memory and allocates
its arena from the handle's bump allocator. A handle can be attached
to at most one Input (terminfo-spec 10.3).
Implements the terminfo-spec capability core:
- src/terminfo.{c,h}: the TermInfo capability struct (generation,
colors, flags, confirmed, theme group), the xterm-256color baseline
init, and terminfo_parse for both storage formats (legacy 0432 and
extended-number 01036) including the extended capability table
(RGB/Tc/Su/Smulx). Parsing is bounds-checked everywhere and
all-or-nothing: malformed input returns a nonzero code without
touching the struct (TINV-3). A successful parse replaces the
standard capabilities the entry owns — absent booleans clear, colors
becomes the entry's max_colors — matching ncurses semantics where an
entry fully describes its terminal. terminfo_grant applies
creation-time evidence (COLORTERM) and bumps the generation only on
actual change.
- terminfo.ts: queryTermInfo() as the single blessed entry point.
Locates entries via the ncurses search path (TERMINFO, ~/.terminfo,
TERMINFO_DIRS, compiled-in defaults; letter and hex directory
layouts; traversal-safe, magic-validated — ported from the
bombshell-dev/ui feat/terminfo spike), parses into a fresh shared
WebAssembly.Memory with a bump allocator (the renderer and input
parser will attach to the same memory in follow-ups), applies
COLORTERM evidence, and runs the sans-IO probe batch (OSC 10/11/12,
kitty OSC 21/22, XTGETTCAP RGB;Tc, DECRQM 2026, kitty keyboard and
graphics, DA1 fence) over injectable streams. It never rejects on
environmental grounds: missing entries, non-TTY streams, timeouts,
and aborts all resolve with whatever evidence was gathered. Raw mode
is saved and restored around the probe window.
- test/terminfo.test.ts + embedded fixtures (tasks/gen-fixtures.ts):
real xterm-256color, tic-compiled extended-caps entry, hand-built
01036 entry (macOS ships ncurses 6.0, which predates that format),
16-color downgrade entry. The extended-block layout (name offsets
relative to the names sub-table) was verified against tic output
byte-by-byte.
The probe window currently closes on timeout only; DA1 fence
recognition lands with the input parser integration.
Adds specs/terminfo-spec.md defining the shared capability layer:
- One shared WebAssembly.Memory with three tenants (terminfo region,
renderer heap, input heap); both instances receive explicit region
pointers plus a capability struct pointer. Split-module builds must
link with disjoint static footprints (TINV-7).
- A fixed-layout TermInfo struct: generation counter, max_colors,
capability flag bits, probe-confirmation bits, and a theme group
(OSC 10/11/12 foreground/background/cursor).
- Progressive enhancement (TINV-5): a conservative xterm-256color
baseline owned by the terminfo module, raised only by positive
evidence with precedence baseline < terminfo entry < environment
(COLORTERM) < probe response. This deliberately supersedes the
renderer's historical unconditional truecolor emission.
- Sans-IO probe (TINV-6): a query batch (OSC 10/11/12, kitty OSC 21
color, kitty OSC 22 pointer shape, XTGETTCAP RGB;Tc, DECRQM 2026,
kitty keyboard, kitty graphics) fenced by DA1, with responses
recognized by the input parser during normal scans. queryTermInfo()
is the single blessed entry point; every environmental dependency
(env, terminfo bytes, streams, timeout) is injectable.
Updates input-spec.md: the terminfo option becomes the TermInfo handle;
new normative section 6 covers key_* trie loading and query-response
recognition (responses are consumed silently and never leak into
events); terminfo parsing leaves the deferred list.
Updates renderer-spec.md: new section 7.6 (capability-gated emission)
specifies the color-encoding ladder (truecolor / 256 / 16), bce-gated
erase, the mode-2026 synchronized-output frame wrap, and
generation-triggered full redraw; INV-7 and section 11.2 are amended so
the shared capability layer and the frame-scoped sync wrap cannot be
read as violating renderer/input independence or the no-terminal-state
boundary.
References: OSC 8 spec (egmontkob gist), kitty color-stack and
pointer-shapes protocols, Ghostty synchronized-output guidance.
Bumps the github-actions group with 2 updates: [actions/cache](https://github.com/actions/cache) and [CodSpeedHQ/action](https://github.com/codspeedhq/action).
Updates `actions/cache` from 6.0.0 to 6.1.0
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/2c8a9bd7457de244a408f35966fab2fb45fda9c8...55cc8345863c7cc4c66a329aec7e433d2d1c52a9)
Updates `CodSpeedHQ/action` from 4.17.6 to 4.18.1
- [Release notes](https://github.com/codspeedhq/action/releases)
- [Changelog](https://github.com/CodSpeedHQ/action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codspeedhq/action/compare/63f3e98b61959fe67f146a3ff022e4136fe9bb9c...a4a36bb07c0638b0b4ca52bf1f3dad1b4289e52f)
---
updated-dependencies:
- dependency-name: actions/cache
dependency-version: 6.1.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: github-actions
- dependency-name: CodSpeedHQ/action
dependency-version: 4.18.1
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: github-actions
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* 📝 add transitions design specification
Design spec for clayterm transitions: frame-snapshot-compatible interpolation
of element position, size, and color properties. Defines the deltaTime
convention, the animating signal on RenderResult, declarative enter/exit
semantics that replace Clay's function-pointer callbacks, and cancellation
as a structural consequence of re-describing state. Implementation is
gated on bumping the Clay submodule past the upstream transition commit.
* ⬆️ bump Clay submodule to latest main (transitions API)
* 🔧 adapt clayterm to new Clay signatures (OpenTextElement, EndLayout)
* ⬆️ pin Clay to 938967a (work around upstream CLAY_WASM_EXPORT typo)
* ✨ add deltaTime parameter to reduce()
* 🔧 add deltaTime to Native.reduce signature
* ✨ track deltaTime on Term, accept deltaTime override
* ✨ add animating_count to Clayterm context
* 🔧 expose animating() via Native binding
* ✨ surface animating: boolean on RenderResult
* 📝 rewrite transitions spec for v1 (Clay-supported subset)
Scope v1 to what Clay currently supports without userData on transition
callbacks: one duration and one easing per element, applied to all listed
properties. Drop per-property longhand, enter/exit deltas, cubicBezier,
and corner radius — each with an explicit "Deferred Until Upstream Clay"
entry in §13 referencing nicbarker/clay#603 and the forthcoming exit-flag
work. Easings are plain string literals ("linear" | "easeIn" | "easeOut"
| "easeInOut") since v1 has no parametric easings.
* ✨ add transition property names, bitmask helpers, and Easing
* ✨ add transition field type to OpenElement
* ✨ encode transition block in pack()
* ✨ register Clay handlers, interpolate on property change
Co-Authored-By:
* ✨ reset deltaTime to 0 after idle (preserve transitions across long gaps)
* ✅ verify color transitions work in line mode
* 🎨 apply deno fmt and clang-format
* ✨ add transitions demo (collapsing sidebar)
* 📝 reference transitions-spec from renderer-spec
* ✨ rewrite transitions demo as interactive full-screen menu
* ✨ add clay-transitions demo port (v1-compatible subset)
Ports the spirit of the raylib-transitions demo to clayterm: a 4×4 grid
of colored boxes that animate position, size, and bg color. Shuffle (s)
animates positions via Clay's transition system; recolor (c) toggles
between two palettes with animated bg interpolation; hover tints each
box by blending its bg toward white (overlay-color field is not yet in
the v1 command buffer, so lighten-on-hover substitutes). Full mouse
tracking is wired via mouseTracking() + pointer state from input events.
* 🎨 let clay-transitions demo rows fill available height
* 🎨 remove modeline from clay-transitions demo
* 📝 note ct_active_context is a workaround for Clay userData PR
* 🎨 use border-only boxes in clay-transitions demo
* 🎨 prevent menu text from wrapping during sidebar transition
* 🔥 drop unused grow import in transitions-run test
* Update transitions branch with upstream changes (#53)
* ✨ add snapshot() for pre-packing directive subtrees
Introduces a new `snapshot(ops)` constructor that pre-packs a directive
array into its transfer encoding. The returned opaque `Op` can be
spliced into any directive array, and during packing its bytes are
copied directly into the command buffer without re-encoding.
This enables higher-level frameworks to implement dirty tracking:
unchanged component subtrees can reuse a cached snapshot, skipping
the per-frame packing cost entirely.
* docs: add maintainer build guide
* 💄 format build docs
* 🐛 improve pack string overflow errors
* os matrix test in ci (#36)
* 🧼 optimize build
* 🧼 compress bundled wasm
* 🧼 optimize wcwidth.c size
* ⚙️ update npm settings
* 🐛 install wasm-opt in ci
* ⚡ use brotli-11 + z85 wasm encoding
* 📌 pin @types/node to v22
* 🧼 apply @ghostdevv review suggestions from PR #35
Co-Authored-By: ghostdevv <git@willow.sh>
* 🔨 add type to bundle-wasm
* 💌 signed, sealed, delivered
* chore: use hashes for versions
* chore: don't save git credentials
* chore: use array syntax
for some reason the schema for the actions wants it to be an array
* perf: set concurrency limits to reduce cost and improve dx
Without this it means that, for example, if I push a change to a PR then
shortly push again this workflow will be running twice. This change will
cancel the old run before starting the new one, which reduces the
overall actions cost and DX as you don't have extra runs
* chore: use hashes for versions
* chore: update node version
* perf: set concurrency limits to reduce cost and improve dx
Without this it means that, for example, if I push a change to a PR then
shortly push again this workflow will be running twice. This change will
cancel the old run before starting the new one, which reduces the
overall actions cost and DX as you don't have extra runs
* chore: don't save git credentials
* chore: mitigate potential template injection
See https://docs.zizmor.sh/audits/#template-injection
* chore: update ::set-output command to new syntax
https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
* chore: use hashes for versions
* chore: don't save git credentials
* chore: limit id-token permission to the publishing steps
* chore: explicitly disable npm cache to mitigate cache poisoning attacks
* chore: mitigate potential template injection
See https://docs.zizmor.sh/audits/#template-injection
* chore: use oidc
This should be using OIDC for publishing
* chore: update ::set-output command to new syntax
https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
* 🙅 revert aggressive optimization experiments pending benchmark
Reverts three changes that need benchmarks before landing:
- -Oz / wasm-opt
- brotli+Z85 wasm compression
- wcwidth.c rewrite
* Add CodSpeed performance benchmarks
* 🔨 use deno
* 🧼 deno fmt
* ⚙️ vitest -> tinybench
* 🧼 remove codspeed assets
* 🔨 fix ci
* add type module
* fmt
* chore: update github url (#38)
* downgrade to tinybench@5
* move to examples folder with readme
* 🔧 export animating from wasm build
* 🧪 cover transitions in snapshots and validation
* ✅ enforce nonnegative transition duration
---------
Co-authored-by: Charles Lowell <cowboyd@frontside.com>
Co-authored-by: Jacob Bolda <me@jacobbolda.com>
Co-authored-by: Nate Moore <nate@natemoo.re>
Co-authored-by: ghostdevv <git@willow.sh>
Co-authored-by: Nate Moore <git@natemoo.re>
Co-authored-by: codspeed-hq[bot] <117304815+codspeed-hq[bot]@users.noreply.github.com>
Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
* remove uncecessary note
* clean up and consolidate
* re-organize transitions examples
* 🔥remove redundant cast
---------
Co-authored-by: Ryan Rauh <rauhryan@users.noreply.github.com>
Co-authored-by: Jacob Bolda <me@jacobbolda.com>
Co-authored-by: Nate Moore <nate@natemoo.re>
Co-authored-by: ghostdevv <git@willow.sh>
Co-authored-by: Nate Moore <git@natemoo.re>
Co-authored-by: codspeed-hq[bot] <117304815+codspeed-hq[bot]@users.noreply.github.com>
Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
Co-authored-by: Ryan Rauh <rauh.ryan@gmail.com>
Bumps the github-actions group with 4 updates in the / directory: [actions/checkout](https://github.com/actions/checkout), [actions/cache](https://github.com/actions/cache), [CodSpeedHQ/action](https://github.com/codspeedhq/action) and [actions/github-script](https://github.com/actions/github-script).
Updates `actions/checkout` from 6.0.2 to 7.0.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6.0.2...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)
Updates `actions/cache` from 5.0.5 to 6.0.0
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/27d5ce7f107fe9357f9df03efb73ab90386fccae...2c8a9bd7457de244a408f35966fab2fb45fda9c8)
Updates `CodSpeedHQ/action` from 4.17.0 to 4.17.6
- [Release notes](https://github.com/codspeedhq/action/releases)
- [Changelog](https://github.com/CodSpeedHQ/action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codspeedhq/action/compare/9d332c4d90b43981c3e55ae8e38e68709996240f...63f3e98b61959fe67f146a3ff022e4136fe9bb9c)
Updates `actions/github-script` from 7.1.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/f28e40c7f34bde8b3046d885e986cb6290c5673b...3a2844b7e9c422d3c10d287c895573f7108da1b3)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-version: 7.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions
- dependency-name: actions/cache
dependency-version: 6.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions
- dependency-name: CodSpeedHQ/action
dependency-version: 4.17.6
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: github-actions
- dependency-name: actions/github-script
dependency-version: 9.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* test: failing repro for nested-clip
* fix: stack clip regions so nested clips restore the parent rect
Clip state was a single rect, so closing an inner clip turned
clipping off entirely and later siblings leaked past the outer
bounds. Replace it with a fixed-depth stack: SCISSOR_START pushes
intersect(parent, child) and SCISSOR_END pops, restoring the parent
rect while the stack is non-empty. The active top mirrors into the
existing clipx/clipy/clipw/cliph scalars, so setcell is unchanged.
Fixes #77
* test: move nested-clip regression into test/clip.test.ts
* 🌆 Use visual language for clipping tests
* 📝 Update render spec to include clipping behavior
* fix: honor clip depth limit
---------
Co-authored-by: Charles Lowell <cowboyd@frontside.com>
* test: add input event-loop integration test
Drives createInput as a consumer would (read chunk, scan, flush a pending ESC, dispatch) and asserts the same event stream whether input is fed whole, byte-by-byte, or split mid-sequence. Covers the lone-ESC flush path.
* bench: add macro throughput WallTime bench
In-process bench over a large mixed corpus, measured in WallTime where real work dominates placement/JIT/alloc noise (the Simulation micro-benches sit on codegen cliffs). Feeds the corpus in small reads: a single scan() drains at most 256 events (the wasm event-buffer cap), so small reads keep every call under the cap and process the whole corpus.
* bench: remove input micro-suite (codegen-cliff artifacts)
The Simulation input micro-benches (long input burst, printable ASCII single char) move by 50-90% on unrelated changes, even a test-only rename, because their simulated cost snaps to a different value when the combined wasm shifts. Input perf is now gated by the throughput WallTime bench; correctness by the event-loop integration test.
* ci(bench): pin build runner and drop wasm cache
Pin ubuntu-24.04 so the wasm toolchain is stable and drop the wasm cache so main and PRs always rebuild identically. The cache froze main's baseline on a stale build, so every PR compared a fresh build against a stale baseline and produced phantom regressions.
* fix(ci): return a promise from throughput bench for CodSpeed walltime
CodSpeed's walltime tinybench plugin only populates result.latency on its async path; a sync task fn leaves it undefined and crashes (Cannot destructure 'min' of result.latency). startup.bench works because its tasks return a promise (spawnFixture). Return Promise.resolve() from the throughput task so the plugin takes the async path — a bare async fn with no await would trip deno's require-await lint. The walltime job runs startup and throughput as separate node processes.
* bench: move render/ops to WallTime macro benches, drop the simulation job
CodSpeed Simulation (Valgrind) is unviable for CI here: flaky measurements (dashboard layout swung 20x, diff render 17% on changes that touch no render code) and unpredictable runtime — the same commit's simulation job finished in ~2 min one run and hung past 30 min the next. Convert render/ops to ms-scale WallTime macro benches (looped, promise-returning, ~7-11ms at <1% variance) run as separate node processes in the walltime job, and drop the simulation job entirely. mod.ts is now a local aggregator for deno task bench.
* ref(test): use built-in
* ref(bench): centralize withCodSpeed workaround
* ref(test): use semantic util functions
* ✨ Add glyph-cell text backgrounds
Packs a bg color into the pack string
after decoding the bg color, its pass through to the render text method
properly
* chore: update the render-spec
* Add a very usage of text bg in the examples
* Adds a demo that looks like a code diff to show all the different colors features in action
* adds test to verify the bg color is reset have the directive
* move our tests into color.test.ts
The size report builds head then base without cleaning between them. For PRs that change only build config (Makefile, patches/, flags) and not src/*.c, make sees clayterm.wasm as newer than the unchanged sources and skips the base rebuild, so base re-measures head's artifact, delta is 0, and no size comment is posted. Run make clean && make for both builds so each is from scratch.
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).
Updates `actions/checkout` from 6.0.2 to 6.0.3
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-version: 6.0.3
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: github-actions
...
Signed-off-by: dependabot[bot] <support@github.com>