Buffer & Diff Engine
Typed-array cell buffer with cell-level diff. WASM-accelerated when available.
Buffer Operations 200×50
Diff Renderer 200×50
Frame Breakdown
What happens in a single 2.3ms frame — every stage measured.
Pure TypeScript Flexbox + Grid
Zero native dependencies. Incremental caching for unchanged subtrees.
Full Computation cache invalidated
Incremental Cache unchanged subtrees
WASM Tokenizer + Auto-Virtualization
DFA-based Rust tokenizer with automatic line virtualization. Only visible lines create React elements.
Auto-Virtualized Rendering
Only visible lines create React elements. Tokenizes everything, renders the viewport.
Cached Re-render
Same code, second render — tokens cached in ref.
First paint (cold)
DECSTBM Scroll Regions
Terminal-native scroll regions for pure scroll operations. The terminal shifts pixels — Storm writes only the new row.
Optional WASM Modules
Rust-compiled WebAssembly. Loads automatically. Falls back to TypeScript silently. Zero configuration.
Diff Renderer
Rust render_line() for ANSI string generation. Eliminates JS string concatenation overhead.
Syntax Tokenizer
Deterministic finite automaton in Rust. Zero-copy tokenization — returns byte offsets, not strings.
Total Binary
Both modules in a single WASM binary. Ships with the npm package. No separate install.
Near-Zero GC Pressure
Typed-array buffers eliminate ~30,000 Cell objects per frame. No object allocation in the render hot path.
Allocation Cost
Why It Matters
Traditional frameworks allocate a JS object per cell — a 200×50 terminal creates 10,000 objects every frame.
Storm uses Int32Array + Uint8Array — flat typed arrays. One allocation for the entire buffer. No per-cell GC pressure.
~30,000 fewer objects per frame.
ScrollView at Scale
Full React render with all children materialized. For extreme lists, use VirtualList.
ScrollView renderToString
ScrollView materializes all children as React elements. For lists beyond 1,000 items, use VirtualList — it renders only visible rows.
items={data}
height={20}
renderItem={(item) => ...}
/>
Why It's Fast
Storm's architecture is fundamentally different from traditional terminal frameworks.
| Feature | Traditional | Storm |
| Render strategy | Full repaint every frame | Cell-level diff — 97% skipped |
| Buffer model | JS object per cell 30K+/frame | Typed arrays — zero Cell objects |
| ANSI generation | String concat O(n) allocs | WASM render_line — 3.4× faster |
| Layout caching | Full recompute every render | Incremental — 8.6M ops/sec cached |
| Scroll optimization | Full repaint per scroll | DECSTBM — 78% fewer bytes |
| Animation path | Same pipeline as structural | Dual-speed — requestRender() skips React |
Three Rendering Paths
Storm picks the fastest path per frame. Most frames use the direct cell path — the others exist for correctness.
Spinner, cursor blink, single character change. Skip layout, skip clean rows, diff one cell.
One dirty row repainted. Clean subtrees skipped entirely. Diff scans only painted rows.
10 rows changed. Full clear + repaint + diff. Still under 50µs on a 300×80 terminal.
Per-Row Damage Tracking
Each row tracks which columns were written. The diff scans only damaged columns, not the full width.
How We Benchmark
No fake cache hits. Layout cache is explicitly invalidated between iterations. Every measurement does real computation.
GC pressure included. No manual GC calls between measurements. GC spikes are detected and flagged.
p50/p99 tracked. Extreme benchmarks report median and 99th percentile, not just averages.
WASM noted. Results indicate when WASM acceleration is active vs pure TypeScript.
Real pipeline. "Full frame" includes React reconciliation, layout, paint, and diff — not just one stage.
Reproducible. Run them yourself:
npx tsx examples/benchmarks-extreme.ts