Agent skill

Slt Migration

by subinium in subinium/SuperLightTUI

Migrate Rust TUIs from ratatui (or cursive, Python textual) to SuperLightTUI v0.20.

MITAuto-check passed

Install Slt Migration

skills CLI
$ npx skills add subinium/SuperLightTUI --skill slt-migration -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install subinium/SuperLightTUI slt-migration --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/subinium/SuperLightTUI.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/slt-migration .claude/skills/slt-migration && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
slt-migration
GitHub stars
191
Token cost
~6.2k tokens
SKILL.md length
2,072 words
Files
1
Skills in repo
4
Repo updated
First seen
Licence
MIT

At a glance

Migrate Rust TUIs from ratatui (or cursive, Python textual) to SuperLightTUI v0.20.

  • Works in 9 steps: Inventory ratatui widgets used. → Convert the run loop. Replace… → Move state out of the draw closure. Most… → …
  • Porting an existing TUI codebase to SLT
  • SKILL.md covers When to use, v0.20 removed APIs (do NOT…, Mental model translation and ratatui → SLT mapping, plus 6 more sections
  • Calls cargo

What it does

Slt Migration is an agent skill from subinium/SuperLightTUI. Migrate Rust TUIs from ratatui (or cursive, Python textual) to SuperLightTUI v0.20. Use when porting an existing TUI codebase to SLT, or when the user asks "how do I do X from ratatui in SLT". Korean triggers "ratatui 마이그레이션", "SLT로 포팅", "이걸 SLT로".

Its SKILL.md is about 6.2k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It works with Rust and Python. The repository describes itself as: 🦀 An immediate-mode Rust TUI framework with flexbox layout and Tailwind-style chaining API. The licence is MIT.

When your agent uses it

  • Porting an existing TUI codebase to SLT
  • The user asks how do I do X from ratatui in SLT

Example prompts

  • “how do I do X from ratatui in SLT”
  • “ratatui 마이그레이션”
  • “SLT로 포팅”
  • “/slt-migration”

Requirements

  • Python 3

Workflow steps

9 steps, taken from the first numbered list in SKILL.md.

  1. Inventory ratatui widgets used.
  2. Convert the run loop. Replace Terminal::new setup + draw loop + disable_raw_mode teardown with one of slt::run, slt::run_with…
  3. Move state out of the draw closure. Most ratatui apps already do this. Keep the same shape — your App struct now feeds into one SLT closure.
  4. Replace layout splitters. Each Layout::default().constraints(...).split(area) becomes nested ui.row / ui.col + .fill / .h / .w / .h_pct /…
  5. Replace each f.render_widget(...) with the SLT method. Convert widget by widget. Verify any uncertain method via the v0.20 mapping table…
  6. Adopt v0.20 builders. Where the old code hand-rolled gauges, breadcrumbs, scrollable-with-line-numbers, or split panes, use the v0.20…
  7. Replace event handling. Convert raw crossterm::event::read() matches to ui.key(), ui.key_code(), ui.key_mod(), ui.mouse_down()…
  8. Replace &App threading with provide/use_context for read-only shared state.
  9. Run cargo check and fix one widget at a time. Add tests with slt::TestBackend::new(80, 24).render(|ui| ...) once a section compiles.

What it can do on your machine

Read from SKILL.md and the folder at commit 15fd63a. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • cargo

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • github.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Slt Migration loads about 6.2k tokens when it runs. Until then it costs about 66 tokens; SKILL.md has 2,072 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~66
When it runs · the whole SKILL.md, loaded when a task matches
~6.2k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from subinium/SuperLightTUI at commit 15fd63a, republished under its MIT licence (© subinium). 2,072 words, ~6,214 tokens.

Download SKILL.mdSave it as .claude/skills/slt-migration/SKILL.md (or your agent's skills folder).
name
slt-migration
description
Migrate Rust TUIs from ratatui (or cursive, Python textual) to SuperLightTUI v0.20. Use when porting an existing TUI codebase to SLT, or when the user asks "how do I do X from ratatui in SLT". Korean triggers "ratatui 마이그레이션", "SLT로 포팅", "이걸 SLT로".

SLT Migration Skill (from ratatui / cursive / textual) — v0.20

This skill complements .claude/skills/slt/SKILL.md (authoring). Use this one to port an existing codebase. After migration switch to the slt skill for day-to-day work.

Targets SLT v0.20.0 (Cargo.toml: version = "0.20.0"). Every API name below has been verified against src/lib.rs and src/context/widgets_*.rs. The v0.20 API consistency pass removed several v0.19 names — the "v0.20 removed APIs" table below lists every one. Do not migrate to a removed name.

When to use

Trigger when:

  • The user says "migrate from ratatui", "port from cursive", "ratatui equivalent in SLT", or "rewrite this TUI in SLT".
  • A file in scope imports ratatui, tui, or cursive, or Cargo.toml lists them.
  • The user is comparing libraries and wants concrete mappings.
  • A Python textual project is being rewritten in Rust.

If starting fresh (no existing TUI), use the slt skill instead.

v0.20 removed APIs (do NOT migrate to these)

If you see these in any third-party doc, blog post, or AI training data — they are GONE in v0.20. Use the replacement.

RemovedReplacement
gauge_w(r, w)ui.gauge(r).width(w)
gauge_colored(r, c)ui.gauge(r).color(c)
line_gauge_with(r, opts)ui.line_gauge(r).<chain>
breadcrumb_sep(b, s)ui.breadcrumb(b).separator(s)
breadcrumb_response(b) / breadcrumb_response_with(b, s)ui.breadcrumb(b).show() -> BreadcrumbResponse
LineGaugeOptsLineGauge<'_> builder (chained)
HighlightRange::single(i)HighlightRange::line(i)
label_owned(s)label(s) (accepts impl Into<String>)

Mental model translation

Compact side-by-side — read once before writing any code.

AspectratatuiSLT
Loop ownershipYou own it: terminal.draw(|f| ...)slt::run(|ui| ...) owns it
LayoutLayout::default().constraints(...).split(area) → Vec<Rect>ui.row / ui.col / ui.bordered(...).col(...) (flexbox)
Widget APIBuild a widget value, then f.render_widget(widget, rect)Method on &mut Context: ui.text(...), ui.list(&mut state)
StateApp struct outside the closurePlain Rust variables outside the closure (same idiom)
Render modeRetained mental model — recompute every drawImmediate — describe every frame
Hit testingManual Rect mathResponse { clicked, right_clicked, hovered, focused, gained_focus, lost_focus, rect }
Setup / teardownenable_raw_mode, EnterAlternateScreen, panic hook by youAll handled by slt::run (incl. terminal-restoring panic hook)
Threading shared state&mut App parameter chainui.provide(value, |ui| ...) + ui.use_context::<T>()

cursive: callback-based — siv.add_global_callback, layered views, owns its loop. SLT has no callbacks; check inputs and branch in the closure.

textual (Python): retained App+Widget classes, CSS-like styling, compose() yields widgets, on_* event handlers. SLT replaces all of that with one closure and chained method calls.

ratatui → SLT mapping

Run loop

ratatui (typical):

rust
let mut terminal = Terminal::new(CrosstermBackend::new(io::stdout()))?;
terminal::enable_raw_mode()?;
crossterm::execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?;
loop {
    terminal.draw(|f| ui(f, &mut app))?;
    if let Event::Key(key) = event::read()? {
        if key.code == KeyCode::Char('q') { break; }
        // dispatch to app
    }
}
crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?;
terminal::disable_raw_mode()?;

SLT v0.20:

rust
fn main() -> std::io::Result<()> {
    let mut app = App::default();
    slt::run(|ui| {
        if ui.key('q') || ui.key_code(KeyCode::Esc) { ui.quit(); }
        render(ui, &mut app);
    })
}

slt::run enters alternate screen, enables raw mode, installs a panic hook restoring terminal state, and tears down on exit. Variants:

  • slt::run_with(RunConfig::default().mouse(true).theme(Theme::dark()), \|ui\| ...) — mouse capture, custom theme
  • slt::run_inline(rows, \|ui\| ...) — render below the prompt, no alt screen
  • slt::run_static(\|ui\| ...) — append-only scrollback (use with ui.static_log(...))
  • slt::run_async::<Message>(\|ui, messages\| ...) — tokio integration (async feature)
Widget mapping (top ratatui widgets → v0.20 SLT)

Every SLT method below is verified against src/context/widgets_*.rs.

ratatuiSLT v0.20
Block::default().borders(Borders::ALL).title("X")ui.bordered(Border::Rounded).title("X").col(|ui| ...)
Block::default().borders(Borders::ALL).border_type(BorderType::Rounded)ui.bordered(Border::Rounded).col(...)
Block::default().borders(Borders::TOP | Borders::BOTTOM)ui.bordered(Border::Single).border_sides(BorderSides::vertical()).col(...)
Paragraph::new("text")ui.text("text")
Paragraph::new("text").wrap(Wrap { trim: true })ui.text("text").wrap()
Paragraph::new("text").alignment(Alignment::Center)ui.text("text").text_center()
List::new(items).highlight_style(...)ui.list(&mut ListState) — items via ListState::set_items(...)
Table::new(rows).widths(&[...])ui.table(&mut TableState) — rows via TableState::set_rows(...), auto column widths
Tabs::new(titles).select(idx)ui.tabs(&mut TabsState) — labels via TabsState::new(["Files", "Settings"])
Gauge::default().percent(75).label("75%")ui.gauge(0.75).label("75%").width(24).color(Color::Green) (v0.20 builder)
Gauge::default().percent(75) (no label)ui.gauge(0.75) or ui.progress(0.75)
LineGauge::default().ratio(0.6).label("Memory")ui.line_gauge(0.6).label("Memory").filled('━').empty('─').width(48)
BarChart::default().data(&[("a", 1), ("b", 2)])ui.bar_chart(&[("a", 1.0), ("b", 2.0)], max_width) (values are f64)
Chart::new(datasets)ui.chart(width, height, |c| { c.line(&data).color(c).label("X"); })
Sparkline::default().data(&[1, 2, 3])ui.sparkline(&[1.0, 2.0, 3.0], width)
Span::styled("x", Style::default().fg(Color::Red))ui.text("x").fg(Color::Red)
Line::from(vec![span1, span2])ui.row(|ui| { ui.text("a"); ui.text("b"); }) or ui.line(|ui| ...)
Clear widgetusually unneeded — render at top-level or use ui.modal(|ui| ...) for overlays
v0.20-only widgets (no ratatui equivalent — use these on the migration target)

These are SLT additions; if your ratatui app hand-rolled them, replace with the built-in:

Patternv0.20 SLT
Scrollable code/log with line numbersui.scrollable_with_gutter(&mut scroll, GutterOpts::line_numbers(total, vp_h), |ui, abs| { ui.text(lines[abs]); })
Search highlights in scrollableScrollState::set_highlights(&[HighlightRange::line(7), HighlightRange::span(15, 3)]) + state.highlight_next/previous
Two-pane resizable layoutui.split_pane(&mut SplitPaneState::new(0.5), |ui| ..., |ui| ...) (horizontal) or ui.vsplit_pane(...) (vertical)
Breadcrumb with click feedbacklet r = ui.breadcrumb(&segs).separator(" › ").show(); if let Some(i) = r.clicked_segment { ... }
Tooltipsif ui.button("X").on_hover(ui, "Save").clicked { ... }
Animated valuelet alpha = ui.animate_value("fade", target, 30); (eased, 1-line)
Modal with focus trap (WCAG)ui.modal_with(ModalOptions { tab_trap: true }, |ui| ...) (plain ui.modal keeps Esc-friendly behavior)
Per-subtree theme overrideui.container().theme(custom_theme).col(|ui| ...)
Theme densityTheme::compact() / comfortable() / spacious() instead of manual spacing
Custom widgets — Layer 3

ratatui custom Widget trait impls don't translate directly. Two options:

Option A — function (preferred for one-off widgets):

rust
fn render_my_widget(ui: &mut Context, data: &MyData) { /* method calls */ }

Option B — implement SLT's Widget trait (for reusable libraries):

rust
struct Label<'a> { text: &'a str }
impl<'a> slt::Widget for Label<'a> {
    type Response = slt::Response;
    fn ui(&mut self, ui: &mut slt::Context) -> Self::Response {
        ui.register_focusable();
        ui.text(self.text).bold();
        slt::Response::default()
    }
}
ui.add(Label { text: "hello" });

Note: SLT's Widget trait is different from ratatui's. ratatui's Widget::render(self, area, buf) is a stateless paint into a Buffer. SLT's Widget::ui(&mut self, ui) is an immediate-mode call that returns a Response.

Layout mapping

ratatui:

rust
let chunks = Layout::default()
    .direction(Direction::Vertical)
    .constraints([Constraint::Length(3), Constraint::Min(0), Constraint::Length(1)])
    .split(area);
f.render_widget(header, chunks[0]);
f.render_widget(body, chunks[1]);
f.render_widget(footer, chunks[2]);

SLT:

rust
ui.col(|ui| {
    ui.container().h(3).col(|ui| { /* header */ });
    ui.container().fill().col(|ui| { /* body — fills remaining (v0.20 fill() == grow(1)) */ });
    ui.container().h(1).col(|ui| { /* footer */ });
});

Constraint translation:

ratatuiSLT
Constraint::Length(3).h(3) (col) or .w(3) (row)
Constraint::Min(0).fill() (v0.20) or .grow(1)
Constraint::Min(n).min_h(n).fill() (col) or .min_w(n).fill() (row)
Constraint::Max(n).max_h(n) or .max_w(n)
Constraint::Percentage(50).h_pct(50) or .w_pct(50)
Constraint::Ratio(1, 3).h_ratio(1, 3) or .w_ratio(1, 3)
.margin(1) on Layout.p(1) on parent container
.spacing(1) between chunks.gap(1) on parent row / col

Constraints value type also exposes the v0.20 WidthSpec set: Constraints::default().w_pct(50), .w_ratio(1, 3), .w_minmax(10, 30) — see examples/v020_widthspec.rs.

State mapping (re-exported via slt::*)
ratatui / your codeSLT
ListStateslt::ListState (set_items, set_filter, selected_item, visible_indices)
TableStateslt::TableState (set_rows, toggle_sort, sort_by, set_filter, next_page, prev_page)
TabsStateslt::TabsState (new(["Files", "Settings"]), selected_label)
ScrollbarState / manual offsetslt::ScrollState + ui.scrollable(&mut state).col(...) or ui.scrollable_with_gutter(...)
your own input: Stringslt::TextInputState::with_placeholder("…") (validators via add_validator)
your own textarea: Vec<String>slt::TextareaState::default().word_wrap(80)
your own selection setslt::SelectState, slt::RadioState, slt::MultiSelectState
tree viewslt::TreeState, slt::DirectoryTreeState::from_paths(...)
modal flagslt::Context::modal(|ui| ...) or modal_with(ModalOptions{tab_trap:true}, ...)
toast queueslt::ToastState::default() + ui.notify(level, msg)
log/history viewslt::RichLogState::new() (capped at 10000) or RichLogState::new_unbounded()
Threading state (CRITICAL for ratatui apps)

ratatui apps typically thread &mut App (or &App) through every render fn:

rust
fn render(f: &mut Frame, app: &mut App) {
    render_header(f, app);
    render_body(f, app);
    render_footer(f, app);
}

In SLT v0.19+ replace read-only sharing with provide/use_context:

rust
struct AppCtx { theme: slt::Theme, tick: u64, settings: Settings }

slt::run(|ui| {
    let ctx = AppCtx { theme: *ui.theme(), tick: ui.tick(), settings: app.settings.clone() };
    ui.provide(ctx, |ui| {
        render_header(ui);
        render_body(ui, &mut app.doc);   // writes still pass &mut explicitly
        render_footer(ui);
    });
});

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

Reserve explicit &mut parameters for writes (&mut MyDocState).

Event mapping
ratatuiSLT
KeyCode::Char('q') matchif ui.key('q') { ... }
KeyCode::Esc matchif ui.key_code(KeyCode::Esc) { ... }
KeyModifiers::CONTROL + Char('c')if ui.key_mod('c', KeyModifiers::CONTROL) { ... } (Ctrl-C is also auto-handled by slt::run)
MouseEventKind::Down(MouseButton::Left)if let Some((x, y)) = ui.mouse_down() { ... } or Response.clicked
MouseEventKind::Down(MouseButton::Right)Response.right_clicked (v0.20)
MouseEventKind::ScrollUpif ui.scroll_up() { ... }
Manual hit testif ui.button("X").clicked { ... } (Response.clicked is a public field)
Focus eventsResponse.gained_focus / Response.lost_focus (v0.20)
paste eventfor s in ui.pastes() { ... }
Sequence detectionif ui.key_seq("gg") { ... }

Modal-aware: ui.key(), ui.key_code(), ui.key_mod() are filtered when a modal is open. For global shortcuts that bypass modals, use ui.raw_key_code() / ui.raw_key_mod().

Consume: ui.consume_key(c) / ui.consume_key_code(code) mark events handled so child widgets don't re-process. Useful for global shortcuts taking precedence over text input.

Style mapping
ratatuiSLT
Style::default().fg(Color::Red)Style::new().fg(Color::Red)
Style::default().add_modifier(Modifier::BOLD)Style::new().bold()
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)Style::new().fg(Color::Red).bold()
Style::default().bg(Color::Blue)Style::new().bg(Color::Blue)
Per-text styling: Span::styled("x", style)Chain on the call: ui.text("x").fg(Color::Red).bold()
Modifier::DIM / ITALIC / UNDERLINED / REVERSED / CROSSED_OUT.dim() / .italic() / .underline() / .reversed() / .strikethrough()
Conditional stylingui.text("x").with_if(is_error, |t| { t.bold().fg(Color::Red); }) (v0.19+)

Style is Copy in both libraries — no clone needed.

Color mapping

Both libraries have:

  • 16 named colors: Red, Green, Blue, Yellow, Cyan, Magenta, Black, White, plus LightRed, LightGreen, etc.
  • 256-color: Color::Indexed(N) ↔ Color::Indexed(N)
  • 24-bit: Color::Rgb(r, g, b) ↔ Color::Rgb(r, g, b)
  • Color::Reset in both

Differences:

  • ratatui has Color::Gray and Color::DarkGray — SLT only has Color::DarkGray. Use Color::Indexed(8) (ANSI bright black) or Color::Rgb(128, 128, 128) for mid-gray.
  • For semantic colors prefer slt::palette::tailwind::* (BLUE.c500, RED.c700) — same 11-shade scale across 22 palettes, identical to Tailwind CSS.
Border type mapping
ratatui BorderTypeSLT Border
BorderType::PlainBorder::Single
BorderType::RoundedBorder::Rounded
BorderType::DoubleBorder::Double
BorderType::ThickBorder::Thick
BorderType::QuadrantInside / QuadrantOutsideno direct equivalent — use a dashed style or custom draw
ratatui BordersSLT BorderSides
Borders::ALLdefault — ui.bordered(Border::Rounded) draws all 4
Borders::TOPBorderSides::top()
Borders::BOTTOMBorderSides::bottom()
Borders::LEFT / RIGHTBorderSides::left() / right()
Borders::TOP | Borders::BOTTOMBorderSides::vertical()
Borders::LEFT | Borders::RIGHTBorderSides::horizontal()

Use via .border_sides(...): ui.bordered(Border::Single).border_sides(BorderSides::vertical()).col(...).

Theme

ratatui has no built-in theme. If you have ad-hoc Color::* constants, replace with slt::Theme and ui.color(ThemeColor::Primary) so themes can swap. v0.20 additions:

  • Theme::dark() / light() (base)
  • Theme::compact() / comfortable() / spacious() (density variants of dark)
  • Theme::dracula() / nord() / tokyo_night() / gruvbox_dark() / one_dark() / catppuccin() / solarized_dark() / solarized_light()
  • ThemeBuilder::builder_from(Theme::nord()) — extend a preset
  • ContainerBuilder::theme(custom) — per-subtree override

cursive → SLT mapping

cursive is callback-driven. SLT replaces both pattern and event loop with the imperative closure model.

cursiveSLT
Cursive::default().run()slt::run(|ui| { ... })
siv.add_global_callback(Key::Esc, |s| s.quit())if ui.key_code(KeyCode::Esc) { ui.quit(); }
TextView::new("hello")ui.text("hello")
EditView::new()ui.text_input(&mut TextInputState)
SelectView::new().item("a", 0).item("b", 1)ui.select(&mut SelectState)
Dialog::around(view).button("OK", |s| ...)ui.modal(|ui| { ui.text(...); if ui.button("OK").clicked { ... } })
LinearLayout::vertical()ui.col(|ui| ...)
LinearLayout::horizontal()ui.row(|ui| ...)
siv.add_layer(view)render at top-level; for overlays use ui.modal(...) / ui.overlay_at(anchor, |ui| ...)
Cursive::set_user_data(state)plain Rust variable outside the closure, captured by reference

Mental shift: cursive callbacks fire on input. SLT's closure runs every frame. State updates are visible immediately.

Show full SKILL.md (796 more words)Show less

textual (Python) → SLT mapping

textual is class-based with reactive state and CSS. SLT is functional with plain variables.

textualSLT
class App(App) with compose()slt::run(|ui| { ... }) closure
reactive attributesplain Rust variables outside the closure
CSS-like stylingThemeBuilder + per-widget chains (.fg(Color::Red).bold())
Static("hello")ui.text("hello")
Button("Click") + on_button_pressedif ui.button("Click").clicked { ... } inline
Input(placeholder="...")ui.text_input(&mut TextInputState::with_placeholder("..."))
DataTableui.table(&mut TableState)
ScrollableContainerui.scrollable(&mut ScrollState).col(|ui| ...) or scrollable_with_gutter
Container(...)ui.bordered(...).col(...) or ui.container().col(...)
compose() yielding child widgetsthe closure body — order is layout order
Async event handlersslt::run_async (async feature) returns a tokio::sync::mpsc::Sender
CSS animationsslt::Tween / slt::Spring / slt::Keyframes / ui.animate_value("id", target, ticks)

Common migration pitfalls

  • "I have a struct that implements Widget trait." — drop ratatui's. Either rewrite as fn render_my_widget(ui: &mut Context, data: &MyData), or implement slt::Widget (different shape — see Custom widgets section).
  • "My App has a draw(&mut self, frame: &mut Frame) method." — convert to fn render(ui: &mut Context, app: &mut App) and call from slt::run(\|ui\| render(ui, &mut app)).
  • "ratatui ListState lives across frames." — same in SLT. let mut list = ListState::new(); outside the closure; pass &mut list to ui.list(&mut list) each frame.
  • "I want raw crossterm events." — prefer ui.key() / ui.key_code() / ui.key_mod() / ui.mouse_down() / ui.scroll_up(). Raw ui.events() is for advanced cases (key release, paste handling, custom modifier matching). For modal-aware globals use ui.raw_key_code() / ui.raw_key_mod().
  • "I have heavy custom layout math (.split() arithmetic on Rect)." — try ui.row / ui.col + .fill / .h / .w / .h_pct / .w_pct / .align / .justify first. Flexbox handles 95% of cases. Drop to ui.container().draw(\|buf, rect\| { ... }) only when flexbox can't express it. The draw closure must be 'static (deferred execution).
  • "Constraint::Percentage(50) is everywhere." — .w_pct(50) (row child) or .h_pct(50) (col child). Both take u8.
  • "I use Layout::default().margin(1).split(area)." — .p(1) on the parent container.
  • "I check Response.rect immediately." — SLT layout runs after the closure. Frame 1 returns zero Rect. Guard with if ui.tick() > 0 { ... }.
  • "Borders::ALL." — SLT default. ui.bordered(Border::Rounded) draws all 4 sides. Subset via .border_sides(BorderSides::vertical()).
  • "I want Color::Gray." — doesn't exist. Use Color::Indexed(8) or Color::Rgb(128, 128, 128). Or pull from palette::tailwind::SLATE.c500.
  • "My ratatui app calls terminal.clear() between frames." — don't. SLT diffs the buffer and only emits changed cells. Manual clear breaks the diff and causes flicker.
  • "My panic hook restores raw mode." — drop it. slt::run installs one on first call.
  • "I'm threading &App through every render fn." — replace read-only sharing with ui.provide(ctx, \|ui\| ...) + ui.use_context::<AppCtx>(). Keep &mut for writes.
  • "My ratatui Gauge has no label." — use ui.gauge(0.75) (no label) or ui.progress(0.75) (display widget, returns &mut Self).

Migration workflow

  1. Inventory ratatui widgets used.

    sh
    grep -rn "render_widget\|f\.render_widget" src/
    grep -rn "Block::\|Paragraph::\|List::\|Table::\|Tabs::\|Gauge::\|BarChart::\|Chart::\|Sparkline::" src/

    Map each to an SLT method via the tables above.

  2. Convert the run loop. Replace Terminal::new setup + draw loop + disable_raw_mode teardown with one of slt::run, slt::run_with, slt::run_inline, or slt::run_async.

  3. Move state out of the draw closure. Most ratatui apps already do this. Keep the same shape — your App struct now feeds into one SLT closure.

  4. Replace layout splitters. Each Layout::default().constraints(...).split(area) becomes nested ui.row / ui.col + .fill / .h / .w / .h_pct / .w_pct / .align / .justify. .gap(n) instead of .spacing(n), .p(n) instead of .margin(n).

  5. Replace each f.render_widget(...) with the SLT method. Convert widget by widget. Verify any uncertain method via the v0.20 mapping table or grep src/context/widgets_*.rs.

  6. Adopt v0.20 builders. Where the old code hand-rolled gauges, breadcrumbs, scrollable-with-line-numbers, or split panes, use the v0.20 builders/opts directly. They handle hit-testing and accessibility.

  7. Replace event handling. Convert raw crossterm::event::read() matches to ui.key(), ui.key_code(), ui.key_mod(), ui.mouse_down(), ui.scroll_up(). Drop manual hit-testing in favor of Response.clicked / right_clicked / hovered / gained_focus / lost_focus.

  8. Replace &App threading with provide/use_context for read-only shared state.

  9. Run cargo check and fix one widget at a time. Add tests with slt::TestBackend::new(80, 24).render(\|ui\| ...) once a section compiles.

After everything compiles, run the full quality gate (project CLAUDE.md): cargo fmt -- --check, cargo check --all-features, cargo clippy --all-features -- -D warnings, cargo test --all-features, cargo check --examples --all-features.

Things SLT v0.20 doesn't have a direct equivalent for

Be honest with the user — these need workarounds:

  • ratatui Canvas braille drawing primitive. SLT has ui.canvas(width, height, \|cv\| { cv.line(...); cv.circle(...); }) but the API takes a CanvasContext closure, not a value-typed widget. Custom point/line drawing logic needs a small rewrite. See src/context/widgets_viz.rs:1289.
  • ratatui Wrap { trim: true } exact semantics. SLT wraps via container width and .wrap() on text; trim-leading-whitespace behavior isn't identical. Test wrap-heavy text manually.
  • cursive's deep view layering / multiple modal stacks. SLT supports ui.modal(...) / ui.modal_with(...) / ui.overlay_at(anchor, \|ui\| ...) but not arbitrary nested view managers. Most uses fold into if state.show_modal { ui.modal(\|ui\| ...) }.
  • textual's CSS hot reload. Themes are Rust values (no hot reload). Use cargo watch -x run for fast iteration.

If a feature genuinely doesn't map, tell the user — don't fake it.

References

  • .claude/skills/slt/SKILL.md — SLT authoring skill (use after migration is done).
  • .claude/skills/slt/REFERENCES.md — feature flags, v0.20 surface, doc pointers.
  • examples/v020_*.rs — runnable v0.20 demos (gauge, breadcrumb, scrollable_with_gutter, split_pane, etc.).
  • tests/v020_*.rs — regression tests showing canonical TestBackend + sequence patterns.
  • src/lib.rs — authoritative public re-exports.
  • src/context/widgets_*.rs — authoritative widget signatures.
  • ratatui repo: https://github.com/ratatui-org/ratatui
  • cursive repo: https://github.com/gyscos/cursive
  • textual repo: https://github.com/Textualize/textual

© subinium, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .claude/skills/slt-migration of subinium/SuperLightTUI.

Open the folder on GitHubat commit 15fd63a

Compare with similar skills

Slt Migration next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Slt Migration compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Slt Migration this skillsubinium/SuperLightTUI191—~6.2kAutomated safety check: PassMIT
Update V8 Versionopeninterpreter/openinterpreter69k2 repos~845Automated safety check: PassApache-2.0
Firecrawl Page Scrape Integrationfirecrawl/firecrawl190k1 repos~944Automated safety check: PassISC
Fory Releaseapache/fory4.6k—~2.9kAutomated safety check: PassApache-2.0
Apple Container Test RunnerRustPython/RustPython22k—~467Automated safety check: PassMIT
Release Skillsnexmoe/eve4213 repos~3.3kAutomated safety check: PassNone

Similar skills

  • Update V8 Version

    openinterpreter/openinterpreter

    Bumps the pinned v8 and rusty_v8 versions in Codex, validates the release-candidate path with the v8-canary check, and traces failures to upstream build changes.

    69k GitHub starsUsed in 2 repos~845 tokens
    DevOps & CloudAuto-check passed
  • Adds Firecrawl's /scrape endpoint to application code to pull markdown, HTML, links, screenshots or structured data from a single known URL.

    190k GitHub starsUsed in 1 repo~944 tokens
    Data & AnalyticsAuto-check passed
  • Fory Release

    apache/fory

    Prepare an Apache Fory release candidate from a clean release branch, including the version bump, RC tag, JVM staging, ASF source artifacts, SVN upload, and vote email.

    4.6k GitHub stars~2.9k tokensUpdated yesterday
    Auto-check passed
  • Apple Container Test Runner

    RustPython/RustPython

    Runs RustPython tests inside a Linux container built with Apple's container CLI, so macOS users can compare Linux results with their local ones.

    22k GitHub stars~467 tokensUpdated yesterday
    Testing & QAAuto-check passed
  • Release Skills

    nexmoe/eve

    Universal release workflow. An agent skill from nexmoe/eve.

    421 GitHub starsUsed in 3 repos~3.3k tokens
    DevelopmentAuto-check passed
  • RustPython C-API Expansion

    RustPython/RustPython

    Implements missing CPython C-API functions in RustPython's crates/capi, mapping each header to its module with the pyo3-ffi header split.

    22k GitHub stars~831 tokensUpdated yesterday
    DevelopmentAuto-check passed

More from subinium/SuperLightTUI

  • Slt

    subinium/SuperLightTUI

    Build Rust TUI apps with SuperLightTUI v0.23 (immediate-mode terminal UI).

    191 GitHub stars~5.1k tokensUpdated 29 days ago
    Auto-check passed
  • Slt

    subinium/SuperLightTUI

    Build Rust TUI apps with SuperLightTUI v0.20 (immediate-mode terminal UI).

    191 GitHub stars~5k tokensUpdated 29 days ago
    Auto-check passed
  • Slt Migration

    subinium/SuperLightTUI

    Migrate Rust TUIs from ratatui (or cursive, Python textual) to SuperLightTUI v0.23.

    191 GitHub stars~6.2k tokensUpdated 29 days ago
    Auto-check passed

Works with

Questions about Slt Migration

What does Slt Migration do?

Migrate Rust TUIs from ratatui (or cursive, Python textual) to SuperLightTUI v0.20. Slt Migration is an agent skill from subinium/SuperLightTUI.20.

When should I use Slt Migration?

Slt Migration fits situations like: porting an existing TUI codebase to SLT; the user asks how do I do X from ratatui in SLT.

How do I install Slt Migration in Claude Code?

Run `npx skills add subinium/SuperLightTUI --skill slt-migration -a claude-code`. Or copy the skill folder (.claude/skills/slt-migration in subinium/SuperLightTUI) into .claude/skills/slt-migration in your project. Claude Code loads it when a task matches its description.

How do I install Slt Migration in Codex?

Run `npx skills add subinium/SuperLightTUI --skill slt-migration -a codex`. Or copy the skill folder (.claude/skills/slt-migration in subinium/SuperLightTUI) into .agents/skills/slt-migration in your project. Codex loads it when a task matches its description.

Can I use Slt Migration in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add subinium/SuperLightTUI --skill slt-migration -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/slt-migration, .gemini/skills/slt-migration, .github/skills/slt-migration and .opencode/skills/slt-migration in your project.

What does Slt Migration need to run?

Going by SKILL.md and its folder, Slt Migration needs the command-line tools its instructions call (cargo). Our summary lists: Python 3.

Does Slt Migration access the network?

SKILL.md names 1 domain. As links in the text: github.com. This is read from the text; nothing was executed.

Is Slt Migration safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Slt Migration use?

Slt Migration is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Slt Migration use?

About 6.2k tokens (SKILL.md is roughly 25k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Slt Migration?

Skills that share tags, products or a category with Slt Migration: Update V8 Version (openinterpreter/openinterpreter, 69k stars), Firecrawl Page Scrape Integration (firecrawl/firecrawl, 190k stars), Fory Release (apache/fory, 4.6k stars) and Apple Container Test Runner (RustPython/RustPython, 22k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Slt Migration?

subinium (a GitHub user) maintains it in subinium/SuperLightTUI, which has 191 GitHub stars. The repository holds 4 skills in this directory. The repository was last updated on September 9, 2026.

Source: subinium/SuperLightTUI on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.