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.
- package/dist/auto-explore-run.js +1 -0
- package/dist/cli.js +52 -42
- package/dist/engine.d.ts +42 -0
- package/dist/engine.js +1 -1
- package/dist/step-overlay.d.ts +3 -3
- package/dist/step-overlay.js +60 -52
- package/package.json +1 -1
package/dist/auto-explore-run.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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.
|
|
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
|
package/dist/step-overlay.d.ts
CHANGED
|
@@ -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;
|
package/dist/step-overlay.js
CHANGED
|
@@ -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);
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
152
|
-
await showRing(
|
|
153
|
-
await new Promise((
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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(
|
|
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(
|
|
187
|
-
await this.waitFor({
|
|
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
|
|
193
|
-
if (
|
|
194
|
-
label =
|
|
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(
|
|
205
|
-
await showRing(
|
|
206
|
-
await new Promise((
|
|
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(
|
|
214
|
-
await new Promise((
|
|
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((
|
|
218
|
-
await hideRing(
|
|
225
|
+
await new Promise((res) => setTimeout(res, clickPostPop()));
|
|
226
|
+
await hideRing(page);
|
|
219
227
|
return result;
|
|
220
228
|
};
|
|
221
229
|
}
|