waygraph 0.15.52 → 0.15.54

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 (156) hide show
  1. package/README.md +8 -1
  2. package/dist/analysis/graph.d.ts +3 -0
  3. package/dist/analysis/map-check.d.ts +3 -2
  4. package/dist/auto-session/helpers.d.ts +10 -0
  5. package/dist/blocks-select.d.ts +3 -0
  6. package/dist/blocks-select.js +2 -0
  7. package/dist/cli/flags.js +3 -1
  8. package/dist/cli/usage.js +4 -1
  9. package/dist/cli.js +4 -1
  10. package/dist/commands/agent-dive-scaffold.d.ts +0 -20
  11. package/dist/commands/agent-dive-scaffold.js +3 -3
  12. package/dist/commands/init.js +1 -1
  13. package/dist/commands/upgrade.d.ts +1 -1
  14. package/dist/commands/upgrade.js +7 -1
  15. package/dist/config.d.ts +55 -19
  16. package/dist/config.js +55 -13
  17. package/dist/core/block.d.ts +7 -2
  18. package/dist/core/block.js +7 -3
  19. package/dist/core/blocks/api.d.ts +1 -0
  20. package/dist/core/blocks/method.d.ts +2 -0
  21. package/dist/core/blocks/method.js +1 -0
  22. package/dist/core/blocks/nav.d.ts +1 -0
  23. package/dist/core/blocks/page.d.ts +1 -0
  24. package/dist/core/branch-regression.d.ts +1 -0
  25. package/dist/core/compose.d.ts +1 -0
  26. package/dist/core/engine.d.ts +2 -0
  27. package/dist/core/engine.js +3 -1
  28. package/dist/core/flow.d.ts +11 -0
  29. package/dist/core/flow.js +32 -8
  30. package/dist/core/map-builder.d.ts +34 -1
  31. package/dist/core/map-builder.js +42 -4
  32. package/dist/core/run-graph.d.ts +2 -0
  33. package/dist/core/run-graph.js +1 -0
  34. package/dist/errors.d.ts +1 -0
  35. package/dist/extensions.d.ts +1 -0
  36. package/dist/extensions.js +1 -0
  37. package/dist/highlights/demo-pace.d.ts +4 -1
  38. package/dist/highlights/demo-pace.js +28 -3
  39. package/dist/highlights/device.d.ts +2 -0
  40. package/dist/highlights/device.js +2 -0
  41. package/dist/highlights/types.d.ts +6 -0
  42. package/dist/index.d.ts +2 -2
  43. package/dist/pilot-overlay/action-ring.d.ts +0 -9
  44. package/dist/pilot-overlay/action-ring.js +1 -1
  45. package/dist/pilot-overlay/panel.d.ts +2 -1
  46. package/dist/runner/interaction-patch.js +32 -1
  47. package/dist/runner/main.js +5 -2
  48. package/dist/runner/step-mode.js +10 -4
  49. package/dist/session/auto-explore-run.js +6 -0
  50. package/dist/session/auto-explore.d.ts +2 -1
  51. package/dist/session/auto-session-ipc.d.ts +1 -2
  52. package/dist/session/auto-session-ipc.js +1 -1
  53. package/dist/session/block-inject.d.ts +0 -13
  54. package/dist/session/block-inject.js +3 -3
  55. package/dist/session/overlay-beacon.d.ts +7 -0
  56. package/dist/session/overlay-beacon.js +5 -0
  57. package/dist/session/pilot.d.ts +1 -2
  58. package/dist/session/pilot.js +1 -2
  59. package/dist/traverse/coverage.d.ts +4 -4
  60. package/dist/traverse/coverage.js +4 -1
  61. package/dist/traverse/lease.d.ts +1 -0
  62. package/dist/traverse/lease.js +1 -0
  63. package/dist/traverse/run.d.ts +3 -0
  64. package/dist/types.d.ts +1 -1
  65. package/examples/routed-demo/src/flows/routed-demo-map.flow.ts +1 -1
  66. package/examples/routed-demo/src/flows/routed-demo.flow.ts +2 -2
  67. package/examples/saucedemo/README.md +11 -12
  68. package/examples/saucedemo/config/waygraph.config.ts +17 -0
  69. package/examples/saucedemo/docs/CONVENTION.md +1 -2
  70. package/examples/saucedemo/docs/HELPERS.md +1 -1
  71. package/examples/saucedemo/package-lock.json +10 -3
  72. package/examples/saucedemo/package.json +12 -29
  73. package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-cart.block.ts +0 -49
  74. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +22 -25
  75. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/inventory.page.block.ts +0 -6
  76. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +38 -96
  77. package/examples/saucedemo/src/blocks/saucedemo-web/methods/fill-password.method.block.ts +1 -26
  78. package/examples/saucedemo/src/blocks/saucedemo-web/methods/fill-username.method.block.ts +1 -45
  79. package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +14 -77
  80. package/examples/saucedemo/src/flows/api-demo.flow.ts +17 -11
  81. package/examples/saucedemo/src/flows/cart-bulk.flow.ts +19 -14
  82. package/examples/saucedemo/src/flows/cart-continue.flow.ts +10 -8
  83. package/examples/saucedemo/src/flows/cart-mem.flow.ts +7 -7
  84. package/examples/saucedemo/src/flows/catalog-logout.flow.ts +13 -8
  85. package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +26 -48
  86. package/examples/saucedemo/src/flows/checkout.flow.ts +70 -86
  87. package/examples/saucedemo/src/flows/device-showcase.flow.ts +24 -25
  88. package/examples/saucedemo/src/flows/login.flow.ts +11 -18
  89. package/examples/saucedemo/src/flows/mem-stub-demo.flow.ts +9 -20
  90. package/examples/saucedemo/src/flows/shop.flow.ts +15 -20
  91. package/examples/saucedemo/src/flows/smoke.flow.ts +12 -27
  92. package/examples/saucedemo/src/flows/stub-on-error.flow.ts +24 -43
  93. package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +26 -34
  94. package/examples/saucedemo/src/states/checkout.mem-stubs.ts +11 -0
  95. package/examples/saucedemo/src/stubs/device-showcase.ts +193 -0
  96. package/examples/saucedemo/src/stubs/login.ts +37 -0
  97. package/examples/saucedemo/tests/chain-flow-episodes.spec.ts +7 -9
  98. package/examples/saucedemo/tests/submit-login-branch.spec.ts +18 -5
  99. package/package.json +12 -5
  100. package/templates/quickstart/README.md +4 -7
  101. package/templates/quickstart/config/waygraph.config.ts +17 -0
  102. package/templates/quickstart/docs/CONVENTION.md +1 -2
  103. package/templates/quickstart/docs/HELPERS.md +1 -1
  104. package/templates/quickstart/package.json +14 -14
  105. package/templates/quickstart/src/blocks/SITE-MAP.md +14 -3
  106. package/templates/quickstart/src/blocks/saucedemo-web/cart/nav-cart.block.ts +0 -49
  107. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +22 -25
  108. package/templates/quickstart/src/blocks/saucedemo-web/inventory/inventory.page.block.ts +0 -6
  109. package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +38 -96
  110. package/templates/quickstart/src/blocks/saucedemo-web/methods/fill-password.method.block.ts +1 -26
  111. package/templates/quickstart/src/blocks/saucedemo-web/methods/fill-username.method.block.ts +1 -45
  112. package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +14 -77
  113. package/templates/quickstart/src/flows/api-demo.flow.ts +43 -0
  114. package/templates/quickstart/src/flows/cart-bulk.flow.ts +19 -14
  115. package/templates/quickstart/src/flows/cart-continue.flow.ts +10 -8
  116. package/templates/quickstart/src/flows/cart-mem.flow.ts +7 -7
  117. package/templates/quickstart/src/flows/catalog-logout.flow.ts +13 -8
  118. package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +26 -48
  119. package/templates/quickstart/src/flows/checkout.flow.ts +70 -63
  120. package/templates/quickstart/src/flows/device-showcase.flow.ts +24 -25
  121. package/templates/quickstart/src/flows/login.flow.ts +11 -18
  122. package/templates/quickstart/src/flows/mem-stub-demo.flow.ts +12 -0
  123. package/templates/quickstart/src/flows/shop.flow.ts +15 -20
  124. package/templates/quickstart/src/flows/smoke.flow.ts +12 -27
  125. package/templates/quickstart/src/flows/stub-on-error.flow.ts +31 -0
  126. package/templates/quickstart/src/flows/viewer-blocked.flow.ts +26 -34
  127. package/templates/quickstart/src/states/checkout.mem-stubs.ts +11 -0
  128. package/templates/quickstart/src/stubs/device-showcase.ts +193 -0
  129. package/templates/quickstart/src/stubs/login.ts +37 -0
  130. package/templates/quickstart/tests/chain-flow.spec.ts +7 -10
  131. package/templates/scaffold/README.md +1 -1
  132. package/templates/scaffold/STRUCTURE.md +5 -2
  133. package/templates/scaffold/config/waygraph.config.ts +24 -5
  134. package/templates/scaffold/package.json +6 -6
  135. package/templates/scaffold/src/flows/(external)/hoppscotch/auth.flow.ts +36 -0
  136. package/templates/scaffold/src/flows/(external)/hoppscotch/get.flow.ts +24 -0
  137. package/templates/scaffold/src/flows/(external)/hoppscotch/post.flow.ts +34 -0
  138. package/templates/scaffold/src/flows/(user-stories)/email-verification.flow.ts +47 -0
  139. package/templates/scaffold/src/flows/README.md +35 -14
  140. package/templates/scaffold/src/flows/{shop.flow.ts → home/cart.flow.ts} +18 -17
  141. package/templates/scaffold/src/flows/{example.flow.ts → home/hello.flow.ts} +12 -13
  142. package/templates/scaffold/tests/example.spec.ts +5 -5
  143. package/templates/scaffold/tests/hoppscotch.spec.ts +3 -3
  144. package/templates/scaffold/tests/mail-verify.spec.ts +7 -7
  145. package/templates/skills/waygraph-convention.skill.md +14 -6
  146. package/examples/saucedemo/playwright.config.ts +0 -19
  147. package/examples/saucedemo/scripts/drive-auto-cli.mjs +0 -93
  148. package/examples/saucedemo/scripts/prove-add-to-cart-menu.mjs +0 -126
  149. package/examples/saucedemo/scripts/prove-auto-graph.mjs +0 -92
  150. package/examples/saucedemo/scripts/prove-wrong-login-runtime.mjs +0 -63
  151. package/templates/quickstart/playwright.config.ts +0 -14
  152. package/templates/quickstart/scripts/drive-auto-cli.mjs +0 -93
  153. package/templates/scaffold/src/flows/hoppscotch-auth.flow.ts +0 -35
  154. package/templates/scaffold/src/flows/hoppscotch-get.flow.ts +0 -23
  155. package/templates/scaffold/src/flows/hoppscotch-post.flow.ts +0 -33
  156. package/templates/scaffold/src/flows/mail-verify.flow.ts +0 -46
@@ -17,6 +17,7 @@ export declare function branchRoutes(flow: Flow<any>): ReadonlyMap<string, Flow<
17
17
  * `.branch()` continuations. Static - doesn't run anything.
18
18
  */
19
19
  export declare function collectBranchFlows(flow: Flow<any>): BranchEntry[];
20
+ /** The outcome of one branch path in a branch regression run. */
20
21
  export interface BranchRunResult {
21
22
  path: string;
22
23
  status: "ok" | "failed";
@@ -67,6 +67,7 @@ export interface FastForwardComposedBlock<In extends Checkpoint<string>, Out ext
67
67
  }
68
68
  /** True when `block` came from {@link fastForwardComposeBlock}. */
69
69
  export declare function isFastForwardBlock(block: Block<any, any>): block is FastForwardComposedBlock<any, any>;
70
+ /** Like `composeBlock`, but the steps run as one blitz-paced unit in a demo. */
70
71
  export declare function fastForwardComposeBlock<In extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, Out>]): FastForwardComposedBlock<In, Out>;
71
72
  export declare function fastForwardComposeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, Out>]): FastForwardComposedBlock<In, Out>;
72
73
  export declare function fastForwardComposeBlock<In extends Checkpoint<string>, B extends Checkpoint<string>, C extends Checkpoint<string>, Out extends Checkpoint<string>>(name: string, steps: readonly [Block<In, B>, Block<B, C>, Block<C, Out>]): FastForwardComposedBlock<In, Out>;
@@ -16,6 +16,8 @@ export declare class Engine {
16
16
  private readonly config;
17
17
  constructor(config?: EngineConfig);
18
18
  /**
19
+ * @deprecated Build flows with `engine.map()`, which checks each step's Block kind and nests flows with `.flow()`.
20
+ *
19
21
  * Builds a runnable {@link Flow} from `[start, ...Blocks, end]`, typechecked
20
22
  * so each Block's `In` must match the previous Block's `Out` - the same
21
23
  * check `connect()` does, just declared as a flat array instead of hand-nested
@@ -36,7 +36,9 @@ export class Engine {
36
36
  * .end();
37
37
  */
38
38
  map(options) {
39
- return MapBuilder.begin(this.defineFlow.bind(this), options?.homeOrigin);
39
+ const { homeOrigin, ...flowOptions } = options ?? {};
40
+ const define = this.defineFlow.bind(this);
41
+ return MapBuilder.begin((blocks, extra) => define(blocks, { ...flowOptions, ...extra }), homeOrigin);
40
42
  }
41
43
  }
42
44
  /**
@@ -66,6 +66,7 @@ export interface BlockInfo {
66
66
  */
67
67
  block: Block<any, any>;
68
68
  }
69
+ /** A runnable chain of Blocks built by `engine.map()`. `run(mem)` launches its own browser; `run(context, mem)` reuses one. */
69
70
  export interface Flow<Out extends Checkpoint<string>> {
70
71
  /**
71
72
  * True once this Flow has been wrapped in {@link withSessionReset} - a
@@ -176,6 +177,7 @@ export declare function buildFlow<Out extends Checkpoint<string>>(middle: readon
176
177
  * exactly as if a human had opened the app fresh, not wherever the
177
178
  * previous Flow in the chain happened to leave the page.
178
179
  * @example const loginFlow = withSessionReset(engine.defineFlow([start, LoginBlock, end]));
180
+ * @deprecated Use `map().flow(sub, { reset: true })`; the reset belongs to where flows are joined.
179
181
  */
180
182
  export declare function withSessionReset<Out extends Checkpoint<string>>(flow: Flow<Out>): Flow<Out>;
181
183
  /**
@@ -184,6 +186,7 @@ export declare function withSessionReset<Out extends Checkpoint<string>>(flow: F
184
186
  * Blocks are executing, instead of one fixed title for the whole run.
185
187
  * Non-destructive, same pattern as {@link withSessionReset}.
186
188
  * @example const ticket123 = withTitle(loginFlow, "Ticket #123 - Sales rep signs in");
189
+ * @deprecated Name a flow with `ctx.title(...)` in a stub on its first step.
187
190
  */
188
191
  export declare function withTitle<Out extends Checkpoint<string>>(flow: Flow<Out>, title: string): Flow<Out>;
189
192
  /**
@@ -195,6 +198,7 @@ export declare function withTitle<Out extends Checkpoint<string>>(flow: Flow<Out
195
198
  * this itself - the CLI's `--data`/`--mem-stub` seeding, or a bare-library
196
199
  * caller's own explicit `seedMemStub(mem, flow)` call, does the filling.
197
200
  * @example const demoLogin = withMemStub(loginFlow);
201
+ * @deprecated Use `engine.map({ memStub: true })`.
198
202
  */
199
203
  export declare function withMemStub<Out extends Checkpoint<string>>(flow: Flow<Out>): Flow<Out>;
200
204
  /**
@@ -205,17 +209,20 @@ export declare function withMemStub<Out extends Checkpoint<string>>(flow: Flow<O
205
209
  * @example withDemoPace(happyFlow, 0.5) // 2x faster than normal
206
210
  * @example withDemoPace(gapFlow, 2.5) // 2.5x slower
207
211
  * @example withDemoPace(gapFlow, 4500) // absolute ~4.5s gates/dwells
212
+ * @deprecated Use `engine.map({ demoPace })`, a step `{ pace }`, or `demo.pace` in the config.
208
213
  */
209
214
  export declare function withDemoPace<Out extends Checkpoint<string>>(flow: Flow<Out>, pace: DemoPace): Flow<Out>;
210
215
  /**
211
216
  * Default highlight look for a Flow episode (size / weight / optional tone).
212
217
  * Per-slot stub or {@link withHighlightFixtures} values win when set.
213
218
  * @example withHighlightStyle(withDemoPace(gapFlow, "slow"), { size: "lg", weight: "bold" })
219
+ * @deprecated Use `engine.map({ highlightStyle })`.
214
220
  */
215
221
  export declare function withHighlightStyle<Out extends Checkpoint<string>>(flow: Flow<Out>, style: HighlightStyleDefaults): Flow<Out>;
216
222
  /**
217
223
  * Demo pacing for one Block or compose unit (not FF - use fastForwardComposeBlock for blitz).
218
224
  * @example withBlockPace(composeBlock("gap-review", [...]), "slow")
225
+ * @deprecated Use a step option: `.method(Block, { pace })`.
219
226
  */
220
227
  export declare function withBlockPace<In extends Checkpoint<string>, Out extends Checkpoint<string>>(block: Block<In, Out>, pace: DemoPace): Block<In, Out>;
221
228
  /**
@@ -235,6 +242,7 @@ export declare function withBlockPace<In extends Checkpoint<string>, Out extends
235
242
  * engine.defineFlow([start, NavLoginBlock, SubmitLoginActionBlock, end]),
236
243
  * "locked_out_user stays on LoginPage with the error banner - working as intended.",
237
244
  * );
245
+ * @deprecated Use `engine.map({ expectedFailure: reason })`.
238
246
  */
239
247
  export declare function withExpectedFailure<Out extends Checkpoint<string>>(flow: Flow<Out>, reason: string): Flow<Out>;
240
248
  /**
@@ -243,6 +251,7 @@ export declare function withExpectedFailure<Out extends Checkpoint<string>>(flow
243
251
  * stubOnError slots. Non-destructive, same pattern as {@link withTitle}.
244
252
  * Demo/`chain --step` reads `flow.highlightFixtures`; `run()` ignores it.
245
253
  * @example withHighlightFixtures(issueFlow, { "submit-login": { stubBefore: { email: { label: "AC-1" } } } })
254
+ * @deprecated Chain ctx stubs on the Block: `Block.stubAfter((ctx) => …)`.
246
255
  */
247
256
  export declare function withHighlightFixtures<Out extends Checkpoint<string>>(flow: Flow<Out>, fixtures: import("../highlights.js").HighlightFixtureMap): Flow<Out>;
248
257
  /** Everything a `with*` wrapper can set, given up front: `engine.defineFlow([...], { title, memStub, ... })`. */
@@ -262,6 +271,7 @@ export interface FlowOptions {
262
271
  }
263
272
  /** Applies each set option through its `with*` wrapper, so both spellings produce the same Flow. */
264
273
  export declare function applyFlowOptions<Out extends Checkpoint<string>>(flow: Flow<Out>, options: FlowOptions | undefined): Flow<Out>;
274
+ export declare function clearSessionState(context: BrowserContext, page: Page): Promise<void>;
265
275
  /**
266
276
  * Runs several complete, independent Flows one after another, reusing the
267
277
  * same page/context/mem in order - each remains its own validated
@@ -279,5 +289,6 @@ export declare function applyFlowOptions<Out extends Checkpoint<string>>(flow: F
279
289
  * chain - nothing to reset yet). The combined result is whatever the LAST
280
290
  * flow resolved to.
281
291
  * @example chainFlow(ticket123Flow, ticket456Flow).run(context, mem)
292
+ * @deprecated Nest flows with `map().flow(a).flow(b, { reset: true })`.
282
293
  */
283
294
  export declare function chainFlow(...flows: readonly Flow<any>[]): Flow<any>;
package/dist/core/flow.js CHANGED
@@ -2,14 +2,25 @@
2
2
  import { connect } from "../types.js";
3
3
  import { chromiumExecutablePath, extensionPathsFromEnv, launchContext } from "../browser.js";
4
4
  import { resolveExtensions } from "../extensions.js";
5
+ import { loadWaygraphConfig } from "../config.js";
5
6
  import { WaygraphError, MSG_CHAINFLOW_BLOCK_NOT_FOUND_BY_INDEX, MSG_CHAINFLOW_BLOCK_NOT_FOUND_BY_NAME, MSG_CHAINFLOW_EMPTY, } from "../errors.js";
6
7
  import { preflight, runGraph } from "./run-graph.js";
7
8
  import { resolveLauncher } from "./config.js";
8
9
  import { MemPage } from "../mem-page.js";
9
- import { findBlockIndex, modVerify, withVerify } from "./block.js";
10
+ import { copyWaygraphRuntime, findBlockIndex, modVerify, withVerify } from "./block.js";
10
11
  import { normalizeHighlightSize, normalizeHighlightTone, normalizeHighlightWeight } from "../highlights.js";
11
12
  /** Launches the browser and context an Engine uses when the caller gave none, from its config. */
12
- async function openOwnContext(config) {
13
+ async function openOwnContext(engineConfig) {
14
+ // A config chosen with `--config` / WAYGRAPH_CONFIG wins over the one the Engine was built with, so a flow that imports
15
+ // an engine from one config file still follows the config the run selected.
16
+ const chosen = process.env.WAYGRAPH_CONFIG ? await loadWaygraphConfig(process.cwd()) : undefined;
17
+ const config = chosen
18
+ ? {
19
+ ...engineConfig,
20
+ ...(chosen.baseUrl !== undefined ? { baseUrl: chosen.baseUrl } : {}),
21
+ ...(chosen.extensions !== undefined ? { extensions: chosen.extensions } : {}),
22
+ }
23
+ : engineConfig;
13
24
  const browserName = config?.browserName ?? "chromium";
14
25
  const executablePath = browserName === "chromium" ? chromiumExecutablePath() : undefined;
15
26
  const baseURL = config?.baseUrl ?? process.env.WAYGRAPH_BASE_URL;
@@ -109,7 +120,12 @@ export function buildFlow(middle, engineConfig) {
109
120
  };
110
121
  return {
111
122
  resetSession: false,
112
- blocks: () => middle.map((b) => ({ name: b.name, block: b, ...(b.routes ? { routes: b.routes } : {}) })),
123
+ blocks: () => middle.map((b) => ({
124
+ name: b.name,
125
+ block: b,
126
+ ...(b.routes ? { routes: b.routes } : {}),
127
+ ...(b.__waygraphResetBefore ? { resetSessionBefore: true } : {}),
128
+ })),
113
129
  run: run,
114
130
  withBlockVerify(block, verify) {
115
131
  const index = findBlockIndex(middle, block);
@@ -139,6 +155,7 @@ export function buildFlow(middle, engineConfig) {
139
155
  * exactly as if a human had opened the app fresh, not wherever the
140
156
  * previous Flow in the chain happened to leave the page.
141
157
  * @example const loginFlow = withSessionReset(engine.defineFlow([start, LoginBlock, end]));
158
+ * @deprecated Use `map().flow(sub, { reset: true })`; the reset belongs to where flows are joined.
142
159
  */
143
160
  export function withSessionReset(flow) {
144
161
  return {
@@ -154,6 +171,7 @@ export function withSessionReset(flow) {
154
171
  * Blocks are executing, instead of one fixed title for the whole run.
155
172
  * Non-destructive, same pattern as {@link withSessionReset}.
156
173
  * @example const ticket123 = withTitle(loginFlow, "Ticket #123 - Sales rep signs in");
174
+ * @deprecated Name a flow with `ctx.title(...)` in a stub on its first step.
157
175
  */
158
176
  export function withTitle(flow, title) {
159
177
  return {
@@ -172,6 +190,7 @@ export function withTitle(flow, title) {
172
190
  * this itself - the CLI's `--data`/`--mem-stub` seeding, or a bare-library
173
191
  * caller's own explicit `seedMemStub(mem, flow)` call, does the filling.
174
192
  * @example const demoLogin = withMemStub(loginFlow);
193
+ * @deprecated Use `engine.map({ memStub: true })`.
175
194
  */
176
195
  export function withMemStub(flow) {
177
196
  return {
@@ -189,6 +208,7 @@ export function withMemStub(flow) {
189
208
  * @example withDemoPace(happyFlow, 0.5) // 2x faster than normal
190
209
  * @example withDemoPace(gapFlow, 2.5) // 2.5x slower
191
210
  * @example withDemoPace(gapFlow, 4500) // absolute ~4.5s gates/dwells
211
+ * @deprecated Use `engine.map({ demoPace })`, a step `{ pace }`, or `demo.pace` in the config.
192
212
  */
193
213
  export function withDemoPace(flow, pace) {
194
214
  const demoPace = pace;
@@ -203,6 +223,7 @@ export function withDemoPace(flow, pace) {
203
223
  * Default highlight look for a Flow episode (size / weight / optional tone).
204
224
  * Per-slot stub or {@link withHighlightFixtures} values win when set.
205
225
  * @example withHighlightStyle(withDemoPace(gapFlow, "slow"), { size: "lg", weight: "bold" })
226
+ * @deprecated Use `engine.map({ highlightStyle })`.
206
227
  */
207
228
  export function withHighlightStyle(flow, style) {
208
229
  const highlightStyle = {
@@ -220,12 +241,12 @@ export function withHighlightStyle(flow, style) {
220
241
  /**
221
242
  * Demo pacing for one Block or compose unit (not FF - use fastForwardComposeBlock for blitz).
222
243
  * @example withBlockPace(composeBlock("gap-review", [...]), "slow")
244
+ * @deprecated Use a step option: `.method(Block, { pace })`.
223
245
  */
224
246
  export function withBlockPace(block, pace) {
225
- return {
226
- ...block,
227
- demoPace: pace,
228
- };
247
+ const next = { ...block, demoPace: pace };
248
+ copyWaygraphRuntime(block, next);
249
+ return next;
229
250
  }
230
251
  /**
231
252
  * Marks a Flow as one that's SUPPOSED to throw - e.g. a locked-out user's
@@ -244,6 +265,7 @@ export function withBlockPace(block, pace) {
244
265
  * engine.defineFlow([start, NavLoginBlock, SubmitLoginActionBlock, end]),
245
266
  * "locked_out_user stays on LoginPage with the error banner - working as intended.",
246
267
  * );
268
+ * @deprecated Use `engine.map({ expectedFailure: reason })`.
247
269
  */
248
270
  export function withExpectedFailure(flow, reason) {
249
271
  return {
@@ -259,6 +281,7 @@ export function withExpectedFailure(flow, reason) {
259
281
  * stubOnError slots. Non-destructive, same pattern as {@link withTitle}.
260
282
  * Demo/`chain --step` reads `flow.highlightFixtures`; `run()` ignores it.
261
283
  * @example withHighlightFixtures(issueFlow, { "submit-login": { stubBefore: { email: { label: "AC-1" } } } })
284
+ * @deprecated Chain ctx stubs on the Block: `Block.stubAfter((ctx) => …)`.
262
285
  */
263
286
  export function withHighlightFixtures(flow, fixtures) {
264
287
  return {
@@ -306,7 +329,7 @@ function locateInChain(flows, block) {
306
329
  }
307
330
  throw new WaygraphError("WG_FLOW_BLOCK_NOT_FOUND", MSG_CHAINFLOW_BLOCK_NOT_FOUND_BY_NAME(blockName));
308
331
  }
309
- async function clearSessionState(context, page) {
332
+ export async function clearSessionState(context, page) {
310
333
  await context.clearCookies().catch(() => { });
311
334
  await page
312
335
  .evaluate(() => {
@@ -337,6 +360,7 @@ async function clearSessionState(context, page) {
337
360
  * chain - nothing to reset yet). The combined result is whatever the LAST
338
361
  * flow resolved to.
339
362
  * @example chainFlow(ticket123Flow, ticket456Flow).run(context, mem)
363
+ * @deprecated Nest flows with `map().flow(a).flow(b, { reset: true })`.
340
364
  */
341
365
  export function chainFlow(...flows) {
342
366
  if (flows.length === 0) {
@@ -1,6 +1,7 @@
1
1
  import type { Block, Checkpoint } from "../types.js";
2
2
  import type { EndMarker, StartMarker } from "./run-graph.js";
3
3
  import type { Flow, FlowOptions } from "./flow.js";
4
+ import type { DemoPace, HighlightStyleDefaults } from "../highlights.js";
4
5
  import type { NavBlock } from "./blocks/nav.js";
5
6
  import type { PageBlock } from "./blocks/page.js";
6
7
  import type { AssertBlock, MethodBlock } from "./blocks/method.js";
@@ -13,7 +14,7 @@ type S = Checkpoint<"__start__">;
13
14
  * defineFlow) - a real cycle check:structure forbids. This type is how that's avoided.
14
15
  */
15
16
  export type DefineFlowFn = (blocks: readonly [StartMarker, ...Block<any, any>[], EndMarker], options?: FlowOptions) => Flow<any>;
16
- /** Per-step options every `MapBuilder` step method (`.gotoPage()`/`.method()`/`.assert()`) takes. */
17
+ /** Per-step options for the map builder: `ff` fast-forwards the step, `pace` sets its demo pace. */
17
18
  export interface MapStepOpts {
18
19
  /**
19
20
  * Fast-forward just this step, inline - no `.ffStart()/.ffEnd()` bracket needed. Consecutive
@@ -22,7 +23,14 @@ export interface MapStepOpts {
22
23
  * @example map().gotoPage(NavLoginBlock, { ff: true }).method(FillUsernameBlock, { ff: true })
23
24
  */
24
25
  ff?: boolean;
26
+ /**
27
+ * Demo pace for just this step (`"blitz"` skips the typing animation and the pause after the action; `"fast"`, `"slow"`,
28
+ * or a number scale the narration gates). The map equivalent of `withBlockPace`, with no wrapper.
29
+ * @example map().method(SetBearerTokenBlock, { pace: "blitz" })
30
+ */
31
+ pace?: DemoPace;
25
32
  }
33
+ /** Flow-level settings for `engine.map({ … })`: `memStub`, `sessionReset`, `expectedFailure`, `demoPace`, `highlightStyle`, `homeOrigin`. */
26
34
  export interface MapBuilderOptions {
27
35
  /**
28
36
  * This project's own origin (e.g. `"https://app.example.com"`) - enables
@@ -31,6 +39,24 @@ export interface MapBuilderOptions {
31
39
  * way; only the internal/external origin distinction is opt-in).
32
40
  */
33
41
  homeOrigin?: string;
42
+ /** Fill missing `requires` keys from `registerMemStub` when the flow runs (what `withMemStub` did). */
43
+ memStub?: boolean;
44
+ /**
45
+ * This flow wants a fresh browser session (cookies and storage cleared) when it runs after another one, in a CLI chain
46
+ * (`waygraph run "a then b"`) or nested with `.flow(sub)`: a second user signing in, a logged-out check.
47
+ */
48
+ sessionReset?: boolean;
49
+ /** Marks a flow that is supposed to throw, with the reason shown in the demo (what `withExpectedFailure` did). */
50
+ expectedFailure?: string;
51
+ /** Default demo pace for the whole flow (what `withDemoPace` did). */
52
+ demoPace?: DemoPace;
53
+ /** Default ring tone, size and weight for the whole flow (what `withHighlightStyle` did). */
54
+ highlightStyle?: HighlightStyleDefaults;
55
+ }
56
+ /** What `.flow()` takes for the sub-flow it runs inline. */
57
+ export interface MapFlowOpts {
58
+ /** Clear cookies and storage right before the sub-flow, e.g. to start a second user fresh. Defaults to the sub-flow's own `sessionReset`. */
59
+ reset?: boolean;
34
60
  }
35
61
  /**
36
62
  * Fluent builder over {@link Engine.defineFlow} - see `map()`'s own doc
@@ -119,6 +145,12 @@ export declare class MapBuilder<Out extends Checkpoint<string>> {
119
145
  method<NextOut extends Checkpoint<string>>(block: (MethodBlock<Out, NextOut> | EffectBlock<Out, NextOut>) & {
120
146
  name: string;
121
147
  }, opts?: MapStepOpts): MapBuilder<NextOut>;
148
+ /**
149
+ * Runs another flow's steps inline here, in the same page, browser and `mem`. This is how flows nest:
150
+ * `map().flow(loginFlow).flow(viewerFlow, { reset: true })`. With `{ reset: true }` cookies and storage are cleared
151
+ * right before the sub-flow's first step. Replaces `chainFlow` and `withSessionReset`.
152
+ */
153
+ flow<NextOut extends Checkpoint<string>>(sub: Flow<NextOut>, opts?: MapFlowOpts): MapBuilder<NextOut>;
122
154
  /**
123
155
  * Branch: run everything added so far, then continue into whichever `routes[tag]` matches the
124
156
  * resolved Checkpoint's tag - same page, same context, same mem. `routes` must cover every tag
@@ -166,6 +198,7 @@ export declare class MapBuilder<Out extends Checkpoint<string>> {
166
198
  * left open at `.end()` closes silently; an EXPLICIT `.ffStart()` left open
167
199
  * still throws - the author opened that one and needs to close it themselves.
168
200
  */
201
+ /** @param options @deprecated Pass flow settings to `map({ … })` instead. */
169
202
  end(options?: FlowOptions): Flow<Out>;
170
203
  }
171
204
  export {};
@@ -2,6 +2,30 @@ import { WaygraphError, MSG_MAP_WRONG_KIND, MSG_MAP_WRONG_SALT, MSG_MAP_WRONG_OR
2
2
  import { end, start } from "./run-graph.js";
3
3
  import { MemPage } from "../mem-page.js";
4
4
  import { fastForwardComposeBlock } from "./compose.js";
5
+ import { clearSessionState, withBlockPace } from "./flow.js";
6
+ import { copyWaygraphRuntime } from "./block.js";
7
+ /** Per-step options every `MapBuilder` step method (`.gotoPage()`/`.method()`/`.assert()`) takes. */
8
+ /** The same Block, but it clears cookies and storage before it acts. Keeps the runtime markers. */
9
+ function resetBefore(block) {
10
+ const act = block.instruction.act.bind(block.instruction);
11
+ const next = {
12
+ ...block,
13
+ instruction: {
14
+ ...block.instruction,
15
+ async act(page, input, mem) {
16
+ await clearSessionState(page.context(), page);
17
+ return act(page, input, mem);
18
+ },
19
+ },
20
+ };
21
+ copyWaygraphRuntime(block, next);
22
+ Object.defineProperty(next, "__waygraphResetBefore", { value: true, enumerable: false, configurable: true });
23
+ return next;
24
+ }
25
+ /** Applies a step's `pace` option, keeping the Block's runtime markers. */
26
+ function paced(block, opts) {
27
+ return (opts?.pace === undefined ? block : withBlockPace(block, opts.pace));
28
+ }
5
29
  function mapKindOf(block) {
6
30
  return block?.__waygraphKind;
7
31
  }
@@ -237,7 +261,7 @@ export class MapBuilder {
237
261
  gotoPage(block, opts) {
238
262
  assertMapKind(block, ["nav", "page"], "gotoPage");
239
263
  assertMapOrigin(block, this.homeOrigin, "internal", "gotoPage");
240
- return this.appendStep(block, opts?.ff);
264
+ return this.appendStep(paced(block, opts), opts?.ff);
241
265
  }
242
266
  /**
243
267
  * Appends a genuinely cross-origin navigation step (e.g. `(external)/`
@@ -249,7 +273,7 @@ export class MapBuilder {
249
273
  gotoExternal(block, opts) {
250
274
  assertMapKind(block, ["nav", "page"], "gotoExternal");
251
275
  assertMapOrigin(block, this.homeOrigin, "external", "gotoExternal");
252
- return this.appendStep(block, opts?.ff);
276
+ return this.appendStep(paced(block, opts), opts?.ff);
253
277
  }
254
278
  /**
255
279
  * Appends a self-loop verification step - requires a Block from
@@ -258,7 +282,7 @@ export class MapBuilder {
258
282
  */
259
283
  assert(block, opts) {
260
284
  assertMapKind(block, ["assert"], "assert");
261
- return this.appendStep(block, opts?.ff);
285
+ return this.appendStep(paced(block, opts), opts?.ff);
262
286
  }
263
287
  /**
264
288
  * Appends a non-navigating page action (submit, upload, add/remove an
@@ -274,7 +298,20 @@ export class MapBuilder {
274
298
  throw new WaygraphError("WG_MAP_WRONG_KIND", MSG_MAP_METHOD_GOT_ASSERT(block.name));
275
299
  }
276
300
  assertMapSalt(block, ["method", "effect"], "method");
277
- return this.appendStep(block, opts?.ff);
301
+ return this.appendStep(paced(block, opts), opts?.ff);
302
+ }
303
+ /**
304
+ * Runs another flow's steps inline here, in the same page, browser and `mem`. This is how flows nest:
305
+ * `map().flow(loginFlow).flow(viewerFlow, { reset: true })`. With `{ reset: true }` cookies and storage are cleared
306
+ * right before the sub-flow's first step. Replaces `chainFlow` and `withSessionReset`.
307
+ */
308
+ flow(sub, opts) {
309
+ let next = this;
310
+ sub.blocks().forEach((info, i) => {
311
+ const block = i === 0 && (opts?.reset ?? sub.resetSession) ? resetBefore(info.block) : info.block;
312
+ next = next.appendStep(block);
313
+ });
314
+ return next;
278
315
  }
279
316
  /**
280
317
  * Branch: run everything added so far, then continue into whichever `routes[tag]` matches the
@@ -329,6 +366,7 @@ export class MapBuilder {
329
366
  * left open at `.end()` closes silently; an EXPLICIT `.ffStart()` left open
330
367
  * still throws - the author opened that one and needs to close it themselves.
331
368
  */
369
+ /** @param options @deprecated Pass flow settings to `map({ … })` instead. */
332
370
  end(options) {
333
371
  if (this.ff && !this.ff.auto) {
334
372
  throw new WaygraphError("WG_MAP_FF_NOT_CLOSED", MSG_MAP_FF_NOT_CLOSED(this.ff.name));
@@ -36,6 +36,7 @@ export interface Layout {
36
36
  /** Function form gets the full resolved Checkpoint, same shape `runVerify`/a Block's own `verify` already use - not just the tag. */
37
37
  verify: Trait[] | ((out: Checkpoint<string>) => Trait[]);
38
38
  }
39
+ /** Options for `defineLayout`: which Checkpoints it applies to and the Traits it enforces. */
39
40
  export interface LayoutOptions {
40
41
  name: string;
41
42
  description?: string;
@@ -142,6 +143,7 @@ export declare function spawnTab<TOut extends Checkpoint<string>>(entry: Block<C
142
143
  export declare function narrate<T>(locator: Locator, caption: string, action: () => Promise<T>): Promise<T>;
143
144
  /** Reserved markers bookending a `defineFlow([start, ...blocks, end])` call. */
144
145
  export declare const start: unique symbol;
146
+ /** Marks the end of a hand-built chain. Only the deprecated `defineFlow` needs it; `map()` does not. */
145
147
  export declare const end: unique symbol;
146
148
  export type StartMarker = typeof start;
147
149
  export type EndMarker = typeof end;
@@ -219,4 +219,5 @@ export async function narrate(locator, caption, action) {
219
219
  }
220
220
  /** Reserved markers bookending a `defineFlow([start, ...blocks, end])` call. */
221
221
  export const start = Symbol("waygraph.start");
222
+ /** Marks the end of a hand-built chain. Only the deprecated `defineFlow` needs it; `map()` does not. */
222
223
  export const end = Symbol("waygraph.end");
package/dist/errors.d.ts CHANGED
@@ -148,6 +148,7 @@ export declare const MSG_FASTFORWARD_EMPTY: (name: string) => string;
148
148
  export declare const MSG_CHAINFLOW_BLOCK_NOT_FOUND_BY_INDEX: (index: number, count: number) => string;
149
149
  export declare const MSG_CHAINFLOW_BLOCK_NOT_FOUND_BY_NAME: (blockName: string) => string;
150
150
  export declare const MSG_CHAINFLOW_EMPTY = "chainFlow: give at least one Flow";
151
+ /** The code of a `WaygraphError`, e.g. `WG_PREFLIGHT_MISSING_KEY`. */
151
152
  export type WaygraphErrorCode = keyof typeof WAYGRAPH_ERRORS;
152
153
  /** A thrown engine error with a stable, greppable `.code` - see WAYGRAPH_ERRORS for the full list. */
153
154
  export declare class WaygraphError extends Error {
@@ -4,6 +4,7 @@ export type ExtensionSpec = string | {
4
4
  path?: string;
5
5
  sha256?: string;
6
6
  };
7
+ /** Names accepted in `extensions: [...]` mapped to their Chrome Web Store ids. */
7
8
  export declare const KNOWN_EXTENSIONS: Readonly<Record<string, string>>;
8
9
  /** Unpacks a .crx and pins the manifest `key`, so the extension keeps its store id (its origin is then stable, e.g. for an API allowlist). */
9
10
  export declare function unpackCrx(crx: Buffer, dest: string): void;
@@ -4,6 +4,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, join, resolve, sep } from "node:path";
6
6
  import { inflateRawSync } from "node:zlib";
7
+ /** Names accepted in `extensions: [...]` mapped to their Chrome Web Store ids. */
7
8
  export const KNOWN_EXTENSIONS = {
8
9
  hoppscotch: "amknoiejhlmhancpahfcfcfhllgkpbld",
9
10
  };
@@ -9,12 +9,15 @@ export declare const FIXTURE_DURATION_SLOW_MS_DEFAULT = 4000;
9
9
  export declare const SLIDE_DURATION_MS_DEFAULT = 2000;
10
10
  /** @deprecated Prefer FIXTURE_DURATION_FAST_MS_DEFAULT */
11
11
  export declare const SLIDE_DURATION_FAST_MS_DEFAULT = 600;
12
+ /** Turns any accepted pace spelling into a `DemoPace`; unknown values mean normal. */
12
13
  export declare function normalizeDemoPace(raw: unknown): DemoPace;
13
- /** Named / numeric pace -> scale vs normal (1 = normal). Absolute ms (>20) maps via default dwell. */
14
+ /** The multiplier a pace applies to dwell and gate time (1 = normal); `demo.speeds` redefines the named ones. */
14
15
  export declare function demoPaceScale(pace: DemoPace): number;
16
+ /** True when a pace is blitz: no cursor, ring pop or typing animation. */
15
17
  export declare function demoPaceIsBlitz(pace: DemoPace): boolean;
16
18
  /** True when pace should use the short-gate / fast fixture path (not slow authored). */
17
19
  export declare function demoPaceIsFast(pace: DemoPace): boolean;
20
+ /** True when a pace lengthens dwell and gates. */
18
21
  export declare function demoPaceIsSlow(pace: DemoPace): boolean;
19
22
  /** Autoplay / Next-gate ms for this pace (base is WAYGRAPH_AUTOPLAY_MS or 1800). */
20
23
  export declare function demoPaceGateMs(pace: DemoPace, baseAutoplayMs: number): number;
@@ -8,6 +8,7 @@ export const FIXTURE_DURATION_SLOW_MS_DEFAULT = 4000;
8
8
  export const SLIDE_DURATION_MS_DEFAULT = FIXTURE_DURATION_MS_DEFAULT;
9
9
  /** @deprecated Prefer FIXTURE_DURATION_FAST_MS_DEFAULT */
10
10
  export const SLIDE_DURATION_FAST_MS_DEFAULT = FIXTURE_DURATION_FAST_MS_DEFAULT;
11
+ /** Turns any accepted pace spelling into a `DemoPace`; unknown values mean normal. */
11
12
  export function normalizeDemoPace(raw) {
12
13
  if (typeof raw === "number" && Number.isFinite(raw) && raw > 0)
13
14
  return raw;
@@ -34,6 +35,25 @@ export function normalizeDemoPace(raw) {
34
35
  return "normal";
35
36
  }
36
37
  /** Named / numeric pace -> scale vs normal (1 = normal). Absolute ms (>20) maps via default dwell. */
38
+ /**
39
+ * What each named pace means, as a multiplier of normal dwell (1 = normal). `demo.speeds` in the config (or
40
+ * `WAYGRAPH_PACE_SPEEDS`, a JSON object) overrides any of them; the defaults are blitz 0.05, fast 0.33, slow 2.
41
+ */
42
+ function paceSpeeds() {
43
+ const out = { blitz: 0.05, fast: 0.33, slow: 2 };
44
+ try {
45
+ const raw = process.env.WAYGRAPH_PACE_SPEEDS ? JSON.parse(process.env.WAYGRAPH_PACE_SPEEDS) : {};
46
+ for (const k of ["blitz", "fast", "slow"]) {
47
+ if (typeof raw[k] === "number" && raw[k] > 0)
48
+ out[k] = Math.min(10, raw[k]);
49
+ }
50
+ }
51
+ catch {
52
+ /* ignore a malformed value: defaults apply */
53
+ }
54
+ return out;
55
+ }
56
+ /** The multiplier a pace applies to dwell and gate time (1 = normal); `demo.speeds` redefines the named ones. */
37
57
  export function demoPaceScale(pace) {
38
58
  if (typeof pace === "number") {
39
59
  if (pace > 20)
@@ -41,13 +61,14 @@ export function demoPaceScale(pace) {
41
61
  return Math.min(10, Math.max(0.05, pace));
42
62
  }
43
63
  if (pace === "blitz")
44
- return 0.05;
64
+ return paceSpeeds().blitz;
45
65
  if (pace === "fast")
46
- return 0.33;
66
+ return paceSpeeds().fast;
47
67
  if (pace === "slow")
48
- return 2;
68
+ return paceSpeeds().slow;
49
69
  return 1;
50
70
  }
71
+ /** True when a pace is blitz: no cursor, ring pop or typing animation. */
51
72
  export function demoPaceIsBlitz(pace) {
52
73
  return pace === "blitz" || (typeof pace === "number" && pace > 0 && pace <= 0.1);
53
74
  }
@@ -61,6 +82,7 @@ export function demoPaceIsFast(pace) {
61
82
  return pace < 0.75;
62
83
  return false;
63
84
  }
85
+ /** True when a pace lengthens dwell and gates. */
64
86
  export function demoPaceIsSlow(pace) {
65
87
  if (pace === "slow")
66
88
  return true;
@@ -73,6 +95,9 @@ export function demoPaceIsSlow(pace) {
73
95
  }
74
96
  /** Autoplay / Next-gate ms for this pace (base is WAYGRAPH_AUTOPLAY_MS or 1800). */
75
97
  export function demoPaceGateMs(pace, baseAutoplayMs) {
98
+ if (pace === "blitz" && process.env.WAYGRAPH_PACE_SPEEDS) {
99
+ return Math.min(30000, Math.max(20, Math.round(baseAutoplayMs * paceSpeeds().blitz)));
100
+ }
76
101
  if (demoPaceIsBlitz(pace))
77
102
  return Math.min(80, baseAutoplayMs);
78
103
  if (typeof pace === "number" && pace > 20) {
@@ -3,7 +3,9 @@ import type { DeviceOrientation, DevicePreset, DeviceState, DeviceViewport } fro
3
3
  export declare const DEVICE_PRESETS: Record<DevicePreset, Omit<DeviceState, "touchMode"> & {
4
4
  touchMode?: boolean;
5
5
  }>;
6
+ /** Turns a device name (`mobile`, `tablet`, ...) into a known preset. */
6
7
  export declare function normalizeDevicePreset(raw: unknown): DevicePreset | undefined;
8
+ /** Turns `portrait` / `landscape` spellings into a device orientation. */
7
9
  export declare function normalizeDeviceOrientation(raw: unknown): DeviceOrientation | undefined;
8
10
  /** Infer orientation from box (square counts as landscape). */
9
11
  export declare function orientationFromViewport(vp: DeviceViewport): DeviceOrientation;
@@ -25,6 +25,7 @@ export const DEVICE_PRESETS = {
25
25
  orientation: "landscape",
26
26
  },
27
27
  };
28
+ /** Turns a device name (`mobile`, `tablet`, ...) into a known preset. */
28
29
  export function normalizeDevicePreset(raw) {
29
30
  const s = String(raw ?? "")
30
31
  .trim()
@@ -37,6 +38,7 @@ export function normalizeDevicePreset(raw) {
37
38
  return "desktop";
38
39
  return undefined;
39
40
  }
41
+ /** Turns `portrait` / `landscape` spellings into a device orientation. */
40
42
  export function normalizeDeviceOrientation(raw) {
41
43
  const s = String(raw ?? "")
42
44
  .trim()
@@ -33,6 +33,7 @@ export type WaygraphTodoItem = {
33
33
  /** Explicit current. If omitted, derived from todoIndex (index === todoIndex). */
34
34
  current?: boolean;
35
35
  };
36
+ /** One todo item as authored: a string or `{ id, text, done }`. */
36
37
  export type WaygraphTodoInput = string | WaygraphTodoItem;
37
38
  /**
38
39
  * Dock list presentation:
@@ -69,6 +70,7 @@ export type WaygraphTodoGroupInput = {
69
70
  /** Sequential only: which item is current (0-based). */
70
71
  todoIndex?: number;
71
72
  };
73
+ /** A titled checklist in the demo todo dock. */
72
74
  export type WaygraphTodoGroup = {
73
75
  id?: string;
74
76
  title?: string;
@@ -300,6 +302,7 @@ export interface WaygraphHighlightStub extends FixtureDurationFields {
300
302
  }
301
303
  /** Flow fixture patch: label required; selector optional (inherits from stub). */
302
304
  export type WaygraphHighlightFixture = Partial<Pick<WaygraphHighlightStub, "selector">> & Required<Pick<WaygraphHighlightStub, "label">> & Pick<WaygraphHighlightStub, "detail" | "tag" | "tone" | "size" | "weight" | "zoom" | "zoomOut" | "focus" | "followMouse" | "follow" | "color" | "cursor" | "todo" | "gesture"> & FixtureDurationFields;
305
+ /** A static stub phase: named ring slots to paint before or after a step. */
303
306
  export type HighlightStubPhase = Record<string, WaygraphHighlightStub>;
304
307
  /**
305
308
  * Open stub lifecycle context. Authors set rings + episode fixtures freely —
@@ -459,6 +462,7 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
459
462
  highlights?: HighlightStubPhase;
460
463
  } & StubPhaseFixtures): void;
461
464
  };
465
+ /** A stub callback: receives the stub `ctx` and paints rings, titles and todos. */
462
466
  export type StubLifecycleFn<Out extends Checkpoint<string>> = (ctx: StubCtx<Out>) => void | HighlightStubPhase | Promise<void | HighlightStubPhase>;
463
467
  /**
464
468
  * Block stub authoring:
@@ -551,6 +555,7 @@ export type HighlightStyleDefaults = {
551
555
  * absolute gate/dwell ms when `> 20` (e.g. `4500`)
552
556
  */
553
557
  export type DemoPaceName = "blitz" | "fast" | "normal" | "slow";
558
+ /** How fast a demo step runs: `"fast"`, `"slow"`, `"blitz"`, `"normal"`, or a multiplier (`0.5`, `2`). */
554
559
  export type DemoPace = DemoPaceName | number;
555
560
  export type WaygraphSlidesOrFn<Out extends Checkpoint<string>> = readonly WaygraphSlide[] | ((out: Out) => readonly WaygraphSlide[]);
556
561
  /** Demo stub phase names (block instruction + flow fixtures). */
@@ -570,6 +575,7 @@ export interface WaygraphHighlight {
570
575
  label: string;
571
576
  tone?: HighlightTone;
572
577
  }
578
+ /** A ring or caption after its defaults and overrides are merged, ready to paint. */
573
579
  export type ResolvedHighlight = WaygraphHighlightStub;
574
580
  export type StubBagState = {
575
581
  highlights: HighlightStubPhase;