@sanity/workflow-diagram 0.2.0 → 0.4.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # @sanity/workflow-diagram
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b341a7d: Add a navigation-free harness view for ad hoc workflow definitions with light and dark screenshot themes.
8
+
9
+ ### Patch Changes
10
+
11
+ - 7276702: Stage tooltips use editor vocabulary: the current stage's lock badge reads "N document hold(s) active" instead of "mutation guard(s)", and the explain tooltip's overflow note reads "+ N more condition(s)" instead of "gate(s)".
12
+ - Updated dependencies [239d9f6]
13
+ - Updated dependencies [3af2ca0]
14
+ - Updated dependencies [7276702]
15
+ - Updated dependencies [58f8211]
16
+ - Updated dependencies [00c089d]
17
+ - Updated dependencies [71cd58e]
18
+ - Updated dependencies [cd03973]
19
+ - Updated dependencies [d26cc1a]
20
+ - Updated dependencies [9126299]
21
+ - Updated dependencies [72018af]
22
+ - Updated dependencies [5cd8ffd]
23
+ - Updated dependencies [b82bda4]
24
+ - Updated dependencies [55a54b3]
25
+ - Updated dependencies [b82bda4]
26
+ - Updated dependencies [4490442]
27
+ - Updated dependencies [1540dce]
28
+ - @sanity/workflow-engine@0.18.0
29
+
30
+ ## 0.3.0
31
+
32
+ ### Minor Changes
33
+
34
+ - f9389e5: The explain tooltips' activity roster narrates causal waits: an activity that does not complete without a caller shows what it waits on ("Waits on caller action …") under its executor-classification line, on both the static and live tiers.
35
+ - b43de49: elkjs now loads lazily on the first layout instead of riding the package's static import graph. Bundlers split the ~1.4 MB (minified) ELK engine into an async chunk fetched only when a diagram actually renders, cutting the package's synchronous bundle cost by roughly 72% (about 600 KB → 168 KB gzipped on the critical path).
36
+
37
+ ### Patch Changes
38
+
39
+ - Updated dependencies [f9389e5]
40
+ - Updated dependencies [d0c62ea]
41
+ - Updated dependencies [c3eed2e]
42
+ - Updated dependencies [df4bd80]
43
+ - Updated dependencies [092a0d4]
44
+ - Updated dependencies [5a1a9fe]
45
+ - Updated dependencies [e3a7ba2]
46
+ - Updated dependencies [30fed9e]
47
+ - Updated dependencies [1321ba5]
48
+ - Updated dependencies [e683875]
49
+ - Updated dependencies [a8ace4d]
50
+ - @sanity/workflow-engine@0.17.0
51
+
3
52
  ## 0.2.0
4
53
 
5
54
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -32,15 +32,7 @@ var jsxRuntime = require("react/jsx-runtime");
32
32
 
33
33
  require("@xyflow/react/dist/style.css");
34
34
 
35
- var ui = require("@sanity/ui"), react$1 = require("@xyflow/react"), react = require("react"), workflowEngine = require("@sanity/workflow-engine"), ELK = require("elkjs/lib/elk.bundled.js"), icons = require("@sanity/icons"), reactDom = require("react-dom");
36
-
37
- function _interopDefaultCompat(e) {
38
- return e && typeof e == "object" && "default" in e ? e : {
39
- default: e
40
- };
41
- }
42
-
43
- var ELK__default = /* @__PURE__ */ _interopDefaultCompat(ELK);
35
+ var ui = require("@sanity/ui"), react$1 = require("@xyflow/react"), react = require("react"), workflowEngine = require("@sanity/workflow-engine"), icons = require("@sanity/icons"), reactDom = require("react-dom");
44
36
 
45
37
  const DevModeContext = react.createContext(!1);
46
38
 
@@ -54,6 +46,12 @@ function useHoveredStage() {
54
46
  return react.useContext(HoveredStageContext);
55
47
  }
56
48
 
49
+ function narratedWaits(answer, definition) {
50
+ return answer.completesWithoutCaller === "yes" ? [] : workflowEngine.narrateAutonomyWaits(answer.waitsOn, {
51
+ definition: definition
52
+ }).map(workflowEngine.sentenceCase);
53
+ }
54
+
57
55
  const ExplainContext = react.createContext(void 0);
58
56
 
59
57
  function useExplain() {
@@ -81,11 +79,15 @@ function indexExplanations(sites, definition) {
81
79
  live: !1
82
80
  }), byStage.set(site.stage, list);
83
81
  }
84
- const activitiesByStage = new Map(definition.stages.map(stage => [ stage.name, (stage.activities ?? []).map(activity => ({
85
- name: activity.name,
86
- title: activity.title ?? activity.name,
87
- classification: workflowEngine.deriveExecutorClassification(activity)
88
- })) ]));
82
+ const autonomy = workflowEngine.deriveWorkflowAutonomy(definition), activitiesByStage = new Map(definition.stages.map(stage => {
83
+ const stageAutonomy = workflowEngine.stageAutonomyOf(autonomy, stage.name);
84
+ return [ stage.name, (stage.activities ?? []).map(activity => ({
85
+ name: activity.name,
86
+ title: activity.title ?? activity.name,
87
+ classification: workflowEngine.deriveExecutorClassification(activity),
88
+ waits: narratedWaits(workflowEngine.activityAutonomyOf(stageAutonomy, activity.name), definition)
89
+ })) ];
90
+ }));
89
91
  return {
90
92
  transition: (stage, name) => transitions.get(`${stage}#${name}`),
91
93
  stageGates: stage => byStage.get(stage) ?? [],
@@ -142,7 +144,8 @@ function indexLiveExplanations(evaluation) {
142
144
  const activities = evaluation.currentStage.activities.map(a => ({
143
145
  name: a.activity.name,
144
146
  title: a.activity.title ?? a.activity.name,
145
- classification: a.classification
147
+ classification: a.classification,
148
+ waits: narratedWaits(a.autonomy, evaluation.definition)
146
149
  }));
147
150
  return {
148
151
  transition: (stage, name) => transitions.get(`${stage}#${name}`),
@@ -193,7 +196,7 @@ function cappedGates(gates) {
193
196
  const shown = gates.slice(0, STAGE_GATE_CAP), hidden = gates.length - shown.length;
194
197
  return {
195
198
  shown: shown,
196
- overflowNote: hidden > 0 ? `+ ${hidden} more gate${hidden === 1 ? "" : "s"}` : void 0
199
+ overflowNote: hidden > 0 ? `+ ${hidden} more condition${hidden === 1 ? "" : "s"}` : void 0
197
200
  };
198
201
  }
199
202
 
@@ -565,7 +568,15 @@ function GhostNode({data: data}) {
565
568
  });
566
569
  }
567
570
 
568
- const DEFAULT_WIDTH = 180, DEFAULT_HEIGHT = 72, elk = new ELK__default.default, STAGE_CHAR_PX = 8.5, STAGE_PAD_X = 16, STAGE_ICON_W = 25, STAGE_PAD_Y = 13, STAGE_LINE_H = 18, STAGE_MAX_TEXT_WIDTH = 150, STAGE_MIN_TEXT_WIDTH = 44;
571
+ let elkLoad;
572
+
573
+ function loadElk() {
574
+ return elkLoad ??= import("elkjs/lib/elk.bundled.js").then(mod => new mod.default).catch(err => {
575
+ throw elkLoad = void 0, err;
576
+ }), elkLoad;
577
+ }
578
+
579
+ const STAGE_CHAR_PX = 8.5, STAGE_PAD_X = 16, STAGE_ICON_W = 25, STAGE_PAD_Y = 13, STAGE_LINE_H = 18, STAGE_MAX_TEXT_WIDTH = 150, STAGE_MIN_TEXT_WIDTH = 44;
569
580
 
570
581
  function wrapStageLabel(title) {
571
582
  const words = title.trim().split(/\s+/), lines = [];
@@ -638,9 +649,9 @@ async function layoutGraph(nodes, edges) {
638
649
  nodes: [ ...nodes ],
639
650
  edges: [ ...edges ]
640
651
  };
641
- const railNodes = new Set(edges.flatMap(e => edgeFlags(e).isPrimary ? [ e.source, e.target ] : [])), boxOf = new Map(nodes.map(n => [ n.id, {
642
- w: n.width ?? DEFAULT_WIDTH,
643
- h: n.height ?? DEFAULT_HEIGHT
652
+ const elk = await loadElk(), railNodes = new Set(edges.flatMap(e => edgeFlags(e).isPrimary ? [ e.source, e.target ] : [])), boxOf = new Map(nodes.map(n => [ n.id, {
653
+ w: n.width ?? 180,
654
+ h: n.height ?? 72
644
655
  } ])), portMeta = /* @__PURE__ */ new Map, portFor = args => {
645
656
  const id = `${args.node}.${args.key}`, list = portMeta.get(args.node) ?? [];
646
657
  return list.push({
@@ -691,8 +702,8 @@ async function layoutGraph(nodes, edges) {
691
702
  layoutOptions: defaultLayoutOptions(),
692
703
  children: nodes.map(n => ({
693
704
  id: n.id,
694
- width: n.width ?? DEFAULT_WIDTH,
695
- height: n.height ?? DEFAULT_HEIGHT,
705
+ width: n.width ?? 180,
706
+ height: n.height ?? 72,
696
707
  layoutOptions: {
697
708
  ...layerConstraintFor(n).layoutOptions,
698
709
  "elk.portConstraints": constraint
@@ -746,8 +757,8 @@ function faceOffRailPorts(args) {
746
757
  function pass1Centers(pass1, boxOf) {
747
758
  return new Map((pass1.children ?? []).map(c => {
748
759
  const box = boxOf.get(c.id) ?? {
749
- w: DEFAULT_WIDTH,
750
- h: DEFAULT_HEIGHT
760
+ w: 180,
761
+ h: 72
751
762
  };
752
763
  return [ c.id, {
753
764
  cx: (c.x ?? 0) + box.w / 2,
@@ -830,8 +841,8 @@ function pinnedPortPositions(args) {
830
841
  for (const child of args.pass1.children ?? []) pinChildPorts(child, {
831
842
  meta: args.portMeta.get(child.id) ?? [],
832
843
  box: args.boxOf.get(child.id) ?? {
833
- w: DEFAULT_WIDTH,
834
- h: DEFAULT_HEIGHT
844
+ w: 180,
845
+ h: 72
835
846
  },
836
847
  centers: centers,
837
848
  out: out
@@ -1481,10 +1492,22 @@ function ActivityRoster({activities: activities}) {
1481
1492
  /* @__PURE__ */
1482
1493
  return jsxRuntime.jsx(ui.Stack, {
1483
1494
  space: 2,
1484
- children: activities.map(activity => /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
1485
- size: 1,
1486
- muted: !0,
1487
- children: [ activity.title, " — ", workflowEngine.EXECUTOR_CLASSIFICATION_DISPLAY[activity.classification].title ]
1495
+ children: activities.map(activity => /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1496
+ space: 1,
1497
+ children: [
1498
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
1499
+ size: 1,
1500
+ muted: !0,
1501
+ children: [ activity.title, " — ", workflowEngine.EXECUTOR_CLASSIFICATION_DISPLAY[activity.classification].title ]
1502
+ }), activity.waits.map((wait, i) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1503
+ size: 1,
1504
+ muted: !0,
1505
+ style: {
1506
+ paddingLeft: 12,
1507
+ lineHeight: 1.4
1508
+ },
1509
+ children: wait
1510
+ }, i)) ]
1488
1511
  }, activity.name))
1489
1512
  });
1490
1513
  }
@@ -1577,7 +1600,7 @@ function nodeOpacity(data) {
1577
1600
  function GuardBadge({count: count}) {
1578
1601
  /* @__PURE__ */
1579
1602
  return jsxRuntime.jsxs("span", {
1580
- title: `${count} mutation guard${count === 1 ? "" : "s"} active`,
1603
+ title: `${count} document hold${count === 1 ? "" : "s"} active`,
1581
1604
  style: {
1582
1605
  display: "inline-flex",
1583
1606
  alignItems: "center",
package/dist/index.js CHANGED
@@ -8,9 +8,7 @@ import { MarkerType, getSmoothStepPath, Handle, Position, BaseEdge, EdgeLabelRen
8
8
 
9
9
  import { createContext, useContext, Fragment, useState, useMemo, useEffect, useRef, useCallback } from "react";
10
10
 
11
- import { sentenceCase, describeSiteHeading, deriveExecutorClassification, describeCondition, checklistLines, isTerminalStage, EXECUTOR_CLASSIFICATION_DISPLAY } from "@sanity/workflow-engine";
12
-
13
- import ELK from "elkjs/lib/elk.bundled.js";
11
+ import { sentenceCase, describeSiteHeading, deriveWorkflowAutonomy, stageAutonomyOf, activityAutonomyOf, deriveExecutorClassification, describeCondition, narrateAutonomyWaits, checklistLines, isTerminalStage, EXECUTOR_CLASSIFICATION_DISPLAY } from "@sanity/workflow-engine";
14
12
 
15
13
  import { CheckmarkCircleIcon, CircleIcon, LockIcon } from "@sanity/icons";
16
14
 
@@ -28,6 +26,12 @@ function useHoveredStage() {
28
26
  return useContext(HoveredStageContext);
29
27
  }
30
28
 
29
+ function narratedWaits(answer, definition) {
30
+ return answer.completesWithoutCaller === "yes" ? [] : narrateAutonomyWaits(answer.waitsOn, {
31
+ definition: definition
32
+ }).map(sentenceCase);
33
+ }
34
+
31
35
  const ExplainContext = createContext(void 0);
32
36
 
33
37
  function useExplain() {
@@ -55,11 +59,15 @@ function indexExplanations(sites, definition) {
55
59
  live: !1
56
60
  }), byStage.set(site.stage, list);
57
61
  }
58
- const activitiesByStage = new Map(definition.stages.map(stage => [ stage.name, (stage.activities ?? []).map(activity => ({
59
- name: activity.name,
60
- title: activity.title ?? activity.name,
61
- classification: deriveExecutorClassification(activity)
62
- })) ]));
62
+ const autonomy = deriveWorkflowAutonomy(definition), activitiesByStage = new Map(definition.stages.map(stage => {
63
+ const stageAutonomy = stageAutonomyOf(autonomy, stage.name);
64
+ return [ stage.name, (stage.activities ?? []).map(activity => ({
65
+ name: activity.name,
66
+ title: activity.title ?? activity.name,
67
+ classification: deriveExecutorClassification(activity),
68
+ waits: narratedWaits(activityAutonomyOf(stageAutonomy, activity.name), definition)
69
+ })) ];
70
+ }));
63
71
  return {
64
72
  transition: (stage, name) => transitions.get(`${stage}#${name}`),
65
73
  stageGates: stage => byStage.get(stage) ?? [],
@@ -116,7 +124,8 @@ function indexLiveExplanations(evaluation) {
116
124
  const activities = evaluation.currentStage.activities.map(a => ({
117
125
  name: a.activity.name,
118
126
  title: a.activity.title ?? a.activity.name,
119
- classification: a.classification
127
+ classification: a.classification,
128
+ waits: narratedWaits(a.autonomy, evaluation.definition)
120
129
  }));
121
130
  return {
122
131
  transition: (stage, name) => transitions.get(`${stage}#${name}`),
@@ -167,7 +176,7 @@ function cappedGates(gates) {
167
176
  const shown = gates.slice(0, STAGE_GATE_CAP), hidden = gates.length - shown.length;
168
177
  return {
169
178
  shown: shown,
170
- overflowNote: hidden > 0 ? `+ ${hidden} more gate${hidden === 1 ? "" : "s"}` : void 0
179
+ overflowNote: hidden > 0 ? `+ ${hidden} more condition${hidden === 1 ? "" : "s"}` : void 0
171
180
  };
172
181
  }
173
182
 
@@ -539,7 +548,15 @@ function GhostNode({data: data}) {
539
548
  });
540
549
  }
541
550
 
542
- const DEFAULT_WIDTH = 180, DEFAULT_HEIGHT = 72, elk = new ELK, STAGE_CHAR_PX = 8.5, STAGE_PAD_X = 16, STAGE_ICON_W = 25, STAGE_PAD_Y = 13, STAGE_LINE_H = 18, STAGE_MAX_TEXT_WIDTH = 150, STAGE_MIN_TEXT_WIDTH = 44;
551
+ let elkLoad;
552
+
553
+ function loadElk() {
554
+ return elkLoad ??= import("elkjs/lib/elk.bundled.js").then(mod => new mod.default).catch(err => {
555
+ throw elkLoad = void 0, err;
556
+ }), elkLoad;
557
+ }
558
+
559
+ const STAGE_CHAR_PX = 8.5, STAGE_PAD_X = 16, STAGE_ICON_W = 25, STAGE_PAD_Y = 13, STAGE_LINE_H = 18, STAGE_MAX_TEXT_WIDTH = 150, STAGE_MIN_TEXT_WIDTH = 44;
543
560
 
544
561
  function wrapStageLabel(title) {
545
562
  const words = title.trim().split(/\s+/), lines = [];
@@ -612,9 +629,9 @@ async function layoutGraph(nodes, edges) {
612
629
  nodes: [ ...nodes ],
613
630
  edges: [ ...edges ]
614
631
  };
615
- const railNodes = new Set(edges.flatMap(e => edgeFlags(e).isPrimary ? [ e.source, e.target ] : [])), boxOf = new Map(nodes.map(n => [ n.id, {
616
- w: n.width ?? DEFAULT_WIDTH,
617
- h: n.height ?? DEFAULT_HEIGHT
632
+ const elk = await loadElk(), railNodes = new Set(edges.flatMap(e => edgeFlags(e).isPrimary ? [ e.source, e.target ] : [])), boxOf = new Map(nodes.map(n => [ n.id, {
633
+ w: n.width ?? 180,
634
+ h: n.height ?? 72
618
635
  } ])), portMeta = /* @__PURE__ */ new Map, portFor = args => {
619
636
  const id = `${args.node}.${args.key}`, list = portMeta.get(args.node) ?? [];
620
637
  return list.push({
@@ -665,8 +682,8 @@ async function layoutGraph(nodes, edges) {
665
682
  layoutOptions: defaultLayoutOptions(),
666
683
  children: nodes.map(n => ({
667
684
  id: n.id,
668
- width: n.width ?? DEFAULT_WIDTH,
669
- height: n.height ?? DEFAULT_HEIGHT,
685
+ width: n.width ?? 180,
686
+ height: n.height ?? 72,
670
687
  layoutOptions: {
671
688
  ...layerConstraintFor(n).layoutOptions,
672
689
  "elk.portConstraints": constraint
@@ -720,8 +737,8 @@ function faceOffRailPorts(args) {
720
737
  function pass1Centers(pass1, boxOf) {
721
738
  return new Map((pass1.children ?? []).map(c => {
722
739
  const box = boxOf.get(c.id) ?? {
723
- w: DEFAULT_WIDTH,
724
- h: DEFAULT_HEIGHT
740
+ w: 180,
741
+ h: 72
725
742
  };
726
743
  return [ c.id, {
727
744
  cx: (c.x ?? 0) + box.w / 2,
@@ -804,8 +821,8 @@ function pinnedPortPositions(args) {
804
821
  for (const child of args.pass1.children ?? []) pinChildPorts(child, {
805
822
  meta: args.portMeta.get(child.id) ?? [],
806
823
  box: args.boxOf.get(child.id) ?? {
807
- w: DEFAULT_WIDTH,
808
- h: DEFAULT_HEIGHT
824
+ w: 180,
825
+ h: 72
809
826
  },
810
827
  centers: centers,
811
828
  out: out
@@ -1455,10 +1472,22 @@ function ActivityRoster({activities: activities}) {
1455
1472
  /* @__PURE__ */
1456
1473
  return jsx(Stack, {
1457
1474
  space: 2,
1458
- children: activities.map(activity => /* @__PURE__ */ jsxs(Text, {
1459
- size: 1,
1460
- muted: !0,
1461
- children: [ activity.title, " — ", EXECUTOR_CLASSIFICATION_DISPLAY[activity.classification].title ]
1475
+ children: activities.map(activity => /* @__PURE__ */ jsxs(Stack, {
1476
+ space: 1,
1477
+ children: [
1478
+ /* @__PURE__ */ jsxs(Text, {
1479
+ size: 1,
1480
+ muted: !0,
1481
+ children: [ activity.title, " — ", EXECUTOR_CLASSIFICATION_DISPLAY[activity.classification].title ]
1482
+ }), activity.waits.map((wait, i) => /* @__PURE__ */ jsx(Text, {
1483
+ size: 1,
1484
+ muted: !0,
1485
+ style: {
1486
+ paddingLeft: 12,
1487
+ lineHeight: 1.4
1488
+ },
1489
+ children: wait
1490
+ }, i)) ]
1462
1491
  }, activity.name))
1463
1492
  });
1464
1493
  }
@@ -1551,7 +1580,7 @@ function nodeOpacity(data) {
1551
1580
  function GuardBadge({count: count}) {
1552
1581
  /* @__PURE__ */
1553
1582
  return jsxs("span", {
1554
- title: `${count} mutation guard${count === 1 ? "" : "s"} active`,
1583
+ title: `${count} document hold${count === 1 ? "" : "s"} active`,
1555
1584
  style: {
1556
1585
  display: "inline-flex",
1557
1586
  alignItems: "center",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/workflow-diagram",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "React component rendering a workflow definition as a stage diagram — ELK layered layout, current-stage and visited-path highlighting, transition tooltips on line hover.",
5
5
  "keywords": [
6
6
  "diagram",
@@ -44,7 +44,7 @@
44
44
  "@sanity/icons": "^3.7.7",
45
45
  "@xyflow/react": "^12.11.1",
46
46
  "elkjs": "^0.11.1",
47
- "@sanity/workflow-engine": "0.16.0"
47
+ "@sanity/workflow-engine": "0.18.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@sanity/pkg-utils": "^10.5.3",
@@ -58,8 +58,8 @@
58
58
  "react-dom": "^19.2.7",
59
59
  "vite": "^8.1.2",
60
60
  "vitest": "^4.1.8",
61
- "@sanity/workflow-engine-test": "0.11.0",
62
- "@sanity/workflow-examples": "0.6.0"
61
+ "@sanity/workflow-engine-test": "0.13.0",
62
+ "@sanity/workflow-examples": "0.8.0"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "@sanity/ui": "^3.3.0",