OpenTUI

repository·main·Indexed 11 days ago

https://github.com/anomalyco/opentui

A high-performance, component-based terminal UI core written in Zig with TypeScript bindings. It features a hierarchical component model using Renderables, a host-agnostic keybinding engine (@opentui/keymap) with support for multi-key sequences and layers, and dedicated integrations for React and SolidJS. Includes built-in support for Tree-sitter language parsers and QR code rendering.

Tokens
160.6K
Snippets
440
Records
627
Agent score
95%

What's inside OpenTUI

  1. Use @opentui/keymap/solid for Solid apps

    main

    The @opentui/keymap/solid package provides Solid-specific bindings for OpenTUI keymaps. These bindings consume a pre-created Keymap<Renderable, KeyEvent> from @opentui/keymap/opentui.

    Note: These bindings do not wrap the DOM/HTML adapter used in standard browser Solid apps; they are designed for OpenTUI applications using the OpenTUI keymap model.

    import { KeymapProvider, useBindings } from "@opentui/keymap/solid"
    import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui"
    
    // keymap is created via @opentui/keymap/opentui and passed to the provider
    <KeymapProvider keymap={keymap}>
      <App />
    </KeymapProvider>
  2. OpenTUI Package Overview

    main

    OpenTUI is a high-performance, component-based terminal UI core written in Zig with TypeScript bindings. It uses a C ABI for cross-language compatibility and features a flexible layout system.

    Available Packages

    • @opentui/core: TypeScript bindings for the native Zig core; provides the imperative API and all primitives.
    • @opentui/three: Three.js WebGPU renderer for OpenTUI.
    • @opentui/solid: SolidJS reconciler for OpenTUI.
    • @opentui/react: React reconciler for OpenTUI.
    • @opentui/keymap: Shared engine for commands, keybindings, and sequences.
    • @opentui/qrcode: QR encoder and terminal renderable integrations.
    • @opentui/ssh: Serves imperative, React, or Solid OpenTUI applications over SSH.
    • @opentui/examples: Example browser and standalone examples executable build.
    • @opentui/web: Private documentation website and AI agent skill source.
  3. What is NativeSpanFeed?

    main

    A NativeSpanFeed is a low-level, zero-copy wrapper around a native Zig byte feed. It is designed for high-performance transport of native bytes (such as frame data from a renderer) into JavaScript.

    Important: Most users should not use NativeSpanFeed directly. Instead, use createCliRenderer() and pass your stdin/stdout streams to it. NativeSpanFeed is intended for native integrations that already produce data via a native span-feed pointer.

  4. Use Middleware in SSH sessions

    main

    Middleware allows you to intercept sessions before they reach the main handler. Middleware receives a MiddlewareSession which includes all standard session fields (like term, cols, rows) and a context object, but it does not have access to the renderer.

    The renderer is only created once the middleware chain reaches the final .serve(handler). This ensures that a session denied by middleware does not trigger an alternate screen or renderer initialization.

    Middleware features:

    • context: An object used to pass data down the chain.
    • deny(): A method to reject the session immediately.
  5. Understand node ownership in core slots

    main

    The behavior of node lifecycle depends on how you define your slot contribution:

    1. Plain Function: The host owns the returned nodes. When the contribution is deactivated or disposed, the host automatically detaches and destroys the nodes.
    2. Managed Slot Object (CoreManagedSlot): The plugin owns the nodes. When deactivated, the host detaches the nodes but does not destroy them. This allows the plugin to reuse the same nodes if it becomes active again. The plugin is responsible for cleanup via the onDispose hook.
  6. Use EditorTraits to communicate with the host UI

    main

    The traits property allows a TextareaRenderable to advertise its intent to a host UI. This includes which keys it wants to capture, whether the UI should visually suspend (e.g., dimming borders), and a status label for footers. Assigning a new EditorTraits object emits the traits-changed event.

    Trait Fields:

    • capture: EditorCapture[] - Keys to consume: "escape", "navigate", "submit", "tab".
    • suspend: boolean - Hint to the host to suspend ambient UI.
    • status: string - Optional short label for a status bar.
    import { EditBufferRenderableEvents, type EditorTraits } from "@opentui/core"
    
    textarea.traits = {
      capture: ["escape", "submit"], // consume these before host binds
      suspend: false,
      status: "Composing reply",
    } satisfies EditorTraits
    
    textarea.on(EditBufferRenderableEvents.TRAITS_CHANGED, (traits) => {
      updateFooter(traits.status ?? "")
    })
  7. Use Box as a Flexbox layout container

    main

    The Box component acts as a flex container for its children. You can control the layout using standard flexbox-like properties:

    • flexDirection: The direction of children ("column" or "row").
    • justifyContent: Alignment along the main axis ("flex-start", etc.).
    • alignItems: Alignment along the cross axis ("stretch", etc.).
    • gap: The spacing between child elements.
    • padding: Internal spacing between the border and children.
    • flexGrow: Determines how much a child should grow to fill available space.
    const container = Box(
      {
        flexDirection: "column",
        justifyContent: "space-between",
        alignItems: "stretch",
        width: 50,
        height: 20,
        padding: 1,
        gap: 1,
      },
      Text({ content: "Header" }),
      Box({ flexGrow: 1, backgroundColor: "#222" }, Text({ content: "Content area" })),
      Text({ content: "Footer" }),
    )
  8. Configure External Output Mode

    main

    The externalOutputMode option controls how writes made through the configured stdout.write are handled while the renderer is active. It does not affect stderr, the built-in console overlay, or renderer-owned native frames.

    • "capture-stdout": Intercepts stdout.write, queues the text, and flushes it above the footer. Only valid when screenMode is "split-footer".
    • "passthrough": Leaves stdout.write untouched; output goes directly to the configured stdout.

    The default mode depends on the screenMode: "capture-stdout" for split-footer, and "passthrough" for all others.

    // Captured stdout appears above the footer
    const renderer = await createCliRenderer({
      screenMode: "split-footer",
      externalOutputMode: "capture-stdout",
    })
    
    // Switch at runtime
    renderer.externalOutputMode = "passthrough"
  9. How FFI Fast Path Paired Benchmarking works

    main

    The ffi-fast-path-paired-benchmark.ts tool is the preferred method for comparing revisions. It works by running retained batches sequentially and recording provenance and diagnostics.

    Comparison Logic

    • It reports paired nominal and multiplicity-adjusted bootstrap intervals.
    • Success Criteria: Negative deltas indicate faster performance. For a change to be considered safe, the adjusted upper bound of the regression must stay at or below a 3% regression.
    • Failures: Calibration failures trigger a retry of the complete pair. Lifecycle failures will abort the entire run.

    Requirements for Paired Runs

    • Use absolute paths for --baseline-root and --candidate-root.
    • The roots must use matching scenario/calibration sources and native libraries.
    • The pair count (--runs) must be an even number.
    • Worktrees must be clean unless --allow-dirty is used.
    • Legacy Baselines: If the baseline predates this suite, you must manually copy ffi-fast-path-scenarios.ts and ffi-fast-path-calibration.ts from the candidate into the baseline paths and use the --allow-dirty flag.
  10. How React slots work in OpenTUI

    main

    React slots allow external modules to contribute ReactNode UI into host-defined regions. The host maintains control over the layout and the types of the slots, while plugins only receive the context and props exposed by the host.

    Core API Components

    • createReactSlotRegistry(renderer, context, options?): Creates a registry specifically typed for ReactNode. It accepts the same SlotRegistryOptions as the core createSlotRegistry.
    • Slot<TSlots, TContext>: A generic React component that requires a registry prop to resolve and render plugins.
    • createSlot(registry, options?): A convenience helper that returns a <Slot /> component already bound to the provided registry.
    • ReactPlugin<TSlots, TContext>: A type alias for plugins that return ReactNode.

    Basic Usage Pattern

    1. Define your slot types and context.
    2. Create a registry using createReactSlotRegistry.
    3. Register plugins using registry.register() (no wrapper function required).
    4. Render the <Slot /> component in your React tree.
    import { createCliRenderer } from "@opentui/core"
    import { createReactSlotRegistry, createRoot, Slot } from "@opentui/react"
    
    type Slots = {
      statusbar: { user: string }
    }
    
    const context = { appName: "react-app", version: "1.0.0" }
    const renderer = await createCliRenderer()
    
    const registry = createReactSlotRegistry<Slots, typeof context>(renderer, context)
    
    const unregister = registry.register({
      id: "clock-plugin",
      slots: {
        statusbar(ctx, props) {
          return <text>{`${ctx.appName}:${props.user}`}</text>
        },
      },
    })
    
    const AppSlot = Slot<Slots, typeof context>
    
    function App() {
      return (
        <AppSlot registry={registry} name="statusbar" user="sam" mode="replace">
          <text>fallback-statusbar</text>
        </AppSlot>
      )
    }
    
    createRoot(renderer).render(<App />)
  11. Format the `colorMatrix` cell mask

    main

    The cellMask parameter for colorMatrix(...) uses packed triplets of floats:

    [x, y, perCellStrength, x, y, perCellStrength, ...]

    • x, y: Cell coordinates.
    • perCellStrength: A multiplier applied to the method's global strength.

    Behavioral Rules:

    • Incomplete trailing values (not a multiple of 3) are ignored.
    • Out-of-bounds or non-finite coordinates are skipped.
    • Non-finite effective strengths are skipped.