waygraph 0.15.51 → 0.15.53

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 (153) hide show
  1. package/README.md +8 -1
  2. package/dist/analysis/graph.d.ts +1 -1
  3. package/dist/analysis/graph.js +10 -15
  4. package/dist/analysis/map-check.d.ts +3 -2
  5. package/dist/analysis/practices-check.d.ts +0 -1
  6. package/dist/analysis/practices-check.js +0 -13
  7. package/dist/auto-session/session.d.ts +1 -1
  8. package/dist/auto-session/session.js +13 -8
  9. package/dist/blocks-select.d.ts +7 -4
  10. package/dist/blocks-select.js +0 -14
  11. package/dist/browser.d.ts +27 -0
  12. package/dist/browser.js +62 -0
  13. package/dist/cli/flags.js +3 -1
  14. package/dist/cli/run-chain.js +4 -0
  15. package/dist/cli/usage.js +17 -1
  16. package/dist/cli.js +13 -1
  17. package/dist/commands/agent-dive-scaffold.d.ts +5 -20
  18. package/dist/commands/agent-dive-scaffold.js +10 -3
  19. package/dist/commands/extensions.d.ts +1 -0
  20. package/dist/commands/extensions.js +22 -0
  21. package/dist/commands/init.js +43 -5
  22. package/dist/commands/test.js +16 -2
  23. package/dist/commands/try.js +2 -1
  24. package/dist/commands/upgrade.d.ts +6 -0
  25. package/dist/commands/upgrade.js +113 -0
  26. package/dist/config.d.ts +81 -3
  27. package/dist/config.js +91 -16
  28. package/dist/core/block.d.ts +19 -2
  29. package/dist/core/block.js +43 -3
  30. package/dist/core/blocks/nav.d.ts +11 -0
  31. package/dist/core/blocks/nav.js +8 -0
  32. package/dist/core/config.d.ts +5 -0
  33. package/dist/core/engine.d.ts +19 -16
  34. package/dist/core/engine.js +6 -4
  35. package/dist/core/flow.d.ts +27 -0
  36. package/dist/core/flow.js +75 -28
  37. package/dist/core/map-builder.d.ts +30 -4
  38. package/dist/core/map-builder.js +45 -7
  39. package/dist/env-file.d.ts +7 -7
  40. package/dist/env-file.js +29 -16
  41. package/dist/extensions.d.ts +21 -0
  42. package/dist/extensions.js +178 -0
  43. package/dist/highlights/demo-pace.d.ts +0 -1
  44. package/dist/highlights/demo-pace.js +24 -3
  45. package/dist/index.d.ts +7 -5
  46. package/dist/index.js +3 -2
  47. package/dist/pilot-overlay/action-ring.d.ts +0 -9
  48. package/dist/pilot-overlay/action-ring.js +1 -1
  49. package/dist/pilot-overlay/overlay-fixtures.d.ts +0 -7
  50. package/dist/pilot-overlay/overlay-fixtures.js +0 -45
  51. package/dist/pilot-overlay/panel.d.ts +2 -1
  52. package/dist/playwright-config.d.ts +4 -0
  53. package/dist/playwright-config.js +15 -0
  54. package/dist/playwright.config.d.ts +2 -0
  55. package/dist/playwright.config.js +6 -0
  56. package/dist/runner/demo-log.js +1 -7
  57. package/dist/runner/inpage/core.d.ts +1 -1
  58. package/dist/runner/inpage/core.js +8 -7
  59. package/dist/runner/interaction-patch.js +32 -1
  60. package/dist/runner/main.js +22 -11
  61. package/dist/runner/rings.js +0 -49
  62. package/dist/runner/step-mode.js +10 -4
  63. package/dist/session/auto-explore-run.js +9 -5
  64. package/dist/session/auto-explore.d.ts +2 -1
  65. package/dist/session/auto-explore.js +6 -4
  66. package/dist/session/auto-session-ipc.d.ts +1 -2
  67. package/dist/session/auto-session-ipc.js +1 -1
  68. package/dist/session/block-inject.d.ts +0 -13
  69. package/dist/session/block-inject.js +3 -3
  70. package/dist/session/pilot.d.ts +0 -2
  71. package/dist/session/pilot.js +1 -2
  72. package/dist/session/step-overlay.js +3 -1
  73. package/dist/traverse/coverage.d.ts +0 -4
  74. package/dist/traverse/coverage.js +1 -1
  75. package/dist/traverse/run.js +3 -1
  76. package/dist/types.d.ts +1 -1
  77. package/dist/ui/components/ring.js +1 -0
  78. package/dist/ui/overlay.css +1 -0
  79. package/dist/ui/shadow.d.ts +1 -3
  80. package/dist/ui/shadow.js +0 -4
  81. package/dist/ui/tokens.d.ts +0 -11
  82. package/dist/ui/tokens.js +0 -5
  83. package/dist/video-dir.d.ts +8 -0
  84. package/dist/video-dir.js +60 -0
  85. package/dist/webserver.js +5 -1
  86. package/package.json +11 -5
  87. package/templates/scaffold/README.md +5 -3
  88. package/templates/scaffold/STRUCTURE.md +10 -4
  89. package/templates/scaffold/config/waygraph.config.ts +45 -0
  90. package/templates/scaffold/package.json +7 -7
  91. package/templates/scaffold/src/flows/(external)/hoppscotch/auth.flow.ts +36 -0
  92. package/templates/scaffold/src/flows/(external)/hoppscotch/get.flow.ts +24 -0
  93. package/templates/scaffold/src/flows/(external)/hoppscotch/post.flow.ts +34 -0
  94. package/templates/scaffold/src/flows/(user-stories)/email-verification.flow.ts +47 -0
  95. package/templates/scaffold/src/flows/README.md +35 -14
  96. package/templates/scaffold/src/flows/home/cart.flow.ts +51 -0
  97. package/templates/scaffold/src/flows/home/hello.flow.ts +42 -0
  98. package/templates/scaffold/src/map/(app_base)/docs/_nav.block.ts +0 -9
  99. package/templates/scaffold/src/map/(app_base)/docs/_page.block.ts +0 -5
  100. package/templates/scaffold/src/map/(app_base)/docs/_sel.ts +1 -1
  101. package/templates/scaffold/src/map/(app_base)/home/_methods/add-item.effect.block.ts +3 -19
  102. package/templates/scaffold/src/map/(app_base)/home/_methods/assert-hello.method.block.ts +2 -25
  103. package/templates/scaffold/src/map/(app_base)/home/_methods/assert-item-added.method.block.ts +2 -19
  104. package/templates/scaffold/src/map/(app_base)/home/_methods/clear-cart.method.block.ts +1 -17
  105. package/templates/scaffold/src/map/(app_base)/home/_methods/remove-item.effect.block.ts +1 -10
  106. package/templates/scaffold/src/map/(app_base)/home/_nav.block.ts +1 -12
  107. package/templates/scaffold/src/map/(app_base)/home/_page.block.ts +0 -9
  108. package/templates/scaffold/src/map/(app_base)/home/_sel.ts +3 -1
  109. package/templates/scaffold/src/map/(app_base)/home/nav-verification-link.block.ts +1 -23
  110. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/assert-response-body.method.block.ts +21 -0
  111. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/assert-response-header.method.block.ts +19 -0
  112. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/assert-status.method.block.ts +20 -0
  113. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/pick-extension-interceptor.method.block.ts +18 -0
  114. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/read-response-body.method.block.ts +21 -0
  115. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/read-response-headers.method.block.ts +21 -0
  116. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/send-request.method.block.ts +15 -0
  117. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-bearer-token.method.block.ts +20 -0
  118. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-headers.method.block.ts +23 -0
  119. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-json-body.method.block.ts +36 -0
  120. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-method.method.block.ts +17 -0
  121. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-query-params.method.block.ts +22 -0
  122. package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-url.method.block.ts +18 -0
  123. package/templates/scaffold/src/map/(external)/hoppscotch/_nav.block.ts +11 -0
  124. package/templates/scaffold/src/map/(external)/hoppscotch/_page.block.ts +36 -0
  125. package/templates/scaffold/src/map/(external)/hoppscotch/_sel.ts +27 -0
  126. package/templates/scaffold/src/map/(external)/hoppscotch/settings/_nav.block.ts +10 -0
  127. package/templates/scaffold/src/map/(external)/mailpit/_methods/assert-email-content.method.block.ts +2 -27
  128. package/templates/scaffold/src/map/(external)/mailpit/_methods/assert-email-received.method.block.ts +2 -21
  129. package/templates/scaffold/src/map/(external)/mailpit/_methods/extract-email-link.method.block.ts +1 -19
  130. package/templates/scaffold/src/map/(external)/mailpit/_methods/open-message.method.block.ts +1 -11
  131. package/templates/scaffold/src/map/(external)/mailpit/_nav.block.ts +1 -9
  132. package/templates/scaffold/src/map/(external)/mailpit/_page.block.ts +0 -11
  133. package/templates/scaffold/src/states/demo.mem-keys.ts +20 -19
  134. package/templates/scaffold/src/states/demo.mem-stubs.ts +4 -0
  135. package/templates/scaffold/src/states/demo.states.ts +5 -1
  136. package/templates/scaffold/tests/example.spec.ts +6 -7
  137. package/templates/scaffold/tests/hoppscotch.spec.ts +97 -0
  138. package/templates/scaffold/tests/mail-verify.spec.ts +8 -17
  139. package/templates/scaffold/tsconfig.json +1 -1
  140. package/templates/skills/waygraph-convention.skill.md +40 -3
  141. package/templates/skills/waygraph-pilot.skill.md +4 -5
  142. package/dist/browser-path.d.ts +0 -5
  143. package/dist/browser-path.js +0 -18
  144. package/dist/runner/overlay-present.d.ts +0 -8
  145. package/dist/runner/overlay-present.js +0 -57
  146. package/dist/ui/components/banner.d.ts +0 -2
  147. package/dist/ui/components/banner.js +0 -10
  148. package/dist/ui/components/cursor.d.ts +0 -2
  149. package/dist/ui/components/cursor.js +0 -19
  150. package/templates/scaffold/playwright.config.ts +0 -12
  151. package/templates/scaffold/src/flows/example.flow.ts +0 -29
  152. package/templates/scaffold/src/flows/mail-verify.flow.ts +0 -85
  153. package/templates/scaffold/src/flows/shop.flow.ts +0 -74
@@ -10,7 +10,8 @@ import { connect } from "../types.js";
10
10
  import { MemPage } from "../mem-page.js";
11
11
  import { Engine, start, end, chainFlow, isFastForwardBlock, branchRoutes, runBranchRegression } from "../core.js";
12
12
  import { withWebServer } from "../webserver.js";
13
- import { chromiumExecutablePath } from "../browser-path.js";
13
+ import { resolveVideoDir } from "../video-dir.js";
14
+ import { chromiumExecutablePath, extensionPathsFromEnv, launchContext } from "../browser.js";
14
15
 
15
16
  async function runMain() {
16
17
  const projectDir = process.argv[2];
@@ -230,6 +231,9 @@ async function runMain() {
230
231
  : "waygraph demo: opaque FFCompose in flow -> Playwright slowMo=0 (blitz); set WAYGRAPH_SLOWMO to override",
231
232
  );
232
233
  }
234
+ // Step (demo) mode: slowMo is waygraph's own beat after each action, not Playwright's per-operation slowMo, so a
235
+ // step's pace can remove it and typing is not taxed per keystroke. The browser launches at 0.
236
+ const launchSlowMo = step ? 0 : slowMo;
233
237
  const baseURL = process.env.WAYGRAPH_BASE_URL;
234
238
  const title = process.env.WAYGRAPH_TITLE;
235
239
  // Comma-separated Block names (their real .name, e.g. "login") that
@@ -252,7 +256,7 @@ async function runMain() {
252
256
  const videoEnv = process.env.WAYGRAPH_VIDEO;
253
257
  let videoDir = null;
254
258
  if (videoEnv) {
255
- videoDir = videoEnv === "1" ? join(projectDir, ".waygraph-videos") : videoEnv;
259
+ videoDir = videoEnv === "1" ? await resolveVideoDir(projectDir) : videoEnv;
256
260
  mkdirSync(videoDir, { recursive: true });
257
261
  }
258
262
  /** Fixed 16:9 capture for --video (demo QA and headless run). Step+video no longer maximizes. */
@@ -262,7 +266,7 @@ async function runMain() {
262
266
  const fixedVideoViewport = videoDir
263
267
  ? (videoViewportParsed ?? (step ? DEFAULT_VIDEO_VIEWPORT : DEFAULT_RUN_VIDEO_VIEWPORT))
264
268
  : null;
265
- const engine = new Engine({ headless: !headed, slowMo });
269
+ const engine = new Engine({ headless: !headed, slowMo: launchSlowMo });
266
270
  let result;
267
271
  // Non-interactive execution (no --step) runs the whole chain as one
268
272
  // composed flow with no per-block loop of its own to defer seeding
@@ -290,12 +294,6 @@ async function runMain() {
290
294
  const executablePath = chromiumExecutablePath();
291
295
  // Maximized for interactive step, and for fixed video viewport so the
292
296
  // locked record size can actually fit (otherwise .webm gets grey pad).
293
- const browser = await chromium.launch({
294
- headless: !headed,
295
- slowMo,
296
- args: step || fixedVideoViewport ? ["--start-maximized"] : [],
297
- ...(executablePath ? { executablePath } : {}),
298
- });
299
297
  const contextOpts = fixedVideoViewport
300
298
  ? {
301
299
  baseURL,
@@ -313,7 +311,17 @@ async function runMain() {
313
311
  ? { dir: videoDir, size: fixedVideoViewport }
314
312
  : { dir: videoDir };
315
313
  }
316
- const context = await browser.newContext(contextOpts);
314
+ const { context, close: closeBrowser } = await launchContext({
315
+ launcher: chromium,
316
+ launch: {
317
+ headless: !headed,
318
+ slowMo: launchSlowMo,
319
+ args: step || fixedVideoViewport ? ["--start-maximized"] : [],
320
+ ...(executablePath ? { executablePath } : {}),
321
+ },
322
+ context: contextOpts,
323
+ extensions: extensionPathsFromEnv(),
324
+ });
317
325
  let pageForVideo = null;
318
326
  try {
319
327
  if (step) {
@@ -369,7 +377,7 @@ async function runMain() {
369
377
  if (savedVideo) {
370
378
  console.log("waygraph: video saved to " + savedVideo);
371
379
  }
372
- await browser.close();
380
+ await closeBrowser();
373
381
  }
374
382
  } else {
375
383
  if (chainFlows && chainFlows.length > 0) {
@@ -421,6 +429,9 @@ async function runAllBranchesMode(chainFlows, mem, baseURL, headed, engine) {
421
429
  process.exitCode = 1;
422
430
  return;
423
431
  }
432
+ if (extensionPathsFromEnv().length > 0) {
433
+ console.warn("waygraph run --all-branches: extensions are not loaded (branches run in cloned contexts)");
434
+ }
424
435
  const { chromium } = await import("@playwright/test");
425
436
  const browser = await chromium.launch({ headless: !headed });
426
437
  const cloneSession = process.env.WAYGRAPH_SHARED_SESSION !== "1";
@@ -381,55 +381,6 @@ export async function cycleHighlightRings(page, highlights, gatesFast, opts) {
381
381
  }
382
382
 
383
383
 
384
- /**
385
- * Paint a NAMED ring instance (ui/css/ring.css's shared `.wg-ring-el`/`.wg-ring-el-label`) at a
386
- * known viewport box - the primitive both the demo/auto singleton (`showRing`, always "wg-ring")
387
- * and Pilot's several simultaneous fixture rings (their own ids) paint through, so there is one
388
- * ring-rendering implementation, not two. No collision/follow/focus - those are `#wg-ring`'s own
389
- * extras (see `showRing`/`inpage/core.js`'s `__wgPositionRing`), meaningless for an independent
390
- * fixture ring that isn't "the current action."
391
- */
392
- export async function showRingAt(page, ringId, labelId, box, label, tone, style) {
393
- const size = normalizeHighlightSize(style && style.size);
394
- const weight = normalizeHighlightWeight(style && style.weight);
395
- const color = style && style.color ? String(style.color) : "";
396
- await page
397
- .evaluate(
398
- ({ ringId, labelId, box, label, tone, size, weight, color }) => {
399
- if (window.__wgPaintRingAt) {
400
- window.__wgPaintRingAt(ringId, labelId, box, label, tone, { size, weight, color });
401
- }
402
- },
403
- { ringId, labelId, box, label, tone: tone || "planned", size, weight, color },
404
- )
405
- .catch(() => {});
406
- }
407
-
408
- /** Hide (not remove) a named ring instance painted via {@link showRingAt}. */
409
- export async function hideRingAt(page, ringId, labelId) {
410
- await page
411
- .evaluate(
412
- ({ ringId, labelId }) => {
413
- if (window.__wgHideRingAt) window.__wgHideRingAt(ringId, labelId);
414
- },
415
- { ringId, labelId },
416
- )
417
- .catch(() => {});
418
- }
419
-
420
- /** Remove a named ring instance entirely (Pilot's fixture rings are short-lived, not reused). */
421
- export async function removeRingAt(page, ringId, labelId) {
422
- await page
423
- .evaluate(
424
- ({ ringId, labelId }) => {
425
- __wgById(ringId)?.remove();
426
- __wgById(labelId)?.remove();
427
- },
428
- { ringId, labelId },
429
- )
430
- .catch(() => {});
431
- }
432
-
433
384
  export async function showRing(page, box, label, tone, style) {
434
385
  const size = normalizeHighlightSize(style && style.size);
435
386
  const weight = normalizeHighlightWeight(style && style.weight);
@@ -1,7 +1,7 @@
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
3
  import { instrumentInteractionHighlighting } from "./interaction-patch.js";
4
- import { applyHighlightStyleDefaults, demoPaceGateMs, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, formatHighlightCaption, hasAuthoredStubAfter, resolveSlides, resolveStepDemoPace, resolveTodoDockUi, runStubPhase } from "../highlights.js";
4
+ import { applyHighlightStyleDefaults, normalizeDemoPace, demoPaceGateMs, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, formatHighlightCaption, hasAuthoredStubAfter, resolveSlides, resolveStepDemoPace, resolveTodoDockUi, runStubPhase } from "../highlights.js";
5
5
  import { markStepRunning, resetPageState, teardownOverlay } from "./teardown.js";
6
6
  import { demoLog, logStubPhaseFixtures, logTodoDockFull, paceSpeakFields, summarizeDevice } from "./demo-log.js";
7
7
  import { extractVerifyHighlights, presentFailPanel, renderAfterStep, renderBeforeStep } from "./step-panels.js";
@@ -26,6 +26,8 @@ export async function runStepMode(engine, start, end, context, page, mem, resolv
26
26
  const demoFast = process.env.WAYGRAPH_DEMO_FAST === "1";
27
27
  const stepperMode = process.env.WAYGRAPH_STEPPER === "full" ? "full" : "carousel";
28
28
  const pacing = { gatesFast: demoFast, gatesSlow: false, skipTheater: false, demoPace: "normal" };
29
+ // A title set by a stub (`ctx.title`) names the flow from that step on, until another stub sets a new one.
30
+ let stickyTitle;
29
31
  const stubBeforeRef = { current: [] };
30
32
  /** Live todo dock for Method fill/click advance (same carry as stage.state.lastTodoDock). */
31
33
  const todoDockRef = { current: undefined };
@@ -213,15 +215,18 @@ export async function runStepMode(engine, start, end, context, page, mem, resolv
213
215
  }
214
216
  // Episode pace: block > flow > CLI --fast (only when no authored pace).
215
217
  // Authoring wins: withDemoPace("slow"|2|4500) keeps slow dwell even under --fast.
218
+ // `demo.pace` in the config (WAYGRAPH_DEMO_PACE) is the default for flows and steps that set none.
219
+ const defaultPace = process.env.WAYGRAPH_DEMO_PACE ? normalizeDemoPace(process.env.WAYGRAPH_DEMO_PACE) : undefined;
216
220
  const stepPace = resolveStepDemoPace({
217
221
  blockPace: r.block.demoPace,
218
- flowPace: r.demoPace,
222
+ flowPace: r.demoPace ?? defaultPace,
219
223
  fastForward: !!r.block.fastForward,
220
224
  wasFastForward: !!r.wasFastForward,
221
225
  });
222
226
  const authoredPace =
223
227
  (r.block.demoPace != null && r.block.demoPace !== "") ||
224
- (r.demoPace != null && r.demoPace !== "");
228
+ (r.demoPace != null && r.demoPace !== "") ||
229
+ defaultPace != null;
225
230
  const cliFast = demoFast || fastBlockNames.has(r.block.name);
226
231
  pacing.demoPace =
227
232
  stepPace === "normal" && cliFast && !authoredPace
@@ -266,8 +271,9 @@ export async function runStepMode(engine, start, end, context, page, mem, resolv
266
271
  (stage.state.lastTodoDock && stage.state.lastTodoDock.groups[0] && stage.state.lastTodoDock.groups[0].items) ||
267
272
  stubBeforePhase.todos ||
268
273
  [];
274
+ if (stubBeforePhase.title && String(stubBeforePhase.title).trim()) stickyTitle = String(stubBeforePhase.title).trim();
269
275
  const overlayTitle =
270
- (stubBeforePhase.title && String(stubBeforePhase.title).trim()) ||
276
+ stickyTitle ||
271
277
  (r.episodeTitle && String(r.episodeTitle).trim()) ||
272
278
  title ||
273
279
  "waygraph demo";
@@ -5,9 +5,9 @@ import { Engine, start, end, locate } from "../core.js";
5
5
  import { MemPage } from "../mem-page.js";
6
6
  import { buildExploreContext, buildExploreMenu, } from "./auto-explore.js";
7
7
  import { installDemoChrome, instrumentInteractionHighlighting } from "./step-overlay.js";
8
- import { resolveBaseUrl } from "../config.js";
8
+ import { configuredExtensions, resolveBaseUrl } from "../config.js";
9
9
  import { withWebServer } from "../webserver.js";
10
- import { chromiumExecutablePath } from "../browser-path.js";
10
+ import { chromiumExecutablePath, launchContext } from "../browser.js";
11
11
  /** See `../config.js`'s own `resolveBaseUrl` - re-exported so existing importers of this module
12
12
  * don't need to change their import path. */
13
13
  export { resolveBaseUrl };
@@ -587,13 +587,17 @@ async function runAutoExploreCore(projectDir, options = {}) {
587
587
  launchOpts.slowMo = 250;
588
588
  if (executablePath)
589
589
  launchOpts.executablePath = executablePath;
590
- const browser = await chromium.launch(launchOpts);
591
590
  const contextOpts = cli
592
591
  ? { viewport: { width: 1280, height: 720 } }
593
592
  : { viewport: null };
594
593
  if (baseURL)
595
594
  contextOpts.baseURL = baseURL;
596
- const context = await browser.newContext(contextOpts);
595
+ const { close: closeBrowser, context } = await launchContext({
596
+ launcher: chromium,
597
+ launch: launchOpts,
598
+ context: contextOpts,
599
+ extensions: await configuredExtensions(projectDir),
600
+ });
597
601
  let page = await context.newPage();
598
602
  if (startUrl) {
599
603
  await page.goto(startUrl, { waitUntil: "domcontentloaded" }).catch(() => { });
@@ -692,6 +696,6 @@ async function runAutoExploreCore(projectDir, options = {}) {
692
696
  finally {
693
697
  cliRl?.close();
694
698
  await context.close();
695
- await browser.close();
699
+ await closeBrowser();
696
700
  }
697
701
  }
@@ -24,7 +24,7 @@ export interface ExploreEdge extends WaygraphEdge {
24
24
  label?: string;
25
25
  instanceOption?: WaygraphInstanceOption;
26
26
  }
27
- export interface ExploreSection {
27
+ interface ExploreSection {
28
28
  title: string;
29
29
  edges: ExploreEdge[];
30
30
  }
@@ -55,3 +55,4 @@ export declare function buildExploreContext(projectDir: string, opts?: ExploreCo
55
55
  graph: WaygraphGraph;
56
56
  library: Awaited<ReturnType<typeof loadBlockLibrary>>;
57
57
  }>;
58
+ export {};
@@ -169,7 +169,7 @@ export function exploreChoices(graph, here) {
169
169
  const out = [];
170
170
  for (const edge of graph.edges) {
171
171
  if (here === null) {
172
- if (edge.kind !== "nav")
172
+ if (edge.kind !== "nav" || edge.from !== "*")
173
173
  continue;
174
174
  }
175
175
  else if (edge.from !== here && edge.from !== "*") {
@@ -283,8 +283,9 @@ export async function buildExploreMenu(page, graph, library, here) {
283
283
  continue;
284
284
  if (here === null) {
285
285
  // Bootstrap only: URL deep-links as "Start here". Click-nav needs a live
286
- // control so it cannot seed an unknown page.
287
- if (!isUrlNav(entry))
286
+ // control so it cannot seed an unknown page, and a Nav with `from` needs
287
+ // that state first (e.g. signed in) so it cannot start one either.
288
+ if (!isUrlNav(entry) || edge.from !== "*")
288
289
  continue;
289
290
  push(navigate, edge);
290
291
  continue;
@@ -295,7 +296,8 @@ export async function buildExploreMenu(page, graph, library, here) {
295
296
  // Graph still marks NavBlocks from:"*" for pathfinding/traverse; auto menu
296
297
  // only offers click-nav whose control is visible on this page (navRunnable).
297
298
  // Otherwise LoginForm lists every app route + mailpit as if legal moves.
298
- if (isUrlNav(entry))
299
+ // A url Nav that declares `from` is only an edge from those pages, so it IS a real move here.
300
+ if (isUrlNav(entry) && edge.from === "*")
299
301
  continue;
300
302
  if (isBackEdge(edge) && (edge.from === here || edge.from === "*")) {
301
303
  push(back, edge);
@@ -1,6 +1,6 @@
1
1
  import { AutoSession, type AutoSessionInit, type SessionSnapshot, type InspectDomOptions, type DomSnapshot, type TraceStep, type ApplyPathResult, type ConsoleLogEntry, type StorageSnapshot, type StubFileKind } from "../auto-session.js";
2
2
  import type { PilotHighlightFixtures } from "../pilot-overlay.js";
3
- export type { PilotHighlightFixtures, PilotFixtureRing } from "../pilot-overlay.js";
3
+ export type { PilotHighlightFixtures } from "../pilot-overlay.js";
4
4
  export interface SessionMeta {
5
5
  sessionId: string;
6
6
  socketPath: string;
@@ -54,7 +54,6 @@ export type HighlightResponse = {
54
54
  ok: false;
55
55
  error: string;
56
56
  };
57
- export declare function readSessionMeta(projectDir: string, sessionId: string): SessionMeta | null;
58
57
  /**
59
58
  * Resolve session metadata from a project hint (cwd) or the global registry.
60
59
  * Fixes "session unreachable" when the agent calls status/send from a different
@@ -58,7 +58,7 @@ function logPathFor(projectDir, sessionId) {
58
58
  function generateSessionId() {
59
59
  return randomBytes(4).toString("hex");
60
60
  }
61
- export function readSessionMeta(projectDir, sessionId) {
61
+ function readSessionMeta(projectDir, sessionId) {
62
62
  const p = metaPath(projectDir, sessionId);
63
63
  if (!existsSync(p))
64
64
  return null;
@@ -1,21 +1,8 @@
1
- /**
2
- * This package's own installed root (contains `templates/`/`examples/`, sibling to `dist/`) -
3
- * this file compiles to `dist/session/block-inject.js`, two levels down, not one. Same class of
4
- * bug as `cli/util.ts`'s own `packageRoot()` (see its comment) - self-inflicted here by moving
5
- * this file into `src/session/` without updating the path math to match the new depth.
6
- */
7
- export declare function packageRoot(): string;
8
- /** Human label for an injected root (used in merged `file` paths). */
9
- export declare function injectLabel(root: string): string;
10
1
  /**
11
2
  * Display path for a Block discovered under an inject root (not the host cwd).
12
3
  * Primary project files stay relative as-is; injected ones are tagged `@label/...`.
13
4
  */
14
5
  export declare function injectTaggedFile(hostDir: string, root: string, relFile: string): string;
15
- /**
16
- * Resolve one `--inject` token to an absolute directory (preset name or path).
17
- */
18
- export declare function resolveInjectRoot(token: string, cwd?: string): string;
19
6
  export declare function resolveInjectRoots(tokens: string[], cwd?: string): string[];
20
7
  /** Roots to scan: host project first, then each inject path (deduped). */
21
8
  export declare function exploreRoots(projectDir: string, inject?: string[]): string[];
@@ -12,11 +12,11 @@ import { homedir } from "node:os";
12
12
  * bug as `cli/util.ts`'s own `packageRoot()` (see its comment) - self-inflicted here by moving
13
13
  * this file into `src/session/` without updating the path math to match the new depth.
14
14
  */
15
- export function packageRoot() {
15
+ function packageRoot() {
16
16
  return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
17
17
  }
18
18
  /** Human label for an injected root (used in merged `file` paths). */
19
- export function injectLabel(root) {
19
+ function injectLabel(root) {
20
20
  return basename(root.replace(/\/$/, ""));
21
21
  }
22
22
  /**
@@ -50,7 +50,7 @@ const PRESETS = {
50
50
  /**
51
51
  * Resolve one `--inject` token to an absolute directory (preset name or path).
52
52
  */
53
- export function resolveInjectRoot(token, cwd = process.cwd()) {
53
+ function resolveInjectRoot(token, cwd = process.cwd()) {
54
54
  const raw = token.trim();
55
55
  if (!raw)
56
56
  throw new Error("waygraph inject: empty --inject value");
@@ -29,7 +29,6 @@
29
29
  * new execution primitive.
30
30
  */
31
31
  import { type WaygraphGraph } from "../analysis/graph.js";
32
- import { listBrowserSessions, type SessionMeta } from "./auto-session-ipc.js";
33
32
  import { type BrowserStartInit } from "./browser.js";
34
33
  import type { SessionSnapshot } from "../auto-session.js";
35
34
  export interface PilotStartResult {
@@ -51,4 +50,3 @@ export interface PilotStartResult {
51
50
  export declare function pilotStart(init: BrowserStartInit): Promise<PilotStartResult>;
52
51
  /** Reattach to an existing browser session — does not spawn a new browser. */
53
52
  export declare function pilotAttach(projectDir: string, sessionId: string, inject?: string[]): Promise<PilotStartResult>;
54
- export { listBrowserSessions, type SessionMeta };
@@ -29,7 +29,7 @@
29
29
  * new execution primitive.
30
30
  */
31
31
  import { discoverGraph } from "../analysis/graph.js";
32
- import { requestSession, listBrowserSessions, resolveSessionMeta, } from "./auto-session-ipc.js";
32
+ import { requestSession, resolveSessionMeta, } from "./auto-session-ipc.js";
33
33
  import { browserStart } from "./browser.js";
34
34
  /**
35
35
  * Starts a detached session and reads back its own graph + starting
@@ -77,4 +77,3 @@ export async function pilotAttach(projectDir, sessionId, inject) {
77
77
  snapshot: status.snapshot,
78
78
  };
79
79
  }
80
- export { listBrowserSessions };
@@ -86,7 +86,9 @@ export async function installDemoChrome(page, title = "waygraph auto", options =
86
86
  ring.style.width = box.width + pad * 2 + "px";
87
87
  ring.style.height = box.height + pad * 2 + "px";
88
88
  ring.style.opacity = "1";
89
- ringLabel.textContent = label;
89
+ // Drawn from data-label (CSS ::after), not DOM text, so the page's own text selectors never match it.
90
+ ringLabel.dataset.label = label;
91
+ ringLabel.textContent = "";
90
92
  ringLabel.style.left = box.x + "px";
91
93
  ringLabel.style.top = box.y + box.height + 8 + "px";
92
94
  ringLabel.style.opacity = "1";
@@ -27,10 +27,6 @@ export interface TraverseCoverageReport {
27
27
  leaf?: string;
28
28
  }>;
29
29
  }
30
- /** Static graph edge key = Block runtime name. */
31
- export declare function edgeKeyFromGraphEdge(edge: {
32
- block: string;
33
- }): string;
34
30
  /** Collect unique edge keys from a WaygraphGraph-like edges list. */
35
31
  export declare function collectGraphEdgeKeys(edges: ReadonlyArray<{
36
32
  block: string;
@@ -10,7 +10,7 @@
10
10
  import { mkdirSync, writeFileSync } from "node:fs";
11
11
  import { dirname, join } from "node:path";
12
12
  /** Static graph edge key = Block runtime name. */
13
- export function edgeKeyFromGraphEdge(edge) {
13
+ function edgeKeyFromGraphEdge(edge) {
14
14
  return edge.block;
15
15
  }
16
16
  /** Collect unique edge keys from a WaygraphGraph-like edges list. */
@@ -5,7 +5,7 @@ import { defaultMemValueForKey } from "../session/auto-explore-run.js";
5
5
  import { resolveBaseUrl } from "../config.js";
6
6
  import { EdgeLeaseCoordinator } from "./lease.js";
7
7
  import { buildCoverageReport, collectGraphEdgeKeys, defaultCoverageOutPath, formatCoverageLine, writeCoverageReport, } from "./coverage.js";
8
- import { chromiumExecutablePath } from "../browser-path.js";
8
+ import { chromiumExecutablePath } from "../browser.js";
9
9
  function seedMem(library, mem, dataJson) {
10
10
  const raw = dataJson ?? process.env.WAYGRAPH_DATA ?? process.env.WAYGRAPH_AUTO_MEM;
11
11
  let envParsed = {};
@@ -253,6 +253,8 @@ export async function runTraverse(projectDir, options = {}) {
253
253
  const allEdgeKeys = collectGraphEdgeKeys(graph.edges);
254
254
  const leases = new EdgeLeaseCoordinator({ projectDir, persist: parallel > 1 });
255
255
  const engine = new Engine({ headless: !headed, slowMo: headed ? 100 : 0 });
256
+ if (process.env.WAYGRAPH_EXTENSIONS)
257
+ console.error("waygraph traverse: extensions are not loaded (workers use plain contexts)");
256
258
  const { chromium } = await import("@playwright/test");
257
259
  const executablePath = chromiumExecutablePath();
258
260
  const launchOpts = {
package/dist/types.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { Page } from "@playwright/test";
2
2
  import type { MemPage, MemKey } from "./mem-page.js";
3
3
  import type { Trait } from "./trait.js";
4
4
  import type { HighlightStubPhaseOrFn, WaygraphHighlight, WaygraphSlidesOrFn } from "./highlights.js";
5
- export type { WaygraphHighlight, WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, HighlightStubPhase, HighlightStubPhaseOrFn, HighlightFixtureMap, ResolvedHighlight, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, WaygraphTodoItem, WaygraphTodoInput, } from "./highlights.js";
5
+ export type { WaygraphHighlight } from "./highlights.js";
6
6
  /**
7
7
  * A Checkpoint is identified solely by its string tag - a phantom marker with no
8
8
  * embedded data. Two Checkpoints with the same tag are the same state everywhere.
@@ -20,6 +20,7 @@ export function ringCss() {
20
20
  "#wg-ring-label{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
21
21
  `max-width:min(360px,70vw);white-space:normal;padding:6px 10px;border-radius:7px;background:${p.labelBg};color:${p.labelFg};` +
22
22
  "font:600 12px/1.35 system-ui,sans-serif;transition:opacity .3s ease;}" +
23
+ "#wg-ring-label::after{content:attr(data-label);}" +
23
24
  toneRules("#wg-ring-label", (t) => `background:${t.labelBg};color:${t.labelFg};`) +
24
25
  // Gray = a real Playwright-driven instruction: mark it with a robot.
25
26
  `#wg-ring-label[data-tone=auto]::before{${ROBOT}}` +
@@ -86,6 +86,7 @@
86
86
  font: 600 12px/1.35 var(--wg-font); transition: opacity .3s ease;
87
87
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; box-shadow: 0 2px 8px rgba(0,0,0,.25);
88
88
  }
89
+ #wg-ring-label::after { content: attr(data-label); }
89
90
  .wg-ring-el-label[data-tone=planned] { --wg-tone-bg: var(--wg-tone-planned-bg); --wg-tone-fg: var(--wg-tone-planned-fg); }
90
91
  .wg-ring-el-label[data-tone=auto] { --wg-tone-bg: var(--wg-tone-auto-bg); --wg-tone-fg: var(--wg-tone-auto-fg); }
91
92
  .wg-ring-el-label[data-tone=info] { --wg-tone-bg: var(--wg-tone-info-bg); --wg-tone-fg: var(--wg-tone-info-fg); }
@@ -7,7 +7,7 @@
7
7
  * lookups check the shadow root first and fall back to the light DOM (the device stage shell and
8
8
  * host-page markers such as `[data-wg-zoomed]` intentionally stay in the light DOM).
9
9
  */
10
- import type { BrowserContext, Page } from "@playwright/test";
10
+ import type { Page } from "@playwright/test";
11
11
  /**
12
12
  * `overlaySheet` is the ONE real-CSS bundle (`ui/stylesheet.ts` -> `dist/ui/overlay.css`, built on
13
13
  * Open Props - see `src/ui/css/README.md`) every surface shares; passed in from Node since in-page
@@ -16,5 +16,3 @@ import type { BrowserContext, Page } from "@playwright/test";
16
16
  export declare function installShadowRoot(overlaySheet?: string): void;
17
17
  /** Idempotent; call before any overlay install on a page. Installs the shared stylesheet bundle. */
18
18
  export declare function ensureShadowRoot(page: Page): Promise<void>;
19
- /** Also define the helpers on every future document (pilot overlay persists across navigations). */
20
- export declare function installShadowRootEverywhere(context: BrowserContext): Promise<void>;
package/dist/ui/shadow.js CHANGED
@@ -44,7 +44,3 @@ export function installShadowRoot(overlaySheet) {
44
44
  export async function ensureShadowRoot(page) {
45
45
  await page.evaluate(installShadowRoot, getOverlaySheet()).catch(() => { });
46
46
  }
47
- /** Also define the helpers on every future document (pilot overlay persists across navigations). */
48
- export async function installShadowRootEverywhere(context) {
49
- await context.addInitScript(installShadowRoot, getOverlaySheet());
50
- }
@@ -18,14 +18,3 @@ export interface Tone {
18
18
  export declare const TONES: Record<ToneName, Tone>;
19
19
  /** Dark surface shared by panels, banner, docks, HUD chips. */
20
20
  export declare const SURFACE = "rgba(20,10,40,.94)";
21
- export declare const ACCENT = "#7C3AED";
22
- /** Secondary accent used on the dark surface (section titles, kinds). */
23
- export declare const ACCENT_SOFT = "#c9a6ff";
24
- /** Stacking order: one scale instead of magic numbers. */
25
- export declare const Z: {
26
- readonly top: 2147483647;
27
- readonly ring: 2147483646;
28
- readonly fx: 2147483645;
29
- readonly focus: 2147483644;
30
- readonly dock: 2147483000;
31
- };
package/dist/ui/tokens.js CHANGED
@@ -14,8 +14,3 @@ export const TONES = {
14
14
  };
15
15
  /** Dark surface shared by panels, banner, docks, HUD chips. */
16
16
  export const SURFACE = "rgba(20,10,40,.94)";
17
- export const ACCENT = "#7C3AED";
18
- /** Secondary accent used on the dark surface (section titles, kinds). */
19
- export const ACCENT_SOFT = "#c9a6ff";
20
- /** Stacking order: one scale instead of magic numbers. */
21
- export const Z = { top: 2147483647, ring: 2147483646, fx: 2147483645, focus: 2147483644, dock: 2147483000 };
@@ -0,0 +1,8 @@
1
+ /** The user's Downloads folder, or undefined when there is none (a server, a container). */
2
+ export declare function downloadsDir(): string | undefined;
3
+ /**
4
+ * Where `--video` recordings go: `WAYGRAPH_VIDEO_DIR`, else `video.dir` from the config (absolute, `~/…`, or relative to
5
+ * the project), else `<Downloads>/waygraph-videos/<project>`. With no usable Downloads folder, or on CI, it is
6
+ * `<project>/.waygraph-videos`. An explicit `--video <dir>` never reaches this.
7
+ */
8
+ export declare function resolveVideoDir(projectDir: string): Promise<string>;
@@ -0,0 +1,60 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { basename, isAbsolute, join, resolve } from "node:path";
4
+ import { loadWaygraphConfig } from "./config.js";
5
+ /** The user's Downloads folder, or undefined when there is none (a server, a container). */
6
+ export function downloadsDir() {
7
+ const home = homedir();
8
+ let dir = process.env.XDG_DOWNLOAD_DIR;
9
+ if (!dir && process.platform === "linux") {
10
+ try {
11
+ const line = readFileSync(join(process.env.XDG_CONFIG_HOME ?? join(home, ".config"), "user-dirs.dirs"), "utf-8")
12
+ .split("\n")
13
+ .find((l) => l.startsWith("XDG_DOWNLOAD_DIR="));
14
+ dir = line?.slice("XDG_DOWNLOAD_DIR=".length).replace(/^"|"$/g, "").replace("$HOME", home);
15
+ }
16
+ catch {
17
+ /* no user-dirs file: use the conventional name */
18
+ }
19
+ }
20
+ dir ??= join(home, "Downloads");
21
+ return existsSync(dir) ? dir : undefined;
22
+ }
23
+ const expandHome = (p) => (p === "~" || p.startsWith("~/") ? join(homedir(), p.slice(1)) : p);
24
+ /** True if a file can be created in `dir` (macOS asks permission for Downloads the first time; a denial surfaces here). */
25
+ function writable(dir) {
26
+ try {
27
+ mkdirSync(dir, { recursive: true });
28
+ const probe = join(dir, `.waygraph-probe-${process.pid}`);
29
+ writeFileSync(probe, "");
30
+ rmSync(probe);
31
+ return true;
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
37
+ /**
38
+ * Where `--video` recordings go: `WAYGRAPH_VIDEO_DIR`, else `video.dir` from the config (absolute, `~/…`, or relative to
39
+ * the project), else `<Downloads>/waygraph-videos/<project>`. With no usable Downloads folder, or on CI, it is
40
+ * `<project>/.waygraph-videos`. An explicit `--video <dir>` never reaches this.
41
+ */
42
+ export async function resolveVideoDir(projectDir) {
43
+ const fallback = join(projectDir, ".waygraph-videos");
44
+ const configured = process.env.WAYGRAPH_VIDEO_DIR || (await loadWaygraphConfig(projectDir))?.video?.dir;
45
+ if (configured) {
46
+ const dir = isAbsolute(expandHome(configured)) ? expandHome(configured) : resolve(projectDir, configured);
47
+ if (writable(dir))
48
+ return dir;
49
+ console.error(`waygraph: cannot write videos to ${dir} - using ${fallback}`);
50
+ return fallback;
51
+ }
52
+ const downloads = process.env.CI ? undefined : downloadsDir();
53
+ if (!downloads)
54
+ return fallback;
55
+ const dir = join(downloads, "waygraph-videos", basename(resolve(projectDir)));
56
+ if (writable(dir))
57
+ return dir;
58
+ console.error(`waygraph: cannot write to ${downloads} (denied?) - saving videos in ${fallback}`);
59
+ return fallback;
60
+ }
package/dist/webserver.js CHANGED
@@ -1,9 +1,10 @@
1
- // Starts/stops a project's own playwright.config `webServer` around `waygraph run`/`demo`/`auto`,
1
+ // Starts/stops a project's `webServer` (waygraph.config, or a legacy playwright.config) around `waygraph run`/`demo`/`auto`,
2
2
  // the same block `waygraph test` already honors.
3
3
  import { existsSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import { spawn } from "node:child_process";
7
+ import { loadWaygraphConfig } from "./config.js";
7
8
  const CONFIG_FILES = [
8
9
  "playwright.config.ts",
9
10
  "playwright.config.mts",
@@ -12,6 +13,9 @@ const CONFIG_FILES = [
12
13
  "playwright.config.cjs",
13
14
  ];
14
15
  async function loadWebServerConfigs(projectDir) {
16
+ const fromWaygraph = (await loadWaygraphConfig(projectDir))?.webServer;
17
+ if (fromWaygraph)
18
+ return Array.isArray(fromWaygraph) ? fromWaygraph : [fromWaygraph];
15
19
  for (const name of CONFIG_FILES) {
16
20
  const p = join(projectDir, name);
17
21
  if (!existsSync(p))