blecsd-tui

📁 kadajett/blecsd-skill 📅 13 days ago
3
总安装量
3
周安装量
#58547
全站排名
安装命令
npx skills add https://github.com/kadajett/blecsd-skill --skill blecsd-tui

Agent 安装分布

opencode 3
gemini-cli 3
github-copilot 3
codex 3
kimi-cli 3
amp 3

Skill 文档

blECSd Core Library Skill

blECSd is a modern, high-performance terminal UI library built on TypeScript and ECS (Entity Component System) architecture using bitecs. It is a ground-up rewrite of the original blessed node library, NOT backwards-compatible. Version: 0.6.0. Node.js >= 22.0.0.

Hard Rules (Non-Negotiable)

1. Purely Functional, No OOP

BANNED: class, this, new (except Map/Set/Error), prototype manipulation, inheritance.

// WRONG
class MyWidget {
  private x: number;
  constructor(x: number) { this.x = x; }
  move(dx: number) { this.x += dx; }
}

// CORRECT
interface MyWidget { readonly x: number; }
function createMyWidget(x: number): MyWidget { return { x }; }
function moveWidget(w: MyWidget, dx: number): MyWidget { return { x: w.x + dx }; }

2. No Direct bitecs Imports

Only three files may import from 'bitecs': src/core/ecs.ts, src/core/world.ts, src/core/types.ts. Everything else imports from 'blecsd' (external) or '../core/ecs' (internal).

// BANNED
import { addEntity, hasComponent } from 'bitecs';

// CORRECT (external consumer or example)
import { addEntity, hasComponent } from 'blecsd';

// CORRECT (internal library code)
import { addEntity, hasComponent } from '../core/ecs';

3. Library-First Design

Users control their own world and update loop. All functions take world as a parameter. Never own a global world. Never create implicit dependencies on the update loop.

// Users can run systems standalone without the built-in loop
import { createWorld, addEntity, setPosition, renderSystem } from 'blecsd';
const world = createWorld();
const eid = addEntity(world);
setPosition(world, eid, 10, 5);
renderSystem(world); // Works without a scheduler

4. Input Priority

INPUT phase is always first in the update loop. It cannot be reordered. All pending input is processed every frame. No events may be lost or delayed.

5. Early Returns and Guard Clauses

Handle errors first, happy path last. Max nesting 2-3 levels.

// WRONG: deep nesting
function process(world: World, eid: Entity): Result {
  if (hasComponent(world, Position, eid)) {
    if (hasComponent(world, Velocity, eid)) {
      return doWork(world, eid);
    }
  }
  return { error: 'missing' };
}

// CORRECT: guard clauses
function process(world: World, eid: Entity): Result {
  if (!hasComponent(world, Position, eid)) return { error: 'no position' };
  if (!hasComponent(world, Velocity, eid)) return { error: 'no velocity' };
  return doWork(world, eid);
}

6. File Size Limits

  • Component files: max 200 lines
  • Widget files: max 300 lines per sub-file
  • All other source files: max 500 lines

7. Strict TypeScript

  • All functions have explicit return types
  • No any (use unknown + type guards)
  • Prefer readonly arrays and objects
  • Branded types for IDs

Architecture

Update Loop Phases (in order)

  1. INPUT (always first, immutable position) – Process all pending keyboard/mouse input
  2. EARLY_UPDATE – Pre-processing, state transitions
  3. UPDATE – Main game/app logic
  4. LATE_UPDATE – Post-processing, cleanup
  5. ANIMATION – Physics, springs, tweens, momentum scrolling
  6. LAYOUT – Calculate positions, sizes, constraints
  7. RENDER – Write to screen buffer
  8. POST_RENDER – Cleanup, telemetry

Where Does Logic Go?

Question Module
Pure data storage (typed arrays, stores)? components/ (200 lines max)
Queries entities and transforms state? systems/
Combines components into user-facing API? widgets/ (300 lines/sub-file)
Pure function, no ECS dependency? utils/
Validates config or input? schemas/
Handles terminal I/O? terminal/
ECS primitive (addEntity, etc.)? core/

Rule: Components = data only. Systems = logic. Never put business logic in component files.

ECS Pattern

Components are pure data containers:

const Position = defineComponent({ x: Types.f32, y: Types.f32 });

Systems are pure functions that query and transform:

const movementSystem = defineSystem((world) => {
  const entities = movingQuery(world);
  for (const eid of entities) {
    Position.x[eid] += Velocity.x[eid];
    Position.y[eid] += Velocity.y[eid];
  }
  return world;
});

Zod Validation

Use Zod at system boundaries only:

const ScreenConfigSchema = z.object({
  width: z.number().int().positive(),
  height: z.number().int().positive(),
  title: z.string().optional(),
});

API Surface (Three Tiers)

Tier 1: Curated Top-Level ('blecsd')

~80 exports for common use cases:

ECS Core:

  • createWorld(), destroyWorld(world)
  • addEntity(world), removeEntity(world, eid)
  • addComponent(world, eid, Component), hasComponent(world, Component, eid)
  • Types: Entity, World, System

Entity Factories:

  • createScreenEntity(world, config) – Root screen entity
  • createBoxEntity(world, config) – Box/container
  • createTextEntity(world, config) – Text display
  • createButtonEntity(world, config) – Button
  • createCheckboxEntity(world, config) – Checkbox
  • createInputEntity(world, config) – Text input
  • createSelectEntity(world, config) – Dropdown select
  • createListEntity(world, config) – List

Systems:

  • inputSystem(world) – Process keyboard/mouse
  • layoutSystem(world) – Calculate layout
  • renderSystem(world) – Render to buffer
  • outputSystem(world) – Write buffer to terminal
  • animationSystem(world) – Update animations
  • focusSystem(world) – Manage focus
  • cleanup(), clearScreen()

Component Helpers:

  • getText(world, eid), setText(world, eid, text)
  • getPosition(world, eid), setPosition(world, eid, x, y)
  • getDimensions(world, eid), setDimensions(world, eid, w, h)
  • setZIndex(world, eid, z), getZIndex(world, eid), normalizeZIndices(world)
  • toggle(world, eid) – Toggle visibility
  • scrollByLines(world, eid, delta), scrollToTop/Bottom/Line(world, eid, ...)
  • focusNext(world), focusPrev(world)
  • prepend(world, parentEid, childEid) – Add child to front
  • hitTest(world, x, y) – Find entity at coordinates

Terminal I/O:

  • enableInput(world), disableInput(world)
  • enableKeys(world), disableKeys(world)
  • enableMouse(world), disableMouse(world)
  • stripAnsi(str), createDoubleBuffer(w, h)
  • getCell(buf, x, y), setCell(buf, x, y, cell)
  • clearBuffer(buf), fillRect(buf, x, y, w, h, cell)

Schemas:

  • BoxConfigSchema, PositionValueSchema, TextConfigSchema

Utilities:

  • renderText(text, options), wrapText(text, width)
  • getLine(rope, n), getLines(rope), getStats(rope)

Tier 2: Module Subpath Imports

Full module access for advanced use:

import { position, content, scroll } from 'blecsd/components';
import { animationSystem, collisionSystem } from 'blecsd/systems';
import { box, tabs, modal, flexbox } from 'blecsd/widgets';
import { createDoubleBuffer } from 'blecsd/terminal';
import { BoxConfigSchema } from 'blecsd/schemas';
import { renderText, wrapText } from 'blecsd/utils';
import { enableDebugOverlay } from 'blecsd/debug';
import { queueKeyEvent } from 'blecsd/input';

Tier 3: Namespace Objects (Preferred for Complex Apps)

Frozen plain objects grouping related functions:

import { position, content, dimensions, border } from 'blecsd/components';

position.set(world, eid, 10, 5);
content.set(world, eid, 'Hello');
dimensions.set(world, eid, 40, 10);
border.set(world, eid, { type: 'line' });

Component Namespaces (from blecsd/components)

Each is a frozen object of pure functions:

Namespace Purpose Key Functions
position x, y, z coordinates set, get, setZIndex, getZIndex
dimensions width, height set, get
content Text content set, get, setText, getText
renderable Visibility, style toggle, setVisible, isVisible
border Border style set, get, hasBorder
padding Inner padding set, get
hierarchy Parent-child tree setParent, getParent, getChildren, prepend
focus Focus management setFocusable, isFocused
scroll Scroll state set, get, scrollBy
interactive Click/hover set, isInteractive
animation Animation state set, get
velocity Movement speed set, get
collision Hit detection set, get, isEnabled
health Entity health/HP set, get, damage, heal
sprite Sprite data set, get, setFrame
tileMap Tile grid data set, get
camera Camera viewport set, get, setTarget
particle Particle emitter set, get
stateMachine FSM state set, get, transition
timer Countdown/stopwatch set, get, start, stop
userData Custom data store set, get

Widget Namespaces (from blecsd/widgets)

Over 50 widget namespaces. Key ones:

Namespace Purpose Create Function
box Container with border, padding createBox(world, config)
text Static text display createText(world, config)
buttonWidget Clickable button createButton(world, config)
checkboxWidget Toggle checkbox createCheckbox(world, config)
radioWidget Radio button group createRadioGroup(world, config)
listWidget Selectable list createList(world, config)
listTable Tabular list createListTable(world, config)
tabs Tab container createTabs(world, config)
modal Modal dialog createModal(world, config)
flexbox Flex layout createFlexContainer(world, config)
grid Grid layout createGrid(world, config)
splitPane Resizable split view createSplitPane(world, config)
scrollableBox Scrollable container createScrollableBox(world, config)
fileManager File browser createFileManager(world, config)
tree Tree view createTree(world, config)
panel Titled panel createPanel(world, config)
progressBarWidget Progress indicator createProgressBar(world, config)
formWidget Form with fields createForm(world, config)
toast Toast notification showInfoToast/showErrorToast/...
message Status message showInfo/showError/showWarning/showSuccess
commandPalette Command palette (Ctrl+P) createCommandPalette(world, config)
bigText Large ASCII text createBigText(world, config)
canvas Drawing canvas createCanvas(world, config)
sparkline Inline chart createSparkline(world, config)
barChart Bar chart createBarChart(world, config)
lineChart Line chart createLineChart(world, config)
gauge Gauge/meter createGauge(world, config)
virtualizedList Performant large list createVirtualizedList(world, config)
accordion Collapsible sections createAccordion(world, config)
autocomplete Autocomplete input createAutocomplete(world, config)
calendar Date picker calendar createCalendar(world, config)
streamingText Streaming text display createStreamingText(world, config)
terminalWidget Embedded terminal createTerminal(world, config)
devTools ECS inspector createDevTools(world, config)

Systems (from blecsd/systems)

System Purpose Phase
inputSystem Process keyboard/mouse events INPUT
focusSystem Tab navigation, focus management INPUT
animationSystem Tweens, springs, easing ANIMATION
layoutSystem Position/size calculation LAYOUT
renderSystem Write entities to screen buffer RENDER
outputSystem Flush buffer to terminal POST_RENDER
collisionSystem AABB collision detection UPDATE
cameraSystem Camera viewport tracking UPDATE
particleSystem Particle emitter/updater ANIMATION
behaviorSystem AI/movement behaviors UPDATE
sceneGraphSystem Transform hierarchy EARLY_UPDATE
stateMachineSystem FSM transitions UPDATE
smoothScrollSystem Momentum scrolling ANIMATION
gameLoopSystem Fixed timestep physics UPDATE
workerPoolSystem Web worker management UPDATE
constraintLayout Constraint-based layout LAYOUT
frameBudgetSystem Frame time monitoring POST_RENDER

Common Patterns

Hello World

import {
  createWorld, createScreenEntity, createBoxEntity,
  setPosition, setDimensions, setText,
  inputSystem, layoutSystem, renderSystem, outputSystem,
  prepend,
} from 'blecsd';

const world = createWorld();
const screen = createScreenEntity(world, { width: 80, height: 24 });
const box = createBoxEntity(world, {
  position: { x: 10, y: 5 },
  dimensions: { width: 30, height: 5 },
  border: { type: 'line' },
  content: 'Hello, blECSd!',
});
prepend(world, screen, box);

// Run systems manually
inputSystem(world);
layoutSystem(world);
renderSystem(world);
outputSystem(world);

Using the Scheduler

import { createWorld, createScheduler, PhaseType } from 'blecsd';

const world = createWorld();
const scheduler = createScheduler();

scheduler.register(inputSystem, PhaseType.INPUT);
scheduler.register(layoutSystem, PhaseType.LAYOUT);
scheduler.register(renderSystem, PhaseType.RENDER);
scheduler.register(outputSystem, PhaseType.POST_RENDER);

// Game loop
function tick() {
  scheduler.run(world);
  requestAnimationFrame(tick);
}
tick();

Custom System

import { defineQuery, defineSystem, hasComponent } from 'blecsd';
import { Position } from 'blecsd/components';
import { Velocity } from 'blecsd/components';

const movingQuery = defineQuery([Position, Velocity]);

function createMovementSystem() {
  return defineSystem((world) => {
    const entities = movingQuery(world);
    for (const eid of entities) {
      Position.x[eid] += Velocity.x[eid];
      Position.y[eid] += Velocity.y[eid];
    }
    return world;
  });
}

Widget API Pattern

import { createBox, setBoxContent, isBox } from 'blecsd/widgets';

const box = createBox(world, {
  position: { x: 0, y: 0 },
  dimensions: { width: '100%', height: '100%' },
  border: { type: 'line', fg: 0x00ff00 },
  padding: { top: 1, left: 2 },
  content: 'Initial content',
});

// Update content
setBoxContent(world, box, 'Updated content');

// Type check
if (isBox(world, box)) { /* ... */ }

Keyboard Shortcuts

Global: Tab (focus next), Shift+Tab (focus prev), Escape (blur). Lists: Up/k, Down/j, Home/g, End/G, PageUp/Down, Enter (select), / (search). Text input: Ctrl+A (start), Ctrl+E (end), Ctrl+U (delete to start), Ctrl+K (delete to end), Ctrl+W (delete word).

Error Handling

blECSd uses Result types for recoverable errors:

import { ok, err, isOk, isErr, map } from 'blecsd/errors';

function parseConfig(raw: unknown): Result<Config, ValidationError> {
  const result = ConfigSchema.safeParse(raw);
  if (!result.success) return err(createValidationError(result.error));
  return ok(result.data);
}

Error categories: validation, terminal, system, entity, component, input, render, config, internal.

Testing

import { describe, it, expect } from 'vitest';
import { createWorld, addEntity, addComponent, hasComponent } from 'blecsd';

describe('movement system', () => {
  it('updates position from velocity', () => {
    const world = createWorld();
    const eid = addEntity(world);
    addComponent(world, eid, Position);
    addComponent(world, eid, Velocity);
    Position.x[eid] = 0;
    Velocity.x[eid] = 5;

    movementSystem(world);

    expect(Position.x[eid]).toBe(5);
  });
});

Common Anti-Patterns

  1. Using classes – All code must be functional. Use factory functions + plain objects.
  2. Importing from bitecs directly – Always import from blecsd or ../core/ecs.
  3. Putting logic in component files – Components are data only. Logic goes in systems.
  4. Adding world to functions that don’t need it – Only ECS-aware functions need world.
  5. Deep nesting – Use guard clauses and early returns.
  6. Using any – Use unknown with type guards.
  7. Missing Zod validation at boundaries – All config objects need Zod schemas.
  8. Creating files when existing modules suffice – Search src/ before creating new files.
  9. Forgetting barrel exports – Update the module’s index.ts when adding exports.
  10. Processing input outside INPUT phase – All input goes through inputSystem.

Module Ownership (Ambiguous Names)

Several function names exist in multiple modules:

Function Canonical Module Notes
moveCursor components/textInput/cursor 6+ versions exist. Public API exports text-input version.
fillRect terminal/screen/cell 3D package has its own on PixelFramebuffer.
getText components/content Rope utils also have getText but different signature.

Always check which module you need. Use explicit subpath imports for non-canonical versions.

Development Commands

pnpm install          # Install dependencies
pnpm dev              # Development mode
pnpm build            # Build (run frequently, catches issues tests miss)
pnpm test             # Run tests
pnpm test:watch       # Watch mode
pnpm lint             # Biome linter
pnpm lint:fix         # Auto-fix lint
pnpm typecheck        # TypeScript type check

Performance Tips

  • Cache queries: const myQuery = defineQuery([Position, Velocity]) once, reuse everywhere.
  • Batch component reads: access all components for an entity in one pass.
  • Use dirty tracking: only re-render entities that changed.
  • Virtualize large lists with createVirtualizedList.
  • Use double buffering (createDoubleBuffer) for flicker-free rendering.
  • Avoid allocations in hot loops: reuse objects, pre-allocate arrays.
  • Use frameBudgetSystem to monitor frame times.

Add-on Packages

blECSd has optional packages for specialized functionality:

Package Import Purpose
@blecsd/3d import { ... } from '@blecsd/3d' 3D rendering with software rasterizer
@blecsd/ai import { ... } from '@blecsd/ai' AI/LLM interface widgets
@blecsd/game import { ... } from '@blecsd/game' High-level game API
@blecsd/audio import { ... } from '@blecsd/audio' Audio management
@blecsd/media import { ... } from '@blecsd/media' Image/video/GIF rendering