feat: v0.21.1 — interaction-signal completeness + ergonomics + WASM/perf + crate hygiene#276
Merged
Conversation
Add find_text, region/assert_region, assert_styled_contains, snapshot, and assert_snapshot_eq to TestBackend for ergonomic content+style and sub-rectangle assertions, plus unified-diff snapshot comparison. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the text gradient API additively: - gradient_stops(&[(f32, Color)]): multi-stop horizontal foreground gradient, stops clamped to 0..1 and sorted internally; empty slice is a no-op, single stop renders solid. - bg_gradient(from, to) / bg_gradient_stops(&[(f32, Color)]): background analogues applying the gradient to cell background instead of fg. All variants reuse the existing per-column char mapping (denom = len-1, t = i/denom) and Color::blend clamping so width handling stays consistent with gradient(). Adds 8 TestBackend unit tests covering first/middle/last interpolation, unsorted input, single stop, and empty edge cases for both fg and bg. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add bubbletea key.Matches parity to the keymap catalog: - Binding::matches(&KeyEvent) -> bool, with press-only and modifier contains() semantics matching Context::key_mod. - KeyMap::matched(&KeyEvent) -> Option<&Binding>, first-registration-wins. - Context::keymap_match(&KeyMap) -> Option<&Binding> peeking the frame's unconsumed key presses, respecting the modal/overlay guard. Covered by unit tests (incl. release-event and overlap edge cases) and TestBackend-driven Context tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add ListState::move_item(from, to) — bounds-checked, no-op safe, keeps the search cache and per-item heights aligned, rebuilds the filtered view, and follows selection onto the moved item. Add Context::list_reorderable / list_reorderable_colored — opt-in reorderable list returning a ListResponse (Deref<Response>) exposing .reordered: Option<(usize, usize)>. Shift/Alt + Up/Down moves the selected item; plain navigation and the existing list() API are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add moon, bounce, circle, points, arc, toggle, and arrow presets to SpinnerState plus a SpinnerPreset enum and SpinnerState::preset() builder for cli-spinners / ratatui-throbber parity. dots() and line() are unchanged. The chars field stays private; only constructors and a new frame_count() helper are exposed. Adds focused unit tests covering each preset's sequence, cycling, preset/constructor equivalence, and a large-tick wrap-around edge case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task A — intrinsic-size measurement (v0.21.1): - Context::measure_text(&str, Option<u16>) -> (u16, u16): reuses the layout engine's wrap_lines kernel (no duplicated width logic) to report the (width, rows) text would occupy unwrapped (None / Some(0)) or wrapped to a column budget (Some(w)). Display-width aware (wide CJK = 2, combining = 0), saturating to u16. - Context::measured_rect(&str) -> Option<Rect>: read-only lookup into the previous frame's name->rect bookkeeping (prev_group_rects) for a named group(...) container. Task B — resize debounce/coalesce (v0.21.1): - poll_events no longer fires on_resize per Event::Resize. A burst within one poll batch now collapses to a SINGLE on_resize at end-of-batch, picking up the final terminal size (handle_resize re-reads terminal::size()). Avoids N x (Clear(All) + double realloc + size() syscall) while dragging a window edge. The SIGCONT/resume redraw path in run_with is untouched. - Added pure helper resize_invocations_for_batch for a unit-testable rule; a debug_assert keeps the has_resize flag and the helper in agreement. Tests: 6 measure_text/measured_rect cases (multi-line, wrapping, Some(0) edge, wide-CJK, first-frame None, post-render group rect) + 3 resize-coalesce cases (3-event batch -> 1 invocation, no-resize -> 0, has_resize agreement). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add additive, non-breaking ergonomic APIs to Color: - From<(u8,u8,u8)>, From<[u8;3]>, From<u32> (0xRRGGBB) -> Color::Rgb - FromStr parsing #RRGGBB / RRGGBB / #RGB / RGB and named colors, with a new public ColorParseError (Display + std::error::Error) - from_hsl, from_hsv (h in degrees, s/l/v in 0..1) and rotate_hue, using pure f32 HSL/HSV round-trip math (no new deps) Named/indexed colors resolve through the existing palette before hue rotation. Adds focused unit tests covering hex round-trips, HSL/HSV primaries, hue rotation, clamping/wrapping, and FromStr error cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Close the two biggest WASM backend parity gaps against the native crossterm path. DOM flush diff: DomBackend now keeps a `prev: Buffer` snapshot and mutates only the spans whose cell actually changed (Cell derives PartialEq over symbol + style + hyperlink), mirroring the native ANSI diff in src/buffer.rs. Steady-state frames now issue almost no DOM writes instead of touching every cell. A grid rebuild (first frame or resize) forces a full repaint. Event parity (Phase 1-2): wire the remaining input sources into slt Events the core run loop consumes, matching crossterm's mapping: - mouse wheel -> ScrollUp/Down/Left/Right at the cell under the cursor (dominant-axis resolution, zero-delta ignored) - window resize -> Resize(cols, rows); the backend recomputes the grid from the container pixel size, resizes its buffer, and rebuilds the DOM grid next flush - focus/blur -> FocusGained / FocusLost - clipboard paste -> Paste(text) WheelEvent/FocusEvent/ClipboardEvent/DataTransfer web-sys wrappers are not in this crate's feature set, so the new listeners read the generic web_sys::Event reflectively via js-sys (no Cargo changes). Position-dependent listeners now read the live grid size from the backend so they stay correct after a resize. Image overlay (Phase 3) is intentionally left as a follow-up. Adds host-runnable logic tests for the wheel-delta mapping and the color/style CSS translation (incl. zero-delta, tie, and default-style edge cases). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…SC52 read hazard - sync-output capability gate: probe DECRQM ?2026 in probe_capabilities, set Capabilities::sync_output on positive support, and gate BSU/ESU emission on a tri-state resolution. Silent/headless probes stay Unknown and keep emitting exactly as before (behavior-preserving default). - sprixel reblit scan: replace the O(n*m) previous.sprixels.iter().any(..) position lookup with a hashed-key HashSet and add a per-row clean+hash shortcut so untouched footprint rows skip the per-cell annihilation scan. - read_clipboard: document the stdin typeahead-swallow concurrency hazard and recommended usage (signature unchanged). - bench: add #[doc(hidden)] __bench_flush_kitty, __BenchSprixelFixture, __bench_new_sprixel_fixture, and __bench_flush_sprixels wrappers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e-click, scroll_delta, focus traversal Close the issue #208 follow-up: text_input/slider/number_input now report gained_focus/lost_focus via a shared focus_transitions() helper. Add Response.submitted (Enter in text_input), .double_clicked (same-cell within ~400ms), .scroll_delta (hover-gated wheel), and on_click/on_changed/on_focus/ on_submit/on_double_click callback chaining. Add public focus_next/focus_prev and focus_next_in_group/focus_prev_in_group for programmatic + group-scoped focus traversal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… fixes Wire crate-root re-exports for the 9 merged units (ColorParseError, ListResponse, SpinnerPreset, sprixel bench fns), fix the focus_next doc example to a real API, and add explicit let-_ discards for registration-only widget calls in tests. Full core gate green: fmt/check --all-features/ test --all-features (all pass)/clippy --all-features --all-targets -D warnings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the missing rustdoc the audit flagged: - # Example blocks on definition_list and divider_text. - # Panics sections (with the actual emitted message) on the hooks that can panic on a rules-of-hooks / type-mismatch violation: use_memo, use_memo_ref, use_effect, animate_value. - # Family cross-references on gauge / line_gauge linking the gauge family. - # See also on animate_bool / animate_value. Doc-only; no code or signatures changed. All added doctests compile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire a timed criterion arm for the existing __BenchKittyFixture + __bench_new_kitty_fixture hooks (issue #206 alloc suite), which were re-exported at the crate root but had no bench arm. The new flush_kitty group sweeps 1/8/32 placements, flushing each fixture into a reused hermetic Vec<u8> sink in the same shape as the flush group. The sprixel/__bench_flush_kitty hooks named in the unit brief (__bench_flush_kitty, __bench_new_sprixel_fixture, __BenchSprixelFixture, __bench_flush_sprixels) are not present in this fork yet, so those arms are deferred to the integration step. See integration notes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump to 0.21.1 (root + slt-wasm + dep). Expand the published-crate exclude so agent/skill scaffolding, VHS recordings, and dev/CI config (AGENTS.md, .agents/, .claude/, *.tape, deny.toml, _typos.toml, .gitignore) no longer ship in the tarball (~140KB leaked into 0.21.0). Remove the orphaned committed.toml (its CI gate was dropped in 0.21.0) and its stale CI-reference row. Add the 0.21.1 changelog section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add examples/v0210_widgets.rs covering the v0.21.0 widgets that previously shipped without a runnable example: the standalone paginator (PaginatorState / PaginatorStyle), the numeric stepper (NumberInputState / number_input), the variable-height virtual_list (virtual_list_variable + ListState item heights), the async-free frame-clock scheduler (schedule / every / debounce, #248), and the devtools inspector panel (set_inspector / Ctrl+F12, #268). Self-contained Standard-archetype screen (no overlay / no scrollback), builds on the default feature set since the scheduler is wall-clock based. In-frame async (spawn/poll) is noted as out of scope to keep the binary async-free. Requires an explicit [[example]] entry in Cargo.toml (autoexamples = false); the integrator must add it — see integration_notes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…x, example registration - examples/v0211_tour.rs: showcase of the new 0.21.1 APIs (HSL/hex/rotate_hue, gradient_stops/bg_gradient, spinner presets, list_reorderable, measure_text, KeyMap dispatch, focus_next, Response.submitted/double_clicked/scroll_delta). - Register v0210_widgets + v0211_tour as [[example]] (autoexamples=false). - benches: add bench_flush_sprixel_reblit using the merged sprixel bench hooks; gate the kitty/sprixel bench re-exports behind crossterm (fixes no-default). - VHS gallery: rewrite the 6 build-inside-pty tapes to call the prebuilt ./target/release/examples/<x> binary (CI already pre-builds examples), so a cold-cache build no longer overruns the fixed Sleep and captures a blank frame — the gallery_manifest parity gate stays green. - typos: rename test-local `ofr` binding. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vhs-action@v2's bundled ffmpeg installer fails on the current runner
("Failed to install ffmpeg"), so the advisory gallery job never reaches the
(now prebuilt-binary) tapes. Install ffmpeg from apt first so the action finds
it present. CI-only; the gallery job is continue-on-error and the README<->tape
parity hard gate (gallery_manifest) is unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <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.
v0.21.1 — post-0.21.0 competitive-gap audit closeout
Closes the additive (non-breaking) gaps surfaced by the v0.22 backlog audit, folded into a single 0.21.1 release. Every item below is purely additive — no public signature changes, no deprecation removals.
Added
submitted(Enter in a focused single-linetext_input),double_clicked(same-cell within ~400ms; the 2nd still reportsclicked),scroll_delta: i32(hover-gated wheel). Chainableon_click/on_changed/on_focus/on_submit/on_double_click.text_input/slider/number_inputnow populategained_focus/lost_focusvia a sharedfocus_transitionshelper (closes the feat(context): expand Response with right_clicked / gained_focus / lost_focus #208 follow-up).focus_next/focus_prev(modal-trap aware) + group-scopedfocus_next_in_group/focus_prev_in_group.From<(u8,u8,u8)>/From<[u8;3]>/From<u32>/FromStr(+ColorParseError);from_hsl/from_hsv/rotate_hue.gradient_stops(&[(f32,Color)]),bg_gradient,bg_gradient_stops.Binding::matches,KeyMap::matched,Context::keymap_match(bubbleteakey.Matchesparity).SpinnerPreset+moon/bounce/circle/points/arc/toggle/arrow/preset/frame_count.ListState::move_item, opt-inlist_reorderable/_colored→ListResponse{ reordered }.measure_text(text, Option<max_width>) -> (w, rows),measured_rect(name) -> Option<Rect>.DomBackend::resize+ wheel/resize/blur/paste event wiring (Phase 1-2 parity).find_text/region/assert_region/assert_styled_contains/snapshot/assert_snapshot_eq.v0211_tour(new APIs) +v0210_widgets(paginator/number_input/variable virtual_list/scheduler/inspector).Changed
?2026probe; headless/silent terminals keep emitting as before.Capabilities::sync_outputis now populated.Perf
on_resizeper poll-batch (SIGCONT path unchanged).Docs
read_clipboarddocuments the stdin typeahead-swallow hazard.definition_list/divider_textexamples, hook# Panics, Gauge/LineGauge cross-refs).Housekeeping
AGENTS.md,.agents/,.claude/,*.tape,deny.toml,_typos.toml) — ~140KB that leaked into the 0.21.0 tarball.committed.toml(its CI gate was dropped in 0.21.0).Sleep-overrun blank-frame failures).Verification
Full extended gate green locally (real exit codes):
fmt,check --all-features,clippy --all-features --all-targets -D warnings,test --all-features,check --examples,typos,check --no-default-features,check -p slt-wasm --target wasm32-unknown-unknown,cargo hack check --each-feature,audit,deny check, MSRV 1.81.Intentionally NOT included (with rationale)
🤖 Generated with Claude Code