waygraph 0.16.1 → 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 (55) hide show
  1. package/dist/cli/commands/codemod-stubs.js +90 -2
  2. package/dist/cli/run-chain.js +2 -2
  3. package/dist/cli/util.js +1 -1
  4. package/dist/demo/narration/demo-pace.d.ts +1 -1
  5. package/dist/demo/narration/demo-pace.js +1 -1
  6. package/dist/demo/narration/merge.d.ts +1 -1
  7. package/dist/demo/narration/merge.js +1 -1
  8. package/dist/demo/narration/narration.d.ts +2 -6
  9. package/dist/demo/narration/narration.js +5 -17
  10. package/dist/demo/narration/stub-ctx.js +3 -3
  11. package/dist/demo/overlay/inpage/core.js +0 -6
  12. package/dist/demo/overlay/overlay-css.js +1 -55
  13. package/dist/demo/overlay/overlay.js +3 -2
  14. package/dist/demo/overlay/rings.d.ts +0 -8
  15. package/dist/demo/overlay/rings.js +0 -15
  16. package/dist/demo/overlay/ui/overlay.css +40 -0
  17. package/dist/demo/stepper/after.js +4 -17
  18. package/dist/demo/stepper/before.js +3 -1
  19. package/dist/demo/stepper/demo-log.js +1 -1
  20. package/dist/demo/stepper/interaction-patch.js +3 -15
  21. package/dist/demo/stepper/panel-after.js +1 -1
  22. package/dist/demo/stepper/slides.d.ts +3 -0
  23. package/dist/demo/stepper/slides.js +18 -2
  24. package/dist/demo/stepper/teardown.js +0 -1
  25. package/dist/demo/verbose-log.js +1 -1
  26. package/dist/engine/block/block.d.ts +1 -12
  27. package/dist/engine/block/kinds/method.d.ts +0 -2
  28. package/dist/engine/block/kinds/method.js +0 -1
  29. package/dist/engine/block/kinds/nav.js +1 -1
  30. package/dist/engine/run/run.d.ts +1 -19
  31. package/dist/engine/run/run.js +0 -88
  32. package/dist/engine/stub/ring.d.ts +2 -4
  33. package/dist/engine/stub/ring.js +1 -0
  34. package/dist/engine/stub/stub.d.ts +16 -8
  35. package/dist/index.d.ts +2 -3
  36. package/dist/index.js +2 -2
  37. package/dist/runner/plan/find.js +2 -2
  38. package/dist/runner/plan/plan.js +1 -1
  39. package/dist/runner/plan/seed-mem.js +5 -5
  40. package/dist/runner/plan/spec.js +1 -1
  41. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
  42. package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +2 -1
  43. package/examples/saucedemo/src/flows/checkout.flow.ts +2 -1
  44. package/examples/saucedemo/src/flows/shop.flow.ts +2 -1
  45. package/examples/saucedemo/src/stubs/device-showcase.ts +37 -43
  46. package/examples/saucedemo/src/stubs/finish-order.ts +38 -0
  47. package/package.json +1 -1
  48. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
  49. package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +2 -1
  50. package/templates/quickstart/src/flows/checkout.flow.ts +2 -1
  51. package/templates/quickstart/src/flows/shop.flow.ts +2 -1
  52. package/templates/quickstart/src/stubs/device-showcase.ts +37 -43
  53. package/templates/quickstart/src/stubs/finish-order.ts +38 -0
  54. package/templates/scaffold/src/flows/home/hello.flow.ts +3 -0
  55. package/templates/scaffold/src/map/(app_base)/home/_methods/assert-hello.method.block.ts +1 -9
@@ -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.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
+ }
@@ -154,7 +154,6 @@ export async function teardownOverlay(page) {
154
154
  window.removeEventListener("scroll", window.__wgRingTrack, true);
155
155
  window.__wgRingTrack = null;
156
156
  }
157
- window.__wgNarrateOwnsRing = false;
158
157
  for (const id of [
159
158
  "wg-panel",
160
159
  "wg-ring",
@@ -1,7 +1,7 @@
1
1
  import { envFlag } from "../engine/env.js";
2
2
  /**
3
3
  * One log format for the live overlay surfaces (auto / browser / pilot): `[HH:MM:SS.mmm] waygraph <surface>: ...`, the
4
- * same shape as the demo runner's `step-mode/demo-log.ts`. Silenced by WAYGRAPH_JSON=1.
4
+ * same shape as the demo stepper's `stepper/demo-log.ts`. Silenced by WAYGRAPH_JSON=1.
5
5
  */
6
6
  function ts() {
7
7
  const d = new Date();
@@ -2,7 +2,6 @@ import type { Page } from "@playwright/test";
2
2
  import type { Checkpoint } from "../checkpoint/checkpoint.js";
3
3
  import type { MemKey } from "../mem/mem.js";
4
4
  import { MemPage } from "../mem/mem.js";
5
- import type { WaygraphSlidesOrFn } from "../stub/ring.js";
6
5
  import type { StubLifecycleFn } from "../stub/stub.js";
7
6
  import type { Trait } from "../trait/trait.js";
8
7
  import type { ActionPage, WaygraphInstanceOption } from "./action-page.js";
@@ -56,16 +55,6 @@ export interface Instruction<In extends Checkpoint<string>, Out extends Checkpoi
56
55
  stubAfter?: StubLifecycleFn<Out>;
57
56
  /** When the step fails, before the failure panel; written by `Block.stubOnError((ctx) => ...)`. */
58
57
  stubOnError?: StubLifecycleFn<Out>;
59
- /**
60
- * Multi-step demo captions ("yap" slides) with Next between each -
61
- * not block lifecycle. Explains a long process; optional `selector` rings
62
- * while that slide is up. Flow fixtures may override via `slides`.
63
- * @example slides: [
64
- * { caption: "Checkout is three screens", tag: "YAP" },
65
- * { caption: "Overview confirms totals", selector: ".summary_info" },
66
- * ]
67
- */
68
- slides?: WaygraphSlidesOrFn<Out>;
69
58
  }
70
59
  /**
71
60
  * A Block wraps one instruction. Its Observed type is an internal detail, erased here.
@@ -90,7 +79,7 @@ export interface Block<In extends Checkpoint<string>, Out extends Checkpoint<str
90
79
  * A human-readable sentence describing what this Block actually tests or
91
80
  * does - "the outsider case: a user outside a request's audience is
92
81
  * denied its detail page," not a restatement of the code. Purely
93
- * additive, optional narration for a human watching a run (step-mode
82
+ * additive, optional narration for a human watching a run (demo
94
83
  * tooling shows it before the Block runs) - never read by
95
84
  * `runGraph`/`connect`/anything that decides behavior. A Block with none
96
85
  * just shows its `name` instead.
@@ -39,8 +39,6 @@ export interface AssertBlockOptions<Out extends Checkpoint<string> = Checkpoint<
39
39
  verify: Trait[] | ((out: Out) => Trait[]);
40
40
  /** Mem keys this assertion's `verify` reads (e.g. a mem-aware Trait) - checked by preflight before the flow runs. */
41
41
  requires?: readonly MemKey<unknown>[];
42
- /** Multi-step yap slides (demo only) - same field `Instruction.slides` exposes elsewhere. */
43
- slides?: import("../../stub/ring.js").WaygraphSlidesOrFn<Out>;
44
42
  }
45
43
  /**
46
44
  * Sugar for a self-loop-only "pure assertion" Block: no hand-written
@@ -42,7 +42,6 @@ export function defineAssertBlock(options) {
42
42
  }
43
43
  },
44
44
  verify: options.verify,
45
- ...(options.slides ? { slides: options.slides } : {}),
46
45
  },
47
46
  });
48
47
  Object.defineProperty(built, "__waygraphKind", {
@@ -28,7 +28,7 @@ export function defineNavBlock(options) {
28
28
  await page.goto(url);
29
29
  }
30
30
  else {
31
- // Always Locator.click() so step-mode's demo cursor/pulse patch
31
+ // Always Locator.click() so the demo stepper's cursor/pulse patch
32
32
  // (instrumentInteractionHighlighting) sees NavBlock click-nav -
33
33
  // never page.click() / goto teleport.
34
34
  const selector = typeof options.click === "function" ? options.click(mem) : options.click;
@@ -2,7 +2,7 @@ import { MemPage } from "../mem/mem.js";
2
2
  import type { Block } from "../block/block.js";
3
3
  import type { Checkpoint } from "../checkpoint/checkpoint.js";
4
4
  import type { Trait } from "../trait/trait.js";
5
- import type { BrowserContext, Locator, Page } from "@playwright/test";
5
+ import type { BrowserContext, Page } from "@playwright/test";
6
6
  import type { SomeBlock } from "../block/block.js";
7
7
  /**
8
8
  * Checks `block.requires` against `mem` and throws, naming every missing key at
@@ -125,21 +125,3 @@ export interface RunGraphOptions {
125
125
  * }
126
126
  */
127
127
  export declare function spawnTab<TOut extends Checkpoint<string>>(entry: Block<Checkpoint<"__start__">, TOut>, page: Page, mem: MemPage, maxSteps?: number): Promise<TOut>;
128
- /**
129
- * Wraps one `act()` interaction with a caption and highlight, for a Block
130
- * author who wants to explicitly narrate a specific step rather than rely
131
- * on the generic per-fill/per-click narration `waygraph chain
132
- * WAYGRAPH_STEP=1` already does automatically -
133
- * `await narrate(submitButton, "confirms the order", () => submitButton.click())`
134
- * reads at the call site instead of being inferred.
135
- *
136
- * A true no-op everywhere except inside a `WAYGRAPH_STEP=1` run: it checks
137
- * for a `window.__wgPositionRing` hook the step-mode overlay installs on
138
- * the page before doing anything, so a headless/CI/automated run (nobody
139
- * watching, per-character delays and highlight pauses would just be wasted
140
- * time) pays zero extra cost - not even the boundingBox() lookup runs. The
141
- * `action` itself always runs regardless; only the narration is
142
- * conditional.
143
- * @example await narrate(submitButton, "confirms the order", () => submitButton.click())
144
- */
145
- export declare function narrate<T>(locator: Locator, caption: string, action: () => Promise<T>): Promise<T>;
@@ -2,7 +2,6 @@ import { WaygraphError, MSG_PREFLIGHT_MISSING_KEY, MSG_RUNGRAPH_MAX_STEPS, MSG_R
2
2
  import { checkpoint } from "../checkpoint/checkpoint.js";
3
3
  import { beginRun, endRun, isConnected, lastLeaf, stepEnded, stepFailed, stepStarted } from "./events.js";
4
4
  import { runPrecondition, runVerify } from "../trait/trait.js";
5
- import { ignorePageGone } from "../page-gone.js";
6
5
  /**
7
6
  * Checks `block.requires` against `mem` and throws, naming every missing key at
8
7
  * once, if any are unset. `connect()` unions each Block's `requires` into the
@@ -144,90 +143,3 @@ export async function runGraph(entry, terminals, context, mem, maxSteps = 5000,
144
143
  export async function spawnTab(entry, page, mem, maxSteps = 5000) {
145
144
  return runGraph(entry, undefined, page.context(), mem, maxSteps);
146
145
  }
147
- /**
148
- * Wraps one `act()` interaction with a caption and highlight, for a Block
149
- * author who wants to explicitly narrate a specific step rather than rely
150
- * on the generic per-fill/per-click narration `waygraph chain
151
- * WAYGRAPH_STEP=1` already does automatically -
152
- * `await narrate(submitButton, "confirms the order", () => submitButton.click())`
153
- * reads at the call site instead of being inferred.
154
- *
155
- * A true no-op everywhere except inside a `WAYGRAPH_STEP=1` run: it checks
156
- * for a `window.__wgPositionRing` hook the step-mode overlay installs on
157
- * the page before doing anything, so a headless/CI/automated run (nobody
158
- * watching, per-character delays and highlight pauses would just be wasted
159
- * time) pays zero extra cost - not even the boundingBox() lookup runs. The
160
- * `action` itself always runs regardless; only the narration is
161
- * conditional.
162
- * @example await narrate(submitButton, "confirms the order", () => submitButton.click())
163
- */
164
- export async function narrate(locator, caption, action) {
165
- const page = locator.page();
166
- const active = await page
167
- .evaluate(() => typeof globalThis.__wgPositionRing === "function")
168
- .catch(() => false);
169
- if (!active)
170
- return action();
171
- let owning = false;
172
- try {
173
- const box = await locator.boundingBox();
174
- if (box) {
175
- await page
176
- .evaluate(({ box, caption }) => {
177
- const w = globalThis;
178
- if (w.__wgPositionRing)
179
- w.__wgPositionRing(box, caption, "planned");
180
- // The CLI's own automatic per-fill/per-click narration checks
181
- // this before showing (and overwriting) its own guess - an
182
- // explicitly authored caption should win, not get immediately
183
- // replaced a moment later by the generic one.
184
- w.__wgLastNarrate = Date.now();
185
- // Also claim the ring itself: the auto-highlight click/fill
186
- // patches each hide the ring right after THEIR own step
187
- // finishes, which - if action() below does more than one thing
188
- // (e.g. a click then a wait for a toast) - would wipe this
189
- // caption after just the first sub-step instead of the whole
190
- // narrated action. Their hideRing() calls no-op while this is
191
- // true; narrate() below is the one that actually clears it.
192
- w.__wgNarrateOwnsRing = true;
193
- }, { box, caption })
194
- .catch(ignorePageGone);
195
- owning = true;
196
- // The ring alone is pointless if action() fires on the very next
197
- // tick - a click that navigates (e.g. a form submit) wipes the ring
198
- // before a human can read the caption at all. Give it the same
199
- // "pop for a few seconds" dwell as the CLI's own auto-highlight
200
- // clicks before the real action runs.
201
- await new Promise((resolve) => setTimeout(resolve, 650));
202
- // Re-stamp __wgLastNarrate right before handing off to action() - the
203
- // CLI's auto-highlight patches only treat a narrate() as "just
204
- // handled" within a short window (500ms), which the dwell above
205
- // already burned through. Without this, action()'s own click/fill
206
- // (same patched Locator prototype) would stomp this caption with its
207
- // own generic one a moment after it finally became visible.
208
- await page
209
- .evaluate(() => {
210
- globalThis.__wgLastNarrate = Date.now();
211
- })
212
- .catch(ignorePageGone);
213
- }
214
- }
215
- catch {
216
- // best-effort - the real action below still runs either way
217
- }
218
- try {
219
- return await action();
220
- }
221
- finally {
222
- if (owning) {
223
- await page
224
- .evaluate(() => {
225
- const w = globalThis;
226
- w.__wgNarrateOwnsRing = false;
227
- if (w.__wgHideRing)
228
- w.__wgHideRing();
229
- })
230
- .catch(ignorePageGone);
231
- }
232
- }
233
- }
@@ -1,4 +1,3 @@
1
- import type { Checkpoint } from "../checkpoint/checkpoint.js";
2
1
  /** Shared dwell fields on stubs, fixtures, and yap slides. */
3
2
  export type FixtureDurationFields = {
4
3
  /**
@@ -83,8 +82,8 @@ export interface WaygraphHighlightStub extends FixtureDurationFields {
83
82
  /** A static stub phase: named ring slots to paint before or after a step. */
84
83
  export type HighlightStubPhase = Record<string, WaygraphHighlightStub>;
85
84
  /**
86
- * One yap/slide in a multi-step demo caption sequence.
87
- * Not block lifecycle - demo pauses (Next / auto-next) between slides.
85
+ * One yap/slide in a multi-step demo caption sequence, painted with {@link StubCtx.slides}.
86
+ * The demo pauses on each (Next, or auto-next) before moving on.
88
87
  */
89
88
  export interface WaygraphSlide extends FixtureDurationFields {
90
89
  /** Primary caption (required). */
@@ -119,7 +118,6 @@ export type HighlightStyleDefaults = {
119
118
  size?: HighlightSize;
120
119
  weight?: HighlightWeight;
121
120
  };
122
- export type WaygraphSlidesOrFn<Out extends Checkpoint<string>> = readonly WaygraphSlide[] | ((out: Out) => readonly WaygraphSlide[]);
123
121
  /** A ring or caption after its defaults and overrides are merged, ready to paint. */
124
122
  export type ResolvedHighlight = WaygraphHighlightStub;
125
123
  /** Map author aliases (`error` -> danger) to a canonical tone. */
@@ -1,3 +1,4 @@
1
+ // A ring: one highlighted element with its caption, and its tone, size and weight; slides between steps.
1
2
  /** Map author aliases (`error` -> danger) to a canonical tone. */
2
3
  export function normalizeHighlightTone(raw) {
3
4
  if (typeof raw !== "string")
@@ -2,7 +2,7 @@ import type { Checkpoint } from "../checkpoint/checkpoint.js";
2
2
  import type { MemPage } from "../mem/mem.js";
3
3
  import type { TodoDockState, TodoDockUiOpts, TodoListStyle, WaygraphTodoGroupInput, WaygraphTodoInput, WaygraphTodoItem, WaygraphTodosOpts } from "./todos.js";
4
4
  import type { DeviceOrientation, DevicePreset, DeviceState, DeviceViewport } from "./viewport.js";
5
- import type { HighlightStubPhase, ResolvedHighlight, WaygraphHighlightStub } from "./ring.js";
5
+ import type { HighlightStubPhase, ResolvedHighlight, WaygraphHighlightStub, WaygraphSlide } from "./ring.js";
6
6
  /** Where the top banner sits. */
7
7
  export type BannerPos = "left" | "center" | "right";
8
8
  /** Banner UX, mirrors {@link TodoDockUiOpts}: pass `false` fields to opt out for this phase. */
@@ -87,10 +87,8 @@ export type StubPhaseFixtures = {
87
87
  * LoginBlock.stubBefore((ctx) => {
88
88
  * ctx.todos("sign-in", ["Enter email", "Enter password", "Click Sign in"], { index: 0 });
89
89
  * ctx.zoom(1.35);
90
- * ctx.highlights({
91
- * email: { selector: "#email", label: "Email" },
92
- * submit: { selector: "#login-button", label: "Sign in" },
93
- * });
90
+ * ctx.ring("email", { selector: "#email", label: "Email" });
91
+ * ctx.ring("submit", { selector: "#login-button", label: "Sign in" });
94
92
  * })
95
93
  * // Sequential plan: bump todoIndex in a later phase / a later Block's stubBefore
96
94
  * .stubAfter((ctx) => {
@@ -109,9 +107,7 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
109
107
  readonly out: Out | undefined;
110
108
  /** Present on stubOnError when the step threw. */
111
109
  readonly error: unknown | undefined;
112
- /** Replace named highlight rings for this phase. */
113
- highlights(slots: HighlightStubPhase): void;
114
- /** Set / overwrite one named ring. */
110
+ /** Paints one named ring this phase; the same id again replaces it. */
115
111
  ring(id: string, stub: WaygraphHighlightStub): void;
116
112
  /**
117
113
  * Floating checklist: `ctx.todos("login", ["Enter email", "Enter password"], { title: "Sign in", index: 0 })`.
@@ -174,6 +170,15 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
174
170
  caption(text: string): void;
175
171
  /** Clears the caption (fades it out). */
176
172
  hideCaption(): void;
173
+ /**
174
+ * Multi-step "yap" captions with Next between each, for explaining a longer process. In `stubBefore` they play
175
+ * before the step runs, in `stubAfter` after it lands; a slide with a `selector` rings it while it is up.
176
+ * @example ctx.slides([
177
+ * { caption: "Checkout is three screens", tag: "YAP" },
178
+ * { caption: "Overview confirms totals", selector: ".summary_info", duration: true },
179
+ * ]);
180
+ */
181
+ slides(slides: readonly WaygraphSlide[]): void;
177
182
  /** Shorthand for `bannerUi({ hidden: true })`. */
178
183
  hideBanner(): void;
179
184
  /**
@@ -226,6 +231,8 @@ export type StubPhaseResult = {
226
231
  bannerUi?: BannerUiOpts;
227
232
  caption?: string;
228
233
  networkTabVisible?: boolean;
234
+ /** Slides to play in this phase ({@link StubCtx.slides}); empty when the stub set none. */
235
+ slides: WaygraphSlide[];
229
236
  todoPos?: "left" | "right";
230
237
  /** When true, keep other docks (multi-todo). Default false = replace. */
231
238
  todoParallel?: boolean;
@@ -260,6 +267,7 @@ export type StubBagState = {
260
267
  bannerUi?: BannerUiOpts | undefined;
261
268
  caption?: string | undefined;
262
269
  networkTabVisible?: boolean | undefined;
270
+ slides?: readonly WaygraphSlide[] | undefined;
263
271
  todoPos?: "left" | "right" | undefined;
264
272
  /** Author patched todo-dock UX this phase. */
265
273
  todoDockUi?: TodoDockUiOpts | undefined;
package/dist/index.d.ts CHANGED
@@ -11,7 +11,7 @@ export { css, byText, byRole } from "./engine/trait/selector.js";
11
11
  export { WaygraphError, WAYGRAPH_ERRORS } from "./engine/errors.js";
12
12
  export type { WaygraphErrorCode } from "./engine/errors.js";
13
13
  export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText } from "./engine/trait/trait.js";
14
- export { runGraph, spawnTab, narrate, preflight, defineLayout } from "./engine/run/run.js";
14
+ export { runGraph, spawnTab, preflight, defineLayout } from "./engine/run/run.js";
15
15
  export { Engine, map } from "./engine/engine.js";
16
16
  export { branch } from "./engine/block/branch.js";
17
17
  export { withVerify, modVerify, modVerifyAll, defineBlock } from "./engine/block/block.js";
@@ -29,7 +29,7 @@ export type { BranchEntry, BranchRunResult, RunBranchRegressionOptions } from ".
29
29
  export type { ApiBlockOptions, ApiCallResult } from "./engine/block/kinds/api.js";
30
30
  export type { MapStepOpts } from "./engine/flow/map-steps.js";
31
31
  export type { MapFlowOpts } from "./engine/flow/map-builder.js";
32
- export { resolveHighlightSlots, resolveSlides, runStubPhase, hasAuthoredStubAfter, hasAuthoredStubOnError } from "./demo/narration/narration.js";
32
+ export { resolveHighlightSlots, runStubPhase, hasAuthoredStubAfter, hasAuthoredStubOnError } from "./demo/narration/narration.js";
33
33
  export { resolveFixtureDwellMs, normalizeDemoPace, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT } from "./demo/narration/demo-pace.js";
34
34
  export { formatHighlightCaption, applyHighlightStyleDefaults, toneIconPrefix } from "./demo/narration/style.js";
35
35
  export { normalizeHighlightTone, normalizeHighlightSize, normalizeHighlightWeight } from "./engine/stub/ring.js";
@@ -79,7 +79,6 @@ export { resolveExtension, KNOWN_EXTENSIONS } from "./runtime/extensions.js";
79
79
  export type { ExtensionSpec } from "./engine/config.js";
80
80
  export type { WebServerConfig } from "./runtime/webserver.js";
81
81
  export type { MapStart } from "./engine/engine.js";
82
- export type { WaygraphSlidesOrFn } from "./engine/stub/ring.js";
83
82
  export type { ConnectLayout } from "./engine/run/connect.js";
84
83
  export type { EffectExtras } from "./engine/block/kinds/effect.js";
85
84
  export type { BlockBase, RoutedBlockBase } from "./engine/checkpoint/route.js";
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ export { registerMemStub, seedMemStub, getMemStub } from "./engine/mem/mem-stub.
9
9
  export { css, byText, byRole } from "./engine/trait/selector.js";
10
10
  export { WaygraphError, WAYGRAPH_ERRORS } from "./engine/errors.js";
11
11
  export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText } from "./engine/trait/trait.js";
12
- export { runGraph, spawnTab, narrate, preflight, defineLayout } from "./engine/run/run.js";
12
+ export { runGraph, spawnTab, preflight, defineLayout } from "./engine/run/run.js";
13
13
  export { Engine, map } from "./engine/engine.js";
14
14
  export { branch } from "./engine/block/branch.js";
15
15
  export { withVerify, modVerify, modVerifyAll, defineBlock } from "./engine/block/block.js";
@@ -23,7 +23,7 @@ export { locate } from "./engine/locate.js";
23
23
  export { composeBlock, fastForwardComposeBlock, isFastForwardBlock } from "./engine/block/compose.js";
24
24
  export { MapBuilder } from "./engine/flow/map-builder.js";
25
25
  export { branchRoutes, collectBranchFlows, runBranchRegression } from "./engine/flow/branch-regression.js";
26
- export { resolveHighlightSlots, resolveSlides, runStubPhase, hasAuthoredStubAfter, hasAuthoredStubOnError } from "./demo/narration/narration.js";
26
+ export { resolveHighlightSlots, runStubPhase, hasAuthoredStubAfter, hasAuthoredStubOnError } from "./demo/narration/narration.js";
27
27
  export { resolveFixtureDwellMs, normalizeDemoPace, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT } from "./demo/narration/demo-pace.js";
28
28
  export { formatHighlightCaption, applyHighlightStyleDefaults, toneIconPrefix } from "./demo/narration/style.js";
29
29
  export { normalizeHighlightTone, normalizeHighlightSize, normalizeHighlightWeight } from "./engine/stub/ring.js";
@@ -32,11 +32,11 @@ export async function findBlock(projectDir, ref) {
32
32
  if (byRuntimeName.length === 1)
33
33
  return byRuntimeName[0];
34
34
  if (byRuntimeName.length > 1) {
35
- throw new Error("waygraph chain: \"" + ref + "\" matches " + byRuntimeName.length + " Blocks by name - " +
35
+ throw new Error("waygraph run --blocks: \"" + ref + "\" matches " + byRuntimeName.length + " Blocks by name - " +
36
36
  byRuntimeName.map((b) => b.exportName + " (" + b.file + ")").join(", ") +
37
37
  ". Use the export name to disambiguate.");
38
38
  }
39
- throw new Error("waygraph chain: no Block named \"" + ref + "\" found under " + projectDir);
39
+ throw new Error("waygraph run --blocks: no Block named \"" + ref + "\" found under " + projectDir);
40
40
  }
41
41
  /**
42
42
  * Finds an already-defined Flow by its export name (e.g. loginFlow) among
@@ -24,7 +24,7 @@ async function findBareFlow(projectDir, spec) {
24
24
  function parseSegments(spec) {
25
25
  const segments = parseChainSpec(spec);
26
26
  if (segments.length === 0)
27
- throw new Error("waygraph chain: empty spec - give at least one block name");
27
+ throw new Error("waygraph run --blocks: empty spec - give at least one block name");
28
28
  return segments;
29
29
  }
30
30
  async function resolveSegment(projectDir, seg, mem, options) {
@@ -4,7 +4,7 @@ const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v)
4
4
  function seedMemFromRequires(mem, requires, json, label, stubActive) {
5
5
  if (requires.length === 0) {
6
6
  if (json !== undefined) {
7
- throw new Error("waygraph chain: \"" + label + "\" takes no input (empty requires) but got a payload: " + json);
7
+ throw new Error("waygraph run --blocks: \"" + label + "\" takes no input (empty requires) but got a payload: " + json);
8
8
  }
9
9
  return;
10
10
  }
@@ -22,7 +22,7 @@ function seedMemFromRequires(mem, requires, json, label, stubActive) {
22
22
  haveParsed = true;
23
23
  }
24
24
  catch (err) {
25
- throw new Error("waygraph chain: \"" + label + "\" payload is not valid JSON - " + String(err));
25
+ throw new Error("waygraph run --blocks: \"" + label + "\" payload is not valid JSON - " + String(err));
26
26
  }
27
27
  }
28
28
  if (!haveParsed) {
@@ -30,7 +30,7 @@ function seedMemFromRequires(mem, requires, json, label, stubActive) {
30
30
  // this flow; when it is, fall through with an empty object so the
31
31
  // per-key loop below gets a chance to fill each one from the registry.
32
32
  if (!stubActive) {
33
- throw new Error("waygraph chain: \"" + label + "\" requires " + requires.map((k) => k.name).join(", ") +
33
+ throw new Error("waygraph run --blocks: \"" + label + "\" requires " + requires.map((k) => k.name).join(", ") +
34
34
  " - give a JSON payload (inline blockName({...}) or --data '{...}'), or enable --mem-stub" +
35
35
  " with registerMemStub for these keys");
36
36
  }
@@ -54,7 +54,7 @@ function seedMemFromRequires(mem, requires, json, label, stubActive) {
54
54
  return;
55
55
  }
56
56
  if (!isRecord(parsed)) {
57
- throw new Error("waygraph chain: \"" + label + "\" requires " + requires.length + " keys (" +
57
+ throw new Error("waygraph run --blocks: \"" + label + "\" requires " + requires.length + " keys (" +
58
58
  requires.map((k) => k.name).join(", ") + ") - payload must be an object keyed by each key's name");
59
59
  }
60
60
  const payload = parsed;
@@ -72,7 +72,7 @@ function seedMemFromRequires(mem, requires, json, label, stubActive) {
72
72
  stillMissing.push(k);
73
73
  }
74
74
  if (stillMissing.length > 0) {
75
- throw new Error("waygraph chain: \"" + label + "\" payload is missing required key(s): " +
75
+ throw new Error("waygraph run --blocks: \"" + label + "\" payload is missing required key(s): " +
76
76
  stillMissing.map((k) => "\"" + k.name + "\"").join(", "));
77
77
  }
78
78
  }
@@ -8,7 +8,7 @@ export function parseChainSpec(spec) {
8
8
  .map((s) => {
9
9
  const m = /^([A-Za-z_$][\w-]*)\s*(?:\(([\s\S]*)\))?$/.exec(s);
10
10
  if (!m) {
11
- throw new Error("waygraph chain: could not parse segment \"" + s + "\" - expected \"blockName\" or \"blockName({...json...})\"");
11
+ throw new Error("waygraph run --blocks: could not parse segment \"" + s + "\" - expected \"blockName\" or \"blockName({...json...})\"");
12
12
  }
13
13
  const ref = m[1];
14
14
  const json = m[2] ? m[2].trim() : undefined;
@@ -1,4 +1,4 @@
1
- import { defineMethodBlock, Trait, narrate } from "waygraph";
1
+ import { defineMethodBlock, Trait } from "waygraph";
2
2
  import {
3
3
  CheckoutOverviewPage,
4
4
  OrderComplete,
@@ -21,9 +21,7 @@ export const FinishOrderBlock = defineMethodBlock({
21
21
  instruction: {
22
22
  async act(page) {
23
23
  const finishButton = page.locator(CheckoutOverviewSel.finishBtn);
24
- await narrate(finishButton, "Placing the order", () =>
25
- finishButton.click(),
26
- );
24
+ await finishButton.click();
27
25
  },
28
26
  verify: [
29
27
  Trait.text(
@@ -31,35 +29,5 @@ export const FinishOrderBlock = defineMethodBlock({
31
29
  "Thank you for your order!",
32
30
  ),
33
31
  ],
34
- // Shown AFTER act (order already placed) - selectors must match the
35
- // completion page, not #finish on the overview.
36
- slides: [
37
- {
38
- caption: "Checkout is a short pipeline",
39
- detail: "Cart -> info -> overview -> done. You just placed the order.",
40
- tag: "YAP",
41
- tone: "info",
42
- duration: true,
43
- },
44
- {
45
- caption: "Completion page is the durable proof",
46
- detail:
47
- "tone: success - the thank-you header is what verify asserts on.",
48
- tag: "YAP",
49
- tone: "success",
50
- selector: CheckoutOverviewSel.completeHeader,
51
- duration: 2500,
52
- fastMode: 700,
53
- },
54
- {
55
- caption: "Episode continues after this yap",
56
- detail: "Next step rings the same banner, then you move on.",
57
- tag: "YAP",
58
- tone: "planned",
59
- selector: CheckoutOverviewSel.completeHeader,
60
- duration: true,
61
- fastMode: 500,
62
- },
63
- ],
64
32
  },
65
33
  });
@@ -12,6 +12,7 @@ import { FillLastNameBlock } from "../blocks/saucedemo-web/checkout-step-one/met
12
12
  import { FillPostalCodeBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/fill-postal-code.method.block.js";
13
13
  import { SubmitCheckoutInfoBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.js";
14
14
  import { FinishOrderBlock } from "../blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.js";
15
+ import { orderPlacedYap, placingOrder } from "../stubs/finish-order.js";
15
16
  import { NavBackToProductsBlock } from "../blocks/saucedemo-web/inventory-item/nav-back-to-products.block.js";
16
17
 
17
18
  /** Full checkout, then the "Back Home" click-based Nav Block returns to the catalogue. */
@@ -35,6 +36,6 @@ export const checkoutThenBrowseAgainFlow = engine
35
36
  .method(FillLastNameBlock)
36
37
  .method(FillPostalCodeBlock)
37
38
  .method(SubmitCheckoutInfoBlock)
38
- .method(FinishOrderBlock)
39
+ .method(FinishOrderBlock.stubBefore(placingOrder).stubAfter(orderPlacedYap))
39
40
  .gotoPage(NavBackToProductsBlock)
40
41
  .end();
@@ -12,6 +12,7 @@ import { FillLastNameBlock } from "../blocks/saucedemo-web/checkout-step-one/met
12
12
  import { FillPostalCodeBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/fill-postal-code.method.block.js";
13
13
  import { SubmitCheckoutInfoBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.js";
14
14
  import { FinishOrderBlock } from "../blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.js";
15
+ import { orderPlacedYap, placingOrder } from "../stubs/finish-order.js";
15
16
  import { LoginSel } from "../blocks/saucedemo-web/methods/login.sel.js";
16
17
 
17
18
  export const checkoutFlow = engine
@@ -80,5 +81,5 @@ export const checkoutFlow = engine
80
81
  }),
81
82
  )
82
83
  .method(SubmitCheckoutInfoBlock.stubBefore((ctx) => ctx.hideCaption()))
83
- .method(FinishOrderBlock)
84
+ .method(FinishOrderBlock.stubBefore(placingOrder).stubAfter(orderPlacedYap))
84
85
  .end();
@@ -8,6 +8,7 @@ import { FillLastNameBlock } from "../blocks/saucedemo-web/checkout-step-one/met
8
8
  import { FillPostalCodeBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/fill-postal-code.method.block.js";
9
9
  import { SubmitCheckoutInfoBlock } from "../blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.js";
10
10
  import { FinishOrderBlock } from "../blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.js";
11
+ import { orderPlacedYap, placingOrder } from "../stubs/finish-order.js";
11
12
 
12
13
  /** Add the selected item, check out and place the order. Starts signed in (run it after loginFlow). */
13
14
  export const shopFlow = engine
@@ -20,5 +21,5 @@ export const shopFlow = engine
20
21
  .method(FillLastNameBlock)
21
22
  .method(FillPostalCodeBlock)
22
23
  .method(SubmitCheckoutInfoBlock)
23
- .method(FinishOrderBlock)
24
+ .method(FinishOrderBlock.stubBefore(placingOrder).stubAfter(orderPlacedYap))
24
25
  .end();