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,246 +1,10 @@
1
- import { installOverlay } from "../overlay/overlay.js";
2
- import { applyHighlightStyleDefaults, formatHighlightCaption } from "../narration/style.js";
1
+ // The stepper's panels: before a step (what will happen, its mem), after it (the result and rings), and on failure.
2
+ import { ignorePageGone } from "../../engine/page-gone.js";
3
3
  import { hasAuthoredStubOnError, runStubPhase } from "../narration/narration.js";
4
+ import { applyHighlightStyleDefaults, formatHighlightCaption } from "../narration/style.js";
5
+ import { installOverlay } from "../overlay/overlay.js";
4
6
  import { cycleHighlightRings } from "../overlay/rings.js";
5
- import { envFlag, envString } from "../../engine/env.js";
6
- import { ignorePageGone, ignoreTimeout } from "../../engine/page-gone.js";
7
- export async function renderBeforeStep(page, info) {
8
- await installOverlay(page, info.title, info.bannerUi, info.caption, info.networkTabVisible);
9
- // A ring left highlighting the PREVIOUS step's element (and its live
10
- // resize/scroll tracker) shouldn't linger once a new step's own panel is
11
- // up - only relevant when a Block's act() doesn't navigate away, since a
12
- // real navigation already wipes document.documentElement's children.
13
- await page
14
- .evaluate(() => {
15
- if (window.__wgRingTrack) {
16
- window.removeEventListener("resize", window.__wgRingTrack);
17
- window.removeEventListener("scroll", window.__wgRingTrack, true);
18
- window.__wgRingTrack = null;
19
- }
20
- if (window.__wgHideRing)
21
- window.__wgHideRing();
22
- if (window.__wgHideCursor)
23
- window.__wgHideCursor();
24
- })
25
- .catch(ignorePageGone);
26
- // miniStepLabel / todosHtmlFromInfo are Node-only — browser evaluate has no closure.
27
- const payload = { ...info, stepLabel: miniStepLabel(info) };
28
- await page
29
- .evaluate((info) => {
30
- // Reused in place, not removed + recreated, every step - the old
31
- // remove()-then-fade-back-in cycle was a real flash on every single
32
- // transition (the user: "the appear and disappear... hurts eyes and
33
- // dizzy"). Updating one persistent element's content has nothing to
34
- // flash - it only ever fades in ONCE, the first time this page
35
- // genuinely has no panel yet (a real navigation wiped the whole
36
- // document, or this is the very first step).
37
- let panel = __wgById("wg-panel");
38
- const isNewPanel = !panel;
39
- if (!panel) {
40
- panel = document.createElement("div");
41
- panel.id = "wg-panel";
42
- }
43
- const pct = Math.round((info.index / info.total) * 100);
44
- // A real tab bar, not a single text line - every episode this chain
45
- // touches, the current one reading as active (the user: "if episode 1 is
46
- // active, the episode 2 tab is inactive"). Absent entirely for an ad
47
- // hoc block chain with no named Flows.
48
- const episodes = info.allEpisodes && info.allEpisodes.length > 0
49
- ? __wgEl("div", { id: "wg-episodes" }, info.allEpisodes.map((e) => {
50
- const cls = e.episodeNumber < info.episodeNumber ? "wg-ep-done"
51
- : e.episodeNumber === info.episodeNumber ? "wg-ep-current"
52
- : "wg-ep-upcoming";
53
- // The gentle one-time glow only plays on the tab actually
54
- // being entered right now, not on every render of it.
55
- const enteredCls = info.justEnteredEpisode && e.episodeNumber === info.episodeNumber
56
- ? " wg-ep-entered"
57
- : "";
58
- return __wgEl("span", { class: "wg-ep-tab " + cls + enteredCls }, "Episode " + e.episodeNumber + ": " + (e.episodeTitle || ""));
59
- }))
60
- : null;
61
- // Scoped to THIS episode's own blocks (info.allNames is already the
62
- // episode-local subset the caller computed) - moduleIndex is this
63
- // step's position WITHIN that subset, not the whole chain's index -
64
- // "in episode 2 it shows 2nd to the last... it should start from the
65
- // first block" (the user).
66
- const modulesClass = info.stepperMode === "full" ? "wg-modules-full" : "wg-modules-carousel";
67
- const modules = info.allNames.map((name, idx) => {
68
- const cls = idx < info.moduleIndex ? "wg-mod-done" : idx === info.moduleIndex ? "wg-mod-current" : "wg-mod-upcoming";
69
- const desc = info.allDescriptions && info.allDescriptions[idx];
70
- return __wgEl("span", { class: "wg-mod " + cls, title: desc || undefined }, String(name));
71
- });
72
- const pace = info.paceBadge || info.paceLabel
73
- ? __wgEl("div", { class: "wg-pace", data: { paceKind: info.paceKind || "normal" } }, __wgEl("span", { class: "wg-pace-badge" }, info.paceBadge || "1x"), __wgEl("span", null, info.paceLabel || ""))
74
- : null;
75
- const keys = info.keys.length === 0
76
- ? [__wgEl("div", { class: "wg-key" }, "(no MemKeys required)")]
77
- : info.keys.map((k) => {
78
- const field = __wgEl("textarea", { data: { key: k.name }, attrs: { rows: 2 } });
79
- field.value = k.value;
80
- return __wgEl("div", { class: "wg-key" }, __wgEl("label", null, k.name), field, __wgEl("div", { class: "wg-key-pretty", data: { prettyFor: k.name } }));
81
- });
82
- let autoNow = false;
83
- try {
84
- autoNow = localStorage.getItem("wg-autoplay") === "1";
85
- }
86
- catch {
87
- /* private mode / blocked storage - defaults to manual */
88
- }
89
- const autoplay = __wgEl("input", { id: "wg-autoplay-cb", attrs: { type: "checkbox" } });
90
- autoplay.checked = autoNow;
91
- // Todos render into floating #wg-todo-dock via __wgSyncTodos - never
92
- // inside .wg-body (would vanish under --mini / Hide collapse).
93
- __wgSet(panel, __wgEl("div", { id: "wg-progress" }, __wgEl("div", { id: "wg-progress-bar", style: "width:" + pct + "%" })), episodes, pace, __wgEl("div", { id: "wg-modules", class: modulesClass }, modules), __wgEl("h3", null, "Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName), info.description ? __wgEl("div", { class: "wg-narration" }, info.description) : null, keys, __wgEl("div", { class: "wg-autoplay-row" }, __wgEl("label", null, autoplay, " Auto-advance")), __wgEl("div", { id: "wg-gate-manual", style: autoNow ? "display:none" : "" }, __wgEl("button", { id: "wg-run" }, "Run this step \u25B6")), __wgEl("div", { id: "wg-gate-auto", class: "wg-auto", style: autoNow ? "" : "display:none" }, "Auto-advancing..."));
94
- if (isNewPanel) {
95
- __wgAdd(panel);
96
- requestAnimationFrame(() => panel.classList.add("wg-in"));
97
- }
98
- else {
99
- panel.classList.add("wg-in");
100
- }
101
- if (window.__wgWirePanelChrome) {
102
- window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
103
- stepLabel: info.stepLabel || "",
104
- forceCollapsed: info.forceCollapsed === true ? true : undefined,
105
- fastForward: !!info.fastForward,
106
- isApi: !!info.isApi,
107
- });
108
- }
109
- if (window.__wgSyncTodos) {
110
- const sync = info.todoSync || (info.todos && info.todos.length ? "set" : "keep");
111
- // Prefer dock-only payload. Passing list:[] with a dock used to clear
112
- // marks a frame later (empty-list clear path).
113
- if (sync === "keep" && !info.todoDock) {
114
- /* leave floating dock alone */
115
- }
116
- else {
117
- const payload = {
118
- sync: sync,
119
- dock: info.todoDock || null,
120
- pos: info.todoPos || null,
121
- todoId: info.todoId || (info.todoDock && info.todoDock.id) || null,
122
- };
123
- if (!info.todoDock && info.todos && info.todos.length) {
124
- payload.list = info.todos;
125
- }
126
- window.__wgSyncTodos(payload);
127
- }
128
- }
129
- if (window.__wgStampModal) {
130
- window.__wgStampModal(panel, "panel", {
131
- phase: "before",
132
- step: info.index + 1,
133
- total: info.total,
134
- block: info.blockName || "",
135
- ready: true,
136
- });
137
- }
138
- // Live, human-readable preview of what a MemKey's raw JSON will
139
- // actually write - object fields become "Field: value" lines
140
- // (camelCase split the same way state tags already are); a
141
- // non-object payload (a plain string/number/array) is shown as-is.
142
- // Hidden entirely in JSON mode - shares the SAME global toggle the
143
- // result display's own Pretty/JSON buttons already set, not a
144
- // second, separate preference.
145
- const prettyMemValue = (raw) => {
146
- let parsed;
147
- try {
148
- parsed = JSON.parse(raw);
149
- }
150
- catch {
151
- return [__wgEl("em", null, "(invalid JSON)")];
152
- }
153
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
154
- return [JSON.stringify(parsed)];
155
- }
156
- return Object.entries(parsed).map(([field, value]) => {
157
- const label = field.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
158
- return __wgEl("div", { class: "wg-key-pretty-row" }, __wgEl("span", { class: "wg-key-pretty-label" }, label + ":"), " " + String(value));
159
- });
160
- };
161
- const applyKeyPretty = () => {
162
- const prettyOn = window.__wgPretty !== false;
163
- panel.querySelectorAll("textarea[data-key]").forEach((ta) => {
164
- const previewEl = ta.parentElement?.querySelector(".wg-key-pretty");
165
- if (!previewEl)
166
- return;
167
- previewEl.style.display = prettyOn ? "" : "none";
168
- if (prettyOn)
169
- __wgSet(previewEl, prettyMemValue(ta.value));
170
- });
171
- };
172
- applyKeyPretty();
173
- panel.querySelectorAll("textarea[data-key]").forEach((ta) => {
174
- ta.addEventListener("input", applyKeyPretty);
175
- });
176
- const runBtn = __wgById("wg-run");
177
- if (runBtn) {
178
- runBtn.addEventListener("click", () => {
179
- const edits = {};
180
- panel.querySelectorAll("textarea[data-key]").forEach((el) => {
181
- edits[el.getAttribute("data-key") ?? ""] = el.value;
182
- });
183
- window.__wgNext(edits);
184
- });
185
- }
186
- // Live toggle - flips localStorage immediately so an in-flight gate()
187
- // poll (on the Node side) picks it up within its next poll slice,
188
- // without needing this whole panel to re-render. A manual click
189
- // still always wins over an active autoplay wait, whichever the
190
- // checkbox says - this is the "hybrid" control: autoplay is a
191
- // default, not a lockout.
192
- const cb = __wgById("wg-autoplay-cb");
193
- if (cb) {
194
- cb.addEventListener("change", () => {
195
- try {
196
- localStorage.setItem("wg-autoplay", cb.checked ? "1" : "0");
197
- }
198
- catch {
199
- /* private mode / blocked storage - toggle still works this page */
200
- }
201
- const manual = __wgById("wg-gate-manual");
202
- const auto = __wgById("wg-gate-auto");
203
- if (manual)
204
- manual.style.display = cb.checked ? "none" : "";
205
- if (auto)
206
- auto.style.display = cb.checked ? "" : "none";
207
- });
208
- }
209
- }, payload)
210
- .catch(ignorePageGone);
211
- if (envString("WAYGRAPH_PROVE_SHOT") || envFlag("WAYGRAPH_PROVE_EXIT")) {
212
- // Default: prove once on first painted panel. PROVE_EVERY=1 re-checks
213
- // every step (noisy; useful when hunting a mid-chain blank).
214
- const proveEvery = envFlag("WAYGRAPH_PROVE_EVERY");
215
- const already = globalThis.__wgProveDone;
216
- if (!already || proveEvery || envFlag("WAYGRAPH_PROVE_EXIT")) {
217
- await page.waitForTimeout(500).catch(ignoreTimeout);
218
- const shot = envString("WAYGRAPH_PROVE_SHOT");
219
- if (shot)
220
- await page.screenshot({ path: shot }).catch(ignorePageGone);
221
- const ok = await page
222
- .evaluate(() => {
223
- const beacons = typeof window.__wgOverlayBeacon === "function" ? window.__wgOverlayBeacon() : [];
224
- const panel = beacons.find((b) => b.modal === "panel" && b.ready && b.visible);
225
- const textOk = panel && panel.textLen >= 12;
226
- const opacityOk = panel && Number(panel.opacity) > 0.5;
227
- return {
228
- ok: !!(panel && textOk && opacityOk),
229
- beacons,
230
- hasRun: !!__wgById("wg-run"),
231
- readyAttr: __wgById("wg-panel")?.getAttribute("data-wg-ready") || null,
232
- };
233
- })
234
- .catch((e) => ({ ok: false, reason: String(e instanceof Error ? e.message : e) }));
235
- console.error("WAYGRAPH_PROVE " + JSON.stringify(ok));
236
- globalThis.__wgProveDone = true;
237
- if (envFlag("WAYGRAPH_PROVE_EXIT")) {
238
- process.exit(ok && ok.ok ? 0 : 2);
239
- }
240
- }
241
- }
242
- }
243
- function miniStepLabel(info) {
7
+ export function miniStepLabel(info) {
244
8
  const ep = info.episodeNumber !== undefined && info.episodeNumber !== null
245
9
  ? "Ep " + info.episodeNumber + " \u00b7 "
246
10
  : "";
@@ -275,20 +39,7 @@ export async function presentFailPanel(page, opts) {
275
39
  });
276
40
  const errHighlights = errPhase.highlights.map((h) => {
277
41
  const styled = applyHighlightStyleDefaults(h, opts.highlightStyle);
278
- return {
279
- selector: styled.selector,
280
- label: formatHighlightCaption(styled),
281
- duration: styled.duration,
282
- fastMode: styled.fastMode,
283
- tone: styled.tone,
284
- size: styled.size,
285
- weight: styled.weight,
286
- zoom: styled.zoom,
287
- zoomOut: styled.zoomOut,
288
- focus: !!styled.focus,
289
- color: styled.color,
290
- detail: styled.detail,
291
- };
42
+ return { ...styled, label: formatHighlightCaption(styled), focus: !!styled.focus };
292
43
  });
293
44
  await cycleHighlightRings(page, errHighlights, !!gatesFast, {
294
45
  defaultHoldMs: 2000,
@@ -313,167 +64,6 @@ export async function presentFailPanel(page, opts) {
313
64
  });
314
65
  return gate();
315
66
  }
316
- export async function renderAfterStep(page, info) {
317
- await installOverlay(page, info.title, info.bannerUi, info.caption, info.networkTabVisible);
318
- // Clear any tracker from a previous highlight before cycling through this
319
- // step's own.
320
- await page
321
- .evaluate(() => {
322
- if (window.__wgRingTrack) {
323
- window.removeEventListener("resize", window.__wgRingTrack);
324
- window.removeEventListener("scroll", window.__wgRingTrack, true);
325
- window.__wgRingTrack = null;
326
- }
327
- })
328
- .catch(ignorePageGone);
329
- const highlights = info.highlights || [];
330
- const gatesFast = !!info.gatesFast;
331
- // Cycle through EVERY declared/recovered highlight in order, each shown
332
- // long enough to actually register - "it highlights something [...] then
333
- // it highlights something [else] and next," not just the first one.
334
- // duration / fastMode on stubAfter (or flow fixtures) override the default
335
- // 900ms / 200ms holds when set.
336
- await cycleHighlightRings(page, highlights, gatesFast, {
337
- pace: info.pace,
338
- stepLabel: miniStepLabel(info),
339
- todoDock: info.todoDock,
340
- todos: info.todos,
341
- todoSync: info.todoSync,
342
- todoDockRef: info.todoDockRef,
343
- // stubAfter already authored progress - do not re-index by ring 0..n
344
- advanceTodos: false,
345
- });
346
- // After-ring advances may have moved the dock - surface latest for caller.
347
- if (info.todoDockRef && info.todoDockRef.current) {
348
- info.todoDock = info.todoDockRef.current;
349
- }
350
- const afterPayload = { ...info, stepLabel: miniStepLabel(info) };
351
- await page
352
- .evaluate((info) => {
353
- // Reused in place - see renderBeforeStep's own comment on this.
354
- let panel = __wgById("wg-panel");
355
- const isNewPanel = !panel;
356
- if (!panel) {
357
- panel = document.createElement("div");
358
- panel.id = "wg-panel";
359
- }
360
- const pct = Math.round(((info.index + 1) / info.total) * 100);
361
- const heading = info.isLast
362
- ? "End of chain - " + info.total + " / " + info.total + " blocks covered - " + info.blockName + " done"
363
- : "Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + " done";
364
- const buttonLabel = info.isLast ? "Finish" : "Next \u25B6";
365
- const modulesClass = info.stepperMode === "full" ? "wg-modules-full" : "wg-modules-carousel";
366
- const modules = info.allNames.map((name, idx) => {
367
- const cls = idx <= info.moduleIndex ? "wg-mod-done" : "wg-mod-upcoming";
368
- const desc = info.allDescriptions && info.allDescriptions[idx];
369
- return __wgEl("span", { class: "wg-mod " + cls, title: desc || undefined }, String(name));
370
- });
371
- // QA-friendly by default ("LoginSuccess" -> "Login Success") - raw
372
- // JSON is one click away for whoever actually wants __state.
373
- const stateTag = info.result && info.result.__state ? info.result.__state : "";
374
- const prettyText = stateTag
375
- .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
376
- .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2") || info.resultTag;
377
- const pretty = window.__wgPretty !== false;
378
- let autoNow = false;
379
- try {
380
- autoNow = localStorage.getItem("wg-autoplay") === "1";
381
- }
382
- catch {
383
- /* private mode / blocked storage - defaults to manual */
384
- }
385
- const autoplay = __wgEl("input", { id: "wg-autoplay-cb", attrs: { type: "checkbox" } });
386
- autoplay.checked = autoNow;
387
- const episodes = info.allEpisodes && info.allEpisodes.length > 0
388
- ? __wgEl("div", { id: "wg-episodes" }, info.allEpisodes.map((e) => {
389
- const cls = e.episodeNumber < info.episodeNumber ? "wg-ep-done"
390
- : e.episodeNumber === info.episodeNumber ? "wg-ep-current"
391
- : "wg-ep-upcoming";
392
- return __wgEl("span", { class: "wg-ep-tab " + cls }, "Episode " + e.episodeNumber + ": " + (e.episodeTitle || ""));
393
- }))
394
- : null;
395
- const pace = info.paceBadge || info.paceLabel
396
- ? __wgEl("div", { class: "wg-pace", data: { paceKind: info.paceKind || "normal" } }, __wgEl("span", { class: "wg-pace-badge" }, info.paceBadge || "1x"), __wgEl("span", null, info.paceLabel || ""))
397
- : null;
398
- // Todos float in #wg-todo-dock via __wgSyncTodos, never in the panel.
399
- __wgSet(panel, __wgEl("div", { id: "wg-progress" }, __wgEl("div", { id: "wg-progress-bar", style: "width:" + pct + "%" })), episodes, pace, __wgEl("div", { id: "wg-modules", class: modulesClass }, modules), __wgEl("h3", null, heading), __wgEl("div", { class: "wg-toggle" }, __wgEl("button", { class: "wg-toggle-btn" + (pretty ? " wg-active" : ""), data: { mode: "pretty" } }, "Pretty"), __wgEl("button", { class: "wg-toggle-btn" + (pretty ? "" : " wg-active"), data: { mode: "json" } }, "JSON")), __wgEl("div", { class: "wg-result wg-result-pretty", style: "display:" + (pretty ? "block" : "none") }, String(prettyText)), __wgEl("div", { class: "wg-result wg-result-json", style: "display:" + (pretty ? "none" : "block") }, String(info.resultTag)), __wgEl("div", { class: "wg-autoplay-row" }, __wgEl("label", null, autoplay, " Auto-advance")), __wgEl("div", { id: "wg-gate-manual", style: autoNow ? "display:none" : "" }, __wgEl("button", { id: "wg-run" }, buttonLabel)), __wgEl("div", { id: "wg-gate-auto", class: "wg-auto", style: autoNow ? "" : "display:none" }, "Auto-advancing..."));
400
- if (isNewPanel) {
401
- __wgAdd(panel);
402
- requestAnimationFrame(() => panel.classList.add("wg-in"));
403
- }
404
- else {
405
- panel.classList.add("wg-in");
406
- }
407
- if (window.__wgWirePanelChrome) {
408
- window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
409
- stepLabel: info.stepLabel || "",
410
- forceCollapsed: info.forceCollapsed === true ? true : undefined,
411
- fastForward: !!info.fastForward,
412
- isApi: !!info.isApi,
413
- });
414
- }
415
- if (window.__wgSyncTodos) {
416
- const sync = info.todoSync || (info.todos && info.todos.length ? "set" : "keep");
417
- // Prefer dock-only payload. Passing list:[] with a dock used to clear
418
- // marks a frame later (empty-list clear path).
419
- if (sync === "keep" && !info.todoDock) {
420
- /* leave floating dock alone */
421
- }
422
- else {
423
- const payload = {
424
- sync: sync,
425
- dock: info.todoDock || null,
426
- pos: info.todoPos || null,
427
- todoId: info.todoId || (info.todoDock && info.todoDock.id) || null,
428
- };
429
- if (!info.todoDock && info.todos && info.todos.length) {
430
- payload.list = info.todos;
431
- }
432
- window.__wgSyncTodos(payload);
433
- }
434
- }
435
- if (window.__wgStampModal) {
436
- window.__wgStampModal(panel, "panel", {
437
- phase: "after",
438
- step: info.index + 1,
439
- total: info.total,
440
- block: info.blockName || "",
441
- ready: true,
442
- });
443
- }
444
- panel.querySelectorAll(".wg-toggle-btn").forEach((btn) => {
445
- btn.addEventListener("click", () => {
446
- const wantPretty = btn.getAttribute("data-mode") === "pretty";
447
- window.__wgPretty = wantPretty;
448
- panel.querySelectorAll(".wg-toggle-btn").forEach((b) => b.classList.remove("wg-active"));
449
- btn.classList.add("wg-active");
450
- panel.querySelector(".wg-result-pretty").style.display = wantPretty ? "block" : "none";
451
- panel.querySelector(".wg-result-json").style.display = wantPretty ? "none" : "block";
452
- });
453
- });
454
- const runBtn = __wgById("wg-run");
455
- if (runBtn)
456
- runBtn.addEventListener("click", () => window.__wgNext({}));
457
- const cb = __wgById("wg-autoplay-cb");
458
- if (cb) {
459
- cb.addEventListener("change", () => {
460
- try {
461
- localStorage.setItem("wg-autoplay", cb.checked ? "1" : "0");
462
- }
463
- catch {
464
- /* private mode / blocked storage - toggle still works this page */
465
- }
466
- const manual = __wgById("wg-gate-manual");
467
- const auto = __wgById("wg-gate-auto");
468
- if (manual)
469
- manual.style.display = cb.checked ? "none" : "";
470
- if (auto)
471
- auto.style.display = cb.checked ? "" : "none";
472
- });
473
- }
474
- }, afterPayload)
475
- .catch(ignorePageGone);
476
- }
477
67
  /**
478
68
  * A Block threw - act()/observe() rejected, or Flow.run's own verify Trait
479
69
  * check failed. Shown instead of letting it crash the whole Node process
@@ -500,7 +90,7 @@ async function renderStepError(page, info) {
500
90
  panel.classList.add("wg-in");
501
91
  const episodes = info.allEpisodes && info.allEpisodes.length > 0
502
92
  ? __wgEl("div", { id: "wg-episodes" }, info.allEpisodes.map((e) => {
503
- const cls = e.episodeNumber < info.episodeNumber ? "wg-ep-done"
93
+ const cls = e.episodeNumber < (info.episodeNumber ?? 0) ? "wg-ep-done"
504
94
  : e.episodeNumber === info.episodeNumber ? "wg-ep-current"
505
95
  : "wg-ep-upcoming";
506
96
  return __wgEl("span", { class: "wg-ep-tab " + cls }, "Episode " + e.episodeNumber + ": " + (e.episodeTitle || ""));
@@ -574,16 +164,9 @@ export function extractVerifyHighlights(block, resultTag) {
574
164
  const name = t && t.name;
575
165
  if (typeof name !== "string")
576
166
  continue;
577
- let m = /^visible\((.+)\)$/.exec(name);
578
- if (m) {
579
- highlights.push({ selector: m[1], label: name, tone: "auto" });
580
- continue;
581
- }
582
- m = /^text-equals\((.+?),\s*"/.exec(name);
583
- if (m) {
584
- highlights.push({ selector: m[1], label: name, tone: "auto" });
585
- continue;
586
- }
167
+ const selector = /^visible\((.+)\)$/.exec(name)?.[1] ?? /^text-equals\((.+?),\s*"/.exec(name)?.[1];
168
+ if (selector)
169
+ highlights.push({ selector, label: name, tone: "auto" });
587
170
  }
588
171
  return highlights;
589
172
  }
@@ -41,7 +41,7 @@ export async function createStepper(page, mem, steps, options) {
41
41
  paceStep(pacing, view.step, demoFast, options.fastBlocks);
42
42
  await showBefore(ctx, view);
43
43
  applyMemEdits(ctx, view, await gate.wait());
44
- await markStepRunning(page, {});
44
+ await markStepRunning(page);
45
45
  await labelNavClick(page, view.step);
46
46
  },
47
47
  async after(_index, result) {
@@ -1,4 +1,4 @@
1
- import type { WgPage } from "../types.js";
1
+ import type { StepperRefs, WgPage } from "../types.js";
2
2
  /**
3
3
  * Patches Locator.prototype.fill (via any real locator's own prototype
4
4
  * chain - Playwright doesn't export the class directly) so every fill(),
@@ -26,16 +26,15 @@ import type { WgPage } from "../types.js";
26
26
  * ("subtree intercepts pointer events"). Empty/partial fills + a lucky
27
27
  * edge click on Login then produced Sauce Demo's Epic sadface, verify
28
28
  * still passed on the error banner, and the next block blew up. Collapse
29
- * also matches the user's "step is running / can't interrupt" ask. (_opts kept
30
- * for call-site compat; autoCollapsePanel is now unconditional.)
29
+ * also matches the user's "step is running / can't interrupt" ask.
31
30
  */
32
- export declare function markStepRunning(page: WgPage, _opts: any): Promise<void>;
31
+ export declare function markStepRunning(page: WgPage): Promise<void>;
33
32
  /**
34
33
  * Real navigation wipes document + overlay helpers. Reinstall and re-paint
35
34
  * carried todos / device / optional stubBefore ring so keep-dock and nav
36
35
  * highlights do not vanish until the next ctx.todos() / stubAfter (PIA #15).
37
36
  */
38
- export declare function restoreTheaterAfterNavigation(page: WgPage, todoDockRef: any, deviceRef: any, stubBeforeRef: any): Promise<void>;
37
+ export declare function restoreTheaterAfterNavigation(page: WgPage, todoDockRef: StepperRefs["todoDock"], deviceRef: StepperRefs["device"], stubBeforeRef: StepperRefs["stubBefore"]): Promise<void>;
39
38
  /**
40
39
  * Removes every overlay element (panel, ring, ring-label, cursor,
41
40
  * click-pulse, banner) and stops the live resize/scroll ring tracker, once
@@ -30,10 +30,9 @@ import { ignorePageGone } from "../../engine/page-gone.js";
30
30
  * ("subtree intercepts pointer events"). Empty/partial fills + a lucky
31
31
  * edge click on Login then produced Sauce Demo's Epic sadface, verify
32
32
  * still passed on the error banner, and the next block blew up. Collapse
33
- * also matches the user's "step is running / can't interrupt" ask. (_opts kept
34
- * for call-site compat; autoCollapsePanel is now unconditional.)
33
+ * also matches the user's "step is running / can't interrupt" ask.
35
34
  */
36
- export async function markStepRunning(page, _opts) {
35
+ export async function markStepRunning(page) {
37
36
  await page
38
37
  .evaluate(() => {
39
38
  const runBtn = __wgById("wg-run");
@@ -82,13 +81,13 @@ export async function markStepRunning(page, _opts) {
82
81
  export async function restoreTheaterAfterNavigation(page, todoDockRef, deviceRef, stubBeforeRef) {
83
82
  const needs = (await page
84
83
  .evaluate(() => !window.__wgSyncTodos || !__wgById("wg-ring"))
85
- .catch(() => true)) || false;
84
+ .catch(ignorePageGone)) ?? true;
86
85
  if (!needs) {
87
86
  // Overlay survived (SPA / no full document wipe) - still refresh dock
88
87
  // if Node has a carry and the DOM lost .wg-todo-dock.
89
88
  const hasDock = await page
90
89
  .evaluate(() => !!__wgQ(".wg-todo-dock, #wg-todo-dock"))
91
- .catch(() => false);
90
+ .catch(ignorePageGone);
92
91
  if (hasDock || !todoDockRef || !todoDockRef.current)
93
92
  return;
94
93
  }
@@ -155,7 +154,6 @@ export async function teardownOverlay(page) {
155
154
  window.removeEventListener("scroll", window.__wgRingTrack, true);
156
155
  window.__wgRingTrack = null;
157
156
  }
158
- window.__wgNarrateOwnsRing = false;
159
157
  for (const id of [
160
158
  "wg-panel",
161
159
  "wg-ring",
@@ -1,6 +1,7 @@
1
- import type { WgPage } from "../types.js";
1
+ import type { StepperRefs, WgPage } from "../types.js";
2
+ import type { ResolvedHighlight } from "../../engine/stub/ring.js";
2
3
  /** Advance sequential todo dock to stubIndex and push to the page (Method act). */
3
- export declare function syncTodoDockAdvance(page: WgPage, todoDockRef: any, stubIndex: number, stub: any): Promise<void>;
4
+ export declare function syncTodoDockAdvance(page: WgPage, todoDockRef: StepperRefs["todoDock"] | undefined, stubIndex: number, stub: ResolvedHighlight | null | undefined): Promise<void>;
4
5
  export declare function probeTodoDocksOnPage(page: WgPage): Promise<never[] | {
5
6
  key: string;
6
7
  pos: string;
@@ -22,7 +22,7 @@ function resolveTodoAdvanceIndex(dock, stub, stubIndex) {
22
22
  return stubIndex;
23
23
  const curIdx = items.findIndex((t) => t.current);
24
24
  let idx = stubIndex;
25
- const key = stub && (stub.todo || stub.slotId || stub.id);
25
+ const key = stub && (stub.todo || stub.slotId);
26
26
  if (key) {
27
27
  const k = String(key).trim().toLowerCase();
28
28
  let found = items.findIndex((t) => t.id && String(t.id).toLowerCase() === k);
@@ -90,7 +90,7 @@ export async function probeTodoDocksOnPage(page) {
90
90
  let current = "";
91
91
  items.forEach((li) => {
92
92
  if (li.classList.contains("wg-todo-current")) {
93
- current = (li.textContent || "").replace(/s+/g, " ").trim().slice(0, 48);
93
+ current = (li.textContent || "").replace(/\s+/g, " ").trim().slice(0, 48);
94
94
  }
95
95
  });
96
96
  return {
@@ -1,14 +1,14 @@
1
1
  import type { MemPage } from "../../engine/mem/mem.js";
2
- import type { Block } from "../../engine/block/block.js";
2
+ import type { SomeBlock } from "../../engine/block/block.js";
3
3
  import type { DemoPace } from "../../engine/stub/pace.js";
4
4
  import type { HighlightStyleDefaults } from "../../engine/stub/ring.js";
5
- import type { WgPage } from "../types.js";
5
+ import type { StepperRefs, WgPage } from "../types.js";
6
6
  import type { createOverlayStage } from "../overlay/overlay-stage.js";
7
7
  import type { Gate } from "./gate.js";
8
8
  import type { Pacing } from "./pacing.js";
9
9
  /** A step as the stepper sees it (the runner's PlayStep). */
10
10
  export interface StepperStep {
11
- block: Block<any, any> & {
11
+ block: SomeBlock & {
12
12
  description?: string;
13
13
  fastForward?: boolean;
14
14
  demoPace?: DemoPace;
@@ -30,17 +30,7 @@ export interface StepperContext {
30
30
  stage: ReturnType<typeof createOverlayStage>;
31
31
  gate: Gate;
32
32
  pacing: Pacing;
33
- refs: {
34
- stubBefore: {
35
- current: any[];
36
- };
37
- todoDock: {
38
- current: any;
39
- };
40
- device: {
41
- current: any;
42
- };
43
- };
33
+ refs: StepperRefs;
44
34
  title: string | undefined;
45
35
  stepperMode: "full" | "carousel";
46
36
  /** A title set by a stub (`ctx.title`) names the flow from that step on, until another stub sets a new one. */