Documentation
Browse documentation

DocumentationWrite a widget

The manifest

manifest.json tells PlayerVox OverCrow what a widget is before any of its code runs: its identity and version, its names, the sizes it supports, the permissions it asks for and the rows it adds to its options menu. You write it by hand; it is packaged exactly as written.

The file is one strict JSON object. A byte-order mark, a duplicate key, trailing data or an unknown field rejects it: there is no field that PlayerVox OverCrow ignores.

A complete example

JSONtemplates/counter/manifest.json
{
  "schemaVersion": 1,
  "apiVersion": 1,
  "id": "{{id}}",
  "version": "0.1.0",
  "name": {
    "en": "{{name}}",
    "fr": "{{name}}"
  },
  "sizing": {
    "fit": "none",
    "preferred": { "width": 200, "height": 96 },
    "min": { "width": 140, "height": 72 },
    "max": { "width": 640, "height": 640 }
  }
}

overcrow-widget init writes this file; {{id}} and {{name}} are the values it fills in. A manifest with permissions and a menu:

JSONdocs/content/examples/countdown/manifest.json
{
  "schemaVersion": 1,
  "apiVersion": 1,
  "id": "nova.countdown",
  "version": "1.0.0",
  "name": { "en": "Countdown", "fr": "Compte à rebours" },
  "sizing": {
    "fit": "none",
    "preferred": { "width": 220, "height": 190 },
    "min": { "width": 180, "height": 160 },
    "max": { "width": 480, "height": 400 }
  },
  "permissions": {
    "storage": true
  },
  "wrapper": {
    "menu": [
      {
        "type": "toggle",
        "id": "show-rounds",
        "label": { "en": "Show finished rounds", "fr": "Afficher les tours terminés" },
        "icon": "circle-check",
        "default": true
      },
      {
        "type": "action",
        "id": "reset-rounds",
        "label": { "en": "Reset the rounds", "fr": "Remettre les tours à zéro" },
        "icon": "rotate-ccw",
        "visibleWhen": "show-rounds"
      }
    ]
  }
}

Fields

FieldTypeRequiredMeaning
schemaVersioninteger 1 to 1yesManifest document version.
apiVersioninteger 1 to 1yesWidget API version; selects this schema.
idwidget IDyesWidget ID, from 3 bytes to 128 bytes: at least two dot-separated segments of [a-z0-9-], each at most 63 bytes and not starting or ending with -; <handle>.<name> with the publisher's handle, or a domain the publisher verified, reversed, then a name. com.playervox and com.playervox.* are reserved for packages signed by PlayerVox.
versionversionyesSemVer 2.0.0 MAJOR.MINOR.PATCH[-PRERELEASE] in canonical form, at most 64 bytes; build metadata is rejected.
nameWidgetNameyesLocalized widget name shown by the host.
sizingSizingyesSize and fit rules applied by the widget's frame.
vmVmRequestnoVM budget request; absent means the defaults.
permissionsPermissionsnoRequested permissions; absent means none.
wrapperwrappernoWidget rows of the host options menu.
requireshost featuresnoDistinct host data sources the widget cannot work without, as in a wrapper.menu row's requires. On a machine that lacks one, the host neither starts nor shows the widget and keeps its layout; the widget comes back when the source does.

Identity and version

  • id is the widget's identity everywhere: in the catalog, in the user's profile, in its storage. It is <handle>.<name>, your publisher handle in the PlayerVox OverCrow creator space and a name (nova.lol-timers), or, once you verified a domain, that domain reversed and a name (gg.nova.lol-timers). An ID is final: it is never given to another widget, even after yours is removed. nova, example, yourhandle and yourname are example handles that nobody can publish under, and IDs under com.playervox are reserved for widgets published by PlayerVox.
  • version is a semantic version. A published version never changes: any change to a published widget is a new, higher version.
  • schemaVersion and apiVersion are both 1.

Names

name gives the name PlayerVox OverCrow shows in its widget list and menus, in English and in French. Both are required. A name shows as written: no control character and no invisible or text-direction character, such as U+200B (zero-width space) or U+202E (right-to-left override). Menu labels follow the same rule.

FieldTypeRequiredMeaning
enname textyesEnglish name.
frname textyesFrench name.

Sizes

sizing says how large the widget is when the user adds it, how small and how large the user may resize it, and whether PlayerVox OverCrow can fit its frame to its content. Lengths are logical pixels at 100 % scale; the user's content scale (50 % to 175 %) multiplies them.

FieldTypeRequiredMeaning
fitnone | both | heightyesContent fitting the widget supports: both axes, height only, or none. Manual resizing is always available.
defaultModefit | manualnoMode of a newly placed widget; default fit when fit is not none, else manual. fit with fit: none is rejected.
preferredDimensionsyesInitial size; within min and max.
minDimensionsyesSmallest size.
maxDimensionsyesLargest size.

Each of preferred, min and max is a pair of dimensions, with min ≤ preferred ≤ max on both axes:

FieldTypeRequiredMeaning
widthinteger 1 to 4096yesLogical pixels.
heightinteger 1 to 4096yesLogical pixels.

With fit: "both", PlayerVox OverCrow sizes the frame to what the view needs, in both directions: the Clock grows when the user turns the seconds on. With fit: "height", the user chooses the width and the height follows the content. With fit: "none", the frame keeps the size the user gave it and the view fills it. The user can always resize by hand.

JSONwidgets/clock/manifest.json
  "sizing": {
    "fit": "both",
    "preferred": { "width": 110, "height": 60 },
    "min": { "width": 40, "height": 24 },
    "max": { "width": 480, "height": 240 }
  },

Permissions

A widget gets nothing it has not declared here, and a declaration grants nothing by itself: the user consents first, in the Control Center. Services and permissions explains each permission and what it allows.

FieldTypeRequiredMeaning
networklist of NetworkRule ≤ 32noDistinct network rules.
storagebooleannoHost key-value storage; default false.
clipboardWritebooleannoClipboard text writes; default false.
gameEventslist of game event ≤ 32noDistinct overcrow.game.<name>.v1 event names.
capabilitiescapability namesnoDistinct capabilities of the schema.
JSONwidgets/stopwatch/manifest.json
  "permissions": {
    "capabilities": ["stopwatch.read", "stopwatch.control"]
  }

A network rule is an object of its own; its fields are described with network rules.

The options menu

Every widget has an options menu, opened from its frame. PlayerVox OverCrow puts its own rows first: opacity, content size, fit to content and, when the widget needs one, its account. Visibility in Passive mode is the eye in the widget's toolbar, not a menu row. wrapper.menu adds yours below them: toggles, sliders, choices, actions and groups, each with a label in both languages.

The menu is as wide as its widest row, from 248 to 320 points. Beyond, a label ends with "…" and shows whole in a tooltip; a choice shows its current value on at most 120 points. Keep labels short, in both languages.

JSONwidgets/clock/manifest.json
  "wrapper": {
    "menu": [
      {
        "type": "toggle",
        "id": "show-seconds",
        "label": { "en": "Show seconds", "fr": "Afficher les secondes" },
        "default": false
      },
      {
        "type": "toggle",
        "id": "show-date",
        "label": { "en": "Show date", "fr": "Afficher la date" },
        "default": true
      },
      {
        "type": "choice",
        "id": "date-format",
        "label": { "en": "Date format", "fr": "Format de date" },
        "visibleWhen": "show-date",
        "choices": [
          { "value": "day-month-year", "label": { "en": "dd/mm/yyyy", "fr": "jj/mm/aaaa" } },
          { "value": "year-month-day", "label": { "en": "yyyy-mm-dd", "fr": "aaaa-mm-jj" } },
          { "value": "month-day-year", "label": { "en": "mm/dd/yyyy", "fr": "mm/jj/aaaa" } }
        ],
        "default": "day-month-year"
      }
    ]
  }

PlayerVox OverCrow draws the rows, stores their values in the user's profile and gives them to the logic: read a value with option(id, fallback), and follow changes with onHost. An action row has no value: it calls the handler you registered with onMenu. See the options menu in the logic.

A menu row can set one of its declared values or send its action, nothing else: it cannot grant a permission or open a link, and choosing a row is not a user action that allows a protected service call.

Fields of every row

FieldTypeRequiredMeaning
typetoggle | slider | choice | action | groupyesRow type.
ididentifier ≤ 48 bytesyesUnique among all rows of the widget; key of the stored value.
labelMenuLabelyesLocalized row label.
iconLucide icon namenoLeading icon from the host list, drawn by the host.
visibleWhenidentifier ≤ 48 bytesnoID of a toggle row declared earlier; this row is shown only while that toggle is on.
requiresfps | telemetry.cpuTemperature | telemetry.gpuTemperaturenoHost data source; the host hides the row when it cannot supply it.

A row with visibleWhen shows only while the named toggle is on: the Clock's date format appears when the date does. A row with requires is hidden on a machine that cannot supply that data, as the Performance widget's temperature rows are:

JSONwidgets/performance/manifest.json
            "requires": "telemetry.cpuTemperature",

A group row opens a side flyout with its own rows; the Performance widget keeps its five metric toggles under one row:

JSONwidgets/performance/manifest.json
        "type": "group",
        "id": "shown-values",
        "label": { "en": "Shown values", "fr": "Valeurs affichées" },
        "rows": [

Row types

toggle

Boolean switch.

FieldTypeRequiredMeaning
defaultbooleanyesInitial value.

slider

Bounded number; step > 0, step ≤ max - min, at most 10000 steps, default within range.

FieldTypeRequiredMeaning
minnumber -1000000 to 1000000yesLower bound.
maxnumber -1000000 to 1000000yesUpper bound, greater than min.
stepnumber -1000000 to 1000000yesIncrement.
defaultnumber -1000000 to 1000000yesInitial value.

choice

One value among declared choices.

FieldTypeRequiredMeaning
choiceslist of MenuChoice ≤ 16yesAt least 2 choices.
defaultidentifier ≤ 48 bytesyesValue of one declared choice.

action

Calls the handler registered with onMenu, with the row ID. Not a user action.

group

Side flyout holding non-group rows.

FieldTypeRequiredMeaning
rowslist of MenuRow ≤ 16yesAt least one row; a group is not allowed here.

Labels and choices

A label is an object with both languages:

FieldTypeRequiredMeaning
enlabel textyesEnglish label.
frlabel textyesFrench label.

Each entry of choices is a value and its label:

FieldTypeRequiredMeaning
valueidentifier ≤ 48 bytesyesStored value; unique within the row.
labelMenuLabelyesLocalized choice label.

Required data sources

requires at the top level lists data sources the widget cannot work without, with the same names as a row's requires. On a machine that lacks one, PlayerVox OverCrow neither starts nor shows the widget and keeps its place in the layout; the widget comes back when the source does. Use it for a widget that has nothing to show otherwise, such as a frame-rate counter without a frame-rate source. For a single optional value, prefer a row's requires.

A larger memory budget

The logic runs with a heap of 16 MiB. A widget that needs more asks for it with vm; nothing else about the VM's budgets can be changed.

FieldTypeRequiredMeaning
heapMiBinteger 16 to 48yesQuickJS heap ceiling in MiB, from 16 MiB to 48 MiB. The VM process ceiling (64 MiB) does not change.

The memory ceiling of the widget's process stays the same, so a larger heap leaves less room for everything else. Ask for it only when overcrow-widget dev shows the widget stopping with resource_limit.

How the manifest is checked

overcrow-widget check validates the manifest with the validator PlayerVox OverCrow itself runs, and points at the member it refuses:

TEXT
error[manifest.sizing]: `sizing` is out of bounds or inconsistent
  --> manifest.json:7:3
   |
 7 |   "sizing": {
   |   ^
   = help: `min` <= `preferred` <= `max`, each within the schema's widget size bounds
check: 1 error(s), 0 warning(s)

PlayerVox OverCrow validates it again when the package is admitted to the catalog, when it is installed and each time the widget starts. A new version whose manifest asks for more than the previous one (a network rule, a game event, a capability, storage or the clipboard) waits until the user approves it in the Control Center before it replaces the installed version.