waygraph 0.12.6 → 0.12.8

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 (34) hide show
  1. package/README.md +2 -1
  2. package/dist/agent-dive.d.ts +19 -0
  3. package/dist/agent-dive.js +162 -0
  4. package/dist/auto-explore-run.d.ts +5 -0
  5. package/dist/auto-explore-run.js +15 -5
  6. package/dist/cli.d.ts +1 -1
  7. package/dist/cli.js +152 -1
  8. package/dist/traverse-run.d.ts +24 -0
  9. package/dist/traverse-run.js +227 -0
  10. package/package.json +1 -1
  11. package/templates/agents/waygraph-author.agent.md +36 -0
  12. package/templates/agents/waygraph-healer.agent.md +33 -0
  13. package/templates/agents/waygraph-planner.agent.md +35 -0
  14. package/templates/scaffold/README.md +11 -8
  15. package/templates/scaffold/STRUCTURE.md +42 -17
  16. package/templates/scaffold/package.json +13 -8
  17. package/templates/scaffold/playwright.config.ts +15 -0
  18. package/templates/scaffold/scripts/fixture-server.mjs +41 -0
  19. package/templates/scaffold/scripts/with-fixture.mjs +69 -0
  20. package/templates/scaffold/src/blocks/SITE-MAP.md +5 -6
  21. package/templates/scaffold/src/blocks/demo-web/NAV.md +17 -8
  22. package/templates/scaffold/src/blocks/demo-web/demo-sel.ts +14 -0
  23. package/templates/scaffold/src/blocks/demo-web/home.html +65 -0
  24. package/templates/scaffold/src/blocks/demo-web/home.page.block.ts +29 -0
  25. package/templates/scaffold/src/blocks/demo-web/methods/add-item.effect.block.ts +68 -0
  26. package/templates/scaffold/src/blocks/demo-web/methods/assert-hello.method.block.ts +11 -12
  27. package/templates/scaffold/src/blocks/demo-web/methods/clear-cart.method.block.ts +26 -0
  28. package/templates/scaffold/src/blocks/demo-web/methods/remove-item.effect.block.ts +63 -0
  29. package/templates/scaffold/src/blocks/demo-web/nav-home.block.ts +5 -8
  30. package/templates/scaffold/src/flows/example.flow.ts +3 -4
  31. package/templates/scaffold/src/flows/shop.flow.ts +42 -0
  32. package/templates/scaffold/src/states/demo.mem-keys.ts +7 -0
  33. package/templates/scaffold/src/states/demo.states.ts +2 -0
  34. package/templates/scaffold/tests/example.spec.ts +9 -0
@@ -1,20 +1,29 @@
1
1
  # demo-web / (synthetic home)
2
2
 
3
- **URL:** `data:text/html,…` (offline hello - replace with your real `/`)
3
+ **URL:** `http://127.0.0.1:4177/home.html` (offline catalog via `scripts/fixture-server.mjs` - replace with your real `/`)
4
4
 
5
5
  ## Nav-to
6
6
 
7
7
  | From | Block | How |
8
8
  |------|-------|-----|
9
- | start | `nav-home` | `defineNavBlock` `url` → data HTML |
9
+ | start | `nav-home` | `defineNavBlock` `url` → home.html |
10
10
 
11
- ## Methods
11
+ ## Page hub
12
12
 
13
- | Block | Kind | Notes |
14
- |-------|------|-------|
15
- | `assert-hello` | Method | Verify heading; demo stubs + fixtures |
13
+ | Block | Checkpoint | Methods |
14
+ |-------|------------|---------|
15
+ | `page-home` | `Home` | add-item, remove-item, clear-cart, assert-hello |
16
+
17
+ ## Methods / Effects
18
+
19
+ | Block | Kind | Auto |
20
+ |-------|------|------|
21
+ | `assert-hello` | Method | single row |
22
+ | `add-item` | Effect | one row per live Add button (`instanceOptions`) |
23
+ | `remove-item` | Effect | one row per live Remove button |
24
+ | `clear-cart` | Method | single row |
16
25
 
17
26
  ## States / mem-keys
18
27
 
19
- - Checkpoint `Home` after nav
20
- - Checkpoint `HomeVerified` after assert-hello
28
+ - Checkpoints: `Home`, `HomeVerified`, `ItemInCart`, `CartEmpty`
29
+ - Mem: `demo.selectedItem` (`SelectedItem`) - filled by auto or `--data`
@@ -0,0 +1,14 @@
1
+ /** Offline catalog URL — HTTP fixture server (not file://; Flatpak Chromium blocks /tmp file URLs). */
2
+ export const HOME_URL =
3
+ process.env.WAYGRAPH_HOME_URL ??
4
+ `http://${process.env.WAYGRAPH_FIXTURE_HOST || "127.0.0.1"}:${process.env.WAYGRAPH_FIXTURE_PORT || "4177"}/home.html`;
5
+
6
+ export const DemoSel = {
7
+ title: "h1",
8
+ sub: "#sub",
9
+ cartCount: "[data-test=cart-count]",
10
+ catalog: "#catalog",
11
+ clear: "[data-test=clear-cart]",
12
+ addBtn: (id: string) => `[data-test=add-${id}]`,
13
+ removeBtn: (id: string) => `[data-test=remove-${id}]`,
14
+ } as const;
@@ -0,0 +1,65 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Waygraph scaffold demo</title>
6
+ <style>
7
+ body { font-family: system-ui, sans-serif; margin: 2rem; max-width: 32rem; }
8
+ h1 { margin: 0 0 0.5rem; }
9
+ #cart { margin: 1rem 0; font-weight: 600; }
10
+ .item { display: flex; gap: 0.75rem; align-items: center; margin: 0.5rem 0; }
11
+ button { cursor: pointer; }
12
+ </style>
13
+ </head>
14
+ <body>
15
+ <h1>Hello Waygraph</h1>
16
+ <p id="sub">Scaffold demo - Effect / auto / Page hub</p>
17
+ <div id="cart">Cart: <span data-test="cart-count">0</span></div>
18
+ <ul id="catalog">
19
+ <li class="item" data-id="alpha">
20
+ <span class="name">Alpha</span>
21
+ <button type="button" data-test="add-alpha" class="add">Add Alpha</button>
22
+ <button type="button" data-test="remove-alpha" class="remove" hidden>Remove Alpha</button>
23
+ </li>
24
+ <li class="item" data-id="beta">
25
+ <span class="name">Beta</span>
26
+ <button type="button" data-test="add-beta" class="add">Add Beta</button>
27
+ <button type="button" data-test="remove-beta" class="remove" hidden>Remove Beta</button>
28
+ </li>
29
+ <li class="item" data-id="gamma">
30
+ <span class="name">Gamma</span>
31
+ <button type="button" data-test="add-gamma" class="add">Add Gamma</button>
32
+ <button type="button" data-test="remove-gamma" class="remove" hidden>Remove Gamma</button>
33
+ </li>
34
+ </ul>
35
+ <button type="button" data-test="clear-cart" id="clear">Clear cart</button>
36
+ <script>
37
+ const countEl = document.querySelector("[data-test=cart-count]");
38
+ const sync = () => {
39
+ const n = document.querySelectorAll(".remove:not([hidden])").length;
40
+ countEl.textContent = String(n);
41
+ };
42
+ document.querySelectorAll(".item").forEach((row) => {
43
+ const add = row.querySelector(".add");
44
+ const remove = row.querySelector(".remove");
45
+ add.addEventListener("click", () => {
46
+ add.hidden = true;
47
+ remove.hidden = false;
48
+ sync();
49
+ });
50
+ remove.addEventListener("click", () => {
51
+ remove.hidden = true;
52
+ add.hidden = false;
53
+ sync();
54
+ });
55
+ });
56
+ document.getElementById("clear").addEventListener("click", () => {
57
+ document.querySelectorAll(".item").forEach((row) => {
58
+ row.querySelector(".add").hidden = false;
59
+ row.querySelector(".remove").hidden = true;
60
+ });
61
+ sync();
62
+ });
63
+ </script>
64
+ </body>
65
+ </html>
@@ -0,0 +1,29 @@
1
+ import { definePageBlock, Trait } from "waygraph";
2
+ import type { Home } from "../../states/demo.states.js";
3
+ import { DemoSel } from "./demo-sel.js";
4
+ import { AddItemBlock } from "./methods/add-item.effect.block.js";
5
+ import { RemoveItemBlock } from "./methods/remove-item.effect.block.js";
6
+ import { ClearCartBlock } from "./methods/clear-cart.method.block.js";
7
+ import { AssertHelloBlock } from "./methods/assert-hello.method.block.js";
8
+
9
+ /**
10
+ * Kind: Page
11
+ * Helper: definePageBlock
12
+ * Route: demo-web/
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
+ export const HomePageBlock = definePageBlock<Home>({
19
+ name: "page-home",
20
+ description: "Offline home hub - add/remove effects, clear, assert-hello.",
21
+ checkpoint: "Home",
22
+ verify: [Trait.visible(DemoSel.catalog)],
23
+ methods: {
24
+ addItem: () => AddItemBlock,
25
+ removeItem: () => RemoveItemBlock,
26
+ clearCart: () => ClearCartBlock,
27
+ assertHello: () => AssertHelloBlock,
28
+ },
29
+ });
@@ -0,0 +1,68 @@
1
+ import {
2
+ defineEffectBlock,
3
+ checkpoint,
4
+ type Checkpoint,
5
+ type WaygraphInstanceOption,
6
+ } from "waygraph";
7
+ import type { ItemInCart } from "../../../states/demo.states.js";
8
+ import { SelectedItem } from "../../../states/demo.mem-keys.js";
9
+ import { DemoSel } from "../demo-sel.js";
10
+
11
+ /**
12
+ * Kind: Effect
13
+ * Helper: defineEffectBlock
14
+ * Route: demo-web/methods/
15
+ *
16
+ * Mutates one catalog row from mem. `waygraph auto` lists one "Add …" row per
17
+ * live Add button via instanceOptions.
18
+ */
19
+ export const AddItemBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>({
20
+ name: "add-item",
21
+ description: "Adds the mem-selected catalog item to the cart.",
22
+ requires: [SelectedItem.key],
23
+ instruction: {
24
+ async act(page, _input, mem) {
25
+ const { id } = mem.get(SelectedItem.key);
26
+ await page.locator(DemoSel.addBtn(id)).click();
27
+ },
28
+ resolve: () => checkpoint("ItemInCart"),
29
+ verify: [
30
+ {
31
+ name: "item-added",
32
+ async check(page, mem) {
33
+ const { id } = mem.get(SelectedItem.key);
34
+ return page.locator(DemoSel.removeBtn(id)).isVisible();
35
+ },
36
+ },
37
+ ],
38
+ stubBefore: {
39
+ add: { selector: DemoSel.catalog, label: "Catalog" },
40
+ },
41
+ stubAfter: {
42
+ cart: { selector: DemoSel.cartCount, label: "Cart count", duration: true },
43
+ },
44
+ stubOnError: {
45
+ cart: { selector: DemoSel.cartCount, label: "Cart at failure", tag: "FAIL" },
46
+ },
47
+ },
48
+ async instanceOptions(page): Promise<readonly WaygraphInstanceOption[]> {
49
+ const buttons = page.locator("button.add:not([hidden])");
50
+ const n = await buttons.count();
51
+ const out: WaygraphInstanceOption[] = [];
52
+ for (let i = 0; i < n; i++) {
53
+ const btn = buttons.nth(i);
54
+ const label = ((await btn.textContent()) || "").trim();
55
+ const testId = await btn.getAttribute("data-test");
56
+ const id = (testId || "").replace(/^add-/, "");
57
+ if (!id) continue;
58
+ out.push({
59
+ id,
60
+ label: label || `Add ${id}`,
61
+ key: SelectedItem.key,
62
+ value: { id, name: id },
63
+ highlight: DemoSel.addBtn(id),
64
+ });
65
+ }
66
+ return out;
67
+ },
68
+ });
@@ -1,48 +1,47 @@
1
1
  import { defineMethodBlock, checkpoint, Trait } from "waygraph";
2
+ import { DemoSel } from "../demo-sel.js";
2
3
 
3
4
  /**
4
5
  * Kind: Method
5
6
  * Helper: defineMethodBlock
6
7
  * Route: demo-web/methods/
7
8
  *
8
- * Same-URL assert after nav - shows stubBefore / stubAfter / stubOnError shape.
9
+ * Same-URL assert after nav - stubs + fixtures + YAP slide.
9
10
  */
10
11
  export const AssertHelloBlock = defineMethodBlock({
11
12
  name: "assert-hello",
12
13
  description: "Confirms the hello heading is still visible after arrival.",
13
14
  instruction: {
14
- async act(_page) {
15
- // No DOM mutation - observe/verify own the proof.
16
- },
15
+ async act(_page) {},
17
16
  async observe(page) {
18
- await page.locator("h1").first().waitFor({ state: "visible", timeout: 5_000 });
17
+ await page.locator(DemoSel.title).first().waitFor({ state: "visible", timeout: 5_000 });
19
18
  return "ok" as const;
20
19
  },
21
20
  resolve: () => checkpoint("HomeVerified"),
22
- verify: [Trait.text("h1", "Hello Waygraph"), Trait.visible("#sub")],
21
+ verify: [Trait.text(DemoSel.title, "Hello Waygraph"), Trait.visible(DemoSel.sub)],
23
22
  stubBefore: {
24
- title: { selector: "h1", label: "Heading about to check" },
23
+ title: { selector: DemoSel.title, label: "Heading about to check" },
25
24
  },
26
25
  stubAfter: {
27
26
  title: {
28
- selector: "h1",
27
+ selector: DemoSel.title,
29
28
  label: "Hello confirmed",
30
29
  detail: "Offline scaffold proof point.",
31
30
  duration: true,
32
31
  },
33
- sub: { selector: "#sub", label: "Subcopy", duration: 1500 },
32
+ sub: { selector: DemoSel.sub, label: "Subcopy", duration: 1500 },
34
33
  },
35
34
  stubOnError: {
36
35
  title: {
37
- selector: "h1",
36
+ selector: DemoSel.title,
38
37
  label: "Heading at failure",
39
38
  tag: "FAIL",
40
39
  },
41
40
  },
42
41
  slides: [
43
42
  {
44
- caption: "Scaffold is route-shaped",
45
- detail: "demo-web/ = synthetic /. Grow folders per real page.tsx.",
43
+ caption: "Scaffold covers auto + narration",
44
+ detail: "Page hub, Effect instanceOptions, stubs, fixtures, YAP.",
46
45
  tag: "YAP",
47
46
  duration: true,
48
47
  },
@@ -0,0 +1,26 @@
1
+ import { defineMethodBlock, checkpoint, Trait } from "waygraph";
2
+ import { DemoSel } from "../demo-sel.js";
3
+
4
+ /**
5
+ * Kind: Method
6
+ * Helper: defineMethodBlock
7
+ * Route: demo-web/methods/
8
+ */
9
+ export const ClearCartBlock = defineMethodBlock({
10
+ name: "clear-cart",
11
+ description: "Clears every item via the Clear cart control.",
12
+ instruction: {
13
+ async act(page) {
14
+ await page.locator(DemoSel.clear).click();
15
+ },
16
+ resolve: () => checkpoint("CartEmpty"),
17
+ verify: [Trait.text(DemoSel.cartCount, "0")],
18
+ stubBefore: {
19
+ clear: { selector: DemoSel.clear, label: "Clear cart" },
20
+ },
21
+ stubAfter: {
22
+ cart: { selector: DemoSel.cartCount, label: "Empty cart", duration: true },
23
+ },
24
+ stubOnError: {},
25
+ },
26
+ });
@@ -0,0 +1,63 @@
1
+ import {
2
+ defineEffectBlock,
3
+ checkpoint,
4
+ type Checkpoint,
5
+ type WaygraphInstanceOption,
6
+ } from "waygraph";
7
+ import type { CartEmpty } from "../../../states/demo.states.js";
8
+ import { SelectedItem } from "../../../states/demo.mem-keys.js";
9
+ import { DemoSel } from "../demo-sel.js";
10
+
11
+ /**
12
+ * Kind: Effect
13
+ * Helper: defineEffectBlock
14
+ * Route: demo-web/methods/
15
+ *
16
+ * Removes one in-cart row. Auto menus one row per visible Remove button.
17
+ */
18
+ export const RemoveItemBlock = defineEffectBlock<Checkpoint<string>, CartEmpty>({
19
+ name: "remove-item",
20
+ description: "Removes the mem-selected catalog item from the cart.",
21
+ requires: [SelectedItem.key],
22
+ instruction: {
23
+ async act(page, _input, mem) {
24
+ const { id } = mem.get(SelectedItem.key);
25
+ await page.locator(DemoSel.removeBtn(id)).click();
26
+ },
27
+ resolve: () => checkpoint("CartEmpty"),
28
+ verify: [
29
+ {
30
+ name: "item-removed",
31
+ async check(page, mem) {
32
+ const { id } = mem.get(SelectedItem.key);
33
+ return page.locator(DemoSel.addBtn(id)).isVisible();
34
+ },
35
+ },
36
+ ],
37
+ stubBefore: {},
38
+ stubAfter: {
39
+ cart: { selector: DemoSel.cartCount, label: "Cart count", duration: true },
40
+ },
41
+ stubOnError: {},
42
+ },
43
+ async instanceOptions(page): Promise<readonly WaygraphInstanceOption[]> {
44
+ const buttons = page.locator("button.remove:not([hidden])");
45
+ const n = await buttons.count();
46
+ const out: WaygraphInstanceOption[] = [];
47
+ for (let i = 0; i < n; i++) {
48
+ const btn = buttons.nth(i);
49
+ const label = ((await btn.textContent()) || "").trim();
50
+ const testId = await btn.getAttribute("data-test");
51
+ const id = (testId || "").replace(/^remove-/, "");
52
+ if (!id) continue;
53
+ out.push({
54
+ id,
55
+ label: label || `Remove ${id}`,
56
+ key: SelectedItem.key,
57
+ value: { id, name: id },
58
+ highlight: DemoSel.removeBtn(id),
59
+ });
60
+ }
61
+ return out;
62
+ },
63
+ });
@@ -1,8 +1,5 @@
1
1
  import { defineNavBlock, Trait } from "waygraph";
2
-
3
- /** Offline hello page - swap `url` for a real app route when you graduate. */
4
- export const HOME_URL =
5
- 'data:text/html,<!doctype html><html><body><h1>Hello Waygraph</h1><p id="sub">Scaffold demo</p></body></html>';
2
+ import { DemoSel, HOME_URL } from "./demo-sel.js";
6
3
 
7
4
  /**
8
5
  * Kind: Nav
@@ -11,15 +8,15 @@ export const HOME_URL =
11
8
  */
12
9
  export const NavHomeBlock = defineNavBlock({
13
10
  name: "nav-home",
14
- description: "Opens the offline hello page (data: URL).",
11
+ description: "Opens the offline catalog page (HTTP fixture :4177).",
15
12
  checkpoint: "Home",
16
13
  url: HOME_URL,
17
- verify: [Trait.text("h1", "Hello Waygraph")],
14
+ verify: [Trait.text(DemoSel.title, "Hello Waygraph"), Trait.visible(DemoSel.catalog)],
18
15
  stubBefore: {},
19
16
  stubAfter: {
20
- title: { selector: "h1", label: "Home heading", duration: true },
17
+ title: { selector: DemoSel.title, label: "Home heading", duration: true },
21
18
  },
22
19
  stubOnError: {
23
- title: { selector: "h1", label: "Home heading at failure" },
20
+ title: { selector: DemoSel.title, label: "Home heading at failure" },
24
21
  },
25
22
  });
@@ -1,13 +1,12 @@
1
1
  import { Engine, start, end, withTitle, withHighlightFixtures } from "waygraph";
2
2
  import { NavHomeBlock } from "../blocks/demo-web/nav-home.block.js";
3
3
  import { AssertHelloBlock } from "../blocks/demo-web/methods/assert-hello.method.block.js";
4
+ // Page hub registered for auto/docs grouping (arrival-only after nav).
5
+ import "../blocks/demo-web/home.page.block.js";
4
6
 
5
7
  const engine = new Engine();
6
8
 
7
- /**
8
- * Offline example: nav to data: hello page, then assert heading.
9
- * Flow fixtures override ticket-style copy on stubs (demo narration).
10
- */
9
+ /** Smoke: nav + assert (no mem). Green offline. */
11
10
  export const exampleFlow = withHighlightFixtures(
12
11
  withTitle(
13
12
  engine.defineFlow([start, NavHomeBlock, AssertHelloBlock, end]),
@@ -0,0 +1,42 @@
1
+ import {
2
+ Engine,
3
+ start,
4
+ end,
5
+ withTitle,
6
+ withHighlightFixtures,
7
+ withSessionReset,
8
+ } from "waygraph";
9
+ import { NavHomeBlock } from "../blocks/demo-web/nav-home.block.js";
10
+ import { AddItemBlock } from "../blocks/demo-web/methods/add-item.effect.block.js";
11
+ import { ClearCartBlock } from "../blocks/demo-web/methods/clear-cart.method.block.js";
12
+ import "../blocks/demo-web/home.page.block.js";
13
+
14
+ const engine = new Engine();
15
+
16
+ /**
17
+ * Effect + Method demo (set mem for add-item, or use `waygraph auto` which
18
+ * fills SelectedItem via instanceOptions).
19
+ *
20
+ * waygraph demo --blocks shopFlow --data '{"demo.selectedItem":{"id":"alpha","name":"Alpha"}}'
21
+ * waygraph auto # after landing on home, pick Add Alpha / Add Beta / …
22
+ */
23
+ export const shopFlow = withHighlightFixtures(
24
+ withTitle(
25
+ withSessionReset(
26
+ engine.defineFlow([start, NavHomeBlock, AddItemBlock, ClearCartBlock, end]),
27
+ ),
28
+ "Scaffold: Shop (Effect + clear)",
29
+ ),
30
+ {
31
+ "add-item": {
32
+ stubAfter: {
33
+ cart: {
34
+ label: "AC · item in cart",
35
+ detail: "Cart count bumped after Add.",
36
+ tag: "AC",
37
+ duration: true,
38
+ },
39
+ },
40
+ },
41
+ },
42
+ );
@@ -0,0 +1,7 @@
1
+ import { keyGroup } from "waygraph";
2
+
3
+ /**
4
+ * Which catalog row `add-item` / `remove-item` act on.
5
+ * `waygraph auto` fills this via Effect `instanceOptions` (one menu row per live button).
6
+ */
7
+ export const SelectedItem = keyGroup<{ id: string; name: string }>("demo.selectedItem");
@@ -2,3 +2,5 @@ import type { Checkpoint } from "waygraph";
2
2
 
3
3
  export type Home = Checkpoint<"Home">;
4
4
  export type HomeVerified = Checkpoint<"HomeVerified">;
5
+ export type ItemInCart = Checkpoint<"ItemInCart">;
6
+ export type CartEmpty = Checkpoint<"CartEmpty">;
@@ -1,8 +1,17 @@
1
1
  import { test, expect } from "@playwright/test";
2
2
  import { MemPage, checkpoint } from "waygraph";
3
3
  import { exampleFlow } from "../src/flows/example.flow.js";
4
+ import { shopFlow } from "../src/flows/shop.flow.js";
5
+ import { SelectedItem } from "../src/states/demo.mem-keys.js";
4
6
 
5
7
  test("example flow reaches HomeVerified", async ({ context }) => {
6
8
  const result = await exampleFlow.run(context, new MemPage());
7
9
  expect(result).toEqual(checkpoint("HomeVerified"));
8
10
  });
11
+
12
+ test("shop flow adds then clears (Effect + Method)", async ({ context }) => {
13
+ const mem = new MemPage();
14
+ mem.set(SelectedItem.key, { id: "alpha", name: "Alpha" });
15
+ const result = await shopFlow.run(context, mem);
16
+ expect(result).toEqual(checkpoint("CartEmpty"));
17
+ });