waygraph 0.16.1 → 0.16.4

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 (154) hide show
  1. package/README.md +12 -0
  2. package/dist/analysis/practices-check.d.ts +1 -1
  3. package/dist/analysis/practices-check.js +36 -0
  4. package/dist/beta.d.ts +6 -0
  5. package/dist/beta.js +5 -0
  6. package/dist/cli/commands/codemod-flows.js +2 -2
  7. package/dist/cli/commands/codemod-stubs.js +132 -2
  8. package/dist/cli/commands/pilot.js +3 -16
  9. package/dist/cli/commands/session-ops.js +26 -1
  10. package/dist/cli/flags.d.ts +1 -1
  11. package/dist/cli/flags.js +1 -1
  12. package/dist/cli/run-chain.js +2 -2
  13. package/dist/cli/usage.js +6 -0
  14. package/dist/cli/util.js +1 -1
  15. package/dist/demo/narration/demo-pace.d.ts +1 -1
  16. package/dist/demo/narration/demo-pace.js +1 -1
  17. package/dist/demo/narration/merge.d.ts +1 -1
  18. package/dist/demo/narration/merge.js +1 -1
  19. package/dist/demo/narration/narration.d.ts +2 -6
  20. package/dist/demo/narration/narration.js +5 -17
  21. package/dist/demo/narration/stub-ctx.js +126 -124
  22. package/dist/demo/overlay/cursor.js +1 -1
  23. package/dist/demo/overlay/inpage/banner.js +5 -0
  24. package/dist/demo/overlay/inpage/core.js +1 -6
  25. package/dist/demo/overlay/inpage/network.js +1 -1
  26. package/dist/demo/overlay/inpage/todos.js +0 -15
  27. package/dist/demo/overlay/network-capture.js +2 -2
  28. package/dist/demo/overlay/overlay-css.js +19 -76
  29. package/dist/demo/overlay/overlay-stage.js +1 -1
  30. package/dist/demo/overlay/overlay.js +3 -2
  31. package/dist/demo/overlay/phase-fixtures.js +5 -5
  32. package/dist/demo/overlay/pilot/shell.js +7 -5
  33. package/dist/demo/overlay/rings.d.ts +0 -8
  34. package/dist/demo/overlay/rings.js +0 -15
  35. package/dist/demo/overlay/ui/components/ring.js +2 -2
  36. package/dist/demo/overlay/ui/overlay.css +136 -33
  37. package/dist/demo/overlay/ui/shadow.js +69 -2
  38. package/dist/demo/stepper/after.js +4 -17
  39. package/dist/demo/stepper/before.js +3 -1
  40. package/dist/demo/stepper/demo-log.js +8 -8
  41. package/dist/demo/stepper/interaction-patch.js +3 -15
  42. package/dist/demo/stepper/panel-after.js +1 -1
  43. package/dist/demo/stepper/slides.d.ts +3 -0
  44. package/dist/demo/stepper/slides.js +18 -2
  45. package/dist/demo/stepper/stepper.js +1 -1
  46. package/dist/demo/stepper/teardown.js +0 -1
  47. package/dist/demo/verbose-log.js +1 -1
  48. package/dist/engine/block/block.d.ts +1 -12
  49. package/dist/engine/block/kind.d.ts +3 -0
  50. package/dist/engine/block/kind.js +11 -0
  51. package/dist/engine/block/kinds/method.d.ts +0 -2
  52. package/dist/engine/block/kinds/method.js +0 -1
  53. package/dist/engine/block/kinds/nav.js +1 -1
  54. package/dist/engine/errors.d.ts +12 -0
  55. package/dist/engine/errors.js +12 -0
  56. package/dist/engine/read/read-block.d.ts +61 -0
  57. package/dist/engine/read/read-block.js +94 -0
  58. package/dist/engine/read/read-key.d.ts +32 -0
  59. package/dist/engine/read/read-key.js +34 -0
  60. package/dist/engine/read/read-page.d.ts +19 -0
  61. package/dist/engine/read/read-page.js +50 -0
  62. package/dist/engine/read/readers.d.ts +82 -0
  63. package/dist/engine/read/readers.js +43 -0
  64. package/dist/engine/read/run-read.d.ts +17 -0
  65. package/dist/engine/read/run-read.js +156 -0
  66. package/dist/engine/run/connect.js +3 -2
  67. package/dist/engine/run/events.d.ts +28 -10
  68. package/dist/engine/run/events.js +52 -38
  69. package/dist/engine/run/run.d.ts +1 -19
  70. package/dist/engine/run/run.js +21 -97
  71. package/dist/engine/stub/ring.d.ts +2 -4
  72. package/dist/engine/stub/ring.js +1 -0
  73. package/dist/engine/stub/stub.d.ts +38 -25
  74. package/dist/index.d.ts +9 -7
  75. package/dist/index.js +11 -6
  76. package/dist/live/auto/auto.js +2 -3
  77. package/dist/live/auto/menu.js +3 -2
  78. package/dist/live/auto-session/auto-session.d.ts +9 -2
  79. package/dist/live/auto-session/auto-session.js +23 -20
  80. package/dist/live/auto-session/feed.d.ts +16 -0
  81. package/dist/live/auto-session/feed.js +71 -0
  82. package/dist/live/auto-session/helpers.d.ts +7 -0
  83. package/dist/live/auto-session/helpers.js +9 -0
  84. package/dist/live/auto-session/ipc.d.ts +15 -47
  85. package/dist/live/auto-session/ipc.js +34 -4
  86. package/dist/live/auto-session/protocol.d.ts +127 -0
  87. package/dist/live/auto-session/protocol.js +6 -0
  88. package/dist/live/auto-session/read.d.ts +25 -0
  89. package/dist/live/auto-session/read.js +50 -0
  90. package/dist/live/traverse/worker.d.ts +1 -0
  91. package/dist/live/traverse/worker.js +6 -1
  92. package/dist/manifest/manifest.d.ts +22 -2
  93. package/dist/manifest/manifest.js +27 -1
  94. package/dist/manifest/project.d.ts +10 -1
  95. package/dist/manifest/project.js +9 -12
  96. package/dist/manifest/read-blocks.d.ts +2 -0
  97. package/dist/manifest/read-blocks.js +23 -0
  98. package/dist/manifest/scan.d.ts +3 -2
  99. package/dist/manifest/scan.js +8 -4
  100. package/dist/runner/plan/find.js +2 -2
  101. package/dist/runner/plan/plan.js +1 -1
  102. package/dist/runner/plan/seed-mem.js +5 -5
  103. package/dist/runner/plan/spec.js +1 -1
  104. package/dist/runner/runner.d.ts +0 -1
  105. package/dist/runner/runner.js +6 -6
  106. package/dist/runtime/full-launch.d.ts +2 -1
  107. package/dist/runtime/full-launch.js +29 -27
  108. package/dist/runtime/run-log.d.ts +2 -0
  109. package/dist/runtime/run-log.js +13 -6
  110. package/examples/saucedemo/README.md +2 -2
  111. package/examples/saucedemo/src/blocks/SITE-MAP.md +3 -0
  112. package/examples/saucedemo/src/blocks/saucedemo-web/cart/cart.read.block.ts +26 -0
  113. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
  114. package/examples/saucedemo/src/blocks/saucedemo-web/header.read.block.ts +25 -0
  115. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/inventory.read.block.ts +28 -0
  116. package/examples/saucedemo/src/flows/api-demo.flow.ts +1 -1
  117. package/examples/saucedemo/src/flows/cart-bulk.flow.ts +1 -1
  118. package/examples/saucedemo/src/flows/cart-continue.flow.ts +1 -1
  119. package/examples/saucedemo/src/flows/catalog-logout.flow.ts +1 -1
  120. package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +3 -2
  121. package/examples/saucedemo/src/flows/checkout.flow.ts +13 -12
  122. package/examples/saucedemo/src/flows/login.flow.ts +1 -1
  123. package/examples/saucedemo/src/flows/shop.flow.ts +3 -2
  124. package/examples/saucedemo/src/flows/stub-on-error.flow.ts +1 -1
  125. package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +1 -1
  126. package/examples/saucedemo/src/states/checkout.reads.ts +9 -0
  127. package/examples/saucedemo/src/stubs/device-showcase.ts +70 -76
  128. package/examples/saucedemo/src/stubs/finish-order.ts +38 -0
  129. package/examples/saucedemo/src/stubs/login.ts +5 -5
  130. package/package.json +5 -1
  131. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
  132. package/templates/quickstart/src/flows/api-demo.flow.ts +1 -1
  133. package/templates/quickstart/src/flows/cart-bulk.flow.ts +1 -1
  134. package/templates/quickstart/src/flows/cart-continue.flow.ts +1 -1
  135. package/templates/quickstart/src/flows/catalog-logout.flow.ts +1 -1
  136. package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +3 -2
  137. package/templates/quickstart/src/flows/checkout.flow.ts +13 -12
  138. package/templates/quickstart/src/flows/login.flow.ts +1 -1
  139. package/templates/quickstart/src/flows/shop.flow.ts +3 -2
  140. package/templates/quickstart/src/flows/stub-on-error.flow.ts +1 -1
  141. package/templates/quickstart/src/flows/viewer-blocked.flow.ts +1 -1
  142. package/templates/quickstart/src/stubs/device-showcase.ts +70 -76
  143. package/templates/quickstart/src/stubs/finish-order.ts +38 -0
  144. package/templates/quickstart/src/stubs/login.ts +5 -5
  145. package/templates/scaffold/src/flows/(external)/hoppscotch/auth.flow.ts +1 -1
  146. package/templates/scaffold/src/flows/(external)/hoppscotch/get.flow.ts +1 -1
  147. package/templates/scaffold/src/flows/(external)/hoppscotch/post.flow.ts +1 -1
  148. package/templates/scaffold/src/flows/(user-stories)/email-verification.flow.ts +4 -4
  149. package/templates/scaffold/src/flows/README.md +1 -1
  150. package/templates/scaffold/src/flows/home/cart.flow.ts +7 -7
  151. package/templates/scaffold/src/flows/home/hello.flow.ts +10 -7
  152. package/templates/scaffold/src/map/(app_base)/home/_methods/assert-hello.method.block.ts +1 -9
  153. package/templates/skills/waygraph-convention.skill.md +10 -2
  154. package/templates/skills/waygraph-pilot.skill.md +7 -0
@@ -13,8 +13,10 @@ import { ignorePageGone } from "../../../engine/page-gone.js";
13
13
  // re-invented) - genuinely the same visual language, not just the ring CSS
14
14
  // this file already shared via WAYGRAPH_RING_CSS.
15
15
  const OVERLAY_CSS = `
16
+ /* Every element here sits in a slot of the overlay layout (ui/css/layout.css) - bottom-right, bottom-left, top-right -
17
+ or on its stage (the spotlight); none is positioned by hand. */
16
18
  #wg-pilot-overlay {
17
- position: fixed; z-index: 2147483000; bottom: 12px; right: 12px;
19
+ z-index: var(--wg-z-top); display: flex; flex-direction: column; align-items: flex-end;
18
20
  font: 12px/1.4 ui-monospace, "SF Mono", Consolas, monospace;
19
21
  color: #fff; pointer-events: auto;
20
22
  }
@@ -56,7 +58,7 @@ const OVERLAY_CSS = `
56
58
  #wg-pilot-panel-graph .wg-pilot-graph-edge { padding: 2px 0 2px 14px; border-left: 2px solid rgba(255,255,255,.15); font-size: 11px; }
57
59
  #wg-pilot-panel-graph .wg-pilot-graph-edge .wg-pilot-kind { color: #c9a6ff; }
58
60
  #wg-pilot-activity {
59
- position: fixed; z-index: 2147483000; bottom: 12px; left: 12px;
61
+ z-index: var(--wg-z-chrome);
60
62
  font: 12px/1.4 ui-monospace, "SF Mono", Consolas, monospace;
61
63
  background: ${SURFACE}; color: #fff; padding: 6px 10px; border-radius: 8px;
62
64
  box-shadow: 0 2px 8px rgba(0,0,0,0.25); white-space: nowrap; pointer-events: none;
@@ -68,12 +70,12 @@ const OVERLAY_CSS = `
68
70
  #wg-banner all come from the ONE shared bundle ensureShadowRoot installs (ui/css/*.css) - not
69
71
  redefined here. */
70
72
  /* Spotlight (demo focus:true) - dim page, cutout around target. */
71
- #wg-pilot-fx-focus{position:fixed;z-index:2147483644;pointer-events:none;
73
+ #wg-pilot-fx-focus{position:fixed;z-index:var(--wg-z-focus);pointer-events:none;
72
74
  border-radius:12px;box-shadow:0 0 0 9999px rgba(8,4,20,.62);
73
75
  opacity:0;transition:opacity .3s ease,left .3s ease,top .3s ease,width .3s ease,height .3s ease;}
74
76
  #wg-pilot-fx-focus.wg-in{opacity:1;}
75
77
  /* Zoom HUD (demo parity - badge only; no CSS scale of the page). */
76
- #wg-pilot-fx-zoom{position:fixed;z-index:2147483646;bottom:14px;right:14px;
78
+ #wg-pilot-fx-zoom{z-index:var(--wg-z-chrome);
77
79
  display:flex;align-items:center;gap:7px;padding:6px 11px 6px 8px;
78
80
  border-radius:999px;background:${SURFACE};color:#f0e8ff;
79
81
  border:1px solid rgba(250,204,21,.6);box-shadow:0 6px 18px rgba(0,0,0,.4);
@@ -83,7 +85,7 @@ const OVERLAY_CSS = `
83
85
  #wg-pilot-fx-zoom .wg-pilot-fx-zoom-ico svg{width:14px;height:14px;display:block;}
84
86
  #wg-pilot-fx-zoom .wg-pilot-fx-zoom-val{color:#fde68a;font-variant-numeric:tabular-nums;font-weight:800;min-width:3.2em;}
85
87
  #wg-pilot-fx-zoom[data-zoomed="1"]{border-color:#fbbf24;}
86
- #wg-pilot-fx-device{position:fixed;z-index:2147483646;top:14px;right:14px;
88
+ #wg-pilot-fx-device{z-index:var(--wg-z-chrome);
87
89
  padding:8px 12px;border-radius:10px;background:${SURFACE};color:#f0e8ff;
88
90
  border:1px solid rgba(124,58,237,.55);font:700 12px/1.2 system-ui,sans-serif;
89
91
  pointer-events:none;box-shadow:0 6px 18px rgba(0,0,0,.35);}
@@ -51,11 +51,3 @@ export declare function captionForLocator(page: WgPage, locator: Locator, stubs:
51
51
  }>;
52
52
  export declare function dwellMatchedStub(page: WgPage, locator: Locator, stubs: readonly ResolvedHighlight[] | undefined, pacing: Pick<Pacing, "gatesFast" | "demoPace"> | undefined): Promise<void>;
53
53
  export declare function hideRing(page: WgPage): Promise<void>;
54
- /**
55
- * True if the engine's own narrate() (waygraph's public export, for a Block
56
- * author explicitly captioning one action) JUST positioned the ring for
57
- * THIS action, moments ago - the automatic per-fill/per-click narration
58
- * below should not immediately overwrite an author's own explicit caption
59
- * with its generic guess.
60
- */
61
- export declare function wasJustNarrated(page: WgPage): Promise<boolean>;
@@ -405,18 +405,3 @@ export async function hideRing(page) {
405
405
  })
406
406
  .catch(ignorePageGone);
407
407
  }
408
- /**
409
- * True if the engine's own narrate() (waygraph's public export, for a Block
410
- * author explicitly captioning one action) JUST positioned the ring for
411
- * THIS action, moments ago - the automatic per-fill/per-click narration
412
- * below should not immediately overwrite an author's own explicit caption
413
- * with its generic guess.
414
- */
415
- export async function wasJustNarrated(page) {
416
- return page
417
- .evaluate(() => {
418
- const w = window;
419
- return typeof w.__wgLastNarrate === "number" && Date.now() - w.__wgLastNarrate < 500;
420
- })
421
- .catch(() => false);
422
- }
@@ -11,13 +11,13 @@ function toneRules(sel, decl, only) {
11
11
  /** The highlight ring + its label (`#wg-ring`, `#wg-ring-label`): tone / size / weight variants. */
12
12
  export function ringCss() {
13
13
  const p = TONES.planned;
14
- return ("#wg-ring{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
14
+ return ("#wg-ring{position:fixed;z-index:var(--wg-z-ring,2147483646);pointer-events:none;opacity:0;" +
15
15
  `border:2.5px solid ${p.ring};border-radius:10px;` +
16
16
  // Opacity fade only - NEVER transition border-color/box-shadow: tone swaps must snap.
17
17
  `box-shadow:0 0 0 4px ${p.glow};transition:opacity .3s ease;}` +
18
18
  toneRules("#wg-ring", (t) => `border-color:${t.ring};box-shadow:0 0 0 4px ${t.glow};`) +
19
19
  // A real element (not ::after) so JS can measure it and clamp it back onto the screen.
20
- "#wg-ring-label{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
20
+ "#wg-ring-label{position:fixed;z-index:var(--wg-z-ring,2147483646);pointer-events:none;opacity:0;" +
21
21
  `max-width:min(360px,70vw);white-space:normal;padding:6px 10px;border-radius:7px;background:${p.labelBg};color:${p.labelFg};` +
22
22
  "font:600 12px/1.35 system-ui,sans-serif;transition:opacity .3s ease;}" +
23
23
  "#wg-ring-label::after{content:attr(data-label);}" +
@@ -31,16 +31,21 @@
31
31
  --wg-font: system-ui, sans-serif;
32
32
  --wg-font-mono: ui-monospace, "SF Mono", Consolas, monospace;
33
33
 
34
- /* Stacking: Open Props' --layer-important is the one legitimate use of a max z-index (a
35
- floating control that must sit above literally anything the host page can throw at it). */
36
- --wg-z-top: var(--layer-important, 2147483647);
37
- --wg-z-ring: 2147483646;
38
- --wg-z-fx: 2147483645;
39
- --wg-z-focus: 2147483644;
40
- --wg-z-dock: 2147483000;
41
- /* Banner sits BELOW rings/labels/todos - a title card must never shadow a highlight. */
42
- --wg-z-banner: 2147483000;
43
-
34
+ /* Stacking. All overlay UI lives in one fixed parent (#wg-ui, layout.css) that sits above the host page with the one
35
+ max z-index; these values only order the layers inside it, lowest first. */
36
+ --wg-z-ui: var(--layer-important, 2147483647);
37
+ --wg-z-focus: 1; /* the spotlight veil; chrome stepping back while a ring is the focus */
38
+ --wg-z-chrome: 2; /* banner, todo docks, caption, chips, network tab */
39
+ --wg-z-dock: 2;
40
+ --wg-z-banner: 2;
41
+ --wg-z-fx: 4; /* rings and their labels: a highlight is never covered by chrome */
42
+ --wg-z-ring: 4;
43
+ --wg-z-top: 6; /* the step panel and other controls a person clicks */
44
+ --wg-z-toast: 7; /* short-lived notices (device change) */
45
+ --wg-z-cursor: 8; /* the cursor, click pulse and swipe trail: the pointer is always on top */
46
+ /* Spacing of the chrome layout: between items in a slot, and from the viewport edge. */
47
+ --wg-gap: 10px;
48
+ --wg-edge: 14px;
44
49
  --wg-ease: var(--ease-3, cubic-bezier(.22,1,.36,1));
45
50
  --wg-shadow-dock: var(--shadow-3, 0 2px 10px rgba(0,0,0,.3));
46
51
  --wg-shadow-banner: var(--shadow-4, 0 8px 20px rgba(0,0,0,.3));
@@ -50,6 +55,74 @@
50
55
  }
51
56
 
52
57
 
58
+ /*
59
+ * The overlay's one layout. Every piece of waygraph UI lives in #wg-ui, a fixed layer over the page, in one of two
60
+ * places:
61
+ *
62
+ * #wg-stage things anchored to the page: ring, label, cursor, click pulse, spotlight veil, swipe trail. They follow
63
+ * a target's coordinates (position: fixed against the viewport).
64
+ * #wg-chrome everything else, placed in a slot. Nothing in it is positioned by hand: a slot is a flex column with
65
+ * a gap, so what shares a place stacks instead of overlapping, and a slot never grows past its row.
66
+ *
67
+ * top-left | top-center | top-right banner (by its side) · device chip / toast
68
+ * left | | right todo docks (by side)
69
+ * bottom-left | bottom-center | bottom-right network tab · typing, caption, step panel · zoom
70
+ *
71
+ * The slot an element goes in is decided once, in ui/shadow.ts (by id and data-pos), and a data-pos change moves it.
72
+ * Each row lays out on its own, so a wide step panel never squeezes the side docks.
73
+ */
74
+ #wg-ui { position: fixed; inset: 0; z-index: var(--wg-z-ui); pointer-events: none; }
75
+ #wg-stage { display: contents; }
76
+ #wg-chrome {
77
+ position: absolute; inset: 0; box-sizing: border-box;
78
+ display: flex; flex-direction: column; gap: var(--wg-gap); padding: var(--wg-edge);
79
+ }
80
+ .wg-row { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: var(--wg-gap); min-height: 0; }
81
+ .wg-row-top { flex: 0 0 auto; align-items: start; }
82
+ .wg-row-middle { flex: 1 1 0; display: flex; justify-content: space-between; align-items: stretch; }
83
+ .wg-row-bottom { flex: 0 1 auto; align-items: end; max-height: 62vh; }
84
+ .wg-slot {
85
+ display: flex; flex-direction: column; gap: var(--wg-gap);
86
+ min-width: 0; min-height: 0; max-height: 100%; max-width: 100%;
87
+ }
88
+ .wg-slot[data-slot$="left"] { align-items: flex-start; }
89
+ .wg-slot[data-slot$="center"] { align-items: center; }
90
+ .wg-slot[data-slot$="right"] { align-items: flex-end; }
91
+ .wg-row-bottom .wg-slot { justify-content: flex-end; }
92
+ /* A slotted element shrinks (and scrolls inside) rather than spilling out of its slot: a min-width of its own (the
93
+ network tab's 260px) must not push it past the slot into its neighbour. */
94
+ .wg-slot > * { position: relative; flex: 0 1 auto; box-sizing: border-box; pointer-events: auto; }
95
+ /* The slot's limits win over an element's own sizing (an #id rule's min-width / max-height), hence the id here. */
96
+ #wg-chrome .wg-slot > * { min-width: 0; min-height: 0; max-width: 100%; max-height: 100%; }
97
+
98
+ /* Order inside a shared slot: what is being typed, what the narration says, then the controls. */
99
+ #wg-typing-badge { order: 1; }
100
+ #wg-caption-bar { order: 2; }
101
+ #wg-panel, #wg-auto-panel { order: 3; }
102
+ #wg-device-toast { order: 1; }
103
+ #wg-device-badge, #wg-pilot-fx-device { order: 2; }
104
+
105
+ /*
106
+ * Chrome that comes and goes takes no space while hidden (an empty gap above the step panel would read as a bug), and
107
+ * still fades: display switches with the transition (allow-discrete), and @starting-style is where the fade starts.
108
+ */
109
+ #wg-caption-bar, #wg-typing-badge, #wg-network-log, #wg-device-badge {
110
+ transition-property: opacity, transform, display; transition-behavior: allow-discrete;
111
+ }
112
+ #wg-caption-bar:not(.wg-in), #wg-typing-badge:not(.wg-in), #wg-network-log:not(.wg-in), #wg-device-badge:not(.wg-in) {
113
+ display: none;
114
+ }
115
+ @starting-style {
116
+ #wg-caption-bar.wg-in, #wg-typing-badge.wg-in, #wg-network-log.wg-in, #wg-device-badge.wg-in { opacity: 0; }
117
+ }
118
+
119
+ /* Narrow viewports: the bottom row's three slots stack (network, then the step panel full width, then zoom). */
120
+ @media (max-width: 640px) {
121
+ .wg-row-bottom { grid-template-columns: minmax(0, 1fr); }
122
+ .wg-slot[data-slot="bottom-center"] { align-items: stretch; }
123
+ }
124
+
125
+
53
126
  /*
54
127
  * ONE ring implementation, shared by every surface (demo/auto's singleton #wg-ring, and Pilot's
55
128
  * multiple simultaneous fixture rings) - `.wg-ring-el`/`.wg-ring-el-label` is the real class every
@@ -110,14 +183,14 @@
110
183
 
111
184
  /* Travelling mouse cursor + click ripple (#wg-cursor, #wg-click-pulse). */
112
185
  #wg-cursor {
113
- position: fixed; z-index: var(--wg-z-top); width: 24px; height: 24px; pointer-events: none;
186
+ position: fixed; z-index: var(--wg-z-cursor); width: 24px; height: 24px; pointer-events: none;
114
187
  left: 0; top: 0; opacity: 0; margin: 0;
115
188
  /* Travel time is JS-driven via --wg-cursor-ms (a hardcoded duration would make speed a no-op). */
116
189
  transition: transform var(--wg-cursor-ms, 600ms) var(--wg-ease), opacity .2s ease;
117
190
  filter: drop-shadow(0 2px 4px rgba(12,12,26,.4));
118
191
  }
119
192
  #wg-click-pulse {
120
- position: fixed; z-index: var(--wg-z-top); width: 14px; height: 14px;
193
+ position: fixed; z-index: var(--wg-z-cursor); width: 14px; height: 14px;
121
194
  margin-left: -7px; margin-top: -7px; border-radius: 50%; pointer-events: none; opacity: 0;
122
195
  border: 2px solid var(--wg-tone-ring, var(--wg-tone-planned-ring)); background: var(--wg-tone-pulse, var(--wg-tone-planned-glow));
123
196
  }
@@ -130,19 +203,16 @@
130
203
  @keyframes wg-pulse { 0% { opacity: .9; transform: scale(.4); } 100% { opacity: 0; transform: scale(2.4); } }
131
204
 
132
205
 
133
- /* Top title banner (#wg-banner). Sits BELOW the ring/label/dock layers (--wg-z-banner) - a title
134
- * card must never shadow a highlight; see tokens.css. Moves out of the way of a ring it overlaps
135
- * (data-collision, driven from inpage/banner.ts's __wgBannerAvoidRing) unless authored off. */
206
+ /* Top title banner (#wg-banner), in the top-left / top-center / top-right slot by its data-pos (layout.css). Sits
207
+ * below rings and labels (--wg-z-banner): a title card never shadows a highlight. Moves to another slot when a ring
208
+ * overlaps it (inpage/banner.ts's __wgBannerAvoidRing) unless authored off. */
136
209
  #wg-banner {
137
- position: fixed; z-index: var(--wg-z-banner); top: 14px; left: 14px;
210
+ z-index: var(--wg-z-banner);
138
211
  background: var(--wg-surface); color: #fff; border-radius: var(--wg-radius-lg); padding: 10px 16px;
139
212
  font: 14px/1.4 var(--wg-font); box-shadow: var(--wg-shadow-banner);
140
- border: 1px solid rgba(124,58,237,.4); cursor: pointer;
141
- transition: left .3s var(--wg-ease), right .3s var(--wg-ease), transform .3s var(--wg-ease);
213
+ border: 1px solid rgba(124,58,237,.4); cursor: pointer; max-width: min(92vw, 520px);
214
+ transition: opacity .3s var(--wg-ease);
142
215
  }
143
- #wg-banner[data-pos=left] { left: 14px; right: auto; transform: none; }
144
- #wg-banner[data-pos=center] { left: 50%; right: auto; transform: translateX(-50%); }
145
- #wg-banner[data-pos=right] { right: 14px; left: auto; transform: none; }
146
216
  /* Same "auto opacity" step-back the todo dock already has (.wg-todo-behind) while a ring caption
147
217
  is the current focus - added via __wgBannerSetBehind, not just the collision-driven move. */
148
218
  #wg-banner.wg-banner-behind { opacity: .42; }
@@ -161,14 +231,11 @@
161
231
  * duplicate of this file's own rules, is consolidated.
162
232
  */
163
233
  #wg-pilot-fx-todos {
164
- position: fixed; z-index: var(--wg-z-fx); top: 104px; max-width: 280px; max-height: calc(100vh - 132px);
165
- overflow: auto; font: 12px/1.4 ui-sans-serif, system-ui, sans-serif; color: #fff;
234
+ z-index: var(--wg-z-dock); width: min(280px, 42vw); overflow: auto; font: 12px/1.4 ui-sans-serif, system-ui, sans-serif; color: #fff;
166
235
  background: var(--wg-surface); border-radius: var(--wg-radius-md); padding: 10px 12px;
167
236
  box-shadow: var(--wg-shadow-dock); pointer-events: auto; transition: opacity .25s ease;
168
237
  }
169
238
  #wg-pilot-fx-todos.wg-todo-behind { z-index: var(--wg-z-focus); opacity: .42; pointer-events: none; }
170
- #wg-pilot-fx-todos[data-pos=left] { left: 14px; right: auto; }
171
- #wg-pilot-fx-todos[data-pos=right] { right: 14px; left: auto; }
172
239
  #wg-pilot-fx-todos .wg-pilot-fx-todo-title {
173
240
  font: 700 11px/1.2 system-ui, sans-serif; color: var(--wg-accent-soft);
174
241
  letter-spacing: .04em; text-transform: uppercase; margin: 0 0 8px;
@@ -199,19 +266,55 @@
199
266
  * chrome, this narrates the PAGE itself so it reads as a caption card, not more chrome).
200
267
  */
201
268
  #wg-caption-bar {
202
- position: fixed; z-index: var(--wg-z-focus); left: 0; right: 0; bottom: 0;
203
- /* Bottom padding clears the collapsed step-pill (#wg-panel, bottom:12px, ~40px tall,
204
- z-index:2147483647 - always on top) - without it the pill sat right over the caption card,
205
- both centered at the bottom of the viewport (see #wg-typing-badge's own bottom:88px, same
206
- reason). */
207
- padding: 0 6% 64px; display: flex; justify-content: center;
269
+ /* In the bottom-center slot, above the step panel (layout.css): the slot's gap keeps them apart. */
270
+ z-index: var(--wg-z-chrome); display: flex; justify-content: center; max-width: min(820px, 92vw);
208
271
  pointer-events: none; opacity: 0; transform: translateY(6px);
209
- transition: opacity .3s ease, transform .3s ease;
272
+ transition-duration: .3s; transition-timing-function: ease;
210
273
  }
211
274
  #wg-caption-bar.wg-in { opacity: 1; transform: translateY(0); }
212
275
  #wg-caption-bar .wg-caption-text {
213
276
  font: 500 15px/1.4 var(--wg-font); color: #1c1917; text-align: center;
214
- max-width: 82%; text-wrap: balance;
277
+ text-wrap: balance;
215
278
  background: #fff; border-radius: var(--wg-radius-lg);
216
279
  padding: 10px 18px; box-shadow: var(--wg-shadow-banner);
217
280
  }
281
+
282
+
283
+ /* The network tab: real page traffic (network-capture.ts) and defineApiBlock's own calls, bottom-left (the one corner
284
+ * free of the zoom HUD and the typing chip). Hidden until ctx.ui.showNetworkTab(); stays up once shown. In the shared
285
+ * bundle, not the runner CSS: a navigation rebuilds the panel the moment the shadow root is back, before any later
286
+ * stylesheet, and without its CSS it painted unstyled and visible, then faded out. */
287
+ #wg-network-log{z-index:var(--wg-z-chrome);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-duration:.25s;transition-timing-function:ease;overflow:hidden;}
288
+ #wg-network-log.wg-in{opacity:1;transform:translateY(0);}
289
+ #wg-network-log.wg-net-collapsed .wg-net-list{display:none;}
290
+ #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;}
291
+ #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;}
292
+ #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;}
293
+ #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;}
294
+ #wg-network-log .wg-net-collapse:hover{background:rgba(124,58,237,.42);}
295
+ #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;}
296
+ #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;}
297
+ #wg-network-log .wg-net-row:hover,#wg-network-log .wg-net-row.wg-net-open{background:rgba(124,58,237,.18);}
298
+ #wg-network-log .wg-net-row.wg-in{opacity:1;transform:translateX(0);}
299
+ #wg-network-log .wg-net-method{flex:0 0 auto;font-weight:800;font-size:10px;min-width:38px;}
300
+ #wg-network-log .wg-net-method-get{color:#3B82F6;}
301
+ #wg-network-log .wg-net-method-post{color:#22C55E;}
302
+ #wg-network-log .wg-net-method-put{color:#F97316;}
303
+ #wg-network-log .wg-net-method-delete{color:#EF4444;}
304
+ #wg-network-log .wg-net-method-other{color:#9CA3AF;}
305
+ #wg-network-log .wg-net-path{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#e0e0f0;}
306
+ #wg-network-log .wg-net-status{flex:0 0 auto;font-weight:700;color:#22C55E;}
307
+ #wg-network-log .wg-net-row[data-ok="0"] .wg-net-status{color:#EF4444;}
308
+ #wg-network-log .wg-net-ms{flex:0 0 auto;color:#9CA3AF;font-size:10px;}
309
+ #wg-network-log .wg-net-detail{list-style:none;background:rgba(0,0,0,.28);border-radius:6px;padding:8px;margin:2px 0 6px;}
310
+ #wg-network-log .wg-net-detail-section{margin-bottom:8px;}
311
+ #wg-network-log .wg-net-detail-section:last-child{margin-bottom:0;}
312
+ #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;}
313
+ #wg-network-log .wg-net-headers{width:100%;border-collapse:collapse;font-size:11px;}
314
+ #wg-network-log .wg-net-headers td{padding:1px 6px 1px 0;vertical-align:top;word-break:break-all;}
315
+ #wg-network-log .wg-net-headers td:first-child{color:#c9a6ff;white-space:nowrap;}
316
+ #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;}
317
+ #wg-network-log .wg-net-curl{color:#7dd3fc;}
318
+ #wg-network-log .wg-net-empty{font-size:11px;color:#6b7280;font-style:italic;}
319
+ #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;}
320
+ #wg-network-log .wg-net-copy:hover{background:rgba(124,58,237,.38);}
@@ -15,18 +15,85 @@ export function installShadowRoot(overlaySheet) {
15
15
  host?.remove();
16
16
  host = document.createElement("div");
17
17
  host.id = "wg-root";
18
- // No box of its own: children are position:fixed, so the host must never affect page layout.
18
+ // No box of its own: everything inside is in the fixed #wg-ui layer, so the host never affects page layout.
19
19
  host.style.cssText = "display:contents";
20
20
  host.attachShadow({ mode: "open" });
21
21
  document.documentElement.appendChild(host);
22
22
  }
23
23
  return host.shadowRoot;
24
24
  };
25
+ // The one layout (ui/css/layout.css): #wg-ui holds the stage (anchored things) and the chrome's slots. Where a chrome
26
+ // element goes is decided here, once, by its id and data-pos; anything not in the table is anchored (the stage).
27
+ const SLOTS = ["top-left", "top-center", "top-right", "left", "right", "bottom-left", "bottom-center", "bottom-right"];
28
+ const SLOT_OF = {
29
+ "wg-typing-badge": "bottom-center",
30
+ "wg-caption-bar": "bottom-center",
31
+ "wg-panel": "bottom-center",
32
+ "wg-auto-panel": "bottom-center",
33
+ "wg-network-log": "bottom-left",
34
+ "wg-pilot-activity": "bottom-left",
35
+ "wg-zoom-badge": "bottom-right",
36
+ "wg-pilot-fx-zoom": "bottom-right",
37
+ "wg-pilot-overlay": "bottom-right",
38
+ "wg-device-toast": "top-right",
39
+ "wg-device-badge": "top-right",
40
+ "wg-pilot-fx-device": "top-right",
41
+ };
42
+ const slotOf = (el) => {
43
+ if (!(el instanceof HTMLElement))
44
+ return undefined;
45
+ const pos = el.dataset.pos;
46
+ if (el.id === "wg-banner")
47
+ return pos === "center" || pos === "right" ? `top-${pos}` : "top-left";
48
+ if (el.classList.contains("wg-todo-dock") || el.id === "wg-todo-dock" || el.id === "wg-pilot-fx-todos")
49
+ return pos === "right" ? "right" : "left";
50
+ return SLOT_OF[el.id];
51
+ };
52
+ const ui = () => {
53
+ const r = root();
54
+ let layer = r.getElementById("wg-ui");
55
+ if (layer)
56
+ return layer;
57
+ layer = document.createElement("div");
58
+ layer.id = "wg-ui";
59
+ const stage = document.createElement("div");
60
+ stage.id = "wg-stage";
61
+ const chrome = document.createElement("div");
62
+ chrome.id = "wg-chrome";
63
+ for (const [row, slots] of [["top", SLOTS.slice(0, 3)], ["middle", SLOTS.slice(3, 5)], ["bottom", SLOTS.slice(5)]]) {
64
+ const rowEl = document.createElement("div");
65
+ rowEl.className = `wg-row wg-row-${row}`;
66
+ for (const name of slots) {
67
+ const slot = document.createElement("div");
68
+ slot.className = "wg-slot";
69
+ slot.dataset.slot = name;
70
+ rowEl.appendChild(slot);
71
+ }
72
+ chrome.appendChild(rowEl);
73
+ }
74
+ layer.append(stage, chrome);
75
+ r.appendChild(layer);
76
+ // A data-pos change (banner click, a dock flipping away from a ring) moves the element to its new slot.
77
+ new MutationObserver((records) => {
78
+ for (const rec of records)
79
+ if (rec.target instanceof Element && rec.target.parentElement?.classList.contains("wg-slot"))
80
+ place(rec.target);
81
+ }).observe(chrome, { subtree: true, attributes: true, attributeFilter: ["data-pos"] });
82
+ return layer;
83
+ };
84
+ const place = (node) => {
85
+ const layer = ui();
86
+ const slot = node instanceof Element ? slotOf(node) : undefined;
87
+ const parent = slot ? layer.querySelector(`[data-slot="${slot}"]`) : layer.querySelector("#wg-stage");
88
+ if (node.parentNode !== parent)
89
+ parent.appendChild(node);
90
+ return node;
91
+ };
25
92
  w.__wgRoot = root;
26
93
  w.__wgById = (id) => root().getElementById(id) || document.getElementById(id);
27
94
  w.__wgQ = (sel) => root().querySelector(sel) || document.querySelector(sel);
28
95
  w.__wgQA = (sel) => Array.from(root().querySelectorAll(sel)).concat(Array.from(document.querySelectorAll(sel)));
29
- w.__wgAdd = (node) => root().appendChild(node);
96
+ w.__wgAdd = place;
30
97
  w.__wgCss = (css, key) => {
31
98
  const r = root();
32
99
  let style = Array.from(r.children).find((c) => c.tagName === "STYLE" && c.getAttribute("data-key") === key);
@@ -1,18 +1,19 @@
1
1
  import { envFlag } from "../../engine/env.js";
2
- import { hasAuthoredStubAfter, resolveSlides, runStubPhase } from "../narration/narration.js";
2
+ import { hasAuthoredStubAfter, runStubPhase } from "../narration/narration.js";
3
3
  import { applyHighlightStyleDefaults, formatHighlightCaption } from "../narration/style.js";
4
4
  import { logBannerDom, probeBanner } from "./banner-log.js";
5
5
  import { logDocks } from "./before.js";
6
6
  import { demoLog, logStubPhaseFixtures, logTodoDockFull, paceSpeakFields, summarizeDevice } from "./demo-log.js";
7
7
  import { renderAfterStep } from "./panel-after.js";
8
- import { presentSlides } from "./slides.js";
8
+ import { showSlides } from "./slides.js";
9
9
  import { extractVerifyHighlights } from "./step-panels.js";
10
10
  import { forceCollapsed } from "./view.js";
11
11
  const quiet = () => envFlag("WAYGRAPH_JSON");
12
12
  export async function showAfter(ctx, view, result) {
13
13
  const { stage, refs } = ctx;
14
- await showSlides(ctx, view, result);
15
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);
16
17
  const applied = after ? await stage.applyPhase(after.phase) : { appliedTodo: { sync: "keep" }, appliedDevice: { sync: "keep" } };
17
18
  await renderAfterStep(ctx.page, {
18
19
  index: view.index,
@@ -62,20 +63,6 @@ export async function showAfter(ctx, view, result) {
62
63
  }
63
64
  await ctx.gate.wait();
64
65
  }
65
- async function showSlides(ctx, view, result) {
66
- const slides = resolveSlides(view.step.block, { out: result }).map((s) => applyHighlightStyleDefaults(s, view.step.highlightStyle));
67
- if (slides.length === 0)
68
- return;
69
- await presentSlides(ctx.page, slides, {
70
- title: ctx.title,
71
- blockName: view.step.block.name,
72
- fast: ctx.pacing.gatesFast,
73
- pace: ctx.pacing.demoPace,
74
- episodeNumber: view.step.episodeNumber,
75
- episodeTitle: view.step.episodeTitle,
76
- autoplayMs: ctx.gate.autoplayMs,
77
- });
78
- }
79
66
  /** Runs the Block's stubAfter; its rings carry the flow's highlight style and a caption. */
80
67
  async function narrateAfter(ctx, view, result) {
81
68
  const style = view.step.highlightStyle;
@@ -1,4 +1,4 @@
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
3
  import { envFlag } from "../../engine/env.js";
4
4
  import { runStubPhase } from "../narration/narration.js";
@@ -6,6 +6,7 @@ import { applyHighlightStyleDefaults, formatHighlightCaption } from "../narratio
6
6
  import { logBannerDom, probeBanner } from "./banner-log.js";
7
7
  import { demoLog, logStubPhaseFixtures, logTodoDockFull, paceSpeakFields, summarizeDevice } from "./demo-log.js";
8
8
  import { renderBeforeStep } from "./panel-before.js";
9
+ import { showSlides } from "./slides.js";
9
10
  import { probeTodoDocksOnPage } from "./todo-advance.js";
10
11
  import { forceCollapsed } from "./view.js";
11
12
  const quiet = () => envFlag("WAYGRAPH_JSON");
@@ -13,6 +14,7 @@ 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";
@@ -177,13 +177,13 @@ export function logStubPhaseFixtures(phaseName, phase) {
177
177
  return;
178
178
  const tag = "stub." + phaseName;
179
179
  if (phase.title)
180
- demoFixture(tag, "ctx.title(...)", JSON.stringify(phase.title));
180
+ demoFixture(tag, "ctx.ui.title(...)", JSON.stringify(phase.title));
181
181
  if (phase.titlePos)
182
- demoFixture(tag, "ctx.titlePos(...)", JSON.stringify(phase.titlePos));
182
+ demoFixture(tag, "ctx.ui.titlePos(...)", JSON.stringify(phase.titlePos));
183
183
  if (phase.bannerUi)
184
- demoFixture(tag, "ctx.bannerUi(...)", JSON.stringify(phase.bannerUi));
184
+ demoFixture(tag, "ctx.ui.bannerUi(...)", JSON.stringify(phase.bannerUi));
185
185
  if (phase.deviceSync && phase.deviceSync !== "keep") {
186
- demoFixture(tag, phase.deviceSync === "clear" ? "ctx.hideDevice()" : "ctx.device(...)", phase.device
186
+ demoFixture(tag, phase.deviceSync === "clear" ? "ctx.ui.hideDevice()" : "ctx.ui.device(...)", phase.device
187
187
  ? {
188
188
  preset: phase.device.preset,
189
189
  w: phase.device.viewport && phase.device.viewport.width,
@@ -194,11 +194,11 @@ export function logStubPhaseFixtures(phaseName, phase) {
194
194
  : phase.deviceSync);
195
195
  }
196
196
  if (phase.zoom != null)
197
- demoFixture(tag, "ctx.zoom(" + phase.zoom + ")", phase.zoom + "x");
197
+ demoFixture(tag, "ctx.ui.zoom(" + phase.zoom + ")", phase.zoom + "x");
198
198
  if (phase.zoomOut !== undefined)
199
- demoFixture(tag, "ctx.zoomOut(" + !!phase.zoomOut + ")", !!phase.zoomOut);
199
+ demoFixture(tag, "ctx.ui.zoomOut(" + !!phase.zoomOut + ")", !!phase.zoomOut);
200
200
  if (phase.todoSync && phase.todoSync !== "keep") {
201
- demoFixture(tag, phase.todoSync === "clear" ? "ctx.hideTodos() / clear" : "ctx.todos(...)", phase.todoDock
201
+ demoFixture(tag, phase.todoSync === "clear" ? "ctx.ui.hideTodos() / clear" : "ctx.ui.todos(...)", phase.todoDock
202
202
  ? {
203
203
  id: phase.todoDock.id,
204
204
  pos: phase.todoDock.pos,
@@ -208,7 +208,7 @@ export function logStubPhaseFixtures(phaseName, phase) {
208
208
  : phase.todoSync);
209
209
  }
210
210
  phase.highlights.forEach((h, i) => {
211
- demoFixture(tag, "ctx.ring/highlights[" + i + "]", {
211
+ demoFixture(tag, "ctx.ui.ring[" + i + "]", {
212
212
  selector: h.selector,
213
213
  label: h.label,
214
214
  detail: h.detail,
@@ -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";
@@ -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.
@@ -148,11 +148,6 @@ export function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stu
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();
@@ -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
@@ -21,7 +21,7 @@ export async function renderAfterStep(page, info) {
21
21
  // Cycle through EVERY declared/recovered highlight in order, each shown
22
22
  // long enough to actually register - "it highlights something [...] then
23
23
  // it highlights something [else] and next," not just the first one.
24
- // duration / fastMode on stubAfter (or flow fixtures) override the default
24
+ // duration / fastMode on stubAfter rings override the default
25
25
  // 900ms / 200ms holds when set.
26
26
  await cycleHighlightRings(page, highlights, gatesFast, {
27
27
  pace: info.pace,
@@ -1,6 +1,7 @@
1
1
  import type { WgPage } from "../types.js";
2
2
  import { type WaygraphSlide } from "../../engine/stub/ring.js";
3
3
  import type { DemoPace } from "../../engine/stub/pace.js";
4
+ import type { StepperContext, StepView } from "./view.js";
4
5
  /** Where the slides sit (title, Block, episode) and how fast they advance. */
5
6
  export interface SlidesOptions {
6
7
  title?: string | undefined;
@@ -21,3 +22,5 @@ export interface SlidesOptions {
21
22
  * doing nothing. In-page data-wg-acked + autoplay poll is the source of truth.
22
23
  */
23
24
  export declare function presentSlides(page: WgPage, slides: readonly WaygraphSlide[], opts?: SlidesOptions): Promise<void>;
25
+ /** Plays a stub phase's `ctx.ui.slides(...)` for the step on screen, in the flow's highlight style. */
26
+ export declare function showSlides(ctx: StepperContext, view: StepView, slides: readonly WaygraphSlide[]): Promise<void>;
@@ -1,10 +1,10 @@
1
1
  import { demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeDemoPace, resolveFixtureDwellMs } from "../narration/demo-pace.js";
2
- import { formatHighlightCaption } from "../narration/style.js";
3
2
  import { normalizeHighlightSize, normalizeHighlightTone, normalizeHighlightWeight } from "../../engine/stub/ring.js";
4
3
  import { installOverlay } from "../overlay/overlay.js";
5
4
  import { applyHighlightZoom, ensureSelectorInView, hideRing, showRing } from "../overlay/rings.js";
6
- import { demoPaceKind } from "./demo-log.js";
5
+ import { demoLog, demoPaceKind } from "./demo-log.js";
7
6
  import { envFlag, envNumber, envString } from "../../engine/env.js";
7
+ import { applyHighlightStyleDefaults, formatHighlightCaption } from "../narration/style.js";
8
8
  /**
9
9
  * Multi-step yap captions - not block lifecycle. Each slide waits for Next
10
10
  * (or auto-next), optional ring via slide.selector.
@@ -231,3 +231,19 @@ export async function presentSlides(page, slides, opts) {
231
231
  }
232
232
  await hideRing(page);
233
233
  }
234
+ /** Plays a stub phase's `ctx.ui.slides(...)` for the step on screen, in the flow's highlight style. */
235
+ export async function showSlides(ctx, view, slides) {
236
+ if (slides.length === 0)
237
+ return;
238
+ if (!envFlag("WAYGRAPH_JSON"))
239
+ demoLog(` slides ${slides.length}: ` + slides.map((s) => JSON.stringify(s.caption)).join(" | "));
240
+ await presentSlides(ctx.page, slides.map((s) => applyHighlightStyleDefaults(s, view.step.highlightStyle)), {
241
+ title: ctx.title,
242
+ blockName: view.step.block.name,
243
+ fast: ctx.pacing.gatesFast,
244
+ pace: ctx.pacing.demoPace,
245
+ episodeNumber: view.step.episodeNumber,
246
+ episodeTitle: view.step.episodeTitle,
247
+ autoplayMs: ctx.gate.autoplayMs,
248
+ });
249
+ }