waygraph 0.15.18 → 0.15.19

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.
package/README.md CHANGED
@@ -298,10 +298,26 @@ code, an autonomous agent authoring Blocks) - it warns, never fails the process.
298
298
 
299
299
  **`waygraph typecheck [project]`** runs `tsc --noEmit` then the same **bad-practice** scan
300
300
  (wildcard `Checkpoint<string>`, assert blocks missing type args, multi-`.fill()` Methods,
301
- fill+click in one Method). Practices warn only unless `tsc` fails; use `--no-practices` to
302
- skip the scan. Opt out per file: `// waygraph-ignore-practices` or
303
- `// waygraph-ignore: multi-input, combined-action`. Print full agent guidance:
304
- `waygraph --skill-convention`.
301
+ fill+click in one Method, empty `verify` on a transition Block or `defineAssertBlock`).
302
+ Practices warn only unless `tsc` fails; use `--no-practices` to skip the scan. Opt out per
303
+ file: `// waygraph-ignore-practices` or `// waygraph-ignore: multi-input, combined-action,
304
+ empty-verify`. Print full agent guidance: `waygraph --skill-convention`.
305
+
306
+ **Empty verify is the one that matters most for codegen'd Blocks.** A framework can enforce
307
+ that a check *exists* and stays structurally honest (typed, wired, right file) - it cannot
308
+ author the semantic content of that check, since only someone who knows what "success" means
309
+ for this specific app can write it. That's a correct place to draw the line, but it shifts
310
+ all the risk onto whether anything catches a Block left with a placeholder `verify: []` by
311
+ the time it ships - which would otherwise pass every static check, the orphan scan, and
312
+ `tsc`, and run green in CI forever, because an empty array isn't a wrong check, it's the
313
+ *absence* of one. `waygraph check`/`typecheck` now flag exactly that: a `defineMethodBlock`/
314
+ `defineEffectBlock` whose `In`/`Out` Checkpoints actually differ (a real transition, not a
315
+ self-loop like `fill-username`) with no `verify` key or a literal `verify: []`, and the same
316
+ for `defineAssertBlock` (whose entire job is checking). Silence it deliberately, the same as
317
+ any other practice warning, with `// waygraph-ignore: empty-verify` - e.g. when a downstream
318
+ `defineAssertBlock` right after it in the `.flow.ts` is what actually confirms the transition
319
+ (see `templates/scaffold`'s own `open-message.method.block.ts`, confirmed by
320
+ `AssertEmailContentBlock` right after it, not by its own `verify`).
305
321
 
306
322
  ### Demo narration
307
323
 
@@ -998,25 +1014,22 @@ really a Nav/Page/Assert/Method from this package's own factories.
998
1014
  |------|---------|
999
1015
  | Watch Sauce Demo step-through (temp dir only) | `npx waygraph try demo` |
1000
1016
  | Interactive explore (Add/Remove/Open details from live page) | `cd examples/saucedemo && npm i && npx waygraph auto` |
1001
- | Scaffold a new **offline** project (green `npm test` on local fixture `:4177`) | `npx create-waygraph my-app` **or** `npx waygraph init my-app` |
1017
+ | Scaffold a new **offline** project (green `npm test` on local fixture `:4177`) | `npx waygraph init my-app` |
1002
1018
  | Install coding-agent defs for diving an app into Blocks | `npx waygraph agent-dive --loop claude` (also `opencode` / `cursor` / `vscode`) |
1003
1019
  | Print a packaged agent skill to stdout | `npx waygraph --skill` (list) / `--skill-pilot` / `--skill-pilot-blind` / `--skill-convention` |
1004
1020
  | Add waygraph to an existing repo | `npm install waygraph @playwright/test` |
1005
1021
 
1006
- **Scaffold is not hidden inside waygraph alone** - the offline starter also ships as
1007
- [`create-waygraph`](https://github.com/deviate-dv8/create-waygraph) on npm (same template
1008
- as `waygraph init` since 0.7.5). Use whichever entry you already have:
1009
- `npx create-waygraph my-app` when reading the GitHub Pages docs, or `npx waygraph init my-app`
1010
- when the CLI is already installed. After either:
1011
-
1012
1022
  ```bash
1023
+ npx waygraph init my-app
1013
1024
  cd my-app && npm install && npx playwright install chromium && npm test
1014
1025
  ```
1015
1026
 
1016
1027
  `try demo` is different: live saucedemo.com, step-through **Sign In -> Shop & Checkout ->
1017
1028
  blocked Viewer login**, then headless tests. Permanent full example:
1018
- [`examples/saucedemo`](./examples/saucedemo). Offline empty scaffold: `init` /
1019
- `create-waygraph`.
1029
+ [`examples/saucedemo`](./examples/saucedemo). Offline empty scaffold: `init`.
1030
+
1031
+ **`create-waygraph` (npm) is deprecated** - it was the identical `init` template shipped as
1032
+ a second package for no real benefit. Use `npx waygraph init my-app` instead.
1020
1033
 
1021
1034
  ```bash
1022
1035
  # From this checkout, after npm run build:
@@ -1090,8 +1103,8 @@ suite - it forwards to the local `node_modules/.bin/playwright` (falling back to
1090
1103
  `npx playwright` if the project hasn't installed it locally yet), so `waygraph test` behaves
1091
1104
  exactly like `playwright test`, and anything after it (`--grep`, a spec path, `--headed`, …)
1092
1105
  passes straight through. `waygraph test ui` (or `waygraph test --ui`) launches Playwright's
1093
- interactive UI Mode the same way. Scaffolded projects (`waygraph init`/`create-waygraph`)
1094
- wire this up as `npm test` / `npm run test:ui`, and ship `trace: "retain-on-failure"` plus
1106
+ interactive UI Mode the same way. Scaffolded projects (`waygraph init`) wire this up as
1107
+ `npm test` / `npm run test:ui`, and ship `trace: "retain-on-failure"` plus
1095
1108
  the `html` reporter in `playwright.config.ts`, so a failing `waygraph test` already has a
1096
1109
  trace waiting - `waygraph test report` opens the HTML report, and
1097
1110
  `waygraph test show-trace <trace.zip>` opens one trace directly (both forward to Playwright's
@@ -1321,7 +1334,7 @@ src/
1321
1334
  index.ts public barrel
1322
1335
  examples/saucedemo/ live Sauce Demo (Page + methods/ + Sel)
1323
1336
  templates/quickstart/ try demo / init template (mirrors sauce)
1324
- templates/scaffold/ offline init/create-waygraph scaffold template
1337
+ templates/scaffold/ offline waygraph init scaffold template
1325
1338
  templates/agents/ waygraph agent-dive coding-agent definitions
1326
1339
  docs/ GitHub Pages static HTML + docs/proposals/ RFCs
1327
1340
  openspec/ spec-driven planning: specs/ (current), changes/ (in-flight),
@@ -1,4 +1,4 @@
1
- export type PracticeKind = "wildcard-checkpoint" | "assert-no-type-arg" | "multi-input" | "combined-action";
1
+ export type PracticeKind = "wildcard-checkpoint" | "assert-no-type-arg" | "multi-input" | "combined-action" | "empty-verify";
2
2
  export interface PracticeWarning {
3
3
  kind: PracticeKind;
4
4
  file: string;
@@ -9,6 +9,19 @@ import { relative } from "node:path";
9
9
  const WILDCARD_CHECKPOINT = /\bdefine(?:Method|Effect|Page|Nav|MemNav|NavClick|Action)Block\s*<[^>]*\bCheckpoint\s*<\s*string\s*>/;
10
10
  /** defineAssertBlock({ … }) with no explicit Out type — defaults to wildcard. */
11
11
  const ASSERT_NO_TYPE_ARG = /\bdefineAssertBlock\s*\(\s*\{/;
12
+ /** defineAssertBlock, any form - used to scope the empty-verify check below. */
13
+ const IS_ASSERT_BLOCK = /\bdefineAssertBlock\s*[<(]/;
14
+ /**
15
+ * define(Method|Effect)Block<In, Out> with two BARE identifier type args
16
+ * (never a nested generic like Checkpoint<string> - that's WILDCARD_CHECKPOINT's
17
+ * own warning) - lets a real transition (In !== Out) be told apart from a
18
+ * self-loop (In === Out, e.g. fill-username) using only source text.
19
+ */
20
+ const METHOD_EFFECT_GENERIC = /\bdefine(?:Method|Effect)Block\s*<\s*([A-Za-z_$][\w$]*)\s*,\s*([A-Za-z_$][\w$]*)\s*>/;
21
+ /** `verify: []` - a literal empty array, the codegen-stub shape this check exists for. */
22
+ const EMPTY_VERIFY_ARRAY = /\bverify\s*:\s*\[\s*\]/;
23
+ /** Any `verify:` key at all, empty or not. */
24
+ const HAS_VERIFY_KEY = /\bverify\s*:/;
12
25
  const SCAN_GLOB = /\.(block|flow)\.ts$/;
13
26
  const METHOD_OR_EFFECT_FILE = /\.(method|effect)\.block\.ts$/;
14
27
  const METHOD_OR_EFFECT_DEFINE = /\bdefine(?:Method|Effect)Block\b/;
@@ -80,6 +93,27 @@ export function collectPracticeWarnings(projectDir, walkDir, opts) {
80
93
  detail: "Method/Effect act() mixes .fill() and .click() — fill and submit must be separate Blocks (see examples/saucedemo login split)",
81
94
  });
82
95
  }
96
+ const genericMatch = METHOD_EFFECT_GENERIC.exec(src);
97
+ const isTransition = !!genericMatch && genericMatch[1] !== genericMatch[2];
98
+ if (isTransition &&
99
+ !isIgnored("empty-verify", ignore) &&
100
+ (!HAS_VERIFY_KEY.test(src) || EMPTY_VERIFY_ARRAY.test(src))) {
101
+ warnings.push({
102
+ kind: "empty-verify",
103
+ file: rel,
104
+ detail: `transition Block (${genericMatch[1]} → ${genericMatch[2]}) has no verify checks — resolve() moving ` +
105
+ "to a new Checkpoint is never confirmed by anything; add a Trait (or defineAssertBlock right after it), " +
106
+ "or opt out explicitly with // waygraph-ignore: empty-verify if that's intentional",
107
+ });
108
+ }
109
+ }
110
+ if (!isIgnored("empty-verify", ignore) && IS_ASSERT_BLOCK.test(src) && EMPTY_VERIFY_ARRAY.test(src)) {
111
+ warnings.push({
112
+ kind: "empty-verify",
113
+ file: rel,
114
+ detail: "defineAssertBlock's verify array is empty — an assert Block whose only job is checking will pass " +
115
+ "trivially forever; fill in real Traits or remove the stub",
116
+ });
83
117
  }
84
118
  }
85
119
  return warnings;
@@ -94,6 +128,8 @@ export function practiceKindLabel(kind) {
94
128
  return "multiple inputs in one Block";
95
129
  case "combined-action":
96
130
  return "fill and click in one Block";
131
+ case "empty-verify":
132
+ return "empty verify — transition unconfirmed";
97
133
  default: {
98
134
  const _exhaustive = kind;
99
135
  return _exhaustive;
File without changes
@@ -0,0 +1,8 @@
1
+ {
2
+ "sessionId": "9f0287e6",
3
+ "socketPath": "/tmp/waygraph-auto/9f0287e6.sock",
4
+ "pid": 2653914,
5
+ "projectDir": "/home/dan/Desktop/Projects/waygraph/examples/saucedemo",
6
+ "startedAt": "2026-09-23T01:33:06.508Z",
7
+ "headless": false
8
+ }
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.15.18",
3
+ "version": "0.15.19",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",
@@ -1,3 +1,4 @@
1
+ // waygraph-ignore: empty-verify (confirmed by AssertEmailContentBlock right after it in mail-verify.flow.ts)
1
2
  import { defineMethodBlock, checkpoint } from "waygraph";
2
3
  import type { MailpitInbox, MailpitMessageOpen } from "../../../../states/demo.states.js";
3
4
  import { ExpectedRecipient } from "../../../../states/demo.mem-keys.js";
@@ -24,9 +24,9 @@ against a live site, use `--skill-pilot-blind`.
24
24
  an explicit type argument when it sits between two specifically-typed Blocks in a Flow.
25
25
  Not just static content or an action's result - it also names a *feature state* worth a
26
26
  Checkpoint of its own (a submit button greyed out until required fields are filled, a
27
- field error under one input, a stale "Save" re-enabling). No built-in `Trait.disabled` -
28
- write the state condition as a custom Trait (`isDisabled()`, a class, `aria-disabled`),
29
- same as any other Trait: real DOM check, no inline selector.
27
+ field error under one input, a stale "Save" re-enabling). `Trait.disabled(selector)` /
28
+ `Trait.enabled(selector)` cover that directly; write a custom Trait only for a state
29
+ condition neither one expresses.
30
30
  5. **`requires` = externally supplied.** A key an earlier Block in the same chain produces
31
31
  is never listed under `requires` - preflight checks the whole chain up front.
32
32
  6. **Keep Checkpoints typed — never `Checkpoint<string>`.** Block helpers take concrete
@@ -37,6 +37,16 @@ against a live site, use `--skill-pilot-blind`.
37
37
  7. **One input/action per Method Block.** No block that `.fill()`s two fields or `.fill()` + `.click()` in one
38
38
  `act()` — split like saucedemo login (`fill-username`, `fill-password`, `submit-login`). Opt out per file:
39
39
  `// waygraph-ignore: multi-input` or `// waygraph-ignore-practices`.
40
+ 8. **A transition never ships with an empty `verify`.** A framework can enforce that a check
41
+ *exists*; it can't author the semantic content of one - only whoever knows what "success"
42
+ means for this app can write it. That's the right line to draw, but it means an unfilled
43
+ `verify: []` placeholder (codegen's honest way of not guessing) is indistinguishable from
44
+ a Block that was never given real verification at all, and it passes `check`/`typecheck`/
45
+ the orphan scan/`tsc` silently unless something is actually looking for it. Something is:
46
+ `defineMethodBlock`/`defineEffectBlock` where `In`/`Out` genuinely differ (a transition,
47
+ not a self-loop) needs a non-empty `verify`, same for `defineAssertBlock`. Opt out only
48
+ when a *different* Block right after it in the `.flow.ts` is what actually confirms the
49
+ transition (e.g. an `AssertXBlock`) - `// waygraph-ignore: empty-verify`.
40
50
 
41
51
  ## Block kinds (file convention)
42
52