waygraph 0.10.8 → 0.10.9

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
@@ -3,7 +3,7 @@
3
3
  A typed graph of reusable Blocks for driving a browser through E2E flows, in place of a
4
4
  flat pile of ad hoc helper functions.
5
5
 
6
- **Current:** `0.10.6` — `try auto` headed by default; `init` / create-waygraph scaffolds on 0.10 + `.flow.ts` scripts.
6
+ **Current:** `0.10.9` — demo carousel stepper (default), `--fast` / `--full`, Hide = compact `N / M`.
7
7
 
8
8
  This repo is the `waygraph` npm package itself. The full live **Sauce Demo** example
9
9
  (Page inventory hub, Effect Add/Remove, MemNav Open details, `waygraph auto`) ships
@@ -271,7 +271,8 @@ Mermaid) is `waygraph graph [project]` (`--mermaid`). Unattended JSON execution
271
271
  | Same via `auto` | `waygraph auto src/flows/shop.flow.ts --data '{…}'` |
272
272
  | Run by export | `waygraph run --blocks shopFlow` |
273
273
  | Manual watch | `waygraph demo src/flows/shop.flow.ts` |
274
- | Auto-advance | `waygraph demo --blocks shopFlow --auto-next` |
274
+ | Auto-advance | `waygraph demo --blocks shopFlow --auto-next` (nav auto-hides strip) |
275
+ | Fast / full strip | `waygraph demo … --fast` · `--full` (classic chips; default = carousel) |
275
276
  | QA watch + record | `waygraph demo --blocks shopFlow --auto-play-video` |
276
277
  | Ad-hoc Blocks | `waygraph run --blocks "login then nav-cart" --data '{…}'` |
277
278
  | Headed execute | `waygraph run --blocks shopFlow --non-headless --video` |
package/dist/cli.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Primary verbs (less is more):
6
6
  * auto Explore picker; `.flow.ts` / `--blocks From To` = run that path
7
- * demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--auto-play-video`
7
+ * demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--fast` `--full`
8
8
  * run Execute; `--blocks` `--data` `--non-headless` `--video`
9
9
  *
10
10
  * Also: list / nav / validate / check / graph / init / try
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Primary verbs (less is more):
6
6
  * auto Explore picker; `.flow.ts` / `--blocks From To` = run that path
7
- * demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--auto-play-video`
7
+ * demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--fast` `--full`
8
8
  * run Execute; `--blocks` `--data` `--non-headless` `--video`
9
9
  *
10
10
  * Also: list / nav / validate / check / graph / init / try
@@ -469,12 +469,16 @@ const RING_CSS =
469
469
  "@keyframes wg-ep-enter{0%{box-shadow:0 0 0 0 rgba(124,58,237,.55);}" +
470
470
  "100%{box-shadow:0 0 0 10px rgba(124,58,237,0);}}" +
471
471
  "#wg-episodes .wg-ep-entered{border-radius:6px;animation:wg-ep-enter 1.1s ease-out;}" +
472
- // The block breadcrumb below it is scoped to the CURRENT episode's own
473
- // blocks only, not the whole chain - "in episode 2 it shows 2nd to the
474
- // last... it should start from the first block" (Dan). An ad hoc block
475
- // chain with no episodes still shows the whole chain here, unchanged.
476
- "#wg-modules{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 10px;}" +
477
- "#wg-modules .wg-mod{padding:3px 9px;border-radius:6px;font:600 11px system-ui,sans-serif;}" +
472
+ // Block strip: carousel (default) or classic wrap (--full).
473
+ "#wg-modules.wg-modules-carousel{display:flex;flex-wrap:nowrap;gap:8px;margin:0 0 10px;" +
474
+ "overflow-x:auto;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;" +
475
+ "padding:4px 2px 8px;scrollbar-width:thin;}" +
476
+ "#wg-modules.wg-modules-carousel .wg-mod{flex:0 0 auto;scroll-snap-align:center;" +
477
+ "padding:6px 12px;border-radius:999px;font:600 12px system-ui,sans-serif;}" +
478
+ "#wg-modules.wg-modules-carousel .wg-mod-current{transform:scale(1.06);" +
479
+ "box-shadow:0 0 0 2px rgba(124,58,237,.45);}" +
480
+ "#wg-modules.wg-modules-full{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 10px;}" +
481
+ "#wg-modules.wg-modules-full .wg-mod{padding:3px 9px;border-radius:6px;font:600 11px system-ui,sans-serif;}" +
478
482
  "#wg-modules .wg-mod-done{background:#2a1650;color:#9a7ad1;}" +
479
483
  "#wg-modules .wg-mod-current{background:#7C3AED;color:#fff;}" +
480
484
  "#wg-modules .wg-mod-upcoming{background:transparent;color:#5a4a80;border:1px solid #3a2a60;}" +
@@ -525,22 +529,23 @@ const RING_CSS =
525
529
  "#wg-panel .wg-expected-heading{color:#ffcf7a;}" +
526
530
  "#wg-panel .wg-expected-reason{font:600 12.5px/1.5 system-ui,sans-serif;background:#2a2410;" +
527
531
  "color:#ffe6ae;border-radius:8px;padding:10px;margin:0 0 8px;}" +
528
- // Hide / Show chrome + mobile bottom-sheet layout (Dan: stepper + auto panels).
532
+ // Hide / Show chrome: collapsed = compact "N / M · block" pill (not empty chrome).
529
533
  "#wg-panel .wg-chrome{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0 0 8px;}" +
530
534
  "#wg-panel .wg-chrome-title{font:700 11px/1.2 system-ui,sans-serif;color:#c9a6ff;" +
531
535
  "letter-spacing:.04em;text-transform:uppercase;}" +
532
536
  "#wg-panel button.wg-hide-btn{margin:0;padding:4px 10px;font:600 11px system-ui,sans-serif;" +
533
537
  "background:#3a2a60;color:#e8dcff;border:1px solid #5b3aa8;border-radius:6px;cursor:pointer;}" +
534
538
  "#wg-panel button.wg-hide-btn:hover{background:#4b2a80;}" +
535
- "#wg-panel.wg-collapsed{width:auto;max-width:90vw;padding:8px 12px;max-height:none;overflow:hidden;}" +
539
+ "#wg-panel.wg-collapsed{width:auto;max-width:92vw;padding:8px 12px;max-height:none;overflow:hidden;}" +
536
540
  "#wg-panel.wg-collapsed .wg-body{display:none;}" +
537
541
  "#wg-panel.wg-collapsed .wg-chrome{margin:0;}" +
542
+ "#wg-panel.wg-collapsed .wg-chrome-title{font:700 13px/1.25 system-ui,sans-serif;color:#fff;" +
543
+ "letter-spacing:0;text-transform:none;}" +
538
544
  "@media (max-width:640px){" +
539
545
  "#wg-panel{left:8px;right:8px;bottom:8px;transform:none;max-width:none;width:auto;" +
540
546
  "max-height:min(55vh,calc(100vh - 16px));padding:12px 14px;border-radius:12px;}" +
541
547
  "#wg-panel.wg-collapsed{left:50%;right:auto;transform:translateX(-50%);width:auto;}" +
542
548
  "#wg-episodes{overflow-x:auto;-webkit-overflow-scrolling:touch;flex-wrap:nowrap;}" +
543
- "#wg-modules{gap:4px;}" +
544
549
  "#wg-panel .wg-narration{font-size:13px;}" +
545
550
  "#wg-panel button{width:100%;}" +
546
551
  "#wg-panel .wg-error-actions{flex-direction:column;}" +
@@ -736,8 +741,11 @@ async function installOverlay(page, title) {
736
741
  }
737
742
  if (iconLink.href !== favicon) iconLink.href = favicon;
738
743
  // Hide/Show for #wg-panel - call after every panel.innerHTML refresh.
739
- window.__wgWirePanelChrome = (panel, storageKey, chromeTitle) => {
744
+ // Collapsed title shows "N / M · block" (compact progress). forceCollapsed
745
+ // is for auto-next navs / video - does not overwrite the user's Hide preference.
746
+ window.__wgWirePanelChrome = (panel, storageKey, chromeTitle, opts) => {
740
747
  if (!panel) return;
748
+ opts = opts || {};
741
749
  if (!panel.querySelector(":scope > .wg-chrome")) {
742
750
  const body = document.createElement("div");
743
751
  body.className = "wg-body";
@@ -757,31 +765,56 @@ async function installOverlay(page, title) {
757
765
  panel.appendChild(chrome);
758
766
  panel.appendChild(body);
759
767
  }
760
- const apply = (hidden) => {
768
+ const stepLabel = opts.stepLabel || panel.dataset.wgStepLabel || "";
769
+ if (stepLabel) panel.dataset.wgStepLabel = stepLabel;
770
+ const apply = (hidden, persist) => {
761
771
  panel.classList.toggle("wg-collapsed", hidden);
762
772
  const t = panel.querySelector("[data-wg-toggle]");
773
+ const titleEl = panel.querySelector(".wg-chrome-title");
774
+ const label = panel.dataset.wgStepLabel || stepLabel;
775
+ if (titleEl) {
776
+ titleEl.textContent = hidden
777
+ ? (label || chromeTitle || "waygraph demo")
778
+ : (chromeTitle || "waygraph demo");
779
+ }
763
780
  if (t) t.textContent = hidden ? "Show" : "Hide";
764
- try {
765
- localStorage.setItem(storageKey, hidden ? "1" : "0");
766
- } catch {
767
- /* private mode */
781
+ if (persist !== false) {
782
+ try {
783
+ localStorage.setItem(storageKey, hidden ? "1" : "0");
784
+ } catch {
785
+ /* private mode */
786
+ }
768
787
  }
769
788
  };
770
789
  let hidden = false;
771
- try {
772
- hidden = localStorage.getItem(storageKey) === "1";
773
- } catch {
774
- /* ignore */
790
+ if (opts.forceCollapsed === true) {
791
+ hidden = true;
792
+ } else if (opts.forceCollapsed === false) {
793
+ hidden = false;
794
+ } else {
795
+ try {
796
+ hidden = localStorage.getItem(storageKey) === "1";
797
+ } catch {
798
+ /* ignore */
799
+ }
775
800
  }
776
- apply(hidden);
801
+ apply(hidden, opts.forceCollapsed === true ? false : true);
777
802
  const toggle = panel.querySelector("[data-wg-toggle]");
778
803
  if (toggle && !toggle.dataset.wgWired) {
779
804
  toggle.dataset.wgWired = "1";
780
805
  toggle.addEventListener("click", (e) => {
781
806
  e.stopPropagation();
782
- apply(!panel.classList.contains("wg-collapsed"));
807
+ apply(!panel.classList.contains("wg-collapsed"), true);
783
808
  });
784
809
  }
810
+ const cur = panel.querySelector("#wg-modules .wg-mod-current");
811
+ if (cur && typeof cur.scrollIntoView === "function") {
812
+ try {
813
+ cur.scrollIntoView({ inline: "center", block: "nearest", behavior: "smooth" });
814
+ } catch {
815
+ cur.scrollIntoView(false);
816
+ }
817
+ }
785
818
  };
786
819
  },
787
820
  { title, favicon: WAYGRAPH_FAVICON, bannerPos, envAutoplay },
@@ -850,6 +883,7 @@ async function renderBeforeStep(page, info) {
850
883
  // step's position WITHIN that subset, not the whole chain's index -
851
884
  // "in episode 2 it shows 2nd to the last... it should start from the
852
885
  // first block" (Dan).
886
+ const modulesClass = info.stepperMode === "full" ? "wg-modules-full" : "wg-modules-carousel";
853
887
  const modulesHtml = info.allNames
854
888
  .map((name, idx) => {
855
889
  const cls = idx < info.moduleIndex ? "wg-mod-done" : idx === info.moduleIndex ? "wg-mod-current" : "wg-mod-upcoming";
@@ -864,7 +898,7 @@ async function renderBeforeStep(page, info) {
864
898
  let html =
865
899
  "<div id=\\"wg-progress\\"><div id=\\"wg-progress-bar\\" style=\\"width:" + pct + "%\\"></div></div>" +
866
900
  episodesHtml +
867
- "<div id=\\"wg-modules\\">" + modulesHtml + "</div>" +
901
+ "<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
868
902
  "<h3>Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + "</h3>" +
869
903
  narrationHtml;
870
904
  if (info.keys.length === 0) {
@@ -897,7 +931,12 @@ async function renderBeforeStep(page, info) {
897
931
  document.documentElement.appendChild(panel);
898
932
  requestAnimationFrame(() => panel.classList.add("wg-in"));
899
933
  }
900
- if (window.__wgWirePanelChrome) window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo");
934
+ if (window.__wgWirePanelChrome) {
935
+ window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
936
+ stepLabel: (info.index + 1) + " / " + info.total + (info.blockName ? " · " + info.blockName : ""),
937
+ forceCollapsed: info.forceCollapsed === true ? true : info.forceCollapsed === false ? false : undefined,
938
+ });
939
+ }
901
940
  // Live, human-readable preview of what a MemKey's raw JSON will
902
941
  // actually write - object fields become "Field: value" lines
903
942
  // (camelCase split the same way state tags already are); a
@@ -1040,6 +1079,7 @@ async function renderAfterStep(page, info) {
1040
1079
  : "Step " + (info.index + 1) + " / " + info.total + " - " + info.blockName + " done";
1041
1080
  const buttonLabel = info.isLast ? "Finish" : "Next \\u25B6";
1042
1081
  const escA = (s) => String(s).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
1082
+ const modulesClass = info.stepperMode === "full" ? "wg-modules-full" : "wg-modules-carousel";
1043
1083
  const modulesHtml = info.allNames
1044
1084
  .map((name, idx) => {
1045
1085
  const cls = idx <= info.moduleIndex ? "wg-mod-done" : "wg-mod-upcoming";
@@ -1094,7 +1134,7 @@ async function renderAfterStep(page, info) {
1094
1134
  panel.innerHTML =
1095
1135
  "<div id=\\"wg-progress\\"><div id=\\"wg-progress-bar\\" style=\\"width:" + pct + "%\\"></div></div>" +
1096
1136
  episodesHtml +
1097
- "<div id=\\"wg-modules\\">" + modulesHtml + "</div>" +
1137
+ "<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
1098
1138
  "<h3>" + heading + "</h3>" +
1099
1139
  resultHtml +
1100
1140
  gateHtml;
@@ -1102,7 +1142,12 @@ async function renderAfterStep(page, info) {
1102
1142
  document.documentElement.appendChild(panel);
1103
1143
  requestAnimationFrame(() => panel.classList.add("wg-in"));
1104
1144
  }
1105
- if (window.__wgWirePanelChrome) window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo");
1145
+ if (window.__wgWirePanelChrome) {
1146
+ window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
1147
+ stepLabel: (info.index + 1) + " / " + info.total + (info.blockName ? " · " + info.blockName : ""),
1148
+ forceCollapsed: info.forceCollapsed === true ? true : info.forceCollapsed === false ? false : undefined,
1149
+ });
1150
+ }
1106
1151
  panel.querySelectorAll(".wg-toggle-btn").forEach((btn) => {
1107
1152
  btn.addEventListener("click", () => {
1108
1153
  const wantPretty = btn.getAttribute("data-mode") === "pretty";
@@ -1169,6 +1214,7 @@ async function renderStepError(page, info) {
1169
1214
  .join("") +
1170
1215
  "</div>"
1171
1216
  : "";
1217
+ const modulesClass = info.stepperMode === "full" ? "wg-modules-full" : "wg-modules-carousel";
1172
1218
  const modulesHtml = info.allNames
1173
1219
  .map((name, idx) => {
1174
1220
  const cls = idx === info.moduleIndex ? "wg-mod-current" : idx < info.moduleIndex ? "wg-mod-done" : "wg-mod-upcoming";
@@ -1186,7 +1232,7 @@ async function renderStepError(page, info) {
1186
1232
  : "";
1187
1233
  panel.innerHTML =
1188
1234
  episodesHtml +
1189
- "<div id=\\"wg-modules\\">" + modulesHtml + "</div>" +
1235
+ "<div id=\\"wg-modules\\" class=\\"" + modulesClass + "\\">" + modulesHtml + "</div>" +
1190
1236
  "<h3 class=\\"" + (isExpected ? "wg-expected-heading" : "wg-error-heading") + "\\">" + headingText + "</h3>" +
1191
1237
  reasonHtml +
1192
1238
  "<div class=\\"wg-error-msg\\">" + esc(info.message) + "</div>" +
@@ -1198,7 +1244,12 @@ async function renderStepError(page, info) {
1198
1244
  document.documentElement.appendChild(panel);
1199
1245
  requestAnimationFrame(() => panel.classList.add("wg-in"));
1200
1246
  }
1201
- if (window.__wgWirePanelChrome) window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo");
1247
+ if (window.__wgWirePanelChrome) {
1248
+ window.__wgWirePanelChrome(panel, "wg-panel-hidden", "waygraph demo", {
1249
+ stepLabel: (info.index + 1) + " / " + info.total + (info.blockName ? " · " + info.blockName : ""),
1250
+ forceCollapsed: info.forceCollapsed === true ? true : info.forceCollapsed === false ? false : undefined,
1251
+ });
1252
+ }
1202
1253
  const runBtn = document.getElementById("wg-run");
1203
1254
  if (runBtn) runBtn.addEventListener("click", () => window.__wgNext({}));
1204
1255
  const retryBtn = document.getElementById("wg-error-retry");
@@ -1308,6 +1359,9 @@ async function markStepRunning(page, opts) {
1308
1359
  panel.classList.add("wg-collapsed");
1309
1360
  const toggle = panel.querySelector("[data-wg-toggle]");
1310
1361
  if (toggle) toggle.textContent = "Show";
1362
+ const titleEl = panel.querySelector(".wg-chrome-title");
1363
+ const label = panel.dataset.wgStepLabel;
1364
+ if (titleEl && label) titleEl.textContent = label;
1311
1365
  }
1312
1366
  }
1313
1367
  }, { autoCollapsePanel: !!(opts && opts.autoCollapsePanel) })
@@ -1644,7 +1698,11 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
1644
1698
  // own added dwell while the rest of the chain keeps the full theatrical
1645
1699
  // pace. Playwright's own slowMo is process-wide and untouched by this -
1646
1700
  // only OUR added pauses (ring pop, cursor travel, typing delay) shrink.
1647
- const pacing = { fast: false };
1701
+ // --fast / WAYGRAPH_DEMO_FAST: every step uses fast interaction pacing + shorter gates.
1702
+ // WAYGRAPH_FAST_BLOCKS still names per-block overrides when --fast is off.
1703
+ const demoFast = process.env.WAYGRAPH_DEMO_FAST === "1";
1704
+ const stepperMode = process.env.WAYGRAPH_STEPPER === "full" ? "full" : "carousel";
1705
+ const pacing = { fast: demoFast };
1648
1706
  instrumentInteractionHighlighting(page, mem, slowMo, pacing);
1649
1707
  // Force the panel checkbox from this process's flags/env at run start.
1650
1708
  // installOverlay only seeds localStorage when the key is null (so mid-run
@@ -1689,7 +1747,11 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
1689
1747
  // which way the checkbox is set. That's the hybrid Dan asked for: some
1690
1748
  // steps auto-advance, some get a manual click, toggled as the demo goes,
1691
1749
  // not fixed for the whole run from a single env var.
1692
- const autoplayMs = process.env.WAYGRAPH_AUTOPLAY_MS ? Number(process.env.WAYGRAPH_AUTOPLAY_MS) : 1800;
1750
+ const autoplayMs = process.env.WAYGRAPH_AUTOPLAY_MS
1751
+ ? Number(process.env.WAYGRAPH_AUTOPLAY_MS)
1752
+ : demoFast
1753
+ ? 450
1754
+ : 1800;
1693
1755
  const currentAutoplay = () =>
1694
1756
  page
1695
1757
  .evaluate(() => {
@@ -1789,7 +1851,12 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
1789
1851
  if ((clearSession || r.resetSession) && i > 0) {
1790
1852
  await resetPageState(context, page, baseURL);
1791
1853
  }
1792
- pacing.fast = fastBlockNames.has(r.block.name);
1854
+ pacing.fast = demoFast || fastBlockNames.has(r.block.name);
1855
+ const isNavBlock = r.block.__waygraphKind === "nav";
1856
+ const autoNow = await currentAutoplay();
1857
+ // Auto-next: tuck stepper on NavBlocks so the page transition fills the frame.
1858
+ // Video recording still collapses every step.
1859
+ const forceCollapsed = !!process.env.WAYGRAPH_VIDEO || (autoNow && isNavBlock);
1793
1860
  const requires = r.block.requires ?? [];
1794
1861
  const keys = requires.map((k) => {
1795
1862
  let value = "<not yet set>";
@@ -1814,6 +1881,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
1814
1881
  title,
1815
1882
  episodeNumber: r.episodeNumber,
1816
1883
  episodeTitle: r.episodeTitle,
1884
+ stepperMode,
1885
+ forceCollapsed,
1817
1886
  });
1818
1887
  const edits = await gate();
1819
1888
  for (const k of requires) {
@@ -1826,7 +1895,7 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
1826
1895
  }
1827
1896
  }
1828
1897
  const recordingVideo = !!process.env.WAYGRAPH_VIDEO;
1829
- const autoCollapsePanel = recordingVideo || (await currentAutoplay());
1898
+ const autoCollapsePanel = recordingVideo || (await currentAutoplay() && isNavBlock) || forceCollapsed;
1830
1899
  await markStepRunning(page, { autoCollapsePanel });
1831
1900
  // NavBlock click-nav: label the upcoming Locator.click demo cursor as
1832
1901
  // "nav: <block>" so pia/demo watchers see cursor+pulse on click nav
@@ -1876,6 +1945,8 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
1876
1945
  episodeNumber: r.episodeNumber,
1877
1946
  episodeTitle: r.episodeTitle,
1878
1947
  expectedFailureReason: r.expectedFailureReason,
1948
+ stepperMode,
1949
+ forceCollapsed: false,
1879
1950
  });
1880
1951
  const errEdits = await gate();
1881
1952
  if (errEdits && errEdits.__wgRetry) {
@@ -1920,6 +1991,9 @@ async function runStepMode(engine, start, end, context, page, mem, resolved, slo
1920
1991
  title,
1921
1992
  episodeNumber: r.episodeNumber,
1922
1993
  episodeTitle: r.episodeTitle,
1994
+ stepperMode,
1995
+ // After a nav under auto-next, stay compact until the next action block expands.
1996
+ forceCollapsed: !!process.env.WAYGRAPH_VIDEO || ((await currentAutoplay()) && isNavBlock),
1923
1997
  });
1924
1998
  await gate();
1925
1999
  }
@@ -2840,6 +2914,12 @@ function parseRunFlags(argv) {
2840
2914
  else if (a === "--auto-play-video") {
2841
2915
  out.autoPlayVideo = true;
2842
2916
  }
2917
+ else if (a === "--fast") {
2918
+ out.fast = true;
2919
+ }
2920
+ else if (a === "--full") {
2921
+ out.fullStepper = true;
2922
+ }
2843
2923
  else if (a === "--non-headless") {
2844
2924
  out.nonHeadless = true;
2845
2925
  }
@@ -2945,6 +3025,10 @@ function applyRunFlags(flags, opts) {
2945
3025
  if (flags.step === undefined)
2946
3026
  flags.step = true;
2947
3027
  }
3028
+ if ((flags.fast || flags.fullStepper) && !opts?.allowDemoUi) {
3029
+ console.error("waygraph: --fast / --full are demo-only flags");
3030
+ process.exit(1);
3031
+ }
2948
3032
  if (flags.step === true) {
2949
3033
  process.env.WAYGRAPH_STEP = "1";
2950
3034
  process.env.WAYGRAPH_HEADED = "1";
@@ -2980,6 +3064,12 @@ function applyRunFlags(flags, opts) {
2980
3064
  if (flags.data !== undefined) {
2981
3065
  process.env.WAYGRAPH_DATA = flags.data;
2982
3066
  }
3067
+ if (flags.fast) {
3068
+ process.env.WAYGRAPH_DEMO_FAST = "1";
3069
+ }
3070
+ if (flags.fullStepper) {
3071
+ process.env.WAYGRAPH_STEPPER = "full";
3072
+ }
2983
3073
  }
2984
3074
  /**
2985
3075
  * BASE_URL when neither --base-url nor WAYGRAPH_BASE_URL is set:
@@ -3135,6 +3225,8 @@ Primary (less is more):
3135
3225
  waygraph demo [--blocks <flow|file|spec>] Watch with step overlay (QA path)
3136
3226
  --data '{...}' Mem seed JSON (or inline flow({...}))
3137
3227
  --auto-next Auto-advance steps (alias: --autoplay)
3228
+ --fast Faster transitions + shorter auto-next gates
3229
+ --full Classic wrap-all block chips (default: carousel)
3138
3230
  --auto-play-video QA: --auto-next + --video (+ step)
3139
3231
  --video-viewport WxH Recording size (default demo: 1920x1080)
3140
3232
  --title / --base-url
@@ -3155,7 +3247,8 @@ Examples:
3155
3247
  waygraph list # file → export map
3156
3248
  waygraph run src/flows/shop.flow.ts --data '{...}'
3157
3249
  waygraph run --blocks shopFlow --non-headless --video
3158
- waygraph demo --blocks src/flows/cart-bulk.flow.ts --auto-next
3250
+ waygraph demo --blocks src/flows/cart-bulk.flow.ts --auto-next --fast
3251
+ waygraph demo src/flows/shop.flow.ts --full
3159
3252
  waygraph try auto
3160
3253
  waygraph try auto:cli
3161
3254
  waygraph auto src/flows/shop.flow.ts --data '{...}' # run by file (same as run)
@@ -3168,6 +3261,7 @@ Aliases (compat): \`chain <spec>\` -> run --blocks; \`chain auto A B\` -> auto -
3168
3261
 
3169
3262
  Project path optional (defaults to cwd). Flags beat WAYGRAPH_* env.
3170
3263
  \`run\`/\`demo\`/\`auto\`: Flow export, .flow.ts path, or "a then b" chain (auto also explores).
3264
+ Hide stepper = compact "N / M · block" pill. Auto-next auto-hides on NavBlocks.
3171
3265
  `);
3172
3266
  process.exit(0);
3173
3267
  }
@@ -3282,13 +3376,13 @@ async function main() {
3282
3376
  }
3283
3377
  case "demo": {
3284
3378
  const flags = parseRunFlags(args.slice(1));
3285
- applyRunFlags(flags, { allowAutoPlayVideo: true });
3379
+ applyRunFlags(flags, { allowAutoPlayVideo: true, allowDemoUi: true });
3286
3380
  const spec = resolveSpec(flags);
3287
3381
  if (!spec) {
3288
3382
  console.error("waygraph demo: missing flow/spec — e.g.\n" +
3289
3383
  " waygraph demo src/flows/shop.flow.ts\n" +
3290
3384
  " waygraph demo --blocks shopFlow\n" +
3291
- " Flags: --blocks --data --auto-next --auto-play-video --title --base-url --video");
3385
+ " Flags: --blocks --data --auto-next --fast --full --auto-play-video --title --base-url --video");
3292
3386
  process.exit(1);
3293
3387
  }
3294
3388
  const proj = flags.blocks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.10.8",
3
+ "version": "0.10.9",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",
@@ -23,7 +23,7 @@
23
23
  "baseUrl": "https://www.saucedemo.com"
24
24
  },
25
25
  "dependencies": {
26
- "waygraph": "^0.10.6"
26
+ "waygraph": "^0.10.9"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@playwright/test": "^1.55.0",
@@ -18,7 +18,7 @@
18
18
  "baseUrl": "https://example.com"
19
19
  },
20
20
  "dependencies": {
21
- "waygraph": "^0.10.6"
21
+ "waygraph": "^0.10.9"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@playwright/test": "^1.55.0",