Widgets (wire format)
A widget is a flat JSON object. Its type field selects the schema; remaining fields vary per type. The TypeScript union in widgets.ts is the source of truth.
Per-widget reference pages list every field, every event, and a usage example. This page is the index.
Input widgets
Emit input.widget_event messages.
| Type | Category |
|---|---|
joystick | input |
button | input |
dpad | input |
text_input | input |
tap_area | input |
choice_list | input |
grid | input |
drawing_canvas | input |
Display widgets
Render-only. Never emit input.
| Type | Category |
|---|---|
label | display |
image | display |
timer | display |
progress_bar | display |
Why widgets are flat
Positions and sizes are flattened on the wire (position_x, position_y, size_w, size_h) so generated C# can stay as POD with nullable fields, no nested types. Any host that speaks JSON can emit them without a schema.
Data binding
String fields on display widgets may contain template placeholders like "{score}". The web controller resolves these against screen.data on mount and on every screen.update_data. Use {{ to embed a literal {.
Adding a new widget
- Add the interface to
widgets.tsand a new entry inWIDGET_TYPES. - Add it to the
Widgetunion. - Add a renderer in
packages/web-controller/src/widgets/. - Run
pnpm --filter @pairkit/shared-protocol generate:csharp. - Write the per-widget reference page under
apps/web/src/content/docs/docs/widgets/. - Add the new entry to
WIDGET_CATEGORIESinscripts/generate-docs.tsand re-run the docs generator.