{
  "schema_version": 1,
  "type": "skill",
  "slug": "adom-panel-control",
  "title": "Panel Control",
  "brief": "The Adom web app displays a set of interactive panels such as the 3D layout editor and schematic designer. Panels provide live views, controls, and data for the workcell and its hardware. Manipulate t",
  "version": "1.0.0",
  "tags": [],
  "license": "MIT",
  "source_path": "SKILL.md",
  "readme": "---\nname: adom-panel-control\ndescription: Use when the user wants to open, close, add, or remove a panel; change the workspace layout; resize a split; show or hide a sensor view, camera, log, control panel, or any Adom editor panel; or asks \"what panels are available\". Examples: \"open the 3D viewer\", \"add a web view tab\", \"close the robot log\", \"resize the split to 70/30\", \"show me the motor control panel\", \"what panels can I open?\".\nuser-invokable: true\n---\n\n# Adom Panel Control\n\nThe Adom web app displays a set of interactive panels such as the 3D layout editor and schematic designer. Panels provide live views, controls, and data for the workcell and its hardware. Manipulate the Adom editor workspace: add/remove tabs, resize splits, and query the layout.\n\n## Environment\n\nAll API calls require:\n- **Base URL:** `https://hydrogen.adom.inc/api/workspaces/editor/{owner}/{repo}/current`\n- **JSON parsing:** `jq` is **not available** in the container. Use `python3 -m json.tool` to pretty-print JSON output instead.\n- **Auth:** `X-Api-Key` header. Obtain the key by reading `/var/run/adom/api-key` — this file is always present in the container, is read-only, and contains the token with no leading or trailing whitespace:\n  ```bash\n  API_KEY=$(cat /var/run/adom/api-key)\n  ```\n  Fall back to env vars (`ADOM_API_KEY`, `API_KEY`, `X-Api-Key`) only if that file is missing. Ask the user only as a last resort.\n\n## Critical Rules — Read Before Doing Anything\n\n- **NEVER use PUT to replace the full layout.** It wipes the entire workspace and breaks node IDs. Always use granular `POST /tabs`, `DELETE /tabs`, and `PATCH /splits` calls — even for complex multi-step changes. Build up the desired layout one operation at a time.\n- **NEVER echo or display the API key** in tool output. Store it in a shell variable — never `cat` it on its own or log it.\n- **Add before removing** when swapping tabs in a leaf — the API returns 400 if you try to remove the last tab, so always add the new tab first, then remove old ones.\n- **Remove tabs from highest index first** when removing multiple tabs from a leaf, to avoid index shifting.\n- **Re-fetch the layout before each batch of operations** — state can change between calls. Always confirm current node IDs and tab indices are still valid.\n\n## Workflow\n\n**Always follow this order when making changes:**\n\n0. **Setup** — resolve credentials, repo, and target user (ask once, remember for the session)\n1. **GET** the current layout to find leaf node IDs\n2. Identify the correct leaf `id` for where the user wants the tab placed\n3. Look up the `panelType` full ID from the catalog below\n4. **POST/DELETE/PATCH** to make the change\n5. Confirm success to the user\n\n### Step 0 — Setup: resolve credentials, repo, and target user\n\nAt the start of the session, if `OWNER`, `REPO` (THIS IS AN ADOM REPO NOT A GITHUB REPO), or `API_KEY` are not yet known, ask the user:\n\n> \"What is the Adom owner (username or org) and repository name for this project?\"\n\nRemember the answers for the rest of the conversation — do not ask again.\n\nThen read the API key and set up the base URL:\n\n```bash\nAPI_KEY=$(cat /var/run/adom/api-key)\nBASE=\"https://hydrogen.adom.inc/api/workspaces/editor/$OWNER/$REPO/current\"\n```\n\n**Discover the target user.** Workspaces are scoped per-user. You must discover who has an open editor:\n\n```bash\ncurl -s -H \"X-Api-Key: $API_KEY\" \"$BASE/users\" | python3 -m json.tool\n# → { \"users\": [{ \"username\": \"kcknox\" }] }\n```\n\n- **One user** (most common): auto-detected by the server — no extra header needed. Set `TARGET_HEADER=\"\"`.\n- **Multiple users**: ask the user which person's workspace to modify. Remember the choice and set:\n  ```bash\n  TARGET_HEADER='-H \"X-Target-Username: <chosen-username>\"'\n  ```\n- **Zero users**: the editor is not open in any browser. Tell the user to open the editor first.\n\nSave `OWNER`, `REPO`, `API_KEY`, `BASE`, and `TARGET_HEADER` (if needed) in your memory file so you don't repeat this setup.\n\n### Step 1 — GET current layout\n\n```bash\ncurl -s -H \"X-Api-Key: $API_KEY\" $TARGET_HEADER \"$BASE\" | python3 -m json.tool\n```\n\nParse the response to find:\n- All `leaf` nodes and their `id` values (needed for adding/removing tabs)\n- All `split` nodes and their `id` values (needed for resizing)\n- The `tabs` array on each leaf and `activeTabIndex`\n\n### Step 2 — POST: Add a tab\n\n```bash\ncurl -s -X POST \\\n  -H \"X-Api-Key: $API_KEY\" $TARGET_HEADER \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"panelId\":\"<leaf-id>\",\"panelType\":\"<full-panel-id>\"}' \\\n  \"$BASE/tabs\"\n```\n\nReturns `{ \"tabId\": \"<uuid>\" }` on success.\n\n> If the user doesn't specify which panel (leaf) to add to and there is more than one leaf, ask them where they want it placed, or place it in the largest/most relevant leaf.\n\n### Step 3 — DELETE: Remove a tab\n\n```bash\ncurl -s -X DELETE \\\n  -H \"X-Api-Key: $API_KEY\" $TARGET_HEADER \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"panelId\":\"<leaf-id>\",\"tabIndex\":<number>}' \\\n  \"$BASE/tabs\"\n```\n\n`tabIndex` is 0-based. GET the layout first to confirm the correct index.\n\n**Finding a tab by name:** When the user says \"close the Robot Log\", GET the layout, walk every `leaf` node, find the tab whose `panelType` matches the catalog ID (e.g. `adom/a1b2c3d4-bbbb-4000-a000-00000000000b`), then DELETE using that leaf's `id` and the tab's array index.\n\n> Cannot remove the last tab in a leaf — the API returns 400. Inform the user if this happens.\n\n### Step 4 — PATCH: Resize a split\n\n```bash\ncurl -s -X PATCH \\\n  -H \"X-Api-Key: $API_KEY\" $TARGET_HEADER \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"ratio\":0.3}' \\\n  \"$BASE/splits/<split-id>\"\n```\n\n`ratio` is the fraction given to the **first** child (0.1–0.9). So \"70/30\" → `ratio: 0.7`.\n\n<!-- NOTE: DELETE /api/workspaces/editor/{owner}/{repo}/current (clear workspace on editor unmount)\n     is intentionally omitted — an AI agent should never clear the entire workspace. -->\n\n### Step 5 — PUT: PROHIBITED\n\n> **DO NOT use PUT.** It wipes the entire workspace. There is no valid use case for an AI agent. Use `POST /tabs`, `DELETE /tabs`, and `PATCH /splits` instead.\n\n### Step 6 — GET /events: SSE event stream (optional, for real-time sync)\n\n```bash\ncurl -s -H \"X-Api-Key: $API_KEY\" \"$BASE/events\"\n```\n\nEmits `{ \"type\": \"connected\" }` once, then `{ \"type\": \"workspace_updated\" }` on every mutation. The container is typically the mutator, not the listener — this is mainly useful if you need to wait for another actor's change before proceeding.\n\n## Layout Structure Reference\n\nThe workspace is a binary tree:\n\n- **SplitNode** — `{ type: \"split\", id, direction: \"horizontal\"|\"vertical\", ratio: 0.1–0.9, first: PanelNode, second: PanelNode }`\n- **LeafNode** — `{ type: \"leaf\", id, tabs: PanelTab[], activeTabIndex: number }`\n- **PanelTab** — `{ id, panelType: \"<full-panel-id>\", panelState?: { ... } }`\n\n## Singleton Panels\n\nThese may only appear **once** in the entire workspace. Check the current layout before adding — if already present, tell the user rather than adding a duplicate.\n\n| Full ID | Name |\n|---------|------|\n| `adom/a1b2c3d4-1111-4000-a000-000000000001` | 3D Viewer |\n| `adom/a1b2c3d4-0012-4000-a000-000000000012` | Schematic Editor |\n\n## Panel Catalog\n\n### Development\n\n| Full ID | Name | Description |\n|---------|------|-------------|\n| `adom/a1b2c3d4-eeee-4000-a000-00000000000e` | Visual Studio Code | Embedded VS Code editor |\n| `adom/a1b2c3d4-0012-4000-a000-000000000012` | Schematic Editor | Circuit schematic editor *(singleton)* |\n| `adom/a1b2c3d4-0014-4000-a000-000000000014` | Script Runner | Execute automation scripts |\n\n### Control\n\n| Full ID | Name | Description |\n|---------|------|-------------|\n| `adom/a1b2c3d4-3333-4000-a000-000000000003` | Motor Control | XRP robot motor speed/direction |\n| `adom/a1b2c3d4-4444-4000-a000-000000000004` | LED Control | XRP robot LED color/brightness |\n| `adom/a1b2c3d4-5555-4000-a000-000000000005` | Servo Control | XRP robot servo angle |\n| `adom/a1b2c3d4-001b-4000-a000-00000000001b` | Drone Control | Drone flight control and telemetry |\n| `adom/a1b2c3d4-001c-4000-a000-00000000001c` | ESC Control | Electronic Speed Controller |\n| `adom/a1b2c3d4-0010-4000-a000-000000000010` | Lights Control | RGB/white LED strip with color wheel |\n| `adom/a1b2c3d4-0022-4000-a000-000000000022` | Workcell Power | Power supply voltage/current limits |\n| `adom/a1b2c3d4-0023-4000-a000-000000000023` | Basic Control Panel | GPIO pin modes and digital write |\n\n### Visualization\n\n| Full ID | Name | Description |\n|---------|------|-------------|\n| `adom/a1b2c3d4-1111-4000-a000-000000000001` | 3D Viewer | Workcell/robot 3D view *(singleton)* |\n| `adom/a1b2c3d4-001a-4000-a000-00000000001a` | Babylon OTB | Babylon.js 3D viewer with OTB support |\n| `adom/a1b2c3d4-6666-4000-a000-000000000006` | IMU Sensor | Accelerometer, gyroscope, orientation |\n| `adom/a1b2c3d4-8888-4000-a000-000000000008` | Time of Flight Sensor | ToF distance readings |\n| `adom/a1b2c3d4-9999-4000-a000-000000000009` | Ultrasonic Sensor | Ultrasonic distance / obstacle detection |\n| `adom/a1b2c3d4-aaaa-4000-a000-00000000000a` | Line Follower | Line following sensor array |\n| `adom/a1b2c3d4-0015-4000-a000-000000000015` | Temperature Graph | Real-time temperature graphing |\n| `adom/a1b2c3d4-0016-4000-a000-000000000016` | Chip Data | Microchip data monitoring |\n| `adom/a1b2c3d4-0017-4000-a000-000000000017` | Chip Statistics | Microchip performance metrics |\n| `adom/a1b2c3d4-0018-4000-a000-000000000018` | Oven Data | Oven temperature/control monitoring |\n| `adom/a1b2c3d4-0019-4000-a000-000000000019` | ToF Sensor | Time-of-Flight data visualization |\n| `adom/a1b2c3d4-001d-4000-a000-00000000001d` | Bosch Sensors | Bosch sensor suite monitoring |\n| `adom/a1b2c3d4-001e-4000-a000-00000000001e` | BMP Sensor | Bosch BMP pressure/temperature |\n| `adom/a1b2c3d4-0020-4000-a000-000000000020` | BMV Sensor | Bosch BMV sensor visualization |\n| `adom/a1b2c3d4-0021-4000-a000-000000000021` | UWB Sensor | Ultra-Wideband positioning |\n\n### Media\n\n| Full ID | Name | Description |\n|---------|------|-------------|\n| `adom/a1b2c3d4-ffff-4000-a000-00000000000f` | Live Stream | Live camera: static/free-moving or XY zoom |\n| `adom/a1b2c3d4-ffff-4000-a000-000000000100` | WebRTC Player | Single WebRTC video stream |\n| `adom/a1b2c3d4-2660-4a00-a000-000000000266` | XY Zoom Camera | Dual WebRTC streams with minimap |\n\n### Utility\n\n| Full ID | Name | Description |\n|---------|------|-------------|\n| `adom/a1b2c3d4-0011-4000-a000-000000000011` | System Log | Real-time system log with filtering |\n| `adom/a1b2c3d4-bbbb-4000-a000-00000000000b` | Robot Log | XRP robot console output |\n| `adom/a1b2c3d4-7777-4000-a000-000000000007` | Battery Charger | Battery charging status and power |\n| `adom/a1b2c3d4-cccc-4000-a000-00000000000c` | Curriculum | XRP robot educational content |\n| `adom/a1b2c3d4-0031-4000-a000-000000000031` | Web View | Embedded browser — load any URL |\n\n## Panel Details\n\n### XY Zoom Camera\n\nAvailable on all workcells (included with the base $500/mo subscription).\n\nShows **two simultaneous live video feeds**:\n\n1. **Wide view** — A 120° FOV camera providing a constant birds-eye view of the entire workcell. This feed always shows the full workcell regardless of where the camera gantry is positioned. Use it for spatial awareness and general monitoring.\n2. **Zoom view** — A zoomable camera that can magnify down to a **10 mm x 10 mm** area filling the entire feed. Use it for close-up inspection of components, solder joints, traces, wire connections, and fine details.\n\n**Controls:** X/Y click-to-move or drag, zoom slider/scroll, preset position save/recall.\n\n**Upgrade — xyPanTiltZoom** (+$100/mo): Adds pan (horizontal rotation), tilt (90° straight-down to angled perspectives), and **3D viewer linking** — locks the physical camera to the user's 3D layout editor viewpoint so the real camera moves as they orbit/pan/zoom.\n\n### 3D Viewer\n\nInteractive 3D view of the workcell. Shows all molecules, scaffolds, wires, and the base scaffold in physical positions. Users can orbit, pan, and zoom. When xyPanTiltZoom 3D linking is enabled, navigating this viewer controls the physical camera.\n\n### Schematic Editor\n\nDisplays the project's scaffold schematic pages. Users can browse pages, inspect component pins, trace nets, and see how molecules are electrically connected.\n\n## User Plugins (Custom Panels)\n\nUsers can create custom panels via plugins in `plugin-content/`. Each plugin subfolder contains code and an HTML interface that appears as a panel in the web app. The front-end panel communicates with a mini web server on the Docker container, which can interact with hardware via USB, Ethernet, GPIO, or the Adom API.\n\n**Examples:** motor control, sensor dashboard, firmware manager, test runner.\n\n## Relationship to Docker API\n\nPanels and the Docker API are bidirectional:\n\n- **Panels → API**: Panel actions (e.g. camera moves) update the Docker container via WebSocket.\n- **API → Panels**: REST commands (e.g. scripted camera moves) update panels in real time.\n- **Messaging**: Scripts can send notifications via `POST /message/send`.\n- **Viewer count**: `GET /viewers/count` returns how many users are viewing the project.\n\nSee the `adom-api` guide for the full API reference.\n\n## Troubleshooting\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `409 Conflict` | Editor is not open in the browser, or multiple users and no `X-Target-Username` | Check `GET .../current/users` first. If zero users, ask user to open the editor. If multiple, specify the target username. |\n| `404` on panelId | The leaf ID is stale or wrong | Re-GET the layout and use fresh IDs |\n| `400` on tab remove | Tried to remove the last tab in a leaf | Tell the user — a leaf must always have at least one tab |\n| `400` on add tab | Missing `panelId` or `panelType` | Verify both fields are present and non-empty |\n| `401 Unauthorized` | Missing or invalid API key | Read `/var/run/adom/api-key`; if missing, check env vars or ask the user |\n| Singleton already present | Adding a panel that only allows one instance | Inform the user it's already open; offer to navigate to it instead |\n",
  "author": {
    "id": "695820315b5f1e4db2fcf602",
    "name": "Kyle Bergstedt",
    "email": "kyle@adom.inc"
  },
  "visibility": {
    "public": true
  },
  "hero": null,
  "sample_prompts": [],
  "discovery_triggers": [],
  "discovery_pitch": null,
  "metadata": {},
  "created_at": "2026-05-28T05:30:27.483Z",
  "updated_at": "2026-05-28T05:30:27.483Z",
  "sub_skills": [],
  "parent_app": null
}