Adom Workspace Control
Architecture: Hydrogen vs VS Code vs Docker
Browser
└── Hydrogen (workspace shell — panels, tabs, splits)
├── 3D Viewer panel
├── Web View panels (shotlog, AV, etc.)
├── Sensor panels, camera feeds, etc.
└── VS Code panel (iframe → code-server running on Docker)
└── code-server (its own world, own API, own extensions)
Hydrogen is the outer workspace shell. The adom-cli hydrogen workspace API and the REST endpoints documented below control Hydrogen — adding/removing tabs, splitting panes, resizing, etc. These operate on the panel layout, not on what's inside any panel.
VS Code (code-server) runs inside the Docker container and is embedded as an iframe in one of Hydrogen's panels. Hydrogen cannot control what happens inside VS Code — it can only add/remove the VS Code panel itself. To control VS Code (open files, reveal in explorer, run commands), you must use code-server's own mechanisms:
Code-server remote CLI (safe for opening files, NOT folders):
/usr/lib/code-server/lib/vscode/bin/remote-cli/code-linux.sh --reuse-window /path/to/file.pngThis opens a file in a new editor tab without changing the workspace root. NEVER pass a folder path — that changes the workspace root and breaks the user's session.
Code-server IPC socket (
$VSCODE_IPC_HOOK_CLI): Low-level Unix socket the CLI uses under the hood. POST{ type: "open", fileURIs: [...] }.VS Code extensions: A code-server extension can expose an HTTP API on a local port, giving Docker processes full access to the VS Code API (
vscode.window.showTextDocument(),vscode.commands.executeCommand('revealInExplorer'), etc.).
Key rule: Hydrogen API = panel layout. Code-server CLI/extensions = VS Code internals. Don't mix them up.
Manipulate the Adom editor workspace: add/remove tabs, split/close panes, move/swap tabs between panes, resize splits, take screenshots, and query the layout.
USE adom-cli — NOT curl
adom-cli auto-detects auth, owner, and repo. Use it instead of raw curl for all workspace operations. The curl examples further below are reference only — prefer the CLI commands.
# These just work — no setup, no API key, no owner/repo needed:
adom-cli hydrogen workspace tabs # list all tabs (flat)
adom-cli hydrogen workspace find-tab "Schematic Editor" # find by name
adom-cli hydrogen screenshot workspace # screenshot (auto-saved)
adom-cli hydrogen screenshot panel --name "Schematic Editor" # screenshot by name
adom-cli hydrogen workspace add-tab --panel-id <id> --panel-type "adom/..." --display-name "My Tab" --alert
adom-cli hydrogen workspace remove-tab --name "My Tab" # remove by name
adom-cli hydrogen workspace active-tab --name "Schematic Editor" # activate by name
adom-cli hydrogen workspace close-panel "My Tab" # close by name
Name-based addressing: Most commands accept --name to target tabs by display name instead of UUIDs. workspace tabs returns a flat list with names — no tree parsing needed.
Sharing & Permissions
adom-cli hydrogen screenshot status # check what's available
adom-cli hydrogen sharing request --share tab # request tab sharing
adom-cli hydrogen sharing request --share screen --audio --reason "Recording demo" # screen + mic
Screenshots — Quick Reference
Files saved automatically to ~/project/screenshots/. Prints the file path on success.
adom-cli hydrogen screenshot workspace # all panels
adom-cli hydrogen screenshot panel --panel-id <leaf-id> # active tab
adom-cli hydrogen screenshot panel --tab-id <tab-id> # specific tab
adom-cli hydrogen screenshot screen # entire display
adom-cli hydrogen screenshot panel --panel-id <id> --method html2canvas # sandbox fallback
adom-cli hydrogen screenshot status # check sharing
Unified flow: Just call screenshot with an optional --reason. If sharing isn't active, the approval dialog opens automatically and the call blocks until the user approves (up to 90s).
adom-cli hydrogen screenshot workspace --reason "Need to see the layout"
adom-cli hydrogen screenshot panel --name "Schematic Editor" --reason "Debugging schematic"
No more two-call flow — one call handles prompt + wait + capture. html2canvas works without sharing for sandbox panels only.
Screen Recording — capture video with audio
Files saved automatically to ~/project/recordings/. Prints the file path on success.
adom-cli hydrogen recording start # start immediately
adom-cli hydrogen recording start --countdown 3 # 3-2-1 countdown
adom-cli hydrogen recording start --countdown 5 --reason "Demo walkthrough" # with reason
adom-cli hydrogen recording stop # stop (file saved automatically)
If sources aren't enabled, the user is auto-prompted. Use sharing request first for a smoother experience. Output is WebM (VP9 + Opus).
Audio in recordings: Use adom-cli hydrogen audio enable before starting to ensure mic is active. Check the start response — recording (video: true, audio: true) confirms mic is captured.
CRITICAL: Create the output directory BEFORE stopping the recording.
mkdir -p ~/project/recordings
adom-cli hydrogen recording stop -o ~/project/recordings/demo.webm
The stop command discards the recording on any save error — there is no retry. Always mkdir -p before stop.
Sharing scope matters for recordings. Use adom-cli hydrogen screenshot status to check, or sharing request --share screen if you need desktop apps visible. Tab sharing only captures the Hydrogen editor.
Captions shown with adom-cli hydrogen caption show overlay on the workspace and are captured in both tab and screen modes.
Audio — microphone control and recording
Audio files saved automatically to ~/project/audio/.
adom-cli hydrogen audio enable # enable mic (silent if cached)
adom-cli hydrogen audio enable --reason "Need mic for voiceover"
adom-cli hydrogen audio disable # disable mic
adom-cli hydrogen audio status # check mic state
adom-cli hydrogen audio start # start recording
adom-cli hydrogen audio stop # stop (file saved automatically)
adom-cli hydrogen audio level # get rms/peak/clipping/gain
adom-cli hydrogen audio gain 1.5 # set gain (0.0-4.0, persisted)
Mic level/gain: Use audio level to verify input is healthy before recording. If clipping: true reduce gain; if peak < 0.05 increase it. Gain is persisted across sessions.
Captions — overlay text on the workspace
# Default (center, medium, 3 seconds)
adom-cli hydrogen caption show "Hello World"
# Large at top for 5 seconds
adom-cli hydrogen caption show "Step 1: Calibrating" -d 5 -p top -s large
# Small at bottom, indefinite (stays until hidden)
adom-cli hydrogen caption show "Waiting for input..." -d 0 -p bottom -s small
# Hide the current caption
adom-cli hydrogen caption hide
| Option | Flag | Default | Values |
|---|---|---|---|
| Duration (seconds) | -d | 3 | Any number, 0 = indefinite |
| Position | -p | center | top, center, bottom |
| Size | -s | medium | small (32px), medium (56px), large (96px) |
Global desktop captions (stays visible over Fusion / KiCad / any native app)
adom-cli hydrogen caption is a browser DOM overlay: it only shows on top of the Hydrogen workspace. The moment a native desktop app (Fusion, KiCad, Chrome) comes to the foreground, the hydrogen caption is hidden behind it.
For captions that MUST stay visible over everything, including native apps and screen recordings, use adom-desktop desktop_caption (1.3.20+). It is a Win32 always-on-top click-through layered window. Requires the Adom Desktop app to be running and connected to the relay.
# Show a caption (persistent until replaced or hidden)
adom-desktop desktop_caption '{"text":"Step 1: Opening the board","position":"top","size":"large","duration":0}'
# Replace instantly with another caption
adom-desktop desktop_caption '{"text":"Now exporting gerbers","position":"top","size":"medium","duration":0}'
# Hide
adom-desktop desktop_caption '{"action":"hide"}'
Positions: top, center, bottom. Sizes: large (72px), medium (32px), small (20px). duration is in MILLISECONDS, 0 means persistent. Only one caption visible at a time; each call replaces the previous. Click-through so it never blocks mouse input.
Use desktop_caption for demo recordings that bring native apps forward, and adom-cli hydrogen caption for caption overlays scoped to the browser workspace.
Tab alerts — blink a tab to get attention
adom-cli hydrogen alert set --name "Schematic Editor" # blink indefinitely (default)
adom-cli hydrogen alert set --name "Schematic Editor" -d 5 # auto-clear after 5s
adom-cli hydrogen alert clear --name "Schematic Editor" # clear specific
adom-cli hydrogen alert clear # clear all
Navigation bar — minimize/expand/toggle
adom-cli hydrogen nav minimize # collapse the nav bar
adom-cli hydrogen nav expand # expand the nav bar
adom-cli hydrogen nav toggle # toggle between states
Panel-specific APIs (e.g. navigating a Web View to a URL) are documented in separate skills under .claude/skills/adom-panels/<panel-name>/SKILL.md. See .claude/skills/adom-panels/SKILL.md for the full catalog and links.
Adom Viewer: To open the AV panel programmatically (split pane, navigate Web View to AV URL), see adom-viewer.md — it has step-by-step instructions under "Rule: Always Open AV Panel If Not Connected".
Environment (for raw curl — prefer adom-cli above)
All API calls require:
- Base URL:
https://hydrogen.adom.inc/api/workspaces/editor/{owner}/{repo}/current - JSON parsing:
jqis not available in the container. Usepython3 -m json.toolto pretty-print JSON output instead. - Auth:
X-Api-Keyheader. 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:
Fall back to env vars (API_KEY=$(cat /var/run/adom/api-key)ADOM_API_KEY,API_KEY,X-Api-Key) only if that file is missing. Ask the user only as a last resort.
Critical Rules — Read Before Doing Anything
NEVER use PUT to replace the full layout. It wipes the entire workspace and breaks node IDs. Always use granular endpoints (
POST /tabs,DELETE /tabs,PATCH /splits,POST /splits,POST /moves,POST /swaps,DELETE /panels) — even for complex multi-step changes. Build up the desired layout one operation at a time.NEVER echo or display the API key in tool output. Store it in a shell variable — never
catit on its own or log it.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.
Remove tabs from highest index first when removing multiple tabs from a leaf, to avoid index shifting.
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.
Be cautious when closing panes or removing VS Code tabs — one VS Code panel instance is hosting this chat session. Closing it terminates the conversation. Since there is no reliable way to identify which specific VS Code instance is running the session, avoid closing panes that contain a VS Code tab or removing VS Code tabs unless the user explicitly requested it.
NEVER place a new tab on the same pane that hosts VS Code. Claude Code runs INSIDE VS Code, which is the user's primary chat surface. When you
POST /tabswithout specifying apanelId, or when you calladom-cli hydrogen workspace add-tabwithout--panel-id, the server defaults to the focused pane — and the focused pane is almost always the one the user is typing in, which is VS Code. Your shiny new webview tab then parks itself on top of the chat. The user has to drag it off every single time. This has shipped buggy for everyone and it is a hard rule: query the layout, find the pane whose tabs include a VS Code panelType, and target a DIFFERENT pane. If the workspace only has one pane, split it first (or ask the user). Specifically:# VS Code's panelType is the canonical 'eeee' UUID below. VSCODE_PT="adom/a1b2c3d4-eeee-4000-a000-00000000000e" # Walk every leaf in the layout. Return the first leaf whose tabs # do NOT include VS Code. Fall back to the currently-focused pane # only if every pane has VS Code (single-pane layout). When there # are multiple non-VS-Code panes, prefer the one whose activeTab # matches something webview-like, or just pick the first. adom-cli hydrogen workspace get | python3 -c ' import json, sys VSCODE_PT = "adom/a1b2c3d4-eeee-4000-a000-00000000000e" layout = json.load(sys.stdin) def leaves(node): if "tabs" in node: yield node else: if "first" in node: yield from leaves(node["first"]) if "second" in node: yield from leaves(node["second"]) focused = layout.get("focusedPanelId") non_vscode = [l for l in leaves(layout["root"]) if not any(t.get("panelType") == VSCODE_PT for t in l["tabs"])] if non_vscode: # Prefer the focused pane IF it is itself non-VS-Code, otherwise # take the first non-VS-Code pane in tree order. picked = next((l for l in non_vscode if l["id"] == focused), non_vscode[0]) print(picked["id"]) else: print("") # single-pane layout, caller must split or ask 'Every CLI and every webapp that opens a Hydrogen tab MUST do this check. The
video-postcrate implements it insrc/webapp/tab.rs; copy that pattern. Do not trustfocusedPanelIdfor target selection. Do not useadom-cli hydrogen workspace active-tabto "fix" the wrong placement after the fact — by that point the user has already seen their chat get covered.
Workflow
Always follow this order when making changes:
- Setup — resolve credentials, repo, and target user (ask once, remember for the session)
- GET the current layout to find leaf node IDs
- Identify the correct leaf
idfor where the user wants the tab placed - Look up the
panelTypefull ID from the catalog below - POST/DELETE/PATCH to make the change
- Confirm success to the user
Step 0 — Setup: resolve credentials, repo, and target user
At the start of the session, read the API key and auto-discover the owner and repo via the Carbon API:
API_KEY=$(cat /var/run/adom/api-key)
# Auto-discover owner and repo from the Carbon container registry
SLUG=$(echo "$VSCODE_PROXY_URI" | sed 's|.*-\([^.]*\)\.adom\.cloud.*|\1|')
CONTAINER_INFO=$(curl -s -H "X-Api-Key: $API_KEY" "https://carbon.adom.inc/containers/$SLUG")
OWNER=$(echo "$CONTAINER_INFO" | python3 -c "import sys,json; print(json.load(sys.stdin)['repository']['owner']['name'])")
REPO=$(echo "$CONTAINER_INFO" | python3 -c "import sys,json; print(json.load(sys.stdin)['repository']['name'])")
BASE="https://hydrogen.adom.inc/api/workspaces/editor/$OWNER/$REPO/current"
The slug is extracted from $VSCODE_PROXY_URI (everything after the last -, before .adom.cloud). The Carbon API returns the full container info including repository.owner.name and repository.name. Do not ask the user for owner/repo — always use this endpoint to resolve them automatically.
Legacy containers: If the Carbon API returns {"error":"CONTAINER_NOT_FOUND"}, this is likely a legacy container that predates the Carbon registry. In that case, fall back to asking the user:
"What is the Adom owner (username or org) and Adom repository name for this project?"
As a last resort, you can try to parse $VSCODE_PROXY_URI (format {owner}-{repo}-{slug}.adom.cloud), but this is unreliable if the repo name contains hyphens.
Remember the answers for the rest of the conversation — do not ask again.
Discover the target user. Workspaces are scoped per-user. You must discover who has an open editor:
curl -s -H "X-Api-Key: $API_KEY" "$BASE/users" | python3 -m json.tool
# → { "users": [{ "username": "kcknox" }] }
- One user (most common): auto-detected by the server — no extra header needed. Set
TARGET_HEADER="". - Multiple users: ask the user which person's workspace to modify. Remember the choice and set:
TARGET_HEADER='-H "X-Target-Username: <chosen-username>"' - Zero users: the editor is not open in any browser. Tell the user to open the editor first.
Save OWNER, REPO, API_KEY, BASE, and TARGET_HEADER (if needed) in your memory file so you don't repeat this setup.
Step 1 — GET current layout
curl -s -H "X-Api-Key: $API_KEY" $TARGET_HEADER "$BASE" | python3 -m json.tool
Parse the response to find:
- All
leafnodes and theiridvalues (needed for adding/removing tabs) - All
splitnodes and theiridvalues (needed for resizing) - The
tabsarray on each leaf andactiveTabIndex
Step 2 — POST: Add a tab
curl -s -X POST \
-H "X-Api-Key: $API_KEY" $TARGET_HEADER \
-H "Content-Type: application/json" \
-d '{"panelId":"<leaf-id>","panelType":"<full-panel-id>"}' \
"$BASE/tabs"
Returns { "tabId": "<uuid>" } on success.
Custom tab name and icon: You can optionally set displayName and displayIcon to override the tab header. MDI icons are monochrome and inherit the theme color. For colored icons, use a custom SVG via data: URL.
MDI icon example:
curl -s -X POST \
-H "X-Api-Key: $API_KEY" $TARGET_HEADER \
-H "Content-Type: application/json" \
-d '{"panelId":"<leaf-id>","panelType":"adom/a1b2c3d4-0031-4000-a000-000000000031","displayName":"eBay","displayIcon":"mdi:cart"}' \
"$BASE/tabs"
Custom SVG icon example (colored):
curl -s -X POST \
-H "X-Api-Key: $API_KEY" $TARGET_HEADER \
-H "Content-Type: application/json" \
-d '{"panelId":"<leaf-id>","panelType":"adom/a1b2c3d4-0031-4000-a000-000000000031","displayName":"RMFG","displayIcon":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIgZmlsbD0iIzRDQUY1MCIvPjwvc3ZnPg=="}' \
"$BASE/tabs"
displayName— custom label shown on the tab (instead of the panel's default name)displayIcon— MDI icon string (e.g.mdi:github, monochrome) ordata:URL for custom colored SVG/image (e.g.data:image/svg+xml;base64,...)- Both are optional and stored in
panelState. Omit them to use the panel's default name/icon.
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.
Step 3 — DELETE: Remove a tab
curl -s -X DELETE \
-H "X-Api-Key: $API_KEY" $TARGET_HEADER \
-H "Content-Type: application/json" \
-d '{"panelId":"<leaf-id>","tabIndex":<number>}' \
"$BASE/tabs"
tabIndex is 0-based. GET the layout first to confirm the correct index.
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.
Cannot remove the last tab in a leaf — the API returns 400. Inform the user if this happens.
Step 4 — PATCH: Resize a split
curl -s -X PATCH \
-H "X-Api-Key: $API_KEY" $TARGET_HEADER \
-H "Content-Type: application/json" \
-d '{"ratio":0.3}' \
"$BASE/splits/<split-id>"
ratio is the fraction given to the first child (0.1–0.9). So "70/30" → ratio: 0.7.
Step 5 — POST: Split a pane
Split an existing pane into two, creating a new split node.
curl -s -X POST \
-H "X-Api-Key: $API_KEY" $TARGET_HEADER \
-H "Content-Type: application/json" \
-d '{"panelId":"<leaf-id>","direction":"horizontal","panelType":"<full-panel-id>"}' \
"$BASE/splits"
Returns { "panelId": "<new-leaf-id>", "tabId": "<new-tab-id>" }.
| Field | Required | Description |
|---|---|---|
panelId | yes | Leaf node to split |
direction | yes | "horizontal" or "vertical" |
panelType | yes | Panel type for the new pane's tab |
position | no | "before" or "after" (default "after") |
ratio | no | Split ratio 0.1–0.9 (default 0.5) |
displayName | no | Custom tab header label |
displayIcon | no | MDI icon string (e.g. mdi:github, monochrome) or custom SVG/image as data: URL — use data: for colored icons |
Step 6 — POST: Move a tab between panes
Move a tab from one pane to another without creating a split.
curl -s -X POST \
-H "X-Api-Key: $API_KEY" $TARGET_HEADER \
-H "Content-Type: application/json" \
-d '{"sourcePanelId":"<leaf-id>","tabId":"<tab-uuid>","targetPanelId":"<other-leaf-id>"}' \
"$BASE/moves"
Returns 204 No Content. If the moved tab was the last one in the source pane, the source pane is automatically removed.
| Field | Required | Description |
|---|---|---|
sourcePanelId | yes | Pane containing the tab |
tabId | yes | UUID of the tab to move |
targetPanelId | yes | Pane to move the tab into |
targetIndex | no | Insert position (appends if omitted) |
Step 7 — POST: Swap content between panes
Swap all tabs between two panes.
curl -s -X POST \
-H "X-Api-Key: $API_KEY" $TARGET_HEADER \
-H "Content-Type: application/json" \
-d '{"sourcePanelId":"<leaf-id-1>","targetPanelId":"<leaf-id-2>"}' \
"$BASE/swaps"
Returns 204 No Content.
Step 8 — POST: Bring a tab to the foreground
Switch the visible tab within a pane. Use this when a pane has multiple tabs and you want to show one that is currently in the background.
# By tab index (0-based)
curl -s -X POST \
-H "X-Api-Key: $API_KEY" $TARGET_HEADER \
-H "Content-Type: application/json" \
-d '{"panelId":"<leaf-id>","tabIndex":2}' \
"$BASE/active-tab"
# By tab UUID (preferred — stable if tabs are reordered)
curl -s -X POST \
-H "X-Api-Key: $API_KEY" $TARGET_HEADER \
-H "Content-Type: application/json" \
-d '{"panelId":"<leaf-id>","tabId":"<tab-uuid>"}' \
"$BASE/active-tab"
Returns 204 No Content.
| Field | Required | Description |
|---|---|---|
panelId | yes | Leaf node containing the tabs |
tabIndex | one of | 0-based position of the tab to show |
tabId | one of | UUID of the tab to show |
When the user says "show the IMU sensor" or "switch to the Robot Log tab", GET the layout, find the leaf and tab by
panelType, then callPOST /active-tabwith that leaf'sidand the tab'sid.
Step 9 — DELETE: Close a pane
Remove a pane entirely. Its sibling is promoted to replace the parent split.
curl -s -X DELETE \
-H "X-Api-Key: $API_KEY" $TARGET_HEADER \
"$BASE/panels/<leaf-id>"
Returns 204 No Content. Cannot close the last pane — the API returns 400.
Step 10 — PUT: PROHIBITED
DO NOT use PUT. It wipes the entire workspace. There is no valid use case for an AI agent. Use the granular endpoints above instead.
Step 11 — GET /events: SSE event stream (optional, for real-time sync)
curl -s -H "X-Api-Key: $API_KEY" "$BASE/events"
Emits { "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.
Layout Structure Reference
The workspace is a binary tree:
- SplitNode —
{ type: "split", id, direction: "horizontal"|"vertical", ratio: 0.1–0.9, first: PanelNode, second: PanelNode } - LeafNode —
{ type: "leaf", id, tabs: PanelTab[], activeTabIndex: number } - PanelTab —
{ id, panelType: "<full-panel-id>", panelState?: { displayName?, displayIcon?, ... } }
Singleton Panels
These 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.
| Full ID | Name |
|---|---|
adom/a1b2c3d4-1111-4000-a000-000000000001 | 3D Viewer |
adom/a1b2c3d4-0012-4000-a000-000000000012 | Schematic Editor |
Panel Catalog
Development
| Full ID | Name | Description |
|---|---|---|
adom/a1b2c3d4-eeee-4000-a000-00000000000e | Visual Studio Code | Embedded VS Code editor |
adom/a1b2c3d4-0012-4000-a000-000000000012 | Schematic Editor | Circuit schematic editor (singleton) |
adom/a1b2c3d4-0014-4000-a000-000000000014 | Script Runner | Execute automation scripts |
Control
| Full ID | Name | Description |
|---|---|---|
adom/a1b2c3d4-3333-4000-a000-000000000003 | Motor Control | XRP robot motor speed/direction |
adom/a1b2c3d4-4444-4000-a000-000000000004 | LED Control | XRP robot LED color/brightness |
adom/a1b2c3d4-5555-4000-a000-000000000005 | Servo Control | XRP robot servo angle |
adom/a1b2c3d4-001b-4000-a000-00000000001b | Drone Control | Drone flight control and telemetry |
adom/a1b2c3d4-001c-4000-a000-00000000001c | ESC Control | Electronic Speed Controller |
adom/a1b2c3d4-0010-4000-a000-000000000010 | Lights Control | RGB/white LED strip with color wheel |
adom/a1b2c3d4-0022-4000-a000-000000000022 | Workcell Power | Power supply voltage/current limits |
adom/a1b2c3d4-0023-4000-a000-000000000023 | Basic Control Panel | GPIO pin modes and digital write |
Visualization
| Full ID | Name | Description |
|---|---|---|
adom/a1b2c3d4-1111-4000-a000-000000000001 | 3D Viewer | Workcell/robot 3D view (singleton) |
adom/a1b2c3d4-001a-4000-a000-00000000001a | Babylon OTB | Babylon.js 3D viewer with OTB support |
adom/a1b2c3d4-6666-4000-a000-000000000006 | IMU Sensor | Accelerometer, gyroscope, orientation |
adom/a1b2c3d4-8888-4000-a000-000000000008 | Time of Flight Sensor | ToF distance readings |
adom/a1b2c3d4-9999-4000-a000-000000000009 | Ultrasonic Sensor | Ultrasonic distance / obstacle detection |
adom/a1b2c3d4-aaaa-4000-a000-00000000000a | Line Follower | Line following sensor array |
adom/a1b2c3d4-0015-4000-a000-000000000015 | Temperature Graph | Real-time temperature graphing |
adom/a1b2c3d4-0016-4000-a000-000000000016 | Chip Data | Microchip data monitoring |
adom/a1b2c3d4-0017-4000-a000-000000000017 | Chip Statistics | Microchip performance metrics |
adom/a1b2c3d4-0018-4000-a000-000000000018 | Oven Data | Oven temperature/control monitoring |
adom/a1b2c3d4-0019-4000-a000-000000000019 | ToF Sensor | Time-of-Flight data visualization |
adom/a1b2c3d4-001d-4000-a000-00000000001d | Bosch Sensors | Bosch sensor suite monitoring |
adom/a1b2c3d4-001e-4000-a000-00000000001e | BMP Sensor | Bosch BMP pressure/temperature |
adom/a1b2c3d4-0020-4000-a000-000000000020 | BMV Sensor | Bosch BMV sensor visualization |
adom/a1b2c3d4-0021-4000-a000-000000000021 | UWB Sensor | Ultra-Wideband positioning |
Media
| Full ID | Name | Description |
|---|---|---|
adom/a1b2c3d4-ffff-4000-a000-00000000000f | Live Stream | Live camera: static/free-moving or XY zoom |
adom/a1b2c3d4-ffff-4000-a000-000000000100 | WebRTC Player | Single WebRTC video stream |
adom/a1b2c3d4-2660-4a00-a000-000000000266 | XY Zoom Camera | Dual WebRTC streams with minimap |
Utility
| Full ID | Name | Description |
|---|---|---|
adom/a1b2c3d4-0011-4000-a000-000000000011 | System Log | Real-time system log with filtering |
adom/a1b2c3d4-bbbb-4000-a000-00000000000b | Robot Log | XRP robot console output |
adom/a1b2c3d4-7777-4000-a000-000000000007 | Battery Charger | Battery charging status and power |
adom/a1b2c3d4-cccc-4000-a000-00000000000c | Curriculum | XRP robot educational content |
adom/a1b2c3d4-0031-4000-a000-000000000031 | Web View | Embedded browser — load any URL |
Renaming Tabs and Changing Icons
Tab headers can be customized with a display name and/or icon. These overrides are stored in panelState.displayName and panelState.displayIcon.
displayIcon accepts two formats:
- MDI icon string — e.g.
mdi:github,mdi:cart(see icon reference below) data:URL — base64-encoded image or SVG, e.g.data:image/svg+xml;base64,...ordata:image/png;base64,...
When creating a tab
Pass displayName and/or displayIcon in the POST body to /tabs or /splits:
# MDI icon
curl -s -X POST \
-H "X-Api-Key: $API_KEY" $TARGET_HEADER \
-H "Content-Type: application/json" \
-d '{"panelId":"<leaf-id>","panelType":"adom/a1b2c3d4-0031-4000-a000-000000000031","displayName":"Docs","displayIcon":"mdi:book-open-variant"}' \
"$BASE/tabs"
# Custom SVG (base64-encode the SVG first, then prefix with data:image/svg+xml;base64,)
SVG_B64=$(echo '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#00b8b0" d="M12 2L2 7l10 5 10-5-10-5z"/></svg>' | base64 -w 0)
curl -s -X POST \
-H "X-Api-Key: $API_KEY" $TARGET_HEADER \
-H "Content-Type: application/json" \
-d "{\"panelId\":\"<leaf-id>\",\"panelType\":\"adom/a1b2c3d4-0031-4000-a000-000000000031\",\"displayName\":\"My App\",\"displayIcon\":\"data:image/svg+xml;base64,$SVG_B64\"}" \
"$BASE/tabs"
Renaming an existing tab
There is no dedicated rename endpoint. To rename an existing tab, update the workspace state via the editor PUT endpoint. However, the recommended approach is to set the name when creating the tab.
If the user renames a tab via the UI (right-click → Rename), it is saved automatically via the client-side updateTabDisplay() store method and persisted through the normal autosave flow.
Icon reference
Icons use the Material Design Icons set via Iconify. Common examples:
| Icon | String |
|---|---|
| Cart | mdi:cart |
| GitHub | mdi:github |
| Home | mdi:home |
| Rocket | mdi:rocket-launch |
| Book | mdi:book-open-variant |
| Globe | mdi:earth |
| Code | mdi:code-braces |
| Settings | mdi:cog |
Browse all icons at https://icon-sets.iconify.design/mdi/.
Web View Panel Control
Web View panels have additional commands via adom-cli hydrogen webview. All commands accept --name, --panel-id, or --tab-id for targeting.
Navigate a Web View to a URL
adom-cli hydrogen webview navigate --name "Bug Review" "https://..."
adom-cli hydrogen webview navigate --panel-id <leaf-id> "https://..."
Refresh the current page
adom-cli hydrogen webview refresh --name "Bug Review"
Show or hide the address bar
adom-cli hydrogen webview set-header --name "Bug Review" true # hide
adom-cli hydrogen webview set-header --name "Bug Review" false # show
Important: The argument is HIDDEN, not VISIBLE. true hides the header, false shows it.
Enable or disable proxy mode
adom-cli hydrogen webview set-proxy --name "Bug Review" true # enable
adom-cli hydrogen webview set-proxy --name "Bug Review" false # disable
Proxy mode tracks in-page navigation and scroll position.
Sandbox Panel Control
Sandbox panels run JavaScript in an isolated iframe. All commands accept --name, --panel-id, or --tab-id for targeting.
# Execute JavaScript by tab name
adom-cli hydrogen sandbox eval --name "My Script" 'document.body.innerHTML = "<h1>Hi</h1>"'
# Reset the sandbox — wipe iframe and clear state
adom-cli hydrogen sandbox reset --name "My Script"
# Hide/show the toolbar
adom-cli hydrogen sandbox set-header --name "My Script" true
Eval response statuses:
executed— code ran, result printederror— code threw, error printed, exit 1queued— auto-approve off, user must approve in panelrejected— user rejected queued code, exit 1
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
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. |
404 on panelId | The leaf ID is stale or wrong | Re-GET the layout and use fresh IDs |
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 |
400 on add tab | Missing panelId or panelType | Verify both fields are present and non-empty |
401 Unauthorized | Missing or invalid API key | Read /var/run/adom/api-key; if missing, check env vars or ask the user |
| Singleton already present | Adding a panel that only allows one instance | Inform the user it's already open; offer to navigate to it instead |
400 on close pane | Tried to close the last pane | Tell the user — the workspace must always have at least one pane |
400 on move tab | Tried to move the last tab from the only pane | Cannot leave the workspace empty — add a replacement tab first |