@sanity/workflow-studio-plugin 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- var jsxRuntime = require("react/jsx-runtime"), Timeline = require("@sanity/icons/Timeline"), ui = require("@sanity/ui"), workflowReact = require("@sanity/workflow-react"), react = require("react"), structure = require("sanity/structure"), workflowEngine = require("@sanity/workflow-engine"), Checkmark = require("@sanity/icons/Checkmark"), Close = require("@sanity/icons/Close"), formatDistanceToNow = require("date-fns/formatDistanceToNow"), workflowComponents = require("@sanity/workflow-components"), csm = require("@sanity/client/csm"), telemetry = require("@sanity/telemetry"), workflowStudio = require("@sanity/workflow-studio"), sanity = require("sanity"), Calendar = require("@sanity/icons/Calendar"), User = require("@sanity/icons/User"), Search = require("@sanity/icons/Search"), ChevronRight = require("@sanity/icons/ChevronRight"), Edit = require("@sanity/icons/Edit"), content = require("@sanity/util/content"), InfoOutline = require("@sanity/icons/InfoOutline"), Document = require("@sanity/icons/Document"), WarningOutline = require("@sanity/icons/WarningOutline"), router = require("sanity/router"), types = require("@sanity/types"), Launch = require("@sanity/icons/Launch"), Empty$1 = require("@sanity/icons/Empty"), ErrorFilled = require("@sanity/icons/ErrorFilled"), RemoveCircle = require("@sanity/icons/RemoveCircle"), Add = require("@sanity/icons/Add"), Ulist = require("@sanity/icons/Ulist"), CheckmarkCircle = require("@sanity/icons/CheckmarkCircle"), Bolt = require("@sanity/icons/Bolt"), ChevronDown = require("@sanity/icons/ChevronDown"), pluralize = require("pluralize-esm"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal"), Clock = require("@sanity/icons/Clock"), Cog = require("@sanity/icons/Cog"), ArrowUp = require("@sanity/icons/ArrowUp"), rxjs = require("rxjs"), operators = require("rxjs/operators"), Transfer = require("@sanity/icons/Transfer");
3
+ var jsxRuntime = require("react/jsx-runtime"), Timeline = require("@sanity/icons/Timeline"), ui = require("@sanity/ui"), workflowReact = require("@sanity/workflow-react"), react = require("react"), structure = require("sanity/structure"), workflowEngine = require("@sanity/workflow-engine"), Checkmark = require("@sanity/icons/Checkmark"), Close = require("@sanity/icons/Close"), formatDistanceToNow = require("date-fns/formatDistanceToNow"), workflowComponents = require("@sanity/workflow-components"), csm = require("@sanity/client/csm"), telemetry = require("@sanity/telemetry"), workflowStudio = require("@sanity/workflow-studio"), sanity = require("sanity"), Calendar = require("@sanity/icons/Calendar"), User = require("@sanity/icons/User"), Search = require("@sanity/icons/Search"), ChevronRight = require("@sanity/icons/ChevronRight"), Edit = require("@sanity/icons/Edit"), content = require("@sanity/util/content"), InfoOutline = require("@sanity/icons/InfoOutline"), Document = require("@sanity/icons/Document"), WarningOutline = require("@sanity/icons/WarningOutline"), router = require("sanity/router"), types = require("@sanity/types"), Launch = require("@sanity/icons/Launch"), ErrorOutline = require("@sanity/icons/ErrorOutline"), Empty$1 = require("@sanity/icons/Empty"), ErrorFilled = require("@sanity/icons/ErrorFilled"), RemoveCircle = require("@sanity/icons/RemoveCircle"), Add = require("@sanity/icons/Add"), Ulist = require("@sanity/icons/Ulist"), CheckmarkCircle = require("@sanity/icons/CheckmarkCircle"), Bolt = require("@sanity/icons/Bolt"), ChevronDown = require("@sanity/icons/ChevronDown"), pluralize = require("pluralize-esm"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal"), Clock = require("@sanity/icons/Clock"), Cog = require("@sanity/icons/Cog"), ArrowUp = require("@sanity/icons/ArrowUp"), rxjs = require("rxjs"), operators = require("rxjs/operators"), Transfer = require("@sanity/icons/Transfer");
4
4
 
5
5
  function _interopDefaultCompat(e) {
6
6
  return e && typeof e == "object" && "default" in e ? e : {
@@ -333,16 +333,25 @@ function parseLocalDate(value) {
333
333
  return parsed2.getFullYear() === year && parsed2.getMonth() === (month ?? NaN) - 1 && parsed2.getDate() === day ? parsed2 : /* @__PURE__ */ new Date(NaN);
334
334
  }
335
335
 
336
+ function storedDateKind(value) {
337
+ return /^\d{4}-\d{2}-\d{2}$/.test(value) ? "date" : "datetime";
338
+ }
339
+
336
340
  function parseStoredDateValue(value) {
337
- if (typeof value != "string") return;
338
- const kind = /^\d{4}-\d{2}-\d{2}$/.test(value) ? "date" : "datetime";
339
- return parseDateFieldValue(value, kind);
341
+ if (typeof value == "string") return parseDateFieldValue(value, storedDateKind(value));
340
342
  }
341
343
 
342
344
  function serializeDateFieldValue(date, kind) {
343
345
  return hasTimeOfDay(kind) ? date.toISOString() : `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
344
346
  }
345
347
 
348
+ function isDueDatePast(value, now) {
349
+ const kind = storedDateKind(value);
350
+ if (!hasTimeOfDay(kind)) return value < serializeDateFieldValue(now, kind);
351
+ const due = parseDateFieldValue(value, kind);
352
+ return due !== void 0 && due.getTime() < now.getTime();
353
+ }
354
+
346
355
  class EffectsIncompleteError extends Error {
347
356
  reason;
348
357
  constructor(reason, options) {
@@ -523,7 +532,7 @@ function isLiveEntry(entry) {
523
532
  const isTodoDone = item => item.status === "done", toggledTodoStatus = item => isTodoDone(item) ? "open" : "done";
524
533
 
525
534
  function isTodoOverdue(item, today = /* @__PURE__ */ new Date) {
526
- return !item.dueDate || isTodoDone(item) ? !1 : item.dueDate < serializeDateFieldValue(today, "date");
535
+ return !item.dueDate || isTodoDone(item) ? !1 : isDueDatePast(item.dueDate, today);
527
536
  }
528
537
 
529
538
  function todoEditKind(patch) {
@@ -722,13 +731,25 @@ function isItemAssignedTo(item, who) {
722
731
  return item.assignee != null && assigneeMatches(item.assignee, who);
723
732
  }
724
733
 
734
+ function matchingAssignees(state, who) {
735
+ 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 ] : []);
736
+ }
737
+
725
738
  function isActivityAssignedTo(args) {
726
- 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);
739
+ return matchingAssignees(args.state, args.who).length > 0;
740
+ }
741
+
742
+ function isRoleAssignedTo(args) {
743
+ return matchingAssignees(args.state, args.who).some(member => member.type === "role");
744
+ }
745
+
746
+ function isItemRoleAssignedTo(item, who) {
747
+ return isItemAssignedTo(item, who) && item.assignee?.type === "role";
727
748
  }
728
749
 
729
750
  function assignedWork(args) {
730
- 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({
731
- state: byActivity.get(t.activity.name) ?? [],
751
+ 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({
752
+ state: stateOf(t),
732
753
  who: who
733
754
  })), items = deriveTodoItems({
734
755
  instance: instance,
@@ -738,7 +759,11 @@ function assignedWork(args) {
738
759
  return {
739
760
  activities: activities,
740
761
  openItems: items.filter(row => !isTodoDone(row.item)),
741
- doneItems: items.filter(row => isTodoDone(row.item))
762
+ doneItems: items.filter(row => isTodoDone(row.item)),
763
+ roleMatched: activities.some(t => isRoleAssignedTo({
764
+ state: stateOf(t),
765
+ who: who
766
+ })) || items.some(row => isItemRoleAssignedTo(row.item, who))
742
767
  };
743
768
  }
744
769
 
@@ -787,7 +812,8 @@ function forMeWorkOf(entries, identity) {
787
812
  return {
788
813
  work: work,
789
814
  count: work.reduce((n, {count: count}) => n + count, 0),
790
- hasRows: work.some(({shown: shown}) => hasAssignedRows(shown))
815
+ hasRows: work.some(({shown: shown}) => hasAssignedRows(shown)),
816
+ hasRoleMatched: work.some(({shown: shown}) => shown.roleMatched)
791
817
  };
792
818
  }
793
819
 
@@ -827,7 +853,7 @@ function concludesHere(actionEval, activityName) {
827
853
  return changesOwnStatus(actionEval.action, activityName) || actionEval.firing?.exitsStage === !0;
828
854
  }
829
855
 
830
- const SEMANTIC_FACE = {
856
+ const DECISION_SEMANTIC_FACE = {
831
857
  "decision.accept": {
832
858
  tone: "positive",
833
859
  icon: Checkmark.CheckmarkIcon
@@ -838,6 +864,15 @@ const SEMANTIC_FACE = {
838
864
  }
839
865
  };
840
866
 
867
+ function isDecisionSemantic(value) {
868
+ return Object.hasOwn(DECISION_SEMANTIC_FACE, value);
869
+ }
870
+
871
+ function decisionSemanticFace(semantics) {
872
+ const semantic = semantics.find(isDecisionSemantic);
873
+ return semantic === void 0 ? void 0 : DECISION_SEMANTIC_FACE[semantic];
874
+ }
875
+
841
876
  function actionButtonFace(args) {
842
877
  const mode = concludesHere(args.actionEval, args.activityName) ? "default" : "ghost";
843
878
  if (actionAdvanceStatus(args.actionEval.action, args.activityName) === "failed") return {
@@ -845,7 +880,7 @@ function actionButtonFace(args) {
845
880
  tone: "critical",
846
881
  icon: void 0
847
882
  };
848
- const [semantic] = args.actionEval.semantics ?? [], face = semantic === void 0 ? void 0 : SEMANTIC_FACE[semantic];
883
+ const face = decisionSemanticFace(args.actionEval.semantics ?? []);
849
884
  return {
850
885
  mode: mode,
851
886
  tone: face?.tone ?? "default",
@@ -1312,21 +1347,32 @@ function bridgedSelfId(users, meId) {
1312
1347
  return users.find(user => user.membership.id === meId)?.profile?.sanityUserId ?? classified.globalId ?? (classified.namespace === "project" ? void 0 : meId);
1313
1348
  }
1314
1349
 
1315
- function useBridgedSelf() {
1350
+ function useSelfState() {
1316
1351
  const me = sanity.useCurrentUser(), {users: users, loading: loading, error: error} = workflowStudio.useStudioProjectUsers();
1317
- if (!me) return;
1318
- const id = bridgedSelfId(users, me.id);
1319
- if (id !== void 0) return {
1320
- id: id,
1321
- roles: me.roles ?? []
1352
+ if (!me) return {
1353
+ kind: "unavailable"
1322
1354
  };
1323
- if (!loading) {
1324
- if (error !== void 0) {
1325
- warnSelfUnavailableOnce(`load:${me.id}`, `workflow: the project member directory failed to load ("${workflowEngine.errorMessage(error)}") — the logged-in user ("${me.id}") is project-scoped, and only that directory maps it to the account-global identity the engine stores, so workflow assignment and authoring controls stay disabled. A mounted view does not retry on its own; the next consumer to ask reloads the directory once its backoff has passed.`);
1326
- return;
1355
+ const id = bridgedSelfId(users, me.id);
1356
+ return id !== void 0 ? {
1357
+ kind: "resolved",
1358
+ self: {
1359
+ id: id,
1360
+ roles: me.roles ?? []
1327
1361
  }
1328
- warnSelfUnavailableOnce(`unresolved:${me.id}`, `workflow: the project member directory cannot resolve the logged-in user ("${me.id}") to an account-global identity — the engine stores account-global user ids only, so workflow assignment and authoring controls stay disabled. Typically this user is not a member of this project.`);
1329
- }
1362
+ } : loading ? {
1363
+ kind: "pending"
1364
+ } : error !== void 0 ? (warnSelfUnavailableOnce(`load:${me.id}`, `workflow: the project member directory failed to load ("${workflowEngine.errorMessage(error)}") — the logged-in user ("${me.id}") is project-scoped, and only that directory maps it to the account-global identity the engine stores, so workflow assignment and authoring controls stay disabled. A mounted view does not retry on its own; the next consumer to ask reloads the directory once its backoff has passed.`),
1365
+ {
1366
+ kind: "unavailable"
1367
+ }) : (warnSelfUnavailableOnce(`unresolved:${me.id}`, `workflow: the project member directory cannot resolve the logged-in user ("${me.id}") to an account-global identity — the engine stores account-global user ids only, so workflow assignment and authoring controls stay disabled. Typically this user is not a member of this project.`),
1368
+ {
1369
+ kind: "unavailable"
1370
+ });
1371
+ }
1372
+
1373
+ function useBridgedSelf() {
1374
+ const state = useSelfState();
1375
+ return state.kind === "resolved" ? state.self : void 0;
1330
1376
  }
1331
1377
 
1332
1378
  function useSelfActor() {
@@ -1335,13 +1381,22 @@ function useSelfActor() {
1335
1381
  }
1336
1382
 
1337
1383
  function useAssignmentIdentity() {
1338
- const self = useBridgedSelf();
1339
- if (self !== void 0) return {
1340
- userId: self.id,
1341
- roles: self.roles.map(r => r.name)
1384
+ const state = useSelfState();
1385
+ if (state.kind !== "resolved") return state;
1386
+ const {id: id, roles: roles} = state.self;
1387
+ return {
1388
+ kind: "resolved",
1389
+ identity: {
1390
+ userId: id,
1391
+ roles: roles.map(r => r.name)
1392
+ }
1342
1393
  };
1343
1394
  }
1344
1395
 
1396
+ function identityOf(state) {
1397
+ return state.kind === "resolved" ? state.identity : void 0;
1398
+ }
1399
+
1345
1400
  const projectMembersByUsers = /* @__PURE__ */ new WeakMap;
1346
1401
 
1347
1402
  function projectMembersFrom(users) {
@@ -1361,9 +1416,10 @@ function projectMembersFrom(users) {
1361
1416
  }
1362
1417
 
1363
1418
  function useProjectMembers() {
1364
- const {users: users, loading: loading, error: error} = workflowStudio.useStudioProjectUsers();
1419
+ const {users: users, roles: roles, loading: loading, error: error} = workflowStudio.useStudioProjectUsers();
1365
1420
  return {
1366
1421
  members: react.useMemo(() => projectMembersFrom(users), [ users ]),
1422
+ roles: roles,
1367
1423
  loading: loading,
1368
1424
  error: error === void 0 ? void 0 : workflowEngine.errorMessage(error)
1369
1425
  };
@@ -1448,6 +1504,8 @@ function buildParams(decls, values) {
1448
1504
  };
1449
1505
  }
1450
1506
 
1507
+ const LOADING_CUE_DELAY_MS = 400;
1508
+
1451
1509
  function useDelayedFlag(active, delayMs) {
1452
1510
  const [held, setHeld] = react.useState(!1);
1453
1511
  return react.useEffect(() => {
@@ -1717,10 +1775,12 @@ function PickedValueRow({children: children, onRequestChange: onRequestChange})
1717
1775
  }
1718
1776
 
1719
1777
  function MemberPicker({selectedIds: selectedIds, onSelect: onSelect, unassignRow: unassignRow = !1}) {
1720
- const state = useProjectMembers();
1778
+ const {error: error, loading: loading, members: members} = useProjectMembers();
1721
1779
  /* @__PURE__ */
1722
1780
  return jsxRuntime.jsx(workflowComponents.MemberPicker, {
1723
- ...state,
1781
+ error: error,
1782
+ loading: loading,
1783
+ members: members,
1724
1784
  onSelect: onSelect,
1725
1785
  selectedIds: selectedIds,
1726
1786
  unassignRow: unassignRow
@@ -2453,15 +2513,14 @@ function Separator() {
2453
2513
  }
2454
2514
 
2455
2515
  function stageTitle(definition, stageName) {
2456
- return definition?.stages.find(stage => stage.name === stageName)?.title ?? stageName;
2457
- }
2458
-
2459
- function findActivityNode(args) {
2460
- return args.definition?.stages.find(s => s.name === args.stageName)?.activities?.find(a => a.name === args.activityName);
2516
+ return workflowEngine.findStageNode({
2517
+ definition: definition,
2518
+ stageName: stageName
2519
+ })?.title ?? stageName;
2461
2520
  }
2462
2521
 
2463
2522
  function findActivity(args) {
2464
- const activity = findActivityNode(args);
2523
+ const activity = workflowEngine.findActivityNode(args);
2465
2524
  return {
2466
2525
  title: activity?.title ?? args.activityName,
2467
2526
  description: activity?.description
@@ -2504,20 +2563,20 @@ function noteRow(args) {
2504
2563
  }
2505
2564
 
2506
2565
  function AssigneeBadge({assignee: assignee}) {
2507
- const {members: members} = useProjectMembers();
2566
+ const vocabulary = useProjectMembers();
2508
2567
  /* @__PURE__ */
2509
2568
  return jsxRuntime.jsx(workflowComponents.AssigneeBadge, {
2510
2569
  assignee: assignee,
2511
- members: members
2570
+ vocabulary: vocabulary
2512
2571
  });
2513
2572
  }
2514
2573
 
2515
2574
  function AssigneeBadges({assignees: assignees}) {
2516
- const {members: members} = useProjectMembers();
2575
+ const vocabulary = useProjectMembers();
2517
2576
  /* @__PURE__ */
2518
2577
  return jsxRuntime.jsx(workflowComponents.AssigneeBadges, {
2519
2578
  assignees: assignees,
2520
- members: members
2579
+ vocabulary: vocabulary
2521
2580
  });
2522
2581
  }
2523
2582
 
@@ -3925,6 +3984,267 @@ function EditableField({field: field, entry: entry, description: description, in
3925
3984
  });
3926
3985
  }
3927
3986
 
3987
+ function useDrainEffects(instanceId) {
3988
+ const {drainEffectsFor: drainEffectsFor} = useWorkflowContext(), toast = useWorkflowToast(), [busy, setBusy] = react.useState(!1);
3989
+ return {
3990
+ busy: busy,
3991
+ run: async () => {
3992
+ setBusy(!0);
3993
+ try {
3994
+ await drainEffectsFor(instanceId), toast.push({
3995
+ id: TOAST_ID.effectsDrain,
3996
+ status: "info",
3997
+ title: "Ran the registered handlers"
3998
+ });
3999
+ } catch (err) {
4000
+ toast.push({
4001
+ id: TOAST_ID.effectsDrain,
4002
+ status: "error",
4003
+ title: "Failed to run pending effects",
4004
+ description: describeError(err)
4005
+ });
4006
+ } finally {
4007
+ setBusy(!1);
4008
+ }
4009
+ }
4010
+ };
4011
+ }
4012
+
4013
+ function isRunnable(verb) {
4014
+ return verb === "drain-effects";
4015
+ }
4016
+
4017
+ function effectLabel(effect) {
4018
+ return effect.title ?? effect.name;
4019
+ }
4020
+
4021
+ const ACTION_LABEL = {
4022
+ "drain-effects": "Run the step again"
4023
+ }, REMEDY_TEXT = {
4024
+ "retry-effect": "The step has to run again before this workflow can continue.",
4025
+ "reset-activity": "Reset the task to try it again, or skip it.",
4026
+ "set-stage": "Move the workflow on to its next stage yourself."
4027
+ };
4028
+
4029
+ function detailOf(cause) {
4030
+ switch (cause.kind) {
4031
+ case "failed-effect":
4032
+ return `The step “${effectLabel(cause.effect)}” failed`;
4033
+
4034
+ case "hung-effect":
4035
+ return `The step “${effectLabel(cause.effect)}” didn’t finish`;
4036
+
4037
+ case "failed-activity":
4038
+ return "This task failed";
4039
+
4040
+ default:
4041
+ return summaryOf(cause);
4042
+ }
4043
+ }
4044
+
4045
+ function summaryOf(cause) {
4046
+ switch (cause.kind) {
4047
+ case "failed-effect":
4048
+ return "An automated step failed";
4049
+
4050
+ case "hung-effect":
4051
+ return "An automated step didn’t finish";
4052
+
4053
+ case "failed-activity":
4054
+ return "A task failed";
4055
+
4056
+ case "no-transition-fires":
4057
+ return "Every task is done, but nothing moves this workflow on";
4058
+
4059
+ case "transition-unevaluable":
4060
+ return "This workflow is waiting on something it can’t read yet, and will move on by itself";
4061
+ }
4062
+ }
4063
+
4064
+ function activityDeclaringEffect(args) {
4065
+ return workflowEngine.findStageNode(args)?.activities?.find(activity => (activity.actions ?? []).some(action => (action.effects ?? []).some(effect => effect.name === args.effectName)))?.name;
4066
+ }
4067
+
4068
+ function faultingActivity(args) {
4069
+ const {cause: cause} = args;
4070
+ if (cause.kind === "failed-activity") return cause.activity;
4071
+ if (cause.kind === "failed-effect" || cause.kind === "hung-effect") return activityDeclaringEffect({
4072
+ definition: args.definition,
4073
+ effectName: cause.effect.name,
4074
+ stageName: args.stageName
4075
+ });
4076
+ }
4077
+
4078
+ function isRecoverable(cause) {
4079
+ return cause.kind === "transition-unevaluable";
4080
+ }
4081
+
4082
+ function diagnosisOf(args) {
4083
+ if (args.evaluation !== void 0) return workflowEngine.diagnoseInstance(workflowEngine.diagnoseInputFromEvaluation(args.evaluation));
4084
+ const cause = workflowEngine.documentStuckCause({
4085
+ instance: args.instance,
4086
+ definition: args.definition
4087
+ });
4088
+ return cause === void 0 ? void 0 : {
4089
+ state: "stuck",
4090
+ cause: cause
4091
+ };
4092
+ }
4093
+
4094
+ function instanceFault(args) {
4095
+ const diagnosis = diagnosisOf(args);
4096
+ if (diagnosis === void 0 || diagnosis.state !== "stuck") return;
4097
+ const verbs = workflowEngine.remediationsFor(diagnosis).map(remediation2 => remediation2.verb);
4098
+ return {
4099
+ activity: faultingActivity({
4100
+ cause: diagnosis.cause,
4101
+ definition: args.definition,
4102
+ stageName: args.instance.currentStage
4103
+ }),
4104
+ detail: detailOf(diagnosis.cause),
4105
+ summary: summaryOf(diagnosis.cause),
4106
+ remedies: verbs.flatMap(verb => REMEDY_TEXT[verb] ?? []),
4107
+ actions: verbs.flatMap(verb => isRunnable(verb) ? [ {
4108
+ verb: verb,
4109
+ label: ACTION_LABEL[verb]
4110
+ } ] : []),
4111
+ recoverable: isRecoverable(diagnosis.cause)
4112
+ };
4113
+ }
4114
+
4115
+ function looksLikeDefinition(value) {
4116
+ return typeof value == "object" && value !== null && Array.isArray(value.stages);
4117
+ }
4118
+
4119
+ const parsed = /* @__PURE__ */ new WeakMap;
4120
+
4121
+ function definitionSnapshotOf(instance) {
4122
+ if (parsed.has(instance)) return parsed.get(instance);
4123
+ let definition;
4124
+ try {
4125
+ const candidate = workflowEngine.parseDefinitionSnapshot(instance);
4126
+ if (!looksLikeDefinition(candidate)) throw new Error(`definitionSnapshot on "${instance._id}" parsed to a non-definition shape`);
4127
+ definition = candidate;
4128
+ } catch (err) {
4129
+ console.error("[workflow-studio-plugin] unreadable definitionSnapshot:", err), definition = void 0;
4130
+ }
4131
+ return parsed.set(instance, definition), definition;
4132
+ }
4133
+
4134
+ function useDefinition(entry) {
4135
+ return react.useMemo(() => {
4136
+ if (entry) return entry.evaluation ? entry.evaluation.definition : definitionSnapshotOf(entry.instance);
4137
+ }, [ entry ]);
4138
+ }
4139
+
4140
+ function AbortedBadge({instance: instance}) {
4141
+ return workflowEngine.terminalState(instance) !== "aborted" ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
4142
+ fontSize: 1,
4143
+ tone: "critical",
4144
+ children: "Aborted"
4145
+ });
4146
+ }
4147
+
4148
+ function OlderDefinitionBadge({instance: instance}) {
4149
+ const {latestDefinitionVersions: latestDefinitionVersions} = useWorkflowContext(), latest = latestDefinitionVersions.get(instance.definition);
4150
+ return latest === void 0 || latest <= instance.pinnedVersion ? null : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4151
+ description: "Running workflows keep the version they started on",
4152
+ text: `Started on v${instance.pinnedVersion}, latest is now v${latest}`,
4153
+ textWeight: "medium",
4154
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
4155
+ fontSize: 1,
4156
+ tone: "caution",
4157
+ children: "Older version"
4158
+ })
4159
+ });
4160
+ }
4161
+
4162
+ function useInstanceFault(entry) {
4163
+ return instanceFault({
4164
+ definition: useDefinition(entry),
4165
+ evaluation: entry.evaluation,
4166
+ instance: entry.instance
4167
+ });
4168
+ }
4169
+
4170
+ function InstanceFaultNote({entry: entry, scope: scope}) {
4171
+ const fault = useInstanceFault(entry);
4172
+ if (fault === void 0 || scope.kind === "task" && fault.activity !== scope.activityName) return null;
4173
+ const Icon = fault.recoverable ? WarningOutline.WarningOutlineIcon : ErrorOutline.ErrorOutlineIcon;
4174
+ /* @__PURE__ */
4175
+ return jsxRuntime.jsx(ui.Card, {
4176
+ padding: 3,
4177
+ radius: 3,
4178
+ tone: fault.recoverable ? "caution" : "critical",
4179
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
4180
+ align: "flex-start",
4181
+ gap: 3,
4182
+ children: [
4183
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4184
+ size: 1,
4185
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, {})
4186
+ }),
4187
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
4188
+ flex: 1,
4189
+ gap: 3,
4190
+ children: [
4191
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4192
+ size: 1,
4193
+ weight: "medium",
4194
+ children: scope.kind === "run" ? fault.summary : fault.detail
4195
+ }), scope.kind === "run" ? /* @__PURE__ */ jsxRuntime.jsx(RunControl, {
4196
+ activity: fault.activity,
4197
+ onOpenActivity: scope.onOpenActivity
4198
+ }) : /* @__PURE__ */ jsxRuntime.jsx(TaskControls, {
4199
+ actions: fault.actions,
4200
+ instanceId: entry.instance._id,
4201
+ remedies: fault.remedies
4202
+ }) ]
4203
+ }) ]
4204
+ })
4205
+ });
4206
+ }
4207
+
4208
+ function RunControl({activity: activity, onOpenActivity: onOpenActivity}) {
4209
+ return activity === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4210
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4211
+ fontSize: 1,
4212
+ mode: "ghost",
4213
+ onClick: () => onOpenActivity(activity),
4214
+ padding: 2,
4215
+ text: "View task"
4216
+ })
4217
+ });
4218
+ }
4219
+
4220
+ const RUN_VERB = {
4221
+ "drain-effects": drain => drain.run()
4222
+ };
4223
+
4224
+ function TaskControls({actions: actions, instanceId: instanceId, remedies: remedies}) {
4225
+ const drain = useDrainEffects(instanceId);
4226
+ /* @__PURE__ */
4227
+ return jsxRuntime.jsxs(jsxRuntime.Fragment, {
4228
+ children: [ remedies.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4229
+ muted: !0,
4230
+ size: 1,
4231
+ children: remedies.join(" ")
4232
+ }), actions.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4233
+ gap: 2,
4234
+ children: actions.map(action => /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4235
+ disabled: drain.busy,
4236
+ fontSize: 1,
4237
+ mode: "ghost",
4238
+ onClick: () => {
4239
+ RUN_VERB[action.verb](drain);
4240
+ },
4241
+ padding: 2,
4242
+ text: action.label
4243
+ }, action.verb))
4244
+ }) ]
4245
+ });
4246
+ }
4247
+
3928
4248
  function MetaRow({children: children, fillHeight: fillHeight, label: label}) {
3929
4249
  /* @__PURE__ */
3930
4250
  return jsxRuntime.jsxs(ui.Flex, {
@@ -4053,7 +4373,7 @@ function useBadgeCapTrim() {
4053
4373
  }
4054
4374
 
4055
4375
  function SpinnerSlot({busy: busy}) {
4056
- const size = useTextIconSize(), trim = useCapTrimFor(size);
4376
+ const size = useTextIconSize(), trim = useCapTrimFor(size), spinning = useDelayedFlag(busy, LOADING_CUE_DELAY_MS);
4057
4377
  /* @__PURE__ */
4058
4378
  return jsxRuntime.jsx(ui.Flex, {
4059
4379
  align: "center",
@@ -4065,7 +4385,7 @@ function SpinnerSlot({busy: busy}) {
4065
4385
  marginTop: trim,
4066
4386
  width: size
4067
4387
  },
4068
- children: busy ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
4388
+ children: spinning ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
4069
4389
  muted: !0,
4070
4390
  size: 1
4071
4391
  }) : null
@@ -4073,6 +4393,7 @@ function SpinnerSlot({busy: busy}) {
4073
4393
  }
4074
4394
 
4075
4395
  function LoadingRow({label: label, padding: padding}) {
4396
+ const announced = useDelayedFlag(!0, LOADING_CUE_DELAY_MS);
4076
4397
  /* @__PURE__ */
4077
4398
  return jsxRuntime.jsxs(ui.Flex, {
4078
4399
  align: "center",
@@ -4083,12 +4404,11 @@ function LoadingRow({label: label, padding: padding}) {
4083
4404
  children: [
4084
4405
  /* @__PURE__ */ jsxRuntime.jsx(SpinnerSlot, {
4085
4406
  busy: !0
4086
- }),
4087
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4407
+ }), announced ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4088
4408
  muted: !0,
4089
4409
  size: 1,
4090
4410
  children: label
4091
- }) ]
4411
+ }) : null ]
4092
4412
  });
4093
4413
  }
4094
4414
 
@@ -4144,7 +4464,7 @@ function AvatarButton({avatar: avatar, onClick: onClick, onMouseDown: onMouseDow
4144
4464
  }
4145
4465
 
4146
4466
  function RowAssignees({assignees: assignees, hint: hint}) {
4147
- const ids = assignees.flatMap(item => item.type === "user" ? [ item.id ] : []), {members: members} = useProjectMembers(), roles = assignees.flatMap(item => item.type === "role" ? [ workflowComponents.memberRoleFor(members, item.role) ] : []), displays = useUserDisplays(ids);
4467
+ const ids = assignees.flatMap(item => item.type === "user" ? [ item.id ] : []), vocabulary = useProjectMembers(), roles = assignees.flatMap(item => item.type === "role" ? [ workflowComponents.memberRoleFor(vocabulary, item.role) ] : []), displays = useUserDisplays(ids);
4148
4468
  if (ids.length === 0 && roles.length === 0) return null;
4149
4469
  const bothKinds = roles.length > 0 && displays.length > 0;
4150
4470
  /* @__PURE__ */
@@ -4355,49 +4675,40 @@ function ForMeEmptyState() {
4355
4675
  });
4356
4676
  }
4357
4677
 
4358
- function looksLikeDefinition(value) {
4359
- return typeof value == "object" && value !== null && Array.isArray(value.stages);
4360
- }
4678
+ const OVERDUE_HINT = "Now overdue";
4361
4679
 
4362
- const parsed = /* @__PURE__ */ new WeakMap;
4363
-
4364
- function definitionSnapshotOf(instance) {
4365
- if (parsed.has(instance)) return parsed.get(instance);
4366
- let definition;
4367
- try {
4368
- const candidate = workflowEngine.parseDefinitionSnapshot(instance);
4369
- if (!looksLikeDefinition(candidate)) throw new Error(`definitionSnapshot on "${instance._id}" parsed to a non-definition shape`);
4370
- definition = candidate;
4371
- } catch (err) {
4372
- console.error("[workflow-studio-plugin] unreadable definitionSnapshot:", err), definition = void 0;
4373
- }
4374
- return parsed.set(instance, definition), definition;
4375
- }
4376
-
4377
- function useDefinition(entry) {
4378
- return react.useMemo(() => {
4379
- if (entry) return entry.evaluation ? entry.evaluation.definition : definitionSnapshotOf(entry.instance);
4380
- }, [ entry ]);
4680
+ function hintFace(args) {
4681
+ return args.overdue ? args.reason === void 0 ? {
4682
+ text: OVERDUE_HINT
4683
+ } : {
4684
+ text: OVERDUE_HINT,
4685
+ description: args.reason
4686
+ } : args.reason === void 0 ? void 0 : {
4687
+ text: args.reason
4688
+ };
4381
4689
  }
4382
4690
 
4383
- function AbortedBadge({instance: instance}) {
4384
- return workflowEngine.terminalState(instance) !== "aborted" ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
4385
- fontSize: 1,
4386
- tone: "critical",
4387
- children: "Aborted"
4691
+ function DueDate({dueDate: dueDate, hintDisabled: hintDisabled, overdue: overdue, reason: reason}) {
4692
+ const face = hintFace({
4693
+ overdue: overdue,
4694
+ reason: reason
4695
+ }), text = overdue ?
4696
+ /* @__PURE__ */ jsxRuntime.jsx(sanity.TextWithTone, {
4697
+ size: 1,
4698
+ tone: "caution",
4699
+ children: formatDate(dueDate)
4700
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4701
+ muted: !0,
4702
+ size: 1,
4703
+ children: formatDate(dueDate)
4388
4704
  });
4389
- }
4390
-
4391
- function OlderDefinitionBadge({instance: instance}) {
4392
- const {latestDefinitionVersions: latestDefinitionVersions} = useWorkflowContext(), latest = latestDefinitionVersions.get(instance.definition);
4393
- return latest === void 0 || latest <= instance.pinnedVersion ? null : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4394
- description: "Running workflows keep the version they started on",
4395
- text: `Started on v${instance.pinnedVersion}, latest is now v${latest}`,
4396
- textWeight: "medium",
4397
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
4398
- fontSize: 1,
4399
- tone: "caution",
4400
- children: "Older version"
4705
+ return face === void 0 ? text :
4706
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4707
+ align: "center",
4708
+ children: /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4709
+ ...face,
4710
+ disabled: hintDisabled === !0,
4711
+ children: text
4401
4712
  })
4402
4713
  });
4403
4714
  }
@@ -4437,18 +4748,6 @@ function CheckboxSlot({children: children}) {
4437
4748
  });
4438
4749
  }
4439
4750
 
4440
- function DueDateText({dueDate: dueDate, overdue: overdue}) {
4441
- return overdue ? /* @__PURE__ */ jsxRuntime.jsx(sanity.TextWithTone, {
4442
- size: 1,
4443
- tone: "caution",
4444
- children: formatDate(dueDate)
4445
- }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4446
- muted: !0,
4447
- size: 1,
4448
- children: formatDate(dueDate)
4449
- });
4450
- }
4451
-
4452
4751
  function DueDateEditor({dueDate: dueDate, onDone: onDone, onPatch: onPatch}) {
4453
4752
  /* @__PURE__ */
4454
4753
  return jsxRuntime.jsx(workflowComponents.ClearableDatePicker, {
@@ -4468,8 +4767,9 @@ function DueDateEditor({dueDate: dueDate, onDone: onDone, onPatch: onPatch}) {
4468
4767
  }
4469
4768
 
4470
4769
  function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4471
- const [open, setOpen] = react.useState(!1), overdue = isTodoOverdue(item), display = item.dueDate ? /* @__PURE__ */ jsxRuntime.jsx(DueDateText, {
4770
+ const [open, setOpen] = react.useState(!1), overdue = isTodoOverdue(item), display = item.dueDate ? /* @__PURE__ */ jsxRuntime.jsx(DueDate, {
4472
4771
  dueDate: item.dueDate,
4772
+ hintDisabled: open,
4473
4773
  overdue: overdue
4474
4774
  }) : null;
4475
4775
  return editable ? /* @__PURE__ */ jsxRuntime.jsx(DismissablePopover, {
@@ -4480,18 +4780,14 @@ function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4480
4780
  }),
4481
4781
  onDismiss: () => setOpen(!1),
4482
4782
  open: open,
4483
- children: display ? /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4484
- disabled: !overdue || open,
4485
- text: "Now overdue",
4486
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4487
- "aria-label": "Change due date",
4488
- fontSize: 1,
4489
- mode: "bleed",
4490
- onClick: () => setOpen(v => !v),
4491
- padding: 2,
4492
- tone: overdue ? "caution" : "default",
4493
- children: display
4494
- })
4783
+ children: display ? /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4784
+ "aria-label": "Change due date",
4785
+ fontSize: 1,
4786
+ mode: "bleed",
4787
+ onClick: () => setOpen(v => !v),
4788
+ padding: 2,
4789
+ tone: overdue ? "caution" : "default",
4790
+ children: display
4495
4791
  }) : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4496
4792
  disabled: open,
4497
4793
  text: "Set due date",
@@ -4505,9 +4801,6 @@ function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4505
4801
  radius: 2
4506
4802
  })
4507
4803
  })
4508
- }) : display && overdue ? /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4509
- text: "Now overdue",
4510
- children: display
4511
4804
  }) : display;
4512
4805
  }
4513
4806
 
@@ -5160,9 +5453,15 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
5160
5453
  const detail = deriveActivityDetail({
5161
5454
  entry: entry,
5162
5455
  activityName: activityName
5163
- });
5456
+ }), faultingActivity2 = useInstanceFault(entry)?.activity;
5164
5457
  if (!detail) return null;
5165
- const instanceId = entry.instance._id, notice = blockedNotice(detail), hasBody = detail.state.length > 0 || notice !== void 0;
5458
+ const instanceId = entry.instance._id, notice = blockedNotice(detail), fault = /* @__PURE__ */ jsxRuntime.jsx(InstanceFaultNote, {
5459
+ entry: entry,
5460
+ scope: {
5461
+ kind: "task",
5462
+ activityName: activityName
5463
+ }
5464
+ }), faulted = faultingActivity2 === activityName, hasBody = detail.state.length > 0 || notice !== void 0 || faulted;
5166
5465
  /* @__PURE__ */
5167
5466
  return jsxRuntime.jsxs(ui.Dialog, {
5168
5467
  footer: detail.presentation.terminalActions.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(TerminalFooter, {
@@ -5203,7 +5502,7 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
5203
5502
  padding: 4,
5204
5503
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
5205
5504
  gap: 5,
5206
- children: [ detail.state.map(field => /* @__PURE__ */ jsxRuntime.jsx(FieldRow, {
5505
+ children: [ fault, detail.state.map(field => /* @__PURE__ */ jsxRuntime.jsx(FieldRow, {
5207
5506
  activityName: activityName,
5208
5507
  detail: detail,
5209
5508
  entry: entry,
@@ -5384,6 +5683,18 @@ function CountedLabel({count: count, label: label}) {
5384
5683
  });
5385
5684
  }
5386
5685
 
5686
+ function CountedHeading({count: count, title: title}) {
5687
+ /* @__PURE__ */
5688
+ return jsxRuntime.jsx(CountedLabel, {
5689
+ count: count,
5690
+ label: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5691
+ size: 1,
5692
+ weight: "semibold",
5693
+ children: title
5694
+ })
5695
+ });
5696
+ }
5697
+
5387
5698
  function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5388
5699
  /* @__PURE__ */
5389
5700
  return jsxRuntime.jsxs(ui.Flex, {
@@ -5392,13 +5703,9 @@ function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5392
5703
  paddingLeft: 2,
5393
5704
  paddingY: end === void 0 ? 3 : 1,
5394
5705
  children: [
5395
- /* @__PURE__ */ jsxRuntime.jsx(CountedLabel, {
5706
+ /* @__PURE__ */ jsxRuntime.jsx(CountedHeading, {
5396
5707
  count: count,
5397
- label: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5398
- size: 1,
5399
- weight: "semibold",
5400
- children: title
5401
- })
5708
+ title: title
5402
5709
  }), busy === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(SpinnerSlot, {
5403
5710
  busy: busy
5404
5711
  }), end === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
@@ -5410,21 +5717,81 @@ function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5410
5717
  });
5411
5718
  }
5412
5719
 
5720
+ const EMPTY_METRIC_OPACITY = .5;
5721
+
5722
+ function MetricText({children: children, tone: tone}) {
5723
+ return tone === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5724
+ muted: !0,
5725
+ size: 1,
5726
+ children: children
5727
+ }) : /* @__PURE__ */ jsxRuntime.jsx(sanity.TextWithTone, {
5728
+ size: 1,
5729
+ tone: tone,
5730
+ children: children
5731
+ });
5732
+ }
5733
+
5734
+ function MetricPair({Icon: Icon, empty: empty, face: face, tone: tone, value: value}) {
5735
+ /* @__PURE__ */
5736
+ return jsxRuntime.jsx(HoverHint, {
5737
+ ...face,
5738
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
5739
+ align: "center",
5740
+ gap: 2,
5741
+ style: empty ? {
5742
+ opacity: EMPTY_METRIC_OPACITY
5743
+ } : void 0,
5744
+ children: [
5745
+ /* @__PURE__ */ jsxRuntime.jsx(MetricText, {
5746
+ tone: tone,
5747
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, {
5748
+ style: {
5749
+ color: "inherit"
5750
+ }
5751
+ })
5752
+ }),
5753
+ /* @__PURE__ */ jsxRuntime.jsx(MetricText, {
5754
+ tone: tone,
5755
+ children: value
5756
+ }) ]
5757
+ })
5758
+ });
5759
+ }
5760
+
5761
+ function AlertGlyph({Icon: Icon, hint: hint, tone: tone}) {
5762
+ /* @__PURE__ */
5763
+ return jsxRuntime.jsx(HoverHint, {
5764
+ text: hint,
5765
+ children: /* @__PURE__ */ jsxRuntime.jsx(sanity.TextWithTone, {
5766
+ size: 1,
5767
+ tone: tone,
5768
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, {
5769
+ style: {
5770
+ color: "inherit"
5771
+ }
5772
+ })
5773
+ })
5774
+ });
5775
+ }
5776
+
5413
5777
  function ActivityDateControl({instanceId: instanceId, state: state, surface: surface, dueDates: dueDates}) {
5414
5778
  const editField = useEditField(surface), {save: save} = useSaveField({
5415
5779
  instanceId: instanceId
5416
5780
  }), [open, setOpen] = react.useState(!1), raw = dateControlValue({
5417
5781
  state: state,
5418
5782
  dueDates: dueDates
5419
- }), display = raw === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5420
- muted: !0,
5421
- size: 1,
5422
- children: formatDate(raw)
5783
+ }), overdue = raw !== void 0 && isDueDatePast(raw, /* @__PURE__ */ new Date), dueDate = reason => raw === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(DueDate, {
5784
+ dueDate: raw,
5785
+ hintDisabled: open,
5786
+ overdue: overdue,
5787
+ ...reason === void 0 ? {} : {
5788
+ reason: reason
5789
+ }
5423
5790
  });
5424
- if (state.kind === "none") return display;
5425
- if (state.kind === "closed") return display === null ? null : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
5426
- text: state.reason,
5427
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
5791
+ if (state.kind === "none") return dueDate();
5792
+ if (state.kind === "closed") {
5793
+ const display = dueDate(state.reason);
5794
+ return display === null ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
5428
5795
  "aria-label": "Date",
5429
5796
  as: "span",
5430
5797
  fontSize: 1,
@@ -5433,8 +5800,8 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5433
5800
  onMouseDown: stopRowMouseDown,
5434
5801
  padding: 2,
5435
5802
  children: display
5436
- })
5437
- });
5803
+ });
5804
+ }
5438
5805
  const kind = dateControlKind(state), commit = (mode, value) => {
5439
5806
  state.kind === "editable" && save(() => editField({
5440
5807
  instanceId: instanceId,
@@ -5470,7 +5837,7 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5470
5837
  }),
5471
5838
  onDismiss: () => setOpen(!1),
5472
5839
  open: open,
5473
- children: display ? /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
5840
+ children: raw !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
5474
5841
  "aria-label": "Change date",
5475
5842
  as: "span",
5476
5843
  fontSize: 1,
@@ -5478,7 +5845,7 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5478
5845
  onClick: handleClick,
5479
5846
  onMouseDown: stopRowMouseDown,
5480
5847
  padding: 2,
5481
- children: display
5848
+ children: dueDate()
5482
5849
  }) : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
5483
5850
  disabled: open,
5484
5851
  text: "Set date",
@@ -5568,7 +5935,7 @@ function TitleTag({hint: hint, tone: tone, children: children}) {
5568
5935
  });
5569
5936
  }
5570
5937
 
5571
- function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees, assignState: assignState, dateState: dateState, dueDates: dueDates, instanceId: instanceId, onOpen: onOpen, surface: surface, terminalActions: terminalActions = []}) {
5938
+ function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees, assignState: assignState, dateState: dateState, dueDates: dueDates, faultHint: faultHint, instanceId: instanceId, onOpen: onOpen, surface: surface, terminalActions: terminalActions = []}) {
5572
5939
  const settled = workflowEngine.isTerminalActivityStatus(face.status), dimmed = settledDim(settled);
5573
5940
  /* @__PURE__ */
5574
5941
  return jsxRuntime.jsxs(WorkRow, {
@@ -5601,6 +5968,10 @@ function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees,
5601
5968
  /* @__PURE__ */ jsxRuntime.jsx(ActivityRowTitle, {
5602
5969
  dimmed: dimmed,
5603
5970
  children: face.title
5971
+ }), faultHint === void 0 || face.status === "failed" ? null : /* @__PURE__ */ jsxRuntime.jsx(AlertGlyph, {
5972
+ Icon: ErrorOutline.ErrorOutlineIcon,
5973
+ hint: faultHint,
5974
+ tone: "critical"
5604
5975
  }), face.automated ? /* @__PURE__ */ jsxRuntime.jsx(TitleTag, {
5605
5976
  hint: "Completes on its own — no user action is needed",
5606
5977
  children: "Automated"
@@ -5616,7 +5987,7 @@ function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees,
5616
5987
  }
5617
5988
 
5618
5989
  function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity, surface: surface}) {
5619
- const activities = (entry.evaluation?.currentStage.activities ?? []).filter(t => !t.scopedOut), activityState = stateByActivity(entry.instance);
5990
+ const activities = (entry.evaluation?.currentStage.activities ?? []).filter(t => !t.scopedOut), activityState = stateByActivity(entry.instance), fault = useInstanceFault(entry);
5620
5991
  return activities.length === 0 ?
5621
5992
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
5622
5993
  paddingX: 1,
@@ -5634,6 +6005,9 @@ function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity, surface:
5634
6005
  activityState: activityState,
5635
6006
  editableFields: entry.evaluation?.editableFields
5636
6007
  }),
6008
+ ...fault?.activity === t.activity.name ? {
6009
+ faultHint: fault.summary
6010
+ } : {},
5637
6011
  instanceId: entry.instance._id,
5638
6012
  onOpen: () => onOpenActivity(t.activity.name),
5639
6013
  surface: surface
@@ -5780,7 +6154,10 @@ function progressFillPercent(face) {
5780
6154
  }
5781
6155
 
5782
6156
  function deriveFieldPills(args) {
5783
- const {scope: scope, definition: definition, instance: instance, committed: committed, evaluation: evaluation} = args, declared = scope === "workflow" ? definition?.fields ?? [] : definition?.stages.find(s => s.name === instance.currentStage)?.fields ?? [], entries = scope === "workflow" ? instance.fields : workflowEngine.findOpenStageEntry(instance)?.fields ?? [], entriesByName = new Map(entries.map(entry => [ entry.name, entry ])), editables = isEvaluationCurrent({
6157
+ const {scope: scope, definition: definition, instance: instance, committed: committed, evaluation: evaluation} = args, declared = scope === "workflow" ? definition?.fields ?? [] : workflowEngine.findStageNode({
6158
+ definition: definition,
6159
+ stageName: instance.currentStage
6160
+ })?.fields ?? [], entries = scope === "workflow" ? instance.fields : workflowEngine.findOpenStageEntry(instance)?.fields ?? [], entriesByName = new Map(entries.map(entry => [ entry.name, entry ])), editables = isEvaluationCurrent({
5784
6161
  committed: committed,
5785
6162
  evaluation: evaluation
5786
6163
  }) ? editablesByName(evaluation, scope) : /* @__PURE__ */ new Map, declaredNames = new Set(declared.map(d => d.name));
@@ -6014,7 +6391,7 @@ function AvatarCapAligned({children: children}) {
6014
6391
  }
6015
6392
 
6016
6393
  function AssigneesFace({assignees: assignees}) {
6017
- const {members: members} = useProjectMembers(), userIds = assignees.flatMap(a => a.type === "user" ? [ a.id ] : []), roles = assignees.flatMap(a => a.type === "role" ? [ workflowComponents.roleLabelFor(members, a.role) ] : []);
6394
+ const vocabulary = useProjectMembers(), userIds = assignees.flatMap(a => a.type === "user" ? [ a.id ] : []), roles = assignees.flatMap(a => a.type === "role" ? [ workflowComponents.roleLabelFor(vocabulary, a.role) ] : []);
6018
6395
  /* @__PURE__ */
6019
6396
  return jsxRuntime.jsxs(jsxRuntime.Fragment, {
6020
6397
  children: [ userIds.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(AvatarCapAligned, {
@@ -6970,6 +7347,27 @@ function HintedMenuButton({hint: hint, ...menuButtonProps}) {
6970
7347
  });
6971
7348
  }
6972
7349
 
7350
+ function InlineEmphasis({children: children}) {
7351
+ const {font: font} = ui.useTheme_v2();
7352
+ /* @__PURE__ */
7353
+ return jsxRuntime.jsx("span", {
7354
+ style: {
7355
+ fontWeight: font.text.weights.medium
7356
+ },
7357
+ children: children
7358
+ });
7359
+ }
7360
+
7361
+ const MINUTE_MS = 6e4;
7362
+
7363
+ function useMinuteClock() {
7364
+ const [now, setNow] = react.useState(() => /* @__PURE__ */ new Date);
7365
+ return react.useEffect(() => {
7366
+ const timer = setInterval(() => setNow(/* @__PURE__ */ new Date), MINUTE_MS);
7367
+ return () => clearInterval(timer);
7368
+ }, []), now;
7369
+ }
7370
+
6973
7371
  function historyWindow(args) {
6974
7372
  const {entries: entries, expanded: expanded} = args;
6975
7373
  return {
@@ -6979,28 +7377,10 @@ function historyWindow(args) {
6979
7377
  }
6980
7378
 
6981
7379
  function PendingEffectRows({instance: instance, now: now}) {
6982
- const {drainEffectsFor: drainEffectsFor, completeEffectFor: completeEffectFor, effectHandlers: effectHandlers} = useWorkflowContext(), toast = useWorkflowToast(), [busy, setBusy] = react.useState(!1), pending = instance.pendingEffects ?? [];
7380
+ const {completeEffectFor: completeEffectFor, effectHandlers: effectHandlers} = useWorkflowContext(), toast = useWorkflowToast(), drain = useDrainEffects(instance._id), [resolving, setResolving] = react.useState(!1), pending = instance.pendingEffects ?? [];
6983
7381
  if (pending.length === 0) return null;
6984
- const runRegistered = async () => {
6985
- setBusy(!0);
6986
- try {
6987
- await drainEffectsFor(instance._id), toast.push({
6988
- id: TOAST_ID.effectsDrain,
6989
- status: "info",
6990
- title: "Ran the registered handlers"
6991
- });
6992
- } catch (err) {
6993
- toast.push({
6994
- id: TOAST_ID.effectsDrain,
6995
- status: "error",
6996
- title: "Failed to run pending effects",
6997
- description: describeError(err)
6998
- });
6999
- } finally {
7000
- setBusy(!1);
7001
- }
7002
- }, resolve = async (effectKey, status) => {
7003
- setBusy(!0);
7382
+ const resolve = async (effectKey, status) => {
7383
+ setResolving(!0);
7004
7384
  try {
7005
7385
  await completeEffectFor(instance._id, {
7006
7386
  effectKey: effectKey,
@@ -7019,18 +7399,20 @@ function PendingEffectRows({instance: instance, now: now}) {
7019
7399
  description: describeError(err)
7020
7400
  });
7021
7401
  } finally {
7022
- setBusy(!1);
7402
+ setResolving(!1);
7023
7403
  }
7024
7404
  };
7025
7405
  /* @__PURE__ */
7026
7406
  return jsxRuntime.jsx(jsxRuntime.Fragment, {
7027
7407
  children: pending.map(fx => /* @__PURE__ */ jsxRuntime.jsx(PendingEffectRow, {
7028
- busy: busy,
7408
+ busy: drain.busy || resolving,
7029
7409
  effect: fx,
7030
7410
  instanceId: instance._id,
7031
7411
  now: now,
7032
7412
  onResolve: status => resolve(fx._key, status),
7033
- onRunHandlers: runRegistered,
7413
+ onRunHandlers: () => {
7414
+ drain.run();
7415
+ },
7034
7416
  registered: fx.name in effectHandlers
7035
7417
  }, fx._key))
7036
7418
  });
@@ -7114,12 +7496,7 @@ function PendingEffectRow({busy: busy, effect: effect, instanceId: instanceId, n
7114
7496
  }
7115
7497
 
7116
7498
  function ActivityLog({instance: instance, definition: definition, windowed: windowed = !0}) {
7117
- const titles = react.useMemo(() => makeTitleResolver(definition), [ definition ]), entries = react.useMemo(() => instance.history.toReversed(), [ instance.history ]), [now, setNow] = react.useState(() => /* @__PURE__ */ new Date), [expanded, setExpanded] = react.useState(!1);
7118
- react.useEffect(() => {
7119
- const timer = setInterval(() => setNow(/* @__PURE__ */ new Date), 6e4);
7120
- return () => clearInterval(timer);
7121
- }, []);
7122
- const history = historyWindow({
7499
+ const titles = react.useMemo(() => makeTitleResolver(definition), [ definition ]), entries = react.useMemo(() => instance.history.toReversed(), [ instance.history ]), now = useMinuteClock(), [expanded, setExpanded] = react.useState(!1), history = historyWindow({
7123
7500
  entries: entries,
7124
7501
  expanded: expanded || !windowed
7125
7502
  });
@@ -7222,12 +7599,9 @@ function HistoryRow({entry: entry, now: now, titles: titles}) {
7222
7599
  }
7223
7600
 
7224
7601
  function ActorNameSpan({id: id}) {
7225
- const {name: name} = useUserDisplay(id), {font: font} = ui.useTheme_v2();
7602
+ const {name: name} = useUserDisplay(id);
7226
7603
  /* @__PURE__ */
7227
- return jsxRuntime.jsx("span", {
7228
- style: {
7229
- fontWeight: font.text.weights.medium
7230
- },
7604
+ return jsxRuntime.jsx(InlineEmphasis, {
7231
7605
  children: name
7232
7606
  });
7233
7607
  }
@@ -7635,7 +8009,6 @@ function StageOverviewCard({overview: overview}) {
7635
8009
  }
7636
8010
 
7637
8011
  function AdvanceCard({advance: advance}) {
7638
- const {font: font} = ui.useTheme_v2();
7639
8012
  /* @__PURE__ */
7640
8013
  return jsxRuntime.jsx(ui.Card, {
7641
8014
  padding: 3,
@@ -7647,10 +8020,7 @@ function AdvanceCard({advance: advance}) {
7647
8020
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
7648
8021
  size: 1,
7649
8022
  children: [ "Advances to ",
7650
- /* @__PURE__ */ jsxRuntime.jsx("span", {
7651
- style: {
7652
- fontWeight: font.text.weights.medium
7653
- },
8023
+ /* @__PURE__ */ jsxRuntime.jsx(InlineEmphasis, {
7654
8024
  children: advance.toTitle
7655
8025
  }), ADVANCE_TAILS[advance.kind] ]
7656
8026
  }), advance.kind !== "conditions" ? null : advance.lines.map((line, index) =>
@@ -7774,18 +8144,23 @@ function StageSection({entry: entry, onOpenActivity: onOpenActivity, surface: su
7774
8144
  });
7775
8145
  }
7776
8146
 
7777
- function CautionNote({action: action, label: label}) {
8147
+ const BANNER_PADDING = 3, ICONS = {
8148
+ caution: /* @__PURE__ */ jsxRuntime.jsx(WarningOutline.WarningOutlineIcon, {}),
8149
+ critical: /* @__PURE__ */ jsxRuntime.jsx(ErrorOutline.ErrorOutlineIcon, {})
8150
+ };
8151
+
8152
+ function NoteBanner({action: action, label: label, tone: tone = "caution"}) {
7778
8153
  /* @__PURE__ */
7779
8154
  return jsxRuntime.jsxs(ui.Card, {
7780
8155
  paddingLeft: 3,
7781
8156
  paddingRight: 2,
7782
- paddingY: 3,
8157
+ paddingY: BANNER_PADDING,
7783
8158
  radius: 3,
7784
8159
  style: {
7785
8160
  alignItems: "center",
7786
8161
  display: "flex"
7787
8162
  },
7788
- tone: "caution",
8163
+ tone: tone,
7789
8164
  children: [
7790
8165
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
7791
8166
  align: "center",
@@ -7795,7 +8170,7 @@ function CautionNote({action: action, label: label}) {
7795
8170
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
7796
8171
  muted: !0,
7797
8172
  size: 1,
7798
- children: /* @__PURE__ */ jsxRuntime.jsx(WarningOutline.WarningOutlineIcon, {})
8173
+ children: ICONS[tone]
7799
8174
  }),
7800
8175
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
7801
8176
  muted: !0,
@@ -7832,7 +8207,7 @@ function UnreadableDocsNote({unreadable: unreadable}) {
7832
8207
  if (unreadable.length === 0) return null;
7833
8208
  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.`;
7834
8209
  /* @__PURE__ */
7835
- return jsxRuntime.jsx(CautionNote, {
8210
+ return jsxRuntime.jsx(NoteBanner, {
7836
8211
  action: /* @__PURE__ */ jsxRuntime.jsx(CopyDetailsButton, {
7837
8212
  unreadable: unreadable
7838
8213
  }),
@@ -7878,6 +8253,21 @@ function InvalidDocNotice({invalid: invalid}) {
7878
8253
  });
7879
8254
  }
7880
8255
 
8256
+ function RoleAssignedNotice() {
8257
+ /* @__PURE__ */
8258
+ return jsxRuntime.jsx(ui.Box, {
8259
+ padding: 2,
8260
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
8261
+ muted: !0,
8262
+ size: 1,
8263
+ style: {
8264
+ fontStyle: "italic"
8265
+ },
8266
+ children: "Including tasks assigned to your role"
8267
+ })
8268
+ });
8269
+ }
8270
+
7881
8271
  function TabSwitch({ariaControls: ariaControls, idPrefix: idPrefix, options: options, selected: selected, onSelect: onSelect}) {
7882
8272
  /* @__PURE__ */
7883
8273
  return jsxRuntime.jsx(ui.TabList, {
@@ -8142,8 +8532,15 @@ function presentedResumeEntry(args) {
8142
8532
  if (!args.startNew) return findResumeEntry(args);
8143
8533
  }
8144
8534
 
8535
+ function settledByAnotherWriter(cause, instance) {
8536
+ return workflowEngine.isRevisionConflict(cause) && workflowEngine.terminalState(instance) === "completed";
8537
+ }
8538
+
8145
8539
  function classifyStartError(err) {
8146
- return err instanceof workflowEngine.StartNotSettledError ? {
8540
+ return err instanceof workflowEngine.StartNotSettledError ? err.instance !== void 0 && settledByAnotherWriter(err.cause, err.instance) ? {
8541
+ kind: "started-settled-elsewhere",
8542
+ instance: err.instance
8543
+ } : {
8147
8544
  kind: "started-not-settled",
8148
8545
  instance: err.instance,
8149
8546
  description: describeError(err.cause)
@@ -8156,8 +8553,16 @@ function classifyStartError(err) {
8156
8553
  };
8157
8554
  }
8158
8555
 
8556
+ function startProducedRun(outcome) {
8557
+ return outcome.kind === "started-not-settled" || outcome.kind === "started-settled-elsewhere";
8558
+ }
8559
+
8159
8560
  function startOutcomeToast(outcome, label) {
8160
- return outcome.kind === "started-not-settled" ? {
8561
+ return outcome.kind === "started-settled-elsewhere" ? {
8562
+ id: TOAST_ID.start,
8563
+ status: "info",
8564
+ title: `“${label}” started and finished`
8565
+ } : outcome.kind === "started-not-settled" ? {
8161
8566
  id: TOAST_ID.start,
8162
8567
  status: "warning",
8163
8568
  title: `“${label}” started but didn’t finish`,
@@ -8723,7 +9128,7 @@ function DiscoverySpinner() {
8723
9128
  });
8724
9129
  }
8725
9130
 
8726
- const BOOT_CUE_DELAY_MS = 400, GROUP_GAP = 2;
9131
+ const GROUP_GAP = 2;
8727
9132
 
8728
9133
  function useWorkflowsTab() {
8729
9134
  const {params: params, setParams: setParams} = structure.usePaneRouter();
@@ -8748,7 +9153,7 @@ function useOpenActivity(entries) {
8748
9153
  }
8749
9154
 
8750
9155
  function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappings: docTypeMappings, initialValue: initialValue}) {
8751
- const identity = useAssignmentIdentity(), panelId = react.useId(), {params: params} = structure.usePaneRouter(), {view: view, setView: setView} = useWorkflowsTab(), telemetry2 = workflowReact.useWorkflowTelemetry(), {openActivity: openActivity, setOpenActivity: setOpenActivity, dialogEntry: dialogEntry} = useOpenActivity(entries), active = entries.filter(isLiveEntry), finished = entries.filter(e => !isLiveEntry(e)), forMe = forMeWorkOf(active, identity), [landing] = react.useState(() => focusedLanding(params?.[WORKFLOWS_FOCUS_PARAM], entries.map(e => e.instance._id))), assignedWorkUnknown = active.some(e => e.invalid !== void 0), booting = useDelayedFlag(active.some(e => !e.ready && e.invalid === void 0), BOOT_CUE_DELAY_MS), sectionFor = e => /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceSection, {
9156
+ const identity = useAssignmentIdentity(), panelId = react.useId(), {params: params} = structure.usePaneRouter(), {view: view, setView: setView} = useWorkflowsTab(), telemetry2 = workflowReact.useWorkflowTelemetry(), {openActivity: openActivity, setOpenActivity: setOpenActivity, dialogEntry: dialogEntry} = useOpenActivity(entries), active = entries.filter(isLiveEntry), finished = entries.filter(e => !isLiveEntry(e)), forMe = forMeWorkOf(active, identityOf(identity)), [landing] = react.useState(() => focusedLanding(params?.[WORKFLOWS_FOCUS_PARAM], entries.map(e => e.instance._id))), assignedWorkUnknown = active.some(e => e.invalid !== void 0), booting = active.some(e => !e.ready && e.invalid === void 0), sectionFor = e => /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceSection, {
8752
9157
  defaultOpen: landingDefaultOpen(landing, e.instance._id),
8753
9158
  entry: e,
8754
9159
  onOpenActivity: activityName => setOpenActivity({
@@ -8888,10 +9293,10 @@ function OverviewEmptyState({mappings: mappings, docId: docId, initialValue: ini
8888
9293
  }
8889
9294
 
8890
9295
  function ForMeTab({forMe: forMe, assignedWorkUnknown: assignedWorkUnknown, onOpenActivity: onOpenActivity}) {
8891
- return forMe.hasRows ? /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
9296
+ return forMe.hasRows ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
8892
9297
  gap: GROUP_GAP,
8893
9298
  paddingBottom: 4,
8894
- children: forMe.work.map(({entry: entry, who: who, shown: shown}) => /* @__PURE__ */ jsxRuntime.jsx(ForMeList, {
9299
+ children: [ forMe.hasRoleMatched ? /* @__PURE__ */ jsxRuntime.jsx(RoleAssignedNotice, {}) : null, forMe.work.map(({entry: entry, who: who, shown: shown}) => /* @__PURE__ */ jsxRuntime.jsx(ForMeList, {
8895
9300
  entry: entry,
8896
9301
  identity: who,
8897
9302
  work: shown,
@@ -8900,7 +9305,7 @@ function ForMeTab({forMe: forMe, assignedWorkUnknown: assignedWorkUnknown, onOpe
8900
9305
  activityName: activityName,
8901
9306
  stage: entry.instance.currentStage
8902
9307
  })
8903
- }, entry.instance._id))
9308
+ }, entry.instance._id)) ]
8904
9309
  }) : assignedWorkUnknown ? null : /* @__PURE__ */ jsxRuntime.jsx(ForMeEmptyState, {});
8905
9310
  }
8906
9311
 
@@ -10138,7 +10543,7 @@ function ResumeNote({onStartNew: onStartNew}) {
10138
10543
 
10139
10544
  function handleStartError(args) {
10140
10545
  const {label: label, toast: toast, seedInstance: seedInstance, onClose: onClose} = args, outcome = classifyStartError(args.err);
10141
- toast.push(startOutcomeToast(outcome, label)), outcome.kind === "started-not-settled" && (outcome.instance !== void 0 && seedInstance(outcome.instance),
10546
+ toast.push(startOutcomeToast(outcome, label)), startProducedRun(outcome) && (outcome.instance !== void 0 && seedInstance(outcome.instance),
10142
10547
  onClose());
10143
10548
  }
10144
10549
 
@@ -10491,7 +10896,7 @@ async function startRequest(engine, request) {
10491
10896
  };
10492
10897
  } catch (err) {
10493
10898
  const outcome = classifyStartError(err);
10494
- return outcome.kind === "started-not-settled" ? outcome.instance ? {
10899
+ return startProducedRun(outcome) ? outcome.instance ? {
10495
10900
  instance: outcome.instance
10496
10901
  } : {} : outcome.kind === "not-allowed" ? {} : {
10497
10902
  failure: outcome.description
@@ -10692,6 +11097,28 @@ function unassignedLineHint(args) {
10692
11097
  };
10693
11098
  }
10694
11099
 
11100
+ function taskCountState(args) {
11101
+ const {entry: entry, identity: identity} = args, freshness = entry.invalid === void 0 ? evaluationFreshness({
11102
+ committed: entry.committed,
11103
+ evaluation: entry.evaluation
11104
+ }) : void 0;
11105
+ return identity.kind === "unavailable" ? {
11106
+ kind: "unknowable"
11107
+ } : identity.kind === "pending" || freshness === "pending" ? {
11108
+ kind: "pending"
11109
+ } : freshness === "stale" ? {
11110
+ kind: "stale"
11111
+ } : {
11112
+ kind: "ready",
11113
+ tasks: assignedTaskCount({
11114
+ instance: entry.instance,
11115
+ committed: entry.committed,
11116
+ evaluation: entry.evaluation,
11117
+ who: identity.identity
11118
+ })
11119
+ };
11120
+ }
11121
+
10695
11122
  function WorkflowFormStrip(props) {
10696
11123
  const {mappings: mappings} = props, {isDocResolved: isDocResolved, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {docId: docId, entries: entries} = useWorkflowsForDocument(props.value?._id ?? null), {params: params, setParams: setParams} = structure.usePaneRouter(), telemetry2 = workflowReact.useWorkflowTelemetry(), openWorkflowsView = focusId => {
10697
11124
  telemetry2.log(WorkflowFormStripClicked), setParams(focusedViewParams(params, focusId));
@@ -10779,7 +11206,7 @@ function InstanceLine({entry: entry, onOpen: onOpen}) {
10779
11206
  aside: unprimed ? void 0 : /* @__PURE__ */ jsxRuntime.jsx(TaskCountSpinner, {
10780
11207
  entry: entry
10781
11208
  }),
10782
- hint: lineHint(entry, identity),
11209
+ hint: lineHint(entry, identityOf(identity)),
10783
11210
  onOpen: onOpen,
10784
11211
  children: [
10785
11212
  /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
@@ -10838,28 +11265,16 @@ function finishedHint(instance) {
10838
11265
  }
10839
11266
 
10840
11267
  function useTaskCountState(entry) {
10841
- const identity = useAssignmentIdentity(), freshness = entry.invalid === void 0 ? evaluationFreshness({
10842
- committed: entry.committed,
10843
- evaluation: entry.evaluation
10844
- }) : void 0;
10845
- return !identity || freshness === "pending" ? {
10846
- kind: "pending"
10847
- } : freshness === "stale" ? {
10848
- kind: "stale"
10849
- } : {
10850
- kind: "ready",
10851
- tasks: assignedTaskCount({
10852
- instance: entry.instance,
10853
- committed: entry.committed,
10854
- evaluation: entry.evaluation,
10855
- who: identity
10856
- })
10857
- };
11268
+ const identity = useAssignmentIdentity();
11269
+ return taskCountState({
11270
+ entry: entry,
11271
+ identity: identity
11272
+ });
10858
11273
  }
10859
11274
 
10860
11275
  function TaskCountText({entry: entry}) {
10861
11276
  const state = useTaskCountState(entry);
10862
- return state.kind === "pending" || state.kind === "ready" && state.tasks === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
11277
+ return state.kind === "pending" || state.kind === "unknowable" || state.kind === "ready" && state.tasks === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
10863
11278
  muted: !0,
10864
11279
  size: 1,
10865
11280
  children: state.kind === "stale" ? "Updating…" : pluralize__default.default("assigned task", state.tasks, !0)
@@ -11100,13 +11515,13 @@ exports.ActivityLog = ActivityLog;
11100
11515
 
11101
11516
  exports.ActivityRow = ActivityRow;
11102
11517
 
11103
- exports.BreadcrumbTail = BreadcrumbTail;
11518
+ exports.AlertGlyph = AlertGlyph;
11104
11519
 
11105
- exports.CautionNote = CautionNote;
11520
+ exports.BreadcrumbTail = BreadcrumbTail;
11106
11521
 
11107
11522
  exports.CodeChip = CodeChip;
11108
11523
 
11109
- exports.CountedLabel = CountedLabel;
11524
+ exports.CountedHeading = CountedHeading;
11110
11525
 
11111
11526
  exports.DismissablePopover = DismissablePopover;
11112
11527
 
@@ -11122,6 +11537,10 @@ exports.Hairline = Hairline;
11122
11537
 
11123
11538
  exports.HoverHint = HoverHint;
11124
11539
 
11540
+ exports.InlineEmphasis = InlineEmphasis;
11541
+
11542
+ exports.InstanceFaultNote = InstanceFaultNote;
11543
+
11125
11544
  exports.InstanceSnapshotBody = InstanceSnapshotBody;
11126
11545
 
11127
11546
  exports.InvalidDocNotice = InvalidDocNotice;
@@ -11134,6 +11553,12 @@ exports.LogEventOnMount = LogEventOnMount;
11134
11553
 
11135
11554
  exports.MetaRow = MetaRow;
11136
11555
 
11556
+ exports.MetricPair = MetricPair;
11557
+
11558
+ exports.NoteBanner = NoteBanner;
11559
+
11560
+ exports.RoleAssignedNotice = RoleAssignedNotice;
11561
+
11137
11562
  exports.SpinnerSlot = SpinnerSlot;
11138
11563
 
11139
11564
  exports.StageFace = StageFace;
@@ -11186,16 +11611,18 @@ exports.dueDatesOf = dueDatesOf;
11186
11611
 
11187
11612
  exports.findActivity = findActivity;
11188
11613
 
11189
- exports.findActivityNode = findActivityNode;
11190
-
11191
11614
  exports.formatDate = formatDate;
11192
11615
 
11616
+ exports.formatShortAgo = formatShortAgo;
11617
+
11193
11618
  exports.formatShortDateTime = formatShortDateTime;
11194
11619
 
11195
11620
  exports.formatTimeAgo = formatTimeAgo;
11196
11621
 
11197
11622
  exports.gdrLocality = gdrLocality;
11198
11623
 
11624
+ exports.identityOf = identityOf;
11625
+
11199
11626
  exports.instanceBreadcrumb = instanceBreadcrumb;
11200
11627
 
11201
11628
  exports.instanceState = instanceState;
@@ -11204,12 +11631,16 @@ exports.instanceTitle = instanceTitle;
11204
11631
 
11205
11632
  exports.isActivityAssignedTo = isActivityAssignedTo;
11206
11633
 
11634
+ exports.isDueDatePast = isDueDatePast;
11635
+
11207
11636
  exports.isEvaluationStale = isEvaluationStale;
11208
11637
 
11209
11638
  exports.isLiveEntry = isLiveEntry;
11210
11639
 
11211
11640
  exports.isOpenActivityStatus = isOpenActivityStatus;
11212
11641
 
11642
+ exports.isRoleAssignedTo = isRoleAssignedTo;
11643
+
11213
11644
  exports.landingState = landingState;
11214
11645
 
11215
11646
  exports.mappingIssueDetail = mappingIssueDetail;
@@ -11240,16 +11671,14 @@ exports.toolTabState = toolTabState;
11240
11671
 
11241
11672
  exports.useAssignmentIdentity = useAssignmentIdentity;
11242
11673
 
11243
- exports.useBadgeCapTrim = useBadgeCapTrim;
11244
-
11245
11674
  exports.useContainerToken = useContainerToken;
11246
11675
 
11247
11676
  exports.useDefinition = useDefinition;
11248
11677
 
11249
- exports.useDelayedFlag = useDelayedFlag;
11250
-
11251
11678
  exports.useLogEventOnMount = useLogEventOnMount;
11252
11679
 
11680
+ exports.useMinuteClock = useMinuteClock;
11681
+
11253
11682
  exports.useProjectMembers = useProjectMembers;
11254
11683
 
11255
11684
  exports.useSpaceToken = useSpaceToken;