# 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 forms (Nickel / TOML) + i18n + AI    │
│   load_form → 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 Nickel (`.ncl`) or TOML file describes the same form for CLI, TUI, and HTTP. No per-backend code. See [ADR-001](./adr/adr-001-nickel-form-definition.md).

**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.

---

## Form Loading

### `load_form` — Unified Entry Point

All binaries and roundtrip paths call `form_parser::load_form(path)`. Extension determines the loader:

```rust
pub fn load_form(path: impl AsRef<Path>) -> Result<FormDefinition> {
    match path.as_ref().extension().and_then(|e| e.to_str()) {
        Some("ncl") => load_from_ncl(path),
        _           => load_from_file(path),   // TOML path, unchanged
    }
}
```

`.ncl` forms are loaded via `nickel export --format json` as a subprocess. If the export fails for any reason — contract violation, syntax error, missing import — the process aborts before any interaction begins. TOML forms are deserialized directly via `serde`.

Fragment lazy-loading (`includes:` in TOML groups) is replaced by Nickel native imports. `build_element_list` is now a pure in-memory operation with no I/O. The functions `expand_includes` and `load_fragment_form` remain available in `fragments.rs` as rescue code.

### `when_false` — Conditional Field Output

Fields with a `when:` condition that evaluates to false are skipped by default. `when_false = "default"` instructs the executor to inject the field's `default` value into results even when the condition is false:

```toml
[fields.tls_cert_path]
when       = "tls_enabled == true"
when_false = "default"
default    = "/etc/ssl/cert.pem"
```

This ensures Nickel roundtrip writers always have a value for every schema field regardless of which branches the user navigated.

---

## 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<Value>;

    // 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<HashMap<String, Value>>;

    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<dyn FormBackend>`. 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<Box<dyn FormBackend>> {
    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<String, Value>,  // accumulated field values
    pub locale: Option<String>,           // 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 Phase 2.
This ensures conditional branches are known before iterating the element list.

```rust
let selector_field_names = identify_selector_fields(&form);
// execute only selectors → populate results
```

**Phase 2 — Build element list (pure)**

With Phase 1 results known, the element list is built from the fully-composed in-memory form.
No I/O occurs here — fragments were resolved by `load_form` before execution began.

```rust
let element_list = build_element_list(&form);
```

**Phase 3 — Execute remaining fields**

Iterates the element list, evaluates `when:` conditions per-element, applies `when_false` defaults
for skipped fields, 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
│
├── form_parser::load_form(path)          → FormDefinition  (.ncl via nickel export | .toml via serde)
├── BackendFactory::create(BackendType::Cli)   → Box<dyn FormBackend>
├── 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() — pure, no I/O
│       └── Phase 3: iterate → when/when_false → render_display_item() / execute_field()
│
└── HashMap<String, Value> → 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<String, Value>,
) -> Result<Value>
```

The `FormBackend::execute_field` trait impl wraps it:

```rust
async fn execute_field(&self, field: &FieldDefinition, context: &RenderContext) -> Result<Value> {
    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

No unresolved items.
