Architecture

A condensed extract of the design constraints. For implementation depth, see CLAUDE.md at the repo root.

The flow

The full path of one command:

page UI (widget Shadow DOM)
  ↕ WebSocket (127.0.0.1)
@hover-dev/core (Node service)
  ↕ spawn child
local CLI agent (claude -p / codex exec)
  ↕ MCP
Playwright MCP server
  ↕ CDP
isolated debug Chrome
  ↕ DOM
user's dev page

Step events flow back the same path in reverse.

Boundary constraints

These are load-bearing — several are non-obvious:

  • The agent never launches its own Chromium. It connects to whatever debug Chrome is on chromeDebugPort via connectOverCDP and picks the existing context/page whose URL matches the dev-server origin.
  • The service is allowed to spawn one specific Chrome: the isolated debug Chrome under <tmpdir>/hover-chrome. This happens either at dev-server startup (when autoLaunchChrome: true) or on widget demand. It is not the user's primary Chrome profile.
  • Sandboxing is per-agent. Hard-sandbox agents get an explicit --allowedTools mcp__playwright --disallowedTools … lockdown. Soft-sandbox agents get OS-level constraints (--sandbox read-only) plus a strict developer_instructions system prompt; a determined hallucinating agent could still try a built-in shell call, hence the ⚠ badge in the widget.
  • The injected UI lives in a Shadow DOM marked with data-hover="true" so Playwright tests can skip it. Tailwind's default scan does not work inside Shadow DOM — inline styles or CSS-in-JS only.
  • The local Node service binds to 127.0.0.1 only. Plugins are no-op in production builds.
  • Generated Playwright code prefers page.getByRole / getByText over CSS / XPath selectors.
  • Cookies / localStorage never transit the Node service; auth state stays inside the browser and is handled by Playwright in-process.

Why isolated debug Chrome, not the user's normal browser?

Hover deliberately does not attach to the user's primary Chrome profile. Doing so would require the user to relaunch their everyday browser with --remote-debugging-port and would expose every tab, cookie, and extension to whatever the agent does. The trade-off is honest: the user has to log into the app once inside the debug Chrome, but the profile dir at <tmpdir>/hover-chrome persists across runs.

Why is filesystem access disallowed on the agent?

The agent only needs the Playwright MCP server. Allowing Bash, Edit, Write, Read, etc. dramatically widens the blast radius if the prompt is hijacked or if the agent hallucinates a destructive action. The single write path (__vibe_tests__/<slug>.spec.ts) is granted by the Node service, not by the agent's tool list.