waygraph 0.12.22 → 0.12.24
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.js +219 -39
- package/dist/engine.d.ts +9 -9
- package/dist/highlights.d.ts +111 -8
- package/dist/highlights.js +227 -38
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/types.d.ts +22 -10
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -207,6 +207,7 @@ import { tmpdir } from "node:os";
|
|
|
207
207
|
import {
|
|
208
208
|
resolveHighlightSlots,
|
|
209
209
|
resolveSlides,
|
|
210
|
+
runStubPhase,
|
|
210
211
|
resolveFixtureDwellMs,
|
|
211
212
|
resolveSlideDwellMs,
|
|
212
213
|
formatHighlightCaption,
|
|
@@ -224,6 +225,7 @@ import {
|
|
|
224
225
|
demoPaceIsSlow,
|
|
225
226
|
formatDemoPaceBadge,
|
|
226
227
|
formatDemoPaceLabel,
|
|
228
|
+
normalizeTodos,
|
|
227
229
|
} from "waygraph";
|
|
228
230
|
|
|
229
231
|
function walkDir(dir, pattern) {
|
|
@@ -507,6 +509,14 @@ const RING_CSS =
|
|
|
507
509
|
"#wg-panel .wg-pace[data-pace-kind=blitz] .wg-pace-badge{background:#22C55E;color:#052e16;}" +
|
|
508
510
|
"#wg-panel .wg-pace[data-pace-kind=ms] .wg-pace-badge{background:#3B82F6;color:#fff;}" +
|
|
509
511
|
"#wg-panel .wg-narration{margin:0 0 12px;font:italic 14px/1.4 system-ui,sans-serif;color:#f0e8ff;}" +
|
|
512
|
+
"#wg-todos{list-style:none;margin:0 0 12px;padding:8px 10px;background:#0f0620;border-radius:8px;" +
|
|
513
|
+
"border:1px solid #3a2a60;}" +
|
|
514
|
+
"#wg-todos li{display:flex;gap:8px;align-items:flex-start;margin:0 0 6px;font:600 12.5px/1.35 system-ui,sans-serif;}" +
|
|
515
|
+
"#wg-todos li:last-child{margin-bottom:0;}" +
|
|
516
|
+
"#wg-todos .wg-todo-mark{flex:0 0 auto;width:1.1em;text-align:center;}" +
|
|
517
|
+
"#wg-todos .wg-todo-done{color:#6ee7b7;text-decoration:line-through;opacity:.85;}" +
|
|
518
|
+
"#wg-todos .wg-todo-current{color:#fff;}" +
|
|
519
|
+
"#wg-todos .wg-todo-pending{color:#9a7ad1;}" +
|
|
510
520
|
"#wg-progress{height:4px;background:#2a1650;border-radius:2px;margin:0 0 12px;overflow:hidden;}" +
|
|
511
521
|
"#wg-progress-bar{height:100%;background:#7C3AED;border-radius:2px;transition:width .3s ease;}" +
|
|
512
522
|
// A real tab bar for episodes - the currently-active episode reads as
|
|
@@ -767,6 +777,31 @@ async function installOverlay(page, title) {
|
|
|
767
777
|
const ringLabel = document.getElementById("wg-ring-label");
|
|
768
778
|
if (ring) ring.style.opacity = "0";
|
|
769
779
|
if (ringLabel) ringLabel.style.opacity = "0";
|
|
780
|
+
if (window.__wgClearZoom) window.__wgClearZoom();
|
|
781
|
+
};
|
|
782
|
+
window.__wgClearZoom = () => {
|
|
783
|
+
document.querySelectorAll("[data-wg-zoomed=\\"1\\"]").forEach((el) => {
|
|
784
|
+
el.style.removeProperty("transform");
|
|
785
|
+
el.style.removeProperty("transform-origin");
|
|
786
|
+
el.style.removeProperty("transition");
|
|
787
|
+
el.style.removeProperty("z-index");
|
|
788
|
+
el.style.removeProperty("position");
|
|
789
|
+
delete el.dataset.wgZoomed;
|
|
790
|
+
});
|
|
791
|
+
};
|
|
792
|
+
window.__wgApplyZoom = (sel, scale) => {
|
|
793
|
+
if (window.__wgClearZoom) window.__wgClearZoom();
|
|
794
|
+
const n = Number(scale);
|
|
795
|
+
if (!Number.isFinite(n) || n <= 1.001) return;
|
|
796
|
+
const el = document.querySelector(sel);
|
|
797
|
+
if (!el) return;
|
|
798
|
+
const st = getComputedStyle(el);
|
|
799
|
+
if (st.position === "static") el.style.position = "relative";
|
|
800
|
+
el.style.zIndex = "2147483645";
|
|
801
|
+
el.style.transition = "transform .35s ease";
|
|
802
|
+
el.style.transformOrigin = "center center";
|
|
803
|
+
el.style.transform = "scale(" + n + ")";
|
|
804
|
+
el.dataset.wgZoomed = "1";
|
|
770
805
|
};
|
|
771
806
|
const POSITIONS = ["left", "center", "right"];
|
|
772
807
|
const applyPos = (el, pos) => {
|
|
@@ -988,7 +1023,8 @@ async function renderBeforeStep(page, info) {
|
|
|
988
1023
|
paceHtml +
|
|
989
1024
|
"<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
|
|
990
1025
|
"<h3>Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + "</h3>" +
|
|
991
|
-
narrationHtml
|
|
1026
|
+
narrationHtml +
|
|
1027
|
+
todosHtmlFromInfo(info, esc);
|
|
992
1028
|
if (info.keys.length === 0) {
|
|
993
1029
|
html += "<div class=\\"wg-key\\">(no MemKeys required)</div>";
|
|
994
1030
|
}
|
|
@@ -1021,12 +1057,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1021
1057
|
}
|
|
1022
1058
|
if (window.__wgWirePanelChrome) {
|
|
1023
1059
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1024
|
-
stepLabel:
|
|
1025
|
-
(info.index + 1) +
|
|
1026
|
-
" / " +
|
|
1027
|
-
info.total +
|
|
1028
|
-
(info.blockName ? " · " + info.blockName : "") +
|
|
1029
|
-
(info.paceBadge ? " · " + info.paceBadge : ""),
|
|
1060
|
+
stepLabel: miniStepLabel(info),
|
|
1030
1061
|
forceCollapsed: info.forceCollapsed === true ? true : info.forceCollapsed === false ? false : undefined,
|
|
1031
1062
|
});
|
|
1032
1063
|
}
|
|
@@ -1103,6 +1134,102 @@ async function renderBeforeStep(page, info) {
|
|
|
1103
1134
|
.catch(() => {});
|
|
1104
1135
|
}
|
|
1105
1136
|
|
|
1137
|
+
async function ensureSelectorInView(page, selector) {
|
|
1138
|
+
try {
|
|
1139
|
+
const loc = page.locator(selector).first();
|
|
1140
|
+
await loc.scrollIntoViewIfNeeded().catch(() => {});
|
|
1141
|
+
await page
|
|
1142
|
+
.evaluate((sel) => {
|
|
1143
|
+
const el = document.querySelector(sel);
|
|
1144
|
+
if (!el) return;
|
|
1145
|
+
try {
|
|
1146
|
+
el.scrollIntoView({ block: "center", inline: "center", behavior: "instant" });
|
|
1147
|
+
} catch {
|
|
1148
|
+
try {
|
|
1149
|
+
el.scrollIntoView(true);
|
|
1150
|
+
} catch {
|
|
1151
|
+
/* ignore */
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
let p = el.parentElement;
|
|
1155
|
+
while (p && p !== document.documentElement && p !== document.body) {
|
|
1156
|
+
const st = getComputedStyle(p);
|
|
1157
|
+
const ox = st.overflowX;
|
|
1158
|
+
const oy = st.overflowY;
|
|
1159
|
+
if (/(auto|scroll|overlay)/.test(ox) || /(auto|scroll|overlay)/.test(oy)) {
|
|
1160
|
+
const er = el.getBoundingClientRect();
|
|
1161
|
+
const pr = p.getBoundingClientRect();
|
|
1162
|
+
if (er.left < pr.left || er.right > pr.right || er.top < pr.top || er.bottom > pr.bottom) {
|
|
1163
|
+
p.scrollLeft += er.left + er.width / 2 - (pr.left + pr.width / 2);
|
|
1164
|
+
p.scrollTop += er.top + er.height / 2 - (pr.top + pr.height / 2);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
p = p.parentElement;
|
|
1168
|
+
}
|
|
1169
|
+
}, selector)
|
|
1170
|
+
.catch(() => {});
|
|
1171
|
+
} catch {
|
|
1172
|
+
/* best-effort */
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
async function applyHighlightZoom(page, selector, zoom) {
|
|
1177
|
+
const z = Number(zoom);
|
|
1178
|
+
if (!Number.isFinite(z) || z <= 1.001) {
|
|
1179
|
+
await page.evaluate(() => {
|
|
1180
|
+
if (window.__wgClearZoom) window.__wgClearZoom();
|
|
1181
|
+
}).catch(() => {});
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
await page
|
|
1185
|
+
.evaluate(
|
|
1186
|
+
({ sel, scale }) => {
|
|
1187
|
+
if (window.__wgApplyZoom) window.__wgApplyZoom(sel, scale);
|
|
1188
|
+
},
|
|
1189
|
+
{ sel: selector, scale: z },
|
|
1190
|
+
)
|
|
1191
|
+
.catch(() => {});
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
function todosHtmlFromInfo(info, escFn) {
|
|
1195
|
+
const list = info.todos || [];
|
|
1196
|
+
if (!list.length) return "";
|
|
1197
|
+
return (
|
|
1198
|
+
"<ul id=\\"wg-todos\\">" +
|
|
1199
|
+
list
|
|
1200
|
+
.map((t) => {
|
|
1201
|
+
const cls = t.current ? "wg-todo-current" : t.done ? "wg-todo-done" : "wg-todo-pending";
|
|
1202
|
+
const mark = t.done ? "\\u2713" : t.current ? "\\u2192" : "\\u25CB";
|
|
1203
|
+
return (
|
|
1204
|
+
"<li class=\\"" +
|
|
1205
|
+
cls +
|
|
1206
|
+
"\\"><span class=\\"wg-todo-mark\\">" +
|
|
1207
|
+
mark +
|
|
1208
|
+
"</span><span>" +
|
|
1209
|
+
escFn(t.text || "") +
|
|
1210
|
+
"</span></li>"
|
|
1211
|
+
);
|
|
1212
|
+
})
|
|
1213
|
+
.join("") +
|
|
1214
|
+
"</ul>"
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
function miniStepLabel(info) {
|
|
1219
|
+
const ep =
|
|
1220
|
+
info.episodeNumber !== undefined && info.episodeNumber !== null
|
|
1221
|
+
? "Ep " + info.episodeNumber + " \\u00b7 "
|
|
1222
|
+
: "";
|
|
1223
|
+
return (
|
|
1224
|
+
ep +
|
|
1225
|
+
(info.index + 1) +
|
|
1226
|
+
" / " +
|
|
1227
|
+
info.total +
|
|
1228
|
+
(info.blockName ? " \\u00b7 " + info.blockName : "") +
|
|
1229
|
+
(info.paceBadge ? " \\u00b7 " + info.paceBadge : "")
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1106
1233
|
async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
1107
1234
|
const list = highlights || [];
|
|
1108
1235
|
// defaultHoldMs: when set (fail path), use instead of legacy 900/200 so BUG
|
|
@@ -1112,12 +1239,50 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
|
1112
1239
|
for (let i = 0; i < list.length; i++) {
|
|
1113
1240
|
const h = list[i];
|
|
1114
1241
|
try {
|
|
1242
|
+
await ensureSelectorInView(page, h.selector);
|
|
1243
|
+
await applyHighlightZoom(page, h.selector, h.zoom);
|
|
1115
1244
|
const box = await page.locator(h.selector).first().boundingBox();
|
|
1116
1245
|
if (box) {
|
|
1117
1246
|
await showRing(page, box, h.label, h.tone || "planned", {
|
|
1118
1247
|
size: h.size,
|
|
1119
1248
|
weight: h.weight,
|
|
1120
1249
|
});
|
|
1250
|
+
if ((opts && opts.todos && opts.todos.length) || (h.todos && h.todos.length)) {
|
|
1251
|
+
const rows =
|
|
1252
|
+
opts && opts.todos && opts.todos.length
|
|
1253
|
+
? opts.todos
|
|
1254
|
+
: normalizeTodos(h.todos, h.todoIndex);
|
|
1255
|
+
await page
|
|
1256
|
+
.evaluate((todos) => {
|
|
1257
|
+
const ul = document.getElementById("wg-todos");
|
|
1258
|
+
if (!ul) return;
|
|
1259
|
+
ul.innerHTML = todos
|
|
1260
|
+
.map((t) => {
|
|
1261
|
+
const cls = t.current
|
|
1262
|
+
? "wg-todo-current"
|
|
1263
|
+
: t.done
|
|
1264
|
+
? "wg-todo-done"
|
|
1265
|
+
: "wg-todo-pending";
|
|
1266
|
+
const mark = t.done ? "\\u2713" : t.current ? "\\u2192" : "\\u25CB";
|
|
1267
|
+
const esc = (s) =>
|
|
1268
|
+
String(s)
|
|
1269
|
+
.replace(/&/g, "&")
|
|
1270
|
+
.replace(/</g, "<")
|
|
1271
|
+
.replace(/>/g, ">");
|
|
1272
|
+
return (
|
|
1273
|
+
"<li class=\\"" +
|
|
1274
|
+
cls +
|
|
1275
|
+
"\\"><span class=\\"wg-todo-mark\\">" +
|
|
1276
|
+
mark +
|
|
1277
|
+
"</span><span>" +
|
|
1278
|
+
esc(t.text || "") +
|
|
1279
|
+
"</span></li>"
|
|
1280
|
+
);
|
|
1281
|
+
})
|
|
1282
|
+
.join("");
|
|
1283
|
+
}, rows)
|
|
1284
|
+
.catch(() => {});
|
|
1285
|
+
}
|
|
1121
1286
|
const authored = resolveFixtureDwellMs(h, { gatesFast, pace });
|
|
1122
1287
|
const legacyMs = i === list.length - 1 ? 200 : 900;
|
|
1123
1288
|
const holdMs =
|
|
@@ -1261,6 +1426,7 @@ async function renderAfterStep(page, info) {
|
|
|
1261
1426
|
paceHtml +
|
|
1262
1427
|
"<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
|
|
1263
1428
|
"<h3>" + heading + "</h3>" +
|
|
1429
|
+
todosHtmlFromInfo(info, escA) +
|
|
1264
1430
|
resultHtml +
|
|
1265
1431
|
gateHtml;
|
|
1266
1432
|
if (isNewPanel) {
|
|
@@ -1269,12 +1435,7 @@ async function renderAfterStep(page, info) {
|
|
|
1269
1435
|
}
|
|
1270
1436
|
if (window.__wgWirePanelChrome) {
|
|
1271
1437
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1272
|
-
stepLabel:
|
|
1273
|
-
(info.index + 1) +
|
|
1274
|
-
" / " +
|
|
1275
|
-
info.total +
|
|
1276
|
-
(info.blockName ? " · " + info.blockName : "") +
|
|
1277
|
-
(info.paceBadge ? " · " + info.paceBadge : ""),
|
|
1438
|
+
stepLabel: miniStepLabel(info),
|
|
1278
1439
|
forceCollapsed: info.forceCollapsed === true ? true : info.forceCollapsed === false ? false : undefined,
|
|
1279
1440
|
});
|
|
1280
1441
|
}
|
|
@@ -1376,12 +1537,7 @@ async function renderStepError(page, info) {
|
|
|
1376
1537
|
}
|
|
1377
1538
|
if (window.__wgWirePanelChrome) {
|
|
1378
1539
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1379
|
-
stepLabel:
|
|
1380
|
-
(info.index + 1) +
|
|
1381
|
-
" / " +
|
|
1382
|
-
info.total +
|
|
1383
|
-
(info.blockName ? " · " + info.blockName : "") +
|
|
1384
|
-
(info.paceBadge ? " · " + info.paceBadge : ""),
|
|
1540
|
+
stepLabel: miniStepLabel(info),
|
|
1385
1541
|
forceCollapsed: info.forceCollapsed === true ? true : info.forceCollapsed === false ? false : undefined,
|
|
1386
1542
|
});
|
|
1387
1543
|
}
|
|
@@ -1609,6 +1765,8 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
1609
1765
|
await installOverlay(page, title);
|
|
1610
1766
|
if (s.selector) {
|
|
1611
1767
|
try {
|
|
1768
|
+
await ensureSelectorInView(page, s.selector);
|
|
1769
|
+
await applyHighlightZoom(page, s.selector, s.zoom);
|
|
1612
1770
|
const box = await page.locator(s.selector).first().boundingBox();
|
|
1613
1771
|
if (box) await showRing(page, box, caption, slideTone, slideStyle);
|
|
1614
1772
|
} catch {
|
|
@@ -1696,11 +1854,14 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
1696
1854
|
if (window.__wgWirePanelChrome) {
|
|
1697
1855
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1698
1856
|
stepLabel:
|
|
1857
|
+
(info.episodeNumber !== undefined && info.episodeNumber !== null
|
|
1858
|
+
? "Ep " + info.episodeNumber + " \\u00b7 "
|
|
1859
|
+
: "") +
|
|
1699
1860
|
"Slide " +
|
|
1700
1861
|
(info.index + 1) +
|
|
1701
1862
|
" / " +
|
|
1702
1863
|
info.total +
|
|
1703
|
-
(info.blockName ? "
|
|
1864
|
+
(info.blockName ? " \\u00b7 " + info.blockName : ""),
|
|
1704
1865
|
forceCollapsed: false,
|
|
1705
1866
|
});
|
|
1706
1867
|
}
|
|
@@ -2368,9 +2529,11 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2368
2529
|
!!r.wasFastForward;
|
|
2369
2530
|
const fixtures = r.highlightFixtures;
|
|
2370
2531
|
const flowStyle = r.highlightStyle;
|
|
2371
|
-
|
|
2532
|
+
const stubBeforePhase = await runStubPhase(r.block, "stubBefore", { fixtures });
|
|
2533
|
+
stubBeforeRef.current = stubBeforePhase.highlights.map((h) =>
|
|
2372
2534
|
applyHighlightStyleDefaults(h, flowStyle),
|
|
2373
2535
|
);
|
|
2536
|
+
const stubBeforeTodos = stubBeforePhase.todos || [];
|
|
2374
2537
|
const isNavBlock = r.block.__waygraphKind === "nav";
|
|
2375
2538
|
const autoNow = await currentAutoplay();
|
|
2376
2539
|
// Auto-next: tuck stepper on NavBlocks so the page transition fills the frame.
|
|
@@ -2416,6 +2579,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2416
2579
|
episodeTitle: r.episodeTitle,
|
|
2417
2580
|
stepperMode,
|
|
2418
2581
|
forceCollapsed,
|
|
2582
|
+
todos: stubBeforeTodos,
|
|
2419
2583
|
...paceSpeak,
|
|
2420
2584
|
});
|
|
2421
2585
|
const edits = await gate();
|
|
@@ -2475,9 +2639,11 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2475
2639
|
}
|
|
2476
2640
|
})
|
|
2477
2641
|
.catch(() => {});
|
|
2478
|
-
const
|
|
2642
|
+
const errPhase = await runStubPhase(r.block, "stubOnError", {
|
|
2479
2643
|
fixtures: fixturesOnError,
|
|
2480
|
-
|
|
2644
|
+
error: err,
|
|
2645
|
+
});
|
|
2646
|
+
const errHighlights = errPhase.highlights.map((h) => {
|
|
2481
2647
|
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
2482
2648
|
return {
|
|
2483
2649
|
selector: styled.selector,
|
|
@@ -2487,11 +2653,13 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2487
2653
|
tone: styled.tone,
|
|
2488
2654
|
size: styled.size,
|
|
2489
2655
|
weight: styled.weight,
|
|
2656
|
+
zoom: styled.zoom,
|
|
2490
2657
|
};
|
|
2491
2658
|
});
|
|
2492
2659
|
await cycleHighlightRings(page, errHighlights, !!pacing.gatesFast, {
|
|
2493
2660
|
defaultHoldMs: 2000,
|
|
2494
2661
|
pace: pacing.demoPace,
|
|
2662
|
+
todos: errPhase.todos,
|
|
2495
2663
|
});
|
|
2496
2664
|
}
|
|
2497
2665
|
// A thrown act()/observe()/a failed verify Trait used to just crash
|
|
@@ -2557,21 +2725,26 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2557
2725
|
});
|
|
2558
2726
|
}
|
|
2559
2727
|
let highlights;
|
|
2728
|
+
let stubAfterTodos = [];
|
|
2560
2729
|
if (hasAuthoredStubAfter(r.block, result, fixturesAfter)) {
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2730
|
+
const afterPhase = await runStubPhase(r.block, "stubAfter", {
|
|
2731
|
+
out: result,
|
|
2732
|
+
fixtures: fixturesAfter,
|
|
2733
|
+
});
|
|
2734
|
+
stubAfterTodos = afterPhase.todos || [];
|
|
2735
|
+
highlights = afterPhase.highlights.map((h) => {
|
|
2736
|
+
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
2737
|
+
return {
|
|
2738
|
+
selector: styled.selector,
|
|
2739
|
+
label: formatHighlightCaption(styled),
|
|
2740
|
+
duration: styled.duration,
|
|
2741
|
+
fastMode: styled.fastMode,
|
|
2742
|
+
tone: styled.tone,
|
|
2743
|
+
size: styled.size,
|
|
2744
|
+
weight: styled.weight,
|
|
2745
|
+
zoom: styled.zoom,
|
|
2746
|
+
};
|
|
2747
|
+
});
|
|
2575
2748
|
} else {
|
|
2576
2749
|
highlights = extractVerifyHighlights(r.block, result.__state).map((h) =>
|
|
2577
2750
|
applyHighlightStyleDefaults(h, r.highlightStyle),
|
|
@@ -2596,6 +2769,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2596
2769
|
episodeNumber: r.episodeNumber,
|
|
2597
2770
|
episodeTitle: r.episodeTitle,
|
|
2598
2771
|
stepperMode,
|
|
2772
|
+
todos: stubAfterTodos,
|
|
2599
2773
|
// After a nav under auto-next, stay compact until the next action block expands.
|
|
2600
2774
|
forceCollapsed: !!process.env.WAYGRAPH_VIDEO || ((await currentAutoplay()) && isNavBlock),
|
|
2601
2775
|
});
|
|
@@ -2690,7 +2864,7 @@ async function runChainedFlows(chainFlow, chainFlows, context, mem, page) {
|
|
|
2690
2864
|
/** Parses WAYGRAPH_VIDEO_VIEWPORT / --video-viewport: "1920x1080" or "1920,1080". */
|
|
2691
2865
|
function parseVideoViewport(raw) {
|
|
2692
2866
|
if (!raw || !raw.trim()) return null;
|
|
2693
|
-
const m = raw.trim().match(/^(
|
|
2867
|
+
const m = raw.trim().match(/^(\\d{3,5})[xX,](\\d{3,5})$/);
|
|
2694
2868
|
if (!m) return null;
|
|
2695
2869
|
const width = Number(m[1]);
|
|
2696
2870
|
const height = Number(m[2]);
|
|
@@ -2981,7 +3155,13 @@ async function main() {
|
|
|
2981
3155
|
...(executablePath ? { executablePath } : {}),
|
|
2982
3156
|
});
|
|
2983
3157
|
const contextOpts = fixedVideoViewport
|
|
2984
|
-
? {
|
|
3158
|
+
? {
|
|
3159
|
+
baseURL,
|
|
3160
|
+
viewport: fixedVideoViewport,
|
|
3161
|
+
// Lock DPR so --video-viewport 800x450 stays 800x450 in the .webm
|
|
3162
|
+
// (HiDPI hosts otherwise scale recordVideo to e.g. 1920x1080).
|
|
3163
|
+
deviceScaleFactor: 1,
|
|
3164
|
+
}
|
|
2985
3165
|
: step
|
|
2986
3166
|
? { baseURL, viewport: null }
|
|
2987
3167
|
: { baseURL, viewport: { width: 1280, height: 720 } };
|
package/dist/engine.d.ts
CHANGED
|
@@ -603,9 +603,9 @@ export type NavBlockOptions<Out extends Checkpoint<string>> = {
|
|
|
603
603
|
checkpoint: Out["__state"];
|
|
604
604
|
requires?: readonly MemKey<any>[];
|
|
605
605
|
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
606
|
-
stubBefore?: import("./highlights.js").
|
|
607
|
-
stubAfter?: import("./highlights.js").
|
|
608
|
-
stubOnError?: import("./highlights.js").
|
|
606
|
+
stubBefore?: import("./highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
607
|
+
stubAfter?: import("./highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
608
|
+
stubOnError?: import("./highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
609
609
|
/** @deprecated Prefer stubAfter. */
|
|
610
610
|
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
611
611
|
/**
|
|
@@ -667,9 +667,9 @@ export type NavClickBlockOptions<Out extends Checkpoint<string>> = {
|
|
|
667
667
|
click: string | ((mem: MemPage) => string);
|
|
668
668
|
requires?: readonly MemKey<any>[];
|
|
669
669
|
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
670
|
-
stubBefore?: import("./highlights.js").
|
|
671
|
-
stubAfter?: import("./highlights.js").
|
|
672
|
-
stubOnError?: import("./highlights.js").
|
|
670
|
+
stubBefore?: import("./highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
671
|
+
stubAfter?: import("./highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
672
|
+
stubOnError?: import("./highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
673
673
|
/** @deprecated Prefer stubAfter. */
|
|
674
674
|
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
675
675
|
instanceOptions?: Block<Checkpoint<string>, Out>["instanceOptions"];
|
|
@@ -699,9 +699,9 @@ export type PageBlockOptions<Out extends Checkpoint<string>> = {
|
|
|
699
699
|
/** Checkpoint tag for this screen (hub). */
|
|
700
700
|
checkpoint: Out["__state"];
|
|
701
701
|
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
702
|
-
stubBefore?: import("./highlights.js").
|
|
703
|
-
stubAfter?: import("./highlights.js").
|
|
704
|
-
stubOnError?: import("./highlights.js").
|
|
702
|
+
stubBefore?: import("./highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
703
|
+
stubAfter?: import("./highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
704
|
+
stubOnError?: import("./highlights.js").HighlightStubPhaseOrFn<Out>;
|
|
705
705
|
/** @deprecated Prefer stubAfter. */
|
|
706
706
|
highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
|
|
707
707
|
requires?: readonly MemKey<any>[];
|
package/dist/highlights.d.ts
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Block stubBefore / stubAfter / stubOnError + flow highlightFixtures + demo slides.
|
|
3
3
|
* Demo narration only - never affects runGraph pass/fail.
|
|
4
4
|
*
|
|
5
|
+
* Stubs are **open block lifecycles** - prefer `stubBefore(ctx) { ... }`, not a
|
|
6
|
+
* closed slot object. Inside the fn authors set fixtures (todos, zoom) and rings.
|
|
7
|
+
* Object maps remain a shorthand for highlight-only slots.
|
|
8
|
+
*
|
|
5
9
|
* Lifecycle (stubs) ≠ yap slides: stubs are fill/after/error rings tied to the block;
|
|
6
10
|
* slides are multi-step captions with Next between them (long-process explain).
|
|
7
11
|
*
|
|
@@ -24,6 +28,26 @@ export type FixtureDurationFields = {
|
|
|
24
28
|
*/
|
|
25
29
|
fastMode?: number;
|
|
26
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* One checklist row for episode planning (demo panel).
|
|
33
|
+
* Set via {@link StubCtx.todos} / {@link StubCtx.todoIndex} inside the lifecycle.
|
|
34
|
+
*/
|
|
35
|
+
export type WaygraphTodoItem = {
|
|
36
|
+
text: string;
|
|
37
|
+
/** Explicit done. If omitted, derived from todoIndex (index < todoIndex). */
|
|
38
|
+
done?: boolean;
|
|
39
|
+
/** Explicit current. If omitted, derived from todoIndex (index === todoIndex). */
|
|
40
|
+
current?: boolean;
|
|
41
|
+
};
|
|
42
|
+
export type WaygraphTodoInput = string | WaygraphTodoItem;
|
|
43
|
+
/** Episode-level fixtures authored inside stubBefore/After/OnError(ctx). */
|
|
44
|
+
export type StubPhaseFixtures = {
|
|
45
|
+
todos?: readonly WaygraphTodoInput[];
|
|
46
|
+
/** Which todo is current (0-based). Rows before = done, after = pending. */
|
|
47
|
+
todoIndex?: number;
|
|
48
|
+
/** Default zoom for rings that omit their own zoom. */
|
|
49
|
+
zoom?: number;
|
|
50
|
+
};
|
|
27
51
|
/** One named highlight slot on a block (selector + caption). */
|
|
28
52
|
export interface WaygraphHighlightStub extends FixtureDurationFields {
|
|
29
53
|
selector: string;
|
|
@@ -50,11 +74,72 @@ export interface WaygraphHighlightStub extends FixtureDurationFields {
|
|
|
50
74
|
* Aliases: regular, strong.
|
|
51
75
|
*/
|
|
52
76
|
weight?: HighlightWeight;
|
|
77
|
+
/**
|
|
78
|
+
* Magnify this target while its ring is shown (e.g. `1.35`).
|
|
79
|
+
* Cleared when the ring hides / next highlight starts. `1` or omit = off.
|
|
80
|
+
* Episode default: {@link StubCtx.zoom}.
|
|
81
|
+
*/
|
|
82
|
+
zoom?: number;
|
|
53
83
|
}
|
|
54
84
|
/** Flow fixture patch: label required; selector optional (inherits from stub). */
|
|
55
|
-
export type WaygraphHighlightFixture = Partial<Pick<WaygraphHighlightStub, "selector">> & Required<Pick<WaygraphHighlightStub, "label">> & Pick<WaygraphHighlightStub, "detail" | "tag" | "tone" | "size" | "weight"> & FixtureDurationFields;
|
|
85
|
+
export type WaygraphHighlightFixture = Partial<Pick<WaygraphHighlightStub, "selector">> & Required<Pick<WaygraphHighlightStub, "label">> & Pick<WaygraphHighlightStub, "detail" | "tag" | "tone" | "size" | "weight" | "zoom"> & FixtureDurationFields;
|
|
56
86
|
export type HighlightStubPhase = Record<string, WaygraphHighlightStub>;
|
|
57
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Open stub lifecycle context. Authors set rings + episode fixtures freely —
|
|
89
|
+
* not restricted to returning a closed slot object.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* stubBefore: (ctx) => {
|
|
93
|
+
* ctx.todos(["Enter email", "Enter password", "Click Sign in"]);
|
|
94
|
+
* ctx.todoIndex(0);
|
|
95
|
+
* ctx.zoom(1.35);
|
|
96
|
+
* ctx.highlights({
|
|
97
|
+
* email: { selector: "#email", label: "Email" },
|
|
98
|
+
* submit: { selector: "#login-button", label: "Sign in" },
|
|
99
|
+
* });
|
|
100
|
+
* }
|
|
101
|
+
* // Sequential plan: bump todoIndex in a later phase / later block's stubBefore
|
|
102
|
+
* stubAfter: (ctx) => {
|
|
103
|
+
* ctx.todos(["Enter email", "Enter password", "Click Sign in"]);
|
|
104
|
+
* ctx.todoIndex(2);
|
|
105
|
+
* ctx.ring("inventory", { selector: ".inventory_list", label: "Landed" });
|
|
106
|
+
* }
|
|
107
|
+
*/
|
|
108
|
+
export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
|
|
109
|
+
/** Present after resolve (stubAfter). Undefined for stubBefore. */
|
|
110
|
+
readonly out: Out | undefined;
|
|
111
|
+
/** Present on stubOnError when the step threw. */
|
|
112
|
+
readonly error: unknown | undefined;
|
|
113
|
+
/** Replace named highlight rings for this phase. */
|
|
114
|
+
highlights(slots: HighlightStubPhase): void;
|
|
115
|
+
/** Set / overwrite one named ring. */
|
|
116
|
+
ring(id: string, stub: WaygraphHighlightStub): void;
|
|
117
|
+
/** Episode checklist (demo panel). */
|
|
118
|
+
todos(items: readonly WaygraphTodoInput[]): void;
|
|
119
|
+
/** Current checklist index (bump for sequential plans). */
|
|
120
|
+
todoIndex(n: number): void;
|
|
121
|
+
/** Default zoom for rings without their own zoom. */
|
|
122
|
+
zoom(n: number): void;
|
|
123
|
+
/** Batch-set rings + episode fixtures. */
|
|
124
|
+
set(partial: {
|
|
125
|
+
highlights?: HighlightStubPhase;
|
|
126
|
+
} & StubPhaseFixtures): void;
|
|
127
|
+
};
|
|
128
|
+
export type StubLifecycleFn<Out extends Checkpoint<string>> = (ctx: StubCtx<Out>) => void | HighlightStubPhase | Promise<void | HighlightStubPhase>;
|
|
129
|
+
/**
|
|
130
|
+
* Block stub authoring:
|
|
131
|
+
* - **preferred:** open lifecycle `stubBefore(ctx) { ctx.todos(...); ctx.ring(...) }`
|
|
132
|
+
* - **shorthand:** static slot map `{ email: { selector, label } }`
|
|
133
|
+
* - **legacy:** `(out) => slotMap` still accepted (treated as highlights only)
|
|
134
|
+
*/
|
|
135
|
+
export type HighlightStubPhaseOrFn<Out extends Checkpoint<string>> = HighlightStubPhase | StubLifecycleFn<Out> | ((out: Out) => HighlightStubPhase);
|
|
136
|
+
/** Result of running a stub phase (rings + episode fixtures). */
|
|
137
|
+
export type StubPhaseResult = {
|
|
138
|
+
highlights: ResolvedHighlight[];
|
|
139
|
+
todos: WaygraphTodoItem[];
|
|
140
|
+
todoIndex?: number;
|
|
141
|
+
zoom?: number;
|
|
142
|
+
};
|
|
58
143
|
/**
|
|
59
144
|
* One yap/slide in a multi-step demo caption sequence.
|
|
60
145
|
* Not block lifecycle - demo pauses (Next / auto-next) between slides.
|
|
@@ -69,6 +154,8 @@ export interface WaygraphSlide extends FixtureDurationFields {
|
|
|
69
154
|
tone?: HighlightTone;
|
|
70
155
|
size?: HighlightSize;
|
|
71
156
|
weight?: HighlightWeight;
|
|
157
|
+
/** Magnify selector target while this slide is showing (same as stub zoom). */
|
|
158
|
+
zoom?: number;
|
|
72
159
|
}
|
|
73
160
|
/**
|
|
74
161
|
* Demo ring tones (iconified).
|
|
@@ -189,9 +276,25 @@ export interface WaygraphHighlight {
|
|
|
189
276
|
}
|
|
190
277
|
export type ResolvedHighlight = WaygraphHighlightStub;
|
|
191
278
|
/**
|
|
192
|
-
*
|
|
193
|
-
|
|
194
|
-
|
|
279
|
+
* Normalize checklist rows; apply todoIndex for sequential done/current.
|
|
280
|
+
*/
|
|
281
|
+
export declare function normalizeTodos(todos: readonly WaygraphTodoInput[] | undefined, todoIndex?: number): WaygraphTodoItem[];
|
|
282
|
+
/**
|
|
283
|
+
* Run an open stub lifecycle (preferred) or resolve a static slot map.
|
|
284
|
+
* Returns rings + episode fixtures (todos / todoIndex / zoom).
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* const before = await runStubPhase(block, "stubBefore", { fixtures });
|
|
288
|
+
* // before.todos, before.highlights
|
|
289
|
+
*/
|
|
290
|
+
export declare function runStubPhase(block: Block<any, any>, phase: HighlightStubPhaseName, opts?: {
|
|
291
|
+
out?: Checkpoint<string>;
|
|
292
|
+
error?: unknown;
|
|
293
|
+
fixtures?: HighlightFixtureMap;
|
|
294
|
+
}): Promise<StubPhaseResult>;
|
|
295
|
+
/**
|
|
296
|
+
* Sync resolve of highlight slots. Prefer {@link runStubPhase} for open
|
|
297
|
+
* async lifecycles + episode fixtures (todos/zoom).
|
|
195
298
|
*/
|
|
196
299
|
export declare function resolveHighlightSlots(block: Block<any, any>, phase: HighlightStubPhaseName, opts?: {
|
|
197
300
|
out?: Checkpoint<string>;
|
|
@@ -214,12 +317,12 @@ export declare function formatHighlightCaption(h: {
|
|
|
214
317
|
tone?: HighlightTone | string;
|
|
215
318
|
}): string;
|
|
216
319
|
/**
|
|
217
|
-
* True when the block authored
|
|
218
|
-
*
|
|
320
|
+
* True when the block authored stubAfter (lifecycle fn, slots, fixtures, or legacy highlights).
|
|
321
|
+
* When true, demo should not use verify-trait fallback.
|
|
219
322
|
*/
|
|
220
323
|
export declare function hasAuthoredStubAfter(block: Block<any, any>, out?: Checkpoint<string>, fixtures?: HighlightFixtureMap): boolean;
|
|
221
324
|
/**
|
|
222
|
-
* True when stubOnError
|
|
325
|
+
* True when stubOnError is authored (lifecycle fn and/or rings).
|
|
223
326
|
* Demo fail path only - success never consults this.
|
|
224
327
|
*/
|
|
225
328
|
export declare function hasAuthoredStubOnError(block: Block<any, any>, fixtures?: HighlightFixtureMap): boolean;
|
package/dist/highlights.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Block stubBefore / stubAfter / stubOnError + flow highlightFixtures + demo slides.
|
|
3
3
|
* Demo narration only - never affects runGraph pass/fail.
|
|
4
4
|
*
|
|
5
|
+
* Stubs are **open block lifecycles** - prefer `stubBefore(ctx) { ... }`, not a
|
|
6
|
+
* closed slot object. Inside the fn authors set fixtures (todos, zoom) and rings.
|
|
7
|
+
* Object maps remain a shorthand for highlight-only slots.
|
|
8
|
+
*
|
|
5
9
|
* Lifecycle (stubs) ≠ yap slides: stubs are fill/after/error rings tied to the block;
|
|
6
10
|
* slides are multi-step captions with Next between them (long-process explain).
|
|
7
11
|
*
|
|
@@ -289,11 +293,43 @@ function pickStyleFields(base, patch) {
|
|
|
289
293
|
out.weight = normalizeHighlightWeight(weight);
|
|
290
294
|
return out;
|
|
291
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Normalize checklist rows; apply todoIndex for sequential done/current.
|
|
298
|
+
*/
|
|
299
|
+
export function normalizeTodos(todos, todoIndex) {
|
|
300
|
+
if (!todos || todos.length === 0)
|
|
301
|
+
return [];
|
|
302
|
+
const idx = todoIndex !== undefined && Number.isFinite(todoIndex) ? Math.floor(todoIndex) : undefined;
|
|
303
|
+
return todos.map((t, i) => {
|
|
304
|
+
const text = typeof t === "string" ? t.trim() : String(t?.text ?? "").trim();
|
|
305
|
+
const item = { text: text || `Item ${i + 1}` };
|
|
306
|
+
if (typeof t === "object" && t) {
|
|
307
|
+
if (t.done !== undefined)
|
|
308
|
+
item.done = !!t.done;
|
|
309
|
+
if (t.current !== undefined)
|
|
310
|
+
item.current = !!t.current;
|
|
311
|
+
}
|
|
312
|
+
if (idx !== undefined) {
|
|
313
|
+
if (item.done === undefined)
|
|
314
|
+
item.done = i < idx;
|
|
315
|
+
if (item.current === undefined)
|
|
316
|
+
item.current = i === idx;
|
|
317
|
+
}
|
|
318
|
+
return item;
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
function pickZoom(base, patch) {
|
|
322
|
+
const zoom = patch?.zoom !== undefined ? patch.zoom : base?.zoom;
|
|
323
|
+
if (zoom !== undefined && Number.isFinite(zoom) && zoom > 0)
|
|
324
|
+
return { zoom };
|
|
325
|
+
return {};
|
|
326
|
+
}
|
|
292
327
|
function mergeSlot(base, patch) {
|
|
293
328
|
if (!base && !patch)
|
|
294
329
|
return null;
|
|
295
330
|
const dwell = pickDurationFields(base, patch);
|
|
296
331
|
const style = pickStyleFields(base, patch);
|
|
332
|
+
const zoom = pickZoom(base, patch);
|
|
297
333
|
if (!base && patch) {
|
|
298
334
|
if (!patch.selector)
|
|
299
335
|
return null;
|
|
@@ -304,6 +340,7 @@ function mergeSlot(base, patch) {
|
|
|
304
340
|
...(patch.tag ? { tag: patch.tag } : {}),
|
|
305
341
|
...style,
|
|
306
342
|
...dwell,
|
|
343
|
+
...zoom,
|
|
307
344
|
};
|
|
308
345
|
}
|
|
309
346
|
if (base && !patch) {
|
|
@@ -312,6 +349,7 @@ function mergeSlot(base, patch) {
|
|
|
312
349
|
...(base.tone ? { tone: normalizeHighlightTone(base.tone) } : {}),
|
|
313
350
|
...(base.size ? { size: normalizeHighlightSize(base.size) } : {}),
|
|
314
351
|
...(base.weight ? { weight: normalizeHighlightWeight(base.weight) } : {}),
|
|
352
|
+
...pickZoom(base, undefined),
|
|
315
353
|
};
|
|
316
354
|
}
|
|
317
355
|
return {
|
|
@@ -325,8 +363,78 @@ function mergeSlot(base, patch) {
|
|
|
325
363
|
...(patch.tag !== undefined ? { tag: patch.tag } : base.tag ? { tag: base.tag } : {}),
|
|
326
364
|
...style,
|
|
327
365
|
...dwell,
|
|
366
|
+
...zoom,
|
|
328
367
|
};
|
|
329
368
|
}
|
|
369
|
+
function createStubCtx(bag, opts) {
|
|
370
|
+
return {
|
|
371
|
+
out: opts.out,
|
|
372
|
+
error: opts.error,
|
|
373
|
+
highlights(slots) {
|
|
374
|
+
bag.highlights = { ...(slots || {}) };
|
|
375
|
+
},
|
|
376
|
+
ring(id, stub) {
|
|
377
|
+
bag.highlights = { ...bag.highlights, [id]: stub };
|
|
378
|
+
},
|
|
379
|
+
todos(items) {
|
|
380
|
+
bag.todos = items;
|
|
381
|
+
},
|
|
382
|
+
todoIndex(n) {
|
|
383
|
+
bag.todoIndex = n;
|
|
384
|
+
},
|
|
385
|
+
zoom(n) {
|
|
386
|
+
if (Number.isFinite(n) && n > 0)
|
|
387
|
+
bag.zoom = n;
|
|
388
|
+
},
|
|
389
|
+
set(partial) {
|
|
390
|
+
if (partial.highlights)
|
|
391
|
+
bag.highlights = { ...partial.highlights };
|
|
392
|
+
if (partial.todos !== undefined)
|
|
393
|
+
bag.todos = partial.todos;
|
|
394
|
+
if (partial.todoIndex !== undefined)
|
|
395
|
+
bag.todoIndex = partial.todoIndex;
|
|
396
|
+
if (partial.zoom !== undefined && Number.isFinite(partial.zoom) && partial.zoom > 0) {
|
|
397
|
+
bag.zoom = partial.zoom;
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
/** Lift legacy per-slot todos (0.12.23) onto the phase bag once. */
|
|
403
|
+
function liftLegacySlotTodos(bag) {
|
|
404
|
+
if (bag.todos && bag.todos.length)
|
|
405
|
+
return;
|
|
406
|
+
for (const stub of Object.values(bag.highlights)) {
|
|
407
|
+
const legacy = stub;
|
|
408
|
+
if (legacy.todos && legacy.todos.length) {
|
|
409
|
+
bag.todos = legacy.todos;
|
|
410
|
+
if (legacy.todoIndex !== undefined)
|
|
411
|
+
bag.todoIndex = legacy.todoIndex;
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function applyDefaultZoom(slots, defaultZoom) {
|
|
417
|
+
if (defaultZoom === undefined || !(defaultZoom > 0))
|
|
418
|
+
return slots;
|
|
419
|
+
return slots.map((h) => (h.zoom !== undefined && h.zoom > 0 ? h : { ...h, zoom: defaultZoom }));
|
|
420
|
+
}
|
|
421
|
+
function mergePhaseMaps(phaseMap, fixturePhase) {
|
|
422
|
+
const slotIds = new Set([...Object.keys(phaseMap), ...Object.keys(fixturePhase || {})]);
|
|
423
|
+
const ordered = [...slotIds].sort((a, b) => {
|
|
424
|
+
const na = Number(a);
|
|
425
|
+
const nb = Number(b);
|
|
426
|
+
if (!Number.isNaN(na) && !Number.isNaN(nb))
|
|
427
|
+
return na - nb;
|
|
428
|
+
return a.localeCompare(b);
|
|
429
|
+
});
|
|
430
|
+
const resolved = [];
|
|
431
|
+
for (const id of ordered) {
|
|
432
|
+
const merged = mergeSlot(phaseMap[id], fixturePhase?.[id]);
|
|
433
|
+
if (merged)
|
|
434
|
+
resolved.push(merged);
|
|
435
|
+
}
|
|
436
|
+
return resolved;
|
|
437
|
+
}
|
|
330
438
|
function phaseFromLegacyHighlights(highlights) {
|
|
331
439
|
const out = {};
|
|
332
440
|
if (!highlights)
|
|
@@ -342,58 +450,121 @@ function phaseFromLegacyHighlights(highlights) {
|
|
|
342
450
|
});
|
|
343
451
|
return out;
|
|
344
452
|
}
|
|
453
|
+
function readStubRaw(block, phase) {
|
|
454
|
+
const instr = block.instruction;
|
|
455
|
+
if (phase === "stubBefore")
|
|
456
|
+
return instr.stubBefore;
|
|
457
|
+
if (phase === "stubAfter")
|
|
458
|
+
return instr.stubAfter;
|
|
459
|
+
return instr.stubOnError;
|
|
460
|
+
}
|
|
461
|
+
function applyLegacyHighlightsShim(block, phase, phaseMap, out) {
|
|
462
|
+
if (phase !== "stubAfter" || Object.keys(phaseMap).length > 0)
|
|
463
|
+
return phaseMap;
|
|
464
|
+
const instr = block.instruction;
|
|
465
|
+
if (!instr.highlights)
|
|
466
|
+
return phaseMap;
|
|
467
|
+
let legacy = instr.highlights;
|
|
468
|
+
if (typeof legacy === "function") {
|
|
469
|
+
try {
|
|
470
|
+
legacy = legacy(out ?? { __state: "" });
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
legacy = [];
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return phaseFromLegacyHighlights(Array.isArray(legacy) ? legacy : []);
|
|
477
|
+
}
|
|
345
478
|
/**
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
479
|
+
* Run an open stub lifecycle (preferred) or resolve a static slot map.
|
|
480
|
+
* Returns rings + episode fixtures (todos / todoIndex / zoom).
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* const before = await runStubPhase(block, "stubBefore", { fixtures });
|
|
484
|
+
* // before.todos, before.highlights
|
|
349
485
|
*/
|
|
350
|
-
export function
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
?
|
|
354
|
-
:
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
let phaseMap = {};
|
|
486
|
+
export async function runStubPhase(block, phase, opts) {
|
|
487
|
+
const bag = { highlights: {} };
|
|
488
|
+
const ctx = createStubCtx(bag, {
|
|
489
|
+
...(opts?.out !== undefined ? { out: opts.out } : {}),
|
|
490
|
+
...(opts?.error !== undefined ? { error: opts.error } : {}),
|
|
491
|
+
});
|
|
492
|
+
const raw = readStubRaw(block, phase);
|
|
358
493
|
if (typeof raw === "function") {
|
|
359
494
|
try {
|
|
360
|
-
|
|
495
|
+
const ret = await raw(ctx);
|
|
496
|
+
if (isStubPhase(ret)) {
|
|
497
|
+
bag.highlights = { ...bag.highlights, ...ret };
|
|
498
|
+
}
|
|
361
499
|
}
|
|
362
500
|
catch {
|
|
363
|
-
|
|
501
|
+
/* best-effort */
|
|
502
|
+
}
|
|
503
|
+
// Compat: legacy `(out) => map` that expected Checkpoint (__state on arg)
|
|
504
|
+
if (Object.keys(bag.highlights).length === 0 && !bag.todos) {
|
|
505
|
+
try {
|
|
506
|
+
const legacyOut = (opts?.out ?? { __state: "" });
|
|
507
|
+
const ret2 = raw(legacyOut);
|
|
508
|
+
if (isStubPhase(ret2))
|
|
509
|
+
bag.highlights = { ...ret2 };
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
/* ignore */
|
|
513
|
+
}
|
|
364
514
|
}
|
|
365
515
|
}
|
|
366
516
|
else if (isStubPhase(raw)) {
|
|
367
|
-
|
|
517
|
+
bag.highlights = { ...raw };
|
|
368
518
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
519
|
+
bag.highlights = applyLegacyHighlightsShim(block, phase, bag.highlights, opts?.out);
|
|
520
|
+
liftLegacySlotTodos(bag);
|
|
521
|
+
const fixturePhase = opts?.fixtures?.[block.name]?.[phase];
|
|
522
|
+
let highlights = mergePhaseMaps(bag.highlights, fixturePhase);
|
|
523
|
+
highlights = applyDefaultZoom(highlights, bag.zoom);
|
|
524
|
+
const todos = normalizeTodos(bag.todos, bag.todoIndex);
|
|
525
|
+
return {
|
|
526
|
+
highlights,
|
|
527
|
+
todos,
|
|
528
|
+
...(bag.todoIndex !== undefined ? { todoIndex: bag.todoIndex } : {}),
|
|
529
|
+
...(bag.zoom !== undefined ? { zoom: bag.zoom } : {}),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Sync resolve of highlight slots. Prefer {@link runStubPhase} for open
|
|
534
|
+
* async lifecycles + episode fixtures (todos/zoom).
|
|
535
|
+
*/
|
|
536
|
+
export function resolveHighlightSlots(block, phase, opts) {
|
|
537
|
+
const bag = { highlights: {} };
|
|
538
|
+
const raw = readStubRaw(block, phase);
|
|
539
|
+
if (typeof raw === "function") {
|
|
540
|
+
try {
|
|
541
|
+
const ctx = createStubCtx(bag, {
|
|
542
|
+
...(opts?.out !== undefined ? { out: opts.out } : {}),
|
|
543
|
+
});
|
|
544
|
+
const ret = raw(ctx);
|
|
545
|
+
if (isStubPhase(ret)) {
|
|
546
|
+
bag.highlights = { ...bag.highlights, ...ret };
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
372
550
|
try {
|
|
373
|
-
|
|
551
|
+
const legacyOut = (opts?.out ?? { __state: "" });
|
|
552
|
+
const ret2 = raw(legacyOut);
|
|
553
|
+
if (isStubPhase(ret2))
|
|
554
|
+
bag.highlights = { ...ret2 };
|
|
374
555
|
}
|
|
375
556
|
catch {
|
|
376
|
-
|
|
557
|
+
bag.highlights = {};
|
|
377
558
|
}
|
|
378
559
|
}
|
|
379
|
-
phaseMap = phaseFromLegacyHighlights(Array.isArray(legacy) ? legacy : []);
|
|
380
560
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const ordered = [...slotIds].sort((a, b) => {
|
|
384
|
-
const na = Number(a);
|
|
385
|
-
const nb = Number(b);
|
|
386
|
-
if (!Number.isNaN(na) && !Number.isNaN(nb))
|
|
387
|
-
return na - nb;
|
|
388
|
-
return a.localeCompare(b);
|
|
389
|
-
});
|
|
390
|
-
const resolved = [];
|
|
391
|
-
for (const id of ordered) {
|
|
392
|
-
const merged = mergeSlot(phaseMap[id], fixturePhase?.[id]);
|
|
393
|
-
if (merged)
|
|
394
|
-
resolved.push(merged);
|
|
561
|
+
else if (isStubPhase(raw)) {
|
|
562
|
+
bag.highlights = { ...raw };
|
|
395
563
|
}
|
|
396
|
-
|
|
564
|
+
bag.highlights = applyLegacyHighlightsShim(block, phase, bag.highlights, opts?.out);
|
|
565
|
+
liftLegacySlotTodos(bag);
|
|
566
|
+
const fixturePhase = opts?.fixtures?.[block.name]?.[phase];
|
|
567
|
+
return applyDefaultZoom(mergePhaseMaps(bag.highlights, fixturePhase), bag.zoom);
|
|
397
568
|
}
|
|
398
569
|
/**
|
|
399
570
|
* Resolve multi-step yap slides: flow fixture `slides` wins when present,
|
|
@@ -428,21 +599,39 @@ export function formatHighlightCaption(h) {
|
|
|
428
599
|
return `${icon}${tag}${primary} - ${h.detail}`;
|
|
429
600
|
return `${icon}${tag}${primary}`;
|
|
430
601
|
}
|
|
602
|
+
function stubPhaseLooksAuthored(block, phase, fixtures) {
|
|
603
|
+
const raw = readStubRaw(block, phase);
|
|
604
|
+
if (typeof raw === "function")
|
|
605
|
+
return true;
|
|
606
|
+
if (isStubPhase(raw) && Object.keys(raw).length > 0)
|
|
607
|
+
return true;
|
|
608
|
+
const fx = fixtures?.[block.name]?.[phase];
|
|
609
|
+
if (fx && Object.keys(fx).length > 0)
|
|
610
|
+
return true;
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
431
613
|
/**
|
|
432
|
-
* True when the block authored
|
|
433
|
-
*
|
|
614
|
+
* True when the block authored stubAfter (lifecycle fn, slots, fixtures, or legacy highlights).
|
|
615
|
+
* When true, demo should not use verify-trait fallback.
|
|
434
616
|
*/
|
|
435
617
|
export function hasAuthoredStubAfter(block, out, fixtures) {
|
|
618
|
+
if (stubPhaseLooksAuthored(block, "stubAfter", fixtures))
|
|
619
|
+
return true;
|
|
620
|
+
const instr = block.instruction;
|
|
621
|
+
if (instr.highlights)
|
|
622
|
+
return true;
|
|
436
623
|
return resolveHighlightSlots(block, "stubAfter", {
|
|
437
624
|
...(out !== undefined ? { out } : {}),
|
|
438
625
|
...(fixtures !== undefined ? { fixtures } : {}),
|
|
439
626
|
}).length > 0;
|
|
440
627
|
}
|
|
441
628
|
/**
|
|
442
|
-
* True when stubOnError
|
|
629
|
+
* True when stubOnError is authored (lifecycle fn and/or rings).
|
|
443
630
|
* Demo fail path only - success never consults this.
|
|
444
631
|
*/
|
|
445
632
|
export function hasAuthoredStubOnError(block, fixtures) {
|
|
633
|
+
if (stubPhaseLooksAuthored(block, "stubOnError", fixtures))
|
|
634
|
+
return true;
|
|
446
635
|
return (resolveHighlightSlots(block, "stubOnError", {
|
|
447
636
|
...(fixtures !== undefined ? { fixtures } : {}),
|
|
448
637
|
}).length > 0);
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ export { connect, checkpoint } from "./types.js";
|
|
|
3
3
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
4
4
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
5
5
|
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
6
|
-
export { resolveHighlightSlots, resolveSlides, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
7
|
-
export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, } from "./highlights.js";
|
|
6
|
+
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
7
|
+
export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, } from "./highlights.js";
|
|
8
8
|
export type { Flow, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
|
|
9
9
|
export type { ActionPage } from "./types.js";
|
|
10
10
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { connect, checkpoint } from "./types.js";
|
|
|
2
2
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
3
3
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
4
4
|
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
5
|
-
export { resolveHighlightSlots, resolveSlides, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
5
|
+
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
6
6
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
|
7
7
|
export { isFileSelectToken, parseBlocksSelect, matchBlocksSelect, globToRegExp, } from "./blocks-select.js";
|
|
8
8
|
export { EdgeLeaseCoordinator } from "./traverse-lease.js";
|
package/dist/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Page } from "@playwright/test";
|
|
|
2
2
|
import type { MemPage, MemKey } from "./mem-page.js";
|
|
3
3
|
import type { Trait } from "./trait.js";
|
|
4
4
|
import type { HighlightStubPhaseOrFn, WaygraphHighlight, WaygraphSlidesOrFn } from "./highlights.js";
|
|
5
|
-
export type { WaygraphHighlight, WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, HighlightStubPhase, HighlightStubPhaseOrFn, HighlightFixtureMap, ResolvedHighlight, } from "./highlights.js";
|
|
5
|
+
export type { WaygraphHighlight, WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, HighlightStubPhase, HighlightStubPhaseOrFn, HighlightFixtureMap, ResolvedHighlight, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, WaygraphTodoItem, WaygraphTodoInput, } from "./highlights.js";
|
|
6
6
|
/**
|
|
7
7
|
* A Checkpoint is identified solely by its string tag - a phantom marker with no
|
|
8
8
|
* embedded data. Two Checkpoints with the same tag are the same state everywhere.
|
|
@@ -90,22 +90,34 @@ export interface Instruction<In extends Checkpoint<string>, Out extends Checkpoi
|
|
|
90
90
|
*/
|
|
91
91
|
verify?: Trait[] | ((out: Out) => Trait[]);
|
|
92
92
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
93
|
+
* Open block lifecycle **before / during** `act` (demo rings + fixtures).
|
|
94
|
+
* Prefer `stubBefore(ctx) { ctx.todos(...); ctx.ring(...) }` — not a closed
|
|
95
|
+
* slot object. Object map `{ email: { selector, label } }` remains a shorthand.
|
|
96
|
+
* Flow {@link HighlightFixtureMap} still overrides ring labels.
|
|
97
|
+
* @example stubBefore: (ctx) => {
|
|
98
|
+
* ctx.todos(["Email", "Password", "Submit"]);
|
|
99
|
+
* ctx.todoIndex(0);
|
|
100
|
+
* ctx.highlights({ email: { selector: "#email", label: "Email" } });
|
|
101
|
+
* }
|
|
96
102
|
*/
|
|
97
103
|
stubBefore?: HighlightStubPhaseOrFn<Out>;
|
|
98
104
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
105
|
+
* Open block lifecycle **after** resolve (step panel rings + fixtures).
|
|
106
|
+
* Prefer `stubAfter(ctx) { ... }`. Object map shorthand still works.
|
|
107
|
+
* Prefer this over legacy {@link Instruction.highlights}.
|
|
108
|
+
* @example stubAfter: (ctx) => {
|
|
109
|
+
* ctx.todoIndex(2);
|
|
110
|
+
* ctx.ring("badge", { selector: ".cart", label: "Cart updated" });
|
|
111
|
+
* }
|
|
102
112
|
*/
|
|
103
113
|
stubAfter?: HighlightStubPhaseOrFn<Out>;
|
|
104
114
|
/**
|
|
105
|
-
*
|
|
115
|
+
* Open block lifecycle when a **step throws** (failed verify / act error) -
|
|
106
116
|
* before the error panel. Success-only {@link Instruction.stubAfter} never runs
|
|
107
|
-
* here
|
|
108
|
-
* @example stubOnError:
|
|
117
|
+
* here. Prefer `stubOnError(ctx) { ... }`.
|
|
118
|
+
* @example stubOnError: (ctx) => {
|
|
119
|
+
* ctx.ring("card", { selector: ".alt-1", label: "Card at failure", tag: "BUG" });
|
|
120
|
+
* }
|
|
109
121
|
*/
|
|
110
122
|
stubOnError?: HighlightStubPhaseOrFn<Out>;
|
|
111
123
|
/**
|