waygraph 0.12.43 → 0.12.44

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 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.43` — overlay beacons (blind-agent gate), expected-failure panel on branched LoginPage, `--mini`, Hide stickiness.
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
@@ -412,10 +412,14 @@ function seedMemForFlow(mem, flowBlocks, json, label) {
412
412
  const RING_CSS =
413
413
  "#wg-ring{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
414
414
  "border:2.5px solid #7C3AED;border-radius:10px;" +
415
- "box-shadow:0 0 0 4px rgba(124,58,237,.16);transition:opacity .3s ease,border-color .15s,box-shadow .15s;}" +
415
+ // Opacity fade only - NEVER transition border-color/box-shadow. Tone swaps
416
+ // (auto gray -> planned purple -> warning yellow) must snap instantly;
417
+ // color transitions read as a muddy gray/purple/yellow morph on ring 2+.
418
+ "box-shadow:0 0 0 4px rgba(124,58,237,.16);transition:opacity .3s ease;}" +
416
419
  // Planned (stubs / fixtures / YAP / instruction.highlights) = purple.
417
420
  // Automation (verify fallback, unmatched fill/click) = gray - operators
418
421
  // can ignore engine checks and watch purple + semantic tones.
422
+ "#wg-ring[data-tone=planned]{border-color:#7C3AED;box-shadow:0 0 0 4px rgba(124,58,237,.16);}" +
419
423
  "#wg-ring[data-tone=auto]{border-color:#9CA3AF;box-shadow:0 0 0 4px rgba(156,163,175,.28);}" +
420
424
  "#wg-ring[data-tone=info]{border-color:#3B82F6;box-shadow:0 0 0 4px rgba(59,130,246,.22);}" +
421
425
  "#wg-ring[data-tone=warning]{border-color:#EAB308;box-shadow:0 0 0 4px rgba(234,179,8,.22);}" +
@@ -431,7 +435,8 @@ const RING_CSS =
431
435
  // window.__wgPositionRing in installOverlay.
432
436
  "#wg-ring-label{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
433
437
  "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,background .15s,color .15s;}" +
438
+ "font:600 12px/1.35 system-ui,sans-serif;transition:opacity .3s ease;}" +
439
+ "#wg-ring-label[data-tone=planned]{background:#7C3AED;color:#fff;}" +
435
440
  "#wg-ring-label[data-tone=auto]{background:#6B7280;color:#fff;}" +
436
441
  "#wg-ring-label[data-tone=info]{background:#2563EB;color:#fff;}" +
437
442
  "#wg-ring-label[data-tone=warning]{background:#EAB308;color:#1c1917;}" +
@@ -509,6 +514,18 @@ const RING_CSS =
509
514
  "#wg-panel .wg-pace[data-pace-kind=blitz] .wg-pace-badge{background:#22C55E;color:#052e16;}" +
510
515
  "#wg-panel .wg-pace[data-pace-kind=ms] .wg-pace-badge{background:#3B82F6;color:#fff;}" +
511
516
  "#wg-panel .wg-narration{margin:0 0 12px;font:italic 14px/1.4 system-ui,sans-serif;color:#f0e8ff;}" +
517
+ // Todo checklist lives in #wg-todo-dock (fixed, outside #wg-panel) so
518
+ // --mini / Hide collapse never hides it (.wg-body {display:none} would).
519
+ // Click dock to slide left <-> right (WAYGRAPH_TODO_POS / --todo-left|right).
520
+ "#wg-todo-dock{position:fixed;z-index:2147483646;top:72px;left:14px;" +
521
+ "width:min(280px,42vw);max-height:calc(100vh - 100px);overflow:auto;box-sizing:border-box;" +
522
+ "padding:10px 12px;background:rgba(20,10,40,.94);color:#fff;border-radius:12px;" +
523
+ "border:1px solid rgba(124,58,237,.45);box-shadow:0 8px 24px rgba(0,0,0,.35);" +
524
+ "pointer-events:auto;cursor:pointer;" +
525
+ "transition:transform .4s cubic-bezier(.22,1,.36,1);transform:translateX(0);}" +
526
+ "#wg-todo-dock[data-pos=right]{transform:translateX(calc(100vw - 100% - 28px));}" +
527
+ "#wg-todo-dock[data-pos=left]{transform:translateX(0);}" +
528
+ "#wg-todo-dock #wg-todos{list-style:none;margin:0;padding:0;background:transparent;border:none;}" +
512
529
  "#wg-todos{list-style:none;margin:0 0 12px;padding:8px 10px;background:#0f0620;border-radius:8px;" +
513
530
  "border:1px solid #3a2a60;}" +
514
531
  "#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 +664,12 @@ async function installOverlay(page, title) {
647
664
  // navigation that rebuilds the banner keeps the human's last pick).
648
665
  const envPos = (process.env.WAYGRAPH_TITLE_POS || "left").toLowerCase();
649
666
  const bannerPos = envPos === "center" || envPos === "right" ? envPos : "left";
667
+ const envTodoPos = (process.env.WAYGRAPH_TODO_POS || "left").toLowerCase();
668
+ const todoPos = envTodoPos === "right" ? "right" : "left";
650
669
  const envAutoplay = process.env.WAYGRAPH_AUTOPLAY === "1";
651
670
  await page
652
671
  .evaluate(
653
- ({ title, favicon, bannerPos, envAutoplay }) => {
672
+ ({ title, favicon, bannerPos, todoPos, envAutoplay }) => {
654
673
  // Seed the live autoplay toggle from the env default on first ever
655
674
  // load only - a real navigation re-runs this, and re-stamping here
656
675
  // would silently undo a human's mid-run checkbox click.
@@ -818,32 +837,46 @@ async function installOverlay(page, title) {
818
837
  /* private mode / blocked storage - position still applies this page */
819
838
  }
820
839
  };
821
- if (title && !document.getElementById("wg-banner")) {
822
- const banner = document.createElement("div");
823
- banner.id = "wg-banner";
824
- let saved = null;
825
- try {
826
- saved = localStorage.getItem("wg-banner-pos");
827
- } catch {
828
- /* ignore */
840
+ if (title) {
841
+ let banner = document.getElementById("wg-banner");
842
+ if (!banner) {
843
+ banner = document.createElement("div");
844
+ banner.id = "wg-banner";
845
+ let saved = null;
846
+ try {
847
+ saved = localStorage.getItem("wg-banner-pos");
848
+ } catch {
849
+ /* ignore */
850
+ }
851
+ const startPos =
852
+ saved && POSITIONS.includes(saved) ? saved : bannerPos;
853
+ applyPos(banner, startPos);
854
+ banner.title = "Click to move: top left / center / right";
855
+ banner.addEventListener("click", (e) => {
856
+ e.stopPropagation();
857
+ const i = POSITIONS.indexOf(banner.dataset.pos || "left");
858
+ applyPos(banner, POSITIONS[(i + 1) % POSITIONS.length]);
859
+ });
860
+ const tag = document.createElement("span");
861
+ tag.className = "wg-banner-tag";
862
+ tag.textContent = "waygraph demo";
863
+ const text = document.createElement("span");
864
+ text.className = "wg-banner-text";
865
+ text.textContent = title;
866
+ banner.appendChild(tag);
867
+ banner.appendChild(text);
868
+ document.documentElement.appendChild(banner);
869
+ } else {
870
+ // Fixture / episode title changes every step - update in place
871
+ // (banner is created once; do not leave the first step's text stuck).
872
+ let text = banner.querySelector(".wg-banner-text");
873
+ if (!text) {
874
+ text = document.createElement("span");
875
+ text.className = "wg-banner-text";
876
+ banner.appendChild(text);
877
+ }
878
+ text.textContent = title;
829
879
  }
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
880
  }
848
881
  // Tab title/favicon: a real navigation resets document.title and any
849
882
  // <link rel="icon"> the new document brings, so re-check (not
@@ -1046,8 +1079,93 @@ async function installOverlay(page, title) {
1046
1079
  }
1047
1080
  }
1048
1081
  };
1082
+ // Floating todo dock - lives outside #wg-panel so --mini / Hide never
1083
+ // hide the checklist. Click cycles left <-> right with CSS transform.
1084
+ // Authors set side via ctx.todoPos("left"|"right"); env / --todo-* fallback.
1085
+ window.__wgSyncTodos = (list, pos) => {
1086
+ list = Array.isArray(list) ? list : [];
1087
+ const POS = ["left", "right"];
1088
+ let dock = document.getElementById("wg-todo-dock");
1089
+ // Drop any in-panel leftover (old builds put #wg-todos in .wg-body).
1090
+ document.querySelectorAll("#wg-panel #wg-todos").forEach((el) => el.remove());
1091
+ if (!list.length) {
1092
+ if (dock) dock.remove();
1093
+ return;
1094
+ }
1095
+ const wantPos =
1096
+ pos === "left" || pos === "right"
1097
+ ? pos
1098
+ : null;
1099
+ if (!dock) {
1100
+ dock = document.createElement("div");
1101
+ dock.id = "wg-todo-dock";
1102
+ dock.setAttribute("data-wg-ui", "1");
1103
+ dock.setAttribute("data-wg-modal", "todos");
1104
+ dock.setAttribute("data-wg-ready", "1");
1105
+ let saved = null;
1106
+ try {
1107
+ saved = localStorage.getItem("wg-todo-pos");
1108
+ } catch {
1109
+ /* ignore */
1110
+ }
1111
+ const start =
1112
+ wantPos ||
1113
+ (saved && POS.includes(saved) ? saved : todoPos === "right" ? "right" : "left");
1114
+ dock.dataset.pos = start;
1115
+ dock.title = "Click to move checklist: left / right";
1116
+ dock.addEventListener("click", (e) => {
1117
+ e.stopPropagation();
1118
+ const i = POS.indexOf(dock.dataset.pos || "left");
1119
+ const next = POS[(i + 1) % POS.length];
1120
+ dock.dataset.pos = next;
1121
+ try {
1122
+ localStorage.setItem("wg-todo-pos", next);
1123
+ } catch {
1124
+ /* ignore */
1125
+ }
1126
+ });
1127
+ document.documentElement.appendChild(dock);
1128
+ } else if (wantPos) {
1129
+ dock.dataset.pos = wantPos;
1130
+ try {
1131
+ localStorage.setItem("wg-todo-pos", wantPos);
1132
+ } catch {
1133
+ /* ignore */
1134
+ }
1135
+ }
1136
+ const esc = (s) =>
1137
+ String(s)
1138
+ .replace(/&/g, "&amp;")
1139
+ .replace(/</g, "&lt;")
1140
+ .replace(/>/g, "&gt;");
1141
+ dock.innerHTML =
1142
+ '<ul id="wg-todos">' +
1143
+ list
1144
+ .map((t) => {
1145
+ const cls = t.current
1146
+ ? "wg-todo-current"
1147
+ : t.done
1148
+ ? "wg-todo-done"
1149
+ : "wg-todo-pending";
1150
+ const mark = t.done ? "\u2713" : t.current ? "\u2192" : "\u25CB";
1151
+ return (
1152
+ '<li class="' +
1153
+ cls +
1154
+ '"><span class="wg-todo-mark">' +
1155
+ mark +
1156
+ "</span><span>" +
1157
+ esc(t.text || "") +
1158
+ "</span></li>"
1159
+ );
1160
+ })
1161
+ .join("") +
1162
+ "</ul>";
1163
+ if (window.__wgStampModal) {
1164
+ window.__wgStampModal(dock, "todos", { ready: true });
1165
+ }
1166
+ };
1049
1167
  },
1050
- { title, favicon: WAYGRAPH_FAVICON, bannerPos, envAutoplay },
1168
+ { title, favicon: WAYGRAPH_FAVICON, bannerPos, todoPos, envAutoplay },
1051
1169
  )
1052
1170
  .catch(() => {});
1053
1171
  }
@@ -1137,41 +1255,15 @@ async function renderBeforeStep(page, info) {
1137
1255
  esc(info.paceLabel || "") +
1138
1256
  "</span></div>"
1139
1257
  : "";
1140
- const todosHtml = (() => {
1141
- const list = info.todos || [];
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
- })();
1258
+ // Todos render into floating #wg-todo-dock via __wgSyncTodos - never
1259
+ // inside .wg-body (would vanish under --mini / Hide collapse).
1167
1260
  let html =
1168
1261
  "<div id=\\"wg-progress\\"><div id=\\"wg-progress-bar\\" style=\\"width:" + pct + "%\\"></div></div>" +
1169
1262
  episodesHtml +
1170
1263
  paceHtml +
1171
1264
  "<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
1172
1265
  "<h3>Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + "</h3>" +
1173
- narrationHtml +
1174
- todosHtml;
1266
+ narrationHtml;
1175
1267
  if (info.keys.length === 0) {
1176
1268
  html += "<div class=\\"wg-key\\">(no MemKeys required)</div>";
1177
1269
  }
@@ -1210,6 +1302,7 @@ async function renderBeforeStep(page, info) {
1210
1302
  forceCollapsed: info.forceCollapsed === true ? true : undefined,
1211
1303
  });
1212
1304
  }
1305
+ if (window.__wgSyncTodos) window.__wgSyncTodos(info.todos || [], info.todoPos || null);
1213
1306
  if (window.__wgStampModal) {
1214
1307
  window.__wgStampModal(panel, "panel", {
1215
1308
  phase: "before",
@@ -1552,6 +1645,11 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1552
1645
  : normalizeTodos(h.todos, h.todoIndex);
1553
1646
  await page
1554
1647
  .evaluate((todos) => {
1648
+ if (window.__wgSyncTodos) {
1649
+ window.__wgSyncTodos(todos);
1650
+ return;
1651
+ }
1652
+ // Fallback for pages that never got installOverlay sync helper.
1555
1653
  const ul = document.getElementById("wg-todos");
1556
1654
  if (!ul) return;
1557
1655
  ul.innerHTML = todos
@@ -1720,33 +1818,7 @@ async function renderAfterStep(page, info) {
1720
1818
  escA(info.paceLabel || "") +
1721
1819
  "</span></div>"
1722
1820
  : "";
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
- })();
1821
+ const todosHtml = ""; // todos float in #wg-todo-dock via __wgSyncTodos
1750
1822
  panel.innerHTML =
1751
1823
  "<div id=\\"wg-progress\\"><div id=\\"wg-progress-bar\\" style=\\"width:" + pct + "%\\"></div></div>" +
1752
1824
  episodesHtml +
@@ -1768,6 +1840,7 @@ async function renderAfterStep(page, info) {
1768
1840
  forceCollapsed: info.forceCollapsed === true ? true : undefined,
1769
1841
  });
1770
1842
  }
1843
+ if (window.__wgSyncTodos) window.__wgSyncTodos(info.todos || [], info.todoPos || null);
1771
1844
  if (window.__wgStampModal) {
1772
1845
  window.__wgStampModal(panel, "panel", {
1773
1846
  phase: "after",
@@ -2390,7 +2463,7 @@ async function teardownOverlay(page) {
2390
2463
  window.__wgRingTrack = null;
2391
2464
  }
2392
2465
  window.__wgNarrateOwnsRing = false;
2393
- for (const id of ["wg-panel", "wg-ring", "wg-ring-label", "wg-cursor", "wg-click-pulse", "wg-banner"]) {
2466
+ for (const id of ["wg-panel", "wg-ring", "wg-ring-label", "wg-cursor", "wg-click-pulse", "wg-banner", "wg-todo-dock"]) {
2394
2467
  const el = document.getElementById(id);
2395
2468
  if (el) el.remove();
2396
2469
  }
@@ -2921,6 +2994,12 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
2921
2994
  applyHighlightStyleDefaults(h, flowStyle),
2922
2995
  );
2923
2996
  const stubBeforeTodos = stubBeforePhase.todos || [];
2997
+ const overlayTitle =
2998
+ (stubBeforePhase.title && String(stubBeforePhase.title).trim()) ||
2999
+ (r.episodeTitle && String(r.episodeTitle).trim()) ||
3000
+ title ||
3001
+ "waygraph demo";
3002
+ const overlayTodoPos = stubBeforePhase.todoPos || undefined;
2924
3003
  const isNavBlock = r.block.__waygraphKind === "nav";
2925
3004
  const autoNow = await currentAutoplay();
2926
3005
  // Video / --mini: compact pill. Never pass false - Hide/localStorage wins.
@@ -2965,7 +3044,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
2965
3044
  moduleIndex,
2966
3045
  allEpisodes,
2967
3046
  justEnteredEpisode,
2968
- title,
3047
+ title: overlayTitle,
3048
+ todoPos: overlayTodoPos,
2969
3049
  episodeNumber: r.episodeNumber,
2970
3050
  episodeTitle: r.episodeTitle,
2971
3051
  stepperMode,
@@ -3124,12 +3204,18 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
3124
3204
  }
3125
3205
  let highlights;
3126
3206
  let stubAfterTodos = [];
3207
+ let afterOverlayTitle = overlayTitle;
3208
+ let afterTodoPos = overlayTodoPos;
3127
3209
  if (hasAuthoredStubAfter(r.block, result, fixturesAfter)) {
3128
3210
  const afterPhase = await runStubPhase(r.block, "stubAfter", {
3129
3211
  out: result,
3130
3212
  fixtures: fixturesAfter,
3131
3213
  });
3132
3214
  stubAfterTodos = afterPhase.todos || [];
3215
+ if (afterPhase.title && String(afterPhase.title).trim()) {
3216
+ afterOverlayTitle = String(afterPhase.title).trim();
3217
+ }
3218
+ if (afterPhase.todoPos) afterTodoPos = afterPhase.todoPos;
3133
3219
  highlights = afterPhase.highlights.map((h) => {
3134
3220
  const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
3135
3221
  return {
@@ -3163,7 +3249,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
3163
3249
  allDescriptions: moduleDescriptions,
3164
3250
  moduleIndex,
3165
3251
  allEpisodes,
3166
- title,
3252
+ title: afterOverlayTitle,
3253
+ todoPos: afterTodoPos,
3167
3254
  episodeNumber: r.episodeNumber,
3168
3255
  episodeTitle: r.episodeTitle,
3169
3256
  stepperMode,
@@ -4191,6 +4278,12 @@ function parseRunFlags(argv) {
4191
4278
  else if (a === "--mini" || a === "--stepper-mini") {
4192
4279
  out.miniStepper = true;
4193
4280
  }
4281
+ else if (a === "--todo-left") {
4282
+ out.todoPos = "left";
4283
+ }
4284
+ else if (a === "--todo-right") {
4285
+ out.todoPos = "right";
4286
+ }
4194
4287
  else if (a === "--ff-expand") {
4195
4288
  out.ffExpand = true;
4196
4289
  }
@@ -4311,8 +4404,8 @@ function applyRunFlags(flags, opts) {
4311
4404
  process.env.WAYGRAPH_HEADED = "0";
4312
4405
  }
4313
4406
  }
4314
- if ((flags.fast || flags.fullStepper || flags.miniStepper) && !opts?.allowDemoUi) {
4315
- console.error("waygraph: --fast / --full / --mini are demo-only flags");
4407
+ if ((flags.fast || flags.fullStepper || flags.miniStepper || flags.todoPos) && !opts?.allowDemoUi) {
4408
+ console.error("waygraph: --fast / --full / --mini / --todo-left|--todo-right are demo-only flags");
4316
4409
  process.exit(1);
4317
4410
  }
4318
4411
  if (flags.step === true) {
@@ -4359,6 +4452,9 @@ function applyRunFlags(flags, opts) {
4359
4452
  if (flags.miniStepper) {
4360
4453
  process.env.WAYGRAPH_MINI = "1";
4361
4454
  }
4455
+ if (flags.todoPos === "left" || flags.todoPos === "right") {
4456
+ process.env.WAYGRAPH_TODO_POS = flags.todoPos;
4457
+ }
4362
4458
  if (flags.ffExpand) {
4363
4459
  process.env.WAYGRAPH_FF_EXPAND = "1";
4364
4460
  }
@@ -4524,6 +4620,7 @@ Primary (less is more):
4524
4620
  --fast Shorter auto-next / Next gates (keeps smooth cursor)
4525
4621
  --full Classic wrap-all block chips (default: carousel)
4526
4622
  --mini Force compact mini panel (Hide pill + Next); alias --stepper-mini
4623
+ --todo-left|--todo-right Floating checklist dock side (also ctx.todoPos / WAYGRAPH_TODO_POS)
4527
4624
  --ff-expand Expand fastForwardComposeBlock inners as separate steps
4528
4625
  --ff-disabled Dispute: expand FF (alias --no-ff); blitz kept on those inners
4529
4626
  --auto-play-video Unattended + recorded: --auto-next + --video (+ step); headless by default
@@ -54,6 +54,11 @@ export type StubPhaseFixtures = {
54
54
  zoomOut?: boolean;
55
55
  /** Top demo banner title (the purple "waygraph demo" card). */
56
56
  title?: string;
57
+ /**
58
+ * Floating checklist dock side. `left` | `right`.
59
+ * Authored control (also click / WAYGRAPH_TODO_POS / --todo-left|right).
60
+ */
61
+ todoPos?: "left" | "right";
57
62
  };
58
63
  /** One named highlight slot on a block (selector + caption). */
59
64
  export interface WaygraphHighlightStub extends FixtureDurationFields {
@@ -161,6 +166,11 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
161
166
  title(text: string): void;
162
167
  /** Alias for {@link StubCtx.title}. */
163
168
  banner(text: string): void;
169
+ /**
170
+ * Floating checklist dock side (`left` | `right`). Authors control this in
171
+ * code; click / env / --todo-left|right are fallbacks.
172
+ */
173
+ todoPos(side: "left" | "right"): void;
164
174
  /** Batch-set rings + episode fixtures. */
165
175
  set(partial: {
166
176
  highlights?: HighlightStubPhase;
@@ -182,6 +192,7 @@ export type StubPhaseResult = {
182
192
  zoom?: number;
183
193
  zoomOut?: boolean;
184
194
  title?: string;
195
+ todoPos?: "left" | "right";
185
196
  };
186
197
  /**
187
198
  * One yap/slide in a multi-step demo caption sequence.
@@ -322,6 +333,8 @@ export type ResolvedHighlight = WaygraphHighlightStub;
322
333
  * Normalize checklist rows; apply todoIndex for sequential done/current.
323
334
  */
324
335
  export declare function normalizeTodos(todos: readonly WaygraphTodoInput[] | undefined, todoIndex?: number): WaygraphTodoItem[];
336
+ /** Normalize checklist dock side. */
337
+ export declare function normalizeTodoPos(raw: unknown): "left" | "right" | undefined;
325
338
  /**
326
339
  * Run an open stub lifecycle (preferred) or resolve a static slot map.
327
340
  * Returns rings + episode fixtures (todos / todoIndex / zoom).
@@ -383,6 +383,17 @@ function mergeSlot(base, patch) {
383
383
  ...fx,
384
384
  };
385
385
  }
386
+ /** Normalize checklist dock side. */
387
+ export function normalizeTodoPos(raw) {
388
+ const s = String(raw ?? "")
389
+ .trim()
390
+ .toLowerCase();
391
+ if (s === "left" || s === "l")
392
+ return "left";
393
+ if (s === "right" || s === "r")
394
+ return "right";
395
+ return undefined;
396
+ }
386
397
  function createStubCtx(bag, opts) {
387
398
  return {
388
399
  out: opts.out,
@@ -412,6 +423,11 @@ function createStubCtx(bag, opts) {
412
423
  banner(text) {
413
424
  bag.title = String(text ?? "");
414
425
  },
426
+ todoPos(side) {
427
+ const p = normalizeTodoPos(side);
428
+ if (p)
429
+ bag.todoPos = p;
430
+ },
415
431
  set(partial) {
416
432
  if (partial.highlights)
417
433
  bag.highlights = { ...partial.highlights };
@@ -426,6 +442,9 @@ function createStubCtx(bag, opts) {
426
442
  bag.zoomOut = !!partial.zoomOut;
427
443
  if (partial.title !== undefined)
428
444
  bag.title = String(partial.title ?? "");
445
+ const tp = normalizeTodoPos(partial.todoPos);
446
+ if (tp)
447
+ bag.todoPos = tp;
429
448
  },
430
449
  };
431
450
  }
@@ -565,6 +584,7 @@ export async function runStubPhase(block, phase, opts) {
565
584
  ...(bag.zoom !== undefined ? { zoom: bag.zoom } : {}),
566
585
  ...(bag.zoomOut !== undefined ? { zoomOut: bag.zoomOut } : {}),
567
586
  ...(bag.title !== undefined && bag.title !== "" ? { title: bag.title } : {}),
587
+ ...(bag.todoPos !== undefined ? { todoPos: bag.todoPos } : {}),
568
588
  };
569
589
  }
570
590
  /**
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ 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";
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, 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
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";
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";
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, 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";
@@ -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
- "transition:opacity .3s ease,border-color .15s,box-shadow .15s;}" +
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,background .15s,color .15s;}" +
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;}" +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.12.43",
3
+ "version": "0.12.44",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",