waygraph 0.12.43 → 0.12.45
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 +1 -1
- package/dist/cli.js +326 -92
- package/dist/highlights.d.ts +145 -2
- package/dist/highlights.js +284 -27
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/step-overlay.js +5 -2
- package/examples/saucedemo/src/blocks/saucedemo-web/cart/nav-cart.block.ts +6 -1
- package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts +12 -2
- package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login.method.block.ts +18 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
A typed graph of reusable Blocks for driving a browser through E2E flows, in place of a
|
|
4
4
|
flat pile of ad hoc helper functions.
|
|
5
5
|
|
|
6
|
-
**Current:** `0.12.
|
|
6
|
+
**Current:** `0.12.44` — floating todo dock (visible under `--mini`, `ctx.todoPos` / click / `--todo-left|right`), live `ctx.title` banner updates, instant highlight tone snaps.
|
|
7
7
|
|
|
8
8
|
This repo is the `waygraph` npm package itself. The full live **Sauce Demo** example
|
|
9
9
|
(Page inventory hub, Effect Add/Remove, MemNav Open details, `waygraph auto`) ships
|
package/dist/cli.js
CHANGED
|
@@ -226,6 +226,10 @@ import {
|
|
|
226
226
|
formatDemoPaceBadge,
|
|
227
227
|
formatDemoPaceLabel,
|
|
228
228
|
normalizeTodos,
|
|
229
|
+
buildTodoDock,
|
|
230
|
+
applyTodoPhase,
|
|
231
|
+
advanceTodoDock,
|
|
232
|
+
completeSequentialTodoDock,
|
|
229
233
|
} from "waygraph";
|
|
230
234
|
|
|
231
235
|
function walkDir(dir, pattern) {
|
|
@@ -412,10 +416,14 @@ function seedMemForFlow(mem, flowBlocks, json, label) {
|
|
|
412
416
|
const RING_CSS =
|
|
413
417
|
"#wg-ring{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
|
|
414
418
|
"border:2.5px solid #7C3AED;border-radius:10px;" +
|
|
415
|
-
|
|
419
|
+
// Opacity fade only - NEVER transition border-color/box-shadow. Tone swaps
|
|
420
|
+
// (auto gray -> planned purple -> warning yellow) must snap instantly;
|
|
421
|
+
// color transitions read as a muddy gray/purple/yellow morph on ring 2+.
|
|
422
|
+
"box-shadow:0 0 0 4px rgba(124,58,237,.16);transition:opacity .3s ease;}" +
|
|
416
423
|
// Planned (stubs / fixtures / YAP / instruction.highlights) = purple.
|
|
417
424
|
// Automation (verify fallback, unmatched fill/click) = gray - operators
|
|
418
425
|
// can ignore engine checks and watch purple + semantic tones.
|
|
426
|
+
"#wg-ring[data-tone=planned]{border-color:#7C3AED;box-shadow:0 0 0 4px rgba(124,58,237,.16);}" +
|
|
419
427
|
"#wg-ring[data-tone=auto]{border-color:#9CA3AF;box-shadow:0 0 0 4px rgba(156,163,175,.28);}" +
|
|
420
428
|
"#wg-ring[data-tone=info]{border-color:#3B82F6;box-shadow:0 0 0 4px rgba(59,130,246,.22);}" +
|
|
421
429
|
"#wg-ring[data-tone=warning]{border-color:#EAB308;box-shadow:0 0 0 4px rgba(234,179,8,.22);}" +
|
|
@@ -431,7 +439,8 @@ const RING_CSS =
|
|
|
431
439
|
// window.__wgPositionRing in installOverlay.
|
|
432
440
|
"#wg-ring-label{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
|
|
433
441
|
"max-width:min(360px,70vw);white-space:normal;padding:6px 10px;border-radius:7px;background:#7C3AED;color:#fff;" +
|
|
434
|
-
"font:600 12px/1.35 system-ui,sans-serif;transition:opacity .3s ease
|
|
442
|
+
"font:600 12px/1.35 system-ui,sans-serif;transition:opacity .3s ease;}" +
|
|
443
|
+
"#wg-ring-label[data-tone=planned]{background:#7C3AED;color:#fff;}" +
|
|
435
444
|
"#wg-ring-label[data-tone=auto]{background:#6B7280;color:#fff;}" +
|
|
436
445
|
"#wg-ring-label[data-tone=info]{background:#2563EB;color:#fff;}" +
|
|
437
446
|
"#wg-ring-label[data-tone=warning]{background:#EAB308;color:#1c1917;}" +
|
|
@@ -509,6 +518,18 @@ const RING_CSS =
|
|
|
509
518
|
"#wg-panel .wg-pace[data-pace-kind=blitz] .wg-pace-badge{background:#22C55E;color:#052e16;}" +
|
|
510
519
|
"#wg-panel .wg-pace[data-pace-kind=ms] .wg-pace-badge{background:#3B82F6;color:#fff;}" +
|
|
511
520
|
"#wg-panel .wg-narration{margin:0 0 12px;font:italic 14px/1.4 system-ui,sans-serif;color:#f0e8ff;}" +
|
|
521
|
+
// Todo checklist lives in #wg-todo-dock (fixed, outside #wg-panel) so
|
|
522
|
+
// --mini / Hide collapse never hides it (.wg-body {display:none} would).
|
|
523
|
+
// Click dock to slide left <-> right (WAYGRAPH_TODO_POS / --todo-left|right).
|
|
524
|
+
"#wg-todo-dock{position:fixed;z-index:2147483646;top:72px;left:14px;" +
|
|
525
|
+
"width:min(280px,42vw);max-height:calc(100vh - 100px);overflow:auto;box-sizing:border-box;" +
|
|
526
|
+
"padding:10px 12px;background:rgba(20,10,40,.94);color:#fff;border-radius:12px;" +
|
|
527
|
+
"border:1px solid rgba(124,58,237,.45);box-shadow:0 8px 24px rgba(0,0,0,.35);" +
|
|
528
|
+
"pointer-events:auto;cursor:pointer;" +
|
|
529
|
+
"transition:transform .4s cubic-bezier(.22,1,.36,1);transform:translateX(0);}" +
|
|
530
|
+
"#wg-todo-dock[data-pos=right]{transform:translateX(calc(100vw - 100% - 28px));}" +
|
|
531
|
+
"#wg-todo-dock[data-pos=left]{transform:translateX(0);}" +
|
|
532
|
+
"#wg-todo-dock #wg-todos{list-style:none;margin:0;padding:0;background:transparent;border:none;}" +
|
|
512
533
|
"#wg-todos{list-style:none;margin:0 0 12px;padding:8px 10px;background:#0f0620;border-radius:8px;" +
|
|
513
534
|
"border:1px solid #3a2a60;}" +
|
|
514
535
|
"#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;}" +
|
|
@@ -647,10 +668,12 @@ async function installOverlay(page, title) {
|
|
|
647
668
|
// navigation that rebuilds the banner keeps the human's last pick).
|
|
648
669
|
const envPos = (process.env.WAYGRAPH_TITLE_POS || "left").toLowerCase();
|
|
649
670
|
const bannerPos = envPos === "center" || envPos === "right" ? envPos : "left";
|
|
671
|
+
const envTodoPos = (process.env.WAYGRAPH_TODO_POS || "left").toLowerCase();
|
|
672
|
+
const todoPos = envTodoPos === "right" ? "right" : "left";
|
|
650
673
|
const envAutoplay = process.env.WAYGRAPH_AUTOPLAY === "1";
|
|
651
674
|
await page
|
|
652
675
|
.evaluate(
|
|
653
|
-
({ title, favicon, bannerPos, envAutoplay }) => {
|
|
676
|
+
({ title, favicon, bannerPos, todoPos, envAutoplay }) => {
|
|
654
677
|
// Seed the live autoplay toggle from the env default on first ever
|
|
655
678
|
// load only - a real navigation re-runs this, and re-stamping here
|
|
656
679
|
// would silently undo a human's mid-run checkbox click.
|
|
@@ -818,32 +841,46 @@ async function installOverlay(page, title) {
|
|
|
818
841
|
/* private mode / blocked storage - position still applies this page */
|
|
819
842
|
}
|
|
820
843
|
};
|
|
821
|
-
if (title
|
|
822
|
-
|
|
823
|
-
banner
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
saved =
|
|
827
|
-
|
|
828
|
-
|
|
844
|
+
if (title) {
|
|
845
|
+
let banner = document.getElementById("wg-banner");
|
|
846
|
+
if (!banner) {
|
|
847
|
+
banner = document.createElement("div");
|
|
848
|
+
banner.id = "wg-banner";
|
|
849
|
+
let saved = null;
|
|
850
|
+
try {
|
|
851
|
+
saved = localStorage.getItem("wg-banner-pos");
|
|
852
|
+
} catch {
|
|
853
|
+
/* ignore */
|
|
854
|
+
}
|
|
855
|
+
const startPos =
|
|
856
|
+
saved && POSITIONS.includes(saved) ? saved : bannerPos;
|
|
857
|
+
applyPos(banner, startPos);
|
|
858
|
+
banner.title = "Click to move: top left / center / right";
|
|
859
|
+
banner.addEventListener("click", (e) => {
|
|
860
|
+
e.stopPropagation();
|
|
861
|
+
const i = POSITIONS.indexOf(banner.dataset.pos || "left");
|
|
862
|
+
applyPos(banner, POSITIONS[(i + 1) % POSITIONS.length]);
|
|
863
|
+
});
|
|
864
|
+
const tag = document.createElement("span");
|
|
865
|
+
tag.className = "wg-banner-tag";
|
|
866
|
+
tag.textContent = "waygraph demo";
|
|
867
|
+
const text = document.createElement("span");
|
|
868
|
+
text.className = "wg-banner-text";
|
|
869
|
+
text.textContent = title;
|
|
870
|
+
banner.appendChild(tag);
|
|
871
|
+
banner.appendChild(text);
|
|
872
|
+
document.documentElement.appendChild(banner);
|
|
873
|
+
} else {
|
|
874
|
+
// Fixture / episode title changes every step - update in place
|
|
875
|
+
// (banner is created once; do not leave the first step's text stuck).
|
|
876
|
+
let text = banner.querySelector(".wg-banner-text");
|
|
877
|
+
if (!text) {
|
|
878
|
+
text = document.createElement("span");
|
|
879
|
+
text.className = "wg-banner-text";
|
|
880
|
+
banner.appendChild(text);
|
|
881
|
+
}
|
|
882
|
+
text.textContent = title;
|
|
829
883
|
}
|
|
830
|
-
const startPos =
|
|
831
|
-
saved && POSITIONS.includes(saved) ? saved : bannerPos;
|
|
832
|
-
applyPos(banner, startPos);
|
|
833
|
-
banner.title = "Click to move: top left / center / right";
|
|
834
|
-
banner.addEventListener("click", (e) => {
|
|
835
|
-
e.stopPropagation();
|
|
836
|
-
const i = POSITIONS.indexOf(banner.dataset.pos || "left");
|
|
837
|
-
applyPos(banner, POSITIONS[(i + 1) % POSITIONS.length]);
|
|
838
|
-
});
|
|
839
|
-
const tag = document.createElement("span");
|
|
840
|
-
tag.className = "wg-banner-tag";
|
|
841
|
-
tag.textContent = "waygraph demo";
|
|
842
|
-
const text = document.createElement("span");
|
|
843
|
-
text.textContent = title;
|
|
844
|
-
banner.appendChild(tag);
|
|
845
|
-
banner.appendChild(text);
|
|
846
|
-
document.documentElement.appendChild(banner);
|
|
847
884
|
}
|
|
848
885
|
// Tab title/favicon: a real navigation resets document.title and any
|
|
849
886
|
// <link rel="icon"> the new document brings, so re-check (not
|
|
@@ -1046,8 +1083,177 @@ async function installOverlay(page, title) {
|
|
|
1046
1083
|
}
|
|
1047
1084
|
}
|
|
1048
1085
|
};
|
|
1086
|
+
// Floating todo dock - lives outside #wg-panel so --mini / Hide never
|
|
1087
|
+
// hide the checklist. Click cycles left <-> right with CSS transform.
|
|
1088
|
+
// Authors set side via ctx.todoPos("left"|"right"); env / --todo-* fallback.
|
|
1089
|
+
// Payload:
|
|
1090
|
+
// { sync: "clear" } -> remove dock
|
|
1091
|
+
// { sync: "keep" } -> leave dock alone (no wipe)
|
|
1092
|
+
// { sync: "set", dock } / { sync:"set", list } -> render / recreate
|
|
1093
|
+
// Compat: bare array = set list (empty array = clear - legacy only).
|
|
1094
|
+
window.__wgSyncTodos = (payload, pos) => {
|
|
1095
|
+
let sync = "set";
|
|
1096
|
+
let list = [];
|
|
1097
|
+
let dockState = null;
|
|
1098
|
+
let wantPos =
|
|
1099
|
+
pos === "left" || pos === "right" ? pos : null;
|
|
1100
|
+
if (Array.isArray(payload)) {
|
|
1101
|
+
list = payload;
|
|
1102
|
+
sync = list.length ? "set" : "clear";
|
|
1103
|
+
} else if (payload && typeof payload === "object") {
|
|
1104
|
+
sync = payload.sync === "clear" || payload.sync === "keep" ? payload.sync : "set";
|
|
1105
|
+
dockState = payload.dock || null;
|
|
1106
|
+
if (payload.pos === "left" || payload.pos === "right") wantPos = payload.pos;
|
|
1107
|
+
if (Array.isArray(payload.list)) list = payload.list;
|
|
1108
|
+
else if (dockState && Array.isArray(dockState.groups)) {
|
|
1109
|
+
// Flatten for legacy callers; full dock rendered below when present.
|
|
1110
|
+
list = [];
|
|
1111
|
+
for (const g of dockState.groups) {
|
|
1112
|
+
if (g && Array.isArray(g.items)) list = list.concat(g.items);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
} else {
|
|
1116
|
+
list = [];
|
|
1117
|
+
sync = "clear";
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
let dock = document.getElementById("wg-todo-dock");
|
|
1121
|
+
// Drop any in-panel leftover (old builds put #wg-todos in .wg-body).
|
|
1122
|
+
document.querySelectorAll("#wg-panel #wg-todos").forEach((el) => el.remove());
|
|
1123
|
+
|
|
1124
|
+
// keep + no new payload: do not remove an existing dock (PIA #10).
|
|
1125
|
+
if (sync === "keep") {
|
|
1126
|
+
if (!dockState && !list.length) return;
|
|
1127
|
+
// After navigation the DOM may be gone - recreate from carried dock.
|
|
1128
|
+
sync = "set";
|
|
1129
|
+
}
|
|
1130
|
+
if (sync === "clear" || (sync === "set" && !dockState && !list.length)) {
|
|
1131
|
+
if (dock) dock.remove();
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
const POS = ["left", "right"];
|
|
1136
|
+
if (!dock) {
|
|
1137
|
+
dock = document.createElement("div");
|
|
1138
|
+
dock.id = "wg-todo-dock";
|
|
1139
|
+
dock.setAttribute("data-wg-ui", "1");
|
|
1140
|
+
dock.setAttribute("data-wg-modal", "todos");
|
|
1141
|
+
dock.setAttribute("data-wg-ready", "1");
|
|
1142
|
+
let saved = null;
|
|
1143
|
+
try {
|
|
1144
|
+
saved = localStorage.getItem("wg-todo-pos");
|
|
1145
|
+
} catch {
|
|
1146
|
+
/* ignore */
|
|
1147
|
+
}
|
|
1148
|
+
const start =
|
|
1149
|
+
wantPos ||
|
|
1150
|
+
(saved && POS.includes(saved) ? saved : todoPos === "right" ? "right" : "left");
|
|
1151
|
+
dock.dataset.pos = start;
|
|
1152
|
+
dock.title = "Click to move checklist: left / right";
|
|
1153
|
+
dock.addEventListener("click", (e) => {
|
|
1154
|
+
e.stopPropagation();
|
|
1155
|
+
const i = POS.indexOf(dock.dataset.pos || "left");
|
|
1156
|
+
const next = POS[(i + 1) % POS.length];
|
|
1157
|
+
dock.dataset.pos = next;
|
|
1158
|
+
try {
|
|
1159
|
+
localStorage.setItem("wg-todo-pos", next);
|
|
1160
|
+
} catch {
|
|
1161
|
+
/* ignore */
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
document.documentElement.appendChild(dock);
|
|
1165
|
+
} else if (wantPos) {
|
|
1166
|
+
dock.dataset.pos = wantPos;
|
|
1167
|
+
try {
|
|
1168
|
+
localStorage.setItem("wg-todo-pos", wantPos);
|
|
1169
|
+
} catch {
|
|
1170
|
+
/* ignore */
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const esc = (s) =>
|
|
1175
|
+
String(s)
|
|
1176
|
+
.replace(/&/g, "&")
|
|
1177
|
+
.replace(/</g, "<")
|
|
1178
|
+
.replace(/>/g, ">")
|
|
1179
|
+
.replace(/"/g, """);
|
|
1180
|
+
const attrId = (raw) => {
|
|
1181
|
+
const id = raw != null && String(raw).trim() ? String(raw).trim() : "";
|
|
1182
|
+
return id ? ' data-wg-todo-item="' + esc(id) + '"' : "";
|
|
1183
|
+
};
|
|
1184
|
+
const renderItems = (items, style) => {
|
|
1185
|
+
const st = style === "checklist" ? "checklist" : "sequential";
|
|
1186
|
+
return (
|
|
1187
|
+
'<ul id="wg-todos" data-wg-todo-style="' +
|
|
1188
|
+
st +
|
|
1189
|
+
'">' +
|
|
1190
|
+
(items || [])
|
|
1191
|
+
.map((t) => {
|
|
1192
|
+
const cls = t.current
|
|
1193
|
+
? "wg-todo-current"
|
|
1194
|
+
: t.done
|
|
1195
|
+
? "wg-todo-done"
|
|
1196
|
+
: "wg-todo-pending";
|
|
1197
|
+
let mark;
|
|
1198
|
+
if (st === "checklist") {
|
|
1199
|
+
mark = t.done ? "\u2611" : "\u2610";
|
|
1200
|
+
} else {
|
|
1201
|
+
mark = t.done ? "\u2713" : t.current ? "\u2192" : "\u25CB";
|
|
1202
|
+
}
|
|
1203
|
+
const rowId = t.id || t.name || "";
|
|
1204
|
+
return (
|
|
1205
|
+
"<li class=\"" +
|
|
1206
|
+
cls +
|
|
1207
|
+
"\"" +
|
|
1208
|
+
attrId(rowId) +
|
|
1209
|
+
'><span class="wg-todo-mark">' +
|
|
1210
|
+
mark +
|
|
1211
|
+
"</span><span>" +
|
|
1212
|
+
esc(t.text || "") +
|
|
1213
|
+
"</span></li>"
|
|
1214
|
+
);
|
|
1215
|
+
})
|
|
1216
|
+
.join("") +
|
|
1217
|
+
"</ul>"
|
|
1218
|
+
);
|
|
1219
|
+
};
|
|
1220
|
+
|
|
1221
|
+
const dockId =
|
|
1222
|
+
(dockState && dockState.id) ||
|
|
1223
|
+
(payload && !Array.isArray(payload) && payload.todoId) ||
|
|
1224
|
+
"";
|
|
1225
|
+
if (dockId) dock.setAttribute("data-wg-todo-id", String(dockId));
|
|
1226
|
+
else dock.removeAttribute("data-wg-todo-id");
|
|
1227
|
+
|
|
1228
|
+
let html = "";
|
|
1229
|
+
if (dockState && Array.isArray(dockState.groups) && dockState.groups.length) {
|
|
1230
|
+
const dockTitle = dockState.title && String(dockState.title).trim();
|
|
1231
|
+
if (dockTitle) {
|
|
1232
|
+
html += '<div class="wg-todo-dock-title">' + esc(dockTitle) + "</div>";
|
|
1233
|
+
}
|
|
1234
|
+
for (const g of dockState.groups) {
|
|
1235
|
+
const gTitle = g.title && String(g.title).trim();
|
|
1236
|
+
const gid = g.id || g.name || "";
|
|
1237
|
+
html +=
|
|
1238
|
+
'<div class="wg-todo-group"' +
|
|
1239
|
+
(gid ? ' data-wg-todo-group="' + esc(gid) + '"' : "") +
|
|
1240
|
+
">";
|
|
1241
|
+
if (gTitle) {
|
|
1242
|
+
html += '<div class="wg-todo-group-title">' + esc(gTitle) + "</div>";
|
|
1243
|
+
}
|
|
1244
|
+
html += renderItems(g.items, g.style || dockState.style);
|
|
1245
|
+
html += "</div>";
|
|
1246
|
+
}
|
|
1247
|
+
} else {
|
|
1248
|
+
html = renderItems(list, "sequential");
|
|
1249
|
+
}
|
|
1250
|
+
dock.innerHTML = html;
|
|
1251
|
+
if (window.__wgStampModal) {
|
|
1252
|
+
window.__wgStampModal(dock, "todos", { ready: true });
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1049
1255
|
},
|
|
1050
|
-
{ title, favicon: WAYGRAPH_FAVICON, bannerPos, envAutoplay },
|
|
1256
|
+
{ title, favicon: WAYGRAPH_FAVICON, bannerPos, todoPos, envAutoplay },
|
|
1051
1257
|
)
|
|
1052
1258
|
.catch(() => {});
|
|
1053
1259
|
}
|
|
@@ -1137,41 +1343,15 @@ async function renderBeforeStep(page, info) {
|
|
|
1137
1343
|
esc(info.paceLabel || "") +
|
|
1138
1344
|
"</span></div>"
|
|
1139
1345
|
: "";
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
if (!list.length) return "";
|
|
1143
|
-
return (
|
|
1144
|
-
'<ul id=\\"wg-todos\\">' +
|
|
1145
|
-
list
|
|
1146
|
-
.map((t) => {
|
|
1147
|
-
const cls = t.current ? "wg-todo-current" : t.done ? "wg-todo-done" : "wg-todo-pending";
|
|
1148
|
-
const mark = t.done
|
|
1149
|
-
? String.fromCharCode(0x2713)
|
|
1150
|
-
: t.current
|
|
1151
|
-
? String.fromCharCode(0x2192)
|
|
1152
|
-
: String.fromCharCode(0x25cb);
|
|
1153
|
-
return (
|
|
1154
|
-
'<li class=\\"' +
|
|
1155
|
-
cls +
|
|
1156
|
-
'\\"><span class=\\"wg-todo-mark\\">' +
|
|
1157
|
-
mark +
|
|
1158
|
-
'</span><span>' +
|
|
1159
|
-
esc(t.text || "") +
|
|
1160
|
-
"</span></li>"
|
|
1161
|
-
);
|
|
1162
|
-
})
|
|
1163
|
-
.join("") +
|
|
1164
|
-
"</ul>"
|
|
1165
|
-
);
|
|
1166
|
-
})();
|
|
1346
|
+
// Todos render into floating #wg-todo-dock via __wgSyncTodos - never
|
|
1347
|
+
// inside .wg-body (would vanish under --mini / Hide collapse).
|
|
1167
1348
|
let html =
|
|
1168
1349
|
"<div id=\\"wg-progress\\"><div id=\\"wg-progress-bar\\" style=\\"width:" + pct + "%\\"></div></div>" +
|
|
1169
1350
|
episodesHtml +
|
|
1170
1351
|
paceHtml +
|
|
1171
1352
|
"<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
|
|
1172
1353
|
"<h3>Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + "</h3>" +
|
|
1173
|
-
narrationHtml
|
|
1174
|
-
todosHtml;
|
|
1354
|
+
narrationHtml;
|
|
1175
1355
|
if (info.keys.length === 0) {
|
|
1176
1356
|
html += "<div class=\\"wg-key\\">(no MemKeys required)</div>";
|
|
1177
1357
|
}
|
|
@@ -1210,6 +1390,16 @@ async function renderBeforeStep(page, info) {
|
|
|
1210
1390
|
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1211
1391
|
});
|
|
1212
1392
|
}
|
|
1393
|
+
if (window.__wgSyncTodos) {
|
|
1394
|
+
const sync = info.todoSync || (info.todos && info.todos.length ? "set" : "keep");
|
|
1395
|
+
window.__wgSyncTodos({
|
|
1396
|
+
sync: sync,
|
|
1397
|
+
dock: info.todoDock || null,
|
|
1398
|
+
list: info.todos || [],
|
|
1399
|
+
pos: info.todoPos || null,
|
|
1400
|
+
todoId: info.todoId || (info.todoDock && info.todoDock.id) || null,
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1213
1403
|
if (window.__wgStampModal) {
|
|
1214
1404
|
window.__wgStampModal(panel, "panel", {
|
|
1215
1405
|
phase: "before",
|
|
@@ -1552,6 +1742,11 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
|
1552
1742
|
: normalizeTodos(h.todos, h.todoIndex);
|
|
1553
1743
|
await page
|
|
1554
1744
|
.evaluate((todos) => {
|
|
1745
|
+
if (window.__wgSyncTodos) {
|
|
1746
|
+
window.__wgSyncTodos(todos);
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
// Fallback for pages that never got installOverlay sync helper.
|
|
1555
1750
|
const ul = document.getElementById("wg-todos");
|
|
1556
1751
|
if (!ul) return;
|
|
1557
1752
|
ul.innerHTML = todos
|
|
@@ -1720,33 +1915,7 @@ async function renderAfterStep(page, info) {
|
|
|
1720
1915
|
escA(info.paceLabel || "") +
|
|
1721
1916
|
"</span></div>"
|
|
1722
1917
|
: "";
|
|
1723
|
-
const todosHtml =
|
|
1724
|
-
const list = info.todos || [];
|
|
1725
|
-
if (!list.length) return "";
|
|
1726
|
-
return (
|
|
1727
|
-
'<ul id=\\"wg-todos\\">' +
|
|
1728
|
-
list
|
|
1729
|
-
.map((t) => {
|
|
1730
|
-
const cls = t.current ? "wg-todo-current" : t.done ? "wg-todo-done" : "wg-todo-pending";
|
|
1731
|
-
const mark = t.done
|
|
1732
|
-
? String.fromCharCode(0x2713)
|
|
1733
|
-
: t.current
|
|
1734
|
-
? String.fromCharCode(0x2192)
|
|
1735
|
-
: String.fromCharCode(0x25cb);
|
|
1736
|
-
return (
|
|
1737
|
-
'<li class=\\"' +
|
|
1738
|
-
cls +
|
|
1739
|
-
'\\"><span class=\\"wg-todo-mark\\">' +
|
|
1740
|
-
mark +
|
|
1741
|
-
'</span><span>' +
|
|
1742
|
-
escA(t.text || "") +
|
|
1743
|
-
"</span></li>"
|
|
1744
|
-
);
|
|
1745
|
-
})
|
|
1746
|
-
.join("") +
|
|
1747
|
-
"</ul>"
|
|
1748
|
-
);
|
|
1749
|
-
})();
|
|
1918
|
+
const todosHtml = ""; // todos float in #wg-todo-dock via __wgSyncTodos
|
|
1750
1919
|
panel.innerHTML =
|
|
1751
1920
|
"<div id=\\"wg-progress\\"><div id=\\"wg-progress-bar\\" style=\\"width:" + pct + "%\\"></div></div>" +
|
|
1752
1921
|
episodesHtml +
|
|
@@ -1768,6 +1937,16 @@ async function renderAfterStep(page, info) {
|
|
|
1768
1937
|
forceCollapsed: info.forceCollapsed === true ? true : undefined,
|
|
1769
1938
|
});
|
|
1770
1939
|
}
|
|
1940
|
+
if (window.__wgSyncTodos) {
|
|
1941
|
+
const sync = info.todoSync || (info.todos && info.todos.length ? "set" : "keep");
|
|
1942
|
+
window.__wgSyncTodos({
|
|
1943
|
+
sync: sync,
|
|
1944
|
+
dock: info.todoDock || null,
|
|
1945
|
+
list: info.todos || [],
|
|
1946
|
+
pos: info.todoPos || null,
|
|
1947
|
+
todoId: info.todoId || (info.todoDock && info.todoDock.id) || null,
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1771
1950
|
if (window.__wgStampModal) {
|
|
1772
1951
|
window.__wgStampModal(panel, "panel", {
|
|
1773
1952
|
phase: "after",
|
|
@@ -2390,7 +2569,7 @@ async function teardownOverlay(page) {
|
|
|
2390
2569
|
window.__wgRingTrack = null;
|
|
2391
2570
|
}
|
|
2392
2571
|
window.__wgNarrateOwnsRing = false;
|
|
2393
|
-
for (const id of ["wg-panel", "wg-ring", "wg-ring-label", "wg-cursor", "wg-click-pulse", "wg-banner"]) {
|
|
2572
|
+
for (const id of ["wg-panel", "wg-ring", "wg-ring-label", "wg-cursor", "wg-click-pulse", "wg-banner", "wg-todo-dock"]) {
|
|
2394
2573
|
const el = document.getElementById(id);
|
|
2395
2574
|
if (el) el.remove();
|
|
2396
2575
|
}
|
|
@@ -2835,6 +3014,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2835
3014
|
}
|
|
2836
3015
|
|
|
2837
3016
|
let result;
|
|
3017
|
+
/** Carry floating todo dock across steps (empty stubBefore must not wipe). */
|
|
3018
|
+
let lastTodoDock = undefined;
|
|
2838
3019
|
for (let i = 0; i < resolved.length; i++) {
|
|
2839
3020
|
const r = resolved[i];
|
|
2840
3021
|
// The block breadcrumb is scoped to THIS step's own episode, not the
|
|
@@ -2920,7 +3101,24 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2920
3101
|
stubBeforeRef.current = stubBeforePhase.highlights.map((h) =>
|
|
2921
3102
|
applyHighlightStyleDefaults(h, flowStyle),
|
|
2922
3103
|
);
|
|
2923
|
-
const
|
|
3104
|
+
const appliedBefore = applyTodoPhase(lastTodoDock, {
|
|
3105
|
+
todoSync: stubBeforePhase.todoSync,
|
|
3106
|
+
todoDock: stubBeforePhase.todoDock,
|
|
3107
|
+
todos: stubBeforePhase.todos,
|
|
3108
|
+
todoPos: stubBeforePhase.todoPos,
|
|
3109
|
+
});
|
|
3110
|
+
lastTodoDock = appliedBefore.dock;
|
|
3111
|
+
const stubBeforeTodos =
|
|
3112
|
+
(lastTodoDock && lastTodoDock.groups[0] && lastTodoDock.groups[0].items) ||
|
|
3113
|
+
stubBeforePhase.todos ||
|
|
3114
|
+
[];
|
|
3115
|
+
const overlayTitle =
|
|
3116
|
+
(stubBeforePhase.title && String(stubBeforePhase.title).trim()) ||
|
|
3117
|
+
(r.episodeTitle && String(r.episodeTitle).trim()) ||
|
|
3118
|
+
title ||
|
|
3119
|
+
"waygraph demo";
|
|
3120
|
+
const overlayTodoPos =
|
|
3121
|
+
(lastTodoDock && lastTodoDock.pos) || stubBeforePhase.todoPos || undefined;
|
|
2924
3122
|
const isNavBlock = r.block.__waygraphKind === "nav";
|
|
2925
3123
|
const autoNow = await currentAutoplay();
|
|
2926
3124
|
// Video / --mini: compact pill. Never pass false - Hide/localStorage wins.
|
|
@@ -2965,12 +3163,16 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
2965
3163
|
moduleIndex,
|
|
2966
3164
|
allEpisodes,
|
|
2967
3165
|
justEnteredEpisode,
|
|
2968
|
-
title,
|
|
3166
|
+
title: overlayTitle,
|
|
3167
|
+
todoPos: overlayTodoPos,
|
|
2969
3168
|
episodeNumber: r.episodeNumber,
|
|
2970
3169
|
episodeTitle: r.episodeTitle,
|
|
2971
3170
|
stepperMode,
|
|
2972
3171
|
forceCollapsed,
|
|
2973
3172
|
todos: stubBeforeTodos,
|
|
3173
|
+
todoDock: lastTodoDock,
|
|
3174
|
+
todoSync: appliedBefore.sync,
|
|
3175
|
+
todoId: lastTodoDock && lastTodoDock.id,
|
|
2974
3176
|
...paceSpeak,
|
|
2975
3177
|
});
|
|
2976
3178
|
const edits = await gate();
|
|
@@ -3124,12 +3326,30 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3124
3326
|
}
|
|
3125
3327
|
let highlights;
|
|
3126
3328
|
let stubAfterTodos = [];
|
|
3329
|
+
let afterOverlayTitle = overlayTitle;
|
|
3330
|
+
let afterTodoPos = overlayTodoPos;
|
|
3331
|
+
let appliedAfter = { dock: lastTodoDock, sync: "keep" };
|
|
3127
3332
|
if (hasAuthoredStubAfter(r.block, result, fixturesAfter)) {
|
|
3128
3333
|
const afterPhase = await runStubPhase(r.block, "stubAfter", {
|
|
3129
3334
|
out: result,
|
|
3130
3335
|
fixtures: fixturesAfter,
|
|
3131
3336
|
});
|
|
3132
|
-
|
|
3337
|
+
appliedAfter = applyTodoPhase(lastTodoDock, {
|
|
3338
|
+
todoSync: afterPhase.todoSync,
|
|
3339
|
+
todoDock: afterPhase.todoDock,
|
|
3340
|
+
todos: afterPhase.todos,
|
|
3341
|
+
todoPos: afterPhase.todoPos,
|
|
3342
|
+
});
|
|
3343
|
+
lastTodoDock = appliedAfter.dock;
|
|
3344
|
+
stubAfterTodos =
|
|
3345
|
+
(lastTodoDock && lastTodoDock.groups[0] && lastTodoDock.groups[0].items) ||
|
|
3346
|
+
afterPhase.todos ||
|
|
3347
|
+
[];
|
|
3348
|
+
if (afterPhase.title && String(afterPhase.title).trim()) {
|
|
3349
|
+
afterOverlayTitle = String(afterPhase.title).trim();
|
|
3350
|
+
}
|
|
3351
|
+
if (afterPhase.todoPos) afterTodoPos = afterPhase.todoPos;
|
|
3352
|
+
if (lastTodoDock && lastTodoDock.pos) afterTodoPos = lastTodoDock.pos;
|
|
3133
3353
|
highlights = afterPhase.highlights.map((h) => {
|
|
3134
3354
|
const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
|
|
3135
3355
|
return {
|
|
@@ -3163,11 +3383,15 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
|
|
|
3163
3383
|
allDescriptions: moduleDescriptions,
|
|
3164
3384
|
moduleIndex,
|
|
3165
3385
|
allEpisodes,
|
|
3166
|
-
title,
|
|
3386
|
+
title: afterOverlayTitle,
|
|
3387
|
+
todoPos: afterTodoPos,
|
|
3167
3388
|
episodeNumber: r.episodeNumber,
|
|
3168
3389
|
episodeTitle: r.episodeTitle,
|
|
3169
3390
|
stepperMode,
|
|
3170
3391
|
todos: stubAfterTodos,
|
|
3392
|
+
todoDock: lastTodoDock,
|
|
3393
|
+
todoSync: appliedAfter.sync,
|
|
3394
|
+
todoId: lastTodoDock && lastTodoDock.id,
|
|
3171
3395
|
forceCollapsed:
|
|
3172
3396
|
!!process.env.WAYGRAPH_VIDEO ||
|
|
3173
3397
|
process.env.WAYGRAPH_MINI === "1" ||
|
|
@@ -4191,6 +4415,12 @@ function parseRunFlags(argv) {
|
|
|
4191
4415
|
else if (a === "--mini" || a === "--stepper-mini") {
|
|
4192
4416
|
out.miniStepper = true;
|
|
4193
4417
|
}
|
|
4418
|
+
else if (a === "--todo-left") {
|
|
4419
|
+
out.todoPos = "left";
|
|
4420
|
+
}
|
|
4421
|
+
else if (a === "--todo-right") {
|
|
4422
|
+
out.todoPos = "right";
|
|
4423
|
+
}
|
|
4194
4424
|
else if (a === "--ff-expand") {
|
|
4195
4425
|
out.ffExpand = true;
|
|
4196
4426
|
}
|
|
@@ -4311,8 +4541,8 @@ function applyRunFlags(flags, opts) {
|
|
|
4311
4541
|
process.env.WAYGRAPH_HEADED = "0";
|
|
4312
4542
|
}
|
|
4313
4543
|
}
|
|
4314
|
-
if ((flags.fast || flags.fullStepper || flags.miniStepper) && !opts?.allowDemoUi) {
|
|
4315
|
-
console.error("waygraph: --fast / --full / --mini are demo-only flags");
|
|
4544
|
+
if ((flags.fast || flags.fullStepper || flags.miniStepper || flags.todoPos) && !opts?.allowDemoUi) {
|
|
4545
|
+
console.error("waygraph: --fast / --full / --mini / --todo-left|--todo-right are demo-only flags");
|
|
4316
4546
|
process.exit(1);
|
|
4317
4547
|
}
|
|
4318
4548
|
if (flags.step === true) {
|
|
@@ -4359,6 +4589,9 @@ function applyRunFlags(flags, opts) {
|
|
|
4359
4589
|
if (flags.miniStepper) {
|
|
4360
4590
|
process.env.WAYGRAPH_MINI = "1";
|
|
4361
4591
|
}
|
|
4592
|
+
if (flags.todoPos === "left" || flags.todoPos === "right") {
|
|
4593
|
+
process.env.WAYGRAPH_TODO_POS = flags.todoPos;
|
|
4594
|
+
}
|
|
4362
4595
|
if (flags.ffExpand) {
|
|
4363
4596
|
process.env.WAYGRAPH_FF_EXPAND = "1";
|
|
4364
4597
|
}
|
|
@@ -4524,6 +4757,7 @@ Primary (less is more):
|
|
|
4524
4757
|
--fast Shorter auto-next / Next gates (keeps smooth cursor)
|
|
4525
4758
|
--full Classic wrap-all block chips (default: carousel)
|
|
4526
4759
|
--mini Force compact mini panel (Hide pill + Next); alias --stepper-mini
|
|
4760
|
+
--todo-left|--todo-right Floating checklist dock side (also ctx.todoPos / WAYGRAPH_TODO_POS)
|
|
4527
4761
|
--ff-expand Expand fastForwardComposeBlock inners as separate steps
|
|
4528
4762
|
--ff-disabled Dispute: expand FF (alias --no-ff); blitz kept on those inners
|
|
4529
4763
|
--auto-play-video Unattended + recorded: --auto-next + --video (+ step); headless by default
|
package/dist/highlights.d.ts
CHANGED
|
@@ -34,17 +34,75 @@ export type FixtureDurationFields = {
|
|
|
34
34
|
*/
|
|
35
35
|
export type WaygraphTodoItem = {
|
|
36
36
|
text: string;
|
|
37
|
+
/**
|
|
38
|
+
* Stable id for external asserts / redirects (Mailhog, deep links).
|
|
39
|
+
* Stamped as `data-wg-todo-item` on the row.
|
|
40
|
+
*/
|
|
41
|
+
id?: string;
|
|
42
|
+
/** Alias for {@link WaygraphTodoItem.id}. */
|
|
43
|
+
name?: string;
|
|
37
44
|
/** Explicit done. If omitted, derived from todoIndex (index < todoIndex). */
|
|
38
45
|
done?: boolean;
|
|
39
46
|
/** Explicit current. If omitted, derived from todoIndex (index === todoIndex). */
|
|
40
47
|
current?: boolean;
|
|
41
48
|
};
|
|
42
49
|
export type WaygraphTodoInput = string | WaygraphTodoItem;
|
|
50
|
+
/**
|
|
51
|
+
* Dock list presentation:
|
|
52
|
+
* - `sequential` - arrow on current, strike done (demo walkthrough)
|
|
53
|
+
* - `checklist` - checkbox done/pending only (no current arrow; independent checks)
|
|
54
|
+
*/
|
|
55
|
+
export type TodoListStyle = "sequential" | "checklist";
|
|
56
|
+
/** One titled group in the floating todo dock (FR / Scenarios / ACs, etc.). */
|
|
57
|
+
export type WaygraphTodoGroupInput = {
|
|
58
|
+
/** Stable group id -> `data-wg-todo-group`. */
|
|
59
|
+
id?: string;
|
|
60
|
+
/** Alias for {@link WaygraphTodoGroupInput.id}. */
|
|
61
|
+
name?: string;
|
|
62
|
+
/** Group heading shown above the items. */
|
|
63
|
+
title?: string;
|
|
64
|
+
/** Per-group style; falls back to dock default ({@link StubCtx.todoStyle}). */
|
|
65
|
+
style?: TodoListStyle;
|
|
66
|
+
items: readonly WaygraphTodoInput[];
|
|
67
|
+
/** Sequential only: which item is current (0-based). */
|
|
68
|
+
todoIndex?: number;
|
|
69
|
+
};
|
|
70
|
+
export type WaygraphTodoGroup = {
|
|
71
|
+
id?: string;
|
|
72
|
+
title?: string;
|
|
73
|
+
style: TodoListStyle;
|
|
74
|
+
items: WaygraphTodoItem[];
|
|
75
|
+
};
|
|
76
|
+
/** Full floating dock payload passed to the browser. */
|
|
77
|
+
export type TodoDockState = {
|
|
78
|
+
/**
|
|
79
|
+
* Stable dock id for external targeting after redirects
|
|
80
|
+
* (`#wg-todo-dock[data-wg-todo-id="…"]`).
|
|
81
|
+
*/
|
|
82
|
+
id?: string;
|
|
83
|
+
/** Default style for groups that omit their own. */
|
|
84
|
+
style: TodoListStyle;
|
|
85
|
+
/** Single-list title when using {@link StubCtx.todos} + {@link StubCtx.todoTitle}. */
|
|
86
|
+
title?: string;
|
|
87
|
+
groups: WaygraphTodoGroup[];
|
|
88
|
+
pos?: "left" | "right";
|
|
89
|
+
};
|
|
43
90
|
/** Episode-level fixtures authored inside stubBefore/After/OnError(ctx). */
|
|
44
91
|
export type StubPhaseFixtures = {
|
|
45
92
|
todos?: readonly WaygraphTodoInput[];
|
|
46
93
|
/** Which todo is current (0-based). Rows before = done, after = pending. */
|
|
47
94
|
todoIndex?: number;
|
|
95
|
+
/** Heading above a single {@link StubCtx.todos} list. */
|
|
96
|
+
todoTitle?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Stable dock id (`data-wg-todo-id`) for external redirect asserts
|
|
99
|
+
* (e.g. Mailhog -> back to app and still find the checklist).
|
|
100
|
+
*/
|
|
101
|
+
todoId?: string;
|
|
102
|
+
/** `sequential` (default) or `checklist`. */
|
|
103
|
+
todoStyle?: TodoListStyle;
|
|
104
|
+
/** Multiple titled lists (FR / Scenarios / ACs). Wins over bare todos when set. */
|
|
105
|
+
todoGroups?: readonly WaygraphTodoGroupInput[];
|
|
48
106
|
/**
|
|
49
107
|
* Camera zoom (Screen Studio style) - scales the page toward the target /
|
|
50
108
|
* cursor. Not element CSS zoom. Typical `1.25` .. `2`.
|
|
@@ -54,6 +112,11 @@ export type StubPhaseFixtures = {
|
|
|
54
112
|
zoomOut?: boolean;
|
|
55
113
|
/** Top demo banner title (the purple "waygraph demo" card). */
|
|
56
114
|
title?: string;
|
|
115
|
+
/**
|
|
116
|
+
* Floating checklist dock side. `left` | `right`.
|
|
117
|
+
* Authored control (also click / WAYGRAPH_TODO_POS / --todo-left|right).
|
|
118
|
+
*/
|
|
119
|
+
todoPos?: "left" | "right";
|
|
57
120
|
};
|
|
58
121
|
/** One named highlight slot on a block (selector + caption). */
|
|
59
122
|
export interface WaygraphHighlightStub extends FixtureDurationFields {
|
|
@@ -147,10 +210,34 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
|
|
|
147
210
|
highlights(slots: HighlightStubPhase): void;
|
|
148
211
|
/** Set / overwrite one named ring. */
|
|
149
212
|
ring(id: string, stub: WaygraphHighlightStub): void;
|
|
150
|
-
/** Episode checklist (demo panel). */
|
|
213
|
+
/** Episode checklist (demo panel). Single list; use {@link StubCtx.todoGroups} for FR/AC packs. */
|
|
151
214
|
todos(items: readonly WaygraphTodoInput[]): void;
|
|
152
215
|
/** Current checklist index (bump for sequential plans). */
|
|
153
216
|
todoIndex(n: number): void;
|
|
217
|
+
/** Heading above the single {@link StubCtx.todos} list. */
|
|
218
|
+
todoTitle(text: string): void;
|
|
219
|
+
/**
|
|
220
|
+
* Stable dock id for external selectors after redirects
|
|
221
|
+
* (`#wg-todo-dock[data-wg-todo-id="ep10-coverage"]`).
|
|
222
|
+
*/
|
|
223
|
+
todoId(id: string): void;
|
|
224
|
+
/**
|
|
225
|
+
* Dock presentation: `sequential` (arrow walkthrough) or `checklist`
|
|
226
|
+
* (independent checkboxes, no current arrow).
|
|
227
|
+
*/
|
|
228
|
+
todoStyle(style: TodoListStyle): void;
|
|
229
|
+
/**
|
|
230
|
+
* Multiple titled lists (e.g. Functional Requirements / Scenarios / ACs).
|
|
231
|
+
* Replaces a bare {@link StubCtx.todos} list when set.
|
|
232
|
+
*/
|
|
233
|
+
todoGroups(groups: readonly WaygraphTodoGroupInput[]): void;
|
|
234
|
+
/**
|
|
235
|
+
* Hide the floating todo dock. Omit todos on a later block = **keep** the
|
|
236
|
+
* previous dock (carry forward). Only hideTodos / clearTodos / todos([]) clears it.
|
|
237
|
+
*/
|
|
238
|
+
hideTodos(): void;
|
|
239
|
+
/** Alias for {@link StubCtx.hideTodos}. */
|
|
240
|
+
clearTodos(): void;
|
|
154
241
|
/** Default camera zoom for rings without their own zoom. */
|
|
155
242
|
zoom(n: number): void;
|
|
156
243
|
/**
|
|
@@ -161,6 +248,11 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
|
|
|
161
248
|
title(text: string): void;
|
|
162
249
|
/** Alias for {@link StubCtx.title}. */
|
|
163
250
|
banner(text: string): void;
|
|
251
|
+
/**
|
|
252
|
+
* Floating checklist dock side (`left` | `right`). Authors control this in
|
|
253
|
+
* code; click / env / --todo-left|right are fallbacks.
|
|
254
|
+
*/
|
|
255
|
+
todoPos(side: "left" | "right"): void;
|
|
164
256
|
/** Batch-set rings + episode fixtures. */
|
|
165
257
|
set(partial: {
|
|
166
258
|
highlights?: HighlightStubPhase;
|
|
@@ -177,11 +269,21 @@ export type HighlightStubPhaseOrFn<Out extends Checkpoint<string>> = HighlightSt
|
|
|
177
269
|
/** Result of running a stub phase (rings + episode fixtures). */
|
|
178
270
|
export type StubPhaseResult = {
|
|
179
271
|
highlights: ResolvedHighlight[];
|
|
272
|
+
/** Flattened first-group items (compat). Prefer {@link todoDock}. */
|
|
180
273
|
todos: WaygraphTodoItem[];
|
|
181
274
|
todoIndex?: number;
|
|
275
|
+
todoDock?: TodoDockState;
|
|
276
|
+
/**
|
|
277
|
+
* Todo dock intent for this phase:
|
|
278
|
+
* - `set` - replace dock with {@link todoDock}
|
|
279
|
+
* - `clear` - hide dock (author called hideTodos / empty todos())
|
|
280
|
+
* - `keep` - author omitted todos; demo must leave previous dock alone
|
|
281
|
+
*/
|
|
282
|
+
todoSync?: "set" | "clear" | "keep";
|
|
182
283
|
zoom?: number;
|
|
183
284
|
zoomOut?: boolean;
|
|
184
285
|
title?: string;
|
|
286
|
+
todoPos?: "left" | "right";
|
|
185
287
|
};
|
|
186
288
|
/**
|
|
187
289
|
* One yap/slide in a multi-step demo caption sequence.
|
|
@@ -318,10 +420,51 @@ export interface WaygraphHighlight {
|
|
|
318
420
|
tone?: HighlightTone;
|
|
319
421
|
}
|
|
320
422
|
export type ResolvedHighlight = WaygraphHighlightStub;
|
|
423
|
+
/** Normalize checklist dock side. */
|
|
424
|
+
export declare function normalizeTodoPos(raw: unknown): "left" | "right" | undefined;
|
|
425
|
+
/** Normalize dock list style. */
|
|
426
|
+
export declare function normalizeTodoStyle(raw: unknown): TodoListStyle | undefined;
|
|
321
427
|
/**
|
|
322
428
|
* Normalize checklist rows; apply todoIndex for sequential done/current.
|
|
429
|
+
* In checklist mode, todoIndex is ignored unless items omit explicit done.
|
|
430
|
+
*/
|
|
431
|
+
export declare function normalizeTodos(todos: readonly WaygraphTodoInput[] | undefined, todoIndex?: number, style?: TodoListStyle): WaygraphTodoItem[];
|
|
432
|
+
/** Build the floating dock from bag / phase fields. */
|
|
433
|
+
export declare function buildTodoDock(opts: {
|
|
434
|
+
todos?: readonly WaygraphTodoInput[] | undefined;
|
|
435
|
+
todoIndex?: number | undefined;
|
|
436
|
+
todoTitle?: string | undefined;
|
|
437
|
+
todoId?: string | undefined;
|
|
438
|
+
todoStyle?: TodoListStyle | undefined;
|
|
439
|
+
todoGroups?: readonly WaygraphTodoGroupInput[] | undefined;
|
|
440
|
+
todoPos?: "left" | "right" | undefined;
|
|
441
|
+
}): TodoDockState | undefined;
|
|
442
|
+
/**
|
|
443
|
+
* Carry-forward rules for the floating todo dock across demo steps.
|
|
444
|
+
* - clear: author called hideTodos / empty todos()
|
|
445
|
+
* - set: author authored a new dock this phase
|
|
446
|
+
* - keep: author omitted todos — preserve previous dock (do not wipe)
|
|
447
|
+
*/
|
|
448
|
+
export declare function applyTodoPhase(prev: TodoDockState | undefined, phase: {
|
|
449
|
+
todoSync?: "set" | "clear" | "keep";
|
|
450
|
+
todoDock?: TodoDockState;
|
|
451
|
+
todos?: WaygraphTodoItem[];
|
|
452
|
+
todoPos?: "left" | "right";
|
|
453
|
+
todoId?: string;
|
|
454
|
+
}): {
|
|
455
|
+
dock: TodoDockState | undefined;
|
|
456
|
+
sync: "set" | "clear" | "keep";
|
|
457
|
+
};
|
|
458
|
+
/**
|
|
459
|
+
* Advance every sequential group to `index` (capped). Checklist groups unchanged.
|
|
460
|
+
* Used while cycling rings / fill/click stubs so the walkthrough moves.
|
|
461
|
+
*/
|
|
462
|
+
export declare function advanceTodoDock(dock: TodoDockState | undefined, index: number): TodoDockState | undefined;
|
|
463
|
+
/**
|
|
464
|
+
* Mark sequential groups complete (all done, none current) - e.g. end of step.
|
|
465
|
+
* Checklist groups keep explicit done flags.
|
|
323
466
|
*/
|
|
324
|
-
export declare function
|
|
467
|
+
export declare function completeSequentialTodoDock(dock: TodoDockState | undefined): TodoDockState | undefined;
|
|
325
468
|
/**
|
|
326
469
|
* Run an open stub lifecycle (preferred) or resolve a static slot map.
|
|
327
470
|
* Returns rings + episode fixtures (todos / todoIndex / zoom).
|
package/dist/highlights.js
CHANGED
|
@@ -293,31 +293,6 @@ function pickStyleFields(base, patch) {
|
|
|
293
293
|
out.weight = normalizeHighlightWeight(weight);
|
|
294
294
|
return out;
|
|
295
295
|
}
|
|
296
|
-
/**
|
|
297
|
-
* Normalize checklist rows; apply todoIndex for sequential done/current.
|
|
298
|
-
*/
|
|
299
|
-
export function normalizeTodos(todos, todoIndex) {
|
|
300
|
-
if (!todos || todos.length === 0)
|
|
301
|
-
return [];
|
|
302
|
-
const idx = todoIndex !== undefined && Number.isFinite(todoIndex) ? Math.floor(todoIndex) : undefined;
|
|
303
|
-
return todos.map((t, i) => {
|
|
304
|
-
const text = typeof t === "string" ? t.trim() : String(t?.text ?? "").trim();
|
|
305
|
-
const item = { text: text || `Item ${i + 1}` };
|
|
306
|
-
if (typeof t === "object" && t) {
|
|
307
|
-
if (t.done !== undefined)
|
|
308
|
-
item.done = !!t.done;
|
|
309
|
-
if (t.current !== undefined)
|
|
310
|
-
item.current = !!t.current;
|
|
311
|
-
}
|
|
312
|
-
if (idx !== undefined) {
|
|
313
|
-
if (item.done === undefined)
|
|
314
|
-
item.done = i < idx;
|
|
315
|
-
if (item.current === undefined)
|
|
316
|
-
item.current = i === idx;
|
|
317
|
-
}
|
|
318
|
-
return item;
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
296
|
function pickZoom(base, patch) {
|
|
322
297
|
const zoom = patch?.zoom !== undefined ? patch.zoom : base?.zoom;
|
|
323
298
|
if (zoom !== undefined && Number.isFinite(zoom) && zoom > 0)
|
|
@@ -383,6 +358,191 @@ function mergeSlot(base, patch) {
|
|
|
383
358
|
...fx,
|
|
384
359
|
};
|
|
385
360
|
}
|
|
361
|
+
/** Normalize checklist dock side. */
|
|
362
|
+
export function normalizeTodoPos(raw) {
|
|
363
|
+
const s = String(raw ?? "")
|
|
364
|
+
.trim()
|
|
365
|
+
.toLowerCase();
|
|
366
|
+
if (s === "left" || s === "l")
|
|
367
|
+
return "left";
|
|
368
|
+
if (s === "right" || s === "r")
|
|
369
|
+
return "right";
|
|
370
|
+
return undefined;
|
|
371
|
+
}
|
|
372
|
+
/** Normalize dock list style. */
|
|
373
|
+
export function normalizeTodoStyle(raw) {
|
|
374
|
+
const s = String(raw ?? "")
|
|
375
|
+
.trim()
|
|
376
|
+
.toLowerCase();
|
|
377
|
+
if (s === "sequential" || s === "seq" || s === "steps" || s === "step")
|
|
378
|
+
return "sequential";
|
|
379
|
+
if (s === "checklist" || s === "check" || s === "checks" || s === "box")
|
|
380
|
+
return "checklist";
|
|
381
|
+
return undefined;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Normalize checklist rows; apply todoIndex for sequential done/current.
|
|
385
|
+
* In checklist mode, todoIndex is ignored unless items omit explicit done.
|
|
386
|
+
*/
|
|
387
|
+
export function normalizeTodos(todos, todoIndex, style = "sequential") {
|
|
388
|
+
if (!todos || todos.length === 0)
|
|
389
|
+
return [];
|
|
390
|
+
const idx = todoIndex !== undefined && Number.isFinite(todoIndex) ? Math.floor(todoIndex) : undefined;
|
|
391
|
+
return todos.map((t, i) => {
|
|
392
|
+
const text = typeof t === "string" ? t.trim() : String(t?.text ?? "").trim();
|
|
393
|
+
const item = { text: text || `Item ${i + 1}` };
|
|
394
|
+
if (typeof t === "object" && t) {
|
|
395
|
+
if (t.done !== undefined)
|
|
396
|
+
item.done = !!t.done;
|
|
397
|
+
if (t.current !== undefined)
|
|
398
|
+
item.current = !!t.current;
|
|
399
|
+
const idRaw = t.id ?? t.name;
|
|
400
|
+
if (idRaw != null && String(idRaw).trim())
|
|
401
|
+
item.id = String(idRaw).trim();
|
|
402
|
+
}
|
|
403
|
+
if (style === "checklist") {
|
|
404
|
+
if (item.done === undefined)
|
|
405
|
+
item.done = false;
|
|
406
|
+
item.current = false;
|
|
407
|
+
return item;
|
|
408
|
+
}
|
|
409
|
+
if (idx !== undefined) {
|
|
410
|
+
if (item.done === undefined)
|
|
411
|
+
item.done = i < idx;
|
|
412
|
+
if (item.current === undefined)
|
|
413
|
+
item.current = i === idx;
|
|
414
|
+
}
|
|
415
|
+
return item;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
/** Build the floating dock from bag / phase fields. */
|
|
419
|
+
export function buildTodoDock(opts) {
|
|
420
|
+
const style = normalizeTodoStyle(opts.todoStyle) || "sequential";
|
|
421
|
+
const pos = normalizeTodoPos(opts.todoPos);
|
|
422
|
+
const dockId = opts.todoId != null && String(opts.todoId).trim() ? String(opts.todoId).trim() : undefined;
|
|
423
|
+
if (opts.todoGroups && opts.todoGroups.length) {
|
|
424
|
+
const groups = opts.todoGroups
|
|
425
|
+
.map((g) => {
|
|
426
|
+
const gs = normalizeTodoStyle(g.style) || style;
|
|
427
|
+
const items = normalizeTodos(g.items, g.todoIndex, gs);
|
|
428
|
+
if (!items.length)
|
|
429
|
+
return null;
|
|
430
|
+
const group = { style: gs, items };
|
|
431
|
+
if (g.title && String(g.title).trim())
|
|
432
|
+
group.title = String(g.title).trim();
|
|
433
|
+
const gid = g.id ?? g.name;
|
|
434
|
+
if (gid != null && String(gid).trim())
|
|
435
|
+
group.id = String(gid).trim();
|
|
436
|
+
return group;
|
|
437
|
+
})
|
|
438
|
+
.filter((g) => !!g);
|
|
439
|
+
if (!groups.length)
|
|
440
|
+
return undefined;
|
|
441
|
+
const dock = { style, groups };
|
|
442
|
+
if (pos)
|
|
443
|
+
dock.pos = pos;
|
|
444
|
+
if (dockId)
|
|
445
|
+
dock.id = dockId;
|
|
446
|
+
return dock;
|
|
447
|
+
}
|
|
448
|
+
const items = normalizeTodos(opts.todos, opts.todoIndex, style);
|
|
449
|
+
if (!items.length)
|
|
450
|
+
return undefined;
|
|
451
|
+
const group = { style, items };
|
|
452
|
+
if (opts.todoTitle && String(opts.todoTitle).trim()) {
|
|
453
|
+
group.title = String(opts.todoTitle).trim();
|
|
454
|
+
}
|
|
455
|
+
const dock = { style, groups: [group] };
|
|
456
|
+
if (opts.todoTitle && String(opts.todoTitle).trim()) {
|
|
457
|
+
dock.title = String(opts.todoTitle).trim();
|
|
458
|
+
}
|
|
459
|
+
if (pos)
|
|
460
|
+
dock.pos = pos;
|
|
461
|
+
if (dockId)
|
|
462
|
+
dock.id = dockId;
|
|
463
|
+
return dock;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Carry-forward rules for the floating todo dock across demo steps.
|
|
467
|
+
* - clear: author called hideTodos / empty todos()
|
|
468
|
+
* - set: author authored a new dock this phase
|
|
469
|
+
* - keep: author omitted todos — preserve previous dock (do not wipe)
|
|
470
|
+
*/
|
|
471
|
+
export function applyTodoPhase(prev, phase) {
|
|
472
|
+
const sync = phase.todoSync ||
|
|
473
|
+
(phase.todoDock ? "set" : phase.todos && phase.todos.length ? "set" : "keep");
|
|
474
|
+
if (sync === "clear")
|
|
475
|
+
return { dock: undefined, sync: "clear" };
|
|
476
|
+
if (sync === "set") {
|
|
477
|
+
if (phase.todoDock)
|
|
478
|
+
return { dock: phase.todoDock, sync: "set" };
|
|
479
|
+
if (phase.todos && phase.todos.length) {
|
|
480
|
+
const dock = buildTodoDock({
|
|
481
|
+
todos: phase.todos,
|
|
482
|
+
...(phase.todoPos ? { todoPos: phase.todoPos } : {}),
|
|
483
|
+
...(phase.todoId ? { todoId: phase.todoId } : {}),
|
|
484
|
+
});
|
|
485
|
+
return { dock, sync: "set" };
|
|
486
|
+
}
|
|
487
|
+
return { dock: undefined, sync: "clear" };
|
|
488
|
+
}
|
|
489
|
+
return { dock: prev, sync: "keep" };
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Advance every sequential group to `index` (capped). Checklist groups unchanged.
|
|
493
|
+
* Used while cycling rings / fill/click stubs so the walkthrough moves.
|
|
494
|
+
*/
|
|
495
|
+
export function advanceTodoDock(dock, index) {
|
|
496
|
+
if (!dock || !dock.groups.length)
|
|
497
|
+
return dock;
|
|
498
|
+
const i = Number.isFinite(index) ? Math.max(0, Math.floor(index)) : 0;
|
|
499
|
+
return {
|
|
500
|
+
...dock,
|
|
501
|
+
groups: dock.groups.map((g) => {
|
|
502
|
+
if (g.style !== "sequential")
|
|
503
|
+
return g;
|
|
504
|
+
const capped = Math.min(i, Math.max(0, g.items.length - 1));
|
|
505
|
+
const raw = g.items.map((it) => {
|
|
506
|
+
const row = { text: it.text };
|
|
507
|
+
if (it.id)
|
|
508
|
+
row.id = it.id;
|
|
509
|
+
else if (it.name)
|
|
510
|
+
row.id = it.name;
|
|
511
|
+
return row;
|
|
512
|
+
});
|
|
513
|
+
return {
|
|
514
|
+
...g,
|
|
515
|
+
items: normalizeTodos(raw, capped, "sequential"),
|
|
516
|
+
};
|
|
517
|
+
}),
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Mark sequential groups complete (all done, none current) - e.g. end of step.
|
|
522
|
+
* Checklist groups keep explicit done flags.
|
|
523
|
+
*/
|
|
524
|
+
export function completeSequentialTodoDock(dock) {
|
|
525
|
+
if (!dock)
|
|
526
|
+
return dock;
|
|
527
|
+
return {
|
|
528
|
+
...dock,
|
|
529
|
+
groups: dock.groups.map((g) => {
|
|
530
|
+
if (g.style !== "sequential")
|
|
531
|
+
return g;
|
|
532
|
+
return {
|
|
533
|
+
...g,
|
|
534
|
+
items: g.items.map((it) => {
|
|
535
|
+
const row = { text: it.text, done: true, current: false };
|
|
536
|
+
if (it.id)
|
|
537
|
+
row.id = it.id;
|
|
538
|
+
else if (it.name)
|
|
539
|
+
row.id = it.name;
|
|
540
|
+
return row;
|
|
541
|
+
}),
|
|
542
|
+
};
|
|
543
|
+
}),
|
|
544
|
+
};
|
|
545
|
+
}
|
|
386
546
|
function createStubCtx(bag, opts) {
|
|
387
547
|
return {
|
|
388
548
|
out: opts.out,
|
|
@@ -394,11 +554,52 @@ function createStubCtx(bag, opts) {
|
|
|
394
554
|
bag.highlights = { ...bag.highlights, [id]: stub };
|
|
395
555
|
},
|
|
396
556
|
todos(items) {
|
|
557
|
+
bag.todoTouched = true;
|
|
558
|
+
bag.hideTodos = false;
|
|
397
559
|
bag.todos = items;
|
|
560
|
+
delete bag.todoGroups;
|
|
561
|
+
// Empty list = explicit clear (same as hideTodos).
|
|
562
|
+
if (!items || !items.length)
|
|
563
|
+
bag.hideTodos = true;
|
|
398
564
|
},
|
|
399
565
|
todoIndex(n) {
|
|
400
566
|
bag.todoIndex = n;
|
|
401
567
|
},
|
|
568
|
+
todoTitle(text) {
|
|
569
|
+
bag.todoTitle = String(text ?? "");
|
|
570
|
+
},
|
|
571
|
+
todoId(id) {
|
|
572
|
+
bag.todoTouched = true;
|
|
573
|
+
const cleaned = String(id ?? "").trim();
|
|
574
|
+
if (cleaned)
|
|
575
|
+
bag.todoId = cleaned;
|
|
576
|
+
else
|
|
577
|
+
delete bag.todoId;
|
|
578
|
+
},
|
|
579
|
+
todoStyle(style) {
|
|
580
|
+
const s = normalizeTodoStyle(style);
|
|
581
|
+
if (s)
|
|
582
|
+
bag.todoStyle = s;
|
|
583
|
+
},
|
|
584
|
+
todoGroups(groups) {
|
|
585
|
+
bag.todoTouched = true;
|
|
586
|
+
bag.hideTodos = false;
|
|
587
|
+
bag.todoGroups = groups;
|
|
588
|
+
if (!groups || !groups.length)
|
|
589
|
+
bag.hideTodos = true;
|
|
590
|
+
},
|
|
591
|
+
hideTodos() {
|
|
592
|
+
bag.todoTouched = true;
|
|
593
|
+
bag.hideTodos = true;
|
|
594
|
+
bag.todos = [];
|
|
595
|
+
delete bag.todoGroups;
|
|
596
|
+
},
|
|
597
|
+
clearTodos() {
|
|
598
|
+
bag.todoTouched = true;
|
|
599
|
+
bag.hideTodos = true;
|
|
600
|
+
bag.todos = [];
|
|
601
|
+
delete bag.todoGroups;
|
|
602
|
+
},
|
|
402
603
|
zoom(n) {
|
|
403
604
|
if (Number.isFinite(n) && n > 0)
|
|
404
605
|
bag.zoom = n;
|
|
@@ -412,13 +613,41 @@ function createStubCtx(bag, opts) {
|
|
|
412
613
|
banner(text) {
|
|
413
614
|
bag.title = String(text ?? "");
|
|
414
615
|
},
|
|
616
|
+
todoPos(side) {
|
|
617
|
+
const p = normalizeTodoPos(side);
|
|
618
|
+
if (p)
|
|
619
|
+
bag.todoPos = p;
|
|
620
|
+
},
|
|
415
621
|
set(partial) {
|
|
416
622
|
if (partial.highlights)
|
|
417
623
|
bag.highlights = { ...partial.highlights };
|
|
418
|
-
if (partial.todos !== undefined)
|
|
624
|
+
if (partial.todos !== undefined) {
|
|
625
|
+
bag.todoTouched = true;
|
|
626
|
+
bag.hideTodos = !partial.todos || !partial.todos.length;
|
|
419
627
|
bag.todos = partial.todos;
|
|
628
|
+
if (partial.todoGroups === undefined)
|
|
629
|
+
delete bag.todoGroups;
|
|
630
|
+
}
|
|
420
631
|
if (partial.todoIndex !== undefined)
|
|
421
632
|
bag.todoIndex = partial.todoIndex;
|
|
633
|
+
if (partial.todoTitle !== undefined)
|
|
634
|
+
bag.todoTitle = String(partial.todoTitle ?? "");
|
|
635
|
+
if (partial.todoId !== undefined) {
|
|
636
|
+
bag.todoTouched = true;
|
|
637
|
+
const cleaned = String(partial.todoId ?? "").trim();
|
|
638
|
+
if (cleaned)
|
|
639
|
+
bag.todoId = cleaned;
|
|
640
|
+
else
|
|
641
|
+
delete bag.todoId;
|
|
642
|
+
}
|
|
643
|
+
const ts = normalizeTodoStyle(partial.todoStyle);
|
|
644
|
+
if (ts)
|
|
645
|
+
bag.todoStyle = ts;
|
|
646
|
+
if (partial.todoGroups !== undefined) {
|
|
647
|
+
bag.todoTouched = true;
|
|
648
|
+
bag.hideTodos = !partial.todoGroups || !partial.todoGroups.length;
|
|
649
|
+
bag.todoGroups = partial.todoGroups;
|
|
650
|
+
}
|
|
422
651
|
if (partial.zoom !== undefined && Number.isFinite(partial.zoom) && partial.zoom > 0) {
|
|
423
652
|
bag.zoom = partial.zoom;
|
|
424
653
|
}
|
|
@@ -426,6 +655,9 @@ function createStubCtx(bag, opts) {
|
|
|
426
655
|
bag.zoomOut = !!partial.zoomOut;
|
|
427
656
|
if (partial.title !== undefined)
|
|
428
657
|
bag.title = String(partial.title ?? "");
|
|
658
|
+
const tp = normalizeTodoPos(partial.todoPos);
|
|
659
|
+
if (tp)
|
|
660
|
+
bag.todoPos = tp;
|
|
429
661
|
},
|
|
430
662
|
};
|
|
431
663
|
}
|
|
@@ -557,14 +789,39 @@ export async function runStubPhase(block, phase, opts) {
|
|
|
557
789
|
let highlights = mergePhaseMaps(bag.highlights, fixturePhase);
|
|
558
790
|
highlights = applyDefaultZoom(highlights, bag.zoom);
|
|
559
791
|
highlights = applyDefaultZoomOut(highlights, bag.zoomOut);
|
|
560
|
-
const
|
|
792
|
+
const todoDock = bag.hideTodos
|
|
793
|
+
? undefined
|
|
794
|
+
: buildTodoDock({
|
|
795
|
+
todos: bag.todos,
|
|
796
|
+
todoIndex: bag.todoIndex,
|
|
797
|
+
todoTitle: bag.todoTitle,
|
|
798
|
+
todoId: bag.todoId,
|
|
799
|
+
todoStyle: bag.todoStyle,
|
|
800
|
+
todoGroups: bag.todoGroups,
|
|
801
|
+
todoPos: bag.todoPos,
|
|
802
|
+
});
|
|
803
|
+
const todos = todoDock?.groups[0]?.items ||
|
|
804
|
+
(bag.hideTodos
|
|
805
|
+
? []
|
|
806
|
+
: normalizeTodos(bag.todos, bag.todoIndex, normalizeTodoStyle(bag.todoStyle) || "sequential"));
|
|
807
|
+
// Omit todos entirely => keep previous dock. Explicit empty / hideTodos => clear.
|
|
808
|
+
const todoSync = bag.hideTodos
|
|
809
|
+
? "clear"
|
|
810
|
+
: bag.todoTouched && todoDock
|
|
811
|
+
? "set"
|
|
812
|
+
: bag.todoTouched && !todoDock
|
|
813
|
+
? "clear"
|
|
814
|
+
: "keep";
|
|
561
815
|
return {
|
|
562
816
|
highlights,
|
|
563
817
|
todos,
|
|
818
|
+
todoSync,
|
|
564
819
|
...(bag.todoIndex !== undefined ? { todoIndex: bag.todoIndex } : {}),
|
|
820
|
+
...(todoDock ? { todoDock } : {}),
|
|
565
821
|
...(bag.zoom !== undefined ? { zoom: bag.zoom } : {}),
|
|
566
822
|
...(bag.zoomOut !== undefined ? { zoomOut: bag.zoomOut } : {}),
|
|
567
823
|
...(bag.title !== undefined && bag.title !== "" ? { title: bag.title } : {}),
|
|
824
|
+
...(bag.todoPos !== undefined ? { todoPos: bag.todoPos } : {}),
|
|
568
825
|
};
|
|
569
826
|
}
|
|
570
827
|
/**
|
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, 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, 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, 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";
|
|
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, 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, 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/dist/step-overlay.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
const RING_CSS = "#wg-ring{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
|
|
2
2
|
"border:2.5px solid #7C3AED;border-radius:10px;box-shadow:0 0 0 4px rgba(124,58,237,.16);" +
|
|
3
|
-
|
|
3
|
+
// Opacity only - tone color must snap (no gray/purple/yellow morph).
|
|
4
|
+
"transition:opacity .3s ease;}" +
|
|
4
5
|
// Automation = gray; semantic tones for authored highlights.
|
|
6
|
+
"#wg-ring[data-tone=planned]{border-color:#7C3AED;box-shadow:0 0 0 4px rgba(124,58,237,.16);}" +
|
|
5
7
|
"#wg-ring[data-tone=auto]{border-color:#9CA3AF;box-shadow:0 0 0 4px rgba(156,163,175,.28);}" +
|
|
6
8
|
"#wg-ring[data-tone=info]{border-color:#3B82F6;box-shadow:0 0 0 4px rgba(59,130,246,.22);}" +
|
|
7
9
|
"#wg-ring[data-tone=warning]{border-color:#EAB308;box-shadow:0 0 0 4px rgba(234,179,8,.22);}" +
|
|
@@ -9,7 +11,8 @@ const RING_CSS = "#wg-ring{position:fixed;z-index:2147483646;pointer-events:none
|
|
|
9
11
|
"#wg-ring[data-tone=success]{border-color:#22C55E;box-shadow:0 0 0 4px rgba(34,197,94,.22);}" +
|
|
10
12
|
"#wg-ring-label{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
|
|
11
13
|
"white-space:nowrap;padding:4px 9px;border-radius:7px;background:#7C3AED;color:#fff;" +
|
|
12
|
-
"font:600 12px/1.2 system-ui,sans-serif;transition:opacity .3s ease
|
|
14
|
+
"font:600 12px/1.2 system-ui,sans-serif;transition:opacity .3s ease;}" +
|
|
15
|
+
"#wg-ring-label[data-tone=planned]{background:#7C3AED;color:#fff;}" +
|
|
13
16
|
"#wg-ring-label[data-tone=auto]{background:#6B7280;color:#fff;}" +
|
|
14
17
|
"#wg-ring-label[data-tone=info]{background:#2563EB;color:#fff;}" +
|
|
15
18
|
"#wg-ring-label[data-tone=warning]{background:#EAB308;color:#1c1917;}" +
|
|
@@ -17,7 +17,12 @@ export const NavCartBlock = defineNavClickBlock<CartPage>({
|
|
|
17
17
|
verify: [Trait.url({ pathname: "/cart.html" })],
|
|
18
18
|
stubBefore: (ctx) => {
|
|
19
19
|
ctx.title("Open cart");
|
|
20
|
-
ctx.
|
|
20
|
+
ctx.todoId("saucedemo-cart");
|
|
21
|
+
ctx.todos([
|
|
22
|
+
{ id: "cart-find", text: "Find the product" },
|
|
23
|
+
{ id: "cart-add", text: "Add to cart" },
|
|
24
|
+
{ id: "cart-open", text: "Open cart" },
|
|
25
|
+
]);
|
|
21
26
|
ctx.todoIndex(2);
|
|
22
27
|
ctx.zoom(1.4);
|
|
23
28
|
ctx.zoomOut(false);
|
package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-to-cart.effect.block.ts
CHANGED
|
@@ -36,7 +36,12 @@ 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
|
-
ctx.
|
|
39
|
+
ctx.todoId("saucedemo-cart");
|
|
40
|
+
ctx.todos([
|
|
41
|
+
{ id: "cart-find", text: "Find the product" },
|
|
42
|
+
{ id: "cart-add", text: "Add to cart" },
|
|
43
|
+
{ id: "cart-open", text: "Open cart" },
|
|
44
|
+
]);
|
|
40
45
|
ctx.todoIndex(0);
|
|
41
46
|
ctx.zoom(1.35);
|
|
42
47
|
ctx.zoomOut(false);
|
|
@@ -63,7 +68,12 @@ export const AddToCartBlock = defineEffectBlock<Checkpoint<string>, ItemInCart>(
|
|
|
63
68
|
},
|
|
64
69
|
stubAfter: (ctx) => {
|
|
65
70
|
ctx.banner("In the cart");
|
|
66
|
-
ctx.
|
|
71
|
+
ctx.todoId("saucedemo-cart");
|
|
72
|
+
ctx.todos([
|
|
73
|
+
{ id: "cart-find", text: "Find the product" },
|
|
74
|
+
{ id: "cart-add", text: "Add to cart" },
|
|
75
|
+
{ id: "cart-open", text: "Open cart" },
|
|
76
|
+
]);
|
|
67
77
|
ctx.todoIndex(1);
|
|
68
78
|
ctx.zoomOut(false);
|
|
69
79
|
ctx.ring("badge", {
|
|
@@ -49,7 +49,12 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
49
49
|
// sequential highlight queue (appear / dwell / fade), focus + color.
|
|
50
50
|
stubBefore: (ctx) => {
|
|
51
51
|
ctx.title("Signing in");
|
|
52
|
-
ctx.
|
|
52
|
+
ctx.todoId("saucedemo-login");
|
|
53
|
+
ctx.todos([
|
|
54
|
+
{ id: "login-user", text: "Enter username" },
|
|
55
|
+
{ id: "login-pass", text: "Enter password" },
|
|
56
|
+
{ id: "login-submit", text: "Click Login" },
|
|
57
|
+
]);
|
|
53
58
|
ctx.todoIndex(0);
|
|
54
59
|
ctx.zoom(1.35);
|
|
55
60
|
ctx.highlights({
|
|
@@ -82,7 +87,12 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
82
87
|
// inventory shelf. Success (LoggedIn) hands off to shopFlow.
|
|
83
88
|
if (ctx.out && ctx.out.__state === "LoginPage") {
|
|
84
89
|
ctx.title("Login blocked");
|
|
85
|
-
ctx.
|
|
90
|
+
ctx.todoId("saucedemo-login");
|
|
91
|
+
ctx.todos([
|
|
92
|
+
{ id: "login-user", text: "Enter username" },
|
|
93
|
+
{ id: "login-pass", text: "Enter password" },
|
|
94
|
+
{ id: "login-submit", text: "Click Login" },
|
|
95
|
+
]);
|
|
86
96
|
ctx.todoIndex(2);
|
|
87
97
|
ctx.ring("error", {
|
|
88
98
|
selector: '[data-test="error"]',
|
|
@@ -95,7 +105,12 @@ export const SubmitLoginActionBlock = defineMethodBlock<LoginPage, LoginSubmitOu
|
|
|
95
105
|
return;
|
|
96
106
|
}
|
|
97
107
|
ctx.banner("Inventory");
|
|
98
|
-
ctx.
|
|
108
|
+
ctx.todoId("saucedemo-login");
|
|
109
|
+
ctx.todos([
|
|
110
|
+
{ id: "login-user", text: "Enter username" },
|
|
111
|
+
{ id: "login-pass", text: "Enter password" },
|
|
112
|
+
{ id: "login-submit", text: "Click Login" },
|
|
113
|
+
]);
|
|
99
114
|
ctx.todoIndex(2);
|
|
100
115
|
// Landed on inventory - hand off to shopFlow for product -> cart zoom.
|
|
101
116
|
ctx.ring("shelf", {
|