superlighttui 0.18.2

Super Light TUI - A lightweight, ergonomic terminal UI library
Documentation
# SLT Architecture

This document describes how the code is organized and how data flows through the system.
For design philosophy and conventions, see [Design Principles](DESIGN_PRINCIPLES.md).

Related docs:
- [QUICK_START.md]QUICK_START.md
- [WIDGETS.md]WIDGETS.md
- [PATTERNS.md]PATTERNS.md
- [EXAMPLES.md]EXAMPLES.md
- [BACKENDS.md]BACKENDS.md
- [DEBUGGING.md]DEBUGGING.md
- [TESTING.md]TESTING.md

---

## Module Map

```
src/
├── lib.rs                      # Crate root, public re-exports, run()/frame() entry points
├── context.rs                  # Facade for core context types + widget impl modules
├── context/
│   ├── state.rs                # State<T>, Response
│   ├── bars.rs                 # BarDirection, Bar, BarChartConfig, BarGroup
│   ├── widget.rs               # Widget trait
│   ├── core.rs                 # Context struct + checkpoint / rollback state
│   ├── container.rs            # ContainerBuilder + CanvasContext
│   ├── runtime.rs              # Core Context methods (hooks, focus, notifications)
│   ├── helpers.rs              # Shared helper functions for widget impls
│   ├── widgets_display.rs      # Display/layout facade
│   ├── widgets_display/
│   │   ├── text.rs             # text, style chains, size/margin helpers
│   │   ├── rich_output.rs      # big_text, image, streaming, tool approval, context bar
│   │   ├── status.rs           # alert, breadcrumb, badge, stat, code_block, empty_state
│   │   └── layout.rs           # screen, row/col, modal, tooltip, container, scrollable, form helpers
│   ├── widgets_interactive.rs  # Interactive facade
│   ├── widgets_interactive/
│   │   ├── collections.rs      # grid, list, calendar, file picker
│   │   ├── selection.rs        # table, tabs, button, checkbox, toggle, select, radio, multi_select
│   │   ├── rich_markdown.rs    # rich_log, virtual_list, command palette, markdown, key_seq
│   │   ├── events.rs           # keyboard, mouse, theme, size, quit helpers
│   │   └── tree_widgets.rs     # tree widget internals
│   ├── widgets_input.rs        # Input facade
│   ├── widgets_input/
│   │   ├── text_input.rs       # text input widget
│   │   ├── feedback.rs         # spinner, toast, slider
│   │   └── textarea_progress.rs # textarea and progress widgets
│   └── widgets_viz.rs          # Charts, sparklines, heatmap, treemap, candlestick, stacked bar, canvas, QR
│
├── widgets.rs                  # Facade for widget state types
├── widgets/
│   ├── input.rs                # StaticOutput, TextInputState, FormField, FormState, ToastState, ToastMessage, ToastLevel, AlertLevel, TextareaState, SpinnerState
│   ├── collections.rs          # ListState, FilePickerState, TabsState, TableState, ScrollState
│   ├── feedback.rs             # RichLogState, RichLogEntry, CalendarState, ButtonVariant, Trend
│   ├── selection.rs            # SelectState, RadioState, MultiSelectState, TreeState, DirectoryTreeState, PaletteCommand
│   └── commanding.rs           # CommandPaletteState, streaming states, ScreenState, ModeState, tool approval types, ContextItem
│
├── layout.rs                   # Thin facade re-exporting layout kernels
├── layout/
│   ├── command.rs              # Command enum recorded by Context
│   ├── tree.rs                 # LayoutNode, NodeKind, build_tree(), wrap helpers
│   ├── collect.rs              # collect_all(), FrameData, raw-draw collection helpers
│   ├── flexbox.rs              # compute(), layout_row(), layout_column(), gap/grow/shrink resolution
│   ├── render.rs               # render(), render_inner(), render_container_border(), clipping, viewport culling
│   └── tests.rs                # Layout-focused kernel tests
│
├── style.rs                    # Style struct, Border, Padding, Margin, Constraints, Modifiers, Align, Justify
├── style/
│   ├── color.rs                # Color enum (Named, Indexed, Rgb), ColorDepth, color blending
│   └── theme.rs                # Theme struct, Spacing, ThemeColor, 10 presets, ThemeBuilder, contrast helpers
│
├── terminal.rs                 # Terminal backend
│   ├── Terminal                # Full-screen mode — alternate screen, raw mode, mouse capture
│   ├── InlineTerminal          # Inline mode — renders below cursor, no alternate screen
│   └── ANSI output, synchronized output (DECSET 2026), event polling
│
├── terminal/
│   └── selection.rs            # SelectionState, text selection overlay rendering
│
├── anim.rs                     # Animation primitives
│   ├── Tween                   # Linear interpolation with 9 easing functions
│   ├── Spring                  # Physics-based spring animation
│   ├── Keyframes               # Timeline with stops and loop modes
│   ├── Sequence                # Chained tween segments
│   └── Stagger                 # Delayed animation for list items
│
├── chart.rs                    # ChartBuilder, ChartConfig, Dataset, Marker
├── chart/
│   ├── render.rs               # Chart rendering
│   ├── axis.rs                 # TickSpec, tick generation and formatting helpers
│   ├── bar.rs                  # Bar chart rendering
│   ├── grid.rs                 # Grid lines
│   └── braille.rs              # Braille dot patterns for line/scatter charts
│
├── buffer.rs                   # Double-buffer with clip stack and diff tracking
├── syntax.rs                   # Tree-sitter-based syntax highlighting helpers
├── sixel.rs                    # Sixel image protocol support
├── cell.rs                     # Cell = char + Style + optional URL
├── rect.rs                     # Rect struct, bounds checking, intersection
├── event.rs                    # Event, KeyCode, KeyModifiers, MouseEvent, MouseButton
├── halfblock.rs                # Half-block (▀▄) image rendering
├── keymap.rs                   # KeyMap, Binding structs
├── palette.rs                  # 256-color palette definitions
└── test_utils.rs               # TestBackend, EventBuilder for headless testing
```

---

## Frame Lifecycle

Every frame follows this exact sequence. The engine performs **four top-level DFS traversals** of the layout tree per frame (build → layout → collect → render), plus optional passes for the debug overlay.

```
1. EVENT POLL
   └── Terminal polls for keyboard/mouse events (non-blocking)
   └── Events stored in Context for widget consumption

2. USER CLOSURE
   └── User's closure runs: ui.text(), ui.button(), ui.col(), etc.
   └── Each call pushes a Command to Context's internal command list
   └── No layout is computed yet — just recording intent

3. POST-CLOSURE NORMALIZATION
   └── process_focus_keys()
   └── render_notifications()
   └── emit_pending_tooltips()
   └── Scoped stacks settle before layout; quit can short-circuit here

4. BUILD TREE — build_tree()                         [DFS pass 1 of 4]
   └── Flat Command list → nested LayoutNode tree
   └── Parent-child relationships resolved from open/close markers

5. FLEXBOX LAYOUT — flexbox::compute()               [DFS pass 2 of 4]
   └── layout_row() / layout_column() walk the tree
   └── Resolves: sizes, gaps, grow factors, min/max constraints
   └── Breakpoint-conditional styles evaluated against terminal width

6. COLLECT ALL — collect_all()                       [DFS pass 3 of 4]
   └── One DFS over the laid-out tree; returns a FrameData bundle
   └── Gathers, in a single walk: scroll regions, hit areas, group rects,
       content rects, focus rects/groups, raw-draw viewport rects
   └── See "The collect_all consolidation" below for what this replaced

7. RENDER + DEFERRED DRAW — layout::render()         [DFS pass 4 of 4]
   └── render() → render_inner() → render_container_border()
   └── Writes Cell values to the back buffer
   └── Clip stack ensures children don't overflow parent bounds
   └── Viewport culling: nodes fully outside the viewport are skipped
   └── Deferred raw-draw callbacks replay into collected raw-draw rects

8. DIFF + FLUSH
   └── Compare front buffer (previous frame) vs back buffer (current frame)
   └── apply_style_delta() — only emit ANSI attributes that changed
   └── Synchronized output (DECSET 2026) prevents tearing on supported terminals
   └── Swap front ↔ back buffers

9. DEBUG OVERLAY (optional, F12)
   └── render_debug_overlay() adds 1–2 extra DFS passes when enabled
   └── Off by default; pure diagnostic path
```

For the custom-backend entry point that drives this lifecycle manually, see `docs/BACKENDS.md`.
Terminal-owned run loops add selection overlay and clipboard handling around the shared kernel before the final flush.

### The `collect_all` consolidation

The per-frame DFS count used to be higher. Before consolidation, the collect phase performed seven independent tree walks — one each for scroll regions, hit areas, group rects, content rects, focus rects, focus groups, and raw-draw viewport rects. That was 7 DFS traversals stacked on top of the build, layout, and render walks — 10 traversals per frame in total.

`collect_all()` folds those seven collect-phase walks into **one** DFS that produces a single `FrameData` struct holding every vector the runtime needs for the next frame's hit-testing and scroll feedback. The top-level pipeline is still four DFS passes (build, layout, collect, render) — `collect_all` did not fuse the phases, it fused the sub-walks inside one phase.

Net effect: **10 traversals per frame → 4**. That is the real story; the marketing line "single DFS" was shorthand for "collect no longer does seven walks" and understated what is actually a four-stage pipeline.

---

## One-Frame Delay Feedback

Layout-computed data feeds back to the **next** frame via settled `prev_*` fields on `Context`, sourced from session state carried between frames:

```
Frame N:   closure runs → layout computed → focus_count, hit_areas, scroll_bounds stored
                                            Frame N+1: closure reads prev_focus_count, prev_hit_areas → makes decisions
```

This is an intentional design choice of immediate-mode UI:
- Widget positions are not known until layout runs (after the closure)
- So interaction checks (hover, click) use positions from the previous frame
- This introduces a one-frame delay that is imperceptible at 60 FPS

Interactive widgets depend on `prev_*` data for hit testing, scroll bounds, and focus count.

---

## Module Dependency Flow

```
lib.rs (entry point)
  ├── context.rs ←── context/widgets_*.rs (impl blocks on Context)
  │     ↑
  │     ├── widgets.rs (state types)
  │     ├── style.rs ←── style/color.rs, style/theme.rs
  │     ├── layout.rs ←── layout/command.rs, layout/tree.rs, layout/collect.rs, layout/flexbox.rs, layout/render.rs
  │     ├── buffer.rs ←── cell.rs
  │     ├── anim.rs
  │     ├── event.rs
  │     └── rect.rs
    ├── terminal.rs ←── terminal/selection.rs
  │     ↑
  │     └── buffer.rs, event.rs (for flush and polling)
    └── chart.rs ←── chart/render.rs, chart/axis.rs, chart/bar.rs, chart/grid.rs, chart/braille.rs
```

Key observations:
- `context.rs` stays the public hub, but heavy logic is now split into smaller files under `src/context/`
- `widgets.rs` stays the public state catalog, but the concrete state types are grouped under `src/widgets/`
- `terminal.rs` is isolated — it only knows about `buffer` and `event`
- `layout.rs` is now only a facade; the real kernels live under `src/layout/`
- `style`, `layout`, `anim` are largely independent of each other
- Widget facades under `src/context/widgets_*.rs` now act as indexes for narrower implementation files

- The `Backend` / `AppState` / `frame()` path in `src/lib.rs` is the low-level escape hatch when SLT does not own the event loop

---

## Visibility Rules

| Visibility | Use when | Example |
|------------|----------|---------|
| `pub` | Part of the user-facing API | `pub fn text()`, `pub struct Style` |
| `pub(crate)` | Shared across modules, not for users | `pub(crate) struct FrameData` |
| `pub(super)` | Shared with parent module's submodules only | `pub(super) fn render_border()` |
| Private (no modifier) | Implementation detail within a single file | Helper functions, internal state |

### Re-export Rule

**The public API is defined by `lib.rs` re-exports.** Users should never need deep imports like `slt::context::widgets_display::...`. If something is public, it must be re-exported from the crate root.

### Why This Matters for Semver

Every `pub use` in `lib.rs` is a semver commitment. Adding a re-export is non-breaking. Removing one is breaking. Be deliberate about what gets re-exported.

---

## File Conventions

- **Module pattern**: `module.rs` + `module/` directory (Rust 2018 style, NOT `mod.rs`)
- **Submodule imports**: `use super::*;` to access parent types
- **Splitting safety**: When splitting a file, keep `#[derive(...)]` and `#[cfg_attr(...)]` attached to their type definitions — they must not get separated by the split boundary