@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,37 +1,41 @@
1
- import { tryParseGdr, resourceFromParsed, latestDeployedDefinitions, gdrRef, isSubjectEntry, isInputSourced, missingRequiredInputs, initialFieldIssues, releaseRef, refKindAcceptsTypes, rejectedRefTypes, ActionDisabledError, MutationGuardDeniedError, errorMessage, findOpenStageEntry, actionRendering, terminalState, ENGINE_API_VERSION, WORKFLOW_INSTANCE_TYPE, WORKFLOW_DEFINITION_TYPE, GUARD_DOC_TYPE, isTodoListEntry, isTodoListItem, isTerminalActivityStatus, describeCondition, sentenceCase, checklistLines, findCurrentActivityEntry, scalarValidationIssues, extractDocumentId, isSingleDocRefEntry, isGdr, resolveFieldEntry, isSingleDocRefKind, isNotesEntry, parseDefinitionSnapshot, StartNotSettledError, StartNotAllowedError, isUnprimed, unboundAllowedReads, explainStartAllowed, evaluateStartFilter, startKindOf, readsRootDocument, instancesQuery, assertReadableModel, isTerminalStage, DEFAULT_TRANSITION_WHEN, isStartableDefinition, acceptsDocumentType, definitionsListGroq, gdrFromResource, instanceWatchesDocument, aclPathForResource, definitionLookupGroq, instanceDocId, documentActionDenials } from "@sanity/workflow-engine";
2
-
3
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
4
2
 
5
3
  import { TimelineIcon } from "@sanity/icons/Timeline";
6
4
 
7
5
  import { Tooltip, Box, Stack, Text, useClickOutsideEvent, Popover, Flex, TextInput, Button, Select, Checkbox, Autocomplete, Card, TextArea, useToast, Dialog, Switch, TextSkeleton, Code, Spinner, useTheme_v2, Badge, MenuButton, Menu, MenuItem, useElementSize, TabList, Tab, TabPanel, MenuDivider } from "@sanity/ui";
8
6
 
9
- import { createContext, useContext, useEffect, useSyncExternalStore, useCallback, useMemo, useRef, useState, Fragment as Fragment$1, createElement, isValidElement, useId, useLayoutEffect, Component, lazy, Suspense } from "react";
7
+ import { editFieldTarget, useWorkflowTelemetry, useRefcountedIds } from "@sanity/workflow-react";
8
+
9
+ import { createContext, useContext, useEffect, useSyncExternalStore, useCallback, useRef, useMemo, useState, Fragment as Fragment$1, createElement, isValidElement, useId, useLayoutEffect, Component, lazy, Suspense } from "react";
10
10
 
11
11
  import { usePaneRouter } from "sanity/structure";
12
12
 
13
+ import { tryParseGdr, resourceFromParsed, latestDeployedDefinitions, isStartableDefinition, isSubjectEntry, isInputSourced, acceptsDocumentType, missingRequiredInputs, initialFieldIssues, gdrRef, releaseRef, refKindAcceptsTypes, rejectedRefTypes, ActionDisabledError, MutationGuardDeniedError, errorMessage, findOpenStageEntry, actionRendering, terminalState, isTodoListEntry, isTodoListItem, isTerminalActivityStatus, describeCondition, sentenceCase, checklistLines, findCurrentActivityEntry, ENGINE_API_VERSION, WORKFLOW_INSTANCE_TYPE, WORKFLOW_DEFINITION_TYPE, GUARD_DOC_TYPE, scalarValidationIssues, classifyPrincipalId, toBareId, isSingleDocRefEntry, isGdr, resolveFieldEntry, isSingleDocRefKind, isNotesEntry, parseDefinitionSnapshot, instancesQuery, assertReadableModel, projectStartSliceRow, isUnprimed, StartNotSettledError, StartNotAllowedError, humanize, unboundRequirementReads, explainStartRequirement, singleSubjectRequirementRefused, evaluateStartFilter, startKindOf, hasSingleSubjectRequirement, readsRootDocument, isTerminalStage, DEFAULT_TRANSITION_WHEN, latestDefinitionsGroq, gdrFromResource, subscriptionDocumentsForInstance, aclPathForResource, definitionLookupGroq, instanceDocId, documentActionDenials } from "@sanity/workflow-engine";
14
+
15
+ import { CheckmarkIcon } from "@sanity/icons/Checkmark";
16
+
17
+ import { CloseIcon } from "@sanity/icons/Close";
18
+
13
19
  import { formatDistanceToNow } from "date-fns/formatDistanceToNow";
14
20
 
15
- import { getPublishedId } from "@sanity/client/csm";
21
+ import { sameAssignee, projectMemberRow, ClearableDatePicker, MemberPicker as MemberPicker$1, AssigneePicker as AssigneePicker$1, MemberAvatar, AssigneeBadges as AssigneeBadges$1, AssigneeBadge as AssigneeBadge$1, ProgressBar, MemberAvatarGroup, clampProgress, progressCaption, DatePicker, roleListLabel } from "@sanity/workflow-components";
22
+
23
+ import { getPublishedId, getDraftId } from "@sanity/client/csm";
24
+
25
+ import { defineEvent } from "@sanity/telemetry";
16
26
 
17
27
  import { useStudioProjectUsers, useStudioObserver, useWorkflowEngine, useWorkflowInstances, useWorkflowSession } from "@sanity/workflow-studio";
18
28
 
19
- import { useCurrentUser, SanityDefaultPreview, Preview, useValuePreview, useClient, useSchema, useWorkspace, TextWithTone, usePerspective, useIsReleaseActive, useActiveReleases, useTemplates, useInitialValueResolverContext, resolveInitialValue, definePlugin, isObjectInputProps } from "sanity";
29
+ import { useCurrentUser, SanityDefaultPreview, Preview, useValuePreview, useClient, useSchema, useWorkspace, TextWithTone, usePerspective, useIsReleaseActive, useDocumentPreviewStore, useActiveReleases, useTemplates, useInitialValueResolverContext, resolveInitialValue, definePlugin, isObjectInputProps } from "sanity";
20
30
 
21
31
  import { CalendarIcon } from "@sanity/icons/Calendar";
22
32
 
23
- import { ClearableDatePicker, MemberPicker as MemberPicker$1, AssigneePicker as AssigneePicker$1, MemberAvatar, AssigneeBadges as AssigneeBadges$1, AssigneeBadge as AssigneeBadge$1, ProgressBar, MemberAvatarGroup, clampProgress, progressCaption, DatePicker, roleListLabel } from "@sanity/workflow-components";
24
-
25
33
  import { UserIcon } from "@sanity/icons/User";
26
34
 
27
- import { CloseIcon } from "@sanity/icons/Close";
28
-
29
35
  import { SearchIcon } from "@sanity/icons/Search";
30
36
 
31
37
  import { ChevronRightIcon } from "@sanity/icons/ChevronRight";
32
38
 
33
- import { CheckmarkIcon } from "@sanity/icons/Checkmark";
34
-
35
39
  import { EditIcon } from "@sanity/icons/Edit";
36
40
 
37
41
  import { randomKey } from "@sanity/util/content";
@@ -70,7 +74,9 @@ import { EllipsisHorizontalIcon } from "@sanity/icons/EllipsisHorizontal";
70
74
 
71
75
  import { ArrowUpIcon } from "@sanity/icons/ArrowUp";
72
76
 
73
- import { useRefcountedIds } from "@sanity/workflow-react";
77
+ import { of } from "rxjs";
78
+
79
+ import { switchMap, distinctUntilChanged } from "rxjs/operators";
74
80
 
75
81
  import { TransferIcon } from "@sanity/icons/Transfer";
76
82
 
@@ -87,20 +93,58 @@ function gdrLocality(id, contentResource) {
87
93
  };
88
94
  }
89
95
 
90
- function mappingsForDocType(mappings, docType) {
91
- return mappings.filter(m => m.docType === docType);
96
+ function assertUniqueWorkflowMappings(mappings) {
97
+ const keys = /* @__PURE__ */ new Set;
98
+ for (const mapping of mappings) {
99
+ const key = mappingKey(mapping);
100
+ if (keys.has(key)) throw new Error(`Duplicate workflow mapping for ${key}`);
101
+ keys.add(key);
102
+ }
103
+ }
104
+
105
+ function discoverWorkflowMappings(args) {
106
+ assertUniqueWorkflowMappings(args.overrides ?? []);
107
+ const discovered = latestDeployedDefinitions(args.definitions).flatMap(definition => {
108
+ if (!isStartableDefinition(definition)) return [];
109
+ const subject = (definition.fields ?? []).find(isSubjectEntry);
110
+ if (subject === void 0 || !isInputSourced(subject)) return [];
111
+ const source = {
112
+ fields: [ ...definition.fields ?? [] ]
113
+ };
114
+ return [ ...args.schemaContentTypes ].filter(docType => acceptsDocumentType(source, docType)).map(docType => ({
115
+ docType: docType,
116
+ definition: definition.name,
117
+ label: definition.title ?? definition.name
118
+ }));
119
+ }), byKey = new Map(discovered.map(mapping => [ mappingKey(mapping), mapping ]));
120
+ for (const override of args.overrides ?? []) {
121
+ const key = mappingKey(override);
122
+ byKey.set(key, override);
123
+ }
124
+ return [ ...byKey.values() ];
92
125
  }
93
126
 
94
- function mappingForDocType(mappings, docType) {
95
- return mappingsForDocType(mappings, docType)[0];
127
+ function mappingAutoStartMap(mappings) {
128
+ const byType = /* @__PURE__ */ Object.create(null);
129
+ for (const mapping of mappings) {
130
+ if (!mapping.autoStart) continue;
131
+ const definitions = byType[mapping.docType] ?? [];
132
+ byType[mapping.docType] = [ ...definitions, mapping.definition ];
133
+ }
134
+ return byType;
135
+ }
136
+
137
+ function mappingsForDocType(mappings, docType) {
138
+ return mappings.filter(m => m.docType === docType);
96
139
  }
97
140
 
98
141
  function mappingKey(mapping) {
99
142
  return `${mapping.docType}::${mapping.definition}`;
100
143
  }
101
144
 
102
- function workflowDocTypes(mappings) {
103
- return new Set(mappings.map(m => m.docType));
145
+ function documentSubjectEntry(definition) {
146
+ const fields = definition.fields ?? [];
147
+ return fields.find(isSubjectEntry) ?? fields.find(entry => entry.type === "doc.ref" && entry.name === "subject");
104
148
  }
105
149
 
106
150
  function mappingStartBlocks(args) {
@@ -421,7 +465,7 @@ function stateByActivity(instance) {
421
465
  }
422
466
 
423
467
  function evaluationFreshness(args) {
424
- return args.evaluation ? args.committed.lastChangedAt > args.evaluation.instance.lastChangedAt ? "stale" : "current" : "pending";
468
+ return args.evaluation ? Date.parse(args.committed.lastChangedAt) > Date.parse(args.evaluation.instance.lastChangedAt) ? "stale" : "current" : "pending";
425
469
  }
426
470
 
427
471
  function isEvaluationCurrent(args) {
@@ -447,50 +491,6 @@ function isLiveEntry(entry) {
447
491
  return terminalState(entry.instance) === "in-flight";
448
492
  }
449
493
 
450
- const WORKFLOW_API_VERSION = ENGINE_API_VERSION, WORKFLOW_SYSTEM_TYPES = [ WORKFLOW_INSTANCE_TYPE, WORKFLOW_DEFINITION_TYPE, GUARD_DOC_TYPE ];
451
-
452
- function editTargetOf(field) {
453
- return {
454
- scope: field.scope,
455
- field: field.name,
456
- ...field.activity ? {
457
- activity: field.activity
458
- } : {}
459
- };
460
- }
461
-
462
- const EMPTY_ENTRIES = [], WorkflowContext = createContext(null);
463
-
464
- function useWorkflowContext() {
465
- const value = useContext(WorkflowContext);
466
- if (!value) throw new Error("useWorkflowContext must render inside the workflow plugin provider");
467
- return value;
468
- }
469
-
470
- function useWorkflowsForDocument(rawId) {
471
- const {register: register, unregister: unregister, entries: entries} = useWorkflowContext(), docId = rawId ? getPublishedId(rawId) : void 0;
472
- useEffect(() => {
473
- if (docId) return register(docId), () => unregister(docId);
474
- }, [ docId, register, unregister ]);
475
- const list = useSyncExternalStore(entries.subscribe, useCallback(() => docId ? entries.getDocument(docId) : EMPTY_ENTRIES, [ entries, docId ]));
476
- return {
477
- docId: docId,
478
- entries: list
479
- };
480
- }
481
-
482
- function useHeldWorkflowEntry(instanceId) {
483
- const {entries: entries} = useWorkflowContext();
484
- return useSyncExternalStore(entries.subscribe, useCallback(() => instanceId ? entries.getInstance(instanceId) : void 0, [ entries, instanceId ]));
485
- }
486
-
487
- function useWorkflowInstanceEntry(instanceId) {
488
- const {requestInstance: requestInstance, releaseInstance: releaseInstance} = useWorkflowContext();
489
- return useEffect(() => {
490
- if (instanceId) return requestInstance(instanceId), () => releaseInstance(instanceId);
491
- }, [ instanceId, requestInstance, releaseInstance ]), useHeldWorkflowEntry(instanceId);
492
- }
493
-
494
494
  const isTodoDone = item => item.status === "done", toggledTodoStatus = item => isTodoDone(item) ? "open" : "done";
495
495
 
496
496
  function isTodoOverdue(item, today = /* @__PURE__ */ new Date) {
@@ -566,7 +566,7 @@ function buildTickIndex(evaluation) {
566
566
  function todoFieldEditability(args) {
567
567
  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));
568
568
  return match ? match.editable ? {
569
- editTarget: editTargetOf(match)
569
+ editTarget: editFieldTarget(match)
570
570
  } : match.disabledReason === void 0 ? {} : {
571
571
  editDisabledReason: match.disabledReason
572
572
  } : {};
@@ -631,8 +631,8 @@ function assigneeMatches(member, who) {
631
631
  return member.type === "user" ? member.id === who.userId : who.roles.includes(member.role);
632
632
  }
633
633
 
634
- function sameAssignee(a, b) {
635
- return a.type === "user" && b.type === "user" ? a.id === b.id : a.type === "role" && b.type === "role" ? a.role === b.role : !1;
634
+ function changedAssignee(current, next) {
635
+ return next.find(item => !current.some(existing => sameAssignee(item, existing))) ?? current.find(item => !next.some(existing => sameAssignee(item, existing)));
636
636
  }
637
637
 
638
638
  function isAssigneeShape(value) {
@@ -761,13 +761,29 @@ function concludesHere(actionEval, activityName) {
761
761
  return changesOwnStatus(actionEval.action, activityName) || actionEval.firing?.exitsStage === !0;
762
762
  }
763
763
 
764
- function actionButtonTone(args) {
765
- return concludesHere(args.actionEval, args.activityName) ? {
766
- mode: "default",
767
- tone: actionAdvanceStatus(args.actionEval.action, args.activityName) === "failed" ? "critical" : "default"
768
- } : {
769
- mode: "ghost",
770
- tone: "default"
764
+ const SEMANTIC_FACE = {
765
+ "decision.accept": {
766
+ tone: "positive",
767
+ icon: CheckmarkIcon
768
+ },
769
+ "decision.decline": {
770
+ tone: "caution",
771
+ icon: CloseIcon
772
+ }
773
+ };
774
+
775
+ function actionButtonFace(args) {
776
+ const mode = concludesHere(args.actionEval, args.activityName) ? "default" : "ghost";
777
+ if (actionAdvanceStatus(args.actionEval.action, args.activityName) === "failed") return {
778
+ mode: mode,
779
+ tone: "critical",
780
+ icon: void 0
781
+ };
782
+ const [semantic] = args.actionEval.semantics ?? [], face = semantic === void 0 ? void 0 : SEMANTIC_FACE[semantic];
783
+ return {
784
+ mode: mode,
785
+ tone: face?.tone ?? "default",
786
+ icon: face?.icon
771
787
  };
772
788
  }
773
789
 
@@ -991,6 +1007,112 @@ function deriveActivityDetail(args) {
991
1007
  };
992
1008
  }
993
1009
 
1010
+ const WORKFLOW_API_VERSION = ENGINE_API_VERSION, WORKFLOW_SYSTEM_TYPES = [ WORKFLOW_INSTANCE_TYPE, WORKFLOW_DEFINITION_TYPE, GUARD_DOC_TYPE ], EMPTY_ENTRIES = [], WorkflowContext = createContext(null);
1011
+
1012
+ function useWorkflowContext() {
1013
+ const value = useContext(WorkflowContext);
1014
+ if (!value) throw new Error("useWorkflowContext must render inside the workflow plugin provider");
1015
+ return value;
1016
+ }
1017
+
1018
+ function useWorkflowsForDocument(rawId) {
1019
+ return useDocumentWorkflowEntries(rawId, !0);
1020
+ }
1021
+
1022
+ function useCommittedWorkflowsForDocument(rawId) {
1023
+ return useDocumentWorkflowEntries(rawId, !1);
1024
+ }
1025
+
1026
+ function useDocumentWorkflowEntries(rawId, evaluate) {
1027
+ const {register: register, unregister: unregister, requestEvaluation: requestEvaluation, releaseEvaluation: releaseEvaluation, entries: entries} = useWorkflowContext(), docId = rawId ? getPublishedId(rawId) : void 0;
1028
+ useEffect(() => {
1029
+ if (docId) return register(docId), () => unregister(docId);
1030
+ }, [ docId, register, unregister ]);
1031
+ const list = useSyncExternalStore(entries.subscribe, useCallback(() => docId ? entries.getDocument(docId) : EMPTY_ENTRIES, [ entries, docId ])), evaluationKey = evaluate ? list.map(entry => entry.instance._id).join("\0") : "";
1032
+ return useEffect(() => {
1033
+ if (!evaluate) return;
1034
+ const ids = evaluationKey === "" ? [] : evaluationKey.split("\0");
1035
+ for (const id of ids) requestEvaluation(id);
1036
+ return () => {
1037
+ for (const id of ids) releaseEvaluation(id);
1038
+ };
1039
+ }, [ evaluate, evaluationKey, requestEvaluation, releaseEvaluation ]), {
1040
+ docId: docId,
1041
+ entries: list
1042
+ };
1043
+ }
1044
+
1045
+ function useHeldWorkflowEntry(instanceId) {
1046
+ const {entries: entries} = useWorkflowContext();
1047
+ return useSyncExternalStore(entries.subscribe, useCallback(() => instanceId ? entries.getInstance(instanceId) : void 0, [ entries, instanceId ]));
1048
+ }
1049
+
1050
+ function useWorkflowInstanceEntry(instanceId, options = {}) {
1051
+ const {requestInstance: requestInstance, releaseInstance: releaseInstance, requestEvaluation: requestEvaluation, releaseEvaluation: releaseEvaluation} = useWorkflowContext(), evaluate = options.evaluate ?? !0;
1052
+ return useEffect(() => {
1053
+ if (instanceId) return requestInstance(instanceId), () => releaseInstance(instanceId);
1054
+ }, [ instanceId, requestInstance, releaseInstance ]), useEffect(() => {
1055
+ if (!(!instanceId || !evaluate)) return requestEvaluation(instanceId), () => releaseEvaluation(instanceId);
1056
+ }, [ instanceId, evaluate, requestEvaluation, releaseEvaluation ]), useHeldWorkflowEntry(instanceId);
1057
+ }
1058
+
1059
+ const WorkflowToolOpened = defineEvent({
1060
+ name: "Editorial Workflows Studio Plugin Tool Opened",
1061
+ version: 1,
1062
+ description: "The Workflows tool was opened or its tab was switched"
1063
+ }), WorkflowDocumentViewOpened = defineEvent({
1064
+ name: "Editorial Workflows Studio Plugin Document View Opened",
1065
+ version: 1,
1066
+ description: "The Workflows document view was opened or its tab was switched"
1067
+ }), WorkflowInstanceDetailViewed = defineEvent({
1068
+ name: "Editorial Workflows Studio Plugin Instance Detail Viewed",
1069
+ version: 1,
1070
+ description: "The Workflows tool's per-instance detail view was opened"
1071
+ }), WorkflowFormStripClicked = defineEvent({
1072
+ name: "Editorial Workflows Studio Plugin Form Strip Clicked",
1073
+ version: 1,
1074
+ description: "The form strip's instance line was clicked, opening the document view"
1075
+ }), WorkflowStartDialogOpened = defineEvent({
1076
+ name: "Editorial Workflows Studio Plugin Start Dialog Opened",
1077
+ version: 1,
1078
+ description: "The start-workflow dialog was opened"
1079
+ }), WorkflowStartDialogSubmitted = defineEvent({
1080
+ name: "Editorial Workflows Studio Plugin Start Dialog Submitted",
1081
+ version: 1,
1082
+ description: "The start-workflow dialog's confirm was pressed"
1083
+ }), WorkflowAutoStartRan = defineEvent({
1084
+ name: "Editorial Workflows Studio Plugin Auto Start Ran",
1085
+ version: 1,
1086
+ description: "A fresh document's auto-start ran — one start request per configured workflow"
1087
+ }), WorkflowActionControlUsed = defineEvent({
1088
+ name: "Editorial Workflows Studio Plugin Action Control Used",
1089
+ version: 1,
1090
+ description: "An action-firing control was used, attributed to its UI surface"
1091
+ }), WorkflowActivityDialogOpened = defineEvent({
1092
+ name: "Editorial Workflows Studio Plugin Activity Dialog Opened",
1093
+ version: 1,
1094
+ description: "The activity detail dialog was opened"
1095
+ }), WorkflowTaskFiltersApplied = defineEvent({
1096
+ name: "Editorial Workflows Studio Plugin Task Filters Applied",
1097
+ version: 1,
1098
+ description: "The tool's task-filter menu closed with a changed selection"
1099
+ }), WorkflowTodoToggled = defineEvent({
1100
+ name: "Editorial Workflows Studio Plugin Todo Toggled",
1101
+ version: 1,
1102
+ description: "A todo checkbox was toggled, attributed to its UI surface and write seam"
1103
+ });
1104
+
1105
+ function useLogEventOnMount(event, data) {
1106
+ const telemetry = useWorkflowTelemetry(), logged = useRef(!1);
1107
+ useEffect(() => {
1108
+ logged.current || (logged.current = !0, telemetry.log(event, data));
1109
+ }, [ telemetry, event, data ]);
1110
+ }
1111
+
1112
+ function LogEventOnMount(props) {
1113
+ return useLogEventOnMount(props.event, props.data), null;
1114
+ }
1115
+
994
1116
  function isEmptyValue(value) {
995
1117
  return value == null || value === "" || Array.isArray(value) && value.length === 0;
996
1118
  }
@@ -1022,10 +1144,23 @@ function personActor(user) {
1022
1144
  };
1023
1145
  }
1024
1146
 
1147
+ const memberIndexes = /* @__PURE__ */ new WeakMap;
1148
+
1149
+ function memberIndex(members) {
1150
+ const cached = memberIndexes.get(members);
1151
+ if (cached) return cached;
1152
+ const created = new Map(members.map(member => [ member.id, member ]));
1153
+ return memberIndexes.set(members, created), created;
1154
+ }
1155
+
1025
1156
  const selfUserFor = (id, me) => me != null && me.id === id ? me : void 0;
1026
1157
 
1158
+ function globalIdOf(users, projectScopedId) {
1159
+ return users.find(user => user.membership.id === projectScopedId)?.profile?.sanityUserId ?? projectScopedId;
1160
+ }
1161
+
1027
1162
  function userDisplayFor(args) {
1028
- const member = args.members.find(m => m.id === args.id), self = selfUserFor(args.id, args.me);
1163
+ const matchId = args.matchId ?? args.id, member = memberIndex(args.members).get(matchId), self = selfUserFor(matchId, args.me);
1029
1164
  return {
1030
1165
  id: args.id,
1031
1166
  name: member?.displayName ?? self?.name ?? args.id,
@@ -1034,42 +1169,69 @@ function userDisplayFor(args) {
1034
1169
  }
1035
1170
 
1036
1171
  function useUserDisplays(ids) {
1037
- const {members: members} = useProjectMembers(), me = useCurrentUser();
1038
- return ids.map(id => userDisplayFor({
1172
+ const {members: members} = useProjectMembers(), {users: users} = useStudioProjectUsers(), meRaw = useCurrentUser(), me = useMemo(() => meRaw === null ? null : {
1173
+ ...meRaw,
1174
+ id: globalIdOf(users, meRaw.id)
1175
+ }, [ meRaw, users ]);
1176
+ return useMemo(() => ids.map(id => userDisplayFor({
1039
1177
  id: id,
1040
1178
  members: members,
1041
- me: me
1042
- }));
1179
+ me: me,
1180
+ matchId: globalIdOf(users, id)
1181
+ })), [ ids, me, members, users ]);
1043
1182
  }
1044
1183
 
1045
1184
  function useUserDisplay(id) {
1046
- const [display] = useUserDisplays([ id ]);
1185
+ const ids = useMemo(() => [ id ], [ id ]), [display] = useUserDisplays(ids);
1047
1186
  return display ?? {
1048
1187
  name: id,
1049
1188
  imageUrl: void 0
1050
1189
  };
1051
1190
  }
1052
1191
 
1192
+ const warnedNotMember = /* @__PURE__ */ new Set;
1193
+
1194
+ function useBridgedSelf() {
1195
+ const me = useCurrentUser(), {users: users, loading: loading} = useStudioProjectUsers();
1196
+ if (!me || loading) return;
1197
+ const classified = classifyPrincipalId(me.id), id = users.find(user => user.membership.id === me.id)?.profile?.sanityUserId ?? classified.globalId ?? (classified.namespace === "project" ? void 0 : me.id);
1198
+ if (id === void 0) {
1199
+ 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.`));
1200
+ return;
1201
+ }
1202
+ return {
1203
+ id: id,
1204
+ roles: me.roles ?? []
1205
+ };
1206
+ }
1207
+
1208
+ function useSelfActor() {
1209
+ const self = useBridgedSelf();
1210
+ return self === void 0 ? void 0 : personActor(self);
1211
+ }
1212
+
1053
1213
  function useAssignmentIdentity() {
1054
- const me = useCurrentUser();
1055
- if (me) return {
1056
- userId: me.id,
1057
- roles: (me.roles ?? []).map(r => r.name)
1214
+ const self = useBridgedSelf();
1215
+ if (self !== void 0) return {
1216
+ userId: self.id,
1217
+ roles: self.roles.map(r => r.name)
1058
1218
  };
1059
1219
  }
1060
1220
 
1221
+ const projectMembersByUsers = /* @__PURE__ */ new WeakMap;
1222
+
1061
1223
  function projectMembersFrom(users) {
1062
- return users.map(({membership: membership, profile: profile}) => ({
1063
- id: membership.id,
1064
- displayName: profile?.displayName ?? membership.id,
1065
- ...profile?.email ? {
1066
- email: profile.email
1067
- } : {},
1068
- ...profile?.imageUrl ? {
1069
- imageUrl: profile.imageUrl
1070
- } : {},
1071
- roles: (membership.roles ?? []).map(role => role.name)
1224
+ const cached = projectMembersByUsers.get(users);
1225
+ if (cached) return cached;
1226
+ const members = users.map(({membership: membership, profile: profile}) => projectMemberRow({
1227
+ membershipId: membership.id,
1228
+ roles: membership.roles,
1229
+ sanityUserId: profile?.sanityUserId,
1230
+ displayName: profile?.displayName,
1231
+ email: profile?.email,
1232
+ imageUrl: profile?.imageUrl ?? void 0
1072
1233
  }));
1234
+ return projectMembersByUsers.set(users, members), members;
1073
1235
  }
1074
1236
 
1075
1237
  function useProjectMembers() {
@@ -1395,12 +1557,12 @@ function MemberPicker({selectedIds: selectedIds, onSelect: onSelect, unassignRow
1395
1557
  });
1396
1558
  }
1397
1559
 
1398
- function AssigneePicker({value: value, onToggle: onToggle}) {
1560
+ function AssigneePicker({value: value, onChange: onChange}) {
1399
1561
  const state = useProjectMembers();
1400
1562
  /* @__PURE__ */
1401
1563
  return jsx(AssigneePicker$1, {
1402
1564
  ...state,
1403
- onToggle: onToggle,
1565
+ onChange: onChange,
1404
1566
  value: value
1405
1567
  });
1406
1568
  }
@@ -1511,7 +1673,7 @@ function DocPicker({value: value, onChange: onChange, types: types, readOnly: re
1511
1673
  children: /* @__PURE__ */ jsx(Box, {
1512
1674
  flex: 1,
1513
1675
  children: /* @__PURE__ */ jsx(PreviewOrId, {
1514
- id: extractDocumentId(value.id),
1676
+ id: toBareId(value.id),
1515
1677
  type: value.type
1516
1678
  })
1517
1679
  })
@@ -1607,7 +1769,7 @@ function DocRefsInput({value: value, onChange: onChange, types: types}) {
1607
1769
  /* @__PURE__ */ jsx(Box, {
1608
1770
  flex: 1,
1609
1771
  children: /* @__PURE__ */ jsx(PreviewOrId, {
1610
- id: extractDocumentId(ref.id),
1772
+ id: toBareId(ref.id),
1611
1773
  type: ref.type
1612
1774
  })
1613
1775
  }),
@@ -1730,7 +1892,7 @@ function useActionClusterPending() {
1730
1892
  }
1731
1893
 
1732
1894
  function useFireAction(args) {
1733
- const {instanceId: instanceId, activity: activity, action: action, label: label} = args, {fireActionFor: fireActionFor} = useWorkflowContext(), toast = useClosableToast(), cluster = useActionClusterLock(), [localPending, setLocalPending] = useState(!1), pending = localPending || cluster?.pending === !0;
1895
+ const {instanceId: instanceId, activity: activity, action: action, label: label, surface: surface, viaMenu: viaMenu} = args, {fireActionFor: fireActionFor} = useWorkflowContext(), telemetry = useWorkflowTelemetry(), toast = useClosableToast(), cluster = useActionClusterLock(), [localPending, setLocalPending] = useState(!1), pending = localPending || cluster?.pending === !0;
1734
1896
  return {
1735
1897
  fire: useCallback(async params => {
1736
1898
  if (pending) return !1;
@@ -1745,6 +1907,11 @@ function useFireAction(args) {
1745
1907
  }), toast.push({
1746
1908
  status: "success",
1747
1909
  title: `${label} done`
1910
+ }), telemetry.log(WorkflowActionControlUsed, {
1911
+ instanceId: instanceId,
1912
+ surface: surface,
1913
+ viaMenu: viaMenu,
1914
+ success: !0
1748
1915
  }), !0;
1749
1916
  } catch (err) {
1750
1917
  return console.error(`[workflow-studio-plugin] firing "${action}" on "${activity}" failed:`, err),
@@ -1752,15 +1919,29 @@ function useFireAction(args) {
1752
1919
  status: "error",
1753
1920
  title: `${label} failed`,
1754
1921
  description: describeError(err)
1922
+ }), telemetry.log(WorkflowActionControlUsed, {
1923
+ instanceId: instanceId,
1924
+ surface: surface,
1925
+ viaMenu: viaMenu,
1926
+ success: !1
1755
1927
  }), !1;
1756
1928
  } finally {
1757
1929
  setLocalPending(!1), cluster?.onFireChange(!1);
1758
1930
  }
1759
- }, [ fireActionFor, instanceId, activity, action, label, pending, toast, cluster ]),
1931
+ }, [ fireActionFor, instanceId, activity, action, label, pending, toast, cluster, telemetry, surface, viaMenu ]),
1760
1932
  pending: pending
1761
1933
  };
1762
1934
  }
1763
1935
 
1936
+ const ACTION_ICON_GAP = 2;
1937
+
1938
+ function actionIconProps(icon) {
1939
+ return icon ? {
1940
+ gap: ACTION_ICON_GAP,
1941
+ icon: icon
1942
+ } : {};
1943
+ }
1944
+
1764
1945
  function stopRowMouseDown(e) {
1765
1946
  e.preventDefault(), e.stopPropagation();
1766
1947
  }
@@ -1788,18 +1969,21 @@ function RowClickShield({active: active, children: children}) {
1788
1969
  });
1789
1970
  }
1790
1971
 
1791
- function FireButton({instanceId: instanceId, activity: activity, action: action, label: label, mode: mode = "default", chrome: chrome, tone: tone = "default"}) {
1972
+ function FireButton({instanceId: instanceId, activity: activity, action: action, label: label, surface: surface, mode: mode = "default", chrome: chrome, tone: tone = "default", icon: icon}) {
1792
1973
  const {fire: fire, pending: pending} = useFireAction({
1793
1974
  instanceId: instanceId,
1794
1975
  activity: activity,
1795
1976
  action: action,
1796
- label: label
1977
+ label: label,
1978
+ surface: surface,
1979
+ viaMenu: !1
1797
1980
  }), handleClick = e => {
1798
1981
  chrome?.inButtonCard === !0 && e.stopPropagation(), !pending && fire();
1799
1982
  };
1800
1983
  /* @__PURE__ */
1801
1984
  return jsx(Button, {
1802
1985
  fontSize: 1,
1986
+ ...actionIconProps(icon),
1803
1987
  loading: pending,
1804
1988
  mode: chrome?.mode ?? mode,
1805
1989
  onClick: handleClick,
@@ -1813,31 +1997,39 @@ function FireButton({instanceId: instanceId, activity: activity, action: action,
1813
1997
  });
1814
1998
  }
1815
1999
 
1816
- function ActivityActionRow({actionEval: actionEval, instanceId: instanceId, activity: activity, chrome: chrome}) {
2000
+ function ActivityActionRow({actionEval: actionEval, instanceId: instanceId, activity: activity, surface: surface, chrome: chrome}) {
1817
2001
  const rowKind = actionRowKind(actionEval);
1818
2002
  return rowKind.kind === "disabled" ? /* @__PURE__ */ jsx(DisabledActionButton, {
1819
2003
  actionEval: actionEval,
2004
+ activity: activity,
1820
2005
  chrome: chrome
1821
2006
  }) : rowKind.kind === "plain" ? /* @__PURE__ */ jsx(PlainActionButton, {
1822
2007
  actionEval: actionEval,
1823
2008
  instanceId: instanceId,
1824
2009
  activity: activity,
1825
- chrome: chrome
2010
+ chrome: chrome,
2011
+ surface: surface
1826
2012
  }) : /* @__PURE__ */ jsx(ParamsActionButton, {
1827
2013
  actionEval: actionEval,
1828
2014
  instanceId: instanceId,
1829
2015
  activity: activity,
1830
- chrome: chrome
2016
+ chrome: chrome,
2017
+ surface: surface
1831
2018
  });
1832
2019
  }
1833
2020
 
1834
- function DisabledActionButton({actionEval: actionEval, chrome: chrome}) {
1835
- const label = actionTriggerLabel(actionEval), button = /* @__PURE__ */ jsx(Button, {
2021
+ function DisabledActionButton({actionEval: actionEval, activity: activity, chrome: chrome}) {
2022
+ const label = actionTriggerLabel(actionEval), {tone: tone, icon: icon} = actionButtonFace({
2023
+ actionEval: actionEval,
2024
+ activityName: activity
2025
+ }), button = /* @__PURE__ */ jsx(Button, {
1836
2026
  fontSize: 1,
2027
+ ...actionIconProps(icon),
1837
2028
  mode: chrome?.mode ?? "ghost",
1838
2029
  onClick: e => e.stopPropagation(),
1839
2030
  padding: 2,
1840
2031
  text: label,
2032
+ tone: tone,
1841
2033
  ...spanTriggerProps(chrome),
1842
2034
  disabled: !0
1843
2035
  });
@@ -1847,17 +2039,19 @@ function DisabledActionButton({actionEval: actionEval, chrome: chrome}) {
1847
2039
  }) : button;
1848
2040
  }
1849
2041
 
1850
- function PlainActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, chrome: chrome}) {
1851
- const {action: action} = actionEval, label = actionLabel(action), btn = actionButtonTone({
2042
+ function PlainActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, surface: surface, chrome: chrome}) {
2043
+ const {action: action} = actionEval, label = actionLabel(action), btn = actionButtonFace({
1852
2044
  actionEval: actionEval,
1853
2045
  activityName: activity
1854
2046
  }), button = /* @__PURE__ */ jsx(FireButton, {
1855
2047
  action: action.name,
2048
+ icon: btn.icon,
1856
2049
  instanceId: instanceId,
1857
2050
  label: label,
1858
2051
  mode: btn.mode,
1859
2052
  activity: activity,
1860
2053
  chrome: chrome,
2054
+ surface: surface,
1861
2055
  tone: btn.tone
1862
2056
  });
1863
2057
  return action.description ? /* @__PURE__ */ jsx(HoverHint, {
@@ -1866,12 +2060,14 @@ function PlainActionButton({actionEval: actionEval, instanceId: instanceId, acti
1866
2060
  }) : button;
1867
2061
  }
1868
2062
 
1869
- function ParamsActionDialog({actionEval: actionEval, instanceId: instanceId, activity: activity, onClose: onClose}) {
2063
+ function ParamsActionDialog({actionEval: actionEval, instanceId: instanceId, activity: activity, surface: surface, viaMenu: viaMenu, onClose: onClose}) {
1870
2064
  const {action: action} = actionEval, label = actionLabel(action), decls = action.params ?? [], [values, setValues] = useState({}), {fire: fire, pending: pending} = useFireAction({
1871
2065
  instanceId: instanceId,
1872
2066
  activity: activity,
1873
2067
  action: action.name,
1874
- label: label
2068
+ label: label,
2069
+ surface: surface,
2070
+ viaMenu: viaMenu
1875
2071
  }), built = buildParams(decls, values), confirm = async () => {
1876
2072
  await fire(built.params) && onClose();
1877
2073
  };
@@ -1926,8 +2122,8 @@ function ParamsActionDialog({actionEval: actionEval, instanceId: instanceId, act
1926
2122
  });
1927
2123
  }
1928
2124
 
1929
- function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, chrome: chrome}) {
1930
- const [open, setOpen] = useState(!1), btn = actionButtonTone({
2125
+ function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, surface: surface, chrome: chrome}) {
2126
+ const [open, setOpen] = useState(!1), btn = actionButtonFace({
1931
2127
  actionEval: actionEval,
1932
2128
  activityName: activity
1933
2129
  });
@@ -1936,6 +2132,7 @@ function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, act
1936
2132
  children: [
1937
2133
  /* @__PURE__ */ jsx(Button, {
1938
2134
  fontSize: 1,
2135
+ ...actionIconProps(btn.icon),
1939
2136
  mode: chrome?.mode ?? btn.mode,
1940
2137
  onClick: e => {
1941
2138
  chrome?.inButtonCard === !0 && e.stopPropagation(), setOpen(!0);
@@ -1950,7 +2147,9 @@ function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, act
1950
2147
  actionEval: actionEval,
1951
2148
  activity: activity,
1952
2149
  instanceId: instanceId,
1953
- onClose: () => setOpen(!1)
2150
+ onClose: () => setOpen(!1),
2151
+ surface: surface,
2152
+ viaMenu: !1
1954
2153
  })
1955
2154
  }) : null ]
1956
2155
  });
@@ -2244,27 +2443,10 @@ function useLocalDocRef(gdr) {
2244
2443
  }
2245
2444
 
2246
2445
  function useActualDocType(bareId) {
2247
- const client = useClient({
2248
- apiVersion: WORKFLOW_API_VERSION
2249
- }), [probe, setProbe] = useState();
2446
+ const {actualTypes: actualTypes} = useWorkflowContext();
2250
2447
  return useEffect(() => {
2251
- if (!bareId) return;
2252
- let cancelled = !1;
2253
- return client.fetch("*[_id == $id || _id == $draft || _id in path($version)][0]._type", {
2254
- id: bareId,
2255
- draft: `drafts.${bareId}`,
2256
- version: `versions.*.${bareId}`
2257
- }, {
2258
- perspective: "raw"
2259
- }).then(result => {
2260
- cancelled || setProbe({
2261
- id: bareId,
2262
- value: result
2263
- });
2264
- }).catch(() => {}), () => {
2265
- cancelled = !0;
2266
- };
2267
- }, [ client, bareId ]), probe !== void 0 && probe.id === bareId ? probe.value : void 0;
2448
+ if (bareId !== null) return actualTypes.track(bareId);
2449
+ }, [ actualTypes, bareId ]), useSyncExternalStore(actualTypes.subscribe, useCallback(() => bareId === null ? void 0 : actualTypes.read(bareId), [ actualTypes, bareId ]));
2268
2450
  }
2269
2451
 
2270
2452
  function useDocLink(gdr) {
@@ -2761,7 +2943,7 @@ function ReadOnlyDocRefs({entry: entry}) {
2761
2943
  padding: 2,
2762
2944
  radius: 2,
2763
2945
  children: /* @__PURE__ */ jsx(PreviewOrId, {
2764
- id: extractDocumentId(ref.id),
2946
+ id: toBareId(ref.id),
2765
2947
  type: ref.type
2766
2948
  })
2767
2949
  }, ref.id))
@@ -2844,7 +3026,7 @@ function useFieldDraft(args) {
2844
3026
  }
2845
3027
 
2846
3028
  function EditableFieldControl({instanceId: instanceId, field: field}) {
2847
- const {editFieldFor: editFieldFor, previewFieldFor: previewFieldFor, discardFieldPreviewFor: discardFieldPreviewFor} = useWorkflowContext(), me = useCurrentUser(), held = useHeldWorkflowEntry(instanceId), entry = held ? resolveFieldEntry(held.instance, field) : void 0, evaluation = held?.evaluation, target = editTargetOf(field), consequence = fieldConsequence(evaluation, field.name), advanceTo = advanceTargetTitle(evaluation, field.name);
3029
+ const {editFieldFor: editFieldFor, previewFieldFor: previewFieldFor, discardFieldPreviewFor: discardFieldPreviewFor} = useWorkflowContext(), selfActor = useSelfActor(), held = useHeldWorkflowEntry(instanceId), entry = held ? resolveFieldEntry(held.instance, field) : void 0, evaluation = held?.evaluation, target = editFieldTarget(field), consequence = fieldConsequence(evaluation, field.name), advanceTo = advanceTargetTitle(evaluation, field.name);
2848
3030
  /* @__PURE__ */
2849
3031
  return jsx(EditableField, {
2850
3032
  field: field,
@@ -2880,7 +3062,7 @@ function EditableFieldControl({instanceId: instanceId, field: field}) {
2880
3062
  mode: "append",
2881
3063
  value: noteRow({
2882
3064
  body: body,
2883
- actor: me ? personActor(me) : void 0,
3065
+ actor: selfActor,
2884
3066
  at: /* @__PURE__ */ (new Date).toISOString()
2885
3067
  })
2886
3068
  }).then(() => {})
@@ -3015,7 +3197,10 @@ function AssigneeField({field: field, onSave: onSave, onUnset: onUnset}) {
3015
3197
  align: "center",
3016
3198
  children: /* @__PURE__ */ jsx(DismissablePopover, {
3017
3199
  content: /* @__PURE__ */ jsx(AssigneePicker, {
3018
- onToggle: pickAndClose,
3200
+ onChange: next => {
3201
+ const changed = changedAssignee(cur ? [ cur ] : [], next);
3202
+ changed && pickAndClose(changed);
3203
+ },
3019
3204
  value: cur ? [ cur ] : []
3020
3205
  }),
3021
3206
  onDismiss: () => setOpen(!1),
@@ -3540,11 +3725,11 @@ function AssignActivityControl({instanceId: instanceId, state: state, assigneeId
3540
3725
  setBusy(!0);
3541
3726
  try {
3542
3727
  selectedIds.has(memberId) ? await editFieldFor(instanceId, {
3543
- target: editTargetOf(field),
3728
+ target: editFieldTarget(field),
3544
3729
  mode: "set",
3545
3730
  value: members.filter(m => !(m.type === "user" && m.id === memberId))
3546
3731
  }) : await editFieldFor(instanceId, {
3547
- target: editTargetOf(field),
3732
+ target: editFieldTarget(field),
3548
3733
  mode: "append",
3549
3734
  value: {
3550
3735
  type: "user",
@@ -3973,8 +4158,8 @@ function TodoItemRowView({breadcrumb: breadcrumb, row: row, onToggle: onToggle,
3973
4158
  });
3974
4159
  }
3975
4160
 
3976
- function TodoItemsList({breadcrumb: breadcrumb, entry: entry, filter: filter}) {
3977
- const {instance: instance} = entry, definition = useDefinition(entry), {editFieldFor: editFieldFor, fireActionFor: fireActionFor} = useWorkflowContext(), toast = useClosableToast(), [busy, setBusy] = useState(!1), derivation = deriveTodoItems(entry), rows = derivation.rows.filter(row => rowMatches(row, filter)), appendTarget = appendTargetFor(derivation.appendTargets, filter), run = async op => {
4161
+ function TodoItemsList({breadcrumb: breadcrumb, entry: entry, filter: filter, surface: surface}) {
4162
+ const {instance: instance} = entry, definition = useDefinition(entry), {editFieldFor: editFieldFor, fireActionFor: fireActionFor} = useWorkflowContext(), telemetry = useWorkflowTelemetry(), toast = useClosableToast(), [busy, setBusy] = useState(!1), derivation = deriveTodoItems(entry), rows = derivation.rows.filter(row => rowMatches(row, filter)), appendTarget = appendTargetFor(derivation.appendTargets, filter), run = async op => {
3978
4163
  if (busy) return !1;
3979
4164
  setBusy(!0);
3980
4165
  try {
@@ -3999,15 +4184,35 @@ function TodoItemsList({breadcrumb: breadcrumb, entry: entry, filter: filter}) {
3999
4184
  mode: "set",
4000
4185
  value: next
4001
4186
  }));
4002
- }, toggleRow = row => {
4003
- if (row.editTarget) return patchItem(row, {
4004
- status: toggledTodoStatus(row.item)
4005
- });
4187
+ }, toggleRow = async row => {
4188
+ if (busy) return;
4189
+ if (row.editTarget) {
4190
+ const done = !isTodoDone(row.item), success = await patchItem(row, {
4191
+ status: toggledTodoStatus(row.item)
4192
+ }) === !0;
4193
+ telemetry.log(WorkflowTodoToggled, {
4194
+ instanceId: instance._id,
4195
+ surface: surface,
4196
+ via: "field-edit",
4197
+ done: done,
4198
+ success: success
4199
+ });
4200
+ return;
4201
+ }
4006
4202
  const tick = row.tick;
4007
- return tick && tick.actionEval.allowed && !isTodoDone(row.item) ? run(() => fireActionFor(instance._id, {
4008
- activity: tick.activity,
4009
- action: tick.actionEval.action.name
4010
- })) : Promise.resolve();
4203
+ if (tick && tick.actionEval.allowed && !isTodoDone(row.item)) {
4204
+ const success = await run(() => fireActionFor(instance._id, {
4205
+ activity: tick.activity,
4206
+ action: tick.actionEval.action.name
4207
+ }));
4208
+ telemetry.log(WorkflowTodoToggled, {
4209
+ instanceId: instance._id,
4210
+ surface: surface,
4211
+ via: "action",
4212
+ done: !0,
4213
+ success: success
4214
+ });
4215
+ }
4011
4216
  }, addItem = label => appendTarget ? run(() => editFieldFor(instance._id, {
4012
4217
  target: appendTarget,
4013
4218
  mode: "append",
@@ -4124,26 +4329,39 @@ const ACTIONS_MENU_TRIGGER = {
4124
4329
  padding: 2
4125
4330
  };
4126
4331
 
4127
- function ActionMenuItem({actionEval: actionEval, instanceId: instanceId, activity: activity, onCollectParams: onCollectParams}) {
4128
- const rowKind = actionRowKind(actionEval), label = actionTriggerLabel(actionEval), {fire: fire, pending: pending} = useFireAction({
4332
+ function ActionMenuItem({actionEval: actionEval, instanceId: instanceId, activity: activity, surface: surface, onCollectParams: onCollectParams}) {
4333
+ const rowKind = actionRowKind(actionEval), label = actionTriggerLabel(actionEval), {tone: tone, icon: icon} = actionButtonFace({
4334
+ actionEval: actionEval,
4335
+ activityName: activity
4336
+ }), iconProp = icon ? {
4337
+ icon: icon
4338
+ } : {}, {fire: fire, pending: pending} = useFireAction({
4129
4339
  instanceId: instanceId,
4130
4340
  activity: activity,
4131
4341
  action: actionEval.action.name,
4132
- label: label
4342
+ label: label,
4343
+ surface: surface,
4344
+ viaMenu: !0
4133
4345
  });
4134
4346
  return rowKind.kind === "disabled" ? /* @__PURE__ */ jsx(MenuItem, {
4135
4347
  disabled: !0,
4136
- text: label
4348
+ ...iconProp,
4349
+ text: label,
4350
+ tone: tone
4137
4351
  }) : rowKind.kind === "plain" ? /* @__PURE__ */ jsx(MenuItem, {
4138
4352
  disabled: pending,
4353
+ ...iconProp,
4139
4354
  onClick: () => {
4140
4355
  fire();
4141
4356
  },
4142
- text: label
4357
+ text: label,
4358
+ tone: tone
4143
4359
  }) : /* @__PURE__ */ jsx(MenuItem, {
4144
4360
  disabled: pending,
4361
+ ...iconProp,
4145
4362
  onClick: onCollectParams,
4146
- text: label
4363
+ text: label,
4364
+ tone: tone
4147
4365
  });
4148
4366
  }
4149
4367
 
@@ -4159,7 +4377,7 @@ function rowShield(menuButton) {
4159
4377
  });
4160
4378
  }
4161
4379
 
4162
- function ActionsMenuButton({actions: actions, instanceId: instanceId, activity: activity, label: label, mode: mode = "ghost", inButtonCard: inButtonCard = !1}) {
4380
+ function ActionsMenuButton({actions: actions, instanceId: instanceId, activity: activity, label: label, surface: surface, mode: mode = "ghost", inButtonCard: inButtonCard = !1}) {
4163
4381
  const [paramsAction, setParamsAction] = useState(void 0), clusterPending = useActionClusterPending(), menuId = useId(), menuButton = /* @__PURE__ */ jsx(MenuButton, {
4164
4382
  button: /* @__PURE__ */ jsx(Button, {
4165
4383
  ...ACTIONS_MENU_TRIGGER,
@@ -4178,7 +4396,8 @@ function ActionsMenuButton({actions: actions, instanceId: instanceId, activity:
4178
4396
  actionEval: a,
4179
4397
  activity: activity,
4180
4398
  instanceId: instanceId,
4181
- onCollectParams: () => setParamsAction(a)
4399
+ onCollectParams: () => setParamsAction(a),
4400
+ surface: surface
4182
4401
  }, a.action.name))
4183
4402
  }),
4184
4403
  popover: {
@@ -4194,7 +4413,9 @@ function ActionsMenuButton({actions: actions, instanceId: instanceId, activity:
4194
4413
  actionEval: paramsAction,
4195
4414
  activity: activity,
4196
4415
  instanceId: instanceId,
4197
- onClose: () => setParamsAction(void 0)
4416
+ onClose: () => setParamsAction(void 0),
4417
+ surface: surface,
4418
+ viaMenu: !0
4198
4419
  })
4199
4420
  }) : null ]
4200
4421
  });
@@ -4210,7 +4431,8 @@ function TerminalFooter({actions: actions, activity: activity, instanceId: insta
4210
4431
  children: /* @__PURE__ */ jsx(ActivityActionRow, {
4211
4432
  actionEval: only,
4212
4433
  activity: activity,
4213
- instanceId: instanceId
4434
+ instanceId: instanceId,
4435
+ surface: "terminal-footer"
4214
4436
  })
4215
4437
  }) : /* @__PURE__ */ jsx(FittedActions, {
4216
4438
  actions: actions,
@@ -4256,6 +4478,7 @@ function FittedActions({actions: actions, activity: activity, instanceId: instan
4256
4478
  children: [
4257
4479
  /* @__PURE__ */ jsx(MeasureRow, {
4258
4480
  actions: actions,
4481
+ activity: activity,
4259
4482
  onElement: setMeasureEl
4260
4483
  }),
4261
4484
  /* @__PURE__ */ jsxs(FooterRow, {
@@ -4263,19 +4486,21 @@ function FittedActions({actions: actions, activity: activity, instanceId: instan
4263
4486
  children: [ inline === void 0 ? null : actions.slice(0, inline).map(a => /* @__PURE__ */ jsx(ActivityActionRow, {
4264
4487
  actionEval: a,
4265
4488
  activity: activity,
4266
- instanceId: instanceId
4489
+ instanceId: instanceId,
4490
+ surface: "terminal-footer"
4267
4491
  }, a.action.name)), overflow.length > 0 ? /* @__PURE__ */ jsx(ActionsMenuButton, {
4268
4492
  actions: overflow,
4269
4493
  activity: activity,
4270
4494
  instanceId: instanceId,
4271
4495
  label: MORE_ACTIONS_LABEL,
4272
- mode: "default"
4496
+ mode: "default",
4497
+ surface: "terminal-footer"
4273
4498
  }) : null ]
4274
4499
  }) ]
4275
4500
  });
4276
4501
  }
4277
4502
 
4278
- function MeasureRow({actions: actions, onElement: onElement}) {
4503
+ function MeasureRow({actions: actions, activity: activity, onElement: onElement}) {
4279
4504
  /* @__PURE__ */
4280
4505
  return jsx("div", {
4281
4506
  "aria-hidden": !0,
@@ -4289,12 +4514,20 @@ function MeasureRow({actions: actions, onElement: onElement}) {
4289
4514
  display: "inline-flex",
4290
4515
  whiteSpace: "nowrap"
4291
4516
  },
4292
- children: [ actions.map(a => /* @__PURE__ */ jsx(Button, {
4293
- as: "span",
4294
- fontSize: 1,
4295
- padding: 2,
4296
- text: actionTriggerLabel(a)
4297
- }, a.action.name)),
4517
+ children: [ actions.map(a => {
4518
+ const {icon: icon} = actionButtonFace({
4519
+ actionEval: a,
4520
+ activityName: activity
4521
+ });
4522
+ /* @__PURE__ */
4523
+ return jsx(Button, {
4524
+ as: "span",
4525
+ fontSize: 1,
4526
+ ...actionIconProps(icon),
4527
+ padding: 2,
4528
+ text: actionTriggerLabel(a)
4529
+ }, a.action.name);
4530
+ }),
4298
4531
  /* @__PURE__ */ jsx(Button, {
4299
4532
  ...ACTIONS_MENU_TRIGGER,
4300
4533
  as: "span",
@@ -4304,7 +4537,7 @@ function MeasureRow({actions: actions, onElement: onElement}) {
4304
4537
  });
4305
4538
  }
4306
4539
 
4307
- function ActivityDetailDialog({entry: entry, activityName: activityName, breadcrumb: breadcrumb, definition: definition, document: document, onClose: onClose}) {
4540
+ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcrumb: breadcrumb, definition: definition, document: document, source: source, onClose: onClose}) {
4308
4541
  const detail = deriveActivityDetail({
4309
4542
  entry: entry,
4310
4543
  activityName: activityName
@@ -4326,6 +4559,13 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
4326
4559
  onClose: onClose,
4327
4560
  width: 1,
4328
4561
  children: [
4562
+ /* @__PURE__ */ jsx(LogEventOnMount, {
4563
+ data: {
4564
+ instanceId: instanceId,
4565
+ source: source
4566
+ },
4567
+ event: WorkflowActivityDialogOpened
4568
+ }),
4329
4569
  /* @__PURE__ */ jsx(Card, {
4330
4570
  borderBottom: hasBody,
4331
4571
  style: {
@@ -4433,7 +4673,8 @@ function TopActions({detail: detail, definition: definition, instanceId: instanc
4433
4673
  children: [ p.actions.map(a => /* @__PURE__ */ jsx(ActivityActionRow, {
4434
4674
  actionEval: a,
4435
4675
  activity: activityName,
4436
- instanceId: instanceId
4676
+ instanceId: instanceId,
4677
+ surface: "dialog-strip"
4437
4678
  }, a.action.name)), p.manualTarget ? /* @__PURE__ */ jsx(Button, {
4438
4679
  as: "a",
4439
4680
  fontSize: 1,
@@ -4469,7 +4710,8 @@ function FieldRow({field: field, detail: detail, entry: entry, activityName: act
4469
4710
  scope: "activity",
4470
4711
  field: field.name,
4471
4712
  activity: activityName
4472
- }
4713
+ },
4714
+ surface: "activity-dialog"
4473
4715
  }) ]
4474
4716
  });
4475
4717
  if (field._type === "assignees") {
@@ -4576,7 +4818,26 @@ function CountedLabel({count: count, label: label}) {
4576
4818
  });
4577
4819
  }
4578
4820
 
4579
- function GroupHeading({count: count, title: title, end: end}) {
4821
+ function SpinnerSlot({busy: busy, size: size}) {
4822
+ /* @__PURE__ */
4823
+ return jsx(Flex, {
4824
+ align: "center",
4825
+ flex: "none",
4826
+ justify: "center",
4827
+ style: {
4828
+ width: 19,
4829
+ height: 19
4830
+ },
4831
+ children: busy ? /* @__PURE__ */ jsx(Spinner, {
4832
+ muted: !0,
4833
+ ...size !== void 0 ? {
4834
+ size: size
4835
+ } : {}
4836
+ }) : null
4837
+ });
4838
+ }
4839
+
4840
+ function GroupHeading({busy: busy, count: count, title: title, end: end}) {
4580
4841
  /* @__PURE__ */
4581
4842
  return jsxs(Flex, {
4582
4843
  align: "center",
@@ -4591,6 +4852,9 @@ function GroupHeading({count: count, title: title, end: end}) {
4591
4852
  weight: "semibold",
4592
4853
  children: title
4593
4854
  })
4855
+ }), busy === void 0 ? null : /* @__PURE__ */ jsx(SpinnerSlot, {
4856
+ busy: busy,
4857
+ size: 1
4594
4858
  }), end === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, {
4595
4859
  children: [
4596
4860
  /* @__PURE__ */ jsx(Flex, {
@@ -4600,29 +4864,7 @@ function GroupHeading({count: count, title: title, end: end}) {
4600
4864
  });
4601
4865
  }
4602
4866
 
4603
- function CopyDetailsButton({unreadable: unreadable}) {
4604
- const toast = useClosableToast();
4605
- /* @__PURE__ */
4606
- return jsx(Button, {
4607
- fontSize: 1,
4608
- mode: "ghost",
4609
- onClick: () => {
4610
- navigator.clipboard.writeText(unreadableDocsReport(unreadable)).then(() => toast.push({
4611
- status: "success",
4612
- title: "Details copied"
4613
- }), () => toast.push({
4614
- status: "error",
4615
- title: "Could not copy the details"
4616
- }));
4617
- },
4618
- padding: 2,
4619
- text: "Copy details"
4620
- });
4621
- }
4622
-
4623
- function UnreadableDocsNote({unreadable: unreadable}) {
4624
- if (unreadable.length === 0) return null;
4625
- 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.`;
4867
+ function CautionNote({action: action, label: label}) {
4626
4868
  /* @__PURE__ */
4627
4869
  return jsxs(Card, {
4628
4870
  padding: 2,
@@ -4649,35 +4891,64 @@ function UnreadableDocsNote({unreadable: unreadable}) {
4649
4891
  size: 1,
4650
4892
  children: label
4651
4893
  }) ]
4652
- }),
4653
- /* @__PURE__ */ jsx(CopyDetailsButton, {
4654
- unreadable: unreadable
4655
- }) ]
4894
+ }), action ]
4656
4895
  });
4657
4896
  }
4658
4897
 
4659
- const HEADLINES = {
4660
- "model-ahead": "Workflow needs a newer engine",
4661
- "shape-violation": "Unreadable workflow document"
4662
- };
4663
-
4664
- function remediation(invalid) {
4665
- return invalid.reason === "model-ahead" ? `"${invalid.documentId}" was created by a newer version of the workflow engine than this Studio can read. It needs a plugin update that supports the newer data model — share the details with your Studio maintainers.` : `This version of the Studio can't read "${invalid.documentId}" — it was likely created by a newer or different version of the workflow tooling. Share the details with your Studio maintainers.`;
4666
- }
4667
-
4668
- function InvalidDocNotice({invalid: invalid}) {
4898
+ function CopyDetailsButton({unreadable: unreadable}) {
4899
+ const toast = useClosableToast();
4669
4900
  /* @__PURE__ */
4670
- return jsx(Card, {
4671
- border: !0,
4672
- padding: 3,
4673
- radius: 2,
4674
- tone: "caution",
4675
- children: /* @__PURE__ */ jsxs(Stack, {
4676
- space: 3,
4677
- children: [
4678
- /* @__PURE__ */ jsx(Text, {
4679
- size: 1,
4680
- weight: "medium",
4901
+ return jsx(Button, {
4902
+ fontSize: 1,
4903
+ mode: "ghost",
4904
+ onClick: () => {
4905
+ navigator.clipboard.writeText(unreadableDocsReport(unreadable)).then(() => toast.push({
4906
+ status: "success",
4907
+ title: "Details copied"
4908
+ }), () => toast.push({
4909
+ status: "error",
4910
+ title: "Could not copy the details"
4911
+ }));
4912
+ },
4913
+ padding: 2,
4914
+ text: "Copy details"
4915
+ });
4916
+ }
4917
+
4918
+ function UnreadableDocsNote({unreadable: unreadable}) {
4919
+ if (unreadable.length === 0) return null;
4920
+ 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.`;
4921
+ /* @__PURE__ */
4922
+ return jsx(CautionNote, {
4923
+ action: /* @__PURE__ */ jsx(CopyDetailsButton, {
4924
+ unreadable: unreadable
4925
+ }),
4926
+ label: label
4927
+ });
4928
+ }
4929
+
4930
+ const HEADLINES = {
4931
+ "model-ahead": "Workflow needs a newer engine",
4932
+ "shape-violation": "Unreadable workflow document"
4933
+ };
4934
+
4935
+ function remediation(invalid) {
4936
+ return invalid.reason === "model-ahead" ? `"${invalid.documentId}" was created by a newer version of the workflow engine than this Studio can read. It needs a plugin update that supports the newer data model — share the details with your Studio maintainers.` : `This version of the Studio can't read "${invalid.documentId}" — it was likely created by a newer or different version of the workflow tooling. Share the details with your Studio maintainers.`;
4937
+ }
4938
+
4939
+ function InvalidDocNotice({invalid: invalid}) {
4940
+ /* @__PURE__ */
4941
+ return jsx(Card, {
4942
+ border: !0,
4943
+ padding: 3,
4944
+ radius: 2,
4945
+ tone: "caution",
4946
+ children: /* @__PURE__ */ jsxs(Stack, {
4947
+ space: 3,
4948
+ children: [
4949
+ /* @__PURE__ */ jsx(Text, {
4950
+ size: 1,
4951
+ weight: "medium",
4681
4952
  children: HEADLINES[invalid.reason]
4682
4953
  }),
4683
4954
  /* @__PURE__ */ jsx(Text, {
@@ -4713,6 +4984,18 @@ function TabSwitch({ariaControls: ariaControls, idPrefix: idPrefix, options: opt
4713
4984
  });
4714
4985
  }
4715
4986
 
4987
+ function useDelayedFlag(active, delayMs) {
4988
+ const [held, setHeld] = useState(!1);
4989
+ return useEffect(() => {
4990
+ if (!active) {
4991
+ setHeld(!1);
4992
+ return;
4993
+ }
4994
+ const timer = setTimeout(() => setHeld(!0), delayMs);
4995
+ return () => clearTimeout(timer);
4996
+ }, [ active, delayMs ]), held && active;
4997
+ }
4998
+
4716
4999
  function instanceTitle(instance, definition) {
4717
5000
  return definition?.title ?? instance.definition;
4718
5001
  }
@@ -4962,6 +5245,25 @@ function buildStartRequest(args) {
4962
5245
  };
4963
5246
  }
4964
5247
 
5248
+ function sharedPreflightDataset(engine, tag) {
5249
+ let pending;
5250
+ return () => {
5251
+ if (pending !== void 0) return pending;
5252
+ const {query: query, params: params} = instancesQuery({
5253
+ tag: tag,
5254
+ filter: {
5255
+ includeCompleted: !0
5256
+ }
5257
+ });
5258
+ return pending = engine.query({
5259
+ groq: query,
5260
+ params: params
5261
+ }).then(rows => rows.map(assertReadableModel).map(projectStartSliceRow)).finally(() => {
5262
+ pending = void 0;
5263
+ }), pending;
5264
+ };
5265
+ }
5266
+
4965
5267
  function findResumeEntry(args) {
4966
5268
  return args.entries.find(e => isUnprimed(e.instance) && e.instance.definition === args.definitionName && (args.heldInstanceId === void 0 || e.instance._id === args.heldInstanceId));
4967
5269
  }
@@ -4977,7 +5279,8 @@ function classifyStartError(err) {
4977
5279
  instance: err.instance,
4978
5280
  description: describeError(err.cause)
4979
5281
  } : err instanceof StartNotAllowedError ? {
4980
- kind: "not-allowed"
5282
+ kind: "not-allowed",
5283
+ description: err.unmetRequirements.map(requirementCopy).join("; ")
4981
5284
  } : {
4982
5285
  kind: "failed",
4983
5286
  description: describeError(err)
@@ -4990,22 +5293,30 @@ function boundReleaseId(mapping, selectedReleaseId) {
4990
5293
 
4991
5294
  function startGate(args) {
4992
5295
  const {mapping: mapping, mappingIssue: mappingIssue, selectedReleaseId: selectedReleaseId, releaseActive: releaseActive} = args;
4993
- return mappingIssue !== void 0 ? {
5296
+ if (mappingIssue !== void 0) return {
4994
5297
  blocked: !0,
4995
5298
  tooltip: mappingIssue
4996
- } : mapping.perspectiveField?.required === !0 && selectedReleaseId === void 0 ? {
5299
+ };
5300
+ if (mapping.perspectiveField?.required === !0 && selectedReleaseId === void 0) return {
4997
5301
  blocked: !0,
4998
5302
  tooltip: "Pick a release in the perspective dropdown first"
4999
- } : mapping.perspectiveField !== void 0 && selectedReleaseId !== void 0 && !releaseActive ? {
5303
+ };
5304
+ if (mapping.perspectiveField !== void 0 && selectedReleaseId !== void 0 && !releaseActive) return {
5000
5305
  blocked: !0,
5001
5306
  tooltip: `Release "${selectedReleaseId}" is not active — only active releases can host new workflows`
5002
- } : args.startFilterFailed === !0 ? {
5307
+ };
5308
+ if (args.startFilterFailed === !0) return {
5003
5309
  blocked: !0,
5004
5310
  tooltip: `${mapping.label} can't start for this document right now — the workflow's start condition isn't met`
5005
- } : args.startAllowedRefused === !0 ? {
5006
- blocked: !0,
5007
- tooltip: `${mapping.label} can't be started for this document right now — the workflow doesn't allow another run with these inputs`
5008
- } : {
5311
+ };
5312
+ if (args.unmetRequirement !== void 0) {
5313
+ const requirement = args.unmetRequirement;
5314
+ return {
5315
+ blocked: !0,
5316
+ tooltip: requirementCopy(requirement)
5317
+ };
5318
+ }
5319
+ return {
5009
5320
  blocked: !1,
5010
5321
  tooltip: void 0
5011
5322
  };
@@ -5028,10 +5339,10 @@ async function preflightFilter(args) {
5028
5339
  });
5029
5340
  }
5030
5341
 
5031
- async function preflightAllowed(args) {
5032
- const {allowed: allowed, mapping: mapping, fields: fields, scope: scope} = args;
5033
- return unboundAllowedReads(allowed, fields).length > 0 ? !1 : (await explainStartAllowed({
5034
- allowed: allowed,
5342
+ async function preflightGroqRequirement(args) {
5343
+ const {query: query, mapping: mapping, fields: fields, scope: scope} = args;
5344
+ return unboundRequirementReads(query, fields).length > 0 ? !1 : (await explainStartRequirement({
5345
+ query: query,
5035
5346
  definition: {
5036
5347
  name: mapping.definition
5037
5348
  },
@@ -5040,6 +5351,25 @@ async function preflightAllowed(args) {
5040
5351
  })).outcome === "unsatisfied";
5041
5352
  }
5042
5353
 
5354
+ async function preflightSingleSubjectRequirement(args) {
5355
+ const {mapping: mapping, start: start, declaredFields: declaredFields, fields: fields, scope: scope} = args;
5356
+ return singleSubjectRequirementRefused({
5357
+ definition: {
5358
+ name: mapping.definition,
5359
+ ...declaredFields !== void 0 ? {
5360
+ fields: [ ...declaredFields ]
5361
+ } : {},
5362
+ start: start
5363
+ },
5364
+ fields: fields,
5365
+ scope: scope
5366
+ });
5367
+ }
5368
+
5369
+ function requirementCopy(requirement) {
5370
+ return requirement.description ?? requirement.title ?? humanize(requirement.name);
5371
+ }
5372
+
5043
5373
  async function performStart(args) {
5044
5374
  const {mapping: mapping, docId: docId, initialValue: initialValue, selectedReleaseId: selectedReleaseId} = args, releaseId = boundReleaseId(mapping, selectedReleaseId);
5045
5375
  try {
@@ -5066,7 +5396,21 @@ async function performStart(args) {
5066
5396
  }
5067
5397
 
5068
5398
  function useStartVerdicts(args) {
5069
- const {mapping: mapping, start: start, docId: docId, initialValue: initialValue, selectedReleaseId: selectedReleaseId} = args, skip = args.hidden || args.resumable;
5399
+ const {mapping: mapping, start: start, docId: docId, initialValue: initialValue, selectedReleaseId: selectedReleaseId, entries: entries} = args, {engine: engine, binding: binding} = useWorkflowContext(), fetchDataset = useMemo(() => sharedPreflightDataset(engine, binding.tag), [ engine, binding.tag ]), skip = args.hidden || args.resumable, groqRequirements = useMemo(() => start?.requirements?.filter(requirement => requirement.type === "groq") ?? [], [ start?.requirements ]), refusedGroq = useGroqRequirementVerdicts({
5400
+ mapping: mapping,
5401
+ requirements: groqRequirements,
5402
+ docId: docId,
5403
+ selectedReleaseId: selectedReleaseId,
5404
+ skip: skip,
5405
+ fetchDataset: fetchDataset
5406
+ }), singleSubjectRefused = useSingleSubjectVerdict({
5407
+ mapping: mapping,
5408
+ start: start,
5409
+ docId: docId,
5410
+ selectedReleaseId: selectedReleaseId,
5411
+ entries: entries,
5412
+ skip: skip
5413
+ }), unmetRequirement = start?.requirements?.find(requirement => requirement.type === "singleSubject" ? singleSubjectRefused : refusedGroq.includes(requirement.name));
5070
5414
  return {
5071
5415
  filterFailed: useFilterVerdict({
5072
5416
  mapping: mapping,
@@ -5074,25 +5418,15 @@ function useStartVerdicts(args) {
5074
5418
  docId: docId,
5075
5419
  initialValue: initialValue,
5076
5420
  selectedReleaseId: selectedReleaseId,
5077
- skip: skip
5421
+ skip: skip,
5422
+ fetchDataset: fetchDataset
5078
5423
  }),
5079
- allowedRefused: useAllowedVerdict({
5080
- mapping: mapping,
5081
- allowed: start?.allowed,
5082
- docId: docId,
5083
- selectedReleaseId: selectedReleaseId,
5084
- skip: skip
5085
- })
5424
+ unmetRequirement: unmetRequirement
5086
5425
  };
5087
5426
  }
5088
5427
 
5089
5428
  function preflightScope(args) {
5090
- const {engine: engine, tag: tag, mapping: mapping, contentResource: contentResource, docId: docId} = args, {query: query, params: params} = instancesQuery({
5091
- tag: tag,
5092
- filter: {
5093
- includeCompleted: !0
5094
- }
5095
- });
5429
+ const {tag: tag, mapping: mapping, contentResource: contentResource, docId: docId, fetchDataset: fetchDataset} = args;
5096
5430
  return {
5097
5431
  tag: tag,
5098
5432
  now: /* @__PURE__ */ (new Date).toISOString(),
@@ -5101,15 +5435,12 @@ function preflightScope(args) {
5101
5435
  contentResource: contentResource,
5102
5436
  documentId: docId
5103
5437
  }).id,
5104
- fetchDataset: async () => (await engine.query({
5105
- groq: query,
5106
- params: params
5107
- })).map(assertReadableModel)
5438
+ fetchDataset: fetchDataset
5108
5439
  };
5109
5440
  }
5110
5441
 
5111
5442
  function useFilterVerdict(args) {
5112
- const {mapping: mapping, filter: filter, docId: docId, initialValue: initialValue, selectedReleaseId: selectedReleaseId, skip: skip} = args, {engine: engine, binding: binding} = useWorkflowContext(), [failed, setFailed] = useState(!1), rootRead = filter !== void 0 && readsRootDocument(filter), candidateValue = rootRead ? initialValue : null;
5443
+ const {mapping: mapping, filter: filter, docId: docId, initialValue: initialValue, selectedReleaseId: selectedReleaseId, skip: skip, fetchDataset: fetchDataset} = args, {binding: binding} = useWorkflowContext(), [failed, setFailed] = useState(!1), rootRead = filter !== void 0 && readsRootDocument(filter), candidateValue = rootRead ? initialValue : null;
5113
5444
  return useEffect(() => {
5114
5445
  if (skip || filter === void 0 || docId === void 0) {
5115
5446
  setFailed(!1);
@@ -5123,57 +5454,112 @@ function useFilterVerdict(args) {
5123
5454
  candidateValue: candidateValue,
5124
5455
  selectedReleaseId: selectedReleaseId,
5125
5456
  scope: preflightScope({
5126
- engine: engine,
5127
5457
  tag: binding.tag,
5128
5458
  mapping: mapping,
5129
5459
  contentResource: binding.contentResource,
5130
- docId: docId
5460
+ docId: docId,
5461
+ fetchDataset: fetchDataset
5131
5462
  })
5132
5463
  }).catch(showRowOnError("start.filter", mapping)).then(verdict => {
5133
5464
  cancelled || setFailed(verdict);
5134
5465
  }), () => {
5135
5466
  cancelled = !0;
5136
5467
  };
5137
- }, [ filter, rootRead, skip, docId, selectedReleaseId, candidateValue, mapping, engine, binding ]),
5468
+ }, [ filter, rootRead, skip, docId, selectedReleaseId, candidateValue, mapping, binding, fetchDataset ]),
5138
5469
  failed;
5139
5470
  }
5140
5471
 
5141
- function useAllowedVerdict(args) {
5142
- const {mapping: mapping, allowed: allowed, docId: docId, selectedReleaseId: selectedReleaseId, skip: skip} = args, {engine: engine, binding: binding, mappingFields: mappingFields} = useWorkflowContext(), [refused, setRefused] = useState(!1);
5472
+ function preflightSeedFields(args) {
5473
+ const ctx = {
5474
+ mapping: args.mapping,
5475
+ subjectDocId: args.docId,
5476
+ selectedReleaseId: args.selectedReleaseId,
5477
+ contentResource: args.contentResource,
5478
+ declaredFields: args.declaredFields
5479
+ };
5480
+ return Object.fromEntries(buildStartFields({
5481
+ ctx: ctx,
5482
+ initFields: [],
5483
+ values: {}
5484
+ }).map(field => [ field.name, field.value ]));
5485
+ }
5486
+
5487
+ function useGroqRequirementVerdicts(args) {
5488
+ const {mapping: mapping, requirements: requirements, docId: docId, selectedReleaseId: selectedReleaseId, skip: skip, fetchDataset: fetchDataset} = args, {binding: binding, mappingFields: mappingFields} = useWorkflowContext(), [refused, setRefused] = useState([]);
5143
5489
  return useEffect(() => {
5144
- if (skip || allowed === void 0 || docId === void 0) {
5145
- setRefused(!1);
5490
+ if (skip || requirements.length === 0 || docId === void 0) {
5491
+ setRefused(current => current.length === 0 ? current : []);
5146
5492
  return;
5147
5493
  }
5148
5494
  let cancelled = !1;
5149
- const ctx = {
5495
+ const fields = preflightSeedFields({
5150
5496
  mapping: mapping,
5151
- subjectDocId: docId,
5497
+ docId: docId,
5152
5498
  selectedReleaseId: selectedReleaseId,
5153
5499
  contentResource: binding.contentResource,
5154
5500
  declaredFields: mappingFields.get(mappingKey(mapping))
5155
- }, fields = Object.fromEntries(buildStartFields({
5156
- ctx: ctx,
5157
- initFields: [],
5158
- values: {}
5159
- }).map(field => [ field.name, field.value ]));
5160
- return preflightAllowed({
5161
- allowed: allowed,
5501
+ }), scope = preflightScope({
5502
+ tag: binding.tag,
5162
5503
  mapping: mapping,
5163
- fields: fields,
5504
+ contentResource: binding.contentResource,
5505
+ docId: docId,
5506
+ fetchDataset: fetchDataset
5507
+ });
5508
+ return Promise.all(requirements.map(async requirement => ({
5509
+ name: requirement.name,
5510
+ refused: await preflightGroqRequirement({
5511
+ query: requirement.query,
5512
+ mapping: mapping,
5513
+ fields: fields,
5514
+ scope: scope
5515
+ }).catch(showRowOnError(`start.requirements.${requirement.name}`, mapping))
5516
+ }))).then(verdicts => {
5517
+ if (!cancelled) {
5518
+ const next = verdicts.filter(verdict => verdict.refused).map(verdict => verdict.name);
5519
+ setRefused(current => current.length === next.length && current.every((name, index) => name === next[index]) ? current : next);
5520
+ }
5521
+ }), () => {
5522
+ cancelled = !0;
5523
+ };
5524
+ }, [ requirements, skip, docId, selectedReleaseId, mapping, binding, mappingFields, fetchDataset ]),
5525
+ refused;
5526
+ }
5527
+
5528
+ function useSingleSubjectVerdict(args) {
5529
+ const {mapping: mapping, start: start, docId: docId, selectedReleaseId: selectedReleaseId, entries: entries, skip: skip} = args, {binding: binding, mappingFields: mappingFields} = useWorkflowContext(), [refused, setRefused] = useState(!1), dedupe = start !== void 0 && hasSingleSubjectRequirement({
5530
+ start: start
5531
+ });
5532
+ return useEffect(() => {
5533
+ if (skip || !dedupe || start === void 0 || docId === void 0) {
5534
+ setRefused(!1);
5535
+ return;
5536
+ }
5537
+ let cancelled = !1;
5538
+ const declaredFields = mappingFields.get(mappingKey(mapping));
5539
+ return preflightSingleSubjectRequirement({
5540
+ mapping: mapping,
5541
+ start: start,
5542
+ declaredFields: declaredFields,
5543
+ fields: preflightSeedFields({
5544
+ mapping: mapping,
5545
+ docId: docId,
5546
+ selectedReleaseId: selectedReleaseId,
5547
+ contentResource: binding.contentResource,
5548
+ declaredFields: declaredFields
5549
+ }),
5164
5550
  scope: preflightScope({
5165
- engine: engine,
5166
5551
  tag: binding.tag,
5167
5552
  mapping: mapping,
5553
+ docId: docId,
5168
5554
  contentResource: binding.contentResource,
5169
- docId: docId
5555
+ fetchDataset: () => Promise.resolve(entries.map(({instance: instance}) => projectStartSliceRow(instance)))
5170
5556
  })
5171
- }).catch(showRowOnError("start.allowed", mapping)).then(verdict => {
5557
+ }).catch(showRowOnError("singleSubject requirement", mapping)).then(verdict => {
5172
5558
  cancelled || setRefused(verdict);
5173
5559
  }), () => {
5174
5560
  cancelled = !0;
5175
5561
  };
5176
- }, [ allowed, skip, docId, selectedReleaseId, mapping, engine, binding, mappingFields ]),
5562
+ }, [ dedupe, start, skip, docId, selectedReleaseId, entries, mapping, binding, mappingFields ]),
5177
5563
  refused;
5178
5564
  }
5179
5565
 
@@ -5183,7 +5569,7 @@ function showRowOnError(key, mapping) {
5183
5569
  }
5184
5570
 
5185
5571
  function useStartWorkflow(args) {
5186
- const {mapping: mapping, docId: docId, initialValue: initialValue} = args, toast = useClosableToast(), {engine: engine, openStartDialog: openStartDialog, mappingIssues: mappingIssues2, mappingStarts: mappingStarts} = useWorkflowContext(), observer = useStudioObserver({
5572
+ const {mapping: mapping, docId: docId, initialValue: initialValue, source: source} = args, toast = useClosableToast(), {engine: engine, openStartDialog: openStartDialog, mappingIssues: mappingIssues2, mappingStarts: mappingStarts} = useWorkflowContext(), observer = useStudioObserver({
5187
5573
  engine: engine
5188
5574
  }), [starting, setStarting] = useState(!1), {selectedReleaseId: selectedReleaseId} = usePerspective(), releaseActive = useIsReleaseActive(), startBlock = mappingStarts.get(mappingKey(mapping)), hidden = startKindOf({
5189
5575
  start: startBlock
@@ -5191,12 +5577,13 @@ function useStartWorkflow(args) {
5191
5577
  entries: entries,
5192
5578
  definitionName: mapping.definition,
5193
5579
  heldInstanceId: void 0
5194
- }) !== void 0, {filterFailed: filterFailed, allowedRefused: allowedRefused} = useStartVerdicts({
5580
+ }) !== void 0, {filterFailed: filterFailed, unmetRequirement: unmetRequirement} = useStartVerdicts({
5195
5581
  mapping: mapping,
5196
5582
  start: startBlock,
5197
5583
  docId: docId,
5198
5584
  initialValue: initialValue,
5199
5585
  selectedReleaseId: selectedReleaseId,
5586
+ entries: entries,
5200
5587
  hidden: hidden,
5201
5588
  resumable: resumable
5202
5589
  }), {blocked: blocked, tooltip: tooltip} = startGate({
@@ -5205,7 +5592,9 @@ function useStartWorkflow(args) {
5205
5592
  selectedReleaseId: selectedReleaseId,
5206
5593
  releaseActive: releaseActive,
5207
5594
  startFilterFailed: filterFailed,
5208
- startAllowedRefused: allowedRefused
5595
+ ...unmetRequirement !== void 0 ? {
5596
+ unmetRequirement: unmetRequirement
5597
+ } : {}
5209
5598
  });
5210
5599
  return {
5211
5600
  blocked: blocked,
@@ -5227,7 +5616,10 @@ function useStartWorkflow(args) {
5227
5616
  title: "Could not persist the document before starting",
5228
5617
  description: describeError(err)
5229
5618
  }),
5230
- openStartDialog: openStartDialog
5619
+ openStartDialog: request => openStartDialog({
5620
+ ...request,
5621
+ source: source
5622
+ })
5231
5623
  });
5232
5624
  } finally {
5233
5625
  setStarting(!1);
@@ -5237,11 +5629,12 @@ function useStartWorkflow(args) {
5237
5629
  };
5238
5630
  }
5239
5631
 
5240
- function StartWorkflowButton({mapping: mapping, docId: docId, initialValue: initialValue, mode: mode = "default", label: label, iconOnly: iconOnly = !1, tone: tone = "primary"}) {
5632
+ function StartWorkflowButton({mapping: mapping, docId: docId, initialValue: initialValue, source: source, mode: mode = "default", label: label, iconOnly: iconOnly = !1, tone: tone = "primary"}) {
5241
5633
  const {blocked: blocked, tooltip: tooltip, hidden: hidden, starting: starting, start: start} = useStartWorkflow({
5242
5634
  mapping: mapping,
5243
5635
  docId: docId,
5244
- initialValue: initialValue
5636
+ initialValue: initialValue,
5637
+ source: source
5245
5638
  });
5246
5639
  if (hidden) return null;
5247
5640
  const startLabel = label ?? `Start ${mapping.label}`, button = /* @__PURE__ */ jsx(Button, {
@@ -5274,7 +5667,7 @@ function useStartableMappings(mappings) {
5274
5667
  }) !== "autonomous");
5275
5668
  }
5276
5669
 
5277
- function StartWorkflowControl({mappings: mappings, docId: docId, initialValue: initialValue, label: label, iconOnly: iconOnly = !1, mode: mode = "default", tone: tone = "primary"}) {
5670
+ function StartWorkflowControl({mappings: mappings, docId: docId, initialValue: initialValue, source: source, label: label, iconOnly: iconOnly = !1, mode: mode = "default", tone: tone = "primary"}) {
5278
5671
  const menuId = useId(), startable = useStartableMappings(mappings), [first] = startable;
5279
5672
  if (first === void 0) return null;
5280
5673
  if (startable.length === 1) /* @__PURE__ */
@@ -5287,6 +5680,7 @@ function StartWorkflowControl({mappings: mappings, docId: docId, initialValue: i
5287
5680
  },
5288
5681
  mapping: first,
5289
5682
  mode: mode,
5683
+ source: source,
5290
5684
  tone: tone
5291
5685
  });
5292
5686
  const menuLabel = label ?? "Start a workflow";
@@ -5312,7 +5706,8 @@ function StartWorkflowControl({mappings: mappings, docId: docId, initialValue: i
5312
5706
  children: startable.map(mapping => /* @__PURE__ */ jsx(StartWorkflowMenuItem, {
5313
5707
  docId: docId,
5314
5708
  initialValue: initialValue,
5315
- mapping: mapping
5709
+ mapping: mapping,
5710
+ source: source
5316
5711
  }, mappingKey(mapping)))
5317
5712
  }),
5318
5713
  popover: {
@@ -5323,11 +5718,12 @@ function StartWorkflowControl({mappings: mappings, docId: docId, initialValue: i
5323
5718
  });
5324
5719
  }
5325
5720
 
5326
- function StartWorkflowMenuItem({mapping: mapping, docId: docId, initialValue: initialValue}) {
5721
+ function StartWorkflowMenuItem({mapping: mapping, docId: docId, initialValue: initialValue, source: source}) {
5327
5722
  const {blocked: blocked, tooltip: tooltip, starting: starting, start: start} = useStartWorkflow({
5328
5723
  mapping: mapping,
5329
5724
  docId: docId,
5330
- initialValue: initialValue
5725
+ initialValue: initialValue,
5726
+ source: source
5331
5727
  }), item = /* @__PURE__ */ jsx(MenuItem, {
5332
5728
  disabled: blocked || starting || !docId,
5333
5729
  onClick: () => {
@@ -5382,13 +5778,15 @@ function RowTerminalActions({actions: actions, activity: activity, instanceId: i
5382
5778
  mode: "ghost",
5383
5779
  inButtonCard: !0
5384
5780
  },
5385
- instanceId: instanceId
5781
+ instanceId: instanceId,
5782
+ surface: "inline-row"
5386
5783
  }) : /* @__PURE__ */ jsx(ActionsMenuButton, {
5387
5784
  actions: actions,
5388
5785
  activity: activity,
5389
5786
  inButtonCard: !0,
5390
5787
  instanceId: instanceId,
5391
- label: "Select action"
5788
+ label: "Select action",
5789
+ surface: "inline-row"
5392
5790
  })
5393
5791
  }) : null;
5394
5792
  }
@@ -5486,7 +5884,8 @@ function ForMeList({entry: entry, work: work, identity: identity, onOpenActivity
5486
5884
  filter: {
5487
5885
  kind: "mine",
5488
5886
  identity: identity
5489
- }
5887
+ },
5888
+ surface: "document-view-for-me"
5490
5889
  }) : null ]
5491
5890
  })
5492
5891
  });
@@ -5551,9 +5950,16 @@ function instanceNotice(entry) {
5551
5950
  size: 1,
5552
5951
  children: "This workflow didn’t finish starting — use Start workflow to continue it"
5553
5952
  })
5554
- }) : pending ? /* @__PURE__ */ jsx(LoadingRow, {
5555
- label: "Loading…",
5556
- padding: 4
5953
+ }) : pending ? /* @__PURE__ */ jsx(WorkRow, {
5954
+ lead: /* @__PURE__ */ jsx(Spinner, {
5955
+ muted: !0,
5956
+ size: 1
5957
+ }),
5958
+ children: /* @__PURE__ */ jsx(Text, {
5959
+ muted: !0,
5960
+ size: 1,
5961
+ children: "Loading…"
5962
+ })
5557
5963
  }) : null;
5558
5964
  }
5559
5965
 
@@ -5688,7 +6094,8 @@ function TodoFieldDialog({entry: entry, scope: scope, field: field, title: title
5688
6094
  kind: "field",
5689
6095
  scope: scope,
5690
6096
  field: field
5691
- }
6097
+ },
6098
+ surface: "todo-field-dialog"
5692
6099
  })
5693
6100
  })
5694
6101
  });
@@ -5901,7 +6308,7 @@ function PillButton({children: children, pill: pill, hintDisabled: hintDisabled
5901
6308
  }
5902
6309
 
5903
6310
  function useFieldCommit({entry: entry, editability: editability}) {
5904
- const {editFieldFor: editFieldFor} = useWorkflowContext(), target = editTargetOf(editability);
6311
+ const {editFieldFor: editFieldFor} = useWorkflowContext(), target = editFieldTarget(editability);
5905
6312
  return {
5906
6313
  set: value => editFieldFor(entry.instance._id, {
5907
6314
  target: target,
@@ -6117,8 +6524,9 @@ function AssigneePill({entry: entry, pill: pill, editability: editability}) {
6117
6524
  /* @__PURE__ */
6118
6525
  return jsx(PillPopover, {
6119
6526
  content: /* @__PURE__ */ jsx(AssigneePicker, {
6120
- onToggle: assignee => {
6121
- setOpen(!1), pick(assignee);
6527
+ onChange: next => {
6528
+ const changed = changedAssignee(current ? [ current ] : [], next);
6529
+ changed && (setOpen(!1), pick(changed));
6122
6530
  },
6123
6531
  value: current ? [ current ] : []
6124
6532
  }),
@@ -6137,10 +6545,8 @@ function AssigneesPill({entry: entry, pill: pill, editability: editability}) {
6137
6545
  /* @__PURE__ */
6138
6546
  return jsx(PillPopover, {
6139
6547
  content: /* @__PURE__ */ jsx(AssigneePicker, {
6140
- onToggle: assignee => {
6141
- if (saving) return;
6142
- const next = current.some(a => sameAssignee(a, assignee)) ? current.filter(a => !sameAssignee(a, assignee)) : [ ...current, assignee ];
6143
- save(() => set(next));
6548
+ onChange: next => {
6549
+ saving || save(() => set(next));
6144
6550
  },
6145
6551
  value: current
6146
6552
  }),
@@ -6245,15 +6651,15 @@ function Hairline({weight: weight, style: style}) {
6245
6651
  });
6246
6652
  }
6247
6653
 
6248
- function CollapsibleBand({header: header, children: children, background: background = !1, defaultOpen: defaultOpen = !0, sticky: sticky = !1, title: title}) {
6249
- const [open, setOpen] = useState(defaultOpen), verb = open ? "Collapse" : "Expand";
6654
+ function CollapsibleBand({header: header, children: children, background: background = !1, defaultOpen: defaultOpen = !0, onToggle: onToggle, open: controlledOpen, sticky: sticky = !1, title: title}) {
6655
+ const [heldOpen, setHeldOpen] = useState(defaultOpen), open = controlledOpen ?? heldOpen, toggle = onToggle ?? (() => setHeldOpen(value => !value)), verb = open ? "Collapse" : "Expand";
6250
6656
  /* @__PURE__ */
6251
6657
  return jsxs(Stack, {
6252
6658
  gap: 1,
6253
6659
  children: [
6254
6660
  /* @__PURE__ */ jsx(Card, {
6255
6661
  onDoubleClick: event => {
6256
- event.target instanceof Element && event.target.closest("a, button") || setOpen(v => !v);
6662
+ event.target instanceof Element && event.target.closest("a, button") || toggle();
6257
6663
  },
6258
6664
  padding: 1,
6259
6665
  radius: 3,
@@ -6274,7 +6680,7 @@ function CollapsibleBand({header: header, children: children, background: backgr
6274
6680
  fontSize: 1,
6275
6681
  icon: open ? ChevronDownIcon : ChevronRightIcon,
6276
6682
  mode: "bleed",
6277
- onClick: () => setOpen(v => !v),
6683
+ onClick: toggle,
6278
6684
  padding: 2
6279
6685
  }), header ]
6280
6686
  })
@@ -6617,20 +7023,7 @@ function InstanceDebugMenu({entry: entry}) {
6617
7023
  }
6618
7024
 
6619
7025
  function InstanceAccordion({entry: entry, children: children, defaultOpen: defaultOpen}) {
6620
- const definition = useDefinition(entry), {instance: instance, ready: ready} = entry, settled = !isLiveEntry(entry), title = instanceTitle(instance, definition), badgeTrim = useBadgeCapTrim(), status = /* @__PURE__ */ jsxs(Flex, {
6621
- align: "center",
6622
- gap: 2,
6623
- style: badgeTrim,
6624
- children: [
6625
- /* @__PURE__ */ jsx(AbortedBadge, {
6626
- instance: instance
6627
- }), settled ? null : /* @__PURE__ */ jsx(OlderDefinitionBadge, {
6628
- instance: instance
6629
- }), !ready && entry.invalid === void 0 ? /* @__PURE__ */ jsx(Spinner, {
6630
- muted: !0,
6631
- size: 1
6632
- }) : null ]
6633
- });
7026
+ const definition = useDefinition(entry), {instance: instance} = entry, settled = !isLiveEntry(entry), title = instanceTitle(instance, definition), badgeTrim = useBadgeCapTrim();
6634
7027
  /* @__PURE__ */
6635
7028
  return jsx(CollapsibleBand, {
6636
7029
  defaultOpen: defaultOpen ?? !settled,
@@ -6653,7 +7046,18 @@ function InstanceAccordion({entry: entry, children: children, defaultOpen: defau
6653
7046
  textOverflow: "ellipsis",
6654
7047
  weight: "medium",
6655
7048
  children: title
6656
- }), status ]
7049
+ }),
7050
+ /* @__PURE__ */ jsxs(Flex, {
7051
+ align: "center",
7052
+ gap: 2,
7053
+ style: badgeTrim,
7054
+ children: [
7055
+ /* @__PURE__ */ jsx(AbortedBadge, {
7056
+ instance: instance
7057
+ }), settled ? null : /* @__PURE__ */ jsx(OlderDefinitionBadge, {
7058
+ instance: instance
7059
+ }) ]
7060
+ }) ]
6657
7061
  }),
6658
7062
  /* @__PURE__ */ jsx(TrailingHairline, {}),
6659
7063
  /* @__PURE__ */ jsx(InstanceDebugMenu, {
@@ -6671,7 +7075,7 @@ function InstanceAccordion({entry: entry, children: children, defaultOpen: defau
6671
7075
  }
6672
7076
 
6673
7077
  function PartOfBand({instance: instance}) {
6674
- const parentRef = instance.ancestors.at(-1), parentId = parentRef ? extractDocumentId(parentRef.id) : null, parentEntry = useWorkflowInstanceEntry(parentId), parentDefinition = useDefinition(parentEntry);
7078
+ const parentRef = instance.ancestors.at(-1), parentId = parentRef ? toBareId(parentRef.id) : null, parentEntry = useWorkflowInstanceEntry(parentId), parentDefinition = useDefinition(parentEntry);
6675
7079
  if (!parentId) return null;
6676
7080
  const title = parentEntry ? instanceTitle(parentEntry.instance, parentDefinition) : parentId;
6677
7081
  /* @__PURE__ */
@@ -7187,7 +7591,12 @@ function StageSection({entry: entry, onOpenActivity: onOpenActivity}) {
7187
7591
  }
7188
7592
 
7189
7593
  const VIEW_TAB_CODEC = workflowsTabCodec([ "overview", "for-me" ]), WorkflowsView = props => {
7190
- 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);
7594
+ const {documentId: documentId, schemaType: schemaType} = props, {isDocResolved: isDocResolved, mappings: mappings, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {entries: entries} = useWorkflowsForDocument(documentId), {view: view} = useWorkflowsTab();
7595
+ useLogEventOnMount(WorkflowDocumentViewOpened, {
7596
+ tab: view,
7597
+ via: "open"
7598
+ });
7599
+ const resolved = isDocResolved(documentId), docTypeMappings = mappingsForDocType(mappings, schemaType.name);
7191
7600
  if (entries.length === 0) return discoveryInvalid !== void 0 ? /* @__PURE__ */ jsx(Box, {
7192
7601
  padding: 4,
7193
7602
  children: /* @__PURE__ */ jsx(InvalidDocNotice, {
@@ -7228,11 +7637,9 @@ const VIEW_TAB_CODEC = workflowsTabCodec([ "overview", "for-me" ]), WorkflowsVie
7228
7637
  };
7229
7638
 
7230
7639
  function DiscoverySpinner() {
7231
- const [visible, setVisible] = useState(!1);
7232
- return useEffect(() => {
7233
- const timer = setTimeout(() => setVisible(!0), 1e3);
7234
- return () => clearTimeout(timer);
7235
- }, []), /* @__PURE__ */ jsx(Flex, {
7640
+ const visible = useDelayedFlag(!0, 1e3);
7641
+ /* @__PURE__ */
7642
+ return jsx(Flex, {
7236
7643
  align: "center",
7237
7644
  height: "fill",
7238
7645
  justify: "center",
@@ -7242,7 +7649,7 @@ function DiscoverySpinner() {
7242
7649
  });
7243
7650
  }
7244
7651
 
7245
- const GROUP_GAP = 1;
7652
+ const BOOT_CUE_DELAY_MS = 400, GROUP_GAP = 1;
7246
7653
 
7247
7654
  function useWorkflowsTab() {
7248
7655
  const {params: params, setParams: setParams} = usePaneRouter();
@@ -7267,7 +7674,7 @@ function useOpenActivity(entries) {
7267
7674
  }
7268
7675
 
7269
7676
  function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappings: docTypeMappings, initialValue: initialValue}) {
7270
- const identity = useAssignmentIdentity(), panelId = useId(), {params: params} = 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] = useState(() => focusedLanding(params?.[WORKFLOWS_FOCUS_PARAM], entries.map(e => e.instance._id))), assignedWorkUnknown = active.some(e => e.invalid !== void 0), sectionFor = e => /* @__PURE__ */ jsx(WorkflowInstanceSection, {
7677
+ const identity = useAssignmentIdentity(), panelId = useId(), {params: params} = usePaneRouter(), {view: view, setView: setView} = useWorkflowsTab(), telemetry = useWorkflowTelemetry(), {openActivity: openActivity, setOpenActivity: setOpenActivity, dialogEntry: dialogEntry} = useOpenActivity(entries), active = entries.filter(isLiveEntry), finished = entries.filter(e => !isLiveEntry(e)), forMe = forMeWorkOf(active, identity), [landing] = 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__ */ jsx(WorkflowInstanceSection, {
7271
7678
  defaultOpen: landingDefaultOpen(landing, e.instance._id),
7272
7679
  entry: e,
7273
7680
  onOpenActivity: activityName => setOpenActivity({
@@ -7288,6 +7695,7 @@ function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappin
7288
7695
  padding: 3,
7289
7696
  children: [
7290
7697
  /* @__PURE__ */ jsx(GroupHeading, {
7698
+ busy: booting,
7291
7699
  count: active.length,
7292
7700
  end:
7293
7701
  /* @__PURE__ */ jsx(Box, {
@@ -7300,6 +7708,7 @@ function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappin
7300
7708
  initialValue: initialValue,
7301
7709
  mappings: docTypeMappings,
7302
7710
  mode: "bleed",
7711
+ source: "document-view-header",
7303
7712
  tone: "default"
7304
7713
  })
7305
7714
  }),
@@ -7308,7 +7717,12 @@ function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappin
7308
7717
  /* @__PURE__ */ jsx(TabSwitch, {
7309
7718
  ariaControls: `${panelId}-panel`,
7310
7719
  idPrefix: panelId,
7311
- onSelect: setView,
7720
+ onSelect: next => {
7721
+ next !== view && telemetry.log(WorkflowDocumentViewOpened, {
7722
+ tab: next,
7723
+ via: "tab-switch"
7724
+ }), setView(next);
7725
+ },
7312
7726
  options: [ {
7313
7727
  value: "overview",
7314
7728
  label: "Overview"
@@ -7382,6 +7796,7 @@ function OverviewEmptyState({mappings: mappings, docId: docId, initialValue: ini
7382
7796
  label: "Start workflow",
7383
7797
  mappings: mappings,
7384
7798
  mode: "ghost",
7799
+ source: "document-view-empty-state",
7385
7800
  tone: "default"
7386
7801
  })
7387
7802
  },
@@ -7415,7 +7830,8 @@ function SectionActivityDialog({entry: entry, activityName: activityName, onClos
7415
7830
  breadcrumb: instanceBreadcrumb(instance, definition),
7416
7831
  definition: definition,
7417
7832
  entry: entry,
7418
- onClose: onClose
7833
+ onClose: onClose,
7834
+ source: "document-view"
7419
7835
  });
7420
7836
  }
7421
7837
 
@@ -7423,10 +7839,8 @@ function workflowsView(S) {
7423
7839
  return S.view.component(WorkflowsView).id(WORKFLOWS_VIEW_ID).title("Workflows");
7424
7840
  }
7425
7841
 
7426
- function workflowDefaultDocumentNode(options) {
7427
- return (S, ctx) => {
7428
- if (mappingForDocType(options.mappings, ctx.schemaType)) return S.document().views([ S.view.form(), workflowsView(S) ]);
7429
- };
7842
+ function workflowDefaultDocumentNode() {
7843
+ return S => S.document().views([ S.view.form(), workflowsView(S) ]);
7430
7844
  }
7431
7845
 
7432
7846
  function lockTitleFor(args) {
@@ -7439,26 +7853,12 @@ function lockTitleFor(args) {
7439
7853
  return workflows.length === 1 ? `The ${workflowList} workflow is holding this document.` : `The ${workflowList} workflows are holding this document.`;
7440
7854
  }
7441
7855
 
7442
- const EMPTY = {
7443
- byType: /* @__PURE__ */ new Map,
7444
- definitions: /* @__PURE__ */ new Map,
7445
- warnings: []
7446
- };
7447
-
7448
7856
  function resolveAutoStart(args) {
7449
- const {autoStart: autoStart, context: context, knownDocTypes: knownDocTypes, definitions: definitions} = args;
7450
- if (autoStart === void 0) return EMPTY;
7451
- const evaluated = evaluateMap(autoStart, context);
7452
- if ("warning" in evaluated) return {
7453
- byType: /* @__PURE__ */ new Map,
7454
- definitions: /* @__PURE__ */ new Map,
7455
- warnings: [ evaluated.warning ]
7456
- };
7457
- const map = evaluated.map, latestByName = new Map(latestDeployedDefinitions(definitions).map(d => [ d.name, d ])), byType = /* @__PURE__ */ new Map, resolvedDefinitions = /* @__PURE__ */ new Map, warnings = [];
7458
- for (const [docType, spec] of Object.entries(map)) {
7857
+ const {autoStart: autoStart, knownDocTypes: knownDocTypes, definitions: definitions} = args, latestByName = new Map(latestDeployedDefinitions(definitions).map(d => [ d.name, d ])), byType = /* @__PURE__ */ new Map, resolvedDefinitions = /* @__PURE__ */ new Map, warnings = [];
7858
+ for (const [docType, names] of Object.entries(autoStart)) {
7459
7859
  const entry = resolveEntry({
7460
7860
  docType: docType,
7461
- spec: spec,
7861
+ names: names,
7462
7862
  knownDocTypes: knownDocTypes,
7463
7863
  latestByName: latestByName
7464
7864
  });
@@ -7474,35 +7874,15 @@ function resolveAutoStart(args) {
7474
7874
  };
7475
7875
  }
7476
7876
 
7477
- function evaluateMap(autoStart, context) {
7478
- let map;
7479
- try {
7480
- map = typeof autoStart == "function" ? autoStart(context) : autoStart;
7481
- } catch (err) {
7482
- return {
7483
- warning: `autoStart: config function threw — ignoring it (${describeError(err)}).`
7484
- };
7485
- }
7486
- return typeof map != "object" || map === null ? {
7487
- warning: "autoStart: config did not resolve to an object — ignoring it."
7488
- } : {
7489
- map: map
7490
- };
7491
- }
7492
-
7493
- function specNames(spec) {
7494
- return typeof spec == "string" ? [ spec ] : Array.isArray(spec) ? spec : [];
7495
- }
7496
-
7497
7877
  function resolveEntry(args) {
7498
- const {docType: docType, spec: spec, knownDocTypes: knownDocTypes, latestByName: latestByName} = args;
7878
+ const {docType: docType, names: names, knownDocTypes: knownDocTypes, latestByName: latestByName} = args;
7499
7879
  if (!knownDocTypes.has(docType)) return {
7500
7880
  valid: [],
7501
7881
  definitions: [],
7502
7882
  warnings: [ `autoStart: no schema type "${docType}" — entry skipped.` ]
7503
7883
  };
7504
7884
  const valid = [], definitions = [], warnings = [];
7505
- for (const name of specNames(spec)) {
7885
+ for (const name of names) {
7506
7886
  const definition = latestByName.get(name), issue = workflowIssue({
7507
7887
  name: name,
7508
7888
  docType: docType,
@@ -7528,11 +7908,57 @@ function workflowIssue(args) {
7528
7908
  const {name: name, docType: docType, definition: definition} = args;
7529
7909
  if (definition === void 0) return `autoStart: "${docType}" → workflow "${name}" is not deployed — skipped.`;
7530
7910
  if (!isStartableDefinition(definition)) return `autoStart: "${docType}" → workflow "${name}" is spawn-only, not startable — skipped.`;
7531
- if (!acceptsDocumentType({
7532
- fields: [ ...definition.fields ?? [] ]
7533
- }, docType)) return `autoStart: "${docType}" → workflow "${name}" has no subject accepting "${docType}" — skipped.`;
7534
- const subject = (definition.fields ?? []).find(isSubjectEntry);
7535
- if (subject !== void 0 && !isInputSourced(subject)) return `autoStart: "${docType}" → workflow "${name}" has a self-filling subject (not caller-provided), so the document can't be its subject — skipped.`;
7911
+ const subject = documentSubjectEntry(definition), acceptsType = subject?.types === void 0 || subject.types.includes(docType);
7912
+ if (subject === void 0 || !acceptsType) return `autoStart: "${docType}" workflow "${name}" has no document subject accepting "${docType}" — skipped.`;
7913
+ if (!isInputSourced(subject)) return `autoStart: "${docType}" → workflow "${name}" has a self-filling subject (not caller-provided), so the document can't be its subject — skipped.`;
7914
+ }
7915
+
7916
+ function actualType$(previews, bareId) {
7917
+ return previews.unstable_observeDocumentPairAvailability(bareId).pipe(switchMap(({draft: draft, published: published}) => draft.available ? previews.observeDocumentTypeFromId(getDraftId(bareId)) : published.available ? previews.observeDocumentTypeFromId(bareId) : draft.reason === "PERMISSION_DENIED" || published.reason === "PERMISSION_DENIED" ? of(void 0) : previews.unstable_observeVersionDocumentIds(bareId).pipe(switchMap(([firstVersionId]) => firstVersionId === void 0 ? of(null) : previews.observeDocumentTypeFromId(firstVersionId)))), distinctUntilChanged());
7918
+ }
7919
+
7920
+ const REBUILD_DELAY_MS = 5e3;
7921
+
7922
+ function createActualTypeProbeStore(previews) {
7923
+ const verdicts = /* @__PURE__ */ new Map, live = /* @__PURE__ */ new Map, listeners = /* @__PURE__ */ new Set, notify = () => {
7924
+ for (const listener of [ ...listeners ]) listener();
7925
+ }, start = (bareId, entry) => actualType$(previews, bareId).subscribe({
7926
+ next: value => {
7927
+ verdicts.has(bareId) && verdicts.get(bareId) === value || (verdicts.set(bareId, value),
7928
+ notify());
7929
+ },
7930
+ error: err => {
7931
+ console.error(`[workflow-studio-plugin] actual-type probe for "${bareId}" failed:`, err),
7932
+ entry.subscription = void 0, entry.rebuildTimer = setTimeout(() => {
7933
+ entry.rebuildTimer = void 0, !(live.get(bareId) !== entry || entry.count === 0) && (entry.subscription = start(bareId, entry));
7934
+ }, REBUILD_DELAY_MS);
7935
+ }
7936
+ });
7937
+ return {
7938
+ subscribe(listener) {
7939
+ return listeners.add(listener), () => {
7940
+ listeners.delete(listener);
7941
+ };
7942
+ },
7943
+ read: bareId => verdicts.get(bareId),
7944
+ track(bareId) {
7945
+ let entry = live.get(bareId);
7946
+ if (entry === void 0) {
7947
+ const created = {
7948
+ count: 0,
7949
+ subscription: void 0,
7950
+ rebuildTimer: void 0
7951
+ };
7952
+ live.set(bareId, created), created.subscription = start(bareId, created), entry = created;
7953
+ }
7954
+ entry.count += 1;
7955
+ let released = !1;
7956
+ return () => {
7957
+ released || (released = !0, entry.count -= 1, !(entry.count > 0) && (entry.subscription?.unsubscribe(),
7958
+ entry.rebuildTimer !== void 0 && clearTimeout(entry.rebuildTimer), live.get(bareId) === entry && live.delete(bareId)));
7959
+ };
7960
+ }
7961
+ };
7536
7962
  }
7537
7963
 
7538
7964
  function contentDocumentTypes(schema) {
@@ -7541,7 +7967,7 @@ function contentDocumentTypes(schema) {
7541
7967
 
7542
7968
  async function readDeployedDefinitions(engine) {
7543
7969
  return (await engine.query({
7544
- groq: definitionsListGroq("desc")
7970
+ groq: latestDefinitionsGroq()
7545
7971
  })).map(assertReadableModel);
7546
7972
  }
7547
7973
 
@@ -7552,20 +7978,31 @@ const EMPTY_SNAPSHOT = {
7552
7978
  };
7553
7979
 
7554
7980
  function useDiscoveryState(args) {
7555
- const {registered: registered, requested: requested, contentResource: contentResource} = args, watching = registered.length + requested.length > 0, [latest, setLatest] = useState(EMPTY_SNAPSHOT), [resolved, setResolved] = useState(EMPTY_SNAPSHOT), onSnapshot = useCallback(snapshot => {
7556
- setLatest(snapshot), snapshot.instances !== void 0 && setResolved(snapshot);
7557
- }, []);
7558
- useEffect(() => {
7559
- watching || (setLatest(EMPTY_SNAPSHOT), setResolved(EMPTY_SNAPSHOT));
7560
- }, [ watching ]);
7561
- const filter = useMemo(() => ({
7562
- documents: registered.map(id => gdrFromResource(contentResource, id)),
7981
+ const {observed: observed, requested: requested, contentResource: contentResource} = args, documentWatching = observed.length > 0, requestedWatching = requested.length > 0, documents = useDiscoveryStream(documentWatching), explicit = useDiscoveryStream(requestedWatching), documentFilter = useMemo(() => documentWatching ? {
7982
+ documents: observed.map(id => gdrFromResource(contentResource, id)),
7983
+ includeCompleted: !0
7984
+ } : void 0, [ documentWatching, observed, contentResource ]), requestedFilter = useMemo(() => requestedWatching ? {
7563
7985
  ids: requested,
7564
7986
  includeCompleted: !0
7565
- }), [ registered, requested, contentResource ]);
7987
+ } : void 0, [ requestedWatching, requested ]), resolved = useMemo(() => mergeResolved(documents.resolved, explicit.resolved), [ documents.resolved, explicit.resolved ]);
7566
7988
  return {
7567
- watching: watching,
7568
- filter: filter,
7989
+ documentFilter: documentFilter,
7990
+ requestedFilter: requestedFilter,
7991
+ loading: documents.loading || explicit.loading,
7992
+ invalid: documents.invalid ?? explicit.invalid,
7993
+ resolved: resolved,
7994
+ onDocumentSnapshot: documents.onSnapshot,
7995
+ onRequestedSnapshot: explicit.onSnapshot
7996
+ };
7997
+ }
7998
+
7999
+ function useDiscoveryStream(watching) {
8000
+ const [latest, setLatest] = useState(EMPTY_SNAPSHOT), [resolved, setResolved] = useState(EMPTY_SNAPSHOT), onSnapshot = useCallback(snapshot => {
8001
+ setLatest(snapshot), snapshot.instances !== void 0 && setResolved(snapshot);
8002
+ }, []);
8003
+ return useEffect(() => {
8004
+ watching || (setLatest(EMPTY_SNAPSHOT), setResolved(EMPTY_SNAPSHOT));
8005
+ }, [ watching ]), {
7569
8006
  loading: watching && latest.instances === void 0 && latest.invalid === void 0,
7570
8007
  invalid: watching ? latest.invalid : void 0,
7571
8008
  resolved: resolved,
@@ -7573,18 +8010,62 @@ function useDiscoveryState(args) {
7573
8010
  };
7574
8011
  }
7575
8012
 
7576
- function indexDiscoveredInstances(args) {
7577
- const {instances: instances, registered: registered, contentResource: contentResource} = args, ordered = [ ...instances ].sort((a, b) => b.startedAt.localeCompare(a.startedAt)), index = /* @__PURE__ */ new Map;
7578
- for (const bareId of registered) {
7579
- const uri = gdrFromResource(contentResource, bareId), watching = ordered.filter(inst => instanceWatchesDocument(inst, uri));
7580
- watching.length > 0 && index.set(bareId, watching.map(inst => inst._id));
8013
+ function mergeResolved(documents, explicit) {
8014
+ const rows = [ ...documents.instances ?? [], ...explicit.instances ?? [] ], freshest = /* @__PURE__ */ new Map;
8015
+ for (const instance of rows) {
8016
+ const current = freshest.get(instance._id);
8017
+ (current === void 0 || Date.parse(instance.lastChangedAt) > Date.parse(current.lastChangedAt)) && freshest.set(instance._id, instance);
7581
8018
  }
8019
+ return {
8020
+ instances: documents.instances === void 0 && explicit.instances === void 0 ? void 0 : [ ...freshest.values() ],
8021
+ invalid: documents.invalid ?? explicit.invalid,
8022
+ registered: documents.registered
8023
+ };
8024
+ }
8025
+
8026
+ function createDiscoveryIndexer(deriveWatchSet = subscriptionDocumentsForInstance) {
8027
+ const cached = /* @__PURE__ */ new Map;
8028
+ return {
8029
+ index(args) {
8030
+ const liveIds = new Set(args.instances.map(instance => instance._id));
8031
+ for (const id of cached.keys()) liveIds.has(id) || cached.delete(id);
8032
+ const watchedById = /* @__PURE__ */ new Map;
8033
+ for (const instance of args.instances) {
8034
+ let row = cached.get(instance._id);
8035
+ row?.source !== instance && (row = {
8036
+ source: instance,
8037
+ globalDocumentIds: [ ...new Set(deriveWatchSet(instance).documents.map(document => document.globalDocumentId)) ]
8038
+ }, cached.set(instance._id, row)), watchedById.set(instance._id, row.globalDocumentIds);
8039
+ }
8040
+ return buildDiscoveryIndex(args, watchedById);
8041
+ }
8042
+ };
8043
+ }
8044
+
8045
+ function buildDiscoveryIndex(args, watchedById) {
8046
+ 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 => [ gdrFromResource(contentResource, bareId), bareId ]));
8047
+ for (const instance of ordered) appendInstanceWatches({
8048
+ index: index,
8049
+ registeredByUri: registeredByUri,
8050
+ instanceId: instance._id,
8051
+ watched: watchedById.get(instance._id) ?? []
8052
+ });
7582
8053
  return {
7583
8054
  instances: new Map(ordered.map(inst => [ inst._id, inst ])),
7584
8055
  index: index
7585
8056
  };
7586
8057
  }
7587
8058
 
8059
+ function appendInstanceWatches(args) {
8060
+ const {index: index, registeredByUri: registeredByUri, instanceId: instanceId, watched: watched} = args;
8061
+ for (const uri of watched) {
8062
+ const bareId = registeredByUri.get(uri);
8063
+ if (bareId === void 0) continue;
8064
+ const ids = index.get(bareId);
8065
+ ids === void 0 ? index.set(bareId, [ instanceId ]) : ids.push(instanceId);
8066
+ }
8067
+ }
8068
+
7588
8069
  function sameInstance(a, b) {
7589
8070
  return a === b || a._id === b._id && a._rev === b._rev;
7590
8071
  }
@@ -7608,7 +8089,7 @@ function entryOf(inst, report) {
7608
8089
  instance: report?.evaluation?.instance ?? inst,
7609
8090
  committed: inst,
7610
8091
  evaluation: report?.evaluation,
7611
- ready: report?.ready ?? !1,
8092
+ ready: report?.ready ?? terminalState(inst) !== "in-flight",
7612
8093
  invalid: report?.invalid,
7613
8094
  guards: report?.guards
7614
8095
  };
@@ -7701,7 +8182,19 @@ function createEntriesStore() {
7701
8182
  };
7702
8183
  }
7703
8184
 
7704
- const notReady = (..._args) => Promise.reject(new Error("workflow session not ready yet"));
8185
+ function sessionPlan(args) {
8186
+ const demandedIds = new Set(args.demanded), evaluated = [ ...args.discovered.values() ].filter(inst => demandedIds.has(inst._id));
8187
+ return {
8188
+ evaluated: evaluated,
8189
+ guardScope: evaluated.map(inst => inst._id).sort()
8190
+ };
8191
+ }
8192
+
8193
+ function useDefaultLayout(props) {
8194
+ return useMemo(() => props.renderDefault(props), [ props ]);
8195
+ }
8196
+
8197
+ const notReady = (..._args) => Promise.reject(new Error("workflow session not ready yet")), NO_REGISTERED_DOCUMENTS = [], DEMAND_RELEASE_GRACE_MS = 500;
7705
8198
 
7706
8199
  function workspaceParts(config) {
7707
8200
  if (!config.projectId || !config.dataset) throw new Error("workflow plugin requires a workspace with projectId and dataset");
@@ -7729,10 +8222,11 @@ class InstanceEvaluatorBoundary extends Component {
7729
8222
  }
7730
8223
 
7731
8224
  function InstanceEvaluator(props) {
7732
- 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} = useWorkflowSession({
8225
+ 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} = useWorkflowSession({
7733
8226
  engine: engine,
7734
8227
  instanceId: instanceId,
7735
- grantsFromPath: grantsFromPath
8228
+ grantsFromPath: grantsFromPath,
8229
+ guardScope: guardScope
7736
8230
  });
7737
8231
  return useEffect(() => {
7738
8232
  onReport(instanceId, {
@@ -7775,9 +8269,9 @@ function engineOptionsFrom(config) {
7775
8269
  }
7776
8270
 
7777
8271
  function WorkflowProvider(props) {
7778
- const {config: config} = props, studioClient = useClient({
8272
+ const {config: config} = props, defaultLayout = useDefaultLayout(props), studioClient = useClient({
7779
8273
  apiVersion: WORKFLOW_API_VERSION
7780
- }), schema = useSchema(), {name: workspaceName} = useWorkspace(), {projectId: projectId, dataset: contentDataset} = workspaceParts(studioClient.config()), workflowDataset = config.workflowDataset ?? contentDataset, binding = useMemo(() => ({
8274
+ }), schema = useSchema(), {projectId: projectId, dataset: contentDataset} = workspaceParts(studioClient.config()), workflowDataset = config.workflowDataset ?? contentDataset, binding = useMemo(() => ({
7781
8275
  engineResource: {
7782
8276
  type: "dataset",
7783
8277
  id: `${projectId}.${workflowDataset}`
@@ -7795,11 +8289,19 @@ function WorkflowProvider(props) {
7795
8289
  }), grantsFromPath = aclPathForResource({
7796
8290
  type: "dataset",
7797
8291
  id: `${projectId}.${workflowDataset}`
7798
- }), {ids: registered, acquire: register, release: unregister} = useRefcountedIds(), {ids: requested, acquire: requestInstance, release: releaseInstance} = useRefcountedIds(), [seeded, setSeeded] = useState(/* @__PURE__ */ new Map), seedInstance = useCallback(instance => {
8292
+ }), {ids: registered, acquire: registerActive, release: unregisterActive} = useRefcountedIds(), {ids: observed, acquire: retainRegistration, release: releaseRegistration} = useRefcountedIds({
8293
+ releaseDelayMs: DEMAND_RELEASE_GRACE_MS
8294
+ }), register = useCallback(id => {
8295
+ registerActive(id), retainRegistration(id);
8296
+ }, [ registerActive, retainRegistration ]), unregister = useCallback(id => {
8297
+ unregisterActive(id), releaseRegistration(id);
8298
+ }, [ unregisterActive, releaseRegistration ]), {ids: requested, acquire: requestInstance, release: releaseInstance} = useRefcountedIds(), {ids: evaluationDemand, acquire: requestEvaluation, release: releaseEvaluation} = useRefcountedIds({
8299
+ releaseDelayMs: DEMAND_RELEASE_GRACE_MS
8300
+ }), [seeded, setSeeded] = useState(/* @__PURE__ */ new Map), seedInstance = useCallback(instance => {
7799
8301
  setSeeded(prev => new Map(prev).set(instance._id, instance));
7800
8302
  }, []), [startRequest2, setStartRequest] = useState(void 0), openStartDialog = useCallback(r => setStartRequest(r), []), closeStartDialog = useCallback(() => setStartRequest(void 0), []), [entriesStore] = useState(createEntriesStore);
7801
8303
  useEffect(() => () => entriesStore.dispose(), [ entriesStore ]);
7802
- const onReport = useCallback((instanceId, report) => {
8304
+ const previewStore = useDocumentPreviewStore(), actualTypes = useMemo(() => createActualTypeProbeStore(previewStore), [ previewStore ]), onReport = useCallback((instanceId, report) => {
7803
8305
  entriesStore.report(instanceId, report);
7804
8306
  }, [ entriesStore ]), drainRef = useRef(void 0), drainCommitted = useCallback(async instanceId => {
7805
8307
  try {
@@ -7846,15 +8348,18 @@ function WorkflowProvider(props) {
7846
8348
  });
7847
8349
  }, [ engine ]);
7848
8350
  drainRef.current = drainEffectsFor;
7849
- const {watching: watching, filter: filter, loading: loading, invalid: discoveryInvalid, resolved: resolved, onSnapshot: onSnapshot} = useDiscoveryState({
7850
- registered: registered,
8351
+ const {documentFilter: documentFilter, requestedFilter: requestedFilter, loading: loading, invalid: discoveryInvalid, resolved: resolved, onDocumentSnapshot: onDocumentSnapshot, onRequestedSnapshot: onRequestedSnapshot} = useDiscoveryState({
8352
+ observed: observed,
7851
8353
  requested: requested,
7852
8354
  contentResource: binding.contentResource
7853
- }), {instances: discovered, index: docIndex} = useMemo(() => indexDiscoveredInstances({
8355
+ }), discoveryIndexer = useMemo(createDiscoveryIndexer, []), {instances: discovered, index: docIndex} = useMemo(() => discoveryIndexer.index({
7854
8356
  instances: resolved.instances ?? [],
7855
8357
  registered: resolved.registered,
7856
8358
  contentResource: binding.contentResource
7857
- }), [ resolved, binding.contentResource ]), resolvedDocs = useMemo(() => new Set(resolved.registered), [ resolved.registered ]);
8359
+ }), [ discoveryIndexer, resolved, binding.contentResource ]), {evaluated: evaluated, guardScope: guardScope} = useMemo(() => sessionPlan({
8360
+ discovered: discovered,
8361
+ demanded: evaluationDemand
8362
+ }), [ discovered, evaluationDemand ]), resolvedDocs = useMemo(() => new Set(resolved.registered), [ resolved.registered ]);
7858
8363
  useEffect(() => {
7859
8364
  setSeeded(prev => {
7860
8365
  const kept = [ ...prev ].filter(([id]) => !discovered.has(id));
@@ -7867,22 +8372,23 @@ function WorkflowProvider(props) {
7867
8372
  docIndex: docIndex
7868
8373
  });
7869
8374
  }, [ entriesStore, seeded, discovered, docIndex ]);
7870
- const isDocResolved = useCallback(docId => resolvedDocs.has(docId), [ resolvedDocs ]), schemaContentTypes = useMemo(() => new Set(contentDocumentTypes(schema)), [ schema ]), {issues: issues, starts: starts, fields: fields, latestVersions: latestVersions, autoStartByType: autoStartByType, autoStartDefinitions: autoStartDefinitions} = useDeployedDefinitions({
8375
+ const isDocResolved = useCallback(docId => resolvedDocs.has(docId), [ resolvedDocs ]), schemaContentTypes = useMemo(() => new Set(contentDocumentTypes(schema)), [ schema ]), {mappings: mappings, issues: issues, starts: starts, fields: fields, latestVersions: latestVersions, autoStartByType: autoStartByType, autoStartDefinitions: autoStartDefinitions} = useDeployedDefinitions({
7871
8376
  engine: engine,
7872
- mappings: config.mappings,
8377
+ overrides: config.mappings,
7873
8378
  contentResource: binding.contentResource,
7874
8379
  schemaContentTypes: schemaContentTypes,
7875
8380
  startRequest: startRequest2,
7876
- autoStart: config.autoStart,
7877
- schema: schema,
7878
- workspaceName: workspaceName
8381
+ schema: schema
7879
8382
  }), value = useMemo(() => ({
7880
8383
  register: register,
7881
8384
  unregister: unregister,
7882
8385
  isDocResolved: isDocResolved,
7883
8386
  entries: entriesStore,
8387
+ actualTypes: actualTypes,
7884
8388
  requestInstance: requestInstance,
7885
8389
  releaseInstance: releaseInstance,
8390
+ requestEvaluation: requestEvaluation,
8391
+ releaseEvaluation: releaseEvaluation,
7886
8392
  seedInstance: seedInstance,
7887
8393
  openStartDialog: openStartDialog,
7888
8394
  closeStartDialog: closeStartDialog,
@@ -7897,7 +8403,7 @@ function WorkflowProvider(props) {
7897
8403
  completeEffectFor: completeEffectFor,
7898
8404
  engine: engine,
7899
8405
  binding: binding,
7900
- mappings: config.mappings,
8406
+ mappings: mappings,
7901
8407
  mappingIssues: issues,
7902
8408
  mappingStarts: starts,
7903
8409
  mappingFields: fields,
@@ -7905,24 +8411,30 @@ function WorkflowProvider(props) {
7905
8411
  effectHandlers: engine.effectHandlers,
7906
8412
  autoStartByType: autoStartByType,
7907
8413
  autoStartDefinitions: autoStartDefinitions
7908
- }), [ 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 ]);
8414
+ }), [ 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 ]);
7909
8415
  /* @__PURE__ */
7910
8416
  return jsxs(WorkflowContext.Provider, {
7911
8417
  value: value,
7912
- children: [ watching ? /* @__PURE__ */ jsx(DiscoverySubscription, {
8418
+ children: [ documentFilter ? /* @__PURE__ */ jsx(DiscoverySubscription, {
7913
8419
  engine: engine,
7914
- filter: filter,
7915
- onSnapshot: onSnapshot,
8420
+ filter: documentFilter,
8421
+ onSnapshot: onDocumentSnapshot,
7916
8422
  registered: registered
7917
- }) : null, [ ...discovered.values() ].map(inst => /* @__PURE__ */ jsx(InstanceEvaluatorBoundary, {
8423
+ }) : null, requestedFilter ? /* @__PURE__ */ jsx(DiscoverySubscription, {
8424
+ engine: engine,
8425
+ filter: requestedFilter,
8426
+ onSnapshot: onRequestedSnapshot,
8427
+ registered: NO_REGISTERED_DOCUMENTS
8428
+ }) : null, evaluated.map(inst => /* @__PURE__ */ jsx(InstanceEvaluatorBoundary, {
7918
8429
  instanceId: inst._id,
7919
8430
  children: /* @__PURE__ */ jsx(InstanceEvaluator, {
7920
8431
  engine: engine,
7921
8432
  grantsFromPath: grantsFromPath,
8433
+ guardScope: guardScope,
7922
8434
  instanceId: inst._id,
7923
8435
  onReport: onReport
7924
8436
  })
7925
- }, inst._id)), props.renderDefault(props), props.overlay ]
8437
+ }, inst._id)), defaultLayout, props.overlay ]
7926
8438
  });
7927
8439
  }
7928
8440
 
@@ -7932,14 +8444,25 @@ function sameEntries(a, b) {
7932
8444
  return !0;
7933
8445
  }
7934
8446
 
8447
+ function sameMappings(a, b) {
8448
+ return a.length === b.length && a.every((mapping, index) => {
8449
+ const other = b[index];
8450
+ 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;
8451
+ });
8452
+ }
8453
+
7935
8454
  function useDeployedDefinitions(args) {
7936
- const {engine: engine, mappings: mappings, contentResource: contentResource, schemaContentTypes: schemaContentTypes, startRequest: startRequest2, autoStart: autoStart, schema: schema, workspaceName: workspaceName} = args, [issues, setIssues] = useState(/* @__PURE__ */ new Map), [starts, setStarts] = useState(/* @__PURE__ */ new Map), [fields, setFields] = useState(/* @__PURE__ */ new Map), [latestVersions, setLatestVersions] = useState(/* @__PURE__ */ new Map), [autoStartByType, setAutoStartByType] = useState(
8455
+ const {engine: engine, overrides: overrides, contentResource: contentResource, schemaContentTypes: schemaContentTypes, startRequest: startRequest2, schema: schema} = args, [mappings, setMappings] = useState([]), [issues, setIssues] = useState(/* @__PURE__ */ new Map), [starts, setStarts] = useState(/* @__PURE__ */ new Map), [fields, setFields] = useState(/* @__PURE__ */ new Map), [latestVersions, setLatestVersions] = useState(/* @__PURE__ */ new Map), [autoStartByType, setAutoStartByType] = useState(
7937
8456
  /* @__PURE__ */ new Map), [autoStartDefinitions, setAutoStartDefinitions] = useState(/* @__PURE__ */ new Map), logged = useRef(/* @__PURE__ */ new Set);
7938
8457
  return useEffect(() => {
7939
8458
  let cancelled = !1;
7940
8459
  return (async () => {
7941
- const definitions = await readDeployedDefinitions(engine), found = mappingIssues({
7942
- mappings: mappings,
8460
+ const definitions = await readDeployedDefinitions(engine), effectiveMappings = discoverWorkflowMappings({
8461
+ definitions: definitions,
8462
+ schemaContentTypes: schemaContentTypes,
8463
+ overrides: overrides
8464
+ }), found = mappingIssues({
8465
+ mappings: effectiveMappings,
7943
8466
  definitions: definitions,
7944
8467
  contentResource: contentResource,
7945
8468
  schemaContentTypes: schemaContentTypes
@@ -7949,32 +8472,30 @@ function useDeployedDefinitions(args) {
7949
8472
  logged.current.has(signature) || (logged.current.add(signature), console.error(`[workflow-studio-plugin] ${message}`));
7950
8473
  }
7951
8474
  const resolvedAutoStart = resolveAutoStart({
7952
- autoStart: autoStart,
7953
- context: {
7954
- workspaceName: workspaceName,
7955
- schema: schema
7956
- },
8475
+ autoStart: mappingAutoStartMap(effectiveMappings),
7957
8476
  knownDocTypes: new Set(schema.getTypeNames()),
7958
8477
  definitions: definitions
7959
8478
  });
7960
8479
  if (logOnce(resolvedAutoStart.warnings, logged.current), cancelled) return;
8480
+ setMappings(prev => sameMappings(prev, effectiveMappings) ? prev : effectiveMappings),
7961
8481
  setIssues(prev => sameEntries(prev, found) ? prev : found), setStarts(mappingStartBlocks({
7962
- mappings: mappings,
8482
+ mappings: effectiveMappings,
7963
8483
  definitions: definitions
7964
8484
  })), setFields(mappingDeclaredFields({
7965
- mappings: mappings,
8485
+ mappings: effectiveMappings,
7966
8486
  definitions: definitions
7967
8487
  }));
7968
8488
  const versions = new Map(latestDeployedDefinitions(definitions).map(d => [ d.name, d.version ]));
7969
8489
  setLatestVersions(prev => sameEntries(prev, versions) ? prev : versions), setAutoStartByType(resolvedAutoStart.byType),
7970
8490
  setAutoStartDefinitions(resolvedAutoStart.definitions);
7971
8491
  })().catch(err => {
7972
- console.error("[workflow-studio-plugin] mapping validation could not read the deployed definitions keeping the previous verdicts:", err);
8492
+ console.error("[workflow-studio-plugin] mapping discovery failed keeping the last resolved workflow state:", err);
7973
8493
  }), () => {
7974
8494
  cancelled = !0;
7975
8495
  };
7976
- }, [ engine, mappings, contentResource, schemaContentTypes, startRequest2, autoStart, schema, workspaceName ]),
8496
+ }, [ engine, overrides, contentResource, schemaContentTypes, startRequest2, schema ]),
7977
8497
  {
8498
+ mappings: mappings,
7978
8499
  issues: issues,
7979
8500
  starts: starts,
7980
8501
  fields: fields,
@@ -8363,12 +8884,12 @@ function DocRefsFieldInput({entry: entry, mappedType: mappedType, value: value,
8363
8884
  }
8364
8885
 
8365
8886
  function NotesStartInput({onChange: onChange}) {
8366
- const me = useCurrentUser(), [body, setBody] = useState(""), update = next => {
8887
+ const selfActor = useSelfActor(), [body, setBody] = useState(""), update = next => {
8367
8888
  setBody(next);
8368
8889
  const trimmed = next.trim();
8369
8890
  onChange(trimmed === "" ? [] : [ noteRow({
8370
8891
  body: trimmed,
8371
- actor: me ? personActor(me) : void 0,
8892
+ actor: selfActor,
8372
8893
  at: /* @__PURE__ */ (new Date).toISOString()
8373
8894
  }) ]);
8374
8895
  };
@@ -8577,7 +9098,7 @@ function handleStartError(args) {
8577
9098
  toast.push({
8578
9099
  status: "warning",
8579
9100
  title: `${label} can't be started right now`,
8580
- description: "The workflow does not allow another run with these inputs."
9101
+ description: outcome.description
8581
9102
  });
8582
9103
  return;
8583
9104
  }
@@ -8589,7 +9110,7 @@ function handleStartError(args) {
8589
9110
  }
8590
9111
 
8591
9112
  function StartWorkflowForm({request: request, onClose: onClose}) {
8592
- 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} = usePerspective(), releaseId = boundReleaseId(mapping, selectedReleaseId), [values, setValues] = useState({}), [attempt, setAttempt] = useState(void 0), starting = attempt !== void 0, [heldInstanceId, setHeldInstanceId] = useState(void 0), [startNew, setStartNew] = useState(!1), {entries: entries} = useWorkflowsForDocument(subjectDocId), resumeEntry = presentedResumeEntry({
9113
+ const {definition: definitionName, label: label, mapping: mapping, subjectDocId: subjectDocId} = request, {engine: engine, binding: binding, seedInstance: seedInstance} = useWorkflowContext(), telemetry = useWorkflowTelemetry(), toast = useClosableToast(), {def: definition, error: definitionError} = useLatestDefinition(definitionName), {selectedReleaseId: selectedReleaseId} = usePerspective(), releaseId = boundReleaseId(mapping, selectedReleaseId), [values, setValues] = useState({}), [attempt, setAttempt] = useState(void 0), starting = attempt !== void 0, [heldInstanceId, setHeldInstanceId] = useState(void 0), [startNew, setStartNew] = useState(!1), {entries: entries} = useWorkflowsForDocument(subjectDocId), resumeEntry = presentedResumeEntry({
8593
9114
  entries: entries,
8594
9115
  definitionName: definitionName,
8595
9116
  heldInstanceId: heldInstanceId,
@@ -8612,7 +9133,10 @@ function StartWorkflowForm({request: request, onClose: onClose}) {
8612
9133
  [name]: v
8613
9134
  })), heldInstanceId !== void 0 && declineResume();
8614
9135
  }, onStart = async () => {
8615
- setAttempt({
9136
+ telemetry.log(WorkflowStartDialogSubmitted, {
9137
+ source: request.source,
9138
+ resumed: resuming
9139
+ }), setAttempt({
8616
9140
  resume: resumeEntry
8617
9141
  });
8618
9142
  const instanceId = resumeEntry?.instance._id ?? heldInstanceId ?? instanceDocId(binding.tag);
@@ -8692,8 +9216,9 @@ function StartWorkflowForm({request: request, onClose: onClose}) {
8692
9216
  }
8693
9217
 
8694
9218
  function StartWorkflowDialog({request: request, onClose: onClose}) {
8695
- /* @__PURE__ */
8696
- return jsx(Dialog, {
9219
+ return useLogEventOnMount(WorkflowStartDialogOpened, {
9220
+ source: request.source
9221
+ }), /* @__PURE__ */ jsx(Dialog, {
8697
9222
  header: `Start ${request.label}`,
8698
9223
  id: "start-workflow",
8699
9224
  onClose: onClose,
@@ -8760,7 +9285,7 @@ function isFreshDocument(value) {
8760
9285
  }
8761
9286
 
8762
9287
  function subjectEntry(definition) {
8763
- return (definition.fields ?? []).find(isSubjectEntry);
9288
+ return documentSubjectEntry(definition);
8764
9289
  }
8765
9290
 
8766
9291
  function autoStartInputs(args) {
@@ -8787,11 +9312,11 @@ function pendingWorkflows(workflows, live) {
8787
9312
  }
8788
9313
 
8789
9314
  function extraRequiredInputs(definition) {
8790
- const fields = definition.fields ?? [], subjectNames = new Set(fields.filter(isSubjectEntry).map(entry => entry.name)), requiredNames = new Set(missingRequiredInputs({
9315
+ const fields = definition.fields ?? [], subjectName = subjectEntry(definition)?.name, requiredNames = new Set(missingRequiredInputs({
8791
9316
  entryDefs: fields,
8792
9317
  initialFields: []
8793
9318
  }).map(input => input.name));
8794
- return fields.filter(entry => requiredNames.has(entry.name) && !subjectNames.has(entry.name));
9319
+ return fields.filter(entry => requiredNames.has(entry.name) && entry.name !== subjectName);
8795
9320
  }
8796
9321
 
8797
9322
  function buildAutoStartRequests(args) {
@@ -8832,21 +9357,21 @@ function AutoStartGate(props) {
8832
9357
  }
8833
9358
 
8834
9359
  function AutoStartDriver(props) {
8835
- const {rawDocId: rawDocId, docType: docType, workflows: workflows, value: value, fallback: fallback} = props, {isDocResolved: isDocResolved, autoStartDefinitions: autoStartDefinitions, engine: engine, binding: binding, seedInstance: seedInstance} = useWorkflowContext(), observer = useStudioObserver({
9360
+ const {rawDocId: rawDocId, docType: docType, workflows: workflows, value: value, fallback: fallback} = props, {isDocResolved: isDocResolved, autoStartDefinitions: autoStartDefinitions, engine: engine, binding: binding, seedInstance: seedInstance} = useWorkflowContext(), telemetry = useWorkflowTelemetry(), observer = useStudioObserver({
8836
9361
  engine: engine
8837
9362
  }), {selectedReleaseId: selectedReleaseId} = usePerspective(), {entries: entries} = useWorkflowsForDocument(rawDocId), docId = getPublishedId(rawDocId), valueRef = useRef(value);
8838
9363
  valueRef.current = value;
8839
9364
  const pending = useMemo(() => pendingWorkflows(workflows, new Set(entries.map(entry => entry.instance.definition))), [ workflows, entries ]), inputs = useMemo(() => autoStartInputs({
8840
9365
  workflows: pending,
8841
9366
  definitions: autoStartDefinitions
8842
- }), [ pending, autoStartDefinitions ]), pendingRef = useRef(pending);
9367
+ }), [ pending, autoStartDefinitions ]), definitionsResolved = workflows.every(name => autoStartDefinitions.has(name)), pendingRef = useRef(pending);
8843
9368
  pendingRef.current = pending;
8844
9369
  const instanceIds = useRef(/* @__PURE__ */ new Map), instanceIdFor = useCallback(workflow => {
8845
9370
  const held = instanceIds.current.get(workflow);
8846
9371
  if (held !== void 0) return held;
8847
9372
  const id = instanceDocId(binding.tag);
8848
9373
  return instanceIds.current.set(workflow, id), id;
8849
- }, [ binding.tag ]), [fired, setFired] = useState(!1), [settled, setSettled] = useState(!1), [failure, setFailure] = useState(void 0), firedRef = useRef(!1), startAll = useCallback(async values => {
9374
+ }, [ binding.tag ]), [fired, setFired] = useState(!1), [settled, setSettled] = useState(!1), [failure, setFailure] = useState(void 0), firedRef = useRef(!1), startAll = useCallback(async (values, mode) => {
8850
9375
  if (!firedRef.current) {
8851
9376
  firedRef.current = !0, setFired(!0);
8852
9377
  try {
@@ -8858,20 +9383,25 @@ function AutoStartDriver(props) {
8858
9383
  releaseId: selectedReleaseId
8859
9384
  } : {}
8860
9385
  });
9386
+ const requests = buildAutoStartRequests({
9387
+ workflows: pendingRef.current,
9388
+ definitions: autoStartDefinitions,
9389
+ subject: {
9390
+ docId: docId,
9391
+ type: docType
9392
+ },
9393
+ contentResource: binding.contentResource,
9394
+ values: values,
9395
+ instanceIdFor: instanceIdFor
9396
+ });
9397
+ telemetry.log(WorkflowAutoStartRan, {
9398
+ mode: mode,
9399
+ workflowCount: requests.length
9400
+ });
8861
9401
  const {failure: failure2} = await runStarts({
8862
9402
  engine: engine,
8863
9403
  seed: seedInstance,
8864
- requests: buildAutoStartRequests({
8865
- workflows: pendingRef.current,
8866
- definitions: autoStartDefinitions,
8867
- subject: {
8868
- docId: docId,
8869
- type: docType
8870
- },
8871
- contentResource: binding.contentResource,
8872
- values: values,
8873
- instanceIdFor: instanceIdFor
8874
- })
9404
+ requests: requests
8875
9405
  });
8876
9406
  failure2 !== void 0 && setFailure(failure2);
8877
9407
  } catch (err) {
@@ -8880,17 +9410,17 @@ function AutoStartDriver(props) {
8880
9410
  setSettled(!0);
8881
9411
  }
8882
9412
  }
8883
- }, [ autoStartDefinitions, docId, docType, binding, engine, observer, seedInstance, instanceIdFor, selectedReleaseId ]), retry = useCallback(() => {
9413
+ }, [ autoStartDefinitions, docId, docType, binding, engine, observer, seedInstance, instanceIdFor, selectedReleaseId, telemetry ]), retry = useCallback(() => {
8884
9414
  firedRef.current = !1, setFired(!1), setSettled(!1), setFailure(void 0);
8885
9415
  }, []), action = autoStartAction({
8886
- resolved: isDocResolved(docId),
9416
+ resolved: definitionsResolved && isDocResolved(docId),
8887
9417
  pendingCount: pending.length,
8888
9418
  needsInput: inputs.length > 0,
8889
9419
  fired: fired,
8890
9420
  settled: settled
8891
9421
  });
8892
9422
  return useEffect(() => {
8893
- action === "silent-start" && startAll({});
9423
+ action === "silent-start" && startAll({}, "silent");
8894
9424
  }, [ action, startAll ]), failure !== void 0 ? /* @__PURE__ */ jsx(AutoStartFailure, {
8895
9425
  message: failure,
8896
9426
  onProceed: () => setFailure(void 0),
@@ -8902,7 +9432,7 @@ function AutoStartDriver(props) {
8902
9432
  docType: docType,
8903
9433
  inputs: inputs,
8904
9434
  onStart: values => {
8905
- startAll(values);
9435
+ startAll(values, "collect");
8906
9436
  },
8907
9437
  starting: fired,
8908
9438
  workflowCount: pending.length
@@ -9124,7 +9654,9 @@ function finishedLinesOf(entries) {
9124
9654
  }
9125
9655
 
9126
9656
  function WorkflowFormStrip(props) {
9127
- const {mappings: mappings} = props, {isDocResolved: isDocResolved, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {docId: docId, entries: entries} = useWorkflowsForDocument(props.value?._id ?? null), {params: params, setParams: setParams} = usePaneRouter(), openWorkflowsView = focusId => setParams(focusedViewParams(params, focusId)), active = entries.filter(isLiveEntry), finished = finishedLinesOf(entries), invalid = surfacedInvalid(discoveryInvalid, entries), resolved = docId !== void 0 && isDocResolved(docId);
9657
+ const {mappings: mappings} = props, {isDocResolved: isDocResolved, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {docId: docId, entries: entries} = useWorkflowsForDocument(props.value?._id ?? null), {params: params, setParams: setParams} = usePaneRouter(), telemetry = useWorkflowTelemetry(), openWorkflowsView = focusId => {
9658
+ telemetry.log(WorkflowFormStripClicked), setParams(focusedViewParams(params, focusId));
9659
+ }, active = entries.filter(isLiveEntry), finished = finishedLinesOf(entries), invalid = surfacedInvalid(discoveryInvalid, entries), resolved = docId !== void 0 && isDocResolved(docId);
9128
9660
  /* @__PURE__ */
9129
9661
  return jsxs(Stack, {
9130
9662
  gap: 4,
@@ -9158,7 +9690,8 @@ function StripLines({active: active, docId: docId, finished: finished, initialVa
9158
9690
  initialValue: initialValue,
9159
9691
  label: "Start workflow",
9160
9692
  mappings: mappings,
9161
- mode: "bleed"
9693
+ mode: "bleed",
9694
+ source: "form-strip"
9162
9695
  })
9163
9696
  }) : null ]
9164
9697
  });
@@ -9305,6 +9838,61 @@ function TaskCountSpinner({entry: entry}) {
9305
9838
  });
9306
9839
  }
9307
9840
 
9841
+ function stagePillState(instance) {
9842
+ const definition = definitionSnapshotOf(instance), stage = definition?.stages.find(candidate => candidate.name === instance.currentStage), state = terminalState(instance);
9843
+ return {
9844
+ title: stageTitle(definition, instance.currentStage),
9845
+ terminal: stage !== void 0 && isTerminalStage(stage) || state !== "in-flight",
9846
+ state: state,
9847
+ stageKnown: stage !== void 0
9848
+ };
9849
+ }
9850
+
9851
+ function WorkflowStagePreview(props) {
9852
+ 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;
9853
+ return props.renderDefault({
9854
+ ...props,
9855
+ status: lead ? /* @__PURE__ */ jsx(StagePill, {
9856
+ entry: lead,
9857
+ extraLive: extraLive
9858
+ }) : props.status
9859
+ });
9860
+ }
9861
+
9862
+ function StagePill({entry: entry, extraLive: extraLive}) {
9863
+ /* @__PURE__ */
9864
+ return jsxs(Flex, {
9865
+ align: "center",
9866
+ gap: 2,
9867
+ children: [
9868
+ /* @__PURE__ */ jsx(LeadBadge, {
9869
+ entry: entry
9870
+ }), extraLive > 0 ? /* @__PURE__ */ jsxs(Badge, {
9871
+ fontSize: 1,
9872
+ mode: "outline",
9873
+ tone: "default",
9874
+ children: [ "+", extraLive ]
9875
+ }) : null ]
9876
+ });
9877
+ }
9878
+
9879
+ function LeadBadge({entry: entry}) {
9880
+ const {instance: instance} = entry;
9881
+ if (isUnprimed(instance)) /* @__PURE__ */ return jsx(StartIncompleteBadge, {});
9882
+ const pill = stagePillState(instance);
9883
+ /* @__PURE__ */
9884
+ return jsxs(Badge, {
9885
+ fontSize: 1,
9886
+ mode: pill.terminal ? "default" : "outline",
9887
+ tone: pillTone(pill),
9888
+ children: [ pill.title, pill.state === "completed" ? " ✓" : "" ]
9889
+ });
9890
+ }
9891
+
9892
+ function pillTone(args) {
9893
+ return args.state === "aborted" ? "critical" : args.terminal ? "positive" : args.stageKnown ? "primary" : "default";
9894
+ }
9895
+
9308
9896
  function StartWorkflowDialogHost() {
9309
9897
  const {startRequest: startRequest2, closeStartDialog: closeStartDialog} = useWorkflowContext();
9310
9898
  return startRequest2 ? /* @__PURE__ */ jsx(StartWorkflowDialog, {
@@ -9377,7 +9965,8 @@ function withWorkflowLock(Action, guardAction) {
9377
9965
  Locked;
9378
9966
  }
9379
9967
 
9380
- const workflowStudioPlugin = definePlugin(config => ({
9968
+ const workflowStudioPlugin = definePlugin(config => (assertUniqueWorkflowMappings(config.mappings ?? []),
9969
+ {
9381
9970
  name: "workflow-studio-plugin",
9382
9971
  tools: prev => [ ...prev, workflowsTool ],
9383
9972
  studio: {
@@ -9391,82 +9980,35 @@ const workflowStudioPlugin = definePlugin(config => ({
9391
9980
  },
9392
9981
  form: {
9393
9982
  components: {
9394
- input: props => {
9395
- if (props.id === "root" && isObjectInputProps(props)) {
9396
- const docTypeMappings = mappingsForDocType(config.mappings, props.schemaType.name), fallback = docTypeMappings.length > 0 ? /* @__PURE__ */ jsx(WorkflowFormStrip, {
9397
- ...props,
9398
- mappings: docTypeMappings
9399
- }) : props.renderDefault(props);
9400
- /* @__PURE__ */
9401
- return jsx(AutoStartGate, {
9402
- ...props,
9403
- fallback: fallback
9404
- });
9405
- }
9406
- return props.renderDefault(props);
9407
- }
9983
+ preview: WorkflowStagePreview,
9984
+ input: props => props.id === "root" && isObjectInputProps(props) ? /* @__PURE__ */ jsx(WorkflowRootInput, {
9985
+ ...props
9986
+ }) : props.renderDefault(props)
9408
9987
  }
9409
9988
  },
9410
9989
  document: {
9411
9990
  badges: (prev, ctx) => {
9412
- if (!mappingForDocType(config.mappings, ctx.schemaType) || !ctx.documentId) return prev;
9991
+ if (!ctx.documentId) return prev;
9413
9992
  const docId = ctx.documentId;
9414
9993
  return [ makeActiveWorkflowsBadge(docId), makePerspectiveBadge(docId), ...prev ];
9415
9994
  },
9416
- actions: (prev, ctx) => mappingForDocType(config.mappings, ctx.schemaType) ? prev.map(Action => {
9995
+ actions: prev => prev.map(Action => {
9417
9996
  const guardAction = Action.action ? LOCKABLE_ACTIONS.get(Action.action) : void 0;
9418
9997
  return guardAction ? withWorkflowLock(Action, guardAction) : Action;
9419
- }) : prev
9998
+ })
9420
9999
  }
9421
10000
  }));
9422
10001
 
9423
- function WorkflowStagePreview(props) {
9424
- const {entries: entries} = useWorkflowsForDocument(props.documentId ?? props._id ?? null), lead = entries.find(isLiveEntry) ?? entries[0], extraLive = entries.filter(e => e !== lead && isLiveEntry(e)).length;
9425
- return props.renderDefault({
10002
+ function WorkflowRootInput(props) {
10003
+ const {mappings: mappings} = useWorkflowContext(), docTypeMappings = mappingsForDocType(mappings, props.schemaType.name), fallback = docTypeMappings.length > 0 ? /* @__PURE__ */ jsx(WorkflowFormStrip, {
9426
10004
  ...props,
9427
- status: lead ? /* @__PURE__ */ jsx(StagePill, {
9428
- entry: lead,
9429
- extraLive: extraLive
9430
- }) : props.status
9431
- });
9432
- }
9433
-
9434
- function StagePill({entry: entry, extraLive: extraLive}) {
10005
+ mappings: docTypeMappings
10006
+ }) : props.renderDefault(props);
9435
10007
  /* @__PURE__ */
9436
- return jsxs(Flex, {
9437
- align: "center",
9438
- gap: 2,
9439
- children: [
9440
- /* @__PURE__ */ jsx(LeadBadge, {
9441
- entry: entry
9442
- }), extraLive > 0 ? /* @__PURE__ */ jsxs(Badge, {
9443
- fontSize: 1,
9444
- mode: "outline",
9445
- tone: "default",
9446
- children: [ "+", extraLive ]
9447
- }) : null ]
9448
- });
9449
- }
9450
-
9451
- function LeadBadge({entry: entry}) {
9452
- const {instance: instance, evaluation: evaluation} = entry;
9453
- if (isUnprimed(instance)) /* @__PURE__ */ return jsx(StartIncompleteBadge, {});
9454
- const stage = evaluation?.currentStage?.stage, title = stage?.title ?? stageTitle(definitionSnapshotOf(instance), instance.currentStage), state = terminalState(instance), isTerminal = (stage ? isTerminalStage(stage) : !1) || state !== "in-flight";
9455
- /* @__PURE__ */
9456
- return jsxs(Badge, {
9457
- fontSize: 1,
9458
- mode: isTerminal ? "default" : "outline",
9459
- tone: pillTone({
9460
- state: state,
9461
- isTerminal: isTerminal,
9462
- stageKnown: stage !== void 0
9463
- }),
9464
- children: [ title, state === "completed" ? " ✓" : "" ]
10008
+ return jsx(AutoStartGate, {
10009
+ ...props,
10010
+ fallback: fallback
9465
10011
  });
9466
10012
  }
9467
10013
 
9468
- function pillTone(args) {
9469
- return args.state === "aborted" ? "critical" : args.isTerminal ? "positive" : args.stageKnown ? "primary" : "default";
9470
- }
9471
-
9472
- export { AbortedBadge, ActivitiesList, ActivityDetailDialog, ActivityRow, BreadcrumbTail, CollapsibleBand, DismissablePopover, DocPreviewLink, DocRefFace, GroupHeading, HoverHint, InvalidDocNotice, LinkChip, LoadingRow, StageChip, StageFace, StaleLock, TabSwitch, TodoItemsList, TrailingHairline, UnreadableDocsNote, WorkflowStagePreview, activityRowProps, assigneeUserIdsOf, committedRowFace, dateControlKind, dateControlValue, definitionSnapshotOf, describeError, editTargetOf, findActivity, findActivityNode, formatDate, gdrLocality, instanceBreadcrumb, instanceNotice, instanceTitle, isActivityAssignedTo, isEvaluationStale, isOpenActivityStatus, mappingForDocType, openActivityGone, parseDateFieldValue, parseStoredDateValue, readDeployedDefinitions, rowAssignControlState, rowDateControlState, serializeDateFieldValue, stageTitle, stateByActivity, stopRowClick, stopRowMouseDown, useAssignmentIdentity, useDefinition, useProjectMembers, useSaveField, useUserDisplays, useWorkflowContext, useWorkflowInstanceEntry, workflowDefaultDocumentNode, workflowDocTypes, workflowStudioPlugin, workflowsTabCodec, workflowsView };
10014
+ export { AbortedBadge, ActivitiesList, ActivityDetailDialog, ActivityRow, BreadcrumbTail, CautionNote, CollapsibleBand, DismissablePopover, DocPreviewLink, DocRefFace, GroupHeading, HoverHint, InvalidDocNotice, LinkChip, LoadingRow, SpinnerSlot, StageChip, StageFace, StaleLock, TabSwitch, TodoItemsList, TrailingHairline, UnreadableDocsNote, WORKFLOW_API_VERSION, WorkflowInstanceDetailViewed, WorkflowTaskFiltersApplied, WorkflowToolOpened, activityRowProps, assigneeUserIdsOf, committedRowFace, dateControlKind, dateControlValue, definitionSnapshotOf, describeError, findActivity, findActivityNode, formatDate, gdrLocality, instanceBreadcrumb, instanceNotice, instanceTitle, isActivityAssignedTo, isEvaluationStale, isOpenActivityStatus, openActivityGone, parseDateFieldValue, parseStoredDateValue, readDeployedDefinitions, rowAssignControlState, rowDateControlState, serializeDateFieldValue, stageTitle, stateByActivity, stopRowClick, stopRowMouseDown, useAssignmentIdentity, useClosableToast, useDefinition, useLogEventOnMount, useProjectMembers, useSaveField, useUserDisplays, useWorkflowContext, useWorkflowInstanceEntry, workflowDefaultDocumentNode, workflowStudioPlugin, workflowsTabCodec, workflowsView };