waygraph 0.12.41 → 0.12.43
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 +670 -821
- package/dist/engine.d.ts +8 -8
- package/dist/engine.js +8 -8
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- 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 +11 -0
- 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
|
@@ -274,12 +274,12 @@ async function findBlock(projectDir, ref) {
|
|
|
274
274
|
if (byRuntimeName.length === 1) return byRuntimeName[0];
|
|
275
275
|
if (byRuntimeName.length > 1) {
|
|
276
276
|
throw new Error(
|
|
277
|
-
"waygraph chain:
|
|
277
|
+
"waygraph chain: \\"" + ref + "\\" matches " + byRuntimeName.length + " Blocks by name - " +
|
|
278
278
|
byRuntimeName.map((b) => b.exportName + " (" + b.file + ")").join(", ") +
|
|
279
279
|
". Use the export name to disambiguate.",
|
|
280
280
|
);
|
|
281
281
|
}
|
|
282
|
-
throw new Error("waygraph chain: no Block named
|
|
282
|
+
throw new Error("waygraph chain: no Block named \\"" + ref + "\\" found under " + projectDir);
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
/**
|
|
@@ -316,7 +316,7 @@ function parseChainSpec(spec) {
|
|
|
316
316
|
const m = /^([A-Za-z_$][\\w-]*)\\s*(?:\\(([\\s\\S]*)\\))?$/.exec(s);
|
|
317
317
|
if (!m) {
|
|
318
318
|
throw new Error(
|
|
319
|
-
"waygraph chain: could not parse segment
|
|
319
|
+
"waygraph chain: could not parse segment \\"" + s + "\\" - expected \\"blockName\\" or \\"blockName({...json...})\\"",
|
|
320
320
|
);
|
|
321
321
|
}
|
|
322
322
|
const ref = m[1];
|
|
@@ -329,7 +329,7 @@ function seedMemFromRequires(mem, requires, json, label) {
|
|
|
329
329
|
if (requires.length === 0) {
|
|
330
330
|
if (json !== undefined) {
|
|
331
331
|
throw new Error(
|
|
332
|
-
"waygraph chain:
|
|
332
|
+
"waygraph chain: \\"" + label + "\\" takes no input (empty requires) but got a payload: " + json,
|
|
333
333
|
);
|
|
334
334
|
}
|
|
335
335
|
return;
|
|
@@ -341,7 +341,7 @@ function seedMemFromRequires(mem, requires, json, label) {
|
|
|
341
341
|
json = fromEnv;
|
|
342
342
|
} else {
|
|
343
343
|
throw new Error(
|
|
344
|
-
"waygraph chain:
|
|
344
|
+
"waygraph chain: \\"" + label + "\\" requires " + requires.map((k) => k.name).join(", ") +
|
|
345
345
|
" - give a JSON payload (inline blockName({...}) or --data '{...}')",
|
|
346
346
|
);
|
|
347
347
|
}
|
|
@@ -350,7 +350,7 @@ function seedMemFromRequires(mem, requires, json, label) {
|
|
|
350
350
|
try {
|
|
351
351
|
parsed = JSON.parse(json);
|
|
352
352
|
} catch (err) {
|
|
353
|
-
throw new Error("waygraph chain:
|
|
353
|
+
throw new Error("waygraph chain: \\"" + label + "\\" payload is not valid JSON - " + String(err));
|
|
354
354
|
}
|
|
355
355
|
// Keyed-by-name when every require name is a top-level key (preferred for
|
|
356
356
|
// --data and for {"saucedemo.credentials":{...}} even with one require).
|
|
@@ -367,13 +367,13 @@ function seedMemFromRequires(mem, requires, json, label) {
|
|
|
367
367
|
}
|
|
368
368
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
369
369
|
throw new Error(
|
|
370
|
-
"waygraph chain:
|
|
370
|
+
"waygraph chain: \\"" + label + "\\" requires " + requires.length + " keys (" +
|
|
371
371
|
requires.map((k) => k.name).join(", ") + ") - payload must be an object keyed by each key's name",
|
|
372
372
|
);
|
|
373
373
|
}
|
|
374
374
|
for (const k of requires) {
|
|
375
375
|
if (!(k.name in parsed)) {
|
|
376
|
-
throw new Error("waygraph chain:
|
|
376
|
+
throw new Error("waygraph chain: \\"" + label + "\\" payload is missing required key \\"" + k.name + "\\"");
|
|
377
377
|
}
|
|
378
378
|
mem.set(k, parsed[k.name]);
|
|
379
379
|
}
|
|
@@ -412,12 +412,10 @@ 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
|
-
"box-shadow:0 0 0 4px rgba(124,58,237,.16);" +
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
"transition:opacity .28s ease;}" +
|
|
420
|
-
"#wg-focus-veil.wg-focus-on{opacity:1;}" +
|
|
415
|
+
"box-shadow:0 0 0 4px rgba(124,58,237,.16);transition:opacity .3s ease,border-color .15s,box-shadow .15s;}" +
|
|
416
|
+
// Planned (stubs / fixtures / YAP / instruction.highlights) = purple.
|
|
417
|
+
// Automation (verify fallback, unmatched fill/click) = gray - operators
|
|
418
|
+
// can ignore engine checks and watch purple + semantic tones.
|
|
421
419
|
"#wg-ring[data-tone=auto]{border-color:#9CA3AF;box-shadow:0 0 0 4px rgba(156,163,175,.28);}" +
|
|
422
420
|
"#wg-ring[data-tone=info]{border-color:#3B82F6;box-shadow:0 0 0 4px rgba(59,130,246,.22);}" +
|
|
423
421
|
"#wg-ring[data-tone=warning]{border-color:#EAB308;box-shadow:0 0 0 4px rgba(234,179,8,.22);}" +
|
|
@@ -433,7 +431,7 @@ const RING_CSS =
|
|
|
433
431
|
// window.__wgPositionRing in installOverlay.
|
|
434
432
|
"#wg-ring-label{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
|
|
435
433
|
"max-width:min(360px,70vw);white-space:normal;padding:6px 10px;border-radius:7px;background:#7C3AED;color:#fff;" +
|
|
436
|
-
"font:600 12px/1.35 system-ui,sans-serif;transition:opacity .
|
|
434
|
+
"font:600 12px/1.35 system-ui,sans-serif;transition:opacity .3s ease,background .15s,color .15s;}" +
|
|
437
435
|
"#wg-ring-label[data-tone=auto]{background:#6B7280;color:#fff;}" +
|
|
438
436
|
"#wg-ring-label[data-tone=info]{background:#2563EB;color:#fff;}" +
|
|
439
437
|
"#wg-ring-label[data-tone=warning]{background:#EAB308;color:#1c1917;}" +
|
|
@@ -519,24 +517,6 @@ const RING_CSS =
|
|
|
519
517
|
"#wg-todos .wg-todo-done{color:#6ee7b7;text-decoration:line-through;opacity:.85;}" +
|
|
520
518
|
"#wg-todos .wg-todo-current{color:#fff;}" +
|
|
521
519
|
"#wg-todos .wg-todo-pending{color:#9a7ad1;}" +
|
|
522
|
-
"#wg-todo-modal{position:fixed;z-index:2147483647;left:14px;bottom:14px;min-width:240px;max-width:min(360px,92vw);" +
|
|
523
|
-
"padding:14px 16px 12px;background:rgba(18,8,36,.94);color:#f0e8ff;" +
|
|
524
|
-
"border:1px solid rgba(124,58,237,.55);border-radius:12px;" +
|
|
525
|
-
"box-shadow:0 12px 40px rgba(0,0,0,.45);backdrop-filter:blur(8px);" +
|
|
526
|
-
"opacity:0;transform:translateY(8px);pointer-events:none;" +
|
|
527
|
-
"transition:opacity .28s ease,transform .28s ease;}" +
|
|
528
|
-
"#wg-todo-modal.wg-todo-on{opacity:1;transform:none;pointer-events:auto;}" +
|
|
529
|
-
"#wg-todo-modal .wg-todo-head{font:700 11px/1.2 system-ui,sans-serif;color:#c9a6ff;" +
|
|
530
|
-
"letter-spacing:.04em;text-transform:uppercase;margin:0 0 10px;}" +
|
|
531
|
-
"#wg-todo-modal ul{list-style:none;margin:0;padding:0;}" +
|
|
532
|
-
"#wg-todo-modal li{display:flex;gap:10px;align-items:flex-start;margin:0 0 8px;" +
|
|
533
|
-
"font:600 14px/1.4 system-ui,sans-serif;}" +
|
|
534
|
-
"#wg-todo-modal li:last-child{margin-bottom:0;}" +
|
|
535
|
-
"#wg-todo-modal .wg-todo-mark{flex:0 0 auto;width:1.2em;text-align:center;font-size:15px;}" +
|
|
536
|
-
"#wg-todo-modal .wg-todo-done{color:#6ee7b7;text-decoration:line-through;opacity:.8;}" +
|
|
537
|
-
"#wg-todo-modal .wg-todo-current{color:#fff;}" +
|
|
538
|
-
"#wg-todo-modal .wg-todo-current .wg-todo-mark{color:#c9a6ff;}" +
|
|
539
|
-
"#wg-todo-modal .wg-todo-pending{color:#9a7ad1;}" +
|
|
540
520
|
"#wg-progress{height:4px;background:#2a1650;border-radius:2px;margin:0 0 12px;overflow:hidden;}" +
|
|
541
521
|
"#wg-progress-bar{height:100%;background:#7C3AED;border-radius:2px;transition:width .3s ease;}" +
|
|
542
522
|
// A real tab bar for episodes - the currently-active episode reads as
|
|
@@ -621,18 +601,23 @@ const RING_CSS =
|
|
|
621
601
|
"#wg-panel .wg-expected-heading{color:#ffcf7a;}" +
|
|
622
602
|
"#wg-panel .wg-expected-reason{font:600 12.5px/1.5 system-ui,sans-serif;background:#2a2410;" +
|
|
623
603
|
"color:#ffe6ae;border-radius:8px;padding:10px;margin:0 0 8px;}" +
|
|
624
|
-
// Hide / Show chrome: collapsed = compact "N / M · block" pill
|
|
604
|
+
// Hide / Show chrome: collapsed = compact "N / M · block" pill + Next (manual).
|
|
625
605
|
"#wg-panel .wg-chrome{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0 0 8px;}" +
|
|
626
606
|
"#wg-panel .wg-chrome-title{font:700 11px/1.2 system-ui,sans-serif;color:#c9a6ff;" +
|
|
627
|
-
"letter-spacing:.04em;text-transform:uppercase;}" +
|
|
628
|
-
"#wg-panel
|
|
607
|
+
"letter-spacing:.04em;text-transform:uppercase;flex:1;min-width:0;}" +
|
|
608
|
+
"#wg-panel .wg-chrome-actions{display:flex;align-items:center;gap:6px;flex-shrink:0;}" +
|
|
609
|
+
"#wg-panel button.wg-hide-btn,#wg-panel button.wg-mini-next{margin:0;padding:4px 10px;font:600 11px system-ui,sans-serif;" +
|
|
629
610
|
"background:#3a2a60;color:#e8dcff;border:1px solid #5b3aa8;border-radius:6px;cursor:pointer;}" +
|
|
630
|
-
"#wg-panel button.wg-hide-btn:hover{background:#4b2a80;}" +
|
|
611
|
+
"#wg-panel button.wg-hide-btn:hover,#wg-panel button.wg-mini-next:hover{background:#4b2a80;}" +
|
|
612
|
+
"#wg-panel button.wg-mini-next{background:#2a9d6f;border-color:#22855e;color:#fff;display:none;}" +
|
|
613
|
+
"#wg-panel button.wg-mini-next:hover{background:#22855e;}" +
|
|
614
|
+
"#wg-panel button.wg-mini-next:disabled{opacity:.55;cursor:default;}" +
|
|
631
615
|
"#wg-panel.wg-collapsed{width:auto;max-width:92vw;padding:8px 12px;max-height:none;overflow:hidden;}" +
|
|
632
616
|
"#wg-panel.wg-collapsed .wg-body{display:none;}" +
|
|
633
617
|
"#wg-panel.wg-collapsed .wg-chrome{margin:0;}" +
|
|
634
618
|
"#wg-panel.wg-collapsed .wg-chrome-title{font:700 13px/1.25 system-ui,sans-serif;color:#fff;" +
|
|
635
619
|
"letter-spacing:0;text-transform:none;}" +
|
|
620
|
+
"#wg-panel.wg-collapsed button.wg-mini-next.wg-mini-next-show{display:inline-block;}" +
|
|
636
621
|
"@media (max-width:640px){" +
|
|
637
622
|
"#wg-panel{left:8px;right:8px;bottom:8px;transform:none;max-width:none;width:auto;" +
|
|
638
623
|
"max-height:min(55vh,calc(100vh - 16px));padding:12px 14px;border-radius:12px;}" +
|
|
@@ -640,6 +625,7 @@ const RING_CSS =
|
|
|
640
625
|
"#wg-episodes{overflow-x:auto;-webkit-overflow-scrolling:touch;flex-wrap:nowrap;}" +
|
|
641
626
|
"#wg-panel .wg-narration{font-size:13px;}" +
|
|
642
627
|
"#wg-panel button{width:100%;}" +
|
|
628
|
+
"#wg-panel.wg-collapsed button.wg-mini-next,#wg-panel.wg-collapsed button.wg-hide-btn{width:auto;}" +
|
|
643
629
|
"#wg-panel .wg-error-actions{flex-direction:column;}" +
|
|
644
630
|
"#wg-banner{max-width:min(92vw,320px);font-size:13px;}" +
|
|
645
631
|
"}";
|
|
@@ -675,11 +661,6 @@ async function installOverlay(page, title) {
|
|
|
675
661
|
} catch {
|
|
676
662
|
/* private mode / blocked storage - falls back to manual gating */
|
|
677
663
|
}
|
|
678
|
-
if (!document.getElementById("wg-focus-veil")) {
|
|
679
|
-
const veil = document.createElement("div");
|
|
680
|
-
veil.id = "wg-focus-veil";
|
|
681
|
-
document.documentElement.appendChild(veil);
|
|
682
|
-
}
|
|
683
664
|
if (!document.getElementById("wg-ring")) {
|
|
684
665
|
const ring = document.createElement("div");
|
|
685
666
|
ring.id = "wg-ring";
|
|
@@ -710,10 +691,7 @@ async function installOverlay(page, title) {
|
|
|
710
691
|
window.__wgMoveCursorTo = (x, y, ms, instant) => {
|
|
711
692
|
const cursor = document.getElementById("wg-cursor");
|
|
712
693
|
if (!cursor) return;
|
|
713
|
-
|
|
714
|
-
window.__wgCursorPos = { x: Number(x) || 0, y: Number(y) || 0 };
|
|
715
|
-
window.__wgCursorTravelUntil = Date.now() + (instant ? 0 : travel) + 40;
|
|
716
|
-
cursor.style.setProperty("--wg-cursor-ms", travel + "ms");
|
|
694
|
+
cursor.style.setProperty("--wg-cursor-ms", (ms || 600) + "ms");
|
|
717
695
|
if (instant) {
|
|
718
696
|
const prev = cursor.style.transition;
|
|
719
697
|
cursor.style.transition = "none";
|
|
@@ -732,7 +710,6 @@ async function installOverlay(page, title) {
|
|
|
732
710
|
window.__wgPositionRing = (box, label, tone, style) => {
|
|
733
711
|
const ring = document.getElementById("wg-ring");
|
|
734
712
|
const ringLabel = document.getElementById("wg-ring-label");
|
|
735
|
-
const veil = document.getElementById("wg-focus-veil");
|
|
736
713
|
if (!ring || !ringLabel) return;
|
|
737
714
|
const raw = (tone || "planned") + "";
|
|
738
715
|
const t =
|
|
@@ -750,15 +727,14 @@ async function installOverlay(page, title) {
|
|
|
750
727
|
ring.dataset.size = size;
|
|
751
728
|
ringLabel.dataset.size = size;
|
|
752
729
|
ringLabel.dataset.weight = weight;
|
|
753
|
-
ring.dataset.focus = st.focus ? "1" : "0";
|
|
754
730
|
const pad = size === "sm" ? 3 : size === "lg" ? 10 : 6;
|
|
755
731
|
const margin = 6;
|
|
756
732
|
const vw = window.innerWidth;
|
|
757
733
|
const vh = window.innerHeight;
|
|
758
734
|
let left = box.x - pad;
|
|
759
735
|
let top = box.y - pad;
|
|
760
|
-
const width =
|
|
761
|
-
const height =
|
|
736
|
+
const width = box.width + pad * 2;
|
|
737
|
+
const height = box.height + pad * 2;
|
|
762
738
|
if (left < margin) left = margin;
|
|
763
739
|
if (top < margin) top = margin;
|
|
764
740
|
if (left + width > vw - margin) left = Math.max(margin, vw - margin - width);
|
|
@@ -768,92 +744,21 @@ async function installOverlay(page, title) {
|
|
|
768
744
|
ring.style.width = width + "px";
|
|
769
745
|
ring.style.height = height + "px";
|
|
770
746
|
ring.style.opacity = "1";
|
|
771
|
-
if (veil) {
|
|
772
|
-
veil.style.left = left + "px";
|
|
773
|
-
veil.style.top = top + "px";
|
|
774
|
-
veil.style.width = width + "px";
|
|
775
|
-
veil.style.height = height + "px";
|
|
776
|
-
const br = size === "sm" ? "8px" : size === "lg" ? "12px" : "10px";
|
|
777
|
-
veil.style.borderRadius = br;
|
|
778
|
-
if (st.focus) veil.classList.add("wg-focus-on");
|
|
779
|
-
else veil.classList.remove("wg-focus-on");
|
|
780
|
-
}
|
|
781
747
|
ringLabel.textContent = label;
|
|
782
748
|
ringLabel.style.opacity = "1";
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
else ringLabel.style.removeProperty("color");
|
|
749
|
+
// Measure the label's own natural width/height (its real
|
|
750
|
+
// rendered size, unchanged) before deciding where it fits.
|
|
786
751
|
const lw = ringLabel.offsetWidth;
|
|
787
752
|
const lh = ringLabel.offsetHeight;
|
|
788
753
|
let labelLeft = left;
|
|
789
754
|
let labelTop = top + height + 8;
|
|
790
|
-
if (labelTop + lh > vh - margin) labelTop = top - lh - 8;
|
|
755
|
+
if (labelTop + lh > vh - margin) labelTop = top - lh - 8; // flip above if it'd overflow the bottom
|
|
791
756
|
if (labelTop < margin) labelTop = margin;
|
|
792
757
|
if (labelLeft + lw > vw - margin) labelLeft = Math.max(margin, vw - margin - lw);
|
|
793
758
|
if (labelLeft < margin) labelLeft = margin;
|
|
794
759
|
ringLabel.style.left = labelLeft + "px";
|
|
795
760
|
ringLabel.style.top = labelTop + "px";
|
|
796
761
|
};
|
|
797
|
-
window.__wgHideFocusVeil = () => {
|
|
798
|
-
const veil = document.getElementById("wg-focus-veil");
|
|
799
|
-
if (veil) veil.classList.remove("wg-focus-on");
|
|
800
|
-
};
|
|
801
|
-
window.__wgStartHighlightTrack = (opts) => {
|
|
802
|
-
if (window.__wgStopHighlightTrack) window.__wgStopHighlightTrack();
|
|
803
|
-
const sel = opts.sel;
|
|
804
|
-
const label = opts.label;
|
|
805
|
-
const tone = opts.tone || "planned";
|
|
806
|
-
const size = opts.size || "md";
|
|
807
|
-
const weight = opts.weight || "normal";
|
|
808
|
-
const focus = !!opts.focus;
|
|
809
|
-
const color = opts.color || "";
|
|
810
|
-
const zoom = opts.zoom != null && opts.zoom > 1 ? opts.zoom : 1;
|
|
811
|
-
const camMs = opts.camMs != null ? opts.camMs : 700;
|
|
812
|
-
const style = { size, weight, focus, color };
|
|
813
|
-
// Auto-center only. Never chase the host mouse (that fights the
|
|
814
|
-
// camera). Playwright's real mouse is driven separately by the CLI.
|
|
815
|
-
window.__wgIgnoreHostMouse = true;
|
|
816
|
-
const tick = () => {
|
|
817
|
-
const el = document.querySelector(sel);
|
|
818
|
-
if (el && window.__wgPositionRing) {
|
|
819
|
-
const r = el.getBoundingClientRect();
|
|
820
|
-
window.__wgPositionRing(
|
|
821
|
-
{ x: r.left, y: r.top, width: r.width, height: r.height },
|
|
822
|
-
label,
|
|
823
|
-
tone,
|
|
824
|
-
style,
|
|
825
|
-
);
|
|
826
|
-
}
|
|
827
|
-
window.__wgRingRaf = requestAnimationFrame(tick);
|
|
828
|
-
};
|
|
829
|
-
if (zoom > 1 && window.__wgSetCamera) {
|
|
830
|
-
const el0 = document.querySelector(sel);
|
|
831
|
-
if (el0) {
|
|
832
|
-
const already =
|
|
833
|
-
window.__wgCamera && window.__wgCamera.scale > 1.001;
|
|
834
|
-
const dur = already ? Math.min(camMs, 420) : camMs;
|
|
835
|
-
const pt = window.__wgElDocPoint
|
|
836
|
-
? window.__wgElDocPoint(el0)
|
|
837
|
-
: {
|
|
838
|
-
x: el0.getBoundingClientRect().left + el0.getBoundingClientRect().width / 2,
|
|
839
|
-
y: el0.getBoundingClientRect().top + el0.getBoundingClientRect().height / 2,
|
|
840
|
-
};
|
|
841
|
-
// Lock for the whole ring - host mouse cannot steer.
|
|
842
|
-
window.__wgCameraLock = true;
|
|
843
|
-
window.__wgSetCamera(pt.x, pt.y, zoom, dur);
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
window.__wgRingRaf = requestAnimationFrame(tick);
|
|
847
|
-
// Do NOT listen to host mousemove - it was steering the camera.
|
|
848
|
-
window.__wgRingTrack = null;
|
|
849
|
-
window.__wgStopHighlightTrack = () => {
|
|
850
|
-
if (window.__wgRingRaf) {
|
|
851
|
-
cancelAnimationFrame(window.__wgRingRaf);
|
|
852
|
-
window.__wgRingRaf = null;
|
|
853
|
-
}
|
|
854
|
-
window.__wgStopHighlightTrack = null;
|
|
855
|
-
};
|
|
856
|
-
};
|
|
857
762
|
window.__wgClickPulse = (x, y, tone) => {
|
|
858
763
|
const pulse = document.getElementById("wg-click-pulse");
|
|
859
764
|
if (!pulse) return;
|
|
@@ -878,124 +783,31 @@ async function installOverlay(page, title) {
|
|
|
878
783
|
const ringLabel = document.getElementById("wg-ring-label");
|
|
879
784
|
if (ring) ring.style.opacity = "0";
|
|
880
785
|
if (ringLabel) ringLabel.style.opacity = "0";
|
|
881
|
-
if (window.
|
|
882
|
-
// Do NOT clear camera zoom here - zoomOut:false keeps scale between
|
|
883
|
-
// rings; only resetCameraZoom / teardown call __wgClearZoom.
|
|
884
|
-
};
|
|
885
|
-
// Screen Studio-style camera: scale <body>, keep overlays on <html> fixed.
|
|
886
|
-
// Focus (cx,cy) is DOCUMENT space (pre-transform). Viewport mouse/rects
|
|
887
|
-
// must go through __wgViewportToDoc before SetCamera.
|
|
888
|
-
window.__wgCamera = {
|
|
889
|
-
scale: 1,
|
|
890
|
-
cx: window.innerWidth / 2,
|
|
891
|
-
cy: window.innerHeight / 2,
|
|
892
|
-
};
|
|
893
|
-
window.__wgCameraLock = false;
|
|
894
|
-
window.__wgViewportToDoc = (vx, vy) => {
|
|
895
|
-
const cam = window.__wgCamera || {
|
|
896
|
-
scale: 1,
|
|
897
|
-
cx: window.innerWidth / 2,
|
|
898
|
-
cy: window.innerHeight / 2,
|
|
899
|
-
};
|
|
900
|
-
const s = cam.scale > 1.001 ? cam.scale : 1;
|
|
901
|
-
if (s <= 1.001) return { x: vx, y: vy };
|
|
902
|
-
return {
|
|
903
|
-
x: (vx - window.innerWidth / 2) / s + cam.cx,
|
|
904
|
-
y: (vy - window.innerHeight / 2) / s + cam.cy,
|
|
905
|
-
};
|
|
906
|
-
};
|
|
907
|
-
window.__wgElDocPoint = (el) => {
|
|
908
|
-
const r = el.getBoundingClientRect();
|
|
909
|
-
return window.__wgViewportToDoc(
|
|
910
|
-
r.left + r.width / 2,
|
|
911
|
-
r.top + r.height / 2,
|
|
912
|
-
);
|
|
913
|
-
};
|
|
914
|
-
window.__wgSetCamera = (fx, fy, scale, ms) => {
|
|
915
|
-
const body = document.body;
|
|
916
|
-
if (!body) return;
|
|
917
|
-
const s = Math.max(1, Math.min(Number(scale) || 1, 2.75));
|
|
918
|
-
const vw = window.innerWidth;
|
|
919
|
-
const vh = window.innerHeight;
|
|
920
|
-
window.__wgCamera = { scale: s, cx: fx, cy: fy };
|
|
921
|
-
const dur = ms != null && Number(ms) >= 0 ? Number(ms) : 650;
|
|
922
|
-
body.style.transition =
|
|
923
|
-
dur <= 0
|
|
924
|
-
? "none"
|
|
925
|
-
: "transform " + dur / 1000 + "s cubic-bezier(.22,1,.36,1)";
|
|
926
|
-
body.style.transformOrigin = "0 0";
|
|
927
|
-
if (s <= 1.001) {
|
|
928
|
-
body.style.transform = "";
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
// One transform: scale AND bring focus to viewport center.
|
|
932
|
-
body.style.transform =
|
|
933
|
-
"translate(" + vw / 2 + "px," + vh / 2 + "px) scale(" + s + ") translate(" + -fx + "px," + -fy + "px)";
|
|
934
|
-
};
|
|
935
|
-
window.__wgSetTodos = (todos) => {
|
|
936
|
-
let modal = document.getElementById("wg-todo-modal");
|
|
937
|
-
if (!todos || !todos.length) {
|
|
938
|
-
if (modal) {
|
|
939
|
-
modal.classList.remove("wg-todo-on");
|
|
940
|
-
modal.innerHTML = "";
|
|
941
|
-
}
|
|
942
|
-
return;
|
|
943
|
-
}
|
|
944
|
-
if (!modal) {
|
|
945
|
-
modal = document.createElement("div");
|
|
946
|
-
modal.id = "wg-todo-modal";
|
|
947
|
-
document.documentElement.appendChild(modal);
|
|
948
|
-
}
|
|
949
|
-
const esc = (s) =>
|
|
950
|
-
String(s)
|
|
951
|
-
.replace(/&/g, "&")
|
|
952
|
-
.replace(/</g, "<")
|
|
953
|
-
.replace(/>/g, ">");
|
|
954
|
-
const rows = todos
|
|
955
|
-
.map((t) => {
|
|
956
|
-
const cls = t.current
|
|
957
|
-
? "wg-todo-current"
|
|
958
|
-
: t.done
|
|
959
|
-
? "wg-todo-done"
|
|
960
|
-
: "wg-todo-pending";
|
|
961
|
-
const mark = t.done
|
|
962
|
-
? String.fromCharCode(0x2713)
|
|
963
|
-
: t.current
|
|
964
|
-
? String.fromCharCode(0x2192)
|
|
965
|
-
: String.fromCharCode(0x25cb);
|
|
966
|
-
return (
|
|
967
|
-
'<li class="' +
|
|
968
|
-
cls +
|
|
969
|
-
'"><span class="wg-todo-mark">' +
|
|
970
|
-
mark +
|
|
971
|
-
'</span><span>' +
|
|
972
|
-
esc(t.text || "") +
|
|
973
|
-
'</span></li>'
|
|
974
|
-
);
|
|
975
|
-
})
|
|
976
|
-
.join("");
|
|
977
|
-
modal.innerHTML =
|
|
978
|
-
'<div class="wg-todo-head">Checklist</div><ul>' + rows + '</ul>';
|
|
979
|
-
modal.classList.add("wg-todo-on");
|
|
980
|
-
};
|
|
981
|
-
window.__wgResetCamera = () => {
|
|
982
|
-
window.__wgSetCamera(window.innerWidth / 2, window.innerHeight / 2, 1);
|
|
786
|
+
if (window.__wgClearZoom) window.__wgClearZoom();
|
|
983
787
|
};
|
|
984
788
|
window.__wgClearZoom = () => {
|
|
985
|
-
|
|
789
|
+
document.querySelectorAll("[data-wg-zoomed=\\"1\\"]").forEach((el) => {
|
|
790
|
+
el.style.removeProperty("transform");
|
|
791
|
+
el.style.removeProperty("transform-origin");
|
|
792
|
+
el.style.removeProperty("transition");
|
|
793
|
+
el.style.removeProperty("z-index");
|
|
794
|
+
el.style.removeProperty("position");
|
|
795
|
+
delete el.dataset.wgZoomed;
|
|
796
|
+
});
|
|
986
797
|
};
|
|
987
|
-
window.
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
if (
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
798
|
+
window.__wgApplyZoom = (sel, scale) => {
|
|
799
|
+
if (window.__wgClearZoom) window.__wgClearZoom();
|
|
800
|
+
const n = Number(scale);
|
|
801
|
+
if (!Number.isFinite(n) || n <= 1.001) return;
|
|
802
|
+
const el = document.querySelector(sel);
|
|
803
|
+
if (!el) return;
|
|
804
|
+
const st = getComputedStyle(el);
|
|
805
|
+
if (st.position === "static") el.style.position = "relative";
|
|
806
|
+
el.style.zIndex = "2147483645";
|
|
807
|
+
el.style.transition = "transform .35s ease";
|
|
808
|
+
el.style.transformOrigin = "center center";
|
|
809
|
+
el.style.transform = "scale(" + n + ")";
|
|
810
|
+
el.dataset.wgZoomed = "1";
|
|
999
811
|
};
|
|
1000
812
|
const POSITIONS = ["left", "center", "right"];
|
|
1001
813
|
const applyPos = (el, pos) => {
|
|
@@ -1046,36 +858,127 @@ async function installOverlay(page, title) {
|
|
|
1046
858
|
document.head.appendChild(iconLink);
|
|
1047
859
|
}
|
|
1048
860
|
if (iconLink.href !== favicon) iconLink.href = favicon;
|
|
861
|
+
// Blind-agent overlay beacons: every modal root gets data-wg-ui /
|
|
862
|
+
// data-wg-modal / data-wg-ready so tests can fail on a blank panel.
|
|
863
|
+
window.__wgStampModal = (el, kind, meta) => {
|
|
864
|
+
if (!el) return;
|
|
865
|
+
meta = meta || {};
|
|
866
|
+
el.setAttribute("data-wg-ui", "1");
|
|
867
|
+
el.setAttribute("data-wg-modal", kind || "panel");
|
|
868
|
+
if (meta.phase != null) el.setAttribute("data-wg-phase", String(meta.phase));
|
|
869
|
+
if (meta.step != null) el.setAttribute("data-wg-step", String(meta.step));
|
|
870
|
+
if (meta.total != null) el.setAttribute("data-wg-total", String(meta.total));
|
|
871
|
+
if (meta.block != null) el.setAttribute("data-wg-block", String(meta.block));
|
|
872
|
+
el.setAttribute(
|
|
873
|
+
"data-wg-collapsed",
|
|
874
|
+
el.classList.contains("wg-collapsed") ? "1" : "0",
|
|
875
|
+
);
|
|
876
|
+
el.setAttribute("data-wg-ready", meta.ready === false ? "0" : "1");
|
|
877
|
+
};
|
|
878
|
+
window.__wgOverlayBeacon = () => {
|
|
879
|
+
return Array.from(document.querySelectorAll("[data-wg-ui=\\"1\\"]")).map((el) => {
|
|
880
|
+
const r = el.getBoundingClientRect();
|
|
881
|
+
const st = getComputedStyle(el);
|
|
882
|
+
return {
|
|
883
|
+
id: el.id || null,
|
|
884
|
+
modal: el.getAttribute("data-wg-modal"),
|
|
885
|
+
ready: el.getAttribute("data-wg-ready") === "1",
|
|
886
|
+
phase: el.getAttribute("data-wg-phase"),
|
|
887
|
+
step: el.getAttribute("data-wg-step"),
|
|
888
|
+
block: el.getAttribute("data-wg-block"),
|
|
889
|
+
collapsed:
|
|
890
|
+
el.getAttribute("data-wg-collapsed") === "1" ||
|
|
891
|
+
el.classList.contains("wg-collapsed"),
|
|
892
|
+
opacity: st.opacity,
|
|
893
|
+
textLen: (el.innerText || "").trim().length,
|
|
894
|
+
w: Math.round(r.width),
|
|
895
|
+
h: Math.round(r.height),
|
|
896
|
+
visible: r.width > 0 && r.height > 0 && Number(st.opacity) > 0.05,
|
|
897
|
+
};
|
|
898
|
+
});
|
|
899
|
+
};
|
|
900
|
+
const existingBanner = document.getElementById("wg-banner");
|
|
901
|
+
if (existingBanner) window.__wgStampModal(existingBanner, "banner", { ready: true });
|
|
1049
902
|
// Hide/Show for #wg-panel - call after every panel.innerHTML refresh.
|
|
1050
|
-
// Collapsed
|
|
1051
|
-
// forceCollapsed:
|
|
1052
|
-
// the user's Hide preference (localStorage) wins. Never pass false.
|
|
903
|
+
// Collapsed = "N / M · block" + Next (when not auto). Never pass
|
|
904
|
+
// forceCollapsed:false - omit so Hide/localStorage wins.
|
|
1053
905
|
window.__wgWirePanelChrome = (panel, storageKey, chromeTitle, opts) => {
|
|
1054
906
|
if (!panel) return;
|
|
1055
907
|
opts = opts || {};
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
908
|
+
const ensureChrome = () => {
|
|
909
|
+
let chrome = panel.querySelector(":scope > .wg-chrome");
|
|
910
|
+
if (!chrome) {
|
|
911
|
+
const body = document.createElement("div");
|
|
912
|
+
body.className = "wg-body";
|
|
913
|
+
while (panel.firstChild) body.appendChild(panel.firstChild);
|
|
914
|
+
chrome = document.createElement("div");
|
|
915
|
+
chrome.className = "wg-chrome";
|
|
916
|
+
const titleEl = document.createElement("span");
|
|
917
|
+
titleEl.className = "wg-chrome-title";
|
|
918
|
+
titleEl.textContent = chromeTitle || "waygraph demo";
|
|
919
|
+
const actions = document.createElement("div");
|
|
920
|
+
actions.className = "wg-chrome-actions";
|
|
921
|
+
const nextBtn = document.createElement("button");
|
|
922
|
+
nextBtn.type = "button";
|
|
923
|
+
nextBtn.className = "wg-mini-next";
|
|
924
|
+
nextBtn.setAttribute("data-wg-mini-next", "1");
|
|
925
|
+
nextBtn.textContent = "Next \u25B6";
|
|
926
|
+
const btn = document.createElement("button");
|
|
927
|
+
btn.type = "button";
|
|
928
|
+
btn.className = "wg-hide-btn";
|
|
929
|
+
btn.setAttribute("data-wg-toggle", "1");
|
|
930
|
+
btn.textContent = "Hide";
|
|
931
|
+
actions.appendChild(nextBtn);
|
|
932
|
+
actions.appendChild(btn);
|
|
933
|
+
chrome.appendChild(titleEl);
|
|
934
|
+
chrome.appendChild(actions);
|
|
935
|
+
panel.appendChild(chrome);
|
|
936
|
+
panel.appendChild(body);
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
// Yap slides ship a bare chrome - ensure actions + mini Next exist.
|
|
940
|
+
let actions = chrome.querySelector(".wg-chrome-actions");
|
|
941
|
+
if (!actions) {
|
|
942
|
+
actions = document.createElement("div");
|
|
943
|
+
actions.className = "wg-chrome-actions";
|
|
944
|
+
const hide = chrome.querySelector("[data-wg-toggle]");
|
|
945
|
+
if (hide) actions.appendChild(hide);
|
|
946
|
+
chrome.appendChild(actions);
|
|
947
|
+
}
|
|
948
|
+
if (!actions.querySelector("[data-wg-mini-next]")) {
|
|
949
|
+
const nextBtn = document.createElement("button");
|
|
950
|
+
nextBtn.type = "button";
|
|
951
|
+
nextBtn.className = "wg-mini-next";
|
|
952
|
+
nextBtn.setAttribute("data-wg-mini-next", "1");
|
|
953
|
+
nextBtn.textContent = "Next \u25B6";
|
|
954
|
+
actions.insertBefore(nextBtn, actions.firstChild);
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
ensureChrome();
|
|
1075
958
|
const stepLabel = opts.stepLabel || panel.dataset.wgStepLabel || "";
|
|
1076
959
|
if (stepLabel) panel.dataset.wgStepLabel = stepLabel;
|
|
960
|
+
const syncMiniNext = (hidden) => {
|
|
961
|
+
const miniNext = panel.querySelector("[data-wg-mini-next]");
|
|
962
|
+
if (!miniNext) return;
|
|
963
|
+
let auto = false;
|
|
964
|
+
try {
|
|
965
|
+
auto = localStorage.getItem("wg-autoplay") === "1";
|
|
966
|
+
} catch {
|
|
967
|
+
/* ignore */
|
|
968
|
+
}
|
|
969
|
+
const runBtn = document.getElementById("wg-run");
|
|
970
|
+
const show = !!hidden && !auto && !!runBtn;
|
|
971
|
+
miniNext.classList.toggle("wg-mini-next-show", show);
|
|
972
|
+
if (runBtn) {
|
|
973
|
+
miniNext.textContent = runBtn.textContent || "Next \u25B6";
|
|
974
|
+
miniNext.disabled = !!runBtn.disabled;
|
|
975
|
+
} else {
|
|
976
|
+
miniNext.disabled = true;
|
|
977
|
+
}
|
|
978
|
+
};
|
|
1077
979
|
const apply = (hidden, persist) => {
|
|
1078
980
|
panel.classList.toggle("wg-collapsed", hidden);
|
|
981
|
+
panel.setAttribute("data-wg-collapsed", hidden ? "1" : "0");
|
|
1079
982
|
const t = panel.querySelector("[data-wg-toggle]");
|
|
1080
983
|
const titleEl = panel.querySelector(".wg-chrome-title");
|
|
1081
984
|
const label = panel.dataset.wgStepLabel || stepLabel;
|
|
@@ -1085,6 +988,7 @@ async function installOverlay(page, title) {
|
|
|
1085
988
|
: (chromeTitle || "waygraph demo");
|
|
1086
989
|
}
|
|
1087
990
|
if (t) t.textContent = hidden ? "Show" : "Hide";
|
|
991
|
+
syncMiniNext(hidden);
|
|
1088
992
|
if (persist !== false) {
|
|
1089
993
|
try {
|
|
1090
994
|
localStorage.setItem(storageKey, hidden ? "1" : "0");
|
|
@@ -1113,6 +1017,26 @@ async function installOverlay(page, title) {
|
|
|
1113
1017
|
apply(!panel.classList.contains("wg-collapsed"), true);
|
|
1114
1018
|
});
|
|
1115
1019
|
}
|
|
1020
|
+
const miniNext = panel.querySelector("[data-wg-mini-next]");
|
|
1021
|
+
if (miniNext && !miniNext.dataset.wgWired) {
|
|
1022
|
+
miniNext.dataset.wgWired = "1";
|
|
1023
|
+
miniNext.addEventListener("click", (e) => {
|
|
1024
|
+
e.stopPropagation();
|
|
1025
|
+
const runBtn = document.getElementById("wg-run");
|
|
1026
|
+
if (runBtn && !runBtn.disabled) {
|
|
1027
|
+
runBtn.click();
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
if (typeof window.__wgNext === "function") window.__wgNext({});
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
const autoCb = document.getElementById("wg-autoplay-cb");
|
|
1034
|
+
if (autoCb && !autoCb.dataset.wgMiniWired) {
|
|
1035
|
+
autoCb.dataset.wgMiniWired = "1";
|
|
1036
|
+
autoCb.addEventListener("change", () => {
|
|
1037
|
+
syncMiniNext(panel.classList.contains("wg-collapsed"));
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1116
1040
|
const cur = panel.querySelector("#wg-modules .wg-mod-current");
|
|
1117
1041
|
if (cur && typeof cur.scrollIntoView === "function") {
|
|
1118
1042
|
try {
|
|
@@ -1145,7 +1069,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1145
1069
|
if (window.__wgHideCursor) window.__wgHideCursor();
|
|
1146
1070
|
})
|
|
1147
1071
|
.catch(() => {});
|
|
1148
|
-
// miniStepLabel
|
|
1072
|
+
// miniStepLabel / todosHtmlFromInfo are Node-only — browser evaluate has no closure.
|
|
1149
1073
|
const payload = { ...info, stepLabel: miniStepLabel(info) };
|
|
1150
1074
|
await page
|
|
1151
1075
|
.evaluate((info) => {
|
|
@@ -1169,7 +1093,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1169
1093
|
// active, the episode 2 tab is inactive"). Absent entirely for an ad
|
|
1170
1094
|
// hoc block chain with no named Flows.
|
|
1171
1095
|
const episodesHtml = info.allEpisodes && info.allEpisodes.length > 0
|
|
1172
|
-
? "<div id
|
|
1096
|
+
? "<div id=\\"wg-episodes\\">" +
|
|
1173
1097
|
info.allEpisodes
|
|
1174
1098
|
.map((e) => {
|
|
1175
1099
|
const cls = e.episodeNumber < info.episodeNumber ? "wg-ep-done"
|
|
@@ -1180,7 +1104,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1180
1104
|
const enteredCls = info.justEnteredEpisode && e.episodeNumber === info.episodeNumber
|
|
1181
1105
|
? " wg-ep-entered"
|
|
1182
1106
|
: "";
|
|
1183
|
-
return "<span class
|
|
1107
|
+
return "<span class=\\"wg-ep-tab " + cls + enteredCls + "\\">Episode " + e.episodeNumber + ": " +
|
|
1184
1108
|
esc(e.episodeTitle || "") + "</span>";
|
|
1185
1109
|
})
|
|
1186
1110
|
.join("") +
|
|
@@ -1196,66 +1120,67 @@ async function renderBeforeStep(page, info) {
|
|
|
1196
1120
|
.map((name, idx) => {
|
|
1197
1121
|
const cls = idx < info.moduleIndex ? "wg-mod-done" : idx === info.moduleIndex ? "wg-mod-current" : "wg-mod-upcoming";
|
|
1198
1122
|
const desc = info.allDescriptions && info.allDescriptions[idx];
|
|
1199
|
-
const titleAttr = desc ? " title
|
|
1200
|
-
return "<span class
|
|
1123
|
+
const titleAttr = desc ? " title=\\"" + esc(desc) + "\\"" : "";
|
|
1124
|
+
return "<span class=\\"wg-mod " + cls + "\\"" + titleAttr + ">" + name + "</span>";
|
|
1201
1125
|
})
|
|
1202
1126
|
.join("");
|
|
1203
1127
|
const narrationHtml = info.description
|
|
1204
|
-
? "<div class
|
|
1128
|
+
? "<div class=\\"wg-narration\\">" + esc(info.description) + "</div>"
|
|
1205
1129
|
: "";
|
|
1206
1130
|
const paceHtml =
|
|
1207
1131
|
info.paceBadge || info.paceLabel
|
|
1208
|
-
? "<div class
|
|
1132
|
+
? "<div class=\\"wg-pace\\" data-pace-kind=\\"" +
|
|
1209
1133
|
esc(info.paceKind || "normal") +
|
|
1210
|
-
"
|
|
1134
|
+
"\\"><span class=\\"wg-pace-badge\\">" +
|
|
1211
1135
|
esc(info.paceBadge || "1x") +
|
|
1212
1136
|
"</span><span>" +
|
|
1213
1137
|
esc(info.paceLabel || "") +
|
|
1214
1138
|
"</span></div>"
|
|
1215
1139
|
: "";
|
|
1140
|
+
const todosHtml = (() => {
|
|
1141
|
+
const list = info.todos || [];
|
|
1142
|
+
if (!list.length) return "";
|
|
1143
|
+
return (
|
|
1144
|
+
'<ul id=\\"wg-todos\\">' +
|
|
1145
|
+
list
|
|
1146
|
+
.map((t) => {
|
|
1147
|
+
const cls = t.current ? "wg-todo-current" : t.done ? "wg-todo-done" : "wg-todo-pending";
|
|
1148
|
+
const mark = t.done
|
|
1149
|
+
? String.fromCharCode(0x2713)
|
|
1150
|
+
: t.current
|
|
1151
|
+
? String.fromCharCode(0x2192)
|
|
1152
|
+
: String.fromCharCode(0x25cb);
|
|
1153
|
+
return (
|
|
1154
|
+
'<li class=\\"' +
|
|
1155
|
+
cls +
|
|
1156
|
+
'\\"><span class=\\"wg-todo-mark\\">' +
|
|
1157
|
+
mark +
|
|
1158
|
+
'</span><span>' +
|
|
1159
|
+
esc(t.text || "") +
|
|
1160
|
+
"</span></li>"
|
|
1161
|
+
);
|
|
1162
|
+
})
|
|
1163
|
+
.join("") +
|
|
1164
|
+
"</ul>"
|
|
1165
|
+
);
|
|
1166
|
+
})();
|
|
1216
1167
|
let html =
|
|
1217
|
-
"<div id
|
|
1168
|
+
"<div id=\\"wg-progress\\"><div id=\\"wg-progress-bar\\" style=\\"width:" + pct + "%\\"></div></div>" +
|
|
1218
1169
|
episodesHtml +
|
|
1219
1170
|
paceHtml +
|
|
1220
|
-
"<div id
|
|
1171
|
+
"<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
|
|
1221
1172
|
"<h3>Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + "</h3>" +
|
|
1222
1173
|
narrationHtml +
|
|
1223
|
-
|
|
1224
|
-
const list = info.todos || [];
|
|
1225
|
-
if (!list.length) return "";
|
|
1226
|
-
return (
|
|
1227
|
-
'<ul id="wg-todos">' +
|
|
1228
|
-
list
|
|
1229
|
-
.map((t) => {
|
|
1230
|
-
const cls = t.current ? "wg-todo-current" : t.done ? "wg-todo-done" : "wg-todo-pending";
|
|
1231
|
-
const mark = t.done
|
|
1232
|
-
? String.fromCharCode(0x2713)
|
|
1233
|
-
: t.current
|
|
1234
|
-
? String.fromCharCode(0x2192)
|
|
1235
|
-
: String.fromCharCode(0x25cb);
|
|
1236
|
-
return (
|
|
1237
|
-
'<li class="' +
|
|
1238
|
-
cls +
|
|
1239
|
-
'"><span class="wg-todo-mark">' +
|
|
1240
|
-
mark +
|
|
1241
|
-
'</span><span>' +
|
|
1242
|
-
esc(t.text || "") +
|
|
1243
|
-
'</span></li>'
|
|
1244
|
-
);
|
|
1245
|
-
})
|
|
1246
|
-
.join("") +
|
|
1247
|
-
'</ul>'
|
|
1248
|
-
);
|
|
1249
|
-
})());
|
|
1174
|
+
todosHtml;
|
|
1250
1175
|
if (info.keys.length === 0) {
|
|
1251
|
-
html += "<div class
|
|
1176
|
+
html += "<div class=\\"wg-key\\">(no MemKeys required)</div>";
|
|
1252
1177
|
}
|
|
1253
1178
|
for (const k of info.keys) {
|
|
1254
1179
|
html +=
|
|
1255
|
-
"<div class
|
|
1256
|
-
"<textarea data-key
|
|
1180
|
+
"<div class=\\"wg-key\\"><label>" + k.name + "</label>" +
|
|
1181
|
+
"<textarea data-key=\\"" + k.name + "\\" rows=\\"2\\">" +
|
|
1257
1182
|
k.value.replace(/</g, "<") + "</textarea>" +
|
|
1258
|
-
"<div class
|
|
1183
|
+
"<div class=\\"wg-key-pretty\\" data-pretty-for=\\"" + esc(k.name) + "\\"></div>" +
|
|
1259
1184
|
"</div>";
|
|
1260
1185
|
}
|
|
1261
1186
|
let autoNow = false;
|
|
@@ -1265,17 +1190,19 @@ async function renderBeforeStep(page, info) {
|
|
|
1265
1190
|
/* private mode / blocked storage - defaults to manual */
|
|
1266
1191
|
}
|
|
1267
1192
|
html +=
|
|
1268
|
-
"<div class
|
|
1193
|
+
"<div class=\\"wg-autoplay-row\\"><label><input type=\\"checkbox\\" id=\\"wg-autoplay-cb\\"" +
|
|
1269
1194
|
(autoNow ? " checked" : "") +
|
|
1270
1195
|
"> Auto-advance</label></div>" +
|
|
1271
|
-
"<div id
|
|
1272
|
-
"><button id
|
|
1273
|
-
"<div id
|
|
1196
|
+
"<div id=\\"wg-gate-manual\\"" + (autoNow ? " style=\\"display:none\\"" : "") +
|
|
1197
|
+
"><button id=\\"wg-run\\">Run this step \\u25B6</button></div>" +
|
|
1198
|
+
"<div id=\\"wg-gate-auto\\" class=\\"wg-auto\\"" + (autoNow ? "" : " style=\\"display:none\\"") +
|
|
1274
1199
|
">Auto-advancing...</div>";
|
|
1275
1200
|
panel.innerHTML = html;
|
|
1276
1201
|
if (isNewPanel) {
|
|
1277
1202
|
document.documentElement.appendChild(panel);
|
|
1278
1203
|
requestAnimationFrame(() => panel.classList.add("wg-in"));
|
|
1204
|
+
} else {
|
|
1205
|
+
panel.classList.add("wg-in");
|
|
1279
1206
|
}
|
|
1280
1207
|
if (window.__wgWirePanelChrome) {
|
|
1281
1208
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
@@ -1283,6 +1210,15 @@ async function renderBeforeStep(page, info) {
|
|
|
1283
1210
|
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1284
1211
|
});
|
|
1285
1212
|
}
|
|
1213
|
+
if (window.__wgStampModal) {
|
|
1214
|
+
window.__wgStampModal(panel, "panel", {
|
|
1215
|
+
phase: "before",
|
|
1216
|
+
step: info.index + 1,
|
|
1217
|
+
total: info.total,
|
|
1218
|
+
block: info.blockName || "",
|
|
1219
|
+
ready: true,
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1286
1222
|
// Live, human-readable preview of what a MemKey's raw JSON will
|
|
1287
1223
|
// actually write - object fields become "Field: value" lines
|
|
1288
1224
|
// (camelCase split the same way state tags already are); a
|
|
@@ -1303,7 +1239,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1303
1239
|
return Object.entries(parsed)
|
|
1304
1240
|
.map(([field, value]) => {
|
|
1305
1241
|
const label = field.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
|
|
1306
|
-
return "<div class
|
|
1242
|
+
return "<div class=\\"wg-key-pretty-row\\"><span class=\\"wg-key-pretty-label\\">" +
|
|
1307
1243
|
esc(label) + ":</span> " + esc(String(value)) + "</div>";
|
|
1308
1244
|
})
|
|
1309
1245
|
.join("");
|
|
@@ -1312,7 +1248,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1312
1248
|
const prettyOn = window.__wgPretty !== false;
|
|
1313
1249
|
panel.querySelectorAll("textarea[data-key]").forEach((ta) => {
|
|
1314
1250
|
const key = ta.getAttribute("data-key");
|
|
1315
|
-
const previewEl = panel.querySelector(".wg-key-pretty[data-pretty-for
|
|
1251
|
+
const previewEl = panel.querySelector(".wg-key-pretty[data-pretty-for=\\"" + key + "\\"]");
|
|
1316
1252
|
if (!previewEl) return;
|
|
1317
1253
|
previewEl.style.display = prettyOn ? "" : "none";
|
|
1318
1254
|
if (prettyOn) previewEl.innerHTML = prettyMemValue(ta.value);
|
|
@@ -1354,29 +1290,42 @@ async function renderBeforeStep(page, info) {
|
|
|
1354
1290
|
}
|
|
1355
1291
|
}, payload)
|
|
1356
1292
|
.catch(() => {});
|
|
1293
|
+
if (process.env.WAYGRAPH_PROVE_SHOT || process.env.WAYGRAPH_PROVE_EXIT === "1") {
|
|
1294
|
+
// Default: prove once on first painted panel. PROVE_EVERY=1 re-checks
|
|
1295
|
+
// every step (noisy; useful when hunting a mid-chain blank).
|
|
1296
|
+
const proveEvery = process.env.WAYGRAPH_PROVE_EVERY === "1";
|
|
1297
|
+
const already = globalThis.__wgProveDone;
|
|
1298
|
+
if (!already || proveEvery || process.env.WAYGRAPH_PROVE_EXIT === "1") {
|
|
1299
|
+
await page.waitForTimeout(500).catch(() => {});
|
|
1300
|
+
if (process.env.WAYGRAPH_PROVE_SHOT) {
|
|
1301
|
+
await page.screenshot({ path: process.env.WAYGRAPH_PROVE_SHOT }).catch(() => {});
|
|
1302
|
+
}
|
|
1303
|
+
const ok = await page
|
|
1304
|
+
.evaluate(() => {
|
|
1305
|
+
const beacons =
|
|
1306
|
+
typeof window.__wgOverlayBeacon === "function" ? window.__wgOverlayBeacon() : [];
|
|
1307
|
+
const panel = beacons.find((b) => b.modal === "panel" && b.ready && b.visible);
|
|
1308
|
+
const textOk = panel && panel.textLen >= 12;
|
|
1309
|
+
const opacityOk = panel && Number(panel.opacity) > 0.5;
|
|
1310
|
+
return {
|
|
1311
|
+
ok: !!(panel && textOk && opacityOk),
|
|
1312
|
+
beacons,
|
|
1313
|
+
hasRun: !!document.getElementById("wg-run"),
|
|
1314
|
+
readyAttr: document.getElementById("wg-panel")?.getAttribute("data-wg-ready") || null,
|
|
1315
|
+
};
|
|
1316
|
+
})
|
|
1317
|
+
.catch((e) => ({ ok: false, reason: String(e && e.message ? e.message : e) }));
|
|
1318
|
+
console.error("WAYGRAPH_PROVE " + JSON.stringify(ok));
|
|
1319
|
+
globalThis.__wgProveDone = true;
|
|
1320
|
+
if (process.env.WAYGRAPH_PROVE_EXIT === "1") {
|
|
1321
|
+
process.exit(ok && ok.ok ? 0 : 2);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1357
1325
|
}
|
|
1358
1326
|
|
|
1359
1327
|
async function ensureSelectorInView(page, selector) {
|
|
1360
1328
|
try {
|
|
1361
|
-
// Body-scale camera + scrollIntoView = teleport / "outside viewport" loop.
|
|
1362
|
-
// When zoomed, pan the camera to the target instead of scrolling the document.
|
|
1363
|
-
const zoomed = await page
|
|
1364
|
-
.evaluate(() => !!(window.__wgCamera && window.__wgCamera.scale > 1.001))
|
|
1365
|
-
.catch(() => false);
|
|
1366
|
-
if (zoomed) {
|
|
1367
|
-
await page
|
|
1368
|
-
.evaluate((sel) => {
|
|
1369
|
-
const el = document.querySelector(sel);
|
|
1370
|
-
if (!el || !window.__wgSetCamera || !window.__wgElDocPoint) return;
|
|
1371
|
-
const cam = window.__wgCamera;
|
|
1372
|
-
const pt = window.__wgElDocPoint(el);
|
|
1373
|
-
window.__wgCameraLock = true;
|
|
1374
|
-
window.__wgSetCamera(pt.x, pt.y, cam.scale > 1 ? cam.scale : 1.4, 320);
|
|
1375
|
-
}, selector)
|
|
1376
|
-
.catch(() => {});
|
|
1377
|
-
await new Promise((res) => setTimeout(res, 340));
|
|
1378
|
-
return;
|
|
1379
|
-
}
|
|
1380
1329
|
const loc = page.locator(selector).first();
|
|
1381
1330
|
await loc.scrollIntoViewIfNeeded().catch(() => {});
|
|
1382
1331
|
await page
|
|
@@ -1443,54 +1392,48 @@ async function ensureSelectorInView(page, selector) {
|
|
|
1443
1392
|
}
|
|
1444
1393
|
}
|
|
1445
1394
|
|
|
1446
|
-
async function
|
|
1447
|
-
const z =
|
|
1448
|
-
if (!
|
|
1395
|
+
async function applyHighlightZoom(page, selector, zoom) {
|
|
1396
|
+
const z = Number(zoom);
|
|
1397
|
+
if (!Number.isFinite(z) || z <= 1.001) {
|
|
1449
1398
|
await page.evaluate(() => {
|
|
1450
|
-
if (window.
|
|
1399
|
+
if (window.__wgClearZoom) window.__wgClearZoom();
|
|
1451
1400
|
}).catch(() => {});
|
|
1452
1401
|
return;
|
|
1453
1402
|
}
|
|
1454
|
-
const vx = box.x + box.width / 2;
|
|
1455
|
-
const vy = box.y + box.height / 2;
|
|
1456
|
-
const dur = ms != null ? ms : 650;
|
|
1457
|
-
await page
|
|
1458
|
-
.evaluate(({ vx, vy, scale, dur }) => {
|
|
1459
|
-
if (!window.__wgSetCamera) return;
|
|
1460
|
-
const pt = window.__wgViewportToDoc
|
|
1461
|
-
? window.__wgViewportToDoc(vx, vy)
|
|
1462
|
-
: { x: vx, y: vy };
|
|
1463
|
-
window.__wgSetCamera(pt.x, pt.y, scale, dur);
|
|
1464
|
-
}, { vx, vy, scale: z, dur })
|
|
1465
|
-
.catch(() => {});
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
async function setDemoTodos(page, todos) {
|
|
1469
1403
|
await page
|
|
1470
|
-
.evaluate(
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
async function resetCameraZoom(page) {
|
|
1477
|
-
await page
|
|
1478
|
-
.evaluate(() => {
|
|
1479
|
-
if (window.__wgResetCamera) window.__wgResetCamera();
|
|
1480
|
-
})
|
|
1404
|
+
.evaluate(
|
|
1405
|
+
({ sel, scale }) => {
|
|
1406
|
+
if (window.__wgApplyZoom) window.__wgApplyZoom(sel, scale);
|
|
1407
|
+
},
|
|
1408
|
+
{ sel: selector, scale: z },
|
|
1409
|
+
)
|
|
1481
1410
|
.catch(() => {});
|
|
1482
1411
|
}
|
|
1483
1412
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1413
|
+
function todosHtmlFromInfo(info, escFn) {
|
|
1414
|
+
const list = info.todos || [];
|
|
1415
|
+
if (!list.length) return "";
|
|
1416
|
+
return (
|
|
1417
|
+
"<ul id=\\"wg-todos\\">" +
|
|
1418
|
+
list
|
|
1419
|
+
.map((t) => {
|
|
1420
|
+
const cls = t.current ? "wg-todo-current" : t.done ? "wg-todo-done" : "wg-todo-pending";
|
|
1421
|
+
const mark = t.done ? "\\u2713" : t.current ? "\\u2192" : "\\u25CB";
|
|
1422
|
+
return (
|
|
1423
|
+
"<li class=\\"" +
|
|
1424
|
+
cls +
|
|
1425
|
+
"\\"><span class=\\"wg-todo-mark\\">" +
|
|
1426
|
+
mark +
|
|
1427
|
+
"</span><span>" +
|
|
1428
|
+
escFn(t.text || "") +
|
|
1429
|
+
"</span></li>"
|
|
1430
|
+
);
|
|
1431
|
+
})
|
|
1432
|
+
.join("") +
|
|
1433
|
+
"</ul>"
|
|
1434
|
+
);
|
|
1491
1435
|
}
|
|
1492
1436
|
|
|
1493
|
-
/** Collapsed chrome title: Ep N / i / n / block / pace. */
|
|
1494
1437
|
function miniStepLabel(info) {
|
|
1495
1438
|
const ep =
|
|
1496
1439
|
info.episodeNumber !== undefined && info.episodeNumber !== null
|
|
@@ -1506,176 +1449,175 @@ function miniStepLabel(info) {
|
|
|
1506
1449
|
);
|
|
1507
1450
|
}
|
|
1508
1451
|
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
if (!span) {
|
|
1538
|
-
span = document.createElement("span");
|
|
1539
|
-
span.id = "wg-hl-idx";
|
|
1540
|
-
span.style.marginLeft = "8px";
|
|
1541
|
-
span.style.color = "#c9a6ff";
|
|
1542
|
-
span.style.fontWeight = "700";
|
|
1543
|
-
h3.appendChild(span);
|
|
1544
|
-
}
|
|
1545
|
-
span.textContent = tag;
|
|
1546
|
-
}
|
|
1547
|
-
},
|
|
1548
|
-
{ tag, baseStepLabel, i, n },
|
|
1549
|
-
)
|
|
1550
|
-
.catch(() => {});
|
|
1551
|
-
};
|
|
1552
|
-
|
|
1553
|
-
const stopLiveFollow = async () => {
|
|
1452
|
+
/**
|
|
1453
|
+
* stubOnError rings + amber "expected outcome" / red error panel, then wait
|
|
1454
|
+
* for Stop/Retry. Used when a step throws OR when withExpectedFailure's last
|
|
1455
|
+
* block succeeds on the intentional fail branch (e.g. LoginPage + error banner).
|
|
1456
|
+
*/
|
|
1457
|
+
async function presentFailPanel(page, opts) {
|
|
1458
|
+
const {
|
|
1459
|
+
block,
|
|
1460
|
+
fixtures,
|
|
1461
|
+
error,
|
|
1462
|
+
index,
|
|
1463
|
+
total,
|
|
1464
|
+
blockName,
|
|
1465
|
+
message,
|
|
1466
|
+
allNames,
|
|
1467
|
+
allDescriptions,
|
|
1468
|
+
moduleIndex,
|
|
1469
|
+
allEpisodes,
|
|
1470
|
+
title,
|
|
1471
|
+
episodeNumber,
|
|
1472
|
+
episodeTitle,
|
|
1473
|
+
expectedFailureReason,
|
|
1474
|
+
stepperMode,
|
|
1475
|
+
gatesFast,
|
|
1476
|
+
demoPace,
|
|
1477
|
+
gate,
|
|
1478
|
+
} = opts;
|
|
1479
|
+
if (hasAuthoredStubOnError(block, fixtures)) {
|
|
1554
1480
|
await page
|
|
1555
1481
|
.evaluate(() => {
|
|
1556
|
-
if (window.__wgStopHighlightTrack) window.__wgStopHighlightTrack();
|
|
1557
|
-
if (window.__wgRingRaf) {
|
|
1558
|
-
cancelAnimationFrame(window.__wgRingRaf);
|
|
1559
|
-
window.__wgRingRaf = null;
|
|
1560
|
-
}
|
|
1561
1482
|
if (window.__wgRingTrack) {
|
|
1562
|
-
window.removeEventListener("mousemove", window.__wgRingTrack);
|
|
1563
1483
|
window.removeEventListener("resize", window.__wgRingTrack);
|
|
1564
1484
|
window.removeEventListener("scroll", window.__wgRingTrack, true);
|
|
1565
1485
|
window.__wgRingTrack = null;
|
|
1566
1486
|
}
|
|
1567
|
-
if (window.__wgHideFocusVeil) window.__wgHideFocusVeil();
|
|
1568
1487
|
})
|
|
1569
1488
|
.catch(() => {});
|
|
1570
|
-
|
|
1489
|
+
const errPhase = await runStubPhase(block, "stubOnError", {
|
|
1490
|
+
fixtures,
|
|
1491
|
+
error,
|
|
1492
|
+
out: opts.out,
|
|
1493
|
+
});
|
|
1494
|
+
const errHighlights = errPhase.highlights.map((h) => {
|
|
1495
|
+
const styled = applyHighlightStyleDefaults(h, opts.highlightStyle);
|
|
1496
|
+
return {
|
|
1497
|
+
selector: styled.selector,
|
|
1498
|
+
label: formatHighlightCaption(styled),
|
|
1499
|
+
duration: styled.duration,
|
|
1500
|
+
fastMode: styled.fastMode,
|
|
1501
|
+
tone: styled.tone,
|
|
1502
|
+
size: styled.size,
|
|
1503
|
+
weight: styled.weight,
|
|
1504
|
+
zoom: styled.zoom,
|
|
1505
|
+
};
|
|
1506
|
+
});
|
|
1507
|
+
await cycleHighlightRings(page, errHighlights, !!gatesFast, {
|
|
1508
|
+
defaultHoldMs: 2000,
|
|
1509
|
+
pace: demoPace,
|
|
1510
|
+
todos: errPhase.todos,
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
await renderStepError(page, {
|
|
1514
|
+
index,
|
|
1515
|
+
total,
|
|
1516
|
+
blockName,
|
|
1517
|
+
message,
|
|
1518
|
+
allNames,
|
|
1519
|
+
allDescriptions,
|
|
1520
|
+
moduleIndex,
|
|
1521
|
+
allEpisodes,
|
|
1522
|
+
title,
|
|
1523
|
+
episodeNumber,
|
|
1524
|
+
episodeTitle,
|
|
1525
|
+
expectedFailureReason,
|
|
1526
|
+
stepperMode,
|
|
1527
|
+
});
|
|
1528
|
+
return gate();
|
|
1529
|
+
}
|
|
1571
1530
|
|
|
1531
|
+
async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
1532
|
+
const list = highlights || [];
|
|
1533
|
+
// defaultHoldMs: when set (fail path), use instead of legacy 900/200 so BUG
|
|
1534
|
+
// rings stay visible ~2s before the error panel (PIA stubOnError RFC).
|
|
1535
|
+
const defaultHoldMs = opts && opts.defaultHoldMs != null ? opts.defaultHoldMs : null;
|
|
1536
|
+
const pace = opts && opts.pace !== undefined ? opts.pace : undefined;
|
|
1572
1537
|
for (let i = 0; i < list.length; i++) {
|
|
1573
1538
|
const h = list[i];
|
|
1574
1539
|
try {
|
|
1575
|
-
await stopLiveFollow();
|
|
1576
1540
|
await ensureSelectorInView(page, h.selector);
|
|
1541
|
+
await applyHighlightZoom(page, h.selector, h.zoom);
|
|
1577
1542
|
const box = await page.locator(h.selector).first().boundingBox();
|
|
1578
|
-
if (
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
zoom: z,
|
|
1619
|
-
camMs,
|
|
1620
|
-
},
|
|
1621
|
-
)
|
|
1622
|
-
.catch(() => {});
|
|
1623
|
-
const alreadyZoomed = await page
|
|
1624
|
-
.evaluate(() => !!(window.__wgCamera && window.__wgCamera.scale > 1.001))
|
|
1625
|
-
.catch(() => false);
|
|
1626
|
-
const waitMs = alreadyZoomed ? Math.min(camMs, 420) : camMs;
|
|
1627
|
-
await new Promise((res) => setTimeout(res, waitMs));
|
|
1628
|
-
if (h.cursor !== false) {
|
|
1629
|
-
// Park Playwright real mouse + demo cursor on the LIVE centered target.
|
|
1630
|
-
const live = await page
|
|
1631
|
-
.evaluate((sel) => {
|
|
1632
|
-
const el = document.querySelector(sel);
|
|
1633
|
-
if (!el) return null;
|
|
1634
|
-
const r = el.getBoundingClientRect();
|
|
1635
|
-
return { x: r.left, y: r.top, width: r.width, height: r.height };
|
|
1636
|
-
}, h.selector)
|
|
1637
|
-
.catch(() => null);
|
|
1638
|
-
if (live) {
|
|
1639
|
-
await moveCursorTo(page, live, Math.min(280, camMs), { keepLock: true });
|
|
1543
|
+
if (box) {
|
|
1544
|
+
await showRing(page, box, h.label, h.tone || "planned", {
|
|
1545
|
+
size: h.size,
|
|
1546
|
+
weight: h.weight,
|
|
1547
|
+
});
|
|
1548
|
+
if ((opts && opts.todos && opts.todos.length) || (h.todos && h.todos.length)) {
|
|
1549
|
+
const rows =
|
|
1550
|
+
opts && opts.todos && opts.todos.length
|
|
1551
|
+
? opts.todos
|
|
1552
|
+
: normalizeTodos(h.todos, h.todoIndex);
|
|
1553
|
+
await page
|
|
1554
|
+
.evaluate((todos) => {
|
|
1555
|
+
const ul = document.getElementById("wg-todos");
|
|
1556
|
+
if (!ul) return;
|
|
1557
|
+
ul.innerHTML = todos
|
|
1558
|
+
.map((t) => {
|
|
1559
|
+
const cls = t.current
|
|
1560
|
+
? "wg-todo-current"
|
|
1561
|
+
: t.done
|
|
1562
|
+
? "wg-todo-done"
|
|
1563
|
+
: "wg-todo-pending";
|
|
1564
|
+
const mark = t.done ? "\\u2713" : t.current ? "\\u2192" : "\\u25CB";
|
|
1565
|
+
const esc = (s) =>
|
|
1566
|
+
String(s)
|
|
1567
|
+
.replace(/&/g, "&")
|
|
1568
|
+
.replace(/</g, "<")
|
|
1569
|
+
.replace(/>/g, ">");
|
|
1570
|
+
return (
|
|
1571
|
+
"<li class=\\"" +
|
|
1572
|
+
cls +
|
|
1573
|
+
"\\"><span class=\\"wg-todo-mark\\">" +
|
|
1574
|
+
mark +
|
|
1575
|
+
"</span><span>" +
|
|
1576
|
+
esc(t.text || "") +
|
|
1577
|
+
"</span></li>"
|
|
1578
|
+
);
|
|
1579
|
+
})
|
|
1580
|
+
.join("");
|
|
1581
|
+
}, rows)
|
|
1582
|
+
.catch(() => {});
|
|
1640
1583
|
}
|
|
1584
|
+
const authored = resolveFixtureDwellMs(h, { gatesFast, pace });
|
|
1585
|
+
const legacyMs = i === list.length - 1 ? 200 : 900;
|
|
1586
|
+
const holdMs =
|
|
1587
|
+
authored != null ? authored : defaultHoldMs != null ? defaultHoldMs : legacyMs;
|
|
1588
|
+
await new Promise((res) => setTimeout(res, holdMs));
|
|
1641
1589
|
}
|
|
1642
|
-
// Appear settle
|
|
1643
|
-
await new Promise((res) => setTimeout(res, fadeMs));
|
|
1644
|
-
const authored = resolveFixtureDwellMs(h, { gatesFast, pace });
|
|
1645
|
-
const legacyMs = 1100;
|
|
1646
|
-
const holdMs =
|
|
1647
|
-
authored != null ? authored : defaultHoldMs != null ? defaultHoldMs : legacyMs;
|
|
1648
|
-
await new Promise((res) => setTimeout(res, holdMs));
|
|
1649
|
-
// Disappear before next. zoomOut:false keeps camera for the next ring.
|
|
1650
|
-
await stopLiveFollow();
|
|
1651
|
-
await hideRing(page);
|
|
1652
|
-
const doZoomOut = h.zoomOut !== false;
|
|
1653
|
-
if (doZoomOut) await resetCameraZoom(page);
|
|
1654
|
-
await new Promise((res) => setTimeout(res, doZoomOut ? fadeMs : 120));
|
|
1655
1590
|
} catch {
|
|
1656
|
-
|
|
1591
|
+
// best-effort - a selector that doesn't resolve just gets skipped
|
|
1657
1592
|
}
|
|
1658
1593
|
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
const last = list.length > 0 ? list[list.length - 1] : null;
|
|
1662
|
-
if (!last || last.zoomOut !== false) {
|
|
1663
|
-
await resetCameraZoom(page);
|
|
1664
|
-
} else {
|
|
1665
|
-
// Hold lock into the upcoming act click so follow/scroll cannot yank
|
|
1666
|
-
// the camera off the already-centered target (cart teleport bug).
|
|
1594
|
+
if (list.length > 0) {
|
|
1595
|
+
const last = list[list.length - 1];
|
|
1667
1596
|
await page
|
|
1668
|
-
.evaluate(() => {
|
|
1669
|
-
window.
|
|
1670
|
-
|
|
1597
|
+
.evaluate((h) => {
|
|
1598
|
+
if (!window.__wgPositionRing) return;
|
|
1599
|
+
const reposition = () => {
|
|
1600
|
+
const el = document.querySelector(h.selector);
|
|
1601
|
+
if (!el) {
|
|
1602
|
+
if (window.__wgHideRing) window.__wgHideRing();
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
window.__wgPositionRing(el.getBoundingClientRect(), h.label, h.tone || "planned", {
|
|
1606
|
+
size: h.size || "md",
|
|
1607
|
+
weight: h.weight || "normal",
|
|
1608
|
+
});
|
|
1609
|
+
};
|
|
1610
|
+
reposition();
|
|
1611
|
+
window.__wgRingTrack = reposition;
|
|
1612
|
+
window.addEventListener("resize", reposition);
|
|
1613
|
+
window.addEventListener("scroll", reposition, true);
|
|
1614
|
+
}, last)
|
|
1671
1615
|
.catch(() => {});
|
|
1672
|
-
}
|
|
1673
|
-
if (list.length === 0) {
|
|
1616
|
+
} else {
|
|
1674
1617
|
await hideRing(page);
|
|
1675
1618
|
}
|
|
1676
1619
|
}
|
|
1677
1620
|
|
|
1678
|
-
|
|
1679
1621
|
async function renderAfterStep(page, info) {
|
|
1680
1622
|
await installOverlay(page, info.title);
|
|
1681
1623
|
// Clear any tracker from a previous highlight before cycling through this
|
|
@@ -1721,8 +1663,8 @@ async function renderAfterStep(page, info) {
|
|
|
1721
1663
|
.map((name, idx) => {
|
|
1722
1664
|
const cls = idx <= info.moduleIndex ? "wg-mod-done" : "wg-mod-upcoming";
|
|
1723
1665
|
const desc = info.allDescriptions && info.allDescriptions[idx];
|
|
1724
|
-
const titleAttr = desc ? " title
|
|
1725
|
-
return "<span class
|
|
1666
|
+
const titleAttr = desc ? " title=\\"" + escA(desc) + "\\"" : "";
|
|
1667
|
+
return "<span class=\\"wg-mod " + cls + "\\"" + titleAttr + ">" + name + "</span>";
|
|
1726
1668
|
})
|
|
1727
1669
|
.join("");
|
|
1728
1670
|
// QA-friendly by default ("LoginSuccess" -> "Login Success") - raw
|
|
@@ -1733,13 +1675,13 @@ async function renderAfterStep(page, info) {
|
|
|
1733
1675
|
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2") || info.resultTag;
|
|
1734
1676
|
const pretty = window.__wgPretty !== false;
|
|
1735
1677
|
const resultHtml =
|
|
1736
|
-
"<div class
|
|
1737
|
-
"<button class
|
|
1738
|
-
"<button class
|
|
1678
|
+
"<div class=\\"wg-toggle\\">" +
|
|
1679
|
+
"<button class=\\"wg-toggle-btn" + (pretty ? " wg-active" : "") + "\\" data-mode=\\"pretty\\">Pretty</button>" +
|
|
1680
|
+
"<button class=\\"wg-toggle-btn" + (pretty ? "" : " wg-active") + "\\" data-mode=\\"json\\">JSON</button>" +
|
|
1739
1681
|
"</div>" +
|
|
1740
|
-
"<div class
|
|
1682
|
+
"<div class=\\"wg-result wg-result-pretty\\" style=\\"display:" + (pretty ? "block" : "none") + "\\">" +
|
|
1741
1683
|
prettyText + "</div>" +
|
|
1742
|
-
"<div class
|
|
1684
|
+
"<div class=\\"wg-result wg-result-json\\" style=\\"display:" + (pretty ? "none" : "block") + "\\">" +
|
|
1743
1685
|
info.resultTag + "</div>";
|
|
1744
1686
|
let autoNow = false;
|
|
1745
1687
|
try {
|
|
@@ -1748,21 +1690,21 @@ async function renderAfterStep(page, info) {
|
|
|
1748
1690
|
/* private mode / blocked storage - defaults to manual */
|
|
1749
1691
|
}
|
|
1750
1692
|
const gateHtml =
|
|
1751
|
-
"<div class
|
|
1693
|
+
"<div class=\\"wg-autoplay-row\\"><label><input type=\\"checkbox\\" id=\\"wg-autoplay-cb\\"" +
|
|
1752
1694
|
(autoNow ? " checked" : "") +
|
|
1753
1695
|
"> Auto-advance</label></div>" +
|
|
1754
|
-
"<div id
|
|
1755
|
-
"><button id
|
|
1756
|
-
"<div id
|
|
1696
|
+
"<div id=\\"wg-gate-manual\\"" + (autoNow ? " style=\\"display:none\\"" : "") +
|
|
1697
|
+
"><button id=\\"wg-run\\">" + buttonLabel + "</button></div>" +
|
|
1698
|
+
"<div id=\\"wg-gate-auto\\" class=\\"wg-auto\\"" + (autoNow ? "" : " style=\\"display:none\\"") +
|
|
1757
1699
|
">Auto-advancing...</div>";
|
|
1758
1700
|
const episodesHtml = info.allEpisodes && info.allEpisodes.length > 0
|
|
1759
|
-
? "<div id
|
|
1701
|
+
? "<div id=\\"wg-episodes\\">" +
|
|
1760
1702
|
info.allEpisodes
|
|
1761
1703
|
.map((e) => {
|
|
1762
1704
|
const cls = e.episodeNumber < info.episodeNumber ? "wg-ep-done"
|
|
1763
1705
|
: e.episodeNumber === info.episodeNumber ? "wg-ep-current"
|
|
1764
1706
|
: "wg-ep-upcoming";
|
|
1765
|
-
return "<span class
|
|
1707
|
+
return "<span class=\\"wg-ep-tab " + cls + "\\">Episode " + e.episodeNumber + ": " +
|
|
1766
1708
|
escA(e.episodeTitle || "") + "</span>";
|
|
1767
1709
|
})
|
|
1768
1710
|
.join("") +
|
|
@@ -1770,52 +1712,55 @@ async function renderAfterStep(page, info) {
|
|
|
1770
1712
|
: "";
|
|
1771
1713
|
const paceHtml =
|
|
1772
1714
|
info.paceBadge || info.paceLabel
|
|
1773
|
-
? "<div class
|
|
1715
|
+
? "<div class=\\"wg-pace\\" data-pace-kind=\\"" +
|
|
1774
1716
|
escA(info.paceKind || "normal") +
|
|
1775
|
-
"
|
|
1717
|
+
"\\"><span class=\\"wg-pace-badge\\">" +
|
|
1776
1718
|
escA(info.paceBadge || "1x") +
|
|
1777
1719
|
"</span><span>" +
|
|
1778
1720
|
escA(info.paceLabel || "") +
|
|
1779
1721
|
"</span></div>"
|
|
1780
1722
|
: "";
|
|
1723
|
+
const todosHtml = (() => {
|
|
1724
|
+
const list = info.todos || [];
|
|
1725
|
+
if (!list.length) return "";
|
|
1726
|
+
return (
|
|
1727
|
+
'<ul id=\\"wg-todos\\">' +
|
|
1728
|
+
list
|
|
1729
|
+
.map((t) => {
|
|
1730
|
+
const cls = t.current ? "wg-todo-current" : t.done ? "wg-todo-done" : "wg-todo-pending";
|
|
1731
|
+
const mark = t.done
|
|
1732
|
+
? String.fromCharCode(0x2713)
|
|
1733
|
+
: t.current
|
|
1734
|
+
? String.fromCharCode(0x2192)
|
|
1735
|
+
: String.fromCharCode(0x25cb);
|
|
1736
|
+
return (
|
|
1737
|
+
'<li class=\\"' +
|
|
1738
|
+
cls +
|
|
1739
|
+
'\\"><span class=\\"wg-todo-mark\\">' +
|
|
1740
|
+
mark +
|
|
1741
|
+
'</span><span>' +
|
|
1742
|
+
escA(t.text || "") +
|
|
1743
|
+
"</span></li>"
|
|
1744
|
+
);
|
|
1745
|
+
})
|
|
1746
|
+
.join("") +
|
|
1747
|
+
"</ul>"
|
|
1748
|
+
);
|
|
1749
|
+
})();
|
|
1781
1750
|
panel.innerHTML =
|
|
1782
|
-
"<div id
|
|
1751
|
+
"<div id=\\"wg-progress\\"><div id=\\"wg-progress-bar\\" style=\\"width:" + pct + "%\\"></div></div>" +
|
|
1783
1752
|
episodesHtml +
|
|
1784
1753
|
paceHtml +
|
|
1785
|
-
"<div id
|
|
1754
|
+
"<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
|
|
1786
1755
|
"<h3>" + heading + "</h3>" +
|
|
1787
|
-
|
|
1788
|
-
const list = info.todos || [];
|
|
1789
|
-
if (!list.length) return "";
|
|
1790
|
-
return (
|
|
1791
|
-
'<ul id="wg-todos">' +
|
|
1792
|
-
list
|
|
1793
|
-
.map((t) => {
|
|
1794
|
-
const cls = t.current ? "wg-todo-current" : t.done ? "wg-todo-done" : "wg-todo-pending";
|
|
1795
|
-
const mark = t.done
|
|
1796
|
-
? String.fromCharCode(0x2713)
|
|
1797
|
-
: t.current
|
|
1798
|
-
? String.fromCharCode(0x2192)
|
|
1799
|
-
: String.fromCharCode(0x25cb);
|
|
1800
|
-
return (
|
|
1801
|
-
'<li class="' +
|
|
1802
|
-
cls +
|
|
1803
|
-
'"><span class="wg-todo-mark">' +
|
|
1804
|
-
mark +
|
|
1805
|
-
'</span><span>' +
|
|
1806
|
-
escA(t.text || "") +
|
|
1807
|
-
'</span></li>'
|
|
1808
|
-
);
|
|
1809
|
-
})
|
|
1810
|
-
.join("") +
|
|
1811
|
-
'</ul>'
|
|
1812
|
-
);
|
|
1813
|
-
})()) +
|
|
1756
|
+
todosHtml +
|
|
1814
1757
|
resultHtml +
|
|
1815
1758
|
gateHtml;
|
|
1816
1759
|
if (isNewPanel) {
|
|
1817
1760
|
document.documentElement.appendChild(panel);
|
|
1818
1761
|
requestAnimationFrame(() => panel.classList.add("wg-in"));
|
|
1762
|
+
} else {
|
|
1763
|
+
panel.classList.add("wg-in");
|
|
1819
1764
|
}
|
|
1820
1765
|
if (window.__wgWirePanelChrome) {
|
|
1821
1766
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
@@ -1823,6 +1768,15 @@ async function renderAfterStep(page, info) {
|
|
|
1823
1768
|
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1824
1769
|
});
|
|
1825
1770
|
}
|
|
1771
|
+
if (window.__wgStampModal) {
|
|
1772
|
+
window.__wgStampModal(panel, "panel", {
|
|
1773
|
+
phase: "after",
|
|
1774
|
+
step: info.index + 1,
|
|
1775
|
+
total: info.total,
|
|
1776
|
+
block: info.blockName || "",
|
|
1777
|
+
ready: true,
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1826
1780
|
panel.querySelectorAll(".wg-toggle-btn").forEach((btn) => {
|
|
1827
1781
|
btn.addEventListener("click", () => {
|
|
1828
1782
|
const wantPretty = btn.getAttribute("data-mode") === "pretty";
|
|
@@ -1878,13 +1832,13 @@ async function renderStepError(page, info) {
|
|
|
1878
1832
|
if (!isNewPanel) panel.classList.add("wg-in");
|
|
1879
1833
|
const esc = (s) => String(s).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
1880
1834
|
const episodesHtml = info.allEpisodes && info.allEpisodes.length > 0
|
|
1881
|
-
? "<div id
|
|
1835
|
+
? "<div id=\\"wg-episodes\\">" +
|
|
1882
1836
|
info.allEpisodes
|
|
1883
1837
|
.map((e) => {
|
|
1884
1838
|
const cls = e.episodeNumber < info.episodeNumber ? "wg-ep-done"
|
|
1885
1839
|
: e.episodeNumber === info.episodeNumber ? "wg-ep-current"
|
|
1886
1840
|
: "wg-ep-upcoming";
|
|
1887
|
-
return "<span class
|
|
1841
|
+
return "<span class=\\"wg-ep-tab " + cls + "\\">Episode " + e.episodeNumber + ": " +
|
|
1888
1842
|
esc(e.episodeTitle || "") + "</span>";
|
|
1889
1843
|
})
|
|
1890
1844
|
.join("") +
|
|
@@ -1895,8 +1849,8 @@ async function renderStepError(page, info) {
|
|
|
1895
1849
|
.map((name, idx) => {
|
|
1896
1850
|
const cls = idx === info.moduleIndex ? "wg-mod-current" : idx < info.moduleIndex ? "wg-mod-done" : "wg-mod-upcoming";
|
|
1897
1851
|
const desc = info.allDescriptions && info.allDescriptions[idx];
|
|
1898
|
-
const titleAttr = desc ? " title
|
|
1899
|
-
return "<span class
|
|
1852
|
+
const titleAttr = desc ? " title=\\"" + esc(desc) + "\\"" : "";
|
|
1853
|
+
return "<span class=\\"wg-mod " + cls + "\\"" + titleAttr + ">" + name + "</span>";
|
|
1900
1854
|
})
|
|
1901
1855
|
.join("");
|
|
1902
1856
|
const retryLabel = info.episodeNumber !== undefined ? "Retry Episode " + info.episodeNumber : "Retry";
|
|
@@ -1904,21 +1858,23 @@ async function renderStepError(page, info) {
|
|
|
1904
1858
|
? "Expected outcome - Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + " failed as intended"
|
|
1905
1859
|
: "Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + " threw";
|
|
1906
1860
|
const reasonHtml = isExpected
|
|
1907
|
-
? "<div class
|
|
1861
|
+
? "<div class=\\"wg-expected-reason\\">" + esc(info.expectedFailureReason) + "</div>"
|
|
1908
1862
|
: "";
|
|
1909
1863
|
panel.innerHTML =
|
|
1910
1864
|
episodesHtml +
|
|
1911
|
-
"<div id
|
|
1912
|
-
"<h3 class
|
|
1865
|
+
"<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
|
|
1866
|
+
"<h3 class=\\"" + (isExpected ? "wg-expected-heading" : "wg-error-heading") + "\\">" + headingText + "</h3>" +
|
|
1913
1867
|
reasonHtml +
|
|
1914
|
-
"<div class
|
|
1915
|
-
"<div class
|
|
1916
|
-
"<button id
|
|
1917
|
-
"<button id
|
|
1868
|
+
"<div class=\\"wg-error-msg\\">" + esc(info.message) + "</div>" +
|
|
1869
|
+
"<div class=\\"wg-error-actions\\">" +
|
|
1870
|
+
"<button id=\\"wg-error-retry\\" class=\\"wg-error-retry\\">" + esc(retryLabel) + "</button>" +
|
|
1871
|
+
"<button id=\\"wg-run\\" class=\\"wg-error-stop\\">Stop</button>" +
|
|
1918
1872
|
"</div>";
|
|
1919
1873
|
if (isNewPanel) {
|
|
1920
1874
|
document.documentElement.appendChild(panel);
|
|
1921
1875
|
requestAnimationFrame(() => panel.classList.add("wg-in"));
|
|
1876
|
+
} else {
|
|
1877
|
+
panel.classList.add("wg-in");
|
|
1922
1878
|
}
|
|
1923
1879
|
if (window.__wgWirePanelChrome) {
|
|
1924
1880
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
@@ -1926,6 +1882,15 @@ async function renderStepError(page, info) {
|
|
|
1926
1882
|
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1927
1883
|
});
|
|
1928
1884
|
}
|
|
1885
|
+
if (window.__wgStampModal) {
|
|
1886
|
+
window.__wgStampModal(panel, "panel", {
|
|
1887
|
+
phase: "error",
|
|
1888
|
+
step: info.index + 1,
|
|
1889
|
+
total: info.total,
|
|
1890
|
+
block: info.blockName || "",
|
|
1891
|
+
ready: true,
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1929
1894
|
const runBtn = document.getElementById("wg-run");
|
|
1930
1895
|
if (runBtn) runBtn.addEventListener("click", () => window.__wgNext({}));
|
|
1931
1896
|
const retryBtn = document.getElementById("wg-error-retry");
|
|
@@ -2041,11 +2006,24 @@ async function markStepRunning(page, _opts) {
|
|
|
2041
2006
|
const panel = document.getElementById("wg-panel");
|
|
2042
2007
|
if (panel) {
|
|
2043
2008
|
panel.classList.add("wg-collapsed");
|
|
2009
|
+
panel.setAttribute("data-wg-collapsed", "1");
|
|
2010
|
+
panel.setAttribute("data-wg-phase", "running");
|
|
2011
|
+
if (typeof window.__wgStampModal === "function") {
|
|
2012
|
+
window.__wgStampModal(panel, "panel", {
|
|
2013
|
+
phase: "running",
|
|
2014
|
+
ready: true,
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2044
2017
|
const toggle = panel.querySelector("[data-wg-toggle]");
|
|
2045
2018
|
if (toggle) toggle.textContent = "Show";
|
|
2046
2019
|
const titleEl = panel.querySelector(".wg-chrome-title");
|
|
2047
2020
|
const label = panel.dataset.wgStepLabel;
|
|
2048
2021
|
if (titleEl && label) titleEl.textContent = label;
|
|
2022
|
+
const miniNext = panel.querySelector("[data-wg-mini-next]");
|
|
2023
|
+
if (miniNext) {
|
|
2024
|
+
miniNext.disabled = true;
|
|
2025
|
+
miniNext.classList.remove("wg-mini-next-show");
|
|
2026
|
+
}
|
|
2049
2027
|
}
|
|
2050
2028
|
})
|
|
2051
2029
|
.catch(() => {});
|
|
@@ -2054,16 +2032,14 @@ async function markStepRunning(page, _opts) {
|
|
|
2054
2032
|
async function showRing(page, box, label, tone, style) {
|
|
2055
2033
|
const size = normalizeHighlightSize(style && style.size);
|
|
2056
2034
|
const weight = normalizeHighlightWeight(style && style.weight);
|
|
2057
|
-
const focus = !!(style && style.focus);
|
|
2058
|
-
const color = (style && (style.color || style.labelColor || style.textColor)) || "";
|
|
2059
2035
|
await page
|
|
2060
2036
|
.evaluate(
|
|
2061
|
-
({ box, label, tone, size, weight
|
|
2037
|
+
({ box, label, tone, size, weight }) => {
|
|
2062
2038
|
if (window.__wgPositionRing) {
|
|
2063
|
-
window.__wgPositionRing(box, label, tone || "planned", { size, weight
|
|
2039
|
+
window.__wgPositionRing(box, label, tone || "planned", { size, weight });
|
|
2064
2040
|
}
|
|
2065
2041
|
},
|
|
2066
|
-
{ box, label, tone: tone || "planned", size, weight
|
|
2042
|
+
{ box, label, tone: tone || "planned", size, weight },
|
|
2067
2043
|
)
|
|
2068
2044
|
.catch(() => {});
|
|
2069
2045
|
}
|
|
@@ -2153,10 +2129,7 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2153
2129
|
if (s.selector) {
|
|
2154
2130
|
try {
|
|
2155
2131
|
await ensureSelectorInView(page, s.selector);
|
|
2156
|
-
|
|
2157
|
-
const zb = await page.locator(s.selector).first().boundingBox().catch(() => null);
|
|
2158
|
-
await applyCameraZoom(page, zb, s.zoom);
|
|
2159
|
-
}
|
|
2132
|
+
await applyHighlightZoom(page, s.selector, s.zoom);
|
|
2160
2133
|
const box = await page.locator(s.selector).first().boundingBox();
|
|
2161
2134
|
if (box) await showRing(page, box, caption, slideTone, slideStyle);
|
|
2162
2135
|
} catch {
|
|
@@ -2174,31 +2147,31 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2174
2147
|
panel.id = "wg-panel";
|
|
2175
2148
|
}
|
|
2176
2149
|
panel.classList.remove("wg-error", "wg-expected");
|
|
2177
|
-
// Do not clear wg-collapsed
|
|
2150
|
+
// Do not clear wg-collapsed - WirePanelChrome honors Hide (localStorage)
|
|
2178
2151
|
// unless forceCollapsed:true (--mini / --video).
|
|
2179
2152
|
const esc = (t) =>
|
|
2180
2153
|
String(t).replace(/&/g, "&").replace(/</g, "<").replace(/"/g, """);
|
|
2181
2154
|
const episodeLine =
|
|
2182
2155
|
info.episodeNumber !== undefined && info.episodeNumber !== null
|
|
2183
|
-
? "<div class
|
|
2156
|
+
? "<div class=\\"wg-episode\\">Episode " +
|
|
2184
2157
|
info.episodeNumber +
|
|
2185
2158
|
(info.episodeTitle ? ": " + esc(info.episodeTitle) : "") +
|
|
2186
2159
|
" · yap slides</div>"
|
|
2187
2160
|
: "";
|
|
2188
2161
|
const paceHtml =
|
|
2189
2162
|
info.paceBadge || info.paceLabel
|
|
2190
|
-
? "<div class
|
|
2163
|
+
? "<div class=\\"wg-pace\\" data-pace-kind=\\"" +
|
|
2191
2164
|
esc(info.paceKind || "normal") +
|
|
2192
|
-
"
|
|
2165
|
+
"\\"><span class=\\"wg-pace-badge\\">" +
|
|
2193
2166
|
esc(info.paceBadge || "1x") +
|
|
2194
2167
|
"</span><span>" +
|
|
2195
2168
|
esc(info.paceLabel || "") +
|
|
2196
2169
|
"</span></div>"
|
|
2197
2170
|
: "";
|
|
2198
2171
|
panel.innerHTML =
|
|
2199
|
-
"<div class
|
|
2200
|
-
"<button type
|
|
2201
|
-
"<div class
|
|
2172
|
+
"<div class=\\"wg-chrome\\"><span class=\\"wg-chrome-title\\">waygraph demo</span>" +
|
|
2173
|
+
"<button type=\\"button\\" class=\\"wg-hide-btn\\" data-wg-toggle=\\"1\\">Hide</button></div>" +
|
|
2174
|
+
"<div class=\\"wg-body\\">" +
|
|
2202
2175
|
episodeLine +
|
|
2203
2176
|
paceHtml +
|
|
2204
2177
|
"<h3>Slide " +
|
|
@@ -2208,35 +2181,35 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2208
2181
|
(info.blockName ? " · " + esc(info.blockName) : "") +
|
|
2209
2182
|
"</h3>" +
|
|
2210
2183
|
(info.tag
|
|
2211
|
-
? "<div style
|
|
2184
|
+
? "<div style=\\"border:none;padding:0;margin:0 0 6px;font-size:11px;color:#c9a6ff\\">" +
|
|
2212
2185
|
esc(info.tag) +
|
|
2213
2186
|
"</div>"
|
|
2214
2187
|
: "") +
|
|
2215
|
-
"<div class
|
|
2188
|
+
"<div class=\\"wg-narration\\">" +
|
|
2216
2189
|
esc(info.caption) +
|
|
2217
2190
|
"</div>" +
|
|
2218
2191
|
(info.detail
|
|
2219
|
-
? "<div class
|
|
2192
|
+
? "<div class=\\"wg-result-pretty\\" style=\\"margin:0 0 12px;color:#f0e8ff\\">" +
|
|
2220
2193
|
esc(info.detail) +
|
|
2221
2194
|
"</div>"
|
|
2222
2195
|
: "") +
|
|
2223
2196
|
(info.dwellMs != null
|
|
2224
|
-
? "<div class
|
|
2197
|
+
? "<div class=\\"wg-auto\\" id=\\"wg-dwell-hint\\" style=\\"margin:0 0 8px\\">Hold " +
|
|
2225
2198
|
(info.dwellMs / 1000).toFixed(1) +
|
|
2226
2199
|
"s before next...</div>"
|
|
2227
2200
|
: "") +
|
|
2228
|
-
"<div class
|
|
2201
|
+
"<div class=\\"wg-autoplay-row\\"><label><input type=\\"checkbox\\" id=\\"wg-autoplay-cb\\"" +
|
|
2229
2202
|
(info.autoNow ? " checked" : "") +
|
|
2230
2203
|
"> Auto-advance</label></div>" +
|
|
2231
|
-
"<div id
|
|
2232
|
-
(info.autoNow ? " style
|
|
2233
|
-
"><button type
|
|
2204
|
+
"<div id=\\"wg-gate-manual\\"" +
|
|
2205
|
+
(info.autoNow ? " style=\\"display:none\\"" : "") +
|
|
2206
|
+
"><button type=\\"button\\" id=\\"wg-run\\"" +
|
|
2234
2207
|
(info.dwellMs != null ? " disabled" : "") +
|
|
2235
2208
|
">" +
|
|
2236
2209
|
(info.isLast ? "Continue \\u25B6" : "Next slide \\u25B6") +
|
|
2237
2210
|
"</button></div>" +
|
|
2238
|
-
"<div id
|
|
2239
|
-
(info.autoNow ? "" : " style
|
|
2211
|
+
"<div id=\\"wg-gate-auto\\" class=\\"wg-auto\\"" +
|
|
2212
|
+
(info.autoNow ? "" : " style=\\"display:none\\"") +
|
|
2240
2213
|
">Auto-advancing...</div>" +
|
|
2241
2214
|
"</div>";
|
|
2242
2215
|
if (isNew) {
|
|
@@ -2254,10 +2227,19 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2254
2227
|
" / " +
|
|
2255
2228
|
info.total +
|
|
2256
2229
|
(info.blockName ? " \\u00b7 " + info.blockName : ""),
|
|
2257
|
-
// Video / --mini
|
|
2230
|
+
// Video / --mini: compact pill. Else omit so Hide sticks.
|
|
2258
2231
|
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
2259
2232
|
});
|
|
2260
2233
|
}
|
|
2234
|
+
if (window.__wgStampModal) {
|
|
2235
|
+
window.__wgStampModal(panel, "panel", {
|
|
2236
|
+
phase: "slide",
|
|
2237
|
+
step: info.index + 1,
|
|
2238
|
+
total: info.total,
|
|
2239
|
+
block: info.blockName || "",
|
|
2240
|
+
ready: true,
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2261
2243
|
let autoNow = false;
|
|
2262
2244
|
try {
|
|
2263
2245
|
autoNow = localStorage.getItem("wg-autoplay") === "1";
|
|
@@ -2408,8 +2390,7 @@ async function teardownOverlay(page) {
|
|
|
2408
2390
|
window.__wgRingTrack = null;
|
|
2409
2391
|
}
|
|
2410
2392
|
window.__wgNarrateOwnsRing = false;
|
|
2411
|
-
|
|
2412
|
-
for (const id of ["wg-panel", "wg-ring", "wg-ring-label", "wg-cursor", "wg-click-pulse", "wg-banner", "wg-todo-modal", "wg-focus-veil"]) {
|
|
2393
|
+
for (const id of ["wg-panel", "wg-ring", "wg-ring-label", "wg-cursor", "wg-click-pulse", "wg-banner"]) {
|
|
2413
2394
|
const el = document.getElementById(id);
|
|
2414
2395
|
if (el) el.remove();
|
|
2415
2396
|
}
|
|
@@ -2417,53 +2398,24 @@ async function teardownOverlay(page) {
|
|
|
2417
2398
|
.catch(() => {});
|
|
2418
2399
|
}
|
|
2419
2400
|
|
|
2420
|
-
async function moveCursorTo(page, box, ms
|
|
2401
|
+
async function moveCursorTo(page, box, ms) {
|
|
2421
2402
|
const x = box.x + box.width / 2;
|
|
2422
2403
|
const y = box.y + box.height / 2;
|
|
2423
2404
|
const travel = Math.max(0, Number(ms) || 0);
|
|
2424
|
-
const keepLock = !!(opts && opts.keepLock);
|
|
2425
|
-
// Camera stays locked; demo cursor + Playwright real mouse move together.
|
|
2426
|
-
// Host OS mouse is ignored (no mousemove camera steer).
|
|
2427
2405
|
await page
|
|
2428
2406
|
.evaluate(({ x, y, ms }) => {
|
|
2429
|
-
window.__wgCameraLock = true;
|
|
2430
|
-
window.__wgIgnoreHostMouse = true;
|
|
2431
2407
|
if (window.__wgMoveCursorTo) window.__wgMoveCursorTo(x, y, ms);
|
|
2432
2408
|
}, { x, y, ms: travel })
|
|
2433
2409
|
.catch(() => {});
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
await page.mouse.move(x, y, { steps });
|
|
2438
|
-
} else {
|
|
2439
|
-
await page.mouse.move(x, y);
|
|
2440
|
-
}
|
|
2441
|
-
} catch {
|
|
2442
|
-
/* headed / detached - demo cursor still moved */
|
|
2443
|
-
}
|
|
2410
|
+
// CSS transition is async - wait the full travel so the cursor is ON the
|
|
2411
|
+
// target before ring/pulse/real click (NavBlock click nav was skipping
|
|
2412
|
+
// this and looked like a teleport).
|
|
2444
2413
|
if (travel > 0) {
|
|
2445
|
-
await new Promise((res) => setTimeout(res,
|
|
2446
|
-
}
|
|
2447
|
-
if (!keepLock) {
|
|
2448
|
-
await page
|
|
2449
|
-
.evaluate(() => {
|
|
2450
|
-
window.__wgCameraLock = false;
|
|
2451
|
-
})
|
|
2452
|
-
.catch(() => {});
|
|
2414
|
+
await new Promise((res) => setTimeout(res, travel));
|
|
2453
2415
|
}
|
|
2454
2416
|
return { x, y };
|
|
2455
2417
|
}
|
|
2456
2418
|
|
|
2457
|
-
/** Live viewport center of a locator (after camera transform) - no scroll. */
|
|
2458
|
-
async function liveCenterOf(locator) {
|
|
2459
|
-
return locator
|
|
2460
|
-
.evaluate((el) => {
|
|
2461
|
-
const r = el.getBoundingClientRect();
|
|
2462
|
-
return { x: r.left + r.width / 2, y: r.top + r.height / 2, w: r.width, h: r.height };
|
|
2463
|
-
})
|
|
2464
|
-
.catch(() => null);
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
2419
|
async function clickPulseAt(page, x, y, tone) {
|
|
2468
2420
|
await page
|
|
2469
2421
|
.evaluate(({ x, y, tone }) => {
|
|
@@ -2588,47 +2540,17 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2588
2540
|
proto.click = async function (options) {
|
|
2589
2541
|
let clickPoint = null;
|
|
2590
2542
|
let clickTone = "auto";
|
|
2591
|
-
let keptCameraLock = false;
|
|
2592
2543
|
try {
|
|
2593
2544
|
await installOverlay(page);
|
|
2545
|
+
// Wait until the target is visible BEFORE measuring - otherwise
|
|
2546
|
+
// defineNavBlock({ click }) (and any slow-to-appear control) skips
|
|
2547
|
+
// the cursor entirely: boundingBox was null, then originalClick
|
|
2548
|
+
// waited and clicked with no demo animation.
|
|
2594
2549
|
await this.waitFor({
|
|
2595
2550
|
state: "visible",
|
|
2596
2551
|
timeout: (options && options.timeout) || 30000,
|
|
2597
2552
|
}).catch(() => {});
|
|
2598
|
-
const
|
|
2599
|
-
.evaluate(() => !!(window.__wgCamera && window.__wgCamera.scale > 1.001))
|
|
2600
|
-
.catch(() => false);
|
|
2601
|
-
if (zoomed) {
|
|
2602
|
-
// Re-auto-center on this target, then lock - no host mouse, no scroll.
|
|
2603
|
-
await page
|
|
2604
|
-
.evaluate(() => {
|
|
2605
|
-
window.__wgCameraLock = true;
|
|
2606
|
-
window.__wgIgnoreHostMouse = true;
|
|
2607
|
-
})
|
|
2608
|
-
.catch(() => {});
|
|
2609
|
-
keptCameraLock = true;
|
|
2610
|
-
const handle = await this.elementHandle().catch(() => null);
|
|
2611
|
-
if (handle) {
|
|
2612
|
-
await page
|
|
2613
|
-
.evaluate(
|
|
2614
|
-
(el) => {
|
|
2615
|
-
if (!el || !window.__wgSetCamera || !window.__wgElDocPoint) return;
|
|
2616
|
-
const cam = window.__wgCamera || { scale: 1.4 };
|
|
2617
|
-
const pt = window.__wgElDocPoint(el);
|
|
2618
|
-
const s = cam.scale > 1.001 ? cam.scale : 1.4;
|
|
2619
|
-
window.__wgSetCamera(pt.x, pt.y, s, 280);
|
|
2620
|
-
},
|
|
2621
|
-
handle,
|
|
2622
|
-
)
|
|
2623
|
-
.catch(() => {});
|
|
2624
|
-
await handle.dispose().catch(() => {});
|
|
2625
|
-
await new Promise((res) => setTimeout(res, 300));
|
|
2626
|
-
}
|
|
2627
|
-
}
|
|
2628
|
-
const live = await liveCenterOf(this);
|
|
2629
|
-
const box = live
|
|
2630
|
-
? { x: live.x - live.w / 2, y: live.y - live.h / 2, width: live.w, height: live.h }
|
|
2631
|
-
: await this.boundingBox();
|
|
2553
|
+
const box = await this.boundingBox();
|
|
2632
2554
|
const narrated = box ? await wasJustNarrated(page) : false;
|
|
2633
2555
|
if (box && !narrated) {
|
|
2634
2556
|
let fallback = "click";
|
|
@@ -2636,8 +2558,10 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2636
2558
|
const text = (await this.textContent())?.trim();
|
|
2637
2559
|
if (text && text.length > 0 && text.length <= 30) fallback = text;
|
|
2638
2560
|
} catch {
|
|
2639
|
-
|
|
2561
|
+
// element has no simple text (an icon button, say) - generic label is fine
|
|
2640
2562
|
}
|
|
2563
|
+
// Prefer "nav: …" when this locator is a NavBlock click target
|
|
2564
|
+
// (set on page by runStepMode just before act).
|
|
2641
2565
|
try {
|
|
2642
2566
|
const navLabel = await page.evaluate(() => window.__wgPendingNavClickLabel || null);
|
|
2643
2567
|
if (navLabel) fallback = String(navLabel);
|
|
@@ -2647,80 +2571,39 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2647
2571
|
const stubs = (stubBeforeRef && stubBeforeRef.current) || [];
|
|
2648
2572
|
const cap = await captionForLocator(page, this, stubs, fallback);
|
|
2649
2573
|
clickTone = cap.tone;
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
const aim = again
|
|
2653
|
-
? { x: again.x - again.w / 2, y: again.y - again.h / 2, width: again.w, height: again.h }
|
|
2654
|
-
: box;
|
|
2655
|
-
clickPoint = await moveCursorTo(page, aim, travel, { keepLock: zoomed });
|
|
2656
|
-
const liveBox = aim;
|
|
2657
|
-
clickPoint = {
|
|
2658
|
-
x: liveBox.x + liveBox.width / 2,
|
|
2659
|
-
y: liveBox.y + liveBox.height / 2,
|
|
2660
|
-
};
|
|
2661
|
-
await showRing(page, liveBox, cap.label, cap.tone, {
|
|
2574
|
+
clickPoint = await moveCursorTo(page, box, cursorMs(600));
|
|
2575
|
+
await showRing(page, box, cap.label, cap.tone, {
|
|
2662
2576
|
size: cap.size,
|
|
2663
2577
|
weight: cap.weight,
|
|
2664
2578
|
});
|
|
2665
2579
|
await dwellMatchedStub(page, this, stubs, pacing);
|
|
2580
|
+
// "pop for a few seconds" - Dan's own phrase, matching the
|
|
2581
|
+
// zsign demo-engine's ring-before-click pattern in
|
|
2582
|
+
// services/help-center-clip-engine's video-pipeline.
|
|
2666
2583
|
await new Promise((res) => setTimeout(res, clickPrePop()));
|
|
2667
2584
|
} else if (box) {
|
|
2668
|
-
|
|
2669
|
-
|
|
2585
|
+
// Ring/caption already handled by narrate() - still move the
|
|
2586
|
+
// cursor + pulse the click point, just skip re-showing the ring.
|
|
2587
|
+
clickPoint = await moveCursorTo(page, box, cursorMs(200));
|
|
2670
2588
|
clickTone = "planned";
|
|
2671
2589
|
}
|
|
2672
2590
|
} catch {
|
|
2673
|
-
|
|
2591
|
+
// best-effort - the real click below still runs either way
|
|
2674
2592
|
}
|
|
2675
2593
|
if (clickPoint) {
|
|
2676
2594
|
await clickPulseAt(page, clickPoint.x, clickPoint.y, clickTone);
|
|
2595
|
+
// clickPulseAt only triggers the CSS animation class - it doesn't
|
|
2596
|
+
// wait for it. Without a pause here, the real click (and any
|
|
2597
|
+
// resulting navigation/DOM change) fires while the ripple is still
|
|
2598
|
+
// mid-animation, sometimes cutting it off before it's even visible.
|
|
2599
|
+
// Wait out the same .5s the "@keyframes wg-pulse" rule uses, so the
|
|
2600
|
+
// mock click visually completes before the real one fires.
|
|
2677
2601
|
await new Promise((res) => setTimeout(res, skipTheater() ? 0 : 500));
|
|
2678
2602
|
}
|
|
2679
|
-
const
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
let result;
|
|
2684
|
-
if (zoomedClick || keptCameraLock) {
|
|
2685
|
-
// Real Playwright mouse at live visual center - never scrollIntoView.
|
|
2686
|
-
const pt =
|
|
2687
|
-
clickPoint ||
|
|
2688
|
-
(await liveCenterOf(this)) ||
|
|
2689
|
-
null;
|
|
2690
|
-
if (pt && Number.isFinite(pt.x) && Number.isFinite(pt.y)) {
|
|
2691
|
-
await page.mouse.click(pt.x, pt.y);
|
|
2692
|
-
result = undefined;
|
|
2693
|
-
} else {
|
|
2694
|
-
const handle = await this.elementHandle().catch(() => null);
|
|
2695
|
-
if (handle) {
|
|
2696
|
-
await handle.evaluate((el) => {
|
|
2697
|
-
if (el && typeof el.click === "function") el.click();
|
|
2698
|
-
});
|
|
2699
|
-
await handle.dispose().catch(() => {});
|
|
2700
|
-
result = undefined;
|
|
2701
|
-
} else {
|
|
2702
|
-
result = await originalClick.call(this, {
|
|
2703
|
-
...(options || {}),
|
|
2704
|
-
force: true,
|
|
2705
|
-
timeout: (options && options.timeout) || 5000,
|
|
2706
|
-
});
|
|
2707
|
-
}
|
|
2708
|
-
}
|
|
2709
|
-
} else {
|
|
2710
|
-
result = await originalClick.call(this, options);
|
|
2711
|
-
}
|
|
2712
|
-
await new Promise((res) => setTimeout(res, clickPostPop()));
|
|
2713
|
-
await hideRing(page);
|
|
2714
|
-
return result;
|
|
2715
|
-
} finally {
|
|
2716
|
-
if (keptCameraLock) {
|
|
2717
|
-
await page
|
|
2718
|
-
.evaluate(() => {
|
|
2719
|
-
window.__wgCameraLock = false;
|
|
2720
|
-
})
|
|
2721
|
-
.catch(() => {});
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2603
|
+
const result = await originalClick.call(this, options);
|
|
2604
|
+
await new Promise((res) => setTimeout(res, clickPostPop()));
|
|
2605
|
+
await hideRing(page);
|
|
2606
|
+
return result;
|
|
2724
2607
|
};
|
|
2725
2608
|
}
|
|
2726
2609
|
|
|
@@ -2830,7 +2713,10 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2830
2713
|
// installOverlay only seeds localStorage when the key is null (so mid-run
|
|
2831
2714
|
// checkbox clicks survive navigations). Without this, a prior --autoplay
|
|
2832
2715
|
// session sticks forever and agents cannot flip back with --no-autoplay.
|
|
2833
|
-
|
|
2716
|
+
// Must run on the *demo origin* (after goto), not about:blank - localStorage
|
|
2717
|
+
// is origin-scoped.
|
|
2718
|
+
const syncAutoplayFromEnv = async () => {
|
|
2719
|
+
if (process.env.WAYGRAPH_AUTOPLAY === undefined) return;
|
|
2834
2720
|
const on = process.env.WAYGRAPH_AUTOPLAY === "1";
|
|
2835
2721
|
await page
|
|
2836
2722
|
.evaluate((want) => {
|
|
@@ -2841,7 +2727,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2841
2727
|
}
|
|
2842
2728
|
}, on)
|
|
2843
2729
|
.catch(() => {});
|
|
2844
|
-
}
|
|
2730
|
+
};
|
|
2731
|
+
await syncAutoplayFromEnv();
|
|
2845
2732
|
// Some real Blocks (e.g. zsign-all's login.block.ts) call
|
|
2846
2733
|
// page.setViewportSize({ width: 1280, height: 720 }) inside their own
|
|
2847
2734
|
// act() - a hardcoded override for THEIR OWN testing consistency, with no
|
|
@@ -2995,6 +2882,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2995
2882
|
// still rendered on screen.
|
|
2996
2883
|
if ((clearSession || r.resetSession) && i > 0) {
|
|
2997
2884
|
await resetPageState(context, page, baseURL);
|
|
2885
|
+
// clear() wiped origin storage - re-apply CLI autoplay default.
|
|
2886
|
+
await syncAutoplayFromEnv();
|
|
2998
2887
|
}
|
|
2999
2888
|
// Episode pace: block > flow > CLI --fast (only when no authored pace).
|
|
3000
2889
|
// Authoring wins: withDemoPace("slow"|2|4500) keeps slow dwell even under --fast.
|
|
@@ -3034,9 +2923,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3034
2923
|
const stubBeforeTodos = stubBeforePhase.todos || [];
|
|
3035
2924
|
const isNavBlock = r.block.__waygraphKind === "nav";
|
|
3036
2925
|
const autoNow = await currentAutoplay();
|
|
3037
|
-
// Video / --mini: compact
|
|
3038
|
-
// Otherwise omit (undefined) so Hide/localStorage is honored - never pass false.
|
|
3039
|
-
// WAYGRAPH_STEPPER_MINI=1 remains a compat env alias for WAYGRAPH_MINI.
|
|
2926
|
+
// Video / --mini: compact pill. Never pass false - Hide/localStorage wins.
|
|
3040
2927
|
const forceCollapsed =
|
|
3041
2928
|
!!process.env.WAYGRAPH_VIDEO ||
|
|
3042
2929
|
process.env.WAYGRAPH_MINI === "1" ||
|
|
@@ -3086,7 +2973,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3086
2973
|
todos: stubBeforeTodos,
|
|
3087
2974
|
...paceSpeak,
|
|
3088
2975
|
});
|
|
3089
|
-
await setDemoTodos(page, stubBeforeTodos);
|
|
3090
2976
|
const edits = await gate();
|
|
3091
2977
|
for (const k of requires) {
|
|
3092
2978
|
if (edits[k.name] !== undefined) {
|
|
@@ -3098,40 +2984,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3098
2984
|
}
|
|
3099
2985
|
}
|
|
3100
2986
|
const recordingVideo = !!process.env.WAYGRAPH_VIDEO;
|
|
3101
|
-
// Sequential stubBefore queue (appear -> dwell -> fade) before act.
|
|
3102
|
-
if (stubBeforeRef.current && stubBeforeRef.current.length) {
|
|
3103
|
-
await setDemoBannerTitle(page, stubBeforePhase.title);
|
|
3104
|
-
const beforeRings = stubBeforeRef.current.map((h) => {
|
|
3105
|
-
const styled = applyHighlightStyleDefaults(h, flowStyle);
|
|
3106
|
-
return {
|
|
3107
|
-
selector: styled.selector,
|
|
3108
|
-
label: formatHighlightCaption(styled),
|
|
3109
|
-
duration: styled.duration,
|
|
3110
|
-
fastMode: styled.fastMode,
|
|
3111
|
-
tone: styled.tone,
|
|
3112
|
-
size: styled.size,
|
|
3113
|
-
weight: styled.weight,
|
|
3114
|
-
zoom: styled.zoom,
|
|
3115
|
-
focus: styled.focus,
|
|
3116
|
-
color: styled.color,
|
|
3117
|
-
cursor: styled.cursor,
|
|
3118
|
-
zoomOut: styled.zoomOut,
|
|
3119
|
-
};
|
|
3120
|
-
});
|
|
3121
|
-
await cycleHighlightRings(page, beforeRings, !!pacing.gatesFast, {
|
|
3122
|
-
pace: pacing.demoPace,
|
|
3123
|
-
todos: stubBeforeTodos,
|
|
3124
|
-
stepLabel: miniStepLabel({
|
|
3125
|
-
index: i,
|
|
3126
|
-
total: resolved.length,
|
|
3127
|
-
blockName: r.block.name,
|
|
3128
|
-
episodeNumber: r.episodeNumber,
|
|
3129
|
-
paceBadge: paceSpeak.paceBadge,
|
|
3130
|
-
}),
|
|
3131
|
-
});
|
|
3132
|
-
} else if (stubBeforePhase && stubBeforePhase.title) {
|
|
3133
|
-
await setDemoBannerTitle(page, stubBeforePhase.title);
|
|
3134
|
-
}
|
|
3135
2987
|
await markStepRunning(page, {});
|
|
3136
2988
|
// NavBlock click-nav: label the upcoming Locator.click demo cursor as
|
|
3137
2989
|
// "nav: <block>" so pia/demo watchers see cursor+pulse on click nav
|
|
@@ -3164,52 +3016,12 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3164
3016
|
delete window.__wgPendingNavClickLabel;
|
|
3165
3017
|
})
|
|
3166
3018
|
.catch(() => {});
|
|
3167
|
-
// stubOnError
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
if (window.__wgRingTrack) {
|
|
3174
|
-
window.removeEventListener("resize", window.__wgRingTrack);
|
|
3175
|
-
window.removeEventListener("scroll", window.__wgRingTrack, true);
|
|
3176
|
-
window.__wgRingTrack = null;
|
|
3177
|
-
}
|
|
3178
|
-
})
|
|
3179
|
-
.catch(() => {});
|
|
3180
|
-
const errPhase = await runStubPhase(r.block, "stubOnError", {
|
|
3181
|
-
fixtures: fixturesOnError,
|
|
3182
|
-
error: err,
|
|
3183
|
-
});
|
|
3184
|
-
await setDemoBannerTitle(page, errPhase.title);
|
|
3185
|
-
const errHighlights = errPhase.highlights.map((h) => {
|
|
3186
|
-
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
3187
|
-
return {
|
|
3188
|
-
selector: styled.selector,
|
|
3189
|
-
label: formatHighlightCaption(styled),
|
|
3190
|
-
duration: styled.duration,
|
|
3191
|
-
fastMode: styled.fastMode,
|
|
3192
|
-
tone: styled.tone,
|
|
3193
|
-
size: styled.size,
|
|
3194
|
-
weight: styled.weight,
|
|
3195
|
-
zoom: styled.zoom,
|
|
3196
|
-
focus: styled.focus,
|
|
3197
|
-
color: styled.color,
|
|
3198
|
-
cursor: styled.cursor,
|
|
3199
|
-
zoomOut: styled.zoomOut,
|
|
3200
|
-
};
|
|
3201
|
-
});
|
|
3202
|
-
await cycleHighlightRings(page, errHighlights, !!pacing.gatesFast, {
|
|
3203
|
-
defaultHoldMs: 2000,
|
|
3204
|
-
pace: pacing.demoPace,
|
|
3205
|
-
todos: errPhase.todos,
|
|
3206
|
-
});
|
|
3207
|
-
}
|
|
3208
|
-
// A thrown act()/observe()/a failed verify Trait used to just crash
|
|
3209
|
-
// the whole Node process with a raw stack trace - the browser closes
|
|
3210
|
-
// (main()'s own try/finally) before a human watching ever sees WHY.
|
|
3211
|
-
// Show it on-screen and let a real click acknowledge it first.
|
|
3212
|
-
await renderStepError(page, {
|
|
3019
|
+
// stubOnError + error/expected panel BEFORE acknowledging (PIA RFC).
|
|
3020
|
+
const errEdits = await presentFailPanel(page, {
|
|
3021
|
+
block: r.block,
|
|
3022
|
+
fixtures: r.highlightFixtures,
|
|
3023
|
+
highlightStyle: r.highlightStyle,
|
|
3024
|
+
error: err,
|
|
3213
3025
|
index: i,
|
|
3214
3026
|
total: resolved.length,
|
|
3215
3027
|
blockName: r.block.name,
|
|
@@ -3223,14 +3035,13 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3223
3035
|
episodeTitle: r.episodeTitle,
|
|
3224
3036
|
expectedFailureReason: r.expectedFailureReason,
|
|
3225
3037
|
stepperMode,
|
|
3226
|
-
|
|
3038
|
+
gatesFast: pacing.gatesFast,
|
|
3039
|
+
demoPace: pacing.demoPace,
|
|
3040
|
+
gate,
|
|
3227
3041
|
});
|
|
3228
|
-
const errEdits = await gate();
|
|
3229
3042
|
if (errEdits && errEdits.__wgRetry) {
|
|
3230
|
-
// Same clean slate a normal episode boundary gets - not just
|
|
3231
|
-
// re-running the failed block against whatever broken/half-
|
|
3232
|
-
// navigated state it left the page in.
|
|
3233
3043
|
await resetPageState(context, page, baseURL);
|
|
3044
|
+
await syncAutoplayFromEnv();
|
|
3234
3045
|
i = episodeStartIndex - 1;
|
|
3235
3046
|
continue;
|
|
3236
3047
|
}
|
|
@@ -3252,6 +3063,50 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3252
3063
|
})
|
|
3253
3064
|
.catch(() => {});
|
|
3254
3065
|
result = stepOutcome.result;
|
|
3066
|
+
// withExpectedFailure last block that SUCCEEDS on the intentional fail
|
|
3067
|
+
// branch (e.g. submit-login -> LoginPage + error banner). Still show
|
|
3068
|
+
// stubOnError rings + amber "expected outcome" panel - branching no
|
|
3069
|
+
// longer throws, but the demo must not skip the educational overlay.
|
|
3070
|
+
if (r.expectedFailureReason) {
|
|
3071
|
+
const errEdits = await presentFailPanel(page, {
|
|
3072
|
+
block: r.block,
|
|
3073
|
+
fixtures: r.highlightFixtures,
|
|
3074
|
+
highlightStyle: r.highlightStyle,
|
|
3075
|
+
error: undefined,
|
|
3076
|
+
out: result,
|
|
3077
|
+
index: i,
|
|
3078
|
+
total: resolved.length,
|
|
3079
|
+
blockName: r.block.name,
|
|
3080
|
+
message:
|
|
3081
|
+
"Resolved " +
|
|
3082
|
+
(result && result.__state ? result.__state : JSON.stringify(result)) +
|
|
3083
|
+
" (expected failure path)",
|
|
3084
|
+
allNames: moduleNames,
|
|
3085
|
+
allDescriptions: moduleDescriptions,
|
|
3086
|
+
moduleIndex,
|
|
3087
|
+
allEpisodes,
|
|
3088
|
+
title,
|
|
3089
|
+
episodeNumber: r.episodeNumber,
|
|
3090
|
+
episodeTitle: r.episodeTitle,
|
|
3091
|
+
expectedFailureReason: r.expectedFailureReason,
|
|
3092
|
+
stepperMode,
|
|
3093
|
+
gatesFast: pacing.gatesFast,
|
|
3094
|
+
demoPace: pacing.demoPace,
|
|
3095
|
+
gate,
|
|
3096
|
+
});
|
|
3097
|
+
if (errEdits && errEdits.__wgRetry) {
|
|
3098
|
+
await resetPageState(context, page, baseURL);
|
|
3099
|
+
await syncAutoplayFromEnv();
|
|
3100
|
+
i = episodeStartIndex - 1;
|
|
3101
|
+
continue;
|
|
3102
|
+
}
|
|
3103
|
+
if (process.env.WAYGRAPH_JSON !== "1") {
|
|
3104
|
+
console.log(
|
|
3105
|
+
"waygraph: expected failure on " + r.block.name + " - " + r.expectedFailureReason,
|
|
3106
|
+
);
|
|
3107
|
+
}
|
|
3108
|
+
break;
|
|
3109
|
+
}
|
|
3255
3110
|
const fixturesAfter = r.highlightFixtures;
|
|
3256
3111
|
const slides = resolveSlides(r.block, { out: result, fixtures: fixturesAfter }).map((s) =>
|
|
3257
3112
|
applyHighlightStyleDefaults(s, r.highlightStyle),
|
|
@@ -3275,8 +3130,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3275
3130
|
fixtures: fixturesAfter,
|
|
3276
3131
|
});
|
|
3277
3132
|
stubAfterTodos = afterPhase.todos || [];
|
|
3278
|
-
await setDemoBannerTitle(page, afterPhase.title);
|
|
3279
|
-
await setDemoTodos(page, afterPhase.todos || []);
|
|
3280
3133
|
highlights = afterPhase.highlights.map((h) => {
|
|
3281
3134
|
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
3282
3135
|
return {
|
|
@@ -3288,10 +3141,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3288
3141
|
size: styled.size,
|
|
3289
3142
|
weight: styled.weight,
|
|
3290
3143
|
zoom: styled.zoom,
|
|
3291
|
-
focus: styled.focus,
|
|
3292
|
-
color: styled.color,
|
|
3293
|
-
cursor: styled.cursor,
|
|
3294
|
-
zoomOut: styled.zoomOut,
|
|
3295
3144
|
};
|
|
3296
3145
|
});
|
|
3297
3146
|
} else {
|
|
@@ -4674,7 +4523,7 @@ Primary (less is more):
|
|
|
4674
4523
|
--auto-next Auto-advance steps (alias: --autoplay)
|
|
4675
4524
|
--fast Shorter auto-next / Next gates (keeps smooth cursor)
|
|
4676
4525
|
--full Classic wrap-all block chips (default: carousel)
|
|
4677
|
-
--mini Force compact mini panel (Hide pill); alias --stepper-mini
|
|
4526
|
+
--mini Force compact mini panel (Hide pill + Next); alias --stepper-mini
|
|
4678
4527
|
--ff-expand Expand fastForwardComposeBlock inners as separate steps
|
|
4679
4528
|
--ff-disabled Dispute: expand FF (alias --no-ff); blitz kept on those inners
|
|
4680
4529
|
--auto-play-video Unattended + recorded: --auto-next + --video (+ step); headless by default
|