Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
398 changes: 398 additions & 0 deletions .claude/skills/slt-migration/SKILL.md

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions .claude/skills/slt/REFERENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,21 @@ Load this only when the user asks about specific feature flags, or when building

## Doc pointers

- `docs/COMPLETE_REFERENCE.md` — full API, single-file, ~1500 lines (LLM-optimized)
- `docs/COMPLETE_REFERENCE.md` — full API, single-file, ~1530 lines (LLM-optimized)
- `docs/COOKBOOK.md` — 5 copy-paste app recipes
- `docs/STATE_APIS.md` — every public `*State` struct with methods
- `docs/STATE_APIS.md` — every public `*State` struct with methods (note: `RichLogState::new()` is bounded at 10000 entries since v0.19.2; use `RichLogState::new_unbounded()` for unlimited)
- `docs/PREVIOUS_FRAME_GUIDE.md` — frame timing, when `Response.rect` is valid
- `docs/PATTERNS.md` — reusable patterns
- `docs/PATTERNS.md` — reusable patterns including `provide` / `use_context` / `use_state_named` / `with_if` (v0.19.0+ component DX)
- `docs/EXAMPLES.md` — annotated table of every example; start here when looking for a runnable reference
- `docs/ARCHITECTURE.md` — render pipeline (commands → build_tree → flexbox → collect → render → flush)
- `docs/THEMING.md` — `Theme` presets, `ThemeColor` semantic tokens, contrast helpers
- `docs/TESTING.md` — `TestBackend`, `EventBuilder`, snapshot patterns
- `docs/THEMING.md` — `Theme` presets, `ThemeColor` semantic tokens, contrast helpers (`ThemeBuilder` is `const fn` since v0.19.2; themes can be defined at compile time)
- `docs/TESTING.md` — `TestBackend`, `EventBuilder` (incl. v0.19.1 `mouse_up` / `drag` / `key_release` / `focus_gained` / `focus_lost`), snapshot patterns
- `docs/AI_GUIDE.md` — concise AI-oriented overview
- `docs/BACKENDS.md` — `Backend`, `AppState`, `frame()` low-level paths
- `docs/BACKENDS.md` — `Backend`, `AppState`, `frame()` low-level paths; sixel auto-detection uses an exact-match list (`mlterm` / `foot` / `yaft` / `xterm-256color-sixel`) plus the `"sixel"` substring catch-all and `SLT_FORCE_SIXEL=1` opt-in
- `docs/DEBUGGING.md` — F12 layout overlay, common debug flags
- `docs/ANIMATION.md` — `Tween` / `Spring` / `Keyframes` / `Sequence` / `Stagger` (`Stagger::is_all_done()` reports completion across all items, distinct from `is_done()`)
- `src/lib.rs` — authoritative public re-exports
- `examples/` — 25+ runnable examples
- `examples/` — 32 runnable examples (highlights: `demo_cjk` CJK / wide-char rendering, `demo_website` `provide` / `use_context` composition, `demo_dashboard` full layout)

## Release / deployment reference

Expand Down
20 changes: 16 additions & 4 deletions .claude/skills/slt/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,20 @@ layout for N hasn't happened yet, so `Response.rect` reflects frame N-1. On fram
it's a zero `Rect`. For measurement-dependent logic, guard with
`if ui.tick() > 0 { /* use rect */ }`. See `docs/PREVIOUS_FRAME_GUIDE.md`.

For larger apps, write "components as functions": `fn render_card(ui: &mut Context, data: &Card)`.
Share read-mostly state via `ui.provide(value, |ui| ...)` + `ui.use_context::<T>()`
(avoids threading `&theme` / `&tick` / `&mut toasts` through every helper fn).
Component-local state lives in `ui.use_state_named(id)` — the id-keyed variant
is safe inside conditionals, unlike order-based `use_state`. Conditional styling:
`.with_if(cond, modifier)` on text and `ContainerBuilder`. See `docs/PATTERNS.md`.

## Authoring workflow

1. Confirm the goal. What app is the user building? Data table? Dashboard? Form? Game?
2. Check `docs/COOKBOOK.md` for a matching recipe (login / data table / modal+toast / dashboard / file picker). Start from that if it fits.
3. Otherwise, read `docs/COMPLETE_REFERENCE.md` (single condensed file) and grep `src/lib.rs` for the needed re-exports.
4. For state types, read `docs/STATE_APIS.md` — every public `*State` struct listed with methods.
5. Stick to the small core grammar: `ui.text / row / col / bordered / button / text_input / table / list / modal / toast / chart / canvas / tabs / select / tree / toast / spinner`.
5. Stick to the small core grammar: `ui.text / row / col / bordered / button / text_input / table / list / modal / toast / chart / canvas / tabs / select / tree / spinner`. For component composition (v0.19.0+): `ui.provide(...)` / `ui.use_context::<T>()` / `ui.use_state_named(id)` / `.with_if(cond, modifier)`.
6. Before writing `ui.foo(...)`, grep `src/context/` to confirm the method exists. Do NOT invent APIs.
7. Run the quality gate (below) before saying "done".

Expand Down Expand Up @@ -77,16 +84,21 @@ Red flags that mean STOP:
- **`unsafe` blocks.** `#![forbid(unsafe_code)]` is on at crate root. Hard compile error.
- **Forgetting `'static` on `ContainerBuilder::draw()` closure.** Raw draw is deferred.
- **Mixing crossterm raw events with `ui.*` helpers.** Prefer `ui.key()`, `ui.key_code()`, `ui.key_mod()`. Raw events are for advanced cases only.
- **`use_state()` inside `if` / `match` / `for`.** Order-based hooks misbehave when call order changes between frames. Use `ui.use_state_named(id)` (id-keyed) for state inside conditionals.
- **Threading `&theme`, `&tick`, `&mut state` through every render fn.** v0.19.0+ has `ui.provide(value, |ui| ...)` + `ui.use_context::<T>()` for cross-scope reads. Reserve explicit params for *writes*.
- **Hard-coding `Color::Rgb(...)` in widget code.** Pull from `ui.theme()` (`primary`, `text`, `border`, `selected_bg`, etc.) so themes can swap. v0.19.2 made `ThemeBuilder` `const fn` — themes can be defined at compile time.
- **`RichLogState::new()` for unbounded logs.** v0.19.2 capped `new()` at 10000 entries. Use `RichLogState::new_unbounded()` if you really want unlimited accumulation (tail-style log viewers).
- **Animating without `slt::Tween` / `slt::Spring`.** Don't reinvent — use the animation primitives.
- **Printing to stdout/stderr from a widget.** `#![warn(clippy::print_stdout)]` / `print_stderr`. A library must not write to stdout.

## Reading order when stuck

1. `docs/COMPLETE_REFERENCE.md` — condensed everything, start here.
2. `docs/COOKBOOK.md` — 5 full app recipes.
3. `src/lib.rs` — authoritative public re-exports.
4. `examples/` — 25+ runnable examples; find the closest pattern.
5. If still stuck: ask the user. Korean conventions to honor: "ㄱㄱ" = proceed immediately, "켜줘" = open the file in Cursor (not `cat` to terminal).
3. `docs/PATTERNS.md` — component composition (`provide` / `use_context` / `use_state_named` / `with_if`) and state-ownership idioms.
4. `src/lib.rs` — authoritative public re-exports.
5. `examples/` — 32 runnable examples; find the closest pattern. `demo_cjk` for CJK / wide-char rendering, `demo_website` for the canonical `provide` / `use_context` example.
6. If still stuck: ask the user. Korean conventions to honor: "ㄱㄱ" = proceed immediately, "켜줘" = open the file in Cursor (not `cat` to terminal).

## Testing pattern (headless)

Expand Down
61 changes: 61 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,67 @@

## [Unreleased]

## [0.19.3] — 2026-04-27

Patch release covering 11 v0.19.x patch-safe issues plus 6 cross-cutting
extensions framing SLT in terms of broader UI library patterns (CSS / Flutter /
React Native positioning, performance budget, migration guidance, visual
snapshot regression infrastructure).

### Added

- **`feat(layout)` — `Anchor` enum + `overlay_at` / `modal_at`** (#200) — 9-cell positioning (`TopLeft`, `TopCenter`, …, `BottomRight`). Maps to CSS `place-self`, Flutter `Align(alignment:)`, React Native `position: absolute`.
- **`feat(layout)` — `overlay_at_offset(anchor, dx, dy, …)` / `modal_at_offset(…)`** — CSS `inset`-style offset on top of 9-cell anchor. Sign convention: positive `(dx, dy)` always inset toward viewport center. Mapping documented in `docs/POSITIONING.md`.
- **`feat(layout)` — `DebugLayer::{All, TopMost, BaseOnly}`** (#201) + `Context::set_debug_layer` / `debug_layer()` — F12 overlay scoped to a single layer. `All` is the default → no API call needed for the reported case.
- **`feat(api)` — `min_h` / `max_h` breakpoint variants** (#147) — `xs_min_h` / `sm_min_h` / `md_min_h` / `lg_min_h` / `xl_min_h` / `min_h_at` and same for `max_h`. Symmetric with `min_w` / `max_w` breakpoint coverage.
- **`feat(skill)` — `.claude/skills/slt-migration/SKILL.md`** (398 lines) — Migration skill mapping `ratatui` / `cursive` / `textual` → SLT with grep-verified API references.
- **`feat(test)` — `tests/visual_snapshots.rs` + 5 baselines** — Visual snapshot regression infrastructure using `insta`. Catches layout drift, border render bugs, theme color shifts, CJK width issues. Baselines: `demo`, `demo_dashboard`, `demo_cjk`, `demo_infoviz`, `demo_overlay_anchor`.
- **`docs(positioning)` — `docs/POSITIONING.md`** (286 lines) — CSS `place-self` / Flutter `Align`+`Positioned` / React Native `position: absolute` ↔ SLT `Anchor` mapping with migration recipes.
- **`docs(performance)` — `docs/PERFORMANCE.md`** (336 lines) — 60 fps frame budget, allocation budget, 6 optimization patterns, comparison vs React / Flutter / UIKit / ratatui, regression detection workflow.
- **`docs(migration)` — `docs/MIGRATION.md`** (234 lines) — v0.19 → v0.20 migration guide with deprecation table + sed-based codemod + comparison vs React / Vue / Angular / Flutter migration tooling.
- **`example` — `examples/demo_overlay_anchor.rs`** — 9 anchor positions + 4 inset corners using `overlay_at_offset`.

### Fixes

- **`fix(layout)` overlay `align(End)/justify(End)` rendered at center** (#200 part 2) — root cause in `src/layout/flexbox.rs`: overlay sizing block hard-coded shrink-and-center, starving any inner `grow`. New `any_grow` heuristic expands the wrapper to full area when a child has `grow > 0`; legacy behavior preserved otherwise.
- **`fix(layout)` `container.grow(1).draw(|buf, rect|)` inside overlay didn't render** (#200 part 3) — same root cause: 0×0 wrapper rect was being skipped. Same fix resolves both bugs together.
- **`fix(layout)` F12 debug overlay skipped `node.overlays`** (#201 part A) — `render_debug_overlay` now walks both `node.children` and `node.overlays`, matching `count_leaf_widgets`. Default-on; no API call needed.

### Perf

- **`perf(container)` integer `isqrt` for `filled_circle`** (#146) — Newton's method replaces `f64::sqrt()` round-trip. MSRV 1.81 blocks `u64::isqrt` (1.84+); migrate when MSRV bumps.
- **`perf(layout)` `LayoutNode` size 432 → 320 bytes (~26 % reduction)** (#153) — 6 text-only fields extracted into `Box<TextNodeData>`. Spacer / Container / RawDraw nodes (the majority) now pay 8 bytes (`Option<Box<…>>`) instead of ~120 bytes of always-`None` fields. `const _ASSERT_LAYOUT_NODE_SIZE` regression guard at `tree.rs:3-15`.
- **`perf(layout)` `commands` Vec capacity reused via `FrameState.commands_buf`** (#150) — eliminates per-frame Vec allocation in `Context::new`.
- **`perf(layout)` `FrameData` Vec capacity reused via `&mut FrameData`** (#155) — `collect_all` signature changed to `(&LayoutNode, &mut FrameData)`. 8 Vec allocations per frame eliminated.
- **`perf(layout)` `wrap_segments` `line_segs` capacity hint** (#157) — `Vec::with_capacity(segments.len().min(16))` reduces early growth churn on text wrap path.
- **`perf(layout)` viewport bound check before bottom border corner** (#162) — gates `set_char` on `bottom_i < viewport_bottom`. No functional change (OOB writes were silently skipped); saves up to 2 `set_char` per scrolled border frame.

### Refactor

- **`refactor(api)` deprecate long-form aliases** (#148) — `pad()` / `min_width()` / `max_width()` / `min_height()` / `max_height()` are now `#[deprecated(since = "0.20.0")]`. Use short forms: `p()` / `min_w()` / `max_w()` / `min_h()` / `max_h()`. Internal callers updated. Migration guide in `docs/MIGRATION.md`.
- **`refactor(debug)` F12 per-layer color tagging** — Base = green family, Overlay = red, Modal = blue. Status bar adds breakdown: `14 widgets (8 base, 5 overlay, 1 modal)`. Inspired by Chrome DevTools / React DevTools / Flutter Inspector layer color conventions.
- **`refactor(layout)` `group_name: Option<Arc<str>>` confirmed in `LayoutNode`** (#152) — already shipped earlier in v0.19.x; collect-side conversion is now a pointer bump (atomic increment) rather than heap alloc.

### Docs

- **`docs(skill)` SLT skill (`.claude/skills/slt/`) v0.19.x sync** — v0.19.0 component DX (`provide` / `use_context` / `use_state_named` / `with_if`), `RichLogState` bounded default, `ThemeBuilder` `const fn`, `EventBuilder` v0.19.1 chain wrappers.
- **`docs(audit)` 17 docs files audited and synced to v0.19.x** — BLOCKING: `AppCtx` `'static` lifetime fix in AI_GUIDE / COOKBOOK examples (owned `Theme` via `*ui.theme()` Copy pattern, mirroring `examples/demo_website.rs`). HIGH: 16 leaked GitHub issue refs scrubbed from prose. MEDIUM: `COMPLETE_REFERENCE` version banner update, `WIDGETS` separator path corrected, `EXAMPLES` `demo_cjk` row added with audit prose cleanup, `llms.txt` `try_get` signature corrected and `demo_website` / `demo_cjk` added to examples list.
- **`docs(testing)` visual snapshot regression workflow** — section in `docs/TESTING.md` covering `cargo test --test visual_snapshots`, `cargo insta review` flow, scope of detection.
- **`docs(debugging)` F12 layer-color reading guide** — section in `docs/DEBUGGING.md` describing color tagging and status-line breakdown.

### Tests

- **7 new regression tests** for #200 (overlay anchor + 2 bug fixes) and #201 (F12 walks overlays, layer color distinction, count breakdown).
- **5 visual snapshot baselines** committed in `tests/snapshots/visual__*.snap`.

### Asset cleanup

- Removed orphan assets (~6.1 MB): `assets/tui-builders-demo.gif`, `assets/demo_tetris.png`, `examples/demo_wiki.rs` (depended on private `assets/blackpink/`, broke external builds).

### Notes

This release closes the v0.19.x perf / refactor backlog (11 patch-safe issues) plus pairs with the external reporter promise on #200 / #201. Breaking-change issues (#98, #134, #149, #161, #184, #192, #193) remain deferred to v0.20.0. #102 (textarea undo / redo) remains blocked on `cargo-semver-checks` private-field rule; #171 (line-hash flush skip) remains blocked on bench gate. 3-pass review (5 author agents → 10 independent reviewers → 5 post-fix reviewers) per group; full Core + Extended Gate green at tag.

## [0.19.2] — 2026-04-27

Patch release covering 34 v0.19.x issues plus two long-standing visual regressions
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

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

10 changes: 5 additions & 5 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.19.2"
version = "0.19.3"
edition = "2021"
description = "Super Light TUI - A lightweight, ergonomic terminal UI library"
license = "MIT"
Expand Down Expand Up @@ -142,10 +142,6 @@ path = "examples/demo_website.rs"
name = "demo_infoviz"
path = "examples/demo_infoviz.rs"

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

[[example]]
name = "cookbook_dashboard"
path = "examples/cookbook_dashboard.rs"
Expand Down Expand Up @@ -182,6 +178,10 @@ path = "examples/demo_kitty_image.rs"
name = "demo_cjk"
path = "examples/demo_cjk.rs"

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

[[bench]]
name = "benchmarks"
harness = false
10 changes: 9 additions & 1 deletion _typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,12 @@
"flate2" = "flate2"

[files]
extend-exclude = ["CHANGELOG.md", "Cargo.lock", "AUDIT-REPORT.md", "docs/README.*.md"]
extend-exclude = [
"CHANGELOG.md",
"Cargo.lock",
"AUDIT-REPORT.md",
"docs/README.*.md",
# Visual regression snapshots can contain truncated text the typos
# checker misreads as misspellings (e.g. "anc" cut from "anchor").
"tests/snapshots/visual__*.snap",
]
Binary file removed assets/demo_tetris.png
Binary file not shown.
Binary file removed assets/tui-builders-demo.gif
Binary file not shown.
50 changes: 50 additions & 0 deletions docs/AI_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ Pass `WidgetColors::new().fg(color).bg(color)` as the last argument. See `docs/T

`use_state()` and `use_memo()` must be called in the same order every frame. Never put them inside conditionals.

Exception (v0.19.0): `ui.use_state_named(id)` is the id-keyed variant and IS safe inside `if`/`match` branches because it keys by the supplied `&'static str` instead of call order. Reach for it when you genuinely need a hook in a conditional path.

```rust
// Wrong: order-based hook inside a conditional drifts the call order.
if expanded {
let count = ui.use_state(|| 0); // BAD — frame N has it, frame N+1 may not.
}

// Right: id-keyed variant is safe inside conditionals.
if expanded {
let count = ui.use_state_named::<i32>("sidebar.count"); // OK
}
```

The original `use_state` / `use_memo` order rule still holds — only the id-keyed `*_named` variants opt out of it.

### "How do I handle keyboard shortcuts?"

Use `key(c)`, `key_code(code)`, or `key_mod(c, mods)`. For modal-aware shortcuts use the regular versions. For global shortcuts that bypass modals, use `raw_key_code()` or `raw_key_mod()`. For key sequence detection use `key_seq("gg")`.
Expand Down Expand Up @@ -85,6 +101,40 @@ For the frame timeline and prev-frame rect rules, see [Previous Frame Guide](PRE
- Use `palette::tailwind` colors instead of hardcoding RGB values.
- Check `docs/FEATURES.md` before using feature-gated APIs.

### Context injection: stop threading shared state through every render fn

When a value is *read* by many nested render functions (theme, current tick, current user, toast bus), do not thread `&theme`, `&tick`, `&mut toasts` parameters through every signature. Use `ui.provide(value, |ui| ...)` once near the root, then have nested code read it back with `ui.use_context::<T>()` (panics if missing) or `ui.try_use_context::<T>()` (returns `Option<&T>`). Added in v0.19.0.

```rust
// `provide` boxes the value as `dyn Any`, so the type must satisfy `T: 'static`.
// `Theme` is `Copy`, so deref-copy from `ui.theme()`. Use `&'static str` for
// string literals; switch to `String` if the value comes from runtime input.
struct AppCtx {
theme: slt::Theme,
tick: u64,
user: &'static str,
}

slt::run(|ui: &mut slt::Context| {
let ctx = AppCtx { theme: *ui.theme(), tick: ui.tick(), user: "subin" };
ui.provide(ctx, |ui| {
render_header(ui);
render_card(ui);
});
});

fn render_card(ui: &mut slt::Context) {
let ctx = ui.use_context::<AppCtx>();
ui.text(format!("hi {} (tick {})", ctx.user, ctx.tick));
}
```

Reserve explicit parameters for **writes** (e.g. `&mut MyDocState`) — those should still be passed in, not read out of the context bag. See [PATTERNS.md](PATTERNS.md) for the full pattern.

### Conditional styling without re-chaining

`with_if(cond, modifier)` and `with(modifier)` (v0.19.0) let you fold conditional styling into a single fluent chain on text and `ContainerBuilder`. Replace `if cond { t.bold(); t.fg(Color::Red); }` style branching with `ui.text(...).with_if(is_error, |t| t.bold().fg(Color::Red))`. Cleaner diffs, no broken chains.

## Internal widget rules for agents

When editing SLT built-in widgets inside `src/context/*`, prefer the internal interaction helpers instead of hand-rolling event scans again.
Expand Down
Loading
Loading