waygraph 0.16.1 → 0.16.2
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/dist/cli/commands/codemod-stubs.js +90 -2
- package/dist/cli/run-chain.js +2 -2
- 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 +3 -3
- package/dist/demo/overlay/inpage/core.js +0 -6
- package/dist/demo/overlay/overlay-css.js +1 -55
- package/dist/demo/overlay/overlay.js +3 -2
- package/dist/demo/overlay/rings.d.ts +0 -8
- package/dist/demo/overlay/rings.js +0 -15
- package/dist/demo/overlay/ui/overlay.css +40 -0
- package/dist/demo/stepper/after.js +4 -17
- package/dist/demo/stepper/before.js +3 -1
- package/dist/demo/stepper/demo-log.js +1 -1
- 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/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/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/run/run.d.ts +1 -19
- package/dist/engine/run/run.js +0 -88
- package/dist/engine/stub/ring.d.ts +2 -4
- package/dist/engine/stub/ring.js +1 -0
- package/dist/engine/stub/stub.d.ts +16 -8
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -2
- 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/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
- package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +2 -1
- package/examples/saucedemo/src/flows/checkout.flow.ts +2 -1
- package/examples/saucedemo/src/flows/shop.flow.ts +2 -1
- package/examples/saucedemo/src/stubs/device-showcase.ts +37 -43
- package/examples/saucedemo/src/stubs/finish-order.ts +38 -0
- package/package.json +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
- package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +2 -1
- package/templates/quickstart/src/flows/checkout.flow.ts +2 -1
- package/templates/quickstart/src/flows/shop.flow.ts +2 -1
- package/templates/quickstart/src/stubs/device-showcase.ts +37 -43
- package/templates/quickstart/src/stubs/finish-order.ts +38 -0
- package/templates/scaffold/src/flows/home/hello.flow.ts +3 -0
- package/templates/scaffold/src/map/(app_base)/home/_methods/assert-hello.method.block.ts +1 -9
|
@@ -4,7 +4,9 @@
|
|
|
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. Works on the TypeScript AST (ts-morph).
|
|
8
10
|
import { Node, SyntaxKind } from "ts-morph";
|
|
9
11
|
const RENAMED = {
|
|
10
12
|
banner: (a) => `title(${a.join(", ")})`,
|
|
@@ -127,7 +129,7 @@ function convertSlotMaps(sf) {
|
|
|
127
129
|
}
|
|
128
130
|
return changed;
|
|
129
131
|
}
|
|
130
|
-
/** `defineXBlock({ ..., stubBefore: fn })` (or in its instruction) -> `defineXBlock({ ... }).stubBefore(fn)`. */
|
|
132
|
+
/** `defineXBlock({ ..., stubBefore: fn, slides })` (or in its instruction) -> `defineXBlock({ ... }).stubBefore(fn).stubAfter(slides)`. */
|
|
131
133
|
function hoistDefinitionStubs(sf) {
|
|
132
134
|
let changed = false;
|
|
133
135
|
for (const call of sf.getDescendantsOfKind(SyntaxKind.CallExpression).reverse()) {
|
|
@@ -141,9 +143,17 @@ function hoistDefinitionStubs(sf) {
|
|
|
141
143
|
if (Node.isPropertyAssignment(instruction) && Node.isObjectLiteralExpression(instruction.getInitializer()))
|
|
142
144
|
holders.push(instruction.getInitializer());
|
|
143
145
|
const chained = [];
|
|
146
|
+
let slides;
|
|
144
147
|
for (const holder of holders) {
|
|
145
148
|
for (const prop of holder.getProperties()) {
|
|
146
149
|
const name = Node.isPropertyAssignment(prop) || Node.isMethodDeclaration(prop) ? prop.getName() : "";
|
|
150
|
+
if (name === "slides" && Node.isPropertyAssignment(prop)) {
|
|
151
|
+
const init = prop.getInitializerOrThrow();
|
|
152
|
+
// `slides: (out) => [...]` read the Checkpoint the step reached; in stubAfter that is ctx.out.
|
|
153
|
+
slides = Node.isArrowFunction(init) || Node.isFunctionExpression(init) ? `(${init.getText()})(ctx.out!)` : init.getText();
|
|
154
|
+
prop.remove();
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
147
157
|
if (!PHASES.has(name))
|
|
148
158
|
continue;
|
|
149
159
|
let fn;
|
|
@@ -157,6 +167,15 @@ function hoistDefinitionStubs(sf) {
|
|
|
157
167
|
prop.remove();
|
|
158
168
|
}
|
|
159
169
|
}
|
|
170
|
+
if (slides !== undefined) {
|
|
171
|
+
const after = chained.findIndex((c) => c.startsWith(".stubAfter("));
|
|
172
|
+
if (after < 0)
|
|
173
|
+
chained.push(`.stubAfter((ctx) => ctx.slides(${slides}))`);
|
|
174
|
+
else {
|
|
175
|
+
const fn = chained[after].slice(".stubAfter(".length, -1);
|
|
176
|
+
chained[after] = `.stubAfter(async (ctx) => {\n await (${fn})(ctx);\n ctx.slides(${slides});\n})`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
160
179
|
if (chained.length) {
|
|
161
180
|
call.replaceWithText(call.getText() + chained.join(""));
|
|
162
181
|
changed = true;
|
|
@@ -164,6 +183,71 @@ function hoistDefinitionStubs(sf) {
|
|
|
164
183
|
}
|
|
165
184
|
return changed;
|
|
166
185
|
}
|
|
186
|
+
/** `ctx.highlights({ a: ring, b: ring })` -> `ctx.ring("a", ring); ctx.ring("b", ring);`, one call at a time. */
|
|
187
|
+
function convertHighlights(sf, problems) {
|
|
188
|
+
let changed = false;
|
|
189
|
+
const skipped = new Set();
|
|
190
|
+
for (;;) {
|
|
191
|
+
// Raw text replacement forgets the file's nodes, so look the next call up again each time.
|
|
192
|
+
const statement = sf.getDescendantsOfKind(SyntaxKind.ExpressionStatement).find((st) => {
|
|
193
|
+
const c = ctxCall(st);
|
|
194
|
+
return c && c.method === "highlights" && !skipped.has(st.getStart()) && isStubCtx(c.call, c.receiver);
|
|
195
|
+
});
|
|
196
|
+
if (!statement)
|
|
197
|
+
return changed;
|
|
198
|
+
const c = ctxCall(statement);
|
|
199
|
+
const map = c.call.getArguments()[0];
|
|
200
|
+
if (!Node.isObjectLiteralExpression(map)) {
|
|
201
|
+
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" });
|
|
202
|
+
skipped.add(statement.getStart());
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
// Each ring keeps its own lines, moved to the statement's indent (replaceWithText would re-indent them).
|
|
206
|
+
const column = (n) => n.getStart() - n.getStartLinePos();
|
|
207
|
+
const indent = " ".repeat(column(statement));
|
|
208
|
+
const rings = map.getProperties().flatMap((p) => {
|
|
209
|
+
if (!Node.isPropertyAssignment(p))
|
|
210
|
+
return [];
|
|
211
|
+
const from = column(p);
|
|
212
|
+
const value = p.getInitializerOrThrow().getText().split("\n").map((l, n) => (n === 0 ? l : indent + l.slice(Math.min(from, l.search(/\S|$/))))).join("\n");
|
|
213
|
+
return [`${c.receiver}.ring(${JSON.stringify(p.getName().replace(/^["']|["']$/g, ""))}, ${value});`];
|
|
214
|
+
});
|
|
215
|
+
sf.replaceText([statement.getStart(), statement.getEnd()], rings.join("\n" + indent));
|
|
216
|
+
changed = true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* `narrate(locator, caption, () => action)` in an `act` -> just the action. The caption was narration inside the
|
|
221
|
+
* Block; it belongs in a `stubBefore` ring where the flow uses the Block, which the codemod can't place, so it is
|
|
222
|
+
* reported.
|
|
223
|
+
*/
|
|
224
|
+
function unwrapNarrate(sf, problems) {
|
|
225
|
+
let changed = false;
|
|
226
|
+
for (const call of sf.getDescendantsOfKind(SyntaxKind.CallExpression).reverse()) {
|
|
227
|
+
if (call.wasForgotten() || call.getExpression().getText() !== "narrate" || call.getArguments().length !== 3)
|
|
228
|
+
continue;
|
|
229
|
+
const [, captionNode, action] = call.getArguments();
|
|
230
|
+
const caption = captionNode.getText();
|
|
231
|
+
const line = call.getStartLineNumber();
|
|
232
|
+
let body = `(${action.getText()})()`;
|
|
233
|
+
if (Node.isArrowFunction(action) && !Node.isBlock(action.getBody()))
|
|
234
|
+
body = action.getBody().getText();
|
|
235
|
+
call.replaceWithText(body);
|
|
236
|
+
problems.push({ line, reason: `narrate(...) was removed; its caption ${caption} is gone: ring the element in a stubBefore where the flow uses this Block` });
|
|
237
|
+
changed = true;
|
|
238
|
+
}
|
|
239
|
+
if (changed) {
|
|
240
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
241
|
+
const spec = decl.getNamedImports().find((n) => n.getName() === "narrate");
|
|
242
|
+
if (!spec)
|
|
243
|
+
continue;
|
|
244
|
+
spec.remove();
|
|
245
|
+
if (decl.getNamedImports().length === 0 && !decl.getDefaultImport() && !decl.getNamespaceImport())
|
|
246
|
+
decl.remove();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return changed;
|
|
250
|
+
}
|
|
167
251
|
/** Rewrites the old stub forms in every file; returns the files it changed and the calls left for a person. */
|
|
168
252
|
export function convertStubs(project) {
|
|
169
253
|
const changed = [];
|
|
@@ -171,6 +255,10 @@ export function convertStubs(project) {
|
|
|
171
255
|
for (const sf of project.getSourceFiles()) {
|
|
172
256
|
const left = [];
|
|
173
257
|
let touched = hoistDefinitionStubs(sf);
|
|
258
|
+
if (unwrapNarrate(sf, left))
|
|
259
|
+
touched = true;
|
|
260
|
+
if (convertHighlights(sf, left))
|
|
261
|
+
touched = true;
|
|
174
262
|
if (convertSlotMaps(sf))
|
|
175
263
|
touched = true;
|
|
176
264
|
for (const block of sf.getDescendantsOfKind(SyntaxKind.Block).reverse()) {
|
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/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
|
}
|
|
@@ -25,9 +25,6 @@ export function createStubCtx(bag, opts) {
|
|
|
25
25
|
mem: opts.mem,
|
|
26
26
|
out: opts.out,
|
|
27
27
|
error: opts.error,
|
|
28
|
-
highlights(slots) {
|
|
29
|
-
bag.highlights = { ...(slots || {}) };
|
|
30
|
-
},
|
|
31
28
|
ring(id, stub) {
|
|
32
29
|
bag.highlights = { ...bag.highlights, [id]: stub };
|
|
33
30
|
},
|
|
@@ -134,6 +131,9 @@ export function createStubCtx(bag, opts) {
|
|
|
134
131
|
caption(text) {
|
|
135
132
|
bag.caption = String(text ?? "");
|
|
136
133
|
},
|
|
134
|
+
slides(slides) {
|
|
135
|
+
bag.slides = slides;
|
|
136
|
+
},
|
|
137
137
|
hideCaption() {
|
|
138
138
|
bag.caption = "";
|
|
139
139
|
},
|
|
@@ -275,12 +275,6 @@ export function installCore({ envAutoplay, todoDockUi }) {
|
|
|
275
275
|
pulse.classList.add("wg-pulse");
|
|
276
276
|
};
|
|
277
277
|
window.__wgHideRing = (opts) => {
|
|
278
|
-
// While a narrate() call owns the ring (mid multi-step action),
|
|
279
|
-
// the auto-highlight click/fill patches' own end-of-step hide is
|
|
280
|
-
// a no-op - narrate() itself clears it once the WHOLE wrapped
|
|
281
|
-
// action finishes, not just its first sub-step.
|
|
282
|
-
if (window.__wgNarrateOwnsRing)
|
|
283
|
-
return;
|
|
284
278
|
if (window.__wgStopRingFollow)
|
|
285
279
|
window.__wgStopRingFollow();
|
|
286
280
|
const ring = __wgById("wg-ring");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// CSS for the runner-only chrome (step panel, chips) and for the host page (device stage), next to the shared bundle in ui/css.
|
|
2
2
|
import { SURFACE } from "./ui/tokens.js";
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
|
-
//
|
|
4
|
+
// The demo stepper (waygraph demo) - human-verification overlay: one Block at a time, a
|
|
5
5
|
// browser-injected panel showing/editing that Block's MemKeys, a "Run this
|
|
6
6
|
// step" gate, then a highlight ring over whatever its own verify Traits just
|
|
7
7
|
// confirmed, then a "Next" gate before moving on. Ring CSS borrowed from
|
|
@@ -114,60 +114,6 @@ export const RUNNER_EXTRA_CSS =
|
|
|
114
114
|
"#wg-zoom-badge .wg-zoom-ico svg{width:14px;height:14px;display:block;}" +
|
|
115
115
|
"#wg-zoom-badge .wg-zoom-val{color:#c9a6ff;font-variant-numeric:tabular-nums;font-weight:800;min-width:3.2em;}" +
|
|
116
116
|
"#wg-zoom-badge[data-zoomed=\"1\"]{border-color:#7C3AED;}" +
|
|
117
|
-
// Network-tab overlay (real page traffic via network-capture.js + defineApiBlock's own
|
|
118
|
-
// page.request calls) - bottom-left, the one corner free of the zoom HUD (bottom-right) and the
|
|
119
|
-
// typing chip (bottom-center). Persistent (does not auto-fade) once any entry exists - this is a
|
|
120
|
-
// real Postman/DevTools-style inspector meant to be reviewed, not a flash-and-gone toast.
|
|
121
|
-
"#wg-network-log{position:fixed;z-index:2147483646;bottom:14px;left:14px;min-width:260px;" +
|
|
122
|
-
"max-width:min(92vw,420px);max-height:min(60vh,420px);display:flex;flex-direction:column;" +
|
|
123
|
-
"border-radius:10px;background:" + SURFACE + ";color:#f0e8ff;border:1px solid rgba(124,58,237,.45);" +
|
|
124
|
-
"box-shadow:0 8px 22px rgba(0,0,0,.4);font:12px/1.4 ui-monospace,\"SF Mono\",Consolas,monospace;" +
|
|
125
|
-
"opacity:0;transform:translateY(8px);transition:opacity .25s ease,transform .25s ease;overflow:hidden;}" +
|
|
126
|
-
"#wg-network-log.wg-in{opacity:1;transform:translateY(0);}" +
|
|
127
|
-
"#wg-network-log.wg-net-collapsed .wg-net-list{display:none;}" +
|
|
128
|
-
"#wg-network-log .wg-net-header{display:flex;align-items:center;gap:6px;padding:8px 10px;" +
|
|
129
|
-
"border-bottom:1px solid rgba(124,58,237,.25);cursor:default;flex:0 0 auto;}" +
|
|
130
|
-
"#wg-network-log .wg-net-title{font:700 10px/1.2 system-ui,sans-serif;color:#c9a6ff;" +
|
|
131
|
-
"letter-spacing:.06em;text-transform:uppercase;flex:1 1 auto;}" +
|
|
132
|
-
"#wg-network-log .wg-net-count{font:700 10px/1.2 system-ui,sans-serif;color:#9CA3AF;" +
|
|
133
|
-
"background:rgba(124,58,237,.22);border-radius:999px;padding:1px 7px;}" +
|
|
134
|
-
"#wg-network-log .wg-net-collapse{flex:0 0 auto;width:18px;height:18px;border-radius:5px;border:none;" +
|
|
135
|
-
"background:rgba(124,58,237,.28);color:#e8dcff;font:700 13px/1 system-ui,sans-serif;cursor:pointer;" +
|
|
136
|
-
"pointer-events:auto;}" +
|
|
137
|
-
"#wg-network-log .wg-net-collapse:hover{background:rgba(124,58,237,.42);}" +
|
|
138
|
-
"#wg-network-log .wg-net-list{list-style:none;margin:0;padding:6px 8px;display:flex;" +
|
|
139
|
-
"flex-direction:column;gap:2px;overflow-y:auto;pointer-events:auto;}" +
|
|
140
|
-
"#wg-network-log .wg-net-row{display:flex;align-items:baseline;gap:7px;opacity:0;padding:3px 4px;" +
|
|
141
|
-
"border-radius:5px;cursor:pointer;transform:translateX(-4px);transition:opacity .2s ease,transform .2s ease;}" +
|
|
142
|
-
"#wg-network-log .wg-net-row:hover,#wg-network-log .wg-net-row.wg-net-open{background:rgba(124,58,237,.18);}" +
|
|
143
|
-
"#wg-network-log .wg-net-row.wg-in{opacity:1;transform:translateX(0);}" +
|
|
144
|
-
"#wg-network-log .wg-net-method{flex:0 0 auto;font-weight:800;font-size:10px;min-width:38px;}" +
|
|
145
|
-
"#wg-network-log .wg-net-method-get{color:#3B82F6;}" +
|
|
146
|
-
"#wg-network-log .wg-net-method-post{color:#22C55E;}" +
|
|
147
|
-
"#wg-network-log .wg-net-method-put{color:#F97316;}" +
|
|
148
|
-
"#wg-network-log .wg-net-method-delete{color:#EF4444;}" +
|
|
149
|
-
"#wg-network-log .wg-net-method-other{color:#9CA3AF;}" +
|
|
150
|
-
"#wg-network-log .wg-net-path{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;" +
|
|
151
|
-
"white-space:nowrap;color:#e0e0f0;}" +
|
|
152
|
-
"#wg-network-log .wg-net-status{flex:0 0 auto;font-weight:700;color:#22C55E;}" +
|
|
153
|
-
"#wg-network-log .wg-net-row[data-ok=\"0\"] .wg-net-status{color:#EF4444;}" +
|
|
154
|
-
"#wg-network-log .wg-net-ms{flex:0 0 auto;color:#9CA3AF;font-size:10px;}" +
|
|
155
|
-
"#wg-network-log .wg-net-detail{list-style:none;background:rgba(0,0,0,.28);border-radius:6px;" +
|
|
156
|
-
"padding:8px;margin:2px 0 6px;}" +
|
|
157
|
-
"#wg-network-log .wg-net-detail-section{margin-bottom:8px;}" +
|
|
158
|
-
"#wg-network-log .wg-net-detail-section:last-child{margin-bottom:0;}" +
|
|
159
|
-
"#wg-network-log .wg-net-detail-section h4{margin:0 0 4px;font:700 9px/1.2 system-ui,sans-serif;" +
|
|
160
|
-
"color:#9CA3AF;letter-spacing:.06em;text-transform:uppercase;}" +
|
|
161
|
-
"#wg-network-log .wg-net-headers{width:100%;border-collapse:collapse;font-size:11px;}" +
|
|
162
|
-
"#wg-network-log .wg-net-headers td{padding:1px 6px 1px 0;vertical-align:top;word-break:break-all;}" +
|
|
163
|
-
"#wg-network-log .wg-net-headers td:first-child{color:#c9a6ff;white-space:nowrap;}" +
|
|
164
|
-
"#wg-network-log .wg-net-body{margin:0;font-size:11px;white-space:pre-wrap;word-break:break-all;" +
|
|
165
|
-
"max-height:160px;overflow-y:auto;color:#e0e0f0;}" +
|
|
166
|
-
"#wg-network-log .wg-net-curl{color:#7dd3fc;}" +
|
|
167
|
-
"#wg-network-log .wg-net-empty{font-size:11px;color:#6b7280;font-style:italic;}" +
|
|
168
|
-
"#wg-network-log .wg-net-copy{margin-top:5px;padding:3px 9px;border-radius:5px;border:1px solid rgba(124,58,237,.5);" +
|
|
169
|
-
"background:rgba(124,58,237,.22);color:#e8dcff;font:700 10px system-ui,sans-serif;cursor:pointer;}" +
|
|
170
|
-
"#wg-network-log .wg-net-copy:hover{background:rgba(124,58,237,.38);}" +
|
|
171
117
|
// Typing chip (method fills: username / password / etc.)
|
|
172
118
|
"#wg-typing-badge{position:fixed;z-index:2147483646;bottom:88px;left:50%;transform:translateX(-50%);" +
|
|
173
119
|
"display:flex;align-items:center;gap:8px;padding:8px 14px;" +
|
|
@@ -25,6 +25,9 @@ import { ignorePageGone } from "../../engine/page-gone.js";
|
|
|
25
25
|
export async function installOverlay(page, title, ui, caption, networkTabVisible) {
|
|
26
26
|
// ring/cursor/banner/dock come from the ONE shared bundle every surface installs (ui/css/*.css).
|
|
27
27
|
await ensureShadowRoot(page);
|
|
28
|
+
// Runner-only chrome (step panel, zoom/typing/device chips) - separate key, no collision. First, so nothing below
|
|
29
|
+
// builds chrome ahead of its CSS (an element painted unstyled for a moment, then restyled).
|
|
30
|
+
await page.evaluate(([css, key]) => __wgCss(css, key), [RUNNER_EXTRA_CSS, "wg-runner-css"]).catch(ignorePageGone);
|
|
28
31
|
// Idempotent (guarded on page.__wgNetworkCaptureInstalled) - safe to call every step even though
|
|
29
32
|
// installOverlay itself runs every step; the page.on() listeners this installs are attached to
|
|
30
33
|
// the Page object, not any one document, so they already survive navigations on their own.
|
|
@@ -46,8 +49,6 @@ export async function installOverlay(page, title, ui, caption, networkTabVisible
|
|
|
46
49
|
if (networkTabVisible !== undefined) {
|
|
47
50
|
await page.evaluate(setNetworkTabVisible, networkTabVisible).catch(ignorePageGone);
|
|
48
51
|
}
|
|
49
|
-
// Runner-only chrome (step panel, zoom/typing/device chips) - separate key, no collision.
|
|
50
|
-
await page.evaluate(([css, key]) => __wgCss(css, key), [RUNNER_EXTRA_CSS, "wg-runner-css"]).catch(ignorePageGone);
|
|
51
52
|
await page.addStyleTag({ content: HOST_CSS }).catch(ignorePageGone);
|
|
52
53
|
// Default top-left; override with WAYGRAPH_TITLE_POS=left|center|right.
|
|
53
54
|
// Click cycles left -> center -> right (persisted in localStorage so a
|
|
@@ -51,11 +51,3 @@ export declare function captionForLocator(page: WgPage, locator: Locator, stubs:
|
|
|
51
51
|
}>;
|
|
52
52
|
export declare function dwellMatchedStub(page: WgPage, locator: Locator, stubs: readonly ResolvedHighlight[] | undefined, pacing: Pick<Pacing, "gatesFast" | "demoPace"> | undefined): Promise<void>;
|
|
53
53
|
export declare function hideRing(page: WgPage): Promise<void>;
|
|
54
|
-
/**
|
|
55
|
-
* True if the engine's own narrate() (waygraph's public export, for a Block
|
|
56
|
-
* author explicitly captioning one action) JUST positioned the ring for
|
|
57
|
-
* THIS action, moments ago - the automatic per-fill/per-click narration
|
|
58
|
-
* below should not immediately overwrite an author's own explicit caption
|
|
59
|
-
* with its generic guess.
|
|
60
|
-
*/
|
|
61
|
-
export declare function wasJustNarrated(page: WgPage): Promise<boolean>;
|
|
@@ -405,18 +405,3 @@ export async function hideRing(page) {
|
|
|
405
405
|
})
|
|
406
406
|
.catch(ignorePageGone);
|
|
407
407
|
}
|
|
408
|
-
/**
|
|
409
|
-
* True if the engine's own narrate() (waygraph's public export, for a Block
|
|
410
|
-
* author explicitly captioning one action) JUST positioned the ring for
|
|
411
|
-
* THIS action, moments ago - the automatic per-fill/per-click narration
|
|
412
|
-
* below should not immediately overwrite an author's own explicit caption
|
|
413
|
-
* with its generic guess.
|
|
414
|
-
*/
|
|
415
|
-
export async function wasJustNarrated(page) {
|
|
416
|
-
return page
|
|
417
|
-
.evaluate(() => {
|
|
418
|
-
const w = window;
|
|
419
|
-
return typeof w.__wgLastNarrate === "number" && Date.now() - w.__wgLastNarrate < 500;
|
|
420
|
-
})
|
|
421
|
-
.catch(() => false);
|
|
422
|
-
}
|
|
@@ -215,3 +215,43 @@
|
|
|
215
215
|
background: #fff; border-radius: var(--wg-radius-lg);
|
|
216
216
|
padding: 10px 18px; box-shadow: var(--wg-shadow-banner);
|
|
217
217
|
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
/* The network tab: real page traffic (network-capture.ts) and defineApiBlock's own calls, bottom-left (the one corner
|
|
221
|
+
* free of the zoom HUD and the typing chip). Hidden until ctx.showNetworkTab(); stays up once shown. In the shared
|
|
222
|
+
* bundle, not the runner CSS: a navigation rebuilds the panel the moment the shadow root is back, before any later
|
|
223
|
+
* stylesheet, and without its CSS it painted unstyled and visible, then faded out. */
|
|
224
|
+
#wg-network-log{position:fixed;z-index:2147483646;bottom:14px;left:14px;min-width:260px;max-width:min(92vw,420px);max-height:min(60vh,420px);display:flex;flex-direction:column;border-radius:10px;background:var(--wg-surface);color:#f0e8ff;border:1px solid rgba(124,58,237,.45);box-shadow:0 8px 22px rgba(0,0,0,.4);font:12px/1.4 ui-monospace,"SF Mono",Consolas,monospace;opacity:0;transform:translateY(8px);transition:opacity .25s ease,transform .25s ease;overflow:hidden;}
|
|
225
|
+
#wg-network-log.wg-in{opacity:1;transform:translateY(0);}
|
|
226
|
+
#wg-network-log.wg-net-collapsed .wg-net-list{display:none;}
|
|
227
|
+
#wg-network-log .wg-net-header{display:flex;align-items:center;gap:6px;padding:8px 10px;border-bottom:1px solid rgba(124,58,237,.25);cursor:default;flex:0 0 auto;}
|
|
228
|
+
#wg-network-log .wg-net-title{font:700 10px/1.2 system-ui,sans-serif;color:#c9a6ff;letter-spacing:.06em;text-transform:uppercase;flex:1 1 auto;}
|
|
229
|
+
#wg-network-log .wg-net-count{font:700 10px/1.2 system-ui,sans-serif;color:#9CA3AF;background:rgba(124,58,237,.22);border-radius:999px;padding:1px 7px;}
|
|
230
|
+
#wg-network-log .wg-net-collapse{flex:0 0 auto;width:18px;height:18px;border-radius:5px;border:none;background:rgba(124,58,237,.28);color:#e8dcff;font:700 13px/1 system-ui,sans-serif;cursor:pointer;pointer-events:auto;}
|
|
231
|
+
#wg-network-log .wg-net-collapse:hover{background:rgba(124,58,237,.42);}
|
|
232
|
+
#wg-network-log .wg-net-list{list-style:none;margin:0;padding:6px 8px;display:flex;flex-direction:column;gap:2px;overflow-y:auto;pointer-events:auto;}
|
|
233
|
+
#wg-network-log .wg-net-row{display:flex;align-items:baseline;gap:7px;opacity:0;padding:3px 4px;border-radius:5px;cursor:pointer;transform:translateX(-4px);transition:opacity .2s ease,transform .2s ease;}
|
|
234
|
+
#wg-network-log .wg-net-row:hover,#wg-network-log .wg-net-row.wg-net-open{background:rgba(124,58,237,.18);}
|
|
235
|
+
#wg-network-log .wg-net-row.wg-in{opacity:1;transform:translateX(0);}
|
|
236
|
+
#wg-network-log .wg-net-method{flex:0 0 auto;font-weight:800;font-size:10px;min-width:38px;}
|
|
237
|
+
#wg-network-log .wg-net-method-get{color:#3B82F6;}
|
|
238
|
+
#wg-network-log .wg-net-method-post{color:#22C55E;}
|
|
239
|
+
#wg-network-log .wg-net-method-put{color:#F97316;}
|
|
240
|
+
#wg-network-log .wg-net-method-delete{color:#EF4444;}
|
|
241
|
+
#wg-network-log .wg-net-method-other{color:#9CA3AF;}
|
|
242
|
+
#wg-network-log .wg-net-path{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#e0e0f0;}
|
|
243
|
+
#wg-network-log .wg-net-status{flex:0 0 auto;font-weight:700;color:#22C55E;}
|
|
244
|
+
#wg-network-log .wg-net-row[data-ok="0"] .wg-net-status{color:#EF4444;}
|
|
245
|
+
#wg-network-log .wg-net-ms{flex:0 0 auto;color:#9CA3AF;font-size:10px;}
|
|
246
|
+
#wg-network-log .wg-net-detail{list-style:none;background:rgba(0,0,0,.28);border-radius:6px;padding:8px;margin:2px 0 6px;}
|
|
247
|
+
#wg-network-log .wg-net-detail-section{margin-bottom:8px;}
|
|
248
|
+
#wg-network-log .wg-net-detail-section:last-child{margin-bottom:0;}
|
|
249
|
+
#wg-network-log .wg-net-detail-section h4{margin:0 0 4px;font:700 9px/1.2 system-ui,sans-serif;color:#9CA3AF;letter-spacing:.06em;text-transform:uppercase;}
|
|
250
|
+
#wg-network-log .wg-net-headers{width:100%;border-collapse:collapse;font-size:11px;}
|
|
251
|
+
#wg-network-log .wg-net-headers td{padding:1px 6px 1px 0;vertical-align:top;word-break:break-all;}
|
|
252
|
+
#wg-network-log .wg-net-headers td:first-child{color:#c9a6ff;white-space:nowrap;}
|
|
253
|
+
#wg-network-log .wg-net-body{margin:0;font-size:11px;white-space:pre-wrap;word-break:break-all;max-height:160px;overflow-y:auto;color:#e0e0f0;}
|
|
254
|
+
#wg-network-log .wg-net-curl{color:#7dd3fc;}
|
|
255
|
+
#wg-network-log .wg-net-empty{font-size:11px;color:#6b7280;font-style:italic;}
|
|
256
|
+
#wg-network-log .wg-net-copy{margin-top:5px;padding:3px 9px;border-radius:5px;border:1px solid rgba(124,58,237,.5);background:rgba(124,58,237,.22);color:#e8dcff;font:700 10px system-ui,sans-serif;cursor:pointer;}
|
|
257
|
+
#wg-network-log .wg-net-copy:hover{background:rgba(124,58,237,.38);}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { envFlag } from "../../engine/env.js";
|
|
2
|
-
import { hasAuthoredStubAfter,
|
|
2
|
+
import { hasAuthoredStubAfter, runStubPhase } from "../narration/narration.js";
|
|
3
3
|
import { applyHighlightStyleDefaults, formatHighlightCaption } from "../narration/style.js";
|
|
4
4
|
import { logBannerDom, probeBanner } from "./banner-log.js";
|
|
5
5
|
import { logDocks } from "./before.js";
|
|
6
6
|
import { demoLog, logStubPhaseFixtures, logTodoDockFull, paceSpeakFields, summarizeDevice } from "./demo-log.js";
|
|
7
7
|
import { renderAfterStep } from "./panel-after.js";
|
|
8
|
-
import {
|
|
8
|
+
import { showSlides } from "./slides.js";
|
|
9
9
|
import { extractVerifyHighlights } from "./step-panels.js";
|
|
10
10
|
import { forceCollapsed } from "./view.js";
|
|
11
11
|
const quiet = () => envFlag("WAYGRAPH_JSON");
|
|
12
12
|
export async function showAfter(ctx, view, result) {
|
|
13
13
|
const { stage, refs } = ctx;
|
|
14
|
-
await showSlides(ctx, view, result);
|
|
15
14
|
const after = hasAuthoredStubAfter(view.step.block, result) ? await narrateAfter(ctx, view, result) : undefined;
|
|
15
|
+
if (after)
|
|
16
|
+
await showSlides(ctx, view, after.phase.slides);
|
|
16
17
|
const applied = after ? await stage.applyPhase(after.phase) : { appliedTodo: { sync: "keep" }, appliedDevice: { sync: "keep" } };
|
|
17
18
|
await renderAfterStep(ctx.page, {
|
|
18
19
|
index: view.index,
|
|
@@ -62,20 +63,6 @@ export async function showAfter(ctx, view, result) {
|
|
|
62
63
|
}
|
|
63
64
|
await ctx.gate.wait();
|
|
64
65
|
}
|
|
65
|
-
async function showSlides(ctx, view, result) {
|
|
66
|
-
const slides = resolveSlides(view.step.block, { out: result }).map((s) => applyHighlightStyleDefaults(s, view.step.highlightStyle));
|
|
67
|
-
if (slides.length === 0)
|
|
68
|
-
return;
|
|
69
|
-
await presentSlides(ctx.page, slides, {
|
|
70
|
-
title: ctx.title,
|
|
71
|
-
blockName: view.step.block.name,
|
|
72
|
-
fast: ctx.pacing.gatesFast,
|
|
73
|
-
pace: ctx.pacing.demoPace,
|
|
74
|
-
episodeNumber: view.step.episodeNumber,
|
|
75
|
-
episodeTitle: view.step.episodeTitle,
|
|
76
|
-
autoplayMs: ctx.gate.autoplayMs,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
66
|
/** Runs the Block's stubAfter; its rings carry the flow's highlight style and a caption. */
|
|
80
67
|
async function narrateAfter(ctx, view, result) {
|
|
81
68
|
const style = view.step.highlightStyle;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Before a demo step runs: play the Block's stubBefore narration (rings, todos, device, title, caption) and show the
|
|
1
|
+
// Before a demo step runs: play the Block's stubBefore narration (slides, rings, todos, device, title, caption) and show the
|
|
2
2
|
// step's panel, then log what was painted so an agent without eyes can check it.
|
|
3
3
|
import { envFlag } from "../../engine/env.js";
|
|
4
4
|
import { runStubPhase } from "../narration/narration.js";
|
|
@@ -6,6 +6,7 @@ import { applyHighlightStyleDefaults, formatHighlightCaption } from "../narratio
|
|
|
6
6
|
import { logBannerDom, probeBanner } from "./banner-log.js";
|
|
7
7
|
import { demoLog, logStubPhaseFixtures, logTodoDockFull, paceSpeakFields, summarizeDevice } from "./demo-log.js";
|
|
8
8
|
import { renderBeforeStep } from "./panel-before.js";
|
|
9
|
+
import { showSlides } from "./slides.js";
|
|
9
10
|
import { probeTodoDocksOnPage } from "./todo-advance.js";
|
|
10
11
|
import { forceCollapsed } from "./view.js";
|
|
11
12
|
const quiet = () => envFlag("WAYGRAPH_JSON");
|
|
@@ -13,6 +14,7 @@ export async function showBefore(ctx, view) {
|
|
|
13
14
|
const { stage } = ctx;
|
|
14
15
|
const phase = await narrateBefore(ctx, view);
|
|
15
16
|
const { appliedTodo } = await stage.applyPhase(phase, { reapplyDeviceOnKeep: true });
|
|
17
|
+
await showSlides(ctx, view, phase.slides);
|
|
16
18
|
if (phase.title && String(phase.title).trim())
|
|
17
19
|
ctx.stickyTitle = String(phase.title).trim();
|
|
18
20
|
view.overlayTitle = ctx.stickyTitle || (view.step.episodeTitle && String(view.step.episodeTitle).trim()) || ctx.title || "waygraph demo";
|
|
@@ -208,7 +208,7 @@ export function logStubPhaseFixtures(phaseName, phase) {
|
|
|
208
208
|
: phase.todoSync);
|
|
209
209
|
}
|
|
210
210
|
phase.highlights.forEach((h, i) => {
|
|
211
|
-
demoFixture(tag, "ctx.ring
|
|
211
|
+
demoFixture(tag, "ctx.ring[" + i + "]", {
|
|
212
212
|
selector: h.selector,
|
|
213
213
|
label: h.label,
|
|
214
214
|
detail: h.detail,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { installOverlay } from "../overlay/overlay.js";
|
|
2
|
-
import { applyHighlightZoom, captionForLocator, dwellMatchedStub, ensureLocatorInView, hideRing, matchStubIndexForLocator, showRing
|
|
2
|
+
import { applyHighlightZoom, captionForLocator, dwellMatchedStub, ensureLocatorInView, hideRing, matchStubIndexForLocator, showRing } from "../overlay/rings.js";
|
|
3
3
|
import { syncTodoDockAdvance } from "./todo-advance.js";
|
|
4
4
|
import { clickPulseAt, moveCursorTo } from "../overlay/cursor.js";
|
|
5
5
|
import { demoHighlight } from "./demo-log.js";
|
|
@@ -109,7 +109,7 @@ export function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stu
|
|
|
109
109
|
catch {
|
|
110
110
|
/* ignore */
|
|
111
111
|
}
|
|
112
|
-
if (box
|
|
112
|
+
if (box) {
|
|
113
113
|
// Only trust the "last mem.get()" as THIS fill's source if it
|
|
114
114
|
// happened recently - a stale read from several actions ago is
|
|
115
115
|
// more likely unrelated than actually describing this field.
|
|
@@ -148,11 +148,6 @@ export function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stu
|
|
|
148
148
|
if (cap && cap.label)
|
|
149
149
|
typeLabel = String(cap.label).slice(0, 40);
|
|
150
150
|
}
|
|
151
|
-
else if (box) {
|
|
152
|
-
// Ring/caption already handled by narrate() - still move the
|
|
153
|
-
// cursor there, just skip re-showing the ring with a generic label.
|
|
154
|
-
await moveCursorTo(page, box, cursorMs(200));
|
|
155
|
-
}
|
|
156
151
|
await page
|
|
157
152
|
.evaluate((label) => {
|
|
158
153
|
if (window.__wgSetTypingBadge)
|
|
@@ -191,8 +186,7 @@ export function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stu
|
|
|
191
186
|
// own click scroll is instant and reads as a teleport under --fast.
|
|
192
187
|
await ensureLocatorInView(locator, { instant: skipTheater() });
|
|
193
188
|
const box = await locator.boundingBox();
|
|
194
|
-
|
|
195
|
-
if (box && !narrated) {
|
|
189
|
+
if (box) {
|
|
196
190
|
let fallback = "click";
|
|
197
191
|
try {
|
|
198
192
|
const text = (await locator.textContent())?.trim();
|
|
@@ -241,12 +235,6 @@ export function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stu
|
|
|
241
235
|
// services/help-center-clip-engine's video-pipeline.
|
|
242
236
|
await new Promise((res) => setTimeout(res, clickPrePop()));
|
|
243
237
|
}
|
|
244
|
-
else if (box) {
|
|
245
|
-
// Ring/caption already handled by narrate() - still move the
|
|
246
|
-
// cursor + pulse the click point, just skip re-showing the ring.
|
|
247
|
-
clickPoint = await moveCursorTo(page, box, cursorMs(200));
|
|
248
|
-
clickTone = "planned";
|
|
249
|
-
}
|
|
250
238
|
if (clickPoint) {
|
|
251
239
|
await clickPulseAt(page, clickPoint.x, clickPoint.y, clickTone);
|
|
252
240
|
// clickPulseAt only triggers the CSS animation class - it doesn't
|
|
@@ -21,7 +21,7 @@ export async function renderAfterStep(page, info) {
|
|
|
21
21
|
// Cycle through EVERY declared/recovered highlight in order, each shown
|
|
22
22
|
// long enough to actually register - "it highlights something [...] then
|
|
23
23
|
// it highlights something [else] and next," not just the first one.
|
|
24
|
-
// duration / fastMode on stubAfter
|
|
24
|
+
// duration / fastMode on stubAfter rings override the default
|
|
25
25
|
// 900ms / 200ms holds when set.
|
|
26
26
|
await cycleHighlightRings(page, highlights, gatesFast, {
|
|
27
27
|
pace: info.pace,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { WgPage } from "../types.js";
|
|
2
2
|
import { type WaygraphSlide } from "../../engine/stub/ring.js";
|
|
3
3
|
import type { DemoPace } from "../../engine/stub/pace.js";
|
|
4
|
+
import type { StepperContext, StepView } from "./view.js";
|
|
4
5
|
/** Where the slides sit (title, Block, episode) and how fast they advance. */
|
|
5
6
|
export interface SlidesOptions {
|
|
6
7
|
title?: string | undefined;
|
|
@@ -21,3 +22,5 @@ export interface SlidesOptions {
|
|
|
21
22
|
* doing nothing. In-page data-wg-acked + autoplay poll is the source of truth.
|
|
22
23
|
*/
|
|
23
24
|
export declare function presentSlides(page: WgPage, slides: readonly WaygraphSlide[], opts?: SlidesOptions): Promise<void>;
|
|
25
|
+
/** Plays a stub phase's `ctx.slides(...)` for the step on screen, in the flow's highlight style. */
|
|
26
|
+
export declare function showSlides(ctx: StepperContext, view: StepView, slides: readonly WaygraphSlide[]): Promise<void>;
|