waygraph 0.16.0 → 0.16.2

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 (143) hide show
  1. package/dist/cli/commands/browser.d.ts +2 -0
  2. package/dist/cli/commands/browser.js +109 -0
  3. package/dist/cli/commands/codemod-stubs.js +90 -2
  4. package/dist/cli/commands/pilot.d.ts +2 -0
  5. package/dist/cli/commands/pilot.js +102 -0
  6. package/dist/cli/commands/session-ops.d.ts +3 -0
  7. package/dist/cli/commands/session-ops.js +257 -0
  8. package/dist/cli/commands/session.js +20 -461
  9. package/dist/cli/run-chain.js +2 -2
  10. package/dist/cli/usage.d.ts +1 -1
  11. package/dist/cli/util.js +1 -1
  12. package/dist/demo/narration/demo-pace.d.ts +2 -2
  13. package/dist/demo/narration/demo-pace.js +1 -1
  14. package/dist/demo/narration/device.d.ts +2 -2
  15. package/dist/demo/narration/merge.d.ts +5 -3
  16. package/dist/demo/narration/merge.js +3 -7
  17. package/dist/demo/narration/narration.d.ts +9 -13
  18. package/dist/demo/narration/narration.js +5 -17
  19. package/dist/demo/narration/stub-ctx.js +3 -3
  20. package/dist/demo/narration/todo-dock.d.ts +5 -5
  21. package/dist/demo/overlay/cursor.d.ts +9 -2
  22. package/dist/demo/overlay/cursor.js +1 -1
  23. package/dist/demo/overlay/device-stage.d.ts +15 -2
  24. package/dist/demo/overlay/inpage/banner.d.ts +2 -1
  25. package/dist/demo/overlay/inpage/banner.js +3 -3
  26. package/dist/demo/overlay/inpage/caption.d.ts +1 -1
  27. package/dist/demo/overlay/inpage/core.d.ts +2 -1
  28. package/dist/demo/overlay/inpage/core.js +1 -7
  29. package/dist/demo/overlay/inpage/device-chip.d.ts +1 -1
  30. package/dist/demo/overlay/inpage/device-chip.js +6 -6
  31. package/dist/demo/overlay/inpage/network.d.ts +3 -2
  32. package/dist/demo/overlay/inpage/network.js +1 -12
  33. package/dist/demo/overlay/inpage/panel-chrome.d.ts +1 -1
  34. package/dist/demo/overlay/inpage/panel-chrome.js +1 -1
  35. package/dist/demo/overlay/inpage/stamp.d.ts +1 -1
  36. package/dist/demo/overlay/inpage/stamp.js +1 -1
  37. package/dist/demo/overlay/inpage/swipe-zoom.d.ts +1 -1
  38. package/dist/demo/overlay/inpage/swipe-zoom.js +1 -1
  39. package/dist/demo/overlay/inpage/todos.d.ts +2 -1
  40. package/dist/demo/overlay/inpage/todos.js +1 -2
  41. package/dist/demo/overlay/network-capture.d.ts +2 -2
  42. package/dist/demo/overlay/network-capture.js +1 -1
  43. package/dist/demo/overlay/overlay-css.js +1 -55
  44. package/dist/demo/overlay/overlay-stage.d.ts +19 -11
  45. package/dist/demo/overlay/overlay.d.ts +2 -2
  46. package/dist/demo/overlay/overlay.js +3 -2
  47. package/dist/demo/overlay/rings.d.ts +38 -18
  48. package/dist/demo/overlay/rings.js +21 -62
  49. package/dist/demo/overlay/ui/overlay.css +40 -0
  50. package/dist/demo/stepper/after.js +11 -34
  51. package/dist/demo/stepper/banner-log.d.ts +4 -6
  52. package/dist/demo/stepper/banner-log.js +3 -1
  53. package/dist/demo/stepper/before.js +6 -4
  54. package/dist/demo/stepper/demo-log.d.ts +26 -9
  55. package/dist/demo/stepper/demo-log.js +10 -27
  56. package/dist/demo/stepper/interaction-patch.d.ts +3 -2
  57. package/dist/demo/stepper/interaction-patch.js +22 -34
  58. package/dist/demo/stepper/panel-after.d.ts +17 -0
  59. package/dist/demo/stepper/panel-after.js +166 -0
  60. package/dist/demo/stepper/panel-before.d.ts +12 -0
  61. package/dist/demo/stepper/panel-before.js +241 -0
  62. package/dist/demo/stepper/slides.d.ts +16 -1
  63. package/dist/demo/stepper/slides.js +21 -5
  64. package/dist/demo/stepper/step-panels.d.ts +57 -9
  65. package/dist/demo/stepper/step-panels.js +10 -427
  66. package/dist/demo/stepper/stepper.js +1 -1
  67. package/dist/demo/stepper/teardown.d.ts +4 -5
  68. package/dist/demo/stepper/teardown.js +4 -6
  69. package/dist/demo/stepper/todo-advance.d.ts +3 -2
  70. package/dist/demo/stepper/todo-advance.js +2 -2
  71. package/dist/demo/stepper/view.d.ts +4 -14
  72. package/dist/demo/types.d.ts +69 -1
  73. package/dist/demo/verbose-log.js +1 -1
  74. package/dist/engine/block/action-page.d.ts +53 -0
  75. package/dist/engine/block/action-page.js +1 -0
  76. package/dist/engine/block/block.d.ts +24 -90
  77. package/dist/engine/block/block.js +2 -77
  78. package/dist/engine/block/branch.d.ts +17 -0
  79. package/dist/engine/block/branch.js +40 -0
  80. package/dist/engine/block/dialog.d.ts +13 -0
  81. package/dist/engine/block/dialog.js +37 -0
  82. package/dist/engine/block/kinds/method.d.ts +0 -2
  83. package/dist/engine/block/kinds/method.js +0 -1
  84. package/dist/engine/block/kinds/nav.js +1 -1
  85. package/dist/engine/flow/map-branch.d.ts +9 -0
  86. package/dist/engine/flow/map-branch.js +57 -0
  87. package/dist/engine/flow/map-builder.d.ts +7 -22
  88. package/dist/engine/flow/map-builder.js +6 -145
  89. package/dist/engine/flow/map-steps.d.ts +30 -0
  90. package/dist/engine/flow/map-steps.js +87 -0
  91. package/dist/engine/run/run.d.ts +1 -19
  92. package/dist/engine/run/run.js +0 -88
  93. package/dist/engine/stub/ring.d.ts +2 -4
  94. package/dist/engine/stub/ring.js +1 -0
  95. package/dist/engine/stub/stub.d.ts +16 -8
  96. package/dist/engine/trait/trait.js +1 -1
  97. package/dist/index.d.ts +15 -12
  98. package/dist/index.js +9 -7
  99. package/dist/live/auto/auto.d.ts +3 -15
  100. package/dist/live/auto/auto.js +10 -420
  101. package/dist/live/auto/explore.d.ts +2 -2
  102. package/dist/live/auto/mem.d.ts +11 -0
  103. package/dist/live/auto/mem.js +85 -0
  104. package/dist/live/auto/menu.d.ts +30 -0
  105. package/dist/live/auto/menu.js +335 -0
  106. package/dist/live/auto-session/auto-session.d.ts +23 -103
  107. package/dist/live/auto-session/auto-session.js +100 -370
  108. package/dist/live/auto-session/console.d.ts +4 -0
  109. package/dist/live/auto-session/console.js +41 -0
  110. package/dist/live/auto-session/files.d.ts +44 -0
  111. package/dist/live/auto-session/files.js +145 -0
  112. package/dist/live/auto-session/helpers.d.ts +5 -2
  113. package/dist/live/auto-session/helpers.js +17 -0
  114. package/dist/live/auto-session/inspect.d.ts +4 -0
  115. package/dist/live/auto-session/inspect.js +75 -0
  116. package/dist/live/auto-session/ipc.d.ts +3 -22
  117. package/dist/live/auto-session/ipc.js +7 -147
  118. package/dist/live/auto-session/open-browser.d.ts +6 -0
  119. package/dist/live/auto-session/open-browser.js +47 -0
  120. package/dist/live/pilot/pilot.d.ts +1 -1
  121. package/dist/live/pilot/pilot.js +2 -1
  122. package/dist/live/traverse/traverse.js +6 -200
  123. package/dist/live/traverse/worker.d.ts +37 -0
  124. package/dist/live/traverse/worker.js +200 -0
  125. package/dist/runner/plan/find.js +2 -2
  126. package/dist/runner/plan/plan.js +1 -1
  127. package/dist/runner/plan/seed-mem.js +5 -5
  128. package/dist/runner/plan/spec.js +1 -1
  129. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
  130. package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +2 -1
  131. package/examples/saucedemo/src/flows/checkout.flow.ts +2 -1
  132. package/examples/saucedemo/src/flows/shop.flow.ts +2 -1
  133. package/examples/saucedemo/src/stubs/device-showcase.ts +37 -43
  134. package/examples/saucedemo/src/stubs/finish-order.ts +38 -0
  135. package/package.json +5 -1
  136. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
  137. package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +2 -1
  138. package/templates/quickstart/src/flows/checkout.flow.ts +2 -1
  139. package/templates/quickstart/src/flows/shop.flow.ts +2 -1
  140. package/templates/quickstart/src/stubs/device-showcase.ts +37 -43
  141. package/templates/quickstart/src/stubs/finish-order.ts +38 -0
  142. package/templates/scaffold/src/flows/home/hello.flow.ts +3 -0
  143. package/templates/scaffold/src/map/(app_base)/home/_methods/assert-hello.method.block.ts +1 -9
@@ -1,11 +1,11 @@
1
1
  import { ANSI, ansiPaint, demoFixture, demoHighlight, demoLog } from "../stepper/demo-log.js";
2
2
  import { playSwipeTrail } from "./cursor.js";
3
- import { advanceTodoDock, normalizeTodos } from "../narration/todo-dock.js";
3
+ import { advanceTodoDock } from "../narration/todo-dock.js";
4
4
  import { formatHighlightCaption } from "../narration/style.js";
5
5
  import { normalizeHighlightSize, normalizeHighlightTone, normalizeHighlightWeight } from "../../engine/stub/ring.js";
6
6
  import { resolveFixtureDwellMs } from "../narration/demo-pace.js";
7
7
  import { envNumber } from "../../engine/env.js";
8
- import { ignorePageGone } from "../../engine/page-gone.js";
8
+ import { ignorePageGone, ignoreTimeout } from "../../engine/page-gone.js";
9
9
  /**
10
10
  * Scroll a locator into view for demo theater.
11
11
  * Prefer CSS smooth scroll + scrollend wait. Do NOT call Playwright's
@@ -184,8 +184,7 @@ export async function cycleHighlightRings(page, highlights, gatesFast, opts) {
184
184
  .catch(ignorePageGone);
185
185
  }
186
186
  let lastRingEndedAt = null;
187
- for (let i = 0; i < list.length; i++) {
188
- const h = list[i];
187
+ for (const [i, h] of list.entries()) {
189
188
  const ringStarted = Date.now();
190
189
  try {
191
190
  await ensureSelectorInView(page, h.selector);
@@ -200,13 +199,13 @@ export async function cycleHighlightRings(page, highlights, gatesFast, opts) {
200
199
  // "does it exist right now" check - ensureSelectorInView above already gave the page a real
201
200
  // moment to settle.
202
201
  const hLoc = page.locator(h.selector).first();
203
- const box = (await hLoc.count().catch(() => 0)) > 0
202
+ const box = ((await hLoc.count().catch(ignorePageGone)) ?? 0) > 0
204
203
  ? await hLoc
205
204
  .evaluate((el) => {
206
205
  const r = el.getBoundingClientRect();
207
206
  return { x: r.x, y: r.y, width: r.width, height: r.height };
208
207
  })
209
- .catch(() => null)
208
+ .catch(ignorePageGone)
210
209
  : null;
211
210
  if (box && box.width > 0 && box.height > 0) {
212
211
  await showRing(page, box, h.label, h.tone || "planned", {
@@ -230,10 +229,8 @@ export async function cycleHighlightRings(page, highlights, gatesFast, opts) {
230
229
  }, dock)
231
230
  .catch(ignorePageGone);
232
231
  }
233
- else if (!dock && ((opts && opts.todos && opts.todos.length) || (h.todos && h.todos.length))) {
234
- const rows = opts && opts.todos && opts.todos.length
235
- ? opts.todos
236
- : normalizeTodos(h.todos, h.todoIndex);
232
+ else if (!dock && opts?.todos?.length) {
233
+ const rows = opts.todos;
237
234
  await page
238
235
  .evaluate((todos) => {
239
236
  if (window.__wgSyncTodos)
@@ -273,16 +270,16 @@ export async function cycleHighlightRings(page, highlights, gatesFast, opts) {
273
270
  }
274
271
  }
275
272
  catch (err) {
276
- demoHighlight(h || { selector: h && h.selector, label: "(error)" }, "SKIP " + String(err instanceof Error ? err.message : err).slice(0, 80));
273
+ demoHighlight(h, "SKIP " + String(err instanceof Error ? err.message : err).slice(0, 80));
277
274
  }
278
275
  }
279
- if (list.length > 0) {
280
- const last = list[list.length - 1];
276
+ const last = list.at(-1);
277
+ if (last) {
281
278
  // Resolved through Playwright's own locator (>> piercing / :has-text() / :visible support),
282
279
  // not handed to the page as a string for a raw DOM querySelector to fail silently on - see
283
280
  // showRing's own comment (same fix, same reason) and __wgFollowRing's in core.js.
284
281
  const handle = last.selector
285
- ? await page.locator(last.selector).first().elementHandle({ timeout: 100 }).catch(() => null)
282
+ ? await page.locator(last.selector).first().elementHandle({ timeout: 100 }).catch(ignoreTimeout)
286
283
  : null;
287
284
  await page
288
285
  .evaluate(({ h, el }) => {
@@ -333,7 +330,7 @@ export async function showRing(page, box, label, tone, style) {
333
330
  // syntax, invisible to document.querySelector. The resolved element handle is passed straight
334
331
  // into evaluate (Playwright unwraps it to the real DOM node inside the page), so __wgFollowRing's
335
332
  // per-frame RAF loop tracks that exact element directly - no repeated selector re-resolution.
336
- const handle = selector ? await page.locator(selector).first().elementHandle({ timeout: 100 }).catch(() => null) : null;
333
+ const handle = selector ? await page.locator(selector).first().elementHandle({ timeout: 100 }).catch(ignoreTimeout) : null;
337
334
  await page
338
335
  .evaluate(({ box, label, tone, size, weight, el, focus }) => {
339
336
  if (el && window.__wgFollowRing) {
@@ -352,46 +349,18 @@ export async function showRing(page, box, label, tone, style) {
352
349
  }, { box, label, tone: tone || "planned", size, weight, el: handle, focus })
353
350
  .catch(ignorePageGone);
354
351
  }
355
- /** Match a locator to a stubBefore slot by overlapping bounding boxes. */
356
- async function matchStubForLocator(page, locator, stubs) {
357
- if (!stubs || stubs.length === 0)
358
- return null;
359
- const box = await locator.boundingBox().catch(() => null);
360
- if (!box)
361
- return null;
362
- for (const s of stubs) {
363
- try {
364
- const b = await page.locator(s.selector).first().boundingBox();
365
- if (b &&
366
- Math.abs(b.x - box.x) < 4 &&
367
- Math.abs(b.y - box.y) < 4 &&
368
- Math.abs(b.width - box.width) < 8) {
369
- return s;
370
- }
371
- }
372
- catch {
373
- // A stub selector Playwright can't parse can't be this element either.
374
- }
375
- }
376
- return null;
377
- }
378
- /** 0-based stub index for Method fill/click todo advance; -1 if none. */
352
+ /** The stubBefore ring a locator is, by overlapping bounding boxes: its index, or -1. For a Method's todo advance. */
379
353
  export async function matchStubIndexForLocator(page, locator, stubs) {
380
354
  if (!stubs || stubs.length === 0)
381
355
  return -1;
382
- const box = await locator.boundingBox().catch(() => null);
356
+ const box = await locator.boundingBox().catch(ignorePageGone);
383
357
  if (!box)
384
358
  return -1;
385
359
  for (let i = 0; i < stubs.length; i++) {
386
- const s = stubs[i];
387
360
  try {
388
- const b = await page.locator(s.selector).first().boundingBox();
389
- if (b &&
390
- Math.abs(b.x - box.x) < 4 &&
391
- Math.abs(b.y - box.y) < 4 &&
392
- Math.abs(b.width - box.width) < 8) {
361
+ const b = await page.locator(stubs[i].selector).first().boundingBox();
362
+ if (b && Math.abs(b.x - box.x) < 4 && Math.abs(b.y - box.y) < 4 && Math.abs(b.width - box.width) < 8)
393
363
  return i;
394
- }
395
364
  }
396
365
  catch {
397
366
  // A stub selector Playwright can't parse can't be this element either.
@@ -399,6 +368,11 @@ export async function matchStubIndexForLocator(page, locator, stubs) {
399
368
  }
400
369
  return -1;
401
370
  }
371
+ /** The stubBefore ring a locator is, or null. */
372
+ async function matchStubForLocator(page, locator, stubs) {
373
+ const i = await matchStubIndexForLocator(page, locator, stubs);
374
+ return i >= 0 && stubs ? stubs[i] : null;
375
+ }
402
376
  export async function captionForLocator(page, locator, stubs, fallback) {
403
377
  const matched = await matchStubForLocator(page, locator, stubs);
404
378
  if (matched) {
@@ -431,18 +405,3 @@ export async function hideRing(page) {
431
405
  })
432
406
  .catch(ignorePageGone);
433
407
  }
434
- /**
435
- * True if the engine's own narrate() (waygraph's public export, for a Block
436
- * author explicitly captioning one action) JUST positioned the ring for
437
- * THIS action, moments ago - the automatic per-fill/per-click narration
438
- * below should not immediately overwrite an author's own explicit caption
439
- * with its generic guess.
440
- */
441
- export async function wasJustNarrated(page) {
442
- return page
443
- .evaluate(() => {
444
- const w = window;
445
- return typeof w.__wgLastNarrate === "number" && Date.now() - w.__wgLastNarrate < 500;
446
- })
447
- .catch(() => false);
448
- }
@@ -215,3 +215,43 @@
215
215
  background: #fff; border-radius: var(--wg-radius-lg);
216
216
  padding: 10px 18px; box-shadow: var(--wg-shadow-banner);
217
217
  }
218
+
219
+
220
+ /* The network tab: real page traffic (network-capture.ts) and defineApiBlock's own calls, bottom-left (the one corner
221
+ * free of the zoom HUD and the typing chip). Hidden until ctx.showNetworkTab(); stays up once shown. In the shared
222
+ * bundle, not the runner CSS: a navigation rebuilds the panel the moment the shadow root is back, before any later
223
+ * stylesheet, and without its CSS it painted unstyled and visible, then faded out. */
224
+ #wg-network-log{position:fixed;z-index:2147483646;bottom:14px;left:14px;min-width:260px;max-width:min(92vw,420px);max-height:min(60vh,420px);display:flex;flex-direction:column;border-radius:10px;background:var(--wg-surface);color:#f0e8ff;border:1px solid rgba(124,58,237,.45);box-shadow:0 8px 22px rgba(0,0,0,.4);font:12px/1.4 ui-monospace,"SF Mono",Consolas,monospace;opacity:0;transform:translateY(8px);transition:opacity .25s ease,transform .25s ease;overflow:hidden;}
225
+ #wg-network-log.wg-in{opacity:1;transform:translateY(0);}
226
+ #wg-network-log.wg-net-collapsed .wg-net-list{display:none;}
227
+ #wg-network-log .wg-net-header{display:flex;align-items:center;gap:6px;padding:8px 10px;border-bottom:1px solid rgba(124,58,237,.25);cursor:default;flex:0 0 auto;}
228
+ #wg-network-log .wg-net-title{font:700 10px/1.2 system-ui,sans-serif;color:#c9a6ff;letter-spacing:.06em;text-transform:uppercase;flex:1 1 auto;}
229
+ #wg-network-log .wg-net-count{font:700 10px/1.2 system-ui,sans-serif;color:#9CA3AF;background:rgba(124,58,237,.22);border-radius:999px;padding:1px 7px;}
230
+ #wg-network-log .wg-net-collapse{flex:0 0 auto;width:18px;height:18px;border-radius:5px;border:none;background:rgba(124,58,237,.28);color:#e8dcff;font:700 13px/1 system-ui,sans-serif;cursor:pointer;pointer-events:auto;}
231
+ #wg-network-log .wg-net-collapse:hover{background:rgba(124,58,237,.42);}
232
+ #wg-network-log .wg-net-list{list-style:none;margin:0;padding:6px 8px;display:flex;flex-direction:column;gap:2px;overflow-y:auto;pointer-events:auto;}
233
+ #wg-network-log .wg-net-row{display:flex;align-items:baseline;gap:7px;opacity:0;padding:3px 4px;border-radius:5px;cursor:pointer;transform:translateX(-4px);transition:opacity .2s ease,transform .2s ease;}
234
+ #wg-network-log .wg-net-row:hover,#wg-network-log .wg-net-row.wg-net-open{background:rgba(124,58,237,.18);}
235
+ #wg-network-log .wg-net-row.wg-in{opacity:1;transform:translateX(0);}
236
+ #wg-network-log .wg-net-method{flex:0 0 auto;font-weight:800;font-size:10px;min-width:38px;}
237
+ #wg-network-log .wg-net-method-get{color:#3B82F6;}
238
+ #wg-network-log .wg-net-method-post{color:#22C55E;}
239
+ #wg-network-log .wg-net-method-put{color:#F97316;}
240
+ #wg-network-log .wg-net-method-delete{color:#EF4444;}
241
+ #wg-network-log .wg-net-method-other{color:#9CA3AF;}
242
+ #wg-network-log .wg-net-path{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#e0e0f0;}
243
+ #wg-network-log .wg-net-status{flex:0 0 auto;font-weight:700;color:#22C55E;}
244
+ #wg-network-log .wg-net-row[data-ok="0"] .wg-net-status{color:#EF4444;}
245
+ #wg-network-log .wg-net-ms{flex:0 0 auto;color:#9CA3AF;font-size:10px;}
246
+ #wg-network-log .wg-net-detail{list-style:none;background:rgba(0,0,0,.28);border-radius:6px;padding:8px;margin:2px 0 6px;}
247
+ #wg-network-log .wg-net-detail-section{margin-bottom:8px;}
248
+ #wg-network-log .wg-net-detail-section:last-child{margin-bottom:0;}
249
+ #wg-network-log .wg-net-detail-section h4{margin:0 0 4px;font:700 9px/1.2 system-ui,sans-serif;color:#9CA3AF;letter-spacing:.06em;text-transform:uppercase;}
250
+ #wg-network-log .wg-net-headers{width:100%;border-collapse:collapse;font-size:11px;}
251
+ #wg-network-log .wg-net-headers td{padding:1px 6px 1px 0;vertical-align:top;word-break:break-all;}
252
+ #wg-network-log .wg-net-headers td:first-child{color:#c9a6ff;white-space:nowrap;}
253
+ #wg-network-log .wg-net-body{margin:0;font-size:11px;white-space:pre-wrap;word-break:break-all;max-height:160px;overflow-y:auto;color:#e0e0f0;}
254
+ #wg-network-log .wg-net-curl{color:#7dd3fc;}
255
+ #wg-network-log .wg-net-empty{font-size:11px;color:#6b7280;font-style:italic;}
256
+ #wg-network-log .wg-net-copy{margin-top:5px;padding:3px 9px;border-radius:5px;border:1px solid rgba(124,58,237,.5);background:rgba(124,58,237,.22);color:#e8dcff;font:700 10px system-ui,sans-serif;cursor:pointer;}
257
+ #wg-network-log .wg-net-copy:hover{background:rgba(124,58,237,.38);}
@@ -1,17 +1,19 @@
1
- import { hasAuthoredStubAfter, resolveSlides, runStubPhase } from "../narration/narration.js";
1
+ import { envFlag } from "../../engine/env.js";
2
+ import { hasAuthoredStubAfter, runStubPhase } from "../narration/narration.js";
2
3
  import { applyHighlightStyleDefaults, formatHighlightCaption } from "../narration/style.js";
3
- import { demoLog, logStubPhaseFixtures, logTodoDockFull, paceSpeakFields, summarizeDevice } from "./demo-log.js";
4
- import { extractVerifyHighlights, renderAfterStep } from "./step-panels.js";
5
- import { presentSlides } from "./slides.js";
6
4
  import { logBannerDom, probeBanner } from "./banner-log.js";
7
5
  import { logDocks } from "./before.js";
6
+ import { demoLog, logStubPhaseFixtures, logTodoDockFull, paceSpeakFields, summarizeDevice } from "./demo-log.js";
7
+ import { renderAfterStep } from "./panel-after.js";
8
+ import { showSlides } from "./slides.js";
9
+ import { extractVerifyHighlights } from "./step-panels.js";
8
10
  import { forceCollapsed } from "./view.js";
9
- import { envFlag } from "../../engine/env.js";
10
11
  const quiet = () => envFlag("WAYGRAPH_JSON");
11
12
  export async function showAfter(ctx, view, result) {
12
13
  const { stage, refs } = ctx;
13
- await showSlides(ctx, view, result);
14
14
  const after = hasAuthoredStubAfter(view.step.block, result) ? await narrateAfter(ctx, view, result) : undefined;
15
+ if (after)
16
+ await showSlides(ctx, view, after.phase.slides);
15
17
  const applied = after ? await stage.applyPhase(after.phase) : { appliedTodo: { sync: "keep" }, appliedDevice: { sync: "keep" } };
16
18
  await renderAfterStep(ctx.page, {
17
19
  index: view.index,
@@ -61,20 +63,6 @@ export async function showAfter(ctx, view, result) {
61
63
  }
62
64
  await ctx.gate.wait();
63
65
  }
64
- async function showSlides(ctx, view, result) {
65
- const slides = resolveSlides(view.step.block, { out: result }).map((s) => applyHighlightStyleDefaults(s, view.step.highlightStyle));
66
- if (slides.length === 0)
67
- return;
68
- await presentSlides(ctx.page, slides, ctx.gate.wait, {
69
- title: ctx.title,
70
- blockName: view.step.block.name,
71
- fast: ctx.pacing.gatesFast,
72
- pace: ctx.pacing.demoPace,
73
- episodeNumber: view.step.episodeNumber,
74
- episodeTitle: view.step.episodeTitle,
75
- autoplayMs: ctx.gate.autoplayMs,
76
- });
77
- }
78
66
  /** Runs the Block's stubAfter; its rings carry the flow's highlight style and a caption. */
79
67
  async function narrateAfter(ctx, view, result) {
80
68
  const style = view.step.highlightStyle;
@@ -89,21 +77,10 @@ async function narrateAfter(ctx, view, result) {
89
77
  }),
90
78
  });
91
79
  const highlights = phase.highlights.map((h) => {
80
+ // The ring's look and timing only; its todo, gesture and cursor belong to stubBefore.
92
81
  const styled = applyHighlightStyleDefaults(h, style);
93
- return {
94
- selector: styled.selector,
95
- label: formatHighlightCaption(styled),
96
- duration: styled.duration,
97
- fastMode: styled.fastMode,
98
- tone: styled.tone,
99
- size: styled.size,
100
- weight: styled.weight,
101
- zoom: styled.zoom,
102
- zoomOut: styled.zoomOut,
103
- focus: !!styled.focus,
104
- color: styled.color,
105
- detail: styled.detail,
106
- };
82
+ const { tag: _tag, cursor: _cursor, todo: _todo, slotId: _slotId, gesture: _gesture, ...ring } = styled;
83
+ return { ...ring, label: formatHighlightCaption(styled), focus: !!styled.focus };
107
84
  });
108
85
  return { phase, highlights };
109
86
  }
@@ -1,6 +1,7 @@
1
- import type { WgPage } from "../types.js";
1
+ import type { BannerUiState, WgPage } from "../types.js";
2
+ import type { StubPhaseResult } from "../../engine/stub/stub.js";
2
3
  /** Authored banner UX from a stub phase -> the { pos, collision, hidden } shape installOverlay takes. */
3
- export declare function bannerUiFromPhase(phase: any): Record<string, any>;
4
+ export declare function bannerUiFromPhase(phase: Pick<StubPhaseResult, "titlePos" | "bannerUi"> | null | undefined): BannerUiState;
4
5
  /** Read the live banner: text, position, whether it was moved by ring collision; drains the move log. */
5
6
  export declare function probeBanner(page: WgPage): Promise<{
6
7
  present: boolean;
@@ -30,8 +31,5 @@ export declare function probeBanner(page: WgPage): Promise<{
30
31
  w: number;
31
32
  h: number;
32
33
  log: any;
33
- } | {
34
- present: boolean;
35
- log: never[];
36
34
  }>;
37
- export declare function logBannerDom(tag: any, p: any): void;
35
+ export declare function logBannerDom(tag: string, p: Awaited<ReturnType<typeof probeBanner>> | null | undefined): void;
@@ -1,4 +1,5 @@
1
1
  import { demoLog } from "./demo-log.js";
2
+ import { ignorePageGone } from "../../engine/page-gone.js";
2
3
  /** Authored banner UX from a stub phase -> the { pos, collision, hidden } shape installOverlay takes. */
3
4
  export function bannerUiFromPhase(phase) {
4
5
  const ui = {};
@@ -41,7 +42,8 @@ export async function probeBanner(page) {
41
42
  log,
42
43
  };
43
44
  })
44
- .catch(() => ({ present: false, log: [] }));
45
+ .catch(ignorePageGone)
46
+ .then((probe) => probe ?? { present: false, log: [] });
45
47
  }
46
48
  export function logBannerDom(tag, p) {
47
49
  if (!p)
@@ -1,18 +1,20 @@
1
- // Before a demo step runs: play the Block's stubBefore narration (rings, todos, device, title, caption) and show the
1
+ // Before a demo step runs: play the Block's stubBefore narration (slides, rings, todos, device, title, caption) and show the
2
2
  // step's panel, then log what was painted so an agent without eyes can check it.
3
+ import { envFlag } from "../../engine/env.js";
3
4
  import { runStubPhase } from "../narration/narration.js";
4
5
  import { applyHighlightStyleDefaults, formatHighlightCaption } from "../narration/style.js";
6
+ import { logBannerDom, probeBanner } from "./banner-log.js";
5
7
  import { demoLog, logStubPhaseFixtures, logTodoDockFull, paceSpeakFields, summarizeDevice } from "./demo-log.js";
6
- import { renderBeforeStep } from "./step-panels.js";
8
+ import { renderBeforeStep } from "./panel-before.js";
9
+ import { showSlides } from "./slides.js";
7
10
  import { probeTodoDocksOnPage } from "./todo-advance.js";
8
- import { logBannerDom, probeBanner } from "./banner-log.js";
9
11
  import { forceCollapsed } from "./view.js";
10
- import { envFlag } from "../../engine/env.js";
11
12
  const quiet = () => envFlag("WAYGRAPH_JSON");
12
13
  export async function showBefore(ctx, view) {
13
14
  const { stage } = ctx;
14
15
  const phase = await narrateBefore(ctx, view);
15
16
  const { appliedTodo } = await stage.applyPhase(phase, { reapplyDeviceOnKeep: true });
17
+ await showSlides(ctx, view, phase.slides);
16
18
  if (phase.title && String(phase.title).trim())
17
19
  ctx.stickyTitle = String(phase.title).trim();
18
20
  view.overlayTitle = ctx.stickyTitle || (view.step.episodeTitle && String(view.step.episodeTitle).trim()) || ctx.title || "waygraph demo";
@@ -1,5 +1,21 @@
1
- export declare function demoPaceKind(pace: any): "normal" | "blitz" | "fast" | "slow" | "ms" | "num-slow";
2
- export declare function paceSpeakFields(pace: any, baseAutoplayMs: any): {
1
+ import type { StubPhaseResult } from "../../engine/stub/stub.js";
2
+ import type { TodoDockState } from "../../engine/stub/todos.js";
3
+ import type { DeviceState } from "../../engine/stub/viewport.js";
4
+ /** What a HIGHLIGHT log line shows: a ring as painted (no selector for a narrated caption). Logged, not checked. */
5
+ type LoggedHighlight = {
6
+ selector?: string | null | undefined;
7
+ label?: string | undefined;
8
+ tone?: string | undefined;
9
+ color?: string | undefined;
10
+ focus?: boolean | undefined;
11
+ zoom?: number | undefined;
12
+ zoomOut?: boolean | undefined;
13
+ gesture?: string | undefined;
14
+ weight?: string | undefined;
15
+ size?: string | undefined;
16
+ };
17
+ export declare function demoPaceKind(pace: unknown): string;
18
+ export declare function paceSpeakFields(pace: unknown, baseAutoplayMs: number | undefined): {
3
19
  pace: import("../../index.js").DemoPace;
4
20
  paceBadge: string;
5
21
  paceLabel: string;
@@ -19,13 +35,14 @@ export declare const ANSI: {
19
35
  magenta: string;
20
36
  white: string;
21
37
  };
22
- export declare function ansiPaint(code: any, text: any): string;
23
- export declare function demoLog(msg: any): void;
38
+ export declare function ansiPaint(code: string, text: unknown): string;
39
+ export declare function demoLog(msg: string): void;
24
40
  /** Fixture catch-all: how it was called + value. */
25
- export declare function demoFixture(kind: any, call: any, value: any): void;
41
+ export declare function demoFixture(kind: string, call: string, value?: unknown): void;
26
42
  /** Highlight trigger line: PURPLE · Cart items - Product landed in cart */
27
- export declare function demoHighlight(h: any, extra: any): void;
28
- export declare function logStubPhaseFixtures(phaseName: any, phase: any): void;
43
+ export declare function demoHighlight(h: LoggedHighlight | null | undefined, extra?: string): void;
44
+ export declare function logStubPhaseFixtures(phaseName: string, phase: StubPhaseResult | null | undefined): void;
29
45
  /** Full todo dump for agents (every group/item id + flags). */
30
- export declare function logTodoDockFull(dock: any, tag: any): void;
31
- export declare function summarizeDevice(d: any): string;
46
+ export declare function logTodoDockFull(dock: TodoDockState | null | undefined, tag?: string): void;
47
+ export declare function summarizeDevice(d: DeviceState | null | undefined): string;
48
+ export {};
@@ -14,7 +14,7 @@ export function demoPaceKind(pace) {
14
14
  }
15
15
  export function paceSpeakFields(pace, baseAutoplayMs) {
16
16
  const p = normalizeDemoPace(pace);
17
- const base = Number.isFinite(baseAutoplayMs) && baseAutoplayMs > 0 ? baseAutoplayMs : 1800;
17
+ const base = baseAutoplayMs !== undefined && Number.isFinite(baseAutoplayMs) && baseAutoplayMs > 0 ? baseAutoplayMs : 1800;
18
18
  return {
19
19
  pace: p,
20
20
  paceBadge: formatDemoPaceBadge(p, base),
@@ -207,12 +207,10 @@ export function logStubPhaseFixtures(phaseName, phase) {
207
207
  }
208
208
  : phase.todoSync);
209
209
  }
210
- const rings = phase.highlights || [];
211
- for (let i = 0; i < rings.length; i++) {
212
- const h = rings[i];
213
- demoFixture(tag, "ctx.ring/highlights[" + i + "]", {
210
+ phase.highlights.forEach((h, i) => {
211
+ demoFixture(tag, "ctx.ring[" + i + "]", {
214
212
  selector: h.selector,
215
- label: h.label || h.caption,
213
+ label: h.label,
216
214
  detail: h.detail,
217
215
  tone: h.tone || "planned",
218
216
  color: h.color,
@@ -222,10 +220,7 @@ export function logStubPhaseFixtures(phaseName, phase) {
222
220
  gesture: h.gesture,
223
221
  duration: h.duration,
224
222
  });
225
- }
226
- if (phase.slides && phase.slides.length) {
227
- demoFixture(tag, "ctx.slides(...)", { n: phase.slides.length });
228
- }
223
+ });
229
224
  }
230
225
  function summarizeTodoDock(dock) {
231
226
  if (!dock)
@@ -267,8 +262,7 @@ export function logTodoDockFull(dock, tag) {
267
262
  demoLog(prefix + " (no groups)");
268
263
  return;
269
264
  }
270
- for (let gi = 0; gi < groups.length; gi++) {
271
- const g = groups[gi];
265
+ groups.forEach((g, gi) => {
272
266
  const gLabel = "group[" +
273
267
  gi +
274
268
  "]" +
@@ -276,22 +270,11 @@ export function logTodoDockFull(dock, tag) {
276
270
  (g.title ? ' title="' + String(g.title).slice(0, 40) + '"' : "") +
277
271
  (g.style ? " style=" + g.style : "");
278
272
  demoLog(prefix + " " + gLabel);
279
- const items = g.items || [];
280
- for (let ti = 0; ti < items.length; ti++) {
281
- const t = items[ti];
273
+ (g.items || []).forEach((t, ti) => {
282
274
  const mark = t.done ? "done" : t.current ? "CURRENT" : "pending";
283
- demoLog(prefix +
284
- " [" +
285
- ti +
286
- "] " +
287
- mark +
288
- (t.id ? " id=" + t.id : "") +
289
- ' text="' +
290
- String(t.text || "").slice(0, 80) +
291
- '"' +
292
- (t.detail ? ' detail="' + String(t.detail).slice(0, 40) + '"' : ""));
293
- }
294
- }
275
+ demoLog(prefix + " [" + ti + "] " + mark + (t.id ? " id=" + t.id : "") + ' text="' + String(t.text || "").slice(0, 80) + '"');
276
+ });
277
+ });
295
278
  }
296
279
  export function summarizeDevice(d) {
297
280
  if (!d)
@@ -1,3 +1,4 @@
1
- import type { WgPage } from "../types.js";
1
+ import type { StepperRefs, WgPage } from "../types.js";
2
+ import type { Pacing } from "./pacing.js";
2
3
  import type { MemPage } from "../../engine/mem/mem.js";
3
- export declare function instrumentInteractionHighlighting(page: WgPage, mem: MemPage, slowMo: number, pacing: any, stubBeforeRef: any, todoDockRef: any, deviceRef: any): void;
4
+ export declare function instrumentInteractionHighlighting(page: WgPage, mem: MemPage, slowMo: number, pacing: Pacing, stubBeforeRef: StepperRefs["stubBefore"], todoDockRef: StepperRefs["todoDock"], deviceRef: StepperRefs["device"]): void;
@@ -1,5 +1,5 @@
1
1
  import { installOverlay } from "../overlay/overlay.js";
2
- import { applyHighlightZoom, captionForLocator, dwellMatchedStub, ensureLocatorInView, hideRing, matchStubIndexForLocator, showRing, wasJustNarrated } from "../overlay/rings.js";
2
+ import { applyHighlightZoom, captionForLocator, dwellMatchedStub, ensureLocatorInView, hideRing, matchStubIndexForLocator, showRing } from "../overlay/rings.js";
3
3
  import { syncTodoDockAdvance } from "./todo-advance.js";
4
4
  import { clickPulseAt, moveCursorTo } from "../overlay/cursor.js";
5
5
  import { demoHighlight } from "./demo-log.js";
@@ -70,7 +70,7 @@ export function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stu
70
70
  const memTrack = { lastKeyName: null, at: 0 };
71
71
  const originalGet = mem.get.bind(mem);
72
72
  mem.get = (key) => {
73
- memTrack.lastKeyName = key && key.name;
73
+ memTrack.lastKeyName = key.name;
74
74
  memTrack.at = Date.now();
75
75
  return originalGet(key);
76
76
  };
@@ -109,7 +109,7 @@ export function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stu
109
109
  catch {
110
110
  /* ignore */
111
111
  }
112
- if (box && !(await wasJustNarrated(page))) {
112
+ if (box) {
113
113
  // Only trust the "last mem.get()" as THIS fill's source if it
114
114
  // happened recently - a stale read from several actions ago is
115
115
  // more likely unrelated than actually describing this field.
@@ -126,33 +126,28 @@ export function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stu
126
126
  await applyHighlightZoom(page, stub.selector, stub.zoom, stub.zoomOut);
127
127
  }
128
128
  demoHighlight({
129
- selector: stub && stub.selector,
129
+ selector: stub?.selector,
130
130
  label: cap.label,
131
- tone: cap.tone || (stub && stub.tone) || "planned",
132
- color: stub && stub.color,
133
- focus: !!(stub && stub.focus),
134
- zoom: stub && stub.zoom,
135
- zoomOut: stub && stub.zoomOut,
136
- gesture: stub && stub.gesture,
131
+ tone: cap.tone || (stub?.tone) || "planned",
132
+ color: stub?.color,
133
+ focus: !!(stub?.focus),
134
+ zoom: stub?.zoom,
135
+ zoomOut: stub?.zoomOut,
136
+ gesture: stub?.gesture,
137
137
  weight: cap.weight,
138
138
  size: cap.size,
139
139
  }, "fill");
140
140
  await showRing(page, box, cap.label, cap.tone, {
141
141
  size: cap.size,
142
142
  weight: cap.weight,
143
- selector: stub && stub.selector ? stub.selector : undefined,
144
- focus: !!(stub && stub.focus),
143
+ selector: stub?.selector,
144
+ focus: !!(stub?.focus),
145
145
  });
146
146
  await dwellMatchedStub(page, locator, stubs, pacing);
147
147
  await new Promise((res) => setTimeout(res, skipTheater() ? 0 : 200));
148
148
  if (cap && cap.label)
149
149
  typeLabel = String(cap.label).slice(0, 40);
150
150
  }
151
- else if (box) {
152
- // Ring/caption already handled by narrate() - still move the
153
- // cursor there, just skip re-showing the ring with a generic label.
154
- await moveCursorTo(page, box, cursorMs(200));
155
- }
156
151
  await page
157
152
  .evaluate((label) => {
158
153
  if (window.__wgSetTypingBadge)
@@ -191,8 +186,7 @@ export function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stu
191
186
  // own click scroll is instant and reads as a teleport under --fast.
192
187
  await ensureLocatorInView(locator, { instant: skipTheater() });
193
188
  const box = await locator.boundingBox();
194
- const narrated = box ? await wasJustNarrated(page) : false;
195
- if (box && !narrated) {
189
+ if (box) {
196
190
  let fallback = "click";
197
191
  try {
198
192
  const text = (await locator.textContent())?.trim();
@@ -218,22 +212,22 @@ export function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stu
218
212
  await applyHighlightZoom(page, stub.selector, stub.zoom, stub.zoomOut);
219
213
  }
220
214
  demoHighlight({
221
- selector: stub && stub.selector,
215
+ selector: stub?.selector,
222
216
  label: cap.label,
223
- tone: cap.tone || (stub && stub.tone) || "planned",
224
- color: stub && stub.color,
225
- focus: !!(stub && stub.focus),
226
- zoom: stub && stub.zoom,
227
- zoomOut: stub && stub.zoomOut,
228
- gesture: stub && stub.gesture,
217
+ tone: cap.tone || (stub?.tone) || "planned",
218
+ color: stub?.color,
219
+ focus: !!(stub?.focus),
220
+ zoom: stub?.zoom,
221
+ zoomOut: stub?.zoomOut,
222
+ gesture: stub?.gesture,
229
223
  weight: cap.weight,
230
224
  size: cap.size,
231
225
  }, "click");
232
226
  await showRing(page, box, cap.label, cap.tone, {
233
227
  size: cap.size,
234
228
  weight: cap.weight,
235
- selector: stub && stub.selector ? stub.selector : undefined,
236
- focus: !!(stub && stub.focus),
229
+ selector: stub?.selector,
230
+ focus: !!(stub?.focus),
237
231
  });
238
232
  await dwellMatchedStub(page, locator, stubs, pacing);
239
233
  // "pop for a few seconds" - the user's own phrase, matching the
@@ -241,12 +235,6 @@ export function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stu
241
235
  // services/help-center-clip-engine's video-pipeline.
242
236
  await new Promise((res) => setTimeout(res, clickPrePop()));
243
237
  }
244
- else if (box) {
245
- // Ring/caption already handled by narrate() - still move the
246
- // cursor + pulse the click point, just skip re-showing the ring.
247
- clickPoint = await moveCursorTo(page, box, cursorMs(200));
248
- clickTone = "planned";
249
- }
250
238
  if (clickPoint) {
251
239
  await clickPulseAt(page, clickPoint.x, clickPoint.y, clickTone);
252
240
  // clickPulseAt only triggers the CSS animation class - it doesn't
@@ -0,0 +1,17 @@
1
+ import type { Checkpoint } from "../../engine/checkpoint/checkpoint.js";
2
+ import type { DemoPace } from "../../engine/stub/pace.js";
3
+ import type { ResolvedHighlight } from "../../engine/stub/ring.js";
4
+ import type { TodoDockState } from "../../engine/stub/todos.js";
5
+ import type { Ref, WgPage } from "../types.js";
6
+ import { type PanelTodos, type StepPanelInfo } from "./step-panels.js";
7
+ /** The panel after a step: the Checkpoint it resolved to, the rings to cycle, and whether it was the last step. */
8
+ export interface AfterPanelInfo extends StepPanelInfo, PanelTodos {
9
+ result: Checkpoint<string>;
10
+ resultTag: string;
11
+ highlights: readonly ResolvedHighlight[];
12
+ gatesFast?: boolean | undefined;
13
+ isLast: boolean;
14
+ pace?: DemoPace | undefined;
15
+ todoDockRef?: Ref<TodoDockState | undefined> | undefined;
16
+ }
17
+ export declare function renderAfterStep(page: WgPage, info: AfterPanelInfo): Promise<void>;