waygraph 0.7.15 → 0.10.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.
Files changed (93) hide show
  1. package/README.md +90 -52
  2. package/dist/auto-explore-run.js +13 -7
  3. package/dist/auto-explore.js +47 -36
  4. package/dist/cli.d.ts +8 -12
  5. package/dist/cli.js +229 -249
  6. package/dist/engine.d.ts +77 -7
  7. package/dist/engine.js +108 -7
  8. package/dist/graph.d.ts +2 -1
  9. package/dist/graph.js +51 -11
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.js +1 -1
  12. package/examples/saucedemo/README.md +19 -5
  13. package/examples/saucedemo/docs/CONVENTION.md +22 -18
  14. package/examples/saucedemo/docs/HELPERS.md +186 -0
  15. package/examples/saucedemo/package-lock.json +1 -1
  16. package/examples/saucedemo/package.json +11 -5
  17. package/examples/saucedemo/scripts/capture-wrong-login.mjs +1 -1
  18. package/examples/saucedemo/scripts/drive-auto-cli.mjs +93 -0
  19. package/examples/saucedemo/scripts/prove-wrong-login-runtime.mjs +1 -1
  20. package/examples/saucedemo/scripts/show-auto-menu.mjs +72 -0
  21. package/examples/saucedemo/src/blocks/SITE-MAP.md +11 -8
  22. package/examples/saucedemo/src/blocks/saucedemo-web/NAV.md +3 -3
  23. package/examples/saucedemo/src/blocks/saucedemo-web/cart/NAV.md +1 -1
  24. package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-cart.block.ts +3 -3
  25. package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-checkout-info.block.ts +3 -3
  26. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/NAV.md +1 -1
  27. package/{templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/actions/submit-checkout-info.action.block.ts → examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.ts} +5 -5
  28. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/NAV.md +1 -1
  29. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/{actions/finish-order.action.block.ts → methods/finish-order.method.block.ts} +5 -5
  30. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/NAV.md +17 -5
  31. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/inventory.page.block.ts +37 -0
  32. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-all-to-cart.method.block.ts +55 -0
  33. package/{templates/quickstart/src/blocks/saucedemo-web/inventory/actions → examples/saucedemo/src/blocks/saucedemo-web/inventory/methods}/add-to-cart.effect.block.ts +6 -6
  34. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/{actions → methods}/inventory-items.ts +40 -20
  35. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/remove-all-from-cart.method.block.ts +55 -0
  36. package/{templates/quickstart/src/blocks/saucedemo-web/inventory/actions → examples/saucedemo/src/blocks/saucedemo-web/inventory/methods}/remove-from-cart.effect.block.ts +5 -5
  37. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/nav-item-detail.block.ts +4 -4
  38. package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-back-to-inventory.block.ts +3 -3
  39. package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-back-to-products.block.ts +3 -3
  40. package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-continue-shopping.block.ts +3 -3
  41. package/examples/saucedemo/src/blocks/saucedemo-web/{actions → methods}/submit-login-for-flow.ts +2 -2
  42. package/examples/saucedemo/src/blocks/saucedemo-web/{actions/submit-login.action.block.ts → methods/submit-login.method.block.ts} +5 -5
  43. package/{templates/quickstart/src/blocks/saucedemo-web/actions/submit-logout.action.block.ts → examples/saucedemo/src/blocks/saucedemo-web/methods/submit-logout.method.block.ts} +5 -5
  44. package/examples/saucedemo/src/flows/cart-bulk.flow.ts +19 -0
  45. package/examples/saucedemo/src/flows/cart-mem.flow.ts +1 -1
  46. package/examples/saucedemo/src/flows/catalog-logout.flow.ts +1 -1
  47. package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +4 -4
  48. package/examples/saucedemo/src/flows/checkout.flow.ts +4 -4
  49. package/examples/saucedemo/src/flows/login.flow.ts +1 -1
  50. package/examples/saucedemo/src/flows/shop.flow.ts +3 -3
  51. package/examples/saucedemo/src/flows/smoke.flow.ts +2 -2
  52. package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +1 -1
  53. package/examples/saucedemo/tests/flow-block-verify.spec.ts +1 -1
  54. package/examples/saucedemo/tests/submit-login-branch.spec.ts +1 -1
  55. package/package.json +1 -1
  56. package/templates/quickstart/README-saucedemo-commands.md +72 -0
  57. package/templates/quickstart/docs/CONVENTION.md +5 -5
  58. package/templates/quickstart/docs/HELPERS.md +186 -0
  59. package/templates/quickstart/package.json +9 -5
  60. package/templates/quickstart/scripts/drive-auto-cli.mjs +93 -0
  61. package/templates/quickstart/src/blocks/SITE-MAP.md +11 -8
  62. package/templates/quickstart/src/blocks/saucedemo-web/NAV.md +3 -3
  63. package/templates/quickstart/src/blocks/saucedemo-web/cart/NAV.md +1 -1
  64. package/templates/quickstart/src/blocks/saucedemo-web/cart/nav-cart.block.ts +3 -3
  65. package/templates/quickstart/src/blocks/saucedemo-web/cart/nav-checkout-info.block.ts +3 -3
  66. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/NAV.md +1 -1
  67. package/{examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/actions/submit-checkout-info.action.block.ts → templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.ts} +5 -5
  68. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/NAV.md +1 -1
  69. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/{actions/finish-order.action.block.ts → methods/finish-order.method.block.ts} +5 -5
  70. package/templates/quickstart/src/blocks/saucedemo-web/inventory/NAV.md +17 -5
  71. package/templates/quickstart/src/blocks/saucedemo-web/inventory/inventory.page.block.ts +37 -0
  72. package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-all-to-cart.method.block.ts +55 -0
  73. package/{examples/saucedemo/src/blocks/saucedemo-web/inventory/actions → templates/quickstart/src/blocks/saucedemo-web/inventory/methods}/add-to-cart.effect.block.ts +6 -6
  74. package/templates/quickstart/src/blocks/saucedemo-web/inventory/{actions → methods}/inventory-items.ts +40 -20
  75. package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/remove-all-from-cart.method.block.ts +55 -0
  76. package/{examples/saucedemo/src/blocks/saucedemo-web/inventory/actions → templates/quickstart/src/blocks/saucedemo-web/inventory/methods}/remove-from-cart.effect.block.ts +5 -5
  77. package/templates/quickstart/src/blocks/saucedemo-web/inventory/nav-item-detail.block.ts +1 -1
  78. package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-back-to-inventory.block.ts +3 -3
  79. package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-back-to-products.block.ts +3 -3
  80. package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-continue-shopping.block.ts +3 -3
  81. package/templates/quickstart/src/blocks/saucedemo-web/{actions → methods}/submit-login-for-flow.ts +2 -2
  82. package/templates/quickstart/src/blocks/saucedemo-web/{actions/submit-login.action.block.ts → methods/submit-login.method.block.ts} +5 -5
  83. package/{examples/saucedemo/src/blocks/saucedemo-web/actions/submit-logout.action.block.ts → templates/quickstart/src/blocks/saucedemo-web/methods/submit-logout.method.block.ts} +5 -5
  84. package/templates/quickstart/src/flows/cart-bulk.flow.ts +19 -0
  85. package/templates/quickstart/src/flows/cart-mem.flow.ts +1 -1
  86. package/templates/quickstart/src/flows/catalog-logout.flow.ts +1 -1
  87. package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +4 -4
  88. package/templates/quickstart/src/flows/checkout.flow.ts +4 -4
  89. package/templates/quickstart/src/flows/login.flow.ts +1 -1
  90. package/templates/quickstart/src/flows/shop.flow.ts +3 -3
  91. package/templates/quickstart/src/flows/smoke.flow.ts +2 -2
  92. package/templates/quickstart/src/flows/viewer-blocked.flow.ts +1 -1
  93. package/templates/scaffold/package.json +1 -1
package/README.md CHANGED
@@ -3,9 +3,11 @@
3
3
  A typed graph of reusable Blocks for driving a browser through E2E flows, in place of a
4
4
  flat pile of ad hoc helper functions.
5
5
 
6
+ **Current:** `0.10.0` — CLI less-is-more (`auto` / `demo` / `run` + flags). Page hubs still as in 0.9.0.
7
+
6
8
  This repo is the `waygraph` npm package itself. The full live **Sauce Demo** example
7
- (Effect Add/Remove, MemNav Open details, `waygraph auto`) ships in-tree at
8
- [`examples/saucedemo`](./examples/saucedemo). `waygraph try demo` copies
9
+ (Page inventory hub, Effect Add/Remove, MemNav Open details, `waygraph auto`) ships
10
+ in-tree at [`examples/saucedemo`](./examples/saucedemo). `waygraph try demo` copies
9
11
  [`templates/quickstart`](./templates/quickstart) (same Sauce Demo Blocks) into an OS
10
12
  temp dir.
11
13
 
@@ -14,20 +16,72 @@ See [ROADMAP.md](./ROADMAP.md) for what's shipped, spec'd, and planned by versio
14
16
 
15
17
  ## Docs (GitHub Pages)
16
18
 
17
- Browser docs (quick start, **demo / step / autoplay**, **auto explore**, engine handout, consumer layout, deploy):
19
+ Browser docs (quick start, **demo / run**, **auto explore**, engine handout, consumer layout, deploy):
18
20
 
19
21
  - **Published:** https://deviate-dv8.github.io/waygraph/ (workflow auto-enables Pages via `enablement: true`; manual Settings only if org policy blocks it)
20
22
  - **Source:** [`docs/`](./docs/) — static HTML, no build step
21
23
  - **Local preview:** `npm run docs:preview` → http://127.0.0.1:4173/
22
24
  - **Deploy:** push to `main` touching `docs/**` runs [`.github/workflows/pages.yml`](./.github/workflows/pages.yml) (`workflow_dispatch` also works)
23
- - **Demo contract:** [`docs/demo.html`](./docs/demo.html) — `--step` vs `--autoplay`
24
- - **Auto explore:** [`docs/auto.html`](./docs/auto.html) — Effect / MemNav picker (TS salt over Blocks)
25
+ - **Demo / run contract:** [`docs/demo.html`](./docs/demo.html) — `demo` / `run` / flags (`--auto-next`, `--auto-play-video`)
26
+ - **Auto explore:** [`docs/auto.html`](./docs/auto.html) — prefer `auto --cli` / `npm run auto:cli` (same menus as headed)
25
27
  - **In-repo example:** [`examples/saucedemo`](./examples/saucedemo) — full Sauce Demo project
26
28
  - **Convention showcase (Pages):** [`docs/saucedemo/`](./docs/saucedemo/) —
27
29
  https://deviate-dv8.github.io/waygraph/saucedemo/
30
+ - **Helpers (consumer copy):** [`examples/saucedemo/docs/HELPERS.md`](./examples/saucedemo/docs/HELPERS.md)
28
31
 
29
32
  This README stays the in-repo API narrative; Pages is the same material for eyeballing and cross-mesh handoff.
30
33
 
34
+ ## 0.9.0 — Page, Method, Sel
35
+
36
+ Runtime is still one type: `Block`. Helpers are TypeScript salt.
37
+
38
+ | Helper | Role | Typical file |
39
+ |--------|------|----------------|
40
+ | `definePageBlock` | Screen hub (checkpoint + `verify` + registered methods) | `*.page.block.ts` |
41
+ | `defineMethodBlock` | One-shot non-nav step (submit, upload, logout) | `methods/*.method.block.ts` |
42
+ | `defineEffectBlock` | Instance mutate + `instanceOptions` auto menu | `methods/*.effect.block.ts` |
43
+ | `defineNavBlock` / `defineNavClickBlock` | `goto` / click-nav | `nav-*.block.ts` |
44
+ | `defineMemNavBlock` | Nav + per-row `instanceOptions` | `nav-*.block.ts` |
45
+ | `defineActionBlock` | **Deprecated** alias of `defineMethodBlock` | — |
46
+
47
+ **Page hub:** one Checkpoint = this screen. Methods hang off the page for readability /
48
+ auto grouping. Arrival-only hubs omit `url`/`click` (previous Block already landed here).
49
+ Deep-link hubs pass `url` or `click` like a Nav.
50
+
51
+ **`methods/` folder** (was `actions/`): on-page work lives next to the route, not free-floating.
52
+
53
+ **`*Sel`:** DOM selectors only (static strings + `(id) => …` for item-no-1 style). Mem keys
54
+ store values (which item / upload queue), not selectors. `instanceOptions` scrapes the live
55
+ DOM into menu rows and `mem.set`s on pick; predefined methods seed mem from the flow instead.
56
+
57
+ ```typescript
58
+ import { definePageBlock, Trait } from "waygraph";
59
+
60
+ export const InventorySel = {
61
+ list: ".inventory_list",
62
+ addBtn: (id: string) => `[data-test="add-to-cart-${id}"]`,
63
+ };
64
+
65
+ export const InventoryPage = definePageBlock({
66
+ name: "page-inventory",
67
+ checkpoint: "LoggedIn",
68
+ verify: [
69
+ Trait.url({ pathname: "/inventory.html" }),
70
+ Trait.visible(InventorySel.list),
71
+ ],
72
+ methods: {
73
+ addToCart: () => AddToCartBlock,
74
+ removeFromCart: () => RemoveFromCartBlock,
75
+ addAllToCart: () => AddAllToCartBlock,
76
+ removeAllFromCart: () => RemoveAllFromCartBlock,
77
+ },
78
+ });
79
+ ```
80
+
81
+ Live reference: `examples/saucedemo/src/blocks/saucedemo-web/inventory/`
82
+ (`inventory.page.block.ts` + `methods/` + `InventorySel`). Bulk `add-all-to-cart` /
83
+ `remove-all-from-cart` are one menu row each (`from: "*"` — works after leave/return).
84
+
31
85
 
32
86
  ## Getting started (pick one)
33
87
 
@@ -172,24 +226,16 @@ a fresh tab (the run won't close a page you handed it unless you say so), and
172
226
  so the caller can keep driving it - hands a run tab back instead of losing it. See
173
227
  "Recipes" for capture-on-popup, which stays a documented pattern until a second real use
174
228
  case promotes it to an engine API.
175
- `defineNavBlock({ name, checkpoint, url })` or `defineNavBlock({ name, checkpoint, click })`
176
- builds a Block whose only possible action is navigating - exactly one of `url`/`click` is
177
- required, enforced at compile time. Both accept a plain string or `(mem) => string`/
178
- `(mem) => selector` for parameterized routes; the generated `act()` is always exactly
179
- `page.goto(url)` or `page.locator(click).click()`, never author-supplied. `click` is the
180
- opinionated default - navigating by clicking a real element already on the page (a nav
181
- link, a sidebar item) instead of teleporting straight to a URL, so a Block actually proves
182
- the app's own navigation path works instead of skipping past it. `url` still exists for the
183
- real cases with no click path to get there (a deep link from an email, a public share
184
- link). A
185
- regular `defineBlock`'s `act()` receives its `page` typed as `ActionPage` instead of raw
186
- `Page` - structurally identical, every method still fully present and callable, except
187
- `goto`/`reload`/`goBack`/`goForward` are re-declared `@deprecated`, so a TypeScript-aware
188
- editor shows navigation struck through the instant it's typed in a regular Block, pointing
189
- at `defineNavBlock` - nothing is blocked, no build ever fails, existing code that already
190
- navigates from a regular Block keeps compiling and running unchanged. `waygraph check
191
- [project]` is the complementary whole-project sweep for contexts with no editor watching
192
- (CI, generated code) - same warning, on demand, across every `*.block.ts` file at once.
229
+ `defineNavBlock({ name, checkpoint, url })` or `defineNavClickBlock({ name, checkpoint, click })`
230
+ builds a Block whose only possible action is navigating. Prefer `defineNavClickBlock` for
231
+ click-nav in app code; `defineNavBlock` for `url` / `goto` deep links. Both accept a plain
232
+ string or `(mem) => string` / `(mem) => selector`. Generated `act()` is always exactly
233
+ `page.goto(url)` or `page.locator(click).click()`. For screen hubs that also register
234
+ methods, prefer `definePageBlock` (see **0.9.0 — Page, Method, Sel** above). A regular
235
+ `defineBlock` / `defineMethodBlock`'s `act()` receives `page` typed as `ActionPage` —
236
+ `goto`/`reload`/`goBack`/`goForward` are `@deprecated` there (editor strike-through pointing
237
+ at Nav/Page helpers). `waygraph check [project]` is the complementary whole-project sweep
238
+ for contexts with no editor watching (CI, generated code).
193
239
  `new Engine({ browsers: { chromium, firefox, webkit } })` overrides which `BrowserType`
194
240
  actually launches for a `mem`-only run, per browser name - waygraph is deliberately "just
195
241
  an opinionated Playwright," so a stealth-patched or otherwise customized launcher (e.g.
@@ -216,33 +262,20 @@ Deliberately **not yet implemented** (tracked on the project board):
216
262
  Mermaid) is `waygraph graph [project]` (`--mermaid`). Unattended JSON execution is
217
263
  `chain` + `WAYGRAPH_JSON=1`.
218
264
 
219
- **Friendly demo run:** `waygraph demo <flow> [project]` defaults to **STEP on +
220
- autoplay off** (manual "Run this step" / "Next"). Flags beat env:
265
+ **Friendly demo / run / auto (0.10.0 — less is more):**
221
266
 
222
267
  | Want | Command |
223
268
  |------|---------|
224
- | Manual watch | `waygraph demo shopFlow` |
225
- | Auto-advance | `waygraph demo shopFlow --autoplay` |
226
- | Same overlay on a chain | `waygraph chain "login then nav-cart" --step` |
227
- | No overlay | `waygraph demo shopFlow --no-step` |
228
-
229
- `--step` = headed overlay. `--autoplay` = panel Auto-advance starts checked (timer;
230
- flip mid-run; manual click always wins). `chain` does **not** default to step — pass
231
- `--step`. BASE_URL from `--base-url`, else `WAYGRAPH_BASE_URL`, else
232
- `package.json` `waygraph.baseUrl`, else `playwright.config` `baseURL`. Prefer flags
233
- (or `npm run demo*`) over a pile of `WAYGRAPH_*` vars. NavBlock `click` shows demo
234
- cursor travel + pulse before the real click; `url` NavBlocks still `goto`. Full page:
235
- https://deviate-dv8.github.io/waygraph/demo.html
236
-
237
- **Flow video (headless OK):** `waygraph chain "loginFlow({...}) then viewerBlockedFlow({...})" --video`
238
- uses Playwright `recordVideo` and prints the `.webm` path when done (default dir:
239
- `.waygraph-videos/`). Lighter than help-center-clip-engine (no overlay/ffmpeg pipeline).
240
- Same flag works on `waygraph demo`. Programmatic: `Engine({ recordVideo: { dir: "./videos" } })`.
241
-
242
- **Project hygiene:** `waygraph check` warns on nav outside NavBlocks **and** lists
243
- **orphan Blocks** (a `*.block.ts` export not wired into any `.flow.ts` defineFlow array).
244
- Zero orphans is required before `waygraph chain auto <fromCheckpoint> <toCheckpoint>`
245
- builds a chain spec from the `waygraph auto` graph.
269
+ | Manual watch | `waygraph demo --blocks shopFlow` |
270
+ | Auto-advance | `waygraph demo --blocks shopFlow --auto-next` |
271
+ | QA watch + record | `waygraph demo --blocks shopFlow --auto-play-video` |
272
+ | Ad-hoc Blocks | `waygraph run --blocks "login then nav-cart" --data '{…}'` |
273
+ | Headed execute | `waygraph run --blocks shopFlow --non-headless --video` |
274
+ | Explore | `waygraph auto` / `waygraph auto --cli` |
275
+ | Path-find | `waygraph auto --blocks LoginPage OrderComplete` |
276
+
277
+ `--auto-next` (alias `--autoplay`) = panel Auto-advance. `--auto-play-video` is **demo only**.
278
+ `chain` remains a compat alias for `run --blocks` / `auto --blocks`.
246
279
 
247
280
  ## Example
248
281
 
@@ -361,17 +394,22 @@ npm run build # tsc -p tsconfig.build.json, emits dist/
361
394
  ## Layout
362
395
 
363
396
  ```
364
- package.json the "waygraph" npm package itself
397
+ package.json the "waygraph" npm package itself (0.9.0+)
365
398
  src/
366
399
  types.ts Checkpoint, Instruction, Block, connect()
367
400
  mem-page.ts MemKey, key(), MemPage
368
401
  trait.ts Trait (type + discoverable Trait.url/.text/.visible), runVerify()
369
- engine.ts runGraph(), spawnTab(), composeBlock(), Engine, start, end, Flow
402
+ engine.ts runGraph(), definePageBlock, defineMethodBlock, Engine, …
403
+ graph.ts discoverGraph, orphans, paths
404
+ auto-explore.ts waygraph auto menu
370
405
  index.ts public barrel
371
- typecheck/ compile-time-only fixtures (assignability, @ts-expect-error cases)
406
+ examples/saucedemo/ live Sauce Demo (Page + methods/ + Sel)
407
+ templates/quickstart/ try demo / init template (mirrors sauce)
408
+ docs/ GitHub Pages static HTML
409
+ typecheck/ compile-time-only fixtures
372
410
  tests/ runtime unit + integration tests (@playwright/test)
373
- tsconfig.json base config - typecheck script includes src/ + typecheck/ + tests/
374
- tsconfig.build.json extends base, src/ only, emits to dist/
411
+ tsconfig.json base config
412
+ tsconfig.build.json emits to dist/
375
413
  ```
376
414
 
377
415
  In-package Sauce Demo: [`examples/saucedemo`](./examples/saucedemo). Other consumer
@@ -84,7 +84,7 @@ function defaultMemValueForKey(keyName) {
84
84
  return undefined;
85
85
  }
86
86
  function seedMemFromEnv(mem, entry) {
87
- const raw = process.env.WAYGRAPH_AUTO_MEM;
87
+ const raw = process.env.WAYGRAPH_DATA ?? process.env.WAYGRAPH_AUTO_MEM;
88
88
  if (!raw)
89
89
  return false;
90
90
  try {
@@ -99,9 +99,9 @@ function seedMemFromEnv(mem, entry) {
99
99
  return false;
100
100
  }
101
101
  }
102
- /** Pre-seed known demo keys (saucedemo creds) so headful does not stop for mem forms. */
102
+ /** Pre-seed known demo keys (saucedemo creds) so headful/cli do not stop for mem forms. */
103
103
  function seedDefaultMem(library, mem) {
104
- const raw = process.env.WAYGRAPH_AUTO_MEM;
104
+ const raw = process.env.WAYGRAPH_DATA ?? process.env.WAYGRAPH_AUTO_MEM;
105
105
  let envParsed = {};
106
106
  if (raw) {
107
107
  try {
@@ -151,7 +151,7 @@ async function ensureMem(entry, mem, cli) {
151
151
  await promptMemCli(entry, mem);
152
152
  return;
153
153
  }
154
- throw new Error(`Block "${entry.block.name}" needs MemPage keys - set WAYGRAPH_AUTO_MEM JSON or use --cli to enter values`);
154
+ throw new Error(`Block "${entry.block.name}" needs MemPage keys - pass --data '{...}' / WAYGRAPH_DATA (or WAYGRAPH_AUTO_MEM), or enter values in --cli`);
155
155
  }
156
156
  async function setAutoPanelRunning(page, label) {
157
157
  await page
@@ -447,7 +447,7 @@ function printCliMenu(menu, library) {
447
447
  }
448
448
  else {
449
449
  const desc = entry?.description ? ` - ${entry.description}` : "";
450
- console.log(` [${n}] ${c.block} (${c.kind}) → ${c.to}${desc}`);
450
+ console.log(` [${n}] ${c.block} (${c.kind}) -> ${c.to}${desc}`);
451
451
  }
452
452
  }
453
453
  }
@@ -532,7 +532,11 @@ export async function runAutoExplore(projectDir, options = {}) {
532
532
  const pickWait = armPickWait();
533
533
  pickSlot.resolve = pickWait.resolve;
534
534
  const here = await detectHere(page, library.navBlocks);
535
+ if (cli)
536
+ console.error("waygraph auto: scanning page for moves ...");
535
537
  const menu = await buildExploreMenu(page, graph, library, here);
538
+ if (cli)
539
+ console.error(`waygraph auto: ${menu.flat.length} move(s) available`);
536
540
  if (menu.flat.length === 0) {
537
541
  console.error(here
538
542
  ? `waygraph auto: no runnable Blocks on "${here}" - nothing on this page matches the graph.`
@@ -568,10 +572,12 @@ export async function runAutoExplore(projectDir, options = {}) {
568
572
  else {
569
573
  await ensureMem(entry, mem, cli);
570
574
  }
575
+ const pickLabel = edge.label ?? entry.block.name;
576
+ console.error(`waygraph auto: running [${pick.index + 1}] ${pickLabel} ...`);
571
577
  await runOneBlock(engine, entry, context, page, mem, !cli);
572
578
  const landed = await detectHere(page, library.navBlocks);
573
- lastRunNote = `${entry.block.name} → ${landed ?? edge.to}`;
574
- console.error(`waygraph auto: ran ${entry.block.name} → ${landed ?? edge.to}`);
579
+ lastRunNote = `${entry.block.name} -> ${landed ?? edge.to}`;
580
+ console.error(`waygraph auto: ran ${entry.block.name} -> ${landed ?? edge.to}`);
575
581
  }
576
582
  catch (err) {
577
583
  lastRunNote = `${entry.block.name} failed`;
@@ -37,7 +37,8 @@ function isBlockLike(val) {
37
37
  return instruction !== null && typeof instruction === "object" && typeof instruction?.act === "function";
38
38
  }
39
39
  function isNavBlockMarked(val) {
40
- return val.__waygraphKind === "nav";
40
+ const kind = val.__waygraphKind;
41
+ return kind === "nav" || kind === "page";
41
42
  }
42
43
  function isUrlNav(entry) {
43
44
  return entry.block.__waygraphNavClick === undefined;
@@ -52,7 +53,8 @@ async function navRunnable(page, entry) {
52
53
  if (typeof click !== "string")
53
54
  return false;
54
55
  try {
55
- return await page.locator(click).first().isVisible({ timeout: 800 });
56
+ // Short timeout: menu scan must stay snappy (many edges miss on this page).
57
+ return await page.locator(click).first().isVisible({ timeout: 150 });
56
58
  }
57
59
  catch {
58
60
  return false;
@@ -61,14 +63,19 @@ async function navRunnable(page, entry) {
61
63
  async function wildcardActionRunnable(page, entry, here) {
62
64
  if (here === null)
63
65
  return false;
64
- if (entry.block.name === "add-to-cart") {
65
- try {
66
- return await page.locator("#add-to-cart-sauce-labs-backpack").first().isVisible({ timeout: 800 });
66
+ const name = entry.block.name;
67
+ // Bulk / non-instance * actions: show when matching live buttons exist.
68
+ try {
69
+ if (name === "add-all-to-cart" || name === "add-to-cart") {
70
+ return await page.locator('button[data-test^="add-to-cart-"]').first().isVisible({ timeout: 150 });
67
71
  }
68
- catch {
69
- return false;
72
+ if (name === "remove-all-from-cart" || name === "remove-from-cart") {
73
+ return await page.locator('button[data-test^="remove-"]').first().isVisible({ timeout: 150 });
70
74
  }
71
75
  }
76
+ catch {
77
+ return false;
78
+ }
72
79
  return false;
73
80
  }
74
81
  /**
@@ -139,27 +146,40 @@ export async function buildExploreMenu(page, graph, library, here) {
139
146
  const navigate = [];
140
147
  const back = [];
141
148
  const seen = new Set();
149
+ // Union Out tags create duplicate edges for the same Block - cache instanceOptions
150
+ // so we only scrape the DOM once per Block name per menu build.
151
+ const instanceOptionsCache = new Map();
142
152
  const push = (bucket, edge) => {
143
153
  if (seen.has(edge.block))
144
154
  return;
145
155
  seen.add(edge.block);
146
156
  bucket.push(edge);
147
157
  };
158
+ async function loadInstanceOptions(entry) {
159
+ const fn = entry.block.instanceOptions;
160
+ if (!fn)
161
+ return [];
162
+ const cached = instanceOptionsCache.get(entry.block.name);
163
+ if (cached)
164
+ return cached;
165
+ let options = [];
166
+ try {
167
+ options = await fn(page);
168
+ }
169
+ catch {
170
+ options = [];
171
+ }
172
+ instanceOptionsCache.set(entry.block.name, options);
173
+ return options;
174
+ }
148
175
  for (const edge of graph.edges) {
149
176
  const entry = library.byName.get(edge.block);
150
177
  if (!entry)
151
178
  continue;
152
179
  if (edge.kind === "action") {
153
180
  if (here !== null && edge.from === here) {
154
- const instanceOptionsFn = entry.block.instanceOptions;
155
- if (instanceOptionsFn) {
156
- let options = [];
157
- try {
158
- options = await instanceOptionsFn(page);
159
- }
160
- catch {
161
- options = [];
162
- }
181
+ if (entry.block.instanceOptions) {
182
+ const options = await loadInstanceOptions(entry);
163
183
  for (const option of options) {
164
184
  const seenKey = `${edge.block}::${option.id}`;
165
185
  if (seen.has(seenKey))
@@ -177,15 +197,8 @@ export async function buildExploreMenu(page, graph, library, here) {
177
197
  }
178
198
  }
179
199
  else if (here !== null && edge.from === "*") {
180
- const instanceOptionsFn = entry.block.instanceOptions;
181
- if (instanceOptionsFn) {
182
- let options = [];
183
- try {
184
- options = await instanceOptionsFn(page);
185
- }
186
- catch {
187
- options = [];
188
- }
200
+ if (entry.block.instanceOptions) {
201
+ const options = await loadInstanceOptions(entry);
189
202
  for (const option of options) {
190
203
  const seenKey = `${edge.block}::${option.id}`;
191
204
  if (seen.has(seenKey))
@@ -195,7 +208,12 @@ export async function buildExploreMenu(page, graph, library, here) {
195
208
  }
196
209
  }
197
210
  else if (await wildcardActionRunnable(page, entry, here)) {
198
- push(anywhere, edge);
211
+ const bulkLabel = {
212
+ "add-all-to-cart": "Add all to cart",
213
+ "remove-all-from-cart": "Remove all from cart",
214
+ };
215
+ const label = bulkLabel[entry.block.name] ?? (entry.description?.trim() || undefined);
216
+ push(anywhere, label ? { ...edge, label } : edge);
199
217
  }
200
218
  }
201
219
  continue;
@@ -203,15 +221,8 @@ export async function buildExploreMenu(page, graph, library, here) {
203
221
  // Nav: MemNavBlock (instanceOptions) expands one row per live target;
204
222
  // plain NavBlock still uses click-visibility (navRunnable).
205
223
  if (here !== null && (edge.from === here || edge.from === "*") && edge.to !== here) {
206
- const instanceOptionsFn = entry.block.instanceOptions;
207
- if (instanceOptionsFn) {
208
- let options = [];
209
- try {
210
- options = await instanceOptionsFn(page);
211
- }
212
- catch {
213
- options = [];
214
- }
224
+ if (entry.block.instanceOptions) {
225
+ const options = await loadInstanceOptions(entry);
215
226
  const bucket = isBackEdge(edge) ? back : navigate;
216
227
  for (const option of options) {
217
228
  const seenKey = `${edge.block}::${option.id}`;
@@ -249,7 +260,7 @@ export async function buildExploreMenu(page, graph, library, here) {
249
260
  else {
250
261
  if (forward.length > 0) {
251
262
  sections.push({
252
- title: `Forward from ${here}`,
263
+ title: `Methods from ${here}`,
253
264
  edges: forward.sort((a, b) => a.block.localeCompare(b.block)),
254
265
  });
255
266
  }
package/dist/cli.d.ts CHANGED
@@ -2,18 +2,14 @@
2
2
  /**
3
3
  * waygraph CLI -- tooling around this same package's engine.
4
4
  *
5
- * Commands (watch first, then run, then inspect, then scaffold):
6
- * demo <flow|spec> [project] Watch a flow/chain with step overlay (default STEP+headed)
7
- * try [demo|auto] One-shot saucedemo in an OS temp dir (demo=stepper, auto=explore)
8
- * chain <spec> [project] Ad-hoc Blocks by name (+ --step/--autoplay like demo)
9
- * run <flow> [project] Run a named flow (no overlay unless --step)
10
- * list / nav / validate Discover and inspect flows
11
- * check / auto / graph Hygiene + state graph
12
- * init <name> Offline scaffold
5
+ * Primary verbs (less is more):
6
+ * auto Explore picker; `--blocks From To` = graph path-find + run
7
+ * demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--auto-play-video`
8
+ * run Execute; `--blocks` `--data` `--non-headless` `--video`
13
9
  *
14
- * "project" defaults to the current directory.
15
- * A project is any directory containing *.flow.ts files (typically under src/flows/).
16
- * Run flags (--step/--autoplay/--base-url/--title/--video) beat WAYGRAPH_* env when present.
17
- * demo defaults: --step on, --autoplay off (manual "Run this step" / "Next").
10
+ * Also: list / nav / validate / check / graph / init / try
11
+ * Aliases (one release): `chain` -> run/demo --blocks; `--autoplay` -> `--auto-next`
12
+ *
13
+ * "project" defaults to cwd. Flags beat WAYGRAPH_* env.
18
14
  */
19
15
  export {};