@sanity/workflow-studio-plugin 0.4.0 → 0.5.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 workflowEngine = require("@sanity/workflow-engine"), jsxRuntime = require("react/jsx-runtime"), Timeline = require("@sanity/icons/Timeline"), ui = require("@sanity/ui"), react = require("react"), structure = require("sanity/structure"), formatDistanceToNow = require("date-fns/formatDistanceToNow"), csm = require("@sanity/client/csm"), workflowStudio = require("@sanity/workflow-studio"), sanity = require("sanity"), Calendar = require("@sanity/icons/Calendar"), workflowComponents = require("@sanity/workflow-components"), User = require("@sanity/icons/User"), Close = require("@sanity/icons/Close"), Search = require("@sanity/icons/Search"), ChevronRight = require("@sanity/icons/ChevronRight"), Checkmark = require("@sanity/icons/Checkmark"), Edit = require("@sanity/icons/Edit"), content = require("@sanity/util/content"), InfoOutline = require("@sanity/icons/InfoOutline"), Document = require("@sanity/icons/Document"), WarningOutline = require("@sanity/icons/WarningOutline"), router = require("sanity/router"), types = require("@sanity/types"), Launch = require("@sanity/icons/Launch"), Empty$1 = require("@sanity/icons/Empty"), ErrorFilled = require("@sanity/icons/ErrorFilled"), RemoveCircle = require("@sanity/icons/RemoveCircle"), Add = require("@sanity/icons/Add"), Bolt = require("@sanity/icons/Bolt"), ChevronDown = require("@sanity/icons/ChevronDown"), CheckmarkCircle = require("@sanity/icons/CheckmarkCircle"), Ulist = require("@sanity/icons/Ulist"), pluralize = require("pluralize-esm"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal"), ArrowUp = require("@sanity/icons/ArrowUp"), workflowReact = require("@sanity/workflow-react"), Transfer = require("@sanity/icons/Transfer");
4
4
 
5
5
  function _interopDefaultCompat(e) {
6
6
  return e && typeof e == "object" && "default" in e ? e : {
@@ -170,7 +170,49 @@ function mappingIssues(args) {
170
170
  return issues;
171
171
  }
172
172
 
173
- const WORKFLOWS_VIEW_ID = "workflows", WORKFLOWS_TAB_PARAM = "workflowsTab", WORKFLOW_INTENT = "workflow";
173
+ const WORKFLOWS_VIEW_ID = "workflows", WORKFLOWS_TAB_PARAM = "workflowsTab";
174
+
175
+ function withoutWorkflowsTab(params) {
176
+ const {[WORKFLOWS_TAB_PARAM]: _previous, ...rest} = params ?? {};
177
+ return rest;
178
+ }
179
+
180
+ function workflowsTabCodec(tabs) {
181
+ const [defaultTab] = tabs;
182
+ return {
183
+ read: params => tabs.find(tab => tab === params?.[WORKFLOWS_TAB_PARAM]) ?? defaultTab,
184
+ write: (params, tab) => {
185
+ const rest = withoutWorkflowsTab(params);
186
+ return tab === defaultTab ? rest : {
187
+ ...rest,
188
+ [WORKFLOWS_TAB_PARAM]: tab
189
+ };
190
+ }
191
+ };
192
+ }
193
+
194
+ const WORKFLOWS_FOCUS_PARAM = "workflowsFocus";
195
+
196
+ function focusedViewParams(params, focusId) {
197
+ return {
198
+ ...withoutWorkflowsTab(params),
199
+ view: WORKFLOWS_VIEW_ID,
200
+ [WORKFLOWS_FOCUS_PARAM]: focusId
201
+ };
202
+ }
203
+
204
+ function focusedLanding(focus, instanceIds) {
205
+ if (!(focus === void 0 || !instanceIds.includes(focus))) return {
206
+ focusedId: focus,
207
+ instanceIds: instanceIds
208
+ };
209
+ }
210
+
211
+ function landingDefaultOpen(landing, instanceId) {
212
+ if (!(landing === void 0 || !landing.instanceIds.includes(instanceId))) return instanceId === landing.focusedId;
213
+ }
214
+
215
+ const WORKFLOW_INTENT = "workflow";
174
216
 
175
217
  function handlesWorkflowIntent(intent, params) {
176
218
  return workflowIntentState(intent, params) !== null;
@@ -243,6 +285,16 @@ function formatTimeAgo(value) {
243
285
  });
244
286
  }
245
287
 
288
+ function stampFace(verb, at) {
289
+ const ago = formatTimeAgo(at);
290
+ return ago === "" ? {
291
+ lead: `${verb} ${formatShortDateTime(at)}`
292
+ } : {
293
+ lead: `${verb} ${ago}`,
294
+ detail: formatShortDateTime(at)
295
+ };
296
+ }
297
+
246
298
  function formatDate(value) {
247
299
  if (!value) return "—";
248
300
  const d = parseDateFieldValue(value, "date") ?? new Date(value);
@@ -292,6 +344,10 @@ function hrefIfHttp(value) {
292
344
  }
293
345
  }
294
346
 
347
+ function unreadableDocsReport(docs) {
348
+ return docs.map(doc => `Unreadable workflow document\nID: ${doc.documentId}\nReason: ${doc.reason}\nDetail: ${doc.message}`).join(`\n\n`);
349
+ }
350
+
295
351
  function isButton(actionEval) {
296
352
  return workflowEngine.actionRendering(actionEval) === "button";
297
353
  }
@@ -348,20 +404,27 @@ function useWorkflowContext() {
348
404
  }
349
405
 
350
406
  function useWorkflowsForDocument(rawId) {
351
- const {register: register, unregister: unregister, byDocument: byDocument} = useWorkflowContext(), docId = rawId ? csm.getPublishedId(rawId) : void 0;
352
- return react.useEffect(() => {
407
+ const {register: register, unregister: unregister, entries: entries} = useWorkflowContext(), docId = rawId ? csm.getPublishedId(rawId) : void 0;
408
+ react.useEffect(() => {
353
409
  if (docId) return register(docId), () => unregister(docId);
354
- }, [ docId, register, unregister ]), {
410
+ }, [ docId, register, unregister ]);
411
+ const list = react.useSyncExternalStore(entries.subscribe, react.useCallback(() => docId ? entries.getDocument(docId) : EMPTY_ENTRIES, [ entries, docId ]));
412
+ return {
355
413
  docId: docId,
356
- entries: docId ? byDocument.get(docId) ?? EMPTY_ENTRIES : EMPTY_ENTRIES
414
+ entries: list
357
415
  };
358
416
  }
359
417
 
418
+ function useHeldWorkflowEntry(instanceId) {
419
+ const {entries: entries} = useWorkflowContext();
420
+ return react.useSyncExternalStore(entries.subscribe, react.useCallback(() => instanceId ? entries.getInstance(instanceId) : void 0, [ entries, instanceId ]));
421
+ }
422
+
360
423
  function useWorkflowInstanceEntry(instanceId) {
361
- const {byInstance: byInstance, requestInstance: requestInstance, releaseInstance: releaseInstance} = useWorkflowContext();
362
- if (react.useEffect(() => {
424
+ const {requestInstance: requestInstance, releaseInstance: releaseInstance} = useWorkflowContext();
425
+ return react.useEffect(() => {
363
426
  if (instanceId) return requestInstance(instanceId), () => releaseInstance(instanceId);
364
- }, [ instanceId, requestInstance, releaseInstance ]), !!instanceId) return byInstance.get(instanceId);
427
+ }, [ instanceId, requestInstance, releaseInstance ]), useHeldWorkflowEntry(instanceId);
365
428
  }
366
429
 
367
430
  const isTodoDone = item => item.status === "done", toggledTodoStatus = item => isTodoDone(item) ? "open" : "done";
@@ -630,14 +693,17 @@ function changesOwnStatus(action, activityName) {
630
693
  return ownStatusOps(action, activityName).length > 0;
631
694
  }
632
695
 
696
+ function concludesHere(actionEval, activityName) {
697
+ return changesOwnStatus(actionEval.action, activityName) || actionEval.firing?.exitsStage === !0;
698
+ }
699
+
633
700
  function actionButtonTone(args) {
634
- const status = actionAdvanceStatus(args.action, args.activityName);
635
- return status === void 0 ? {
701
+ return concludesHere(args.actionEval, args.activityName) ? {
702
+ mode: "default",
703
+ tone: actionAdvanceStatus(args.actionEval.action, args.activityName) === "failed" ? "critical" : "default"
704
+ } : {
636
705
  mode: "ghost",
637
706
  tone: "default"
638
- } : {
639
- mode: "default",
640
- tone: status === "failed" ? "critical" : "default"
641
707
  };
642
708
  }
643
709
 
@@ -699,6 +765,11 @@ function actionRowKind(actionEval) {
699
765
  };
700
766
  }
701
767
 
768
+ function actionTriggerLabel(actionEval) {
769
+ const label = actionLabel(actionEval.action);
770
+ return actionRowKind(actionEval).kind === "params-form" ? `${label}…` : label;
771
+ }
772
+
702
773
  function tickActionNames(args) {
703
774
  const arrayFieldNames = new Set(args.state.flatMap(f => f._type === "array" ? [ f.name ] : [])), names = /* @__PURE__ */ new Set;
704
775
  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 +799,8 @@ function deriveActivityPresentation(args) {
728
799
  settled: settled,
729
800
  triggeredActions: settled ? [] : args.activityEval.actions.filter(a => a.triggered === !0),
730
801
  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))
802
+ actions: buttonActions.filter(a => !concludesHere(a, activityName)),
803
+ terminalActions: buttonActions.filter(a => concludesHere(a, activityName))
733
804
  };
734
805
  }
735
806
 
@@ -742,15 +813,78 @@ function rowTerminalActions(args) {
742
813
  }).terminalActions;
743
814
  }
744
815
 
745
- function rowAssignField(args) {
746
- return (groupEditableByActivity(args.editableFields ?? []).get(args.activityName) ?? []).find(f => f.type === "assignees" && f.editable);
816
+ function isDeclaredEditable(field) {
817
+ return field.editable !== void 0;
818
+ }
819
+
820
+ function rowControlState(args) {
821
+ const {kinds: kinds, activity: activity, status: status, editableFields: editableFields} = args;
822
+ if (!(activity.fields ?? []).some(field => kinds.includes(field.type) && isDeclaredEditable(field))) return {
823
+ kind: "none"
824
+ };
825
+ if (editableFields !== void 0) {
826
+ const field = (groupEditableByActivity(editableFields).get(activity.name) ?? []).find(f => kinds.includes(f.type));
827
+ return field?.editable ? {
828
+ kind: "editable",
829
+ field: field
830
+ } : field?.disabledReason ? {
831
+ kind: "closed",
832
+ reason: describeEditDisabledReason(field.disabledReason)
833
+ } : {
834
+ kind: "closed",
835
+ reason: describeEditDisabledReason({
836
+ kind: "not-editable"
837
+ })
838
+ };
839
+ }
840
+ return workflowEngine.isTerminalActivityStatus(status) ? {
841
+ kind: "closed",
842
+ reason: describeEditDisabledReason({
843
+ kind: "edit-window-closed",
844
+ detail: "activity settled"
845
+ })
846
+ } : {
847
+ kind: "loading"
848
+ };
849
+ }
850
+
851
+ function rowAssignControlState(args) {
852
+ return rowControlState({
853
+ kinds: [ "assignees" ],
854
+ ...args
855
+ });
856
+ }
857
+
858
+ function rowDateControlState(args) {
859
+ return rowControlState({
860
+ kinds: [ "date", "datetime" ],
861
+ ...args
862
+ });
863
+ }
864
+
865
+ function committedRowFace(row) {
866
+ return {
867
+ activityName: row.activityName,
868
+ title: row.title,
869
+ status: row.status,
870
+ automated: !1,
871
+ blocked: !1
872
+ };
747
873
  }
748
874
 
749
875
  function activityRowProps(args) {
750
- const {activityEval: activityEval, activityState: activityState, editableFields: editableFields} = args, activityName = activityEval.activity.name, state = activityState.get(activityName) ?? [];
876
+ const {activityEval: activityEval, activityState: activityState, editableFields: editableFields} = args, state = activityState.get(activityEval.activity.name) ?? [];
751
877
  return {
752
- assignField: rowAssignField({
753
- activityName: activityName,
878
+ face: {
879
+ activityName: activityEval.activity.name,
880
+ title: activityLabel(activityEval.activity),
881
+ status: activityEval.status,
882
+ automated: showsAutomatedPill(activityEval),
883
+ blocked: showsBlockedTag(activityEval)
884
+ },
885
+ assignState: rowAssignControlState({
886
+ activity: activityEval.activity,
887
+ status: activityEval.status,
754
888
  editableFields: editableFields
755
889
  }),
756
890
  assigneeIds: assigneeUserIdsOf(state),
@@ -762,6 +896,15 @@ function activityRowProps(args) {
762
896
  };
763
897
  }
764
898
 
899
+ function dateControlKind(state) {
900
+ if (state.kind === "editable" && (state.field.type === "date" || state.field.type === "datetime")) return state.field.type;
901
+ }
902
+
903
+ function dateControlValue(args) {
904
+ const {state: state, dates: dates} = args;
905
+ return state.kind === "editable" ? typeof state.field.value == "string" && state.field.value !== "" ? state.field.value : void 0 : dates.find(value => parseStoredDateValue(value) !== void 0);
906
+ }
907
+
765
908
  function activityStateOf(instance, activityName) {
766
909
  return workflowEngine.findCurrentActivityEntry(instance, activityName)?.fields ?? [];
767
910
  }
@@ -788,10 +931,17 @@ function isEmptyValue(value) {
788
931
  return value == null || value === "" || Array.isArray(value) && value.length === 0;
789
932
  }
790
933
 
934
+ function effectiveValidation(kind, validation) {
935
+ return kind !== "progress" ? validation : {
936
+ min: validation?.min ?? 0,
937
+ max: validation?.max ?? 100
938
+ };
939
+ }
940
+
791
941
  function scalarValidationIssue(args) {
792
942
  const issues = workflowEngine.scalarValidationIssues({
793
943
  entryType: args.kind,
794
- validation: args.validation,
944
+ validation: effectiveValidation(args.kind, args.validation),
795
945
  value: args.value
796
946
  });
797
947
  return issues === void 0 ? void 0 : `${args.label}: ${issues.join("; ")}`;
@@ -999,8 +1149,19 @@ function HintBody(props) {
999
1149
  });
1000
1150
  }
1001
1151
 
1152
+ function textInputFacet(kind) {
1153
+ return kind === "number" || kind === "progress" ? {
1154
+ type: "number"
1155
+ } : kind === "url" ? {
1156
+ type: "url"
1157
+ } : {};
1158
+ }
1159
+
1002
1160
  function scalarInputConstraints(kind, validation) {
1003
- return validation === void 0 ? {} : kind === "number" ? {
1161
+ return kind === "progress" ? {
1162
+ min: validation?.min ?? 0,
1163
+ max: validation?.max ?? 100
1164
+ } : validation === void 0 ? {} : kind === "number" ? {
1004
1165
  min: validation.min,
1005
1166
  max: validation.max
1006
1167
  } : kind === "string" || kind === "text" ? {
@@ -1113,7 +1274,7 @@ function ChoiceSelect({options: options, value: value, onChange: onChange, readO
1113
1274
  });
1114
1275
  }
1115
1276
 
1116
- const SCALAR_INPUT_KINDS = [ "string", "text", "url", "number", "date", "dateTime", "datetime", "boolean" ], SCALAR_INPUT_KIND_SET = new Set(SCALAR_INPUT_KINDS);
1277
+ const SCALAR_INPUT_KINDS = [ "string", "text", "url", "number", "progress", "date", "dateTime", "datetime", "boolean" ], SCALAR_INPUT_KIND_SET = new Set(SCALAR_INPUT_KINDS);
1117
1278
 
1118
1279
  function isScalarInputKind(kind) {
1119
1280
  return SCALAR_INPUT_KIND_SET.has(kind);
@@ -1124,29 +1285,19 @@ function textValueOf(value) {
1124
1285
  }
1125
1286
 
1126
1287
  function ScalarInput({kind: kind, value: value, onChange: onChange, options: options, validation: validation}) {
1127
- if (options !== void 0) /* @__PURE__ */
1128
- return jsxRuntime.jsx(ChoiceSelect, {
1288
+ return options !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ChoiceSelect, {
1129
1289
  onChange: onChange,
1130
1290
  options: options,
1131
1291
  value: value
1132
- });
1133
- if (kind === "boolean") /* @__PURE__ */
1134
- return jsxRuntime.jsx(ui.Checkbox, {
1292
+ }) : kind === "boolean" ? /* @__PURE__ */ jsxRuntime.jsx(ui.Checkbox, {
1135
1293
  checked: value === !0,
1136
1294
  onChange: e => onChange(e.currentTarget.checked)
1137
- });
1138
- if (kind === "date" || kind === "dateTime" || kind === "datetime") /* @__PURE__ */
1139
- return jsxRuntime.jsx(DateFieldInput, {
1295
+ }) : kind === "date" || kind === "dateTime" || kind === "datetime" ? /* @__PURE__ */ jsxRuntime.jsx(DateFieldInput, {
1140
1296
  kind: kind === "date" ? "date" : "datetime",
1141
1297
  onChange: next => onChange(next ?? ""),
1142
1298
  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
- } : {},
1299
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, {
1300
+ ...textInputFacet(kind),
1150
1301
  ...scalarInputConstraints(kind, validation),
1151
1302
  fontSize: 1,
1152
1303
  onChange: e => onChange(e.currentTarget.value),
@@ -1234,25 +1385,26 @@ function MemberPickerButton({label: label = "Select member…", onChange: onChan
1234
1385
  });
1235
1386
  }
1236
1387
 
1237
- function StubPreview({id: id, layout: layout, schemaType: schemaType}) {
1388
+ function useStubValuePreview(id, schemaType) {
1238
1389
  const value = react.useMemo(() => ({
1239
1390
  _id: id,
1240
1391
  _type: schemaType.name
1241
- }), [ id, schemaType.name ]);
1242
- return sanity.useValuePreview({
1392
+ }), [ id, schemaType.name ]), preview = sanity.useValuePreview({
1243
1393
  schemaType: schemaType,
1244
1394
  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, {
1395
+ });
1396
+ return {
1397
+ value: value,
1398
+ preview: preview
1399
+ };
1400
+ }
1401
+
1402
+ function StubPreview({id: id, schemaType: schemaType}) {
1403
+ const {value: value, preview: preview} = useStubValuePreview(id, schemaType);
1404
+ return preview.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(sanity.SanityDefaultPreview, {
1253
1405
  isPlaceholder: !0
1254
1406
  }) : /* @__PURE__ */ jsxRuntime.jsx(sanity.Preview, {
1255
- layout: layout,
1407
+ layout: "default",
1256
1408
  schemaType: schemaType,
1257
1409
  skipVisibilityCheck: !0,
1258
1410
  value: value
@@ -1362,7 +1514,6 @@ function PreviewOrId({id: id, type: type, fallbackTitle: fallbackTitle}) {
1362
1514
  const schemaType = sanity.useSchema().get(type);
1363
1515
  return schemaType ? /* @__PURE__ */ jsxRuntime.jsx(StubPreview, {
1364
1516
  id: id,
1365
- layout: "default",
1366
1517
  schemaType: schemaType
1367
1518
  }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1368
1519
  align: "center",
@@ -1496,12 +1647,30 @@ function useClosableToast() {
1496
1647
  }), [ toast ]);
1497
1648
  }
1498
1649
 
1650
+ const ActionClusterContext = react.createContext(void 0), ActionClusterProvider = ActionClusterContext.Provider;
1651
+
1652
+ function useActionClusterState() {
1653
+ const [pending, setPending] = react.useState(!1);
1654
+ return react.useMemo(() => ({
1655
+ pending: pending,
1656
+ onFireChange: setPending
1657
+ }), [ pending ]);
1658
+ }
1659
+
1660
+ function useActionClusterLock() {
1661
+ return react.useContext(ActionClusterContext);
1662
+ }
1663
+
1664
+ function useActionClusterPending() {
1665
+ return useActionClusterLock()?.pending === !0;
1666
+ }
1667
+
1499
1668
  function useFireAction(args) {
1500
- const {instanceId: instanceId, activity: activity, action: action, label: label} = args, {fireActionFor: fireActionFor} = useWorkflowContext(), toast = useClosableToast(), [pending, setPending] = react.useState(!1);
1669
+ const {instanceId: instanceId, activity: activity, action: action, label: label} = args, {fireActionFor: fireActionFor} = useWorkflowContext(), toast = useClosableToast(), cluster = useActionClusterLock(), [localPending, setLocalPending] = react.useState(!1), pending = localPending || cluster?.pending === !0;
1501
1670
  return {
1502
1671
  fire: react.useCallback(async params => {
1503
1672
  if (pending) return !1;
1504
- setPending(!0);
1673
+ setLocalPending(!0), cluster?.onFireChange(!0);
1505
1674
  try {
1506
1675
  return await fireActionFor(instanceId, {
1507
1676
  activity: activity,
@@ -1521,9 +1690,9 @@ function useFireAction(args) {
1521
1690
  description: describeError(err)
1522
1691
  }), !1;
1523
1692
  } finally {
1524
- setPending(!1);
1693
+ setLocalPending(!1), cluster?.onFireChange(!1);
1525
1694
  }
1526
- }, [ fireActionFor, instanceId, activity, action, label, pending, toast ]),
1695
+ }, [ fireActionFor, instanceId, activity, action, label, pending, toast, cluster ]),
1527
1696
  pending: pending
1528
1697
  };
1529
1698
  }
@@ -1532,6 +1701,10 @@ function stopRowMouseDown(e) {
1532
1701
  e.preventDefault(), e.stopPropagation();
1533
1702
  }
1534
1703
 
1704
+ function stopRowClick(e) {
1705
+ e.stopPropagation();
1706
+ }
1707
+
1535
1708
  function spanTriggerProps(chrome) {
1536
1709
  return chrome?.inButtonCard === !0 ? {
1537
1710
  as: "span",
@@ -1567,7 +1740,6 @@ function FireButton({instanceId: instanceId, activity: activity, action: action,
1567
1740
  mode: chrome?.mode ?? mode,
1568
1741
  onClick: handleClick,
1569
1742
  padding: 2,
1570
- style: chrome?.style,
1571
1743
  text: label,
1572
1744
  tone: tone,
1573
1745
  ...spanTriggerProps(chrome),
@@ -1596,12 +1768,11 @@ function ActivityActionRow({actionEval: actionEval, instanceId: instanceId, acti
1596
1768
  }
1597
1769
 
1598
1770
  function DisabledActionButton({actionEval: actionEval, chrome: chrome}) {
1599
- const label = actionLabel(actionEval.action), button = /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1771
+ const label = actionTriggerLabel(actionEval), button = /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1600
1772
  fontSize: 1,
1601
1773
  mode: chrome?.mode ?? "ghost",
1602
1774
  onClick: e => e.stopPropagation(),
1603
1775
  padding: 2,
1604
- style: chrome?.style,
1605
1776
  text: label,
1606
1777
  ...spanTriggerProps(chrome),
1607
1778
  disabled: !0
@@ -1614,7 +1785,7 @@ function DisabledActionButton({actionEval: actionEval, chrome: chrome}) {
1614
1785
 
1615
1786
  function PlainActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, chrome: chrome}) {
1616
1787
  const {action: action} = actionEval, label = actionLabel(action), btn = actionButtonTone({
1617
- action: action,
1788
+ actionEval: actionEval,
1618
1789
  activityName: activity
1619
1790
  }), button = /* @__PURE__ */ jsxRuntime.jsx(FireButton, {
1620
1791
  action: action.name,
@@ -1692,8 +1863,8 @@ function ParamsActionDialog({actionEval: actionEval, instanceId: instanceId, act
1692
1863
  }
1693
1864
 
1694
1865
  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,
1866
+ const [open, setOpen] = react.useState(!1), btn = actionButtonTone({
1867
+ actionEval: actionEval,
1697
1868
  activityName: activity
1698
1869
  });
1699
1870
  /* @__PURE__ */
@@ -1706,11 +1877,8 @@ function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, act
1706
1877
  chrome?.inButtonCard === !0 && e.stopPropagation(), setOpen(!0);
1707
1878
  },
1708
1879
  padding: 2,
1709
- text: `${label}…`,
1880
+ text: actionTriggerLabel(actionEval),
1710
1881
  tone: btn.tone,
1711
- ...chrome?.style ? {
1712
- style: chrome.style
1713
- } : {},
1714
1882
  ...spanTriggerProps(chrome)
1715
1883
  }), open ? /* @__PURE__ */ jsxRuntime.jsx(RowClickShield, {
1716
1884
  active: chrome?.inButtonCard === !0,
@@ -1724,178 +1892,6 @@ function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, act
1724
1892
  });
1725
1893
  }
1726
1894
 
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
1895
  const DIM = {
1900
1896
  opacity: .6
1901
1897
  };
@@ -1933,8 +1929,12 @@ function stageTitle(definition, stageName) {
1933
1929
  return definition?.stages.find(stage => stage.name === stageName)?.title ?? stageName;
1934
1930
  }
1935
1931
 
1932
+ function findActivityNode(args) {
1933
+ return args.definition?.stages.find(s => s.name === args.stageName)?.activities?.find(a => a.name === args.activityName);
1934
+ }
1935
+
1936
1936
  function findActivity(args) {
1937
- const activity = args.definition?.stages.find(s => s.name === args.stageName)?.activities?.find(a => a.name === args.activityName);
1937
+ const activity = findActivityNode(args);
1938
1938
  return {
1939
1939
  title: activity?.title ?? args.activityName,
1940
1940
  description: activity?.description
@@ -2068,14 +2068,15 @@ function FieldInput$1({kind: kind, value: value, onChange: onChange, onEnter: on
2068
2068
  });
2069
2069
 
2070
2070
  case "number":
2071
+ case "progress":
2071
2072
  /* @__PURE__ */
2072
2073
  return jsxRuntime.jsx(ui.TextInput, {
2074
+ ...textInputFacet(kind),
2075
+ ...scalarInputConstraints(kind, validation),
2073
2076
  fontSize: 1,
2074
2077
  onChange: e => onChange(e.currentTarget.value === "" ? null : Number(e.currentTarget.value)),
2075
2078
  onKeyDown: e => e.key === "Enter" && onEnter(),
2076
2079
  readOnly: readOnly,
2077
- ...scalarInputConstraints(kind, validation),
2078
- type: "number",
2079
2080
  value: textValueOf(value)
2080
2081
  });
2081
2082
 
@@ -2103,6 +2104,30 @@ function FieldInput$1({kind: kind, value: value, onChange: onChange, onEnter: on
2103
2104
  }
2104
2105
  }
2105
2106
 
2107
+ function LinkChip({hint: hint, children: children, as: as = "a", ...anchorProps}) {
2108
+ /* @__PURE__ */
2109
+ return jsxRuntime.jsx(HoverHint, {
2110
+ text: hint,
2111
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
2112
+ __unstable_focusRing: !0,
2113
+ as: as,
2114
+ "data-as": "a",
2115
+ padding: 2,
2116
+ radius: 3,
2117
+ style: {
2118
+ alignItems: "center",
2119
+ color: "inherit",
2120
+ display: "flex",
2121
+ minWidth: 0,
2122
+ textDecoration: "none"
2123
+ },
2124
+ tone: "inherit",
2125
+ ...anchorProps,
2126
+ children: children
2127
+ })
2128
+ });
2129
+ }
2130
+
2106
2131
  function docLinkState(input) {
2107
2132
  const {bareId: bareId, resource: resource} = input;
2108
2133
  if (bareId === void 0 || resource === void 0) return {
@@ -2212,7 +2237,6 @@ function DocRefFace({gdr: gdr}) {
2212
2237
  const {parsed: parsed2, local: local, schemaType: schemaType} = useLocalDocRef(gdr);
2213
2238
  return parsed2 && local && schemaType ? /* @__PURE__ */ jsxRuntime.jsx(StubPreview, {
2214
2239
  id: parsed2.documentId,
2215
- layout: "default",
2216
2240
  schemaType: schemaType
2217
2241
  }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
2218
2242
  align: "center",
@@ -2242,15 +2266,23 @@ function ForeignRefNotice({bareId: bareId, resource: resource}) {
2242
2266
  }
2243
2267
 
2244
2268
  function MissingDocNotice({bareId: bareId, layout: layout}) {
2245
- return layout === "inline" ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2246
- muted: !0,
2247
- size: 1,
2269
+ return layout === "inline" ?
2270
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
2271
+ padding: 2,
2248
2272
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
2249
2273
  align: "center",
2250
- as: "span",
2251
- gap: 1,
2274
+ gap: 2,
2252
2275
  children: [
2253
- /* @__PURE__ */ jsxRuntime.jsx(WarningOutline.WarningOutlineIcon, {}), " Document unavailable" ]
2276
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2277
+ muted: !0,
2278
+ size: 1,
2279
+ children: /* @__PURE__ */ jsxRuntime.jsx(WarningOutline.WarningOutlineIcon, {})
2280
+ }),
2281
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2282
+ muted: !0,
2283
+ size: 1,
2284
+ children: "Document unavailable"
2285
+ }) ]
2254
2286
  })
2255
2287
  }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
2256
2288
  border: !0,
@@ -2283,44 +2315,73 @@ function UnopenableTypeNotice({actualType: actualType, bareId: bareId}) {
2283
2315
  });
2284
2316
  }
2285
2317
 
2286
- function LinkedDocPreview({bareId: bareId, layout: layout, schemaType: schemaType}) {
2287
- return layout === "inline" ? /* @__PURE__ */ jsxRuntime.jsx(router.IntentLink, {
2288
- intent: "edit",
2289
- 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",
2302
- schemaType: schemaType
2303
- })
2304
- }) : /* @__PURE__ */ jsxRuntime.jsx(router.IntentLink, {
2305
- intent: "edit",
2306
- onClick: e => e.stopPropagation(),
2307
- params: {
2308
- id: bareId
2309
- },
2310
- style: {
2311
- color: "inherit",
2312
- textDecoration: "none",
2318
+ function renderTypeIcon(icon) {
2319
+ return typeof icon == "function" ? react.createElement(icon) : react.isValidElement(icon) ? icon : /* @__PURE__ */ jsxRuntime.jsx(Document.DocumentIcon, {});
2320
+ }
2321
+
2322
+ function InlineChipFace({bareId: bareId, schemaType: schemaType}) {
2323
+ const {preview: preview} = useStubValuePreview(bareId, schemaType);
2324
+ /* @__PURE__ */
2325
+ return jsxRuntime.jsxs(ui.Flex, {
2326
+ align: "center",
2327
+ gap: 2,
2328
+ children: [
2329
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2330
+ size: 1,
2331
+ children: renderTypeIcon(schemaType.icon)
2332
+ }), preview.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.TextSkeleton, {
2333
+ animated: !0,
2334
+ radius: 1,
2335
+ size: 1,
2336
+ style: {
2337
+ width: 96
2338
+ }
2339
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2340
+ size: 1,
2341
+ textOverflow: "ellipsis",
2342
+ weight: "medium",
2343
+ children: preview.value?.title ?? /* @__PURE__ */ jsxRuntime.jsx("em", {
2344
+ children: "Untitled"
2345
+ })
2346
+ }) ]
2347
+ });
2348
+ }
2349
+
2350
+ function LinkedDocPreview({bareId: bareId, layout: layout, schemaType: schemaType}) {
2351
+ const EditIntentLink = react.useMemo(() => function(linkProps) {
2352
+ /* @__PURE__ */
2353
+ return jsxRuntime.jsx(router.IntentLink, {
2354
+ ...linkProps,
2355
+ intent: "edit",
2356
+ params: {
2357
+ id: bareId
2358
+ }
2359
+ });
2360
+ }, [ bareId ]);
2361
+ return layout === "inline" ? /* @__PURE__ */ jsxRuntime.jsx(LinkChip, {
2362
+ as: EditIntentLink,
2363
+ hint: "Open document",
2364
+ onClick: e => e.stopPropagation(),
2365
+ children: /* @__PURE__ */ jsxRuntime.jsx(InlineChipFace, {
2366
+ bareId: bareId,
2367
+ schemaType: schemaType
2368
+ })
2369
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
2370
+ __unstable_focusRing: !0,
2371
+ as: EditIntentLink,
2372
+ border: !0,
2373
+ "data-as": "a",
2374
+ onClick: e => e.stopPropagation(),
2375
+ padding: 1,
2376
+ radius: 3,
2377
+ style: {
2378
+ color: "inherit",
2379
+ textDecoration: "none",
2313
2380
  display: "block"
2314
2381
  },
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
- })
2382
+ children: /* @__PURE__ */ jsxRuntime.jsx(StubPreview, {
2383
+ id: bareId,
2384
+ schemaType: schemaType
2324
2385
  })
2325
2386
  });
2326
2387
  }
@@ -2464,6 +2525,10 @@ const fieldValueRenderer = {
2464
2525
  }, ref.id))
2465
2526
  }) : /* @__PURE__ */ jsxRuntime.jsx(Empty, {}),
2466
2527
  number: renderTextValue,
2528
+ progress: field => /* @__PURE__ */ jsxRuntime.jsx(workflowComponents.ProgressBar, {
2529
+ label: field.title ?? field.name,
2530
+ value: field.value
2531
+ }),
2467
2532
  object: field => field.value === null ? /* @__PURE__ */ jsxRuntime.jsx(Empty, {}) : /* @__PURE__ */ jsxRuntime.jsx(ui.Code, {
2468
2533
  size: 1,
2469
2534
  style: {
@@ -2715,7 +2780,7 @@ function useFieldDraft(args) {
2715
2780
  }
2716
2781
 
2717
2782
  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);
2783
+ const {editFieldFor: editFieldFor, previewFieldFor: previewFieldFor, discardFieldPreviewFor: discardFieldPreviewFor} = useWorkflowContext(), me = sanity.useCurrentUser(), held = useHeldWorkflowEntry(instanceId), entry = held ? workflowEngine.resolveFieldEntry(held.instance, field) : void 0, evaluation = held?.evaluation, target = editTargetOf(field), consequence = fieldConsequence(evaluation, field.name), advanceTo = advanceTargetTitle(evaluation, field.name);
2719
2784
  /* @__PURE__ */
2720
2785
  return jsxRuntime.jsx(EditableField, {
2721
2786
  field: field,
@@ -3257,6 +3322,27 @@ function ActivityStatusIcon({status: status}) {
3257
3322
  });
3258
3323
  }
3259
3324
 
3325
+ function LoadingRow({label: label, padding: padding}) {
3326
+ /* @__PURE__ */
3327
+ return jsxRuntime.jsxs(ui.Flex, {
3328
+ align: "center",
3329
+ gap: 2,
3330
+ ...padding === void 0 ? {} : {
3331
+ padding: padding
3332
+ },
3333
+ children: [
3334
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
3335
+ muted: !0,
3336
+ size: 1
3337
+ }),
3338
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
3339
+ muted: !0,
3340
+ size: 1,
3341
+ children: label
3342
+ }) ]
3343
+ });
3344
+ }
3345
+
3260
3346
  function useSpaceToken(index) {
3261
3347
  const value = ui.useTheme_v2().space[index];
3262
3348
  if (value === void 0) throw new Error(`theme is missing space token ${index}`);
@@ -3287,6 +3373,27 @@ function useBadgeCapTrim() {
3287
3373
  };
3288
3374
  }
3289
3375
 
3376
+ function AvatarDisplay({avatar: avatar, "aria-label": ariaLabel}) {
3377
+ const trim = useAvatarCapTrim();
3378
+ /* @__PURE__ */
3379
+ return jsxRuntime.jsx(ui.Box, {
3380
+ "aria-label": ariaLabel,
3381
+ as: "span",
3382
+ padding: 2,
3383
+ style: {
3384
+ display: "inline-flex"
3385
+ },
3386
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
3387
+ align: "center",
3388
+ justify: "center",
3389
+ style: {
3390
+ margin: trim
3391
+ },
3392
+ children: avatar
3393
+ })
3394
+ });
3395
+ }
3396
+
3290
3397
  function AvatarButton({avatar: avatar, onClick: onClick, onMouseDown: onMouseDown, as: as, "aria-label": ariaLabel, ref: ref}) {
3291
3398
  const trim = useAvatarCapTrim();
3292
3399
  /* @__PURE__ */
@@ -3329,10 +3436,13 @@ function UserAvatar({id: id}) {
3329
3436
  });
3330
3437
  }
3331
3438
 
3332
- function UserAvatarGroup({ids: ids}) {
3439
+ function UserAvatarGroup({ids: ids, hint: hint}) {
3333
3440
  const displays = useUserDisplays(ids);
3334
3441
  return displays.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
3335
3442
  text: displays.map(d => d.name).join(", "),
3443
+ ...hint === void 0 ? {} : {
3444
+ description: hint
3445
+ },
3336
3446
  children: /* @__PURE__ */ jsxRuntime.jsx(workflowComponents.MemberAvatarGroup, {
3337
3447
  members: displays.map(display => ({
3338
3448
  id: display.id,
@@ -3343,9 +3453,26 @@ function UserAvatarGroup({ids: ids}) {
3343
3453
  });
3344
3454
  }
3345
3455
 
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) {
3456
+ function AssignActivityControl({instanceId: instanceId, state: state, assigneeIds: assigneeIds = []}) {
3457
+ const {editFieldFor: editFieldFor} = useWorkflowContext(), toast = useClosableToast(), [open, setOpen] = react.useState(!1), [busy, setBusy] = react.useState(!1);
3458
+ if (state.kind === "none") return assigneeIds.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(AvatarDisplay, {
3459
+ "aria-label": "Assignees",
3460
+ avatar: /* @__PURE__ */ jsxRuntime.jsx(UserAvatarGroup, {
3461
+ ids: assigneeIds
3462
+ })
3463
+ });
3464
+ if (state.kind === "closed") return assigneeIds.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(AvatarButton, {
3465
+ "aria-label": "Assignees",
3466
+ as: "span",
3467
+ avatar: /* @__PURE__ */ jsxRuntime.jsx(UserAvatarGroup, {
3468
+ hint: state.reason,
3469
+ ids: assigneeIds
3470
+ }),
3471
+ onClick: stopRowClick,
3472
+ onMouseDown: stopRowMouseDown
3473
+ });
3474
+ 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 => {
3475
+ if (!(busy || field === void 0)) {
3349
3476
  setBusy(!0);
3350
3477
  try {
3351
3478
  selectedIds.has(memberId) ? await editFieldFor(instanceId, {
@@ -3398,8 +3525,11 @@ function AssignActivityControl({instanceId: instanceId, field: field, assigneeId
3398
3525
  /* @__PURE__ */
3399
3526
  return jsxRuntime.jsx(DismissablePopover, {
3400
3527
  content: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
3401
- onClick: e => e.stopPropagation(),
3402
- children: /* @__PURE__ */ jsxRuntime.jsx(MemberPicker, {
3528
+ onClick: stopRowClick,
3529
+ children: state.kind === "loading" ? /* @__PURE__ */ jsxRuntime.jsx(LoadingRow, {
3530
+ label: "Loading…",
3531
+ padding: 3
3532
+ }) : /* @__PURE__ */ jsxRuntime.jsx(MemberPicker, {
3403
3533
  onSelect: member => member && void toggle(member.id),
3404
3534
  selectedIds: selectedIds
3405
3535
  })
@@ -3687,9 +3817,9 @@ function BreadcrumbTail({segments: segments, style: style}) {
3687
3817
  }
3688
3818
 
3689
3819
  function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3690
- const frame = /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
3820
+ const frame =
3821
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
3691
3822
  align: "center",
3692
- gap: 1,
3693
3823
  children: [
3694
3824
  /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
3695
3825
  align: "center",
@@ -3703,6 +3833,7 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3703
3833
  align: "center",
3704
3834
  flex: 1,
3705
3835
  gap: 2,
3836
+ paddingLeft: 2,
3706
3837
  style: {
3707
3838
  minWidth: 0
3708
3839
  },
@@ -3710,6 +3841,7 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3710
3841
  }), end === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
3711
3842
  align: "center",
3712
3843
  gap: 2,
3844
+ paddingLeft: 2,
3713
3845
  style: {
3714
3846
  flexShrink: 0
3715
3847
  },
@@ -3718,14 +3850,14 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3718
3850
  });
3719
3851
  return onOpen === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
3720
3852
  padding: 1,
3721
- radius: 2,
3853
+ radius: 3,
3722
3854
  children: frame
3723
3855
  }) :
3724
3856
  /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
3725
3857
  as: "button",
3726
3858
  onClick: onOpen,
3727
3859
  padding: 1,
3728
- radius: 2,
3860
+ radius: 3,
3729
3861
  style: {
3730
3862
  textAlign: "left"
3731
3863
  },
@@ -3912,6 +4044,202 @@ function AutomationActionRow({actionEval: actionEval, definition: definition}) {
3912
4044
  });
3913
4045
  }
3914
4046
 
4047
+ function inlineActionCount(args) {
4048
+ const {available: available, actionWidths: actionWidths, menuWidth: menuWidth, gap: gap} = args, max = Math.min(3, actionWidths.length);
4049
+ for (let count = max; count > 0; count--) {
4050
+ const buttons = actionWidths.slice(0, count).reduce((sum, w) => sum + w, 0) + gap * (count - 1), menu = count === actionWidths.length ? 0 : menuWidth + gap;
4051
+ if (buttons + menu <= available) return count;
4052
+ }
4053
+ return 0;
4054
+ }
4055
+
4056
+ const ACTIONS_MENU_TRIGGER = {
4057
+ fontSize: 1,
4058
+ gap: 1,
4059
+ iconRight: ChevronDown.ChevronDownIcon,
4060
+ padding: 2
4061
+ };
4062
+
4063
+ function ActionMenuItem({actionEval: actionEval, instanceId: instanceId, activity: activity, onCollectParams: onCollectParams}) {
4064
+ const rowKind = actionRowKind(actionEval), label = actionTriggerLabel(actionEval), {fire: fire, pending: pending} = useFireAction({
4065
+ instanceId: instanceId,
4066
+ activity: activity,
4067
+ action: actionEval.action.name,
4068
+ label: label
4069
+ });
4070
+ return rowKind.kind === "disabled" ? /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
4071
+ disabled: !0,
4072
+ text: label
4073
+ }) : rowKind.kind === "plain" ? /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
4074
+ disabled: pending,
4075
+ onClick: () => {
4076
+ fire();
4077
+ },
4078
+ text: label
4079
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
4080
+ disabled: pending,
4081
+ onClick: onCollectParams,
4082
+ text: label
4083
+ });
4084
+ }
4085
+
4086
+ function rowShield(menuButton) {
4087
+ /* @__PURE__ */
4088
+ return jsxRuntime.jsx("span", {
4089
+ onClick: e => e.stopPropagation(),
4090
+ onMouseDown: stopRowMouseDown,
4091
+ style: {
4092
+ display: "contents"
4093
+ },
4094
+ children: menuButton
4095
+ });
4096
+ }
4097
+
4098
+ function ActionsMenuButton({actions: actions, instanceId: instanceId, activity: activity, label: label, mode: mode = "ghost", inButtonCard: inButtonCard = !1}) {
4099
+ const [paramsAction, setParamsAction] = react.useState(void 0), clusterPending = useActionClusterPending(), menuId = react.useId(), menuButton = /* @__PURE__ */ jsxRuntime.jsx(ui.MenuButton, {
4100
+ button: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4101
+ ...ACTIONS_MENU_TRIGGER,
4102
+ mode: mode,
4103
+ text: label,
4104
+ ...inButtonCard ? {
4105
+ as: "span"
4106
+ } : {},
4107
+ ...clusterPending ? {
4108
+ disabled: !0
4109
+ } : {}
4110
+ }),
4111
+ id: `workflow-actions-menu-${menuId}`,
4112
+ menu: /* @__PURE__ */ jsxRuntime.jsx(ui.Menu, {
4113
+ children: actions.map(a => /* @__PURE__ */ jsxRuntime.jsx(ActionMenuItem, {
4114
+ actionEval: a,
4115
+ activity: activity,
4116
+ instanceId: instanceId,
4117
+ onCollectParams: () => setParamsAction(a)
4118
+ }, a.action.name))
4119
+ }),
4120
+ popover: {
4121
+ placement: "top-end",
4122
+ portal: !0
4123
+ }
4124
+ });
4125
+ /* @__PURE__ */
4126
+ return jsxRuntime.jsxs(jsxRuntime.Fragment, {
4127
+ children: [ inButtonCard ? rowShield(menuButton) : menuButton, paramsAction ? /* @__PURE__ */ jsxRuntime.jsx(RowClickShield, {
4128
+ active: inButtonCard,
4129
+ children: /* @__PURE__ */ jsxRuntime.jsx(ParamsActionDialog, {
4130
+ actionEval: paramsAction,
4131
+ activity: activity,
4132
+ instanceId: instanceId,
4133
+ onClose: () => setParamsAction(void 0)
4134
+ })
4135
+ }) : null ]
4136
+ });
4137
+ }
4138
+
4139
+ const FOOTER_GAP_PX = 8, MORE_ACTIONS_LABEL = "More actions";
4140
+
4141
+ function TerminalFooter({actions: actions, activity: activity, instanceId: instanceId}) {
4142
+ const lock = useActionClusterState(), [only, ...rest] = actions;
4143
+ return only ? /* @__PURE__ */ jsxRuntime.jsx(ActionClusterProvider, {
4144
+ value: lock,
4145
+ children: rest.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(FooterRow, {
4146
+ children: /* @__PURE__ */ jsxRuntime.jsx(ActivityActionRow, {
4147
+ actionEval: only,
4148
+ activity: activity,
4149
+ instanceId: instanceId
4150
+ })
4151
+ }) : /* @__PURE__ */ jsxRuntime.jsx(FittedActions, {
4152
+ actions: actions,
4153
+ activity: activity,
4154
+ instanceId: instanceId
4155
+ })
4156
+ }) : null;
4157
+ }
4158
+
4159
+ function FooterRow({children: children, onElement: onElement}) {
4160
+ /* @__PURE__ */
4161
+ return jsxRuntime.jsx(ui.Box, {
4162
+ padding: 3,
4163
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4164
+ align: "center",
4165
+ justify: "flex-end",
4166
+ ...onElement ? {
4167
+ ref: onElement
4168
+ } : {},
4169
+ style: {
4170
+ gap: FOOTER_GAP_PX
4171
+ },
4172
+ children: children
4173
+ })
4174
+ });
4175
+ }
4176
+
4177
+ function FittedActions({actions: actions, activity: activity, instanceId: instanceId}) {
4178
+ 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);
4179
+ react.useLayoutEffect(() => {
4180
+ if (measureEl === null || footerSize === null || measureSize === null) return;
4181
+ const widths = Array.from(measureEl.children, clone => clone.getBoundingClientRect().width);
4182
+ setInline(inlineActionCount({
4183
+ available: footerSize.border.width,
4184
+ actionWidths: widths.slice(0, -1),
4185
+ menuWidth: widths.at(-1) ?? 0,
4186
+ gap: FOOTER_GAP_PX
4187
+ }));
4188
+ }, [ actions, footerSize, measureEl, measureSize ]);
4189
+ const overflow = inline === void 0 ? [] : actions.slice(inline);
4190
+ /* @__PURE__ */
4191
+ return jsxRuntime.jsxs(jsxRuntime.Fragment, {
4192
+ children: [
4193
+ /* @__PURE__ */ jsxRuntime.jsx(MeasureRow, {
4194
+ actions: actions,
4195
+ onElement: setMeasureEl
4196
+ }),
4197
+ /* @__PURE__ */ jsxRuntime.jsxs(FooterRow, {
4198
+ onElement: setFooterEl,
4199
+ children: [ inline === void 0 ? null : actions.slice(0, inline).map(a => /* @__PURE__ */ jsxRuntime.jsx(ActivityActionRow, {
4200
+ actionEval: a,
4201
+ activity: activity,
4202
+ instanceId: instanceId
4203
+ }, a.action.name)), overflow.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ActionsMenuButton, {
4204
+ actions: overflow,
4205
+ activity: activity,
4206
+ instanceId: instanceId,
4207
+ label: MORE_ACTIONS_LABEL,
4208
+ mode: "default"
4209
+ }) : null ]
4210
+ }) ]
4211
+ });
4212
+ }
4213
+
4214
+ function MeasureRow({actions: actions, onElement: onElement}) {
4215
+ /* @__PURE__ */
4216
+ return jsxRuntime.jsx("div", {
4217
+ "aria-hidden": !0,
4218
+ style: {
4219
+ height: 0,
4220
+ overflow: "hidden"
4221
+ },
4222
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", {
4223
+ ref: onElement,
4224
+ style: {
4225
+ display: "inline-flex",
4226
+ whiteSpace: "nowrap"
4227
+ },
4228
+ children: [ actions.map(a => /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4229
+ as: "span",
4230
+ fontSize: 1,
4231
+ padding: 2,
4232
+ text: actionTriggerLabel(a)
4233
+ }, a.action.name)),
4234
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4235
+ ...ACTIONS_MENU_TRIGGER,
4236
+ as: "span",
4237
+ text: MORE_ACTIONS_LABEL
4238
+ }) ]
4239
+ })
4240
+ });
4241
+ }
4242
+
3915
4243
  function ActivityDetailDialog({entry: entry, activityName: activityName, breadcrumb: breadcrumb, definition: definition, document: document, onClose: onClose}) {
3916
4244
  const detail = deriveActivityDetail({
3917
4245
  entry: entry,
@@ -3921,12 +4249,11 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
3921
4249
  const instanceId = entry.instance._id, notice = blockedNotice(detail), hasBody = detail.state.length > 0 || notice !== void 0;
3922
4250
  /* @__PURE__ */
3923
4251
  return jsxRuntime.jsxs(ui.Dialog, {
3924
- footer: /* @__PURE__ */ jsxRuntime.jsx(TerminalFooter, {
3925
- activityName: activityName,
3926
- detail: detail,
3927
- instanceId: instanceId,
3928
- onClose: onClose
3929
- }),
4252
+ footer: detail.presentation.terminalActions.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(TerminalFooter, {
4253
+ actions: detail.presentation.terminalActions,
4254
+ activity: activityName,
4255
+ instanceId: instanceId
4256
+ }) : void 0,
3930
4257
  header: /* @__PURE__ */ jsxRuntime.jsx(Breadcrumb, {
3931
4258
  emphasizeCurrent: !0,
3932
4259
  segments: [ ...breadcrumb, detail.title ]
@@ -4095,8 +4422,11 @@ function FieldRow({field: field, detail: detail, entry: entry, activityName: act
4095
4422
  /* @__PURE__ */ jsxRuntime.jsx(FieldValue, {
4096
4423
  field: field
4097
4424
  }), assignField ? /* @__PURE__ */ jsxRuntime.jsx(AssignActivityControl, {
4098
- field: assignField,
4099
- instanceId: instanceId
4425
+ instanceId: instanceId,
4426
+ state: {
4427
+ kind: "editable",
4428
+ field: assignField
4429
+ }
4100
4430
  }) : null ]
4101
4431
  }) ]
4102
4432
  });
@@ -4129,31 +4459,6 @@ function blockedNotice(detail) {
4129
4459
  });
4130
4460
  }
4131
4461
 
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
4462
  function EmptyState({action: action, description: description, icon: icon, title: title}) {
4158
4463
  /* @__PURE__ */
4159
4464
  return jsxRuntime.jsx(ui.Card, {
@@ -4231,13 +4536,69 @@ function GroupHeading({count: count, title: title, end: end}) {
4231
4536
  });
4232
4537
  }
4233
4538
 
4539
+ function CopyDetailsButton({unreadable: unreadable}) {
4540
+ const toast = useClosableToast();
4541
+ /* @__PURE__ */
4542
+ return jsxRuntime.jsx(ui.Button, {
4543
+ fontSize: 1,
4544
+ mode: "ghost",
4545
+ onClick: () => {
4546
+ navigator.clipboard.writeText(unreadableDocsReport(unreadable)).then(() => toast.push({
4547
+ status: "success",
4548
+ title: "Details copied"
4549
+ }), () => toast.push({
4550
+ status: "error",
4551
+ title: "Could not copy the details"
4552
+ }));
4553
+ },
4554
+ padding: 2,
4555
+ text: "Copy details"
4556
+ });
4557
+ }
4558
+
4559
+ function UnreadableDocsNote({unreadable: unreadable}) {
4560
+ if (unreadable.length === 0) return null;
4561
+ const label = unreadable.length === 1 ? "1 workflow document isn’t listed — this Studio can’t read it. Share the details with your Studio maintainers." : `${unreadable.length} workflow documents aren’t listed — this Studio can’t read them. Share the details with your Studio maintainers.`;
4562
+ /* @__PURE__ */
4563
+ return jsxRuntime.jsxs(ui.Card, {
4564
+ padding: 2,
4565
+ paddingLeft: 3,
4566
+ radius: 3,
4567
+ style: {
4568
+ alignItems: "center",
4569
+ display: "flex"
4570
+ },
4571
+ tone: "caution",
4572
+ children: [
4573
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
4574
+ align: "center",
4575
+ flex: 1,
4576
+ gap: 3,
4577
+ children: [
4578
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4579
+ muted: !0,
4580
+ size: 1,
4581
+ children: /* @__PURE__ */ jsxRuntime.jsx(WarningOutline.WarningOutlineIcon, {})
4582
+ }),
4583
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4584
+ muted: !0,
4585
+ size: 1,
4586
+ children: label
4587
+ }) ]
4588
+ }),
4589
+ /* @__PURE__ */ jsxRuntime.jsx(CopyDetailsButton, {
4590
+ unreadable: unreadable
4591
+ }) ]
4592
+ });
4593
+ }
4594
+
4234
4595
  const HEADLINES = {
4235
4596
  "model-ahead": "Workflow needs a newer engine",
4236
4597
  "shape-violation": "Unreadable workflow document"
4237
4598
  };
4238
4599
 
4239
4600
  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.`;
4601
+ 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
4602
  }
4242
4603
 
4243
4604
  function InvalidDocNotice({invalid: invalid}) {
@@ -4248,20 +4609,22 @@ function InvalidDocNotice({invalid: invalid}) {
4248
4609
  radius: 2,
4249
4610
  tone: "caution",
4250
4611
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
4251
- space: 2,
4612
+ space: 3,
4252
4613
  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
- })
4614
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4615
+ size: 1,
4616
+ weight: "medium",
4617
+ children: HEADLINES[invalid.reason]
4260
4618
  }),
4261
4619
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4262
4620
  muted: !0,
4263
4621
  size: 1,
4264
4622
  children: remediation(invalid)
4623
+ }),
4624
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4625
+ children: /* @__PURE__ */ jsxRuntime.jsx(CopyDetailsButton, {
4626
+ unreadable: [ invalid ]
4627
+ })
4265
4628
  }) ]
4266
4629
  })
4267
4630
  });
@@ -4301,6 +4664,30 @@ function openActivityGone(args) {
4301
4664
  return evaluation !== void 0 && !evaluation.currentStage.activities.some(a => a.activity.name === target.activityName);
4302
4665
  }
4303
4666
 
4667
+ function HintedMenuButton({hint: hint, ...menuButtonProps}) {
4668
+ const [open, setOpen] = react.useState(!1), popover = {
4669
+ tone: "default",
4670
+ ...menuButtonProps.popover?.placement === void 0 ? {
4671
+ placement: "bottom-end",
4672
+ fallbackPlacements: [ "top-end" ]
4673
+ } : {},
4674
+ ...menuButtonProps.popover
4675
+ };
4676
+ return hint === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.MenuButton, {
4677
+ ...menuButtonProps,
4678
+ popover: popover
4679
+ }) : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4680
+ disabled: open,
4681
+ text: hint,
4682
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.MenuButton, {
4683
+ ...menuButtonProps,
4684
+ onClose: () => setOpen(!1),
4685
+ onOpen: () => setOpen(!0),
4686
+ popover: popover
4687
+ })
4688
+ });
4689
+ }
4690
+
4304
4691
  function subjectGdr(ctx) {
4305
4692
  return !ctx.subjectDocId || !ctx.mapping ? null : subjectGdrRef({
4306
4693
  mapping: ctx.mapping,
@@ -4348,6 +4735,10 @@ function missingInitFields(initFields, values) {
4348
4735
  return initFields.filter(entry => gatesStart(entry) && !isFieldFilled(entry, values[entry.name] ?? null));
4349
4736
  }
4350
4737
 
4738
+ function numericFilled(value) {
4739
+ return value !== null && value !== "" && Number.isFinite(Number(value));
4740
+ }
4741
+
4351
4742
  const filledByKind = {
4352
4743
  actor: value => value !== null,
4353
4744
  array: value => Array.isArray(value) && value.length > 0,
@@ -4355,7 +4746,8 @@ const filledByKind = {
4355
4746
  boolean: () => !0,
4356
4747
  "doc.ref": value => value !== null,
4357
4748
  "doc.refs": value => Array.isArray(value) && value.length > 0,
4358
- number: value => value !== null && value !== "" && Number.isFinite(Number(value)),
4749
+ number: numericFilled,
4750
+ progress: numericFilled,
4359
4751
  "release.ref": value => value !== null,
4360
4752
  subject: value => value !== null
4361
4753
  };
@@ -4373,7 +4765,14 @@ const asInitial = candidate => candidate, scalarInitialValue = (entry, value) =>
4373
4765
  type: entry.type,
4374
4766
  name: entry.name,
4375
4767
  value: value
4376
- }), initialValueByKind = {
4768
+ }), numericInitialValue = (entry, value) => {
4769
+ const n = Number(value);
4770
+ return Number.isFinite(n) ? asInitial({
4771
+ type: entry.type,
4772
+ name: entry.name,
4773
+ value: n
4774
+ }) : null;
4775
+ }, initialValueByKind = {
4377
4776
  actor: (entry, value) => asInitial({
4378
4777
  type: entry.type,
4379
4778
  name: entry.name,
@@ -4398,14 +4797,8 @@ const asInitial = candidate => candidate, scalarInitialValue = (entry, value) =>
4398
4797
  value: value
4399
4798
  }),
4400
4799
  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
- },
4800
+ number: numericInitialValue,
4801
+ progress: numericInitialValue,
4409
4802
  "release.ref": (entry, value) => asInitial({
4410
4803
  type: entry.type,
4411
4804
  name: entry.name,
@@ -4884,6 +5277,58 @@ function StartWorkflowMenuItem({mapping: mapping, docId: docId, initialValue: in
4884
5277
  }) : item;
4885
5278
  }
4886
5279
 
5280
+ function settledDim(settled) {
5281
+ return settled ? {
5282
+ opacity: .66
5283
+ } : void 0;
5284
+ }
5285
+
5286
+ function ActivityStatusLead({status: status}) {
5287
+ /* @__PURE__ */
5288
+ return jsxRuntime.jsx(HoverHint, {
5289
+ text: ACTIVITY_STATUS_LABEL[status],
5290
+ children: /* @__PURE__ */ jsxRuntime.jsx(ActivityStatusIcon, {
5291
+ status: status
5292
+ })
5293
+ });
5294
+ }
5295
+
5296
+ function ActivityRowTitle({dimmed: dimmed, children: children}) {
5297
+ /* @__PURE__ */
5298
+ return jsxRuntime.jsx(ui.Text, {
5299
+ size: 1,
5300
+ style: {
5301
+ ...dimmed,
5302
+ minWidth: 0
5303
+ },
5304
+ textOverflow: "ellipsis",
5305
+ weight: "medium",
5306
+ children: children
5307
+ });
5308
+ }
5309
+
5310
+ function RowTerminalActions({actions: actions, activity: activity, instanceId: instanceId}) {
5311
+ const lock = useActionClusterState(), [only, ...rest] = actions;
5312
+ return only ? /* @__PURE__ */ jsxRuntime.jsx(ActionClusterProvider, {
5313
+ value: lock,
5314
+ children: rest.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ActivityActionRow, {
5315
+ actionEval: only,
5316
+ activity: activity,
5317
+ chrome: {
5318
+ mode: "ghost",
5319
+ inButtonCard: !0
5320
+ },
5321
+ instanceId: instanceId
5322
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ActionsMenuButton, {
5323
+ actions: actions,
5324
+ activity: activity,
5325
+ inButtonCard: !0,
5326
+ instanceId: instanceId,
5327
+ label: "Select action"
5328
+ })
5329
+ }) : null;
5330
+ }
5331
+
4887
5332
  function TitleTag({hint: hint, tone: tone, children: children}) {
4888
5333
  const badgeTrim = useBadgeCapTrim();
4889
5334
  /* @__PURE__ */
@@ -4897,57 +5342,37 @@ function TitleTag({hint: hint, tone: tone, children: children}) {
4897
5342
  },
4898
5343
  children: children
4899
5344
  })
4900
- });
4901
- }
4902
-
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, {
5345
+ });
5346
+ }
5347
+
5348
+ function ActivityRow({face: face, breadcrumb: breadcrumb, assigneeIds: assigneeIds, assignState: assignState, dateControl: dateControl, instanceId: instanceId, onOpen: onOpen, terminalActions: terminalActions = []}) {
5349
+ const settled = workflowEngine.isTerminalActivityStatus(face.status), dimmed = settledDim(settled);
5350
+ /* @__PURE__ */
5351
+ return jsxRuntime.jsxs(WorkRow, {
4920
5352
  end: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
4921
- children: [ settled || terminalActions.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(TerminalActions, {
5353
+ children: [ settled || terminalActions.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(RowTerminalActions, {
4922
5354
  actions: terminalActions,
4923
- activity: activity.name,
4924
- inButtonCard: !0,
5355
+ activity: face.activityName,
5356
+ instanceId: instanceId
5357
+ }), dateControl,
5358
+ /* @__PURE__ */ jsxRuntime.jsx(AssignActivityControl, {
5359
+ assigneeIds: assigneeIds,
4925
5360
  instanceId: instanceId,
4926
- mode: "ghost"
4927
- }), assignmentControl ]
5361
+ state: assignState
5362
+ }) ]
4928
5363
  }),
4929
- lead:
4930
- /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4931
- text: ACTIVITY_STATUS_LABEL[status],
4932
- children: /* @__PURE__ */ jsxRuntime.jsx(ActivityStatusIcon, {
4933
- status: status
4934
- })
5364
+ lead: /* @__PURE__ */ jsxRuntime.jsx(ActivityStatusLead, {
5365
+ status: face.status
4935
5366
  }),
4936
5367
  onOpen: onOpen,
4937
5368
  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, {
5369
+ /* @__PURE__ */ jsxRuntime.jsx(ActivityRowTitle, {
5370
+ dimmed: dimmed,
5371
+ children: face.title
5372
+ }), face.automated ? /* @__PURE__ */ jsxRuntime.jsx(TitleTag, {
4948
5373
  hint: "Completes on its own — no user action is needed",
4949
5374
  children: "Automated"
4950
- }) : null, showsBlockedTag(activityEval) ? /* @__PURE__ */ jsxRuntime.jsx(TitleTag, {
5375
+ }) : null, face.blocked ? /* @__PURE__ */ jsxRuntime.jsx(TitleTag, {
4951
5376
  hint: "Nothing can be done on this activity right now — open it to see why",
4952
5377
  tone: "caution",
4953
5378
  children: "Blocked"
@@ -4988,7 +5413,6 @@ function ForMeList({entry: entry, work: work, identity: identity, onOpenActivity
4988
5413
  activityState: activityState,
4989
5414
  editableFields: entry.evaluation?.editableFields
4990
5415
  }),
4991
- activityEval: activityEval,
4992
5416
  breadcrumb: breadcrumb,
4993
5417
  instanceId: instance._id,
4994
5418
  onOpen: () => onOpenActivity(activityEval.activity.name)
@@ -5032,33 +5456,12 @@ function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity}) {
5032
5456
  activityState: activityState,
5033
5457
  editableFields: entry.evaluation?.editableFields
5034
5458
  }),
5035
- activityEval: t,
5036
5459
  instanceId: entry.instance._id,
5037
5460
  onOpen: () => onOpenActivity(t.activity.name)
5038
5461
  }, t.activity.name))
5039
5462
  });
5040
5463
  }
5041
5464
 
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
5465
  function instanceNotice(entry) {
5063
5466
  const {instance: instance, committed: committed, evaluation: evaluation} = entry, state = workflowEngine.terminalState(instance);
5064
5467
  if (state !== "in-flight")
@@ -5098,6 +5501,7 @@ const EDITOR_BY_KIND = {
5098
5501
  date: "scalar",
5099
5502
  datetime: "scalar",
5100
5503
  number: "scalar",
5504
+ progress: "scalar",
5101
5505
  string: "scalar",
5102
5506
  text: "scalar",
5103
5507
  url: "scalar"
@@ -5160,9 +5564,16 @@ function faceOf(args) {
5160
5564
  } : workflowEngine.isSingleDocRefEntry(entry) && entry.value !== null ? {
5161
5565
  kind: "docRef",
5162
5566
  gdr: entry.value
5567
+ } : entry._type === "progress" && entry.value !== null ? {
5568
+ kind: "progress",
5569
+ value: entry.value
5163
5570
  } : todoShaped && entry._type === "array" ? textFace(todoTextOf(entry)) : textFace(textOf(entry));
5164
5571
  }
5165
5572
 
5573
+ function progressFillPercent(face) {
5574
+ if (face.kind === "progress") return workflowComponents.clampProgress(face.value);
5575
+ }
5576
+
5166
5577
  function deriveFieldPills(args) {
5167
5578
  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
5579
  committed: committed,
@@ -5303,6 +5714,15 @@ function SetFace({face: face}) {
5303
5714
  })
5304
5715
  }) : face.kind === "assignees" ? /* @__PURE__ */ jsxRuntime.jsx(AssigneesFace, {
5305
5716
  assignees: face.assignees
5717
+ }) : face.kind === "progress" ?
5718
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5719
+ size: 1,
5720
+ style: {
5721
+ fontVariantNumeric: "tabular-nums",
5722
+ minWidth: "4ch",
5723
+ textAlign: "right"
5724
+ },
5725
+ children: workflowComponents.progressCaption(face.value)
5306
5726
  }) : /* @__PURE__ */ jsxRuntime.jsx(FaceText, {
5307
5727
  children: face.kind === "text" ? face.text : ""
5308
5728
  });
@@ -5356,15 +5776,21 @@ function AssigneesFace({assignees: assignees}) {
5356
5776
  });
5357
5777
  }
5358
5778
 
5359
- function usePillChrome() {
5360
- const {color: color} = ui.useTheme_v2();
5779
+ function usePillChrome(face) {
5780
+ 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 ? {} : {
5781
+ backgroundImage: `linear-gradient(${fill}, ${fill})`,
5782
+ backgroundRepeat: "no-repeat",
5783
+ backgroundSize: `${fillPercent}% 100%`,
5784
+ transition: "background-size 300ms ease"
5785
+ };
5361
5786
  return {
5362
5787
  mode: "bleed",
5363
5788
  padding: 2,
5364
5789
  radius: 6,
5365
5790
  style: {
5366
5791
  boxShadow: `inset 0 0 0 1px ${color.button.ghost.default.enabled.border}`,
5367
- maxWidth: "100%"
5792
+ maxWidth: "100%",
5793
+ ...progressFill
5368
5794
  }
5369
5795
  };
5370
5796
  }
@@ -5395,7 +5821,7 @@ function PillFaceContent({children: children}) {
5395
5821
  }
5396
5822
 
5397
5823
  function PillButton({children: children, pill: pill, hintDisabled: hintDisabled = !1, onClick: onClick}) {
5398
- const chrome = usePillChrome();
5824
+ const chrome = usePillChrome(pill.face);
5399
5825
  /* @__PURE__ */
5400
5826
  return jsxRuntime.jsx(PillHint, {
5401
5827
  disabled: hintDisabled,
@@ -5428,7 +5854,23 @@ function useFieldCommit({entry: entry, editability: editability}) {
5428
5854
  function PillPopover({pill: pill, content: content2, open: open, onToggle: onToggle, onDismiss: onDismiss}) {
5429
5855
  /* @__PURE__ */
5430
5856
  return jsxRuntime.jsx(DismissablePopover, {
5431
- content: content2,
5857
+ content: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
5858
+ gap: 1,
5859
+ children: [
5860
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
5861
+ padding: 3,
5862
+ paddingBottom: 0,
5863
+ style: {
5864
+ maxWidth: 360
5865
+ },
5866
+ children: /* @__PURE__ */ jsxRuntime.jsx(FieldHead, {
5867
+ label: pill.title,
5868
+ ...pill.description === void 0 ? {} : {
5869
+ description: pill.description
5870
+ }
5871
+ })
5872
+ }), content2 ]
5873
+ }),
5432
5874
  fallbackPlacements: [ "top-start" ],
5433
5875
  onDismiss: onDismiss,
5434
5876
  open: open,
@@ -5728,6 +6170,66 @@ function FieldPill({entry: entry, pill: pill, scope: scope}) {
5728
6170
  });
5729
6171
  }
5730
6172
 
6173
+ function Hairline({weight: weight, style: style}) {
6174
+ const {color: color} = ui.useTheme_v2();
6175
+ /* @__PURE__ */
6176
+ return jsxRuntime.jsx("div", {
6177
+ style: {
6178
+ borderTop: `${weight}px solid ${color.border}`,
6179
+ ...style
6180
+ }
6181
+ });
6182
+ }
6183
+
6184
+ function CollapsibleBand({header: header, children: children, background: background = !1, defaultOpen: defaultOpen = !0, sticky: sticky = !1, title: title}) {
6185
+ const [open, setOpen] = react.useState(defaultOpen), verb = open ? "Collapse" : "Expand";
6186
+ /* @__PURE__ */
6187
+ return jsxRuntime.jsxs(ui.Stack, {
6188
+ gap: 1,
6189
+ children: [
6190
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
6191
+ onDoubleClick: event => {
6192
+ event.target instanceof Element && event.target.closest("a, button") || setOpen(v => !v);
6193
+ },
6194
+ padding: 1,
6195
+ radius: 3,
6196
+ style: {
6197
+ userSelect: "none",
6198
+ ...sticky ? {
6199
+ position: "sticky",
6200
+ top: 0,
6201
+ zIndex: 1
6202
+ } : {}
6203
+ },
6204
+ tone: background ? "transparent" : "inherit",
6205
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
6206
+ align: "center",
6207
+ children: [
6208
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
6209
+ "aria-label": `${verb} ${title}`,
6210
+ fontSize: 1,
6211
+ icon: open ? ChevronDown.ChevronDownIcon : ChevronRight.ChevronRightIcon,
6212
+ mode: "bleed",
6213
+ onClick: () => setOpen(v => !v),
6214
+ padding: 2
6215
+ }), header ]
6216
+ })
6217
+ }), open ? children : null ]
6218
+ });
6219
+ }
6220
+
6221
+ function TrailingHairline() {
6222
+ /* @__PURE__ */
6223
+ return jsxRuntime.jsx(ui.Box, {
6224
+ flex: 1,
6225
+ paddingLeft: 1,
6226
+ paddingRight: 1,
6227
+ children: /* @__PURE__ */ jsxRuntime.jsx(Hairline, {
6228
+ weight: .5
6229
+ })
6230
+ });
6231
+ }
6232
+
5731
6233
  const HISTORY_KIND_GROUPS = {
5732
6234
  Stages: [ "stageEntered", "stageExited", "transitionFired", "aborted" ],
5733
6235
  Activities: [ "activityStatusChanged" ],
@@ -6050,8 +6552,8 @@ function InstanceDebugMenu({entry: entry}) {
6050
6552
  });
6051
6553
  }
6052
6554
 
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, {
6555
+ function InstanceAccordion({entry: entry, children: children, defaultOpen: defaultOpen}) {
6556
+ const definition = useDefinition(entry), {instance: instance, ready: ready} = entry, settled = !isLiveEntry(entry), title = instanceTitle(instance, definition), badgeTrim = useBadgeCapTrim(), status = /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
6055
6557
  align: "center",
6056
6558
  gap: 2,
6057
6559
  style: badgeTrim,
@@ -6061,60 +6563,46 @@ function InstanceAccordion({entry: entry, children: children}) {
6061
6563
  }), settled ? null : /* @__PURE__ */ jsxRuntime.jsx(OlderDefinitionBadge, {
6062
6564
  instance: instance
6063
6565
  }), !ready && entry.invalid === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
6064
- muted: !0
6566
+ muted: !0,
6567
+ size: 1
6065
6568
  }) : null ]
6066
6569
  });
6067
6570
  /* @__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, {
6571
+ return jsxRuntime.jsx(CollapsibleBand, {
6572
+ defaultOpen: defaultOpen ?? !settled,
6573
+ title: title,
6574
+ header: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
6575
+ children: [
6576
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
6078
6577
  align: "center",
6079
- gap: 1,
6578
+ gap: 2,
6579
+ paddingLeft: 2,
6580
+ style: {
6581
+ minWidth: 0
6582
+ },
6080
6583
  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
- })
6106
- }),
6107
- /* @__PURE__ */ jsxRuntime.jsx(InstanceDebugMenu, {
6108
- entry: entry
6109
- }) ]
6110
- })
6111
- }), open ?
6112
- /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
6584
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
6585
+ size: 1,
6586
+ style: {
6587
+ minWidth: 0
6588
+ },
6589
+ textOverflow: "ellipsis",
6590
+ weight: "medium",
6591
+ children: title
6592
+ }), status ]
6593
+ }),
6594
+ /* @__PURE__ */ jsxRuntime.jsx(TrailingHairline, {}),
6595
+ /* @__PURE__ */ jsxRuntime.jsx(InstanceDebugMenu, {
6596
+ entry: entry
6597
+ }) ]
6598
+ }),
6599
+ sticky: !0,
6600
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
6113
6601
  gap: 3,
6114
6602
  paddingBottom: 2,
6115
6603
  paddingLeft: 5,
6116
6604
  children: children
6117
- }) : null ]
6605
+ })
6118
6606
  });
6119
6607
  }
6120
6608
 
@@ -6370,18 +6858,6 @@ const NODE_ICON = {
6370
6858
  done: CheckmarkCircle.CheckmarkCircleIcon
6371
6859
  };
6372
6860
 
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
6861
  function WorkflowSpine({definition: definition, evaluation: evaluation, instance: instance}) {
6386
6862
  const nodes = deriveSpine({
6387
6863
  definition: definition,
@@ -6400,7 +6876,8 @@ function WorkflowSpine({definition: definition, evaluation: evaluation, instance
6400
6876
  style: {
6401
6877
  transform: "translateY(-0.5px)",
6402
6878
  width: connectorWidth
6403
- }
6879
+ },
6880
+ weight: 1
6404
6881
  }) : null,
6405
6882
  /* @__PURE__ */ jsxRuntime.jsx(SpineNodeView, {
6406
6883
  definition: definition,
@@ -6505,7 +6982,9 @@ function StageOverviewCard({overview: overview}) {
6505
6982
  })
6506
6983
  }), hasLiveZone ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
6507
6984
  children: [
6508
- /* @__PURE__ */ jsxRuntime.jsx(Hairline, {}),
6985
+ /* @__PURE__ */ jsxRuntime.jsx(Hairline, {
6986
+ weight: 1
6987
+ }),
6509
6988
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
6510
6989
  padding: 2,
6511
6990
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
@@ -6558,31 +7037,32 @@ function AdvanceCard({advance: advance}) {
6558
7037
  }
6559
7038
 
6560
7039
  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, {
7040
+ const face = stampFace(stamp.kind === "entered" ? "Entered" : "Completed", stamp.at);
7041
+ return face.detail === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
6563
7042
  muted: !0,
6564
7043
  size: 1,
6565
- children: [ verb, " ", absolute ]
7044
+ children: face.lead
6566
7045
  }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
6567
7046
  gap: 3,
6568
7047
  children: [
6569
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
7048
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
6570
7049
  muted: !0,
6571
7050
  size: 1,
6572
- children: [ verb, " ", ago ]
7051
+ children: face.lead
6573
7052
  }),
6574
7053
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
6575
7054
  muted: !0,
6576
7055
  size: 1,
6577
- children: absolute
7056
+ children: face.detail
6578
7057
  }) ]
6579
7058
  });
6580
7059
  }
6581
7060
 
6582
- function WorkflowInstanceSection({entry: entry, onOpenActivity: onOpenActivity}) {
7061
+ function WorkflowInstanceSection({defaultOpen: defaultOpen, entry: entry, onOpenActivity: onOpenActivity}) {
6583
7062
  const definition = useDefinition(entry), {instance: instance} = entry;
6584
7063
  /* @__PURE__ */
6585
7064
  return jsxRuntime.jsxs(InstanceAccordion, {
7065
+ defaultOpen: defaultOpen,
6586
7066
  entry: entry,
6587
7067
  children: [ instance.ancestors.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(PartOfBand, {
6588
7068
  instance: instance
@@ -6642,7 +7122,7 @@ function StageSection({entry: entry, onOpenActivity: onOpenActivity}) {
6642
7122
  });
6643
7123
  }
6644
7124
 
6645
- const WorkflowsView = props => {
7125
+ const VIEW_TAB_CODEC = workflowsTabCodec([ "overview", "for-me" ]), WorkflowsView = props => {
6646
7126
  const {documentId: documentId, schemaType: schemaType} = props, {isDocResolved: isDocResolved, mappings: mappings, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {entries: entries} = useWorkflowsForDocument(documentId), resolved = isDocResolved(documentId), docTypeMappings = mappingsForDocType(mappings, schemaType.name);
6647
7127
  if (entries.length === 0) return discoveryInvalid !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
6648
7128
  padding: 4,
@@ -6703,14 +7183,8 @@ const GROUP_GAP = 1;
6703
7183
  function useWorkflowsTab() {
6704
7184
  const {params: params, setParams: setParams} = structure.usePaneRouter();
6705
7185
  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
- }
7186
+ view: VIEW_TAB_CODEC.read(params),
7187
+ setView: next => setParams(VIEW_TAB_CODEC.write(params, next))
6714
7188
  };
6715
7189
  }
6716
7190
 
@@ -6729,7 +7203,8 @@ function useOpenActivity(entries) {
6729
7203
  }
6730
7204
 
6731
7205
  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, {
7206
+ const identity = useAssignmentIdentity(), panelId = react.useId(), {params: params} = structure.usePaneRouter(), {view: view, setView: setView} = useWorkflowsTab(), {openActivity: openActivity, setOpenActivity: setOpenActivity, dialogEntry: dialogEntry} = useOpenActivity(entries), active = entries.filter(isLiveEntry), finished = entries.filter(e => !isLiveEntry(e)), forMe = forMeWorkOf(active, identity), [landing] = react.useState(() => focusedLanding(params?.[WORKFLOWS_FOCUS_PARAM], entries.map(e => e.instance._id))), assignedWorkUnknown = active.some(e => e.invalid !== void 0), sectionFor = e => /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceSection, {
7207
+ defaultOpen: landingDefaultOpen(landing, e.instance._id),
6733
7208
  entry: e,
6734
7209
  onOpenActivity: activityName => setOpenActivity({
6735
7210
  instanceId: e.instance._id,
@@ -7046,20 +7521,51 @@ function indexDiscoveredInstances(args) {
7046
7521
  };
7047
7522
  }
7048
7523
 
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
- });
7524
+ function sameInstance(a, b) {
7525
+ return a === b || a._id === b._id && a._rev === b._rev;
7526
+ }
7527
+
7528
+ function sameEntry(a, b) {
7529
+ 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;
7530
+ }
7531
+
7532
+ function reconciled(args) {
7533
+ const {entries: entries, previous: previous, same: same} = args, map = /* @__PURE__ */ new Map;
7534
+ let allReused = !0;
7535
+ for (const [key, next] of entries) {
7536
+ const prev = previous?.get(key), reusable = prev !== void 0 && same(prev, next);
7537
+ reusable || (allReused = !1), map.set(key, reusable ? prev : next);
7061
7538
  }
7062
- return map;
7539
+ return previous !== void 0 && allReused && previous.size === map.size ? previous : map;
7540
+ }
7541
+
7542
+ function entryOf(inst, report) {
7543
+ return {
7544
+ instance: report?.evaluation?.instance ?? inst,
7545
+ committed: inst,
7546
+ evaluation: report?.evaluation,
7547
+ ready: report?.ready ?? !1,
7548
+ invalid: report?.invalid,
7549
+ guards: report?.guards
7550
+ };
7551
+ }
7552
+
7553
+ function mergeEntries(args) {
7554
+ const {seeded: seeded, discovered: discovered, reports: reports, previous: previous} = args, merged = new Map([ ...seeded, ...discovered ]);
7555
+ return reconciled({
7556
+ entries: [ ...merged ].map(([instanceId, inst]) => [ instanceId, entryOf(inst, reports.get(instanceId)) ]),
7557
+ previous: previous,
7558
+ same: sameEntry
7559
+ });
7560
+ }
7561
+
7562
+ function deriveByDocument(args) {
7563
+ const {docIndex: docIndex, byInstance: byInstance, previous: previous} = args;
7564
+ return reconciled({
7565
+ entries: [ ...docIndex ].map(([docId, instanceIds]) => [ docId, instanceIds.map(id => byInstance.get(id)).filter(entry => entry !== void 0) ]),
7566
+ previous: previous,
7567
+ same: (a, b) => a.length === b.length && b.every((entry, i) => entry === a[i])
7568
+ });
7063
7569
  }
7064
7570
 
7065
7571
  const REPORT_KEYS = [ "evaluation", "ready", "invalid", "guards", "fireAction", "editField", "previewField", "discardFieldPreview" ];
@@ -7080,6 +7586,57 @@ function nextReports(prev, update) {
7080
7586
  return next.set(instanceId, report), next;
7081
7587
  }
7082
7588
 
7589
+ const REPORT_FLUSH_MS = 50, EMPTY_COMMITTED = {
7590
+ seeded: /* @__PURE__ */ new Map,
7591
+ discovered: /* @__PURE__ */ new Map,
7592
+ docIndex: /* @__PURE__ */ new Map
7593
+ };
7594
+
7595
+ function createEntriesStore() {
7596
+ let committed = EMPTY_COMMITTED, reports = /* @__PURE__ */ new Map, byInstance = /* @__PURE__ */ new Map, byDocument = /* @__PURE__ */ new Map;
7597
+ const listeners = /* @__PURE__ */ new Set;
7598
+ let flushTimer;
7599
+ const recompute = () => {
7600
+ flushTimer !== void 0 && (clearTimeout(flushTimer), flushTimer = void 0);
7601
+ const nextByInstance = mergeEntries({
7602
+ ...committed,
7603
+ reports: reports,
7604
+ previous: byInstance
7605
+ }), nextByDocument = deriveByDocument({
7606
+ docIndex: committed.docIndex,
7607
+ byInstance: nextByInstance,
7608
+ previous: byDocument
7609
+ });
7610
+ if (!(nextByInstance === byInstance && nextByDocument === byDocument)) {
7611
+ byInstance = nextByInstance, byDocument = nextByDocument;
7612
+ for (const listener of [ ...listeners ]) listener();
7613
+ }
7614
+ };
7615
+ return {
7616
+ setCommitted(inputs) {
7617
+ committed = inputs, recompute();
7618
+ },
7619
+ report(instanceId, report) {
7620
+ const next = nextReports(reports, {
7621
+ instanceId: instanceId,
7622
+ report: report
7623
+ });
7624
+ next !== reports && (reports = next, flushTimer ??= setTimeout(recompute, REPORT_FLUSH_MS));
7625
+ },
7626
+ getReport: instanceId => reports.get(instanceId),
7627
+ getInstance: instanceId => byInstance.get(instanceId),
7628
+ getDocument: docId => byDocument.get(docId) ?? EMPTY_ENTRIES,
7629
+ subscribe(listener) {
7630
+ return listeners.add(listener), () => {
7631
+ listeners.delete(listener);
7632
+ };
7633
+ },
7634
+ dispose() {
7635
+ flushTimer !== void 0 && clearTimeout(flushTimer), flushTimer = void 0, listeners.clear();
7636
+ }
7637
+ };
7638
+ }
7639
+
7083
7640
  const notReady = (..._args) => Promise.reject(new Error("workflow session not ready yet"));
7084
7641
 
7085
7642
  function workspaceParts(config) {
@@ -7130,10 +7687,10 @@ function InstanceEvaluator(props) {
7130
7687
  }
7131
7688
 
7132
7689
  function DiscoverySubscription(props) {
7133
- const {engine: engine, registered: registered, filter: filter, onSnapshot: onSnapshot} = props, {instances: instances, invalid: invalid} = workflowStudio.useWorkflowInstances({
7690
+ const {engine: engine, registered: registered, filter: filter, onSnapshot: onSnapshot} = props, {instances: instances, unreadable: unreadable} = workflowStudio.useWorkflowInstances({
7134
7691
  engine: engine,
7135
7692
  filter: filter
7136
- });
7693
+ }), invalid = unreadable[0];
7137
7694
  return react.useEffect(() => {
7138
7695
  onSnapshot({
7139
7696
  instances: instances,
@@ -7177,14 +7734,11 @@ function WorkflowProvider(props) {
7177
7734
  id: `${projectId}.${workflowDataset}`
7178
7735
  }), {ids: registered, acquire: register, release: unregister} = workflowReact.useRefcountedIds(), {ids: requested, acquire: requestInstance, release: releaseInstance} = workflowReact.useRefcountedIds(), [seeded, setSeeded] = react.useState(/* @__PURE__ */ new Map), seedInstance = react.useCallback(instance => {
7179
7736
  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 => {
7737
+ }, []), [startRequest2, setStartRequest] = react.useState(void 0), openStartDialog = react.useCallback(r => setStartRequest(r), []), closeStartDialog = react.useCallback(() => setStartRequest(void 0), []), [entriesStore] = react.useState(createEntriesStore);
7738
+ react.useEffect(() => () => entriesStore.dispose(), [ entriesStore ]);
7739
+ const onReport = react.useCallback((instanceId, report) => {
7740
+ entriesStore.report(instanceId, report);
7741
+ }, [ entriesStore ]), drainRef = react.useRef(void 0), drainCommitted = react.useCallback(async instanceId => {
7188
7742
  try {
7189
7743
  await (drainRef.current?.(instanceId));
7190
7744
  } catch (err) {
@@ -7193,7 +7747,7 @@ function WorkflowProvider(props) {
7193
7747
  });
7194
7748
  }
7195
7749
  }, []), fireActionFor = react.useCallback((instanceId, args) => {
7196
- const report = reportsRef.current.get(instanceId);
7750
+ const report = entriesStore.getReport(instanceId);
7197
7751
  return report ? report.fireAction({
7198
7752
  activity: args.activity,
7199
7753
  action: args.action,
@@ -7201,15 +7755,15 @@ function WorkflowProvider(props) {
7201
7755
  params: args.params
7202
7756
  } : {}
7203
7757
  }).then(async res => (await drainCommitted(instanceId), res)) : notReady(args);
7204
- }, [ drainCommitted ]), editFieldFor = react.useCallback((instanceId, args) => {
7205
- const report = reportsRef.current.get(instanceId);
7758
+ }, [ entriesStore, drainCommitted ]), editFieldFor = react.useCallback((instanceId, args) => {
7759
+ const report = entriesStore.getReport(instanceId);
7206
7760
  return report ? report.editField(args).then(async res => (await drainCommitted(instanceId),
7207
7761
  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 => {
7762
+ }, [ entriesStore, drainCommitted ]), previewFieldFor = react.useCallback((instanceId, args) => {
7763
+ entriesStore.getReport(instanceId)?.previewField(args);
7764
+ }, [ entriesStore ]), discardFieldPreviewFor = react.useCallback((instanceId, target) => {
7765
+ entriesStore.getReport(instanceId)?.discardFieldPreview(target);
7766
+ }, [ entriesStore ]), drainEffectsFor = react.useCallback(async instanceId => {
7213
7767
  await engine.drainEffects({
7214
7768
  instanceId: instanceId
7215
7769
  });
@@ -7243,23 +7797,14 @@ function WorkflowProvider(props) {
7243
7797
  const kept = [ ...prev ].filter(([id]) => !discovered.has(id));
7244
7798
  return kept.length === prev.size ? prev : new Map(kept);
7245
7799
  });
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({
7800
+ }, [ discovered ]), react.useLayoutEffect(() => {
7801
+ entriesStore.setCommitted({
7802
+ seeded: seeded,
7803
+ discovered: discovered,
7804
+ docIndex: docIndex
7805
+ });
7806
+ }, [ entriesStore, seeded, discovered, docIndex ]);
7807
+ const isDocResolved = react.useCallback(docId => resolvedDocs.has(docId), [ resolvedDocs ]), schemaContentTypes = react.useMemo(() => new Set(contentDocumentTypes(schema)), [ schema ]), {issues: issues, starts: starts, fields: fields, latestVersions: latestVersions, autoStartByType: autoStartByType, autoStartDefinitions: autoStartDefinitions} = useDeployedDefinitions({
7263
7808
  engine: engine,
7264
7809
  mappings: config.mappings,
7265
7810
  contentResource: binding.contentResource,
@@ -7272,8 +7817,7 @@ function WorkflowProvider(props) {
7272
7817
  register: register,
7273
7818
  unregister: unregister,
7274
7819
  isDocResolved: isDocResolved,
7275
- byDocument: byDocument,
7276
- byInstance: byInstance,
7820
+ entries: entriesStore,
7277
7821
  requestInstance: requestInstance,
7278
7822
  releaseInstance: releaseInstance,
7279
7823
  seedInstance: seedInstance,
@@ -7298,7 +7842,7 @@ function WorkflowProvider(props) {
7298
7842
  effectHandlers: engine.effectHandlers,
7299
7843
  autoStartByType: autoStartByType,
7300
7844
  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 ]);
7845
+ }), [ register, unregister, isDocResolved, entriesStore, requestInstance, releaseInstance, seedInstance, openStartDialog, closeStartDialog, startRequest2, loading, discoveryInvalid, fireActionFor, editFieldFor, previewFieldFor, discardFieldPreviewFor, drainEffectsFor, completeEffectFor, engine, binding, config.mappings, issues, starts, latestVersions, autoStartByType, autoStartDefinitions ]);
7302
7846
  /* @__PURE__ */
7303
7847
  return jsxRuntime.jsxs(WorkflowContext.Provider, {
7304
7848
  value: value,
@@ -8501,19 +9045,32 @@ function TodoGlance({row: row}) {
8501
9045
  });
8502
9046
  }
8503
9047
 
9048
+ function finishedLinesOf(entries) {
9049
+ const activeTypes = new Set(entries.filter(isLiveEntry).map(entry => entry.instance.definition)), settled = entries.flatMap(entry => {
9050
+ const stamp = entry.instance.completedAt;
9051
+ return stamp === void 0 || activeTypes.has(entry.instance.definition) ? [] : [ {
9052
+ entry: entry,
9053
+ at: Date.parse(stamp)
9054
+ } ];
9055
+ }), latestByType = /* @__PURE__ */ new Map;
9056
+ for (const candidate of settled) {
9057
+ const best = latestByType.get(candidate.entry.instance.definition);
9058
+ (best === void 0 || candidate.at > best.at) && latestByType.set(candidate.entry.instance.definition, candidate);
9059
+ }
9060
+ return [ ...latestByType.values() ].sort((a, b) => b.at - a.at).map(({entry: entry}) => entry);
9061
+ }
9062
+
8504
9063
  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);
9064
+ const {mappings: mappings} = props, {isDocResolved: isDocResolved, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {docId: docId, entries: entries} = useWorkflowsForDocument(props.value?._id ?? null), {params: params, setParams: setParams} = structure.usePaneRouter(), openWorkflowsView = focusId => setParams(focusedViewParams(params, focusId)), active = entries.filter(isLiveEntry), finished = finishedLinesOf(entries), invalid = surfacedInvalid(discoveryInvalid, entries), resolved = docId !== void 0 && isDocResolved(docId);
8509
9065
  /* @__PURE__ */
8510
9066
  return jsxRuntime.jsxs(ui.Stack, {
8511
9067
  gap: 4,
8512
9068
  children: [ invalid !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(InvalidDocNotice, {
8513
9069
  invalid: invalid
8514
- }) : null, active.length > 0 || resolved ? /* @__PURE__ */ jsxRuntime.jsx(StripLines, {
9070
+ }) : null, active.length > 0 || finished.length > 0 || resolved ? /* @__PURE__ */ jsxRuntime.jsx(StripLines, {
8515
9071
  active: active,
8516
9072
  docId: docId,
9073
+ finished: finished,
8517
9074
  initialValue: props.value ?? null,
8518
9075
  mappings: mappings,
8519
9076
  onOpen: openWorkflowsView,
@@ -8522,32 +9079,17 @@ function WorkflowFormStrip(props) {
8522
9079
  });
8523
9080
  }
8524
9081
 
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;
9082
+ function StripLines({active: active, docId: docId, finished: finished, initialValue: initialValue, mappings: mappings, onOpen: onOpen, resolved: resolved}) {
8527
9083
  /* @__PURE__ */
8528
9084
  return jsxRuntime.jsxs(ui.Stack, {
8529
9085
  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, {
9086
+ children: [ active.map(entry => /* @__PURE__ */ jsxRuntime.jsx(InstanceLine, {
9087
+ entry: entry,
9088
+ onOpen: () => onOpen(entry.instance._id)
9089
+ }, entry.instance._id)), finished.map(entry => /* @__PURE__ */ jsxRuntime.jsx(FinishedLine, {
9090
+ entry: entry,
9091
+ onOpen: () => onOpen(entry.instance._id)
9092
+ }, entry.instance._id)), resolved ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
8551
9093
  children: /* @__PURE__ */ jsxRuntime.jsx(StartWorkflowControl, {
8552
9094
  docId: docId,
8553
9095
  initialValue: initialValue,
@@ -8601,7 +9143,7 @@ function InstanceLine({entry: entry, onOpen: onOpen}) {
8601
9143
  /* @__PURE__ */
8602
9144
  return jsxRuntime.jsxs(StripLine, {
8603
9145
  aside: unprimed ? void 0 : /* @__PURE__ */ jsxRuntime.jsx(TaskCountSpinner, {
8604
- entries: [ entry ]
9146
+ entry: entry
8605
9147
  }),
8606
9148
  hint: lineHint(identity, forMeWorkOf([ entry ], identity)),
8607
9149
  onOpen: onOpen,
@@ -8620,34 +9162,69 @@ function InstanceLine({entry: entry, onOpen: onOpen}) {
8620
9162
  title: stageTitle(definition, entry.instance.currentStage)
8621
9163
  }),
8622
9164
  /* @__PURE__ */ jsxRuntime.jsx(TaskCountText, {
8623
- entries: [ entry ]
9165
+ entry: entry
8624
9166
  }) ]
8625
9167
  }) ]
8626
9168
  });
8627
9169
  }
8628
9170
 
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") ? {
9171
+ function FinishedLine({entry: entry, onOpen: onOpen}) {
9172
+ const definition = useDefinition(entry), badgeTrim = useBadgeCapTrim(), {instance: instance} = entry;
9173
+ /* @__PURE__ */
9174
+ return jsxRuntime.jsxs(StripLine, {
9175
+ hint: finishedHint(instance),
9176
+ onOpen: onOpen,
9177
+ children: [
9178
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
9179
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
9180
+ size: 1,
9181
+ weight: "medium",
9182
+ children: instanceTitle(instance, definition)
9183
+ })
9184
+ }), workflowEngine.terminalState(instance) === "aborted" ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
9185
+ style: badgeTrim,
9186
+ children: /* @__PURE__ */ jsxRuntime.jsx(AbortedBadge, {
9187
+ instance: instance
9188
+ })
9189
+ }) : /* @__PURE__ */ jsxRuntime.jsx(StageFace, {
9190
+ completed: !0,
9191
+ title: stageTitle(definition, instance.currentStage)
9192
+ }) ]
9193
+ });
9194
+ }
9195
+
9196
+ function finishedHint(instance) {
9197
+ const verb = workflowEngine.terminalState(instance) === "aborted" ? "Aborted" : "Completed", face = stampFace(verb, instance.completedAt ?? "");
9198
+ return {
9199
+ text: face.lead,
9200
+ ...face.detail === void 0 ? {} : {
9201
+ description: face.detail
9202
+ }
9203
+ };
9204
+ }
9205
+
9206
+ function useTaskCountState(entry) {
9207
+ const identity = useAssignmentIdentity(), freshness = entry.invalid === void 0 ? evaluationFreshness({
9208
+ committed: entry.committed,
9209
+ evaluation: entry.evaluation
9210
+ }) : void 0;
9211
+ return !identity || freshness === "pending" ? {
8635
9212
  kind: "pending"
8636
- } : freshness.includes("stale") ? {
9213
+ } : freshness === "stale" ? {
8637
9214
  kind: "stale"
8638
9215
  } : {
8639
9216
  kind: "ready",
8640
- tasks: entries.reduce((n, e) => n + assignedTaskCount({
8641
- instance: e.instance,
8642
- committed: e.committed,
8643
- evaluation: e.evaluation,
9217
+ tasks: assignedTaskCount({
9218
+ instance: entry.instance,
9219
+ committed: entry.committed,
9220
+ evaluation: entry.evaluation,
8644
9221
  who: identity
8645
- }), 0)
9222
+ })
8646
9223
  };
8647
9224
  }
8648
9225
 
8649
- function TaskCountText({entries: entries}) {
8650
- const state = useTaskCountState(entries);
9226
+ function TaskCountText({entry: entry}) {
9227
+ const state = useTaskCountState(entry);
8651
9228
  return state.kind === "pending" || state.kind === "ready" && state.tasks === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
8652
9229
  muted: !0,
8653
9230
  size: 1,
@@ -8655,8 +9232,8 @@ function TaskCountText({entries: entries}) {
8655
9232
  });
8656
9233
  }
8657
9234
 
8658
- function TaskCountSpinner({entries: entries}) {
8659
- return useTaskCountState(entries).kind !== "pending" ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
9235
+ function TaskCountSpinner({entry: entry}) {
9236
+ return useTaskCountState(entry).kind !== "pending" ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
8660
9237
  paddingLeft: 1,
8661
9238
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
8662
9239
  muted: !0,
@@ -8837,7 +9414,11 @@ exports.ActivitiesList = ActivitiesList;
8837
9414
 
8838
9415
  exports.ActivityDetailDialog = ActivityDetailDialog;
8839
9416
 
8840
- exports.ActivityStatusIcon = ActivityStatusIcon;
9417
+ exports.ActivityRow = ActivityRow;
9418
+
9419
+ exports.BreadcrumbTail = BreadcrumbTail;
9420
+
9421
+ exports.CollapsibleBand = CollapsibleBand;
8841
9422
 
8842
9423
  exports.DismissablePopover = DismissablePopover;
8843
9424
 
@@ -8847,34 +9428,50 @@ exports.DocRefFace = DocRefFace;
8847
9428
 
8848
9429
  exports.GroupHeading = GroupHeading;
8849
9430
 
8850
- exports.HintedMenuButton = HintedMenuButton;
8851
-
8852
9431
  exports.HoverHint = HoverHint;
8853
9432
 
8854
9433
  exports.InvalidDocNotice = InvalidDocNotice;
8855
9434
 
9435
+ exports.LinkChip = LinkChip;
9436
+
8856
9437
  exports.LoadingRow = LoadingRow;
8857
9438
 
8858
9439
  exports.StageChip = StageChip;
8859
9440
 
8860
- exports.StageGlyph = StageGlyph;
9441
+ exports.StageFace = StageFace;
8861
9442
 
8862
9443
  exports.StaleLock = StaleLock;
8863
9444
 
9445
+ exports.TabSwitch = TabSwitch;
9446
+
8864
9447
  exports.TodoItemsList = TodoItemsList;
8865
9448
 
8866
- exports.UserAvatarGroup = UserAvatarGroup;
9449
+ exports.TrailingHairline = TrailingHairline;
9450
+
9451
+ exports.UnreadableDocsNote = UnreadableDocsNote;
8867
9452
 
8868
9453
  exports.WorkflowStagePreview = WorkflowStagePreview;
8869
9454
 
9455
+ exports.activityRowProps = activityRowProps;
9456
+
8870
9457
  exports.assigneeUserIdsOf = assigneeUserIdsOf;
8871
9458
 
9459
+ exports.committedRowFace = committedRowFace;
9460
+
9461
+ exports.dateControlKind = dateControlKind;
9462
+
9463
+ exports.dateControlValue = dateControlValue;
9464
+
8872
9465
  exports.definitionSnapshotOf = definitionSnapshotOf;
8873
9466
 
8874
9467
  exports.describeError = describeError;
8875
9468
 
9469
+ exports.editTargetOf = editTargetOf;
9470
+
8876
9471
  exports.findActivity = findActivity;
8877
9472
 
9473
+ exports.findActivityNode = findActivityNode;
9474
+
8878
9475
  exports.formatDate = formatDate;
8879
9476
 
8880
9477
  exports.gdrLocality = gdrLocality;
@@ -8895,22 +9492,34 @@ exports.mappingForDocType = mappingForDocType;
8895
9492
 
8896
9493
  exports.openActivityGone = openActivityGone;
8897
9494
 
9495
+ exports.parseDateFieldValue = parseDateFieldValue;
9496
+
8898
9497
  exports.parseStoredDateValue = parseStoredDateValue;
8899
9498
 
8900
9499
  exports.readDeployedDefinitions = readDeployedDefinitions;
8901
9500
 
9501
+ exports.rowAssignControlState = rowAssignControlState;
9502
+
9503
+ exports.rowDateControlState = rowDateControlState;
9504
+
9505
+ exports.serializeDateFieldValue = serializeDateFieldValue;
9506
+
8902
9507
  exports.stageTitle = stageTitle;
8903
9508
 
8904
9509
  exports.stateByActivity = stateByActivity;
8905
9510
 
9511
+ exports.stopRowClick = stopRowClick;
9512
+
9513
+ exports.stopRowMouseDown = stopRowMouseDown;
9514
+
8906
9515
  exports.useAssignmentIdentity = useAssignmentIdentity;
8907
9516
 
8908
9517
  exports.useDefinition = useDefinition;
8909
9518
 
8910
- exports.useDocLink = useDocLink;
8911
-
8912
9519
  exports.useProjectMembers = useProjectMembers;
8913
9520
 
9521
+ exports.useSaveField = useSaveField;
9522
+
8914
9523
  exports.useUserDisplays = useUserDisplays;
8915
9524
 
8916
9525
  exports.useWorkflowContext = useWorkflowContext;
@@ -8923,4 +9532,6 @@ exports.workflowDocTypes = workflowDocTypes;
8923
9532
 
8924
9533
  exports.workflowStudioPlugin = workflowStudioPlugin;
8925
9534
 
9535
+ exports.workflowsTabCodec = workflowsTabCodec;
9536
+
8926
9537
  exports.workflowsView = workflowsView;