Nova · screens

The screen is a document.

Nova draws an app from documents. An action is its data, a layout over that data, and the triggers that change it. Your own components do the drawing. Because the screen is a document, it can be written, checked and replaced while the app runs.

pnpm add @niscorp/nova @niscorp/prism @niscorp/strata zod · 0.5.1 · Apache-2.0

A ticket, running, and the document it is drawn from

Change the document

The ticket’s layout

{
  component: 'Stack',
  props: { gap: 'tight' },
  children: [
    { component: 'Title', children: '$.show' },
    { component: 'Text', children: '{{$.seats}} seats' },
    {
      component: 'Button',
      ref: 'more',
      children: 'One more',
    },
  ],
}
The ticket, runningpress it

The Tempest

2 seats

Press “One more” a few times, then change the document. The screen is drawn again from what you chose, and the count stays where you left it: nothing was reloaded, and no component was written. A model can make the same change, and Nova checks it first.

First steps →
An action

Four parts, one document.

An action is the unit of an app. This one is a booking, whole: there is no other file behind it.

data is what the action holds when it opens. Everything else reads it or changes it.

data
{ show: 'The Tempest', seats: 2, total: 0 }

layout is a tree of your components, with bindings into the data. It is resolved, never run.

layout
{
  component: 'Stack',
  children: [
    {
      component: 'Text',
      children: '{{$.seats}} seats for {{$.show}}',
    },
    { component: 'Text', children: '{{$.total}} euros' },
    {
      component: 'Button',
      ref: 'more',
      children: 'One more',
    },
    {
      component: 'Button',
      ref: 'price',
      children: 'Price it',
    },
  ],
}

triggers answer an event on a ref with steps: here one changes the data, and one makes a call.

triggers
[
  {
    event: 'ui:click',
    ref: 'more',
    do: [{ increment: 'seats' }],
  },
  {
    event: 'ui:click',
    ref: 'price',
    do: [{ call: 'quote' }],
  },
]

endpoints are the calls an action can make, by name: what is sent, and where the answer goes.

endpoints
{
  quote: {
    url: '/api/quote',
    method: 'POST',
    request: { seats: { $ref: '$.seats' } },
    response: { $ref: '$.total' },
    target: 'total',
  },
}
Nova’s audit of this document finds nothing.
A layout

Three ways to read the data.

A layout runs no code. It points into the action’s data, and Nova resolves each node before your component sees it. Three nodes, read against the same data.

The action’s data
{ show: 'The Tempest', seats: 2, name: '' }

A string that starts with $. is a path. The value at it is put in its place.

In the layout
{ component: 'Text', children: '$.show' }
Drawn
The Tempest

A string with {{…}} in it is a template. Each path in it is filled in.

In the layout
{ component: 'Text', children: '{{$.seats}} seats' }
Drawn
2 seats

A node with if draws one branch or the other. An empty name is not there, so it is the second.

In the layout
{
  if: '$.name',
  then: { component: 'Text', children: 'For {{$.name}}' },
  else: { component: 'Text', children: 'For nobody yet' },
}
Drawn
For nobody yet
Adapters

One tree, any surface.

Nova’s core resolves a layout to a plain tree, with no framework in it. An adapter turns that tree into something on a surface, with your components, and the same events come back from every one. Five ship in the box. An adapter is a walker over the tree and a kit of about fifteen plain components, so one for another surface is a folder beside these.

AdapterSurfaceHow it is pressed
adapters/reacta browser, your React componentspointer and keyboard
adapters/vuea browser, your Vue componentspointer and keyboard
adapters/doma browser, no framework: this pagepointer and keyboard
adapters/ttyany terminal, a line at a timetype the number beside it
adapters/inkany terminal, full screenthe same numbers, and focus
yoursSvelte, React Native, anything that can draw a treewhatever that surface has
The audit

Found out before it is pressed.

A document can come from a model. It can parse, mount and draw, and still be broken at the press. auditAction reads a document against itself: every ref against the triggers, every call against the endpoints, every binding against the data.

The booking, as a model might hand it over

{
  id: 'booking',
  data: { show: 'The Tempest', seats: 2, total: 0 },
  layout: {
    component: 'Stack',
    children: [
      {
        component: 'Text',
        children: '{{$.seats}} seats for {{$.show}}',
      },
      { component: 'Text', children: '{{$.discount}} off' },
      {
        component: 'Button',
        ref: 'price',
        children: 'Price it',
      },
      {
        component: 'Button',
        ref: 'refund',
        children: 'Refund',
      },
    ],
  },
  triggers: [
    {
      event: 'ui:click',
      ref: 'price',
      do: [{ call: 'charge' }],
    },
  ],
  endpoints: {
    quote: {
      url: '/api/quote',
      method: 'POST',
      request: { seats: { $ref: '$.seats' } },
      response: { $ref: '$.total' },
      target: 'total',
    },
  },
}
3 faults
step calls endpoint "charge" but endpoints has no "charge"
layout binds "$.discount" but data has no "discount" default
layout ref "refund" has no trigger — dead chrome
Said plainly

What it is not.

Not a serverNova runs where its shell runs: in a page, in a terminal, or behind Moss, which serves a shell to each person. It has no server of its own.
Not a page builderThere is no editor and nothing to drag. A document is written: by you, by your coding agent, or by a model in the running app.
Not a programming languageA layout can loop over a list, pick a branch and compare two values. It cannot call a function or run code. What has to be worked out is a Prism document.
Not generated codeA model never writes JSX or HTML here. It writes a document in a closed grammar, and the same components draw it.
Not a routerThere are no routes. An action stands on a canvas, and going somewhere puts another on top of it.
Not ReactThe core has no framework in it. React is one adapter among the ones that ship, and this page uses none of it.