@sanity/workflow-studio-plugin 0.28.0 → 0.30.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
 
@@ -18,7 +18,7 @@ import { CloseIcon } from "@sanity/icons/Close";
18
18
 
19
19
  import { formatDistanceToNow } from "date-fns/formatDistanceToNow";
20
20
 
21
- import { sameAssignee, projectMemberRow, ClearableDatePicker, AssigneePicker as AssigneePicker$1, MemberPicker as MemberPicker$1, MemberAvatar, AssigneeBadges as AssigneeBadges$1, AssigneeBadge as AssigneeBadge$1, ProgressBar, memberRoleFor, AssigneeStack, roleLabel, clampProgress, MemberAvatarGroup, progressCaption, DatePicker, roleLabelFor, roleListLabel } from "@sanity/workflow-components";
21
+ import { sameAssignee, projectMemberRow, providerTitle, ClearableDatePicker, AssigneePicker as AssigneePicker$1, MemberPicker as MemberPicker$1, MemberAvatar, AssigneeBadges as AssigneeBadges$1, AssigneeBadge as AssigneeBadge$1, ProgressBar, memberRoleFor, AssigneeStack, roleLabel, clampProgress, MemberAvatarGroup, progressCaption, DatePicker, roleLabelFor, roleListLabel } from "@sanity/workflow-components";
22
22
 
23
23
  import { getPublishedId, getDraftId } from "@sanity/client/csm";
24
24
 
@@ -26,7 +26,7 @@ import { defineEvent } from "@sanity/telemetry";
26
26
 
27
27
  import { useStudioProjectUsers, useStudioObserver, useWorkflowEngine, useWorkflowInstances, useWorkflowSession } from "@sanity/workflow-studio";
28
28
 
29
- import { useCurrentUser, Preview, SanityDefaultPreview, useValuePreview, useClient, useSchema, useWorkspace, TextWithTone, usePerspective, useIsReleaseActive, useDocumentPreviewStore, useActiveReleases, useTemplates, useInitialValueResolverContext, resolveInitialValue, definePlugin, isObjectInputProps } from "sanity";
29
+ import { useCurrentUser, Preview, useValuePreview, SanityDefaultPreview, useClient, useSchema, useWorkspace, TextWithTone, usePerspective, useIsReleaseActive, useDocumentPreviewStore, useActiveReleases, useTemplates, useInitialValueResolverContext, resolveInitialValue, definePlugin, isObjectInputProps } from "sanity";
30
30
 
31
31
  import { CalendarIcon } from "@sanity/icons/Calendar";
32
32
 
@@ -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";
@@ -367,7 +367,7 @@ function landingDefaultOpen(landing, instanceId) {
367
367
  if (!(landing === void 0 || !landing.instanceIds.includes(instanceId))) return instanceId === landing.focusedId;
368
368
  }
369
369
 
370
- const WORKFLOWS_TOOL_NAME = "workflows", WORKFLOW_INTENT = "workflow";
370
+ const WORKFLOWS_TOOL_NAME = "workflows", WORKFLOW_INTENT = "workflow", RUN_SEARCH_PARAM = "run";
371
371
 
372
372
  function pathSegmentName(args) {
373
373
  return args.names.find(name => name === args.segment);
@@ -379,9 +379,10 @@ function handlesWorkflowIntent(intent, params) {
379
379
 
380
380
  function workflowIntentState(intent, params) {
381
381
  const instanceId = params.instanceId;
382
- return intent === WORKFLOW_INTENT && typeof instanceId == "string" ? {
383
- instanceId: instanceId
384
- } : null;
382
+ return intent !== WORKFLOW_INTENT || typeof instanceId != "string" ? null : {
383
+ workflowsTab: "overview",
384
+ _searchParams: [ [ RUN_SEARCH_PARAM, instanceId ] ]
385
+ };
385
386
  }
386
387
 
387
388
  const pad2 = n => String(n).padStart(2, "0"), DUE_DATE_FIELD_KINDS = [ "dueDate", "dueDatetime" ], DATE_FIELD_KINDS = [ "date", "datetime", ...DUE_DATE_FIELD_KINDS ], DATE_FIELD_KIND_SET = new Set(DATE_FIELD_KINDS), DUE_DATE_FIELD_KIND_SET = new Set(DUE_DATE_FIELD_KINDS);
@@ -409,16 +410,25 @@ function parseLocalDate(value) {
409
410
  return parsed2.getFullYear() === year && parsed2.getMonth() === (month ?? NaN) - 1 && parsed2.getDate() === day ? parsed2 : /* @__PURE__ */ new Date(NaN);
410
411
  }
411
412
 
413
+ function storedDateKind(value) {
414
+ return /^\d{4}-\d{2}-\d{2}$/.test(value) ? "date" : "datetime";
415
+ }
416
+
412
417
  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);
418
+ if (typeof value == "string") return parseDateFieldValue(value, storedDateKind(value));
416
419
  }
417
420
 
418
421
  function serializeDateFieldValue(date, kind) {
419
422
  return hasTimeOfDay(kind) ? date.toISOString() : `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
420
423
  }
421
424
 
425
+ function isDueDatePast(value, now) {
426
+ const kind = storedDateKind(value);
427
+ if (!hasTimeOfDay(kind)) return value < serializeDateFieldValue(now, kind);
428
+ const due = parseDateFieldValue(value, kind);
429
+ return due !== void 0 && due.getTime() < now.getTime();
430
+ }
431
+
422
432
  class EffectsIncompleteError extends Error {
423
433
  reason;
424
434
  constructor(reason, options) {
@@ -469,14 +479,19 @@ function formatTimeAgo(value) {
469
479
  }
470
480
 
471
481
  function formatShortAgo(value, now) {
482
+ const span = formatShortSpan(value, now);
483
+ return span === "—" ? "—" : span === "now" ? "just now" : `${span} ago`;
484
+ }
485
+
486
+ function formatShortSpan(value, now) {
472
487
  const d = new Date(value);
473
488
  if (Number.isNaN(d.getTime())) return "—";
474
489
  const seconds = Math.floor((now.getTime() - d.getTime()) / 1e3);
475
- if (seconds < 60) return "just now";
490
+ if (seconds < 60) return "now";
476
491
  const minutes = Math.floor(seconds / 60);
477
- if (minutes < 60) return `${minutes}m ago`;
492
+ if (minutes < 60) return `${minutes}m`;
478
493
  const hours = Math.floor(minutes / 60);
479
- return hours < 24 ? `${hours}h ago` : `${Math.floor(hours / 24)}d ago`;
494
+ return hours < 24 ? `${hours}h` : `${Math.floor(hours / 24)}d`;
480
495
  }
481
496
 
482
497
  function stampFace(verb, at) {
@@ -599,7 +614,7 @@ function isLiveEntry(entry) {
599
614
  const isTodoDone = item => item.status === "done", toggledTodoStatus = item => isTodoDone(item) ? "open" : "done";
600
615
 
601
616
  function isTodoOverdue(item, today = /* @__PURE__ */ new Date) {
602
- return !item.dueDate || isTodoDone(item) ? !1 : item.dueDate < serializeDateFieldValue(today, "date");
617
+ return !item.dueDate || isTodoDone(item) ? !1 : isDueDatePast(item.dueDate, today);
603
618
  }
604
619
 
605
620
  function todoEditKind(patch) {
@@ -700,7 +715,7 @@ function todoFieldEditability(args) {
700
715
  }
701
716
 
702
717
  function itemRow(args) {
703
- const {item: item, items: items, field: field, source: source, ticks: ticks, editability: editability} = args, tick = ticks.get(tickKey(field.name, item._key));
718
+ const {item: item, items: items, field: field, source: source, ticks: ticks, editability: editability} = args, tick = ticks.get(tickKey(field.name, item._key)), eligibleAssigneeRoles = field.of.find(column => column.name === "assignee" && column.type === "assignee")?.roles;
704
719
  return {
705
720
  item: item,
706
721
  items: items,
@@ -715,6 +730,9 @@ function itemRow(args) {
715
730
  ...editability.editDisabledReason ? {
716
731
  editDisabledReason: editability.editDisabledReason
717
732
  } : {},
733
+ ...eligibleAssigneeRoles === void 0 ? {} : {
734
+ eligibleAssigneeRoles: eligibleAssigneeRoles
735
+ },
718
736
  ...tick ? {
719
737
  tick: tick
720
738
  } : {}
@@ -920,7 +938,7 @@ function concludesHere(actionEval, activityName) {
920
938
  return changesOwnStatus(actionEval.action, activityName) || actionEval.firing?.exitsStage === !0;
921
939
  }
922
940
 
923
- const SEMANTIC_FACE = {
941
+ const DECISION_SEMANTIC_FACE = {
924
942
  "decision.accept": {
925
943
  tone: "positive",
926
944
  icon: CheckmarkIcon
@@ -931,6 +949,15 @@ const SEMANTIC_FACE = {
931
949
  }
932
950
  };
933
951
 
952
+ function isDecisionSemantic(value) {
953
+ return Object.hasOwn(DECISION_SEMANTIC_FACE, value);
954
+ }
955
+
956
+ function decisionSemanticFace(semantics) {
957
+ const semantic = semantics.find(isDecisionSemantic);
958
+ return semantic === void 0 ? void 0 : DECISION_SEMANTIC_FACE[semantic];
959
+ }
960
+
934
961
  function actionButtonFace(args) {
935
962
  const mode = concludesHere(args.actionEval, args.activityName) ? "default" : "ghost";
936
963
  if (actionAdvanceStatus(args.actionEval.action, args.activityName) === "failed") return {
@@ -938,7 +965,7 @@ function actionButtonFace(args) {
938
965
  tone: "critical",
939
966
  icon: void 0
940
967
  };
941
- const [semantic] = args.actionEval.semantics ?? [], face = semantic === void 0 ? void 0 : SEMANTIC_FACE[semantic];
968
+ const face = decisionSemanticFace(args.actionEval.semantics ?? []);
942
969
  return {
943
970
  mode: mode,
944
971
  tone: face?.tone ?? "default",
@@ -1105,24 +1132,10 @@ function dateValuesOf(state, matches) {
1105
1132
  return state.filter(entry => matches(entry._type)).map(entry => entry.value).filter(value => typeof value == "string" && value !== "");
1106
1133
  }
1107
1134
 
1108
- function datesOf(state) {
1109
- return dateValuesOf(state, isDateFieldKind);
1110
- }
1111
-
1112
1135
  function dueDatesOf(state) {
1113
1136
  return dateValuesOf(state, isDueDateFieldKind);
1114
1137
  }
1115
1138
 
1116
- function committedRowFace(row) {
1117
- return {
1118
- activityName: row.activityName,
1119
- title: row.title,
1120
- status: row.status,
1121
- automated: !1,
1122
- blocked: !1
1123
- };
1124
- }
1125
-
1126
1139
  function activityRowProps(args) {
1127
1140
  const {activityEval: activityEval, activityState: activityState, editableFields: editableFields} = args, state = activityState.get(activityEval.activity.name) ?? [], controlArgs = {
1128
1141
  activity: activityEval.activity,
@@ -1221,15 +1234,15 @@ function useWorkflowInstanceEntry(instanceId, options = {}) {
1221
1234
  }
1222
1235
 
1223
1236
  const WorkflowToolOpened = defineEvent({
1224
- name: "Editorial Workflows Studio Plugin Tool Opened",
1225
- version: 2,
1237
+ name: "Workflows Studio Plugin Tool Opened",
1238
+ version: 3,
1226
1239
  description: "The Workflows tool was opened or its tab was switched"
1227
1240
  }), WorkflowDocumentViewOpened = defineEvent({
1228
- name: "Editorial Workflows Studio Plugin Document View Opened",
1241
+ name: "Workflows Studio Plugin Document View Opened",
1229
1242
  version: 1,
1230
1243
  description: "The Workflows document view was opened or its tab was switched"
1231
1244
  }), WorkflowInstanceDetailViewed = defineEvent({
1232
- name: "Editorial Workflows Studio Plugin Instance Detail Viewed",
1245
+ name: "Workflows Studio Plugin Instance Detail Viewed",
1233
1246
  version: 1,
1234
1247
  description: "The Workflows tool's per-instance detail view was opened"
1235
1248
  });
@@ -1242,67 +1255,63 @@ function definitionFingerprint(definition) {
1242
1255
  }
1243
1256
 
1244
1257
  const WorkflowDefinitionDetailViewed = defineEvent({
1245
- name: "Editorial Workflows Studio Plugin Definition Detail Viewed",
1258
+ name: "Workflows Studio Plugin Definition Detail Viewed",
1246
1259
  version: 1,
1247
1260
  description: "A workflow's definition page was opened in the Workflows tool"
1248
1261
  }), WorkflowFormStripClicked = defineEvent({
1249
- name: "Editorial Workflows Studio Plugin Form Strip Clicked",
1262
+ name: "Workflows Studio Plugin Form Strip Clicked",
1250
1263
  version: 1,
1251
1264
  description: "The form strip's instance line was clicked, opening the document view"
1252
1265
  }), WorkflowDocumentLinkClicked = defineEvent({
1253
- name: "Editorial Workflows Studio Plugin Document Link Clicked",
1254
- version: 2,
1266
+ name: "Workflows Studio Plugin Document Link Clicked",
1267
+ version: 3,
1255
1268
  description: "A document link was clicked, opening the referenced document's editor"
1256
1269
  }), WorkflowStartDialogOpened = defineEvent({
1257
- name: "Editorial Workflows Studio Plugin Start Dialog Opened",
1270
+ name: "Workflows Studio Plugin Start Dialog Opened",
1258
1271
  version: 1,
1259
1272
  description: "The start-workflow dialog was opened"
1260
1273
  }), WorkflowStartDialogSubmitted = defineEvent({
1261
- name: "Editorial Workflows Studio Plugin Start Dialog Submitted",
1274
+ name: "Workflows Studio Plugin Start Dialog Submitted",
1262
1275
  version: 1,
1263
1276
  description: "The start-workflow dialog's confirm was pressed"
1264
1277
  }), WorkflowAutoStartRan = defineEvent({
1265
- name: "Editorial Workflows Studio Plugin Auto Start Ran",
1278
+ name: "Workflows Studio Plugin Auto Start Ran",
1266
1279
  version: 1,
1267
1280
  description: "A fresh document's auto-start ran — one start request per configured workflow"
1268
1281
  }), WorkflowActionControlUsed = defineEvent({
1269
- name: "Editorial Workflows Studio Plugin Action Control Used",
1270
- version: 2,
1282
+ name: "Workflows Studio Plugin Action Control Used",
1283
+ version: 3,
1271
1284
  description: "An action-firing control was used, attributed to its UI surface"
1272
1285
  }), WorkflowFieldControlUsed = defineEvent({
1273
- name: "Editorial Workflows Studio Plugin Field Control Used",
1274
- version: 1,
1286
+ name: "Workflows Studio Plugin Field Control Used",
1287
+ version: 2,
1275
1288
  description: "A field-editing control committed an edit, attributed to its UI surface"
1276
1289
  }), WorkflowActivityDialogOpened = defineEvent({
1277
- name: "Editorial Workflows Studio Plugin Activity Dialog Opened",
1278
- version: 1,
1290
+ name: "Workflows Studio Plugin Activity Dialog Opened",
1291
+ version: 2,
1279
1292
  description: "The activity detail dialog was opened"
1280
- }), WorkflowTaskFiltersApplied = defineEvent({
1281
- name: "Editorial Workflows Studio Plugin Task Filters Applied",
1282
- version: 1,
1283
- description: "The tool's task-filter menu closed with a changed selection"
1284
1293
  }), WorkflowBoardWorkflowSelected = defineEvent({
1285
- name: "Editorial Workflows Studio Plugin Board Workflow Selected",
1286
- version: 2,
1287
- description: "The workflow documents page settled on the workflow it's showing"
1294
+ name: "Workflows Studio Plugin Board Workflow Selected",
1295
+ version: 3,
1296
+ description: "The Overview's board display settled on the workflow it's showing"
1288
1297
  }), WorkflowTitleSeedDrifted = defineEvent({
1289
- name: "Editorial Workflows Studio Plugin Title Seed Drifted",
1298
+ name: "Workflows Studio Plugin Title Seed Drifted",
1290
1299
  version: 1,
1291
1300
  description: "A cold-start seeded preview title diverged from the live preview pipeline"
1292
1301
  }), WorkflowTodoToggled = defineEvent({
1293
- name: "Editorial Workflows Studio Plugin Todo Toggled",
1302
+ name: "Workflows Studio Plugin Todo Toggled",
1294
1303
  version: 1,
1295
1304
  description: "A todo checkbox was toggled, attributed to its UI surface and write seam"
1296
1305
  }), WorkflowTodoEdited = defineEvent({
1297
- name: "Editorial Workflows Studio Plugin Todo Edited",
1306
+ name: "Workflows Studio Plugin Todo Edited",
1298
1307
  version: 1,
1299
1308
  description: "A non-toggle todo-list write, attributed to its UI surface and gesture"
1300
1309
  }), WorkflowAbortDialogOpened = defineEvent({
1301
- name: "Editorial Workflows Studio Plugin Abort Dialog Opened",
1310
+ name: "Workflows Studio Plugin Abort Dialog Opened",
1302
1311
  version: 1,
1303
1312
  description: "The abort-workflow confirm was opened"
1304
1313
  }), WorkflowAbortDialogSubmitted = defineEvent({
1305
- name: "Editorial Workflows Studio Plugin Abort Dialog Submitted",
1314
+ name: "Workflows Studio Plugin Abort Dialog Submitted",
1306
1315
  version: 1,
1307
1316
  description: "The abort-workflow confirm was submitted, carrying the attempt's outcome"
1308
1317
  });
@@ -1338,6 +1347,12 @@ function scalarValidationIssue(args) {
1338
1347
  return issues === void 0 ? void 0 : `${args.label}: ${issues.join("; ")}`;
1339
1348
  }
1340
1349
 
1350
+ function countBy(rows, keysOf) {
1351
+ const counts = /* @__PURE__ */ new Map;
1352
+ for (const row of rows) for (const key of keysOf(row)) counts.set(key, (counts.get(key) ?? 0) + 1);
1353
+ return counts;
1354
+ }
1355
+
1341
1356
  function personActor(user) {
1342
1357
  const roles = user.roles.map(role => typeof role == "string" ? role : role.name);
1343
1358
  return {
@@ -1369,10 +1384,16 @@ function userDisplayFor(args) {
1369
1384
  return {
1370
1385
  id: args.id,
1371
1386
  name: member?.displayName ?? self?.name ?? args.id,
1372
- imageUrl: member?.imageUrl ?? self?.profileImage ?? void 0
1387
+ imageUrl: member?.imageUrl ?? self?.profileImage ?? void 0,
1388
+ loginProvider: member?.loginProvider
1373
1389
  };
1374
1390
  }
1375
1391
 
1392
+ function disambiguatedNames(displays) {
1393
+ const counts = countBy(displays, display => [ display.name ]);
1394
+ return displays.map(display => (counts.get(display.name) ?? 0) < 2 || display.loginProvider === void 0 ? display.name : `${display.name} (${providerTitle(display.loginProvider) ?? display.loginProvider})`);
1395
+ }
1396
+
1376
1397
  function useUserDisplays(ids) {
1377
1398
  const {members: members} = useProjectMembers(), {users: users} = useStudioProjectUsers(), meRaw = useCurrentUser(), me = useMemo(() => meRaw === null ? null : {
1378
1399
  ...meRaw,
@@ -1405,21 +1426,32 @@ function bridgedSelfId(users, meId) {
1405
1426
  return users.find(user => user.membership.id === meId)?.profile?.sanityUserId ?? classified.globalId ?? (classified.namespace === "project" ? void 0 : meId);
1406
1427
  }
1407
1428
 
1408
- function useBridgedSelf() {
1429
+ function useSelfState() {
1409
1430
  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 ?? []
1431
+ if (!me) return {
1432
+ kind: "unavailable"
1415
1433
  };
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;
1434
+ const id = bridgedSelfId(users, me.id);
1435
+ return id !== void 0 ? {
1436
+ kind: "resolved",
1437
+ self: {
1438
+ id: id,
1439
+ roles: me.roles ?? []
1420
1440
  }
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
- }
1441
+ } : loading ? {
1442
+ kind: "pending"
1443
+ } : 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.`),
1444
+ {
1445
+ kind: "unavailable"
1446
+ }) : (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.`),
1447
+ {
1448
+ kind: "unavailable"
1449
+ });
1450
+ }
1451
+
1452
+ function useBridgedSelf() {
1453
+ const state = useSelfState();
1454
+ return state.kind === "resolved" ? state.self : void 0;
1423
1455
  }
1424
1456
 
1425
1457
  function useSelfActor() {
@@ -1428,13 +1460,26 @@ function useSelfActor() {
1428
1460
  }
1429
1461
 
1430
1462
  function useAssignmentIdentity() {
1431
- const self = useBridgedSelf();
1432
- if (self !== void 0) return {
1433
- userId: self.id,
1434
- roles: self.roles.map(r => r.name)
1463
+ const state = useSelfState();
1464
+ if (state.kind !== "resolved") return state;
1465
+ const {id: id, roles: roles} = state.self;
1466
+ return {
1467
+ kind: "resolved",
1468
+ identity: {
1469
+ userId: id,
1470
+ roles: roles.map(r => r.name)
1471
+ }
1435
1472
  };
1436
1473
  }
1437
1474
 
1475
+ function identityOf(state) {
1476
+ return state.kind === "resolved" ? state.identity : void 0;
1477
+ }
1478
+
1479
+ function useCurrentUserId() {
1480
+ return identityOf(useAssignmentIdentity())?.userId;
1481
+ }
1482
+
1438
1483
  const projectMembersByUsers = /* @__PURE__ */ new WeakMap;
1439
1484
 
1440
1485
  function projectMembersFrom(users) {
@@ -1542,6 +1587,8 @@ function buildParams(decls, values) {
1542
1587
  };
1543
1588
  }
1544
1589
 
1590
+ const LOADING_CUE_DELAY_MS = 400;
1591
+
1545
1592
  function useDelayedFlag(active, delayMs) {
1546
1593
  const [held, setHeld] = useState(!1);
1547
1594
  return useEffect(() => {
@@ -1823,23 +1870,27 @@ function MemberPicker({selectedIds: selectedIds, onSelect: onSelect, unassignRow
1823
1870
  });
1824
1871
  }
1825
1872
 
1826
- function AssigneePicker({value: value, onChange: onChange}) {
1873
+ function AssigneePicker({value: value, onChange: onChange, eligibleRoles: eligibleRoles, roleAliases: roleAliases}) {
1827
1874
  const state = useProjectMembers();
1828
1875
  /* @__PURE__ */
1829
1876
  return jsx(AssigneePicker$1, {
1830
1877
  ...state,
1878
+ eligibleRoles: eligibleRoles,
1831
1879
  onChange: onChange,
1880
+ roleAliases: roleAliases,
1832
1881
  value: value
1833
1882
  });
1834
1883
  }
1835
1884
 
1836
- function SingleAssigneePicker({current: current, onPick: onPick}) {
1885
+ function SingleAssigneePicker({current: current, onPick: onPick, eligibleRoles: eligibleRoles, roleAliases: roleAliases}) {
1837
1886
  /* @__PURE__ */
1838
1887
  return jsx(AssigneePicker, {
1888
+ eligibleRoles: eligibleRoles,
1839
1889
  onChange: next => {
1840
1890
  const picked = singleAssigneePick(current, next);
1841
1891
  picked !== void 0 && onPick(picked);
1842
1892
  },
1893
+ roleAliases: roleAliases,
1843
1894
  value: current ? [ current ] : []
1844
1895
  });
1845
1896
  }
@@ -2152,7 +2203,6 @@ const TOAST_ID = {
2152
2203
  effectResolve: "workflow-effect-resolve",
2153
2204
  effectsDrain: "workflow-effects-drain",
2154
2205
  effectsIncomplete: "workflow-effects-incomplete",
2155
- orphanSettle: "workflow-orphan-settle",
2156
2206
  start: "workflow-start"
2157
2207
  };
2158
2208
 
@@ -2549,11 +2599,10 @@ function Separator() {
2549
2599
  }
2550
2600
 
2551
2601
  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);
2602
+ return findStageNode({
2603
+ definition: definition,
2604
+ stageName: stageName
2605
+ })?.title ?? stageName;
2557
2606
  }
2558
2607
 
2559
2608
  function findActivity(args) {
@@ -3013,8 +3062,7 @@ function PendingDocFace({bareId: bareId, layout: layout}) {
3013
3062
  children: id
3014
3063
  });
3015
3064
  }
3016
- return compact ?
3017
- /* @__PURE__ */ jsxs(Flex, {
3065
+ return compact ? /* @__PURE__ */ jsxs(Flex, {
3018
3066
  align: "center",
3019
3067
  gap: 2,
3020
3068
  children: [
@@ -3688,9 +3736,11 @@ function AssigneeField({field: field, instanceId: instanceId, onSave: onSave, on
3688
3736
  children: /* @__PURE__ */ jsx(DismissablePopover, {
3689
3737
  content: /* @__PURE__ */ jsx(SingleAssigneePicker, {
3690
3738
  current: cur,
3739
+ eligibleRoles: field.roles,
3691
3740
  onPick: picked => {
3692
3741
  setOpen(!1), pick(picked);
3693
- }
3742
+ },
3743
+ roleAliases: field.roleAliases
3694
3744
  }),
3695
3745
  onDismiss: () => setOpen(!1),
3696
3746
  open: open,
@@ -4021,6 +4071,267 @@ function EditableField({field: field, entry: entry, description: description, in
4021
4071
  });
4022
4072
  }
4023
4073
 
4074
+ function useDrainEffects(instanceId) {
4075
+ const {drainEffectsFor: drainEffectsFor} = useWorkflowContext(), toast = useWorkflowToast(), [busy, setBusy] = useState(!1);
4076
+ return {
4077
+ busy: busy,
4078
+ run: async () => {
4079
+ setBusy(!0);
4080
+ try {
4081
+ await drainEffectsFor(instanceId), toast.push({
4082
+ id: TOAST_ID.effectsDrain,
4083
+ status: "info",
4084
+ title: "Ran the registered handlers"
4085
+ });
4086
+ } catch (err) {
4087
+ toast.push({
4088
+ id: TOAST_ID.effectsDrain,
4089
+ status: "error",
4090
+ title: "Failed to run pending effects",
4091
+ description: describeError(err)
4092
+ });
4093
+ } finally {
4094
+ setBusy(!1);
4095
+ }
4096
+ }
4097
+ };
4098
+ }
4099
+
4100
+ function isRunnable(verb) {
4101
+ return verb === "drain-effects";
4102
+ }
4103
+
4104
+ function effectLabel(effect) {
4105
+ return effect.title ?? effect.name;
4106
+ }
4107
+
4108
+ const ACTION_LABEL = {
4109
+ "drain-effects": "Run the step again"
4110
+ }, REMEDY_TEXT = {
4111
+ "retry-effect": "The step has to run again before this workflow can continue.",
4112
+ "reset-activity": "Reset the task to try it again, or skip it.",
4113
+ "set-stage": "Move the workflow on to its next stage yourself."
4114
+ };
4115
+
4116
+ function detailOf(cause) {
4117
+ switch (cause.kind) {
4118
+ case "failed-effect":
4119
+ return `The step “${effectLabel(cause.effect)}” failed`;
4120
+
4121
+ case "hung-effect":
4122
+ return `The step “${effectLabel(cause.effect)}” didn’t finish`;
4123
+
4124
+ case "failed-activity":
4125
+ return "This task failed";
4126
+
4127
+ default:
4128
+ return summaryOf(cause);
4129
+ }
4130
+ }
4131
+
4132
+ function summaryOf(cause) {
4133
+ switch (cause.kind) {
4134
+ case "failed-effect":
4135
+ return "An automated step failed";
4136
+
4137
+ case "hung-effect":
4138
+ return "An automated step didn’t finish";
4139
+
4140
+ case "failed-activity":
4141
+ return "A task failed";
4142
+
4143
+ case "no-transition-fires":
4144
+ return "Every task is done, but nothing moves this workflow on";
4145
+
4146
+ case "transition-unevaluable":
4147
+ return "This workflow is waiting on something it can’t read yet, and will move on by itself";
4148
+ }
4149
+ }
4150
+
4151
+ function activityDeclaringEffect(args) {
4152
+ return findStageNode(args)?.activities?.find(activity => (activity.actions ?? []).some(action => (action.effects ?? []).some(effect => effect.name === args.effectName)))?.name;
4153
+ }
4154
+
4155
+ function faultingActivity(args) {
4156
+ const {cause: cause} = args;
4157
+ if (cause.kind === "failed-activity") return cause.activity;
4158
+ if (cause.kind === "failed-effect" || cause.kind === "hung-effect") return activityDeclaringEffect({
4159
+ definition: args.definition,
4160
+ effectName: cause.effect.name,
4161
+ stageName: args.stageName
4162
+ });
4163
+ }
4164
+
4165
+ function isRecoverable(cause) {
4166
+ return cause.kind === "transition-unevaluable";
4167
+ }
4168
+
4169
+ function diagnosisOf(args) {
4170
+ if (args.evaluation !== void 0) return diagnoseInstance(diagnoseInputFromEvaluation(args.evaluation));
4171
+ const cause = documentStuckCause({
4172
+ instance: args.instance,
4173
+ definition: args.definition
4174
+ });
4175
+ return cause === void 0 ? void 0 : {
4176
+ state: "stuck",
4177
+ cause: cause
4178
+ };
4179
+ }
4180
+
4181
+ function instanceFault(args) {
4182
+ const diagnosis = diagnosisOf(args);
4183
+ if (diagnosis === void 0 || diagnosis.state !== "stuck") return;
4184
+ const verbs = remediationsFor(diagnosis).map(remediation2 => remediation2.verb);
4185
+ return {
4186
+ activity: faultingActivity({
4187
+ cause: diagnosis.cause,
4188
+ definition: args.definition,
4189
+ stageName: args.instance.currentStage
4190
+ }),
4191
+ detail: detailOf(diagnosis.cause),
4192
+ summary: summaryOf(diagnosis.cause),
4193
+ remedies: verbs.flatMap(verb => REMEDY_TEXT[verb] ?? []),
4194
+ actions: verbs.flatMap(verb => isRunnable(verb) ? [ {
4195
+ verb: verb,
4196
+ label: ACTION_LABEL[verb]
4197
+ } ] : []),
4198
+ recoverable: isRecoverable(diagnosis.cause)
4199
+ };
4200
+ }
4201
+
4202
+ function looksLikeDefinition(value) {
4203
+ return typeof value == "object" && value !== null && Array.isArray(value.stages);
4204
+ }
4205
+
4206
+ const parsed = /* @__PURE__ */ new WeakMap;
4207
+
4208
+ function definitionSnapshotOf(instance) {
4209
+ if (parsed.has(instance)) return parsed.get(instance);
4210
+ let definition;
4211
+ try {
4212
+ const candidate = parseDefinitionSnapshot(instance);
4213
+ if (!looksLikeDefinition(candidate)) throw new Error(`definitionSnapshot on "${instance._id}" parsed to a non-definition shape`);
4214
+ definition = candidate;
4215
+ } catch (err) {
4216
+ console.error("[workflow-studio-plugin] unreadable definitionSnapshot:", err), definition = void 0;
4217
+ }
4218
+ return parsed.set(instance, definition), definition;
4219
+ }
4220
+
4221
+ function useDefinition(entry) {
4222
+ return useMemo(() => {
4223
+ if (entry) return entry.evaluation ? entry.evaluation.definition : definitionSnapshotOf(entry.instance);
4224
+ }, [ entry ]);
4225
+ }
4226
+
4227
+ function AbortedBadge({instance: instance}) {
4228
+ return terminalState(instance) !== "aborted" ? null : /* @__PURE__ */ jsx(Badge, {
4229
+ fontSize: 1,
4230
+ tone: "critical",
4231
+ children: "Aborted"
4232
+ });
4233
+ }
4234
+
4235
+ function OlderDefinitionBadge({instance: instance}) {
4236
+ const {latestDefinitionVersions: latestDefinitionVersions} = useWorkflowContext(), latest = latestDefinitionVersions.get(instance.definition);
4237
+ return latest === void 0 || latest <= instance.pinnedVersion ? null : /* @__PURE__ */ jsx(HoverHint, {
4238
+ description: "Running workflows keep the version they started on",
4239
+ text: `Started on v${instance.pinnedVersion}, latest is now v${latest}`,
4240
+ textWeight: "medium",
4241
+ children: /* @__PURE__ */ jsx(Badge, {
4242
+ fontSize: 1,
4243
+ tone: "caution",
4244
+ children: "Older version"
4245
+ })
4246
+ });
4247
+ }
4248
+
4249
+ function useInstanceFault(entry) {
4250
+ return instanceFault({
4251
+ definition: useDefinition(entry),
4252
+ evaluation: entry.evaluation,
4253
+ instance: entry.instance
4254
+ });
4255
+ }
4256
+
4257
+ function InstanceFaultNote({entry: entry, scope: scope}) {
4258
+ const fault = useInstanceFault(entry);
4259
+ if (fault === void 0 || scope.kind === "task" && fault.activity !== scope.activityName) return null;
4260
+ const Icon = fault.recoverable ? WarningOutlineIcon : ErrorOutlineIcon;
4261
+ /* @__PURE__ */
4262
+ return jsx(Card, {
4263
+ padding: 3,
4264
+ radius: 3,
4265
+ tone: fault.recoverable ? "caution" : "critical",
4266
+ children: /* @__PURE__ */ jsxs(Flex, {
4267
+ align: "flex-start",
4268
+ gap: 3,
4269
+ children: [
4270
+ /* @__PURE__ */ jsx(Text, {
4271
+ size: 1,
4272
+ children: /* @__PURE__ */ jsx(Icon, {})
4273
+ }),
4274
+ /* @__PURE__ */ jsxs(Stack, {
4275
+ flex: 1,
4276
+ gap: 3,
4277
+ children: [
4278
+ /* @__PURE__ */ jsx(Text, {
4279
+ size: 1,
4280
+ weight: "medium",
4281
+ children: scope.kind === "run" ? fault.summary : fault.detail
4282
+ }), scope.kind === "run" ? /* @__PURE__ */ jsx(RunControl, {
4283
+ activity: fault.activity,
4284
+ onOpenActivity: scope.onOpenActivity
4285
+ }) : /* @__PURE__ */ jsx(TaskControls, {
4286
+ actions: fault.actions,
4287
+ instanceId: entry.instance._id,
4288
+ remedies: fault.remedies
4289
+ }) ]
4290
+ }) ]
4291
+ })
4292
+ });
4293
+ }
4294
+
4295
+ function RunControl({activity: activity, onOpenActivity: onOpenActivity}) {
4296
+ return activity === void 0 ? null : /* @__PURE__ */ jsx(Flex, {
4297
+ children: /* @__PURE__ */ jsx(Button, {
4298
+ fontSize: 1,
4299
+ mode: "ghost",
4300
+ onClick: () => onOpenActivity(activity),
4301
+ padding: 2,
4302
+ text: "View task"
4303
+ })
4304
+ });
4305
+ }
4306
+
4307
+ const RUN_VERB = {
4308
+ "drain-effects": drain => drain.run()
4309
+ };
4310
+
4311
+ function TaskControls({actions: actions, instanceId: instanceId, remedies: remedies}) {
4312
+ const drain = useDrainEffects(instanceId);
4313
+ /* @__PURE__ */
4314
+ return jsxs(Fragment, {
4315
+ children: [ remedies.length === 0 ? null : /* @__PURE__ */ jsx(Text, {
4316
+ muted: !0,
4317
+ size: 1,
4318
+ children: remedies.join(" ")
4319
+ }), actions.length === 0 ? null : /* @__PURE__ */ jsx(Flex, {
4320
+ gap: 2,
4321
+ children: actions.map(action => /* @__PURE__ */ jsx(Button, {
4322
+ disabled: drain.busy,
4323
+ fontSize: 1,
4324
+ mode: "ghost",
4325
+ onClick: () => {
4326
+ RUN_VERB[action.verb](drain);
4327
+ },
4328
+ padding: 2,
4329
+ text: action.label
4330
+ }, action.verb))
4331
+ }) ]
4332
+ });
4333
+ }
4334
+
4024
4335
  function MetaRow({children: children, fillHeight: fillHeight, label: label}) {
4025
4336
  /* @__PURE__ */
4026
4337
  return jsxs(Flex, {
@@ -4108,6 +4419,12 @@ function useSpaceToken(index) {
4108
4419
  return value;
4109
4420
  }
4110
4421
 
4422
+ function useRadiusToken(index) {
4423
+ const value = useTheme_v2().radius[index];
4424
+ if (value === void 0) throw new Error(`theme is missing radius token ${index}`);
4425
+ return value;
4426
+ }
4427
+
4111
4428
  function useContainerToken(index) {
4112
4429
  const value = useTheme_v2().container[index];
4113
4430
  if (value === void 0) throw new Error(`theme is missing container token ${index}`);
@@ -4148,10 +4465,9 @@ function useBadgeCapTrim() {
4148
4465
  };
4149
4466
  }
4150
4467
 
4151
- function SpinnerSlot({busy: busy}) {
4152
- const size = useTextIconSize(), trim = useCapTrimFor(size);
4153
- /* @__PURE__ */
4154
- return jsx(Flex, {
4468
+ function SpinnerSlot({busy: busy, reserve: reserve = !0}) {
4469
+ const size = useTextIconSize(), trim = useCapTrimFor(size), spinning = useDelayedFlag(busy, LOADING_CUE_DELAY_MS);
4470
+ return !spinning && !reserve ? null : /* @__PURE__ */ jsx(Flex, {
4155
4471
  align: "center",
4156
4472
  flex: "none",
4157
4473
  justify: "center",
@@ -4161,7 +4477,7 @@ function SpinnerSlot({busy: busy}) {
4161
4477
  marginTop: trim,
4162
4478
  width: size
4163
4479
  },
4164
- children: busy ? /* @__PURE__ */ jsx(Spinner, {
4480
+ children: spinning ? /* @__PURE__ */ jsx(Spinner, {
4165
4481
  muted: !0,
4166
4482
  size: 1
4167
4483
  }) : null
@@ -4169,6 +4485,7 @@ function SpinnerSlot({busy: busy}) {
4169
4485
  }
4170
4486
 
4171
4487
  function LoadingRow({label: label, padding: padding}) {
4488
+ const announced = useDelayedFlag(!0, LOADING_CUE_DELAY_MS);
4172
4489
  /* @__PURE__ */
4173
4490
  return jsxs(Flex, {
4174
4491
  align: "center",
@@ -4179,12 +4496,11 @@ function LoadingRow({label: label, padding: padding}) {
4179
4496
  children: [
4180
4497
  /* @__PURE__ */ jsx(SpinnerSlot, {
4181
4498
  busy: !0
4182
- }),
4183
- /* @__PURE__ */ jsx(Text, {
4499
+ }), announced ? /* @__PURE__ */ jsx(Text, {
4184
4500
  muted: !0,
4185
4501
  size: 1,
4186
4502
  children: label
4187
- }) ]
4503
+ }) : null ]
4188
4504
  });
4189
4505
  }
4190
4506
 
@@ -4245,8 +4561,7 @@ function RowAssignees({assignees: assignees, hint: hint}) {
4245
4561
  const bothKinds = roles.length > 0 && displays.length > 0;
4246
4562
  /* @__PURE__ */
4247
4563
  return jsx(HoverHint, {
4248
- content:
4249
- /* @__PURE__ */ jsxs(Stack, {
4564
+ content: /* @__PURE__ */ jsxs(Stack, {
4250
4565
  gap: 3,
4251
4566
  children: [ roles.length > 0 ? /* @__PURE__ */ jsx(HintSection, {
4252
4567
  names: roles.map(roleLabel),
@@ -4254,7 +4569,7 @@ function RowAssignees({assignees: assignees, hint: hint}) {
4254
4569
  label: "Roles"
4255
4570
  } : {}
4256
4571
  }) : null, displays.length > 0 ? /* @__PURE__ */ jsx(HintSection, {
4257
- names: displays.map(display => display.name),
4572
+ names: disambiguatedNames(displays),
4258
4573
  ...bothKinds ? {
4259
4574
  label: "Members"
4260
4575
  } : {}
@@ -4333,8 +4648,7 @@ function AssignActivityControl({instanceId: instanceId, state: state, surface: s
4333
4648
  }
4334
4649
  }, handleClick = e => {
4335
4650
  e.stopPropagation(), setOpen(v => !v);
4336
- }, button = assignees.length > 0 ?
4337
- /* @__PURE__ */ jsx(AvatarButton, {
4651
+ }, button = assignees.length > 0 ? /* @__PURE__ */ jsx(AvatarButton, {
4338
4652
  "aria-label": "Assignees",
4339
4653
  as: "span",
4340
4654
  avatar: /* @__PURE__ */ jsx(RowAssignees, {
@@ -4364,9 +4678,11 @@ function AssignActivityControl({instanceId: instanceId, state: state, surface: s
4364
4678
  label: "Loading…",
4365
4679
  padding: 3
4366
4680
  }) : /* @__PURE__ */ jsx(AssigneePicker, {
4681
+ eligibleRoles: state.field.roles,
4367
4682
  onChange: next => {
4368
4683
  change(next);
4369
4684
  },
4685
+ roleAliases: state.field.roleAliases,
4370
4686
  value: current
4371
4687
  })
4372
4688
  }),
@@ -4451,49 +4767,39 @@ function ForMeEmptyState() {
4451
4767
  });
4452
4768
  }
4453
4769
 
4454
- function looksLikeDefinition(value) {
4455
- return typeof value == "object" && value !== null && Array.isArray(value.stages);
4456
- }
4457
-
4458
- const parsed = /* @__PURE__ */ new WeakMap;
4459
-
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
- }
4770
+ const OVERDUE_HINT = "Now overdue";
4472
4771
 
4473
- function useDefinition(entry) {
4474
- return useMemo(() => {
4475
- if (entry) return entry.evaluation ? entry.evaluation.definition : definitionSnapshotOf(entry.instance);
4476
- }, [ entry ]);
4772
+ function hintFace(args) {
4773
+ return args.overdue ? args.reason === void 0 ? {
4774
+ text: OVERDUE_HINT
4775
+ } : {
4776
+ text: OVERDUE_HINT,
4777
+ description: args.reason
4778
+ } : args.reason === void 0 ? void 0 : {
4779
+ text: args.reason
4780
+ };
4477
4781
  }
4478
4782
 
4479
- function AbortedBadge({instance: instance}) {
4480
- return terminalState(instance) !== "aborted" ? null : /* @__PURE__ */ jsx(Badge, {
4481
- fontSize: 1,
4482
- tone: "critical",
4483
- children: "Aborted"
4783
+ function DueDate({dueDate: dueDate, hintDisabled: hintDisabled, overdue: overdue, reason: reason}) {
4784
+ const face = hintFace({
4785
+ overdue: overdue,
4786
+ reason: reason
4787
+ }), text = overdue ? /* @__PURE__ */ jsx(TextWithTone, {
4788
+ size: 1,
4789
+ tone: "caution",
4790
+ children: formatDate(dueDate)
4791
+ }) : /* @__PURE__ */ jsx(Text, {
4792
+ muted: !0,
4793
+ size: 1,
4794
+ children: formatDate(dueDate)
4484
4795
  });
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"
4796
+ return face === void 0 ? text :
4797
+ /* @__PURE__ */ jsx(Flex, {
4798
+ align: "center",
4799
+ children: /* @__PURE__ */ jsx(HoverHint, {
4800
+ ...face,
4801
+ disabled: hintDisabled === !0,
4802
+ children: text
4497
4803
  })
4498
4804
  });
4499
4805
  }
@@ -4524,24 +4830,12 @@ function CheckboxSlot({children: children}) {
4524
4830
  /* @__PURE__ */
4525
4831
  return jsx(Flex, {
4526
4832
  align: "center",
4527
- justify: "center",
4528
- style: {
4529
- lineHeight: 0,
4530
- margin: trim
4531
- },
4532
- children: children
4533
- });
4534
- }
4535
-
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)
4833
+ justify: "center",
4834
+ style: {
4835
+ lineHeight: 0,
4836
+ margin: trim
4837
+ },
4838
+ children: children
4545
4839
  });
4546
4840
  }
4547
4841
 
@@ -4564,8 +4858,9 @@ function DueDateEditor({dueDate: dueDate, onDone: onDone, onPatch: onPatch}) {
4564
4858
  }
4565
4859
 
4566
4860
  function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4567
- const [open, setOpen] = useState(!1), overdue = isTodoOverdue(item), display = item.dueDate ? /* @__PURE__ */ jsx(DueDateText, {
4861
+ const [open, setOpen] = useState(!1), overdue = isTodoOverdue(item), display = item.dueDate ? /* @__PURE__ */ jsx(DueDate, {
4568
4862
  dueDate: item.dueDate,
4863
+ hintDisabled: open,
4569
4864
  overdue: overdue
4570
4865
  }) : null;
4571
4866
  return editable ? /* @__PURE__ */ jsx(DismissablePopover, {
@@ -4576,18 +4871,14 @@ function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4576
4871
  }),
4577
4872
  onDismiss: () => setOpen(!1),
4578
4873
  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
- })
4874
+ children: display ? /* @__PURE__ */ jsx(Button, {
4875
+ "aria-label": "Change due date",
4876
+ fontSize: 1,
4877
+ mode: "bleed",
4878
+ onClick: () => setOpen(v => !v),
4879
+ padding: 2,
4880
+ tone: overdue ? "caution" : "default",
4881
+ children: display
4591
4882
  }) : /* @__PURE__ */ jsx(HoverHint, {
4592
4883
  disabled: open,
4593
4884
  text: "Set due date",
@@ -4601,13 +4892,10 @@ function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4601
4892
  radius: 2
4602
4893
  })
4603
4894
  })
4604
- }) : display && overdue ? /* @__PURE__ */ jsx(HoverHint, {
4605
- text: "Now overdue",
4606
- children: display
4607
4895
  }) : display;
4608
4896
  }
4609
4897
 
4610
- function AssigneeControl({item: item, editable: editable, onPatch: onPatch}) {
4898
+ function AssigneeControl({item: item, editable: editable, eligibleRoles: eligibleRoles, onPatch: onPatch, roleAliases: roleAliases}) {
4611
4899
  const [open, setOpen] = useState(!1), current = item.assignee ?? null, chip = current ? /* @__PURE__ */ jsx(RowAssignees, {
4612
4900
  assignees: [ current ]
4613
4901
  }) : null;
@@ -4615,11 +4903,13 @@ function AssigneeControl({item: item, editable: editable, onPatch: onPatch}) {
4615
4903
  content:
4616
4904
  /* @__PURE__ */ jsx(SingleAssigneePicker, {
4617
4905
  current: current,
4906
+ eligibleRoles: eligibleRoles,
4618
4907
  onPick: picked => {
4619
4908
  setOpen(!1), onPatch({
4620
4909
  assignee: picked
4621
4910
  });
4622
- }
4911
+ },
4912
+ roleAliases: roleAliases
4623
4913
  }),
4624
4914
  onDismiss: () => setOpen(!1),
4625
4915
  open: open,
@@ -4721,8 +5011,7 @@ function BreadcrumbTail({segments: segments, style: style}) {
4721
5011
  }
4722
5012
 
4723
5013
  function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
4724
- const frame =
4725
- /* @__PURE__ */ jsxs(Flex, {
5014
+ const frame = /* @__PURE__ */ jsxs(Flex, {
4726
5015
  align: "center",
4727
5016
  children: [
4728
5017
  /* @__PURE__ */ jsx(Flex, {
@@ -4756,8 +5045,7 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
4756
5045
  padding: 1,
4757
5046
  radius: 3,
4758
5047
  children: frame
4759
- }) :
4760
- /* @__PURE__ */ jsx(Card, {
5048
+ }) : /* @__PURE__ */ jsx(Card, {
4761
5049
  as: "button",
4762
5050
  onClick: onOpen,
4763
5051
  padding: 1,
@@ -4769,7 +5057,7 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
4769
5057
  });
4770
5058
  }
4771
5059
 
4772
- function TodoItemRowView({breadcrumb: breadcrumb, row: row, onToggle: onToggle, onPatch: onPatch, onRemove: onRemove}) {
5060
+ function TodoItemRowView({breadcrumb: breadcrumb, row: row, onToggle: onToggle, onPatch: onPatch, onRemove: onRemove, roleAliases: roleAliases}) {
4773
5061
  const {canClick: canClick, hint: hint} = rowInteractivity(row), editable = row.editTarget !== void 0, checkbox = /* @__PURE__ */ jsx(TodoCheckbox, {
4774
5062
  canClick: canClick,
4775
5063
  checked: isTodoDone(row.item),
@@ -4786,8 +5074,10 @@ function TodoItemRowView({breadcrumb: breadcrumb, row: row, onToggle: onToggle,
4786
5074
  }),
4787
5075
  /* @__PURE__ */ jsx(AssigneeControl, {
4788
5076
  editable: editable,
5077
+ eligibleRoles: row.eligibleAssigneeRoles,
4789
5078
  item: row.item,
4790
- onPatch: onPatch
5079
+ onPatch: onPatch,
5080
+ roleAliases: roleAliases
4791
5081
  }), onRemove === void 0 ? null : /* @__PURE__ */ jsx(RemoveItemButton, {
4792
5082
  onClick: onRemove
4793
5083
  }) ]
@@ -4964,6 +5254,7 @@ function TodoItemsList({breadcrumb: breadcrumb, entry: entry, filter: filter, sh
4964
5254
  onToggle: () => {
4965
5255
  toggleRow(row);
4966
5256
  },
5257
+ roleAliases: definition?.roleAliases,
4967
5258
  row: row
4968
5259
  }, rowKey(row)))
4969
5260
  }), addControl === null ? null : /* @__PURE__ */ jsx(Flex, {
@@ -5256,9 +5547,15 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
5256
5547
  const detail = deriveActivityDetail({
5257
5548
  entry: entry,
5258
5549
  activityName: activityName
5259
- });
5550
+ }), faultingActivity2 = useInstanceFault(entry)?.activity;
5260
5551
  if (!detail) return null;
5261
- const instanceId = entry.instance._id, notice = blockedNotice(detail), hasBody = detail.state.length > 0 || notice !== void 0;
5552
+ const instanceId = entry.instance._id, notice = blockedNotice(detail), fault = /* @__PURE__ */ jsx(InstanceFaultNote, {
5553
+ entry: entry,
5554
+ scope: {
5555
+ kind: "task",
5556
+ activityName: activityName
5557
+ }
5558
+ }), faulted = faultingActivity2 === activityName, hasBody = detail.state.length > 0 || notice !== void 0 || faulted;
5262
5559
  /* @__PURE__ */
5263
5560
  return jsxs(Dialog, {
5264
5561
  footer: detail.presentation.terminalActions.length > 0 ? /* @__PURE__ */ jsx(TerminalFooter, {
@@ -5299,7 +5596,7 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
5299
5596
  padding: 4,
5300
5597
  children: /* @__PURE__ */ jsxs(Stack, {
5301
5598
  gap: 5,
5302
- children: [ detail.state.map(field => /* @__PURE__ */ jsx(FieldRow, {
5599
+ children: [ fault, detail.state.map(field => /* @__PURE__ */ jsx(FieldRow, {
5303
5600
  activityName: activityName,
5304
5601
  detail: detail,
5305
5602
  entry: entry,
@@ -5480,6 +5777,18 @@ function CountedLabel({count: count, label: label}) {
5480
5777
  });
5481
5778
  }
5482
5779
 
5780
+ function CountedHeading({count: count, title: title}) {
5781
+ /* @__PURE__ */
5782
+ return jsx(CountedLabel, {
5783
+ count: count,
5784
+ label: /* @__PURE__ */ jsx(Text, {
5785
+ size: 1,
5786
+ weight: "semibold",
5787
+ children: title
5788
+ })
5789
+ });
5790
+ }
5791
+
5483
5792
  function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5484
5793
  /* @__PURE__ */
5485
5794
  return jsxs(Flex, {
@@ -5488,13 +5797,9 @@ function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5488
5797
  paddingLeft: 2,
5489
5798
  paddingY: end === void 0 ? 3 : 1,
5490
5799
  children: [
5491
- /* @__PURE__ */ jsx(CountedLabel, {
5800
+ /* @__PURE__ */ jsx(CountedHeading, {
5492
5801
  count: count,
5493
- label: /* @__PURE__ */ jsx(Text, {
5494
- size: 1,
5495
- weight: "semibold",
5496
- children: title
5497
- })
5802
+ title: title
5498
5803
  }), busy === void 0 ? null : /* @__PURE__ */ jsx(SpinnerSlot, {
5499
5804
  busy: busy
5500
5805
  }), end === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, {
@@ -5506,21 +5811,85 @@ function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5506
5811
  });
5507
5812
  }
5508
5813
 
5814
+ const EMPTY_METRIC_OPACITY = .5;
5815
+
5816
+ function MetricText({children: children, tone: tone}) {
5817
+ return tone === void 0 ? /* @__PURE__ */ jsx(Text, {
5818
+ muted: !0,
5819
+ size: 1,
5820
+ children: children
5821
+ }) : /* @__PURE__ */ jsx(TextWithTone, {
5822
+ size: 1,
5823
+ tone: tone,
5824
+ children: children
5825
+ });
5826
+ }
5827
+
5828
+ function MetricPair({Icon: Icon, empty: empty, face: face, tone: tone, value: value}) {
5829
+ /* @__PURE__ */
5830
+ return jsx(HoverHint, {
5831
+ ...face,
5832
+ children: /* @__PURE__ */ jsxs(Flex, {
5833
+ align: "center",
5834
+ gap: 2,
5835
+ style: empty ? {
5836
+ opacity: EMPTY_METRIC_OPACITY
5837
+ } : void 0,
5838
+ children: [
5839
+ /* @__PURE__ */ jsx(MetricText, {
5840
+ tone: tone,
5841
+ children: /* @__PURE__ */ jsx(Icon, {
5842
+ style: {
5843
+ color: "inherit"
5844
+ }
5845
+ })
5846
+ }),
5847
+ /* @__PURE__ */ jsx(MetricText, {
5848
+ tone: tone,
5849
+ children: value
5850
+ }) ]
5851
+ })
5852
+ });
5853
+ }
5854
+
5855
+ function AlertGlyph({Icon: Icon, hint: hint, label: label, tone: tone}) {
5856
+ /* @__PURE__ */
5857
+ return jsx(HoverHint, {
5858
+ text: hint,
5859
+ children: /* @__PURE__ */ jsx(TextWithTone, {
5860
+ size: 1,
5861
+ tone: tone,
5862
+ children: /* @__PURE__ */ jsx(Icon, {
5863
+ ...label === void 0 ? {} : {
5864
+ "aria-label": label,
5865
+ role: "img"
5866
+ },
5867
+ style: {
5868
+ color: "inherit"
5869
+ }
5870
+ })
5871
+ })
5872
+ });
5873
+ }
5874
+
5509
5875
  function ActivityDateControl({instanceId: instanceId, state: state, surface: surface, dueDates: dueDates}) {
5510
5876
  const editField = useEditField(surface), {save: save} = useSaveField({
5511
5877
  instanceId: instanceId
5512
5878
  }), [open, setOpen] = useState(!1), raw = dateControlValue({
5513
5879
  state: state,
5514
5880
  dueDates: dueDates
5515
- }), display = raw === void 0 ? null : /* @__PURE__ */ jsx(Text, {
5516
- muted: !0,
5517
- size: 1,
5518
- children: formatDate(raw)
5881
+ }), overdue = raw !== void 0 && isDueDatePast(raw, /* @__PURE__ */ new Date), dueDate = reason => raw === void 0 ? null : /* @__PURE__ */ jsx(DueDate, {
5882
+ dueDate: raw,
5883
+ hintDisabled: open,
5884
+ overdue: overdue,
5885
+ ...reason === void 0 ? {} : {
5886
+ reason: reason
5887
+ }
5519
5888
  });
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, {
5889
+ if (state.kind === "none") return dueDate();
5890
+ if (state.kind === "closed") {
5891
+ const display = dueDate(state.reason);
5892
+ return display === null ? null : /* @__PURE__ */ jsx(Button, {
5524
5893
  "aria-label": "Date",
5525
5894
  as: "span",
5526
5895
  fontSize: 1,
@@ -5529,8 +5898,8 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5529
5898
  onMouseDown: stopRowMouseDown,
5530
5899
  padding: 2,
5531
5900
  children: display
5532
- })
5533
- });
5901
+ });
5902
+ }
5534
5903
  const kind = dateControlKind(state), commit = (mode, value) => {
5535
5904
  state.kind === "editable" && save(() => editField({
5536
5905
  instanceId: instanceId,
@@ -5566,7 +5935,7 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5566
5935
  }),
5567
5936
  onDismiss: () => setOpen(!1),
5568
5937
  open: open,
5569
- children: display ? /* @__PURE__ */ jsx(Button, {
5938
+ children: raw !== void 0 ? /* @__PURE__ */ jsx(Button, {
5570
5939
  "aria-label": "Change date",
5571
5940
  as: "span",
5572
5941
  fontSize: 1,
@@ -5574,7 +5943,7 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5574
5943
  onClick: handleClick,
5575
5944
  onMouseDown: stopRowMouseDown,
5576
5945
  padding: 2,
5577
- children: display
5946
+ children: dueDate()
5578
5947
  }) : /* @__PURE__ */ jsx(HoverHint, {
5579
5948
  disabled: open,
5580
5949
  text: "Set date",
@@ -5664,12 +6033,11 @@ function TitleTag({hint: hint, tone: tone, children: children}) {
5664
6033
  });
5665
6034
  }
5666
6035
 
5667
- function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees, assignState: assignState, dateState: dateState, dueDates: dueDates, instanceId: instanceId, onOpen: onOpen, surface: surface, terminalActions: terminalActions = []}) {
6036
+ 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
6037
  const settled = isTerminalActivityStatus(face.status), dimmed = settledDim(settled);
5669
6038
  /* @__PURE__ */
5670
6039
  return jsxs(WorkRow, {
5671
- end:
5672
- /* @__PURE__ */ jsxs(Fragment, {
6040
+ end: /* @__PURE__ */ jsxs(Fragment, {
5673
6041
  children: [
5674
6042
  /* @__PURE__ */ jsx(ActivityDateControl, {
5675
6043
  dueDates: dueDates,
@@ -5697,6 +6065,10 @@ function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees,
5697
6065
  /* @__PURE__ */ jsx(ActivityRowTitle, {
5698
6066
  dimmed: dimmed,
5699
6067
  children: face.title
6068
+ }), faultHint === void 0 || face.status === "failed" ? null : /* @__PURE__ */ jsx(AlertGlyph, {
6069
+ Icon: ErrorOutlineIcon,
6070
+ hint: faultHint,
6071
+ tone: "critical"
5700
6072
  }), face.automated ? /* @__PURE__ */ jsx(TitleTag, {
5701
6073
  hint: "Completes on its own — no user action is needed",
5702
6074
  children: "Automated"
@@ -5712,9 +6084,8 @@ function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees,
5712
6084
  }
5713
6085
 
5714
6086
  function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity, surface: surface}) {
5715
- const activities = (entry.evaluation?.currentStage.activities ?? []).filter(t => !t.scopedOut), activityState = stateByActivity(entry.instance);
5716
- return activities.length === 0 ?
5717
- /* @__PURE__ */ jsx(Box, {
6087
+ const activities = (entry.evaluation?.currentStage.activities ?? []).filter(t => !t.scopedOut), activityState = stateByActivity(entry.instance), fault = useInstanceFault(entry);
6088
+ return activities.length === 0 ? /* @__PURE__ */ jsx(Box, {
5718
6089
  paddingX: 1,
5719
6090
  paddingY: 3,
5720
6091
  children: /* @__PURE__ */ jsx(Text, {
@@ -5730,6 +6101,9 @@ function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity, surface:
5730
6101
  activityState: activityState,
5731
6102
  editableFields: entry.evaluation?.editableFields
5732
6103
  }),
6104
+ ...fault?.activity === t.activity.name ? {
6105
+ faultHint: fault.summary
6106
+ } : {},
5733
6107
  instanceId: entry.instance._id,
5734
6108
  onOpen: () => onOpenActivity(t.activity.name),
5735
6109
  surface: surface
@@ -5739,8 +6113,7 @@ function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity, surface:
5739
6113
 
5740
6114
  function instanceNotice(entry) {
5741
6115
  const {instance: instance, committed: committed, evaluation: evaluation} = entry, state = terminalState(instance);
5742
- if (state !== "in-flight")
5743
- /* @__PURE__ */
6116
+ if (state !== "in-flight") /* @__PURE__ */
5744
6117
  return jsx(Box, {
5745
6118
  paddingX: 1,
5746
6119
  paddingY: 2,
@@ -5876,7 +6249,10 @@ function progressFillPercent(face) {
5876
6249
  }
5877
6250
 
5878
6251
  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({
6252
+ const {scope: scope, definition: definition, instance: instance, committed: committed, evaluation: evaluation} = args, declared = scope === "workflow" ? definition?.fields ?? [] : findStageNode({
6253
+ definition: definition,
6254
+ stageName: instance.currentStage
6255
+ })?.fields ?? [], entries = scope === "workflow" ? instance.fields : findOpenStageEntry(instance)?.fields ?? [], entriesByName = new Map(entries.map(entry => [ entry.name, entry ])), editables = isEvaluationCurrent({
5880
6256
  committed: committed,
5881
6257
  evaluation: evaluation
5882
6258
  }) ? editablesByName(evaluation, scope) : /* @__PURE__ */ new Map, declaredNames = new Set(declared.map(d => d.name));
@@ -5925,7 +6301,7 @@ function UserAvatar({id: id}) {
5925
6301
  function UserAvatarGroup({ids: ids, hint: hint}) {
5926
6302
  const displays = useUserDisplays(ids);
5927
6303
  return displays.length === 0 ? null : /* @__PURE__ */ jsx(HoverHint, {
5928
- text: displays.map(d => d.name).join(", "),
6304
+ text: disambiguatedNames(displays).join(", "),
5929
6305
  ...hint === void 0 ? {} : {
5930
6306
  description: hint
5931
6307
  },
@@ -5984,8 +6360,7 @@ function FieldPills({entry: entry, scope: scope, definition: definition, surface
5984
6360
  pill: pill,
5985
6361
  scope: scope,
5986
6362
  surface: surface
5987
- }, pill.name)), overflow > 0 ?
5988
- /* @__PURE__ */ jsx(Button, {
6363
+ }, pill.name)), overflow > 0 ? /* @__PURE__ */ jsx(Button, {
5989
6364
  mode: "bleed",
5990
6365
  onClick: () => setExpanded(v => !v),
5991
6366
  padding: 2,
@@ -6424,9 +6799,11 @@ function AssigneePill({entry: entry, pill: pill, editability: editability, surfa
6424
6799
  return jsx(PillPopover, {
6425
6800
  content: /* @__PURE__ */ jsx(SingleAssigneePicker, {
6426
6801
  current: current,
6802
+ eligibleRoles: editability.roles,
6427
6803
  onPick: picked => {
6428
6804
  setOpen(!1), pick(picked);
6429
- }
6805
+ },
6806
+ roleAliases: editability.roleAliases
6430
6807
  }),
6431
6808
  onDismiss: () => setOpen(!1),
6432
6809
  onToggle: () => setOpen(v => !v),
@@ -6440,13 +6817,15 @@ function AssigneesPill({entry: entry, pill: pill, editability: editability, surf
6440
6817
  entry: entry,
6441
6818
  editability: editability,
6442
6819
  surface: surface
6443
- }), current = pill.face.kind === "assignees" ? pill.face.assignees : [];
6820
+ }), current = pill.face.kind === "assignees" ? pill.face.assignees : [], change = next => {
6821
+ saving || save(() => set(next));
6822
+ };
6444
6823
  /* @__PURE__ */
6445
6824
  return jsx(PillPopover, {
6446
6825
  content: /* @__PURE__ */ jsx(AssigneePicker, {
6447
- onChange: next => {
6448
- saving || save(() => set(next));
6449
- },
6826
+ eligibleRoles: editability.roles,
6827
+ onChange: change,
6828
+ roleAliases: editability.roleAliases,
6450
6829
  value: current
6451
6830
  }),
6452
6831
  onDismiss: () => setOpen(!1),
@@ -7032,6 +7411,24 @@ function historyLine(h, titles) {
7032
7411
  return historyLiner[h._type](h, titles);
7033
7412
  }
7034
7413
 
7414
+ function AgoStamp({at: at, now: now}) {
7415
+ /* @__PURE__ */
7416
+ return jsx(Flex, {
7417
+ align: "center",
7418
+ children: /* @__PURE__ */ jsx(HoverHint, {
7419
+ text: formatShortDateTime(at),
7420
+ children: /* @__PURE__ */ jsx(Text, {
7421
+ muted: !0,
7422
+ size: 1,
7423
+ style: {
7424
+ whiteSpace: "nowrap"
7425
+ },
7426
+ children: formatShortAgo(at, now)
7427
+ })
7428
+ })
7429
+ });
7430
+ }
7431
+
7035
7432
  function CodeChip({value: value}) {
7036
7433
  /* @__PURE__ */
7037
7434
  return jsx("code", {
@@ -7066,6 +7463,27 @@ function HintedMenuButton({hint: hint, ...menuButtonProps}) {
7066
7463
  });
7067
7464
  }
7068
7465
 
7466
+ function InlineEmphasis({children: children}) {
7467
+ const {font: font} = useTheme_v2();
7468
+ /* @__PURE__ */
7469
+ return jsx("span", {
7470
+ style: {
7471
+ fontWeight: font.text.weights.medium
7472
+ },
7473
+ children: children
7474
+ });
7475
+ }
7476
+
7477
+ const MINUTE_MS = 6e4;
7478
+
7479
+ function useMinuteClock() {
7480
+ const [now, setNow] = useState(() => /* @__PURE__ */ new Date);
7481
+ return useEffect(() => {
7482
+ const timer = setInterval(() => setNow(/* @__PURE__ */ new Date), MINUTE_MS);
7483
+ return () => clearInterval(timer);
7484
+ }, []), now;
7485
+ }
7486
+
7069
7487
  function historyWindow(args) {
7070
7488
  const {entries: entries, expanded: expanded} = args;
7071
7489
  return {
@@ -7075,28 +7493,10 @@ function historyWindow(args) {
7075
7493
  }
7076
7494
 
7077
7495
  function PendingEffectRows({instance: instance, now: now}) {
7078
- const {drainEffectsFor: drainEffectsFor, completeEffectFor: completeEffectFor, effectHandlers: effectHandlers} = useWorkflowContext(), toast = useWorkflowToast(), [busy, setBusy] = useState(!1), pending = instance.pendingEffects ?? [];
7496
+ const {completeEffectFor: completeEffectFor, effectHandlers: effectHandlers} = useWorkflowContext(), toast = useWorkflowToast(), drain = useDrainEffects(instance._id), [resolving, setResolving] = useState(!1), pending = instance.pendingEffects ?? [];
7079
7497
  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);
7498
+ const resolve = async (effectKey, status) => {
7499
+ setResolving(!0);
7100
7500
  try {
7101
7501
  await completeEffectFor(instance._id, {
7102
7502
  effectKey: effectKey,
@@ -7115,18 +7515,20 @@ function PendingEffectRows({instance: instance, now: now}) {
7115
7515
  description: describeError(err)
7116
7516
  });
7117
7517
  } finally {
7118
- setBusy(!1);
7518
+ setResolving(!1);
7119
7519
  }
7120
7520
  };
7121
7521
  /* @__PURE__ */
7122
7522
  return jsx(Fragment, {
7123
7523
  children: pending.map(fx => /* @__PURE__ */ jsx(PendingEffectRow, {
7124
- busy: busy,
7524
+ busy: drain.busy || resolving,
7125
7525
  effect: fx,
7126
7526
  instanceId: instance._id,
7127
7527
  now: now,
7128
7528
  onResolve: status => resolve(fx._key, status),
7129
- onRunHandlers: runRegistered,
7529
+ onRunHandlers: () => {
7530
+ drain.run();
7531
+ },
7130
7532
  registered: fx.name in effectHandlers
7131
7533
  }, fx._key))
7132
7534
  });
@@ -7210,12 +7612,7 @@ function PendingEffectRow({busy: busy, effect: effect, instanceId: instanceId, n
7210
7612
  }
7211
7613
 
7212
7614
  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({
7615
+ const titles = useMemo(() => makeTitleResolver(definition), [ definition ]), entries = useMemo(() => instance.history.toReversed(), [ instance.history ]), now = useMinuteClock(), [expanded, setExpanded] = useState(!1), history = historyWindow({
7219
7616
  entries: entries,
7220
7617
  expanded: expanded || !windowed
7221
7618
  });
@@ -7257,16 +7654,9 @@ function FeedStamp({at: at, now: now}) {
7257
7654
  size: 1,
7258
7655
  children: "·"
7259
7656
  }),
7260
- /* @__PURE__ */ jsx(HoverHint, {
7261
- text: formatDateTime(at),
7262
- children: /* @__PURE__ */ jsx(Text, {
7263
- muted: !0,
7264
- size: 1,
7265
- style: {
7266
- whiteSpace: "nowrap"
7267
- },
7268
- children: formatShortAgo(at, now)
7269
- })
7657
+ /* @__PURE__ */ jsx(AgoStamp, {
7658
+ at: at,
7659
+ now: now
7270
7660
  }) ]
7271
7661
  });
7272
7662
  }
@@ -7318,12 +7708,9 @@ function HistoryRow({entry: entry, now: now, titles: titles}) {
7318
7708
  }
7319
7709
 
7320
7710
  function ActorNameSpan({id: id}) {
7321
- const {name: name} = useUserDisplay(id), {font: font} = useTheme_v2();
7711
+ const {name: name} = useUserDisplay(id);
7322
7712
  /* @__PURE__ */
7323
- return jsx("span", {
7324
- style: {
7325
- fontWeight: font.text.weights.medium
7326
- },
7713
+ return jsx(InlineEmphasis, {
7327
7714
  children: name
7328
7715
  });
7329
7716
  }
@@ -7378,8 +7765,7 @@ function InstanceCardMenu({entry: entry, title: title}) {
7378
7765
  return jsxs(Fragment, {
7379
7766
  children: [
7380
7767
  /* @__PURE__ */ jsx(MenuButton, {
7381
- button:
7382
- /* @__PURE__ */ jsx(Button, {
7768
+ button: /* @__PURE__ */ jsx(Button, {
7383
7769
  "aria-label": `More options for ${title}`,
7384
7770
  fontSize: 1,
7385
7771
  icon: EllipsisHorizontalIcon,
@@ -7700,8 +8086,7 @@ function StageOverviewCard({overview: overview}) {
7700
8086
  size: 1,
7701
8087
  weight: "medium",
7702
8088
  children: overview.title
7703
- }), overview.description === void 0 ? null :
7704
- /* @__PURE__ */ jsx(Text, {
8089
+ }), overview.description === void 0 ? null : /* @__PURE__ */ jsx(Text, {
7705
8090
  muted: !0,
7706
8091
  size: 1,
7707
8092
  style: {
@@ -7731,7 +8116,6 @@ function StageOverviewCard({overview: overview}) {
7731
8116
  }
7732
8117
 
7733
8118
  function AdvanceCard({advance: advance}) {
7734
- const {font: font} = useTheme_v2();
7735
8119
  /* @__PURE__ */
7736
8120
  return jsx(Card, {
7737
8121
  padding: 3,
@@ -7743,14 +8127,10 @@ function AdvanceCard({advance: advance}) {
7743
8127
  /* @__PURE__ */ jsxs(Text, {
7744
8128
  size: 1,
7745
8129
  children: [ "Advances to ",
7746
- /* @__PURE__ */ jsx("span", {
7747
- style: {
7748
- fontWeight: font.text.weights.medium
7749
- },
8130
+ /* @__PURE__ */ jsx(InlineEmphasis, {
7750
8131
  children: advance.toTitle
7751
8132
  }), ADVANCE_TAILS[advance.kind] ]
7752
- }), advance.kind !== "conditions" ? null : advance.lines.map((line, index) =>
7753
- /* @__PURE__ */ jsx(Text, {
8133
+ }), advance.kind !== "conditions" ? null : advance.lines.map((line, index) => /* @__PURE__ */ jsx(Text, {
7754
8134
  muted: !0,
7755
8135
  size: 1,
7756
8136
  style: {
@@ -8804,8 +9184,7 @@ const VIEW_TAB_CODEC = workflowsTabCodec([ "overview", "for-me" ]), WorkflowsVie
8804
9184
  children: /* @__PURE__ */ jsx(InvalidDocNotice, {
8805
9185
  invalid: discoveryInvalid
8806
9186
  })
8807
- }) : resolved ?
8808
- /* @__PURE__ */ jsx(Flex, {
9187
+ }) : resolved ? /* @__PURE__ */ jsx(Flex, {
8809
9188
  direction: "column",
8810
9189
  height: "fill",
8811
9190
  padding: 4,
@@ -8854,7 +9233,7 @@ function DiscoverySpinner() {
8854
9233
  });
8855
9234
  }
8856
9235
 
8857
- const BOOT_CUE_DELAY_MS = 400, GROUP_GAP = 2;
9236
+ const GROUP_GAP = 2;
8858
9237
 
8859
9238
  function useWorkflowsTab() {
8860
9239
  const {params: params, setParams: setParams} = usePaneRouter();
@@ -8879,7 +9258,7 @@ function useOpenActivity(entries) {
8879
9258
  }
8880
9259
 
8881
9260
  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, {
9261
+ 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
9262
  defaultOpen: landingDefaultOpen(landing, e.instance._id),
8884
9263
  entry: e,
8885
9264
  onOpenActivity: activityName => setOpenActivity({
@@ -9107,7 +9486,10 @@ function resolveEntry(args) {
9107
9486
  }
9108
9487
  valid.push(name), definition && definitions.push({
9109
9488
  name: name,
9110
- fields: definition.fields
9489
+ fields: definition.fields,
9490
+ ...definition.roleAliases === void 0 ? {} : {
9491
+ roleAliases: definition.roleAliases
9492
+ }
9111
9493
  });
9112
9494
  }
9113
9495
  return {
@@ -9624,6 +10006,7 @@ function WorkflowProvider(props) {
9624
10006
  engine: engine,
9625
10007
  binding: binding,
9626
10008
  mappings: mappings,
10009
+ previewHydration: config.previewHydration,
9627
10010
  mappingIssues: issues,
9628
10011
  mappingStarts: starts,
9629
10012
  mappingFields: fields,
@@ -9631,7 +10014,7 @@ function WorkflowProvider(props) {
9631
10014
  effectHandlers: engine.effectHandlers,
9632
10015
  autoStartByType: autoStartByType,
9633
10016
  autoStartDefinitions: autoStartDefinitions
9634
- }), [ register, unregister, isDocResolved, entriesStore, actualTypes, requestInstance, releaseInstance, requestEvaluation, releaseEvaluation, seedInstance, openStartDialog, closeStartDialog, startRequest2, loading, discoveryInvalid, fireActionFor, editFieldFor, previewFieldFor, discardFieldPreviewFor, drainEffectsFor, completeEffectFor, engine, binding, mappings, issues, starts, fields, latestVersions, autoStartByType, autoStartDefinitions ]);
10017
+ }), [ register, unregister, isDocResolved, entriesStore, actualTypes, requestInstance, releaseInstance, requestEvaluation, releaseEvaluation, seedInstance, openStartDialog, closeStartDialog, startRequest2, loading, discoveryInvalid, fireActionFor, editFieldFor, previewFieldFor, discardFieldPreviewFor, drainEffectsFor, completeEffectFor, engine, binding, mappings, config.previewHydration, issues, starts, fields, latestVersions, autoStartByType, autoStartDefinitions ]);
9635
10018
  /* @__PURE__ */
9636
10019
  return jsxs(WorkflowContext.Provider, {
9637
10020
  value: value,
@@ -9733,7 +10116,7 @@ function logOnce(messages, logged) {
9733
10116
  for (const message of messages) logged.has(message) || (logged.add(message), console.warn(`[workflow-studio-plugin] ${message}`));
9734
10117
  }
9735
10118
 
9736
- function AssigneesInput({value: value, onChange: onChange}) {
10119
+ function AssigneesInput({value: value, onChange: onChange, eligibleRoles: eligibleRoles, roleAliases: roleAliases}) {
9737
10120
  const [open, setOpen] = useState(!1), remove = index => onChange(value.filter((_, i) => i !== index));
9738
10121
  /* @__PURE__ */
9739
10122
  return jsxs(Stack, {
@@ -9767,7 +10150,9 @@ function AssigneesInput({value: value, onChange: onChange}) {
9767
10150
  wrap: "wrap",
9768
10151
  children: /* @__PURE__ */ jsx(DismissablePopover, {
9769
10152
  content: /* @__PURE__ */ jsx(AssigneePicker, {
10153
+ eligibleRoles: eligibleRoles,
9770
10154
  onChange: next => onChange([ ...next ]),
10155
+ roleAliases: roleAliases,
9771
10156
  value: value
9772
10157
  }),
9773
10158
  onDismiss: () => setOpen(!1),
@@ -9836,11 +10221,29 @@ function ChecklistInput({value: value, onChange: onChange}) {
9836
10221
  });
9837
10222
  }
9838
10223
 
9839
- function ReleasePicker({value: value, onChange: onChange}) {
9840
- const {binding: binding} = useWorkflowContext(), {data: data, loading: loading, error: error} = useActiveReleases(), releases = useMemo(() => data.filter(release => release.state === "active").map(release => ({
10224
+ function titleOf(release) {
10225
+ return release.metadata.title ?? release.name;
10226
+ }
10227
+
10228
+ function useReleaseTitles() {
10229
+ const {data: data} = useActiveReleases();
10230
+ return useMemo(() => new Map(data.map(release => [ release.name, titleOf(release) ])), [ data ]);
10231
+ }
10232
+
10233
+ function useActiveReleaseOptions() {
10234
+ const {data: data, loading: loading, error: error} = useActiveReleases(), releases = useMemo(() => data.filter(release => release.state === "active").map(release => ({
9841
10235
  name: release.name,
9842
- title: release.metadata.title ?? release.name
9843
- })).toSorted((a, b) => a.title.localeCompare(b.title)), [ data ]), labelOf = useMemo(() => {
10236
+ title: titleOf(release)
10237
+ })).toSorted((a, b) => a.title.localeCompare(b.title)), [ data ]);
10238
+ return {
10239
+ error: error,
10240
+ loading: loading,
10241
+ releases: releases
10242
+ };
10243
+ }
10244
+
10245
+ function ReleasePicker({value: value, onChange: onChange}) {
10246
+ const {binding: binding} = useWorkflowContext(), {error: error, loading: loading, releases: releases} = useActiveReleaseOptions(), labelOf = useMemo(() => {
9844
10247
  const m = new Map(releases.map(r => [ r.name, r.title ]));
9845
10248
  return name => m.get(name) ?? name;
9846
10249
  }, [ releases ]);
@@ -10108,8 +10511,10 @@ const docRefInput = ({entry: entry, docType: docType, releaseId: releaseId, valu
10108
10511
  onChange: onChange,
10109
10512
  value: value ?? []
10110
10513
  }),
10111
- assignees: ({value: value, onChange: onChange}) => /* @__PURE__ */ jsx(AssigneesInput, {
10514
+ assignees: ({entry: entry, roleAliases: roleAliases, value: value, onChange: onChange}) => /* @__PURE__ */ jsx(AssigneesInput, {
10515
+ eligibleRoles: entry.roles,
10112
10516
  onChange: onChange,
10517
+ roleAliases: roleAliases,
10113
10518
  value: value ?? []
10114
10519
  }),
10115
10520
  array: ({entry: entry, value: value, onChange: onChange}) => {
@@ -10151,7 +10556,7 @@ function FieldInput(props) {
10151
10556
  });
10152
10557
  }
10153
10558
 
10154
- function InitFieldRow({entry: entry, docType: docType, releaseId: releaseId, value: value, onChange: onChange}) {
10559
+ function InitFieldRow({entry: entry, docType: docType, releaseId: releaseId, roleAliases: roleAliases, value: value, onChange: onChange}) {
10155
10560
  const label = entry.title ?? entry.name;
10156
10561
  /* @__PURE__ */
10157
10562
  return jsxs(Stack, {
@@ -10175,6 +10580,7 @@ function InitFieldRow({entry: entry, docType: docType, releaseId: releaseId, val
10175
10580
  entry: entry,
10176
10581
  onChange: onChange,
10177
10582
  releaseId: releaseId,
10583
+ roleAliases: roleAliases,
10178
10584
  value: value
10179
10585
  }) ]
10180
10586
  });
@@ -10224,7 +10630,7 @@ function StartFooter({canStart: canStart, starting: starting, startLabel: startL
10224
10630
  });
10225
10631
  }
10226
10632
 
10227
- function StartInputList({initFields: initFields, mapping: mapping, releaseId: releaseId, values: values, onChange: onChange}) {
10633
+ function StartInputList({initFields: initFields, mapping: mapping, releaseId: releaseId, roleAliases: roleAliases, values: values, onChange: onChange}) {
10228
10634
  return initFields.length === 0 ? /* @__PURE__ */ jsx(Text, {
10229
10635
  muted: !0,
10230
10636
  size: 1,
@@ -10235,6 +10641,7 @@ function StartInputList({initFields: initFields, mapping: mapping, releaseId: re
10235
10641
  entry: entry,
10236
10642
  onChange: v => onChange(entry.name, v),
10237
10643
  releaseId: releaseId,
10644
+ roleAliases: roleAliases,
10238
10645
  value: values[entry.name] ?? null
10239
10646
  }, entry.name))
10240
10647
  });
@@ -10365,6 +10772,7 @@ function StartWorkflowForm({request: request, onClose: onClose}) {
10365
10772
  mapping: mapping,
10366
10773
  onChange: setValue,
10367
10774
  releaseId: releaseId,
10775
+ roleAliases: definition.roleAliases,
10368
10776
  values: values
10369
10777
  }),
10370
10778
  /* @__PURE__ */ jsx(StartFooter, {
@@ -10419,11 +10827,12 @@ function AutoStartInputDialog(props) {
10419
10827
  }),
10420
10828
  /* @__PURE__ */ jsx(Stack, {
10421
10829
  gap: 4,
10422
- children: inputs.map(({entry: entry}) => /* @__PURE__ */ jsx(InitFieldRow, {
10830
+ children: inputs.map(({entry: entry, roleAliases: roleAliases}) => /* @__PURE__ */ jsx(InitFieldRow, {
10423
10831
  docType: docType,
10424
10832
  entry: entry,
10425
10833
  onChange: value => setValue(entry.name, value),
10426
10834
  releaseId: void 0,
10835
+ roleAliases: roleAliases,
10427
10836
  value: values[entry.name] ?? null
10428
10837
  }, entry.name))
10429
10838
  }),
@@ -10452,23 +10861,65 @@ function subjectEntry(definition) {
10452
10861
  return documentSubjectEntry(definition);
10453
10862
  }
10454
10863
 
10455
- function autoStartInputs(args) {
10864
+ function autoStartInputPlan(args) {
10456
10865
  const byName = /* @__PURE__ */ new Map;
10457
10866
  for (const name of args.workflows) {
10458
10867
  const definition = args.definitions.get(name);
10459
10868
  if (definition !== void 0) for (const entry of extraRequiredInputs(definition)) {
10460
- const existing = byName.get(entry.name);
10461
- existing === void 0 ? byName.set(entry.name, {
10869
+ const conflict = mergeAutoStartInput(byName, {
10462
10870
  entry: entry,
10463
- workflows: [ name ]
10464
- }) : existing.workflows.push(name);
10871
+ workflow: name,
10872
+ roleAliases: definition.roleAliases
10873
+ });
10874
+ if (conflict !== void 0) return {
10875
+ inputs: [],
10876
+ conflict: conflict
10877
+ };
10465
10878
  }
10466
10879
  }
10467
- return [ ...byName.values() ];
10880
+ return {
10881
+ inputs: [ ...byName.values() ]
10882
+ };
10883
+ }
10884
+
10885
+ function mergeAutoStartInput(byName, args) {
10886
+ const existing = byName.get(args.entry.name);
10887
+ if (existing === void 0) {
10888
+ byName.set(args.entry.name, {
10889
+ entry: args.entry,
10890
+ workflows: [ args.workflow ],
10891
+ ...args.roleAliases === void 0 ? {} : {
10892
+ roleAliases: args.roleAliases
10893
+ }
10894
+ });
10895
+ return;
10896
+ }
10897
+ if (!sameRoleSet(existing.entry.roles, args.entry.roles)) return `Auto-start input "${args.entry.name}" is shared by workflows "${existing.workflows.join('", "')}" and "${args.workflow}" with different role constraints`;
10898
+ existing.workflows.push(args.workflow), sameRoleAliases(existing.roleAliases, args.roleAliases) || delete existing.roleAliases;
10899
+ }
10900
+
10901
+ function sameRoleSet(left, right) {
10902
+ return roleSetKey(left) === roleSetKey(right);
10903
+ }
10904
+
10905
+ function roleSetKey(roles) {
10906
+ return roles === void 0 ? void 0 : JSON.stringify(sortedUnique(roles));
10907
+ }
10908
+
10909
+ function sameRoleAliases(left, right) {
10910
+ return roleAliasesKey(left) === roleAliasesKey(right);
10911
+ }
10912
+
10913
+ function roleAliasesKey(aliases) {
10914
+ if (aliases !== void 0) return JSON.stringify(Object.entries(aliases).map(([role, fulfillers]) => [ role, sortedUnique(fulfillers) ]).toSorted(([left], [right]) => left.localeCompare(right)));
10915
+ }
10916
+
10917
+ function sortedUnique(values) {
10918
+ return [ ...new Set(values) ].toSorted();
10468
10919
  }
10469
10920
 
10470
10921
  function autoStartAction(args) {
10471
- return args.fired ? args.settled ? "reveal" : "starting" : args.pendingCount === 0 ? "reveal" : args.resolved ? args.needsInput ? "collect" : "silent-start" : "wait";
10922
+ return args.fired ? args.settled ? "reveal" : "starting" : args.blocked || args.pendingCount === 0 ? "reveal" : args.resolved ? args.needsInput ? "collect" : "silent-start" : "wait";
10472
10923
  }
10473
10924
 
10474
10925
  function pendingWorkflows(workflows, live) {
@@ -10525,10 +10976,14 @@ function AutoStartDriver(props) {
10525
10976
  engine: engine
10526
10977
  }), {selectedReleaseId: selectedReleaseId} = usePerspective(), {entries: entries} = useWorkflowsForDocument(rawDocId), docId = getPublishedId(rawDocId), valueRef = useRef(value);
10527
10978
  valueRef.current = value;
10528
- const pending = useMemo(() => pendingWorkflows(workflows, new Set(entries.map(entry => entry.instance.definition))), [ workflows, entries ]), inputs = useMemo(() => autoStartInputs({
10979
+ const pending = useMemo(() => pendingWorkflows(workflows, new Set(entries.map(entry => entry.instance.definition))), [ workflows, entries ]), inputPlan = useMemo(() => autoStartInputPlan({
10529
10980
  workflows: pending,
10530
10981
  definitions: autoStartDefinitions
10531
- }), [ pending, autoStartDefinitions ]), definitionsResolved = workflows.every(name => autoStartDefinitions.has(name)), pendingRef = useRef(pending);
10982
+ }), [ pending, autoStartDefinitions ]), inputs = inputPlan.inputs;
10983
+ useEffect(() => {
10984
+ inputPlan.conflict !== void 0 && console.warn(`[Workflows] Auto-start skipped. ${inputPlan.conflict}`);
10985
+ }, [ inputPlan.conflict ]);
10986
+ const definitionsResolved = workflows.every(name => autoStartDefinitions.has(name)), pendingRef = useRef(pending);
10532
10987
  pendingRef.current = pending;
10533
10988
  const instanceIds = useRef(/* @__PURE__ */ new Map), instanceIdFor = useCallback(workflow => {
10534
10989
  const held = instanceIds.current.get(workflow);
@@ -10580,6 +11035,7 @@ function AutoStartDriver(props) {
10580
11035
  resolved: definitionsResolved && isDocResolved(docId),
10581
11036
  pendingCount: pending.length,
10582
11037
  needsInput: inputs.length > 0,
11038
+ blocked: inputPlan.conflict !== void 0,
10583
11039
  fired: fired,
10584
11040
  settled: settled
10585
11041
  });
@@ -10822,6 +11278,28 @@ function unassignedLineHint(args) {
10822
11278
  };
10823
11279
  }
10824
11280
 
11281
+ function taskCountState(args) {
11282
+ const {entry: entry, identity: identity} = args, freshness = entry.invalid === void 0 ? evaluationFreshness({
11283
+ committed: entry.committed,
11284
+ evaluation: entry.evaluation
11285
+ }) : void 0;
11286
+ return identity.kind === "unavailable" ? {
11287
+ kind: "unknowable"
11288
+ } : identity.kind === "pending" || freshness === "pending" ? {
11289
+ kind: "pending"
11290
+ } : freshness === "stale" ? {
11291
+ kind: "stale"
11292
+ } : {
11293
+ kind: "ready",
11294
+ tasks: assignedTaskCount({
11295
+ instance: entry.instance,
11296
+ committed: entry.committed,
11297
+ evaluation: entry.evaluation,
11298
+ who: identity.identity
11299
+ })
11300
+ };
11301
+ }
11302
+
10825
11303
  function WorkflowFormStrip(props) {
10826
11304
  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
11305
  telemetry.log(WorkflowFormStripClicked), setParams(focusedViewParams(params, focusId));
@@ -10909,7 +11387,7 @@ function InstanceLine({entry: entry, onOpen: onOpen}) {
10909
11387
  aside: unprimed ? void 0 : /* @__PURE__ */ jsx(TaskCountSpinner, {
10910
11388
  entry: entry
10911
11389
  }),
10912
- hint: lineHint(entry, identity),
11390
+ hint: lineHint(entry, identityOf(identity)),
10913
11391
  onOpen: onOpen,
10914
11392
  children: [
10915
11393
  /* @__PURE__ */ jsx(Flex, {
@@ -10968,28 +11446,16 @@ function finishedHint(instance) {
10968
11446
  }
10969
11447
 
10970
11448
  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
- };
11449
+ const identity = useAssignmentIdentity();
11450
+ return taskCountState({
11451
+ entry: entry,
11452
+ identity: identity
11453
+ });
10988
11454
  }
10989
11455
 
10990
11456
  function TaskCountText({entry: entry}) {
10991
11457
  const state = useTaskCountState(entry);
10992
- return state.kind === "pending" || state.kind === "ready" && state.tasks === 0 ? null : /* @__PURE__ */ jsx(Text, {
11458
+ return state.kind === "pending" || state.kind === "unknowable" || state.kind === "ready" && state.tasks === 0 ? null : /* @__PURE__ */ jsx(Text, {
10993
11459
  muted: !0,
10994
11460
  size: 1,
10995
11461
  children: state.kind === "stale" ? "Updating…" : pluralize("assigned task", state.tasks, !0)
@@ -11015,19 +11481,13 @@ function StartWorkflowDialogHost() {
11015
11481
  }
11016
11482
 
11017
11483
  const TOOL_TAB_LABELS = {
11018
- overview: "Workflows",
11019
- "all-documents": "All documents",
11020
- "for-me": "For me"
11484
+ overview: "Overview",
11485
+ "for-me": "For me",
11486
+ definitions: "Definitions"
11021
11487
  }, TOOL_TAB_VALUES = Object.keys(TOOL_TAB_LABELS), TOOL_TABS = TOOL_TAB_VALUES.map(value => ({
11022
11488
  value: value,
11023
11489
  label: TOOL_TAB_LABELS[value]
11024
- })), LANDING_TAB = "overview", TASKS_TAB = "all-documents", WORKFLOW_PAGE_LABELS = {
11025
- workflow: "Documents",
11026
- definition: "Definition"
11027
- }, WORKFLOW_PAGE_VALUES = Object.keys(WORKFLOW_PAGE_LABELS), DEFAULT_WORKFLOW_PAGE = "workflow", WORKFLOW_PAGES = WORKFLOW_PAGE_VALUES.filter(page => page !== DEFAULT_WORKFLOW_PAGE), WORKFLOW_PAGE_TABS = WORKFLOW_PAGE_VALUES.map(value => ({
11028
- value: value,
11029
- label: WORKFLOW_PAGE_LABELS[value]
11030
- })), toolRouter = route.create("/", [ route.create("/instance/:instanceId"), route.create(`/${LANDING_TAB}/:workflowName`, [ route.create("/:workflowPage") ]), route.create("/:workflowsTab") ]);
11490
+ })), LANDING_TAB = "overview", DEFINITIONS_TAB = "definitions", toolRouter = route.create("/", [ route.create(`/${DEFINITIONS_TAB}/:workflowName`), route.create("/:workflowsTab") ]);
11031
11491
 
11032
11492
  function toolTabState(tab) {
11033
11493
  return {
@@ -11045,39 +11505,17 @@ function workflowState(workflowName) {
11045
11505
  };
11046
11506
  }
11047
11507
 
11048
- function instanceState(instanceId) {
11049
- return {
11050
- instanceId: instanceId
11051
- };
11052
- }
11053
-
11054
11508
  function tabSelectionNavigates(current, tab) {
11055
11509
  return !(current.kind === "home" && tab === current.tab);
11056
11510
  }
11057
11511
 
11058
- function toolTabLabel(tab) {
11059
- return TOOL_TAB_LABELS[tab];
11060
- }
11061
-
11062
- function backToTabLabel(tab) {
11063
- return `Back to ${toolTabLabel(tab)}`;
11064
- }
11065
-
11066
- function workflowPageState(page, workflowName) {
11067
- return page === DEFAULT_WORKFLOW_PAGE ? workflowState(workflowName) : {
11068
- ...workflowState(workflowName),
11069
- workflowPage: page
11070
- };
11071
- }
11072
-
11073
11512
  function toolRoute(state) {
11074
- const {instanceId: instanceId, workflowName: workflowName} = state;
11075
- if (typeof instanceId == "string") return {
11076
- kind: "instance",
11077
- instanceId: instanceId,
11078
- tab: TASKS_TAB
11513
+ const {workflowName: workflowName} = state;
11514
+ if (typeof workflowName == "string") return {
11515
+ kind: "definition",
11516
+ workflowName: workflowName,
11517
+ tab: DEFINITIONS_TAB
11079
11518
  };
11080
- if (typeof workflowName == "string") return workflowRoute(workflowName, state.workflowPage);
11081
11519
  const tab = pathSegmentName({
11082
11520
  segment: state.workflowsTab,
11083
11521
  names: TOOL_TAB_VALUES
@@ -11091,24 +11529,11 @@ function toolRoute(state) {
11091
11529
  };
11092
11530
  }
11093
11531
 
11094
- function workflowRoute(workflowName, pageSegment) {
11095
- if (pageSegment === void 0) return {
11096
- kind: "workflow",
11097
- workflowName: workflowName,
11098
- tab: LANDING_TAB
11099
- };
11100
- const page = pathSegmentName({
11101
- segment: pageSegment,
11102
- names: WORKFLOW_PAGES
11103
- });
11104
- return page === void 0 ? {
11105
- kind: "unnamed",
11106
- tab: LANDING_TAB
11107
- } : {
11108
- kind: page,
11109
- workflowName: workflowName,
11110
- tab: LANDING_TAB
11532
+ function definitionsRouteOf(route2) {
11533
+ if (route2.kind === "definition") return {
11534
+ workflowName: route2.workflowName
11111
11535
  };
11536
+ if (route2.kind === "home" && route2.tab === "definitions") return {};
11112
11537
  }
11113
11538
 
11114
11539
  const WorkflowsToolRoot = lazy(() => import("./workflows-tool-root.js"));
@@ -11220,4 +11645,4 @@ function WorkflowRootInput(props) {
11220
11645
  });
11221
11646
  }
11222
11647
 
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 };
11648
+ export { AbortWorkflowDialog, ActivityDetailDialog, ActivityLog, AgoStamp, AlertGlyph, CodeChip, CountedHeading, DismissablePopover, DocPreviewLink, DocRefFace, EmptyState, HoverHint, InstanceFaultNote, InstanceSnapshotBody, InvalidDocNotice, LoadingRow, LogEventOnMount, MetaRow, MetricPair, NoteBanner, RUN_SEARCH_PARAM, RoleAssignedNotice, RowAssignees, SpinnerSlot, TOOL_TABS, TabSwitch, UnreadableDocsNote, WORKFLOW_API_VERSION, WorkflowBoardWorkflowSelected, WorkflowDefinitionDetailViewed, WorkflowInstanceDetailViewed, WorkflowTitleSeedDrifted, WorkflowToolOpened, assigneesOf, countBy, definitionFingerprint, definitionsRouteOf, describeError, dueDatesOf, findActivity, formatShortDateTime, formatTimeAgo, gdrLocality, identityOf, instanceBreadcrumb, instanceTitle, isDueDatePast, isEvaluationStale, isLiveEntry, isOpenActivityStatus, landingState, mappingIssueDetail, namesNoDeployedDefinition, openActivityGone, openableSchemaType, parseStoredDateValue, readDeployedDefinitions, stageTitle, tabSelectionNavigates, toolRoute, toolTabState, useAssignmentIdentity, useAvatarSize, useCapTrimFor, useContainerToken, useCurrentUserId, useDefinition, useLogEventOnMount, useMinuteClock, useProjectMembers, useRadiusToken, useReleaseTitles, useSpaceToken, useTextIconSize, useUserDisplays, useWorkflowContext, useWorkflowInstanceEntry, workflowDefaultDocumentNode, workflowState, workflowStudioPlugin, workflowsView };