Skip to main content

codetether_agent/tui/ui/
main.rs

1//! Top-level TUI view dispatcher.
2//!
3//! Routes the current `ViewMode` to the appropriate renderer.
4//! All per-view rendering lives in dedicated sub-modules
5//! (see `chat_view`, `sessions`, `inspector`, `webview`, etc.)
6//! to satisfy SRP and the 50-line file limit for new modules.
7
8use ratatui::Frame;
9
10use crate::tui::app::state::App;
11use crate::tui::bus_log::{ProtocolSummary, render_bus_log_with_summary};
12use crate::tui::latency::render_latency;
13use crate::tui::lsp::render_lsp;
14use crate::tui::models::ViewMode;
15use crate::tui::ralph_view::render_ralph_view;
16use crate::tui::rlm::render_rlm;
17use crate::tui::settings::render_settings;
18use crate::tui::swarm_view::render_swarm_view;
19use crate::tui::symbol_search::render_symbol_search;
20
21use super::chat_view::render_chat_view;
22use super::sessions::render_sessions_view;
23
24/// Top-level entry point called by the TUI event loop on every frame.
25///
26/// Dispatches to the active [`ViewMode`] renderer and renders overlays.
27///
28/// # Examples
29///
30/// ```rust,no_run
31/// # use codetether_agent::tui::ui::main::ui;
32/// # fn demo(f: &mut ratatui::Frame, app: &mut codetether_agent::tui::app::state::App, sess: &codetether_agent::session::Session) {
33/// ui(f, app, sess);
34/// # }
35/// ```
36pub fn ui(f: &mut Frame, app: &mut App, session: &crate::session::Session) {
37    dispatch_view(f, app, session);
38    render_overlays(f, app);
39}
40
41fn dispatch_view(f: &mut Frame, app: &mut App, session: &crate::session::Session) {
42    match app.state.view_mode {
43        ViewMode::Chat => render_chat_or_webview(f, app, session),
44        ViewMode::Sessions => render_sessions_view(f, app),
45        ViewMode::Swarm => render_swarm_view(f, &mut app.state.swarm, f.area()),
46        ViewMode::Ralph => render_ralph_view(f, &mut app.state.ralph, f.area()),
47        ViewMode::Bus => render_bus_view(f, app),
48        ViewMode::Model => crate::tui::model_picker::render_model_picker(f, f.area(), app, session),
49        ViewMode::Settings => render_settings(f, f.area(), &app.state),
50        ViewMode::Lsp => render_lsp(f, f.area(), &app.state.cwd_display, &app.state.status),
51        ViewMode::Rlm => render_rlm(
52            f,
53            f.area(),
54            &app.state.cwd_display,
55            &app.state.status,
56            app.state.sessions.len(),
57            app.state.selected_session,
58        ),
59        ViewMode::Latency => render_latency(f, f.area(), app),
60        ViewMode::Protocol => {}
61        ViewMode::FilePicker => crate::tui::app::file_picker::render_file_picker(f, f.area(), app),
62        ViewMode::Inspector => super::inspector::render_inspector_view(f, app),
63    }
64}
65
66fn render_chat_or_webview(f: &mut Frame, app: &mut App, session: &crate::session::Session) {
67    if super::webview::layout::is_webview(app.state.chat_layout_mode) {
68        super::webview::render(f, app);
69    } else {
70        render_chat_view(f, app, session);
71    }
72}
73
74fn render_bus_view(f: &mut Frame, app: &mut App) {
75    let mut registered_agents = app
76        .state
77        .worker_bridge_registered_agents
78        .iter()
79        .cloned()
80        .collect::<Vec<_>>();
81    registered_agents.sort();
82    let summary = ProtocolSummary {
83        cwd_display: app.state.cwd_display.clone(),
84        worker_id: app.state.worker_id.clone(),
85        worker_name: app.state.worker_name.clone(),
86        a2a_connected: app.state.a2a_connected,
87        processing: app.state.worker_bridge_processing_state,
88        registered_agents,
89        queued_tasks: app.state.worker_task_queue.len(),
90        recent_task: app.state.recent_tasks.last().cloned(),
91    };
92    render_bus_log_with_summary(f, &mut app.state.bus_log, f.area(), Some(summary))
93}
94
95fn render_overlays(f: &mut Frame, app: &mut App) {
96    if app.state.symbol_search.loading
97        || !app.state.symbol_search.query.is_empty()
98        || !app.state.symbol_search.results.is_empty()
99        || app.state.symbol_search.error.is_some()
100    {
101        render_symbol_search(f, &mut app.state.symbol_search, f.area());
102    }
103
104    if app.state.watchdog_notification.is_some() {
105        crate::tui::app::watchdog::render_watchdog_notification(f, f.area(), &app.state);
106    }
107}