@sanity/workflow-studio-plugin 0.26.0 → 0.28.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.
@@ -2,7 +2,7 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
 
3
3
  import { TimelineIcon } from "@sanity/icons/Timeline";
4
4
 
5
- import { Popover, Tooltip, Box, Stack, Text, useClickOutsideEvent, Flex, TextInput, Button, Select, Checkbox, Autocomplete, Card, TextArea, useToast, Dialog, Switch, TextSkeleton, Code, useTheme_v2, Spinner, Badge, MenuButton, Menu, MenuItem, useElementSize, TabList, Tab, TabPanel } from "@sanity/ui";
5
+ import { Popover, Tooltip, Box, Stack, Text, useClickOutsideEvent, Flex, TextInput, Button, Select, Checkbox, Autocomplete, Card, TextArea, useToast, Dialog, Switch, TextSkeleton, Code, useTheme_v2, Spinner, Badge, MenuButton, Menu, MenuItem, useElementSize, MenuDivider, TabList, Tab, TabPanel } from "@sanity/ui";
6
6
 
7
7
  import { editFieldTarget, useWorkflowTelemetry, useRefcountedIds } from "@sanity/workflow-react";
8
8
 
@@ -10,7 +10,7 @@ import { createContext, useContext, useEffect, useSyncExternalStore, useCallback
10
10
 
11
11
  import { usePaneRouter } from "sanity/structure";
12
12
 
13
- import { WORKFLOW_INSTANCE_TYPE, WORKFLOW_DEFINITION_TYPE, GUARD_DOC_TYPE, tryParseGdr, resourceFromParsed, latestDeployedDefinitions, isStartableDefinition, isSubjectEntry, isInputSourced, acceptsDocumentType, initialFieldIssues, gdrRef, releaseRef, refKindAcceptsTypes, rejectedRefTypes, ActionDisabledError, MutationGuardDeniedError, EditFieldDeniedError, errorMessage, terminalState, findOpenStageEntry, actionRendering, isTodoListEntry, isTodoListItem, isTerminalActivityStatus, describeCondition, sentenceCase, checklistLines, findCurrentActivityEntry, ENGINE_API_VERSION, scalarValidationIssues, classifyPrincipalId, toBareId, isSingleDocRefEntry, isGdr, resolveFieldEntry, isSingleDocRefKind, isNotesEntry, parseDefinitionSnapshot, isUnprimed, isTerminalStage, DEFAULT_TRANSITION_WHEN, instancesQuery, assertReadableModel, projectStartSliceRow, StartNotSettledError, StartNotAllowedError, humanize, unboundRequirementReads, explainStartRequirement, singleSubjectRequirementRefused, evaluateStartFilter, startKindOf, hasSingleSubjectRequirement, readsRootDocument, latestDefinitionsGroq, gdrFromResource, subscriptionDocumentsForInstance, aclPathForResource, definitionLookupGroq, instanceDocId, missingRequiredInputs, documentActionDenials } from "@sanity/workflow-engine";
13
+ import { WORKFLOW_INSTANCE_TYPE, WORKFLOW_DEFINITION_TYPE, GUARD_DOC_TYPE, tryParseGdr, resourceFromParsed, latestDeployedDefinitions, isStartableDefinition, isSubjectEntry, isInputSourced, acceptsDocumentType, initialFieldIssues, gdrRef, releaseRef, refKindAcceptsTypes, rejectedRefTypes, ActionDisabledError, MutationGuardDeniedError, EditFieldDeniedError, errorMessage, terminalState, findOpenStageEntry, actionRendering, isTodoListEntry, isTodoListItem, isTerminalActivityStatus, describeCondition, sentenceCase, checklistLines, findCurrentActivityEntry, ENGINE_API_VERSION, scalarValidationIssues, classifyPrincipalId, userLoginProvider, toBareId, isSingleDocRefEntry, isGdr, resolveFieldEntry, isSingleDocRefKind, isNotesEntry, parseDefinitionSnapshot, isUnprimed, isTerminalStage, DEFAULT_TRANSITION_WHEN, instancesQuery, assertReadableModel, projectStartSliceRow, StartNotSettledError, StartNotAllowedError, isRevisionConflict, humanize, unboundRequirementReads, explainStartRequirement, singleSubjectRequirementRefused, evaluateStartFilter, startKindOf, hasSingleSubjectRequirement, readsRootDocument, latestDefinitionsGroq, gdrFromResource, subscriptionDocumentsForInstance, aclPathForResource, definitionLookupGroq, instanceDocId, missingRequiredInputs, documentActionDenials } from "@sanity/workflow-engine";
14
14
 
15
15
  import { CheckmarkIcon } from "@sanity/icons/Checkmark";
16
16
 
@@ -18,7 +18,7 @@ import { CloseIcon } from "@sanity/icons/Close";
18
18
 
19
19
  import { formatDistanceToNow } from "date-fns/formatDistanceToNow";
20
20
 
21
- import { sameAssignee, projectMemberRow, ClearableDatePicker, MemberPicker as MemberPicker$1, AssigneePicker as AssigneePicker$1, MemberAvatar, AssigneeBadges as AssigneeBadges$1, AssigneeBadge as AssigneeBadge$1, ProgressBar, MemberAvatarGroup, clampProgress, progressCaption, DatePicker, roleListLabel } from "@sanity/workflow-components";
21
+ import { sameAssignee, projectMemberRow, ClearableDatePicker, AssigneePicker as AssigneePicker$1, MemberPicker as MemberPicker$1, MemberAvatar, AssigneeBadges as AssigneeBadges$1, AssigneeBadge as AssigneeBadge$1, ProgressBar, memberRoleFor, AssigneeStack, roleLabel, clampProgress, MemberAvatarGroup, progressCaption, DatePicker, roleLabelFor, roleListLabel } from "@sanity/workflow-components";
22
22
 
23
23
  import { getPublishedId, getDraftId } from "@sanity/client/csm";
24
24
 
@@ -60,18 +60,26 @@ import { RemoveCircleIcon } from "@sanity/icons/RemoveCircle";
60
60
 
61
61
  import { AddIcon } from "@sanity/icons/Add";
62
62
 
63
+ import { UlistIcon } from "@sanity/icons/Ulist";
64
+
65
+ import { CheckmarkCircleIcon } from "@sanity/icons/CheckmarkCircle";
66
+
63
67
  import { BoltIcon } from "@sanity/icons/Bolt";
64
68
 
65
69
  import { ChevronDownIcon } from "@sanity/icons/ChevronDown";
66
70
 
67
- import { CheckmarkCircleIcon } from "@sanity/icons/CheckmarkCircle";
71
+ import pluralize from "pluralize-esm";
68
72
 
69
- import { UlistIcon } from "@sanity/icons/Ulist";
73
+ import { EllipsisHorizontalIcon } from "@sanity/icons/EllipsisHorizontal";
70
74
 
71
- import pluralize from "pluralize-esm";
75
+ import { ClockIcon } from "@sanity/icons/Clock";
76
+
77
+ import { CogIcon } from "@sanity/icons/Cog";
72
78
 
73
79
  import { ArrowUpIcon } from "@sanity/icons/ArrowUp";
74
80
 
81
+ import { ErrorOutlineIcon } from "@sanity/icons/ErrorOutline";
82
+
75
83
  import { of } from "rxjs";
76
84
 
77
85
  import { switchMap, distinctUntilChanged } from "rxjs/operators";
@@ -359,13 +367,7 @@ function landingDefaultOpen(landing, instanceId) {
359
367
  if (!(landing === void 0 || !landing.instanceIds.includes(instanceId))) return instanceId === landing.focusedId;
360
368
  }
361
369
 
362
- const WORKFLOWS_TOOL_NAME = "workflows";
363
-
364
- function workflowsToolAvailable(tools) {
365
- return tools.some(tool => tool.name === WORKFLOWS_TOOL_NAME);
366
- }
367
-
368
- const WORKFLOW_INTENT = "workflow";
370
+ const WORKFLOWS_TOOL_NAME = "workflows", WORKFLOW_INTENT = "workflow";
369
371
 
370
372
  function pathSegmentName(args) {
371
373
  return args.names.find(name => name === args.segment);
@@ -438,6 +440,10 @@ function describeEditDisabledReason(reason) {
438
440
  return editDisabledSentence[reason.kind](reason);
439
441
  }
440
442
 
443
+ function reasonRestatesInstanceState(reason) {
444
+ return reason.kind === "instance-aborted" || reason.kind === "instance-completed";
445
+ }
446
+
441
447
  function formatDateTime(value) {
442
448
  if (!value) return "—";
443
449
  const d = new Date(value);
@@ -756,6 +762,11 @@ function changedAssignee(current, next) {
756
762
  return next.find(item => !current.some(existing => sameAssignee(item, existing))) ?? current.find(item => !next.some(existing => sameAssignee(item, existing)));
757
763
  }
758
764
 
765
+ function singleAssigneePick(current, next) {
766
+ const changed = changedAssignee(current ? [ current ] : [], next);
767
+ if (changed !== void 0) return current && sameAssignee(changed, current) ? null : changed;
768
+ }
769
+
759
770
  function isAssigneeShape(value) {
760
771
  if (typeof value != "object" || value === null) return !1;
761
772
  const {type: type, id: id, role: role} = value;
@@ -768,22 +779,44 @@ function isActorShape(value) {
768
779
  return typeof id != "string" ? !1 : roles === void 0 || Array.isArray(roles) && roles.every(r => typeof r == "string");
769
780
  }
770
781
 
771
- function assigneeUserIdsOf(entries) {
772
- const ids = entries.flatMap(entry => entry._type === "assignees" ? entry.value.flatMap(a => a.type === "user" ? [ a.id ] : []) : entry._type === "assignee" ? entry.value?.type === "user" ? [ entry.value.id ] : [] : []);
773
- return [ ...new Set(ids) ];
782
+ function assigneesOf(entries) {
783
+ return entries.flatMap(entry => entry._type === "assignees" ? entry.value : []);
784
+ }
785
+
786
+ function assigneeEdit(current, next) {
787
+ const changed = changedAssignee(current, next);
788
+ if (changed !== void 0) return next.some(item => sameAssignee(item, changed)) ? {
789
+ mode: "append",
790
+ value: changed
791
+ } : {
792
+ mode: "set",
793
+ value: next
794
+ };
774
795
  }
775
796
 
776
797
  function isItemAssignedTo(item, who) {
777
798
  return item.assignee != null && assigneeMatches(item.assignee, who);
778
799
  }
779
800
 
801
+ function matchingAssignees(state, who) {
802
+ return state.flatMap(entry => entry._type === "assignees" ? entry.value.filter(a => assigneeMatches(a, who)) : entry._type === "assignee" && entry.value !== null && assigneeMatches(entry.value, who) ? [ entry.value ] : []);
803
+ }
804
+
780
805
  function isActivityAssignedTo(args) {
781
- return args.state.some(entry => entry._type === "assignees" ? entry.value.some(a => assigneeMatches(a, args.who)) : entry._type === "assignee" ? entry.value !== null && assigneeMatches(entry.value, args.who) : !1);
806
+ return matchingAssignees(args.state, args.who).length > 0;
807
+ }
808
+
809
+ function isRoleAssignedTo(args) {
810
+ return matchingAssignees(args.state, args.who).some(member => member.type === "role");
811
+ }
812
+
813
+ function isItemRoleAssignedTo(item, who) {
814
+ return isItemAssignedTo(item, who) && item.assignee?.type === "role";
782
815
  }
783
816
 
784
817
  function assignedWork(args) {
785
- const {instance: instance, committed: committed, who: who} = args, evaluation = args.staleEvaluation === "keep" || isEvaluationCurrent(args) ? args.evaluation : void 0, byActivity = stateByActivity(instance), activities = (evaluation?.currentStage.activities ?? []).filter(t => isActivityAssignedTo({
786
- state: byActivity.get(t.activity.name) ?? [],
818
+ const {instance: instance, committed: committed, who: who} = args, evaluation = args.staleEvaluation === "keep" || isEvaluationCurrent(args) ? args.evaluation : void 0, byActivity = stateByActivity(instance), stateOf = t => byActivity.get(t.activity.name) ?? [], activities = (evaluation?.currentStage.activities ?? []).filter(t => isActivityAssignedTo({
819
+ state: stateOf(t),
787
820
  who: who
788
821
  })), items = deriveTodoItems({
789
822
  instance: instance,
@@ -793,7 +826,11 @@ function assignedWork(args) {
793
826
  return {
794
827
  activities: activities,
795
828
  openItems: items.filter(row => !isTodoDone(row.item)),
796
- doneItems: items.filter(row => isTodoDone(row.item))
829
+ doneItems: items.filter(row => isTodoDone(row.item)),
830
+ roleMatched: activities.some(t => isRoleAssignedTo({
831
+ state: stateOf(t),
832
+ who: who
833
+ })) || items.some(row => isItemRoleAssignedTo(row.item, who))
797
834
  };
798
835
  }
799
836
 
@@ -842,7 +879,8 @@ function forMeWorkOf(entries, identity) {
842
879
  return {
843
880
  work: work,
844
881
  count: work.reduce((n, {count: count}) => n + count, 0),
845
- hasRows: work.some(({shown: shown}) => hasAssignedRows(shown))
882
+ hasRows: work.some(({shown: shown}) => hasAssignedRows(shown)),
883
+ hasRoleMatched: work.some(({shown: shown}) => shown.roleMatched)
846
884
  };
847
885
  }
848
886
 
@@ -1100,7 +1138,7 @@ function activityRowProps(args) {
1100
1138
  blocked: showsBlockedTag(activityEval)
1101
1139
  },
1102
1140
  assignState: rowAssignControlState(controlArgs),
1103
- assigneeIds: assigneeUserIdsOf(state),
1141
+ assignees: assigneesOf(state),
1104
1142
  dateState: rowDateControlState(controlArgs),
1105
1143
  dueDates: dueDatesOf(state),
1106
1144
  terminalActions: rowTerminalActions({
@@ -1408,15 +1446,18 @@ function projectMembersFrom(users) {
1408
1446
  sanityUserId: profile?.sanityUserId,
1409
1447
  displayName: profile?.displayName,
1410
1448
  email: profile?.email,
1411
- imageUrl: profile?.imageUrl ?? void 0
1449
+ imageUrl: profile?.imageUrl ?? void 0,
1450
+ loginProvider: userLoginProvider(profile),
1451
+ isCurrentUser: membership.isCurrentUser
1412
1452
  }));
1413
1453
  return projectMembersByUsers.set(users, members), members;
1414
1454
  }
1415
1455
 
1416
1456
  function useProjectMembers() {
1417
- const {users: users, loading: loading, error: error} = useStudioProjectUsers();
1457
+ const {users: users, roles: roles, loading: loading, error: error} = useStudioProjectUsers();
1418
1458
  return {
1419
1459
  members: useMemo(() => projectMembersFrom(users), [ users ]),
1460
+ roles: roles,
1420
1461
  loading: loading,
1421
1462
  error: error === void 0 ? void 0 : errorMessage(error)
1422
1463
  };
@@ -1770,10 +1811,12 @@ function PickedValueRow({children: children, onRequestChange: onRequestChange})
1770
1811
  }
1771
1812
 
1772
1813
  function MemberPicker({selectedIds: selectedIds, onSelect: onSelect, unassignRow: unassignRow = !1}) {
1773
- const state = useProjectMembers();
1814
+ const {error: error, loading: loading, members: members} = useProjectMembers();
1774
1815
  /* @__PURE__ */
1775
1816
  return jsx(MemberPicker$1, {
1776
- ...state,
1817
+ error: error,
1818
+ loading: loading,
1819
+ members: members,
1777
1820
  onSelect: onSelect,
1778
1821
  selectedIds: selectedIds,
1779
1822
  unassignRow: unassignRow
@@ -1790,6 +1833,17 @@ function AssigneePicker({value: value, onChange: onChange}) {
1790
1833
  });
1791
1834
  }
1792
1835
 
1836
+ function SingleAssigneePicker({current: current, onPick: onPick}) {
1837
+ /* @__PURE__ */
1838
+ return jsx(AssigneePicker, {
1839
+ onChange: next => {
1840
+ const picked = singleAssigneePick(current, next);
1841
+ picked !== void 0 && onPick(picked);
1842
+ },
1843
+ value: current ? [ current ] : []
1844
+ });
1845
+ }
1846
+
1793
1847
  function MemberPickerButton({label: label = "Select member…", onChange: onChange, selectedIds: selectedIds, unassignRow: unassignRow = !1, value: value}) {
1794
1848
  const [open, setOpen] = useState(!1), {imageUrl: imageUrl} = useUserDisplay(value?.id ?? ""), selected = selectedIds ?? (value ? /* @__PURE__ */ new Set([ value.id ]) : /* @__PURE__ */ new Set), toggle = () => setOpen(v => !v);
1795
1849
  /* @__PURE__ */
@@ -1799,7 +1853,7 @@ function MemberPickerButton({label: label = "Select member…", onChange: onChan
1799
1853
  setOpen(!1), onChange(member && !selected.has(member.id) ? {
1800
1854
  id: member.id,
1801
1855
  displayName: member.displayName,
1802
- roles: member.roles
1856
+ roles: member.roles.map(role => role.name)
1803
1857
  } : null);
1804
1858
  },
1805
1859
  selectedIds: selected,
@@ -2487,7 +2541,7 @@ function Separator() {
2487
2541
  alignItems: "center",
2488
2542
  display: "inline-flex",
2489
2543
  height: "0.7em",
2490
- margin: "0 0.25em",
2544
+ margin: "0 0.5em",
2491
2545
  overflow: "hidden"
2492
2546
  },
2493
2547
  children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
@@ -2546,20 +2600,20 @@ function noteRow(args) {
2546
2600
  }
2547
2601
 
2548
2602
  function AssigneeBadge({assignee: assignee}) {
2549
- const {members: members} = useProjectMembers();
2603
+ const vocabulary = useProjectMembers();
2550
2604
  /* @__PURE__ */
2551
2605
  return jsx(AssigneeBadge$1, {
2552
2606
  assignee: assignee,
2553
- members: members
2607
+ vocabulary: vocabulary
2554
2608
  });
2555
2609
  }
2556
2610
 
2557
2611
  function AssigneeBadges({assignees: assignees}) {
2558
- const {members: members} = useProjectMembers();
2612
+ const vocabulary = useProjectMembers();
2559
2613
  /* @__PURE__ */
2560
2614
  return jsx(AssigneeBadges$1, {
2561
2615
  assignees: assignees,
2562
- members: members
2616
+ vocabulary: vocabulary
2563
2617
  });
2564
2618
  }
2565
2619
 
@@ -3606,18 +3660,18 @@ function ActorField({field: field, instanceId: instanceId, onSave: onSave, onUns
3606
3660
  }
3607
3661
 
3608
3662
  function useAssigneePick(args) {
3609
- const {current: current, onSave: onSave, onUnset: onUnset} = args, {saving: saving, save: save} = useSaveField({
3663
+ const {onSave: onSave, onUnset: onUnset} = args, {saving: saving, save: save} = useSaveField({
3610
3664
  instanceId: args.instanceId
3611
3665
  });
3612
3666
  return {
3613
3667
  saving: saving,
3614
- pick: assignee => {
3668
+ pick: picked => {
3615
3669
  if (!saving) {
3616
- if (current && sameAssignee(assignee, current)) {
3670
+ if (picked === null) {
3617
3671
  onUnset !== void 0 && save(onUnset);
3618
3672
  return;
3619
3673
  }
3620
- save(() => onSave(assignee));
3674
+ save(() => onSave(picked));
3621
3675
  }
3622
3676
  }
3623
3677
  };
@@ -3625,22 +3679,18 @@ function useAssigneePick(args) {
3625
3679
 
3626
3680
  function AssigneeField({field: field, instanceId: instanceId, onSave: onSave, onUnset: onUnset}) {
3627
3681
  const [open, setOpen] = useState(!1), cur = isAssigneeShape(field.value) ? field.value : null, {pick: pick} = useAssigneePick({
3628
- current: cur,
3629
3682
  instanceId: instanceId,
3630
3683
  onSave: onSave,
3631
3684
  onUnset: onUnset
3632
- }), pickAndClose = assignee => {
3633
- setOpen(!1), pick(assignee);
3634
- };
3685
+ });
3635
3686
  return field.editable ? /* @__PURE__ */ jsx(Flex, {
3636
3687
  align: "center",
3637
3688
  children: /* @__PURE__ */ jsx(DismissablePopover, {
3638
- content: /* @__PURE__ */ jsx(AssigneePicker, {
3639
- onChange: next => {
3640
- const changed = changedAssignee(cur ? [ cur ] : [], next);
3641
- changed && pickAndClose(changed);
3642
- },
3643
- value: cur ? [ cur ] : []
3689
+ content: /* @__PURE__ */ jsx(SingleAssigneePicker, {
3690
+ current: cur,
3691
+ onPick: picked => {
3692
+ setOpen(!1), pick(picked);
3693
+ }
3644
3694
  }),
3645
3695
  onDismiss: () => setOpen(!1),
3646
3696
  open: open,
@@ -4189,70 +4239,85 @@ function AvatarButton({avatar: avatar, onClick: onClick, onMouseDown: onMouseDow
4189
4239
  });
4190
4240
  }
4191
4241
 
4192
- function UserAvatar({id: id}) {
4193
- const {name: name, imageUrl: imageUrl} = useUserDisplay(id);
4242
+ function RowAssignees({assignees: assignees, hint: hint}) {
4243
+ const ids = assignees.flatMap(item => item.type === "user" ? [ item.id ] : []), vocabulary = useProjectMembers(), roles = assignees.flatMap(item => item.type === "role" ? [ memberRoleFor(vocabulary, item.role) ] : []), displays = useUserDisplays(ids);
4244
+ if (ids.length === 0 && roles.length === 0) return null;
4245
+ const bothKinds = roles.length > 0 && displays.length > 0;
4194
4246
  /* @__PURE__ */
4195
4247
  return jsx(HoverHint, {
4196
- text: name,
4197
- children: /* @__PURE__ */ jsx(MemberAvatar, {
4198
- imageUrl: imageUrl,
4199
- name: name
4200
- })
4201
- });
4202
- }
4203
-
4204
- function UserAvatarGroup({ids: ids, hint: hint}) {
4205
- const displays = useUserDisplays(ids);
4206
- return displays.length === 0 ? null : /* @__PURE__ */ jsx(HoverHint, {
4207
- text: displays.map(d => d.name).join(", "),
4208
- ...hint === void 0 ? {} : {
4209
- description: hint
4210
- },
4211
- children: /* @__PURE__ */ jsx(MemberAvatarGroup, {
4248
+ content:
4249
+ /* @__PURE__ */ jsxs(Stack, {
4250
+ gap: 3,
4251
+ children: [ roles.length > 0 ? /* @__PURE__ */ jsx(HintSection, {
4252
+ names: roles.map(roleLabel),
4253
+ ...bothKinds ? {
4254
+ label: "Roles"
4255
+ } : {}
4256
+ }) : null, displays.length > 0 ? /* @__PURE__ */ jsx(HintSection, {
4257
+ names: displays.map(display => display.name),
4258
+ ...bothKinds ? {
4259
+ label: "Members"
4260
+ } : {}
4261
+ }) : null, hint === void 0 ? null : /* @__PURE__ */ jsx(Text, {
4262
+ muted: !0,
4263
+ size: 1,
4264
+ children: hint
4265
+ }) ]
4266
+ }),
4267
+ children: /* @__PURE__ */ jsx(AssigneeStack, {
4212
4268
  members: displays.map(display => ({
4213
4269
  id: display.id,
4214
4270
  displayName: display.name,
4215
4271
  imageUrl: display.imageUrl
4216
- }))
4272
+ })),
4273
+ roles: roles
4217
4274
  })
4218
4275
  });
4219
4276
  }
4220
4277
 
4221
- function AssignActivityControl({instanceId: instanceId, state: state, surface: surface, assigneeIds: assigneeIds = []}) {
4278
+ function HintSection({label: label, names: names}) {
4279
+ /* @__PURE__ */
4280
+ return jsxs(Stack, {
4281
+ gap: 2,
4282
+ children: [ label === void 0 ? null : /* @__PURE__ */ jsx(Text, {
4283
+ size: 1,
4284
+ weight: "medium",
4285
+ children: label
4286
+ }),
4287
+ /* @__PURE__ */ jsx(Text, {
4288
+ size: 1,
4289
+ children: names.join(", ")
4290
+ }) ]
4291
+ });
4292
+ }
4293
+
4294
+ function AssignActivityControl({instanceId: instanceId, state: state, surface: surface, assignees: assignees = []}) {
4222
4295
  const editField = useEditField(surface), report = useRejectionReport(), [open, setOpen] = useState(!1), [busy, setBusy] = useState(!1);
4223
- if (state.kind === "none") return assigneeIds.length === 0 ? null : /* @__PURE__ */ jsx(AvatarDisplay, {
4296
+ if (state.kind === "none") return assignees.length === 0 ? null : /* @__PURE__ */ jsx(AvatarDisplay, {
4224
4297
  "aria-label": "Assignees",
4225
- avatar: /* @__PURE__ */ jsx(UserAvatarGroup, {
4226
- ids: assigneeIds
4298
+ avatar: /* @__PURE__ */ jsx(RowAssignees, {
4299
+ assignees: assignees
4227
4300
  })
4228
4301
  });
4229
- if (state.kind === "closed") return assigneeIds.length === 0 ? null : /* @__PURE__ */ jsx(AvatarButton, {
4302
+ if (state.kind === "closed") return assignees.length === 0 ? null : /* @__PURE__ */ jsx(AvatarButton, {
4230
4303
  "aria-label": "Assignees",
4231
4304
  as: "span",
4232
- avatar: /* @__PURE__ */ jsx(UserAvatarGroup, {
4233
- hint: state.reason,
4234
- ids: assigneeIds
4305
+ avatar: /* @__PURE__ */ jsx(RowAssignees, {
4306
+ assignees: assignees,
4307
+ hint: state.reason
4235
4308
  }),
4236
4309
  onClick: stopRowClick,
4237
4310
  onMouseDown: stopRowMouseDown
4238
4311
  });
4239
- 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 => {
4240
- if (!(busy || field === void 0)) {
4312
+ const field = state.kind === "editable" ? state.field : void 0, current = field?.value ?? [], change = async next => {
4313
+ const edit = assigneeEdit(current, next);
4314
+ if (!(busy || field === void 0 || edit === void 0)) {
4241
4315
  setBusy(!0);
4242
4316
  try {
4243
- selectedIds.has(memberId) ? await editField({
4244
- instanceId: instanceId,
4245
- field: field,
4246
- mode: "set",
4247
- value: members.filter(m => !(m.type === "user" && m.id === memberId))
4248
- }) : await editField({
4317
+ await editField({
4249
4318
  instanceId: instanceId,
4250
4319
  field: field,
4251
- mode: "append",
4252
- value: {
4253
- type: "user",
4254
- id: memberId
4255
- }
4320
+ ...edit
4256
4321
  }), report.dismiss(instanceToastId("assign", instanceId));
4257
4322
  } catch (err) {
4258
4323
  report.rejected({
@@ -4268,12 +4333,12 @@ function AssignActivityControl({instanceId: instanceId, state: state, surface: s
4268
4333
  }
4269
4334
  }, handleClick = e => {
4270
4335
  e.stopPropagation(), setOpen(v => !v);
4271
- }, button = assigneeIds.length > 0 ?
4336
+ }, button = assignees.length > 0 ?
4272
4337
  /* @__PURE__ */ jsx(AvatarButton, {
4273
4338
  "aria-label": "Assignees",
4274
4339
  as: "span",
4275
- avatar: /* @__PURE__ */ jsx(UserAvatarGroup, {
4276
- ids: assigneeIds
4340
+ avatar: /* @__PURE__ */ jsx(RowAssignees, {
4341
+ assignees: assignees
4277
4342
  }),
4278
4343
  onClick: handleClick,
4279
4344
  onMouseDown: stopRowMouseDown
@@ -4298,9 +4363,11 @@ function AssignActivityControl({instanceId: instanceId, state: state, surface: s
4298
4363
  children: state.kind === "loading" ? /* @__PURE__ */ jsx(LoadingRow, {
4299
4364
  label: "Loading…",
4300
4365
  padding: 3
4301
- }) : /* @__PURE__ */ jsx(MemberPicker, {
4302
- onSelect: member => member && void toggle(member.id),
4303
- selectedIds: selectedIds
4366
+ }) : /* @__PURE__ */ jsx(AssigneePicker, {
4367
+ onChange: next => {
4368
+ change(next);
4369
+ },
4370
+ value: current
4304
4371
  })
4305
4372
  }),
4306
4373
  onDismiss: () => setOpen(!1),
@@ -4332,6 +4399,58 @@ function canRemoveRow(args) {
4332
4399
  return editTarget === void 0 || appendTarget === void 0 ? !1 : editTarget.scope === appendTarget.scope && editTarget.field === appendTarget.field && editTarget.activity === appendTarget.activity;
4333
4400
  }
4334
4401
 
4402
+ function EmptyState({action: action, description: description, icon: icon, title: title}) {
4403
+ /* @__PURE__ */
4404
+ return jsx(Card, {
4405
+ border: !0,
4406
+ flex: 1,
4407
+ padding: 4,
4408
+ radius: 3,
4409
+ style: {
4410
+ alignItems: "center",
4411
+ display: "flex",
4412
+ justifyContent: "center"
4413
+ },
4414
+ children: /* @__PURE__ */ jsxs(Stack, {
4415
+ gap: 4,
4416
+ children: [
4417
+ /* @__PURE__ */ jsx(Text, {
4418
+ align: "center",
4419
+ size: 4,
4420
+ children: icon
4421
+ }),
4422
+ /* @__PURE__ */ jsxs(Stack, {
4423
+ gap: 3,
4424
+ children: [
4425
+ /* @__PURE__ */ jsx(Text, {
4426
+ align: "center",
4427
+ size: 1,
4428
+ weight: "medium",
4429
+ children: title
4430
+ }),
4431
+ /* @__PURE__ */ jsx(Text, {
4432
+ align: "center",
4433
+ muted: !0,
4434
+ size: 1,
4435
+ children: description
4436
+ }) ]
4437
+ }), action ? /* @__PURE__ */ jsx(Flex, {
4438
+ justify: "center",
4439
+ children: action
4440
+ }) : null ]
4441
+ })
4442
+ });
4443
+ }
4444
+
4445
+ function ForMeEmptyState() {
4446
+ /* @__PURE__ */
4447
+ return jsx(EmptyState, {
4448
+ description: "No activities or to-dos are assigned to you",
4449
+ icon: /* @__PURE__ */ jsx(CheckmarkCircleIcon, {}),
4450
+ title: "Nothing to do!"
4451
+ });
4452
+ }
4453
+
4335
4454
  function looksLikeDefinition(value) {
4336
4455
  return typeof value == "object" && value !== null && Array.isArray(value.stages);
4337
4456
  }
@@ -4489,22 +4608,18 @@ function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4489
4608
  }
4490
4609
 
4491
4610
  function AssigneeControl({item: item, editable: editable, onPatch: onPatch}) {
4492
- const [open, setOpen] = useState(!1), assignedId = item.assignee?.type === "user" ? item.assignee.id : void 0, chip = assignedId ? /* @__PURE__ */ jsx(UserAvatar, {
4493
- id: assignedId
4611
+ const [open, setOpen] = useState(!1), current = item.assignee ?? null, chip = current ? /* @__PURE__ */ jsx(RowAssignees, {
4612
+ assignees: [ current ]
4494
4613
  }) : null;
4495
4614
  return editable ? /* @__PURE__ */ jsx(DismissablePopover, {
4496
4615
  content:
4497
- /* @__PURE__ */ jsx(MemberPicker, {
4498
- onSelect: member => {
4616
+ /* @__PURE__ */ jsx(SingleAssigneePicker, {
4617
+ current: current,
4618
+ onPick: picked => {
4499
4619
  setOpen(!1), onPatch({
4500
- assignee: member && member.id !== assignedId ? {
4501
- type: "user",
4502
- id: member.id
4503
- } : null
4620
+ assignee: picked
4504
4621
  });
4505
- },
4506
- selectedIds: assignedId ? /* @__PURE__ */ new Set([ assignedId ]) : /* @__PURE__ */ new Set,
4507
- unassignRow: !0
4622
+ }
4508
4623
  }),
4509
4624
  onDismiss: () => setOpen(!1),
4510
4625
  open: open,
@@ -4700,7 +4815,7 @@ function TodoItemRowView({breadcrumb: breadcrumb, row: row, onToggle: onToggle,
4700
4815
  });
4701
4816
  }
4702
4817
 
4703
- function TodoItemsList({breadcrumb: breadcrumb, entry: entry, filter: filter, surface: surface}) {
4818
+ function TodoItemsList({breadcrumb: breadcrumb, entry: entry, filter: filter, showEmpty: showEmpty = !1, surface: surface}) {
4704
4819
  const {instance: instance} = entry, definition = useDefinition(entry), {editFieldFor: editFieldFor, fireActionFor: fireActionFor} = useWorkflowContext(), telemetry = useWorkflowTelemetry(), report = useRejectionReport(), [busy, setBusy] = useState(!1), derivation = deriveTodoItems(entry), rows = derivation.rows.filter(row => rowMatches(row, filter)), appendTarget = appendTargetFor(derivation.appendTargets, filter), run = async op => {
4705
4820
  if (busy) return !1;
4706
4821
  setBusy(!0);
@@ -4796,8 +4911,28 @@ function TodoItemsList({breadcrumb: breadcrumb, entry: entry, filter: filter, su
4796
4911
  kind: "remove",
4797
4912
  success: success
4798
4913
  });
4799
- };
4800
- if (rows.length === 0 && !appendTarget) return null;
4914
+ }, empty = rows.length === 0;
4915
+ if (empty && !showEmpty) return null;
4916
+ const addControl = appendTarget ? /* @__PURE__ */ jsx(AddItemControl, {
4917
+ busy: busy,
4918
+ button: /* @__PURE__ */ jsx(Button, {
4919
+ fontSize: 1,
4920
+ icon: AddIcon,
4921
+ mode: "ghost",
4922
+ padding: 2,
4923
+ text: "Add item"
4924
+ }),
4925
+ onAdd: addItem
4926
+ }) : null;
4927
+ if (empty) /* @__PURE__ */
4928
+ return jsx(EmptyState, {
4929
+ ...addControl === null ? {} : {
4930
+ action: addControl
4931
+ },
4932
+ description: "Nothing has been added to this list",
4933
+ icon: /* @__PURE__ */ jsx(UlistIcon, {}),
4934
+ title: "No to-dos"
4935
+ });
4801
4936
  const rowKey = row => `${row.scope}:${row.activityName ?? ""}:${row.fieldName}:${row.item._key}`, rowBreadcrumb = row => {
4802
4937
  if (breadcrumb === void 0 || row.activityName === void 0) return breadcrumb;
4803
4938
  const {title: title} = findActivity({
@@ -4831,19 +4966,9 @@ function TodoItemsList({breadcrumb: breadcrumb, entry: entry, filter: filter, su
4831
4966
  },
4832
4967
  row: row
4833
4968
  }, rowKey(row)))
4834
- }), appendTarget ? /* @__PURE__ */ jsx(Flex, {
4835
- children: /* @__PURE__ */ jsx(AddItemControl, {
4836
- busy: busy,
4837
- button: /* @__PURE__ */ jsx(Button, {
4838
- fontSize: 1,
4839
- icon: AddIcon,
4840
- mode: "ghost",
4841
- padding: 2,
4842
- text: "Add item"
4843
- }),
4844
- onAdd: addItem
4845
- })
4846
- }) : null ]
4969
+ }), addControl === null ? null : /* @__PURE__ */ jsx(Flex, {
4970
+ children: addControl
4971
+ }) ]
4847
4972
  });
4848
4973
  }
4849
4974
 
@@ -5282,6 +5407,7 @@ function FieldRow({field: field, detail: detail, entry: entry, activityName: act
5282
5407
  field: field.name,
5283
5408
  activity: activityName
5284
5409
  },
5410
+ showEmpty: !0,
5285
5411
  surface: "activity-dialog"
5286
5412
  }) ]
5287
5413
  });
@@ -5338,58 +5464,6 @@ function blockedNotice(detail) {
5338
5464
  });
5339
5465
  }
5340
5466
 
5341
- function EmptyState({action: action, description: description, icon: icon, title: title}) {
5342
- /* @__PURE__ */
5343
- return jsx(Card, {
5344
- border: !0,
5345
- flex: 1,
5346
- padding: 4,
5347
- radius: 3,
5348
- style: {
5349
- alignItems: "center",
5350
- display: "flex",
5351
- justifyContent: "center"
5352
- },
5353
- children: /* @__PURE__ */ jsxs(Stack, {
5354
- gap: 4,
5355
- children: [
5356
- /* @__PURE__ */ jsx(Text, {
5357
- align: "center",
5358
- size: 4,
5359
- children: icon
5360
- }),
5361
- /* @__PURE__ */ jsxs(Stack, {
5362
- gap: 3,
5363
- children: [
5364
- /* @__PURE__ */ jsx(Text, {
5365
- align: "center",
5366
- size: 1,
5367
- weight: "medium",
5368
- children: title
5369
- }),
5370
- /* @__PURE__ */ jsx(Text, {
5371
- align: "center",
5372
- muted: !0,
5373
- size: 1,
5374
- children: description
5375
- }) ]
5376
- }), action ? /* @__PURE__ */ jsx(Flex, {
5377
- justify: "center",
5378
- children: action
5379
- }) : null ]
5380
- })
5381
- });
5382
- }
5383
-
5384
- function ForMeEmptyState() {
5385
- /* @__PURE__ */
5386
- return jsx(EmptyState, {
5387
- description: "No activities or to-dos are assigned to you",
5388
- icon: /* @__PURE__ */ jsx(CheckmarkCircleIcon, {}),
5389
- title: "Nothing to do!"
5390
- });
5391
- }
5392
-
5393
5467
  function CountedLabel({count: count, label: label}) {
5394
5468
  return count === void 0 || count === 0 ? /* @__PURE__ */ jsx(Fragment, {
5395
5469
  children: label
@@ -5590,17 +5664,13 @@ function TitleTag({hint: hint, tone: tone, children: children}) {
5590
5664
  });
5591
5665
  }
5592
5666
 
5593
- function ActivityRow({face: face, breadcrumb: breadcrumb, assigneeIds: assigneeIds, assignState: assignState, dateState: dateState, dueDates: dueDates, instanceId: instanceId, onOpen: onOpen, surface: surface, terminalActions: terminalActions = []}) {
5667
+ function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees, assignState: assignState, dateState: dateState, dueDates: dueDates, instanceId: instanceId, onOpen: onOpen, surface: surface, terminalActions: terminalActions = []}) {
5594
5668
  const settled = isTerminalActivityStatus(face.status), dimmed = settledDim(settled);
5595
5669
  /* @__PURE__ */
5596
5670
  return jsxs(WorkRow, {
5597
- end: /* @__PURE__ */ jsxs(Fragment, {
5598
- children: [ settled || terminalActions.length === 0 ? null : /* @__PURE__ */ jsx(RowTerminalActions, {
5599
- actions: terminalActions,
5600
- activity: face.activityName,
5601
- instanceId: instanceId,
5602
- surface: surface
5603
- }),
5671
+ end:
5672
+ /* @__PURE__ */ jsxs(Fragment, {
5673
+ children: [
5604
5674
  /* @__PURE__ */ jsx(ActivityDateControl, {
5605
5675
  dueDates: dueDates,
5606
5676
  instanceId: instanceId,
@@ -5608,10 +5678,15 @@ function ActivityRow({face: face, breadcrumb: breadcrumb, assigneeIds: assigneeI
5608
5678
  surface: surface
5609
5679
  }),
5610
5680
  /* @__PURE__ */ jsx(AssignActivityControl, {
5611
- assigneeIds: assigneeIds,
5681
+ assignees: assignees,
5612
5682
  instanceId: instanceId,
5613
5683
  state: assignState,
5614
5684
  surface: surface
5685
+ }), settled || terminalActions.length === 0 ? null : /* @__PURE__ */ jsx(RowTerminalActions, {
5686
+ actions: terminalActions,
5687
+ activity: face.activityName,
5688
+ instanceId: instanceId,
5689
+ surface: surface
5615
5690
  }) ]
5616
5691
  }),
5617
5692
  lead: /* @__PURE__ */ jsx(ActivityStatusLead, {
@@ -5835,30 +5910,60 @@ function deriveFieldPills(args) {
5835
5910
  });
5836
5911
  }
5837
5912
 
5838
- function TodoFieldDialog({entry: entry, scope: scope, field: field, title: title, onClose: onClose}) {
5913
+ function UserAvatar({id: id}) {
5914
+ const {name: name, imageUrl: imageUrl} = useUserDisplay(id);
5839
5915
  /* @__PURE__ */
5840
- return jsx(Dialog, {
5841
- header: title,
5842
- id: `todo-field-${scope}-${field}`,
5843
- onClose: onClose,
5844
- width: 1,
5845
- children: /* @__PURE__ */ jsx(Box, {
5846
- padding: 4,
5847
- children: /* @__PURE__ */ jsx(TodoItemsList, {
5848
- entry: entry,
5849
- filter: {
5850
- kind: "field",
5851
- scope: scope,
5852
- field: field
5853
- },
5854
- surface: "todo-field-dialog"
5855
- })
5916
+ return jsx(HoverHint, {
5917
+ text: name,
5918
+ children: /* @__PURE__ */ jsx(MemberAvatar, {
5919
+ imageUrl: imageUrl,
5920
+ name: name
5856
5921
  })
5857
5922
  });
5858
5923
  }
5859
5924
 
5860
- const MAX_VISIBLE_PILLS = 4;
5861
-
5925
+ function UserAvatarGroup({ids: ids, hint: hint}) {
5926
+ const displays = useUserDisplays(ids);
5927
+ return displays.length === 0 ? null : /* @__PURE__ */ jsx(HoverHint, {
5928
+ text: displays.map(d => d.name).join(", "),
5929
+ ...hint === void 0 ? {} : {
5930
+ description: hint
5931
+ },
5932
+ children: /* @__PURE__ */ jsx(MemberAvatarGroup, {
5933
+ members: displays.map(display => ({
5934
+ id: display.id,
5935
+ displayName: display.name,
5936
+ imageUrl: display.imageUrl
5937
+ }))
5938
+ })
5939
+ });
5940
+ }
5941
+
5942
+ function TodoFieldDialog({entry: entry, scope: scope, field: field, title: title, onClose: onClose}) {
5943
+ /* @__PURE__ */
5944
+ return jsx(Dialog, {
5945
+ header: title,
5946
+ id: `todo-field-${scope}-${field}`,
5947
+ onClose: onClose,
5948
+ width: 1,
5949
+ children: /* @__PURE__ */ jsx(Box, {
5950
+ padding: 4,
5951
+ children: /* @__PURE__ */ jsx(TodoItemsList, {
5952
+ entry: entry,
5953
+ filter: {
5954
+ kind: "field",
5955
+ scope: scope,
5956
+ field: field
5957
+ },
5958
+ showEmpty: !0,
5959
+ surface: "todo-field-dialog"
5960
+ })
5961
+ })
5962
+ });
5963
+ }
5964
+
5965
+ const MAX_VISIBLE_PILLS = 4;
5966
+
5862
5967
  function FieldPills({entry: entry, scope: scope, definition: definition, surface: surface}) {
5863
5968
  const [expanded, setExpanded] = useState(!1), pills = deriveFieldPills({
5864
5969
  scope: scope,
@@ -5968,12 +6073,24 @@ function PillContent({pill: pill}) {
5968
6073
  children: [ lead,
5969
6074
  /* @__PURE__ */ jsx(FaceText, {
5970
6075
  muted: !0,
5971
- children: pill.editability?.editable === !0 ? `Set ${pill.title}` : pill.title
6076
+ children: pill.title
5972
6077
  }) ]
5973
6078
  }) : /* @__PURE__ */ jsxs(Fragment, {
5974
6079
  children: [ lead,
5975
- /* @__PURE__ */ jsx(SetFace, {
5976
- face: face
6080
+ /* @__PURE__ */ jsxs(Flex, {
6081
+ align: "center",
6082
+ gap: 1,
6083
+ style: {
6084
+ minWidth: 0
6085
+ },
6086
+ children: [
6087
+ /* @__PURE__ */ jsx(FaceText, {
6088
+ muted: !0,
6089
+ children: `${pill.title}:`
6090
+ }),
6091
+ /* @__PURE__ */ jsx(SetFace, {
6092
+ face: face
6093
+ }) ]
5977
6094
  }) ]
5978
6095
  });
5979
6096
  }
@@ -5993,7 +6110,7 @@ function AvatarCapAligned({children: children}) {
5993
6110
  }
5994
6111
 
5995
6112
  function AssigneesFace({assignees: assignees}) {
5996
- const userIds = assignees.flatMap(a => a.type === "user" ? [ a.id ] : []), roles = assignees.flatMap(a => a.type === "role" ? [ a.role ] : []);
6113
+ const vocabulary = useProjectMembers(), userIds = assignees.flatMap(a => a.type === "user" ? [ a.id ] : []), roles = assignees.flatMap(a => a.type === "role" ? [ roleLabelFor(vocabulary, a.role) ] : []);
5997
6114
  /* @__PURE__ */
5998
6115
  return jsxs(Fragment, {
5999
6116
  children: [ userIds.length > 0 ? /* @__PURE__ */ jsx(AvatarCapAligned, {
@@ -6026,14 +6143,11 @@ function usePillChrome(face) {
6026
6143
  }
6027
6144
 
6028
6145
  function PillHint({pill: pill, disabled: disabled = !1, children: children}) {
6146
+ const {description: description} = pill;
6029
6147
  /* @__PURE__ */
6030
6148
  return jsx(HoverHint, {
6031
- disabled: disabled,
6032
- text: pill.title,
6033
- textWeight: "medium",
6034
- ...pill.description === void 0 ? {} : {
6035
- description: pill.description
6036
- },
6149
+ disabled: disabled || description === void 0,
6150
+ text: description ?? "",
6037
6151
  children: children
6038
6152
  });
6039
6153
  }
@@ -6119,7 +6233,7 @@ function PillPopover({pill: pill, content: content, open: open, onToggle: onTogg
6119
6233
  }
6120
6234
 
6121
6235
  function ReadOnlyPill({pill: pill}) {
6122
- const [open, setOpen] = useState(!1), reason = pill.editability?.disabledReason;
6236
+ const [open, setOpen] = useState(!1), verdict = pill.editability?.disabledReason, reason = verdict !== void 0 && reasonRestatesInstanceState(verdict) ? void 0 : verdict;
6123
6237
  /* @__PURE__ */
6124
6238
  return jsx(PillPopover, {
6125
6239
  content: /* @__PURE__ */ jsx(Box, {
@@ -6302,19 +6416,17 @@ function AssigneePill({entry: entry, pill: pill, editability: editability, surfa
6302
6416
  editability: editability,
6303
6417
  surface: surface
6304
6418
  }), current = pill.entry?._type === "assignee" ? pill.entry.value : null, {pick: pick} = useAssigneePick({
6305
- current: current,
6306
6419
  instanceId: entry.instance._id,
6307
6420
  onSave: set,
6308
6421
  onUnset: unset
6309
6422
  });
6310
6423
  /* @__PURE__ */
6311
6424
  return jsx(PillPopover, {
6312
- content: /* @__PURE__ */ jsx(AssigneePicker, {
6313
- onChange: next => {
6314
- const changed = changedAssignee(current ? [ current ] : [], next);
6315
- changed && (setOpen(!1), pick(changed));
6316
- },
6317
- value: current ? [ current ] : []
6425
+ content: /* @__PURE__ */ jsx(SingleAssigneePicker, {
6426
+ current: current,
6427
+ onPick: picked => {
6428
+ setOpen(!1), pick(picked);
6429
+ }
6318
6430
  }),
6319
6431
  onDismiss: () => setOpen(!1),
6320
6432
  onToggle: () => setOpen(v => !v),
@@ -6443,165 +6555,6 @@ function instanceBreadcrumb(instance, definition) {
6443
6555
  return [ instanceTitle(instance, definition), stageTitle(definition, instance.currentStage) ];
6444
6556
  }
6445
6557
 
6446
- function Hairline({weight: weight, style: style}) {
6447
- const {color: color} = useTheme_v2();
6448
- /* @__PURE__ */
6449
- return jsx("div", {
6450
- style: {
6451
- borderTop: `${weight}px solid ${color.border}`,
6452
- ...style
6453
- }
6454
- });
6455
- }
6456
-
6457
- function CollapsibleBand({header: header, children: children, background: background = !1, defaultOpen: defaultOpen = !0, onToggle: onToggle, open: controlledOpen, sticky: sticky = !1, title: title}) {
6458
- const [heldOpen, setHeldOpen] = useState(defaultOpen), open = controlledOpen ?? heldOpen, toggle = onToggle ?? (() => setHeldOpen(value => !value)), verb = open ? "Collapse" : "Expand";
6459
- /* @__PURE__ */
6460
- return jsxs(Stack, {
6461
- gap: 1,
6462
- children: [
6463
- /* @__PURE__ */ jsx(Card, {
6464
- onDoubleClick: event => {
6465
- event.target instanceof Element && event.target.closest("a, button") || toggle();
6466
- },
6467
- padding: 1,
6468
- radius: 3,
6469
- style: {
6470
- userSelect: "none",
6471
- ...sticky ? {
6472
- position: "sticky",
6473
- top: 0,
6474
- zIndex: 1
6475
- } : {}
6476
- },
6477
- tone: background ? "transparent" : "inherit",
6478
- children: /* @__PURE__ */ jsxs(Flex, {
6479
- align: "center",
6480
- children: [
6481
- /* @__PURE__ */ jsx(Button, {
6482
- "aria-label": `${verb} ${title}`,
6483
- fontSize: 1,
6484
- icon: open ? ChevronDownIcon : ChevronRightIcon,
6485
- mode: "bleed",
6486
- onClick: toggle,
6487
- padding: 2
6488
- }), header ]
6489
- })
6490
- }), open ? children : null ]
6491
- });
6492
- }
6493
-
6494
- function TrailingHairline() {
6495
- /* @__PURE__ */
6496
- return jsx(Box, {
6497
- flex: 1,
6498
- paddingLeft: 1,
6499
- paddingRight: 1,
6500
- children: /* @__PURE__ */ jsx(Hairline, {
6501
- weight: .5
6502
- })
6503
- });
6504
- }
6505
-
6506
- function InstanceAccordion({entry: entry, children: children, defaultOpen: defaultOpen}) {
6507
- const definition = useDefinition(entry), {instance: instance} = entry, settled = !isLiveEntry(entry), title = instanceTitle(instance, definition), badgeTrim = useBadgeCapTrim();
6508
- /* @__PURE__ */
6509
- return jsx(CollapsibleBand, {
6510
- defaultOpen: defaultOpen ?? !settled,
6511
- title: title,
6512
- header: /* @__PURE__ */ jsxs(Fragment, {
6513
- children: [
6514
- /* @__PURE__ */ jsxs(Flex, {
6515
- align: "center",
6516
- gap: 2,
6517
- paddingLeft: 2,
6518
- style: {
6519
- minWidth: 0
6520
- },
6521
- children: [
6522
- /* @__PURE__ */ jsx(Text, {
6523
- size: 1,
6524
- style: {
6525
- minWidth: 0
6526
- },
6527
- textOverflow: "ellipsis",
6528
- weight: "medium",
6529
- children: title
6530
- }),
6531
- /* @__PURE__ */ jsxs(Flex, {
6532
- align: "center",
6533
- gap: 2,
6534
- style: badgeTrim,
6535
- children: [
6536
- /* @__PURE__ */ jsx(AbortedBadge, {
6537
- instance: instance
6538
- }), settled ? null : /* @__PURE__ */ jsx(OlderDefinitionBadge, {
6539
- instance: instance
6540
- }) ]
6541
- }) ]
6542
- }),
6543
- /* @__PURE__ */ jsx(TrailingHairline, {}) ]
6544
- }),
6545
- sticky: !0,
6546
- children: /* @__PURE__ */ jsx(Box, {
6547
- paddingBottom: 2,
6548
- paddingLeft: 5,
6549
- children: children
6550
- })
6551
- });
6552
- }
6553
-
6554
- function InstanceToolLink({entry: entry}) {
6555
- const {tools: tools} = useWorkspace(), instanceId = entry.instance._id, WorkflowIntentLink = useMemo(() => function(linkProps) {
6556
- /* @__PURE__ */
6557
- return jsx(IntentLink, {
6558
- ...linkProps,
6559
- intent: WORKFLOW_INTENT,
6560
- params: {
6561
- instanceId: instanceId
6562
- }
6563
- });
6564
- }, [ instanceId ]);
6565
- return workflowsToolAvailable(tools) ? /* @__PURE__ */ jsx(Flex, {
6566
- children: /* @__PURE__ */ jsx(Button, {
6567
- as: WorkflowIntentLink,
6568
- fontSize: 1,
6569
- mode: "bleed",
6570
- padding: 2,
6571
- text: "View workflow details"
6572
- })
6573
- }) : null;
6574
- }
6575
-
6576
- function PartOfBand({instance: instance}) {
6577
- const parentRef = instance.ancestors.at(-1), parentId = parentRef ? toBareId(parentRef.id) : null, parentEntry = useWorkflowInstanceEntry(parentId), parentDefinition = useDefinition(parentEntry);
6578
- if (!parentId) return null;
6579
- const title = parentEntry ? instanceTitle(parentEntry.instance, parentDefinition) : parentId;
6580
- /* @__PURE__ */
6581
- return jsx(Card, {
6582
- border: !0,
6583
- padding: 3,
6584
- radius: 3,
6585
- tone: "primary",
6586
- children: /* @__PURE__ */ jsxs(Flex, {
6587
- align: "center",
6588
- gap: 2,
6589
- children: [
6590
- /* @__PURE__ */ jsx(Text, {
6591
- size: 1,
6592
- children: /* @__PURE__ */ jsx(ArrowUpIcon, {})
6593
- }),
6594
- /* @__PURE__ */ jsxs(Text, {
6595
- size: 1,
6596
- children: [ "Part of ",
6597
- /* @__PURE__ */ jsx("strong", {
6598
- children: title
6599
- }) ]
6600
- }) ]
6601
- })
6602
- });
6603
- }
6604
-
6605
6558
  const UNCONDITIONAL_WHENS = /* @__PURE__ */ new Set([ "true", DEFAULT_TRANSITION_WHEN ]);
6606
6559
 
6607
6560
  function stageRounds(instance) {
@@ -6661,6 +6614,31 @@ function nodeState(args) {
6661
6614
  return args.name === args.instance.currentStage ? "current" : args.visits === 0 ? "upcoming" : args.currentIndex >= 0 && args.index > args.currentIndex ? "revisit" : "done";
6662
6615
  }
6663
6616
 
6617
+ function roundDetail(round) {
6618
+ return round === void 0 ? void 0 : `Round ${round}`;
6619
+ }
6620
+
6621
+ function instanceStageFace(args) {
6622
+ const {definition: definition, instance: instance} = args;
6623
+ if (isUnprimed(instance)) return;
6624
+ const current = definition === void 0 ? void 0 : deriveSpine({
6625
+ definition: definition,
6626
+ instance: instance
6627
+ }).find(node => node.state === "current");
6628
+ if (current === void 0) return {
6629
+ title: stageTitle(definition, instance.currentStage),
6630
+ completed: terminalState(instance) === "completed"
6631
+ };
6632
+ const detail = roundDetail(current.round);
6633
+ return {
6634
+ title: current.title,
6635
+ completed: current.completedAt !== void 0,
6636
+ ...detail === void 0 ? {} : {
6637
+ detail: detail
6638
+ }
6639
+ };
6640
+ }
6641
+
6664
6642
  function stageOverview(args) {
6665
6643
  const {definition: definition, node: node, transitions: transitions} = args, stage = definition.stages.find(s => s.name === node.name), advance = node.state === "current" ? advanceHint({
6666
6644
  definition: definition,
@@ -6727,97 +6705,878 @@ function advanceHint(args) {
6727
6705
  };
6728
6706
  }
6729
6707
 
6730
- function conditionLines(description) {
6731
- return description.checklist.kind === "group" && description.checklist.op === "all" ? description.checklist.items.flatMap(item => checklistLines({
6732
- ...description,
6733
- checklist: item
6734
- }, {
6735
- marks: !1
6736
- })) : checklistLines(description, {
6737
- marks: !1
6708
+ function conditionLines(description) {
6709
+ return description.checklist.kind === "group" && description.checklist.op === "all" ? description.checklist.items.flatMap(item => checklistLines({
6710
+ ...description,
6711
+ checklist: item
6712
+ }, {
6713
+ marks: !1
6714
+ })) : checklistLines(description, {
6715
+ marks: !1
6716
+ });
6717
+ }
6718
+
6719
+ function Hairline({weight: weight, style: style}) {
6720
+ const {color: color} = useTheme_v2();
6721
+ /* @__PURE__ */
6722
+ return jsx("div", {
6723
+ style: {
6724
+ borderTop: `${weight}px solid ${color.border}`,
6725
+ ...style
6726
+ }
6727
+ });
6728
+ }
6729
+
6730
+ function StageGlyph(props) {
6731
+ /* @__PURE__ */
6732
+ return jsxs("svg", {
6733
+ "data-sanity-icon": "stage",
6734
+ fill: "none",
6735
+ height: "1em",
6736
+ viewBox: "0 0 25 25",
6737
+ width: "1em",
6738
+ xmlns: "http://www.w3.org/2000/svg",
6739
+ ...props,
6740
+ children: [
6741
+ /* @__PURE__ */ jsx("path", {
6742
+ d: "M14.7998 12.5C14.7998 11.2295 13.7705 10.2002 12.5 10.2002C11.2295 10.2002 10.2002 11.2295 10.2002 12.5C10.2002 13.7705 11.2295 14.7998 12.5 14.7998V16C10.5668 16 9 14.4332 9 12.5C9 10.5668 10.5668 9 12.5 9C14.4332 9 16 10.5668 16 12.5C16 14.4332 14.4332 16 12.5 16V14.7998C13.7705 14.7998 14.7998 13.7705 14.7998 12.5Z",
6743
+ fill: "currentColor"
6744
+ }),
6745
+ /* @__PURE__ */ jsx("path", {
6746
+ d: "M4.5 12.5C4.5 12.1687 4.76863 11.9 5.1 11.9H9.5V13.1H5.1C4.76863 13.1 4.5 12.8314 4.5 12.5V12.5Z",
6747
+ fill: "currentColor"
6748
+ }),
6749
+ /* @__PURE__ */ jsx("path", {
6750
+ d: "M15.5 11.9H19.9C20.2314 11.9 20.5 12.1687 20.5 12.5V12.5C20.5 12.8314 20.2314 13.1 19.9 13.1H15.5V11.9Z",
6751
+ fill: "currentColor"
6752
+ }) ]
6753
+ });
6754
+ }
6755
+
6756
+ function StageFace({completed: completed = !1, detail: detail, glyph: glyph = !0, title: title}) {
6757
+ const tone = completed ? "default" : "primary";
6758
+ /* @__PURE__ */
6759
+ return jsxs(Flex, {
6760
+ align: "center",
6761
+ as: "span",
6762
+ gap: 2,
6763
+ children: [ glyph ? /* @__PURE__ */ jsx(TextWithTone, {
6764
+ size: 1,
6765
+ tone: tone,
6766
+ children: completed ? /* @__PURE__ */ jsx(CheckmarkCircleFilledIcon, {
6767
+ style: {
6768
+ color: "inherit"
6769
+ }
6770
+ }) : /* @__PURE__ */ jsx(StageGlyph, {
6771
+ style: {
6772
+ color: "inherit"
6773
+ }
6774
+ })
6775
+ }) : null,
6776
+ /* @__PURE__ */ jsx(TextWithTone, {
6777
+ size: 1,
6778
+ tone: tone,
6779
+ weight: "medium",
6780
+ children: title
6781
+ }), detail === void 0 ? null : /* @__PURE__ */ jsx(Text, {
6782
+ muted: !0,
6783
+ size: 1,
6784
+ children: detail
6785
+ }) ]
6786
+ });
6787
+ }
6788
+
6789
+ function StageChip({completed: completed = !1, detail: detail, interactive: interactive = !1, title: title}) {
6790
+ const {color: color} = useTheme_v2();
6791
+ /* @__PURE__ */
6792
+ return jsx(Card, {
6793
+ padding: 3,
6794
+ radius: 4,
6795
+ style: {
6796
+ boxShadow: `inset 0 0 0 1px ${completed ? color.badge.default.fg : color.focusRing}`,
6797
+ cursor: interactive ? "help" : "default"
6798
+ },
6799
+ tone: completed ? "default" : "primary",
6800
+ ...interactive ? {
6801
+ tabIndex: 0
6802
+ } : {},
6803
+ children: /* @__PURE__ */ jsx(StageFace, {
6804
+ completed: completed,
6805
+ title: title,
6806
+ ...detail === void 0 ? {} : {
6807
+ detail: detail
6808
+ }
6809
+ })
6810
+ });
6811
+ }
6812
+
6813
+ function StartIncompleteBadge({style: style}) {
6814
+ /* @__PURE__ */
6815
+ return jsx(Badge, {
6816
+ fontSize: 1,
6817
+ mode: "outline",
6818
+ style: style,
6819
+ tone: "caution",
6820
+ children: "Start incomplete"
6821
+ });
6822
+ }
6823
+
6824
+ function normalizeAbortReason(raw) {
6825
+ const trimmed = raw.trim();
6826
+ return trimmed === "" ? void 0 : trimmed;
6827
+ }
6828
+
6829
+ function abortCommitReport(args) {
6830
+ return args.changed ? {
6831
+ outcome: "aborted",
6832
+ toast: abortedToast(args.title)
6833
+ } : {
6834
+ outcome: "already-settled",
6835
+ toast: abortAlreadySettledToast(args.title)
6836
+ };
6837
+ }
6838
+
6839
+ function abortedToast(title) {
6840
+ return {
6841
+ id: TOAST_ID.abort,
6842
+ status: "info",
6843
+ title: `Aborted “${title}”`
6844
+ };
6845
+ }
6846
+
6847
+ function abortAlreadySettledToast(title) {
6848
+ return {
6849
+ id: TOAST_ID.abort,
6850
+ status: "info",
6851
+ title: `“${title}” had already finished`,
6852
+ description: "Nothing was aborted — the workflow reached a terminal state first."
6853
+ };
6854
+ }
6855
+
6856
+ function abortUnconfirmedToast(args) {
6857
+ return {
6858
+ id: TOAST_ID.abort,
6859
+ status: "warning",
6860
+ title: `Couldn’t confirm “${args.title}” was aborted`,
6861
+ description: `${describeError(args.err)} — the abort may still have committed, so check the workflow’s state before trying again.`
6862
+ };
6863
+ }
6864
+
6865
+ function AbortWorkflowDialog({entry: entry, onClose: onClose}) {
6866
+ const {engine: engine} = useWorkflowContext(), definition = useDefinition(entry), toast = useWorkflowToast(), telemetry = useWorkflowTelemetry(), [typedReason, setTypedReason] = useState(""), [pending, setPending] = useState(!1), {instance: instance} = entry, title = instanceTitle(instance, definition), reason = normalizeAbortReason(typedReason), reasonInputId = `abort-workflow-reason-${instance._id}`;
6867
+ useLogEventOnMount(WorkflowAbortDialogOpened, {
6868
+ instanceId: instance._id
6869
+ });
6870
+ const confirm = async () => {
6871
+ if (reason === void 0) return;
6872
+ const log = outcome => telemetry.log(WorkflowAbortDialogSubmitted, {
6873
+ instanceId: instance._id,
6874
+ outcome: outcome
6875
+ });
6876
+ setPending(!0);
6877
+ try {
6878
+ const {changed: changed} = await engine.abortInstance({
6879
+ instanceId: instance._id,
6880
+ reason: reason
6881
+ }), report = abortCommitReport({
6882
+ title: title,
6883
+ changed: changed
6884
+ });
6885
+ toast.push(report.toast), log(report.outcome), onClose();
6886
+ } catch (err) {
6887
+ toast.push(abortUnconfirmedToast({
6888
+ title: title,
6889
+ err: err
6890
+ })), log("unconfirmed"), setPending(!1);
6891
+ }
6892
+ };
6893
+ /* @__PURE__ */
6894
+ return jsx(Dialog, {
6895
+ footer: /* @__PURE__ */ jsx(Box, {
6896
+ padding: 3,
6897
+ children: /* @__PURE__ */ jsxs(Flex, {
6898
+ gap: 2,
6899
+ justify: "flex-end",
6900
+ children: [
6901
+ /* @__PURE__ */ jsx(Button, {
6902
+ disabled: pending,
6903
+ fontSize: 1,
6904
+ mode: "bleed",
6905
+ onClick: onClose,
6906
+ padding: 2,
6907
+ text: "Cancel"
6908
+ }),
6909
+ /* @__PURE__ */ jsx(Button, {
6910
+ disabled: reason === void 0,
6911
+ fontSize: 1,
6912
+ loading: pending,
6913
+ onClick: () => {
6914
+ confirm();
6915
+ },
6916
+ padding: 2,
6917
+ text: "Abort workflow",
6918
+ tone: "critical"
6919
+ }) ]
6920
+ })
6921
+ }),
6922
+ header: "Abort workflow",
6923
+ id: `abort-workflow-${instance._id}`,
6924
+ onClose: () => {
6925
+ pending || onClose();
6926
+ },
6927
+ width: 0,
6928
+ children: /* @__PURE__ */ jsx(Box, {
6929
+ padding: 4,
6930
+ children: /* @__PURE__ */ jsxs(Stack, {
6931
+ gap: 5,
6932
+ children: [
6933
+ /* @__PURE__ */ jsxs(Text, {
6934
+ size: 1,
6935
+ children: [ "Abort the ",
6936
+ /* @__PURE__ */ jsx("strong", {
6937
+ children: title
6938
+ }), " workflow? This cannot be undone." ]
6939
+ }),
6940
+ /* @__PURE__ */ jsxs(Stack, {
6941
+ gap: 3,
6942
+ children: [
6943
+ /* @__PURE__ */ jsx(Text, {
6944
+ as: "label",
6945
+ htmlFor: reasonInputId,
6946
+ size: 1,
6947
+ weight: "medium",
6948
+ children: "Reason"
6949
+ }),
6950
+ /* @__PURE__ */ jsx(TextInput, {
6951
+ autoFocus: !0,
6952
+ fontSize: 1,
6953
+ id: reasonInputId,
6954
+ onChange: event => setTypedReason(event.currentTarget.value),
6955
+ placeholder: "Why is this workflow being stopped?",
6956
+ value: typedReason
6957
+ }) ]
6958
+ }) ]
6959
+ })
6960
+ })
6961
+ });
6962
+ }
6963
+
6964
+ function makeTitleResolver(definition) {
6965
+ const activities = (definition?.stages ?? []).flatMap(s => s.activities ?? []);
6966
+ return {
6967
+ action: (activityName, actionName) => activities.find(t => t.name === activityName)?.actions?.find(a => a.name === actionName)?.title ?? actionName,
6968
+ activity: name => activities.find(t => t.name === name)?.title ?? name,
6969
+ stage: name => stageTitle(definition, name)
6970
+ };
6971
+ }
6972
+
6973
+ function attributed(actor, ...parts) {
6974
+ if (actor) return {
6975
+ actorId: actor.id,
6976
+ parts: parts
6977
+ };
6978
+ const [first, ...rest] = parts;
6979
+ return typeof first != "string" ? {
6980
+ actorId: void 0,
6981
+ parts: parts
6982
+ } : {
6983
+ actorId: void 0,
6984
+ parts: [ first.charAt(0).toUpperCase() + first.slice(1), ...rest ]
6985
+ };
6986
+ }
6987
+
6988
+ function standalone(...parts) {
6989
+ return {
6990
+ actorId: void 0,
6991
+ parts: parts
6992
+ };
6993
+ }
6994
+
6995
+ const historyLiner = {
6996
+ stageEntered: (h, titles) => attributed(h.actor, `entered ${titles.stage(h.stage)}${h.fromStage ? ` from ${titles.stage(h.fromStage)}` : ""}`),
6997
+ stageExited: (h, titles) => attributed(h.actor, `left ${titles.stage(h.stage)} for ${titles.stage(h.toStage)}`),
6998
+ activityStatusChanged: (h, titles) => attributed(h.actor, `changed ${titles.activity(h.activity)}: ${h.from} → ${h.to}`),
6999
+ actionFired: (h, titles) => {
7000
+ const phrase = `fired ${titles.action(h.activity, h.action)} on ${titles.activity(h.activity)}`;
7001
+ return h.triggered ? standalone(`The workflow ${phrase}`) : attributed(h.actor, phrase);
7002
+ },
7003
+ transitionFired: (h, titles) => attributed(h.actor, `moved ${titles.stage(h.fromStage)} → ${titles.stage(h.toStage)}`),
7004
+ effectQueued: h => standalone("Effect ", {
7005
+ code: h.effect
7006
+ }, " queued"),
7007
+ effectCompleted: h => {
7008
+ const detail = h.detail ? ` — ${h.detail}` : "";
7009
+ return h.actor ? attributed(h.actor, "resolved effect ", {
7010
+ code: h.effect
7011
+ }, ` — ${h.status}${detail}`) : standalone("Effect ", {
7012
+ code: h.effect
7013
+ }, ` ${h.status}${detail}`);
7014
+ },
7015
+ effectClaimReleased: h => attributed(h.actor, "released a stale claim on effect ", {
7016
+ code: h.effect
7017
+ }, ` ${h.via === "sweep" ? "via the sweeper" : "via a drain takeover"}`),
7018
+ spawned: (h, titles) => attributed(h.actor, `spawned a child workflow from ${titles.activity(h.activity)}`),
7019
+ subworkflowAdopted: (h, titles) => standalone(`Re-adopted child workflow into ${titles.activity(h.activity)} on re-entering ${titles.stage(h.stage)}`),
7020
+ subworkflowResolved: (h, titles) => standalone(`Child workflow of ${titles.activity(h.activity)} ${h.status === "done" ? "completed" : "was aborted"}`),
7021
+ subworkflowOrphaned: h => standalone(`Orphaned child workflow reached terminal — ${h.detail}`),
7022
+ aborted: (h, titles) => attributed(h.actor, `aborted the workflow at ${titles.stage(h.stage)}${h.reason ? ` — ${h.reason}` : ""}`),
7023
+ opApplied: (h, titles) => h.action ? attributed(h.actor, `changed state via action ${titles.action(h.activity ?? "", h.action)}`) : h.activity ? attributed(h.actor, `changed state via activity ${titles.activity(h.activity)}`) : attributed(h.actor, "changed state via ", {
7024
+ code: h.opType
7025
+ }),
7026
+ fieldQueryDiscarded: h => standalone({
7027
+ code: JSON.stringify(h)
7028
+ })
7029
+ };
7030
+
7031
+ function historyLine(h, titles) {
7032
+ return historyLiner[h._type](h, titles);
7033
+ }
7034
+
7035
+ function CodeChip({value: value}) {
7036
+ /* @__PURE__ */
7037
+ return jsx("code", {
7038
+ style: {
7039
+ overflowWrap: "anywhere"
7040
+ },
7041
+ children: value
7042
+ });
7043
+ }
7044
+
7045
+ function HintedMenuButton({hint: hint, ...menuButtonProps}) {
7046
+ const [open, setOpen] = useState(!1), popover = {
7047
+ tone: "default",
7048
+ ...menuButtonProps.popover?.placement === void 0 ? {
7049
+ placement: "bottom-end",
7050
+ fallbackPlacements: [ "top-end" ]
7051
+ } : {},
7052
+ ...menuButtonProps.popover
7053
+ };
7054
+ return hint === void 0 ? /* @__PURE__ */ jsx(MenuButton, {
7055
+ ...menuButtonProps,
7056
+ popover: popover
7057
+ }) : /* @__PURE__ */ jsx(HoverHint, {
7058
+ disabled: open,
7059
+ text: hint,
7060
+ children: /* @__PURE__ */ jsx(MenuButton, {
7061
+ ...menuButtonProps,
7062
+ onClose: () => setOpen(!1),
7063
+ onOpen: () => setOpen(!0),
7064
+ popover: popover
7065
+ })
7066
+ });
7067
+ }
7068
+
7069
+ function historyWindow(args) {
7070
+ const {entries: entries, expanded: expanded} = args;
7071
+ return {
7072
+ overflows: entries.length > 6,
7073
+ shown: expanded ? entries : entries.slice(0, 6)
7074
+ };
7075
+ }
7076
+
7077
+ function PendingEffectRows({instance: instance, now: now}) {
7078
+ const {drainEffectsFor: drainEffectsFor, completeEffectFor: completeEffectFor, effectHandlers: effectHandlers} = useWorkflowContext(), toast = useWorkflowToast(), [busy, setBusy] = useState(!1), pending = instance.pendingEffects ?? [];
7079
+ if (pending.length === 0) return null;
7080
+ const runRegistered = async () => {
7081
+ setBusy(!0);
7082
+ try {
7083
+ await drainEffectsFor(instance._id), toast.push({
7084
+ id: TOAST_ID.effectsDrain,
7085
+ status: "info",
7086
+ title: "Ran the registered handlers"
7087
+ });
7088
+ } catch (err) {
7089
+ toast.push({
7090
+ id: TOAST_ID.effectsDrain,
7091
+ status: "error",
7092
+ title: "Failed to run pending effects",
7093
+ description: describeError(err)
7094
+ });
7095
+ } finally {
7096
+ setBusy(!1);
7097
+ }
7098
+ }, resolve = async (effectKey, status) => {
7099
+ setBusy(!0);
7100
+ try {
7101
+ await completeEffectFor(instance._id, {
7102
+ effectKey: effectKey,
7103
+ status: status,
7104
+ detail: "Resolved in Studio"
7105
+ }), toast.push({
7106
+ id: TOAST_ID.effectResolve,
7107
+ status: "info",
7108
+ title: `Effect marked as ${status}`
7109
+ });
7110
+ } catch (err) {
7111
+ toast.push({
7112
+ id: TOAST_ID.effectResolve,
7113
+ status: "error",
7114
+ title: "Failed to resolve the effect",
7115
+ description: describeError(err)
7116
+ });
7117
+ } finally {
7118
+ setBusy(!1);
7119
+ }
7120
+ };
7121
+ /* @__PURE__ */
7122
+ return jsx(Fragment, {
7123
+ children: pending.map(fx => /* @__PURE__ */ jsx(PendingEffectRow, {
7124
+ busy: busy,
7125
+ effect: fx,
7126
+ instanceId: instance._id,
7127
+ now: now,
7128
+ onResolve: status => resolve(fx._key, status),
7129
+ onRunHandlers: runRegistered,
7130
+ registered: fx.name in effectHandlers
7131
+ }, fx._key))
7132
+ });
7133
+ }
7134
+
7135
+ function PendingEffectRow({busy: busy, effect: effect, instanceId: instanceId, now: now, onResolve: onResolve, onRunHandlers: onRunHandlers, registered: registered}) {
7136
+ const label = effect.title ?? effect.name, avatarSize = useAvatarSize();
7137
+ /* @__PURE__ */
7138
+ return jsxs(Flex, {
7139
+ align: "center",
7140
+ gap: 2,
7141
+ children: [
7142
+ /* @__PURE__ */ jsx(FeedGlyph, {
7143
+ hint: "Waiting to run",
7144
+ icon: /* @__PURE__ */ jsx(ClockIcon, {})
7145
+ }),
7146
+ /* @__PURE__ */ jsxs(Flex, {
7147
+ align: "center",
7148
+ flex: 1,
7149
+ gap: 1,
7150
+ style: {
7151
+ minWidth: 0
7152
+ },
7153
+ children: [
7154
+ /* @__PURE__ */ jsx(Box, {
7155
+ style: {
7156
+ minWidth: 0
7157
+ },
7158
+ children: /* @__PURE__ */ jsxs(Text, {
7159
+ size: 1,
7160
+ textOverflow: "ellipsis",
7161
+ children: [ effect.title ?? /* @__PURE__ */ jsx(CodeChip, {
7162
+ value: effect.name
7163
+ }), registered ? " — ready to run in Studio" : " — waiting for the runtime" ]
7164
+ })
7165
+ }),
7166
+ /* @__PURE__ */ jsx(FeedStamp, {
7167
+ at: effect.queuedAt,
7168
+ now: now
7169
+ }) ]
7170
+ }),
7171
+ /* @__PURE__ */ jsx(Flex, {
7172
+ align: "center",
7173
+ flex: "none",
7174
+ style: {
7175
+ height: avatarSize
7176
+ },
7177
+ children: /* @__PURE__ */ jsx(HintedMenuButton, {
7178
+ button: /* @__PURE__ */ jsx(Button, {
7179
+ "aria-label": `Resolve ${label}`,
7180
+ disabled: busy,
7181
+ fontSize: 1,
7182
+ icon: EllipsisHorizontalIcon,
7183
+ mode: "bleed",
7184
+ padding: 2
7185
+ }),
7186
+ hint: "Resolve",
7187
+ id: `pending-effect-menu-${instanceId}-${effect._key}`,
7188
+ menu: /* @__PURE__ */ jsxs(Menu, {
7189
+ children: [ registered ? /* @__PURE__ */ jsx(MenuItem, {
7190
+ onClick: onRunHandlers,
7191
+ text: "Run registered handlers"
7192
+ }) : null,
7193
+ /* @__PURE__ */ jsx(MenuItem, {
7194
+ onClick: () => onResolve("done"),
7195
+ text: "Mark done"
7196
+ }),
7197
+ /* @__PURE__ */ jsx(MenuItem, {
7198
+ onClick: () => onResolve("failed"),
7199
+ text: "Mark failed",
7200
+ tone: "critical"
7201
+ }) ]
7202
+ }),
7203
+ popover: {
7204
+ placement: "bottom-end",
7205
+ portal: !0
7206
+ }
7207
+ })
7208
+ }) ]
7209
+ });
7210
+ }
7211
+
7212
+ function ActivityLog({instance: instance, definition: definition, windowed: windowed = !0}) {
7213
+ const titles = useMemo(() => makeTitleResolver(definition), [ definition ]), entries = useMemo(() => instance.history.toReversed(), [ instance.history ]), [now, setNow] = useState(() => /* @__PURE__ */ new Date), [expanded, setExpanded] = useState(!1);
7214
+ useEffect(() => {
7215
+ const timer = setInterval(() => setNow(/* @__PURE__ */ new Date), 6e4);
7216
+ return () => clearInterval(timer);
7217
+ }, []);
7218
+ const history = historyWindow({
7219
+ entries: entries,
7220
+ expanded: expanded || !windowed
7221
+ });
7222
+ return entries.length === 0 && (instance.pendingEffects ?? []).length === 0 ? /* @__PURE__ */ jsx(Text, {
7223
+ muted: !0,
7224
+ size: 1,
7225
+ children: "No activity yet"
7226
+ }) : /* @__PURE__ */ jsxs(Stack, {
7227
+ gap: 3,
7228
+ children: [
7229
+ /* @__PURE__ */ jsx(PendingEffectRows, {
7230
+ instance: instance,
7231
+ now: now
7232
+ }), history.shown.map(h => /* @__PURE__ */ jsx(HistoryRow, {
7233
+ entry: h,
7234
+ now: now,
7235
+ titles: titles
7236
+ }, h._key)), windowed && history.overflows ? /* @__PURE__ */ jsx(Flex, {
7237
+ children: /* @__PURE__ */ jsx(Button, {
7238
+ fontSize: 1,
7239
+ mode: "bleed",
7240
+ onClick: () => setExpanded(!expanded),
7241
+ padding: 2,
7242
+ text: expanded ? "Show less history" : "Show all history"
7243
+ })
7244
+ }) : null ]
7245
+ });
7246
+ }
7247
+
7248
+ function FeedStamp({at: at, now: now}) {
7249
+ /* @__PURE__ */
7250
+ return jsxs(Flex, {
7251
+ align: "center",
7252
+ flex: "none",
7253
+ gap: 1,
7254
+ children: [
7255
+ /* @__PURE__ */ jsx(Text, {
7256
+ muted: !0,
7257
+ size: 1,
7258
+ children: "·"
7259
+ }),
7260
+ /* @__PURE__ */ jsx(HoverHint, {
7261
+ text: formatDateTime(at),
7262
+ children: /* @__PURE__ */ jsx(Text, {
7263
+ muted: !0,
7264
+ size: 1,
7265
+ style: {
7266
+ whiteSpace: "nowrap"
7267
+ },
7268
+ children: formatShortAgo(at, now)
7269
+ })
7270
+ }) ]
7271
+ });
7272
+ }
7273
+
7274
+ function HistoryRow({entry: entry, now: now, titles: titles}) {
7275
+ const line = historyLine(entry, titles);
7276
+ /* @__PURE__ */
7277
+ return jsxs(Flex, {
7278
+ align: "center",
7279
+ gap: 2,
7280
+ children: [ line.actorId === void 0 ? /* @__PURE__ */ jsx(FeedGlyph, {
7281
+ hint: "The workflow",
7282
+ icon: /* @__PURE__ */ jsx(CogIcon, {})
7283
+ }) : /* @__PURE__ */ jsx(UserAvatar, {
7284
+ id: line.actorId
7285
+ }),
7286
+ /* @__PURE__ */ jsxs(Flex, {
7287
+ align: "center",
7288
+ flex: 1,
7289
+ gap: 1,
7290
+ style: {
7291
+ minWidth: 0
7292
+ },
7293
+ children: [
7294
+ /* @__PURE__ */ jsx(Box, {
7295
+ style: {
7296
+ minWidth: 0
7297
+ },
7298
+ children: /* @__PURE__ */ jsxs(Text, {
7299
+ muted: !0,
7300
+ size: 1,
7301
+ textOverflow: "ellipsis",
7302
+ children: [ line.actorId === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, {
7303
+ children: [
7304
+ /* @__PURE__ */ jsx(ActorNameSpan, {
7305
+ id: line.actorId
7306
+ }), " " ]
7307
+ }), line.parts.map((part, index) => typeof part == "string" ? part : /* @__PURE__ */ jsx(CodeChip, {
7308
+ value: part.code
7309
+ }, index)) ]
7310
+ })
7311
+ }),
7312
+ /* @__PURE__ */ jsx(FeedStamp, {
7313
+ at: entry.at,
7314
+ now: now
7315
+ }) ]
7316
+ }) ]
7317
+ });
7318
+ }
7319
+
7320
+ function ActorNameSpan({id: id}) {
7321
+ const {name: name} = useUserDisplay(id), {font: font} = useTheme_v2();
7322
+ /* @__PURE__ */
7323
+ return jsx("span", {
7324
+ style: {
7325
+ fontWeight: font.text.weights.medium
7326
+ },
7327
+ children: name
7328
+ });
7329
+ }
7330
+
7331
+ function FeedGlyph({hint: hint, icon: icon}) {
7332
+ const size = useAvatarSize();
7333
+ /* @__PURE__ */
7334
+ return jsx(HoverHint, {
7335
+ text: hint,
7336
+ children: /* @__PURE__ */ jsx(Flex, {
7337
+ align: "center",
7338
+ flex: "none",
7339
+ justify: "center",
7340
+ style: {
7341
+ height: size,
7342
+ width: size
7343
+ },
7344
+ children: /* @__PURE__ */ jsx(Text, {
7345
+ muted: !0,
7346
+ size: 1,
7347
+ children: icon
7348
+ })
7349
+ })
7350
+ });
7351
+ }
7352
+
7353
+ function InstanceHistoryDialog({entry: entry, onClose: onClose}) {
7354
+ const definition = useDefinition(entry), {instance: instance} = entry;
7355
+ /* @__PURE__ */
7356
+ return jsx(Dialog, {
7357
+ header: /* @__PURE__ */ jsx(Breadcrumb, {
7358
+ emphasizeCurrent: !0,
7359
+ segments: [ instanceTitle(instance, definition), "History" ]
7360
+ }),
7361
+ id: `workflow-instance-history-${instance._id}`,
7362
+ onClose: onClose,
7363
+ width: 1,
7364
+ children: /* @__PURE__ */ jsx(Box, {
7365
+ padding: 4,
7366
+ children: /* @__PURE__ */ jsx(ActivityLog, {
7367
+ definition: definition,
7368
+ instance: instance,
7369
+ windowed: !1
7370
+ })
7371
+ })
7372
+ });
7373
+ }
7374
+
7375
+ function InstanceCardMenu({entry: entry, title: title}) {
7376
+ const [dialog, setDialog] = useState(void 0), menuId = useId(), close = () => setDialog(void 0);
7377
+ /* @__PURE__ */
7378
+ return jsxs(Fragment, {
7379
+ children: [
7380
+ /* @__PURE__ */ jsx(MenuButton, {
7381
+ button:
7382
+ /* @__PURE__ */ jsx(Button, {
7383
+ "aria-label": `More options for ${title}`,
7384
+ fontSize: 1,
7385
+ icon: EllipsisHorizontalIcon,
7386
+ mode: "bleed",
7387
+ padding: 2
7388
+ }),
7389
+ id: `workflow-instance-menu-${menuId}`,
7390
+ menu: /* @__PURE__ */ jsxs(Menu, {
7391
+ children: [
7392
+ /* @__PURE__ */ jsx(MenuItem, {
7393
+ onClick: () => setDialog("history"),
7394
+ text: "Workflow history"
7395
+ }), isLiveEntry(entry) ? /* @__PURE__ */ jsxs(Fragment, {
7396
+ children: [
7397
+ /* @__PURE__ */ jsx(MenuDivider, {}),
7398
+ /* @__PURE__ */ jsx(MenuItem, {
7399
+ onClick: () => setDialog("abort"),
7400
+ text: "Abort workflow…",
7401
+ tone: "critical"
7402
+ }) ]
7403
+ }) : null ]
7404
+ }),
7405
+ popover: {
7406
+ placement: "bottom-end",
7407
+ portal: !0
7408
+ }
7409
+ }), dialog === "history" ? /* @__PURE__ */ jsx(InstanceHistoryDialog, {
7410
+ entry: entry,
7411
+ onClose: close
7412
+ }) : null, dialog === "abort" ? /* @__PURE__ */ jsx(AbortWorkflowDialog, {
7413
+ entry: entry,
7414
+ onClose: close
7415
+ }) : null ]
7416
+ });
7417
+ }
7418
+
7419
+ function InstanceCard({entry: entry, children: children, defaultOpen: defaultOpen}) {
7420
+ const definition = useDefinition(entry), {instance: instance} = entry, settled = !isLiveEntry(entry), [open, setOpen] = useState(defaultOpen ?? !settled), title = instanceTitle(instance, definition), menuSlot = useSpaceToken(2) * 2 + useTextIconSize();
7421
+ /* @__PURE__ */
7422
+ return jsxs(Card, {
7423
+ border: !0,
7424
+ overflow: "hidden",
7425
+ radius: 4,
7426
+ children: [
7427
+ /* @__PURE__ */ jsxs(Box, {
7428
+ style: {
7429
+ position: "relative"
7430
+ },
7431
+ children: [
7432
+ /* @__PURE__ */ jsx(Card, {
7433
+ "aria-expanded": open,
7434
+ as: "button",
7435
+ onClick: () => setOpen(value => !value),
7436
+ padding: 3,
7437
+ style: {
7438
+ display: "block",
7439
+ textAlign: "left",
7440
+ width: "100%"
7441
+ },
7442
+ ...open ? {
7443
+ tone: "transparent"
7444
+ } : {},
7445
+ children: /* @__PURE__ */ jsxs(Flex, {
7446
+ align: "center",
7447
+ gap: 3,
7448
+ children: [
7449
+ /* @__PURE__ */ jsx(Text, {
7450
+ muted: !0,
7451
+ size: 1,
7452
+ children: open ? /* @__PURE__ */ jsx(ChevronDownIcon, {}) : /* @__PURE__ */ jsx(ChevronRightIcon, {})
7453
+ }),
7454
+ /* @__PURE__ */ jsxs(Stack, {
7455
+ flex: 1,
7456
+ gap: 2,
7457
+ style: {
7458
+ minWidth: 0
7459
+ },
7460
+ children: [
7461
+ /* @__PURE__ */ jsx(Text, {
7462
+ size: 1,
7463
+ textOverflow: "ellipsis",
7464
+ weight: "medium",
7465
+ children: title
7466
+ }),
7467
+ /* @__PURE__ */ jsx(HeaderStage, {
7468
+ entry: entry
7469
+ }) ]
7470
+ }),
7471
+ /* @__PURE__ */ jsxs(Flex, {
7472
+ align: "center",
7473
+ flex: "none",
7474
+ children: [
7475
+ /* @__PURE__ */ jsx(HeaderBadges, {
7476
+ instance: instance,
7477
+ settled: settled
7478
+ }),
7479
+ /* @__PURE__ */ jsx(Box, {
7480
+ flex: "none",
7481
+ style: {
7482
+ width: menuSlot
7483
+ }
7484
+ }) ]
7485
+ }) ]
7486
+ })
7487
+ }),
7488
+ /* @__PURE__ */ jsx(Flex, {
7489
+ align: "center",
7490
+ style: {
7491
+ bottom: 0,
7492
+ position: "absolute",
7493
+ right: 0,
7494
+ top: 0
7495
+ },
7496
+ children: /* @__PURE__ */ jsx(Box, {
7497
+ paddingRight: 3,
7498
+ children: /* @__PURE__ */ jsx(InstanceCardMenu, {
7499
+ entry: entry,
7500
+ title: title
7501
+ })
7502
+ })
7503
+ }) ]
7504
+ }), open ? /* @__PURE__ */ jsxs(Fragment, {
7505
+ children: [
7506
+ /* @__PURE__ */ jsx(Hairline, {
7507
+ weight: 1
7508
+ }),
7509
+ /* @__PURE__ */ jsx(Box, {
7510
+ padding: 3,
7511
+ children: children
7512
+ }) ]
7513
+ }) : null ]
6738
7514
  });
6739
7515
  }
6740
7516
 
6741
- function StageGlyph(props) {
7517
+ function HeaderStage({entry: entry}) {
7518
+ const definition = useDefinition(entry), badgeTrim = useBadgeCapTrim(), face = instanceStageFace({
7519
+ definition: definition,
7520
+ instance: entry.instance
7521
+ });
6742
7522
  /* @__PURE__ */
6743
- return jsxs("svg", {
6744
- "data-sanity-icon": "stage",
6745
- fill: "none",
6746
- height: "1em",
6747
- viewBox: "0 0 25 25",
6748
- width: "1em",
6749
- xmlns: "http://www.w3.org/2000/svg",
6750
- ...props,
6751
- children: [
6752
- /* @__PURE__ */ jsx("path", {
6753
- d: "M14.7998 12.5C14.7998 11.2295 13.7705 10.2002 12.5 10.2002C11.2295 10.2002 10.2002 11.2295 10.2002 12.5C10.2002 13.7705 11.2295 14.7998 12.5 14.7998V16C10.5668 16 9 14.4332 9 12.5C9 10.5668 10.5668 9 12.5 9C14.4332 9 16 10.5668 16 12.5C16 14.4332 14.4332 16 12.5 16V14.7998C13.7705 14.7998 14.7998 13.7705 14.7998 12.5Z",
6754
- fill: "currentColor"
6755
- }),
6756
- /* @__PURE__ */ jsx("path", {
6757
- d: "M4.5 12.5C4.5 12.1687 4.76863 11.9 5.1 11.9H9.5V13.1H5.1C4.76863 13.1 4.5 12.8314 4.5 12.5V12.5Z",
6758
- fill: "currentColor"
6759
- }),
6760
- /* @__PURE__ */ jsx("path", {
6761
- d: "M15.5 11.9H19.9C20.2314 11.9 20.5 12.1687 20.5 12.5V12.5C20.5 12.8314 20.2314 13.1 19.9 13.1H15.5V11.9Z",
6762
- fill: "currentColor"
6763
- }) ]
7523
+ return jsx(Flex, {
7524
+ align: "center",
7525
+ children: face === void 0 ? /* @__PURE__ */ jsx(StartIncompleteBadge, {
7526
+ style: badgeTrim
7527
+ }) : /* @__PURE__ */ jsx(StageFace, {
7528
+ ...face.detail === void 0 ? {} : {
7529
+ detail: face.detail
7530
+ },
7531
+ completed: face.completed,
7532
+ glyph: !1,
7533
+ title: face.title
7534
+ })
6764
7535
  });
6765
7536
  }
6766
7537
 
6767
- function StageFace({completed: completed = !1, detail: detail, title: title}) {
6768
- const tone = completed ? "default" : "primary";
7538
+ function HeaderBadges({instance: instance, settled: settled}) {
7539
+ const badgeTrim = useBadgeCapTrim();
6769
7540
  /* @__PURE__ */
6770
7541
  return jsxs(Flex, {
6771
7542
  align: "center",
6772
- as: "span",
7543
+ flex: "none",
6773
7544
  gap: 2,
7545
+ style: badgeTrim,
6774
7546
  children: [
6775
- /* @__PURE__ */ jsx(TextWithTone, {
6776
- size: 1,
6777
- tone: tone,
6778
- children: completed ? /* @__PURE__ */ jsx(CheckmarkCircleFilledIcon, {
6779
- style: {
6780
- color: "inherit"
6781
- }
6782
- }) : /* @__PURE__ */ jsx(StageGlyph, {
6783
- style: {
6784
- color: "inherit"
6785
- }
6786
- })
6787
- }),
6788
- /* @__PURE__ */ jsx(TextWithTone, {
6789
- size: 1,
6790
- tone: tone,
6791
- weight: "medium",
6792
- children: title
6793
- }), detail === void 0 ? null : /* @__PURE__ */ jsx(Text, {
6794
- muted: !0,
6795
- size: 1,
6796
- children: detail
7547
+ /* @__PURE__ */ jsx(AbortedBadge, {
7548
+ instance: instance
7549
+ }), settled ? null : /* @__PURE__ */ jsx(OlderDefinitionBadge, {
7550
+ instance: instance
6797
7551
  }) ]
6798
7552
  });
6799
7553
  }
6800
7554
 
6801
- function StageChip({completed: completed = !1, detail: detail, interactive: interactive = !1, title: title}) {
6802
- const {color: color} = useTheme_v2();
7555
+ function PartOfBand({instance: instance}) {
7556
+ const parentRef = instance.ancestors.at(-1), parentId = parentRef ? toBareId(parentRef.id) : null, parentEntry = useWorkflowInstanceEntry(parentId), parentDefinition = useDefinition(parentEntry);
7557
+ if (!parentId) return null;
7558
+ const title = parentEntry ? instanceTitle(parentEntry.instance, parentDefinition) : parentId;
6803
7559
  /* @__PURE__ */
6804
7560
  return jsx(Card, {
7561
+ border: !0,
6805
7562
  padding: 3,
6806
- radius: 4,
6807
- style: {
6808
- boxShadow: `inset 0 0 0 1px ${completed ? color.badge.default.fg : color.focusRing}`,
6809
- cursor: interactive ? "help" : "default"
6810
- },
6811
- tone: completed ? "default" : "primary",
6812
- ...interactive ? {
6813
- tabIndex: 0
6814
- } : {},
6815
- children: /* @__PURE__ */ jsx(StageFace, {
6816
- completed: completed,
6817
- title: title,
6818
- ...detail === void 0 ? {} : {
6819
- detail: detail
6820
- }
7563
+ radius: 3,
7564
+ tone: "primary",
7565
+ children: /* @__PURE__ */ jsxs(Flex, {
7566
+ align: "center",
7567
+ gap: 2,
7568
+ children: [
7569
+ /* @__PURE__ */ jsx(Text, {
7570
+ size: 1,
7571
+ children: /* @__PURE__ */ jsx(ArrowUpIcon, {})
7572
+ }),
7573
+ /* @__PURE__ */ jsxs(Text, {
7574
+ size: 1,
7575
+ children: [ "Part of ",
7576
+ /* @__PURE__ */ jsx("strong", {
7577
+ children: title
7578
+ }) ]
7579
+ }) ]
6821
7580
  })
6822
7581
  });
6823
7582
  }
@@ -6864,20 +7623,23 @@ function SpineNodeView({definition: definition, node: node, transitions: transit
6864
7623
  transitions: transitions
6865
7624
  })
6866
7625
  });
6867
- if (node.state === "current") /* @__PURE__ */
6868
- return jsx(HoverHint, {
6869
- content: card,
6870
- "data-testid": `spine-node-${node.name}`,
6871
- flush: !0,
6872
- children: /* @__PURE__ */ jsx(StageChip, {
6873
- ...node.round === void 0 ? {} : {
6874
- detail: `Round ${node.round}`
6875
- },
6876
- completed: node.completedAt !== void 0,
6877
- interactive: !0,
6878
- title: node.title
6879
- })
6880
- });
7626
+ if (node.state === "current") {
7627
+ const detail = roundDetail(node.round);
7628
+ /* @__PURE__ */
7629
+ return jsx(HoverHint, {
7630
+ content: card,
7631
+ "data-testid": `spine-node-${node.name}`,
7632
+ flush: !0,
7633
+ children: /* @__PURE__ */ jsx(StageChip, {
7634
+ ...detail === void 0 ? {} : {
7635
+ detail: detail
7636
+ },
7637
+ completed: node.completedAt !== void 0,
7638
+ interactive: !0,
7639
+ title: node.title
7640
+ })
7641
+ });
7642
+ }
6881
7643
  const Icon = NODE_ICON[node.state];
6882
7644
  /* @__PURE__ */
6883
7645
  return jsx(HoverHint, {
@@ -7028,20 +7790,13 @@ function StampLines({stamp: stamp}) {
7028
7790
 
7029
7791
  function WorkflowInstanceSection({defaultOpen: defaultOpen, entry: entry, onOpenActivity: onOpenActivity}) {
7030
7792
  /* @__PURE__ */
7031
- return jsx(InstanceAccordion, {
7793
+ return jsx(InstanceCard, {
7032
7794
  defaultOpen: defaultOpen,
7033
7795
  entry: entry,
7034
- children: /* @__PURE__ */ jsxs(Stack, {
7035
- gap: 3,
7036
- children: [
7037
- /* @__PURE__ */ jsx(InstanceSnapshotBody, {
7038
- entry: entry,
7039
- onOpenActivity: onOpenActivity,
7040
- surface: "document-view"
7041
- }),
7042
- /* @__PURE__ */ jsx(InstanceToolLink, {
7043
- entry: entry
7044
- }) ]
7796
+ children: /* @__PURE__ */ jsx(InstanceSnapshotBody, {
7797
+ entry: entry,
7798
+ onOpenActivity: onOpenActivity,
7799
+ surface: "document-view"
7045
7800
  })
7046
7801
  });
7047
7802
  }
@@ -7068,7 +7823,9 @@ function InstanceSnapshotBody({entry: entry, onOpenActivity: onOpenActivity, sur
7068
7823
  /* @__PURE__ */ jsx(Card, {
7069
7824
  borderBottom: !0,
7070
7825
  padding: 2,
7071
- tone: definition ? "neutral" : "caution",
7826
+ ...definition ? {} : {
7827
+ tone: "caution"
7828
+ },
7072
7829
  children: definition ? /* @__PURE__ */ jsx(WorkflowSpine, {
7073
7830
  definition: definition,
7074
7831
  evaluation: entry.evaluation,
@@ -7113,18 +7870,23 @@ function StageSection({entry: entry, onOpenActivity: onOpenActivity, surface: su
7113
7870
  });
7114
7871
  }
7115
7872
 
7116
- function CautionNote({action: action, label: label}) {
7873
+ const BANNER_PADDING = 3, ICONS = {
7874
+ caution: /* @__PURE__ */ jsx(WarningOutlineIcon, {}),
7875
+ critical: /* @__PURE__ */ jsx(ErrorOutlineIcon, {})
7876
+ };
7877
+
7878
+ function NoteBanner({action: action, label: label, tone: tone = "caution"}) {
7117
7879
  /* @__PURE__ */
7118
7880
  return jsxs(Card, {
7119
7881
  paddingLeft: 3,
7120
7882
  paddingRight: 2,
7121
- paddingY: 3,
7883
+ paddingY: BANNER_PADDING,
7122
7884
  radius: 3,
7123
7885
  style: {
7124
7886
  alignItems: "center",
7125
7887
  display: "flex"
7126
7888
  },
7127
- tone: "caution",
7889
+ tone: tone,
7128
7890
  children: [
7129
7891
  /* @__PURE__ */ jsxs(Flex, {
7130
7892
  align: "center",
@@ -7134,7 +7896,7 @@ function CautionNote({action: action, label: label}) {
7134
7896
  /* @__PURE__ */ jsx(Text, {
7135
7897
  muted: !0,
7136
7898
  size: 1,
7137
- children: /* @__PURE__ */ jsx(WarningOutlineIcon, {})
7899
+ children: ICONS[tone]
7138
7900
  }),
7139
7901
  /* @__PURE__ */ jsx(Text, {
7140
7902
  muted: !0,
@@ -7171,7 +7933,7 @@ function UnreadableDocsNote({unreadable: unreadable}) {
7171
7933
  if (unreadable.length === 0) return null;
7172
7934
  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.`;
7173
7935
  /* @__PURE__ */
7174
- return jsx(CautionNote, {
7936
+ return jsx(NoteBanner, {
7175
7937
  action: /* @__PURE__ */ jsx(CopyDetailsButton, {
7176
7938
  unreadable: unreadable
7177
7939
  }),
@@ -7217,6 +7979,21 @@ function InvalidDocNotice({invalid: invalid}) {
7217
7979
  });
7218
7980
  }
7219
7981
 
7982
+ function RoleAssignedNotice() {
7983
+ /* @__PURE__ */
7984
+ return jsx(Box, {
7985
+ padding: 2,
7986
+ children: /* @__PURE__ */ jsx(Text, {
7987
+ muted: !0,
7988
+ size: 1,
7989
+ style: {
7990
+ fontStyle: "italic"
7991
+ },
7992
+ children: "Including tasks assigned to your role"
7993
+ })
7994
+ });
7995
+ }
7996
+
7220
7997
  function TabSwitch({ariaControls: ariaControls, idPrefix: idPrefix, options: options, selected: selected, onSelect: onSelect}) {
7221
7998
  /* @__PURE__ */
7222
7999
  return jsx(TabList, {
@@ -7243,30 +8020,6 @@ function openActivityGone(args) {
7243
8020
  return evaluation !== void 0 && !evaluation.currentStage.activities.some(a => a.activity.name === target.activityName);
7244
8021
  }
7245
8022
 
7246
- function HintedMenuButton({hint: hint, ...menuButtonProps}) {
7247
- const [open, setOpen] = useState(!1), popover = {
7248
- tone: "default",
7249
- ...menuButtonProps.popover?.placement === void 0 ? {
7250
- placement: "bottom-end",
7251
- fallbackPlacements: [ "top-end" ]
7252
- } : {},
7253
- ...menuButtonProps.popover
7254
- };
7255
- return hint === void 0 ? /* @__PURE__ */ jsx(MenuButton, {
7256
- ...menuButtonProps,
7257
- popover: popover
7258
- }) : /* @__PURE__ */ jsx(HoverHint, {
7259
- disabled: open,
7260
- text: hint,
7261
- children: /* @__PURE__ */ jsx(MenuButton, {
7262
- ...menuButtonProps,
7263
- onClose: () => setOpen(!1),
7264
- onOpen: () => setOpen(!0),
7265
- popover: popover
7266
- })
7267
- });
7268
- }
7269
-
7270
8023
  function subjectGdr(ctx) {
7271
8024
  return !ctx.subjectDocId || !ctx.mapping ? null : subjectGdrRef({
7272
8025
  mapping: ctx.mapping,
@@ -7505,8 +8258,15 @@ function presentedResumeEntry(args) {
7505
8258
  if (!args.startNew) return findResumeEntry(args);
7506
8259
  }
7507
8260
 
8261
+ function settledByAnotherWriter(cause, instance) {
8262
+ return isRevisionConflict(cause) && terminalState(instance) === "completed";
8263
+ }
8264
+
7508
8265
  function classifyStartError(err) {
7509
- return err instanceof StartNotSettledError ? {
8266
+ return err instanceof StartNotSettledError ? err.instance !== void 0 && settledByAnotherWriter(err.cause, err.instance) ? {
8267
+ kind: "started-settled-elsewhere",
8268
+ instance: err.instance
8269
+ } : {
7510
8270
  kind: "started-not-settled",
7511
8271
  instance: err.instance,
7512
8272
  description: describeError(err.cause)
@@ -7519,8 +8279,16 @@ function classifyStartError(err) {
7519
8279
  };
7520
8280
  }
7521
8281
 
8282
+ function startProducedRun(outcome) {
8283
+ return outcome.kind === "started-not-settled" || outcome.kind === "started-settled-elsewhere";
8284
+ }
8285
+
7522
8286
  function startOutcomeToast(outcome, label) {
7523
- return outcome.kind === "started-not-settled" ? {
8287
+ return outcome.kind === "started-settled-elsewhere" ? {
8288
+ id: TOAST_ID.start,
8289
+ status: "info",
8290
+ title: `“${label}” started and finished`
8291
+ } : outcome.kind === "started-not-settled" ? {
7524
8292
  id: TOAST_ID.start,
7525
8293
  status: "warning",
7526
8294
  title: `“${label}” started but didn’t finish`,
@@ -8086,7 +8854,7 @@ function DiscoverySpinner() {
8086
8854
  });
8087
8855
  }
8088
8856
 
8089
- const BOOT_CUE_DELAY_MS = 400, GROUP_GAP = 1;
8857
+ const BOOT_CUE_DELAY_MS = 400, GROUP_GAP = 2;
8090
8858
 
8091
8859
  function useWorkflowsTab() {
8092
8860
  const {params: params, setParams: setParams} = usePaneRouter();
@@ -8251,10 +9019,10 @@ function OverviewEmptyState({mappings: mappings, docId: docId, initialValue: ini
8251
9019
  }
8252
9020
 
8253
9021
  function ForMeTab({forMe: forMe, assignedWorkUnknown: assignedWorkUnknown, onOpenActivity: onOpenActivity}) {
8254
- return forMe.hasRows ? /* @__PURE__ */ jsx(Stack, {
9022
+ return forMe.hasRows ? /* @__PURE__ */ jsxs(Stack, {
8255
9023
  gap: GROUP_GAP,
8256
9024
  paddingBottom: 4,
8257
- children: forMe.work.map(({entry: entry, who: who, shown: shown}) => /* @__PURE__ */ jsx(ForMeList, {
9025
+ children: [ forMe.hasRoleMatched ? /* @__PURE__ */ jsx(RoleAssignedNotice, {}) : null, forMe.work.map(({entry: entry, who: who, shown: shown}) => /* @__PURE__ */ jsx(ForMeList, {
8258
9026
  entry: entry,
8259
9027
  identity: who,
8260
9028
  work: shown,
@@ -8263,7 +9031,7 @@ function ForMeTab({forMe: forMe, assignedWorkUnknown: assignedWorkUnknown, onOpe
8263
9031
  activityName: activityName,
8264
9032
  stage: entry.instance.currentStage
8265
9033
  })
8266
- }, entry.instance._id))
9034
+ }, entry.instance._id)) ]
8267
9035
  }) : assignedWorkUnknown ? null : /* @__PURE__ */ jsx(ForMeEmptyState, {});
8268
9036
  }
8269
9037
 
@@ -8966,27 +9734,19 @@ function logOnce(messages, logged) {
8966
9734
  }
8967
9735
 
8968
9736
  function AssigneesInput({value: value, onChange: onChange}) {
8969
- const {members: members} = useProjectMembers(), [membersOpen, setMembersOpen] = useState(!1), roles = useMemo(() => [ ...new Set(members.flatMap(m => m.roles)) ].toSorted(), [ members ]), userIds = useMemo(() => new Set(value.flatMap(a => a.type === "user" ? [ a.id ] : [])), [ value ]), add = a => {
8970
- value.some(x => sameAssignee(x, a)) || onChange([ ...value, a ]);
8971
- }, remove = i => onChange(value.filter((_, idx) => idx !== i)), toggleUser = id => {
8972
- const user = {
8973
- type: "user",
8974
- id: id
8975
- };
8976
- userIds.has(id) ? onChange(value.filter(a => !sameAssignee(a, user))) : add(user);
8977
- };
9737
+ const [open, setOpen] = useState(!1), remove = index => onChange(value.filter((_, i) => i !== index));
8978
9738
  /* @__PURE__ */
8979
9739
  return jsxs(Stack, {
8980
9740
  gap: 2,
8981
9741
  children: [ value.length > 0 ? /* @__PURE__ */ jsx(Flex, {
8982
9742
  gap: 2,
8983
9743
  wrap: "wrap",
8984
- children: value.map((a, i) => /* @__PURE__ */ jsxs(Flex, {
9744
+ children: value.map((assignee, index) => /* @__PURE__ */ jsxs(Flex, {
8985
9745
  align: "center",
8986
9746
  gap: 1,
8987
9747
  children: [
8988
9748
  /* @__PURE__ */ jsx(AssigneeBadge, {
8989
- assignee: a
9749
+ assignee: assignee
8990
9750
  }),
8991
9751
  /* @__PURE__ */ jsx(HoverHint, {
8992
9752
  text: "Remove",
@@ -8995,60 +9755,33 @@ function AssigneesInput({value: value, onChange: onChange}) {
8995
9755
  fontSize: 1,
8996
9756
  icon: CloseIcon,
8997
9757
  mode: "bleed",
8998
- onClick: () => remove(i),
9758
+ onClick: () => remove(index),
8999
9759
  padding: 2
9000
9760
  })
9001
9761
  }) ]
9002
- }, a.type === "user" ? `user:${a.id}` : `role:${a.role}`))
9762
+ }, assignee.type === "user" ? `user:${assignee.id}` : `role:${assignee.role}`))
9003
9763
  }) : null,
9004
- /* @__PURE__ */ jsxs(Flex, {
9764
+ /* @__PURE__ */ jsx(Flex, {
9005
9765
  align: "center",
9006
9766
  gap: 2,
9007
9767
  wrap: "wrap",
9008
- children: [
9009
- /* @__PURE__ */ jsx(DismissablePopover, {
9010
- content: /* @__PURE__ */ jsx(MemberPicker, {
9011
- onSelect: member => member && toggleUser(member.id),
9012
- selectedIds: userIds
9768
+ children: /* @__PURE__ */ jsx(DismissablePopover, {
9769
+ content: /* @__PURE__ */ jsx(AssigneePicker, {
9770
+ onChange: next => onChange([ ...next ]),
9771
+ value: value
9013
9772
  }),
9014
- onDismiss: () => setMembersOpen(!1),
9015
- open: membersOpen,
9773
+ onDismiss: () => setOpen(!1),
9774
+ open: open,
9016
9775
  placement: "bottom-start",
9017
9776
  children: /* @__PURE__ */ jsx(Button, {
9018
9777
  fontSize: 1,
9019
9778
  icon: UserIcon,
9020
9779
  mode: "ghost",
9021
- onClick: () => setMembersOpen(v => !v),
9780
+ onClick: () => setOpen(v => !v),
9022
9781
  padding: 2,
9023
- text: "Add member"
9024
- })
9025
- }), roles.length > 0 ? /* @__PURE__ */ jsx(MenuButton, {
9026
- button: /* @__PURE__ */ jsx(Button, {
9027
- fontSize: 1,
9028
- icon: AddIcon,
9029
- mode: "ghost",
9030
- padding: 2,
9031
- text: "Add role"
9032
- }),
9033
- id: "assignees-add-role",
9034
- menu: /* @__PURE__ */ jsx(Menu, {
9035
- style: {
9036
- maxHeight: 280,
9037
- overflowY: "auto"
9038
- },
9039
- children: roles.map(r => /* @__PURE__ */ jsx(MenuItem, {
9040
- onClick: () => add({
9041
- type: "role",
9042
- role: r
9043
- }),
9044
- text: `Role: ${r}`
9045
- }, r))
9782
+ text: "Assign"
9046
9783
  })
9047
- }) : /* @__PURE__ */ jsx(Text, {
9048
- muted: !0,
9049
- size: 1,
9050
- children: "No project roles found"
9051
- }) ]
9784
+ })
9052
9785
  }) ]
9053
9786
  });
9054
9787
  }
@@ -9535,7 +10268,7 @@ function ResumeNote({onStartNew: onStartNew}) {
9535
10268
 
9536
10269
  function handleStartError(args) {
9537
10270
  const {label: label, toast: toast, seedInstance: seedInstance, onClose: onClose} = args, outcome = classifyStartError(args.err);
9538
- toast.push(startOutcomeToast(outcome, label)), outcome.kind === "started-not-settled" && (outcome.instance !== void 0 && seedInstance(outcome.instance),
10271
+ toast.push(startOutcomeToast(outcome, label)), startProducedRun(outcome) && (outcome.instance !== void 0 && seedInstance(outcome.instance),
9539
10272
  onClose());
9540
10273
  }
9541
10274
 
@@ -9888,7 +10621,7 @@ async function startRequest(engine, request) {
9888
10621
  };
9889
10622
  } catch (err) {
9890
10623
  const outcome = classifyStartError(err);
9891
- return outcome.kind === "started-not-settled" ? outcome.instance ? {
10624
+ return startProducedRun(outcome) ? outcome.instance ? {
9892
10625
  instance: outcome.instance
9893
10626
  } : {} : outcome.kind === "not-allowed" ? {} : {
9894
10627
  failure: outcome.description
@@ -9978,17 +10711,6 @@ function makePerspectiveBadge(documentId) {
9978
10711
  return WorkflowPerspectiveBadge;
9979
10712
  }
9980
10713
 
9981
- function StartIncompleteBadge({style: style}) {
9982
- /* @__PURE__ */
9983
- return jsx(Badge, {
9984
- fontSize: 1,
9985
- mode: "outline",
9986
- style: style,
9987
- tone: "caution",
9988
- children: "Start incomplete"
9989
- });
9990
- }
9991
-
9992
10714
  const GLANCE_CAP = 3;
9993
10715
 
9994
10716
  function AssignedTasksHint({work: work}) {
@@ -10498,4 +11220,4 @@ function WorkflowRootInput(props) {
10498
11220
  });
10499
11221
  }
10500
11222
 
10501
- export { ActivityDetailDialog, ActivityRow, BreadcrumbTail, CautionNote, CollapsibleBand, CountedLabel, DismissablePopover, DocPreviewLink, DocRefFace, EmptyState, ForMeEmptyState, Hairline, HintedMenuButton, HoverHint, InstanceSnapshotBody, InvalidDocNotice, LinkChip, LoadingRow, LogEventOnMount, MetaRow, SpinnerSlot, StageFace, StaleLock, TOAST_ID, TOOL_TABS, TabSwitch, TrailingHairline, UnreadableDocsNote, UserAvatar, WORKFLOW_API_VERSION, WORKFLOW_PAGE_TABS, WorkflowAbortDialogOpened, WorkflowAbortDialogSubmitted, WorkflowBoardWorkflowSelected, WorkflowDefinitionDetailViewed, WorkflowInstanceDetailViewed, WorkflowTaskFiltersApplied, WorkflowTitleSeedDrifted, WorkflowToolOpened, activityRowProps, assigneeUserIdsOf, backToTabLabel, committedRowFace, createSubscribers, datesOf, definitionFingerprint, definitionSnapshotOf, describeError, dueDatesOf, findActivity, findActivityNode, formatDate, formatDateTime, formatShortAgo, formatShortDateTime, formatTimeAgo, gdrLocality, instanceBreadcrumb, instanceState, instanceTitle, isActivityAssignedTo, isEvaluationStale, isLiveEntry, isOpenActivityStatus, landingState, mappingIssueDetail, namesNoDeployedDefinition, openActivityGone, openableSchemaType, parseStoredDateValue, readDeployedDefinitions, rowAssignControlState, rowDateControlState, stageTitle, stateByActivity, tabSelectionNavigates, toolRoute, toolTabState, useAssignmentIdentity, useAvatarSize, useBadgeCapTrim, useContainerToken, useDefinition, useDelayedFlag, useLogEventOnMount, useProjectMembers, useSpaceToken, useUserDisplay, useUserDisplays, useWorkflowContext, useWorkflowInstanceEntry, useWorkflowToast, workflowDefaultDocumentNode, workflowPageState, workflowState, workflowStudioPlugin, workflowsView };
11223
+ export { AbortWorkflowDialog, ActivityDetailDialog, ActivityLog, ActivityRow, BreadcrumbTail, CodeChip, CountedLabel, DismissablePopover, DocPreviewLink, DocRefFace, EmptyState, ForMeEmptyState, Hairline, HoverHint, InstanceSnapshotBody, InvalidDocNotice, LinkChip, LoadingRow, LogEventOnMount, MetaRow, NoteBanner, RoleAssignedNotice, SpinnerSlot, StageFace, StaleLock, TOAST_ID, TOOL_TABS, TabSwitch, UnreadableDocsNote, WORKFLOW_API_VERSION, WORKFLOW_PAGE_TABS, WorkflowBoardWorkflowSelected, WorkflowDefinitionDetailViewed, WorkflowInstanceDetailViewed, WorkflowTaskFiltersApplied, WorkflowTitleSeedDrifted, WorkflowToolOpened, activityRowProps, assigneesOf, backToTabLabel, committedRowFace, createSubscribers, datesOf, definitionFingerprint, definitionSnapshotOf, describeError, dueDatesOf, findActivity, findActivityNode, formatDate, formatShortDateTime, formatTimeAgo, gdrLocality, instanceBreadcrumb, instanceState, instanceTitle, isActivityAssignedTo, isEvaluationStale, isLiveEntry, isOpenActivityStatus, isRoleAssignedTo, landingState, mappingIssueDetail, namesNoDeployedDefinition, openActivityGone, openableSchemaType, parseStoredDateValue, readDeployedDefinitions, rowAssignControlState, rowDateControlState, stageTitle, stateByActivity, tabSelectionNavigates, toolRoute, toolTabState, useAssignmentIdentity, useBadgeCapTrim, useContainerToken, useDefinition, useDelayedFlag, useLogEventOnMount, useProjectMembers, useSpaceToken, useUserDisplays, useWorkflowContext, useWorkflowInstanceEntry, useWorkflowToast, workflowDefaultDocumentNode, workflowPageState, workflowState, workflowStudioPlugin, workflowsView };