waygraph 0.12.40 → 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.d.ts +1 -1
- package/dist/cli.js +392 -788
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Primary verbs (less is more):
|
|
6
6
|
* auto Explore picker; `.flow.ts` / `--blocks From To` = run that path
|
|
7
|
-
* demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--fast` `--full` `--ff-expand` `--ff-disabled`
|
|
7
|
+
* demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--fast` `--full` `--mini` `--ff-expand` `--ff-disabled`
|
|
8
8
|
* run Execute; `--blocks` `--data` `--non-headless` `--video`
|
|
9
9
|
*
|
|
10
10
|
* Also: list / nav / validate / check / graph / init / agent-dive / traverse / try
|
|
@@ -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,32 +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
|
-
//
|
|
862
|
+
// Collapsed = "N / M · block" + Next (when not auto). Never pass
|
|
863
|
+
// forceCollapsed:false - omit so Hide/localStorage wins.
|
|
1052
864
|
window.__wgWirePanelChrome = (panel, storageKey, chromeTitle, opts) => {
|
|
1053
865
|
if (!panel) return;
|
|
1054
866
|
opts = opts || {};
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
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();
|
|
1074
917
|
const stepLabel = opts.stepLabel || panel.dataset.wgStepLabel || "";
|
|
1075
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
|
+
};
|
|
1076
938
|
const apply = (hidden, persist) => {
|
|
1077
939
|
panel.classList.toggle("wg-collapsed", hidden);
|
|
1078
940
|
const t = panel.querySelector("[data-wg-toggle]");
|
|
@@ -1084,6 +946,7 @@ async function installOverlay(page, title) {
|
|
|
1084
946
|
: (chromeTitle || "waygraph demo");
|
|
1085
947
|
}
|
|
1086
948
|
if (t) t.textContent = hidden ? "Show" : "Hide";
|
|
949
|
+
syncMiniNext(hidden);
|
|
1087
950
|
if (persist !== false) {
|
|
1088
951
|
try {
|
|
1089
952
|
localStorage.setItem(storageKey, hidden ? "1" : "0");
|
|
@@ -1095,8 +958,6 @@ async function installOverlay(page, title) {
|
|
|
1095
958
|
let hidden = false;
|
|
1096
959
|
if (opts.forceCollapsed === true) {
|
|
1097
960
|
hidden = true;
|
|
1098
|
-
} else if (opts.forceCollapsed === false) {
|
|
1099
|
-
hidden = false;
|
|
1100
961
|
} else {
|
|
1101
962
|
try {
|
|
1102
963
|
hidden = localStorage.getItem(storageKey) === "1";
|
|
@@ -1104,6 +965,7 @@ async function installOverlay(page, title) {
|
|
|
1104
965
|
/* ignore */
|
|
1105
966
|
}
|
|
1106
967
|
}
|
|
968
|
+
// force mini does not overwrite Hide preference in storage.
|
|
1107
969
|
apply(hidden, opts.forceCollapsed === true ? false : true);
|
|
1108
970
|
const toggle = panel.querySelector("[data-wg-toggle]");
|
|
1109
971
|
if (toggle && !toggle.dataset.wgWired) {
|
|
@@ -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 {
|
|
@@ -1167,7 +1049,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1167
1049
|
// active, the episode 2 tab is inactive"). Absent entirely for an ad
|
|
1168
1050
|
// hoc block chain with no named Flows.
|
|
1169
1051
|
const episodesHtml = info.allEpisodes && info.allEpisodes.length > 0
|
|
1170
|
-
? "<div id
|
|
1052
|
+
? "<div id=\\"wg-episodes\\">" +
|
|
1171
1053
|
info.allEpisodes
|
|
1172
1054
|
.map((e) => {
|
|
1173
1055
|
const cls = e.episodeNumber < info.episodeNumber ? "wg-ep-done"
|
|
@@ -1178,7 +1060,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1178
1060
|
const enteredCls = info.justEnteredEpisode && e.episodeNumber === info.episodeNumber
|
|
1179
1061
|
? " wg-ep-entered"
|
|
1180
1062
|
: "";
|
|
1181
|
-
return "<span class
|
|
1063
|
+
return "<span class=\\"wg-ep-tab " + cls + enteredCls + "\\">Episode " + e.episodeNumber + ": " +
|
|
1182
1064
|
esc(e.episodeTitle || "") + "</span>";
|
|
1183
1065
|
})
|
|
1184
1066
|
.join("") +
|
|
@@ -1194,66 +1076,40 @@ async function renderBeforeStep(page, info) {
|
|
|
1194
1076
|
.map((name, idx) => {
|
|
1195
1077
|
const cls = idx < info.moduleIndex ? "wg-mod-done" : idx === info.moduleIndex ? "wg-mod-current" : "wg-mod-upcoming";
|
|
1196
1078
|
const desc = info.allDescriptions && info.allDescriptions[idx];
|
|
1197
|
-
const titleAttr = desc ? " title
|
|
1198
|
-
return "<span class
|
|
1079
|
+
const titleAttr = desc ? " title=\\"" + esc(desc) + "\\"" : "";
|
|
1080
|
+
return "<span class=\\"wg-mod " + cls + "\\"" + titleAttr + ">" + name + "</span>";
|
|
1199
1081
|
})
|
|
1200
1082
|
.join("");
|
|
1201
1083
|
const narrationHtml = info.description
|
|
1202
|
-
? "<div class
|
|
1084
|
+
? "<div class=\\"wg-narration\\">" + esc(info.description) + "</div>"
|
|
1203
1085
|
: "";
|
|
1204
1086
|
const paceHtml =
|
|
1205
1087
|
info.paceBadge || info.paceLabel
|
|
1206
|
-
? "<div class
|
|
1088
|
+
? "<div class=\\"wg-pace\\" data-pace-kind=\\"" +
|
|
1207
1089
|
esc(info.paceKind || "normal") +
|
|
1208
|
-
"
|
|
1090
|
+
"\\"><span class=\\"wg-pace-badge\\">" +
|
|
1209
1091
|
esc(info.paceBadge || "1x") +
|
|
1210
1092
|
"</span><span>" +
|
|
1211
1093
|
esc(info.paceLabel || "") +
|
|
1212
1094
|
"</span></div>"
|
|
1213
1095
|
: "";
|
|
1214
1096
|
let html =
|
|
1215
|
-
"<div id
|
|
1097
|
+
"<div id=\\"wg-progress\\"><div id=\\"wg-progress-bar\\" style=\\"width:" + pct + "%\\"></div></div>" +
|
|
1216
1098
|
episodesHtml +
|
|
1217
1099
|
paceHtml +
|
|
1218
|
-
"<div id
|
|
1100
|
+
"<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
|
|
1219
1101
|
"<h3>Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + "</h3>" +
|
|
1220
1102
|
narrationHtml +
|
|
1221
|
-
(
|
|
1222
|
-
const list = info.todos || [];
|
|
1223
|
-
if (!list.length) return "";
|
|
1224
|
-
return (
|
|
1225
|
-
'<ul id="wg-todos">' +
|
|
1226
|
-
list
|
|
1227
|
-
.map((t) => {
|
|
1228
|
-
const cls = t.current ? "wg-todo-current" : t.done ? "wg-todo-done" : "wg-todo-pending";
|
|
1229
|
-
const mark = t.done
|
|
1230
|
-
? String.fromCharCode(0x2713)
|
|
1231
|
-
: t.current
|
|
1232
|
-
? String.fromCharCode(0x2192)
|
|
1233
|
-
: String.fromCharCode(0x25cb);
|
|
1234
|
-
return (
|
|
1235
|
-
'<li class="' +
|
|
1236
|
-
cls +
|
|
1237
|
-
'"><span class="wg-todo-mark">' +
|
|
1238
|
-
mark +
|
|
1239
|
-
'</span><span>' +
|
|
1240
|
-
esc(t.text || "") +
|
|
1241
|
-
'</span></li>'
|
|
1242
|
-
);
|
|
1243
|
-
})
|
|
1244
|
-
.join("") +
|
|
1245
|
-
'</ul>'
|
|
1246
|
-
);
|
|
1247
|
-
})());
|
|
1103
|
+
todosHtmlFromInfo(info, esc);
|
|
1248
1104
|
if (info.keys.length === 0) {
|
|
1249
|
-
html += "<div class
|
|
1105
|
+
html += "<div class=\\"wg-key\\">(no MemKeys required)</div>";
|
|
1250
1106
|
}
|
|
1251
1107
|
for (const k of info.keys) {
|
|
1252
1108
|
html +=
|
|
1253
|
-
"<div class
|
|
1254
|
-
"<textarea data-key
|
|
1109
|
+
"<div class=\\"wg-key\\"><label>" + k.name + "</label>" +
|
|
1110
|
+
"<textarea data-key=\\"" + k.name + "\\" rows=\\"2\\">" +
|
|
1255
1111
|
k.value.replace(/</g, "<") + "</textarea>" +
|
|
1256
|
-
"<div class
|
|
1112
|
+
"<div class=\\"wg-key-pretty\\" data-pretty-for=\\"" + esc(k.name) + "\\"></div>" +
|
|
1257
1113
|
"</div>";
|
|
1258
1114
|
}
|
|
1259
1115
|
let autoNow = false;
|
|
@@ -1263,12 +1119,12 @@ async function renderBeforeStep(page, info) {
|
|
|
1263
1119
|
/* private mode / blocked storage - defaults to manual */
|
|
1264
1120
|
}
|
|
1265
1121
|
html +=
|
|
1266
|
-
"<div class
|
|
1122
|
+
"<div class=\\"wg-autoplay-row\\"><label><input type=\\"checkbox\\" id=\\"wg-autoplay-cb\\"" +
|
|
1267
1123
|
(autoNow ? " checked" : "") +
|
|
1268
1124
|
"> Auto-advance</label></div>" +
|
|
1269
|
-
"<div id
|
|
1270
|
-
"><button id
|
|
1271
|
-
"<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\\"") +
|
|
1272
1128
|
">Auto-advancing...</div>";
|
|
1273
1129
|
panel.innerHTML = html;
|
|
1274
1130
|
if (isNewPanel) {
|
|
@@ -1278,7 +1134,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1278
1134
|
if (window.__wgWirePanelChrome) {
|
|
1279
1135
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1280
1136
|
stepLabel: miniStepLabel(info),
|
|
1281
|
-
forceCollapsed: info.forceCollapsed === true ? true :
|
|
1137
|
+
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1282
1138
|
});
|
|
1283
1139
|
}
|
|
1284
1140
|
// Live, human-readable preview of what a MemKey's raw JSON will
|
|
@@ -1301,7 +1157,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1301
1157
|
return Object.entries(parsed)
|
|
1302
1158
|
.map(([field, value]) => {
|
|
1303
1159
|
const label = field.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
|
|
1304
|
-
return "<div class
|
|
1160
|
+
return "<div class=\\"wg-key-pretty-row\\"><span class=\\"wg-key-pretty-label\\">" +
|
|
1305
1161
|
esc(label) + ":</span> " + esc(String(value)) + "</div>";
|
|
1306
1162
|
})
|
|
1307
1163
|
.join("");
|
|
@@ -1310,7 +1166,7 @@ async function renderBeforeStep(page, info) {
|
|
|
1310
1166
|
const prettyOn = window.__wgPretty !== false;
|
|
1311
1167
|
panel.querySelectorAll("textarea[data-key]").forEach((ta) => {
|
|
1312
1168
|
const key = ta.getAttribute("data-key");
|
|
1313
|
-
const previewEl = panel.querySelector(".wg-key-pretty[data-pretty-for
|
|
1169
|
+
const previewEl = panel.querySelector(".wg-key-pretty[data-pretty-for=\\"" + key + "\\"]");
|
|
1314
1170
|
if (!previewEl) return;
|
|
1315
1171
|
previewEl.style.display = prettyOn ? "" : "none";
|
|
1316
1172
|
if (prettyOn) previewEl.innerHTML = prettyMemValue(ta.value);
|
|
@@ -1356,25 +1212,6 @@ async function renderBeforeStep(page, info) {
|
|
|
1356
1212
|
|
|
1357
1213
|
async function ensureSelectorInView(page, selector) {
|
|
1358
1214
|
try {
|
|
1359
|
-
// Body-scale camera + scrollIntoView = teleport / "outside viewport" loop.
|
|
1360
|
-
// When zoomed, pan the camera to the target instead of scrolling the document.
|
|
1361
|
-
const zoomed = await page
|
|
1362
|
-
.evaluate(() => !!(window.__wgCamera && window.__wgCamera.scale > 1.001))
|
|
1363
|
-
.catch(() => false);
|
|
1364
|
-
if (zoomed) {
|
|
1365
|
-
await page
|
|
1366
|
-
.evaluate((sel) => {
|
|
1367
|
-
const el = document.querySelector(sel);
|
|
1368
|
-
if (!el || !window.__wgSetCamera || !window.__wgElDocPoint) return;
|
|
1369
|
-
const cam = window.__wgCamera;
|
|
1370
|
-
const pt = window.__wgElDocPoint(el);
|
|
1371
|
-
window.__wgCameraLock = true;
|
|
1372
|
-
window.__wgSetCamera(pt.x, pt.y, cam.scale > 1 ? cam.scale : 1.4, 320);
|
|
1373
|
-
}, selector)
|
|
1374
|
-
.catch(() => {});
|
|
1375
|
-
await new Promise((res) => setTimeout(res, 340));
|
|
1376
|
-
return;
|
|
1377
|
-
}
|
|
1378
1215
|
const loc = page.locator(selector).first();
|
|
1379
1216
|
await loc.scrollIntoViewIfNeeded().catch(() => {});
|
|
1380
1217
|
await page
|
|
@@ -1441,54 +1278,48 @@ async function ensureSelectorInView(page, selector) {
|
|
|
1441
1278
|
}
|
|
1442
1279
|
}
|
|
1443
1280
|
|
|
1444
|
-
async function
|
|
1445
|
-
const z =
|
|
1446
|
-
if (!
|
|
1281
|
+
async function applyHighlightZoom(page, selector, zoom) {
|
|
1282
|
+
const z = Number(zoom);
|
|
1283
|
+
if (!Number.isFinite(z) || z <= 1.001) {
|
|
1447
1284
|
await page.evaluate(() => {
|
|
1448
|
-
if (window.
|
|
1285
|
+
if (window.__wgClearZoom) window.__wgClearZoom();
|
|
1449
1286
|
}).catch(() => {});
|
|
1450
1287
|
return;
|
|
1451
1288
|
}
|
|
1452
|
-
const vx = box.x + box.width / 2;
|
|
1453
|
-
const vy = box.y + box.height / 2;
|
|
1454
|
-
const dur = ms != null ? ms : 650;
|
|
1455
|
-
await page
|
|
1456
|
-
.evaluate(({ vx, vy, scale, dur }) => {
|
|
1457
|
-
if (!window.__wgSetCamera) return;
|
|
1458
|
-
const pt = window.__wgViewportToDoc
|
|
1459
|
-
? window.__wgViewportToDoc(vx, vy)
|
|
1460
|
-
: { x: vx, y: vy };
|
|
1461
|
-
window.__wgSetCamera(pt.x, pt.y, scale, dur);
|
|
1462
|
-
}, { vx, vy, scale: z, dur })
|
|
1463
|
-
.catch(() => {});
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
async function setDemoTodos(page, todos) {
|
|
1467
|
-
await page
|
|
1468
|
-
.evaluate((list) => {
|
|
1469
|
-
if (window.__wgSetTodos) window.__wgSetTodos(list || []);
|
|
1470
|
-
}, todos && todos.length ? todos : [])
|
|
1471
|
-
.catch(() => {});
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
async function resetCameraZoom(page) {
|
|
1475
1289
|
await page
|
|
1476
|
-
.evaluate(
|
|
1477
|
-
|
|
1478
|
-
|
|
1290
|
+
.evaluate(
|
|
1291
|
+
({ sel, scale }) => {
|
|
1292
|
+
if (window.__wgApplyZoom) window.__wgApplyZoom(sel, scale);
|
|
1293
|
+
},
|
|
1294
|
+
{ sel: selector, scale: z },
|
|
1295
|
+
)
|
|
1479
1296
|
.catch(() => {});
|
|
1480
1297
|
}
|
|
1481
1298
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
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
|
+
);
|
|
1489
1321
|
}
|
|
1490
1322
|
|
|
1491
|
-
/** Collapsed chrome title: Ep N / i / n / block / pace. */
|
|
1492
1323
|
function miniStepLabel(info) {
|
|
1493
1324
|
const ep =
|
|
1494
1325
|
info.episodeNumber !== undefined && info.episodeNumber !== null
|
|
@@ -1506,174 +1337,94 @@ function miniStepLabel(info) {
|
|
|
1506
1337
|
|
|
1507
1338
|
async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
1508
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).
|
|
1509
1342
|
const defaultHoldMs = opts && opts.defaultHoldMs != null ? opts.defaultHoldMs : null;
|
|
1510
1343
|
const pace = opts && opts.pace !== undefined ? opts.pace : undefined;
|
|
1511
|
-
const fadeMs = 320;
|
|
1512
|
-
const n = list.length;
|
|
1513
|
-
const baseStepLabel = (opts && opts.stepLabel) || "";
|
|
1514
|
-
|
|
1515
|
-
const updateHlProgress = async (i) => {
|
|
1516
|
-
if (n <= 0) return;
|
|
1517
|
-
const tag = "[" + (i + 1) + "/" + n + "]";
|
|
1518
|
-
await page
|
|
1519
|
-
.evaluate(
|
|
1520
|
-
({ tag, baseStepLabel, i, n }) => {
|
|
1521
|
-
const panel = document.getElementById("wg-panel");
|
|
1522
|
-
if (!panel) return;
|
|
1523
|
-
panel.dataset.wgHlIdx = String(i + 1);
|
|
1524
|
-
panel.dataset.wgHlN = String(n);
|
|
1525
|
-
const titleEl = panel.querySelector(".wg-chrome-title");
|
|
1526
|
-
if (titleEl) {
|
|
1527
|
-
const base = panel.dataset.wgStepLabel || baseStepLabel || "waygraph demo";
|
|
1528
|
-
if (panel.classList.contains("wg-collapsed")) {
|
|
1529
|
-
titleEl.textContent = base + " " + tag;
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
let span = document.getElementById("wg-hl-idx");
|
|
1533
|
-
const h3 = panel.querySelector("h3");
|
|
1534
|
-
if (h3) {
|
|
1535
|
-
if (!span) {
|
|
1536
|
-
span = document.createElement("span");
|
|
1537
|
-
span.id = "wg-hl-idx";
|
|
1538
|
-
span.style.marginLeft = "8px";
|
|
1539
|
-
span.style.color = "#c9a6ff";
|
|
1540
|
-
span.style.fontWeight = "700";
|
|
1541
|
-
h3.appendChild(span);
|
|
1542
|
-
}
|
|
1543
|
-
span.textContent = tag;
|
|
1544
|
-
}
|
|
1545
|
-
},
|
|
1546
|
-
{ tag, baseStepLabel, i, n },
|
|
1547
|
-
)
|
|
1548
|
-
.catch(() => {});
|
|
1549
|
-
};
|
|
1550
|
-
|
|
1551
|
-
const stopLiveFollow = async () => {
|
|
1552
|
-
await page
|
|
1553
|
-
.evaluate(() => {
|
|
1554
|
-
if (window.__wgStopHighlightTrack) window.__wgStopHighlightTrack();
|
|
1555
|
-
if (window.__wgRingRaf) {
|
|
1556
|
-
cancelAnimationFrame(window.__wgRingRaf);
|
|
1557
|
-
window.__wgRingRaf = null;
|
|
1558
|
-
}
|
|
1559
|
-
if (window.__wgRingTrack) {
|
|
1560
|
-
window.removeEventListener("mousemove", window.__wgRingTrack);
|
|
1561
|
-
window.removeEventListener("resize", window.__wgRingTrack);
|
|
1562
|
-
window.removeEventListener("scroll", window.__wgRingTrack, true);
|
|
1563
|
-
window.__wgRingTrack = null;
|
|
1564
|
-
}
|
|
1565
|
-
if (window.__wgHideFocusVeil) window.__wgHideFocusVeil();
|
|
1566
|
-
})
|
|
1567
|
-
.catch(() => {});
|
|
1568
|
-
};
|
|
1569
|
-
|
|
1570
1344
|
for (let i = 0; i < list.length; i++) {
|
|
1571
1345
|
const h = list[i];
|
|
1572
1346
|
try {
|
|
1573
|
-
await stopLiveFollow();
|
|
1574
1347
|
await ensureSelectorInView(page, h.selector);
|
|
1348
|
+
await applyHighlightZoom(page, h.selector, h.zoom);
|
|
1575
1349
|
const box = await page.locator(h.selector).first().boundingBox();
|
|
1576
|
-
if (
|
|
1577
|
-
|
|
1578
|
-
|
|
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
|
-
zoom: z,
|
|
1617
|
-
camMs,
|
|
1618
|
-
},
|
|
1619
|
-
)
|
|
1620
|
-
.catch(() => {});
|
|
1621
|
-
const alreadyZoomed = await page
|
|
1622
|
-
.evaluate(() => !!(window.__wgCamera && window.__wgCamera.scale > 1.001))
|
|
1623
|
-
.catch(() => false);
|
|
1624
|
-
const waitMs = alreadyZoomed ? Math.min(camMs, 420) : camMs;
|
|
1625
|
-
await new Promise((res) => setTimeout(res, waitMs));
|
|
1626
|
-
if (h.cursor !== false) {
|
|
1627
|
-
// Park Playwright real mouse + demo cursor on the LIVE centered target.
|
|
1628
|
-
const live = await page
|
|
1629
|
-
.evaluate((sel) => {
|
|
1630
|
-
const el = document.querySelector(sel);
|
|
1631
|
-
if (!el) return null;
|
|
1632
|
-
const r = el.getBoundingClientRect();
|
|
1633
|
-
return { x: r.left, y: r.top, width: r.width, height: r.height };
|
|
1634
|
-
}, h.selector)
|
|
1635
|
-
.catch(() => null);
|
|
1636
|
-
if (live) {
|
|
1637
|
-
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(() => {});
|
|
1638
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));
|
|
1639
1396
|
}
|
|
1640
|
-
// Appear settle
|
|
1641
|
-
await new Promise((res) => setTimeout(res, fadeMs));
|
|
1642
|
-
const authored = resolveFixtureDwellMs(h, { gatesFast, pace });
|
|
1643
|
-
const legacyMs = 1100;
|
|
1644
|
-
const holdMs =
|
|
1645
|
-
authored != null ? authored : defaultHoldMs != null ? defaultHoldMs : legacyMs;
|
|
1646
|
-
await new Promise((res) => setTimeout(res, holdMs));
|
|
1647
|
-
// Disappear before next. zoomOut:false keeps camera for the next ring.
|
|
1648
|
-
await stopLiveFollow();
|
|
1649
|
-
await hideRing(page);
|
|
1650
|
-
const doZoomOut = h.zoomOut !== false;
|
|
1651
|
-
if (doZoomOut) await resetCameraZoom(page);
|
|
1652
|
-
await new Promise((res) => setTimeout(res, doZoomOut ? fadeMs : 120));
|
|
1653
1397
|
} catch {
|
|
1654
|
-
|
|
1398
|
+
// best-effort - a selector that doesn't resolve just gets skipped
|
|
1655
1399
|
}
|
|
1656
1400
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
const last = list.length > 0 ? list[list.length - 1] : null;
|
|
1660
|
-
if (!last || last.zoomOut !== false) {
|
|
1661
|
-
await resetCameraZoom(page);
|
|
1662
|
-
} else {
|
|
1663
|
-
// Hold lock into the upcoming act click so follow/scroll cannot yank
|
|
1664
|
-
// the camera off the already-centered target (cart teleport bug).
|
|
1401
|
+
if (list.length > 0) {
|
|
1402
|
+
const last = list[list.length - 1];
|
|
1665
1403
|
await page
|
|
1666
|
-
.evaluate(() => {
|
|
1667
|
-
window.
|
|
1668
|
-
|
|
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)
|
|
1669
1422
|
.catch(() => {});
|
|
1670
|
-
}
|
|
1671
|
-
if (list.length === 0) {
|
|
1423
|
+
} else {
|
|
1672
1424
|
await hideRing(page);
|
|
1673
1425
|
}
|
|
1674
1426
|
}
|
|
1675
1427
|
|
|
1676
|
-
|
|
1677
1428
|
async function renderAfterStep(page, info) {
|
|
1678
1429
|
await installOverlay(page, info.title);
|
|
1679
1430
|
// Clear any tracker from a previous highlight before cycling through this
|
|
@@ -1717,8 +1468,8 @@ async function renderAfterStep(page, info) {
|
|
|
1717
1468
|
.map((name, idx) => {
|
|
1718
1469
|
const cls = idx <= info.moduleIndex ? "wg-mod-done" : "wg-mod-upcoming";
|
|
1719
1470
|
const desc = info.allDescriptions && info.allDescriptions[idx];
|
|
1720
|
-
const titleAttr = desc ? " title
|
|
1721
|
-
return "<span class
|
|
1471
|
+
const titleAttr = desc ? " title=\\"" + escA(desc) + "\\"" : "";
|
|
1472
|
+
return "<span class=\\"wg-mod " + cls + "\\"" + titleAttr + ">" + name + "</span>";
|
|
1722
1473
|
})
|
|
1723
1474
|
.join("");
|
|
1724
1475
|
// QA-friendly by default ("LoginSuccess" -> "Login Success") - raw
|
|
@@ -1729,13 +1480,13 @@ async function renderAfterStep(page, info) {
|
|
|
1729
1480
|
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2") || info.resultTag;
|
|
1730
1481
|
const pretty = window.__wgPretty !== false;
|
|
1731
1482
|
const resultHtml =
|
|
1732
|
-
"<div class
|
|
1733
|
-
"<button class
|
|
1734
|
-
"<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>" +
|
|
1735
1486
|
"</div>" +
|
|
1736
|
-
"<div class
|
|
1487
|
+
"<div class=\\"wg-result wg-result-pretty\\" style=\\"display:" + (pretty ? "block" : "none") + "\\">" +
|
|
1737
1488
|
prettyText + "</div>" +
|
|
1738
|
-
"<div class
|
|
1489
|
+
"<div class=\\"wg-result wg-result-json\\" style=\\"display:" + (pretty ? "none" : "block") + "\\">" +
|
|
1739
1490
|
info.resultTag + "</div>";
|
|
1740
1491
|
let autoNow = false;
|
|
1741
1492
|
try {
|
|
@@ -1744,21 +1495,21 @@ async function renderAfterStep(page, info) {
|
|
|
1744
1495
|
/* private mode / blocked storage - defaults to manual */
|
|
1745
1496
|
}
|
|
1746
1497
|
const gateHtml =
|
|
1747
|
-
"<div class
|
|
1498
|
+
"<div class=\\"wg-autoplay-row\\"><label><input type=\\"checkbox\\" id=\\"wg-autoplay-cb\\"" +
|
|
1748
1499
|
(autoNow ? " checked" : "") +
|
|
1749
1500
|
"> Auto-advance</label></div>" +
|
|
1750
|
-
"<div id
|
|
1751
|
-
"><button id
|
|
1752
|
-
"<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\\"") +
|
|
1753
1504
|
">Auto-advancing...</div>";
|
|
1754
1505
|
const episodesHtml = info.allEpisodes && info.allEpisodes.length > 0
|
|
1755
|
-
? "<div id
|
|
1506
|
+
? "<div id=\\"wg-episodes\\">" +
|
|
1756
1507
|
info.allEpisodes
|
|
1757
1508
|
.map((e) => {
|
|
1758
1509
|
const cls = e.episodeNumber < info.episodeNumber ? "wg-ep-done"
|
|
1759
1510
|
: e.episodeNumber === info.episodeNumber ? "wg-ep-current"
|
|
1760
1511
|
: "wg-ep-upcoming";
|
|
1761
|
-
return "<span class
|
|
1512
|
+
return "<span class=\\"wg-ep-tab " + cls + "\\">Episode " + e.episodeNumber + ": " +
|
|
1762
1513
|
escA(e.episodeTitle || "") + "</span>";
|
|
1763
1514
|
})
|
|
1764
1515
|
.join("") +
|
|
@@ -1766,47 +1517,21 @@ async function renderAfterStep(page, info) {
|
|
|
1766
1517
|
: "";
|
|
1767
1518
|
const paceHtml =
|
|
1768
1519
|
info.paceBadge || info.paceLabel
|
|
1769
|
-
? "<div class
|
|
1520
|
+
? "<div class=\\"wg-pace\\" data-pace-kind=\\"" +
|
|
1770
1521
|
escA(info.paceKind || "normal") +
|
|
1771
|
-
"
|
|
1522
|
+
"\\"><span class=\\"wg-pace-badge\\">" +
|
|
1772
1523
|
escA(info.paceBadge || "1x") +
|
|
1773
1524
|
"</span><span>" +
|
|
1774
1525
|
escA(info.paceLabel || "") +
|
|
1775
1526
|
"</span></div>"
|
|
1776
1527
|
: "";
|
|
1777
1528
|
panel.innerHTML =
|
|
1778
|
-
"<div id
|
|
1529
|
+
"<div id=\\"wg-progress\\"><div id=\\"wg-progress-bar\\" style=\\"width:" + pct + "%\\"></div></div>" +
|
|
1779
1530
|
episodesHtml +
|
|
1780
1531
|
paceHtml +
|
|
1781
|
-
"<div id
|
|
1532
|
+
"<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
|
|
1782
1533
|
"<h3>" + heading + "</h3>" +
|
|
1783
|
-
(
|
|
1784
|
-
const list = info.todos || [];
|
|
1785
|
-
if (!list.length) return "";
|
|
1786
|
-
return (
|
|
1787
|
-
'<ul id="wg-todos">' +
|
|
1788
|
-
list
|
|
1789
|
-
.map((t) => {
|
|
1790
|
-
const cls = t.current ? "wg-todo-current" : t.done ? "wg-todo-done" : "wg-todo-pending";
|
|
1791
|
-
const mark = t.done
|
|
1792
|
-
? String.fromCharCode(0x2713)
|
|
1793
|
-
: t.current
|
|
1794
|
-
? String.fromCharCode(0x2192)
|
|
1795
|
-
: String.fromCharCode(0x25cb);
|
|
1796
|
-
return (
|
|
1797
|
-
'<li class="' +
|
|
1798
|
-
cls +
|
|
1799
|
-
'"><span class="wg-todo-mark">' +
|
|
1800
|
-
mark +
|
|
1801
|
-
'</span><span>' +
|
|
1802
|
-
escA(t.text || "") +
|
|
1803
|
-
'</span></li>'
|
|
1804
|
-
);
|
|
1805
|
-
})
|
|
1806
|
-
.join("") +
|
|
1807
|
-
'</ul>'
|
|
1808
|
-
);
|
|
1809
|
-
})()) +
|
|
1534
|
+
todosHtmlFromInfo(info, escA) +
|
|
1810
1535
|
resultHtml +
|
|
1811
1536
|
gateHtml;
|
|
1812
1537
|
if (isNewPanel) {
|
|
@@ -1816,7 +1541,7 @@ async function renderAfterStep(page, info) {
|
|
|
1816
1541
|
if (window.__wgWirePanelChrome) {
|
|
1817
1542
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1818
1543
|
stepLabel: miniStepLabel(info),
|
|
1819
|
-
forceCollapsed: info.forceCollapsed === true ? true :
|
|
1544
|
+
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1820
1545
|
});
|
|
1821
1546
|
}
|
|
1822
1547
|
panel.querySelectorAll(".wg-toggle-btn").forEach((btn) => {
|
|
@@ -1873,13 +1598,13 @@ async function renderStepError(page, info) {
|
|
|
1873
1598
|
if (!isNewPanel) panel.classList.add("wg-in");
|
|
1874
1599
|
const esc = (s) => String(s).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
1875
1600
|
const episodesHtml = info.allEpisodes && info.allEpisodes.length > 0
|
|
1876
|
-
? "<div id
|
|
1601
|
+
? "<div id=\\"wg-episodes\\">" +
|
|
1877
1602
|
info.allEpisodes
|
|
1878
1603
|
.map((e) => {
|
|
1879
1604
|
const cls = e.episodeNumber < info.episodeNumber ? "wg-ep-done"
|
|
1880
1605
|
: e.episodeNumber === info.episodeNumber ? "wg-ep-current"
|
|
1881
1606
|
: "wg-ep-upcoming";
|
|
1882
|
-
return "<span class
|
|
1607
|
+
return "<span class=\\"wg-ep-tab " + cls + "\\">Episode " + e.episodeNumber + ": " +
|
|
1883
1608
|
esc(e.episodeTitle || "") + "</span>";
|
|
1884
1609
|
})
|
|
1885
1610
|
.join("") +
|
|
@@ -1890,8 +1615,8 @@ async function renderStepError(page, info) {
|
|
|
1890
1615
|
.map((name, idx) => {
|
|
1891
1616
|
const cls = idx === info.moduleIndex ? "wg-mod-current" : idx < info.moduleIndex ? "wg-mod-done" : "wg-mod-upcoming";
|
|
1892
1617
|
const desc = info.allDescriptions && info.allDescriptions[idx];
|
|
1893
|
-
const titleAttr = desc ? " title
|
|
1894
|
-
return "<span class
|
|
1618
|
+
const titleAttr = desc ? " title=\\"" + esc(desc) + "\\"" : "";
|
|
1619
|
+
return "<span class=\\"wg-mod " + cls + "\\"" + titleAttr + ">" + name + "</span>";
|
|
1895
1620
|
})
|
|
1896
1621
|
.join("");
|
|
1897
1622
|
const retryLabel = info.episodeNumber !== undefined ? "Retry Episode " + info.episodeNumber : "Retry";
|
|
@@ -1899,17 +1624,17 @@ async function renderStepError(page, info) {
|
|
|
1899
1624
|
? "Expected outcome - Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + " failed as intended"
|
|
1900
1625
|
: "Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + " threw";
|
|
1901
1626
|
const reasonHtml = isExpected
|
|
1902
|
-
? "<div class
|
|
1627
|
+
? "<div class=\\"wg-expected-reason\\">" + esc(info.expectedFailureReason) + "</div>"
|
|
1903
1628
|
: "";
|
|
1904
1629
|
panel.innerHTML =
|
|
1905
1630
|
episodesHtml +
|
|
1906
|
-
"<div id
|
|
1907
|
-
"<h3 class
|
|
1631
|
+
"<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
|
|
1632
|
+
"<h3 class=\\"" + (isExpected ? "wg-expected-heading" : "wg-error-heading") + "\\">" + headingText + "</h3>" +
|
|
1908
1633
|
reasonHtml +
|
|
1909
|
-
"<div class
|
|
1910
|
-
"<div class
|
|
1911
|
-
"<button id
|
|
1912
|
-
"<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>" +
|
|
1913
1638
|
"</div>";
|
|
1914
1639
|
if (isNewPanel) {
|
|
1915
1640
|
document.documentElement.appendChild(panel);
|
|
@@ -1918,7 +1643,7 @@ async function renderStepError(page, info) {
|
|
|
1918
1643
|
if (window.__wgWirePanelChrome) {
|
|
1919
1644
|
window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
|
|
1920
1645
|
stepLabel: miniStepLabel(info),
|
|
1921
|
-
forceCollapsed: info.forceCollapsed === true ? true :
|
|
1646
|
+
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1922
1647
|
});
|
|
1923
1648
|
}
|
|
1924
1649
|
const runBtn = document.getElementById("wg-run");
|
|
@@ -2041,6 +1766,11 @@ async function markStepRunning(page, _opts) {
|
|
|
2041
1766
|
const titleEl = panel.querySelector(".wg-chrome-title");
|
|
2042
1767
|
const label = panel.dataset.wgStepLabel;
|
|
2043
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
|
+
}
|
|
2044
1774
|
}
|
|
2045
1775
|
})
|
|
2046
1776
|
.catch(() => {});
|
|
@@ -2049,16 +1779,14 @@ async function markStepRunning(page, _opts) {
|
|
|
2049
1779
|
async function showRing(page, box, label, tone, style) {
|
|
2050
1780
|
const size = normalizeHighlightSize(style && style.size);
|
|
2051
1781
|
const weight = normalizeHighlightWeight(style && style.weight);
|
|
2052
|
-
const focus = !!(style && style.focus);
|
|
2053
|
-
const color = (style && (style.color || style.labelColor || style.textColor)) || "";
|
|
2054
1782
|
await page
|
|
2055
1783
|
.evaluate(
|
|
2056
|
-
({ box, label, tone, size, weight
|
|
1784
|
+
({ box, label, tone, size, weight }) => {
|
|
2057
1785
|
if (window.__wgPositionRing) {
|
|
2058
|
-
window.__wgPositionRing(box, label, tone || "planned", { size, weight
|
|
1786
|
+
window.__wgPositionRing(box, label, tone || "planned", { size, weight });
|
|
2059
1787
|
}
|
|
2060
1788
|
},
|
|
2061
|
-
{ box, label, tone: tone || "planned", size, weight
|
|
1789
|
+
{ box, label, tone: tone || "planned", size, weight },
|
|
2062
1790
|
)
|
|
2063
1791
|
.catch(() => {});
|
|
2064
1792
|
}
|
|
@@ -2148,10 +1876,7 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2148
1876
|
if (s.selector) {
|
|
2149
1877
|
try {
|
|
2150
1878
|
await ensureSelectorInView(page, s.selector);
|
|
2151
|
-
|
|
2152
|
-
const zb = await page.locator(s.selector).first().boundingBox().catch(() => null);
|
|
2153
|
-
await applyCameraZoom(page, zb, s.zoom);
|
|
2154
|
-
}
|
|
1879
|
+
await applyHighlightZoom(page, s.selector, s.zoom);
|
|
2155
1880
|
const box = await page.locator(s.selector).first().boundingBox();
|
|
2156
1881
|
if (box) await showRing(page, box, caption, slideTone, slideStyle);
|
|
2157
1882
|
} catch {
|
|
@@ -2168,30 +1893,32 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2168
1893
|
panel = document.createElement("div");
|
|
2169
1894
|
panel.id = "wg-panel";
|
|
2170
1895
|
}
|
|
2171
|
-
panel.classList.remove("wg-
|
|
1896
|
+
panel.classList.remove("wg-error", "wg-expected");
|
|
1897
|
+
// Do not clear wg-collapsed - WirePanelChrome honors Hide (localStorage)
|
|
1898
|
+
// unless forceCollapsed:true (--mini / --video).
|
|
2172
1899
|
const esc = (t) =>
|
|
2173
1900
|
String(t).replace(/&/g, "&").replace(/</g, "<").replace(/"/g, """);
|
|
2174
1901
|
const episodeLine =
|
|
2175
1902
|
info.episodeNumber !== undefined && info.episodeNumber !== null
|
|
2176
|
-
? "<div class
|
|
1903
|
+
? "<div class=\\"wg-episode\\">Episode " +
|
|
2177
1904
|
info.episodeNumber +
|
|
2178
1905
|
(info.episodeTitle ? ": " + esc(info.episodeTitle) : "") +
|
|
2179
1906
|
" · yap slides</div>"
|
|
2180
1907
|
: "";
|
|
2181
1908
|
const paceHtml =
|
|
2182
1909
|
info.paceBadge || info.paceLabel
|
|
2183
|
-
? "<div class
|
|
1910
|
+
? "<div class=\\"wg-pace\\" data-pace-kind=\\"" +
|
|
2184
1911
|
esc(info.paceKind || "normal") +
|
|
2185
|
-
"
|
|
1912
|
+
"\\"><span class=\\"wg-pace-badge\\">" +
|
|
2186
1913
|
esc(info.paceBadge || "1x") +
|
|
2187
1914
|
"</span><span>" +
|
|
2188
1915
|
esc(info.paceLabel || "") +
|
|
2189
1916
|
"</span></div>"
|
|
2190
1917
|
: "";
|
|
2191
1918
|
panel.innerHTML =
|
|
2192
|
-
"<div class
|
|
2193
|
-
"<button type
|
|
2194
|
-
"<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\\">" +
|
|
2195
1922
|
episodeLine +
|
|
2196
1923
|
paceHtml +
|
|
2197
1924
|
"<h3>Slide " +
|
|
@@ -2201,35 +1928,35 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2201
1928
|
(info.blockName ? " · " + esc(info.blockName) : "") +
|
|
2202
1929
|
"</h3>" +
|
|
2203
1930
|
(info.tag
|
|
2204
|
-
? "<div style
|
|
1931
|
+
? "<div style=\\"border:none;padding:0;margin:0 0 6px;font-size:11px;color:#c9a6ff\\">" +
|
|
2205
1932
|
esc(info.tag) +
|
|
2206
1933
|
"</div>"
|
|
2207
1934
|
: "") +
|
|
2208
|
-
"<div class
|
|
1935
|
+
"<div class=\\"wg-narration\\">" +
|
|
2209
1936
|
esc(info.caption) +
|
|
2210
1937
|
"</div>" +
|
|
2211
1938
|
(info.detail
|
|
2212
|
-
? "<div class
|
|
1939
|
+
? "<div class=\\"wg-result-pretty\\" style=\\"margin:0 0 12px;color:#f0e8ff\\">" +
|
|
2213
1940
|
esc(info.detail) +
|
|
2214
1941
|
"</div>"
|
|
2215
1942
|
: "") +
|
|
2216
1943
|
(info.dwellMs != null
|
|
2217
|
-
? "<div class
|
|
1944
|
+
? "<div class=\\"wg-auto\\" id=\\"wg-dwell-hint\\" style=\\"margin:0 0 8px\\">Hold " +
|
|
2218
1945
|
(info.dwellMs / 1000).toFixed(1) +
|
|
2219
1946
|
"s before next...</div>"
|
|
2220
1947
|
: "") +
|
|
2221
|
-
"<div class
|
|
1948
|
+
"<div class=\\"wg-autoplay-row\\"><label><input type=\\"checkbox\\" id=\\"wg-autoplay-cb\\"" +
|
|
2222
1949
|
(info.autoNow ? " checked" : "") +
|
|
2223
1950
|
"> Auto-advance</label></div>" +
|
|
2224
|
-
"<div id
|
|
2225
|
-
(info.autoNow ? " style
|
|
2226
|
-
"><button type
|
|
1951
|
+
"<div id=\\"wg-gate-manual\\"" +
|
|
1952
|
+
(info.autoNow ? " style=\\"display:none\\"" : "") +
|
|
1953
|
+
"><button type=\\"button\\" id=\\"wg-run\\"" +
|
|
2227
1954
|
(info.dwellMs != null ? " disabled" : "") +
|
|
2228
1955
|
">" +
|
|
2229
1956
|
(info.isLast ? "Continue \\u25B6" : "Next slide \\u25B6") +
|
|
2230
1957
|
"</button></div>" +
|
|
2231
|
-
"<div id
|
|
2232
|
-
(info.autoNow ? "" : " style
|
|
1958
|
+
"<div id=\\"wg-gate-auto\\" class=\\"wg-auto\\"" +
|
|
1959
|
+
(info.autoNow ? "" : " style=\\"display:none\\"") +
|
|
2233
1960
|
">Auto-advancing...</div>" +
|
|
2234
1961
|
"</div>";
|
|
2235
1962
|
if (isNew) {
|
|
@@ -2247,7 +1974,8 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2247
1974
|
" / " +
|
|
2248
1975
|
info.total +
|
|
2249
1976
|
(info.blockName ? " \\u00b7 " + info.blockName : ""),
|
|
2250
|
-
|
|
1977
|
+
// Video / --mini: compact pill. Else omit so Hide sticks.
|
|
1978
|
+
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
2251
1979
|
});
|
|
2252
1980
|
}
|
|
2253
1981
|
let autoNow = false;
|
|
@@ -2317,6 +2045,12 @@ async function presentSlides(page, slides, _gate, opts) {
|
|
|
2317
2045
|
paceBadge: formatDemoPaceBadge(demoPace, baseAutoplay),
|
|
2318
2046
|
paceLabel: formatDemoPaceLabel(demoPace, baseAutoplay),
|
|
2319
2047
|
paceKind: demoPaceKind(demoPace),
|
|
2048
|
+
forceCollapsed:
|
|
2049
|
+
!!process.env.WAYGRAPH_VIDEO ||
|
|
2050
|
+
process.env.WAYGRAPH_MINI === "1" ||
|
|
2051
|
+
process.env.WAYGRAPH_STEPPER_MINI === "1"
|
|
2052
|
+
? true
|
|
2053
|
+
: undefined,
|
|
2320
2054
|
},
|
|
2321
2055
|
);
|
|
2322
2056
|
const started = Date.now();
|
|
@@ -2394,8 +2128,7 @@ async function teardownOverlay(page) {
|
|
|
2394
2128
|
window.__wgRingTrack = null;
|
|
2395
2129
|
}
|
|
2396
2130
|
window.__wgNarrateOwnsRing = false;
|
|
2397
|
-
|
|
2398
|
-
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"]) {
|
|
2399
2132
|
const el = document.getElementById(id);
|
|
2400
2133
|
if (el) el.remove();
|
|
2401
2134
|
}
|
|
@@ -2403,53 +2136,24 @@ async function teardownOverlay(page) {
|
|
|
2403
2136
|
.catch(() => {});
|
|
2404
2137
|
}
|
|
2405
2138
|
|
|
2406
|
-
async function moveCursorTo(page, box, ms
|
|
2139
|
+
async function moveCursorTo(page, box, ms) {
|
|
2407
2140
|
const x = box.x + box.width / 2;
|
|
2408
2141
|
const y = box.y + box.height / 2;
|
|
2409
2142
|
const travel = Math.max(0, Number(ms) || 0);
|
|
2410
|
-
const keepLock = !!(opts && opts.keepLock);
|
|
2411
|
-
// Camera stays locked; demo cursor + Playwright real mouse move together.
|
|
2412
|
-
// Host OS mouse is ignored (no mousemove camera steer).
|
|
2413
2143
|
await page
|
|
2414
2144
|
.evaluate(({ x, y, ms }) => {
|
|
2415
|
-
window.__wgCameraLock = true;
|
|
2416
|
-
window.__wgIgnoreHostMouse = true;
|
|
2417
2145
|
if (window.__wgMoveCursorTo) window.__wgMoveCursorTo(x, y, ms);
|
|
2418
2146
|
}, { x, y, ms: travel })
|
|
2419
2147
|
.catch(() => {});
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
await page.mouse.move(x, y, { steps });
|
|
2424
|
-
} else {
|
|
2425
|
-
await page.mouse.move(x, y);
|
|
2426
|
-
}
|
|
2427
|
-
} catch {
|
|
2428
|
-
/* headed / detached - demo cursor still moved */
|
|
2429
|
-
}
|
|
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).
|
|
2430
2151
|
if (travel > 0) {
|
|
2431
|
-
await new Promise((res) => setTimeout(res,
|
|
2432
|
-
}
|
|
2433
|
-
if (!keepLock) {
|
|
2434
|
-
await page
|
|
2435
|
-
.evaluate(() => {
|
|
2436
|
-
window.__wgCameraLock = false;
|
|
2437
|
-
})
|
|
2438
|
-
.catch(() => {});
|
|
2152
|
+
await new Promise((res) => setTimeout(res, travel));
|
|
2439
2153
|
}
|
|
2440
2154
|
return { x, y };
|
|
2441
2155
|
}
|
|
2442
2156
|
|
|
2443
|
-
/** Live viewport center of a locator (after camera transform) - no scroll. */
|
|
2444
|
-
async function liveCenterOf(locator) {
|
|
2445
|
-
return locator
|
|
2446
|
-
.evaluate((el) => {
|
|
2447
|
-
const r = el.getBoundingClientRect();
|
|
2448
|
-
return { x: r.left + r.width / 2, y: r.top + r.height / 2, w: r.width, h: r.height };
|
|
2449
|
-
})
|
|
2450
|
-
.catch(() => null);
|
|
2451
|
-
}
|
|
2452
|
-
|
|
2453
2157
|
async function clickPulseAt(page, x, y, tone) {
|
|
2454
2158
|
await page
|
|
2455
2159
|
.evaluate(({ x, y, tone }) => {
|
|
@@ -2574,47 +2278,17 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2574
2278
|
proto.click = async function (options) {
|
|
2575
2279
|
let clickPoint = null;
|
|
2576
2280
|
let clickTone = "auto";
|
|
2577
|
-
let keptCameraLock = false;
|
|
2578
2281
|
try {
|
|
2579
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.
|
|
2580
2287
|
await this.waitFor({
|
|
2581
2288
|
state: "visible",
|
|
2582
2289
|
timeout: (options && options.timeout) || 30000,
|
|
2583
2290
|
}).catch(() => {});
|
|
2584
|
-
const
|
|
2585
|
-
.evaluate(() => !!(window.__wgCamera && window.__wgCamera.scale > 1.001))
|
|
2586
|
-
.catch(() => false);
|
|
2587
|
-
if (zoomed) {
|
|
2588
|
-
// Re-auto-center on this target, then lock - no host mouse, no scroll.
|
|
2589
|
-
await page
|
|
2590
|
-
.evaluate(() => {
|
|
2591
|
-
window.__wgCameraLock = true;
|
|
2592
|
-
window.__wgIgnoreHostMouse = true;
|
|
2593
|
-
})
|
|
2594
|
-
.catch(() => {});
|
|
2595
|
-
keptCameraLock = true;
|
|
2596
|
-
const handle = await this.elementHandle().catch(() => null);
|
|
2597
|
-
if (handle) {
|
|
2598
|
-
await page
|
|
2599
|
-
.evaluate(
|
|
2600
|
-
(el) => {
|
|
2601
|
-
if (!el || !window.__wgSetCamera || !window.__wgElDocPoint) return;
|
|
2602
|
-
const cam = window.__wgCamera || { scale: 1.4 };
|
|
2603
|
-
const pt = window.__wgElDocPoint(el);
|
|
2604
|
-
const s = cam.scale > 1.001 ? cam.scale : 1.4;
|
|
2605
|
-
window.__wgSetCamera(pt.x, pt.y, s, 280);
|
|
2606
|
-
},
|
|
2607
|
-
handle,
|
|
2608
|
-
)
|
|
2609
|
-
.catch(() => {});
|
|
2610
|
-
await handle.dispose().catch(() => {});
|
|
2611
|
-
await new Promise((res) => setTimeout(res, 300));
|
|
2612
|
-
}
|
|
2613
|
-
}
|
|
2614
|
-
const live = await liveCenterOf(this);
|
|
2615
|
-
const box = live
|
|
2616
|
-
? { x: live.x - live.w / 2, y: live.y - live.h / 2, width: live.w, height: live.h }
|
|
2617
|
-
: await this.boundingBox();
|
|
2291
|
+
const box = await this.boundingBox();
|
|
2618
2292
|
const narrated = box ? await wasJustNarrated(page) : false;
|
|
2619
2293
|
if (box && !narrated) {
|
|
2620
2294
|
let fallback = "click";
|
|
@@ -2622,8 +2296,10 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2622
2296
|
const text = (await this.textContent())?.trim();
|
|
2623
2297
|
if (text && text.length > 0 && text.length <= 30) fallback = text;
|
|
2624
2298
|
} catch {
|
|
2625
|
-
|
|
2299
|
+
// element has no simple text (an icon button, say) - generic label is fine
|
|
2626
2300
|
}
|
|
2301
|
+
// Prefer "nav: …" when this locator is a NavBlock click target
|
|
2302
|
+
// (set on page by runStepMode just before act).
|
|
2627
2303
|
try {
|
|
2628
2304
|
const navLabel = await page.evaluate(() => window.__wgPendingNavClickLabel || null);
|
|
2629
2305
|
if (navLabel) fallback = String(navLabel);
|
|
@@ -2633,80 +2309,39 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2633
2309
|
const stubs = (stubBeforeRef && stubBeforeRef.current) || [];
|
|
2634
2310
|
const cap = await captionForLocator(page, this, stubs, fallback);
|
|
2635
2311
|
clickTone = cap.tone;
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
const aim = again
|
|
2639
|
-
? { x: again.x - again.w / 2, y: again.y - again.h / 2, width: again.w, height: again.h }
|
|
2640
|
-
: box;
|
|
2641
|
-
clickPoint = await moveCursorTo(page, aim, travel, { keepLock: zoomed });
|
|
2642
|
-
const liveBox = aim;
|
|
2643
|
-
clickPoint = {
|
|
2644
|
-
x: liveBox.x + liveBox.width / 2,
|
|
2645
|
-
y: liveBox.y + liveBox.height / 2,
|
|
2646
|
-
};
|
|
2647
|
-
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, {
|
|
2648
2314
|
size: cap.size,
|
|
2649
2315
|
weight: cap.weight,
|
|
2650
2316
|
});
|
|
2651
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.
|
|
2652
2321
|
await new Promise((res) => setTimeout(res, clickPrePop()));
|
|
2653
2322
|
} else if (box) {
|
|
2654
|
-
|
|
2655
|
-
|
|
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));
|
|
2656
2326
|
clickTone = "planned";
|
|
2657
2327
|
}
|
|
2658
2328
|
} catch {
|
|
2659
|
-
|
|
2329
|
+
// best-effort - the real click below still runs either way
|
|
2660
2330
|
}
|
|
2661
2331
|
if (clickPoint) {
|
|
2662
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.
|
|
2663
2339
|
await new Promise((res) => setTimeout(res, skipTheater() ? 0 : 500));
|
|
2664
2340
|
}
|
|
2665
|
-
const
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
let result;
|
|
2670
|
-
if (zoomedClick || keptCameraLock) {
|
|
2671
|
-
// Real Playwright mouse at live visual center - never scrollIntoView.
|
|
2672
|
-
const pt =
|
|
2673
|
-
clickPoint ||
|
|
2674
|
-
(await liveCenterOf(this)) ||
|
|
2675
|
-
null;
|
|
2676
|
-
if (pt && Number.isFinite(pt.x) && Number.isFinite(pt.y)) {
|
|
2677
|
-
await page.mouse.click(pt.x, pt.y);
|
|
2678
|
-
result = undefined;
|
|
2679
|
-
} else {
|
|
2680
|
-
const handle = await this.elementHandle().catch(() => null);
|
|
2681
|
-
if (handle) {
|
|
2682
|
-
await handle.evaluate((el) => {
|
|
2683
|
-
if (el && typeof el.click === "function") el.click();
|
|
2684
|
-
});
|
|
2685
|
-
await handle.dispose().catch(() => {});
|
|
2686
|
-
result = undefined;
|
|
2687
|
-
} else {
|
|
2688
|
-
result = await originalClick.call(this, {
|
|
2689
|
-
...(options || {}),
|
|
2690
|
-
force: true,
|
|
2691
|
-
timeout: (options && options.timeout) || 5000,
|
|
2692
|
-
});
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2695
|
-
} else {
|
|
2696
|
-
result = await originalClick.call(this, options);
|
|
2697
|
-
}
|
|
2698
|
-
await new Promise((res) => setTimeout(res, clickPostPop()));
|
|
2699
|
-
await hideRing(page);
|
|
2700
|
-
return result;
|
|
2701
|
-
} finally {
|
|
2702
|
-
if (keptCameraLock) {
|
|
2703
|
-
await page
|
|
2704
|
-
.evaluate(() => {
|
|
2705
|
-
window.__wgCameraLock = false;
|
|
2706
|
-
})
|
|
2707
|
-
.catch(() => {});
|
|
2708
|
-
}
|
|
2709
|
-
}
|
|
2341
|
+
const result = await originalClick.call(this, options);
|
|
2342
|
+
await new Promise((res) => setTimeout(res, clickPostPop()));
|
|
2343
|
+
await hideRing(page);
|
|
2344
|
+
return result;
|
|
2710
2345
|
};
|
|
2711
2346
|
}
|
|
2712
2347
|
|
|
@@ -3020,10 +2655,13 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3020
2655
|
const stubBeforeTodos = stubBeforePhase.todos || [];
|
|
3021
2656
|
const isNavBlock = r.block.__waygraphKind === "nav";
|
|
3022
2657
|
const autoNow = await currentAutoplay();
|
|
3023
|
-
// Video
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
2658
|
+
// Video / --mini: compact pill. Never pass false - Hide/localStorage wins.
|
|
2659
|
+
const forceCollapsed =
|
|
2660
|
+
!!process.env.WAYGRAPH_VIDEO ||
|
|
2661
|
+
process.env.WAYGRAPH_MINI === "1" ||
|
|
2662
|
+
process.env.WAYGRAPH_STEPPER_MINI === "1"
|
|
2663
|
+
? true
|
|
2664
|
+
: undefined;
|
|
3027
2665
|
const requires = r.block.requires ?? [];
|
|
3028
2666
|
const keys = requires.map((k) => {
|
|
3029
2667
|
let value = "<not yet set>";
|
|
@@ -3067,7 +2705,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3067
2705
|
todos: stubBeforeTodos,
|
|
3068
2706
|
...paceSpeak,
|
|
3069
2707
|
});
|
|
3070
|
-
await setDemoTodos(page, stubBeforeTodos);
|
|
3071
2708
|
const edits = await gate();
|
|
3072
2709
|
for (const k of requires) {
|
|
3073
2710
|
if (edits[k.name] !== undefined) {
|
|
@@ -3079,40 +2716,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3079
2716
|
}
|
|
3080
2717
|
}
|
|
3081
2718
|
const recordingVideo = !!process.env.WAYGRAPH_VIDEO;
|
|
3082
|
-
// Sequential stubBefore queue (appear -> dwell -> fade) before act.
|
|
3083
|
-
if (stubBeforeRef.current && stubBeforeRef.current.length) {
|
|
3084
|
-
await setDemoBannerTitle(page, stubBeforePhase.title);
|
|
3085
|
-
const beforeRings = stubBeforeRef.current.map((h) => {
|
|
3086
|
-
const styled = applyHighlightStyleDefaults(h, flowStyle);
|
|
3087
|
-
return {
|
|
3088
|
-
selector: styled.selector,
|
|
3089
|
-
label: formatHighlightCaption(styled),
|
|
3090
|
-
duration: styled.duration,
|
|
3091
|
-
fastMode: styled.fastMode,
|
|
3092
|
-
tone: styled.tone,
|
|
3093
|
-
size: styled.size,
|
|
3094
|
-
weight: styled.weight,
|
|
3095
|
-
zoom: styled.zoom,
|
|
3096
|
-
focus: styled.focus,
|
|
3097
|
-
color: styled.color,
|
|
3098
|
-
cursor: styled.cursor,
|
|
3099
|
-
zoomOut: styled.zoomOut,
|
|
3100
|
-
};
|
|
3101
|
-
});
|
|
3102
|
-
await cycleHighlightRings(page, beforeRings, !!pacing.gatesFast, {
|
|
3103
|
-
pace: pacing.demoPace,
|
|
3104
|
-
todos: stubBeforeTodos,
|
|
3105
|
-
stepLabel: miniStepLabel({
|
|
3106
|
-
index: i,
|
|
3107
|
-
total: resolved.length,
|
|
3108
|
-
blockName: r.block.name,
|
|
3109
|
-
episodeNumber: r.episodeNumber,
|
|
3110
|
-
paceBadge: paceSpeak.paceBadge,
|
|
3111
|
-
}),
|
|
3112
|
-
});
|
|
3113
|
-
} else if (stubBeforePhase && stubBeforePhase.title) {
|
|
3114
|
-
await setDemoBannerTitle(page, stubBeforePhase.title);
|
|
3115
|
-
}
|
|
3116
2719
|
await markStepRunning(page, {});
|
|
3117
2720
|
// NavBlock click-nav: label the upcoming Locator.click demo cursor as
|
|
3118
2721
|
// "nav: <block>" so pia/demo watchers see cursor+pulse on click nav
|
|
@@ -3162,7 +2765,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3162
2765
|
fixtures: fixturesOnError,
|
|
3163
2766
|
error: err,
|
|
3164
2767
|
});
|
|
3165
|
-
await setDemoBannerTitle(page, errPhase.title);
|
|
3166
2768
|
const errHighlights = errPhase.highlights.map((h) => {
|
|
3167
2769
|
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
3168
2770
|
return {
|
|
@@ -3174,10 +2776,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3174
2776
|
size: styled.size,
|
|
3175
2777
|
weight: styled.weight,
|
|
3176
2778
|
zoom: styled.zoom,
|
|
3177
|
-
focus: styled.focus,
|
|
3178
|
-
color: styled.color,
|
|
3179
|
-
cursor: styled.cursor,
|
|
3180
|
-
zoomOut: styled.zoomOut,
|
|
3181
2779
|
};
|
|
3182
2780
|
});
|
|
3183
2781
|
await cycleHighlightRings(page, errHighlights, !!pacing.gatesFast, {
|
|
@@ -3204,7 +2802,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3204
2802
|
episodeTitle: r.episodeTitle,
|
|
3205
2803
|
expectedFailureReason: r.expectedFailureReason,
|
|
3206
2804
|
stepperMode,
|
|
3207
|
-
forceCollapsed
|
|
2805
|
+
// Omit forceCollapsed on errors so Hide is not stomped.
|
|
3208
2806
|
});
|
|
3209
2807
|
const errEdits = await gate();
|
|
3210
2808
|
if (errEdits && errEdits.__wgRetry) {
|
|
@@ -3256,8 +2854,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3256
2854
|
fixtures: fixturesAfter,
|
|
3257
2855
|
});
|
|
3258
2856
|
stubAfterTodos = afterPhase.todos || [];
|
|
3259
|
-
await setDemoBannerTitle(page, afterPhase.title);
|
|
3260
|
-
await setDemoTodos(page, afterPhase.todos || []);
|
|
3261
2857
|
highlights = afterPhase.highlights.map((h) => {
|
|
3262
2858
|
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
3263
2859
|
return {
|
|
@@ -3269,10 +2865,6 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3269
2865
|
size: styled.size,
|
|
3270
2866
|
weight: styled.weight,
|
|
3271
2867
|
zoom: styled.zoom,
|
|
3272
|
-
focus: styled.focus,
|
|
3273
|
-
color: styled.color,
|
|
3274
|
-
cursor: styled.cursor,
|
|
3275
|
-
zoomOut: styled.zoomOut,
|
|
3276
2868
|
};
|
|
3277
2869
|
});
|
|
3278
2870
|
} else {
|
|
@@ -3300,7 +2892,12 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3300
2892
|
episodeTitle: r.episodeTitle,
|
|
3301
2893
|
stepperMode,
|
|
3302
2894
|
todos: stubAfterTodos,
|
|
3303
|
-
forceCollapsed:
|
|
2895
|
+
forceCollapsed:
|
|
2896
|
+
!!process.env.WAYGRAPH_VIDEO ||
|
|
2897
|
+
process.env.WAYGRAPH_MINI === "1" ||
|
|
2898
|
+
process.env.WAYGRAPH_STEPPER_MINI === "1"
|
|
2899
|
+
? true
|
|
2900
|
+
: undefined,
|
|
3304
2901
|
});
|
|
3305
2902
|
await gate();
|
|
3306
2903
|
}
|
|
@@ -4315,6 +3912,9 @@ function parseRunFlags(argv) {
|
|
|
4315
3912
|
else if (a === "--full") {
|
|
4316
3913
|
out.fullStepper = true;
|
|
4317
3914
|
}
|
|
3915
|
+
else if (a === "--mini" || a === "--stepper-mini") {
|
|
3916
|
+
out.miniStepper = true;
|
|
3917
|
+
}
|
|
4318
3918
|
else if (a === "--ff-expand") {
|
|
4319
3919
|
out.ffExpand = true;
|
|
4320
3920
|
}
|
|
@@ -4435,8 +4035,8 @@ function applyRunFlags(flags, opts) {
|
|
|
4435
4035
|
process.env.WAYGRAPH_HEADED = "0";
|
|
4436
4036
|
}
|
|
4437
4037
|
}
|
|
4438
|
-
if ((flags.fast || flags.fullStepper) && !opts?.allowDemoUi) {
|
|
4439
|
-
console.error("waygraph: --fast / --full are demo-only flags");
|
|
4038
|
+
if ((flags.fast || flags.fullStepper || flags.miniStepper) && !opts?.allowDemoUi) {
|
|
4039
|
+
console.error("waygraph: --fast / --full / --mini are demo-only flags");
|
|
4440
4040
|
process.exit(1);
|
|
4441
4041
|
}
|
|
4442
4042
|
if (flags.step === true) {
|
|
@@ -4480,6 +4080,9 @@ function applyRunFlags(flags, opts) {
|
|
|
4480
4080
|
if (flags.fullStepper) {
|
|
4481
4081
|
process.env.WAYGRAPH_STEPPER = "full";
|
|
4482
4082
|
}
|
|
4083
|
+
if (flags.miniStepper) {
|
|
4084
|
+
process.env.WAYGRAPH_MINI = "1";
|
|
4085
|
+
}
|
|
4483
4086
|
if (flags.ffExpand) {
|
|
4484
4087
|
process.env.WAYGRAPH_FF_EXPAND = "1";
|
|
4485
4088
|
}
|
|
@@ -4644,6 +4247,7 @@ Primary (less is more):
|
|
|
4644
4247
|
--auto-next Auto-advance steps (alias: --autoplay)
|
|
4645
4248
|
--fast Shorter auto-next / Next gates (keeps smooth cursor)
|
|
4646
4249
|
--full Classic wrap-all block chips (default: carousel)
|
|
4250
|
+
--mini Force compact mini panel (Hide pill + Next); alias --stepper-mini
|
|
4647
4251
|
--ff-expand Expand fastForwardComposeBlock inners as separate steps
|
|
4648
4252
|
--ff-disabled Dispute: expand FF (alias --no-ff); blitz kept on those inners
|
|
4649
4253
|
--auto-play-video Unattended + recorded: --auto-next + --video (+ step); headless by default
|
|
@@ -5038,7 +4642,7 @@ Agents shipped: waygraph-planner, waygraph-author, waygraph-healer.
|
|
|
5038
4642
|
console.error("waygraph demo: missing flow/spec — e.g.\n" +
|
|
5039
4643
|
" waygraph demo src/flows/shop.flow.ts\n" +
|
|
5040
4644
|
" waygraph demo --blocks shopFlow\n" +
|
|
5041
|
-
" Flags: --blocks --data --auto-next --fast --full --ff-expand --ff-disabled --auto-play-video --title --base-url --video");
|
|
4645
|
+
" Flags: --blocks --data --auto-next --fast --full --mini --ff-expand --ff-disabled --auto-play-video --title --base-url --video");
|
|
5042
4646
|
process.exit(1);
|
|
5043
4647
|
}
|
|
5044
4648
|
const proj = flags.blocks
|