Triggers and steps
What a press does is written as steps: a list of small changes to the action’s data, in order.
pnpm add @niscorp/nova @niscorp/prism @niscorp/strata zodThe 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.
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) }));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.
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' }] },
],
};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.
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()));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.
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();Run it.
The data after three presses, and after starting again. Nothing here is a handler: every change is a step in the document.
npx tsx triggers.mts{"admitted":2,"open":true,"names":["Ada","Grace"]}
{"admitted":0,"open":true,"names":[]}