waygraph 0.15.32 → 0.15.33

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 (56) hide show
  1. package/dist/highlights/run-phase.js +2 -0
  2. package/dist/highlights/stub-ctx.js +36 -2
  3. package/dist/highlights/types.d.ts +35 -3
  4. package/dist/index.d.ts +1 -1
  5. package/dist/pilot-overlay/fixtures.d.ts +9 -1
  6. package/dist/pilot-overlay/fixtures.js +38 -1
  7. package/dist/pilot-overlay/overlay-fixtures.d.ts +9 -0
  8. package/dist/pilot-overlay/overlay-fixtures.js +45 -0
  9. package/dist/pilot-overlay/shell.js +4 -23
  10. package/dist/pilot-overlay.d.ts +1 -0
  11. package/dist/pilot-overlay.js +1 -0
  12. package/dist/runner/banner-log.js +62 -0
  13. package/dist/runner/demo-log.js +9 -1
  14. package/dist/runner/inpage/banner.js +56 -1
  15. package/dist/runner/inpage/core.js +2 -0
  16. package/dist/runner/overlay-css.js +6 -27
  17. package/dist/runner/overlay-install.js +7 -4
  18. package/dist/runner/overlay-present.d.ts +8 -0
  19. package/dist/runner/overlay-present.js +57 -0
  20. package/dist/runner/overlay-stage.js +105 -0
  21. package/dist/runner/step-mode.js +35 -126
  22. package/dist/runner/step-panels.js +3 -3
  23. package/dist/step-overlay.js +1 -3
  24. package/dist/ui/overlay.css +209 -0
  25. package/dist/ui/shadow.d.ts +7 -2
  26. package/dist/ui/shadow.js +12 -4
  27. package/dist/ui/stylesheet.d.ts +7 -0
  28. package/dist/ui/stylesheet.js +18 -0
  29. package/dist/verbose-log.d.ts +1 -0
  30. package/dist/verbose-log.js +16 -0
  31. package/examples/saucedemo/.waygraph-auto/03df352d.log +0 -0
  32. package/examples/saucedemo/.waygraph-auto/0a8c3719.log +0 -0
  33. package/examples/saucedemo/.waygraph-auto/232a1d81.log +0 -0
  34. package/examples/saucedemo/.waygraph-auto/2553d107.log +0 -0
  35. package/examples/saucedemo/.waygraph-auto/2f4f531e.log +0 -0
  36. package/examples/saucedemo/.waygraph-auto/3bdb2f5a.log +0 -0
  37. package/examples/saucedemo/.waygraph-auto/5b838485.log +0 -0
  38. package/examples/saucedemo/.waygraph-auto/5d13bdde.log +0 -0
  39. package/examples/saucedemo/.waygraph-auto/63b0f4cd.log +0 -0
  40. package/examples/saucedemo/.waygraph-auto/6f05bdc1.log +0 -0
  41. package/examples/saucedemo/.waygraph-auto/70279fc3.log +0 -0
  42. package/examples/saucedemo/.waygraph-auto/731a14bd.log +0 -0
  43. package/examples/saucedemo/.waygraph-auto/76128a2d.log +0 -0
  44. package/examples/saucedemo/.waygraph-auto/796ab578.log +0 -0
  45. package/examples/saucedemo/.waygraph-auto/859a261d.log +0 -0
  46. package/examples/saucedemo/.waygraph-auto/a1ab5d29.log +0 -0
  47. package/examples/saucedemo/.waygraph-auto/a94738b7.log +0 -0
  48. package/examples/saucedemo/.waygraph-auto/b3d172f3.log +0 -0
  49. package/examples/saucedemo/.waygraph-auto/b57921c5.log +0 -0
  50. package/examples/saucedemo/.waygraph-auto/b9ab0983.log +0 -0
  51. package/examples/saucedemo/.waygraph-auto/d37719a6.log +0 -0
  52. package/examples/saucedemo/.waygraph-auto/da0a06bf.log +0 -0
  53. package/examples/saucedemo/.waygraph-auto/dc58eb72.log +0 -0
  54. package/examples/saucedemo/.waygraph-auto/ff616932.log +0 -0
  55. package/package.json +3 -2
  56. package/examples/saucedemo/.waygraph-auto/535e8922.json +0 -8
@@ -107,6 +107,8 @@ export async function runStubPhase(block, phase, opts) {
107
107
  ...(bag.zoom !== undefined ? { zoom: bag.zoom } : {}),
108
108
  ...(bag.zoomOut !== undefined ? { zoomOut: bag.zoomOut } : {}),
109
109
  ...(bag.title !== undefined && bag.title !== "" ? { title: bag.title } : {}),
110
+ ...(bag.titlePos !== undefined ? { titlePos: bag.titlePos } : {}),
111
+ ...(bag.bannerUi !== undefined ? { bannerUi: bag.bannerUi } : {}),
110
112
  ...(bag.todoPos !== undefined ? { todoPos: bag.todoPos } : {}),
111
113
  ...(bag.todoDockUi !== undefined ? { todoDockUi: bag.todoDockUi } : {}),
112
114
  ...(bag.todoParallel === true ? { todoParallel: true } : {}),
@@ -162,11 +162,35 @@ export function createStubCtx(bag, opts) {
162
162
  zoomOut(keep) {
163
163
  bag.zoomOut = !!keep;
164
164
  },
165
- title(text) {
165
+ title(text, opts) {
166
166
  bag.title = String(text ?? "");
167
+ const p = normalizeBannerPos(opts?.pos);
168
+ if (p)
169
+ bag.titlePos = p;
167
170
  },
168
- banner(text) {
171
+ banner(text, opts) {
169
172
  bag.title = String(text ?? "");
173
+ const p = normalizeBannerPos(opts?.pos);
174
+ if (p)
175
+ bag.titlePos = p;
176
+ },
177
+ titlePos(side) {
178
+ const p = normalizeBannerPos(side);
179
+ if (p)
180
+ bag.titlePos = p;
181
+ },
182
+ bannerPos(side) {
183
+ const p = normalizeBannerPos(side);
184
+ if (p)
185
+ bag.titlePos = p;
186
+ },
187
+ bannerUi(opts) {
188
+ if (!opts || typeof opts !== "object")
189
+ return;
190
+ bag.bannerUi = { ...(bag.bannerUi || {}), ...opts };
191
+ },
192
+ hideBanner() {
193
+ bag.bannerUi = { ...(bag.bannerUi || {}), hidden: true };
170
194
  },
171
195
  todoPos(side) {
172
196
  const p = normalizeTodoPos(side);
@@ -256,6 +280,12 @@ export function createStubCtx(bag, opts) {
256
280
  bag.zoomOut = !!partial.zoomOut;
257
281
  if (partial.title !== undefined)
258
282
  bag.title = String(partial.title ?? "");
283
+ const bp = normalizeBannerPos(partial.titlePos);
284
+ if (bp)
285
+ bag.titlePos = bp;
286
+ if (partial.bannerUi && typeof partial.bannerUi === "object") {
287
+ bag.bannerUi = { ...(bag.bannerUi || {}), ...partial.bannerUi };
288
+ }
259
289
  const tp = normalizeTodoPos(partial.todoPos);
260
290
  if (tp)
261
291
  bag.todoPos = tp;
@@ -279,3 +309,7 @@ export function liftLegacySlotTodos(bag) {
279
309
  }
280
310
  }
281
311
  }
312
+ function normalizeBannerPos(raw) {
313
+ const v = String(raw ?? "").toLowerCase();
314
+ return v === "left" || v === "center" || v === "right" ? v : undefined;
315
+ }
@@ -41,6 +41,15 @@ export type WaygraphTodoInput = string | WaygraphTodoItem;
41
41
  * - `bullets` - flat bullet list (no arrow/checkbox; open notes / trimmed dock)
42
42
  */
43
43
  export type TodoListStyle = "sequential" | "checklist" | "bullets";
44
+ /** Where the top banner sits. */
45
+ export type BannerPos = "left" | "center" | "right";
46
+ /** Banner UX, mirrors {@link TodoDockUiOpts}: pass `false` fields to opt out for this phase. */
47
+ export interface BannerUiOpts {
48
+ /** Move to a free spot while a highlight ring would overlap it (default true). */
49
+ collision?: boolean;
50
+ /** Hide the banner entirely for this phase. */
51
+ hidden?: boolean;
52
+ }
44
53
  /** One titled group in the floating todo dock (FR / Scenarios / ACs, etc.). */
45
54
  export type WaygraphTodoGroupInput = {
46
55
  /** Stable group id -> `data-wg-todo-group`. */
@@ -146,6 +155,10 @@ export type StubPhaseFixtures = {
146
155
  zoomOut?: boolean;
147
156
  /** Top demo banner title (the purple "waygraph demo" card). */
148
157
  title?: string;
158
+ /** Banner side/position (`left` | `center` | `right`). Authored control, like {@link todoPos}. */
159
+ titlePos?: BannerPos;
160
+ /** Banner UX: collision (move away from a ring it overlaps), hidden. See {@link BannerUiOpts}. */
161
+ bannerUi?: BannerUiOpts;
149
162
  /**
150
163
  * Floating checklist dock side. `left` | `right`.
151
164
  * Authored control (also click / WAYGRAPH_TODO_POS / --todo-left|right).
@@ -373,10 +386,25 @@ export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
373
386
  * Default zoomOut for rings in this phase. `false` = keep camera between rings.
374
387
  */
375
388
  zoomOut(keep: boolean): void;
376
- /** Top banner title (waygraph demo card). */
377
- title(text: string): void;
389
+ /** Top banner title (waygraph demo card). `opts.pos` also places it (left | center | right). */
390
+ title(text: string, opts?: {
391
+ pos?: BannerPos;
392
+ }): void;
378
393
  /** Alias for {@link StubCtx.title}. */
379
- banner(text: string): void;
394
+ banner(text: string, opts?: {
395
+ pos?: BannerPos;
396
+ }): void;
397
+ /** Place the banner: `left` | `center` | `right` (authored; click / env are fallbacks). */
398
+ titlePos(side: BannerPos): void;
399
+ /** Alias for {@link StubCtx.titlePos}. */
400
+ bannerPos(side: BannerPos): void;
401
+ /**
402
+ * Banner UX: `{ collision: false }` keeps it put even over a ring; `{ hidden: true }` hides it.
403
+ * @example ctx.bannerUi({ collision: false });
404
+ */
405
+ bannerUi(opts: BannerUiOpts): void;
406
+ /** Shorthand for `bannerUi({ hidden: true })`. */
407
+ hideBanner(): void;
380
408
  /**
381
409
  * Floating checklist dock side (`left` | `right`). Authors control this in
382
410
  * code; click / env / --todo-left|right are fallbacks.
@@ -433,6 +461,8 @@ export type StubPhaseResult = {
433
461
  zoom?: number;
434
462
  zoomOut?: boolean;
435
463
  title?: string;
464
+ titlePos?: BannerPos;
465
+ bannerUi?: BannerUiOpts;
436
466
  todoPos?: "left" | "right";
437
467
  /** When true, keep other docks (multi-todo). Default false = replace. */
438
468
  todoParallel?: boolean;
@@ -528,6 +558,8 @@ export type StubBagState = {
528
558
  zoom?: number | undefined;
529
559
  zoomOut?: boolean | undefined;
530
560
  title?: string | undefined;
561
+ titlePos?: BannerPos | undefined;
562
+ bannerUi?: BannerUiOpts | undefined;
531
563
  todoPos?: "left" | "right" | undefined;
532
564
  /** Author patched todo-dock UX this phase. */
533
565
  todoDockUi?: TodoDockUiOpts | undefined;
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { registerMemStub, seedMemStub, getMemStub } from "./mem-stub.js";
5
5
  export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText, } from "./trait.js";
6
6
  export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, defineAssertBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withMemStub, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, chainFlow, defineLayout, map, MapBuilder, } from "./engine.js";
7
7
  export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, normalizeDeviceOrientation, applyOrientation, orientationFromViewport, DEVICE_PRESETS, resolveTodoDockUi, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, WAYGRAPH_RING_CSS, } from "./highlights.js";
8
- export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, WaygraphTodosOpts, TodoDockUiOpts, TodoDockUiResolved, TodoDockState, TodoListStyle, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, DevicePreset, DeviceViewport, DeviceState, DeviceOrientation, } from "./highlights.js";
8
+ export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, WaygraphTodosOpts, TodoDockUiOpts, BannerPos, BannerUiOpts, TodoDockUiResolved, TodoDockState, TodoListStyle, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, DevicePreset, DeviceViewport, DeviceState, DeviceOrientation, } from "./highlights.js";
9
9
  export type { Flow, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, AssertBlock, AssertBlockOptions, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, Layout, LayoutOptions, MapBuilderOptions, } from "./engine.js";
10
10
  export type { ActionPage } from "./types.js";
11
11
  export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
@@ -1,4 +1,4 @@
1
- import type { DevicePreset, DeviceState, HighlightTone, TodoDockUiOpts } from "../highlights.js";
1
+ import type { BannerPos, BannerUiOpts, DevicePreset, DeviceState, HighlightTone, TodoDockUiOpts } from "../highlights.js";
2
2
  import type { Page } from "@playwright/test";
3
3
  /** One ring the Pilot agent wants painted (same fields as demo stub rings). */
4
4
  export type PilotFixtureRing = {
@@ -68,6 +68,14 @@ export type PilotHighlightFixtures = {
68
68
  device?: DevicePreset | DeviceState;
69
69
  /** Drop every agent fixture ring/todo/focus/zoom immediately. */
70
70
  clear?: boolean;
71
+ /** Top banner title card (same as `ctx.title`). */
72
+ title?: string;
73
+ /** Banner position (same as `ctx.titlePos`). */
74
+ titlePos?: BannerPos;
75
+ /** Banner UX: collision / hidden (same as `ctx.bannerUi`). */
76
+ bannerUi?: BannerUiOpts;
77
+ /** Alias of {@link todoUi} - the name `ctx.todoDockUi` uses. */
78
+ todoDockUi?: TodoDockUiOpts;
71
79
  };
72
80
  /**
73
81
  * Paint (or clear) agent-authored highlight fixtures on the live page.
@@ -92,8 +92,41 @@ export async function showPilotFixtures(page, fixtures) {
92
92
  : typeof fixtures.device === "string"
93
93
  ? fixtures.device
94
94
  : fixtures.device.preset || "device";
95
+ const bannerTitle = fixtures.clear ? undefined : fixtures.title;
96
+ const bannerPos = fixtures.clear ? undefined : fixtures.titlePos;
97
+ const bannerHidden = !!fixtures.clear || !!fixtures.bannerUi?.hidden;
98
+ const bannerCollision = fixtures.bannerUi?.collision !== false;
95
99
  return page
96
- .evaluate(({ rings, todos, todoIndex, todoTitle, todoPos, holdMs, zoom, zoomSelector, zoomOut, clearAll, deviceLabel, todoUi, }) => {
100
+ .evaluate(({ rings, todos, todoIndex, todoTitle, todoPos, holdMs, zoom, zoomSelector, zoomOut, clearAll, deviceLabel, todoUi, bannerTitle, bannerPos, bannerHidden, bannerCollision, }) => {
101
+ // Same #wg-banner element/CSS the demo runner paints (ui/css/banner.css, already in the
102
+ // shared bundle) - so ctx.title / ctx.titlePos / ctx.bannerUi behave identically on a Pilot
103
+ // session, not a separate re-implementation.
104
+ if (bannerHidden) {
105
+ const b = __wgById("wg-banner");
106
+ if (b)
107
+ b.style.display = "none";
108
+ }
109
+ else if (bannerTitle) {
110
+ let banner = __wgById("wg-banner");
111
+ if (!banner) {
112
+ banner = document.createElement("div");
113
+ banner.id = "wg-banner";
114
+ const tag = document.createElement("span");
115
+ tag.className = "wg-banner-tag";
116
+ tag.textContent = "waygraph pilot";
117
+ const text = document.createElement("span");
118
+ text.className = "wg-banner-text";
119
+ banner.appendChild(tag);
120
+ banner.appendChild(text);
121
+ __wgAdd(banner);
122
+ }
123
+ banner.style.display = "";
124
+ banner.dataset.pos = bannerPos || banner.dataset.pos || "left";
125
+ banner.dataset.collision = bannerCollision ? "1" : "0";
126
+ const textEl = banner.querySelector(".wg-banner-text");
127
+ if (textEl)
128
+ textEl.textContent = bannerTitle;
129
+ }
97
130
  const w = window;
98
131
  if (w.__wgPilotFxHideTimer)
99
132
  clearTimeout(w.__wgPilotFxHideTimer);
@@ -329,6 +362,10 @@ export async function showPilotFixtures(page, fixtures) {
329
362
  clearAll: fixtures.clear === true,
330
363
  deviceLabel,
331
364
  todoUi,
365
+ bannerTitle,
366
+ bannerPos,
367
+ bannerHidden,
368
+ bannerCollision,
332
369
  })
333
370
  .catch(() => ({ painted: 0, missing: rings.map((r) => r.selector) }));
334
371
  }
@@ -0,0 +1,9 @@
1
+ import type { StubPhaseResult } from "../highlights.js";
2
+ import type { PilotHighlightFixtures } from "./fixtures.js";
3
+ /**
4
+ * Resolve agent-sent "waygraph overlay fixtures" (`auto highlight` JSON / shorthand) through the SAME
5
+ * stub-ctx path a Block's `stubBefore(ctx)` uses, so ctx.todos / ctx.device / ctx.title / ctx.bannerUi semantics
6
+ * (keep vs set vs clear, defaults, normalisation) cannot diverge between `waygraph demo` and pilot/auto/browser.
7
+ * Not Playwright test fixtures - these describe what the waygraph overlay paints on the page under test.
8
+ */
9
+ export declare function overlayFixturesToPhase(f: PilotHighlightFixtures): Promise<StubPhaseResult>;
@@ -0,0 +1,45 @@
1
+ import { runStubPhase } from "../highlights.js";
2
+ /**
3
+ * Resolve agent-sent "waygraph overlay fixtures" (`auto highlight` JSON / shorthand) through the SAME
4
+ * stub-ctx path a Block's `stubBefore(ctx)` uses, so ctx.todos / ctx.device / ctx.title / ctx.bannerUi semantics
5
+ * (keep vs set vs clear, defaults, normalisation) cannot diverge between `waygraph demo` and pilot/auto/browser.
6
+ * Not Playwright test fixtures - these describe what the waygraph overlay paints on the page under test.
7
+ */
8
+ export async function overlayFixturesToPhase(f) {
9
+ const lifecycle = (ctx) => {
10
+ if (f.clear) {
11
+ ctx.hideTodos();
12
+ ctx.hideDevice();
13
+ return;
14
+ }
15
+ if (f.title !== undefined)
16
+ ctx.title(f.title, f.titlePos ? { pos: f.titlePos } : undefined);
17
+ else if (f.titlePos)
18
+ ctx.titlePos(f.titlePos);
19
+ if (f.bannerUi)
20
+ ctx.bannerUi(f.bannerUi);
21
+ if (f.todos !== undefined) {
22
+ ctx.todos("pilot", f.todos, {
23
+ title: f.todoTitle ?? "Plan",
24
+ index: f.todoIndex ?? 0,
25
+ pos: f.todoPos === "left" ? "left" : "right",
26
+ });
27
+ }
28
+ const ui = f.todoDockUi ?? f.todoUi;
29
+ if (ui)
30
+ ctx.todoDockUi(ui);
31
+ if (f.device)
32
+ ctx.device(f.device);
33
+ if (typeof f.zoom === "number" && Number.isFinite(f.zoom) && f.zoom > 0)
34
+ ctx.zoom(f.zoom);
35
+ if (f.zoomOut !== undefined)
36
+ ctx.zoomOut(f.zoomOut);
37
+ if (f.rings?.length) {
38
+ f.rings.forEach((r, i) => {
39
+ const { selector, label, ...rest } = r;
40
+ ctx.ring(`ring${i + 1}`, { selector, label, ...rest });
41
+ });
42
+ }
43
+ };
44
+ return runStubPhase({ name: "overlay-fixtures", stubBefore: lifecycle }, "stubBefore");
45
+ }
@@ -1,8 +1,6 @@
1
1
  // Split out of the former 950-line pilot-overlay.ts (see src/ARCHITECTURE.md). Behavior unchanged.
2
2
  import { SURFACE } from "../ui/tokens.js";
3
3
  import { ensureShadowRoot, installShadowRoot } from "../ui/shadow.js";
4
- import { pilotFxRingCss } from "../ui/components/ring.js";
5
- import { WAYGRAPH_RING_CSS } from "../highlights.js";
6
4
  // Real, direct user request: this overlay's own colors were an improvised
7
5
  // purple/white scheme (#a78bfa / #1a1033) that didn't match anything else -
8
6
  // "our design here seems like barely shares with the ui on the stepper
@@ -64,26 +62,9 @@ const OVERLAY_CSS = `
64
62
  opacity: 0; transform: translateY(4px); transition: opacity 0.15s, transform 0.15s;
65
63
  }
66
64
  #wg-pilot-activity.wg-pilot-toast-show { opacity: 1; transform: translateY(0); }
67
- /* Agent-sent fixture rings: generated from ui/tokens (tones shared with #wg-ring). */
68
- ${pilotFxRingCss()}
69
- #wg-pilot-fx-todos{
70
- position:fixed;z-index:2147483645;top:72px;max-width:280px;max-height:calc(100vh - 100px);overflow:auto;
71
- font:12px/1.4 ui-sans-serif,system-ui,sans-serif;color:#fff;
72
- background:${SURFACE};border-radius:10px;padding:10px 12px;
73
- box-shadow:0 2px 10px rgba(0,0,0,.3);pointer-events:auto;
74
- transition:opacity .25s ease;
75
- }
76
- #wg-pilot-fx-todos.wg-todo-behind{z-index:2147483643;opacity:.42;pointer-events:none;}
77
- #wg-pilot-fx-todos[data-pos=left]{left:14px;right:auto;}
78
- #wg-pilot-fx-todos[data-pos=right]{right:14px;left:auto;}
79
- #wg-pilot-fx-todos .wg-pilot-fx-todo-title{font-weight:700;color:#c9a6ff;margin-bottom:6px;}
80
- #wg-pilot-fx-todos ol{margin:0;padding-left:18px;}
81
- #wg-pilot-fx-todos li{margin:3px 0;color:#ddd;}
82
- #wg-pilot-fx-todos li.wg-pilot-fx-todo-done{color:#888;text-decoration:line-through;}
83
- #wg-pilot-fx-todos li.wg-pilot-fx-todo-now{color:#fff;font-weight:700;}
84
- #wg-pilot-fx-todos[data-compact="1"]:not(:hover) li.wg-todo-fold{display:none;}
85
- #wg-pilot-fx-todos .wg-todo-more{margin-top:6px;padding:4px 8px;border-radius:8px;
86
- background:rgba(124,58,237,.22);font:700 11px/1.2 system-ui,sans-serif;color:#e8dcff;}
65
+ /* Agent fixture rings (.wg-pilot-fx-ring/label), #wg-pilot-fx-todos, #wg-ring/#wg-ring-label and
66
+ #wg-banner all come from the ONE shared bundle ensureShadowRoot installs (ui/css/*.css) - not
67
+ redefined here. */
87
68
  /* Spotlight (demo focus:true) - dim page, cutout around target. */
88
69
  #wg-pilot-fx-focus{position:fixed;z-index:2147483644;pointer-events:none;
89
70
  border-radius:12px;box-shadow:0 0 0 9999px rgba(8,4,20,.62);
@@ -107,7 +88,7 @@ ${pilotFxRingCss()}
107
88
  #wg-pilot-fx-device[data-preset=mobile]{border-color:#3B82F6;}
108
89
  #wg-pilot-fx-device[data-preset=tablet]{border-color:#22C55E;}
109
90
  #wg-pilot-fx-device[data-preset=desktop]{border-color:#9CA3AF;}
110
- ` + WAYGRAPH_RING_CSS;
91
+ `;
111
92
  /** Browser-side shell install — runs on every document via context.addInitScript. */
112
93
  function installPilotOverlayShell(css) {
113
94
  if (__wgById("wg-pilot-overlay"))
@@ -2,3 +2,4 @@ export * from "./pilot-overlay/shell.js";
2
2
  export * from "./pilot-overlay/fixtures.js";
3
3
  export * from "./pilot-overlay/panel.js";
4
4
  export * from "./pilot-overlay/action-ring.js";
5
+ export * from "./pilot-overlay/overlay-fixtures.js";
@@ -3,3 +3,4 @@ export * from "./pilot-overlay/shell.js";
3
3
  export * from "./pilot-overlay/fixtures.js";
4
4
  export * from "./pilot-overlay/panel.js";
5
5
  export * from "./pilot-overlay/action-ring.js";
6
+ export * from "./pilot-overlay/overlay-fixtures.js";
@@ -0,0 +1,62 @@
1
+ // Banner (title card) authoring + verbose log helpers. Mirrors the todo dock's probe/log path
2
+ // so an agent without eyes can read banner state from the demo log.
3
+ import { demoLog } from "./demo-log.js";
4
+
5
+ /** Authored banner UX from a stub phase -> the { pos, collision, hidden } shape installOverlay takes. */
6
+ export function bannerUiFromPhase(phase) {
7
+ const ui = {};
8
+ if (phase && phase.titlePos) ui.pos = phase.titlePos;
9
+ if (phase && phase.bannerUi) {
10
+ if (phase.bannerUi.collision !== undefined) ui.collision = !!phase.bannerUi.collision;
11
+ if (phase.bannerUi.hidden !== undefined) ui.hidden = !!phase.bannerUi.hidden;
12
+ }
13
+ return ui;
14
+ }
15
+
16
+ /** Read the live banner: text, position, whether it was moved by ring collision; drains the move log. */
17
+ export async function probeBanner(page) {
18
+ return page
19
+ .evaluate(() => {
20
+ const b = __wgById("wg-banner");
21
+ const log = window.__wgBannerLog || [];
22
+ window.__wgBannerLog = [];
23
+ if (!b) return { present: false, log };
24
+ const r = b.getBoundingClientRect();
25
+ const text = b.querySelector(".wg-banner-text");
26
+ return {
27
+ present: true,
28
+ text: text ? text.textContent : "",
29
+ pos: b.dataset.pos || null,
30
+ home: b.dataset.home || null,
31
+ moved: b.dataset.moved || null,
32
+ collision: b.dataset.collision !== "0",
33
+ hidden: b.style.display === "none",
34
+ authored: b.dataset.authored === "1",
35
+ left: Math.round(r.left),
36
+ top: Math.round(r.top),
37
+ w: Math.round(r.width),
38
+ h: Math.round(r.height),
39
+ log,
40
+ };
41
+ })
42
+ .catch(() => ({ present: false, log: [] }));
43
+ }
44
+
45
+ export function logBannerDom(tag, p) {
46
+ if (!p) return;
47
+ if (!p.present) {
48
+ demoLog(" " + tag + " banner-dom absent");
49
+ return;
50
+ }
51
+ demoLog(
52
+ " " + tag + " banner-dom " +
53
+ JSON.stringify(String(p.text || "").slice(0, 60)) +
54
+ "@" + p.pos +
55
+ (p.home && p.home !== p.pos ? " (home=" + p.home + ")" : "") +
56
+ " " + p.left + "," + p.top + " " + p.w + "x" + p.h +
57
+ " collision=" + (p.collision ? "on" : "off") +
58
+ (p.hidden ? " HIDDEN" : "") +
59
+ (p.authored ? " authored" : ""),
60
+ );
61
+ for (const line of p.log || []) demoLog(" " + tag + " banner " + line);
62
+ }
@@ -109,8 +109,14 @@ function demoTs() {
109
109
  );
110
110
  }
111
111
 
112
+ let logSurface = "demo";
113
+ /** Which surface is talking (demo | auto | browser | pilot) - same log format everywhere, only the tag differs. */
114
+ export function setDemoLogSurface(name) {
115
+ logSurface = String(name || "demo");
116
+ }
117
+
112
118
  function demoPrefix() {
113
- return ansiPaint(ANSI.dim, "[" + demoTs() + "]") + " waygraph demo: ";
119
+ return ansiPaint(ANSI.dim, "[" + demoTs() + "]") + " waygraph " + logSurface + ": ";
114
120
  }
115
121
 
116
122
  export function demoLog(msg) {
@@ -170,6 +176,8 @@ export function logStubPhaseFixtures(phaseName, phase) {
170
176
  const tag = "stub." + phaseName;
171
177
  if (phase.title) demoFixture(tag, "ctx.title(...)", JSON.stringify(phase.title));
172
178
  if (phase.banner) demoFixture(tag, "ctx.banner(...)", JSON.stringify(phase.banner));
179
+ if (phase.titlePos) demoFixture(tag, "ctx.titlePos(...)", JSON.stringify(phase.titlePos));
180
+ if (phase.bannerUi) demoFixture(tag, "ctx.bannerUi(...)", JSON.stringify(phase.bannerUi));
173
181
  if (phase.deviceSync && phase.deviceSync !== "keep") {
174
182
  demoFixture(
175
183
  tag,
@@ -1,10 +1,11 @@
1
1
  // In-page installer (runs inside the browser via page.evaluate): must stay self-contained - no module-scope closure.
2
2
  // Split out of the former single installOverlay evaluate (see src/ARCHITECTURE.md). Shares state only via window.__wg*.
3
3
  /* eslint-disable no-unused-vars */
4
- export function installBanner({ title, favicon, bannerPos, todoPos, envAutoplay, todoDockUi }) {
4
+ export function installBanner({ title, favicon, bannerPos, todoPos, envAutoplay, todoDockUi, bannerUi }) {
5
5
  const POSITIONS = ["left", "center", "right"];
6
6
  const applyPos = (el, pos) => {
7
7
  el.dataset.pos = pos;
8
+ el.dataset.home = pos;
8
9
  try {
9
10
  localStorage.setItem("wg-banner-pos", pos);
10
11
  } catch {
@@ -52,6 +53,60 @@ export function installBanner({ title, favicon, bannerPos, todoPos, envAutoplay,
52
53
  text.textContent = title;
53
54
  }
54
55
  }
56
+ // Authored banner UX (StubCtx.titlePos / bannerUi): applied every step, wins over click/env.
57
+ if (bannerUi) {
58
+ const cur = window.__wgBannerUi || {};
59
+ window.__wgBannerUi = { ...cur, ...bannerUi };
60
+ }
61
+ const bui = window.__wgBannerUi || {};
62
+ const bannerEl = __wgById("wg-banner");
63
+ if (bannerEl) {
64
+ if (bui.pos && POSITIONS.includes(bui.pos) && bannerEl.dataset.home !== bui.pos) {
65
+ bannerEl.dataset.pos = bui.pos;
66
+ bannerEl.dataset.home = bui.pos;
67
+ bannerEl.dataset.authored = "1";
68
+ }
69
+ bannerEl.dataset.collision = bui.collision === false ? "0" : "1";
70
+ bannerEl.style.display = bui.hidden ? "none" : "";
71
+ }
72
+ // Move the banner out of the way of a highlight ring (mirrors the todo dock's collision flip).
73
+ window.__wgBannerAvoidRing = (box) => {
74
+ const b = __wgById("wg-banner");
75
+ if (!b || !box || b.dataset.collision === "0" || b.style.display === "none") return;
76
+ const pad = 10;
77
+ const hits = () => {
78
+ const r = b.getBoundingClientRect();
79
+ return !(
80
+ r.right < box.x - pad ||
81
+ r.left > box.x + box.width + pad ||
82
+ r.bottom < box.y - pad ||
83
+ r.top > box.y + box.height + pad
84
+ );
85
+ };
86
+ const home = b.dataset.home || b.dataset.pos || "left";
87
+ b.dataset.pos = home;
88
+ if (!hits()) {
89
+ delete b.dataset.moved;
90
+ return;
91
+ }
92
+ for (const cand of POSITIONS.filter((p) => p !== home)) {
93
+ b.dataset.pos = cand;
94
+ if (!hits()) {
95
+ b.dataset.moved = home + ">" + cand;
96
+ (window.__wgBannerLog = window.__wgBannerLog || []).push("moved " + home + " -> " + cand + " (ring overlap)");
97
+ return;
98
+ }
99
+ }
100
+ b.dataset.pos = home;
101
+ b.dataset.moved = "none-free";
102
+ (window.__wgBannerLog = window.__wgBannerLog || []).push("overlap at every position (kept " + home + ")");
103
+ };
104
+ window.__wgBannerRestore = () => {
105
+ const b = __wgById("wg-banner");
106
+ if (!b) return;
107
+ if (b.dataset.home) b.dataset.pos = b.dataset.home;
108
+ delete b.dataset.moved;
109
+ };
55
110
  // Tab title/favicon: a real navigation resets document.title and any
56
111
  // <link rel="icon"> the new document brings, so re-check (not
57
112
  // re-append) on every call instead of a one-time flag.
@@ -114,6 +114,7 @@ export function installCore({ title, favicon, bannerPos, todoPos, envAutoplay, t
114
114
  ringLabel.style.top = labelTop + "px";
115
115
  if (window.__wgTodosSetBehind) window.__wgTodosSetBehind(true);
116
116
  if (window.__wgTodosAvoidRingCollision) window.__wgTodosAvoidRingCollision(box);
117
+ if (window.__wgBannerAvoidRing) window.__wgBannerAvoidRing(box);
117
118
  };
118
119
  /** Dim/lower floating todo docks while a ring caption is the focus. */
119
120
  window.__wgTodosSetBehind = (on) => {
@@ -220,6 +221,7 @@ export function installCore({ title, favicon, bannerPos, todoPos, envAutoplay, t
220
221
  if (ringLabel) ringLabel.style.opacity = "0";
221
222
  if (window.__wgClearFocus) window.__wgClearFocus();
222
223
  if (window.__wgTodosSetBehind) window.__wgTodosSetBehind(false);
224
+ if (window.__wgBannerRestore) window.__wgBannerRestore();
223
225
  window.__wgTodoCollisionLocked = false;
224
226
  __wgQA(".wg-todo-dock[data-tucked], #wg-todo-dock[data-tucked]").forEach((el) => {
225
227
  delete el.dataset.tucked;
@@ -1,7 +1,5 @@
1
1
  // Moved verbatim from the former CHAIN_RUNNER_SCRIPT template string in cli.ts (see src/ARCHITECTURE.md).
2
2
  // Runs inside the target project's own waygraph copy - keep it dependency-light and self-contained.
3
- import { WAYGRAPH_RING_CSS } from "../highlights.js";
4
- import { cursorCss } from "../ui/components/cursor.js";
5
3
  import { SURFACE } from "../ui/tokens.js";
6
4
 
7
5
  // ---------------------------------------------------------------------------
@@ -13,22 +11,11 @@ import { SURFACE } from "../ui/tokens.js";
13
11
  // label-under-box language, minus everything camera/narration-specific).
14
12
  // Purely CLI-level orchestration - no engine changes, no change to the
15
13
  // non-STEP path above.
16
- export const RING_CSS =
17
- // Shared with pilot-overlay.ts's showPilotVision - same ring/label CSS,
18
- // one source of truth (highlights.ts), so a Blind Pilot vision ring and a
19
- // demo stepper ring never visually drift apart.
20
- WAYGRAPH_RING_CSS +
21
- // Mouse cursor icon that travels to a target before it's acted on, plus a
22
- // quick expanding ripple at the moment of a click - same idea as
23
- // help-center-clip-engine's #clip-cursor/#clip-ring (video-pipeline). The
24
- // shape itself is an inline SVG set as innerHTML in installOverlay below
25
- // (dark fill + white stroke, same as the clip-engine's own cursor) - a
26
- // plain solid-white CSS clip-path (the first attempt here) had no outline
27
- // at all and all but disappeared against this app's light background.
28
- // Travel duration is JS-driven per call via --wg-cursor-ms, same reason
29
- // the clip engine's own comment gives: a hardcoded CSS duration would
30
- // make the speed param a no-op.
31
- cursorCss() +
14
+ // Runner-only chrome (step panel, todo-dock legacy variant, zoom/typing/device chips) - NOT
15
+ // ring/cursor/banner, which come from the one shared bundle every surface installs
16
+ // (ui/css/*.css, see ensureShadowRoot in overlay-install.js). Injected under its own key so it
17
+ // never collides with that bundle.
18
+ export const RUNNER_EXTRA_CSS =
32
19
  // Spotlight: dim everything except the highlight target (focus: true).
33
20
  "#wg-focus-veil{position:fixed;z-index:2147483644;pointer-events:none;" +
34
21
  "border-radius:12px;box-shadow:0 0 0 9999px rgba(8,4,20,.62);" +
@@ -225,15 +212,7 @@ export const RING_CSS =
225
212
  "#wg-modules .wg-mod-done{background:#2a1650;color:#9a7ad1;}" +
226
213
  "#wg-modules .wg-mod-current{background:#7C3AED;color:#fff;}" +
227
214
  "#wg-modules .wg-mod-upcoming{background:transparent;color:#5a4a80;border:1px solid #3a2a60;}" +
228
- "#wg-banner{position:fixed;z-index:2147483647;top:14px;max-width:320px;" +
229
- "background:" + SURFACE + ";color:#fff;border-radius:12px;padding:10px 16px;" +
230
- "font:14px/1.4 system-ui,sans-serif;box-shadow:0 8px 20px rgba(0,0,0,.3);" +
231
- "border:1px solid rgba(124,58,237,.4);cursor:pointer;user-select:none;}" +
232
- "#wg-banner[data-pos=left]{left:14px;right:auto;transform:none;}" +
233
- "#wg-banner[data-pos=center]{left:50%;right:auto;transform:translateX(-50%);}" +
234
- "#wg-banner[data-pos=right]{right:14px;left:auto;transform:none;}" +
235
- "#wg-banner .wg-banner-tag{display:block;font-size:10px;font-weight:700;color:#c9a6ff;" +
236
- "letter-spacing:.05em;text-transform:uppercase;margin-bottom:2px;}" +
215
+ // #wg-banner: comes from the shared bundle (ui/css/banner.css) now - not redefined here.
237
216
  "#wg-panel .wg-key{margin:8px 0;}" +
238
217
  // A live, human-readable preview of what's about to be written for this
239
218
  // MemKey - Dan: "i want pretty to exist in memkeys too... it shows what
@@ -1,6 +1,6 @@
1
1
  // Moved verbatim from the former CHAIN_RUNNER_SCRIPT template string in cli.ts (see src/ARCHITECTURE.md).
2
2
  // Runs inside the target project's own waygraph copy - keep it dependency-light and self-contained.
3
- import { HOST_CSS, RING_CSS, WAYGRAPH_FAVICON } from "./overlay-css.js";
3
+ import { HOST_CSS, RUNNER_EXTRA_CSS, WAYGRAPH_FAVICON } from "./overlay-css.js";
4
4
  import { ensureShadowRoot } from "../ui/shadow.js";
5
5
  import { resolveTodoDockUi } from "../highlights.js";
6
6
  import { installCore } from "./inpage/core.js";
@@ -12,9 +12,12 @@ import { installTodos } from "./inpage/todos.js";
12
12
  import { installDevice } from "./inpage/device.js";
13
13
  import { applyVideoDeviceStage } from "./device-stage.js";
14
14
 
15
- export async function installOverlay(page, title) {
15
+ /** `ui` = authored banner UX from the stub phase: { pos, collision, hidden } (all optional). */
16
+ export async function installOverlay(page, title, ui) {
17
+ // ring/cursor/banner/dock come from the ONE shared bundle every surface installs (ui/css/*.css).
16
18
  await ensureShadowRoot(page);
17
- await page.evaluate(([css, key]) => __wgCss(css, key), [RING_CSS, "wg-overlay-css"]).catch(() => {});
19
+ // Runner-only chrome (step panel, zoom/typing/device chips) - separate key, no collision.
20
+ await page.evaluate(([css, key]) => __wgCss(css, key), [RUNNER_EXTRA_CSS, "wg-runner-css"]).catch(() => {});
18
21
  await page.addStyleTag({ content: HOST_CSS }).catch(() => {});
19
22
  // Default top-left; override with WAYGRAPH_TITLE_POS=left|center|right.
20
23
  // Click cycles left -> center -> right (persisted in localStorage so a
@@ -25,7 +28,7 @@ export async function installOverlay(page, title) {
25
28
  const todoPos = envTodoPos === "right" ? "right" : "left";
26
29
  const envAutoplay = process.env.WAYGRAPH_AUTOPLAY === "1";
27
30
  const todoDockUi = resolveTodoDockUi();
28
- const args = { title, favicon: WAYGRAPH_FAVICON, bannerPos, todoPos, envAutoplay, todoDockUi };
31
+ const args = { title, favicon: WAYGRAPH_FAVICON, bannerPos, todoPos, envAutoplay, todoDockUi, bannerUi: ui || null };
29
32
  await page.evaluate(installCore, args).catch(() => {});
30
33
  await page.evaluate(installSwipeZoom, args).catch(() => {});
31
34
  await page.evaluate(installBanner, args).catch(() => {});
@@ -0,0 +1,8 @@
1
+ import type { Page } from "@playwright/test";
2
+ import type { StubPhaseResult } from "../highlights/types.js";
3
+
4
+ export interface SessionOverlay {
5
+ present(tag: string, phase: Partial<StubPhaseResult>): Promise<void>;
6
+ restore(): Promise<void>;
7
+ }
8
+ export function createSessionOverlay(page: Page, surface: string): SessionOverlay;