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
@@ -0,0 +1,166 @@
1
+ // The demo panel after a step: what it resolved to (pretty / JSON), its rings, and the Next gate.
2
+ import { ignorePageGone } from "../../engine/page-gone.js";
3
+ import { installOverlay } from "../overlay/overlay.js";
4
+ import { cycleHighlightRings } from "../overlay/rings.js";
5
+ import { miniStepLabel } from "./step-panels.js";
6
+ export async function renderAfterStep(page, info) {
7
+ await installOverlay(page, info.title, info.bannerUi, info.caption, info.networkTabVisible);
8
+ // Clear any tracker from a previous highlight before cycling through this
9
+ // step's own.
10
+ await page
11
+ .evaluate(() => {
12
+ if (window.__wgRingTrack) {
13
+ window.removeEventListener("resize", window.__wgRingTrack);
14
+ window.removeEventListener("scroll", window.__wgRingTrack, true);
15
+ window.__wgRingTrack = null;
16
+ }
17
+ })
18
+ .catch(ignorePageGone);
19
+ const highlights = info.highlights || [];
20
+ const gatesFast = !!info.gatesFast;
21
+ // Cycle through EVERY declared/recovered highlight in order, each shown
22
+ // long enough to actually register - "it highlights something [...] then
23
+ // it highlights something [else] and next," not just the first one.
24
+ // duration / fastMode on stubAfter rings override the default
25
+ // 900ms / 200ms holds when set.
26
+ await cycleHighlightRings(page, highlights, gatesFast, {
27
+ pace: info.pace,
28
+ stepLabel: miniStepLabel(info),
29
+ todoDock: info.todoDock,
30
+ todos: info.todos,
31
+ todoSync: info.todoSync,
32
+ todoDockRef: info.todoDockRef,
33
+ // stubAfter already authored progress - do not re-index by ring 0..n
34
+ advanceTodos: false,
35
+ });
36
+ // After-ring advances may have moved the dock - surface latest for caller.
37
+ if (info.todoDockRef && info.todoDockRef.current) {
38
+ info.todoDock = info.todoDockRef.current;
39
+ }
40
+ const afterPayload = { ...info, stepLabel: miniStepLabel(info) };
41
+ await page
42
+ .evaluate((info) => {
43
+ // Reused in place - see renderBeforeStep's own comment on this.
44
+ let panel = __wgById("wg-panel");
45
+ const isNewPanel = !panel;
46
+ if (!panel) {
47
+ panel = document.createElement("div");
48
+ panel.id = "wg-panel";
49
+ }
50
+ const pct = Math.round(((info.index + 1) / info.total) * 100);
51
+ const heading = info.isLast
52
+ ? "End of chain - " + info.total + " / " + info.total + " blocks covered - " + info.blockName + " done"
53
+ : "Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + " done";
54
+ const buttonLabel = info.isLast ? "Finish" : "Next \u25B6";
55
+ const modulesClass = info.stepperMode === "full" ? "wg-modules-full" : "wg-modules-carousel";
56
+ const modules = info.allNames.map((name, idx) => {
57
+ const cls = idx <= info.moduleIndex ? "wg-mod-done" : "wg-mod-upcoming";
58
+ const desc = info.allDescriptions && info.allDescriptions[idx];
59
+ return __wgEl("span", { class: "wg-mod " + cls, title: desc || undefined }, String(name));
60
+ });
61
+ // QA-friendly by default ("LoginSuccess" -> "Login Success") - raw
62
+ // JSON is one click away for whoever actually wants __state.
63
+ const stateTag = info.result && info.result.__state ? info.result.__state : "";
64
+ const prettyText = stateTag
65
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
66
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2") || info.resultTag;
67
+ const pretty = window.__wgPretty !== false;
68
+ let autoNow = false;
69
+ try {
70
+ autoNow = localStorage.getItem("wg-autoplay") === "1";
71
+ }
72
+ catch {
73
+ /* private mode / blocked storage - defaults to manual */
74
+ }
75
+ const autoplay = __wgEl("input", { id: "wg-autoplay-cb", attrs: { type: "checkbox" } });
76
+ autoplay.checked = autoNow;
77
+ const episodes = info.allEpisodes && info.allEpisodes.length > 0
78
+ ? __wgEl("div", { id: "wg-episodes" }, info.allEpisodes.map((e) => {
79
+ const cls = e.episodeNumber < (info.episodeNumber ?? 0) ? "wg-ep-done"
80
+ : e.episodeNumber === info.episodeNumber ? "wg-ep-current"
81
+ : "wg-ep-upcoming";
82
+ return __wgEl("span", { class: "wg-ep-tab " + cls }, "Episode " + e.episodeNumber + ": " + (e.episodeTitle || ""));
83
+ }))
84
+ : null;
85
+ const pace = info.paceBadge || info.paceLabel
86
+ ? __wgEl("div", { class: "wg-pace", data: { paceKind: info.paceKind || "normal" } }, __wgEl("span", { class: "wg-pace-badge" }, info.paceBadge || "1x"), __wgEl("span", null, info.paceLabel || ""))
87
+ : null;
88
+ // Todos float in #wg-todo-dock via __wgSyncTodos, never in the panel.
89
+ __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..."));
90
+ if (isNewPanel) {
91
+ __wgAdd(panel);
92
+ requestAnimationFrame(() => panel.classList.add("wg-in"));
93
+ }
94
+ else {
95
+ panel.classList.add("wg-in");
96
+ }
97
+ if (window.__wgWirePanelChrome) {
98
+ window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
99
+ stepLabel: info.stepLabel || "",
100
+ forceCollapsed: info.forceCollapsed === true ? true : undefined,
101
+ fastForward: !!info.fastForward,
102
+ isApi: !!info.isApi,
103
+ });
104
+ }
105
+ if (window.__wgSyncTodos) {
106
+ const sync = info.todoSync || (info.todos && info.todos.length ? "set" : "keep");
107
+ // Prefer dock-only payload. Passing list:[] with a dock used to clear
108
+ // marks a frame later (empty-list clear path).
109
+ if (sync === "keep" && !info.todoDock) {
110
+ /* leave floating dock alone */
111
+ }
112
+ else {
113
+ const payload = {
114
+ sync: sync,
115
+ dock: info.todoDock || null,
116
+ pos: info.todoPos || null,
117
+ todoId: info.todoId || (info.todoDock && info.todoDock.id) || null,
118
+ };
119
+ if (!info.todoDock && info.todos && info.todos.length) {
120
+ payload.list = info.todos;
121
+ }
122
+ window.__wgSyncTodos(payload);
123
+ }
124
+ }
125
+ if (window.__wgStampModal) {
126
+ window.__wgStampModal(panel, "panel", {
127
+ phase: "after",
128
+ step: info.index + 1,
129
+ total: info.total,
130
+ block: info.blockName || "",
131
+ ready: true,
132
+ });
133
+ }
134
+ panel.querySelectorAll(".wg-toggle-btn").forEach((btn) => {
135
+ btn.addEventListener("click", () => {
136
+ const wantPretty = btn.getAttribute("data-mode") === "pretty";
137
+ window.__wgPretty = wantPretty;
138
+ panel.querySelectorAll(".wg-toggle-btn").forEach((b) => b.classList.remove("wg-active"));
139
+ btn.classList.add("wg-active");
140
+ panel.querySelector(".wg-result-pretty").style.display = wantPretty ? "block" : "none";
141
+ panel.querySelector(".wg-result-json").style.display = wantPretty ? "none" : "block";
142
+ });
143
+ });
144
+ const runBtn = __wgById("wg-run");
145
+ if (runBtn)
146
+ runBtn.addEventListener("click", () => window.__wgNext({}));
147
+ const cb = __wgById("wg-autoplay-cb");
148
+ if (cb) {
149
+ cb.addEventListener("change", () => {
150
+ try {
151
+ localStorage.setItem("wg-autoplay", cb.checked ? "1" : "0");
152
+ }
153
+ catch {
154
+ /* private mode / blocked storage - toggle still works this page */
155
+ }
156
+ const manual = __wgById("wg-gate-manual");
157
+ const auto = __wgById("wg-gate-auto");
158
+ if (manual)
159
+ manual.style.display = cb.checked ? "none" : "";
160
+ if (auto)
161
+ auto.style.display = cb.checked ? "" : "none";
162
+ });
163
+ }
164
+ }, afterPayload)
165
+ .catch(ignorePageGone);
166
+ }
@@ -0,0 +1,12 @@
1
+ import type { WgPage } from "../types.js";
2
+ import { type PanelTodos, type StepPanelInfo } from "./step-panels.js";
3
+ /** The panel before a step: its description, the mem it reads (editable), and whether its episode just began. */
4
+ export interface BeforePanelInfo extends StepPanelInfo, PanelTodos {
5
+ description: string;
6
+ keys: readonly {
7
+ name: string;
8
+ value: string;
9
+ }[];
10
+ justEnteredEpisode: boolean;
11
+ }
12
+ export declare function renderBeforeStep(page: WgPage, info: BeforePanelInfo): Promise<void>;
@@ -0,0 +1,241 @@
1
+ // The demo panel before a step: episode and step chips, pace, the mem it reads (editable), and the Run gate.
2
+ import { envFlag, envString } from "../../engine/env.js";
3
+ import { ignorePageGone, ignoreTimeout } from "../../engine/page-gone.js";
4
+ import { installOverlay } from "../overlay/overlay.js";
5
+ import { miniStepLabel } from "./step-panels.js";
6
+ export async function renderBeforeStep(page, info) {
7
+ await installOverlay(page, info.title, info.bannerUi, info.caption, info.networkTabVisible);
8
+ // A ring left highlighting the PREVIOUS step's element (and its live
9
+ // resize/scroll tracker) shouldn't linger once a new step's own panel is
10
+ // up - only relevant when a Block's act() doesn't navigate away, since a
11
+ // real navigation already wipes document.documentElement's children.
12
+ await page
13
+ .evaluate(() => {
14
+ if (window.__wgRingTrack) {
15
+ window.removeEventListener("resize", window.__wgRingTrack);
16
+ window.removeEventListener("scroll", window.__wgRingTrack, true);
17
+ window.__wgRingTrack = null;
18
+ }
19
+ if (window.__wgHideRing)
20
+ window.__wgHideRing();
21
+ if (window.__wgHideCursor)
22
+ window.__wgHideCursor();
23
+ })
24
+ .catch(ignorePageGone);
25
+ // miniStepLabel / todosHtmlFromInfo are Node-only — browser evaluate has no closure.
26
+ const payload = { ...info, stepLabel: miniStepLabel(info) };
27
+ await page
28
+ .evaluate((info) => {
29
+ // Reused in place, not removed + recreated, every step - the old
30
+ // remove()-then-fade-back-in cycle was a real flash on every single
31
+ // transition (the user: "the appear and disappear... hurts eyes and
32
+ // dizzy"). Updating one persistent element's content has nothing to
33
+ // flash - it only ever fades in ONCE, the first time this page
34
+ // genuinely has no panel yet (a real navigation wiped the whole
35
+ // document, or this is the very first step).
36
+ let panel = __wgById("wg-panel");
37
+ const isNewPanel = !panel;
38
+ if (!panel) {
39
+ panel = document.createElement("div");
40
+ panel.id = "wg-panel";
41
+ }
42
+ const pct = Math.round((info.index / info.total) * 100);
43
+ // A real tab bar, not a single text line - every episode this chain
44
+ // touches, the current one reading as active (the user: "if episode 1 is
45
+ // active, the episode 2 tab is inactive"). Absent entirely for an ad
46
+ // hoc block chain with no named Flows.
47
+ const episodes = info.allEpisodes && info.allEpisodes.length > 0
48
+ ? __wgEl("div", { id: "wg-episodes" }, info.allEpisodes.map((e) => {
49
+ const cls = e.episodeNumber < (info.episodeNumber ?? 0) ? "wg-ep-done"
50
+ : e.episodeNumber === info.episodeNumber ? "wg-ep-current"
51
+ : "wg-ep-upcoming";
52
+ // The gentle one-time glow only plays on the tab actually
53
+ // being entered right now, not on every render of it.
54
+ const enteredCls = info.justEnteredEpisode && e.episodeNumber === info.episodeNumber
55
+ ? " wg-ep-entered"
56
+ : "";
57
+ return __wgEl("span", { class: "wg-ep-tab " + cls + enteredCls }, "Episode " + e.episodeNumber + ": " + (e.episodeTitle || ""));
58
+ }))
59
+ : null;
60
+ // Scoped to THIS episode's own blocks (info.allNames is already the
61
+ // episode-local subset the caller computed) - moduleIndex is this
62
+ // step's position WITHIN that subset, not the whole chain's index -
63
+ // "in episode 2 it shows 2nd to the last... it should start from the
64
+ // first block" (the user).
65
+ const modulesClass = info.stepperMode === "full" ? "wg-modules-full" : "wg-modules-carousel";
66
+ const modules = info.allNames.map((name, idx) => {
67
+ const cls = idx < info.moduleIndex ? "wg-mod-done" : idx === info.moduleIndex ? "wg-mod-current" : "wg-mod-upcoming";
68
+ const desc = info.allDescriptions && info.allDescriptions[idx];
69
+ return __wgEl("span", { class: "wg-mod " + cls, title: desc || undefined }, String(name));
70
+ });
71
+ const pace = info.paceBadge || info.paceLabel
72
+ ? __wgEl("div", { class: "wg-pace", data: { paceKind: info.paceKind || "normal" } }, __wgEl("span", { class: "wg-pace-badge" }, info.paceBadge || "1x"), __wgEl("span", null, info.paceLabel || ""))
73
+ : null;
74
+ const keys = info.keys.length === 0
75
+ ? [__wgEl("div", { class: "wg-key" }, "(no MemKeys required)")]
76
+ : info.keys.map((k) => {
77
+ const field = __wgEl("textarea", { data: { key: k.name }, attrs: { rows: 2 } });
78
+ field.value = k.value;
79
+ return __wgEl("div", { class: "wg-key" }, __wgEl("label", null, k.name), field, __wgEl("div", { class: "wg-key-pretty", data: { prettyFor: k.name } }));
80
+ });
81
+ let autoNow = false;
82
+ try {
83
+ autoNow = localStorage.getItem("wg-autoplay") === "1";
84
+ }
85
+ catch {
86
+ /* private mode / blocked storage - defaults to manual */
87
+ }
88
+ const autoplay = __wgEl("input", { id: "wg-autoplay-cb", attrs: { type: "checkbox" } });
89
+ autoplay.checked = autoNow;
90
+ // Todos render into floating #wg-todo-dock via __wgSyncTodos - never
91
+ // inside .wg-body (would vanish under --mini / Hide collapse).
92
+ __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..."));
93
+ if (isNewPanel) {
94
+ __wgAdd(panel);
95
+ requestAnimationFrame(() => panel.classList.add("wg-in"));
96
+ }
97
+ else {
98
+ panel.classList.add("wg-in");
99
+ }
100
+ if (window.__wgWirePanelChrome) {
101
+ window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
102
+ stepLabel: info.stepLabel || "",
103
+ forceCollapsed: info.forceCollapsed === true ? true : undefined,
104
+ fastForward: !!info.fastForward,
105
+ isApi: !!info.isApi,
106
+ });
107
+ }
108
+ if (window.__wgSyncTodos) {
109
+ const sync = info.todoSync || (info.todos && info.todos.length ? "set" : "keep");
110
+ // Prefer dock-only payload. Passing list:[] with a dock used to clear
111
+ // marks a frame later (empty-list clear path).
112
+ if (sync === "keep" && !info.todoDock) {
113
+ /* leave floating dock alone */
114
+ }
115
+ else {
116
+ const payload = {
117
+ sync: sync,
118
+ dock: info.todoDock || null,
119
+ pos: info.todoPos || null,
120
+ todoId: info.todoId || (info.todoDock && info.todoDock.id) || null,
121
+ };
122
+ if (!info.todoDock && info.todos && info.todos.length) {
123
+ payload.list = info.todos;
124
+ }
125
+ window.__wgSyncTodos(payload);
126
+ }
127
+ }
128
+ if (window.__wgStampModal) {
129
+ window.__wgStampModal(panel, "panel", {
130
+ phase: "before",
131
+ step: info.index + 1,
132
+ total: info.total,
133
+ block: info.blockName || "",
134
+ ready: true,
135
+ });
136
+ }
137
+ // Live, human-readable preview of what a MemKey's raw JSON will
138
+ // actually write - object fields become "Field: value" lines
139
+ // (camelCase split the same way state tags already are); a
140
+ // non-object payload (a plain string/number/array) is shown as-is.
141
+ // Hidden entirely in JSON mode - shares the SAME global toggle the
142
+ // result display's own Pretty/JSON buttons already set, not a
143
+ // second, separate preference.
144
+ const prettyMemValue = (raw) => {
145
+ let parsed;
146
+ try {
147
+ parsed = JSON.parse(raw);
148
+ }
149
+ catch {
150
+ return [__wgEl("em", null, "(invalid JSON)")];
151
+ }
152
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
153
+ return [JSON.stringify(parsed)];
154
+ }
155
+ return Object.entries(parsed).map(([field, value]) => {
156
+ const label = field.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
157
+ return __wgEl("div", { class: "wg-key-pretty-row" }, __wgEl("span", { class: "wg-key-pretty-label" }, label + ":"), " " + String(value));
158
+ });
159
+ };
160
+ const applyKeyPretty = () => {
161
+ const prettyOn = window.__wgPretty !== false;
162
+ panel.querySelectorAll("textarea[data-key]").forEach((ta) => {
163
+ const previewEl = ta.parentElement?.querySelector(".wg-key-pretty");
164
+ if (!previewEl)
165
+ return;
166
+ previewEl.style.display = prettyOn ? "" : "none";
167
+ if (prettyOn)
168
+ __wgSet(previewEl, prettyMemValue(ta.value));
169
+ });
170
+ };
171
+ applyKeyPretty();
172
+ panel.querySelectorAll("textarea[data-key]").forEach((ta) => {
173
+ ta.addEventListener("input", applyKeyPretty);
174
+ });
175
+ const runBtn = __wgById("wg-run");
176
+ if (runBtn) {
177
+ runBtn.addEventListener("click", () => {
178
+ const edits = {};
179
+ panel.querySelectorAll("textarea[data-key]").forEach((el) => {
180
+ edits[el.getAttribute("data-key") ?? ""] = el.value;
181
+ });
182
+ window.__wgNext(edits);
183
+ });
184
+ }
185
+ // Live toggle - flips localStorage immediately so an in-flight gate()
186
+ // poll (on the Node side) picks it up within its next poll slice,
187
+ // without needing this whole panel to re-render. A manual click
188
+ // still always wins over an active autoplay wait, whichever the
189
+ // checkbox says - this is the "hybrid" control: autoplay is a
190
+ // default, not a lockout.
191
+ const cb = __wgById("wg-autoplay-cb");
192
+ if (cb) {
193
+ cb.addEventListener("change", () => {
194
+ try {
195
+ localStorage.setItem("wg-autoplay", cb.checked ? "1" : "0");
196
+ }
197
+ catch {
198
+ /* private mode / blocked storage - toggle still works this page */
199
+ }
200
+ const manual = __wgById("wg-gate-manual");
201
+ const auto = __wgById("wg-gate-auto");
202
+ if (manual)
203
+ manual.style.display = cb.checked ? "none" : "";
204
+ if (auto)
205
+ auto.style.display = cb.checked ? "" : "none";
206
+ });
207
+ }
208
+ }, payload)
209
+ .catch(ignorePageGone);
210
+ if (envString("WAYGRAPH_PROVE_SHOT") || envFlag("WAYGRAPH_PROVE_EXIT")) {
211
+ // Default: prove once on first painted panel. PROVE_EVERY=1 re-checks
212
+ // every step (noisy; useful when hunting a mid-chain blank).
213
+ const proveEvery = envFlag("WAYGRAPH_PROVE_EVERY");
214
+ const already = globalThis.__wgProveDone;
215
+ if (!already || proveEvery || envFlag("WAYGRAPH_PROVE_EXIT")) {
216
+ await page.waitForTimeout(500).catch(ignoreTimeout);
217
+ const shot = envString("WAYGRAPH_PROVE_SHOT");
218
+ if (shot)
219
+ await page.screenshot({ path: shot }).catch(ignorePageGone);
220
+ const ok = await page
221
+ .evaluate(() => {
222
+ const beacons = typeof window.__wgOverlayBeacon === "function" ? window.__wgOverlayBeacon() : [];
223
+ const panel = beacons.find((b) => b.modal === "panel" && b.ready && b.visible);
224
+ const textOk = panel && panel.textLen >= 12;
225
+ const opacityOk = panel && Number(panel.opacity) > 0.5;
226
+ return {
227
+ ok: !!(panel && textOk && opacityOk),
228
+ beacons,
229
+ hasRun: !!__wgById("wg-run"),
230
+ readyAttr: __wgById("wg-panel")?.getAttribute("data-wg-ready") || null,
231
+ };
232
+ })
233
+ .catch((e) => ({ ok: false, reason: String(e instanceof Error ? e.message : e) }));
234
+ console.error("WAYGRAPH_PROVE " + JSON.stringify(ok));
235
+ globalThis.__wgProveDone = true;
236
+ if (envFlag("WAYGRAPH_PROVE_EXIT")) {
237
+ process.exit(ok && ok.ok ? 0 : 2);
238
+ }
239
+ }
240
+ }
241
+ }
@@ -1,4 +1,17 @@
1
1
  import type { WgPage } from "../types.js";
2
+ import { type WaygraphSlide } from "../../engine/stub/ring.js";
3
+ import type { DemoPace } from "../../engine/stub/pace.js";
4
+ import type { StepperContext, StepView } from "./view.js";
5
+ /** Where the slides sit (title, Block, episode) and how fast they advance. */
6
+ export interface SlidesOptions {
7
+ title?: string | undefined;
8
+ blockName?: string | undefined;
9
+ episodeNumber?: number | undefined;
10
+ episodeTitle?: string | undefined;
11
+ fast?: boolean | undefined;
12
+ pace?: DemoPace | undefined;
13
+ autoplayMs?: number | undefined;
14
+ }
2
15
  /**
3
16
  * Multi-step yap captions - not block lifecycle. Each slide waits for Next
4
17
  * (or auto-next), optional ring via slide.selector.
@@ -8,4 +21,6 @@ import type { WgPage } from "../types.js";
8
21
  * __wgNext is momentarily wedged, which left "Next slide" looking live but
9
22
  * doing nothing. In-page data-wg-acked + autoplay poll is the source of truth.
10
23
  */
11
- export declare function presentSlides(page: WgPage, slides: any, _gate: any, opts: any): Promise<void>;
24
+ export declare function presentSlides(page: WgPage, slides: readonly WaygraphSlide[], opts?: SlidesOptions): Promise<void>;
25
+ /** Plays a stub phase's `ctx.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.
@@ -14,7 +14,7 @@ import { envFlag, envNumber, envString } from "../../engine/env.js";
14
14
  * __wgNext is momentarily wedged, which left "Next slide" looking live but
15
15
  * doing nothing. In-page data-wg-acked + autoplay poll is the source of truth.
16
16
  */
17
- export async function presentSlides(page, slides, _gate, opts) {
17
+ export async function presentSlides(page, slides, opts) {
18
18
  const title = (opts && opts.title) || "waygraph demo";
19
19
  const blockName = (opts && opts.blockName) || "";
20
20
  const episodeNumber = opts && opts.episodeNumber;
@@ -39,7 +39,7 @@ export async function presentSlides(page, slides, _gate, opts) {
39
39
  if (s.selector) {
40
40
  try {
41
41
  await ensureSelectorInView(page, s.selector);
42
- await applyHighlightZoom(page, s.selector, s.zoom, s.zoomOut);
42
+ await applyHighlightZoom(page, s.selector, s.zoom, undefined);
43
43
  // Playwright's own locator (>> piercing / :has-text() / :visible support), not a raw
44
44
  // querySelector string - see rings.js's showRing for the same fix, same reason. count()
45
45
  // gate: Locator.evaluate() has no timeout option and auto-waits ~30s for a match to
@@ -57,7 +57,7 @@ export async function presentSlides(page, slides, _gate, opts) {
57
57
  await showRing(page, box, caption, slideTone, {
58
58
  ...slideStyle,
59
59
  selector: s.selector,
60
- focus: !!s.focus,
60
+ focus: false,
61
61
  });
62
62
  }
63
63
  }
@@ -231,3 +231,19 @@ export async function presentSlides(page, slides, _gate, opts) {
231
231
  }
232
232
  await hideRing(page);
233
233
  }
234
+ /** Plays a stub phase's `ctx.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
+ }
@@ -1,12 +1,64 @@
1
- import type { WgPage } from "../types.js";
2
- export declare function renderBeforeStep(page: WgPage, info: any): Promise<void>;
1
+ import type { SomeBlock } from "../../engine/block/block.js";
2
+ import type { Checkpoint } from "../../engine/checkpoint/checkpoint.js";
3
+ import type { MemPage } from "../../engine/mem/mem.js";
4
+ import type { HighlightStyleDefaults } from "../../engine/stub/ring.js";
5
+ import type { TodoDockState, WaygraphTodoItem } from "../../engine/stub/todos.js";
6
+ import type { DemoPace } from "../../engine/stub/pace.js";
7
+ import type { BannerUiState, WgPage } from "../types.js";
8
+ /** What every step panel shows: where the step sits in the run and its episode, its pace, and how the panel looks. */
9
+ export interface StepPanelInfo {
10
+ index: number;
11
+ total: number;
12
+ blockName: string;
13
+ allNames: readonly string[];
14
+ allDescriptions?: readonly string[] | undefined;
15
+ moduleIndex: number;
16
+ allEpisodes?: readonly {
17
+ episodeNumber: number;
18
+ episodeTitle: string;
19
+ }[] | undefined;
20
+ episodeNumber?: number | undefined;
21
+ episodeTitle?: string | undefined;
22
+ title?: string | undefined;
23
+ bannerUi?: BannerUiState | null | undefined;
24
+ caption?: string | undefined;
25
+ networkTabVisible?: boolean | undefined;
26
+ stepperMode?: "full" | "carousel" | undefined;
27
+ forceCollapsed?: boolean | undefined;
28
+ fastForward?: boolean | undefined;
29
+ isApi?: boolean | undefined;
30
+ paceBadge?: string | undefined;
31
+ paceLabel?: string | undefined;
32
+ paceKind?: string | undefined;
33
+ }
34
+ /** The todo dock a panel syncs when it paints. */
35
+ export interface PanelTodos {
36
+ todos?: readonly WaygraphTodoItem[] | undefined;
37
+ todoDock?: TodoDockState | undefined;
38
+ todoSync?: "set" | "clear" | "keep" | undefined;
39
+ todoId?: string | undefined;
40
+ todoPos?: string | undefined;
41
+ }
42
+ /** A failed step: what threw (or the expected-failure result), its stubOnError narration, and the Retry/Stop gate. */
43
+ export interface FailPanelOptions extends StepPanelInfo {
44
+ block: SomeBlock;
45
+ highlightStyle?: HighlightStyleDefaults | undefined;
46
+ error: unknown;
47
+ out?: Checkpoint<string>;
48
+ message: string;
49
+ expectedFailureReason?: string | undefined;
50
+ gatesFast?: boolean | undefined;
51
+ demoPace?: DemoPace | undefined;
52
+ gate: () => Promise<Record<string, string>>;
53
+ mem: MemPage;
54
+ }
55
+ export declare function miniStepLabel(info: Pick<StepPanelInfo, "episodeNumber" | "index" | "total" | "blockName" | "paceBadge">): string;
3
56
  /**
4
57
  * stubOnError rings + amber "expected outcome" / red error panel, then wait
5
58
  * for Stop/Retry. Used when a step throws OR when a `map({ expectedFailure })` flow's last
6
59
  * block succeeds on the intentional fail branch (e.g. LoginPage + error banner).
7
60
  */
8
- export declare function presentFailPanel(page: WgPage, opts: any): Promise<any>;
9
- export declare function renderAfterStep(page: WgPage, info: any): Promise<void>;
61
+ export declare function presentFailPanel(page: WgPage, opts: FailPanelOptions): Promise<Record<string, string>>;
10
62
  /**
11
63
  * Recovers a DOM selector from a built-in Trait's own .name string -
12
64
  * visible(sel) / text-equals(sel, "...") - since Trait.check itself is
@@ -14,8 +66,4 @@ export declare function renderAfterStep(page: WgPage, info: any): Promise<void>;
14
66
  * hand-written bespoke Trait, or url-matches(...) (no DOM target), yields
15
67
  * nothing to highlight, which is fine - the panel still shows the result.
16
68
  */
17
- export declare function extractVerifyHighlights(block: any, resultTag: any): {
18
- selector: string | undefined;
19
- label: string;
20
- tone: string;
21
- }[];
69
+ export declare function extractVerifyHighlights(block: SomeBlock, resultTag: string): import("../../engine/stub/ring.js").WaygraphHighlightStub[];