Nova · guide

Messages

Two actions on two canvases. One says something happened, and the other answers, without either knowing the other.

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

The kit

Nova draws nothing itself. It resolves a layout to a tree, and an adapter’s components draw the tree. This file uses the terminal kit, so it runs anywhere Node does; in an app the registry holds your own components.

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

One action speaks, one listens

Actions do not call each other. emit publishes a message on a channel, with a payload, and any action with a message trigger on that channel answers it. The listener reads what was said as @event.payload.

The two actions here name the same channel and nothing else of each other.

One action speaks, one listens
const seats: ActionDefinition = {
  id: 'seats',
  data: { count: 2 },
  layout: { component: 'Button', ref: 'book', children: 'Book {{$.count}} seats' },
  triggers: [{ event: 'ui:click', ref: 'book', do: [{ emit: { channel: 'booked', payload: '$.count' } }] }],
};
const tally: ActionDefinition = {
  id: 'tally',
  data: { bookings: 0, seats: 0 },
  layout: { component: 'Text', children: '{{$.bookings}} bookings, the last for {{$.seats}}' },
  triggers: [{ message: 'booked', do: [{ increment: 'bookings' }, { set: 'seats', value: '@event.payload' }] }],
};
Step 3 of 4

Two canvases

Each action stands on a canvas of its own. A message reaches every action the shell holds, on any canvas.

Two canvases
const shell = createShell({ canvases: [{ id: 'main' }, { id: 'side' }], registry, actions: { seats, tally } });
shell.push('main', 'seats');
shell.push('side', 'tally');
await shellSettled(shell);
const draw = (): void => console.log(createTtyView(registry, shellView(shell).api).render().text);
draw();
Step 4 of 4

Book twice

Two presses on one canvas, and the other canvas counts them.

Book twice
shell.dispatch({ type: 'ui:click', ref: 'book' });
shell.dispatch({ type: 'ui:click', ref: 'book' });
await shellSettled(shell);
draw();
Then

Run it.

Both canvases, before and after. The tally changed, and nothing in seats names it.

Run it
npx tsx messages.mts
It prints
── main ────────────────────────────────────────────────────
[1] (Book 2 seats)

── side ────────────────────────────────────────────────────
0 bookings, the last for 0
── main ────────────────────────────────────────────────────
[1] (Book 2 seats)

── side ────────────────────────────────────────────────────
2 bookings, the last for 2