waygraph 0.16.1 → 0.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/README.md +12 -0
  2. package/dist/analysis/practices-check.d.ts +1 -1
  3. package/dist/analysis/practices-check.js +36 -0
  4. package/dist/beta.d.ts +6 -0
  5. package/dist/beta.js +5 -0
  6. package/dist/cli/commands/codemod-flows.js +2 -2
  7. package/dist/cli/commands/codemod-stubs.js +132 -2
  8. package/dist/cli/commands/pilot.js +3 -16
  9. package/dist/cli/commands/session-ops.js +26 -1
  10. package/dist/cli/flags.d.ts +1 -1
  11. package/dist/cli/flags.js +1 -1
  12. package/dist/cli/run-chain.js +2 -2
  13. package/dist/cli/usage.js +6 -0
  14. package/dist/cli/util.js +1 -1
  15. package/dist/demo/narration/demo-pace.d.ts +1 -1
  16. package/dist/demo/narration/demo-pace.js +1 -1
  17. package/dist/demo/narration/merge.d.ts +1 -1
  18. package/dist/demo/narration/merge.js +1 -1
  19. package/dist/demo/narration/narration.d.ts +2 -6
  20. package/dist/demo/narration/narration.js +5 -17
  21. package/dist/demo/narration/stub-ctx.js +126 -124
  22. package/dist/demo/overlay/cursor.js +1 -1
  23. package/dist/demo/overlay/inpage/banner.js +5 -0
  24. package/dist/demo/overlay/inpage/core.js +1 -6
  25. package/dist/demo/overlay/inpage/network.js +1 -1
  26. package/dist/demo/overlay/inpage/todos.js +0 -15
  27. package/dist/demo/overlay/network-capture.js +2 -2
  28. package/dist/demo/overlay/overlay-css.js +19 -76
  29. package/dist/demo/overlay/overlay-stage.js +1 -1
  30. package/dist/demo/overlay/overlay.js +3 -2
  31. package/dist/demo/overlay/phase-fixtures.js +5 -5
  32. package/dist/demo/overlay/pilot/shell.js +7 -5
  33. package/dist/demo/overlay/rings.d.ts +0 -8
  34. package/dist/demo/overlay/rings.js +0 -15
  35. package/dist/demo/overlay/ui/components/ring.js +2 -2
  36. package/dist/demo/overlay/ui/overlay.css +136 -33
  37. package/dist/demo/overlay/ui/shadow.js +69 -2
  38. package/dist/demo/stepper/after.js +4 -17
  39. package/dist/demo/stepper/before.js +3 -1
  40. package/dist/demo/stepper/demo-log.js +8 -8
  41. package/dist/demo/stepper/interaction-patch.js +3 -15
  42. package/dist/demo/stepper/panel-after.js +1 -1
  43. package/dist/demo/stepper/slides.d.ts +3 -0
  44. package/dist/demo/stepper/slides.js +18 -2
  45. package/dist/demo/stepper/stepper.js +1 -1
  46. package/dist/demo/stepper/teardown.js +0 -1
  47. package/dist/demo/verbose-log.js +1 -1
  48. package/dist/engine/block/block.d.ts +1 -12
  49. package/dist/engine/block/kind.d.ts +3 -0
  50. package/dist/engine/block/kind.js +11 -0
  51. package/dist/engine/block/kinds/method.d.ts +0 -2
  52. package/dist/engine/block/kinds/method.js +0 -1
  53. package/dist/engine/block/kinds/nav.js +1 -1
  54. package/dist/engine/errors.d.ts +12 -0
  55. package/dist/engine/errors.js +12 -0
  56. package/dist/engine/read/read-block.d.ts +61 -0
  57. package/dist/engine/read/read-block.js +94 -0
  58. package/dist/engine/read/read-key.d.ts +32 -0
  59. package/dist/engine/read/read-key.js +34 -0
  60. package/dist/engine/read/read-page.d.ts +19 -0
  61. package/dist/engine/read/read-page.js +50 -0
  62. package/dist/engine/read/readers.d.ts +82 -0
  63. package/dist/engine/read/readers.js +43 -0
  64. package/dist/engine/read/run-read.d.ts +17 -0
  65. package/dist/engine/read/run-read.js +156 -0
  66. package/dist/engine/run/connect.js +3 -2
  67. package/dist/engine/run/events.d.ts +28 -10
  68. package/dist/engine/run/events.js +52 -38
  69. package/dist/engine/run/run.d.ts +1 -19
  70. package/dist/engine/run/run.js +21 -97
  71. package/dist/engine/stub/ring.d.ts +2 -4
  72. package/dist/engine/stub/ring.js +1 -0
  73. package/dist/engine/stub/stub.d.ts +38 -25
  74. package/dist/index.d.ts +9 -7
  75. package/dist/index.js +11 -6
  76. package/dist/live/auto/auto.js +2 -3
  77. package/dist/live/auto/menu.js +3 -2
  78. package/dist/live/auto-session/auto-session.d.ts +9 -2
  79. package/dist/live/auto-session/auto-session.js +23 -20
  80. package/dist/live/auto-session/feed.d.ts +16 -0
  81. package/dist/live/auto-session/feed.js +71 -0
  82. package/dist/live/auto-session/helpers.d.ts +7 -0
  83. package/dist/live/auto-session/helpers.js +9 -0
  84. package/dist/live/auto-session/ipc.d.ts +15 -47
  85. package/dist/live/auto-session/ipc.js +34 -4
  86. package/dist/live/auto-session/protocol.d.ts +127 -0
  87. package/dist/live/auto-session/protocol.js +6 -0
  88. package/dist/live/auto-session/read.d.ts +25 -0
  89. package/dist/live/auto-session/read.js +50 -0
  90. package/dist/live/traverse/worker.d.ts +1 -0
  91. package/dist/live/traverse/worker.js +6 -1
  92. package/dist/manifest/manifest.d.ts +22 -2
  93. package/dist/manifest/manifest.js +27 -1
  94. package/dist/manifest/project.d.ts +10 -1
  95. package/dist/manifest/project.js +9 -12
  96. package/dist/manifest/read-blocks.d.ts +2 -0
  97. package/dist/manifest/read-blocks.js +23 -0
  98. package/dist/manifest/scan.d.ts +3 -2
  99. package/dist/manifest/scan.js +8 -4
  100. package/dist/runner/plan/find.js +2 -2
  101. package/dist/runner/plan/plan.js +1 -1
  102. package/dist/runner/plan/seed-mem.js +5 -5
  103. package/dist/runner/plan/spec.js +1 -1
  104. package/dist/runner/runner.d.ts +0 -1
  105. package/dist/runner/runner.js +6 -6
  106. package/dist/runtime/full-launch.d.ts +2 -1
  107. package/dist/runtime/full-launch.js +29 -27
  108. package/dist/runtime/run-log.d.ts +2 -0
  109. package/dist/runtime/run-log.js +13 -6
  110. package/examples/saucedemo/README.md +2 -2
  111. package/examples/saucedemo/src/blocks/SITE-MAP.md +3 -0
  112. package/examples/saucedemo/src/blocks/saucedemo-web/cart/cart.read.block.ts +26 -0
  113. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
  114. package/examples/saucedemo/src/blocks/saucedemo-web/header.read.block.ts +25 -0
  115. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/inventory.read.block.ts +28 -0
  116. package/examples/saucedemo/src/flows/api-demo.flow.ts +1 -1
  117. package/examples/saucedemo/src/flows/cart-bulk.flow.ts +1 -1
  118. package/examples/saucedemo/src/flows/cart-continue.flow.ts +1 -1
  119. package/examples/saucedemo/src/flows/catalog-logout.flow.ts +1 -1
  120. package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +3 -2
  121. package/examples/saucedemo/src/flows/checkout.flow.ts +13 -12
  122. package/examples/saucedemo/src/flows/login.flow.ts +1 -1
  123. package/examples/saucedemo/src/flows/shop.flow.ts +3 -2
  124. package/examples/saucedemo/src/flows/stub-on-error.flow.ts +1 -1
  125. package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +1 -1
  126. package/examples/saucedemo/src/states/checkout.reads.ts +9 -0
  127. package/examples/saucedemo/src/stubs/device-showcase.ts +70 -76
  128. package/examples/saucedemo/src/stubs/finish-order.ts +38 -0
  129. package/examples/saucedemo/src/stubs/login.ts +5 -5
  130. package/package.json +5 -1
  131. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +2 -34
  132. package/templates/quickstart/src/flows/api-demo.flow.ts +1 -1
  133. package/templates/quickstart/src/flows/cart-bulk.flow.ts +1 -1
  134. package/templates/quickstart/src/flows/cart-continue.flow.ts +1 -1
  135. package/templates/quickstart/src/flows/catalog-logout.flow.ts +1 -1
  136. package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +3 -2
  137. package/templates/quickstart/src/flows/checkout.flow.ts +13 -12
  138. package/templates/quickstart/src/flows/login.flow.ts +1 -1
  139. package/templates/quickstart/src/flows/shop.flow.ts +3 -2
  140. package/templates/quickstart/src/flows/stub-on-error.flow.ts +1 -1
  141. package/templates/quickstart/src/flows/viewer-blocked.flow.ts +1 -1
  142. package/templates/quickstart/src/stubs/device-showcase.ts +70 -76
  143. package/templates/quickstart/src/stubs/finish-order.ts +38 -0
  144. package/templates/quickstart/src/stubs/login.ts +5 -5
  145. package/templates/scaffold/src/flows/(external)/hoppscotch/auth.flow.ts +1 -1
  146. package/templates/scaffold/src/flows/(external)/hoppscotch/get.flow.ts +1 -1
  147. package/templates/scaffold/src/flows/(external)/hoppscotch/post.flow.ts +1 -1
  148. package/templates/scaffold/src/flows/(user-stories)/email-verification.flow.ts +4 -4
  149. package/templates/scaffold/src/flows/README.md +1 -1
  150. package/templates/scaffold/src/flows/home/cart.flow.ts +7 -7
  151. package/templates/scaffold/src/flows/home/hello.flow.ts +10 -7
  152. package/templates/scaffold/src/map/(app_base)/home/_methods/assert-hello.method.block.ts +1 -9
  153. package/templates/skills/waygraph-convention.skill.md +10 -2
  154. package/templates/skills/waygraph-pilot.skill.md +7 -0
@@ -25,136 +25,138 @@ export function createStubCtx(bag, opts) {
25
25
  mem: opts.mem,
26
26
  out: opts.out,
27
27
  error: opts.error,
28
- highlights(slots) {
29
- bag.highlights = { ...(slots || {}) };
30
- },
31
- ring(id, stub) {
32
- bag.highlights = { ...bag.highlights, [id]: stub };
33
- },
34
- todos(id, items, opts = {}) {
35
- setDock(id, opts);
36
- bag.todos = items;
37
- if (opts.index != null)
38
- bag.todoIndex = Number(opts.index);
39
- if (!items.length)
28
+ ui: {
29
+ ring(id, stub) {
30
+ bag.highlights = { ...bag.highlights, [id]: stub };
31
+ },
32
+ todos(id, items, opts = {}) {
33
+ setDock(id, opts);
34
+ bag.todos = items;
35
+ if (opts.index != null)
36
+ bag.todoIndex = Number(opts.index);
37
+ if (!items.length)
38
+ bag.hideTodos = true;
39
+ },
40
+ todoIndex(n) {
41
+ bag.todoIndex = n;
42
+ },
43
+ todoGroups(id, groups, opts = {}) {
44
+ setDock(id, opts);
45
+ bag.todoGroups = groups;
46
+ if (!groups.length)
47
+ bag.hideTodos = true;
48
+ },
49
+ hideTodos() {
50
+ bag.todoTouched = true;
40
51
  bag.hideTodos = true;
41
- },
42
- todoIndex(n) {
43
- bag.todoIndex = n;
44
- },
45
- todoGroups(id, groups, opts = {}) {
46
- setDock(id, opts);
47
- bag.todoGroups = groups;
48
- if (!groups.length)
49
- bag.hideTodos = true;
50
- },
51
- hideTodos() {
52
- bag.todoTouched = true;
53
- bag.hideTodos = true;
54
- bag.todos = [];
55
- delete bag.todoGroups;
56
- },
57
- device(preset) {
58
- bag.deviceTouched = true;
59
- bag.hideDevice = false;
60
- const resolved = resolveDeviceState(preset, bag.touchOverride, bag.orientationOverride);
61
- if (resolved) {
62
- bag.device = resolved;
63
- if (resolved.preset === "desktop" && !resolved.touchMode) {
64
- bag.hideDevice = true;
52
+ bag.todos = [];
53
+ delete bag.todoGroups;
54
+ },
55
+ device(preset) {
56
+ bag.deviceTouched = true;
57
+ bag.hideDevice = false;
58
+ const resolved = resolveDeviceState(preset, bag.touchOverride, bag.orientationOverride);
59
+ if (resolved) {
60
+ bag.device = resolved;
61
+ if (resolved.preset === "desktop" && !resolved.touchMode) {
62
+ bag.hideDevice = true;
63
+ }
65
64
  }
66
- }
67
- },
68
- viewport(box) {
69
- bag.deviceTouched = true;
70
- bag.hideDevice = false;
71
- const resolved = resolveDeviceState(box, bag.touchOverride, bag.orientationOverride);
72
- if (resolved)
73
- bag.device = resolved;
74
- },
75
- touch(on) {
76
- bag.deviceTouched = true;
77
- const enabled = on === undefined ? true : !!on;
78
- bag.touchOverride = enabled;
79
- if (enabled)
65
+ },
66
+ viewport(box) {
67
+ bag.deviceTouched = true;
80
68
  bag.hideDevice = false;
81
- if (bag.device) {
82
- bag.device = {
83
- ...bag.device,
84
- touchMode: enabled,
85
- hasTouch: bag.device.hasTouch || enabled,
86
- };
69
+ const resolved = resolveDeviceState(box, bag.touchOverride, bag.orientationOverride);
70
+ if (resolved)
71
+ bag.device = resolved;
72
+ },
73
+ touch(on) {
74
+ bag.deviceTouched = true;
75
+ const enabled = on === undefined ? true : !!on;
76
+ bag.touchOverride = enabled;
87
77
  if (enabled)
88
78
  bag.hideDevice = false;
89
- }
90
- else if (enabled) {
91
- const base = resolveDeviceState("desktop", true, bag.orientationOverride);
79
+ if (bag.device) {
80
+ bag.device = {
81
+ ...bag.device,
82
+ touchMode: enabled,
83
+ hasTouch: bag.device.hasTouch || enabled,
84
+ };
85
+ if (enabled)
86
+ bag.hideDevice = false;
87
+ }
88
+ else if (enabled) {
89
+ const base = resolveDeviceState("desktop", true, bag.orientationOverride);
90
+ if (base)
91
+ bag.device = { ...base, touchMode: true, hasTouch: true };
92
+ }
93
+ },
94
+ orientation(o) {
95
+ const want = normalizeDeviceOrientation(o);
96
+ if (!want)
97
+ return;
98
+ bag.deviceTouched = true;
99
+ bag.hideDevice = false;
100
+ bag.orientationOverride = want;
101
+ const base = bag.device ||
102
+ resolveDeviceState("mobile", bag.touchOverride) ||
103
+ resolveDeviceState("mobile");
92
104
  if (base)
93
- bag.device = { ...base, touchMode: true, hasTouch: true };
94
- }
95
- },
96
- orientation(o) {
97
- const want = normalizeDeviceOrientation(o);
98
- if (!want)
99
- return;
100
- bag.deviceTouched = true;
101
- bag.hideDevice = false;
102
- bag.orientationOverride = want;
103
- const base = bag.device ||
104
- resolveDeviceState("mobile", bag.touchOverride) ||
105
- resolveDeviceState("mobile");
106
- if (base)
107
- bag.device = applyOrientation(base, want);
108
- },
109
- hideDevice() {
110
- bag.deviceTouched = true;
111
- bag.hideDevice = true;
112
- bag.device = resolveDeviceState("desktop", false);
113
- bag.touchOverride = false;
114
- bag.orientationOverride = undefined;
115
- },
116
- zoom(n) {
117
- if (Number.isFinite(n) && n > 0)
118
- bag.zoom = n;
119
- },
120
- zoomOut(keep) {
121
- bag.zoomOut = !!keep;
122
- },
123
- title(text, opts) {
124
- bag.title = String(text ?? "");
125
- const p = normalizeBannerPos(opts?.pos);
126
- if (p)
127
- bag.titlePos = p;
128
- },
129
- titlePos(side) {
130
- const p = normalizeBannerPos(side);
131
- if (p)
132
- bag.titlePos = p;
133
- },
134
- caption(text) {
135
- bag.caption = String(text ?? "");
136
- },
137
- hideCaption() {
138
- bag.caption = "";
139
- },
140
- showNetworkTab() {
141
- bag.networkTabVisible = true;
142
- },
143
- hideNetworkTab() {
144
- bag.networkTabVisible = false;
145
- },
146
- bannerUi(opts) {
147
- if (!opts || typeof opts !== "object")
148
- return;
149
- bag.bannerUi = { ...(bag.bannerUi || {}), ...opts };
150
- },
151
- hideBanner() {
152
- bag.bannerUi = { ...(bag.bannerUi || {}), hidden: true };
153
- },
154
- todoDockUi(opts) {
155
- if (!opts || typeof opts !== "object")
156
- return;
157
- bag.todoDockUi = { ...(bag.todoDockUi || {}), ...opts };
105
+ bag.device = applyOrientation(base, want);
106
+ },
107
+ hideDevice() {
108
+ bag.deviceTouched = true;
109
+ bag.hideDevice = true;
110
+ bag.device = resolveDeviceState("desktop", false);
111
+ bag.touchOverride = false;
112
+ bag.orientationOverride = undefined;
113
+ },
114
+ zoom(n) {
115
+ if (Number.isFinite(n) && n > 0)
116
+ bag.zoom = n;
117
+ },
118
+ zoomOut(keep) {
119
+ bag.zoomOut = !!keep;
120
+ },
121
+ title(text, opts) {
122
+ bag.title = String(text ?? "");
123
+ const p = normalizeBannerPos(opts?.pos);
124
+ if (p)
125
+ bag.titlePos = p;
126
+ },
127
+ titlePos(side) {
128
+ const p = normalizeBannerPos(side);
129
+ if (p)
130
+ bag.titlePos = p;
131
+ },
132
+ caption(text) {
133
+ bag.caption = String(text ?? "");
134
+ },
135
+ slides(slides) {
136
+ bag.slides = slides;
137
+ },
138
+ hideCaption() {
139
+ bag.caption = "";
140
+ },
141
+ showNetworkTab() {
142
+ bag.networkTabVisible = true;
143
+ },
144
+ hideNetworkTab() {
145
+ bag.networkTabVisible = false;
146
+ },
147
+ bannerUi(opts) {
148
+ if (!opts || typeof opts !== "object")
149
+ return;
150
+ bag.bannerUi = { ...(bag.bannerUi || {}), ...opts };
151
+ },
152
+ hideBanner() {
153
+ bag.bannerUi = { ...(bag.bannerUi || {}), hidden: true };
154
+ },
155
+ todoDockUi(opts) {
156
+ if (!opts || typeof opts !== "object")
157
+ return;
158
+ bag.todoDockUi = { ...(bag.todoDockUi || {}), ...opts };
159
+ },
158
160
  },
159
161
  };
160
162
  }
@@ -51,7 +51,7 @@ export async function playSwipeTrail(page, opts) {
51
51
  layer.id = "wg-swipe-layer";
52
52
  layer.setAttribute("data-wg-ui", "1");
53
53
  layer.style.cssText =
54
- "position:fixed;inset:0;z-index:2147483646;pointer-events:none;overflow:hidden;";
54
+ "position:fixed;inset:0;z-index:var(--wg-z-cursor,2147483646);pointer-events:none;overflow:hidden;";
55
55
  const finger = document.createElement("div");
56
56
  finger.style.cssText =
57
57
  "position:absolute;width:28px;height:28px;margin:-14px 0 0 -14px;border-radius:50%;" +
@@ -2,6 +2,7 @@ export function installBanner({ title, favicon, bannerPos, bannerUi, tag: banner
2
2
  const POSITIONS = ["left", "center", "right"];
3
3
  const applyPos = (el, pos) => {
4
4
  el.dataset.pos = pos;
5
+ __wgAdd(el);
5
6
  el.dataset.home = pos;
6
7
  try {
7
8
  localStorage.setItem("wg-banner-pos", pos);
@@ -79,6 +80,7 @@ export function installBanner({ title, favicon, bannerPos, bannerUi, tag: banner
79
80
  if (bannerEl) {
80
81
  if (bui.pos && POSITIONS.includes(bui.pos) && bannerEl.dataset.home !== bui.pos) {
81
82
  bannerEl.dataset.pos = bui.pos;
83
+ __wgAdd(bannerEl);
82
84
  bannerEl.dataset.home = bui.pos;
83
85
  bannerEl.dataset.authored = "1";
84
86
  }
@@ -108,6 +110,7 @@ export function installBanner({ title, favicon, bannerPos, bannerUi, tag: banner
108
110
  (window.__wgBannerLog = window.__wgBannerLog || []).push(line);
109
111
  };
110
112
  b.dataset.pos = home;
113
+ __wgAdd(b);
111
114
  if (!hits()) {
112
115
  delete b.dataset.moved;
113
116
  log("clear");
@@ -115,6 +118,7 @@ export function installBanner({ title, favicon, bannerPos, bannerUi, tag: banner
115
118
  }
116
119
  for (const cand of POSITIONS.filter((p) => p !== home)) {
117
120
  b.dataset.pos = cand;
121
+ __wgAdd(b);
118
122
  if (!hits()) {
119
123
  b.dataset.moved = home + ">" + cand;
120
124
  log("moved:" + cand, "moved " + home + " -> " + cand + " (ring overlap)");
@@ -122,6 +126,7 @@ export function installBanner({ title, favicon, bannerPos, bannerUi, tag: banner
122
126
  }
123
127
  }
124
128
  b.dataset.pos = home;
129
+ __wgAdd(b);
125
130
  b.dataset.moved = "none-free";
126
131
  log("none-free", "overlap at every position (kept " + home + ")");
127
132
  };
@@ -209,6 +209,7 @@ export function installCore({ envAutoplay, todoDockUi }) {
209
209
  continue;
210
210
  const cur = dock.dataset.pos === "right" ? "right" : "left";
211
211
  dock.dataset.pos = cur === "right" ? "left" : "right";
212
+ __wgAdd(dock);
212
213
  try {
213
214
  localStorage.setItem("wg-todo-pos", dock.dataset.pos);
214
215
  }
@@ -275,12 +276,6 @@ export function installCore({ envAutoplay, todoDockUi }) {
275
276
  pulse.classList.add("wg-pulse");
276
277
  };
277
278
  window.__wgHideRing = (opts) => {
278
- // While a narrate() call owns the ring (mid multi-step action),
279
- // the auto-highlight click/fill patches' own end-of-step hide is
280
- // a no-op - narrate() itself clears it once the WHOLE wrapped
281
- // action finishes, not just its first sub-step.
282
- if (window.__wgNarrateOwnsRing)
283
- return;
284
279
  if (window.__wgStopRingFollow)
285
280
  window.__wgStopRingFollow();
286
281
  const ring = __wgById("wg-ring");
@@ -145,7 +145,7 @@ export function addNetworkEntry(entry) {
145
145
  const countEl = panel.querySelector(".wg-net-count");
146
146
  if (countEl)
147
147
  countEl.textContent = String(w.__wgNetworkEntries.length);
148
- // Always captured, only shown when explicitly turned on (ctx.showNetworkTab(); default hidden -
148
+ // Always captured, only shown when explicitly turned on (ctx.ui.showNetworkTab(); default hidden -
149
149
  // see StubCtx's own doc comment) - the DOM/row still gets built either way, so toggling visible
150
150
  // later shows the full history immediately, not just entries captured after that point.
151
151
  if (w.__wgNetworkTabVisible) {
@@ -38,19 +38,6 @@ export function installTodos({ todoPos }) {
38
38
  // Drop any in-panel leftover (old builds put #wg-todos in .wg-body).
39
39
  __wgQA("#wg-panel #wg-todos").forEach((el) => el.remove());
40
40
  const allDocks = () => [...__wgQA(".wg-todo-dock, #wg-todo-dock")].filter((el, i, arr) => arr.indexOf(el) === i);
41
- const relayoutTodoDocks = () => {
42
- const docks = allDocks();
43
- // Must match .wg-todo-dock/#wg-todo-dock's own CSS `top` (demo/overlay/overlay-css.ts,
44
- // ui/css/dock.css) - this inline style OVERRIDES that CSS the instant more than one
45
- // dock exists (multi-todo stacking), so a CSS-only change here is silently underwater;
46
- // real bug this was: the CSS top was bumped 72 -> 104 for banner clearance, but this
47
- // hardcoded 72 kept winning at runtime and nothing actually moved.
48
- let top = 104;
49
- for (const el of docks) {
50
- el.style.top = top + "px";
51
- top += Math.max(48, el.getBoundingClientRect().height) + 10;
52
- }
53
- };
54
41
  const findDock = (key) => {
55
42
  if (key === "_default") {
56
43
  return (__wgQ('.wg-todo-dock[data-wg-todo-key="_default"]') ||
@@ -88,7 +75,6 @@ export function installTodos({ todoPos }) {
88
75
  else {
89
76
  allDocks().forEach((el) => el.remove());
90
77
  }
91
- relayoutTodoDocks();
92
78
  return;
93
79
  }
94
80
  if (sync === "set" && replace) {
@@ -285,7 +271,6 @@ export function installTodos({ todoPos }) {
285
271
  });
286
272
  }
287
273
  dock._wgLastPayload = syncPayload;
288
- relayoutTodoDocks();
289
274
  if (window.__wgStampModal) {
290
275
  window.__wgStampModal(dock, "todos", { ready: true });
291
276
  }
@@ -55,6 +55,8 @@ export async function restoreNetworkHistory(page) {
55
55
  }
56
56
  }
57
57
  export function installNetworkCapture(page) {
58
+ // defineApiBlock reports its calls through the engine's hook; show them in the same panel.
59
+ setApiCallRecorder((p, entry) => recordNetworkEntry(p, entry));
58
60
  if (page.__wgNetworkCaptureInstalled)
59
61
  return;
60
62
  page.__wgNetworkCaptureInstalled = true;
@@ -123,5 +125,3 @@ export function installNetworkCapture(page) {
123
125
  });
124
126
  });
125
127
  }
126
- // defineApiBlock reports its calls through core's hook; show them in the same panel.
127
- setApiCallRecorder((page, entry) => recordNetworkEntry(page, entry));
@@ -1,7 +1,7 @@
1
1
  // CSS for the runner-only chrome (step panel, chips) and for the host page (device stage), next to the shared bundle in ui/css.
2
2
  import { SURFACE } from "./ui/tokens.js";
3
3
  // ---------------------------------------------------------------------------
4
- // WAYGRAPH_STEP=1 - human-verification overlay: one Block at a time, a
4
+ // The demo stepper (waygraph demo) - human-verification overlay: one Block at a time, a
5
5
  // browser-injected panel showing/editing that Block's MemKeys, a "Run this
6
6
  // step" gate, then a highlight ring over whatever its own verify Traits just
7
7
  // confirmed, then a "Next" gate before moving on. Ring CSS borrowed from
@@ -15,12 +15,12 @@ import { SURFACE } from "./ui/tokens.js";
15
15
  // never collides with that bundle.
16
16
  export const RUNNER_EXTRA_CSS =
17
17
  // Spotlight: dim everything except the highlight target (focus: true).
18
- "#wg-focus-veil{position:fixed;z-index:2147483644;pointer-events:none;" +
18
+ "#wg-focus-veil{position:fixed;z-index:var(--wg-z-focus);pointer-events:none;" +
19
19
  "border-radius:12px;box-shadow:0 0 0 9999px rgba(8,4,20,.62);" +
20
20
  "opacity:0;transition:opacity .3s ease,left .3s ease,top .3s ease,width .3s ease,height .3s ease;}" +
21
21
  "#wg-focus-veil.wg-in{opacity:1;}" +
22
22
  // Touch swipe trail (mobile/tablet orientation + device theater).
23
- "#wg-swipe-layer{position:fixed;inset:0;z-index:2147483646;pointer-events:none;" +
23
+ "#wg-swipe-layer{position:fixed;inset:0;z-index:var(--wg-z-cursor);pointer-events:none;" +
24
24
  "overflow:hidden;}" +
25
25
  "#wg-swipe-layer .wg-swipe-dot{position:absolute;width:14px;height:14px;margin:-7px 0 0 -7px;" +
26
26
  "border-radius:50%;background:rgba(147,197,253,.55);border:2px solid rgba(255,255,255,.85);" +
@@ -34,8 +34,8 @@ export const RUNNER_EXTRA_CSS =
34
34
  "border:1px solid rgba(56,189,248,.55);font:700 12px/1.2 system-ui,sans-serif;" +
35
35
  "letter-spacing:.04em;text-transform:uppercase;opacity:0;transition:opacity .25s ease;}" +
36
36
  "#wg-swipe-layer.wg-in .wg-swipe-label{opacity:1;}" +
37
- "#wg-panel{position:fixed;z-index:2147483647;left:50%;bottom:12px;transform:translateX(-50%);" +
38
- "max-width:min(92vw,640px);max-height:calc(100vh - 24px);overflow-y:auto;box-sizing:border-box;" +
37
+ "#wg-panel{z-index:var(--wg-z-top);" +
38
+ "width:min(92vw,640px);overflow-y:auto;box-sizing:border-box;" +
39
39
  "background:" + SURFACE + ";color:#fff;border-radius:14px;" +
40
40
  "padding:16px 20px;font:14px/1.4 system-ui,sans-serif;box-shadow:0 12px 30px rgba(0,0,0,.35);" +
41
41
  // .06s was tuned back when the panel was removed and recreated on
@@ -78,16 +78,14 @@ export const RUNNER_EXTRA_CSS =
78
78
  // Todo checklist(s) float outside #wg-panel so --mini / Hide never hide them.
79
79
  // Multiple docks (one per todoId) are supported - stacked on the same side.
80
80
  // Click a dock to slide left <-> right (WAYGRAPH_TODO_POS / --todo-left|right).
81
- ".wg-todo-dock,#wg-todo-dock{position:fixed;z-index:2147483646;top:104px;left:14px;" +
82
- "width:min(280px,42vw);max-height:calc(100vh - 100px);overflow:auto;box-sizing:border-box;" +
81
+ ".wg-todo-dock,#wg-todo-dock{z-index:var(--wg-z-dock);" +
82
+ "width:min(280px,42vw);overflow:auto;box-sizing:border-box;" +
83
83
  "padding:10px 12px;background:" + SURFACE + ";color:#fff;border-radius:12px;" +
84
84
  "border:1px solid rgba(124,58,237,.45);box-shadow:0 8px 24px rgba(0,0,0,.35);" +
85
85
  "pointer-events:auto;cursor:pointer;" +
86
- "transition:transform .4s cubic-bezier(.22,1,.36,1),top .35s ease,opacity .25s ease,z-index 0s;transform:translateX(0);}" +
87
- ".wg-todo-dock[data-pos=right],#wg-todo-dock[data-pos=right]{transform:translateX(calc(100vw - 100% - 28px));}" +
88
- ".wg-todo-dock[data-pos=left],#wg-todo-dock[data-pos=left]{transform:translateX(0);}" +
86
+ "transition:opacity .25s ease,z-index 0s;}" +
89
87
  /* While a highlight ring is up: sit under the ring so captions stay readable. */
90
- ".wg-todo-dock.wg-todo-behind,#wg-todo-dock.wg-todo-behind{z-index:2147483643;opacity:.42;}" +
88
+ ".wg-todo-dock.wg-todo-behind,#wg-todo-dock.wg-todo-behind{z-index:var(--wg-z-focus);opacity:.42;}" +
91
89
  /* Collision tuck: shrink so a mid-page ring can still breathe. */
92
90
  ".wg-todo-dock[data-tucked=\"1\"],#wg-todo-dock[data-tucked=\"1\"]{max-height:min(28vh,220px);}" +
93
91
  /* Compact + cap: fold overflow rows; hover / data-expanded reveals more. */
@@ -103,7 +101,7 @@ export const RUNNER_EXTRA_CSS =
103
101
  // Always-on zoom HUD - bottom-right (top-left is crowded: todos + banners). Violet, matching the
104
102
  // rest of the overlay's design tokens - this used to be a lone amber/yellow badge, the one
105
103
  // surface that never got pulled into the shared palette when everything else did.
106
- "#wg-zoom-badge{position:fixed;z-index:2147483646;bottom:14px;right:14px;" +
104
+ "#wg-zoom-badge{z-index:var(--wg-z-chrome);" +
107
105
  "display:flex;align-items:center;gap:7px;padding:6px 11px 6px 8px;" +
108
106
  "border-radius:999px;background:" + SURFACE + ";color:#f0e8ff;" +
109
107
  "border:1px solid rgba(124,58,237,.55);box-shadow:0 6px 18px rgba(0,0,0,.4);" +
@@ -114,71 +112,17 @@ export const RUNNER_EXTRA_CSS =
114
112
  "#wg-zoom-badge .wg-zoom-ico svg{width:14px;height:14px;display:block;}" +
115
113
  "#wg-zoom-badge .wg-zoom-val{color:#c9a6ff;font-variant-numeric:tabular-nums;font-weight:800;min-width:3.2em;}" +
116
114
  "#wg-zoom-badge[data-zoomed=\"1\"]{border-color:#7C3AED;}" +
117
- // Network-tab overlay (real page traffic via network-capture.js + defineApiBlock's own
118
- // page.request calls) - bottom-left, the one corner free of the zoom HUD (bottom-right) and the
119
- // typing chip (bottom-center). Persistent (does not auto-fade) once any entry exists - this is a
120
- // real Postman/DevTools-style inspector meant to be reviewed, not a flash-and-gone toast.
121
- "#wg-network-log{position:fixed;z-index:2147483646;bottom:14px;left:14px;min-width:260px;" +
122
- "max-width:min(92vw,420px);max-height:min(60vh,420px);display:flex;flex-direction:column;" +
123
- "border-radius:10px;background:" + SURFACE + ";color:#f0e8ff;border:1px solid rgba(124,58,237,.45);" +
124
- "box-shadow:0 8px 22px rgba(0,0,0,.4);font:12px/1.4 ui-monospace,\"SF Mono\",Consolas,monospace;" +
125
- "opacity:0;transform:translateY(8px);transition:opacity .25s ease,transform .25s ease;overflow:hidden;}" +
126
- "#wg-network-log.wg-in{opacity:1;transform:translateY(0);}" +
127
- "#wg-network-log.wg-net-collapsed .wg-net-list{display:none;}" +
128
- "#wg-network-log .wg-net-header{display:flex;align-items:center;gap:6px;padding:8px 10px;" +
129
- "border-bottom:1px solid rgba(124,58,237,.25);cursor:default;flex:0 0 auto;}" +
130
- "#wg-network-log .wg-net-title{font:700 10px/1.2 system-ui,sans-serif;color:#c9a6ff;" +
131
- "letter-spacing:.06em;text-transform:uppercase;flex:1 1 auto;}" +
132
- "#wg-network-log .wg-net-count{font:700 10px/1.2 system-ui,sans-serif;color:#9CA3AF;" +
133
- "background:rgba(124,58,237,.22);border-radius:999px;padding:1px 7px;}" +
134
- "#wg-network-log .wg-net-collapse{flex:0 0 auto;width:18px;height:18px;border-radius:5px;border:none;" +
135
- "background:rgba(124,58,237,.28);color:#e8dcff;font:700 13px/1 system-ui,sans-serif;cursor:pointer;" +
136
- "pointer-events:auto;}" +
137
- "#wg-network-log .wg-net-collapse:hover{background:rgba(124,58,237,.42);}" +
138
- "#wg-network-log .wg-net-list{list-style:none;margin:0;padding:6px 8px;display:flex;" +
139
- "flex-direction:column;gap:2px;overflow-y:auto;pointer-events:auto;}" +
140
- "#wg-network-log .wg-net-row{display:flex;align-items:baseline;gap:7px;opacity:0;padding:3px 4px;" +
141
- "border-radius:5px;cursor:pointer;transform:translateX(-4px);transition:opacity .2s ease,transform .2s ease;}" +
142
- "#wg-network-log .wg-net-row:hover,#wg-network-log .wg-net-row.wg-net-open{background:rgba(124,58,237,.18);}" +
143
- "#wg-network-log .wg-net-row.wg-in{opacity:1;transform:translateX(0);}" +
144
- "#wg-network-log .wg-net-method{flex:0 0 auto;font-weight:800;font-size:10px;min-width:38px;}" +
145
- "#wg-network-log .wg-net-method-get{color:#3B82F6;}" +
146
- "#wg-network-log .wg-net-method-post{color:#22C55E;}" +
147
- "#wg-network-log .wg-net-method-put{color:#F97316;}" +
148
- "#wg-network-log .wg-net-method-delete{color:#EF4444;}" +
149
- "#wg-network-log .wg-net-method-other{color:#9CA3AF;}" +
150
- "#wg-network-log .wg-net-path{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;" +
151
- "white-space:nowrap;color:#e0e0f0;}" +
152
- "#wg-network-log .wg-net-status{flex:0 0 auto;font-weight:700;color:#22C55E;}" +
153
- "#wg-network-log .wg-net-row[data-ok=\"0\"] .wg-net-status{color:#EF4444;}" +
154
- "#wg-network-log .wg-net-ms{flex:0 0 auto;color:#9CA3AF;font-size:10px;}" +
155
- "#wg-network-log .wg-net-detail{list-style:none;background:rgba(0,0,0,.28);border-radius:6px;" +
156
- "padding:8px;margin:2px 0 6px;}" +
157
- "#wg-network-log .wg-net-detail-section{margin-bottom:8px;}" +
158
- "#wg-network-log .wg-net-detail-section:last-child{margin-bottom:0;}" +
159
- "#wg-network-log .wg-net-detail-section h4{margin:0 0 4px;font:700 9px/1.2 system-ui,sans-serif;" +
160
- "color:#9CA3AF;letter-spacing:.06em;text-transform:uppercase;}" +
161
- "#wg-network-log .wg-net-headers{width:100%;border-collapse:collapse;font-size:11px;}" +
162
- "#wg-network-log .wg-net-headers td{padding:1px 6px 1px 0;vertical-align:top;word-break:break-all;}" +
163
- "#wg-network-log .wg-net-headers td:first-child{color:#c9a6ff;white-space:nowrap;}" +
164
- "#wg-network-log .wg-net-body{margin:0;font-size:11px;white-space:pre-wrap;word-break:break-all;" +
165
- "max-height:160px;overflow-y:auto;color:#e0e0f0;}" +
166
- "#wg-network-log .wg-net-curl{color:#7dd3fc;}" +
167
- "#wg-network-log .wg-net-empty{font-size:11px;color:#6b7280;font-style:italic;}" +
168
- "#wg-network-log .wg-net-copy{margin-top:5px;padding:3px 9px;border-radius:5px;border:1px solid rgba(124,58,237,.5);" +
169
- "background:rgba(124,58,237,.22);color:#e8dcff;font:700 10px system-ui,sans-serif;cursor:pointer;}" +
170
- "#wg-network-log .wg-net-copy:hover{background:rgba(124,58,237,.38);}" +
171
115
  // Typing chip (method fills: username / password / etc.)
172
- "#wg-typing-badge{position:fixed;z-index:2147483646;bottom:88px;left:50%;transform:translateX(-50%);" +
116
+ "#wg-typing-badge{z-index:var(--wg-z-chrome);" +
173
117
  "display:flex;align-items:center;gap:8px;padding:8px 14px;" +
174
118
  "border-radius:999px;background:rgba(20,10,40,.95);color:#e0f2fe;" +
175
119
  "border:1px solid rgba(56,189,248,.55);box-shadow:0 8px 22px rgba(0,0,0,.4);" +
176
120
  "font:700 12px/1.2 system-ui,sans-serif;pointer-events:none;" +
177
- "opacity:0;transition:opacity .2s ease;}" +
121
+ "opacity:0;transition-duration:.2s;}" +
178
122
  "#wg-typing-badge.wg-in{opacity:1;}" +
179
123
  "#wg-typing-badge .wg-ty-dots{letter-spacing:.15em;color:#7dd3fc;}" +
180
124
  // Device toast + chip (0.13.2+) - icon toast on fixture change, then compact chip.
181
- "#wg-device-toast{position:fixed;z-index:2147483647;top:14px;right:14px;" +
125
+ "#wg-device-toast{z-index:var(--wg-z-toast);" +
182
126
  "display:flex;align-items:center;gap:10px;min-width:200px;max-width:min(92vw,320px);" +
183
127
  "padding:12px 14px;border-radius:14px;background:rgba(20,10,40,.96);color:#f0e8ff;" +
184
128
  "border:1px solid rgba(124,58,237,.55);box-shadow:0 10px 28px rgba(0,0,0,.4);" +
@@ -200,20 +144,21 @@ export const RUNNER_EXTRA_CSS =
200
144
  "#wg-device-toast .wg-dev-title{font:800 13px/1.2 system-ui,sans-serif;color:#fff;}" +
201
145
  "#wg-device-toast .wg-dev-sub{margin-top:3px;font:600 11px/1.3 system-ui,sans-serif;" +
202
146
  "color:#c9a6ff;letter-spacing:.02em;}" +
203
- "#wg-device-badge{position:fixed;z-index:2147483646;top:14px;right:14px;" +
147
+ "#wg-device-badge{z-index:var(--wg-z-chrome);" +
204
148
  "display:inline-flex;align-items:center;gap:6px;padding:6px 10px 6px 8px;" +
205
149
  "border-radius:999px;background:rgba(20,10,40,.92);color:#f0e8ff;" +
206
150
  "border:1px solid rgba(124,58,237,.5);font:700 11px/1.2 system-ui,sans-serif;" +
207
151
  "letter-spacing:.04em;text-transform:uppercase;pointer-events:none;" +
208
152
  "box-shadow:0 4px 14px rgba(0,0,0,.3);opacity:0;transform:translateY(-4px);" +
209
- "transition:opacity .3s ease,transform .35s cubic-bezier(.22,1,.36,1);}" +
153
+ "transition-duration:.35s;transition-timing-function:cubic-bezier(.22,1,.36,1);}" +
210
154
  "#wg-device-badge.wg-in{opacity:1;transform:translateY(0);}" +
211
155
  "#wg-device-badge[data-preset=mobile]{border-color:#3B82F6;}" +
212
156
  "#wg-device-badge[data-preset=tablet]{border-color:#22C55E;}" +
213
157
  "#wg-device-badge[data-preset=desktop]{border-color:#9CA3AF;}" +
214
158
  "#wg-device-badge .wg-dev-icon{width:16px;height:16px;display:flex;align-items:center;justify-content:center;}" +
215
159
  "#wg-device-badge .wg-dev-icon svg{width:14px;height:14px;display:block;}" +
216
- "#wg-device-badge .wg-dev-label{white-space:nowrap;}" +
160
+ "#wg-device-badge{overflow:hidden;}" +
161
+ "#wg-device-badge .wg-dev-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;}" +
217
162
  "#wg-cursor[data-touch=1]{width:28px;height:28px;}" +
218
163
  "#wg-todos,.wg-todos-list{list-style:none;margin:0 0 12px;padding:8px 10px;background:#0f0620;border-radius:8px;" +
219
164
  "border:1px solid #3a2a60;}" +
@@ -324,16 +269,14 @@ export const RUNNER_EXTRA_CSS =
324
269
  "#wg-panel button.wg-mini-next{background:#2a9d6f;border-color:#22855e;color:#fff;display:none;}" +
325
270
  "#wg-panel button.wg-mini-next:hover{background:#22855e;}" +
326
271
  "#wg-panel button.wg-mini-next:disabled{opacity:.55;cursor:default;}" +
327
- "#wg-panel.wg-collapsed{width:auto;max-width:92vw;padding:8px 12px;max-height:none;overflow:hidden;}" +
272
+ "#wg-panel.wg-collapsed{width:auto;padding:8px 12px;overflow:hidden;}" +
328
273
  "#wg-panel.wg-collapsed .wg-body{display:none;}" +
329
274
  "#wg-panel.wg-collapsed .wg-chrome{margin:0;}" +
330
275
  "#wg-panel.wg-collapsed .wg-chrome-title{font:700 13px/1.25 system-ui,sans-serif;color:#fff;" +
331
276
  "letter-spacing:0;text-transform:none;}" +
332
277
  "#wg-panel.wg-collapsed button.wg-mini-next.wg-mini-next-show{display:inline-block;}" +
333
278
  "@media (max-width:640px){" +
334
- "#wg-panel{left:8px;right:8px;bottom:8px;transform:none;max-width:none;width:auto;" +
335
- "max-height:min(55vh,calc(100vh - 16px));padding:12px 14px;border-radius:12px;}" +
336
- "#wg-panel.wg-collapsed{left:50%;right:auto;transform:translateX(-50%);width:auto;}" +
279
+ "#wg-panel{width:auto;padding:12px 14px;border-radius:12px;}" +
337
280
  "#wg-episodes{overflow-x:auto;-webkit-overflow-scrolling:touch;flex-wrap:nowrap;}" +
338
281
  "#wg-panel .wg-narration{font-size:13px;}" +
339
282
  "#wg-panel button{width:100%;}" +
@@ -49,7 +49,7 @@ export function createOverlayStage(page, { todoDockRef, deviceRef }) {
49
49
  if (!docks.length) {
50
50
  // Empty registry: do NOT clear the DOM. renderBefore/After may have
51
51
  // just painted from lastTodoDock (keep). Clearing here made todos
52
- // vanish until the next ctx.todos() set (PIA #15).
52
+ // vanish until the next ctx.ui.todos() set (PIA #15).
53
53
  return;
54
54
  }
55
55
  await page
@@ -25,6 +25,9 @@ import { ignorePageGone } from "../../engine/page-gone.js";
25
25
  export async function installOverlay(page, title, ui, caption, networkTabVisible) {
26
26
  // ring/cursor/banner/dock come from the ONE shared bundle every surface installs (ui/css/*.css).
27
27
  await ensureShadowRoot(page);
28
+ // Runner-only chrome (step panel, zoom/typing/device chips) - separate key, no collision. First, so nothing below
29
+ // builds chrome ahead of its CSS (an element painted unstyled for a moment, then restyled).
30
+ await page.evaluate(([css, key]) => __wgCss(css, key), [RUNNER_EXTRA_CSS, "wg-runner-css"]).catch(ignorePageGone);
28
31
  // Idempotent (guarded on page.__wgNetworkCaptureInstalled) - safe to call every step even though
29
32
  // installOverlay itself runs every step; the page.on() listeners this installs are attached to
30
33
  // the Page object, not any one document, so they already survive navigations on their own.
@@ -46,8 +49,6 @@ export async function installOverlay(page, title, ui, caption, networkTabVisible
46
49
  if (networkTabVisible !== undefined) {
47
50
  await page.evaluate(setNetworkTabVisible, networkTabVisible).catch(ignorePageGone);
48
51
  }
49
- // Runner-only chrome (step panel, zoom/typing/device chips) - separate key, no collision.
50
- await page.evaluate(([css, key]) => __wgCss(css, key), [RUNNER_EXTRA_CSS, "wg-runner-css"]).catch(ignorePageGone);
51
52
  await page.addStyleTag({ content: HOST_CSS }).catch(ignorePageGone);
52
53
  // Default top-left; override with WAYGRAPH_TITLE_POS=left|center|right.
53
54
  // Click cycles left -> center -> right (persisted in localStorage so a
@@ -43,17 +43,17 @@ export function phaseToOverlayFixtures(phase, holdMs = 6000) {
43
43
  export function describeOverlayFixtures(tag, f) {
44
44
  const out = [];
45
45
  if (f.title)
46
- out.push(`${tag} ctx.title(${JSON.stringify(f.title)})${f.titlePos ? ` pos=${f.titlePos}` : ""}`);
46
+ out.push(`${tag} ctx.ui.title(${JSON.stringify(f.title)})${f.titlePos ? ` pos=${f.titlePos}` : ""}`);
47
47
  if (f.bannerUi)
48
- out.push(`${tag} ctx.bannerUi(${JSON.stringify(f.bannerUi)})`);
48
+ out.push(`${tag} ctx.ui.bannerUi(${JSON.stringify(f.bannerUi)})`);
49
49
  if (f.todos?.length)
50
- out.push(`${tag} ctx.todos n=${f.todos.length} index=${f.todoIndex ?? 0}${f.todoPos ? ` pos=${f.todoPos}` : ""}`);
50
+ out.push(`${tag} ctx.ui.todos n=${f.todos.length} index=${f.todoIndex ?? 0}${f.todoPos ? ` pos=${f.todoPos}` : ""}`);
51
51
  if (f.todoUi)
52
52
  out.push(`${tag} todoUi ${JSON.stringify(f.todoUi)}`);
53
53
  if (f.device)
54
- out.push(`${tag} ctx.device(${typeof f.device === "string" ? f.device : f.device.preset ?? "custom"})`);
54
+ out.push(`${tag} ctx.ui.device(${typeof f.device === "string" ? f.device : f.device.preset ?? "custom"})`);
55
55
  if (f.zoom)
56
- out.push(`${tag} ctx.zoom(${f.zoom})`);
56
+ out.push(`${tag} ctx.ui.zoom(${f.zoom})`);
57
57
  for (const r of f.rings ?? [])
58
58
  out.push(`${tag} ring ${r.selector} tone=${r.tone ?? "planned"} label=${JSON.stringify(r.label)}`);
59
59
  return out;