Skip to content

Quickstart ​

This example places a local marker through a public command, then renders it through a public layer. It needs no DOM, framework or renderer import.

Run the workspace ​

Use Node 24 LTS and the pnpm version pinned by the root packageManager field. From the repository root:

sh
pnpm install --frozen-lockfile
pnpm build
pnpm docs:dev

The packages are private during this preview. Use their workspace exports; there is no public registry installation step yet.

Define an extension ​

ts
import type { Extension, Point } from '@talerook/sdk';

export function createMarkerExtension(): Extension {
  return {
    manifest: {
      id: 'example.marker',
      version: '0.1.0',
      apiVersion: 1,
      capabilities: ['engine.commands', 'vtt.layers'],
    },
    activate(context) {
      let marker: Point | undefined;
      context.engine.registerCommand({
        id: 'example.marker/place',
        title: 'Place marker',
        execute() {
          marker = { x: 160, y: 120 };
          context.vtt.invalidate();
          return null;
        },
      });
      context.vtt.registerLayer({
        id: 'example.marker/overlay',
        order: 50,
        render() {
          const at = marker;
          return at ? [{ kind: 'circle', center: at, radius: 12, fill: '#9ebdeb' }] : [];
        },
      });
    },
  };
}

Contribution IDs start with the manifest ID followed by /. Registration returns an idempotent disposer, and the host also owns it automatically. You can release a contribution early without losing unload cleanup.

Load, operate, unload ​

ts
import { createExtensionHost } from '@talerook/engine';

const host = createExtensionHost({
  onError(error, extensionId) {
    console.error(extensionId, error);
  },
});

await host.load(createMarkerExtension());
await host.execute('example.marker/place', null);

// Your renderer calls host.renderLayers(projectedTableView).
// The marker disappears from the registry on unload.
await host.unload('example.marker');
await host.dispose();

This marker is local, transient state. Sending a durable application command requires the host's dispatch adapter. A local command must not bypass server authorization or write canonical table state directly.

Try the complete example ​

examples/beacon contains the first-party beacon example. Its marker tool, place/clear/rally commands, layer, panel and presentation use the same public SDK. Load a fresh installation through its public package entry:

ts
import { createExtensionHost } from '@talerook/engine';
import { createBeaconExtension, BEACON_IDS } from '@talerook/example-beacon';

const beaconHost = createExtensionHost();
await beaconHost.load(createBeaconExtension());
await beaconHost.execute(BEACON_IDS.place, { x: 240, y: 320 });
await beaconHost.execute(BEACON_IDS.clear, null);
await beaconHost.unload(BEACON_IDS.extension);
await beaconHost.dispose();

The marker and its presentation label are transient. The panel can place it at scene centre, clear it, or move the first controlled projected piece to it. That last action invokes BEACON_IDS.rally (example.beacon/rally), which sends token.move through CommandContext.dispatch, retaining the sceneId captured by that rendered panel action. Missing scene or piece targets reject; rendering another scene does not retarget an earlier action. It requires the host's reviewed authority adapter; the extension makes no direct network call. session.reset clears local marker state through the event API.

The no-adapter snippet above exercises only local place/clear. Durable movement is still authorized and committed by the server. Follow the source alongside the contribution reference to see how input, command execution and redraws connect. The browser application owns the actual render and panel adapters.

SDK 0.1 private preview. Hosting at docs.talerook.com is planned.