# Architecture Technical positioning and internal architecture of TypeDialog. ## Positioning TypeDialog is not a terminal prompt library — it is a **form orchestration layer** that sits two abstraction levels above tools like Inquire, Ratatui, or Axum. The comparison point is relevant: Inquire (`inquire = "0.9"`) is one of TypeDialog's dependencies, consumed as the render engine for the CLI backend. A user of TypeDialog never calls `Text::new("Name?").prompt()` directly — that call happens inside `InquireBackend::execute_field_sync`, invisible to the form author. ### Abstraction Stack ```text ┌─────────────────────────────────────────────────────┐ │ typedialog │ │ Declarative TOML forms + i18n + templates + AI │ │ BackendFactory → dispatch by feature │ ├──────────────┬──────────────┬──────────────────────-┤ │ CLI backend │ TUI backend │ Web backend │ │ (Inquire) │ (Ratatui) │ (Axum) │ └──────────────┴──────────────┴───────────────────────┘ │ Nushell plugin (nu-protocol) ``` ### What TypeDialog Is Not | What the primitive does | TypeDialog | |---|---| | Library for use in your CLI app | Is the app — or the library others embed | | Terminal input tool | Multi-surface entry point (CLI, TUI, Web, AI) | | Comparable to Rhai or Nushell | Integrates Nushell via `nu-plugin`/`nu-protocol` as an output channel | ### Differentiators **Unified schema** — a single TOML file describes the same form for CLI, TUI, and HTTP. No per-backend code. **Nushell as integration runtime** — outputs flow as structured data into Nu pipelines via the Nushell plugin protocol (`nu-plugin = "0.110.0"`), not as text. **AI backend** (`typedialog-ai` + `typedialog-agent`) — forms can be generated or processed by agents backed by SurrealDB (`kv-mem`), Tantivy full-text search, and HNSW vector search (`instant-distance`). **Native i18n** — Fluent bundles with system locale detection, no external tooling required. --- ## BackendFactory ### The `FormBackend` Trait All backends implement a single async trait (`backends/mod.rs:27`): ```rust #[async_trait] pub trait FormBackend: Send + Sync { async fn initialize(&mut self) -> Result<()>; async fn render_display_item(&self, item: &DisplayItem, context: &RenderContext) -> Result<()>; async fn execute_field(&self, field: &FieldDefinition, context: &RenderContext) -> Result; // Default impl: falls back to field-by-field via execute_field // TUI and Web override this for complete-form rendering async fn execute_form_complete(...) -> Result>; async fn shutdown(&mut self) -> Result<()>; fn as_any(&self) -> &dyn std::any::Any; fn is_available() -> bool where Self: Sized; fn name(&self) -> &str; } ``` `execute_form_complete` has a default implementation that delegates to `execute_field` sequentially. Backends that need full-form rendering (TUI, Web) override it. ### Factory Dispatch `BackendFactory::create` returns `Box`. The selection is a compile-time `#[cfg(feature)]` gate combined with runtime match (`backends/mod.rs:109`): ```rust pub fn create(backend_type: BackendType) -> Result> { match backend_type { BackendType::Cli => { #[cfg(feature = "cli")] { Ok(Box::new(cli::InquireBackend::new())) } #[cfg(not(feature = "cli"))] { Err(/* clear message */) } } #[cfg(feature = "tui")] BackendType::Tui => Ok(Box::new(tui::RatatuiBackend::new())), #[cfg(feature = "web")] BackendType::Web { port } => Ok(Box::new(web::WebBackend::new(port))), } } ``` The backend code for a disabled feature is not compiled into the binary. The trait object provides runtime polymorphism; the feature gate provides compile-time dead-code elimination. ### Auto-Detection `BackendFactory::auto_detect` follows a fallback chain (`backends/mod.rs:132`): ```text TYPEDIALOG_BACKEND=tui → TUI (if feature active) TYPEDIALOG_BACKEND=web → Web (port from TYPEDIALOG_PORT, default 9000) default → Cli ``` ### RenderContext Passed to every render and field execution call: ```rust pub struct RenderContext { pub results: HashMap, // accumulated field values pub locale: Option, // locale override } ``` `results` carries the accumulated answers from previous fields. `InquireBackend::execute_field_sync` receives this as `previous_results` and uses it for `options_from` filtering in `Select` fields. --- ## Form Execution ### Three-Phase Execution `execute_with_backend_two_phase_with_defaults` (`form_parser/executor.rs`) is the primary execution path: **Phase 1 — Selector fields first** Fields that control `when:` conditionals are identified and executed before loading any fragments. This ensures conditional branches are known before lazy-loading dependent content. ```rust let selector_field_names = identify_selector_fields(&form); // execute only selectors → populate results ``` **Phase 2 — Build element list with lazy loading** With Phase 1 results known, fragments (`includes:`) are loaded only if their controlling condition is met. ```rust let element_list = build_element_list(&form, base_dir, &results)?; ``` **Phase 3 — Execute remaining fields** Iterates the element list, evaluates `when:` conditions per-element, and dispatches to the backend. Two sub-modes: - `DisplayMode::Complete` — passes all items and fields to `execute_form_complete` at once (used by TUI and Web for reactive rendering) - Field-by-field — sequential execution with condition evaluation per step (used by CLI) ### Full Execution Flow ```text typedialog binary │ ├── parse TOML → FormDefinition ├── BackendFactory::create(BackendType::Cli) → Box ├── execute_with_backend_two_phase_with_defaults(form, backend, i18n, base_dir, defaults) │ │ │ ├── Phase 1: selector fields → backend.execute_field() │ ├── Phase 2: build_element_list() with lazy fragment loading │ └── Phase 3: iterate → render_display_item() / execute_field() │ └── HashMap → JSON / YAML / TOML output ``` ### CLI Field Execution The `InquireBackend` implements field execution as a synchronous function (`backends/cli.rs`): ```rust pub(crate) fn execute_field_sync( &self, field: &FieldDefinition, previous_results: &HashMap, ) -> Result ``` The `FormBackend::execute_field` trait impl wraps it: ```rust async fn execute_field(&self, field: &FieldDefinition, context: &RenderContext) -> Result { self.execute_field_sync(field, &context.results) } ``` `filter_options_from` lives in `backends/cli.rs` as `pub(crate)` and is called from within `execute_field_sync` for `Select` fields. It filters the declared options to only those present in a referenced prior field's value — enabling dependent selects. ### Legacy Sync Path `execute_with_base_dir`, `execute`, and `load_and_execute_from_file` are a pre-`BackendFactory` sync path gated on `#[cfg(feature = "cli")]`. They call `InquireBackend::execute_field_sync` directly and bypass the async executor. Available for backward compatibility; the canonical path for new code is `execute_with_backend_two_phase_with_defaults`. --- ## Known Technical Debt ### `RenderContext` Clone Cost The executor clones `results` into `context` on every field iteration: ```rust context.results = results.clone(); ``` For forms with many fields containing large values (editor content, multi-select arrays), each field execution pays O(n) clone cost over all previous results.