# Coming from blessed

> A blessed alternative is planned, not built: controlroom is reserved at 0.0.1 and none of its screen API exists yet. This page maps blessed's screen, box, list, key handling and render loop onto controlroom's spec, says what does not map, and shows what burgee migrate reports for a blessed program today.

Source: https://burgee.interlace.tools/docs/coming-from/blessed

**controlroom** is the burgee family's planned **blessed alternative** for full-screen,
keyboard-driven terminal programs. It is **reserved, not usable yet**: the published version
exports one constant, `status = 'reserved'`, and every controlroom snippet on this page is
marked **Planned, not built**. The page exists now so that a blessed program can be read
against the [spec](https://github.com/ofri-peretz/burgee/blob/main/.sdlc/intents/controlroom/spec.md)
before the code lands, and so that `burgee migrate` has somewhere to send you.

Only drawing inline output, with no keys? You want flagstaff, which you can use today:
[Which one do I need?](/docs/packages#which-one-do-i-need)

## Migrate from blessed: not one import

There will be no `controlroom/blessed`. blessed's surface is too large to reproduce
honestly (spec R18), so moving is a rewrite of each screen, not of the import line. What
does exist today is the report. `burgee migrate` finds the blessed sites in your project and
names the section of this page that covers each one:

```bash
npx burgee migrate --dry-run --json
```

It reports and never rewrites. The API a rewrite would target is not built, and rewriting
onto it would turn a program that runs into one that does not load. For this program:

```js
const blessed = require('blessed');

const screen = blessed.screen({ smartCSR: true });
const list = blessed.list({ items: ['build', 'test'], keys: true });
screen.append(list);
screen.key(['q', 'C-c'], () => process.exit(0));
screen.render();
```

the report's `guided` list reads, one entry per site:

```json
[
  { "file": "src/ui.js", "line": 1, "from": "blessed", "pattern": "import", "guide": "https://burgee.interlace.tools/docs/coming-from/blessed#the-mapping" },
  { "file": "src/ui.js", "line": 3, "from": "blessed", "pattern": "screen", "guide": "https://burgee.interlace.tools/docs/coming-from/blessed#screen" },
  { "file": "src/ui.js", "line": 4, "from": "blessed", "pattern": "list", "guide": "https://burgee.interlace.tools/docs/coming-from/blessed#list" },
  { "file": "src/ui.js", "line": 6, "from": "blessed", "pattern": "key", "guide": "https://burgee.interlace.tools/docs/coming-from/blessed#key-handling" },
  { "file": "src/ui.js", "line": 7, "from": "blessed", "pattern": "render", "guide": "https://burgee.interlace.tools/docs/coming-from/blessed#render-loop" }
]
```

A site is reported only when the receiver came from blessed in the same file:
`screen.key(` on a `screen` from `blessed.screen()` is a key binding, and `screen.key(` on
anything else is not. A screen passed in from another file is not followed, but that file's
own `require('blessed')` still is. The exit code stays 0, because nothing was refused. The
[Migrate](/docs/migrate) page has the whole report.

## The mapping

| blessed | controlroom | spec | on main today |
| :-- | :-- | :-- | :-- |
| `blessed.screen()` | `open(runtime, { screen: 'alternate' })` | R4 | no |
| `screen.destroy()` | `close()` on what `open()` returned | R4 | no |
| `blessed.box({ top, left, width, height })` | a pane in a `layout()` tree, holding a flagstaff component | R8, R5 | no; flagstaff's `boxComponent` is |
| `blessed.list()` | flagstaff's `tasks` in a pane, for a list you show | R5 | no; `tasks` is |
| `blessed.listbar()` | a tab bar: flagstaff's `tabBar` and R9's tab state | R3, R9 | no |
| `blessed.log()` | flagstaff's `logTail` in a pane | R3 | no |
| `screen.key([...], fn)` | an entry in a keymap, which is data | R2, R9 | no |
| `screen.on('keypress', fn)` | the same keymap | R2, R9 | no |
| `screen.render()` | nothing: a state change repaints through flagstaff | R5, R15 | no |
| `screen.program.alternateBuffer()` | `screen: 'alternate'`, left on every exit path | R4, R1 | closeout's `alternateScreen` is |
| `mouse: true`, `on('click')` | nothing | out of scope | — |
| `blessed.textbox()`, `blessed.textarea()` | caique's prompts, outside a pane | out of scope | caique is |

"On main today" is literal: a row that says no has no code behind it yet.

### Screen

`blessed.screen()` takes over the terminal when it is constructed. controlroom's
`open(runtime, { screen: 'alternate' })` is specified to do the same **only** when roundel's
output policy says `tty` and stdin can go raw (R4). In every other mode it opens a static
session instead, so the same program runs in a pipe, in CI and under `--json`. `runtime` is
the structural runtime the family's packages take instead of reading `process`.

```js
// Planned, not built: open() is R4. controlroom exports none of this today.
import { open } from 'controlroom';

const screen = open(runtime, { screen: 'alternate' });
try {
  await runTheApp(screen);
} finally {
  screen.close();
}
```

R19 adds `screen: 'inline'`, the default: a live region under the program's own output,
which stays in the scrollback. blessed has no equivalent of that mode.

`burgee migrate` reports `blessed.screen()`, `new blessed.Screen()` and `blessed.program()`
here. A program is blessed's lower-level handle on the terminal, and what it does for a
blessed screen is what `open()` is specified to do for a controlroom one.

### Box

A blessed box positions itself: `top`, `left`, `width` and `height` as cells, percentages or
`'center'`. In controlroom a pane does not position itself. One layout tree splits the screen
into rows and columns of fixed, fractional (`{ fr, min }`) and fit-to-content sizes (R8),
and a pane is a name in that tree. What a pane draws is a flagstaff component, so its
border, padding and title come from flagstaff's `boxComponent`, which exists today.

```js
// Planned, not built: layout() is R8, and it is not on main.
import { layout } from 'controlroom';

const tree = {
  direction: 'column',
  parts: [
    { content: { direction: 'row', parts: [{ size: { fr: 1, min: 20 }, content: 'learn' }, { size: { fr: 2 }, content: 'tasks' }] } },
    { size: 8, content: 'logs' },
  ],
};
layout(tree, { x: 0, y: 0, width: 100, height: 30 }).get('tasks');
// { x: 34, y: 0, width: 66, height: 22 }
```

There is no flexbox and no constraint solver: one pass hands out the fixed cells and the
minimums, and a second shares the rest by fraction. A terminal too small for the minimums
clips the later parts, never the first. How a pane is handed its component (R5, R10) is not
designed in detail yet, so it is not shown.

`burgee migrate` reports `box()`, `text()`, `element()`, `log()` and `layout()` here. The
last is blessed's own auto-positioning container, which the layout tree replaces outright.

### List

A list you **show**, such as steps with a state each, is flagstaff's `tasks` in a pane. It
exists today, and outside a terminal it prints one line per task that has settled. A list
the user **picks from**, blessed's `keys: true` with a `select` event, has no planned pane
component. A pick is a prompt, and prompts are caique's: `select`, asked before or after the
screen, not inside it. `listbar` maps to a tab bar (R3, R9).

`burgee migrate` reports `list()`, `listtable()` and `listbar()` here.

### Key handling

blessed binds a key to a function: `screen.key(['q', 'C-c'], fn)`. controlroom binds a key
to an **action name**, in a keymap that is plain data, and the screen's state moves through a
reducer (R9). The hint line is generated from the same keymap, so it cannot advertise a key
that is not bound.

```js
// Planned, not built: initial(), reduce() and hints() are R9, and they are not on main.
import { hints, initial, reduce } from 'controlroom';

const keymap = { left: 'tab.prev', right: 'tab.next', s: 'toggle:status', q: 'quit', 'ctrl+c': 'quit' };
const labels = { 'tab.prev': 'switch tab', 'tab.next': 'switch tab', 'toggle:status': 'toggle status', quit: 'quit' };

let state = initial(['Status', 'Tail logs'], ['learn', 'tasks']);
state = reduce(state, keymap.right); // the second tab is active
hints(keymap, labels); // '←→ switch tab  s toggle status  q/^C quit'
```

An action the reducer does not know, such as `quit` here, leaves the state alone and is the
program's own to handle. Key names change spelling, as the spec's examples write them:

| blessed | controlroom |
| :-- | :-- |
| `C-c` | `ctrl+c` |
| `S-tab` | `shift+tab` |
| `escape`, `left`, `right`, `q` | unchanged |

The final spelling is caique's key decoder (R2), which is not built either. Raw mode is taken
once for the screen's whole life and handed back by closeout. No API waits for a key unless
there is a terminal to press it on (R7).

`burgee migrate` reports `key()`, `onceKey()`, `unkey()` and `removeKey()` here, on a
screen, an element or a program, and every `on()` or `once()` whose event name starts with
`key`: `keypress`, and blessed's `key q` form.

### Render loop

blessed draws nothing until you call `screen.render()`, so a blessed program calls it after
every change. controlroom has no render call to make. A state change is drawn by the
compositor (R5): it writes the whole frame through flagstaff's repaint loop, diffed line by
line and wrapped in synchronized output, and lays it out again on resize. controlroom never
writes to the terminal itself (R15). When you move a screen, delete its `render()` calls
rather than translating them. `burgee migrate` reports each one, including
`box.screen.render()`.

### Alternate screen

blessed's screen switches to the alternate buffer itself, through its program's
`alternateBuffer()`, and `normalBuffer()` switches back. controlroom's is the `screen:
'alternate'` option, and leaving it is registered with closeout, so Ctrl+C, SIGTERM, a throw
and a normal return all hand the terminal back (R1, R4).

The mechanism `open()` is specified to use is on main today, in closeout:

```js
import { alternateScreen, rawMode } from 'closeout';

const undo = [rawMode(process.stdin), alternateScreen(process.stdout)];
try {
  await runTheFullScreenApp();
} finally {
  for (const back of undo) back();
}
```

Each call makes its change and registers its undo, which runs once on whichever exit comes
first. This has not been tested beside blessed, which manages the terminal itself, so it is
shown as the mechanism and not as a step to add to a blessed program.

`burgee migrate` reports `alternateBuffer()` and `normalBuffer()` here, on a program or on
`screen.program`.

## What does not map

These are out of scope in controlroom's intent, so a program that depends on them should
stay on blessed.

### Mouse

Clicks, the wheel and drags (`mouse: true`, `screen.enableMouse()`, `on('click')`) have no
controlroom equivalent, and none is planned. Mouse support is reopened only by an adopter's
measured need. `burgee migrate` reports each mouse site it can see, so you know how many
there are: `enableMouse()`, and `on()` with `mouse`, `click`, `mousedown`, `mouseup`,
`mousemove`, `mouseover`, `mouseout`, `wheeldown` or `wheelup`.

### Text editing in a pane

`textbox()`, `textarea()` and `form` edit text inside the screen, and `burgee migrate`
reports the first two here. controlroom does not edit text in a pane: prompts
stay with caique, asked outside a pane. The one exception is planned for chat-shaped
screens: caique's line editor hosted as the input line of an inline screen (R20). A text
editor pane stays out.

### Legacy Windows consoles

controlroom handles no Windows legacy console quirks beyond what `node:readline` already
does. If your blessed program relies on behaviour specific to an old Windows console, check
that before you plan a move.

The rest of blessed's widget catalogue, including images, embedded terminals and forms, has
no controlroom counterpart either. The mapping above is the whole of what is planned.

## Is controlroom compatible with blessed?

**No, by design.** There is no drop-in and nothing is graded: no blessed suite runs against
controlroom, and none will, because the API is not being reproduced. The
[Compatibility](/docs/compatibility) page carries controlroom's planned grade against **Ink**,
whose suite is to be vendored and run with a control (R13). It will not carry a blessed row.

## What you gain over blessed

All of this is what the spec commits controlroom to, not something it does today.

- **Every caller gets an answer, not just a terminal.** In `pipe`, `ci` and `accessible`
  mode a screen prints each pane's static projection, in the declared pane order and under
  its label. It writes no alternate screen, no cursor movement, no `\r` and no escape byte.
  Tabs hide nothing and no key hints are printed (R6).
- **`--json` is an event stream.** Under `--json`, stdout belongs to the result envelope and
  the screen's events go to stderr as NDJSON, `{ event, pane, state }`, so an agent reads
  the same program a person watches (R6).
- **It never hangs.** No API waits for a key unless the mode is `tty` and stdin can go raw.
  A wait that cannot be satisfied resolves at once with the static result, or throws with a
  `fix` (R7).
- **One render engine.** The spinner in a pane is the same flagstaff component, drawn by the
  same frame loop, as the spinner in an ordinary CLI (R15).
- **Nothing installed from outside the family.** That is a rule every published package in
  this repository is held to by a lock, controlroom included.

This page does not say what blessed writes to a pipe or under CI, and makes no size or speed
comparison: none has been measured here. The spec's conformance test, which spawns a screen
in every mode and fails on an escape byte, a `\r` or a hang, is how these claims will be
checked when the code exists.

## When to switch from blessed

**Not yet.** None of controlroom's screen API exists, and a blessed program has nothing to
move onto. The spec's order is the core first (R4 to R7), then the native API (R8 to R10)
with these guides' snippets. Watch the
[package page](/docs/packages/controlroom), which changes when a requirement ships.

When it exists, switch if your program's output has to work for a pipe, CI, a screen reader
or an agent as well as for a person at a terminal. Stay on blessed if you depend on the
mouse, on editing text inside a pane, or on widgets outside the mapping above.
