# TUI Orchestrator: Framework-Based Design ## Philosophy Shift ### From Building Blocks to Framework **Old approach:** Provide individual primitives (keys, bindings, focus) that users wire together manually. **New approach:** Provide complete TUI framework where users define components and library handles everything else. This is a **plugin-play model**: - Library is the runtime - Components are plugins - Extension points allow customization - Everything else is optional with sensible defaults --- ## The "Ready to Use" Vision ### What Users Should Do ```rust // 1. Define component #[derive(Debug, Clone)] enum LoginPage { Username, Password, LoginBtn, } #[derive(Debug, Clone)] enum LoginEvent { AttemptLogin { username: String, password: String }, Cancel, } impl Component for LoginPage { type Focus = LoginPage; type Action = ComponentAction; type Event = LoginEvent; fn targets(&self) -> &[Self::Focus] { ... } fn handle(&mut self, focus: &Self::Focus, action: Self::Action) -> Result> { ... } } // 2. Register and run fn main() -> Result<()> { let mut orch = Orchestrator::builder() .with_page("login", LoginPage::new()) .with_default_bindings() .build()?; orch.run()?; } ``` ### What Library Does **Automatically:** - Input processing (read keys, route to actions) - Focus management (next, prev, set, clear overlay) - Page navigation (call on_exit, swap, call on_enter) - Lifecycle hooks (on_focus, on_blur called at right time) - Default bindings (Tab=Next, Enter=Select, etc.) - Event collection and routing **Never:** - Forces user to write glue code - Requires manual lifecycle management - Makes assumptions about app structure - Requires complex configuration --- ## Extension Model ### Three-Layer Architecture ``` Layer 1: Core Framework (Library) ├── Component trait ├── Orchestrator runtime ├── Default bindings └── Router + lifecycle Layer 2: Extension Points (For komp_ac) ├── ModeResolver - dynamic mode resolution ├── OverlayManager - custom overlay types ├── EventHandler - custom event routing └── FocusNavigation - boundary detection Layer 3: App Logic (User) ├── Page definitions ├── Business logic (gRPC, authentication) └── Rendering ``` ### Layer 1: What Library Provides **Component trait** - The abstraction: - `targets()` - What's focusable - `handle()` - What happens on action - `on_enter/on_exit` - Lifecycle hooks - `on_focus/on_blur` - Focus lifecycle - `handle_text()` - Optional text input - `can_navigate_*()` - Optional boundary detection **Orchestrator** - The runtime: - `register_page()` - Add pages - `navigate_to()` - Page navigation - `process_frame()` - Process one input frame - `run()` - Complete main loop **Standard actions** - Common patterns: - `Next`, `Prev`, `First`, `Last` - Navigation - `Select`, `Cancel` - Selection - `TypeChar`, `Backspace`, `Delete` - Text input - `Custom(usize)` - User extension ### Layer 2: Extension Points Each extension has a **default implementation** that works for simple apps, and a **trait** that komp_ac implements for custom behavior. #### ModeResolver **Default:** Static mode stack ```rust pub struct DefaultModeResolver; impl ModeResolver for DefaultModeResolver { fn resolve(&self, _focus: &dyn Any) -> Vec { vec![ModeName::General] } } ``` **komp_ac extension:** Dynamic Canvas-style mode resolution ```rust pub struct CanvasModeResolver { app_state: AppState, } impl ModeResolver for CanvasModeResolver { fn resolve(&self, focus: &dyn Any) -> Vec { // Check if focus is canvas field // Get editor mode (Edit/ReadOnly) // Return mode stack: [EditorMode, Common, Global] } } ``` **Use case:** Simple app doesn't care about modes. komp_ac needs dynamic resolution based on editor state. #### OverlayManager **Default:** Simple dialog/input overlay ```rust pub struct DefaultOverlayManager { stack: Vec, } impl OverlayManager for DefaultOverlayManager { fn handle_input(&mut self, key: Key) -> Option { ... } } ``` **komp_ac extension:** Complex overlay types (command palette, find file, search palette) ```rust pub struct KompAcOverlayManager { command_bar: CommandBar, find_file: FindFilePalette, search: SearchPalette, } impl OverlayManager for KompAcOverlayManager { fn handle_input(&mut self, key: Key) -> Option { // Route to appropriate overlay } } ``` **Use case:** Simple app uses built-in dialogs. komp_ac needs custom overlays that integrate with editor, gRPC, etc. #### EventHandler **Default:** Return events to user ```rust pub struct DefaultEventHandler; impl EventHandler for DefaultEventHandler { fn handle(&mut self, event: E) -> Result { // Just pass events back to user Ok(HandleResult::Consumed) } } ``` **komp_ac extension:** Route to page/global/canvas handlers ```rust pub struct KompAcEventHandler { router: Router, focus: FocusManager, canvas_handlers: HashMap>, } impl EventHandler for KompAcEventHandler { fn handle(&mut self, event: AppEvent) -> Result { match self.focus.current() { Some(FocusTarget::CanvasField(_)) => self.canvas_handler.handle(event), _ => self.page_handler.handle(event), } } } ``` **Use case:** Simple app just processes events. komp_ac needs complex routing based on focus type and context. ### Layer 3: App Logic **This is entirely user-defined:** - Page structs/enums - Business logic - API calls (gRPC, HTTP) - State management - Rendering The library never touches this. --- ## Key Design Decisions ### 1. Associated Types vs Generics **Choice:** Component trait uses associated types ```rust pub trait Component { type Focus: FocusId; type Action: Action; type Event: Clone + Debug; } ``` **Why:** - One component = one configuration - Type system ensures consistency - Cleaner trait signature **Alternative:** Generics `Component` **Why not:** - More verbose - Type inference harder - Less "component feels like a thing" ### 2. Automatic vs Explicit Navigation **Choice:** Library automatically moves focus on Next/Prev actions **Why:** - Reduces boilerplate - Consistent behavior across apps - Component only needs to know "button was pressed" **Alternative:** Library passes Next/Prev action, component decides what to do **Why not:** - Every component implements same logic - Easy to miss patterns - Library already has FocusManager—use it **Escape hatch:** Components can override with `can_navigate_forward/backward()` ### 3. Event Model **Choice:** Components return `Option`, library collects and returns **Why:** - Library can handle internal events (focus changes, page nav) - Users get clean list of events to process - Decouples component from application **Alternative:** Components emit events directly to channel/bus **Why not:** - Requires async or channels - More complex setup - Library can't orchestrate internal events ### 4. Page vs Component **Choice:** Library doesn't distinguish—everything is a Component **Why:** - Simpler API - User can nest components if needed - Flat hierarchy, easy to understand **Alternative:** Library has `Page` and `Component` concepts **Why not:** - Forces app structure - Some apps don't have pages - More concepts to learn ### 5. Extension Points **Choice:** Extension points are trait objects (`Box + 'static`) **Why:** - Allows komp_ac to pass stateful resolvers - Flexible at runtime - Can be swapped dynamically **Alternative:** Generic with bounds (``) **Why not:** - Monomorphization bloat - Can't store different implementations - Less flexible --- ## Comparison: Building Blocks vs Framework ### Building Blocks (Old Design) **What user writes:** ```rust // Setup let mut focus = FocusManager::new(); let mut bindings = Bindings::new(); let mut router = Router::new(); // Configuration bindings.bind(Key::tab(), MyAction::Next); bindings.bind(Key::enter(), MyAction::Select); focus.set_targets(page.targets()); router.navigate(Page::Login); // Main loop loop { let key = read_key()?; if let Some(action) = bindings.handle(key) { match action { MyAction::Next => focus.next(), MyAction::Select => { let focused = focus.current()?; let result = page.handle_button(focused)?; // Handle result... } } } render(&focus, &router)?; } ``` **Problems:** - Tons of boilerplate - User must understand all systems - Easy to miss lifecycle (forgot to call on_exit?) - Manual wiring everywhere - Every app reinvents same code ### Framework (New Design) **What user writes:** ```rust impl Component for LoginPage { fn targets(&self) -> &[Focus] { ... } fn handle(&mut self, focus: &Focus, action: Action) -> Result> { ... } } fn main() -> Result<()> { let mut orch = Orchestrator::builder() .with_page("login", LoginPage::new()) .build()?; orch.run()?; } ``` **Benefits:** - Zero boilerplate - Library handles everything - Lifecycle automatic - Consistent behavior - Easy to reason about --- ## Extension Strategy for komp_ac ### What komp_ac Keeps komp_ac continues to own: - All page state and logic - gRPC client and authentication - Rendering with ratatui - Canvas editor integration - Command palette logic - Find file palette logic - Business rules ### What komp_ac Replaces komp_ac removes: - `InputOrchestrator` - Uses library's `Orchestrator` - `ActionDecider` routing logic - Uses library's event handler - Manual lifecycle calls - Uses library's automatic hooks - Mode stack assembly - Uses library's `ModeResolver` extension - Overlay management - Uses library's `OverlayManager` extension ### Integration Pattern komp_ac implements `Component` trait for each page: ```rust impl Component for LoginPage { type Focus = FocusTarget; type Action = ResolvedAction; type Event = AppEvent; fn targets(&self) -> &[Self::Focus] { // Return existing focus targets &[FocusTarget::CanvasField(0), FocusTarget::Button(0), ...] } fn handle(&mut self, focus: &Self::Focus, action: Self::Action) -> Result> { // Return existing app events match (focus, action) { (FocusTarget::Button(0), ResolvedAction::Keybind(KeybindAction::Save)) => { Ok(Some(AppEvent::FormSave { path: self.path.clone() })) } _ => Ok(None), } } } ``` komp_ac uses extension points: ```rust let mut orch = Orchestrator::new() .with_mode_resolver(CanvasModeResolver::new(app_state)) .with_overlay_manager(KompAcOverlayManager::new()) .with_event_handler(KompAcEventHandler::new(router, focus)); ``` **Result:** komp_ac uses library's core while keeping all custom behavior. --- ## Future-Proofing ### What Can Be Added Without Breaking Changes 1. **Additional lifecycle hooks:** Add new methods to `Component` trait with default impls 2. **More actions:** Add variants to `ComponentAction` enum 3. **New overlay types:** Implement `OverlayManager` trait 4. **Custom input sources:** Implement `InputSource` trait 5. **Animation support:** Add hooks for frame updates 6. **Accessibility:** Add hooks for screen readers ### What Requires Breaking Changes 1. **Component trait signature:** Changing associated types 2. **Orchestrator API:** Major method signature changes 3. **Extension point contracts:** Changing trait methods **Strategy:** Mark APIs as `#[doc(hidden)]` or `#[deprecated]` before removing. --- ## Summary The redesigned TUI Orchestrator is: 1. **Complete framework** - Not just building blocks 2. **Zero boilerplate** - Users define components, library runs show 3. **Sensible defaults** - Works without configuration 4. **Fully extendable** - Trait-based extension points 5. **komp_ac compatible** - Can replace existing orchestration 6. **User-focused** - "register page" not "bind chord to registry" The library becomes a **TUI runtime** where users write application logic and library handles everything else.