Architecture
Overview of unblessed's architecture and design principles.
Core Principles
unblessed is built on three fundamental principles:
- Platform-Agnostic Core: All widget logic lives in
@unblessed/corewith zero platform dependencies - Runtime Dependency Injection: Platform-specific APIs are injected at runtime, allowing the same code to run anywhere
- Modular Packages: Install only what you need - Node.js runtime, browser runtime, or blessed compatibility
Package Structure
@unblessed/core → Platform-agnostic core (defines Runtime interface)
@unblessed/node → Node.js runtime implementation
@unblessed/browser → Browser runtime (XTerm.js integration)
@unblessed/blessed → Backward-compatible wrapper for blessed users
@unblessed/core
The platform-agnostic core contains all the TUI logic:
- Widget system: Box, List, Table, Form, etc.
- Rendering engine: Screen updates and diff algorithms
- Event handling: Keyboard, mouse, and custom events
- Layout engine: Positioning, sizing, and borders
- Style system: Colors, attributes, and themes
Key characteristic: Zero platform dependencies. All platform-specific code uses the Runtime interface.
@unblessed/node
The Node.js runtime implementation:
- NodeRuntime class: Implements the Runtime interface using Node.js APIs
- Auto-initialization: Sets up runtime on import
- Re-exports: All core widgets plus Node-specific features
- TTY handling: Direct terminal control via Node.js
Usage:
import { Screen, Box } from '@unblessed/node';
// Runtime automatically initialized - just use it!
@unblessed/browser
The browser runtime implementation:
- BrowserRuntime class: Implements Runtime interface with polyfills
- XTerm.js integration: Seamless terminal emulation
- Auto-initialization: Sets up runtime and polyfills on import
- Bundled data: Includes terminfo data for terminal emulation
Usage:
import { Screen, Box } from '@unblessed/browser';
import { Terminal } from 'xterm';
const term = new Terminal();
term.open(document.getElementById('terminal'));
const screen = new Screen({ terminal: term });
// Runtime automatically initialized - works in browser!
@unblessed/blessed
100% backward-compatible blessed wrapper:
- Drop-in replacement: Change import, keep code unchanged
- Thin wrapper: Delegates to @unblessed/node
- Blessed API: Classic constructor patterns and methods
- Migration path: Easy upgrade from blessed to unblessed
Usage:
// Old blessed code
// import blessed from 'blessed';
// New unblessed code - just change the import!
import blessed from '@unblessed/blessed';
// Everything else works the same
const screen = blessed.screen();
const box = blessed.box({ ... });
Runtime Dependency Injection
The cornerstone of unblessed's cross-platform capability is runtime dependency injection.
The Runtime Interface
The core defines a Runtime interface that abstracts all platform-specific APIs:
export interface Runtime {
fs: FileSystemAPI; // File system operations
process: ProcessAPI; // Process info and events
tty: TtyAPI; // Terminal control
buffer: BufferAPI; // Buffer handling
stream: StreamAPI; // Stream operations
// ... other platform APIs
}
How It Works
- Core code requests runtime:
import { getRuntime } from '@unblessed/core/runtime-context';
// In widget code
const runtime = getRuntime();
const data = runtime.fs.readFileSync(path);
- Platform package provides runtime:
// @unblessed/node/src/auto-init.ts
import { setRuntime } from '@unblessed/core';
import { NodeRuntime } from './node-runtime';
setRuntime(new NodeRuntime()); // Injects Node.js implementation
- Browser uses polyfills:
// @unblessed/browser/src/auto-init.ts
import { setRuntime } from '@unblessed/core';
import { BrowserRuntime } from './browser-runtime';
setRuntime(new BrowserRuntime()); // Injects browser polyfills
Benefits
- Single Codebase: Write once, run anywhere
- Testability: Mock runtime for unit tests
- Extensibility: Add new platforms (Deno, Bun) by implementing Runtime
- Zero Dependencies: Core has no platform dependencies
Auto-Initialization Pattern
unblessed uses an auto-initialization pattern for ergonomics:
// ✅ Modern unblessed - just import and use
import { Screen, Box } from '@unblessed/node';
const screen = new Screen();
// ❌ Old pattern - no longer needed
import { initRuntime } from '@unblessed/node';
initRuntime(); // Not required!
When you import from @unblessed/node or @unblessed/browser, the runtime is automatically initialized before any widgets are created.
Why: Reduces boilerplate and cognitive load. Users shouldn't think about initialization.
Widget Hierarchy
Widgets form a tree structure with the Screen at the root:
Screen (root)
├── Box (header)
├── List (sidebar)
│ └── Box (list item)
├── Box (content)
└── Box (footer)
Parent-Child Attachment
Widgets attach to parents using the parent option:
const box = new Box({
parent: screen, // Attach to screen
content: 'Hello'
});
// Child automatically added to parent.children
console.log(screen.children.includes(box)); // true
Benefits
- Automatic rendering: Children render when parent renders
- Event bubbling: Events propagate through the tree
- Coordinate system: Child positions relative to parent
- Z-index management: Paint order determined by tree
Rendering Pipeline
unblessed uses a smart rendering pipeline:
- Dirty tracking: Only re-render changed widgets
- Screen diff: Calculate minimal terminal updates
- Smart CSR: Use scroll regions when possible
- Buffer optimization: Minimize escape sequences
box.setContent('New content'); // Marks box as dirty
screen.render(); // Only updates changed regions
See Rendering for details.
Event System
unblessed provides a rich event system:
- DOM-like events:
focus,blur,click,keypress - Custom events: Define your own with
.emit() - Event bubbling: Events propagate up the widget tree
- Key handlers: Global and widget-specific key bindings
// Widget event
box.on('click', () => console.log('Clicked!'));
// Global key
screen.key('C-c', () => process.exit(0));
// Custom event
box.emit('custom-event', data);
See Events for details.
TypeScript Integration
unblessed is built with TypeScript in strict mode:
- Full type safety: All widgets and options are typed
- IntelliSense: IDE autocomplete and documentation
- Compile-time errors: Catch mistakes early
- Generic support: Type-safe custom widgets
import { Box, type BoxOptions } from '@unblessed/node';
// Full type checking
const options: BoxOptions = {
top: 'center',
left: 'center',
width: '50%',
height: 10,
border: { type: 'line' },
style: { fg: 'cyan' }
};
const box = new Box(options);
Performance Considerations
unblessed is designed for performance:
- Lazy rendering: Only compute what's visible
- Smart diffing: Minimal terminal updates
- Event batching: Coalesce rapid updates
- Memory pooling: Reuse buffers when possible
Benchmarks (on modern hardware):
- Empty screen render: ~6.5ms
- Complex screen (100 boxes): ~11ms
- Large list (1K items): ~187ms
See Performance for optimization tips.
Next Steps
- Runtime System - Deep dive into runtime injection
- Widgets - Understanding the widget system
- Rendering - How rendering works
- Events - Event handling and propagation