Recreate repository due to Git object corruption (all files preserved)
This commit is contained in:
540
PLAN.md
Normal file
540
PLAN.md
Normal file
@@ -0,0 +1,540 @@
|
||||
# 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:
|
||||
```rust
|
||||
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:**
|
||||
|
||||
```rust
|
||||
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:**
|
||||
|
||||
```rust
|
||||
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:**
|
||||
|
||||
```rust
|
||||
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:**
|
||||
|
||||
```rust
|
||||
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):**
|
||||
|
||||
```rust
|
||||
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):**
|
||||
|
||||
```rust
|
||||
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):**
|
||||
|
||||
```rust
|
||||
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:**
|
||||
|
||||
```rust
|
||||
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:**
|
||||
|
||||
```rust
|
||||
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:**
|
||||
|
||||
```rust
|
||||
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:
|
||||
|
||||
```rust
|
||||
#[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
|
||||
|
||||
- [x] 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
|
||||
Reference in New Issue
Block a user