waygraph 0.13.4 → 0.13.7

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.
@@ -88,6 +88,22 @@ export type TodoDockState = {
88
88
  groups: WaygraphTodoGroup[];
89
89
  pos?: "left" | "right";
90
90
  };
91
+ /** Options for compact {@link StubCtx.todos}(`id`, items, opts). */
92
+ export type WaygraphTodosOpts = {
93
+ /** Dock heading. */
94
+ title?: string;
95
+ /** Current item index (0-based). */
96
+ index?: number;
97
+ /** `sequential` | `checklist` | `bullets`. */
98
+ style?: TodoListStyle;
99
+ /** Dock side. */
100
+ pos?: "left" | "right";
101
+ /**
102
+ * Keep other docks with different ids (multi-todo). Default false —
103
+ * one checklist at a time (setting todos replaces the previous dock).
104
+ */
105
+ parallel?: boolean;
106
+ };
91
107
  /** Episode-level fixtures authored inside stubBefore/After/OnError(ctx). */
92
108
  export type StubPhaseFixtures = {
93
109
  todos?: readonly WaygraphTodoInput[];
@@ -242,16 +258,24 @@ export interface WaygraphHighlightStub extends FixtureDurationFields {
242
258
  * for authored stub queues; set false to keep cursor hidden).
243
259
  */
244
260
  cursor?: boolean;
261
+ /**
262
+ * Optional link to a todo item id. When set, Method fill/click advances that
263
+ * todo row instead of relying on stub array index (avoids password/submit/username
264
+ * alphabetical reorder bugs).
265
+ */
266
+ todo?: string;
267
+ /** Slot key from stubBefore/After map (e.g. "username") - set by resolver. */
268
+ slotId?: string;
245
269
  /**
246
270
  * Pointer feel when touchMode is on (0.13+):
247
271
  * - `tap` - short press via touchscreen (default in touch mode)
248
272
  * - `hold` - long-press (~600ms) then release
249
273
  * - `click` - mouse click even in touch mode
250
274
  */
251
- gesture?: "tap" | "hold" | "click";
275
+ gesture?: "tap" | "hold" | "click" | "swipe";
252
276
  }
253
277
  /** Flow fixture patch: label required; selector optional (inherits from stub). */
254
- 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"> & FixtureDurationFields;
278
+ 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;
255
279
  export type HighlightStubPhase = Record<string, WaygraphHighlightStub>;
256
280
  /**
257
281
  * Open stub lifecycle context. Authors set rings + episode fixtures freely —
@@ -283,8 +307,14 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
283
307
  highlights(slots: HighlightStubPhase): void;
284
308
  /** Set / overwrite one named ring. */
285
309
  ring(id: string, stub: WaygraphHighlightStub): void;
286
- /** Episode checklist (demo panel). Single list; use {@link StubCtx.todoGroups} for FR/AC packs. */
310
+ /**
311
+ * Floating checklist. Prefer the compact form:
312
+ * ctx.todos("login", ["Enter email", "Enter password"], { title: "Sign in", index: 0 })
313
+ * Legacy: ctx.todos([...]); ctx.todoId(...); ctx.todoIndex(...); …
314
+ * Default = one dock (setting todos replaces). Pass `{ parallel: true }` to keep others.
315
+ */
287
316
  todos(items: readonly WaygraphTodoInput[]): void;
317
+ todos(id: string, items: readonly WaygraphTodoInput[], opts?: WaygraphTodosOpts): void;
288
318
  /** Current checklist index (bump for sequential plans). */
289
319
  todoIndex(n: number): void;
290
320
  /** Heading above the single {@link StubCtx.todos} list. */
@@ -392,6 +422,8 @@ export type StubPhaseResult = {
392
422
  zoomOut?: boolean;
393
423
  title?: string;
394
424
  todoPos?: "left" | "right";
425
+ /** When true, keep other docks (multi-todo). Default false = replace. */
426
+ todoParallel?: boolean;
395
427
  };
396
428
  /**
397
429
  * One yap/slide in a multi-step demo caption sequence.
@@ -475,12 +475,19 @@ function pickFxFields(base, patch) {
475
475
  const focus = patch?.focus !== undefined ? patch.focus : base?.focus;
476
476
  const color = patch?.color !== undefined ? patch.color : base?.color;
477
477
  const cursor = patch?.cursor !== undefined ? patch.cursor : base?.cursor;
478
+ const todo = patch?.todo !== undefined ? patch.todo : base?.todo;
479
+ const gesture = patch?.gesture !== undefined ? patch.gesture : base?.gesture;
478
480
  if (focus !== undefined)
479
481
  out.focus = !!focus;
480
482
  if (color !== undefined && String(color).trim())
481
483
  out.color = String(color).trim();
482
484
  if (cursor !== undefined)
483
485
  out.cursor = !!cursor;
486
+ if (todo !== undefined && String(todo).trim())
487
+ out.todo = String(todo).trim();
488
+ if (gesture === "tap" || gesture === "hold" || gesture === "click" || gesture === "swipe") {
489
+ out.gesture = gesture;
490
+ }
484
491
  return out;
485
492
  }
486
493
  function mergeSlot(base, patch) {
@@ -634,9 +641,7 @@ export function buildTodoDock(opts) {
634
641
  if (!items.length)
635
642
  return undefined;
636
643
  const group = { style, items };
637
- if (opts.todoTitle && String(opts.todoTitle).trim()) {
638
- group.title = String(opts.todoTitle).trim();
639
- }
644
+ // Title once on the dock - not also on the lone group (avoids duplicate headings).
640
645
  const dock = { style, groups: [group] };
641
646
  if (opts.todoTitle && String(opts.todoTitle).trim()) {
642
647
  dock.title = String(opts.todoTitle).trim();
@@ -738,13 +743,38 @@ function createStubCtx(bag, opts) {
738
743
  ring(id, stub) {
739
744
  bag.highlights = { ...bag.highlights, [id]: stub };
740
745
  },
741
- todos(items) {
746
+ todos(a, b, c) {
742
747
  bag.todoTouched = true;
743
748
  bag.hideTodos = false;
744
- bag.todos = items;
745
749
  delete bag.todoGroups;
746
- // Empty list = explicit clear (same as hideTodos).
747
- if (!items || !items.length)
750
+ if (typeof a === "string") {
751
+ const id = String(a).trim();
752
+ if (id)
753
+ bag.todoId = id;
754
+ else
755
+ delete bag.todoId;
756
+ const items = (Array.isArray(b) ? b : []);
757
+ bag.todos = items;
758
+ const opts = (c && typeof c === "object" ? c : {});
759
+ if (opts.title != null)
760
+ bag.todoTitle = String(opts.title);
761
+ if (opts.index != null)
762
+ bag.todoIndex = Number(opts.index);
763
+ const st = normalizeTodoStyle(opts.style);
764
+ if (st)
765
+ bag.todoStyle = st;
766
+ const pos = normalizeTodoPos(opts.pos);
767
+ if (pos)
768
+ bag.todoPos = pos;
769
+ bag.todoParallel = opts.parallel === true;
770
+ if (!items.length)
771
+ bag.hideTodos = true;
772
+ return;
773
+ }
774
+ bag.todos = a;
775
+ // Legacy list-only form: leave parallel unset (CLI treats as replace).
776
+ delete bag.todoParallel;
777
+ if (!a || !a.length)
748
778
  bag.hideTodos = true;
749
779
  },
750
780
  todoIndex(n) {
@@ -977,19 +1007,26 @@ function applyDefaultZoomOut(slots, defaultZoomOut) {
977
1007
  return slots.map((h) => h.zoomOut !== undefined ? h : { ...h, zoomOut: defaultZoomOut });
978
1008
  }
979
1009
  function mergePhaseMaps(phaseMap, fixturePhase) {
980
- const slotIds = new Set([...Object.keys(phaseMap), ...Object.keys(fixturePhase || {})]);
981
- const ordered = [...slotIds].sort((a, b) => {
982
- const na = Number(a);
983
- const nb = Number(b);
984
- if (!Number.isNaN(na) && !Number.isNaN(nb))
985
- return na - nb;
986
- return a.localeCompare(b);
987
- });
1010
+ const phaseKeys = Object.keys(phaseMap);
1011
+ const fixtureKeys = Object.keys(fixturePhase || {});
1012
+ const allKeys = [...new Set([...phaseKeys, ...fixtureKeys])];
1013
+ const allNumeric = allKeys.length > 0 && allKeys.every((k) => Number.isFinite(Number(k)) && String(Number(k)) === k);
1014
+ // Numeric stub ids (legacy "0","1") sort by number. Named stubs keep author
1015
+ // insertion order - localeCompare used to reorder password/submit/username
1016
+ // and advance the wrong todo index on fill.
1017
+ const ordered = allNumeric
1018
+ ? [...allKeys].sort((a, b) => Number(a) - Number(b))
1019
+ : [...phaseKeys, ...fixtureKeys.filter((k) => !phaseKeys.includes(k))];
988
1020
  const resolved = [];
989
1021
  for (const id of ordered) {
990
1022
  const merged = mergeSlot(phaseMap[id], fixturePhase?.[id]);
991
- if (merged)
992
- resolved.push(merged);
1023
+ if (merged) {
1024
+ resolved.push({
1025
+ ...merged,
1026
+ slotId: id,
1027
+ ...(merged.todo ? { todo: merged.todo } : {}),
1028
+ });
1029
+ }
993
1030
  }
994
1031
  return resolved;
995
1032
  }
@@ -1138,6 +1175,7 @@ export async function runStubPhase(block, phase, opts) {
1138
1175
  ...(bag.zoomOut !== undefined ? { zoomOut: bag.zoomOut } : {}),
1139
1176
  ...(bag.title !== undefined && bag.title !== "" ? { title: bag.title } : {}),
1140
1177
  ...(bag.todoPos !== undefined ? { todoPos: bag.todoPos } : {}),
1178
+ ...(bag.todoParallel === true ? { todoParallel: true } : {}),
1141
1179
  };
1142
1180
  }
1143
1181
  /**
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
4
4
  export { Trait, urlMatches, textEquals, visible } from "./trait.js";
5
5
  export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
6
6
  export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, normalizeDeviceOrientation, applyOrientation, orientationFromViewport, DEVICE_PRESETS, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
7
- export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, TodoDockState, TodoListStyle, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, DevicePreset, DeviceViewport, DeviceState, DeviceOrientation, } from "./highlights.js";
7
+ export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, WaygraphTodosOpts, TodoDockState, TodoListStyle, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, DevicePreset, DeviceViewport, DeviceState, DeviceOrientation, } from "./highlights.js";
8
8
  export type { Flow, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
9
9
  export type { ActionPage } from "./types.js";
10
10
  export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
@@ -26,7 +26,7 @@
26
26
  "prove": "npm run prove:login-branch && npm run prove:graph && npm run prove:add-to-cart-menu && npm test",
27
27
  "record": "waygraph run --blocks \"loginFlow then shopFlow({\\\"saucedemo.selectedItem\\\":{\\\"id\\\":\\\"sauce-labs-backpack\\\",\\\"name\\\":\\\"Sauce Labs Backpack\\\"}})\" --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\" --video --no-step",
28
28
  "demo:device": "waygraph demo --blocks deviceShowcaseFlow --auto-next --title \"Device showcase\" --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"},\\\"saucedemo.selectedItem\\\":{\\\"id\\\":\\\"sauce-labs-backpack\\\",\\\"name\\\":\\\"Sauce Labs Backpack\\\"}}\"",
29
- "demo:device:video": "waygraph demo --blocks deviceShowcaseFlow --auto-next --video ./.tmp/device-showcase --title \"Device showcase\" --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"},\\\"saucedemo.selectedItem\\\":{\\\"id\\\":\\\"sauce-labs-backpack\\\",\\\"name\\\":\\\"Sauce Labs Backpack\\\"}}\"",
29
+ "demo:device:video": "waygraph demo --blocks deviceShowcaseFlow --auto-play-video --title \"Device showcase\" --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"},\\\"saucedemo.selectedItem\\\":{\\\"id\\\":\\\"sauce-labs-backpack\\\",\\\"name\\\":\\\"Sauce Labs Backpack\\\"}}\" --video ./.tmp/device-showcase",
30
30
  "demo:bulk": "waygraph demo --blocks cartBulkFlow --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\"",
31
31
  "demo:bulk:auto-next": "waygraph demo --blocks cartBulkFlow --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\" --auto-next --title \"Cart bulk demo\"",
32
32
  "demo:bulk:video": "waygraph demo --blocks cartBulkFlow --data \"{\\\"saucedemo.credentials\\\":{\\\"username\\\":\\\"standard_user\\\",\\\"password\\\":\\\"secret_sauce\\\"}}\" --auto-play-video --title \"Cart bulk demo\"",
@@ -18,25 +18,28 @@ export const NavCartBlock = defineNavClickBlock<CartPage>({
18
18
  verify: [Trait.url({ pathname: "/cart.html" })],
19
19
  stubBefore: (ctx: StubCtx) => {
20
20
  ctx.title("Open cart · landscape");
21
- // Device showcase: rotate tablet to landscape before the cart tap.
22
21
  ctx.landscape();
23
- ctx.todoId("saucedemo-cart");
24
- ctx.todos([
25
- { id: "cart-find", text: "Find the product" },
26
- { id: "cart-add", text: "Add to cart" },
27
- { id: "cart-open", text: "Open cart" },
28
- ]);
29
- ctx.todoIndex(2);
22
+ ctx.todos(
23
+ "saucedemo",
24
+ [
25
+ { id: "login-user", text: "Enter username", done: true },
26
+ { id: "login-pass", text: "Enter password", done: true },
27
+ { id: "login-submit", text: "Tap Login", done: true },
28
+ { id: "cart-find", text: "Find the product", done: true },
29
+ { id: "cart-add", text: "Add to cart", done: true },
30
+ { id: "cart-open", text: "Open cart" },
31
+ ],
32
+ { title: "Device showcase", index: 5 },
33
+ );
30
34
  ctx.zoom(1.4);
31
35
  ctx.zoomOut(false);
32
36
  ctx.highlights({
33
37
  cart: {
34
38
  selector: ".shopping_cart_link",
35
39
  label: "Cart",
36
- detail: "Zoom cart chrome",
40
+ detail: "Open cart",
37
41
  focus: true,
38
42
  color: "#c9a6ff",
39
- zoom: 1.55,
40
43
  zoomOut: false,
41
44
  followMouse: false,
42
45
  weight: "bold",
@@ -46,16 +49,16 @@ export const NavCartBlock = defineNavClickBlock<CartPage>({
46
49
  },
47
50
  stubAfter: (ctx: StubCtx) => {
48
51
  ctx.banner("Your cart · desktop");
49
- // Showcase finale: back to desktop.
50
52
  ctx.clearDevice();
51
- ctx.zoomOut(false);
53
+ ctx.hideTodos();
54
+ ctx.zoomOut(true);
52
55
  ctx.ring("items", {
53
56
  selector: InventorySel.cartItem,
54
57
  label: "Cart items",
55
58
  detail: "Product landed in cart",
56
59
  focus: true,
57
60
  color: "#86efac",
58
- zoom: 1.4,
61
+ zoomOut: true,
59
62
  followMouse: false,
60
63
  duration: true,
61
64
  });
@@ -37,16 +37,20 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
37
37
  // Demo: zoom product -> add (stay zoomed), then badge after act.
38
38
  stubBefore: (ctx: StubCtx) => {
39
39
  ctx.title("Pick a product · tablet");
40
- // Device showcase: tablet portrait (toast + lerp from mobile landscape).
41
40
  ctx.device("tablet");
42
41
  ctx.portrait();
43
- ctx.todoId("saucedemo-cart");
44
- ctx.todos([
45
- { id: "cart-find", text: "Find the product" },
46
- { id: "cart-add", text: "Add to cart" },
47
- { id: "cart-open", text: "Open cart" },
48
- ]);
49
- ctx.todoIndex(0);
42
+ ctx.todos(
43
+ "saucedemo",
44
+ [
45
+ { id: "login-user", text: "Enter username", done: true },
46
+ { id: "login-pass", text: "Enter password", done: true },
47
+ { id: "login-submit", text: "Tap Login", done: true },
48
+ { id: "cart-find", text: "Find the product" },
49
+ { id: "cart-add", text: "Add to cart" },
50
+ { id: "cart-open", text: "Open cart" },
51
+ ],
52
+ { title: "Device showcase", index: 3 },
53
+ );
50
54
  ctx.zoom(1.35);
51
55
  ctx.zoomOut(false);
52
56
  ctx.highlights({
@@ -72,13 +76,18 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
72
76
  },
73
77
  stubAfter: (ctx: StubCtx) => {
74
78
  ctx.banner("In the cart");
75
- ctx.todoId("saucedemo-cart");
76
- ctx.todos([
77
- { id: "cart-find", text: "Find the product" },
78
- { id: "cart-add", text: "Add to cart" },
79
- { id: "cart-open", text: "Open cart" },
80
- ]);
81
- ctx.todoIndex(1);
79
+ ctx.todos(
80
+ "saucedemo",
81
+ [
82
+ { id: "login-user", text: "Enter username", done: true },
83
+ { id: "login-pass", text: "Enter password", done: true },
84
+ { id: "login-submit", text: "Tap Login", done: true },
85
+ { id: "cart-find", text: "Find the product", done: true },
86
+ { id: "cart-add", text: "Add to cart" },
87
+ { id: "cart-open", text: "Open cart" },
88
+ ],
89
+ { title: "Device showcase", index: 4 },
90
+ );
82
91
  ctx.zoomOut(false);
83
92
  ctx.ring("badge", {
84
93
  selector: InventorySel.cartBadge,
@@ -86,7 +95,6 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
86
95
  detail: "Item count updated",
87
96
  focus: true,
88
97
  color: "#86efac",
89
- zoom: 1.6,
90
98
  zoomOut: false,
91
99
  duration: true,
92
100
  });
@@ -51,21 +51,26 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
51
51
  // sequential highlight queue (appear / dwell / fade), focus + color.
52
52
  stubBefore: (ctx: StubCtx) => {
53
53
  ctx.title("Signing in · mobile");
54
- // 0.13.3: mobile portrait + touch; tap Login.
55
54
  ctx.device("mobile");
56
55
  ctx.portrait();
57
- ctx.todoId("saucedemo-login");
58
- ctx.todos([
59
- { id: "login-user", text: "Enter username" },
60
- { id: "login-pass", text: "Enter password" },
61
- { id: "login-submit", text: "Tap Login" },
62
- ]);
63
- ctx.todoIndex(0);
56
+ ctx.todos(
57
+ "saucedemo",
58
+ [
59
+ { id: "login-user", text: "Enter username" },
60
+ { id: "login-pass", text: "Enter password" },
61
+ { id: "login-submit", text: "Tap Login" },
62
+ { id: "cart-find", text: "Find the product" },
63
+ { id: "cart-add", text: "Add to cart" },
64
+ { id: "cart-open", text: "Open cart" },
65
+ ],
66
+ { title: "Device showcase", index: 0 },
67
+ );
64
68
  ctx.zoom(1.35);
65
69
  ctx.highlights({
66
70
  username: {
67
71
  selector: "#user-name",
68
72
  label: "Username",
73
+ todo: "login-user",
69
74
  focus: true,
70
75
  color: "#c9a6ff",
71
76
  zoom: 1.4,
@@ -73,6 +78,7 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
73
78
  password: {
74
79
  selector: "#password",
75
80
  label: "Password",
81
+ todo: "login-pass",
76
82
  focus: true,
77
83
  color: "#c9a6ff",
78
84
  zoom: 1.4,
@@ -80,6 +86,7 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
80
86
  submit: {
81
87
  selector: "#login-button",
82
88
  label: "Login",
89
+ todo: "login-submit",
83
90
  focus: true,
84
91
  color: "#86efac",
85
92
  zoom: 1.55,
@@ -89,17 +96,17 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
89
96
  });
90
97
  },
91
98
  stubAfter: (ctx: StubCtx) => {
92
- // Fail branch lands on LoginPage with Epic sadface - do not ring the
93
- // inventory shelf. Success (LoggedIn) hands off to shopFlow.
94
99
  if (ctx.out && ctx.out.__state === "LoginPage") {
95
100
  ctx.title("Login blocked");
96
- ctx.todoId("saucedemo-login");
97
- ctx.todos([
98
- { id: "login-user", text: "Enter username", done: true },
99
- { id: "login-pass", text: "Enter password", done: true },
100
- { id: "login-submit", text: "Click Login", done: true },
101
- ]);
102
- ctx.todoStyle("checklist");
101
+ ctx.todos(
102
+ "saucedemo",
103
+ [
104
+ { id: "login-user", text: "Enter username", done: true },
105
+ { id: "login-pass", text: "Enter password", done: true },
106
+ { id: "login-submit", text: "Click Login", done: true },
107
+ ],
108
+ { title: "Sign in", style: "checklist" },
109
+ );
103
110
  ctx.ring("error", {
104
111
  selector: '[data-test="error"]',
105
112
  label: "Login error banner",
@@ -111,16 +118,19 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
111
118
  return;
112
119
  }
113
120
  ctx.banner("Inventory · landscape");
114
- // Rotate mobile to landscape on the product shelf (toast + lerp).
115
121
  ctx.landscape();
116
- ctx.todoId("saucedemo-login");
117
- ctx.todos([
118
- { id: "login-user", text: "Enter username" },
119
- { id: "login-pass", text: "Enter password" },
120
- { id: "login-submit", text: "Click Login" },
121
- ]);
122
- ctx.todoIndex(2);
123
- // Landed on inventory - hand off to shopFlow for product -> cart zoom.
122
+ ctx.todos(
123
+ "saucedemo",
124
+ [
125
+ { id: "login-user", text: "Enter username", done: true },
126
+ { id: "login-pass", text: "Enter password", done: true },
127
+ { id: "login-submit", text: "Tap Login", done: true },
128
+ { id: "cart-find", text: "Find the product" },
129
+ { id: "cart-add", text: "Add to cart" },
130
+ { id: "cart-open", text: "Open cart" },
131
+ ],
132
+ { title: "Device showcase", index: 3 },
133
+ );
124
134
  ctx.ring("shelf", {
125
135
  selector: ".inventory_list",
126
136
  label: "Product shelf",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.13.4",
3
+ "version": "0.13.7",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",
@@ -19,7 +19,14 @@
19
19
  "dist",
20
20
  "bin",
21
21
  "templates",
22
- "examples"
22
+ "examples",
23
+ "!examples/**/.tmp",
24
+ "!examples/**/.tmp/**",
25
+ "!examples/**/.waygraph-traverse",
26
+ "!examples/**/.waygraph-traverse/**",
27
+ "!examples/**/.waygraph-videos",
28
+ "!examples/**/.waygraph-videos/**",
29
+ "!**/*.webm"
23
30
  ],
24
31
  "repository": {
25
32
  "type": "git",
@@ -1,29 +0,0 @@
1
- {
2
- "updatedAt": "2026-09-17T11:18:26.658Z",
3
- "leases": {
4
- "submit-login": {
5
- "workerId": "traverse-2",
6
- "claimedAt": 1789643826618
7
- },
8
- "add-all-to-cart": {
9
- "workerId": "traverse-2",
10
- "claimedAt": 1789643827327
11
- },
12
- "nav-cart": {
13
- "workerId": "traverse-2",
14
- "claimedAt": 1789643829570
15
- },
16
- "nav-continue-shopping": {
17
- "workerId": "traverse-2",
18
- "claimedAt": 1789643855282
19
- },
20
- "nav-item-detail::sauce-labs-backpack": {
21
- "workerId": "traverse-2",
22
- "claimedAt": 1789643857049
23
- },
24
- "nav-back-to-inventory": {
25
- "workerId": "traverse-2",
26
- "claimedAt": 1789643906658
27
- }
28
- }
29
- }