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
chromeDebugPortviaconnectOverCDPand 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 (whenautoLaunchChrome: 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 strictdeveloper_instructionssystem 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.1only. Plugins are no-op in production builds. - Generated Playwright code prefers
page.getByRole / getByTextover 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.