From d76d08360b4cfa84e166f6fd637c690044c92aed Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 27 Jul 2026 23:58:54 -0500 Subject: [PATCH] docs(skills): add inspecting-hermes-desktop-dom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The port is only half of it. Ships the skill that tells the agent the capability exists, when reaching for it beats reading .tsx, and how not to hurt the user's running app while using it. Lands in skills/software-development/ next to node-inspect-debugger, which covers the same protocol for Node/perf work — this one is the DOM/CSS half. Load-bearing parts: don't relaunch or kill the user's app to get a port (a mid-serve kill nukes Chromium's socket pool and the fallout gets blamed on the last CSS edit); never dump the whole DOM into context; prefer the maintained SELECTORS map to invented querySelectors; and CDP answers factual questions only — whether it *looks* right is still the user's call. --- .../inspecting-hermes-desktop-dom/SKILL.md | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 skills/software-development/inspecting-hermes-desktop-dom/SKILL.md diff --git a/skills/software-development/inspecting-hermes-desktop-dom/SKILL.md b/skills/software-development/inspecting-hermes-desktop-dom/SKILL.md new file mode 100644 index 000000000000..1521fd10ec38 --- /dev/null +++ b/skills/software-development/inspecting-hermes-desktop-dom/SKILL.md @@ -0,0 +1,159 @@ +--- +name: inspecting-hermes-desktop-dom +description: "Read the live Hermes desktop DOM/CSS over CDP." +version: 1.0.0 +author: Hermes Agent +license: MIT +platforms: [linux, macos, windows] +metadata: + hermes: + tags: [desktop, electron, cdp, dom, ui-verification, self-inspection] + related_skills: [node-inspect-debugger, systematic-debugging, dogfood] +--- + +# Inspecting the live Hermes desktop DOM + +## Overview + +When you are developing `apps/desktop` and the user is running that same app +(`hgui` / `npm run dev`), you can read the **live rendered DOM** of the window +they are looking at — computed styles, geometry, which CSS rule actually won, +console output — instead of inferring it from `.tsx` and being wrong. + +Dev-server runs open a Chrome DevTools Protocol port on `127.0.0.1:9222` +automatically. The renderer is a Chromium page, so everything DevTools can read, +a script can read. + +**This does not replace looking at it.** CDP answers *factual* questions ("what +is the computed padding", "did this element render", "which selector matches"). +It cannot tell you whether the result looks good. Colour balance, spacing feel, +and "is this ugly" still need the user's eyes or a screenshot. Answer facts with +CDP; hand aesthetics to the user. + +## When to Use + +- Verifying a UI change actually took effect in the running app +- "Why is this element still X?" — find the winning rule before editing anything +- Locating a stable selector for a component you're about to change +- Checking a design token's computed value on a real node +- Reading renderer console errors the user mentions but can't copy out + +**Don't use for:** perf profiling or heap work (`node-inspect-debugger`, +`debugging-hermes-desktop`), or anything where the real question is "does this +look right". + +## The port + +Open on `127.0.0.1:9222` for any dev-server run. Closed in exactly two cases +(`apps/desktop/electron/dev-cdp.ts`): + +- **packaged builds** — always, and no environment value overrides it; +- **no `HERMES_DESKTOP_DEV_SERVER`** — an unpackaged `electron .` against + `dist/` is how the packaged app gets smoke tested, so it behaves like one. + +`HERMES_DESKTOP_CDP_PORT` moves the port (`=9333`) or disables it (`=off`). + +Check before doing anything else: + +```bash +curl -s --max-time 3 http://127.0.0.1:${HERMES_DESKTOP_CDP_PORT:-9222}/json/version +``` + +Empty → no port. Do not guess another port silently. + +**Never relaunch the user's app to get a port.** That destroys their session and +their state. Launch your own isolated instance instead (below). + +## Reading the DOM + +`apps/desktop/scripts/eval.mjs` is the one-liner: + +```bash +cd apps/desktop +node scripts/eval.mjs "document.querySelectorAll('[data-slot]').length" +``` + +For multi-step work use the shared client — it has target discovery and +promise-aware eval: + +```js +import { CDP, SELECTORS } from './scripts/perf/lib/cdp.mjs' + +const cdp = await CDP.connect({ port: 9222, match: '5174' }) +const out = await cdp.eval(`JSON.stringify({ + radius: getComputedStyle(document.documentElement).getPropertyValue('--radius-scalar').trim(), + composer: !!document.querySelector('[data-slot="composer-rich-input"]') +})`) +cdp.close() +``` + +`SELECTORS` in `scripts/perf/lib/cdp.mjs` holds the stable `data-slot` hooks +(composer, thread viewport, assistant message, turn pair, profile rail). Prefer +them over inventing a `querySelector` — they are updated as a unit when +components move. + +## The question this is best at: which rule won? + +Editing every call site because a style "isn't applying" is the classic waste. +Read the real node first: + +```js +const el = document.querySelector('[data-slot="aui_assistant-message-root"] a') +JSON.stringify({ + ownClasses: el.className, + weight: getComputedStyle(el).fontWeight, + parents: (() => { + const out = [] + let n = el + while ((n = n.parentElement) && out.length < 6) out.push(n.className) + return out + })() +}) +``` + +If the node carries no class of its own, the value is **inherited** — sweeping +call sites will not fix it, and you need the ancestor rule. A plugin stylesheet +(e.g. `@tailwindcss/typography`'s `prose a { font-weight: 500 }`) routinely beats +a utility class; override on the shared class, not at each usage. + +## Your own isolated instance + +When there is no port, or you must not disturb the user's window: + +```bash +cd apps/desktop +HERMES_HOME=/tmp/cdp-probe-home \ +HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 \ +HERMES_DESKTOP_CDP_PORT=9333 \ + npx electron . --user-data-dir=/tmp/cdp-probe-userdata +``` + +The separate `--user-data-dir` dodges Electron's single-instance lock, so it +cannot collide with a running `hgui`; the separate `HERMES_HOME` keeps it away +from real sessions. Pick a port other than 9222 for the same reason. Run it in +the background and kill it when done. + +`npm run perf:serve` does the same with a temp `HERMES_HOME` baked in, if you +also want the perf harness. + +## Pitfalls + +- **Never kill the user's dev server or app to "free" anything.** A mid-serve + kill nukes Chromium's socket pool, and the resulting `ERR_NETWORK_CHANGED` + gets blamed on whatever you just changed. +- **A throwaway `HERMES_HOME` has no backend.** The app logs `ECONNREFUSED` for + `hermes:api` and may exit on its own. The renderer still mounts and the DOM is + readable — read promptly, and don't mistake a self-exited probe for a broken + port. Chromium logs `DevTools listening on ws://127.0.0.1:/…` when it + binds; that line is the proof the port opened. +- **Poll, don't probe once.** A just-launched app needs a second or two before + the port answers. +- **Never dump the whole DOM.** The desktop renders hundreds of nodes and + `outerHTML` will bury your context. Project down to a small JSON object inside + the evaluated expression. +- **Pass `match` to `CDP.connect`.** Without it you may attach to the pet + overlay, quick-entry window, or a devtools target instead of the main window. +- **`cdp.eval` returns the value; raw `Runtime.evaluate` double-nests it** + (`.result.result.value`). Use the wrapper. +- **`import.meta.env.DEV` is `true` under `vite dev`** in this repo. The note in + `apps/desktop/scripts/profile-typing-lag.md` claiming otherwise is stale.