waygraph 0.12.42 → 0.12.44
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 +20 -1
- package/dist/auto-explore-run.js +49 -0
- package/dist/cli.js +464 -91
- package/dist/engine.d.ts +8 -8
- package/dist/engine.js +8 -8
- package/dist/highlights.d.ts +13 -0
- package/dist/highlights.js +20 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/overlay-beacon.d.ts +44 -0
- package/dist/overlay-beacon.js +85 -0
- package/dist/step-overlay.d.ts +15 -0
- package/dist/step-overlay.js +16 -2
- package/examples/saucedemo/package-lock.json +1 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +16 -0
- package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +11 -7
- package/package.json +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +16 -0
- package/templates/quickstart/src/flows/viewer-blocked.flow.ts +11 -7
package/dist/cli.js
CHANGED
|
@@ -412,10 +412,14 @@ function seedMemForFlow(mem, flowBlocks, json, label) {
|
|
|
412
412
|
const RING_CSS =
|
|
413
413
|
"#wg-ring{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
|
|
414
414
|
"border:2.5px solid #7C3AED;border-radius:10px;" +
|
|
415
|
-
|
|
415
|
+
// Opacity fade only - NEVER transition border-color/box-shadow. Tone swaps
|
|
416
|
+
// (auto gray -> planned purple -> warning yellow) must snap instantly;
|
|
417
|
+
// color transitions read as a muddy gray/purple/yellow morph on ring 2+.
|
|
418
|
+
"box-shadow:0 0 0 4px rgba(124,58,237,.16);transition:opacity .3s ease;}" +
|
|
416
419
|
// Planned (stubs / fixtures / YAP / instruction.highlights) = purple.
|
|
417
420
|
// Automation (verify fallback, unmatched fill/click) = gray - operators
|
|
418
421
|
// can ignore engine checks and watch purple + semantic tones.
|
|
422
|
+
"#wg-ring[data-tone=planned]{border-color:#7C3AED;box-shadow:0 0 0 4px rgba(124,58,237,.16);}" +
|
|
419
423
|
"#wg-ring[data-tone=auto]{border-color:#9CA3AF;box-shadow:0 0 0 4px rgba(156,163,175,.28);}" +
|
|
420
424
|
"#wg-ring[data-tone=info]{border-color:#3B82F6;box-shadow:0 0 0 4px rgba(59,130,246,.22);}" +
|
|
421
425
|
"#wg-ring[data-tone=warning]{border-color:#EAB308;box-shadow:0 0 0 4px rgba(234,179,8,.22);}" +
|
|
@@ -431,7 +435,8 @@ const RING_CSS =
|
|
|
431
435
|
// window.__wgPositionRing in installOverlay.
|
|
432
436
|
"#wg-ring-label{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
|
|
433
437
|
"max-width:min(360px,70vw);white-space:normal;padding:6px 10px;border-radius:7px;background:#7C3AED;color:#fff;" +
|
|
434
|
-
"font:600 12px/1.35 system-ui,sans-serif;transition:opacity .3s ease
|
|
438
|
+
"font:600 12px/1.35 system-ui,sans-serif;transition:opacity .3s ease;}" +
|
|
439
|
+
"#wg-ring-label[data-tone=planned]{background:#7C3AED;color:#fff;}" +
|
|
435
440
|
"#wg-ring-label[data-tone=auto]{background:#6B7280;color:#fff;}" +
|
|
436
441
|
"#wg-ring-label[data-tone=info]{background:#2563EB;color:#fff;}" +
|
|
437
442
|
"#wg-ring-label[data-tone=warning]{background:#EAB308;color:#1c1917;}" +
|
|
@@ -509,6 +514,18 @@ const RING_CSS =
|
|
|
509
514
|
"#wg-panel .wg-pace[data-pace-kind=blitz] .wg-pace-badge{background:#22C55E;color:#052e16;}" +
|
|
510
515
|
"#wg-panel .wg-pace[data-pace-kind=ms] .wg-pace-badge{background:#3B82F6;color:#fff;}" +
|
|
511
516
|
"#wg-panel .wg-narration{margin:0 0 12px;font:italic 14px/1.4 system-ui,sans-serif;color:#f0e8ff;}" +
|
|
517
|
+
// Todo checklist lives in #wg-todo-dock (fixed, outside #wg-panel) so
|
|
518
|
+
// --mini / Hide collapse never hides it (.wg-body {display:none} would).
|
|
519
|
+
// Click dock to slide left <-> right (WAYGRAPH_TODO_POS / --todo-left|right).
|
|
520
|
+
"#wg-todo-dock{position:fixed;z-index:2147483646;top:72px;left:14px;" +
|
|
521
|
+
"width:min(280px,42vw);max-height:calc(100vh - 100px);overflow:auto;box-sizing:border-box;" +
|
|
522
|
+
"padding:10px 12px;background:rgba(20,10,40,.94);color:#fff;border-radius:12px;" +
|
|
523
|
+
"border:1px solid rgba(124,58,237,.45);box-shadow:0 8px 24px rgba(0,0,0,.35);" +
|
|
524
|
+
"pointer-events:auto;cursor:pointer;" +
|
|
525
|
+
"transition:transform .4s cubic-bezier(.22,1,.36,1);transform:translateX(0);}" +
|
|
526
|
+
"#wg-todo-dock[data-pos=right]{transform:translateX(calc(100vw - 100% - 28px));}" +
|
|
527
|
+
"#wg-todo-dock[data-pos=left]{transform:translateX(0);}" +
|
|
528
|
+
"#wg-todo-dock #wg-todos{list-style:none;margin:0;padding:0;background:transparent;border:none;}" +
|
|
512
529
|
"#wg-todos{list-style:none;margin:0 0 12px;padding:8px 10px;background:#0f0620;border-radius:8px;" +
|
|
513
530
|
"border:1px solid #3a2a60;}" +
|
|
514
531
|
"#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;}" +
|
|
@@ -647,10 +664,12 @@ async function installOverlay(page, title) {
|
|
|
647
664
|
// navigation that rebuilds the banner keeps the human's last pick).
|
|
648
665
|
const envPos = (process.env.WAYGRAPH_TITLE_POS || "left").toLowerCase();
|
|
649
666
|
const bannerPos = envPos === "center" || envPos === "right" ? envPos : "left";
|
|
667
|
+
const envTodoPos = (process.env.WAYGRAPH_TODO_POS || "left").toLowerCase();
|
|
668
|
+
const todoPos = envTodoPos === "right" ? "right" : "left";
|
|
650
669
|
const envAutoplay = process.env.WAYGRAPH_AUTOPLAY === "1";
|
|
651
670
|
await page
|
|
652
671
|
.evaluate(
|
|
653
|
-
({ title, favicon, bannerPos, envAutoplay }) => {
|
|
672
|
+
({ title, favicon, bannerPos, todoPos, envAutoplay }) => {
|
|
654
673
|
// Seed the live autoplay toggle from the env default on first ever
|
|
655
674
|
// load only - a real navigation re-runs this, and re-stamping here
|
|
656
675
|
// would silently undo a human's mid-run checkbox click.
|
|
@@ -818,32 +837,46 @@ async function installOverlay(page, title) {
|
|
|
818
837
|
/* private mode / blocked storage - position still applies this page */
|
|
819
838
|
}
|
|
820
839
|
};
|
|
821
|
-
if (title
|
|
822
|
-
|
|
823
|
-
banner
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
saved =
|
|
827
|
-
|
|
828
|
-
|
|
840
|
+
if (title) {
|
|
841
|
+
let banner = document.getElementById("wg-banner");
|
|
842
|
+
if (!banner) {
|
|
843
|
+
banner = document.createElement("div");
|
|
844
|
+
banner.id = "wg-banner";
|
|
845
|
+
let saved = null;
|
|
846
|
+
try {
|
|
847
|
+
saved = localStorage.getItem("wg-banner-pos");
|
|
848
|
+
} catch {
|
|
849
|
+
/* ignore */
|
|
850
|
+
}
|
|
851
|
+
const startPos =
|
|
852
|
+
saved && POSITIONS.includes(saved) ? saved : bannerPos;
|
|
853
|
+
applyPos(banner, startPos);
|
|
854
|
+
banner.title = "Click to move: top left / center / right";
|
|
855
|
+
banner.addEventListener("click", (e) => {
|
|
856
|
+
e.stopPropagation();
|
|
857
|
+
const i = POSITIONS.indexOf(banner.dataset.pos || "left");
|
|
858
|
+
applyPos(banner, POSITIONS[(i + 1) % POSITIONS.length]);
|
|
859
|
+
});
|
|
860
|
+
const tag = document.createElement("span");
|
|
861
|
+
tag.className = "wg-banner-tag";
|
|
862
|
+
tag.textContent = "waygraph demo";
|
|
863
|
+
const text = document.createElement("span");
|
|
864
|
+
text.className = "wg-banner-text";
|
|
865
|
+
text.textContent = title;
|
|
866
|
+
banner.appendChild(tag);
|
|
867
|
+
banner.appendChild(text);
|
|
868
|
+
document.documentElement.appendChild(banner);
|
|
869
|
+
} else {
|
|
870
|
+
// Fixture / episode title changes every step - update in place
|
|
871
|
+
// (banner is created once; do not leave the first step's text stuck).
|
|
872
|
+
let text = banner.querySelector(".wg-banner-text");
|
|
873
|
+
if (!text) {
|
|
874
|
+
text = document.createElement("span");
|
|
875
|
+
text.className = "wg-banner-text";
|
|
876
|
+
banner.appendChild(text);
|
|
877
|
+
}
|
|
878
|
+
text.textContent = title;
|
|
829
879
|
}
|
|
830
|
-
const startPos =
|
|
831
|
-
saved && POSITIONS.includes(saved) ? saved : bannerPos;
|
|
832
|
-
applyPos(banner, startPos);
|
|
833
|
-
banner.title = "Click to move: top left / center / right";
|
|
834
|
-
banner.addEventListener("click", (e) => {
|
|
835
|
-
e.stopPropagation();
|
|
836
|
-
const i = POSITIONS.indexOf(banner.dataset.pos || "left");
|
|
837
|
-
applyPos(banner, POSITIONS[(i + 1) % POSITIONS.length]);
|
|
838
|
-
});
|
|
839
|
-
const tag = document.createElement("span");
|
|
840
|
-
tag.className = "wg-banner-tag";
|
|
841
|
-
tag.textContent = "waygraph demo";
|
|
842
|
-
const text = document.createElement("span");
|
|
843
|
-
text.textContent = title;
|
|
844
|
-
banner.appendChild(tag);
|
|
845
|
-
banner.appendChild(text);
|
|
846
|
-
document.documentElement.appendChild(banner);
|
|
847
880
|
}
|
|
848
881
|
// Tab title/favicon: a real navigation resets document.title and any
|
|
849
882
|
// <link rel="icon"> the new document brings, so re-check (not
|
|
@@ -858,6 +891,47 @@ async function installOverlay(page, title) {
|
|
|
858
891
|
document.head.appendChild(iconLink);
|
|
859
892
|
}
|
|
860
893
|
if (iconLink.href !== favicon) iconLink.href = favicon;
|
|
894
|
+
// Blind-agent overlay beacons: every modal root gets data-wg-ui /
|
|
895
|
+
// data-wg-modal / data-wg-ready so tests can fail on a blank panel.
|
|
896
|
+
window.__wgStampModal = (el, kind, meta) => {
|
|
897
|
+
if (!el) return;
|
|
898
|
+
meta = meta || {};
|
|
899
|
+
el.setAttribute("data-wg-ui", "1");
|
|
900
|
+
el.setAttribute("data-wg-modal", kind || "panel");
|
|
901
|
+
if (meta.phase != null) el.setAttribute("data-wg-phase", String(meta.phase));
|
|
902
|
+
if (meta.step != null) el.setAttribute("data-wg-step", String(meta.step));
|
|
903
|
+
if (meta.total != null) el.setAttribute("data-wg-total", String(meta.total));
|
|
904
|
+
if (meta.block != null) el.setAttribute("data-wg-block", String(meta.block));
|
|
905
|
+
el.setAttribute(
|
|
906
|
+
"data-wg-collapsed",
|
|
907
|
+
el.classList.contains("wg-collapsed") ? "1" : "0",
|
|
908
|
+
);
|
|
909
|
+
el.setAttribute("data-wg-ready", meta.ready === false ? "0" : "1");
|
|
910
|
+
};
|
|
911
|
+
window.__wgOverlayBeacon = () => {
|
|
912
|
+
return Array.from(document.querySelectorAll("[data-wg-ui=\\"1\\"]")).map((el) => {
|
|
913
|
+
const r = el.getBoundingClientRect();
|
|
914
|
+
const st = getComputedStyle(el);
|
|
915
|
+
return {
|
|
916
|
+
id: el.id || null,
|
|
917
|
+
modal: el.getAttribute("data-wg-modal"),
|
|
918
|
+
ready: el.getAttribute("data-wg-ready") === "1",
|
|
919
|
+
phase: el.getAttribute("data-wg-phase"),
|
|
920
|
+
step: el.getAttribute("data-wg-step"),
|
|
921
|
+
block: el.getAttribute("data-wg-block"),
|
|
922
|
+
collapsed:
|
|
923
|
+
el.getAttribute("data-wg-collapsed") === "1" ||
|
|
924
|
+
el.classList.contains("wg-collapsed"),
|
|
925
|
+
opacity: st.opacity,
|
|
926
|
+
textLen: (el.innerText || "").trim().length,
|
|
927
|
+
w: Math.round(r.width),
|
|
928
|
+
h: Math.round(r.height),
|
|
929
|
+
visible: r.width > 0 && r.height > 0 && Number(st.opacity) > 0.05,
|
|
930
|
+
};
|
|
931
|
+
});
|
|
932
|
+
};
|
|
933
|
+
const existingBanner = document.getElementById("wg-banner");
|
|
934
|
+
if (existingBanner) window.__wgStampModal(existingBanner, "banner", { ready: true });
|
|
861
935
|
// Hide/Show for #wg-panel - call after every panel.innerHTML refresh.
|
|
862
936
|
// Collapsed = "N / M · block" + Next (when not auto). Never pass
|
|
863
937
|
// forceCollapsed:false - omit so Hide/localStorage wins.
|
|
@@ -937,6 +1011,7 @@ async function installOverlay(page, title) {
|
|
|
937
1011
|
};
|
|
938
1012
|
const apply = (hidden, persist) => {
|
|
939
1013
|
panel.classList.toggle("wg-collapsed", hidden);
|
|
1014
|
+
panel.setAttribute("data-wg-collapsed", hidden ? "1" : "0");
|
|
940
1015
|
const t = panel.querySelector("[data-wg-toggle]");
|
|
941
1016
|
const titleEl = panel.querySelector(".wg-chrome-title");
|
|
942
1017
|
const label = panel.dataset.wgStepLabel || stepLabel;
|
|
@@ -1004,8 +1079,93 @@ async function installOverlay(page, title) {
|
|
|
1004
1079
|
}
|
|
1005
1080
|
}
|
|
1006
1081
|
};
|
|
1082
|
+
// Floating todo dock - lives outside #wg-panel so --mini / Hide never
|
|
1083
|
+
// hide the checklist. Click cycles left <-> right with CSS transform.
|
|
1084
|
+
// Authors set side via ctx.todoPos("left"|"right"); env / --todo-* fallback.
|
|
1085
|
+
window.__wgSyncTodos = (list, pos) => {
|
|
1086
|
+
list = Array.isArray(list) ? list : [];
|
|
1087
|
+
const POS = ["left", "right"];
|
|
1088
|
+
let dock = document.getElementById("wg-todo-dock");
|
|
1089
|
+
// Drop any in-panel leftover (old builds put #wg-todos in .wg-body).
|
|
1090
|
+
document.querySelectorAll("#wg-panel #wg-todos").forEach((el) => el.remove());
|
|
1091
|
+
if (!list.length) {
|
|
1092
|
+
if (dock) dock.remove();
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
const wantPos =
|
|
1096
|
+
pos === "left" || pos === "right"
|
|
1097
|
+
? pos
|
|
1098
|
+
: null;
|
|
1099
|
+
if (!dock) {
|
|
1100
|
+
dock = document.createElement("div");
|
|
1101
|
+
dock.id = "wg-todo-dock";
|
|
1102
|
+
dock.setAttribute("data-wg-ui", "1");
|
|
1103
|
+
dock.setAttribute("data-wg-modal", "todos");
|
|
1104
|
+
dock.setAttribute("data-wg-ready", "1");
|
|
1105
|
+
let saved = null;
|
|
1106
|
+
try {
|
|
1107
|
+
saved = localStorage.getItem("wg-todo-pos");
|
|
1108
|
+
} catch {
|
|
1109
|
+
/* ignore */
|
|
1110
|
+
}
|
|
1111
|
+
const start =
|
|
1112
|
+
wantPos ||
|
|
1113
|
+
(saved && POS.includes(saved) ? saved : todoPos === "right" ? "right" : "left");
|
|
1114
|
+
dock.dataset.pos = start;
|
|
1115
|
+
dock.title = "Click to move checklist: left / right";
|
|
1116
|
+
dock.addEventListener("click", (e) => {
|
|
1117
|
+
e.stopPropagation();
|
|
1118
|
+
const i = POS.indexOf(dock.dataset.pos || "left");
|
|
1119
|
+
const next = POS[(i + 1) % POS.length];
|
|
1120
|
+
dock.dataset.pos = next;
|
|
1121
|
+
try {
|
|
1122
|
+
localStorage.setItem("wg-todo-pos", next);
|
|
1123
|
+
} catch {
|
|
1124
|
+
/* ignore */
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
document.documentElement.appendChild(dock);
|
|
1128
|
+
} else if (wantPos) {
|
|
1129
|
+
dock.dataset.pos = wantPos;
|
|
1130
|
+
try {
|
|
1131
|
+
localStorage.setItem("wg-todo-pos", wantPos);
|
|
1132
|
+
} catch {
|
|
1133
|
+
/* ignore */
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
const esc = (s) =>
|
|
1137
|
+
String(s)
|
|
1138
|
+
.replace(/&/g, "&")
|
|
1139
|
+
.replace(/</g, "<")
|
|
1140
|
+
.replace(/>/g, ">");
|
|
1141
|
+
dock.innerHTML =
|
|
1142
|
+
'<ul id="wg-todos">' +
|
|
1143
|
+
list
|
|
1144
|
+
.map((t) => {
|
|
1145
|
+
const cls = t.current
|
|
1146
|
+
? "wg-todo-current"
|
|
1147
|
+
: t.done
|
|
1148
|
+
? "wg-todo-done"
|
|
1149
|
+
: "wg-todo-pending";
|
|
1150
|
+
const mark = t.done ? "\u2713" : t.current ? "\u2192" : "\u25CB";
|
|
1151
|
+
return (
|
|
1152
|
+
'<li class="' +
|
|
1153
|
+
cls +
|
|
1154
|
+
'"><span class="wg-todo-mark">' +
|
|
1155
|
+
mark +
|
|
1156
|
+
"</span><span>" +
|
|
1157
|
+
esc(t.text || "") +
|
|
1158
|
+
"</span></li>"
|
|
1159
|
+
);
|
|
1160
|
+
})
|
|
1161
|
+
.join("") +
|
|
1162
|
+
"</ul>";
|
|
1163
|
+
if (window.__wgStampModal) {
|
|
1164
|
+
window.__wgStampModal(dock, "todos", { ready: true });
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1007
1167
|
},
|
|
1008
|
-
{ title, favicon: WAYGRAPH_FAVICON, bannerPos, envAutoplay },
|
|
1168
|
+
{ title, favicon: WAYGRAPH_FAVICON, bannerPos, todoPos, envAutoplay },
|
|
1009
1169
|
)
|
|
1010
1170
|
.catch(() => {});
|
|
1011
1171
|
}
|
|
@@ -1027,6 +1187,8 @@ async function renderBeforeStep(page, info) {
|
|
|
1027
1187
|
if (window.__wgHideCursor) window.__wgHideCursor();
|
|
1028
1188
|
})
|
|
1029
1189
|
.catch(() => {});
|
|
1190
|
+
// miniStepLabel / todosHtmlFromInfo are Node-only — browser evaluate has no closure.
|
|
1191
|
+
const payload = { ...info, stepLabel: miniStepLabel(info) };
|
|
1030
1192
|
await page
|
|
1031
1193
|
.evaluate((info) => {
|
|
1032
1194
|
// Reused in place, not removed + recreated, every step - the old
|
|
@@ -1093,14 +1255,15 @@ async function renderBeforeStep(page, info) {
|
|
|
1093
1255
|
esc(info.paceLabel || "") +
|
|
1094
1256
|
"</span></div>"
|
|
1095
1257
|
: "";
|
|
1258
|
+
// Todos render into floating #wg-todo-dock via __wgSyncTodos - never
|
|
1259
|
+
// inside .wg-body (would vanish under --mini / Hide collapse).
|
|
1096
1260
|
let html =
|
|
1097
1261
|
"<div id=\\"wg-progress\\"><div id=\\"wg-progress-bar\\" style=\\"width:" + pct + "%\\"></div></div>" +
|
|
1098
1262
|
episodesHtml +
|
|
1099
1263
|
paceHtml +
|
|
1100
1264
|
"<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
|
|
1101
1265
|
"<h3>Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + "</h3>" +
|
|
1102
|
-
narrationHtml
|
|
1103
|
-
todosHtmlFromInfo(info, esc);
|
|
1266
|
+
narrationHtml;
|
|
1104
1267
|
if (info.keys.length === 0) {
|
|
1105
1268
|
html += "<div class=\\"wg-key\\">(no MemKeys required)</div>";
|
|
1106
1269
|
}
|
|
@@ -1130,13 +1293,25 @@ async function renderBeforeStep(page, info) {
|
|
|
1130
1293
|
if (isNewPanel) {
|
|
1131
1294
|
document.documentElement.appendChild(panel);
|
|
1132
1295
|
requestAnimationFrame(() => panel.classList.add("wg-in"));
|
|
1296
|
+
} else {
|
|
1297
|
+
panel.classList.add("wg-in");
|
|
1133
1298
|
}
|
|
1134
1299
|
if (window.__wgWirePanelChrome) {
|
|
1135
1300
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1136
|
-
stepLabel:
|
|
1301
|
+
stepLabel: info.stepLabel || "",
|
|
1137
1302
|
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1138
1303
|
});
|
|
1139
1304
|
}
|
|
1305
|
+
if (window.__wgSyncTodos) window.__wgSyncTodos(info.todos || [], info.todoPos || null);
|
|
1306
|
+
if (window.__wgStampModal) {
|
|
1307
|
+
window.__wgStampModal(panel, "panel", {
|
|
1308
|
+
phase: "before",
|
|
1309
|
+
step: info.index + 1,
|
|
1310
|
+
total: info.total,
|
|
1311
|
+
block: info.blockName || "",
|
|
1312
|
+
ready: true,
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1140
1315
|
// Live, human-readable preview of what a MemKey's raw JSON will
|
|
1141
1316
|
// actually write - object fields become "Field: value" lines
|
|
1142
1317
|
// (camelCase split the same way state tags already are); a
|
|
@@ -1206,8 +1381,40 @@ async function renderBeforeStep(page, info) {
|
|
|
1206
1381
|
if (auto) auto.style.display = cb.checked ? "" : "none";
|
|
1207
1382
|
});
|
|
1208
1383
|
}
|
|
1209
|
-
},
|
|
1384
|
+
}, payload)
|
|
1210
1385
|
.catch(() => {});
|
|
1386
|
+
if (process.env.WAYGRAPH_PROVE_SHOT || process.env.WAYGRAPH_PROVE_EXIT === "1") {
|
|
1387
|
+
// Default: prove once on first painted panel. PROVE_EVERY=1 re-checks
|
|
1388
|
+
// every step (noisy; useful when hunting a mid-chain blank).
|
|
1389
|
+
const proveEvery = process.env.WAYGRAPH_PROVE_EVERY === "1";
|
|
1390
|
+
const already = globalThis.__wgProveDone;
|
|
1391
|
+
if (!already || proveEvery || process.env.WAYGRAPH_PROVE_EXIT === "1") {
|
|
1392
|
+
await page.waitForTimeout(500).catch(() => {});
|
|
1393
|
+
if (process.env.WAYGRAPH_PROVE_SHOT) {
|
|
1394
|
+
await page.screenshot({ path: process.env.WAYGRAPH_PROVE_SHOT }).catch(() => {});
|
|
1395
|
+
}
|
|
1396
|
+
const ok = await page
|
|
1397
|
+
.evaluate(() => {
|
|
1398
|
+
const beacons =
|
|
1399
|
+
typeof window.__wgOverlayBeacon === "function" ? window.__wgOverlayBeacon() : [];
|
|
1400
|
+
const panel = beacons.find((b) => b.modal === "panel" && b.ready && b.visible);
|
|
1401
|
+
const textOk = panel && panel.textLen >= 12;
|
|
1402
|
+
const opacityOk = panel && Number(panel.opacity) > 0.5;
|
|
1403
|
+
return {
|
|
1404
|
+
ok: !!(panel && textOk && opacityOk),
|
|
1405
|
+
beacons,
|
|
1406
|
+
hasRun: !!document.getElementById("wg-run"),
|
|
1407
|
+
readyAttr: document.getElementById("wg-panel")?.getAttribute("data-wg-ready") || null,
|
|
1408
|
+
};
|
|
1409
|
+
})
|
|
1410
|
+
.catch((e) => ({ ok: false, reason: String(e && e.message ? e.message : e) }));
|
|
1411
|
+
console.error("WAYGRAPH_PROVE " + JSON.stringify(ok));
|
|
1412
|
+
globalThis.__wgProveDone = true;
|
|
1413
|
+
if (process.env.WAYGRAPH_PROVE_EXIT === "1") {
|
|
1414
|
+
process.exit(ok && ok.ok ? 0 : 2);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1211
1418
|
}
|
|
1212
1419
|
|
|
1213
1420
|
async function ensureSelectorInView(page, selector) {
|
|
@@ -1335,6 +1542,85 @@ function miniStepLabel(info) {
|
|
|
1335
1542
|
);
|
|
1336
1543
|
}
|
|
1337
1544
|
|
|
1545
|
+
/**
|
|
1546
|
+
* stubOnError rings + amber "expected outcome" / red error panel, then wait
|
|
1547
|
+
* for Stop/Retry. Used when a step throws OR when withExpectedFailure's last
|
|
1548
|
+
* block succeeds on the intentional fail branch (e.g. LoginPage + error banner).
|
|
1549
|
+
*/
|
|
1550
|
+
async function presentFailPanel(page, opts) {
|
|
1551
|
+
const {
|
|
1552
|
+
block,
|
|
1553
|
+
fixtures,
|
|
1554
|
+
error,
|
|
1555
|
+
index,
|
|
1556
|
+
total,
|
|
1557
|
+
blockName,
|
|
1558
|
+
message,
|
|
1559
|
+
allNames,
|
|
1560
|
+
allDescriptions,
|
|
1561
|
+
moduleIndex,
|
|
1562
|
+
allEpisodes,
|
|
1563
|
+
title,
|
|
1564
|
+
episodeNumber,
|
|
1565
|
+
episodeTitle,
|
|
1566
|
+
expectedFailureReason,
|
|
1567
|
+
stepperMode,
|
|
1568
|
+
gatesFast,
|
|
1569
|
+
demoPace,
|
|
1570
|
+
gate,
|
|
1571
|
+
} = opts;
|
|
1572
|
+
if (hasAuthoredStubOnError(block, fixtures)) {
|
|
1573
|
+
await page
|
|
1574
|
+
.evaluate(() => {
|
|
1575
|
+
if (window.__wgRingTrack) {
|
|
1576
|
+
window.removeEventListener("resize", window.__wgRingTrack);
|
|
1577
|
+
window.removeEventListener("scroll", window.__wgRingTrack, true);
|
|
1578
|
+
window.__wgRingTrack = null;
|
|
1579
|
+
}
|
|
1580
|
+
})
|
|
1581
|
+
.catch(() => {});
|
|
1582
|
+
const errPhase = await runStubPhase(block, "stubOnError", {
|
|
1583
|
+
fixtures,
|
|
1584
|
+
error,
|
|
1585
|
+
out: opts.out,
|
|
1586
|
+
});
|
|
1587
|
+
const errHighlights = errPhase.highlights.map((h) => {
|
|
1588
|
+
const styled = applyHighlightStyleDefaults(h, opts.highlightStyle);
|
|
1589
|
+
return {
|
|
1590
|
+
selector: styled.selector,
|
|
1591
|
+
label: formatHighlightCaption(styled),
|
|
1592
|
+
duration: styled.duration,
|
|
1593
|
+
fastMode: styled.fastMode,
|
|
1594
|
+
tone: styled.tone,
|
|
1595
|
+
size: styled.size,
|
|
1596
|
+
weight: styled.weight,
|
|
1597
|
+
zoom: styled.zoom,
|
|
1598
|
+
};
|
|
1599
|
+
});
|
|
1600
|
+
await cycleHighlightRings(page, errHighlights, !!gatesFast, {
|
|
1601
|
+
defaultHoldMs: 2000,
|
|
1602
|
+
pace: demoPace,
|
|
1603
|
+
todos: errPhase.todos,
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
await renderStepError(page, {
|
|
1607
|
+
index,
|
|
1608
|
+
total,
|
|
1609
|
+
blockName,
|
|
1610
|
+
message,
|
|
1611
|
+
allNames,
|
|
1612
|
+
allDescriptions,
|
|
1613
|
+
moduleIndex,
|
|
1614
|
+
allEpisodes,
|
|
1615
|
+
title,
|
|
1616
|
+
episodeNumber,
|
|
1617
|
+
episodeTitle,
|
|
1618
|
+
expectedFailureReason,
|
|
1619
|
+
stepperMode,
|
|
1620
|
+
});
|
|
1621
|
+
return gate();
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1338
1624
|
async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
1339
1625
|
const list = highlights || [];
|
|
1340
1626
|
// defaultHoldMs: when set (fail path), use instead of legacy 900/200 so BUG
|
|
@@ -1359,6 +1645,11 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
|
1359
1645
|
: normalizeTodos(h.todos, h.todoIndex);
|
|
1360
1646
|
await page
|
|
1361
1647
|
.evaluate((todos) => {
|
|
1648
|
+
if (window.__wgSyncTodos) {
|
|
1649
|
+
window.__wgSyncTodos(todos);
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
// Fallback for pages that never got installOverlay sync helper.
|
|
1362
1653
|
const ul = document.getElementById("wg-todos");
|
|
1363
1654
|
if (!ul) return;
|
|
1364
1655
|
ul.innerHTML = todos
|
|
@@ -1447,7 +1738,9 @@ async function renderAfterStep(page, info) {
|
|
|
1447
1738
|
// 900ms / 200ms holds when set.
|
|
1448
1739
|
await cycleHighlightRings(page, highlights, gatesFast, {
|
|
1449
1740
|
pace: info.pace,
|
|
1741
|
+
stepLabel: miniStepLabel(info),
|
|
1450
1742
|
});
|
|
1743
|
+
const afterPayload = { ...info, stepLabel: miniStepLabel(info) };
|
|
1451
1744
|
await page
|
|
1452
1745
|
.evaluate((info) => {
|
|
1453
1746
|
// Reused in place - see renderBeforeStep's own comment on this.
|
|
@@ -1525,25 +1818,38 @@ async function renderAfterStep(page, info) {
|
|
|
1525
1818
|
escA(info.paceLabel || "") +
|
|
1526
1819
|
"</span></div>"
|
|
1527
1820
|
: "";
|
|
1821
|
+
const todosHtml = ""; // todos float in #wg-todo-dock via __wgSyncTodos
|
|
1528
1822
|
panel.innerHTML =
|
|
1529
1823
|
"<div id=\\"wg-progress\\"><div id=\\"wg-progress-bar\\" style=\\"width:" + pct + "%\\"></div></div>" +
|
|
1530
1824
|
episodesHtml +
|
|
1531
1825
|
paceHtml +
|
|
1532
1826
|
"<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
|
|
1533
1827
|
"<h3>" + heading + "</h3>" +
|
|
1534
|
-
|
|
1828
|
+
todosHtml +
|
|
1535
1829
|
resultHtml +
|
|
1536
1830
|
gateHtml;
|
|
1537
1831
|
if (isNewPanel) {
|
|
1538
1832
|
document.documentElement.appendChild(panel);
|
|
1539
1833
|
requestAnimationFrame(() => panel.classList.add("wg-in"));
|
|
1834
|
+
} else {
|
|
1835
|
+
panel.classList.add("wg-in");
|
|
1540
1836
|
}
|
|
1541
1837
|
if (window.__wgWirePanelChrome) {
|
|
1542
1838
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1543
|
-
stepLabel:
|
|
1839
|
+
stepLabel: info.stepLabel || "",
|
|
1544
1840
|
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1545
1841
|
});
|
|
1546
1842
|
}
|
|
1843
|
+
if (window.__wgSyncTodos) window.__wgSyncTodos(info.todos || [], info.todoPos || null);
|
|
1844
|
+
if (window.__wgStampModal) {
|
|
1845
|
+
window.__wgStampModal(panel, "panel", {
|
|
1846
|
+
phase: "after",
|
|
1847
|
+
step: info.index + 1,
|
|
1848
|
+
total: info.total,
|
|
1849
|
+
block: info.blockName || "",
|
|
1850
|
+
ready: true,
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1547
1853
|
panel.querySelectorAll(".wg-toggle-btn").forEach((btn) => {
|
|
1548
1854
|
btn.addEventListener("click", () => {
|
|
1549
1855
|
const wantPretty = btn.getAttribute("data-mode") === "pretty";
|
|
@@ -1570,7 +1876,7 @@ async function renderAfterStep(page, info) {
|
|
|
1570
1876
|
if (auto) auto.style.display = cb.checked ? "" : "none";
|
|
1571
1877
|
});
|
|
1572
1878
|
}
|
|
1573
|
-
},
|
|
1879
|
+
}, afterPayload)
|
|
1574
1880
|
.catch(() => {});
|
|
1575
1881
|
}
|
|
1576
1882
|
|
|
@@ -1582,6 +1888,7 @@ async function renderAfterStep(page, info) {
|
|
|
1582
1888
|
*/
|
|
1583
1889
|
async function renderStepError(page, info) {
|
|
1584
1890
|
await installOverlay(page, info.title);
|
|
1891
|
+
const errorPayload = { ...info, stepLabel: miniStepLabel(info) };
|
|
1585
1892
|
await page
|
|
1586
1893
|
.evaluate((info) => {
|
|
1587
1894
|
// Reused in place - see renderBeforeStep's own comment on this.
|
|
@@ -1639,18 +1946,29 @@ async function renderStepError(page, info) {
|
|
|
1639
1946
|
if (isNewPanel) {
|
|
1640
1947
|
document.documentElement.appendChild(panel);
|
|
1641
1948
|
requestAnimationFrame(() => panel.classList.add("wg-in"));
|
|
1949
|
+
} else {
|
|
1950
|
+
panel.classList.add("wg-in");
|
|
1642
1951
|
}
|
|
1643
1952
|
if (window.__wgWirePanelChrome) {
|
|
1644
1953
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1645
|
-
stepLabel:
|
|
1954
|
+
stepLabel: info.stepLabel || "",
|
|
1646
1955
|
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1647
1956
|
});
|
|
1648
1957
|
}
|
|
1958
|
+
if (window.__wgStampModal) {
|
|
1959
|
+
window.__wgStampModal(panel, "panel", {
|
|
1960
|
+
phase: "error",
|
|
1961
|
+
step: info.index + 1,
|
|
1962
|
+
total: info.total,
|
|
1963
|
+
block: info.blockName || "",
|
|
1964
|
+
ready: true,
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1649
1967
|
const runBtn = document.getElementById("wg-run");
|
|
1650
1968
|
if (runBtn) runBtn.addEventListener("click", () => window.__wgNext({}));
|
|
1651
1969
|
const retryBtn = document.getElementById("wg-error-retry");
|
|
1652
1970
|
if (retryBtn) retryBtn.addEventListener("click", () => window.__wgNext({ __wgRetry: "1" }));
|
|
1653
|
-
},
|
|
1971
|
+
}, errorPayload)
|
|
1654
1972
|
.catch(() => {});
|
|
1655
1973
|
}
|
|
1656
1974
|
|
|
@@ -1761,6 +2079,14 @@ async function markStepRunning(page, _opts) {
|
|
|
1761
2079
|
const panel = document.getElementById("wg-panel");
|
|
1762
2080
|
if (panel) {
|
|
1763
2081
|
panel.classList.add("wg-collapsed");
|
|
2082
|
+
panel.setAttribute("data-wg-collapsed", "1");
|
|
2083
|
+
panel.setAttribute("data-wg-phase", "running");
|
|
2084
|
+
if (typeof window.__wgStampModal === "function") {
|
|
2085
|
+
window.__wgStampModal(panel, "panel", {
|
|
2086
|
+
phase: "running",
|
|
2087
|
+
ready: true,
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
1764
2090
|
const toggle = panel.querySelector("[data-wg-toggle]");
|
|
1765
2091
|
if (toggle) toggle.textContent = "Show";
|
|
1766
2092
|
const titleEl = panel.querySelector(".wg-chrome-title");
|
|
@@ -1978,6 +2304,15 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
1978
2304
|
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1979
2305
|
});
|
|
1980
2306
|
}
|
|
2307
|
+
if (window.__wgStampModal) {
|
|
2308
|
+
window.__wgStampModal(panel, "panel", {
|
|
2309
|
+
phase: "slide",
|
|
2310
|
+
step: info.index + 1,
|
|
2311
|
+
total: info.total,
|
|
2312
|
+
block: info.blockName || "",
|
|
2313
|
+
ready: true,
|
|
2314
|
+
});
|
|
2315
|
+
}
|
|
1981
2316
|
let autoNow = false;
|
|
1982
2317
|
try {
|
|
1983
2318
|
autoNow = localStorage.getItem("wg-autoplay") === "1";
|
|
@@ -2128,7 +2463,7 @@ async function teardownOverlay(page) {
|
|
|
2128
2463
|
window.__wgRingTrack = null;
|
|
2129
2464
|
}
|
|
2130
2465
|
window.__wgNarrateOwnsRing = false;
|
|
2131
|
-
for (const id of ["wg-panel", "wg-ring", "wg-ring-label", "wg-cursor", "wg-click-pulse", "wg-banner"]) {
|
|
2466
|
+
for (const id of ["wg-panel", "wg-ring", "wg-ring-label", "wg-cursor", "wg-click-pulse", "wg-banner", "wg-todo-dock"]) {
|
|
2132
2467
|
const el = document.getElementById(id);
|
|
2133
2468
|
if (el) el.remove();
|
|
2134
2469
|
}
|
|
@@ -2451,7 +2786,10 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2451
2786
|
// installOverlay only seeds localStorage when the key is null (so mid-run
|
|
2452
2787
|
// checkbox clicks survive navigations). Without this, a prior --autoplay
|
|
2453
2788
|
// session sticks forever and agents cannot flip back with --no-autoplay.
|
|
2454
|
-
|
|
2789
|
+
// Must run on the *demo origin* (after goto), not about:blank - localStorage
|
|
2790
|
+
// is origin-scoped.
|
|
2791
|
+
const syncAutoplayFromEnv = async () => {
|
|
2792
|
+
if (process.env.WAYGRAPH_AUTOPLAY === undefined) return;
|
|
2455
2793
|
const on = process.env.WAYGRAPH_AUTOPLAY === "1";
|
|
2456
2794
|
await page
|
|
2457
2795
|
.evaluate((want) => {
|
|
@@ -2462,7 +2800,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2462
2800
|
}
|
|
2463
2801
|
}, on)
|
|
2464
2802
|
.catch(() => {});
|
|
2465
|
-
}
|
|
2803
|
+
};
|
|
2804
|
+
await syncAutoplayFromEnv();
|
|
2466
2805
|
// Some real Blocks (e.g. zsign-all's login.block.ts) call
|
|
2467
2806
|
// page.setViewportSize({ width: 1280, height: 720 }) inside their own
|
|
2468
2807
|
// act() - a hardcoded override for THEIR OWN testing consistency, with no
|
|
@@ -2616,6 +2955,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2616
2955
|
// still rendered on screen.
|
|
2617
2956
|
if ((clearSession || r.resetSession) && i > 0) {
|
|
2618
2957
|
await resetPageState(context, page, baseURL);
|
|
2958
|
+
// clear() wiped origin storage - re-apply CLI autoplay default.
|
|
2959
|
+
await syncAutoplayFromEnv();
|
|
2619
2960
|
}
|
|
2620
2961
|
// Episode pace: block > flow > CLI --fast (only when no authored pace).
|
|
2621
2962
|
// Authoring wins: withDemoPace("slow"|2|4500) keeps slow dwell even under --fast.
|
|
@@ -2653,6 +2994,12 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2653
2994
|
applyHighlightStyleDefaults(h, flowStyle),
|
|
2654
2995
|
);
|
|
2655
2996
|
const stubBeforeTodos = stubBeforePhase.todos || [];
|
|
2997
|
+
const overlayTitle =
|
|
2998
|
+
(stubBeforePhase.title && String(stubBeforePhase.title).trim()) ||
|
|
2999
|
+
(r.episodeTitle && String(r.episodeTitle).trim()) ||
|
|
3000
|
+
title ||
|
|
3001
|
+
"waygraph demo";
|
|
3002
|
+
const overlayTodoPos = stubBeforePhase.todoPos || undefined;
|
|
2656
3003
|
const isNavBlock = r.block.__waygraphKind === "nav";
|
|
2657
3004
|
const autoNow = await currentAutoplay();
|
|
2658
3005
|
// Video / --mini: compact pill. Never pass false - Hide/localStorage wins.
|
|
@@ -2697,7 +3044,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2697
3044
|
moduleIndex,
|
|
2698
3045
|
allEpisodes,
|
|
2699
3046
|
justEnteredEpisode,
|
|
2700
|
-
title,
|
|
3047
|
+
title: overlayTitle,
|
|
3048
|
+
todoPos: overlayTodoPos,
|
|
2701
3049
|
episodeNumber: r.episodeNumber,
|
|
2702
3050
|
episodeTitle: r.episodeTitle,
|
|
2703
3051
|
stepperMode,
|
|
@@ -2748,47 +3096,12 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2748
3096
|
delete window.__wgPendingNavClickLabel;
|
|
2749
3097
|
})
|
|
2750
3098
|
.catch(() => {});
|
|
2751
|
-
// stubOnError
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
if (window.__wgRingTrack) {
|
|
2758
|
-
window.removeEventListener("resize", window.__wgRingTrack);
|
|
2759
|
-
window.removeEventListener("scroll", window.__wgRingTrack, true);
|
|
2760
|
-
window.__wgRingTrack = null;
|
|
2761
|
-
}
|
|
2762
|
-
})
|
|
2763
|
-
.catch(() => {});
|
|
2764
|
-
const errPhase = await runStubPhase(r.block, "stubOnError", {
|
|
2765
|
-
fixtures: fixturesOnError,
|
|
2766
|
-
error: err,
|
|
2767
|
-
});
|
|
2768
|
-
const errHighlights = errPhase.highlights.map((h) => {
|
|
2769
|
-
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
2770
|
-
return {
|
|
2771
|
-
selector: styled.selector,
|
|
2772
|
-
label: formatHighlightCaption(styled),
|
|
2773
|
-
duration: styled.duration,
|
|
2774
|
-
fastMode: styled.fastMode,
|
|
2775
|
-
tone: styled.tone,
|
|
2776
|
-
size: styled.size,
|
|
2777
|
-
weight: styled.weight,
|
|
2778
|
-
zoom: styled.zoom,
|
|
2779
|
-
};
|
|
2780
|
-
});
|
|
2781
|
-
await cycleHighlightRings(page, errHighlights, !!pacing.gatesFast, {
|
|
2782
|
-
defaultHoldMs: 2000,
|
|
2783
|
-
pace: pacing.demoPace,
|
|
2784
|
-
todos: errPhase.todos,
|
|
2785
|
-
});
|
|
2786
|
-
}
|
|
2787
|
-
// A thrown act()/observe()/a failed verify Trait used to just crash
|
|
2788
|
-
// the whole Node process with a raw stack trace - the browser closes
|
|
2789
|
-
// (main()'s own try/finally) before a human watching ever sees WHY.
|
|
2790
|
-
// Show it on-screen and let a real click acknowledge it first.
|
|
2791
|
-
await renderStepError(page, {
|
|
3099
|
+
// stubOnError + error/expected panel BEFORE acknowledging (PIA RFC).
|
|
3100
|
+
const errEdits = await presentFailPanel(page, {
|
|
3101
|
+
block: r.block,
|
|
3102
|
+
fixtures: r.highlightFixtures,
|
|
3103
|
+
highlightStyle: r.highlightStyle,
|
|
3104
|
+
error: err,
|
|
2792
3105
|
index: i,
|
|
2793
3106
|
total: resolved.length,
|
|
2794
3107
|
blockName: r.block.name,
|
|
@@ -2802,14 +3115,13 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2802
3115
|
episodeTitle: r.episodeTitle,
|
|
2803
3116
|
expectedFailureReason: r.expectedFailureReason,
|
|
2804
3117
|
stepperMode,
|
|
2805
|
-
|
|
3118
|
+
gatesFast: pacing.gatesFast,
|
|
3119
|
+
demoPace: pacing.demoPace,
|
|
3120
|
+
gate,
|
|
2806
3121
|
});
|
|
2807
|
-
const errEdits = await gate();
|
|
2808
3122
|
if (errEdits && errEdits.__wgRetry) {
|
|
2809
|
-
// Same clean slate a normal episode boundary gets - not just
|
|
2810
|
-
// re-running the failed block against whatever broken/half-
|
|
2811
|
-
// navigated state it left the page in.
|
|
2812
3123
|
await resetPageState(context, page, baseURL);
|
|
3124
|
+
await syncAutoplayFromEnv();
|
|
2813
3125
|
i = episodeStartIndex - 1;
|
|
2814
3126
|
continue;
|
|
2815
3127
|
}
|
|
@@ -2831,6 +3143,50 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2831
3143
|
})
|
|
2832
3144
|
.catch(() => {});
|
|
2833
3145
|
result = stepOutcome.result;
|
|
3146
|
+
// withExpectedFailure last block that SUCCEEDS on the intentional fail
|
|
3147
|
+
// branch (e.g. submit-login -> LoginPage + error banner). Still show
|
|
3148
|
+
// stubOnError rings + amber "expected outcome" panel - branching no
|
|
3149
|
+
// longer throws, but the demo must not skip the educational overlay.
|
|
3150
|
+
if (r.expectedFailureReason) {
|
|
3151
|
+
const errEdits = await presentFailPanel(page, {
|
|
3152
|
+
block: r.block,
|
|
3153
|
+
fixtures: r.highlightFixtures,
|
|
3154
|
+
highlightStyle: r.highlightStyle,
|
|
3155
|
+
error: undefined,
|
|
3156
|
+
out: result,
|
|
3157
|
+
index: i,
|
|
3158
|
+
total: resolved.length,
|
|
3159
|
+
blockName: r.block.name,
|
|
3160
|
+
message:
|
|
3161
|
+
"Resolved " +
|
|
3162
|
+
(result && result.__state ? result.__state : JSON.stringify(result)) +
|
|
3163
|
+
" (expected failure path)",
|
|
3164
|
+
allNames: moduleNames,
|
|
3165
|
+
allDescriptions: moduleDescriptions,
|
|
3166
|
+
moduleIndex,
|
|
3167
|
+
allEpisodes,
|
|
3168
|
+
title,
|
|
3169
|
+
episodeNumber: r.episodeNumber,
|
|
3170
|
+
episodeTitle: r.episodeTitle,
|
|
3171
|
+
expectedFailureReason: r.expectedFailureReason,
|
|
3172
|
+
stepperMode,
|
|
3173
|
+
gatesFast: pacing.gatesFast,
|
|
3174
|
+
demoPace: pacing.demoPace,
|
|
3175
|
+
gate,
|
|
3176
|
+
});
|
|
3177
|
+
if (errEdits && errEdits.__wgRetry) {
|
|
3178
|
+
await resetPageState(context, page, baseURL);
|
|
3179
|
+
await syncAutoplayFromEnv();
|
|
3180
|
+
i = episodeStartIndex - 1;
|
|
3181
|
+
continue;
|
|
3182
|
+
}
|
|
3183
|
+
if (process.env.WAYGRAPH_JSON !== "1") {
|
|
3184
|
+
console.log(
|
|
3185
|
+
"waygraph: expected failure on " + r.block.name + " - " + r.expectedFailureReason,
|
|
3186
|
+
);
|
|
3187
|
+
}
|
|
3188
|
+
break;
|
|
3189
|
+
}
|
|
2834
3190
|
const fixturesAfter = r.highlightFixtures;
|
|
2835
3191
|
const slides = resolveSlides(r.block, { out: result, fixtures: fixturesAfter }).map((s) =>
|
|
2836
3192
|
applyHighlightStyleDefaults(s, r.highlightStyle),
|
|
@@ -2848,12 +3204,18 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2848
3204
|
}
|
|
2849
3205
|
let highlights;
|
|
2850
3206
|
let stubAfterTodos = [];
|
|
3207
|
+
let afterOverlayTitle = overlayTitle;
|
|
3208
|
+
let afterTodoPos = overlayTodoPos;
|
|
2851
3209
|
if (hasAuthoredStubAfter(r.block, result, fixturesAfter)) {
|
|
2852
3210
|
const afterPhase = await runStubPhase(r.block, "stubAfter", {
|
|
2853
3211
|
out: result,
|
|
2854
3212
|
fixtures: fixturesAfter,
|
|
2855
3213
|
});
|
|
2856
3214
|
stubAfterTodos = afterPhase.todos || [];
|
|
3215
|
+
if (afterPhase.title && String(afterPhase.title).trim()) {
|
|
3216
|
+
afterOverlayTitle = String(afterPhase.title).trim();
|
|
3217
|
+
}
|
|
3218
|
+
if (afterPhase.todoPos) afterTodoPos = afterPhase.todoPos;
|
|
2857
3219
|
highlights = afterPhase.highlights.map((h) => {
|
|
2858
3220
|
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
2859
3221
|
return {
|
|
@@ -2887,7 +3249,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2887
3249
|
allDescriptions: moduleDescriptions,
|
|
2888
3250
|
moduleIndex,
|
|
2889
3251
|
allEpisodes,
|
|
2890
|
-
title,
|
|
3252
|
+
title: afterOverlayTitle,
|
|
3253
|
+
todoPos: afterTodoPos,
|
|
2891
3254
|
episodeNumber: r.episodeNumber,
|
|
2892
3255
|
episodeTitle: r.episodeTitle,
|
|
2893
3256
|
stepperMode,
|
|
@@ -3915,6 +4278,12 @@ function parseRunFlags(argv) {
|
|
|
3915
4278
|
else if (a === "--mini" || a === "--stepper-mini") {
|
|
3916
4279
|
out.miniStepper = true;
|
|
3917
4280
|
}
|
|
4281
|
+
else if (a === "--todo-left") {
|
|
4282
|
+
out.todoPos = "left";
|
|
4283
|
+
}
|
|
4284
|
+
else if (a === "--todo-right") {
|
|
4285
|
+
out.todoPos = "right";
|
|
4286
|
+
}
|
|
3918
4287
|
else if (a === "--ff-expand") {
|
|
3919
4288
|
out.ffExpand = true;
|
|
3920
4289
|
}
|
|
@@ -4035,8 +4404,8 @@ function applyRunFlags(flags, opts) {
|
|
|
4035
4404
|
process.env.WAYGRAPH_HEADED = "0";
|
|
4036
4405
|
}
|
|
4037
4406
|
}
|
|
4038
|
-
if ((flags.fast || flags.fullStepper || flags.miniStepper) && !opts?.allowDemoUi) {
|
|
4039
|
-
console.error("waygraph: --fast / --full / --mini are demo-only flags");
|
|
4407
|
+
if ((flags.fast || flags.fullStepper || flags.miniStepper || flags.todoPos) && !opts?.allowDemoUi) {
|
|
4408
|
+
console.error("waygraph: --fast / --full / --mini / --todo-left|--todo-right are demo-only flags");
|
|
4040
4409
|
process.exit(1);
|
|
4041
4410
|
}
|
|
4042
4411
|
if (flags.step === true) {
|
|
@@ -4083,6 +4452,9 @@ function applyRunFlags(flags, opts) {
|
|
|
4083
4452
|
if (flags.miniStepper) {
|
|
4084
4453
|
process.env.WAYGRAPH_MINI = "1";
|
|
4085
4454
|
}
|
|
4455
|
+
if (flags.todoPos === "left" || flags.todoPos === "right") {
|
|
4456
|
+
process.env.WAYGRAPH_TODO_POS = flags.todoPos;
|
|
4457
|
+
}
|
|
4086
4458
|
if (flags.ffExpand) {
|
|
4087
4459
|
process.env.WAYGRAPH_FF_EXPAND = "1";
|
|
4088
4460
|
}
|
|
@@ -4248,6 +4620,7 @@ Primary (less is more):
|
|
|
4248
4620
|
--fast Shorter auto-next / Next gates (keeps smooth cursor)
|
|
4249
4621
|
--full Classic wrap-all block chips (default: carousel)
|
|
4250
4622
|
--mini Force compact mini panel (Hide pill + Next); alias --stepper-mini
|
|
4623
|
+
--todo-left|--todo-right Floating checklist dock side (also ctx.todoPos / WAYGRAPH_TODO_POS)
|
|
4251
4624
|
--ff-expand Expand fastForwardComposeBlock inners as separate steps
|
|
4252
4625
|
--ff-disabled Dispute: expand FF (alias --no-ff); blitz kept on those inners
|
|
4253
4626
|
--auto-play-video Unattended + recorded: --auto-next + --video (+ step); headless by default
|