waygraph 0.12.8 → 0.12.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -428,6 +428,7 @@ async function headfulPick(page, menu, library, mem, waitPick) {
428
428
  window.__wgPositionRing(
429
429
  { x: r.x, y: r.y, width: r.width, height: r.height },
430
430
  btn.textContent?.trim()?.slice(0, 80) || "target",
431
+ "auto",
431
432
  );
432
433
  });
433
434
  btn.addEventListener("mouseleave", () => {
package/dist/cli.js CHANGED
@@ -396,7 +396,11 @@ function seedMemForFlow(mem, flowBlocks, json, label) {
396
396
  const RING_CSS =
397
397
  "#wg-ring{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
398
398
  "border:2.5px solid #7C3AED;border-radius:10px;" +
399
- "box-shadow:0 0 0 4px rgba(124,58,237,.16);transition:opacity .3s ease;}" +
399
+ "box-shadow:0 0 0 4px rgba(124,58,237,.16);transition:opacity .3s ease,border-color .15s,box-shadow .15s;}" +
400
+ // Planned (stubs / fixtures / YAP / instruction.highlights) = purple.
401
+ // Automation (verify fallback, unmatched fill/click) = yellow - so operators
402
+ // can ignore engine checks and only watch purple narration.
403
+ "#wg-ring[data-tone=auto]{border-color:#EAB308;box-shadow:0 0 0 4px rgba(234,179,8,.22);}" +
400
404
  // A real element, not a ::after pseudo-element - a pseudo-element's
401
405
  // position is CSS-relative to the ring's own box (left:0 always meant
402
406
  // "the ring's own left edge"), so it had no way to be clamped back onto
@@ -407,7 +411,8 @@ const RING_CSS =
407
411
  // window.__wgPositionRing in installOverlay.
408
412
  "#wg-ring-label{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
409
413
  "max-width:min(360px,70vw);white-space:normal;padding:6px 10px;border-radius:7px;background:#7C3AED;color:#fff;" +
410
- "font:600 12px/1.35 system-ui,sans-serif;transition:opacity .3s ease;}" +
414
+ "font:600 12px/1.35 system-ui,sans-serif;transition:opacity .3s ease,background .15s,color .15s;}" +
415
+ "#wg-ring-label[data-tone=auto]{background:#EAB308;color:#1c1917;}" +
411
416
  // Mouse cursor icon that travels to a target before it's acted on, plus a
412
417
  // quick expanding ripple at the moment of a click - same idea as
413
418
  // help-center-clip-engine's #clip-cursor/#clip-ring (video-pipeline). The
@@ -425,6 +430,7 @@ const RING_CSS =
425
430
  "#wg-click-pulse{position:fixed;z-index:2147483647;width:14px;height:14px;" +
426
431
  "margin-left:-7px;margin-top:-7px;border-radius:50%;pointer-events:none;opacity:0;" +
427
432
  "border:2px solid #7C3AED;background:rgba(124,58,237,.25);}" +
433
+ "#wg-click-pulse[data-tone=auto]{border-color:#EAB308;background:rgba(234,179,8,.28);}" +
428
434
  "#wg-click-pulse.wg-pulse{animation:wg-pulse .5s ease-out;}" +
429
435
  "@keyframes wg-pulse{0%{opacity:.9;transform:scale(.4);}100%{opacity:0;transform:scale(2.4);}}" +
430
436
  "#wg-panel{position:fixed;z-index:2147483647;left:50%;bottom:12px;transform:translateX(-50%);" +
@@ -640,26 +646,13 @@ async function installOverlay(page, title) {
640
646
  const cursor = document.getElementById("wg-cursor");
641
647
  if (cursor) cursor.style.opacity = "0";
642
648
  };
643
- window.__wgClickPulse = (x, y) => {
644
- const pulse = document.getElementById("wg-click-pulse");
645
- if (!pulse) return;
646
- pulse.style.left = x + "px";
647
- pulse.style.top = y + "px";
648
- pulse.classList.remove("wg-pulse");
649
- void pulse.offsetWidth;
650
- pulse.classList.add("wg-pulse");
651
- };
652
- // Clamps the ring AND its label to stay fully on-screen, same
653
- // width/height always - only the position moves. A target near a
654
- // viewport edge (real case: "login success" highlight landing top
655
- // right) used to just run the label text off-screen with no guard
656
- // at all - same mistake as the banner had before its own
657
- // left/center/right positions existed, not repeating it here by
658
- // shrinking anything, only repositioning.
659
- window.__wgPositionRing = (box, label) => {
649
+ window.__wgPositionRing = (box, label, tone) => {
660
650
  const ring = document.getElementById("wg-ring");
661
651
  const ringLabel = document.getElementById("wg-ring-label");
662
652
  if (!ring || !ringLabel) return;
653
+ const t = tone === "auto" ? "auto" : "planned";
654
+ ring.dataset.tone = t;
655
+ ringLabel.dataset.tone = t;
663
656
  const margin = 6;
664
657
  const vw = window.innerWidth;
665
658
  const vh = window.innerHeight;
@@ -691,6 +684,16 @@ async function installOverlay(page, title) {
691
684
  ringLabel.style.left = labelLeft + "px";
692
685
  ringLabel.style.top = labelTop + "px";
693
686
  };
687
+ window.__wgClickPulse = (x, y, tone) => {
688
+ const pulse = document.getElementById("wg-click-pulse");
689
+ if (!pulse) return;
690
+ pulse.dataset.tone = tone === "auto" ? "auto" : "planned";
691
+ pulse.style.left = x + "px";
692
+ pulse.style.top = y + "px";
693
+ pulse.classList.remove("wg-pulse");
694
+ void pulse.offsetWidth;
695
+ pulse.classList.add("wg-pulse");
696
+ };
694
697
  window.__wgHideRing = () => {
695
698
  // While a narrate() call owns the ring (mid multi-step action),
696
699
  // the auto-highlight click/fill patches' own end-of-step hide is
@@ -1031,7 +1034,7 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1031
1034
  try {
1032
1035
  const box = await page.locator(h.selector).first().boundingBox();
1033
1036
  if (box) {
1034
- await showRing(page, box, h.label);
1037
+ await showRing(page, box, h.label, h.tone || "planned");
1035
1038
  const authored = resolveFixtureDwellMs(h, { gatesFast });
1036
1039
  const legacyMs = i === list.length - 1 ? 200 : 900;
1037
1040
  const holdMs =
@@ -1053,7 +1056,7 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1053
1056
  if (window.__wgHideRing) window.__wgHideRing();
1054
1057
  return;
1055
1058
  }
1056
- window.__wgPositionRing(el.getBoundingClientRect(), h.label);
1059
+ window.__wgPositionRing(el.getBoundingClientRect(), h.label, h.tone || "planned");
1057
1060
  };
1058
1061
  reposition();
1059
1062
  window.__wgRingTrack = reposition;
@@ -1297,7 +1300,10 @@ function resolveDeclaredHighlights(block, resultTag) {
1297
1300
  }
1298
1301
  }
1299
1302
  if (!Array.isArray(highlights)) return [];
1300
- return highlights.filter((h) => h && typeof h.selector === "string" && typeof h.label === "string");
1303
+ return highlights.filter((h) => h && typeof h.selector === "string" && typeof h.label === "string").map((h) => ({
1304
+ ...h,
1305
+ tone: h.tone === "auto" ? "auto" : "planned",
1306
+ }));
1301
1307
  }
1302
1308
 
1303
1309
  /**
@@ -1327,12 +1333,12 @@ function extractVerifyHighlights(block, resultTag) {
1327
1333
  if (typeof name !== "string") continue;
1328
1334
  let m = /^visible\\((.+)\\)$/.exec(name);
1329
1335
  if (m) {
1330
- highlights.push({ selector: m[1], label: name });
1336
+ highlights.push({ selector: m[1], label: name, tone: "auto" });
1331
1337
  continue;
1332
1338
  }
1333
1339
  m = /^text-equals\\((.+?),\\s*"/.exec(name);
1334
1340
  if (m) {
1335
- highlights.push({ selector: m[1], label: name });
1341
+ highlights.push({ selector: m[1], label: name, tone: "auto" });
1336
1342
  continue;
1337
1343
  }
1338
1344
  }
@@ -1395,13 +1401,13 @@ async function markStepRunning(page, _opts) {
1395
1401
  .catch(() => {});
1396
1402
  }
1397
1403
 
1398
- async function showRing(page, box, label) {
1404
+ async function showRing(page, box, label, tone) {
1399
1405
  await page
1400
1406
  .evaluate(
1401
- ({ box, label }) => {
1402
- if (window.__wgPositionRing) window.__wgPositionRing(box, label);
1407
+ ({ box, label, tone }) => {
1408
+ if (window.__wgPositionRing) window.__wgPositionRing(box, label, tone || "planned");
1403
1409
  },
1404
- { box, label },
1410
+ { box, label, tone: tone || "planned" },
1405
1411
  )
1406
1412
  .catch(() => {});
1407
1413
  }
@@ -1431,8 +1437,8 @@ async function matchStubForLocator(page, locator, stubs) {
1431
1437
 
1432
1438
  async function captionForLocator(page, locator, stubs, fallback) {
1433
1439
  const matched = await matchStubForLocator(page, locator, stubs);
1434
- if (matched) return formatHighlightCaption(matched);
1435
- return fallback;
1440
+ if (matched) return { label: formatHighlightCaption(matched), tone: "planned" };
1441
+ return { label: fallback, tone: "auto" };
1436
1442
  }
1437
1443
 
1438
1444
  async function dwellMatchedStub(page, locator, stubs, pacing) {
@@ -1474,7 +1480,7 @@ async function presentSlides(page, slides, _gate, opts) {
1474
1480
  if (s.selector) {
1475
1481
  try {
1476
1482
  const box = await page.locator(s.selector).first().boundingBox();
1477
- if (box) await showRing(page, box, caption);
1483
+ if (box) await showRing(page, box, caption, "planned");
1478
1484
  } catch {
1479
1485
  await hideRing(page);
1480
1486
  }
@@ -1705,11 +1711,11 @@ async function moveCursorTo(page, box, ms) {
1705
1711
  return { x, y };
1706
1712
  }
1707
1713
 
1708
- async function clickPulseAt(page, x, y) {
1714
+ async function clickPulseAt(page, x, y, tone) {
1709
1715
  await page
1710
- .evaluate(({ x, y }) => {
1711
- if (window.__wgClickPulse) window.__wgClickPulse(x, y);
1712
- }, { x, y })
1716
+ .evaluate(({ x, y, tone }) => {
1717
+ if (window.__wgClickPulse) window.__wgClickPulse(x, y, tone || "planned");
1718
+ }, { x, y, tone: tone || "planned" })
1713
1719
  .catch(() => {});
1714
1720
  }
1715
1721
 
@@ -1783,9 +1789,9 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
1783
1789
  ? "from mem: " + memTrack.lastKeyName
1784
1790
  : "writing from mem";
1785
1791
  const stubs = (stubBeforeRef && stubBeforeRef.current) || [];
1786
- const label = await captionForLocator(page, this, stubs, fallback);
1792
+ const cap = await captionForLocator(page, this, stubs, fallback);
1787
1793
  await moveCursorTo(page, box, cursorMs(500));
1788
- await showRing(page, box, label);
1794
+ await showRing(page, box, cap.label, cap.tone);
1789
1795
  await dwellMatchedStub(page, this, stubs, pacing);
1790
1796
  await new Promise((res) => setTimeout(res, skipTheater() ? 0 : 200));
1791
1797
  } else if (box) {
@@ -1824,6 +1830,7 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
1824
1830
  const originalClick = proto.click;
1825
1831
  proto.click = async function (options) {
1826
1832
  let clickPoint = null;
1833
+ let clickTone = "auto";
1827
1834
  try {
1828
1835
  await installOverlay(page);
1829
1836
  // Wait until the target is visible BEFORE measuring - otherwise
@@ -1853,9 +1860,10 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
1853
1860
  /* ignore */
1854
1861
  }
1855
1862
  const stubs = (stubBeforeRef && stubBeforeRef.current) || [];
1856
- const label = await captionForLocator(page, this, stubs, fallback);
1863
+ const cap = await captionForLocator(page, this, stubs, fallback);
1864
+ clickTone = cap.tone;
1857
1865
  clickPoint = await moveCursorTo(page, box, cursorMs(600));
1858
- await showRing(page, box, label);
1866
+ await showRing(page, box, cap.label, cap.tone);
1859
1867
  await dwellMatchedStub(page, this, stubs, pacing);
1860
1868
  // "pop for a few seconds" - Dan's own phrase, matching the
1861
1869
  // zsign demo-engine's ring-before-click pattern in
@@ -1865,12 +1873,13 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
1865
1873
  // Ring/caption already handled by narrate() - still move the
1866
1874
  // cursor + pulse the click point, just skip re-showing the ring.
1867
1875
  clickPoint = await moveCursorTo(page, box, cursorMs(200));
1876
+ clickTone = "planned";
1868
1877
  }
1869
1878
  } catch {
1870
1879
  // best-effort - the real click below still runs either way
1871
1880
  }
1872
1881
  if (clickPoint) {
1873
- await clickPulseAt(page, clickPoint.x, clickPoint.y);
1882
+ await clickPulseAt(page, clickPoint.x, clickPoint.y, clickTone);
1874
1883
  // clickPulseAt only triggers the CSS animation class - it doesn't
1875
1884
  // wait for it. Without a pause here, the real click (and any
1876
1885
  // resulting navigation/DOM change) fires while the ripple is still
@@ -2257,6 +2266,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
2257
2266
  label: formatHighlightCaption(h),
2258
2267
  duration: h.duration,
2259
2268
  fastMode: h.fastMode,
2269
+ tone: "planned",
2260
2270
  }));
2261
2271
  await cycleHighlightRings(page, errHighlights, !!pacing.gatesFast, {
2262
2272
  defaultHoldMs: 2000,
@@ -2329,6 +2339,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
2329
2339
  label: formatHighlightCaption(h),
2330
2340
  duration: h.duration,
2331
2341
  fastMode: h.fastMode,
2342
+ tone: "planned",
2332
2343
  }),
2333
2344
  );
2334
2345
  } else {
@@ -3855,8 +3866,7 @@ FAIL: [Broke at edge[traverse-1] block=... from=... to=...]
3855
3866
  headed,
3856
3867
  ...(baseUrl ? { baseURL: baseUrl } : {}),
3857
3868
  });
3858
- process.exitCode = code;
3859
- break;
3869
+ process.exit(code);
3860
3870
  }
3861
3871
  case "agent-dive":
3862
3872
  case "init-agents": {
package/dist/engine.d.ts CHANGED
@@ -467,6 +467,48 @@ export declare class Engine {
467
467
  Block<H, I>,
468
468
  EndMarker
469
469
  ]): Flow<I>;
470
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>, H extends Checkpoint<string>, I extends Checkpoint<string>, J extends Checkpoint<string>>(blocks: readonly [
471
+ StartMarker,
472
+ Block<S, B>,
473
+ Block<B, C>,
474
+ Block<C, D>,
475
+ Block<D, E>,
476
+ Block<E, F>,
477
+ Block<F, G>,
478
+ Block<G, H>,
479
+ Block<H, I>,
480
+ Block<I, J>,
481
+ EndMarker
482
+ ]): Flow<J>;
483
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>, H extends Checkpoint<string>, I extends Checkpoint<string>, J extends Checkpoint<string>, K extends Checkpoint<string>>(blocks: readonly [
484
+ StartMarker,
485
+ Block<S, B>,
486
+ Block<B, C>,
487
+ Block<C, D>,
488
+ Block<D, E>,
489
+ Block<E, F>,
490
+ Block<F, G>,
491
+ Block<G, H>,
492
+ Block<H, I>,
493
+ Block<I, J>,
494
+ Block<J, K>,
495
+ EndMarker
496
+ ]): Flow<K>;
497
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>, H extends Checkpoint<string>, I extends Checkpoint<string>, J extends Checkpoint<string>, K extends Checkpoint<string>, L extends Checkpoint<string>>(blocks: readonly [
498
+ StartMarker,
499
+ Block<S, B>,
500
+ Block<B, C>,
501
+ Block<C, D>,
502
+ Block<D, E>,
503
+ Block<E, F>,
504
+ Block<F, G>,
505
+ Block<G, H>,
506
+ Block<H, I>,
507
+ Block<I, J>,
508
+ Block<J, K>,
509
+ Block<K, L>,
510
+ EndMarker
511
+ ]): Flow<L>;
470
512
  }
471
513
  /**
472
514
  * Attaches routing to `block` without touching its act/observe/resolve/verify -
package/dist/engine.js CHANGED
@@ -139,7 +139,7 @@ export async function narrate(locator, caption, action) {
139
139
  .evaluate(({ box, caption }) => {
140
140
  const w = globalThis;
141
141
  if (w.__wgPositionRing)
142
- w.__wgPositionRing(box, caption);
142
+ w.__wgPositionRing(box, caption, "planned");
143
143
  // The CLI's own automatic per-fill/per-click narration checks
144
144
  // this before showing (and overwriting) its own guess - an
145
145
  // explicitly authored caption should win, not get immediately
@@ -3,18 +3,18 @@ import type { MemPage } from "./mem-page.js";
3
3
  export declare function installDemoChrome(page: Page, title?: string, options?: {
4
4
  banner?: boolean;
5
5
  }): Promise<void>;
6
- /** Demo cursor + ring on fill/click during auto explore headful runs. */
6
+ /** Demo cursor + ring on fill/click during auto explore headful runs (automation = yellow). */
7
7
  export declare function instrumentInteractionHighlighting(page: Page, mem: MemPage, slowMo: number): void;
8
8
  declare global {
9
9
  interface Window {
10
10
  __wgMoveCursorTo?: (x: number, y: number, ms?: number) => void;
11
- __wgClickPulse?: (x: number, y: number) => void;
11
+ __wgClickPulse?: (x: number, y: number, tone?: string) => void;
12
12
  __wgPositionRing?: (box: {
13
13
  x: number;
14
14
  y: number;
15
15
  width: number;
16
16
  height: number;
17
- }, label: string) => void;
17
+ }, label: string, tone?: string) => void;
18
18
  __wgHideRing?: () => void;
19
19
  __wgPendingNavClickLabel?: string;
20
20
  __wgWirePanelChrome?: (panel: HTMLElement, storageKey: string, chromeTitle?: string) => void;
@@ -1,8 +1,11 @@
1
1
  const RING_CSS = "#wg-ring{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
2
- "border:2.5px solid #7C3AED;border-radius:10px;box-shadow:0 0 0 4px rgba(124,58,237,.16);transition:opacity .3s ease;}" +
2
+ "border:2.5px solid #7C3AED;border-radius:10px;box-shadow:0 0 0 4px rgba(124,58,237,.16);" +
3
+ "transition:opacity .3s ease,border-color .15s,box-shadow .15s;}" +
4
+ "#wg-ring[data-tone=auto]{border-color:#EAB308;box-shadow:0 0 0 4px rgba(234,179,8,.22);}" +
3
5
  "#wg-ring-label{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
4
6
  "white-space:nowrap;padding:4px 9px;border-radius:7px;background:#7C3AED;color:#fff;" +
5
- "font:600 12px/1.2 system-ui,sans-serif;transition:opacity .3s ease;}" +
7
+ "font:600 12px/1.2 system-ui,sans-serif;transition:opacity .3s ease,background .15s,color .15s;}" +
8
+ "#wg-ring-label[data-tone=auto]{background:#EAB308;color:#1c1917;}" +
6
9
  "#wg-cursor{position:fixed;z-index:2147483647;width:24px;height:24px;pointer-events:none;" +
7
10
  "left:0;top:0;opacity:0;margin:0;" +
8
11
  "transition:transform var(--wg-cursor-ms,600ms) cubic-bezier(.22,1,.36,1),opacity .2s ease;" +
@@ -10,6 +13,7 @@ const RING_CSS = "#wg-ring{position:fixed;z-index:2147483646;pointer-events:none
10
13
  "#wg-click-pulse{position:fixed;z-index:2147483647;width:14px;height:14px;" +
11
14
  "margin-left:-7px;margin-top:-7px;border-radius:50%;pointer-events:none;opacity:0;" +
12
15
  "border:2px solid #7C3AED;background:rgba(124,58,237,.25);}" +
16
+ "#wg-click-pulse[data-tone=auto]{border-color:#EAB308;background:rgba(234,179,8,.28);}" +
13
17
  "#wg-click-pulse.wg-pulse{animation:wg-pulse .5s ease-out;}" +
14
18
  "@keyframes wg-pulse{0%{opacity:.9;transform:scale(.4);}100%{opacity:0;transform:scale(2.4);}}" +
15
19
  "#wg-banner{position:fixed;z-index:2147483647;top:14px;left:14px;" +
@@ -24,7 +28,7 @@ export async function installDemoChrome(page, title = "waygraph auto", options =
24
28
  const showBanner = options.banner !== false;
25
29
  await page.addStyleTag({ content: RING_CSS }).catch(() => { });
26
30
  await page
27
- .evaluate(({ title, favicon }) => {
31
+ .evaluate(({ title, favicon, showBanner }) => {
28
32
  if (!document.getElementById("wg-ring")) {
29
33
  const ring = document.createElement("div");
30
34
  ring.id = "wg-ring";
@@ -57,21 +61,25 @@ export async function installDemoChrome(page, title = "waygraph auto", options =
57
61
  cursor.style.transform = "translate(" + x + "px," + y + "px)";
58
62
  cursor.style.opacity = "1";
59
63
  };
60
- window.__wgClickPulse = (x, y) => {
64
+ window.__wgClickPulse = (x, y, tone) => {
61
65
  const pulse = document.getElementById("wg-click-pulse");
62
66
  if (!pulse)
63
67
  return;
68
+ pulse.dataset.tone = tone === "auto" ? "auto" : "planned";
64
69
  pulse.style.left = x + "px";
65
70
  pulse.style.top = y + "px";
66
71
  pulse.classList.remove("wg-pulse");
67
72
  void pulse.offsetWidth;
68
73
  pulse.classList.add("wg-pulse");
69
74
  };
70
- window.__wgPositionRing = (box, label) => {
75
+ window.__wgPositionRing = (box, label, tone) => {
71
76
  const ring = document.getElementById("wg-ring");
72
77
  const ringLabel = document.getElementById("wg-ring-label");
73
78
  if (!ring || !ringLabel)
74
79
  return;
80
+ const t = tone === "auto" ? "auto" : "planned";
81
+ ring.dataset.tone = t;
82
+ ringLabel.dataset.tone = t;
75
83
  ring.style.left = box.x - 6 + "px";
76
84
  ring.style.top = box.y - 6 + "px";
77
85
  ring.style.width = box.width + 12 + "px";
@@ -111,16 +119,24 @@ async function moveCursorTo(page, box, ms) {
111
119
  await new Promise((r) => setTimeout(r, ms));
112
120
  return { x, y };
113
121
  }
114
- async function showRing(page, box, label) {
115
- await page.evaluate(({ box, label }) => window.__wgPositionRing?.(box, label), { box, label }).catch(() => { });
122
+ async function showRing(page, box, label, tone = "auto") {
123
+ await page
124
+ .evaluate(({ box, label, tone }) => window.__wgPositionRing?.(box, label, tone), {
125
+ box,
126
+ label,
127
+ tone,
128
+ })
129
+ .catch(() => { });
116
130
  }
117
131
  async function hideRing(page) {
118
132
  await page.evaluate(() => window.__wgHideRing?.()).catch(() => { });
119
133
  }
120
- async function clickPulseAt(page, x, y) {
121
- await page.evaluate(({ x, y }) => window.__wgClickPulse?.(x, y), { x, y }).catch(() => { });
134
+ async function clickPulseAt(page, x, y, tone = "auto") {
135
+ await page
136
+ .evaluate(({ x, y, tone }) => window.__wgClickPulse?.(x, y, tone), { x, y, tone })
137
+ .catch(() => { });
122
138
  }
123
- /** Demo cursor + ring on fill/click during auto explore headful runs. */
139
+ /** Demo cursor + ring on fill/click during auto explore headful runs (automation = yellow). */
124
140
  export function instrumentInteractionHighlighting(page, mem, slowMo) {
125
141
  const typeDelay = () => (slowMo ? 0 : 30);
126
142
  const clickPrePop = () => (slowMo ? 300 : 700);
@@ -133,89 +149,81 @@ export function instrumentInteractionHighlighting(page, mem, slowMo) {
133
149
  memTrack.at = Date.now();
134
150
  return originalGet(key);
135
151
  };
136
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
137
152
  const proto = Object.getPrototypeOf(page.locator("html"));
138
153
  if (!proto.__wgFillPatched) {
139
154
  proto.__wgFillPatched = true;
140
- // Keep unbound — originalFill.call(this, …) must receive the Locator as `this`, not the prototype.
141
155
  const originalFill = proto.fill;
142
156
  proto.fill = async function (value, options) {
143
- const livePage = (typeof this.page === "function" ? this.page() : page);
144
157
  try {
145
- await installDemoChrome(livePage);
158
+ await installDemoChrome(page, "", { banner: false });
146
159
  const box = await this.boundingBox();
147
160
  if (box) {
148
161
  const label = memTrack.lastKeyName && Date.now() - memTrack.at < 3000
149
162
  ? "from mem: " + memTrack.lastKeyName
150
- : "writing";
151
- await moveCursorTo(livePage, box, cursorMs(500));
152
- await showRing(livePage, box, label);
153
- await new Promise((r) => setTimeout(r, 200));
163
+ : "writing from mem";
164
+ await moveCursorTo(page, box, cursorMs(500));
165
+ await showRing(page, box, label, "auto");
166
+ await new Promise((res) => setTimeout(res, 200));
154
167
  }
155
168
  }
156
169
  catch {
157
170
  /* best-effort */
158
171
  }
159
- // Prefer a single fill - clear+pressSequentially can hang when the auto
160
- // panel steals focus mid-type (looks stuck on "Running: submit-login").
161
- if (slowMo || typeDelay() === 0) {
162
- await originalFill.call(this, value, options);
172
+ let result;
173
+ try {
174
+ await originalFill.call(this, "", {
175
+ timeout: options?.timeout,
176
+ });
177
+ const seqOpts = { delay: typeDelay() };
178
+ const to = options?.timeout;
179
+ if (to !== undefined)
180
+ seqOpts.timeout = to;
181
+ result = await this.pressSequentially(String(value), seqOpts);
163
182
  }
164
- else {
165
- try {
166
- await originalFill.call(this, "", options);
167
- const seqOpts = { delay: typeDelay() };
168
- if (options?.timeout !== undefined)
169
- seqOpts.timeout = options.timeout;
170
- await this.pressSequentially(String(value), seqOpts);
171
- }
172
- catch {
173
- await originalFill.call(this, value, options);
174
- }
183
+ catch {
184
+ result = await originalFill.call(this, value, options);
175
185
  }
176
- await hideRing(livePage);
186
+ await hideRing(page);
187
+ return result;
177
188
  };
178
189
  }
179
190
  if (!proto.__wgClickPatched) {
180
191
  proto.__wgClickPatched = true;
181
192
  const originalClick = proto.click;
182
193
  proto.click = async function (options) {
183
- const livePage = (typeof this.page === "function" ? this.page() : page);
184
194
  let clickPoint = null;
185
195
  try {
186
- await installDemoChrome(livePage);
187
- await this.waitFor({ state: "visible", timeout: options?.timeout ?? 15000 }).catch(() => { });
196
+ await installDemoChrome(page, "", { banner: false });
197
+ await this.waitFor({
198
+ state: "visible",
199
+ timeout: options?.timeout || 30000,
200
+ }).catch(() => { });
188
201
  const box = await this.boundingBox();
189
202
  if (box) {
190
203
  let label = "click";
191
204
  try {
192
- const navLabel = await livePage.evaluate(() => window.__wgPendingNavClickLabel ?? null);
193
- if (navLabel)
194
- label = String(navLabel);
195
- else {
196
- const text = (await this.textContent())?.trim();
197
- if (text && text.length <= 30)
198
- label = text;
199
- }
205
+ const text = (await this.textContent())?.trim();
206
+ if (text && text.length > 0 && text.length <= 30)
207
+ label = text;
200
208
  }
201
209
  catch {
202
210
  /* ignore */
203
211
  }
204
- clickPoint = await moveCursorTo(livePage, box, cursorMs(600));
205
- await showRing(livePage, box, label);
206
- await new Promise((r) => setTimeout(r, clickPrePop()));
212
+ clickPoint = await moveCursorTo(page, box, cursorMs(600));
213
+ await showRing(page, box, label, "auto");
214
+ await new Promise((res) => setTimeout(res, clickPrePop()));
207
215
  }
208
216
  }
209
217
  catch {
210
218
  /* best-effort */
211
219
  }
212
220
  if (clickPoint) {
213
- await clickPulseAt(livePage, clickPoint.x, clickPoint.y);
214
- await new Promise((r) => setTimeout(r, 200));
221
+ await clickPulseAt(page, clickPoint.x, clickPoint.y, "auto");
222
+ await new Promise((res) => setTimeout(res, 80));
215
223
  }
216
224
  const result = await originalClick.call(this, options);
217
- await new Promise((r) => setTimeout(r, clickPostPop()));
218
- await hideRing(livePage).catch(() => { });
225
+ await new Promise((res) => setTimeout(res, clickPostPop()));
226
+ await hideRing(page);
219
227
  return result;
220
228
  };
221
229
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.12.8",
3
+ "version": "0.12.9",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",