waygraph 0.15.51 → 0.15.52

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 (130) hide show
  1. package/dist/analysis/graph.d.ts +1 -1
  2. package/dist/analysis/graph.js +10 -15
  3. package/dist/analysis/practices-check.d.ts +0 -1
  4. package/dist/analysis/practices-check.js +0 -13
  5. package/dist/auto-session/session.d.ts +1 -1
  6. package/dist/auto-session/session.js +13 -8
  7. package/dist/blocks-select.d.ts +7 -4
  8. package/dist/blocks-select.js +0 -14
  9. package/dist/browser.d.ts +27 -0
  10. package/dist/browser.js +62 -0
  11. package/dist/cli/run-chain.js +4 -0
  12. package/dist/cli/usage.js +14 -1
  13. package/dist/cli.js +10 -1
  14. package/dist/commands/agent-dive-scaffold.d.ts +5 -0
  15. package/dist/commands/agent-dive-scaffold.js +7 -0
  16. package/dist/commands/extensions.d.ts +1 -0
  17. package/dist/commands/extensions.js +22 -0
  18. package/dist/commands/init.js +42 -4
  19. package/dist/commands/test.js +16 -2
  20. package/dist/commands/try.js +2 -1
  21. package/dist/commands/upgrade.d.ts +6 -0
  22. package/dist/commands/upgrade.js +107 -0
  23. package/dist/config.d.ts +47 -3
  24. package/dist/config.js +45 -12
  25. package/dist/core/block.d.ts +12 -0
  26. package/dist/core/block.js +36 -0
  27. package/dist/core/blocks/nav.d.ts +11 -0
  28. package/dist/core/blocks/nav.js +8 -0
  29. package/dist/core/config.d.ts +5 -0
  30. package/dist/core/engine.d.ts +17 -16
  31. package/dist/core/engine.js +3 -3
  32. package/dist/core/flow.d.ts +17 -0
  33. package/dist/core/flow.js +44 -21
  34. package/dist/core/map-builder.d.ts +3 -3
  35. package/dist/core/map-builder.js +3 -3
  36. package/dist/env-file.d.ts +7 -7
  37. package/dist/env-file.js +29 -16
  38. package/dist/extensions.d.ts +21 -0
  39. package/dist/extensions.js +178 -0
  40. package/dist/index.d.ts +6 -4
  41. package/dist/index.js +3 -2
  42. package/dist/pilot-overlay/overlay-fixtures.d.ts +0 -7
  43. package/dist/pilot-overlay/overlay-fixtures.js +0 -45
  44. package/dist/playwright-config.d.ts +4 -0
  45. package/dist/playwright-config.js +15 -0
  46. package/dist/playwright.config.d.ts +2 -0
  47. package/dist/playwright.config.js +6 -0
  48. package/dist/runner/demo-log.js +1 -7
  49. package/dist/runner/inpage/core.d.ts +1 -1
  50. package/dist/runner/inpage/core.js +8 -7
  51. package/dist/runner/main.js +18 -10
  52. package/dist/runner/rings.js +0 -49
  53. package/dist/session/auto-explore-run.js +9 -5
  54. package/dist/session/auto-explore.js +6 -4
  55. package/dist/session/step-overlay.js +3 -1
  56. package/dist/traverse/run.js +3 -1
  57. package/dist/ui/components/ring.js +1 -0
  58. package/dist/ui/overlay.css +1 -0
  59. package/dist/ui/shadow.d.ts +1 -3
  60. package/dist/ui/shadow.js +0 -4
  61. package/dist/ui/tokens.d.ts +0 -11
  62. package/dist/ui/tokens.js +0 -5
  63. package/dist/video-dir.d.ts +8 -0
  64. package/dist/video-dir.js +60 -0
  65. package/dist/webserver.js +5 -1
  66. package/package.json +1 -1
  67. package/templates/scaffold/README.md +4 -2
  68. package/templates/scaffold/STRUCTURE.md +5 -2
  69. package/templates/scaffold/config/waygraph.config.ts +26 -0
  70. package/templates/scaffold/package.json +1 -1
  71. package/templates/scaffold/src/flows/README.md +5 -5
  72. package/templates/scaffold/src/flows/example.flow.ts +31 -17
  73. package/templates/scaffold/src/flows/hoppscotch-auth.flow.ts +35 -0
  74. package/templates/scaffold/src/flows/hoppscotch-get.flow.ts +23 -0
  75. package/templates/scaffold/src/flows/hoppscotch-post.flow.ts +33 -0
  76. package/templates/scaffold/src/flows/mail-verify.flow.ts +36 -75
  77. package/templates/scaffold/src/flows/shop.flow.ts +41 -65
  78. package/templates/scaffold/src/map/(app_base)/docs/_nav.block.ts +0 -9
  79. package/templates/scaffold/src/map/(app_base)/docs/_page.block.ts +0 -5
  80. package/templates/scaffold/src/map/(app_base)/docs/_sel.ts +1 -1
  81. package/templates/scaffold/src/map/(app_base)/home/_methods/add-item.effect.block.ts +3 -19
  82. package/templates/scaffold/src/map/(app_base)/home/_methods/assert-hello.method.block.ts +2 -25
  83. package/templates/scaffold/src/map/(app_base)/home/_methods/assert-item-added.method.block.ts +2 -19
  84. package/templates/scaffold/src/map/(app_base)/home/_methods/clear-cart.method.block.ts +1 -17
  85. package/templates/scaffold/src/map/(app_base)/home/_methods/remove-item.effect.block.ts +1 -10
  86. package/templates/scaffold/src/map/(app_base)/home/_nav.block.ts +1 -12
  87. package/templates/scaffold/src/map/(app_base)/home/_page.block.ts +0 -9
  88. package/templates/scaffold/src/map/(app_base)/home/_sel.ts +3 -1
  89. package/templates/scaffold/src/map/(app_base)/home/nav-verification-link.block.ts +1 -23
  90. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/assert-response-body.method.block.ts +21 -0
  91. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/assert-response-header.method.block.ts +19 -0
  92. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/assert-status.method.block.ts +20 -0
  93. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/pick-extension-interceptor.method.block.ts +18 -0
  94. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/read-response-body.method.block.ts +21 -0
  95. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/read-response-headers.method.block.ts +21 -0
  96. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/send-request.method.block.ts +15 -0
  97. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-bearer-token.method.block.ts +20 -0
  98. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-headers.method.block.ts +23 -0
  99. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-json-body.method.block.ts +36 -0
  100. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-method.method.block.ts +17 -0
  101. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-query-params.method.block.ts +22 -0
  102. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-url.method.block.ts +18 -0
  103. package/templates/scaffold/src/map/(external)/hoppscotch/_nav.block.ts +11 -0
  104. package/templates/scaffold/src/map/(external)/hoppscotch/_page.block.ts +36 -0
  105. package/templates/scaffold/src/map/(external)/hoppscotch/_sel.ts +27 -0
  106. package/templates/scaffold/src/map/(external)/hoppscotch/settings/_nav.block.ts +10 -0
  107. package/templates/scaffold/src/map/(external)/mailpit/_methods/assert-email-content.method.block.ts +2 -27
  108. package/templates/scaffold/src/map/(external)/mailpit/_methods/assert-email-received.method.block.ts +2 -21
  109. package/templates/scaffold/src/map/(external)/mailpit/_methods/extract-email-link.method.block.ts +1 -19
  110. package/templates/scaffold/src/map/(external)/mailpit/_methods/open-message.method.block.ts +1 -11
  111. package/templates/scaffold/src/map/(external)/mailpit/_nav.block.ts +1 -9
  112. package/templates/scaffold/src/map/(external)/mailpit/_page.block.ts +0 -11
  113. package/templates/scaffold/src/states/demo.mem-keys.ts +20 -19
  114. package/templates/scaffold/src/states/demo.mem-stubs.ts +4 -0
  115. package/templates/scaffold/src/states/demo.states.ts +5 -1
  116. package/templates/scaffold/tests/example.spec.ts +2 -3
  117. package/templates/scaffold/tests/hoppscotch.spec.ts +97 -0
  118. package/templates/scaffold/tests/mail-verify.spec.ts +2 -11
  119. package/templates/scaffold/tsconfig.json +1 -1
  120. package/templates/skills/waygraph-convention.skill.md +32 -3
  121. package/templates/skills/waygraph-pilot.skill.md +4 -5
  122. package/dist/browser-path.d.ts +0 -5
  123. package/dist/browser-path.js +0 -18
  124. package/dist/runner/overlay-present.d.ts +0 -8
  125. package/dist/runner/overlay-present.js +0 -57
  126. package/dist/ui/components/banner.d.ts +0 -2
  127. package/dist/ui/components/banner.js +0 -10
  128. package/dist/ui/components/cursor.d.ts +0 -2
  129. package/dist/ui/components/cursor.js +0 -19
  130. package/templates/scaffold/playwright.config.ts +0 -12
@@ -0,0 +1,23 @@
1
+ import { Engine } from "waygraph";
2
+ import config from "../../config/waygraph.config.js";
3
+ import { NavHoppscotchSettingsBlock } from "../map/(external)/hoppscotch/settings/_nav.block.js";
4
+ import { PickExtensionInterceptorBlock } from "../map/(external)/hoppscotch/_methods/pick-extension-interceptor.method.block.js";
5
+ import { NavHoppscotchRestBlock } from "../map/(external)/hoppscotch/_nav.block.js";
6
+ import { SetUrlBlock } from "../map/(external)/hoppscotch/_methods/set-url.method.block.js";
7
+ import { SendRequestBlock } from "../map/(external)/hoppscotch/_methods/send-request.method.block.js";
8
+ import { AssertStatusBlock } from "../map/(external)/hoppscotch/_methods/assert-status.method.block.js";
9
+ import "../map/(external)/hoppscotch/_page.block.js";
10
+
11
+ const engine = new Engine(config);
12
+
13
+ /** GET a URL and check the status. Needs `extensions: ["hoppscotch"]` in waygraph.config.ts. */
14
+ export const hoppscotchGetFlow = engine
15
+ .map()
16
+ .start()
17
+ .gotoExternal(NavHoppscotchSettingsBlock)
18
+ .method(PickExtensionInterceptorBlock)
19
+ .gotoExternal(NavHoppscotchRestBlock)
20
+ .method(SetUrlBlock)
21
+ .method(SendRequestBlock)
22
+ .assert(AssertStatusBlock)
23
+ .end({ title: "Scaffold: Hoppscotch GET" });
@@ -0,0 +1,33 @@
1
+ import { Engine } from "waygraph";
2
+ import config from "../../config/waygraph.config.js";
3
+ import { NavHoppscotchSettingsBlock } from "../map/(external)/hoppscotch/settings/_nav.block.js";
4
+ import { PickExtensionInterceptorBlock } from "../map/(external)/hoppscotch/_methods/pick-extension-interceptor.method.block.js";
5
+ import { NavHoppscotchRestBlock } from "../map/(external)/hoppscotch/_nav.block.js";
6
+ import { SetMethodBlock } from "../map/(external)/hoppscotch/_methods/set-method.method.block.js";
7
+ import { SetUrlBlock } from "../map/(external)/hoppscotch/_methods/set-url.method.block.js";
8
+ import { SetHeadersBlock } from "../map/(external)/hoppscotch/_methods/set-headers.method.block.js";
9
+ import { SetJsonBodyBlock } from "../map/(external)/hoppscotch/_methods/set-json-body.method.block.js";
10
+ import { SendRequestBlock } from "../map/(external)/hoppscotch/_methods/send-request.method.block.js";
11
+ import { AssertStatusBlock } from "../map/(external)/hoppscotch/_methods/assert-status.method.block.js";
12
+ import { ReadResponseBodyBlock } from "../map/(external)/hoppscotch/_methods/read-response-body.method.block.js";
13
+ import { AssertResponseBodyBlock } from "../map/(external)/hoppscotch/_methods/assert-response-body.method.block.js";
14
+ import "../map/(external)/hoppscotch/_page.block.js";
15
+
16
+ const engine = new Engine(config);
17
+
18
+ /** POST JSON with a header, check the status and the response body. Needs `extensions: ["hoppscotch"]`. */
19
+ export const hoppscotchPostFlow = engine
20
+ .map()
21
+ .start()
22
+ .gotoExternal(NavHoppscotchSettingsBlock)
23
+ .method(PickExtensionInterceptorBlock)
24
+ .gotoExternal(NavHoppscotchRestBlock)
25
+ .method(SetMethodBlock)
26
+ .method(SetUrlBlock)
27
+ .method(SetHeadersBlock)
28
+ .method(SetJsonBodyBlock)
29
+ .method(SendRequestBlock)
30
+ .assert(AssertStatusBlock)
31
+ .method(ReadResponseBodyBlock)
32
+ .assert(AssertResponseBodyBlock)
33
+ .end({ title: "Scaffold: Hoppscotch POST with header and JSON body" });
@@ -1,4 +1,5 @@
1
- import { Engine, start, end, withTitle, withHighlightFixtures } from "waygraph";
1
+ import { Engine } from "waygraph";
2
+ import config from "../../config/waygraph.config.js";
2
3
  import { NavMailpitInboxBlock } from "../map/(external)/mailpit/_nav.block.js";
3
4
  import { AssertEmailReceivedBlock } from "../map/(external)/mailpit/_methods/assert-email-received.method.block.js";
4
5
  import { OpenMessageBlock } from "../map/(external)/mailpit/_methods/open-message.method.block.js";
@@ -8,78 +9,38 @@ import { NavVerificationLinkBlock } from "../map/(app_base)/home/nav-verificatio
8
9
  import "../map/(external)/mailpit/_page.block.js";
9
10
  import { MailpitSel } from "../map/(external)/mailpit/_sel.js";
10
11
 
11
- const engine = new Engine();
12
+ const engine = new Engine(config);
12
13
 
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
- );
14
+ /** Needs a Mailpit at WAYGRAPH_MAIL_URL (default http://127.0.0.1:8025); tests/mail-verify.spec.ts starts one. */
15
+ export const mailVerifyFlow = engine
16
+ .map()
17
+ .start()
18
+ .gotoExternal(NavMailpitInboxBlock)
19
+ .assert(
20
+ AssertEmailReceivedBlock.stubAfter((ctx) => {
21
+ ctx.ring("note", {
22
+ selector: MailpitSel.messageRow,
23
+ label: "AC · email arrived",
24
+ detail: "Confirmed before opening anything.",
25
+ tag: "AC",
26
+ });
27
+ }),
28
+ )
29
+ .method(OpenMessageBlock)
30
+ .assert(
31
+ AssertEmailContentBlock.stubAfter((ctx) => {
32
+ ctx.ring("note", { selector: MailpitSel.previewIframe, label: "AC · email body matches expected copy", tag: "AC" });
33
+ }),
34
+ )
35
+ .method(
36
+ ExtractEmailLinkBlock.stubAfter((ctx) => {
37
+ ctx.ring("note", {
38
+ selector: MailpitSel.previewIframe,
39
+ label: "AC · link found",
40
+ detail: "Read from the message preview iframe.",
41
+ tag: "AC",
42
+ });
43
+ }),
44
+ )
45
+ .gotoPage(NavVerificationLinkBlock)
46
+ .end({ title: "Scaffold: Cross-origin mail verification (QA-rich)" });
@@ -1,74 +1,50 @@
1
- import {
2
- Engine,
3
- start,
4
- end,
5
- withTitle,
6
- withHighlightFixtures,
7
- withSessionReset,
8
- withMemStub,
9
- registerMemStub,
10
- } from "waygraph";
1
+ import { Engine } from "waygraph";
2
+ import config from "../../config/waygraph.config.js";
3
+ import { DemoSel } from "../map/(app_base)/home/_sel.js";
11
4
  import { NavHomeBlock } from "../map/(app_base)/home/_nav.block.js";
12
5
  import { AddItemBlock } from "../map/(app_base)/home/_methods/add-item.effect.block.js";
13
6
  import { AssertItemAddedBlock } from "../map/(app_base)/home/_methods/assert-item-added.method.block.js";
14
7
  import { RemoveItemBlock } from "../map/(app_base)/home/_methods/remove-item.effect.block.js";
15
8
  import { ClearCartBlock } from "../map/(app_base)/home/_methods/clear-cart.method.block.js";
16
- import { SelectedItem } from "../states/demo.mem-keys.js";
9
+ import "../states/demo.mem-stubs.js";
17
10
  import "../map/(app_base)/home/_page.block.js";
18
11
 
19
- const engine = new Engine();
12
+ const engine = new Engine(config);
20
13
 
21
- /**
22
- * memStub: a real fixture row (`alpha`/`Alpha`), registered once so this Flow never needs
23
- * --data at all. Still override-able - `--data '{"demo.selectedItem":{"id":"beta",...}}'`
24
- * wins over this registration, same as any other memStub (see docs/REFERENCE.md's own
25
- * "memStub - fake values for requires" section).
26
- */
27
- registerMemStub(SelectedItem.key, () => ({ id: "alpha", name: "Alpha" }));
14
+ // `memStub: true` fills SelectedItem (alpha) from src/states/demo.mem-stubs.ts; --data overrides it.
15
+ const AddShown = AddItemBlock.stubBefore((ctx) => {
16
+ ctx.ring("add", { selector: DemoSel.catalog, label: "Catalog" });
17
+ })
18
+ .stubAfter((ctx) => {
19
+ ctx.ring("cart", {
20
+ selector: DemoSel.cartCount,
21
+ label: "AC · item in cart",
22
+ detail: "Cart count bumped after Add.",
23
+ tag: "AC",
24
+ duration: true,
25
+ });
26
+ })
27
+ .stubOnError((ctx) => {
28
+ ctx.ring("cart", { selector: DemoSel.cartCount, label: "Cart at failure", tag: "FAIL" });
29
+ });
28
30
 
29
- /**
30
- * Effect + Method demo. `requires: [SelectedItem.key]` (on `add-item`) is filled automatically
31
- * by the memStub registration above - no --data needed. Override it with real data, or use
32
- * `waygraph auto` which fills SelectedItem via instanceOptions instead:
33
- *
34
- * waygraph demo --blocks shopFlow # memStub fills it
35
- * waygraph demo --blocks shopFlow --data '{"demo.selectedItem":{"id":"beta","name":"Beta"}}'
36
- * waygraph auto # after landing on home, pick Add Alpha / Add Beta / Remove ...
37
- *
38
- * Add -> assert it actually landed (mem-matched Remove button + cart count,
39
- * only provable from the method's real effect, not a static page state) ->
40
- * Remove that same row (per-item Effect, `remove-item`) -> Add again ->
41
- * Clear cart (bulk Method, `clear-cart`) - keeps both the per-item and bulk
42
- * clear paths atomic and each wired into a real flow, not orphaned.
43
- */
44
- export const shopFlow = withMemStub(
45
- withHighlightFixtures(
46
- withTitle(
47
- withSessionReset(
48
- engine.defineFlow([
49
- start,
50
- NavHomeBlock,
51
- AddItemBlock,
52
- AssertItemAddedBlock,
53
- RemoveItemBlock,
54
- AddItemBlock,
55
- ClearCartBlock,
56
- end,
57
- ]),
58
- ),
59
- "Scaffold: Shop (Effect + clear)",
60
- ),
61
- {
62
- "add-item": {
63
- stubAfter: {
64
- cart: {
65
- label: "AC · item in cart",
66
- detail: "Cart count bumped after Add.",
67
- tag: "AC",
68
- duration: true,
69
- },
70
- },
71
- },
72
- },
73
- ),
74
- );
31
+ const RemoveShown = RemoveItemBlock.stubAfter((ctx) => {
32
+ ctx.ring("cart", { selector: DemoSel.cartCount, label: "Cart count", duration: true });
33
+ });
34
+
35
+ const ClearShown = ClearCartBlock.stubBefore((ctx) => {
36
+ ctx.ring("clear", { selector: DemoSel.clear, label: "Clear cart" });
37
+ }).stubAfter((ctx) => {
38
+ ctx.ring("cart", { selector: DemoSel.cartCount, label: "Empty cart", duration: true });
39
+ });
40
+
41
+ export const shopFlow = engine
42
+ .map()
43
+ .start()
44
+ .gotoPage(NavHomeBlock)
45
+ .method(AddShown)
46
+ .assert(AssertItemAddedBlock)
47
+ .method(RemoveShown)
48
+ .method(AddShown)
49
+ .method(ClearShown)
50
+ .end({ title: "Scaffold: Shop (Effect + clear)", sessionReset: true, memStub: true });
@@ -2,15 +2,6 @@ import { defineNavBlock, Trait } from "waygraph";
2
2
  import type { Docs } from "../../../states/demo.states.js";
3
3
  import { DocsSel, DOCS_URL } from "./_sel.js";
4
4
 
5
- /**
6
- * Kind: Nav
7
- * Route: (app_base)/docs/
8
- *
9
- * Same shape as home/ (its own file:// page, no server) - lives
10
- * under (app_base), not (external). See docs/REFERENCE.md "Waygraph Map" and
11
- * openspec/changes/waygraph-map. `(app_base)` is purely organizational
12
- * (Next.js route-group style) - never part of the Checkpoint tag.
13
- */
14
5
  export const NavDocsBlock = defineNavBlock<Docs>({
15
6
  name: "nav-docs",
16
7
  description: "Opens the docs page (Waygraph Map convention demo).",
@@ -2,11 +2,6 @@ import { definePageBlock, Trait } from "waygraph";
2
2
  import type { Docs } from "../../../states/demo.states.js";
3
3
  import { DocsSel } from "./_sel.js";
4
4
 
5
- /**
6
- * Kind: Page
7
- * Route: (app_base)/docs/
8
- * Arrival-only (nav.block.ts already landed on Docs).
9
- */
10
5
  export const DocsPageBlock = definePageBlock<Docs>({
11
6
  name: "page-docs",
12
7
  description: "Docs hub - Waygraph Map convention demo.",
@@ -1,4 +1,4 @@
1
- // Relative to playwright.config.ts's baseURL (the (app_base) folder, set by `waygraph init`).
1
+ // Relative to baseUrl in config/waygraph.config.ts (the (app_base) folder, set by `waygraph init`).
2
2
  export const DOCS_URL = "docs/index.html";
3
3
 
4
4
  export const DocsSel = {
@@ -7,14 +7,7 @@ import type { CanAddItem, ItemInCart } from "../../../../states/demo.states.js";
7
7
  import { SelectedItem } from "../../../../states/demo.mem-keys.js";
8
8
  import { DemoSel } from "../_sel.js";
9
9
 
10
- /**
11
- * Kind: Effect
12
- * Helper: defineEffectBlock
13
- * Route: demo-web/methods/
14
- *
15
- * Mutates one catalog row from mem. `waygraph auto` lists one "Add …" row per
16
- * live Add button via instanceOptions.
17
- */
10
+ /** `waygraph auto` lists one "Add …" row per live Add button via `instanceOptions`. */
18
11
  export const AddItemBlock = defineEffectBlock<CanAddItem, ItemInCart>({
19
12
  name: "add-item",
20
13
  description: "Adds the mem-selected catalog item to the cart.",
@@ -36,7 +29,7 @@ export const AddItemBlock = defineEffectBlock<CanAddItem, ItemInCart>({
36
29
  ],
37
30
  },
38
31
  async instanceOptions(page): Promise<readonly WaygraphInstanceOption[]> {
39
- const buttons = page.locator("button.add:not([hidden])");
32
+ const buttons = page.locator(DemoSel.addButtons);
40
33
  const n = await buttons.count();
41
34
  const out: WaygraphInstanceOption[] = [];
42
35
  for (let i = 0; i < n; i++) {
@@ -55,13 +48,4 @@ export const AddItemBlock = defineEffectBlock<CanAddItem, ItemInCart>({
55
48
  }
56
49
  return out;
57
50
  },
58
- })
59
- .stubBefore((ctx) => {
60
- ctx.ring("add", { selector: DemoSel.catalog, label: "Catalog" });
61
- })
62
- .stubAfter((ctx) => {
63
- ctx.ring("cart", { selector: DemoSel.cartCount, label: "Cart count", duration: true });
64
- })
65
- .stubOnError((ctx) => {
66
- ctx.ring("cart", { selector: DemoSel.cartCount, label: "Cart at failure", tag: "FAIL" });
67
- });
51
+ });
@@ -2,15 +2,7 @@ import { defineAssertBlock, Trait } from "waygraph";
2
2
  import type { Home } from "../../../../states/demo.states.js";
3
3
  import { DemoSel } from "../_sel.js";
4
4
 
5
- /**
6
- * Kind: Assert
7
- * Helper: defineAssertBlock
8
- * Route: demo-web/methods/
9
- *
10
- * Self-loop-only "verify what's already on the page" check - no hand-written
11
- * act/resolve to accidentally do a second thing. Same-URL assert after nav,
12
- * with stubs + fixtures + a YAP slide.
13
- */
5
+ /** Heading still shows after arrival; the slide is a YAP caption for the demo overlay. */
14
6
  export const AssertHelloBlock = defineAssertBlock<Home>({
15
7
  name: "assert-hello",
16
8
  description: "Confirms the hello heading is still visible after arrival.",
@@ -25,19 +17,4 @@ export const AssertHelloBlock = defineAssertBlock<Home>({
25
17
  duration: true,
26
18
  },
27
19
  ],
28
- })
29
- .stubBefore((ctx) => {
30
- ctx.ring("title", { selector: DemoSel.title, label: "Heading about to check" });
31
- })
32
- .stubAfter((ctx) => {
33
- ctx.ring("title", {
34
- selector: DemoSel.title,
35
- label: "Hello confirmed",
36
- detail: "Offline scaffold proof point.",
37
- duration: true,
38
- });
39
- ctx.ring("sub", { selector: DemoSel.sub, label: "Subcopy", duration: 1500 });
40
- })
41
- .stubOnError((ctx) => {
42
- ctx.ring("title", { selector: DemoSel.title, label: "Heading at failure", tag: "FAIL" });
43
- });
20
+ });
@@ -3,11 +3,7 @@ import type { ItemInCart } from "../../../../states/demo.states.js";
3
3
  import { SelectedItem } from "../../../../states/demo.mem-keys.js";
4
4
  import { DemoSel } from "../_sel.js";
5
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
- */
6
+ // Reads mem at check time, so it follows whichever item add-item acted on.
11
7
  const removeButtonVisibleForSelectedItem: TraitType = {
12
8
  name: "remove-button-visible-for-selected-item",
13
9
  async check(page, mem) {
@@ -16,20 +12,7 @@ const removeButtonVisibleForSelectedItem: TraitType = {
16
12
  },
17
13
  };
18
14
 
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
- */
15
+ /** The mem-selected item's own Remove button appeared and the cart count reflects it. */
33
16
  export const AssertItemAddedBlock = defineAssertBlock<ItemInCart>({
34
17
  name: "assert-item-added",
35
18
  description: "Confirms the mem-selected item's own Remove button appeared and the cart count reflects it.",
@@ -2,16 +2,6 @@ import { defineMethodBlock, checkpoint, Trait } from "waygraph";
2
2
  import type { ItemInCart, CartEmpty } from "../../../../states/demo.states.js";
3
3
  import { DemoSel } from "../_sel.js";
4
4
 
5
- /**
6
- * Kind: Method
7
- * Helper: defineMethodBlock
8
- * Route: (app_base)/home/_methods/
9
- *
10
- * Real, pre-existing bug found live while verifying the Waygraph Map
11
- * rename: this Block had no explicit <In, Out> generic at all, so static
12
- * discovery (`waygraph graph`) always skipped it silently ("no ...generic
13
- * call found") - present before this move, not caused by it.
14
- */
15
5
  export const ClearCartBlock = defineMethodBlock<ItemInCart, CartEmpty>({
16
6
  name: "clear-cart",
17
7
  description: "Clears every item via the Clear cart control.",
@@ -22,10 +12,4 @@ export const ClearCartBlock = defineMethodBlock<ItemInCart, CartEmpty>({
22
12
  resolve: () => checkpoint("CartEmpty"),
23
13
  verify: [Trait.text(DemoSel.cartCount, "0")],
24
14
  },
25
- })
26
- .stubBefore((ctx) => {
27
- ctx.ring("clear", { selector: DemoSel.clear, label: "Clear cart" });
28
- })
29
- .stubAfter((ctx) => {
30
- ctx.ring("cart", { selector: DemoSel.cartCount, label: "Empty cart", duration: true });
31
- });
15
+ });
@@ -7,13 +7,6 @@ import type { CartEmpty, ItemInCart } from "../../../../states/demo.states.js";
7
7
  import { SelectedItem } from "../../../../states/demo.mem-keys.js";
8
8
  import { DemoSel } from "../_sel.js";
9
9
 
10
- /**
11
- * Kind: Effect
12
- * Helper: defineEffectBlock
13
- * Route: demo-web/methods/
14
- *
15
- * Removes one in-cart row. Auto menus one row per visible Remove button.
16
- */
17
10
  export const RemoveItemBlock = defineEffectBlock<ItemInCart, CartEmpty>({
18
11
  name: "remove-item",
19
12
  description: "Removes the mem-selected catalog item from the cart.",
@@ -35,7 +28,7 @@ export const RemoveItemBlock = defineEffectBlock<ItemInCart, CartEmpty>({
35
28
  ],
36
29
  },
37
30
  async instanceOptions(page): Promise<readonly WaygraphInstanceOption[]> {
38
- const buttons = page.locator("button.remove:not([hidden])");
31
+ const buttons = page.locator(DemoSel.removeButtons);
39
32
  const n = await buttons.count();
40
33
  const out: WaygraphInstanceOption[] = [];
41
34
  for (let i = 0; i < n; i++) {
@@ -54,6 +47,4 @@ export const RemoveItemBlock = defineEffectBlock<ItemInCart, CartEmpty>({
54
47
  }
55
48
  return out;
56
49
  },
57
- }).stubAfter((ctx) => {
58
- ctx.ring("cart", { selector: DemoSel.cartCount, label: "Cart count", duration: true });
59
50
  });
@@ -2,21 +2,10 @@ import { defineNavBlock, Trait } from "waygraph";
2
2
  import type { Home } from "../../../states/demo.states.js";
3
3
  import { DemoSel, HOME_URL } from "./_sel.js";
4
4
 
5
- /**
6
- * Kind: Nav
7
- * Helper: defineNavBlock
8
- * Route: (app_base)/home/ (synthetic "/")
9
- */
10
5
  export const NavHomeBlock = defineNavBlock<Home>({
11
6
  name: "nav-home",
12
7
  description: "Opens the offline catalog page (file://, no server).",
13
8
  checkpoint: "Home",
14
9
  url: HOME_URL,
15
10
  verify: [Trait.text(DemoSel.title, "Hello Waygraph"), Trait.visible(DemoSel.catalog)],
16
- })
17
- .stubAfter((ctx) => {
18
- ctx.ring("title", { selector: DemoSel.title, label: "Home heading", duration: true });
19
- })
20
- .stubOnError((ctx) => {
21
- ctx.ring("title", { selector: DemoSel.title, label: "Home heading at failure" });
22
- });
11
+ });
@@ -6,15 +6,6 @@ import { RemoveItemBlock } from "./_methods/remove-item.effect.block.js";
6
6
  import { ClearCartBlock } from "./_methods/clear-cart.method.block.js";
7
7
  import { AssertHelloBlock } from "./_methods/assert-hello.method.block.js";
8
8
 
9
- /**
10
- * Kind: Page
11
- * Helper: definePageBlock
12
- * Route: (app_base)/home/
13
- *
14
- * Hub for the offline home screen. Methods hang here for docs/auto grouping;
15
- * edges in flows still reference the method Blocks directly.
16
- * Arrival-only (nav-home already landed on Home).
17
- */
18
9
  export const HomePageBlock = definePageBlock<Home>({
19
10
  name: "page-home",
20
11
  description: "Offline home hub - add/remove effects, clear, assert-hello.",
@@ -1,4 +1,4 @@
1
- // Relative to playwright.config.ts's baseURL (the (app_base) folder, set by `waygraph init`).
1
+ // Relative to baseUrl in config/waygraph.config.ts (the (app_base) folder, set by `waygraph init`).
2
2
  export const HOME_URL = "home/index.html";
3
3
 
4
4
  export const DemoSel = {
@@ -7,6 +7,8 @@ export const DemoSel = {
7
7
  cartCount: "[data-test=cart-count]",
8
8
  catalog: "#catalog",
9
9
  clear: "[data-test=clear-cart]",
10
+ addButtons: "button.add:not([hidden])",
11
+ removeButtons: "button.remove:not([hidden])",
10
12
  addBtn: (id: string) => `[data-test=add-${id}]`,
11
13
  removeBtn: (id: string) => `[data-test=remove-${id}]`,
12
14
  verifiedBanner: "#verified-banner",
@@ -3,29 +3,7 @@ import type { HomeVerified } from "../../../states/demo.states.js";
3
3
  import { EmailLink } from "../../../states/demo.mem-keys.js";
4
4
  import { DemoSel } from "./_sel.js";
5
5
 
6
- /**
7
- * Kind: Nav
8
- * Helper: defineNavBlock
9
- * Route: (app_base)/home/
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
- */
6
+ /** Follows the link `extract-email-link` put in mem. No `requires`: an earlier Block in the chain produces it. */
29
7
  export const NavVerificationLinkBlock = defineNavBlock<HomeVerified>({
30
8
  name: "nav-verification-link",
31
9
  checkpoint: "HomeVerified",
@@ -0,0 +1,21 @@
1
+ import { defineAssertBlock, type Trait } from "waygraph";
2
+ import type { HoppscotchRest } from "../../../../states/demo.states.js";
3
+ import { ExpectedResponseText, ResponseBody } from "../../../../states/demo.mem-keys.js";
4
+
5
+ // Hoppscotch pretty-prints JSON, so compare with whitespace removed from both sides.
6
+ const squash = (text: string): string => text.replace(/\s+/g, "");
7
+
8
+ const bodyContainsExpected: Trait = {
9
+ name: "response-body-contains-expected",
10
+ async check(_page, mem) {
11
+ return squash(mem.get(ResponseBody)).includes(squash(mem.get(ExpectedResponseText)));
12
+ },
13
+ };
14
+
15
+ export const AssertResponseBodyBlock = defineAssertBlock<HoppscotchRest>({
16
+ name: "assert-response-body",
17
+ description: "Confirms the response body read earlier contains the mem-supplied text (whitespace ignored).",
18
+ checkpoint: "HoppscotchRest",
19
+ requires: [ExpectedResponseText],
20
+ verify: [bodyContainsExpected],
21
+ });
@@ -0,0 +1,19 @@
1
+ import { defineAssertBlock, type Trait } from "waygraph";
2
+ import type { HoppscotchRest } from "../../../../states/demo.states.js";
3
+ import { ExpectedResponseHeader, ResponseHeaders } from "../../../../states/demo.mem-keys.js";
4
+
5
+ const headerMatches: Trait = {
6
+ name: "response-header-matches",
7
+ async check(_page, mem) {
8
+ const { name, value } = mem.get(ExpectedResponseHeader.key);
9
+ return mem.get(ResponseHeaders)[name.toLowerCase()] === value;
10
+ },
11
+ };
12
+
13
+ export const AssertResponseHeaderBlock = defineAssertBlock<HoppscotchRest>({
14
+ name: "assert-response-header",
15
+ description: "Confirms a response header read earlier has the mem-supplied value.",
16
+ checkpoint: "HoppscotchRest",
17
+ requires: [ExpectedResponseHeader.key],
18
+ verify: [headerMatches],
19
+ });