Gemini 4 and Rust TUIs

Ratatui with a bit of Argon

Posted by Isaac on Tuesday, October 13, 2026

We last looked at Ratatui and VCR back in July. I recently had a need for VCR and it reminded me that Ratatui was something I demo’ed but never really tried to use.

With Google Gemini 4 Argon releasing today, I thought this might be a fine opportunity to test it with some new and rust based.

I’m not just going to make a TUI that does nothing. Instead, I’ll be pairing this with a demo I’ll be giving down in Rochester MN for the Code and Connect night.

Ratatui

I’ll clone down the repo

(base) builder@LuiGi:~/Workspaces$ git clone https://github.com/ratatui/ratatui.git
Cloning into 'ratatui'...
remote: Enumerating objects: 20599, done.
remote: Counting objects: 100% (164/164), done.
remote: Compressing objects: 100% (119/119), done.
remote: Total 20599 (delta 123), reused 45 (delta 45), pack-reused 20435 (from 3)
Receiving objects: 100% (20599/20599), 137.81 MiB | 635.00 KiB/s, done.
Resolving deltas: 100% (14750/14750), done.

It’s always good practice to keep one’s Rust environment up to date, so I did that next with

$ rustup update stable
$ rustup default stable

Once I saw it was updated

(base) builder@LuiGi:~/Workspaces/ratatui$ rustup update stable
info: syncing channel updates for stable-x86_64-unknown-linux-gnu
info: latest update on 2026-10-01 for version 1.99.0 (b940084d7 2026-09-28)
info: removing previous version of component cargo
info: removing previous version of component clippy
info: removing previous version of component rust-docs
info: removing previous version of component rust-std
info: removing previous version of component rustc
info: removing previous version of component rustfmt
info: downloading 6 components
        cargo installed                       10.45 MiB
       clippy installed                        4.99 MiB
    rust-docs installed                       23.18 MiB
     rust-std installed                       30.13 MiB
        rustc installed                       80.15 MiB
      rustfmt installed                        2.37 MiB
  stable-x86_64-unknown-linux-gnu updated - rustc 1.99.0 (b940084d7 2026-09-28) (from rustc 1.97.1 (8bab26f4f 2026-07-14))

info: checking for self-update (current version: 1.29.0)
info: downloading self-update (new version: 1.29.1)
(base) builder@LuiGi:~/Workspaces/ratatui$ rustup default stable
info: using existing install for stable-x86_64-unknown-linux-gnu
info: default toolchain set to stable-x86_64-unknown-linux-gnu

  stable-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0 (b940084d7 2026-09-28)

info: note that the toolchain 'stable-x86_64-unknown-linux-gnu' is currently in use (overridden by '/home/builder/Workspaces/ratatui/rust-toolchain.toml')

I started by running cargo run -p demo2

It downloaded and compiled and I was given a very colourful app, albeit aligned in the upper left of my ghostty terminal

/img/2026-10-ratatui-01.png

Same with the Gnome terminal so it must just be how that TUI is designed

/img/2026-10-ratatui-02.png

Chart, however was full size demo so I was confident the framework worked

Same with “popup”

Gemini 4 with Argon in Agy

Let’s give it a nice start with Rust. This is something I’ve had challenges with Gemini 3.8 flash in the past

Let’s give it a test.

I would argue that is exactly what i was desiring.

The Rust code is pretty clean

(base) builder@LuiGi:~/Workspaces/ratatui$ cat examples/apps/medical-file-analyzer/src/main.rs
//! A Ratatui example based on `popup` that displays a purple "Medical File Analyizer" page
//! with an interactive file selection popup and a "Load" confirmation action.

use std::env;
use std::fs;
use std::io::stdout;
use std::path::{Path, PathBuf};

use color_eyre::Result;
use crossterm::event::{
    self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, MouseButton,
    MouseEvent, MouseEventKind,
};
use crossterm::execute;
use ratatui::layout::{Alignment, Constraint, Layout, Position, Rect};
use ratatui::style::{Color, Modifier, Style, Stylize};
use ratatui::text::{Line, Text};
use ratatui::widgets::{
    Block, Clear, HighlightSpacing, List, ListItem, ListState, Paragraph, Wrap,
};
use ratatui::{DefaultTerminal, Frame};

fn main() -> Result<()> {
    color_eyre::install()?;
    let current_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
    let app = App::new(current_dir);
    ratatui::run(|terminal| app.run(terminal))
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct DirEntryItem {
    name: String,
    path: PathBuf,
    is_dir: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PopupFocus {
    FileList,
    LoadButton,
    CancelButton,
}

#[derive(Debug)]
struct App {
    should_exit: bool,
    show_popup: bool,
    current_dir: PathBuf,
    entries: Vec<DirEntryItem>,
    list_state: ListState,
    confirmed_file: Option<PathBuf>,
    loaded_file: Option<String>,
    focus: PopupFocus,
    // Cached layout areas for mouse hit-testing
    list_inner_area: Rect,
    load_button_area: Rect,
    cancel_button_area: Rect,
}

impl App {
    fn new(start_dir: PathBuf) -> Self {
        let mut app = Self {
            should_exit: false,
            show_popup: false,
            current_dir: start_dir,
            entries: Vec::new(),
            list_state: ListState::default(),
            confirmed_file: None,
            loaded_file: None,
            focus: PopupFocus::FileList,
            list_inner_area: Rect::default(),
            load_button_area: Rect::default(),
            cancel_button_area: Rect::default(),
        };
        app.refresh_directory();
        app
    }

    fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
        execute!(stdout(), EnableMouseCapture)?;
        let result = self.event_loop(terminal);
        let _ = execute!(stdout(), DisableMouseCapture);
        result
    }

    fn event_loop(&mut self, terminal: &mut DefaultTerminal) -> Result<()> {
        while !self.should_exit {
            terminal.draw(|frame| self.render(frame))?;
            match event::read()? {
                Event::Key(key) => self.on_key_event(key),
                Event::Mouse(mouse) => self.on_mouse_event(mouse),
                _ => {}
            }
        }
        Ok(())
    }

    fn refresh_directory(&mut self) {
        let mut entries = Vec::new();

        if let Some(parent) = self.current_dir.parent() {
            entries.push(DirEntryItem {
                name: "..".to_string(),
                path: parent.to_path_buf(),
                is_dir: true,
            });
        }

        if let Ok(read_dir) = fs::read_dir(&self.current_dir) {
            let mut dir_items: Vec<DirEntryItem> = read_dir
                .filter_map(Result::ok)
                .map(|entry| {
                    let path = entry.path();
                    let is_dir = path.is_dir();
                    let file_name = entry.file_name().to_string_lossy().into_owned();
                    let name = if is_dir {
                        format!("{file_name}/")
                    } else {
                        file_name
                    };
                    DirEntryItem { name, path, is_dir }
                })
                .collect();

            // Sort directories first, then alphabetically by name
            dir_items.sort_by(|a, b| b.is_dir.cmp(&a.is_dir).then_with(|| a.name.cmp(&b.name)));
            entries.extend(dir_items);
        }

        self.entries = entries;
        if self.entries.is_empty() {
            self.list_state.select(None);
        } else {
            self.list_state.select(Some(0));
            self.update_selected_file_from_highlight();
        }
    }

    fn update_selected_file_from_highlight(&mut self) {
        if let Some(idx) = self.list_state.selected()
            && let Some(entry) = self.entries.get(idx)
            && !entry.is_dir
        {
            self.confirmed_file = Some(entry.path.clone());
        }
    }

    fn on_key_event(&mut self, key: KeyEvent) {
        if !key.is_press() {
            return;
        }

        if !self.show_popup {
            match key.code {
                KeyCode::Char('q') | KeyCode::Esc => self.should_exit = true,
                KeyCode::Char('p') | KeyCode::Enter => {
                    self.show_popup = true;
                    self.focus = PopupFocus::FileList;
                }
                _ => {}
            }
            return;
        }

        match key.code {
            KeyCode::Esc | KeyCode::Char('p') => {
                self.show_popup = false;
            }
            KeyCode::Char('q') => {
                self.should_exit = true;
            }
            KeyCode::Tab | KeyCode::Right => {
                self.focus = match self.focus {
                    PopupFocus::FileList => PopupFocus::LoadButton,
                    PopupFocus::LoadButton => PopupFocus::CancelButton,
                    PopupFocus::CancelButton => PopupFocus::FileList,
                };
            }
            KeyCode::BackTab | KeyCode::Left => {
                self.focus = match self.focus {
                    PopupFocus::FileList => PopupFocus::CancelButton,
                    PopupFocus::LoadButton => PopupFocus::FileList,
                    PopupFocus::CancelButton => PopupFocus::LoadButton,
                };
            }
            KeyCode::Up | KeyCode::Char('k') => {
                self.focus = PopupFocus::FileList;
                self.select_previous();
            }
            KeyCode::Down | KeyCode::Char('j') => {
                self.focus = PopupFocus::FileList;
                self.select_next();
            }
            KeyCode::Backspace => {
                if let Some(parent) = self.current_dir.parent().map(Path::to_path_buf) {
                    self.current_dir = parent;
                    self.refresh_directory();
                }
            }
            KeyCode::Char('l') => {
                self.confirm_load();
            }
            KeyCode::Enter => match self.focus {
                PopupFocus::FileList => self.activate_highlighted_entry(),
                PopupFocus::LoadButton => self.confirm_load(),
                PopupFocus::CancelButton => self.show_popup = false,
            },
            _ => {}
        }
    }

    fn on_mouse_event(&mut self, mouse: MouseEvent) {
        let pos = Position::new(mouse.column, mouse.row);

        if !self.show_popup {
            return;
        }

        match mouse.kind {
            MouseEventKind::ScrollUp => {
                if self.list_inner_area.contains(pos) {
                    self.select_previous();
                }
            }
            MouseEventKind::ScrollDown => {
                if self.list_inner_area.contains(pos) {
                    self.select_next();
                }
            }
            MouseEventKind::Down(MouseButton::Left) => {
                if self.load_button_area.contains(pos) {
                    self.focus = PopupFocus::LoadButton;
                    self.confirm_load();
                } else if self.cancel_button_area.contains(pos) {
                    self.focus = PopupFocus::CancelButton;
                    self.show_popup = false;
                } else if self.list_inner_area.contains(pos) {
                    self.on_list_click(pos);
                } else {
                    // Click outside interactive popup areas
                }
            }
            _ => {}
        }
    }

    fn on_list_click(&mut self, pos: Position) {
        self.focus = PopupFocus::FileList;
        let relative_row = usize::from(pos.y.saturating_sub(self.list_inner_area.y));
        let clicked_index = self.list_state.offset() + relative_row;
        if clicked_index < self.entries.len() {
            let already_selected = self.list_state.selected() == Some(clicked_index);
            self.list_state.select(Some(clicked_index));
            if let Some(entry) = self.entries.get(clicked_index).cloned() {
                if entry.is_dir {
                    if already_selected {
                        self.current_dir = entry.path;
                        self.refresh_directory();
                    }
                } else {
                    self.confirmed_file = Some(entry.path);
                }
            }
        }
    }

    fn select_previous(&mut self) {
        self.list_state.select_previous();
        self.update_selected_file_from_highlight();
    }

    fn select_next(&mut self) {
        match self.list_state.selected() {
            Some(current) if current + 1 < self.entries.len() => {
                self.list_state.select(Some(current + 1));
            }
            None if !self.entries.is_empty() => {
                self.list_state.select(Some(0));
            }
            _ => {}
        }
        self.update_selected_file_from_highlight();
    }

    fn activate_highlighted_entry(&mut self) {
        let Some(idx) = self.list_state.selected() else {
            return;
        };
        let Some(entry) = self.entries.get(idx).cloned() else {
            return;
        };

        if entry.is_dir {
            self.current_dir = entry.path;
            self.refresh_directory();
        } else {
            self.confirmed_file = Some(entry.path);
            self.focus = PopupFocus::LoadButton;
        }
    }

    fn confirm_load(&mut self) {
        self.update_selected_file_from_highlight();
        if let Some(path) = &self.confirmed_file {
            let file_name = path
                .file_name()
                .map_or_else(|| path.display().to_string(), |n| n.to_string_lossy().into_owned());
            self.loaded_file = Some(file_name);
            self.show_popup = false;
        }
    }

    fn render(&mut self, frame: &mut Frame) {
        let area = frame.area();

        let layout = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]);
        let [instructions, content] = area.layout(&layout);

        frame.render_widget(
            Line::from("Press 'p' to toggle file popup, 'q' to quit").centered(),
            instructions,
        );

        self.render_main_page(frame, content);

        if self.show_popup {
            self.render_popup(frame, area);
        }
    }

    fn render_main_page(&self, frame: &mut Frame, area: Rect) {
        let block = Block::bordered()
            .title("Medical File Analyizer")
            .on_magenta()
            .white();

        let inner = block.inner(area);
        frame.render_widget(block, area);

        let lines = if let Some(file_name) = &self.loaded_file {
            vec![
                Line::from("Medical File Analyizer").bold(),
                Line::default(),
                Line::from(format!("Loaded: {file_name}")).bold(),
            ]
        } else {
            vec![
                Line::from("Medical File Analyizer").bold(),
                Line::default(),
                Line::from("Press 'p' to select and load a file"),
            ]
        };

        let vertical_center = Layout::vertical([
            Constraint::Fill(1),
            Constraint::Length(u16::try_from(lines.len()).unwrap_or(3)),
            Constraint::Fill(1),
        ]);
        let [_, text_area, _] = inner.layout(&vertical_center);

        let paragraph = Paragraph::new(Text::from(lines))
            .alignment(Alignment::Center)
            .on_magenta()
            .white();
        frame.render_widget(paragraph, text_area);
    }

    fn render_popup(&mut self, frame: &mut Frame, area: Rect) {
        let centered_area = area.centered(Constraint::Percentage(70), Constraint::Percentage(70));
        frame.render_widget(Clear, centered_area);

        let popup_block = Block::bordered()
            .title("Select a File to Load")
            .style(Style::default().bg(Color::Black).fg(Color::White));
        let inner = popup_block.inner(centered_area);
        frame.render_widget(popup_block, centered_area);

        let popup_layout = Layout::vertical([
            Constraint::Length(2), // Current directory
            Constraint::Fill(1),   // File list
            Constraint::Length(2), // Selected file confirmation display
            Constraint::Length(3), // Buttons (Load / Cancel)
            Constraint::Length(1), // Popup help line
        ]);
        let [dir_area, list_area, selected_area, buttons_area, help_area] =
            inner.layout(&popup_layout);

        let dir_text = Paragraph::new(format!("Directory: {}", self.current_dir.display()))
            .wrap(Wrap { trim: true })
            .style(Style::default().fg(Color::Cyan));
        frame.render_widget(dir_text, dir_area);

        self.render_popup_list(frame, list_area);

        let selected_label = self.confirmed_file.as_ref().map_or_else(
            || "Selected file: (none - choose a file from the list)".to_string(),
            |path| {
                let name = path
                    .file_name()
                    .map_or_else(|| path.display().to_string(), |n| n.to_string_lossy().into_owned());
                format!("Selected file: {name}")
            },
        );
        let selected_paragraph =
            Paragraph::new(selected_label).style(Style::default().fg(Color::Green));
        frame.render_widget(selected_paragraph, selected_area);

        self.render_popup_buttons(frame, buttons_area);

        let help = Line::from(
            "Up/Down: navigate | Enter: open/select | Tab: focus Load | Click 'Load' or press 'l' to load",
        )
        .centered()
        .style(Style::default().fg(Color::Gray));
        frame.render_widget(help, help_area);
    }

    fn render_popup_list(&mut self, frame: &mut Frame, list_area: Rect) {
        let list_border_style = if self.focus == PopupFocus::FileList {
            Style::default().fg(Color::Yellow)
        } else {
            Style::default().fg(Color::Gray)
        };
        let list_block = Block::bordered()
            .title("Files & Folders")
            .border_style(list_border_style);
        self.list_inner_area = list_block.inner(list_area);

        let items: Vec<ListItem> = self
            .entries
            .iter()
            .map(|entry| {
                let style = if entry.is_dir {
                    Style::default()
                        .fg(Color::LightBlue)
                        .add_modifier(Modifier::BOLD)
                } else {
                    Style::default().fg(Color::White)
                };
                ListItem::new(Line::styled(entry.name.clone(), style))
            })
            .collect();

        let list = List::new(items)
            .block(list_block)
            .highlight_style(
                Style::default()
                    .bg(Color::DarkGray)
                    .fg(Color::Yellow)
                    .add_modifier(Modifier::BOLD),
            )
            .highlight_symbol("> ")
            .highlight_spacing(HighlightSpacing::Always);

        frame.render_stateful_widget(list, list_area, &mut self.list_state);
    }

    fn render_popup_buttons(&mut self, frame: &mut Frame, buttons_area: Rect) {
        let buttons_layout = Layout::horizontal([
            Constraint::Fill(1),
            Constraint::Length(14),
            Constraint::Length(4),
            Constraint::Length(14),
            Constraint::Fill(1),
        ]);
        let [_, load_area, _, cancel_area, _] = buttons_area.layout(&buttons_layout);
        self.load_button_area = load_area;
        self.cancel_button_area = cancel_area;

        let can_load = self.confirmed_file.is_some();
        let load_style = if self.focus == PopupFocus::LoadButton {
            Style::default()
                .fg(Color::Black)
                .bg(if can_load { Color::Green } else { Color::DarkGray })
                .add_modifier(Modifier::BOLD)
        } else if can_load {
            Style::default().fg(Color::Green)
        } else {
            Style::default().fg(Color::DarkGray)
        };

        let load_button = Paragraph::new("Load")
            .alignment(Alignment::Center)
            .block(Block::bordered().style(load_style));
        frame.render_widget(load_button, load_area);

        let cancel_style = if self.focus == PopupFocus::CancelButton {
            Style::default()
                .fg(Color::Black)
                .bg(Color::Red)
                .add_modifier(Modifier::BOLD)
        } else {
            Style::default().fg(Color::Red)
        };
        let cancel_button = Paragraph::new("Cancel")
            .alignment(Alignment::Center)
            .block(Block::bordered().style(cancel_style));
        frame.render_widget(cancel_button, cancel_area);
    }
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;

    use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
    use ratatui::Terminal;
    use ratatui::backend::TestBackend;
    use ratatui::style::Color;

    use super::*;

    fn buffer_text(terminal: &Terminal<TestBackend>) -> String {
        let buf = terminal.backend().buffer();
        let mut out = String::new();
        for y in 0..buf.area.height {
            for x in 0..buf.area.width {
                out.push_str(buf[(x, y)].symbol());
            }
            out.push('\n');
        }
        out
    }

    #[test]
    fn initial_page_is_purple_with_medical_file_analyizer_text() {
        let mut app = App::new(PathBuf::from("."));
        let backend = TestBackend::new(80, 24);
        let mut terminal = Terminal::new(backend).unwrap();

        terminal.draw(|frame| app.render(frame)).unwrap();

        let text = buffer_text(&terminal);
        assert!(text.contains("Medical File Analyizer"));

        // Verify the main content block has a purple (Magenta) background
        let buf = terminal.backend().buffer();
        assert_eq!(buf[(40, 12)].bg, Color::Magenta);
    }

    #[test]
    fn selecting_file_and_clicking_load_closes_popup_and_shows_loaded_filename() {
        let mut app = App::new(PathBuf::from("."));
        app.entries = vec![DirEntryItem {
            name: "patient_record.csv".to_string(),
            path: PathBuf::from("/tmp/patient_record.csv"),
            is_dir: false,
        }];
        app.list_state.select(Some(0));
        app.update_selected_file_from_highlight();

        // Open popup with 'p'
        app.on_key_event(KeyEvent::new(KeyCode::Char('p'), KeyModifiers::NONE));
        assert!(app.show_popup);

        let backend = TestBackend::new(100, 30);
        let mut terminal = Terminal::new(backend).unwrap();
        terminal.draw(|frame| app.render(frame)).unwrap();

        let popup_text = buffer_text(&terminal);
        assert!(popup_text.contains("patient_record.csv"));
        assert!(popup_text.contains("Load"));

        // Simulate clicking the Load button
        let load_x = app.load_button_area.x + 1;
        let load_y = app.load_button_area.y + 1;
        app.on_mouse_event(MouseEvent {
            kind: MouseEventKind::Down(MouseButton::Left),
            column: load_x,
            row: load_y,
            modifiers: KeyModifiers::NONE,
        });

        assert!(!app.show_popup);
        assert_eq!(app.loaded_file.as_deref(), Some("patient_record.csv"));

        terminal.draw(|frame| app.render(frame)).unwrap();
        let loaded_text = buffer_text(&terminal);
        assert!(loaded_text.contains("Loaded: patient_record.csv"));
    }
}

I’m now going to give what I think is a hard ask to Gemini4 - take a Flask (python, web) based implementation in another folder and update this TUI to do similar functionality which is in a whole different language (Rust) and framework (TUI, not web)

/img/2026-10-ratatui-07.png

A behaviour that was rather new this time I had not seen with Gemini 3.x was it would take a bit. like 30s to a minute on a step

/img/2026-10-ratatui-08.png

But then it would come back with a very complete file. Almost like it was really nailing it before responding (I know I’m anthropomorphizing the LLM, but it just felt it was doing a more complete job and ensuring what it presented was solid)

/img/2026-10-ratatui-09.png

It finished with commands and controls:

3. TUI Assessment View & Controls (main.rs)
    • When a file is confirmed in App::confirm_load, App::start_analysis extracts the document text and streams the Ollama
    analysis in a background thread while keeping the TUI responsive.
    • Renders the structured GRADE Standard Clinical Assessment in App::build_structured_assessment_lines with keyboard &
    mouse support:
        • p: Open file selection popup
        • m: Toggle FAST / DETAILED model mode
        • e: Toggle Summarize / Evaluate action
        • r: Toggle between structured GRADE Assessment view and Raw JSON/text output
        • a / Enter: Re-run analysis on the loaded file
        • s: Save formatted plain-text clinical report (<filename>-<action>.txt)
        • Up/Down (j/k), PageUp/PageDown, Home, or Mouse Wheel: Scroll the analysis output
        • q / Esc: Quit

/img/2026-10-ratatui-10.png

Let’s get it started

OLLAMA_URL="http://eval.steeped.icu:11444" \
OLLAMA_MODEL_FAST="gemma4:e4b" \
OLLAMA_MODEL_DETAILED="medgemma1.5:4b" \
cargo run -p medical-file-analyzer

I then ran my own medical report

/img/2026-10-ratatui-11.png

Lets test with some sample files

Adding Markdown output was done quickly as well

/img/2026-10-ratatui-13.png

One quick test

To share it, I can use cargo build to make a binary:

$ cargo build --release -p medical-file-analyzer
   Compiling proc-macro2 v1.0.106
   Compiling unicode-ident v1.0.18
   Compiling quote v1.0.45
   Compiling libc v0.2.175
   Compiling cfg-if v1.0.1
   Compiling foldhash v0.2.0
   Compiling equivalent v1.0.2
   Compiling allocator-api2 v0.2.21
   Compiling rustversion v1.0.22
   Compiling zmij v1.0.2
   Compiling bitflags v2.13.2
   Compiling thiserror v2.0.21
   Compiling strsim v0.11.1
   Compiling ident_case v1.0.1
   Compiling once_cell v1.21.3
   Compiling autocfg v1.5.0
   Compiling either v1.15.0
   Compiling itoa v1.0.15
   Compiling heck v0.5.0
   Compiling unicode-segmentation v1.13.3
   Compiling static_assertions v1.1.0
   Compiling parking_lot_core v0.9.11
   Compiling itertools v0.15.0
   Compiling signal-hook v0.3.18
   Compiling unicode-width v0.2.2
   Compiling tracing-core v0.1.36
   Compiling lazy_static v1.5.0
   Compiling critical-section v1.2.0
   Compiling lock_api v0.4.13
   Compiling scopeguard v1.2.0
   Compiling hashbrown v0.17.1
   Compiling hashbrown v0.16.1
   Compiling log v0.4.27
   Compiling rustix v1.0.8
   Compiling indoc v2.0.7
   Compiling smallvec v1.15.1
   Compiling sharded-slab v0.1.7
   Compiling thread_local v1.1.9
   Compiling convert_case v0.7.1
   Compiling deranged v0.5.8
   Compiling litrs v1.0.0
   Compiling castaway v0.2.4
   Compiling owo-colors v4.2.2
   Compiling linux-raw-sys v0.9.4
   Compiling compact_str v0.10.0
   Compiling object v0.36.7
   Compiling serde_core v1.0.229
   Compiling time-core v0.1.9
   Compiling syn v3.0.2
   Compiling syn v2.0.117
   Compiling signal-hook-registry v1.4.6
   Compiling mio v1.0.4
   Compiling powerfmt v0.2.0
   Compiling num_threads v0.1.7
   Compiling lru v0.18.5
   Compiling num-conv v0.2.2
   Compiling pin-project-lite v0.2.16
   Compiling memchr v2.7.5
   Compiling tracing v0.1.44
   Compiling document-features v0.2.12
   Compiling parking_lot v0.12.4
   Compiling tracing-subscriber v0.3.23
   Compiling line-clipping v0.3.8
   Compiling eyre v0.6.12
   Compiling signal-hook-mio v0.2.4
   Compiling adler2 v2.0.1
   Compiling color-spantrace v0.3.0
   Compiling gimli v0.31.1
   Compiling miniz_oxide v0.8.9
   Compiling rustc-demangle v0.1.26
   Compiling unicode-truncate v3.0.0
   Compiling time v0.3.55
   Compiling serde_json v1.0.151
   Compiling serde v1.0.229
   Compiling indenter v0.3.4
   Compiling tracing-error v0.2.1
   Compiling strum_macros v0.28.0
   Compiling derive_more-impl v2.0.1
   Compiling derive_more v2.0.1
   Compiling crossterm v0.29.0
   Compiling strum v0.28.0
   Compiling addr2line v0.24.2
   Compiling darling_core v0.24.0
   Compiling thiserror-impl v2.0.21
   Compiling serde_derive v1.0.229
   Compiling backtrace v0.3.75
   Compiling kasuari v0.4.12
   Compiling ratatui-core v0.1.2 (/home/isaac/Workspaces/ratatui/ratatui-core)
   Compiling color-eyre v0.6.5
   Compiling darling_macro v0.24.0
   Compiling darling v0.24.0
   Compiling instability v0.3.14
   Compiling ratatui-widgets v0.3.2 (/home/isaac/Workspaces/ratatui/ratatui-widgets)
   Compiling ratatui-crossterm v0.1.2 (/home/isaac/Workspaces/ratatui/ratatui-crossterm)
   Compiling ratatui-macros v0.7.2 (/home/isaac/Workspaces/ratatui/ratatui-macros)
   Compiling ratatui v0.30.2 (/home/isaac/Workspaces/ratatui/ratatui)
   Compiling medical-file-analyzer v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/medical-file-analyzer)
    Finished `release` profile [optimized] target(s) in 8.41s

Which made a nice 1.9Mb executable

isaac@isaac-G707:~/Workspaces/ratatui$ target/release/medical-file-analyzer
isaac@isaac-G707:~/Workspaces/ratatui$ ls -l target/release/medical-file-analyzer
-rwxrwxr-x 2 isaac isaac 1989072 Oct 11 14:41 target/release/medical-file-analyzer
isaac@isaac-G707:~/Workspaces/ratatui$ ls -lh target/release/medical-file-analyzer
-rwxrwxr-x 2 isaac isaac 1.9M Oct 11 14:41 target/release/medical-file-analyzer

Artistic quandaries

I gave it coding work and it did fantastic. I’m quite please with the results.

I next wanted to see how it would handle some retro 8-bit style graphics by asking it to create an 8-bit retro Rod of Aesculapius in the background.

/img/2026-10-ratatui-15.png

Not bad on the first pass

/img/2026-10-ratatui-16.png

I decided I wanted the two-headed snake, not the one, and so I prompted it to change it to the staff of Hermes

/img/2026-10-ratatui-17.png

But that looks even worse

/img/2026-10-ratatui-18.png

I then went with a 3d letter style

/img/2026-10-ratatui-19.png

Which gave me Vikings colours which was a bonus I had not expected

/img/2026-10-ratatui-20.png

I even added a popup version info page.

Cross compile

Right now I’m compiling for Linux x64 but there are those out there that run Windows and Mac as well.

If we use rustup target list we can see some options for target destinations

$ rustup target list
aarch64-apple-darwin
aarch64-apple-ios
aarch64-apple-ios-macabi
aarch64-apple-ios-sim
aarch64-apple-tvos
aarch64-apple-tvos-sim
aarch64-apple-visionos
aarch64-apple-visionos-sim
aarch64-apple-watchos
aarch64-apple-watchos-sim
aarch64-linux-android
aarch64-pc-windows-gnullvm
aarch64-pc-windows-msvc
aarch64-unknown-freebsd
aarch64-unknown-fuchsia
aarch64-unknown-linux-gnu
aarch64-unknown-linux-musl
aarch64-unknown-linux-ohos
aarch64-unknown-none
aarch64-unknown-none-softfloat
aarch64-unknown-uefi
arm-linux-androideabi
arm-unknown-linux-gnueabi
arm-unknown-linux-gnueabihf
arm-unknown-linux-musleabi
arm-unknown-linux-musleabihf
arm64ec-pc-windows-msvc
armv5te-unknown-linux-gnueabi
armv5te-unknown-linux-musleabi
armv7-linux-androideabi
armv7-unknown-linux-gnueabi
armv7-unknown-linux-gnueabihf
armv7-unknown-linux-musleabi
armv7-unknown-linux-musleabihf
armv7-unknown-linux-ohos
armv7a-none-eabi
armv7a-none-eabihf
armv7r-none-eabi
armv7r-none-eabihf
armv8r-none-eabihf
i586-unknown-linux-gnu
i586-unknown-linux-musl
i686-linux-android
i686-pc-windows-gnu
i686-pc-windows-gnullvm
i686-pc-windows-msvc
i686-unknown-freebsd
i686-unknown-linux-gnu
i686-unknown-linux-musl
i686-unknown-uefi
loongarch32-unknown-none
loongarch32-unknown-none-softfloat
loongarch64-unknown-linux-gnu
loongarch64-unknown-linux-musl
loongarch64-unknown-none
loongarch64-unknown-none-softfloat
nvptx64-nvidia-cuda
powerpc-unknown-linux-gnu
powerpc64-unknown-linux-gnu
powerpc64-unknown-linux-musl
powerpc64le-unknown-linux-gnu
powerpc64le-unknown-linux-musl
riscv32i-unknown-none-elf
riscv32im-unknown-none-elf
riscv32imac-unknown-none-elf
riscv32imafc-unknown-none-elf
riscv32imc-unknown-none-elf
riscv64a23-unknown-linux-gnu
riscv64gc-unknown-linux-gnu
riscv64gc-unknown-linux-musl
riscv64gc-unknown-none-elf
riscv64imac-unknown-none-elf
s390x-unknown-linux-gnu
sparc64-unknown-linux-gnu
sparcv9-sun-solaris
thumbv6m-none-eabi
thumbv7a-none-eabi
thumbv7a-none-eabihf
thumbv7em-none-eabi
thumbv7em-none-eabihf
thumbv7m-none-eabi
thumbv7neon-linux-androideabi
thumbv7neon-unknown-linux-gnueabihf
thumbv7r-none-eabi
thumbv7r-none-eabihf
thumbv8m.base-none-eabi
thumbv8m.main-none-eabi
thumbv8m.main-none-eabihf
thumbv8r-none-eabihf
wasm32-unknown-emscripten
wasm32-unknown-unknown
wasm32-wasip1
wasm32-wasip1-threads
wasm32-wasip2
wasm32v1-none
x86_64-apple-darwin
x86_64-apple-ios
x86_64-apple-ios-macabi
x86_64-fortanix-unknown-sgx
x86_64-linux-android
x86_64-pc-solaris
x86_64-pc-windows-gnu
x86_64-pc-windows-gnullvm
x86_64-pc-windows-msvc
x86_64-unknown-freebsd
x86_64-unknown-fuchsia
x86_64-unknown-illumos
x86_64-unknown-linux-gnu (installed)
x86_64-unknown-linux-gnuasan
x86_64-unknown-linux-gnumsan
x86_64-unknown-linux-gnutsan
x86_64-unknown-linux-gnux32
x86_64-unknown-linux-musl
x86_64-unknown-linux-ohos
x86_64-unknown-netbsd
x86_64-unknown-none
x86_64-unknown-redox
x86_64-unknown-uefi

I have no idea where this is going, but let’s add the target for my phone

$ rustup target add aarch64-linux-android
info: downloading component rust-std
     rust-std installed                       28.96 MiB

and a windows PC

$ rustup target add x86_64-pc-windows-gnu
info: downloading component rust-std
     rust-std installed                       27.07 MiB

I’ll add the cross compiler with

$ cargo install cross

(trimmed the verbose output)

I then ran

$ cargo build --release --target x86_64-pc-windows-gnu

... snip ...
Compiling matchers v0.2.0
Compiling ppv-lite86 v0.2.21
error: error calling dlltool 'x86_64-w64-mingw32-dlltool': No such file or directory (os error 2)

error: could not compile `windows-sys` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...

Seems I need ‘mingw-w64’ installed:

$ sudo apt update && sudo apt install mingw-w64

[sudo: authenticate] Password:
Hit:1 http://us.archive.ubuntu.com/ubuntu resolute InRelease
Hit:2 https://download.docker.com/linux/ubuntu resolute InRelease
Hit:3 https://packages.microsoft.com/repos/azure-cli noble InRelease
Hit:4 http://us.archive.ubuntu.com/ubuntu resolute-updates InRelease
Hit:5 https://packages.microsoft.com/repos/code stable InRelease
Hit:6 http://us.archive.ubuntu.com/ubuntu resolute-backports InRelease
... snip ...
it:16 https://ppa.launchpadcontent.net/obsproject/obs-studio/ubuntu resolute InRelease
Fetched 14.3 kB in 9s (1,649 B/s)
116 packages can be upgraded. Run 'apt list --upgradable' to see them.
The following package was automatically installed and is no longer required:
  grub-pc-bin
Use 'sudo apt autoremove' to remove it.

Installing:
  mingw-w64

Installing dependencies:
  binutils-mingw-w64-base     g++-mingw-w64-x86-64-win32        gcc-mingw-w64-x86-64-posix
  binutils-mingw-w64-i686     gcc-mingw-w64                     gcc-mingw-w64-x86-64-posix-runtime
  binutils-mingw-w64-x86-64   gcc-mingw-w64-base                gcc-mingw-w64-x86-64-win32
  g++-mingw-w64               gcc-mingw-w64-i686                gcc-mingw-w64-x86-64-win32-runtime
  g++-mingw-w64-i686          gcc-mingw-w64-i686-posix          mingw-w64-common
  g++-mingw-w64-i686-posix    gcc-mingw-w64-i686-posix-runtime  mingw-w64-i686-dev
  g++-mingw-w64-i686-win32    gcc-mingw-w64-i686-win32          mingw-w64-x86-64-dev
  g++-mingw-w64-x86-64        gcc-mingw-w64-i686-win32-runtime
  g++-mingw-w64-x86-64-posix  gcc-mingw-w64-x86-64

Suggested packages:
  gcc-13-locales  wine  wine64

Summary:
  Upgrading: 0, Installing: 26, Removing: 0, Not Upgrading: 116
  Download size: 278 MB
  Space needed: 1,394 MB / 844 GB available

Continue? [Y/n] Y
Get:1 http://us.archive.ubuntu.com/ubuntu resolute/univ
... snip ...

I went and pulled the last valid Windows ISO i had (sadly was 15yo and was Server 2008)

/img/2026-10-ratatui-21.png

Though in trying to debug the error, seems Windows 7 and 2008 are too old for ‘modern’ windows apps

/img/2026-10-ratatui-22.png

I hunted down a Windows 11 ISO and at the license key step, i just selected “I have none” to see what it would do.

It certainly started the process

/img/2026-10-ratatui-23.png

It took a few goes, some crashes and mangling a password to get past trying to make me sign in to a Windows account just to load windows, but I got there.

I then set it to download the build of all the apps

/img/2026-10-ratatui-24.png

This launched

/img/2026-10-ratatui-25.png

But it made me realize that one windows, setting some env vars before launch would be a bit of a hassle for a normal user.

Let’s fix that

/img/2026-10-ratatui-26.png

It got done

/img/2026-10-ratatui-27.png

I transferred and ran it, and was now able to set the URL in the app

/img/2026-10-ratatui-28.png

I transferred a sample PDF to the desktop and chose to load

/img/2026-10-ratatui-29.png

Looks like Windows might have an issue. perhaps we were using a command line tool to do the PDF conversion that doesn’t not exist in windows

/img/2026-10-ratatui-30.png

Agy indeed rebuilt with a native rust based PDF to Txt converter

/img/2026-10-ratatui-33.png

Then when I ran the new binary, it worked fantastic

/img/2026-10-ratatui-32.png

CICD

I have a good start now. I pushed up to my fork of the Ratatui repo and created a PR

/img/2026-10-ratatui-34.png

I have an Actions YAML there that I wrote myself (for some reason, I just didn’t want the AI to do it today)

Android

For Android to compile, we’ll need to install ndk

$ cargo install cargo-ndk

I realized later this assumes you have an NDK installed for Android. So I went back and did that, then did the cargo-ndk step.

$ sudo apt update && sudo apt install wget unzip -y
$ cd ~
$ wget https://dl.google.com/android/repository/android-ndk-r26c-linux.zip
$ mkdir -p ~/Android/Sdk/ndk
$ unzip android-ndk-r26c-linux.zip -d ~/Android/Sdk/ndk
$ rm android-ndk-r26c-linux.zip
$ vi ~/.bashrc
$ export PATH=$PATH:$HOME/Android/Sdk/ndk/android-ndk-r26c/toolchains/llvm/prebuilt/linux-x86_64/bin
$ cd Workspaces/
$ cd ratatui/
$ export ANDROID_NDK_HOME=$HOME/Android/Sdk/ndk/android-ndk-r26c
$ cargo install cargo-ndk --force

Now it compiled

isaac@isaac-G707:~/Workspaces/ratatui$ cargo ndk --version
cargo-ndk 4.1.2
isaac@isaac-G707:~/Workspaces/ratatui$ cargo ndk -t aarch64-linux-android build
    Building arm64-v8a (aarch64-linux-android)
   Compiling proc-macro2 v1.0.106
   Compiling unicode-ident v1.0.18
   Compiling quote v1.0.45
   Compiling libc v0.2.175
   Compiling cfg-if v1.0.1
   Compiling autocfg v1.5.0
   Compiling log v0.4.27
   Compiling memchr v2.7.5
   Compiling thiserror v2.0.21
   Compiling smallvec v1.15.1
   Compiling itoa v1.0.15
   Compiling zmij v1.0.2
   Compiling pin-project-lite v0.2.16
   Compiling bitflags v2.13.2
   Compiling futures-core v0.3.31
   Compiling once_cell v1.21.3
   Compiling spin v0.9.8
   Compiling heck v0.5.0
   Compiling equivalent v1.0.2
   Compiling lazy_static v1.5.0
   Compiling rustversion v1.0.22
   Compiling allocator-api2 v0.2.21
   Compiling foldhash v0.2.0
   Compiling signal-hook v0.3.18
   Compiling regex-syntax v0.8.5
   Compiling ident_case v1.0.1
   Compiling tracing-core v0.1.36
   Compiling either v1.15.0
   Compiling unicode-segmentation v1.13.3
   Compiling strsim v0.11.1
   Compiling time-macros v0.2.32
   Compiling num_threads v0.1.7
   Compiling unicode-width v0.2.2
   Compiling aho-corasick v1.1.3
   Compiling itertools v0.15.0
   Compiling lock_api v0.4.13
   Compiling hashbrown v0.17.1
   Compiling hashbrown v0.16.1
   Compiling num-conv v0.2.2
   Compiling time-core v0.1.9
   Compiling parking_lot_core v0.9.11
   Compiling deranged v0.5.8
   Compiling static_assertions v1.1.0
   Compiling powerfmt v0.2.0
   Compiling critical-section v1.2.0
   Compiling rustix v1.0.8
   Compiling syn v2.0.117
   Compiling syn v3.0.2
   Compiling scopeguard v1.2.0
   Compiling linux-raw-sys v0.9.4
   Compiling indoc v2.0.7
   Compiling convert_case v0.7.1
   Compiling mio v1.0.4
   Compiling signal-hook-registry v1.4.6
   Compiling castaway v0.2.4
   Compiling lru v0.18.5
   Compiling compact_str v0.10.0
   Compiling errno v0.3.13
   Compiling litrs v1.0.0
   Compiling tracing-log v0.2.0
   Compiling parking_lot v0.12.4
   Compiling sharded-slab v0.1.7
   Compiling signal-hook-mio v0.2.4
   Compiling line-clipping v0.3.8
   Compiling thread_local v1.1.9
   Compiling nu-ansi-term v0.50.1
   Compiling owo-colors v4.2.2
   Compiling regex-automata v0.4.9
   Compiling document-features v0.2.12
   Compiling object v0.36.7
   Compiling gimli v0.31.1
   Compiling time v0.3.55
   Compiling unicode-truncate v3.0.0
   Compiling color-spantrace v0.3.0
   Compiling adler2 v2.0.1
   Compiling eyre v0.6.12
   Compiling indenter v0.3.4
   Compiling miniz_oxide v0.8.9
   Compiling rustc-demangle v0.1.26
   Compiling zeroize v1.8.1
   Compiling libm v0.2.15
   Compiling subtle v2.6.1
   Compiling version_check v0.9.5
   Compiling typenum v1.18.0
   Compiling getrandom v0.2.16
   Compiling const-oid v0.9.6
   Compiling num-traits v0.2.19
   Compiling rand_core v0.6.4
   Compiling serde_core v1.0.229
   Compiling generic-array v0.14.7
   Compiling base64ct v1.8.0
   Compiling zerocopy v0.8.26
   Compiling stable_deref_trait v1.2.0
   Compiling serde v1.0.229
   Compiling pem-rfc7468 v0.7.0
   Compiling fnv v1.0.7
   Compiling der v0.7.10
   Compiling semver v1.0.26
   Compiling bytes v1.11.1
   Compiling darling_core v0.24.0
   Compiling futures-sink v0.3.31
   Compiling addr2line v0.24.2
   Compiling synstructure v0.13.2
   Compiling socket2 v0.6.0
   Compiling block-buffer v0.10.4
   Compiling crypto-common v0.1.6
   Compiling siphasher v1.0.1
   Compiling digest v0.10.7
   Compiling spki v0.7.3
   Compiling matchers v0.2.0
   Compiling pkcs8 v0.10.2
   Compiling rand v0.8.5
   Compiling phf_shared v0.11.3
   Compiling futures-channel v0.3.31
   Compiling slab v0.4.11
   Compiling pin-utils v0.1.0
   Compiling ppv-lite86 v0.2.21
   Compiling futures-task v0.3.31
   Compiling futures-io v0.3.31
   Compiling getrandom v0.3.3
   Compiling shlex v1.3.0
   Compiling hmac v0.12.1
   Compiling http v1.3.1
   Compiling cpufeatures v0.2.17
   Compiling phf_generator v0.11.3
   Compiling cc v1.2.33
   Compiling rand_core v0.10.0
   Compiling backtrace v0.3.75
   Compiling thiserror v1.0.69
   Compiling sha2 v0.10.9
   Compiling signature v2.2.0
   Compiling ff v0.13.1
   Compiling litemap v0.8.0
   Compiling getrandom v0.4.1
   Compiling writeable v0.6.1
   Compiling base16ct v0.2.0
   Compiling syn v1.0.109
   Compiling sec1 v0.7.3
   Compiling group v0.13.0
   Compiling hkdf v0.12.4
   Compiling num-integer v0.1.46
   Compiling crypto-bigint v0.5.5
   Compiling icu_normalizer_data v2.0.0
   Compiling httparse v1.10.1
   Compiling icu_properties_data v2.0.1
   Compiling utf8parse v0.2.2
   Compiling http-body v1.0.1
   Compiling chacha20 v0.10.0
   Compiling rustls-pki-types v1.12.0
   Compiling memoffset v0.9.1
   Compiling camino v1.2.1
   Compiling serde_json v1.0.151
   Compiling tower-service v0.3.3
   Compiling try-lock v0.2.5
   Compiling untrusted v0.9.0
   Compiling cfg_aliases v0.2.1
   Compiling nix v0.29.0
   Compiling want v0.3.1
   Compiling rand v0.10.3
   Compiling rfc6979 v0.4.0
   Compiling rand_chacha v0.3.1
   Compiling ordered-float v4.6.0
   Compiling num-bigint-dig v0.8.6
   Compiling base64 v0.22.1
   Compiling rustls v0.23.45
   Compiling ucd-trie v0.1.7
   Compiling elliptic-curve v0.13.8
   Compiling thiserror-impl v2.0.21
   Compiling serde_derive v1.0.229
   Compiling primeorder v0.13.6
   Compiling ecdsa v0.16.9
   Compiling num-iter v0.1.45
   Compiling phf_codegen v0.11.3
   Compiling percent-encoding v2.3.1
   Compiling crossbeam-utils v0.8.21
   Compiling bytemuck v1.23.2
   Compiling terminfo v0.9.0
   Compiling ring v0.17.14
   Compiling tracing-attributes v0.1.31
   Compiling strum_macros v0.28.0
   Compiling derive_more-impl v2.0.1
   Compiling zerofrom-derive v0.1.6
   Compiling yoke-derive v0.8.0
   Compiling zerovec-derive v0.11.1
   Compiling displaydoc v0.2.5
   Compiling tokio-macros v2.6.0
   Compiling futures-macro v0.3.31
   Compiling darling_macro v0.24.0
   Compiling thiserror-impl v1.0.69
   Compiling derive_more v2.0.1
   Compiling crossterm v0.29.0
   Compiling kasuari v0.4.12
   Compiling darling v0.24.0
   Compiling tokio v1.50.0
   Compiling instability v0.3.14
   Compiling phf_macros v0.11.3
   Compiling pest v2.8.1
   Compiling futures-util v0.3.31
   Compiling atomic v0.6.1
   Compiling form_urlencoded v1.2.1
   Compiling tracing v0.1.44
   Compiling anstyle-parse v1.0.0
   Compiling tracing-subscriber v0.3.23
   Compiling num-bigint v0.4.6
   Compiling rand_core v0.9.3
   Compiling wezterm-dynamic-derive v0.1.1
   Compiling ed25519 v2.2.3
   Compiling phf v0.11.3
   Compiling pkcs1 v0.7.5
   Compiling android_system_properties v0.1.5
   Compiling zerofrom v0.1.6
   Compiling lab v0.11.0
   Compiling tower-layer v0.3.3
   Compiling yoke v0.8.0
   Compiling anstyle-query v1.1.4
   Compiling openssl-probe v0.1.6
   Compiling pest_meta v2.8.1
   Compiling zerovec v0.11.4
   Compiling zerotrie v0.2.2
   Compiling utf8_iter v1.0.4
   Compiling by_address v1.2.1
   Compiling is_terminal_polyfill v1.70.1
   Compiling bit-vec v0.6.3
   Compiling minimal-lexical v0.2.1
   Compiling sync_wrapper v1.0.2
   Compiling strum v0.28.0
   Compiling ratatui-core v0.1.2 (/home/isaac/Workspaces/ratatui/ratatui-core)
   Compiling anyhow v1.0.99
   Compiling colorchoice v1.0.4
   Compiling anstyle v1.0.14
   Compiling wezterm-dynamic v0.2.1
   Compiling nom v7.1.3
   Compiling bit-set v0.5.3
   Compiling simple_asn1 v0.6.3
   Compiling tracing-error v0.2.1
   Compiling color-eyre v0.6.5
   Compiling tinystr v0.8.1
   Compiling potential_utf v0.1.2
   Compiling mac_address v1.1.8
   Compiling icu_locale_core v2.0.0
   Compiling icu_collections v2.0.0
   Compiling cargo-platform v0.3.1
   Compiling futures-executor v0.3.31
   Compiling rustc_version v0.4.1
   Compiling pest_generator v2.8.1
   Compiling hyper v1.6.0
   Compiling cargo_metadata v0.23.1
   Compiling tokio-util v0.7.16
   Compiling anstream v1.0.0
   Compiling palette_derive v0.7.7
   Compiling rustls-native-certs v0.8.1
   Compiling curve25519-dalek v4.1.3
   Compiling icu_provider v2.0.0
   Compiling tower v0.5.2
   Compiling ratatui-widgets v0.3.2 (/home/isaac/Workspaces/ratatui/ratatui-widgets)
   Compiling ratatui-crossterm v0.1.2 (/home/isaac/Workspaces/ratatui/ratatui-crossterm)
   Compiling rustls-webpki v0.103.15
   Compiling icu_properties v2.0.1
   Compiling icu_normalizer v2.0.0
   Compiling hyper-util v0.1.16
   Compiling csscolorparser v0.6.2
   Compiling rsa v0.9.10
   Compiling iana-time-zone v0.1.63
   Compiling rand_chacha v0.9.0
   Compiling uuid v1.18.0
   Compiling ed25519-dalek v2.2.0
   Compiling idna_adapter v1.2.1
   Compiling pin-project-internal v1.1.10
   Compiling snafu-derive v0.8.6
   Compiling idna v1.0.3
   Compiling p256 v0.13.2
   Compiling p384 v0.13.1
   Compiling pem v3.0.5
   Compiling regex v1.11.1
   Compiling euclid v0.22.11
   Compiling palette_math v0.7.7
   Compiling octocrab v0.54.2
   Compiling ratatui-macros v0.7.2 (/home/isaac/Workspaces/ratatui/ratatui-macros)
   Compiling deltae v0.3.2
   Compiling ratatui v0.30.2 (/home/isaac/Workspaces/ratatui/ratatui)
   Compiling bitflags v1.3.2
   Compiling ryu v1.0.20
   Compiling iri-string v0.7.8
   Compiling ratatui-state-examples v0.0.0 (/home/isaac/Workspaces/ratatui/examples/concepts/state)
   Compiling clap_lex v1.0.0
   Compiling android-tzdata v0.1.1
   Compiling wezterm-input-types v0.1.0
   Compiling serde_urlencoded v0.7.1
   Compiling chrono v0.4.41
   Compiling wezterm-color-types v0.3.0
   Compiling jsonwebtoken v10.3.0
   Compiling clap_builder v4.6.7
   Compiling fancy-regex v0.11.0
   Compiling palette v0.7.7
   Compiling hyper-timeout v0.5.2
   Compiling url v2.5.4
   Compiling crossbeam-channel v0.5.15
   Compiling pin-project v1.1.10
   Compiling wezterm-blob-leases v0.1.1
   Compiling rand v0.9.2
   Compiling pest_derive v2.8.1
   Compiling futures v0.3.31
   Compiling http-serde v2.1.1
   Compiling serde_path_to_error v0.1.17
   Compiling wezterm-bidi v0.2.3
   Compiling filedescriptor v0.8.3
   Compiling tower-http v0.6.6
   Compiling num-derive v0.4.2
   Compiling clap_derive v4.6.7
   Compiling http-body-util v0.1.3
   Compiling vtparse v0.6.2
   Compiling csscolorparser v0.9.0
   Compiling uuid v0.8.2
   Compiling secrecy v0.10.3
   Compiling termios v0.3.3
   Compiling simplerand v1.6.0
   Compiling hex v0.4.3
   Compiling web-time v1.1.0
   Compiling tokio-rustls v0.26.2
   Compiling fixedbitset v0.4.2
   Compiling arc-swap v1.7.1
   Compiling finl_unicode v1.3.0
   Compiling memmem v0.1.1
   Compiling symlink v0.1.0
   Compiling fakeit v1.4.1
   Compiling hyper-rustls v0.27.7
   Compiling tracing-appender v0.2.5
   Compiling colorgrad v0.9.0
   Compiling snafu v0.8.6
   Compiling tokio-stream v0.1.19
   Compiling rand_chacha v0.10.0
   Compiling line_drawing v1.0.1
   Compiling termina v0.4.0
   Compiling constraints v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/constraints)
   Compiling mouse-drawing v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/mouse-drawing)
   Compiling volatility-surface v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/volatility-surface)
   Compiling termwiz v0.23.3
   Compiling flex v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/flex)
   Compiling table v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/table)
   Compiling ratatui-termina v0.1.0 (/home/isaac/Workspaces/ratatui/ratatui-termina)
   Compiling modifiers v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/modifiers)
   Compiling hyperlink v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/hyperlink)
   Compiling constraint-explorer v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/constraint-explorer)
   Compiling canvas v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/canvas)
   Compiling clap v4.6.7
   Compiling widget-ref-container v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/widget-ref-container)
   Compiling tracing v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/tracing)
   Compiling minimal v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/minimal)
   Compiling hello-world v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/hello-world)
   Compiling scrollbar v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/scrollbar)
   Compiling calendar-explorer v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/calendar-explorer)
   Compiling colors-rgb v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/colors-rgb)
   Compiling demo2 v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/demo2)
   Compiling user-input v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/user-input)
   Compiling popup v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/popup)
   Compiling demo v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/demo)
   Compiling custom-widget v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/custom-widget)
   Compiling advanced-widget-impl v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/advanced-widget-impl)
   Compiling weather v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/weather)
   Compiling color-explorer v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/color-explorer)
   Compiling gauge v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/gauge)
   Compiling release-header v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/release-header)
   Compiling todo-list v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/todo-list)
   Compiling medical-file-analyzer v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/medical-file-analyzer)
   Compiling chart v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/chart)
   Compiling input-form v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/input-form)
   Compiling inline v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/inline)
   Compiling ratatui-termwiz v0.1.2 (/home/isaac/Workspaces/ratatui/ratatui-termwiz)
   Compiling panic v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/panic)
   Compiling async-github v0.0.0 (/home/isaac/Workspaces/ratatui/examples/apps/async-github)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 54.65s

I rebuilt with the --release flag.

I used adb to transfer it to the phone and Termux to try and run it.

/img/2026-10-ratatui-31.png

But it wouldn’t fire up (though it would remotely in Ubuntu using adb shell). I was going to keep working on it. However, the more I thought on it, the less I wanted to pursue this approach.

Even if I made a fat app, i would prefer a progressive web app or just start fresh.

Summary

The goal here was two-fold. I wanted to work on the Ratatui Rust-based TUI framework. I also wanted to try using the new Google Gemini4 Argon model to do it.

Per the Google Announcement just over a week ago, the prices on Argon will be $2/m input and $10/m output tokens.

The prices keep changing so as of this moment in time (October 12th, 2026) that lines up to (Anthropic) Claude Sonnet 5.5 prices

/img/2026-10-ratatui-35.png

and OpenAI GPT-6.1 sol prices

/img/2026-10-ratatui-36.png

My feeling is we are getting a deal here. I think we are getting a better than Sonnet 5.5 and GPT 6.1 Sol experience, at least with coding.

Yes, it has these odd little pauses when doing a lo of work, but it always comes back with a bunch of well written code after and I think that is far better than my experiences with Gemini 3.8 flash.

I would find Gemini 3.1 Pro good, but wasn’t really the ‘coding’ tool. It did a great job debugging for me, true. But the flash models were what I was using and the latest 3.8 would have far too many mistakes.

I had no mistakes with this Gemini 4 Argon model (and if I did, they were my errors in design). Even if it burns my budget faster, I’ll be switching entirely to Gemini 4 Argon after release.