@petukhovart/agent-view 0.11.0 → 0.13.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.
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +59 -39
- package/dist/cdp/transport.d.ts.map +1 -1
- package/dist/cdp/transport.js +262 -1
- package/dist/cdp/transport.js.map +1 -1
- package/dist/cdp/types.d.ts +93 -0
- package/dist/cdp/types.d.ts.map +1 -1
- package/dist/cdp/types.js +15 -0
- package/dist/cdp/types.js.map +1 -1
- package/dist/cli/commands/dialog.d.ts +11 -0
- package/dist/cli/commands/dialog.d.ts.map +1 -0
- package/dist/cli/commands/dialog.js +30 -0
- package/dist/cli/commands/dialog.js.map +1 -0
- package/dist/cli/commands/upload.d.ts +10 -0
- package/dist/cli/commands/upload.d.ts.map +1 -0
- package/dist/cli/commands/upload.js +35 -0
- package/dist/cli/commands/upload.js.map +1 -0
- package/dist/cli/index.js +71 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/inspectors/dialog/index.d.ts +20 -0
- package/dist/inspectors/dialog/index.d.ts.map +1 -0
- package/dist/inspectors/dialog/index.js +58 -0
- package/dist/inspectors/dialog/index.js.map +1 -0
- package/dist/server/server.d.ts +32 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +184 -0
- package/dist/server/server.js.map +1 -1
- package/dist/server/tauri-dialog-shim.d.ts +27 -0
- package/dist/server/tauri-dialog-shim.d.ts.map +1 -0
- package/dist/server/tauri-dialog-shim.js +55 -0
- package/dist/server/tauri-dialog-shim.js.map +1 -0
- package/package.json +1 -1
- package/skills/verify/SKILL.md +129 -23
- package/skills/verify-recipe/SKILL.md +0 -286
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** What the arm script reports back from the page. */
|
|
2
|
+
export var TauriShimResult;
|
|
3
|
+
(function (TauriShimResult) {
|
|
4
|
+
TauriShimResult["Armed"] = "armed";
|
|
5
|
+
/** The window is not a Tauri webview — nothing to patch, and nothing is wrong. */
|
|
6
|
+
TauriShimResult["NoTauri"] = "no-tauri";
|
|
7
|
+
})(TauriShimResult || (TauriShimResult = {}));
|
|
8
|
+
const STORE_KEY = '__agentViewTauriDialog__';
|
|
9
|
+
/**
|
|
10
|
+
* Tauri opens its file dialog in Rust, past the webview, so CDP cannot see it
|
|
11
|
+
* and cannot close it. The one seam is the JS side of the call:
|
|
12
|
+
* `@tauri-apps/plugin-dialog` resolves `window.__TAURI_INTERNALS__.invoke` at
|
|
13
|
+
* call time, so replacing that function answers the dialog before the Rust side
|
|
14
|
+
* ever runs. The patch survives repeated arming but not a navigation — a fresh
|
|
15
|
+
* document has a fresh `window`, and the window must be armed again.
|
|
16
|
+
*/
|
|
17
|
+
export function buildTauriArmScript(arm) {
|
|
18
|
+
return `(() => {
|
|
19
|
+
const internals = window.__TAURI_INTERNALS__;
|
|
20
|
+
if (!internals || typeof internals.invoke !== 'function') return ${JSON.stringify(TauriShimResult.NoTauri)};
|
|
21
|
+
let store = window[${JSON.stringify(STORE_KEY)}];
|
|
22
|
+
// Disarming a window that was never armed must not install the patch: a
|
|
23
|
+
// command whose whole purpose is to remove interference cannot add some.
|
|
24
|
+
if (!store && ${JSON.stringify(arm === null)}) return ${JSON.stringify(TauriShimResult.NoTauri)};
|
|
25
|
+
if (!store) {
|
|
26
|
+
store = window[${JSON.stringify(STORE_KEY)}] = { arm: null, fired: [] };
|
|
27
|
+
const original = internals.invoke.bind(internals);
|
|
28
|
+
internals.invoke = function (cmd, args, opts) {
|
|
29
|
+
const arm = store.arm;
|
|
30
|
+
if (arm && (cmd === 'plugin:dialog|open' || cmd === 'plugin:dialog|save')) {
|
|
31
|
+
store.arm = null;
|
|
32
|
+
store.fired.push({ ts: Date.now(), cmd: cmd, cancel: arm.kind === 'cancel' });
|
|
33
|
+
if (store.fired.length > 20) store.fired.shift();
|
|
34
|
+
if (arm.kind === 'cancel') return Promise.resolve(null);
|
|
35
|
+
const multiple = cmd === 'plugin:dialog|open' && !!(args && args.options && args.options.multiple);
|
|
36
|
+
return Promise.resolve(multiple ? arm.files : (arm.files[0] === undefined ? null : arm.files[0]));
|
|
37
|
+
}
|
|
38
|
+
return original(cmd, args, opts);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
store.arm = ${JSON.stringify(arm)};
|
|
42
|
+
return ${JSON.stringify(TauriShimResult.Armed)};
|
|
43
|
+
})()`;
|
|
44
|
+
}
|
|
45
|
+
export function buildTauriStatusScript() {
|
|
46
|
+
return `(() => {
|
|
47
|
+
const store = window[${JSON.stringify(STORE_KEY)}];
|
|
48
|
+
return {
|
|
49
|
+
patched: !!store,
|
|
50
|
+
armed: !!(store && store.arm),
|
|
51
|
+
fired: store ? store.fired : [],
|
|
52
|
+
};
|
|
53
|
+
})()`;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=tauri-dialog-shim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tauri-dialog-shim.js","sourceRoot":"","sources":["../../src/server/tauri-dialog-shim.ts"],"names":[],"mappings":"AAEA,sDAAsD;AACtD,MAAM,CAAN,IAAY,eAIX;AAJD,WAAY,eAAe;IACzB,kCAAe,CAAA;IACf,kFAAkF;IAClF,uCAAoB,CAAA;AACtB,CAAC,EAJW,eAAe,KAAf,eAAe,QAI1B;AAQD,MAAM,SAAS,GAAG,0BAA0B,CAAA;AAE5C;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAA0B;IAC5D,OAAO;;qEAE4D,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,OAAO,CAAC;uBACrF,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;;;kBAG9B,IAAI,CAAC,SAAS,CAAC,GAAG,KAAK,IAAI,CAAC,YAAY,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,OAAO,CAAC;;qBAE5E,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;;;;;;;;;;;;;;;gBAe9B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;WACxB,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,KAAK,CAAC;KAC3C,CAAA;AACL,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO;yBACgB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;;;;;;KAM7C,CAAA;AACL,CAAC"}
|
package/package.json
CHANGED
package/skills/verify/SKILL.md
CHANGED
|
@@ -47,7 +47,8 @@ agent-view dom --diff # Lines changed since last dom call (+ a
|
|
|
47
47
|
### Interaction
|
|
48
48
|
```bash
|
|
49
49
|
agent-view click <ref> # Click element by ref from dom output
|
|
50
|
-
agent-view click --
|
|
50
|
+
agent-view click --filter "Save" # Find element by text and click
|
|
51
|
+
agent-view click --pos 100,200 # Click by coordinates — CANVAS ONLY, see below
|
|
51
52
|
agent-view click <ref> --double # Double-click (fires dblclick handlers); works with --filter / --pos too
|
|
52
53
|
agent-view fill <ref> "text" # Type into input field
|
|
53
54
|
agent-view drag --from <ref> --to <ref> # Drag element to another element by ref
|
|
@@ -61,6 +62,100 @@ ref and coordinate (e.g. `--from <ref> --to-pos 400,300`). For canvas/Pixi targe
|
|
|
61
62
|
Refs are resolved fresh on each call, so window resizes between snapshots are tolerated.
|
|
62
63
|
Increase `--steps` for handlers using `globalpointermove` so intermediate frames are not skipped.
|
|
63
64
|
|
|
65
|
+
**Coordinates are a last resort.** `--pos` / `--from-pos` / `--to-pos` exist for canvas and WebGL,
|
|
66
|
+
where no ref exists. On DOM the correct order is `dom --filter "<text>"` → take the `[ref=N]` →
|
|
67
|
+
`click <ref>`, or `click --filter "<text>"` in one step. A coordinate pair breaks on any layout
|
|
68
|
+
shift, scroll, zoom, or window resize, and it clicks whatever now sits at that point — silently.
|
|
69
|
+
If you reach for `--pos` on a DOM element, first say why the ref was not usable.
|
|
70
|
+
|
|
71
|
+
### Modals & file pickers (`dialog`, `upload`)
|
|
72
|
+
|
|
73
|
+
A modal that agent-view cannot answer stops a run dead: the window looks frozen, every
|
|
74
|
+
later command times out, and nothing says why. Two kinds, handled differently.
|
|
75
|
+
|
|
76
|
+
**JS modals — `alert` / `confirm` / `prompt` / `beforeunload` — are answered for you.**
|
|
77
|
+
Nothing to set up. While agent-view is attached these never block the page: the default
|
|
78
|
+
standing answer is *dismiss*, and each one is recorded. `agent-view dialog` is the
|
|
79
|
+
reliable place to read that record; the console feed carries the same line
|
|
80
|
+
(`[agent-view] confirm auto-dismissed: <message>`) but only from the moment console
|
|
81
|
+
attaches, so a modal answered before your first `console` call reaches `dialog` alone.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
agent-view dialog # standing answer + every modal this window has seen
|
|
85
|
+
agent-view dialog policy accept # confirm() → true from now on
|
|
86
|
+
agent-view dialog policy accept --text "name" # prompt() → "name"
|
|
87
|
+
agent-view dialog policy dismiss # back to the default
|
|
88
|
+
agent-view dialog dismiss # answer one that is open right now
|
|
89
|
+
agent-view dialog accept --text "x" # …ditto, accepting
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`dialog accept` / `dialog dismiss` exist for a modal that was already open **before**
|
|
93
|
+
agent-view attached — that one produced no event, so no policy applied to it.
|
|
94
|
+
|
|
95
|
+
**Native file pickers never open — you answer them in advance.** Which mechanism applies
|
|
96
|
+
depends on how the app opens the picker, and `dialog arm` sets up all of them at once,
|
|
97
|
+
so you do not have to know:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# The input already exists in the DOM (even hidden) — no picker at all, cheapest path
|
|
101
|
+
agent-view upload --selector "#file-input" --file ./fixtures/a.png
|
|
102
|
+
agent-view upload --selector "#imgs" --file ./a.png --file ./b.png # multi-select
|
|
103
|
+
agent-view upload --ref 12 --file ./a.png # if the AX tree exposes it
|
|
104
|
+
|
|
105
|
+
# The input is created inside the click handler, or the app calls a native dialog API
|
|
106
|
+
agent-view dialog arm --file ./fixtures/a.png # then click the button that opens it
|
|
107
|
+
agent-view dialog arm --cancel # act as if the user pressed Cancel
|
|
108
|
+
agent-view dialog disarm # let real pickers open again
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Rules that actually bite:
|
|
112
|
+
|
|
113
|
+
- **Arm before the click.** A picker cannot be caught once it is open. `arm` is one-shot —
|
|
114
|
+
it is spent by the first picker and interception turns itself off, so a later click
|
|
115
|
+
opens a real OS dialog.
|
|
116
|
+
- **Click through `agent-view click`, never `eval "el.click()"`.** Chromium refuses to
|
|
117
|
+
open a file picker without user activation, and an eval-driven click carries none: the
|
|
118
|
+
picker is silently dropped and never intercepted.
|
|
119
|
+
- **Hidden inputs have no ref.** `display:none` / `v-show="false"` keeps them out of the
|
|
120
|
+
AX tree, so `dom` never prints one. Use `--selector`. `upload` has no `--filter` on
|
|
121
|
+
purpose: an accessible name lands on the label, not on the input behind it.
|
|
122
|
+
- **`beforeunload` is always dismissed**, whatever the policy — accepting it navigates
|
|
123
|
+
away and loses the state you are checking.
|
|
124
|
+
- Paths are resolved against your cwd and must exist — CDP accepts a bad path silently and
|
|
125
|
+
the app then reads an empty file.
|
|
126
|
+
- `agent-view dialog` after the fact shows what was intercepted and what was answered.
|
|
127
|
+
|
|
128
|
+
Known limits: `showOpenFilePicker()` (File System Access API) exposes no input to fill, so
|
|
129
|
+
it can only be cancelled. Electron does not implement `window.prompt` at all. Native
|
|
130
|
+
dialogs opened straight from an Electron **main** process (`dialog.showOpenDialog` behind
|
|
131
|
+
an IPC channel) are out of reach — CDP does not see the main process.
|
|
132
|
+
|
|
133
|
+
### Waiting (`wait`)
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
agent-view wait --filter "Saved" # until the text appears in the AX tree
|
|
137
|
+
agent-view wait --filter "Saved" --timeout 20 # max wait in seconds (default 10)
|
|
138
|
+
agent-view wait --filter "Row 5" --window "Main" # specific window
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Exits as soon as the element appears; exits non-zero on timeout — so `&&` after it is a real gate.
|
|
142
|
+
|
|
143
|
+
**Never sleep for a fixed time.** No `sleep`, no `timeout`, no `ping -n N 127.0.0.1` as a delay
|
|
144
|
+
(agents reach for `ping` when the harness blocks `sleep` — it is the same mistake in worse
|
|
145
|
+
clothing). A fixed pause is either too short, and you assert against a half-rendered UI, or too
|
|
146
|
+
long, and you burn wall-clock on every step. agent-view has a condition-based wait for every kind
|
|
147
|
+
of signal:
|
|
148
|
+
|
|
149
|
+
| You are waiting for… | Use |
|
|
150
|
+
|---|---|
|
|
151
|
+
| An element to render | `wait --filter "<text>"` |
|
|
152
|
+
| A store/state value | `watch "<expr>" --until "<expr>"` |
|
|
153
|
+
| A log line | `console --follow --until "<pattern>"` |
|
|
154
|
+
| A request to fire | `network --follow --until "<url>"` |
|
|
155
|
+
|
|
156
|
+
If none of these fits, poll: `dom --filter X --count` in a loop with an explicit attempt cap, and
|
|
157
|
+
report how many attempts it took.
|
|
158
|
+
|
|
64
159
|
### Screenshots
|
|
65
160
|
```bash
|
|
66
161
|
agent-view screenshot --scale 0.5 # Recommended: JPEG at half-res (~3× fewer vision tokens)
|
|
@@ -239,6 +334,7 @@ Verifications cost very different amounts. Pick the cheapest tool that can actua
|
|
|
239
334
|
| Count of matching elements | `dom --filter X --count` | Single integer, no tree output, no ref mutations |
|
|
240
335
|
| App state, store contents, computed values | `eval "expr"` | DOM doesn't expose JS state; reading the tree to infer it is wasteful and unreliable |
|
|
241
336
|
| Does `window.X` / a globally-exposed API exist? | `eval "typeof window.X"` | DOM doesn't show JS globals; only authoritative check |
|
|
337
|
+
| An element that has not rendered yet | `wait --filter "<text>"` | Exits on appearance and non-zero on timeout — a real gate, unlike a fixed pause |
|
|
242
338
|
| State *trajectory* — what changed during/after an action | `watch "expr" --until …` or `--max-changes 1` | `eval` shows the final snapshot only; `watch` shows the diffs in order |
|
|
243
339
|
| Worker logic (SharedWorker / ServiceWorker) | `eval --target <name>` | Workers have no DOM at all |
|
|
244
340
|
| Did the last action throw or warn? | `console --clear` before, `console --level error,warn` after | Catches errors that don't surface in the DOM |
|
|
@@ -247,6 +343,9 @@ Verifications cost very different amounts. Pick the cheapest tool that can actua
|
|
|
247
343
|
| Layout, spacing, full-window visual regression | `screenshot --scale 0.5` | The only tool that sees pixels — but expensive (~6k tokens), use last |
|
|
248
344
|
| Canvas/WebGL scene contents | `scene --diff` | DOM is empty for canvas apps |
|
|
249
345
|
| What DOM nodes changed after an interaction | `dom --diff` | Returns only `+`/`-` lines; much cheaper than re-reading the full tree |
|
|
346
|
+
| Selecting a file for an input that exists in the DOM | `upload --selector` | No picker opens at all; works on hidden inputs, which have no ref |
|
|
347
|
+
| Selecting a file when the input appears only mid-click | `dialog arm --file` then `click` | The only way — the input does not exist before the click and is gone after |
|
|
348
|
+
| The window stopped responding after a click | `dialog` | Shows whether a modal was answered, and what the app was told |
|
|
250
349
|
|
|
251
350
|
When two tools could answer the same question, prefer the one higher up the table. A common mistake is screenshotting to check "is the count = 5?" when `eval "store.counter"` returns the number directly for ~50 tokens.
|
|
252
351
|
|
|
@@ -256,9 +355,25 @@ When two tools could answer the same question, prefer the one higher up the tabl
|
|
|
256
355
|
|
|
257
356
|
A run can produce one of three outcomes per step: **pass**, **fail**, **requires_visual_review**. There is no fourth bucket called "actually fine, here's why". A failed `Expected:` line is FAIL.
|
|
258
357
|
|
|
259
|
-
|
|
358
|
+
**How to run the commands themselves:**
|
|
359
|
+
|
|
360
|
+
- **Never discard output.** No `>/dev/null`, no `2>&1` to nowhere, no `| head -1` on a command whose
|
|
361
|
+
failure you have not yet read. Every agent-view command prints either the evidence or the reason
|
|
362
|
+
it failed; a suppressed `click` that matched nothing looks exactly like a successful one.
|
|
363
|
+
- **Chain with `&&`, not newlines.** Newline-separated commands keep running after a failure, so a
|
|
364
|
+
broken first step is followed by three steps acting on the wrong state. `&&` stops at the first
|
|
365
|
+
non-zero exit.
|
|
366
|
+
- **One action, then one check.** `click` is not evidence. The evidence is the `dom --diff`,
|
|
367
|
+
`dom --filter … --count`, `eval`, or `console --level error` you run after it. A block of three
|
|
368
|
+
clicks in a row with no check between them proves nothing about any of them.
|
|
369
|
+
- **Call the `agent-view` binary.** Install it once in the target project
|
|
370
|
+
(`pnpm add -D @petukhovart/agent-view`) and call `agent-view …` or `pnpm exec agent-view …`.
|
|
371
|
+
Do not prefix every call with `npx <package>`: it re-resolves the package on each invocation and
|
|
372
|
+
falls outside this skill's `allowed-tools`, so each call needs a fresh permission prompt.
|
|
260
373
|
|
|
261
|
-
|
|
374
|
+
These heuristics catch real bugs. Skipping them is how a run silently passes while the bug sits in plain sight in the same data:
|
|
375
|
+
|
|
376
|
+
1. **A failed expectation is FAIL.** If output disagrees with what the step expected, mark `fail` and continue. Do not soften the expectation. Do not invent prose explanations inline ("label reuse", "convention", "arithmetic off"). Justifications belong in the bug report after the run, never in the per-step log.
|
|
262
377
|
|
|
263
378
|
2. **UI-vs-model mismatch is the bug, not noise.** When a count or hierarchy check returns `match: false`:
|
|
264
379
|
- Default hypothesis: the UI renderer is wrong.
|
|
@@ -267,29 +382,14 @@ These heuristics catch real bugs. Skipping them is how recipes silently pass whi
|
|
|
267
382
|
|
|
268
383
|
3. **Defensive eval reads.** Every `node.field` read (e.g. `transform.x`, `transform.width`) must be sentinel-checked before being used in arithmetic. A renamed field silently returns `NaN`/`null`, which fail-passes downstream comparisons. Add `isFinite(value)` / `value !== undefined` guards inline.
|
|
269
384
|
|
|
270
|
-
4. **No hardcoded literal IDs.**
|
|
385
|
+
4. **No hardcoded literal IDs.** A hardcoded node-ID prefix that no longer matches the current scene degrades the whole check to a silent no-op. Verify at least one expected ID exists; if not, derive IDs by role at runtime and proceed with the corrected lookup, and say the plan needs an ID refresh.
|
|
271
386
|
|
|
272
|
-
5. **Reload checkpoint is not optional.** If the
|
|
387
|
+
5. **Reload checkpoint is not optional.** If the feature mutated persisted structure, run one: `agent-view eval "location.reload()"`, wait for the app to come back, re-read the structural signature, diff. Drift is a real bug, not a "fixed-up on save".
|
|
273
388
|
|
|
274
|
-
6. **
|
|
389
|
+
6. **Invariants run first or fail closed.** When the plan states invariants, execute those steps before the action-specific checks. A failed invariant is FAIL for that invariant *and* a flag on the rest of the run — keep running the remaining steps, tag them as "trust-impaired until invariant restored".
|
|
275
390
|
|
|
276
391
|
7. **Never claim a `window.*` API is missing without `eval`.** Before reporting "API not exposed" / "global X doesn't exist" / "the host doesn't expose Y", you MUST run `agent-view eval "typeof window.X"` and report the literal result (`"undefined"` / `"object"` / `"function"`). DOM scraping cannot answer this question — globals are not in the AX tree. If `eval` returns `"undefined"`, the API really is absent from the main world; if it returns anything else, the API is reachable and your earlier conclusion was wrong. No exceptions, no "I checked the source code instead".
|
|
277
392
|
|
|
278
|
-
### Recipe Execution Mode (when a recipe file exists)
|
|
279
|
-
|
|
280
|
-
If the developer points you at a `.claude/verify-recipes/<slug>.md` file, or one is discoverable via `ls .claude/verify-recipes/`, execute it inline yourself — **no subagent**.
|
|
281
|
-
|
|
282
|
-
1. `Read` the recipe.
|
|
283
|
-
2. If it has a `## Repro Steps` section, follow them — log in, navigate, set up data — using `agent-view` commands. Modern recipes (0.6+) may have `## Manual Preconditions` / `## Bringup` / `## Machine Preconditions` instead; treat those as documentation describing the expected state and execute the actions inline. There is no formal DSL — read what the section says and do it.
|
|
284
|
-
3. Resolve the window id once with `agent-view discover` if you need `--window`.
|
|
285
|
-
4. **If the recipe has an `## Invariants` section, run those steps first.** A failed invariant is FAIL — record it, then continue to evidence commands but tag subsequent results as "trust-impaired" until the invariant is restored.
|
|
286
|
-
5. Run each `## Evidence Commands` subsection in order. Compare output to its `Expected:` line. Mark each as `pass` / `fail` / `requires_visual_review`. **Follow the Execution discipline above** — particularly rules 1 and 2.
|
|
287
|
-
6. After 2–3 consecutive failures, stop and flag the recipe as likely stale. Distinguish "recipe stale" (hardcoded IDs no longer match) from "feature broken" (invariants violated on a current scene).
|
|
288
|
-
7. Run `## Regression Checks`.
|
|
289
|
-
8. **Run the round-trip checkpoint** (discipline rule 5) — either the recipe's section or, if absent and the feature is persistent, an ad-hoc reload check.
|
|
290
|
-
9. If a `## Design Conformance` section is present, run the inline workflow below.
|
|
291
|
-
10. Report a tight summary: passed / failed / visual-review counts plus one-liner per failure, and **any invariant violations called out separately**. Don't paste raw stdout unless asked.
|
|
292
|
-
|
|
293
393
|
### Ad-hoc Mode (standalone)
|
|
294
394
|
|
|
295
395
|
After making code changes:
|
|
@@ -315,9 +415,15 @@ When UI scenarios are pre-generated (e.g., from a plan file with `## UI Scenario
|
|
|
315
415
|
|
|
316
416
|
This mode works with any workflow that generates plan files with UI scenarios.
|
|
317
417
|
|
|
418
|
+
### Reporting
|
|
419
|
+
|
|
420
|
+
Run the whole thing inline — **no subagent**. Resolve the window id once with `agent-view discover` if you need `--window`.
|
|
421
|
+
|
|
422
|
+
Report a tight summary: passed / failed / visual-review counts, one line per failure, invariant violations called out separately. Don't paste raw stdout unless asked. After 2–3 consecutive failures, stop and distinguish "the plan is stale" (hardcoded IDs no longer match the current UI) from "the feature is broken" (invariants violated on a current scene) — they need opposite fixes.
|
|
423
|
+
|
|
318
424
|
### Design Conformance (inline)
|
|
319
425
|
|
|
320
|
-
When
|
|
426
|
+
When you are given `(label, screenshot command, expected reference path)` rows — from a plan, or from the developer directly — execute them yourself, no subagent.
|
|
321
427
|
|
|
322
428
|
For each row:
|
|
323
429
|
1. Run the screenshot command (capture the saved file path from stdout).
|
|
@@ -330,7 +436,7 @@ Tolerance default: a designer's code-review level — flag what they'd notice, i
|
|
|
330
436
|
## Resilience
|
|
331
437
|
|
|
332
438
|
- **Stale refs:** After HMR, navigation, or state change — re-run `dom` for fresh refs before interacting
|
|
333
|
-
- **Element not found:**
|
|
439
|
+
- **Element not found:** `agent-view wait --filter "<text>" --timeout 5` (covers the render delay after HMR). If it times out — report FAIL. Do not insert a blind pause and retry.
|
|
334
440
|
- **CDP disconnect:** Run `agent-view discover` to check. If no windows — `agent-view launch` (auto-starts Electron/Browser/Tauri). On `PORT_CONFLICT` — surface PID/process and ask user.
|
|
335
441
|
- **`CDP_TIMEOUT` error:** the command hit the server-side deadline; cached CDP sessions for that port were dropped, so retry once. Repeated timeouts mean the app's DevTools endpoint is wedged — restart the app.
|
|
336
442
|
- **Max retries per command:** 2. After that — SKIP scenario step with warning
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: verify-recipe
|
|
3
|
-
description: "Generates a concrete, command-by-command agent-view recipe for verifying a feature or bugfix. Use when the developer wants to write a verify-recipe.md, create a verification plan, build an agent-view recipe, or produce a verify checklist for a change they shipped. Triggers on: write a verify-recipe, write verification steps, generate verify steps for the feature/bug I just shipped/fixed, make a verify-recipe.md, what should I run to verify X, create a verification plan, agent-view recipe for this fix, verify checklist for this feature. Does NOT execute the checks — it authors the plan. For running checks against a live app, use the verify skill instead."
|
|
4
|
-
allowed-tools: Read, Write, Bash(agent-view *)
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Verification Recipe Generator (rigor edition)
|
|
8
|
-
|
|
9
|
-
You help the developer author a disciplined, cheapest-first verification recipe for a feature they shipped or a bug they fixed. You do not run the checks — you produce a `.claude/verify-recipes/<slug>.md` file that any AI coding agent can execute later.
|
|
10
|
-
|
|
11
|
-
This edition is hardened against the failure mode of "construction-validated only" recipes that pass while real bugs sit in plain sight in the same data. See `## Bug-class invariants` and `## Discipline rules`.
|
|
12
|
-
|
|
13
|
-
## What this produces
|
|
14
|
-
|
|
15
|
-
A file at `.claude/verify-recipes/<kebab-slug>.md` containing:
|
|
16
|
-
|
|
17
|
-
- **REPRO STEPS** — exact state the app must be in before checks run.
|
|
18
|
-
- **NARROWED SIGNAL** — the measurable indicator that proves success or failure.
|
|
19
|
-
- **EVIDENCE COMMANDS** — ordered `agent-view` calls, cheapest first.
|
|
20
|
-
- **POSITIVE-CASE ASSERTIONS** — what "pass" looks like for each command.
|
|
21
|
-
- **BUG-CLASS INVARIANTS** *(required)* — properties that must hold across all states the recipe touches, not just the action-result state.
|
|
22
|
-
- **ROUND-TRIP CHECKPOINT** *(required when the feature touches persistence or mutable structure)* — a save-and-reload (or equivalent) comparison.
|
|
23
|
-
- **REGRESSION CHECKS** — adjacent paths that must not have broken.
|
|
24
|
-
- **DESIGN CONFORMANCE** *(optional)* — reference image comparisons.
|
|
25
|
-
|
|
26
|
-
Create the directory if missing: `mkdir -p .claude/verify-recipes`
|
|
27
|
-
|
|
28
|
-
## Methodology
|
|
29
|
-
|
|
30
|
-
Frame the recipe with **hard-debug** discipline: REPRO → narrowed signal → minimize scope → root-cause check → fix verification.
|
|
31
|
-
|
|
32
|
-
1. Start from a reproducible starting state, not "open the app and poke around".
|
|
33
|
-
2. Convert vague expectations ("looks right") into measurable signals (`store.user.role === 'admin'`).
|
|
34
|
-
3. Prefer the cheapest tool that can answer the question — a value check costs ~50 tokens, a screenshot costs ~6 000.
|
|
35
|
-
4. Include at least one negative-case check (the old symptom must no longer appear).
|
|
36
|
-
5. Include at least one regression check (an adjacent flow must still work).
|
|
37
|
-
6. **Construction-only verification is not enough.** For any non-trivial feature, add invariant checks (see next section) and a round-trip checkpoint.
|
|
38
|
-
|
|
39
|
-
## Bug-class invariants (required section in every recipe)
|
|
40
|
-
|
|
41
|
-
The recipe must include an `## Invariants` section listing properties that hold *regardless of how state was reached*. These catch bugs that construction-only tests cannot see, because the bug lives in the gap between what the model says and what the user sees / what persistence preserves.
|
|
42
|
-
|
|
43
|
-
Pick at least one invariant from each applicable class. Skip a class only if it provably cannot apply to the feature.
|
|
44
|
-
|
|
45
|
-
### Class A — UI ≡ Model
|
|
46
|
-
For every visible representation of state (tree, list, panel, breadcrumb, overlay, badge, count):
|
|
47
|
-
- **Count parity:** `count(DOM rows of kind X) === count(model nodes of kind X)`.
|
|
48
|
-
- **Hierarchy parity:** for any parent/child relationship the model expresses, the DOM must mirror it. A flat-rendered DOM of a nested model is a UI bug, not a model bug.
|
|
49
|
-
- **Text parity:** the label shown in the UI for a node must equal `model.get(nodeId).name` (or whichever field the UI claims to display).
|
|
50
|
-
|
|
51
|
-
Encode this as a single `eval` that returns a JSON struct so a single failed comparison is unambiguous:
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
agent-view eval "var model=<source-of-truth>;var dom=<measurement>;JSON.stringify({modelCount:model.size, domCount:dom.length, match:model.size===dom.length})"
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
If `match: false` — that is a FAIL. Do not rationalize a DOM/model count mismatch as "label reuse" or "the breadcrumb counts too" without confirming the matched elements' bounding boxes and DOM ancestry first.
|
|
58
|
-
|
|
59
|
-
### Class B — Round-trip
|
|
60
|
-
For any feature that creates / mutates persisted structure:
|
|
61
|
-
- `state ≡ deserialize(serialize(state))` (model survives save→load with bit-identical structure for the projected fields).
|
|
62
|
-
- `world(child) === origWorld(child)` immediately after a wrap/unwrap action (the wrapping must not silently shift the wrapped element).
|
|
63
|
-
- Reloading the page (`agent-view eval "location.reload()"`) and re-reading the same state must produce an identical signature.
|
|
64
|
-
|
|
65
|
-
A round-trip discrepancy that the save serializer silently "fixes" is a model bug, not a save-format bug. Live-vs-persisted divergence between actions and reloads is a class-B failure even if save/load round-trips cleanly with itself.
|
|
66
|
-
|
|
67
|
-
### Class C — Reversibility & identity
|
|
68
|
-
- Every user-facing command produces exactly one `commandHistory` entry (`canUndo→true`; one `undo()` fully reverses).
|
|
69
|
-
- `undo` then `redo` produces an identical state to "do once".
|
|
70
|
-
- The set of nodes after `do → undo → redo` equals the set after one `do`. Compare full IDs, not counts — a swap of one node for another with the same name is a real bug.
|
|
71
|
-
|
|
72
|
-
### Class D — No phantom state
|
|
73
|
-
- For each visible entity the user can interact with, there exists exactly one corresponding model node.
|
|
74
|
-
- For each model node, there exists at most one corresponding UI entity in each view.
|
|
75
|
-
- An entity that appears in two views (e.g. tree and canvas) must have the same identity in both (the same `nodeId` resolves both).
|
|
76
|
-
|
|
77
|
-
This class is the one most often missed. Verify it explicitly when the feature changes structure.
|
|
78
|
-
|
|
79
|
-
### Class E — Config conformance (rendering vs. configured constraint)
|
|
80
|
-
|
|
81
|
-
Class A compares model to DOM. Class E compares the **rendered output** (DOM bounds, canvas geometry, computed styles, layout sizes) to an **explicit configuration value** the system declares it must respect. This is the home for invariants that have no DOM counterpart to diff against — they assert against a config the user (or product) chose.
|
|
82
|
-
|
|
83
|
-
Typical examples — adapt names to the project:
|
|
84
|
-
|
|
85
|
-
- **Snap/grid:** every shape's bounding box must satisfy `x % gridSize === 0` (and same for `y / width / height`). Read `gridSize` from the live config (`store.gridSettings.gridSize`, `theme.grid.size`, `viewport.snap`, …); do not hardcode.
|
|
86
|
-
- **Viewport bounds:** elements declared "viewport-contained" must have `getBoundingClientRect()` within the viewport rect.
|
|
87
|
-
- **Z-order / layering:** elements declared "above modals" must have computed `z-index` above the modal's. Compare against the configured layer table, not a literal number.
|
|
88
|
-
- **Spacing / density tokens:** if the design system declares a spacing scale, computed `margin` / `padding` must be a multiple of the base token.
|
|
89
|
-
- **Theme tokens:** computed colors / fonts must match the active theme's declared values, not literal hex codes.
|
|
90
|
-
|
|
91
|
-
Generic helper shape:
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
agent-view eval "var cfg=<read live config value>; var items=<measure rendered items>; var off=items.filter(function(it){return !<conforms(it, cfg)>}); JSON.stringify({cfg:cfg, total:items.length, offCount:off.length, sample:off.slice(0,3)})"
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Expected: `offCount: 0`. A non-zero count means the renderer ignored a config the system promised to honor — that is a real bug class, even if the model is internally consistent and round-trips cleanly. Bugs of the form "element is half a pixel off the grid", "modal renders behind the toast", "freshly-created group is not snapped" all live here and fall between the other classes.
|
|
98
|
-
|
|
99
|
-
When generating recipes, ask: *what config values does this feature claim to respect?* Each one is one Class-E invariant. If the answer is "none" — say so explicitly in the recipe, so a future reader doesn't wonder why the class is absent.
|
|
100
|
-
|
|
101
|
-
## Discipline rules
|
|
102
|
-
|
|
103
|
-
These rules govern how the recipe is *executed* — they must appear verbatim in the recipe's `## Discipline` section so the executor cannot forget them.
|
|
104
|
-
|
|
105
|
-
1. **A failed `Expected:` line is FAIL, not "actually fine".** Rationalizations belong in the bug report after the run, not inline in the run log. If the actual output disagrees with the expected line, mark `FAIL` and continue to the next step; do not edit the expected line, do not invent a "label reuse" or "convention" explanation that makes the discrepancy disappear from the count.
|
|
106
|
-
|
|
107
|
-
2. **Prefer model-vs-DOM mismatches as the bug, not as noise.** When a Class-A invariant fails, the default hypothesis is "the UI renderer is wrong", not "the DOM filter matched something extra". To rule out the latter, query bounding boxes and ancestor chains for every match; if the matches turn out to be sibling rows in the same list, the renderer is the bug.
|
|
108
|
-
|
|
109
|
-
3. **Defensive eval reads.** Every read of `node.transform.x` (or any model field) must include a sentinel: `isFinite(value)` or `value !== undefined`. A silent `NaN`/`null` from a renamed field looks like a real `0` in arithmetic and will fail-pass the assertion.
|
|
110
|
-
|
|
111
|
-
4. **No hardcoded literal IDs.** All node IDs in evals must be resolved by role at runtime (e.g. "the first group at root", "the line at root not inside any group"). Hardcoded prefixes silently degrade the recipe to no-op when the scene differs.
|
|
112
|
-
|
|
113
|
-
5. **The reload checkpoint is not optional.** For any recipe that touches mutable structure, include at least one `location.reload()` between an action and a re-measurement. Compare the projected signature before and after; differences are real bugs, not "fixed-up on save".
|
|
114
|
-
|
|
115
|
-
6. **Compose, do not narrate.** Final report is `pass / fail / requires_visual_review` counts plus one-line failure reasons. Do not paste raw stdout. Do not include prose justifications in the per-step output.
|
|
116
|
-
|
|
117
|
-
7. **`requires_visual_review` is an executor verdict, not an authoring shortcut.** When generating the recipe, draft every step so it executes through `agent-view`. Do not write `requires_visual_review` as the planned status of a step just because the path looks tricky (event delegation, canvas hit-test, file dialog, etc.) — first encode the cheapest sub-check that *can* run programmatically (e.g. "no console error during the action", "model state changed", "command history advanced by one"). The `requires_visual_review` label is set by the *executor* at run time, only after at least one genuine attempt to drive the action via agent-view failed for a reason they record. Authoring the recipe with `requires_visual_review` prebaked is what lets real regressions sit unobserved.
|
|
118
|
-
|
|
119
|
-
## Tool-cost decision tree
|
|
120
|
-
|
|
121
|
-
Pick the first row that can answer the question. Only go lower when the row above can't:
|
|
122
|
-
|
|
123
|
-
| Question | Command | Why it's cheapest |
|
|
124
|
-
|---|---|---|
|
|
125
|
-
| Element exists / has specific text / role | `agent-view dom --filter "<text>" --depth 2` | Structured text, zero vision tokens |
|
|
126
|
-
| Count of matching elements | `agent-view dom --filter X --count` | Single integer, zero tree tokens |
|
|
127
|
-
| App state, store value, computed flag | `agent-view eval "<expr>"` | Returns the value directly; DOM inference is wasteful and fragile |
|
|
128
|
-
| What changed between action and final state | `agent-view watch "<expr>" --until "<condition>"` or `--max-changes 1` | `eval` shows the snapshot; `watch` shows the trajectory |
|
|
129
|
-
| SharedWorker / ServiceWorker internal state | `agent-view eval --target <name> "<expr>"` | Workers have no DOM |
|
|
130
|
-
| Did this action throw or warn silently? | `agent-view console --clear` before, `agent-view console --level error,warn` after | Catches uncaught exceptions invisible to DOM |
|
|
131
|
-
| Layout, spacing, visual regression | `agent-view screenshot --scale 0.5` | Last resort — only tool that sees pixels (~6 000 tokens) |
|
|
132
|
-
| Canvas / WebGL scene state | `agent-view scene --diff` | DOM is empty for canvas apps |
|
|
133
|
-
|
|
134
|
-
**Anti-patterns to reject:**
|
|
135
|
-
- Opening with a screenshot to "see the state" — use `dom --filter` or `eval` first.
|
|
136
|
-
- Using `eval` when `dom --filter` answers the question.
|
|
137
|
-
- Assertions that depend on transient state without `watch --until` to stabilize first.
|
|
138
|
-
- "Check that it looks right" — every assertion must be a concrete pass/fail criterion. Single legitimate exception: `## Design Conformance`.
|
|
139
|
-
- Inventing design reference paths (`.figma-refs/...`) when the developer did not provide them.
|
|
140
|
-
- **Treating SG (or any model layer) as the sole source of truth.** Model is one of several representations; the bug may live in the gap between model and UI. The recipe must check the gap explicitly.
|
|
141
|
-
|
|
142
|
-
## Workflow
|
|
143
|
-
|
|
144
|
-
### Step 1 — gather context
|
|
145
|
-
|
|
146
|
-
Ask in plain text (no tool calls yet):
|
|
147
|
-
|
|
148
|
-
1. What was shipped or fixed?
|
|
149
|
-
2. What was the original symptom or expected behavior?
|
|
150
|
-
3. Any known failure mode or edge case to cover?
|
|
151
|
-
4. Does this feature touch persisted structure (YAML, IndexedDB, server)? If yes → mandatory round-trip section.
|
|
152
|
-
5. Which views render this state (tree, panel, list, canvas, breadcrumb)? Each named view → one UI-≡-Model invariant in Class A.
|
|
153
|
-
6. Which **configured constraints** does this feature claim to respect (grid/snap, viewport, z-order/layer table, spacing tokens, theme tokens, density)? Each named constraint → one Class-E invariant. If none — say so explicitly.
|
|
154
|
-
7. *(Optional)* Local design reference images? Local files only.
|
|
155
|
-
|
|
156
|
-
Wait for the response before continuing.
|
|
157
|
-
|
|
158
|
-
### Step 2 — derive invariants before drafting commands
|
|
159
|
-
|
|
160
|
-
Before writing any `agent-view` call, list the invariants the feature must satisfy. For each, write one sentence in the form:
|
|
161
|
-
> "After any sequence of allowed actions, `<measurable property>` must hold."
|
|
162
|
-
|
|
163
|
-
This list seeds the `## Invariants` section. If you cannot articulate three invariants for a non-trivial feature, the feature is under-specified — ask the developer for clarification.
|
|
164
|
-
|
|
165
|
-
### Step 3 — draft the recipe
|
|
166
|
-
|
|
167
|
-
```markdown
|
|
168
|
-
# Verify: <feature or fix name>
|
|
169
|
-
|
|
170
|
-
Generated: <date>
|
|
171
|
-
Scope: <one sentence>
|
|
172
|
-
|
|
173
|
-
## Repro Steps
|
|
174
|
-
1. <Exact starting state>
|
|
175
|
-
2. <Action(s) that trigger the behavior under test>
|
|
176
|
-
|
|
177
|
-
## Narrowed Signal
|
|
178
|
-
`<agent-view command>` must return `<expected value>`.
|
|
179
|
-
|
|
180
|
-
## Invariants
|
|
181
|
-
<!-- Required. List each invariant the feature must maintain. -->
|
|
182
|
-
|
|
183
|
-
- **A1 (UI≡Model — <view name>):** `<eval expression that returns {model: N, dom: M, match: bool}>` must return `match: true`.
|
|
184
|
-
- **A2 (Hierarchy):** For each model node with `parentId === X`, its UI row must be a DOM-descendant of the UI row of `X`.
|
|
185
|
-
- **B1 (Round-trip):** After `location.reload()`, the projected signature `<projection>` is identical to the pre-reload signature.
|
|
186
|
-
- **B2 (No silent wrap-shift):** After `<wrap-creating action>`, `getWorldTransform(child).x === origWorldX(child)` for every wrapped child.
|
|
187
|
-
- **C1 (Single undo):** After `<action>`, `commandHistory.canUndo === true` and one `undo()` produces a signature identical to the pre-action signature.
|
|
188
|
-
- **D1 (No phantoms):** For each visible UI row of kind `<X>`, there exists exactly one model node it resolves to via its `nodeId` data-attribute (or the equivalent identifier).
|
|
189
|
-
- **E1 (Config conformance — <constraint name>):** Every rendered `<entity>` must satisfy `<conforms(rendered, configValue)>`, where `<configValue>` is read live from `<config source>` (do not hardcode). `offCount: 0` is required.
|
|
190
|
-
|
|
191
|
-
## Discipline
|
|
192
|
-
|
|
193
|
-
1. A failed `Expected:` line is FAIL, not "actually fine".
|
|
194
|
-
2. UI-vs-model mismatches are the bug; confirm by ancestry/bbox before dismissing.
|
|
195
|
-
3. Every `node.field` read in eval must be sentinel-checked (`isFinite` / `!== undefined`).
|
|
196
|
-
4. No hardcoded literal IDs; resolve by role at runtime.
|
|
197
|
-
5. Reload checkpoint is mandatory when the feature touches mutable structure.
|
|
198
|
-
6. Report `pass / fail / requires_visual_review` per step; failure prose belongs in the bug report only.
|
|
199
|
-
7. Do not pre-bake `requires_visual_review` as the planned status of a step. Every step ships with an executable `agent-view` path (at minimum a console-error gate around the action). The executor sets `requires_visual_review` only after a real attempt fails for a recorded reason.
|
|
200
|
-
|
|
201
|
-
## Evidence Commands
|
|
202
|
-
|
|
203
|
-
### 0. Setup — capture baseline signature
|
|
204
|
-
```bash
|
|
205
|
-
agent-view eval "<signature expression — projection of all structural fields>"
|
|
206
|
-
```
|
|
207
|
-
Stash the output as `BASELINE_SIG`.
|
|
208
|
-
|
|
209
|
-
### 1. <What this proves>
|
|
210
|
-
```bash
|
|
211
|
-
agent-view <command>
|
|
212
|
-
```
|
|
213
|
-
Expected: <concrete criterion>
|
|
214
|
-
Cost: ~<N> tokens
|
|
215
|
-
|
|
216
|
-
### 2. ... (one per action / invariant pair)
|
|
217
|
-
|
|
218
|
-
### N. Round-trip checkpoint (if applicable)
|
|
219
|
-
```bash
|
|
220
|
-
agent-view eval "location.reload()"
|
|
221
|
-
# wait for app to come back
|
|
222
|
-
agent-view eval "<same signature expression as step 0>"
|
|
223
|
-
```
|
|
224
|
-
Expected: identical to `BASELINE_SIG` after the action's expected effect is accounted for. Diff any drift.
|
|
225
|
-
|
|
226
|
-
## Positive-Case Assertions
|
|
227
|
-
- [ ] <criterion>
|
|
228
|
-
|
|
229
|
-
## Regression Checks
|
|
230
|
-
- [ ] <adjacent flow> — `agent-view <command>` → `<expected>`
|
|
231
|
-
|
|
232
|
-
## Design Conformance
|
|
233
|
-
<!-- Optional. Include ONLY if developer provided design refs. -->
|
|
234
|
-
|
|
235
|
-
| Step Label | Screenshot Command | Expected Reference |
|
|
236
|
-
|---|---|---|
|
|
237
|
-
| <area name> | `agent-view screenshot --crop "<area>" --scale 0.5` | `<absolute path>` |
|
|
238
|
-
|
|
239
|
-
Tolerance: `normal`.
|
|
240
|
-
|
|
241
|
-
## Anti-patterns avoided
|
|
242
|
-
- <recipe-specific traps>
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
### Step 4 — save the file
|
|
246
|
-
|
|
247
|
-
Determine a kebab-slug. Save to `.claude/verify-recipes/<slug>.md`. Create the directory first.
|
|
248
|
-
|
|
249
|
-
Confirm the path to the developer.
|
|
250
|
-
|
|
251
|
-
## Worked example: "added group component to scene editor"
|
|
252
|
-
|
|
253
|
-
**Developer input:**
|
|
254
|
-
> Added a group component that wraps multiple scene elements. Ctrl+G groups selected elements; Ctrl+Shift+G ungroups. Groups appear in the left tree panel as collapsible nodes. Groups are persisted in YAML.
|
|
255
|
-
|
|
256
|
-
**Recipe excerpt (Invariants + Round-trip — the parts that catch the bugs construction-only recipes miss):**
|
|
257
|
-
|
|
258
|
-
```markdown
|
|
259
|
-
## Invariants
|
|
260
|
-
|
|
261
|
-
- **A1 (UI≡Model, tree count):**
|
|
262
|
-
```
|
|
263
|
-
agent-view eval "var sg=window.__editorCore.sceneGraph;var modelCount=0;sg.nodes.forEach(function(n){if(n.componentType==='group')modelCount++});var domCount=[...document.querySelectorAll('[role=treeitem]')].filter(function(e){return e.textContent.includes('Группа')}).length;JSON.stringify({modelCount:modelCount, domCount:domCount, match:modelCount===domCount})"
|
|
264
|
-
```
|
|
265
|
-
must return `match: true`.
|
|
266
|
-
|
|
267
|
-
- **A2 (Tree hierarchy):**
|
|
268
|
-
```
|
|
269
|
-
agent-view eval "var sg=window.__editorCore.sceneGraph;var violations=[];sg.nodes.forEach(function(n,id){if(n.componentType==='group'){n.childrenIds.forEach(function(cid){var domChild=document.querySelector('[data-node-id=\"'+cid+'\"]');var domParent=document.querySelector('[data-node-id=\"'+id+'\"]');if(domChild&&domParent&&!domParent.contains(domChild))violations.push({c:cid,p:id})})}});JSON.stringify({violations:violations.length})"
|
|
270
|
-
```
|
|
271
|
-
must return `violations: 0`.
|
|
272
|
-
|
|
273
|
-
- **B1 (Round-trip after Ctrl+G + reload):**
|
|
274
|
-
Capture signature, dispatch Ctrl+G, capture signature, reload, capture signature. The post-reload signature must equal the post-Ctrl+G signature *projected to the persisted fields*. Drift in `transform.x` of any wrapped child means the wrap path did not normalize coords.
|
|
275
|
-
|
|
276
|
-
- **B2 (No silent wrap-shift):**
|
|
277
|
-
Before Ctrl+G: capture `getWorldTransform(child).x` for each leaf. After Ctrl+G: re-capture. Difference must be 0 (within float epsilon). Non-zero means the wrap action shifted the child in world space — a regression.
|
|
278
|
-
|
|
279
|
-
- **C1 (Single undo):**
|
|
280
|
-
After Ctrl+G, `commandHistory.canUndo === true`. After one `undo()`, signature equals pre-Ctrl+G signature exactly.
|
|
281
|
-
|
|
282
|
-
- **D1 (No phantom group rows):**
|
|
283
|
-
Same eval as A1; if the result is `modelCount: 1, domCount: 2`, that is a phantom-row bug. Confirm by `agent-view eval "[...document.querySelectorAll('span')].filter(e=>e.textContent==='Группа'&&e.children.length===0).map(e=>{var r=e.getBoundingClientRect();return{x:r.x,y:r.y}})"` — two adjacent rows in the same column = phantom.
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
This single example pair (A1 + D1 + B1 + B2) catches the three bugs from the 4.7 grouping session that the construction-only recipes missed.
|