diff --git a/frontend/src/shared/browserCommandHandler.ts b/frontend/src/shared/browserCommandHandler.ts index 945d9801..51327b9b 100644 --- a/frontend/src/shared/browserCommandHandler.ts +++ b/frontend/src/shared/browserCommandHandler.ts @@ -959,6 +959,45 @@ function axNodesToCandidates(nodes: any[], sessionId?: string): RankItem[] { return out; } +// What the user watching a form get filled sees on the page itself: blue when the agent picks a +// field, green once the value is VERIFIED in it, red when it never landed. Green and red are the +// point: a fill that silently failed used to look exactly like one that worked. +const FLASH_COLORS = { + acting: 'rgba(77,163,255,0.9)', + ok: 'rgba(52,199,89,0.95)', + fail: 'rgba(248,113,113,0.95)', +} as const; +// Long enough for a human to catch a field going green as the form fills; the intent flash stays +// short because the outcome flash is right behind it. +const FLASH_MS = { acting: 450, ok: 900, fail: 1200 } as const; + +// Outline only, NEVER an injected node. A little "filled!" label with text in it would land in +// document.body.innerText, which is exactly what BrowserGetText returns and what the send path +// diffs to prove a composer cleared. Decorating the page must never change what the page SAYS. +// (browser-use needed a whole data-browser-use-exclude mechanism because their panel does inject.) +function flashField( + wv: BrowserWebview, objectId: string | undefined, sessionId: string | undefined, + kind: keyof typeof FLASH_COLORS, +): void { + if (!objectId) return; + sendCdp(wv, 'Runtime.callFunctionOn', { + objectId, + // Saved-once + timer-reset, because a fill flashes twice in a row (intent, then outcome) and a + // naive save would capture the FIRST flash's own outline as the "original" and leave it painted. + functionDeclaration: + 'function() {' + + ' if (this.oswFlashTimer) clearTimeout(this.oswFlashTimer);' + + ' if (!this.oswFlashSaved) this.oswFlashSaved = [this.style.outline, this.style.outlineOffset];' + + ` this.style.outline = "3px solid ${FLASH_COLORS[kind]}"; this.style.outlineOffset = "2px";` + + ' var el = this;' + + ' this.oswFlashTimer = setTimeout(function () {' + + ' el.style.outline = el.oswFlashSaved[0]; el.style.outlineOffset = el.oswFlashSaved[1];' + + ' el.oswFlashSaved = null; el.oswFlashTimer = null;' + + ` }, ${FLASH_MS[kind]});` + + ' }', + }, sessionId).catch(() => {}); +} + // Cumulative top-left offset of a frame within the root viewport: climb the session chain adding each owning