waygraph 0.10.8 → 0.11.0

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.
package/dist/engine.d.ts CHANGED
@@ -180,6 +180,12 @@ export interface Flow<Out extends Checkpoint<string>> {
180
180
  * alarming one indistinguishable from a real break.
181
181
  */
182
182
  readonly expectedFailureReason?: string;
183
+ /**
184
+ * Set once this Flow has been wrapped in {@link withHighlightFixtures} -
185
+ * demo narration overrides (unlimited purposes on `.flow.ts`) keyed by
186
+ * block.name. `run()` never reads this; step/`demo` merges onto block stubs.
187
+ */
188
+ readonly highlightFixtures?: import("./highlights.js").HighlightFixtureMap;
183
189
  /**
184
190
  * This Flow's constituent Blocks, in order between `start`/`end`, as plain
185
191
  * data - for introspection/visualization tools, without running anything.
@@ -266,6 +272,14 @@ export declare function withTitle<Out extends Checkpoint<string>>(flow: Flow<Out
266
272
  * );
267
273
  */
268
274
  export declare function withExpectedFailure<Out extends Checkpoint<string>>(flow: Flow<Out>, reason: string): Flow<Out>;
275
+ /**
276
+ * Attaches demo highlight fixtures to a Flow - unlimited purposes on `.flow.ts`
277
+ * (AC copy, BUG/GATE tags, detail) merged onto each block's stubBefore/stubAfter
278
+ * slots. Non-destructive, same pattern as {@link withTitle}.
279
+ * Demo/`chain --step` reads `flow.highlightFixtures`; `run()` ignores it.
280
+ * @example withHighlightFixtures(issueFlow, { "submit-login": { stubBefore: { email: { label: "AC-1" } } } })
281
+ */
282
+ export declare function withHighlightFixtures<Out extends Checkpoint<string>>(flow: Flow<Out>, fixtures: import("./highlights.js").HighlightFixtureMap): Flow<Out>;
269
283
  /**
270
284
  * Runs several complete, independent Flows one after another, reusing the
271
285
  * same page/context/mem in order - each remains its own validated
@@ -493,6 +507,9 @@ export type NavBlockOptions<Out extends Checkpoint<string>> = {
493
507
  checkpoint: Out["__state"];
494
508
  requires?: readonly MemKey<any>[];
495
509
  verify?: Trait[] | ((out: Out) => Trait[]);
510
+ stubBefore?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
511
+ stubAfter?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
512
+ /** @deprecated Prefer stubAfter. */
496
513
  highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
497
514
  /**
498
515
  * Live per-instance menu rows for `waygraph auto` (see {@link Block.instanceOptions}).
@@ -553,6 +570,9 @@ export type NavClickBlockOptions<Out extends Checkpoint<string>> = {
553
570
  click: string | ((mem: MemPage) => string);
554
571
  requires?: readonly MemKey<any>[];
555
572
  verify?: Trait[] | ((out: Out) => Trait[]);
573
+ stubBefore?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
574
+ stubAfter?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
575
+ /** @deprecated Prefer stubAfter. */
556
576
  highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
557
577
  instanceOptions?: Block<Checkpoint<string>, Out>["instanceOptions"];
558
578
  };
@@ -581,6 +601,9 @@ export type PageBlockOptions<Out extends Checkpoint<string>> = {
581
601
  /** Checkpoint tag for this screen (hub). */
582
602
  checkpoint: Out["__state"];
583
603
  verify?: Trait[] | ((out: Out) => Trait[]);
604
+ stubBefore?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
605
+ stubAfter?: import("./highlights.js").HighlightStubPhase | ((out: Out) => import("./highlights.js").HighlightStubPhase);
606
+ /** @deprecated Prefer stubAfter. */
584
607
  highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
585
608
  requires?: readonly MemKey<any>[];
586
609
  /**
package/dist/engine.js CHANGED
@@ -377,6 +377,21 @@ export function withExpectedFailure(flow, reason) {
377
377
  modBlockVerify: (block, nameOrIndex, newCheck) => withExpectedFailure(flow.modBlockVerify(block, nameOrIndex, newCheck), reason),
378
378
  };
379
379
  }
380
+ /**
381
+ * Attaches demo highlight fixtures to a Flow - unlimited purposes on `.flow.ts`
382
+ * (AC copy, BUG/GATE tags, detail) merged onto each block's stubBefore/stubAfter
383
+ * slots. Non-destructive, same pattern as {@link withTitle}.
384
+ * Demo/`chain --step` reads `flow.highlightFixtures`; `run()` ignores it.
385
+ * @example withHighlightFixtures(issueFlow, { "submit-login": { stubBefore: { email: { label: "AC-1" } } } })
386
+ */
387
+ export function withHighlightFixtures(flow, fixtures) {
388
+ return {
389
+ ...flow,
390
+ highlightFixtures: fixtures,
391
+ withBlockVerify: (block, verify) => withHighlightFixtures(flow.withBlockVerify(block, verify), fixtures),
392
+ modBlockVerify: (block, nameOrIndex, newCheck) => withHighlightFixtures(flow.modBlockVerify(block, nameOrIndex, newCheck), fixtures),
393
+ };
394
+ }
380
395
  function locateInChain(flows, block) {
381
396
  if (typeof block === "number") {
382
397
  let remaining = block;
@@ -659,6 +674,8 @@ export function defineNavBlock(options) {
659
674
  },
660
675
  resolve: () => checkpoint(options.checkpoint),
661
676
  ...(options.verify ? { verify: options.verify } : {}),
677
+ ...(options.stubBefore ? { stubBefore: options.stubBefore } : {}),
678
+ ...(options.stubAfter ? { stubAfter: options.stubAfter } : {}),
662
679
  ...(options.highlights ? { highlights: options.highlights } : {}),
663
680
  },
664
681
  });
@@ -692,6 +709,8 @@ export function defineNavClickBlock(options) {
692
709
  ...(options.description ? { description: options.description } : {}),
693
710
  ...(options.requires ? { requires: options.requires } : {}),
694
711
  ...(options.verify ? { verify: options.verify } : {}),
712
+ ...(options.stubBefore ? { stubBefore: options.stubBefore } : {}),
713
+ ...(options.stubAfter ? { stubAfter: options.stubAfter } : {}),
695
714
  ...(options.highlights ? { highlights: options.highlights } : {}),
696
715
  ...(options.instanceOptions ? { instanceOptions: options.instanceOptions } : {}),
697
716
  });
@@ -738,6 +757,8 @@ export function definePageBlock(options) {
738
757
  },
739
758
  resolve: () => checkpoint(options.checkpoint),
740
759
  ...(options.verify ? { verify: options.verify } : {}),
760
+ ...(options.stubBefore ? { stubBefore: options.stubBefore } : {}),
761
+ ...(options.stubAfter ? { stubAfter: options.stubAfter } : {}),
741
762
  ...(options.highlights ? { highlights: options.highlights } : {}),
742
763
  },
743
764
  });
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Block stubBefore / stubAfter + flow highlightFixtures + demo slides.
3
+ * Demo narration only - never affects runGraph pass/fail.
4
+ *
5
+ * Lifecycle (stubs) ≠ yap slides: stubs are fill/after rings tied to the block;
6
+ * slides are multi-step captions with Next between them (long-process explain).
7
+ */
8
+ import type { Block, Checkpoint } from "./types.js";
9
+ /** One named highlight slot on a block (selector + caption). */
10
+ export interface WaygraphHighlightStub {
11
+ selector: string;
12
+ /** Primary caption line. */
13
+ label: string;
14
+ /** Extra caption line (ring / slide body). */
15
+ detail?: string;
16
+ /** Short badge, e.g. AC / BUG / GATE / YAP. */
17
+ tag?: string;
18
+ }
19
+ /** Flow fixture patch: label required; selector optional (inherits from stub). */
20
+ export type WaygraphHighlightFixture = Partial<Pick<WaygraphHighlightStub, "selector">> & Required<Pick<WaygraphHighlightStub, "label">> & Pick<WaygraphHighlightStub, "detail" | "tag">;
21
+ export type HighlightStubPhase = Record<string, WaygraphHighlightStub>;
22
+ export type HighlightStubPhaseOrFn<Out extends Checkpoint<string>> = HighlightStubPhase | ((out: Out) => HighlightStubPhase);
23
+ /**
24
+ * One yap/slide in a multi-step demo caption sequence.
25
+ * Not block lifecycle - demo pauses (Next / auto-next) between slides.
26
+ */
27
+ export interface WaygraphSlide {
28
+ /** Primary caption (required). */
29
+ caption: string;
30
+ detail?: string;
31
+ tag?: string;
32
+ /** Optional ring while this slide is showing. */
33
+ selector?: string;
34
+ }
35
+ export type WaygraphSlidesOrFn<Out extends Checkpoint<string>> = readonly WaygraphSlide[] | ((out: Out) => readonly WaygraphSlide[]);
36
+ /** Per-block fixture map attached to a Flow via {@link withHighlightFixtures}. */
37
+ export type HighlightFixtureMap = Record<string, {
38
+ stubBefore?: Record<string, WaygraphHighlightFixture>;
39
+ stubAfter?: Record<string, WaygraphHighlightFixture>;
40
+ /** Override / supply multi-step yap slides for this block (demo only). */
41
+ slides?: readonly WaygraphSlide[];
42
+ }>;
43
+ /** Legacy array highlight (shimmed to stubAfter keys "0", "1", ...). */
44
+ export interface WaygraphHighlight {
45
+ selector: string;
46
+ label: string;
47
+ }
48
+ export type ResolvedHighlight = WaygraphHighlightStub;
49
+ /**
50
+ * Resolve ordered highlight slots for a phase, merging flow fixtures over block stubs.
51
+ * Shim: empty stubAfter + legacy `instruction.highlights` -> auto-keyed stubAfter.
52
+ */
53
+ export declare function resolveHighlightSlots(block: Block<any, any>, phase: "stubBefore" | "stubAfter", opts?: {
54
+ out?: Checkpoint<string>;
55
+ fixtures?: HighlightFixtureMap;
56
+ }): ResolvedHighlight[];
57
+ /**
58
+ * Resolve multi-step yap slides: flow fixture `slides` wins when present,
59
+ * else `instruction.slides` (function form allowed).
60
+ */
61
+ export declare function resolveSlides(block: Block<any, any>, opts?: {
62
+ out?: Checkpoint<string>;
63
+ fixtures?: HighlightFixtureMap;
64
+ }): WaygraphSlide[];
65
+ /** Caption for ring / slide UI (label|caption + optional detail/tag). */
66
+ export declare function formatHighlightCaption(h: {
67
+ label?: string;
68
+ caption?: string;
69
+ detail?: string;
70
+ tag?: string;
71
+ }): string;
72
+ /**
73
+ * True when the block authored a non-empty stubAfter (after resolve + shim),
74
+ * including legacy highlights. When true, demo should not use verify-trait fallback.
75
+ */
76
+ export declare function hasAuthoredStubAfter(block: Block<any, any>, out?: Checkpoint<string>, fixtures?: HighlightFixtureMap): boolean;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Block stubBefore / stubAfter + flow highlightFixtures + demo slides.
3
+ * Demo narration only - never affects runGraph pass/fail.
4
+ *
5
+ * Lifecycle (stubs) ≠ yap slides: stubs are fill/after rings tied to the block;
6
+ * slides are multi-step captions with Next between them (long-process explain).
7
+ */
8
+ function isStubPhase(v) {
9
+ return !!v && typeof v === "object" && !Array.isArray(v);
10
+ }
11
+ function mergeSlot(base, patch) {
12
+ if (!base && !patch)
13
+ return null;
14
+ if (!base && patch) {
15
+ if (!patch.selector)
16
+ return null;
17
+ return {
18
+ selector: patch.selector,
19
+ label: patch.label,
20
+ ...(patch.detail ? { detail: patch.detail } : {}),
21
+ ...(patch.tag ? { tag: patch.tag } : {}),
22
+ };
23
+ }
24
+ if (base && !patch)
25
+ return { ...base };
26
+ return {
27
+ selector: patch.selector ?? base.selector,
28
+ label: patch.label,
29
+ ...(patch.detail !== undefined
30
+ ? { detail: patch.detail }
31
+ : base.detail
32
+ ? { detail: base.detail }
33
+ : {}),
34
+ ...(patch.tag !== undefined ? { tag: patch.tag } : base.tag ? { tag: base.tag } : {}),
35
+ };
36
+ }
37
+ function phaseFromLegacyHighlights(highlights) {
38
+ const out = {};
39
+ if (!highlights)
40
+ return out;
41
+ highlights.forEach((h, i) => {
42
+ if (h && typeof h.selector === "string" && typeof h.label === "string") {
43
+ out[String(i)] = { selector: h.selector, label: h.label };
44
+ }
45
+ });
46
+ return out;
47
+ }
48
+ /**
49
+ * Resolve ordered highlight slots for a phase, merging flow fixtures over block stubs.
50
+ * Shim: empty stubAfter + legacy `instruction.highlights` -> auto-keyed stubAfter.
51
+ */
52
+ export function resolveHighlightSlots(block, phase, opts) {
53
+ const instr = block.instruction;
54
+ let raw = phase === "stubBefore" ? instr.stubBefore : instr.stubAfter;
55
+ let phaseMap = {};
56
+ if (typeof raw === "function") {
57
+ try {
58
+ phaseMap = raw(opts?.out ?? { __state: "" }) || {};
59
+ }
60
+ catch {
61
+ phaseMap = {};
62
+ }
63
+ }
64
+ else if (isStubPhase(raw)) {
65
+ phaseMap = { ...raw };
66
+ }
67
+ if (phase === "stubAfter" && Object.keys(phaseMap).length === 0 && instr.highlights) {
68
+ let legacy = instr.highlights;
69
+ if (typeof legacy === "function") {
70
+ try {
71
+ legacy = legacy(opts?.out ?? { __state: "" });
72
+ }
73
+ catch {
74
+ legacy = [];
75
+ }
76
+ }
77
+ phaseMap = phaseFromLegacyHighlights(Array.isArray(legacy) ? legacy : []);
78
+ }
79
+ const fixturePhase = opts?.fixtures?.[block.name]?.[phase];
80
+ const slotIds = new Set([...Object.keys(phaseMap), ...Object.keys(fixturePhase || {})]);
81
+ const ordered = [...slotIds].sort((a, b) => {
82
+ const na = Number(a);
83
+ const nb = Number(b);
84
+ if (!Number.isNaN(na) && !Number.isNaN(nb))
85
+ return na - nb;
86
+ return a.localeCompare(b);
87
+ });
88
+ const resolved = [];
89
+ for (const id of ordered) {
90
+ const merged = mergeSlot(phaseMap[id], fixturePhase?.[id]);
91
+ if (merged)
92
+ resolved.push(merged);
93
+ }
94
+ return resolved;
95
+ }
96
+ /**
97
+ * Resolve multi-step yap slides: flow fixture `slides` wins when present,
98
+ * else `instruction.slides` (function form allowed).
99
+ */
100
+ export function resolveSlides(block, opts) {
101
+ const fromFixture = opts?.fixtures?.[block.name]?.slides;
102
+ if (fromFixture && fromFixture.length > 0) {
103
+ return [...fromFixture];
104
+ }
105
+ const instr = block.instruction;
106
+ let raw = instr.slides;
107
+ if (typeof raw === "function") {
108
+ try {
109
+ raw = raw(opts?.out ?? { __state: "" });
110
+ }
111
+ catch {
112
+ raw = [];
113
+ }
114
+ }
115
+ if (!Array.isArray(raw))
116
+ return [];
117
+ return raw.filter((s) => s && typeof s.caption === "string" && s.caption.length > 0);
118
+ }
119
+ /** Caption for ring / slide UI (label|caption + optional detail/tag). */
120
+ export function formatHighlightCaption(h) {
121
+ const primary = h.caption ?? h.label ?? "";
122
+ const tag = h.tag ? `[${h.tag}] ` : "";
123
+ if (h.detail)
124
+ return `${tag}${primary} - ${h.detail}`;
125
+ return `${tag}${primary}`;
126
+ }
127
+ /**
128
+ * True when the block authored a non-empty stubAfter (after resolve + shim),
129
+ * including legacy highlights. When true, demo should not use verify-trait fallback.
130
+ */
131
+ export function hasAuthoredStubAfter(block, out, fixtures) {
132
+ return resolveHighlightSlots(block, "stubAfter", {
133
+ ...(out !== undefined ? { out } : {}),
134
+ ...(fixtures !== undefined ? { fixtures } : {}),
135
+ }).length > 0;
136
+ }
package/dist/index.d.ts CHANGED
@@ -2,7 +2,9 @@ export type { Checkpoint, Instruction, Block, DefinedBlock, Start, WaygraphInsta
2
2
  export { connect, checkpoint } from "./types.js";
3
3
  export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
4
4
  export { Trait, urlMatches, textEquals, visible } from "./trait.js";
5
- export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, chainFlow, } from "./engine.js";
5
+ export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
6
+ export { resolveHighlightSlots, resolveSlides, formatHighlightCaption, hasAuthoredStubAfter, } from "./highlights.js";
7
+ export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, HighlightStubPhase, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, } from "./highlights.js";
6
8
  export type { Flow, EngineConfig, ComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
7
9
  export type { ActionPage } from "./types.js";
8
10
  export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { connect, checkpoint } from "./types.js";
2
2
  export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
3
3
  export { Trait, urlMatches, textEquals, visible } from "./trait.js";
4
- export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, chainFlow, } from "./engine.js";
4
+ export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, preflight, withSessionReset, withTitle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
5
+ export { resolveHighlightSlots, resolveSlides, formatHighlightCaption, hasAuthoredStubAfter, } from "./highlights.js";
5
6
  export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
package/dist/types.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { Page } from "@playwright/test";
2
2
  import type { MemPage, MemKey } from "./mem-page.js";
3
3
  import type { Trait } from "./trait.js";
4
+ import type { HighlightStubPhaseOrFn, WaygraphHighlight, WaygraphSlidesOrFn } from "./highlights.js";
5
+ export type { WaygraphHighlight, WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, HighlightStubPhase, HighlightStubPhaseOrFn, HighlightFixtureMap, ResolvedHighlight, } from "./highlights.js";
4
6
  /**
5
7
  * A Checkpoint is identified solely by its string tag - a phantom marker with no
6
8
  * embedded data. Two Checkpoints with the same tag are the same state everywhere.
@@ -88,24 +90,33 @@ export interface Instruction<In extends Checkpoint<string>, Out extends Checkpoi
88
90
  */
89
91
  verify?: Trait[] | ((out: Out) => Trait[]);
90
92
  /**
91
- * Optional. Arbitrary highlight points for step-mode tooling to show, in
92
- * order, after this Block resolves - independent of `verify`, for
93
- * calling out something worth a human's attention regardless of whether
94
- * it's also being asserted ("remember this signature ID" isn't a
95
- * pass/fail check). A function form picks different highlights per
96
- * resolved `Out`, same as `verify`. Purely additive UI narration - never
97
- * consulted by `runGraph`/`connect`/anything that decides behavior.
98
- * @example highlights: (out) => [{ selector: "#signature-id", label: "remember this ID" }]
93
+ * Catch-all demo slots shown **before / during** `act` (fill/click rings).
94
+ * Named map; `{}` is valid. Flow {@link HighlightFixtureMap} overrides labels.
95
+ * @example stubBefore: { email: { selector: "#email", label: "Email" } }
96
+ */
97
+ stubBefore?: HighlightStubPhaseOrFn<Out>;
98
+ /**
99
+ * Catch-all demo slots shown **after** resolve (step panel rings).
100
+ * Named map; `{}` is valid. Prefer this over legacy {@link Instruction.highlights}.
101
+ * @example stubAfter: { badge: { selector: ".cart", label: "Cart updated" } }
102
+ */
103
+ stubAfter?: HighlightStubPhaseOrFn<Out>;
104
+ /**
105
+ * Multi-step demo captions ("yap" slides) with Next between each -
106
+ * not block lifecycle. Explains a long process; optional `selector` rings
107
+ * while that slide is up. Flow fixtures may override via `slides`.
108
+ * @example slides: [
109
+ * { caption: "Checkout is three screens", tag: "YAP" },
110
+ * { caption: "Overview confirms totals", selector: ".summary_info" },
111
+ * ]
112
+ */
113
+ slides?: WaygraphSlidesOrFn<Out>;
114
+ /**
115
+ * @deprecated Prefer {@link Instruction.stubAfter}. Shimmed to stubAfter keys
116
+ * `"0"`, `"1"`, … when stubAfter is empty.
99
117
  */
100
118
  highlights?: readonly WaygraphHighlight[] | ((out: Out) => readonly WaygraphHighlight[]);
101
119
  }
102
- /** One arbitrary highlight point - see {@link Instruction.highlights}. */
103
- export interface WaygraphHighlight {
104
- /** CSS selector for the element to highlight. */
105
- selector: string;
106
- /** Caption shown next to the highlight ring. */
107
- label: string;
108
- }
109
120
  /**
110
121
  * One selectable option for a Block whose real choices depend on the live
111
122
  * page - "which of these items to add to cart" is only knowable by looking
@@ -18,6 +18,34 @@ export const FinishOrderBlock = defineMethodBlock<CheckoutOverviewPage, OrderCom
18
18
  },
19
19
  resolve: () => checkpoint("OrderComplete"),
20
20
  verify: [Trait.text(".complete-header", "Thank you for your order!")],
21
- highlights: () => [{ selector: ".complete-header", label: "remember this: order confirmed" }],
21
+ stubBefore: {
22
+ finish: { selector: "#finish", label: "Finish" },
23
+ },
24
+ stubAfter: {
25
+ thanks: {
26
+ selector: ".complete-header",
27
+ label: "Order confirmed",
28
+ detail: "Thank you for your order!",
29
+ },
30
+ },
31
+ slides: [
32
+ {
33
+ caption: "Checkout is a short pipeline",
34
+ detail: "Cart → info → overview → done. Watch the last click place the order.",
35
+ tag: "YAP",
36
+ },
37
+ {
38
+ caption: "Finish submits the overview",
39
+ detail: "This click is the only mutating step on this screen.",
40
+ tag: "YAP",
41
+ selector: "#finish",
42
+ },
43
+ {
44
+ caption: "Completion banner is the proof",
45
+ detail: "The next ring calls out the thank-you header.",
46
+ tag: "YAP",
47
+ selector: ".complete-header",
48
+ },
49
+ ],
22
50
  },
23
51
  });
@@ -30,7 +30,14 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
30
30
  },
31
31
  },
32
32
  ],
33
- highlights: () => [{ selector: InventorySel.cartBadge, label: "remember this: cart now has the item" }],
33
+ stubBefore: {},
34
+ stubAfter: {
35
+ badge: {
36
+ selector: InventorySel.cartBadge,
37
+ label: "Cart badge",
38
+ detail: "Item is in the cart",
39
+ },
40
+ },
34
41
  },
35
42
  async instanceOptions(page): Promise<readonly WaygraphInstanceOption[]> {
36
43
  const items = await collectAddableItems(page);
@@ -38,5 +38,11 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
38
38
  out.__state === "LoggedIn"
39
39
  ? [Trait.url({ pathname: "/inventory.html" })]
40
40
  : [Trait.visible('[data-test="error"]')],
41
+ stubBefore: {
42
+ username: { selector: "#user-name", label: "Username" },
43
+ password: { selector: "#password", label: "Password" },
44
+ submit: { selector: "#login-button", label: "Login" },
45
+ },
46
+ stubAfter: {},
41
47
  },
42
48
  });
@@ -1,4 +1,4 @@
1
- import { Engine, start, end, withTitle } from "waygraph";
1
+ import { Engine, start, end, withTitle, withHighlightFixtures } from "waygraph";
2
2
  import type {
3
3
  LoginPage,
4
4
  LoggedIn,
@@ -21,25 +21,64 @@ const engine = new Engine({ headless: false, slowMo: 250 });
21
21
  // Atomic nav/action per real URL - the shape `waygraph auto` discovers.
22
22
  // Explicit type args: AddToCartBlock's In is Checkpoint<string> (wildcard),
23
23
  // which trips defineFlow overload inference across a long array.
24
- export const checkoutFlow = withTitle(
25
- engine.defineFlow<
26
- LoginPage,
27
- LoggedIn,
28
- ItemInCart,
29
- CartPage,
30
- CheckoutInfoPage,
31
- CheckoutOverviewPage,
32
- OrderComplete
33
- >([
34
- start,
35
- NavLoginBlock,
36
- SubmitLoginForFlow,
37
- AddToCartBlock,
38
- NavCartBlock,
39
- NavCheckoutInfoBlock,
40
- SubmitCheckoutInfoBlock,
41
- FinishOrderBlock,
42
- end,
43
- ]),
44
- "Owner: Full Checkout",
24
+ //
25
+ // Demo narration showcase (waygraph 0.11+):
26
+ // - Block stubs: submit-login stubBefore, add-to-cart / finish-order stubAfter
27
+ // - Flow fixtures: ticket-style captions (unlimited purposes on .flow.ts)
28
+ // - Slides on finish-order: multi-step yap with Next between slides
29
+ export const checkoutFlow = withHighlightFixtures(
30
+ withTitle(
31
+ engine.defineFlow<
32
+ LoginPage,
33
+ LoggedIn,
34
+ ItemInCart,
35
+ CartPage,
36
+ CheckoutInfoPage,
37
+ CheckoutOverviewPage,
38
+ OrderComplete
39
+ >([
40
+ start,
41
+ NavLoginBlock,
42
+ SubmitLoginForFlow,
43
+ AddToCartBlock,
44
+ NavCartBlock,
45
+ NavCheckoutInfoBlock,
46
+ SubmitCheckoutInfoBlock,
47
+ FinishOrderBlock,
48
+ end,
49
+ ]),
50
+ "Owner: Full Checkout",
51
+ ),
52
+ {
53
+ "submit-login": {
54
+ stubBefore: {
55
+ username: {
56
+ label: "Demo user",
57
+ detail: "standard_user from Mem — library block stays neutral.",
58
+ tag: "AC",
59
+ },
60
+ password: { label: "Password", detail: "Filled from Mem credentials." },
61
+ submit: { label: "Sign in", tag: "GATE" },
62
+ },
63
+ },
64
+ "add-to-cart": {
65
+ stubAfter: {
66
+ badge: {
67
+ label: "Cart now has the item",
68
+ detail: "Badge updates after Add to cart.",
69
+ tag: "AC",
70
+ },
71
+ },
72
+ },
73
+ "finish-order": {
74
+ stubAfter: {
75
+ thanks: {
76
+ label: "Order placed",
77
+ detail: "Completion header is the durable proof.",
78
+ tag: "GATE",
79
+ },
80
+ },
81
+ // Optional: fixture can replace block slides; leave unset to use block slides.
82
+ },
83
+ },
45
84
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.10.8",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",
@@ -23,7 +23,7 @@
23
23
  "baseUrl": "https://www.saucedemo.com"
24
24
  },
25
25
  "dependencies": {
26
- "waygraph": "^0.10.6"
26
+ "waygraph": "^0.11.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@playwright/test": "^1.55.0",
@@ -18,6 +18,34 @@ export const FinishOrderBlock = defineMethodBlock<CheckoutOverviewPage, OrderCom
18
18
  },
19
19
  resolve: () => checkpoint("OrderComplete"),
20
20
  verify: [Trait.text(".complete-header", "Thank you for your order!")],
21
- highlights: () => [{ selector: ".complete-header", label: "remember this: order confirmed" }],
21
+ stubBefore: {
22
+ finish: { selector: "#finish", label: "Finish" },
23
+ },
24
+ stubAfter: {
25
+ thanks: {
26
+ selector: ".complete-header",
27
+ label: "Order confirmed",
28
+ detail: "Thank you for your order!",
29
+ },
30
+ },
31
+ slides: [
32
+ {
33
+ caption: "Checkout is a short pipeline",
34
+ detail: "Cart → info → overview → done. Watch the last click place the order.",
35
+ tag: "YAP",
36
+ },
37
+ {
38
+ caption: "Finish submits the overview",
39
+ detail: "This click is the only mutating step on this screen.",
40
+ tag: "YAP",
41
+ selector: "#finish",
42
+ },
43
+ {
44
+ caption: "Completion banner is the proof",
45
+ detail: "The next ring calls out the thank-you header.",
46
+ tag: "YAP",
47
+ selector: ".complete-header",
48
+ },
49
+ ],
22
50
  },
23
51
  });
@@ -30,7 +30,14 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
30
30
  },
31
31
  },
32
32
  ],
33
- highlights: () => [{ selector: InventorySel.cartBadge, label: "remember this: cart now has the item" }],
33
+ stubBefore: {},
34
+ stubAfter: {
35
+ badge: {
36
+ selector: InventorySel.cartBadge,
37
+ label: "Cart badge",
38
+ detail: "Item is in the cart",
39
+ },
40
+ },
34
41
  },
35
42
  async instanceOptions(page): Promise<readonly WaygraphInstanceOption[]> {
36
43
  const items = await collectAddableItems(page);
@@ -38,5 +38,11 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
38
38
  out.__state === "LoggedIn"
39
39
  ? [Trait.url({ pathname: "/inventory.html" })]
40
40
  : [Trait.visible('[data-test="error"]')],
41
+ stubBefore: {
42
+ username: { selector: "#user-name", label: "Username" },
43
+ password: { selector: "#password", label: "Password" },
44
+ submit: { selector: "#login-button", label: "Login" },
45
+ },
46
+ stubAfter: {},
41
47
  },
42
48
  });