waygraph 0.15.2 → 0.15.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/auto-session-ipc.d.ts +14 -0
  2. package/dist/auto-session-ipc.js +4 -0
  3. package/dist/auto-session.d.ts +15 -0
  4. package/dist/auto-session.js +34 -1
  5. package/dist/cli.js +43 -2
  6. package/dist/engine.d.ts +124 -12
  7. package/dist/engine.js +170 -13
  8. package/dist/highlights.d.ts +8 -0
  9. package/dist/highlights.js +2 -0
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.js +1 -1
  12. package/dist/pilot-overlay.d.ts +42 -0
  13. package/dist/pilot-overlay.js +148 -0
  14. package/dist/types.d.ts +17 -0
  15. package/examples/saucedemo/.waygraph-auto/1d266123.log +0 -0
  16. package/examples/saucedemo/.waygraph-auto/2c7e8edd.log +0 -0
  17. package/examples/saucedemo/.waygraph-auto/49edc885.log +4 -0
  18. package/examples/saucedemo/.waygraph-auto/79600ead.log +0 -0
  19. package/examples/saucedemo/.waygraph-auto/d2c6ed66.log +0 -0
  20. package/examples/saucedemo/.waygraph-auto/ddcb4212.log +0 -0
  21. package/examples/saucedemo/.waygraph-auto/ebc2ebdb.log +0 -0
  22. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +8 -7
  23. package/examples/saucedemo/src/blocks/saucedemo-web/methods/fill-password.method.block.ts +8 -10
  24. package/examples/saucedemo/src/blocks/saucedemo-web/methods/fill-username.method.block.ts +11 -10
  25. package/examples/saucedemo/src/blocks/saucedemo-web/methods/submit-login-for-flow.ts +5 -4
  26. package/examples/saucedemo/src/flows/checkout.flow.ts +61 -97
  27. package/examples/saucedemo/src/flows/login.flow.ts +11 -2
  28. package/examples/saucedemo/src/flows/shop.flow.ts +13 -13
  29. package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +9 -2
  30. package/package.json +1 -1
  31. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/methods/finish-order.method.block.ts +8 -7
  32. package/templates/quickstart/src/blocks/saucedemo-web/methods/fill-password.method.block.ts +8 -10
  33. package/templates/quickstart/src/blocks/saucedemo-web/methods/fill-username.method.block.ts +11 -10
  34. package/templates/quickstart/src/blocks/saucedemo-web/methods/submit-login-for-flow.ts +5 -4
  35. package/templates/quickstart/src/flows/checkout.flow.ts +61 -97
  36. package/templates/quickstart/src/flows/login.flow.ts +11 -2
  37. package/templates/quickstart/src/flows/shop.flow.ts +13 -13
  38. package/templates/quickstart/src/flows/viewer-blocked.flow.ts +9 -2
  39. package/templates/skills/waygraph-blind-pilot.skill.md +2 -1
  40. package/templates/skills/waygraph-pilot.skill.md +17 -0
  41. package/examples/saucedemo/.waygraph-auto/0b1f5b26.json +0 -8
package/dist/engine.js CHANGED
@@ -811,52 +811,108 @@ export class MapBuilder {
811
811
  engine;
812
812
  steps;
813
813
  homeOrigin;
814
- constructor(engine, steps, homeOrigin) {
814
+ ff;
815
+ constructor(engine, steps, homeOrigin, ff = null) {
815
816
  this.engine = engine;
816
817
  this.steps = steps;
817
818
  this.homeOrigin = homeOrigin;
819
+ this.ff = ff;
818
820
  }
819
821
  /** @internal - use `engine.map()` or the standalone `map()` export. */
820
822
  static begin(engine, homeOrigin) {
821
- return new MapBuilder(engine, [], homeOrigin);
823
+ return new MapBuilder(engine, [], homeOrigin, null);
822
824
  }
823
825
  /** Readable chain-opener - mirrors `defineFlow`'s leading `start` sentinel. Returns `this` unchanged; entirely optional. */
824
826
  start() {
825
827
  return this;
826
828
  }
829
+ /**
830
+ * Open a fast-forward window - steps until {@link ffEnd} collapse into one
831
+ * opaque `fastForwardComposeBlock` (blitz theater). Kind/salt checks still
832
+ * run on each inner step as it is added.
833
+ */
834
+ ffStart(name) {
835
+ if (this.ff) {
836
+ throw new Error(`Waygraph map: .ffStart() while already inside "${this.ff.name}" - call .ffEnd() first`);
837
+ }
838
+ const ffName = typeof name === "string" && name.trim()
839
+ ? name.trim()
840
+ : `ff-${this.steps.length + 1}`;
841
+ return new MapBuilder(this.engine, this.steps, this.homeOrigin, {
842
+ name: ffName,
843
+ buffer: [],
844
+ });
845
+ }
846
+ /**
847
+ * Close the current fast-forward window and append one FFCompose step.
848
+ * Throws if empty or if {@link ffStart} was never opened.
849
+ */
850
+ ffEnd() {
851
+ if (!this.ff) {
852
+ throw new Error("Waygraph map: .ffEnd() with no open .ffStart()");
853
+ }
854
+ if (this.ff.buffer.length === 0) {
855
+ throw new Error(`Waygraph map: .ffEnd() for "${this.ff.name}" has no steps - add .gotoPage/.method/… inside the window`);
856
+ }
857
+ const composed = fastForwardComposeBlock(this.ff.name,
858
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
859
+ this.ff.buffer);
860
+ return new MapBuilder(this.engine, [...this.steps, composed], this.homeOrigin, null);
861
+ }
862
+ appendStep(block) {
863
+ if (this.ff) {
864
+ return new MapBuilder(this.engine, this.steps, this.homeOrigin, {
865
+ name: this.ff.name,
866
+ buffer: [...this.ff.buffer, block],
867
+ });
868
+ }
869
+ return new MapBuilder(this.engine, [...this.steps, block], this.homeOrigin, null);
870
+ }
827
871
  /**
828
872
  * Appends an internal navigation/page-arrival step - requires a Block from
829
873
  * `defineNavBlock`/`defineMemNavBlock`/`definePageBlock`. Throws if given
830
874
  * anything else, including a `homeOrigin`-mismatched static `url`.
875
+ * Compile-time: {@link NavBlock} or {@link PageBlock} only (not Assert/Method).
831
876
  */
832
877
  gotoPage(block) {
833
878
  assertMapKind(block, ["nav", "page"], "gotoPage");
834
879
  assertMapOrigin(block, this.homeOrigin, "internal", "gotoPage");
835
- return new MapBuilder(this.engine, [...this.steps, block], this.homeOrigin);
880
+ return this.appendStep(block);
836
881
  }
837
882
  /**
838
883
  * Appends a genuinely cross-origin navigation step (e.g. `(external)/`
839
884
  * Blocks in the Waygraph Map convention: mailpit, maildrop.cc) - same
840
885
  * kind requirement as {@link gotoPage}, plus the inverse `homeOrigin` check.
886
+ * Compile-time: {@link NavBlock} or {@link PageBlock} only.
841
887
  */
842
888
  gotoExternal(block) {
843
889
  assertMapKind(block, ["nav", "page"], "gotoExternal");
844
890
  assertMapOrigin(block, this.homeOrigin, "external", "gotoExternal");
845
- return new MapBuilder(this.engine, [...this.steps, block], this.homeOrigin);
891
+ return this.appendStep(block);
846
892
  }
847
- /** Appends a self-loop verification step - requires a Block from `defineAssertBlock`. */
893
+ /**
894
+ * Appends a self-loop verification step - requires a Block from
895
+ * `defineAssertBlock`. Compile-time: {@link AssertBlock} only (not `.method()`).
896
+ */
848
897
  assert(block) {
849
898
  assertMapKind(block, ["assert"], "assert");
850
- return new MapBuilder(this.engine, [...this.steps, block], this.homeOrigin);
899
+ return this.appendStep(block);
851
900
  }
852
901
  /**
853
902
  * Appends a non-navigating page action (submit, upload, add/remove an
854
903
  * instance) - requires a Block from `defineMethodBlock`/`defineActionBlock`/
855
904
  * `defineEffectBlock`/`defineMemEffectBlock`.
905
+ * Compile-time: {@link MethodBlock} or {@link EffectBlock} only - Assert
906
+ * Blocks must use `.assert()`. Runtime also rejects `__waygraphKind =
907
+ * "assert"` (those still carry method salt from the factory internals).
856
908
  */
857
909
  method(block) {
910
+ if (mapKindOf(block) === "assert") {
911
+ throw new Error(`Waygraph map: .method("${block.name}") got a defineAssertBlock - use .assert() for ` +
912
+ "self-loop verify steps (Assert Blocks share method salt internally but are not methods).");
913
+ }
858
914
  assertMapSalt(block, ["method", "effect"], "method");
859
- return new MapBuilder(this.engine, [...this.steps, block], this.homeOrigin);
915
+ return this.appendStep(block);
860
916
  }
861
917
  /**
862
918
  * Finalizes this chain into a real, runnable {@link Flow} - same object
@@ -867,6 +923,9 @@ export class MapBuilder {
867
923
  * isn't a flow.
868
924
  */
869
925
  end() {
926
+ if (this.ff) {
927
+ throw new Error(`Waygraph map: .end() while .ffStart("${this.ff.name}") is still open - call .ffEnd() first`);
928
+ }
870
929
  if (this.steps.length === 0) {
871
930
  throw new Error("Waygraph map: .end() called with zero steps - add at least one .gotoPage()/.gotoExternal()/" +
872
931
  ".assert()/.method() before .end()");
@@ -937,8 +996,38 @@ function stripMethods(block) {
937
996
  ...(block.next ? { next: block.next } : {}),
938
997
  ...(block.requires ? { requires: block.requires } : {}),
939
998
  ...(block.routes ? { routes: block.routes } : {}),
999
+ ...(block.instanceOptions ? { instanceOptions: block.instanceOptions } : {}),
940
1000
  };
941
1001
  }
1002
+ /** Copy non-enumerable waygraph markers so decorate/withVerify keep map() kind checks. */
1003
+ function copyWaygraphRuntime(from, to) {
1004
+ for (const key of [
1005
+ "__waygraphKind",
1006
+ "__waygraphSalt",
1007
+ "__waygraphNavUrl",
1008
+ "__waygraphNavClick",
1009
+ ]) {
1010
+ const desc = Object.getOwnPropertyDescriptor(from, key);
1011
+ if (!desc)
1012
+ continue;
1013
+ // Re-define as configurable so later decorate chains can copy again
1014
+ // (factory markers are non-configurable on the original).
1015
+ Object.defineProperty(to, key, {
1016
+ value: desc.value,
1017
+ enumerable: false,
1018
+ writable: false,
1019
+ configurable: true,
1020
+ });
1021
+ }
1022
+ const ff = from.fastForward;
1023
+ if (ff === true) {
1024
+ to.fastForward = true;
1025
+ }
1026
+ const pace = from.demoPace;
1027
+ if (pace !== undefined) {
1028
+ to.demoPace = pace;
1029
+ }
1030
+ }
942
1031
  /**
943
1032
  * Builds a Block with `withVerify`/`modVerify`/`modVerifyAll` attached as
944
1033
  * methods - `LoginBlock.withVerify([...])`, discoverable by typing
@@ -958,12 +1047,19 @@ export function defineBlock(base) {
958
1047
  ...(base.routes ? { routes: base.routes } : {}),
959
1048
  ...(base.instanceOptions ? { instanceOptions: base.instanceOptions } : {}),
960
1049
  };
961
- return {
1050
+ // Methods must close over `defined` (this object), not `plain` - factories
1051
+ // attach __waygraphKind/__waygraphSalt on the returned object after
1052
+ // defineBlock returns; decorate/withVerify need those markers.
1053
+ const defined = {
962
1054
  ...plain,
963
- withVerify: (verify) => withVerify(plain, verify),
964
- modVerify: (nameOrIndex, newCheck) => modVerify(plain, nameOrIndex, newCheck),
965
- modVerifyAll: (patches) => modVerifyAll(plain, patches),
1055
+ withVerify: (verify) => withVerify(defined, verify),
1056
+ modVerify: (nameOrIndex, newCheck) => modVerify(defined, nameOrIndex, newCheck),
1057
+ modVerifyAll: (patches) => modVerifyAll(defined, patches),
1058
+ stubBefore: (stub) => withStubBefore(defined, stub),
1059
+ stubAfter: (stub) => withStubAfter(defined, stub),
1060
+ stubOnError: (stub) => withStubOnError(defined, stub),
966
1061
  };
1062
+ return defined;
967
1063
  }
968
1064
  /**
969
1065
  * Builds a {@link NavBlock}. The generated `act()` is always exactly
@@ -1120,6 +1216,8 @@ export function defineAssertBlock(options) {
1120
1216
  enumerable: false,
1121
1217
  configurable: false,
1122
1218
  });
1219
+ // Brand for map().assert() - distinct from MethodBlock so .method(assert)
1220
+ // is a compile error even though runtime still carries method salt.
1123
1221
  return built;
1124
1222
  }
1125
1223
  function resolvePageMethods(methods) {
@@ -1326,7 +1424,64 @@ export async function locate(page, library, options) {
1326
1424
  */
1327
1425
  export function withVerify(block, verify) {
1328
1426
  const base = stripMethods(block);
1329
- return defineBlock({ ...base, instruction: { ...base.instruction, verify } });
1427
+ const next = defineBlock({ ...base, instruction: { ...base.instruction, verify } });
1428
+ copyWaygraphRuntime(block, next);
1429
+ return next;
1430
+ }
1431
+ function isHighlightStubPhase(v) {
1432
+ return !!v && typeof v === "object" && !Array.isArray(v);
1433
+ }
1434
+ /** Run a prior stub (fn or slot map) into an open ctx so decorate can chain. */
1435
+ async function applyStubIntoCtx(raw, ctx) {
1436
+ if (raw === undefined)
1437
+ return;
1438
+ if (typeof raw === "function") {
1439
+ const ret = await raw(ctx);
1440
+ if (isHighlightStubPhase(ret)) {
1441
+ for (const [id, stub] of Object.entries(ret)) {
1442
+ if (stub)
1443
+ ctx.ring(id, stub);
1444
+ }
1445
+ }
1446
+ return;
1447
+ }
1448
+ if (isHighlightStubPhase(raw)) {
1449
+ for (const [id, stub] of Object.entries(raw)) {
1450
+ if (stub)
1451
+ ctx.ring(id, stub);
1452
+ }
1453
+ }
1454
+ }
1455
+ function withStubPhase(block, phase, stub) {
1456
+ const base = stripMethods(block);
1457
+ const instr = base.instruction;
1458
+ const prev = instr[phase];
1459
+ const chained = async (ctx) => {
1460
+ await applyStubIntoCtx(prev, ctx);
1461
+ await applyStubIntoCtx(stub, ctx);
1462
+ };
1463
+ const next = defineBlock({
1464
+ ...base,
1465
+ instruction: { ...base.instruction, [phase]: chained },
1466
+ });
1467
+ copyWaygraphRuntime(block, next);
1468
+ return next;
1469
+ }
1470
+ /**
1471
+ * Attach / chain a stubBefore lifecycle on a Block. Prefer
1472
+ * `(ctx) => { ctx.ring(...); const x = ctx.mem?.get(Key); }`.
1473
+ * @example FillUsernameBlock.stubBefore((ctx) => { ctx.ring("username", { ... }) })
1474
+ */
1475
+ export function withStubBefore(block, stub) {
1476
+ return withStubPhase(block, "stubBefore", stub);
1477
+ }
1478
+ /** @see {@link withStubBefore} */
1479
+ export function withStubAfter(block, stub) {
1480
+ return withStubPhase(block, "stubAfter", stub);
1481
+ }
1482
+ /** @see {@link withStubBefore} */
1483
+ export function withStubOnError(block, stub) {
1484
+ return withStubPhase(block, "stubOnError", stub);
1330
1485
  }
1331
1486
  /**
1332
1487
  * Replaces one Trait inside `block`'s verify list, leaving every other check
@@ -1371,5 +1526,7 @@ export function modVerify(block, nameOrIndex, newCheck) {
1371
1526
  * same fail-loud-on-unknown-name behavior holds for every entry.
1372
1527
  */
1373
1528
  export function modVerifyAll(block, patches) {
1374
- return Object.entries(patches).reduce((current, [name, newCheck]) => modVerify(current, name, newCheck), defineBlock(stripMethods(block)));
1529
+ const seed = defineBlock(stripMethods(block));
1530
+ copyWaygraphRuntime(block, seed);
1531
+ return Object.entries(patches).reduce((current, [name, newCheck]) => modVerify(current, name, newCheck), seed);
1375
1532
  }
@@ -13,6 +13,7 @@
13
13
  * before advancing is authored the same way on stubs, flow fixtures, and slides.
14
14
  */
15
15
  import type { Block, Checkpoint } from "./types.js";
16
+ import type { MemPage } from "./mem-page.js";
16
17
  /** Shared dwell fields on stubs, fixtures, and yap slides. */
17
18
  export type FixtureDurationFields = {
18
19
  /**
@@ -313,6 +314,12 @@ export type HighlightStubPhase = Record<string, WaygraphHighlightStub>;
313
314
  * }
314
315
  */
315
316
  export type StubCtx<Out extends Checkpoint<string> = Checkpoint<string>> = {
317
+ /**
318
+ * Live Mem for this run - same store `act`/`observe` read. Prefer this over
319
+ * closing over outer scope or inventing parallel fixture bags.
320
+ * Undefined only if the caller ran a stub phase without passing mem (tests).
321
+ */
322
+ readonly mem: MemPage | undefined;
316
323
  /** Present after resolve (stubAfter). Undefined for stubBefore. */
317
324
  readonly out: Out | undefined;
318
325
  /** Present on stubOnError when the step threw. */
@@ -636,6 +643,7 @@ export declare function completeSequentialTodoDock(dock: TodoDockState | undefin
636
643
  export declare function runStubPhase(block: Block<any, any>, phase: HighlightStubPhaseName, opts?: {
637
644
  out?: Checkpoint<string>;
638
645
  error?: unknown;
646
+ mem?: MemPage;
639
647
  fixtures?: HighlightFixtureMap;
640
648
  }): Promise<StubPhaseResult>;
641
649
  /**
@@ -795,6 +795,7 @@ export function completeSequentialTodoDock(dock) {
795
795
  }
796
796
  function createStubCtx(bag, opts) {
797
797
  return {
798
+ mem: opts.mem,
798
799
  out: opts.out,
799
800
  error: opts.error,
800
801
  highlights(slots) {
@@ -1143,6 +1144,7 @@ export async function runStubPhase(block, phase, opts) {
1143
1144
  const ctx = createStubCtx(bag, {
1144
1145
  ...(opts?.out !== undefined ? { out: opts.out } : {}),
1145
1146
  ...(opts?.error !== undefined ? { error: opts.error } : {}),
1147
+ ...(opts?.mem !== undefined ? { mem: opts.mem } : {}),
1146
1148
  });
1147
1149
  const raw = readStubRaw(block, phase);
1148
1150
  if (typeof raw === "function") {
package/dist/index.d.ts CHANGED
@@ -2,10 +2,10 @@ 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, frameVisible, frameTextEquals, frameContainsText } from "./trait.js";
5
- 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, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, defineLayout, map, MapBuilder, } from "./engine.js";
5
+ 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, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, chainFlow, defineLayout, map, MapBuilder, } from "./engine.js";
6
6
  export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, normalizeDeviceOrientation, applyOrientation, orientationFromViewport, DEVICE_PRESETS, 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";
7
7
  export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, WaygraphTodosOpts, 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 { Flow, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, AssertBlockOptions, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, Layout, LayoutOptions, MapBuilderOptions, } from "./engine.js";
8
+ 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";
9
9
  export type { ActionPage } from "./types.js";
10
10
  export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
11
11
  export type { WaygraphGraph, WaygraphNode, WaygraphEdge, SkippedBlock, OrphanBlock } from "./graph.js";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
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, frameVisible, frameTextEquals, frameContainsText } from "./trait.js";
4
- 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, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, chainFlow, defineLayout, map, MapBuilder, } from "./engine.js";
4
+ 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, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, chainFlow, defineLayout, map, MapBuilder, } from "./engine.js";
5
5
  export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, normalizeDeviceOrientation, applyOrientation, orientationFromViewport, DEVICE_PRESETS, 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";
6
6
  export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
7
7
  export { isFileSelectToken, parseBlocksSelect, matchBlocksSelect, globToRegExp, } from "./blocks-select.js";
@@ -52,8 +52,50 @@ export declare function showPilotActivity(page: Page, text: string): Promise<voi
52
52
  * raw click/type/upload IS a real Playwright-driven action (`auto` was
53
53
  * always documented as meaning exactly that), while a pure `auto dom` read
54
54
  * touches nothing, so it gets its own "orange" tone instead.
55
+ *
56
+ * For agent-authored multi-ring / todo narration, use {@link showPilotFixtures}
57
+ * (`auto highlight`) instead - this vision ring is one-shot inspect feedback.
55
58
  */
56
59
  export declare function showPilotVision(page: Page, selector: string, label: string, tone?: HighlightTone): Promise<void>;
60
+ /** One ring the Pilot agent wants painted (same fields as demo stub rings). */
61
+ export type PilotFixtureRing = {
62
+ selector: string;
63
+ label: string;
64
+ tone?: HighlightTone;
65
+ size?: "sm" | "md" | "lg";
66
+ weight?: "normal" | "bold";
67
+ };
68
+ /**
69
+ * Agent-sent highlight fixtures for a live Pilot / auto session.
70
+ * Painted by {@link showPilotFixtures}; driven via IPC `op: "highlight"`.
71
+ */
72
+ export type PilotHighlightFixtures = {
73
+ /** Replace current fixture rings with these (empty + no todos = clear). */
74
+ rings?: readonly PilotFixtureRing[];
75
+ /** Optional floating todo list (demo-dock language, simplified). */
76
+ todos?: readonly string[];
77
+ /** 0-based index of the "current" todo row (others before it = done). */
78
+ todoIndex?: number;
79
+ /** Optional title above the todo list. */
80
+ todoTitle?: string;
81
+ /**
82
+ * How long fixtures stay visible (ms). `0` = until the next highlight /
83
+ * clear. Default `12000`. Vision rings (`showPilotVision`) stay separate.
84
+ */
85
+ holdMs?: number;
86
+ /** Drop every agent fixture ring/todo immediately. */
87
+ clear?: boolean;
88
+ };
89
+ /**
90
+ * 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.
93
+ * Does not import `cli.ts` (see file header).
94
+ */
95
+ export declare function showPilotFixtures(page: Page, fixtures: PilotHighlightFixtures): Promise<{
96
+ painted: number;
97
+ missing: string[];
98
+ }>;
57
99
  /** Minimal shape needed to render the graph tree - matches WaygraphGraph. */
58
100
  export interface PilotOverlayGraph {
59
101
  nodes: readonly {
@@ -60,6 +60,44 @@ const OVERLAY_CSS = `
60
60
  opacity: 0; transform: translateY(4px); transition: opacity 0.15s, transform 0.15s;
61
61
  }
62
62
  #wg-pilot-activity.wg-pilot-toast-show { opacity: 1; transform: translateY(0); }
63
+ /* Agent-sent fixture rings share tone/size CSS with #wg-ring (demo language). */
64
+ .wg-pilot-fx-ring{position:fixed;z-index:2147483645;pointer-events:none;opacity:0;
65
+ border:3px solid #7C3AED;border-radius:10px;box-sizing:border-box;
66
+ transition:opacity .12s ease,left .12s,top .12s,width .12s,height .12s;}
67
+ .wg-pilot-fx-ring[data-tone=planned]{border-color:#7C3AED;box-shadow:0 0 0 4px rgba(124,58,237,.16);}
68
+ .wg-pilot-fx-ring[data-tone=auto]{border-color:#9CA3AF;box-shadow:0 0 0 4px rgba(156,163,175,.28);}
69
+ .wg-pilot-fx-ring[data-tone=info]{border-color:#3B82F6;box-shadow:0 0 0 4px rgba(59,130,246,.22);}
70
+ .wg-pilot-fx-ring[data-tone=warning]{border-color:#EAB308;box-shadow:0 0 0 4px rgba(234,179,8,.22);}
71
+ .wg-pilot-fx-ring[data-tone=danger]{border-color:#EF4444;box-shadow:0 0 0 4px rgba(239,68,68,.22);}
72
+ .wg-pilot-fx-ring[data-tone=success]{border-color:#22C55E;box-shadow:0 0 0 4px rgba(34,197,94,.22);}
73
+ .wg-pilot-fx-ring[data-tone=orange]{border-color:#F97316;box-shadow:0 0 0 4px rgba(249,115,22,.22);}
74
+ .wg-pilot-fx-label{position:fixed;z-index:2147483645;pointer-events:none;opacity:0;
75
+ font:600 12px/1.3 ui-sans-serif,system-ui,sans-serif;padding:5px 9px;border-radius:6px;
76
+ max-width:min(360px,80vw);box-shadow:0 2px 8px rgba(0,0,0,.25);white-space:nowrap;
77
+ overflow:hidden;text-overflow:ellipsis;transition:opacity .12s ease;}
78
+ .wg-pilot-fx-label[data-tone=planned]{background:#7C3AED;color:#fff;}
79
+ .wg-pilot-fx-label[data-tone=auto]{background:#6B7280;color:#fff;}
80
+ .wg-pilot-fx-label[data-tone=info]{background:#2563EB;color:#fff;}
81
+ .wg-pilot-fx-label[data-tone=warning]{background:#EAB308;color:#1c1917;}
82
+ .wg-pilot-fx-label[data-tone=danger]{background:#DC2626;color:#fff;}
83
+ .wg-pilot-fx-label[data-tone=success]{background:#16A34A;color:#fff;}
84
+ .wg-pilot-fx-label[data-tone=orange]{background:#EA580C;color:#fff;}
85
+ .wg-pilot-fx-ring[data-size=sm]{border-width:1.5px;border-radius:8px;}
86
+ .wg-pilot-fx-ring[data-size=lg]{border-width:4px;border-radius:12px;}
87
+ .wg-pilot-fx-label[data-size=sm]{font-size:10px;padding:4px 7px;}
88
+ .wg-pilot-fx-label[data-size=lg]{font-size:16px;padding:8px 14px;}
89
+ .wg-pilot-fx-label[data-weight=bold]{font-weight:800;}
90
+ #wg-pilot-fx-todos{
91
+ position:fixed;z-index:2147483645;top:72px;left:14px;max-width:280px;
92
+ font:12px/1.4 ui-sans-serif,system-ui,sans-serif;color:#fff;
93
+ background:rgba(20,10,40,.94);border-radius:10px;padding:10px 12px;
94
+ box-shadow:0 2px 10px rgba(0,0,0,.3);pointer-events:none;
95
+ }
96
+ #wg-pilot-fx-todos .wg-pilot-fx-todo-title{font-weight:700;color:#c9a6ff;margin-bottom:6px;}
97
+ #wg-pilot-fx-todos ol{margin:0;padding-left:18px;}
98
+ #wg-pilot-fx-todos li{margin:3px 0;color:#ddd;}
99
+ #wg-pilot-fx-todos li.wg-pilot-fx-todo-done{color:#888;text-decoration:line-through;}
100
+ #wg-pilot-fx-todos li.wg-pilot-fx-todo-now{color:#fff;font-weight:700;}
63
101
  ` + WAYGRAPH_RING_CSS;
64
102
  /** Idempotent - safe to call before every update, matching narrate mode's own precedent. */
65
103
  async function ensureInstalled(page) {
@@ -159,6 +197,9 @@ export async function showPilotActivity(page, text) {
159
197
  * raw click/type/upload IS a real Playwright-driven action (`auto` was
160
198
  * always documented as meaning exactly that), while a pure `auto dom` read
161
199
  * touches nothing, so it gets its own "orange" tone instead.
200
+ *
201
+ * For agent-authored multi-ring / todo narration, use {@link showPilotFixtures}
202
+ * (`auto highlight`) instead - this vision ring is one-shot inspect feedback.
162
203
  */
163
204
  export async function showPilotVision(page, selector, label, tone = "auto") {
164
205
  await ensureInstalled(page);
@@ -211,6 +252,113 @@ export async function showPilotVision(page, selector, label, tone = "auto") {
211
252
  }, { selector, label, tone })
212
253
  .catch(() => { });
213
254
  }
255
+ /**
256
+ * 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.
259
+ * Does not import `cli.ts` (see file header).
260
+ */
261
+ export async function showPilotFixtures(page, fixtures) {
262
+ await ensureInstalled(page);
263
+ const holdMs = fixtures.clear === true
264
+ ? 0
265
+ : fixtures.holdMs === undefined
266
+ ? 12_000
267
+ : Math.max(0, fixtures.holdMs);
268
+ const rings = fixtures.clear ? [] : (fixtures.rings ?? []);
269
+ const todos = fixtures.clear ? [] : (fixtures.todos ?? []);
270
+ const todoIndex = fixtures.todoIndex ?? 0;
271
+ const todoTitle = fixtures.todoTitle ?? "Plan";
272
+ return page
273
+ .evaluate(({ rings, todos, todoIndex, todoTitle, holdMs }) => {
274
+ const w = window;
275
+ if (w.__wgPilotFxHideTimer)
276
+ clearTimeout(w.__wgPilotFxHideTimer);
277
+ const clearFx = () => {
278
+ document.querySelectorAll(".wg-pilot-fx-ring, .wg-pilot-fx-label").forEach((el) => el.remove());
279
+ document.getElementById("wg-pilot-fx-todos")?.remove();
280
+ };
281
+ w.__wgPilotFxClear = clearFx;
282
+ clearFx();
283
+ const missing = [];
284
+ let painted = 0;
285
+ for (const ring of rings) {
286
+ const el = document.querySelector(ring.selector);
287
+ if (!el) {
288
+ missing.push(ring.selector);
289
+ continue;
290
+ }
291
+ const rect = el.getBoundingClientRect();
292
+ if (rect.width === 0 && rect.height === 0) {
293
+ missing.push(ring.selector);
294
+ continue;
295
+ }
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;
300
+ const ringEl = document.createElement("div");
301
+ ringEl.className = "wg-pilot-fx-ring";
302
+ ringEl.dataset.tone = tone;
303
+ ringEl.dataset.size = size;
304
+ ringEl.style.left = `${rect.left - pad}px`;
305
+ ringEl.style.top = `${rect.top - pad}px`;
306
+ ringEl.style.width = `${Math.max(4, rect.width + pad * 2)}px`;
307
+ ringEl.style.height = `${Math.max(4, rect.height + pad * 2)}px`;
308
+ ringEl.style.opacity = "1";
309
+ const labelEl = document.createElement("div");
310
+ labelEl.className = "wg-pilot-fx-label";
311
+ labelEl.dataset.tone = tone;
312
+ labelEl.dataset.size = size;
313
+ labelEl.dataset.weight = weight;
314
+ labelEl.textContent = ring.label;
315
+ labelEl.style.opacity = "1";
316
+ document.documentElement.appendChild(ringEl);
317
+ document.documentElement.appendChild(labelEl);
318
+ const lw = labelEl.offsetWidth;
319
+ const lh = labelEl.offsetHeight;
320
+ let labelLeft = rect.left - pad;
321
+ let labelTop = rect.top - pad + rect.height + pad * 2 + 8;
322
+ if (labelTop + lh > window.innerHeight - 6)
323
+ labelTop = rect.top - pad - lh - 8;
324
+ if (labelTop < 6)
325
+ labelTop = 6;
326
+ if (labelLeft + lw > window.innerWidth - 6) {
327
+ labelLeft = Math.max(6, window.innerWidth - 6 - lw);
328
+ }
329
+ if (labelLeft < 6)
330
+ labelLeft = 6;
331
+ labelEl.style.left = `${labelLeft}px`;
332
+ labelEl.style.top = `${labelTop}px`;
333
+ painted += 1;
334
+ }
335
+ if (todos.length > 0) {
336
+ const dock = document.createElement("div");
337
+ dock.id = "wg-pilot-fx-todos";
338
+ const title = document.createElement("div");
339
+ title.className = "wg-pilot-fx-todo-title";
340
+ title.textContent = todoTitle;
341
+ dock.appendChild(title);
342
+ const ol = document.createElement("ol");
343
+ todos.forEach((text, i) => {
344
+ const li = document.createElement("li");
345
+ li.textContent = text;
346
+ if (i < todoIndex)
347
+ li.className = "wg-pilot-fx-todo-done";
348
+ else if (i === todoIndex)
349
+ li.className = "wg-pilot-fx-todo-now";
350
+ ol.appendChild(li);
351
+ });
352
+ dock.appendChild(ol);
353
+ document.documentElement.appendChild(dock);
354
+ }
355
+ if (holdMs > 0 && (painted > 0 || todos.length > 0)) {
356
+ w.__wgPilotFxHideTimer = setTimeout(() => clearFx(), holdMs);
357
+ }
358
+ return { painted, missing };
359
+ }, { rings, todos, todoIndex, todoTitle, holdMs })
360
+ .catch(() => ({ painted: 0, missing: rings.map((r) => r.selector) }));
361
+ }
214
362
  /**
215
363
  * Refreshes the badge text and the (possibly-collapsed) panel's two tabs
216
364
  * ("Here" - current live menu; "All nodes" - whole project graph as a
package/dist/types.d.ts CHANGED
@@ -276,6 +276,23 @@ export type DefinedBlock<In extends Checkpoint<string>, Out extends Checkpoint<s
276
276
  withVerify(verify: Trait[] | ((out: Out) => Trait[])): DefinedBlock<In, Out>;
277
277
  modVerify(nameOrIndex: string | number, newCheck: Trait["check"] | Trait): DefinedBlock<In, Out>;
278
278
  modVerifyAll(patches: Record<string, Trait["check"] | Trait>): DefinedBlock<In, Out>;
279
+ /**
280
+ * Demo narration before act - prefer `(ctx) => { ctx.ring(...); ctx.mem?.get(...) }`.
281
+ * Chains after any existing stubBefore on this Block (library then call-site).
282
+ * Overloads keep `ctx` typed when the author passes a lifecycle function
283
+ * (a single union parameter would make the callback param `any`).
284
+ */
285
+ stubBefore(stub: import("./highlights.js").HighlightStubPhase): DefinedBlock<In, Out>;
286
+ stubBefore(stub: import("./highlights.js").StubLifecycleFn<Out>): DefinedBlock<In, Out>;
287
+ stubBefore(stub: (out: Out) => import("./highlights.js").HighlightStubPhase): DefinedBlock<In, Out>;
288
+ /** Demo narration after resolve. Prefer ctx callbacks over slot-map objects. */
289
+ stubAfter(stub: import("./highlights.js").HighlightStubPhase): DefinedBlock<In, Out>;
290
+ stubAfter(stub: import("./highlights.js").StubLifecycleFn<Out>): DefinedBlock<In, Out>;
291
+ stubAfter(stub: (out: Out) => import("./highlights.js").HighlightStubPhase): DefinedBlock<In, Out>;
292
+ /** Demo narration when the step fails / expected-failure path. */
293
+ stubOnError(stub: import("./highlights.js").HighlightStubPhase): DefinedBlock<In, Out>;
294
+ stubOnError(stub: import("./highlights.js").StubLifecycleFn<Out>): DefinedBlock<In, Out>;
295
+ stubOnError(stub: (out: Out) => import("./highlights.js").HighlightStubPhase): DefinedBlock<In, Out>;
279
296
  };
280
297
  /**
281
298
  * Structural (not imported) twin of `engine.ts`'s `Layout` - `types.ts` sits
File without changes
File without changes
@@ -0,0 +1,4 @@
1
+ (node:218615) 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:218615) 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
File without changes
File without changes
File without changes
@@ -1,4 +1,4 @@
1
- import { defineMethodBlock, checkpoint, Trait, narrate } from "waygraph";
1
+ import { defineMethodBlock, checkpoint, Trait, narrate, type StubCtx } from "waygraph";
2
2
  import type { CheckoutOverviewPage, OrderComplete } from "../../../../states/checkout.states.js";
3
3
  import { CheckoutOverviewSel } from "./checkout-overview.sel.js";
4
4
 
@@ -8,6 +8,7 @@ import { CheckoutOverviewSel } from "./checkout-overview.sel.js";
8
8
  * Route: saucedemo-web/checkout-step-two/methods/ (= /checkout-step-two.html)
9
9
  *
10
10
  * Places the order on the overview page - form/finish click, not a NavBlock.
11
+ * Stubs use open ctx lifecycles (preferred) - not static slot maps.
11
12
  */
12
13
  export const FinishOrderBlock = defineMethodBlock<CheckoutOverviewPage, OrderComplete>({
13
14
  name: "finish-order",
@@ -19,23 +20,23 @@ export const FinishOrderBlock = defineMethodBlock<CheckoutOverviewPage, OrderCom
19
20
  },
20
21
  resolve: () => checkpoint("OrderComplete"),
21
22
  verify: [Trait.text(CheckoutOverviewSel.completeHeader, "Thank you for your order!")],
22
- stubBefore: {
23
- finish: {
23
+ stubBefore: (ctx: StubCtx) => {
24
+ ctx.ring("finish", {
24
25
  selector: CheckoutOverviewSel.finishBtn,
25
26
  label: "Finish",
26
27
  detail: "tone: warning (yellow) - place order",
27
28
  tone: "warning",
28
29
  weight: "bold",
29
- },
30
+ });
30
31
  },
31
- stubAfter: {
32
- thanks: {
32
+ stubAfter: (ctx: StubCtx) => {
33
+ ctx.ring("thanks", {
33
34
  selector: CheckoutOverviewSel.completeHeader,
34
35
  label: "Order confirmed",
35
36
  detail: "tone: success (green) - thank you!",
36
37
  tone: "success",
37
38
  duration: true,
38
- },
39
+ });
39
40
  },
40
41
  // Shown AFTER act (order already placed) - selectors must match the
41
42
  // completion page, not #finish on the overview.