---
name: hd-ui-mac
description: >
  macOS platform companion to hd-ui — the Apple-specific half of DRIVING HD's UI.
  Every menu/dialog/panel id, the GET /ui/actions → POST /ui/invoke contract, and
  registerUiAction live in hd-ui (adom/hd-bootstrap); THIS skill carries only what's
  macOS-specific: HD's webview is WKWebView with NO Chrome DevTools Protocol, so the
  legacy CDP .click() path is dead — you drive the UI via the platform-agnostic
  command bus (hd-ui-command → /ui/_result) and the backend invoke_ui_action RPC
  (e.g. dom.measure resolves a selector's rect WITHOUT CDP). Mirror of hd-ui-windows
  (raw CDP clicking, hd-cdp.js, WebView2 CDP). Trigger words — drive HD UI macos,
  WKWebView, no CDP, dom.measure, hd-ui-command, invoke_ui_action, ui/invoke macos,
  click HD menu mac, open dialog mac.
---

# hd-ui (macOS) — driving the UI without CDP

This is the **macOS half** of `hd-ui`. The window layout, the menu/dialog catalog, the
list of action ids, and the whole `GET /ui/actions` → `POST /ui/invoke` workflow are
host-agnostic and live in the generic **hd-ui** skill — read that first. This skill only
covers what changes on a Mac: **how clicks reach the webview when there is no CDP.**

## The macOS reality — WKWebView has no CDP

On macOS HD's webview is **WKWebView**, which exposes **no Chrome DevTools Protocol
port**. The Windows lineage drove the UI by CDP `.click()` on CSS selectors (`hd-cdp.js`
→ WebView2's CDP). **None of that works here** — there's no port to attach to (the same
limitation that makes `/eval` effectively fire-and-forget on macOS). So on macOS the
command bus isn't merely *preferred*, it's the **only** way to operate HD's UI. For an
element that isn't on the bus yet, **register it** (a 2-line `registerUiAction(...)` —
see hd-ui) rather than reaching for a selector click; there is nothing to click *through*.

## How the bus crosses to WKWebView — `hd-ui-command` → `/ui/_result`

`POST /ui/invoke {id}` is platform-agnostic precisely because it does **not** depend on
CDP. The backend (`src-tauri/crates/hd-control/src/lib.rs`, `invoke_ui_action`) emits a
Tauri event **`hd-ui-command` `{id, args, request_id}`**; the frontend dispatcher runs
the registered handler and reports back via **`POST /ui/_result` `{request_id, ok,
value}`**. The call blocks until that result lands (or the timeout fires). This is the
identical round-trip on every host — but on macOS it's load-bearing, since it's the
substitute for the CDP path Windows still has.

## `dom.measure` — resolve a selector's rect without CDP

The one place the Windows path leaned on CDP that macOS had to re-plumb is **geometry**.
Where Windows cropped a region screenshot server-side via CDP `Page.captureScreenshot`
with a clip, macOS can't — so the capture path calls **`invoke_ui_action(app,
"dom.measure", {selector}, 3000)`** to ask the *frontend* for the element's rect, then
`screencapture`s the webview content area and crops the PNG to that rect
(`hd-control/src/lib.rs` ~5193). `dom.measure` returns `{left, top, width, height,
viewport:{width,height}}` in CSS px. It's a normal bus action — the macOS analog of CDP's
`Runtime.evaluate` for "where is this element," reusable by any caller that needs a
selector's geometry without CDP.

## NOT here (lives in the Windows companion)

Raw CDP `.click()` driving, the `node C:\Users\…\hd-cdp.js` snippets, the WebView2 CDP
port, and the CSS-selector-click playbook are the **Windows** UI path — see
`hd-ui-windows`. On macOS that machinery does not exist; drive everything through the
command bus (`/ui/actions` → `/ui/invoke`) and register anything that's missing.
