Nova · guide

Triggers and steps

What a press does is written as steps: a list of small changes to the action’s data, in order.

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

Three buttons, three triggers

A component with a ref can be answered. A trigger names an event and a ref, and do is what happens: a list of steps, run in order.

These steps are mutations, and there are ten of them: set, toggle, increment, decrement, push, pop, removeAt, move, clear and reset. Each names a path in the data. @event.payload is what the press carried.

Three buttons, three triggers
const door: ActionDefinition = {
  id: 'door',
  data: { admitted: 0, open: false, names: [] },
  layout: {
    component: 'Stack',
    children: [
      { component: 'Text', children: '{{$.admitted}} admitted' },
      { component: 'Button', ref: 'admit', children: 'Admit' },
      { component: 'Button', ref: 'doors', children: 'Doors' },
      { component: 'Button', ref: 'again', children: 'Start again' },
    ],
  },
  triggers: [
    { event: 'ui:click', ref: 'admit', do: [{ increment: 'admitted' }, { push: 'names', value: '@event.payload' }] },
    { event: 'ui:click', ref: 'doors', do: [{ toggle: 'open' }] },
    { event: 'ui:click', ref: 'again', do: [{ reset: 'admitted' }, { clear: 'names' }] },
  ],
};
Step 3 of 4

A shell, and what the action holds

push answers with the id of the instance it made. Its runtime holds the data, and held prints it.

A shell, and what the action holds
const shell = createShell({ canvases: [{ id: 'main' }], registry, actions: { door } });
const instanceId = shell.push('main', 'door');
await shellSettled(shell);
const held = (): void => console.log(JSON.stringify(shell.getRuntime(instanceId)?.getData()));
Step 4 of 4

Press them

A press is a ui:click on a ref. Two people are admitted and the doors open, and then the count starts again: reset puts a path back to what the definition said, and clear empties a list.

Press them
shell.dispatch({ type: 'ui:click', ref: 'admit', payload: 'Ada' });
shell.dispatch({ type: 'ui:click', ref: 'admit', payload: 'Grace' });
shell.dispatch({ type: 'ui:click', ref: 'doors' });
await shellSettled(shell);
held();

shell.dispatch({ type: 'ui:click', ref: 'again' });
await shellSettled(shell);
held();
Then

Run it.

The data after three presses, and after starting again. Nothing here is a handler: every change is a step in the document.

Run it
npx tsx triggers.mts
It prints
{"admitted":2,"open":true,"names":["Ada","Grace"]}
{"admitted":0,"open":true,"names":[]}