waygraph 0.12.15 → 0.12.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -235,7 +235,9 @@ snapshot, partitions edges by hash, and claims via in-process edge leases (audit
235
235
  `docs/proposals/traverse-ffcompose-rfc.md`.
236
236
 
237
237
  **Richer demo narration:** automation rings are **gray**; authored stubs/slides can use
238
- `tone: "info" | "warning" | "danger" | "success"` (iconified captions). Episode pacing:
238
+ `tone: "info" | "warning" | "danger" | "success"` (iconified captions), plus
239
+ `size: "sm" | "md" | "lg"` and `weight: "normal" | "bold"`. Flow episode defaults:
240
+ `withHighlightStyle(flow, { size, weight, tone? })` (slot/fixture wins). Episode pacing:
239
241
  `withDemoPace(flow, "fast" | "slow" | "normal" | "blitz")` or `withBlockPace(block, ...)`
240
242
  (FFCompose stays blitz). Living objectives: `docs/proposals/OBJECTIVES.md`.
241
243
  `spawnTab(entry, page, mem)` drives a genuinely separate second tab through its own
package/dist/cli.js CHANGED
@@ -213,6 +213,9 @@ import {
213
213
  hasAuthoredStubOnError,
214
214
  normalizeHighlightTone,
215
215
  normalizeDemoPace,
216
+ normalizeHighlightSize,
217
+ normalizeHighlightWeight,
218
+ applyHighlightStyleDefaults,
216
219
  resolveStepDemoPace,
217
220
  } from "waygraph";
218
221
 
@@ -425,6 +428,13 @@ const RING_CSS =
425
428
  "#wg-ring-label[data-tone=warning]{background:#EAB308;color:#1c1917;}" +
426
429
  "#wg-ring-label[data-tone=danger]{background:#DC2626;color:#fff;}" +
427
430
  "#wg-ring-label[data-tone=success]{background:#16A34A;color:#fff;}" +
431
+ // size: ring pad + label font; weight: label boldness
432
+ "#wg-ring[data-size=sm]{border-width:1.5px;border-radius:8px;}" +
433
+ "#wg-ring[data-size=lg]{border-width:4px;border-radius:12px;}" +
434
+ "#wg-ring-label[data-size=sm]{font-size:10px;line-height:1.25;padding:4px 7px;border-radius:5px;}" +
435
+ "#wg-ring-label[data-size=lg]{font-size:16px;line-height:1.35;padding:8px 14px;border-radius:9px;max-width:min(480px,85vw);}" +
436
+ "#wg-ring-label[data-weight=bold]{font-weight:800;}" +
437
+ "#wg-ring-label[data-weight=normal]{font-weight:600;}" +
428
438
  // Mouse cursor icon that travels to a target before it's acted on, plus a
429
439
  // quick expanding ripple at the moment of a click - same idea as
430
440
  // help-center-clip-engine's #clip-cursor/#clip-ring (video-pipeline). The
@@ -662,7 +672,7 @@ async function installOverlay(page, title) {
662
672
  const cursor = document.getElementById("wg-cursor");
663
673
  if (cursor) cursor.style.opacity = "0";
664
674
  };
665
- window.__wgPositionRing = (box, label, tone) => {
675
+ window.__wgPositionRing = (box, label, tone, style) => {
666
676
  const ring = document.getElementById("wg-ring");
667
677
  const ringLabel = document.getElementById("wg-ring-label");
668
678
  if (!ring || !ringLabel) return;
@@ -671,15 +681,25 @@ async function installOverlay(page, title) {
671
681
  raw === "auto" || raw === "info" || raw === "warning" || raw === "danger" || raw === "success"
672
682
  ? raw
673
683
  : "planned";
684
+ const st = style && typeof style === "object" ? style : {};
685
+ const sizeRaw = (st.size || "md") + "";
686
+ const size =
687
+ sizeRaw === "sm" || sizeRaw === "lg" ? sizeRaw : "md";
688
+ const weightRaw = (st.weight || "normal") + "";
689
+ const weight = weightRaw === "bold" ? "bold" : "normal";
674
690
  ring.dataset.tone = t;
675
691
  ringLabel.dataset.tone = t;
692
+ ring.dataset.size = size;
693
+ ringLabel.dataset.size = size;
694
+ ringLabel.dataset.weight = weight;
695
+ const pad = size === "sm" ? 3 : size === "lg" ? 10 : 6;
676
696
  const margin = 6;
677
697
  const vw = window.innerWidth;
678
698
  const vh = window.innerHeight;
679
- let left = box.x - 6;
680
- let top = box.y - 6;
681
- const width = box.width + 12;
682
- const height = box.height + 12;
699
+ let left = box.x - pad;
700
+ let top = box.y - pad;
701
+ const width = box.width + pad * 2;
702
+ const height = box.height + pad * 2;
683
703
  if (left < margin) left = margin;
684
704
  if (top < margin) top = margin;
685
705
  if (left + width > vw - margin) left = Math.max(margin, vw - margin - width);
@@ -1058,7 +1078,10 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1058
1078
  try {
1059
1079
  const box = await page.locator(h.selector).first().boundingBox();
1060
1080
  if (box) {
1061
- await showRing(page, box, h.label, h.tone || "planned");
1081
+ await showRing(page, box, h.label, h.tone || "planned", {
1082
+ size: h.size,
1083
+ weight: h.weight,
1084
+ });
1062
1085
  const authored = resolveFixtureDwellMs(h, { gatesFast });
1063
1086
  const legacyMs = i === list.length - 1 ? 200 : 900;
1064
1087
  const holdMs =
@@ -1080,7 +1103,10 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1080
1103
  if (window.__wgHideRing) window.__wgHideRing();
1081
1104
  return;
1082
1105
  }
1083
- window.__wgPositionRing(el.getBoundingClientRect(), h.label, h.tone || "planned");
1106
+ window.__wgPositionRing(el.getBoundingClientRect(), h.label, h.tone || "planned", {
1107
+ size: h.size || "md",
1108
+ weight: h.weight || "normal",
1109
+ });
1084
1110
  };
1085
1111
  reposition();
1086
1112
  window.__wgRingTrack = reposition;
@@ -1425,13 +1451,17 @@ async function markStepRunning(page, _opts) {
1425
1451
  .catch(() => {});
1426
1452
  }
1427
1453
 
1428
- async function showRing(page, box, label, tone) {
1454
+ async function showRing(page, box, label, tone, style) {
1455
+ const size = normalizeHighlightSize(style && style.size);
1456
+ const weight = normalizeHighlightWeight(style && style.weight);
1429
1457
  await page
1430
1458
  .evaluate(
1431
- ({ box, label, tone }) => {
1432
- if (window.__wgPositionRing) window.__wgPositionRing(box, label, tone || "planned");
1459
+ ({ box, label, tone, size, weight }) => {
1460
+ if (window.__wgPositionRing) {
1461
+ window.__wgPositionRing(box, label, tone || "planned", { size, weight });
1462
+ }
1433
1463
  },
1434
- { box, label, tone: tone || "planned" },
1464
+ { box, label, tone: tone || "planned", size, weight },
1435
1465
  )
1436
1466
  .catch(() => {});
1437
1467
  }
@@ -1465,9 +1495,11 @@ async function captionForLocator(page, locator, stubs, fallback) {
1465
1495
  return {
1466
1496
  label: formatHighlightCaption(matched),
1467
1497
  tone: normalizeHighlightTone(matched.tone),
1498
+ size: normalizeHighlightSize(matched.size),
1499
+ weight: normalizeHighlightWeight(matched.weight),
1468
1500
  };
1469
1501
  }
1470
- return { label: fallback, tone: "auto" };
1502
+ return { label: fallback, tone: "auto", size: "md", weight: "normal" };
1471
1503
  }
1472
1504
 
1473
1505
  async function dwellMatchedStub(page, locator, stubs, pacing) {
@@ -1510,13 +1542,17 @@ async function presentSlides(page, slides, _gate, opts) {
1510
1542
  const s = slides[i];
1511
1543
  const caption = formatHighlightCaption(s);
1512
1544
  const slideTone = normalizeHighlightTone(s.tone);
1545
+ const slideStyle = {
1546
+ size: normalizeHighlightSize(s.size),
1547
+ weight: normalizeHighlightWeight(s.weight),
1548
+ };
1513
1549
  const isLast = i === slides.length - 1;
1514
1550
  const dwellMs = resolveFixtureDwellMs(s, { gatesFast, pace: demoPace });
1515
1551
  await installOverlay(page, title);
1516
1552
  if (s.selector) {
1517
1553
  try {
1518
1554
  const box = await page.locator(s.selector).first().boundingBox();
1519
- if (box) await showRing(page, box, caption, slideTone);
1555
+ if (box) await showRing(page, box, caption, slideTone, slideStyle);
1520
1556
  } catch {
1521
1557
  await hideRing(page);
1522
1558
  }
@@ -1828,7 +1864,10 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
1828
1864
  const stubs = (stubBeforeRef && stubBeforeRef.current) || [];
1829
1865
  const cap = await captionForLocator(page, this, stubs, fallback);
1830
1866
  await moveCursorTo(page, box, cursorMs(500));
1831
- await showRing(page, box, cap.label, cap.tone);
1867
+ await showRing(page, box, cap.label, cap.tone, {
1868
+ size: cap.size,
1869
+ weight: cap.weight,
1870
+ });
1832
1871
  await dwellMatchedStub(page, this, stubs, pacing);
1833
1872
  await new Promise((res) => setTimeout(res, skipTheater() ? 0 : 200));
1834
1873
  } else if (box) {
@@ -1900,7 +1939,10 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
1900
1939
  const cap = await captionForLocator(page, this, stubs, fallback);
1901
1940
  clickTone = cap.tone;
1902
1941
  clickPoint = await moveCursorTo(page, box, cursorMs(600));
1903
- await showRing(page, box, cap.label, cap.tone);
1942
+ await showRing(page, box, cap.label, cap.tone, {
1943
+ size: cap.size,
1944
+ weight: cap.weight,
1945
+ });
1904
1946
  await dwellMatchedStub(page, this, stubs, pacing);
1905
1947
  // "pop for a few seconds" - Dan's own phrase, matching the
1906
1948
  // zsign demo-engine's ring-before-click pattern in
@@ -2236,7 +2278,10 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
2236
2278
  !!r.block.fastForward ||
2237
2279
  !!r.wasFastForward;
2238
2280
  const fixtures = r.highlightFixtures;
2239
- stubBeforeRef.current = resolveHighlightSlots(r.block, "stubBefore", { fixtures });
2281
+ const flowStyle = r.highlightStyle;
2282
+ stubBeforeRef.current = resolveHighlightSlots(r.block, "stubBefore", { fixtures }).map((h) =>
2283
+ applyHighlightStyleDefaults(h, flowStyle),
2284
+ );
2240
2285
  const isNavBlock = r.block.__waygraphKind === "nav";
2241
2286
  const autoNow = await currentAutoplay();
2242
2287
  // Auto-next: tuck stepper on NavBlocks so the page transition fills the frame.
@@ -2328,13 +2373,18 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
2328
2373
  .catch(() => {});
2329
2374
  const errHighlights = resolveHighlightSlots(r.block, "stubOnError", {
2330
2375
  fixtures: fixturesOnError,
2331
- }).map((h) => ({
2332
- selector: h.selector,
2333
- label: formatHighlightCaption(h),
2334
- duration: h.duration,
2335
- fastMode: h.fastMode,
2336
- tone: normalizeHighlightTone(h.tone),
2337
- }));
2376
+ }).map((h) => {
2377
+ const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
2378
+ return {
2379
+ selector: styled.selector,
2380
+ label: formatHighlightCaption(styled),
2381
+ duration: styled.duration,
2382
+ fastMode: styled.fastMode,
2383
+ tone: styled.tone,
2384
+ size: styled.size,
2385
+ weight: styled.weight,
2386
+ };
2387
+ });
2338
2388
  await cycleHighlightRings(page, errHighlights, !!pacing.gatesFast, {
2339
2389
  defaultHoldMs: 2000,
2340
2390
  });
@@ -2387,7 +2437,9 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
2387
2437
  .catch(() => {});
2388
2438
  result = stepOutcome.result;
2389
2439
  const fixturesAfter = r.highlightFixtures;
2390
- const slides = resolveSlides(r.block, { out: result, fixtures: fixturesAfter });
2440
+ const slides = resolveSlides(r.block, { out: result, fixtures: fixturesAfter }).map((s) =>
2441
+ applyHighlightStyleDefaults(s, r.highlightStyle),
2442
+ );
2391
2443
  if (slides.length > 0) {
2392
2444
  await presentSlides(page, slides, gate, {
2393
2445
  title,
@@ -2402,16 +2454,23 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
2402
2454
  let highlights;
2403
2455
  if (hasAuthoredStubAfter(r.block, result, fixturesAfter)) {
2404
2456
  highlights = resolveHighlightSlots(r.block, "stubAfter", { out: result, fixtures: fixturesAfter }).map(
2405
- (h) => ({
2406
- selector: h.selector,
2407
- label: formatHighlightCaption(h),
2408
- duration: h.duration,
2409
- fastMode: h.fastMode,
2410
- tone: normalizeHighlightTone(h.tone),
2411
- }),
2457
+ (h) => {
2458
+ const styled = applyHighlightStyleDefaults(h, r.highlightStyle);
2459
+ return {
2460
+ selector: styled.selector,
2461
+ label: formatHighlightCaption(styled),
2462
+ duration: styled.duration,
2463
+ fastMode: styled.fastMode,
2464
+ tone: styled.tone,
2465
+ size: styled.size,
2466
+ weight: styled.weight,
2467
+ };
2468
+ },
2412
2469
  );
2413
2470
  } else {
2414
- highlights = extractVerifyHighlights(r.block, result.__state);
2471
+ highlights = extractVerifyHighlights(r.block, result.__state).map((h) =>
2472
+ applyHighlightStyleDefaults(h, r.highlightStyle),
2473
+ );
2415
2474
  }
2416
2475
  await renderAfterStep(page, {
2417
2476
  index: i,
@@ -2593,6 +2652,7 @@ async function main() {
2593
2652
  seedMem: idx === 0 ? seedMem : undefined,
2594
2653
  highlightFixtures: flow.highlightFixtures,
2595
2654
  demoPace: flow.demoPace,
2655
+ highlightStyle: flow.highlightStyle,
2596
2656
  expectedFailureReason:
2597
2657
  flow.expectedFailureReason && idx === blockInfos.length - 1
2598
2658
  ? flow.expectedFailureReason
@@ -2662,6 +2722,7 @@ async function main() {
2662
2722
  expectedFailureReason: flow.expectedFailureReason,
2663
2723
  highlightFixtures: flow.highlightFixtures,
2664
2724
  demoPace: flow.demoPace,
2725
+ highlightStyle: flow.highlightStyle,
2665
2726
  seedMem: () => seedMemForFlow(mem, flow.blocks(), seg.json, seg.ref),
2666
2727
  });
2667
2728
  } else {
@@ -2672,6 +2733,7 @@ async function main() {
2672
2733
  episodeTitle: undefined,
2673
2734
  highlightFixtures: undefined,
2674
2735
  demoPace: undefined,
2736
+ highlightStyle: undefined,
2675
2737
  seedMem: () => seedMemForBlock(mem, r, seg.json),
2676
2738
  });
2677
2739
  }
@@ -2697,6 +2759,7 @@ async function main() {
2697
2759
  episodeTitle: meta.episodeTitle,
2698
2760
  highlightFixtures: meta.highlightFixtures,
2699
2761
  demoPace: meta.demoPace,
2762
+ highlightStyle: meta.highlightStyle,
2700
2763
  expectedFailureReason:
2701
2764
  meta.expectedFailureReason && remainingInFlow === 1 ? meta.expectedFailureReason : undefined,
2702
2765
  seedMem: isFirstOfSegment ? meta.seedMem : undefined,
package/dist/engine.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { Block, Checkpoint, Instruction, DefinedBlock, ActionPage, Waygraph
3
3
  import { MemPage } from "./mem-page.js";
4
4
  import type { MemKey } from "./mem-page.js";
5
5
  import type { Trait } from "./trait.js";
6
+ import { type HighlightStyleDefaults, type DemoPace } from "./highlights.js";
6
7
  /**
7
8
  * Checks `block.requires` against `mem` and throws, naming every missing key at
8
9
  * once, if any are unset. `connect()` unions each Block's `requires` into the
@@ -191,6 +192,11 @@ export interface Flow<Out extends Checkpoint<string>> {
191
192
  * `run()` ignores; step/demo applies per Block in the episode.
192
193
  */
193
194
  readonly demoPace?: import("./highlights.js").DemoPace;
195
+ /**
196
+ * Default highlight size/weight/tone for this Flow's episode (`withHighlightStyle`).
197
+ * Per-slot stub/fixture values win. `run()` ignores.
198
+ */
199
+ readonly highlightStyle?: import("./highlights.js").HighlightStyleDefaults;
194
200
  /**
195
201
  * This Flow's constituent Blocks, in order between `start`/`end`, as plain
196
202
  * data - for introspection/visualization tools, without running anything.
@@ -264,12 +270,18 @@ export declare function withTitle<Out extends Checkpoint<string>>(flow: Flow<Out
264
270
  * use this when an entire episode should be fast or slow.
265
271
  * @example withDemoPace(withTitle(gapFlow, "Episode 2 - gaps"), "slow")
266
272
  */
267
- export declare function withDemoPace<Out extends Checkpoint<string>>(flow: Flow<Out>, pace: import("./highlights.js").DemoPace): Flow<Out>;
273
+ export declare function withDemoPace<Out extends Checkpoint<string>>(flow: Flow<Out>, pace: DemoPace): Flow<Out>;
274
+ /**
275
+ * Default highlight look for a Flow episode (size / weight / optional tone).
276
+ * Per-slot stub or {@link withHighlightFixtures} values win when set.
277
+ * @example withHighlightStyle(withDemoPace(gapFlow, "slow"), { size: "lg", weight: "bold" })
278
+ */
279
+ export declare function withHighlightStyle<Out extends Checkpoint<string>>(flow: Flow<Out>, style: HighlightStyleDefaults): Flow<Out>;
268
280
  /**
269
281
  * Demo pacing for one Block or compose unit (not FF - use fastForwardComposeBlock for blitz).
270
282
  * @example withBlockPace(composeBlock("gap-review", [...]), "slow")
271
283
  */
272
- export declare function withBlockPace<In extends Checkpoint<string>, Out extends Checkpoint<string>>(block: Block<In, Out>, pace: import("./highlights.js").DemoPace): Block<In, Out>;
284
+ export declare function withBlockPace<In extends Checkpoint<string>, Out extends Checkpoint<string>>(block: Block<In, Out>, pace: DemoPace): Block<In, Out>;
273
285
  /**
274
286
  * Marks a Flow as one that's SUPPOSED to throw - e.g. a locked-out user's
275
287
  * login attempt genuinely failing, demonstrating the app's own real
package/dist/engine.js CHANGED
@@ -2,6 +2,7 @@ import { chromium, firefox, webkit } from "@playwright/test";
2
2
  import { connect, checkpoint } from "./types.js";
3
3
  import { MemPage } from "./mem-page.js";
4
4
  import { runVerify, runPrecondition } from "./trait.js";
5
+ import { normalizeHighlightTone, normalizeHighlightSize, normalizeHighlightWeight, } from "./highlights.js";
5
6
  const LAUNCHERS = { chromium, firefox, webkit };
6
7
  /**
7
8
  * Resolves which `BrowserType` actually launches for a `mem`-only run (no
@@ -366,6 +367,24 @@ export function withDemoPace(flow, pace) {
366
367
  modBlockVerify: (block, nameOrIndex, newCheck) => withDemoPace(flow.modBlockVerify(block, nameOrIndex, newCheck), demoPace),
367
368
  };
368
369
  }
370
+ /**
371
+ * Default highlight look for a Flow episode (size / weight / optional tone).
372
+ * Per-slot stub or {@link withHighlightFixtures} values win when set.
373
+ * @example withHighlightStyle(withDemoPace(gapFlow, "slow"), { size: "lg", weight: "bold" })
374
+ */
375
+ export function withHighlightStyle(flow, style) {
376
+ const highlightStyle = {
377
+ ...(style.tone !== undefined ? { tone: normalizeHighlightTone(style.tone) } : {}),
378
+ ...(style.size !== undefined ? { size: normalizeHighlightSize(style.size) } : {}),
379
+ ...(style.weight !== undefined ? { weight: normalizeHighlightWeight(style.weight) } : {}),
380
+ };
381
+ return {
382
+ ...flow,
383
+ highlightStyle,
384
+ withBlockVerify: (block, verify) => withHighlightStyle(flow.withBlockVerify(block, verify), highlightStyle),
385
+ modBlockVerify: (block, nameOrIndex, newCheck) => withHighlightStyle(flow.modBlockVerify(block, nameOrIndex, newCheck), highlightStyle),
386
+ };
387
+ }
369
388
  /**
370
389
  * Demo pacing for one Block or compose unit (not FF - use fastForwardComposeBlock for blitz).
371
390
  * @example withBlockPace(composeBlock("gap-review", [...]), "slow")
@@ -40,9 +40,19 @@ export interface WaygraphHighlightStub extends FixtureDurationFields {
40
40
  * - `info` blue · `warning` yellow · `danger` red · `success` green
41
41
  */
42
42
  tone?: HighlightTone;
43
+ /**
44
+ * Ring padding + label font scale: `sm` | `md` (default) | `lg`.
45
+ * Aliases: small/medium/large, s/m/l.
46
+ */
47
+ size?: HighlightSize;
48
+ /**
49
+ * Label font weight: `normal` (default) | `bold`.
50
+ * Aliases: regular, strong.
51
+ */
52
+ weight?: HighlightWeight;
43
53
  }
44
54
  /** Flow fixture patch: label required; selector optional (inherits from stub). */
45
- export type WaygraphHighlightFixture = Partial<Pick<WaygraphHighlightStub, "selector">> & Required<Pick<WaygraphHighlightStub, "label">> & Pick<WaygraphHighlightStub, "detail" | "tag" | "tone"> & FixtureDurationFields;
55
+ export type WaygraphHighlightFixture = Partial<Pick<WaygraphHighlightStub, "selector">> & Required<Pick<WaygraphHighlightStub, "label">> & Pick<WaygraphHighlightStub, "detail" | "tag" | "tone" | "size" | "weight"> & FixtureDurationFields;
46
56
  export type HighlightStubPhase = Record<string, WaygraphHighlightStub>;
47
57
  export type HighlightStubPhaseOrFn<Out extends Checkpoint<string>> = HighlightStubPhase | ((out: Out) => HighlightStubPhase);
48
58
  /**
@@ -57,14 +67,40 @@ export interface WaygraphSlide extends FixtureDurationFields {
57
67
  /** Optional ring while this slide is showing. */
58
68
  selector?: string;
59
69
  tone?: HighlightTone;
70
+ size?: HighlightSize;
71
+ weight?: HighlightWeight;
60
72
  }
61
73
  /**
62
74
  * Demo ring tones (iconified).
63
75
  * Automation (`auto`) is gray so authored planned/info/warning/danger/success stand out.
64
76
  */
65
77
  export type HighlightTone = "planned" | "auto" | "info" | "warning" | "danger" | "success";
78
+ /** Ring + label scale. */
79
+ export type HighlightSize = "sm" | "md" | "lg";
80
+ /** Label font weight. */
81
+ export type HighlightWeight = "normal" | "bold";
82
+ /** Optional Flow-level defaults for demo rings (episode-wide). Stub/fixture wins. */
83
+ export type HighlightStyleDefaults = {
84
+ tone?: HighlightTone;
85
+ size?: HighlightSize;
86
+ weight?: HighlightWeight;
87
+ };
88
+ /** Apply Flow defaults under per-slot authored values (slot wins when set). */
89
+ export declare function applyHighlightStyleDefaults<T extends {
90
+ tone?: HighlightTone | string;
91
+ size?: HighlightSize | string;
92
+ weight?: HighlightWeight | string;
93
+ }>(slot: T, defaults?: HighlightStyleDefaults | null): T & {
94
+ tone: HighlightTone;
95
+ size: HighlightSize;
96
+ weight: HighlightWeight;
97
+ };
66
98
  /** Map author aliases (`error` -> danger) to a canonical tone. */
67
99
  export declare function normalizeHighlightTone(raw: unknown): HighlightTone;
100
+ /** Map size aliases to sm/md/lg. */
101
+ export declare function normalizeHighlightSize(raw: unknown): HighlightSize;
102
+ /** Map weight aliases to normal/bold. */
103
+ export declare function normalizeHighlightWeight(raw: unknown): HighlightWeight;
68
104
  /** Prefix label with a short ASCII icon for semantic tones. */
69
105
  export declare function toneIconPrefix(tone: HighlightTone): string;
70
106
  /**
@@ -8,6 +8,25 @@
8
8
  * All fixture shapes share optional {@link FixtureDurationFields} so demo dwell
9
9
  * before advancing is authored the same way on stubs, flow fixtures, and slides.
10
10
  */
11
+ /** Apply Flow defaults under per-slot authored values (slot wins when set). */
12
+ export function applyHighlightStyleDefaults(slot, defaults) {
13
+ const tone = slot.tone !== undefined && slot.tone !== ""
14
+ ? normalizeHighlightTone(slot.tone)
15
+ : defaults?.tone !== undefined
16
+ ? normalizeHighlightTone(defaults.tone)
17
+ : "planned";
18
+ const size = slot.size !== undefined && slot.size !== ""
19
+ ? normalizeHighlightSize(slot.size)
20
+ : defaults?.size !== undefined
21
+ ? normalizeHighlightSize(defaults.size)
22
+ : "md";
23
+ const weight = slot.weight !== undefined && slot.weight !== ""
24
+ ? normalizeHighlightWeight(slot.weight)
25
+ : defaults?.weight !== undefined
26
+ ? normalizeHighlightWeight(defaults.weight)
27
+ : "normal";
28
+ return { ...slot, tone, size, weight };
29
+ }
11
30
  const TONE_ICONS = {
12
31
  planned: "",
13
32
  auto: "",
@@ -35,6 +54,26 @@ export function normalizeHighlightTone(raw) {
35
54
  return "planned";
36
55
  return "planned";
37
56
  }
57
+ /** Map size aliases to sm/md/lg. */
58
+ export function normalizeHighlightSize(raw) {
59
+ if (typeof raw !== "string")
60
+ return "md";
61
+ const t = raw.trim().toLowerCase();
62
+ if (t === "sm" || t === "s" || t === "small" || t === "tiny")
63
+ return "sm";
64
+ if (t === "lg" || t === "l" || t === "large" || t === "big" || t === "xl")
65
+ return "lg";
66
+ return "md";
67
+ }
68
+ /** Map weight aliases to normal/bold. */
69
+ export function normalizeHighlightWeight(raw) {
70
+ if (typeof raw !== "string")
71
+ return "normal";
72
+ const t = raw.trim().toLowerCase();
73
+ if (t === "bold" || t === "strong" || t === "heavy" || t === "b")
74
+ return "bold";
75
+ return "normal";
76
+ }
38
77
  /** Prefix label with a short ASCII icon for semantic tones. */
39
78
  export function toneIconPrefix(tone) {
40
79
  const icon = TONE_ICONS[tone];
@@ -116,10 +155,24 @@ function pickDurationFields(base, patch) {
116
155
  out.fastMode = fastMode;
117
156
  return out;
118
157
  }
158
+ function pickStyleFields(base, patch) {
159
+ const out = {};
160
+ const tone = patch?.tone !== undefined ? patch.tone : base?.tone;
161
+ const size = patch?.size !== undefined ? patch.size : base?.size;
162
+ const weight = patch?.weight !== undefined ? patch.weight : base?.weight;
163
+ if (tone !== undefined)
164
+ out.tone = normalizeHighlightTone(tone);
165
+ if (size !== undefined)
166
+ out.size = normalizeHighlightSize(size);
167
+ if (weight !== undefined)
168
+ out.weight = normalizeHighlightWeight(weight);
169
+ return out;
170
+ }
119
171
  function mergeSlot(base, patch) {
120
172
  if (!base && !patch)
121
173
  return null;
122
174
  const dwell = pickDurationFields(base, patch);
175
+ const style = pickStyleFields(base, patch);
123
176
  if (!base && patch) {
124
177
  if (!patch.selector)
125
178
  return null;
@@ -128,12 +181,18 @@ function mergeSlot(base, patch) {
128
181
  label: patch.label,
129
182
  ...(patch.detail ? { detail: patch.detail } : {}),
130
183
  ...(patch.tag ? { tag: patch.tag } : {}),
131
- ...(patch.tone ? { tone: normalizeHighlightTone(patch.tone) } : {}),
184
+ ...style,
132
185
  ...dwell,
133
186
  };
134
187
  }
135
- if (base && !patch)
136
- return { ...base };
188
+ if (base && !patch) {
189
+ return {
190
+ ...base,
191
+ ...(base.tone ? { tone: normalizeHighlightTone(base.tone) } : {}),
192
+ ...(base.size ? { size: normalizeHighlightSize(base.size) } : {}),
193
+ ...(base.weight ? { weight: normalizeHighlightWeight(base.weight) } : {}),
194
+ };
195
+ }
137
196
  return {
138
197
  selector: patch.selector ?? base.selector,
139
198
  label: patch.label,
@@ -143,11 +202,7 @@ function mergeSlot(base, patch) {
143
202
  ? { detail: base.detail }
144
203
  : {}),
145
204
  ...(patch.tag !== undefined ? { tag: patch.tag } : base.tag ? { tag: base.tag } : {}),
146
- ...(patch.tone !== undefined
147
- ? { tone: normalizeHighlightTone(patch.tone) }
148
- : base.tone
149
- ? { tone: normalizeHighlightTone(base.tone) }
150
- : {}),
205
+ ...style,
151
206
  ...dwell,
152
207
  };
153
208
  }
package/dist/index.d.ts CHANGED
@@ -2,9 +2,9 @@ export type { Checkpoint, Instruction, Block, DefinedBlock, Start, WaygraphInsta
2
2
  export { connect, checkpoint } from "./types.js";
3
3
  export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
4
4
  export { Trait, urlMatches, textEquals, visible } from "./trait.js";
5
- export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
6
- export { resolveHighlightSlots, resolveSlides, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, toneIconPrefix, resolveStepDemoPace, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
7
- export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, DemoPace, } from "./highlights.js";
5
+ export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
6
+ export { resolveHighlightSlots, resolveSlides, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
7
+ export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, } from "./highlights.js";
8
8
  export type { Flow, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, } from "./engine.js";
9
9
  export type { ActionPage } from "./types.js";
10
10
  export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  export { connect, checkpoint } from "./types.js";
2
2
  export { MemKey, MemPage, key, keyGroup } from "./mem-page.js";
3
3
  export { Trait, urlMatches, textEquals, visible } from "./trait.js";
4
- export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
5
- export { resolveHighlightSlots, resolveSlides, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, toneIconPrefix, resolveStepDemoPace, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
4
+ export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, } from "./engine.js";
5
+ export { resolveHighlightSlots, resolveSlides, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, } from "./highlights.js";
6
6
  export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
7
7
  export { isFileSelectToken, parseBlocksSelect, matchBlocksSelect, globToRegExp, } from "./blocks-select.js";
8
8
  export { EdgeLeaseCoordinator } from "./traverse-lease.js";
@@ -15,6 +15,12 @@ const RING_CSS = "#wg-ring{position:fixed;z-index:2147483646;pointer-events:none
15
15
  "#wg-ring-label[data-tone=warning]{background:#EAB308;color:#1c1917;}" +
16
16
  "#wg-ring-label[data-tone=danger]{background:#DC2626;color:#fff;}" +
17
17
  "#wg-ring-label[data-tone=success]{background:#16A34A;color:#fff;}" +
18
+ "#wg-ring[data-size=sm]{border-width:1.5px;border-radius:8px;}" +
19
+ "#wg-ring[data-size=lg]{border-width:4px;border-radius:12px;}" +
20
+ "#wg-ring-label[data-size=sm]{font-size:10px;line-height:1.25;padding:4px 7px;border-radius:5px;}" +
21
+ "#wg-ring-label[data-size=lg]{font-size:16px;line-height:1.35;padding:8px 14px;border-radius:9px;}" +
22
+ "#wg-ring-label[data-weight=bold]{font-weight:800;}" +
23
+ "#wg-ring-label[data-weight=normal]{font-weight:600;}" +
18
24
  "#wg-cursor{position:fixed;z-index:2147483647;width:24px;height:24px;pointer-events:none;" +
19
25
  "left:0;top:0;opacity:0;margin:0;" +
20
26
  "transition:transform var(--wg-cursor-ms,600ms) cubic-bezier(.22,1,.36,1),opacity .2s ease;" +
@@ -93,7 +99,7 @@ export async function installDemoChrome(page, title = "waygraph auto", options =
93
99
  void pulse.offsetWidth;
94
100
  pulse.classList.add("wg-pulse");
95
101
  };
96
- window.__wgPositionRing = (box, label, tone) => {
102
+ window.__wgPositionRing = (box, label, tone, style) => {
97
103
  const ring = document.getElementById("wg-ring");
98
104
  const ringLabel = document.getElementById("wg-ring-label");
99
105
  if (!ring || !ringLabel)
@@ -106,12 +112,20 @@ export async function installDemoChrome(page, title = "waygraph auto", options =
106
112
  raw === "success"
107
113
  ? raw
108
114
  : "planned";
115
+ const sizeRaw = ((style && style.size) || "md") + "";
116
+ const size = sizeRaw === "sm" || sizeRaw === "lg" ? sizeRaw : "md";
117
+ const weightRaw = ((style && style.weight) || "normal") + "";
118
+ const weight = weightRaw === "bold" ? "bold" : "normal";
109
119
  ring.dataset.tone = t;
110
120
  ringLabel.dataset.tone = t;
111
- ring.style.left = box.x - 6 + "px";
112
- ring.style.top = box.y - 6 + "px";
113
- ring.style.width = box.width + 12 + "px";
114
- ring.style.height = box.height + 12 + "px";
121
+ ring.dataset.size = size;
122
+ ringLabel.dataset.size = size;
123
+ ringLabel.dataset.weight = weight;
124
+ const pad = size === "sm" ? 3 : size === "lg" ? 10 : 6;
125
+ ring.style.left = box.x - pad + "px";
126
+ ring.style.top = box.y - pad + "px";
127
+ ring.style.width = box.width + pad * 2 + "px";
128
+ ring.style.height = box.height + pad * 2 + "px";
115
129
  ring.style.opacity = "1";
116
130
  ringLabel.textContent = label;
117
131
  ringLabel.style.left = box.x + "px";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.12.15",
3
+ "version": "0.12.17",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",