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
{
"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:
{
"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
| Field | Type | Required | Meaning |
|---|---|---|---|
schemaVersion | integer 1 to 1 | yes | Manifest document version. |
apiVersion | integer 1 to 1 | yes | Widget API version; selects this schema. |
id | widget ID | yes | Widget 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. |
version | version | yes | SemVer 2.0.0 MAJOR.MINOR.PATCH[-PRERELEASE] in canonical form, at most 64 bytes; build metadata is rejected. |
name | WidgetName | yes | Localized widget name shown by the host. |
sizing | Sizing | yes | Size and fit rules applied by the widget's frame. |
vm | VmRequest | no | VM budget request; absent means the defaults. |
permissions | Permissions | no | Requested permissions; absent means none. |
wrapper | wrapper | no | Widget rows of the host options menu. |
requires | host features | no | Distinct 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
idis 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,yourhandleandyournameare example handles that nobody can publish under, and IDs undercom.playervoxare reserved for widgets published by PlayerVox.versionis a semantic version. A published version never changes: any change to a published widget is a new, higher version.schemaVersionandapiVersionare both1.
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.
| Field | Type | Required | Meaning |
|---|---|---|---|
en | name text | yes | English name. |
fr | name text | yes | French 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.
| Field | Type | Required | Meaning |
|---|---|---|---|
fit | none | both | height | yes | Content fitting the widget supports: both axes, height only, or none. Manual resizing is always available. |
defaultMode | fit | manual | no | Mode of a newly placed widget; default fit when fit is not none, else manual. fit with fit: none is rejected. |
preferred | Dimensions | yes | Initial size; within min and max. |
min | Dimensions | yes | Smallest size. |
max | Dimensions | yes | Largest size. |
Each of preferred, min and max is a pair of dimensions, with
min ≤ preferred ≤ max on both axes:
| Field | Type | Required | Meaning |
|---|---|---|---|
width | integer 1 to 4096 | yes | Logical pixels. |
height | integer 1 to 4096 | yes | Logical 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.
"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.
| Field | Type | Required | Meaning |
|---|---|---|---|
network | list of NetworkRule ≤ 32 | no | Distinct network rules. |
storage | boolean | no | Host key-value storage; default false. |
clipboardWrite | boolean | no | Clipboard text writes; default false. |
gameEvents | list of game event ≤ 32 | no | Distinct overcrow.game.<name>.v1 event names. |
capabilities | capability names | no | Distinct capabilities of the schema. |
"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.
"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
| Field | Type | Required | Meaning |
|---|---|---|---|
type | toggle | slider | choice | action | group | yes | Row type. |
id | identifier ≤ 48 bytes | yes | Unique among all rows of the widget; key of the stored value. |
label | MenuLabel | yes | Localized row label. |
icon | Lucide icon name | no | Leading icon from the host list, drawn by the host. |
visibleWhen | identifier ≤ 48 bytes | no | ID of a toggle row declared earlier; this row is shown only while that toggle is on. |
requires | fps | telemetry.cpuTemperature | telemetry.gpuTemperature | no | Host 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:
"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:
"type": "group",
"id": "shown-values",
"label": { "en": "Shown values", "fr": "Valeurs affichées" },
"rows": [Row types
toggle
Boolean switch.
| Field | Type | Required | Meaning |
|---|---|---|---|
default | boolean | yes | Initial value. |
slider
Bounded number; step > 0, step ≤ max - min, at most 10000 steps, default within range.
| Field | Type | Required | Meaning |
|---|---|---|---|
min | number -1000000 to 1000000 | yes | Lower bound. |
max | number -1000000 to 1000000 | yes | Upper bound, greater than min. |
step | number -1000000 to 1000000 | yes | Increment. |
default | number -1000000 to 1000000 | yes | Initial value. |
choice
One value among declared choices.
| Field | Type | Required | Meaning |
|---|---|---|---|
choices | list of MenuChoice ≤ 16 | yes | At least 2 choices. |
default | identifier ≤ 48 bytes | yes | Value 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.
| Field | Type | Required | Meaning |
|---|---|---|---|
rows | list of MenuRow ≤ 16 | yes | At least one row; a group is not allowed here. |
Labels and choices
A label is an object with both languages:
| Field | Type | Required | Meaning |
|---|---|---|---|
en | label text | yes | English label. |
fr | label text | yes | French label. |
Each entry of choices is a value and its label:
| Field | Type | Required | Meaning |
|---|---|---|---|
value | identifier ≤ 48 bytes | yes | Stored value; unique within the row. |
label | MenuLabel | yes | Localized 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.
| Field | Type | Required | Meaning |
|---|---|---|---|
heapMiB | integer 16 to 48 | yes | QuickJS 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:
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.