npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@flyingrobots/bijou-tui

v5.0.0

Published

TEA runtime for terminal UIs — model/update/view with keyboard input, alt screen, and layout helpers.

Readme

@flyingrobots/bijou-tui

The high-fidelity TEA runtime for Bijou.

@flyingrobots/bijou-tui provides the application loop, layout primitives, and physics-powered orchestration needed to build complex interactive terminal apps.

Role

  • The Elm Architecture (TEA): A deterministic state-update-view loop for industrial-strength terminal software.
  • Fractal TEA: Compose nested sub-apps with createSubAppAdapter(), initSubApp(), updateSubApp(), and mount().
  • Declarative Motion: Interpolate layout changes smoothly with physics-based springs and tween animations.
  • Surface-First Pipeline: Programmable rendering middleware for fragments, diffing, and shader-based transitions.

Install

npm install @flyingrobots/bijou @flyingrobots/bijou-node @flyingrobots/bijou-tui

Quick Start (Sub-App Composition)

import { startApp } from '@flyingrobots/bijou-node';
import { createSubAppAdapter, mount, type App } from '@flyingrobots/bijou-tui';
import { createSurface } from '@flyingrobots/bijou';

type ChildMsg = { type: 'tick' };
type ParentModel = {
  left: { count: number };
  right: { count: number };
};
type ParentMsg = { type: 'left'; msg: ChildMsg } | { type: 'right'; msg: ChildMsg };

const childApp: App<{ count: number }, ChildMsg> = {
  init: () => [{ count: 0 }, []],
  update: (msg, model) => [model, []],
  view: (model) => {
    const s = createSurface(20, 5);
    s.fill({ char: '.' });
    return s;
  }
};

const mapLeft = createSubAppAdapter<ParentMsg, ChildMsg>({
  tick: (msg) => ({ type: 'left', msg }),
});

const mapRight = createSubAppAdapter<ParentMsg, ChildMsg>({
  tick: (msg) => ({ type: 'right', msg }),
});

const app: App<ParentModel, ParentMsg> = {
  init: () => [{ left: { count: 0 }, right: { count: 0 } }, []],
  update: (msg, model) => [model, []],
  view: (model) => {
    const [left] = mount(childApp, { model: model.left, onMsg: mapLeft });
    const [right] = mount(childApp, { model: model.right, onMsg: mapRight });
    
    const screen = createSurface(80, 24);
    screen.blit(left, 0, 0);
    screen.blit(right, 40, 0);
    return screen;
  }
};

await startApp(app);

For Node hosts, prefer startApp() for the first-app path. Reach for run(app, { ctx }) when the host owns context creation explicitly.

When you need to mix small string fragments with surface-returning primitives, keep composition on the surface side: use contentSurface() directly or pass strings into vstackSurface() / hstackSurface(). Raw strings are still not a valid view() return type.

Strategy: Choosing Component Families

Select the family based on the interaction semantic.

Overlays and Interruption

  • drawer(): Supplemental detail while maintaining main context.
  • modal(): Required decision that blocks background activity.
  • toast(): Transient notification for a single event.
  • tooltip(): Micro-explanation for a local target.
  • debugOverlay(): Development-only perf HUD composited onto any app surface.

Collection Interaction

  • navigableTable(): Keyboard-driven traversal and cell inspection.
  • browsableList(): Description-led traversal in one dimension.
  • commandPalette(): Action discovery and navigation.

Shell and Workspace Layout

  • createFramedApp(): Batteries-included workspace with tabs, panes, and help.
  • splitPane(): Dynamic primary/secondary context comparison.
  • grid(): Stable regions with simultaneous visibility.
  • viewport(): The canonical scroll mask for rich composition.

For framed shells, Node hosts can still prefer startApp(app): the hosted Node bootstrap delegates to self-running framed apps automatically. Use the explicit runner path when you want to stay inside @flyingrobots/bijou-tui or when the host owns ctx directly:

import { createFramedApp, runFramedApp } from '@flyingrobots/bijou-tui';

const app = createFramedApp({ pages: [page] });

await app.run({ ctx });
// or: await runFramedApp({ pages: [page] }, { ctx });

This path keeps the shell batteries included: mouse input defaults to true, the shared runtime loop still does the heavy lifting, and frame timing/budget telemetry stays attached to the frame model for shell-owned UI. For Node-hosted apps, startApp(app) remains the default bootstrap.

Animation

Spring Physics

import { animate, SPRING_PRESETS } from '@flyingrobots/bijou-tui';

const cmd = animate({
  from: 0,
  to: 100,
  spring: 'wobbly',
  onFrame: (v) => ({ type: 'scroll', y: v }),
});

Timeline Orchestration

import { timeline } from '@flyingrobots/bijou-tui';

const tl = timeline()
  .add('slideIn', { type: 'tween', from: -100, to: 0, duration: 300 })
  .label('settled')
  .add('bounce', { from: 0, to: 10, spring: 'wobbly' }, 'settled')
  .build();

Post-Process Shaders

import { run, surfaceShaderFilter, scanlines, vignette } from '@flyingrobots/bijou-tui';

await run(app, {
  configurePipeline(pipeline) {
    pipeline.use('PostProcess', surfaceShaderFilter(
      scanlines({ dimFactor: 0.82 }),
      vignette({ edgeFactor: 0.78 }),
    ));
  },
});

Use surfaceShaderFilter(...) to compose built-in post-process passes like scanlines(), flicker(), noise(), and vignette() over the packed target surface before diff/output.

Testing

Use testRuntime() when you want an inspectable harness instead of a one-shot script result:

import { testRuntime } from '@flyingrobots/bijou-tui';

const harness = await testRuntime(app, { ctx });
await harness.press('q');

expect(harness.frame).toBeDefined();
expect(harness.messages).toHaveLength(1);
expect(harness.commands.every((record) => record.settled)).toBe(true);

await harness.teardown();

Keep runScript() for fixture-style interaction playback and GIF/demo capture, and use testRuntime() when you need direct assertions on snapshots, emitted messages, command outcomes, or cleanup disposal.

Documentation


Built with 💎 by FLYING ROBOTS