Nova · guide

Drawn in a terminal

The same documents, drawn as text. Every thing that can be pressed carries a number, and a press is that number.

Install it
pnpm add @niscorp/nova @niscorp/prism @niscorp/strata zod
Step 1 of 4

The kit

Every guide here has drawn with this kit. This one looks at what it draws. defaultRegistry() is about fifteen plain components, as text.

The kit
import { createShell, shellSettled, shellView } from '@niscorp/nova';
import type { ActionDefinition } from '@niscorp/nova';
import { createTtyView } from '@niscorp/nova/adapters/tty';
import { defaultRegistry } from '@niscorp/nova/adapters/tty/components';

const registry = defaultRegistry();
registry.register('CanvasSlot', ({ children }) => ({ lines: children.flatMap((child) => child.lines) }));
Step 2 of 4

A ticket

The action is the one from the guides before: nothing in it says where it will be drawn. The same document is drawn by React, by Vue, by plain DOM and here.

A ticket
const ticket: ActionDefinition = {
  id: 'ticket',
  data: { seats: 2 },
  layout: {
    component: 'Panel',
    props: { title: 'The Tempest' },
    children: [
      { component: 'Text', children: '{{$.seats}} seats' },
      { component: 'Button', ref: 'more', children: 'One more' },
      { component: 'Button', ref: 'fewer', children: 'One fewer' },
    ],
  },
  triggers: [
    { event: 'ui:click', ref: 'more', do: [{ increment: 'seats' }] },
    { event: 'ui:click', ref: 'fewer', do: [{ decrement: 'seats' }] },
  ],
};
Step 3 of 4

A frame of text, and what can be pressed

createTtyView is given the kit and the shell’s render surface. render() answers a frame: the whole screen as text, and interactives, one for every [n] on it, each with the ref it stands for.

A frame of text, and what can be pressed
const shell = createShell({ canvases: [{ id: 'main' }], registry, actions: { ticket } });
shell.push('main', 'ticket');
await shellSettled(shell);
const view = createTtyView(registry, shellView(shell).api);
console.log(view.render().text);
for (const interactive of view.render().interactives) {
  console.log(`[${interactive.index}] ${interactive.kind} ${interactive.ref}`);
}
Step 4 of 4

Press a number

A line host reads a number and dispatches what it stands for. press does that: the interactive’s ref, and the instance it sits in as the event’s origin.

Press a number
const press = async (index: number): Promise<void> => {
  const interactive = view.render().interactives.find((one) => one.index === index);
  if (interactive === undefined) return;
  shell.dispatch({ type: 'ui:click', ref: interactive.ref, origin: interactive.origin });
  await shellSettled(shell);
};
await press(1);
await press(1);
console.log(view.render().text);
Then

Run it.

The ticket, the two things that can be pressed on it, each on a line, and the ticket again after [1] twice.

Run it
npx tsx terminal.mts
It prints
── main ────────────────────────────────────────────────────
┌─ The Tempest ─────────────────────────────────
│ 2 seats
│ [1] (One more)
│ [2] (One fewer)
[1] click more
[2] click fewer
── main ────────────────────────────────────────────────────
┌─ The Tempest ─────────────────────────────────
│ 4 seats
│ [1] (One more)
│ [2] (One fewer)