Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Terminal User Interface architecture

The cerebro TUI, in crates/cerebro-tui, is a Ratatui-based terminal app that provides interactive browsing of project activity data. It uses the Component + Action architecture pattern recommended by the Ratatui community.

Architecture pattern

Component trait

All UI components implement the Component trait:

#![allow(unused)]
fn main() {
pub trait Component {
    fn init(&mut self, area: Size) -> Result<()>;
    fn handle_events(&mut self, event: Option<Event>) -> Result<Option<Action>>;
    fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>>;
    fn handle_mouse_event(&mut self, mouse: MouseEvent) -> Result<Option<Action>>;
    fn update(&mut self, action: Action) -> Result<Option<Action>>;
    fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()>;
    fn as_any_mut(&mut self) -> &mut dyn Any;
    fn help_entries(&self) -> Vec<HelpEntry>;
}
}

Each component encapsulates its own state, including scroll positions, filter selections, and text input focus, and communicates with other components through the Action dispatch system.

Action dispatch

The Action enum is the single communication channel between components and the Home orchestrator:

Component ──Action──> Home ──Action──> Component

Actions fall into these categories:

CategoryExamples
LifecycleTick, Render, Resize, Quit, ClearScreen
Data loadingDataLoaded, LoadError, TriggerRefresh, RefreshComplete
NavigationSwitchView, SelectProject, SwitchTab
UI stateShowPreview, ClosePreview, SetInputMode
ConfigToggleProjectActive, SubmitAddProject, ConfigUpdated
BuildTriggerBuild, BuildOutput, BuildComplete, CloseBuildLog
Text inputStartJournalEntry, StartManualNote, SaveInput, CancelInput

Key binding system

A context-based system resolves key bindings to prevent collisions:

ContextKeysPurpose
Globalq, r, b, ?, Space, 1-5Always active: quit, refresh, build, help, leader key, view switching
Sidebarj/k, arrows, Tab, lNavigate sidebar list, switch focus to content
ContentTab, Shift+Tab, hSwitch focus back to sidebar

The leader key, Space, followed by d/p/j/t/c provides an alternative view switching path.

Component hierarchy

Home (orchestrator)
├── Sidebar (project list, view navigation)
├── Dashboard (summary cards, project table)
├── Projects (tabbed sessions/commits/todos, note editor)
├── Journal (date-grouped entries, entry editor)
├── Todos (filterable table, search)
├── Config (project toggle list, add-project form)
├── Help (keybinding overlay)
└── Widgets (shared)
    ├── Spinner (loading indicator)
    ├── Toast (notifications)
    ├── EmptyState (no-data display)
    ├── SummaryCard (dashboard stats)
    ├── BuildLog (build output modal)
    └── Modal (generic overlay)

Data flow

The TUI follows a Flux-inspired data flow:

1. Home.do_initial_load_sync() collects data from cortex
2. Action::DataLoaded(contexts) dispatched to all components
3. Each component stores relevant data in its own state
4. User interaction → Component.handle_key_event() → Action
5. Home.update() processes Action, may dispatch follow-up Actions
6. Components receive Actions via Component.update()
7. Frame render → Component.draw() reads from local state

Async operations

Long-running operations spawn tokio tasks and report back via Actions:

  • Data refresh: trigger_refresh() spawns collect_all() in a tokio task, sends DataLoaded or LoadError on completion
  • Config writes: spawn_config_toggle() writes TOML via toml_edit, sends ConfigUpdated
  • Build: TriggerBuild spawns cerebro build as a subprocess, streams output via BuildOutput events

Input modes

The TUI supports three input modes tracked by InputMode:

ModeDescriptionActive when
NavigationDefault: keys control UIAlways, except during text editing
JournalEntryMulti-line text editing with ratatui-textareaJournal view, editing an entry
ManualNoteMulti-line text editing with ratatui-textareaProjects view, editing status/next sections

In editing modes, the text input widget intercepts most navigation keys. Escape cancels input and returns to Navigation mode.

Testing strategy

The TUI uses three testing layers:

LayerToolPurpose
Unit tests#[cfg(test)]Component state transitions, action handling, key binding parsing
Integration testsratatui::backend::TestBackendRender full app to buffer, assert on content
BDD testsCucumber, defined in tests/cucumber.rsUser-facing scenarios in Gherkin
System testsGhostty devtoolsTerminal lifecycle, resize, responsiveness

Integration tests construct Home directly, call set_cerebro_config() and do_initial_load_sync().await, then render and assert against the buffer. BDD tests assert against real component state, such as home.view_name(), not buffer text.