5.4 KiB
5.4 KiB
Graphical Coding — Database Save Format
This document describes the JSON structure stored in the Supabase rooms table under the graphical_coding column.
Top-level shape
{
"nodes": [ ...GraphNode ],
"connections": [ ...Connection ]
}
The entire graph is serialized as a single JSON blob — no normalization, no references across rows.
GraphNode
{
"id": "gc-1-1234567890",
"kind": "source | trigger | effect",
"label": "My Node",
"position": { "x": 100, "y": 200 },
"inputs": [ ...PortDef ],
"outputs": [ ...PortDef ],
"config": { ...kind-specific config }
}
| Field | Type | Description |
|---|---|---|
id |
string |
Unique node identifier, format gc-<index>-<timestamp> |
kind |
"source" | "trigger" | "effect" |
Node category |
label |
string |
User-visible name |
position |
{ x: number, y: number } |
Canvas position in pixels |
inputs |
PortDef[] |
Input ports |
outputs |
PortDef[] |
Output ports |
config |
object |
Kind-specific configuration (see below) |
PortDef
{ "id": "gc-1-1234567890", "name": "fired", "dataType": "boolean" }
| Field | Type | Description |
|---|---|---|
id |
string |
Port identifier (shares format with node IDs) |
name |
string |
Display name, e.g. "fired", "value", "trigger" |
dataType |
string |
"number", "string", "boolean", "object", or "any" |
Connection
{
"id": "conn-1234567890",
"fromNode": "gc-1-111",
"fromPort": "gc-1-111-o0",
"toNode": "gc-3-333",
"toPort": "gc-3-333-i0"
}
| Field | Type | Description |
|---|---|---|
id |
string |
Unique connection identifier |
fromNode |
string |
Source node ID |
fromPort |
string |
Source port ID (output port) |
toNode |
string |
Target node ID |
toPort |
string |
Target port ID (input port) |
Node config by kind
source — REST API data feed
{
"endpoint": "https://api.example.com/sensor",
"condition": ">",
"threshold": 42
}
| Field | Type | Description |
|---|---|---|
endpoint |
string |
REST API URL polled for data |
condition |
">" | "<" | "==" | "!=" | ">=" | "<=" |
Comparison operator applied to the API response value |
threshold |
number |
Value compared against the API response |
trigger — User interaction in the scene
{
"sourceItemId": 7,
"triggerEvent": "clicked",
"cooldown": 500
}
| Field | Type | Description |
|---|---|---|
sourceItemId |
number | null |
Scene item ID to watch (null = any item) |
triggerEvent |
"clicked" | "proximity_enter" | "proximity_exit" |
Interaction type |
cooldown |
number |
Minimum milliseconds between trigger firings |
effect — Scene modification
{
"inputMode": "trigger",
"targetObjectType": "item",
"targetItemId": 3,
"effectProperty": "color",
"targetColor": "#ff0000"
}
| Field | Type | Description |
|---|---|---|
inputMode |
"trigger" | "stream" |
Whether the effect fires on a trigger pulse or follows a continuous stream value |
targetObjectType |
"item" | "sky" |
What to modify |
targetItemId |
number | null |
Scene item ID (only relevant when targetObjectType is "item") |
effectProperty |
"color" | "position" | "rotation" | "scale" | "visibility" |
Which property to change |
Additional fields depend on effectProperty:
color
{ "targetColor": "#ffffff" }
position
{ "positionAxis": "x", "targetPositionValue": 2.5 }
rotation
{ "rotationAxis": "y", "targetRotationValue": 90 }
targetRotationValue is in degrees.
scale
{ "targetScale": 2.0 }
visibility
{ "targetVisibility": false }
Full example
A graph with one trigger and one effect, connected:
{
"nodes": [
{
"id": "gc-1",
"kind": "trigger",
"label": "On Click",
"position": { "x": 120, "y": 200 },
"inputs": [],
"outputs": [
{ "id": "gc-1-o0", "name": "fired", "dataType": "boolean" }
],
"config": {
"sourceItemId": null,
"triggerEvent": "clicked",
"cooldown": 500
}
},
{
"id": "gc-2",
"kind": "effect",
"label": "Skybox Color",
"position": { "x": 480, "y": 200 },
"inputs": [
{ "id": "gc-2-i0", "name": "trigger", "dataType": "boolean" }
],
"outputs": [],
"config": {
"inputMode": "trigger",
"targetObjectType": "sky",
"targetItemId": null,
"effectProperty": "color",
"targetColor": "#3a0f8c"
}
}
],
"connections": [
{
"id": "conn-9",
"fromNode": "gc-1",
"fromPort": "gc-1-o0",
"toNode": "gc-2",
"toPort": "gc-2-i0"
}
]
}
Persistence notes
- Column:
graphical_coding(JSONB) in the Supabaseroomstable. Not yet reflected insupabase-types.ts. - Save: triggered manually via the "Save Project" button; the whole object is JSON-stringified and sent as a
FormDatafield toPATCH /api/save-room/[id]. - Load: the value is read from
roomData.graphical_codingincreator.tsxand hydrated into the ZustandSceneStore. - Local cache: Zustand's
persistmiddleware also writes the graph tolocalStorageunder the key"scene-storage", so edits survive a page refresh before an explicit save.