skill / adom-workspace-control
!

Not installable via adompkg

This skill has no published release. adompkg install kyle/adom-workspace-control will not work until a maintainer publishes a tarball with install.sh and uninstall.sh.

See the publishing docs for the package.json schema and tarball layout required to ship this skill.

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.png
    

    This 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: jq is not available in the container. Use python3 -m json.tool to pretty-print JSON output instead.
  • 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:
    API_KEY=$(cat /var/run/adom/api-key)
    
    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.

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 cat it 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 /tabs without specifying a panelId, or when you call adom-cli hydrogen workspace add-tab without --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-post crate implements it in src/webapp/tab.rs; copy that pattern. Do not trust focusedPanelId for target selection. Do not use adom-cli hydrogen workspace active-tab to "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:

  1. Setup — resolve credentials, repo, and target user (ask once, remember for the session)
  2. GET the current layout to find leaf node IDs
  3. Identify the correct leaf id for where the user wants the tab placed
  4. Look up the panelType full ID from the catalog below
  5. POST/DELETE/PATCH to make the change
  6. 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 leaf nodes and their id values (needed for adding/removing tabs)
  • All split nodes and their id values (needed for resizing)
  • The tabs array on each leaf and activeTabIndex

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)
  • displayIconMDI icon string (e.g. mdi:github, monochrome) or data: 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 call POST /active-tab with that leaf's id and the tab's id.

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,... or data: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 printed
  • error — code threw, error printed, exit 1
  • queued — auto-approve off, user must approve in panel
  • rejected — 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