waygraph 0.12.29 → 0.12.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -527,6 +527,24 @@ const RING_CSS =
527
527
  "#wg-todos .wg-todo-done{color:#6ee7b7;text-decoration:line-through;opacity:.85;}" +
528
528
  "#wg-todos .wg-todo-current{color:#fff;}" +
529
529
  "#wg-todos .wg-todo-pending{color:#9a7ad1;}" +
530
+ "#wg-todo-modal{position:fixed;z-index:2147483647;left:14px;bottom:14px;min-width:240px;max-width:min(360px,92vw);" +
531
+ "padding:14px 16px 12px;background:rgba(18,8,36,.94);color:#f0e8ff;" +
532
+ "border:1px solid rgba(124,58,237,.55);border-radius:12px;" +
533
+ "box-shadow:0 12px 40px rgba(0,0,0,.45);backdrop-filter:blur(8px);" +
534
+ "opacity:0;transform:translateY(8px);pointer-events:none;" +
535
+ "transition:opacity .28s ease,transform .28s ease;}" +
536
+ "#wg-todo-modal.wg-todo-on{opacity:1;transform:none;pointer-events:auto;}" +
537
+ "#wg-todo-modal .wg-todo-head{font:700 11px/1.2 system-ui,sans-serif;color:#c9a6ff;" +
538
+ "letter-spacing:.04em;text-transform:uppercase;margin:0 0 10px;}" +
539
+ "#wg-todo-modal ul{list-style:none;margin:0;padding:0;}" +
540
+ "#wg-todo-modal li{display:flex;gap:10px;align-items:flex-start;margin:0 0 8px;" +
541
+ "font:600 14px/1.4 system-ui,sans-serif;}" +
542
+ "#wg-todo-modal li:last-child{margin-bottom:0;}" +
543
+ "#wg-todo-modal .wg-todo-mark{flex:0 0 auto;width:1.2em;text-align:center;font-size:15px;}" +
544
+ "#wg-todo-modal .wg-todo-done{color:#6ee7b7;text-decoration:line-through;opacity:.8;}" +
545
+ "#wg-todo-modal .wg-todo-current{color:#fff;}" +
546
+ "#wg-todo-modal .wg-todo-current .wg-todo-mark{color:#c9a6ff;}" +
547
+ "#wg-todo-modal .wg-todo-pending{color:#9a7ad1;}" +
530
548
  "#wg-progress{height:4px;background:#2a1650;border-radius:2px;margin:0 0 12px;overflow:hidden;}" +
531
549
  "#wg-progress-bar{height:100%;background:#7C3AED;border-radius:2px;transition:width .3s ease;}" +
532
550
  // A real tab bar for episodes - the currently-active episode reads as
@@ -707,11 +725,6 @@ async function installOverlay(page, title) {
707
725
  cursor.style.transform = "translate(" + x + "px," + y + "px)";
708
726
  }
709
727
  cursor.style.opacity = "1";
710
- // Screen Studio: pan the zoomed camera with the cursor.
711
- const cam = window.__wgCamera;
712
- if (cam && cam.scale > 1.001 && window.__wgSetCamera) {
713
- window.__wgSetCamera(x, y, cam.scale, instant ? 0 : travel);
714
- }
715
728
  };
716
729
  window.__wgHideCursor = () => {
717
730
  const cursor = document.getElementById("wg-cursor");
@@ -801,12 +814,14 @@ async function installOverlay(page, title) {
801
814
  };
802
815
  // Screen Studio-style camera: scale <body>, keep overlays on <html> fixed.
803
816
  window.__wgCamera = { scale: 1, cx: 0, cy: 0 };
804
- window.__wgSetCamera = (cx, cy, scale, ms) => {
817
+ window.__wgSetCamera = (fx, fy, scale, ms) => {
805
818
  const body = document.body;
806
819
  if (!body) return;
807
820
  const s = Math.max(1, Math.min(Number(scale) || 1, 2.75));
808
- window.__wgCamera = { scale: s, cx, cy };
809
- const dur = ms != null && Number(ms) >= 0 ? Number(ms) : 500;
821
+ const vw = window.innerWidth;
822
+ const vh = window.innerHeight;
823
+ window.__wgCamera = { scale: s, cx: fx, cy: fy };
824
+ const dur = ms != null && Number(ms) >= 0 ? Number(ms) : 650;
810
825
  body.style.transition =
811
826
  dur <= 0
812
827
  ? "none"
@@ -816,8 +831,51 @@ async function installOverlay(page, title) {
816
831
  body.style.transform = "";
817
832
  return;
818
833
  }
834
+ // One transform: scale AND bring focus to viewport center.
819
835
  body.style.transform =
820
- "translate(" + cx + "px," + cy + "px) scale(" + s + ") translate(" + -cx + "px," + -cy + "px)";
836
+ "translate(" + vw / 2 + "px," + vh / 2 + "px) scale(" + s + ") translate(" + -fx + "px," + -fy + "px)";
837
+ };
838
+ window.__wgSetTodos = (todos) => {
839
+ let modal = document.getElementById("wg-todo-modal");
840
+ if (!todos || !todos.length) {
841
+ if (modal) {
842
+ modal.classList.remove("wg-todo-on");
843
+ modal.innerHTML = "";
844
+ }
845
+ return;
846
+ }
847
+ if (!modal) {
848
+ modal = document.createElement("div");
849
+ modal.id = "wg-todo-modal";
850
+ document.documentElement.appendChild(modal);
851
+ }
852
+ const esc = (s) =>
853
+ String(s)
854
+ .replace(/&/g, "&amp;")
855
+ .replace(/</g, "&lt;")
856
+ .replace(/>/g, "&gt;");
857
+ const rows = todos
858
+ .map((t) => {
859
+ const cls = t.current
860
+ ? "wg-todo-current"
861
+ : t.done
862
+ ? "wg-todo-done"
863
+ : "wg-todo-pending";
864
+ const mark = t.done ? "\u2713" : t.current ? "\u2192" : "\u25CB";
865
+ return (
866
+ "<li class=\"" +
867
+ cls +
868
+ "\"><span class=\"wg-todo-mark\">" +
869
+ mark +
870
+ "</span><span>" +
871
+ esc(t.text || "") +
872
+ "</span></li>"
873
+ );
874
+ })
875
+ .join("");
876
+ modal.innerHTML =
877
+ "<div class=\"wg-todo-head\">Checklist</div><ul>" + rows + "</ul>";
878
+ modal.classList.add("wg-todo-on");
821
879
  };
822
880
  window.__wgResetCamera = () => {
823
881
  window.__wgSetCamera(window.innerWidth / 2, window.innerHeight / 2, 1);
@@ -1059,7 +1117,29 @@ async function renderBeforeStep(page, info) {
1059
1117
  "<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
1060
1118
  "<h3>Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + "</h3>" +
1061
1119
  narrationHtml +
1062
- todosHtmlFromInfo(info, esc);
1120
+ ((() => {
1121
+ const list = info.todos || [];
1122
+ if (!list.length) return "";
1123
+ return (
1124
+ "<ul id=\"wg-todos\">" +
1125
+ list
1126
+ .map((t) => {
1127
+ const cls = t.current ? "wg-todo-current" : t.done ? "wg-todo-done" : "wg-todo-pending";
1128
+ const mark = t.done ? "\u2713" : t.current ? "\u2192" : "\u25CB";
1129
+ return (
1130
+ "<li class=\"" +
1131
+ cls +
1132
+ "\"><span class=\"wg-todo-mark\">" +
1133
+ mark +
1134
+ "</span><span>" +
1135
+ esc(t.text || "") +
1136
+ "</span></li>"
1137
+ );
1138
+ })
1139
+ .join("") +
1140
+ "</ul>"
1141
+ );
1142
+ })());
1063
1143
  if (info.keys.length === 0) {
1064
1144
  html += "<div class=\\"wg-key\\">(no MemKeys required)</div>";
1065
1145
  }
@@ -1237,9 +1317,9 @@ async function ensureSelectorInView(page, selector) {
1237
1317
  }
1238
1318
  }
1239
1319
 
1240
- async function applyCameraZoom(page, box, zoom) {
1320
+ async function applyCameraZoom(page, box, zoom, ms) {
1241
1321
  const z = zoom != null && Number(zoom) > 1 ? Number(zoom) : 1;
1242
- if (!box) {
1322
+ if (!box || z <= 1) {
1243
1323
  await page.evaluate(() => {
1244
1324
  if (window.__wgResetCamera) window.__wgResetCamera();
1245
1325
  }).catch(() => {});
@@ -1247,10 +1327,19 @@ async function applyCameraZoom(page, box, zoom) {
1247
1327
  }
1248
1328
  const cx = box.x + box.width / 2;
1249
1329
  const cy = box.y + box.height / 2;
1330
+ const dur = ms != null ? ms : 650;
1250
1331
  await page
1251
- .evaluate(({ cx, cy, scale }) => {
1252
- if (window.__wgSetCamera) window.__wgSetCamera(cx, cy, scale);
1253
- }, { cx, cy, scale: z })
1332
+ .evaluate(({ cx, cy, scale, dur }) => {
1333
+ if (window.__wgSetCamera) window.__wgSetCamera(cx, cy, scale, dur);
1334
+ }, { cx, cy, scale: z, dur })
1335
+ .catch(() => {});
1336
+ }
1337
+
1338
+ async function setDemoTodos(page, todos) {
1339
+ await page
1340
+ .evaluate((list) => {
1341
+ if (window.__wgSetTodos) window.__wgSetTodos(list || []);
1342
+ }, todos && todos.length ? todos : [])
1254
1343
  .catch(() => {});
1255
1344
  }
1256
1345
 
@@ -1271,6 +1360,21 @@ async function setDemoBannerTitle(page, title) {
1271
1360
  .catch(() => {});
1272
1361
  }
1273
1362
 
1363
+ /** Collapsed chrome title: Ep N / i / n / block / pace. */
1364
+ function miniStepLabel(info) {
1365
+ const ep =
1366
+ info.episodeNumber !== undefined && info.episodeNumber !== null
1367
+ ? "Ep " + info.episodeNumber + " \\u00b7 "
1368
+ : "";
1369
+ return (
1370
+ ep +
1371
+ (info.index + 1) +
1372
+ " / " +
1373
+ info.total +
1374
+ (info.blockName ? " \\u00b7 " + info.blockName : "") +
1375
+ (info.paceBadge ? " \\u00b7 " + info.paceBadge : "")
1376
+ );
1377
+ }
1274
1378
 
1275
1379
  async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1276
1380
  const list = highlights || [];
@@ -1340,15 +1444,27 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1340
1444
  const box = await page.locator(h.selector).first().boundingBox();
1341
1445
  if (!box) continue;
1342
1446
  await updateHlProgress(i);
1343
- await applyCameraZoom(page, box, h.zoom);
1344
- const useCursor = h.cursor !== false;
1345
- if (useCursor) {
1346
- const cx = box.x + box.width / 2;
1347
- const cy = box.y + box.height / 2;
1447
+ if (opts && opts.todos && opts.todos.length) {
1448
+ const rows = opts.todos.map((t, ti) => ({
1449
+ text: t.text,
1450
+ done: ti < i,
1451
+ current: ti === i,
1452
+ }));
1453
+ await setDemoTodos(page, rows);
1454
+ } else if (h.todos && h.todos.length) {
1455
+ await setDemoTodos(page, normalizeTodos(h.todos, h.todoIndex));
1456
+ }
1457
+ const cx = box.x + box.width / 2;
1458
+ const cy = box.y + box.height / 2;
1459
+ const z = h.zoom != null && Number(h.zoom) > 1 ? Number(h.zoom) : 1;
1460
+ const camMs = 700;
1461
+ // One-shot camera: zoom + center together (no zoom-then-align).
1462
+ await applyCameraZoom(page, box, z, camMs);
1463
+ if (h.cursor !== false) {
1348
1464
  await page
1349
- .evaluate(({ cx, cy }) => {
1350
- if (window.__wgMoveCursorTo) window.__wgMoveCursorTo(cx, cy, 500, false);
1351
- }, { cx, cy })
1465
+ .evaluate(({ cx, cy, camMs }) => {
1466
+ if (window.__wgMoveCursorTo) window.__wgMoveCursorTo(cx, cy, camMs, false);
1467
+ }, { cx, cy, camMs })
1352
1468
  .catch(() => {});
1353
1469
  }
1354
1470
  await showRing(page, box, h.label, h.tone || "planned", {
@@ -1357,9 +1473,10 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1357
1473
  focus: !!h.focus,
1358
1474
  color: h.color || h.labelColor || h.textColor,
1359
1475
  });
1360
- // Live follow while this ring is up (seamless, not taped).
1476
+ await new Promise((res) => setTimeout(res, camMs));
1477
+ // Live ring track only - camera stays locked (no per-frame re-pan).
1361
1478
  await page
1362
- .evaluate(({ sel, label, tone, size, weight, focus, color, zoom }) => {
1479
+ .evaluate(({ sel, label, tone, size, weight, focus, color }) => {
1363
1480
  const tick = () => {
1364
1481
  const el = document.querySelector(sel);
1365
1482
  if (!el) {
@@ -1371,26 +1488,10 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1371
1488
  if (window.__wgPositionRing) {
1372
1489
  window.__wgPositionRing(box, label, tone || "planned", { size, weight, focus, color });
1373
1490
  }
1374
- if (zoom && zoom > 1 && window.__wgSetCamera) {
1375
- // Prefer cursor tip when visible (Screen Studio follow-mouse);
1376
- // else keep the ring target centered.
1377
- let cx = box.x + box.width / 2;
1378
- let cy = box.y + box.height / 2;
1379
- const cur = document.getElementById("wg-cursor");
1380
- if (cur && cur.style.opacity !== "0") {
1381
- const m = /translate\(([-\d.]+)px,\s*([-\d.]+)px\)/.exec(cur.style.transform || "");
1382
- if (m) {
1383
- cx = Number(m[1]);
1384
- cy = Number(m[2]);
1385
- }
1386
- }
1387
- window.__wgSetCamera(cx, cy, zoom, 0);
1388
- }
1389
1491
  window.__wgRingRaf = requestAnimationFrame(tick);
1390
1492
  };
1391
1493
  window.__wgRingRaf = requestAnimationFrame(tick);
1392
- const onScroll = () => {};
1393
- window.__wgRingTrack = onScroll;
1494
+ window.__wgRingTrack = () => {};
1394
1495
  }, {
1395
1496
  sel: h.selector,
1396
1497
  label: h.label,
@@ -1399,47 +1500,9 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1399
1500
  weight: h.weight || "normal",
1400
1501
  focus: !!h.focus,
1401
1502
  color: h.color || h.labelColor || h.textColor || "",
1402
- zoom: h.zoom != null && h.zoom > 1 ? h.zoom : 0,
1403
1503
  })
1404
1504
  .catch(() => {});
1405
1505
 
1406
- if ((opts && opts.todos && opts.todos.length) || (h.todos && h.todos.length)) {
1407
- const rows =
1408
- opts && opts.todos && opts.todos.length
1409
- ? opts.todos
1410
- : normalizeTodos(h.todos, h.todoIndex);
1411
- await page
1412
- .evaluate((todos) => {
1413
- const ul = document.getElementById("wg-todos");
1414
- if (!ul) return;
1415
- ul.innerHTML = todos
1416
- .map((t) => {
1417
- const cls = t.current
1418
- ? "wg-todo-current"
1419
- : t.done
1420
- ? "wg-todo-done"
1421
- : "wg-todo-pending";
1422
- const mark = t.done ? "\\u2713" : t.current ? "\\u2192" : "\\u25CB";
1423
- const esc = (s) =>
1424
- String(s)
1425
- .replace(/&/g, "&amp;")
1426
- .replace(/</g, "&lt;")
1427
- .replace(/>/g, "&gt;");
1428
- return (
1429
- "<li class=\\"" +
1430
- cls +
1431
- "\\"><span class=\\"wg-todo-mark\\">" +
1432
- mark +
1433
- "</span><span>" +
1434
- esc(t.text || "") +
1435
- "</span></li>"
1436
- );
1437
- })
1438
- .join("");
1439
- }, rows)
1440
- .catch(() => {});
1441
- }
1442
-
1443
1506
  // Appear settle
1444
1507
  await new Promise((res) => setTimeout(res, fadeMs));
1445
1508
  const authored = resolveFixtureDwellMs(h, { gatesFast, pace });
@@ -1570,7 +1633,29 @@ async function renderAfterStep(page, info) {
1570
1633
  paceHtml +
1571
1634
  "<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
1572
1635
  "<h3>" + heading + "</h3>" +
1573
- todosHtmlFromInfo(info, escA) +
1636
+ ((() => {
1637
+ const list = info.todos || [];
1638
+ if (!list.length) return "";
1639
+ return (
1640
+ "<ul id=\"wg-todos\">" +
1641
+ list
1642
+ .map((t) => {
1643
+ const cls = t.current ? "wg-todo-current" : t.done ? "wg-todo-done" : "wg-todo-pending";
1644
+ const mark = t.done ? "\u2713" : t.current ? "\u2192" : "\u25CB";
1645
+ return (
1646
+ "<li class=\"" +
1647
+ cls +
1648
+ "\"><span class=\"wg-todo-mark\">" +
1649
+ mark +
1650
+ "</span><span>" +
1651
+ escA(t.text || "") +
1652
+ "</span></li>"
1653
+ );
1654
+ })
1655
+ .join("") +
1656
+ "</ul>"
1657
+ );
1658
+ })()) +
1574
1659
  resultHtml +
1575
1660
  gateHtml;
1576
1661
  if (isNewPanel) {
@@ -2158,7 +2243,7 @@ async function teardownOverlay(page) {
2158
2243
  window.__wgRingTrack = null;
2159
2244
  }
2160
2245
  window.__wgNarrateOwnsRing = false;
2161
- for (const id of ["wg-panel", "wg-ring", "wg-ring-label", "wg-cursor", "wg-click-pulse", "wg-banner"]) {
2246
+ for (const id of ["wg-panel", "wg-ring", "wg-ring-label", "wg-cursor", "wg-click-pulse", "wg-banner", "wg-todo-modal"]) {
2162
2247
  const el = document.getElementById(id);
2163
2248
  if (el) el.remove();
2164
2249
  }
@@ -2732,6 +2817,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
2732
2817
  todos: stubBeforeTodos,
2733
2818
  ...paceSpeak,
2734
2819
  });
2820
+ await setDemoTodos(page, stubBeforeTodos);
2735
2821
  const edits = await gate();
2736
2822
  for (const k of requires) {
2737
2823
  if (edits[k.name] !== undefined) {
@@ -2919,6 +3005,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
2919
3005
  });
2920
3006
  stubAfterTodos = afterPhase.todos || [];
2921
3007
  await setDemoBannerTitle(page, afterPhase.title);
3008
+ await setDemoTodos(page, afterPhase.todos || []);
2922
3009
  highlights = afterPhase.highlights.map((h) => {
2923
3010
  const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
2924
3011
  return {
package/dist/engine.d.ts CHANGED
@@ -544,6 +544,57 @@ export declare class Engine {
544
544
  Block<K, L>,
545
545
  EndMarker
546
546
  ]): Flow<L>;
547
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>, H extends Checkpoint<string>, I extends Checkpoint<string>, J extends Checkpoint<string>, K extends Checkpoint<string>, L extends Checkpoint<string>, M extends Checkpoint<string>>(blocks: readonly [
548
+ StartMarker,
549
+ Block<S, B>,
550
+ Block<B, C>,
551
+ Block<C, D>,
552
+ Block<D, E>,
553
+ Block<E, F>,
554
+ Block<F, G>,
555
+ Block<G, H>,
556
+ Block<H, I>,
557
+ Block<I, J>,
558
+ Block<J, K>,
559
+ Block<K, L>,
560
+ Block<L, M>,
561
+ EndMarker
562
+ ]): Flow<M>;
563
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>, H extends Checkpoint<string>, I extends Checkpoint<string>, J extends Checkpoint<string>, K extends Checkpoint<string>, L extends Checkpoint<string>, M extends Checkpoint<string>, N extends Checkpoint<string>>(blocks: readonly [
564
+ StartMarker,
565
+ Block<S, B>,
566
+ Block<B, C>,
567
+ Block<C, D>,
568
+ Block<D, E>,
569
+ Block<E, F>,
570
+ Block<F, G>,
571
+ Block<G, H>,
572
+ Block<H, I>,
573
+ Block<I, J>,
574
+ Block<J, K>,
575
+ Block<K, L>,
576
+ Block<L, M>,
577
+ Block<M, N>,
578
+ EndMarker
579
+ ]): Flow<N>;
580
+ defineFlow<B extends Checkpoint<string>, C extends Checkpoint<string>, D extends Checkpoint<string>, E extends Checkpoint<string>, F extends Checkpoint<string>, G extends Checkpoint<string>, H extends Checkpoint<string>, I extends Checkpoint<string>, J extends Checkpoint<string>, K extends Checkpoint<string>, L extends Checkpoint<string>, M extends Checkpoint<string>, N extends Checkpoint<string>, O extends Checkpoint<string>>(blocks: readonly [
581
+ StartMarker,
582
+ Block<S, B>,
583
+ Block<B, C>,
584
+ Block<C, D>,
585
+ Block<D, E>,
586
+ Block<E, F>,
587
+ Block<F, G>,
588
+ Block<G, H>,
589
+ Block<H, I>,
590
+ Block<I, J>,
591
+ Block<J, K>,
592
+ Block<K, L>,
593
+ Block<L, M>,
594
+ Block<M, N>,
595
+ Block<N, O>,
596
+ EndMarker
597
+ ]): Flow<O>;
547
598
  }
548
599
  /**
549
600
  * Attaches routing to `block` without touching its act/observe/resolve/verify -
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.12.29",
3
+ "version": "0.12.31",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",