From b4dd37ea2152fd0f6404dc38fb4aeddabaf04fce Mon Sep 17 00:00:00 2001 From: ciregenz Date: Fri, 31 Jul 2026 15:54:04 -0700 Subject: [PATCH] [eric] browser: one source for the field flash, so intent and outcome cannot drift --- frontend/src/shared/browserCommandHandler.ts | 28 +++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/frontend/src/shared/browserCommandHandler.ts b/frontend/src/shared/browserCommandHandler.ts index 51327b9b..36bf5cd3 100644 --- a/frontend/src/shared/browserCommandHandler.ts +++ b/frontend/src/shared/browserCommandHandler.ts @@ -971,6 +971,20 @@ const FLASH_COLORS = { // short because the outcome flash is right behind it. const FLASH_MS = { acting: 450, ok: 900, fail: 1200 } as const; +// The outline dance itself, over a variable `el`. One source, because it is now applied from two +// places (straight away, and from a listener the page fires later) and they must not drift. +// 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. +function flashBody(el: string, kind: keyof typeof FLASH_COLORS): string { + return `if (${el}.oswFlashTimer) clearTimeout(${el}.oswFlashTimer);` + + ` if (!${el}.oswFlashSaved) ${el}.oswFlashSaved = [${el}.style.outline, ${el}.style.outlineOffset];` + + ` ${el}.style.outline = "3px solid ${FLASH_COLORS[kind]}"; ${el}.style.outlineOffset = "2px";` + + ` ${el}.oswFlashTimer = setTimeout(function () {` + + ` ${el}.style.outline = ${el}.oswFlashSaved[0]; ${el}.style.outlineOffset = ${el}.oswFlashSaved[1];` + + ` ${el}.oswFlashSaved = null; ${el}.oswFlashTimer = null;` + + ` }, ${FLASH_MS[kind]});`; +} + // 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. @@ -982,19 +996,7 @@ function flashField( 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]});` - + ' }', + functionDeclaration: `function() { var el = this; ${flashBody('el', kind)} }`, }, sessionId).catch(() => {}); }