@sanity/workflow-studio-plugin 0.4.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- var workflowEngine = require("@sanity/workflow-engine"), jsxRuntime = require("react/jsx-runtime"), Timeline = require("@sanity/icons/Timeline"), ui = require("@sanity/ui"), react = require("react"), structure = require("sanity/structure"), formatDistanceToNow = require("date-fns/formatDistanceToNow"), csm = require("@sanity/client/csm"), workflowStudio = require("@sanity/workflow-studio"), sanity = require("sanity"), Calendar = require("@sanity/icons/Calendar"), workflowComponents = require("@sanity/workflow-components"), User = require("@sanity/icons/User"), Close = require("@sanity/icons/Close"), Search = require("@sanity/icons/Search"), ChevronDown = require("@sanity/icons/ChevronDown"), ChevronRight = require("@sanity/icons/ChevronRight"), Checkmark = require("@sanity/icons/Checkmark"), Edit = require("@sanity/icons/Edit"), content = require("@sanity/util/content"), InfoOutline = require("@sanity/icons/InfoOutline"), WarningOutline = require("@sanity/icons/WarningOutline"), router = require("sanity/router"), types = require("@sanity/types"), Launch = require("@sanity/icons/Launch"), Empty$1 = require("@sanity/icons/Empty"), ErrorFilled = require("@sanity/icons/ErrorFilled"), RemoveCircle = require("@sanity/icons/RemoveCircle"), Add = require("@sanity/icons/Add"), Bolt = require("@sanity/icons/Bolt"), CheckmarkCircle = require("@sanity/icons/CheckmarkCircle"), Document = require("@sanity/icons/Document"), Ulist = require("@sanity/icons/Ulist"), pluralize = require("pluralize-esm"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal"), ArrowUp = require("@sanity/icons/ArrowUp"), workflowReact = require("@sanity/workflow-react"), Transfer = require("@sanity/icons/Transfer");
3
+ var jsxRuntime = require("react/jsx-runtime"), Timeline = require("@sanity/icons/Timeline"), ui = require("@sanity/ui"), react = require("react"), structure = require("sanity/structure"), workflowEngine = require("@sanity/workflow-engine"), Checkmark = require("@sanity/icons/Checkmark"), Close = require("@sanity/icons/Close"), formatDistanceToNow = require("date-fns/formatDistanceToNow"), csm = require("@sanity/client/csm"), workflowStudio = require("@sanity/workflow-studio"), sanity = require("sanity"), Calendar = require("@sanity/icons/Calendar"), workflowComponents = require("@sanity/workflow-components"), User = require("@sanity/icons/User"), Search = require("@sanity/icons/Search"), ChevronRight = require("@sanity/icons/ChevronRight"), Edit = require("@sanity/icons/Edit"), content = require("@sanity/util/content"), InfoOutline = require("@sanity/icons/InfoOutline"), Document = require("@sanity/icons/Document"), WarningOutline = require("@sanity/icons/WarningOutline"), router = require("sanity/router"), types = require("@sanity/types"), Launch = require("@sanity/icons/Launch"), Empty$1 = require("@sanity/icons/Empty"), ErrorFilled = require("@sanity/icons/ErrorFilled"), RemoveCircle = require("@sanity/icons/RemoveCircle"), Add = require("@sanity/icons/Add"), Bolt = require("@sanity/icons/Bolt"), ChevronDown = require("@sanity/icons/ChevronDown"), CheckmarkCircle = require("@sanity/icons/CheckmarkCircle"), Ulist = require("@sanity/icons/Ulist"), pluralize = require("pluralize-esm"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal"), ArrowUp = require("@sanity/icons/ArrowUp"), workflowReact = require("@sanity/workflow-react"), Transfer = require("@sanity/icons/Transfer");
4
4
 
5
5
  function _interopDefaultCompat(e) {
6
6
  return e && typeof e == "object" && "default" in e ? e : {
@@ -23,20 +23,58 @@ function gdrLocality(id, contentResource) {
23
23
  };
24
24
  }
25
25
 
26
- function mappingsForDocType(mappings, docType) {
27
- return mappings.filter(m => m.docType === docType);
26
+ function assertUniqueWorkflowMappings(mappings) {
27
+ const keys = /* @__PURE__ */ new Set;
28
+ for (const mapping of mappings) {
29
+ const key = mappingKey(mapping);
30
+ if (keys.has(key)) throw new Error(`Duplicate workflow mapping for ${key}`);
31
+ keys.add(key);
32
+ }
33
+ }
34
+
35
+ function discoverWorkflowMappings(args) {
36
+ assertUniqueWorkflowMappings(args.overrides ?? []);
37
+ const discovered = workflowEngine.latestDeployedDefinitions(args.definitions).flatMap(definition => {
38
+ if (!workflowEngine.isStartableDefinition(definition)) return [];
39
+ const subject = (definition.fields ?? []).find(workflowEngine.isSubjectEntry);
40
+ if (subject === void 0 || !workflowEngine.isInputSourced(subject)) return [];
41
+ const source = {
42
+ fields: [ ...definition.fields ?? [] ]
43
+ };
44
+ return [ ...args.schemaContentTypes ].filter(docType => workflowEngine.acceptsDocumentType(source, docType)).map(docType => ({
45
+ docType: docType,
46
+ definition: definition.name,
47
+ label: definition.title ?? definition.name
48
+ }));
49
+ }), byKey = new Map(discovered.map(mapping => [ mappingKey(mapping), mapping ]));
50
+ for (const override of args.overrides ?? []) {
51
+ const key = mappingKey(override);
52
+ byKey.set(key, override);
53
+ }
54
+ return [ ...byKey.values() ];
55
+ }
56
+
57
+ function mappingAutoStartMap(mappings) {
58
+ const byType = /* @__PURE__ */ Object.create(null);
59
+ for (const mapping of mappings) {
60
+ if (!mapping.autoStart) continue;
61
+ const definitions = byType[mapping.docType] ?? [];
62
+ byType[mapping.docType] = [ ...definitions, mapping.definition ];
63
+ }
64
+ return byType;
28
65
  }
29
66
 
30
- function mappingForDocType(mappings, docType) {
31
- return mappingsForDocType(mappings, docType)[0];
67
+ function mappingsForDocType(mappings, docType) {
68
+ return mappings.filter(m => m.docType === docType);
32
69
  }
33
70
 
34
71
  function mappingKey(mapping) {
35
72
  return `${mapping.docType}::${mapping.definition}`;
36
73
  }
37
74
 
38
- function workflowDocTypes(mappings) {
39
- return new Set(mappings.map(m => m.docType));
75
+ function documentSubjectEntry(definition) {
76
+ const fields = definition.fields ?? [];
77
+ return fields.find(workflowEngine.isSubjectEntry) ?? fields.find(entry => entry.type === "doc.ref" && entry.name === "subject");
40
78
  }
41
79
 
42
80
  function mappingStartBlocks(args) {
@@ -170,7 +208,49 @@ function mappingIssues(args) {
170
208
  return issues;
171
209
  }
172
210
 
173
- const WORKFLOWS_VIEW_ID = "workflows", WORKFLOWS_TAB_PARAM = "workflowsTab", WORKFLOW_INTENT = "workflow";
211
+ const WORKFLOWS_VIEW_ID = "workflows", WORKFLOWS_TAB_PARAM = "workflowsTab";
212
+
213
+ function withoutWorkflowsTab(params) {
214
+ const {[WORKFLOWS_TAB_PARAM]: _previous, ...rest} = params ?? {};
215
+ return rest;
216
+ }
217
+
218
+ function workflowsTabCodec(tabs) {
219
+ const [defaultTab] = tabs;
220
+ return {
221
+ read: params => tabs.find(tab => tab === params?.[WORKFLOWS_TAB_PARAM]) ?? defaultTab,
222
+ write: (params, tab) => {
223
+ const rest = withoutWorkflowsTab(params);
224
+ return tab === defaultTab ? rest : {
225
+ ...rest,
226
+ [WORKFLOWS_TAB_PARAM]: tab
227
+ };
228
+ }
229
+ };
230
+ }
231
+
232
+ const WORKFLOWS_FOCUS_PARAM = "workflowsFocus";
233
+
234
+ function focusedViewParams(params, focusId) {
235
+ return {
236
+ ...withoutWorkflowsTab(params),
237
+ view: WORKFLOWS_VIEW_ID,
238
+ [WORKFLOWS_FOCUS_PARAM]: focusId
239
+ };
240
+ }
241
+
242
+ function focusedLanding(focus, instanceIds) {
243
+ if (!(focus === void 0 || !instanceIds.includes(focus))) return {
244
+ focusedId: focus,
245
+ instanceIds: instanceIds
246
+ };
247
+ }
248
+
249
+ function landingDefaultOpen(landing, instanceId) {
250
+ if (!(landing === void 0 || !landing.instanceIds.includes(instanceId))) return instanceId === landing.focusedId;
251
+ }
252
+
253
+ const WORKFLOW_INTENT = "workflow";
174
254
 
175
255
  function handlesWorkflowIntent(intent, params) {
176
256
  return workflowIntentState(intent, params) !== null;
@@ -243,6 +323,16 @@ function formatTimeAgo(value) {
243
323
  });
244
324
  }
245
325
 
326
+ function stampFace(verb, at) {
327
+ const ago = formatTimeAgo(at);
328
+ return ago === "" ? {
329
+ lead: `${verb} ${formatShortDateTime(at)}`
330
+ } : {
331
+ lead: `${verb} ${ago}`,
332
+ detail: formatShortDateTime(at)
333
+ };
334
+ }
335
+
246
336
  function formatDate(value) {
247
337
  if (!value) return "—";
248
338
  const d = parseDateFieldValue(value, "date") ?? new Date(value);
@@ -292,6 +382,10 @@ function hrefIfHttp(value) {
292
382
  }
293
383
  }
294
384
 
385
+ function unreadableDocsReport(docs) {
386
+ return docs.map(doc => `Unreadable workflow document\nID: ${doc.documentId}\nReason: ${doc.reason}\nDetail: ${doc.message}`).join(`\n\n`);
387
+ }
388
+
295
389
  function isButton(actionEval) {
296
390
  return workflowEngine.actionRendering(actionEval) === "button";
297
391
  }
@@ -301,7 +395,7 @@ function stateByActivity(instance) {
301
395
  }
302
396
 
303
397
  function evaluationFreshness(args) {
304
- return args.evaluation ? args.committed.lastChangedAt > args.evaluation.instance.lastChangedAt ? "stale" : "current" : "pending";
398
+ return args.evaluation ? Date.parse(args.committed.lastChangedAt) > Date.parse(args.evaluation.instance.lastChangedAt) ? "stale" : "current" : "pending";
305
399
  }
306
400
 
307
401
  function isEvaluationCurrent(args) {
@@ -348,20 +442,44 @@ function useWorkflowContext() {
348
442
  }
349
443
 
350
444
  function useWorkflowsForDocument(rawId) {
351
- const {register: register, unregister: unregister, byDocument: byDocument} = useWorkflowContext(), docId = rawId ? csm.getPublishedId(rawId) : void 0;
352
- return react.useEffect(() => {
445
+ return useDocumentWorkflowEntries(rawId, !0);
446
+ }
447
+
448
+ function useCommittedWorkflowsForDocument(rawId) {
449
+ return useDocumentWorkflowEntries(rawId, !1);
450
+ }
451
+
452
+ function useDocumentWorkflowEntries(rawId, evaluate) {
453
+ const {register: register, unregister: unregister, requestEvaluation: requestEvaluation, releaseEvaluation: releaseEvaluation, entries: entries} = useWorkflowContext(), docId = rawId ? csm.getPublishedId(rawId) : void 0;
454
+ react.useEffect(() => {
353
455
  if (docId) return register(docId), () => unregister(docId);
354
- }, [ docId, register, unregister ]), {
456
+ }, [ docId, register, unregister ]);
457
+ const list = react.useSyncExternalStore(entries.subscribe, react.useCallback(() => docId ? entries.getDocument(docId) : EMPTY_ENTRIES, [ entries, docId ])), evaluationKey = evaluate ? list.map(entry => entry.instance._id).join("\0") : "";
458
+ return react.useEffect(() => {
459
+ if (!evaluate) return;
460
+ const ids = evaluationKey === "" ? [] : evaluationKey.split("\0");
461
+ for (const id of ids) requestEvaluation(id);
462
+ return () => {
463
+ for (const id of ids) releaseEvaluation(id);
464
+ };
465
+ }, [ evaluate, evaluationKey, requestEvaluation, releaseEvaluation ]), {
355
466
  docId: docId,
356
- entries: docId ? byDocument.get(docId) ?? EMPTY_ENTRIES : EMPTY_ENTRIES
467
+ entries: list
357
468
  };
358
469
  }
359
470
 
360
- function useWorkflowInstanceEntry(instanceId) {
361
- const {byInstance: byInstance, requestInstance: requestInstance, releaseInstance: releaseInstance} = useWorkflowContext();
362
- if (react.useEffect(() => {
471
+ function useHeldWorkflowEntry(instanceId) {
472
+ const {entries: entries} = useWorkflowContext();
473
+ return react.useSyncExternalStore(entries.subscribe, react.useCallback(() => instanceId ? entries.getInstance(instanceId) : void 0, [ entries, instanceId ]));
474
+ }
475
+
476
+ function useWorkflowInstanceEntry(instanceId, options = {}) {
477
+ const {requestInstance: requestInstance, releaseInstance: releaseInstance, requestEvaluation: requestEvaluation, releaseEvaluation: releaseEvaluation} = useWorkflowContext(), evaluate = options.evaluate ?? !0;
478
+ return react.useEffect(() => {
363
479
  if (instanceId) return requestInstance(instanceId), () => releaseInstance(instanceId);
364
- }, [ instanceId, requestInstance, releaseInstance ]), !!instanceId) return byInstance.get(instanceId);
480
+ }, [ instanceId, requestInstance, releaseInstance ]), react.useEffect(() => {
481
+ if (!(!instanceId || !evaluate)) return requestEvaluation(instanceId), () => releaseEvaluation(instanceId);
482
+ }, [ instanceId, evaluate, requestEvaluation, releaseEvaluation ]), useHeldWorkflowEntry(instanceId);
365
483
  }
366
484
 
367
485
  const isTodoDone = item => item.status === "done", toggledTodoStatus = item => isTodoDone(item) ? "open" : "done";
@@ -630,14 +748,33 @@ function changesOwnStatus(action, activityName) {
630
748
  return ownStatusOps(action, activityName).length > 0;
631
749
  }
632
750
 
633
- function actionButtonTone(args) {
634
- const status = actionAdvanceStatus(args.action, args.activityName);
635
- return status === void 0 ? {
636
- mode: "ghost",
637
- tone: "default"
638
- } : {
639
- mode: "default",
640
- tone: status === "failed" ? "critical" : "default"
751
+ function concludesHere(actionEval, activityName) {
752
+ return changesOwnStatus(actionEval.action, activityName) || actionEval.firing?.exitsStage === !0;
753
+ }
754
+
755
+ const SEMANTIC_FACE = {
756
+ "decision.accept": {
757
+ tone: "positive",
758
+ icon: Checkmark.CheckmarkIcon
759
+ },
760
+ "decision.decline": {
761
+ tone: "caution",
762
+ icon: Close.CloseIcon
763
+ }
764
+ };
765
+
766
+ function actionButtonFace(args) {
767
+ const mode = concludesHere(args.actionEval, args.activityName) ? "default" : "ghost";
768
+ if (actionAdvanceStatus(args.actionEval.action, args.activityName) === "failed") return {
769
+ mode: mode,
770
+ tone: "critical",
771
+ icon: void 0
772
+ };
773
+ const [semantic] = args.actionEval.semantics ?? [], face = semantic === void 0 ? void 0 : SEMANTIC_FACE[semantic];
774
+ return {
775
+ mode: mode,
776
+ tone: face?.tone ?? "default",
777
+ icon: face?.icon
641
778
  };
642
779
  }
643
780
 
@@ -699,6 +836,11 @@ function actionRowKind(actionEval) {
699
836
  };
700
837
  }
701
838
 
839
+ function actionTriggerLabel(actionEval) {
840
+ const label = actionLabel(actionEval.action);
841
+ return actionRowKind(actionEval).kind === "params-form" ? `${label}…` : label;
842
+ }
843
+
702
844
  function tickActionNames(args) {
703
845
  const arrayFieldNames = new Set(args.state.flatMap(f => f._type === "array" ? [ f.name ] : [])), names = /* @__PURE__ */ new Set;
704
846
  for (const a of args.actions) for (const o of tickOpsOf(a.action)) arrayFieldNames.has(o.target.field) && tickedItemKey(o.where) !== void 0 && names.add(a.action.name);
@@ -728,8 +870,8 @@ function deriveActivityPresentation(args) {
728
870
  settled: settled,
729
871
  triggeredActions: settled ? [] : args.activityEval.actions.filter(a => a.triggered === !0),
730
872
  manualTarget: kind === "manual" ? manualLink(activity) : void 0,
731
- actions: buttonActions.filter(a => !changesOwnStatus(a.action, activityName)),
732
- terminalActions: buttonActions.filter(a => changesOwnStatus(a.action, activityName))
873
+ actions: buttonActions.filter(a => !concludesHere(a, activityName)),
874
+ terminalActions: buttonActions.filter(a => concludesHere(a, activityName))
733
875
  };
734
876
  }
735
877
 
@@ -742,15 +884,78 @@ function rowTerminalActions(args) {
742
884
  }).terminalActions;
743
885
  }
744
886
 
745
- function rowAssignField(args) {
746
- return (groupEditableByActivity(args.editableFields ?? []).get(args.activityName) ?? []).find(f => f.type === "assignees" && f.editable);
887
+ function isDeclaredEditable(field) {
888
+ return field.editable !== void 0;
889
+ }
890
+
891
+ function rowControlState(args) {
892
+ const {kinds: kinds, activity: activity, status: status, editableFields: editableFields} = args;
893
+ if (!(activity.fields ?? []).some(field => kinds.includes(field.type) && isDeclaredEditable(field))) return {
894
+ kind: "none"
895
+ };
896
+ if (editableFields !== void 0) {
897
+ const field = (groupEditableByActivity(editableFields).get(activity.name) ?? []).find(f => kinds.includes(f.type));
898
+ return field?.editable ? {
899
+ kind: "editable",
900
+ field: field
901
+ } : field?.disabledReason ? {
902
+ kind: "closed",
903
+ reason: describeEditDisabledReason(field.disabledReason)
904
+ } : {
905
+ kind: "closed",
906
+ reason: describeEditDisabledReason({
907
+ kind: "not-editable"
908
+ })
909
+ };
910
+ }
911
+ return workflowEngine.isTerminalActivityStatus(status) ? {
912
+ kind: "closed",
913
+ reason: describeEditDisabledReason({
914
+ kind: "edit-window-closed",
915
+ detail: "activity settled"
916
+ })
917
+ } : {
918
+ kind: "loading"
919
+ };
920
+ }
921
+
922
+ function rowAssignControlState(args) {
923
+ return rowControlState({
924
+ kinds: [ "assignees" ],
925
+ ...args
926
+ });
927
+ }
928
+
929
+ function rowDateControlState(args) {
930
+ return rowControlState({
931
+ kinds: [ "date", "datetime" ],
932
+ ...args
933
+ });
934
+ }
935
+
936
+ function committedRowFace(row) {
937
+ return {
938
+ activityName: row.activityName,
939
+ title: row.title,
940
+ status: row.status,
941
+ automated: !1,
942
+ blocked: !1
943
+ };
747
944
  }
748
945
 
749
946
  function activityRowProps(args) {
750
- const {activityEval: activityEval, activityState: activityState, editableFields: editableFields} = args, activityName = activityEval.activity.name, state = activityState.get(activityName) ?? [];
947
+ const {activityEval: activityEval, activityState: activityState, editableFields: editableFields} = args, state = activityState.get(activityEval.activity.name) ?? [];
751
948
  return {
752
- assignField: rowAssignField({
753
- activityName: activityName,
949
+ face: {
950
+ activityName: activityEval.activity.name,
951
+ title: activityLabel(activityEval.activity),
952
+ status: activityEval.status,
953
+ automated: showsAutomatedPill(activityEval),
954
+ blocked: showsBlockedTag(activityEval)
955
+ },
956
+ assignState: rowAssignControlState({
957
+ activity: activityEval.activity,
958
+ status: activityEval.status,
754
959
  editableFields: editableFields
755
960
  }),
756
961
  assigneeIds: assigneeUserIdsOf(state),
@@ -762,6 +967,15 @@ function activityRowProps(args) {
762
967
  };
763
968
  }
764
969
 
970
+ function dateControlKind(state) {
971
+ if (state.kind === "editable" && (state.field.type === "date" || state.field.type === "datetime")) return state.field.type;
972
+ }
973
+
974
+ function dateControlValue(args) {
975
+ const {state: state, dates: dates} = args;
976
+ return state.kind === "editable" ? typeof state.field.value == "string" && state.field.value !== "" ? state.field.value : void 0 : dates.find(value => parseStoredDateValue(value) !== void 0);
977
+ }
978
+
765
979
  function activityStateOf(instance, activityName) {
766
980
  return workflowEngine.findCurrentActivityEntry(instance, activityName)?.fields ?? [];
767
981
  }
@@ -788,10 +1002,17 @@ function isEmptyValue(value) {
788
1002
  return value == null || value === "" || Array.isArray(value) && value.length === 0;
789
1003
  }
790
1004
 
1005
+ function effectiveValidation(kind, validation) {
1006
+ return kind !== "progress" ? validation : {
1007
+ min: validation?.min ?? 0,
1008
+ max: validation?.max ?? 100
1009
+ };
1010
+ }
1011
+
791
1012
  function scalarValidationIssue(args) {
792
1013
  const issues = workflowEngine.scalarValidationIssues({
793
1014
  entryType: args.kind,
794
- validation: args.validation,
1015
+ validation: effectiveValidation(args.kind, args.validation),
795
1016
  value: args.value
796
1017
  });
797
1018
  return issues === void 0 ? void 0 : `${args.label}: ${issues.join("; ")}`;
@@ -808,10 +1029,19 @@ function personActor(user) {
808
1029
  };
809
1030
  }
810
1031
 
1032
+ const memberIndexes = /* @__PURE__ */ new WeakMap;
1033
+
1034
+ function memberIndex(members) {
1035
+ const cached = memberIndexes.get(members);
1036
+ if (cached) return cached;
1037
+ const created = new Map(members.map(member => [ member.id, member ]));
1038
+ return memberIndexes.set(members, created), created;
1039
+ }
1040
+
811
1041
  const selfUserFor = (id, me) => me != null && me.id === id ? me : void 0;
812
1042
 
813
1043
  function userDisplayFor(args) {
814
- const member = args.members.find(m => m.id === args.id), self = selfUserFor(args.id, args.me);
1044
+ const member = memberIndex(args.members).get(args.id), self = selfUserFor(args.id, args.me);
815
1045
  return {
816
1046
  id: args.id,
817
1047
  name: member?.displayName ?? self?.name ?? args.id,
@@ -821,19 +1051,20 @@ function userDisplayFor(args) {
821
1051
 
822
1052
  function useUserDisplays(ids) {
823
1053
  const {members: members} = useProjectMembers(), me = sanity.useCurrentUser();
824
- return ids.map(id => userDisplayFor({
1054
+ return react.useMemo(() => ids.map(id => userDisplayFor({
825
1055
  id: id,
826
1056
  members: members,
827
1057
  me: me
828
- }));
1058
+ })), [ ids, me, members ]);
829
1059
  }
830
1060
 
831
1061
  function useUserDisplay(id) {
832
- const [display] = useUserDisplays([ id ]);
833
- return display ?? {
834
- name: id,
835
- imageUrl: void 0
836
- };
1062
+ const {members: members} = useProjectMembers(), me = sanity.useCurrentUser();
1063
+ return react.useMemo(() => userDisplayFor({
1064
+ id: id,
1065
+ members: members,
1066
+ me: me
1067
+ }), [ id, me, members ]);
837
1068
  }
838
1069
 
839
1070
  function useAssignmentIdentity() {
@@ -844,8 +1075,12 @@ function useAssignmentIdentity() {
844
1075
  };
845
1076
  }
846
1077
 
1078
+ const projectMembersByUsers = /* @__PURE__ */ new WeakMap;
1079
+
847
1080
  function projectMembersFrom(users) {
848
- return users.map(({membership: membership, profile: profile}) => ({
1081
+ const cached = projectMembersByUsers.get(users);
1082
+ if (cached) return cached;
1083
+ const members = users.map(({membership: membership, profile: profile}) => ({
849
1084
  id: membership.id,
850
1085
  displayName: profile?.displayName ?? membership.id,
851
1086
  ...profile?.email ? {
@@ -856,6 +1091,7 @@ function projectMembersFrom(users) {
856
1091
  } : {},
857
1092
  roles: (membership.roles ?? []).map(role => role.name)
858
1093
  }));
1094
+ return projectMembersByUsers.set(users, members), members;
859
1095
  }
860
1096
 
861
1097
  function useProjectMembers() {
@@ -999,8 +1235,19 @@ function HintBody(props) {
999
1235
  });
1000
1236
  }
1001
1237
 
1238
+ function textInputFacet(kind) {
1239
+ return kind === "number" || kind === "progress" ? {
1240
+ type: "number"
1241
+ } : kind === "url" ? {
1242
+ type: "url"
1243
+ } : {};
1244
+ }
1245
+
1002
1246
  function scalarInputConstraints(kind, validation) {
1003
- return validation === void 0 ? {} : kind === "number" ? {
1247
+ return kind === "progress" ? {
1248
+ min: validation?.min ?? 0,
1249
+ max: validation?.max ?? 100
1250
+ } : validation === void 0 ? {} : kind === "number" ? {
1004
1251
  min: validation.min,
1005
1252
  max: validation.max
1006
1253
  } : kind === "string" || kind === "text" ? {
@@ -1113,7 +1360,7 @@ function ChoiceSelect({options: options, value: value, onChange: onChange, readO
1113
1360
  });
1114
1361
  }
1115
1362
 
1116
- const SCALAR_INPUT_KINDS = [ "string", "text", "url", "number", "date", "dateTime", "datetime", "boolean" ], SCALAR_INPUT_KIND_SET = new Set(SCALAR_INPUT_KINDS);
1363
+ const SCALAR_INPUT_KINDS = [ "string", "text", "url", "number", "progress", "date", "dateTime", "datetime", "boolean" ], SCALAR_INPUT_KIND_SET = new Set(SCALAR_INPUT_KINDS);
1117
1364
 
1118
1365
  function isScalarInputKind(kind) {
1119
1366
  return SCALAR_INPUT_KIND_SET.has(kind);
@@ -1124,29 +1371,19 @@ function textValueOf(value) {
1124
1371
  }
1125
1372
 
1126
1373
  function ScalarInput({kind: kind, value: value, onChange: onChange, options: options, validation: validation}) {
1127
- if (options !== void 0) /* @__PURE__ */
1128
- return jsxRuntime.jsx(ChoiceSelect, {
1374
+ return options !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ChoiceSelect, {
1129
1375
  onChange: onChange,
1130
1376
  options: options,
1131
1377
  value: value
1132
- });
1133
- if (kind === "boolean") /* @__PURE__ */
1134
- return jsxRuntime.jsx(ui.Checkbox, {
1378
+ }) : kind === "boolean" ? /* @__PURE__ */ jsxRuntime.jsx(ui.Checkbox, {
1135
1379
  checked: value === !0,
1136
1380
  onChange: e => onChange(e.currentTarget.checked)
1137
- });
1138
- if (kind === "date" || kind === "dateTime" || kind === "datetime") /* @__PURE__ */
1139
- return jsxRuntime.jsx(DateFieldInput, {
1381
+ }) : kind === "date" || kind === "dateTime" || kind === "datetime" ? /* @__PURE__ */ jsxRuntime.jsx(DateFieldInput, {
1140
1382
  kind: kind === "date" ? "date" : "datetime",
1141
1383
  onChange: next => onChange(next ?? ""),
1142
1384
  value: value
1143
- });
1144
- const inputType = kind === "url" || kind === "number" ? kind : void 0;
1145
- /* @__PURE__ */
1146
- return jsxRuntime.jsx(ui.TextInput, {
1147
- ...inputType ? {
1148
- type: inputType
1149
- } : {},
1385
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, {
1386
+ ...textInputFacet(kind),
1150
1387
  ...scalarInputConstraints(kind, validation),
1151
1388
  fontSize: 1,
1152
1389
  onChange: e => onChange(e.currentTarget.value),
@@ -1234,25 +1471,26 @@ function MemberPickerButton({label: label = "Select member…", onChange: onChan
1234
1471
  });
1235
1472
  }
1236
1473
 
1237
- function StubPreview({id: id, layout: layout, schemaType: schemaType}) {
1474
+ function useStubValuePreview(id, schemaType) {
1238
1475
  const value = react.useMemo(() => ({
1239
1476
  _id: id,
1240
1477
  _type: schemaType.name
1241
- }), [ id, schemaType.name ]);
1242
- return sanity.useValuePreview({
1478
+ }), [ id, schemaType.name ]), preview = sanity.useValuePreview({
1243
1479
  schemaType: schemaType,
1244
1480
  value: value
1245
- })?.isLoading ? layout === "inline" ? /* @__PURE__ */ jsxRuntime.jsx(ui.TextSkeleton, {
1246
- animated: !0,
1247
- radius: 1,
1248
- size: 1,
1249
- style: {
1250
- width: 96
1251
- }
1252
- }) : /* @__PURE__ */ jsxRuntime.jsx(sanity.SanityDefaultPreview, {
1481
+ });
1482
+ return {
1483
+ value: value,
1484
+ preview: preview
1485
+ };
1486
+ }
1487
+
1488
+ function StubPreview({id: id, schemaType: schemaType}) {
1489
+ const {value: value, preview: preview} = useStubValuePreview(id, schemaType);
1490
+ return preview.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(sanity.SanityDefaultPreview, {
1253
1491
  isPlaceholder: !0
1254
1492
  }) : /* @__PURE__ */ jsxRuntime.jsx(sanity.Preview, {
1255
- layout: layout,
1493
+ layout: "default",
1256
1494
  schemaType: schemaType,
1257
1495
  skipVisibilityCheck: !0,
1258
1496
  value: value
@@ -1295,7 +1533,7 @@ function DocPicker({value: value, onChange: onChange, types: types2, readOnly: r
1295
1533
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1296
1534
  flex: 1,
1297
1535
  children: /* @__PURE__ */ jsxRuntime.jsx(PreviewOrId, {
1298
- id: workflowEngine.extractDocumentId(value.id),
1536
+ id: workflowEngine.toBareId(value.id),
1299
1537
  type: value.type
1300
1538
  })
1301
1539
  })
@@ -1362,7 +1600,6 @@ function PreviewOrId({id: id, type: type, fallbackTitle: fallbackTitle}) {
1362
1600
  const schemaType = sanity.useSchema().get(type);
1363
1601
  return schemaType ? /* @__PURE__ */ jsxRuntime.jsx(StubPreview, {
1364
1602
  id: id,
1365
- layout: "default",
1366
1603
  schemaType: schemaType
1367
1604
  }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1368
1605
  align: "center",
@@ -1392,7 +1629,7 @@ function DocRefsInput({value: value, onChange: onChange, types: types2}) {
1392
1629
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1393
1630
  flex: 1,
1394
1631
  children: /* @__PURE__ */ jsxRuntime.jsx(PreviewOrId, {
1395
- id: workflowEngine.extractDocumentId(ref.id),
1632
+ id: workflowEngine.toBareId(ref.id),
1396
1633
  type: ref.type
1397
1634
  })
1398
1635
  }),
@@ -1496,12 +1733,30 @@ function useClosableToast() {
1496
1733
  }), [ toast ]);
1497
1734
  }
1498
1735
 
1736
+ const ActionClusterContext = react.createContext(void 0), ActionClusterProvider = ActionClusterContext.Provider;
1737
+
1738
+ function useActionClusterState() {
1739
+ const [pending, setPending] = react.useState(!1);
1740
+ return react.useMemo(() => ({
1741
+ pending: pending,
1742
+ onFireChange: setPending
1743
+ }), [ pending ]);
1744
+ }
1745
+
1746
+ function useActionClusterLock() {
1747
+ return react.useContext(ActionClusterContext);
1748
+ }
1749
+
1750
+ function useActionClusterPending() {
1751
+ return useActionClusterLock()?.pending === !0;
1752
+ }
1753
+
1499
1754
  function useFireAction(args) {
1500
- const {instanceId: instanceId, activity: activity, action: action, label: label} = args, {fireActionFor: fireActionFor} = useWorkflowContext(), toast = useClosableToast(), [pending, setPending] = react.useState(!1);
1755
+ const {instanceId: instanceId, activity: activity, action: action, label: label} = args, {fireActionFor: fireActionFor} = useWorkflowContext(), toast = useClosableToast(), cluster = useActionClusterLock(), [localPending, setLocalPending] = react.useState(!1), pending = localPending || cluster?.pending === !0;
1501
1756
  return {
1502
1757
  fire: react.useCallback(async params => {
1503
1758
  if (pending) return !1;
1504
- setPending(!0);
1759
+ setLocalPending(!0), cluster?.onFireChange(!0);
1505
1760
  try {
1506
1761
  return await fireActionFor(instanceId, {
1507
1762
  activity: activity,
@@ -1521,17 +1776,30 @@ function useFireAction(args) {
1521
1776
  description: describeError(err)
1522
1777
  }), !1;
1523
1778
  } finally {
1524
- setPending(!1);
1779
+ setLocalPending(!1), cluster?.onFireChange(!1);
1525
1780
  }
1526
- }, [ fireActionFor, instanceId, activity, action, label, pending, toast ]),
1781
+ }, [ fireActionFor, instanceId, activity, action, label, pending, toast, cluster ]),
1527
1782
  pending: pending
1528
1783
  };
1529
1784
  }
1530
1785
 
1786
+ const ACTION_ICON_GAP = 2;
1787
+
1788
+ function actionIconProps(icon) {
1789
+ return icon ? {
1790
+ gap: ACTION_ICON_GAP,
1791
+ icon: icon
1792
+ } : {};
1793
+ }
1794
+
1531
1795
  function stopRowMouseDown(e) {
1532
1796
  e.preventDefault(), e.stopPropagation();
1533
1797
  }
1534
1798
 
1799
+ function stopRowClick(e) {
1800
+ e.stopPropagation();
1801
+ }
1802
+
1535
1803
  function spanTriggerProps(chrome) {
1536
1804
  return chrome?.inButtonCard === !0 ? {
1537
1805
  as: "span",
@@ -1551,7 +1819,7 @@ function RowClickShield({active: active, children: children}) {
1551
1819
  });
1552
1820
  }
1553
1821
 
1554
- function FireButton({instanceId: instanceId, activity: activity, action: action, label: label, mode: mode = "default", chrome: chrome, tone: tone = "default"}) {
1822
+ function FireButton({instanceId: instanceId, activity: activity, action: action, label: label, mode: mode = "default", chrome: chrome, tone: tone = "default", icon: icon}) {
1555
1823
  const {fire: fire, pending: pending} = useFireAction({
1556
1824
  instanceId: instanceId,
1557
1825
  activity: activity,
@@ -1563,11 +1831,11 @@ function FireButton({instanceId: instanceId, activity: activity, action: action,
1563
1831
  /* @__PURE__ */
1564
1832
  return jsxRuntime.jsx(ui.Button, {
1565
1833
  fontSize: 1,
1834
+ ...actionIconProps(icon),
1566
1835
  loading: pending,
1567
1836
  mode: chrome?.mode ?? mode,
1568
1837
  onClick: handleClick,
1569
1838
  padding: 2,
1570
- style: chrome?.style,
1571
1839
  text: label,
1572
1840
  tone: tone,
1573
1841
  ...spanTriggerProps(chrome),
@@ -1581,6 +1849,7 @@ function ActivityActionRow({actionEval: actionEval, instanceId: instanceId, acti
1581
1849
  const rowKind = actionRowKind(actionEval);
1582
1850
  return rowKind.kind === "disabled" ? /* @__PURE__ */ jsxRuntime.jsx(DisabledActionButton, {
1583
1851
  actionEval: actionEval,
1852
+ activity: activity,
1584
1853
  chrome: chrome
1585
1854
  }) : rowKind.kind === "plain" ? /* @__PURE__ */ jsxRuntime.jsx(PlainActionButton, {
1586
1855
  actionEval: actionEval,
@@ -1595,14 +1864,18 @@ function ActivityActionRow({actionEval: actionEval, instanceId: instanceId, acti
1595
1864
  });
1596
1865
  }
1597
1866
 
1598
- function DisabledActionButton({actionEval: actionEval, chrome: chrome}) {
1599
- const label = actionLabel(actionEval.action), button = /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1867
+ function DisabledActionButton({actionEval: actionEval, activity: activity, chrome: chrome}) {
1868
+ const label = actionTriggerLabel(actionEval), {tone: tone, icon: icon} = actionButtonFace({
1869
+ actionEval: actionEval,
1870
+ activityName: activity
1871
+ }), button = /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1600
1872
  fontSize: 1,
1873
+ ...actionIconProps(icon),
1601
1874
  mode: chrome?.mode ?? "ghost",
1602
1875
  onClick: e => e.stopPropagation(),
1603
1876
  padding: 2,
1604
- style: chrome?.style,
1605
1877
  text: label,
1878
+ tone: tone,
1606
1879
  ...spanTriggerProps(chrome),
1607
1880
  disabled: !0
1608
1881
  });
@@ -1613,11 +1886,12 @@ function DisabledActionButton({actionEval: actionEval, chrome: chrome}) {
1613
1886
  }
1614
1887
 
1615
1888
  function PlainActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, chrome: chrome}) {
1616
- const {action: action} = actionEval, label = actionLabel(action), btn = actionButtonTone({
1617
- action: action,
1889
+ const {action: action} = actionEval, label = actionLabel(action), btn = actionButtonFace({
1890
+ actionEval: actionEval,
1618
1891
  activityName: activity
1619
1892
  }), button = /* @__PURE__ */ jsxRuntime.jsx(FireButton, {
1620
1893
  action: action.name,
1894
+ icon: btn.icon,
1621
1895
  instanceId: instanceId,
1622
1896
  label: label,
1623
1897
  mode: btn.mode,
@@ -1692,8 +1966,8 @@ function ParamsActionDialog({actionEval: actionEval, instanceId: instanceId, act
1692
1966
  }
1693
1967
 
1694
1968
  function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, chrome: chrome}) {
1695
- const {action: action} = actionEval, label = actionLabel(action), [open, setOpen] = react.useState(!1), btn = actionButtonTone({
1696
- action: action,
1969
+ const [open, setOpen] = react.useState(!1), btn = actionButtonFace({
1970
+ actionEval: actionEval,
1697
1971
  activityName: activity
1698
1972
  });
1699
1973
  /* @__PURE__ */
@@ -1701,16 +1975,14 @@ function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, act
1701
1975
  children: [
1702
1976
  /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1703
1977
  fontSize: 1,
1978
+ ...actionIconProps(btn.icon),
1704
1979
  mode: chrome?.mode ?? btn.mode,
1705
1980
  onClick: e => {
1706
1981
  chrome?.inButtonCard === !0 && e.stopPropagation(), setOpen(!0);
1707
1982
  },
1708
1983
  padding: 2,
1709
- text: `${label}…`,
1984
+ text: actionTriggerLabel(actionEval),
1710
1985
  tone: btn.tone,
1711
- ...chrome?.style ? {
1712
- style: chrome.style
1713
- } : {},
1714
1986
  ...spanTriggerProps(chrome)
1715
1987
  }), open ? /* @__PURE__ */ jsxRuntime.jsx(RowClickShield, {
1716
1988
  active: chrome?.inButtonCard === !0,
@@ -1724,178 +1996,6 @@ function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, act
1724
1996
  });
1725
1997
  }
1726
1998
 
1727
- function HintedMenuButton({hint: hint, wrapMenuButton: wrapMenuButton, ...menuButtonProps}) {
1728
- const [open, setOpen] = react.useState(!1), wrap = wrapMenuButton ?? (node => node);
1729
- return hint === void 0 ? wrap(/* @__PURE__ */ jsxRuntime.jsx(ui.MenuButton, {
1730
- ...menuButtonProps
1731
- })) : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
1732
- disabled: open,
1733
- text: hint,
1734
- children: wrap(
1735
- /* @__PURE__ */ jsxRuntime.jsx(ui.MenuButton, {
1736
- ...menuButtonProps,
1737
- onClose: () => setOpen(!1),
1738
- onOpen: () => setOpen(!0)
1739
- }))
1740
- });
1741
- }
1742
-
1743
- function OverflowMenuItem({actionEval: actionEval, instanceId: instanceId, activity: activity, onCollectParams: onCollectParams}) {
1744
- const rowKind = actionRowKind(actionEval), label = actionLabel(actionEval.action), {fire: fire, pending: pending} = useFireAction({
1745
- instanceId: instanceId,
1746
- activity: activity,
1747
- action: actionEval.action.name,
1748
- label: label
1749
- });
1750
- return rowKind.kind === "disabled" ? /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
1751
- disabled: !0,
1752
- text: label
1753
- }) : rowKind.kind === "plain" ? /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
1754
- disabled: pending,
1755
- onClick: () => {
1756
- fire();
1757
- },
1758
- text: label
1759
- }) : /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
1760
- onClick: onCollectParams,
1761
- text: `${label}…`
1762
- });
1763
- }
1764
-
1765
- function rowShield(menuButton) {
1766
- /* @__PURE__ */
1767
- return jsxRuntime.jsx("span", {
1768
- onClick: e => e.stopPropagation(),
1769
- onMouseDown: stopRowMouseDown,
1770
- style: {
1771
- display: "contents"
1772
- },
1773
- children: menuButton
1774
- });
1775
- }
1776
-
1777
- function ActionOverflowMenu({actions: actions, instanceId: instanceId, activity: activity, tone: tone, chrome: chrome}) {
1778
- const [paramsAction, setParamsAction] = react.useState(void 0), menuId = react.useId(), inButtonCard = chrome?.inButtonCard === !0;
1779
- /* @__PURE__ */
1780
- return jsxRuntime.jsxs(jsxRuntime.Fragment, {
1781
- children: [
1782
- /* @__PURE__ */ jsxRuntime.jsx(HintedMenuButton, {
1783
- button: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1784
- "aria-label": "More options",
1785
- fontSize: 1,
1786
- icon: ChevronDown.ChevronDownIcon,
1787
- padding: 2,
1788
- tone: tone,
1789
- ...chrome?.mode ? {
1790
- mode: chrome.mode
1791
- } : {},
1792
- ...chrome?.style ? {
1793
- style: chrome.style
1794
- } : {},
1795
- ...inButtonCard ? {
1796
- as: "span"
1797
- } : {}
1798
- }),
1799
- hint: "More options",
1800
- id: `workflow-action-overflow-${menuId}`,
1801
- menu: /* @__PURE__ */ jsxRuntime.jsx(ui.Menu, {
1802
- children: actions.map(a => /* @__PURE__ */ jsxRuntime.jsx(OverflowMenuItem, {
1803
- actionEval: a,
1804
- activity: activity,
1805
- instanceId: instanceId,
1806
- onCollectParams: () => setParamsAction(a)
1807
- }, a.action.name))
1808
- }),
1809
- popover: {
1810
- placement: "top-end",
1811
- portal: !0
1812
- },
1813
- ...inButtonCard ? {
1814
- wrapMenuButton: rowShield
1815
- } : {}
1816
- }), paramsAction ? /* @__PURE__ */ jsxRuntime.jsx(RowClickShield, {
1817
- active: inButtonCard,
1818
- children: /* @__PURE__ */ jsxRuntime.jsx(ParamsActionDialog, {
1819
- actionEval: paramsAction,
1820
- activity: activity,
1821
- instanceId: instanceId,
1822
- onClose: () => setParamsAction(void 0)
1823
- })
1824
- }) : null ]
1825
- });
1826
- }
1827
-
1828
- const PRIMARY_SEGMENT = {
1829
- borderBottomRightRadius: 0,
1830
- borderTopRightRadius: 0
1831
- }, MENU_SEGMENT = {
1832
- borderBottomLeftRadius: 0,
1833
- borderTopLeftRadius: 0
1834
- };
1835
-
1836
- function TerminalActions({actions: actions, activity: activity, instanceId: instanceId, mode: mode = "default", inButtonCard: inButtonCard = !1}) {
1837
- const {button: button, color: color} = ui.useTheme_v2(), [primary, ...overflow] = actions;
1838
- if (!primary) return null;
1839
- const ghost = mode === "ghost", embed = inButtonCard ? {
1840
- inButtonCard: !0
1841
- } : {};
1842
- if (overflow.length === 0) {
1843
- const chrome = {
1844
- ...ghost ? {
1845
- mode: "ghost"
1846
- } : {},
1847
- ...embed
1848
- };
1849
- /* @__PURE__ */
1850
- return jsxRuntime.jsx(ActivityActionRow, {
1851
- actionEval: primary,
1852
- activity: activity,
1853
- chrome: chrome,
1854
- instanceId: instanceId
1855
- });
1856
- }
1857
- const {tone: tone} = actionButtonTone({
1858
- action: primary.action,
1859
- activityName: activity
1860
- }), segment = ghost ? {
1861
- mode: "ghost"
1862
- } : {};
1863
- /* @__PURE__ */
1864
- return jsxRuntime.jsxs(ui.Flex, {
1865
- children: [
1866
- /* @__PURE__ */ jsxRuntime.jsx(ActivityActionRow, {
1867
- actionEval: primary,
1868
- activity: activity,
1869
- chrome: {
1870
- ...segment,
1871
- style: PRIMARY_SEGMENT,
1872
- ...embed
1873
- },
1874
- instanceId: instanceId
1875
- }), ghost ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1876
- flex: "none",
1877
- style: {
1878
- background: color.bg,
1879
- width: 1
1880
- }
1881
- }),
1882
- /* @__PURE__ */ jsxRuntime.jsx(ActionOverflowMenu, {
1883
- actions: overflow,
1884
- activity: activity,
1885
- chrome: {
1886
- ...segment,
1887
- style: ghost ? {
1888
- ...MENU_SEGMENT,
1889
- marginLeft: -button.border.width
1890
- } : MENU_SEGMENT,
1891
- ...embed
1892
- },
1893
- instanceId: instanceId,
1894
- tone: tone
1895
- }) ]
1896
- });
1897
- }
1898
-
1899
1999
  const DIM = {
1900
2000
  opacity: .6
1901
2001
  };
@@ -1933,8 +2033,12 @@ function stageTitle(definition, stageName) {
1933
2033
  return definition?.stages.find(stage => stage.name === stageName)?.title ?? stageName;
1934
2034
  }
1935
2035
 
2036
+ function findActivityNode(args) {
2037
+ return args.definition?.stages.find(s => s.name === args.stageName)?.activities?.find(a => a.name === args.activityName);
2038
+ }
2039
+
1936
2040
  function findActivity(args) {
1937
- const activity = args.definition?.stages.find(s => s.name === args.stageName)?.activities?.find(a => a.name === args.activityName);
2041
+ const activity = findActivityNode(args);
1938
2042
  return {
1939
2043
  title: activity?.title ?? args.activityName,
1940
2044
  description: activity?.description
@@ -2068,14 +2172,15 @@ function FieldInput$1({kind: kind, value: value, onChange: onChange, onEnter: on
2068
2172
  });
2069
2173
 
2070
2174
  case "number":
2175
+ case "progress":
2071
2176
  /* @__PURE__ */
2072
2177
  return jsxRuntime.jsx(ui.TextInput, {
2178
+ ...textInputFacet(kind),
2179
+ ...scalarInputConstraints(kind, validation),
2073
2180
  fontSize: 1,
2074
2181
  onChange: e => onChange(e.currentTarget.value === "" ? null : Number(e.currentTarget.value)),
2075
2182
  onKeyDown: e => e.key === "Enter" && onEnter(),
2076
2183
  readOnly: readOnly,
2077
- ...scalarInputConstraints(kind, validation),
2078
- type: "number",
2079
2184
  value: textValueOf(value)
2080
2185
  });
2081
2186
 
@@ -2103,8 +2208,32 @@ function FieldInput$1({kind: kind, value: value, onChange: onChange, onEnter: on
2103
2208
  }
2104
2209
  }
2105
2210
 
2106
- function docLinkState(input) {
2107
- const {bareId: bareId, resource: resource} = input;
2211
+ function LinkChip({hint: hint, children: children, as: as = "a", ...anchorProps}) {
2212
+ /* @__PURE__ */
2213
+ return jsxRuntime.jsx(HoverHint, {
2214
+ text: hint,
2215
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
2216
+ __unstable_focusRing: !0,
2217
+ as: as,
2218
+ "data-as": "a",
2219
+ padding: 2,
2220
+ radius: 3,
2221
+ style: {
2222
+ alignItems: "center",
2223
+ color: "inherit",
2224
+ display: "flex",
2225
+ minWidth: 0,
2226
+ textDecoration: "none"
2227
+ },
2228
+ tone: "inherit",
2229
+ ...anchorProps,
2230
+ children: children
2231
+ })
2232
+ });
2233
+ }
2234
+
2235
+ function docLinkState(input) {
2236
+ const {bareId: bareId, resource: resource} = input;
2108
2237
  if (bareId === void 0 || resource === void 0) return {
2109
2238
  kind: "unresolvable"
2110
2239
  };
@@ -2212,7 +2341,6 @@ function DocRefFace({gdr: gdr}) {
2212
2341
  const {parsed: parsed2, local: local, schemaType: schemaType} = useLocalDocRef(gdr);
2213
2342
  return parsed2 && local && schemaType ? /* @__PURE__ */ jsxRuntime.jsx(StubPreview, {
2214
2343
  id: parsed2.documentId,
2215
- layout: "default",
2216
2344
  schemaType: schemaType
2217
2345
  }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
2218
2346
  align: "center",
@@ -2242,15 +2370,23 @@ function ForeignRefNotice({bareId: bareId, resource: resource}) {
2242
2370
  }
2243
2371
 
2244
2372
  function MissingDocNotice({bareId: bareId, layout: layout}) {
2245
- return layout === "inline" ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2246
- muted: !0,
2247
- size: 1,
2373
+ return layout === "inline" ?
2374
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
2375
+ padding: 2,
2248
2376
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
2249
2377
  align: "center",
2250
- as: "span",
2251
- gap: 1,
2378
+ gap: 2,
2252
2379
  children: [
2253
- /* @__PURE__ */ jsxRuntime.jsx(WarningOutline.WarningOutlineIcon, {}), " Document unavailable" ]
2380
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2381
+ muted: !0,
2382
+ size: 1,
2383
+ children: /* @__PURE__ */ jsxRuntime.jsx(WarningOutline.WarningOutlineIcon, {})
2384
+ }),
2385
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2386
+ muted: !0,
2387
+ size: 1,
2388
+ children: "Document unavailable"
2389
+ }) ]
2254
2390
  })
2255
2391
  }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
2256
2392
  border: !0,
@@ -2283,44 +2419,73 @@ function UnopenableTypeNotice({actualType: actualType, bareId: bareId}) {
2283
2419
  });
2284
2420
  }
2285
2421
 
2422
+ function renderTypeIcon(icon) {
2423
+ return typeof icon == "function" ? react.createElement(icon) : react.isValidElement(icon) ? icon : /* @__PURE__ */ jsxRuntime.jsx(Document.DocumentIcon, {});
2424
+ }
2425
+
2426
+ function InlineChipFace({bareId: bareId, schemaType: schemaType}) {
2427
+ const {preview: preview} = useStubValuePreview(bareId, schemaType);
2428
+ /* @__PURE__ */
2429
+ return jsxRuntime.jsxs(ui.Flex, {
2430
+ align: "center",
2431
+ gap: 2,
2432
+ children: [
2433
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2434
+ size: 1,
2435
+ children: renderTypeIcon(schemaType.icon)
2436
+ }), preview.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.TextSkeleton, {
2437
+ animated: !0,
2438
+ radius: 1,
2439
+ size: 1,
2440
+ style: {
2441
+ width: 96
2442
+ }
2443
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2444
+ size: 1,
2445
+ textOverflow: "ellipsis",
2446
+ weight: "medium",
2447
+ children: preview.value?.title ?? /* @__PURE__ */ jsxRuntime.jsx("em", {
2448
+ children: "Untitled"
2449
+ })
2450
+ }) ]
2451
+ });
2452
+ }
2453
+
2286
2454
  function LinkedDocPreview({bareId: bareId, layout: layout, schemaType: schemaType}) {
2287
- return layout === "inline" ? /* @__PURE__ */ jsxRuntime.jsx(router.IntentLink, {
2288
- intent: "edit",
2455
+ const EditIntentLink = react.useMemo(() => function(linkProps) {
2456
+ /* @__PURE__ */
2457
+ return jsxRuntime.jsx(router.IntentLink, {
2458
+ ...linkProps,
2459
+ intent: "edit",
2460
+ params: {
2461
+ id: bareId
2462
+ }
2463
+ });
2464
+ }, [ bareId ]);
2465
+ return layout === "inline" ? /* @__PURE__ */ jsxRuntime.jsx(LinkChip, {
2466
+ as: EditIntentLink,
2467
+ hint: "Open document",
2289
2468
  onClick: e => e.stopPropagation(),
2290
- params: {
2291
- id: bareId
2292
- },
2293
- style: {
2294
- color: "inherit",
2295
- textDecoration: "none",
2296
- display: "flex",
2297
- alignItems: "center"
2298
- },
2299
- children: /* @__PURE__ */ jsxRuntime.jsx(StubPreview, {
2300
- id: bareId,
2301
- layout: "inline",
2469
+ children: /* @__PURE__ */ jsxRuntime.jsx(InlineChipFace, {
2470
+ bareId: bareId,
2302
2471
  schemaType: schemaType
2303
2472
  })
2304
- }) : /* @__PURE__ */ jsxRuntime.jsx(router.IntentLink, {
2305
- intent: "edit",
2473
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
2474
+ __unstable_focusRing: !0,
2475
+ as: EditIntentLink,
2476
+ border: !0,
2477
+ "data-as": "a",
2306
2478
  onClick: e => e.stopPropagation(),
2307
- params: {
2308
- id: bareId
2309
- },
2479
+ padding: 1,
2480
+ radius: 3,
2310
2481
  style: {
2311
2482
  color: "inherit",
2312
2483
  textDecoration: "none",
2313
2484
  display: "block"
2314
2485
  },
2315
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
2316
- border: !0,
2317
- padding: 1,
2318
- radius: 3,
2319
- children: /* @__PURE__ */ jsxRuntime.jsx(StubPreview, {
2320
- id: bareId,
2321
- layout: "default",
2322
- schemaType: schemaType
2323
- })
2486
+ children: /* @__PURE__ */ jsxRuntime.jsx(StubPreview, {
2487
+ id: bareId,
2488
+ schemaType: schemaType
2324
2489
  })
2325
2490
  });
2326
2491
  }
@@ -2464,6 +2629,10 @@ const fieldValueRenderer = {
2464
2629
  }, ref.id))
2465
2630
  }) : /* @__PURE__ */ jsxRuntime.jsx(Empty, {}),
2466
2631
  number: renderTextValue,
2632
+ progress: field => /* @__PURE__ */ jsxRuntime.jsx(workflowComponents.ProgressBar, {
2633
+ label: field.title ?? field.name,
2634
+ value: field.value
2635
+ }),
2467
2636
  object: field => field.value === null ? /* @__PURE__ */ jsxRuntime.jsx(Empty, {}) : /* @__PURE__ */ jsxRuntime.jsx(ui.Code, {
2468
2637
  size: 1,
2469
2638
  style: {
@@ -2632,7 +2801,7 @@ function ReadOnlyDocRefs({entry: entry}) {
2632
2801
  padding: 2,
2633
2802
  radius: 2,
2634
2803
  children: /* @__PURE__ */ jsxRuntime.jsx(PreviewOrId, {
2635
- id: workflowEngine.extractDocumentId(ref.id),
2804
+ id: workflowEngine.toBareId(ref.id),
2636
2805
  type: ref.type
2637
2806
  })
2638
2807
  }, ref.id))
@@ -2715,7 +2884,7 @@ function useFieldDraft(args) {
2715
2884
  }
2716
2885
 
2717
2886
  function EditableFieldControl({instanceId: instanceId, field: field}) {
2718
- const {editFieldFor: editFieldFor, previewFieldFor: previewFieldFor, discardFieldPreviewFor: discardFieldPreviewFor, byInstance: byInstance} = useWorkflowContext(), me = sanity.useCurrentUser(), held = byInstance.get(instanceId), entry = held ? workflowEngine.resolveFieldEntry(held.instance, field) : void 0, evaluation = held?.evaluation, target = editTargetOf(field), consequence = fieldConsequence(evaluation, field.name), advanceTo = advanceTargetTitle(evaluation, field.name);
2887
+ const {editFieldFor: editFieldFor, previewFieldFor: previewFieldFor, discardFieldPreviewFor: discardFieldPreviewFor} = useWorkflowContext(), me = sanity.useCurrentUser(), held = useHeldWorkflowEntry(instanceId), entry = held ? workflowEngine.resolveFieldEntry(held.instance, field) : void 0, evaluation = held?.evaluation, target = editTargetOf(field), consequence = fieldConsequence(evaluation, field.name), advanceTo = advanceTargetTitle(evaluation, field.name);
2719
2888
  /* @__PURE__ */
2720
2889
  return jsxRuntime.jsx(EditableField, {
2721
2890
  field: field,
@@ -3257,6 +3426,27 @@ function ActivityStatusIcon({status: status}) {
3257
3426
  });
3258
3427
  }
3259
3428
 
3429
+ function LoadingRow({label: label, padding: padding}) {
3430
+ /* @__PURE__ */
3431
+ return jsxRuntime.jsxs(ui.Flex, {
3432
+ align: "center",
3433
+ gap: 2,
3434
+ ...padding === void 0 ? {} : {
3435
+ padding: padding
3436
+ },
3437
+ children: [
3438
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
3439
+ muted: !0,
3440
+ size: 1
3441
+ }),
3442
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
3443
+ muted: !0,
3444
+ size: 1,
3445
+ children: label
3446
+ }) ]
3447
+ });
3448
+ }
3449
+
3260
3450
  function useSpaceToken(index) {
3261
3451
  const value = ui.useTheme_v2().space[index];
3262
3452
  if (value === void 0) throw new Error(`theme is missing space token ${index}`);
@@ -3287,6 +3477,27 @@ function useBadgeCapTrim() {
3287
3477
  };
3288
3478
  }
3289
3479
 
3480
+ function AvatarDisplay({avatar: avatar, "aria-label": ariaLabel}) {
3481
+ const trim = useAvatarCapTrim();
3482
+ /* @__PURE__ */
3483
+ return jsxRuntime.jsx(ui.Box, {
3484
+ "aria-label": ariaLabel,
3485
+ as: "span",
3486
+ padding: 2,
3487
+ style: {
3488
+ display: "inline-flex"
3489
+ },
3490
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
3491
+ align: "center",
3492
+ justify: "center",
3493
+ style: {
3494
+ margin: trim
3495
+ },
3496
+ children: avatar
3497
+ })
3498
+ });
3499
+ }
3500
+
3290
3501
  function AvatarButton({avatar: avatar, onClick: onClick, onMouseDown: onMouseDown, as: as, "aria-label": ariaLabel, ref: ref}) {
3291
3502
  const trim = useAvatarCapTrim();
3292
3503
  /* @__PURE__ */
@@ -3329,10 +3540,13 @@ function UserAvatar({id: id}) {
3329
3540
  });
3330
3541
  }
3331
3542
 
3332
- function UserAvatarGroup({ids: ids}) {
3543
+ function UserAvatarGroup({ids: ids, hint: hint}) {
3333
3544
  const displays = useUserDisplays(ids);
3334
3545
  return displays.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
3335
3546
  text: displays.map(d => d.name).join(", "),
3547
+ ...hint === void 0 ? {} : {
3548
+ description: hint
3549
+ },
3336
3550
  children: /* @__PURE__ */ jsxRuntime.jsx(workflowComponents.MemberAvatarGroup, {
3337
3551
  members: displays.map(display => ({
3338
3552
  id: display.id,
@@ -3343,9 +3557,26 @@ function UserAvatarGroup({ids: ids}) {
3343
3557
  });
3344
3558
  }
3345
3559
 
3346
- function AssignActivityControl({instanceId: instanceId, field: field, assigneeIds: assigneeIds = []}) {
3347
- const {editFieldFor: editFieldFor} = useWorkflowContext(), toast = useClosableToast(), [open, setOpen] = react.useState(!1), [busy, setBusy] = react.useState(!1), members = field.value ?? [], selectedIds = new Set(members.flatMap(m => m.type === "user" ? [ m.id ] : [])), toggle = async memberId => {
3348
- if (!busy) {
3560
+ function AssignActivityControl({instanceId: instanceId, state: state, assigneeIds: assigneeIds = []}) {
3561
+ const {editFieldFor: editFieldFor} = useWorkflowContext(), toast = useClosableToast(), [open, setOpen] = react.useState(!1), [busy, setBusy] = react.useState(!1);
3562
+ if (state.kind === "none") return assigneeIds.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(AvatarDisplay, {
3563
+ "aria-label": "Assignees",
3564
+ avatar: /* @__PURE__ */ jsxRuntime.jsx(UserAvatarGroup, {
3565
+ ids: assigneeIds
3566
+ })
3567
+ });
3568
+ if (state.kind === "closed") return assigneeIds.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(AvatarButton, {
3569
+ "aria-label": "Assignees",
3570
+ as: "span",
3571
+ avatar: /* @__PURE__ */ jsxRuntime.jsx(UserAvatarGroup, {
3572
+ hint: state.reason,
3573
+ ids: assigneeIds
3574
+ }),
3575
+ onClick: stopRowClick,
3576
+ onMouseDown: stopRowMouseDown
3577
+ });
3578
+ const field = state.kind === "editable" ? state.field : void 0, members = field?.value ?? [], selectedIds = new Set(members.flatMap(m => m.type === "user" ? [ m.id ] : [])), toggle = async memberId => {
3579
+ if (!(busy || field === void 0)) {
3349
3580
  setBusy(!0);
3350
3581
  try {
3351
3582
  selectedIds.has(memberId) ? await editFieldFor(instanceId, {
@@ -3398,8 +3629,11 @@ function AssignActivityControl({instanceId: instanceId, field: field, assigneeId
3398
3629
  /* @__PURE__ */
3399
3630
  return jsxRuntime.jsx(DismissablePopover, {
3400
3631
  content: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
3401
- onClick: e => e.stopPropagation(),
3402
- children: /* @__PURE__ */ jsxRuntime.jsx(MemberPicker, {
3632
+ onClick: stopRowClick,
3633
+ children: state.kind === "loading" ? /* @__PURE__ */ jsxRuntime.jsx(LoadingRow, {
3634
+ label: "Loading…",
3635
+ padding: 3
3636
+ }) : /* @__PURE__ */ jsxRuntime.jsx(MemberPicker, {
3403
3637
  onSelect: member => member && void toggle(member.id),
3404
3638
  selectedIds: selectedIds
3405
3639
  })
@@ -3687,9 +3921,9 @@ function BreadcrumbTail({segments: segments, style: style}) {
3687
3921
  }
3688
3922
 
3689
3923
  function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3690
- const frame = /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
3924
+ const frame =
3925
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
3691
3926
  align: "center",
3692
- gap: 1,
3693
3927
  children: [
3694
3928
  /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
3695
3929
  align: "center",
@@ -3703,6 +3937,7 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3703
3937
  align: "center",
3704
3938
  flex: 1,
3705
3939
  gap: 2,
3940
+ paddingLeft: 2,
3706
3941
  style: {
3707
3942
  minWidth: 0
3708
3943
  },
@@ -3710,6 +3945,7 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3710
3945
  }), end === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
3711
3946
  align: "center",
3712
3947
  gap: 2,
3948
+ paddingLeft: 2,
3713
3949
  style: {
3714
3950
  flexShrink: 0
3715
3951
  },
@@ -3718,14 +3954,14 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3718
3954
  });
3719
3955
  return onOpen === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
3720
3956
  padding: 1,
3721
- radius: 2,
3957
+ radius: 3,
3722
3958
  children: frame
3723
3959
  }) :
3724
3960
  /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
3725
3961
  as: "button",
3726
3962
  onClick: onOpen,
3727
3963
  padding: 1,
3728
- radius: 2,
3964
+ radius: 3,
3729
3965
  style: {
3730
3966
  textAlign: "left"
3731
3967
  },
@@ -3912,6 +4148,222 @@ function AutomationActionRow({actionEval: actionEval, definition: definition}) {
3912
4148
  });
3913
4149
  }
3914
4150
 
4151
+ function inlineActionCount(args) {
4152
+ const {available: available, actionWidths: actionWidths, menuWidth: menuWidth, gap: gap} = args, max = Math.min(3, actionWidths.length);
4153
+ for (let count = max; count > 0; count--) {
4154
+ const buttons = actionWidths.slice(0, count).reduce((sum, w) => sum + w, 0) + gap * (count - 1), menu = count === actionWidths.length ? 0 : menuWidth + gap;
4155
+ if (buttons + menu <= available) return count;
4156
+ }
4157
+ return 0;
4158
+ }
4159
+
4160
+ const ACTIONS_MENU_TRIGGER = {
4161
+ fontSize: 1,
4162
+ gap: 1,
4163
+ iconRight: ChevronDown.ChevronDownIcon,
4164
+ padding: 2
4165
+ };
4166
+
4167
+ function ActionMenuItem({actionEval: actionEval, instanceId: instanceId, activity: activity, onCollectParams: onCollectParams}) {
4168
+ const rowKind = actionRowKind(actionEval), label = actionTriggerLabel(actionEval), {tone: tone, icon: icon} = actionButtonFace({
4169
+ actionEval: actionEval,
4170
+ activityName: activity
4171
+ }), iconProp = icon ? {
4172
+ icon: icon
4173
+ } : {}, {fire: fire, pending: pending} = useFireAction({
4174
+ instanceId: instanceId,
4175
+ activity: activity,
4176
+ action: actionEval.action.name,
4177
+ label: label
4178
+ });
4179
+ return rowKind.kind === "disabled" ? /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
4180
+ disabled: !0,
4181
+ ...iconProp,
4182
+ text: label,
4183
+ tone: tone
4184
+ }) : rowKind.kind === "plain" ? /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
4185
+ disabled: pending,
4186
+ ...iconProp,
4187
+ onClick: () => {
4188
+ fire();
4189
+ },
4190
+ text: label,
4191
+ tone: tone
4192
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
4193
+ disabled: pending,
4194
+ ...iconProp,
4195
+ onClick: onCollectParams,
4196
+ text: label,
4197
+ tone: tone
4198
+ });
4199
+ }
4200
+
4201
+ function rowShield(menuButton) {
4202
+ /* @__PURE__ */
4203
+ return jsxRuntime.jsx("span", {
4204
+ onClick: e => e.stopPropagation(),
4205
+ onMouseDown: stopRowMouseDown,
4206
+ style: {
4207
+ display: "contents"
4208
+ },
4209
+ children: menuButton
4210
+ });
4211
+ }
4212
+
4213
+ function ActionsMenuButton({actions: actions, instanceId: instanceId, activity: activity, label: label, mode: mode = "ghost", inButtonCard: inButtonCard = !1}) {
4214
+ const [paramsAction, setParamsAction] = react.useState(void 0), clusterPending = useActionClusterPending(), menuId = react.useId(), menuButton = /* @__PURE__ */ jsxRuntime.jsx(ui.MenuButton, {
4215
+ button: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4216
+ ...ACTIONS_MENU_TRIGGER,
4217
+ mode: mode,
4218
+ text: label,
4219
+ ...inButtonCard ? {
4220
+ as: "span"
4221
+ } : {},
4222
+ ...clusterPending ? {
4223
+ disabled: !0
4224
+ } : {}
4225
+ }),
4226
+ id: `workflow-actions-menu-${menuId}`,
4227
+ menu: /* @__PURE__ */ jsxRuntime.jsx(ui.Menu, {
4228
+ children: actions.map(a => /* @__PURE__ */ jsxRuntime.jsx(ActionMenuItem, {
4229
+ actionEval: a,
4230
+ activity: activity,
4231
+ instanceId: instanceId,
4232
+ onCollectParams: () => setParamsAction(a)
4233
+ }, a.action.name))
4234
+ }),
4235
+ popover: {
4236
+ placement: "top-end",
4237
+ portal: !0
4238
+ }
4239
+ });
4240
+ /* @__PURE__ */
4241
+ return jsxRuntime.jsxs(jsxRuntime.Fragment, {
4242
+ children: [ inButtonCard ? rowShield(menuButton) : menuButton, paramsAction ? /* @__PURE__ */ jsxRuntime.jsx(RowClickShield, {
4243
+ active: inButtonCard,
4244
+ children: /* @__PURE__ */ jsxRuntime.jsx(ParamsActionDialog, {
4245
+ actionEval: paramsAction,
4246
+ activity: activity,
4247
+ instanceId: instanceId,
4248
+ onClose: () => setParamsAction(void 0)
4249
+ })
4250
+ }) : null ]
4251
+ });
4252
+ }
4253
+
4254
+ const FOOTER_GAP_PX = 8, MORE_ACTIONS_LABEL = "More actions";
4255
+
4256
+ function TerminalFooter({actions: actions, activity: activity, instanceId: instanceId}) {
4257
+ const lock = useActionClusterState(), [only, ...rest] = actions;
4258
+ return only ? /* @__PURE__ */ jsxRuntime.jsx(ActionClusterProvider, {
4259
+ value: lock,
4260
+ children: rest.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(FooterRow, {
4261
+ children: /* @__PURE__ */ jsxRuntime.jsx(ActivityActionRow, {
4262
+ actionEval: only,
4263
+ activity: activity,
4264
+ instanceId: instanceId
4265
+ })
4266
+ }) : /* @__PURE__ */ jsxRuntime.jsx(FittedActions, {
4267
+ actions: actions,
4268
+ activity: activity,
4269
+ instanceId: instanceId
4270
+ })
4271
+ }) : null;
4272
+ }
4273
+
4274
+ function FooterRow({children: children, onElement: onElement}) {
4275
+ /* @__PURE__ */
4276
+ return jsxRuntime.jsx(ui.Box, {
4277
+ padding: 3,
4278
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4279
+ align: "center",
4280
+ justify: "flex-end",
4281
+ ...onElement ? {
4282
+ ref: onElement
4283
+ } : {},
4284
+ style: {
4285
+ gap: FOOTER_GAP_PX
4286
+ },
4287
+ children: children
4288
+ })
4289
+ });
4290
+ }
4291
+
4292
+ function FittedActions({actions: actions, activity: activity, instanceId: instanceId}) {
4293
+ const [footerEl, setFooterEl] = react.useState(null), [measureEl, setMeasureEl] = react.useState(null), footerSize = ui.useElementSize(footerEl), measureSize = ui.useElementSize(measureEl), [inline, setInline] = react.useState(void 0);
4294
+ react.useLayoutEffect(() => {
4295
+ if (measureEl === null || footerSize === null || measureSize === null) return;
4296
+ const widths = Array.from(measureEl.children, clone => clone.getBoundingClientRect().width);
4297
+ setInline(inlineActionCount({
4298
+ available: footerSize.border.width,
4299
+ actionWidths: widths.slice(0, -1),
4300
+ menuWidth: widths.at(-1) ?? 0,
4301
+ gap: FOOTER_GAP_PX
4302
+ }));
4303
+ }, [ actions, footerSize, measureEl, measureSize ]);
4304
+ const overflow = inline === void 0 ? [] : actions.slice(inline);
4305
+ /* @__PURE__ */
4306
+ return jsxRuntime.jsxs(jsxRuntime.Fragment, {
4307
+ children: [
4308
+ /* @__PURE__ */ jsxRuntime.jsx(MeasureRow, {
4309
+ actions: actions,
4310
+ activity: activity,
4311
+ onElement: setMeasureEl
4312
+ }),
4313
+ /* @__PURE__ */ jsxRuntime.jsxs(FooterRow, {
4314
+ onElement: setFooterEl,
4315
+ children: [ inline === void 0 ? null : actions.slice(0, inline).map(a => /* @__PURE__ */ jsxRuntime.jsx(ActivityActionRow, {
4316
+ actionEval: a,
4317
+ activity: activity,
4318
+ instanceId: instanceId
4319
+ }, a.action.name)), overflow.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ActionsMenuButton, {
4320
+ actions: overflow,
4321
+ activity: activity,
4322
+ instanceId: instanceId,
4323
+ label: MORE_ACTIONS_LABEL,
4324
+ mode: "default"
4325
+ }) : null ]
4326
+ }) ]
4327
+ });
4328
+ }
4329
+
4330
+ function MeasureRow({actions: actions, activity: activity, onElement: onElement}) {
4331
+ /* @__PURE__ */
4332
+ return jsxRuntime.jsx("div", {
4333
+ "aria-hidden": !0,
4334
+ style: {
4335
+ height: 0,
4336
+ overflow: "hidden"
4337
+ },
4338
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", {
4339
+ ref: onElement,
4340
+ style: {
4341
+ display: "inline-flex",
4342
+ whiteSpace: "nowrap"
4343
+ },
4344
+ children: [ actions.map(a => {
4345
+ const {icon: icon} = actionButtonFace({
4346
+ actionEval: a,
4347
+ activityName: activity
4348
+ });
4349
+ /* @__PURE__ */
4350
+ return jsxRuntime.jsx(ui.Button, {
4351
+ as: "span",
4352
+ fontSize: 1,
4353
+ ...actionIconProps(icon),
4354
+ padding: 2,
4355
+ text: actionTriggerLabel(a)
4356
+ }, a.action.name);
4357
+ }),
4358
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4359
+ ...ACTIONS_MENU_TRIGGER,
4360
+ as: "span",
4361
+ text: MORE_ACTIONS_LABEL
4362
+ }) ]
4363
+ })
4364
+ });
4365
+ }
4366
+
3915
4367
  function ActivityDetailDialog({entry: entry, activityName: activityName, breadcrumb: breadcrumb, definition: definition, document: document, onClose: onClose}) {
3916
4368
  const detail = deriveActivityDetail({
3917
4369
  entry: entry,
@@ -3921,12 +4373,11 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
3921
4373
  const instanceId = entry.instance._id, notice = blockedNotice(detail), hasBody = detail.state.length > 0 || notice !== void 0;
3922
4374
  /* @__PURE__ */
3923
4375
  return jsxRuntime.jsxs(ui.Dialog, {
3924
- footer: /* @__PURE__ */ jsxRuntime.jsx(TerminalFooter, {
3925
- activityName: activityName,
3926
- detail: detail,
3927
- instanceId: instanceId,
3928
- onClose: onClose
3929
- }),
4376
+ footer: detail.presentation.terminalActions.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(TerminalFooter, {
4377
+ actions: detail.presentation.terminalActions,
4378
+ activity: activityName,
4379
+ instanceId: instanceId
4380
+ }) : void 0,
3930
4381
  header: /* @__PURE__ */ jsxRuntime.jsx(Breadcrumb, {
3931
4382
  emphasizeCurrent: !0,
3932
4383
  segments: [ ...breadcrumb, detail.title ]
@@ -4095,8 +4546,11 @@ function FieldRow({field: field, detail: detail, entry: entry, activityName: act
4095
4546
  /* @__PURE__ */ jsxRuntime.jsx(FieldValue, {
4096
4547
  field: field
4097
4548
  }), assignField ? /* @__PURE__ */ jsxRuntime.jsx(AssignActivityControl, {
4098
- field: assignField,
4099
- instanceId: instanceId
4549
+ instanceId: instanceId,
4550
+ state: {
4551
+ kind: "editable",
4552
+ field: assignField
4553
+ }
4100
4554
  }) : null ]
4101
4555
  }) ]
4102
4556
  });
@@ -4129,31 +4583,6 @@ function blockedNotice(detail) {
4129
4583
  });
4130
4584
  }
4131
4585
 
4132
- function TerminalFooter({detail: detail, instanceId: instanceId, activityName: activityName, onClose: onClose}) {
4133
- /* @__PURE__ */
4134
- return jsxRuntime.jsx(ui.Box, {
4135
- padding: 3,
4136
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
4137
- align: "center",
4138
- gap: 2,
4139
- justify: "flex-end",
4140
- children: [
4141
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4142
- fontSize: 1,
4143
- mode: "bleed",
4144
- onClick: onClose,
4145
- padding: 2,
4146
- text: "Cancel"
4147
- }),
4148
- /* @__PURE__ */ jsxRuntime.jsx(TerminalActions, {
4149
- actions: detail.presentation.terminalActions,
4150
- activity: activityName,
4151
- instanceId: instanceId
4152
- }) ]
4153
- })
4154
- });
4155
- }
4156
-
4157
4586
  function EmptyState({action: action, description: description, icon: icon, title: title}) {
4158
4587
  /* @__PURE__ */
4159
4588
  return jsxRuntime.jsx(ui.Card, {
@@ -4207,7 +4636,26 @@ function CountedLabel({count: count, label: label}) {
4207
4636
  });
4208
4637
  }
4209
4638
 
4210
- function GroupHeading({count: count, title: title, end: end}) {
4639
+ function SpinnerSlot({busy: busy, size: size}) {
4640
+ /* @__PURE__ */
4641
+ return jsxRuntime.jsx(ui.Flex, {
4642
+ align: "center",
4643
+ flex: "none",
4644
+ justify: "center",
4645
+ style: {
4646
+ width: 19,
4647
+ height: 19
4648
+ },
4649
+ children: busy ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
4650
+ muted: !0,
4651
+ ...size !== void 0 ? {
4652
+ size: size
4653
+ } : {}
4654
+ }) : null
4655
+ });
4656
+ }
4657
+
4658
+ function GroupHeading({busy: busy, count: count, title: title, end: end}) {
4211
4659
  /* @__PURE__ */
4212
4660
  return jsxRuntime.jsxs(ui.Flex, {
4213
4661
  align: "center",
@@ -4222,11 +4670,70 @@ function GroupHeading({count: count, title: title, end: end}) {
4222
4670
  weight: "semibold",
4223
4671
  children: title
4224
4672
  })
4673
+ }), busy === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(SpinnerSlot, {
4674
+ busy: busy,
4675
+ size: 1
4225
4676
  }), end === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
4226
4677
  children: [
4227
- /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4228
- flex: 1
4229
- }), end ]
4678
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4679
+ flex: 1
4680
+ }), end ]
4681
+ }) ]
4682
+ });
4683
+ }
4684
+
4685
+ function CopyDetailsButton({unreadable: unreadable}) {
4686
+ const toast = useClosableToast();
4687
+ /* @__PURE__ */
4688
+ return jsxRuntime.jsx(ui.Button, {
4689
+ fontSize: 1,
4690
+ mode: "ghost",
4691
+ onClick: () => {
4692
+ navigator.clipboard.writeText(unreadableDocsReport(unreadable)).then(() => toast.push({
4693
+ status: "success",
4694
+ title: "Details copied"
4695
+ }), () => toast.push({
4696
+ status: "error",
4697
+ title: "Could not copy the details"
4698
+ }));
4699
+ },
4700
+ padding: 2,
4701
+ text: "Copy details"
4702
+ });
4703
+ }
4704
+
4705
+ function UnreadableDocsNote({unreadable: unreadable}) {
4706
+ if (unreadable.length === 0) return null;
4707
+ 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.`;
4708
+ /* @__PURE__ */
4709
+ return jsxRuntime.jsxs(ui.Card, {
4710
+ padding: 2,
4711
+ paddingLeft: 3,
4712
+ radius: 3,
4713
+ style: {
4714
+ alignItems: "center",
4715
+ display: "flex"
4716
+ },
4717
+ tone: "caution",
4718
+ children: [
4719
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
4720
+ align: "center",
4721
+ flex: 1,
4722
+ gap: 3,
4723
+ children: [
4724
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4725
+ muted: !0,
4726
+ size: 1,
4727
+ children: /* @__PURE__ */ jsxRuntime.jsx(WarningOutline.WarningOutlineIcon, {})
4728
+ }),
4729
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4730
+ muted: !0,
4731
+ size: 1,
4732
+ children: label
4733
+ }) ]
4734
+ }),
4735
+ /* @__PURE__ */ jsxRuntime.jsx(CopyDetailsButton, {
4736
+ unreadable: unreadable
4230
4737
  }) ]
4231
4738
  });
4232
4739
  }
@@ -4237,7 +4744,7 @@ const HEADLINES = {
4237
4744
  };
4238
4745
 
4239
4746
  function remediation(invalid) {
4240
- return invalid.reason === "model-ahead" ? `"${invalid.documentId}" was written by a newer engine data model. Upgrade @sanity/workflow-engine (and this plugin) to work with it.` : `"${invalid.documentId}" has a corrupt stored shape and cannot be read. Inspect the document, or reset the workflow dataset.`;
4747
+ 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.`;
4241
4748
  }
4242
4749
 
4243
4750
  function InvalidDocNotice({invalid: invalid}) {
@@ -4248,20 +4755,22 @@ function InvalidDocNotice({invalid: invalid}) {
4248
4755
  radius: 2,
4249
4756
  tone: "caution",
4250
4757
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
4251
- space: 2,
4758
+ space: 3,
4252
4759
  children: [
4253
- /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4254
- text: invalid.message,
4255
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4256
- size: 1,
4257
- weight: "medium",
4258
- children: HEADLINES[invalid.reason]
4259
- })
4760
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4761
+ size: 1,
4762
+ weight: "medium",
4763
+ children: HEADLINES[invalid.reason]
4260
4764
  }),
4261
4765
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4262
4766
  muted: !0,
4263
4767
  size: 1,
4264
4768
  children: remediation(invalid)
4769
+ }),
4770
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4771
+ children: /* @__PURE__ */ jsxRuntime.jsx(CopyDetailsButton, {
4772
+ unreadable: [ invalid ]
4773
+ })
4265
4774
  }) ]
4266
4775
  })
4267
4776
  });
@@ -4286,6 +4795,18 @@ function TabSwitch({ariaControls: ariaControls, idPrefix: idPrefix, options: opt
4286
4795
  });
4287
4796
  }
4288
4797
 
4798
+ function useDelayedFlag(active, delayMs) {
4799
+ const [held, setHeld] = react.useState(!1);
4800
+ return react.useEffect(() => {
4801
+ if (!active) {
4802
+ setHeld(!1);
4803
+ return;
4804
+ }
4805
+ const timer = setTimeout(() => setHeld(!0), delayMs);
4806
+ return () => clearTimeout(timer);
4807
+ }, [ active, delayMs ]), held && active;
4808
+ }
4809
+
4289
4810
  function instanceTitle(instance, definition) {
4290
4811
  return definition?.title ?? instance.definition;
4291
4812
  }
@@ -4301,6 +4822,30 @@ function openActivityGone(args) {
4301
4822
  return evaluation !== void 0 && !evaluation.currentStage.activities.some(a => a.activity.name === target.activityName);
4302
4823
  }
4303
4824
 
4825
+ function HintedMenuButton({hint: hint, ...menuButtonProps}) {
4826
+ const [open, setOpen] = react.useState(!1), popover = {
4827
+ tone: "default",
4828
+ ...menuButtonProps.popover?.placement === void 0 ? {
4829
+ placement: "bottom-end",
4830
+ fallbackPlacements: [ "top-end" ]
4831
+ } : {},
4832
+ ...menuButtonProps.popover
4833
+ };
4834
+ return hint === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.MenuButton, {
4835
+ ...menuButtonProps,
4836
+ popover: popover
4837
+ }) : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4838
+ disabled: open,
4839
+ text: hint,
4840
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.MenuButton, {
4841
+ ...menuButtonProps,
4842
+ onClose: () => setOpen(!1),
4843
+ onOpen: () => setOpen(!0),
4844
+ popover: popover
4845
+ })
4846
+ });
4847
+ }
4848
+
4304
4849
  function subjectGdr(ctx) {
4305
4850
  return !ctx.subjectDocId || !ctx.mapping ? null : subjectGdrRef({
4306
4851
  mapping: ctx.mapping,
@@ -4348,6 +4893,10 @@ function missingInitFields(initFields, values) {
4348
4893
  return initFields.filter(entry => gatesStart(entry) && !isFieldFilled(entry, values[entry.name] ?? null));
4349
4894
  }
4350
4895
 
4896
+ function numericFilled(value) {
4897
+ return value !== null && value !== "" && Number.isFinite(Number(value));
4898
+ }
4899
+
4351
4900
  const filledByKind = {
4352
4901
  actor: value => value !== null,
4353
4902
  array: value => Array.isArray(value) && value.length > 0,
@@ -4355,7 +4904,8 @@ const filledByKind = {
4355
4904
  boolean: () => !0,
4356
4905
  "doc.ref": value => value !== null,
4357
4906
  "doc.refs": value => Array.isArray(value) && value.length > 0,
4358
- number: value => value !== null && value !== "" && Number.isFinite(Number(value)),
4907
+ number: numericFilled,
4908
+ progress: numericFilled,
4359
4909
  "release.ref": value => value !== null,
4360
4910
  subject: value => value !== null
4361
4911
  };
@@ -4373,7 +4923,14 @@ const asInitial = candidate => candidate, scalarInitialValue = (entry, value) =>
4373
4923
  type: entry.type,
4374
4924
  name: entry.name,
4375
4925
  value: value
4376
- }), initialValueByKind = {
4926
+ }), numericInitialValue = (entry, value) => {
4927
+ const n = Number(value);
4928
+ return Number.isFinite(n) ? asInitial({
4929
+ type: entry.type,
4930
+ name: entry.name,
4931
+ value: n
4932
+ }) : null;
4933
+ }, initialValueByKind = {
4377
4934
  actor: (entry, value) => asInitial({
4378
4935
  type: entry.type,
4379
4936
  name: entry.name,
@@ -4398,14 +4955,8 @@ const asInitial = candidate => candidate, scalarInitialValue = (entry, value) =>
4398
4955
  value: value
4399
4956
  }),
4400
4957
  subject: singleRefInitialValue,
4401
- number: (entry, value) => {
4402
- const n = Number(value);
4403
- return Number.isFinite(n) ? asInitial({
4404
- type: entry.type,
4405
- name: entry.name,
4406
- value: n
4407
- }) : null;
4408
- },
4958
+ number: numericInitialValue,
4959
+ progress: numericInitialValue,
4409
4960
  "release.ref": (entry, value) => asInitial({
4410
4961
  type: entry.type,
4411
4962
  name: entry.name,
@@ -4884,6 +5435,58 @@ function StartWorkflowMenuItem({mapping: mapping, docId: docId, initialValue: in
4884
5435
  }) : item;
4885
5436
  }
4886
5437
 
5438
+ function settledDim(settled) {
5439
+ return settled ? {
5440
+ opacity: .66
5441
+ } : void 0;
5442
+ }
5443
+
5444
+ function ActivityStatusLead({status: status}) {
5445
+ /* @__PURE__ */
5446
+ return jsxRuntime.jsx(HoverHint, {
5447
+ text: ACTIVITY_STATUS_LABEL[status],
5448
+ children: /* @__PURE__ */ jsxRuntime.jsx(ActivityStatusIcon, {
5449
+ status: status
5450
+ })
5451
+ });
5452
+ }
5453
+
5454
+ function ActivityRowTitle({dimmed: dimmed, children: children}) {
5455
+ /* @__PURE__ */
5456
+ return jsxRuntime.jsx(ui.Text, {
5457
+ size: 1,
5458
+ style: {
5459
+ ...dimmed,
5460
+ minWidth: 0
5461
+ },
5462
+ textOverflow: "ellipsis",
5463
+ weight: "medium",
5464
+ children: children
5465
+ });
5466
+ }
5467
+
5468
+ function RowTerminalActions({actions: actions, activity: activity, instanceId: instanceId}) {
5469
+ const lock = useActionClusterState(), [only, ...rest] = actions;
5470
+ return only ? /* @__PURE__ */ jsxRuntime.jsx(ActionClusterProvider, {
5471
+ value: lock,
5472
+ children: rest.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ActivityActionRow, {
5473
+ actionEval: only,
5474
+ activity: activity,
5475
+ chrome: {
5476
+ mode: "ghost",
5477
+ inButtonCard: !0
5478
+ },
5479
+ instanceId: instanceId
5480
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ActionsMenuButton, {
5481
+ actions: actions,
5482
+ activity: activity,
5483
+ inButtonCard: !0,
5484
+ instanceId: instanceId,
5485
+ label: "Select action"
5486
+ })
5487
+ }) : null;
5488
+ }
5489
+
4887
5490
  function TitleTag({hint: hint, tone: tone, children: children}) {
4888
5491
  const badgeTrim = useBadgeCapTrim();
4889
5492
  /* @__PURE__ */
@@ -4900,54 +5503,34 @@ function TitleTag({hint: hint, tone: tone, children: children}) {
4900
5503
  });
4901
5504
  }
4902
5505
 
4903
- function ActivityRow({activityEval: activityEval, breadcrumb: breadcrumb, assigneeIds: assigneeIds, assignField: assignField, instanceId: instanceId, onOpen: onOpen, terminalActions: terminalActions = []}) {
4904
- const {activity: activity, status: status} = activityEval, settled = workflowEngine.isTerminalActivityStatus(status), dimmed = settled ? {
4905
- opacity: .66
4906
- } : void 0;
4907
- let assignmentControl = null;
4908
- return assignField ? assignmentControl = /* @__PURE__ */ jsxRuntime.jsx(AssignActivityControl, {
4909
- assigneeIds: assigneeIds,
4910
- field: assignField,
4911
- instanceId: instanceId
4912
- }) : assigneeIds.length > 0 && (assignmentControl = /* @__PURE__ */ jsxRuntime.jsx(AvatarButton, {
4913
- "aria-label": "Assignees",
4914
- as: "span",
4915
- avatar: /* @__PURE__ */ jsxRuntime.jsx(UserAvatarGroup, {
4916
- ids: assigneeIds
4917
- })
4918
- })),
4919
- /* @__PURE__ */ jsxRuntime.jsxs(WorkRow, {
5506
+ function ActivityRow({face: face, breadcrumb: breadcrumb, assigneeIds: assigneeIds, assignState: assignState, dateControl: dateControl, instanceId: instanceId, onOpen: onOpen, terminalActions: terminalActions = []}) {
5507
+ const settled = workflowEngine.isTerminalActivityStatus(face.status), dimmed = settledDim(settled);
5508
+ /* @__PURE__ */
5509
+ return jsxRuntime.jsxs(WorkRow, {
4920
5510
  end: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
4921
- children: [ settled || terminalActions.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(TerminalActions, {
5511
+ children: [ settled || terminalActions.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(RowTerminalActions, {
4922
5512
  actions: terminalActions,
4923
- activity: activity.name,
4924
- inButtonCard: !0,
5513
+ activity: face.activityName,
5514
+ instanceId: instanceId
5515
+ }), dateControl,
5516
+ /* @__PURE__ */ jsxRuntime.jsx(AssignActivityControl, {
5517
+ assigneeIds: assigneeIds,
4925
5518
  instanceId: instanceId,
4926
- mode: "ghost"
4927
- }), assignmentControl ]
5519
+ state: assignState
5520
+ }) ]
4928
5521
  }),
4929
- lead:
4930
- /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4931
- text: ACTIVITY_STATUS_LABEL[status],
4932
- children: /* @__PURE__ */ jsxRuntime.jsx(ActivityStatusIcon, {
4933
- status: status
4934
- })
5522
+ lead: /* @__PURE__ */ jsxRuntime.jsx(ActivityStatusLead, {
5523
+ status: face.status
4935
5524
  }),
4936
5525
  onOpen: onOpen,
4937
5526
  children: [
4938
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4939
- size: 1,
4940
- style: {
4941
- ...dimmed,
4942
- minWidth: 0
4943
- },
4944
- textOverflow: "ellipsis",
4945
- weight: "medium",
4946
- children: activityLabel(activity)
4947
- }), showsAutomatedPill(activityEval) ? /* @__PURE__ */ jsxRuntime.jsx(TitleTag, {
5527
+ /* @__PURE__ */ jsxRuntime.jsx(ActivityRowTitle, {
5528
+ dimmed: dimmed,
5529
+ children: face.title
5530
+ }), face.automated ? /* @__PURE__ */ jsxRuntime.jsx(TitleTag, {
4948
5531
  hint: "Completes on its own — no user action is needed",
4949
5532
  children: "Automated"
4950
- }) : null, showsBlockedTag(activityEval) ? /* @__PURE__ */ jsxRuntime.jsx(TitleTag, {
5533
+ }) : null, face.blocked ? /* @__PURE__ */ jsxRuntime.jsx(TitleTag, {
4951
5534
  hint: "Nothing can be done on this activity right now — open it to see why",
4952
5535
  tone: "caution",
4953
5536
  children: "Blocked"
@@ -4988,7 +5571,6 @@ function ForMeList({entry: entry, work: work, identity: identity, onOpenActivity
4988
5571
  activityState: activityState,
4989
5572
  editableFields: entry.evaluation?.editableFields
4990
5573
  }),
4991
- activityEval: activityEval,
4992
5574
  breadcrumb: breadcrumb,
4993
5575
  instanceId: instance._id,
4994
5576
  onOpen: () => onOpenActivity(activityEval.activity.name)
@@ -5032,33 +5614,12 @@ function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity}) {
5032
5614
  activityState: activityState,
5033
5615
  editableFields: entry.evaluation?.editableFields
5034
5616
  }),
5035
- activityEval: t,
5036
5617
  instanceId: entry.instance._id,
5037
5618
  onOpen: () => onOpenActivity(t.activity.name)
5038
5619
  }, t.activity.name))
5039
5620
  });
5040
5621
  }
5041
5622
 
5042
- function LoadingRow({label: label, padding: padding}) {
5043
- /* @__PURE__ */
5044
- return jsxRuntime.jsxs(ui.Flex, {
5045
- align: "center",
5046
- gap: 2,
5047
- ...padding === void 0 ? {} : {
5048
- padding: padding
5049
- },
5050
- children: [
5051
- /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
5052
- muted: !0
5053
- }),
5054
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5055
- muted: !0,
5056
- size: 1,
5057
- children: label
5058
- }) ]
5059
- });
5060
- }
5061
-
5062
5623
  function instanceNotice(entry) {
5063
5624
  const {instance: instance, committed: committed, evaluation: evaluation} = entry, state = workflowEngine.terminalState(instance);
5064
5625
  if (state !== "in-flight")
@@ -5084,9 +5645,16 @@ function instanceNotice(entry) {
5084
5645
  size: 1,
5085
5646
  children: "This workflow didn’t finish starting — use Start workflow to continue it"
5086
5647
  })
5087
- }) : pending ? /* @__PURE__ */ jsxRuntime.jsx(LoadingRow, {
5088
- label: "Loading…",
5089
- padding: 4
5648
+ }) : pending ? /* @__PURE__ */ jsxRuntime.jsx(WorkRow, {
5649
+ lead: /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
5650
+ muted: !0,
5651
+ size: 1
5652
+ }),
5653
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5654
+ muted: !0,
5655
+ size: 1,
5656
+ children: "Loading…"
5657
+ })
5090
5658
  }) : null;
5091
5659
  }
5092
5660
 
@@ -5098,6 +5666,7 @@ const EDITOR_BY_KIND = {
5098
5666
  date: "scalar",
5099
5667
  datetime: "scalar",
5100
5668
  number: "scalar",
5669
+ progress: "scalar",
5101
5670
  string: "scalar",
5102
5671
  text: "scalar",
5103
5672
  url: "scalar"
@@ -5160,9 +5729,16 @@ function faceOf(args) {
5160
5729
  } : workflowEngine.isSingleDocRefEntry(entry) && entry.value !== null ? {
5161
5730
  kind: "docRef",
5162
5731
  gdr: entry.value
5732
+ } : entry._type === "progress" && entry.value !== null ? {
5733
+ kind: "progress",
5734
+ value: entry.value
5163
5735
  } : todoShaped && entry._type === "array" ? textFace(todoTextOf(entry)) : textFace(textOf(entry));
5164
5736
  }
5165
5737
 
5738
+ function progressFillPercent(face) {
5739
+ if (face.kind === "progress") return workflowComponents.clampProgress(face.value);
5740
+ }
5741
+
5166
5742
  function deriveFieldPills(args) {
5167
5743
  const {scope: scope, definition: definition, instance: instance, committed: committed, evaluation: evaluation} = args, declared = scope === "workflow" ? definition?.fields ?? [] : definition?.stages.find(s => s.name === instance.currentStage)?.fields ?? [], entries = scope === "workflow" ? instance.fields : workflowEngine.findOpenStageEntry(instance)?.fields ?? [], entriesByName = new Map(entries.map(entry => [ entry.name, entry ])), editables = isEvaluationCurrent({
5168
5744
  committed: committed,
@@ -5303,6 +5879,15 @@ function SetFace({face: face}) {
5303
5879
  })
5304
5880
  }) : face.kind === "assignees" ? /* @__PURE__ */ jsxRuntime.jsx(AssigneesFace, {
5305
5881
  assignees: face.assignees
5882
+ }) : face.kind === "progress" ?
5883
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5884
+ size: 1,
5885
+ style: {
5886
+ fontVariantNumeric: "tabular-nums",
5887
+ minWidth: "4ch",
5888
+ textAlign: "right"
5889
+ },
5890
+ children: workflowComponents.progressCaption(face.value)
5306
5891
  }) : /* @__PURE__ */ jsxRuntime.jsx(FaceText, {
5307
5892
  children: face.kind === "text" ? face.text : ""
5308
5893
  });
@@ -5356,15 +5941,21 @@ function AssigneesFace({assignees: assignees}) {
5356
5941
  });
5357
5942
  }
5358
5943
 
5359
- function usePillChrome() {
5360
- const {color: color} = ui.useTheme_v2();
5944
+ function usePillChrome(face) {
5945
+ const {color: color} = ui.useTheme_v2(), fillPercent = face === void 0 ? void 0 : progressFillPercent(face), fill = `color-mix(in srgb, ${color.focusRing} 24%, transparent)`, progressFill = fillPercent === void 0 ? {} : {
5946
+ backgroundImage: `linear-gradient(${fill}, ${fill})`,
5947
+ backgroundRepeat: "no-repeat",
5948
+ backgroundSize: `${fillPercent}% 100%`,
5949
+ transition: "background-size 300ms ease"
5950
+ };
5361
5951
  return {
5362
5952
  mode: "bleed",
5363
5953
  padding: 2,
5364
5954
  radius: 6,
5365
5955
  style: {
5366
5956
  boxShadow: `inset 0 0 0 1px ${color.button.ghost.default.enabled.border}`,
5367
- maxWidth: "100%"
5957
+ maxWidth: "100%",
5958
+ ...progressFill
5368
5959
  }
5369
5960
  };
5370
5961
  }
@@ -5395,7 +5986,7 @@ function PillFaceContent({children: children}) {
5395
5986
  }
5396
5987
 
5397
5988
  function PillButton({children: children, pill: pill, hintDisabled: hintDisabled = !1, onClick: onClick}) {
5398
- const chrome = usePillChrome();
5989
+ const chrome = usePillChrome(pill.face);
5399
5990
  /* @__PURE__ */
5400
5991
  return jsxRuntime.jsx(PillHint, {
5401
5992
  disabled: hintDisabled,
@@ -5428,7 +6019,23 @@ function useFieldCommit({entry: entry, editability: editability}) {
5428
6019
  function PillPopover({pill: pill, content: content2, open: open, onToggle: onToggle, onDismiss: onDismiss}) {
5429
6020
  /* @__PURE__ */
5430
6021
  return jsxRuntime.jsx(DismissablePopover, {
5431
- content: content2,
6022
+ content: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
6023
+ gap: 1,
6024
+ children: [
6025
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
6026
+ padding: 3,
6027
+ paddingBottom: 0,
6028
+ style: {
6029
+ maxWidth: 360
6030
+ },
6031
+ children: /* @__PURE__ */ jsxRuntime.jsx(FieldHead, {
6032
+ label: pill.title,
6033
+ ...pill.description === void 0 ? {} : {
6034
+ description: pill.description
6035
+ }
6036
+ })
6037
+ }), content2 ]
6038
+ }),
5432
6039
  fallbackPlacements: [ "top-start" ],
5433
6040
  onDismiss: onDismiss,
5434
6041
  open: open,
@@ -5728,6 +6335,66 @@ function FieldPill({entry: entry, pill: pill, scope: scope}) {
5728
6335
  });
5729
6336
  }
5730
6337
 
6338
+ function Hairline({weight: weight, style: style}) {
6339
+ const {color: color} = ui.useTheme_v2();
6340
+ /* @__PURE__ */
6341
+ return jsxRuntime.jsx("div", {
6342
+ style: {
6343
+ borderTop: `${weight}px solid ${color.border}`,
6344
+ ...style
6345
+ }
6346
+ });
6347
+ }
6348
+
6349
+ function CollapsibleBand({header: header, children: children, background: background = !1, defaultOpen: defaultOpen = !0, sticky: sticky = !1, title: title}) {
6350
+ const [open, setOpen] = react.useState(defaultOpen), verb = open ? "Collapse" : "Expand";
6351
+ /* @__PURE__ */
6352
+ return jsxRuntime.jsxs(ui.Stack, {
6353
+ gap: 1,
6354
+ children: [
6355
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
6356
+ onDoubleClick: event => {
6357
+ event.target instanceof Element && event.target.closest("a, button") || setOpen(v => !v);
6358
+ },
6359
+ padding: 1,
6360
+ radius: 3,
6361
+ style: {
6362
+ userSelect: "none",
6363
+ ...sticky ? {
6364
+ position: "sticky",
6365
+ top: 0,
6366
+ zIndex: 1
6367
+ } : {}
6368
+ },
6369
+ tone: background ? "transparent" : "inherit",
6370
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
6371
+ align: "center",
6372
+ children: [
6373
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
6374
+ "aria-label": `${verb} ${title}`,
6375
+ fontSize: 1,
6376
+ icon: open ? ChevronDown.ChevronDownIcon : ChevronRight.ChevronRightIcon,
6377
+ mode: "bleed",
6378
+ onClick: () => setOpen(v => !v),
6379
+ padding: 2
6380
+ }), header ]
6381
+ })
6382
+ }), open ? children : null ]
6383
+ });
6384
+ }
6385
+
6386
+ function TrailingHairline() {
6387
+ /* @__PURE__ */
6388
+ return jsxRuntime.jsx(ui.Box, {
6389
+ flex: 1,
6390
+ paddingLeft: 1,
6391
+ paddingRight: 1,
6392
+ children: /* @__PURE__ */ jsxRuntime.jsx(Hairline, {
6393
+ weight: .5
6394
+ })
6395
+ });
6396
+ }
6397
+
5731
6398
  const HISTORY_KIND_GROUPS = {
5732
6399
  Stages: [ "stageEntered", "stageExited", "transitionFired", "aborted" ],
5733
6400
  Activities: [ "activityStatusChanged" ],
@@ -6050,76 +6717,60 @@ function InstanceDebugMenu({entry: entry}) {
6050
6717
  });
6051
6718
  }
6052
6719
 
6053
- function InstanceAccordion({entry: entry, children: children}) {
6054
- const definition = useDefinition(entry), {instance: instance, ready: ready} = entry, settled = !isLiveEntry(entry), [open, setOpen] = react.useState(!settled), title = instanceTitle(instance, definition), badgeTrim = useBadgeCapTrim(), status = /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
6055
- align: "center",
6056
- gap: 2,
6057
- style: badgeTrim,
6058
- children: [
6059
- /* @__PURE__ */ jsxRuntime.jsx(AbortedBadge, {
6060
- instance: instance
6061
- }), settled ? null : /* @__PURE__ */ jsxRuntime.jsx(OlderDefinitionBadge, {
6062
- instance: instance
6063
- }), !ready && entry.invalid === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
6064
- muted: !0
6065
- }) : null ]
6066
- });
6720
+ function InstanceAccordion({entry: entry, children: children, defaultOpen: defaultOpen}) {
6721
+ const definition = useDefinition(entry), {instance: instance} = entry, settled = !isLiveEntry(entry), title = instanceTitle(instance, definition), badgeTrim = useBadgeCapTrim();
6067
6722
  /* @__PURE__ */
6068
- return jsxRuntime.jsxs(ui.Stack, {
6069
- gap: 2,
6070
- children: [
6071
- /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
6072
- style: {
6073
- position: "sticky",
6074
- top: 0,
6075
- zIndex: 1
6076
- },
6077
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
6723
+ return jsxRuntime.jsx(CollapsibleBand, {
6724
+ defaultOpen: defaultOpen ?? !settled,
6725
+ title: title,
6726
+ header: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
6727
+ children: [
6728
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
6078
6729
  align: "center",
6079
- gap: 1,
6730
+ gap: 2,
6731
+ paddingLeft: 2,
6732
+ style: {
6733
+ minWidth: 0
6734
+ },
6080
6735
  children: [
6081
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
6082
- flex: 1,
6083
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
6084
- "aria-label": open ? `Collapse ${title}` : `Expand ${title}`,
6085
- mode: "bleed",
6086
- onClick: () => setOpen(v => !v),
6087
- paddingX: 2,
6088
- paddingY: 3,
6089
- radius: 3,
6090
- width: "fill",
6091
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
6092
- align: "center",
6093
- gap: 2,
6094
- children: [
6095
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
6096
- size: 1,
6097
- children: open ? /* @__PURE__ */ jsxRuntime.jsx(ChevronDown.ChevronDownIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(ChevronRight.ChevronRightIcon, {})
6098
- }),
6099
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
6100
- size: 1,
6101
- weight: "medium",
6102
- children: title
6103
- }), status ]
6104
- })
6105
- })
6736
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
6737
+ size: 1,
6738
+ style: {
6739
+ minWidth: 0
6740
+ },
6741
+ textOverflow: "ellipsis",
6742
+ weight: "medium",
6743
+ children: title
6106
6744
  }),
6107
- /* @__PURE__ */ jsxRuntime.jsx(InstanceDebugMenu, {
6108
- entry: entry
6745
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
6746
+ align: "center",
6747
+ gap: 2,
6748
+ style: badgeTrim,
6749
+ children: [
6750
+ /* @__PURE__ */ jsxRuntime.jsx(AbortedBadge, {
6751
+ instance: instance
6752
+ }), settled ? null : /* @__PURE__ */ jsxRuntime.jsx(OlderDefinitionBadge, {
6753
+ instance: instance
6754
+ }) ]
6109
6755
  }) ]
6110
- })
6111
- }), open ?
6112
- /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
6756
+ }),
6757
+ /* @__PURE__ */ jsxRuntime.jsx(TrailingHairline, {}),
6758
+ /* @__PURE__ */ jsxRuntime.jsx(InstanceDebugMenu, {
6759
+ entry: entry
6760
+ }) ]
6761
+ }),
6762
+ sticky: !0,
6763
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
6113
6764
  gap: 3,
6114
6765
  paddingBottom: 2,
6115
6766
  paddingLeft: 5,
6116
6767
  children: children
6117
- }) : null ]
6768
+ })
6118
6769
  });
6119
6770
  }
6120
6771
 
6121
6772
  function PartOfBand({instance: instance}) {
6122
- const parentRef = instance.ancestors.at(-1), parentId = parentRef ? workflowEngine.extractDocumentId(parentRef.id) : null, parentEntry = useWorkflowInstanceEntry(parentId), parentDefinition = useDefinition(parentEntry);
6773
+ const parentRef = instance.ancestors.at(-1), parentId = parentRef ? workflowEngine.toBareId(parentRef.id) : null, parentEntry = useWorkflowInstanceEntry(parentId), parentDefinition = useDefinition(parentEntry);
6123
6774
  if (!parentId) return null;
6124
6775
  const title = parentEntry ? instanceTitle(parentEntry.instance, parentDefinition) : parentId;
6125
6776
  /* @__PURE__ */
@@ -6370,18 +7021,6 @@ const NODE_ICON = {
6370
7021
  done: CheckmarkCircle.CheckmarkCircleIcon
6371
7022
  };
6372
7023
 
6373
- function Hairline({style: style}) {
6374
- const {color: color} = ui.useTheme_v2();
6375
- /* @__PURE__ */
6376
- return jsxRuntime.jsx(ui.Card, {
6377
- style: {
6378
- height: "1px",
6379
- background: color.border,
6380
- ...style
6381
- }
6382
- });
6383
- }
6384
-
6385
7024
  function WorkflowSpine({definition: definition, evaluation: evaluation, instance: instance}) {
6386
7025
  const nodes = deriveSpine({
6387
7026
  definition: definition,
@@ -6400,7 +7039,8 @@ function WorkflowSpine({definition: definition, evaluation: evaluation, instance
6400
7039
  style: {
6401
7040
  transform: "translateY(-0.5px)",
6402
7041
  width: connectorWidth
6403
- }
7042
+ },
7043
+ weight: 1
6404
7044
  }) : null,
6405
7045
  /* @__PURE__ */ jsxRuntime.jsx(SpineNodeView, {
6406
7046
  definition: definition,
@@ -6505,7 +7145,9 @@ function StageOverviewCard({overview: overview}) {
6505
7145
  })
6506
7146
  }), hasLiveZone ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
6507
7147
  children: [
6508
- /* @__PURE__ */ jsxRuntime.jsx(Hairline, {}),
7148
+ /* @__PURE__ */ jsxRuntime.jsx(Hairline, {
7149
+ weight: 1
7150
+ }),
6509
7151
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
6510
7152
  padding: 2,
6511
7153
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
@@ -6558,31 +7200,32 @@ function AdvanceCard({advance: advance}) {
6558
7200
  }
6559
7201
 
6560
7202
  function StampLines({stamp: stamp}) {
6561
- const verb = stamp.kind === "entered" ? "Entered" : "Completed", ago = formatTimeAgo(stamp.at), absolute = formatShortDateTime(stamp.at);
6562
- return ago === "" ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
7203
+ const face = stampFace(stamp.kind === "entered" ? "Entered" : "Completed", stamp.at);
7204
+ return face.detail === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
6563
7205
  muted: !0,
6564
7206
  size: 1,
6565
- children: [ verb, " ", absolute ]
7207
+ children: face.lead
6566
7208
  }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
6567
7209
  gap: 3,
6568
7210
  children: [
6569
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
7211
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
6570
7212
  muted: !0,
6571
7213
  size: 1,
6572
- children: [ verb, " ", ago ]
7214
+ children: face.lead
6573
7215
  }),
6574
7216
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
6575
7217
  muted: !0,
6576
7218
  size: 1,
6577
- children: absolute
7219
+ children: face.detail
6578
7220
  }) ]
6579
7221
  });
6580
7222
  }
6581
7223
 
6582
- function WorkflowInstanceSection({entry: entry, onOpenActivity: onOpenActivity}) {
7224
+ function WorkflowInstanceSection({defaultOpen: defaultOpen, entry: entry, onOpenActivity: onOpenActivity}) {
6583
7225
  const definition = useDefinition(entry), {instance: instance} = entry;
6584
7226
  /* @__PURE__ */
6585
7227
  return jsxRuntime.jsxs(InstanceAccordion, {
7228
+ defaultOpen: defaultOpen,
6586
7229
  entry: entry,
6587
7230
  children: [ instance.ancestors.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(PartOfBand, {
6588
7231
  instance: instance
@@ -6642,7 +7285,7 @@ function StageSection({entry: entry, onOpenActivity: onOpenActivity}) {
6642
7285
  });
6643
7286
  }
6644
7287
 
6645
- const WorkflowsView = props => {
7288
+ const VIEW_TAB_CODEC = workflowsTabCodec([ "overview", "for-me" ]), WorkflowsView = props => {
6646
7289
  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);
6647
7290
  if (entries.length === 0) return discoveryInvalid !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
6648
7291
  padding: 4,
@@ -6684,11 +7327,9 @@ const WorkflowsView = props => {
6684
7327
  };
6685
7328
 
6686
7329
  function DiscoverySpinner() {
6687
- const [visible, setVisible] = react.useState(!1);
6688
- return react.useEffect(() => {
6689
- const timer = setTimeout(() => setVisible(!0), 1e3);
6690
- return () => clearTimeout(timer);
6691
- }, []), /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
7330
+ const visible = useDelayedFlag(!0, 1e3);
7331
+ /* @__PURE__ */
7332
+ return jsxRuntime.jsx(ui.Flex, {
6692
7333
  align: "center",
6693
7334
  height: "fill",
6694
7335
  justify: "center",
@@ -6698,19 +7339,13 @@ function DiscoverySpinner() {
6698
7339
  });
6699
7340
  }
6700
7341
 
6701
- const GROUP_GAP = 1;
7342
+ const BOOT_CUE_DELAY_MS = 400, GROUP_GAP = 1;
6702
7343
 
6703
7344
  function useWorkflowsTab() {
6704
7345
  const {params: params, setParams: setParams} = structure.usePaneRouter();
6705
7346
  return {
6706
- view: params?.[WORKFLOWS_TAB_PARAM] === "for-me" ? "for-me" : "overview",
6707
- setView: next => {
6708
- const {[WORKFLOWS_TAB_PARAM]: _tab, ...rest} = params ?? {};
6709
- setParams(next === "overview" ? rest : {
6710
- ...rest,
6711
- [WORKFLOWS_TAB_PARAM]: next
6712
- });
6713
- }
7347
+ view: VIEW_TAB_CODEC.read(params),
7348
+ setView: next => setParams(VIEW_TAB_CODEC.write(params, next))
6714
7349
  };
6715
7350
  }
6716
7351
 
@@ -6729,7 +7364,8 @@ function useOpenActivity(entries) {
6729
7364
  }
6730
7365
 
6731
7366
  function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappings: docTypeMappings, initialValue: initialValue}) {
6732
- const identity = useAssignmentIdentity(), panelId = react.useId(), {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), assignedWorkUnknown = active.some(e => e.invalid !== void 0), sectionFor = e => /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceSection, {
7367
+ const identity = useAssignmentIdentity(), panelId = react.useId(), {params: params} = structure.usePaneRouter(), {view: view, setView: setView} = useWorkflowsTab(), {openActivity: openActivity, setOpenActivity: setOpenActivity, dialogEntry: dialogEntry} = useOpenActivity(entries), active = entries.filter(isLiveEntry), finished = entries.filter(e => !isLiveEntry(e)), forMe = forMeWorkOf(active, identity), [landing] = react.useState(() => focusedLanding(params?.[WORKFLOWS_FOCUS_PARAM], entries.map(e => e.instance._id))), assignedWorkUnknown = active.some(e => e.invalid !== void 0), booting = useDelayedFlag(active.some(e => !e.ready && e.invalid === void 0), BOOT_CUE_DELAY_MS), sectionFor = e => /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceSection, {
7368
+ defaultOpen: landingDefaultOpen(landing, e.instance._id),
6733
7369
  entry: e,
6734
7370
  onOpenActivity: activityName => setOpenActivity({
6735
7371
  instanceId: e.instance._id,
@@ -6749,6 +7385,7 @@ function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappin
6749
7385
  padding: 3,
6750
7386
  children: [
6751
7387
  /* @__PURE__ */ jsxRuntime.jsx(GroupHeading, {
7388
+ busy: booting,
6752
7389
  count: active.length,
6753
7390
  end:
6754
7391
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
@@ -6884,10 +7521,8 @@ function workflowsView(S) {
6884
7521
  return S.view.component(WorkflowsView).id(WORKFLOWS_VIEW_ID).title("Workflows");
6885
7522
  }
6886
7523
 
6887
- function workflowDefaultDocumentNode(options) {
6888
- return (S, ctx) => {
6889
- if (mappingForDocType(options.mappings, ctx.schemaType)) return S.document().views([ S.view.form(), workflowsView(S) ]);
6890
- };
7524
+ function workflowDefaultDocumentNode() {
7525
+ return S => S.document().views([ S.view.form(), workflowsView(S) ]);
6891
7526
  }
6892
7527
 
6893
7528
  function lockTitleFor(args) {
@@ -6900,26 +7535,12 @@ function lockTitleFor(args) {
6900
7535
  return workflows.length === 1 ? `The ${workflowList} workflow is holding this document.` : `The ${workflowList} workflows are holding this document.`;
6901
7536
  }
6902
7537
 
6903
- const EMPTY = {
6904
- byType: /* @__PURE__ */ new Map,
6905
- definitions: /* @__PURE__ */ new Map,
6906
- warnings: []
6907
- };
6908
-
6909
7538
  function resolveAutoStart(args) {
6910
- const {autoStart: autoStart, context: context, knownDocTypes: knownDocTypes, definitions: definitions} = args;
6911
- if (autoStart === void 0) return EMPTY;
6912
- const evaluated = evaluateMap(autoStart, context);
6913
- if ("warning" in evaluated) return {
6914
- byType: /* @__PURE__ */ new Map,
6915
- definitions: /* @__PURE__ */ new Map,
6916
- warnings: [ evaluated.warning ]
6917
- };
6918
- const map = evaluated.map, latestByName = new Map(workflowEngine.latestDeployedDefinitions(definitions).map(d => [ d.name, d ])), byType = /* @__PURE__ */ new Map, resolvedDefinitions = /* @__PURE__ */ new Map, warnings = [];
6919
- for (const [docType, spec] of Object.entries(map)) {
7539
+ const {autoStart: autoStart, knownDocTypes: knownDocTypes, definitions: definitions} = args, latestByName = new Map(workflowEngine.latestDeployedDefinitions(definitions).map(d => [ d.name, d ])), byType = /* @__PURE__ */ new Map, resolvedDefinitions = /* @__PURE__ */ new Map, warnings = [];
7540
+ for (const [docType, names] of Object.entries(autoStart)) {
6920
7541
  const entry = resolveEntry({
6921
7542
  docType: docType,
6922
- spec: spec,
7543
+ names: names,
6923
7544
  knownDocTypes: knownDocTypes,
6924
7545
  latestByName: latestByName
6925
7546
  });
@@ -6935,35 +7556,15 @@ function resolveAutoStart(args) {
6935
7556
  };
6936
7557
  }
6937
7558
 
6938
- function evaluateMap(autoStart, context) {
6939
- let map;
6940
- try {
6941
- map = typeof autoStart == "function" ? autoStart(context) : autoStart;
6942
- } catch (err) {
6943
- return {
6944
- warning: `autoStart: config function threw — ignoring it (${describeError(err)}).`
6945
- };
6946
- }
6947
- return typeof map != "object" || map === null ? {
6948
- warning: "autoStart: config did not resolve to an object — ignoring it."
6949
- } : {
6950
- map: map
6951
- };
6952
- }
6953
-
6954
- function specNames(spec) {
6955
- return typeof spec == "string" ? [ spec ] : Array.isArray(spec) ? spec : [];
6956
- }
6957
-
6958
7559
  function resolveEntry(args) {
6959
- const {docType: docType, spec: spec, knownDocTypes: knownDocTypes, latestByName: latestByName} = args;
7560
+ const {docType: docType, names: names, knownDocTypes: knownDocTypes, latestByName: latestByName} = args;
6960
7561
  if (!knownDocTypes.has(docType)) return {
6961
7562
  valid: [],
6962
7563
  definitions: [],
6963
7564
  warnings: [ `autoStart: no schema type "${docType}" — entry skipped.` ]
6964
7565
  };
6965
7566
  const valid = [], definitions = [], warnings = [];
6966
- for (const name of specNames(spec)) {
7567
+ for (const name of names) {
6967
7568
  const definition = latestByName.get(name), issue = workflowIssue({
6968
7569
  name: name,
6969
7570
  docType: docType,
@@ -6989,11 +7590,9 @@ function workflowIssue(args) {
6989
7590
  const {name: name, docType: docType, definition: definition} = args;
6990
7591
  if (definition === void 0) return `autoStart: "${docType}" → workflow "${name}" is not deployed — skipped.`;
6991
7592
  if (!workflowEngine.isStartableDefinition(definition)) return `autoStart: "${docType}" → workflow "${name}" is spawn-only, not startable — skipped.`;
6992
- if (!workflowEngine.acceptsDocumentType({
6993
- fields: [ ...definition.fields ?? [] ]
6994
- }, docType)) return `autoStart: "${docType}" → workflow "${name}" has no subject accepting "${docType}" — skipped.`;
6995
- const subject = (definition.fields ?? []).find(workflowEngine.isSubjectEntry);
6996
- if (subject !== void 0 && !workflowEngine.isInputSourced(subject)) return `autoStart: "${docType}" → workflow "${name}" has a self-filling subject (not caller-provided), so the document can't be its subject — skipped.`;
7593
+ const subject = documentSubjectEntry(definition), acceptsType = subject?.types === void 0 || subject.types.includes(docType);
7594
+ if (subject === void 0 || !acceptsType) return `autoStart: "${docType}" → workflow "${name}" has no document subject accepting "${docType}" — skipped.`;
7595
+ if (!workflowEngine.isInputSourced(subject)) return `autoStart: "${docType}" → workflow "${name}" has a self-filling subject (not caller-provided), so the document can't be its subject — skipped.`;
6997
7596
  }
6998
7597
 
6999
7598
  function contentDocumentTypes(schema) {
@@ -7013,20 +7612,31 @@ const EMPTY_SNAPSHOT = {
7013
7612
  };
7014
7613
 
7015
7614
  function useDiscoveryState(args) {
7016
- const {registered: registered, requested: requested, contentResource: contentResource} = args, watching = registered.length + requested.length > 0, [latest, setLatest] = react.useState(EMPTY_SNAPSHOT), [resolved, setResolved] = react.useState(EMPTY_SNAPSHOT), onSnapshot = react.useCallback(snapshot => {
7017
- setLatest(snapshot), snapshot.instances !== void 0 && setResolved(snapshot);
7018
- }, []);
7019
- react.useEffect(() => {
7020
- watching || (setLatest(EMPTY_SNAPSHOT), setResolved(EMPTY_SNAPSHOT));
7021
- }, [ watching ]);
7022
- const filter = react.useMemo(() => ({
7023
- documents: registered.map(id => workflowEngine.gdrFromResource(contentResource, id)),
7615
+ const {observed: observed, requested: requested, contentResource: contentResource} = args, documentWatching = observed.length > 0, requestedWatching = requested.length > 0, documents = useDiscoveryStream(documentWatching), explicit = useDiscoveryStream(requestedWatching), documentFilter = react.useMemo(() => documentWatching ? {
7616
+ documents: observed.map(id => workflowEngine.gdrFromResource(contentResource, id)),
7617
+ includeCompleted: !0
7618
+ } : void 0, [ documentWatching, observed, contentResource ]), requestedFilter = react.useMemo(() => requestedWatching ? {
7024
7619
  ids: requested,
7025
7620
  includeCompleted: !0
7026
- }), [ registered, requested, contentResource ]);
7621
+ } : void 0, [ requestedWatching, requested ]), resolved = react.useMemo(() => mergeResolved(documents.resolved, explicit.resolved), [ documents.resolved, explicit.resolved ]);
7027
7622
  return {
7028
- watching: watching,
7029
- filter: filter,
7623
+ documentFilter: documentFilter,
7624
+ requestedFilter: requestedFilter,
7625
+ loading: documents.loading || explicit.loading,
7626
+ invalid: documents.invalid ?? explicit.invalid,
7627
+ resolved: resolved,
7628
+ onDocumentSnapshot: documents.onSnapshot,
7629
+ onRequestedSnapshot: explicit.onSnapshot
7630
+ };
7631
+ }
7632
+
7633
+ function useDiscoveryStream(watching) {
7634
+ const [latest, setLatest] = react.useState(EMPTY_SNAPSHOT), [resolved, setResolved] = react.useState(EMPTY_SNAPSHOT), onSnapshot = react.useCallback(snapshot => {
7635
+ setLatest(snapshot), snapshot.instances !== void 0 && setResolved(snapshot);
7636
+ }, []);
7637
+ return react.useEffect(() => {
7638
+ watching || (setLatest(EMPTY_SNAPSHOT), setResolved(EMPTY_SNAPSHOT));
7639
+ }, [ watching ]), {
7030
7640
  loading: watching && latest.instances === void 0 && latest.invalid === void 0,
7031
7641
  invalid: watching ? latest.invalid : void 0,
7032
7642
  resolved: resolved,
@@ -7034,32 +7644,107 @@ function useDiscoveryState(args) {
7034
7644
  };
7035
7645
  }
7036
7646
 
7037
- function indexDiscoveredInstances(args) {
7038
- const {instances: instances, registered: registered, contentResource: contentResource} = args, ordered = [ ...instances ].sort((a, b) => b.startedAt.localeCompare(a.startedAt)), index = /* @__PURE__ */ new Map;
7039
- for (const bareId of registered) {
7040
- const uri = workflowEngine.gdrFromResource(contentResource, bareId), watching = ordered.filter(inst => workflowEngine.instanceWatchesDocument(inst, uri));
7041
- watching.length > 0 && index.set(bareId, watching.map(inst => inst._id));
7647
+ function mergeResolved(documents, explicit) {
7648
+ const rows = [ ...documents.instances ?? [], ...explicit.instances ?? [] ], freshest = /* @__PURE__ */ new Map;
7649
+ for (const instance of rows) {
7650
+ const current = freshest.get(instance._id);
7651
+ (current === void 0 || Date.parse(instance.lastChangedAt) > Date.parse(current.lastChangedAt)) && freshest.set(instance._id, instance);
7042
7652
  }
7653
+ return {
7654
+ instances: documents.instances === void 0 && explicit.instances === void 0 ? void 0 : [ ...freshest.values() ],
7655
+ invalid: documents.invalid ?? explicit.invalid,
7656
+ registered: documents.registered
7657
+ };
7658
+ }
7659
+
7660
+ function createDiscoveryIndexer(deriveWatchSet = workflowEngine.subscriptionDocumentsForInstance) {
7661
+ const cached = /* @__PURE__ */ new Map;
7662
+ return {
7663
+ index(args) {
7664
+ const liveIds = new Set(args.instances.map(instance => instance._id));
7665
+ for (const id of cached.keys()) liveIds.has(id) || cached.delete(id);
7666
+ const watchedById = /* @__PURE__ */ new Map;
7667
+ for (const instance of args.instances) {
7668
+ let row = cached.get(instance._id);
7669
+ row?.source !== instance && (row = {
7670
+ source: instance,
7671
+ globalDocumentIds: [ ...new Set(deriveWatchSet(instance).documents.map(document => document.globalDocumentId)) ]
7672
+ }, cached.set(instance._id, row)), watchedById.set(instance._id, row.globalDocumentIds);
7673
+ }
7674
+ return buildDiscoveryIndex(args, watchedById);
7675
+ }
7676
+ };
7677
+ }
7678
+
7679
+ function buildDiscoveryIndex(args, watchedById) {
7680
+ const {instances: instances, registered: registered, contentResource: contentResource} = args, ordered = [ ...instances ].sort((a, b) => b.startedAt.localeCompare(a.startedAt)), index = /* @__PURE__ */ new Map, registeredByUri = new Map(registered.map(bareId => [ workflowEngine.gdrFromResource(contentResource, bareId), bareId ]));
7681
+ for (const instance of ordered) appendInstanceWatches({
7682
+ index: index,
7683
+ registeredByUri: registeredByUri,
7684
+ instanceId: instance._id,
7685
+ watched: watchedById.get(instance._id) ?? []
7686
+ });
7043
7687
  return {
7044
7688
  instances: new Map(ordered.map(inst => [ inst._id, inst ])),
7045
7689
  index: index
7046
7690
  };
7047
7691
  }
7048
7692
 
7049
- function mergeEntries(args) {
7050
- const map = /* @__PURE__ */ new Map, merged = new Map([ ...args.seeded, ...args.discovered ]);
7051
- for (const [instanceId, inst] of merged) {
7052
- const report = args.reports.get(instanceId);
7053
- map.set(instanceId, {
7054
- instance: report?.evaluation?.instance ?? inst,
7055
- committed: inst,
7056
- evaluation: report?.evaluation,
7057
- ready: report?.ready ?? !1,
7058
- invalid: report?.invalid,
7059
- guards: report?.guards
7060
- });
7693
+ function appendInstanceWatches(args) {
7694
+ const {index: index, registeredByUri: registeredByUri, instanceId: instanceId, watched: watched} = args;
7695
+ for (const uri of watched) {
7696
+ const bareId = registeredByUri.get(uri);
7697
+ if (bareId === void 0) continue;
7698
+ const ids = index.get(bareId);
7699
+ ids === void 0 ? index.set(bareId, [ instanceId ]) : ids.push(instanceId);
7700
+ }
7701
+ }
7702
+
7703
+ function sameInstance(a, b) {
7704
+ return a === b || a._id === b._id && a._rev === b._rev;
7705
+ }
7706
+
7707
+ function sameEntry(a, b) {
7708
+ return sameInstance(a.instance, b.instance) && sameInstance(a.committed, b.committed) && a.evaluation === b.evaluation && a.ready === b.ready && a.invalid === b.invalid && a.guards === b.guards;
7709
+ }
7710
+
7711
+ function reconciled(args) {
7712
+ const {entries: entries, previous: previous, same: same} = args, map = /* @__PURE__ */ new Map;
7713
+ let allReused = !0;
7714
+ for (const [key, next] of entries) {
7715
+ const prev = previous?.get(key), reusable = prev !== void 0 && same(prev, next);
7716
+ reusable || (allReused = !1), map.set(key, reusable ? prev : next);
7061
7717
  }
7062
- return map;
7718
+ return previous !== void 0 && allReused && previous.size === map.size ? previous : map;
7719
+ }
7720
+
7721
+ function entryOf(inst, report) {
7722
+ return {
7723
+ instance: report?.evaluation?.instance ?? inst,
7724
+ committed: inst,
7725
+ evaluation: report?.evaluation,
7726
+ ready: report?.ready ?? workflowEngine.terminalState(inst) !== "in-flight",
7727
+ invalid: report?.invalid,
7728
+ guards: report?.guards
7729
+ };
7730
+ }
7731
+
7732
+ function mergeEntries(args) {
7733
+ const {seeded: seeded, discovered: discovered, reports: reports, previous: previous} = args, merged = new Map([ ...seeded, ...discovered ]);
7734
+ return reconciled({
7735
+ entries: [ ...merged ].map(([instanceId, inst]) => [ instanceId, entryOf(inst, reports.get(instanceId)) ]),
7736
+ previous: previous,
7737
+ same: sameEntry
7738
+ });
7739
+ }
7740
+
7741
+ function deriveByDocument(args) {
7742
+ const {docIndex: docIndex, byInstance: byInstance, previous: previous} = args;
7743
+ return reconciled({
7744
+ entries: [ ...docIndex ].map(([docId, instanceIds]) => [ docId, instanceIds.map(id => byInstance.get(id)).filter(entry => entry !== void 0) ]),
7745
+ previous: previous,
7746
+ same: (a, b) => a.length === b.length && b.every((entry, i) => entry === a[i])
7747
+ });
7063
7748
  }
7064
7749
 
7065
7750
  const REPORT_KEYS = [ "evaluation", "ready", "invalid", "guards", "fireAction", "editField", "previewField", "discardFieldPreview" ];
@@ -7080,7 +7765,70 @@ function nextReports(prev, update) {
7080
7765
  return next.set(instanceId, report), next;
7081
7766
  }
7082
7767
 
7083
- const notReady = (..._args) => Promise.reject(new Error("workflow session not ready yet"));
7768
+ const REPORT_FLUSH_MS = 50, EMPTY_COMMITTED = {
7769
+ seeded: /* @__PURE__ */ new Map,
7770
+ discovered: /* @__PURE__ */ new Map,
7771
+ docIndex: /* @__PURE__ */ new Map
7772
+ };
7773
+
7774
+ function createEntriesStore() {
7775
+ let committed = EMPTY_COMMITTED, reports = /* @__PURE__ */ new Map, byInstance = /* @__PURE__ */ new Map, byDocument = /* @__PURE__ */ new Map;
7776
+ const listeners = /* @__PURE__ */ new Set;
7777
+ let flushTimer;
7778
+ const recompute = () => {
7779
+ flushTimer !== void 0 && (clearTimeout(flushTimer), flushTimer = void 0);
7780
+ const nextByInstance = mergeEntries({
7781
+ ...committed,
7782
+ reports: reports,
7783
+ previous: byInstance
7784
+ }), nextByDocument = deriveByDocument({
7785
+ docIndex: committed.docIndex,
7786
+ byInstance: nextByInstance,
7787
+ previous: byDocument
7788
+ });
7789
+ if (!(nextByInstance === byInstance && nextByDocument === byDocument)) {
7790
+ byInstance = nextByInstance, byDocument = nextByDocument;
7791
+ for (const listener of [ ...listeners ]) listener();
7792
+ }
7793
+ };
7794
+ return {
7795
+ setCommitted(inputs) {
7796
+ committed = inputs, recompute();
7797
+ },
7798
+ report(instanceId, report) {
7799
+ const next = nextReports(reports, {
7800
+ instanceId: instanceId,
7801
+ report: report
7802
+ });
7803
+ next !== reports && (reports = next, flushTimer ??= setTimeout(recompute, REPORT_FLUSH_MS));
7804
+ },
7805
+ getReport: instanceId => reports.get(instanceId),
7806
+ getInstance: instanceId => byInstance.get(instanceId),
7807
+ getDocument: docId => byDocument.get(docId) ?? EMPTY_ENTRIES,
7808
+ subscribe(listener) {
7809
+ return listeners.add(listener), () => {
7810
+ listeners.delete(listener);
7811
+ };
7812
+ },
7813
+ dispose() {
7814
+ flushTimer !== void 0 && clearTimeout(flushTimer), flushTimer = void 0, listeners.clear();
7815
+ }
7816
+ };
7817
+ }
7818
+
7819
+ function sessionPlan(args) {
7820
+ const demandedIds = new Set(args.demanded), evaluated = [ ...args.discovered.values() ].filter(inst => demandedIds.has(inst._id));
7821
+ return {
7822
+ evaluated: evaluated,
7823
+ guardScope: evaluated.map(inst => inst._id).sort()
7824
+ };
7825
+ }
7826
+
7827
+ function useDefaultLayout(props) {
7828
+ return react.useMemo(() => props.renderDefault(props), [ props ]);
7829
+ }
7830
+
7831
+ const notReady = (..._args) => Promise.reject(new Error("workflow session not ready yet")), NO_REGISTERED_DOCUMENTS = [], DEMAND_RELEASE_GRACE_MS = 500;
7084
7832
 
7085
7833
  function workspaceParts(config) {
7086
7834
  if (!config.projectId || !config.dataset) throw new Error("workflow plugin requires a workspace with projectId and dataset");
@@ -7108,10 +7856,11 @@ class InstanceEvaluatorBoundary extends react.Component {
7108
7856
  }
7109
7857
 
7110
7858
  function InstanceEvaluator(props) {
7111
- const {engine: engine, instanceId: instanceId, grantsFromPath: grantsFromPath, onReport: onReport} = props, {evaluation: evaluation, ready: ready, invalid: invalid, guards: guards, fireAction: fireAction, editField: editField, previewField: previewField, discardFieldPreview: discardFieldPreview} = workflowStudio.useWorkflowSession({
7859
+ const {engine: engine, instanceId: instanceId, grantsFromPath: grantsFromPath, guardScope: guardScope, onReport: onReport} = props, {evaluation: evaluation, ready: ready, invalid: invalid, guards: guards, fireAction: fireAction, editField: editField, previewField: previewField, discardFieldPreview: discardFieldPreview} = workflowStudio.useWorkflowSession({
7112
7860
  engine: engine,
7113
7861
  instanceId: instanceId,
7114
- grantsFromPath: grantsFromPath
7862
+ grantsFromPath: grantsFromPath,
7863
+ guardScope: guardScope
7115
7864
  });
7116
7865
  return react.useEffect(() => {
7117
7866
  onReport(instanceId, {
@@ -7130,10 +7879,10 @@ function InstanceEvaluator(props) {
7130
7879
  }
7131
7880
 
7132
7881
  function DiscoverySubscription(props) {
7133
- const {engine: engine, registered: registered, filter: filter, onSnapshot: onSnapshot} = props, {instances: instances, invalid: invalid} = workflowStudio.useWorkflowInstances({
7882
+ const {engine: engine, registered: registered, filter: filter, onSnapshot: onSnapshot} = props, {instances: instances, unreadable: unreadable} = workflowStudio.useWorkflowInstances({
7134
7883
  engine: engine,
7135
7884
  filter: filter
7136
- });
7885
+ }), invalid = unreadable[0];
7137
7886
  return react.useEffect(() => {
7138
7887
  onSnapshot({
7139
7888
  instances: instances,
@@ -7155,9 +7904,9 @@ function engineOptionsFrom(config) {
7155
7904
  }
7156
7905
 
7157
7906
  function WorkflowProvider(props) {
7158
- const {config: config} = props, studioClient = sanity.useClient({
7907
+ const {config: config} = props, defaultLayout = useDefaultLayout(props), studioClient = sanity.useClient({
7159
7908
  apiVersion: WORKFLOW_API_VERSION
7160
- }), schema = sanity.useSchema(), {name: workspaceName} = sanity.useWorkspace(), {projectId: projectId, dataset: contentDataset} = workspaceParts(studioClient.config()), workflowDataset = config.workflowDataset ?? contentDataset, binding = react.useMemo(() => ({
7909
+ }), schema = sanity.useSchema(), {projectId: projectId, dataset: contentDataset} = workspaceParts(studioClient.config()), workflowDataset = config.workflowDataset ?? contentDataset, binding = react.useMemo(() => ({
7161
7910
  engineResource: {
7162
7911
  type: "dataset",
7163
7912
  id: `${projectId}.${workflowDataset}`
@@ -7175,16 +7924,21 @@ function WorkflowProvider(props) {
7175
7924
  }), grantsFromPath = workflowEngine.aclPathForResource({
7176
7925
  type: "dataset",
7177
7926
  id: `${projectId}.${workflowDataset}`
7178
- }), {ids: registered, acquire: register, release: unregister} = workflowReact.useRefcountedIds(), {ids: requested, acquire: requestInstance, release: releaseInstance} = workflowReact.useRefcountedIds(), [seeded, setSeeded] = react.useState(/* @__PURE__ */ new Map), seedInstance = react.useCallback(instance => {
7927
+ }), {ids: registered, acquire: registerActive, release: unregisterActive} = workflowReact.useRefcountedIds(), {ids: observed, acquire: retainRegistration, release: releaseRegistration} = workflowReact.useRefcountedIds({
7928
+ releaseDelayMs: DEMAND_RELEASE_GRACE_MS
7929
+ }), register = react.useCallback(id => {
7930
+ registerActive(id), retainRegistration(id);
7931
+ }, [ registerActive, retainRegistration ]), unregister = react.useCallback(id => {
7932
+ unregisterActive(id), releaseRegistration(id);
7933
+ }, [ unregisterActive, releaseRegistration ]), {ids: requested, acquire: requestInstance, release: releaseInstance} = workflowReact.useRefcountedIds(), {ids: evaluationDemand, acquire: requestEvaluation, release: releaseEvaluation} = workflowReact.useRefcountedIds({
7934
+ releaseDelayMs: DEMAND_RELEASE_GRACE_MS
7935
+ }), [seeded, setSeeded] = react.useState(/* @__PURE__ */ new Map), seedInstance = react.useCallback(instance => {
7179
7936
  setSeeded(prev => new Map(prev).set(instance._id, instance));
7180
- }, []), [startRequest2, setStartRequest] = react.useState(void 0), openStartDialog = react.useCallback(r => setStartRequest(r), []), closeStartDialog = react.useCallback(() => setStartRequest(void 0), []), [reports, setReports] = react.useState(/* @__PURE__ */ new Map), onReport = react.useCallback((instanceId, report) => {
7181
- setReports(prev => nextReports(prev, {
7182
- instanceId: instanceId,
7183
- report: report
7184
- }));
7185
- }, []), reportsRef = react.useRef(reports);
7186
- reportsRef.current = reports;
7187
- const drainRef = react.useRef(void 0), drainCommitted = react.useCallback(async instanceId => {
7937
+ }, []), [startRequest2, setStartRequest] = react.useState(void 0), openStartDialog = react.useCallback(r => setStartRequest(r), []), closeStartDialog = react.useCallback(() => setStartRequest(void 0), []), [entriesStore] = react.useState(createEntriesStore);
7938
+ react.useEffect(() => () => entriesStore.dispose(), [ entriesStore ]);
7939
+ const onReport = react.useCallback((instanceId, report) => {
7940
+ entriesStore.report(instanceId, report);
7941
+ }, [ entriesStore ]), drainRef = react.useRef(void 0), drainCommitted = react.useCallback(async instanceId => {
7188
7942
  try {
7189
7943
  await (drainRef.current?.(instanceId));
7190
7944
  } catch (err) {
@@ -7193,7 +7947,7 @@ function WorkflowProvider(props) {
7193
7947
  });
7194
7948
  }
7195
7949
  }, []), fireActionFor = react.useCallback((instanceId, args) => {
7196
- const report = reportsRef.current.get(instanceId);
7950
+ const report = entriesStore.getReport(instanceId);
7197
7951
  return report ? report.fireAction({
7198
7952
  activity: args.activity,
7199
7953
  action: args.action,
@@ -7201,15 +7955,15 @@ function WorkflowProvider(props) {
7201
7955
  params: args.params
7202
7956
  } : {}
7203
7957
  }).then(async res => (await drainCommitted(instanceId), res)) : notReady(args);
7204
- }, [ drainCommitted ]), editFieldFor = react.useCallback((instanceId, args) => {
7205
- const report = reportsRef.current.get(instanceId);
7958
+ }, [ entriesStore, drainCommitted ]), editFieldFor = react.useCallback((instanceId, args) => {
7959
+ const report = entriesStore.getReport(instanceId);
7206
7960
  return report ? report.editField(args).then(async res => (await drainCommitted(instanceId),
7207
7961
  res)) : notReady(args);
7208
- }, [ drainCommitted ]), previewFieldFor = react.useCallback((instanceId, args) => {
7209
- reportsRef.current.get(instanceId)?.previewField(args);
7210
- }, []), discardFieldPreviewFor = react.useCallback((instanceId, target) => {
7211
- reportsRef.current.get(instanceId)?.discardFieldPreview(target);
7212
- }, []), drainEffectsFor = react.useCallback(async instanceId => {
7962
+ }, [ entriesStore, drainCommitted ]), previewFieldFor = react.useCallback((instanceId, args) => {
7963
+ entriesStore.getReport(instanceId)?.previewField(args);
7964
+ }, [ entriesStore ]), discardFieldPreviewFor = react.useCallback((instanceId, target) => {
7965
+ entriesStore.getReport(instanceId)?.discardFieldPreview(target);
7966
+ }, [ entriesStore ]), drainEffectsFor = react.useCallback(async instanceId => {
7213
7967
  await engine.drainEffects({
7214
7968
  instanceId: instanceId
7215
7969
  });
@@ -7229,53 +7983,46 @@ function WorkflowProvider(props) {
7229
7983
  });
7230
7984
  }, [ engine ]);
7231
7985
  drainRef.current = drainEffectsFor;
7232
- const {watching: watching, filter: filter, loading: loading, invalid: discoveryInvalid, resolved: resolved, onSnapshot: onSnapshot} = useDiscoveryState({
7233
- registered: registered,
7986
+ const {documentFilter: documentFilter, requestedFilter: requestedFilter, loading: loading, invalid: discoveryInvalid, resolved: resolved, onDocumentSnapshot: onDocumentSnapshot, onRequestedSnapshot: onRequestedSnapshot} = useDiscoveryState({
7987
+ observed: observed,
7234
7988
  requested: requested,
7235
7989
  contentResource: binding.contentResource
7236
- }), {instances: discovered, index: docIndex} = react.useMemo(() => indexDiscoveredInstances({
7990
+ }), discoveryIndexer = react.useMemo(createDiscoveryIndexer, []), {instances: discovered, index: docIndex} = react.useMemo(() => discoveryIndexer.index({
7237
7991
  instances: resolved.instances ?? [],
7238
7992
  registered: resolved.registered,
7239
7993
  contentResource: binding.contentResource
7240
- }), [ resolved, binding.contentResource ]), resolvedDocs = react.useMemo(() => new Set(resolved.registered), [ resolved.registered ]);
7994
+ }), [ discoveryIndexer, resolved, binding.contentResource ]), {evaluated: evaluated, guardScope: guardScope} = react.useMemo(() => sessionPlan({
7995
+ discovered: discovered,
7996
+ demanded: evaluationDemand
7997
+ }), [ discovered, evaluationDemand ]), resolvedDocs = react.useMemo(() => new Set(resolved.registered), [ resolved.registered ]);
7241
7998
  react.useEffect(() => {
7242
7999
  setSeeded(prev => {
7243
8000
  const kept = [ ...prev ].filter(([id]) => !discovered.has(id));
7244
8001
  return kept.length === prev.size ? prev : new Map(kept);
7245
8002
  });
7246
- }, [ discovered ]);
7247
- const byInstance = react.useMemo(() => mergeEntries({
7248
- seeded: seeded,
7249
- discovered: discovered,
7250
- reports: reports
7251
- }), [ discovered, seeded, reports ]), byDocument = react.useMemo(() => {
7252
- const map = /* @__PURE__ */ new Map;
7253
- for (const [bareDocId, instanceIds] of docIndex) {
7254
- const entries = [];
7255
- for (const instanceId of instanceIds) {
7256
- const entry = byInstance.get(instanceId);
7257
- entry && entries.push(entry);
7258
- }
7259
- map.set(bareDocId, entries);
7260
- }
7261
- return map;
7262
- }, [ byInstance, docIndex ]), isDocResolved = react.useCallback(docId => resolvedDocs.has(docId), [ resolvedDocs ]), schemaContentTypes = react.useMemo(() => new Set(contentDocumentTypes(schema)), [ schema ]), {issues: issues, starts: starts, fields: fields, latestVersions: latestVersions, autoStartByType: autoStartByType, autoStartDefinitions: autoStartDefinitions} = useDeployedDefinitions({
8003
+ }, [ discovered ]), react.useLayoutEffect(() => {
8004
+ entriesStore.setCommitted({
8005
+ seeded: seeded,
8006
+ discovered: discovered,
8007
+ docIndex: docIndex
8008
+ });
8009
+ }, [ entriesStore, seeded, discovered, docIndex ]);
8010
+ const isDocResolved = react.useCallback(docId => resolvedDocs.has(docId), [ resolvedDocs ]), schemaContentTypes = react.useMemo(() => new Set(contentDocumentTypes(schema)), [ schema ]), {mappings: mappings, issues: issues, starts: starts, fields: fields, latestVersions: latestVersions, autoStartByType: autoStartByType, autoStartDefinitions: autoStartDefinitions} = useDeployedDefinitions({
7263
8011
  engine: engine,
7264
- mappings: config.mappings,
8012
+ overrides: config.mappings,
7265
8013
  contentResource: binding.contentResource,
7266
8014
  schemaContentTypes: schemaContentTypes,
7267
8015
  startRequest: startRequest2,
7268
- autoStart: config.autoStart,
7269
- schema: schema,
7270
- workspaceName: workspaceName
8016
+ schema: schema
7271
8017
  }), value = react.useMemo(() => ({
7272
8018
  register: register,
7273
8019
  unregister: unregister,
7274
8020
  isDocResolved: isDocResolved,
7275
- byDocument: byDocument,
7276
- byInstance: byInstance,
8021
+ entries: entriesStore,
7277
8022
  requestInstance: requestInstance,
7278
8023
  releaseInstance: releaseInstance,
8024
+ requestEvaluation: requestEvaluation,
8025
+ releaseEvaluation: releaseEvaluation,
7279
8026
  seedInstance: seedInstance,
7280
8027
  openStartDialog: openStartDialog,
7281
8028
  closeStartDialog: closeStartDialog,
@@ -7290,7 +8037,7 @@ function WorkflowProvider(props) {
7290
8037
  completeEffectFor: completeEffectFor,
7291
8038
  engine: engine,
7292
8039
  binding: binding,
7293
- mappings: config.mappings,
8040
+ mappings: mappings,
7294
8041
  mappingIssues: issues,
7295
8042
  mappingStarts: starts,
7296
8043
  mappingFields: fields,
@@ -7298,24 +8045,30 @@ function WorkflowProvider(props) {
7298
8045
  effectHandlers: engine.effectHandlers,
7299
8046
  autoStartByType: autoStartByType,
7300
8047
  autoStartDefinitions: autoStartDefinitions
7301
- }), [ register, unregister, isDocResolved, byDocument, byInstance, requestInstance, releaseInstance, seedInstance, openStartDialog, closeStartDialog, startRequest2, loading, discoveryInvalid, fireActionFor, editFieldFor, previewFieldFor, discardFieldPreviewFor, drainEffectsFor, completeEffectFor, engine, binding, config.mappings, issues, starts, latestVersions, autoStartByType, autoStartDefinitions ]);
8048
+ }), [ register, unregister, isDocResolved, entriesStore, 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 ]);
7302
8049
  /* @__PURE__ */
7303
8050
  return jsxRuntime.jsxs(WorkflowContext.Provider, {
7304
8051
  value: value,
7305
- children: [ watching ? /* @__PURE__ */ jsxRuntime.jsx(DiscoverySubscription, {
8052
+ children: [ documentFilter ? /* @__PURE__ */ jsxRuntime.jsx(DiscoverySubscription, {
7306
8053
  engine: engine,
7307
- filter: filter,
7308
- onSnapshot: onSnapshot,
8054
+ filter: documentFilter,
8055
+ onSnapshot: onDocumentSnapshot,
7309
8056
  registered: registered
7310
- }) : null, [ ...discovered.values() ].map(inst => /* @__PURE__ */ jsxRuntime.jsx(InstanceEvaluatorBoundary, {
8057
+ }) : null, requestedFilter ? /* @__PURE__ */ jsxRuntime.jsx(DiscoverySubscription, {
8058
+ engine: engine,
8059
+ filter: requestedFilter,
8060
+ onSnapshot: onRequestedSnapshot,
8061
+ registered: NO_REGISTERED_DOCUMENTS
8062
+ }) : null, evaluated.map(inst => /* @__PURE__ */ jsxRuntime.jsx(InstanceEvaluatorBoundary, {
7311
8063
  instanceId: inst._id,
7312
8064
  children: /* @__PURE__ */ jsxRuntime.jsx(InstanceEvaluator, {
7313
8065
  engine: engine,
7314
8066
  grantsFromPath: grantsFromPath,
8067
+ guardScope: guardScope,
7315
8068
  instanceId: inst._id,
7316
8069
  onReport: onReport
7317
8070
  })
7318
- }, inst._id)), props.renderDefault(props), props.overlay ]
8071
+ }, inst._id)), defaultLayout, props.overlay ]
7319
8072
  });
7320
8073
  }
7321
8074
 
@@ -7325,14 +8078,25 @@ function sameEntries(a, b) {
7325
8078
  return !0;
7326
8079
  }
7327
8080
 
8081
+ function sameMappings(a, b) {
8082
+ return a.length === b.length && a.every((mapping, index) => {
8083
+ const other = b[index];
8084
+ 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;
8085
+ });
8086
+ }
8087
+
7328
8088
  function useDeployedDefinitions(args) {
7329
- const {engine: engine, mappings: mappings, contentResource: contentResource, schemaContentTypes: schemaContentTypes, startRequest: startRequest2, autoStart: autoStart, schema: schema, workspaceName: workspaceName} = args, [issues, setIssues] = react.useState(/* @__PURE__ */ new Map), [starts, setStarts] = react.useState(/* @__PURE__ */ new Map), [fields, setFields] = react.useState(/* @__PURE__ */ new Map), [latestVersions, setLatestVersions] = react.useState(/* @__PURE__ */ new Map), [autoStartByType, setAutoStartByType] = react.useState(
8089
+ const {engine: engine, overrides: overrides, contentResource: contentResource, schemaContentTypes: schemaContentTypes, startRequest: startRequest2, schema: schema} = args, [mappings, setMappings] = react.useState([]), [issues, setIssues] = react.useState(/* @__PURE__ */ new Map), [starts, setStarts] = react.useState(/* @__PURE__ */ new Map), [fields, setFields] = react.useState(/* @__PURE__ */ new Map), [latestVersions, setLatestVersions] = react.useState(/* @__PURE__ */ new Map), [autoStartByType, setAutoStartByType] = react.useState(
7330
8090
  /* @__PURE__ */ new Map), [autoStartDefinitions, setAutoStartDefinitions] = react.useState(/* @__PURE__ */ new Map), logged = react.useRef(/* @__PURE__ */ new Set);
7331
8091
  return react.useEffect(() => {
7332
8092
  let cancelled = !1;
7333
8093
  return (async () => {
7334
- const definitions = await readDeployedDefinitions(engine), found = mappingIssues({
7335
- mappings: mappings,
8094
+ const definitions = await readDeployedDefinitions(engine), effectiveMappings = discoverWorkflowMappings({
8095
+ definitions: definitions,
8096
+ schemaContentTypes: schemaContentTypes,
8097
+ overrides: overrides
8098
+ }), found = mappingIssues({
8099
+ mappings: effectiveMappings,
7336
8100
  definitions: definitions,
7337
8101
  contentResource: contentResource,
7338
8102
  schemaContentTypes: schemaContentTypes
@@ -7342,32 +8106,30 @@ function useDeployedDefinitions(args) {
7342
8106
  logged.current.has(signature) || (logged.current.add(signature), console.error(`[workflow-studio-plugin] ${message}`));
7343
8107
  }
7344
8108
  const resolvedAutoStart = resolveAutoStart({
7345
- autoStart: autoStart,
7346
- context: {
7347
- workspaceName: workspaceName,
7348
- schema: schema
7349
- },
8109
+ autoStart: mappingAutoStartMap(effectiveMappings),
7350
8110
  knownDocTypes: new Set(schema.getTypeNames()),
7351
8111
  definitions: definitions
7352
8112
  });
7353
8113
  if (logOnce(resolvedAutoStart.warnings, logged.current), cancelled) return;
8114
+ setMappings(prev => sameMappings(prev, effectiveMappings) ? prev : effectiveMappings),
7354
8115
  setIssues(prev => sameEntries(prev, found) ? prev : found), setStarts(mappingStartBlocks({
7355
- mappings: mappings,
8116
+ mappings: effectiveMappings,
7356
8117
  definitions: definitions
7357
8118
  })), setFields(mappingDeclaredFields({
7358
- mappings: mappings,
8119
+ mappings: effectiveMappings,
7359
8120
  definitions: definitions
7360
8121
  }));
7361
8122
  const versions = new Map(workflowEngine.latestDeployedDefinitions(definitions).map(d => [ d.name, d.version ]));
7362
8123
  setLatestVersions(prev => sameEntries(prev, versions) ? prev : versions), setAutoStartByType(resolvedAutoStart.byType),
7363
8124
  setAutoStartDefinitions(resolvedAutoStart.definitions);
7364
8125
  })().catch(err => {
7365
- console.error("[workflow-studio-plugin] mapping validation could not read the deployed definitions — keeping the previous verdicts:", err);
8126
+ console.error("[workflow-studio-plugin] mapping discovery failed — keeping the last resolved workflow state:", err);
7366
8127
  }), () => {
7367
8128
  cancelled = !0;
7368
8129
  };
7369
- }, [ engine, mappings, contentResource, schemaContentTypes, startRequest2, autoStart, schema, workspaceName ]),
8130
+ }, [ engine, overrides, contentResource, schemaContentTypes, startRequest2, schema ]),
7370
8131
  {
8132
+ mappings: mappings,
7371
8133
  issues: issues,
7372
8134
  starts: starts,
7373
8135
  fields: fields,
@@ -8153,7 +8915,7 @@ function isFreshDocument(value) {
8153
8915
  }
8154
8916
 
8155
8917
  function subjectEntry(definition) {
8156
- return (definition.fields ?? []).find(workflowEngine.isSubjectEntry);
8918
+ return documentSubjectEntry(definition);
8157
8919
  }
8158
8920
 
8159
8921
  function autoStartInputs(args) {
@@ -8180,11 +8942,11 @@ function pendingWorkflows(workflows, live) {
8180
8942
  }
8181
8943
 
8182
8944
  function extraRequiredInputs(definition) {
8183
- const fields = definition.fields ?? [], subjectNames = new Set(fields.filter(workflowEngine.isSubjectEntry).map(entry => entry.name)), requiredNames = new Set(workflowEngine.missingRequiredInputs({
8945
+ const fields = definition.fields ?? [], subjectName = subjectEntry(definition)?.name, requiredNames = new Set(workflowEngine.missingRequiredInputs({
8184
8946
  entryDefs: fields,
8185
8947
  initialFields: []
8186
8948
  }).map(input => input.name));
8187
- return fields.filter(entry => requiredNames.has(entry.name) && !subjectNames.has(entry.name));
8949
+ return fields.filter(entry => requiredNames.has(entry.name) && entry.name !== subjectName);
8188
8950
  }
8189
8951
 
8190
8952
  function buildAutoStartRequests(args) {
@@ -8232,7 +8994,7 @@ function AutoStartDriver(props) {
8232
8994
  const pending = react.useMemo(() => pendingWorkflows(workflows, new Set(entries.map(entry => entry.instance.definition))), [ workflows, entries ]), inputs = react.useMemo(() => autoStartInputs({
8233
8995
  workflows: pending,
8234
8996
  definitions: autoStartDefinitions
8235
- }), [ pending, autoStartDefinitions ]), pendingRef = react.useRef(pending);
8997
+ }), [ pending, autoStartDefinitions ]), definitionsResolved = workflows.every(name => autoStartDefinitions.has(name)), pendingRef = react.useRef(pending);
8236
8998
  pendingRef.current = pending;
8237
8999
  const instanceIds = react.useRef(/* @__PURE__ */ new Map), instanceIdFor = react.useCallback(workflow => {
8238
9000
  const held = instanceIds.current.get(workflow);
@@ -8276,7 +9038,7 @@ function AutoStartDriver(props) {
8276
9038
  }, [ autoStartDefinitions, docId, docType, binding, engine, observer, seedInstance, instanceIdFor, selectedReleaseId ]), retry = react.useCallback(() => {
8277
9039
  firedRef.current = !1, setFired(!1), setSettled(!1), setFailure(void 0);
8278
9040
  }, []), action = autoStartAction({
8279
- resolved: isDocResolved(docId),
9041
+ resolved: definitionsResolved && isDocResolved(docId),
8280
9042
  pendingCount: pending.length,
8281
9043
  needsInput: inputs.length > 0,
8282
9044
  fired: fired,
@@ -8501,19 +9263,32 @@ function TodoGlance({row: row}) {
8501
9263
  });
8502
9264
  }
8503
9265
 
9266
+ function finishedLinesOf(entries) {
9267
+ const activeTypes = new Set(entries.filter(isLiveEntry).map(entry => entry.instance.definition)), settled = entries.flatMap(entry => {
9268
+ const stamp = entry.instance.completedAt;
9269
+ return stamp === void 0 || activeTypes.has(entry.instance.definition) ? [] : [ {
9270
+ entry: entry,
9271
+ at: Date.parse(stamp)
9272
+ } ];
9273
+ }), latestByType = /* @__PURE__ */ new Map;
9274
+ for (const candidate of settled) {
9275
+ const best = latestByType.get(candidate.entry.instance.definition);
9276
+ (best === void 0 || candidate.at > best.at) && latestByType.set(candidate.entry.instance.definition, candidate);
9277
+ }
9278
+ return [ ...latestByType.values() ].sort((a, b) => b.at - a.at).map(({entry: entry}) => entry);
9279
+ }
9280
+
8504
9281
  function WorkflowFormStrip(props) {
8505
- const {mappings: mappings} = props, {isDocResolved: isDocResolved, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {docId: docId, entries: entries} = useWorkflowsForDocument(props.value?._id ?? null), {params: params, setParams: setParams} = structure.usePaneRouter(), openWorkflowsView = () => setParams({
8506
- ...params,
8507
- view: WORKFLOWS_VIEW_ID
8508
- }), active = entries.filter(isLiveEntry), invalid = surfacedInvalid(discoveryInvalid, entries), resolved = docId !== void 0 && isDocResolved(docId);
9282
+ const {mappings: mappings} = props, {isDocResolved: isDocResolved, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {docId: docId, entries: entries} = useWorkflowsForDocument(props.value?._id ?? null), {params: params, setParams: setParams} = structure.usePaneRouter(), openWorkflowsView = focusId => setParams(focusedViewParams(params, focusId)), active = entries.filter(isLiveEntry), finished = finishedLinesOf(entries), invalid = surfacedInvalid(discoveryInvalid, entries), resolved = docId !== void 0 && isDocResolved(docId);
8509
9283
  /* @__PURE__ */
8510
9284
  return jsxRuntime.jsxs(ui.Stack, {
8511
9285
  gap: 4,
8512
9286
  children: [ invalid !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(InvalidDocNotice, {
8513
9287
  invalid: invalid
8514
- }) : null, active.length > 0 || resolved ? /* @__PURE__ */ jsxRuntime.jsx(StripLines, {
9288
+ }) : null, active.length > 0 || finished.length > 0 || resolved ? /* @__PURE__ */ jsxRuntime.jsx(StripLines, {
8515
9289
  active: active,
8516
9290
  docId: docId,
9291
+ finished: finished,
8517
9292
  initialValue: props.value ?? null,
8518
9293
  mappings: mappings,
8519
9294
  onOpen: openWorkflowsView,
@@ -8522,32 +9297,17 @@ function WorkflowFormStrip(props) {
8522
9297
  });
8523
9298
  }
8524
9299
 
8525
- function StripLines({active: active, docId: docId, initialValue: initialValue, mappings: mappings, onOpen: onOpen, resolved: resolved}) {
8526
- const identity = useAssignmentIdentity(), single = active.length === 1 ? active[0] : void 0;
9300
+ function StripLines({active: active, docId: docId, finished: finished, initialValue: initialValue, mappings: mappings, onOpen: onOpen, resolved: resolved}) {
8527
9301
  /* @__PURE__ */
8528
9302
  return jsxRuntime.jsxs(ui.Stack, {
8529
9303
  gap: 1,
8530
- children: [ single !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(InstanceLine, {
8531
- entry: single,
8532
- onOpen: onOpen
8533
- }) : null, active.length > 1 ? /* @__PURE__ */ jsxRuntime.jsxs(StripLine, {
8534
- aside: /* @__PURE__ */ jsxRuntime.jsx(TaskCountSpinner, {
8535
- entries: active
8536
- }),
8537
- hint: lineHint(identity, forMeWorkOf(active, identity)),
8538
- onOpen: onOpen,
8539
- children: [
8540
- /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
8541
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
8542
- size: 1,
8543
- weight: "medium",
8544
- children: [ active.length, " active workflows" ]
8545
- })
8546
- }),
8547
- /* @__PURE__ */ jsxRuntime.jsx(TaskCountText, {
8548
- entries: active
8549
- }) ]
8550
- }) : null, resolved ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
9304
+ children: [ active.map(entry => /* @__PURE__ */ jsxRuntime.jsx(InstanceLine, {
9305
+ entry: entry,
9306
+ onOpen: () => onOpen(entry.instance._id)
9307
+ }, entry.instance._id)), finished.map(entry => /* @__PURE__ */ jsxRuntime.jsx(FinishedLine, {
9308
+ entry: entry,
9309
+ onOpen: () => onOpen(entry.instance._id)
9310
+ }, entry.instance._id)), resolved ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
8551
9311
  children: /* @__PURE__ */ jsxRuntime.jsx(StartWorkflowControl, {
8552
9312
  docId: docId,
8553
9313
  initialValue: initialValue,
@@ -8601,7 +9361,7 @@ function InstanceLine({entry: entry, onOpen: onOpen}) {
8601
9361
  /* @__PURE__ */
8602
9362
  return jsxRuntime.jsxs(StripLine, {
8603
9363
  aside: unprimed ? void 0 : /* @__PURE__ */ jsxRuntime.jsx(TaskCountSpinner, {
8604
- entries: [ entry ]
9364
+ entry: entry
8605
9365
  }),
8606
9366
  hint: lineHint(identity, forMeWorkOf([ entry ], identity)),
8607
9367
  onOpen: onOpen,
@@ -8620,34 +9380,69 @@ function InstanceLine({entry: entry, onOpen: onOpen}) {
8620
9380
  title: stageTitle(definition, entry.instance.currentStage)
8621
9381
  }),
8622
9382
  /* @__PURE__ */ jsxRuntime.jsx(TaskCountText, {
8623
- entries: [ entry ]
9383
+ entry: entry
8624
9384
  }) ]
8625
9385
  }) ]
8626
9386
  });
8627
9387
  }
8628
9388
 
8629
- function useTaskCountState(entries) {
8630
- const identity = useAssignmentIdentity(), freshness = entries.filter(e => e.invalid === void 0).map(e => evaluationFreshness({
8631
- committed: e.committed,
8632
- evaluation: e.evaluation
8633
- }));
8634
- return !identity || freshness.includes("pending") ? {
9389
+ function FinishedLine({entry: entry, onOpen: onOpen}) {
9390
+ const definition = useDefinition(entry), badgeTrim = useBadgeCapTrim(), {instance: instance} = entry;
9391
+ /* @__PURE__ */
9392
+ return jsxRuntime.jsxs(StripLine, {
9393
+ hint: finishedHint(instance),
9394
+ onOpen: onOpen,
9395
+ children: [
9396
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
9397
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
9398
+ size: 1,
9399
+ weight: "medium",
9400
+ children: instanceTitle(instance, definition)
9401
+ })
9402
+ }), workflowEngine.terminalState(instance) === "aborted" ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
9403
+ style: badgeTrim,
9404
+ children: /* @__PURE__ */ jsxRuntime.jsx(AbortedBadge, {
9405
+ instance: instance
9406
+ })
9407
+ }) : /* @__PURE__ */ jsxRuntime.jsx(StageFace, {
9408
+ completed: !0,
9409
+ title: stageTitle(definition, instance.currentStage)
9410
+ }) ]
9411
+ });
9412
+ }
9413
+
9414
+ function finishedHint(instance) {
9415
+ const verb = workflowEngine.terminalState(instance) === "aborted" ? "Aborted" : "Completed", face = stampFace(verb, instance.completedAt ?? "");
9416
+ return {
9417
+ text: face.lead,
9418
+ ...face.detail === void 0 ? {} : {
9419
+ description: face.detail
9420
+ }
9421
+ };
9422
+ }
9423
+
9424
+ function useTaskCountState(entry) {
9425
+ const identity = useAssignmentIdentity(), freshness = entry.invalid === void 0 ? evaluationFreshness({
9426
+ committed: entry.committed,
9427
+ evaluation: entry.evaluation
9428
+ }) : void 0;
9429
+ return !identity || freshness === "pending" ? {
8635
9430
  kind: "pending"
8636
- } : freshness.includes("stale") ? {
9431
+ } : freshness === "stale" ? {
8637
9432
  kind: "stale"
8638
9433
  } : {
8639
9434
  kind: "ready",
8640
- tasks: entries.reduce((n, e) => n + assignedTaskCount({
8641
- instance: e.instance,
8642
- committed: e.committed,
8643
- evaluation: e.evaluation,
9435
+ tasks: assignedTaskCount({
9436
+ instance: entry.instance,
9437
+ committed: entry.committed,
9438
+ evaluation: entry.evaluation,
8644
9439
  who: identity
8645
- }), 0)
9440
+ })
8646
9441
  };
8647
9442
  }
8648
9443
 
8649
- function TaskCountText({entries: entries}) {
8650
- const state = useTaskCountState(entries);
9444
+ function TaskCountText({entry: entry}) {
9445
+ const state = useTaskCountState(entry);
8651
9446
  return state.kind === "pending" || state.kind === "ready" && state.tasks === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
8652
9447
  muted: !0,
8653
9448
  size: 1,
@@ -8655,8 +9450,8 @@ function TaskCountText({entries: entries}) {
8655
9450
  });
8656
9451
  }
8657
9452
 
8658
- function TaskCountSpinner({entries: entries}) {
8659
- return useTaskCountState(entries).kind !== "pending" ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
9453
+ function TaskCountSpinner({entry: entry}) {
9454
+ return useTaskCountState(entry).kind !== "pending" ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
8660
9455
  paddingLeft: 1,
8661
9456
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
8662
9457
  muted: !0,
@@ -8665,6 +9460,61 @@ function TaskCountSpinner({entries: entries}) {
8665
9460
  });
8666
9461
  }
8667
9462
 
9463
+ function stagePillState(instance) {
9464
+ const definition = definitionSnapshotOf(instance), stage = definition?.stages.find(candidate => candidate.name === instance.currentStage), state = workflowEngine.terminalState(instance);
9465
+ return {
9466
+ title: stageTitle(definition, instance.currentStage),
9467
+ terminal: stage !== void 0 && workflowEngine.isTerminalStage(stage) || state !== "in-flight",
9468
+ state: state,
9469
+ stageKnown: stage !== void 0
9470
+ };
9471
+ }
9472
+
9473
+ function WorkflowStagePreview(props) {
9474
+ 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;
9475
+ return props.renderDefault({
9476
+ ...props,
9477
+ status: lead ? /* @__PURE__ */ jsxRuntime.jsx(StagePill, {
9478
+ entry: lead,
9479
+ extraLive: extraLive
9480
+ }) : props.status
9481
+ });
9482
+ }
9483
+
9484
+ function StagePill({entry: entry, extraLive: extraLive}) {
9485
+ /* @__PURE__ */
9486
+ return jsxRuntime.jsxs(ui.Flex, {
9487
+ align: "center",
9488
+ gap: 2,
9489
+ children: [
9490
+ /* @__PURE__ */ jsxRuntime.jsx(LeadBadge, {
9491
+ entry: entry
9492
+ }), extraLive > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, {
9493
+ fontSize: 1,
9494
+ mode: "outline",
9495
+ tone: "default",
9496
+ children: [ "+", extraLive ]
9497
+ }) : null ]
9498
+ });
9499
+ }
9500
+
9501
+ function LeadBadge({entry: entry}) {
9502
+ const {instance: instance} = entry;
9503
+ if (workflowEngine.isUnprimed(instance)) /* @__PURE__ */ return jsxRuntime.jsx(StartIncompleteBadge, {});
9504
+ const pill = stagePillState(instance);
9505
+ /* @__PURE__ */
9506
+ return jsxRuntime.jsxs(ui.Badge, {
9507
+ fontSize: 1,
9508
+ mode: pill.terminal ? "default" : "outline",
9509
+ tone: pillTone(pill),
9510
+ children: [ pill.title, pill.state === "completed" ? " ✓" : "" ]
9511
+ });
9512
+ }
9513
+
9514
+ function pillTone(args) {
9515
+ return args.state === "aborted" ? "critical" : args.terminal ? "positive" : args.stageKnown ? "primary" : "default";
9516
+ }
9517
+
8668
9518
  function StartWorkflowDialogHost() {
8669
9519
  const {startRequest: startRequest2, closeStartDialog: closeStartDialog} = useWorkflowContext();
8670
9520
  return startRequest2 ? /* @__PURE__ */ jsxRuntime.jsx(StartWorkflowDialog, {
@@ -8739,7 +9589,8 @@ function withWorkflowLock(Action, guardAction) {
8739
9589
  Locked;
8740
9590
  }
8741
9591
 
8742
- const workflowStudioPlugin = sanity.definePlugin(config => ({
9592
+ const workflowStudioPlugin = sanity.definePlugin(config => (assertUniqueWorkflowMappings(config.mappings ?? []),
9593
+ {
8743
9594
  name: "workflow-studio-plugin",
8744
9595
  tools: prev => [ ...prev, workflowsTool ],
8745
9596
  studio: {
@@ -8753,91 +9604,48 @@ const workflowStudioPlugin = sanity.definePlugin(config => ({
8753
9604
  },
8754
9605
  form: {
8755
9606
  components: {
8756
- input: props => {
8757
- if (props.id === "root" && sanity.isObjectInputProps(props)) {
8758
- const docTypeMappings = mappingsForDocType(config.mappings, props.schemaType.name), fallback = docTypeMappings.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(WorkflowFormStrip, {
8759
- ...props,
8760
- mappings: docTypeMappings
8761
- }) : props.renderDefault(props);
8762
- /* @__PURE__ */
8763
- return jsxRuntime.jsx(AutoStartGate, {
8764
- ...props,
8765
- fallback: fallback
8766
- });
8767
- }
8768
- return props.renderDefault(props);
8769
- }
9607
+ preview: WorkflowStagePreview,
9608
+ input: props => props.id === "root" && sanity.isObjectInputProps(props) ? /* @__PURE__ */ jsxRuntime.jsx(WorkflowRootInput, {
9609
+ ...props
9610
+ }) : props.renderDefault(props)
8770
9611
  }
8771
9612
  },
8772
9613
  document: {
8773
9614
  badges: (prev, ctx) => {
8774
- if (!mappingForDocType(config.mappings, ctx.schemaType) || !ctx.documentId) return prev;
9615
+ if (!ctx.documentId) return prev;
8775
9616
  const docId = ctx.documentId;
8776
9617
  return [ makeActiveWorkflowsBadge(docId), makePerspectiveBadge(docId), ...prev ];
8777
9618
  },
8778
- actions: (prev, ctx) => mappingForDocType(config.mappings, ctx.schemaType) ? prev.map(Action => {
9619
+ actions: prev => prev.map(Action => {
8779
9620
  const guardAction = Action.action ? LOCKABLE_ACTIONS.get(Action.action) : void 0;
8780
9621
  return guardAction ? withWorkflowLock(Action, guardAction) : Action;
8781
- }) : prev
9622
+ })
8782
9623
  }
8783
9624
  }));
8784
9625
 
8785
- function WorkflowStagePreview(props) {
8786
- const {entries: entries} = useWorkflowsForDocument(props.documentId ?? props._id ?? null), lead = entries.find(isLiveEntry) ?? entries[0], extraLive = entries.filter(e => e !== lead && isLiveEntry(e)).length;
8787
- return props.renderDefault({
9626
+ function WorkflowRootInput(props) {
9627
+ const {mappings: mappings} = useWorkflowContext(), docTypeMappings = mappingsForDocType(mappings, props.schemaType.name), fallback = docTypeMappings.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(WorkflowFormStrip, {
8788
9628
  ...props,
8789
- status: lead ? /* @__PURE__ */ jsxRuntime.jsx(StagePill, {
8790
- entry: lead,
8791
- extraLive: extraLive
8792
- }) : props.status
8793
- });
8794
- }
8795
-
8796
- function StagePill({entry: entry, extraLive: extraLive}) {
8797
- /* @__PURE__ */
8798
- return jsxRuntime.jsxs(ui.Flex, {
8799
- align: "center",
8800
- gap: 2,
8801
- children: [
8802
- /* @__PURE__ */ jsxRuntime.jsx(LeadBadge, {
8803
- entry: entry
8804
- }), extraLive > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, {
8805
- fontSize: 1,
8806
- mode: "outline",
8807
- tone: "default",
8808
- children: [ "+", extraLive ]
8809
- }) : null ]
8810
- });
8811
- }
8812
-
8813
- function LeadBadge({entry: entry}) {
8814
- const {instance: instance, evaluation: evaluation} = entry;
8815
- if (workflowEngine.isUnprimed(instance)) /* @__PURE__ */ return jsxRuntime.jsx(StartIncompleteBadge, {});
8816
- const stage = evaluation?.currentStage?.stage, title = stage?.title ?? stageTitle(definitionSnapshotOf(instance), instance.currentStage), state = workflowEngine.terminalState(instance), isTerminal = (stage ? workflowEngine.isTerminalStage(stage) : !1) || state !== "in-flight";
9629
+ mappings: docTypeMappings
9630
+ }) : props.renderDefault(props);
8817
9631
  /* @__PURE__ */
8818
- return jsxRuntime.jsxs(ui.Badge, {
8819
- fontSize: 1,
8820
- mode: isTerminal ? "default" : "outline",
8821
- tone: pillTone({
8822
- state: state,
8823
- isTerminal: isTerminal,
8824
- stageKnown: stage !== void 0
8825
- }),
8826
- children: [ title, state === "completed" ? " ✓" : "" ]
9632
+ return jsxRuntime.jsx(AutoStartGate, {
9633
+ ...props,
9634
+ fallback: fallback
8827
9635
  });
8828
9636
  }
8829
9637
 
8830
- function pillTone(args) {
8831
- return args.state === "aborted" ? "critical" : args.isTerminal ? "positive" : args.stageKnown ? "primary" : "default";
8832
- }
8833
-
8834
9638
  exports.AbortedBadge = AbortedBadge;
8835
9639
 
8836
9640
  exports.ActivitiesList = ActivitiesList;
8837
9641
 
8838
9642
  exports.ActivityDetailDialog = ActivityDetailDialog;
8839
9643
 
8840
- exports.ActivityStatusIcon = ActivityStatusIcon;
9644
+ exports.ActivityRow = ActivityRow;
9645
+
9646
+ exports.BreadcrumbTail = BreadcrumbTail;
9647
+
9648
+ exports.CollapsibleBand = CollapsibleBand;
8841
9649
 
8842
9650
  exports.DismissablePopover = DismissablePopover;
8843
9651
 
@@ -8847,34 +9655,50 @@ exports.DocRefFace = DocRefFace;
8847
9655
 
8848
9656
  exports.GroupHeading = GroupHeading;
8849
9657
 
8850
- exports.HintedMenuButton = HintedMenuButton;
8851
-
8852
9658
  exports.HoverHint = HoverHint;
8853
9659
 
8854
9660
  exports.InvalidDocNotice = InvalidDocNotice;
8855
9661
 
9662
+ exports.LinkChip = LinkChip;
9663
+
8856
9664
  exports.LoadingRow = LoadingRow;
8857
9665
 
9666
+ exports.SpinnerSlot = SpinnerSlot;
9667
+
8858
9668
  exports.StageChip = StageChip;
8859
9669
 
8860
- exports.StageGlyph = StageGlyph;
9670
+ exports.StageFace = StageFace;
8861
9671
 
8862
9672
  exports.StaleLock = StaleLock;
8863
9673
 
9674
+ exports.TabSwitch = TabSwitch;
9675
+
8864
9676
  exports.TodoItemsList = TodoItemsList;
8865
9677
 
8866
- exports.UserAvatarGroup = UserAvatarGroup;
9678
+ exports.TrailingHairline = TrailingHairline;
9679
+
9680
+ exports.UnreadableDocsNote = UnreadableDocsNote;
8867
9681
 
8868
- exports.WorkflowStagePreview = WorkflowStagePreview;
9682
+ exports.activityRowProps = activityRowProps;
8869
9683
 
8870
9684
  exports.assigneeUserIdsOf = assigneeUserIdsOf;
8871
9685
 
9686
+ exports.committedRowFace = committedRowFace;
9687
+
9688
+ exports.dateControlKind = dateControlKind;
9689
+
9690
+ exports.dateControlValue = dateControlValue;
9691
+
8872
9692
  exports.definitionSnapshotOf = definitionSnapshotOf;
8873
9693
 
8874
9694
  exports.describeError = describeError;
8875
9695
 
9696
+ exports.editTargetOf = editTargetOf;
9697
+
8876
9698
  exports.findActivity = findActivity;
8877
9699
 
9700
+ exports.findActivityNode = findActivityNode;
9701
+
8878
9702
  exports.formatDate = formatDate;
8879
9703
 
8880
9704
  exports.gdrLocality = gdrLocality;
@@ -8891,26 +9715,36 @@ exports.isEvaluationStale = isEvaluationStale;
8891
9715
 
8892
9716
  exports.isOpenActivityStatus = isOpenActivityStatus;
8893
9717
 
8894
- exports.mappingForDocType = mappingForDocType;
8895
-
8896
9718
  exports.openActivityGone = openActivityGone;
8897
9719
 
9720
+ exports.parseDateFieldValue = parseDateFieldValue;
9721
+
8898
9722
  exports.parseStoredDateValue = parseStoredDateValue;
8899
9723
 
8900
9724
  exports.readDeployedDefinitions = readDeployedDefinitions;
8901
9725
 
9726
+ exports.rowAssignControlState = rowAssignControlState;
9727
+
9728
+ exports.rowDateControlState = rowDateControlState;
9729
+
9730
+ exports.serializeDateFieldValue = serializeDateFieldValue;
9731
+
8902
9732
  exports.stageTitle = stageTitle;
8903
9733
 
8904
9734
  exports.stateByActivity = stateByActivity;
8905
9735
 
9736
+ exports.stopRowClick = stopRowClick;
9737
+
9738
+ exports.stopRowMouseDown = stopRowMouseDown;
9739
+
8906
9740
  exports.useAssignmentIdentity = useAssignmentIdentity;
8907
9741
 
8908
9742
  exports.useDefinition = useDefinition;
8909
9743
 
8910
- exports.useDocLink = useDocLink;
8911
-
8912
9744
  exports.useProjectMembers = useProjectMembers;
8913
9745
 
9746
+ exports.useSaveField = useSaveField;
9747
+
8914
9748
  exports.useUserDisplays = useUserDisplays;
8915
9749
 
8916
9750
  exports.useWorkflowContext = useWorkflowContext;
@@ -8919,8 +9753,8 @@ exports.useWorkflowInstanceEntry = useWorkflowInstanceEntry;
8919
9753
 
8920
9754
  exports.workflowDefaultDocumentNode = workflowDefaultDocumentNode;
8921
9755
 
8922
- exports.workflowDocTypes = workflowDocTypes;
8923
-
8924
9756
  exports.workflowStudioPlugin = workflowStudioPlugin;
8925
9757
 
9758
+ exports.workflowsTabCodec = workflowsTabCodec;
9759
+
8926
9760
  exports.workflowsView = workflowsView;