Architecture
Kraken TUI uses a Modular Monolith with Cross-Language Facade pattern. It’s a single-process library composed of two language layers connected by an FFI (Foreign Function Interface) boundary.Core Architectural Invariant
Rust is the performance engine; TypeScript is the steering wheel.
Two-Layer Architecture
Native Core (Rust)
A compiled shared library (libkraken_tui.so/dylib/dll) containing all state, computation, and rendering logic. Internally decomposed into strictly bounded modules.
Key characteristics:
- Owns all mutable widget state
- Single source of truth
- Zero knowledge of TypeScript, Bun, or any host runtime
- Independently testable
Host Language Bindings (TypeScript)
A thin ergonomic wrapper that translates Developer intent into FFI commands. Key characteristics:- Contains zero business logic
- Holds opaque
u32handles - Type-safe API for developers
- Command dispatch only
- TypeScript Usage
- Under the Hood
FFI Boundary Contract
The FFI boundary enforces strict invariants to ensure safety and performance:Unidirectional Control Flow
The Host Layer calls into the Native Core. The Native Core never calls back into the Host Layer. Events are delivered through explicit host-driven drain calls.
Single Source of Truth
The Host Layer holds opaque
u32 handles. The Native Core owns all mutable state. Handle(0) is permanently invalid.Copy Semantics
Host-to-native strings are copied from
(*const u8, u32). Native-to-host string outputs use caller-provided buffers (*mut u8, u32) for copy-out operations.FFI Safety
Everyextern "C" entry point wraps its body in catch_unwind. Panics are caught and converted to error codes. No panic ever crosses the FFI boundary.
Internal Module Structure
The Native Core is decomposed into strictly bounded modules:Tree Module
Tree Module
Responsibility: Composition Tree CRUD operations, handle allocation, parent-child relationships, dirty-flag propagation.Dependencies: None (foundational)Operations include: node creation, subtree destruction, indexed child insertion, dirty flag management.
Layout Module
Layout Module
Responsibility: Flexbox constraint resolution using Taffy layout engine. Resize handling. Caches computed positions and dimensions.Dependencies: Tree ModuleProvides hit-test geometry for mouse event routing.
Style Module
Style Module
Responsibility: Color resolution (named, hex, 256-palette), text decoration (bold, italic, underline), border computation.Dependencies: Tree Module, Theme ModuleExplicit styles always win over theme defaults.
Theme Module
Theme Module
Responsibility: Named theme definitions, theme-to-subtree bindings, theme resolution via ancestry traversal.Dependencies: Tree ModuleProvides built-in light and dark themes plus per-widget-type style defaults.
Animation Module
Animation Module
Responsibility: Active animation registry, timed property transitions using elapsed time, interpolation with easing functions.Dependencies: Tree, Style ModulesAdvances each render cycle and marks animated widgets dirty.
Event Module
Event Module
Responsibility: Terminal input capture, event classification (key, mouse, resize, focus), event buffering, hit-testing, focus state machine.Dependencies: Tree, Layout ModulesImplements depth-first, DOM-order focus traversal.
Render Module
Render Module
Responsibility: Double-buffered cell grid, dirty-flag diffing, terminal-intent run generation.Dependencies: Tree, Layout, Style, Text ModulesOnly changed cells generate terminal output.
Text Module
Text Module
Responsibility: Rich text parsing (Markdown to styled spans), syntax highlighting, wrap resolution.Dependencies: Style ModuleBuilt-in parsers are native; custom formats pre-process in Host Layer.
Module Dependency Flow
Render Pipeline
The render pipeline follows a Pipe-and-Filter pattern:Mutation Accumulation
Widget property changes mark nodes dirty but don’t trigger immediate rendering.
Layout Resolution
Flexbox constraints are resolved only for dirty subtrees (O(n) for changed nodes).
The entire pipeline executes in a single native call:
app.render(). This minimizes FFI crossings and keeps all compute-heavy work in Rust.Event Processing
The input subsystem follows an Event-Driven buffer-poll model:Why This Architecture?
Performance
- All CPU-intensive work stays in Rust
- Minimal FFI crossings (one render call per frame)
- Zero serialization overhead (monolithic process)
- Layout and diff computation in compiled native code
Safety
- Single source of truth (Rust owns state)
- No shared mutable state across FFI boundary
- Panic safety (all FFI entry points use
catch_unwind) - Type-safe handles (invalid handle =
0sentinel)
Maintainability
- Clean separation of concerns
- Host Layer is pure command dispatch (no business logic)
- Native Core is independently testable
- Module boundaries enforced by language visibility
Developer Experience
- Ergonomic TypeScript API
- Type-safe widget composition
- Familiar patterns (flexbox, event handlers)
- Optional JSX/reactive layer available