waygraph 0.11.2 → 0.12.5
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/README.md +6 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +192 -68
- package/dist/engine.d.ts +24 -2
- package/dist/engine.js +56 -2
- package/dist/highlights.d.ts +53 -6
- package/dist/highlights.js +65 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/trait.js +3 -1
- package/dist/types.d.ts +7 -0
- package/examples/saucedemo/package-lock.json +1 -1
- package/examples/saucedemo/package.json +1 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +6 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/ff-owner-auth.block.ts +17 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +18 -2
- package/examples/saucedemo/src/flows/checkout.flow.ts +27 -16
- package/examples/saucedemo/src/flows/stub-on-error.flow.ts +48 -0
- package/package.json +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +6 -0
- package/templates/quickstart/src/blocks/saucedemo-web/ff-owner-auth.block.ts +17 -0
- package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +16 -2
- package/templates/quickstart/src/flows/checkout.flow.ts +20 -18
package/README.md
CHANGED
|
@@ -214,6 +214,11 @@ overridable, not one giant Block with no per-step confirmation. `composedBlock.w
|
|
|
214
214
|
`modStepVerify` patch one step from outside, addressed the same three ways
|
|
215
215
|
`withBlockVerify`/`modBlockVerify` already are - a `composeBlock` result is a plain Block,
|
|
216
216
|
so it drops straight into `defineFlow([start, ..., composed, ..., end])` like any other.
|
|
217
|
+
`fastForwardComposeBlock(name, steps)` is the same shape marked for **demo pacing**: one
|
|
218
|
+
opaque step (no per-inner gate) unless `waygraph demo --ff-expand`. Saucedemo
|
|
219
|
+
`checkoutFlow` uses `ff-owner-auth` for the login prefix; `loginFlow` stays expanded for
|
|
220
|
+
Sign In narration. Traverse (later) reuses FF as a seed prefix - see
|
|
221
|
+
`docs/proposals/traverse-ffcompose-rfc.md`.
|
|
217
222
|
`spawnTab(entry, page, mem)` drives a genuinely separate second tab through its own
|
|
218
223
|
Block/Flow, in the same browser context an existing `page` already belongs to - the
|
|
219
224
|
pattern `observe()` could already reach for (it's the only phase allowed to touch
|
|
@@ -272,7 +277,7 @@ Mermaid) is `waygraph graph [project]` (`--mermaid`). Unattended JSON execution
|
|
|
272
277
|
| Run by export | `waygraph run --blocks shopFlow` |
|
|
273
278
|
| Manual watch | `waygraph demo src/flows/shop.flow.ts` |
|
|
274
279
|
| Auto-advance | `waygraph demo --blocks shopFlow --auto-next` (nav auto-hides strip) |
|
|
275
|
-
| Fast / full strip | `waygraph demo … --fast` · `--full` (classic chips; default = carousel) |
|
|
280
|
+
| Fast / full strip | `waygraph demo … --fast` (shorter gates; keeps cursor) · `--full` (classic chips; default = carousel) |
|
|
276
281
|
| QA watch + record | `waygraph demo --blocks shopFlow --auto-play-video` |
|
|
277
282
|
| Ad-hoc Blocks | `waygraph run --blocks "login then nav-cart" --data '{…}'` |
|
|
278
283
|
| Headed execute | `waygraph run --blocks shopFlow --non-headless --video` |
|
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`
|
|
7
|
+
* demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--fast` `--full` `--ff-expand`
|
|
8
8
|
* run Execute; `--blocks` `--data` `--non-headless` `--video`
|
|
9
9
|
*
|
|
10
10
|
* Also: list / nav / validate / check / graph / init / 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`
|
|
7
|
+
* demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--fast` `--full` `--ff-expand`
|
|
8
8
|
* run Execute; `--blocks` `--data` `--non-headless` `--video`
|
|
9
9
|
*
|
|
10
10
|
* Also: list / nav / validate / check / graph / init / try
|
|
@@ -203,8 +203,11 @@ import { tmpdir } from "node:os";
|
|
|
203
203
|
import {
|
|
204
204
|
resolveHighlightSlots,
|
|
205
205
|
resolveSlides,
|
|
206
|
+
resolveFixtureDwellMs,
|
|
207
|
+
resolveSlideDwellMs,
|
|
206
208
|
formatHighlightCaption,
|
|
207
209
|
hasAuthoredStubAfter,
|
|
210
|
+
hasAuthoredStubOnError,
|
|
208
211
|
} from "waygraph";
|
|
209
212
|
|
|
210
213
|
function walkDir(dir, pattern) {
|
|
@@ -1016,40 +1019,29 @@ async function renderBeforeStep(page, info) {
|
|
|
1016
1019
|
.catch(() => {});
|
|
1017
1020
|
}
|
|
1018
1021
|
|
|
1019
|
-
async function
|
|
1020
|
-
|
|
1021
|
-
//
|
|
1022
|
-
//
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
window.removeEventListener("resize", window.__wgRingTrack);
|
|
1027
|
-
window.removeEventListener("scroll", window.__wgRingTrack, true);
|
|
1028
|
-
window.__wgRingTrack = null;
|
|
1029
|
-
}
|
|
1030
|
-
})
|
|
1031
|
-
.catch(() => {});
|
|
1032
|
-
const highlights = info.highlights || [];
|
|
1033
|
-
// Cycle through EVERY declared/recovered highlight in order, each shown
|
|
1034
|
-
// long enough to actually register - "it highlights something [...] then
|
|
1035
|
-
// it highlights something [else] and next," not just the first one.
|
|
1036
|
-
for (let i = 0; i < highlights.length; i++) {
|
|
1037
|
-
const h = highlights[i];
|
|
1022
|
+
async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
1023
|
+
const list = highlights || [];
|
|
1024
|
+
// defaultHoldMs: when set (fail path), use instead of legacy 900/200 so BUG
|
|
1025
|
+
// rings stay visible ~2s before the error panel (PIA stubOnError RFC).
|
|
1026
|
+
const defaultHoldMs = opts && opts.defaultHoldMs != null ? opts.defaultHoldMs : null;
|
|
1027
|
+
for (let i = 0; i < list.length; i++) {
|
|
1028
|
+
const h = list[i];
|
|
1038
1029
|
try {
|
|
1039
1030
|
const box = await page.locator(h.selector).first().boundingBox();
|
|
1040
1031
|
if (box) {
|
|
1041
1032
|
await showRing(page, box, h.label);
|
|
1042
|
-
|
|
1033
|
+
const authored = resolveFixtureDwellMs(h, { gatesFast });
|
|
1034
|
+
const legacyMs = i === list.length - 1 ? 200 : 900;
|
|
1035
|
+
const holdMs =
|
|
1036
|
+
authored != null ? authored : defaultHoldMs != null ? defaultHoldMs : legacyMs;
|
|
1037
|
+
await new Promise((res) => setTimeout(res, holdMs));
|
|
1043
1038
|
}
|
|
1044
1039
|
} catch {
|
|
1045
1040
|
// best-effort - a selector that doesn't resolve just gets skipped
|
|
1046
1041
|
}
|
|
1047
1042
|
}
|
|
1048
|
-
if (
|
|
1049
|
-
|
|
1050
|
-
// the after-step panel - give THAT one live resize/scroll tracking,
|
|
1051
|
-
// same as before.
|
|
1052
|
-
const last = highlights[highlights.length - 1];
|
|
1043
|
+
if (list.length > 0) {
|
|
1044
|
+
const last = list[list.length - 1];
|
|
1053
1045
|
await page
|
|
1054
1046
|
.evaluate((h) => {
|
|
1055
1047
|
if (!window.__wgPositionRing) return;
|
|
@@ -1070,6 +1062,29 @@ async function renderAfterStep(page, info) {
|
|
|
1070
1062
|
} else {
|
|
1071
1063
|
await hideRing(page);
|
|
1072
1064
|
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
async function renderAfterStep(page, info) {
|
|
1068
|
+
await installOverlay(page, info.title);
|
|
1069
|
+
// Clear any tracker from a previous highlight before cycling through this
|
|
1070
|
+
// step's own.
|
|
1071
|
+
await page
|
|
1072
|
+
.evaluate(() => {
|
|
1073
|
+
if (window.__wgRingTrack) {
|
|
1074
|
+
window.removeEventListener("resize", window.__wgRingTrack);
|
|
1075
|
+
window.removeEventListener("scroll", window.__wgRingTrack, true);
|
|
1076
|
+
window.__wgRingTrack = null;
|
|
1077
|
+
}
|
|
1078
|
+
})
|
|
1079
|
+
.catch(() => {});
|
|
1080
|
+
const highlights = info.highlights || [];
|
|
1081
|
+
const gatesFast = !!info.gatesFast;
|
|
1082
|
+
// Cycle through EVERY declared/recovered highlight in order, each shown
|
|
1083
|
+
// long enough to actually register - "it highlights something [...] then
|
|
1084
|
+
// it highlights something [else] and next," not just the first one.
|
|
1085
|
+
// duration / fastMode on stubAfter (or flow fixtures) override the legacy
|
|
1086
|
+
// 900ms / 200ms holds when set.
|
|
1087
|
+
await cycleHighlightRings(page, highlights, gatesFast, null);
|
|
1073
1088
|
await page
|
|
1074
1089
|
.evaluate((info) => {
|
|
1075
1090
|
// Reused in place - see renderBeforeStep's own comment on this.
|
|
@@ -1390,10 +1405,10 @@ async function showRing(page, box, label) {
|
|
|
1390
1405
|
}
|
|
1391
1406
|
|
|
1392
1407
|
/** Match a locator to a stubBefore slot by overlapping bounding boxes. */
|
|
1393
|
-
async function
|
|
1394
|
-
if (!stubs || stubs.length === 0) return
|
|
1408
|
+
async function matchStubForLocator(page, locator, stubs) {
|
|
1409
|
+
if (!stubs || stubs.length === 0) return null;
|
|
1395
1410
|
const box = await locator.boundingBox().catch(() => null);
|
|
1396
|
-
if (!box) return
|
|
1411
|
+
if (!box) return null;
|
|
1397
1412
|
for (const s of stubs) {
|
|
1398
1413
|
try {
|
|
1399
1414
|
const b = await page.locator(s.selector).first().boundingBox();
|
|
@@ -1403,15 +1418,30 @@ async function captionForLocator(page, locator, stubs, fallback) {
|
|
|
1403
1418
|
Math.abs(b.y - box.y) < 4 &&
|
|
1404
1419
|
Math.abs(b.width - box.width) < 8
|
|
1405
1420
|
) {
|
|
1406
|
-
return
|
|
1421
|
+
return s;
|
|
1407
1422
|
}
|
|
1408
1423
|
} catch {
|
|
1409
1424
|
/* skip */
|
|
1410
1425
|
}
|
|
1411
1426
|
}
|
|
1427
|
+
return null;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
async function captionForLocator(page, locator, stubs, fallback) {
|
|
1431
|
+
const matched = await matchStubForLocator(page, locator, stubs);
|
|
1432
|
+
if (matched) return formatHighlightCaption(matched);
|
|
1412
1433
|
return fallback;
|
|
1413
1434
|
}
|
|
1414
1435
|
|
|
1436
|
+
async function dwellMatchedStub(page, locator, stubs, pacing) {
|
|
1437
|
+
const matched = await matchStubForLocator(page, locator, stubs);
|
|
1438
|
+
if (!matched) return;
|
|
1439
|
+
const ms = resolveFixtureDwellMs(matched, { gatesFast: !!(pacing && pacing.gatesFast) });
|
|
1440
|
+
if (ms != null && ms > 0) {
|
|
1441
|
+
await new Promise((res) => setTimeout(res, ms));
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1415
1445
|
/**
|
|
1416
1446
|
* Multi-step yap captions - not block lifecycle. Each slide waits for Next
|
|
1417
1447
|
* (or auto-next), optional ring via slide.selector.
|
|
@@ -1426,6 +1456,7 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
1426
1456
|
const blockName = (opts && opts.blockName) || "";
|
|
1427
1457
|
const episodeNumber = opts && opts.episodeNumber;
|
|
1428
1458
|
const episodeTitle = (opts && opts.episodeTitle) || "";
|
|
1459
|
+
const gatesFast = !!(opts && opts.fast);
|
|
1429
1460
|
const autoplayMs =
|
|
1430
1461
|
opts && opts.autoplayMs
|
|
1431
1462
|
? Number(opts.autoplayMs)
|
|
@@ -1436,6 +1467,7 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
1436
1467
|
const s = slides[i];
|
|
1437
1468
|
const caption = formatHighlightCaption(s);
|
|
1438
1469
|
const isLast = i === slides.length - 1;
|
|
1470
|
+
const dwellMs = resolveFixtureDwellMs(s, { gatesFast });
|
|
1439
1471
|
await installOverlay(page, title);
|
|
1440
1472
|
if (s.selector) {
|
|
1441
1473
|
try {
|
|
@@ -1489,12 +1521,19 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
1489
1521
|
esc(info.detail) +
|
|
1490
1522
|
"</div>"
|
|
1491
1523
|
: "") +
|
|
1524
|
+
(info.dwellMs != null
|
|
1525
|
+
? "<div class=\\"wg-auto\\" id=\\"wg-dwell-hint\\" style=\\"margin:0 0 8px\\">Hold " +
|
|
1526
|
+
(info.dwellMs / 1000).toFixed(1) +
|
|
1527
|
+
"s before next...</div>"
|
|
1528
|
+
: "") +
|
|
1492
1529
|
"<div class=\\"wg-autoplay-row\\"><label><input type=\\"checkbox\\" id=\\"wg-autoplay-cb\\"" +
|
|
1493
1530
|
(info.autoNow ? " checked" : "") +
|
|
1494
1531
|
"> Auto-advance</label></div>" +
|
|
1495
1532
|
"<div id=\\"wg-gate-manual\\"" +
|
|
1496
1533
|
(info.autoNow ? " style=\\"display:none\\"" : "") +
|
|
1497
|
-
"><button type=\\"button\\" id=\\"wg-run\\"
|
|
1534
|
+
"><button type=\\"button\\" id=\\"wg-run\\"" +
|
|
1535
|
+
(info.dwellMs != null ? " disabled" : "") +
|
|
1536
|
+
">" +
|
|
1498
1537
|
(info.isLast ? "Continue \\u25B6" : "Next slide \\u25B6") +
|
|
1499
1538
|
"</button></div>" +
|
|
1500
1539
|
"<div id=\\"wg-gate-auto\\" class=\\"wg-auto\\"" +
|
|
@@ -1544,15 +1583,28 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
1544
1583
|
const runBtn = document.getElementById("wg-run");
|
|
1545
1584
|
if (runBtn) {
|
|
1546
1585
|
runBtn.removeAttribute("data-wg-acked");
|
|
1547
|
-
runBtn.addEventListener(
|
|
1548
|
-
|
|
1549
|
-
(
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
);
|
|
1586
|
+
runBtn.addEventListener("click", (e) => {
|
|
1587
|
+
e.preventDefault();
|
|
1588
|
+
e.stopPropagation();
|
|
1589
|
+
if (runBtn.disabled) return;
|
|
1590
|
+
runBtn.setAttribute("data-wg-acked", "1");
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
if (info.dwellMs != null && info.dwellMs > 0) {
|
|
1594
|
+
const unlockAt = Date.now() + info.dwellMs;
|
|
1595
|
+
const tick = () => {
|
|
1596
|
+
const left = Math.max(0, unlockAt - Date.now());
|
|
1597
|
+
const hint = document.getElementById("wg-dwell-hint");
|
|
1598
|
+
const btn = document.getElementById("wg-run");
|
|
1599
|
+
if (left <= 0) {
|
|
1600
|
+
if (hint) hint.remove();
|
|
1601
|
+
if (btn) btn.disabled = false;
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
if (hint) hint.textContent = "Hold " + (left / 1000).toFixed(1) + "s before next...";
|
|
1605
|
+
setTimeout(tick, 100);
|
|
1606
|
+
};
|
|
1607
|
+
setTimeout(tick, 100);
|
|
1556
1608
|
}
|
|
1557
1609
|
},
|
|
1558
1610
|
{
|
|
@@ -1564,11 +1616,15 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
1564
1616
|
blockName,
|
|
1565
1617
|
isLast,
|
|
1566
1618
|
autoNow: false,
|
|
1619
|
+
dwellMs,
|
|
1567
1620
|
episodeNumber: episodeNumber !== undefined ? episodeNumber : null,
|
|
1568
1621
|
episodeTitle,
|
|
1569
1622
|
},
|
|
1570
1623
|
);
|
|
1571
1624
|
const started = Date.now();
|
|
1625
|
+
// When duration is set: floor both manual Next and auto-next to dwellMs.
|
|
1626
|
+
// When unset: legacy - auto uses autoplayMs, manual Next is immediate.
|
|
1627
|
+
const autoWaitMs = dwellMs != null ? dwellMs : autoplayMs;
|
|
1572
1628
|
for (;;) {
|
|
1573
1629
|
const acked = await page
|
|
1574
1630
|
.evaluate(() => {
|
|
@@ -1576,7 +1632,9 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
1576
1632
|
return !!(btn && btn.getAttribute("data-wg-acked") === "1");
|
|
1577
1633
|
})
|
|
1578
1634
|
.catch(() => false);
|
|
1579
|
-
if (acked)
|
|
1635
|
+
if (acked) {
|
|
1636
|
+
if (dwellMs == null || Date.now() - started >= dwellMs) break;
|
|
1637
|
+
}
|
|
1580
1638
|
const auto = await page
|
|
1581
1639
|
.evaluate(() => {
|
|
1582
1640
|
try {
|
|
@@ -1586,7 +1644,7 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
1586
1644
|
}
|
|
1587
1645
|
})
|
|
1588
1646
|
.catch(() => false);
|
|
1589
|
-
if (auto && Date.now() - started >=
|
|
1647
|
+
if (auto && Date.now() - started >= autoWaitMs) break;
|
|
1590
1648
|
await new Promise((res) => setTimeout(res, 100));
|
|
1591
1649
|
}
|
|
1592
1650
|
}
|
|
@@ -1678,20 +1736,19 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
1678
1736
|
// seconds. When slowMo is already doing the pacing, add none of our own;
|
|
1679
1737
|
// only fall back to a small typing delay when slowMo is off entirely.
|
|
1680
1738
|
//
|
|
1681
|
-
//
|
|
1682
|
-
//
|
|
1683
|
-
//
|
|
1684
|
-
// below are installed once on the shared prototype but invoked once per
|
|
1685
|
-
// real interaction, long after this closure was created.
|
|
1739
|
+
// pacing.skipTheater (WAYGRAPH_FAST_BLOCKS only): near-zero cursor / pop /
|
|
1740
|
+
// typing - blow past this block. --fast sets pacing.gatesFast instead
|
|
1741
|
+
// and must NOT land here, or demo looks like plain waygraph run.
|
|
1686
1742
|
// stubBeforeRef.current = resolved stubBefore slots for the active block.
|
|
1687
|
-
const
|
|
1743
|
+
const skipTheater = () => !!pacing.skipTheater;
|
|
1744
|
+
const typeDelay = () => (skipTheater() ? 0 : slowMo ? 0 : 30);
|
|
1688
1745
|
// Click is a single action, not per-character, so it doesn't compound
|
|
1689
1746
|
// the same way - but slowMo still adds its own pause around the actual
|
|
1690
1747
|
// click, so trim our own explicit "pop" pauses when it's already active
|
|
1691
1748
|
// rather than stacking a full 1.2s on top of that.
|
|
1692
|
-
const clickPrePop = () => (
|
|
1693
|
-
const clickPostPop = () => (
|
|
1694
|
-
const cursorMs = (full) => (
|
|
1749
|
+
const clickPrePop = () => (skipTheater() ? 0 : slowMo ? 300 : 700);
|
|
1750
|
+
const clickPostPop = () => (skipTheater() ? 0 : slowMo ? 200 : 500);
|
|
1751
|
+
const cursorMs = (full) => (skipTheater() ? Math.min(120, full) : full);
|
|
1695
1752
|
// Locator.fill()/click() only ever see a raw call, no context of where
|
|
1696
1753
|
// the value came from. Patching mem.get() to remember the most recently
|
|
1697
1754
|
// read key's name (Blocks read-then-immediately-fill, e.g. const { email
|
|
@@ -1727,7 +1784,8 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
1727
1784
|
const label = await captionForLocator(page, this, stubs, fallback);
|
|
1728
1785
|
await moveCursorTo(page, box, cursorMs(500));
|
|
1729
1786
|
await showRing(page, box, label);
|
|
1730
|
-
await
|
|
1787
|
+
await dwellMatchedStub(page, this, stubs, pacing);
|
|
1788
|
+
await new Promise((res) => setTimeout(res, skipTheater() ? 0 : 200));
|
|
1731
1789
|
} else if (box) {
|
|
1732
1790
|
// Ring/caption already handled by narrate() - still move the
|
|
1733
1791
|
// cursor there, just skip re-showing the ring with a generic label.
|
|
@@ -1796,6 +1854,7 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
1796
1854
|
const label = await captionForLocator(page, this, stubs, fallback);
|
|
1797
1855
|
clickPoint = await moveCursorTo(page, box, cursorMs(600));
|
|
1798
1856
|
await showRing(page, box, label);
|
|
1857
|
+
await dwellMatchedStub(page, this, stubs, pacing);
|
|
1799
1858
|
// "pop for a few seconds" - Dan's own phrase, matching the
|
|
1800
1859
|
// zsign demo-engine's ring-before-click pattern in
|
|
1801
1860
|
// services/help-center-clip-engine's video-pipeline.
|
|
@@ -1816,7 +1875,7 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
1816
1875
|
// mid-animation, sometimes cutting it off before it's even visible.
|
|
1817
1876
|
// Wait out the same .5s the "@keyframes wg-pulse" rule uses, so the
|
|
1818
1877
|
// mock click visually completes before the real one fires.
|
|
1819
|
-
await new Promise((res) => setTimeout(res,
|
|
1878
|
+
await new Promise((res) => setTimeout(res, skipTheater() ? 0 : 500));
|
|
1820
1879
|
}
|
|
1821
1880
|
const result = await originalClick.call(this, options);
|
|
1822
1881
|
await new Promise((res) => setTimeout(res, clickPostPop()));
|
|
@@ -1916,12 +1975,14 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
1916
1975
|
// WAYGRAPH_FAST_BLOCKS=login) can run through with none of the overlay's
|
|
1917
1976
|
// own added dwell while the rest of the chain keeps the full theatrical
|
|
1918
1977
|
// pace. Playwright's own slowMo is process-wide and untouched by this -
|
|
1919
|
-
// only OUR added pauses (ring pop, cursor travel, typing delay) shrink
|
|
1920
|
-
//
|
|
1921
|
-
//
|
|
1978
|
+
// only OUR added pauses (ring pop, cursor travel, typing delay) shrink
|
|
1979
|
+
// when skipTheater is on.
|
|
1980
|
+
// --fast / WAYGRAPH_DEMO_FAST: shorter auto-next / Next gates ONLY -
|
|
1981
|
+
// keeps smooth cursor travel (not akin to waygraph run).
|
|
1982
|
+
// WAYGRAPH_FAST_BLOCKS: named blocks still skip theater (old blow-past).
|
|
1922
1983
|
const demoFast = process.env.WAYGRAPH_DEMO_FAST === "1";
|
|
1923
1984
|
const stepperMode = process.env.WAYGRAPH_STEPPER === "full" ? "full" : "carousel";
|
|
1924
|
-
const pacing = {
|
|
1985
|
+
const pacing = { gatesFast: demoFast, skipTheater: false };
|
|
1925
1986
|
const stubBeforeRef = { current: [] };
|
|
1926
1987
|
instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBeforeRef);
|
|
1927
1988
|
// Force the panel checkbox from this process's flags/env at run start.
|
|
@@ -2008,7 +2069,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2008
2069
|
// gates too, not just skip its interaction dwell - otherwise autoplay
|
|
2009
2070
|
// still stalls the full autoplayMs admiring a step that intentionally
|
|
2010
2071
|
// ran too fast to watch.
|
|
2011
|
-
const ms =
|
|
2072
|
+
const ms =
|
|
2073
|
+
pacing.gatesFast || pacing.skipTheater ? Math.min(400, autoplayMs) : autoplayMs;
|
|
2012
2074
|
let elapsed = 0;
|
|
2013
2075
|
for (;;) {
|
|
2014
2076
|
// Re-read the checkbox EVERY loop tick, not once up front - a human
|
|
@@ -2091,7 +2153,10 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2091
2153
|
if ((clearSession || r.resetSession) && i > 0) {
|
|
2092
2154
|
await resetPageState(context, page, baseURL);
|
|
2093
2155
|
}
|
|
2094
|
-
|
|
2156
|
+
// gatesFast: --fast (whole run) or FFCompose opaque steps (one short gate).
|
|
2157
|
+
// skipTheater: only WAYGRAPH_FAST_BLOCKS - never --fast, never FF by default.
|
|
2158
|
+
pacing.gatesFast = demoFast || !!r.block.fastForward;
|
|
2159
|
+
pacing.skipTheater = fastBlockNames.has(r.block.name);
|
|
2095
2160
|
const fixtures = r.highlightFixtures;
|
|
2096
2161
|
stubBeforeRef.current = resolveHighlightSlots(r.block, "stubBefore", { fixtures });
|
|
2097
2162
|
const isNavBlock = r.block.__waygraphKind === "nav";
|
|
@@ -2170,6 +2235,31 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2170
2235
|
delete window.__wgPendingNavClickLabel;
|
|
2171
2236
|
})
|
|
2172
2237
|
.catch(() => {});
|
|
2238
|
+
// stubOnError: ring the broken UI BEFORE the error panel (PIA RFC).
|
|
2239
|
+
// stubAfter stays success-only - distinct BUG/FAIL copy on the fail path.
|
|
2240
|
+
const fixturesOnError = r.highlightFixtures;
|
|
2241
|
+
if (hasAuthoredStubOnError(r.block, fixturesOnError)) {
|
|
2242
|
+
await page
|
|
2243
|
+
.evaluate(() => {
|
|
2244
|
+
if (window.__wgRingTrack) {
|
|
2245
|
+
window.removeEventListener("resize", window.__wgRingTrack);
|
|
2246
|
+
window.removeEventListener("scroll", window.__wgRingTrack, true);
|
|
2247
|
+
window.__wgRingTrack = null;
|
|
2248
|
+
}
|
|
2249
|
+
})
|
|
2250
|
+
.catch(() => {});
|
|
2251
|
+
const errHighlights = resolveHighlightSlots(r.block, "stubOnError", {
|
|
2252
|
+
fixtures: fixturesOnError,
|
|
2253
|
+
}).map((h) => ({
|
|
2254
|
+
selector: h.selector,
|
|
2255
|
+
label: formatHighlightCaption(h),
|
|
2256
|
+
duration: h.duration,
|
|
2257
|
+
fastMode: h.fastMode,
|
|
2258
|
+
}));
|
|
2259
|
+
await cycleHighlightRings(page, errHighlights, !!pacing.gatesFast, {
|
|
2260
|
+
defaultHoldMs: 2000,
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2173
2263
|
// A thrown act()/observe()/a failed verify Trait used to just crash
|
|
2174
2264
|
// the whole Node process with a raw stack trace - the browser closes
|
|
2175
2265
|
// (main()'s own try/finally) before a human watching ever sees WHY.
|
|
@@ -2223,7 +2313,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2223
2313
|
await presentSlides(page, slides, gate, {
|
|
2224
2314
|
title,
|
|
2225
2315
|
blockName: r.block.name,
|
|
2226
|
-
fast: pacing.
|
|
2316
|
+
fast: pacing.gatesFast,
|
|
2227
2317
|
episodeNumber: r.episodeNumber,
|
|
2228
2318
|
episodeTitle: r.episodeTitle,
|
|
2229
2319
|
autoplayMs,
|
|
@@ -2235,6 +2325,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2235
2325
|
(h) => ({
|
|
2236
2326
|
selector: h.selector,
|
|
2237
2327
|
label: formatHighlightCaption(h),
|
|
2328
|
+
duration: h.duration,
|
|
2329
|
+
fastMode: h.fastMode,
|
|
2238
2330
|
}),
|
|
2239
2331
|
);
|
|
2240
2332
|
} else {
|
|
@@ -2247,6 +2339,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2247
2339
|
result,
|
|
2248
2340
|
resultTag: JSON.stringify(result),
|
|
2249
2341
|
highlights,
|
|
2342
|
+
gatesFast: pacing.gatesFast,
|
|
2250
2343
|
isLast: i === resolved.length - 1,
|
|
2251
2344
|
allNames: moduleNames,
|
|
2252
2345
|
allDescriptions: moduleDescriptions,
|
|
@@ -2362,7 +2455,7 @@ function parseVideoViewport(raw) {
|
|
|
2362
2455
|
async function main() {
|
|
2363
2456
|
const projectDir = process.argv[2];
|
|
2364
2457
|
const spec = process.argv[3];
|
|
2365
|
-
const { connect, MemPage, Engine, start, end, chainFlow } = await import("waygraph");
|
|
2458
|
+
const { connect, MemPage, Engine, start, end, chainFlow, isFastForwardBlock } = await import("waygraph");
|
|
2366
2459
|
const mem = new MemPage();
|
|
2367
2460
|
// Computed early - JSON-report mode's stdout is meant to be ONE parseable
|
|
2368
2461
|
// JSON object for whatever's reading it afterward (a script, an agent),
|
|
@@ -2371,6 +2464,27 @@ async function main() {
|
|
|
2371
2464
|
// truly unexpected failure outside runJsonReportMode's own try/catch)
|
|
2372
2465
|
// reaches stdout.
|
|
2373
2466
|
const jsonReport = process.env.WAYGRAPH_JSON === "1";
|
|
2467
|
+
const ffExpand = process.env.WAYGRAPH_FF_EXPAND === "1";
|
|
2468
|
+
/** Expand or keep fastForwardComposeBlock units for demo/run step lists. */
|
|
2469
|
+
const flattenBlockInfos = (blockInfos) => {
|
|
2470
|
+
const out = [];
|
|
2471
|
+
for (const bi of blockInfos) {
|
|
2472
|
+
const b = bi.block;
|
|
2473
|
+
if (ffExpand && isFastForwardBlock(b)) {
|
|
2474
|
+
for (const step of b.steps()) {
|
|
2475
|
+
out.push({
|
|
2476
|
+
name: step.name,
|
|
2477
|
+
block: step.block,
|
|
2478
|
+
...(step.routes ? { routes: step.routes } : {}),
|
|
2479
|
+
...(bi.resetSessionBefore ? { resetSessionBefore: true } : {}),
|
|
2480
|
+
});
|
|
2481
|
+
}
|
|
2482
|
+
} else {
|
|
2483
|
+
out.push(bi);
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
return out;
|
|
2487
|
+
};
|
|
2374
2488
|
let resolved = [];
|
|
2375
2489
|
let chainFlows = null;
|
|
2376
2490
|
// A bare identifier (no "(", no "then") might name an existing Flow
|
|
@@ -2381,7 +2495,7 @@ async function main() {
|
|
|
2381
2495
|
if (bareRef) {
|
|
2382
2496
|
const flow = await findFlow(projectDir, bareRef);
|
|
2383
2497
|
if (flow && typeof flow.blocks === "function") {
|
|
2384
|
-
const blockInfos = flow.blocks();
|
|
2498
|
+
const blockInfos = flattenBlockInfos(flow.blocks());
|
|
2385
2499
|
// Same --data / WAYGRAPH_DATA seeding as the multi-segment path.
|
|
2386
2500
|
// Without this, \`waygraph run shop.flow.ts --data '{...}'\` (and bare
|
|
2387
2501
|
// export names) hit preflight with an empty MemPage.
|
|
@@ -2466,14 +2580,16 @@ async function main() {
|
|
|
2466
2580
|
});
|
|
2467
2581
|
}
|
|
2468
2582
|
}
|
|
2469
|
-
const combinedBlocks = chainFlow(...flows).blocks();
|
|
2583
|
+
const combinedBlocks = flattenBlockInfos(chainFlow(...flows).blocks());
|
|
2470
2584
|
let fi = 0;
|
|
2471
|
-
|
|
2585
|
+
// Episode lengths must follow the same flatten so remainingInFlow stays aligned.
|
|
2586
|
+
const flowLengths = flows.map((f) => flattenBlockInfos(f.blocks()).length);
|
|
2587
|
+
let remainingInFlow = flowLengths[0];
|
|
2472
2588
|
let isFirstOfSegment = true;
|
|
2473
2589
|
resolved = combinedBlocks.map((bi) => {
|
|
2474
2590
|
while (remainingInFlow === 0) {
|
|
2475
2591
|
fi += 1;
|
|
2476
|
-
remainingInFlow =
|
|
2592
|
+
remainingInFlow = flowLengths[fi];
|
|
2477
2593
|
isFirstOfSegment = true;
|
|
2478
2594
|
}
|
|
2479
2595
|
const meta = flowMeta[fi];
|
|
@@ -2523,7 +2639,7 @@ async function main() {
|
|
|
2523
2639
|
// travel, typing delay, and that block's own gates) - "I want the login
|
|
2524
2640
|
// block to be faster than the rest of the demo," Dan's own phrase.
|
|
2525
2641
|
// Playwright's real slowMo is untouched; this only trims what waygraph
|
|
2526
|
-
// itself adds on top of it.
|
|
2642
|
+
// itself adds on top of it. Distinct from --fast (gates only).
|
|
2527
2643
|
const fastBlockNames = new Set(
|
|
2528
2644
|
(process.env.WAYGRAPH_FAST_BLOCKS || "")
|
|
2529
2645
|
.split(",")
|
|
@@ -3207,6 +3323,9 @@ function parseRunFlags(argv) {
|
|
|
3207
3323
|
else if (a === "--full") {
|
|
3208
3324
|
out.fullStepper = true;
|
|
3209
3325
|
}
|
|
3326
|
+
else if (a === "--ff-expand") {
|
|
3327
|
+
out.ffExpand = true;
|
|
3328
|
+
}
|
|
3210
3329
|
else if (a === "--non-headless") {
|
|
3211
3330
|
out.nonHeadless = true;
|
|
3212
3331
|
}
|
|
@@ -3365,6 +3484,9 @@ function applyRunFlags(flags, opts) {
|
|
|
3365
3484
|
if (flags.fullStepper) {
|
|
3366
3485
|
process.env.WAYGRAPH_STEPPER = "full";
|
|
3367
3486
|
}
|
|
3487
|
+
if (flags.ffExpand) {
|
|
3488
|
+
process.env.WAYGRAPH_FF_EXPAND = "1";
|
|
3489
|
+
}
|
|
3368
3490
|
}
|
|
3369
3491
|
/**
|
|
3370
3492
|
* BASE_URL when neither --base-url nor WAYGRAPH_BASE_URL is set:
|
|
@@ -3520,8 +3642,9 @@ Primary (less is more):
|
|
|
3520
3642
|
waygraph demo [--blocks <flow|file|spec>] Watch with step overlay (QA path)
|
|
3521
3643
|
--data '{...}' Mem seed JSON (or inline flow({...}))
|
|
3522
3644
|
--auto-next Auto-advance steps (alias: --autoplay)
|
|
3523
|
-
--fast
|
|
3645
|
+
--fast Shorter auto-next / Next gates (keeps smooth cursor)
|
|
3524
3646
|
--full Classic wrap-all block chips (default: carousel)
|
|
3647
|
+
--ff-expand Expand fastForwardComposeBlock inners as separate steps
|
|
3525
3648
|
--auto-play-video Unattended + recorded: --auto-next + --video (+ step); headless by default
|
|
3526
3649
|
--auto-play-video-head Same, but keep the browser visible (--non-headless)
|
|
3527
3650
|
--video-viewport WxH Recording size (default demo: 1920x1080)
|
|
@@ -3531,6 +3654,7 @@ Primary (less is more):
|
|
|
3531
3654
|
--non-headless Show browser
|
|
3532
3655
|
--video [dir] Record .webm
|
|
3533
3656
|
--video-viewport WxH Recording size (default run: 1280x720)
|
|
3657
|
+
--ff-expand Same as demo (expand FFCompose inners)
|
|
3534
3658
|
|
|
3535
3659
|
Also:
|
|
3536
3660
|
waygraph list | nav | validate | check | graph | init <name>
|
|
@@ -3678,7 +3802,7 @@ async function main() {
|
|
|
3678
3802
|
console.error("waygraph demo: missing flow/spec — e.g.\n" +
|
|
3679
3803
|
" waygraph demo src/flows/shop.flow.ts\n" +
|
|
3680
3804
|
" waygraph demo --blocks shopFlow\n" +
|
|
3681
|
-
" Flags: --blocks --data --auto-next --fast --full --auto-play-video --title --base-url --video");
|
|
3805
|
+
" Flags: --blocks --data --auto-next --fast --full --ff-expand --auto-play-video --title --base-url --video");
|
|
3682
3806
|
process.exit(1);
|
|
3683
3807
|
}
|
|
3684
3808
|
const proj = flags.blocks
|
package/dist/engine.d.ts
CHANGED
|
@@ -274,8 +274,8 @@ export declare function withTitle<Out extends Checkpoint<string>>(flow: Flow<Out
|
|
|
274
274
|
export declare function withExpectedFailure<Out extends Checkpoint<string>>(flow: Flow<Out>, reason: string): Flow<Out>;
|
|
275
275
|
/**
|
|
276
276
|
* Attaches demo highlight fixtures to a Flow - unlimited purposes on `.flow.ts`
|
|
277
|
-
* (AC copy, BUG/GATE tags, detail) merged onto each block's stubBefore/stubAfter
|
|
278
|
-
* slots. Non-destructive, same pattern as {@link withTitle}.
|
|
277
|
+
* (AC copy, BUG/GATE tags, detail) merged onto each block's stubBefore/stubAfter/
|
|
278
|
+
* stubOnError slots. Non-destructive, same pattern as {@link withTitle}.
|
|
279
279
|
* Demo/`chain --step` reads `flow.highlightFixtures`; `run()` ignores it.
|
|
280
280
|
* @example withHighlightFixtures(issueFlow, { "submit-login": { stubBefore: { email: { label: "AC-1" } } } })
|
|
281
281
|
*/
|
|
@@ -349,6 +349,25 @@ export declare function composeBlock<In extends Checkpoint<string>, B extends Ch
|
|
|
349
349
|
export declare function composeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, D>, Block<D, Out>]): ComposedBlock<In, Out>;
|
|
350
350
|
export declare function composeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, D>, Block<D, E>, Block<E, Out>]): ComposedBlock<In, Out>;
|
|
351
351
|
export declare function composeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, D>, Block<D, E>, Block<E, F>, Block<F, Out>]): ComposedBlock<In, Out>;
|
|
352
|
+
/**
|
|
353
|
+
* Same shape as {@link composeBlock}, marked {@link FastForwardComposedBlock.fastForward}
|
|
354
|
+
* so demo/run/traverse treat it as **one opaque step** (no per-inner gate/ring)
|
|
355
|
+
* unless `--ff-expand` / `WAYGRAPH_FF_EXPAND=1`. Inner acts still run for real;
|
|
356
|
+
* failures are prefixed `ff-name > …` so the locus names both the FF unit and
|
|
357
|
+
* the inner step. Prefer this for boring prefixes (auth, seed) on demo Flows.
|
|
358
|
+
* @example fastForwardComposeBlock("ff-owner-auth", [NavLoginBlock, SubmitLoginForFlow])
|
|
359
|
+
*/
|
|
360
|
+
export interface FastForwardComposedBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>> extends ComposedBlock<In, Out> {
|
|
361
|
+
readonly fastForward: true;
|
|
362
|
+
}
|
|
363
|
+
/** True when `block` came from {@link fastForwardComposeBlock}. */
|
|
364
|
+
export declare function isFastForwardBlock(block: Block<any, any>): block is FastForwardComposedBlock<any, any>;
|
|
365
|
+
export declare function fastForwardComposeBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, Out>]): FastForwardComposedBlock<In, Out>;
|
|
366
|
+
export declare function fastForwardComposeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, Out>]): FastForwardComposedBlock<In, Out>;
|
|
367
|
+
export declare function fastForwardComposeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, Out>]): FastForwardComposedBlock<In, Out>;
|
|
368
|
+
export declare function fastForwardComposeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, D>, Block<D, Out>]): FastForwardComposedBlock<In, Out>;
|
|
369
|
+
export declare function fastForwardComposeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, D>, Block<D, E>, Block<E, Out>]): FastForwardComposedBlock<In, Out>;
|
|
370
|
+
export declare function fastForwardComposeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, D>, Block<D, E>, Block<E, F>, Block<F, Out>]): FastForwardComposedBlock<In, Out>;
|
|
352
371
|
/** Configures how an Engine launches its own browser - only used by `Flow.run(mem)` (no context given); ignored by `Flow.run(context, mem)`, since that context is already launched. */
|
|
353
372
|
export interface EngineConfig {
|
|
354
373
|
/** Default `true`, matching Playwright's own default. */
|
|
@@ -509,6 +528,7 @@ export type NavBlockOptions<Out extends Checkpoint<string>> = {
|
|
|
509
528
|
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
510
529
|
stubBefore?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
511
530
|
stubAfter?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
531
|
+
stubOnError?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
512
532
|
/** @deprecated Prefer stubAfter. */
|
|
513
533
|
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
514
534
|
/**
|
|
@@ -572,6 +592,7 @@ export type NavClickBlockOptions<Out extends Checkpoint<string>> = {
|
|
|
572
592
|
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
573
593
|
stubBefore?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
574
594
|
stubAfter?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
595
|
+
stubOnError?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
575
596
|
/** @deprecated Prefer stubAfter. */
|
|
576
597
|
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
577
598
|
instanceOptions?: Block<Checkpoint<string>, Out>["instanceOptions"];
|
|
@@ -603,6 +624,7 @@ export type PageBlockOptions<Out extends Checkpoint<string>> = {
|
|
|
603
624
|
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
604
625
|
stubBefore?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
605
626
|
stubAfter?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
627
|
+
stubOnError?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
|
|
606
628
|
/** @deprecated Prefer stubAfter. */
|
|
607
629
|
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
608
630
|
requires?: readonly MemKey<any>[];
|