waygraph 0.13.7 → 0.14.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 (297) hide show
  1. package/README.md +820 -232
  2. package/assets/stubs/stub.mp4 +0 -0
  3. package/assets/stubs/stub.pdf +32 -0
  4. package/assets/stubs/stub.png +0 -0
  5. package/dist/agent-dive.d.ts +9 -0
  6. package/dist/agent-dive.js +32 -0
  7. package/dist/auto-explore-run.d.ts +20 -2
  8. package/dist/auto-explore-run.js +9 -8
  9. package/dist/auto-explore.js +22 -2
  10. package/dist/auto-session-ipc.d.ts +123 -0
  11. package/dist/auto-session-ipc.js +381 -0
  12. package/dist/auto-session.d.ts +342 -0
  13. package/dist/auto-session.js +760 -0
  14. package/dist/cli.js +417 -51
  15. package/dist/engine.d.ts +40 -1
  16. package/dist/engine.js +82 -0
  17. package/dist/graph.d.ts +18 -0
  18. package/dist/graph.js +65 -2
  19. package/dist/highlights.d.ts +20 -1
  20. package/dist/highlights.js +60 -0
  21. package/dist/index.d.ts +8 -4
  22. package/dist/index.js +5 -3
  23. package/dist/pilot-overlay.d.ts +88 -0
  24. package/dist/pilot-overlay.js +363 -0
  25. package/dist/pilot.d.ts +49 -0
  26. package/dist/pilot.js +53 -0
  27. package/dist/trait.d.ts +22 -0
  28. package/dist/trait.js +60 -3
  29. package/examples/routed-demo/package.json +26 -0
  30. package/examples/routed-demo/playwright.config.ts +20 -0
  31. package/examples/routed-demo/scripts/fixture-server.mjs +37 -0
  32. package/examples/routed-demo/scripts/with-fixture.mjs +70 -0
  33. package/examples/routed-demo/src/flows/routed-demo.flow.ts +12 -0
  34. package/examples/routed-demo/src/pages/dashboard.html +10 -0
  35. package/examples/routed-demo/src/pages/docs.html +8 -0
  36. package/examples/routed-demo/src/routes/(base_app)/dashboard/dashboard.sel.ts +10 -0
  37. package/examples/routed-demo/src/routes/(base_app)/dashboard/methods/click-widget.block.ts +23 -0
  38. package/examples/routed-demo/src/routes/(base_app)/dashboard/nav.block.ts +18 -0
  39. package/examples/routed-demo/src/routes/(base_app)/dashboard/page.block.ts +22 -0
  40. package/examples/routed-demo/src/routes/(external)/docs/docs.sel.ts +7 -0
  41. package/examples/routed-demo/src/routes/(external)/docs/nav.block.ts +21 -0
  42. package/examples/routed-demo/src/routes/(external)/docs/page.block.ts +15 -0
  43. package/examples/routed-demo/src/states/routed-demo.states.ts +10 -0
  44. package/examples/routed-demo/tests/routed-demo.spec.ts +75 -0
  45. package/examples/routed-demo/tsconfig.json +18 -0
  46. package/examples/routed-demo-consumer/package.json +18 -0
  47. package/examples/saucedemo/.waygraph-auto/010dd92b.log +0 -0
  48. package/examples/saucedemo/.waygraph-auto/011ad671.log +0 -0
  49. package/examples/saucedemo/.waygraph-auto/01b45645.log +0 -0
  50. package/examples/saucedemo/.waygraph-auto/03764153.log +0 -0
  51. package/examples/saucedemo/.waygraph-auto/0593587e.log +0 -0
  52. package/examples/saucedemo/.waygraph-auto/06d12823.log +0 -0
  53. package/examples/saucedemo/.waygraph-auto/0b1f5b26.json +8 -0
  54. package/examples/saucedemo/.waygraph-auto/0c00f25f.log +0 -0
  55. package/examples/saucedemo/.waygraph-auto/0e480591.log +0 -0
  56. package/examples/saucedemo/.waygraph-auto/110958b8.log +0 -0
  57. package/examples/saucedemo/.waygraph-auto/138107c1.log +0 -0
  58. package/examples/saucedemo/.waygraph-auto/159d541b.log +0 -0
  59. package/examples/saucedemo/.waygraph-auto/16aa2c01.log +0 -0
  60. package/examples/saucedemo/.waygraph-auto/16c35dd5.log +0 -0
  61. package/examples/saucedemo/.waygraph-auto/1706547d.log +0 -0
  62. package/examples/saucedemo/.waygraph-auto/17537e42.log +0 -0
  63. package/examples/saucedemo/.waygraph-auto/17ede493.log +0 -0
  64. package/examples/saucedemo/.waygraph-auto/1fe2414f.log +0 -0
  65. package/examples/saucedemo/.waygraph-auto/1fe2e3b8.log +0 -0
  66. package/examples/saucedemo/.waygraph-auto/25f68ada.log +0 -0
  67. package/examples/saucedemo/.waygraph-auto/26ca9cd8.log +0 -0
  68. package/examples/saucedemo/.waygraph-auto/2bb6c4e8.log +0 -0
  69. package/examples/saucedemo/.waygraph-auto/2d425004.log +0 -0
  70. package/examples/saucedemo/.waygraph-auto/2f5b3ea6.log +0 -0
  71. package/examples/saucedemo/.waygraph-auto/31bb303d.log +0 -0
  72. package/examples/saucedemo/.waygraph-auto/31f2042f.log +0 -0
  73. package/examples/saucedemo/.waygraph-auto/36551ec1.log +0 -0
  74. package/examples/saucedemo/.waygraph-auto/36a1c274.log +0 -0
  75. package/examples/saucedemo/.waygraph-auto/381aa338.log +0 -0
  76. package/examples/saucedemo/.waygraph-auto/3882bce1.log +0 -0
  77. package/examples/saucedemo/.waygraph-auto/38acd1dc.log +0 -0
  78. package/examples/saucedemo/.waygraph-auto/3b8b77b9.log +0 -0
  79. package/examples/saucedemo/.waygraph-auto/4028d2a2.log +0 -0
  80. package/examples/saucedemo/.waygraph-auto/4168b208.log +0 -0
  81. package/examples/saucedemo/.waygraph-auto/43ab66ce.log +0 -0
  82. package/examples/saucedemo/.waygraph-auto/43f66838.log +0 -0
  83. package/examples/saucedemo/.waygraph-auto/442171d4.log +0 -0
  84. package/examples/saucedemo/.waygraph-auto/453b1ce1.log +0 -0
  85. package/examples/saucedemo/.waygraph-auto/457322bd.log +0 -0
  86. package/examples/saucedemo/.waygraph-auto/46197027.log +0 -0
  87. package/examples/saucedemo/.waygraph-auto/46889468.log +0 -0
  88. package/examples/saucedemo/.waygraph-auto/4890da1c.log +0 -0
  89. package/examples/saucedemo/.waygraph-auto/4893a63b.log +0 -0
  90. package/examples/saucedemo/.waygraph-auto/4af71440.log +0 -0
  91. package/examples/saucedemo/.waygraph-auto/4f7496d1.log +0 -0
  92. package/examples/saucedemo/.waygraph-auto/50e8baaa.log +0 -0
  93. package/examples/saucedemo/.waygraph-auto/52b3a455.log +0 -0
  94. package/examples/saucedemo/.waygraph-auto/532dd9b8.log +0 -0
  95. package/examples/saucedemo/.waygraph-auto/5391ce40.log +0 -0
  96. package/examples/saucedemo/.waygraph-auto/57dbddb3.log +0 -0
  97. package/examples/saucedemo/.waygraph-auto/5897f501.log +0 -0
  98. package/examples/saucedemo/.waygraph-auto/59c0b57d.log +0 -0
  99. package/examples/saucedemo/.waygraph-auto/5b511794.log +0 -0
  100. package/examples/saucedemo/.waygraph-auto/5c47910f.log +0 -0
  101. package/examples/saucedemo/.waygraph-auto/5eda8273.log +0 -0
  102. package/examples/saucedemo/.waygraph-auto/621518ad.log +0 -0
  103. package/examples/saucedemo/.waygraph-auto/632b7118.log +0 -0
  104. package/examples/saucedemo/.waygraph-auto/64314c2b.log +0 -0
  105. package/examples/saucedemo/.waygraph-auto/66516213.log +0 -0
  106. package/examples/saucedemo/.waygraph-auto/66c17079.log +0 -0
  107. package/examples/saucedemo/.waygraph-auto/69bf0b80.log +0 -0
  108. package/examples/saucedemo/.waygraph-auto/69c2347a.log +0 -0
  109. package/examples/saucedemo/.waygraph-auto/6d22bdb0.log +0 -0
  110. package/examples/saucedemo/.waygraph-auto/7103bebb.log +0 -0
  111. package/examples/saucedemo/.waygraph-auto/740c9faa.log +0 -0
  112. package/examples/saucedemo/.waygraph-auto/74872fc5.log +0 -0
  113. package/examples/saucedemo/.waygraph-auto/75846ca9.log +0 -0
  114. package/examples/saucedemo/.waygraph-auto/7626c85b.log +0 -0
  115. package/examples/saucedemo/.waygraph-auto/76308eec.log +0 -0
  116. package/examples/saucedemo/.waygraph-auto/76c1650e.log +0 -0
  117. package/examples/saucedemo/.waygraph-auto/7861f38f.log +0 -0
  118. package/examples/saucedemo/.waygraph-auto/7af224ed.log +0 -0
  119. package/examples/saucedemo/.waygraph-auto/7c3be585.log +0 -0
  120. package/examples/saucedemo/.waygraph-auto/7ed55a2c.log +0 -0
  121. package/examples/saucedemo/.waygraph-auto/80086ba2.log +0 -0
  122. package/examples/saucedemo/.waygraph-auto/80206369.log +0 -0
  123. package/examples/saucedemo/.waygraph-auto/847f1092.log +0 -0
  124. package/examples/saucedemo/.waygraph-auto/8581abfa.log +0 -0
  125. package/examples/saucedemo/.waygraph-auto/87b7e0fd.log +0 -0
  126. package/examples/saucedemo/.waygraph-auto/87c79256.log +0 -0
  127. package/examples/saucedemo/.waygraph-auto/88af2495.log +0 -0
  128. package/examples/saucedemo/.waygraph-auto/8c04a403.log +0 -0
  129. package/examples/saucedemo/.waygraph-auto/8c9ab09e.log +0 -0
  130. package/examples/saucedemo/.waygraph-auto/8cc5db51.log +0 -0
  131. package/examples/saucedemo/.waygraph-auto/8ea3a69c.log +0 -0
  132. package/examples/saucedemo/.waygraph-auto/9150f913.log +0 -0
  133. package/examples/saucedemo/.waygraph-auto/929a4313.log +0 -0
  134. package/examples/saucedemo/.waygraph-auto/95a599e4.log +0 -0
  135. package/examples/saucedemo/.waygraph-auto/96c45350.log +0 -0
  136. package/examples/saucedemo/.waygraph-auto/986030e6.log +0 -0
  137. package/examples/saucedemo/.waygraph-auto/99787abb.log +0 -0
  138. package/examples/saucedemo/.waygraph-auto/99ddb62c.log +0 -0
  139. package/examples/saucedemo/.waygraph-auto/a63a2d20.log +0 -0
  140. package/examples/saucedemo/.waygraph-auto/a78befb7.log +0 -0
  141. package/examples/saucedemo/.waygraph-auto/a84d9b79.log +0 -0
  142. package/examples/saucedemo/.waygraph-auto/a9270890.log +0 -0
  143. package/examples/saucedemo/.waygraph-auto/aa440153.log +0 -0
  144. package/examples/saucedemo/.waygraph-auto/ab7561e0.log +0 -0
  145. package/examples/saucedemo/.waygraph-auto/ac19ae60.log +0 -0
  146. package/examples/saucedemo/.waygraph-auto/ae21ad8e.log +0 -0
  147. package/examples/saucedemo/.waygraph-auto/af09042f.log +0 -0
  148. package/examples/saucedemo/.waygraph-auto/b1684126.log +0 -0
  149. package/examples/saucedemo/.waygraph-auto/b21e9d12.log +0 -0
  150. package/examples/saucedemo/.waygraph-auto/b5a838be.log +0 -0
  151. package/examples/saucedemo/.waygraph-auto/b603549d.log +0 -0
  152. package/examples/saucedemo/.waygraph-auto/b83e6259.log +0 -0
  153. package/examples/saucedemo/.waygraph-auto/b9cb2796.log +0 -0
  154. package/examples/saucedemo/.waygraph-auto/bb0a0018.log +0 -0
  155. package/examples/saucedemo/.waygraph-auto/bc0bdd0e.log +0 -0
  156. package/examples/saucedemo/.waygraph-auto/bc7d4a83.log +0 -0
  157. package/examples/saucedemo/.waygraph-auto/bd8e082a.log +0 -0
  158. package/examples/saucedemo/.waygraph-auto/bea0c93e.log +0 -0
  159. package/examples/saucedemo/.waygraph-auto/bf35201a.log +0 -0
  160. package/examples/saucedemo/.waygraph-auto/bffa3a7a.log +0 -0
  161. package/examples/saucedemo/.waygraph-auto/c4afb8bc.log +0 -0
  162. package/examples/saucedemo/.waygraph-auto/c916a654.log +0 -0
  163. package/examples/saucedemo/.waygraph-auto/c9b8d69e.log +0 -0
  164. package/examples/saucedemo/.waygraph-auto/d02e5e7e.log +0 -0
  165. package/examples/saucedemo/.waygraph-auto/d09aff71.log +0 -0
  166. package/examples/saucedemo/.waygraph-auto/d0e41396.log +0 -0
  167. package/examples/saucedemo/.waygraph-auto/d1370d76.log +0 -0
  168. package/examples/saucedemo/.waygraph-auto/d3714ae4.log +0 -0
  169. package/examples/saucedemo/.waygraph-auto/da0685dc.log +0 -0
  170. package/examples/saucedemo/.waygraph-auto/da894ad8.log +0 -0
  171. package/examples/saucedemo/.waygraph-auto/daa2f922.log +0 -0
  172. package/examples/saucedemo/.waygraph-auto/daefb614.log +0 -0
  173. package/examples/saucedemo/.waygraph-auto/db97e833.log +0 -0
  174. package/examples/saucedemo/.waygraph-auto/e101d19a.log +0 -0
  175. package/examples/saucedemo/.waygraph-auto/e207c989.log +0 -0
  176. package/examples/saucedemo/.waygraph-auto/e633e545.log +0 -0
  177. package/examples/saucedemo/.waygraph-auto/e69aec68.log +0 -0
  178. package/examples/saucedemo/.waygraph-auto/e70e2d06.log +0 -0
  179. package/examples/saucedemo/.waygraph-auto/ebad3410.log +0 -0
  180. package/examples/saucedemo/.waygraph-auto/ecbb0966.log +0 -0
  181. package/examples/saucedemo/.waygraph-auto/ecdee753.log +0 -0
  182. package/examples/saucedemo/.waygraph-auto/ed888783.log +0 -0
  183. package/examples/saucedemo/.waygraph-auto/ed931dac.log +0 -0
  184. package/examples/saucedemo/.waygraph-auto/ef655a3f.log +0 -0
  185. package/examples/saucedemo/.waygraph-auto/f3c29b21.log +0 -0
  186. package/examples/saucedemo/.waygraph-auto/f53f7cfc.log +0 -0
  187. package/examples/saucedemo/.waygraph-auto/f5ad5830.log +0 -0
  188. package/examples/saucedemo/.waygraph-auto/faed36dd.log +0 -0
  189. package/examples/saucedemo/docs/CONVENTION.md +25 -5
  190. package/examples/saucedemo/docs/HELPERS.md +15 -4
  191. package/examples/saucedemo/scripts/capture-wrong-login.mjs +8 -2
  192. package/examples/saucedemo/scripts/prove-wrong-login-runtime.mjs +8 -2
  193. package/examples/saucedemo/src/blocks/SITE-MAP.md +14 -3
  194. package/examples/saucedemo/src/blocks/saucedemo-web/NAV.md +15 -1
  195. package/examples/saucedemo/src/blocks/saucedemo-web/cart/NAV.md +1 -0
  196. package/examples/saucedemo/src/blocks/saucedemo-web/cart/cart.sel.ts +9 -0
  197. package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-cart.block.ts +3 -2
  198. package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-checkout-info.block.ts +2 -1
  199. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/NAV.md +6 -1
  200. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/methods/checkout-info.sel.ts +11 -0
  201. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/methods/fill-first-name.method.block.ts +22 -0
  202. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/methods/fill-last-name.method.block.ts +21 -0
  203. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/methods/fill-postal-code.method.block.ts +21 -0
  204. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.ts +6 -7
  205. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/NAV.md +2 -0
  206. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/checkout-overview.sel.ts +9 -0
  207. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +7 -6
  208. package/examples/saucedemo/src/blocks/saucedemo-web/ff-owner-auth.block.ts +7 -2
  209. package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/NAV.md +1 -0
  210. package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/item-detail.sel.ts +9 -0
  211. package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-back-to-inventory.block.ts +2 -1
  212. package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-back-to-products.block.ts +2 -1
  213. package/examples/saucedemo/src/blocks/saucedemo-web/inventory-item/nav-continue-shopping.block.ts +2 -1
  214. package/examples/saucedemo/src/blocks/saucedemo-web/methods/fill-password.method.block.ts +51 -0
  215. package/examples/saucedemo/src/blocks/saucedemo-web/methods/fill-username.method.block.ts +69 -0
  216. package/examples/saucedemo/src/blocks/saucedemo-web/methods/login.sel.ts +13 -0
  217. package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login-for-flow.ts +4 -4
  218. package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +13 -51
  219. package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-logout.method.block.ts +4 -3
  220. package/examples/saucedemo/src/blocks/saucedemo-web/nav-login.block.ts +14 -6
  221. package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +15 -0
  222. package/examples/saucedemo/src/flows/checkout.flow.ts +20 -2
  223. package/examples/saucedemo/src/flows/device-showcase.flow.ts +4 -0
  224. package/examples/saucedemo/src/flows/login.flow.ts +3 -1
  225. package/examples/saucedemo/src/flows/shop.flow.ts +6 -0
  226. package/examples/saucedemo/src/flows/smoke.flow.ts +13 -7
  227. package/examples/saucedemo/src/flows/stub-on-error.flow.ts +4 -2
  228. package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +6 -2
  229. package/examples/saucedemo/tests/flow-block-verify.spec.ts +3 -3
  230. package/examples/saucedemo/tests/submit-login-branch.spec.ts +5 -3
  231. package/package.json +2 -1
  232. package/templates/agents/waygraph-author.agent.md +43 -5
  233. package/templates/agents/waygraph-planner.agent.md +33 -2
  234. package/templates/quickstart/src/blocks/saucedemo-web/NAV.md +15 -1
  235. package/templates/quickstart/src/blocks/saucedemo-web/cart/NAV.md +1 -0
  236. package/templates/quickstart/src/blocks/saucedemo-web/cart/cart.sel.ts +9 -0
  237. package/templates/quickstart/src/blocks/saucedemo-web/cart/nav-cart.block.ts +19 -15
  238. package/templates/quickstart/src/blocks/saucedemo-web/cart/nav-checkout-info.block.ts +2 -1
  239. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/NAV.md +6 -1
  240. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/methods/checkout-info.sel.ts +11 -0
  241. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/methods/fill-first-name.method.block.ts +22 -0
  242. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/methods/fill-last-name.method.block.ts +21 -0
  243. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/methods/fill-postal-code.method.block.ts +21 -0
  244. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.ts +6 -7
  245. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/NAV.md +2 -0
  246. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/checkout-overview.sel.ts +9 -0
  247. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +7 -6
  248. package/templates/quickstart/src/blocks/saucedemo-web/ff-owner-auth.block.ts +7 -2
  249. package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +24 -16
  250. package/templates/quickstart/src/blocks/saucedemo-web/inventory/nav-item-detail.block.ts +3 -3
  251. package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/NAV.md +1 -0
  252. package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/item-detail.sel.ts +9 -0
  253. package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-back-to-inventory.block.ts +2 -1
  254. package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-back-to-products.block.ts +2 -1
  255. package/templates/quickstart/src/blocks/saucedemo-web/inventory-item/nav-continue-shopping.block.ts +2 -1
  256. package/templates/quickstart/src/blocks/saucedemo-web/methods/fill-password.method.block.ts +51 -0
  257. package/templates/quickstart/src/blocks/saucedemo-web/methods/fill-username.method.block.ts +69 -0
  258. package/templates/quickstart/src/blocks/saucedemo-web/methods/login.sel.ts +13 -0
  259. package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login-for-flow.ts +4 -4
  260. package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +35 -63
  261. package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-logout.method.block.ts +4 -3
  262. package/templates/quickstart/src/blocks/saucedemo-web/nav-login.block.ts +14 -6
  263. package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +15 -0
  264. package/templates/quickstart/src/flows/checkout.flow.ts +29 -2
  265. package/templates/quickstart/src/flows/device-showcase.flow.ts +4 -0
  266. package/templates/quickstart/src/flows/login.flow.ts +3 -1
  267. package/templates/quickstart/src/flows/shop.flow.ts +6 -0
  268. package/templates/quickstart/src/flows/smoke.flow.ts +13 -7
  269. package/templates/quickstart/src/flows/viewer-blocked.flow.ts +6 -2
  270. package/templates/quickstart/tests/chain-flow.spec.ts +8 -1
  271. package/templates/scaffold/STRUCTURE.md +23 -3
  272. package/templates/scaffold/src/blocks/demo-external/mailpit/mailpit-inbox.page.block.ts +30 -0
  273. package/templates/scaffold/src/blocks/demo-external/mailpit/mailpit-sel.ts +5 -0
  274. package/templates/scaffold/src/blocks/demo-external/mailpit/methods/assert-email-content.method.block.ts +48 -0
  275. package/templates/scaffold/src/blocks/demo-external/mailpit/methods/assert-email-received.method.block.ts +49 -0
  276. package/templates/scaffold/src/blocks/demo-external/mailpit/methods/extract-email-link.method.block.ts +41 -0
  277. package/templates/scaffold/src/blocks/demo-external/mailpit/methods/open-message.method.block.ts +39 -0
  278. package/templates/scaffold/src/blocks/demo-external/mailpit/nav-mailpit-inbox.block.ts +20 -0
  279. package/templates/scaffold/src/blocks/demo-web/demo-sel.ts +1 -0
  280. package/templates/scaffold/src/blocks/demo-web/docs.html +8 -0
  281. package/templates/scaffold/src/blocks/demo-web/home.html +4 -0
  282. package/templates/scaffold/src/blocks/demo-web/methods/assert-hello.method.block.ts +34 -38
  283. package/templates/scaffold/src/blocks/demo-web/methods/assert-item-added.method.block.ts +38 -0
  284. package/templates/scaffold/src/blocks/demo-web/nav-verification-link.block.ts +34 -0
  285. package/templates/scaffold/src/flows/mail-verify.flow.ts +85 -0
  286. package/templates/scaffold/src/flows/routes-demo.flow.ts +14 -0
  287. package/templates/scaffold/src/flows/shop.flow.ts +19 -2
  288. package/templates/scaffold/src/routes/(external)/docs/docs.sel.ts +7 -0
  289. package/templates/scaffold/src/routes/(external)/docs/nav.block.ts +21 -0
  290. package/templates/scaffold/src/routes/(external)/docs/page.block.ts +15 -0
  291. package/templates/scaffold/src/states/demo.mem-keys.ts +25 -1
  292. package/templates/scaffold/src/states/demo.states.ts +13 -0
  293. package/templates/scaffold/test-results/.last-run.json +4 -0
  294. package/templates/scaffold/tests/mail-verify.spec.ts +191 -0
  295. package/templates/skills/waygraph-blind-pilot.skill.md +61 -0
  296. package/templates/skills/waygraph-pilot.skill.md +108 -0
  297. package/templates/quickstart/README-saucedemo-commands.md +0 -72
@@ -1,6 +1,8 @@
1
1
  import { Engine, start, end } from "waygraph";
2
2
  import type { LoginPage, LoggedIn, ItemInCart } from "../states/checkout.states.js";
3
3
  import { NavLoginBlock } from "../blocks/saucedemo-web/nav-login.block.js";
4
+ import { FillUsernameBlock } from "../blocks/saucedemo-web/methods/fill-username.method.block.js";
5
+ import { FillPasswordBlock } from "../blocks/saucedemo-web/methods/fill-password.method.block.js";
4
6
  import { SubmitLoginForFlow } from "../blocks/saucedemo-web/methods/submit-login-for-flow.js";
5
7
  import { AddToCartBlock } from "../blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.js";
6
8
 
@@ -10,17 +12,21 @@ const engine = new Engine();
10
12
  // it doesn't need the same DOM confirmation the real checkout.flow.ts wants.
11
13
  // Same Blocks, same act/resolve, zero duplication - only verify differs.
12
14
  // .withVerify([]) reads from the Block itself, discoverable by typing
13
- // "NavLoginBlock."/"SubmitLoginActionBlock." in an editor - not a free function
15
+ // "NavLoginBlock."/"SubmitLoginForFlow." in an editor - not a free function
14
16
  // you have to already know exists.
15
17
  //
16
- // Explicit <LoginPage, LoggedIn, ItemInCart> here - AddToCartBlock's In is now
17
- // the wildcard Checkpoint<string> (it's reused as shopFlow's own entry block
18
- // too), and inferring B/C/D purely from three chained .withVerify([]) calls in
19
- // one array trips TS's overload resolution into unifying the wrong Out type
20
- // partway through; pinning the type args sidesteps it rather than fighting it.
21
- export const smokeFlow = engine.defineFlow<LoginPage, LoggedIn, ItemInCart>([
18
+ // Explicit <LoginPage, LoginPage, LoginPage, LoggedIn, ItemInCart> here -
19
+ // AddToCartBlock's In is now the wildcard Checkpoint<string> (it's reused as
20
+ // shopFlow's own entry block too), and inferring the chain purely from
21
+ // several chained .withVerify([]) calls in one array trips TS's overload
22
+ // resolution into unifying the wrong Out type partway through; pinning the
23
+ // type args sidesteps it rather than fighting it. The two middle `LoginPage`
24
+ // entries are fill-username / fill-password (self-loop Methods).
25
+ export const smokeFlow = engine.defineFlow<LoginPage, LoginPage, LoginPage, LoggedIn, ItemInCart>([
22
26
  start,
23
27
  NavLoginBlock.withVerify([]),
28
+ FillUsernameBlock,
29
+ FillPasswordBlock,
24
30
  SubmitLoginForFlow.withVerify([]),
25
31
  AddToCartBlock.withVerify([]),
26
32
  end,
@@ -1,6 +1,8 @@
1
1
  import { Engine, start, end, withTitle, withSessionReset, withExpectedFailure } from "waygraph";
2
2
  import { NavLoginBlock } from "../blocks/saucedemo-web/nav-login.block.js";
3
- import { SubmitLoginActionBlock } from "../blocks/saucedemo-web/methods/submit-login.method.block.js";
3
+ import { FillUsernameBlock } from "../blocks/saucedemo-web/methods/fill-username.method.block.js";
4
+ import { FillPasswordBlock } from "../blocks/saucedemo-web/methods/fill-password.method.block.js";
5
+ import { SubmitLoginBlock } from "../blocks/saucedemo-web/methods/submit-login.method.block.js";
4
6
 
5
7
  const engine = new Engine();
6
8
 
@@ -22,7 +24,9 @@ const engine = new Engine();
22
24
  // "expected outcome" panel (branching no longer throws).
23
25
  export const viewerBlockedFlow = withExpectedFailure(
24
26
  withTitle(
25
- withSessionReset(engine.defineFlow([start, NavLoginBlock, SubmitLoginActionBlock, end])),
27
+ withSessionReset(
28
+ engine.defineFlow([start, NavLoginBlock, FillUsernameBlock, FillPasswordBlock, SubmitLoginBlock, end]),
29
+ ),
26
30
  "Viewer: Blocked Login Attempt",
27
31
  ),
28
32
  "locked_out_user stays on LoginPage with the error banner - this is the product working as intended.",
@@ -38,16 +38,23 @@ test("chainFlow blocks() lists the flattened episode chain in order", () => {
38
38
  const blocks = chainFlow(loginFlow, shopFlow, viewerBlockedFlow).blocks();
39
39
  expect(blocks.map((b) => b.name)).toEqual([
40
40
  "nav-login",
41
+ "fill-username",
42
+ "fill-password",
41
43
  "submit-login",
42
44
  "add-to-cart",
43
45
  "nav-cart",
44
46
  "nav-checkout-info",
47
+ "fill-first-name",
48
+ "fill-last-name",
49
+ "fill-postal-code",
45
50
  "submit-checkout-info",
46
51
  "finish-order",
47
52
  "nav-login",
53
+ "fill-username",
54
+ "fill-password",
48
55
  "submit-login",
49
56
  ]);
50
- expect(blocks.every((b, i) => (i === 7 ? b.resetSessionBefore === true : !b.resetSessionBefore))).toBe(
57
+ expect(blocks.every((b, i) => (i === 12 ? b.resetSessionBefore === true : !b.resetSessionBefore))).toBe(
51
58
  true,
52
59
  );
53
60
  });
@@ -25,9 +25,16 @@ Docs: [docs/scaffold.html](../../docs/scaffold.html)
25
25
  │ ├── states/
26
26
  │ │ ├── demo.states.ts
27
27
  │ │ └── demo.mem-keys.ts # SelectedItem for Effect / auto
28
- │ └── flows/
29
- │ ├── example.flow.ts # nav + assert (smoke)
30
- │ └── shop.flow.ts # nav + add-item + clear (Effect path)
28
+ │ ├── flows/
29
+ │ │ ├── example.flow.ts # nav + assert (smoke)
30
+ │ │ ├── shop.flow.ts # nav + add-item + clear (Effect path)
31
+ │ │ └── routes-demo.flow.ts # Home (manual mode) -> Docs (Map convention)
32
+ │ └── routes/ # Waygraph Map convention - see README.md
33
+ │ └── (external)/
34
+ │ └── docs/
35
+ │ ├── docs.sel.ts
36
+ │ ├── nav.block.ts # fixed name - every page-slug folder's Nav
37
+ │ └── page.block.ts # fixed name - every page-slug folder's arrival hub
31
38
  ├── scripts/
32
39
  │ ├── fixture-server.mjs # serves home.html on :4177
33
40
  │ └── with-fixture.mjs # start server + run waygraph auto/demo/run
@@ -59,3 +66,16 @@ npm run auto:cli # same menus in the terminal
59
66
  ```
60
67
 
61
68
  After `nav-home`, Effect blocks expose one menu row per visible Add/Remove button.
69
+
70
+ ## Two authoring modes
71
+
72
+ `src/blocks/` (`demo-web/`, `demo-external/`) is **manual mode** - freeform, developer-chosen
73
+ file layout, the default this whole scaffold otherwise uses.
74
+
75
+ `src/routes/` is the **Waygraph Map** convention - `(group)/<page-slug>/page.block.ts` +
76
+ `nav.block.ts` + `methods/*.block.ts`, one Checkpoint per folder, `(group)` purely
77
+ organizational (parens, Next.js route-group style - never part of the Checkpoint tag). Same
78
+ Block helpers, same execution - only where files live and what they're named changes. A
79
+ project can adopt it folder by folder; `routes-demo.flow.ts` proves both modes compose in one
80
+ flow (`nav-home` from `blocks/`, `nav-docs` from `routes/`). See the main package's own
81
+ `README.md` "Waygraph Map" section and `openspec/changes/waygraph-map/`.
@@ -0,0 +1,30 @@
1
+ import { definePageBlock, Trait } from "waygraph";
2
+ import type { MailpitInbox } from "../../../states/demo.states.js";
3
+ import { AssertEmailReceivedBlock } from "./methods/assert-email-received.method.block.js";
4
+ import { OpenMessageBlock } from "./methods/open-message.method.block.js";
5
+ import { AssertEmailContentBlock } from "./methods/assert-email-content.method.block.js";
6
+ import { ExtractEmailLinkBlock } from "./methods/extract-email-link.method.block.js";
7
+
8
+ /**
9
+ * Kind: Page
10
+ * Helper: definePageBlock
11
+ * Route: demo-external/mailpit/ (= http://127.0.0.1:8025/ by default)
12
+ *
13
+ * Hub for the Mailpit dev-inbox screen. Arrival-only (no url/click) -
14
+ * nav-mailpit-inbox owns the goto. Methods: confirm the email arrived, open
15
+ * it, confirm its content, then read its link - all mem-driven (recipient,
16
+ * link pattern, expected content), so this one page's methods serve any
17
+ * number of different email scenarios, not just one.
18
+ */
19
+ export const MailpitInboxPageBlock = definePageBlock<MailpitInbox>({
20
+ name: "page-mailpit-inbox",
21
+ description: "Mailpit dev inbox hub - confirm arrival/content, open the expected message, read its link.",
22
+ checkpoint: "MailpitInbox",
23
+ verify: [Trait.url({ pathname: "/" })],
24
+ methods: {
25
+ assertEmailReceived: () => AssertEmailReceivedBlock,
26
+ openMessage: () => OpenMessageBlock,
27
+ assertEmailContent: () => AssertEmailContentBlock,
28
+ extractEmailLink: () => ExtractEmailLinkBlock,
29
+ },
30
+ });
@@ -0,0 +1,5 @@
1
+ /** Selectors confirmed against a real running `axllent/mailpit` container's web UI. */
2
+ export const MailpitSel = {
3
+ messageRow: "a.message",
4
+ previewIframe: "#preview-html",
5
+ } as const;
@@ -0,0 +1,48 @@
1
+ import { defineAssertBlock, type Trait } from "waygraph";
2
+ import type { MailpitMessageOpen } from "../../../../states/demo.states.js";
3
+ import { ExpectedEmailContent } from "../../../../states/demo.mem-keys.js";
4
+ import { MailpitSel } from "../mailpit-sel.js";
5
+
6
+ /**
7
+ * Mem-aware Trait: the expected copy varies per email scenario, so it can't
8
+ * be a static `Trait.frameContains(...)` call (which bakes its expected
9
+ * string in at Block-definition time) - a Trait's own `check(page, mem)`
10
+ * already receives mem, so a bespoke Trait reads it at check time instead.
11
+ * This is what lets one Block serve any number of different email templates.
12
+ */
13
+ const bodyContainsExpectedText: Trait = {
14
+ name: "body-contains-expected-text",
15
+ async check(page, mem) {
16
+ const expected = mem.get(ExpectedEmailContent);
17
+ const text = await page.frameLocator(MailpitSel.previewIframe).locator("body").textContent();
18
+ return (text ?? "").includes(expected);
19
+ },
20
+ };
21
+
22
+ /**
23
+ * Kind: Assert
24
+ * Helper: defineAssertBlock
25
+ * Route: demo-external/mailpit/methods/
26
+ *
27
+ * "Does the email actually say what it's supposed to" - not just "does a
28
+ * link exist somewhere in it." Reads the message body straight out of the
29
+ * preview iframe (the top-level page's own `Trait.text`/`Trait.visible`
30
+ * can't see inside an iframe at all - see README's "Mail adapters" section).
31
+ * Self-loop on MailpitMessageOpen - no state change, verify only.
32
+ *
33
+ * Deliberately named for what it does, not for one scenario: the expected
34
+ * copy comes from mem (`ExpectedEmailContent`), not a hardcoded string in
35
+ * this file, so this one Block serves any number of different email
36
+ * scenarios - a Block named "assert-verification-email-content" would
37
+ * wrongly imply it only ever checks verification-email copy.
38
+ *
39
+ * Explicit `<MailpitMessageOpen>` type argument - see the same note on
40
+ * `assert-email-received.method.block.ts` for why this can't be inferred.
41
+ */
42
+ export const AssertEmailContentBlock = defineAssertBlock<MailpitMessageOpen>({
43
+ name: "assert-email-content",
44
+ description: "Confirms the open message's body contains the mem-supplied expected copy.",
45
+ checkpoint: "MailpitMessageOpen",
46
+ requires: [ExpectedEmailContent],
47
+ verify: [bodyContainsExpectedText],
48
+ });
@@ -0,0 +1,49 @@
1
+ import { defineAssertBlock, type Trait } from "waygraph";
2
+ import type { MailpitInbox } from "../../../../states/demo.states.js";
3
+ import { ExpectedRecipient } from "../../../../states/demo.mem-keys.js";
4
+ import { MailpitSel } from "../mailpit-sel.js";
5
+
6
+ /**
7
+ * Mem-aware Trait: the selector depends on which recipient this run expects,
8
+ * so it can't be a static `Trait.visible(...)` string - a Trait's own
9
+ * `check(page, mem)` already receives mem, so a bespoke Trait object reads it
10
+ * directly instead of needing a new engine primitive.
11
+ */
12
+ const emailReceived: Trait = {
13
+ name: "email-received",
14
+ async check(page, mem) {
15
+ const { email } = mem.get(ExpectedRecipient.key);
16
+ try {
17
+ await page.locator(MailpitSel.messageRow, { hasText: email }).first().waitFor({
18
+ state: "visible",
19
+ timeout: 10_000,
20
+ });
21
+ return true;
22
+ } catch {
23
+ return false;
24
+ }
25
+ },
26
+ };
27
+
28
+ /**
29
+ * Kind: Assert
30
+ * Helper: defineAssertBlock
31
+ * Route: demo-external/mailpit/methods/
32
+ *
33
+ * "Did the email even arrive" - a lightweight existence check that does not
34
+ * open or consume the message, so it's safe to run before deciding to open
35
+ * anything. Self-loop on MailpitInbox - no state change, verify only.
36
+ *
37
+ * Explicit `<MailpitInbox>` type argument: `defineAssertBlock` defaults to
38
+ * wildcard `Checkpoint<string>` on both sides when uninferred, which only
39
+ * "works" when the assert sits last in a chain (nothing downstream narrows
40
+ * it) - this one sits between two specifically-typed Blocks, so it must be
41
+ * parameterized explicitly or `defineFlow`'s tuple typing breaks.
42
+ */
43
+ export const AssertEmailReceivedBlock = defineAssertBlock<MailpitInbox>({
44
+ name: "assert-email-received",
45
+ description: "Confirms a message addressed to the expected recipient has arrived.",
46
+ checkpoint: "MailpitInbox",
47
+ requires: [ExpectedRecipient.key],
48
+ verify: [emailReceived],
49
+ });
@@ -0,0 +1,41 @@
1
+ import { defineMethodBlock, checkpoint } from "waygraph";
2
+ import type { MailpitMessageOpen } from "../../../../states/demo.states.js";
3
+ import { EmailLink, ExpectedLinkPattern } from "../../../../states/demo.mem-keys.js";
4
+ import { MailpitSel } from "../mailpit-sel.js";
5
+
6
+ /**
7
+ * Kind: Method
8
+ * Helper: defineMethodBlock
9
+ * Route: demo-external/mailpit/methods/
10
+ *
11
+ * Reads a link straight out of the open message's HTML preview iframe via a
12
+ * real DOM read (getAttribute) - no mail-catcher REST API - and stores it in
13
+ * mem. A separate NavBlock does the actual navigation: folding "read the
14
+ * link" and "go there" into one Block would repeat the exact compound-action
15
+ * anti-pattern `waygraph check` flags elsewhere.
16
+ *
17
+ * Deliberately named for what it does, not for one scenario: the
18
+ * link-matching substring comes from mem (`ExpectedLinkPattern`), not a
19
+ * hardcoded string, so this one Block serves any number of different email
20
+ * scenarios in a project (a signup confirmation, a password reset, a magic
21
+ * link, ...) by varying mem per run, not by writing a new Block per
22
+ * scenario - a Block named "extract-verification-link" would wrongly imply
23
+ * it only ever handles verification emails.
24
+ */
25
+ export const ExtractEmailLinkBlock = defineMethodBlock<MailpitMessageOpen, MailpitMessageOpen>({
26
+ name: "extract-email-link",
27
+ description: "Reads the link matching the mem-supplied pattern out of the open message's preview.",
28
+ requires: [ExpectedLinkPattern],
29
+ instruction: {
30
+ async act(page, _in, mem) {
31
+ const pattern = mem.get(ExpectedLinkPattern);
32
+ const frame = page.frameLocator(MailpitSel.previewIframe);
33
+ const link = frame.locator(`a[href*="${pattern}"]`).first();
34
+ await link.waitFor({ state: "visible", timeout: 15_000 });
35
+ const href = await link.getAttribute("href");
36
+ if (!href) throw new Error(`extract-email-link: no link matching "${pattern}" in message preview`);
37
+ mem.set(EmailLink({ url: href }));
38
+ },
39
+ resolve: () => checkpoint("MailpitMessageOpen"),
40
+ },
41
+ });
@@ -0,0 +1,39 @@
1
+ import { defineMethodBlock, checkpoint } from "waygraph";
2
+ import type { MailpitInbox, MailpitMessageOpen } from "../../../../states/demo.states.js";
3
+ import { ExpectedRecipient } from "../../../../states/demo.mem-keys.js";
4
+ import { MailpitSel } from "../mailpit-sel.js";
5
+
6
+ /**
7
+ * Kind: Method
8
+ * Helper: defineMethodBlock
9
+ * Route: demo-external/mailpit/methods/
10
+ *
11
+ * Opens the newest inbox row addressed to the mem-set recipient (not bare
12
+ * inbox position - a prior run's leftover mail for a different recipient
13
+ * would otherwise pick the wrong row). UI click only, no mail-catcher REST
14
+ * API - the row itself is a real link (`page.waitForURL` sees the client-side
15
+ * route change the same as any other in-page navigation).
16
+ */
17
+ export const OpenMessageBlock = defineMethodBlock<MailpitInbox, MailpitMessageOpen>({
18
+ name: "open-message",
19
+ description: "Opens the newest message addressed to the expected recipient.",
20
+ requires: [ExpectedRecipient.key],
21
+ instruction: {
22
+ async act(page, _in, mem) {
23
+ const { email } = mem.get(ExpectedRecipient.key);
24
+ const row = page.locator(MailpitSel.messageRow, { hasText: email }).first();
25
+ try {
26
+ await row.waitFor({ state: "visible", timeout: 30_000 });
27
+ } catch {
28
+ throw new Error(`open-message: no email arrived for "${email}" within 30s`);
29
+ }
30
+ await row.click();
31
+ await page.waitForURL(/\/view\//, { timeout: 15_000 });
32
+ await page.frameLocator(MailpitSel.previewIframe).locator("body").waitFor({
33
+ state: "attached",
34
+ timeout: 15_000,
35
+ });
36
+ },
37
+ resolve: () => checkpoint("MailpitMessageOpen"),
38
+ },
39
+ });
@@ -0,0 +1,20 @@
1
+ import { defineNavBlock, Trait } from "waygraph";
2
+ import type { MailpitInbox } from "../../../states/demo.states.js";
3
+
4
+ /**
5
+ * Kind: Nav
6
+ * Helper: defineNavBlock
7
+ * Route: demo-external/mailpit/ (cross-origin - a real Mailpit web UI, not this app)
8
+ *
9
+ * Cross-origin tooling lives under demo-external/, never demo-web/ - this is
10
+ * a dev-inbox screen, not part of the app under test. `WAYGRAPH_MAIL_URL`
11
+ * defaults to Mailpit's own usual local port.
12
+ */
13
+ const mailpitUrl = (): string => process.env.WAYGRAPH_MAIL_URL ?? "http://127.0.0.1:8025";
14
+
15
+ export const NavMailpitInboxBlock = defineNavBlock<MailpitInbox>({
16
+ name: "nav-mailpit-inbox",
17
+ checkpoint: "MailpitInbox",
18
+ url: mailpitUrl,
19
+ verify: [Trait.url({ pathname: "/" })],
20
+ });
@@ -11,4 +11,5 @@ export const DemoSel = {
11
11
  clear: "[data-test=clear-cart]",
12
12
  addBtn: (id: string) => `[data-test=add-${id}]`,
13
13
  removeBtn: (id: string) => `[data-test=remove-${id}]`,
14
+ verifiedBanner: "#verified-banner",
14
15
  } as const;
@@ -0,0 +1,8 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head><title>Scaffold - docs (Waygraph Map convention demo)</title></head>
4
+ <body>
5
+ <h1 id="docs-heading">Docs</h1>
6
+ <a id="go-home" href="/home.html">Back home</a>
7
+ </body>
8
+ </html>
@@ -14,6 +14,7 @@
14
14
  <body>
15
15
  <h1>Hello Waygraph</h1>
16
16
  <p id="sub">Scaffold demo - Effect / auto / Page hub</p>
17
+ <p id="verified-banner" hidden>Email verified!</p>
17
18
  <div id="cart">Cart: <span data-test="cart-count">0</span></div>
18
19
  <ul id="catalog">
19
20
  <li class="item" data-id="alpha">
@@ -34,6 +35,9 @@
34
35
  </ul>
35
36
  <button type="button" data-test="clear-cart" id="clear">Clear cart</button>
36
37
  <script>
38
+ if (new URLSearchParams(location.search).get("verified") === "1") {
39
+ document.getElementById("verified-banner").hidden = false;
40
+ }
37
41
  const countEl = document.querySelector("[data-test=cart-count]");
38
42
  const sync = () => {
39
43
  const n = document.querySelectorAll(".remove:not([hidden])").length;
@@ -1,50 +1,46 @@
1
- import { defineMethodBlock, checkpoint, Trait } from "waygraph";
1
+ import { defineAssertBlock, Trait } from "waygraph";
2
2
  import { DemoSel } from "../demo-sel.js";
3
3
 
4
4
  /**
5
- * Kind: Method
6
- * Helper: defineMethodBlock
5
+ * Kind: Assert
6
+ * Helper: defineAssertBlock
7
7
  * Route: demo-web/methods/
8
8
  *
9
- * Same-URL assert after nav - stubs + fixtures + YAP slide.
9
+ * Self-loop-only "verify what's already on the page" check - no hand-written
10
+ * act/resolve to accidentally do a second thing. Same-URL assert after nav,
11
+ * with stubs + fixtures + a YAP slide.
10
12
  */
11
- export const AssertHelloBlock = defineMethodBlock({
13
+ export const AssertHelloBlock = defineAssertBlock({
12
14
  name: "assert-hello",
13
15
  description: "Confirms the hello heading is still visible after arrival.",
14
- instruction: {
15
- async act(_page) {},
16
- async observe(page) {
17
- await page.locator(DemoSel.title).first().waitFor({ state: "visible", timeout: 5_000 });
18
- return "ok" as const;
19
- },
20
- resolve: () => checkpoint("HomeVerified"),
21
- verify: [Trait.text(DemoSel.title, "Hello Waygraph"), Trait.visible(DemoSel.sub)],
22
- stubBefore: {
23
- title: { selector: DemoSel.title, label: "Heading about to check" },
24
- },
25
- stubAfter: {
26
- title: {
27
- selector: DemoSel.title,
28
- label: "Hello confirmed",
29
- detail: "Offline scaffold proof point.",
30
- duration: true,
31
- },
32
- sub: { selector: DemoSel.sub, label: "Subcopy", duration: 1500 },
16
+ checkpoint: "HomeVerified",
17
+ waitForHeading: "Hello Waygraph",
18
+ verify: [Trait.text(DemoSel.title, "Hello Waygraph"), Trait.visible(DemoSel.sub)],
19
+ stubBefore: {
20
+ title: { selector: DemoSel.title, label: "Heading about to check" },
21
+ },
22
+ stubAfter: {
23
+ title: {
24
+ selector: DemoSel.title,
25
+ label: "Hello confirmed",
26
+ detail: "Offline scaffold proof point.",
27
+ duration: true,
33
28
  },
34
- stubOnError: {
35
- title: {
36
- selector: DemoSel.title,
37
- label: "Heading at failure",
38
- tag: "FAIL",
39
- },
29
+ sub: { selector: DemoSel.sub, label: "Subcopy", duration: 1500 },
30
+ },
31
+ stubOnError: {
32
+ title: {
33
+ selector: DemoSel.title,
34
+ label: "Heading at failure",
35
+ tag: "FAIL",
40
36
  },
41
- slides: [
42
- {
43
- caption: "Scaffold covers auto + narration",
44
- detail: "Page hub, Effect instanceOptions, stubs, fixtures, YAP.",
45
- tag: "YAP",
46
- duration: true,
47
- },
48
- ],
49
37
  },
38
+ slides: [
39
+ {
40
+ caption: "Scaffold covers auto + narration",
41
+ detail: "Page hub, Effect instanceOptions, stubs, fixtures, YAP.",
42
+ tag: "YAP",
43
+ duration: true,
44
+ },
45
+ ],
50
46
  });
@@ -0,0 +1,38 @@
1
+ import { defineAssertBlock, Trait, type Trait as TraitType } from "waygraph";
2
+ import type { ItemInCart } from "../../../states/demo.states.js";
3
+ import { SelectedItem } from "../../../states/demo.mem-keys.js";
4
+ import { DemoSel } from "../demo-sel.js";
5
+
6
+ /**
7
+ * Mem-aware Trait: which item's Remove button should be visible depends on
8
+ * which row add-item just acted on (mem, not a static selector) - a Trait's
9
+ * own `check(page, mem)` already receives mem, so this reads it directly.
10
+ */
11
+ const removeButtonVisibleForSelectedItem: TraitType = {
12
+ name: "remove-button-visible-for-selected-item",
13
+ async check(page, mem) {
14
+ const { id } = mem.get(SelectedItem.key);
15
+ return page.locator(DemoSel.removeBtn(id)).isVisible();
16
+ },
17
+ };
18
+
19
+ /**
20
+ * Kind: Assert
21
+ * Helper: defineAssertBlock
22
+ * Route: demo-web/methods/
23
+ *
24
+ * "Did add-item actually work" - provable only after the fact, from the
25
+ * method's real effect on the page: the specific row's own Remove button
26
+ * appeared (mem-matched, not just "some" row), AND the aggregate cart count
27
+ * reflects it. Self-loop on ItemInCart - no state change, verify only.
28
+ *
29
+ * Explicit `<ItemInCart>` type argument - `defineAssertBlock` defaults to
30
+ * wildcard `Checkpoint<string>` when uninferred, which only "works" when the
31
+ * assert sits last in a chain; this one sits mid-chain (see shop.flow.ts).
32
+ */
33
+ export const AssertItemAddedBlock = defineAssertBlock<ItemInCart>({
34
+ name: "assert-item-added",
35
+ description: "Confirms the mem-selected item's own Remove button appeared and the cart count reflects it.",
36
+ checkpoint: "ItemInCart",
37
+ verify: [removeButtonVisibleForSelectedItem, Trait.text(DemoSel.cartCount, "1")],
38
+ });
@@ -0,0 +1,34 @@
1
+ import { defineNavBlock, Trait } from "waygraph";
2
+ import type { HomeVerified } from "../../states/demo.states.js";
3
+ import { EmailLink } from "../../states/demo.mem-keys.js";
4
+ import { DemoSel } from "./demo-sel.js";
5
+
6
+ /**
7
+ * Kind: Nav
8
+ * Helper: defineNavBlock
9
+ * Route: demo-web/
10
+ *
11
+ * Navigates back into the app to the link extract-email-link read out of
12
+ * the real Mailpit message, then confirms the page actually reflects it
13
+ * (the verified banner only appears when the exact `verified=1` link was
14
+ * followed, not just any URL). This particular check is app-specific (this
15
+ * scaffold's own verification landing route) even though the mail-reading
16
+ * Blocks feeding it are scenario-agnostic - a real project would have one
17
+ * such Nav per distinct landing route, same as any other app screen.
18
+ *
19
+ * Deliberately no `requires: [EmailLink.key]` here: `requires` means "the
20
+ * caller must seed this externally before the flow starts" (preflight
21
+ * checks the whole chain's requires up front) - `EmailLink` is instead
22
+ * produced by an earlier Block in this very chain, so declaring it as a
23
+ * requirement would make preflight fail before the flow ever gets the
24
+ * chance to produce it. Engine support for declaring a mem key as "produced
25
+ * by an earlier Block in this chain" (vs. externally supplied) is a
26
+ * separate, not-yet-built roadmap item - see ROADMAP.md's "Split requires"
27
+ * row.
28
+ */
29
+ export const NavVerificationLinkBlock = defineNavBlock<HomeVerified>({
30
+ name: "nav-verification-link",
31
+ checkpoint: "HomeVerified",
32
+ url: (mem) => mem.get(EmailLink.key).url,
33
+ verify: [Trait.visible(DemoSel.verifiedBanner)],
34
+ });
@@ -0,0 +1,85 @@
1
+ import { Engine, start, end, withTitle, withHighlightFixtures } from "waygraph";
2
+ import { NavMailpitInboxBlock } from "../blocks/demo-external/mailpit/nav-mailpit-inbox.block.js";
3
+ import { AssertEmailReceivedBlock } from "../blocks/demo-external/mailpit/methods/assert-email-received.method.block.js";
4
+ import { OpenMessageBlock } from "../blocks/demo-external/mailpit/methods/open-message.method.block.js";
5
+ import { AssertEmailContentBlock } from "../blocks/demo-external/mailpit/methods/assert-email-content.method.block.js";
6
+ import { ExtractEmailLinkBlock } from "../blocks/demo-external/mailpit/methods/extract-email-link.method.block.js";
7
+ import { NavVerificationLinkBlock } from "../blocks/demo-web/nav-verification-link.block.js";
8
+ import "../blocks/demo-external/mailpit/mailpit-inbox.page.block.js";
9
+ import { MailpitSel } from "../blocks/demo-external/mailpit/mailpit-sel.js";
10
+
11
+ const engine = new Engine();
12
+
13
+ /**
14
+ * Cross-origin mail demo, QA-rich: navigate to a real Mailpit inbox (a real,
15
+ * separate web UI - not this app), confirm the email actually arrived,
16
+ * open it, confirm its body says what it's supposed to, read a link
17
+ * straight out of the DOM, then navigate back into the app with it - six
18
+ * atomic Blocks, no mail-catcher REST API anywhere.
19
+ *
20
+ * The four mail-reading Blocks (`assert-email-received`, `open-message`,
21
+ * `assert-email-content`, `extract-email-link`) are reusable across any
22
+ * number of different email scenarios in a project (signup confirmation,
23
+ * password reset, magic link, ...) with zero new Blocks per scenario - only
24
+ * mem varies per run: `ExpectedRecipient` (who), `ExpectedLinkPattern`
25
+ * (which link in the body to follow), `ExpectedEmailContent` (what the body
26
+ * should say). This particular flow wires them to this scaffold's own
27
+ * "verified" landing route (`nav-verification-link`, app-specific by
28
+ * nature - a different scenario would use a different landing Nav) - see
29
+ * tests/mail-verify.spec.ts for two different scenarios driven through the
30
+ * same four mail-reading Blocks.
31
+ *
32
+ * Needs a real Mailpit instance reachable at WAYGRAPH_MAIL_URL (default
33
+ * http://127.0.0.1:8025) with a message already waiting for
34
+ * ExpectedRecipient's email - see tests/mail-verify.spec.ts for how the test
35
+ * drives this end to end (spins up a real Mailpit container, sends a real
36
+ * email, then runs this exact flow) - including the negative cases: no email
37
+ * arriving, or content that doesn't match, each fail loud, naming
38
+ * themselves, not a silent pass.
39
+ */
40
+ export const mailVerifyFlow = withHighlightFixtures(
41
+ withTitle(
42
+ engine.defineFlow([
43
+ start,
44
+ NavMailpitInboxBlock,
45
+ AssertEmailReceivedBlock,
46
+ OpenMessageBlock,
47
+ AssertEmailContentBlock,
48
+ ExtractEmailLinkBlock,
49
+ NavVerificationLinkBlock,
50
+ end,
51
+ ]),
52
+ "Scaffold: Cross-origin mail verification (QA-rich)",
53
+ ),
54
+ {
55
+ "assert-email-received": {
56
+ stubAfter: {
57
+ note: {
58
+ selector: MailpitSel.messageRow,
59
+ label: "AC · email arrived",
60
+ detail: "Confirmed before opening anything.",
61
+ tag: "AC",
62
+ },
63
+ },
64
+ },
65
+ "assert-email-content": {
66
+ stubAfter: {
67
+ note: {
68
+ selector: MailpitSel.previewIframe,
69
+ label: "AC · email body matches expected copy",
70
+ tag: "AC",
71
+ },
72
+ },
73
+ },
74
+ "extract-email-link": {
75
+ stubAfter: {
76
+ note: {
77
+ selector: MailpitSel.previewIframe,
78
+ label: "AC · link found",
79
+ detail: "Read from the message preview iframe.",
80
+ tag: "AC",
81
+ },
82
+ },
83
+ },
84
+ },
85
+ );