@sanity/workflow-studio-plugin 0.28.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"), ErrorOutline = require("@sanity/icons/ErrorOutline"), 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) {
@@ -844,7 +853,7 @@ function concludesHere(actionEval, activityName) {
844
853
  return changesOwnStatus(actionEval.action, activityName) || actionEval.firing?.exitsStage === !0;
845
854
  }
846
855
 
847
- const SEMANTIC_FACE = {
856
+ const DECISION_SEMANTIC_FACE = {
848
857
  "decision.accept": {
849
858
  tone: "positive",
850
859
  icon: Checkmark.CheckmarkIcon
@@ -855,6 +864,15 @@ const SEMANTIC_FACE = {
855
864
  }
856
865
  };
857
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
+
858
876
  function actionButtonFace(args) {
859
877
  const mode = concludesHere(args.actionEval, args.activityName) ? "default" : "ghost";
860
878
  if (actionAdvanceStatus(args.actionEval.action, args.activityName) === "failed") return {
@@ -862,7 +880,7 @@ function actionButtonFace(args) {
862
880
  tone: "critical",
863
881
  icon: void 0
864
882
  };
865
- const [semantic] = args.actionEval.semantics ?? [], face = semantic === void 0 ? void 0 : SEMANTIC_FACE[semantic];
883
+ const face = decisionSemanticFace(args.actionEval.semantics ?? []);
866
884
  return {
867
885
  mode: mode,
868
886
  tone: face?.tone ?? "default",
@@ -1329,21 +1347,32 @@ function bridgedSelfId(users, meId) {
1329
1347
  return users.find(user => user.membership.id === meId)?.profile?.sanityUserId ?? classified.globalId ?? (classified.namespace === "project" ? void 0 : meId);
1330
1348
  }
1331
1349
 
1332
- function useBridgedSelf() {
1350
+ function useSelfState() {
1333
1351
  const me = sanity.useCurrentUser(), {users: users, loading: loading, error: error} = workflowStudio.useStudioProjectUsers();
1334
- if (!me) return;
1335
- const id = bridgedSelfId(users, me.id);
1336
- if (id !== void 0) return {
1337
- id: id,
1338
- roles: me.roles ?? []
1352
+ if (!me) return {
1353
+ kind: "unavailable"
1339
1354
  };
1340
- if (!loading) {
1341
- if (error !== void 0) {
1342
- 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.`);
1343
- 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 ?? []
1344
1361
  }
1345
- 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.`);
1346
- }
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;
1347
1376
  }
1348
1377
 
1349
1378
  function useSelfActor() {
@@ -1352,13 +1381,22 @@ function useSelfActor() {
1352
1381
  }
1353
1382
 
1354
1383
  function useAssignmentIdentity() {
1355
- const self = useBridgedSelf();
1356
- if (self !== void 0) return {
1357
- userId: self.id,
1358
- 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
+ }
1359
1393
  };
1360
1394
  }
1361
1395
 
1396
+ function identityOf(state) {
1397
+ return state.kind === "resolved" ? state.identity : void 0;
1398
+ }
1399
+
1362
1400
  const projectMembersByUsers = /* @__PURE__ */ new WeakMap;
1363
1401
 
1364
1402
  function projectMembersFrom(users) {
@@ -1466,6 +1504,8 @@ function buildParams(decls, values) {
1466
1504
  };
1467
1505
  }
1468
1506
 
1507
+ const LOADING_CUE_DELAY_MS = 400;
1508
+
1469
1509
  function useDelayedFlag(active, delayMs) {
1470
1510
  const [held, setHeld] = react.useState(!1);
1471
1511
  return react.useEffect(() => {
@@ -2473,15 +2513,14 @@ function Separator() {
2473
2513
  }
2474
2514
 
2475
2515
  function stageTitle(definition, stageName) {
2476
- return definition?.stages.find(stage => stage.name === stageName)?.title ?? stageName;
2477
- }
2478
-
2479
- function findActivityNode(args) {
2480
- 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;
2481
2520
  }
2482
2521
 
2483
2522
  function findActivity(args) {
2484
- const activity = findActivityNode(args);
2523
+ const activity = workflowEngine.findActivityNode(args);
2485
2524
  return {
2486
2525
  title: activity?.title ?? args.activityName,
2487
2526
  description: activity?.description
@@ -3945,6 +3984,267 @@ function EditableField({field: field, entry: entry, description: description, in
3945
3984
  });
3946
3985
  }
3947
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
+
3948
4248
  function MetaRow({children: children, fillHeight: fillHeight, label: label}) {
3949
4249
  /* @__PURE__ */
3950
4250
  return jsxRuntime.jsxs(ui.Flex, {
@@ -4073,7 +4373,7 @@ function useBadgeCapTrim() {
4073
4373
  }
4074
4374
 
4075
4375
  function SpinnerSlot({busy: busy}) {
4076
- const size = useTextIconSize(), trim = useCapTrimFor(size);
4376
+ const size = useTextIconSize(), trim = useCapTrimFor(size), spinning = useDelayedFlag(busy, LOADING_CUE_DELAY_MS);
4077
4377
  /* @__PURE__ */
4078
4378
  return jsxRuntime.jsx(ui.Flex, {
4079
4379
  align: "center",
@@ -4085,7 +4385,7 @@ function SpinnerSlot({busy: busy}) {
4085
4385
  marginTop: trim,
4086
4386
  width: size
4087
4387
  },
4088
- children: busy ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
4388
+ children: spinning ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
4089
4389
  muted: !0,
4090
4390
  size: 1
4091
4391
  }) : null
@@ -4093,6 +4393,7 @@ function SpinnerSlot({busy: busy}) {
4093
4393
  }
4094
4394
 
4095
4395
  function LoadingRow({label: label, padding: padding}) {
4396
+ const announced = useDelayedFlag(!0, LOADING_CUE_DELAY_MS);
4096
4397
  /* @__PURE__ */
4097
4398
  return jsxRuntime.jsxs(ui.Flex, {
4098
4399
  align: "center",
@@ -4103,12 +4404,11 @@ function LoadingRow({label: label, padding: padding}) {
4103
4404
  children: [
4104
4405
  /* @__PURE__ */ jsxRuntime.jsx(SpinnerSlot, {
4105
4406
  busy: !0
4106
- }),
4107
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4407
+ }), announced ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4108
4408
  muted: !0,
4109
4409
  size: 1,
4110
4410
  children: label
4111
- }) ]
4411
+ }) : null ]
4112
4412
  });
4113
4413
  }
4114
4414
 
@@ -4375,49 +4675,40 @@ function ForMeEmptyState() {
4375
4675
  });
4376
4676
  }
4377
4677
 
4378
- function looksLikeDefinition(value) {
4379
- return typeof value == "object" && value !== null && Array.isArray(value.stages);
4380
- }
4381
-
4382
- const parsed = /* @__PURE__ */ new WeakMap;
4383
-
4384
- function definitionSnapshotOf(instance) {
4385
- if (parsed.has(instance)) return parsed.get(instance);
4386
- let definition;
4387
- try {
4388
- const candidate = workflowEngine.parseDefinitionSnapshot(instance);
4389
- if (!looksLikeDefinition(candidate)) throw new Error(`definitionSnapshot on "${instance._id}" parsed to a non-definition shape`);
4390
- definition = candidate;
4391
- } catch (err) {
4392
- console.error("[workflow-studio-plugin] unreadable definitionSnapshot:", err), definition = void 0;
4393
- }
4394
- return parsed.set(instance, definition), definition;
4395
- }
4678
+ const OVERDUE_HINT = "Now overdue";
4396
4679
 
4397
- function useDefinition(entry) {
4398
- return react.useMemo(() => {
4399
- if (entry) return entry.evaluation ? entry.evaluation.definition : definitionSnapshotOf(entry.instance);
4400
- }, [ 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
+ };
4401
4689
  }
4402
4690
 
4403
- function AbortedBadge({instance: instance}) {
4404
- return workflowEngine.terminalState(instance) !== "aborted" ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
4405
- fontSize: 1,
4406
- tone: "critical",
4407
- 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)
4408
4704
  });
4409
- }
4410
-
4411
- function OlderDefinitionBadge({instance: instance}) {
4412
- const {latestDefinitionVersions: latestDefinitionVersions} = useWorkflowContext(), latest = latestDefinitionVersions.get(instance.definition);
4413
- return latest === void 0 || latest <= instance.pinnedVersion ? null : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4414
- description: "Running workflows keep the version they started on",
4415
- text: `Started on v${instance.pinnedVersion}, latest is now v${latest}`,
4416
- textWeight: "medium",
4417
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
4418
- fontSize: 1,
4419
- tone: "caution",
4420
- 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
4421
4712
  })
4422
4713
  });
4423
4714
  }
@@ -4457,18 +4748,6 @@ function CheckboxSlot({children: children}) {
4457
4748
  });
4458
4749
  }
4459
4750
 
4460
- function DueDateText({dueDate: dueDate, overdue: overdue}) {
4461
- return overdue ? /* @__PURE__ */ jsxRuntime.jsx(sanity.TextWithTone, {
4462
- size: 1,
4463
- tone: "caution",
4464
- children: formatDate(dueDate)
4465
- }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4466
- muted: !0,
4467
- size: 1,
4468
- children: formatDate(dueDate)
4469
- });
4470
- }
4471
-
4472
4751
  function DueDateEditor({dueDate: dueDate, onDone: onDone, onPatch: onPatch}) {
4473
4752
  /* @__PURE__ */
4474
4753
  return jsxRuntime.jsx(workflowComponents.ClearableDatePicker, {
@@ -4488,8 +4767,9 @@ function DueDateEditor({dueDate: dueDate, onDone: onDone, onPatch: onPatch}) {
4488
4767
  }
4489
4768
 
4490
4769
  function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4491
- 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, {
4492
4771
  dueDate: item.dueDate,
4772
+ hintDisabled: open,
4493
4773
  overdue: overdue
4494
4774
  }) : null;
4495
4775
  return editable ? /* @__PURE__ */ jsxRuntime.jsx(DismissablePopover, {
@@ -4500,18 +4780,14 @@ function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4500
4780
  }),
4501
4781
  onDismiss: () => setOpen(!1),
4502
4782
  open: open,
4503
- children: display ? /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4504
- disabled: !overdue || open,
4505
- text: "Now overdue",
4506
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4507
- "aria-label": "Change due date",
4508
- fontSize: 1,
4509
- mode: "bleed",
4510
- onClick: () => setOpen(v => !v),
4511
- padding: 2,
4512
- tone: overdue ? "caution" : "default",
4513
- children: display
4514
- })
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
4515
4791
  }) : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4516
4792
  disabled: open,
4517
4793
  text: "Set due date",
@@ -4525,9 +4801,6 @@ function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4525
4801
  radius: 2
4526
4802
  })
4527
4803
  })
4528
- }) : display && overdue ? /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4529
- text: "Now overdue",
4530
- children: display
4531
4804
  }) : display;
4532
4805
  }
4533
4806
 
@@ -5180,9 +5453,15 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
5180
5453
  const detail = deriveActivityDetail({
5181
5454
  entry: entry,
5182
5455
  activityName: activityName
5183
- });
5456
+ }), faultingActivity2 = useInstanceFault(entry)?.activity;
5184
5457
  if (!detail) return null;
5185
- 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;
5186
5465
  /* @__PURE__ */
5187
5466
  return jsxRuntime.jsxs(ui.Dialog, {
5188
5467
  footer: detail.presentation.terminalActions.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(TerminalFooter, {
@@ -5223,7 +5502,7 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
5223
5502
  padding: 4,
5224
5503
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
5225
5504
  gap: 5,
5226
- children: [ detail.state.map(field => /* @__PURE__ */ jsxRuntime.jsx(FieldRow, {
5505
+ children: [ fault, detail.state.map(field => /* @__PURE__ */ jsxRuntime.jsx(FieldRow, {
5227
5506
  activityName: activityName,
5228
5507
  detail: detail,
5229
5508
  entry: entry,
@@ -5404,6 +5683,18 @@ function CountedLabel({count: count, label: label}) {
5404
5683
  });
5405
5684
  }
5406
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
+
5407
5698
  function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5408
5699
  /* @__PURE__ */
5409
5700
  return jsxRuntime.jsxs(ui.Flex, {
@@ -5412,13 +5703,9 @@ function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5412
5703
  paddingLeft: 2,
5413
5704
  paddingY: end === void 0 ? 3 : 1,
5414
5705
  children: [
5415
- /* @__PURE__ */ jsxRuntime.jsx(CountedLabel, {
5706
+ /* @__PURE__ */ jsxRuntime.jsx(CountedHeading, {
5416
5707
  count: count,
5417
- label: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5418
- size: 1,
5419
- weight: "semibold",
5420
- children: title
5421
- })
5708
+ title: title
5422
5709
  }), busy === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(SpinnerSlot, {
5423
5710
  busy: busy
5424
5711
  }), end === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
@@ -5430,21 +5717,81 @@ function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5430
5717
  });
5431
5718
  }
5432
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
+
5433
5777
  function ActivityDateControl({instanceId: instanceId, state: state, surface: surface, dueDates: dueDates}) {
5434
5778
  const editField = useEditField(surface), {save: save} = useSaveField({
5435
5779
  instanceId: instanceId
5436
5780
  }), [open, setOpen] = react.useState(!1), raw = dateControlValue({
5437
5781
  state: state,
5438
5782
  dueDates: dueDates
5439
- }), display = raw === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5440
- muted: !0,
5441
- size: 1,
5442
- 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
+ }
5443
5790
  });
5444
- if (state.kind === "none") return display;
5445
- if (state.kind === "closed") return display === null ? null : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
5446
- text: state.reason,
5447
- 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, {
5448
5795
  "aria-label": "Date",
5449
5796
  as: "span",
5450
5797
  fontSize: 1,
@@ -5453,8 +5800,8 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5453
5800
  onMouseDown: stopRowMouseDown,
5454
5801
  padding: 2,
5455
5802
  children: display
5456
- })
5457
- });
5803
+ });
5804
+ }
5458
5805
  const kind = dateControlKind(state), commit = (mode, value) => {
5459
5806
  state.kind === "editable" && save(() => editField({
5460
5807
  instanceId: instanceId,
@@ -5490,7 +5837,7 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5490
5837
  }),
5491
5838
  onDismiss: () => setOpen(!1),
5492
5839
  open: open,
5493
- children: display ? /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
5840
+ children: raw !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
5494
5841
  "aria-label": "Change date",
5495
5842
  as: "span",
5496
5843
  fontSize: 1,
@@ -5498,7 +5845,7 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5498
5845
  onClick: handleClick,
5499
5846
  onMouseDown: stopRowMouseDown,
5500
5847
  padding: 2,
5501
- children: display
5848
+ children: dueDate()
5502
5849
  }) : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
5503
5850
  disabled: open,
5504
5851
  text: "Set date",
@@ -5588,7 +5935,7 @@ function TitleTag({hint: hint, tone: tone, children: children}) {
5588
5935
  });
5589
5936
  }
5590
5937
 
5591
- 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 = []}) {
5592
5939
  const settled = workflowEngine.isTerminalActivityStatus(face.status), dimmed = settledDim(settled);
5593
5940
  /* @__PURE__ */
5594
5941
  return jsxRuntime.jsxs(WorkRow, {
@@ -5621,6 +5968,10 @@ function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees,
5621
5968
  /* @__PURE__ */ jsxRuntime.jsx(ActivityRowTitle, {
5622
5969
  dimmed: dimmed,
5623
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"
5624
5975
  }), face.automated ? /* @__PURE__ */ jsxRuntime.jsx(TitleTag, {
5625
5976
  hint: "Completes on its own — no user action is needed",
5626
5977
  children: "Automated"
@@ -5636,7 +5987,7 @@ function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees,
5636
5987
  }
5637
5988
 
5638
5989
  function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity, surface: surface}) {
5639
- 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);
5640
5991
  return activities.length === 0 ?
5641
5992
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
5642
5993
  paddingX: 1,
@@ -5654,6 +6005,9 @@ function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity, surface:
5654
6005
  activityState: activityState,
5655
6006
  editableFields: entry.evaluation?.editableFields
5656
6007
  }),
6008
+ ...fault?.activity === t.activity.name ? {
6009
+ faultHint: fault.summary
6010
+ } : {},
5657
6011
  instanceId: entry.instance._id,
5658
6012
  onOpen: () => onOpenActivity(t.activity.name),
5659
6013
  surface: surface
@@ -5800,7 +6154,10 @@ function progressFillPercent(face) {
5800
6154
  }
5801
6155
 
5802
6156
  function deriveFieldPills(args) {
5803
- 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({
5804
6161
  committed: committed,
5805
6162
  evaluation: evaluation
5806
6163
  }) ? editablesByName(evaluation, scope) : /* @__PURE__ */ new Map, declaredNames = new Set(declared.map(d => d.name));
@@ -6990,6 +7347,27 @@ function HintedMenuButton({hint: hint, ...menuButtonProps}) {
6990
7347
  });
6991
7348
  }
6992
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
+
6993
7371
  function historyWindow(args) {
6994
7372
  const {entries: entries, expanded: expanded} = args;
6995
7373
  return {
@@ -6999,28 +7377,10 @@ function historyWindow(args) {
6999
7377
  }
7000
7378
 
7001
7379
  function PendingEffectRows({instance: instance, now: now}) {
7002
- 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 ?? [];
7003
7381
  if (pending.length === 0) return null;
7004
- const runRegistered = async () => {
7005
- setBusy(!0);
7006
- try {
7007
- await drainEffectsFor(instance._id), toast.push({
7008
- id: TOAST_ID.effectsDrain,
7009
- status: "info",
7010
- title: "Ran the registered handlers"
7011
- });
7012
- } catch (err) {
7013
- toast.push({
7014
- id: TOAST_ID.effectsDrain,
7015
- status: "error",
7016
- title: "Failed to run pending effects",
7017
- description: describeError(err)
7018
- });
7019
- } finally {
7020
- setBusy(!1);
7021
- }
7022
- }, resolve = async (effectKey, status) => {
7023
- setBusy(!0);
7382
+ const resolve = async (effectKey, status) => {
7383
+ setResolving(!0);
7024
7384
  try {
7025
7385
  await completeEffectFor(instance._id, {
7026
7386
  effectKey: effectKey,
@@ -7039,18 +7399,20 @@ function PendingEffectRows({instance: instance, now: now}) {
7039
7399
  description: describeError(err)
7040
7400
  });
7041
7401
  } finally {
7042
- setBusy(!1);
7402
+ setResolving(!1);
7043
7403
  }
7044
7404
  };
7045
7405
  /* @__PURE__ */
7046
7406
  return jsxRuntime.jsx(jsxRuntime.Fragment, {
7047
7407
  children: pending.map(fx => /* @__PURE__ */ jsxRuntime.jsx(PendingEffectRow, {
7048
- busy: busy,
7408
+ busy: drain.busy || resolving,
7049
7409
  effect: fx,
7050
7410
  instanceId: instance._id,
7051
7411
  now: now,
7052
7412
  onResolve: status => resolve(fx._key, status),
7053
- onRunHandlers: runRegistered,
7413
+ onRunHandlers: () => {
7414
+ drain.run();
7415
+ },
7054
7416
  registered: fx.name in effectHandlers
7055
7417
  }, fx._key))
7056
7418
  });
@@ -7134,12 +7496,7 @@ function PendingEffectRow({busy: busy, effect: effect, instanceId: instanceId, n
7134
7496
  }
7135
7497
 
7136
7498
  function ActivityLog({instance: instance, definition: definition, windowed: windowed = !0}) {
7137
- 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);
7138
- react.useEffect(() => {
7139
- const timer = setInterval(() => setNow(/* @__PURE__ */ new Date), 6e4);
7140
- return () => clearInterval(timer);
7141
- }, []);
7142
- 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({
7143
7500
  entries: entries,
7144
7501
  expanded: expanded || !windowed
7145
7502
  });
@@ -7242,12 +7599,9 @@ function HistoryRow({entry: entry, now: now, titles: titles}) {
7242
7599
  }
7243
7600
 
7244
7601
  function ActorNameSpan({id: id}) {
7245
- const {name: name} = useUserDisplay(id), {font: font} = ui.useTheme_v2();
7602
+ const {name: name} = useUserDisplay(id);
7246
7603
  /* @__PURE__ */
7247
- return jsxRuntime.jsx("span", {
7248
- style: {
7249
- fontWeight: font.text.weights.medium
7250
- },
7604
+ return jsxRuntime.jsx(InlineEmphasis, {
7251
7605
  children: name
7252
7606
  });
7253
7607
  }
@@ -7655,7 +8009,6 @@ function StageOverviewCard({overview: overview}) {
7655
8009
  }
7656
8010
 
7657
8011
  function AdvanceCard({advance: advance}) {
7658
- const {font: font} = ui.useTheme_v2();
7659
8012
  /* @__PURE__ */
7660
8013
  return jsxRuntime.jsx(ui.Card, {
7661
8014
  padding: 3,
@@ -7667,10 +8020,7 @@ function AdvanceCard({advance: advance}) {
7667
8020
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
7668
8021
  size: 1,
7669
8022
  children: [ "Advances to ",
7670
- /* @__PURE__ */ jsxRuntime.jsx("span", {
7671
- style: {
7672
- fontWeight: font.text.weights.medium
7673
- },
8023
+ /* @__PURE__ */ jsxRuntime.jsx(InlineEmphasis, {
7674
8024
  children: advance.toTitle
7675
8025
  }), ADVANCE_TAILS[advance.kind] ]
7676
8026
  }), advance.kind !== "conditions" ? null : advance.lines.map((line, index) =>
@@ -8778,7 +9128,7 @@ function DiscoverySpinner() {
8778
9128
  });
8779
9129
  }
8780
9130
 
8781
- const BOOT_CUE_DELAY_MS = 400, GROUP_GAP = 2;
9131
+ const GROUP_GAP = 2;
8782
9132
 
8783
9133
  function useWorkflowsTab() {
8784
9134
  const {params: params, setParams: setParams} = structure.usePaneRouter();
@@ -8803,7 +9153,7 @@ function useOpenActivity(entries) {
8803
9153
  }
8804
9154
 
8805
9155
  function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappings: docTypeMappings, initialValue: initialValue}) {
8806
- 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, {
8807
9157
  defaultOpen: landingDefaultOpen(landing, e.instance._id),
8808
9158
  entry: e,
8809
9159
  onOpenActivity: activityName => setOpenActivity({
@@ -10747,6 +11097,28 @@ function unassignedLineHint(args) {
10747
11097
  };
10748
11098
  }
10749
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
+
10750
11122
  function WorkflowFormStrip(props) {
10751
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 => {
10752
11124
  telemetry2.log(WorkflowFormStripClicked), setParams(focusedViewParams(params, focusId));
@@ -10834,7 +11206,7 @@ function InstanceLine({entry: entry, onOpen: onOpen}) {
10834
11206
  aside: unprimed ? void 0 : /* @__PURE__ */ jsxRuntime.jsx(TaskCountSpinner, {
10835
11207
  entry: entry
10836
11208
  }),
10837
- hint: lineHint(entry, identity),
11209
+ hint: lineHint(entry, identityOf(identity)),
10838
11210
  onOpen: onOpen,
10839
11211
  children: [
10840
11212
  /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
@@ -10893,28 +11265,16 @@ function finishedHint(instance) {
10893
11265
  }
10894
11266
 
10895
11267
  function useTaskCountState(entry) {
10896
- const identity = useAssignmentIdentity(), freshness = entry.invalid === void 0 ? evaluationFreshness({
10897
- committed: entry.committed,
10898
- evaluation: entry.evaluation
10899
- }) : void 0;
10900
- return !identity || freshness === "pending" ? {
10901
- kind: "pending"
10902
- } : freshness === "stale" ? {
10903
- kind: "stale"
10904
- } : {
10905
- kind: "ready",
10906
- tasks: assignedTaskCount({
10907
- instance: entry.instance,
10908
- committed: entry.committed,
10909
- evaluation: entry.evaluation,
10910
- who: identity
10911
- })
10912
- };
11268
+ const identity = useAssignmentIdentity();
11269
+ return taskCountState({
11270
+ entry: entry,
11271
+ identity: identity
11272
+ });
10913
11273
  }
10914
11274
 
10915
11275
  function TaskCountText({entry: entry}) {
10916
11276
  const state = useTaskCountState(entry);
10917
- 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, {
10918
11278
  muted: !0,
10919
11279
  size: 1,
10920
11280
  children: state.kind === "stale" ? "Updating…" : pluralize__default.default("assigned task", state.tasks, !0)
@@ -11155,11 +11515,13 @@ exports.ActivityLog = ActivityLog;
11155
11515
 
11156
11516
  exports.ActivityRow = ActivityRow;
11157
11517
 
11518
+ exports.AlertGlyph = AlertGlyph;
11519
+
11158
11520
  exports.BreadcrumbTail = BreadcrumbTail;
11159
11521
 
11160
11522
  exports.CodeChip = CodeChip;
11161
11523
 
11162
- exports.CountedLabel = CountedLabel;
11524
+ exports.CountedHeading = CountedHeading;
11163
11525
 
11164
11526
  exports.DismissablePopover = DismissablePopover;
11165
11527
 
@@ -11175,6 +11537,10 @@ exports.Hairline = Hairline;
11175
11537
 
11176
11538
  exports.HoverHint = HoverHint;
11177
11539
 
11540
+ exports.InlineEmphasis = InlineEmphasis;
11541
+
11542
+ exports.InstanceFaultNote = InstanceFaultNote;
11543
+
11178
11544
  exports.InstanceSnapshotBody = InstanceSnapshotBody;
11179
11545
 
11180
11546
  exports.InvalidDocNotice = InvalidDocNotice;
@@ -11187,6 +11553,8 @@ exports.LogEventOnMount = LogEventOnMount;
11187
11553
 
11188
11554
  exports.MetaRow = MetaRow;
11189
11555
 
11556
+ exports.MetricPair = MetricPair;
11557
+
11190
11558
  exports.NoteBanner = NoteBanner;
11191
11559
 
11192
11560
  exports.RoleAssignedNotice = RoleAssignedNotice;
@@ -11243,16 +11611,18 @@ exports.dueDatesOf = dueDatesOf;
11243
11611
 
11244
11612
  exports.findActivity = findActivity;
11245
11613
 
11246
- exports.findActivityNode = findActivityNode;
11247
-
11248
11614
  exports.formatDate = formatDate;
11249
11615
 
11616
+ exports.formatShortAgo = formatShortAgo;
11617
+
11250
11618
  exports.formatShortDateTime = formatShortDateTime;
11251
11619
 
11252
11620
  exports.formatTimeAgo = formatTimeAgo;
11253
11621
 
11254
11622
  exports.gdrLocality = gdrLocality;
11255
11623
 
11624
+ exports.identityOf = identityOf;
11625
+
11256
11626
  exports.instanceBreadcrumb = instanceBreadcrumb;
11257
11627
 
11258
11628
  exports.instanceState = instanceState;
@@ -11261,6 +11631,8 @@ exports.instanceTitle = instanceTitle;
11261
11631
 
11262
11632
  exports.isActivityAssignedTo = isActivityAssignedTo;
11263
11633
 
11634
+ exports.isDueDatePast = isDueDatePast;
11635
+
11264
11636
  exports.isEvaluationStale = isEvaluationStale;
11265
11637
 
11266
11638
  exports.isLiveEntry = isLiveEntry;
@@ -11299,16 +11671,14 @@ exports.toolTabState = toolTabState;
11299
11671
 
11300
11672
  exports.useAssignmentIdentity = useAssignmentIdentity;
11301
11673
 
11302
- exports.useBadgeCapTrim = useBadgeCapTrim;
11303
-
11304
11674
  exports.useContainerToken = useContainerToken;
11305
11675
 
11306
11676
  exports.useDefinition = useDefinition;
11307
11677
 
11308
- exports.useDelayedFlag = useDelayedFlag;
11309
-
11310
11678
  exports.useLogEventOnMount = useLogEventOnMount;
11311
11679
 
11680
+ exports.useMinuteClock = useMinuteClock;
11681
+
11312
11682
  exports.useProjectMembers = useProjectMembers;
11313
11683
 
11314
11684
  exports.useSpaceToken = useSpaceToken;