waygraph 0.16.1 → 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 +132 -2
- 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/run-chain.js +2 -2
- package/dist/cli/usage.js +6 -0
- package/dist/cli/util.js +1 -1
- package/dist/demo/narration/demo-pace.d.ts +1 -1
- package/dist/demo/narration/demo-pace.js +1 -1
- package/dist/demo/narration/merge.d.ts +1 -1
- package/dist/demo/narration/merge.js +1 -1
- package/dist/demo/narration/narration.d.ts +2 -6
- package/dist/demo/narration/narration.js +5 -17
- 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 -6
- 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 +19 -76
- package/dist/demo/overlay/overlay-stage.js +1 -1
- package/dist/demo/overlay/overlay.js +3 -2
- package/dist/demo/overlay/phase-fixtures.js +5 -5
- package/dist/demo/overlay/pilot/shell.js +7 -5
- package/dist/demo/overlay/rings.d.ts +0 -8
- package/dist/demo/overlay/rings.js +0 -15
- package/dist/demo/overlay/ui/components/ring.js +2 -2
- package/dist/demo/overlay/ui/overlay.css +136 -33
- package/dist/demo/overlay/ui/shadow.js +69 -2
- package/dist/demo/stepper/after.js +4 -17
- package/dist/demo/stepper/before.js +3 -1
- package/dist/demo/stepper/demo-log.js +8 -8
- package/dist/demo/stepper/interaction-patch.js +3 -15
- package/dist/demo/stepper/panel-after.js +1 -1
- package/dist/demo/stepper/slides.d.ts +3 -0
- package/dist/demo/stepper/slides.js +18 -2
- package/dist/demo/stepper/stepper.js +1 -1
- package/dist/demo/stepper/teardown.js +0 -1
- package/dist/demo/verbose-log.js +1 -1
- package/dist/engine/block/block.d.ts +1 -12
- package/dist/engine/block/kind.d.ts +3 -0
- package/dist/engine/block/kind.js +11 -0
- package/dist/engine/block/kinds/method.d.ts +0 -2
- package/dist/engine/block/kinds/method.js +0 -1
- package/dist/engine/block/kinds/nav.js +1 -1
- 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.d.ts +1 -19
- package/dist/engine/run/run.js +21 -97
- package/dist/engine/stub/ring.d.ts +2 -4
- package/dist/engine/stub/ring.js +1 -0
- package/dist/engine/stub/stub.d.ts +38 -25
- package/dist/index.d.ts +9 -7
- package/dist/index.js +11 -6
- 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/plan/find.js +2 -2
- package/dist/runner/plan/plan.js +1 -1
- package/dist/runner/plan/seed-mem.js +5 -5
- package/dist/runner/plan/spec.js +1 -1
- 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/checkout-step-two/methods/finish-order.method.block.ts +2 -34
- 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 +3 -2
- package/examples/saucedemo/src/flows/checkout.flow.ts +13 -12
- package/examples/saucedemo/src/flows/login.flow.ts +1 -1
- package/examples/saucedemo/src/flows/shop.flow.ts +3 -2
- 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 +70 -76
- package/examples/saucedemo/src/stubs/finish-order.ts +38 -0
- package/examples/saucedemo/src/stubs/login.ts +5 -5
- package/package.json +5 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
- 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 +3 -2
- package/templates/quickstart/src/flows/checkout.flow.ts +13 -12
- package/templates/quickstart/src/flows/login.flow.ts +1 -1
- package/templates/quickstart/src/flows/shop.flow.ts +3 -2
- 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 +70 -76
- package/templates/quickstart/src/stubs/finish-order.ts +38 -0
- 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 +10 -7
- package/templates/scaffold/src/map/(app_base)/home/_methods/assert-hello.method.block.ts +1 -9
- 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) {
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
// aliases (`banner`, `bannerPos`, `clearDevice`, `clearTodos`, `landscape`, `portrait`, `todoDockFull`) become the
|
|
5
5
|
// names they stood for. A stub written as a slot map (`{ email: { selector, label } }`) becomes a function of
|
|
6
6
|
// `ctx.ring` calls, and a stub set in a `define*Block({ ... })` (options or instruction) moves out to a chained
|
|
7
|
-
// `.stubBefore(...)` on the Block
|
|
7
|
+
// `.stubBefore(...)` on the Block; a Block's `slides` become `ctx.slides(...)` in its chained `.stubAfter(...)`;
|
|
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. Last, everything a stub paints moves
|
|
10
|
+
// under `ctx.ui` (`ctx.ring(...)` -> `ctx.ui.ring(...)`). Works on the TypeScript AST (ts-morph).
|
|
8
11
|
import { Node, SyntaxKind } from "ts-morph";
|
|
9
12
|
const RENAMED = {
|
|
10
13
|
banner: (a) => `title(${a.join(", ")})`,
|
|
@@ -127,7 +130,7 @@ function convertSlotMaps(sf) {
|
|
|
127
130
|
}
|
|
128
131
|
return changed;
|
|
129
132
|
}
|
|
130
|
-
/** `defineXBlock({ ..., stubBefore: fn })` (or in its instruction) -> `defineXBlock({ ... }).stubBefore(fn)`. */
|
|
133
|
+
/** `defineXBlock({ ..., stubBefore: fn, slides })` (or in its instruction) -> `defineXBlock({ ... }).stubBefore(fn).stubAfter(slides)`. */
|
|
131
134
|
function hoistDefinitionStubs(sf) {
|
|
132
135
|
let changed = false;
|
|
133
136
|
for (const call of sf.getDescendantsOfKind(SyntaxKind.CallExpression).reverse()) {
|
|
@@ -141,9 +144,17 @@ function hoistDefinitionStubs(sf) {
|
|
|
141
144
|
if (Node.isPropertyAssignment(instruction) && Node.isObjectLiteralExpression(instruction.getInitializer()))
|
|
142
145
|
holders.push(instruction.getInitializer());
|
|
143
146
|
const chained = [];
|
|
147
|
+
let slides;
|
|
144
148
|
for (const holder of holders) {
|
|
145
149
|
for (const prop of holder.getProperties()) {
|
|
146
150
|
const name = Node.isPropertyAssignment(prop) || Node.isMethodDeclaration(prop) ? prop.getName() : "";
|
|
151
|
+
if (name === "slides" && Node.isPropertyAssignment(prop)) {
|
|
152
|
+
const init = prop.getInitializerOrThrow();
|
|
153
|
+
// `slides: (out) => [...]` read the Checkpoint the step reached; in stubAfter that is ctx.out.
|
|
154
|
+
slides = Node.isArrowFunction(init) || Node.isFunctionExpression(init) ? `(${init.getText()})(ctx.out!)` : init.getText();
|
|
155
|
+
prop.remove();
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
147
158
|
if (!PHASES.has(name))
|
|
148
159
|
continue;
|
|
149
160
|
let fn;
|
|
@@ -157,6 +168,15 @@ function hoistDefinitionStubs(sf) {
|
|
|
157
168
|
prop.remove();
|
|
158
169
|
}
|
|
159
170
|
}
|
|
171
|
+
if (slides !== undefined) {
|
|
172
|
+
const after = chained.findIndex((c) => c.startsWith(".stubAfter("));
|
|
173
|
+
if (after < 0)
|
|
174
|
+
chained.push(`.stubAfter((ctx) => ctx.slides(${slides}))`);
|
|
175
|
+
else {
|
|
176
|
+
const fn = chained[after].slice(".stubAfter(".length, -1);
|
|
177
|
+
chained[after] = `.stubAfter(async (ctx) => {\n await (${fn})(ctx);\n ctx.slides(${slides});\n})`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
160
180
|
if (chained.length) {
|
|
161
181
|
call.replaceWithText(call.getText() + chained.join(""));
|
|
162
182
|
changed = true;
|
|
@@ -164,6 +184,110 @@ function hoistDefinitionStubs(sf) {
|
|
|
164
184
|
}
|
|
165
185
|
return changed;
|
|
166
186
|
}
|
|
187
|
+
/** `ctx.highlights({ a: ring, b: ring })` -> `ctx.ring("a", ring); ctx.ring("b", ring);`, one call at a time. */
|
|
188
|
+
function convertHighlights(sf, problems) {
|
|
189
|
+
let changed = false;
|
|
190
|
+
const skipped = new Set();
|
|
191
|
+
for (;;) {
|
|
192
|
+
// Raw text replacement forgets the file's nodes, so look the next call up again each time.
|
|
193
|
+
const statement = sf.getDescendantsOfKind(SyntaxKind.ExpressionStatement).find((st) => {
|
|
194
|
+
const c = ctxCall(st);
|
|
195
|
+
return c && c.method === "highlights" && !skipped.has(st.getStart()) && isStubCtx(c.call, c.receiver);
|
|
196
|
+
});
|
|
197
|
+
if (!statement)
|
|
198
|
+
return changed;
|
|
199
|
+
const c = ctxCall(statement);
|
|
200
|
+
const map = c.call.getArguments()[0];
|
|
201
|
+
if (!Node.isObjectLiteralExpression(map)) {
|
|
202
|
+
problems.push({ line: c.call.getStartLineNumber(), reason: "ctx.highlights(...) was removed and its argument is not an object literal: call ctx.ring(id, ring) for each ring" });
|
|
203
|
+
skipped.add(statement.getStart());
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
// Each ring keeps its own lines, moved to the statement's indent (replaceWithText would re-indent them).
|
|
207
|
+
const column = (n) => n.getStart() - n.getStartLinePos();
|
|
208
|
+
const indent = " ".repeat(column(statement));
|
|
209
|
+
const rings = map.getProperties().flatMap((p) => {
|
|
210
|
+
if (!Node.isPropertyAssignment(p))
|
|
211
|
+
return [];
|
|
212
|
+
const from = column(p);
|
|
213
|
+
const value = p.getInitializerOrThrow().getText().split("\n").map((l, n) => (n === 0 ? l : indent + l.slice(Math.min(from, l.search(/\S|$/))))).join("\n");
|
|
214
|
+
return [`${c.receiver}.ring(${JSON.stringify(p.getName().replace(/^["']|["']$/g, ""))}, ${value});`];
|
|
215
|
+
});
|
|
216
|
+
sf.replaceText([statement.getStart(), statement.getEnd()], rings.join("\n" + indent));
|
|
217
|
+
changed = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* `narrate(locator, caption, () => action)` in an `act` -> just the action. The caption was narration inside the
|
|
222
|
+
* Block; it belongs in a `stubBefore` ring where the flow uses the Block, which the codemod can't place, so it is
|
|
223
|
+
* reported.
|
|
224
|
+
*/
|
|
225
|
+
function unwrapNarrate(sf, problems) {
|
|
226
|
+
let changed = false;
|
|
227
|
+
for (const call of sf.getDescendantsOfKind(SyntaxKind.CallExpression).reverse()) {
|
|
228
|
+
if (call.wasForgotten() || call.getExpression().getText() !== "narrate" || call.getArguments().length !== 3)
|
|
229
|
+
continue;
|
|
230
|
+
const [, captionNode, action] = call.getArguments();
|
|
231
|
+
const caption = captionNode.getText();
|
|
232
|
+
const line = call.getStartLineNumber();
|
|
233
|
+
let body = `(${action.getText()})()`;
|
|
234
|
+
if (Node.isArrowFunction(action) && !Node.isBlock(action.getBody()))
|
|
235
|
+
body = action.getBody().getText();
|
|
236
|
+
call.replaceWithText(body);
|
|
237
|
+
problems.push({ line, reason: `narrate(...) was removed; its caption ${caption} is gone: ring the element in a stubBefore where the flow uses this Block` });
|
|
238
|
+
changed = true;
|
|
239
|
+
}
|
|
240
|
+
if (changed) {
|
|
241
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
242
|
+
const spec = decl.getNamedImports().find((n) => n.getName() === "narrate");
|
|
243
|
+
if (!spec)
|
|
244
|
+
continue;
|
|
245
|
+
spec.remove();
|
|
246
|
+
if (decl.getNamedImports().length === 0 && !decl.getDefaultImport() && !decl.getNamespaceImport())
|
|
247
|
+
decl.remove();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return changed;
|
|
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
|
+
}
|
|
167
291
|
/** Rewrites the old stub forms in every file; returns the files it changed and the calls left for a person. */
|
|
168
292
|
export function convertStubs(project) {
|
|
169
293
|
const changed = [];
|
|
@@ -171,6 +295,10 @@ export function convertStubs(project) {
|
|
|
171
295
|
for (const sf of project.getSourceFiles()) {
|
|
172
296
|
const left = [];
|
|
173
297
|
let touched = hoistDefinitionStubs(sf);
|
|
298
|
+
if (unwrapNarrate(sf, left))
|
|
299
|
+
touched = true;
|
|
300
|
+
if (convertHighlights(sf, left))
|
|
301
|
+
touched = true;
|
|
174
302
|
if (convertSlotMaps(sf))
|
|
175
303
|
touched = true;
|
|
176
304
|
for (const block of sf.getDescendantsOfKind(SyntaxKind.Block).reverse()) {
|
|
@@ -179,6 +307,8 @@ export function convertStubs(project) {
|
|
|
179
307
|
if (convertStatements(block.getStatements(), left))
|
|
180
308
|
touched = true;
|
|
181
309
|
}
|
|
310
|
+
if (moveToUi(sf))
|
|
311
|
+
touched = true;
|
|
182
312
|
if (touched)
|
|
183
313
|
changed.push(sf);
|
|
184
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/run-chain.js
CHANGED
|
@@ -92,12 +92,12 @@ export async function runChainAuto(projectDir, fromTag, toTag) {
|
|
|
92
92
|
const graph = await discoverGraph(projectDir);
|
|
93
93
|
const path = findBlockPath(graph, fromTag, toTag);
|
|
94
94
|
if (!path) {
|
|
95
|
-
console.error(`waygraph
|
|
95
|
+
console.error(`waygraph auto --blocks: no Block path from "${fromTag}" to "${toTag}" in the discovered graph`);
|
|
96
96
|
process.exitCode = 1;
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
99
|
const spec = path.join(" then ");
|
|
100
|
-
console.error(`waygraph
|
|
100
|
+
console.error(`waygraph auto --blocks: ${fromTag} -> ${toTag} via ${spec}`);
|
|
101
101
|
if (!envString("WAYGRAPH_BASE_URL")) {
|
|
102
102
|
const resolved = await resolveBaseUrl(projectDir);
|
|
103
103
|
if (resolved)
|
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
|
package/dist/cli/util.js
CHANGED
|
@@ -4,7 +4,7 @@ import { spawn } from "node:child_process";
|
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
// `try` - a bundled, self-contained showcase. No .flow.ts to write, no
|
|
6
6
|
// scenario to design - copies a small real Flow (nav/action Blocks, Trait
|
|
7
|
-
// verify,
|
|
7
|
+
// verify, ctx stubs) into the target project and runs it in step mode
|
|
8
8
|
// against a public demo site, so a curious dev sees the real overlay/panel
|
|
9
9
|
// experience in under a minute, then gets pointed at the plain waygraph
|
|
10
10
|
// source that produced it.
|
|
@@ -29,7 +29,7 @@ export declare function formatDemoPaceBadge(pace: DemoPace, baseAutoplayMs?: num
|
|
|
29
29
|
*/
|
|
30
30
|
export declare function formatDemoPaceLabel(pace: DemoPace, baseAutoplayMs?: number): string;
|
|
31
31
|
/**
|
|
32
|
-
* Resolve min dwell ms for any fixture (stub
|
|
32
|
+
* Resolve min dwell ms for any fixture (a stub ring or a yap slide),
|
|
33
33
|
* or `null` when duration is unset (caller keeps its default timing).
|
|
34
34
|
*
|
|
35
35
|
* Authoring wins: an explicit slow/numeric pace is not crushed by CLI `--fast`
|
|
@@ -150,7 +150,7 @@ export function formatDemoPaceLabel(pace, baseAutoplayMs = 1800) {
|
|
|
150
150
|
return `pace normal (1x, ~${gate}ms gates)`;
|
|
151
151
|
}
|
|
152
152
|
/**
|
|
153
|
-
* Resolve min dwell ms for any fixture (stub
|
|
153
|
+
* Resolve min dwell ms for any fixture (a stub ring or a yap slide),
|
|
154
154
|
* or `null` when duration is unset (caller keeps its default timing).
|
|
155
155
|
*
|
|
156
156
|
* Authoring wins: an explicit slow/numeric pace is not crushed by CLI `--fast`
|
|
@@ -4,7 +4,7 @@ import type { SomeBlock } from "../../engine/block/block.js";
|
|
|
4
4
|
import type { Checkpoint } from "../../engine/checkpoint/checkpoint.js";
|
|
5
5
|
export declare function applyDefaultZoom(slots: ResolvedHighlight[], defaultZoom?: number): ResolvedHighlight[];
|
|
6
6
|
export declare function applyDefaultZoomOut(slots: ResolvedHighlight[], defaultZoomOut?: boolean): ResolvedHighlight[];
|
|
7
|
-
/** A stub phase's
|
|
7
|
+
/** A stub phase's rings (by id, as ctx.ring set them) as an ordered list of resolved highlights. */
|
|
8
8
|
export declare function resolvePhaseMap(phaseMap: HighlightStubPhase): ResolvedHighlight[];
|
|
9
9
|
/** The stub a Block's flow chained for this phase, if any. */
|
|
10
10
|
export declare function readStubRaw(block: SomeBlock, phase: HighlightStubPhaseName): StubLifecycleFn<Checkpoint<string>> | undefined;
|
|
@@ -40,7 +40,7 @@ export function applyDefaultZoomOut(slots, defaultZoomOut) {
|
|
|
40
40
|
return slots;
|
|
41
41
|
return slots.map((h) => h.zoomOut !== undefined ? h : { ...h, zoomOut: defaultZoomOut });
|
|
42
42
|
}
|
|
43
|
-
/** A stub phase's
|
|
43
|
+
/** A stub phase's rings (by id, as ctx.ring set them) as an ordered list of resolved highlights. */
|
|
44
44
|
export function resolvePhaseMap(phaseMap) {
|
|
45
45
|
const phaseKeys = Object.keys(phaseMap);
|
|
46
46
|
const allNumeric = phaseKeys.length > 0 && phaseKeys.every((k) => Number.isFinite(Number(k)) && String(Number(k)) === k);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { HighlightStubPhaseName, StubPhaseResult } from "../../engine/stub/stub.js";
|
|
2
|
-
import type { ResolvedHighlight
|
|
2
|
+
import type { ResolvedHighlight } from "../../engine/stub/ring.js";
|
|
3
3
|
import type { SomeBlock } from "../../engine/block/block.js";
|
|
4
4
|
import type { Checkpoint } from "../../engine/checkpoint/checkpoint.js";
|
|
5
5
|
import type { MemPage } from "../../engine/mem/mem.js";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Runs a Block's stub for one phase and returns what it painted.
|
|
8
8
|
* Returns rings + episode fixtures (todos / todoIndex / zoom).
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
@@ -23,10 +23,6 @@ export declare function runStubPhase(block: SomeBlock, phase: HighlightStubPhase
|
|
|
23
23
|
export declare function resolveHighlightSlots(block: SomeBlock, phase: HighlightStubPhaseName, opts?: {
|
|
24
24
|
out?: Checkpoint<string>;
|
|
25
25
|
}): ResolvedHighlight[];
|
|
26
|
-
/** Resolve multi-step yap slides from `instruction.slides` (function form allowed). */
|
|
27
|
-
export declare function resolveSlides(block: SomeBlock, opts?: {
|
|
28
|
-
out?: Checkpoint<string>;
|
|
29
|
-
}): WaygraphSlide[];
|
|
30
26
|
/**
|
|
31
27
|
* True when the block authored stubAfter (lifecycle fn or slots).
|
|
32
28
|
* When true, demo should not use verify-trait fallback.
|
|
@@ -3,7 +3,7 @@ import { applyDefaultZoom, applyDefaultZoomOut, readStubRaw, resolvePhaseMap } f
|
|
|
3
3
|
import { buildTodoDock, normalizeTodoStyle, normalizeTodos } from "./todo-dock.js";
|
|
4
4
|
import { resolveDeviceState } from "./device.js";
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Runs a Block's stub for one phase and returns what it painted.
|
|
7
7
|
* Returns rings + episode fixtures (todos / todoIndex / zoom).
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
@@ -27,6 +27,9 @@ export async function runStubPhase(block, phase, opts) {
|
|
|
27
27
|
throw new Error(`${block.name}: its ${phase} stub threw: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
if (phase === "stubOnError" && bag.slides?.length) {
|
|
31
|
+
throw new Error(`${block.name}: its stubOnError stub set slides; slides play before or after a step (stubBefore / stubAfter), not on its failure`);
|
|
32
|
+
}
|
|
30
33
|
let highlights = applyDefaultZoom(resolvePhaseMap(bag.highlights), bag.zoom);
|
|
31
34
|
highlights = applyDefaultZoomOut(highlights, bag.zoomOut);
|
|
32
35
|
const todoDock = bag.hideTodos
|
|
@@ -95,6 +98,7 @@ export async function runStubPhase(block, phase, opts) {
|
|
|
95
98
|
// Boolean, not string - "" isn't a state here, so a plain undefined-check (like zoom/title)
|
|
96
99
|
// is enough; false is real ("author called hideNetworkTab()") and must still be forwarded.
|
|
97
100
|
...(bag.networkTabVisible !== undefined ? { networkTabVisible: bag.networkTabVisible } : {}),
|
|
101
|
+
slides: [...(bag.slides ?? [])],
|
|
98
102
|
...(bag.todoPos !== undefined ? { todoPos: bag.todoPos } : {}),
|
|
99
103
|
...(bag.todoDockUi !== undefined ? { todoDockUi: bag.todoDockUi } : {}),
|
|
100
104
|
...(bag.todoParallel === true ? { todoParallel: true } : {}),
|
|
@@ -118,22 +122,6 @@ export function resolveHighlightSlots(block, phase, opts) {
|
|
|
118
122
|
}
|
|
119
123
|
return applyDefaultZoom(resolvePhaseMap(bag.highlights), bag.zoom);
|
|
120
124
|
}
|
|
121
|
-
/** Resolve multi-step yap slides from `instruction.slides` (function form allowed). */
|
|
122
|
-
export function resolveSlides(block, opts) {
|
|
123
|
-
const instr = block.instruction;
|
|
124
|
-
let raw = instr.slides;
|
|
125
|
-
if (typeof raw === "function") {
|
|
126
|
-
try {
|
|
127
|
-
raw = raw(opts?.out ?? { __state: "" });
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
raw = [];
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (!Array.isArray(raw))
|
|
134
|
-
return [];
|
|
135
|
-
return raw.filter((s) => s && typeof s.caption === "string" && s.caption.length > 0);
|
|
136
|
-
}
|
|
137
125
|
function stubPhaseLooksAuthored(block, phase) {
|
|
138
126
|
return readStubRaw(block, phase) !== undefined;
|
|
139
127
|
}
|