v0.9.0
Highlights
- Widget plugin-UI contribution protocol. Plugins can now ship their own widget surface alongside their server-side manifest. A new
window.__HOVER_WIDGET__host API accepts six contribution surfaces:- Namespaced CSS — auto-prefixed with
[data-plugin-active="<name>"]so plugin styles only apply while engaged. - Declarative DOM mutations —
hide: string[]andaddClass: Record<sel, cls>, reverted on deactivate. (Intended for plugin-owned DOM only; default mode owns its own widgets.) - Toolbar buttons appended to the panel header, with optional
badgefns that re-run on state changes. - Full-panel overlays (slide-over from the panel header, plugin owns the render fn + state mapping).
- WS message handlers keyed by namespaced event type (e.g.
'security:flow:added'), only routed while the plugin's mode is active. onActivate/onDeactivatelifecycle callbacks.
- Namespaced CSS — auto-prefixed with
- Single-mode exclusivity — at most one plugin's contributions are visible at any moment. Default mode equals "no plugin active." Host's
applyMode(newMode)deactivates the prior plugin's UI before installing the new one. - Symmetric mode ownership. Default mode listens for
modespayload changes and hides its own widgets (Record,Fix) when a plugin mode takes over. Plugins never need to know default-mode selectors — adding a new plugin no longer means "list which core buttons should I hide." In-flight recording / fix-picking sessions cancel cleanly when mode leaves default. @hover-dev/securitymigrates onto the new protocol. Its network panel, flow row rendering, orange theme, and status code colour buckets all live inpackages/security/src/widget.jsnow — not hardcoded inclient.js. ~180 lines ofif (mode === 'security')branches removed from core. Net:client.jsshrank from 3337 to ~3155 lines.cursor-agentjoins the agent registry as a third option alongsideclaude(hard sandbox) andcodex(soft sandbox). Cursor is soft-sandbox (⚠ in the dropdown). Install:curl https://cursor.com/install -fsS | bash. Known limits surfaced in the descriptor: no--max-budget-usd, no--mcp-config(users add the Playwright MCP to~/.cursor/mcp.jsonthemselves), no token/cost data in the stream (widget renders–for cursor sessions).@hover-dev/astro/@hover-dev/nuxt/webpack-plugin-hoveraccept plugins. Previously onlyvite-plugin-hoverand@hover-dev/nextdid.hover()/new HoverPlugin()gain a...plugins: HoverPluginManifest[]varargs slot (Nuxt usesplugins?: HoverPluginManifest[]on the module options object). Olderhover({})calls without plugins continue to work unchanged.
Bug fixes (user-reported in flight)
- Fix popover sat at
top: 48pxand overlapped the 28px mode bar. Pre-existing layout bug from when mode bar was added in v0.7 —.fix-popovernever made it into the.panel.has-modebaroverlay-offset selector list. Fixed. - Tooltips punched through the Fix popover. Mouseover events still fired on header buttons under the popover; tip's z-index is higher than the popover's, so it rendered visible over the top of the modal. mouseover handler now short-circuits when
.fix-popover.visibleor any.plugin-overlay.openelement exists in the shadow root. el.hidden = truesilently no-op'd on elements with explicitdisplay. Browser UA[hidden] { display: none }has lowest specificity; any author rule withdisplay: inline-flexwins. Added a catch-all[hidden] { display: none !important }near the top ofstyle.csssohidden = truereliably collapses any widget element (including plugin-contributed DOM).
Internal cleanup
client.js-180 lines. Removedstate.flows, 7networkXxxDOM handles,renderFlowRow,renderNetworkOverlay,upsertFlow, thenetworkBtn.hidden = !engaged+recordBtn.hidden = engagedlines inrenderModeButton, and the hardcodedsecurity:flow:added/:updatedWS branch.template.html-33 lines. Removed.networkbtnheader button +.network-overlayblock.style.css-85 / +19. Removed security-specific selectors (.network-overlay,.network-badge,.flow-row,.flow-status-*, etc.). Added a generic.plugin-overlayshell + the catch-all[hidden]rule.- New
packages/widget-bootstrap/src/widget/host.js— 449 lines. Hand-rolled CSS namespacer (top-level selector-list split, @-rule passthrough), DOM-mutation applier with state recording for revert, overlay shell builder, toolbar button construction with badge refresh, fail-silent everywhere with structured[hover/plugin "<name>"] <where> failed:console logging. - 3 new Playwright tests under
examples/basic-app/__vibe_tests__/plugin-host.spec.ts— covers__HOVER_PLUGINS__descriptor injection,__HOVER_WIDGET__host API exposure withapiVersion: 1, and default mode rendering zero plugin contributions while security is installed-but-inactive. cursor.ts351 lines, 18 new vitest cases. Wire shape:cursor-agent -p "<prompt>" --output-format stream-json --force. Defensive walking of*ToolCallwrapper variants (Cursor doesn't publish a comprehensive schema). Soft-sandbox preface prepended to the user prompt (Cursor has no--append-system-promptflag).
Validation
pnpm typecheckclean across all 10 publishable packages.pnpm test: 106 unit tests pass (@hover-dev/coreagents / specs).pnpm test:e2e: 5/5 Playwright (2 login-and-counter + 3 plugin-host) pass.- Manual smoke: switching to security mode in
examples/basic-appshows Network button, hides Record + Fix, applies orange theme; switching back restores defaults.
What's next
- v0.10.x (planned) — multi-tab / cross-origin polish (Stripe, OAuth flows), more agents (
aider/gemini-cli/qwen-code), Chrome extension. - v0.11.x (planned) — recording semantics for security mode. Now that the widget plugin-UI protocol is in place, this becomes a security-side change with zero core widget edits.
Full diff: v0.8.0...v0.9.0