Moss · guide

A screen by grant

Two people open the same screen and one sees more on it. The richer layout is granted like a screen is.

Install it
pnpm add @niscorp/moss @niscorp/charter @niscorp/nova @niscorp/prism @niscorp/strata @niscorp/tide @niscorp/vex zod @electric-sql/pglite
Step 1 of 3

One screen, and a richer layout of it

The programme screen has a plain layout. The app also ships a second layout of the same screen, with the takings on it, under a name of its own.

The plain layout is what everybody gets. A variant only ever adds.

One screen, and a richer layout of it
import { PGlite } from '@electric-sql/pglite';
import { createServer, defineApp, mintDevToken } from '@niscorp/moss';
import type { RenderNode } from '@niscorp/nova';
import { createPglitePool } from '@niscorp/vex/pglite';

const db = new PGlite();
const runtime = { db, pool: createPglitePool(db), session: 'dev-open' as const };
const text = (says: string) => ({ component: 'Text', children: says });
const actions = { programme: { id: 'programme', layout: text('This week: The Tempest') } };
const layouts = {
  'programme.with-takings': {
    action: 'programme',
    layout: { component: 'Stack', children: [text('This week: The Tempest'), text('Saturday took 4,180')] },
  },
};
Step 2 of 3

Grant the variant

In the charter, a role’s layouts selects variants by name. The manager is granted this one. A member is granted none, so a member gets the plain layout.

Grant the variant
const charter = {
  public: [],
  member: { actions: ['programme'] },
  manager: { extends: ['member'], layouts: ['programme.with-takings'] },
};
const assignments = { grace: ['member'], ruth: ['manager'] };
const shell = { canvases: [{ id: 'main', initial: 'programme' }] };
const server = await createServer(defineApp({ charter, assignments, actions, layouts, shell }), runtime);
Step 3 of 3

The same screen, for each

Moss swaps the variant in when it builds the manager’s shell, before anything is drawn. Grace’s browser is never sent the takings, and her screen has no hidden part.

The same screen, for each
const words = (nodes: readonly RenderNode[]): string[] =>
  nodes.flatMap((node) => (node.type === 'text' ? [node.value] : 'children' in node ? words(node.children) : []));
for (const who of ['grace', 'ruth']) {
  const snapshot = await server.shells!.snapshot(mintDevToken(who), who);
  console.log(who + ':', words(snapshot.trees['main'] ?? []));
}
server.close();
Then

Run it.

The programme as Grace is sent it, and as Ruth is.

Run it
npx tsx variant.mts
It prints
grace: [ 'This week: The Tempest' ]
ruth: [ 'This week: The Tempest', 'Saturday took 4,180' ]