@sanity/workflow-studio-plugin 0.5.0 → 0.21.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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- var workflowEngine = require("@sanity/workflow-engine"), jsxRuntime = require("react/jsx-runtime"), Timeline = require("@sanity/icons/Timeline"), ui = require("@sanity/ui"), react = require("react"), structure = require("sanity/structure"), formatDistanceToNow = require("date-fns/formatDistanceToNow"), csm = require("@sanity/client/csm"), workflowStudio = require("@sanity/workflow-studio"), sanity = require("sanity"), Calendar = require("@sanity/icons/Calendar"), workflowComponents = require("@sanity/workflow-components"), User = require("@sanity/icons/User"), Close = require("@sanity/icons/Close"), Search = require("@sanity/icons/Search"), ChevronRight = require("@sanity/icons/ChevronRight"), Checkmark = require("@sanity/icons/Checkmark"), Edit = require("@sanity/icons/Edit"), content = require("@sanity/util/content"), InfoOutline = require("@sanity/icons/InfoOutline"), Document = require("@sanity/icons/Document"), WarningOutline = require("@sanity/icons/WarningOutline"), router = require("sanity/router"), types = require("@sanity/types"), Launch = require("@sanity/icons/Launch"), Empty$1 = require("@sanity/icons/Empty"), ErrorFilled = require("@sanity/icons/ErrorFilled"), RemoveCircle = require("@sanity/icons/RemoveCircle"), Add = require("@sanity/icons/Add"), Bolt = require("@sanity/icons/Bolt"), ChevronDown = require("@sanity/icons/ChevronDown"), CheckmarkCircle = require("@sanity/icons/CheckmarkCircle"), Ulist = require("@sanity/icons/Ulist"), pluralize = require("pluralize-esm"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal"), ArrowUp = require("@sanity/icons/ArrowUp"), workflowReact = require("@sanity/workflow-react"), Transfer = require("@sanity/icons/Transfer");
3
+ var jsxRuntime = require("react/jsx-runtime"), Timeline = require("@sanity/icons/Timeline"), ui = require("@sanity/ui"), workflowReact = require("@sanity/workflow-react"), react = require("react"), structure = require("sanity/structure"), workflowEngine = require("@sanity/workflow-engine"), Checkmark = require("@sanity/icons/Checkmark"), Close = require("@sanity/icons/Close"), formatDistanceToNow = require("date-fns/formatDistanceToNow"), workflowComponents = require("@sanity/workflow-components"), csm = require("@sanity/client/csm"), telemetry = require("@sanity/telemetry"), workflowStudio = require("@sanity/workflow-studio"), sanity = require("sanity"), Calendar = require("@sanity/icons/Calendar"), User = require("@sanity/icons/User"), Search = require("@sanity/icons/Search"), ChevronRight = require("@sanity/icons/ChevronRight"), Edit = require("@sanity/icons/Edit"), content = require("@sanity/util/content"), InfoOutline = require("@sanity/icons/InfoOutline"), Document = require("@sanity/icons/Document"), WarningOutline = require("@sanity/icons/WarningOutline"), router = require("sanity/router"), types = require("@sanity/types"), Launch = require("@sanity/icons/Launch"), Empty$1 = require("@sanity/icons/Empty"), ErrorFilled = require("@sanity/icons/ErrorFilled"), RemoveCircle = require("@sanity/icons/RemoveCircle"), Add = require("@sanity/icons/Add"), Bolt = require("@sanity/icons/Bolt"), ChevronDown = require("@sanity/icons/ChevronDown"), CheckmarkCircle = require("@sanity/icons/CheckmarkCircle"), Ulist = require("@sanity/icons/Ulist"), pluralize = require("pluralize-esm"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal"), ArrowUp = require("@sanity/icons/ArrowUp"), rxjs = require("rxjs"), operators = require("rxjs/operators"), Transfer = require("@sanity/icons/Transfer");
4
4
 
5
5
  function _interopDefaultCompat(e) {
6
6
  return e && typeof e == "object" && "default" in e ? e : {
@@ -23,20 +23,58 @@ function gdrLocality(id, contentResource) {
23
23
  };
24
24
  }
25
25
 
26
- function mappingsForDocType(mappings, docType) {
27
- return mappings.filter(m => m.docType === docType);
26
+ function assertUniqueWorkflowMappings(mappings) {
27
+ const keys = /* @__PURE__ */ new Set;
28
+ for (const mapping of mappings) {
29
+ const key = mappingKey(mapping);
30
+ if (keys.has(key)) throw new Error(`Duplicate workflow mapping for ${key}`);
31
+ keys.add(key);
32
+ }
33
+ }
34
+
35
+ function discoverWorkflowMappings(args) {
36
+ assertUniqueWorkflowMappings(args.overrides ?? []);
37
+ const discovered = workflowEngine.latestDeployedDefinitions(args.definitions).flatMap(definition => {
38
+ if (!workflowEngine.isStartableDefinition(definition)) return [];
39
+ const subject = (definition.fields ?? []).find(workflowEngine.isSubjectEntry);
40
+ if (subject === void 0 || !workflowEngine.isInputSourced(subject)) return [];
41
+ const source = {
42
+ fields: [ ...definition.fields ?? [] ]
43
+ };
44
+ return [ ...args.schemaContentTypes ].filter(docType => workflowEngine.acceptsDocumentType(source, docType)).map(docType => ({
45
+ docType: docType,
46
+ definition: definition.name,
47
+ label: definition.title ?? definition.name
48
+ }));
49
+ }), byKey = new Map(discovered.map(mapping => [ mappingKey(mapping), mapping ]));
50
+ for (const override of args.overrides ?? []) {
51
+ const key = mappingKey(override);
52
+ byKey.set(key, override);
53
+ }
54
+ return [ ...byKey.values() ];
55
+ }
56
+
57
+ function mappingAutoStartMap(mappings) {
58
+ const byType = /* @__PURE__ */ Object.create(null);
59
+ for (const mapping of mappings) {
60
+ if (!mapping.autoStart) continue;
61
+ const definitions = byType[mapping.docType] ?? [];
62
+ byType[mapping.docType] = [ ...definitions, mapping.definition ];
63
+ }
64
+ return byType;
28
65
  }
29
66
 
30
- function mappingForDocType(mappings, docType) {
31
- return mappingsForDocType(mappings, docType)[0];
67
+ function mappingsForDocType(mappings, docType) {
68
+ return mappings.filter(m => m.docType === docType);
32
69
  }
33
70
 
34
71
  function mappingKey(mapping) {
35
72
  return `${mapping.docType}::${mapping.definition}`;
36
73
  }
37
74
 
38
- function workflowDocTypes(mappings) {
39
- return new Set(mappings.map(m => m.docType));
75
+ function documentSubjectEntry(definition) {
76
+ const fields = definition.fields ?? [];
77
+ return fields.find(workflowEngine.isSubjectEntry) ?? fields.find(entry => entry.type === "doc.ref" && entry.name === "subject");
40
78
  }
41
79
 
42
80
  function mappingStartBlocks(args) {
@@ -357,7 +395,7 @@ function stateByActivity(instance) {
357
395
  }
358
396
 
359
397
  function evaluationFreshness(args) {
360
- return args.evaluation ? args.committed.lastChangedAt > args.evaluation.instance.lastChangedAt ? "stale" : "current" : "pending";
398
+ return args.evaluation ? Date.parse(args.committed.lastChangedAt) > Date.parse(args.evaluation.instance.lastChangedAt) ? "stale" : "current" : "pending";
361
399
  }
362
400
 
363
401
  function isEvaluationCurrent(args) {
@@ -383,50 +421,6 @@ function isLiveEntry(entry) {
383
421
  return workflowEngine.terminalState(entry.instance) === "in-flight";
384
422
  }
385
423
 
386
- const WORKFLOW_API_VERSION = workflowEngine.ENGINE_API_VERSION, WORKFLOW_SYSTEM_TYPES = [ workflowEngine.WORKFLOW_INSTANCE_TYPE, workflowEngine.WORKFLOW_DEFINITION_TYPE, workflowEngine.GUARD_DOC_TYPE ];
387
-
388
- function editTargetOf(field) {
389
- return {
390
- scope: field.scope,
391
- field: field.name,
392
- ...field.activity ? {
393
- activity: field.activity
394
- } : {}
395
- };
396
- }
397
-
398
- const EMPTY_ENTRIES = [], WorkflowContext = react.createContext(null);
399
-
400
- function useWorkflowContext() {
401
- const value = react.useContext(WorkflowContext);
402
- if (!value) throw new Error("useWorkflowContext must render inside the workflow plugin provider");
403
- return value;
404
- }
405
-
406
- function useWorkflowsForDocument(rawId) {
407
- const {register: register, unregister: unregister, entries: entries} = useWorkflowContext(), docId = rawId ? csm.getPublishedId(rawId) : void 0;
408
- react.useEffect(() => {
409
- if (docId) return register(docId), () => unregister(docId);
410
- }, [ docId, register, unregister ]);
411
- const list = react.useSyncExternalStore(entries.subscribe, react.useCallback(() => docId ? entries.getDocument(docId) : EMPTY_ENTRIES, [ entries, docId ]));
412
- return {
413
- docId: docId,
414
- entries: list
415
- };
416
- }
417
-
418
- function useHeldWorkflowEntry(instanceId) {
419
- const {entries: entries} = useWorkflowContext();
420
- return react.useSyncExternalStore(entries.subscribe, react.useCallback(() => instanceId ? entries.getInstance(instanceId) : void 0, [ entries, instanceId ]));
421
- }
422
-
423
- function useWorkflowInstanceEntry(instanceId) {
424
- const {requestInstance: requestInstance, releaseInstance: releaseInstance} = useWorkflowContext();
425
- return react.useEffect(() => {
426
- if (instanceId) return requestInstance(instanceId), () => releaseInstance(instanceId);
427
- }, [ instanceId, requestInstance, releaseInstance ]), useHeldWorkflowEntry(instanceId);
428
- }
429
-
430
424
  const isTodoDone = item => item.status === "done", toggledTodoStatus = item => isTodoDone(item) ? "open" : "done";
431
425
 
432
426
  function isTodoOverdue(item, today = /* @__PURE__ */ new Date) {
@@ -502,7 +496,7 @@ function buildTickIndex(evaluation) {
502
496
  function todoFieldEditability(args) {
503
497
  const {evaluation: evaluation, source: source, fieldName: fieldName} = args, match = evaluation?.editableFields.find(f => f.name === fieldName && f.scope === source.scope && (source.scope !== "activity" || f.activity === source.activityName));
504
498
  return match ? match.editable ? {
505
- editTarget: editTargetOf(match)
499
+ editTarget: workflowReact.editFieldTarget(match)
506
500
  } : match.disabledReason === void 0 ? {} : {
507
501
  editDisabledReason: match.disabledReason
508
502
  } : {};
@@ -567,8 +561,8 @@ function assigneeMatches(member, who) {
567
561
  return member.type === "user" ? member.id === who.userId : who.roles.includes(member.role);
568
562
  }
569
563
 
570
- function sameAssignee(a, b) {
571
- return a.type === "user" && b.type === "user" ? a.id === b.id : a.type === "role" && b.type === "role" ? a.role === b.role : !1;
564
+ function changedAssignee(current, next) {
565
+ return next.find(item => !current.some(existing => workflowComponents.sameAssignee(item, existing))) ?? current.find(item => !next.some(existing => workflowComponents.sameAssignee(item, existing)));
572
566
  }
573
567
 
574
568
  function isAssigneeShape(value) {
@@ -697,13 +691,29 @@ function concludesHere(actionEval, activityName) {
697
691
  return changesOwnStatus(actionEval.action, activityName) || actionEval.firing?.exitsStage === !0;
698
692
  }
699
693
 
700
- function actionButtonTone(args) {
701
- return concludesHere(args.actionEval, args.activityName) ? {
702
- mode: "default",
703
- tone: actionAdvanceStatus(args.actionEval.action, args.activityName) === "failed" ? "critical" : "default"
704
- } : {
705
- mode: "ghost",
706
- tone: "default"
694
+ const SEMANTIC_FACE = {
695
+ "decision.accept": {
696
+ tone: "positive",
697
+ icon: Checkmark.CheckmarkIcon
698
+ },
699
+ "decision.decline": {
700
+ tone: "caution",
701
+ icon: Close.CloseIcon
702
+ }
703
+ };
704
+
705
+ function actionButtonFace(args) {
706
+ const mode = concludesHere(args.actionEval, args.activityName) ? "default" : "ghost";
707
+ if (actionAdvanceStatus(args.actionEval.action, args.activityName) === "failed") return {
708
+ mode: mode,
709
+ tone: "critical",
710
+ icon: void 0
711
+ };
712
+ const [semantic] = args.actionEval.semantics ?? [], face = semantic === void 0 ? void 0 : SEMANTIC_FACE[semantic];
713
+ return {
714
+ mode: mode,
715
+ tone: face?.tone ?? "default",
716
+ icon: face?.icon
707
717
  };
708
718
  }
709
719
 
@@ -927,6 +937,112 @@ function deriveActivityDetail(args) {
927
937
  };
928
938
  }
929
939
 
940
+ const WORKFLOW_API_VERSION = workflowEngine.ENGINE_API_VERSION, WORKFLOW_SYSTEM_TYPES = [ workflowEngine.WORKFLOW_INSTANCE_TYPE, workflowEngine.WORKFLOW_DEFINITION_TYPE, workflowEngine.GUARD_DOC_TYPE ], EMPTY_ENTRIES = [], WorkflowContext = react.createContext(null);
941
+
942
+ function useWorkflowContext() {
943
+ const value = react.useContext(WorkflowContext);
944
+ if (!value) throw new Error("useWorkflowContext must render inside the workflow plugin provider");
945
+ return value;
946
+ }
947
+
948
+ function useWorkflowsForDocument(rawId) {
949
+ return useDocumentWorkflowEntries(rawId, !0);
950
+ }
951
+
952
+ function useCommittedWorkflowsForDocument(rawId) {
953
+ return useDocumentWorkflowEntries(rawId, !1);
954
+ }
955
+
956
+ function useDocumentWorkflowEntries(rawId, evaluate) {
957
+ const {register: register, unregister: unregister, requestEvaluation: requestEvaluation, releaseEvaluation: releaseEvaluation, entries: entries} = useWorkflowContext(), docId = rawId ? csm.getPublishedId(rawId) : void 0;
958
+ react.useEffect(() => {
959
+ if (docId) return register(docId), () => unregister(docId);
960
+ }, [ docId, register, unregister ]);
961
+ const list = react.useSyncExternalStore(entries.subscribe, react.useCallback(() => docId ? entries.getDocument(docId) : EMPTY_ENTRIES, [ entries, docId ])), evaluationKey = evaluate ? list.map(entry => entry.instance._id).join("\0") : "";
962
+ return react.useEffect(() => {
963
+ if (!evaluate) return;
964
+ const ids = evaluationKey === "" ? [] : evaluationKey.split("\0");
965
+ for (const id of ids) requestEvaluation(id);
966
+ return () => {
967
+ for (const id of ids) releaseEvaluation(id);
968
+ };
969
+ }, [ evaluate, evaluationKey, requestEvaluation, releaseEvaluation ]), {
970
+ docId: docId,
971
+ entries: list
972
+ };
973
+ }
974
+
975
+ function useHeldWorkflowEntry(instanceId) {
976
+ const {entries: entries} = useWorkflowContext();
977
+ return react.useSyncExternalStore(entries.subscribe, react.useCallback(() => instanceId ? entries.getInstance(instanceId) : void 0, [ entries, instanceId ]));
978
+ }
979
+
980
+ function useWorkflowInstanceEntry(instanceId, options = {}) {
981
+ const {requestInstance: requestInstance, releaseInstance: releaseInstance, requestEvaluation: requestEvaluation, releaseEvaluation: releaseEvaluation} = useWorkflowContext(), evaluate = options.evaluate ?? !0;
982
+ return react.useEffect(() => {
983
+ if (instanceId) return requestInstance(instanceId), () => releaseInstance(instanceId);
984
+ }, [ instanceId, requestInstance, releaseInstance ]), react.useEffect(() => {
985
+ if (!(!instanceId || !evaluate)) return requestEvaluation(instanceId), () => releaseEvaluation(instanceId);
986
+ }, [ instanceId, evaluate, requestEvaluation, releaseEvaluation ]), useHeldWorkflowEntry(instanceId);
987
+ }
988
+
989
+ const WorkflowToolOpened = telemetry.defineEvent({
990
+ name: "Editorial Workflows Studio Plugin Tool Opened",
991
+ version: 1,
992
+ description: "The Workflows tool was opened or its tab was switched"
993
+ }), WorkflowDocumentViewOpened = telemetry.defineEvent({
994
+ name: "Editorial Workflows Studio Plugin Document View Opened",
995
+ version: 1,
996
+ description: "The Workflows document view was opened or its tab was switched"
997
+ }), WorkflowInstanceDetailViewed = telemetry.defineEvent({
998
+ name: "Editorial Workflows Studio Plugin Instance Detail Viewed",
999
+ version: 1,
1000
+ description: "The Workflows tool's per-instance detail view was opened"
1001
+ }), WorkflowFormStripClicked = telemetry.defineEvent({
1002
+ name: "Editorial Workflows Studio Plugin Form Strip Clicked",
1003
+ version: 1,
1004
+ description: "The form strip's instance line was clicked, opening the document view"
1005
+ }), WorkflowStartDialogOpened = telemetry.defineEvent({
1006
+ name: "Editorial Workflows Studio Plugin Start Dialog Opened",
1007
+ version: 1,
1008
+ description: "The start-workflow dialog was opened"
1009
+ }), WorkflowStartDialogSubmitted = telemetry.defineEvent({
1010
+ name: "Editorial Workflows Studio Plugin Start Dialog Submitted",
1011
+ version: 1,
1012
+ description: "The start-workflow dialog's confirm was pressed"
1013
+ }), WorkflowAutoStartRan = telemetry.defineEvent({
1014
+ name: "Editorial Workflows Studio Plugin Auto Start Ran",
1015
+ version: 1,
1016
+ description: "A fresh document's auto-start ran — one start request per configured workflow"
1017
+ }), WorkflowActionControlUsed = telemetry.defineEvent({
1018
+ name: "Editorial Workflows Studio Plugin Action Control Used",
1019
+ version: 1,
1020
+ description: "An action-firing control was used, attributed to its UI surface"
1021
+ }), WorkflowActivityDialogOpened = telemetry.defineEvent({
1022
+ name: "Editorial Workflows Studio Plugin Activity Dialog Opened",
1023
+ version: 1,
1024
+ description: "The activity detail dialog was opened"
1025
+ }), WorkflowTaskFiltersApplied = telemetry.defineEvent({
1026
+ name: "Editorial Workflows Studio Plugin Task Filters Applied",
1027
+ version: 1,
1028
+ description: "The tool's task-filter menu closed with a changed selection"
1029
+ }), WorkflowTodoToggled = telemetry.defineEvent({
1030
+ name: "Editorial Workflows Studio Plugin Todo Toggled",
1031
+ version: 1,
1032
+ description: "A todo checkbox was toggled, attributed to its UI surface and write seam"
1033
+ });
1034
+
1035
+ function useLogEventOnMount(event, data) {
1036
+ const telemetry2 = workflowReact.useWorkflowTelemetry(), logged = react.useRef(!1);
1037
+ react.useEffect(() => {
1038
+ logged.current || (logged.current = !0, telemetry2.log(event, data));
1039
+ }, [ telemetry2, event, data ]);
1040
+ }
1041
+
1042
+ function LogEventOnMount(props) {
1043
+ return useLogEventOnMount(props.event, props.data), null;
1044
+ }
1045
+
930
1046
  function isEmptyValue(value) {
931
1047
  return value == null || value === "" || Array.isArray(value) && value.length === 0;
932
1048
  }
@@ -958,10 +1074,23 @@ function personActor(user) {
958
1074
  };
959
1075
  }
960
1076
 
1077
+ const memberIndexes = /* @__PURE__ */ new WeakMap;
1078
+
1079
+ function memberIndex(members) {
1080
+ const cached = memberIndexes.get(members);
1081
+ if (cached) return cached;
1082
+ const created = new Map(members.map(member => [ member.id, member ]));
1083
+ return memberIndexes.set(members, created), created;
1084
+ }
1085
+
961
1086
  const selfUserFor = (id, me) => me != null && me.id === id ? me : void 0;
962
1087
 
1088
+ function globalIdOf(users, projectScopedId) {
1089
+ return users.find(user => user.membership.id === projectScopedId)?.profile?.sanityUserId ?? projectScopedId;
1090
+ }
1091
+
963
1092
  function userDisplayFor(args) {
964
- const member = args.members.find(m => m.id === args.id), self = selfUserFor(args.id, args.me);
1093
+ const matchId = args.matchId ?? args.id, member = memberIndex(args.members).get(matchId), self = selfUserFor(matchId, args.me);
965
1094
  return {
966
1095
  id: args.id,
967
1096
  name: member?.displayName ?? self?.name ?? args.id,
@@ -970,42 +1099,69 @@ function userDisplayFor(args) {
970
1099
  }
971
1100
 
972
1101
  function useUserDisplays(ids) {
973
- const {members: members} = useProjectMembers(), me = sanity.useCurrentUser();
974
- return ids.map(id => userDisplayFor({
1102
+ const {members: members} = useProjectMembers(), {users: users} = workflowStudio.useStudioProjectUsers(), meRaw = sanity.useCurrentUser(), me = react.useMemo(() => meRaw === null ? null : {
1103
+ ...meRaw,
1104
+ id: globalIdOf(users, meRaw.id)
1105
+ }, [ meRaw, users ]);
1106
+ return react.useMemo(() => ids.map(id => userDisplayFor({
975
1107
  id: id,
976
1108
  members: members,
977
- me: me
978
- }));
1109
+ me: me,
1110
+ matchId: globalIdOf(users, id)
1111
+ })), [ ids, me, members, users ]);
979
1112
  }
980
1113
 
981
1114
  function useUserDisplay(id) {
982
- const [display] = useUserDisplays([ id ]);
1115
+ const ids = react.useMemo(() => [ id ], [ id ]), [display] = useUserDisplays(ids);
983
1116
  return display ?? {
984
1117
  name: id,
985
1118
  imageUrl: void 0
986
1119
  };
987
1120
  }
988
1121
 
1122
+ const warnedNotMember = /* @__PURE__ */ new Set;
1123
+
1124
+ function useBridgedSelf() {
1125
+ const me = sanity.useCurrentUser(), {users: users, loading: loading} = workflowStudio.useStudioProjectUsers();
1126
+ if (!me || loading) return;
1127
+ const classified = workflowEngine.classifyPrincipalId(me.id), id = users.find(user => user.membership.id === me.id)?.profile?.sanityUserId ?? classified.globalId ?? (classified.namespace === "project" ? void 0 : me.id);
1128
+ if (id === void 0) {
1129
+ warnedNotMember.has(me.id) || (warnedNotMember.add(me.id), console.warn(`workflow: the project member directory cannot resolve the logged-in user ("${me.id}") to an account-global identity — the engine stores account-global user ids only, so workflow assignment and authoring controls stay disabled. Typically this user is not a member of this project.`));
1130
+ return;
1131
+ }
1132
+ return {
1133
+ id: id,
1134
+ roles: me.roles ?? []
1135
+ };
1136
+ }
1137
+
1138
+ function useSelfActor() {
1139
+ const self = useBridgedSelf();
1140
+ return self === void 0 ? void 0 : personActor(self);
1141
+ }
1142
+
989
1143
  function useAssignmentIdentity() {
990
- const me = sanity.useCurrentUser();
991
- if (me) return {
992
- userId: me.id,
993
- roles: (me.roles ?? []).map(r => r.name)
1144
+ const self = useBridgedSelf();
1145
+ if (self !== void 0) return {
1146
+ userId: self.id,
1147
+ roles: self.roles.map(r => r.name)
994
1148
  };
995
1149
  }
996
1150
 
1151
+ const projectMembersByUsers = /* @__PURE__ */ new WeakMap;
1152
+
997
1153
  function projectMembersFrom(users) {
998
- return users.map(({membership: membership, profile: profile}) => ({
999
- id: membership.id,
1000
- displayName: profile?.displayName ?? membership.id,
1001
- ...profile?.email ? {
1002
- email: profile.email
1003
- } : {},
1004
- ...profile?.imageUrl ? {
1005
- imageUrl: profile.imageUrl
1006
- } : {},
1007
- roles: (membership.roles ?? []).map(role => role.name)
1154
+ const cached = projectMembersByUsers.get(users);
1155
+ if (cached) return cached;
1156
+ const members = users.map(({membership: membership, profile: profile}) => workflowComponents.projectMemberRow({
1157
+ membershipId: membership.id,
1158
+ roles: membership.roles,
1159
+ sanityUserId: profile?.sanityUserId,
1160
+ displayName: profile?.displayName,
1161
+ email: profile?.email,
1162
+ imageUrl: profile?.imageUrl ?? void 0
1008
1163
  }));
1164
+ return projectMembersByUsers.set(users, members), members;
1009
1165
  }
1010
1166
 
1011
1167
  function useProjectMembers() {
@@ -1331,12 +1487,12 @@ function MemberPicker({selectedIds: selectedIds, onSelect: onSelect, unassignRow
1331
1487
  });
1332
1488
  }
1333
1489
 
1334
- function AssigneePicker({value: value, onToggle: onToggle}) {
1490
+ function AssigneePicker({value: value, onChange: onChange}) {
1335
1491
  const state = useProjectMembers();
1336
1492
  /* @__PURE__ */
1337
1493
  return jsxRuntime.jsx(workflowComponents.AssigneePicker, {
1338
1494
  ...state,
1339
- onToggle: onToggle,
1495
+ onChange: onChange,
1340
1496
  value: value
1341
1497
  });
1342
1498
  }
@@ -1447,7 +1603,7 @@ function DocPicker({value: value, onChange: onChange, types: types2, readOnly: r
1447
1603
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1448
1604
  flex: 1,
1449
1605
  children: /* @__PURE__ */ jsxRuntime.jsx(PreviewOrId, {
1450
- id: workflowEngine.extractDocumentId(value.id),
1606
+ id: workflowEngine.toBareId(value.id),
1451
1607
  type: value.type
1452
1608
  })
1453
1609
  })
@@ -1543,7 +1699,7 @@ function DocRefsInput({value: value, onChange: onChange, types: types2}) {
1543
1699
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1544
1700
  flex: 1,
1545
1701
  children: /* @__PURE__ */ jsxRuntime.jsx(PreviewOrId, {
1546
- id: workflowEngine.extractDocumentId(ref.id),
1702
+ id: workflowEngine.toBareId(ref.id),
1547
1703
  type: ref.type
1548
1704
  })
1549
1705
  }),
@@ -1666,7 +1822,7 @@ function useActionClusterPending() {
1666
1822
  }
1667
1823
 
1668
1824
  function useFireAction(args) {
1669
- const {instanceId: instanceId, activity: activity, action: action, label: label} = args, {fireActionFor: fireActionFor} = useWorkflowContext(), toast = useClosableToast(), cluster = useActionClusterLock(), [localPending, setLocalPending] = react.useState(!1), pending = localPending || cluster?.pending === !0;
1825
+ const {instanceId: instanceId, activity: activity, action: action, label: label, surface: surface, viaMenu: viaMenu} = args, {fireActionFor: fireActionFor} = useWorkflowContext(), telemetry2 = workflowReact.useWorkflowTelemetry(), toast = useClosableToast(), cluster = useActionClusterLock(), [localPending, setLocalPending] = react.useState(!1), pending = localPending || cluster?.pending === !0;
1670
1826
  return {
1671
1827
  fire: react.useCallback(async params => {
1672
1828
  if (pending) return !1;
@@ -1681,6 +1837,11 @@ function useFireAction(args) {
1681
1837
  }), toast.push({
1682
1838
  status: "success",
1683
1839
  title: `${label} done`
1840
+ }), telemetry2.log(WorkflowActionControlUsed, {
1841
+ instanceId: instanceId,
1842
+ surface: surface,
1843
+ viaMenu: viaMenu,
1844
+ success: !0
1684
1845
  }), !0;
1685
1846
  } catch (err) {
1686
1847
  return console.error(`[workflow-studio-plugin] firing "${action}" on "${activity}" failed:`, err),
@@ -1688,15 +1849,29 @@ function useFireAction(args) {
1688
1849
  status: "error",
1689
1850
  title: `${label} failed`,
1690
1851
  description: describeError(err)
1852
+ }), telemetry2.log(WorkflowActionControlUsed, {
1853
+ instanceId: instanceId,
1854
+ surface: surface,
1855
+ viaMenu: viaMenu,
1856
+ success: !1
1691
1857
  }), !1;
1692
1858
  } finally {
1693
1859
  setLocalPending(!1), cluster?.onFireChange(!1);
1694
1860
  }
1695
- }, [ fireActionFor, instanceId, activity, action, label, pending, toast, cluster ]),
1861
+ }, [ fireActionFor, instanceId, activity, action, label, pending, toast, cluster, telemetry2, surface, viaMenu ]),
1696
1862
  pending: pending
1697
1863
  };
1698
1864
  }
1699
1865
 
1866
+ const ACTION_ICON_GAP = 2;
1867
+
1868
+ function actionIconProps(icon) {
1869
+ return icon ? {
1870
+ gap: ACTION_ICON_GAP,
1871
+ icon: icon
1872
+ } : {};
1873
+ }
1874
+
1700
1875
  function stopRowMouseDown(e) {
1701
1876
  e.preventDefault(), e.stopPropagation();
1702
1877
  }
@@ -1724,18 +1899,21 @@ function RowClickShield({active: active, children: children}) {
1724
1899
  });
1725
1900
  }
1726
1901
 
1727
- function FireButton({instanceId: instanceId, activity: activity, action: action, label: label, mode: mode = "default", chrome: chrome, tone: tone = "default"}) {
1902
+ function FireButton({instanceId: instanceId, activity: activity, action: action, label: label, surface: surface, mode: mode = "default", chrome: chrome, tone: tone = "default", icon: icon}) {
1728
1903
  const {fire: fire, pending: pending} = useFireAction({
1729
1904
  instanceId: instanceId,
1730
1905
  activity: activity,
1731
1906
  action: action,
1732
- label: label
1907
+ label: label,
1908
+ surface: surface,
1909
+ viaMenu: !1
1733
1910
  }), handleClick = e => {
1734
1911
  chrome?.inButtonCard === !0 && e.stopPropagation(), !pending && fire();
1735
1912
  };
1736
1913
  /* @__PURE__ */
1737
1914
  return jsxRuntime.jsx(ui.Button, {
1738
1915
  fontSize: 1,
1916
+ ...actionIconProps(icon),
1739
1917
  loading: pending,
1740
1918
  mode: chrome?.mode ?? mode,
1741
1919
  onClick: handleClick,
@@ -1749,31 +1927,39 @@ function FireButton({instanceId: instanceId, activity: activity, action: action,
1749
1927
  });
1750
1928
  }
1751
1929
 
1752
- function ActivityActionRow({actionEval: actionEval, instanceId: instanceId, activity: activity, chrome: chrome}) {
1930
+ function ActivityActionRow({actionEval: actionEval, instanceId: instanceId, activity: activity, surface: surface, chrome: chrome}) {
1753
1931
  const rowKind = actionRowKind(actionEval);
1754
1932
  return rowKind.kind === "disabled" ? /* @__PURE__ */ jsxRuntime.jsx(DisabledActionButton, {
1755
1933
  actionEval: actionEval,
1934
+ activity: activity,
1756
1935
  chrome: chrome
1757
1936
  }) : rowKind.kind === "plain" ? /* @__PURE__ */ jsxRuntime.jsx(PlainActionButton, {
1758
1937
  actionEval: actionEval,
1759
1938
  instanceId: instanceId,
1760
1939
  activity: activity,
1761
- chrome: chrome
1940
+ chrome: chrome,
1941
+ surface: surface
1762
1942
  }) : /* @__PURE__ */ jsxRuntime.jsx(ParamsActionButton, {
1763
1943
  actionEval: actionEval,
1764
1944
  instanceId: instanceId,
1765
1945
  activity: activity,
1766
- chrome: chrome
1946
+ chrome: chrome,
1947
+ surface: surface
1767
1948
  });
1768
1949
  }
1769
1950
 
1770
- function DisabledActionButton({actionEval: actionEval, chrome: chrome}) {
1771
- const label = actionTriggerLabel(actionEval), button = /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1951
+ function DisabledActionButton({actionEval: actionEval, activity: activity, chrome: chrome}) {
1952
+ const label = actionTriggerLabel(actionEval), {tone: tone, icon: icon} = actionButtonFace({
1953
+ actionEval: actionEval,
1954
+ activityName: activity
1955
+ }), button = /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1772
1956
  fontSize: 1,
1957
+ ...actionIconProps(icon),
1773
1958
  mode: chrome?.mode ?? "ghost",
1774
1959
  onClick: e => e.stopPropagation(),
1775
1960
  padding: 2,
1776
1961
  text: label,
1962
+ tone: tone,
1777
1963
  ...spanTriggerProps(chrome),
1778
1964
  disabled: !0
1779
1965
  });
@@ -1783,17 +1969,19 @@ function DisabledActionButton({actionEval: actionEval, chrome: chrome}) {
1783
1969
  }) : button;
1784
1970
  }
1785
1971
 
1786
- function PlainActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, chrome: chrome}) {
1787
- const {action: action} = actionEval, label = actionLabel(action), btn = actionButtonTone({
1972
+ function PlainActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, surface: surface, chrome: chrome}) {
1973
+ const {action: action} = actionEval, label = actionLabel(action), btn = actionButtonFace({
1788
1974
  actionEval: actionEval,
1789
1975
  activityName: activity
1790
1976
  }), button = /* @__PURE__ */ jsxRuntime.jsx(FireButton, {
1791
1977
  action: action.name,
1978
+ icon: btn.icon,
1792
1979
  instanceId: instanceId,
1793
1980
  label: label,
1794
1981
  mode: btn.mode,
1795
1982
  activity: activity,
1796
1983
  chrome: chrome,
1984
+ surface: surface,
1797
1985
  tone: btn.tone
1798
1986
  });
1799
1987
  return action.description ? /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
@@ -1802,12 +1990,14 @@ function PlainActionButton({actionEval: actionEval, instanceId: instanceId, acti
1802
1990
  }) : button;
1803
1991
  }
1804
1992
 
1805
- function ParamsActionDialog({actionEval: actionEval, instanceId: instanceId, activity: activity, onClose: onClose}) {
1993
+ function ParamsActionDialog({actionEval: actionEval, instanceId: instanceId, activity: activity, surface: surface, viaMenu: viaMenu, onClose: onClose}) {
1806
1994
  const {action: action} = actionEval, label = actionLabel(action), decls = action.params ?? [], [values, setValues] = react.useState({}), {fire: fire, pending: pending} = useFireAction({
1807
1995
  instanceId: instanceId,
1808
1996
  activity: activity,
1809
1997
  action: action.name,
1810
- label: label
1998
+ label: label,
1999
+ surface: surface,
2000
+ viaMenu: viaMenu
1811
2001
  }), built = buildParams(decls, values), confirm = async () => {
1812
2002
  await fire(built.params) && onClose();
1813
2003
  };
@@ -1862,8 +2052,8 @@ function ParamsActionDialog({actionEval: actionEval, instanceId: instanceId, act
1862
2052
  });
1863
2053
  }
1864
2054
 
1865
- function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, chrome: chrome}) {
1866
- const [open, setOpen] = react.useState(!1), btn = actionButtonTone({
2055
+ function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, surface: surface, chrome: chrome}) {
2056
+ const [open, setOpen] = react.useState(!1), btn = actionButtonFace({
1867
2057
  actionEval: actionEval,
1868
2058
  activityName: activity
1869
2059
  });
@@ -1872,6 +2062,7 @@ function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, act
1872
2062
  children: [
1873
2063
  /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1874
2064
  fontSize: 1,
2065
+ ...actionIconProps(btn.icon),
1875
2066
  mode: chrome?.mode ?? btn.mode,
1876
2067
  onClick: e => {
1877
2068
  chrome?.inButtonCard === !0 && e.stopPropagation(), setOpen(!0);
@@ -1886,7 +2077,9 @@ function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, act
1886
2077
  actionEval: actionEval,
1887
2078
  activity: activity,
1888
2079
  instanceId: instanceId,
1889
- onClose: () => setOpen(!1)
2080
+ onClose: () => setOpen(!1),
2081
+ surface: surface,
2082
+ viaMenu: !1
1890
2083
  })
1891
2084
  }) : null ]
1892
2085
  });
@@ -2180,27 +2373,10 @@ function useLocalDocRef(gdr) {
2180
2373
  }
2181
2374
 
2182
2375
  function useActualDocType(bareId) {
2183
- const client = sanity.useClient({
2184
- apiVersion: WORKFLOW_API_VERSION
2185
- }), [probe, setProbe] = react.useState();
2376
+ const {actualTypes: actualTypes} = useWorkflowContext();
2186
2377
  return react.useEffect(() => {
2187
- if (!bareId) return;
2188
- let cancelled = !1;
2189
- return client.fetch("*[_id == $id || _id == $draft || _id in path($version)][0]._type", {
2190
- id: bareId,
2191
- draft: `drafts.${bareId}`,
2192
- version: `versions.*.${bareId}`
2193
- }, {
2194
- perspective: "raw"
2195
- }).then(result => {
2196
- cancelled || setProbe({
2197
- id: bareId,
2198
- value: result
2199
- });
2200
- }).catch(() => {}), () => {
2201
- cancelled = !0;
2202
- };
2203
- }, [ client, bareId ]), probe !== void 0 && probe.id === bareId ? probe.value : void 0;
2378
+ if (bareId !== null) return actualTypes.track(bareId);
2379
+ }, [ actualTypes, bareId ]), react.useSyncExternalStore(actualTypes.subscribe, react.useCallback(() => bareId === null ? void 0 : actualTypes.read(bareId), [ actualTypes, bareId ]));
2204
2380
  }
2205
2381
 
2206
2382
  function useDocLink(gdr) {
@@ -2697,7 +2873,7 @@ function ReadOnlyDocRefs({entry: entry}) {
2697
2873
  padding: 2,
2698
2874
  radius: 2,
2699
2875
  children: /* @__PURE__ */ jsxRuntime.jsx(PreviewOrId, {
2700
- id: workflowEngine.extractDocumentId(ref.id),
2876
+ id: workflowEngine.toBareId(ref.id),
2701
2877
  type: ref.type
2702
2878
  })
2703
2879
  }, ref.id))
@@ -2780,7 +2956,7 @@ function useFieldDraft(args) {
2780
2956
  }
2781
2957
 
2782
2958
  function EditableFieldControl({instanceId: instanceId, field: field}) {
2783
- const {editFieldFor: editFieldFor, previewFieldFor: previewFieldFor, discardFieldPreviewFor: discardFieldPreviewFor} = useWorkflowContext(), me = sanity.useCurrentUser(), held = useHeldWorkflowEntry(instanceId), entry = held ? workflowEngine.resolveFieldEntry(held.instance, field) : void 0, evaluation = held?.evaluation, target = editTargetOf(field), consequence = fieldConsequence(evaluation, field.name), advanceTo = advanceTargetTitle(evaluation, field.name);
2959
+ const {editFieldFor: editFieldFor, previewFieldFor: previewFieldFor, discardFieldPreviewFor: discardFieldPreviewFor} = useWorkflowContext(), selfActor = useSelfActor(), held = useHeldWorkflowEntry(instanceId), entry = held ? workflowEngine.resolveFieldEntry(held.instance, field) : void 0, evaluation = held?.evaluation, target = workflowReact.editFieldTarget(field), consequence = fieldConsequence(evaluation, field.name), advanceTo = advanceTargetTitle(evaluation, field.name);
2784
2960
  /* @__PURE__ */
2785
2961
  return jsxRuntime.jsx(EditableField, {
2786
2962
  field: field,
@@ -2816,7 +2992,7 @@ function EditableFieldControl({instanceId: instanceId, field: field}) {
2816
2992
  mode: "append",
2817
2993
  value: noteRow({
2818
2994
  body: body,
2819
- actor: me ? personActor(me) : void 0,
2995
+ actor: selfActor,
2820
2996
  at: /* @__PURE__ */ (new Date).toISOString()
2821
2997
  })
2822
2998
  }).then(() => {})
@@ -2929,7 +3105,7 @@ function useAssigneePick(args) {
2929
3105
  saving: saving,
2930
3106
  pick: assignee => {
2931
3107
  if (!saving) {
2932
- if (current && sameAssignee(assignee, current)) {
3108
+ if (current && workflowComponents.sameAssignee(assignee, current)) {
2933
3109
  onUnset !== void 0 && save(onUnset);
2934
3110
  return;
2935
3111
  }
@@ -2951,7 +3127,10 @@ function AssigneeField({field: field, onSave: onSave, onUnset: onUnset}) {
2951
3127
  align: "center",
2952
3128
  children: /* @__PURE__ */ jsxRuntime.jsx(DismissablePopover, {
2953
3129
  content: /* @__PURE__ */ jsxRuntime.jsx(AssigneePicker, {
2954
- onToggle: pickAndClose,
3130
+ onChange: next => {
3131
+ const changed = changedAssignee(cur ? [ cur ] : [], next);
3132
+ changed && pickAndClose(changed);
3133
+ },
2955
3134
  value: cur ? [ cur ] : []
2956
3135
  }),
2957
3136
  onDismiss: () => setOpen(!1),
@@ -3476,11 +3655,11 @@ function AssignActivityControl({instanceId: instanceId, state: state, assigneeId
3476
3655
  setBusy(!0);
3477
3656
  try {
3478
3657
  selectedIds.has(memberId) ? await editFieldFor(instanceId, {
3479
- target: editTargetOf(field),
3658
+ target: workflowReact.editFieldTarget(field),
3480
3659
  mode: "set",
3481
3660
  value: members.filter(m => !(m.type === "user" && m.id === memberId))
3482
3661
  }) : await editFieldFor(instanceId, {
3483
- target: editTargetOf(field),
3662
+ target: workflowReact.editFieldTarget(field),
3484
3663
  mode: "append",
3485
3664
  value: {
3486
3665
  type: "user",
@@ -3909,8 +4088,8 @@ function TodoItemRowView({breadcrumb: breadcrumb, row: row, onToggle: onToggle,
3909
4088
  });
3910
4089
  }
3911
4090
 
3912
- function TodoItemsList({breadcrumb: breadcrumb, entry: entry, filter: filter}) {
3913
- const {instance: instance} = entry, definition = useDefinition(entry), {editFieldFor: editFieldFor, fireActionFor: fireActionFor} = useWorkflowContext(), toast = useClosableToast(), [busy, setBusy] = react.useState(!1), derivation = deriveTodoItems(entry), rows = derivation.rows.filter(row => rowMatches(row, filter)), appendTarget = appendTargetFor(derivation.appendTargets, filter), run = async op => {
4091
+ function TodoItemsList({breadcrumb: breadcrumb, entry: entry, filter: filter, surface: surface}) {
4092
+ const {instance: instance} = entry, definition = useDefinition(entry), {editFieldFor: editFieldFor, fireActionFor: fireActionFor} = useWorkflowContext(), telemetry2 = workflowReact.useWorkflowTelemetry(), toast = useClosableToast(), [busy, setBusy] = react.useState(!1), derivation = deriveTodoItems(entry), rows = derivation.rows.filter(row => rowMatches(row, filter)), appendTarget = appendTargetFor(derivation.appendTargets, filter), run = async op => {
3914
4093
  if (busy) return !1;
3915
4094
  setBusy(!0);
3916
4095
  try {
@@ -3935,15 +4114,35 @@ function TodoItemsList({breadcrumb: breadcrumb, entry: entry, filter: filter}) {
3935
4114
  mode: "set",
3936
4115
  value: next
3937
4116
  }));
3938
- }, toggleRow = row => {
3939
- if (row.editTarget) return patchItem(row, {
3940
- status: toggledTodoStatus(row.item)
3941
- });
4117
+ }, toggleRow = async row => {
4118
+ if (busy) return;
4119
+ if (row.editTarget) {
4120
+ const done = !isTodoDone(row.item), success = await patchItem(row, {
4121
+ status: toggledTodoStatus(row.item)
4122
+ }) === !0;
4123
+ telemetry2.log(WorkflowTodoToggled, {
4124
+ instanceId: instance._id,
4125
+ surface: surface,
4126
+ via: "field-edit",
4127
+ done: done,
4128
+ success: success
4129
+ });
4130
+ return;
4131
+ }
3942
4132
  const tick = row.tick;
3943
- return tick && tick.actionEval.allowed && !isTodoDone(row.item) ? run(() => fireActionFor(instance._id, {
3944
- activity: tick.activity,
3945
- action: tick.actionEval.action.name
3946
- })) : Promise.resolve();
4133
+ if (tick && tick.actionEval.allowed && !isTodoDone(row.item)) {
4134
+ const success = await run(() => fireActionFor(instance._id, {
4135
+ activity: tick.activity,
4136
+ action: tick.actionEval.action.name
4137
+ }));
4138
+ telemetry2.log(WorkflowTodoToggled, {
4139
+ instanceId: instance._id,
4140
+ surface: surface,
4141
+ via: "action",
4142
+ done: !0,
4143
+ success: success
4144
+ });
4145
+ }
3947
4146
  }, addItem = label => appendTarget ? run(() => editFieldFor(instance._id, {
3948
4147
  target: appendTarget,
3949
4148
  mode: "append",
@@ -4060,26 +4259,39 @@ const ACTIONS_MENU_TRIGGER = {
4060
4259
  padding: 2
4061
4260
  };
4062
4261
 
4063
- function ActionMenuItem({actionEval: actionEval, instanceId: instanceId, activity: activity, onCollectParams: onCollectParams}) {
4064
- const rowKind = actionRowKind(actionEval), label = actionTriggerLabel(actionEval), {fire: fire, pending: pending} = useFireAction({
4262
+ function ActionMenuItem({actionEval: actionEval, instanceId: instanceId, activity: activity, surface: surface, onCollectParams: onCollectParams}) {
4263
+ const rowKind = actionRowKind(actionEval), label = actionTriggerLabel(actionEval), {tone: tone, icon: icon} = actionButtonFace({
4264
+ actionEval: actionEval,
4265
+ activityName: activity
4266
+ }), iconProp = icon ? {
4267
+ icon: icon
4268
+ } : {}, {fire: fire, pending: pending} = useFireAction({
4065
4269
  instanceId: instanceId,
4066
4270
  activity: activity,
4067
4271
  action: actionEval.action.name,
4068
- label: label
4272
+ label: label,
4273
+ surface: surface,
4274
+ viaMenu: !0
4069
4275
  });
4070
4276
  return rowKind.kind === "disabled" ? /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
4071
4277
  disabled: !0,
4072
- text: label
4278
+ ...iconProp,
4279
+ text: label,
4280
+ tone: tone
4073
4281
  }) : rowKind.kind === "plain" ? /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
4074
4282
  disabled: pending,
4283
+ ...iconProp,
4075
4284
  onClick: () => {
4076
4285
  fire();
4077
4286
  },
4078
- text: label
4287
+ text: label,
4288
+ tone: tone
4079
4289
  }) : /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
4080
4290
  disabled: pending,
4291
+ ...iconProp,
4081
4292
  onClick: onCollectParams,
4082
- text: label
4293
+ text: label,
4294
+ tone: tone
4083
4295
  });
4084
4296
  }
4085
4297
 
@@ -4095,7 +4307,7 @@ function rowShield(menuButton) {
4095
4307
  });
4096
4308
  }
4097
4309
 
4098
- function ActionsMenuButton({actions: actions, instanceId: instanceId, activity: activity, label: label, mode: mode = "ghost", inButtonCard: inButtonCard = !1}) {
4310
+ function ActionsMenuButton({actions: actions, instanceId: instanceId, activity: activity, label: label, surface: surface, mode: mode = "ghost", inButtonCard: inButtonCard = !1}) {
4099
4311
  const [paramsAction, setParamsAction] = react.useState(void 0), clusterPending = useActionClusterPending(), menuId = react.useId(), menuButton = /* @__PURE__ */ jsxRuntime.jsx(ui.MenuButton, {
4100
4312
  button: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4101
4313
  ...ACTIONS_MENU_TRIGGER,
@@ -4114,7 +4326,8 @@ function ActionsMenuButton({actions: actions, instanceId: instanceId, activity:
4114
4326
  actionEval: a,
4115
4327
  activity: activity,
4116
4328
  instanceId: instanceId,
4117
- onCollectParams: () => setParamsAction(a)
4329
+ onCollectParams: () => setParamsAction(a),
4330
+ surface: surface
4118
4331
  }, a.action.name))
4119
4332
  }),
4120
4333
  popover: {
@@ -4130,7 +4343,9 @@ function ActionsMenuButton({actions: actions, instanceId: instanceId, activity:
4130
4343
  actionEval: paramsAction,
4131
4344
  activity: activity,
4132
4345
  instanceId: instanceId,
4133
- onClose: () => setParamsAction(void 0)
4346
+ onClose: () => setParamsAction(void 0),
4347
+ surface: surface,
4348
+ viaMenu: !0
4134
4349
  })
4135
4350
  }) : null ]
4136
4351
  });
@@ -4146,7 +4361,8 @@ function TerminalFooter({actions: actions, activity: activity, instanceId: insta
4146
4361
  children: /* @__PURE__ */ jsxRuntime.jsx(ActivityActionRow, {
4147
4362
  actionEval: only,
4148
4363
  activity: activity,
4149
- instanceId: instanceId
4364
+ instanceId: instanceId,
4365
+ surface: "terminal-footer"
4150
4366
  })
4151
4367
  }) : /* @__PURE__ */ jsxRuntime.jsx(FittedActions, {
4152
4368
  actions: actions,
@@ -4192,6 +4408,7 @@ function FittedActions({actions: actions, activity: activity, instanceId: instan
4192
4408
  children: [
4193
4409
  /* @__PURE__ */ jsxRuntime.jsx(MeasureRow, {
4194
4410
  actions: actions,
4411
+ activity: activity,
4195
4412
  onElement: setMeasureEl
4196
4413
  }),
4197
4414
  /* @__PURE__ */ jsxRuntime.jsxs(FooterRow, {
@@ -4199,19 +4416,21 @@ function FittedActions({actions: actions, activity: activity, instanceId: instan
4199
4416
  children: [ inline === void 0 ? null : actions.slice(0, inline).map(a => /* @__PURE__ */ jsxRuntime.jsx(ActivityActionRow, {
4200
4417
  actionEval: a,
4201
4418
  activity: activity,
4202
- instanceId: instanceId
4419
+ instanceId: instanceId,
4420
+ surface: "terminal-footer"
4203
4421
  }, a.action.name)), overflow.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ActionsMenuButton, {
4204
4422
  actions: overflow,
4205
4423
  activity: activity,
4206
4424
  instanceId: instanceId,
4207
4425
  label: MORE_ACTIONS_LABEL,
4208
- mode: "default"
4426
+ mode: "default",
4427
+ surface: "terminal-footer"
4209
4428
  }) : null ]
4210
4429
  }) ]
4211
4430
  });
4212
4431
  }
4213
4432
 
4214
- function MeasureRow({actions: actions, onElement: onElement}) {
4433
+ function MeasureRow({actions: actions, activity: activity, onElement: onElement}) {
4215
4434
  /* @__PURE__ */
4216
4435
  return jsxRuntime.jsx("div", {
4217
4436
  "aria-hidden": !0,
@@ -4225,12 +4444,20 @@ function MeasureRow({actions: actions, onElement: onElement}) {
4225
4444
  display: "inline-flex",
4226
4445
  whiteSpace: "nowrap"
4227
4446
  },
4228
- children: [ actions.map(a => /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4229
- as: "span",
4230
- fontSize: 1,
4231
- padding: 2,
4232
- text: actionTriggerLabel(a)
4233
- }, a.action.name)),
4447
+ children: [ actions.map(a => {
4448
+ const {icon: icon} = actionButtonFace({
4449
+ actionEval: a,
4450
+ activityName: activity
4451
+ });
4452
+ /* @__PURE__ */
4453
+ return jsxRuntime.jsx(ui.Button, {
4454
+ as: "span",
4455
+ fontSize: 1,
4456
+ ...actionIconProps(icon),
4457
+ padding: 2,
4458
+ text: actionTriggerLabel(a)
4459
+ }, a.action.name);
4460
+ }),
4234
4461
  /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4235
4462
  ...ACTIONS_MENU_TRIGGER,
4236
4463
  as: "span",
@@ -4240,7 +4467,7 @@ function MeasureRow({actions: actions, onElement: onElement}) {
4240
4467
  });
4241
4468
  }
4242
4469
 
4243
- function ActivityDetailDialog({entry: entry, activityName: activityName, breadcrumb: breadcrumb, definition: definition, document: document, onClose: onClose}) {
4470
+ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcrumb: breadcrumb, definition: definition, document: document, source: source, onClose: onClose}) {
4244
4471
  const detail = deriveActivityDetail({
4245
4472
  entry: entry,
4246
4473
  activityName: activityName
@@ -4262,6 +4489,13 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
4262
4489
  onClose: onClose,
4263
4490
  width: 1,
4264
4491
  children: [
4492
+ /* @__PURE__ */ jsxRuntime.jsx(LogEventOnMount, {
4493
+ data: {
4494
+ instanceId: instanceId,
4495
+ source: source
4496
+ },
4497
+ event: WorkflowActivityDialogOpened
4498
+ }),
4265
4499
  /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
4266
4500
  borderBottom: hasBody,
4267
4501
  style: {
@@ -4369,7 +4603,8 @@ function TopActions({detail: detail, definition: definition, instanceId: instanc
4369
4603
  children: [ p.actions.map(a => /* @__PURE__ */ jsxRuntime.jsx(ActivityActionRow, {
4370
4604
  actionEval: a,
4371
4605
  activity: activityName,
4372
- instanceId: instanceId
4606
+ instanceId: instanceId,
4607
+ surface: "dialog-strip"
4373
4608
  }, a.action.name)), p.manualTarget ? /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4374
4609
  as: "a",
4375
4610
  fontSize: 1,
@@ -4405,7 +4640,8 @@ function FieldRow({field: field, detail: detail, entry: entry, activityName: act
4405
4640
  scope: "activity",
4406
4641
  field: field.name,
4407
4642
  activity: activityName
4408
- }
4643
+ },
4644
+ surface: "activity-dialog"
4409
4645
  }) ]
4410
4646
  });
4411
4647
  if (field._type === "assignees") {
@@ -4512,7 +4748,26 @@ function CountedLabel({count: count, label: label}) {
4512
4748
  });
4513
4749
  }
4514
4750
 
4515
- function GroupHeading({count: count, title: title, end: end}) {
4751
+ function SpinnerSlot({busy: busy, size: size}) {
4752
+ /* @__PURE__ */
4753
+ return jsxRuntime.jsx(ui.Flex, {
4754
+ align: "center",
4755
+ flex: "none",
4756
+ justify: "center",
4757
+ style: {
4758
+ width: 19,
4759
+ height: 19
4760
+ },
4761
+ children: busy ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
4762
+ muted: !0,
4763
+ ...size !== void 0 ? {
4764
+ size: size
4765
+ } : {}
4766
+ }) : null
4767
+ });
4768
+ }
4769
+
4770
+ function GroupHeading({busy: busy, count: count, title: title, end: end}) {
4516
4771
  /* @__PURE__ */
4517
4772
  return jsxRuntime.jsxs(ui.Flex, {
4518
4773
  align: "center",
@@ -4527,6 +4782,9 @@ function GroupHeading({count: count, title: title, end: end}) {
4527
4782
  weight: "semibold",
4528
4783
  children: title
4529
4784
  })
4785
+ }), busy === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(SpinnerSlot, {
4786
+ busy: busy,
4787
+ size: 1
4530
4788
  }), end === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
4531
4789
  children: [
4532
4790
  /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
@@ -4536,29 +4794,7 @@ function GroupHeading({count: count, title: title, end: end}) {
4536
4794
  });
4537
4795
  }
4538
4796
 
4539
- function CopyDetailsButton({unreadable: unreadable}) {
4540
- const toast = useClosableToast();
4541
- /* @__PURE__ */
4542
- return jsxRuntime.jsx(ui.Button, {
4543
- fontSize: 1,
4544
- mode: "ghost",
4545
- onClick: () => {
4546
- navigator.clipboard.writeText(unreadableDocsReport(unreadable)).then(() => toast.push({
4547
- status: "success",
4548
- title: "Details copied"
4549
- }), () => toast.push({
4550
- status: "error",
4551
- title: "Could not copy the details"
4552
- }));
4553
- },
4554
- padding: 2,
4555
- text: "Copy details"
4556
- });
4557
- }
4558
-
4559
- function UnreadableDocsNote({unreadable: unreadable}) {
4560
- if (unreadable.length === 0) return null;
4561
- const label = unreadable.length === 1 ? "1 workflow document isn’t listed — this Studio can’t read it. Share the details with your Studio maintainers." : `${unreadable.length} workflow documents aren’t listed — this Studio can’t read them. Share the details with your Studio maintainers.`;
4797
+ function CautionNote({action: action, label: label}) {
4562
4798
  /* @__PURE__ */
4563
4799
  return jsxRuntime.jsxs(ui.Card, {
4564
4800
  padding: 2,
@@ -4585,10 +4821,39 @@ function UnreadableDocsNote({unreadable: unreadable}) {
4585
4821
  size: 1,
4586
4822
  children: label
4587
4823
  }) ]
4588
- }),
4589
- /* @__PURE__ */ jsxRuntime.jsx(CopyDetailsButton, {
4824
+ }), action ]
4825
+ });
4826
+ }
4827
+
4828
+ function CopyDetailsButton({unreadable: unreadable}) {
4829
+ const toast = useClosableToast();
4830
+ /* @__PURE__ */
4831
+ return jsxRuntime.jsx(ui.Button, {
4832
+ fontSize: 1,
4833
+ mode: "ghost",
4834
+ onClick: () => {
4835
+ navigator.clipboard.writeText(unreadableDocsReport(unreadable)).then(() => toast.push({
4836
+ status: "success",
4837
+ title: "Details copied"
4838
+ }), () => toast.push({
4839
+ status: "error",
4840
+ title: "Could not copy the details"
4841
+ }));
4842
+ },
4843
+ padding: 2,
4844
+ text: "Copy details"
4845
+ });
4846
+ }
4847
+
4848
+ function UnreadableDocsNote({unreadable: unreadable}) {
4849
+ if (unreadable.length === 0) return null;
4850
+ const label = unreadable.length === 1 ? "1 workflow document isn’t listed — this Studio can’t read it. Share the details with your Studio maintainers." : `${unreadable.length} workflow documents aren’t listed — this Studio can’t read them. Share the details with your Studio maintainers.`;
4851
+ /* @__PURE__ */
4852
+ return jsxRuntime.jsx(CautionNote, {
4853
+ action: /* @__PURE__ */ jsxRuntime.jsx(CopyDetailsButton, {
4590
4854
  unreadable: unreadable
4591
- }) ]
4855
+ }),
4856
+ label: label
4592
4857
  });
4593
4858
  }
4594
4859
 
@@ -4649,6 +4914,18 @@ function TabSwitch({ariaControls: ariaControls, idPrefix: idPrefix, options: opt
4649
4914
  });
4650
4915
  }
4651
4916
 
4917
+ function useDelayedFlag(active, delayMs) {
4918
+ const [held, setHeld] = react.useState(!1);
4919
+ return react.useEffect(() => {
4920
+ if (!active) {
4921
+ setHeld(!1);
4922
+ return;
4923
+ }
4924
+ const timer = setTimeout(() => setHeld(!0), delayMs);
4925
+ return () => clearTimeout(timer);
4926
+ }, [ active, delayMs ]), held && active;
4927
+ }
4928
+
4652
4929
  function instanceTitle(instance, definition) {
4653
4930
  return definition?.title ?? instance.definition;
4654
4931
  }
@@ -4898,10 +5175,29 @@ function buildStartRequest(args) {
4898
5175
  };
4899
5176
  }
4900
5177
 
4901
- function findResumeEntry(args) {
4902
- return args.entries.find(e => workflowEngine.isUnprimed(e.instance) && e.instance.definition === args.definitionName && (args.heldInstanceId === void 0 || e.instance._id === args.heldInstanceId));
4903
- }
4904
-
5178
+ function sharedPreflightDataset(engine, tag) {
5179
+ let pending;
5180
+ return () => {
5181
+ if (pending !== void 0) return pending;
5182
+ const {query: query, params: params} = workflowEngine.instancesQuery({
5183
+ tag: tag,
5184
+ filter: {
5185
+ includeCompleted: !0
5186
+ }
5187
+ });
5188
+ return pending = engine.query({
5189
+ groq: query,
5190
+ params: params
5191
+ }).then(rows => rows.map(workflowEngine.assertReadableModel).map(workflowEngine.projectStartSliceRow)).finally(() => {
5192
+ pending = void 0;
5193
+ }), pending;
5194
+ };
5195
+ }
5196
+
5197
+ function findResumeEntry(args) {
5198
+ return args.entries.find(e => workflowEngine.isUnprimed(e.instance) && e.instance.definition === args.definitionName && (args.heldInstanceId === void 0 || e.instance._id === args.heldInstanceId));
5199
+ }
5200
+
4905
5201
  function presentedResumeEntry(args) {
4906
5202
  if (args.attempt !== void 0) return args.attempt.resume;
4907
5203
  if (!args.startNew) return findResumeEntry(args);
@@ -4913,7 +5209,8 @@ function classifyStartError(err) {
4913
5209
  instance: err.instance,
4914
5210
  description: describeError(err.cause)
4915
5211
  } : err instanceof workflowEngine.StartNotAllowedError ? {
4916
- kind: "not-allowed"
5212
+ kind: "not-allowed",
5213
+ description: err.unmetRequirements.map(requirementCopy).join("; ")
4917
5214
  } : {
4918
5215
  kind: "failed",
4919
5216
  description: describeError(err)
@@ -4926,22 +5223,30 @@ function boundReleaseId(mapping, selectedReleaseId) {
4926
5223
 
4927
5224
  function startGate(args) {
4928
5225
  const {mapping: mapping, mappingIssue: mappingIssue, selectedReleaseId: selectedReleaseId, releaseActive: releaseActive} = args;
4929
- return mappingIssue !== void 0 ? {
5226
+ if (mappingIssue !== void 0) return {
4930
5227
  blocked: !0,
4931
5228
  tooltip: mappingIssue
4932
- } : mapping.perspectiveField?.required === !0 && selectedReleaseId === void 0 ? {
5229
+ };
5230
+ if (mapping.perspectiveField?.required === !0 && selectedReleaseId === void 0) return {
4933
5231
  blocked: !0,
4934
5232
  tooltip: "Pick a release in the perspective dropdown first"
4935
- } : mapping.perspectiveField !== void 0 && selectedReleaseId !== void 0 && !releaseActive ? {
5233
+ };
5234
+ if (mapping.perspectiveField !== void 0 && selectedReleaseId !== void 0 && !releaseActive) return {
4936
5235
  blocked: !0,
4937
5236
  tooltip: `Release "${selectedReleaseId}" is not active — only active releases can host new workflows`
4938
- } : args.startFilterFailed === !0 ? {
5237
+ };
5238
+ if (args.startFilterFailed === !0) return {
4939
5239
  blocked: !0,
4940
5240
  tooltip: `${mapping.label} can't start for this document right now — the workflow's start condition isn't met`
4941
- } : args.startAllowedRefused === !0 ? {
4942
- blocked: !0,
4943
- tooltip: `${mapping.label} can't be started for this document right now — the workflow doesn't allow another run with these inputs`
4944
- } : {
5241
+ };
5242
+ if (args.unmetRequirement !== void 0) {
5243
+ const requirement = args.unmetRequirement;
5244
+ return {
5245
+ blocked: !0,
5246
+ tooltip: requirementCopy(requirement)
5247
+ };
5248
+ }
5249
+ return {
4945
5250
  blocked: !1,
4946
5251
  tooltip: void 0
4947
5252
  };
@@ -4964,10 +5269,10 @@ async function preflightFilter(args) {
4964
5269
  });
4965
5270
  }
4966
5271
 
4967
- async function preflightAllowed(args) {
4968
- const {allowed: allowed, mapping: mapping, fields: fields, scope: scope} = args;
4969
- return workflowEngine.unboundAllowedReads(allowed, fields).length > 0 ? !1 : (await workflowEngine.explainStartAllowed({
4970
- allowed: allowed,
5272
+ async function preflightGroqRequirement(args) {
5273
+ const {query: query, mapping: mapping, fields: fields, scope: scope} = args;
5274
+ return workflowEngine.unboundRequirementReads(query, fields).length > 0 ? !1 : (await workflowEngine.explainStartRequirement({
5275
+ query: query,
4971
5276
  definition: {
4972
5277
  name: mapping.definition
4973
5278
  },
@@ -4976,6 +5281,25 @@ async function preflightAllowed(args) {
4976
5281
  })).outcome === "unsatisfied";
4977
5282
  }
4978
5283
 
5284
+ async function preflightSingleSubjectRequirement(args) {
5285
+ const {mapping: mapping, start: start, declaredFields: declaredFields, fields: fields, scope: scope} = args;
5286
+ return workflowEngine.singleSubjectRequirementRefused({
5287
+ definition: {
5288
+ name: mapping.definition,
5289
+ ...declaredFields !== void 0 ? {
5290
+ fields: [ ...declaredFields ]
5291
+ } : {},
5292
+ start: start
5293
+ },
5294
+ fields: fields,
5295
+ scope: scope
5296
+ });
5297
+ }
5298
+
5299
+ function requirementCopy(requirement) {
5300
+ return requirement.description ?? requirement.title ?? workflowEngine.humanize(requirement.name);
5301
+ }
5302
+
4979
5303
  async function performStart(args) {
4980
5304
  const {mapping: mapping, docId: docId, initialValue: initialValue, selectedReleaseId: selectedReleaseId} = args, releaseId = boundReleaseId(mapping, selectedReleaseId);
4981
5305
  try {
@@ -5002,7 +5326,21 @@ async function performStart(args) {
5002
5326
  }
5003
5327
 
5004
5328
  function useStartVerdicts(args) {
5005
- const {mapping: mapping, start: start, docId: docId, initialValue: initialValue, selectedReleaseId: selectedReleaseId} = args, skip = args.hidden || args.resumable;
5329
+ const {mapping: mapping, start: start, docId: docId, initialValue: initialValue, selectedReleaseId: selectedReleaseId, entries: entries} = args, {engine: engine, binding: binding} = useWorkflowContext(), fetchDataset = react.useMemo(() => sharedPreflightDataset(engine, binding.tag), [ engine, binding.tag ]), skip = args.hidden || args.resumable, groqRequirements = react.useMemo(() => start?.requirements?.filter(requirement => requirement.type === "groq") ?? [], [ start?.requirements ]), refusedGroq = useGroqRequirementVerdicts({
5330
+ mapping: mapping,
5331
+ requirements: groqRequirements,
5332
+ docId: docId,
5333
+ selectedReleaseId: selectedReleaseId,
5334
+ skip: skip,
5335
+ fetchDataset: fetchDataset
5336
+ }), singleSubjectRefused = useSingleSubjectVerdict({
5337
+ mapping: mapping,
5338
+ start: start,
5339
+ docId: docId,
5340
+ selectedReleaseId: selectedReleaseId,
5341
+ entries: entries,
5342
+ skip: skip
5343
+ }), unmetRequirement = start?.requirements?.find(requirement => requirement.type === "singleSubject" ? singleSubjectRefused : refusedGroq.includes(requirement.name));
5006
5344
  return {
5007
5345
  filterFailed: useFilterVerdict({
5008
5346
  mapping: mapping,
@@ -5010,25 +5348,15 @@ function useStartVerdicts(args) {
5010
5348
  docId: docId,
5011
5349
  initialValue: initialValue,
5012
5350
  selectedReleaseId: selectedReleaseId,
5013
- skip: skip
5351
+ skip: skip,
5352
+ fetchDataset: fetchDataset
5014
5353
  }),
5015
- allowedRefused: useAllowedVerdict({
5016
- mapping: mapping,
5017
- allowed: start?.allowed,
5018
- docId: docId,
5019
- selectedReleaseId: selectedReleaseId,
5020
- skip: skip
5021
- })
5354
+ unmetRequirement: unmetRequirement
5022
5355
  };
5023
5356
  }
5024
5357
 
5025
5358
  function preflightScope(args) {
5026
- const {engine: engine, tag: tag, mapping: mapping, contentResource: contentResource, docId: docId} = args, {query: query, params: params} = workflowEngine.instancesQuery({
5027
- tag: tag,
5028
- filter: {
5029
- includeCompleted: !0
5030
- }
5031
- });
5359
+ const {tag: tag, mapping: mapping, contentResource: contentResource, docId: docId, fetchDataset: fetchDataset} = args;
5032
5360
  return {
5033
5361
  tag: tag,
5034
5362
  now: /* @__PURE__ */ (new Date).toISOString(),
@@ -5037,15 +5365,12 @@ function preflightScope(args) {
5037
5365
  contentResource: contentResource,
5038
5366
  documentId: docId
5039
5367
  }).id,
5040
- fetchDataset: async () => (await engine.query({
5041
- groq: query,
5042
- params: params
5043
- })).map(workflowEngine.assertReadableModel)
5368
+ fetchDataset: fetchDataset
5044
5369
  };
5045
5370
  }
5046
5371
 
5047
5372
  function useFilterVerdict(args) {
5048
- const {mapping: mapping, filter: filter, docId: docId, initialValue: initialValue, selectedReleaseId: selectedReleaseId, skip: skip} = args, {engine: engine, binding: binding} = useWorkflowContext(), [failed, setFailed] = react.useState(!1), rootRead = filter !== void 0 && workflowEngine.readsRootDocument(filter), candidateValue = rootRead ? initialValue : null;
5373
+ const {mapping: mapping, filter: filter, docId: docId, initialValue: initialValue, selectedReleaseId: selectedReleaseId, skip: skip, fetchDataset: fetchDataset} = args, {binding: binding} = useWorkflowContext(), [failed, setFailed] = react.useState(!1), rootRead = filter !== void 0 && workflowEngine.readsRootDocument(filter), candidateValue = rootRead ? initialValue : null;
5049
5374
  return react.useEffect(() => {
5050
5375
  if (skip || filter === void 0 || docId === void 0) {
5051
5376
  setFailed(!1);
@@ -5059,57 +5384,112 @@ function useFilterVerdict(args) {
5059
5384
  candidateValue: candidateValue,
5060
5385
  selectedReleaseId: selectedReleaseId,
5061
5386
  scope: preflightScope({
5062
- engine: engine,
5063
5387
  tag: binding.tag,
5064
5388
  mapping: mapping,
5065
5389
  contentResource: binding.contentResource,
5066
- docId: docId
5390
+ docId: docId,
5391
+ fetchDataset: fetchDataset
5067
5392
  })
5068
5393
  }).catch(showRowOnError("start.filter", mapping)).then(verdict => {
5069
5394
  cancelled || setFailed(verdict);
5070
5395
  }), () => {
5071
5396
  cancelled = !0;
5072
5397
  };
5073
- }, [ filter, rootRead, skip, docId, selectedReleaseId, candidateValue, mapping, engine, binding ]),
5398
+ }, [ filter, rootRead, skip, docId, selectedReleaseId, candidateValue, mapping, binding, fetchDataset ]),
5074
5399
  failed;
5075
5400
  }
5076
5401
 
5077
- function useAllowedVerdict(args) {
5078
- const {mapping: mapping, allowed: allowed, docId: docId, selectedReleaseId: selectedReleaseId, skip: skip} = args, {engine: engine, binding: binding, mappingFields: mappingFields} = useWorkflowContext(), [refused, setRefused] = react.useState(!1);
5402
+ function preflightSeedFields(args) {
5403
+ const ctx = {
5404
+ mapping: args.mapping,
5405
+ subjectDocId: args.docId,
5406
+ selectedReleaseId: args.selectedReleaseId,
5407
+ contentResource: args.contentResource,
5408
+ declaredFields: args.declaredFields
5409
+ };
5410
+ return Object.fromEntries(buildStartFields({
5411
+ ctx: ctx,
5412
+ initFields: [],
5413
+ values: {}
5414
+ }).map(field => [ field.name, field.value ]));
5415
+ }
5416
+
5417
+ function useGroqRequirementVerdicts(args) {
5418
+ const {mapping: mapping, requirements: requirements, docId: docId, selectedReleaseId: selectedReleaseId, skip: skip, fetchDataset: fetchDataset} = args, {binding: binding, mappingFields: mappingFields} = useWorkflowContext(), [refused, setRefused] = react.useState([]);
5079
5419
  return react.useEffect(() => {
5080
- if (skip || allowed === void 0 || docId === void 0) {
5081
- setRefused(!1);
5420
+ if (skip || requirements.length === 0 || docId === void 0) {
5421
+ setRefused(current => current.length === 0 ? current : []);
5082
5422
  return;
5083
5423
  }
5084
5424
  let cancelled = !1;
5085
- const ctx = {
5425
+ const fields = preflightSeedFields({
5086
5426
  mapping: mapping,
5087
- subjectDocId: docId,
5427
+ docId: docId,
5088
5428
  selectedReleaseId: selectedReleaseId,
5089
5429
  contentResource: binding.contentResource,
5090
5430
  declaredFields: mappingFields.get(mappingKey(mapping))
5091
- }, fields = Object.fromEntries(buildStartFields({
5092
- ctx: ctx,
5093
- initFields: [],
5094
- values: {}
5095
- }).map(field => [ field.name, field.value ]));
5096
- return preflightAllowed({
5097
- allowed: allowed,
5431
+ }), scope = preflightScope({
5432
+ tag: binding.tag,
5098
5433
  mapping: mapping,
5099
- fields: fields,
5434
+ contentResource: binding.contentResource,
5435
+ docId: docId,
5436
+ fetchDataset: fetchDataset
5437
+ });
5438
+ return Promise.all(requirements.map(async requirement => ({
5439
+ name: requirement.name,
5440
+ refused: await preflightGroqRequirement({
5441
+ query: requirement.query,
5442
+ mapping: mapping,
5443
+ fields: fields,
5444
+ scope: scope
5445
+ }).catch(showRowOnError(`start.requirements.${requirement.name}`, mapping))
5446
+ }))).then(verdicts => {
5447
+ if (!cancelled) {
5448
+ const next = verdicts.filter(verdict => verdict.refused).map(verdict => verdict.name);
5449
+ setRefused(current => current.length === next.length && current.every((name, index) => name === next[index]) ? current : next);
5450
+ }
5451
+ }), () => {
5452
+ cancelled = !0;
5453
+ };
5454
+ }, [ requirements, skip, docId, selectedReleaseId, mapping, binding, mappingFields, fetchDataset ]),
5455
+ refused;
5456
+ }
5457
+
5458
+ function useSingleSubjectVerdict(args) {
5459
+ const {mapping: mapping, start: start, docId: docId, selectedReleaseId: selectedReleaseId, entries: entries, skip: skip} = args, {binding: binding, mappingFields: mappingFields} = useWorkflowContext(), [refused, setRefused] = react.useState(!1), dedupe = start !== void 0 && workflowEngine.hasSingleSubjectRequirement({
5460
+ start: start
5461
+ });
5462
+ return react.useEffect(() => {
5463
+ if (skip || !dedupe || start === void 0 || docId === void 0) {
5464
+ setRefused(!1);
5465
+ return;
5466
+ }
5467
+ let cancelled = !1;
5468
+ const declaredFields = mappingFields.get(mappingKey(mapping));
5469
+ return preflightSingleSubjectRequirement({
5470
+ mapping: mapping,
5471
+ start: start,
5472
+ declaredFields: declaredFields,
5473
+ fields: preflightSeedFields({
5474
+ mapping: mapping,
5475
+ docId: docId,
5476
+ selectedReleaseId: selectedReleaseId,
5477
+ contentResource: binding.contentResource,
5478
+ declaredFields: declaredFields
5479
+ }),
5100
5480
  scope: preflightScope({
5101
- engine: engine,
5102
5481
  tag: binding.tag,
5103
5482
  mapping: mapping,
5483
+ docId: docId,
5104
5484
  contentResource: binding.contentResource,
5105
- docId: docId
5485
+ fetchDataset: () => Promise.resolve(entries.map(({instance: instance}) => workflowEngine.projectStartSliceRow(instance)))
5106
5486
  })
5107
- }).catch(showRowOnError("start.allowed", mapping)).then(verdict => {
5487
+ }).catch(showRowOnError("singleSubject requirement", mapping)).then(verdict => {
5108
5488
  cancelled || setRefused(verdict);
5109
5489
  }), () => {
5110
5490
  cancelled = !0;
5111
5491
  };
5112
- }, [ allowed, skip, docId, selectedReleaseId, mapping, engine, binding, mappingFields ]),
5492
+ }, [ dedupe, start, skip, docId, selectedReleaseId, entries, mapping, binding, mappingFields ]),
5113
5493
  refused;
5114
5494
  }
5115
5495
 
@@ -5119,7 +5499,7 @@ function showRowOnError(key, mapping) {
5119
5499
  }
5120
5500
 
5121
5501
  function useStartWorkflow(args) {
5122
- const {mapping: mapping, docId: docId, initialValue: initialValue} = args, toast = useClosableToast(), {engine: engine, openStartDialog: openStartDialog, mappingIssues: mappingIssues2, mappingStarts: mappingStarts} = useWorkflowContext(), observer = workflowStudio.useStudioObserver({
5502
+ const {mapping: mapping, docId: docId, initialValue: initialValue, source: source} = args, toast = useClosableToast(), {engine: engine, openStartDialog: openStartDialog, mappingIssues: mappingIssues2, mappingStarts: mappingStarts} = useWorkflowContext(), observer = workflowStudio.useStudioObserver({
5123
5503
  engine: engine
5124
5504
  }), [starting, setStarting] = react.useState(!1), {selectedReleaseId: selectedReleaseId} = sanity.usePerspective(), releaseActive = sanity.useIsReleaseActive(), startBlock = mappingStarts.get(mappingKey(mapping)), hidden = workflowEngine.startKindOf({
5125
5505
  start: startBlock
@@ -5127,12 +5507,13 @@ function useStartWorkflow(args) {
5127
5507
  entries: entries,
5128
5508
  definitionName: mapping.definition,
5129
5509
  heldInstanceId: void 0
5130
- }) !== void 0, {filterFailed: filterFailed, allowedRefused: allowedRefused} = useStartVerdicts({
5510
+ }) !== void 0, {filterFailed: filterFailed, unmetRequirement: unmetRequirement} = useStartVerdicts({
5131
5511
  mapping: mapping,
5132
5512
  start: startBlock,
5133
5513
  docId: docId,
5134
5514
  initialValue: initialValue,
5135
5515
  selectedReleaseId: selectedReleaseId,
5516
+ entries: entries,
5136
5517
  hidden: hidden,
5137
5518
  resumable: resumable
5138
5519
  }), {blocked: blocked, tooltip: tooltip} = startGate({
@@ -5141,7 +5522,9 @@ function useStartWorkflow(args) {
5141
5522
  selectedReleaseId: selectedReleaseId,
5142
5523
  releaseActive: releaseActive,
5143
5524
  startFilterFailed: filterFailed,
5144
- startAllowedRefused: allowedRefused
5525
+ ...unmetRequirement !== void 0 ? {
5526
+ unmetRequirement: unmetRequirement
5527
+ } : {}
5145
5528
  });
5146
5529
  return {
5147
5530
  blocked: blocked,
@@ -5163,7 +5546,10 @@ function useStartWorkflow(args) {
5163
5546
  title: "Could not persist the document before starting",
5164
5547
  description: describeError(err)
5165
5548
  }),
5166
- openStartDialog: openStartDialog
5549
+ openStartDialog: request => openStartDialog({
5550
+ ...request,
5551
+ source: source
5552
+ })
5167
5553
  });
5168
5554
  } finally {
5169
5555
  setStarting(!1);
@@ -5173,11 +5559,12 @@ function useStartWorkflow(args) {
5173
5559
  };
5174
5560
  }
5175
5561
 
5176
- function StartWorkflowButton({mapping: mapping, docId: docId, initialValue: initialValue, mode: mode = "default", label: label, iconOnly: iconOnly = !1, tone: tone = "primary"}) {
5562
+ function StartWorkflowButton({mapping: mapping, docId: docId, initialValue: initialValue, source: source, mode: mode = "default", label: label, iconOnly: iconOnly = !1, tone: tone = "primary"}) {
5177
5563
  const {blocked: blocked, tooltip: tooltip, hidden: hidden, starting: starting, start: start} = useStartWorkflow({
5178
5564
  mapping: mapping,
5179
5565
  docId: docId,
5180
- initialValue: initialValue
5566
+ initialValue: initialValue,
5567
+ source: source
5181
5568
  });
5182
5569
  if (hidden) return null;
5183
5570
  const startLabel = label ?? `Start ${mapping.label}`, button = /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
@@ -5210,7 +5597,7 @@ function useStartableMappings(mappings) {
5210
5597
  }) !== "autonomous");
5211
5598
  }
5212
5599
 
5213
- function StartWorkflowControl({mappings: mappings, docId: docId, initialValue: initialValue, label: label, iconOnly: iconOnly = !1, mode: mode = "default", tone: tone = "primary"}) {
5600
+ function StartWorkflowControl({mappings: mappings, docId: docId, initialValue: initialValue, source: source, label: label, iconOnly: iconOnly = !1, mode: mode = "default", tone: tone = "primary"}) {
5214
5601
  const menuId = react.useId(), startable = useStartableMappings(mappings), [first] = startable;
5215
5602
  if (first === void 0) return null;
5216
5603
  if (startable.length === 1) /* @__PURE__ */
@@ -5223,6 +5610,7 @@ function StartWorkflowControl({mappings: mappings, docId: docId, initialValue: i
5223
5610
  },
5224
5611
  mapping: first,
5225
5612
  mode: mode,
5613
+ source: source,
5226
5614
  tone: tone
5227
5615
  });
5228
5616
  const menuLabel = label ?? "Start a workflow";
@@ -5248,7 +5636,8 @@ function StartWorkflowControl({mappings: mappings, docId: docId, initialValue: i
5248
5636
  children: startable.map(mapping => /* @__PURE__ */ jsxRuntime.jsx(StartWorkflowMenuItem, {
5249
5637
  docId: docId,
5250
5638
  initialValue: initialValue,
5251
- mapping: mapping
5639
+ mapping: mapping,
5640
+ source: source
5252
5641
  }, mappingKey(mapping)))
5253
5642
  }),
5254
5643
  popover: {
@@ -5259,11 +5648,12 @@ function StartWorkflowControl({mappings: mappings, docId: docId, initialValue: i
5259
5648
  });
5260
5649
  }
5261
5650
 
5262
- function StartWorkflowMenuItem({mapping: mapping, docId: docId, initialValue: initialValue}) {
5651
+ function StartWorkflowMenuItem({mapping: mapping, docId: docId, initialValue: initialValue, source: source}) {
5263
5652
  const {blocked: blocked, tooltip: tooltip, starting: starting, start: start} = useStartWorkflow({
5264
5653
  mapping: mapping,
5265
5654
  docId: docId,
5266
- initialValue: initialValue
5655
+ initialValue: initialValue,
5656
+ source: source
5267
5657
  }), item = /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
5268
5658
  disabled: blocked || starting || !docId,
5269
5659
  onClick: () => {
@@ -5318,13 +5708,15 @@ function RowTerminalActions({actions: actions, activity: activity, instanceId: i
5318
5708
  mode: "ghost",
5319
5709
  inButtonCard: !0
5320
5710
  },
5321
- instanceId: instanceId
5711
+ instanceId: instanceId,
5712
+ surface: "inline-row"
5322
5713
  }) : /* @__PURE__ */ jsxRuntime.jsx(ActionsMenuButton, {
5323
5714
  actions: actions,
5324
5715
  activity: activity,
5325
5716
  inButtonCard: !0,
5326
5717
  instanceId: instanceId,
5327
- label: "Select action"
5718
+ label: "Select action",
5719
+ surface: "inline-row"
5328
5720
  })
5329
5721
  }) : null;
5330
5722
  }
@@ -5422,7 +5814,8 @@ function ForMeList({entry: entry, work: work, identity: identity, onOpenActivity
5422
5814
  filter: {
5423
5815
  kind: "mine",
5424
5816
  identity: identity
5425
- }
5817
+ },
5818
+ surface: "document-view-for-me"
5426
5819
  }) : null ]
5427
5820
  })
5428
5821
  });
@@ -5487,9 +5880,16 @@ function instanceNotice(entry) {
5487
5880
  size: 1,
5488
5881
  children: "This workflow didn’t finish starting — use Start workflow to continue it"
5489
5882
  })
5490
- }) : pending ? /* @__PURE__ */ jsxRuntime.jsx(LoadingRow, {
5491
- label: "Loading…",
5492
- padding: 4
5883
+ }) : pending ? /* @__PURE__ */ jsxRuntime.jsx(WorkRow, {
5884
+ lead: /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
5885
+ muted: !0,
5886
+ size: 1
5887
+ }),
5888
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5889
+ muted: !0,
5890
+ size: 1,
5891
+ children: "Loading…"
5892
+ })
5493
5893
  }) : null;
5494
5894
  }
5495
5895
 
@@ -5624,7 +6024,8 @@ function TodoFieldDialog({entry: entry, scope: scope, field: field, title: title
5624
6024
  kind: "field",
5625
6025
  scope: scope,
5626
6026
  field: field
5627
- }
6027
+ },
6028
+ surface: "todo-field-dialog"
5628
6029
  })
5629
6030
  })
5630
6031
  });
@@ -5837,7 +6238,7 @@ function PillButton({children: children, pill: pill, hintDisabled: hintDisabled
5837
6238
  }
5838
6239
 
5839
6240
  function useFieldCommit({entry: entry, editability: editability}) {
5840
- const {editFieldFor: editFieldFor} = useWorkflowContext(), target = editTargetOf(editability);
6241
+ const {editFieldFor: editFieldFor} = useWorkflowContext(), target = workflowReact.editFieldTarget(editability);
5841
6242
  return {
5842
6243
  set: value => editFieldFor(entry.instance._id, {
5843
6244
  target: target,
@@ -6053,8 +6454,9 @@ function AssigneePill({entry: entry, pill: pill, editability: editability}) {
6053
6454
  /* @__PURE__ */
6054
6455
  return jsxRuntime.jsx(PillPopover, {
6055
6456
  content: /* @__PURE__ */ jsxRuntime.jsx(AssigneePicker, {
6056
- onToggle: assignee => {
6057
- setOpen(!1), pick(assignee);
6457
+ onChange: next => {
6458
+ const changed = changedAssignee(current ? [ current ] : [], next);
6459
+ changed && (setOpen(!1), pick(changed));
6058
6460
  },
6059
6461
  value: current ? [ current ] : []
6060
6462
  }),
@@ -6073,10 +6475,8 @@ function AssigneesPill({entry: entry, pill: pill, editability: editability}) {
6073
6475
  /* @__PURE__ */
6074
6476
  return jsxRuntime.jsx(PillPopover, {
6075
6477
  content: /* @__PURE__ */ jsxRuntime.jsx(AssigneePicker, {
6076
- onToggle: assignee => {
6077
- if (saving) return;
6078
- const next = current.some(a => sameAssignee(a, assignee)) ? current.filter(a => !sameAssignee(a, assignee)) : [ ...current, assignee ];
6079
- save(() => set(next));
6478
+ onChange: next => {
6479
+ saving || save(() => set(next));
6080
6480
  },
6081
6481
  value: current
6082
6482
  }),
@@ -6181,15 +6581,15 @@ function Hairline({weight: weight, style: style}) {
6181
6581
  });
6182
6582
  }
6183
6583
 
6184
- function CollapsibleBand({header: header, children: children, background: background = !1, defaultOpen: defaultOpen = !0, sticky: sticky = !1, title: title}) {
6185
- const [open, setOpen] = react.useState(defaultOpen), verb = open ? "Collapse" : "Expand";
6584
+ function CollapsibleBand({header: header, children: children, background: background = !1, defaultOpen: defaultOpen = !0, onToggle: onToggle, open: controlledOpen, sticky: sticky = !1, title: title}) {
6585
+ const [heldOpen, setHeldOpen] = react.useState(defaultOpen), open = controlledOpen ?? heldOpen, toggle = onToggle ?? (() => setHeldOpen(value => !value)), verb = open ? "Collapse" : "Expand";
6186
6586
  /* @__PURE__ */
6187
6587
  return jsxRuntime.jsxs(ui.Stack, {
6188
6588
  gap: 1,
6189
6589
  children: [
6190
6590
  /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
6191
6591
  onDoubleClick: event => {
6192
- event.target instanceof Element && event.target.closest("a, button") || setOpen(v => !v);
6592
+ event.target instanceof Element && event.target.closest("a, button") || toggle();
6193
6593
  },
6194
6594
  padding: 1,
6195
6595
  radius: 3,
@@ -6210,7 +6610,7 @@ function CollapsibleBand({header: header, children: children, background: backgr
6210
6610
  fontSize: 1,
6211
6611
  icon: open ? ChevronDown.ChevronDownIcon : ChevronRight.ChevronRightIcon,
6212
6612
  mode: "bleed",
6213
- onClick: () => setOpen(v => !v),
6613
+ onClick: toggle,
6214
6614
  padding: 2
6215
6615
  }), header ]
6216
6616
  })
@@ -6553,20 +6953,7 @@ function InstanceDebugMenu({entry: entry}) {
6553
6953
  }
6554
6954
 
6555
6955
  function InstanceAccordion({entry: entry, children: children, defaultOpen: defaultOpen}) {
6556
- const definition = useDefinition(entry), {instance: instance, ready: ready} = entry, settled = !isLiveEntry(entry), title = instanceTitle(instance, definition), badgeTrim = useBadgeCapTrim(), status = /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
6557
- align: "center",
6558
- gap: 2,
6559
- style: badgeTrim,
6560
- children: [
6561
- /* @__PURE__ */ jsxRuntime.jsx(AbortedBadge, {
6562
- instance: instance
6563
- }), settled ? null : /* @__PURE__ */ jsxRuntime.jsx(OlderDefinitionBadge, {
6564
- instance: instance
6565
- }), !ready && entry.invalid === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
6566
- muted: !0,
6567
- size: 1
6568
- }) : null ]
6569
- });
6956
+ const definition = useDefinition(entry), {instance: instance} = entry, settled = !isLiveEntry(entry), title = instanceTitle(instance, definition), badgeTrim = useBadgeCapTrim();
6570
6957
  /* @__PURE__ */
6571
6958
  return jsxRuntime.jsx(CollapsibleBand, {
6572
6959
  defaultOpen: defaultOpen ?? !settled,
@@ -6589,7 +6976,18 @@ function InstanceAccordion({entry: entry, children: children, defaultOpen: defau
6589
6976
  textOverflow: "ellipsis",
6590
6977
  weight: "medium",
6591
6978
  children: title
6592
- }), status ]
6979
+ }),
6980
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
6981
+ align: "center",
6982
+ gap: 2,
6983
+ style: badgeTrim,
6984
+ children: [
6985
+ /* @__PURE__ */ jsxRuntime.jsx(AbortedBadge, {
6986
+ instance: instance
6987
+ }), settled ? null : /* @__PURE__ */ jsxRuntime.jsx(OlderDefinitionBadge, {
6988
+ instance: instance
6989
+ }) ]
6990
+ }) ]
6593
6991
  }),
6594
6992
  /* @__PURE__ */ jsxRuntime.jsx(TrailingHairline, {}),
6595
6993
  /* @__PURE__ */ jsxRuntime.jsx(InstanceDebugMenu, {
@@ -6607,7 +7005,7 @@ function InstanceAccordion({entry: entry, children: children, defaultOpen: defau
6607
7005
  }
6608
7006
 
6609
7007
  function PartOfBand({instance: instance}) {
6610
- const parentRef = instance.ancestors.at(-1), parentId = parentRef ? workflowEngine.extractDocumentId(parentRef.id) : null, parentEntry = useWorkflowInstanceEntry(parentId), parentDefinition = useDefinition(parentEntry);
7008
+ const parentRef = instance.ancestors.at(-1), parentId = parentRef ? workflowEngine.toBareId(parentRef.id) : null, parentEntry = useWorkflowInstanceEntry(parentId), parentDefinition = useDefinition(parentEntry);
6611
7009
  if (!parentId) return null;
6612
7010
  const title = parentEntry ? instanceTitle(parentEntry.instance, parentDefinition) : parentId;
6613
7011
  /* @__PURE__ */
@@ -7123,7 +7521,12 @@ function StageSection({entry: entry, onOpenActivity: onOpenActivity}) {
7123
7521
  }
7124
7522
 
7125
7523
  const VIEW_TAB_CODEC = workflowsTabCodec([ "overview", "for-me" ]), WorkflowsView = props => {
7126
- const {documentId: documentId, schemaType: schemaType} = props, {isDocResolved: isDocResolved, mappings: mappings, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {entries: entries} = useWorkflowsForDocument(documentId), resolved = isDocResolved(documentId), docTypeMappings = mappingsForDocType(mappings, schemaType.name);
7524
+ const {documentId: documentId, schemaType: schemaType} = props, {isDocResolved: isDocResolved, mappings: mappings, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {entries: entries} = useWorkflowsForDocument(documentId), {view: view} = useWorkflowsTab();
7525
+ useLogEventOnMount(WorkflowDocumentViewOpened, {
7526
+ tab: view,
7527
+ via: "open"
7528
+ });
7529
+ const resolved = isDocResolved(documentId), docTypeMappings = mappingsForDocType(mappings, schemaType.name);
7127
7530
  if (entries.length === 0) return discoveryInvalid !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
7128
7531
  padding: 4,
7129
7532
  children: /* @__PURE__ */ jsxRuntime.jsx(InvalidDocNotice, {
@@ -7164,11 +7567,9 @@ const VIEW_TAB_CODEC = workflowsTabCodec([ "overview", "for-me" ]), WorkflowsVie
7164
7567
  };
7165
7568
 
7166
7569
  function DiscoverySpinner() {
7167
- const [visible, setVisible] = react.useState(!1);
7168
- return react.useEffect(() => {
7169
- const timer = setTimeout(() => setVisible(!0), 1e3);
7170
- return () => clearTimeout(timer);
7171
- }, []), /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
7570
+ const visible = useDelayedFlag(!0, 1e3);
7571
+ /* @__PURE__ */
7572
+ return jsxRuntime.jsx(ui.Flex, {
7172
7573
  align: "center",
7173
7574
  height: "fill",
7174
7575
  justify: "center",
@@ -7178,7 +7579,7 @@ function DiscoverySpinner() {
7178
7579
  });
7179
7580
  }
7180
7581
 
7181
- const GROUP_GAP = 1;
7582
+ const BOOT_CUE_DELAY_MS = 400, GROUP_GAP = 1;
7182
7583
 
7183
7584
  function useWorkflowsTab() {
7184
7585
  const {params: params, setParams: setParams} = structure.usePaneRouter();
@@ -7203,7 +7604,7 @@ function useOpenActivity(entries) {
7203
7604
  }
7204
7605
 
7205
7606
  function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappings: docTypeMappings, initialValue: initialValue}) {
7206
- const identity = useAssignmentIdentity(), panelId = react.useId(), {params: params} = structure.usePaneRouter(), {view: view, setView: setView} = useWorkflowsTab(), {openActivity: openActivity, setOpenActivity: setOpenActivity, dialogEntry: dialogEntry} = useOpenActivity(entries), active = entries.filter(isLiveEntry), finished = entries.filter(e => !isLiveEntry(e)), forMe = forMeWorkOf(active, identity), [landing] = react.useState(() => focusedLanding(params?.[WORKFLOWS_FOCUS_PARAM], entries.map(e => e.instance._id))), assignedWorkUnknown = active.some(e => e.invalid !== void 0), sectionFor = e => /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceSection, {
7607
+ const identity = useAssignmentIdentity(), panelId = react.useId(), {params: params} = structure.usePaneRouter(), {view: view, setView: setView} = useWorkflowsTab(), telemetry2 = workflowReact.useWorkflowTelemetry(), {openActivity: openActivity, setOpenActivity: setOpenActivity, dialogEntry: dialogEntry} = useOpenActivity(entries), active = entries.filter(isLiveEntry), finished = entries.filter(e => !isLiveEntry(e)), forMe = forMeWorkOf(active, identity), [landing] = react.useState(() => focusedLanding(params?.[WORKFLOWS_FOCUS_PARAM], entries.map(e => e.instance._id))), assignedWorkUnknown = active.some(e => e.invalid !== void 0), booting = useDelayedFlag(active.some(e => !e.ready && e.invalid === void 0), BOOT_CUE_DELAY_MS), sectionFor = e => /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceSection, {
7207
7608
  defaultOpen: landingDefaultOpen(landing, e.instance._id),
7208
7609
  entry: e,
7209
7610
  onOpenActivity: activityName => setOpenActivity({
@@ -7224,6 +7625,7 @@ function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappin
7224
7625
  padding: 3,
7225
7626
  children: [
7226
7627
  /* @__PURE__ */ jsxRuntime.jsx(GroupHeading, {
7628
+ busy: booting,
7227
7629
  count: active.length,
7228
7630
  end:
7229
7631
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
@@ -7236,6 +7638,7 @@ function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappin
7236
7638
  initialValue: initialValue,
7237
7639
  mappings: docTypeMappings,
7238
7640
  mode: "bleed",
7641
+ source: "document-view-header",
7239
7642
  tone: "default"
7240
7643
  })
7241
7644
  }),
@@ -7244,7 +7647,12 @@ function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappin
7244
7647
  /* @__PURE__ */ jsxRuntime.jsx(TabSwitch, {
7245
7648
  ariaControls: `${panelId}-panel`,
7246
7649
  idPrefix: panelId,
7247
- onSelect: setView,
7650
+ onSelect: next => {
7651
+ next !== view && telemetry2.log(WorkflowDocumentViewOpened, {
7652
+ tab: next,
7653
+ via: "tab-switch"
7654
+ }), setView(next);
7655
+ },
7248
7656
  options: [ {
7249
7657
  value: "overview",
7250
7658
  label: "Overview"
@@ -7318,6 +7726,7 @@ function OverviewEmptyState({mappings: mappings, docId: docId, initialValue: ini
7318
7726
  label: "Start workflow",
7319
7727
  mappings: mappings,
7320
7728
  mode: "ghost",
7729
+ source: "document-view-empty-state",
7321
7730
  tone: "default"
7322
7731
  })
7323
7732
  },
@@ -7351,7 +7760,8 @@ function SectionActivityDialog({entry: entry, activityName: activityName, onClos
7351
7760
  breadcrumb: instanceBreadcrumb(instance, definition),
7352
7761
  definition: definition,
7353
7762
  entry: entry,
7354
- onClose: onClose
7763
+ onClose: onClose,
7764
+ source: "document-view"
7355
7765
  });
7356
7766
  }
7357
7767
 
@@ -7359,10 +7769,8 @@ function workflowsView(S) {
7359
7769
  return S.view.component(WorkflowsView).id(WORKFLOWS_VIEW_ID).title("Workflows");
7360
7770
  }
7361
7771
 
7362
- function workflowDefaultDocumentNode(options) {
7363
- return (S, ctx) => {
7364
- if (mappingForDocType(options.mappings, ctx.schemaType)) return S.document().views([ S.view.form(), workflowsView(S) ]);
7365
- };
7772
+ function workflowDefaultDocumentNode() {
7773
+ return S => S.document().views([ S.view.form(), workflowsView(S) ]);
7366
7774
  }
7367
7775
 
7368
7776
  function lockTitleFor(args) {
@@ -7375,26 +7783,12 @@ function lockTitleFor(args) {
7375
7783
  return workflows.length === 1 ? `The ${workflowList} workflow is holding this document.` : `The ${workflowList} workflows are holding this document.`;
7376
7784
  }
7377
7785
 
7378
- const EMPTY = {
7379
- byType: /* @__PURE__ */ new Map,
7380
- definitions: /* @__PURE__ */ new Map,
7381
- warnings: []
7382
- };
7383
-
7384
7786
  function resolveAutoStart(args) {
7385
- const {autoStart: autoStart, context: context, knownDocTypes: knownDocTypes, definitions: definitions} = args;
7386
- if (autoStart === void 0) return EMPTY;
7387
- const evaluated = evaluateMap(autoStart, context);
7388
- if ("warning" in evaluated) return {
7389
- byType: /* @__PURE__ */ new Map,
7390
- definitions: /* @__PURE__ */ new Map,
7391
- warnings: [ evaluated.warning ]
7392
- };
7393
- const map = evaluated.map, latestByName = new Map(workflowEngine.latestDeployedDefinitions(definitions).map(d => [ d.name, d ])), byType = /* @__PURE__ */ new Map, resolvedDefinitions = /* @__PURE__ */ new Map, warnings = [];
7394
- for (const [docType, spec] of Object.entries(map)) {
7787
+ const {autoStart: autoStart, knownDocTypes: knownDocTypes, definitions: definitions} = args, latestByName = new Map(workflowEngine.latestDeployedDefinitions(definitions).map(d => [ d.name, d ])), byType = /* @__PURE__ */ new Map, resolvedDefinitions = /* @__PURE__ */ new Map, warnings = [];
7788
+ for (const [docType, names] of Object.entries(autoStart)) {
7395
7789
  const entry = resolveEntry({
7396
7790
  docType: docType,
7397
- spec: spec,
7791
+ names: names,
7398
7792
  knownDocTypes: knownDocTypes,
7399
7793
  latestByName: latestByName
7400
7794
  });
@@ -7410,35 +7804,15 @@ function resolveAutoStart(args) {
7410
7804
  };
7411
7805
  }
7412
7806
 
7413
- function evaluateMap(autoStart, context) {
7414
- let map;
7415
- try {
7416
- map = typeof autoStart == "function" ? autoStart(context) : autoStart;
7417
- } catch (err) {
7418
- return {
7419
- warning: `autoStart: config function threw — ignoring it (${describeError(err)}).`
7420
- };
7421
- }
7422
- return typeof map != "object" || map === null ? {
7423
- warning: "autoStart: config did not resolve to an object — ignoring it."
7424
- } : {
7425
- map: map
7426
- };
7427
- }
7428
-
7429
- function specNames(spec) {
7430
- return typeof spec == "string" ? [ spec ] : Array.isArray(spec) ? spec : [];
7431
- }
7432
-
7433
7807
  function resolveEntry(args) {
7434
- const {docType: docType, spec: spec, knownDocTypes: knownDocTypes, latestByName: latestByName} = args;
7808
+ const {docType: docType, names: names, knownDocTypes: knownDocTypes, latestByName: latestByName} = args;
7435
7809
  if (!knownDocTypes.has(docType)) return {
7436
7810
  valid: [],
7437
7811
  definitions: [],
7438
7812
  warnings: [ `autoStart: no schema type "${docType}" — entry skipped.` ]
7439
7813
  };
7440
7814
  const valid = [], definitions = [], warnings = [];
7441
- for (const name of specNames(spec)) {
7815
+ for (const name of names) {
7442
7816
  const definition = latestByName.get(name), issue = workflowIssue({
7443
7817
  name: name,
7444
7818
  docType: docType,
@@ -7464,11 +7838,57 @@ function workflowIssue(args) {
7464
7838
  const {name: name, docType: docType, definition: definition} = args;
7465
7839
  if (definition === void 0) return `autoStart: "${docType}" → workflow "${name}" is not deployed — skipped.`;
7466
7840
  if (!workflowEngine.isStartableDefinition(definition)) return `autoStart: "${docType}" → workflow "${name}" is spawn-only, not startable — skipped.`;
7467
- if (!workflowEngine.acceptsDocumentType({
7468
- fields: [ ...definition.fields ?? [] ]
7469
- }, docType)) return `autoStart: "${docType}" → workflow "${name}" has no subject accepting "${docType}" — skipped.`;
7470
- const subject = (definition.fields ?? []).find(workflowEngine.isSubjectEntry);
7471
- if (subject !== void 0 && !workflowEngine.isInputSourced(subject)) return `autoStart: "${docType}" → workflow "${name}" has a self-filling subject (not caller-provided), so the document can't be its subject — skipped.`;
7841
+ const subject = documentSubjectEntry(definition), acceptsType = subject?.types === void 0 || subject.types.includes(docType);
7842
+ if (subject === void 0 || !acceptsType) return `autoStart: "${docType}" workflow "${name}" has no document subject accepting "${docType}" — skipped.`;
7843
+ if (!workflowEngine.isInputSourced(subject)) return `autoStart: "${docType}" → workflow "${name}" has a self-filling subject (not caller-provided), so the document can't be its subject — skipped.`;
7844
+ }
7845
+
7846
+ function actualType$(previews, bareId) {
7847
+ return previews.unstable_observeDocumentPairAvailability(bareId).pipe(operators.switchMap(({draft: draft, published: published}) => draft.available ? previews.observeDocumentTypeFromId(csm.getDraftId(bareId)) : published.available ? previews.observeDocumentTypeFromId(bareId) : draft.reason === "PERMISSION_DENIED" || published.reason === "PERMISSION_DENIED" ? rxjs.of(void 0) : previews.unstable_observeVersionDocumentIds(bareId).pipe(operators.switchMap(([firstVersionId]) => firstVersionId === void 0 ? rxjs.of(null) : previews.observeDocumentTypeFromId(firstVersionId)))), operators.distinctUntilChanged());
7848
+ }
7849
+
7850
+ const REBUILD_DELAY_MS = 5e3;
7851
+
7852
+ function createActualTypeProbeStore(previews) {
7853
+ const verdicts = /* @__PURE__ */ new Map, live = /* @__PURE__ */ new Map, listeners = /* @__PURE__ */ new Set, notify = () => {
7854
+ for (const listener of [ ...listeners ]) listener();
7855
+ }, start = (bareId, entry) => actualType$(previews, bareId).subscribe({
7856
+ next: value => {
7857
+ verdicts.has(bareId) && verdicts.get(bareId) === value || (verdicts.set(bareId, value),
7858
+ notify());
7859
+ },
7860
+ error: err => {
7861
+ console.error(`[workflow-studio-plugin] actual-type probe for "${bareId}" failed:`, err),
7862
+ entry.subscription = void 0, entry.rebuildTimer = setTimeout(() => {
7863
+ entry.rebuildTimer = void 0, !(live.get(bareId) !== entry || entry.count === 0) && (entry.subscription = start(bareId, entry));
7864
+ }, REBUILD_DELAY_MS);
7865
+ }
7866
+ });
7867
+ return {
7868
+ subscribe(listener) {
7869
+ return listeners.add(listener), () => {
7870
+ listeners.delete(listener);
7871
+ };
7872
+ },
7873
+ read: bareId => verdicts.get(bareId),
7874
+ track(bareId) {
7875
+ let entry = live.get(bareId);
7876
+ if (entry === void 0) {
7877
+ const created = {
7878
+ count: 0,
7879
+ subscription: void 0,
7880
+ rebuildTimer: void 0
7881
+ };
7882
+ live.set(bareId, created), created.subscription = start(bareId, created), entry = created;
7883
+ }
7884
+ entry.count += 1;
7885
+ let released = !1;
7886
+ return () => {
7887
+ released || (released = !0, entry.count -= 1, !(entry.count > 0) && (entry.subscription?.unsubscribe(),
7888
+ entry.rebuildTimer !== void 0 && clearTimeout(entry.rebuildTimer), live.get(bareId) === entry && live.delete(bareId)));
7889
+ };
7890
+ }
7891
+ };
7472
7892
  }
7473
7893
 
7474
7894
  function contentDocumentTypes(schema) {
@@ -7477,7 +7897,7 @@ function contentDocumentTypes(schema) {
7477
7897
 
7478
7898
  async function readDeployedDefinitions(engine) {
7479
7899
  return (await engine.query({
7480
- groq: workflowEngine.definitionsListGroq("desc")
7900
+ groq: workflowEngine.latestDefinitionsGroq()
7481
7901
  })).map(workflowEngine.assertReadableModel);
7482
7902
  }
7483
7903
 
@@ -7488,20 +7908,31 @@ const EMPTY_SNAPSHOT = {
7488
7908
  };
7489
7909
 
7490
7910
  function useDiscoveryState(args) {
7491
- const {registered: registered, requested: requested, contentResource: contentResource} = args, watching = registered.length + requested.length > 0, [latest, setLatest] = react.useState(EMPTY_SNAPSHOT), [resolved, setResolved] = react.useState(EMPTY_SNAPSHOT), onSnapshot = react.useCallback(snapshot => {
7492
- setLatest(snapshot), snapshot.instances !== void 0 && setResolved(snapshot);
7493
- }, []);
7494
- react.useEffect(() => {
7495
- watching || (setLatest(EMPTY_SNAPSHOT), setResolved(EMPTY_SNAPSHOT));
7496
- }, [ watching ]);
7497
- const filter = react.useMemo(() => ({
7498
- documents: registered.map(id => workflowEngine.gdrFromResource(contentResource, id)),
7911
+ const {observed: observed, requested: requested, contentResource: contentResource} = args, documentWatching = observed.length > 0, requestedWatching = requested.length > 0, documents = useDiscoveryStream(documentWatching), explicit = useDiscoveryStream(requestedWatching), documentFilter = react.useMemo(() => documentWatching ? {
7912
+ documents: observed.map(id => workflowEngine.gdrFromResource(contentResource, id)),
7913
+ includeCompleted: !0
7914
+ } : void 0, [ documentWatching, observed, contentResource ]), requestedFilter = react.useMemo(() => requestedWatching ? {
7499
7915
  ids: requested,
7500
7916
  includeCompleted: !0
7501
- }), [ registered, requested, contentResource ]);
7917
+ } : void 0, [ requestedWatching, requested ]), resolved = react.useMemo(() => mergeResolved(documents.resolved, explicit.resolved), [ documents.resolved, explicit.resolved ]);
7502
7918
  return {
7503
- watching: watching,
7504
- filter: filter,
7919
+ documentFilter: documentFilter,
7920
+ requestedFilter: requestedFilter,
7921
+ loading: documents.loading || explicit.loading,
7922
+ invalid: documents.invalid ?? explicit.invalid,
7923
+ resolved: resolved,
7924
+ onDocumentSnapshot: documents.onSnapshot,
7925
+ onRequestedSnapshot: explicit.onSnapshot
7926
+ };
7927
+ }
7928
+
7929
+ function useDiscoveryStream(watching) {
7930
+ const [latest, setLatest] = react.useState(EMPTY_SNAPSHOT), [resolved, setResolved] = react.useState(EMPTY_SNAPSHOT), onSnapshot = react.useCallback(snapshot => {
7931
+ setLatest(snapshot), snapshot.instances !== void 0 && setResolved(snapshot);
7932
+ }, []);
7933
+ return react.useEffect(() => {
7934
+ watching || (setLatest(EMPTY_SNAPSHOT), setResolved(EMPTY_SNAPSHOT));
7935
+ }, [ watching ]), {
7505
7936
  loading: watching && latest.instances === void 0 && latest.invalid === void 0,
7506
7937
  invalid: watching ? latest.invalid : void 0,
7507
7938
  resolved: resolved,
@@ -7509,18 +7940,62 @@ function useDiscoveryState(args) {
7509
7940
  };
7510
7941
  }
7511
7942
 
7512
- function indexDiscoveredInstances(args) {
7513
- const {instances: instances, registered: registered, contentResource: contentResource} = args, ordered = [ ...instances ].sort((a, b) => b.startedAt.localeCompare(a.startedAt)), index = /* @__PURE__ */ new Map;
7514
- for (const bareId of registered) {
7515
- const uri = workflowEngine.gdrFromResource(contentResource, bareId), watching = ordered.filter(inst => workflowEngine.instanceWatchesDocument(inst, uri));
7516
- watching.length > 0 && index.set(bareId, watching.map(inst => inst._id));
7943
+ function mergeResolved(documents, explicit) {
7944
+ const rows = [ ...documents.instances ?? [], ...explicit.instances ?? [] ], freshest = /* @__PURE__ */ new Map;
7945
+ for (const instance of rows) {
7946
+ const current = freshest.get(instance._id);
7947
+ (current === void 0 || Date.parse(instance.lastChangedAt) > Date.parse(current.lastChangedAt)) && freshest.set(instance._id, instance);
7517
7948
  }
7949
+ return {
7950
+ instances: documents.instances === void 0 && explicit.instances === void 0 ? void 0 : [ ...freshest.values() ],
7951
+ invalid: documents.invalid ?? explicit.invalid,
7952
+ registered: documents.registered
7953
+ };
7954
+ }
7955
+
7956
+ function createDiscoveryIndexer(deriveWatchSet = workflowEngine.subscriptionDocumentsForInstance) {
7957
+ const cached = /* @__PURE__ */ new Map;
7958
+ return {
7959
+ index(args) {
7960
+ const liveIds = new Set(args.instances.map(instance => instance._id));
7961
+ for (const id of cached.keys()) liveIds.has(id) || cached.delete(id);
7962
+ const watchedById = /* @__PURE__ */ new Map;
7963
+ for (const instance of args.instances) {
7964
+ let row = cached.get(instance._id);
7965
+ row?.source !== instance && (row = {
7966
+ source: instance,
7967
+ globalDocumentIds: [ ...new Set(deriveWatchSet(instance).documents.map(document => document.globalDocumentId)) ]
7968
+ }, cached.set(instance._id, row)), watchedById.set(instance._id, row.globalDocumentIds);
7969
+ }
7970
+ return buildDiscoveryIndex(args, watchedById);
7971
+ }
7972
+ };
7973
+ }
7974
+
7975
+ function buildDiscoveryIndex(args, watchedById) {
7976
+ const {instances: instances, registered: registered, contentResource: contentResource} = args, ordered = [ ...instances ].sort((a, b) => b.startedAt.localeCompare(a.startedAt)), index = /* @__PURE__ */ new Map, registeredByUri = new Map(registered.map(bareId => [ workflowEngine.gdrFromResource(contentResource, bareId), bareId ]));
7977
+ for (const instance of ordered) appendInstanceWatches({
7978
+ index: index,
7979
+ registeredByUri: registeredByUri,
7980
+ instanceId: instance._id,
7981
+ watched: watchedById.get(instance._id) ?? []
7982
+ });
7518
7983
  return {
7519
7984
  instances: new Map(ordered.map(inst => [ inst._id, inst ])),
7520
7985
  index: index
7521
7986
  };
7522
7987
  }
7523
7988
 
7989
+ function appendInstanceWatches(args) {
7990
+ const {index: index, registeredByUri: registeredByUri, instanceId: instanceId, watched: watched} = args;
7991
+ for (const uri of watched) {
7992
+ const bareId = registeredByUri.get(uri);
7993
+ if (bareId === void 0) continue;
7994
+ const ids = index.get(bareId);
7995
+ ids === void 0 ? index.set(bareId, [ instanceId ]) : ids.push(instanceId);
7996
+ }
7997
+ }
7998
+
7524
7999
  function sameInstance(a, b) {
7525
8000
  return a === b || a._id === b._id && a._rev === b._rev;
7526
8001
  }
@@ -7544,7 +8019,7 @@ function entryOf(inst, report) {
7544
8019
  instance: report?.evaluation?.instance ?? inst,
7545
8020
  committed: inst,
7546
8021
  evaluation: report?.evaluation,
7547
- ready: report?.ready ?? !1,
8022
+ ready: report?.ready ?? workflowEngine.terminalState(inst) !== "in-flight",
7548
8023
  invalid: report?.invalid,
7549
8024
  guards: report?.guards
7550
8025
  };
@@ -7637,7 +8112,19 @@ function createEntriesStore() {
7637
8112
  };
7638
8113
  }
7639
8114
 
7640
- const notReady = (..._args) => Promise.reject(new Error("workflow session not ready yet"));
8115
+ function sessionPlan(args) {
8116
+ const demandedIds = new Set(args.demanded), evaluated = [ ...args.discovered.values() ].filter(inst => demandedIds.has(inst._id));
8117
+ return {
8118
+ evaluated: evaluated,
8119
+ guardScope: evaluated.map(inst => inst._id).sort()
8120
+ };
8121
+ }
8122
+
8123
+ function useDefaultLayout(props) {
8124
+ return react.useMemo(() => props.renderDefault(props), [ props ]);
8125
+ }
8126
+
8127
+ const notReady = (..._args) => Promise.reject(new Error("workflow session not ready yet")), NO_REGISTERED_DOCUMENTS = [], DEMAND_RELEASE_GRACE_MS = 500;
7641
8128
 
7642
8129
  function workspaceParts(config) {
7643
8130
  if (!config.projectId || !config.dataset) throw new Error("workflow plugin requires a workspace with projectId and dataset");
@@ -7665,10 +8152,11 @@ class InstanceEvaluatorBoundary extends react.Component {
7665
8152
  }
7666
8153
 
7667
8154
  function InstanceEvaluator(props) {
7668
- const {engine: engine, instanceId: instanceId, grantsFromPath: grantsFromPath, onReport: onReport} = props, {evaluation: evaluation, ready: ready, invalid: invalid, guards: guards, fireAction: fireAction, editField: editField, previewField: previewField, discardFieldPreview: discardFieldPreview} = workflowStudio.useWorkflowSession({
8155
+ const {engine: engine, instanceId: instanceId, grantsFromPath: grantsFromPath, guardScope: guardScope, onReport: onReport} = props, {evaluation: evaluation, ready: ready, invalid: invalid, guards: guards, fireAction: fireAction, editField: editField, previewField: previewField, discardFieldPreview: discardFieldPreview} = workflowStudio.useWorkflowSession({
7669
8156
  engine: engine,
7670
8157
  instanceId: instanceId,
7671
- grantsFromPath: grantsFromPath
8158
+ grantsFromPath: grantsFromPath,
8159
+ guardScope: guardScope
7672
8160
  });
7673
8161
  return react.useEffect(() => {
7674
8162
  onReport(instanceId, {
@@ -7712,9 +8200,9 @@ function engineOptionsFrom(config) {
7712
8200
  }
7713
8201
 
7714
8202
  function WorkflowProvider(props) {
7715
- const {config: config} = props, studioClient = sanity.useClient({
8203
+ const {config: config} = props, defaultLayout = useDefaultLayout(props), studioClient = sanity.useClient({
7716
8204
  apiVersion: WORKFLOW_API_VERSION
7717
- }), schema = sanity.useSchema(), {name: workspaceName} = sanity.useWorkspace(), {projectId: projectId, dataset: contentDataset} = workspaceParts(studioClient.config()), workflowDataset = config.workflowDataset ?? contentDataset, binding = react.useMemo(() => ({
8205
+ }), schema = sanity.useSchema(), {projectId: projectId, dataset: contentDataset} = workspaceParts(studioClient.config()), workflowDataset = config.workflowDataset ?? contentDataset, binding = react.useMemo(() => ({
7718
8206
  engineResource: {
7719
8207
  type: "dataset",
7720
8208
  id: `${projectId}.${workflowDataset}`
@@ -7732,11 +8220,19 @@ function WorkflowProvider(props) {
7732
8220
  }), grantsFromPath = workflowEngine.aclPathForResource({
7733
8221
  type: "dataset",
7734
8222
  id: `${projectId}.${workflowDataset}`
7735
- }), {ids: registered, acquire: register, release: unregister} = workflowReact.useRefcountedIds(), {ids: requested, acquire: requestInstance, release: releaseInstance} = workflowReact.useRefcountedIds(), [seeded, setSeeded] = react.useState(/* @__PURE__ */ new Map), seedInstance = react.useCallback(instance => {
8223
+ }), {ids: registered, acquire: registerActive, release: unregisterActive} = workflowReact.useRefcountedIds(), {ids: observed, acquire: retainRegistration, release: releaseRegistration} = workflowReact.useRefcountedIds({
8224
+ releaseDelayMs: DEMAND_RELEASE_GRACE_MS
8225
+ }), register = react.useCallback(id => {
8226
+ registerActive(id), retainRegistration(id);
8227
+ }, [ registerActive, retainRegistration ]), unregister = react.useCallback(id => {
8228
+ unregisterActive(id), releaseRegistration(id);
8229
+ }, [ unregisterActive, releaseRegistration ]), {ids: requested, acquire: requestInstance, release: releaseInstance} = workflowReact.useRefcountedIds(), {ids: evaluationDemand, acquire: requestEvaluation, release: releaseEvaluation} = workflowReact.useRefcountedIds({
8230
+ releaseDelayMs: DEMAND_RELEASE_GRACE_MS
8231
+ }), [seeded, setSeeded] = react.useState(/* @__PURE__ */ new Map), seedInstance = react.useCallback(instance => {
7736
8232
  setSeeded(prev => new Map(prev).set(instance._id, instance));
7737
8233
  }, []), [startRequest2, setStartRequest] = react.useState(void 0), openStartDialog = react.useCallback(r => setStartRequest(r), []), closeStartDialog = react.useCallback(() => setStartRequest(void 0), []), [entriesStore] = react.useState(createEntriesStore);
7738
8234
  react.useEffect(() => () => entriesStore.dispose(), [ entriesStore ]);
7739
- const onReport = react.useCallback((instanceId, report) => {
8235
+ const previewStore = sanity.useDocumentPreviewStore(), actualTypes = react.useMemo(() => createActualTypeProbeStore(previewStore), [ previewStore ]), onReport = react.useCallback((instanceId, report) => {
7740
8236
  entriesStore.report(instanceId, report);
7741
8237
  }, [ entriesStore ]), drainRef = react.useRef(void 0), drainCommitted = react.useCallback(async instanceId => {
7742
8238
  try {
@@ -7783,15 +8279,18 @@ function WorkflowProvider(props) {
7783
8279
  });
7784
8280
  }, [ engine ]);
7785
8281
  drainRef.current = drainEffectsFor;
7786
- const {watching: watching, filter: filter, loading: loading, invalid: discoveryInvalid, resolved: resolved, onSnapshot: onSnapshot} = useDiscoveryState({
7787
- registered: registered,
8282
+ const {documentFilter: documentFilter, requestedFilter: requestedFilter, loading: loading, invalid: discoveryInvalid, resolved: resolved, onDocumentSnapshot: onDocumentSnapshot, onRequestedSnapshot: onRequestedSnapshot} = useDiscoveryState({
8283
+ observed: observed,
7788
8284
  requested: requested,
7789
8285
  contentResource: binding.contentResource
7790
- }), {instances: discovered, index: docIndex} = react.useMemo(() => indexDiscoveredInstances({
8286
+ }), discoveryIndexer = react.useMemo(createDiscoveryIndexer, []), {instances: discovered, index: docIndex} = react.useMemo(() => discoveryIndexer.index({
7791
8287
  instances: resolved.instances ?? [],
7792
8288
  registered: resolved.registered,
7793
8289
  contentResource: binding.contentResource
7794
- }), [ resolved, binding.contentResource ]), resolvedDocs = react.useMemo(() => new Set(resolved.registered), [ resolved.registered ]);
8290
+ }), [ discoveryIndexer, resolved, binding.contentResource ]), {evaluated: evaluated, guardScope: guardScope} = react.useMemo(() => sessionPlan({
8291
+ discovered: discovered,
8292
+ demanded: evaluationDemand
8293
+ }), [ discovered, evaluationDemand ]), resolvedDocs = react.useMemo(() => new Set(resolved.registered), [ resolved.registered ]);
7795
8294
  react.useEffect(() => {
7796
8295
  setSeeded(prev => {
7797
8296
  const kept = [ ...prev ].filter(([id]) => !discovered.has(id));
@@ -7804,22 +8303,23 @@ function WorkflowProvider(props) {
7804
8303
  docIndex: docIndex
7805
8304
  });
7806
8305
  }, [ entriesStore, seeded, discovered, docIndex ]);
7807
- const isDocResolved = react.useCallback(docId => resolvedDocs.has(docId), [ resolvedDocs ]), schemaContentTypes = react.useMemo(() => new Set(contentDocumentTypes(schema)), [ schema ]), {issues: issues, starts: starts, fields: fields, latestVersions: latestVersions, autoStartByType: autoStartByType, autoStartDefinitions: autoStartDefinitions} = useDeployedDefinitions({
8306
+ const isDocResolved = react.useCallback(docId => resolvedDocs.has(docId), [ resolvedDocs ]), schemaContentTypes = react.useMemo(() => new Set(contentDocumentTypes(schema)), [ schema ]), {mappings: mappings, issues: issues, starts: starts, fields: fields, latestVersions: latestVersions, autoStartByType: autoStartByType, autoStartDefinitions: autoStartDefinitions} = useDeployedDefinitions({
7808
8307
  engine: engine,
7809
- mappings: config.mappings,
8308
+ overrides: config.mappings,
7810
8309
  contentResource: binding.contentResource,
7811
8310
  schemaContentTypes: schemaContentTypes,
7812
8311
  startRequest: startRequest2,
7813
- autoStart: config.autoStart,
7814
- schema: schema,
7815
- workspaceName: workspaceName
8312
+ schema: schema
7816
8313
  }), value = react.useMemo(() => ({
7817
8314
  register: register,
7818
8315
  unregister: unregister,
7819
8316
  isDocResolved: isDocResolved,
7820
8317
  entries: entriesStore,
8318
+ actualTypes: actualTypes,
7821
8319
  requestInstance: requestInstance,
7822
8320
  releaseInstance: releaseInstance,
8321
+ requestEvaluation: requestEvaluation,
8322
+ releaseEvaluation: releaseEvaluation,
7823
8323
  seedInstance: seedInstance,
7824
8324
  openStartDialog: openStartDialog,
7825
8325
  closeStartDialog: closeStartDialog,
@@ -7834,7 +8334,7 @@ function WorkflowProvider(props) {
7834
8334
  completeEffectFor: completeEffectFor,
7835
8335
  engine: engine,
7836
8336
  binding: binding,
7837
- mappings: config.mappings,
8337
+ mappings: mappings,
7838
8338
  mappingIssues: issues,
7839
8339
  mappingStarts: starts,
7840
8340
  mappingFields: fields,
@@ -7842,24 +8342,30 @@ function WorkflowProvider(props) {
7842
8342
  effectHandlers: engine.effectHandlers,
7843
8343
  autoStartByType: autoStartByType,
7844
8344
  autoStartDefinitions: autoStartDefinitions
7845
- }), [ register, unregister, isDocResolved, entriesStore, requestInstance, releaseInstance, seedInstance, openStartDialog, closeStartDialog, startRequest2, loading, discoveryInvalid, fireActionFor, editFieldFor, previewFieldFor, discardFieldPreviewFor, drainEffectsFor, completeEffectFor, engine, binding, config.mappings, issues, starts, latestVersions, autoStartByType, autoStartDefinitions ]);
8345
+ }), [ register, unregister, isDocResolved, entriesStore, actualTypes, requestInstance, releaseInstance, requestEvaluation, releaseEvaluation, seedInstance, openStartDialog, closeStartDialog, startRequest2, loading, discoveryInvalid, fireActionFor, editFieldFor, previewFieldFor, discardFieldPreviewFor, drainEffectsFor, completeEffectFor, engine, binding, mappings, issues, starts, fields, latestVersions, autoStartByType, autoStartDefinitions ]);
7846
8346
  /* @__PURE__ */
7847
8347
  return jsxRuntime.jsxs(WorkflowContext.Provider, {
7848
8348
  value: value,
7849
- children: [ watching ? /* @__PURE__ */ jsxRuntime.jsx(DiscoverySubscription, {
8349
+ children: [ documentFilter ? /* @__PURE__ */ jsxRuntime.jsx(DiscoverySubscription, {
7850
8350
  engine: engine,
7851
- filter: filter,
7852
- onSnapshot: onSnapshot,
8351
+ filter: documentFilter,
8352
+ onSnapshot: onDocumentSnapshot,
7853
8353
  registered: registered
7854
- }) : null, [ ...discovered.values() ].map(inst => /* @__PURE__ */ jsxRuntime.jsx(InstanceEvaluatorBoundary, {
8354
+ }) : null, requestedFilter ? /* @__PURE__ */ jsxRuntime.jsx(DiscoverySubscription, {
8355
+ engine: engine,
8356
+ filter: requestedFilter,
8357
+ onSnapshot: onRequestedSnapshot,
8358
+ registered: NO_REGISTERED_DOCUMENTS
8359
+ }) : null, evaluated.map(inst => /* @__PURE__ */ jsxRuntime.jsx(InstanceEvaluatorBoundary, {
7855
8360
  instanceId: inst._id,
7856
8361
  children: /* @__PURE__ */ jsxRuntime.jsx(InstanceEvaluator, {
7857
8362
  engine: engine,
7858
8363
  grantsFromPath: grantsFromPath,
8364
+ guardScope: guardScope,
7859
8365
  instanceId: inst._id,
7860
8366
  onReport: onReport
7861
8367
  })
7862
- }, inst._id)), props.renderDefault(props), props.overlay ]
8368
+ }, inst._id)), defaultLayout, props.overlay ]
7863
8369
  });
7864
8370
  }
7865
8371
 
@@ -7869,14 +8375,25 @@ function sameEntries(a, b) {
7869
8375
  return !0;
7870
8376
  }
7871
8377
 
8378
+ function sameMappings(a, b) {
8379
+ return a.length === b.length && a.every((mapping, index) => {
8380
+ const other = b[index];
8381
+ return other !== void 0 && mappingKey(mapping) === mappingKey(other) && mapping.label === other.label && mapping.autoStart === other.autoStart && mapping.initialStateBuilder === other.initialStateBuilder && mapping.contextBuilder === other.contextBuilder && mapping.perspectiveField === other.perspectiveField;
8382
+ });
8383
+ }
8384
+
7872
8385
  function useDeployedDefinitions(args) {
7873
- const {engine: engine, mappings: mappings, contentResource: contentResource, schemaContentTypes: schemaContentTypes, startRequest: startRequest2, autoStart: autoStart, schema: schema, workspaceName: workspaceName} = args, [issues, setIssues] = react.useState(/* @__PURE__ */ new Map), [starts, setStarts] = react.useState(/* @__PURE__ */ new Map), [fields, setFields] = react.useState(/* @__PURE__ */ new Map), [latestVersions, setLatestVersions] = react.useState(/* @__PURE__ */ new Map), [autoStartByType, setAutoStartByType] = react.useState(
8386
+ const {engine: engine, overrides: overrides, contentResource: contentResource, schemaContentTypes: schemaContentTypes, startRequest: startRequest2, schema: schema} = args, [mappings, setMappings] = react.useState([]), [issues, setIssues] = react.useState(/* @__PURE__ */ new Map), [starts, setStarts] = react.useState(/* @__PURE__ */ new Map), [fields, setFields] = react.useState(/* @__PURE__ */ new Map), [latestVersions, setLatestVersions] = react.useState(/* @__PURE__ */ new Map), [autoStartByType, setAutoStartByType] = react.useState(
7874
8387
  /* @__PURE__ */ new Map), [autoStartDefinitions, setAutoStartDefinitions] = react.useState(/* @__PURE__ */ new Map), logged = react.useRef(/* @__PURE__ */ new Set);
7875
8388
  return react.useEffect(() => {
7876
8389
  let cancelled = !1;
7877
8390
  return (async () => {
7878
- const definitions = await readDeployedDefinitions(engine), found = mappingIssues({
7879
- mappings: mappings,
8391
+ const definitions = await readDeployedDefinitions(engine), effectiveMappings = discoverWorkflowMappings({
8392
+ definitions: definitions,
8393
+ schemaContentTypes: schemaContentTypes,
8394
+ overrides: overrides
8395
+ }), found = mappingIssues({
8396
+ mappings: effectiveMappings,
7880
8397
  definitions: definitions,
7881
8398
  contentResource: contentResource,
7882
8399
  schemaContentTypes: schemaContentTypes
@@ -7886,32 +8403,30 @@ function useDeployedDefinitions(args) {
7886
8403
  logged.current.has(signature) || (logged.current.add(signature), console.error(`[workflow-studio-plugin] ${message}`));
7887
8404
  }
7888
8405
  const resolvedAutoStart = resolveAutoStart({
7889
- autoStart: autoStart,
7890
- context: {
7891
- workspaceName: workspaceName,
7892
- schema: schema
7893
- },
8406
+ autoStart: mappingAutoStartMap(effectiveMappings),
7894
8407
  knownDocTypes: new Set(schema.getTypeNames()),
7895
8408
  definitions: definitions
7896
8409
  });
7897
8410
  if (logOnce(resolvedAutoStart.warnings, logged.current), cancelled) return;
8411
+ setMappings(prev => sameMappings(prev, effectiveMappings) ? prev : effectiveMappings),
7898
8412
  setIssues(prev => sameEntries(prev, found) ? prev : found), setStarts(mappingStartBlocks({
7899
- mappings: mappings,
8413
+ mappings: effectiveMappings,
7900
8414
  definitions: definitions
7901
8415
  })), setFields(mappingDeclaredFields({
7902
- mappings: mappings,
8416
+ mappings: effectiveMappings,
7903
8417
  definitions: definitions
7904
8418
  }));
7905
8419
  const versions = new Map(workflowEngine.latestDeployedDefinitions(definitions).map(d => [ d.name, d.version ]));
7906
8420
  setLatestVersions(prev => sameEntries(prev, versions) ? prev : versions), setAutoStartByType(resolvedAutoStart.byType),
7907
8421
  setAutoStartDefinitions(resolvedAutoStart.definitions);
7908
8422
  })().catch(err => {
7909
- console.error("[workflow-studio-plugin] mapping validation could not read the deployed definitions keeping the previous verdicts:", err);
8423
+ console.error("[workflow-studio-plugin] mapping discovery failed keeping the last resolved workflow state:", err);
7910
8424
  }), () => {
7911
8425
  cancelled = !0;
7912
8426
  };
7913
- }, [ engine, mappings, contentResource, schemaContentTypes, startRequest2, autoStart, schema, workspaceName ]),
8427
+ }, [ engine, overrides, contentResource, schemaContentTypes, startRequest2, schema ]),
7914
8428
  {
8429
+ mappings: mappings,
7915
8430
  issues: issues,
7916
8431
  starts: starts,
7917
8432
  fields: fields,
@@ -7927,13 +8442,13 @@ function logOnce(messages, logged) {
7927
8442
 
7928
8443
  function AssigneesInput({value: value, onChange: onChange}) {
7929
8444
  const {members: members} = useProjectMembers(), [membersOpen, setMembersOpen] = react.useState(!1), roles = react.useMemo(() => [ ...new Set(members.flatMap(m => m.roles)) ].toSorted(), [ members ]), userIds = react.useMemo(() => new Set(value.flatMap(a => a.type === "user" ? [ a.id ] : [])), [ value ]), add = a => {
7930
- value.some(x => sameAssignee(x, a)) || onChange([ ...value, a ]);
8445
+ value.some(x => workflowComponents.sameAssignee(x, a)) || onChange([ ...value, a ]);
7931
8446
  }, remove = i => onChange(value.filter((_, idx) => idx !== i)), toggleUser = id => {
7932
8447
  const user = {
7933
8448
  type: "user",
7934
8449
  id: id
7935
8450
  };
7936
- userIds.has(id) ? onChange(value.filter(a => !sameAssignee(a, user))) : add(user);
8451
+ userIds.has(id) ? onChange(value.filter(a => !workflowComponents.sameAssignee(a, user))) : add(user);
7937
8452
  };
7938
8453
  /* @__PURE__ */
7939
8454
  return jsxRuntime.jsxs(ui.Stack, {
@@ -8300,12 +8815,12 @@ function DocRefsFieldInput({entry: entry, mappedType: mappedType, value: value,
8300
8815
  }
8301
8816
 
8302
8817
  function NotesStartInput({onChange: onChange}) {
8303
- const me = sanity.useCurrentUser(), [body, setBody] = react.useState(""), update = next => {
8818
+ const selfActor = useSelfActor(), [body, setBody] = react.useState(""), update = next => {
8304
8819
  setBody(next);
8305
8820
  const trimmed = next.trim();
8306
8821
  onChange(trimmed === "" ? [] : [ noteRow({
8307
8822
  body: trimmed,
8308
- actor: me ? personActor(me) : void 0,
8823
+ actor: selfActor,
8309
8824
  at: /* @__PURE__ */ (new Date).toISOString()
8310
8825
  }) ]);
8311
8826
  };
@@ -8514,7 +9029,7 @@ function handleStartError(args) {
8514
9029
  toast.push({
8515
9030
  status: "warning",
8516
9031
  title: `${label} can't be started right now`,
8517
- description: "The workflow does not allow another run with these inputs."
9032
+ description: outcome.description
8518
9033
  });
8519
9034
  return;
8520
9035
  }
@@ -8526,7 +9041,7 @@ function handleStartError(args) {
8526
9041
  }
8527
9042
 
8528
9043
  function StartWorkflowForm({request: request, onClose: onClose}) {
8529
- const {definition: definitionName, label: label, mapping: mapping, subjectDocId: subjectDocId} = request, {engine: engine, binding: binding, seedInstance: seedInstance} = useWorkflowContext(), toast = useClosableToast(), {def: definition, error: definitionError} = useLatestDefinition(definitionName), {selectedReleaseId: selectedReleaseId} = sanity.usePerspective(), releaseId = boundReleaseId(mapping, selectedReleaseId), [values, setValues] = react.useState({}), [attempt, setAttempt] = react.useState(void 0), starting = attempt !== void 0, [heldInstanceId, setHeldInstanceId] = react.useState(void 0), [startNew, setStartNew] = react.useState(!1), {entries: entries} = useWorkflowsForDocument(subjectDocId), resumeEntry = presentedResumeEntry({
9044
+ const {definition: definitionName, label: label, mapping: mapping, subjectDocId: subjectDocId} = request, {engine: engine, binding: binding, seedInstance: seedInstance} = useWorkflowContext(), telemetry2 = workflowReact.useWorkflowTelemetry(), toast = useClosableToast(), {def: definition, error: definitionError} = useLatestDefinition(definitionName), {selectedReleaseId: selectedReleaseId} = sanity.usePerspective(), releaseId = boundReleaseId(mapping, selectedReleaseId), [values, setValues] = react.useState({}), [attempt, setAttempt] = react.useState(void 0), starting = attempt !== void 0, [heldInstanceId, setHeldInstanceId] = react.useState(void 0), [startNew, setStartNew] = react.useState(!1), {entries: entries} = useWorkflowsForDocument(subjectDocId), resumeEntry = presentedResumeEntry({
8530
9045
  entries: entries,
8531
9046
  definitionName: definitionName,
8532
9047
  heldInstanceId: heldInstanceId,
@@ -8549,7 +9064,10 @@ function StartWorkflowForm({request: request, onClose: onClose}) {
8549
9064
  [name]: v
8550
9065
  })), heldInstanceId !== void 0 && declineResume();
8551
9066
  }, onStart = async () => {
8552
- setAttempt({
9067
+ telemetry2.log(WorkflowStartDialogSubmitted, {
9068
+ source: request.source,
9069
+ resumed: resuming
9070
+ }), setAttempt({
8553
9071
  resume: resumeEntry
8554
9072
  });
8555
9073
  const instanceId = resumeEntry?.instance._id ?? heldInstanceId ?? workflowEngine.instanceDocId(binding.tag);
@@ -8629,8 +9147,9 @@ function StartWorkflowForm({request: request, onClose: onClose}) {
8629
9147
  }
8630
9148
 
8631
9149
  function StartWorkflowDialog({request: request, onClose: onClose}) {
8632
- /* @__PURE__ */
8633
- return jsxRuntime.jsx(ui.Dialog, {
9150
+ return useLogEventOnMount(WorkflowStartDialogOpened, {
9151
+ source: request.source
9152
+ }), /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, {
8634
9153
  header: `Start ${request.label}`,
8635
9154
  id: "start-workflow",
8636
9155
  onClose: onClose,
@@ -8697,7 +9216,7 @@ function isFreshDocument(value) {
8697
9216
  }
8698
9217
 
8699
9218
  function subjectEntry(definition) {
8700
- return (definition.fields ?? []).find(workflowEngine.isSubjectEntry);
9219
+ return documentSubjectEntry(definition);
8701
9220
  }
8702
9221
 
8703
9222
  function autoStartInputs(args) {
@@ -8724,11 +9243,11 @@ function pendingWorkflows(workflows, live) {
8724
9243
  }
8725
9244
 
8726
9245
  function extraRequiredInputs(definition) {
8727
- const fields = definition.fields ?? [], subjectNames = new Set(fields.filter(workflowEngine.isSubjectEntry).map(entry => entry.name)), requiredNames = new Set(workflowEngine.missingRequiredInputs({
9246
+ const fields = definition.fields ?? [], subjectName = subjectEntry(definition)?.name, requiredNames = new Set(workflowEngine.missingRequiredInputs({
8728
9247
  entryDefs: fields,
8729
9248
  initialFields: []
8730
9249
  }).map(input => input.name));
8731
- return fields.filter(entry => requiredNames.has(entry.name) && !subjectNames.has(entry.name));
9250
+ return fields.filter(entry => requiredNames.has(entry.name) && entry.name !== subjectName);
8732
9251
  }
8733
9252
 
8734
9253
  function buildAutoStartRequests(args) {
@@ -8769,21 +9288,21 @@ function AutoStartGate(props) {
8769
9288
  }
8770
9289
 
8771
9290
  function AutoStartDriver(props) {
8772
- const {rawDocId: rawDocId, docType: docType, workflows: workflows, value: value, fallback: fallback} = props, {isDocResolved: isDocResolved, autoStartDefinitions: autoStartDefinitions, engine: engine, binding: binding, seedInstance: seedInstance} = useWorkflowContext(), observer = workflowStudio.useStudioObserver({
9291
+ const {rawDocId: rawDocId, docType: docType, workflows: workflows, value: value, fallback: fallback} = props, {isDocResolved: isDocResolved, autoStartDefinitions: autoStartDefinitions, engine: engine, binding: binding, seedInstance: seedInstance} = useWorkflowContext(), telemetry2 = workflowReact.useWorkflowTelemetry(), observer = workflowStudio.useStudioObserver({
8773
9292
  engine: engine
8774
9293
  }), {selectedReleaseId: selectedReleaseId} = sanity.usePerspective(), {entries: entries} = useWorkflowsForDocument(rawDocId), docId = csm.getPublishedId(rawDocId), valueRef = react.useRef(value);
8775
9294
  valueRef.current = value;
8776
9295
  const pending = react.useMemo(() => pendingWorkflows(workflows, new Set(entries.map(entry => entry.instance.definition))), [ workflows, entries ]), inputs = react.useMemo(() => autoStartInputs({
8777
9296
  workflows: pending,
8778
9297
  definitions: autoStartDefinitions
8779
- }), [ pending, autoStartDefinitions ]), pendingRef = react.useRef(pending);
9298
+ }), [ pending, autoStartDefinitions ]), definitionsResolved = workflows.every(name => autoStartDefinitions.has(name)), pendingRef = react.useRef(pending);
8780
9299
  pendingRef.current = pending;
8781
9300
  const instanceIds = react.useRef(/* @__PURE__ */ new Map), instanceIdFor = react.useCallback(workflow => {
8782
9301
  const held = instanceIds.current.get(workflow);
8783
9302
  if (held !== void 0) return held;
8784
9303
  const id = workflowEngine.instanceDocId(binding.tag);
8785
9304
  return instanceIds.current.set(workflow, id), id;
8786
- }, [ binding.tag ]), [fired, setFired] = react.useState(!1), [settled, setSettled] = react.useState(!1), [failure, setFailure] = react.useState(void 0), firedRef = react.useRef(!1), startAll = react.useCallback(async values => {
9305
+ }, [ binding.tag ]), [fired, setFired] = react.useState(!1), [settled, setSettled] = react.useState(!1), [failure, setFailure] = react.useState(void 0), firedRef = react.useRef(!1), startAll = react.useCallback(async (values, mode) => {
8787
9306
  if (!firedRef.current) {
8788
9307
  firedRef.current = !0, setFired(!0);
8789
9308
  try {
@@ -8795,20 +9314,25 @@ function AutoStartDriver(props) {
8795
9314
  releaseId: selectedReleaseId
8796
9315
  } : {}
8797
9316
  });
9317
+ const requests = buildAutoStartRequests({
9318
+ workflows: pendingRef.current,
9319
+ definitions: autoStartDefinitions,
9320
+ subject: {
9321
+ docId: docId,
9322
+ type: docType
9323
+ },
9324
+ contentResource: binding.contentResource,
9325
+ values: values,
9326
+ instanceIdFor: instanceIdFor
9327
+ });
9328
+ telemetry2.log(WorkflowAutoStartRan, {
9329
+ mode: mode,
9330
+ workflowCount: requests.length
9331
+ });
8798
9332
  const {failure: failure2} = await runStarts({
8799
9333
  engine: engine,
8800
9334
  seed: seedInstance,
8801
- requests: buildAutoStartRequests({
8802
- workflows: pendingRef.current,
8803
- definitions: autoStartDefinitions,
8804
- subject: {
8805
- docId: docId,
8806
- type: docType
8807
- },
8808
- contentResource: binding.contentResource,
8809
- values: values,
8810
- instanceIdFor: instanceIdFor
8811
- })
9335
+ requests: requests
8812
9336
  });
8813
9337
  failure2 !== void 0 && setFailure(failure2);
8814
9338
  } catch (err) {
@@ -8817,17 +9341,17 @@ function AutoStartDriver(props) {
8817
9341
  setSettled(!0);
8818
9342
  }
8819
9343
  }
8820
- }, [ autoStartDefinitions, docId, docType, binding, engine, observer, seedInstance, instanceIdFor, selectedReleaseId ]), retry = react.useCallback(() => {
9344
+ }, [ autoStartDefinitions, docId, docType, binding, engine, observer, seedInstance, instanceIdFor, selectedReleaseId, telemetry2 ]), retry = react.useCallback(() => {
8821
9345
  firedRef.current = !1, setFired(!1), setSettled(!1), setFailure(void 0);
8822
9346
  }, []), action = autoStartAction({
8823
- resolved: isDocResolved(docId),
9347
+ resolved: definitionsResolved && isDocResolved(docId),
8824
9348
  pendingCount: pending.length,
8825
9349
  needsInput: inputs.length > 0,
8826
9350
  fired: fired,
8827
9351
  settled: settled
8828
9352
  });
8829
9353
  return react.useEffect(() => {
8830
- action === "silent-start" && startAll({});
9354
+ action === "silent-start" && startAll({}, "silent");
8831
9355
  }, [ action, startAll ]), failure !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(AutoStartFailure, {
8832
9356
  message: failure,
8833
9357
  onProceed: () => setFailure(void 0),
@@ -8839,7 +9363,7 @@ function AutoStartDriver(props) {
8839
9363
  docType: docType,
8840
9364
  inputs: inputs,
8841
9365
  onStart: values => {
8842
- startAll(values);
9366
+ startAll(values, "collect");
8843
9367
  },
8844
9368
  starting: fired,
8845
9369
  workflowCount: pending.length
@@ -9061,7 +9585,9 @@ function finishedLinesOf(entries) {
9061
9585
  }
9062
9586
 
9063
9587
  function WorkflowFormStrip(props) {
9064
- const {mappings: mappings} = props, {isDocResolved: isDocResolved, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {docId: docId, entries: entries} = useWorkflowsForDocument(props.value?._id ?? null), {params: params, setParams: setParams} = structure.usePaneRouter(), openWorkflowsView = focusId => setParams(focusedViewParams(params, focusId)), active = entries.filter(isLiveEntry), finished = finishedLinesOf(entries), invalid = surfacedInvalid(discoveryInvalid, entries), resolved = docId !== void 0 && isDocResolved(docId);
9588
+ const {mappings: mappings} = props, {isDocResolved: isDocResolved, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {docId: docId, entries: entries} = useWorkflowsForDocument(props.value?._id ?? null), {params: params, setParams: setParams} = structure.usePaneRouter(), telemetry2 = workflowReact.useWorkflowTelemetry(), openWorkflowsView = focusId => {
9589
+ telemetry2.log(WorkflowFormStripClicked), setParams(focusedViewParams(params, focusId));
9590
+ }, active = entries.filter(isLiveEntry), finished = finishedLinesOf(entries), invalid = surfacedInvalid(discoveryInvalid, entries), resolved = docId !== void 0 && isDocResolved(docId);
9065
9591
  /* @__PURE__ */
9066
9592
  return jsxRuntime.jsxs(ui.Stack, {
9067
9593
  gap: 4,
@@ -9095,7 +9621,8 @@ function StripLines({active: active, docId: docId, finished: finished, initialVa
9095
9621
  initialValue: initialValue,
9096
9622
  label: "Start workflow",
9097
9623
  mappings: mappings,
9098
- mode: "bleed"
9624
+ mode: "bleed",
9625
+ source: "form-strip"
9099
9626
  })
9100
9627
  }) : null ]
9101
9628
  });
@@ -9242,6 +9769,61 @@ function TaskCountSpinner({entry: entry}) {
9242
9769
  });
9243
9770
  }
9244
9771
 
9772
+ function stagePillState(instance) {
9773
+ const definition = definitionSnapshotOf(instance), stage = definition?.stages.find(candidate => candidate.name === instance.currentStage), state = workflowEngine.terminalState(instance);
9774
+ return {
9775
+ title: stageTitle(definition, instance.currentStage),
9776
+ terminal: stage !== void 0 && workflowEngine.isTerminalStage(stage) || state !== "in-flight",
9777
+ state: state,
9778
+ stageKnown: stage !== void 0
9779
+ };
9780
+ }
9781
+
9782
+ function WorkflowStagePreview(props) {
9783
+ const {mappings: mappings} = useWorkflowContext(), mapped = props.schemaType !== void 0 && mappingsForDocType(mappings, props.schemaType.name).length > 0, {entries: entries} = useCommittedWorkflowsForDocument(mapped ? props._id ?? null : null), lead = entries.find(isLiveEntry) ?? entries[0], extraLive = entries.filter(e => e !== lead && isLiveEntry(e)).length;
9784
+ return props.renderDefault({
9785
+ ...props,
9786
+ status: lead ? /* @__PURE__ */ jsxRuntime.jsx(StagePill, {
9787
+ entry: lead,
9788
+ extraLive: extraLive
9789
+ }) : props.status
9790
+ });
9791
+ }
9792
+
9793
+ function StagePill({entry: entry, extraLive: extraLive}) {
9794
+ /* @__PURE__ */
9795
+ return jsxRuntime.jsxs(ui.Flex, {
9796
+ align: "center",
9797
+ gap: 2,
9798
+ children: [
9799
+ /* @__PURE__ */ jsxRuntime.jsx(LeadBadge, {
9800
+ entry: entry
9801
+ }), extraLive > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, {
9802
+ fontSize: 1,
9803
+ mode: "outline",
9804
+ tone: "default",
9805
+ children: [ "+", extraLive ]
9806
+ }) : null ]
9807
+ });
9808
+ }
9809
+
9810
+ function LeadBadge({entry: entry}) {
9811
+ const {instance: instance} = entry;
9812
+ if (workflowEngine.isUnprimed(instance)) /* @__PURE__ */ return jsxRuntime.jsx(StartIncompleteBadge, {});
9813
+ const pill = stagePillState(instance);
9814
+ /* @__PURE__ */
9815
+ return jsxRuntime.jsxs(ui.Badge, {
9816
+ fontSize: 1,
9817
+ mode: pill.terminal ? "default" : "outline",
9818
+ tone: pillTone(pill),
9819
+ children: [ pill.title, pill.state === "completed" ? " ✓" : "" ]
9820
+ });
9821
+ }
9822
+
9823
+ function pillTone(args) {
9824
+ return args.state === "aborted" ? "critical" : args.terminal ? "positive" : args.stageKnown ? "primary" : "default";
9825
+ }
9826
+
9245
9827
  function StartWorkflowDialogHost() {
9246
9828
  const {startRequest: startRequest2, closeStartDialog: closeStartDialog} = useWorkflowContext();
9247
9829
  return startRequest2 ? /* @__PURE__ */ jsxRuntime.jsx(StartWorkflowDialog, {
@@ -9316,7 +9898,8 @@ function withWorkflowLock(Action, guardAction) {
9316
9898
  Locked;
9317
9899
  }
9318
9900
 
9319
- const workflowStudioPlugin = sanity.definePlugin(config => ({
9901
+ const workflowStudioPlugin = sanity.definePlugin(config => (assertUniqueWorkflowMappings(config.mappings ?? []),
9902
+ {
9320
9903
  name: "workflow-studio-plugin",
9321
9904
  tools: prev => [ ...prev, workflowsTool ],
9322
9905
  studio: {
@@ -9330,84 +9913,37 @@ const workflowStudioPlugin = sanity.definePlugin(config => ({
9330
9913
  },
9331
9914
  form: {
9332
9915
  components: {
9333
- input: props => {
9334
- if (props.id === "root" && sanity.isObjectInputProps(props)) {
9335
- const docTypeMappings = mappingsForDocType(config.mappings, props.schemaType.name), fallback = docTypeMappings.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(WorkflowFormStrip, {
9336
- ...props,
9337
- mappings: docTypeMappings
9338
- }) : props.renderDefault(props);
9339
- /* @__PURE__ */
9340
- return jsxRuntime.jsx(AutoStartGate, {
9341
- ...props,
9342
- fallback: fallback
9343
- });
9344
- }
9345
- return props.renderDefault(props);
9346
- }
9916
+ preview: WorkflowStagePreview,
9917
+ input: props => props.id === "root" && sanity.isObjectInputProps(props) ? /* @__PURE__ */ jsxRuntime.jsx(WorkflowRootInput, {
9918
+ ...props
9919
+ }) : props.renderDefault(props)
9347
9920
  }
9348
9921
  },
9349
9922
  document: {
9350
9923
  badges: (prev, ctx) => {
9351
- if (!mappingForDocType(config.mappings, ctx.schemaType) || !ctx.documentId) return prev;
9924
+ if (!ctx.documentId) return prev;
9352
9925
  const docId = ctx.documentId;
9353
9926
  return [ makeActiveWorkflowsBadge(docId), makePerspectiveBadge(docId), ...prev ];
9354
9927
  },
9355
- actions: (prev, ctx) => mappingForDocType(config.mappings, ctx.schemaType) ? prev.map(Action => {
9928
+ actions: prev => prev.map(Action => {
9356
9929
  const guardAction = Action.action ? LOCKABLE_ACTIONS.get(Action.action) : void 0;
9357
9930
  return guardAction ? withWorkflowLock(Action, guardAction) : Action;
9358
- }) : prev
9931
+ })
9359
9932
  }
9360
9933
  }));
9361
9934
 
9362
- function WorkflowStagePreview(props) {
9363
- const {entries: entries} = useWorkflowsForDocument(props.documentId ?? props._id ?? null), lead = entries.find(isLiveEntry) ?? entries[0], extraLive = entries.filter(e => e !== lead && isLiveEntry(e)).length;
9364
- return props.renderDefault({
9935
+ function WorkflowRootInput(props) {
9936
+ const {mappings: mappings} = useWorkflowContext(), docTypeMappings = mappingsForDocType(mappings, props.schemaType.name), fallback = docTypeMappings.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(WorkflowFormStrip, {
9365
9937
  ...props,
9366
- status: lead ? /* @__PURE__ */ jsxRuntime.jsx(StagePill, {
9367
- entry: lead,
9368
- extraLive: extraLive
9369
- }) : props.status
9370
- });
9371
- }
9372
-
9373
- function StagePill({entry: entry, extraLive: extraLive}) {
9374
- /* @__PURE__ */
9375
- return jsxRuntime.jsxs(ui.Flex, {
9376
- align: "center",
9377
- gap: 2,
9378
- children: [
9379
- /* @__PURE__ */ jsxRuntime.jsx(LeadBadge, {
9380
- entry: entry
9381
- }), extraLive > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, {
9382
- fontSize: 1,
9383
- mode: "outline",
9384
- tone: "default",
9385
- children: [ "+", extraLive ]
9386
- }) : null ]
9387
- });
9388
- }
9389
-
9390
- function LeadBadge({entry: entry}) {
9391
- const {instance: instance, evaluation: evaluation} = entry;
9392
- if (workflowEngine.isUnprimed(instance)) /* @__PURE__ */ return jsxRuntime.jsx(StartIncompleteBadge, {});
9393
- const stage = evaluation?.currentStage?.stage, title = stage?.title ?? stageTitle(definitionSnapshotOf(instance), instance.currentStage), state = workflowEngine.terminalState(instance), isTerminal = (stage ? workflowEngine.isTerminalStage(stage) : !1) || state !== "in-flight";
9938
+ mappings: docTypeMappings
9939
+ }) : props.renderDefault(props);
9394
9940
  /* @__PURE__ */
9395
- return jsxRuntime.jsxs(ui.Badge, {
9396
- fontSize: 1,
9397
- mode: isTerminal ? "default" : "outline",
9398
- tone: pillTone({
9399
- state: state,
9400
- isTerminal: isTerminal,
9401
- stageKnown: stage !== void 0
9402
- }),
9403
- children: [ title, state === "completed" ? " ✓" : "" ]
9941
+ return jsxRuntime.jsx(AutoStartGate, {
9942
+ ...props,
9943
+ fallback: fallback
9404
9944
  });
9405
9945
  }
9406
9946
 
9407
- function pillTone(args) {
9408
- return args.state === "aborted" ? "critical" : args.isTerminal ? "positive" : args.stageKnown ? "primary" : "default";
9409
- }
9410
-
9411
9947
  exports.AbortedBadge = AbortedBadge;
9412
9948
 
9413
9949
  exports.ActivitiesList = ActivitiesList;
@@ -9418,6 +9954,8 @@ exports.ActivityRow = ActivityRow;
9418
9954
 
9419
9955
  exports.BreadcrumbTail = BreadcrumbTail;
9420
9956
 
9957
+ exports.CautionNote = CautionNote;
9958
+
9421
9959
  exports.CollapsibleBand = CollapsibleBand;
9422
9960
 
9423
9961
  exports.DismissablePopover = DismissablePopover;
@@ -9436,6 +9974,8 @@ exports.LinkChip = LinkChip;
9436
9974
 
9437
9975
  exports.LoadingRow = LoadingRow;
9438
9976
 
9977
+ exports.SpinnerSlot = SpinnerSlot;
9978
+
9439
9979
  exports.StageChip = StageChip;
9440
9980
 
9441
9981
  exports.StageFace = StageFace;
@@ -9450,7 +9990,13 @@ exports.TrailingHairline = TrailingHairline;
9450
9990
 
9451
9991
  exports.UnreadableDocsNote = UnreadableDocsNote;
9452
9992
 
9453
- exports.WorkflowStagePreview = WorkflowStagePreview;
9993
+ exports.WORKFLOW_API_VERSION = WORKFLOW_API_VERSION;
9994
+
9995
+ exports.WorkflowInstanceDetailViewed = WorkflowInstanceDetailViewed;
9996
+
9997
+ exports.WorkflowTaskFiltersApplied = WorkflowTaskFiltersApplied;
9998
+
9999
+ exports.WorkflowToolOpened = WorkflowToolOpened;
9454
10000
 
9455
10001
  exports.activityRowProps = activityRowProps;
9456
10002
 
@@ -9466,8 +10012,6 @@ exports.definitionSnapshotOf = definitionSnapshotOf;
9466
10012
 
9467
10013
  exports.describeError = describeError;
9468
10014
 
9469
- exports.editTargetOf = editTargetOf;
9470
-
9471
10015
  exports.findActivity = findActivity;
9472
10016
 
9473
10017
  exports.findActivityNode = findActivityNode;
@@ -9488,8 +10032,6 @@ exports.isEvaluationStale = isEvaluationStale;
9488
10032
 
9489
10033
  exports.isOpenActivityStatus = isOpenActivityStatus;
9490
10034
 
9491
- exports.mappingForDocType = mappingForDocType;
9492
-
9493
10035
  exports.openActivityGone = openActivityGone;
9494
10036
 
9495
10037
  exports.parseDateFieldValue = parseDateFieldValue;
@@ -9514,8 +10056,12 @@ exports.stopRowMouseDown = stopRowMouseDown;
9514
10056
 
9515
10057
  exports.useAssignmentIdentity = useAssignmentIdentity;
9516
10058
 
10059
+ exports.useClosableToast = useClosableToast;
10060
+
9517
10061
  exports.useDefinition = useDefinition;
9518
10062
 
10063
+ exports.useLogEventOnMount = useLogEventOnMount;
10064
+
9519
10065
  exports.useProjectMembers = useProjectMembers;
9520
10066
 
9521
10067
  exports.useSaveField = useSaveField;
@@ -9528,8 +10074,6 @@ exports.useWorkflowInstanceEntry = useWorkflowInstanceEntry;
9528
10074
 
9529
10075
  exports.workflowDefaultDocumentNode = workflowDefaultDocumentNode;
9530
10076
 
9531
- exports.workflowDocTypes = workflowDocTypes;
9532
-
9533
10077
  exports.workflowStudioPlugin = workflowStudioPlugin;
9534
10078
 
9535
10079
  exports.workflowsTabCodec = workflowsTabCodec;