@pokit/tabs-core

@pokit/tabs-core contains the framework-agnostic state, types, and process management used by tabbed terminal adapters.

Installation

bun add @pokit/tabs-core

This package is typically a dependency of tabs adapter implementations (like @pokit/opentui), not installed directly.

Core pieces

State Management

Shared state reducer for tabs UI:

import { createInitialState, reducer } from '@pokit/tabs-core';

const state = createInitialState();
const newState = reducer(state, { type: 'TAB_SELECT', tabId: 'tab-1' });

Process Manager

Handles spawning and managing tab processes:

import { ProcessManager } from '@pokit/tabs-core';

const pm = new ProcessManager({
  onOutput: (tabId, data) => {
    /* handle output */
  },
  onExit: (tabId, code) => {
    /* handle exit */
  },
});

pm.spawn({ id: 'dev', exec: 'npm run dev', cwd, env });
pm.killAll();

Types

import type {
  TabStatus, // 'running' | 'success' | 'error' | 'killed'
  TabProcess, // Process state
  ActivityNode, // Event-driven activity
  GroupNode, // Event-driven group
  EventDrivenState, // Full UI state
} from '@pokit/tabs-core';

Status Indicators

import { STATUS_INDICATORS, getStatusIndicator } from '@pokit/tabs-core';

// STATUS_INDICATORS = {
//   running: '●',
//   success: '✓',
//   error: '✗',
//   killed: '○',
// }

const indicator = getStatusIndicator('running'); // '●'

State Shape

type EventDrivenState = {
  tabs: TabProcess[];
  activeTab: number;
  scrollOffset: number;
};

type TabProcess = {
  id: string;
  label: string;
  exec: string;
  status: TabStatus;
  output: string[];
  exitCode: number | null;
};

Actions

type Action =
  | { type: 'TAB_ADD'; tab: TabProcess }
  | { type: 'TAB_SELECT'; index: number }
  | { type: 'TAB_OUTPUT'; tabId: string; lines: string[] }
  | { type: 'TAB_EXIT'; tabId: string; code: number }
  | { type: 'SCROLL'; offset: number };

Process Manager API

class ProcessManager {
  constructor(callbacks: ProcessManagerCallbacks);

  spawn(spec: TabSpec & { id: string; cwd: string; env: Record<string, string> }): void;
  kill(id: string): void;
  killAll(): void;
  isRunning(id: string): boolean;
}

Constants

// Maximum output lines to keep per tab
const MAX_OUTPUT_LINES = 1000;

// Output batching interval
const OUTPUT_BATCH_MS = 16;

Usage in Adapter

import { createInitialState, reducer, ProcessManager } from '@pokit/tabs-core';

function createTabsAdapter(): TabsAdapter {
  return {
    async run(items, options) {
      let state = createInitialState();

      const pm = new ProcessManager({
        onOutput: (tabId, data) => {
          state = reducer(state, { type: 'TAB_OUTPUT', tabId, lines: [data] });
          render(state);
        },
        onExit: (tabId, code) => {
          state = reducer(state, { type: 'TAB_EXIT', tabId, code });
          render(state);
        },
      });

      // Spawn processes
      for (const item of items) {
        pm.spawn({ ...item, cwd: options.cwd, env: options.env });
      }

      // Wait for completion
      await waitForExit(pm);
    },
  };
}