chore: udpate docs with arch
Some checks failed
CI / Lint (bash) (push) Has been cancelled
CI / Lint (markdown) (push) Has been cancelled
CI / Lint (nickel) (push) Has been cancelled
CI / Lint (nushell) (push) Has been cancelled
CI / Lint (rust) (push) Has been cancelled
CI / Code Coverage (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (macos-latest) (push) Has been cancelled
CI / Build (ubuntu-latest) (push) Has been cancelled
CI / Build (windows-latest) (push) Has been cancelled
CI / Benchmark (push) Has been cancelled
CI / Security Audit (push) Has been cancelled
CI / License Compliance (push) Has been cancelled
Some checks failed
CI / Lint (bash) (push) Has been cancelled
CI / Lint (markdown) (push) Has been cancelled
CI / Lint (nickel) (push) Has been cancelled
CI / Lint (nushell) (push) Has been cancelled
CI / Lint (rust) (push) Has been cancelled
CI / Code Coverage (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (macos-latest) (push) Has been cancelled
CI / Build (ubuntu-latest) (push) Has been cancelled
CI / Build (windows-latest) (push) Has been cancelled
CI / Benchmark (push) Has been cancelled
CI / Security Audit (push) Has been cancelled
CI / License Compliance (push) Has been cancelled
This commit is contained in:
parent
2b19778fd0
commit
78e995177b
@ -38,6 +38,14 @@ Complete documentation for using, building, and deploying TypeDialog.
|
||||
- Installation verification
|
||||
- CI/CD integration
|
||||
|
||||
## Architecture
|
||||
|
||||
- **[architecture.md](architecture.md)** - Technical positioning and internal architecture
|
||||
- Positioning vs Inquire, Ratatui, Axum, Nushell
|
||||
- BackendFactory mechanics and feature-gated dispatch
|
||||
- Three-phase form execution (selector fields, lazy fragments, field-by-field)
|
||||
- Known technical debt
|
||||
|
||||
## Configuration
|
||||
|
||||
1. **[configuration.md](configuration.md)** - Configuration guide
|
||||
@ -70,6 +78,7 @@ Complete documentation for using, building, and deploying TypeDialog.
|
||||
|
||||
### Feature Guides
|
||||
|
||||
- [Architecture](architecture.md) - Positioning, BackendFactory, execution model
|
||||
- [Nickel Integration](nickel.md) - Schema parsing, contracts, i18n, templates
|
||||
- [Encryption](encryption/) - Secure field handling
|
||||
- [Provisioning Generator](prov-gen/) - Infrastructure as Code generation
|
||||
@ -188,6 +197,7 @@ docs/
|
||||
│ └── release.md ← Release workflow
|
||||
│
|
||||
├── Reference
|
||||
│ ├── architecture.md ← Technical positioning & internals
|
||||
│ ├── field_types.md ← Field types reference
|
||||
│ └── nickel.md ← Nickel schema support
|
||||
│
|
||||
|
||||
226
docs/architecture.md
Normal file
226
docs/architecture.md
Normal file
@ -0,0 +1,226 @@
|
||||
# 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<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 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<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() with lazy fragment loading
|
||||
│ └── Phase 3: iterate → 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
|
||||
|
||||
### Unbounded Retry Recursion
|
||||
|
||||
Required-field validation retries via direct recursion in `InquireBackend::execute_field_sync`:
|
||||
|
||||
```rust
|
||||
if is_required && result.is_empty() {
|
||||
return self.execute_field_sync(field, previous_results); // no depth limit
|
||||
}
|
||||
```
|
||||
|
||||
A user who repeatedly submits empty input on a required field grows the call stack indefinitely.
|
||||
|
||||
### `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.
|
||||
Loading…
x
Reference in New Issue
Block a user