@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.
@@ -4,9 +4,9 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
4
4
 
5
5
  import { TimelineIcon } from "@sanity/icons/Timeline";
6
6
 
7
- import { Tooltip, Box, Stack, Text, useClickOutsideEvent, Popover, Flex, TextInput, Button, Select, Checkbox, TextSkeleton, Autocomplete, Card, TextArea, useToast, Dialog, MenuButton, Menu, MenuItem, useTheme_v2, Switch, Code, Badge, TabList, Tab, Spinner, TabPanel, MenuDivider } from "@sanity/ui";
7
+ import { Tooltip, Box, Stack, Text, useClickOutsideEvent, Popover, Flex, TextInput, Button, Select, Checkbox, Autocomplete, Card, TextArea, useToast, Dialog, Switch, TextSkeleton, Code, Spinner, useTheme_v2, Badge, MenuButton, Menu, MenuItem, useElementSize, TabList, Tab, TabPanel, MenuDivider } from "@sanity/ui";
8
8
 
9
- import { createContext, useContext, useEffect, useMemo, useRef, useState, useCallback, useId, Fragment as Fragment$1, Component, lazy, Suspense } from "react";
9
+ import { createContext, useContext, useEffect, useSyncExternalStore, useCallback, useMemo, useRef, useState, Fragment as Fragment$1, createElement, isValidElement, useId, useLayoutEffect, Component, lazy, Suspense } from "react";
10
10
 
11
11
  import { usePaneRouter } from "sanity/structure";
12
12
 
@@ -16,11 +16,11 @@ import { getPublishedId } from "@sanity/client/csm";
16
16
 
17
17
  import { useStudioProjectUsers, useStudioObserver, useWorkflowEngine, useWorkflowInstances, useWorkflowSession } from "@sanity/workflow-studio";
18
18
 
19
- import { useCurrentUser, useValuePreview, SanityDefaultPreview, Preview, useClient, useSchema, useWorkspace, TextWithTone, usePerspective, useIsReleaseActive, useActiveReleases, useTemplates, useInitialValueResolverContext, resolveInitialValue, definePlugin, isObjectInputProps } from "sanity";
19
+ import { useCurrentUser, SanityDefaultPreview, Preview, useValuePreview, useClient, useSchema, useWorkspace, TextWithTone, usePerspective, useIsReleaseActive, useActiveReleases, useTemplates, useInitialValueResolverContext, resolveInitialValue, definePlugin, isObjectInputProps } from "sanity";
20
20
 
21
21
  import { CalendarIcon } from "@sanity/icons/Calendar";
22
22
 
23
- import { ClearableDatePicker, MemberPicker as MemberPicker$1, AssigneePicker as AssigneePicker$1, MemberAvatar, AssigneeBadges as AssigneeBadges$1, AssigneeBadge as AssigneeBadge$1, MemberAvatarGroup, DatePicker, roleListLabel } from "@sanity/workflow-components";
23
+ import { ClearableDatePicker, MemberPicker as MemberPicker$1, AssigneePicker as AssigneePicker$1, MemberAvatar, AssigneeBadges as AssigneeBadges$1, AssigneeBadge as AssigneeBadge$1, ProgressBar, MemberAvatarGroup, clampProgress, progressCaption, DatePicker, roleListLabel } from "@sanity/workflow-components";
24
24
 
25
25
  import { UserIcon } from "@sanity/icons/User";
26
26
 
@@ -28,8 +28,6 @@ import { CloseIcon } from "@sanity/icons/Close";
28
28
 
29
29
  import { SearchIcon } from "@sanity/icons/Search";
30
30
 
31
- import { ChevronDownIcon } from "@sanity/icons/ChevronDown";
32
-
33
31
  import { ChevronRightIcon } from "@sanity/icons/ChevronRight";
34
32
 
35
33
  import { CheckmarkIcon } from "@sanity/icons/Checkmark";
@@ -40,6 +38,8 @@ import { randomKey } from "@sanity/util/content";
40
38
 
41
39
  import { InfoOutlineIcon } from "@sanity/icons/InfoOutline";
42
40
 
41
+ import { DocumentIcon } from "@sanity/icons/Document";
42
+
43
43
  import { WarningOutlineIcon } from "@sanity/icons/WarningOutline";
44
44
 
45
45
  import { IntentLink, useRouter, route } from "sanity/router";
@@ -58,9 +58,9 @@ import { AddIcon } from "@sanity/icons/Add";
58
58
 
59
59
  import { BoltIcon } from "@sanity/icons/Bolt";
60
60
 
61
- import { CheckmarkCircleIcon } from "@sanity/icons/CheckmarkCircle";
61
+ import { ChevronDownIcon } from "@sanity/icons/ChevronDown";
62
62
 
63
- import { DocumentIcon } from "@sanity/icons/Document";
63
+ import { CheckmarkCircleIcon } from "@sanity/icons/CheckmarkCircle";
64
64
 
65
65
  import { UlistIcon } from "@sanity/icons/Ulist";
66
66
 
@@ -234,7 +234,49 @@ function mappingIssues(args) {
234
234
  return issues;
235
235
  }
236
236
 
237
- const WORKFLOWS_VIEW_ID = "workflows", WORKFLOWS_TAB_PARAM = "workflowsTab", WORKFLOW_INTENT = "workflow";
237
+ const WORKFLOWS_VIEW_ID = "workflows", WORKFLOWS_TAB_PARAM = "workflowsTab";
238
+
239
+ function withoutWorkflowsTab(params) {
240
+ const {[WORKFLOWS_TAB_PARAM]: _previous, ...rest} = params ?? {};
241
+ return rest;
242
+ }
243
+
244
+ function workflowsTabCodec(tabs) {
245
+ const [defaultTab] = tabs;
246
+ return {
247
+ read: params => tabs.find(tab => tab === params?.[WORKFLOWS_TAB_PARAM]) ?? defaultTab,
248
+ write: (params, tab) => {
249
+ const rest = withoutWorkflowsTab(params);
250
+ return tab === defaultTab ? rest : {
251
+ ...rest,
252
+ [WORKFLOWS_TAB_PARAM]: tab
253
+ };
254
+ }
255
+ };
256
+ }
257
+
258
+ const WORKFLOWS_FOCUS_PARAM = "workflowsFocus";
259
+
260
+ function focusedViewParams(params, focusId) {
261
+ return {
262
+ ...withoutWorkflowsTab(params),
263
+ view: WORKFLOWS_VIEW_ID,
264
+ [WORKFLOWS_FOCUS_PARAM]: focusId
265
+ };
266
+ }
267
+
268
+ function focusedLanding(focus, instanceIds) {
269
+ if (!(focus === void 0 || !instanceIds.includes(focus))) return {
270
+ focusedId: focus,
271
+ instanceIds: instanceIds
272
+ };
273
+ }
274
+
275
+ function landingDefaultOpen(landing, instanceId) {
276
+ if (!(landing === void 0 || !landing.instanceIds.includes(instanceId))) return instanceId === landing.focusedId;
277
+ }
278
+
279
+ const WORKFLOW_INTENT = "workflow";
238
280
 
239
281
  function handlesWorkflowIntent(intent, params) {
240
282
  return workflowIntentState(intent, params) !== null;
@@ -307,6 +349,16 @@ function formatTimeAgo(value) {
307
349
  });
308
350
  }
309
351
 
352
+ function stampFace(verb, at) {
353
+ const ago = formatTimeAgo(at);
354
+ return ago === "" ? {
355
+ lead: `${verb} ${formatShortDateTime(at)}`
356
+ } : {
357
+ lead: `${verb} ${ago}`,
358
+ detail: formatShortDateTime(at)
359
+ };
360
+ }
361
+
310
362
  function formatDate(value) {
311
363
  if (!value) return "—";
312
364
  const d = parseDateFieldValue(value, "date") ?? new Date(value);
@@ -356,6 +408,10 @@ function hrefIfHttp(value) {
356
408
  }
357
409
  }
358
410
 
411
+ function unreadableDocsReport(docs) {
412
+ return docs.map(doc => `Unreadable workflow document\nID: ${doc.documentId}\nReason: ${doc.reason}\nDetail: ${doc.message}`).join(`\n\n`);
413
+ }
414
+
359
415
  function isButton(actionEval) {
360
416
  return actionRendering(actionEval) === "button";
361
417
  }
@@ -412,20 +468,27 @@ function useWorkflowContext() {
412
468
  }
413
469
 
414
470
  function useWorkflowsForDocument(rawId) {
415
- const {register: register, unregister: unregister, byDocument: byDocument} = useWorkflowContext(), docId = rawId ? getPublishedId(rawId) : void 0;
416
- return useEffect(() => {
471
+ const {register: register, unregister: unregister, entries: entries} = useWorkflowContext(), docId = rawId ? getPublishedId(rawId) : void 0;
472
+ useEffect(() => {
417
473
  if (docId) return register(docId), () => unregister(docId);
418
- }, [ docId, register, unregister ]), {
474
+ }, [ docId, register, unregister ]);
475
+ const list = useSyncExternalStore(entries.subscribe, useCallback(() => docId ? entries.getDocument(docId) : EMPTY_ENTRIES, [ entries, docId ]));
476
+ return {
419
477
  docId: docId,
420
- entries: docId ? byDocument.get(docId) ?? EMPTY_ENTRIES : EMPTY_ENTRIES
478
+ entries: list
421
479
  };
422
480
  }
423
481
 
482
+ function useHeldWorkflowEntry(instanceId) {
483
+ const {entries: entries} = useWorkflowContext();
484
+ return useSyncExternalStore(entries.subscribe, useCallback(() => instanceId ? entries.getInstance(instanceId) : void 0, [ entries, instanceId ]));
485
+ }
486
+
424
487
  function useWorkflowInstanceEntry(instanceId) {
425
- const {byInstance: byInstance, requestInstance: requestInstance, releaseInstance: releaseInstance} = useWorkflowContext();
426
- if (useEffect(() => {
488
+ const {requestInstance: requestInstance, releaseInstance: releaseInstance} = useWorkflowContext();
489
+ return useEffect(() => {
427
490
  if (instanceId) return requestInstance(instanceId), () => releaseInstance(instanceId);
428
- }, [ instanceId, requestInstance, releaseInstance ]), !!instanceId) return byInstance.get(instanceId);
491
+ }, [ instanceId, requestInstance, releaseInstance ]), useHeldWorkflowEntry(instanceId);
429
492
  }
430
493
 
431
494
  const isTodoDone = item => item.status === "done", toggledTodoStatus = item => isTodoDone(item) ? "open" : "done";
@@ -694,14 +757,17 @@ function changesOwnStatus(action, activityName) {
694
757
  return ownStatusOps(action, activityName).length > 0;
695
758
  }
696
759
 
760
+ function concludesHere(actionEval, activityName) {
761
+ return changesOwnStatus(actionEval.action, activityName) || actionEval.firing?.exitsStage === !0;
762
+ }
763
+
697
764
  function actionButtonTone(args) {
698
- const status = actionAdvanceStatus(args.action, args.activityName);
699
- return status === void 0 ? {
765
+ return concludesHere(args.actionEval, args.activityName) ? {
766
+ mode: "default",
767
+ tone: actionAdvanceStatus(args.actionEval.action, args.activityName) === "failed" ? "critical" : "default"
768
+ } : {
700
769
  mode: "ghost",
701
770
  tone: "default"
702
- } : {
703
- mode: "default",
704
- tone: status === "failed" ? "critical" : "default"
705
771
  };
706
772
  }
707
773
 
@@ -763,6 +829,11 @@ function actionRowKind(actionEval) {
763
829
  };
764
830
  }
765
831
 
832
+ function actionTriggerLabel(actionEval) {
833
+ const label = actionLabel(actionEval.action);
834
+ return actionRowKind(actionEval).kind === "params-form" ? `${label}…` : label;
835
+ }
836
+
766
837
  function tickActionNames(args) {
767
838
  const arrayFieldNames = new Set(args.state.flatMap(f => f._type === "array" ? [ f.name ] : [])), names = /* @__PURE__ */ new Set;
768
839
  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);
@@ -792,8 +863,8 @@ function deriveActivityPresentation(args) {
792
863
  settled: settled,
793
864
  triggeredActions: settled ? [] : args.activityEval.actions.filter(a => a.triggered === !0),
794
865
  manualTarget: kind === "manual" ? manualLink(activity) : void 0,
795
- actions: buttonActions.filter(a => !changesOwnStatus(a.action, activityName)),
796
- terminalActions: buttonActions.filter(a => changesOwnStatus(a.action, activityName))
866
+ actions: buttonActions.filter(a => !concludesHere(a, activityName)),
867
+ terminalActions: buttonActions.filter(a => concludesHere(a, activityName))
797
868
  };
798
869
  }
799
870
 
@@ -806,15 +877,78 @@ function rowTerminalActions(args) {
806
877
  }).terminalActions;
807
878
  }
808
879
 
809
- function rowAssignField(args) {
810
- return (groupEditableByActivity(args.editableFields ?? []).get(args.activityName) ?? []).find(f => f.type === "assignees" && f.editable);
880
+ function isDeclaredEditable(field) {
881
+ return field.editable !== void 0;
882
+ }
883
+
884
+ function rowControlState(args) {
885
+ const {kinds: kinds, activity: activity, status: status, editableFields: editableFields} = args;
886
+ if (!(activity.fields ?? []).some(field => kinds.includes(field.type) && isDeclaredEditable(field))) return {
887
+ kind: "none"
888
+ };
889
+ if (editableFields !== void 0) {
890
+ const field = (groupEditableByActivity(editableFields).get(activity.name) ?? []).find(f => kinds.includes(f.type));
891
+ return field?.editable ? {
892
+ kind: "editable",
893
+ field: field
894
+ } : field?.disabledReason ? {
895
+ kind: "closed",
896
+ reason: describeEditDisabledReason(field.disabledReason)
897
+ } : {
898
+ kind: "closed",
899
+ reason: describeEditDisabledReason({
900
+ kind: "not-editable"
901
+ })
902
+ };
903
+ }
904
+ return isTerminalActivityStatus(status) ? {
905
+ kind: "closed",
906
+ reason: describeEditDisabledReason({
907
+ kind: "edit-window-closed",
908
+ detail: "activity settled"
909
+ })
910
+ } : {
911
+ kind: "loading"
912
+ };
913
+ }
914
+
915
+ function rowAssignControlState(args) {
916
+ return rowControlState({
917
+ kinds: [ "assignees" ],
918
+ ...args
919
+ });
920
+ }
921
+
922
+ function rowDateControlState(args) {
923
+ return rowControlState({
924
+ kinds: [ "date", "datetime" ],
925
+ ...args
926
+ });
927
+ }
928
+
929
+ function committedRowFace(row) {
930
+ return {
931
+ activityName: row.activityName,
932
+ title: row.title,
933
+ status: row.status,
934
+ automated: !1,
935
+ blocked: !1
936
+ };
811
937
  }
812
938
 
813
939
  function activityRowProps(args) {
814
- const {activityEval: activityEval, activityState: activityState, editableFields: editableFields} = args, activityName = activityEval.activity.name, state = activityState.get(activityName) ?? [];
940
+ const {activityEval: activityEval, activityState: activityState, editableFields: editableFields} = args, state = activityState.get(activityEval.activity.name) ?? [];
815
941
  return {
816
- assignField: rowAssignField({
817
- activityName: activityName,
942
+ face: {
943
+ activityName: activityEval.activity.name,
944
+ title: activityLabel(activityEval.activity),
945
+ status: activityEval.status,
946
+ automated: showsAutomatedPill(activityEval),
947
+ blocked: showsBlockedTag(activityEval)
948
+ },
949
+ assignState: rowAssignControlState({
950
+ activity: activityEval.activity,
951
+ status: activityEval.status,
818
952
  editableFields: editableFields
819
953
  }),
820
954
  assigneeIds: assigneeUserIdsOf(state),
@@ -826,6 +960,15 @@ function activityRowProps(args) {
826
960
  };
827
961
  }
828
962
 
963
+ function dateControlKind(state) {
964
+ if (state.kind === "editable" && (state.field.type === "date" || state.field.type === "datetime")) return state.field.type;
965
+ }
966
+
967
+ function dateControlValue(args) {
968
+ const {state: state, dates: dates} = args;
969
+ return state.kind === "editable" ? typeof state.field.value == "string" && state.field.value !== "" ? state.field.value : void 0 : dates.find(value => parseStoredDateValue(value) !== void 0);
970
+ }
971
+
829
972
  function activityStateOf(instance, activityName) {
830
973
  return findCurrentActivityEntry(instance, activityName)?.fields ?? [];
831
974
  }
@@ -852,10 +995,17 @@ function isEmptyValue(value) {
852
995
  return value == null || value === "" || Array.isArray(value) && value.length === 0;
853
996
  }
854
997
 
998
+ function effectiveValidation(kind, validation) {
999
+ return kind !== "progress" ? validation : {
1000
+ min: validation?.min ?? 0,
1001
+ max: validation?.max ?? 100
1002
+ };
1003
+ }
1004
+
855
1005
  function scalarValidationIssue(args) {
856
1006
  const issues = scalarValidationIssues({
857
1007
  entryType: args.kind,
858
- validation: args.validation,
1008
+ validation: effectiveValidation(args.kind, args.validation),
859
1009
  value: args.value
860
1010
  });
861
1011
  return issues === void 0 ? void 0 : `${args.label}: ${issues.join("; ")}`;
@@ -1063,8 +1213,19 @@ function HintBody(props) {
1063
1213
  });
1064
1214
  }
1065
1215
 
1216
+ function textInputFacet(kind) {
1217
+ return kind === "number" || kind === "progress" ? {
1218
+ type: "number"
1219
+ } : kind === "url" ? {
1220
+ type: "url"
1221
+ } : {};
1222
+ }
1223
+
1066
1224
  function scalarInputConstraints(kind, validation) {
1067
- return validation === void 0 ? {} : kind === "number" ? {
1225
+ return kind === "progress" ? {
1226
+ min: validation?.min ?? 0,
1227
+ max: validation?.max ?? 100
1228
+ } : validation === void 0 ? {} : kind === "number" ? {
1068
1229
  min: validation.min,
1069
1230
  max: validation.max
1070
1231
  } : kind === "string" || kind === "text" ? {
@@ -1177,7 +1338,7 @@ function ChoiceSelect({options: options, value: value, onChange: onChange, readO
1177
1338
  });
1178
1339
  }
1179
1340
 
1180
- const SCALAR_INPUT_KINDS = [ "string", "text", "url", "number", "date", "dateTime", "datetime", "boolean" ], SCALAR_INPUT_KIND_SET = new Set(SCALAR_INPUT_KINDS);
1341
+ const SCALAR_INPUT_KINDS = [ "string", "text", "url", "number", "progress", "date", "dateTime", "datetime", "boolean" ], SCALAR_INPUT_KIND_SET = new Set(SCALAR_INPUT_KINDS);
1181
1342
 
1182
1343
  function isScalarInputKind(kind) {
1183
1344
  return SCALAR_INPUT_KIND_SET.has(kind);
@@ -1188,29 +1349,19 @@ function textValueOf(value) {
1188
1349
  }
1189
1350
 
1190
1351
  function ScalarInput({kind: kind, value: value, onChange: onChange, options: options, validation: validation}) {
1191
- if (options !== void 0) /* @__PURE__ */
1192
- return jsx(ChoiceSelect, {
1352
+ return options !== void 0 ? /* @__PURE__ */ jsx(ChoiceSelect, {
1193
1353
  onChange: onChange,
1194
1354
  options: options,
1195
1355
  value: value
1196
- });
1197
- if (kind === "boolean") /* @__PURE__ */
1198
- return jsx(Checkbox, {
1356
+ }) : kind === "boolean" ? /* @__PURE__ */ jsx(Checkbox, {
1199
1357
  checked: value === !0,
1200
1358
  onChange: e => onChange(e.currentTarget.checked)
1201
- });
1202
- if (kind === "date" || kind === "dateTime" || kind === "datetime") /* @__PURE__ */
1203
- return jsx(DateFieldInput, {
1359
+ }) : kind === "date" || kind === "dateTime" || kind === "datetime" ? /* @__PURE__ */ jsx(DateFieldInput, {
1204
1360
  kind: kind === "date" ? "date" : "datetime",
1205
1361
  onChange: next => onChange(next ?? ""),
1206
1362
  value: value
1207
- });
1208
- const inputType = kind === "url" || kind === "number" ? kind : void 0;
1209
- /* @__PURE__ */
1210
- return jsx(TextInput, {
1211
- ...inputType ? {
1212
- type: inputType
1213
- } : {},
1363
+ }) : /* @__PURE__ */ jsx(TextInput, {
1364
+ ...textInputFacet(kind),
1214
1365
  ...scalarInputConstraints(kind, validation),
1215
1366
  fontSize: 1,
1216
1367
  onChange: e => onChange(e.currentTarget.value),
@@ -1298,25 +1449,26 @@ function MemberPickerButton({label: label = "Select member…", onChange: onChan
1298
1449
  });
1299
1450
  }
1300
1451
 
1301
- function StubPreview({id: id, layout: layout, schemaType: schemaType}) {
1452
+ function useStubValuePreview(id, schemaType) {
1302
1453
  const value = useMemo(() => ({
1303
1454
  _id: id,
1304
1455
  _type: schemaType.name
1305
- }), [ id, schemaType.name ]);
1306
- return useValuePreview({
1456
+ }), [ id, schemaType.name ]), preview = useValuePreview({
1307
1457
  schemaType: schemaType,
1308
1458
  value: value
1309
- })?.isLoading ? layout === "inline" ? /* @__PURE__ */ jsx(TextSkeleton, {
1310
- animated: !0,
1311
- radius: 1,
1312
- size: 1,
1313
- style: {
1314
- width: 96
1315
- }
1316
- }) : /* @__PURE__ */ jsx(SanityDefaultPreview, {
1459
+ });
1460
+ return {
1461
+ value: value,
1462
+ preview: preview
1463
+ };
1464
+ }
1465
+
1466
+ function StubPreview({id: id, schemaType: schemaType}) {
1467
+ const {value: value, preview: preview} = useStubValuePreview(id, schemaType);
1468
+ return preview.isLoading ? /* @__PURE__ */ jsx(SanityDefaultPreview, {
1317
1469
  isPlaceholder: !0
1318
1470
  }) : /* @__PURE__ */ jsx(Preview, {
1319
- layout: layout,
1471
+ layout: "default",
1320
1472
  schemaType: schemaType,
1321
1473
  skipVisibilityCheck: !0,
1322
1474
  value: value
@@ -1426,7 +1578,6 @@ function PreviewOrId({id: id, type: type, fallbackTitle: fallbackTitle}) {
1426
1578
  const schemaType = useSchema().get(type);
1427
1579
  return schemaType ? /* @__PURE__ */ jsx(StubPreview, {
1428
1580
  id: id,
1429
- layout: "default",
1430
1581
  schemaType: schemaType
1431
1582
  }) : /* @__PURE__ */ jsxs(Flex, {
1432
1583
  align: "center",
@@ -1560,12 +1711,30 @@ function useClosableToast() {
1560
1711
  }), [ toast ]);
1561
1712
  }
1562
1713
 
1714
+ const ActionClusterContext = createContext(void 0), ActionClusterProvider = ActionClusterContext.Provider;
1715
+
1716
+ function useActionClusterState() {
1717
+ const [pending, setPending] = useState(!1);
1718
+ return useMemo(() => ({
1719
+ pending: pending,
1720
+ onFireChange: setPending
1721
+ }), [ pending ]);
1722
+ }
1723
+
1724
+ function useActionClusterLock() {
1725
+ return useContext(ActionClusterContext);
1726
+ }
1727
+
1728
+ function useActionClusterPending() {
1729
+ return useActionClusterLock()?.pending === !0;
1730
+ }
1731
+
1563
1732
  function useFireAction(args) {
1564
- const {instanceId: instanceId, activity: activity, action: action, label: label} = args, {fireActionFor: fireActionFor} = useWorkflowContext(), toast = useClosableToast(), [pending, setPending] = useState(!1);
1733
+ const {instanceId: instanceId, activity: activity, action: action, label: label} = args, {fireActionFor: fireActionFor} = useWorkflowContext(), toast = useClosableToast(), cluster = useActionClusterLock(), [localPending, setLocalPending] = useState(!1), pending = localPending || cluster?.pending === !0;
1565
1734
  return {
1566
1735
  fire: useCallback(async params => {
1567
1736
  if (pending) return !1;
1568
- setPending(!0);
1737
+ setLocalPending(!0), cluster?.onFireChange(!0);
1569
1738
  try {
1570
1739
  return await fireActionFor(instanceId, {
1571
1740
  activity: activity,
@@ -1585,9 +1754,9 @@ function useFireAction(args) {
1585
1754
  description: describeError(err)
1586
1755
  }), !1;
1587
1756
  } finally {
1588
- setPending(!1);
1757
+ setLocalPending(!1), cluster?.onFireChange(!1);
1589
1758
  }
1590
- }, [ fireActionFor, instanceId, activity, action, label, pending, toast ]),
1759
+ }, [ fireActionFor, instanceId, activity, action, label, pending, toast, cluster ]),
1591
1760
  pending: pending
1592
1761
  };
1593
1762
  }
@@ -1596,6 +1765,10 @@ function stopRowMouseDown(e) {
1596
1765
  e.preventDefault(), e.stopPropagation();
1597
1766
  }
1598
1767
 
1768
+ function stopRowClick(e) {
1769
+ e.stopPropagation();
1770
+ }
1771
+
1599
1772
  function spanTriggerProps(chrome) {
1600
1773
  return chrome?.inButtonCard === !0 ? {
1601
1774
  as: "span",
@@ -1631,7 +1804,6 @@ function FireButton({instanceId: instanceId, activity: activity, action: action,
1631
1804
  mode: chrome?.mode ?? mode,
1632
1805
  onClick: handleClick,
1633
1806
  padding: 2,
1634
- style: chrome?.style,
1635
1807
  text: label,
1636
1808
  tone: tone,
1637
1809
  ...spanTriggerProps(chrome),
@@ -1660,12 +1832,11 @@ function ActivityActionRow({actionEval: actionEval, instanceId: instanceId, acti
1660
1832
  }
1661
1833
 
1662
1834
  function DisabledActionButton({actionEval: actionEval, chrome: chrome}) {
1663
- const label = actionLabel(actionEval.action), button = /* @__PURE__ */ jsx(Button, {
1835
+ const label = actionTriggerLabel(actionEval), button = /* @__PURE__ */ jsx(Button, {
1664
1836
  fontSize: 1,
1665
1837
  mode: chrome?.mode ?? "ghost",
1666
1838
  onClick: e => e.stopPropagation(),
1667
1839
  padding: 2,
1668
- style: chrome?.style,
1669
1840
  text: label,
1670
1841
  ...spanTriggerProps(chrome),
1671
1842
  disabled: !0
@@ -1678,7 +1849,7 @@ function DisabledActionButton({actionEval: actionEval, chrome: chrome}) {
1678
1849
 
1679
1850
  function PlainActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, chrome: chrome}) {
1680
1851
  const {action: action} = actionEval, label = actionLabel(action), btn = actionButtonTone({
1681
- action: action,
1852
+ actionEval: actionEval,
1682
1853
  activityName: activity
1683
1854
  }), button = /* @__PURE__ */ jsx(FireButton, {
1684
1855
  action: action.name,
@@ -1756,8 +1927,8 @@ function ParamsActionDialog({actionEval: actionEval, instanceId: instanceId, act
1756
1927
  }
1757
1928
 
1758
1929
  function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, chrome: chrome}) {
1759
- const {action: action} = actionEval, label = actionLabel(action), [open, setOpen] = useState(!1), btn = actionButtonTone({
1760
- action: action,
1930
+ const [open, setOpen] = useState(!1), btn = actionButtonTone({
1931
+ actionEval: actionEval,
1761
1932
  activityName: activity
1762
1933
  });
1763
1934
  /* @__PURE__ */
@@ -1770,11 +1941,8 @@ function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, act
1770
1941
  chrome?.inButtonCard === !0 && e.stopPropagation(), setOpen(!0);
1771
1942
  },
1772
1943
  padding: 2,
1773
- text: `${label}…`,
1944
+ text: actionTriggerLabel(actionEval),
1774
1945
  tone: btn.tone,
1775
- ...chrome?.style ? {
1776
- style: chrome.style
1777
- } : {},
1778
1946
  ...spanTriggerProps(chrome)
1779
1947
  }), open ? /* @__PURE__ */ jsx(RowClickShield, {
1780
1948
  active: chrome?.inButtonCard === !0,
@@ -1788,178 +1956,6 @@ function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, act
1788
1956
  });
1789
1957
  }
1790
1958
 
1791
- function HintedMenuButton({hint: hint, wrapMenuButton: wrapMenuButton, ...menuButtonProps}) {
1792
- const [open, setOpen] = useState(!1), wrap = wrapMenuButton ?? (node => node);
1793
- return hint === void 0 ? wrap(/* @__PURE__ */ jsx(MenuButton, {
1794
- ...menuButtonProps
1795
- })) : /* @__PURE__ */ jsx(HoverHint, {
1796
- disabled: open,
1797
- text: hint,
1798
- children: wrap(
1799
- /* @__PURE__ */ jsx(MenuButton, {
1800
- ...menuButtonProps,
1801
- onClose: () => setOpen(!1),
1802
- onOpen: () => setOpen(!0)
1803
- }))
1804
- });
1805
- }
1806
-
1807
- function OverflowMenuItem({actionEval: actionEval, instanceId: instanceId, activity: activity, onCollectParams: onCollectParams}) {
1808
- const rowKind = actionRowKind(actionEval), label = actionLabel(actionEval.action), {fire: fire, pending: pending} = useFireAction({
1809
- instanceId: instanceId,
1810
- activity: activity,
1811
- action: actionEval.action.name,
1812
- label: label
1813
- });
1814
- return rowKind.kind === "disabled" ? /* @__PURE__ */ jsx(MenuItem, {
1815
- disabled: !0,
1816
- text: label
1817
- }) : rowKind.kind === "plain" ? /* @__PURE__ */ jsx(MenuItem, {
1818
- disabled: pending,
1819
- onClick: () => {
1820
- fire();
1821
- },
1822
- text: label
1823
- }) : /* @__PURE__ */ jsx(MenuItem, {
1824
- onClick: onCollectParams,
1825
- text: `${label}…`
1826
- });
1827
- }
1828
-
1829
- function rowShield(menuButton) {
1830
- /* @__PURE__ */
1831
- return jsx("span", {
1832
- onClick: e => e.stopPropagation(),
1833
- onMouseDown: stopRowMouseDown,
1834
- style: {
1835
- display: "contents"
1836
- },
1837
- children: menuButton
1838
- });
1839
- }
1840
-
1841
- function ActionOverflowMenu({actions: actions, instanceId: instanceId, activity: activity, tone: tone, chrome: chrome}) {
1842
- const [paramsAction, setParamsAction] = useState(void 0), menuId = useId(), inButtonCard = chrome?.inButtonCard === !0;
1843
- /* @__PURE__ */
1844
- return jsxs(Fragment, {
1845
- children: [
1846
- /* @__PURE__ */ jsx(HintedMenuButton, {
1847
- button: /* @__PURE__ */ jsx(Button, {
1848
- "aria-label": "More options",
1849
- fontSize: 1,
1850
- icon: ChevronDownIcon,
1851
- padding: 2,
1852
- tone: tone,
1853
- ...chrome?.mode ? {
1854
- mode: chrome.mode
1855
- } : {},
1856
- ...chrome?.style ? {
1857
- style: chrome.style
1858
- } : {},
1859
- ...inButtonCard ? {
1860
- as: "span"
1861
- } : {}
1862
- }),
1863
- hint: "More options",
1864
- id: `workflow-action-overflow-${menuId}`,
1865
- menu: /* @__PURE__ */ jsx(Menu, {
1866
- children: actions.map(a => /* @__PURE__ */ jsx(OverflowMenuItem, {
1867
- actionEval: a,
1868
- activity: activity,
1869
- instanceId: instanceId,
1870
- onCollectParams: () => setParamsAction(a)
1871
- }, a.action.name))
1872
- }),
1873
- popover: {
1874
- placement: "top-end",
1875
- portal: !0
1876
- },
1877
- ...inButtonCard ? {
1878
- wrapMenuButton: rowShield
1879
- } : {}
1880
- }), paramsAction ? /* @__PURE__ */ jsx(RowClickShield, {
1881
- active: inButtonCard,
1882
- children: /* @__PURE__ */ jsx(ParamsActionDialog, {
1883
- actionEval: paramsAction,
1884
- activity: activity,
1885
- instanceId: instanceId,
1886
- onClose: () => setParamsAction(void 0)
1887
- })
1888
- }) : null ]
1889
- });
1890
- }
1891
-
1892
- const PRIMARY_SEGMENT = {
1893
- borderBottomRightRadius: 0,
1894
- borderTopRightRadius: 0
1895
- }, MENU_SEGMENT = {
1896
- borderBottomLeftRadius: 0,
1897
- borderTopLeftRadius: 0
1898
- };
1899
-
1900
- function TerminalActions({actions: actions, activity: activity, instanceId: instanceId, mode: mode = "default", inButtonCard: inButtonCard = !1}) {
1901
- const {button: button, color: color} = useTheme_v2(), [primary, ...overflow] = actions;
1902
- if (!primary) return null;
1903
- const ghost = mode === "ghost", embed = inButtonCard ? {
1904
- inButtonCard: !0
1905
- } : {};
1906
- if (overflow.length === 0) {
1907
- const chrome = {
1908
- ...ghost ? {
1909
- mode: "ghost"
1910
- } : {},
1911
- ...embed
1912
- };
1913
- /* @__PURE__ */
1914
- return jsx(ActivityActionRow, {
1915
- actionEval: primary,
1916
- activity: activity,
1917
- chrome: chrome,
1918
- instanceId: instanceId
1919
- });
1920
- }
1921
- const {tone: tone} = actionButtonTone({
1922
- action: primary.action,
1923
- activityName: activity
1924
- }), segment = ghost ? {
1925
- mode: "ghost"
1926
- } : {};
1927
- /* @__PURE__ */
1928
- return jsxs(Flex, {
1929
- children: [
1930
- /* @__PURE__ */ jsx(ActivityActionRow, {
1931
- actionEval: primary,
1932
- activity: activity,
1933
- chrome: {
1934
- ...segment,
1935
- style: PRIMARY_SEGMENT,
1936
- ...embed
1937
- },
1938
- instanceId: instanceId
1939
- }), ghost ? null : /* @__PURE__ */ jsx(Box, {
1940
- flex: "none",
1941
- style: {
1942
- background: color.bg,
1943
- width: 1
1944
- }
1945
- }),
1946
- /* @__PURE__ */ jsx(ActionOverflowMenu, {
1947
- actions: overflow,
1948
- activity: activity,
1949
- chrome: {
1950
- ...segment,
1951
- style: ghost ? {
1952
- ...MENU_SEGMENT,
1953
- marginLeft: -button.border.width
1954
- } : MENU_SEGMENT,
1955
- ...embed
1956
- },
1957
- instanceId: instanceId,
1958
- tone: tone
1959
- }) ]
1960
- });
1961
- }
1962
-
1963
1959
  const DIM = {
1964
1960
  opacity: .6
1965
1961
  };
@@ -1997,8 +1993,12 @@ function stageTitle(definition, stageName) {
1997
1993
  return definition?.stages.find(stage => stage.name === stageName)?.title ?? stageName;
1998
1994
  }
1999
1995
 
1996
+ function findActivityNode(args) {
1997
+ return args.definition?.stages.find(s => s.name === args.stageName)?.activities?.find(a => a.name === args.activityName);
1998
+ }
1999
+
2000
2000
  function findActivity(args) {
2001
- const activity = args.definition?.stages.find(s => s.name === args.stageName)?.activities?.find(a => a.name === args.activityName);
2001
+ const activity = findActivityNode(args);
2002
2002
  return {
2003
2003
  title: activity?.title ?? args.activityName,
2004
2004
  description: activity?.description
@@ -2132,14 +2132,15 @@ function FieldInput$1({kind: kind, value: value, onChange: onChange, onEnter: on
2132
2132
  });
2133
2133
 
2134
2134
  case "number":
2135
+ case "progress":
2135
2136
  /* @__PURE__ */
2136
2137
  return jsx(TextInput, {
2138
+ ...textInputFacet(kind),
2139
+ ...scalarInputConstraints(kind, validation),
2137
2140
  fontSize: 1,
2138
2141
  onChange: e => onChange(e.currentTarget.value === "" ? null : Number(e.currentTarget.value)),
2139
2142
  onKeyDown: e => e.key === "Enter" && onEnter(),
2140
2143
  readOnly: readOnly,
2141
- ...scalarInputConstraints(kind, validation),
2142
- type: "number",
2143
2144
  value: textValueOf(value)
2144
2145
  });
2145
2146
 
@@ -2167,6 +2168,30 @@ function FieldInput$1({kind: kind, value: value, onChange: onChange, onEnter: on
2167
2168
  }
2168
2169
  }
2169
2170
 
2171
+ function LinkChip({hint: hint, children: children, as: as = "a", ...anchorProps}) {
2172
+ /* @__PURE__ */
2173
+ return jsx(HoverHint, {
2174
+ text: hint,
2175
+ children: /* @__PURE__ */ jsx(Card, {
2176
+ __unstable_focusRing: !0,
2177
+ as: as,
2178
+ "data-as": "a",
2179
+ padding: 2,
2180
+ radius: 3,
2181
+ style: {
2182
+ alignItems: "center",
2183
+ color: "inherit",
2184
+ display: "flex",
2185
+ minWidth: 0,
2186
+ textDecoration: "none"
2187
+ },
2188
+ tone: "inherit",
2189
+ ...anchorProps,
2190
+ children: children
2191
+ })
2192
+ });
2193
+ }
2194
+
2170
2195
  function docLinkState(input) {
2171
2196
  const {bareId: bareId, resource: resource} = input;
2172
2197
  if (bareId === void 0 || resource === void 0) return {
@@ -2276,7 +2301,6 @@ function DocRefFace({gdr: gdr}) {
2276
2301
  const {parsed: parsed2, local: local, schemaType: schemaType} = useLocalDocRef(gdr);
2277
2302
  return parsed2 && local && schemaType ? /* @__PURE__ */ jsx(StubPreview, {
2278
2303
  id: parsed2.documentId,
2279
- layout: "default",
2280
2304
  schemaType: schemaType
2281
2305
  }) : /* @__PURE__ */ jsxs(Flex, {
2282
2306
  align: "center",
@@ -2306,15 +2330,23 @@ function ForeignRefNotice({bareId: bareId, resource: resource}) {
2306
2330
  }
2307
2331
 
2308
2332
  function MissingDocNotice({bareId: bareId, layout: layout}) {
2309
- return layout === "inline" ? /* @__PURE__ */ jsx(Text, {
2310
- muted: !0,
2311
- size: 1,
2333
+ return layout === "inline" ?
2334
+ /* @__PURE__ */ jsx(Box, {
2335
+ padding: 2,
2312
2336
  children: /* @__PURE__ */ jsxs(Flex, {
2313
2337
  align: "center",
2314
- as: "span",
2315
- gap: 1,
2338
+ gap: 2,
2316
2339
  children: [
2317
- /* @__PURE__ */ jsx(WarningOutlineIcon, {}), " Document unavailable" ]
2340
+ /* @__PURE__ */ jsx(Text, {
2341
+ muted: !0,
2342
+ size: 1,
2343
+ children: /* @__PURE__ */ jsx(WarningOutlineIcon, {})
2344
+ }),
2345
+ /* @__PURE__ */ jsx(Text, {
2346
+ muted: !0,
2347
+ size: 1,
2348
+ children: "Document unavailable"
2349
+ }) ]
2318
2350
  })
2319
2351
  }) : /* @__PURE__ */ jsx(Card, {
2320
2352
  border: !0,
@@ -2347,44 +2379,73 @@ function UnopenableTypeNotice({actualType: actualType, bareId: bareId}) {
2347
2379
  });
2348
2380
  }
2349
2381
 
2350
- function LinkedDocPreview({bareId: bareId, layout: layout, schemaType: schemaType}) {
2351
- return layout === "inline" ? /* @__PURE__ */ jsx(IntentLink, {
2352
- intent: "edit",
2353
- onClick: e => e.stopPropagation(),
2354
- params: {
2355
- id: bareId
2356
- },
2357
- style: {
2358
- color: "inherit",
2359
- textDecoration: "none",
2360
- display: "flex",
2361
- alignItems: "center"
2362
- },
2363
- children: /* @__PURE__ */ jsx(StubPreview, {
2364
- id: bareId,
2365
- layout: "inline",
2382
+ function renderTypeIcon(icon) {
2383
+ return typeof icon == "function" ? createElement(icon) : isValidElement(icon) ? icon : /* @__PURE__ */ jsx(DocumentIcon, {});
2384
+ }
2385
+
2386
+ function InlineChipFace({bareId: bareId, schemaType: schemaType}) {
2387
+ const {preview: preview} = useStubValuePreview(bareId, schemaType);
2388
+ /* @__PURE__ */
2389
+ return jsxs(Flex, {
2390
+ align: "center",
2391
+ gap: 2,
2392
+ children: [
2393
+ /* @__PURE__ */ jsx(Text, {
2394
+ size: 1,
2395
+ children: renderTypeIcon(schemaType.icon)
2396
+ }), preview.isLoading ? /* @__PURE__ */ jsx(TextSkeleton, {
2397
+ animated: !0,
2398
+ radius: 1,
2399
+ size: 1,
2400
+ style: {
2401
+ width: 96
2402
+ }
2403
+ }) : /* @__PURE__ */ jsx(Text, {
2404
+ size: 1,
2405
+ textOverflow: "ellipsis",
2406
+ weight: "medium",
2407
+ children: preview.value?.title ?? /* @__PURE__ */ jsx("em", {
2408
+ children: "Untitled"
2409
+ })
2410
+ }) ]
2411
+ });
2412
+ }
2413
+
2414
+ function LinkedDocPreview({bareId: bareId, layout: layout, schemaType: schemaType}) {
2415
+ const EditIntentLink = useMemo(() => function(linkProps) {
2416
+ /* @__PURE__ */
2417
+ return jsx(IntentLink, {
2418
+ ...linkProps,
2419
+ intent: "edit",
2420
+ params: {
2421
+ id: bareId
2422
+ }
2423
+ });
2424
+ }, [ bareId ]);
2425
+ return layout === "inline" ? /* @__PURE__ */ jsx(LinkChip, {
2426
+ as: EditIntentLink,
2427
+ hint: "Open document",
2428
+ onClick: e => e.stopPropagation(),
2429
+ children: /* @__PURE__ */ jsx(InlineChipFace, {
2430
+ bareId: bareId,
2366
2431
  schemaType: schemaType
2367
2432
  })
2368
- }) : /* @__PURE__ */ jsx(IntentLink, {
2369
- intent: "edit",
2433
+ }) : /* @__PURE__ */ jsx(Card, {
2434
+ __unstable_focusRing: !0,
2435
+ as: EditIntentLink,
2436
+ border: !0,
2437
+ "data-as": "a",
2370
2438
  onClick: e => e.stopPropagation(),
2371
- params: {
2372
- id: bareId
2373
- },
2439
+ padding: 1,
2440
+ radius: 3,
2374
2441
  style: {
2375
2442
  color: "inherit",
2376
2443
  textDecoration: "none",
2377
2444
  display: "block"
2378
2445
  },
2379
- children: /* @__PURE__ */ jsx(Card, {
2380
- border: !0,
2381
- padding: 1,
2382
- radius: 3,
2383
- children: /* @__PURE__ */ jsx(StubPreview, {
2384
- id: bareId,
2385
- layout: "default",
2386
- schemaType: schemaType
2387
- })
2446
+ children: /* @__PURE__ */ jsx(StubPreview, {
2447
+ id: bareId,
2448
+ schemaType: schemaType
2388
2449
  })
2389
2450
  });
2390
2451
  }
@@ -2528,6 +2589,10 @@ const fieldValueRenderer = {
2528
2589
  }, ref.id))
2529
2590
  }) : /* @__PURE__ */ jsx(Empty, {}),
2530
2591
  number: renderTextValue,
2592
+ progress: field => /* @__PURE__ */ jsx(ProgressBar, {
2593
+ label: field.title ?? field.name,
2594
+ value: field.value
2595
+ }),
2531
2596
  object: field => field.value === null ? /* @__PURE__ */ jsx(Empty, {}) : /* @__PURE__ */ jsx(Code, {
2532
2597
  size: 1,
2533
2598
  style: {
@@ -2779,7 +2844,7 @@ function useFieldDraft(args) {
2779
2844
  }
2780
2845
 
2781
2846
  function EditableFieldControl({instanceId: instanceId, field: field}) {
2782
- const {editFieldFor: editFieldFor, previewFieldFor: previewFieldFor, discardFieldPreviewFor: discardFieldPreviewFor, byInstance: byInstance} = useWorkflowContext(), me = useCurrentUser(), held = byInstance.get(instanceId), entry = held ? resolveFieldEntry(held.instance, field) : void 0, evaluation = held?.evaluation, target = editTargetOf(field), consequence = fieldConsequence(evaluation, field.name), advanceTo = advanceTargetTitle(evaluation, field.name);
2847
+ const {editFieldFor: editFieldFor, previewFieldFor: previewFieldFor, discardFieldPreviewFor: discardFieldPreviewFor} = useWorkflowContext(), me = useCurrentUser(), held = useHeldWorkflowEntry(instanceId), entry = held ? resolveFieldEntry(held.instance, field) : void 0, evaluation = held?.evaluation, target = editTargetOf(field), consequence = fieldConsequence(evaluation, field.name), advanceTo = advanceTargetTitle(evaluation, field.name);
2783
2848
  /* @__PURE__ */
2784
2849
  return jsx(EditableField, {
2785
2850
  field: field,
@@ -3321,6 +3386,27 @@ function ActivityStatusIcon({status: status}) {
3321
3386
  });
3322
3387
  }
3323
3388
 
3389
+ function LoadingRow({label: label, padding: padding}) {
3390
+ /* @__PURE__ */
3391
+ return jsxs(Flex, {
3392
+ align: "center",
3393
+ gap: 2,
3394
+ ...padding === void 0 ? {} : {
3395
+ padding: padding
3396
+ },
3397
+ children: [
3398
+ /* @__PURE__ */ jsx(Spinner, {
3399
+ muted: !0,
3400
+ size: 1
3401
+ }),
3402
+ /* @__PURE__ */ jsx(Text, {
3403
+ muted: !0,
3404
+ size: 1,
3405
+ children: label
3406
+ }) ]
3407
+ });
3408
+ }
3409
+
3324
3410
  function useSpaceToken(index) {
3325
3411
  const value = useTheme_v2().space[index];
3326
3412
  if (value === void 0) throw new Error(`theme is missing space token ${index}`);
@@ -3351,6 +3437,27 @@ function useBadgeCapTrim() {
3351
3437
  };
3352
3438
  }
3353
3439
 
3440
+ function AvatarDisplay({avatar: avatar, "aria-label": ariaLabel}) {
3441
+ const trim = useAvatarCapTrim();
3442
+ /* @__PURE__ */
3443
+ return jsx(Box, {
3444
+ "aria-label": ariaLabel,
3445
+ as: "span",
3446
+ padding: 2,
3447
+ style: {
3448
+ display: "inline-flex"
3449
+ },
3450
+ children: /* @__PURE__ */ jsx(Flex, {
3451
+ align: "center",
3452
+ justify: "center",
3453
+ style: {
3454
+ margin: trim
3455
+ },
3456
+ children: avatar
3457
+ })
3458
+ });
3459
+ }
3460
+
3354
3461
  function AvatarButton({avatar: avatar, onClick: onClick, onMouseDown: onMouseDown, as: as, "aria-label": ariaLabel, ref: ref}) {
3355
3462
  const trim = useAvatarCapTrim();
3356
3463
  /* @__PURE__ */
@@ -3393,10 +3500,13 @@ function UserAvatar({id: id}) {
3393
3500
  });
3394
3501
  }
3395
3502
 
3396
- function UserAvatarGroup({ids: ids}) {
3503
+ function UserAvatarGroup({ids: ids, hint: hint}) {
3397
3504
  const displays = useUserDisplays(ids);
3398
3505
  return displays.length === 0 ? null : /* @__PURE__ */ jsx(HoverHint, {
3399
3506
  text: displays.map(d => d.name).join(", "),
3507
+ ...hint === void 0 ? {} : {
3508
+ description: hint
3509
+ },
3400
3510
  children: /* @__PURE__ */ jsx(MemberAvatarGroup, {
3401
3511
  members: displays.map(display => ({
3402
3512
  id: display.id,
@@ -3407,9 +3517,26 @@ function UserAvatarGroup({ids: ids}) {
3407
3517
  });
3408
3518
  }
3409
3519
 
3410
- function AssignActivityControl({instanceId: instanceId, field: field, assigneeIds: assigneeIds = []}) {
3411
- const {editFieldFor: editFieldFor} = useWorkflowContext(), toast = useClosableToast(), [open, setOpen] = useState(!1), [busy, setBusy] = useState(!1), members = field.value ?? [], selectedIds = new Set(members.flatMap(m => m.type === "user" ? [ m.id ] : [])), toggle = async memberId => {
3412
- if (!busy) {
3520
+ function AssignActivityControl({instanceId: instanceId, state: state, assigneeIds: assigneeIds = []}) {
3521
+ const {editFieldFor: editFieldFor} = useWorkflowContext(), toast = useClosableToast(), [open, setOpen] = useState(!1), [busy, setBusy] = useState(!1);
3522
+ if (state.kind === "none") return assigneeIds.length === 0 ? null : /* @__PURE__ */ jsx(AvatarDisplay, {
3523
+ "aria-label": "Assignees",
3524
+ avatar: /* @__PURE__ */ jsx(UserAvatarGroup, {
3525
+ ids: assigneeIds
3526
+ })
3527
+ });
3528
+ if (state.kind === "closed") return assigneeIds.length === 0 ? null : /* @__PURE__ */ jsx(AvatarButton, {
3529
+ "aria-label": "Assignees",
3530
+ as: "span",
3531
+ avatar: /* @__PURE__ */ jsx(UserAvatarGroup, {
3532
+ hint: state.reason,
3533
+ ids: assigneeIds
3534
+ }),
3535
+ onClick: stopRowClick,
3536
+ onMouseDown: stopRowMouseDown
3537
+ });
3538
+ 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 => {
3539
+ if (!(busy || field === void 0)) {
3413
3540
  setBusy(!0);
3414
3541
  try {
3415
3542
  selectedIds.has(memberId) ? await editFieldFor(instanceId, {
@@ -3462,8 +3589,11 @@ function AssignActivityControl({instanceId: instanceId, field: field, assigneeId
3462
3589
  /* @__PURE__ */
3463
3590
  return jsx(DismissablePopover, {
3464
3591
  content: /* @__PURE__ */ jsx(Box, {
3465
- onClick: e => e.stopPropagation(),
3466
- children: /* @__PURE__ */ jsx(MemberPicker, {
3592
+ onClick: stopRowClick,
3593
+ children: state.kind === "loading" ? /* @__PURE__ */ jsx(LoadingRow, {
3594
+ label: "Loading…",
3595
+ padding: 3
3596
+ }) : /* @__PURE__ */ jsx(MemberPicker, {
3467
3597
  onSelect: member => member && void toggle(member.id),
3468
3598
  selectedIds: selectedIds
3469
3599
  })
@@ -3751,9 +3881,9 @@ function BreadcrumbTail({segments: segments, style: style}) {
3751
3881
  }
3752
3882
 
3753
3883
  function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3754
- const frame = /* @__PURE__ */ jsxs(Flex, {
3884
+ const frame =
3885
+ /* @__PURE__ */ jsxs(Flex, {
3755
3886
  align: "center",
3756
- gap: 1,
3757
3887
  children: [
3758
3888
  /* @__PURE__ */ jsx(Flex, {
3759
3889
  align: "center",
@@ -3767,6 +3897,7 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3767
3897
  align: "center",
3768
3898
  flex: 1,
3769
3899
  gap: 2,
3900
+ paddingLeft: 2,
3770
3901
  style: {
3771
3902
  minWidth: 0
3772
3903
  },
@@ -3774,6 +3905,7 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3774
3905
  }), end === void 0 ? null : /* @__PURE__ */ jsx(Flex, {
3775
3906
  align: "center",
3776
3907
  gap: 2,
3908
+ paddingLeft: 2,
3777
3909
  style: {
3778
3910
  flexShrink: 0
3779
3911
  },
@@ -3782,14 +3914,14 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3782
3914
  });
3783
3915
  return onOpen === void 0 ? /* @__PURE__ */ jsx(Card, {
3784
3916
  padding: 1,
3785
- radius: 2,
3917
+ radius: 3,
3786
3918
  children: frame
3787
3919
  }) :
3788
3920
  /* @__PURE__ */ jsx(Card, {
3789
3921
  as: "button",
3790
3922
  onClick: onOpen,
3791
3923
  padding: 1,
3792
- radius: 2,
3924
+ radius: 3,
3793
3925
  style: {
3794
3926
  textAlign: "left"
3795
3927
  },
@@ -3976,6 +4108,202 @@ function AutomationActionRow({actionEval: actionEval, definition: definition}) {
3976
4108
  });
3977
4109
  }
3978
4110
 
4111
+ function inlineActionCount(args) {
4112
+ const {available: available, actionWidths: actionWidths, menuWidth: menuWidth, gap: gap} = args, max = Math.min(3, actionWidths.length);
4113
+ for (let count = max; count > 0; count--) {
4114
+ const buttons = actionWidths.slice(0, count).reduce((sum, w) => sum + w, 0) + gap * (count - 1), menu = count === actionWidths.length ? 0 : menuWidth + gap;
4115
+ if (buttons + menu <= available) return count;
4116
+ }
4117
+ return 0;
4118
+ }
4119
+
4120
+ const ACTIONS_MENU_TRIGGER = {
4121
+ fontSize: 1,
4122
+ gap: 1,
4123
+ iconRight: ChevronDownIcon,
4124
+ padding: 2
4125
+ };
4126
+
4127
+ function ActionMenuItem({actionEval: actionEval, instanceId: instanceId, activity: activity, onCollectParams: onCollectParams}) {
4128
+ const rowKind = actionRowKind(actionEval), label = actionTriggerLabel(actionEval), {fire: fire, pending: pending} = useFireAction({
4129
+ instanceId: instanceId,
4130
+ activity: activity,
4131
+ action: actionEval.action.name,
4132
+ label: label
4133
+ });
4134
+ return rowKind.kind === "disabled" ? /* @__PURE__ */ jsx(MenuItem, {
4135
+ disabled: !0,
4136
+ text: label
4137
+ }) : rowKind.kind === "plain" ? /* @__PURE__ */ jsx(MenuItem, {
4138
+ disabled: pending,
4139
+ onClick: () => {
4140
+ fire();
4141
+ },
4142
+ text: label
4143
+ }) : /* @__PURE__ */ jsx(MenuItem, {
4144
+ disabled: pending,
4145
+ onClick: onCollectParams,
4146
+ text: label
4147
+ });
4148
+ }
4149
+
4150
+ function rowShield(menuButton) {
4151
+ /* @__PURE__ */
4152
+ return jsx("span", {
4153
+ onClick: e => e.stopPropagation(),
4154
+ onMouseDown: stopRowMouseDown,
4155
+ style: {
4156
+ display: "contents"
4157
+ },
4158
+ children: menuButton
4159
+ });
4160
+ }
4161
+
4162
+ function ActionsMenuButton({actions: actions, instanceId: instanceId, activity: activity, label: label, mode: mode = "ghost", inButtonCard: inButtonCard = !1}) {
4163
+ const [paramsAction, setParamsAction] = useState(void 0), clusterPending = useActionClusterPending(), menuId = useId(), menuButton = /* @__PURE__ */ jsx(MenuButton, {
4164
+ button: /* @__PURE__ */ jsx(Button, {
4165
+ ...ACTIONS_MENU_TRIGGER,
4166
+ mode: mode,
4167
+ text: label,
4168
+ ...inButtonCard ? {
4169
+ as: "span"
4170
+ } : {},
4171
+ ...clusterPending ? {
4172
+ disabled: !0
4173
+ } : {}
4174
+ }),
4175
+ id: `workflow-actions-menu-${menuId}`,
4176
+ menu: /* @__PURE__ */ jsx(Menu, {
4177
+ children: actions.map(a => /* @__PURE__ */ jsx(ActionMenuItem, {
4178
+ actionEval: a,
4179
+ activity: activity,
4180
+ instanceId: instanceId,
4181
+ onCollectParams: () => setParamsAction(a)
4182
+ }, a.action.name))
4183
+ }),
4184
+ popover: {
4185
+ placement: "top-end",
4186
+ portal: !0
4187
+ }
4188
+ });
4189
+ /* @__PURE__ */
4190
+ return jsxs(Fragment, {
4191
+ children: [ inButtonCard ? rowShield(menuButton) : menuButton, paramsAction ? /* @__PURE__ */ jsx(RowClickShield, {
4192
+ active: inButtonCard,
4193
+ children: /* @__PURE__ */ jsx(ParamsActionDialog, {
4194
+ actionEval: paramsAction,
4195
+ activity: activity,
4196
+ instanceId: instanceId,
4197
+ onClose: () => setParamsAction(void 0)
4198
+ })
4199
+ }) : null ]
4200
+ });
4201
+ }
4202
+
4203
+ const FOOTER_GAP_PX = 8, MORE_ACTIONS_LABEL = "More actions";
4204
+
4205
+ function TerminalFooter({actions: actions, activity: activity, instanceId: instanceId}) {
4206
+ const lock = useActionClusterState(), [only, ...rest] = actions;
4207
+ return only ? /* @__PURE__ */ jsx(ActionClusterProvider, {
4208
+ value: lock,
4209
+ children: rest.length === 0 ? /* @__PURE__ */ jsx(FooterRow, {
4210
+ children: /* @__PURE__ */ jsx(ActivityActionRow, {
4211
+ actionEval: only,
4212
+ activity: activity,
4213
+ instanceId: instanceId
4214
+ })
4215
+ }) : /* @__PURE__ */ jsx(FittedActions, {
4216
+ actions: actions,
4217
+ activity: activity,
4218
+ instanceId: instanceId
4219
+ })
4220
+ }) : null;
4221
+ }
4222
+
4223
+ function FooterRow({children: children, onElement: onElement}) {
4224
+ /* @__PURE__ */
4225
+ return jsx(Box, {
4226
+ padding: 3,
4227
+ children: /* @__PURE__ */ jsx(Flex, {
4228
+ align: "center",
4229
+ justify: "flex-end",
4230
+ ...onElement ? {
4231
+ ref: onElement
4232
+ } : {},
4233
+ style: {
4234
+ gap: FOOTER_GAP_PX
4235
+ },
4236
+ children: children
4237
+ })
4238
+ });
4239
+ }
4240
+
4241
+ function FittedActions({actions: actions, activity: activity, instanceId: instanceId}) {
4242
+ const [footerEl, setFooterEl] = useState(null), [measureEl, setMeasureEl] = useState(null), footerSize = useElementSize(footerEl), measureSize = useElementSize(measureEl), [inline, setInline] = useState(void 0);
4243
+ useLayoutEffect(() => {
4244
+ if (measureEl === null || footerSize === null || measureSize === null) return;
4245
+ const widths = Array.from(measureEl.children, clone => clone.getBoundingClientRect().width);
4246
+ setInline(inlineActionCount({
4247
+ available: footerSize.border.width,
4248
+ actionWidths: widths.slice(0, -1),
4249
+ menuWidth: widths.at(-1) ?? 0,
4250
+ gap: FOOTER_GAP_PX
4251
+ }));
4252
+ }, [ actions, footerSize, measureEl, measureSize ]);
4253
+ const overflow = inline === void 0 ? [] : actions.slice(inline);
4254
+ /* @__PURE__ */
4255
+ return jsxs(Fragment, {
4256
+ children: [
4257
+ /* @__PURE__ */ jsx(MeasureRow, {
4258
+ actions: actions,
4259
+ onElement: setMeasureEl
4260
+ }),
4261
+ /* @__PURE__ */ jsxs(FooterRow, {
4262
+ onElement: setFooterEl,
4263
+ children: [ inline === void 0 ? null : actions.slice(0, inline).map(a => /* @__PURE__ */ jsx(ActivityActionRow, {
4264
+ actionEval: a,
4265
+ activity: activity,
4266
+ instanceId: instanceId
4267
+ }, a.action.name)), overflow.length > 0 ? /* @__PURE__ */ jsx(ActionsMenuButton, {
4268
+ actions: overflow,
4269
+ activity: activity,
4270
+ instanceId: instanceId,
4271
+ label: MORE_ACTIONS_LABEL,
4272
+ mode: "default"
4273
+ }) : null ]
4274
+ }) ]
4275
+ });
4276
+ }
4277
+
4278
+ function MeasureRow({actions: actions, onElement: onElement}) {
4279
+ /* @__PURE__ */
4280
+ return jsx("div", {
4281
+ "aria-hidden": !0,
4282
+ style: {
4283
+ height: 0,
4284
+ overflow: "hidden"
4285
+ },
4286
+ children: /* @__PURE__ */ jsxs("div", {
4287
+ ref: onElement,
4288
+ style: {
4289
+ display: "inline-flex",
4290
+ whiteSpace: "nowrap"
4291
+ },
4292
+ children: [ actions.map(a => /* @__PURE__ */ jsx(Button, {
4293
+ as: "span",
4294
+ fontSize: 1,
4295
+ padding: 2,
4296
+ text: actionTriggerLabel(a)
4297
+ }, a.action.name)),
4298
+ /* @__PURE__ */ jsx(Button, {
4299
+ ...ACTIONS_MENU_TRIGGER,
4300
+ as: "span",
4301
+ text: MORE_ACTIONS_LABEL
4302
+ }) ]
4303
+ })
4304
+ });
4305
+ }
4306
+
3979
4307
  function ActivityDetailDialog({entry: entry, activityName: activityName, breadcrumb: breadcrumb, definition: definition, document: document, onClose: onClose}) {
3980
4308
  const detail = deriveActivityDetail({
3981
4309
  entry: entry,
@@ -3985,12 +4313,11 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
3985
4313
  const instanceId = entry.instance._id, notice = blockedNotice(detail), hasBody = detail.state.length > 0 || notice !== void 0;
3986
4314
  /* @__PURE__ */
3987
4315
  return jsxs(Dialog, {
3988
- footer: /* @__PURE__ */ jsx(TerminalFooter, {
3989
- activityName: activityName,
3990
- detail: detail,
3991
- instanceId: instanceId,
3992
- onClose: onClose
3993
- }),
4316
+ footer: detail.presentation.terminalActions.length > 0 ? /* @__PURE__ */ jsx(TerminalFooter, {
4317
+ actions: detail.presentation.terminalActions,
4318
+ activity: activityName,
4319
+ instanceId: instanceId
4320
+ }) : void 0,
3994
4321
  header: /* @__PURE__ */ jsx(Breadcrumb, {
3995
4322
  emphasizeCurrent: !0,
3996
4323
  segments: [ ...breadcrumb, detail.title ]
@@ -4159,8 +4486,11 @@ function FieldRow({field: field, detail: detail, entry: entry, activityName: act
4159
4486
  /* @__PURE__ */ jsx(FieldValue, {
4160
4487
  field: field
4161
4488
  }), assignField ? /* @__PURE__ */ jsx(AssignActivityControl, {
4162
- field: assignField,
4163
- instanceId: instanceId
4489
+ instanceId: instanceId,
4490
+ state: {
4491
+ kind: "editable",
4492
+ field: assignField
4493
+ }
4164
4494
  }) : null ]
4165
4495
  }) ]
4166
4496
  });
@@ -4193,31 +4523,6 @@ function blockedNotice(detail) {
4193
4523
  });
4194
4524
  }
4195
4525
 
4196
- function TerminalFooter({detail: detail, instanceId: instanceId, activityName: activityName, onClose: onClose}) {
4197
- /* @__PURE__ */
4198
- return jsx(Box, {
4199
- padding: 3,
4200
- children: /* @__PURE__ */ jsxs(Flex, {
4201
- align: "center",
4202
- gap: 2,
4203
- justify: "flex-end",
4204
- children: [
4205
- /* @__PURE__ */ jsx(Button, {
4206
- fontSize: 1,
4207
- mode: "bleed",
4208
- onClick: onClose,
4209
- padding: 2,
4210
- text: "Cancel"
4211
- }),
4212
- /* @__PURE__ */ jsx(TerminalActions, {
4213
- actions: detail.presentation.terminalActions,
4214
- activity: activityName,
4215
- instanceId: instanceId
4216
- }) ]
4217
- })
4218
- });
4219
- }
4220
-
4221
4526
  function EmptyState({action: action, description: description, icon: icon, title: title}) {
4222
4527
  /* @__PURE__ */
4223
4528
  return jsx(Card, {
@@ -4295,13 +4600,69 @@ function GroupHeading({count: count, title: title, end: end}) {
4295
4600
  });
4296
4601
  }
4297
4602
 
4603
+ function CopyDetailsButton({unreadable: unreadable}) {
4604
+ const toast = useClosableToast();
4605
+ /* @__PURE__ */
4606
+ return jsx(Button, {
4607
+ fontSize: 1,
4608
+ mode: "ghost",
4609
+ onClick: () => {
4610
+ navigator.clipboard.writeText(unreadableDocsReport(unreadable)).then(() => toast.push({
4611
+ status: "success",
4612
+ title: "Details copied"
4613
+ }), () => toast.push({
4614
+ status: "error",
4615
+ title: "Could not copy the details"
4616
+ }));
4617
+ },
4618
+ padding: 2,
4619
+ text: "Copy details"
4620
+ });
4621
+ }
4622
+
4623
+ function UnreadableDocsNote({unreadable: unreadable}) {
4624
+ if (unreadable.length === 0) return null;
4625
+ const label = unreadable.length === 1 ? "1 workflow document isn’t listed — this Studio can’t read it. Share the details with your Studio maintainers." : `${unreadable.length} workflow documents aren’t listed — this Studio can’t read them. Share the details with your Studio maintainers.`;
4626
+ /* @__PURE__ */
4627
+ return jsxs(Card, {
4628
+ padding: 2,
4629
+ paddingLeft: 3,
4630
+ radius: 3,
4631
+ style: {
4632
+ alignItems: "center",
4633
+ display: "flex"
4634
+ },
4635
+ tone: "caution",
4636
+ children: [
4637
+ /* @__PURE__ */ jsxs(Flex, {
4638
+ align: "center",
4639
+ flex: 1,
4640
+ gap: 3,
4641
+ children: [
4642
+ /* @__PURE__ */ jsx(Text, {
4643
+ muted: !0,
4644
+ size: 1,
4645
+ children: /* @__PURE__ */ jsx(WarningOutlineIcon, {})
4646
+ }),
4647
+ /* @__PURE__ */ jsx(Text, {
4648
+ muted: !0,
4649
+ size: 1,
4650
+ children: label
4651
+ }) ]
4652
+ }),
4653
+ /* @__PURE__ */ jsx(CopyDetailsButton, {
4654
+ unreadable: unreadable
4655
+ }) ]
4656
+ });
4657
+ }
4658
+
4298
4659
  const HEADLINES = {
4299
4660
  "model-ahead": "Workflow needs a newer engine",
4300
4661
  "shape-violation": "Unreadable workflow document"
4301
4662
  };
4302
4663
 
4303
4664
  function remediation(invalid) {
4304
- 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.`;
4665
+ return invalid.reason === "model-ahead" ? `"${invalid.documentId}" was created by a newer version of the workflow engine than this Studio can read. It needs a plugin update that supports the newer data model — share the details with your Studio maintainers.` : `This version of the Studio can't read "${invalid.documentId}" it was likely created by a newer or different version of the workflow tooling. Share the details with your Studio maintainers.`;
4305
4666
  }
4306
4667
 
4307
4668
  function InvalidDocNotice({invalid: invalid}) {
@@ -4312,20 +4673,22 @@ function InvalidDocNotice({invalid: invalid}) {
4312
4673
  radius: 2,
4313
4674
  tone: "caution",
4314
4675
  children: /* @__PURE__ */ jsxs(Stack, {
4315
- space: 2,
4676
+ space: 3,
4316
4677
  children: [
4317
- /* @__PURE__ */ jsx(HoverHint, {
4318
- text: invalid.message,
4319
- children: /* @__PURE__ */ jsx(Text, {
4320
- size: 1,
4321
- weight: "medium",
4322
- children: HEADLINES[invalid.reason]
4323
- })
4678
+ /* @__PURE__ */ jsx(Text, {
4679
+ size: 1,
4680
+ weight: "medium",
4681
+ children: HEADLINES[invalid.reason]
4324
4682
  }),
4325
4683
  /* @__PURE__ */ jsx(Text, {
4326
4684
  muted: !0,
4327
4685
  size: 1,
4328
4686
  children: remediation(invalid)
4687
+ }),
4688
+ /* @__PURE__ */ jsx(Flex, {
4689
+ children: /* @__PURE__ */ jsx(CopyDetailsButton, {
4690
+ unreadable: [ invalid ]
4691
+ })
4329
4692
  }) ]
4330
4693
  })
4331
4694
  });
@@ -4365,6 +4728,30 @@ function openActivityGone(args) {
4365
4728
  return evaluation !== void 0 && !evaluation.currentStage.activities.some(a => a.activity.name === target.activityName);
4366
4729
  }
4367
4730
 
4731
+ function HintedMenuButton({hint: hint, ...menuButtonProps}) {
4732
+ const [open, setOpen] = useState(!1), popover = {
4733
+ tone: "default",
4734
+ ...menuButtonProps.popover?.placement === void 0 ? {
4735
+ placement: "bottom-end",
4736
+ fallbackPlacements: [ "top-end" ]
4737
+ } : {},
4738
+ ...menuButtonProps.popover
4739
+ };
4740
+ return hint === void 0 ? /* @__PURE__ */ jsx(MenuButton, {
4741
+ ...menuButtonProps,
4742
+ popover: popover
4743
+ }) : /* @__PURE__ */ jsx(HoverHint, {
4744
+ disabled: open,
4745
+ text: hint,
4746
+ children: /* @__PURE__ */ jsx(MenuButton, {
4747
+ ...menuButtonProps,
4748
+ onClose: () => setOpen(!1),
4749
+ onOpen: () => setOpen(!0),
4750
+ popover: popover
4751
+ })
4752
+ });
4753
+ }
4754
+
4368
4755
  function subjectGdr(ctx) {
4369
4756
  return !ctx.subjectDocId || !ctx.mapping ? null : subjectGdrRef({
4370
4757
  mapping: ctx.mapping,
@@ -4412,6 +4799,10 @@ function missingInitFields(initFields, values) {
4412
4799
  return initFields.filter(entry => gatesStart(entry) && !isFieldFilled(entry, values[entry.name] ?? null));
4413
4800
  }
4414
4801
 
4802
+ function numericFilled(value) {
4803
+ return value !== null && value !== "" && Number.isFinite(Number(value));
4804
+ }
4805
+
4415
4806
  const filledByKind = {
4416
4807
  actor: value => value !== null,
4417
4808
  array: value => Array.isArray(value) && value.length > 0,
@@ -4419,7 +4810,8 @@ const filledByKind = {
4419
4810
  boolean: () => !0,
4420
4811
  "doc.ref": value => value !== null,
4421
4812
  "doc.refs": value => Array.isArray(value) && value.length > 0,
4422
- number: value => value !== null && value !== "" && Number.isFinite(Number(value)),
4813
+ number: numericFilled,
4814
+ progress: numericFilled,
4423
4815
  "release.ref": value => value !== null,
4424
4816
  subject: value => value !== null
4425
4817
  };
@@ -4437,7 +4829,14 @@ const asInitial = candidate => candidate, scalarInitialValue = (entry, value) =>
4437
4829
  type: entry.type,
4438
4830
  name: entry.name,
4439
4831
  value: value
4440
- }), initialValueByKind = {
4832
+ }), numericInitialValue = (entry, value) => {
4833
+ const n = Number(value);
4834
+ return Number.isFinite(n) ? asInitial({
4835
+ type: entry.type,
4836
+ name: entry.name,
4837
+ value: n
4838
+ }) : null;
4839
+ }, initialValueByKind = {
4441
4840
  actor: (entry, value) => asInitial({
4442
4841
  type: entry.type,
4443
4842
  name: entry.name,
@@ -4462,14 +4861,8 @@ const asInitial = candidate => candidate, scalarInitialValue = (entry, value) =>
4462
4861
  value: value
4463
4862
  }),
4464
4863
  subject: singleRefInitialValue,
4465
- number: (entry, value) => {
4466
- const n = Number(value);
4467
- return Number.isFinite(n) ? asInitial({
4468
- type: entry.type,
4469
- name: entry.name,
4470
- value: n
4471
- }) : null;
4472
- },
4864
+ number: numericInitialValue,
4865
+ progress: numericInitialValue,
4473
4866
  "release.ref": (entry, value) => asInitial({
4474
4867
  type: entry.type,
4475
4868
  name: entry.name,
@@ -4942,10 +5335,62 @@ function StartWorkflowMenuItem({mapping: mapping, docId: docId, initialValue: in
4942
5335
  },
4943
5336
  text: `Start ${mapping.label}`
4944
5337
  });
4945
- return tooltip ? /* @__PURE__ */ jsx(HoverHint, {
4946
- text: tooltip,
4947
- children: item
4948
- }) : item;
5338
+ return tooltip ? /* @__PURE__ */ jsx(HoverHint, {
5339
+ text: tooltip,
5340
+ children: item
5341
+ }) : item;
5342
+ }
5343
+
5344
+ function settledDim(settled) {
5345
+ return settled ? {
5346
+ opacity: .66
5347
+ } : void 0;
5348
+ }
5349
+
5350
+ function ActivityStatusLead({status: status}) {
5351
+ /* @__PURE__ */
5352
+ return jsx(HoverHint, {
5353
+ text: ACTIVITY_STATUS_LABEL[status],
5354
+ children: /* @__PURE__ */ jsx(ActivityStatusIcon, {
5355
+ status: status
5356
+ })
5357
+ });
5358
+ }
5359
+
5360
+ function ActivityRowTitle({dimmed: dimmed, children: children}) {
5361
+ /* @__PURE__ */
5362
+ return jsx(Text, {
5363
+ size: 1,
5364
+ style: {
5365
+ ...dimmed,
5366
+ minWidth: 0
5367
+ },
5368
+ textOverflow: "ellipsis",
5369
+ weight: "medium",
5370
+ children: children
5371
+ });
5372
+ }
5373
+
5374
+ function RowTerminalActions({actions: actions, activity: activity, instanceId: instanceId}) {
5375
+ const lock = useActionClusterState(), [only, ...rest] = actions;
5376
+ return only ? /* @__PURE__ */ jsx(ActionClusterProvider, {
5377
+ value: lock,
5378
+ children: rest.length === 0 ? /* @__PURE__ */ jsx(ActivityActionRow, {
5379
+ actionEval: only,
5380
+ activity: activity,
5381
+ chrome: {
5382
+ mode: "ghost",
5383
+ inButtonCard: !0
5384
+ },
5385
+ instanceId: instanceId
5386
+ }) : /* @__PURE__ */ jsx(ActionsMenuButton, {
5387
+ actions: actions,
5388
+ activity: activity,
5389
+ inButtonCard: !0,
5390
+ instanceId: instanceId,
5391
+ label: "Select action"
5392
+ })
5393
+ }) : null;
4949
5394
  }
4950
5395
 
4951
5396
  function TitleTag({hint: hint, tone: tone, children: children}) {
@@ -4964,54 +5409,34 @@ function TitleTag({hint: hint, tone: tone, children: children}) {
4964
5409
  });
4965
5410
  }
4966
5411
 
4967
- function ActivityRow({activityEval: activityEval, breadcrumb: breadcrumb, assigneeIds: assigneeIds, assignField: assignField, instanceId: instanceId, onOpen: onOpen, terminalActions: terminalActions = []}) {
4968
- const {activity: activity, status: status} = activityEval, settled = isTerminalActivityStatus(status), dimmed = settled ? {
4969
- opacity: .66
4970
- } : void 0;
4971
- let assignmentControl = null;
4972
- return assignField ? assignmentControl = /* @__PURE__ */ jsx(AssignActivityControl, {
4973
- assigneeIds: assigneeIds,
4974
- field: assignField,
4975
- instanceId: instanceId
4976
- }) : assigneeIds.length > 0 && (assignmentControl = /* @__PURE__ */ jsx(AvatarButton, {
4977
- "aria-label": "Assignees",
4978
- as: "span",
4979
- avatar: /* @__PURE__ */ jsx(UserAvatarGroup, {
4980
- ids: assigneeIds
4981
- })
4982
- })),
4983
- /* @__PURE__ */ jsxs(WorkRow, {
5412
+ function ActivityRow({face: face, breadcrumb: breadcrumb, assigneeIds: assigneeIds, assignState: assignState, dateControl: dateControl, instanceId: instanceId, onOpen: onOpen, terminalActions: terminalActions = []}) {
5413
+ const settled = isTerminalActivityStatus(face.status), dimmed = settledDim(settled);
5414
+ /* @__PURE__ */
5415
+ return jsxs(WorkRow, {
4984
5416
  end: /* @__PURE__ */ jsxs(Fragment, {
4985
- children: [ settled || terminalActions.length === 0 ? null : /* @__PURE__ */ jsx(TerminalActions, {
5417
+ children: [ settled || terminalActions.length === 0 ? null : /* @__PURE__ */ jsx(RowTerminalActions, {
4986
5418
  actions: terminalActions,
4987
- activity: activity.name,
4988
- inButtonCard: !0,
5419
+ activity: face.activityName,
5420
+ instanceId: instanceId
5421
+ }), dateControl,
5422
+ /* @__PURE__ */ jsx(AssignActivityControl, {
5423
+ assigneeIds: assigneeIds,
4989
5424
  instanceId: instanceId,
4990
- mode: "ghost"
4991
- }), assignmentControl ]
5425
+ state: assignState
5426
+ }) ]
4992
5427
  }),
4993
- lead:
4994
- /* @__PURE__ */ jsx(HoverHint, {
4995
- text: ACTIVITY_STATUS_LABEL[status],
4996
- children: /* @__PURE__ */ jsx(ActivityStatusIcon, {
4997
- status: status
4998
- })
5428
+ lead: /* @__PURE__ */ jsx(ActivityStatusLead, {
5429
+ status: face.status
4999
5430
  }),
5000
5431
  onOpen: onOpen,
5001
5432
  children: [
5002
- /* @__PURE__ */ jsx(Text, {
5003
- size: 1,
5004
- style: {
5005
- ...dimmed,
5006
- minWidth: 0
5007
- },
5008
- textOverflow: "ellipsis",
5009
- weight: "medium",
5010
- children: activityLabel(activity)
5011
- }), showsAutomatedPill(activityEval) ? /* @__PURE__ */ jsx(TitleTag, {
5433
+ /* @__PURE__ */ jsx(ActivityRowTitle, {
5434
+ dimmed: dimmed,
5435
+ children: face.title
5436
+ }), face.automated ? /* @__PURE__ */ jsx(TitleTag, {
5012
5437
  hint: "Completes on its own — no user action is needed",
5013
5438
  children: "Automated"
5014
- }) : null, showsBlockedTag(activityEval) ? /* @__PURE__ */ jsx(TitleTag, {
5439
+ }) : null, face.blocked ? /* @__PURE__ */ jsx(TitleTag, {
5015
5440
  hint: "Nothing can be done on this activity right now — open it to see why",
5016
5441
  tone: "caution",
5017
5442
  children: "Blocked"
@@ -5052,7 +5477,6 @@ function ForMeList({entry: entry, work: work, identity: identity, onOpenActivity
5052
5477
  activityState: activityState,
5053
5478
  editableFields: entry.evaluation?.editableFields
5054
5479
  }),
5055
- activityEval: activityEval,
5056
5480
  breadcrumb: breadcrumb,
5057
5481
  instanceId: instance._id,
5058
5482
  onOpen: () => onOpenActivity(activityEval.activity.name)
@@ -5096,33 +5520,12 @@ function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity}) {
5096
5520
  activityState: activityState,
5097
5521
  editableFields: entry.evaluation?.editableFields
5098
5522
  }),
5099
- activityEval: t,
5100
5523
  instanceId: entry.instance._id,
5101
5524
  onOpen: () => onOpenActivity(t.activity.name)
5102
5525
  }, t.activity.name))
5103
5526
  });
5104
5527
  }
5105
5528
 
5106
- function LoadingRow({label: label, padding: padding}) {
5107
- /* @__PURE__ */
5108
- return jsxs(Flex, {
5109
- align: "center",
5110
- gap: 2,
5111
- ...padding === void 0 ? {} : {
5112
- padding: padding
5113
- },
5114
- children: [
5115
- /* @__PURE__ */ jsx(Spinner, {
5116
- muted: !0
5117
- }),
5118
- /* @__PURE__ */ jsx(Text, {
5119
- muted: !0,
5120
- size: 1,
5121
- children: label
5122
- }) ]
5123
- });
5124
- }
5125
-
5126
5529
  function instanceNotice(entry) {
5127
5530
  const {instance: instance, committed: committed, evaluation: evaluation} = entry, state = terminalState(instance);
5128
5531
  if (state !== "in-flight")
@@ -5162,6 +5565,7 @@ const EDITOR_BY_KIND = {
5162
5565
  date: "scalar",
5163
5566
  datetime: "scalar",
5164
5567
  number: "scalar",
5568
+ progress: "scalar",
5165
5569
  string: "scalar",
5166
5570
  text: "scalar",
5167
5571
  url: "scalar"
@@ -5224,9 +5628,16 @@ function faceOf(args) {
5224
5628
  } : isSingleDocRefEntry(entry) && entry.value !== null ? {
5225
5629
  kind: "docRef",
5226
5630
  gdr: entry.value
5631
+ } : entry._type === "progress" && entry.value !== null ? {
5632
+ kind: "progress",
5633
+ value: entry.value
5227
5634
  } : todoShaped && entry._type === "array" ? textFace(todoTextOf(entry)) : textFace(textOf(entry));
5228
5635
  }
5229
5636
 
5637
+ function progressFillPercent(face) {
5638
+ if (face.kind === "progress") return clampProgress(face.value);
5639
+ }
5640
+
5230
5641
  function deriveFieldPills(args) {
5231
5642
  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 : findOpenStageEntry(instance)?.fields ?? [], entriesByName = new Map(entries.map(entry => [ entry.name, entry ])), editables = isEvaluationCurrent({
5232
5643
  committed: committed,
@@ -5367,6 +5778,15 @@ function SetFace({face: face}) {
5367
5778
  })
5368
5779
  }) : face.kind === "assignees" ? /* @__PURE__ */ jsx(AssigneesFace, {
5369
5780
  assignees: face.assignees
5781
+ }) : face.kind === "progress" ?
5782
+ /* @__PURE__ */ jsx(Text, {
5783
+ size: 1,
5784
+ style: {
5785
+ fontVariantNumeric: "tabular-nums",
5786
+ minWidth: "4ch",
5787
+ textAlign: "right"
5788
+ },
5789
+ children: progressCaption(face.value)
5370
5790
  }) : /* @__PURE__ */ jsx(FaceText, {
5371
5791
  children: face.kind === "text" ? face.text : ""
5372
5792
  });
@@ -5420,15 +5840,21 @@ function AssigneesFace({assignees: assignees}) {
5420
5840
  });
5421
5841
  }
5422
5842
 
5423
- function usePillChrome() {
5424
- const {color: color} = useTheme_v2();
5843
+ function usePillChrome(face) {
5844
+ const {color: color} = useTheme_v2(), fillPercent = face === void 0 ? void 0 : progressFillPercent(face), fill = `color-mix(in srgb, ${color.focusRing} 24%, transparent)`, progressFill = fillPercent === void 0 ? {} : {
5845
+ backgroundImage: `linear-gradient(${fill}, ${fill})`,
5846
+ backgroundRepeat: "no-repeat",
5847
+ backgroundSize: `${fillPercent}% 100%`,
5848
+ transition: "background-size 300ms ease"
5849
+ };
5425
5850
  return {
5426
5851
  mode: "bleed",
5427
5852
  padding: 2,
5428
5853
  radius: 6,
5429
5854
  style: {
5430
5855
  boxShadow: `inset 0 0 0 1px ${color.button.ghost.default.enabled.border}`,
5431
- maxWidth: "100%"
5856
+ maxWidth: "100%",
5857
+ ...progressFill
5432
5858
  }
5433
5859
  };
5434
5860
  }
@@ -5459,7 +5885,7 @@ function PillFaceContent({children: children}) {
5459
5885
  }
5460
5886
 
5461
5887
  function PillButton({children: children, pill: pill, hintDisabled: hintDisabled = !1, onClick: onClick}) {
5462
- const chrome = usePillChrome();
5888
+ const chrome = usePillChrome(pill.face);
5463
5889
  /* @__PURE__ */
5464
5890
  return jsx(PillHint, {
5465
5891
  disabled: hintDisabled,
@@ -5492,7 +5918,23 @@ function useFieldCommit({entry: entry, editability: editability}) {
5492
5918
  function PillPopover({pill: pill, content: content, open: open, onToggle: onToggle, onDismiss: onDismiss}) {
5493
5919
  /* @__PURE__ */
5494
5920
  return jsx(DismissablePopover, {
5495
- content: content,
5921
+ content: /* @__PURE__ */ jsxs(Stack, {
5922
+ gap: 1,
5923
+ children: [
5924
+ /* @__PURE__ */ jsx(Box, {
5925
+ padding: 3,
5926
+ paddingBottom: 0,
5927
+ style: {
5928
+ maxWidth: 360
5929
+ },
5930
+ children: /* @__PURE__ */ jsx(FieldHead, {
5931
+ label: pill.title,
5932
+ ...pill.description === void 0 ? {} : {
5933
+ description: pill.description
5934
+ }
5935
+ })
5936
+ }), content ]
5937
+ }),
5496
5938
  fallbackPlacements: [ "top-start" ],
5497
5939
  onDismiss: onDismiss,
5498
5940
  open: open,
@@ -5792,6 +6234,66 @@ function FieldPill({entry: entry, pill: pill, scope: scope}) {
5792
6234
  });
5793
6235
  }
5794
6236
 
6237
+ function Hairline({weight: weight, style: style}) {
6238
+ const {color: color} = useTheme_v2();
6239
+ /* @__PURE__ */
6240
+ return jsx("div", {
6241
+ style: {
6242
+ borderTop: `${weight}px solid ${color.border}`,
6243
+ ...style
6244
+ }
6245
+ });
6246
+ }
6247
+
6248
+ function CollapsibleBand({header: header, children: children, background: background = !1, defaultOpen: defaultOpen = !0, sticky: sticky = !1, title: title}) {
6249
+ const [open, setOpen] = useState(defaultOpen), verb = open ? "Collapse" : "Expand";
6250
+ /* @__PURE__ */
6251
+ return jsxs(Stack, {
6252
+ gap: 1,
6253
+ children: [
6254
+ /* @__PURE__ */ jsx(Card, {
6255
+ onDoubleClick: event => {
6256
+ event.target instanceof Element && event.target.closest("a, button") || setOpen(v => !v);
6257
+ },
6258
+ padding: 1,
6259
+ radius: 3,
6260
+ style: {
6261
+ userSelect: "none",
6262
+ ...sticky ? {
6263
+ position: "sticky",
6264
+ top: 0,
6265
+ zIndex: 1
6266
+ } : {}
6267
+ },
6268
+ tone: background ? "transparent" : "inherit",
6269
+ children: /* @__PURE__ */ jsxs(Flex, {
6270
+ align: "center",
6271
+ children: [
6272
+ /* @__PURE__ */ jsx(Button, {
6273
+ "aria-label": `${verb} ${title}`,
6274
+ fontSize: 1,
6275
+ icon: open ? ChevronDownIcon : ChevronRightIcon,
6276
+ mode: "bleed",
6277
+ onClick: () => setOpen(v => !v),
6278
+ padding: 2
6279
+ }), header ]
6280
+ })
6281
+ }), open ? children : null ]
6282
+ });
6283
+ }
6284
+
6285
+ function TrailingHairline() {
6286
+ /* @__PURE__ */
6287
+ return jsx(Box, {
6288
+ flex: 1,
6289
+ paddingLeft: 1,
6290
+ paddingRight: 1,
6291
+ children: /* @__PURE__ */ jsx(Hairline, {
6292
+ weight: .5
6293
+ })
6294
+ });
6295
+ }
6296
+
5795
6297
  const HISTORY_KIND_GROUPS = {
5796
6298
  Stages: [ "stageEntered", "stageExited", "transitionFired", "aborted" ],
5797
6299
  Activities: [ "activityStatusChanged" ],
@@ -6114,8 +6616,8 @@ function InstanceDebugMenu({entry: entry}) {
6114
6616
  });
6115
6617
  }
6116
6618
 
6117
- function InstanceAccordion({entry: entry, children: children}) {
6118
- const definition = useDefinition(entry), {instance: instance, ready: ready} = entry, settled = !isLiveEntry(entry), [open, setOpen] = useState(!settled), title = instanceTitle(instance, definition), badgeTrim = useBadgeCapTrim(), status = /* @__PURE__ */ jsxs(Flex, {
6619
+ function InstanceAccordion({entry: entry, children: children, defaultOpen: defaultOpen}) {
6620
+ const definition = useDefinition(entry), {instance: instance, ready: ready} = entry, settled = !isLiveEntry(entry), title = instanceTitle(instance, definition), badgeTrim = useBadgeCapTrim(), status = /* @__PURE__ */ jsxs(Flex, {
6119
6621
  align: "center",
6120
6622
  gap: 2,
6121
6623
  style: badgeTrim,
@@ -6125,60 +6627,46 @@ function InstanceAccordion({entry: entry, children: children}) {
6125
6627
  }), settled ? null : /* @__PURE__ */ jsx(OlderDefinitionBadge, {
6126
6628
  instance: instance
6127
6629
  }), !ready && entry.invalid === void 0 ? /* @__PURE__ */ jsx(Spinner, {
6128
- muted: !0
6630
+ muted: !0,
6631
+ size: 1
6129
6632
  }) : null ]
6130
6633
  });
6131
6634
  /* @__PURE__ */
6132
- return jsxs(Stack, {
6133
- gap: 2,
6134
- children: [
6135
- /* @__PURE__ */ jsx(Card, {
6136
- style: {
6137
- position: "sticky",
6138
- top: 0,
6139
- zIndex: 1
6140
- },
6141
- children: /* @__PURE__ */ jsxs(Flex, {
6635
+ return jsx(CollapsibleBand, {
6636
+ defaultOpen: defaultOpen ?? !settled,
6637
+ title: title,
6638
+ header: /* @__PURE__ */ jsxs(Fragment, {
6639
+ children: [
6640
+ /* @__PURE__ */ jsxs(Flex, {
6142
6641
  align: "center",
6143
- gap: 1,
6642
+ gap: 2,
6643
+ paddingLeft: 2,
6644
+ style: {
6645
+ minWidth: 0
6646
+ },
6144
6647
  children: [
6145
- /* @__PURE__ */ jsx(Box, {
6146
- flex: 1,
6147
- children: /* @__PURE__ */ jsx(Button, {
6148
- "aria-label": open ? `Collapse ${title}` : `Expand ${title}`,
6149
- mode: "bleed",
6150
- onClick: () => setOpen(v => !v),
6151
- paddingX: 2,
6152
- paddingY: 3,
6153
- radius: 3,
6154
- width: "fill",
6155
- children: /* @__PURE__ */ jsxs(Flex, {
6156
- align: "center",
6157
- gap: 2,
6158
- children: [
6159
- /* @__PURE__ */ jsx(Text, {
6160
- size: 1,
6161
- children: open ? /* @__PURE__ */ jsx(ChevronDownIcon, {}) : /* @__PURE__ */ jsx(ChevronRightIcon, {})
6162
- }),
6163
- /* @__PURE__ */ jsx(Text, {
6164
- size: 1,
6165
- weight: "medium",
6166
- children: title
6167
- }), status ]
6168
- })
6169
- })
6170
- }),
6171
- /* @__PURE__ */ jsx(InstanceDebugMenu, {
6172
- entry: entry
6173
- }) ]
6174
- })
6175
- }), open ?
6176
- /* @__PURE__ */ jsx(Stack, {
6648
+ /* @__PURE__ */ jsx(Text, {
6649
+ size: 1,
6650
+ style: {
6651
+ minWidth: 0
6652
+ },
6653
+ textOverflow: "ellipsis",
6654
+ weight: "medium",
6655
+ children: title
6656
+ }), status ]
6657
+ }),
6658
+ /* @__PURE__ */ jsx(TrailingHairline, {}),
6659
+ /* @__PURE__ */ jsx(InstanceDebugMenu, {
6660
+ entry: entry
6661
+ }) ]
6662
+ }),
6663
+ sticky: !0,
6664
+ children: /* @__PURE__ */ jsx(Stack, {
6177
6665
  gap: 3,
6178
6666
  paddingBottom: 2,
6179
6667
  paddingLeft: 5,
6180
6668
  children: children
6181
- }) : null ]
6669
+ })
6182
6670
  });
6183
6671
  }
6184
6672
 
@@ -6434,18 +6922,6 @@ const NODE_ICON = {
6434
6922
  done: CheckmarkCircleIcon
6435
6923
  };
6436
6924
 
6437
- function Hairline({style: style}) {
6438
- const {color: color} = useTheme_v2();
6439
- /* @__PURE__ */
6440
- return jsx(Card, {
6441
- style: {
6442
- height: "1px",
6443
- background: color.border,
6444
- ...style
6445
- }
6446
- });
6447
- }
6448
-
6449
6925
  function WorkflowSpine({definition: definition, evaluation: evaluation, instance: instance}) {
6450
6926
  const nodes = deriveSpine({
6451
6927
  definition: definition,
@@ -6464,7 +6940,8 @@ function WorkflowSpine({definition: definition, evaluation: evaluation, instance
6464
6940
  style: {
6465
6941
  transform: "translateY(-0.5px)",
6466
6942
  width: connectorWidth
6467
- }
6943
+ },
6944
+ weight: 1
6468
6945
  }) : null,
6469
6946
  /* @__PURE__ */ jsx(SpineNodeView, {
6470
6947
  definition: definition,
@@ -6569,7 +7046,9 @@ function StageOverviewCard({overview: overview}) {
6569
7046
  })
6570
7047
  }), hasLiveZone ? /* @__PURE__ */ jsxs(Fragment, {
6571
7048
  children: [
6572
- /* @__PURE__ */ jsx(Hairline, {}),
7049
+ /* @__PURE__ */ jsx(Hairline, {
7050
+ weight: 1
7051
+ }),
6573
7052
  /* @__PURE__ */ jsx(Box, {
6574
7053
  padding: 2,
6575
7054
  children: /* @__PURE__ */ jsxs(Stack, {
@@ -6622,31 +7101,32 @@ function AdvanceCard({advance: advance}) {
6622
7101
  }
6623
7102
 
6624
7103
  function StampLines({stamp: stamp}) {
6625
- const verb = stamp.kind === "entered" ? "Entered" : "Completed", ago = formatTimeAgo(stamp.at), absolute = formatShortDateTime(stamp.at);
6626
- return ago === "" ? /* @__PURE__ */ jsxs(Text, {
7104
+ const face = stampFace(stamp.kind === "entered" ? "Entered" : "Completed", stamp.at);
7105
+ return face.detail === void 0 ? /* @__PURE__ */ jsx(Text, {
6627
7106
  muted: !0,
6628
7107
  size: 1,
6629
- children: [ verb, " ", absolute ]
7108
+ children: face.lead
6630
7109
  }) : /* @__PURE__ */ jsxs(Stack, {
6631
7110
  gap: 3,
6632
7111
  children: [
6633
- /* @__PURE__ */ jsxs(Text, {
7112
+ /* @__PURE__ */ jsx(Text, {
6634
7113
  muted: !0,
6635
7114
  size: 1,
6636
- children: [ verb, " ", ago ]
7115
+ children: face.lead
6637
7116
  }),
6638
7117
  /* @__PURE__ */ jsx(Text, {
6639
7118
  muted: !0,
6640
7119
  size: 1,
6641
- children: absolute
7120
+ children: face.detail
6642
7121
  }) ]
6643
7122
  });
6644
7123
  }
6645
7124
 
6646
- function WorkflowInstanceSection({entry: entry, onOpenActivity: onOpenActivity}) {
7125
+ function WorkflowInstanceSection({defaultOpen: defaultOpen, entry: entry, onOpenActivity: onOpenActivity}) {
6647
7126
  const definition = useDefinition(entry), {instance: instance} = entry;
6648
7127
  /* @__PURE__ */
6649
7128
  return jsxs(InstanceAccordion, {
7129
+ defaultOpen: defaultOpen,
6650
7130
  entry: entry,
6651
7131
  children: [ instance.ancestors.length > 0 ? /* @__PURE__ */ jsx(PartOfBand, {
6652
7132
  instance: instance
@@ -6706,7 +7186,7 @@ function StageSection({entry: entry, onOpenActivity: onOpenActivity}) {
6706
7186
  });
6707
7187
  }
6708
7188
 
6709
- const WorkflowsView = props => {
7189
+ const VIEW_TAB_CODEC = workflowsTabCodec([ "overview", "for-me" ]), WorkflowsView = props => {
6710
7190
  const {documentId: documentId, schemaType: schemaType} = props, {isDocResolved: isDocResolved, mappings: mappings, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {entries: entries} = useWorkflowsForDocument(documentId), resolved = isDocResolved(documentId), docTypeMappings = mappingsForDocType(mappings, schemaType.name);
6711
7191
  if (entries.length === 0) return discoveryInvalid !== void 0 ? /* @__PURE__ */ jsx(Box, {
6712
7192
  padding: 4,
@@ -6767,14 +7247,8 @@ const GROUP_GAP = 1;
6767
7247
  function useWorkflowsTab() {
6768
7248
  const {params: params, setParams: setParams} = usePaneRouter();
6769
7249
  return {
6770
- view: params?.[WORKFLOWS_TAB_PARAM] === "for-me" ? "for-me" : "overview",
6771
- setView: next => {
6772
- const {[WORKFLOWS_TAB_PARAM]: _tab, ...rest} = params ?? {};
6773
- setParams(next === "overview" ? rest : {
6774
- ...rest,
6775
- [WORKFLOWS_TAB_PARAM]: next
6776
- });
6777
- }
7250
+ view: VIEW_TAB_CODEC.read(params),
7251
+ setView: next => setParams(VIEW_TAB_CODEC.write(params, next))
6778
7252
  };
6779
7253
  }
6780
7254
 
@@ -6793,7 +7267,8 @@ function useOpenActivity(entries) {
6793
7267
  }
6794
7268
 
6795
7269
  function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappings: docTypeMappings, initialValue: initialValue}) {
6796
- const identity = useAssignmentIdentity(), panelId = 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__ */ jsx(WorkflowInstanceSection, {
7270
+ const identity = useAssignmentIdentity(), panelId = useId(), {params: params} = usePaneRouter(), {view: view, setView: setView} = useWorkflowsTab(), {openActivity: openActivity, setOpenActivity: setOpenActivity, dialogEntry: dialogEntry} = useOpenActivity(entries), active = entries.filter(isLiveEntry), finished = entries.filter(e => !isLiveEntry(e)), forMe = forMeWorkOf(active, identity), [landing] = useState(() => focusedLanding(params?.[WORKFLOWS_FOCUS_PARAM], entries.map(e => e.instance._id))), assignedWorkUnknown = active.some(e => e.invalid !== void 0), sectionFor = e => /* @__PURE__ */ jsx(WorkflowInstanceSection, {
7271
+ defaultOpen: landingDefaultOpen(landing, e.instance._id),
6797
7272
  entry: e,
6798
7273
  onOpenActivity: activityName => setOpenActivity({
6799
7274
  instanceId: e.instance._id,
@@ -7110,20 +7585,51 @@ function indexDiscoveredInstances(args) {
7110
7585
  };
7111
7586
  }
7112
7587
 
7113
- function mergeEntries(args) {
7114
- const map = /* @__PURE__ */ new Map, merged = new Map([ ...args.seeded, ...args.discovered ]);
7115
- for (const [instanceId, inst] of merged) {
7116
- const report = args.reports.get(instanceId);
7117
- map.set(instanceId, {
7118
- instance: report?.evaluation?.instance ?? inst,
7119
- committed: inst,
7120
- evaluation: report?.evaluation,
7121
- ready: report?.ready ?? !1,
7122
- invalid: report?.invalid,
7123
- guards: report?.guards
7124
- });
7588
+ function sameInstance(a, b) {
7589
+ return a === b || a._id === b._id && a._rev === b._rev;
7590
+ }
7591
+
7592
+ function sameEntry(a, b) {
7593
+ 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;
7594
+ }
7595
+
7596
+ function reconciled(args) {
7597
+ const {entries: entries, previous: previous, same: same} = args, map = /* @__PURE__ */ new Map;
7598
+ let allReused = !0;
7599
+ for (const [key, next] of entries) {
7600
+ const prev = previous?.get(key), reusable = prev !== void 0 && same(prev, next);
7601
+ reusable || (allReused = !1), map.set(key, reusable ? prev : next);
7125
7602
  }
7126
- return map;
7603
+ return previous !== void 0 && allReused && previous.size === map.size ? previous : map;
7604
+ }
7605
+
7606
+ function entryOf(inst, report) {
7607
+ return {
7608
+ instance: report?.evaluation?.instance ?? inst,
7609
+ committed: inst,
7610
+ evaluation: report?.evaluation,
7611
+ ready: report?.ready ?? !1,
7612
+ invalid: report?.invalid,
7613
+ guards: report?.guards
7614
+ };
7615
+ }
7616
+
7617
+ function mergeEntries(args) {
7618
+ const {seeded: seeded, discovered: discovered, reports: reports, previous: previous} = args, merged = new Map([ ...seeded, ...discovered ]);
7619
+ return reconciled({
7620
+ entries: [ ...merged ].map(([instanceId, inst]) => [ instanceId, entryOf(inst, reports.get(instanceId)) ]),
7621
+ previous: previous,
7622
+ same: sameEntry
7623
+ });
7624
+ }
7625
+
7626
+ function deriveByDocument(args) {
7627
+ const {docIndex: docIndex, byInstance: byInstance, previous: previous} = args;
7628
+ return reconciled({
7629
+ entries: [ ...docIndex ].map(([docId, instanceIds]) => [ docId, instanceIds.map(id => byInstance.get(id)).filter(entry => entry !== void 0) ]),
7630
+ previous: previous,
7631
+ same: (a, b) => a.length === b.length && b.every((entry, i) => entry === a[i])
7632
+ });
7127
7633
  }
7128
7634
 
7129
7635
  const REPORT_KEYS = [ "evaluation", "ready", "invalid", "guards", "fireAction", "editField", "previewField", "discardFieldPreview" ];
@@ -7144,6 +7650,57 @@ function nextReports(prev, update) {
7144
7650
  return next.set(instanceId, report), next;
7145
7651
  }
7146
7652
 
7653
+ const REPORT_FLUSH_MS = 50, EMPTY_COMMITTED = {
7654
+ seeded: /* @__PURE__ */ new Map,
7655
+ discovered: /* @__PURE__ */ new Map,
7656
+ docIndex: /* @__PURE__ */ new Map
7657
+ };
7658
+
7659
+ function createEntriesStore() {
7660
+ let committed = EMPTY_COMMITTED, reports = /* @__PURE__ */ new Map, byInstance = /* @__PURE__ */ new Map, byDocument = /* @__PURE__ */ new Map;
7661
+ const listeners = /* @__PURE__ */ new Set;
7662
+ let flushTimer;
7663
+ const recompute = () => {
7664
+ flushTimer !== void 0 && (clearTimeout(flushTimer), flushTimer = void 0);
7665
+ const nextByInstance = mergeEntries({
7666
+ ...committed,
7667
+ reports: reports,
7668
+ previous: byInstance
7669
+ }), nextByDocument = deriveByDocument({
7670
+ docIndex: committed.docIndex,
7671
+ byInstance: nextByInstance,
7672
+ previous: byDocument
7673
+ });
7674
+ if (!(nextByInstance === byInstance && nextByDocument === byDocument)) {
7675
+ byInstance = nextByInstance, byDocument = nextByDocument;
7676
+ for (const listener of [ ...listeners ]) listener();
7677
+ }
7678
+ };
7679
+ return {
7680
+ setCommitted(inputs) {
7681
+ committed = inputs, recompute();
7682
+ },
7683
+ report(instanceId, report) {
7684
+ const next = nextReports(reports, {
7685
+ instanceId: instanceId,
7686
+ report: report
7687
+ });
7688
+ next !== reports && (reports = next, flushTimer ??= setTimeout(recompute, REPORT_FLUSH_MS));
7689
+ },
7690
+ getReport: instanceId => reports.get(instanceId),
7691
+ getInstance: instanceId => byInstance.get(instanceId),
7692
+ getDocument: docId => byDocument.get(docId) ?? EMPTY_ENTRIES,
7693
+ subscribe(listener) {
7694
+ return listeners.add(listener), () => {
7695
+ listeners.delete(listener);
7696
+ };
7697
+ },
7698
+ dispose() {
7699
+ flushTimer !== void 0 && clearTimeout(flushTimer), flushTimer = void 0, listeners.clear();
7700
+ }
7701
+ };
7702
+ }
7703
+
7147
7704
  const notReady = (..._args) => Promise.reject(new Error("workflow session not ready yet"));
7148
7705
 
7149
7706
  function workspaceParts(config) {
@@ -7193,10 +7750,10 @@ function InstanceEvaluator(props) {
7193
7750
  }
7194
7751
 
7195
7752
  function DiscoverySubscription(props) {
7196
- const {engine: engine, registered: registered, filter: filter, onSnapshot: onSnapshot} = props, {instances: instances, invalid: invalid} = useWorkflowInstances({
7753
+ const {engine: engine, registered: registered, filter: filter, onSnapshot: onSnapshot} = props, {instances: instances, unreadable: unreadable} = useWorkflowInstances({
7197
7754
  engine: engine,
7198
7755
  filter: filter
7199
- });
7756
+ }), invalid = unreadable[0];
7200
7757
  return useEffect(() => {
7201
7758
  onSnapshot({
7202
7759
  instances: instances,
@@ -7240,14 +7797,11 @@ function WorkflowProvider(props) {
7240
7797
  id: `${projectId}.${workflowDataset}`
7241
7798
  }), {ids: registered, acquire: register, release: unregister} = useRefcountedIds(), {ids: requested, acquire: requestInstance, release: releaseInstance} = useRefcountedIds(), [seeded, setSeeded] = useState(/* @__PURE__ */ new Map), seedInstance = useCallback(instance => {
7242
7799
  setSeeded(prev => new Map(prev).set(instance._id, instance));
7243
- }, []), [startRequest2, setStartRequest] = useState(void 0), openStartDialog = useCallback(r => setStartRequest(r), []), closeStartDialog = useCallback(() => setStartRequest(void 0), []), [reports, setReports] = useState(/* @__PURE__ */ new Map), onReport = useCallback((instanceId, report) => {
7244
- setReports(prev => nextReports(prev, {
7245
- instanceId: instanceId,
7246
- report: report
7247
- }));
7248
- }, []), reportsRef = useRef(reports);
7249
- reportsRef.current = reports;
7250
- const drainRef = useRef(void 0), drainCommitted = useCallback(async instanceId => {
7800
+ }, []), [startRequest2, setStartRequest] = useState(void 0), openStartDialog = useCallback(r => setStartRequest(r), []), closeStartDialog = useCallback(() => setStartRequest(void 0), []), [entriesStore] = useState(createEntriesStore);
7801
+ useEffect(() => () => entriesStore.dispose(), [ entriesStore ]);
7802
+ const onReport = useCallback((instanceId, report) => {
7803
+ entriesStore.report(instanceId, report);
7804
+ }, [ entriesStore ]), drainRef = useRef(void 0), drainCommitted = useCallback(async instanceId => {
7251
7805
  try {
7252
7806
  await (drainRef.current?.(instanceId));
7253
7807
  } catch (err) {
@@ -7256,7 +7810,7 @@ function WorkflowProvider(props) {
7256
7810
  });
7257
7811
  }
7258
7812
  }, []), fireActionFor = useCallback((instanceId, args) => {
7259
- const report = reportsRef.current.get(instanceId);
7813
+ const report = entriesStore.getReport(instanceId);
7260
7814
  return report ? report.fireAction({
7261
7815
  activity: args.activity,
7262
7816
  action: args.action,
@@ -7264,15 +7818,15 @@ function WorkflowProvider(props) {
7264
7818
  params: args.params
7265
7819
  } : {}
7266
7820
  }).then(async res => (await drainCommitted(instanceId), res)) : notReady(args);
7267
- }, [ drainCommitted ]), editFieldFor = useCallback((instanceId, args) => {
7268
- const report = reportsRef.current.get(instanceId);
7821
+ }, [ entriesStore, drainCommitted ]), editFieldFor = useCallback((instanceId, args) => {
7822
+ const report = entriesStore.getReport(instanceId);
7269
7823
  return report ? report.editField(args).then(async res => (await drainCommitted(instanceId),
7270
7824
  res)) : notReady(args);
7271
- }, [ drainCommitted ]), previewFieldFor = useCallback((instanceId, args) => {
7272
- reportsRef.current.get(instanceId)?.previewField(args);
7273
- }, []), discardFieldPreviewFor = useCallback((instanceId, target) => {
7274
- reportsRef.current.get(instanceId)?.discardFieldPreview(target);
7275
- }, []), drainEffectsFor = useCallback(async instanceId => {
7825
+ }, [ entriesStore, drainCommitted ]), previewFieldFor = useCallback((instanceId, args) => {
7826
+ entriesStore.getReport(instanceId)?.previewField(args);
7827
+ }, [ entriesStore ]), discardFieldPreviewFor = useCallback((instanceId, target) => {
7828
+ entriesStore.getReport(instanceId)?.discardFieldPreview(target);
7829
+ }, [ entriesStore ]), drainEffectsFor = useCallback(async instanceId => {
7276
7830
  await engine.drainEffects({
7277
7831
  instanceId: instanceId
7278
7832
  });
@@ -7306,23 +7860,14 @@ function WorkflowProvider(props) {
7306
7860
  const kept = [ ...prev ].filter(([id]) => !discovered.has(id));
7307
7861
  return kept.length === prev.size ? prev : new Map(kept);
7308
7862
  });
7309
- }, [ discovered ]);
7310
- const byInstance = useMemo(() => mergeEntries({
7311
- seeded: seeded,
7312
- discovered: discovered,
7313
- reports: reports
7314
- }), [ discovered, seeded, reports ]), byDocument = useMemo(() => {
7315
- const map = /* @__PURE__ */ new Map;
7316
- for (const [bareDocId, instanceIds] of docIndex) {
7317
- const entries = [];
7318
- for (const instanceId of instanceIds) {
7319
- const entry = byInstance.get(instanceId);
7320
- entry && entries.push(entry);
7321
- }
7322
- map.set(bareDocId, entries);
7323
- }
7324
- return map;
7325
- }, [ byInstance, docIndex ]), isDocResolved = useCallback(docId => resolvedDocs.has(docId), [ resolvedDocs ]), schemaContentTypes = useMemo(() => new Set(contentDocumentTypes(schema)), [ schema ]), {issues: issues, starts: starts, fields: fields, latestVersions: latestVersions, autoStartByType: autoStartByType, autoStartDefinitions: autoStartDefinitions} = useDeployedDefinitions({
7863
+ }, [ discovered ]), useLayoutEffect(() => {
7864
+ entriesStore.setCommitted({
7865
+ seeded: seeded,
7866
+ discovered: discovered,
7867
+ docIndex: docIndex
7868
+ });
7869
+ }, [ entriesStore, seeded, discovered, docIndex ]);
7870
+ const isDocResolved = useCallback(docId => resolvedDocs.has(docId), [ resolvedDocs ]), schemaContentTypes = useMemo(() => new Set(contentDocumentTypes(schema)), [ schema ]), {issues: issues, starts: starts, fields: fields, latestVersions: latestVersions, autoStartByType: autoStartByType, autoStartDefinitions: autoStartDefinitions} = useDeployedDefinitions({
7326
7871
  engine: engine,
7327
7872
  mappings: config.mappings,
7328
7873
  contentResource: binding.contentResource,
@@ -7335,8 +7880,7 @@ function WorkflowProvider(props) {
7335
7880
  register: register,
7336
7881
  unregister: unregister,
7337
7882
  isDocResolved: isDocResolved,
7338
- byDocument: byDocument,
7339
- byInstance: byInstance,
7883
+ entries: entriesStore,
7340
7884
  requestInstance: requestInstance,
7341
7885
  releaseInstance: releaseInstance,
7342
7886
  seedInstance: seedInstance,
@@ -7361,7 +7905,7 @@ function WorkflowProvider(props) {
7361
7905
  effectHandlers: engine.effectHandlers,
7362
7906
  autoStartByType: autoStartByType,
7363
7907
  autoStartDefinitions: autoStartDefinitions
7364
- }), [ 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 ]);
7908
+ }), [ register, unregister, isDocResolved, entriesStore, requestInstance, releaseInstance, seedInstance, openStartDialog, closeStartDialog, startRequest2, loading, discoveryInvalid, fireActionFor, editFieldFor, previewFieldFor, discardFieldPreviewFor, drainEffectsFor, completeEffectFor, engine, binding, config.mappings, issues, starts, latestVersions, autoStartByType, autoStartDefinitions ]);
7365
7909
  /* @__PURE__ */
7366
7910
  return jsxs(WorkflowContext.Provider, {
7367
7911
  value: value,
@@ -8564,19 +9108,32 @@ function TodoGlance({row: row}) {
8564
9108
  });
8565
9109
  }
8566
9110
 
9111
+ function finishedLinesOf(entries) {
9112
+ const activeTypes = new Set(entries.filter(isLiveEntry).map(entry => entry.instance.definition)), settled = entries.flatMap(entry => {
9113
+ const stamp = entry.instance.completedAt;
9114
+ return stamp === void 0 || activeTypes.has(entry.instance.definition) ? [] : [ {
9115
+ entry: entry,
9116
+ at: Date.parse(stamp)
9117
+ } ];
9118
+ }), latestByType = /* @__PURE__ */ new Map;
9119
+ for (const candidate of settled) {
9120
+ const best = latestByType.get(candidate.entry.instance.definition);
9121
+ (best === void 0 || candidate.at > best.at) && latestByType.set(candidate.entry.instance.definition, candidate);
9122
+ }
9123
+ return [ ...latestByType.values() ].sort((a, b) => b.at - a.at).map(({entry: entry}) => entry);
9124
+ }
9125
+
8567
9126
  function WorkflowFormStrip(props) {
8568
- const {mappings: mappings} = props, {isDocResolved: isDocResolved, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {docId: docId, entries: entries} = useWorkflowsForDocument(props.value?._id ?? null), {params: params, setParams: setParams} = usePaneRouter(), openWorkflowsView = () => setParams({
8569
- ...params,
8570
- view: WORKFLOWS_VIEW_ID
8571
- }), active = entries.filter(isLiveEntry), invalid = surfacedInvalid(discoveryInvalid, entries), resolved = docId !== void 0 && isDocResolved(docId);
9127
+ const {mappings: mappings} = props, {isDocResolved: isDocResolved, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {docId: docId, entries: entries} = useWorkflowsForDocument(props.value?._id ?? null), {params: params, setParams: setParams} = usePaneRouter(), openWorkflowsView = focusId => setParams(focusedViewParams(params, focusId)), active = entries.filter(isLiveEntry), finished = finishedLinesOf(entries), invalid = surfacedInvalid(discoveryInvalid, entries), resolved = docId !== void 0 && isDocResolved(docId);
8572
9128
  /* @__PURE__ */
8573
9129
  return jsxs(Stack, {
8574
9130
  gap: 4,
8575
9131
  children: [ invalid !== void 0 ? /* @__PURE__ */ jsx(InvalidDocNotice, {
8576
9132
  invalid: invalid
8577
- }) : null, active.length > 0 || resolved ? /* @__PURE__ */ jsx(StripLines, {
9133
+ }) : null, active.length > 0 || finished.length > 0 || resolved ? /* @__PURE__ */ jsx(StripLines, {
8578
9134
  active: active,
8579
9135
  docId: docId,
9136
+ finished: finished,
8580
9137
  initialValue: props.value ?? null,
8581
9138
  mappings: mappings,
8582
9139
  onOpen: openWorkflowsView,
@@ -8585,32 +9142,17 @@ function WorkflowFormStrip(props) {
8585
9142
  });
8586
9143
  }
8587
9144
 
8588
- function StripLines({active: active, docId: docId, initialValue: initialValue, mappings: mappings, onOpen: onOpen, resolved: resolved}) {
8589
- const identity = useAssignmentIdentity(), single = active.length === 1 ? active[0] : void 0;
9145
+ function StripLines({active: active, docId: docId, finished: finished, initialValue: initialValue, mappings: mappings, onOpen: onOpen, resolved: resolved}) {
8590
9146
  /* @__PURE__ */
8591
9147
  return jsxs(Stack, {
8592
9148
  gap: 1,
8593
- children: [ single !== void 0 ? /* @__PURE__ */ jsx(InstanceLine, {
8594
- entry: single,
8595
- onOpen: onOpen
8596
- }) : null, active.length > 1 ? /* @__PURE__ */ jsxs(StripLine, {
8597
- aside: /* @__PURE__ */ jsx(TaskCountSpinner, {
8598
- entries: active
8599
- }),
8600
- hint: lineHint(identity, forMeWorkOf(active, identity)),
8601
- onOpen: onOpen,
8602
- children: [
8603
- /* @__PURE__ */ jsx(Flex, {
8604
- children: /* @__PURE__ */ jsxs(Text, {
8605
- size: 1,
8606
- weight: "medium",
8607
- children: [ active.length, " active workflows" ]
8608
- })
8609
- }),
8610
- /* @__PURE__ */ jsx(TaskCountText, {
8611
- entries: active
8612
- }) ]
8613
- }) : null, resolved ? /* @__PURE__ */ jsx(Flex, {
9149
+ children: [ active.map(entry => /* @__PURE__ */ jsx(InstanceLine, {
9150
+ entry: entry,
9151
+ onOpen: () => onOpen(entry.instance._id)
9152
+ }, entry.instance._id)), finished.map(entry => /* @__PURE__ */ jsx(FinishedLine, {
9153
+ entry: entry,
9154
+ onOpen: () => onOpen(entry.instance._id)
9155
+ }, entry.instance._id)), resolved ? /* @__PURE__ */ jsx(Flex, {
8614
9156
  children: /* @__PURE__ */ jsx(StartWorkflowControl, {
8615
9157
  docId: docId,
8616
9158
  initialValue: initialValue,
@@ -8664,7 +9206,7 @@ function InstanceLine({entry: entry, onOpen: onOpen}) {
8664
9206
  /* @__PURE__ */
8665
9207
  return jsxs(StripLine, {
8666
9208
  aside: unprimed ? void 0 : /* @__PURE__ */ jsx(TaskCountSpinner, {
8667
- entries: [ entry ]
9209
+ entry: entry
8668
9210
  }),
8669
9211
  hint: lineHint(identity, forMeWorkOf([ entry ], identity)),
8670
9212
  onOpen: onOpen,
@@ -8683,34 +9225,69 @@ function InstanceLine({entry: entry, onOpen: onOpen}) {
8683
9225
  title: stageTitle(definition, entry.instance.currentStage)
8684
9226
  }),
8685
9227
  /* @__PURE__ */ jsx(TaskCountText, {
8686
- entries: [ entry ]
9228
+ entry: entry
8687
9229
  }) ]
8688
9230
  }) ]
8689
9231
  });
8690
9232
  }
8691
9233
 
8692
- function useTaskCountState(entries) {
8693
- const identity = useAssignmentIdentity(), freshness = entries.filter(e => e.invalid === void 0).map(e => evaluationFreshness({
8694
- committed: e.committed,
8695
- evaluation: e.evaluation
8696
- }));
8697
- return !identity || freshness.includes("pending") ? {
9234
+ function FinishedLine({entry: entry, onOpen: onOpen}) {
9235
+ const definition = useDefinition(entry), badgeTrim = useBadgeCapTrim(), {instance: instance} = entry;
9236
+ /* @__PURE__ */
9237
+ return jsxs(StripLine, {
9238
+ hint: finishedHint(instance),
9239
+ onOpen: onOpen,
9240
+ children: [
9241
+ /* @__PURE__ */ jsx(Flex, {
9242
+ children: /* @__PURE__ */ jsx(Text, {
9243
+ size: 1,
9244
+ weight: "medium",
9245
+ children: instanceTitle(instance, definition)
9246
+ })
9247
+ }), terminalState(instance) === "aborted" ? /* @__PURE__ */ jsx(Flex, {
9248
+ style: badgeTrim,
9249
+ children: /* @__PURE__ */ jsx(AbortedBadge, {
9250
+ instance: instance
9251
+ })
9252
+ }) : /* @__PURE__ */ jsx(StageFace, {
9253
+ completed: !0,
9254
+ title: stageTitle(definition, instance.currentStage)
9255
+ }) ]
9256
+ });
9257
+ }
9258
+
9259
+ function finishedHint(instance) {
9260
+ const verb = terminalState(instance) === "aborted" ? "Aborted" : "Completed", face = stampFace(verb, instance.completedAt ?? "");
9261
+ return {
9262
+ text: face.lead,
9263
+ ...face.detail === void 0 ? {} : {
9264
+ description: face.detail
9265
+ }
9266
+ };
9267
+ }
9268
+
9269
+ function useTaskCountState(entry) {
9270
+ const identity = useAssignmentIdentity(), freshness = entry.invalid === void 0 ? evaluationFreshness({
9271
+ committed: entry.committed,
9272
+ evaluation: entry.evaluation
9273
+ }) : void 0;
9274
+ return !identity || freshness === "pending" ? {
8698
9275
  kind: "pending"
8699
- } : freshness.includes("stale") ? {
9276
+ } : freshness === "stale" ? {
8700
9277
  kind: "stale"
8701
9278
  } : {
8702
9279
  kind: "ready",
8703
- tasks: entries.reduce((n, e) => n + assignedTaskCount({
8704
- instance: e.instance,
8705
- committed: e.committed,
8706
- evaluation: e.evaluation,
9280
+ tasks: assignedTaskCount({
9281
+ instance: entry.instance,
9282
+ committed: entry.committed,
9283
+ evaluation: entry.evaluation,
8707
9284
  who: identity
8708
- }), 0)
9285
+ })
8709
9286
  };
8710
9287
  }
8711
9288
 
8712
- function TaskCountText({entries: entries}) {
8713
- const state = useTaskCountState(entries);
9289
+ function TaskCountText({entry: entry}) {
9290
+ const state = useTaskCountState(entry);
8714
9291
  return state.kind === "pending" || state.kind === "ready" && state.tasks === 0 ? null : /* @__PURE__ */ jsx(Text, {
8715
9292
  muted: !0,
8716
9293
  size: 1,
@@ -8718,8 +9295,8 @@ function TaskCountText({entries: entries}) {
8718
9295
  });
8719
9296
  }
8720
9297
 
8721
- function TaskCountSpinner({entries: entries}) {
8722
- return useTaskCountState(entries).kind !== "pending" ? null : /* @__PURE__ */ jsx(Flex, {
9298
+ function TaskCountSpinner({entry: entry}) {
9299
+ return useTaskCountState(entry).kind !== "pending" ? null : /* @__PURE__ */ jsx(Flex, {
8723
9300
  paddingLeft: 1,
8724
9301
  children: /* @__PURE__ */ jsx(Spinner, {
8725
9302
  muted: !0,
@@ -8892,4 +9469,4 @@ function pillTone(args) {
8892
9469
  return args.state === "aborted" ? "critical" : args.isTerminal ? "positive" : args.stageKnown ? "primary" : "default";
8893
9470
  }
8894
9471
 
8895
- export { AbortedBadge, ActivitiesList, ActivityDetailDialog, ActivityStatusIcon, DismissablePopover, DocPreviewLink, DocRefFace, GroupHeading, HintedMenuButton, HoverHint, InvalidDocNotice, LoadingRow, StageChip, StageGlyph, StaleLock, TodoItemsList, UserAvatarGroup, WorkflowStagePreview, assigneeUserIdsOf, definitionSnapshotOf, describeError, findActivity, formatDate, gdrLocality, instanceBreadcrumb, instanceNotice, instanceTitle, isActivityAssignedTo, isEvaluationStale, isOpenActivityStatus, mappingForDocType, openActivityGone, parseStoredDateValue, readDeployedDefinitions, stageTitle, stateByActivity, useAssignmentIdentity, useDefinition, useDocLink, useProjectMembers, useUserDisplays, useWorkflowContext, useWorkflowInstanceEntry, workflowDefaultDocumentNode, workflowDocTypes, workflowStudioPlugin, workflowsView };
9472
+ export { AbortedBadge, ActivitiesList, ActivityDetailDialog, ActivityRow, BreadcrumbTail, CollapsibleBand, DismissablePopover, DocPreviewLink, DocRefFace, GroupHeading, HoverHint, InvalidDocNotice, LinkChip, LoadingRow, StageChip, StageFace, StaleLock, TabSwitch, TodoItemsList, TrailingHairline, UnreadableDocsNote, WorkflowStagePreview, activityRowProps, assigneeUserIdsOf, committedRowFace, dateControlKind, dateControlValue, definitionSnapshotOf, describeError, editTargetOf, findActivity, findActivityNode, formatDate, gdrLocality, instanceBreadcrumb, instanceNotice, instanceTitle, isActivityAssignedTo, isEvaluationStale, isOpenActivityStatus, mappingForDocType, openActivityGone, parseDateFieldValue, parseStoredDateValue, readDeployedDefinitions, rowAssignControlState, rowDateControlState, serializeDateFieldValue, stageTitle, stateByActivity, stopRowClick, stopRowMouseDown, useAssignmentIdentity, useDefinition, useProjectMembers, useSaveField, useUserDisplays, useWorkflowContext, useWorkflowInstanceEntry, workflowDefaultDocumentNode, workflowDocTypes, workflowStudioPlugin, workflowsTabCodec, workflowsView };