# 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 ```json { "nodes": [ ...GraphNode ], "connections": [ ...Connection ] } ``` The entire graph is serialized as a single JSON blob — no normalization, no references across rows. --- ## `GraphNode` ```json { "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--` | | `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` ```json { "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` ```json { "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 ```json { "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 ```json { "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 ```json { "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`** ```json { "targetColor": "#ffffff" } ``` **`position`** ```json { "positionAxis": "x", "targetPositionValue": 2.5 } ``` **`rotation`** ```json { "rotationAxis": "y", "targetRotationValue": 90 } ``` `targetRotationValue` is in degrees. **`scale`** ```json { "targetScale": 2.0 } ``` **`visibility`** ```json { "targetVisibility": false } ``` --- ## Full example A graph with one trigger and one effect, connected: ```json { "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 Supabase `rooms` table. Not yet reflected in `supabase-types.ts`. - **Save:** triggered manually via the "Save Project" button; the whole object is JSON-stringified and sent as a `FormData` field to `PATCH /api/save-room/[id]`. - **Load:** the value is read from `roomData.graphical_coding` in `creator.tsx` and hydrated into the Zustand `SceneStore`. - **Local cache:** Zustand's `persist` middleware also writes the graph to `localStorage` under the key `"scene-storage"`, so edits survive a page refresh before an explicit save.