waygraph 0.12.41 → 0.12.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +363 -790
- package/package.json +1 -1
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) => {
|
|
@@ -1047,33 +859,82 @@ async function installOverlay(page, title) {
|
|
|
1047
859
|
}
|
|
1048
860
|
if (iconLink.href !== favicon) iconLink.href = favicon;
|
|
1049
861
|
// 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.
|
|
862
|
+
// Collapsed = "N / M · block" + Next (when not auto). Never pass
|
|
863
|
+
// forceCollapsed:false - omit so Hide/localStorage wins.
|
|
1053
864
|
window.__wgWirePanelChrome = (panel, storageKey, chromeTitle, opts) => {
|
|
1054
865
|
if (!panel) return;
|
|
1055
866
|
opts = opts || {};
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
867
|
+
const ensureChrome = () => {
|
|
868
|
+
let chrome = panel.querySelector(":scope > .wg-chrome");
|
|
869
|
+
if (!chrome) {
|
|
870
|
+
const body = document.createElement("div");
|
|
871
|
+
body.className = "wg-body";
|
|
872
|
+
while (panel.firstChild) body.appendChild(panel.firstChild);
|
|
873
|
+
chrome = document.createElement("div");
|
|
874
|
+
chrome.className = "wg-chrome";
|
|
875
|
+
const titleEl = document.createElement("span");
|
|
876
|
+
titleEl.className = "wg-chrome-title";
|
|
877
|
+
titleEl.textContent = chromeTitle || "waygraph demo";
|
|
878
|
+
const actions = document.createElement("div");
|
|
879
|
+
actions.className = "wg-chrome-actions";
|
|
880
|
+
const nextBtn = document.createElement("button");
|
|
881
|
+
nextBtn.type = "button";
|
|
882
|
+
nextBtn.className = "wg-mini-next";
|
|
883
|
+
nextBtn.setAttribute("data-wg-mini-next", "1");
|
|
884
|
+
nextBtn.textContent = "Next \u25B6";
|
|
885
|
+
const btn = document.createElement("button");
|
|
886
|
+
btn.type = "button";
|
|
887
|
+
btn.className = "wg-hide-btn";
|
|
888
|
+
btn.setAttribute("data-wg-toggle", "1");
|
|
889
|
+
btn.textContent = "Hide";
|
|
890
|
+
actions.appendChild(nextBtn);
|
|
891
|
+
actions.appendChild(btn);
|
|
892
|
+
chrome.appendChild(titleEl);
|
|
893
|
+
chrome.appendChild(actions);
|
|
894
|
+
panel.appendChild(chrome);
|
|
895
|
+
panel.appendChild(body);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
// Yap slides ship a bare chrome - ensure actions + mini Next exist.
|
|
899
|
+
let actions = chrome.querySelector(".wg-chrome-actions");
|
|
900
|
+
if (!actions) {
|
|
901
|
+
actions = document.createElement("div");
|
|
902
|
+
actions.className = "wg-chrome-actions";
|
|
903
|
+
const hide = chrome.querySelector("[data-wg-toggle]");
|
|
904
|
+
if (hide) actions.appendChild(hide);
|
|
905
|
+
chrome.appendChild(actions);
|
|
906
|
+
}
|
|
907
|
+
if (!actions.querySelector("[data-wg-mini-next]")) {
|
|
908
|
+
const nextBtn = document.createElement("button");
|
|
909
|
+
nextBtn.type = "button";
|
|
910
|
+
nextBtn.className = "wg-mini-next";
|
|
911
|
+
nextBtn.setAttribute("data-wg-mini-next", "1");
|
|
912
|
+
nextBtn.textContent = "Next \u25B6";
|
|
913
|
+
actions.insertBefore(nextBtn, actions.firstChild);
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
ensureChrome();
|
|
1075
917
|
const stepLabel = opts.stepLabel || panel.dataset.wgStepLabel || "";
|
|
1076
918
|
if (stepLabel) panel.dataset.wgStepLabel = stepLabel;
|
|
919
|
+
const syncMiniNext = (hidden) => {
|
|
920
|
+
const miniNext = panel.querySelector("[data-wg-mini-next]");
|
|
921
|
+
if (!miniNext) return;
|
|
922
|
+
let auto = false;
|
|
923
|
+
try {
|
|
924
|
+
auto = localStorage.getItem("wg-autoplay") === "1";
|
|
925
|
+
} catch {
|
|
926
|
+
/* ignore */
|
|
927
|
+
}
|
|
928
|
+
const runBtn = document.getElementById("wg-run");
|
|
929
|
+
const show = !!hidden && !auto && !!runBtn;
|
|
930
|
+
miniNext.classList.toggle("wg-mini-next-show", show);
|
|
931
|
+
if (runBtn) {
|
|
932
|
+
miniNext.textContent = runBtn.textContent || "Next \u25B6";
|
|
933
|
+
miniNext.disabled = !!runBtn.disabled;
|
|
934
|
+
} else {
|
|
935
|
+
miniNext.disabled = true;
|
|
936
|
+
}
|
|
937
|
+
};
|
|
1077
938
|
const apply = (hidden, persist) => {
|
|
1078
939
|
panel.classList.toggle("wg-collapsed", hidden);
|
|
1079
940
|
const t = panel.querySelector("[data-wg-toggle]");
|
|
@@ -1085,6 +946,7 @@ async function installOverlay(page, title) {
|
|
|
1085
946
|
: (chromeTitle || "waygraph demo");
|
|
1086
947
|
}
|
|
1087
948
|
if (t) t.textContent = hidden ? "Show" : "Hide";
|
|
949
|
+
syncMiniNext(hidden);
|
|
1088
950
|
if (persist !== false) {
|
|
1089
951
|
try {
|
|
1090
952
|
localStorage.setItem(storageKey, hidden ? "1" : "0");
|
|
@@ -1113,6 +975,26 @@ async function installOverlay(page, title) {
|
|
|
1113
975
|
apply(!panel.classList.contains("wg-collapsed"), true);
|
|
1114
976
|
});
|
|
1115
977
|
}
|
|
978
|
+
const miniNext = panel.querySelector("[data-wg-mini-next]");
|
|
979
|
+
if (miniNext && !miniNext.dataset.wgWired) {
|
|
980
|
+
miniNext.dataset.wgWired = "1";
|
|
981
|
+
miniNext.addEventListener("click", (e) => {
|
|
982
|
+
e.stopPropagation();
|
|
983
|
+
const runBtn = document.getElementById("wg-run");
|
|
984
|
+
if (runBtn && !runBtn.disabled) {
|
|
985
|
+
runBtn.click();
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
if (typeof window.__wgNext === "function") window.__wgNext({});
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
const autoCb = document.getElementById("wg-autoplay-cb");
|
|
992
|
+
if (autoCb && !autoCb.dataset.wgMiniWired) {
|
|
993
|
+
autoCb.dataset.wgMiniWired = "1";
|
|
994
|
+
autoCb.addEventListener("change", () => {
|
|
995
|
+
syncMiniNext(panel.classList.contains("wg-collapsed"));
|
|
996
|
+
});
|
|
997
|
+
}
|
|
1116
998
|
const cur = panel.querySelector("#wg-modules .wg-mod-current");
|
|
1117
999
|
if (cur && typeof cur.scrollIntoView === "function") {
|
|
1118
1000
|
try {
|
|
@@ -1145,8 +1027,6 @@ async function renderBeforeStep(page, info) {
|
|
|
1145
1027
|
if (window.__wgHideCursor) window.__wgHideCursor();
|
|
1146
1028
|
})
|
|
1147
1029
|
.catch(() => {});
|
|
1148
|
-
// miniStepLabel is Node-only — compute before evaluate (browser has no closure).
|
|
1149
|
-
const payload = { ...info, stepLabel: miniStepLabel(info) };
|
|
1150
1030
|
await page
|
|
1151
1031
|
.evaluate((info) => {
|
|
1152
1032
|
// Reused in place, not removed + recreated, every step - the old
|
|
@@ -1169,7 +1049,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1169
1049
|
// active, the episode 2 tab is inactive"). Absent entirely for an ad
|
|
1170
1050
|
// hoc block chain with no named Flows.
|
|
1171
1051
|
const episodesHtml = info.allEpisodes && info.allEpisodes.length > 0
|
|
1172
|
-
? "<div id
|
|
1052
|
+
? "<div id=\\"wg-episodes\\">" +
|
|
1173
1053
|
info.allEpisodes
|
|
1174
1054
|
.map((e) => {
|
|
1175
1055
|
const cls = e.episodeNumber < info.episodeNumber ? "wg-ep-done"
|
|
@@ -1180,7 +1060,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1180
1060
|
const enteredCls = info.justEnteredEpisode && e.episodeNumber === info.episodeNumber
|
|
1181
1061
|
? " wg-ep-entered"
|
|
1182
1062
|
: "";
|
|
1183
|
-
return "<span class
|
|
1063
|
+
return "<span class=\\"wg-ep-tab " + cls + enteredCls + "\\">Episode " + e.episodeNumber + ": " +
|
|
1184
1064
|
esc(e.episodeTitle || "") + "</span>";
|
|
1185
1065
|
})
|
|
1186
1066
|
.join("") +
|
|
@@ -1196,66 +1076,40 @@ async function renderBeforeStep(page, info) {
|
|
|
1196
1076
|
.map((name, idx) => {
|
|
1197
1077
|
const cls = idx < info.moduleIndex ? "wg-mod-done" : idx === info.moduleIndex ? "wg-mod-current" : "wg-mod-upcoming";
|
|
1198
1078
|
const desc = info.allDescriptions && info.allDescriptions[idx];
|
|
1199
|
-
const titleAttr = desc ? " title
|
|
1200
|
-
return "<span class
|
|
1079
|
+
const titleAttr = desc ? " title=\\"" + esc(desc) + "\\"" : "";
|
|
1080
|
+
return "<span class=\\"wg-mod " + cls + "\\"" + titleAttr + ">" + name + "</span>";
|
|
1201
1081
|
})
|
|
1202
1082
|
.join("");
|
|
1203
1083
|
const narrationHtml = info.description
|
|
1204
|
-
? "<div class
|
|
1084
|
+
? "<div class=\\"wg-narration\\">" + esc(info.description) + "</div>"
|
|
1205
1085
|
: "";
|
|
1206
1086
|
const paceHtml =
|
|
1207
1087
|
info.paceBadge || info.paceLabel
|
|
1208
|
-
? "<div class
|
|
1088
|
+
? "<div class=\\"wg-pace\\" data-pace-kind=\\"" +
|
|
1209
1089
|
esc(info.paceKind || "normal") +
|
|
1210
|
-
"
|
|
1090
|
+
"\\"><span class=\\"wg-pace-badge\\">" +
|
|
1211
1091
|
esc(info.paceBadge || "1x") +
|
|
1212
1092
|
"</span><span>" +
|
|
1213
1093
|
esc(info.paceLabel || "") +
|
|
1214
1094
|
"</span></div>"
|
|
1215
1095
|
: "";
|
|
1216
1096
|
let html =
|
|
1217
|
-
"<div id
|
|
1097
|
+
"<div id=\\"wg-progress\\"><div id=\\"wg-progress-bar\\" style=\\"width:" + pct + "%\\"></div></div>" +
|
|
1218
1098
|
episodesHtml +
|
|
1219
1099
|
paceHtml +
|
|
1220
|
-
"<div id
|
|
1100
|
+
"<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
|
|
1221
1101
|
"<h3>Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + "</h3>" +
|
|
1222
1102
|
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
|
-
})());
|
|
1103
|
+
todosHtmlFromInfo(info, esc);
|
|
1250
1104
|
if (info.keys.length === 0) {
|
|
1251
|
-
html += "<div class
|
|
1105
|
+
html += "<div class=\\"wg-key\\">(no MemKeys required)</div>";
|
|
1252
1106
|
}
|
|
1253
1107
|
for (const k of info.keys) {
|
|
1254
1108
|
html +=
|
|
1255
|
-
"<div class
|
|
1256
|
-
"<textarea data-key
|
|
1109
|
+
"<div class=\\"wg-key\\"><label>" + k.name + "</label>" +
|
|
1110
|
+
"<textarea data-key=\\"" + k.name + "\\" rows=\\"2\\">" +
|
|
1257
1111
|
k.value.replace(/</g, "<") + "</textarea>" +
|
|
1258
|
-
"<div class
|
|
1112
|
+
"<div class=\\"wg-key-pretty\\" data-pretty-for=\\"" + esc(k.name) + "\\"></div>" +
|
|
1259
1113
|
"</div>";
|
|
1260
1114
|
}
|
|
1261
1115
|
let autoNow = false;
|
|
@@ -1265,12 +1119,12 @@ async function renderBeforeStep(page, info) {
|
|
|
1265
1119
|
/* private mode / blocked storage - defaults to manual */
|
|
1266
1120
|
}
|
|
1267
1121
|
html +=
|
|
1268
|
-
"<div class
|
|
1122
|
+
"<div class=\\"wg-autoplay-row\\"><label><input type=\\"checkbox\\" id=\\"wg-autoplay-cb\\"" +
|
|
1269
1123
|
(autoNow ? " checked" : "") +
|
|
1270
1124
|
"> Auto-advance</label></div>" +
|
|
1271
|
-
"<div id
|
|
1272
|
-
"><button id
|
|
1273
|
-
"<div id
|
|
1125
|
+
"<div id=\\"wg-gate-manual\\"" + (autoNow ? " style=\\"display:none\\"" : "") +
|
|
1126
|
+
"><button id=\\"wg-run\\">Run this step \\u25B6</button></div>" +
|
|
1127
|
+
"<div id=\\"wg-gate-auto\\" class=\\"wg-auto\\"" + (autoNow ? "" : " style=\\"display:none\\"") +
|
|
1274
1128
|
">Auto-advancing...</div>";
|
|
1275
1129
|
panel.innerHTML = html;
|
|
1276
1130
|
if (isNewPanel) {
|
|
@@ -1279,7 +1133,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1279
1133
|
}
|
|
1280
1134
|
if (window.__wgWirePanelChrome) {
|
|
1281
1135
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1282
|
-
stepLabel: info
|
|
1136
|
+
stepLabel: miniStepLabel(info),
|
|
1283
1137
|
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1284
1138
|
});
|
|
1285
1139
|
}
|
|
@@ -1303,7 +1157,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1303
1157
|
return Object.entries(parsed)
|
|
1304
1158
|
.map(([field, value]) => {
|
|
1305
1159
|
const label = field.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
|
|
1306
|
-
return "<div class
|
|
1160
|
+
return "<div class=\\"wg-key-pretty-row\\"><span class=\\"wg-key-pretty-label\\">" +
|
|
1307
1161
|
esc(label) + ":</span> " + esc(String(value)) + "</div>";
|
|
1308
1162
|
})
|
|
1309
1163
|
.join("");
|
|
@@ -1312,7 +1166,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1312
1166
|
const prettyOn = window.__wgPretty !== false;
|
|
1313
1167
|
panel.querySelectorAll("textarea[data-key]").forEach((ta) => {
|
|
1314
1168
|
const key = ta.getAttribute("data-key");
|
|
1315
|
-
const previewEl = panel.querySelector(".wg-key-pretty[data-pretty-for
|
|
1169
|
+
const previewEl = panel.querySelector(".wg-key-pretty[data-pretty-for=\\"" + key + "\\"]");
|
|
1316
1170
|
if (!previewEl) return;
|
|
1317
1171
|
previewEl.style.display = prettyOn ? "" : "none";
|
|
1318
1172
|
if (prettyOn) previewEl.innerHTML = prettyMemValue(ta.value);
|
|
@@ -1352,31 +1206,12 @@ async function renderBeforeStep(page, info) {
|
|
|
1352
1206
|
if (auto) auto.style.display = cb.checked ? "" : "none";
|
|
1353
1207
|
});
|
|
1354
1208
|
}
|
|
1355
|
-
},
|
|
1209
|
+
}, info)
|
|
1356
1210
|
.catch(() => {});
|
|
1357
1211
|
}
|
|
1358
1212
|
|
|
1359
1213
|
async function ensureSelectorInView(page, selector) {
|
|
1360
1214
|
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
1215
|
const loc = page.locator(selector).first();
|
|
1381
1216
|
await loc.scrollIntoViewIfNeeded().catch(() => {});
|
|
1382
1217
|
await page
|
|
@@ -1443,54 +1278,48 @@ async function ensureSelectorInView(page, selector) {
|
|
|
1443
1278
|
}
|
|
1444
1279
|
}
|
|
1445
1280
|
|
|
1446
|
-
async function
|
|
1447
|
-
const z =
|
|
1448
|
-
if (!
|
|
1281
|
+
async function applyHighlightZoom(page, selector, zoom) {
|
|
1282
|
+
const z = Number(zoom);
|
|
1283
|
+
if (!Number.isFinite(z) || z <= 1.001) {
|
|
1449
1284
|
await page.evaluate(() => {
|
|
1450
|
-
if (window.
|
|
1285
|
+
if (window.__wgClearZoom) window.__wgClearZoom();
|
|
1451
1286
|
}).catch(() => {});
|
|
1452
1287
|
return;
|
|
1453
1288
|
}
|
|
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
|
-
await page
|
|
1470
|
-
.evaluate((list) => {
|
|
1471
|
-
if (window.__wgSetTodos) window.__wgSetTodos(list || []);
|
|
1472
|
-
}, todos && todos.length ? todos : [])
|
|
1473
|
-
.catch(() => {});
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
async function resetCameraZoom(page) {
|
|
1477
1289
|
await page
|
|
1478
|
-
.evaluate(
|
|
1479
|
-
|
|
1480
|
-
|
|
1290
|
+
.evaluate(
|
|
1291
|
+
({ sel, scale }) => {
|
|
1292
|
+
if (window.__wgApplyZoom) window.__wgApplyZoom(sel, scale);
|
|
1293
|
+
},
|
|
1294
|
+
{ sel: selector, scale: z },
|
|
1295
|
+
)
|
|
1481
1296
|
.catch(() => {});
|
|
1482
1297
|
}
|
|
1483
1298
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1299
|
+
function todosHtmlFromInfo(info, escFn) {
|
|
1300
|
+
const list = info.todos || [];
|
|
1301
|
+
if (!list.length) return "";
|
|
1302
|
+
return (
|
|
1303
|
+
"<ul id=\\"wg-todos\\">" +
|
|
1304
|
+
list
|
|
1305
|
+
.map((t) => {
|
|
1306
|
+
const cls = t.current ? "wg-todo-current" : t.done ? "wg-todo-done" : "wg-todo-pending";
|
|
1307
|
+
const mark = t.done ? "\\u2713" : t.current ? "\\u2192" : "\\u25CB";
|
|
1308
|
+
return (
|
|
1309
|
+
"<li class=\\"" +
|
|
1310
|
+
cls +
|
|
1311
|
+
"\\"><span class=\\"wg-todo-mark\\">" +
|
|
1312
|
+
mark +
|
|
1313
|
+
"</span><span>" +
|
|
1314
|
+
escFn(t.text || "") +
|
|
1315
|
+
"</span></li>"
|
|
1316
|
+
);
|
|
1317
|
+
})
|
|
1318
|
+
.join("") +
|
|
1319
|
+
"</ul>"
|
|
1320
|
+
);
|
|
1491
1321
|
}
|
|
1492
1322
|
|
|
1493
|
-
/** Collapsed chrome title: Ep N / i / n / block / pace. */
|
|
1494
1323
|
function miniStepLabel(info) {
|
|
1495
1324
|
const ep =
|
|
1496
1325
|
info.episodeNumber !== undefined && info.episodeNumber !== null
|
|
@@ -1508,174 +1337,94 @@ function miniStepLabel(info) {
|
|
|
1508
1337
|
|
|
1509
1338
|
async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
1510
1339
|
const list = highlights || [];
|
|
1340
|
+
// defaultHoldMs: when set (fail path), use instead of legacy 900/200 so BUG
|
|
1341
|
+
// rings stay visible ~2s before the error panel (PIA stubOnError RFC).
|
|
1511
1342
|
const defaultHoldMs = opts && opts.defaultHoldMs != null ? opts.defaultHoldMs : null;
|
|
1512
1343
|
const pace = opts && opts.pace !== undefined ? opts.pace : undefined;
|
|
1513
|
-
const fadeMs = 320;
|
|
1514
|
-
const n = list.length;
|
|
1515
|
-
const baseStepLabel = (opts && opts.stepLabel) || "";
|
|
1516
|
-
|
|
1517
|
-
const updateHlProgress = async (i) => {
|
|
1518
|
-
if (n <= 0) return;
|
|
1519
|
-
const tag = "[" + (i + 1) + "/" + n + "]";
|
|
1520
|
-
await page
|
|
1521
|
-
.evaluate(
|
|
1522
|
-
({ tag, baseStepLabel, i, n }) => {
|
|
1523
|
-
const panel = document.getElementById("wg-panel");
|
|
1524
|
-
if (!panel) return;
|
|
1525
|
-
panel.dataset.wgHlIdx = String(i + 1);
|
|
1526
|
-
panel.dataset.wgHlN = String(n);
|
|
1527
|
-
const titleEl = panel.querySelector(".wg-chrome-title");
|
|
1528
|
-
if (titleEl) {
|
|
1529
|
-
const base = panel.dataset.wgStepLabel || baseStepLabel || "waygraph demo";
|
|
1530
|
-
if (panel.classList.contains("wg-collapsed")) {
|
|
1531
|
-
titleEl.textContent = base + " " + tag;
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
let span = document.getElementById("wg-hl-idx");
|
|
1535
|
-
const h3 = panel.querySelector("h3");
|
|
1536
|
-
if (h3) {
|
|
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 () => {
|
|
1554
|
-
await page
|
|
1555
|
-
.evaluate(() => {
|
|
1556
|
-
if (window.__wgStopHighlightTrack) window.__wgStopHighlightTrack();
|
|
1557
|
-
if (window.__wgRingRaf) {
|
|
1558
|
-
cancelAnimationFrame(window.__wgRingRaf);
|
|
1559
|
-
window.__wgRingRaf = null;
|
|
1560
|
-
}
|
|
1561
|
-
if (window.__wgRingTrack) {
|
|
1562
|
-
window.removeEventListener("mousemove", window.__wgRingTrack);
|
|
1563
|
-
window.removeEventListener("resize", window.__wgRingTrack);
|
|
1564
|
-
window.removeEventListener("scroll", window.__wgRingTrack, true);
|
|
1565
|
-
window.__wgRingTrack = null;
|
|
1566
|
-
}
|
|
1567
|
-
if (window.__wgHideFocusVeil) window.__wgHideFocusVeil();
|
|
1568
|
-
})
|
|
1569
|
-
.catch(() => {});
|
|
1570
|
-
};
|
|
1571
|
-
|
|
1572
1344
|
for (let i = 0; i < list.length; i++) {
|
|
1573
1345
|
const h = list[i];
|
|
1574
1346
|
try {
|
|
1575
|
-
await stopLiveFollow();
|
|
1576
1347
|
await ensureSelectorInView(page, h.selector);
|
|
1348
|
+
await applyHighlightZoom(page, h.selector, h.zoom);
|
|
1577
1349
|
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 });
|
|
1350
|
+
if (box) {
|
|
1351
|
+
await showRing(page, box, h.label, h.tone || "planned", {
|
|
1352
|
+
size: h.size,
|
|
1353
|
+
weight: h.weight,
|
|
1354
|
+
});
|
|
1355
|
+
if ((opts && opts.todos && opts.todos.length) || (h.todos && h.todos.length)) {
|
|
1356
|
+
const rows =
|
|
1357
|
+
opts && opts.todos && opts.todos.length
|
|
1358
|
+
? opts.todos
|
|
1359
|
+
: normalizeTodos(h.todos, h.todoIndex);
|
|
1360
|
+
await page
|
|
1361
|
+
.evaluate((todos) => {
|
|
1362
|
+
const ul = document.getElementById("wg-todos");
|
|
1363
|
+
if (!ul) return;
|
|
1364
|
+
ul.innerHTML = todos
|
|
1365
|
+
.map((t) => {
|
|
1366
|
+
const cls = t.current
|
|
1367
|
+
? "wg-todo-current"
|
|
1368
|
+
: t.done
|
|
1369
|
+
? "wg-todo-done"
|
|
1370
|
+
: "wg-todo-pending";
|
|
1371
|
+
const mark = t.done ? "\\u2713" : t.current ? "\\u2192" : "\\u25CB";
|
|
1372
|
+
const esc = (s) =>
|
|
1373
|
+
String(s)
|
|
1374
|
+
.replace(/&/g, "&")
|
|
1375
|
+
.replace(/</g, "<")
|
|
1376
|
+
.replace(/>/g, ">");
|
|
1377
|
+
return (
|
|
1378
|
+
"<li class=\\"" +
|
|
1379
|
+
cls +
|
|
1380
|
+
"\\"><span class=\\"wg-todo-mark\\">" +
|
|
1381
|
+
mark +
|
|
1382
|
+
"</span><span>" +
|
|
1383
|
+
esc(t.text || "") +
|
|
1384
|
+
"</span></li>"
|
|
1385
|
+
);
|
|
1386
|
+
})
|
|
1387
|
+
.join("");
|
|
1388
|
+
}, rows)
|
|
1389
|
+
.catch(() => {});
|
|
1640
1390
|
}
|
|
1391
|
+
const authored = resolveFixtureDwellMs(h, { gatesFast, pace });
|
|
1392
|
+
const legacyMs = i === list.length - 1 ? 200 : 900;
|
|
1393
|
+
const holdMs =
|
|
1394
|
+
authored != null ? authored : defaultHoldMs != null ? defaultHoldMs : legacyMs;
|
|
1395
|
+
await new Promise((res) => setTimeout(res, holdMs));
|
|
1641
1396
|
}
|
|
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
1397
|
} catch {
|
|
1656
|
-
|
|
1398
|
+
// best-effort - a selector that doesn't resolve just gets skipped
|
|
1657
1399
|
}
|
|
1658
1400
|
}
|
|
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).
|
|
1401
|
+
if (list.length > 0) {
|
|
1402
|
+
const last = list[list.length - 1];
|
|
1667
1403
|
await page
|
|
1668
|
-
.evaluate(() => {
|
|
1669
|
-
window.
|
|
1670
|
-
|
|
1404
|
+
.evaluate((h) => {
|
|
1405
|
+
if (!window.__wgPositionRing) return;
|
|
1406
|
+
const reposition = () => {
|
|
1407
|
+
const el = document.querySelector(h.selector);
|
|
1408
|
+
if (!el) {
|
|
1409
|
+
if (window.__wgHideRing) window.__wgHideRing();
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
window.__wgPositionRing(el.getBoundingClientRect(), h.label, h.tone || "planned", {
|
|
1413
|
+
size: h.size || "md",
|
|
1414
|
+
weight: h.weight || "normal",
|
|
1415
|
+
});
|
|
1416
|
+
};
|
|
1417
|
+
reposition();
|
|
1418
|
+
window.__wgRingTrack = reposition;
|
|
1419
|
+
window.addEventListener("resize", reposition);
|
|
1420
|
+
window.addEventListener("scroll", reposition, true);
|
|
1421
|
+
}, last)
|
|
1671
1422
|
.catch(() => {});
|
|
1672
|
-
}
|
|
1673
|
-
if (list.length === 0) {
|
|
1423
|
+
} else {
|
|
1674
1424
|
await hideRing(page);
|
|
1675
1425
|
}
|
|
1676
1426
|
}
|
|
1677
1427
|
|
|
1678
|
-
|
|
1679
1428
|
async function renderAfterStep(page, info) {
|
|
1680
1429
|
await installOverlay(page, info.title);
|
|
1681
1430
|
// Clear any tracker from a previous highlight before cycling through this
|
|
@@ -1698,9 +1447,7 @@ async function renderAfterStep(page, info) {
|
|
|
1698
1447
|
// 900ms / 200ms holds when set.
|
|
1699
1448
|
await cycleHighlightRings(page, highlights, gatesFast, {
|
|
1700
1449
|
pace: info.pace,
|
|
1701
|
-
stepLabel: miniStepLabel(info),
|
|
1702
1450
|
});
|
|
1703
|
-
const afterPayload = { ...info, stepLabel: miniStepLabel(info) };
|
|
1704
1451
|
await page
|
|
1705
1452
|
.evaluate((info) => {
|
|
1706
1453
|
// Reused in place - see renderBeforeStep's own comment on this.
|
|
@@ -1721,8 +1468,8 @@ async function renderAfterStep(page, info) {
|
|
|
1721
1468
|
.map((name, idx) => {
|
|
1722
1469
|
const cls = idx <= info.moduleIndex ? "wg-mod-done" : "wg-mod-upcoming";
|
|
1723
1470
|
const desc = info.allDescriptions && info.allDescriptions[idx];
|
|
1724
|
-
const titleAttr = desc ? " title
|
|
1725
|
-
return "<span class
|
|
1471
|
+
const titleAttr = desc ? " title=\\"" + escA(desc) + "\\"" : "";
|
|
1472
|
+
return "<span class=\\"wg-mod " + cls + "\\"" + titleAttr + ">" + name + "</span>";
|
|
1726
1473
|
})
|
|
1727
1474
|
.join("");
|
|
1728
1475
|
// QA-friendly by default ("LoginSuccess" -> "Login Success") - raw
|
|
@@ -1733,13 +1480,13 @@ async function renderAfterStep(page, info) {
|
|
|
1733
1480
|
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2") || info.resultTag;
|
|
1734
1481
|
const pretty = window.__wgPretty !== false;
|
|
1735
1482
|
const resultHtml =
|
|
1736
|
-
"<div class
|
|
1737
|
-
"<button class
|
|
1738
|
-
"<button class
|
|
1483
|
+
"<div class=\\"wg-toggle\\">" +
|
|
1484
|
+
"<button class=\\"wg-toggle-btn" + (pretty ? " wg-active" : "") + "\\" data-mode=\\"pretty\\">Pretty</button>" +
|
|
1485
|
+
"<button class=\\"wg-toggle-btn" + (pretty ? "" : " wg-active") + "\\" data-mode=\\"json\\">JSON</button>" +
|
|
1739
1486
|
"</div>" +
|
|
1740
|
-
"<div class
|
|
1487
|
+
"<div class=\\"wg-result wg-result-pretty\\" style=\\"display:" + (pretty ? "block" : "none") + "\\">" +
|
|
1741
1488
|
prettyText + "</div>" +
|
|
1742
|
-
"<div class
|
|
1489
|
+
"<div class=\\"wg-result wg-result-json\\" style=\\"display:" + (pretty ? "none" : "block") + "\\">" +
|
|
1743
1490
|
info.resultTag + "</div>";
|
|
1744
1491
|
let autoNow = false;
|
|
1745
1492
|
try {
|
|
@@ -1748,21 +1495,21 @@ async function renderAfterStep(page, info) {
|
|
|
1748
1495
|
/* private mode / blocked storage - defaults to manual */
|
|
1749
1496
|
}
|
|
1750
1497
|
const gateHtml =
|
|
1751
|
-
"<div class
|
|
1498
|
+
"<div class=\\"wg-autoplay-row\\"><label><input type=\\"checkbox\\" id=\\"wg-autoplay-cb\\"" +
|
|
1752
1499
|
(autoNow ? " checked" : "") +
|
|
1753
1500
|
"> Auto-advance</label></div>" +
|
|
1754
|
-
"<div id
|
|
1755
|
-
"><button id
|
|
1756
|
-
"<div id
|
|
1501
|
+
"<div id=\\"wg-gate-manual\\"" + (autoNow ? " style=\\"display:none\\"" : "") +
|
|
1502
|
+
"><button id=\\"wg-run\\">" + buttonLabel + "</button></div>" +
|
|
1503
|
+
"<div id=\\"wg-gate-auto\\" class=\\"wg-auto\\"" + (autoNow ? "" : " style=\\"display:none\\"") +
|
|
1757
1504
|
">Auto-advancing...</div>";
|
|
1758
1505
|
const episodesHtml = info.allEpisodes && info.allEpisodes.length > 0
|
|
1759
|
-
? "<div id
|
|
1506
|
+
? "<div id=\\"wg-episodes\\">" +
|
|
1760
1507
|
info.allEpisodes
|
|
1761
1508
|
.map((e) => {
|
|
1762
1509
|
const cls = e.episodeNumber < info.episodeNumber ? "wg-ep-done"
|
|
1763
1510
|
: e.episodeNumber === info.episodeNumber ? "wg-ep-current"
|
|
1764
1511
|
: "wg-ep-upcoming";
|
|
1765
|
-
return "<span class
|
|
1512
|
+
return "<span class=\\"wg-ep-tab " + cls + "\\">Episode " + e.episodeNumber + ": " +
|
|
1766
1513
|
escA(e.episodeTitle || "") + "</span>";
|
|
1767
1514
|
})
|
|
1768
1515
|
.join("") +
|
|
@@ -1770,47 +1517,21 @@ async function renderAfterStep(page, info) {
|
|
|
1770
1517
|
: "";
|
|
1771
1518
|
const paceHtml =
|
|
1772
1519
|
info.paceBadge || info.paceLabel
|
|
1773
|
-
? "<div class
|
|
1520
|
+
? "<div class=\\"wg-pace\\" data-pace-kind=\\"" +
|
|
1774
1521
|
escA(info.paceKind || "normal") +
|
|
1775
|
-
"
|
|
1522
|
+
"\\"><span class=\\"wg-pace-badge\\">" +
|
|
1776
1523
|
escA(info.paceBadge || "1x") +
|
|
1777
1524
|
"</span><span>" +
|
|
1778
1525
|
escA(info.paceLabel || "") +
|
|
1779
1526
|
"</span></div>"
|
|
1780
1527
|
: "";
|
|
1781
1528
|
panel.innerHTML =
|
|
1782
|
-
"<div id
|
|
1529
|
+
"<div id=\\"wg-progress\\"><div id=\\"wg-progress-bar\\" style=\\"width:" + pct + "%\\"></div></div>" +
|
|
1783
1530
|
episodesHtml +
|
|
1784
1531
|
paceHtml +
|
|
1785
|
-
"<div id
|
|
1532
|
+
"<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
|
|
1786
1533
|
"<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
|
-
})()) +
|
|
1534
|
+
todosHtmlFromInfo(info, escA) +
|
|
1814
1535
|
resultHtml +
|
|
1815
1536
|
gateHtml;
|
|
1816
1537
|
if (isNewPanel) {
|
|
@@ -1819,7 +1540,7 @@ async function renderAfterStep(page, info) {
|
|
|
1819
1540
|
}
|
|
1820
1541
|
if (window.__wgWirePanelChrome) {
|
|
1821
1542
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1822
|
-
stepLabel: info
|
|
1543
|
+
stepLabel: miniStepLabel(info),
|
|
1823
1544
|
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1824
1545
|
});
|
|
1825
1546
|
}
|
|
@@ -1849,7 +1570,7 @@ async function renderAfterStep(page, info) {
|
|
|
1849
1570
|
if (auto) auto.style.display = cb.checked ? "" : "none";
|
|
1850
1571
|
});
|
|
1851
1572
|
}
|
|
1852
|
-
},
|
|
1573
|
+
}, info)
|
|
1853
1574
|
.catch(() => {});
|
|
1854
1575
|
}
|
|
1855
1576
|
|
|
@@ -1861,7 +1582,6 @@ async function renderAfterStep(page, info) {
|
|
|
1861
1582
|
*/
|
|
1862
1583
|
async function renderStepError(page, info) {
|
|
1863
1584
|
await installOverlay(page, info.title);
|
|
1864
|
-
const errorPayload = { ...info, stepLabel: miniStepLabel(info) };
|
|
1865
1585
|
await page
|
|
1866
1586
|
.evaluate((info) => {
|
|
1867
1587
|
// Reused in place - see renderBeforeStep's own comment on this.
|
|
@@ -1878,13 +1598,13 @@ async function renderStepError(page, info) {
|
|
|
1878
1598
|
if (!isNewPanel) panel.classList.add("wg-in");
|
|
1879
1599
|
const esc = (s) => String(s).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
1880
1600
|
const episodesHtml = info.allEpisodes && info.allEpisodes.length > 0
|
|
1881
|
-
? "<div id
|
|
1601
|
+
? "<div id=\\"wg-episodes\\">" +
|
|
1882
1602
|
info.allEpisodes
|
|
1883
1603
|
.map((e) => {
|
|
1884
1604
|
const cls = e.episodeNumber < info.episodeNumber ? "wg-ep-done"
|
|
1885
1605
|
: e.episodeNumber === info.episodeNumber ? "wg-ep-current"
|
|
1886
1606
|
: "wg-ep-upcoming";
|
|
1887
|
-
return "<span class
|
|
1607
|
+
return "<span class=\\"wg-ep-tab " + cls + "\\">Episode " + e.episodeNumber + ": " +
|
|
1888
1608
|
esc(e.episodeTitle || "") + "</span>";
|
|
1889
1609
|
})
|
|
1890
1610
|
.join("") +
|
|
@@ -1895,8 +1615,8 @@ async function renderStepError(page, info) {
|
|
|
1895
1615
|
.map((name, idx) => {
|
|
1896
1616
|
const cls = idx === info.moduleIndex ? "wg-mod-current" : idx < info.moduleIndex ? "wg-mod-done" : "wg-mod-upcoming";
|
|
1897
1617
|
const desc = info.allDescriptions && info.allDescriptions[idx];
|
|
1898
|
-
const titleAttr = desc ? " title
|
|
1899
|
-
return "<span class
|
|
1618
|
+
const titleAttr = desc ? " title=\\"" + esc(desc) + "\\"" : "";
|
|
1619
|
+
return "<span class=\\"wg-mod " + cls + "\\"" + titleAttr + ">" + name + "</span>";
|
|
1900
1620
|
})
|
|
1901
1621
|
.join("");
|
|
1902
1622
|
const retryLabel = info.episodeNumber !== undefined ? "Retry Episode " + info.episodeNumber : "Retry";
|
|
@@ -1904,17 +1624,17 @@ async function renderStepError(page, info) {
|
|
|
1904
1624
|
? "Expected outcome - Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + " failed as intended"
|
|
1905
1625
|
: "Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + " threw";
|
|
1906
1626
|
const reasonHtml = isExpected
|
|
1907
|
-
? "<div class
|
|
1627
|
+
? "<div class=\\"wg-expected-reason\\">" + esc(info.expectedFailureReason) + "</div>"
|
|
1908
1628
|
: "";
|
|
1909
1629
|
panel.innerHTML =
|
|
1910
1630
|
episodesHtml +
|
|
1911
|
-
"<div id
|
|
1912
|
-
"<h3 class
|
|
1631
|
+
"<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
|
|
1632
|
+
"<h3 class=\\"" + (isExpected ? "wg-expected-heading" : "wg-error-heading") + "\\">" + headingText + "</h3>" +
|
|
1913
1633
|
reasonHtml +
|
|
1914
|
-
"<div class
|
|
1915
|
-
"<div class
|
|
1916
|
-
"<button id
|
|
1917
|
-
"<button id
|
|
1634
|
+
"<div class=\\"wg-error-msg\\">" + esc(info.message) + "</div>" +
|
|
1635
|
+
"<div class=\\"wg-error-actions\\">" +
|
|
1636
|
+
"<button id=\\"wg-error-retry\\" class=\\"wg-error-retry\\">" + esc(retryLabel) + "</button>" +
|
|
1637
|
+
"<button id=\\"wg-run\\" class=\\"wg-error-stop\\">Stop</button>" +
|
|
1918
1638
|
"</div>";
|
|
1919
1639
|
if (isNewPanel) {
|
|
1920
1640
|
document.documentElement.appendChild(panel);
|
|
@@ -1922,7 +1642,7 @@ async function renderStepError(page, info) {
|
|
|
1922
1642
|
}
|
|
1923
1643
|
if (window.__wgWirePanelChrome) {
|
|
1924
1644
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1925
|
-
stepLabel: info
|
|
1645
|
+
stepLabel: miniStepLabel(info),
|
|
1926
1646
|
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1927
1647
|
});
|
|
1928
1648
|
}
|
|
@@ -1930,7 +1650,7 @@ async function renderStepError(page, info) {
|
|
|
1930
1650
|
if (runBtn) runBtn.addEventListener("click", () => window.__wgNext({}));
|
|
1931
1651
|
const retryBtn = document.getElementById("wg-error-retry");
|
|
1932
1652
|
if (retryBtn) retryBtn.addEventListener("click", () => window.__wgNext({ __wgRetry: "1" }));
|
|
1933
|
-
},
|
|
1653
|
+
}, info)
|
|
1934
1654
|
.catch(() => {});
|
|
1935
1655
|
}
|
|
1936
1656
|
|
|
@@ -2046,6 +1766,11 @@ async function markStepRunning(page, _opts) {
|
|
|
2046
1766
|
const titleEl = panel.querySelector(".wg-chrome-title");
|
|
2047
1767
|
const label = panel.dataset.wgStepLabel;
|
|
2048
1768
|
if (titleEl && label) titleEl.textContent = label;
|
|
1769
|
+
const miniNext = panel.querySelector("[data-wg-mini-next]");
|
|
1770
|
+
if (miniNext) {
|
|
1771
|
+
miniNext.disabled = true;
|
|
1772
|
+
miniNext.classList.remove("wg-mini-next-show");
|
|
1773
|
+
}
|
|
2049
1774
|
}
|
|
2050
1775
|
})
|
|
2051
1776
|
.catch(() => {});
|
|
@@ -2054,16 +1779,14 @@ async function markStepRunning(page, _opts) {
|
|
|
2054
1779
|
async function showRing(page, box, label, tone, style) {
|
|
2055
1780
|
const size = normalizeHighlightSize(style && style.size);
|
|
2056
1781
|
const weight = normalizeHighlightWeight(style && style.weight);
|
|
2057
|
-
const focus = !!(style && style.focus);
|
|
2058
|
-
const color = (style && (style.color || style.labelColor || style.textColor)) || "";
|
|
2059
1782
|
await page
|
|
2060
1783
|
.evaluate(
|
|
2061
|
-
({ box, label, tone, size, weight
|
|
1784
|
+
({ box, label, tone, size, weight }) => {
|
|
2062
1785
|
if (window.__wgPositionRing) {
|
|
2063
|
-
window.__wgPositionRing(box, label, tone || "planned", { size, weight
|
|
1786
|
+
window.__wgPositionRing(box, label, tone || "planned", { size, weight });
|
|
2064
1787
|
}
|
|
2065
1788
|
},
|
|
2066
|
-
{ box, label, tone: tone || "planned", size, weight
|
|
1789
|
+
{ box, label, tone: tone || "planned", size, weight },
|
|
2067
1790
|
)
|
|
2068
1791
|
.catch(() => {});
|
|
2069
1792
|
}
|
|
@@ -2153,10 +1876,7 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2153
1876
|
if (s.selector) {
|
|
2154
1877
|
try {
|
|
2155
1878
|
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
|
-
}
|
|
1879
|
+
await applyHighlightZoom(page, s.selector, s.zoom);
|
|
2160
1880
|
const box = await page.locator(s.selector).first().boundingBox();
|
|
2161
1881
|
if (box) await showRing(page, box, caption, slideTone, slideStyle);
|
|
2162
1882
|
} catch {
|
|
@@ -2174,31 +1894,31 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2174
1894
|
panel.id = "wg-panel";
|
|
2175
1895
|
}
|
|
2176
1896
|
panel.classList.remove("wg-error", "wg-expected");
|
|
2177
|
-
// Do not clear wg-collapsed
|
|
1897
|
+
// Do not clear wg-collapsed - WirePanelChrome honors Hide (localStorage)
|
|
2178
1898
|
// unless forceCollapsed:true (--mini / --video).
|
|
2179
1899
|
const esc = (t) =>
|
|
2180
1900
|
String(t).replace(/&/g, "&").replace(/</g, "<").replace(/"/g, """);
|
|
2181
1901
|
const episodeLine =
|
|
2182
1902
|
info.episodeNumber !== undefined && info.episodeNumber !== null
|
|
2183
|
-
? "<div class
|
|
1903
|
+
? "<div class=\\"wg-episode\\">Episode " +
|
|
2184
1904
|
info.episodeNumber +
|
|
2185
1905
|
(info.episodeTitle ? ": " + esc(info.episodeTitle) : "") +
|
|
2186
1906
|
" · yap slides</div>"
|
|
2187
1907
|
: "";
|
|
2188
1908
|
const paceHtml =
|
|
2189
1909
|
info.paceBadge || info.paceLabel
|
|
2190
|
-
? "<div class
|
|
1910
|
+
? "<div class=\\"wg-pace\\" data-pace-kind=\\"" +
|
|
2191
1911
|
esc(info.paceKind || "normal") +
|
|
2192
|
-
"
|
|
1912
|
+
"\\"><span class=\\"wg-pace-badge\\">" +
|
|
2193
1913
|
esc(info.paceBadge || "1x") +
|
|
2194
1914
|
"</span><span>" +
|
|
2195
1915
|
esc(info.paceLabel || "") +
|
|
2196
1916
|
"</span></div>"
|
|
2197
1917
|
: "";
|
|
2198
1918
|
panel.innerHTML =
|
|
2199
|
-
"<div class
|
|
2200
|
-
"<button type
|
|
2201
|
-
"<div class
|
|
1919
|
+
"<div class=\\"wg-chrome\\"><span class=\\"wg-chrome-title\\">waygraph demo</span>" +
|
|
1920
|
+
"<button type=\\"button\\" class=\\"wg-hide-btn\\" data-wg-toggle=\\"1\\">Hide</button></div>" +
|
|
1921
|
+
"<div class=\\"wg-body\\">" +
|
|
2202
1922
|
episodeLine +
|
|
2203
1923
|
paceHtml +
|
|
2204
1924
|
"<h3>Slide " +
|
|
@@ -2208,35 +1928,35 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2208
1928
|
(info.blockName ? " · " + esc(info.blockName) : "") +
|
|
2209
1929
|
"</h3>" +
|
|
2210
1930
|
(info.tag
|
|
2211
|
-
? "<div style
|
|
1931
|
+
? "<div style=\\"border:none;padding:0;margin:0 0 6px;font-size:11px;color:#c9a6ff\\">" +
|
|
2212
1932
|
esc(info.tag) +
|
|
2213
1933
|
"</div>"
|
|
2214
1934
|
: "") +
|
|
2215
|
-
"<div class
|
|
1935
|
+
"<div class=\\"wg-narration\\">" +
|
|
2216
1936
|
esc(info.caption) +
|
|
2217
1937
|
"</div>" +
|
|
2218
1938
|
(info.detail
|
|
2219
|
-
? "<div class
|
|
1939
|
+
? "<div class=\\"wg-result-pretty\\" style=\\"margin:0 0 12px;color:#f0e8ff\\">" +
|
|
2220
1940
|
esc(info.detail) +
|
|
2221
1941
|
"</div>"
|
|
2222
1942
|
: "") +
|
|
2223
1943
|
(info.dwellMs != null
|
|
2224
|
-
? "<div class
|
|
1944
|
+
? "<div class=\\"wg-auto\\" id=\\"wg-dwell-hint\\" style=\\"margin:0 0 8px\\">Hold " +
|
|
2225
1945
|
(info.dwellMs / 1000).toFixed(1) +
|
|
2226
1946
|
"s before next...</div>"
|
|
2227
1947
|
: "") +
|
|
2228
|
-
"<div class
|
|
1948
|
+
"<div class=\\"wg-autoplay-row\\"><label><input type=\\"checkbox\\" id=\\"wg-autoplay-cb\\"" +
|
|
2229
1949
|
(info.autoNow ? " checked" : "") +
|
|
2230
1950
|
"> Auto-advance</label></div>" +
|
|
2231
|
-
"<div id
|
|
2232
|
-
(info.autoNow ? " style
|
|
2233
|
-
"><button type
|
|
1951
|
+
"<div id=\\"wg-gate-manual\\"" +
|
|
1952
|
+
(info.autoNow ? " style=\\"display:none\\"" : "") +
|
|
1953
|
+
"><button type=\\"button\\" id=\\"wg-run\\"" +
|
|
2234
1954
|
(info.dwellMs != null ? " disabled" : "") +
|
|
2235
1955
|
">" +
|
|
2236
1956
|
(info.isLast ? "Continue \\u25B6" : "Next slide \\u25B6") +
|
|
2237
1957
|
"</button></div>" +
|
|
2238
|
-
"<div id
|
|
2239
|
-
(info.autoNow ? "" : " style
|
|
1958
|
+
"<div id=\\"wg-gate-auto\\" class=\\"wg-auto\\"" +
|
|
1959
|
+
(info.autoNow ? "" : " style=\\"display:none\\"") +
|
|
2240
1960
|
">Auto-advancing...</div>" +
|
|
2241
1961
|
"</div>";
|
|
2242
1962
|
if (isNew) {
|
|
@@ -2254,7 +1974,7 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2254
1974
|
" / " +
|
|
2255
1975
|
info.total +
|
|
2256
1976
|
(info.blockName ? " \\u00b7 " + info.blockName : ""),
|
|
2257
|
-
// Video / --mini
|
|
1977
|
+
// Video / --mini: compact pill. Else omit so Hide sticks.
|
|
2258
1978
|
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
2259
1979
|
});
|
|
2260
1980
|
}
|
|
@@ -2408,8 +2128,7 @@ async function teardownOverlay(page) {
|
|
|
2408
2128
|
window.__wgRingTrack = null;
|
|
2409
2129
|
}
|
|
2410
2130
|
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"]) {
|
|
2131
|
+
for (const id of ["wg-panel", "wg-ring", "wg-ring-label", "wg-cursor", "wg-click-pulse", "wg-banner"]) {
|
|
2413
2132
|
const el = document.getElementById(id);
|
|
2414
2133
|
if (el) el.remove();
|
|
2415
2134
|
}
|
|
@@ -2417,53 +2136,24 @@ async function teardownOverlay(page) {
|
|
|
2417
2136
|
.catch(() => {});
|
|
2418
2137
|
}
|
|
2419
2138
|
|
|
2420
|
-
async function moveCursorTo(page, box, ms
|
|
2139
|
+
async function moveCursorTo(page, box, ms) {
|
|
2421
2140
|
const x = box.x + box.width / 2;
|
|
2422
2141
|
const y = box.y + box.height / 2;
|
|
2423
2142
|
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
2143
|
await page
|
|
2428
2144
|
.evaluate(({ x, y, ms }) => {
|
|
2429
|
-
window.__wgCameraLock = true;
|
|
2430
|
-
window.__wgIgnoreHostMouse = true;
|
|
2431
2145
|
if (window.__wgMoveCursorTo) window.__wgMoveCursorTo(x, y, ms);
|
|
2432
2146
|
}, { x, y, ms: travel })
|
|
2433
2147
|
.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
|
-
}
|
|
2148
|
+
// CSS transition is async - wait the full travel so the cursor is ON the
|
|
2149
|
+
// target before ring/pulse/real click (NavBlock click nav was skipping
|
|
2150
|
+
// this and looked like a teleport).
|
|
2444
2151
|
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(() => {});
|
|
2152
|
+
await new Promise((res) => setTimeout(res, travel));
|
|
2453
2153
|
}
|
|
2454
2154
|
return { x, y };
|
|
2455
2155
|
}
|
|
2456
2156
|
|
|
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
2157
|
async function clickPulseAt(page, x, y, tone) {
|
|
2468
2158
|
await page
|
|
2469
2159
|
.evaluate(({ x, y, tone }) => {
|
|
@@ -2588,47 +2278,17 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2588
2278
|
proto.click = async function (options) {
|
|
2589
2279
|
let clickPoint = null;
|
|
2590
2280
|
let clickTone = "auto";
|
|
2591
|
-
let keptCameraLock = false;
|
|
2592
2281
|
try {
|
|
2593
2282
|
await installOverlay(page);
|
|
2283
|
+
// Wait until the target is visible BEFORE measuring - otherwise
|
|
2284
|
+
// defineNavBlock({ click }) (and any slow-to-appear control) skips
|
|
2285
|
+
// the cursor entirely: boundingBox was null, then originalClick
|
|
2286
|
+
// waited and clicked with no demo animation.
|
|
2594
2287
|
await this.waitFor({
|
|
2595
2288
|
state: "visible",
|
|
2596
2289
|
timeout: (options && options.timeout) || 30000,
|
|
2597
2290
|
}).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();
|
|
2291
|
+
const box = await this.boundingBox();
|
|
2632
2292
|
const narrated = box ? await wasJustNarrated(page) : false;
|
|
2633
2293
|
if (box && !narrated) {
|
|
2634
2294
|
let fallback = "click";
|
|
@@ -2636,8 +2296,10 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2636
2296
|
const text = (await this.textContent())?.trim();
|
|
2637
2297
|
if (text && text.length > 0 && text.length <= 30) fallback = text;
|
|
2638
2298
|
} catch {
|
|
2639
|
-
|
|
2299
|
+
// element has no simple text (an icon button, say) - generic label is fine
|
|
2640
2300
|
}
|
|
2301
|
+
// Prefer "nav: …" when this locator is a NavBlock click target
|
|
2302
|
+
// (set on page by runStepMode just before act).
|
|
2641
2303
|
try {
|
|
2642
2304
|
const navLabel = await page.evaluate(() => window.__wgPendingNavClickLabel || null);
|
|
2643
2305
|
if (navLabel) fallback = String(navLabel);
|
|
@@ -2647,80 +2309,39 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2647
2309
|
const stubs = (stubBeforeRef && stubBeforeRef.current) || [];
|
|
2648
2310
|
const cap = await captionForLocator(page, this, stubs, fallback);
|
|
2649
2311
|
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, {
|
|
2312
|
+
clickPoint = await moveCursorTo(page, box, cursorMs(600));
|
|
2313
|
+
await showRing(page, box, cap.label, cap.tone, {
|
|
2662
2314
|
size: cap.size,
|
|
2663
2315
|
weight: cap.weight,
|
|
2664
2316
|
});
|
|
2665
2317
|
await dwellMatchedStub(page, this, stubs, pacing);
|
|
2318
|
+
// "pop for a few seconds" - Dan's own phrase, matching the
|
|
2319
|
+
// zsign demo-engine's ring-before-click pattern in
|
|
2320
|
+
// services/help-center-clip-engine's video-pipeline.
|
|
2666
2321
|
await new Promise((res) => setTimeout(res, clickPrePop()));
|
|
2667
2322
|
} else if (box) {
|
|
2668
|
-
|
|
2669
|
-
|
|
2323
|
+
// Ring/caption already handled by narrate() - still move the
|
|
2324
|
+
// cursor + pulse the click point, just skip re-showing the ring.
|
|
2325
|
+
clickPoint = await moveCursorTo(page, box, cursorMs(200));
|
|
2670
2326
|
clickTone = "planned";
|
|
2671
2327
|
}
|
|
2672
2328
|
} catch {
|
|
2673
|
-
|
|
2329
|
+
// best-effort - the real click below still runs either way
|
|
2674
2330
|
}
|
|
2675
2331
|
if (clickPoint) {
|
|
2676
2332
|
await clickPulseAt(page, clickPoint.x, clickPoint.y, clickTone);
|
|
2333
|
+
// clickPulseAt only triggers the CSS animation class - it doesn't
|
|
2334
|
+
// wait for it. Without a pause here, the real click (and any
|
|
2335
|
+
// resulting navigation/DOM change) fires while the ripple is still
|
|
2336
|
+
// mid-animation, sometimes cutting it off before it's even visible.
|
|
2337
|
+
// Wait out the same .5s the "@keyframes wg-pulse" rule uses, so the
|
|
2338
|
+
// mock click visually completes before the real one fires.
|
|
2677
2339
|
await new Promise((res) => setTimeout(res, skipTheater() ? 0 : 500));
|
|
2678
2340
|
}
|
|
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
|
-
}
|
|
2341
|
+
const result = await originalClick.call(this, options);
|
|
2342
|
+
await new Promise((res) => setTimeout(res, clickPostPop()));
|
|
2343
|
+
await hideRing(page);
|
|
2344
|
+
return result;
|
|
2724
2345
|
};
|
|
2725
2346
|
}
|
|
2726
2347
|
|
|
@@ -3034,9 +2655,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3034
2655
|
const stubBeforeTodos = stubBeforePhase.todos || [];
|
|
3035
2656
|
const isNavBlock = r.block.__waygraphKind === "nav";
|
|
3036
2657
|
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.
|
|
2658
|
+
// Video / --mini: compact pill. Never pass false - Hide/localStorage wins.
|
|
3040
2659
|
const forceCollapsed =
|
|
3041
2660
|
!!process.env.WAYGRAPH_VIDEO ||
|
|
3042
2661
|
process.env.WAYGRAPH_MINI === "1" ||
|
|
@@ -3086,7 +2705,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3086
2705
|
todos: stubBeforeTodos,
|
|
3087
2706
|
...paceSpeak,
|
|
3088
2707
|
});
|
|
3089
|
-
await setDemoTodos(page, stubBeforeTodos);
|
|
3090
2708
|
const edits = await gate();
|
|
3091
2709
|
for (const k of requires) {
|
|
3092
2710
|
if (edits[k.name] !== undefined) {
|
|
@@ -3098,40 +2716,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3098
2716
|
}
|
|
3099
2717
|
}
|
|
3100
2718
|
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
2719
|
await markStepRunning(page, {});
|
|
3136
2720
|
// NavBlock click-nav: label the upcoming Locator.click demo cursor as
|
|
3137
2721
|
// "nav: <block>" so pia/demo watchers see cursor+pulse on click nav
|
|
@@ -3181,7 +2765,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3181
2765
|
fixtures: fixturesOnError,
|
|
3182
2766
|
error: err,
|
|
3183
2767
|
});
|
|
3184
|
-
await setDemoBannerTitle(page, errPhase.title);
|
|
3185
2768
|
const errHighlights = errPhase.highlights.map((h) => {
|
|
3186
2769
|
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
3187
2770
|
return {
|
|
@@ -3193,10 +2776,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3193
2776
|
size: styled.size,
|
|
3194
2777
|
weight: styled.weight,
|
|
3195
2778
|
zoom: styled.zoom,
|
|
3196
|
-
focus: styled.focus,
|
|
3197
|
-
color: styled.color,
|
|
3198
|
-
cursor: styled.cursor,
|
|
3199
|
-
zoomOut: styled.zoomOut,
|
|
3200
2779
|
};
|
|
3201
2780
|
});
|
|
3202
2781
|
await cycleHighlightRings(page, errHighlights, !!pacing.gatesFast, {
|
|
@@ -3223,7 +2802,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3223
2802
|
episodeTitle: r.episodeTitle,
|
|
3224
2803
|
expectedFailureReason: r.expectedFailureReason,
|
|
3225
2804
|
stepperMode,
|
|
3226
|
-
// Omit forceCollapsed on errors so Hide
|
|
2805
|
+
// Omit forceCollapsed on errors so Hide is not stomped.
|
|
3227
2806
|
});
|
|
3228
2807
|
const errEdits = await gate();
|
|
3229
2808
|
if (errEdits && errEdits.__wgRetry) {
|
|
@@ -3275,8 +2854,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3275
2854
|
fixtures: fixturesAfter,
|
|
3276
2855
|
});
|
|
3277
2856
|
stubAfterTodos = afterPhase.todos || [];
|
|
3278
|
-
await setDemoBannerTitle(page, afterPhase.title);
|
|
3279
|
-
await setDemoTodos(page, afterPhase.todos || []);
|
|
3280
2857
|
highlights = afterPhase.highlights.map((h) => {
|
|
3281
2858
|
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
3282
2859
|
return {
|
|
@@ -3288,10 +2865,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3288
2865
|
size: styled.size,
|
|
3289
2866
|
weight: styled.weight,
|
|
3290
2867
|
zoom: styled.zoom,
|
|
3291
|
-
focus: styled.focus,
|
|
3292
|
-
color: styled.color,
|
|
3293
|
-
cursor: styled.cursor,
|
|
3294
|
-
zoomOut: styled.zoomOut,
|
|
3295
2868
|
};
|
|
3296
2869
|
});
|
|
3297
2870
|
} else {
|
|
@@ -4674,7 +4247,7 @@ Primary (less is more):
|
|
|
4674
4247
|
--auto-next Auto-advance steps (alias: --autoplay)
|
|
4675
4248
|
--fast Shorter auto-next / Next gates (keeps smooth cursor)
|
|
4676
4249
|
--full Classic wrap-all block chips (default: carousel)
|
|
4677
|
-
--mini Force compact mini panel (Hide pill); alias --stepper-mini
|
|
4250
|
+
--mini Force compact mini panel (Hide pill + Next); alias --stepper-mini
|
|
4678
4251
|
--ff-expand Expand fastForwardComposeBlock inners as separate steps
|
|
4679
4252
|
--ff-disabled Dispute: expand FF (alias --no-ff); blitz kept on those inners
|
|
4680
4253
|
--auto-play-video Unattended + recorded: --auto-next + --video (+ step); headless by default
|