waygraph 0.15.4 → 0.15.7

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.
@@ -677,11 +677,21 @@ export class AutoSession {
677
677
  */
678
678
  async applyHighlight(fixtures) {
679
679
  this.page = await ensureLivePage(this.context, this.page, this.startUrl);
680
- const label = fixtures.clear === true
681
- ? "clear fixtures"
682
- : `${fixtures.rings?.length ?? 0} ring(s)` +
683
- (fixtures.todos?.length ? `, ${fixtures.todos.length} todo(s)` : "");
684
- await showPilotActivity(this.page, `Highlight: ${label}`);
680
+ const detail = fixtures.clear === true
681
+ ? "clear"
682
+ : [
683
+ fixtures.rings?.length ? `${fixtures.rings.length} ring(s)` : null,
684
+ fixtures.todos?.length ? `${fixtures.todos.length} todo(s)` : null,
685
+ fixtures.zoom && fixtures.zoom > 1.001 ? `zoom ${fixtures.zoom}` : null,
686
+ fixtures.device
687
+ ? `device ${typeof fixtures.device === "string" ? fixtures.device : fixtures.device.preset || "custom"}`
688
+ : null,
689
+ fixtures.rings?.some((r) => r.focus) ? "focus" : null,
690
+ ]
691
+ .filter(Boolean)
692
+ .join(", ") || "fixtures";
693
+ // Same bottom-left activity toast as Running (Dom)/(Block) - agent narration.
694
+ await showPilotActivity(this.page, `Highlighting: ${detail}`);
685
695
  try {
686
696
  const { painted, missing } = await showPilotFixtures(this.page, fixtures);
687
697
  const menu = await this.currentMenu();
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import type { Page } from "@playwright/test";
20
20
  import type { SessionSnapshot } from "./auto-session.js";
21
- import { type HighlightTone } from "./highlights.js";
21
+ import { type HighlightTone, type DevicePreset, type DeviceState } from "./highlights.js";
22
22
  /**
23
23
  * Bottom-left transient toast, separate from the persistent bottom-right
24
24
  * badge - real, direct user request: something that visibly flags "a
@@ -61,13 +61,30 @@ export declare function showPilotVision(page: Page, selector: string, label: str
61
61
  export type PilotFixtureRing = {
62
62
  selector: string;
63
63
  label: string;
64
- tone?: HighlightTone;
65
- size?: "sm" | "md" | "lg";
66
- weight?: "normal" | "bold";
64
+ /** Extra caption line (demo `detail`). */
65
+ detail?: string;
66
+ /** Short badge, e.g. AC / BUG / GATE. */
67
+ tag?: string;
68
+ /** Any {@link HighlightTone} or alias (`error`/`blue`/…). Normalized on paint. */
69
+ tone?: HighlightTone | string;
70
+ size?: "sm" | "md" | "lg" | string;
71
+ weight?: "normal" | "bold" | string;
72
+ /** Caption text color (CSS). Overrides tone label color when set. */
73
+ color?: string;
74
+ /**
75
+ * Camera zoom while this ring is up (demo parity: scroll + zoom badge;
76
+ * does not CSS-scale the page). Typical `1.25`..`2`.
77
+ */
78
+ zoom?: number;
79
+ /** When false, keep zoom after this paint / holdMs. Default true. */
80
+ zoomOut?: boolean;
81
+ /** Spotlight: dim the rest of the page around this target. */
82
+ focus?: boolean;
67
83
  };
68
84
  /**
69
85
  * Agent-sent highlight fixtures for a live Pilot / auto session.
70
86
  * Painted by {@link showPilotFixtures}; driven via IPC `op: "highlight"`.
87
+ * Mirrors demo stub surface: rings, todos, zoom, focus, device.
71
88
  */
72
89
  export type PilotHighlightFixtures = {
73
90
  /** Replace current fixture rings with these (empty + no todos = clear). */
@@ -78,18 +95,36 @@ export type PilotHighlightFixtures = {
78
95
  todoIndex?: number;
79
96
  /** Optional title above the todo list. */
80
97
  todoTitle?: string;
98
+ /** Todo dock side. Default `right`. */
99
+ todoPos?: "left" | "right";
81
100
  /**
82
101
  * How long fixtures stay visible (ms). `0` = until the next highlight /
83
- * clear. Default `12000`. Vision rings (`showPilotVision`) stay separate.
102
+ * clear. Default `30000` (30s) unless specified.
84
103
  */
85
104
  holdMs?: number;
86
- /** Drop every agent fixture ring/todo immediately. */
105
+ /**
106
+ * Camera zoom toward a selector (demo parity: scrollIntoView + zoom badge).
107
+ * Defaults the zoom target to {@link zoomSelector}, else the last ring's
108
+ * selector (or a ring that sets its own `zoom`). Cleared on `clear: true`
109
+ * or when holdMs expires (if zoomOut).
110
+ */
111
+ zoom?: number;
112
+ /** Element to zoom toward when {@link zoom} is set (defaults to last ring). */
113
+ zoomSelector?: string;
114
+ /** When false, keep zoom after fixtures clear/expire. Default true. */
115
+ zoomOut?: boolean;
116
+ /**
117
+ * Viewport / device fixture (`mobile` | `tablet` | `desktop`, or a full
118
+ * {@link DeviceState}). Applied via Playwright `setViewportSize`.
119
+ */
120
+ device?: DevicePreset | DeviceState;
121
+ /** Drop every agent fixture ring/todo/focus/zoom immediately. */
87
122
  clear?: boolean;
88
123
  };
89
124
  /**
90
125
  * Paint (or clear) agent-authored highlight fixtures on the live page.
91
- * Multi-ring + optional todo strip - what Block `stubBefore` does in
92
- * `waygraph demo`, available to the Pilot agent without going through demo.
126
+ * Multi-ring + optional todo strip + zoom/focus/device - the Pilot equivalent
127
+ * of Block `stubBefore` narration in `waygraph demo`.
93
128
  * Does not import `cli.ts` (see file header).
94
129
  */
95
130
  export declare function showPilotFixtures(page: Page, fixtures: PilotHighlightFixtures): Promise<{
@@ -1,4 +1,4 @@
1
- import { WAYGRAPH_RING_CSS } from "./highlights.js";
1
+ import { WAYGRAPH_RING_CSS, normalizeHighlightTone, normalizeHighlightSize, normalizeHighlightWeight, formatHighlightCaption, resolveDeviceState, } from "./highlights.js";
2
2
  // Real, direct user request: this overlay's own colors were an improvised
3
3
  // purple/white scheme (#a78bfa / #1a1033) that didn't match anything else -
4
4
  // "our design here seems like barely shares with the ui on the stepper
@@ -88,16 +88,41 @@ const OVERLAY_CSS = `
88
88
  .wg-pilot-fx-label[data-size=lg]{font-size:16px;padding:8px 14px;}
89
89
  .wg-pilot-fx-label[data-weight=bold]{font-weight:800;}
90
90
  #wg-pilot-fx-todos{
91
- position:fixed;z-index:2147483645;top:72px;left:14px;max-width:280px;
91
+ position:fixed;z-index:2147483645;top:72px;max-width:280px;
92
92
  font:12px/1.4 ui-sans-serif,system-ui,sans-serif;color:#fff;
93
93
  background:rgba(20,10,40,.94);border-radius:10px;padding:10px 12px;
94
94
  box-shadow:0 2px 10px rgba(0,0,0,.3);pointer-events:none;
95
95
  }
96
+ #wg-pilot-fx-todos[data-pos=left]{left:14px;right:auto;}
97
+ #wg-pilot-fx-todos[data-pos=right]{right:14px;left:auto;}
96
98
  #wg-pilot-fx-todos .wg-pilot-fx-todo-title{font-weight:700;color:#c9a6ff;margin-bottom:6px;}
97
99
  #wg-pilot-fx-todos ol{margin:0;padding-left:18px;}
98
100
  #wg-pilot-fx-todos li{margin:3px 0;color:#ddd;}
99
101
  #wg-pilot-fx-todos li.wg-pilot-fx-todo-done{color:#888;text-decoration:line-through;}
100
102
  #wg-pilot-fx-todos li.wg-pilot-fx-todo-now{color:#fff;font-weight:700;}
103
+ /* Spotlight (demo focus:true) - dim page, cutout around target. */
104
+ #wg-pilot-fx-focus{position:fixed;z-index:2147483644;pointer-events:none;
105
+ border-radius:12px;box-shadow:0 0 0 9999px rgba(8,4,20,.62);
106
+ opacity:0;transition:opacity .3s ease,left .3s ease,top .3s ease,width .3s ease,height .3s ease;}
107
+ #wg-pilot-fx-focus.wg-in{opacity:1;}
108
+ /* Zoom HUD (demo parity - badge only; no CSS scale of the page). */
109
+ #wg-pilot-fx-zoom{position:fixed;z-index:2147483646;bottom:14px;right:14px;
110
+ display:flex;align-items:center;gap:7px;padding:6px 11px 6px 8px;
111
+ border-radius:999px;background:rgba(20,10,40,.94);color:#f0e8ff;
112
+ border:1px solid rgba(250,204,21,.6);box-shadow:0 6px 18px rgba(0,0,0,.4);
113
+ font:700 12px/1.2 system-ui,sans-serif;pointer-events:none;}
114
+ #wg-pilot-fx-zoom .wg-pilot-fx-zoom-ico{width:18px;height:18px;display:flex;align-items:center;justify-content:center;
115
+ border-radius:6px;background:rgba(250,204,21,.22);}
116
+ #wg-pilot-fx-zoom .wg-pilot-fx-zoom-ico svg{width:14px;height:14px;display:block;}
117
+ #wg-pilot-fx-zoom .wg-pilot-fx-zoom-val{color:#fde68a;font-variant-numeric:tabular-nums;font-weight:800;min-width:3.2em;}
118
+ #wg-pilot-fx-zoom[data-zoomed="1"]{border-color:#fbbf24;}
119
+ #wg-pilot-fx-device{position:fixed;z-index:2147483646;top:14px;right:14px;
120
+ padding:8px 12px;border-radius:10px;background:rgba(20,10,40,.94);color:#f0e8ff;
121
+ border:1px solid rgba(124,58,237,.55);font:700 12px/1.2 system-ui,sans-serif;
122
+ pointer-events:none;box-shadow:0 6px 18px rgba(0,0,0,.35);}
123
+ #wg-pilot-fx-device[data-preset=mobile]{border-color:#3B82F6;}
124
+ #wg-pilot-fx-device[data-preset=tablet]{border-color:#22C55E;}
125
+ #wg-pilot-fx-device[data-preset=desktop]{border-color:#9CA3AF;}
101
126
  ` + WAYGRAPH_RING_CSS;
102
127
  /** Idempotent - safe to call before every update, matching narrate mode's own precedent. */
103
128
  async function ensureInstalled(page) {
@@ -254,8 +279,8 @@ export async function showPilotVision(page, selector, label, tone = "auto") {
254
279
  }
255
280
  /**
256
281
  * Paint (or clear) agent-authored highlight fixtures on the live page.
257
- * Multi-ring + optional todo strip - what Block `stubBefore` does in
258
- * `waygraph demo`, available to the Pilot agent without going through demo.
282
+ * Multi-ring + optional todo strip + zoom/focus/device - the Pilot equivalent
283
+ * of Block `stubBefore` narration in `waygraph demo`.
259
284
  * Does not import `cli.ts` (see file header).
260
285
  */
261
286
  export async function showPilotFixtures(page, fixtures) {
@@ -263,25 +288,168 @@ export async function showPilotFixtures(page, fixtures) {
263
288
  const holdMs = fixtures.clear === true
264
289
  ? 0
265
290
  : fixtures.holdMs === undefined
266
- ? 12_000
291
+ ? 30_000
267
292
  : Math.max(0, fixtures.holdMs);
268
- const rings = fixtures.clear ? [] : (fixtures.rings ?? []);
293
+ const rings = (fixtures.clear ? [] : (fixtures.rings ?? [])).map((r) => {
294
+ const tone = normalizeHighlightTone(r.tone);
295
+ const size = normalizeHighlightSize(r.size);
296
+ const weight = normalizeHighlightWeight(r.weight);
297
+ const caption = { label: r.label, tone };
298
+ if (r.detail)
299
+ caption.detail = r.detail;
300
+ if (r.tag)
301
+ caption.tag = r.tag;
302
+ const label = formatHighlightCaption(caption);
303
+ return {
304
+ selector: r.selector,
305
+ label,
306
+ tone,
307
+ size,
308
+ weight,
309
+ color: typeof r.color === "string" && r.color.trim() ? r.color.trim() : "",
310
+ zoom: typeof r.zoom === "number" && Number.isFinite(r.zoom) && r.zoom > 0
311
+ ? r.zoom
312
+ : undefined,
313
+ zoomOut: r.zoomOut,
314
+ focus: r.focus === true,
315
+ };
316
+ });
269
317
  const todos = fixtures.clear ? [] : (fixtures.todos ?? []);
270
318
  const todoIndex = fixtures.todoIndex ?? 0;
271
319
  const todoTitle = fixtures.todoTitle ?? "Plan";
320
+ const todoPos = fixtures.todoPos === "left" ? "left" : "right";
321
+ // Effective zoom: phase zoom, else last ring that authored zoom.
322
+ let zoom = fixtures.clear === true
323
+ ? 1
324
+ : typeof fixtures.zoom === "number" && Number.isFinite(fixtures.zoom)
325
+ ? fixtures.zoom
326
+ : 1;
327
+ let zoomSelector = fixtures.zoomSelector ??
328
+ (rings.length > 0 ? rings[rings.length - 1].selector : undefined);
329
+ let zoomOut = fixtures.zoomOut !== false;
330
+ if (!fixtures.clear && zoom <= 1.001) {
331
+ for (let i = rings.length - 1; i >= 0; i--) {
332
+ const r = rings[i];
333
+ if (r.zoom !== undefined && r.zoom > 1.001) {
334
+ zoom = r.zoom;
335
+ zoomSelector = r.selector;
336
+ if (r.zoomOut !== undefined)
337
+ zoomOut = r.zoomOut !== false;
338
+ break;
339
+ }
340
+ }
341
+ }
342
+ else if (!fixtures.clear) {
343
+ // Phase zoomOut can still be overridden by the targeted ring.
344
+ const hit = rings.find((r) => r.selector === zoomSelector);
345
+ if (hit?.zoomOut !== undefined)
346
+ zoomOut = hit.zoomOut !== false;
347
+ }
348
+ // Device viewport (Node-side) before paint so rings land on the new size.
349
+ if (fixtures.clear === true) {
350
+ // Leave viewport alone on clear - only drop overlays.
351
+ }
352
+ else if (fixtures.device !== undefined) {
353
+ const d = resolveDeviceState(fixtures.device);
354
+ if (d) {
355
+ await page
356
+ .setViewportSize({
357
+ width: Math.max(200, Math.floor(d.viewport.width)),
358
+ height: Math.max(200, Math.floor(d.viewport.height)),
359
+ })
360
+ .catch(() => { });
361
+ }
362
+ }
363
+ const deviceLabel = fixtures.clear === true
364
+ ? ""
365
+ : fixtures.device === undefined
366
+ ? ""
367
+ : typeof fixtures.device === "string"
368
+ ? fixtures.device
369
+ : fixtures.device.preset || "device";
272
370
  return page
273
- .evaluate(({ rings, todos, todoIndex, todoTitle, holdMs }) => {
371
+ .evaluate(({ rings, todos, todoIndex, todoTitle, todoPos, holdMs, zoom, zoomSelector, zoomOut, clearAll, deviceLabel, }) => {
274
372
  const w = window;
275
373
  if (w.__wgPilotFxHideTimer)
276
374
  clearTimeout(w.__wgPilotFxHideTimer);
375
+ const clearFocus = () => {
376
+ const veil = document.getElementById("wg-pilot-fx-focus");
377
+ if (!veil)
378
+ return;
379
+ veil.classList.remove("wg-in");
380
+ setTimeout(() => {
381
+ const v = document.getElementById("wg-pilot-fx-focus");
382
+ if (v && !v.classList.contains("wg-in"))
383
+ v.remove();
384
+ }, 320);
385
+ };
386
+ const applyFocus = (box) => {
387
+ let veil = document.getElementById("wg-pilot-fx-focus");
388
+ if (!veil) {
389
+ veil = document.createElement("div");
390
+ veil.id = "wg-pilot-fx-focus";
391
+ document.documentElement.appendChild(veil);
392
+ }
393
+ const pad = 10;
394
+ veil.style.left = `${Math.max(0, box.x - pad)}px`;
395
+ veil.style.top = `${Math.max(0, box.y - pad)}px`;
396
+ veil.style.width = `${Math.max(8, box.width + pad * 2)}px`;
397
+ veil.style.height = `${Math.max(8, box.height + pad * 2)}px`;
398
+ void veil.offsetWidth;
399
+ veil.classList.add("wg-in");
400
+ };
401
+ const setZoomBadge = (scale) => {
402
+ const level = Number.isFinite(scale) && scale > 0 ? scale : 1;
403
+ let badge = document.getElementById("wg-pilot-fx-zoom");
404
+ if (!badge) {
405
+ badge = document.createElement("div");
406
+ badge.id = "wg-pilot-fx-zoom";
407
+ document.documentElement.appendChild(badge);
408
+ }
409
+ const zoomed = level > 1.001 || level < 0.999;
410
+ badge.dataset.zoomed = zoomed ? "1" : "0";
411
+ badge.innerHTML =
412
+ '<span class="wg-pilot-fx-zoom-ico"><svg viewBox="0 0 24 24" fill="none" stroke="#fde68a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5"/></svg></span>' +
413
+ `<span class="wg-pilot-fx-zoom-val">${level.toFixed(2)}\u00d7</span>`;
414
+ };
415
+ const clearZoom = () => {
416
+ // Undo any leftover CSS-scale from older Pilot zoom path.
417
+ const root = document.documentElement;
418
+ root.style.removeProperty("transform");
419
+ root.style.removeProperty("transform-origin");
420
+ root.style.removeProperty("transition");
421
+ document.getElementById("wg-pilot-fx-zoom")?.remove();
422
+ };
423
+ const setDeviceChip = (label) => {
424
+ document.getElementById("wg-pilot-fx-device")?.remove();
425
+ if (!label)
426
+ return;
427
+ const chip = document.createElement("div");
428
+ chip.id = "wg-pilot-fx-device";
429
+ chip.dataset.preset = label;
430
+ chip.textContent = `device \u00b7 ${label}`;
431
+ document.documentElement.appendChild(chip);
432
+ };
433
+ w.__wgPilotFxZoomOutOnHide = zoomOut;
277
434
  const clearFx = () => {
278
435
  document.querySelectorAll(".wg-pilot-fx-ring, .wg-pilot-fx-label").forEach((el) => el.remove());
279
436
  document.getElementById("wg-pilot-fx-todos")?.remove();
437
+ clearFocus();
438
+ if (w.__wgPilotFxZoomOutOnHide !== false || clearAll)
439
+ clearZoom();
440
+ if (clearAll) {
441
+ document.getElementById("wg-pilot-fx-device")?.remove();
442
+ clearZoom();
443
+ }
280
444
  };
281
445
  w.__wgPilotFxClear = clearFx;
282
446
  clearFx();
447
+ if (clearAll) {
448
+ return { painted: 0, missing: [] };
449
+ }
283
450
  const missing = [];
284
451
  let painted = 0;
452
+ let focusBox = null;
285
453
  for (const ring of rings) {
286
454
  const el = document.querySelector(ring.selector);
287
455
  if (!el) {
@@ -293,14 +461,11 @@ export async function showPilotFixtures(page, fixtures) {
293
461
  missing.push(ring.selector);
294
462
  continue;
295
463
  }
296
- const tone = ring.tone || "planned";
297
- const size = ring.size === "sm" || ring.size === "lg" ? ring.size : "md";
298
- const weight = ring.weight === "bold" ? "bold" : "normal";
299
- const pad = size === "sm" ? 3 : size === "lg" ? 10 : 6;
464
+ const pad = ring.size === "sm" ? 3 : ring.size === "lg" ? 10 : 6;
300
465
  const ringEl = document.createElement("div");
301
466
  ringEl.className = "wg-pilot-fx-ring";
302
- ringEl.dataset.tone = tone;
303
- ringEl.dataset.size = size;
467
+ ringEl.dataset.tone = ring.tone;
468
+ ringEl.dataset.size = ring.size;
304
469
  ringEl.style.left = `${rect.left - pad}px`;
305
470
  ringEl.style.top = `${rect.top - pad}px`;
306
471
  ringEl.style.width = `${Math.max(4, rect.width + pad * 2)}px`;
@@ -308,11 +473,14 @@ export async function showPilotFixtures(page, fixtures) {
308
473
  ringEl.style.opacity = "1";
309
474
  const labelEl = document.createElement("div");
310
475
  labelEl.className = "wg-pilot-fx-label";
311
- labelEl.dataset.tone = tone;
312
- labelEl.dataset.size = size;
313
- labelEl.dataset.weight = weight;
476
+ labelEl.dataset.tone = ring.tone;
477
+ labelEl.dataset.size = ring.size;
478
+ labelEl.dataset.weight = ring.weight;
314
479
  labelEl.textContent = ring.label;
315
480
  labelEl.style.opacity = "1";
481
+ if (ring.color) {
482
+ labelEl.style.color = ring.color;
483
+ }
316
484
  document.documentElement.appendChild(ringEl);
317
485
  document.documentElement.appendChild(labelEl);
318
486
  const lw = labelEl.offsetWidth;
@@ -331,10 +499,14 @@ export async function showPilotFixtures(page, fixtures) {
331
499
  labelEl.style.left = `${labelLeft}px`;
332
500
  labelEl.style.top = `${labelTop}px`;
333
501
  painted += 1;
502
+ if (ring.focus) {
503
+ focusBox = { x: rect.left, y: rect.top, width: rect.width, height: rect.height };
504
+ }
334
505
  }
335
506
  if (todos.length > 0) {
336
507
  const dock = document.createElement("div");
337
508
  dock.id = "wg-pilot-fx-todos";
509
+ dock.dataset.pos = todoPos;
338
510
  const title = document.createElement("div");
339
511
  title.className = "wg-pilot-fx-todo-title";
340
512
  title.textContent = todoTitle;
@@ -352,11 +524,50 @@ export async function showPilotFixtures(page, fixtures) {
352
524
  dock.appendChild(ol);
353
525
  document.documentElement.appendChild(dock);
354
526
  }
355
- if (holdMs > 0 && (painted > 0 || todos.length > 0)) {
527
+ if (focusBox)
528
+ applyFocus(focusBox);
529
+ if (zoom > 1.001 && zoomSelector) {
530
+ const target = document.querySelector(zoomSelector);
531
+ if (target) {
532
+ target.scrollIntoView({
533
+ block: "center",
534
+ inline: "center",
535
+ behavior: "instant",
536
+ });
537
+ // Re-apply focus after scroll so the cutout tracks the new rect.
538
+ if (focusBox) {
539
+ const r = target.getBoundingClientRect();
540
+ applyFocus({ x: r.left, y: r.top, width: r.width, height: r.height });
541
+ }
542
+ setZoomBadge(zoom);
543
+ }
544
+ else {
545
+ missing.push(zoomSelector);
546
+ }
547
+ }
548
+ else if (zoom > 1.001) {
549
+ setZoomBadge(zoom);
550
+ }
551
+ if (deviceLabel)
552
+ setDeviceChip(deviceLabel);
553
+ if (holdMs > 0 &&
554
+ (painted > 0 || todos.length > 0 || zoom > 1.001 || !!deviceLabel)) {
356
555
  w.__wgPilotFxHideTimer = setTimeout(() => clearFx(), holdMs);
357
556
  }
358
557
  return { painted, missing };
359
- }, { rings, todos, todoIndex, todoTitle, holdMs })
558
+ }, {
559
+ rings,
560
+ todos,
561
+ todoIndex,
562
+ todoTitle,
563
+ todoPos,
564
+ holdMs,
565
+ zoom,
566
+ zoomSelector: zoomSelector ?? "",
567
+ zoomOut,
568
+ clearAll: fixtures.clear === true,
569
+ deviceLabel,
570
+ })
360
571
  .catch(() => ({ painted: 0, missing: rings.map((r) => r.selector) }));
361
572
  }
362
573
  /**
@@ -0,0 +1,8 @@
1
+ {
2
+ "sessionId": "03710cfc",
3
+ "socketPath": "/tmp/waygraph-auto/03710cfc.sock",
4
+ "pid": 255515,
5
+ "projectDir": "/home/dan/Desktop/Work/zsign/services/waygraph/examples/saucedemo",
6
+ "startedAt": "2026-09-21T09:37:58.750Z",
7
+ "headless": false
8
+ }
File without changes
File without changes
@@ -0,0 +1,4 @@
1
+ (node:223602) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
2
+ (Use `node --trace-warnings ...` to show where the warning was created)
3
+ (node:223602) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
4
+ (Use `node --trace-warnings ...` to show where the warning was created)
File without changes
@@ -0,0 +1,8 @@
1
+ {
2
+ "sessionId": "605d965b",
3
+ "socketPath": "/tmp/waygraph-auto/605d965b.sock",
4
+ "pid": 247729,
5
+ "projectDir": "/home/dan/Desktop/Work/zsign/services/waygraph/examples/saucedemo",
6
+ "startedAt": "2026-09-21T09:34:04.318Z",
7
+ "headless": false
8
+ }
File without changes
File without changes
@@ -0,0 +1,8 @@
1
+ {
2
+ "sessionId": "6b667734",
3
+ "socketPath": "/tmp/waygraph-auto/6b667734.sock",
4
+ "pid": 245167,
5
+ "projectDir": "/home/dan/Desktop/Work/zsign/services/waygraph/examples/saucedemo",
6
+ "startedAt": "2026-09-21T09:33:13.876Z",
7
+ "headless": false
8
+ }
File without changes
@@ -0,0 +1,4 @@
1
+ (node:254673) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
2
+ (Use `node --trace-warnings ...` to show where the warning was created)
3
+ (node:254673) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
4
+ (Use `node --trace-warnings ...` to show where the warning was created)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.15.4",
3
+ "version": "0.15.7",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",
@@ -27,15 +27,25 @@ sending picks.
27
27
 
28
28
  Block `stubBefore` / `withHighlightFixtures` only paint in `waygraph demo`. In Pilot they
29
29
  land in `auto trace` as metadata but do **not** draw on the page. When a human is watching
30
- a headful session (or you want to narrate intent before a send), paint yourself:
30
+ a headful session (or you want to narrate intent before a send), paint yourself with the
31
+ same fixture surface:
31
32
 
32
33
  ```
33
- waygraph auto highlight <sessionId> '{"rings":[{"selector":"#user-name","label":"Username","tone":"planned"},{"selector":"#password","label":"Password","tone":"info"}],"todos":["Fill username","Fill password","Submit"],"todoIndex":0,"holdMs":0}'
34
+ waygraph auto highlight <sessionId> '{"rings":[{"selector":"#user-name","label":"Username","tone":"planned","focus":true},{"selector":"#password","label":"Password","tone":"info","detail":"secret"}],"todos":["Fill username","Fill password","Submit"],"todoIndex":0,"zoom":1.5,"holdMs":0}'
35
+ waygraph auto highlight <sessionId> '{"device":"mobile","rings":[{"selector":".inventory_item:last-child","label":"Last item","zoom":1.6,"focus":true}],"holdMs":30000}'
34
36
  waygraph auto highlight <sessionId> '{"clear":true}'
35
37
  ```
36
38
 
37
- - `tone`: `planned` (purple, default) | `auto` | `info` | `warning` | `danger` | `success` (pilot `orange` is for `auto dom` vision only)
38
- - `holdMs`: ms to keep fixtures; `0` = until the next highlight / clear (default 12000)
39
+ Supported fields (demo parity):
40
+ - `rings[]`: `selector`, `label`, `detail`, `tag`, `tone`, `size`, `weight`, `color`, `zoom`, `zoomOut`, `focus`
41
+ - `todos` / `todoIndex` / `todoTitle` / `todoPos` (`right` default | `left`)
42
+ - `zoom` / `zoomSelector` / `zoomOut` - scroll + zoom badge (not CSS page scale)
43
+ - `device`: `mobile` | `tablet` | `desktop` (Playwright viewport)
44
+ - `holdMs`: ms to keep fixtures; `0` = until the next highlight / clear (default 30000)
45
+ - `clear`: true drops rings/todos/focus/zoom badge
46
+
47
+ - `tone`: all demo tones - `planned` | `auto` | `info` | `warning` | `danger` | `success` | `orange` (aliases like `error`/`blue`/`green` work too)
48
+ - Bottom-left toast shows `Highlighting: …` (same strip as `Running (Dom): …`)
39
49
  - Missing selectors are listed in the JSON response (`missing`) - not a hard failure
40
50
  - Does not replace `auto send` - paint, then run the real Block
41
51