feat(web): add hover command for hover-gated UI (#1783) - #1786
Conversation
Add a first-class `hover <x y|@ref|selector> [--settle]` verb, admitted on web only, that moves the pointer without pressing via the agent-browser backend (mouse move). It rides the existing targeted-touch pipeline (ref/selector/coordinate resolution, occlusion/off-screen guards, settle observation, response builder, recording) through a new optional Interactor/backend `hover` op that only the web provider implements. Touch platforms have no hover state: capabilities advertise it on web only and iOS/Android/Linux reject it at admission with a --platform web hint; longpress stays the mobile hold-gesture verb. Closes #1783
|
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
|
Review findings — changes requested before merge:
Please also include the required planted-red/revert evidence for each new regression test in the PR validation. Remaining iOS Smoke and Coverage lanes were pending when reviewed. |
… revert skill edit Review follow-ups on #1786: - hover @ref on web now dispatches through the provider's own element handle (agent-browser `hover <ref>`) via a new backend hoverTarget, mirroring click/fill's ADR 0011 native-ref path — web ref frames carry no rects, so the coordinate route could never resolve them. The shared preflight + exact-ref dispatch is extracted into dispatchNativeRefInteraction and used by tap/fill/hover; the guarantee matrix native-ref row now lists hover. - Daemon regression test is production-faithful: rect-less web ref frame, scoped provider, asserts no coordinate dispatch. Selector→coordinate and provider hoverRef tests added. - Android emulator coverage summary pin 2/53 → 3/54. - skills/agent-device/SKILL.md reverted (out of scope, AGENTS.md rule). - Docs/help disclose that --settle with @ref on web shares click's existing limitation; use a selector or coordinates for the settled diff.
|
Addressed in fdebd81: 1. hover @ref on web — fixed with a real ref route, not a contract downgrade. You're right that the web ref frame carries no rects, so the coordinate route could never resolve
2. Android coverage pin — updated to 3. SKILL.md — reverted, sorry; only CLI/help/capabilities/docs are touched. Planted-red evidence (each new regression test, production seam removed → red, restored → green; working tree diff verified unchanged after):
Full sweep at fdebd81: typecheck / lint / format / fallow / layering / |
|
Follow-up review at |
…dispatch The provider-integration apple-leak guard partitions every public command into driven/skipped; hover was neither, which failed Integration Tests and took Coverage down with it. Drive it (it reaches the Apple capability refusal, which is scanned like any other error response). Also cover the direct-dispatch handleHoverCommand seam.
|
CI red lanes fixed in e95941d — one root cause: the provider-integration apple-leak guard ( Also added Local: |
|
Follow-up on the remaining blocker + the lane that turned red after it:
|
|
Clean review at 134115c: the prior findings are resolved. The Apple output guard now drives The only red check, |
`boot` and `shutdown` never consulted the host-global device claim store, so a daemon in one state directory could terminate an emulator another daemon held a verified-live claim on and report success (#1799). Rather than adding a claim check to those two handlers, this makes the class unrepresentable: `CommandDescriptor` gains a REQUIRED `deviceClaimPolicy` trait (#1320's vocabulary), and the request-execution scope enforces it where the request runtime bindings create a device binding — the one seam through which any handler can obtain device operations, and already the place per-device deduplication lives. A `transient-exclusive` command acquires a command-scoped claim before operations reach the handler, refuses a foreign live claim with the existing DEVICE_IN_USE/DEVICE_CLAIM_LIVE_OWNER error, and releases in the scope's finally. Every other policy performs no claim-store I/O, so session-bound commands keep #1320's non-goal intact. `hover` also gains the `frameworkTier` it was missing: #1786 landed after #1804's branch point, leaving main's own parity gate red.
Closes #1783.
Summary
Adds a first-class
hover <x y|@ref|selector> [--settle]command, admitted on--platform webonly, so hover-gated UI (a message row's...toolbar, menus that open on pointer enter) is reachable through agent-device the way it already was through the underlyingagent-browserbackend.Shape — minimal by reuse, not by scope:
Interactor.hover?/AgentDeviceBackend.hover?(coordinate: agent-browsermouse move, viamovePointerextracted fromclickCoordinates) plusWebProvider.hoverRef/AgentDeviceBackend.hoverTarget(ref: agent-browserhover <ref-selector>). Only the web provider implements them; touch platforms need no stubs.@ref/selector/coordinate targeting, occlusion/off-screen guards, staleness,--settle(settle-only, no--verify— hover reveals rather than activates), response builder (gesture: 'hover'), event timeline, MCP schema, Node client (interactions.hover), and script recording (hover @refpublishes as a portable selector line).hover @reftakes the ADR 0011 native-ref path exactly likeclick @ref/fill @ref(web ref frames carry no rects, so a coordinate route could never resolve them). The shared preflight → handle-dispatch → exact-ref envelope is extracted intodispatchNativeRefInteractionand used by tap, fill, and hover so the three cannot drift; the guarantee matrixnative-refrow listshover.viewportis (capability: { apple: {}, android: {}, linux: LINUX_NONE }+WEB_INTERACTION_COMMANDS):capabilities --platform weblists it; iOS/Android/Linux reject at admission withUNSUPPORTED_OPERATIONand a--platform webhint.longpressstays the mobile hold-gesture verb.resolvedTargetpipeline (no hittable-ancestor promotion): the pointer only has to enter the matched node's box.Help (
overview,help web,help hover),commands.md, and CHANGELOG are updated so help/capabilities agree with what the CLI does. No skill files are touched.Live validation
Real CLI + managed agent-browser against a fixture reproducing the issue (chat rows whose Delete button is
display:noneuntil:hover):Known, disclosed, pre-existing:
hover @ref --settleon web fails the same wayclick @ref --settledoes today (settle needs a pre-action baseline → runtime resolution → rect-less web frame). Docs/help say to pair--settlewith a selector or coordinates on web; fixing that is a web ref-frame change that applies to click too, out of scope here.Test plan
pnpm typecheck,lint,format:check,check:fallow,check:layering,check:command-docs,check:daemon-wire-compat,check:gate-manifest,check:bundle-owner-filespnpm test:unit(876 files / 6744 tests) — pinned enumerations updated (descriptor parity, timeout policy, post-action observation, capabilities, e2e coverage manifests + Android summary pin, replay arbitraries, command-result map, apple output guardDRIVEN_COMMANDS)test:integration:node55,test:integration:provider52 files / 157 (web desktop scenario now driveshover @ref→ providerhoverRef),test:integration:progress:check(0 missing commands),test:smoke38, interaction-contract 73;test:coveragechanged-line gate PASS 85.19% (threshold 70%)hover→ baremouse move,hoverRef→hover <ref>; web interactor delegation + provider-missing refusal; daemonhover @refon a rect-less web frame takes the providerhoverRefroute with no coordinate dispatch; daemonhover <selector>resolves and dispatches coordinate hover; capability refusal on iOS before dispatch; capabilities matrix web-only; recordedhover @ref→ selector line; direct-dispatchhandleHoverCommand