-
Notifications
You must be signed in to change notification settings - Fork 4
Description
Problem Statement
Oxicord currently renders image attachments as text links (📎 filename.jpg). For a TUI client targeting power users, this is a significant UX gap compared to modern Discord clients that display images inline.
The roadmap already identifies this as a goal:
- Image previews (Ratatui-image integration) (Monitoring for performance impact)
- Image modal viewer ('o' binding)
Proposed Solution
Integrate ratatui-image to render images inline within the message pane. The library handles terminal protocol detection (Sixel, Kitty, iTerm2) and provides StatefulImage widgets that adapt to available render area.
User Experience Vision
- Inline thumbnails: Small previews (configurable max height, e.g., 6-10 rows) render directly in the message flow
- Modal viewer: Pressing
oon a message with attachments opens a fullscreen modal for the selected image - Graceful degradation: Terminals without graphics protocol support fall back to halfblocks or text placeholders
Technical Considerations
Performance Pitfalls (Critical)
The ratatui-image docs and examples highlight several issues that will cause problems if not addressed:
| Problem | Symptom | Solution |
|---|---|---|
| Synchronous encoding in render loop | UI freezes during image load/resize | Offload resize+encode to worker thread (see examples/thread.rs) |
| Unbounded image cache | RAM bloat (30MB to 600MB observed in similar projects) | LRU cache with configurable max entries |
| Re-encoding on every frame | CPU spikes, scroll jank | Cache StatefulProtocol per attachment URL |
| Pixel/cell mismatch | Scroll artifacts, image tearing | Use Picker::from_query_stdio() for accurate font-size detection |
Architecture Requirements
Following Oxicord's Clean Architecture:
domain/
entities/attachment.rs # Already has is_image() - no changes needed
application/
services/image_service.rs # NEW: Manages image fetching, caching, protocol state
infrastructure/
image/ # NEW: HTTP fetching, disk cache (optional)
presentation/
widgets/image_widget.rs # NEW: Wrapper around StatefulImage
ui/image_modal.rs # NEW: Fullscreen image viewer
Threading Model
Oxicord already uses tokio. The examples/tokio.rs pattern fits well:
// Simplified concept - actual implementation will differ
let (tx, rx) = unbounded_channel();
let protocol = ThreadProtocol::new(tx, Some(picker.new_resize_protocol(dyn_img)));
// In event loop:
select! {
Some(request) = rx.recv() => {
protocol.update_resized_protocol(request.resize_encode()?);
}
}The key insight: never call resize_encode() in the render path. Always receive completed protocols from a background task.
Implementation Checklist
Phase 1: Foundation
- Add
ratatui-imagedependency withcrosstermandtokiofeatures - Create
ImageServicein application layer for cache management - Implement
Pickerinitialization in app startup (query terminal capabilities once) - Add LRU cache for
StatefulProtocolkeyed by attachment URL
Phase 2: Inline Rendering
- Modify
message_pane.rsto detect image attachments viaAttachment::is_image() - Calculate inline thumbnail dimensions (respect terminal width, configurable max height)
- Render
StatefulImagewidget within message scroll view - Handle loading state (show placeholder while fetching/encoding)
Phase 3: Modal Viewer
- Add
image_modal.rswidget for fullscreen display - Bind
okey in message pane to open modal (keybinding already defined:Action::OpenAttachments) - Implement arrow key navigation for messages with multiple attachments
- Add
Escto close modal
Phase 4: Polish
- Add configuration options (enable/disable, max cache size, thumbnail height)
- Implement fallback rendering for unsupported terminals
- Add loading/error indicators
- Test on Sixel (xterm, foot), Kitty, and iTerm2 terminals
Resources
- ratatui-image README - Protocol support matrix, widget docs
- examples/thread.rs - Threading pattern with std channels
- examples/tokio.rs - Async pattern (recommended for Oxicord)
- Terminal compatibility matrix - Which protocols work where
Acceptance Criteria
- Images from message attachments render inline without blocking the UI thread
- Scrolling through messages with images does not cause lag or artifacts
- Memory usage remains stable when scrolling through channels with many images
-
okey opens a fullscreen modal for the selected attachment - Terminals without graphics protocol support show a text fallback (e.g.,
[Image: filename.jpg]) - Works on at least: Kitty, Foot (Sixel), and WezTerm (iTerm2)
Open Questions
- Thumbnail sizing: Should thumbnails have a fixed max height (e.g., 8 rows), or scale based on terminal size?
- Caching strategy: In-memory only, or persist decoded images to disk (
~/.cache/oxicord/images/)? - Animated GIFs: Attempt animation (CPU-intensive) or render first frame only?
- Proxy/CDN images: Discord uses
cdn.discordapp.comURLs. Any concerns about rate limiting when fetching many images?
This is a non-trivial feature due to the threading requirements and cache management. However, the existing codebase structure (Clean Architecture, tokio runtime) and well-documented examples from ratatui-image make it approachable.
Suggested starting point: Study examples/tokio.rs thoroughly, then implement a minimal proof-of-concept that renders a single hardcoded image in the message pane before tackling the full integration.
Note on current status:
I've attached a video of my initial POC below. It works, but it's not quite perfect yet—you'll notice some of the jank/artifacts we need to solve before this is ready for prime time.