Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f7a2bcd
feat: add TestBackend region/snapshot query helpers
subinium May 30, 2026
e4899e2
feat(text): add multi-stop and background text gradients
subinium May 30, 2026
dd598f6
feat(keymap): add Binding/KeyMap dispatch + Context::keymap_match
subinium May 30, 2026
ff2b7de
feat: add opt-in list keyboard reorder (move_item + list_reorderable)
subinium May 30, 2026
b995774
feat: add named SpinnerState throbber presets
subinium May 30, 2026
a4dc87d
feat: add intrinsic-size measurement API + resize coalescing
subinium May 30, 2026
d7cd079
feat(color): add ergonomic Color constructors and conversions
subinium May 30, 2026
4141e31
feat(wasm): DOM flush diff + Phase 1-2 event parity
subinium May 30, 2026
50cd79a
feat: gate sync-output on DECRQM, optimize sprixel reblit scan, doc O…
subinium May 30, 2026
2803083
feat(context): interaction-signal core — focus-edge, submitted, doubl…
subinium May 30, 2026
23a4fce
Merge branch 'wf/v0211-color' into release/v0.21.1
subinium May 30, 2026
17b31f5
Merge branch 'wf/v0211-gradient' into release/v0.21.1
subinium May 30, 2026
1957a60
Merge branch 'wf/v0211-keymap' into release/v0.21.1
subinium May 30, 2026
77f4501
Merge branch 'wf/v0211-terminal' into release/v0.21.1
subinium May 30, 2026
dc30b00
Merge branch 'wf/v0211-wasm' into release/v0.21.1
subinium May 30, 2026
0e0a841
Merge branch 'wf/v0211-testutil' into release/v0.21.1
subinium May 30, 2026
8b98455
Merge branch 'wf/v0211-spinner' into release/v0.21.1
subinium May 30, 2026
17ea55c
Merge branch 'wf/v0211-listreorder' into release/v0.21.1
subinium May 30, 2026
8d2b821
Merge branch 'wf/v0211-libperf' into release/v0.21.1
subinium May 30, 2026
e18395b
feat: integrate v0.21.1 feature units — re-exports + must_use/doctest…
subinium May 30, 2026
40fb9b1
docs: complete #244 rustdoc audit (examples, panics, see-also)
subinium May 30, 2026
6695744
bench: add kitty image-layer flush bench arm
subinium May 30, 2026
1495037
chore: v0.21.1 — bump version, crate-hygiene exclude, changelog
subinium May 30, 2026
61b5594
docs(examples): add v0.21.0 widgets coverage example
subinium May 30, 2026
a6490af
Merge branch 'docs/244-rustdoc-completion' into release/v0.21.1
subinium May 30, 2026
481150e
Merge branch 'bench/kitty-flush-arm' into release/v0.21.1
subinium May 30, 2026
0824b88
Merge branch 'examples/v0210-widgets-coverage' into release/v0.21.1
subinium May 30, 2026
53003ab
feat: finish v0.21.1 — v0211 tour example, sprixel bench, VHS tape fi…
subinium May 30, 2026
a00ba87
ci: pre-install ffmpeg for the VHS gallery job
subinium May 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
# vhs-action's bundled ffmpeg installer is flaky on the GitHub runner
# (observed: "Failed to install ffmpeg"). Pre-install it from apt so the
# action finds it already present and skips its own download.
- name: Install ffmpeg for VHS
run: sudo apt-get update && sudo apt-get install -y ffmpeg
- uses: charmbracelet/vhs-action@v2
- name: Build release examples
run: cargo build --release --examples
Expand Down
1 change: 0 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ cargo deny check
| Deny Check | `cargo deny check {advisories,bans,licenses,sources}` | Yes (advisories soft) |
| Doc Coverage | `RUSTFLAGS="-Wmissing_docs" cargo check` | No (soft) |
| Semver Check | `cargo-semver-checks` | No (soft) |
| Commit Style | `committed --no-merge-commit` | No (PR only, soft) |

### Pre-PR Additional Gate
Before creating a PR, wait for CI to pass on the pushed branch:
Expand Down
92 changes: 92 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,97 @@
# Changelog

## [0.21.1] - 2026-05-30

Interaction-signal completeness, API ergonomics, WASM parity, perf, and
published-crate hygiene. This release closes the post-0.21.0 competitive-gap
audit: every confirmed additive (non-breaking) gap is implemented. Headline —
the widgets that assembled their `Response` by hand (`text_input` / `slider` /
`number_input`) now report focus edges; `Response` gains `submitted` /
`double_clicked` / `scroll_delta` plus chainable callbacks; ergonomic
`Color` / gradient / `KeyMap` / focus-traversal / measurement APIs; WASM DOM
diffing and input-event parity; sprixel / resize / sync-output perf; and the
published crate no longer ships agent/CI scaffolding.

### Added

- **Response interaction signals** — `Response::submitted` (Enter in a focused
single-line `text_input`), `Response::double_clicked` (two same-cell left
clicks within ~400ms; the second still reports `clicked`), and
`Response::scroll_delta: i32` (hover-gated net wheel delta, so a chart/canvas
can scroll or zoom locally). Chainable callbacks `on_click` / `on_changed` /
`on_focus` / `on_submit` / `on_double_click`, each taking `&mut Context`.
- **Focus edges for hand-assembled widgets** — `text_input`, `slider`, and
`number_input` now populate `gained_focus` / `lost_focus` via a shared
`focus_transitions` helper (closes the #208 follow-up that left those three
always-false).
- **Programmatic focus traversal** — `Context::focus_next` / `focus_prev`
(modal-trap aware, equivalent to Tab / Shift+Tab) and group-scoped
`focus_next_in_group` / `focus_prev_in_group` for a panel-local focus trap
without a modal.
- **Ergonomic `Color`** — `From<(u8,u8,u8)>`, `From<[u8;3]>`, `From<u32>`
(`0xRRGGBB`), and `FromStr` (`#RRGGBB` / `RRGGBB` / `#RGB` / named, with the
new `ColorParseError`); plus `Color::from_hsl`, `from_hsv`, and `rotate_hue`.
- **Text gradients** — `gradient_stops(&[(f32, Color)])` (multi-stop horizontal,
auto-sorted, empty = no-op, single = solid) and the background variants
`bg_gradient` / `bg_gradient_stops`.
- **KeyMap dispatch** (bubbletea `key.Matches` parity) — `Binding::matches`,
`KeyMap::matched`, and `Context::keymap_match` for declarative key routing.
- **Spinner presets** — `SpinnerPreset` enum + `SpinnerState::moon` / `bounce` /
`circle` / `points` / `arc` / `toggle` / `arrow` / `preset` / `frame_count`
(cli-spinners / ratatui-throbber parity); `dots()` / `line()` unchanged.
- **List reorder** — `ListState::move_item(from, to)` and the opt-in
`Context::list_reorderable` / `list_reorderable_colored`, returning
`ListResponse` (Deref to `Response`, with `.reordered: Option<(usize, usize)>`);
Shift+Up/Down or Alt+Up/Down moves the selected item. Plain `list` unchanged.
- **Intrinsic measurement** — `Context::measure_text(text, Option<max_width>)
-> (width, rows)` using the layout engine's own wrap kernel, and
`Context::measured_rect(name) -> Option<Rect>` (the rect a named `group`
occupied on the previous frame).
- **WASM input parity** — `DomBackend::resize` plus mouse-wheel, window-resize,
focus/blur, and clipboard-paste event wiring (Phase 1-2; native parity).
- **TestBackend query/assertions** — `find_text`, `region` / `assert_region`,
`assert_styled_contains`, and `snapshot` / `assert_snapshot_eq`
(unified-diff panic on mismatch).
- **Examples** — a v0.21.1 API tour plus coverage for previously-undemoed
v0.21.0 widgets (paginator, number_input, variable-height virtual_list,
scheduler, devtools inspector).

### Changed

- Synchronized-output (BSU/ESU) emission is now gated on a DECRQM `?2026`
capability probe; silent/headless terminals keep emitting exactly as before
(only a confirmed-unsupported terminal suppresses the guard).
`Capabilities::sync_output` is now populated.

### Perf

- **WASM DOM flush** diffs against a previous-frame buffer and mutates only the
cells that changed (steady-state DOM writes drop toward zero), mirroring the
native ANSI diff.
- **Sprixel re-blit scan** — replaced the per-frame O(n·m) placement lookup with
a hashed-key set plus a per-row clean/hash shortcut that skips untouched
footprint rows.
- **Resize coalescing** — a burst of resize events within one poll batch now
fires the `Clear(All)` + double realloc + `size()` syscall once at
end-of-batch using the final size (SIGCONT/resume redraw path unchanged).
- New criterion benches for the kitty image-flush and sprixel re-blit paths.

### Docs

- `read_clipboard` documents the stdin typeahead-swallow concurrency hazard and
recommended usage.
- Closed the remaining #244 rustdoc gaps (`definition_list` / `divider_text`
examples, hook `# Panics` sections, Gauge / LineGauge family cross-references).

### Housekeeping

- The published crate no longer ships agent/skill scaffolding, VHS recordings,
or dev/CI config (`AGENTS.md`, `.agents/`, `.claude/`, `*.tape`, `deny.toml`,
`_typos.toml`, `.gitignore` — ~140KB of non-library content that leaked into
the 0.21.0 tarball).
- Removed the orphaned `committed.toml` (its CI gate was dropped in 0.21.0) and
the stale Commit-Style row from the CI reference table.

## [0.21.0] - 2026-05-29

This release closes the competitive-analysis gap audit versus ratatui / bubbletea / ink — 35 issues spanning new widgets, layout primitives, terminal protocols, async/scheduling, correctness fixes, perf, and a breaking hook/API cleanup. Highlights: flex-wrap + flex-basis, a runtime capability probe with an automatic image-blitter ladder, an iTerm2 OSC 1337 image path, a color picker, paginator, number stepper, variable-height virtual list, external TOML themes with hot-reload, a frame-clock scheduler, in-frame async `spawn`/`poll`, RTL/bidi text reordering, grapheme-cluster-correct wrapping, SIGTSTP/SIGCONT job control, and a devtools inspector.
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 32 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = [".", "crates/slt-wasm"]

[package]
name = "superlighttui"
version = "0.21.0"
version = "0.21.1"
edition = "2021"
description = "Super Light TUI - A lightweight, ergonomic terminal UI library"
license = "MIT"
Expand All @@ -14,7 +14,24 @@ readme = "README.md"
keywords = ["tui", "terminal", "cli", "ui", "immediate-mode"]
categories = ["command-line-interface"]
rust-version = "1.81"
exclude = ["examples/", ".github/", "assets/", "AUDIT-REPORT.md", "CLAUDE.md"]
# Keep the published crate to library + docs only. The agent/skill scaffolding,
# VHS recordings, and dev/CI config are not needed by downstream consumers
# (they were leaking into 0.21.0's tarball — ~140KB of non-library content).
exclude = [
"examples/",
".github/",
"assets/",
"AUDIT-REPORT.md",
"CLAUDE.md",
"AGENTS.md",
".agents/",
".claude/",
"scripts/",
"deny.toml",
"_typos.toml",
"*.tape",
".gitignore",
]
# Disable cargo's `examples/*.rs` auto-discovery: only the binaries listed
# below as `[[example]]` are exposed via `cargo run --example`. Source
# demos that compose into a tour (v020_*, cookbook_*, most demo_*,
Expand Down Expand Up @@ -218,6 +235,19 @@ path = "examples/v020_perf_audit.rs"
name = "v020_test_utils"
path = "examples/v020_test_utils.rs"

# ── v0.21 coverage ──────────────────────────────────────────────────
# v0210_widgets: paginator / number_input / variable-height virtual_list /
# scheduler / devtools inspector (widgets that shipped in 0.21.0 without a
# dedicated example). v0211_tour: the 0.21.1 API additions.

[[example]]
name = "v0210_widgets"
path = "examples/v0210_widgets.rs"

[[example]]
name = "v0211_tour"
path = "examples/v0211_tour.rs"

[[bench]]
name = "benchmarks"
harness = false
58 changes: 58 additions & 0 deletions benches/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,62 @@ fn bench_streaming_append_chat(c: &mut Criterion) {
group.finish();
}

/// Kitty image-layer flush (issue #206 alloc suite, now timed). Builds an
/// `__BenchKittyFixture` of `n` distinct 8×8 RGBA placements and measures the
/// inline-mode flush emit cost against a hermetic `Vec<u8>` sink, mirroring the
/// `flush` group's reused-sink pattern. The first flush in the timed loop
/// transmits + places every image; the `KittyImageManager`'s internal
/// `prev_placements` then make subsequent identical flushes near-no-ops, so the
/// steady state measures the manager's per-frame placement-diff scan rather than
/// repeated retransmission — the same damage-skip shape as the sprixel scan.
#[cfg(feature = "crossterm")]
fn bench_flush_kitty_images(c: &mut Criterion) {
let mut group = c.benchmark_group("flush_kitty");
for n in [1_usize, 8, 32] {
group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, &n| {
let mut fixture = slt::__bench_new_kitty_fixture(n);
debug_assert_eq!(fixture.len(), n);
debug_assert_eq!(fixture.is_empty(), n == 0);

let mut sink: Vec<u8> = Vec::with_capacity(64 * 1024);
b.iter(|| {
sink.clear();
fixture
.flush_inline(&mut sink, black_box(0))
.expect("kitty flush into Vec<u8> cannot fail");
black_box(sink.len());
});
});
}
group.finish();
}

/// Sprixel re-blit scan on a steady-state (no-damage) frame — the path the
/// v0.21.1 terminal change optimized (hash-set build + per-row clean/hash
/// shortcut). A structurally-identical current/previous frame re-blits nothing,
/// so this measures pure scan cost as the placement count grows.
#[cfg(feature = "crossterm")]
fn bench_flush_sprixel_reblit(c: &mut Criterion) {
let mut group = c.benchmark_group("flush_sprixel_reblit");
for n in [1_usize, 8, 32] {
group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, &n| {
let fixture = slt::__bench_new_sprixel_fixture(n);
debug_assert_eq!(fixture.len(), n);
debug_assert_eq!(fixture.is_empty(), n == 0);

let mut sink: Vec<u8> = Vec::with_capacity(64 * 1024);
b.iter(|| {
sink.clear();
fixture
.flush(&mut sink, black_box(0))
.expect("sprixel reblit into Vec<u8> cannot fail");
black_box(sink.len());
});
});
}
group.finish();
}

/// Register flush-path benches (only when `crossterm` feature is enabled,
/// which is the default for benches). When the feature is off, this is a
/// no-op so the file still compiles under `--no-default-features`.
Expand All @@ -742,6 +798,8 @@ fn bench_flush_group(c: &mut Criterion) {
bench_flush_static_200x60(c);
bench_flush_full_redraw_300x100(c);
bench_flush_sparse_change_300x100(c);
bench_flush_kitty_images(c);
bench_flush_sprixel_reblit(c);
}
let _ = c;
}
Expand Down
2 changes: 0 additions & 2 deletions committed.toml

This file was deleted.

4 changes: 2 additions & 2 deletions crates/slt-wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "slt-wasm"
version = "0.21.0"
version = "0.21.1"
edition = "2021"
description = "WASM/browser backend for SuperLightTUI"
license = "MIT"
Expand All @@ -9,7 +9,7 @@ homepage = "https://github.com/subinium/SuperLightTUI"
documentation = "https://docs.rs/slt-wasm"

[dependencies]
superlighttui = { version = "0.21.0", path = "../..", default-features = false }
superlighttui = { version = "0.21.1", path = "../..", default-features = false }
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = [
"CssStyleDeclaration",
Expand Down
Loading
Loading