Re:BlECSd – Terminal UI Library Built on an Entity Component...
by kadajett on Friday February 06, 2026 @07:43PM
(#46920111)
blECSd is a terminal UI library for Node.js where every UI element is an entity. Position, rendering, focus, animation are all plain data in typed arrays. Systems are pure functions that process that data each frame. The same architecture that renders a dashboard with form controls can also run physics-based animations, particle effects, or a Doom source port in your terminal.
The project started as a functional rewrite of the 11-year-old Blessed library. The majority of the code was written by Claude Code as a direct rewrite, with the non-vibe-coded work concentrated in the performance layer and Kitty graphics protocol support for the Doom demo. It was a full week of thousands of tests, examples, and rebuilding tools I'd previously written in Ink and open-tui.
What's in the box: 18 widgets (panels, tabs, tables, trees, virtualized lists), 7 form controls, 32 ECS components, collision detection, state machines, and a 3D rendering subsystem with five backends (braille, half-block, sextant, Sixel, Kitty). 10,234 tests across 256 files.
The performance is where it gets interesting. Instead of building on React and fighting memory issues, we leaned into game dev data structures. The hot paths all run on PackedStore, a dense array using the three-vector pattern (data[], dataIndex[], id[], generations[]) for O(1) swap-and-pop with cache-friendly iteration. Systems like spatial hashing went from 4 Map lookups per entity per frame to 1 packed lookup. Scroll physics, dirty rect tracking, collision detection, particle systems, visibility culling all iterate dense arrays instead of pointer-chasing through hash tables.
It's a library, not a framework. You own the world and the update loop. Every function takes world as a parameter. Use one widget or all of them. Works with workers in parallel environments. Written in strict TypeScript, purely functional (no classes), with Zod validation at boundaries.
The vibe coding ratio is honest: LLMs are great at cranking out the 50th widget following an established pattern. But the data layout decisions, the PackedStore design, and figuring out which stores actually need dense iteration vs point lookups required thinking about the problem. The createComponentStore<T>({ iterable: true }) API came out of that, a Map-compatible wrapper that lets you swap backing stores with one flag.
The project started as a functional rewrite of the 11-year-old Blessed library. The majority of the code was written by Claude Code as a direct rewrite, with the non-vibe-coded work concentrated in the performance layer and Kitty graphics protocol support for the Doom demo. It was a full week of thousands of tests, examples, and rebuilding tools I'd previously written in Ink and open-tui.
What's in the box: 18 widgets (panels, tabs, tables, trees, virtualized lists), 7 form controls, 32 ECS components, collision detection, state machines, and a 3D rendering subsystem with five backends (braille, half-block, sextant, Sixel, Kitty). 10,234 tests across 256 files.
The performance is where it gets interesting. Instead of building on React and fighting memory issues, we leaned into game dev data structures. The hot paths all run on PackedStore, a dense array using the three-vector pattern (data[], dataIndex[], id[], generations[]) for O(1) swap-and-pop with cache-friendly iteration. Systems like spatial hashing went from 4 Map lookups per entity per frame to 1 packed lookup. Scroll physics, dirty rect tracking, collision detection, particle systems, visibility culling all iterate dense arrays instead of pointer-chasing through hash tables.
It's a library, not a framework. You own the world and the update loop. Every function takes world as a parameter. Use one widget or all of them. Works with workers in parallel environments. Written in strict TypeScript, purely functional (no classes), with Zod validation at boundaries.
The vibe coding ratio is honest: LLMs are great at cranking out the 50th widget following an established pattern. But the data layout decisions, the PackedStore design, and figuring out which stores actually need dense iteration vs point lookups required thinking about the problem. The createComponentStore<T>({ iterable: true }) API came out of that, a Map-compatible wrapper that lets you swap backing stores with one flag.
- GitHub: https://github.com/Kadajett/blECSd - npm: https://www.npmjs.com/package/blecsd - Examples: https://github.com/Kadajett/blECSd-Examples