Files
pages-tui/PLAN.md

16 KiB

TUI Orchestrator - Complete TUI Framework

Overview

tui_orchestrator is a ready-to-use TUI framework that provides a complete runtime for building terminal user interfaces. Users define their pages, buttons, and logic—library handles everything else: input routing, focus management, page navigation, lifecycle hooks, and event orchestration.

Key Philosophy

"Register pages with buttons and logic—it just works."

The library is a complete application framework where:

  • User defines components (pages with focusable elements)
  • Library orchestrates all runtime concerns
  • Everything is optional—define what you need
  • Fully extendable for complex apps like komp_ac

Zero Boilerplate

Users write:

impl Component for LoginPage {
    fn targets(&self) -> &[Focus];
    fn handle(&mut self, focus: &Focus, action: Action) -> Result<Option<Event>> {
        // What happens when button pressed
    }
}

Library handles:

  • Input processing
  • Focus management
  • Page navigation
  • Lifecycle hooks
  • Event routing
  • Default keybindings

Extension Model

komp_ac can use the library for 90% of functionality while extending:

  • Mode resolution (dynamic Canvas-style modes)
  • Overlay management (command palette, find file, search)
  • Event routing (global vs page vs canvas actions)
  • Custom behaviors (boundary detection, navigation rules)

Defaults work, override what's different.


Architecture

┌─────────────────────────────────────────────────┐
│  User Code (What You Define)                     │
│                                                 │
│  Component trait                                   │
│  - Page structs/enums                            │
│  - Focus targets (buttons, fields)                 │
│  - Button logic (what happens on press)            │
│  - Lifecycle hooks (optional)                       │
└─────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────┐
│  Orchestrator (Library Runtime)                   │
│                                                 │
│  - ComponentRegistry                                 │
│  - FocusManager                                    │
│  - Bindings (default + custom)                     │
│  - Router + history                                │
│  - ModeStack                                       │
│  - OverlayStack                                    │
│  - EventBus                                        │
└─────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────┐
│  Extension Points (For komp_ac)                   │
│                                                 │
│  - ModeResolver (dynamic mode resolution)              │
│  - OverlayManager (custom overlay types)               │
│  - EventHandler (custom event routing)                │
│  - FocusNavigation (boundary detection)               │
└─────────────────────────────────────────────────┘

Implementation Phases

Phase 1: Core Foundation COMPLETE

Completed:

  • src/input/ - Key types, bindings, sequence handling
  • src/focus/ - Focus manager, queries, traits

What this provides:

  • Backend-agnostic key representation
  • Key-to-action mappings
  • Focus tracking with navigation
  • Generic focus IDs (user-defined enums, usize, String, etc.)

Phase 2: Component System (CURRENT)

Goal: Unified abstraction for pages/components.

Files to create:

  • src/component/mod.rs - Component trait
  • src/component/action.rs - Standard component actions
  • src/component/error.rs - Component-specific errors

Component Trait:

pub trait Component {
    type Focus: FocusId + Clone;
    type Action: Action + Clone;
    type Event: Clone + core::fmt::Debug;
    
    fn targets(&self) -> &[Self::Focus];
    fn handle(&mut self, focus: &Self::Focus, action: Self::Action) -> Result<Option<Self::Event>>;
    
    fn on_enter(&mut self) -> Result<()> { Ok(()) }
    fn on_exit(&mut self) -> Result<()> { Ok(()) }
    fn on_focus(&mut self, focus: &Self::Focus) -> Result<()> { Ok(()) }
    fn on_blur(&mut self, focus: &Self::Focus) -> Result<()> { Ok(()) }
    fn handle_text(&mut self, focus: &Self::Focus, ch: char) -> Result<Option<Self::Event>> { Ok(None) }
    fn can_navigate_forward(&self, focus: &Self::Focus) -> bool { true }
    fn can_navigate_backward(&self, focus: &Self::Focus) -> bool { true }
}

Standard Component Actions:

pub enum ComponentAction {
    Next,       // Tab by default
    Prev,        // Shift+Tab by default
    First,
    Last,
    Select,      // Enter by default
    Cancel,      // Esc by default
    TypeChar(char),
    Backspace,
    Delete,
    Custom(usize), // User extension
}

Phase 3: Router & Lifecycle

Goal: Page navigation with automatic lifecycle hooks.

Files to create:

  • src/router/mod.rs - Router trait and implementation
  • src/router/history.rs - Navigation history

Router API:

pub struct Router<C: Component> {
    pages: alloc::collections::HashMap<String, C>,
    current: Option<String>,
    history: alloc::vec::Vec<String>,
}

impl<C: Component> Router<C> {
    pub fn new() -> Self;
    pub fn navigate(&mut self, id: &str) -> Result<()>;
    pub fn back(&mut self) -> Result<Option<()>>;
    pub fn forward(&mut self) -> Result<Option<()>>;
    pub fn current(&self) -> Option<&C>;
}

Automatic behavior:

  • navigate() calls old_page.on_exit() → swaps page → calls new_page.on_enter()
  • back()/forward() manage history stack
  • Focus targets auto-updated from targets()

Phase 4: Orchestrator (The Core Runtime)

Goal: Wire everything together into complete TUI runtime.

Files to create:

  • src/orchestrator/mod.rs - Orchestrator struct
  • src/orchestrator/modes.rs - Mode stack and resolution
  • src/orchestrator/overlays.rs - Overlay stack
  • src/orchestrator/events.rs - Event bus
  • src/orchestrator/bindings.rs - Default + custom bindings

Orchestrator API:

pub struct Orchestrator<C: Component> {
    registry: ComponentRegistry<C>,
    focus: FocusManager<C::Focus>,
    bindings: Bindings<ComponentAction>,
    router: Router<C>,
    modes: ModeStack<ModeName>,
    overlays: OverlayStack<C::Event>,
    events: EventBus<C::Event>,
}

impl<C: Component> Orchestrator<C> {
    pub fn new() -> Self;
    
    pub fn register_page(&mut self, id: &str, page: C) -> Result<()>;
    pub fn navigate_to(&mut self, id: &str) -> Result<()>;
    
    pub fn process_frame(&mut self, key: Key) -> Result<alloc::vec::Vec<C::Event>>;
    pub fn run<I: InputSource>(&mut self, input: I) -> Result<()>;
    
    // Extension points
    pub fn set_mode_resolver<R: ModeResolver + 'static>(&mut self, resolver: R);
    pub fn set_overlay_manager<O: OverlayManager + 'static>(&mut self, manager: O);
    pub fn set_event_handler<H: EventHandler<C::Event> + 'static>(&mut self, handler: H);
}

Process flow:

  1. Check overlay active → route to overlay
  2. Get current mode + focus
  3. Lookup binding → get action
  4. Get current component
  5. Call component.handle(action, focus)
  6. Collect events returned
  7. Handle internal events (focus changes, page nav)
  8. Return external events to user

Phase 5: Extension Traits

Goal: Provide extension points for komp_ac's custom behavior.

Files to create:

  • src/extension/mod.rs - Extension traits
  • src/extension/mode.rs - Mode resolver
  • src/extension/overlay.rs - Overlay manager
  • src/extension/event.rs - Event handler

ModeResolver (for dynamic mode resolution):

pub trait ModeResolver {
    fn resolve(&self, focus: &dyn core::any::Any) -> alloc::vec::Vec<ModeName>;
}

pub struct DefaultModeResolver;
impl ModeResolver for DefaultModeResolver {
    fn resolve(&self, _focus: &dyn core::any::Any) -> alloc::vec::Vec<ModeName> {
        alloc::vec![ModeName::General]
    }
}

OverlayManager (for custom overlays):

pub trait OverlayManager {
    fn is_active(&self) -> bool;
    fn handle_input(&mut self, key: Key) -> Option<OverlayResult>;
}

pub enum OverlayResult {
    Dismissed,
    Selected(OverlayData),
    Continue,
}

EventHandler (for custom event routing):

pub trait EventHandler<E> {
    fn handle(&mut self, event: E) -> Result<HandleResult>;
}

pub enum HandleResult {
    Consumed,
    Forward,
    Navigate(String),
}

Phase 6: Builder & Defaults

Goal: Easy setup with sensible defaults.

Files to create:

  • src/builder/mod.rs - Builder pattern
  • src/defaults/bindings.rs - Preset keybindings

Builder API:

impl<C: Component> Orchestrator<C> {
    pub fn builder() -> Builder<C> { Builder::new() }
}

pub struct Builder<C: Component> {
    orchestrator: Orchestrator<C>,
}

impl<C: Component> Builder<C> {
    pub fn with_page(mut self, id: &str, page: C) -> Result<Self>;
    pub fn with_default_bindings(mut self) -> Self;
    pub fn with_mode_resolver<R: ModeResolver + 'static>(mut self, resolver: R) -> Self;
    pub fn with_overlay_manager<O: OverlayManager + 'static>(mut self, manager: O) -> Self;
    pub fn build(self) -> Result<Orchestrator<C>>;
}

Default bindings:

pub fn default_bindings<A: Action>() -> Bindings<ComponentAction> {
    let mut bindings = Bindings::new();
    bindings.bind(Key::tab(), ComponentAction::Next);
    bindings.bind(Key::shift_tab(), ComponentAction::Prev);
    bindings.bind(Key::enter(), ComponentAction::Select);
    bindings.bind(Key::esc(), ComponentAction::Cancel);
    bindings.bind(Key::ctrl('c'), ComponentAction::Custom(0)); // Quit
}

Phase 7: Integration (Optional)

Goal: Seamless integration with komp_ac.

Files to create:

  • src/integration/mod.rs - Integration helpers
  • src/integration/komp_ac.rs - komp_ac-specific adapters

Adapter pattern:

impl Component for komp_ac::LoginPage {
    type Focus = komp_ac::FocusTarget;
    type Action = komp_ac::ResolvedAction;
    type Event = komp_ac::AppEvent;
    
    fn targets(&self) -> &[Self::Focus] { ... }
    fn handle(&mut self, focus: &Self::Focus, action: Self::Action) -> Result<Option<Self::Event>> { ... }
}

File Structure

src/
├── lib.rs                 # Routing only, re-exports
├── prelude.rs             # Common imports
│
├── input/                 # Phase 1 ✅
│   ├── mod.rs
│   ├── key.rs             # KeyCode, KeyModifiers
│   ├── bindings.rs        # Bindings
│   ├── handler.rs         # InputHandler
│   ├── result.rs         # MatchResult
│   └── action.rs         # Action trait
│
├── focus/                 # Phase 1 ✅
│   ├── mod.rs
│   ├── id.rs              # FocusId trait
│   ├── manager.rs         # FocusManager
│   ├── query.rs           # FocusQuery
│   ├── error.rs           # FocusError
│   └── traits.rs          # Focusable
│
├── component/             # Phase 2
│   ├── mod.rs
│   ├── trait.rs           # Component trait
│   ├── action.rs          # ComponentAction
│   └── error.rs          # ComponentError
│
├── router/                # Phase 3
│   ├── mod.rs
│   ├── router.rs         # Router
│   └── history.rs         # HistoryStack
│
├── orchestrator/          # Phase 4
│   ├── mod.rs
│   ├── core.rs           # Orchestrator
│   ├── modes.rs          # ModeStack, ModeResolver
│   ├── overlays.rs        # OverlayStack
│   ├── bindings.rs        # Component bindings
│   └── events.rs         # EventBus
│
├── extension/             # Phase 5
│   ├── mod.rs
│   ├── mode.rs           # ModeResolver trait
│   ├── overlay.rs         # OverlayManager trait
│   └── event.rs          # EventHandler trait
│
├── builder/               # Phase 6
│   ├── mod.rs
│   ├── builder.rs         # Builder pattern
│   └── defaults.rs       # Default bindings
│
└── integration/           # Phase 7
    ├── mod.rs
    └── komp_ac.rs        # komp_ac adapters

tests/                     # Mirror src/ structure
├── input/
├── focus/
├── component/
├── router/
├── orchestrator/
└── integration/

Design Principles

From AGENTS.md

  • Feature-based tree structure—group by domain
  • Each feature is self-contained—handler, logic, types, tests
  • Functional programming style—pure functions, stateless where possible
  • Use structs, traits, enums, impl, match over if
  • No Arc/Mutex/RefCell
  • Result<T, E> everywhere
  • mod.rs is for routing only
  • No comments unless necessary

Additional for Framework

  • Batteries included—not just building blocks
  • Sensible defaults—zero configuration works
  • Optional everything—define only what you need
  • Extension points—override defaults when needed
  • no_std compatible—works on embedded, WASM
  • Backend-agnostic—no crossterm/ratatui dependencies
  • User-focused—"register page" not "register_chord"

Dependencies

Core (no_std)

  • alloc - For dynamic collections (Vec, HashMap)

Optional Features

  • std - Enable std library support
  • sequences - Enable multi-key sequences

User Experience

Before (Building Blocks)

Users must manually wire everything:

  • Create focus manager
  • Create bindings
  • Create router
  • Set up components
  • Write main loop
  • Handle lifecycle manually

Result: Lots of boilerplate, easy to get wrong.

After (Framework)

Users define components and run:

#[derive(Debug, Clone)]
struct LoginPage;

impl Component for LoginPage {
    fn targets(&self) -> &[Focus] { ... }
    fn handle(&mut self, focus: &Focus, action: Action) -> Result<Option<Event>> { ... }
}

fn main() -> Result<()> {
    let mut orch = Orchestrator::new();
    orch.register_page("login", LoginPage::new())?;
    orch.run()?;
}

Result: Zero boilerplate, everything just works.


Migration for komp_ac

Before Integration

komp_ac has:

  • Custom orchestrator
  • Custom mode resolution (Canvas-style)
  • Custom overlays (command bar, find file, search)
  • Custom action routing (page vs canvas vs global)

After Integration

komp_ac:

  1. Implements Component trait for all pages
  2. Uses library's Orchestrator as runtime
  3. Extends with custom ModeResolver
  4. Extends with custom OverlayManager
  5. Extends with custom EventHandler

Result:

  • Library handles 90% of runtime
  • komp_ac keeps all custom behavior
  • No code duplication
  • Cleaner, more maintainable codebase

Feature Checklist

  • Phase 1: Input handling (keys, bindings, focus)
  • Phase 2: Component trait and actions
  • Phase 3: Router with lifecycle
  • Phase 4: Orchestrator runtime
  • Phase 5: Extension traits
  • Phase 6: Builder and defaults
  • Phase 7: Integration with komp_ac
  • Documentation updates
  • Example applications
  • Full test coverage