Documentation
Browse documentation

DocumentationGet started

Creator guide

This guide takes a PlayerVox OverCrow widget from an empty directory to a submission: create a project, edit its view, style and logic, run it in OverCrow, test it, package it and submit it. Each step links to the page that covers it in full.

To work with an AI assistant instead, add PlayerVox OverCrow's MCP server to it: see MCP.

1. Install the tools

  • overcrow-widget, the widget CLI, one binary for Linux and Windows. See installing the CLI.
  • Node.js 22 or later and npm, so that check can type-check your logic with the project's own TypeScript and the SDK from npm. The CLI checks and packages without it, but warns that types were not checked.
  • PlayerVox OverCrow, to run the widget while you write it.

overcrow-widget doctor tells what your setup has and lacks.

2. Create a project

SH
overcrow-widget init my-counter --template counter
cd my-counter
npm install

npm install brings TypeScript and @overcrow/sdk 1.0.0, named in the project's package.json.

The templates are blank, counter, list and chart. The ID defaults to yourhandle.<dir>: pass --id with your widget's ID, your publisher handle and a name (nova.my-counter), reserved in the creator space. Files of a project lists every file and says which ones are packaged; the SDK types says how to add the SDK to another project.

3. The view

view.ocml is markup: elements, attributes, {…} expressions bound to the state, and on:<event> handlers that call functions exported by the logic.

OCMLtemplates/counter/view.ocml
<box class="counter">
  <text class="value">{state.count}</text>
  <box class="actions">
    <button class="step" label={t("decrement")} on:activate={decrement}>
      <icon name="minus"/>
    </button>
    <button class="step" label={t("increment")} on:activate={increment}>
      <icon name="plus"/>
    </button>
  </box>
</box>

The view also has if, for with a mandatory key, and local components: see the view. Every element and attribute is in elements and attributes.

4. The style

style.ocss is a bounded subset of CSS: class, element, descendant and child selectors, the :hover, :active, :focus, :disabled and :checked states, flex and grid layout, and the theme's design tokens as var(--…), which follow the user's light or dark theme.

OCSStemplates/counter/style.ocss
.step {
  width: var(--icon-button-size);
  height: var(--icon-button-size);
  border-radius: var(--radius-md);
  background: var(--color-surface-raised);
}
OCSStemplates/counter/style.ocss
.step:hover {
  background: var(--color-surface-hover);
}

There is no @media, @import, url() or calc(): an unknown property or selector is an error, not a silent no-op. See the style and style properties and tokens.

5. The logic

logic.ts owns the state. Declare its type once, mutate it in your handlers, and the view follows: after each turn the VM evaluates the view again and sends only what changed.

TStemplates/counter/logic.ts
import { initState } from "@overcrow/sdk";

declare module "@overcrow/sdk" {
  interface WidgetState {
    count: number;
  }
}
TStemplates/counter/logic.ts
export function increment(): void {
  state.count += 1;
}

The logic runs in a sandboxed VM, not in a browser: there is no DOM, fetch, setTimeout, console or Intl. The SDK provides timers, services, formatting helpers for times and numbers in the user's regional format, messages and drawing. See the logic and the SDK reference.

6. Messages

t("key") in the view or the logic reads locales/en.json or locales/fr.json for the user's language, and fills {name} parameters.

JSONtemplates/counter/locales/en.json
{
  "decrement": "Decrease",
  "increment": "Increase"
}

Provide both files or neither, with the same keys. See messages.

7. Check

SH
overcrow-widget check

check runs the same validators as PlayerVox OverCrow: manifest, view, style, locales, a lint of the logic against what the VM runs, the project's TypeScript, and a package built in memory and read back. Each problem has a stable code, a position and a help line; see diagnostics.

8. Run it in OverCrow

SH
overcrow-widget dev

dev sends the widget to the PlayerVox OverCrow overlay running on your machine and reloads it each time you save. OverCrow offers this only when it was started with development installs allowed (OVERCROW_WIDGET_DEVELOPMENT=1; overcrow-widget doctor prints the commands). The widget shows an Unverified · development package marker; its declared permissions are granted for the session only, and nothing is stored. log.info(…) from your logic prints in the terminal. See the development channel.

9. Test it

Two kinds of tests, both described in testing a widget:

  • unit tests of the logic with @overcrow/sdk/testing, which runs your module with virtual time and simulated services;
  • scenarios played by overcrow-widget test in PlayerVox OverCrow's headless runtime: the real VM and renderer, services answered by fixtures, and images compared with references.
JSONtemplates/counter/tests/example.scenario.json
  "host": { "mode": "interactive" },
  "steps": [
    { "expect": { "state": "running", "text": ["0"], "image": "initial" } },
    { "pointer": { "click": { "label": "Increase" } } },
    { "expect": { "text": ["1"], "noText": ["0"], "image": "increased", "fault": "none" } }
  ]
SH
overcrow-widget test

10. Ask for what you need

A widget gets nothing by default. To call an API, keep data or read game information, declare it in manifest.json; the user consents in the Control Center before the widget gets it. A network rule names one HTTPS origin, one method and one complete path, with typed parameters:

JSONdocs/content/examples/weather/manifest.json
  "permissions": {
    "network": [
      {
        "origin": "https://api.example.com",
        "method": "GET",
        "path": "/v1/forecast/{city}",
        "pathParams": { "city": { "type": "slug", "maxLength": 32 } },
        "queryParams": {
          "units": { "type": "enum", "values": ["metric", "imperial"], "required": true }
        }
      }
    ],
    "storage": true
  },
TSdocs/content/examples/weather/logic.ts
    const url = `https://api.example.com/v1/forecast/${state.city}?units=${state.units}`;
    const { status, body } = await http.fetch(url, { as: "json" });

Game and system data come from subscriptions (fps.subscribe, media.subscribe…), which update your state until you cancel them. See services and permissions for what each permission allows, and security for what a widget can never do. The complete example is in docs/content/examples/weather.

11. Package

SH
overcrow-widget package
overcrow-widget inspect dist/yourhandle.my-counter-0.1.0.ocpkg

package writes a deterministic .ocpkg to dist/: the same sources and CLI version give the same bytes on every system. inspect shows what a package holds and asks for, as a reviewer sees it. A package installed locally stays unverified; users get widgets from the signed catalog. See the package.

12. Submit

Run the checks of the creator space on your computer:

SH
overcrow-widget admit --listing optional

Then create your publisher and reserve the widget's ID (getting started), and send the version from the creator space (publish a widget), or from a terminal or a CI job with submit. A person at PlayerVox reviews every version before players get it: see review.