Recreate repository due to Git object corruption (all files preserved)
This commit is contained in:
497
REDESIGN.md
Normal file
497
REDESIGN.md
Normal file
@@ -0,0 +1,497 @@
|
||||
# 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<Option<Self::Event>> { ... }
|
||||
}
|
||||
|
||||
// 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<ModeName> {
|
||||
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<ModeName> {
|
||||
// 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<Overlay>,
|
||||
}
|
||||
|
||||
impl OverlayManager for DefaultOverlayManager {
|
||||
fn handle_input(&mut self, key: Key) -> Option<OverlayResult> { ... }
|
||||
}
|
||||
```
|
||||
|
||||
**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<OverlayResult> {
|
||||
// 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<E>;
|
||||
|
||||
impl<E> EventHandler for DefaultEventHandler<E> {
|
||||
fn handle(&mut self, event: E) -> Result<HandleResult> {
|
||||
// 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<Page, Box<dyn CanvasHandler>>,
|
||||
}
|
||||
|
||||
impl EventHandler for KompAcEventHandler {
|
||||
fn handle(&mut self, event: AppEvent) -> Result<HandleResult> {
|
||||
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<F, A, E>`
|
||||
|
||||
**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<Event>`, 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<dyn Trait> + 'static`)
|
||||
|
||||
**Why:**
|
||||
- Allows komp_ac to pass stateful resolvers
|
||||
- Flexible at runtime
|
||||
- Can be swapped dynamically
|
||||
|
||||
**Alternative:** Generic with bounds (`<R: ModeResolver + Sized>`)
|
||||
|
||||
**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<Option<Event>> { ... }
|
||||
}
|
||||
|
||||
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<Option<Self::Event>> {
|
||||
// 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.
|
||||
Reference in New Issue
Block a user