App Structure

This page documents the App struct, which is the central coordinator for the terminal user interface. The App manages widgets, handles events, coordinates with the controller, and orchestrates rendering.

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                         App                                      │
├─────────────────────────────────────────────────────────────────┤
│  Controller Communication                                        │
│  ├── to_controller: Sender<ControllerInputPayload>              │
│  └── from_controller: Receiver<UiMessage>                       │
├─────────────────────────────────────────────────────────────────┤
│  Widget System                                                   │
│  ├── widgets: HashMap<ID, Box<dyn Widget>>                      │
│  ├── widget_priority_order: Vec<ID>                             │
│  └── conversation_view: Box<dyn ConversationView>               │
├─────────────────────────────────────────────────────────────────┤
│  Session State                                                   │
│  ├── session_id, model_name                                     │
│  ├── context_used, context_limit                                │
│  └── user_turn_counter                                          │
├─────────────────────────────────────────────────────────────────┤
│  Key Handling                                                    │
│  ├── key_handler: Box<dyn KeyHandler>                           │
│  └── exit_handler: Option<Box<dyn ExitHandler>>                 │
├─────────────────────────────────────────────────────────────────┤
│  Layout                                                          │
│  └── layout_template: LayoutTemplate                            │
└─────────────────────────────────────────────────────────────────┘

App Struct

The main application struct contains all TUI state:

pub struct App {
    // Identification
    agent_name: String,
    version: String,

    // Command system
    commands: Vec<Box<dyn SlashCommand>>,
    command_extension: Option<Box<dyn std::any::Any + Send>>,

    // Controller communication
    to_controller: Option<ToControllerTx>,
    from_controller: Option<FromControllerRx>,
    controller: Option<Arc<LLMController>>,

    // Session state
    session_id: i64,
    user_turn_counter: i64,
    model_name: String,
    context_used: i64,
    context_limit: i32,

    // Widget management
    pub widgets: HashMap<&'static str, Box<dyn Widget>>,
    pub widget_priority_order: Vec<&'static str>,

    // Conversation display
    conversation_view: Box<dyn ConversationView>,
    conversation_factory: ConversationViewFactory,

    // Animation state
    throbber_state: ThrobberState,
    waiting_for_response: bool,
    waiting_started: Option<Instant>,
    animation_frame_counter: u8,

    // Turn tracking
    current_turn_id: Option<TurnId>,
    executing_tools: HashSet<String>,

    // UI configuration
    layout_template: LayoutTemplate,
    key_handler: Box<dyn KeyHandler>,
    exit_handler: Option<Box<dyn ExitHandler>>,
}

Field Categories

Identification

FieldTypePurpose
agent_nameStringDisplay name for the agent
versionStringVersion string for status bar

Controller Communication

FieldTypePurpose
to_controllerSender<ControllerInputPayload>Send user input to controller
from_controllerReceiver<UiMessage>Receive updates from controller
controllerArc<LLMController>Direct controller reference

Session State

FieldTypePurpose
session_idi64Current session identifier
user_turn_counteri64Tracks user message count
model_nameStringCurrent LLM model name
context_usedi64Tokens used in context
context_limiti32Maximum context tokens

Widget Management

FieldTypePurpose
widgetsHashMap<&'static str, Box<dyn Widget>>Registered widgets by ID
widget_priority_orderVec<&'static str>Key handling order
conversation_viewBox<dyn ConversationView>Chat display widget
conversation_factoryConversationViewFactoryCreates new views

Processing State

FieldTypePurpose
waiting_for_responseboolAwaiting LLM response
waiting_startedOption<Instant>When waiting began
executing_toolsHashSet<String>Tools currently running
current_turn_idOption<TurnId>Current conversation turn
throbber_stateThrobberStateAnimation state
animation_frame_counteru8Frame counter for animations

Initialization

The App uses a builder-style initialization:

pub fn with_config(config: AppConfig) -> Self {
    // Initialize default theme
    let theme_name = default_theme_name();
    if let Some(theme) = get_theme(theme_name) {
        init_theme(theme_name, theme);
    }

    // Use provided commands or defaults
    let commands = config.commands.unwrap_or_else(default_commands);

    // Create conversation factory
    let default_factory: ConversationViewFactory = Box::new(|| {
        Box::new(ChatView::new())
    });

    let mut app = Self {
        agent_name: config.agent_name,
        version: config.version,
        commands,
        widgets: HashMap::new(),
        widget_priority_order: Vec::new(),
        // ... other fields
    };

    // Register default widgets
    app.register_widget(StatusBar::new());

    app
}

AppConfig

Configuration options for App creation:

pub struct AppConfig {
    pub agent_name: String,
    pub version: String,
    pub commands: Option<Vec<Box<dyn SlashCommand>>>,
    pub key_handler: Option<Box<dyn KeyHandler>>,
    pub exit_handler: Option<Box<dyn ExitHandler>>,
    pub layout_template: Option<LayoutTemplate>,
    pub conversation_factory: Option<ConversationViewFactory>,
    pub status_bar_config: Option<StatusBarConfig>,
    pub command_extension: Option<Box<dyn std::any::Any + Send>>,
}

Widget Registration

Widgets are registered and managed by ID:

impl App {
    pub fn register_widget<W: Widget>(&mut self, widget: W) {
        let id = widget.id();
        self.widgets.insert(id, Box::new(widget));
        self.rebuild_priority_order();
    }

    fn rebuild_priority_order(&mut self) {
        let mut ordered: Vec<_> = self.widgets.iter()
            .map(|(id, w)| (*id, w.priority()))
            .collect();
        ordered.sort_by(|a, b| b.1.cmp(&a.1)); // Descending priority
        self.widget_priority_order = ordered.into_iter().map(|(id, _)| id).collect();
    }
}

Widget Access

Access widgets by ID with type casting:

impl App {
    pub fn widget<W: Widget + 'static>(&self, id: &str) -> Option<&W> {
        self.widgets.get(id)
            .and_then(|w| w.as_any().downcast_ref::<W>())
    }

    pub fn widget_mut<W: Widget + 'static>(&mut self, id: &str) -> Option<&mut W> {
        self.widgets.get_mut(id)
            .and_then(|w| w.as_any_mut().downcast_mut::<W>())
    }
}

Controller Connection

Connect App to the LLM controller:

impl App {
    pub fn connect_controller(
        &mut self,
        to_controller: ToControllerTx,
        from_controller: FromControllerRx,
        controller: Arc<LLMController>,
    ) {
        self.to_controller = Some(to_controller);
        self.from_controller = Some(from_controller);
        self.controller = Some(controller);
    }
}

Session Management

Update session state:

impl App {
    pub fn set_session(
        &mut self,
        session_id: i64,
        model_name: String,
        context_limit: i32,
    ) {
        self.session_id = session_id;
        self.model_name = model_name;
        self.context_limit = context_limit;
        self.context_used = 0;
    }

    pub fn update_context(&mut self, used: i64, limit: i32) {
        self.context_used = used;
        self.context_limit = limit;
    }
}

State Layers

The App maintains multiple state layers:

App State
├── Global State
│   ├── Theme (static RwLock)
│   ├── Agent name, version
│   └── Slash commands

├── Session State
│   ├── Current session ID
│   ├── Model name
│   ├── Context usage
│   └── Turn counter

├── Conversation State (per session)
│   ├── Message history
│   ├── Scroll position
│   └── Streaming buffer

├── Processing State
│   ├── Waiting for response
│   ├── Executing tools
│   └── Current turn ID

└── Widget State
    ├── Active/inactive status
    ├── Widget-specific data
    └── Focus state

Built-in Widget IDs

pub mod widget_ids {
    pub const CHAT_VIEW: &str = "chat_view";
    pub const TEXT_INPUT: &str = "text_input";
    pub const STATUS_BAR: &str = "status_bar";
    pub const PERMISSION_PANEL: &str = "permission_panel";
    pub const QUESTION_PANEL: &str = "question_panel";
    pub const SLASH_POPUP: &str = "slash_popup";
    pub const SESSION_PICKER: &str = "session_picker";
    pub const THEME_PICKER: &str = "theme_picker";
}

Next Steps