waygraph 0.16.2 → 0.16.4
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 +12 -0
- package/dist/analysis/practices-check.d.ts +1 -1
- package/dist/analysis/practices-check.js +36 -0
- package/dist/beta.d.ts +6 -0
- package/dist/beta.js +5 -0
- package/dist/cli/commands/codemod-flows.js +2 -2
- package/dist/cli/commands/codemod-stubs.js +43 -1
- package/dist/cli/commands/pilot.js +3 -16
- package/dist/cli/commands/session-ops.js +26 -1
- package/dist/cli/flags.d.ts +1 -1
- package/dist/cli/flags.js +1 -1
- package/dist/cli/usage.js +6 -0
- package/dist/demo/narration/stub-ctx.js +126 -124
- package/dist/demo/overlay/cursor.js +1 -1
- package/dist/demo/overlay/inpage/banner.js +5 -0
- package/dist/demo/overlay/inpage/core.js +1 -0
- package/dist/demo/overlay/inpage/network.js +1 -1
- package/dist/demo/overlay/inpage/todos.js +0 -15
- package/dist/demo/overlay/network-capture.js +2 -2
- package/dist/demo/overlay/overlay-css.js +18 -21
- package/dist/demo/overlay/overlay-stage.js +1 -1
- package/dist/demo/overlay/phase-fixtures.js +5 -5
- package/dist/demo/overlay/pilot/shell.js +7 -5
- package/dist/demo/overlay/ui/components/ring.js +2 -2
- package/dist/demo/overlay/ui/overlay.css +98 -35
- package/dist/demo/overlay/ui/shadow.js +69 -2
- package/dist/demo/stepper/demo-log.js +8 -8
- package/dist/demo/stepper/slides.d.ts +1 -1
- package/dist/demo/stepper/slides.js +1 -1
- package/dist/demo/stepper/stepper.js +1 -1
- package/dist/engine/block/kind.d.ts +3 -0
- package/dist/engine/block/kind.js +11 -0
- package/dist/engine/errors.d.ts +12 -0
- package/dist/engine/errors.js +12 -0
- package/dist/engine/read/read-block.d.ts +61 -0
- package/dist/engine/read/read-block.js +94 -0
- package/dist/engine/read/read-key.d.ts +32 -0
- package/dist/engine/read/read-key.js +34 -0
- package/dist/engine/read/read-page.d.ts +19 -0
- package/dist/engine/read/read-page.js +50 -0
- package/dist/engine/read/readers.d.ts +82 -0
- package/dist/engine/read/readers.js +43 -0
- package/dist/engine/read/run-read.d.ts +17 -0
- package/dist/engine/read/run-read.js +156 -0
- package/dist/engine/run/connect.js +3 -2
- package/dist/engine/run/events.d.ts +28 -10
- package/dist/engine/run/events.js +52 -38
- package/dist/engine/run/run.js +25 -13
- package/dist/engine/stub/stub.d.ts +26 -21
- package/dist/index.d.ts +7 -4
- package/dist/index.js +9 -4
- package/dist/live/auto/auto.js +2 -3
- package/dist/live/auto/menu.js +3 -2
- package/dist/live/auto-session/auto-session.d.ts +9 -2
- package/dist/live/auto-session/auto-session.js +23 -20
- package/dist/live/auto-session/feed.d.ts +16 -0
- package/dist/live/auto-session/feed.js +71 -0
- package/dist/live/auto-session/helpers.d.ts +7 -0
- package/dist/live/auto-session/helpers.js +9 -0
- package/dist/live/auto-session/ipc.d.ts +15 -47
- package/dist/live/auto-session/ipc.js +34 -4
- package/dist/live/auto-session/protocol.d.ts +127 -0
- package/dist/live/auto-session/protocol.js +6 -0
- package/dist/live/auto-session/read.d.ts +25 -0
- package/dist/live/auto-session/read.js +50 -0
- package/dist/live/traverse/worker.d.ts +1 -0
- package/dist/live/traverse/worker.js +6 -1
- package/dist/manifest/manifest.d.ts +22 -2
- package/dist/manifest/manifest.js +27 -1
- package/dist/manifest/project.d.ts +10 -1
- package/dist/manifest/project.js +9 -12
- package/dist/manifest/read-blocks.d.ts +2 -0
- package/dist/manifest/read-blocks.js +23 -0
- package/dist/manifest/scan.d.ts +3 -2
- package/dist/manifest/scan.js +8 -4
- package/dist/runner/runner.d.ts +0 -1
- package/dist/runner/runner.js +6 -6
- package/dist/runtime/full-launch.d.ts +2 -1
- package/dist/runtime/full-launch.js +29 -27
- package/dist/runtime/run-log.d.ts +2 -0
- package/dist/runtime/run-log.js +13 -6
- package/examples/saucedemo/README.md +2 -2
- package/examples/saucedemo/src/blocks/SITE-MAP.md +3 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/cart.read.block.ts +26 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/header.read.block.ts +25 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/inventory.read.block.ts +28 -0
- package/examples/saucedemo/src/flows/api-demo.flow.ts +1 -1
- package/examples/saucedemo/src/flows/cart-bulk.flow.ts +1 -1
- package/examples/saucedemo/src/flows/cart-continue.flow.ts +1 -1
- package/examples/saucedemo/src/flows/catalog-logout.flow.ts +1 -1
- package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +1 -1
- package/examples/saucedemo/src/flows/checkout.flow.ts +11 -11
- package/examples/saucedemo/src/flows/login.flow.ts +1 -1
- package/examples/saucedemo/src/flows/shop.flow.ts +1 -1
- package/examples/saucedemo/src/flows/stub-on-error.flow.ts +1 -1
- package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +1 -1
- package/examples/saucedemo/src/states/checkout.reads.ts +9 -0
- package/examples/saucedemo/src/stubs/device-showcase.ts +37 -37
- package/examples/saucedemo/src/stubs/finish-order.ts +2 -2
- package/examples/saucedemo/src/stubs/login.ts +5 -5
- package/package.json +5 -1
- package/templates/quickstart/src/flows/api-demo.flow.ts +1 -1
- package/templates/quickstart/src/flows/cart-bulk.flow.ts +1 -1
- package/templates/quickstart/src/flows/cart-continue.flow.ts +1 -1
- package/templates/quickstart/src/flows/catalog-logout.flow.ts +1 -1
- package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +1 -1
- package/templates/quickstart/src/flows/checkout.flow.ts +11 -11
- package/templates/quickstart/src/flows/login.flow.ts +1 -1
- package/templates/quickstart/src/flows/shop.flow.ts +1 -1
- package/templates/quickstart/src/flows/stub-on-error.flow.ts +1 -1
- package/templates/quickstart/src/flows/viewer-blocked.flow.ts +1 -1
- package/templates/quickstart/src/stubs/device-showcase.ts +37 -37
- package/templates/quickstart/src/stubs/finish-order.ts +2 -2
- package/templates/quickstart/src/stubs/login.ts +5 -5
- package/templates/scaffold/src/flows/(external)/hoppscotch/auth.flow.ts +1 -1
- package/templates/scaffold/src/flows/(external)/hoppscotch/get.flow.ts +1 -1
- package/templates/scaffold/src/flows/(external)/hoppscotch/post.flow.ts +1 -1
- package/templates/scaffold/src/flows/(user-stories)/email-verification.flow.ts +4 -4
- package/templates/scaffold/src/flows/README.md +1 -1
- package/templates/scaffold/src/flows/home/cart.flow.ts +7 -7
- package/templates/scaffold/src/flows/home/hello.flow.ts +8 -8
- package/templates/skills/waygraph-convention.skill.md +10 -2
- package/templates/skills/waygraph-pilot.skill.md +7 -0
package/README.md
CHANGED
|
@@ -100,6 +100,7 @@ names) still works and is fine for small projects, but the Map is where the tool
|
|
|
100
100
|
| See the whole graph | `waygraph graph` |
|
|
101
101
|
| Lint your Blocks and folders | `waygraph check` / `waygraph typecheck` / `waygraph map` |
|
|
102
102
|
| Open a persistent browser for an agent | `waygraph browser start` |
|
|
103
|
+
| Read what the page shows, as JSON (beta: Read Blocks) | `waygraph pilot read <id> [path]` |
|
|
103
104
|
| Highlight things on the live page | `waygraph browser highlight <id> "#login\|Login button\|warning"` |
|
|
104
105
|
| Set up coding-agent skills | `waygraph agent-dive --loop claude` |
|
|
105
106
|
|
|
@@ -124,4 +125,15 @@ npm run typecheck && npm test # unit + Playwright suites
|
|
|
124
125
|
Working on waygraph: [`AGENTS.md`](./AGENTS.md) (the rules, how to verify) and
|
|
125
126
|
[`src/ARCHITECTURE.md`](./src/ARCHITECTURE.md) (where the code starts).
|
|
126
127
|
|
|
128
|
+
## Status: alpha
|
|
129
|
+
|
|
130
|
+
> [!WARNING]
|
|
131
|
+
> Waygraph is an alpha build. Its syntax may still change in major ways between releases, and a release can remove
|
|
132
|
+
> API (each removal is listed in [what was removed](./docs/deprecated.md); `waygraph upgrade --codemod` converts most
|
|
133
|
+
> of it). For now we recommend staying on 0.16.0:
|
|
134
|
+
>
|
|
135
|
+
> ```bash
|
|
136
|
+
> npm install --save-exact waygraph@0.16.0
|
|
137
|
+
> ```
|
|
138
|
+
|
|
127
139
|
MIT
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type PracticeKind = "multi-input" | "combined-action" | "empty-verify" | "overcomplex-selector" | "inline-selector-should-use-sel-file";
|
|
1
|
+
export type PracticeKind = "multi-input" | "combined-action" | "empty-verify" | "overcomplex-selector" | "inline-selector-should-use-sel-file" | "hand-written-ui";
|
|
2
2
|
export interface PracticeWarning {
|
|
3
3
|
kind: PracticeKind;
|
|
4
4
|
file: string;
|
|
@@ -46,6 +46,15 @@ const XPATH_LOCATOR = /\.locator\(\s*["'`](?:xpath=)?\/\//;
|
|
|
46
46
|
* scoped to simple, single-token selectors (id/class/attr/tag) so this doesn't fire on the
|
|
47
47
|
* legitimate multi-part selectors `.sel.ts` files themselves are made of. */
|
|
48
48
|
const INLINE_SIMPLE_SELECTOR = /(?:\.locator|\bclick|\burl)\s*[:(]\s*["'`](#|\.)[\w-]+["'`]/;
|
|
49
|
+
/**
|
|
50
|
+
* Hand-written UI in a test project - real, recurring pattern: an LLM wanting a caption, a highlight or a banner builds
|
|
51
|
+
* its own DOM in the page or restyles / reaches into waygraph's overlay, instead of using `ctx.ui`. It changes the page
|
|
52
|
+
* under test and breaks whenever the overlay changes.
|
|
53
|
+
*/
|
|
54
|
+
const OVERLAY_INTERNALS = /\b__wg[A-Za-z]|["'`][^"'`\n]*(?:#wg-|\.wg-|wg-root)/;
|
|
55
|
+
const DOM_BUILD = /\bdocument\.createElement\(|\.innerHTML\s*=|\binsertAdjacentHTML\(|\baddStyleTag\(|\.style\.cssText|\.style\.[a-zA-Z]+\s*=(?!=)/;
|
|
56
|
+
/** Shared narration (`src/stubs/*.ts`) is checked for hand-written UI too, next to Block and flow files. */
|
|
57
|
+
const STUB_FILE = /[\\/]stubs[\\/][^\\/]+\.ts$/;
|
|
49
58
|
const METHOD_OR_EFFECT_FILE = /\.(method|effect)\.block\.ts$/;
|
|
50
59
|
const METHOD_OR_EFFECT_DEFINE = /\bdefine(?:Method|Effect)Block\b/;
|
|
51
60
|
/** Whole-file opt-out: `// waygraph-ignore-practices` */
|
|
@@ -55,6 +64,13 @@ const IGNORE_KINDS = /waygraph-ignore:\s*([^\n*]+)/;
|
|
|
55
64
|
function walkBlockAndFlowFiles(projectDir, walkDir) {
|
|
56
65
|
return walkDir(projectDir, SCAN_GLOB);
|
|
57
66
|
}
|
|
67
|
+
function handWrittenUi(src) {
|
|
68
|
+
if (OVERLAY_INTERNALS.test(src))
|
|
69
|
+
return "reaches into waygraph's overlay (#wg-*, .wg-*, window.__wg*)";
|
|
70
|
+
if (DOM_BUILD.test(src))
|
|
71
|
+
return "builds or restyles page DOM (createElement / innerHTML / addStyleTag / .style)";
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
58
74
|
function ignoredKindsForFile(src) {
|
|
59
75
|
if (IGNORE_ALL.test(src))
|
|
60
76
|
return "all";
|
|
@@ -143,6 +159,9 @@ export function collectPracticeWarnings(projectDir, walkDir, opts) {
|
|
|
143
159
|
});
|
|
144
160
|
}
|
|
145
161
|
}
|
|
162
|
+
const ui = isIgnored("hand-written-ui", ignore) ? undefined : handWrittenUi(src);
|
|
163
|
+
if (ui)
|
|
164
|
+
warnings.push(handWrittenUiWarning(rel, ui));
|
|
146
165
|
if (!isIgnored("empty-verify", ignore) && IS_ASSERT_BLOCK.test(src) && EMPTY_VERIFY_ARRAY.test(src)) {
|
|
147
166
|
warnings.push({
|
|
148
167
|
kind: "empty-verify",
|
|
@@ -152,8 +171,23 @@ export function collectPracticeWarnings(projectDir, walkDir, opts) {
|
|
|
152
171
|
});
|
|
153
172
|
}
|
|
154
173
|
}
|
|
174
|
+
for (const file of walkDir(projectDir, /\.ts$/).filter((f) => STUB_FILE.test(f) && !SCAN_GLOB.test(f))) {
|
|
175
|
+
const src = readFileSync(file, "utf-8");
|
|
176
|
+
const ui = isIgnored("hand-written-ui", ignoredKindsForFile(src)) ? undefined : handWrittenUi(src);
|
|
177
|
+
if (ui)
|
|
178
|
+
warnings.push(handWrittenUiWarning(relative(projectDir, file).replace(/\\/g, "/"), ui));
|
|
179
|
+
}
|
|
155
180
|
return warnings;
|
|
156
181
|
}
|
|
182
|
+
function handWrittenUiWarning(file, what) {
|
|
183
|
+
return {
|
|
184
|
+
kind: "hand-written-ui",
|
|
185
|
+
file,
|
|
186
|
+
detail: `${what} - narration goes through ctx.ui (ring, caption, title, todos, slides, device, zoom, network tab), ` +
|
|
187
|
+
"and a test never changes the page it tests. Missing something in ctx.ui? Ask for it in waygraph instead of " +
|
|
188
|
+
"overriding; opt out with // waygraph-ignore: hand-written-ui",
|
|
189
|
+
};
|
|
190
|
+
}
|
|
157
191
|
export function practiceKindLabel(kind) {
|
|
158
192
|
switch (kind) {
|
|
159
193
|
case "multi-input":
|
|
@@ -166,6 +200,8 @@ export function practiceKindLabel(kind) {
|
|
|
166
200
|
return "regex/XPath selector where a plain one would do";
|
|
167
201
|
case "inline-selector-should-use-sel-file":
|
|
168
202
|
return "inline selector — a _sel.ts/*.sel.ts file already exists here";
|
|
203
|
+
case "hand-written-ui":
|
|
204
|
+
return "hand-written UI — use ctx.ui, don't build or override the overlay";
|
|
169
205
|
default: {
|
|
170
206
|
const _exhaustive = kind;
|
|
171
207
|
return _exhaustive;
|
package/dist/beta.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { reads, ReadKey } from "./engine/read/read-key.js";
|
|
2
|
+
export type { ReadNamespace, ReadSpec, ReadFieldsOf } from "./engine/read/read-key.js";
|
|
3
|
+
export type { Reader, ReadCtx, RowCtx } from "./engine/read/readers.js";
|
|
4
|
+
export { ReadPage } from "./engine/read/read-page.js";
|
|
5
|
+
export { defineReadBlock, readCheckpoint } from "./engine/read/read-block.js";
|
|
6
|
+
export type { ReadBlock, ReadBlockOptions, CheckpointRead } from "./engine/read/read-block.js";
|
package/dist/beta.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The `waygraph/beta` entry: features whose shape may still change before they move into `waygraph`. Import them from
|
|
2
|
+
// "waygraph/beta"; a beta release can rename or reshape them.
|
|
3
|
+
export { reads, ReadKey } from "./engine/read/read-key.js";
|
|
4
|
+
export { ReadPage } from "./engine/read/read-page.js";
|
|
5
|
+
export { defineReadBlock, readCheckpoint } from "./engine/read/read-block.js";
|
|
@@ -103,7 +103,7 @@ function indentOf(node) {
|
|
|
103
103
|
return " ".repeat(statement.getIndentationLevel() * 2);
|
|
104
104
|
}
|
|
105
105
|
function titleStub(block, title, indent) {
|
|
106
|
-
return `${block}.stubBefore((ctx) => {\n${indent} ctx.title(${title});\n${indent} })`;
|
|
106
|
+
return `${block}.stubBefore((ctx) => {\n${indent} ctx.ui.title(${title});\n${indent} })`;
|
|
107
107
|
}
|
|
108
108
|
function chain(head, steps, indent) {
|
|
109
109
|
return [head, ...steps, ".end()"].join(`\n${indent} `);
|
|
@@ -172,7 +172,7 @@ function rewrite(call, kindOf, needsMap) {
|
|
|
172
172
|
if (name === "withTitle") {
|
|
173
173
|
const first = mapCall && firstStepOf(mapCall);
|
|
174
174
|
if (!first)
|
|
175
|
-
return `withTitle(${flow.getText().split("\n")[0]}, …): name the flow with ctx.title(…) in a stub on its first step`;
|
|
175
|
+
return `withTitle(${flow.getText().split("\n")[0]}, …): name the flow with ctx.ui.title(…) in a stub on its first step`;
|
|
176
176
|
addTitle(first, args[1].getText());
|
|
177
177
|
}
|
|
178
178
|
else if (mapCall) {
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// `ctx.ring` calls, and a stub set in a `define*Block({ ... })` (options or instruction) moves out to a chained
|
|
7
7
|
// `.stubBefore(...)` on the Block; a Block's `slides` become `ctx.slides(...)` in its chained `.stubAfter(...)`;
|
|
8
8
|
// `ctx.highlights({ a: ring })` becomes one `ctx.ring("a", ring)` per ring; `narrate(locator, caption, action)` in an
|
|
9
|
-
// `act` becomes the action, reported so the caption can move to a stubBefore ring.
|
|
9
|
+
// `act` becomes the action, reported so the caption can move to a stubBefore ring. Last, everything a stub paints moves
|
|
10
|
+
// under `ctx.ui` (`ctx.ring(...)` -> `ctx.ui.ring(...)`). Works on the TypeScript AST (ts-morph).
|
|
10
11
|
import { Node, SyntaxKind } from "ts-morph";
|
|
11
12
|
const RENAMED = {
|
|
12
13
|
banner: (a) => `title(${a.join(", ")})`,
|
|
@@ -248,6 +249,45 @@ function unwrapNarrate(sf, problems) {
|
|
|
248
249
|
}
|
|
249
250
|
return changed;
|
|
250
251
|
}
|
|
252
|
+
/** Everything a stub paints, which 0.16.3 moved under `ctx.ui`. */
|
|
253
|
+
const UI_METHODS = new Set(["ring", "todos", "todoIndex", "todoGroups", "hideTodos", "device", "viewport", "touch", "orientation", "hideDevice", "zoom", "zoomOut", "title", "titlePos", "bannerUi", "caption", "hideCaption", "slides", "hideBanner", "showNetworkTab", "hideNetworkTab", "todoDockUi"]);
|
|
254
|
+
/** Whether `name` at `node` is a stub's ctx: the nearest enclosing function with a parameter of that name is a stub. */
|
|
255
|
+
function isStubCtxName(node, name) {
|
|
256
|
+
for (const fn of node.getAncestors()) {
|
|
257
|
+
if (!(Node.isArrowFunction(fn) || Node.isFunctionExpression(fn) || Node.isFunctionDeclaration(fn) || Node.isMethodDeclaration(fn)))
|
|
258
|
+
continue;
|
|
259
|
+
const param = fn.getParameters().find((p) => p.getName() === name);
|
|
260
|
+
if (!param)
|
|
261
|
+
continue;
|
|
262
|
+
if (/StubCtx/.test(param.getType().getText(param)))
|
|
263
|
+
return true;
|
|
264
|
+
const parent = fn.getParent();
|
|
265
|
+
const callee = Node.isCallExpression(parent) ? parent.getExpression() : undefined;
|
|
266
|
+
if (Node.isPropertyAccessExpression(callee) && /^stub(Before|After|OnError)$/.test(callee.getName()))
|
|
267
|
+
return true;
|
|
268
|
+
if (Node.isPropertyAssignment(parent) && /^stub(Before|After|OnError)$/.test(parent.getName()))
|
|
269
|
+
return true;
|
|
270
|
+
// `((ctx) => ...)(ctx)`, as the slides merge writes it: called at once with an outer stub's ctx.
|
|
271
|
+
const call = Node.isParenthesizedExpression(parent) ? parent.getParent() : undefined;
|
|
272
|
+
const arg = Node.isCallExpression(call) ? call.getArguments()[0] : undefined;
|
|
273
|
+
return arg !== undefined && Node.isIdentifier(arg) && isStubCtxName(call, arg.getText());
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
/** `ctx.ring(...)` -> `ctx.ui.ring(...)`, everywhere a stub's ctx paints (idempotent: `ctx.ui.ring` is left alone). */
|
|
278
|
+
function moveToUi(sf) {
|
|
279
|
+
let changed = false;
|
|
280
|
+
for (const access of sf.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression).reverse()) {
|
|
281
|
+
if (access.wasForgotten() || !UI_METHODS.has(access.getName()))
|
|
282
|
+
continue;
|
|
283
|
+
const receiver = access.getExpression();
|
|
284
|
+
if (!Node.isIdentifier(receiver) || !isStubCtxName(access, receiver.getText()))
|
|
285
|
+
continue;
|
|
286
|
+
access.replaceWithText(`${receiver.getText()}.ui.${access.getName()}`);
|
|
287
|
+
changed = true;
|
|
288
|
+
}
|
|
289
|
+
return changed;
|
|
290
|
+
}
|
|
251
291
|
/** Rewrites the old stub forms in every file; returns the files it changed and the calls left for a person. */
|
|
252
292
|
export function convertStubs(project) {
|
|
253
293
|
const changed = [];
|
|
@@ -267,6 +307,8 @@ export function convertStubs(project) {
|
|
|
267
307
|
if (convertStatements(block.getStatements(), left))
|
|
268
308
|
touched = true;
|
|
269
309
|
}
|
|
310
|
+
if (moveToUi(sf))
|
|
311
|
+
touched = true;
|
|
270
312
|
if (touched)
|
|
271
313
|
changed.push(sf);
|
|
272
314
|
problems.push(...left.map((p) => ({ file: sf.getFilePath(), ...p })));
|
|
@@ -9,6 +9,7 @@ import { resolveBaseUrl } from "../../runtime/waygraph-config.js";
|
|
|
9
9
|
import { parseCommand } from "../args.js";
|
|
10
10
|
import { applyRunFlags, parseRunFlags } from "../flags.js";
|
|
11
11
|
import { printBrowserSessionsList } from "../usage.js";
|
|
12
|
+
import { isSessionOp } from "./session-ops.js";
|
|
12
13
|
/** Runs a `pilot` subcommand; false when it is a session op for session-ops.ts. */
|
|
13
14
|
export async function pilotCase(args) {
|
|
14
15
|
if (args[1] === "sessions") {
|
|
@@ -76,26 +77,12 @@ export async function pilotCase(args) {
|
|
|
76
77
|
}
|
|
77
78
|
return true;
|
|
78
79
|
}
|
|
79
|
-
if (args[1]
|
|
80
|
-
args[1] !== "status" &&
|
|
81
|
-
args[1] !== "dom" &&
|
|
82
|
-
args[1] !== "trace" &&
|
|
83
|
-
args[1] !== "console" &&
|
|
84
|
-
args[1] !== "storage" &&
|
|
85
|
-
args[1] !== "upload" &&
|
|
86
|
-
args[1] !== "click" &&
|
|
87
|
-
args[1] !== "type" &&
|
|
88
|
-
args[1] !== "press" &&
|
|
89
|
-
args[1] !== "goto" &&
|
|
90
|
-
args[1] !== "reload" &&
|
|
91
|
-
args[1] !== "reach" &&
|
|
92
|
-
args[1] !== "resync" &&
|
|
93
|
-
args[1] !== "highlight") {
|
|
80
|
+
if (!isSessionOp("pilot", args[1])) {
|
|
94
81
|
console.error("waygraph pilot: usage:\n" +
|
|
95
82
|
" waygraph pilot start [--inject …] [--goto <url>] [--headless] [--cli] [--base-url <url>] [project]\n" +
|
|
96
83
|
" waygraph pilot sessions [project] # list live browser sessions\n" +
|
|
97
84
|
" waygraph pilot attach <sessionId> # graph + snapshot for an existing session\n" +
|
|
98
|
-
" waygraph pilot send|status|attach|highlight|… <sessionId> … (controls browser — same as browser/auto)");
|
|
85
|
+
" waygraph pilot send|status|read|attach|highlight|… <sessionId> … (controls browser — same as browser/auto)");
|
|
99
86
|
process.exit(1);
|
|
100
87
|
}
|
|
101
88
|
return false;
|
|
@@ -5,6 +5,8 @@ import { parseHighlightShorthand } from "../../demo/narration/shorthand.js";
|
|
|
5
5
|
import { resolveSessionMeta } from "../../live/auto-session/files.js";
|
|
6
6
|
import { requestSession, runAttachLoop } from "../../live/auto-session/ipc.js";
|
|
7
7
|
import { parseCommand } from "../args.js";
|
|
8
|
+
import { subscribeSession } from "../../live/auto-session/feed.js";
|
|
9
|
+
import { SESSION_OPS } from "../../live/auto-session/protocol.js";
|
|
8
10
|
const SEND_OPTIONS = {
|
|
9
11
|
timeout: { type: "string", value: "<ms>", help: "How long one Block may run (default 15000)" },
|
|
10
12
|
};
|
|
@@ -13,7 +15,8 @@ const DOM_OPTIONS = {
|
|
|
13
15
|
selector: { type: "string", value: "<css>", help: "Only this element's subtree" },
|
|
14
16
|
depth: { type: "string", value: "N", help: "Levels to read" },
|
|
15
17
|
};
|
|
16
|
-
|
|
18
|
+
/** The protocol's ops, as CLI subcommands: `subscribe` is `watch`; `attach` is the CLI's own interactive loop. */
|
|
19
|
+
const OPS = new Set([...SESSION_OPS.filter((op) => op !== "subscribe"), "watch", "attach"]);
|
|
17
20
|
/** Whether `sub` is a session op (`attach` is pilot's own subcommand, not an op, under `pilot`). */
|
|
18
21
|
export function isSessionOp(command, sub) {
|
|
19
22
|
return sub !== undefined && OPS.has(sub) && !(sub === "attach" && command === "pilot");
|
|
@@ -113,6 +116,28 @@ export async function sessionOpCase(args, command) {
|
|
|
113
116
|
process.exitCode = 1;
|
|
114
117
|
return;
|
|
115
118
|
}
|
|
119
|
+
if (sub === "hello") {
|
|
120
|
+
const res = await requestSession(proj, sessionId, { op: "hello" });
|
|
121
|
+
console.log(JSON.stringify(res));
|
|
122
|
+
if (!res.ok)
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (sub === "watch") {
|
|
127
|
+
// One JSON line per run event (steps, reads) and per new snapshot, until Ctrl-C or the session ends.
|
|
128
|
+
const feed = await subscribeSession(proj, sessionId, (message) => console.log(JSON.stringify(message)));
|
|
129
|
+
process.once("SIGINT", () => feed.close());
|
|
130
|
+
await feed.closed;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (sub === "read") {
|
|
134
|
+
const path = args[3];
|
|
135
|
+
const res = await requestSession(proj, sessionId, path ? { op: "read", path } : { op: "read" });
|
|
136
|
+
console.log(JSON.stringify(res));
|
|
137
|
+
if (!res.ok)
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
116
141
|
if (sub === "storage") {
|
|
117
142
|
const res = await requestSession(proj, sessionId, { op: "storage" });
|
|
118
143
|
console.log(JSON.stringify(res));
|
package/dist/cli/flags.d.ts
CHANGED
|
@@ -110,7 +110,7 @@ export declare const RUN_OPTIONS: {
|
|
|
110
110
|
};
|
|
111
111
|
readonly "todo-left": {
|
|
112
112
|
readonly type: "boolean";
|
|
113
|
-
readonly help: "Floating checklist dock on the left (also ctx.todos(id, items, { pos }))";
|
|
113
|
+
readonly help: "Floating checklist dock on the left (also ctx.ui.todos(id, items, { pos }))";
|
|
114
114
|
readonly for: readonly ["demo"];
|
|
115
115
|
};
|
|
116
116
|
readonly "todo-right": {
|
package/dist/cli/flags.js
CHANGED
|
@@ -18,7 +18,7 @@ export const RUN_OPTIONS = {
|
|
|
18
18
|
"fast": { type: "boolean", help: "Shorter auto-next / Next gates (keeps the smooth cursor)", for: ["demo"] },
|
|
19
19
|
"full": { type: "boolean", help: "Classic wrap-all block chips (default: carousel)", for: ["demo"] },
|
|
20
20
|
"mini": { type: "boolean", help: "Force the compact mini panel (Hide pill + Next)", for: ["demo"] },
|
|
21
|
-
"todo-left": { type: "boolean", help: "Floating checklist dock on the left (also ctx.todos(id, items, { pos }))", for: ["demo"] },
|
|
21
|
+
"todo-left": { type: "boolean", help: "Floating checklist dock on the left (also ctx.ui.todos(id, items, { pos }))", for: ["demo"] },
|
|
22
22
|
"todo-right": { type: "boolean", help: "Floating checklist dock on the right", for: ["demo"] },
|
|
23
23
|
"todo-full": { type: "boolean", help: "Full checklist: no compact / collision / behind-ring", for: ["demo"] },
|
|
24
24
|
"todo-smart": { type: "boolean", help: "Compact + collision + behind-ring (the default)", for: ["demo"] },
|
package/dist/cli/usage.js
CHANGED
|
@@ -66,6 +66,12 @@ Primary (less is more):
|
|
|
66
66
|
does nothing observable in the DOM) that inspectDom
|
|
67
67
|
alone can't explain, since it only reads what actually
|
|
68
68
|
rendered, not what the page/network actually said
|
|
69
|
+
waygraph auto watch <sessionId> Live feed, one JSON line each: every run event (steps,
|
|
70
|
+
step:read) and the snapshot after each change. Ctrl-C ends
|
|
71
|
+
waygraph auto hello <sessionId> The session protocol version, waygraph version, where it is
|
|
72
|
+
waygraph auto read <sessionId> [path] (beta) What the page shows, as JSON, from the project's
|
|
73
|
+
Read Blocks (*.read.block.ts); [path] narrows it, e.g.
|
|
74
|
+
post.comments. Read it before reaching for dom
|
|
69
75
|
waygraph auto storage <sessionId> Read localStorage/sessionStorage plus registered
|
|
70
76
|
service workers (scope/active URL/state) - client-
|
|
71
77
|
side state (push-subscription/auth tokens etc.) that
|
|
@@ -25,136 +25,138 @@ export function createStubCtx(bag, opts) {
|
|
|
25
25
|
mem: opts.mem,
|
|
26
26
|
out: opts.out,
|
|
27
27
|
error: opts.error,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
28
|
+
ui: {
|
|
29
|
+
ring(id, stub) {
|
|
30
|
+
bag.highlights = { ...bag.highlights, [id]: stub };
|
|
31
|
+
},
|
|
32
|
+
todos(id, items, opts = {}) {
|
|
33
|
+
setDock(id, opts);
|
|
34
|
+
bag.todos = items;
|
|
35
|
+
if (opts.index != null)
|
|
36
|
+
bag.todoIndex = Number(opts.index);
|
|
37
|
+
if (!items.length)
|
|
38
|
+
bag.hideTodos = true;
|
|
39
|
+
},
|
|
40
|
+
todoIndex(n) {
|
|
41
|
+
bag.todoIndex = n;
|
|
42
|
+
},
|
|
43
|
+
todoGroups(id, groups, opts = {}) {
|
|
44
|
+
setDock(id, opts);
|
|
45
|
+
bag.todoGroups = groups;
|
|
46
|
+
if (!groups.length)
|
|
47
|
+
bag.hideTodos = true;
|
|
48
|
+
},
|
|
49
|
+
hideTodos() {
|
|
50
|
+
bag.todoTouched = true;
|
|
46
51
|
bag.hideTodos = true;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
bag.device = resolved;
|
|
60
|
-
if (resolved.preset === "desktop" && !resolved.touchMode) {
|
|
61
|
-
bag.hideDevice = true;
|
|
52
|
+
bag.todos = [];
|
|
53
|
+
delete bag.todoGroups;
|
|
54
|
+
},
|
|
55
|
+
device(preset) {
|
|
56
|
+
bag.deviceTouched = true;
|
|
57
|
+
bag.hideDevice = false;
|
|
58
|
+
const resolved = resolveDeviceState(preset, bag.touchOverride, bag.orientationOverride);
|
|
59
|
+
if (resolved) {
|
|
60
|
+
bag.device = resolved;
|
|
61
|
+
if (resolved.preset === "desktop" && !resolved.touchMode) {
|
|
62
|
+
bag.hideDevice = true;
|
|
63
|
+
}
|
|
62
64
|
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
bag.deviceTouched = true;
|
|
67
|
-
bag.hideDevice = false;
|
|
68
|
-
const resolved = resolveDeviceState(box, bag.touchOverride, bag.orientationOverride);
|
|
69
|
-
if (resolved)
|
|
70
|
-
bag.device = resolved;
|
|
71
|
-
},
|
|
72
|
-
touch(on) {
|
|
73
|
-
bag.deviceTouched = true;
|
|
74
|
-
const enabled = on === undefined ? true : !!on;
|
|
75
|
-
bag.touchOverride = enabled;
|
|
76
|
-
if (enabled)
|
|
65
|
+
},
|
|
66
|
+
viewport(box) {
|
|
67
|
+
bag.deviceTouched = true;
|
|
77
68
|
bag.hideDevice = false;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
69
|
+
const resolved = resolveDeviceState(box, bag.touchOverride, bag.orientationOverride);
|
|
70
|
+
if (resolved)
|
|
71
|
+
bag.device = resolved;
|
|
72
|
+
},
|
|
73
|
+
touch(on) {
|
|
74
|
+
bag.deviceTouched = true;
|
|
75
|
+
const enabled = on === undefined ? true : !!on;
|
|
76
|
+
bag.touchOverride = enabled;
|
|
84
77
|
if (enabled)
|
|
85
78
|
bag.hideDevice = false;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
79
|
+
if (bag.device) {
|
|
80
|
+
bag.device = {
|
|
81
|
+
...bag.device,
|
|
82
|
+
touchMode: enabled,
|
|
83
|
+
hasTouch: bag.device.hasTouch || enabled,
|
|
84
|
+
};
|
|
85
|
+
if (enabled)
|
|
86
|
+
bag.hideDevice = false;
|
|
87
|
+
}
|
|
88
|
+
else if (enabled) {
|
|
89
|
+
const base = resolveDeviceState("desktop", true, bag.orientationOverride);
|
|
90
|
+
if (base)
|
|
91
|
+
bag.device = { ...base, touchMode: true, hasTouch: true };
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
orientation(o) {
|
|
95
|
+
const want = normalizeDeviceOrientation(o);
|
|
96
|
+
if (!want)
|
|
97
|
+
return;
|
|
98
|
+
bag.deviceTouched = true;
|
|
99
|
+
bag.hideDevice = false;
|
|
100
|
+
bag.orientationOverride = want;
|
|
101
|
+
const base = bag.device ||
|
|
102
|
+
resolveDeviceState("mobile", bag.touchOverride) ||
|
|
103
|
+
resolveDeviceState("mobile");
|
|
89
104
|
if (base)
|
|
90
|
-
bag.device =
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
bag.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
bag.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
},
|
|
146
|
-
bannerUi(opts) {
|
|
147
|
-
if (!opts || typeof opts !== "object")
|
|
148
|
-
return;
|
|
149
|
-
bag.bannerUi = { ...(bag.bannerUi || {}), ...opts };
|
|
150
|
-
},
|
|
151
|
-
hideBanner() {
|
|
152
|
-
bag.bannerUi = { ...(bag.bannerUi || {}), hidden: true };
|
|
153
|
-
},
|
|
154
|
-
todoDockUi(opts) {
|
|
155
|
-
if (!opts || typeof opts !== "object")
|
|
156
|
-
return;
|
|
157
|
-
bag.todoDockUi = { ...(bag.todoDockUi || {}), ...opts };
|
|
105
|
+
bag.device = applyOrientation(base, want);
|
|
106
|
+
},
|
|
107
|
+
hideDevice() {
|
|
108
|
+
bag.deviceTouched = true;
|
|
109
|
+
bag.hideDevice = true;
|
|
110
|
+
bag.device = resolveDeviceState("desktop", false);
|
|
111
|
+
bag.touchOverride = false;
|
|
112
|
+
bag.orientationOverride = undefined;
|
|
113
|
+
},
|
|
114
|
+
zoom(n) {
|
|
115
|
+
if (Number.isFinite(n) && n > 0)
|
|
116
|
+
bag.zoom = n;
|
|
117
|
+
},
|
|
118
|
+
zoomOut(keep) {
|
|
119
|
+
bag.zoomOut = !!keep;
|
|
120
|
+
},
|
|
121
|
+
title(text, opts) {
|
|
122
|
+
bag.title = String(text ?? "");
|
|
123
|
+
const p = normalizeBannerPos(opts?.pos);
|
|
124
|
+
if (p)
|
|
125
|
+
bag.titlePos = p;
|
|
126
|
+
},
|
|
127
|
+
titlePos(side) {
|
|
128
|
+
const p = normalizeBannerPos(side);
|
|
129
|
+
if (p)
|
|
130
|
+
bag.titlePos = p;
|
|
131
|
+
},
|
|
132
|
+
caption(text) {
|
|
133
|
+
bag.caption = String(text ?? "");
|
|
134
|
+
},
|
|
135
|
+
slides(slides) {
|
|
136
|
+
bag.slides = slides;
|
|
137
|
+
},
|
|
138
|
+
hideCaption() {
|
|
139
|
+
bag.caption = "";
|
|
140
|
+
},
|
|
141
|
+
showNetworkTab() {
|
|
142
|
+
bag.networkTabVisible = true;
|
|
143
|
+
},
|
|
144
|
+
hideNetworkTab() {
|
|
145
|
+
bag.networkTabVisible = false;
|
|
146
|
+
},
|
|
147
|
+
bannerUi(opts) {
|
|
148
|
+
if (!opts || typeof opts !== "object")
|
|
149
|
+
return;
|
|
150
|
+
bag.bannerUi = { ...(bag.bannerUi || {}), ...opts };
|
|
151
|
+
},
|
|
152
|
+
hideBanner() {
|
|
153
|
+
bag.bannerUi = { ...(bag.bannerUi || {}), hidden: true };
|
|
154
|
+
},
|
|
155
|
+
todoDockUi(opts) {
|
|
156
|
+
if (!opts || typeof opts !== "object")
|
|
157
|
+
return;
|
|
158
|
+
bag.todoDockUi = { ...(bag.todoDockUi || {}), ...opts };
|
|
159
|
+
},
|
|
158
160
|
},
|
|
159
161
|
};
|
|
160
162
|
}
|
|
@@ -51,7 +51,7 @@ export async function playSwipeTrail(page, opts) {
|
|
|
51
51
|
layer.id = "wg-swipe-layer";
|
|
52
52
|
layer.setAttribute("data-wg-ui", "1");
|
|
53
53
|
layer.style.cssText =
|
|
54
|
-
"position:fixed;inset:0;z-index:2147483646;pointer-events:none;overflow:hidden;";
|
|
54
|
+
"position:fixed;inset:0;z-index:var(--wg-z-cursor,2147483646);pointer-events:none;overflow:hidden;";
|
|
55
55
|
const finger = document.createElement("div");
|
|
56
56
|
finger.style.cssText =
|
|
57
57
|
"position:absolute;width:28px;height:28px;margin:-14px 0 0 -14px;border-radius:50%;" +
|
|
@@ -2,6 +2,7 @@ export function installBanner({ title, favicon, bannerPos, bannerUi, tag: banner
|
|
|
2
2
|
const POSITIONS = ["left", "center", "right"];
|
|
3
3
|
const applyPos = (el, pos) => {
|
|
4
4
|
el.dataset.pos = pos;
|
|
5
|
+
__wgAdd(el);
|
|
5
6
|
el.dataset.home = pos;
|
|
6
7
|
try {
|
|
7
8
|
localStorage.setItem("wg-banner-pos", pos);
|
|
@@ -79,6 +80,7 @@ export function installBanner({ title, favicon, bannerPos, bannerUi, tag: banner
|
|
|
79
80
|
if (bannerEl) {
|
|
80
81
|
if (bui.pos && POSITIONS.includes(bui.pos) && bannerEl.dataset.home !== bui.pos) {
|
|
81
82
|
bannerEl.dataset.pos = bui.pos;
|
|
83
|
+
__wgAdd(bannerEl);
|
|
82
84
|
bannerEl.dataset.home = bui.pos;
|
|
83
85
|
bannerEl.dataset.authored = "1";
|
|
84
86
|
}
|
|
@@ -108,6 +110,7 @@ export function installBanner({ title, favicon, bannerPos, bannerUi, tag: banner
|
|
|
108
110
|
(window.__wgBannerLog = window.__wgBannerLog || []).push(line);
|
|
109
111
|
};
|
|
110
112
|
b.dataset.pos = home;
|
|
113
|
+
__wgAdd(b);
|
|
111
114
|
if (!hits()) {
|
|
112
115
|
delete b.dataset.moved;
|
|
113
116
|
log("clear");
|
|
@@ -115,6 +118,7 @@ export function installBanner({ title, favicon, bannerPos, bannerUi, tag: banner
|
|
|
115
118
|
}
|
|
116
119
|
for (const cand of POSITIONS.filter((p) => p !== home)) {
|
|
117
120
|
b.dataset.pos = cand;
|
|
121
|
+
__wgAdd(b);
|
|
118
122
|
if (!hits()) {
|
|
119
123
|
b.dataset.moved = home + ">" + cand;
|
|
120
124
|
log("moved:" + cand, "moved " + home + " -> " + cand + " (ring overlap)");
|
|
@@ -122,6 +126,7 @@ export function installBanner({ title, favicon, bannerPos, bannerUi, tag: banner
|
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
b.dataset.pos = home;
|
|
129
|
+
__wgAdd(b);
|
|
125
130
|
b.dataset.moved = "none-free";
|
|
126
131
|
log("none-free", "overlap at every position (kept " + home + ")");
|
|
127
132
|
};
|