← Commit history
package.json+1−1
@@ -4,7 +4,7 @@   "type": "bootstrap",   "title": "Adom Hydrogen (platform base)",   "brief": "Layer Hydrogen's workspace onto a standard Adom workstation.",-  "version": "0.5.15",+  "version": "0.5.16",   "description": "Hydrogen's PLATFORM-GENERIC workspace layer on top of adom/core. Installs adom/core for the full Adom ecosystem, then adds everything common to running inside Hydrogen on ANY platform: the runtime-neutral hydrogen-* skills and the generic editor configuration (Claude Code CLI + extension, VS Code settings, trusted domains). Platform-specific layers depend on THIS: adom/hydrogen-windows-bootstrap (WSL2), adom/hydrogen-mac-bootstrap, adom/hydrogen-ubuntu-bootstrap. The reference design other orgs copy to build their own bootstrap on adom/core.",   "org": "adom",   "tags": [
page.json+1−1
@@ -4,7 +4,7 @@   "type": "bootstrap",   "title": "Adom Hydrogen (platform base)",   "brief": "Layer Hydrogen's workspace onto a standard Adom workstation.",-  "version": "0.5.15",+  "version": "0.5.16",   "description": "Hydrogen's PLATFORM-GENERIC workspace layer on top of adom/core. Installs adom/core for the full Adom ecosystem, then adds everything common to running inside Hydrogen on ANY platform: the runtime-neutral hydrogen-* skills and the generic editor configuration (Claude Code CLI + extension, VS Code settings, trusted domains). Platform-specific layers depend on THIS: adom/hydrogen-windows-bootstrap (WSL2), adom/hydrogen-mac-bootstrap, adom/hydrogen-ubuntu-bootstrap. The reference design other orgs copy to build their own bootstrap on adom/core.",   "org": "adom",   "tags": [
skills/hydrogen-api/SKILL.mdadded+790
@@ -0,0 +1,790 @@+---+name: hydrogen-api+description: >+  Complete reference for Hydrogen control API endpoints. The control+  API runs on a dynamic port (find it in the Hydrogen log or via port discovery). Use+  these endpoints to drive Hydrogen programmatically: manage the workspace (the WSL2 `Adom-Workspace` distro), control+  Claude Code, manipulate VS Code, manage workspace tabs, run setup steps, trigger+  virgin reset, and more. The COMPLETE, always-current catalog is+  GET /_manifest (every route with a category + description; curated entries are+  richer). The API Explorer window (Adom menu > API Explorer, or POST /api-explorer/open)+  renders it with a runnable Try per endpoint. GET /test/help lists the gated /test/*+  diagnostics. NEVER blind-call destructive endpoints (see the Safety section).+  Trigger words: Hydrogen API, control API, Hydrogen endpoint, container exec, claude api,+  iframe eval, workspace tabs, setup run-all, workspace status, reload vscode,+  browser profiles, open url, virgin reset api, reboot api, diagnostics,+  ad health, test window-tree, ai cursor, setup panel, screenshot mode.+---++# Hydrogen Control API Reference++The control API is large and grows. This file quoted "~159 endpoints" in its body and+"~200" in its own frontmatter while the router carried **274**: two stale numbers that did+not even agree with each other, in the one document whose job is to be accurate about the+API. So it no longer quotes a count at all. Don't trust a static count or list in+this file to stay exhaustive — the API is **self-documenting**, with two live+sources of truth you should hit first:++| Source | What it gives you | Where |+|---|---|---|+| `GET /_manifest` | **THE complete catalog** — every registered route (`path`, `method`, `args`, `category`, `description`, `visibility`). Curated entries carry richer descriptions; the rest are auto-generated from the router (`scripts/gen-manifest.py`), so it never drifts. **Discover here first — don't guess paths.** | `hydrogen-control/src/lib.rs` `/_manifest` arm |+| **API Explorer window** | The human face of `/_manifest`: every endpoint grouped by category tab, with a runnable **Try** + Copy URL. Open from the Adom menu, or `POST /api-explorer/open`. | Adom menu ▸ API Explorer |+| **Workspace control** (`:47083`) | A SEPARATE server (`hydrogen-proxy`), and its ~30 routes are **NOT in `/_manifest`**: `/tabs`, `/tabs/find`, `/active-tab`, `/splits`, `/swaps`, `/moves`, `/nav`, `/window-mode`, `/caption`, `/recording`, `/screenshot`, `/audio`, `/sharing`, `/notification`. This is the surface the in-container AI reaches through `adom-cli`. Enumerating `/_manifest` alone will NOT show you these. | `hydrogen-proxy/src/lib.rs` |+| `GET /test/help` | The `/test/*` diagnostics (window enumeration, UIA click, dialog classification, cascade reset). **Gated behind `HD_TEST_API_ENABLED=1`** — 404 by default (expected, not a bug). | `hydrogen-control/src/lib.rs` `/test/help` arm |++## ⛔ Safety — the API enforces this, and tells you in-band++The API itself now labels, warns about, and gates dangerous endpoints (single source of+truth: `safety.rs` in hydrogen-control). Three tiers:++- **`catastrophic`** — irreversibly destroys real user state: `/setup/virgin-reset`,+  `/setup/panel/(run-virgin-reset|wipe-keep-auth)`, `/wsl/unregister` (deletes the whole+  workspace distro), `/system/reboot` (reboots the+  user's COMPUTER), `/config-reset`, `/claude/wipe-creds`, `*-delete`.+  **Headless calls (no browser Origin header — i.e. YOU) get HTTP 409 `confirmation_required`+  and NOTHING happens** until you re-call with `?confirm=1`. Only add `?confirm=1` when the+  user explicitly asked for exactly that action — never to make an error go away.+- **`destructive`** — kills/restarts/re-runs real state (recoverable but real):+  `/app/(restart|relaunch-frontend)`, `/reload-vscode`,+  `/container(-stop|-restart)`, `/workspace/apply-updates`, `/setup/(run-all|run-step|state|+  force-close|trigger|countdown)`, `/claude/tabs/close-all`, `/updater/install`.+  Never call these speculatively or in a sweep.+- **`disruptive`** — the user SEES it (window opens, focus moves, typing lands in their+  real chat, screen recording starts): `/hbw/open` (and `/hbw/close` to close one by label), `/api-explorer/open`, `/open-url`,+  `/open-in-profile`, `/window/foreground`, `/ai-cursor/*`, `/recording/native/start`,+  `/ui/toast`, `/claude/(trigger-auth|authorize-in-browser|open-with-message|inject*|send|+  submit|uia-*|new-chat-and-send|type-and-submit)` (the send/submit ones SPEND a real user+  turn). Run only when the user actually wants that action, and clean up after yourself.+  (Calling `/claude/authorize-in-browser` blindly is how you spray OAuth windows into Edge.)++How the API tells you:+- **Before calling:** every `GET /_manifest` entry carries `safety` + `safety_note`. Check+  them before you POST anything unfamiliar. Unlabeled = safe.+- **When you call:** responses from these endpoints include a `_safety` block. READ IT —+  it says exactly what you just did to the user's machine.+- **The 409 gate** is your last chance to stop; it is not an error to route around.++A safe GET/POST returns within a couple seconds with a 2xx or a clean 4xx `{ok:false,...}`.+If you must probe an endpoint you're unsure about, POST an empty `{}` body and treat a+graceful validation error as "it works" — a 500 or a hang is the bug.++**Base URL:** `http://127.0.0.1:<dynamic>` — the same loopback address from inside the+workspace (WSL2 mirrored networking shares loopback with the Windows host) and on the+Windows host. The port is dynamic per launch; read the live URL from+`~/.adom/hydrogen-control-url` — see "Finding the Control Port" below.++## Finding the Control Port++The control API port is dynamic (default 47084, auto-resolves conflicts).++```bash+# From Hydrogen log+adom-bridge hd_log '{"tail":100}' | grep "control="++# From ports.json+type %APPDATA%\hydrogen-desktop\ports.json++# From inside the WSL2 workspace — USE THIS. Hydrogen writes its live control URL to a discovery+# file every launch (mirrored loopback), so you never hunt for the dynamic port:+CTRL="$(cat ~/.adom/hydrogen-control-url)"; curl -sf "$CTRL/health"++# No file access (remote tool, browser)? Probe the DISCOVERY responder: fixed candidate+# ports tried in order (a single fixed port is not bindable on every Windows box; WSL2+# mirrored networking claims ports invisibly). Take the FIRST that answers with+# service == "hydrogen-desktop-discovery"; its control_url is the live control API:+for p in 47080 48080 49180 50280; do+  R=$(curl -sf --max-time 2 "http://127.0.0.1:$p/") && echo "$R" | grep -q hydrogen-desktop-discovery && break+done+```++⭐ **From the workspace, read `~/.adom/hydrogen-control-url`** (e.g. `http://127.0.0.1:<control>`) — a+file, NOT an env var (your non-interactive Bash shells don't source `.bashrc`/`profile.d`). Hydrogen+rewrites it with the live dynamic port every launch. This is the channel for the UI command bus,+the live ports map, Claude control, etc. — distinct from `adom-cli` (`ADOM_HYDROGEN_URL` → SSE),+which is unchanged for web-hydrogen parity.++## Calling gotchas (learned the hard way)++- **Never hardcode the port.** It's dynamic (47084 is only the default and it auto-resolves+  conflicts). Read `~/.adom/hydrogen-control-url` from the workspace, or `GET /ports`. Hardcoding+  47084 is what makes tools show Hydrogen as "down" when it's actually up on another port.+- **Calling from a REMOTE container (hw/cloud)? Use `adom-bridge hd_api`,** the relay's+  pass-through verb: `hd_api {"method":"POST","path":"/...","body":{...}}`. It auto-discovers+  the port; never shell curl through the relay (quoting gets mangled). Long-blocking calls+  (typed injection) can outlive the relay timeout: the work still completes on Hydrogen's side, so+  verify state instead of re-firing.+- **Getting files OUT (recordings, screenshots): streamed binary, never base64.** Endpoints+  return paths inside the WSL distro; a remote container pulls them with `adom-bridge-cli+  --target <desktop> pull_file {filePaths:["\\\\wsl$\\Adom-Workspace\\<path>"], saveTo:...}`+  (1 MiB binary chunks, sha256-verified, no practical size limit). `/recording/native/stop`+  returns the ready-made `fileWindows` UNC path. Full demo-video pipeline: the+  `hydrogen-demo-recording` skill.+- **Confirm it's really the control API, not the SPA.** In dev, the frontend file server+  answers 200 with HTML for *any* path — so a bare 200 doesn't prove you hit Hydrogen's API. Check+  `GET /health` returns JSON with `service:"hydrogen-desktop-control"`.+- **Driving Hydrogen from a page Hydrogen serves? Use relative fetches, not Tauri IPC.** Tauri `invoke`+  is unavailable on remote http origins; a same-origin `fetch('/ports')` / `fetch('/_manifest')`+  works and sidesteps port discovery entirely.+- **Driving from the CLOUD over the relay:** if two Adom Bridges are connected (laptop ++  a VM), `desktop_*` verbs need `--target <name>` (e.g. `AdomLapper`) or they no-route and+  look empty; `nbrowser_*`/`hd_*` auto-route. PowerShell emits **CRLF** — strip `\r` before+  string-comparing its output. Long calls can exceed the relay's per-call timeout — run+  detached + poll a marker (see `hydrogen-cloud-relay-patterns`).++## Health & Status++| Method | Path | Description |+|---|---|---|+| GET | `/health` | Hydrogen alive check — returns `{ok, port, service}` |+| GET | `/buildinfo` | Running build git SHA + build metadata (the only trustworthy "did my build ship?" check) |+| GET | `/ports` | The 4 editable PortConfig ports (proxy, control, cdp, code_server) |+| GET | `/ports/all` | LIVE full port map — every host + WSL2 port Hydrogen has bound, with status + mirrored-mode info |++## ab-health diagnostics (`/ab/*`)++(Canonical since 1.0.47; the old `/ad/*` spellings still answer as aliases.)++These verify the **adom-bridge-cli / relay / embedded-ab** half of Hydrogen is alive (the+bridge that connects this cloud container to Hydrogen). All GET, all return `{ok, ...}`.++| Method | Path | Description |+|---|---|---|+| GET | `/ab/health` | Overall embedded-ab health roll-up |+| GET | `/ab/process` | Is the adom-bridge process running |+| GET | `/ab/embedded` | Embedded-ab module status |+| GET | `/ab/workspace-direct` | ⭐ The **converged** check — direct path to the workspace relay (use this one) |+| GET | `/ab/relay` | Relay (WS/HTTP) reachability |+| GET | `/ab/liveness-beacon` | Liveness beacon freshness |+| GET | `/ab/direct-api` | Direct-API reachability |+| GET | `/ab/discovery-file` | Discovery file present + parseable |++## Driving Hydrogen's UI (command bus)++Open/close/toggle any Hydrogen menu, dialog, or panel — the first-class alternative to CDP clicking. See the `hydrogen-ui` skill.++| Method | Path | Body | Description |+|---|---|---|---|+| GET | `/ui/actions` | — | List every drivable UI action `{id,label,description,group}` (+ `_hints`) |+| POST | `/ui/invoke` | `{"id":"ports.open"}` | Run a UI action; returns `{ok,value}` only after the frontend ran it |+| GET | `/auth-token` | Hydrogen session token |+| GET | `/setup-status` | Overall setup completion state |+| GET | `/diagnostics/all` | Run all diagnostic checks |+| GET | `/diagnostics/{name}` | Run a single diagnostic check |++## Workspace Management++These endpoints manage the workspace: the WSL2 distro `Adom-Workspace`. The paths+keep their historical `container` spelling; there is no container.++| Method | Path | Body | Description |+|---|---|---|---|+| GET | `/container-status` | — | `runtime_available` (WSL2 usable), `workspace_installed` (distro present), workspace exists/running |+| GET | `/container-stats` | — | CPU, memory, disk usage |+| POST | `/container-restart` | — | Restart the workspace (5s timeout) |+| POST | `/container-stop` | — | Stop the workspace |+| POST | `/container-exec` | `{"command":"..."}` | Run command in the workspace as user `adom` |+| POST | `/container-delete` | — | Remove the workspace |+| GET | `/container/logs` | — | Workspace stdout/stderr |+| GET | `/container/env` | — | Workspace environment variables |+| GET | `/container/ports` | — | Forwarded port mappings |+| GET | `/container/disk` | — | Disk usage inside the workspace |+| POST | `/container/restart` | — | Alias for `/container-restart` |+| POST | `/direct/command` | `{...}` | Direct-connect command channel into the workspace |++> `GET /container-status` renamed two fields: `docker_available` → **`runtime_available`**+> and `image_pulled` → **`workspace_installed`**. Old field names are gone.++### Workspace / WSL endpoints++| Method | Path | Body | Description |+|---|---|---|---|+| GET | `/wsl/status` | — | WSL distro state (registered, running, code-server reachable) |+| GET | `/workspace/health` | — | Deep workspace health (distro + code-server + host reachability) |+| GET | `/virgin-reset-progress` | — | Live progress of an in-flight virgin reset |+| POST | `/workspace/terminal-profile` | — | Ensure the stable `Adom-Workspace` Windows Terminal profile (idempotent) |+| POST | `/workspace/terminal-profile/remove` | — | Remove the `Adom-Workspace` Windows Terminal profile(s) |+| POST | `/workspace/check-updates` | — | Check upstream for newer code-server + Claude extension; stage them |+| POST | `/workspace/apply-updates` | — | Apply staged code-server/extension updates + reload editor |++> ⛔ `POST /wsl/unregister` is **REFUSED as deprecated** — it was a headless+> destructive reset (deletes `/home/adom/project`). Use the Setup panel's Virgin+> Reset or `POST /setup/panel/run-virgin-reset` instead.++## WebView & VS Code++| Method | Path | Body | Description |+|---|---|---|---|+| POST | `/eval` | `{"js":"..."}` | JS eval in main Hydrogen webview |+| POST | `/iframe-eval` | `{"js":"...","contextIndex":N}` | JS eval in a specific iframe context (0-14) |+| POST | `/cdp-eval` | `{"expression":"..."}` | Direct Chrome DevTools Protocol eval |+| POST | `/click` | `{"x":N,"y":N}` | Click at coordinates in webview |+| POST | `/mouse-click` | `{"x":N,"y":N,"button":"left"}` | Mouse click with options |+| POST | `/query` | `{"selector":"..."}` | Query DOM elements |+| POST | `/reload-vscode` | — | Reload the VS Code iframe |+| POST | `/vscode/hide-activity-items` | — | Hide activity-bar items (persisted, overflow-proof) |+| GET | `/targets` | — | List CDP targets (all webview contexts) |+| GET | `/cdp-info` | — | CDP connection info (port, URL) |+| GET | `/devtools` | — | DevTools status |++### AI cursor (visible cursor for demos / clicking)++Distinct from `/click` and `/mouse-click` — this renders a **visible** AI cursor,+glides it, and can click. Use for demos or when the user should see where the AI clicks.++| Method | Path | Body | Description |+|---|---|---|---|+| POST | `/ai-cursor/move` | `{"x":N,"y":N}` | Move (glide) the visible AI cursor to coordinates |+| POST | `/ai-cursor/click` | `{"x":N,"y":N}` | Move the visible AI cursor there + click |+| POST | `/ai-cursor/hide` | — | Hide the visible AI cursor |++### Screenshot primitives++| Method | Path | Body | Description |+|---|---|---|---|+| GET | `/capture/viewport` | — | Capture the Hydrogen webview viewport (PNG) |+| POST | `/shot` | `{...}` | Screenshot primitive (see **hydrogen-self-screenshot** for the full workflow) |++### Port hints++| Method | Path | Body | Description |+|---|---|---|---|+| GET | `/adom-port-hints` | — | Read the workspace port-hints map |+| POST | `/adom-port-hints` | `{...}` | Write/merge the workspace port-hints map |++## Claude Code++### Status / auth state (GET)++| Method | Path | Description |+|---|---|---|+| GET | `/claude/status` | Install state, auth, active session, version |+| GET | `/claude/auth-check` | Deep auth check — file exists, token, expiry |+| GET | `/claude/auth-status` | Auth-status detail |+| GET | `/claude/auth-page-state` | Which auth screen the panel shows: `continue-in-browser` \| `subscription-button` \| `chat`; returns `oauth_url` + `at_browser_auth_gate` |+| GET | `/claude/detect-auth-failure` | Scan webview contexts for auth button/errors |+| GET | `/claude/sessions` | List active Claude sessions |++### Auth actions (POST)++| Method | Path | Body | Description |+|---|---|---|---|+| POST | `/claude/find-auth-button` | — | Find + highlight the sign-in button WITHOUT clicking (validation) |+| POST | `/claude/start-auth` | — | Find + highlight + CLICK the "Claude.ai Subscription" button to begin OAuth |+| POST | `/claude/trigger-auth` | — | Alias for start-auth |+| POST | `/claude/detect-state` | — | Detect current auth/panel state |+| POST | `/claude/authorize-in-browser` | `{"timeout_ms":N,"dry_run":B,"fresh":B,"oauth_url":"..."}` | Auto-click the "Authorize" button on the claude.ai OAuth page (unattended sign-in) |+| POST | `/claude/authorize-tab-enter` | — | Approve the Adom OAuth tab via Enter (branded picker flow) |+| POST | `/claude/inject-creds` | — | Push host backup credentials into the workspace |+| POST | `/claude/wipe-creds` | `{"backup":B}` | Wipe Claude credentials (destructive); `backup:false` keeps the host backup so reinject can restore |++### Panel / conversation control (POST unless noted)++| Method | Path | Body | Description |+|---|---|---|---|+| POST | `/claude/open` | — | Open Claude Code panel (`claude-vscode.editor.open`) |+| POST | `/claude/ensure-open` | — | Ensure the panel is open (strict readiness; recovery if not rendered) |+| POST | `/claude/open-with-message` | `{"message":"..."}` | Open + pre-fill message |+| POST | `/claude/focus` | — | Focus Claude panel |+| POST | `/claude/new-conversation` | — | Start a fresh conversation (shows sign-in panel when unauthenticated) |+| POST | `/claude/new-chat-and-send` | `{"text":"...","typed":B,"submit":B,"delay_ms":N}` | Open fresh conversation, wait for compose box, type + (default) submit |+| POST | `/claude/submit` | — | Find + click send button |+| POST | `/claude/send` | `{"message":"..."}` | Send message to Claude |+| POST | `/claude/inject` | `{"message":"..."}` | Inject text into the compose box |+| POST | `/claude/inject-message` | `{"message":"..."}` | Inject into conversation |+| POST | `/claude/type-and-submit` | `{"message":"...","delay_ms":N}` | Type char-by-char (human-paced) + submit |+| POST | `/claude/remote-control` | `{"name":"..."}` | Turn on Claude Code Remote Control (submits `/remote-control` slash command) |+| POST | `/claude/highlight-open-button` | `{"duration":N}` | Glow animation on Claude button |+| POST | `/claude/clear-open-highlight` | — | Clear the open-button highlight |++## OAuth & Auth++| Method | Path | Body | Description |+|---|---|---|---|+| POST | `/claude-auth` | — | Full PKCE OAuth flow — opens browser, sets up callback |+| GET | `/oauth/callback` | — | OAuth callback handler (receives auth code) |+| POST | `/auth/proxy` | varies | Proxy auth requests to carbon.adom.inc |+| POST | `/open-url` | `{"url":"..."}` | Open URL (triggers browser picker) |+| POST | `/start-oauth-proxy` | `{"port":N}` | Start OAuth callback proxy on a port |+| GET | `/browser-profiles` | — | List browsers + profiles with avatars |+| POST | `/open-in-profile` | `{"url":"...","browser":"...","profileDir":"..."}` | Open URL in specific browser profile |+| POST | `/browser-picker/opened` | `{...}` | Frontend reports the browser picker was opened |+| GET | `/browser-picker/last-open` | — | Last browser-picker open event |++## Setup Steps & Setup Panel++### State / trigger / step control++| Method | Path | Body | Description |+|---|---|---|---|+| GET | `/setup/state` | — | Get current setup state (`never-run` \| `in-progress` \| `complete`) |+| POST | `/setup/state` | `{"state":"..."}` | Set setup state |+| POST | `/setup/step/<id>` | `{"continue_after":B}` | ⭐ Run one step by id (forces panel visible first); `continue_after:true` runs this step + every step after it |+| POST | `/setup/force-close` | — | Force-close setup panel |+| POST | `/setup/countdown` | `{"seconds":N,"message":"..."}` | Show countdown overlay |+| GET | `/setup/trigger` | — | Read trigger file |+| DELETE | `/setup/trigger` | — | Delete trigger file |+| POST | `/config-reset` | — | Reset Hydrogen config |++> ⛔ **REFUSED as deprecated** (headless runs with no visible UI):+> `POST /setup/run-all`, `POST /setup/run-step`, `POST /setup/virgin-reset`,+> `POST /wsl/unregister`. They return a refusal pointing at the UI-visible path.+> To run steps: use `POST /setup/step/<id>` or relaunch Hydrogen to auto-run the cascade.++### Setup panel control++| Method | Path | Body | Description |+|---|---|---|---|+| POST | `/setup/panel/show` | — | Force the setup panel visible |+| POST | `/setup/panel/hide` | — | Hide the setup panel |+| POST | `/setup/panel/show-virgin-reset` | — | Show panel + expand the Virgin Reset section |+| POST | `/setup/panel/virgin/show` | — | Show the Virgin Reset section only |+| POST | `/setup/panel/virgin/hide` | — | Hide the Virgin Reset section |+| POST | `/setup/panel/set-virgin-options` | `{"options":{...}}` | Set the virgin-reset checkboxes |+| POST | `/setup/panel/run-virgin-reset` | — | ⭐ The **ONLY** allowed virgin-reset trigger — UI-visible by construction (shows panel + checkboxes + progress before deleting anything) |+| POST | `/setup/panel/wipe-keep-auth` | — | Synchronous wipe that KEEPS Adom + Claude tokens |+| POST | `/setup/screenshot-mode` | `{"enabled":B}` | Toggle per-step screenshot capture (default OFF) |+| GET | `/setup/screenshot-mode` | — | Whether per-step screenshot mode is on |++## Workspace Tabs++| Method | Path | Body | Description |+|---|---|---|---|+| GET | `/workspace/tabs` | — | List all workspace tabs |+| GET | `/workspace/tabs/find?name=X` | — | Find tab by name (404 if not found) |+| POST | `/workspace/tabs` | `{tab spec}` | Add a new tab |++## System++| Method | Path | Body | Description |+|---|---|---|---|+| GET | `/system/nonce` | — | Get nonce for authenticated reboot |+| GET | `/system/reboot-state` | — | Check reboot pending state |+| POST | `/system/reboot` | `{"nonce":"..."}` | Reboot Windows (nonce-authenticated) |+| POST | `/system/cancel-reboot` | — | Cancel pending reboot |++## App lifecycle & window++| Method | Path | Body | Description |+|---|---|---|---|+| POST | `/app/restart` | — | Restart the Hydrogen app (kills + respawns the binary) |+| POST | `/app/relaunch-frontend` | — | Relaunch the Hydrogen GUI (kill+respawn) while it is responsive |+| POST | `/window/foreground` | — | Bring the Hydrogen window to the foreground (unminimize + focus) |++## Diagnostics (`/test/*`)++Window enumeration, UIA clicking, native-dialog classification, and cascade+diagnostics. **Gated behind `HD_TEST_API_ENABLED=1`** (return 404 / "test API+disabled" otherwise). `GET /test/help` self-lists the whole cluster.++| Method | Path | Body | Description |+|---|---|---|---|+| GET | `/test/help` | — | Self-lists every `/test/*` endpoint |+| GET | `/test/window-tree` | — | Enumerate all visible top-level windows |+| POST | `/test/focus` | `{"window":"..."}` | Focus Hydrogen (`self`) or a window by title substring |+| POST | `/test/click-uia` | `{...}` | Poll UIAutomation for a Button by Name; Invoke when found |+| GET | `/test/setup-steps` | — | Raw setup-steps.json + derived summary (alias: `/test/install-steps`) |+| POST | `/test/dump-state` | `{...}` | Write a diagnostic bundle (logs, install-steps, workspace state, tasklist, window-tree) |+| POST | `/test/reset-cascade` | — | Delete install-steps.json + emit setup-reset event |+| POST | `/test/screenshot-hydrogen-window` | `{...}` | Capture Hydrogen's own window to a PNG file |+| GET | `/test/probe-dialogs` | — | Enumerate visible windows, classify against the cascade-blocker catalog (UAC, WSL2 update, MSI installer, …); returns `kind` + `is_blocking` per window |++## Key Patterns++### iframe-eval context discovery+Contexts 0-14 cover all webview frames. Context numbers shift when panels open/close.+Always scan, never hardcode. The Claude Code extension webview IS reachable through+these contexts.++```+POST /iframe-eval {"js":"document.body.innerText.substring(0,100)","contextIndex":3}+```++### container-exec+Runs as user `adom` inside the workspace. Use full paths (`/home/adom/`+not `~/`). Tilde doesn't expand through the exec chain.++```+POST /container-exec {"command":"ls /home/adom/project"}+```++### Re-run a setup step after completion+The trigger file poller STOPS when all steps are done. `POST /setup/run-all` is+**REFUSED as deprecated** — don't use it. Re-run one step by id (or relaunch Hydrogen to+auto-run the cascade):+```+POST /setup/step/<id>+```++## Complete route index++<!-- BEGIN GENERATED ROUTE INDEX -->++Every route the router exposes, 277 across 20 categories, grouped the+way the API Explorer groups them. Generated from the same table `/_manifest` serves, so it+cannot drift from the code; `scripts/check-manifest-fresh.sh` fails the build if it does.++`/_manifest` remains the richer source at runtime: it carries each route's args, its+description, and a `safety` flag (`destructive` / `disruptive`) that this flat index does not.+**Read a route's safety from `/_manifest` before calling it, not from this list.**++**AI Threads** (35)++- `GET /ai-threads`+- `POST /ai-threads/activate`+- `POST /ai-threads/close`+- `GET /ai-threads/dom-debug`+- `POST /ai-threads/dom-event`+- `GET /ai-threads/dom-events`+- `POST /ai-threads/focus`+- `GET /ai-threads/history`+- `POST /ai-threads/hook-event`+- `POST /ai-threads/inject`+- `POST /ai-threads/inspect`+- `POST /ai-threads/install`+- `GET /ai-threads/install/log`+- `POST /ai-threads/issue-inject`+- `GET /ai-threads/issue-queue`+- `GET /ai-threads/names`+- `POST /ai-threads/new`+- `POST /ai-threads/new-tui`+- `GET /ai-threads/prompts`+- `POST /ai-threads/rename`+- `POST /ai-threads/reorder`+- `POST /ai-threads/repo-link`+- `GET /ai-threads/repo-links`+- `POST /ai-threads/resume`+- `POST /ai-threads/run-prompt`+- `GET /ai-threads/settings`+- `POST /ai-threads/settings`+- `POST /ai-threads/tag`+- `GET /ai-threads/tags`+- `GET /ai-threads/tools`+- `POST /ai-threads/uninstall`+- `GET /ai-threads/wait`+- `POST /ai-threads/walk`+- `GET /ai-threads/walk-report`+- `GET /ai-threads/wiki-repos`++**Adom Bridge** (17)++- `GET /ab/direct-api`+- `GET /ab/discovery-file`+- `GET /ab/embedded`+- `GET /ab/health`+- `GET /ab/liveness-beacon`+- `GET /ab/permissions`+- `POST /ab/permissions/request`+- `POST /ab/permissions/revoke`+- `GET /ab/process`+- `GET /ab/relay`+- `GET /ab/settings`+- `POST /ab/settings`+- `POST /ab/show`+- `GET /ab/workspace-direct`+- `POST /direct/command`+- `GET /direct/health`+- `GET /direct/status`++**Auth & User** (7)++- `GET /auth-token`+- `GET /auth/browser-status`+- `GET /auth/proxy`+- `POST /auth/proxy`+- `DELETE /user/avatar`+- `POST /user/avatar`+- `POST /user/refresh`++**Browser & Open** (11)++- `GET /browser-picker/last-open`+- `POST /browser-picker/opened`+- `DELETE /browser-picker/prefs`+- `GET /browser-picker/prefs`+- `PUT /browser-picker/prefs`+- `GET /browser-profiles`+- `POST /open-in-profile`+- `POST /open-url`+- `GET /webview-can-embed`+- `GET /webview-proxy`+- `GET /wiki-proxy`++**Claude** (49)++- `POST /claude-auth`+- `GET /claude/auth-check`+- `GET /claude/auth-page-state`+- `GET /claude/auth-status`+- `POST /claude/authorize-in-browser`+- `POST /claude/authorize-tab-enter`+- `POST /claude/clear-open-highlight`+- `POST /claude/cli-auth/code`+- `POST /claude/cli-auth/start`+- `GET /claude/contexts`+- `GET /claude/conversation-state`+- `GET /claude/conversations`+- `POST /claude/cross-eval`+- `GET /claude/detect-auth-failure`+- `POST /claude/detect-state`+- `POST /claude/ensure-open`+- `POST /claude/find-auth-button`+- `POST /claude/focus`+- `POST /claude/highlight-open-button`+- `POST /claude/inject`+- `POST /claude/inject-creds`+- `POST /claude/inject-message`+- `POST /claude/new-chat-and-send`+- `POST /claude/new-conversation`+- `POST /claude/open`+- `POST /claude/open-with-message`+- `POST /claude/remote-control`+- `POST /claude/sample-prompt`+- `GET /claude/sample-prompts`+- `POST /claude/sample-prompts/refresh`+- `POST /claude/send`+- `GET /claude/sessions`+- `POST /claude/start-auth`+- `GET /claude/status`+- `POST /claude/submit`+- `GET /claude/tabs`+- `POST /claude/tabs/activate`+- `POST /claude/tabs/close-all`+- `POST /claude/tabs/close-empty-groups`+- `POST /claude/tabs/close-untitled`+- `POST /claude/tabs/merge-groups`+- `POST /claude/trigger-auth`+- `POST /claude/type-and-submit`+- `POST /claude/uia-authorize`+- `POST /claude/uia-scan`+- `POST /claude/wipe-creds`+- `POST /config-reset`+- `GET /oauth/callback`+- `POST /start-oauth-proxy`++**Container / Docker (legacy)** (15)++- `POST /container-delete`+- `POST /container-exec`+- `POST /container-pull-image`+- `POST /container-restart`+- `POST /container-start`+- `GET /container-stats`+- `GET /container-status`+- `POST /container-stop`+- `GET /container/disk`+- `GET /container/env`+- `GET /container/logs`+- `GET /container/ports`+- `POST /container/restart`+- `POST /image-delete`+- `POST /volume-delete`++**Editor State Machine (ESM)** (4)++- `POST /editor/audit-prompt`+- `POST /editor/bars`+- `POST /editor/normalize`+- `GET /editor/state`++**Eval & Automation** (16)++- `POST /ai-cursor/click`+- `POST /ai-cursor/hide`+- `POST /ai-cursor/move`+- `POST /cdp-eval`+- `POST /click`+- `GET /devtools`+- `POST /devtools`+- `POST /eval`+- `POST /eval-in`+- `POST /files/inject`+- `POST /iframe-eval`+- `POST /mouse-click`+- `POST /query`+- `POST /reload-vscode`+- `GET /targets`+- `POST /vscode/hide-activity-items`++**Health & Build** (16)++- `GET /_manifest`+- `POST /app/relaunch-frontend`+- `POST /app/restart`+- `GET /buildinfo`+- `GET /cdp-info`+- `GET /console`+- `GET /diagnostics`+- `GET /diagnostics/all`+- `GET /doctor`+- `POST /doctor`+- `GET /health`+- `GET /logs/tail`+- `GET /ports`+- `GET /ports/all`+- `POST /ports/check`+- `POST /ports/set`++**Hydrogen Browser Window** (9)++- `GET /hbw/chrome`+- `POST /hbw/chrome-toggle`+- `POST /hbw/close`+- `POST /hbw/nav`+- `POST /hbw/open`+- `GET /hbw/state`+- `GET /hbw/tip`+- `GET /hbw/tip-state`+- `POST /hbw/tooltip`++**Other** (2)++- `GET /favicon.ico`+- `GET /welcome.html`++**Permissions** (3)++- `POST /permission/dismiss`+- `POST /permission/request`+- `POST /permission/resolve`++**Screenshots & Recording** (9)++- `GET /capture/availability`+- `GET /capture/viewport`+- `GET /recording/native/capabilities`+- `GET /recording/native/sources`+- `POST /recording/native/start`+- `GET /recording/native/status`+- `POST /recording/native/stop`+- `POST /screenshot`+- `POST /shot`++**Settings** (2)++- `GET /settings/external-changes`+- `POST /settings/rebroadcast`++**Setup Cascade** (25)++- `GET /setup-status`+- `POST /setup/countdown`+- `GET /setup/flags`+- `POST /setup/flags`+- `POST /setup/force-close`+- `POST /setup/panel/hide`+- `POST /setup/panel/run-virgin-reset`+- `POST /setup/panel/set-virgin-options`+- `POST /setup/panel/show`+- `POST /setup/panel/show-virgin-reset`+- `POST /setup/panel/virgin/hide`+- `POST /setup/panel/virgin/show`+- `POST /setup/panel/wipe-keep-auth`+- `POST /setup/run-all`+- `POST /setup/run-step`+- `GET /setup/screenshot-mode`+- `POST /setup/screenshot-mode`+- `GET /setup/state`+- `POST /setup/state`+- `POST /setup/step`+- `DELETE /setup/trigger`+- `GET /setup/trigger`+- `POST /setup/virgin-reset`+- `POST /virgin-reset`+- `GET /virgin-reset-progress`++**System** (6)++- `GET /adom-port-hints`+- `POST /adom-port-hints`+- `POST /system/cancel-reboot`+- `GET /system/nonce`+- `POST /system/reboot`+- `GET /system/reboot-state`++**Test & Dev** (13)++- `POST /debug/show-auth-caption`+- `POST /debug/zorder-probe`+- `POST /dev/emit-event`+- `POST /test/click-uia`+- `POST /test/dump-state`+- `POST /test/focus`+- `GET /test/help`+- `GET /test/install-steps`+- `GET /test/probe-dialogs`+- `POST /test/reset-cascade`+- `POST /test/screenshot-hd-window`+- `GET /test/setup-steps`+- `GET /test/window-tree`++**UI & Windows** (11)++- `POST /api-explorer/open`+- `POST /demo/run`+- `POST /panel-webview/fullscreen`+- `POST /ui/_bus-pong`+- `POST /ui/_result`+- `POST /ui/_sync`+- `GET /ui/actions`+- `POST /ui/invoke`+- `POST /ui/toast`+- `POST /window/close`+- `POST /window/foreground`++**Wake & Repair** (5)++- `POST /wake/config`+- `POST /wake/preview`+- `POST /wake/preview/hide`+- `POST /wake/simulate`+- `GET /wake/status`++**Workspace & Updates** (22)++- `POST /updater/check`+- `POST /updater/install`+- `GET /updater/status`+- `POST /workspace/apply-updates`+- `POST /workspace/check-updates`+- `POST /workspace/exec`+- `GET /workspace/health`+- `POST /workspace/migration/approve`+- `POST /workspace/migration/decline`+- `GET /workspace/stats`+- `GET /workspace/tabs`+- `POST /workspace/tabs`+- `GET /workspace/tabs/`+- `POST /workspace/tabs/`+- `GET /workspace/tabs/find`+- `POST /workspace/terminal-profile`+- `POST /workspace/terminal-profile/remove`+- `POST /workspace/vscode-settings`+- `GET /wsl/status`+- `POST /wsl/unregister`+- `GET /wsl/wedge`+- `POST /wsl/wedge-repair`++**Workspace control** (~30 routes, a SEPARATE server on the proxy port, absent from `/_manifest`)++Reached at `/api/workspaces/editor/<owner>/<repo>/current/...`, which the handler strips+before matching. A bare `GET /tabs` on the proxy 404s; that is correct, not a fault.++- `/tabs`+- `/tabs/find`+- `/tabs/alert`+- `/active-tab`+- `/splits`+- `/swaps`+- `/moves`+- `/nav`+- `/reload`+- `/window-mode`+- `/caption`+- `/recording`+- `/screenshot`+- `/audio`+- `/sharing`+- `/notification`+- `/probe`++<!-- END GENERATED ROUTE INDEX -->
skills/hydrogen-avatar/SKILL.md+1−1
@@ -40,7 +40,7 @@ Then their wiki footprint — pages they own, apps/skills they've published, the - **GitHub** — languages, projects, existing avatar style. - **LinkedIn** — role, industry, how they present professionally. - A plain web search on `"<display name>" <company>` for talks, articles, a personal site.-- **Their signed-in browser via the bridge CLI** (`nbrowser_*`, the nbe extension) can reach a+- **Their signed-in browser via adom-bridge-cli** (`nbrowser_*`, the abe extension) can reach a   Google profile or anything else they're already logged into — see `driving-the-extension`.   Prefer this over pup for logged-in surfaces. 
skills/hydrogen-claude-management/SKILL.md+2−2
@@ -19,12 +19,12 @@ description: >  Hydrogen runs Claude Code as webview tabs inside VS Code. This skill is the **first-class API** for managing those conversations programmatically — from Hydrogen's control-API, from the user's AI inside the workspace, or from setup steps.+API, from the user's AI inside the WSL2 workspace, or from setup steps.  All endpoints are on the **Hydrogen control API** (a dynamic localhost port). From inside the workspace, examples below use `$Hydrogen` for `http://127.0.0.1:<port>` (find the port in the Hydrogen log / port discovery). From an **external** context (a cloud container), call the same-endpoints with the ab verb `adom-bridge-cli hd_api '{"path":"/claude/…","method":"POST","body":{…}}'`+endpoints with the ab verb `adom-bridge hd_api '{"path":"/claude/…","method":"POST","body":{…}}'` — it auto-discovers the port. Everything here works in the **background** — no window focus, no foreground, no keystroke stealing. 
skills/hydrogen-demo-recording/SKILL.mdadded+176
@@ -0,0 +1,176 @@+---+name: hydrogen-demo-recording+description: >+  THE recipe for recording a demo video of Hydrogen: screen-record an AI prompt+  being TYPED (visible human-jitter typing) into a Claude tab plus the AI's resulting+  output, then speed the footage 10x to 30x in video-post and lay an adom-tts voiceover+  on top, so a viewer sees exactly what using an app in Hydrogen feels like. Covers the whole+  pipeline end to end: driving Hydrogen's control API (in-workspace OR from a remote container+  via adom-bridge hd_api), the high-fps native recorder, multi-tab multi-prompt demos,+  getting the mp4 out (streamed binary, never base64), and the post-production handoff.+  Trigger words: demo video, record a demo, hd demo, demo recording, record the typing,+  screen record hd, record ai prompt, app demo, demo pipeline, speedup demo, tts overlay+  demo, show what the app feels like, hydrogen-demo-recording.+---++# hydrogen-demo-recording: record an Hydrogen demo video, end to end++The canonical Adom demo: the viewer watches an AI prompt get TYPED into Hydrogen+Desktop, watches the AI do the work, and hears a narrator explain it, all in a tight+sped-up clip. This skill is the full pipeline. It exists because every piece is easy+to get slightly wrong and the pieces live in four different tools.++The shape of every demo:++1. Start Hydrogen's native high-fps recorder (no picker, no sharing banner).+2. Type the prompt into a Claude tab with VISIBLE typing (the sexy part) and submit.+3. Let the AI's output stream on camera.+4. Stop the recorder, collect the mp4.+5. Post-production: speed up 10x to 30x (video-post), overlay adom-tts narration.++## Step 0: pick your calling transport++Everything below is Hydrogen control-API calls. Two contexts:++**Inside the Hydrogen workspace** (the WSL distro): the live base URL is in a file, never an+env var:++```bash+CTRL=$(cat ~/.adom/hydrogen-control-url)+curl -s -X POST "$CTRL/claude/inject" -H 'Content-Type: application/json' -d '{...}'+```++**From a remote container** (hw, cloud): go through the adom-bridge relay's+pass-through verb. Never curl through the relay (quoting gets mangled) and never hunt+for the port (hd_api auto-discovers it):++```bash+adom-bridge hd_api '{"method":"POST","path":"/claude/inject","body":{...}}'+```++`hd_*` verbs auto-route to the desktop running Hydrogen; `desktop_*` and `pull_file` need+`--target <desktop name>` when more than one desktop is on the relay.++## Step 1: preflight (30 seconds, saves the take)++- `GET /health` answers with service `hydrogen-desktop-control` (not the SPA).+- `GET /capture/availability`: nothing else is recording; it also tells you which+  screenshot method coexists with a recording (CDP `POST /screenshot`, not the SSE path).+- `GET /recording/native/sources`: confirm the exact window name to capture.+- `GET /claude/auth-check`: Claude is signed in (a demo of a sign-in wall is not a demo).++## Step 2: start the recorder++```json+POST /recording/native/start+{"source":"self","cursor":true,"codec":"h264","maxSeconds":300,"reason":"demo take cap"}+```++- **`source:"self"` is THE way to record Hydrogen** (alias: `"hd"`). Never guess Hydrogen's window+  title: it carries the version and dev stamps, and title-guessing has recorded the+  WRONG app before (an Edge window titled "Claude"). `window:<substr>` matches other+  apps' windows; `"screen"` records the whole primary monitor.+- WGC per-window capture: high fps, background-safe, no picker, no "you are sharing+  your screen" banner.+- Overriding `maxSeconds` (default 600) REQUIRES a `reason` string, it shows in the+  UI toast if the cap fires. Omit both if the default cap is fine.+- `codec:"h264"` for web-playable output (h265 is smaller but not web-playable).+- `mic:true` only if you want live narration in the raw take; the usual pipeline adds+  TTS narration in post instead.+- Wait about 1 second after start so the first frames show the clean starting state.+- **Verify the take is ALIVE**: `GET /recording/native/status` while recording shows+  `frames` climbing within about 2 seconds. If it stays 0, the take is dead (target+  minimized or wrong window): stop now and fix, do not record 90 seconds of nothing.+  A 0-frame take errors at stop (500) instead of pretending success.++## Step 3: type the prompt(s) on camera++One call per prompt opens a NEW Claude tab, waits for its compose box, types with+human jitter, and submits:++```json+POST /claude/new-chat-and-send+{"text":"<the demo prompt>","typed":true,"submit":true,"delay_ms":35}+```++- `typed:true` is the visible typing (about `delay_ms` per char with 50 percent+  jitter). `typed:false` pastes instantly: fine for seeding, boring on camera.+- The call BLOCKS until typing completes: perfect for sequencing. If a relay call+  times out on a long prompt, the typing still finishes on Hydrogen's side. Do NOT re-fire.+  Confirm with `GET /claude/conversation-state` (turns went up), then continue.+  Keep prompts short enough to type in under about 20 seconds.+- Single-prompt demo into the CURRENT tab instead: `POST /claude/inject` with the+  same body shape.++**Multi-prompt demos** (the 5-tabs pattern): repeat the call once per prompt (each+opens its own tab), then flip through the finished answers on camera with the+BACKGROUND tab switch:++```json+POST /claude/tabs/activate  {"index":0}   // 0-based; or {"label":"substring"}+```++Verify each switch took with `GET /claude/tabs` (`active:true` moved). Pause a few+seconds per tab so the sped-up video gives each answer screen time.+`GET /claude/conversations` gives per-tab context_ids if you want to seed specific+tabs deterministically (`/claude/inject` with `context_index`).++## Step 4: stop and collect the mp4++```json+POST /recording/native/stop+```++The response carries `file` (relative), `fileAbsolute` (path INSIDE the WSL distro),+`fileWindows` (the `\\wsl$\...` UNC path), and `frames`. If your transport hiccups+and you lose the stop response, `GET /recording/native/status` when idle returns+`lastOutput` with the same paths, the take is recoverable.++- **In the workspace**: `fileAbsolute` is directly readable; you are done.+- **Remote container**: pull it as STREAMED BINARY, 1 MiB chunks, sha256-verified.+  Never base64 an mp4 through an exec channel:++```bash+adom-bridge-cli --target <desktop> pull_file '{"filePaths":["<fileWindows value>"],"saveTo":"<dir>"}'+```++## Step 5: post-production++- **Speedup**: the `video-post` skill owns this. The house style is 10x for demos+  where the output matters, 30x for long AI work sessions. Keep the typing segment+  readable (video-post can ramp: normal speed on the typing, fast on the waiting).+- **Narration**: synthesize with `adom-tts say` (it pronounces Adom terms correctly+  and caches repeats), then video-post mixes it as the audio track. Write narration+  that explains what the viewer is SEEING at each sped-up moment.+- **Recording choreography** (captions, panel staging, what makes a take clean) lives+  in the `demo-recording` skill; read it for anything fancier than a single-window take.++## Known hazards++- **An Hydrogen restart kills the take AND the demo tabs.** The recorder dies with the Hydrogen+  process (the unfinalized temp mp4 is lost) and reopened Claude tabs reset. Do not+  run a demo while anyone (including another AI thread) is rebuilding/relaunching Hydrogen+  on the same machine; if a take mysteriously vanished, check whether Hydrogen restarted+  mid-run (window title timestamp or /buildinfo).+- Tab state reads (`GET /claude/tabs`) are DOM reads: labels can be briefly empty+  while the editor reloads. Prefer `GET /claude/conversations` for stable+  context_ids + titles.+- Screenshot of the Claude panel: `POST /screenshot {target:"claude"}` captures the+  editor surface containing it (the panel is a nested webview with no own CDP target).++## Safety and cleanup (non-negotiable)++- These endpoints are disruptive tier and their responses carry a `_safety` block:+  you are typing into the user's REAL Claude panel (each submitted prompt SPENDS a+  real turn; a 5-prompt demo spends 5) and recording their REAL screen. That is the+  point of a demo, but only do it for a demo the user asked for.+- ALWAYS stop a recording you started, even when a step fails: stop first, then+  report. `GET /recording/native/status` tells you if one is live.+- NEVER call `/claude/tabs/close-all` to clean up: it closes the user's own tabs+  too. There is no per-tab close; leave demo tabs open and tell the user they exist.+- Delete junk takes you pulled; do not litter recordings/ with failed attempts.++## Related++[[hydrogen-api]] (transport + safety tiers), [[hydrogen-recording]] (recorder endpoint reference),+demo-recording (choreography), video-post (speedup + publish), adom-tts (narration).
skills/hydrogen-desktop-sse/SKILL.mdadded+241
@@ -0,0 +1,241 @@+---+name: hydrogen-desktop-sse+description: How Hydrogen's local workspace API + SSE works (a.k.a. the workspace API) — replaces Carbon for workspace commands so the workspace's adom-cli controls Hydrogen, not cloud Hydrogen. The workspace is the WSL2 Adom-Workspace distro. Covers the full pipeline, routing, env vars, field format differences, and hard-won lessons. Trigger words — Hydrogen SSE, workspace API, local API, adom-cli workspace, add-tab, move-tab, webview tab, EventSource, workspace_updated, ADOM_CARBON_URL, ADOM_HYDROGEN_URL.+---++# Hydrogen — Local Workspace API + SSE++## Architecture++```+Workspace adom-cli (the WSL2 Adom-Workspace distro)+    → http://127.0.0.1:47083 (Hydrogen's proxy)+    → Hydrogen handles workspace commands locally + broadcasts SSE+    → Hydrogen frontend receives SSE, re-fetches workspace, renders++Cloud Docker's adom-cli (unchanged)+    → carbon.adom.inc → Hydrogen web+```++Hydrogen runs its own workspace API on port 47083 (reverse proxy, default — see PortConfig) so the workspace's adom-cli controls Hydrogen's UI, not cloud Hydrogen. No cross-talk between environments. (Under WSL2 the workspace reaches Hydrogen at `127.0.0.1:47083` — mirrored networking shares loopback with the Windows host.)++## Port 47083 routing (the hydrogen-proxy / reverse-proxy)++All requests hit port 47083 (reverse proxy, default — see PortConfig). The router in `handle_request()` decides:++| Path pattern | Handler | Where |+|---|---|---|+| `/api/workspaces/editor/{owner}/{repo}/current/*` | Local workspace API | In-memory |+| `/api/panels/webview/*`, `/api/panels/sandbox/*` | Local panel API | SSE broadcast |+| `/containers/*` | Local container discovery | Canned response |+| `/proxy/{port}/{path}` | Workspace proxy | TCP forward to the workspace (WSL2 distro) |+| Everything else (`/api/*`, etc.) | Carbon proxy | HTTPS to carbon.adom.inc |++The dev file server on port 1420 forwards `/api/*` directly to `handle_request()` (same function, not TCP — supports SSE streaming via BoxBody).++## SSE Connection++**Endpoint:** `GET /api/workspaces/editor/{owner}/{repo}/current/events`++Uses `tokio::sync::broadcast` channel. On connect sends `{"type":"connected","connectionId":"hydrogen-local"}`. Workspace mutations broadcast `{"type":"workspace_updated"}`.++**Frontend connects** in `PanelWorkspaceComponent.svelte` → `setupSSE()`. In Tauri mode, `$page.params` may be empty (adapter-static), so it falls back to `owner=local, repo=workspace`.++**Critical lesson:** The `onopen` handler PUTs the workspace state. This broadcasts `workspace_updated`, which the frontend re-fetches. Set `skipNextSSESync = true` before the PUT to prevent the feedback loop that kills the connection.++## Container Discovery++adom-cli calls `/containers/{slug}` to get owner/repo. The slug comes from parsing `VSCODE_PROXY_URI`'s hostname — split on `-`, take the LAST segment.++**Response format** (must match Carbon's exactly):+```json+{+  "repository": {+    "owner": { "name": "jlauer12" },+    "name": "workspace"+  },+  "slug": "hdlocal",+  "status": "running"+}+```++NOT `{"owner": "jlauer12", "repository": "workspace"}` — adom-cli reads `container["repository"]["owner"]["name"]`.++## Workspace Env Vars++Set by the WSL2 workspace-creation code when the distro is created:++| Var | Value | Why |+|---|---|---|+| `VSCODE_PROXY_URI` | `http://hydrogen-hdlocal/proxy/{{port}}/` | Slug = `hdlocal` (last segment after `-`) |+| `ADOM_CARBON_URL` | `http://127.0.0.1:47083` | Container discovery + non-workspace API |+| `ADOM_HYDROGEN_URL` | `http://127.0.0.1:47083` | Workspace mutations (adom-cli sends these HERE, not to Carbon) |+| `ADOM_DESKTOP_MODE` | `local` | Tells adom-cli it's a local environment |++Under WSL2, `127.0.0.1` reaches the Windows host directly — mirrored networking shares loopback with the host, so the same `127.0.0.1:<port>` works from inside the distro and on Windows. The canonical code-server port Hydrogen exposes on the host is 7380.++**Also set in adom-cli config** (persists across sessions):+```bash+adom-cli config set-url http://127.0.0.1:47083+adom-cli config set-token <session-token>+```+Plus manually write `hydrogen_url` to `~/.config/adom-cli/config.json` (no CLI command for it).++**API key** injected at `/var/run/adom/api-key` by bootstrap via `GET "$(cat ~/.adom/hydrogen-control-url)/auth-token"` (the control URL is `http://127.0.0.1:<dynamic>`).++## Workspace Tree Format++The frontend serializes splits with `first`/`second`, NOT `children`:+```json+{+  "type": "split",+  "id": "desktop-root-split",+  "direction": "horizontal",+  "ratio": 0.5,+  "first": { "type": "leaf", "id": "pane-left", ... },+  "second": { "type": "leaf", "id": "pane-right", ... }+}+```++All tree traversal functions (`find_leaf_path`, `find_split_path`, `resolve_path`) must handle BOTH `children` array and `first`/`second` keys.++## REST Endpoint Field Names (adom-cli vs Hydrogen)++adom-cli sends different field names than you might expect:++| Endpoint | adom-cli sends | Hydrogen must read |+|---|---|---|+| POST /tabs | `{panelId, panelType, displayName, initialState}` | Construct tab with UUID, merge initialState into panelState |+| POST /moves | `{sourcePanelId, tabId, targetPanelId}` | NOT `sourcePanel`/`targetPanel` |+| DELETE /tabs | `{panelId, tabId}` or `{name}` | Support both |++## Smoke Test (from inside the workspace)++```bash+# 1. Probe — confirms SSE alive+adom-cli hydrogen probe+# → browser_connected: true, sse_connections: 1++# 2. Get workspace+adom-cli hydrogen workspace get++# 3. Add tab+adom-cli hydrogen workspace add-tab \+  --panel-id desktop-pane-right \+  --panel-type adom/a1b2c3d4-0031-4000-a000-000000000031 \+  --display-name "Test Page" \+  --initial-state '{"url":"https://wiki.adom.inc/adom/adom-shotlog"}'++# 4. Move tab+adom-cli hydrogen workspace move-tab \+  --from-panel-id desktop-pane-right \+  --tab-id <tab-id-from-step-3> \+  --to-panel-id desktop-pane-left++# 5. Screenshot to verify+adom-bridge hd_screenshot+```++## What Broke and Why++### SSE never connected on fresh launch+`setupSSE()` checks `$page.params.owner` and `$page.params.repo`. In Tauri mode with adapter-static, these are empty. **Fix:** fall back to `owner=local, repo=workspace` when in Tauri mode.++### SSE connected then immediately disconnected+The `onopen` handler PUTs workspace state → broadcasts `workspace_updated` → frontend re-fetches → gets the state it just PUT → re-applies → triggers autosave → another PUT → loop. **Fix:** set `skipNextSSESync = true` before the `onopen` PUT.++### adom-cli "Could not get owner from container info"+Three stacked issues:+1. `VSCODE_PROXY_URI` was `http://localhost:8080/...` → slug parsed as `8080` → called `/containers/8080` → Carbon 404+2. Container info response had flat `{"owner": "..."}` but adom-cli reads `container["repository"]["owner"]["name"]`+3. `ADOM_HYDROGEN_URL` wasn't set → workspace commands went to `hydrogen.adom.inc` instead of Hydrogen++### add-tab returned "Missing 'tab' in body"+Hydrogen expected `{"panelId", "tab": {id, panelType, panelState}}` but adom-cli sends `{"panelId", "panelType", "displayName", "initialState"}` — flat format. **Fix:** accept both, construct tab with UUID for the flat format.++### move-tab returned "Source/target panel not found"+Two issues:+1. Handler read `sourcePanel`/`targetPanel` but adom-cli sends `sourcePanelId`/`targetPanelId`+2. `find_leaf_path` only traversed `children` array, not `first`/`second` keys++### Webview tabs were blank + opened in native browser+`add_FrameNavigationStarting` was blocking ALL external URLs in child iframes, including legitimate webview panel content. **Fix:** stop blocking frame navigations entirely — webview panels need external URLs. URL interception for window.open still works via `on_new_window`.++### Google showed "blocked" icon+Not an Hydrogen bug. Google sends `X-Frame-Options: SAMEORIGIN` which prevents iframe embedding. Sites that allow embedding (adom.inc, wiki) load fine.++## Port Discovery — How Any Client Finds Hydrogen++All Hydrogen ports are dynamic (configurable via `ports.json`). The ONE constant is the **discovery port: 47080**. It never changes.++```+GET http://127.0.0.1:47080/discover+```++Returns:+```json+{+  "ok": true,+  "code_server": 7380,+  "proxy": 47083,+  "control": 47084,+  "cdp": 47085,+  "hostname": "hd.localhost"+}+```++> **Relay ports:** under the WSL2 runtime the relay listens **same-port 8765+> (WS) / 8766 (HTTP)** inside the distro and is reached same-port via mirrored+> loopback (or via `/proxy/`). There are **no host 47081/47082 listeners**.+> Clients discover the relay ports from `~/.adom/cli-relay-ports.json` (fallback+> 8765/8766), not from `/discover`.++### Who uses it++| Client | What it discovers | How |+|---|---|---|+| adom-bridge relay | Control API port (was hardcoded 9001) | `curl :47080/discover` → use `control` field |+| adom-cli in the workspace (the WSL2 distro) | Proxy + Hydrogen URL | Already set via `ADOM_CARBON_URL` env var at workspace creation |+| External tools / diagnostics | Any port | Hit discovery, get the full map |+| Cloud Docker (this container) | All Hydrogen ports | `curl http://<desktop-ip>:47080/discover` via relay |++### Implementation++Tiny `std::net::TcpListener` on port 47080 in `hydrogen-app/src/lib.rs`. Runs on a plain OS thread (no tokio). Reads `get_runtime_ports()` on each request. Returns raw HTTP response with JSON body.++```rust+std::thread::spawn(move || {+    let listener = std::net::TcpListener::bind("127.0.0.1:47080").unwrap();+    for stream in listener.incoming() {+        let ports = hd_control::get_runtime_ports();+        let body = serde_json::json!({ "ok": true, ...ports });+        // write HTTP/1.1 200 OK + JSON+    }+});+```++### Port defaults (PortConfig)++| Service | Default Port | User-visible? | Workspace Port |+|---|---|---|---|+| Discovery | **47080** (FIXED) | No | n/a |+| Code-server | 7380 | Yes (in webview URLs) | 8080 |+| Proxy | 47083 | No | n/a |+| Control API | 47084 | No | n/a |+| CDP | 47085 | No | n/a |+| Relay WS | — (same-port under WSL2) | No | 8765 |+| Relay HTTP | — (same-port under WSL2) | No | 8766 |++Config stored at `%APPDATA%\hydrogen-desktop\ports.json`. On first launch, defaults are written without scanning. Workspace-side ports (8080, 8765, 8766) are always fixed — only host-side ports are dynamic. The relay (8765/8766) is reached **same-port** under WSL2 (mirrored loopback) — no host 47081/47082 mapping; discover it from `~/.adom/cli-relay-ports.json` (fallback 8765/8766).++## Files++| File | What |+|---|---|+| `src-tauri/crates/hydrogen-proxy/src/lib.rs` | Workspace API, SSE, Carbon proxy, tree mutations (~1500 lines) |+| `src-tauri/crates/hydrogen-control/src/lib.rs` | `/auth-token` endpoint, `/ports` endpoint, `/setup/*` routes |+| `src-tauri/crates/hydrogen-control/src/ports.rs` | PortConfig struct, load/save/resolve, DISCOVERY_PORT constant |+| `src-tauri/crates/hydrogen-app/src/lib.rs` | Discovery server on :47080, port resolution at startup |+| `src/lib/stores/portStore.ts` | Frontend Svelte store, `initPorts()`, `controlUrl()`, `codeServerUrl()` |+| `src/lib/components/editor/workspaces/PanelWorkspaceComponent.svelte` | Frontend SSE setup, autosave, workspace re-fetch |+| `scripts/hydrogen-bootstrap.sh` | Sets adom-cli config, injects API key, uses `HD_CONTROL_PORT` env var |
skills/hydrogen-eval/SKILL.md+3−3
@@ -8,7 +8,7 @@ description: >   value. Use it to build add-ons (e.g. a usage meter in the top bar), demo   overlays (a moving arrow that points at things), highlight elements, read DOM   state, or drive the UI. Works in dev AND release builds. Trigger words:-  eval-in, inject ui, eval into ah, ah overlay, demo arrow, highlight element,+  eval-in, inject ui, eval into hd, hd overlay, demo arrow, highlight element,   add-on, addon, usage meter, inject script, target shell, target workbench,   target claude, find probe, eval-in targets, point at element, nav widget. ---@@ -31,8 +31,8 @@ see "Why two paths" below) and runs your JS there, returning the value. ## Finding the control port + reaching it from the workspace  The control API port is **dynamic** (auto-resolves conflicts per launch). From inside-the workspace, reach Hydrogen on `127.0.0.1` (the workspace shares loopback with the host;-how that works is platform-specific). Read the live control URL from the discovery file `~/.adom/hydrogen-control-url`+the workspace, reach Hydrogen on `127.0.0.1` (WSL2 mirrored networking shares loopback with the+Windows host). Read the live control URL from the discovery file `~/.adom/hydrogen-control-url` (your non-interactive Bash shells don't source `.bashrc`/`profile.d`, so the env var alone is unreliable): 
skills/hydrogen-key-values/SKILL.md+21
@@ -48,6 +48,27 @@ from anywhere.    user asks (e.g. set `hydrogen.editor_font_size`), but prefer the app's own    settings surface when one exists; a raw PATCH bypasses the app's    validation and mirrors.+6. **SCOPE BY MACHINE OR BY ACCOUNT — decide before you write (the+   multi-computer rule).** The store is account-wide: every machine the user+   runs Hydrogen on reads and writes the SAME map, so a key holding+   per-machine state under a plain name gets clobbered by whichever machine+   wrote last. Three classes, three homes:+   - **Account-level preferences** (font size, color scheme, nav collapsed):+     plain `hydrogen.<name>`. Shared across machines *on purpose* — that's+     settings sync, not clobbering.+   - **Per-machine state** (live ports, runtime identity, anything two+     machines would disagree about): the key MUST embed the sanitized+     machine name — `hydrogen.runtime.<machine>`, `bridge.container.<id>`.+     Each machine owns its own key; nothing fights.+   - **Machine-local settings** (launch-on-boot, ab window behavior, WSL+     shutdown): never in the cloud at all — Hydrogen persists these in+     per-machine localStorage precisely so one machine's choice can't leak+     to another.+   Litmus test before writing any new key: "if the user ran two computers,+   should they see the same value?" Yes → plain key. No → machine-suffixed+   key, or local storage. Two machines with the same computer name would+   share a runtime key; the value's `instance_id`/`install_path`/`ts` fields+   let a reader detect that and trust the freshest writer.  ## The keys you'll meet 
skills/hydrogen-lightbulb/SKILL.md+2−2
@@ -53,9 +53,9 @@ GET /claude/sample-prompts    page — the fleet-wide lineup, updated INDEPENDENTLY of Hydrogen releases. Hydrogen checks    it on every menu open (debounced 15 min) plus every 4 hours during business    hours (Mon-Fri 7am-7pm local); a changed list pops into an open menu live.-   Cache: `sample-prompts-remote.json` in Hydrogen's app-data directory. Docs + PR+   Cache: `%APPDATA%\hydrogen-desktop\sample-prompts-remote.json`. Docs + PR    guide: the [Lightbulb Prompts sub-readme](https://wiki.adom.inc/adom/hydrogen-desktop/files/lightbulb-prompts/README.md).-3. **The user's local file** `sample-prompts.json` in Hydrogen's app-data directory —+3. **The user's local file** `%APPDATA%\hydrogen-desktop\sample-prompts.json` —    personal overrides, always wins:  ```json
skills/hydrogen-pup/SKILL.md+3−3
@@ -33,9 +33,9 @@ always available — no setup questions, just check readiness and go. ## ✅ How to open a page in pup (from the workspace)  ```bash-adom-bridge-cli browser_readiness '{}'          # is the bridge ready? which browser?-adom-bridge-cli browser_open_window '{"url":"http://localhost:<port>/...","sessionId":"<task-name>"}'-adom-bridge-cli browser_screenshot '{"sessionId":"<task-name>"}'   # verify what's on screen+adom-bridge browser_readiness '{}'          # is the bridge ready? which browser?+adom-bridge browser_open_window '{"url":"http://localhost:<port>/...","sessionId":"<task-name>"}'+adom-bridge browser_screenshot '{"sessionId":"<task-name>"}'   # verify what's on screen ```  - Reuse ONE window per task via a stable `sessionId` — open tabs in it rather
skills/hydrogen-screen-lock/SKILL.mdadded+89
@@ -0,0 +1,89 @@+---+name: hydrogen-screen-lock+description: >+  Hydrogen screen capture (screenshots + recording) and the display wake-lock. A browser+  getDisplayMedia capture asserts ES_DISPLAY_REQUIRED, which blocks the user's+  screensaver and stops the display turning off → burn-in. Hydrogen is NATIVE: its+  screenshots use CDP and its recording uses Windows.Graphics.Capture (WGC) /+  DXGI — neither holds the display wake-lock — so Hydrogen does not have the web leak.+  Hydrogen still must start→capture→release per op, cap recordings, and show a "●+  recording" indicator so a forgotten recording is never silent. Read before+  adding or using Hydrogen screenshot / screen-recording features. Trigger words: hd+  screenshot, hd recording, screen capture, wake lock, screensaver blocked,+  display won't sleep, ES_DISPLAY_REQUIRED, release capture, burn-in, powercfg+  requests, getDisplayMedia, recording auto-stop, max duration.+---++# Hydrogen screen capture & the display wake-lock++A browser/`getDisplayMedia` screen capture asserts **`ES_DISPLAY_REQUIRED`** →+Windows won't run the screensaver or turn the display off while it's held →+**burn-in**. Web Hydrogen is forced to keep a capture stream alive all session+(the browser re-prompts otherwise), so it **leaks this lock constantly** — that+has cost real nights of burn-in. `powercfg /requests` shows the holder, e.g.+`DISPLAY: msedge.exe — screen capture`.++## Hydrogen is native — it does NOT have this leak++Hydrogen never uses `getDisplayMedia`:++- **Screenshots** use **CDP** (`Page.captureScreenshot` via `/capture/viewport` and+  `/shot`) — a one-shot grab, no `MediaStream`, no wake-lock. See+  [hydrogen-self-screenshot](../hydrogen-self-screenshot/SKILL.md).+- **Recording** uses **Windows.Graphics.Capture (WGC)** per-window + **DXGI Desktop+  Duplication** full-screen (Rust, `crates/hydrogen-control/src/recorder.rs`). **WGC/DXGI+  do NOT call `SetThreadExecutionState` / hold `ES_DISPLAY_REQUIRED`** — so a native+  recording does **not** block the screensaver or keep the display awake. See+  [hydrogen-recording](../hydrogen-recording/SKILL.md).++So the burn-in failure mode that bites web Hydrogen **cannot happen** from Hydrogen's own+capture. The remaining risk is only a *forgotten* recording wasting CPU/disk — which+Hydrogen guards with the rules below.++## Rules Hydrogen must follow (and already does)++- **Screenshots:** grab the frame → done. Never hold a capture between shots.+  (CDP capture is inherently one-shot; there is nothing to release.)+- **Recording:** has a server-side **max-duration auto-stop** (`maxSeconds`, default+  **600s / 10 min**; a watchdog finalizes the mp4 and emits `recording-auto-stopped`+  → a UI toast). Overriding the cap requires a `reason` so the toast can explain it.+- **Loud indicator:** while a native recording runs, Hydrogen shows a persistent **"●+  Recording · <source>"** badge (bottom-right) so it's never silently on.+- **No blur-stop for screen recording:** Hydrogen being backgrounded is the *normal* case+  for native screen recording (you're recording KiCad / Fusion / the desktop), so Hydrogen+  does **not** stop on blur/minimize — that would break the core use. The+  max-duration cap + indicator are the right nets here. (Blur-stop only makes sense+  for a `getDisplayMedia` *tab* capture, which Hydrogen doesn't use.)+- **Treat a held wake-lock as a bug.** Hydrogen shouldn't ever hold one; if you find one,+  diagnose with `powercfg /requests` (elevated) — the holder is listed under+  `DISPLAY:`.++## If you DO find the display pinned on++1. `powercfg /requests` (elevated) — read the `DISPLAY:` section for the holder.+2. If it's a **browser** (`msedge.exe`/`chrome.exe — screen capture`), that's a+   `getDisplayMedia` leak from web Hydrogen or a pup tab capture — stop that capture+   (close the tab / `browser_record_stop`), not an Hydrogen problem.+3. If it's **Hydrogen**, that's unexpected (native capture holds no lock) — capture the+   `powercfg` output and treat it as a bug to file against the recorder.++A screensaver can't clear another process's power request, so "release at the+source" is the fix. Hydrogen's native capture is release-by-construction.++## Separate concern: capture CONTENTION while recording (multi-thread)++Distinct from the wake-lock: when one AI thread is recording, another thread's+**screenshot** must not silently fail. Hydrogen handles this without holding any lock —+CDP `POST /screenshot`, ab `desktop_screenshot_window`/`_screen`, and pup+`browser_screenshot` all **coexist** with a live recording; only the `adom-cli` SSE+screenshot path returns **`capture_busy`** (with `alternatives`) so the caller switches+methods instead of getting an empty frame. Pre-flight with `GET /capture/availability`.+This is a *contention* guard, not a power-state one — full detail in+[hydrogen-self-screenshot](../hydrogen-self-screenshot/SKILL.md) ("During a recording") and+[hydrogen-recording](../hydrogen-recording/SKILL.md).++## Related skills+- [hydrogen-recording](../hydrogen-recording/SKILL.md) — native WGC/DXGI recording, the max-duration cap + reason, the "● Recording" indicator+- [hydrogen-self-screenshot](../hydrogen-self-screenshot/SKILL.md) — native CDP screenshots (no wake-lock)+- [hydrogen-notifications](../hydrogen-notifications/SKILL.md) — OS toasts + the in-app `/ui/toast`+- the `screen-capture-wake-lock` skill (formerly a gallia skill; skills now ship via adom-wiki packages) — the **web-Hydrogen** side of this issue (where the leak actually happens)
skills/hydrogen-settings/SKILL.md+5−4
@@ -13,7 +13,7 @@ description: >   compact ui, developer mode, vim mode, vim emulation, line numbers, code font,   editor font, launch on boot, start with windows, show console, console on   startup, 3d controls, control style, fusion solidworks blender orbit, schematic-  nudge, auto approve, foreground ah, adom desktop window, turn on, turn off,+  nudge, auto approve, foreground hd, adom desktop window, turn on, turn off,   change setting, how do I configure. --- @@ -38,6 +38,7 @@ override. Keys are dotted (`section.subsection.name`). | Key | Default | What it does | |---|---|---| | `general.developer_mode` | `false` | Enables extra developer/debugging features in the app |+| `general.pane_focus_mode` | `true` | Only the focused pane is active + highlighted; turn OFF to keep every pane always active |  **general.appearance** (subsection) | Key | Default | What it does |@@ -59,7 +60,7 @@ override. Keys are dotted (`section.subsection.name`).  ### adom_desktop  *(the embedded Adom Bridge / ab that Hydrogen runs)* When Hydrogen is running it takes over ab, so ab's options live here. See-**hydrogen-adom-bridge** for the Hydrogen↔ab relationship.+[hydrogen-adom-bridge-cli](../hydrogen-adom-bridge-cli/SKILL.md) for the Hydrogen↔ab relationship. | Key | Type / default | What it does | |---|---|---| | `adom_desktop.open` | action button | "Open Adom Bridge" — brings the ab window to the foreground (persists nothing) |@@ -111,5 +112,5 @@ extension-specific settings).  ## Related skills - [hydrogen-adom-menu](../hydrogen-adom-menu/SKILL.md) — the logo menu that opens Settings (and Ports, API Explorer, etc.)-- **hydrogen-ui** (platform bootstrap; `hydrogen-ui-mac` on macOS) — the broader Hydrogen UI / panel layout this dialog sits in-- **hydrogen-adom-bridge** — what the `adom_desktop.*` prefs control (the embedded ab)+- [hydrogen-ui](../hydrogen-ui/SKILL.md) — the broader Hydrogen UI / panel layout this dialog sits in+- [hydrogen-adom-bridge-cli](../hydrogen-adom-bridge-cli/SKILL.md) — what the `adom_desktop.*` prefs control (the embedded ab)
skills/hydrogen-webview/SKILL.md+21−2
@@ -32,6 +32,24 @@ workspace, next to their editor. Nothing else qualifies.   *different, deliberate* destinations (see `hydrogen-open-url` for the decision   guide). "webview" is not ambiguous; it names exactly one thing. +## ⭐ URLs for YOUR OWN servers: direct port, NOT /proxy/<port>/++Hydrogen Desktop runs the workspace in WSL2 with **mirrored networking ON by default**:+every port you bind inside the workspace is reachable DIRECTLY from Windows at+`http://localhost:<port>/`. So when you launch a web server on port 8850, the webview URL is:++```+http://localhost:8850/          ✅ the Hydrogen Desktop way+http://localhost:7380/proxy/8850/   ❌ Web Hydrogen habit — do not default to this here+```++The `/proxy/<port>/` route through code-server (what `VSCODE_PROXY_URI` points at) exists for+Web Hydrogen parity and as the fallback when mirrored networking is off. On Hydrogen Desktop+it costs you: an extra code-server hop, absolute-path assets (`/style.css`) break, and the+page dies whenever the editor reloads. Use the direct port; if it does not answer, check+mirrored networking first (the `hydrogen-networking` skill) before reaching for /proxy.+(Opening a /proxy URL still works — the tab-open response will hint you back to the direct form.)+ ## ✅ The exact command (idempotent — safe to re-run)  ```bash@@ -116,7 +134,8 @@ Under Hydrogen there is one more surface: **hbw**, Hydrogen's standalone built-i Tauri browser window (picker label "Hydrogen Browser Window", destination id `webview-window`, window label `hbw-*`). It is a separate OS window Hydrogen manages directly — bigger than a pane tab, still not a real browser. Open one first-class via-`POST /hbw/open {"url": ...}` on Hydrogen's control API (0.1.113+). The surface shortlist: **wv** (this skill's webview+`POST /hbw/open {"url": ...}` on Hydrogen's control API (0.1.113+), or pick "Hydrogen Browser+Window" in the Browser Picker. The surface shortlist: **wv** (this skill's webview panel; Hydrogen's pane tab inside Hydrogen), **extwv** (the real-browser pane tab for embed-blocked-sites, above), **pup**, **nb** (the user's native browser via nbe), **hbw**. VS+sites, above), **pup**, **nb** (the user's native browser via abe), **hbw**. VS Code's Simple Browser is NEVER one of them.
skills/hydrogen-wiki-review/SKILL.md+2−2
@@ -24,9 +24,9 @@ adom-wiki repo push <slug> -m "…" --files README.md docs/foo.md #    (or: adom-wiki pkg publish --org adom)  # 2. Immediately load the page in pup on the user's machine so they can review it.-adom-bridge-cli browser_open_window '{"sessionId":"wiki-review","profile":"wiki-review","url":"https://wiki.adom.inc/<owner>/<slug>"}'+adom-bridge browser_open_window '{"sessionId":"wiki-review","profile":"wiki-review","url":"https://wiki.adom.inc/<owner>/<slug>"}' #    Already open from a prior review? Refresh + cache-bust instead of opening a duplicate:-#    adom-bridge-cli browser_navigate '{"sessionId":"wiki-review","url":"https://wiki.adom.inc/<owner>/<slug>?cb=2"}'+#    adom-bridge browser_navigate '{"sessionId":"wiki-review","url":"https://wiki.adom.inc/<owner>/<slug>?cb=2"}'  # 3. Screenshot it and post to the shotlog with a one-line description, then hand the user the URL. ```