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.
pnpm add @niscorp/nova @niscorp/prism @niscorp/strata zodThe kit
Every guide here has drawn with this kit. This one looks at what it draws. defaultRegistry() is about fifteen plain components, as text.
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) }));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.
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' }] },
],
};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.
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}`);
}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.
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);Run it.
The ticket, the two things that can be pressed on it, each on a line, and the ticket again after [1] twice.
npx tsx terminal.mts── 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)