tui: replace custom image renderer with ratatui-image #117

Closed
opened 2026-04-12 18:53:06 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As a user, I want images to render correctly in any graphics-capable terminal (Kitty, Sixel, iTerm2, or halfblock fallback), so that gallery detail, generate preview, and entity reference images work reliably.

Problem

The custom Kitty renderer (image/kitty.rs) writes escape sequences to a buffer flushed after terminal.draw(). This causes positioning issues — ratatui has already flushed its buffer and moved the cursor, so Kitty cursor positioning collides with ratatui's state. Images don't appear despite the renderer being detected.

Solution

Replace the custom ImageRenderer trait + kitty.rs/sixel.rs/chafa.rs/halfblock.rs backends with ratatui-image, which renders images as native ratatui widgets. This eliminates the stdout synchronization problem entirely.

Acceptance criteria

Core

  • Add ratatui-image as a dependency
  • Replace ImageRenderer trait with ratatui-image's Picker + StatefulProtocol
  • AppCtx holds a Picker instead of Arc<Mutex<Box<dyn ImageRenderer>>>
  • Each screen that renders images holds a StatefulProtocol in its state
  • Gallery detail view renders images via StatefulImage widget inside frame.render_stateful_widget()
  • Generate preview pane renders via the same mechanism
  • Entity reference image renders via the same mechanism
  • Remove flush_pending() from the render loop — no longer needed
  • Remove image/kitty.rs, image/sixel.rs, image/chafa.rs, image/halfblock.rs
  • Remove image/detect.rsratatui-image::Picker handles protocol detection

Protocol support

  • Kitty graphics protocol works
  • Sixel works (xterm, foot, mlterm)
  • iTerm2 protocol works (iTerm2, WezTerm, Ghostty)
  • Halfblock fallback works in terminals without graphics support
  • tui.toml image_protocol override still respected (map to ratatui-image protocol selection)

Image handling

  • JPEG/WebP/PNG all render (ratatui-image + image crate handle format detection)
  • Images resize to fit the cell rect
  • No flicker on re-render (stateful widget handles caching)
  • Image clears when navigating away from detail view

Tests

  • Update MockRenderer in testing.rs to match new API
  • Integration tests still pass
  • Unit tests for image-related screens updated

Out of scope

  • Mouse drag-to-pan in detail view (charles/loom#112 follow-up)
  • Image zoom (requires pixel-level control beyond widget sizing)

References

  • ratatui-image repo: https://github.com/ratatui/ratatui-image
  • Current renderer: crates/loom-tui/src/image/ (to be replaced)
  • Gallery detail: crates/loom-tui/src/screens/gallery.rs render_detail()
  • Generate preview: crates/loom-tui/src/screens/generate.rs
  • AppCtx renderer field: crates/loom-tui/src/app.rs
## User story As a user, I want images to render correctly in any graphics-capable terminal (Kitty, Sixel, iTerm2, or halfblock fallback), so that gallery detail, generate preview, and entity reference images work reliably. ## Problem The custom Kitty renderer (`image/kitty.rs`) writes escape sequences to a buffer flushed after `terminal.draw()`. This causes positioning issues — ratatui has already flushed its buffer and moved the cursor, so Kitty cursor positioning collides with ratatui's state. Images don't appear despite the renderer being detected. ## Solution Replace the custom `ImageRenderer` trait + `kitty.rs`/`sixel.rs`/`chafa.rs`/`halfblock.rs` backends with [`ratatui-image`](https://github.com/ratatui/ratatui-image), which renders images as native ratatui widgets. This eliminates the stdout synchronization problem entirely. ## Acceptance criteria ### Core - [ ] Add `ratatui-image` as a dependency - [ ] Replace `ImageRenderer` trait with `ratatui-image`'s `Picker` + `StatefulProtocol` - [ ] `AppCtx` holds a `Picker` instead of `Arc<Mutex<Box<dyn ImageRenderer>>>` - [ ] Each screen that renders images holds a `StatefulProtocol` in its state - [ ] Gallery detail view renders images via `StatefulImage` widget inside `frame.render_stateful_widget()` - [ ] Generate preview pane renders via the same mechanism - [ ] Entity reference image renders via the same mechanism - [ ] Remove `flush_pending()` from the render loop — no longer needed - [ ] Remove `image/kitty.rs`, `image/sixel.rs`, `image/chafa.rs`, `image/halfblock.rs` - [ ] Remove `image/detect.rs` — `ratatui-image::Picker` handles protocol detection ### Protocol support - [ ] Kitty graphics protocol works - [ ] Sixel works (xterm, foot, mlterm) - [ ] iTerm2 protocol works (iTerm2, WezTerm, Ghostty) - [ ] Halfblock fallback works in terminals without graphics support - [ ] `tui.toml` `image_protocol` override still respected (map to `ratatui-image` protocol selection) ### Image handling - [ ] JPEG/WebP/PNG all render (ratatui-image + `image` crate handle format detection) - [ ] Images resize to fit the cell rect - [ ] No flicker on re-render (stateful widget handles caching) - [ ] Image clears when navigating away from detail view ### Tests - [ ] Update `MockRenderer` in `testing.rs` to match new API - [ ] Integration tests still pass - [ ] Unit tests for image-related screens updated ## Out of scope - Mouse drag-to-pan in detail view (charles/loom#112 follow-up) - Image zoom (requires pixel-level control beyond widget sizing) ## References - `ratatui-image` repo: https://github.com/ratatui/ratatui-image - Current renderer: `crates/loom-tui/src/image/` (to be replaced) - Gallery detail: `crates/loom-tui/src/screens/gallery.rs` `render_detail()` - Generate preview: `crates/loom-tui/src/screens/generate.rs` - AppCtx renderer field: `crates/loom-tui/src/app.rs`
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/loom#117
No description provided.