waygraph 0.12.37 → 0.12.38

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.
Files changed (2) hide show
  1. package/dist/cli.js +83 -22
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -712,6 +712,7 @@ async function installOverlay(page, title) {
712
712
  if (!cursor) return;
713
713
  const travel = ms || 600;
714
714
  window.__wgCursorPos = { x: Number(x) || 0, y: Number(y) || 0 };
715
+ window.__wgCursorTravelUntil = Date.now() + (instant ? 0 : travel) + 40;
715
716
  cursor.style.setProperty("--wg-cursor-ms", travel + "ms");
716
717
  if (instant) {
717
718
  const prev = cursor.style.transition;
@@ -812,10 +813,8 @@ async function installOverlay(page, title) {
812
813
  let settled = zoom <= 1;
813
814
  const settleAt = Date.now() + camMs;
814
815
  const style = { size, weight, focus, color };
815
- // Screen Studio / Screenify: dead zone = center ~40% of viewport.
816
- // Pan only when the demo cursor leaves that zone - never hard-center
817
- // on every mousemove (that fights like same-pole magnets).
818
- const deadFrac = 0.4;
816
+ // Screen Studio / Screenify: dead zone follow (see followTick).
817
+ const deadFrac = 0.5;
819
818
  let lastPanAt = 0;
820
819
  const readCursorViewport = () => {
821
820
  const cursor = document.getElementById("wg-cursor");
@@ -833,6 +832,11 @@ async function installOverlay(page, title) {
833
832
  if (!window.__wgSetCamera || !window.__wgViewportToDoc) return;
834
833
  const cam = window.__wgCamera;
835
834
  if (!cam || !(cam.scale > 1.001)) return;
835
+ // Skip while the demo cursor is mid CSS travel - reading a
836
+ // mid-lerp rect makes the camera chase noise and teleport.
837
+ if (window.__wgCursorTravelUntil && Date.now() < window.__wgCursorTravelUntil) {
838
+ return;
839
+ }
836
840
  const pos = readCursorViewport();
837
841
  if (!pos) return;
838
842
  const vw = window.innerWidth;
@@ -849,12 +853,13 @@ async function installOverlay(page, title) {
849
853
  else if (dy < -deadY) oy = dy + deadY;
850
854
  if (ox === 0 && oy === 0) return;
851
855
  const now = Date.now();
852
- if (now - lastPanAt < 80) return;
856
+ if (now - lastPanAt < 120) return;
853
857
  lastPanAt = now;
854
- // Viewport overflow -> document delta (divide by scale).
855
- const nx = cam.cx + ox / cam.scale;
856
- const ny = cam.cy + oy / cam.scale;
857
- window.__wgSetCamera(nx, ny, cam.scale, 220);
858
+ // Partial gain - never yank the full overflow in one pan.
859
+ const gain = 0.35;
860
+ const nx = cam.cx + (ox / cam.scale) * gain;
861
+ const ny = cam.cy + (oy / cam.scale) * gain;
862
+ window.__wgSetCamera(nx, ny, cam.scale, 280);
858
863
  };
859
864
  const tick = () => {
860
865
  const el = document.querySelector(sel);
@@ -1708,6 +1713,14 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1708
1713
  const last = list.length > 0 ? list[list.length - 1] : null;
1709
1714
  if (!last || last.zoomOut !== false) {
1710
1715
  await resetCameraZoom(page);
1716
+ } else {
1717
+ // Hold lock into the upcoming act click so follow/scroll cannot yank
1718
+ // the camera off the already-centered target (cart teleport bug).
1719
+ await page
1720
+ .evaluate(() => {
1721
+ window.__wgCameraLock = true;
1722
+ })
1723
+ .catch(() => {});
1711
1724
  }
1712
1725
  if (list.length === 0) {
1713
1726
  await hideRing(page);
@@ -2444,10 +2457,11 @@ async function teardownOverlay(page) {
2444
2457
  .catch(() => {});
2445
2458
  }
2446
2459
 
2447
- async function moveCursorTo(page, box, ms) {
2460
+ async function moveCursorTo(page, box, ms, opts) {
2448
2461
  const x = box.x + box.width / 2;
2449
2462
  const y = box.y + box.height / 2;
2450
2463
  const travel = Math.max(0, Number(ms) || 0);
2464
+ const keepLock = !!(opts && opts.keepLock);
2451
2465
  // Freeze camera follow while the demo cursor travels - otherwise dead-zone
2452
2466
  // pans fight the click target (esp. right-edge cart chrome).
2453
2467
  await page
@@ -2462,11 +2476,13 @@ async function moveCursorTo(page, box, ms) {
2462
2476
  if (travel > 0) {
2463
2477
  await new Promise((res) => setTimeout(res, travel));
2464
2478
  }
2465
- await page
2466
- .evaluate(() => {
2467
- window.__wgCameraLock = false;
2468
- })
2469
- .catch(() => {});
2479
+ if (!keepLock) {
2480
+ await page
2481
+ .evaluate(() => {
2482
+ window.__wgCameraLock = false;
2483
+ })
2484
+ .catch(() => {});
2485
+ }
2470
2486
  return { x, y };
2471
2487
  }
2472
2488
 
@@ -2594,6 +2610,7 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
2594
2610
  proto.click = async function (options) {
2595
2611
  let clickPoint = null;
2596
2612
  let clickTone = "auto";
2613
+ let keptCameraLock = false;
2597
2614
  try {
2598
2615
  await installOverlay(page);
2599
2616
  // Wait until the target is visible BEFORE measuring - otherwise
@@ -2604,6 +2621,20 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
2604
2621
  state: "visible",
2605
2622
  timeout: (options && options.timeout) || 30000,
2606
2623
  }).catch(() => {});
2624
+ // Body-scale camera + Playwright scrollIntoView = teleport storm.
2625
+ // Lock the camera for the whole click when zoomed; force-click so
2626
+ // Playwright does not try to scroll the transformed page.
2627
+ const zoomed = await page
2628
+ .evaluate(() => !!(window.__wgCamera && window.__wgCamera.scale > 1.001))
2629
+ .catch(() => false);
2630
+ if (zoomed) {
2631
+ await page
2632
+ .evaluate(() => {
2633
+ window.__wgCameraLock = true;
2634
+ })
2635
+ .catch(() => {});
2636
+ keptCameraLock = true;
2637
+ }
2607
2638
  const box = await this.boundingBox();
2608
2639
  const narrated = box ? await wasJustNarrated(page) : false;
2609
2640
  if (box && !narrated) {
@@ -2625,8 +2656,18 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
2625
2656
  const stubs = (stubBeforeRef && stubBeforeRef.current) || [];
2626
2657
  const cap = await captionForLocator(page, this, stubs, fallback);
2627
2658
  clickTone = cap.tone;
2628
- clickPoint = await moveCursorTo(page, box, cursorMs(600));
2629
- await showRing(page, box, cap.label, cap.tone, {
2659
+ // Already centered from stubBefore zoom: short cursor, keep lock.
2660
+ const travel = zoomed ? cursorMs(160) : cursorMs(600);
2661
+ clickPoint = await moveCursorTo(page, box, travel, {
2662
+ keepLock: zoomed,
2663
+ });
2664
+ // Re-measure after cursor settle - stale pre-travel box teleports the ring.
2665
+ const liveBox = (await this.boundingBox()) || box;
2666
+ clickPoint = {
2667
+ x: liveBox.x + liveBox.width / 2,
2668
+ y: liveBox.y + liveBox.height / 2,
2669
+ };
2670
+ await showRing(page, liveBox, cap.label, cap.tone, {
2630
2671
  size: cap.size,
2631
2672
  weight: cap.weight,
2632
2673
  });
@@ -2638,7 +2679,10 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
2638
2679
  } else if (box) {
2639
2680
  // Ring/caption already handled by narrate() - still move the
2640
2681
  // cursor + pulse the click point, just skip re-showing the ring.
2641
- clickPoint = await moveCursorTo(page, box, cursorMs(200));
2682
+ const travel = zoomed ? cursorMs(120) : cursorMs(200);
2683
+ clickPoint = await moveCursorTo(page, box, travel, {
2684
+ keepLock: zoomed,
2685
+ });
2642
2686
  clickTone = "planned";
2643
2687
  }
2644
2688
  } catch {
@@ -2654,10 +2698,27 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
2654
2698
  // mock click visually completes before the real one fires.
2655
2699
  await new Promise((res) => setTimeout(res, skipTheater() ? 0 : 500));
2656
2700
  }
2657
- const result = await originalClick.call(this, options);
2658
- await new Promise((res) => setTimeout(res, clickPostPop()));
2659
- await hideRing(page);
2660
- return result;
2701
+ const zoomedClick = await page
2702
+ .evaluate(() => !!(window.__wgCamera && window.__wgCamera.scale > 1.001))
2703
+ .catch(() => false);
2704
+ const clickOpts =
2705
+ zoomedClick || keptCameraLock
2706
+ ? { ...(options || {}), force: true }
2707
+ : options;
2708
+ try {
2709
+ const result = await originalClick.call(this, clickOpts);
2710
+ await new Promise((res) => setTimeout(res, clickPostPop()));
2711
+ await hideRing(page);
2712
+ return result;
2713
+ } finally {
2714
+ if (keptCameraLock) {
2715
+ await page
2716
+ .evaluate(() => {
2717
+ window.__wgCameraLock = false;
2718
+ })
2719
+ .catch(() => {});
2720
+ }
2721
+ }
2661
2722
  };
2662
2723
  }
2663
2724
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.12.37",
3
+ "version": "0.12.38",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",