@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.
@@ -2,7 +2,7 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
 
3
3
  import { TimelineIcon } from "@sanity/icons/Timeline";
4
4
 
5
- import { Popover, Tooltip, Box, Stack, Text, useClickOutsideEvent, Flex, TextInput, Button, Select, Checkbox, Autocomplete, Card, TextArea, useToast, Dialog, Switch, TextSkeleton, Code, useTheme_v2, Spinner, Badge, MenuButton, Menu, MenuItem, useElementSize, MenuDivider, TabList, Tab, TabPanel } from "@sanity/ui";
5
+ import { Popover, Tooltip, Box, Stack, Text, useClickOutsideEvent, Flex, TextInput, Button, Select, Checkbox, Autocomplete, Card, TextArea, useToast, Dialog, Switch, TextSkeleton, Code, Badge, useTheme_v2, Spinner, MenuButton, Menu, MenuItem, useElementSize, MenuDivider, TabList, Tab, TabPanel } from "@sanity/ui";
6
6
 
7
7
  import { editFieldTarget, useWorkflowTelemetry, useRefcountedIds } from "@sanity/workflow-react";
8
8
 
@@ -10,7 +10,7 @@ import { createContext, useContext, useEffect, useSyncExternalStore, useCallback
10
10
 
11
11
  import { usePaneRouter } from "sanity/structure";
12
12
 
13
- import { WORKFLOW_INSTANCE_TYPE, WORKFLOW_DEFINITION_TYPE, GUARD_DOC_TYPE, tryParseGdr, resourceFromParsed, latestDeployedDefinitions, isStartableDefinition, isSubjectEntry, isInputSourced, acceptsDocumentType, initialFieldIssues, gdrRef, releaseRef, refKindAcceptsTypes, rejectedRefTypes, ActionDisabledError, MutationGuardDeniedError, EditFieldDeniedError, errorMessage, terminalState, findOpenStageEntry, actionRendering, isTodoListEntry, isTodoListItem, isTerminalActivityStatus, describeCondition, sentenceCase, checklistLines, findCurrentActivityEntry, ENGINE_API_VERSION, scalarValidationIssues, classifyPrincipalId, userLoginProvider, toBareId, isSingleDocRefEntry, isGdr, resolveFieldEntry, isSingleDocRefKind, isNotesEntry, parseDefinitionSnapshot, isUnprimed, isTerminalStage, DEFAULT_TRANSITION_WHEN, instancesQuery, assertReadableModel, projectStartSliceRow, StartNotSettledError, StartNotAllowedError, isRevisionConflict, humanize, unboundRequirementReads, explainStartRequirement, singleSubjectRequirementRefused, evaluateStartFilter, startKindOf, hasSingleSubjectRequirement, readsRootDocument, latestDefinitionsGroq, gdrFromResource, subscriptionDocumentsForInstance, aclPathForResource, definitionLookupGroq, instanceDocId, missingRequiredInputs, documentActionDenials } from "@sanity/workflow-engine";
13
+ import { WORKFLOW_INSTANCE_TYPE, WORKFLOW_DEFINITION_TYPE, GUARD_DOC_TYPE, tryParseGdr, resourceFromParsed, latestDeployedDefinitions, isStartableDefinition, isSubjectEntry, isInputSourced, acceptsDocumentType, initialFieldIssues, gdrRef, releaseRef, refKindAcceptsTypes, rejectedRefTypes, ActionDisabledError, MutationGuardDeniedError, EditFieldDeniedError, errorMessage, terminalState, findOpenStageEntry, actionRendering, isTodoListEntry, isTodoListItem, isTerminalActivityStatus, describeCondition, sentenceCase, checklistLines, findCurrentActivityEntry, ENGINE_API_VERSION, scalarValidationIssues, classifyPrincipalId, userLoginProvider, toBareId, findStageNode, findActivityNode, isSingleDocRefEntry, isGdr, resolveFieldEntry, isSingleDocRefKind, isNotesEntry, remediationsFor, diagnoseInstance, diagnoseInputFromEvaluation, documentStuckCause, parseDefinitionSnapshot, isUnprimed, isTerminalStage, DEFAULT_TRANSITION_WHEN, instancesQuery, assertReadableModel, projectStartSliceRow, StartNotSettledError, StartNotAllowedError, isRevisionConflict, humanize, unboundRequirementReads, explainStartRequirement, singleSubjectRequirementRefused, evaluateStartFilter, startKindOf, hasSingleSubjectRequirement, readsRootDocument, latestDefinitionsGroq, gdrFromResource, subscriptionDocumentsForInstance, aclPathForResource, definitionLookupGroq, instanceDocId, missingRequiredInputs, documentActionDenials } from "@sanity/workflow-engine";
14
14
 
15
15
  import { CheckmarkIcon } from "@sanity/icons/Checkmark";
16
16
 
@@ -52,6 +52,8 @@ import { isDocumentSchemaType } from "@sanity/types";
52
52
 
53
53
  import { LaunchIcon } from "@sanity/icons/Launch";
54
54
 
55
+ import { ErrorOutlineIcon } from "@sanity/icons/ErrorOutline";
56
+
55
57
  import { EmptyIcon } from "@sanity/icons/Empty";
56
58
 
57
59
  import { ErrorFilledIcon } from "@sanity/icons/ErrorFilled";
@@ -78,8 +80,6 @@ import { CogIcon } from "@sanity/icons/Cog";
78
80
 
79
81
  import { ArrowUpIcon } from "@sanity/icons/ArrowUp";
80
82
 
81
- import { ErrorOutlineIcon } from "@sanity/icons/ErrorOutline";
82
-
83
83
  import { of } from "rxjs";
84
84
 
85
85
  import { switchMap, distinctUntilChanged } from "rxjs/operators";
@@ -409,16 +409,25 @@ function parseLocalDate(value) {
409
409
  return parsed2.getFullYear() === year && parsed2.getMonth() === (month ?? NaN) - 1 && parsed2.getDate() === day ? parsed2 : /* @__PURE__ */ new Date(NaN);
410
410
  }
411
411
 
412
+ function storedDateKind(value) {
413
+ return /^\d{4}-\d{2}-\d{2}$/.test(value) ? "date" : "datetime";
414
+ }
415
+
412
416
  function parseStoredDateValue(value) {
413
- if (typeof value != "string") return;
414
- const kind = /^\d{4}-\d{2}-\d{2}$/.test(value) ? "date" : "datetime";
415
- return parseDateFieldValue(value, kind);
417
+ if (typeof value == "string") return parseDateFieldValue(value, storedDateKind(value));
416
418
  }
417
419
 
418
420
  function serializeDateFieldValue(date, kind) {
419
421
  return hasTimeOfDay(kind) ? date.toISOString() : `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
420
422
  }
421
423
 
424
+ function isDueDatePast(value, now) {
425
+ const kind = storedDateKind(value);
426
+ if (!hasTimeOfDay(kind)) return value < serializeDateFieldValue(now, kind);
427
+ const due = parseDateFieldValue(value, kind);
428
+ return due !== void 0 && due.getTime() < now.getTime();
429
+ }
430
+
422
431
  class EffectsIncompleteError extends Error {
423
432
  reason;
424
433
  constructor(reason, options) {
@@ -599,7 +608,7 @@ function isLiveEntry(entry) {
599
608
  const isTodoDone = item => item.status === "done", toggledTodoStatus = item => isTodoDone(item) ? "open" : "done";
600
609
 
601
610
  function isTodoOverdue(item, today = /* @__PURE__ */ new Date) {
602
- return !item.dueDate || isTodoDone(item) ? !1 : item.dueDate < serializeDateFieldValue(today, "date");
611
+ return !item.dueDate || isTodoDone(item) ? !1 : isDueDatePast(item.dueDate, today);
603
612
  }
604
613
 
605
614
  function todoEditKind(patch) {
@@ -920,7 +929,7 @@ function concludesHere(actionEval, activityName) {
920
929
  return changesOwnStatus(actionEval.action, activityName) || actionEval.firing?.exitsStage === !0;
921
930
  }
922
931
 
923
- const SEMANTIC_FACE = {
932
+ const DECISION_SEMANTIC_FACE = {
924
933
  "decision.accept": {
925
934
  tone: "positive",
926
935
  icon: CheckmarkIcon
@@ -931,6 +940,15 @@ const SEMANTIC_FACE = {
931
940
  }
932
941
  };
933
942
 
943
+ function isDecisionSemantic(value) {
944
+ return Object.hasOwn(DECISION_SEMANTIC_FACE, value);
945
+ }
946
+
947
+ function decisionSemanticFace(semantics) {
948
+ const semantic = semantics.find(isDecisionSemantic);
949
+ return semantic === void 0 ? void 0 : DECISION_SEMANTIC_FACE[semantic];
950
+ }
951
+
934
952
  function actionButtonFace(args) {
935
953
  const mode = concludesHere(args.actionEval, args.activityName) ? "default" : "ghost";
936
954
  if (actionAdvanceStatus(args.actionEval.action, args.activityName) === "failed") return {
@@ -938,7 +956,7 @@ function actionButtonFace(args) {
938
956
  tone: "critical",
939
957
  icon: void 0
940
958
  };
941
- const [semantic] = args.actionEval.semantics ?? [], face = semantic === void 0 ? void 0 : SEMANTIC_FACE[semantic];
959
+ const face = decisionSemanticFace(args.actionEval.semantics ?? []);
942
960
  return {
943
961
  mode: mode,
944
962
  tone: face?.tone ?? "default",
@@ -1405,21 +1423,32 @@ function bridgedSelfId(users, meId) {
1405
1423
  return users.find(user => user.membership.id === meId)?.profile?.sanityUserId ?? classified.globalId ?? (classified.namespace === "project" ? void 0 : meId);
1406
1424
  }
1407
1425
 
1408
- function useBridgedSelf() {
1426
+ function useSelfState() {
1409
1427
  const me = useCurrentUser(), {users: users, loading: loading, error: error} = useStudioProjectUsers();
1410
- if (!me) return;
1411
- const id = bridgedSelfId(users, me.id);
1412
- if (id !== void 0) return {
1413
- id: id,
1414
- roles: me.roles ?? []
1428
+ if (!me) return {
1429
+ kind: "unavailable"
1415
1430
  };
1416
- if (!loading) {
1417
- if (error !== void 0) {
1418
- warnSelfUnavailableOnce(`load:${me.id}`, `workflow: the project member directory failed to load ("${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.`);
1419
- return;
1431
+ const id = bridgedSelfId(users, me.id);
1432
+ return id !== void 0 ? {
1433
+ kind: "resolved",
1434
+ self: {
1435
+ id: id,
1436
+ roles: me.roles ?? []
1420
1437
  }
1421
- 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.`);
1422
- }
1438
+ } : loading ? {
1439
+ kind: "pending"
1440
+ } : error !== void 0 ? (warnSelfUnavailableOnce(`load:${me.id}`, `workflow: the project member directory failed to load ("${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.`),
1441
+ {
1442
+ kind: "unavailable"
1443
+ }) : (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.`),
1444
+ {
1445
+ kind: "unavailable"
1446
+ });
1447
+ }
1448
+
1449
+ function useBridgedSelf() {
1450
+ const state = useSelfState();
1451
+ return state.kind === "resolved" ? state.self : void 0;
1423
1452
  }
1424
1453
 
1425
1454
  function useSelfActor() {
@@ -1428,13 +1457,22 @@ function useSelfActor() {
1428
1457
  }
1429
1458
 
1430
1459
  function useAssignmentIdentity() {
1431
- const self = useBridgedSelf();
1432
- if (self !== void 0) return {
1433
- userId: self.id,
1434
- roles: self.roles.map(r => r.name)
1460
+ const state = useSelfState();
1461
+ if (state.kind !== "resolved") return state;
1462
+ const {id: id, roles: roles} = state.self;
1463
+ return {
1464
+ kind: "resolved",
1465
+ identity: {
1466
+ userId: id,
1467
+ roles: roles.map(r => r.name)
1468
+ }
1435
1469
  };
1436
1470
  }
1437
1471
 
1472
+ function identityOf(state) {
1473
+ return state.kind === "resolved" ? state.identity : void 0;
1474
+ }
1475
+
1438
1476
  const projectMembersByUsers = /* @__PURE__ */ new WeakMap;
1439
1477
 
1440
1478
  function projectMembersFrom(users) {
@@ -1542,6 +1580,8 @@ function buildParams(decls, values) {
1542
1580
  };
1543
1581
  }
1544
1582
 
1583
+ const LOADING_CUE_DELAY_MS = 400;
1584
+
1545
1585
  function useDelayedFlag(active, delayMs) {
1546
1586
  const [held, setHeld] = useState(!1);
1547
1587
  return useEffect(() => {
@@ -2549,11 +2589,10 @@ function Separator() {
2549
2589
  }
2550
2590
 
2551
2591
  function stageTitle(definition, stageName) {
2552
- return definition?.stages.find(stage => stage.name === stageName)?.title ?? stageName;
2553
- }
2554
-
2555
- function findActivityNode(args) {
2556
- return args.definition?.stages.find(s => s.name === args.stageName)?.activities?.find(a => a.name === args.activityName);
2592
+ return findStageNode({
2593
+ definition: definition,
2594
+ stageName: stageName
2595
+ })?.title ?? stageName;
2557
2596
  }
2558
2597
 
2559
2598
  function findActivity(args) {
@@ -4021,6 +4060,267 @@ function EditableField({field: field, entry: entry, description: description, in
4021
4060
  });
4022
4061
  }
4023
4062
 
4063
+ function useDrainEffects(instanceId) {
4064
+ const {drainEffectsFor: drainEffectsFor} = useWorkflowContext(), toast = useWorkflowToast(), [busy, setBusy] = useState(!1);
4065
+ return {
4066
+ busy: busy,
4067
+ run: async () => {
4068
+ setBusy(!0);
4069
+ try {
4070
+ await drainEffectsFor(instanceId), toast.push({
4071
+ id: TOAST_ID.effectsDrain,
4072
+ status: "info",
4073
+ title: "Ran the registered handlers"
4074
+ });
4075
+ } catch (err) {
4076
+ toast.push({
4077
+ id: TOAST_ID.effectsDrain,
4078
+ status: "error",
4079
+ title: "Failed to run pending effects",
4080
+ description: describeError(err)
4081
+ });
4082
+ } finally {
4083
+ setBusy(!1);
4084
+ }
4085
+ }
4086
+ };
4087
+ }
4088
+
4089
+ function isRunnable(verb) {
4090
+ return verb === "drain-effects";
4091
+ }
4092
+
4093
+ function effectLabel(effect) {
4094
+ return effect.title ?? effect.name;
4095
+ }
4096
+
4097
+ const ACTION_LABEL = {
4098
+ "drain-effects": "Run the step again"
4099
+ }, REMEDY_TEXT = {
4100
+ "retry-effect": "The step has to run again before this workflow can continue.",
4101
+ "reset-activity": "Reset the task to try it again, or skip it.",
4102
+ "set-stage": "Move the workflow on to its next stage yourself."
4103
+ };
4104
+
4105
+ function detailOf(cause) {
4106
+ switch (cause.kind) {
4107
+ case "failed-effect":
4108
+ return `The step “${effectLabel(cause.effect)}” failed`;
4109
+
4110
+ case "hung-effect":
4111
+ return `The step “${effectLabel(cause.effect)}” didn’t finish`;
4112
+
4113
+ case "failed-activity":
4114
+ return "This task failed";
4115
+
4116
+ default:
4117
+ return summaryOf(cause);
4118
+ }
4119
+ }
4120
+
4121
+ function summaryOf(cause) {
4122
+ switch (cause.kind) {
4123
+ case "failed-effect":
4124
+ return "An automated step failed";
4125
+
4126
+ case "hung-effect":
4127
+ return "An automated step didn’t finish";
4128
+
4129
+ case "failed-activity":
4130
+ return "A task failed";
4131
+
4132
+ case "no-transition-fires":
4133
+ return "Every task is done, but nothing moves this workflow on";
4134
+
4135
+ case "transition-unevaluable":
4136
+ return "This workflow is waiting on something it can’t read yet, and will move on by itself";
4137
+ }
4138
+ }
4139
+
4140
+ function activityDeclaringEffect(args) {
4141
+ return findStageNode(args)?.activities?.find(activity => (activity.actions ?? []).some(action => (action.effects ?? []).some(effect => effect.name === args.effectName)))?.name;
4142
+ }
4143
+
4144
+ function faultingActivity(args) {
4145
+ const {cause: cause} = args;
4146
+ if (cause.kind === "failed-activity") return cause.activity;
4147
+ if (cause.kind === "failed-effect" || cause.kind === "hung-effect") return activityDeclaringEffect({
4148
+ definition: args.definition,
4149
+ effectName: cause.effect.name,
4150
+ stageName: args.stageName
4151
+ });
4152
+ }
4153
+
4154
+ function isRecoverable(cause) {
4155
+ return cause.kind === "transition-unevaluable";
4156
+ }
4157
+
4158
+ function diagnosisOf(args) {
4159
+ if (args.evaluation !== void 0) return diagnoseInstance(diagnoseInputFromEvaluation(args.evaluation));
4160
+ const cause = documentStuckCause({
4161
+ instance: args.instance,
4162
+ definition: args.definition
4163
+ });
4164
+ return cause === void 0 ? void 0 : {
4165
+ state: "stuck",
4166
+ cause: cause
4167
+ };
4168
+ }
4169
+
4170
+ function instanceFault(args) {
4171
+ const diagnosis = diagnosisOf(args);
4172
+ if (diagnosis === void 0 || diagnosis.state !== "stuck") return;
4173
+ const verbs = remediationsFor(diagnosis).map(remediation2 => remediation2.verb);
4174
+ return {
4175
+ activity: faultingActivity({
4176
+ cause: diagnosis.cause,
4177
+ definition: args.definition,
4178
+ stageName: args.instance.currentStage
4179
+ }),
4180
+ detail: detailOf(diagnosis.cause),
4181
+ summary: summaryOf(diagnosis.cause),
4182
+ remedies: verbs.flatMap(verb => REMEDY_TEXT[verb] ?? []),
4183
+ actions: verbs.flatMap(verb => isRunnable(verb) ? [ {
4184
+ verb: verb,
4185
+ label: ACTION_LABEL[verb]
4186
+ } ] : []),
4187
+ recoverable: isRecoverable(diagnosis.cause)
4188
+ };
4189
+ }
4190
+
4191
+ function looksLikeDefinition(value) {
4192
+ return typeof value == "object" && value !== null && Array.isArray(value.stages);
4193
+ }
4194
+
4195
+ const parsed = /* @__PURE__ */ new WeakMap;
4196
+
4197
+ function definitionSnapshotOf(instance) {
4198
+ if (parsed.has(instance)) return parsed.get(instance);
4199
+ let definition;
4200
+ try {
4201
+ const candidate = parseDefinitionSnapshot(instance);
4202
+ if (!looksLikeDefinition(candidate)) throw new Error(`definitionSnapshot on "${instance._id}" parsed to a non-definition shape`);
4203
+ definition = candidate;
4204
+ } catch (err) {
4205
+ console.error("[workflow-studio-plugin] unreadable definitionSnapshot:", err), definition = void 0;
4206
+ }
4207
+ return parsed.set(instance, definition), definition;
4208
+ }
4209
+
4210
+ function useDefinition(entry) {
4211
+ return useMemo(() => {
4212
+ if (entry) return entry.evaluation ? entry.evaluation.definition : definitionSnapshotOf(entry.instance);
4213
+ }, [ entry ]);
4214
+ }
4215
+
4216
+ function AbortedBadge({instance: instance}) {
4217
+ return terminalState(instance) !== "aborted" ? null : /* @__PURE__ */ jsx(Badge, {
4218
+ fontSize: 1,
4219
+ tone: "critical",
4220
+ children: "Aborted"
4221
+ });
4222
+ }
4223
+
4224
+ function OlderDefinitionBadge({instance: instance}) {
4225
+ const {latestDefinitionVersions: latestDefinitionVersions} = useWorkflowContext(), latest = latestDefinitionVersions.get(instance.definition);
4226
+ return latest === void 0 || latest <= instance.pinnedVersion ? null : /* @__PURE__ */ jsx(HoverHint, {
4227
+ description: "Running workflows keep the version they started on",
4228
+ text: `Started on v${instance.pinnedVersion}, latest is now v${latest}`,
4229
+ textWeight: "medium",
4230
+ children: /* @__PURE__ */ jsx(Badge, {
4231
+ fontSize: 1,
4232
+ tone: "caution",
4233
+ children: "Older version"
4234
+ })
4235
+ });
4236
+ }
4237
+
4238
+ function useInstanceFault(entry) {
4239
+ return instanceFault({
4240
+ definition: useDefinition(entry),
4241
+ evaluation: entry.evaluation,
4242
+ instance: entry.instance
4243
+ });
4244
+ }
4245
+
4246
+ function InstanceFaultNote({entry: entry, scope: scope}) {
4247
+ const fault = useInstanceFault(entry);
4248
+ if (fault === void 0 || scope.kind === "task" && fault.activity !== scope.activityName) return null;
4249
+ const Icon = fault.recoverable ? WarningOutlineIcon : ErrorOutlineIcon;
4250
+ /* @__PURE__ */
4251
+ return jsx(Card, {
4252
+ padding: 3,
4253
+ radius: 3,
4254
+ tone: fault.recoverable ? "caution" : "critical",
4255
+ children: /* @__PURE__ */ jsxs(Flex, {
4256
+ align: "flex-start",
4257
+ gap: 3,
4258
+ children: [
4259
+ /* @__PURE__ */ jsx(Text, {
4260
+ size: 1,
4261
+ children: /* @__PURE__ */ jsx(Icon, {})
4262
+ }),
4263
+ /* @__PURE__ */ jsxs(Stack, {
4264
+ flex: 1,
4265
+ gap: 3,
4266
+ children: [
4267
+ /* @__PURE__ */ jsx(Text, {
4268
+ size: 1,
4269
+ weight: "medium",
4270
+ children: scope.kind === "run" ? fault.summary : fault.detail
4271
+ }), scope.kind === "run" ? /* @__PURE__ */ jsx(RunControl, {
4272
+ activity: fault.activity,
4273
+ onOpenActivity: scope.onOpenActivity
4274
+ }) : /* @__PURE__ */ jsx(TaskControls, {
4275
+ actions: fault.actions,
4276
+ instanceId: entry.instance._id,
4277
+ remedies: fault.remedies
4278
+ }) ]
4279
+ }) ]
4280
+ })
4281
+ });
4282
+ }
4283
+
4284
+ function RunControl({activity: activity, onOpenActivity: onOpenActivity}) {
4285
+ return activity === void 0 ? null : /* @__PURE__ */ jsx(Flex, {
4286
+ children: /* @__PURE__ */ jsx(Button, {
4287
+ fontSize: 1,
4288
+ mode: "ghost",
4289
+ onClick: () => onOpenActivity(activity),
4290
+ padding: 2,
4291
+ text: "View task"
4292
+ })
4293
+ });
4294
+ }
4295
+
4296
+ const RUN_VERB = {
4297
+ "drain-effects": drain => drain.run()
4298
+ };
4299
+
4300
+ function TaskControls({actions: actions, instanceId: instanceId, remedies: remedies}) {
4301
+ const drain = useDrainEffects(instanceId);
4302
+ /* @__PURE__ */
4303
+ return jsxs(Fragment, {
4304
+ children: [ remedies.length === 0 ? null : /* @__PURE__ */ jsx(Text, {
4305
+ muted: !0,
4306
+ size: 1,
4307
+ children: remedies.join(" ")
4308
+ }), actions.length === 0 ? null : /* @__PURE__ */ jsx(Flex, {
4309
+ gap: 2,
4310
+ children: actions.map(action => /* @__PURE__ */ jsx(Button, {
4311
+ disabled: drain.busy,
4312
+ fontSize: 1,
4313
+ mode: "ghost",
4314
+ onClick: () => {
4315
+ RUN_VERB[action.verb](drain);
4316
+ },
4317
+ padding: 2,
4318
+ text: action.label
4319
+ }, action.verb))
4320
+ }) ]
4321
+ });
4322
+ }
4323
+
4024
4324
  function MetaRow({children: children, fillHeight: fillHeight, label: label}) {
4025
4325
  /* @__PURE__ */
4026
4326
  return jsxs(Flex, {
@@ -4149,7 +4449,7 @@ function useBadgeCapTrim() {
4149
4449
  }
4150
4450
 
4151
4451
  function SpinnerSlot({busy: busy}) {
4152
- const size = useTextIconSize(), trim = useCapTrimFor(size);
4452
+ const size = useTextIconSize(), trim = useCapTrimFor(size), spinning = useDelayedFlag(busy, LOADING_CUE_DELAY_MS);
4153
4453
  /* @__PURE__ */
4154
4454
  return jsx(Flex, {
4155
4455
  align: "center",
@@ -4161,7 +4461,7 @@ function SpinnerSlot({busy: busy}) {
4161
4461
  marginTop: trim,
4162
4462
  width: size
4163
4463
  },
4164
- children: busy ? /* @__PURE__ */ jsx(Spinner, {
4464
+ children: spinning ? /* @__PURE__ */ jsx(Spinner, {
4165
4465
  muted: !0,
4166
4466
  size: 1
4167
4467
  }) : null
@@ -4169,6 +4469,7 @@ function SpinnerSlot({busy: busy}) {
4169
4469
  }
4170
4470
 
4171
4471
  function LoadingRow({label: label, padding: padding}) {
4472
+ const announced = useDelayedFlag(!0, LOADING_CUE_DELAY_MS);
4172
4473
  /* @__PURE__ */
4173
4474
  return jsxs(Flex, {
4174
4475
  align: "center",
@@ -4179,12 +4480,11 @@ function LoadingRow({label: label, padding: padding}) {
4179
4480
  children: [
4180
4481
  /* @__PURE__ */ jsx(SpinnerSlot, {
4181
4482
  busy: !0
4182
- }),
4183
- /* @__PURE__ */ jsx(Text, {
4483
+ }), announced ? /* @__PURE__ */ jsx(Text, {
4184
4484
  muted: !0,
4185
4485
  size: 1,
4186
4486
  children: label
4187
- }) ]
4487
+ }) : null ]
4188
4488
  });
4189
4489
  }
4190
4490
 
@@ -4451,49 +4751,40 @@ function ForMeEmptyState() {
4451
4751
  });
4452
4752
  }
4453
4753
 
4454
- function looksLikeDefinition(value) {
4455
- return typeof value == "object" && value !== null && Array.isArray(value.stages);
4456
- }
4457
-
4458
- const parsed = /* @__PURE__ */ new WeakMap;
4754
+ const OVERDUE_HINT = "Now overdue";
4459
4755
 
4460
- function definitionSnapshotOf(instance) {
4461
- if (parsed.has(instance)) return parsed.get(instance);
4462
- let definition;
4463
- try {
4464
- const candidate = parseDefinitionSnapshot(instance);
4465
- if (!looksLikeDefinition(candidate)) throw new Error(`definitionSnapshot on "${instance._id}" parsed to a non-definition shape`);
4466
- definition = candidate;
4467
- } catch (err) {
4468
- console.error("[workflow-studio-plugin] unreadable definitionSnapshot:", err), definition = void 0;
4469
- }
4470
- return parsed.set(instance, definition), definition;
4471
- }
4472
-
4473
- function useDefinition(entry) {
4474
- return useMemo(() => {
4475
- if (entry) return entry.evaluation ? entry.evaluation.definition : definitionSnapshotOf(entry.instance);
4476
- }, [ entry ]);
4756
+ function hintFace(args) {
4757
+ return args.overdue ? args.reason === void 0 ? {
4758
+ text: OVERDUE_HINT
4759
+ } : {
4760
+ text: OVERDUE_HINT,
4761
+ description: args.reason
4762
+ } : args.reason === void 0 ? void 0 : {
4763
+ text: args.reason
4764
+ };
4477
4765
  }
4478
4766
 
4479
- function AbortedBadge({instance: instance}) {
4480
- return terminalState(instance) !== "aborted" ? null : /* @__PURE__ */ jsx(Badge, {
4481
- fontSize: 1,
4482
- tone: "critical",
4483
- children: "Aborted"
4767
+ function DueDate({dueDate: dueDate, hintDisabled: hintDisabled, overdue: overdue, reason: reason}) {
4768
+ const face = hintFace({
4769
+ overdue: overdue,
4770
+ reason: reason
4771
+ }), text = overdue ?
4772
+ /* @__PURE__ */ jsx(TextWithTone, {
4773
+ size: 1,
4774
+ tone: "caution",
4775
+ children: formatDate(dueDate)
4776
+ }) : /* @__PURE__ */ jsx(Text, {
4777
+ muted: !0,
4778
+ size: 1,
4779
+ children: formatDate(dueDate)
4484
4780
  });
4485
- }
4486
-
4487
- function OlderDefinitionBadge({instance: instance}) {
4488
- const {latestDefinitionVersions: latestDefinitionVersions} = useWorkflowContext(), latest = latestDefinitionVersions.get(instance.definition);
4489
- return latest === void 0 || latest <= instance.pinnedVersion ? null : /* @__PURE__ */ jsx(HoverHint, {
4490
- description: "Running workflows keep the version they started on",
4491
- text: `Started on v${instance.pinnedVersion}, latest is now v${latest}`,
4492
- textWeight: "medium",
4493
- children: /* @__PURE__ */ jsx(Badge, {
4494
- fontSize: 1,
4495
- tone: "caution",
4496
- children: "Older version"
4781
+ return face === void 0 ? text :
4782
+ /* @__PURE__ */ jsx(Flex, {
4783
+ align: "center",
4784
+ children: /* @__PURE__ */ jsx(HoverHint, {
4785
+ ...face,
4786
+ disabled: hintDisabled === !0,
4787
+ children: text
4497
4788
  })
4498
4789
  });
4499
4790
  }
@@ -4533,18 +4824,6 @@ function CheckboxSlot({children: children}) {
4533
4824
  });
4534
4825
  }
4535
4826
 
4536
- function DueDateText({dueDate: dueDate, overdue: overdue}) {
4537
- return overdue ? /* @__PURE__ */ jsx(TextWithTone, {
4538
- size: 1,
4539
- tone: "caution",
4540
- children: formatDate(dueDate)
4541
- }) : /* @__PURE__ */ jsx(Text, {
4542
- muted: !0,
4543
- size: 1,
4544
- children: formatDate(dueDate)
4545
- });
4546
- }
4547
-
4548
4827
  function DueDateEditor({dueDate: dueDate, onDone: onDone, onPatch: onPatch}) {
4549
4828
  /* @__PURE__ */
4550
4829
  return jsx(ClearableDatePicker, {
@@ -4564,8 +4843,9 @@ function DueDateEditor({dueDate: dueDate, onDone: onDone, onPatch: onPatch}) {
4564
4843
  }
4565
4844
 
4566
4845
  function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4567
- const [open, setOpen] = useState(!1), overdue = isTodoOverdue(item), display = item.dueDate ? /* @__PURE__ */ jsx(DueDateText, {
4846
+ const [open, setOpen] = useState(!1), overdue = isTodoOverdue(item), display = item.dueDate ? /* @__PURE__ */ jsx(DueDate, {
4568
4847
  dueDate: item.dueDate,
4848
+ hintDisabled: open,
4569
4849
  overdue: overdue
4570
4850
  }) : null;
4571
4851
  return editable ? /* @__PURE__ */ jsx(DismissablePopover, {
@@ -4576,18 +4856,14 @@ function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4576
4856
  }),
4577
4857
  onDismiss: () => setOpen(!1),
4578
4858
  open: open,
4579
- children: display ? /* @__PURE__ */ jsx(HoverHint, {
4580
- disabled: !overdue || open,
4581
- text: "Now overdue",
4582
- children: /* @__PURE__ */ jsx(Button, {
4583
- "aria-label": "Change due date",
4584
- fontSize: 1,
4585
- mode: "bleed",
4586
- onClick: () => setOpen(v => !v),
4587
- padding: 2,
4588
- tone: overdue ? "caution" : "default",
4589
- children: display
4590
- })
4859
+ children: display ? /* @__PURE__ */ jsx(Button, {
4860
+ "aria-label": "Change due date",
4861
+ fontSize: 1,
4862
+ mode: "bleed",
4863
+ onClick: () => setOpen(v => !v),
4864
+ padding: 2,
4865
+ tone: overdue ? "caution" : "default",
4866
+ children: display
4591
4867
  }) : /* @__PURE__ */ jsx(HoverHint, {
4592
4868
  disabled: open,
4593
4869
  text: "Set due date",
@@ -4601,9 +4877,6 @@ function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4601
4877
  radius: 2
4602
4878
  })
4603
4879
  })
4604
- }) : display && overdue ? /* @__PURE__ */ jsx(HoverHint, {
4605
- text: "Now overdue",
4606
- children: display
4607
4880
  }) : display;
4608
4881
  }
4609
4882
 
@@ -5256,9 +5529,15 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
5256
5529
  const detail = deriveActivityDetail({
5257
5530
  entry: entry,
5258
5531
  activityName: activityName
5259
- });
5532
+ }), faultingActivity2 = useInstanceFault(entry)?.activity;
5260
5533
  if (!detail) return null;
5261
- const instanceId = entry.instance._id, notice = blockedNotice(detail), hasBody = detail.state.length > 0 || notice !== void 0;
5534
+ const instanceId = entry.instance._id, notice = blockedNotice(detail), fault = /* @__PURE__ */ jsx(InstanceFaultNote, {
5535
+ entry: entry,
5536
+ scope: {
5537
+ kind: "task",
5538
+ activityName: activityName
5539
+ }
5540
+ }), faulted = faultingActivity2 === activityName, hasBody = detail.state.length > 0 || notice !== void 0 || faulted;
5262
5541
  /* @__PURE__ */
5263
5542
  return jsxs(Dialog, {
5264
5543
  footer: detail.presentation.terminalActions.length > 0 ? /* @__PURE__ */ jsx(TerminalFooter, {
@@ -5299,7 +5578,7 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
5299
5578
  padding: 4,
5300
5579
  children: /* @__PURE__ */ jsxs(Stack, {
5301
5580
  gap: 5,
5302
- children: [ detail.state.map(field => /* @__PURE__ */ jsx(FieldRow, {
5581
+ children: [ fault, detail.state.map(field => /* @__PURE__ */ jsx(FieldRow, {
5303
5582
  activityName: activityName,
5304
5583
  detail: detail,
5305
5584
  entry: entry,
@@ -5480,6 +5759,18 @@ function CountedLabel({count: count, label: label}) {
5480
5759
  });
5481
5760
  }
5482
5761
 
5762
+ function CountedHeading({count: count, title: title}) {
5763
+ /* @__PURE__ */
5764
+ return jsx(CountedLabel, {
5765
+ count: count,
5766
+ label: /* @__PURE__ */ jsx(Text, {
5767
+ size: 1,
5768
+ weight: "semibold",
5769
+ children: title
5770
+ })
5771
+ });
5772
+ }
5773
+
5483
5774
  function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5484
5775
  /* @__PURE__ */
5485
5776
  return jsxs(Flex, {
@@ -5488,13 +5779,9 @@ function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5488
5779
  paddingLeft: 2,
5489
5780
  paddingY: end === void 0 ? 3 : 1,
5490
5781
  children: [
5491
- /* @__PURE__ */ jsx(CountedLabel, {
5782
+ /* @__PURE__ */ jsx(CountedHeading, {
5492
5783
  count: count,
5493
- label: /* @__PURE__ */ jsx(Text, {
5494
- size: 1,
5495
- weight: "semibold",
5496
- children: title
5497
- })
5784
+ title: title
5498
5785
  }), busy === void 0 ? null : /* @__PURE__ */ jsx(SpinnerSlot, {
5499
5786
  busy: busy
5500
5787
  }), end === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, {
@@ -5506,21 +5793,81 @@ function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5506
5793
  });
5507
5794
  }
5508
5795
 
5796
+ const EMPTY_METRIC_OPACITY = .5;
5797
+
5798
+ function MetricText({children: children, tone: tone}) {
5799
+ return tone === void 0 ? /* @__PURE__ */ jsx(Text, {
5800
+ muted: !0,
5801
+ size: 1,
5802
+ children: children
5803
+ }) : /* @__PURE__ */ jsx(TextWithTone, {
5804
+ size: 1,
5805
+ tone: tone,
5806
+ children: children
5807
+ });
5808
+ }
5809
+
5810
+ function MetricPair({Icon: Icon, empty: empty, face: face, tone: tone, value: value}) {
5811
+ /* @__PURE__ */
5812
+ return jsx(HoverHint, {
5813
+ ...face,
5814
+ children: /* @__PURE__ */ jsxs(Flex, {
5815
+ align: "center",
5816
+ gap: 2,
5817
+ style: empty ? {
5818
+ opacity: EMPTY_METRIC_OPACITY
5819
+ } : void 0,
5820
+ children: [
5821
+ /* @__PURE__ */ jsx(MetricText, {
5822
+ tone: tone,
5823
+ children: /* @__PURE__ */ jsx(Icon, {
5824
+ style: {
5825
+ color: "inherit"
5826
+ }
5827
+ })
5828
+ }),
5829
+ /* @__PURE__ */ jsx(MetricText, {
5830
+ tone: tone,
5831
+ children: value
5832
+ }) ]
5833
+ })
5834
+ });
5835
+ }
5836
+
5837
+ function AlertGlyph({Icon: Icon, hint: hint, tone: tone}) {
5838
+ /* @__PURE__ */
5839
+ return jsx(HoverHint, {
5840
+ text: hint,
5841
+ children: /* @__PURE__ */ jsx(TextWithTone, {
5842
+ size: 1,
5843
+ tone: tone,
5844
+ children: /* @__PURE__ */ jsx(Icon, {
5845
+ style: {
5846
+ color: "inherit"
5847
+ }
5848
+ })
5849
+ })
5850
+ });
5851
+ }
5852
+
5509
5853
  function ActivityDateControl({instanceId: instanceId, state: state, surface: surface, dueDates: dueDates}) {
5510
5854
  const editField = useEditField(surface), {save: save} = useSaveField({
5511
5855
  instanceId: instanceId
5512
5856
  }), [open, setOpen] = useState(!1), raw = dateControlValue({
5513
5857
  state: state,
5514
5858
  dueDates: dueDates
5515
- }), display = raw === void 0 ? null : /* @__PURE__ */ jsx(Text, {
5516
- muted: !0,
5517
- size: 1,
5518
- children: formatDate(raw)
5859
+ }), overdue = raw !== void 0 && isDueDatePast(raw, /* @__PURE__ */ new Date), dueDate = reason => raw === void 0 ? null : /* @__PURE__ */ jsx(DueDate, {
5860
+ dueDate: raw,
5861
+ hintDisabled: open,
5862
+ overdue: overdue,
5863
+ ...reason === void 0 ? {} : {
5864
+ reason: reason
5865
+ }
5519
5866
  });
5520
- if (state.kind === "none") return display;
5521
- if (state.kind === "closed") return display === null ? null : /* @__PURE__ */ jsx(HoverHint, {
5522
- text: state.reason,
5523
- children: /* @__PURE__ */ jsx(Button, {
5867
+ if (state.kind === "none") return dueDate();
5868
+ if (state.kind === "closed") {
5869
+ const display = dueDate(state.reason);
5870
+ return display === null ? null : /* @__PURE__ */ jsx(Button, {
5524
5871
  "aria-label": "Date",
5525
5872
  as: "span",
5526
5873
  fontSize: 1,
@@ -5529,8 +5876,8 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5529
5876
  onMouseDown: stopRowMouseDown,
5530
5877
  padding: 2,
5531
5878
  children: display
5532
- })
5533
- });
5879
+ });
5880
+ }
5534
5881
  const kind = dateControlKind(state), commit = (mode, value) => {
5535
5882
  state.kind === "editable" && save(() => editField({
5536
5883
  instanceId: instanceId,
@@ -5566,7 +5913,7 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5566
5913
  }),
5567
5914
  onDismiss: () => setOpen(!1),
5568
5915
  open: open,
5569
- children: display ? /* @__PURE__ */ jsx(Button, {
5916
+ children: raw !== void 0 ? /* @__PURE__ */ jsx(Button, {
5570
5917
  "aria-label": "Change date",
5571
5918
  as: "span",
5572
5919
  fontSize: 1,
@@ -5574,7 +5921,7 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5574
5921
  onClick: handleClick,
5575
5922
  onMouseDown: stopRowMouseDown,
5576
5923
  padding: 2,
5577
- children: display
5924
+ children: dueDate()
5578
5925
  }) : /* @__PURE__ */ jsx(HoverHint, {
5579
5926
  disabled: open,
5580
5927
  text: "Set date",
@@ -5664,7 +6011,7 @@ function TitleTag({hint: hint, tone: tone, children: children}) {
5664
6011
  });
5665
6012
  }
5666
6013
 
5667
- function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees, assignState: assignState, dateState: dateState, dueDates: dueDates, instanceId: instanceId, onOpen: onOpen, surface: surface, terminalActions: terminalActions = []}) {
6014
+ function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees, assignState: assignState, dateState: dateState, dueDates: dueDates, faultHint: faultHint, instanceId: instanceId, onOpen: onOpen, surface: surface, terminalActions: terminalActions = []}) {
5668
6015
  const settled = isTerminalActivityStatus(face.status), dimmed = settledDim(settled);
5669
6016
  /* @__PURE__ */
5670
6017
  return jsxs(WorkRow, {
@@ -5697,6 +6044,10 @@ function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees,
5697
6044
  /* @__PURE__ */ jsx(ActivityRowTitle, {
5698
6045
  dimmed: dimmed,
5699
6046
  children: face.title
6047
+ }), faultHint === void 0 || face.status === "failed" ? null : /* @__PURE__ */ jsx(AlertGlyph, {
6048
+ Icon: ErrorOutlineIcon,
6049
+ hint: faultHint,
6050
+ tone: "critical"
5700
6051
  }), face.automated ? /* @__PURE__ */ jsx(TitleTag, {
5701
6052
  hint: "Completes on its own — no user action is needed",
5702
6053
  children: "Automated"
@@ -5712,7 +6063,7 @@ function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees,
5712
6063
  }
5713
6064
 
5714
6065
  function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity, surface: surface}) {
5715
- const activities = (entry.evaluation?.currentStage.activities ?? []).filter(t => !t.scopedOut), activityState = stateByActivity(entry.instance);
6066
+ const activities = (entry.evaluation?.currentStage.activities ?? []).filter(t => !t.scopedOut), activityState = stateByActivity(entry.instance), fault = useInstanceFault(entry);
5716
6067
  return activities.length === 0 ?
5717
6068
  /* @__PURE__ */ jsx(Box, {
5718
6069
  paddingX: 1,
@@ -5730,6 +6081,9 @@ function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity, surface:
5730
6081
  activityState: activityState,
5731
6082
  editableFields: entry.evaluation?.editableFields
5732
6083
  }),
6084
+ ...fault?.activity === t.activity.name ? {
6085
+ faultHint: fault.summary
6086
+ } : {},
5733
6087
  instanceId: entry.instance._id,
5734
6088
  onOpen: () => onOpenActivity(t.activity.name),
5735
6089
  surface: surface
@@ -5876,7 +6230,10 @@ function progressFillPercent(face) {
5876
6230
  }
5877
6231
 
5878
6232
  function deriveFieldPills(args) {
5879
- const {scope: scope, definition: definition, instance: instance, committed: committed, evaluation: evaluation} = args, declared = scope === "workflow" ? definition?.fields ?? [] : definition?.stages.find(s => s.name === instance.currentStage)?.fields ?? [], entries = scope === "workflow" ? instance.fields : findOpenStageEntry(instance)?.fields ?? [], entriesByName = new Map(entries.map(entry => [ entry.name, entry ])), editables = isEvaluationCurrent({
6233
+ const {scope: scope, definition: definition, instance: instance, committed: committed, evaluation: evaluation} = args, declared = scope === "workflow" ? definition?.fields ?? [] : findStageNode({
6234
+ definition: definition,
6235
+ stageName: instance.currentStage
6236
+ })?.fields ?? [], entries = scope === "workflow" ? instance.fields : findOpenStageEntry(instance)?.fields ?? [], entriesByName = new Map(entries.map(entry => [ entry.name, entry ])), editables = isEvaluationCurrent({
5880
6237
  committed: committed,
5881
6238
  evaluation: evaluation
5882
6239
  }) ? editablesByName(evaluation, scope) : /* @__PURE__ */ new Map, declaredNames = new Set(declared.map(d => d.name));
@@ -7066,6 +7423,27 @@ function HintedMenuButton({hint: hint, ...menuButtonProps}) {
7066
7423
  });
7067
7424
  }
7068
7425
 
7426
+ function InlineEmphasis({children: children}) {
7427
+ const {font: font} = useTheme_v2();
7428
+ /* @__PURE__ */
7429
+ return jsx("span", {
7430
+ style: {
7431
+ fontWeight: font.text.weights.medium
7432
+ },
7433
+ children: children
7434
+ });
7435
+ }
7436
+
7437
+ const MINUTE_MS = 6e4;
7438
+
7439
+ function useMinuteClock() {
7440
+ const [now, setNow] = useState(() => /* @__PURE__ */ new Date);
7441
+ return useEffect(() => {
7442
+ const timer = setInterval(() => setNow(/* @__PURE__ */ new Date), MINUTE_MS);
7443
+ return () => clearInterval(timer);
7444
+ }, []), now;
7445
+ }
7446
+
7069
7447
  function historyWindow(args) {
7070
7448
  const {entries: entries, expanded: expanded} = args;
7071
7449
  return {
@@ -7075,28 +7453,10 @@ function historyWindow(args) {
7075
7453
  }
7076
7454
 
7077
7455
  function PendingEffectRows({instance: instance, now: now}) {
7078
- const {drainEffectsFor: drainEffectsFor, completeEffectFor: completeEffectFor, effectHandlers: effectHandlers} = useWorkflowContext(), toast = useWorkflowToast(), [busy, setBusy] = useState(!1), pending = instance.pendingEffects ?? [];
7456
+ const {completeEffectFor: completeEffectFor, effectHandlers: effectHandlers} = useWorkflowContext(), toast = useWorkflowToast(), drain = useDrainEffects(instance._id), [resolving, setResolving] = useState(!1), pending = instance.pendingEffects ?? [];
7079
7457
  if (pending.length === 0) return null;
7080
- const runRegistered = async () => {
7081
- setBusy(!0);
7082
- try {
7083
- await drainEffectsFor(instance._id), toast.push({
7084
- id: TOAST_ID.effectsDrain,
7085
- status: "info",
7086
- title: "Ran the registered handlers"
7087
- });
7088
- } catch (err) {
7089
- toast.push({
7090
- id: TOAST_ID.effectsDrain,
7091
- status: "error",
7092
- title: "Failed to run pending effects",
7093
- description: describeError(err)
7094
- });
7095
- } finally {
7096
- setBusy(!1);
7097
- }
7098
- }, resolve = async (effectKey, status) => {
7099
- setBusy(!0);
7458
+ const resolve = async (effectKey, status) => {
7459
+ setResolving(!0);
7100
7460
  try {
7101
7461
  await completeEffectFor(instance._id, {
7102
7462
  effectKey: effectKey,
@@ -7115,18 +7475,20 @@ function PendingEffectRows({instance: instance, now: now}) {
7115
7475
  description: describeError(err)
7116
7476
  });
7117
7477
  } finally {
7118
- setBusy(!1);
7478
+ setResolving(!1);
7119
7479
  }
7120
7480
  };
7121
7481
  /* @__PURE__ */
7122
7482
  return jsx(Fragment, {
7123
7483
  children: pending.map(fx => /* @__PURE__ */ jsx(PendingEffectRow, {
7124
- busy: busy,
7484
+ busy: drain.busy || resolving,
7125
7485
  effect: fx,
7126
7486
  instanceId: instance._id,
7127
7487
  now: now,
7128
7488
  onResolve: status => resolve(fx._key, status),
7129
- onRunHandlers: runRegistered,
7489
+ onRunHandlers: () => {
7490
+ drain.run();
7491
+ },
7130
7492
  registered: fx.name in effectHandlers
7131
7493
  }, fx._key))
7132
7494
  });
@@ -7210,12 +7572,7 @@ function PendingEffectRow({busy: busy, effect: effect, instanceId: instanceId, n
7210
7572
  }
7211
7573
 
7212
7574
  function ActivityLog({instance: instance, definition: definition, windowed: windowed = !0}) {
7213
- const titles = useMemo(() => makeTitleResolver(definition), [ definition ]), entries = useMemo(() => instance.history.toReversed(), [ instance.history ]), [now, setNow] = useState(() => /* @__PURE__ */ new Date), [expanded, setExpanded] = useState(!1);
7214
- useEffect(() => {
7215
- const timer = setInterval(() => setNow(/* @__PURE__ */ new Date), 6e4);
7216
- return () => clearInterval(timer);
7217
- }, []);
7218
- const history = historyWindow({
7575
+ const titles = useMemo(() => makeTitleResolver(definition), [ definition ]), entries = useMemo(() => instance.history.toReversed(), [ instance.history ]), now = useMinuteClock(), [expanded, setExpanded] = useState(!1), history = historyWindow({
7219
7576
  entries: entries,
7220
7577
  expanded: expanded || !windowed
7221
7578
  });
@@ -7318,12 +7675,9 @@ function HistoryRow({entry: entry, now: now, titles: titles}) {
7318
7675
  }
7319
7676
 
7320
7677
  function ActorNameSpan({id: id}) {
7321
- const {name: name} = useUserDisplay(id), {font: font} = useTheme_v2();
7678
+ const {name: name} = useUserDisplay(id);
7322
7679
  /* @__PURE__ */
7323
- return jsx("span", {
7324
- style: {
7325
- fontWeight: font.text.weights.medium
7326
- },
7680
+ return jsx(InlineEmphasis, {
7327
7681
  children: name
7328
7682
  });
7329
7683
  }
@@ -7731,7 +8085,6 @@ function StageOverviewCard({overview: overview}) {
7731
8085
  }
7732
8086
 
7733
8087
  function AdvanceCard({advance: advance}) {
7734
- const {font: font} = useTheme_v2();
7735
8088
  /* @__PURE__ */
7736
8089
  return jsx(Card, {
7737
8090
  padding: 3,
@@ -7743,10 +8096,7 @@ function AdvanceCard({advance: advance}) {
7743
8096
  /* @__PURE__ */ jsxs(Text, {
7744
8097
  size: 1,
7745
8098
  children: [ "Advances to ",
7746
- /* @__PURE__ */ jsx("span", {
7747
- style: {
7748
- fontWeight: font.text.weights.medium
7749
- },
8099
+ /* @__PURE__ */ jsx(InlineEmphasis, {
7750
8100
  children: advance.toTitle
7751
8101
  }), ADVANCE_TAILS[advance.kind] ]
7752
8102
  }), advance.kind !== "conditions" ? null : advance.lines.map((line, index) =>
@@ -8854,7 +9204,7 @@ function DiscoverySpinner() {
8854
9204
  });
8855
9205
  }
8856
9206
 
8857
- const BOOT_CUE_DELAY_MS = 400, GROUP_GAP = 2;
9207
+ const GROUP_GAP = 2;
8858
9208
 
8859
9209
  function useWorkflowsTab() {
8860
9210
  const {params: params, setParams: setParams} = usePaneRouter();
@@ -8879,7 +9229,7 @@ function useOpenActivity(entries) {
8879
9229
  }
8880
9230
 
8881
9231
  function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappings: docTypeMappings, initialValue: initialValue}) {
8882
- const identity = useAssignmentIdentity(), panelId = useId(), {params: params} = usePaneRouter(), {view: view, setView: setView} = useWorkflowsTab(), telemetry = useWorkflowTelemetry(), {openActivity: openActivity, setOpenActivity: setOpenActivity, dialogEntry: dialogEntry} = useOpenActivity(entries), active = entries.filter(isLiveEntry), finished = entries.filter(e => !isLiveEntry(e)), forMe = forMeWorkOf(active, identity), [landing] = useState(() => focusedLanding(params?.[WORKFLOWS_FOCUS_PARAM], entries.map(e => e.instance._id))), assignedWorkUnknown = active.some(e => e.invalid !== void 0), booting = useDelayedFlag(active.some(e => !e.ready && e.invalid === void 0), BOOT_CUE_DELAY_MS), sectionFor = e => /* @__PURE__ */ jsx(WorkflowInstanceSection, {
9232
+ const identity = useAssignmentIdentity(), panelId = useId(), {params: params} = usePaneRouter(), {view: view, setView: setView} = useWorkflowsTab(), telemetry = 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] = 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__ */ jsx(WorkflowInstanceSection, {
8883
9233
  defaultOpen: landingDefaultOpen(landing, e.instance._id),
8884
9234
  entry: e,
8885
9235
  onOpenActivity: activityName => setOpenActivity({
@@ -10822,6 +11172,28 @@ function unassignedLineHint(args) {
10822
11172
  };
10823
11173
  }
10824
11174
 
11175
+ function taskCountState(args) {
11176
+ const {entry: entry, identity: identity} = args, freshness = entry.invalid === void 0 ? evaluationFreshness({
11177
+ committed: entry.committed,
11178
+ evaluation: entry.evaluation
11179
+ }) : void 0;
11180
+ return identity.kind === "unavailable" ? {
11181
+ kind: "unknowable"
11182
+ } : identity.kind === "pending" || freshness === "pending" ? {
11183
+ kind: "pending"
11184
+ } : freshness === "stale" ? {
11185
+ kind: "stale"
11186
+ } : {
11187
+ kind: "ready",
11188
+ tasks: assignedTaskCount({
11189
+ instance: entry.instance,
11190
+ committed: entry.committed,
11191
+ evaluation: entry.evaluation,
11192
+ who: identity.identity
11193
+ })
11194
+ };
11195
+ }
11196
+
10825
11197
  function WorkflowFormStrip(props) {
10826
11198
  const {mappings: mappings} = props, {isDocResolved: isDocResolved, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {docId: docId, entries: entries} = useWorkflowsForDocument(props.value?._id ?? null), {params: params, setParams: setParams} = usePaneRouter(), telemetry = useWorkflowTelemetry(), openWorkflowsView = focusId => {
10827
11199
  telemetry.log(WorkflowFormStripClicked), setParams(focusedViewParams(params, focusId));
@@ -10909,7 +11281,7 @@ function InstanceLine({entry: entry, onOpen: onOpen}) {
10909
11281
  aside: unprimed ? void 0 : /* @__PURE__ */ jsx(TaskCountSpinner, {
10910
11282
  entry: entry
10911
11283
  }),
10912
- hint: lineHint(entry, identity),
11284
+ hint: lineHint(entry, identityOf(identity)),
10913
11285
  onOpen: onOpen,
10914
11286
  children: [
10915
11287
  /* @__PURE__ */ jsx(Flex, {
@@ -10968,28 +11340,16 @@ function finishedHint(instance) {
10968
11340
  }
10969
11341
 
10970
11342
  function useTaskCountState(entry) {
10971
- const identity = useAssignmentIdentity(), freshness = entry.invalid === void 0 ? evaluationFreshness({
10972
- committed: entry.committed,
10973
- evaluation: entry.evaluation
10974
- }) : void 0;
10975
- return !identity || freshness === "pending" ? {
10976
- kind: "pending"
10977
- } : freshness === "stale" ? {
10978
- kind: "stale"
10979
- } : {
10980
- kind: "ready",
10981
- tasks: assignedTaskCount({
10982
- instance: entry.instance,
10983
- committed: entry.committed,
10984
- evaluation: entry.evaluation,
10985
- who: identity
10986
- })
10987
- };
11343
+ const identity = useAssignmentIdentity();
11344
+ return taskCountState({
11345
+ entry: entry,
11346
+ identity: identity
11347
+ });
10988
11348
  }
10989
11349
 
10990
11350
  function TaskCountText({entry: entry}) {
10991
11351
  const state = useTaskCountState(entry);
10992
- return state.kind === "pending" || state.kind === "ready" && state.tasks === 0 ? null : /* @__PURE__ */ jsx(Text, {
11352
+ return state.kind === "pending" || state.kind === "unknowable" || state.kind === "ready" && state.tasks === 0 ? null : /* @__PURE__ */ jsx(Text, {
10993
11353
  muted: !0,
10994
11354
  size: 1,
10995
11355
  children: state.kind === "stale" ? "Updating…" : pluralize("assigned task", state.tasks, !0)
@@ -11220,4 +11580,4 @@ function WorkflowRootInput(props) {
11220
11580
  });
11221
11581
  }
11222
11582
 
11223
- export { AbortWorkflowDialog, ActivityDetailDialog, ActivityLog, ActivityRow, BreadcrumbTail, CodeChip, CountedLabel, DismissablePopover, DocPreviewLink, DocRefFace, EmptyState, ForMeEmptyState, Hairline, HoverHint, InstanceSnapshotBody, InvalidDocNotice, LinkChip, LoadingRow, LogEventOnMount, MetaRow, NoteBanner, RoleAssignedNotice, SpinnerSlot, StageFace, StaleLock, TOAST_ID, TOOL_TABS, TabSwitch, UnreadableDocsNote, WORKFLOW_API_VERSION, WORKFLOW_PAGE_TABS, WorkflowBoardWorkflowSelected, WorkflowDefinitionDetailViewed, WorkflowInstanceDetailViewed, WorkflowTaskFiltersApplied, WorkflowTitleSeedDrifted, WorkflowToolOpened, activityRowProps, assigneesOf, backToTabLabel, committedRowFace, createSubscribers, datesOf, definitionFingerprint, definitionSnapshotOf, describeError, dueDatesOf, findActivity, findActivityNode, formatDate, formatShortDateTime, formatTimeAgo, gdrLocality, instanceBreadcrumb, instanceState, instanceTitle, isActivityAssignedTo, isEvaluationStale, isLiveEntry, isOpenActivityStatus, isRoleAssignedTo, landingState, mappingIssueDetail, namesNoDeployedDefinition, openActivityGone, openableSchemaType, parseStoredDateValue, readDeployedDefinitions, rowAssignControlState, rowDateControlState, stageTitle, stateByActivity, tabSelectionNavigates, toolRoute, toolTabState, useAssignmentIdentity, useBadgeCapTrim, useContainerToken, useDefinition, useDelayedFlag, useLogEventOnMount, useProjectMembers, useSpaceToken, useUserDisplays, useWorkflowContext, useWorkflowInstanceEntry, useWorkflowToast, workflowDefaultDocumentNode, workflowPageState, workflowState, workflowStudioPlugin, workflowsView };
11583
+ export { AbortWorkflowDialog, ActivityDetailDialog, ActivityLog, ActivityRow, AlertGlyph, BreadcrumbTail, CodeChip, CountedHeading, DismissablePopover, DocPreviewLink, DocRefFace, EmptyState, ForMeEmptyState, Hairline, HoverHint, InlineEmphasis, InstanceFaultNote, InstanceSnapshotBody, InvalidDocNotice, LinkChip, LoadingRow, LogEventOnMount, MetaRow, MetricPair, NoteBanner, RoleAssignedNotice, SpinnerSlot, StageFace, StaleLock, TOAST_ID, TOOL_TABS, TabSwitch, UnreadableDocsNote, WORKFLOW_API_VERSION, WORKFLOW_PAGE_TABS, WorkflowBoardWorkflowSelected, WorkflowDefinitionDetailViewed, WorkflowInstanceDetailViewed, WorkflowTaskFiltersApplied, WorkflowTitleSeedDrifted, WorkflowToolOpened, activityRowProps, assigneesOf, backToTabLabel, committedRowFace, createSubscribers, datesOf, definitionFingerprint, definitionSnapshotOf, describeError, dueDatesOf, findActivity, formatDate, formatShortAgo, formatShortDateTime, formatTimeAgo, gdrLocality, identityOf, instanceBreadcrumb, instanceState, instanceTitle, isActivityAssignedTo, isDueDatePast, isEvaluationStale, isLiveEntry, isOpenActivityStatus, isRoleAssignedTo, landingState, mappingIssueDetail, namesNoDeployedDefinition, openActivityGone, openableSchemaType, parseStoredDateValue, readDeployedDefinitions, rowAssignControlState, rowDateControlState, stageTitle, stateByActivity, tabSelectionNavigates, toolRoute, toolTabState, useAssignmentIdentity, useContainerToken, useDefinition, useLogEventOnMount, useMinuteClock, useProjectMembers, useSpaceToken, useUserDisplays, useWorkflowContext, useWorkflowInstanceEntry, useWorkflowToast, workflowDefaultDocumentNode, workflowPageState, workflowState, workflowStudioPlugin, workflowsView };