waygraph 0.12.48 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +175 -6
- package/dist/highlights.d.ts +84 -0
- package/dist/highlights.js +214 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +2 -0
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +4 -1
- package/package.json +1 -1
- package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +2 -0
- package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +4 -1
package/dist/cli.js
CHANGED
|
@@ -230,6 +230,8 @@ import {
|
|
|
230
230
|
applyTodoPhase,
|
|
231
231
|
advanceTodoDock,
|
|
232
232
|
completeSequentialTodoDock,
|
|
233
|
+
applyDevicePhase,
|
|
234
|
+
resolveDeviceState,
|
|
233
235
|
} from "waygraph";
|
|
234
236
|
|
|
235
237
|
function walkDir(dir, pattern) {
|
|
@@ -530,6 +532,18 @@ const RING_CSS =
|
|
|
530
532
|
"#wg-todo-dock[data-pos=right]{transform:translateX(calc(100vw - 100% - 28px));}" +
|
|
531
533
|
"#wg-todo-dock[data-pos=left]{transform:translateX(0);}" +
|
|
532
534
|
"#wg-todo-dock #wg-todos{list-style:none;margin:0;padding:0;background:transparent;border:none;}" +
|
|
535
|
+
// Device / touch badge (0.13+) - shows preset + remain|desktop reset.
|
|
536
|
+
"#wg-device-badge{position:fixed;z-index:2147483646;top:14px;right:14px;" +
|
|
537
|
+
"padding:6px 10px;border-radius:999px;background:rgba(20,10,40,.92);color:#f0e8ff;" +
|
|
538
|
+
"border:1px solid rgba(124,58,237,.5);font:700 11px/1.2 system-ui,sans-serif;" +
|
|
539
|
+
"letter-spacing:.04em;text-transform:uppercase;pointer-events:none;" +
|
|
540
|
+
"box-shadow:0 4px 14px rgba(0,0,0,.3);opacity:0;transition:opacity .25s ease;}" +
|
|
541
|
+
"#wg-device-badge.wg-in{opacity:1;}" +
|
|
542
|
+
"#wg-device-badge[data-preset=mobile]{border-color:#3B82F6;}" +
|
|
543
|
+
"#wg-device-badge[data-preset=tablet]{border-color:#22C55E;}" +
|
|
544
|
+
"#wg-device-badge[data-preset=desktop]{border-color:#9CA3AF;}" +
|
|
545
|
+
"#wg-device-badge[data-touch=1]::after{content:' · touch';opacity:.85;}" +
|
|
546
|
+
"#wg-cursor[data-touch=1]{width:28px;height:28px;}" +
|
|
533
547
|
"#wg-todos{list-style:none;margin:0 0 12px;padding:8px 10px;background:#0f0620;border-radius:8px;" +
|
|
534
548
|
"border:1px solid #3a2a60;}" +
|
|
535
549
|
"#wg-todos li{display:flex;gap:8px;align-items:flex-start;margin:0 0 6px;font:600 12.5px/1.35 system-ui,sans-serif;}" +
|
|
@@ -1252,6 +1266,64 @@ async function installOverlay(page, title) {
|
|
|
1252
1266
|
window.__wgStampModal(dock, "todos", { ready: true });
|
|
1253
1267
|
}
|
|
1254
1268
|
};
|
|
1269
|
+
// Device / touch badge (0.13+) - mirror todo sync keep/set/clear.
|
|
1270
|
+
// Payload: { sync, device?, remain? }
|
|
1271
|
+
window.__wgSyncDevice = (payload) => {
|
|
1272
|
+
const sync =
|
|
1273
|
+
payload && (payload.sync === "clear" || payload.sync === "keep" || payload.sync === "set")
|
|
1274
|
+
? payload.sync
|
|
1275
|
+
: "keep";
|
|
1276
|
+
const device = payload && payload.device ? payload.device : null;
|
|
1277
|
+
let badge = document.getElementById("wg-device-badge");
|
|
1278
|
+
const cursor = document.getElementById("wg-cursor");
|
|
1279
|
+
const MOUSE_SVG =
|
|
1280
|
+
"<svg viewBox='0 0 32 32' width='24' height='24'>" +
|
|
1281
|
+
"<path fill='#0C0C1A' stroke='#fff' stroke-width='1.4' stroke-linejoin='round' " +
|
|
1282
|
+
"d='M6 3.5l1.4 22.5 5.8-5.4 4.2 9.4 3.6-1.6-4.2-9.2H26z'/></svg>";
|
|
1283
|
+
const FINGER_SVG =
|
|
1284
|
+
"<svg viewBox='0 0 32 32' width='28' height='28'>" +
|
|
1285
|
+
"<ellipse cx='16' cy='22' rx='7' ry='8' fill='#0C0C1A' stroke='#fff' stroke-width='1.4'/>" +
|
|
1286
|
+
"<rect x='12' y='6' width='8' height='16' rx='4' fill='#0C0C1A' stroke='#fff' stroke-width='1.4'/>" +
|
|
1287
|
+
"</svg>";
|
|
1288
|
+
if (sync === "keep" && !device) return;
|
|
1289
|
+
if (sync === "clear" || (device && device.preset === "desktop" && !device.touchMode)) {
|
|
1290
|
+
if (badge) badge.remove();
|
|
1291
|
+
if (cursor) {
|
|
1292
|
+
cursor.removeAttribute("data-touch");
|
|
1293
|
+
cursor.innerHTML = MOUSE_SVG;
|
|
1294
|
+
}
|
|
1295
|
+
if (sync === "clear" && !device) return;
|
|
1296
|
+
// clear with desktop device: still hide badge (back to main)
|
|
1297
|
+
if (device && device.preset === "desktop" && !device.touchMode) return;
|
|
1298
|
+
}
|
|
1299
|
+
if (!device) return;
|
|
1300
|
+
const preset = device.preset || "desktop";
|
|
1301
|
+
const touch = !!device.touchMode;
|
|
1302
|
+
const remain = payload.remain !== false && sync !== "clear";
|
|
1303
|
+
if (!badge) {
|
|
1304
|
+
badge = document.createElement("div");
|
|
1305
|
+
badge.id = "wg-device-badge";
|
|
1306
|
+
badge.setAttribute("data-wg-ui", "1");
|
|
1307
|
+
document.documentElement.appendChild(badge);
|
|
1308
|
+
}
|
|
1309
|
+
badge.dataset.preset = preset;
|
|
1310
|
+
badge.dataset.touch = touch ? "1" : "0";
|
|
1311
|
+
const label =
|
|
1312
|
+
preset === "desktop" && !touch
|
|
1313
|
+
? "desktop"
|
|
1314
|
+
: preset + (remain ? " · remain" : " · set");
|
|
1315
|
+
badge.textContent = label;
|
|
1316
|
+
badge.classList.add("wg-in");
|
|
1317
|
+
if (cursor) {
|
|
1318
|
+
if (touch) {
|
|
1319
|
+
cursor.setAttribute("data-touch", "1");
|
|
1320
|
+
cursor.innerHTML = FINGER_SVG;
|
|
1321
|
+
} else {
|
|
1322
|
+
cursor.removeAttribute("data-touch");
|
|
1323
|
+
cursor.innerHTML = MOUSE_SVG;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1255
1327
|
},
|
|
1256
1328
|
{ title, favicon: WAYGRAPH_FAVICON, bannerPos, todoPos, envAutoplay },
|
|
1257
1329
|
)
|
|
@@ -2681,6 +2753,49 @@ async function clickPulseAt(page, x, y, tone) {
|
|
|
2681
2753
|
.catch(() => {});
|
|
2682
2754
|
}
|
|
2683
2755
|
|
|
2756
|
+
/**
|
|
2757
|
+
* Apply authored device fixture to the live page (0.13+).
|
|
2758
|
+
* Uses the real setViewportSize (step mode noops block-authored calls).
|
|
2759
|
+
* sync keep with no device = no-op; clear / desktop = restore main feel.
|
|
2760
|
+
*/
|
|
2761
|
+
async function applyDeviceToPage(page, device, sync) {
|
|
2762
|
+
const mode = sync || (device ? "set" : "keep");
|
|
2763
|
+
if (mode === "keep" && !device) return;
|
|
2764
|
+
const d =
|
|
2765
|
+
device ||
|
|
2766
|
+
resolveDeviceState("desktop", false) || {
|
|
2767
|
+
preset: "desktop",
|
|
2768
|
+
viewport: { width: 1280, height: 720, deviceScaleFactor: 1 },
|
|
2769
|
+
isMobile: false,
|
|
2770
|
+
hasTouch: false,
|
|
2771
|
+
touchMode: false,
|
|
2772
|
+
};
|
|
2773
|
+
const realSet =
|
|
2774
|
+
typeof page.__wgRealSetViewportSize === "function"
|
|
2775
|
+
? page.__wgRealSetViewportSize
|
|
2776
|
+
: page.setViewportSize.bind(page);
|
|
2777
|
+
try {
|
|
2778
|
+
await realSet({
|
|
2779
|
+
width: d.viewport.width,
|
|
2780
|
+
height: d.viewport.height,
|
|
2781
|
+
});
|
|
2782
|
+
} catch {
|
|
2783
|
+
/* maximized / null viewport hosts may reject - badge still updates */
|
|
2784
|
+
}
|
|
2785
|
+
await page
|
|
2786
|
+
.evaluate(
|
|
2787
|
+
(payload) => {
|
|
2788
|
+
if (window.__wgSyncDevice) window.__wgSyncDevice(payload);
|
|
2789
|
+
},
|
|
2790
|
+
{
|
|
2791
|
+
sync: mode,
|
|
2792
|
+
device: d,
|
|
2793
|
+
remain: mode === "keep" || mode === "set",
|
|
2794
|
+
},
|
|
2795
|
+
)
|
|
2796
|
+
.catch(() => {});
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2684
2799
|
/**
|
|
2685
2800
|
* True if the engine's own narrate() (waygraph's public export, for a Block
|
|
2686
2801
|
* author explicitly captioning one action) JUST positioned the ring for
|
|
@@ -2697,7 +2812,7 @@ async function wasJustNarrated(page) {
|
|
|
2697
2812
|
.catch(() => false);
|
|
2698
2813
|
}
|
|
2699
2814
|
|
|
2700
|
-
function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBeforeRef, todoDockRef) {
|
|
2815
|
+
function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBeforeRef, todoDockRef, deviceRef) {
|
|
2701
2816
|
// Playwright's own slowMo ALREADY pauses after every single low-level
|
|
2702
2817
|
// action it dispatches - and pressSequentially() fires one such action
|
|
2703
2818
|
// PER CHARACTER. Also giving pressSequentially its own fixed delay
|
|
@@ -2861,7 +2976,36 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2861
2976
|
// mock click visually completes before the real one fires.
|
|
2862
2977
|
await new Promise((res) => setTimeout(res, skipTheater() ? 0 : 500));
|
|
2863
2978
|
}
|
|
2864
|
-
|
|
2979
|
+
// Touch theater (0.13+): prefer Locator.tap / long-press when device
|
|
2980
|
+
// touchMode is on (context launched with hasTouch). gesture on stub
|
|
2981
|
+
// wins: tap | hold | click.
|
|
2982
|
+
let result;
|
|
2983
|
+
const stubsForGesture = (stubBeforeRef && stubBeforeRef.current) || [];
|
|
2984
|
+
let gesture = "click";
|
|
2985
|
+
try {
|
|
2986
|
+
const idx = await matchStubIndexForLocator(page, this, stubsForGesture);
|
|
2987
|
+
const stub = idx >= 0 ? stubsForGesture[idx] : null;
|
|
2988
|
+
if (stub && stub.gesture) gesture = stub.gesture;
|
|
2989
|
+
else if (deviceRef && deviceRef.current && deviceRef.current.touchMode) gesture = "tap";
|
|
2990
|
+
} catch {
|
|
2991
|
+
if (deviceRef && deviceRef.current && deviceRef.current.touchMode) gesture = "tap";
|
|
2992
|
+
}
|
|
2993
|
+
if (gesture === "tap" || gesture === "hold") {
|
|
2994
|
+
try {
|
|
2995
|
+
if (gesture === "hold") {
|
|
2996
|
+
result = await this.tap({
|
|
2997
|
+
...(options || {}),
|
|
2998
|
+
delay: skipTheater() ? 0 : 600,
|
|
2999
|
+
});
|
|
3000
|
+
} else {
|
|
3001
|
+
result = await this.tap(options);
|
|
3002
|
+
}
|
|
3003
|
+
} catch {
|
|
3004
|
+
result = await originalClick.call(this, options);
|
|
3005
|
+
}
|
|
3006
|
+
} else {
|
|
3007
|
+
result = await originalClick.call(this, options);
|
|
3008
|
+
}
|
|
2865
3009
|
await new Promise((res) => setTimeout(res, clickPostPop()));
|
|
2866
3010
|
await hideRing(page);
|
|
2867
3011
|
return result;
|
|
@@ -2971,7 +3115,9 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2971
3115
|
const stubBeforeRef = { current: [] };
|
|
2972
3116
|
/** Live todo dock for Method fill/click advance (same carry as lastTodoDock). */
|
|
2973
3117
|
const todoDockRef = { current: undefined };
|
|
2974
|
-
|
|
3118
|
+
/** Live device fixture for touch theater (same carry as lastDevice). */
|
|
3119
|
+
const deviceRef = { current: undefined };
|
|
3120
|
+
instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBeforeRef, todoDockRef, deviceRef);
|
|
2975
3121
|
// Force the panel checkbox from this process's flags/env at run start.
|
|
2976
3122
|
// installOverlay only seeds localStorage when the key is null (so mid-run
|
|
2977
3123
|
// checkbox clicks survive navigations). Without this, a prior --autoplay
|
|
@@ -3002,7 +3148,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3002
3148
|
// setViewportSize entirely for the duration of step mode. Blocks that
|
|
3003
3149
|
// call it don't need it to actually do anything here - their own
|
|
3004
3150
|
// selectors/layouts still work fine at whatever size the real window
|
|
3005
|
-
// already is.
|
|
3151
|
+
// already is. Authored ctx.device() still uses __wgRealSetViewportSize.
|
|
3152
|
+
page.__wgRealSetViewportSize = page.setViewportSize.bind(page);
|
|
3006
3153
|
page.setViewportSize = async () => {};
|
|
3007
3154
|
let resolveNext = null;
|
|
3008
3155
|
// Render-to-gate race: a slide/step panel's Next button is wired to
|
|
@@ -3100,6 +3247,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3100
3247
|
let result;
|
|
3101
3248
|
/** Carry floating todo dock across steps (empty stubBefore must not wipe). */
|
|
3102
3249
|
let lastTodoDock = undefined;
|
|
3250
|
+
/** Carry device / touch fixture across steps (omit = keep, like todos). */
|
|
3251
|
+
let lastDevice = undefined;
|
|
3103
3252
|
for (let i = 0; i < resolved.length; i++) {
|
|
3104
3253
|
const r = resolved[i];
|
|
3105
3254
|
// The block breadcrumb is scoped to THIS step's own episode, not the
|
|
@@ -3193,6 +3342,15 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3193
3342
|
});
|
|
3194
3343
|
lastTodoDock = appliedBefore.dock;
|
|
3195
3344
|
todoDockRef.current = lastTodoDock;
|
|
3345
|
+
const appliedDeviceBefore = applyDevicePhase(lastDevice, {
|
|
3346
|
+
deviceSync: stubBeforePhase.deviceSync,
|
|
3347
|
+
device: stubBeforePhase.device,
|
|
3348
|
+
});
|
|
3349
|
+
lastDevice = appliedDeviceBefore.device;
|
|
3350
|
+
deviceRef.current = lastDevice;
|
|
3351
|
+
if (appliedDeviceBefore.sync !== "keep" || lastDevice) {
|
|
3352
|
+
await applyDeviceToPage(page, lastDevice, appliedDeviceBefore.sync);
|
|
3353
|
+
}
|
|
3196
3354
|
const stubBeforeTodos =
|
|
3197
3355
|
(lastTodoDock && lastTodoDock.groups[0] && lastTodoDock.groups[0].items) ||
|
|
3198
3356
|
stubBeforePhase.todos ||
|
|
@@ -3418,6 +3576,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3418
3576
|
let afterOverlayTitle = overlayTitle;
|
|
3419
3577
|
let afterTodoPos = overlayTodoPos;
|
|
3420
3578
|
let appliedAfter = { dock: lastTodoDock, sync: "keep" };
|
|
3579
|
+
let appliedDeviceAfter = { device: lastDevice, sync: "keep" };
|
|
3421
3580
|
if (hasAuthoredStubAfter(r.block, result, fixturesAfter)) {
|
|
3422
3581
|
const afterPhase = await runStubPhase(r.block, "stubAfter", {
|
|
3423
3582
|
out: result,
|
|
@@ -3431,6 +3590,15 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3431
3590
|
});
|
|
3432
3591
|
lastTodoDock = appliedAfter.dock;
|
|
3433
3592
|
todoDockRef.current = lastTodoDock;
|
|
3593
|
+
appliedDeviceAfter = applyDevicePhase(lastDevice, {
|
|
3594
|
+
deviceSync: afterPhase.deviceSync,
|
|
3595
|
+
device: afterPhase.device,
|
|
3596
|
+
});
|
|
3597
|
+
lastDevice = appliedDeviceAfter.device;
|
|
3598
|
+
deviceRef.current = lastDevice;
|
|
3599
|
+
if (appliedDeviceAfter.sync !== "keep" || (appliedDeviceAfter.sync === "set" && lastDevice)) {
|
|
3600
|
+
await applyDeviceToPage(page, lastDevice, appliedDeviceAfter.sync);
|
|
3601
|
+
}
|
|
3434
3602
|
stubAfterTodos =
|
|
3435
3603
|
(lastTodoDock && lastTodoDock.groups[0] && lastTodoDock.groups[0].items) ||
|
|
3436
3604
|
afterPhase.todos ||
|
|
@@ -3880,10 +4048,11 @@ async function main() {
|
|
|
3880
4048
|
// Lock DPR so --video-viewport 800x450 stays 800x450 in the .webm
|
|
3881
4049
|
// (HiDPI hosts otherwise scale recordVideo to e.g. 1920x1080).
|
|
3882
4050
|
deviceScaleFactor: 1,
|
|
4051
|
+
hasTouch: true,
|
|
3883
4052
|
}
|
|
3884
4053
|
: step
|
|
3885
|
-
? { baseURL, viewport: null }
|
|
3886
|
-
: { baseURL, viewport: { width: 1280, height: 720 } };
|
|
4054
|
+
? { baseURL, viewport: null, hasTouch: true }
|
|
4055
|
+
: { baseURL, viewport: { width: 1280, height: 720 }, hasTouch: true };
|
|
3887
4056
|
if (videoDir) {
|
|
3888
4057
|
contextOpts.recordVideo = fixedVideoViewport
|
|
3889
4058
|
? { dir: videoDir, size: fixedVideoViewport }
|
package/dist/highlights.d.ts
CHANGED
|
@@ -117,6 +117,57 @@ export type StubPhaseFixtures = {
|
|
|
117
117
|
* Authored control (also click / WAYGRAPH_TODO_POS / --todo-left|right).
|
|
118
118
|
*/
|
|
119
119
|
todoPos?: "left" | "right";
|
|
120
|
+
/**
|
|
121
|
+
* Viewport preset: `mobile` | `tablet` | `desktop` (default / clear).
|
|
122
|
+
* Omit on later blocks = **keep** (same persist pattern as todos).
|
|
123
|
+
*/
|
|
124
|
+
device?: DevicePreset | DeviceState;
|
|
125
|
+
/** Explicit pixel viewport (wins over preset width/height when both set). */
|
|
126
|
+
viewport?: DeviceViewport;
|
|
127
|
+
/** Touch theater + Playwright touchscreen when true. */
|
|
128
|
+
touch?: boolean;
|
|
129
|
+
};
|
|
130
|
+
/** Named viewport presets (0.13+). */
|
|
131
|
+
export type DevicePreset = "mobile" | "tablet" | "desktop";
|
|
132
|
+
/** Explicit viewport box. */
|
|
133
|
+
export type DeviceViewport = {
|
|
134
|
+
width: number;
|
|
135
|
+
height: number;
|
|
136
|
+
deviceScaleFactor?: number;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Carried device fixture (todo-dock shaped persist).
|
|
140
|
+
* Omit on a later step = keep; hideDevice/clearDevice/desktop = clear to desktop.
|
|
141
|
+
*/
|
|
142
|
+
export type DeviceState = {
|
|
143
|
+
preset: DevicePreset;
|
|
144
|
+
viewport: DeviceViewport;
|
|
145
|
+
/** Playwright-ish: treat as mobile layout. */
|
|
146
|
+
isMobile: boolean;
|
|
147
|
+
/** Context needs hasTouch for touchscreen.tap (demo launches with hasTouch). */
|
|
148
|
+
hasTouch: boolean;
|
|
149
|
+
/** Demo gesture theater: finger cursor, tap/hold timing, prefer touchscreen. */
|
|
150
|
+
touchMode: boolean;
|
|
151
|
+
};
|
|
152
|
+
/** Built-in preset sizes (CSS pixels). */
|
|
153
|
+
export declare const DEVICE_PRESETS: Record<DevicePreset, Omit<DeviceState, "touchMode"> & {
|
|
154
|
+
touchMode?: boolean;
|
|
155
|
+
}>;
|
|
156
|
+
export declare function normalizeDevicePreset(raw: unknown): DevicePreset | undefined;
|
|
157
|
+
/** Resolve a preset name or partial state into a full DeviceState. */
|
|
158
|
+
export declare function resolveDeviceState(input: DevicePreset | DeviceState | DeviceViewport | undefined, touchOverride?: boolean): DeviceState | undefined;
|
|
159
|
+
/**
|
|
160
|
+
* Carry-forward for device fixtures (mirror {@link applyTodoPhase}).
|
|
161
|
+
* - clear: hideDevice / clearDevice / device("desktop") with clear intent
|
|
162
|
+
* - set: author set device/viewport/touch this phase
|
|
163
|
+
* - keep: omit - preserve previous (whole episode remain)
|
|
164
|
+
*/
|
|
165
|
+
export declare function applyDevicePhase(prev: DeviceState | undefined, phase: {
|
|
166
|
+
deviceSync?: "set" | "clear" | "keep";
|
|
167
|
+
device?: DeviceState;
|
|
168
|
+
}): {
|
|
169
|
+
device: DeviceState | undefined;
|
|
170
|
+
sync: "set" | "clear" | "keep";
|
|
120
171
|
};
|
|
121
172
|
/** One named highlight slot on a block (selector + caption). */
|
|
122
173
|
export interface WaygraphHighlightStub extends FixtureDurationFields {
|
|
@@ -176,6 +227,13 @@ export interface WaygraphHighlightStub extends FixtureDurationFields {
|
|
|
176
227
|
* for authored stub queues; set false to keep cursor hidden).
|
|
177
228
|
*/
|
|
178
229
|
cursor?: boolean;
|
|
230
|
+
/**
|
|
231
|
+
* Pointer feel when touchMode is on (0.13+):
|
|
232
|
+
* - `tap` - short press via touchscreen (default in touch mode)
|
|
233
|
+
* - `hold` - long-press (~600ms) then release
|
|
234
|
+
* - `click` - mouse click even in touch mode
|
|
235
|
+
*/
|
|
236
|
+
gesture?: "tap" | "hold" | "click";
|
|
179
237
|
}
|
|
180
238
|
/** Flow fixture patch: label required; selector optional (inherits from stub). */
|
|
181
239
|
export type WaygraphHighlightFixture = Partial<Pick<WaygraphHighlightStub, "selector">> & Required<Pick<WaygraphHighlightStub, "label">> & Pick<WaygraphHighlightStub, "detail" | "tag" | "tone" | "size" | "weight" | "zoom" | "zoomOut" | "focus" | "followMouse" | "follow" | "color" | "cursor"> & FixtureDurationFields;
|
|
@@ -238,6 +296,23 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
|
|
|
238
296
|
hideTodos(): void;
|
|
239
297
|
/** Alias for {@link StubCtx.hideTodos}. */
|
|
240
298
|
clearTodos(): void;
|
|
299
|
+
/**
|
|
300
|
+
* Viewport / device fixture (0.13+). `mobile` | `tablet` | `desktop`.
|
|
301
|
+
* Omit on later blocks = **keep** (same persist as todos). Use
|
|
302
|
+
* {@link StubCtx.hideDevice} / {@link StubCtx.clearDevice} to return to desktop.
|
|
303
|
+
*/
|
|
304
|
+
device(preset: DevicePreset | DeviceState): void;
|
|
305
|
+
/** Explicit CSS-pixel viewport (implies mobile-ish unless desktop-sized). */
|
|
306
|
+
viewport(box: DeviceViewport): void;
|
|
307
|
+
/** Enable / disable touch theater + touchscreen gestures. */
|
|
308
|
+
touch(on?: boolean): void;
|
|
309
|
+
/**
|
|
310
|
+
* Reset to desktop viewport and clear touch mode. Omit device on a later
|
|
311
|
+
* block = **keep**; only hideDevice / clearDevice / device("desktop") clears.
|
|
312
|
+
*/
|
|
313
|
+
hideDevice(): void;
|
|
314
|
+
/** Alias for {@link StubCtx.hideDevice}. */
|
|
315
|
+
clearDevice(): void;
|
|
241
316
|
/** Default camera zoom for rings without their own zoom. */
|
|
242
317
|
zoom(n: number): void;
|
|
243
318
|
/**
|
|
@@ -280,6 +355,15 @@ export type StubPhaseResult = {
|
|
|
280
355
|
* - `keep` - author omitted todos; demo must leave previous dock alone
|
|
281
356
|
*/
|
|
282
357
|
todoSync?: "set" | "clear" | "keep";
|
|
358
|
+
/** Resolved device after this phase (undefined when keep with no prior). */
|
|
359
|
+
device?: DeviceState;
|
|
360
|
+
/**
|
|
361
|
+
* Device intent (mirror todoSync):
|
|
362
|
+
* - `set` - apply {@link device}
|
|
363
|
+
* - `clear` - reset to desktop (hideDevice / clearDevice / device desktop)
|
|
364
|
+
* - `keep` - author omitted device; demo must leave previous alone
|
|
365
|
+
*/
|
|
366
|
+
deviceSync?: "set" | "clear" | "keep";
|
|
283
367
|
zoom?: number;
|
|
284
368
|
zoomOut?: boolean;
|
|
285
369
|
title?: string;
|
package/dist/highlights.js
CHANGED
|
@@ -12,6 +12,123 @@
|
|
|
12
12
|
* All fixture shapes share optional {@link FixtureDurationFields} so demo dwell
|
|
13
13
|
* before advancing is authored the same way on stubs, flow fixtures, and slides.
|
|
14
14
|
*/
|
|
15
|
+
/** Built-in preset sizes (CSS pixels). */
|
|
16
|
+
export const DEVICE_PRESETS = {
|
|
17
|
+
mobile: {
|
|
18
|
+
preset: "mobile",
|
|
19
|
+
viewport: { width: 390, height: 844, deviceScaleFactor: 2 },
|
|
20
|
+
isMobile: true,
|
|
21
|
+
hasTouch: true,
|
|
22
|
+
touchMode: true,
|
|
23
|
+
},
|
|
24
|
+
tablet: {
|
|
25
|
+
preset: "tablet",
|
|
26
|
+
viewport: { width: 768, height: 1024, deviceScaleFactor: 2 },
|
|
27
|
+
isMobile: true,
|
|
28
|
+
hasTouch: true,
|
|
29
|
+
touchMode: true,
|
|
30
|
+
},
|
|
31
|
+
desktop: {
|
|
32
|
+
preset: "desktop",
|
|
33
|
+
viewport: { width: 1280, height: 720, deviceScaleFactor: 1 },
|
|
34
|
+
isMobile: false,
|
|
35
|
+
hasTouch: false,
|
|
36
|
+
touchMode: false,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export function normalizeDevicePreset(raw) {
|
|
40
|
+
const s = String(raw ?? "")
|
|
41
|
+
.trim()
|
|
42
|
+
.toLowerCase();
|
|
43
|
+
if (s === "mobile" || s === "phone" || s === "m")
|
|
44
|
+
return "mobile";
|
|
45
|
+
if (s === "tablet" || s === "pad" || s === "t")
|
|
46
|
+
return "tablet";
|
|
47
|
+
if (s === "desktop" || s === "desk" || s === "d" || s === "main")
|
|
48
|
+
return "desktop";
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
/** Resolve a preset name or partial state into a full DeviceState. */
|
|
52
|
+
export function resolveDeviceState(input, touchOverride) {
|
|
53
|
+
if (input === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
if (typeof input === "string") {
|
|
56
|
+
const p = normalizeDevicePreset(input);
|
|
57
|
+
if (!p)
|
|
58
|
+
return undefined;
|
|
59
|
+
const base = DEVICE_PRESETS[p];
|
|
60
|
+
const touchMode = touchOverride !== undefined ? !!touchOverride : !!base.touchMode;
|
|
61
|
+
return {
|
|
62
|
+
preset: base.preset,
|
|
63
|
+
viewport: { ...base.viewport },
|
|
64
|
+
isMobile: base.isMobile,
|
|
65
|
+
hasTouch: base.hasTouch || touchMode,
|
|
66
|
+
touchMode,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if ("width" in input && "height" in input && !("preset" in input)) {
|
|
70
|
+
const vp = input;
|
|
71
|
+
const touchMode = touchOverride !== undefined ? !!touchOverride : true;
|
|
72
|
+
return {
|
|
73
|
+
preset: "mobile",
|
|
74
|
+
viewport: {
|
|
75
|
+
width: Math.max(200, Math.floor(vp.width)),
|
|
76
|
+
height: Math.max(200, Math.floor(vp.height)),
|
|
77
|
+
...(vp.deviceScaleFactor !== undefined
|
|
78
|
+
? { deviceScaleFactor: vp.deviceScaleFactor }
|
|
79
|
+
: { deviceScaleFactor: 2 }),
|
|
80
|
+
},
|
|
81
|
+
isMobile: true,
|
|
82
|
+
hasTouch: true,
|
|
83
|
+
touchMode,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const d = input;
|
|
87
|
+
const preset = normalizeDevicePreset(d.preset) || "desktop";
|
|
88
|
+
const base = DEVICE_PRESETS[preset];
|
|
89
|
+
const touchMode = touchOverride !== undefined
|
|
90
|
+
? !!touchOverride
|
|
91
|
+
: d.touchMode !== undefined
|
|
92
|
+
? !!d.touchMode
|
|
93
|
+
: !!base.touchMode;
|
|
94
|
+
const viewport = d.viewport
|
|
95
|
+
? {
|
|
96
|
+
width: Math.max(200, Math.floor(d.viewport.width)),
|
|
97
|
+
height: Math.max(200, Math.floor(d.viewport.height)),
|
|
98
|
+
...(d.viewport.deviceScaleFactor !== undefined
|
|
99
|
+
? { deviceScaleFactor: d.viewport.deviceScaleFactor }
|
|
100
|
+
: base.viewport.deviceScaleFactor !== undefined
|
|
101
|
+
? { deviceScaleFactor: base.viewport.deviceScaleFactor }
|
|
102
|
+
: {}),
|
|
103
|
+
}
|
|
104
|
+
: { ...base.viewport };
|
|
105
|
+
return {
|
|
106
|
+
preset,
|
|
107
|
+
viewport,
|
|
108
|
+
isMobile: d.isMobile !== undefined ? !!d.isMobile : base.isMobile,
|
|
109
|
+
hasTouch: d.hasTouch !== undefined ? !!d.hasTouch : base.hasTouch || touchMode,
|
|
110
|
+
touchMode,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Carry-forward for device fixtures (mirror {@link applyTodoPhase}).
|
|
115
|
+
* - clear: hideDevice / clearDevice / device("desktop") with clear intent
|
|
116
|
+
* - set: author set device/viewport/touch this phase
|
|
117
|
+
* - keep: omit - preserve previous (whole episode remain)
|
|
118
|
+
*/
|
|
119
|
+
export function applyDevicePhase(prev, phase) {
|
|
120
|
+
const sync = phase.deviceSync ||
|
|
121
|
+
(phase.device ? "set" : "keep");
|
|
122
|
+
if (sync === "clear") {
|
|
123
|
+
return { device: resolveDeviceState("desktop", false), sync: "clear" };
|
|
124
|
+
}
|
|
125
|
+
if (sync === "set") {
|
|
126
|
+
if (phase.device)
|
|
127
|
+
return { device: phase.device, sync: "set" };
|
|
128
|
+
return { device: resolveDeviceState("desktop", false), sync: "clear" };
|
|
129
|
+
}
|
|
130
|
+
return { device: prev, sync: "keep" };
|
|
131
|
+
}
|
|
15
132
|
/** Apply Flow defaults under per-slot authored values (slot wins when set). */
|
|
16
133
|
export function applyHighlightStyleDefaults(slot, defaults) {
|
|
17
134
|
const tone = slot.tone !== undefined && slot.tone !== ""
|
|
@@ -600,6 +717,57 @@ function createStubCtx(bag, opts) {
|
|
|
600
717
|
bag.todos = [];
|
|
601
718
|
delete bag.todoGroups;
|
|
602
719
|
},
|
|
720
|
+
device(preset) {
|
|
721
|
+
bag.deviceTouched = true;
|
|
722
|
+
bag.hideDevice = false;
|
|
723
|
+
const resolved = resolveDeviceState(preset, bag.touchOverride);
|
|
724
|
+
if (resolved) {
|
|
725
|
+
bag.device = resolved;
|
|
726
|
+
if (resolved.preset === "desktop" && !resolved.touchMode) {
|
|
727
|
+
bag.hideDevice = true;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
},
|
|
731
|
+
viewport(box) {
|
|
732
|
+
bag.deviceTouched = true;
|
|
733
|
+
bag.hideDevice = false;
|
|
734
|
+
const resolved = resolveDeviceState(box, bag.touchOverride);
|
|
735
|
+
if (resolved)
|
|
736
|
+
bag.device = resolved;
|
|
737
|
+
},
|
|
738
|
+
touch(on) {
|
|
739
|
+
bag.deviceTouched = true;
|
|
740
|
+
const enabled = on === undefined ? true : !!on;
|
|
741
|
+
bag.touchOverride = enabled;
|
|
742
|
+
if (enabled)
|
|
743
|
+
bag.hideDevice = false;
|
|
744
|
+
if (bag.device) {
|
|
745
|
+
bag.device = {
|
|
746
|
+
...bag.device,
|
|
747
|
+
touchMode: enabled,
|
|
748
|
+
hasTouch: bag.device.hasTouch || enabled,
|
|
749
|
+
};
|
|
750
|
+
if (enabled)
|
|
751
|
+
bag.hideDevice = false;
|
|
752
|
+
}
|
|
753
|
+
else if (enabled) {
|
|
754
|
+
const base = resolveDeviceState("desktop", true);
|
|
755
|
+
if (base)
|
|
756
|
+
bag.device = { ...base, touchMode: true, hasTouch: true };
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
hideDevice() {
|
|
760
|
+
bag.deviceTouched = true;
|
|
761
|
+
bag.hideDevice = true;
|
|
762
|
+
bag.device = resolveDeviceState("desktop", false);
|
|
763
|
+
bag.touchOverride = false;
|
|
764
|
+
},
|
|
765
|
+
clearDevice() {
|
|
766
|
+
bag.deviceTouched = true;
|
|
767
|
+
bag.hideDevice = true;
|
|
768
|
+
bag.device = resolveDeviceState("desktop", false);
|
|
769
|
+
bag.touchOverride = false;
|
|
770
|
+
},
|
|
603
771
|
zoom(n) {
|
|
604
772
|
if (Number.isFinite(n) && n > 0)
|
|
605
773
|
bag.zoom = n;
|
|
@@ -648,6 +816,27 @@ function createStubCtx(bag, opts) {
|
|
|
648
816
|
bag.hideTodos = !partial.todoGroups || !partial.todoGroups.length;
|
|
649
817
|
bag.todoGroups = partial.todoGroups;
|
|
650
818
|
}
|
|
819
|
+
if (partial.touch !== undefined) {
|
|
820
|
+
bag.deviceTouched = true;
|
|
821
|
+
bag.touchOverride = !!partial.touch;
|
|
822
|
+
}
|
|
823
|
+
if (partial.device !== undefined) {
|
|
824
|
+
bag.deviceTouched = true;
|
|
825
|
+
bag.hideDevice = false;
|
|
826
|
+
const resolved = resolveDeviceState(partial.device, bag.touchOverride);
|
|
827
|
+
if (resolved) {
|
|
828
|
+
bag.device = resolved;
|
|
829
|
+
if (resolved.preset === "desktop" && !resolved.touchMode)
|
|
830
|
+
bag.hideDevice = true;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (partial.viewport !== undefined) {
|
|
834
|
+
bag.deviceTouched = true;
|
|
835
|
+
bag.hideDevice = false;
|
|
836
|
+
const resolved = resolveDeviceState(partial.viewport, bag.touchOverride);
|
|
837
|
+
if (resolved)
|
|
838
|
+
bag.device = resolved;
|
|
839
|
+
}
|
|
651
840
|
if (partial.zoom !== undefined && Number.isFinite(partial.zoom) && partial.zoom > 0) {
|
|
652
841
|
bag.zoom = partial.zoom;
|
|
653
842
|
}
|
|
@@ -812,12 +1001,37 @@ export async function runStubPhase(block, phase, opts) {
|
|
|
812
1001
|
: bag.todoTouched && !todoDock
|
|
813
1002
|
? "clear"
|
|
814
1003
|
: "keep";
|
|
1004
|
+
let deviceState;
|
|
1005
|
+
if (bag.hideDevice) {
|
|
1006
|
+
deviceState = resolveDeviceState("desktop", false);
|
|
1007
|
+
}
|
|
1008
|
+
else if (bag.device) {
|
|
1009
|
+
deviceState = bag.touchOverride !== undefined
|
|
1010
|
+
? {
|
|
1011
|
+
...bag.device,
|
|
1012
|
+
touchMode: bag.touchOverride,
|
|
1013
|
+
hasTouch: bag.device.hasTouch || bag.touchOverride,
|
|
1014
|
+
}
|
|
1015
|
+
: bag.device;
|
|
1016
|
+
}
|
|
1017
|
+
else if (bag.touchOverride !== undefined) {
|
|
1018
|
+
deviceState = resolveDeviceState("desktop", bag.touchOverride);
|
|
1019
|
+
}
|
|
1020
|
+
const deviceSync = bag.hideDevice
|
|
1021
|
+
? "clear"
|
|
1022
|
+
: bag.deviceTouched && deviceState
|
|
1023
|
+
? "set"
|
|
1024
|
+
: bag.deviceTouched && !deviceState
|
|
1025
|
+
? "clear"
|
|
1026
|
+
: "keep";
|
|
815
1027
|
return {
|
|
816
1028
|
highlights,
|
|
817
1029
|
todos,
|
|
818
1030
|
todoSync,
|
|
1031
|
+
deviceSync,
|
|
819
1032
|
...(bag.todoIndex !== undefined ? { todoIndex: bag.todoIndex } : {}),
|
|
820
1033
|
...(todoDock ? { todoDock } : {}),
|
|
1034
|
+
...(deviceState ? { device: deviceState } : {}),
|
|
821
1035
|
...(bag.zoom !== undefined ? { zoom: bag.zoom } : {}),
|
|
822
1036
|
...(bag.zoomOut !== undefined ? { zoomOut: bag.zoomOut } : {}),
|
|
823
1037
|
...(bag.title !== undefined && bag.title !== "" ? { title: bag.title } : {}),
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ export { connect, checkpoint } from "./types.js";
|
|
|
3
3
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
4
4
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
5
5
|
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
6
|
-
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
7
|
-
export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, TodoDockState, TodoListStyle, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, } from "./highlights.js";
|
|
6
|
+
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, DEVICE_PRESETS, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
7
|
+
export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, TodoDockState, TodoListStyle, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, DevicePreset, DeviceViewport, DeviceState, } from "./highlights.js";
|
|
8
8
|
export type { Flow, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
|
|
9
9
|
export type { ActionPage } from "./types.js";
|
|
10
10
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { connect, checkpoint } from "./types.js";
|
|
|
2
2
|
export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
|
|
3
3
|
export { Trait, urlMatches, textEquals, visible } from "./trait.js";
|
|
4
4
|
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
|
|
5
|
-
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
5
|
+
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, DEVICE_PRESETS, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
|
|
6
6
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
|
|
7
7
|
export { isFileSelectToken, parseBlocksSelect, matchBlocksSelect, globToRegExp, } from "./blocks-select.js";
|
|
8
8
|
export { EdgeLeaseCoordinator } from "./traverse-lease.js";
|
package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts
CHANGED
|
@@ -37,6 +37,8 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
37
37
|
// Demo: zoom product -> add (stay zoomed), then badge after act.
|
|
38
38
|
stubBefore: (ctx: StubCtx) => {
|
|
39
39
|
ctx.title("Pick a product");
|
|
40
|
+
// Back to desktop after mobile login (explicit clear - not omit/remain).
|
|
41
|
+
ctx.clearDevice();
|
|
40
42
|
ctx.todoId("saucedemo-cart");
|
|
41
43
|
ctx.todos([
|
|
42
44
|
{ id: "cart-find", text: "Find the product" },
|
|
@@ -51,11 +51,13 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
51
51
|
// sequential highlight queue (appear / dwell / fade), focus + color.
|
|
52
52
|
stubBefore: (ctx: StubCtx) => {
|
|
53
53
|
ctx.title("Signing in");
|
|
54
|
+
// 0.13+: mobile viewport + touch theater; omit on later blocks = remain.
|
|
55
|
+
ctx.device("mobile");
|
|
54
56
|
ctx.todoId("saucedemo-login");
|
|
55
57
|
ctx.todos([
|
|
56
58
|
{ id: "login-user", text: "Enter username" },
|
|
57
59
|
{ id: "login-pass", text: "Enter password" },
|
|
58
|
-
{ id: "login-submit", text: "
|
|
60
|
+
{ id: "login-submit", text: "Tap Login" },
|
|
59
61
|
]);
|
|
60
62
|
ctx.todoIndex(0);
|
|
61
63
|
ctx.zoom(1.35);
|
|
@@ -81,6 +83,7 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
81
83
|
color: "#86efac",
|
|
82
84
|
zoom: 1.55,
|
|
83
85
|
weight: "bold",
|
|
86
|
+
gesture: "tap",
|
|
84
87
|
},
|
|
85
88
|
});
|
|
86
89
|
},
|
package/package.json
CHANGED
package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts
CHANGED
|
@@ -36,6 +36,8 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
36
36
|
// Demo: zoom product -> add (stay zoomed), then badge after act.
|
|
37
37
|
stubBefore: (ctx) => {
|
|
38
38
|
ctx.title("Pick a product");
|
|
39
|
+
// Back to desktop after mobile login (explicit clear - not omit/remain).
|
|
40
|
+
ctx.clearDevice();
|
|
39
41
|
ctx.todoId("saucedemo-cart");
|
|
40
42
|
ctx.todos([
|
|
41
43
|
{ id: "cart-find", text: "Find the product" },
|
|
@@ -51,11 +51,13 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
51
51
|
// sequential highlight queue (appear / dwell / fade), focus + color.
|
|
52
52
|
stubBefore: (ctx: StubCtx) => {
|
|
53
53
|
ctx.title("Signing in");
|
|
54
|
+
// 0.13+: mobile viewport + touch theater; omit on later blocks = remain.
|
|
55
|
+
ctx.device("mobile");
|
|
54
56
|
ctx.todoId("saucedemo-login");
|
|
55
57
|
ctx.todos([
|
|
56
58
|
{ id: "login-user", text: "Enter username" },
|
|
57
59
|
{ id: "login-pass", text: "Enter password" },
|
|
58
|
-
{ id: "login-submit", text: "
|
|
60
|
+
{ id: "login-submit", text: "Tap Login" },
|
|
59
61
|
]);
|
|
60
62
|
ctx.todoIndex(0);
|
|
61
63
|
ctx.zoom(1.35);
|
|
@@ -81,6 +83,7 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
81
83
|
color: "#86efac",
|
|
82
84
|
zoom: 1.55,
|
|
83
85
|
weight: "bold",
|
|
86
|
+
gesture: "tap",
|
|
84
87
|
},
|
|
85
88
|
});
|
|
86
89
|
},
|