waygraph 0.12.39 → 0.12.41
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/cli.d.ts +1 -1
- package/dist/cli.js +152 -176
- package/dist/highlights.d.ts +3 -4
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +0 -2
- package/package.json +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +0 -2
package/dist/cli.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Primary verbs (less is more):
|
|
6
6
|
* auto Explore picker; `.flow.ts` / `--blocks From To` = run that path
|
|
7
|
-
* demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--fast` `--full` `--ff-expand` `--ff-disabled`
|
|
7
|
+
* demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--fast` `--full` `--mini` `--ff-expand` `--ff-disabled`
|
|
8
8
|
* run Execute; `--blocks` `--data` `--non-headless` `--video`
|
|
9
9
|
*
|
|
10
10
|
* Also: list / nav / validate / check / graph / init / agent-dive / traverse / try
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Primary verbs (less is more):
|
|
6
6
|
* auto Explore picker; `.flow.ts` / `--blocks From To` = run that path
|
|
7
|
-
* demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--fast` `--full` `--ff-expand` `--ff-disabled`
|
|
7
|
+
* demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--fast` `--full` `--mini` `--ff-expand` `--ff-disabled`
|
|
8
8
|
* run Execute; `--blocks` `--data` `--non-headless` `--video`
|
|
9
9
|
*
|
|
10
10
|
* Also: list / nav / validate / check / graph / init / agent-dive / traverse / try
|
|
@@ -808,59 +808,11 @@ async function installOverlay(page, title) {
|
|
|
808
808
|
const focus = !!opts.focus;
|
|
809
809
|
const color = opts.color || "";
|
|
810
810
|
const zoom = opts.zoom != null && opts.zoom > 1 ? opts.zoom : 1;
|
|
811
|
-
const followMouse = opts.followMouse !== false && zoom > 1;
|
|
812
811
|
const camMs = opts.camMs != null ? opts.camMs : 700;
|
|
813
|
-
let settled = zoom <= 1;
|
|
814
|
-
const settleAt = Date.now() + camMs;
|
|
815
812
|
const style = { size, weight, focus, color };
|
|
816
|
-
//
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
const readCursorViewport = () => {
|
|
820
|
-
const cursor = document.getElementById("wg-cursor");
|
|
821
|
-
if (cursor && cursor.style.opacity !== "0") {
|
|
822
|
-
const r = cursor.getBoundingClientRect();
|
|
823
|
-
if (r.width || r.height || r.left || r.top) {
|
|
824
|
-
return { x: r.left, y: r.top };
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
if (window.__wgCursorPos) return window.__wgCursorPos;
|
|
828
|
-
return null;
|
|
829
|
-
};
|
|
830
|
-
const followTick = () => {
|
|
831
|
-
if (!followMouse || !settled || window.__wgCameraLock) return;
|
|
832
|
-
if (!window.__wgSetCamera || !window.__wgViewportToDoc) return;
|
|
833
|
-
const cam = window.__wgCamera;
|
|
834
|
-
if (!cam || !(cam.scale > 1.001)) return;
|
|
835
|
-
// Skip while the demo cursor is mid CSS travel - reading a
|
|
836
|
-
// mid-lerp rect makes the camera chase noise and teleport.
|
|
837
|
-
if (window.__wgCursorTravelUntil && Date.now() < window.__wgCursorTravelUntil) {
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
const pos = readCursorViewport();
|
|
841
|
-
if (!pos) return;
|
|
842
|
-
const vw = window.innerWidth;
|
|
843
|
-
const vh = window.innerHeight;
|
|
844
|
-
const dx = pos.x - vw / 2;
|
|
845
|
-
const dy = pos.y - vh / 2;
|
|
846
|
-
const deadX = (vw * deadFrac) / 2;
|
|
847
|
-
const deadY = (vh * deadFrac) / 2;
|
|
848
|
-
let ox = 0;
|
|
849
|
-
let oy = 0;
|
|
850
|
-
if (dx > deadX) ox = dx - deadX;
|
|
851
|
-
else if (dx < -deadX) ox = dx + deadX;
|
|
852
|
-
if (dy > deadY) oy = dy - deadY;
|
|
853
|
-
else if (dy < -deadY) oy = dy + deadY;
|
|
854
|
-
if (ox === 0 && oy === 0) return;
|
|
855
|
-
const now = Date.now();
|
|
856
|
-
if (now - lastPanAt < 120) return;
|
|
857
|
-
lastPanAt = now;
|
|
858
|
-
// Partial gain - never yank the full overflow in one pan.
|
|
859
|
-
const gain = 0.35;
|
|
860
|
-
const nx = cam.cx + (ox / cam.scale) * gain;
|
|
861
|
-
const ny = cam.cy + (oy / cam.scale) * gain;
|
|
862
|
-
window.__wgSetCamera(nx, ny, cam.scale, 280);
|
|
863
|
-
};
|
|
813
|
+
// Auto-center only. Never chase the host mouse (that fights the
|
|
814
|
+
// camera). Playwright's real mouse is driven separately by the CLI.
|
|
815
|
+
window.__wgIgnoreHostMouse = true;
|
|
864
816
|
const tick = () => {
|
|
865
817
|
const el = document.querySelector(sel);
|
|
866
818
|
if (el && window.__wgPositionRing) {
|
|
@@ -872,8 +824,6 @@ async function installOverlay(page, title) {
|
|
|
872
824
|
style,
|
|
873
825
|
);
|
|
874
826
|
}
|
|
875
|
-
if (!settled && Date.now() >= settleAt) settled = true;
|
|
876
|
-
followTick();
|
|
877
827
|
window.__wgRingRaf = requestAnimationFrame(tick);
|
|
878
828
|
};
|
|
879
829
|
if (zoom > 1 && window.__wgSetCamera) {
|
|
@@ -882,41 +832,25 @@ async function installOverlay(page, title) {
|
|
|
882
832
|
const already =
|
|
883
833
|
window.__wgCamera && window.__wgCamera.scale > 1.001;
|
|
884
834
|
const dur = already ? Math.min(camMs, 420) : camMs;
|
|
885
|
-
// Focus must be document-space, not raw getBoundingClientRect
|
|
886
|
-
// (viewport) - otherwise already-zoomed pans fight the cursor.
|
|
887
835
|
const pt = window.__wgElDocPoint
|
|
888
836
|
? window.__wgElDocPoint(el0)
|
|
889
837
|
: {
|
|
890
838
|
x: el0.getBoundingClientRect().left + el0.getBoundingClientRect().width / 2,
|
|
891
839
|
y: el0.getBoundingClientRect().top + el0.getBoundingClientRect().height / 2,
|
|
892
840
|
};
|
|
841
|
+
// Lock for the whole ring - host mouse cannot steer.
|
|
893
842
|
window.__wgCameraLock = true;
|
|
894
843
|
window.__wgSetCamera(pt.x, pt.y, zoom, dur);
|
|
895
|
-
// Unlock for dead-zone follow only when explicitly allowed.
|
|
896
|
-
// zoomOut:false / cart-edge targets stay locked through dwell+click.
|
|
897
|
-
if (followMouse && opts.allowFollow) {
|
|
898
|
-
setTimeout(() => {
|
|
899
|
-
window.__wgCameraLock = false;
|
|
900
|
-
}, dur + 40);
|
|
901
|
-
}
|
|
902
844
|
}
|
|
903
845
|
}
|
|
904
846
|
window.__wgRingRaf = requestAnimationFrame(tick);
|
|
905
|
-
//
|
|
906
|
-
|
|
907
|
-
window.__wgCursorPos = { x: ev.clientX, y: ev.clientY };
|
|
908
|
-
};
|
|
909
|
-
window.addEventListener("mousemove", onMove, { passive: true });
|
|
910
|
-
window.__wgRingTrack = onMove;
|
|
847
|
+
// Do NOT listen to host mousemove - it was steering the camera.
|
|
848
|
+
window.__wgRingTrack = null;
|
|
911
849
|
window.__wgStopHighlightTrack = () => {
|
|
912
850
|
if (window.__wgRingRaf) {
|
|
913
851
|
cancelAnimationFrame(window.__wgRingRaf);
|
|
914
852
|
window.__wgRingRaf = null;
|
|
915
853
|
}
|
|
916
|
-
if (window.__wgRingTrack) {
|
|
917
|
-
window.removeEventListener("mousemove", window.__wgRingTrack);
|
|
918
|
-
window.__wgRingTrack = null;
|
|
919
|
-
}
|
|
920
854
|
window.__wgStopHighlightTrack = null;
|
|
921
855
|
};
|
|
922
856
|
};
|
|
@@ -1113,8 +1047,9 @@ async function installOverlay(page, title) {
|
|
|
1113
1047
|
}
|
|
1114
1048
|
if (iconLink.href !== favicon) iconLink.href = favicon;
|
|
1115
1049
|
// Hide/Show for #wg-panel - call after every panel.innerHTML refresh.
|
|
1116
|
-
// Collapsed title shows "N / M · block" (compact progress).
|
|
1117
|
-
//
|
|
1050
|
+
// Collapsed title shows "N / M · block" (compact progress).
|
|
1051
|
+
// forceCollapsed:true = force mini (--mini / --video). Omit otherwise so
|
|
1052
|
+
// the user's Hide preference (localStorage) wins. Never pass false.
|
|
1118
1053
|
window.__wgWirePanelChrome = (panel, storageKey, chromeTitle, opts) => {
|
|
1119
1054
|
if (!panel) return;
|
|
1120
1055
|
opts = opts || {};
|
|
@@ -1161,8 +1096,6 @@ async function installOverlay(page, title) {
|
|
|
1161
1096
|
let hidden = false;
|
|
1162
1097
|
if (opts.forceCollapsed === true) {
|
|
1163
1098
|
hidden = true;
|
|
1164
|
-
} else if (opts.forceCollapsed === false) {
|
|
1165
|
-
hidden = false;
|
|
1166
1099
|
} else {
|
|
1167
1100
|
try {
|
|
1168
1101
|
hidden = localStorage.getItem(storageKey) === "1";
|
|
@@ -1170,6 +1103,7 @@ async function installOverlay(page, title) {
|
|
|
1170
1103
|
/* ignore */
|
|
1171
1104
|
}
|
|
1172
1105
|
}
|
|
1106
|
+
// force mini does not overwrite Hide preference in storage.
|
|
1173
1107
|
apply(hidden, opts.forceCollapsed === true ? false : true);
|
|
1174
1108
|
const toggle = panel.querySelector("[data-wg-toggle]");
|
|
1175
1109
|
if (toggle && !toggle.dataset.wgWired) {
|
|
@@ -1211,6 +1145,8 @@ async function renderBeforeStep(page, info) {
|
|
|
1211
1145
|
if (window.__wgHideCursor) window.__wgHideCursor();
|
|
1212
1146
|
})
|
|
1213
1147
|
.catch(() => {});
|
|
1148
|
+
// miniStepLabel is Node-only — compute before evaluate (browser has no closure).
|
|
1149
|
+
const payload = { ...info, stepLabel: miniStepLabel(info) };
|
|
1214
1150
|
await page
|
|
1215
1151
|
.evaluate((info) => {
|
|
1216
1152
|
// Reused in place, not removed + recreated, every step - the old
|
|
@@ -1343,8 +1279,8 @@ async function renderBeforeStep(page, info) {
|
|
|
1343
1279
|
}
|
|
1344
1280
|
if (window.__wgWirePanelChrome) {
|
|
1345
1281
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1346
|
-
stepLabel:
|
|
1347
|
-
forceCollapsed: info.forceCollapsed === true ? true :
|
|
1282
|
+
stepLabel: info.stepLabel || "",
|
|
1283
|
+
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1348
1284
|
});
|
|
1349
1285
|
}
|
|
1350
1286
|
// Live, human-readable preview of what a MemKey's raw JSON will
|
|
@@ -1416,7 +1352,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1416
1352
|
if (auto) auto.style.display = cb.checked ? "" : "none";
|
|
1417
1353
|
});
|
|
1418
1354
|
}
|
|
1419
|
-
},
|
|
1355
|
+
}, payload)
|
|
1420
1356
|
.catch(() => {});
|
|
1421
1357
|
}
|
|
1422
1358
|
|
|
@@ -1653,18 +1589,10 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
|
1653
1589
|
}
|
|
1654
1590
|
const z = h.zoom != null && Number(h.zoom) > 1 ? Number(h.zoom) : 1;
|
|
1655
1591
|
const camMs = 700;
|
|
1656
|
-
|
|
1657
|
-
h.followMouse !== undefined
|
|
1658
|
-
? !!h.followMouse
|
|
1659
|
-
: h.follow !== undefined
|
|
1660
|
-
? !!h.follow
|
|
1661
|
-
: z > 1;
|
|
1662
|
-
// Track/zoom first, then park the demo cursor on the LIVE rect.
|
|
1663
|
-
// Placing the cursor on a pre-zoom boundingBox leaves it stranded after
|
|
1664
|
-
// body scale (html cursor is outside the transform).
|
|
1592
|
+
// Camera auto-centers on the ring target. Host mouse never steers.
|
|
1665
1593
|
await page
|
|
1666
1594
|
.evaluate(
|
|
1667
|
-
({ sel, label, tone, size, weight, focus, color, zoom,
|
|
1595
|
+
({ sel, label, tone, size, weight, focus, color, zoom, camMs }) => {
|
|
1668
1596
|
if (window.__wgStartHighlightTrack) {
|
|
1669
1597
|
window.__wgStartHighlightTrack({
|
|
1670
1598
|
sel,
|
|
@@ -1675,9 +1603,7 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
|
1675
1603
|
focus,
|
|
1676
1604
|
color,
|
|
1677
1605
|
zoom,
|
|
1678
|
-
followMouse,
|
|
1679
1606
|
camMs,
|
|
1680
|
-
allowFollow,
|
|
1681
1607
|
});
|
|
1682
1608
|
}
|
|
1683
1609
|
},
|
|
@@ -1690,11 +1616,7 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
|
1690
1616
|
focus: !!h.focus,
|
|
1691
1617
|
color: h.color || h.labelColor || h.textColor || "",
|
|
1692
1618
|
zoom: z,
|
|
1693
|
-
followMouse,
|
|
1694
1619
|
camMs,
|
|
1695
|
-
// Follow only while zoomed-out-between-rings is OK. zoomOut:false
|
|
1696
|
-
// means hold the frame for the upcoming click - no chase.
|
|
1697
|
-
allowFollow: followMouse && h.zoomOut !== false,
|
|
1698
1620
|
},
|
|
1699
1621
|
)
|
|
1700
1622
|
.catch(() => {});
|
|
@@ -1704,19 +1626,18 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
|
1704
1626
|
const waitMs = alreadyZoomed ? Math.min(camMs, 420) : camMs;
|
|
1705
1627
|
await new Promise((res) => setTimeout(res, waitMs));
|
|
1706
1628
|
if (h.cursor !== false) {
|
|
1707
|
-
|
|
1708
|
-
|
|
1629
|
+
// Park Playwright real mouse + demo cursor on the LIVE centered target.
|
|
1630
|
+
const live = await page
|
|
1631
|
+
.evaluate((sel) => {
|
|
1709
1632
|
const el = document.querySelector(sel);
|
|
1710
|
-
if (!el
|
|
1711
|
-
window.__wgCameraLock = true;
|
|
1633
|
+
if (!el) return null;
|
|
1712
1634
|
const r = el.getBoundingClientRect();
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
await new Promise((res) => setTimeout(res, Math.min(280, camMs)));
|
|
1635
|
+
return { x: r.left, y: r.top, width: r.width, height: r.height };
|
|
1636
|
+
}, h.selector)
|
|
1637
|
+
.catch(() => null);
|
|
1638
|
+
if (live) {
|
|
1639
|
+
await moveCursorTo(page, live, Math.min(280, camMs), { keepLock: true });
|
|
1640
|
+
}
|
|
1720
1641
|
}
|
|
1721
1642
|
// Appear settle
|
|
1722
1643
|
await new Promise((res) => setTimeout(res, fadeMs));
|
|
@@ -1777,7 +1698,9 @@ async function renderAfterStep(page, info) {
|
|
|
1777
1698
|
// 900ms / 200ms holds when set.
|
|
1778
1699
|
await cycleHighlightRings(page, highlights, gatesFast, {
|
|
1779
1700
|
pace: info.pace,
|
|
1701
|
+
stepLabel: miniStepLabel(info),
|
|
1780
1702
|
});
|
|
1703
|
+
const afterPayload = { ...info, stepLabel: miniStepLabel(info) };
|
|
1781
1704
|
await page
|
|
1782
1705
|
.evaluate((info) => {
|
|
1783
1706
|
// Reused in place - see renderBeforeStep's own comment on this.
|
|
@@ -1896,8 +1819,8 @@ async function renderAfterStep(page, info) {
|
|
|
1896
1819
|
}
|
|
1897
1820
|
if (window.__wgWirePanelChrome) {
|
|
1898
1821
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1899
|
-
stepLabel:
|
|
1900
|
-
forceCollapsed: info.forceCollapsed === true ? true :
|
|
1822
|
+
stepLabel: info.stepLabel || "",
|
|
1823
|
+
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1901
1824
|
});
|
|
1902
1825
|
}
|
|
1903
1826
|
panel.querySelectorAll(".wg-toggle-btn").forEach((btn) => {
|
|
@@ -1926,7 +1849,7 @@ async function renderAfterStep(page, info) {
|
|
|
1926
1849
|
if (auto) auto.style.display = cb.checked ? "" : "none";
|
|
1927
1850
|
});
|
|
1928
1851
|
}
|
|
1929
|
-
},
|
|
1852
|
+
}, afterPayload)
|
|
1930
1853
|
.catch(() => {});
|
|
1931
1854
|
}
|
|
1932
1855
|
|
|
@@ -1938,6 +1861,7 @@ async function renderAfterStep(page, info) {
|
|
|
1938
1861
|
*/
|
|
1939
1862
|
async function renderStepError(page, info) {
|
|
1940
1863
|
await installOverlay(page, info.title);
|
|
1864
|
+
const errorPayload = { ...info, stepLabel: miniStepLabel(info) };
|
|
1941
1865
|
await page
|
|
1942
1866
|
.evaluate((info) => {
|
|
1943
1867
|
// Reused in place - see renderBeforeStep's own comment on this.
|
|
@@ -1998,15 +1922,15 @@ async function renderStepError(page, info) {
|
|
|
1998
1922
|
}
|
|
1999
1923
|
if (window.__wgWirePanelChrome) {
|
|
2000
1924
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
2001
|
-
stepLabel:
|
|
2002
|
-
forceCollapsed: info.forceCollapsed === true ? true :
|
|
1925
|
+
stepLabel: info.stepLabel || "",
|
|
1926
|
+
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
2003
1927
|
});
|
|
2004
1928
|
}
|
|
2005
1929
|
const runBtn = document.getElementById("wg-run");
|
|
2006
1930
|
if (runBtn) runBtn.addEventListener("click", () => window.__wgNext({}));
|
|
2007
1931
|
const retryBtn = document.getElementById("wg-error-retry");
|
|
2008
1932
|
if (retryBtn) retryBtn.addEventListener("click", () => window.__wgNext({ __wgRetry: "1" }));
|
|
2009
|
-
},
|
|
1933
|
+
}, errorPayload)
|
|
2010
1934
|
.catch(() => {});
|
|
2011
1935
|
}
|
|
2012
1936
|
|
|
@@ -2249,7 +2173,9 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2249
2173
|
panel = document.createElement("div");
|
|
2250
2174
|
panel.id = "wg-panel";
|
|
2251
2175
|
}
|
|
2252
|
-
panel.classList.remove("wg-
|
|
2176
|
+
panel.classList.remove("wg-error", "wg-expected");
|
|
2177
|
+
// Do not clear wg-collapsed here - WirePanelChrome honors Hide (localStorage)
|
|
2178
|
+
// unless forceCollapsed:true (--mini / --video).
|
|
2253
2179
|
const esc = (t) =>
|
|
2254
2180
|
String(t).replace(/&/g, "&").replace(/</g, "<").replace(/"/g, """);
|
|
2255
2181
|
const episodeLine =
|
|
@@ -2328,7 +2254,8 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2328
2254
|
" / " +
|
|
2329
2255
|
info.total +
|
|
2330
2256
|
(info.blockName ? " \\u00b7 " + info.blockName : ""),
|
|
2331
|
-
|
|
2257
|
+
// Video / --mini stays on the mini stepper (Ep N · step · block pill).
|
|
2258
|
+
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
2332
2259
|
});
|
|
2333
2260
|
}
|
|
2334
2261
|
let autoNow = false;
|
|
@@ -2398,6 +2325,12 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2398
2325
|
paceBadge: formatDemoPaceBadge(demoPace, baseAutoplay),
|
|
2399
2326
|
paceLabel: formatDemoPaceLabel(demoPace, baseAutoplay),
|
|
2400
2327
|
paceKind: demoPaceKind(demoPace),
|
|
2328
|
+
forceCollapsed:
|
|
2329
|
+
!!process.env.WAYGRAPH_VIDEO ||
|
|
2330
|
+
process.env.WAYGRAPH_MINI === "1" ||
|
|
2331
|
+
process.env.WAYGRAPH_STEPPER_MINI === "1"
|
|
2332
|
+
? true
|
|
2333
|
+
: undefined,
|
|
2401
2334
|
},
|
|
2402
2335
|
);
|
|
2403
2336
|
const started = Date.now();
|
|
@@ -2489,19 +2422,27 @@ async function moveCursorTo(page, box, ms, opts) {
|
|
|
2489
2422
|
const y = box.y + box.height / 2;
|
|
2490
2423
|
const travel = Math.max(0, Number(ms) || 0);
|
|
2491
2424
|
const keepLock = !!(opts && opts.keepLock);
|
|
2492
|
-
//
|
|
2493
|
-
//
|
|
2425
|
+
// Camera stays locked; demo cursor + Playwright real mouse move together.
|
|
2426
|
+
// Host OS mouse is ignored (no mousemove camera steer).
|
|
2494
2427
|
await page
|
|
2495
2428
|
.evaluate(({ x, y, ms }) => {
|
|
2496
2429
|
window.__wgCameraLock = true;
|
|
2430
|
+
window.__wgIgnoreHostMouse = true;
|
|
2497
2431
|
if (window.__wgMoveCursorTo) window.__wgMoveCursorTo(x, y, ms);
|
|
2498
2432
|
}, { x, y, ms: travel })
|
|
2499
2433
|
.catch(() => {});
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2434
|
+
try {
|
|
2435
|
+
if (travel > 0) {
|
|
2436
|
+
const steps = Math.max(1, Math.min(30, Math.round(travel / 20)));
|
|
2437
|
+
await page.mouse.move(x, y, { steps });
|
|
2438
|
+
} else {
|
|
2439
|
+
await page.mouse.move(x, y);
|
|
2440
|
+
}
|
|
2441
|
+
} catch {
|
|
2442
|
+
/* headed / detached - demo cursor still moved */
|
|
2443
|
+
}
|
|
2503
2444
|
if (travel > 0) {
|
|
2504
|
-
await new Promise((res) => setTimeout(res, travel));
|
|
2445
|
+
await new Promise((res) => setTimeout(res, Math.min(80, travel)));
|
|
2505
2446
|
}
|
|
2506
2447
|
if (!keepLock) {
|
|
2507
2448
|
await page
|
|
@@ -2513,6 +2454,16 @@ async function moveCursorTo(page, box, ms, opts) {
|
|
|
2513
2454
|
return { x, y };
|
|
2514
2455
|
}
|
|
2515
2456
|
|
|
2457
|
+
/** Live viewport center of a locator (after camera transform) - no scroll. */
|
|
2458
|
+
async function liveCenterOf(locator) {
|
|
2459
|
+
return locator
|
|
2460
|
+
.evaluate((el) => {
|
|
2461
|
+
const r = el.getBoundingClientRect();
|
|
2462
|
+
return { x: r.left + r.width / 2, y: r.top + r.height / 2, w: r.width, h: r.height };
|
|
2463
|
+
})
|
|
2464
|
+
.catch(() => null);
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2516
2467
|
async function clickPulseAt(page, x, y, tone) {
|
|
2517
2468
|
await page
|
|
2518
2469
|
.evaluate(({ x, y, tone }) => {
|
|
@@ -2640,29 +2591,44 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2640
2591
|
let keptCameraLock = false;
|
|
2641
2592
|
try {
|
|
2642
2593
|
await installOverlay(page);
|
|
2643
|
-
// Wait until the target is visible BEFORE measuring - otherwise
|
|
2644
|
-
// defineNavBlock({ click }) (and any slow-to-appear control) skips
|
|
2645
|
-
// the cursor entirely: boundingBox was null, then originalClick
|
|
2646
|
-
// waited and clicked with no demo animation.
|
|
2647
2594
|
await this.waitFor({
|
|
2648
2595
|
state: "visible",
|
|
2649
2596
|
timeout: (options && options.timeout) || 30000,
|
|
2650
2597
|
}).catch(() => {});
|
|
2651
|
-
// Body-scale camera + Playwright scrollIntoView = teleport storm.
|
|
2652
|
-
// Lock the camera for the whole click when zoomed; force-click so
|
|
2653
|
-
// Playwright does not try to scroll the transformed page.
|
|
2654
2598
|
const zoomed = await page
|
|
2655
2599
|
.evaluate(() => !!(window.__wgCamera && window.__wgCamera.scale > 1.001))
|
|
2656
2600
|
.catch(() => false);
|
|
2657
2601
|
if (zoomed) {
|
|
2602
|
+
// Re-auto-center on this target, then lock - no host mouse, no scroll.
|
|
2658
2603
|
await page
|
|
2659
2604
|
.evaluate(() => {
|
|
2660
2605
|
window.__wgCameraLock = true;
|
|
2606
|
+
window.__wgIgnoreHostMouse = true;
|
|
2661
2607
|
})
|
|
2662
2608
|
.catch(() => {});
|
|
2663
2609
|
keptCameraLock = true;
|
|
2610
|
+
const handle = await this.elementHandle().catch(() => null);
|
|
2611
|
+
if (handle) {
|
|
2612
|
+
await page
|
|
2613
|
+
.evaluate(
|
|
2614
|
+
(el) => {
|
|
2615
|
+
if (!el || !window.__wgSetCamera || !window.__wgElDocPoint) return;
|
|
2616
|
+
const cam = window.__wgCamera || { scale: 1.4 };
|
|
2617
|
+
const pt = window.__wgElDocPoint(el);
|
|
2618
|
+
const s = cam.scale > 1.001 ? cam.scale : 1.4;
|
|
2619
|
+
window.__wgSetCamera(pt.x, pt.y, s, 280);
|
|
2620
|
+
},
|
|
2621
|
+
handle,
|
|
2622
|
+
)
|
|
2623
|
+
.catch(() => {});
|
|
2624
|
+
await handle.dispose().catch(() => {});
|
|
2625
|
+
await new Promise((res) => setTimeout(res, 300));
|
|
2626
|
+
}
|
|
2664
2627
|
}
|
|
2665
|
-
const
|
|
2628
|
+
const live = await liveCenterOf(this);
|
|
2629
|
+
const box = live
|
|
2630
|
+
? { x: live.x - live.w / 2, y: live.y - live.h / 2, width: live.w, height: live.h }
|
|
2631
|
+
: await this.boundingBox();
|
|
2666
2632
|
const narrated = box ? await wasJustNarrated(page) : false;
|
|
2667
2633
|
if (box && !narrated) {
|
|
2668
2634
|
let fallback = "click";
|
|
@@ -2670,10 +2636,8 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2670
2636
|
const text = (await this.textContent())?.trim();
|
|
2671
2637
|
if (text && text.length > 0 && text.length <= 30) fallback = text;
|
|
2672
2638
|
} catch {
|
|
2673
|
-
|
|
2639
|
+
/* ignore */
|
|
2674
2640
|
}
|
|
2675
|
-
// Prefer "nav: …" when this locator is a NavBlock click target
|
|
2676
|
-
// (set on page by runStepMode just before act).
|
|
2677
2641
|
try {
|
|
2678
2642
|
const navLabel = await page.evaluate(() => window.__wgPendingNavClickLabel || null);
|
|
2679
2643
|
if (navLabel) fallback = String(navLabel);
|
|
@@ -2683,13 +2647,13 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2683
2647
|
const stubs = (stubBeforeRef && stubBeforeRef.current) || [];
|
|
2684
2648
|
const cap = await captionForLocator(page, this, stubs, fallback);
|
|
2685
2649
|
clickTone = cap.tone;
|
|
2686
|
-
|
|
2687
|
-
const
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
const liveBox =
|
|
2650
|
+
const travel = zoomed ? cursorMs(200) : cursorMs(600);
|
|
2651
|
+
const again = (await liveCenterOf(this)) || live;
|
|
2652
|
+
const aim = again
|
|
2653
|
+
? { x: again.x - again.w / 2, y: again.y - again.h / 2, width: again.w, height: again.h }
|
|
2654
|
+
: box;
|
|
2655
|
+
clickPoint = await moveCursorTo(page, aim, travel, { keepLock: zoomed });
|
|
2656
|
+
const liveBox = aim;
|
|
2693
2657
|
clickPoint = {
|
|
2694
2658
|
x: liveBox.x + liveBox.width / 2,
|
|
2695
2659
|
y: liveBox.y + liveBox.height / 2,
|
|
@@ -2699,30 +2663,17 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2699
2663
|
weight: cap.weight,
|
|
2700
2664
|
});
|
|
2701
2665
|
await dwellMatchedStub(page, this, stubs, pacing);
|
|
2702
|
-
// "pop for a few seconds" - Dan's own phrase, matching the
|
|
2703
|
-
// zsign demo-engine's ring-before-click pattern in
|
|
2704
|
-
// services/help-center-clip-engine's video-pipeline.
|
|
2705
2666
|
await new Promise((res) => setTimeout(res, clickPrePop()));
|
|
2706
2667
|
} else if (box) {
|
|
2707
|
-
// Ring/caption already handled by narrate() - still move the
|
|
2708
|
-
// cursor + pulse the click point, just skip re-showing the ring.
|
|
2709
2668
|
const travel = zoomed ? cursorMs(120) : cursorMs(200);
|
|
2710
|
-
clickPoint = await moveCursorTo(page, box, travel, {
|
|
2711
|
-
keepLock: zoomed,
|
|
2712
|
-
});
|
|
2669
|
+
clickPoint = await moveCursorTo(page, box, travel, { keepLock: zoomed });
|
|
2713
2670
|
clickTone = "planned";
|
|
2714
2671
|
}
|
|
2715
2672
|
} catch {
|
|
2716
|
-
|
|
2673
|
+
/* best-effort */
|
|
2717
2674
|
}
|
|
2718
2675
|
if (clickPoint) {
|
|
2719
2676
|
await clickPulseAt(page, clickPoint.x, clickPoint.y, clickTone);
|
|
2720
|
-
// clickPulseAt only triggers the CSS animation class - it doesn't
|
|
2721
|
-
// wait for it. Without a pause here, the real click (and any
|
|
2722
|
-
// resulting navigation/DOM change) fires while the ripple is still
|
|
2723
|
-
// mid-animation, sometimes cutting it off before it's even visible.
|
|
2724
|
-
// Wait out the same .5s the "@keyframes wg-pulse" rule uses, so the
|
|
2725
|
-
// mock click visually completes before the real one fires.
|
|
2726
2677
|
await new Promise((res) => setTimeout(res, skipTheater() ? 0 : 500));
|
|
2727
2678
|
}
|
|
2728
2679
|
const zoomedClick = await page
|
|
@@ -2731,21 +2682,29 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2731
2682
|
try {
|
|
2732
2683
|
let result;
|
|
2733
2684
|
if (zoomedClick || keptCameraLock) {
|
|
2734
|
-
// Playwright
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
await handle.dispose().catch(() => {});
|
|
2685
|
+
// Real Playwright mouse at live visual center - never scrollIntoView.
|
|
2686
|
+
const pt =
|
|
2687
|
+
clickPoint ||
|
|
2688
|
+
(await liveCenterOf(this)) ||
|
|
2689
|
+
null;
|
|
2690
|
+
if (pt && Number.isFinite(pt.x) && Number.isFinite(pt.y)) {
|
|
2691
|
+
await page.mouse.click(pt.x, pt.y);
|
|
2742
2692
|
result = undefined;
|
|
2743
2693
|
} else {
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2694
|
+
const handle = await this.elementHandle().catch(() => null);
|
|
2695
|
+
if (handle) {
|
|
2696
|
+
await handle.evaluate((el) => {
|
|
2697
|
+
if (el && typeof el.click === "function") el.click();
|
|
2698
|
+
});
|
|
2699
|
+
await handle.dispose().catch(() => {});
|
|
2700
|
+
result = undefined;
|
|
2701
|
+
} else {
|
|
2702
|
+
result = await originalClick.call(this, {
|
|
2703
|
+
...(options || {}),
|
|
2704
|
+
force: true,
|
|
2705
|
+
timeout: (options && options.timeout) || 5000,
|
|
2706
|
+
});
|
|
2707
|
+
}
|
|
2749
2708
|
}
|
|
2750
2709
|
} else {
|
|
2751
2710
|
result = await originalClick.call(this, options);
|
|
@@ -3075,10 +3034,15 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3075
3034
|
const stubBeforeTodos = stubBeforePhase.todos || [];
|
|
3076
3035
|
const isNavBlock = r.block.__waygraphKind === "nav";
|
|
3077
3036
|
const autoNow = await currentAutoplay();
|
|
3078
|
-
// Video
|
|
3079
|
-
//
|
|
3080
|
-
//
|
|
3081
|
-
const forceCollapsed =
|
|
3037
|
+
// Video / --mini: compact "Ep N · step · block" pill only.
|
|
3038
|
+
// Otherwise omit (undefined) so Hide/localStorage is honored - never pass false.
|
|
3039
|
+
// WAYGRAPH_STEPPER_MINI=1 remains a compat env alias for WAYGRAPH_MINI.
|
|
3040
|
+
const forceCollapsed =
|
|
3041
|
+
!!process.env.WAYGRAPH_VIDEO ||
|
|
3042
|
+
process.env.WAYGRAPH_MINI === "1" ||
|
|
3043
|
+
process.env.WAYGRAPH_STEPPER_MINI === "1"
|
|
3044
|
+
? true
|
|
3045
|
+
: undefined;
|
|
3082
3046
|
const requires = r.block.requires ?? [];
|
|
3083
3047
|
const keys = requires.map((k) => {
|
|
3084
3048
|
let value = "<not yet set>";
|
|
@@ -3259,7 +3223,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3259
3223
|
episodeTitle: r.episodeTitle,
|
|
3260
3224
|
expectedFailureReason: r.expectedFailureReason,
|
|
3261
3225
|
stepperMode,
|
|
3262
|
-
forceCollapsed
|
|
3226
|
+
// Omit forceCollapsed on errors so Hide preference is not stomped.
|
|
3263
3227
|
});
|
|
3264
3228
|
const errEdits = await gate();
|
|
3265
3229
|
if (errEdits && errEdits.__wgRetry) {
|
|
@@ -3355,7 +3319,12 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3355
3319
|
episodeTitle: r.episodeTitle,
|
|
3356
3320
|
stepperMode,
|
|
3357
3321
|
todos: stubAfterTodos,
|
|
3358
|
-
forceCollapsed:
|
|
3322
|
+
forceCollapsed:
|
|
3323
|
+
!!process.env.WAYGRAPH_VIDEO ||
|
|
3324
|
+
process.env.WAYGRAPH_MINI === "1" ||
|
|
3325
|
+
process.env.WAYGRAPH_STEPPER_MINI === "1"
|
|
3326
|
+
? true
|
|
3327
|
+
: undefined,
|
|
3359
3328
|
});
|
|
3360
3329
|
await gate();
|
|
3361
3330
|
}
|
|
@@ -4370,6 +4339,9 @@ function parseRunFlags(argv) {
|
|
|
4370
4339
|
else if (a === "--full") {
|
|
4371
4340
|
out.fullStepper = true;
|
|
4372
4341
|
}
|
|
4342
|
+
else if (a === "--mini" || a === "--stepper-mini") {
|
|
4343
|
+
out.miniStepper = true;
|
|
4344
|
+
}
|
|
4373
4345
|
else if (a === "--ff-expand") {
|
|
4374
4346
|
out.ffExpand = true;
|
|
4375
4347
|
}
|
|
@@ -4490,8 +4462,8 @@ function applyRunFlags(flags, opts) {
|
|
|
4490
4462
|
process.env.WAYGRAPH_HEADED = "0";
|
|
4491
4463
|
}
|
|
4492
4464
|
}
|
|
4493
|
-
if ((flags.fast || flags.fullStepper) && !opts?.allowDemoUi) {
|
|
4494
|
-
console.error("waygraph: --fast / --full are demo-only flags");
|
|
4465
|
+
if ((flags.fast || flags.fullStepper || flags.miniStepper) && !opts?.allowDemoUi) {
|
|
4466
|
+
console.error("waygraph: --fast / --full / --mini are demo-only flags");
|
|
4495
4467
|
process.exit(1);
|
|
4496
4468
|
}
|
|
4497
4469
|
if (flags.step === true) {
|
|
@@ -4535,6 +4507,9 @@ function applyRunFlags(flags, opts) {
|
|
|
4535
4507
|
if (flags.fullStepper) {
|
|
4536
4508
|
process.env.WAYGRAPH_STEPPER = "full";
|
|
4537
4509
|
}
|
|
4510
|
+
if (flags.miniStepper) {
|
|
4511
|
+
process.env.WAYGRAPH_MINI = "1";
|
|
4512
|
+
}
|
|
4538
4513
|
if (flags.ffExpand) {
|
|
4539
4514
|
process.env.WAYGRAPH_FF_EXPAND = "1";
|
|
4540
4515
|
}
|
|
@@ -4699,6 +4674,7 @@ Primary (less is more):
|
|
|
4699
4674
|
--auto-next Auto-advance steps (alias: --autoplay)
|
|
4700
4675
|
--fast Shorter auto-next / Next gates (keeps smooth cursor)
|
|
4701
4676
|
--full Classic wrap-all block chips (default: carousel)
|
|
4677
|
+
--mini Force compact mini panel (Hide pill); alias --stepper-mini
|
|
4702
4678
|
--ff-expand Expand fastForwardComposeBlock inners as separate steps
|
|
4703
4679
|
--ff-disabled Dispute: expand FF (alias --no-ff); blitz kept on those inners
|
|
4704
4680
|
--auto-play-video Unattended + recorded: --auto-next + --video (+ step); headless by default
|
|
@@ -5093,7 +5069,7 @@ Agents shipped: waygraph-planner, waygraph-author, waygraph-healer.
|
|
|
5093
5069
|
console.error("waygraph demo: missing flow/spec — e.g.\n" +
|
|
5094
5070
|
" waygraph demo src/flows/shop.flow.ts\n" +
|
|
5095
5071
|
" waygraph demo --blocks shopFlow\n" +
|
|
5096
|
-
" Flags: --blocks --data --auto-next --fast --full --ff-expand --ff-disabled --auto-play-video --title --base-url --video");
|
|
5072
|
+
" Flags: --blocks --data --auto-next --fast --full --mini --ff-expand --ff-disabled --auto-play-video --title --base-url --video");
|
|
5097
5073
|
process.exit(1);
|
|
5098
5074
|
}
|
|
5099
5075
|
const proj = flags.blocks
|
package/dist/highlights.d.ts
CHANGED
|
@@ -97,12 +97,11 @@ export interface WaygraphHighlightStub extends FixtureDurationFields {
|
|
|
97
97
|
*/
|
|
98
98
|
focus?: boolean;
|
|
99
99
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* Alias: `follow`.
|
|
100
|
+
* Deprecated no-op. Camera auto-centers on the ring target; host mouse is
|
|
101
|
+
* ignored. Playwright's real mouse is driven to the centered target instead.
|
|
103
102
|
*/
|
|
104
103
|
followMouse?: boolean;
|
|
105
|
-
/** Alias for {@link WaygraphHighlightStub.followMouse}. */
|
|
104
|
+
/** Alias for {@link WaygraphHighlightStub.followMouse} (no-op). */
|
|
106
105
|
follow?: boolean;
|
|
107
106
|
/**
|
|
108
107
|
* Caption text color (CSS color). Overrides tone label color when set.
|
package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts
CHANGED
|
@@ -49,7 +49,6 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
49
49
|
color: "#c9a6ff",
|
|
50
50
|
zoom: 1.45,
|
|
51
51
|
zoomOut: false,
|
|
52
|
-
followMouse: true,
|
|
53
52
|
},
|
|
54
53
|
add: {
|
|
55
54
|
selector: InventorySel.addBtn(DEMO_PRODUCT_ID),
|
|
@@ -75,7 +74,6 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
75
74
|
color: "#86efac",
|
|
76
75
|
zoom: 1.6,
|
|
77
76
|
zoomOut: false,
|
|
78
|
-
followMouse: true,
|
|
79
77
|
duration: true,
|
|
80
78
|
});
|
|
81
79
|
},
|
package/package.json
CHANGED
package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts
CHANGED
|
@@ -49,7 +49,6 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
49
49
|
color: "#c9a6ff",
|
|
50
50
|
zoom: 1.45,
|
|
51
51
|
zoomOut: false,
|
|
52
|
-
followMouse: true,
|
|
53
52
|
},
|
|
54
53
|
add: {
|
|
55
54
|
selector: InventorySel.addBtn(DEMO_PRODUCT_ID),
|
|
@@ -75,7 +74,6 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
75
74
|
color: "#86efac",
|
|
76
75
|
zoom: 1.6,
|
|
77
76
|
zoomOut: false,
|
|
78
|
-
followMouse: true,
|
|
79
77
|
duration: true,
|
|
80
78
|
});
|
|
81
79
|
},
|