Skip to main content

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.
All CPU-intensive work — layout computation, tree traversal, buffer diffing, text parsing, hit-testing, event classification — executes in the Native Core. The Host Layer’s sole responsibility is ergonomic command dispatch.

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 u32 handles
  • Type-safe API for developers
  • Command dispatch only
import { Kraken, Box, Text } from "kraken-tui";

const app = Kraken.init();
const container = new Box({ direction: "column" });
const text = new Text({ content: "Hello World" });

container.append(text);
app.setRoot(container);
app.render();

FFI Boundary Contract

The FFI boundary enforces strict invariants to ensure safety and performance:
1

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

Single Source of Truth

The Host Layer holds opaque u32 handles. The Native Core owns all mutable state. Handle(0) is permanently invalid.
3

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

Error Codes, Not Exceptions

Every FFI entry returns 0 on success, -1 on error, and -2 on panic. The Host Layer maps these to language-level exceptions.

FFI Safety

Every extern "C" entry point wraps its body in catch_unwind. Panics are caught and converted to error codes. No panic ever crosses the FFI boundary.
fn ffi_wrap<F: FnOnce() -> u32 + std::panic::UnwindSafe>(f: F) -> i32 {
    match std::panic::catch_unwind(f) {
        Ok(result) => result as i32,
        Err(_) => -2,  // Panic code
    }
}

Internal Module Structure

The Native Core is decomposed into strictly bounded modules:
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.
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.
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.
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.
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.
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.
Responsibility: Double-buffered cell grid, dirty-flag diffing, terminal-intent run generation.Dependencies: Tree, Layout, Style, Text ModulesOnly changed cells generate terminal output.
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:
1

Mutation Accumulation

Widget property changes mark nodes dirty but don’t trigger immediate rendering.
2

Animation Advancement

Active animations interpolate based on elapsed time since last render.
3

Layout Resolution

Flexbox constraints are resolved only for dirty subtrees (O(n) for changed nodes).
4

Dirty-Flag Diffing

Only changed cells are identified by comparing front and back buffers.
5

Terminal I/O

Minimal escape sequences are emitted to update changed cells.
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:
// Host Layer controls the event loop
while (running) {
  app.readInput(16);  // Terminal input → Native event buffer
  
  for (const event of app.drainEvents()) {
    // Native event buffer → Host callbacks
    if (event.type === "key" && event.keyCode === ESC) {
      running = false;
    }
  }
  
  app.render();  // Trigger render pipeline
}
Events are delivered through explicit drain calls, not callbacks. This eliminates callback-based FFI complexity and enforces unidirectional control flow.

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 = 0 sentinel)

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
This architecture satisfies the Dependency Rule from Clean Architecture: dependencies point inward. The Host Layer depends on the Native Core’s command protocol, never the reverse.

Build docs developers (and LLMs) love