# controlroom

> Every export of controlroom, with its signature and doc comment: status, distribute, layout, hints, initial, reduce and 8 more, plus 17 types.

Source: https://controlroom.interlace.tools/docs/api

<!-- Generated by scripts/api-reference.ts from the built dist/*.d.ts. Do not edit; run `npx tsx scripts/api-reference.ts`. -->

controlroom — full-screen, keyboard-driven terminal screens, each with a static
projection for pipes, CI, screen readers and `--json`, and a drop-in path for `ink`.

This entry is the native API: `open()` and the runtime it takes, the layout arithmetic,
the tab, focus, collapse and scroll reducer with the hint line generated from a keymap, and the
compositor. It never loads React. ink's API is `controlroom/ink`, and plugins register
through `controlroom/plugin`.

The approved intent and spec it is built to are `.sdlc/intents/controlroom/intent.md` and
`spec.md` in https://github.com/ofri-peretz/burgee (D-158, amended by D-168).

```ts
import { status, distribute, layout, … } from 'controlroom';
```

## Functions

### collapse

The tree with every collapsed pane given no rows or columns.

```ts
function collapse(tree: Layout, collapsed: ReadonlySet<string>): Layout;
```

| Parameter | Type |
| :-- | :-- |
| `tree` | `Layout` |
| `collapsed` | `ReadonlySet<string>` |

**Returns** `Layout`

### compose

The frame. Live, each pane shows from its scroll entry in `state` (pinned to its bottom when
it has none), and the focused pane, when lines are below it, says how many in its last row.
Not live, scroll state never reaches it and every line of every pane is printed (R6).

```ts
function compose(tree: Layout, panes: Panes, size: Frame, state?: Pick<ScreenState, 'panes' | 'focused' | 'scroll'>): string[];
```

| Parameter | Type |
| :-- | :-- |
| `tree` | `Layout` |
| `panes` | `Panes` |
| `size` | `Frame` |
| `state` (optional) | `Pick<ScreenState, 'panes' \| 'focused' \| 'scroll'>` |

**Returns** `string[]`

### distribute

How many cells each part of one axis gets, given `total`. Exported for the arithmetic's own tests.

```ts
function distribute(total: number, sizes: readonly (number | {
    fr: number;
    min?: number;
})[]): number[];
```

| Parameter | Type |
| :-- | :-- |
| `total` | `number` |
| `sizes` | `readonly (number \| { fr: number; min?: number; })[]` |

**Returns** `number[]`

### extents

controlroom-scroll-pane R2 — each laid-out pane's content and rectangle height, for `reduce`.

```ts
function extents(tree: Layout, panes: Panes, size: Frame): Map<string, Extent>;
```

| Parameter | Type |
| :-- | :-- |
| `tree` | `Layout` |
| `panes` | `Panes` |
| `size` | `Frame` |

**Returns** `Map<string, Extent>`

### fit

`lines` cut to the rectangle: no more rows than it has, every row exactly its width. `at` is
the first row shown (controlroom-scroll-pane R3), and `'end'` shows the last rows. An offset
past the last page is read as the last page, so the view never sits past the content.

```ts
function fit(lines: readonly string[], rect: Pick<Rect, 'width' | 'height'>, at?: number | 'end'): string[];
```

| Parameter | Type |
| :-- | :-- |
| `lines` | `readonly string[]` |
| `rect` | `Pick<Rect, 'width' \| 'height'>` |
| `at` (optional) | `number \| 'end'` |

**Returns** `string[]`

### hints

The hint line for `keymap`: one entry per label, its keys run together in keymap order —
`←→ switch tab  s toggle status`. An action with no label is bound but not advertised.
Arrows read best run together (`←→`); any other set of keys is joined by a slash (`q/^C`).

```ts
function hints(keymap: Keymap, labels: Readonly<Record<string, string>>): string;
```

| Parameter | Type |
| :-- | :-- |
| `keymap` | `Keymap` |
| `labels` | `Readonly<Record<string, string>>` |

**Returns** `string`

### initial

```ts
function initial(tabs?: readonly string[], panes?: readonly string[]): ScreenState;
```

| Parameter | Type |
| :-- | :-- |
| `tabs` (optional) | `readonly string[]` |
| `panes` (optional) | `readonly string[]` |

**Returns** `ScreenState`

### layout

The rectangle of every pane in `tree`, inside `area`. Panes are keyed by name; a name used
twice is a mistake in the tree and throws rather than silently drawing one pane twice.

```ts
function layout(tree: Layout, area: Rect, contents?: Contents): Map<string, Rect>;
```

| Parameter | Type |
| :-- | :-- |
| `tree` | `Layout` |
| `area` | `Rect` |
| `contents` (optional) | `Contents` |

**Returns** `Map<string, Rect>`

### open

```ts
function open(rt: Runtime, given: ScreenOptions): Screen;
```

| Parameter | Type |
| :-- | :-- |
| `rt` | `Runtime` |
| `given` | `ScreenOptions` |

**Returns** `Screen`

### reduce

The next state. `extents` is every laid-out pane's height, which `compose` measures
(`extents()`): scrolling needs it, and without it a scroll action changes nothing.

```ts
function reduce(state: ScreenState, action: string, extents?: ReadonlyMap<string, Extent>): ScreenState;
```

| Parameter | Type |
| :-- | :-- |
| `state` | `ScreenState` |
| `action` | `string` |
| `extents` (optional) | `ReadonlyMap<string, Extent>` |

**Returns** `ScreenState`

### render

A pane's text at time `t`: its animated `frame` when it has one and `live` is set, its
static projection otherwise.

```ts
function render(pane: Pane, t: number, live: boolean): string[];
```

| Parameter | Type |
| :-- | :-- |
| `pane` | `Pane` |
| `t` | `number` |
| `live` | `boolean` |

**Returns** `string[]`

## Classes

### ScreenError

A screen asked for something nobody registered: what is missing, and what to do about it.

```ts
class ScreenError extends TypeError {
    readonly fix: string;
    constructor(message: string, fix: string);
}
```

## Constants

### processRuntime

The real process, narrowed. `isTTY` is a getter so the mode is decided when a screen
opens, not when this module loads; the clock is wall time through the timers Node keeps.

```ts
const processRuntime: () => Runtime;
```

### status

The reserved release's one export, kept so a program that read it still loads.

```ts
const status: "reserved";
```

## Interfaces

### Extent

A laid-out pane's height: how many lines it holds, and how many rows its rectangle has.

```ts
interface Extent {
    readonly content: number;
    readonly rows: number;
}
```

### Frame

The whole frame, `rows` lines of `columns` cells. A row a rectangle does not cover is
spaces, so the frame always replaces whatever was under it.

```ts
interface Frame {
    columns: number;
    rows: number;
    /** Milliseconds since the screen opened, for animated panes. */
    t: number;
    /** Whether animated panes draw their `frame`; a final, settled frame uses each static projection. */
    live: boolean;
}
```

### Pane

A pane: a flagstaff component, the state it is drawn with, and the label a static projection prints.

```ts
interface Pane<S = unknown> {
    component: Component<S>;
    state: S;
    label?: string;
    /**
     * Drawn on a live screen only, and left out of the static projection: a hint line or a tab
     * bar means nothing to a pipe, a screen reader or an agent, which get every pane instead (R6).
     */
    liveOnly?: boolean;
}
```

### Part

```ts
interface Part {
    /** Defaults to `{ fr: 1 }`. */
    size?: Size;
    content: Layout;
}
```

### Rect

```ts
interface Rect {
    x: number;
    y: number;
    width: number;
    height: number;
}
```

### Runtime

```ts
interface Runtime extends LoopRuntime {
    readonly stdout: Writer & OutputStream & {
        /** Rows of the terminal, read at every paint so a resize is honoured. */
        readonly rows?: number | undefined;
        on?(event: 'resize', listener: () => void): unknown;
        off?(event: 'resize', listener: () => void): unknown;
    };
    readonly stdin: KeyInput;
}
```

### Screen

```ts
interface Screen {
    /** What `open()` decided, from roundel's policy. */
    readonly mode: OutputMode;
    /** Whether keys are being read: a `tty` whose stdin can go raw, and nothing else. */
    readonly interactive: boolean;
    readonly state: ScreenState;
    update(pane: string, state: unknown): void;
    /** Text above the live region, written once and never repainted (Ink's `<Static>`). */
    commit(text: string): void;
    /** Run an action as if its key had been pressed: what a static session has instead of keys. */
    dispatch(action: string): void;
    close(): void;
}
```

### ScreenOptions

```ts
interface ScreenOptions {
    /** `'inline'` (the default) keeps the main screen; `'alternate'` takes the whole terminal. */
    screen?: 'inline' | 'alternate';
    /** The panes' arrangement, or a function of the screen state when tabs change what is shown. */
    layout: Layout | ((state: ScreenState) => Layout);
    /** Each pane is a component with its state, or a registered pane (R10) by name with its state. */
    panes: Readonly<Record<string, Pane | {
        pane: string;
        state: unknown;
    }>>;
    /**
     * Key spec → action, as data (caique R2), or the name of a registered keymap (R10). Defaults
     * to the registered `default`. `ctrl+c` is `quit` unless the keymap binds it.
     */
    keymap?: Keymap | string;
    tabs?: readonly string[];
    /** Panes that take focus, in order. */
    focus?: readonly string[];
    /** Whether this run was asked for `--json`. */
    json?: boolean;
    /** Every action, after the screen's own state has taken it: the program's half of the keymap. */
    onAction?(action: string, screen: Screen): void;
    /** R20 — an input line, drawn in one pane of the layout. */
    input?: InputOptions;
}
```

### ScreenState

```ts
interface ScreenState {
    readonly tabs: readonly string[];
    readonly active: number;
    /** Panes that take focus, in focus order. */
    readonly panes: readonly string[];
    readonly focused: number;
    readonly collapsed: ReadonlySet<string>;
    /**
     * controlroom-scroll-pane R1 — each pane's first visible row. A pane with no entry, or with
     * `'end'`, is pinned to its bottom and follows new lines, as a log tail does.
     */
    readonly scroll: ReadonlyMap<string, number | 'end'>;
}
```

### Split

```ts
interface Split {
    /** `row` places parts left to right; `column` places them top to bottom. */
    direction: 'row' | 'column';
    parts: Part[];
}
```

## Types

### Action

The actions the reducer knows. Anything else is the program's own, and leaves the state alone.

```ts
type Action = 'tab.next' | 'tab.prev' | 'focus.next' | 'focus.prev' | `tab:${string}` | `toggle:${string}` | 'scroll.up' | 'scroll.down' | 'scroll.pageUp' | 'scroll.pageDown' | 'scroll.top' | 'scroll.bottom' | 'resize';
```

### Contents

What a pane holds, for `'fit'`. Lines are measured with `linegauge`, so ANSI and wide characters count right.

```ts
type Contents = Readonly<Record<string, readonly string[]>>;
```

### Keymap

Key spec → action name, e.g. `{ left: 'tab.prev', s: 'toggle:status' }`.

```ts
type Keymap = Readonly<Record<string, string>>;
```

### Layout

A pane by name, or a split of parts.

```ts
type Layout = string | Split;
```

### Panes

```ts
type Panes = Readonly<Record<string, Pane>>;
```

### Size

How much of its parent's main axis a part takes.

- a number: exactly that many cells;
- `{ fr, min }`: a share of what the fixed and fitted parts leave, never below `min`;
- `'fit'`: as many cells as the pane's content needs along that axis — its widest line in
  a row, its line count in a column. Only a pane can fit; a split has no content of its own.

```ts
type Size = number | {
    fr: number;
    min?: number;
} | 'fit';
```

### Status

The only value {@link status} has.

```ts
type Status = typeof status;
```
