mcpField
R
ratatui-tui

ratatui-tui

github.com/blacktop開発17737 回インストール1 週間前に更新
リポジトリ

ratatui-tui について

ratatui-tuiは、RatatuiでターミナルUIを開発する人向けのスキルです。プロジェクトテンプレートや実装パターン、リリース前の確認項目を用意し、非同期イベント、画像、ローディングアニメーションも扱います。

主な機能

  1. 01規模に応じたテンプレート選択
  2. 02TEAアーキテクチャとコンポーネントパターン
  3. 03非同期イベント処理パターン
  4. 04画像とローディングアニメーションの統合
  5. 05ターミナル安全性とリリース確認
  6. 06マルチエージェントTUIレビューのワークフロー

活用シーン

  1. 01Ratatuiのデモや単画面ツールの開始
  2. 02非同期タスクを含むTUIの開発
  3. 03複数ビューのコンポーネントアプリ構成
  4. 04リリース前のTUIプロジェクト確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/blacktop/dotfiles --skill ratatui-tui

インストール後、Claude Code で/ratatui-tuiを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/templates/async-app/
Cargo.toml
assets/templates/async-app/src/
main.rs
assets/templates/component-app/
Cargo.toml
assets/templates/component-app/src/
action.rs
app.rs
config.rs
event.rs
logging.rs
main.rs
tui.rs
ui.rs
assets/templates/hello-world/
Cargo.toml
assets/templates/hello-world/src/
main.rs
assets/templates/simple-app/
Cargo.toml
assets/templates/simple-app/src/
main.rs
references/
architecture-patterns.md
async-patterns.md
image-integration.md
style-guide.md
workflows/
tui-review.js

詳細

Ratatui TUI 開発

クイックスタート

  1. テンプレートをコピーしてプロジェクトに追加します。

    cp -r ~/.agents/skills/ratatui-tui/assets/templates/<template>/* .
    

    または、公式テンプレートリポジトリから生成します。

    cargo install --locked cargo-generate
    cargo generate ratatui/templates
    
  2. 実行します。

    cargo run
    

バージョンに関する注意事項(0.30.x)

0.30.x(MSRV 1.88、edition 2024)が対象です。最新のパッチリリースを確認するには、cargo info ratatui を実行してください。

  • モジュール式ワークスペース:アプリは引き続き ratatui に依存します。ウィジェットのライブラリは、API の安定性を高め、依存関係を減らすために ratatui-core に依存する必要があります。
  • ratatui::run(|terminal| ...):ターミナルを初期化し、ターミナルを復元するパニックフックをインストールして、クロージャーを実行し、終了時にターミナルを復元します。
  • Block::shadow(...)(0.30.1 で追加):ブロックやポップアップにドロップシャドウを付けます。
  • 0.29 からの破壊的変更:block::Title を削除、layout::Alignment を HorizontalAlignment に名称変更、Flex::SpaceAround が flexbox の仕様に準拠(以前の動作には Flex::SpaceEvenly を使用)、Marker が網羅性を強制しない型に変更。
  • パフォーマンス:default-features を無効にすると layout-cache も無効になります。明示的に再度有効にするか、レイアウトのパフォーマンスが大幅に低下します。

テンプレートの選択

複雑さテンプレート用途
最小hello-world学習、簡単なデモ
シンプルsimple-app単一画面のアプリ、ツール
非同期async-appバックグラウンドタスク、ネットワーク
フルcomponent-app複数ビュー、設定、ログ記録

選択フロー:

  • 非同期処理やネットワークが必要ですか? → async-app
  • 複数の画面やコンポーネントがありますか? → component-app
  • シンプルなツールだけが必要ですか? → simple-app
  • ratatui を学習中ですか? → hello-world

プロジェクトのセットアップ

最小限の Cargo.toml
[package]
name = "my-tui"
version = "0.1.0"
edition = "2024"

[dependencies]
ratatui = "0.30"
crossterm = "0.29"
color-eyre = "0.6"
完全な依存関係(component-app)
[dependencies]
ratatui = "0.30"
crossterm = { version = "0.29", features = ["event-stream"] }
color-eyre = "0.6"
tokio = { version = "1", features = ["full"] }
futures = "0.3"
clap = { version = "4", features = ["derive"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
serde = { version = "1", features = ["derive"] }
config = "0.15"
dirs = "6"

# Optional: image support (default features include chafa-dyn, which overrides chafa-static)
ratatui-image = { version = "11", default-features = false, features = ["crossterm", "image-defaults", "chafa-static"] }

# Optional: shimmer text animation
tui-shimmer = "0.1"
リリースプロファイル
[profile.release]
lto = true
codegen-units = 1
panic = "abort"
strip = true

コアループ:TEA(The Elm Architecture)

Model → Message → Update → View
  ↑                         |
  └─────────────────────────┘
struct App {
    counter: i32,
    should_quit: bool,
}

enum Message {
    Increment,
    Decrement,
    Quit,
}

impl App {
    fn update(&mut self, msg: Message) {
        match msg {
            Message::Increment => self.counter += 1,
            Message::Decrement => self.counter -= 1,
            Message::Quit => self.should_quit = true,
        }
    }

    fn view(&self, frame: &mut Frame) {
        let text = format!("Counter: {}", self.counter);
        frame.render_widget(Paragraph::new(text), frame.area());
    }
}

スタイリングのルール

Stylize トレイトのヘルパーを使用します。

use ratatui::style::Stylize;

// Good
"text".bold()
"text".dim()
"text".cyan()
"text".on_dark_gray()
"text".bold().cyan()

// Avoid
Style::default().fg(Color::White)  // hardcoded white
Style::default().fg(Color::Black)  // hardcoded black
Style::new().add_modifier(Modifier::BOLD)  // verbose

カラーパレット:

  • プライマリ:.cyan()、.green()
  • エラー:.red()
  • 警告:.yellow()(控えめに使用)
  • 控えめな表示:.dim()、.dark_gray()
  • アクセント:.magenta()

テキストの折り返し:

use textwrap::wrap;
use ratatui::text::Line;

let wrapped: Vec<Line> = wrap(&long_text, width as usize)
    .into_iter()
    .map(|cow| Line::from(cow.into_owned()))
    .collect();

参照:references/style-guide.md

ウィジェットのパターン

StatefulWidget
struct MyList {
    items: Vec<String>,
}

struct MyListState {
    selected: usize,
}

impl StatefulWidget for MyList {
    type State = MyListState;

    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
        // render with state.selected
    }
}

// Usage
frame.render_stateful_widget(my_list, area, &mut state);
レイアウト
let [header, main, footer] = Layout::vertical([
    Constraint::Length(1),
    Constraint::Fill(1),
    Constraint::Length(1),
]).areas(frame.area());

let [left, right] = Layout::horizontal([
    Constraint::Percentage(30),
    Constraint::Fill(1),
]).areas(main);
組み込みの状態型
  • ListState - List ウィジェット用
  • TableState - Table ウィジェット用
  • ScrollbarState - Scrollbar 用

参照:references/architecture-patterns.md

非同期イベント処理

use crossterm::event::{EventStream, Event, KeyCode};
use futures::StreamExt;
use tokio::select;

async fn run(mut app: App) -> Result<()> {
    let mut events = EventStream::new();

    loop {
        // Render
        terminal.draw(|f| app.view(f))?;

        // Handle events
        select! {
            Some(Ok(event)) = events.next() => {
                if let Event::Key(key) = event {
                    match key.code {
                        KeyCode::Char('q') => break,
                        KeyCode::Up => app.update(Message::Up),
                        KeyCode::Down => app.update(Message::Down),
                        _ => {}
                    }
                }
            }
            // Add other channels here (background tasks, timers)
        }

        if app.should_quit {
            break;
        }
    }
    Ok(())
}

参照:references/async-patterns.md

画像の統合

ratatui-image 11.x が対象です。メジャーバージョン間で API が変更されるため、古いスニペットを再利用する前に docs.rs を確認してください。

use ratatui::layout::Size;
use ratatui_image::{picker::Picker, protocol::Protocol, Image, Resize};

// Query protocol and font size once at startup; keep the picker on the app
let picker = Picker::from_query_stdio()?;

// Encode once, fitted inside a box of cells; do this outside `draw`
// (on a worker thread for large images)
let dyn_img = image::open("photo.png")?;
let protocol: Protocol = picker.new_protocol(dyn_img, Size::new(40, 20), Resize::Fit(None))?;

// In render, drawing a pre-encoded Protocol is cheap
frame.render_widget(Image::new(&protocol), area);

重要なポイント:

  • ポータブルなバイナリには chafa-static を default-features = false とともに使用します。両方が有効な場合は、デフォルトの chafa-dyn が優先されます。
  • プロトコルの問い合わせはフレームごとではなく、1 回だけ行います。
  • Image はステートレスでサイズ固定です。エンコードはすべて new_protocol で行われます。
  • StatefulImage はレンダリング領域に合わせて再調整され、レンダリング時にエンコードを行うため、処理をブロックします。ratatui_image::thread::ThreadProtocol を通じて動作させてください(クレートの examples/thread.rs と examples/tokio.rs を参照)。

参照:references/image-integration.md

シマー/ローディングアニメーション

tui-shimmer はテキスト上にハイライトを走らせます。これはコーディングエージェントの TUI で使われる「Loading…」/「Thinking…」の効果です。

use ratatui::style::Style;
use ratatui::text::Line;
use tui_shimmer::{shimmer_spans_with_style, shimmer_spans_with_style_at_phase};

// Time-driven (call every frame; re-render on a tick to animate)
let spans = shimmer_spans_with_style("Loading...", Style::new().cyan());
frame.render_widget(Line::from(spans), area);

// Deterministic: drive phase (0.0..1.0) from app state — testable, pausable
let phase = (self.start.elapsed().as_secs_f32() / 2.0) % 1.0;
let spans = shimmer_spans_with_style_at_phase("Working...", Style::new().cyan(), phase);

重要なポイント:

  • アニメーションには再描画が必要です。イベントループにティックイベント(約80~120ms)を追加します(tokio::time::interval を使った select!、またはタイムアウト付きの event::poll)。
  • Model にフェーズを保存する _at_phase バリアントを推奨します。これにより、レンダリングが純粋になり、アニメーションをテストしやすくなります。
  • 制限のあるターミナルでは、自動的にフォールバックするトゥルーカラーを使用します。
  • API は 1.0 まで実験的です。バージョンを固定し、マイナーバージョンの更新を確認してください。

エラー処理

ratatui::run() / ratatui::init() は、パニック時にパニックを発生させる前にターミナルを復元するパニックフックをインストールします。自分で実装する必要はありません。エラーレポートが出力される前にターミナルが復元されるよう、color-eyre を最初にインストールしてください:

use color_eyre::eyre::Result;

fn main() -> Result<()> {
    color_eyre::install()?;        // eyre hooks before terminal init
    // App::run is the app's own main loop (see templates), not a ratatui API
    let result = ratatui::run(|terminal| App::default().run(terminal));
    Ok(result?)
}

ratatui::init() を使わずに Terminal/Backend を手動で構築する場合に限り、手動でパニックフックを記述してください。

エラーの伝播:

// Use ? for recoverable errors
let file = std::fs::read_to_string(path)?;

// Use color_eyre context
let config = load_config()
    .wrap_err("Failed to load configuration")?;

リリースビルド

cargo build --release

バイナリは target/release/<name> にあります。

サイズの最適化 — バイナリサイズを速度より重視する場合は、上記の Release Profile ブロックを置き換えます:

[profile.release]
lto = true
codegen-units = 1
panic = "abort"
strip = true
opt-level = "z"  # size over speed

テンプレートの概要

hello-world(約25行)

ratatui::run() を使った最小限の ratatui デモです。

simple-app(約80行)

同期イベントループ、App 構造体、基本的な描画を含みます。

async-app(約120行)

Tokio ランタイム、EventStream、select! パターンを含みます。

component-app(約300行)

完全なモジュール構成です:

  • main.rs - エントリーポイント
  • app.rs - App の状態と更新ロジック
  • event.rs - イベント処理
  • ui.rs - 描画
  • action.rs - Action 列挙型
  • tui.rs - ターミナルのセットアップ
  • config.rs - dirs を使った設定
  • logging.rs - tracing のセットアップ

よく使われるパターン

中央配置のポップアップ
fn centered_rect(percent_x: u16, percent_y: u16, area: Rect) -> Rect {
    let [_, center, _] = Layout::vertical([
        Constraint::Percentage((100 - percent_y) / 2),
        Constraint::Percentage(percent_y),
        Constraint::Percentage((100 - percent_y) / 2),
    ]).areas(area);

    let [_, center, _] = Layout::horizontal([
        Constraint::Percentage((100 - percent_x) / 2),
        Constraint::Percentage(percent_x),
        Constraint::Percentage((100 - percent_x) / 2),
    ]).areas(center);

    center
}

ドロップシャドウ付き(0.30.1+):

use ratatui::layout::Offset;
use ratatui::widgets::{Block, Shadow};

let popup = Block::bordered()
    .title("Confirm")
    .shadow(Shadow::dark_shade().offset(Offset::new(2, 1)));
キーバインドの表示
let help = Line::from(vec![
    " q ".bold().cyan(),
    "quit ".dim(),
    " ↑↓ ".bold().cyan(),
    "navigate ".dim(),
    " Enter ".bold().cyan(),
    "select ".dim(),
]);
ステータスバー
let status = Line::from(vec![
    " MODE ".bold().on_cyan(),
    format!(" {} items ", count).dim().into(),
]);

マルチエージェント TUI レビューワークフロー

workflows/tui-review.js は、Claude Code の Workflow ツール向けの動的ワークフローテンプレートです。TUI の各観点(TEA アーキテクチャ、ターミナルの安全性、スタイル、イベント処理、描画パフォーマンス)ごとにレビュー担当エージェントを起動し、報告前に各指摘を反証的に検証するため、確認済みの問題だけが残ります。Workflow ツールがないエージェント(Codex)では、スクリプトを使わず、代わりにこの5つの観点を手動レビュー用チェックリストとして適用してください。

これはそのまま実行するスクリプトではなく、テンプレートとして扱ってください。対象のパス、観点、重大度のしきい値をコードベースに合わせて調整します。複数のエージェントを起動するため、ユーザーから TUI のレビューまたはリリース前チェックを依頼された場合に実行してください:

Workflow({
  scriptPath: "~/.agents/skills/ratatui-tui/workflows/tui-review.js",
  args: { path: "src/" },
})

または、次のように依頼してください:「ratatui-tui スキルの TUI レビューワークフローを src/ に対して実行してください。」

チェックリスト

リリース前に確認してください:

  • cargo fmt
  • cargo clippy --all-features が問題なく完了する
  • テスト以外で unwrap() を使っていない
  • すべての終了経路でターミナルが復元される(ratatui::run() または init/restore)
  • cargo build --release が成功する
  • 対象のターミナルでテストする

コメント