@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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- var jsxRuntime = require("react/jsx-runtime"), Timeline = require("@sanity/icons/Timeline"), ui = require("@sanity/ui"), workflowReact = require("@sanity/workflow-react"), react = require("react"), structure = require("sanity/structure"), workflowEngine = require("@sanity/workflow-engine"), Checkmark = require("@sanity/icons/Checkmark"), Close = require("@sanity/icons/Close"), formatDistanceToNow = require("date-fns/formatDistanceToNow"), workflowComponents = require("@sanity/workflow-components"), csm = require("@sanity/client/csm"), telemetry = require("@sanity/telemetry"), workflowStudio = require("@sanity/workflow-studio"), sanity = require("sanity"), Calendar = require("@sanity/icons/Calendar"), User = require("@sanity/icons/User"), Search = require("@sanity/icons/Search"), ChevronRight = require("@sanity/icons/ChevronRight"), Edit = require("@sanity/icons/Edit"), content = require("@sanity/util/content"), InfoOutline = require("@sanity/icons/InfoOutline"), Document = require("@sanity/icons/Document"), WarningOutline = require("@sanity/icons/WarningOutline"), router = require("sanity/router"), types = require("@sanity/types"), Launch = require("@sanity/icons/Launch"), Empty$1 = require("@sanity/icons/Empty"), ErrorFilled = require("@sanity/icons/ErrorFilled"), RemoveCircle = require("@sanity/icons/RemoveCircle"), Add = require("@sanity/icons/Add"), Ulist = require("@sanity/icons/Ulist"), CheckmarkCircle = require("@sanity/icons/CheckmarkCircle"), Bolt = require("@sanity/icons/Bolt"), ChevronDown = require("@sanity/icons/ChevronDown"), pluralize = require("pluralize-esm"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal"), Clock = require("@sanity/icons/Clock"), Cog = require("@sanity/icons/Cog"), ArrowUp = require("@sanity/icons/ArrowUp"), ErrorOutline = require("@sanity/icons/ErrorOutline"), rxjs = require("rxjs"), operators = require("rxjs/operators"), Transfer = require("@sanity/icons/Transfer");
3
+ var jsxRuntime = require("react/jsx-runtime"), Timeline = require("@sanity/icons/Timeline"), ui = require("@sanity/ui"), workflowReact = require("@sanity/workflow-react"), react = require("react"), structure = require("sanity/structure"), workflowEngine = require("@sanity/workflow-engine"), Checkmark = require("@sanity/icons/Checkmark"), Close = require("@sanity/icons/Close"), formatDistanceToNow = require("date-fns/formatDistanceToNow"), workflowComponents = require("@sanity/workflow-components"), csm = require("@sanity/client/csm"), telemetry = require("@sanity/telemetry"), workflowStudio = require("@sanity/workflow-studio"), sanity = require("sanity"), Calendar = require("@sanity/icons/Calendar"), User = require("@sanity/icons/User"), Search = require("@sanity/icons/Search"), ChevronRight = require("@sanity/icons/ChevronRight"), Edit = require("@sanity/icons/Edit"), content = require("@sanity/util/content"), InfoOutline = require("@sanity/icons/InfoOutline"), Document = require("@sanity/icons/Document"), WarningOutline = require("@sanity/icons/WarningOutline"), router = require("sanity/router"), types = require("@sanity/types"), Launch = require("@sanity/icons/Launch"), ErrorOutline = require("@sanity/icons/ErrorOutline"), Empty$1 = require("@sanity/icons/Empty"), ErrorFilled = require("@sanity/icons/ErrorFilled"), RemoveCircle = require("@sanity/icons/RemoveCircle"), Add = require("@sanity/icons/Add"), Ulist = require("@sanity/icons/Ulist"), CheckmarkCircle = require("@sanity/icons/CheckmarkCircle"), Bolt = require("@sanity/icons/Bolt"), ChevronDown = require("@sanity/icons/ChevronDown"), pluralize = require("pluralize-esm"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal"), Clock = require("@sanity/icons/Clock"), Cog = require("@sanity/icons/Cog"), ArrowUp = require("@sanity/icons/ArrowUp"), rxjs = require("rxjs"), operators = require("rxjs/operators"), Transfer = require("@sanity/icons/Transfer");
4
4
 
5
5
  function _interopDefaultCompat(e) {
6
6
  return e && typeof e == "object" && "default" in e ? e : {
@@ -291,7 +291,7 @@ function landingDefaultOpen(landing, instanceId) {
291
291
  if (!(landing === void 0 || !landing.instanceIds.includes(instanceId))) return instanceId === landing.focusedId;
292
292
  }
293
293
 
294
- const WORKFLOWS_TOOL_NAME = "workflows", WORKFLOW_INTENT = "workflow";
294
+ const WORKFLOWS_TOOL_NAME = "workflows", WORKFLOW_INTENT = "workflow", RUN_SEARCH_PARAM = "run";
295
295
 
296
296
  function pathSegmentName(args) {
297
297
  return args.names.find(name => name === args.segment);
@@ -303,9 +303,10 @@ function handlesWorkflowIntent(intent, params) {
303
303
 
304
304
  function workflowIntentState(intent, params) {
305
305
  const instanceId = params.instanceId;
306
- return intent === WORKFLOW_INTENT && typeof instanceId == "string" ? {
307
- instanceId: instanceId
308
- } : null;
306
+ return intent !== WORKFLOW_INTENT || typeof instanceId != "string" ? null : {
307
+ workflowsTab: "overview",
308
+ _searchParams: [ [ RUN_SEARCH_PARAM, instanceId ] ]
309
+ };
309
310
  }
310
311
 
311
312
  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);
@@ -333,16 +334,25 @@ function parseLocalDate(value) {
333
334
  return parsed2.getFullYear() === year && parsed2.getMonth() === (month ?? NaN) - 1 && parsed2.getDate() === day ? parsed2 : /* @__PURE__ */ new Date(NaN);
334
335
  }
335
336
 
337
+ function storedDateKind(value) {
338
+ return /^\d{4}-\d{2}-\d{2}$/.test(value) ? "date" : "datetime";
339
+ }
340
+
336
341
  function parseStoredDateValue(value) {
337
- if (typeof value != "string") return;
338
- const kind = /^\d{4}-\d{2}-\d{2}$/.test(value) ? "date" : "datetime";
339
- return parseDateFieldValue(value, kind);
342
+ if (typeof value == "string") return parseDateFieldValue(value, storedDateKind(value));
340
343
  }
341
344
 
342
345
  function serializeDateFieldValue(date, kind) {
343
346
  return hasTimeOfDay(kind) ? date.toISOString() : `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
344
347
  }
345
348
 
349
+ function isDueDatePast(value, now) {
350
+ const kind = storedDateKind(value);
351
+ if (!hasTimeOfDay(kind)) return value < serializeDateFieldValue(now, kind);
352
+ const due = parseDateFieldValue(value, kind);
353
+ return due !== void 0 && due.getTime() < now.getTime();
354
+ }
355
+
346
356
  class EffectsIncompleteError extends Error {
347
357
  reason;
348
358
  constructor(reason, options) {
@@ -393,14 +403,19 @@ function formatTimeAgo(value) {
393
403
  }
394
404
 
395
405
  function formatShortAgo(value, now) {
406
+ const span = formatShortSpan(value, now);
407
+ return span === "—" ? "—" : span === "now" ? "just now" : `${span} ago`;
408
+ }
409
+
410
+ function formatShortSpan(value, now) {
396
411
  const d = new Date(value);
397
412
  if (Number.isNaN(d.getTime())) return "—";
398
413
  const seconds = Math.floor((now.getTime() - d.getTime()) / 1e3);
399
- if (seconds < 60) return "just now";
414
+ if (seconds < 60) return "now";
400
415
  const minutes = Math.floor(seconds / 60);
401
- if (minutes < 60) return `${minutes}m ago`;
416
+ if (minutes < 60) return `${minutes}m`;
402
417
  const hours = Math.floor(minutes / 60);
403
- return hours < 24 ? `${hours}h ago` : `${Math.floor(hours / 24)}d ago`;
418
+ return hours < 24 ? `${hours}h` : `${Math.floor(hours / 24)}d`;
404
419
  }
405
420
 
406
421
  function stampFace(verb, at) {
@@ -523,7 +538,7 @@ function isLiveEntry(entry) {
523
538
  const isTodoDone = item => item.status === "done", toggledTodoStatus = item => isTodoDone(item) ? "open" : "done";
524
539
 
525
540
  function isTodoOverdue(item, today = /* @__PURE__ */ new Date) {
526
- return !item.dueDate || isTodoDone(item) ? !1 : item.dueDate < serializeDateFieldValue(today, "date");
541
+ return !item.dueDate || isTodoDone(item) ? !1 : isDueDatePast(item.dueDate, today);
527
542
  }
528
543
 
529
544
  function todoEditKind(patch) {
@@ -624,7 +639,7 @@ function todoFieldEditability(args) {
624
639
  }
625
640
 
626
641
  function itemRow(args) {
627
- const {item: item, items: items, field: field, source: source, ticks: ticks, editability: editability} = args, tick = ticks.get(tickKey(field.name, item._key));
642
+ 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;
628
643
  return {
629
644
  item: item,
630
645
  items: items,
@@ -639,6 +654,9 @@ function itemRow(args) {
639
654
  ...editability.editDisabledReason ? {
640
655
  editDisabledReason: editability.editDisabledReason
641
656
  } : {},
657
+ ...eligibleAssigneeRoles === void 0 ? {} : {
658
+ eligibleAssigneeRoles: eligibleAssigneeRoles
659
+ },
642
660
  ...tick ? {
643
661
  tick: tick
644
662
  } : {}
@@ -844,7 +862,7 @@ function concludesHere(actionEval, activityName) {
844
862
  return changesOwnStatus(actionEval.action, activityName) || actionEval.firing?.exitsStage === !0;
845
863
  }
846
864
 
847
- const SEMANTIC_FACE = {
865
+ const DECISION_SEMANTIC_FACE = {
848
866
  "decision.accept": {
849
867
  tone: "positive",
850
868
  icon: Checkmark.CheckmarkIcon
@@ -855,6 +873,15 @@ const SEMANTIC_FACE = {
855
873
  }
856
874
  };
857
875
 
876
+ function isDecisionSemantic(value) {
877
+ return Object.hasOwn(DECISION_SEMANTIC_FACE, value);
878
+ }
879
+
880
+ function decisionSemanticFace(semantics) {
881
+ const semantic = semantics.find(isDecisionSemantic);
882
+ return semantic === void 0 ? void 0 : DECISION_SEMANTIC_FACE[semantic];
883
+ }
884
+
858
885
  function actionButtonFace(args) {
859
886
  const mode = concludesHere(args.actionEval, args.activityName) ? "default" : "ghost";
860
887
  if (actionAdvanceStatus(args.actionEval.action, args.activityName) === "failed") return {
@@ -862,7 +889,7 @@ function actionButtonFace(args) {
862
889
  tone: "critical",
863
890
  icon: void 0
864
891
  };
865
- const [semantic] = args.actionEval.semantics ?? [], face = semantic === void 0 ? void 0 : SEMANTIC_FACE[semantic];
892
+ const face = decisionSemanticFace(args.actionEval.semantics ?? []);
866
893
  return {
867
894
  mode: mode,
868
895
  tone: face?.tone ?? "default",
@@ -1029,24 +1056,10 @@ function dateValuesOf(state, matches) {
1029
1056
  return state.filter(entry => matches(entry._type)).map(entry => entry.value).filter(value => typeof value == "string" && value !== "");
1030
1057
  }
1031
1058
 
1032
- function datesOf(state) {
1033
- return dateValuesOf(state, isDateFieldKind);
1034
- }
1035
-
1036
1059
  function dueDatesOf(state) {
1037
1060
  return dateValuesOf(state, isDueDateFieldKind);
1038
1061
  }
1039
1062
 
1040
- function committedRowFace(row) {
1041
- return {
1042
- activityName: row.activityName,
1043
- title: row.title,
1044
- status: row.status,
1045
- automated: !1,
1046
- blocked: !1
1047
- };
1048
- }
1049
-
1050
1063
  function activityRowProps(args) {
1051
1064
  const {activityEval: activityEval, activityState: activityState, editableFields: editableFields} = args, state = activityState.get(activityEval.activity.name) ?? [], controlArgs = {
1052
1065
  activity: activityEval.activity,
@@ -1145,15 +1158,15 @@ function useWorkflowInstanceEntry(instanceId, options = {}) {
1145
1158
  }
1146
1159
 
1147
1160
  const WorkflowToolOpened = telemetry.defineEvent({
1148
- name: "Editorial Workflows Studio Plugin Tool Opened",
1149
- version: 2,
1161
+ name: "Workflows Studio Plugin Tool Opened",
1162
+ version: 3,
1150
1163
  description: "The Workflows tool was opened or its tab was switched"
1151
1164
  }), WorkflowDocumentViewOpened = telemetry.defineEvent({
1152
- name: "Editorial Workflows Studio Plugin Document View Opened",
1165
+ name: "Workflows Studio Plugin Document View Opened",
1153
1166
  version: 1,
1154
1167
  description: "The Workflows document view was opened or its tab was switched"
1155
1168
  }), WorkflowInstanceDetailViewed = telemetry.defineEvent({
1156
- name: "Editorial Workflows Studio Plugin Instance Detail Viewed",
1169
+ name: "Workflows Studio Plugin Instance Detail Viewed",
1157
1170
  version: 1,
1158
1171
  description: "The Workflows tool's per-instance detail view was opened"
1159
1172
  });
@@ -1166,67 +1179,63 @@ function definitionFingerprint(definition) {
1166
1179
  }
1167
1180
 
1168
1181
  const WorkflowDefinitionDetailViewed = telemetry.defineEvent({
1169
- name: "Editorial Workflows Studio Plugin Definition Detail Viewed",
1182
+ name: "Workflows Studio Plugin Definition Detail Viewed",
1170
1183
  version: 1,
1171
1184
  description: "A workflow's definition page was opened in the Workflows tool"
1172
1185
  }), WorkflowFormStripClicked = telemetry.defineEvent({
1173
- name: "Editorial Workflows Studio Plugin Form Strip Clicked",
1186
+ name: "Workflows Studio Plugin Form Strip Clicked",
1174
1187
  version: 1,
1175
1188
  description: "The form strip's instance line was clicked, opening the document view"
1176
1189
  }), WorkflowDocumentLinkClicked = telemetry.defineEvent({
1177
- name: "Editorial Workflows Studio Plugin Document Link Clicked",
1178
- version: 2,
1190
+ name: "Workflows Studio Plugin Document Link Clicked",
1191
+ version: 3,
1179
1192
  description: "A document link was clicked, opening the referenced document's editor"
1180
1193
  }), WorkflowStartDialogOpened = telemetry.defineEvent({
1181
- name: "Editorial Workflows Studio Plugin Start Dialog Opened",
1194
+ name: "Workflows Studio Plugin Start Dialog Opened",
1182
1195
  version: 1,
1183
1196
  description: "The start-workflow dialog was opened"
1184
1197
  }), WorkflowStartDialogSubmitted = telemetry.defineEvent({
1185
- name: "Editorial Workflows Studio Plugin Start Dialog Submitted",
1198
+ name: "Workflows Studio Plugin Start Dialog Submitted",
1186
1199
  version: 1,
1187
1200
  description: "The start-workflow dialog's confirm was pressed"
1188
1201
  }), WorkflowAutoStartRan = telemetry.defineEvent({
1189
- name: "Editorial Workflows Studio Plugin Auto Start Ran",
1202
+ name: "Workflows Studio Plugin Auto Start Ran",
1190
1203
  version: 1,
1191
1204
  description: "A fresh document's auto-start ran — one start request per configured workflow"
1192
1205
  }), WorkflowActionControlUsed = telemetry.defineEvent({
1193
- name: "Editorial Workflows Studio Plugin Action Control Used",
1194
- version: 2,
1206
+ name: "Workflows Studio Plugin Action Control Used",
1207
+ version: 3,
1195
1208
  description: "An action-firing control was used, attributed to its UI surface"
1196
1209
  }), WorkflowFieldControlUsed = telemetry.defineEvent({
1197
- name: "Editorial Workflows Studio Plugin Field Control Used",
1198
- version: 1,
1210
+ name: "Workflows Studio Plugin Field Control Used",
1211
+ version: 2,
1199
1212
  description: "A field-editing control committed an edit, attributed to its UI surface"
1200
1213
  }), WorkflowActivityDialogOpened = telemetry.defineEvent({
1201
- name: "Editorial Workflows Studio Plugin Activity Dialog Opened",
1202
- version: 1,
1214
+ name: "Workflows Studio Plugin Activity Dialog Opened",
1215
+ version: 2,
1203
1216
  description: "The activity detail dialog was opened"
1204
- }), WorkflowTaskFiltersApplied = telemetry.defineEvent({
1205
- name: "Editorial Workflows Studio Plugin Task Filters Applied",
1206
- version: 1,
1207
- description: "The tool's task-filter menu closed with a changed selection"
1208
1217
  }), WorkflowBoardWorkflowSelected = telemetry.defineEvent({
1209
- name: "Editorial Workflows Studio Plugin Board Workflow Selected",
1210
- version: 2,
1211
- description: "The workflow documents page settled on the workflow it's showing"
1218
+ name: "Workflows Studio Plugin Board Workflow Selected",
1219
+ version: 3,
1220
+ description: "The Overview's board display settled on the workflow it's showing"
1212
1221
  }), WorkflowTitleSeedDrifted = telemetry.defineEvent({
1213
- name: "Editorial Workflows Studio Plugin Title Seed Drifted",
1222
+ name: "Workflows Studio Plugin Title Seed Drifted",
1214
1223
  version: 1,
1215
1224
  description: "A cold-start seeded preview title diverged from the live preview pipeline"
1216
1225
  }), WorkflowTodoToggled = telemetry.defineEvent({
1217
- name: "Editorial Workflows Studio Plugin Todo Toggled",
1226
+ name: "Workflows Studio Plugin Todo Toggled",
1218
1227
  version: 1,
1219
1228
  description: "A todo checkbox was toggled, attributed to its UI surface and write seam"
1220
1229
  }), WorkflowTodoEdited = telemetry.defineEvent({
1221
- name: "Editorial Workflows Studio Plugin Todo Edited",
1230
+ name: "Workflows Studio Plugin Todo Edited",
1222
1231
  version: 1,
1223
1232
  description: "A non-toggle todo-list write, attributed to its UI surface and gesture"
1224
1233
  }), WorkflowAbortDialogOpened = telemetry.defineEvent({
1225
- name: "Editorial Workflows Studio Plugin Abort Dialog Opened",
1234
+ name: "Workflows Studio Plugin Abort Dialog Opened",
1226
1235
  version: 1,
1227
1236
  description: "The abort-workflow confirm was opened"
1228
1237
  }), WorkflowAbortDialogSubmitted = telemetry.defineEvent({
1229
- name: "Editorial Workflows Studio Plugin Abort Dialog Submitted",
1238
+ name: "Workflows Studio Plugin Abort Dialog Submitted",
1230
1239
  version: 1,
1231
1240
  description: "The abort-workflow confirm was submitted, carrying the attempt's outcome"
1232
1241
  });
@@ -1262,6 +1271,12 @@ function scalarValidationIssue(args) {
1262
1271
  return issues === void 0 ? void 0 : `${args.label}: ${issues.join("; ")}`;
1263
1272
  }
1264
1273
 
1274
+ function countBy(rows, keysOf) {
1275
+ const counts = /* @__PURE__ */ new Map;
1276
+ for (const row of rows) for (const key of keysOf(row)) counts.set(key, (counts.get(key) ?? 0) + 1);
1277
+ return counts;
1278
+ }
1279
+
1265
1280
  function personActor(user) {
1266
1281
  const roles = user.roles.map(role => typeof role == "string" ? role : role.name);
1267
1282
  return {
@@ -1293,10 +1308,16 @@ function userDisplayFor(args) {
1293
1308
  return {
1294
1309
  id: args.id,
1295
1310
  name: member?.displayName ?? self?.name ?? args.id,
1296
- imageUrl: member?.imageUrl ?? self?.profileImage ?? void 0
1311
+ imageUrl: member?.imageUrl ?? self?.profileImage ?? void 0,
1312
+ loginProvider: member?.loginProvider
1297
1313
  };
1298
1314
  }
1299
1315
 
1316
+ function disambiguatedNames(displays) {
1317
+ const counts = countBy(displays, display => [ display.name ]);
1318
+ return displays.map(display => (counts.get(display.name) ?? 0) < 2 || display.loginProvider === void 0 ? display.name : `${display.name} (${workflowComponents.providerTitle(display.loginProvider) ?? display.loginProvider})`);
1319
+ }
1320
+
1300
1321
  function useUserDisplays(ids) {
1301
1322
  const {members: members} = useProjectMembers(), {users: users} = workflowStudio.useStudioProjectUsers(), meRaw = sanity.useCurrentUser(), me = react.useMemo(() => meRaw === null ? null : {
1302
1323
  ...meRaw,
@@ -1329,21 +1350,32 @@ function bridgedSelfId(users, meId) {
1329
1350
  return users.find(user => user.membership.id === meId)?.profile?.sanityUserId ?? classified.globalId ?? (classified.namespace === "project" ? void 0 : meId);
1330
1351
  }
1331
1352
 
1332
- function useBridgedSelf() {
1353
+ function useSelfState() {
1333
1354
  const me = sanity.useCurrentUser(), {users: users, loading: loading, error: error} = workflowStudio.useStudioProjectUsers();
1334
- if (!me) return;
1335
- const id = bridgedSelfId(users, me.id);
1336
- if (id !== void 0) return {
1337
- id: id,
1338
- roles: me.roles ?? []
1355
+ if (!me) return {
1356
+ kind: "unavailable"
1339
1357
  };
1340
- if (!loading) {
1341
- if (error !== void 0) {
1342
- warnSelfUnavailableOnce(`load:${me.id}`, `workflow: the project member directory failed to load ("${workflowEngine.errorMessage(error)}") — the logged-in user ("${me.id}") is project-scoped, and only that directory maps it to the account-global identity the engine stores, so workflow assignment and authoring controls stay disabled. A mounted view does not retry on its own; the next consumer to ask reloads the directory once its backoff has passed.`);
1343
- return;
1358
+ const id = bridgedSelfId(users, me.id);
1359
+ return id !== void 0 ? {
1360
+ kind: "resolved",
1361
+ self: {
1362
+ id: id,
1363
+ roles: me.roles ?? []
1344
1364
  }
1345
- warnSelfUnavailableOnce(`unresolved:${me.id}`, `workflow: the project member directory cannot resolve the logged-in user ("${me.id}") to an account-global identity — the engine stores account-global user ids only, so workflow assignment and authoring controls stay disabled. Typically this user is not a member of this project.`);
1346
- }
1365
+ } : loading ? {
1366
+ kind: "pending"
1367
+ } : error !== void 0 ? (warnSelfUnavailableOnce(`load:${me.id}`, `workflow: the project member directory failed to load ("${workflowEngine.errorMessage(error)}") — the logged-in user ("${me.id}") is project-scoped, and only that directory maps it to the account-global identity the engine stores, so workflow assignment and authoring controls stay disabled. A mounted view does not retry on its own; the next consumer to ask reloads the directory once its backoff has passed.`),
1368
+ {
1369
+ kind: "unavailable"
1370
+ }) : (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.`),
1371
+ {
1372
+ kind: "unavailable"
1373
+ });
1374
+ }
1375
+
1376
+ function useBridgedSelf() {
1377
+ const state = useSelfState();
1378
+ return state.kind === "resolved" ? state.self : void 0;
1347
1379
  }
1348
1380
 
1349
1381
  function useSelfActor() {
@@ -1352,13 +1384,26 @@ function useSelfActor() {
1352
1384
  }
1353
1385
 
1354
1386
  function useAssignmentIdentity() {
1355
- const self = useBridgedSelf();
1356
- if (self !== void 0) return {
1357
- userId: self.id,
1358
- roles: self.roles.map(r => r.name)
1387
+ const state = useSelfState();
1388
+ if (state.kind !== "resolved") return state;
1389
+ const {id: id, roles: roles} = state.self;
1390
+ return {
1391
+ kind: "resolved",
1392
+ identity: {
1393
+ userId: id,
1394
+ roles: roles.map(r => r.name)
1395
+ }
1359
1396
  };
1360
1397
  }
1361
1398
 
1399
+ function identityOf(state) {
1400
+ return state.kind === "resolved" ? state.identity : void 0;
1401
+ }
1402
+
1403
+ function useCurrentUserId() {
1404
+ return identityOf(useAssignmentIdentity())?.userId;
1405
+ }
1406
+
1362
1407
  const projectMembersByUsers = /* @__PURE__ */ new WeakMap;
1363
1408
 
1364
1409
  function projectMembersFrom(users) {
@@ -1466,6 +1511,8 @@ function buildParams(decls, values) {
1466
1511
  };
1467
1512
  }
1468
1513
 
1514
+ const LOADING_CUE_DELAY_MS = 400;
1515
+
1469
1516
  function useDelayedFlag(active, delayMs) {
1470
1517
  const [held, setHeld] = react.useState(!1);
1471
1518
  return react.useEffect(() => {
@@ -1747,23 +1794,27 @@ function MemberPicker({selectedIds: selectedIds, onSelect: onSelect, unassignRow
1747
1794
  });
1748
1795
  }
1749
1796
 
1750
- function AssigneePicker({value: value, onChange: onChange}) {
1797
+ function AssigneePicker({value: value, onChange: onChange, eligibleRoles: eligibleRoles, roleAliases: roleAliases}) {
1751
1798
  const state = useProjectMembers();
1752
1799
  /* @__PURE__ */
1753
1800
  return jsxRuntime.jsx(workflowComponents.AssigneePicker, {
1754
1801
  ...state,
1802
+ eligibleRoles: eligibleRoles,
1755
1803
  onChange: onChange,
1804
+ roleAliases: roleAliases,
1756
1805
  value: value
1757
1806
  });
1758
1807
  }
1759
1808
 
1760
- function SingleAssigneePicker({current: current, onPick: onPick}) {
1809
+ function SingleAssigneePicker({current: current, onPick: onPick, eligibleRoles: eligibleRoles, roleAliases: roleAliases}) {
1761
1810
  /* @__PURE__ */
1762
1811
  return jsxRuntime.jsx(AssigneePicker, {
1812
+ eligibleRoles: eligibleRoles,
1763
1813
  onChange: next => {
1764
1814
  const picked = singleAssigneePick(current, next);
1765
1815
  picked !== void 0 && onPick(picked);
1766
1816
  },
1817
+ roleAliases: roleAliases,
1767
1818
  value: current ? [ current ] : []
1768
1819
  });
1769
1820
  }
@@ -2076,7 +2127,6 @@ const TOAST_ID = {
2076
2127
  effectResolve: "workflow-effect-resolve",
2077
2128
  effectsDrain: "workflow-effects-drain",
2078
2129
  effectsIncomplete: "workflow-effects-incomplete",
2079
- orphanSettle: "workflow-orphan-settle",
2080
2130
  start: "workflow-start"
2081
2131
  };
2082
2132
 
@@ -2473,15 +2523,14 @@ function Separator() {
2473
2523
  }
2474
2524
 
2475
2525
  function stageTitle(definition, stageName) {
2476
- return definition?.stages.find(stage => stage.name === stageName)?.title ?? stageName;
2477
- }
2478
-
2479
- function findActivityNode(args) {
2480
- return args.definition?.stages.find(s => s.name === args.stageName)?.activities?.find(a => a.name === args.activityName);
2526
+ return workflowEngine.findStageNode({
2527
+ definition: definition,
2528
+ stageName: stageName
2529
+ })?.title ?? stageName;
2481
2530
  }
2482
2531
 
2483
2532
  function findActivity(args) {
2484
- const activity = findActivityNode(args);
2533
+ const activity = workflowEngine.findActivityNode(args);
2485
2534
  return {
2486
2535
  title: activity?.title ?? args.activityName,
2487
2536
  description: activity?.description
@@ -2937,8 +2986,7 @@ function PendingDocFace({bareId: bareId, layout: layout}) {
2937
2986
  children: id
2938
2987
  });
2939
2988
  }
2940
- return compact ?
2941
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
2989
+ return compact ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
2942
2990
  align: "center",
2943
2991
  gap: 2,
2944
2992
  children: [
@@ -3612,9 +3660,11 @@ function AssigneeField({field: field, instanceId: instanceId, onSave: onSave, on
3612
3660
  children: /* @__PURE__ */ jsxRuntime.jsx(DismissablePopover, {
3613
3661
  content: /* @__PURE__ */ jsxRuntime.jsx(SingleAssigneePicker, {
3614
3662
  current: cur,
3663
+ eligibleRoles: field.roles,
3615
3664
  onPick: picked => {
3616
3665
  setOpen(!1), pick(picked);
3617
- }
3666
+ },
3667
+ roleAliases: field.roleAliases
3618
3668
  }),
3619
3669
  onDismiss: () => setOpen(!1),
3620
3670
  open: open,
@@ -3945,6 +3995,267 @@ function EditableField({field: field, entry: entry, description: description, in
3945
3995
  });
3946
3996
  }
3947
3997
 
3998
+ function useDrainEffects(instanceId) {
3999
+ const {drainEffectsFor: drainEffectsFor} = useWorkflowContext(), toast = useWorkflowToast(), [busy, setBusy] = react.useState(!1);
4000
+ return {
4001
+ busy: busy,
4002
+ run: async () => {
4003
+ setBusy(!0);
4004
+ try {
4005
+ await drainEffectsFor(instanceId), toast.push({
4006
+ id: TOAST_ID.effectsDrain,
4007
+ status: "info",
4008
+ title: "Ran the registered handlers"
4009
+ });
4010
+ } catch (err) {
4011
+ toast.push({
4012
+ id: TOAST_ID.effectsDrain,
4013
+ status: "error",
4014
+ title: "Failed to run pending effects",
4015
+ description: describeError(err)
4016
+ });
4017
+ } finally {
4018
+ setBusy(!1);
4019
+ }
4020
+ }
4021
+ };
4022
+ }
4023
+
4024
+ function isRunnable(verb) {
4025
+ return verb === "drain-effects";
4026
+ }
4027
+
4028
+ function effectLabel(effect) {
4029
+ return effect.title ?? effect.name;
4030
+ }
4031
+
4032
+ const ACTION_LABEL = {
4033
+ "drain-effects": "Run the step again"
4034
+ }, REMEDY_TEXT = {
4035
+ "retry-effect": "The step has to run again before this workflow can continue.",
4036
+ "reset-activity": "Reset the task to try it again, or skip it.",
4037
+ "set-stage": "Move the workflow on to its next stage yourself."
4038
+ };
4039
+
4040
+ function detailOf(cause) {
4041
+ switch (cause.kind) {
4042
+ case "failed-effect":
4043
+ return `The step “${effectLabel(cause.effect)}” failed`;
4044
+
4045
+ case "hung-effect":
4046
+ return `The step “${effectLabel(cause.effect)}” didn’t finish`;
4047
+
4048
+ case "failed-activity":
4049
+ return "This task failed";
4050
+
4051
+ default:
4052
+ return summaryOf(cause);
4053
+ }
4054
+ }
4055
+
4056
+ function summaryOf(cause) {
4057
+ switch (cause.kind) {
4058
+ case "failed-effect":
4059
+ return "An automated step failed";
4060
+
4061
+ case "hung-effect":
4062
+ return "An automated step didn’t finish";
4063
+
4064
+ case "failed-activity":
4065
+ return "A task failed";
4066
+
4067
+ case "no-transition-fires":
4068
+ return "Every task is done, but nothing moves this workflow on";
4069
+
4070
+ case "transition-unevaluable":
4071
+ return "This workflow is waiting on something it can’t read yet, and will move on by itself";
4072
+ }
4073
+ }
4074
+
4075
+ function activityDeclaringEffect(args) {
4076
+ return workflowEngine.findStageNode(args)?.activities?.find(activity => (activity.actions ?? []).some(action => (action.effects ?? []).some(effect => effect.name === args.effectName)))?.name;
4077
+ }
4078
+
4079
+ function faultingActivity(args) {
4080
+ const {cause: cause} = args;
4081
+ if (cause.kind === "failed-activity") return cause.activity;
4082
+ if (cause.kind === "failed-effect" || cause.kind === "hung-effect") return activityDeclaringEffect({
4083
+ definition: args.definition,
4084
+ effectName: cause.effect.name,
4085
+ stageName: args.stageName
4086
+ });
4087
+ }
4088
+
4089
+ function isRecoverable(cause) {
4090
+ return cause.kind === "transition-unevaluable";
4091
+ }
4092
+
4093
+ function diagnosisOf(args) {
4094
+ if (args.evaluation !== void 0) return workflowEngine.diagnoseInstance(workflowEngine.diagnoseInputFromEvaluation(args.evaluation));
4095
+ const cause = workflowEngine.documentStuckCause({
4096
+ instance: args.instance,
4097
+ definition: args.definition
4098
+ });
4099
+ return cause === void 0 ? void 0 : {
4100
+ state: "stuck",
4101
+ cause: cause
4102
+ };
4103
+ }
4104
+
4105
+ function instanceFault(args) {
4106
+ const diagnosis = diagnosisOf(args);
4107
+ if (diagnosis === void 0 || diagnosis.state !== "stuck") return;
4108
+ const verbs = workflowEngine.remediationsFor(diagnosis).map(remediation2 => remediation2.verb);
4109
+ return {
4110
+ activity: faultingActivity({
4111
+ cause: diagnosis.cause,
4112
+ definition: args.definition,
4113
+ stageName: args.instance.currentStage
4114
+ }),
4115
+ detail: detailOf(diagnosis.cause),
4116
+ summary: summaryOf(diagnosis.cause),
4117
+ remedies: verbs.flatMap(verb => REMEDY_TEXT[verb] ?? []),
4118
+ actions: verbs.flatMap(verb => isRunnable(verb) ? [ {
4119
+ verb: verb,
4120
+ label: ACTION_LABEL[verb]
4121
+ } ] : []),
4122
+ recoverable: isRecoverable(diagnosis.cause)
4123
+ };
4124
+ }
4125
+
4126
+ function looksLikeDefinition(value) {
4127
+ return typeof value == "object" && value !== null && Array.isArray(value.stages);
4128
+ }
4129
+
4130
+ const parsed = /* @__PURE__ */ new WeakMap;
4131
+
4132
+ function definitionSnapshotOf(instance) {
4133
+ if (parsed.has(instance)) return parsed.get(instance);
4134
+ let definition;
4135
+ try {
4136
+ const candidate = workflowEngine.parseDefinitionSnapshot(instance);
4137
+ if (!looksLikeDefinition(candidate)) throw new Error(`definitionSnapshot on "${instance._id}" parsed to a non-definition shape`);
4138
+ definition = candidate;
4139
+ } catch (err) {
4140
+ console.error("[workflow-studio-plugin] unreadable definitionSnapshot:", err), definition = void 0;
4141
+ }
4142
+ return parsed.set(instance, definition), definition;
4143
+ }
4144
+
4145
+ function useDefinition(entry) {
4146
+ return react.useMemo(() => {
4147
+ if (entry) return entry.evaluation ? entry.evaluation.definition : definitionSnapshotOf(entry.instance);
4148
+ }, [ entry ]);
4149
+ }
4150
+
4151
+ function AbortedBadge({instance: instance}) {
4152
+ return workflowEngine.terminalState(instance) !== "aborted" ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
4153
+ fontSize: 1,
4154
+ tone: "critical",
4155
+ children: "Aborted"
4156
+ });
4157
+ }
4158
+
4159
+ function OlderDefinitionBadge({instance: instance}) {
4160
+ const {latestDefinitionVersions: latestDefinitionVersions} = useWorkflowContext(), latest = latestDefinitionVersions.get(instance.definition);
4161
+ return latest === void 0 || latest <= instance.pinnedVersion ? null : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4162
+ description: "Running workflows keep the version they started on",
4163
+ text: `Started on v${instance.pinnedVersion}, latest is now v${latest}`,
4164
+ textWeight: "medium",
4165
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
4166
+ fontSize: 1,
4167
+ tone: "caution",
4168
+ children: "Older version"
4169
+ })
4170
+ });
4171
+ }
4172
+
4173
+ function useInstanceFault(entry) {
4174
+ return instanceFault({
4175
+ definition: useDefinition(entry),
4176
+ evaluation: entry.evaluation,
4177
+ instance: entry.instance
4178
+ });
4179
+ }
4180
+
4181
+ function InstanceFaultNote({entry: entry, scope: scope}) {
4182
+ const fault = useInstanceFault(entry);
4183
+ if (fault === void 0 || scope.kind === "task" && fault.activity !== scope.activityName) return null;
4184
+ const Icon = fault.recoverable ? WarningOutline.WarningOutlineIcon : ErrorOutline.ErrorOutlineIcon;
4185
+ /* @__PURE__ */
4186
+ return jsxRuntime.jsx(ui.Card, {
4187
+ padding: 3,
4188
+ radius: 3,
4189
+ tone: fault.recoverable ? "caution" : "critical",
4190
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
4191
+ align: "flex-start",
4192
+ gap: 3,
4193
+ children: [
4194
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4195
+ size: 1,
4196
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, {})
4197
+ }),
4198
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
4199
+ flex: 1,
4200
+ gap: 3,
4201
+ children: [
4202
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4203
+ size: 1,
4204
+ weight: "medium",
4205
+ children: scope.kind === "run" ? fault.summary : fault.detail
4206
+ }), scope.kind === "run" ? /* @__PURE__ */ jsxRuntime.jsx(RunControl, {
4207
+ activity: fault.activity,
4208
+ onOpenActivity: scope.onOpenActivity
4209
+ }) : /* @__PURE__ */ jsxRuntime.jsx(TaskControls, {
4210
+ actions: fault.actions,
4211
+ instanceId: entry.instance._id,
4212
+ remedies: fault.remedies
4213
+ }) ]
4214
+ }) ]
4215
+ })
4216
+ });
4217
+ }
4218
+
4219
+ function RunControl({activity: activity, onOpenActivity: onOpenActivity}) {
4220
+ return activity === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4221
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4222
+ fontSize: 1,
4223
+ mode: "ghost",
4224
+ onClick: () => onOpenActivity(activity),
4225
+ padding: 2,
4226
+ text: "View task"
4227
+ })
4228
+ });
4229
+ }
4230
+
4231
+ const RUN_VERB = {
4232
+ "drain-effects": drain => drain.run()
4233
+ };
4234
+
4235
+ function TaskControls({actions: actions, instanceId: instanceId, remedies: remedies}) {
4236
+ const drain = useDrainEffects(instanceId);
4237
+ /* @__PURE__ */
4238
+ return jsxRuntime.jsxs(jsxRuntime.Fragment, {
4239
+ children: [ remedies.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4240
+ muted: !0,
4241
+ size: 1,
4242
+ children: remedies.join(" ")
4243
+ }), actions.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4244
+ gap: 2,
4245
+ children: actions.map(action => /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4246
+ disabled: drain.busy,
4247
+ fontSize: 1,
4248
+ mode: "ghost",
4249
+ onClick: () => {
4250
+ RUN_VERB[action.verb](drain);
4251
+ },
4252
+ padding: 2,
4253
+ text: action.label
4254
+ }, action.verb))
4255
+ }) ]
4256
+ });
4257
+ }
4258
+
3948
4259
  function MetaRow({children: children, fillHeight: fillHeight, label: label}) {
3949
4260
  /* @__PURE__ */
3950
4261
  return jsxRuntime.jsxs(ui.Flex, {
@@ -4032,6 +4343,12 @@ function useSpaceToken(index) {
4032
4343
  return value;
4033
4344
  }
4034
4345
 
4346
+ function useRadiusToken(index) {
4347
+ const value = ui.useTheme_v2().radius[index];
4348
+ if (value === void 0) throw new Error(`theme is missing radius token ${index}`);
4349
+ return value;
4350
+ }
4351
+
4035
4352
  function useContainerToken(index) {
4036
4353
  const value = ui.useTheme_v2().container[index];
4037
4354
  if (value === void 0) throw new Error(`theme is missing container token ${index}`);
@@ -4072,10 +4389,9 @@ function useBadgeCapTrim() {
4072
4389
  };
4073
4390
  }
4074
4391
 
4075
- function SpinnerSlot({busy: busy}) {
4076
- const size = useTextIconSize(), trim = useCapTrimFor(size);
4077
- /* @__PURE__ */
4078
- return jsxRuntime.jsx(ui.Flex, {
4392
+ function SpinnerSlot({busy: busy, reserve: reserve = !0}) {
4393
+ const size = useTextIconSize(), trim = useCapTrimFor(size), spinning = useDelayedFlag(busy, LOADING_CUE_DELAY_MS);
4394
+ return !spinning && !reserve ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4079
4395
  align: "center",
4080
4396
  flex: "none",
4081
4397
  justify: "center",
@@ -4085,7 +4401,7 @@ function SpinnerSlot({busy: busy}) {
4085
4401
  marginTop: trim,
4086
4402
  width: size
4087
4403
  },
4088
- children: busy ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
4404
+ children: spinning ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
4089
4405
  muted: !0,
4090
4406
  size: 1
4091
4407
  }) : null
@@ -4093,6 +4409,7 @@ function SpinnerSlot({busy: busy}) {
4093
4409
  }
4094
4410
 
4095
4411
  function LoadingRow({label: label, padding: padding}) {
4412
+ const announced = useDelayedFlag(!0, LOADING_CUE_DELAY_MS);
4096
4413
  /* @__PURE__ */
4097
4414
  return jsxRuntime.jsxs(ui.Flex, {
4098
4415
  align: "center",
@@ -4103,12 +4420,11 @@ function LoadingRow({label: label, padding: padding}) {
4103
4420
  children: [
4104
4421
  /* @__PURE__ */ jsxRuntime.jsx(SpinnerSlot, {
4105
4422
  busy: !0
4106
- }),
4107
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4423
+ }), announced ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4108
4424
  muted: !0,
4109
4425
  size: 1,
4110
4426
  children: label
4111
- }) ]
4427
+ }) : null ]
4112
4428
  });
4113
4429
  }
4114
4430
 
@@ -4169,8 +4485,7 @@ function RowAssignees({assignees: assignees, hint: hint}) {
4169
4485
  const bothKinds = roles.length > 0 && displays.length > 0;
4170
4486
  /* @__PURE__ */
4171
4487
  return jsxRuntime.jsx(HoverHint, {
4172
- content:
4173
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
4488
+ content: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
4174
4489
  gap: 3,
4175
4490
  children: [ roles.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(HintSection, {
4176
4491
  names: roles.map(workflowComponents.roleLabel),
@@ -4178,7 +4493,7 @@ function RowAssignees({assignees: assignees, hint: hint}) {
4178
4493
  label: "Roles"
4179
4494
  } : {}
4180
4495
  }) : null, displays.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(HintSection, {
4181
- names: displays.map(display => display.name),
4496
+ names: disambiguatedNames(displays),
4182
4497
  ...bothKinds ? {
4183
4498
  label: "Members"
4184
4499
  } : {}
@@ -4257,8 +4572,7 @@ function AssignActivityControl({instanceId: instanceId, state: state, surface: s
4257
4572
  }
4258
4573
  }, handleClick = e => {
4259
4574
  e.stopPropagation(), setOpen(v => !v);
4260
- }, button = assignees.length > 0 ?
4261
- /* @__PURE__ */ jsxRuntime.jsx(AvatarButton, {
4575
+ }, button = assignees.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(AvatarButton, {
4262
4576
  "aria-label": "Assignees",
4263
4577
  as: "span",
4264
4578
  avatar: /* @__PURE__ */ jsxRuntime.jsx(RowAssignees, {
@@ -4288,9 +4602,11 @@ function AssignActivityControl({instanceId: instanceId, state: state, surface: s
4288
4602
  label: "Loading…",
4289
4603
  padding: 3
4290
4604
  }) : /* @__PURE__ */ jsxRuntime.jsx(AssigneePicker, {
4605
+ eligibleRoles: state.field.roles,
4291
4606
  onChange: next => {
4292
4607
  change(next);
4293
4608
  },
4609
+ roleAliases: state.field.roleAliases,
4294
4610
  value: current
4295
4611
  })
4296
4612
  }),
@@ -4375,49 +4691,39 @@ function ForMeEmptyState() {
4375
4691
  });
4376
4692
  }
4377
4693
 
4378
- function looksLikeDefinition(value) {
4379
- return typeof value == "object" && value !== null && Array.isArray(value.stages);
4380
- }
4381
-
4382
- const parsed = /* @__PURE__ */ new WeakMap;
4383
-
4384
- function definitionSnapshotOf(instance) {
4385
- if (parsed.has(instance)) return parsed.get(instance);
4386
- let definition;
4387
- try {
4388
- const candidate = workflowEngine.parseDefinitionSnapshot(instance);
4389
- if (!looksLikeDefinition(candidate)) throw new Error(`definitionSnapshot on "${instance._id}" parsed to a non-definition shape`);
4390
- definition = candidate;
4391
- } catch (err) {
4392
- console.error("[workflow-studio-plugin] unreadable definitionSnapshot:", err), definition = void 0;
4393
- }
4394
- return parsed.set(instance, definition), definition;
4395
- }
4694
+ const OVERDUE_HINT = "Now overdue";
4396
4695
 
4397
- function useDefinition(entry) {
4398
- return react.useMemo(() => {
4399
- if (entry) return entry.evaluation ? entry.evaluation.definition : definitionSnapshotOf(entry.instance);
4400
- }, [ entry ]);
4696
+ function hintFace(args) {
4697
+ return args.overdue ? args.reason === void 0 ? {
4698
+ text: OVERDUE_HINT
4699
+ } : {
4700
+ text: OVERDUE_HINT,
4701
+ description: args.reason
4702
+ } : args.reason === void 0 ? void 0 : {
4703
+ text: args.reason
4704
+ };
4401
4705
  }
4402
4706
 
4403
- function AbortedBadge({instance: instance}) {
4404
- return workflowEngine.terminalState(instance) !== "aborted" ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
4405
- fontSize: 1,
4406
- tone: "critical",
4407
- children: "Aborted"
4707
+ function DueDate({dueDate: dueDate, hintDisabled: hintDisabled, overdue: overdue, reason: reason}) {
4708
+ const face = hintFace({
4709
+ overdue: overdue,
4710
+ reason: reason
4711
+ }), text = overdue ? /* @__PURE__ */ jsxRuntime.jsx(sanity.TextWithTone, {
4712
+ size: 1,
4713
+ tone: "caution",
4714
+ children: formatDate(dueDate)
4715
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4716
+ muted: !0,
4717
+ size: 1,
4718
+ children: formatDate(dueDate)
4408
4719
  });
4409
- }
4410
-
4411
- function OlderDefinitionBadge({instance: instance}) {
4412
- const {latestDefinitionVersions: latestDefinitionVersions} = useWorkflowContext(), latest = latestDefinitionVersions.get(instance.definition);
4413
- return latest === void 0 || latest <= instance.pinnedVersion ? null : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4414
- description: "Running workflows keep the version they started on",
4415
- text: `Started on v${instance.pinnedVersion}, latest is now v${latest}`,
4416
- textWeight: "medium",
4417
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
4418
- fontSize: 1,
4419
- tone: "caution",
4420
- children: "Older version"
4720
+ return face === void 0 ? text :
4721
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4722
+ align: "center",
4723
+ children: /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4724
+ ...face,
4725
+ disabled: hintDisabled === !0,
4726
+ children: text
4421
4727
  })
4422
4728
  });
4423
4729
  }
@@ -4457,18 +4763,6 @@ function CheckboxSlot({children: children}) {
4457
4763
  });
4458
4764
  }
4459
4765
 
4460
- function DueDateText({dueDate: dueDate, overdue: overdue}) {
4461
- return overdue ? /* @__PURE__ */ jsxRuntime.jsx(sanity.TextWithTone, {
4462
- size: 1,
4463
- tone: "caution",
4464
- children: formatDate(dueDate)
4465
- }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4466
- muted: !0,
4467
- size: 1,
4468
- children: formatDate(dueDate)
4469
- });
4470
- }
4471
-
4472
4766
  function DueDateEditor({dueDate: dueDate, onDone: onDone, onPatch: onPatch}) {
4473
4767
  /* @__PURE__ */
4474
4768
  return jsxRuntime.jsx(workflowComponents.ClearableDatePicker, {
@@ -4488,8 +4782,9 @@ function DueDateEditor({dueDate: dueDate, onDone: onDone, onPatch: onPatch}) {
4488
4782
  }
4489
4783
 
4490
4784
  function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4491
- const [open, setOpen] = react.useState(!1), overdue = isTodoOverdue(item), display = item.dueDate ? /* @__PURE__ */ jsxRuntime.jsx(DueDateText, {
4785
+ const [open, setOpen] = react.useState(!1), overdue = isTodoOverdue(item), display = item.dueDate ? /* @__PURE__ */ jsxRuntime.jsx(DueDate, {
4492
4786
  dueDate: item.dueDate,
4787
+ hintDisabled: open,
4493
4788
  overdue: overdue
4494
4789
  }) : null;
4495
4790
  return editable ? /* @__PURE__ */ jsxRuntime.jsx(DismissablePopover, {
@@ -4500,18 +4795,14 @@ function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4500
4795
  }),
4501
4796
  onDismiss: () => setOpen(!1),
4502
4797
  open: open,
4503
- children: display ? /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4504
- disabled: !overdue || open,
4505
- text: "Now overdue",
4506
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4507
- "aria-label": "Change due date",
4508
- fontSize: 1,
4509
- mode: "bleed",
4510
- onClick: () => setOpen(v => !v),
4511
- padding: 2,
4512
- tone: overdue ? "caution" : "default",
4513
- children: display
4514
- })
4798
+ children: display ? /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
4799
+ "aria-label": "Change due date",
4800
+ fontSize: 1,
4801
+ mode: "bleed",
4802
+ onClick: () => setOpen(v => !v),
4803
+ padding: 2,
4804
+ tone: overdue ? "caution" : "default",
4805
+ children: display
4515
4806
  }) : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4516
4807
  disabled: open,
4517
4808
  text: "Set due date",
@@ -4525,13 +4816,10 @@ function DueDateControl({item: item, editable: editable, onPatch: onPatch}) {
4525
4816
  radius: 2
4526
4817
  })
4527
4818
  })
4528
- }) : display && overdue ? /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
4529
- text: "Now overdue",
4530
- children: display
4531
4819
  }) : display;
4532
4820
  }
4533
4821
 
4534
- function AssigneeControl({item: item, editable: editable, onPatch: onPatch}) {
4822
+ function AssigneeControl({item: item, editable: editable, eligibleRoles: eligibleRoles, onPatch: onPatch, roleAliases: roleAliases}) {
4535
4823
  const [open, setOpen] = react.useState(!1), current = item.assignee ?? null, chip = current ? /* @__PURE__ */ jsxRuntime.jsx(RowAssignees, {
4536
4824
  assignees: [ current ]
4537
4825
  }) : null;
@@ -4539,11 +4827,13 @@ function AssigneeControl({item: item, editable: editable, onPatch: onPatch}) {
4539
4827
  content:
4540
4828
  /* @__PURE__ */ jsxRuntime.jsx(SingleAssigneePicker, {
4541
4829
  current: current,
4830
+ eligibleRoles: eligibleRoles,
4542
4831
  onPick: picked => {
4543
4832
  setOpen(!1), onPatch({
4544
4833
  assignee: picked
4545
4834
  });
4546
- }
4835
+ },
4836
+ roleAliases: roleAliases
4547
4837
  }),
4548
4838
  onDismiss: () => setOpen(!1),
4549
4839
  open: open,
@@ -4645,8 +4935,7 @@ function BreadcrumbTail({segments: segments, style: style}) {
4645
4935
  }
4646
4936
 
4647
4937
  function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
4648
- const frame =
4649
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
4938
+ const frame = /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
4650
4939
  align: "center",
4651
4940
  children: [
4652
4941
  /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
@@ -4680,8 +4969,7 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
4680
4969
  padding: 1,
4681
4970
  radius: 3,
4682
4971
  children: frame
4683
- }) :
4684
- /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
4972
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
4685
4973
  as: "button",
4686
4974
  onClick: onOpen,
4687
4975
  padding: 1,
@@ -4693,7 +4981,7 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
4693
4981
  });
4694
4982
  }
4695
4983
 
4696
- function TodoItemRowView({breadcrumb: breadcrumb, row: row, onToggle: onToggle, onPatch: onPatch, onRemove: onRemove}) {
4984
+ function TodoItemRowView({breadcrumb: breadcrumb, row: row, onToggle: onToggle, onPatch: onPatch, onRemove: onRemove, roleAliases: roleAliases}) {
4697
4985
  const {canClick: canClick, hint: hint} = rowInteractivity(row), editable = row.editTarget !== void 0, checkbox = /* @__PURE__ */ jsxRuntime.jsx(TodoCheckbox, {
4698
4986
  canClick: canClick,
4699
4987
  checked: isTodoDone(row.item),
@@ -4710,8 +4998,10 @@ function TodoItemRowView({breadcrumb: breadcrumb, row: row, onToggle: onToggle,
4710
4998
  }),
4711
4999
  /* @__PURE__ */ jsxRuntime.jsx(AssigneeControl, {
4712
5000
  editable: editable,
5001
+ eligibleRoles: row.eligibleAssigneeRoles,
4713
5002
  item: row.item,
4714
- onPatch: onPatch
5003
+ onPatch: onPatch,
5004
+ roleAliases: roleAliases
4715
5005
  }), onRemove === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(RemoveItemButton, {
4716
5006
  onClick: onRemove
4717
5007
  }) ]
@@ -4888,6 +5178,7 @@ function TodoItemsList({breadcrumb: breadcrumb, entry: entry, filter: filter, sh
4888
5178
  onToggle: () => {
4889
5179
  toggleRow(row);
4890
5180
  },
5181
+ roleAliases: definition?.roleAliases,
4891
5182
  row: row
4892
5183
  }, rowKey(row)))
4893
5184
  }), addControl === null ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
@@ -5180,9 +5471,15 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
5180
5471
  const detail = deriveActivityDetail({
5181
5472
  entry: entry,
5182
5473
  activityName: activityName
5183
- });
5474
+ }), faultingActivity2 = useInstanceFault(entry)?.activity;
5184
5475
  if (!detail) return null;
5185
- const instanceId = entry.instance._id, notice = blockedNotice(detail), hasBody = detail.state.length > 0 || notice !== void 0;
5476
+ const instanceId = entry.instance._id, notice = blockedNotice(detail), fault = /* @__PURE__ */ jsxRuntime.jsx(InstanceFaultNote, {
5477
+ entry: entry,
5478
+ scope: {
5479
+ kind: "task",
5480
+ activityName: activityName
5481
+ }
5482
+ }), faulted = faultingActivity2 === activityName, hasBody = detail.state.length > 0 || notice !== void 0 || faulted;
5186
5483
  /* @__PURE__ */
5187
5484
  return jsxRuntime.jsxs(ui.Dialog, {
5188
5485
  footer: detail.presentation.terminalActions.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(TerminalFooter, {
@@ -5223,7 +5520,7 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
5223
5520
  padding: 4,
5224
5521
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
5225
5522
  gap: 5,
5226
- children: [ detail.state.map(field => /* @__PURE__ */ jsxRuntime.jsx(FieldRow, {
5523
+ children: [ fault, detail.state.map(field => /* @__PURE__ */ jsxRuntime.jsx(FieldRow, {
5227
5524
  activityName: activityName,
5228
5525
  detail: detail,
5229
5526
  entry: entry,
@@ -5404,6 +5701,18 @@ function CountedLabel({count: count, label: label}) {
5404
5701
  });
5405
5702
  }
5406
5703
 
5704
+ function CountedHeading({count: count, title: title}) {
5705
+ /* @__PURE__ */
5706
+ return jsxRuntime.jsx(CountedLabel, {
5707
+ count: count,
5708
+ label: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5709
+ size: 1,
5710
+ weight: "semibold",
5711
+ children: title
5712
+ })
5713
+ });
5714
+ }
5715
+
5407
5716
  function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5408
5717
  /* @__PURE__ */
5409
5718
  return jsxRuntime.jsxs(ui.Flex, {
@@ -5411,14 +5720,10 @@ function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5411
5720
  gap: 3,
5412
5721
  paddingLeft: 2,
5413
5722
  paddingY: end === void 0 ? 3 : 1,
5414
- children: [
5415
- /* @__PURE__ */ jsxRuntime.jsx(CountedLabel, {
5416
- count: count,
5417
- label: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5418
- size: 1,
5419
- weight: "semibold",
5420
- children: title
5421
- })
5723
+ children: [
5724
+ /* @__PURE__ */ jsxRuntime.jsx(CountedHeading, {
5725
+ count: count,
5726
+ title: title
5422
5727
  }), busy === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(SpinnerSlot, {
5423
5728
  busy: busy
5424
5729
  }), end === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
@@ -5430,21 +5735,85 @@ function GroupHeading({busy: busy, count: count, title: title, end: end}) {
5430
5735
  });
5431
5736
  }
5432
5737
 
5738
+ const EMPTY_METRIC_OPACITY = .5;
5739
+
5740
+ function MetricText({children: children, tone: tone}) {
5741
+ return tone === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5742
+ muted: !0,
5743
+ size: 1,
5744
+ children: children
5745
+ }) : /* @__PURE__ */ jsxRuntime.jsx(sanity.TextWithTone, {
5746
+ size: 1,
5747
+ tone: tone,
5748
+ children: children
5749
+ });
5750
+ }
5751
+
5752
+ function MetricPair({Icon: Icon, empty: empty, face: face, tone: tone, value: value}) {
5753
+ /* @__PURE__ */
5754
+ return jsxRuntime.jsx(HoverHint, {
5755
+ ...face,
5756
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
5757
+ align: "center",
5758
+ gap: 2,
5759
+ style: empty ? {
5760
+ opacity: EMPTY_METRIC_OPACITY
5761
+ } : void 0,
5762
+ children: [
5763
+ /* @__PURE__ */ jsxRuntime.jsx(MetricText, {
5764
+ tone: tone,
5765
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, {
5766
+ style: {
5767
+ color: "inherit"
5768
+ }
5769
+ })
5770
+ }),
5771
+ /* @__PURE__ */ jsxRuntime.jsx(MetricText, {
5772
+ tone: tone,
5773
+ children: value
5774
+ }) ]
5775
+ })
5776
+ });
5777
+ }
5778
+
5779
+ function AlertGlyph({Icon: Icon, hint: hint, label: label, tone: tone}) {
5780
+ /* @__PURE__ */
5781
+ return jsxRuntime.jsx(HoverHint, {
5782
+ text: hint,
5783
+ children: /* @__PURE__ */ jsxRuntime.jsx(sanity.TextWithTone, {
5784
+ size: 1,
5785
+ tone: tone,
5786
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, {
5787
+ ...label === void 0 ? {} : {
5788
+ "aria-label": label,
5789
+ role: "img"
5790
+ },
5791
+ style: {
5792
+ color: "inherit"
5793
+ }
5794
+ })
5795
+ })
5796
+ });
5797
+ }
5798
+
5433
5799
  function ActivityDateControl({instanceId: instanceId, state: state, surface: surface, dueDates: dueDates}) {
5434
5800
  const editField = useEditField(surface), {save: save} = useSaveField({
5435
5801
  instanceId: instanceId
5436
5802
  }), [open, setOpen] = react.useState(!1), raw = dateControlValue({
5437
5803
  state: state,
5438
5804
  dueDates: dueDates
5439
- }), display = raw === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
5440
- muted: !0,
5441
- size: 1,
5442
- children: formatDate(raw)
5805
+ }), overdue = raw !== void 0 && isDueDatePast(raw, /* @__PURE__ */ new Date), dueDate = reason => raw === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(DueDate, {
5806
+ dueDate: raw,
5807
+ hintDisabled: open,
5808
+ overdue: overdue,
5809
+ ...reason === void 0 ? {} : {
5810
+ reason: reason
5811
+ }
5443
5812
  });
5444
- if (state.kind === "none") return display;
5445
- if (state.kind === "closed") return display === null ? null : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
5446
- text: state.reason,
5447
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
5813
+ if (state.kind === "none") return dueDate();
5814
+ if (state.kind === "closed") {
5815
+ const display = dueDate(state.reason);
5816
+ return display === null ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
5448
5817
  "aria-label": "Date",
5449
5818
  as: "span",
5450
5819
  fontSize: 1,
@@ -5453,8 +5822,8 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5453
5822
  onMouseDown: stopRowMouseDown,
5454
5823
  padding: 2,
5455
5824
  children: display
5456
- })
5457
- });
5825
+ });
5826
+ }
5458
5827
  const kind = dateControlKind(state), commit = (mode, value) => {
5459
5828
  state.kind === "editable" && save(() => editField({
5460
5829
  instanceId: instanceId,
@@ -5490,7 +5859,7 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5490
5859
  }),
5491
5860
  onDismiss: () => setOpen(!1),
5492
5861
  open: open,
5493
- children: display ? /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
5862
+ children: raw !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
5494
5863
  "aria-label": "Change date",
5495
5864
  as: "span",
5496
5865
  fontSize: 1,
@@ -5498,7 +5867,7 @@ function ActivityDateControl({instanceId: instanceId, state: state, surface: sur
5498
5867
  onClick: handleClick,
5499
5868
  onMouseDown: stopRowMouseDown,
5500
5869
  padding: 2,
5501
- children: display
5870
+ children: dueDate()
5502
5871
  }) : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
5503
5872
  disabled: open,
5504
5873
  text: "Set date",
@@ -5588,12 +5957,11 @@ function TitleTag({hint: hint, tone: tone, children: children}) {
5588
5957
  });
5589
5958
  }
5590
5959
 
5591
- function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees, assignState: assignState, dateState: dateState, dueDates: dueDates, instanceId: instanceId, onOpen: onOpen, surface: surface, terminalActions: terminalActions = []}) {
5960
+ function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees, assignState: assignState, dateState: dateState, dueDates: dueDates, faultHint: faultHint, instanceId: instanceId, onOpen: onOpen, surface: surface, terminalActions: terminalActions = []}) {
5592
5961
  const settled = workflowEngine.isTerminalActivityStatus(face.status), dimmed = settledDim(settled);
5593
5962
  /* @__PURE__ */
5594
5963
  return jsxRuntime.jsxs(WorkRow, {
5595
- end:
5596
- /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
5964
+ end: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
5597
5965
  children: [
5598
5966
  /* @__PURE__ */ jsxRuntime.jsx(ActivityDateControl, {
5599
5967
  dueDates: dueDates,
@@ -5621,6 +5989,10 @@ function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees,
5621
5989
  /* @__PURE__ */ jsxRuntime.jsx(ActivityRowTitle, {
5622
5990
  dimmed: dimmed,
5623
5991
  children: face.title
5992
+ }), faultHint === void 0 || face.status === "failed" ? null : /* @__PURE__ */ jsxRuntime.jsx(AlertGlyph, {
5993
+ Icon: ErrorOutline.ErrorOutlineIcon,
5994
+ hint: faultHint,
5995
+ tone: "critical"
5624
5996
  }), face.automated ? /* @__PURE__ */ jsxRuntime.jsx(TitleTag, {
5625
5997
  hint: "Completes on its own — no user action is needed",
5626
5998
  children: "Automated"
@@ -5636,9 +6008,8 @@ function ActivityRow({face: face, breadcrumb: breadcrumb, assignees: assignees,
5636
6008
  }
5637
6009
 
5638
6010
  function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity, surface: surface}) {
5639
- const activities = (entry.evaluation?.currentStage.activities ?? []).filter(t => !t.scopedOut), activityState = stateByActivity(entry.instance);
5640
- return activities.length === 0 ?
5641
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
6011
+ const activities = (entry.evaluation?.currentStage.activities ?? []).filter(t => !t.scopedOut), activityState = stateByActivity(entry.instance), fault = useInstanceFault(entry);
6012
+ return activities.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
5642
6013
  paddingX: 1,
5643
6014
  paddingY: 3,
5644
6015
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
@@ -5654,6 +6025,9 @@ function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity, surface:
5654
6025
  activityState: activityState,
5655
6026
  editableFields: entry.evaluation?.editableFields
5656
6027
  }),
6028
+ ...fault?.activity === t.activity.name ? {
6029
+ faultHint: fault.summary
6030
+ } : {},
5657
6031
  instanceId: entry.instance._id,
5658
6032
  onOpen: () => onOpenActivity(t.activity.name),
5659
6033
  surface: surface
@@ -5663,8 +6037,7 @@ function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity, surface:
5663
6037
 
5664
6038
  function instanceNotice(entry) {
5665
6039
  const {instance: instance, committed: committed, evaluation: evaluation} = entry, state = workflowEngine.terminalState(instance);
5666
- if (state !== "in-flight")
5667
- /* @__PURE__ */
6040
+ if (state !== "in-flight") /* @__PURE__ */
5668
6041
  return jsxRuntime.jsx(ui.Box, {
5669
6042
  paddingX: 1,
5670
6043
  paddingY: 2,
@@ -5800,7 +6173,10 @@ function progressFillPercent(face) {
5800
6173
  }
5801
6174
 
5802
6175
  function deriveFieldPills(args) {
5803
- const {scope: scope, definition: definition, instance: instance, committed: committed, evaluation: evaluation} = args, declared = scope === "workflow" ? definition?.fields ?? [] : definition?.stages.find(s => s.name === instance.currentStage)?.fields ?? [], entries = scope === "workflow" ? instance.fields : workflowEngine.findOpenStageEntry(instance)?.fields ?? [], entriesByName = new Map(entries.map(entry => [ entry.name, entry ])), editables = isEvaluationCurrent({
6176
+ const {scope: scope, definition: definition, instance: instance, committed: committed, evaluation: evaluation} = args, declared = scope === "workflow" ? definition?.fields ?? [] : workflowEngine.findStageNode({
6177
+ definition: definition,
6178
+ stageName: instance.currentStage
6179
+ })?.fields ?? [], entries = scope === "workflow" ? instance.fields : workflowEngine.findOpenStageEntry(instance)?.fields ?? [], entriesByName = new Map(entries.map(entry => [ entry.name, entry ])), editables = isEvaluationCurrent({
5804
6180
  committed: committed,
5805
6181
  evaluation: evaluation
5806
6182
  }) ? editablesByName(evaluation, scope) : /* @__PURE__ */ new Map, declaredNames = new Set(declared.map(d => d.name));
@@ -5849,7 +6225,7 @@ function UserAvatar({id: id}) {
5849
6225
  function UserAvatarGroup({ids: ids, hint: hint}) {
5850
6226
  const displays = useUserDisplays(ids);
5851
6227
  return displays.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
5852
- text: displays.map(d => d.name).join(", "),
6228
+ text: disambiguatedNames(displays).join(", "),
5853
6229
  ...hint === void 0 ? {} : {
5854
6230
  description: hint
5855
6231
  },
@@ -5908,8 +6284,7 @@ function FieldPills({entry: entry, scope: scope, definition: definition, surface
5908
6284
  pill: pill,
5909
6285
  scope: scope,
5910
6286
  surface: surface
5911
- }, pill.name)), overflow > 0 ?
5912
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
6287
+ }, pill.name)), overflow > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
5913
6288
  mode: "bleed",
5914
6289
  onClick: () => setExpanded(v => !v),
5915
6290
  padding: 2,
@@ -6348,9 +6723,11 @@ function AssigneePill({entry: entry, pill: pill, editability: editability, surfa
6348
6723
  return jsxRuntime.jsx(PillPopover, {
6349
6724
  content: /* @__PURE__ */ jsxRuntime.jsx(SingleAssigneePicker, {
6350
6725
  current: current,
6726
+ eligibleRoles: editability.roles,
6351
6727
  onPick: picked => {
6352
6728
  setOpen(!1), pick(picked);
6353
- }
6729
+ },
6730
+ roleAliases: editability.roleAliases
6354
6731
  }),
6355
6732
  onDismiss: () => setOpen(!1),
6356
6733
  onToggle: () => setOpen(v => !v),
@@ -6364,13 +6741,15 @@ function AssigneesPill({entry: entry, pill: pill, editability: editability, surf
6364
6741
  entry: entry,
6365
6742
  editability: editability,
6366
6743
  surface: surface
6367
- }), current = pill.face.kind === "assignees" ? pill.face.assignees : [];
6744
+ }), current = pill.face.kind === "assignees" ? pill.face.assignees : [], change = next => {
6745
+ saving || save(() => set(next));
6746
+ };
6368
6747
  /* @__PURE__ */
6369
6748
  return jsxRuntime.jsx(PillPopover, {
6370
6749
  content: /* @__PURE__ */ jsxRuntime.jsx(AssigneePicker, {
6371
- onChange: next => {
6372
- saving || save(() => set(next));
6373
- },
6750
+ eligibleRoles: editability.roles,
6751
+ onChange: change,
6752
+ roleAliases: editability.roleAliases,
6374
6753
  value: current
6375
6754
  }),
6376
6755
  onDismiss: () => setOpen(!1),
@@ -6956,6 +7335,24 @@ function historyLine(h, titles) {
6956
7335
  return historyLiner[h._type](h, titles);
6957
7336
  }
6958
7337
 
7338
+ function AgoStamp({at: at, now: now}) {
7339
+ /* @__PURE__ */
7340
+ return jsxRuntime.jsx(ui.Flex, {
7341
+ align: "center",
7342
+ children: /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
7343
+ text: formatShortDateTime(at),
7344
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
7345
+ muted: !0,
7346
+ size: 1,
7347
+ style: {
7348
+ whiteSpace: "nowrap"
7349
+ },
7350
+ children: formatShortAgo(at, now)
7351
+ })
7352
+ })
7353
+ });
7354
+ }
7355
+
6959
7356
  function CodeChip({value: value}) {
6960
7357
  /* @__PURE__ */
6961
7358
  return jsxRuntime.jsx("code", {
@@ -6990,6 +7387,27 @@ function HintedMenuButton({hint: hint, ...menuButtonProps}) {
6990
7387
  });
6991
7388
  }
6992
7389
 
7390
+ function InlineEmphasis({children: children}) {
7391
+ const {font: font} = ui.useTheme_v2();
7392
+ /* @__PURE__ */
7393
+ return jsxRuntime.jsx("span", {
7394
+ style: {
7395
+ fontWeight: font.text.weights.medium
7396
+ },
7397
+ children: children
7398
+ });
7399
+ }
7400
+
7401
+ const MINUTE_MS = 6e4;
7402
+
7403
+ function useMinuteClock() {
7404
+ const [now, setNow] = react.useState(() => /* @__PURE__ */ new Date);
7405
+ return react.useEffect(() => {
7406
+ const timer = setInterval(() => setNow(/* @__PURE__ */ new Date), MINUTE_MS);
7407
+ return () => clearInterval(timer);
7408
+ }, []), now;
7409
+ }
7410
+
6993
7411
  function historyWindow(args) {
6994
7412
  const {entries: entries, expanded: expanded} = args;
6995
7413
  return {
@@ -6999,28 +7417,10 @@ function historyWindow(args) {
6999
7417
  }
7000
7418
 
7001
7419
  function PendingEffectRows({instance: instance, now: now}) {
7002
- const {drainEffectsFor: drainEffectsFor, completeEffectFor: completeEffectFor, effectHandlers: effectHandlers} = useWorkflowContext(), toast = useWorkflowToast(), [busy, setBusy] = react.useState(!1), pending = instance.pendingEffects ?? [];
7420
+ const {completeEffectFor: completeEffectFor, effectHandlers: effectHandlers} = useWorkflowContext(), toast = useWorkflowToast(), drain = useDrainEffects(instance._id), [resolving, setResolving] = react.useState(!1), pending = instance.pendingEffects ?? [];
7003
7421
  if (pending.length === 0) return null;
7004
- const runRegistered = async () => {
7005
- setBusy(!0);
7006
- try {
7007
- await drainEffectsFor(instance._id), toast.push({
7008
- id: TOAST_ID.effectsDrain,
7009
- status: "info",
7010
- title: "Ran the registered handlers"
7011
- });
7012
- } catch (err) {
7013
- toast.push({
7014
- id: TOAST_ID.effectsDrain,
7015
- status: "error",
7016
- title: "Failed to run pending effects",
7017
- description: describeError(err)
7018
- });
7019
- } finally {
7020
- setBusy(!1);
7021
- }
7022
- }, resolve = async (effectKey, status) => {
7023
- setBusy(!0);
7422
+ const resolve = async (effectKey, status) => {
7423
+ setResolving(!0);
7024
7424
  try {
7025
7425
  await completeEffectFor(instance._id, {
7026
7426
  effectKey: effectKey,
@@ -7039,18 +7439,20 @@ function PendingEffectRows({instance: instance, now: now}) {
7039
7439
  description: describeError(err)
7040
7440
  });
7041
7441
  } finally {
7042
- setBusy(!1);
7442
+ setResolving(!1);
7043
7443
  }
7044
7444
  };
7045
7445
  /* @__PURE__ */
7046
7446
  return jsxRuntime.jsx(jsxRuntime.Fragment, {
7047
7447
  children: pending.map(fx => /* @__PURE__ */ jsxRuntime.jsx(PendingEffectRow, {
7048
- busy: busy,
7448
+ busy: drain.busy || resolving,
7049
7449
  effect: fx,
7050
7450
  instanceId: instance._id,
7051
7451
  now: now,
7052
7452
  onResolve: status => resolve(fx._key, status),
7053
- onRunHandlers: runRegistered,
7453
+ onRunHandlers: () => {
7454
+ drain.run();
7455
+ },
7054
7456
  registered: fx.name in effectHandlers
7055
7457
  }, fx._key))
7056
7458
  });
@@ -7134,12 +7536,7 @@ function PendingEffectRow({busy: busy, effect: effect, instanceId: instanceId, n
7134
7536
  }
7135
7537
 
7136
7538
  function ActivityLog({instance: instance, definition: definition, windowed: windowed = !0}) {
7137
- const titles = react.useMemo(() => makeTitleResolver(definition), [ definition ]), entries = react.useMemo(() => instance.history.toReversed(), [ instance.history ]), [now, setNow] = react.useState(() => /* @__PURE__ */ new Date), [expanded, setExpanded] = react.useState(!1);
7138
- react.useEffect(() => {
7139
- const timer = setInterval(() => setNow(/* @__PURE__ */ new Date), 6e4);
7140
- return () => clearInterval(timer);
7141
- }, []);
7142
- const history = historyWindow({
7539
+ const titles = react.useMemo(() => makeTitleResolver(definition), [ definition ]), entries = react.useMemo(() => instance.history.toReversed(), [ instance.history ]), now = useMinuteClock(), [expanded, setExpanded] = react.useState(!1), history = historyWindow({
7143
7540
  entries: entries,
7144
7541
  expanded: expanded || !windowed
7145
7542
  });
@@ -7181,16 +7578,9 @@ function FeedStamp({at: at, now: now}) {
7181
7578
  size: 1,
7182
7579
  children: "·"
7183
7580
  }),
7184
- /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
7185
- text: formatDateTime(at),
7186
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
7187
- muted: !0,
7188
- size: 1,
7189
- style: {
7190
- whiteSpace: "nowrap"
7191
- },
7192
- children: formatShortAgo(at, now)
7193
- })
7581
+ /* @__PURE__ */ jsxRuntime.jsx(AgoStamp, {
7582
+ at: at,
7583
+ now: now
7194
7584
  }) ]
7195
7585
  });
7196
7586
  }
@@ -7242,12 +7632,9 @@ function HistoryRow({entry: entry, now: now, titles: titles}) {
7242
7632
  }
7243
7633
 
7244
7634
  function ActorNameSpan({id: id}) {
7245
- const {name: name} = useUserDisplay(id), {font: font} = ui.useTheme_v2();
7635
+ const {name: name} = useUserDisplay(id);
7246
7636
  /* @__PURE__ */
7247
- return jsxRuntime.jsx("span", {
7248
- style: {
7249
- fontWeight: font.text.weights.medium
7250
- },
7637
+ return jsxRuntime.jsx(InlineEmphasis, {
7251
7638
  children: name
7252
7639
  });
7253
7640
  }
@@ -7302,8 +7689,7 @@ function InstanceCardMenu({entry: entry, title: title}) {
7302
7689
  return jsxRuntime.jsxs(jsxRuntime.Fragment, {
7303
7690
  children: [
7304
7691
  /* @__PURE__ */ jsxRuntime.jsx(ui.MenuButton, {
7305
- button:
7306
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
7692
+ button: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
7307
7693
  "aria-label": `More options for ${title}`,
7308
7694
  fontSize: 1,
7309
7695
  icon: EllipsisHorizontal.EllipsisHorizontalIcon,
@@ -7624,8 +8010,7 @@ function StageOverviewCard({overview: overview}) {
7624
8010
  size: 1,
7625
8011
  weight: "medium",
7626
8012
  children: overview.title
7627
- }), overview.description === void 0 ? null :
7628
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
8013
+ }), overview.description === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
7629
8014
  muted: !0,
7630
8015
  size: 1,
7631
8016
  style: {
@@ -7655,7 +8040,6 @@ function StageOverviewCard({overview: overview}) {
7655
8040
  }
7656
8041
 
7657
8042
  function AdvanceCard({advance: advance}) {
7658
- const {font: font} = ui.useTheme_v2();
7659
8043
  /* @__PURE__ */
7660
8044
  return jsxRuntime.jsx(ui.Card, {
7661
8045
  padding: 3,
@@ -7667,14 +8051,10 @@ function AdvanceCard({advance: advance}) {
7667
8051
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
7668
8052
  size: 1,
7669
8053
  children: [ "Advances to ",
7670
- /* @__PURE__ */ jsxRuntime.jsx("span", {
7671
- style: {
7672
- fontWeight: font.text.weights.medium
7673
- },
8054
+ /* @__PURE__ */ jsxRuntime.jsx(InlineEmphasis, {
7674
8055
  children: advance.toTitle
7675
8056
  }), ADVANCE_TAILS[advance.kind] ]
7676
- }), advance.kind !== "conditions" ? null : advance.lines.map((line, index) =>
7677
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
8057
+ }), advance.kind !== "conditions" ? null : advance.lines.map((line, index) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
7678
8058
  muted: !0,
7679
8059
  size: 1,
7680
8060
  style: {
@@ -8728,8 +9108,7 @@ const VIEW_TAB_CODEC = workflowsTabCodec([ "overview", "for-me" ]), WorkflowsVie
8728
9108
  children: /* @__PURE__ */ jsxRuntime.jsx(InvalidDocNotice, {
8729
9109
  invalid: discoveryInvalid
8730
9110
  })
8731
- }) : resolved ?
8732
- /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
9111
+ }) : resolved ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
8733
9112
  direction: "column",
8734
9113
  height: "fill",
8735
9114
  padding: 4,
@@ -8778,7 +9157,7 @@ function DiscoverySpinner() {
8778
9157
  });
8779
9158
  }
8780
9159
 
8781
- const BOOT_CUE_DELAY_MS = 400, GROUP_GAP = 2;
9160
+ const GROUP_GAP = 2;
8782
9161
 
8783
9162
  function useWorkflowsTab() {
8784
9163
  const {params: params, setParams: setParams} = structure.usePaneRouter();
@@ -8803,7 +9182,7 @@ function useOpenActivity(entries) {
8803
9182
  }
8804
9183
 
8805
9184
  function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappings: docTypeMappings, initialValue: initialValue}) {
8806
- const identity = useAssignmentIdentity(), panelId = react.useId(), {params: params} = structure.usePaneRouter(), {view: view, setView: setView} = useWorkflowsTab(), telemetry2 = workflowReact.useWorkflowTelemetry(), {openActivity: openActivity, setOpenActivity: setOpenActivity, dialogEntry: dialogEntry} = useOpenActivity(entries), active = entries.filter(isLiveEntry), finished = entries.filter(e => !isLiveEntry(e)), forMe = forMeWorkOf(active, identity), [landing] = react.useState(() => focusedLanding(params?.[WORKFLOWS_FOCUS_PARAM], entries.map(e => e.instance._id))), assignedWorkUnknown = active.some(e => e.invalid !== void 0), booting = useDelayedFlag(active.some(e => !e.ready && e.invalid === void 0), BOOT_CUE_DELAY_MS), sectionFor = e => /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceSection, {
9185
+ const identity = useAssignmentIdentity(), panelId = react.useId(), {params: params} = structure.usePaneRouter(), {view: view, setView: setView} = useWorkflowsTab(), telemetry2 = workflowReact.useWorkflowTelemetry(), {openActivity: openActivity, setOpenActivity: setOpenActivity, dialogEntry: dialogEntry} = useOpenActivity(entries), active = entries.filter(isLiveEntry), finished = entries.filter(e => !isLiveEntry(e)), forMe = forMeWorkOf(active, identityOf(identity)), [landing] = react.useState(() => focusedLanding(params?.[WORKFLOWS_FOCUS_PARAM], entries.map(e => e.instance._id))), assignedWorkUnknown = active.some(e => e.invalid !== void 0), booting = active.some(e => !e.ready && e.invalid === void 0), sectionFor = e => /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceSection, {
8807
9186
  defaultOpen: landingDefaultOpen(landing, e.instance._id),
8808
9187
  entry: e,
8809
9188
  onOpenActivity: activityName => setOpenActivity({
@@ -9031,7 +9410,10 @@ function resolveEntry(args) {
9031
9410
  }
9032
9411
  valid.push(name), definition && definitions.push({
9033
9412
  name: name,
9034
- fields: definition.fields
9413
+ fields: definition.fields,
9414
+ ...definition.roleAliases === void 0 ? {} : {
9415
+ roleAliases: definition.roleAliases
9416
+ }
9035
9417
  });
9036
9418
  }
9037
9419
  return {
@@ -9549,6 +9931,7 @@ function WorkflowProvider(props) {
9549
9931
  engine: engine,
9550
9932
  binding: binding,
9551
9933
  mappings: mappings,
9934
+ previewHydration: config.previewHydration,
9552
9935
  mappingIssues: issues,
9553
9936
  mappingStarts: starts,
9554
9937
  mappingFields: fields,
@@ -9556,7 +9939,7 @@ function WorkflowProvider(props) {
9556
9939
  effectHandlers: engine.effectHandlers,
9557
9940
  autoStartByType: autoStartByType,
9558
9941
  autoStartDefinitions: autoStartDefinitions
9559
- }), [ 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 ]);
9942
+ }), [ 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 ]);
9560
9943
  /* @__PURE__ */
9561
9944
  return jsxRuntime.jsxs(WorkflowContext.Provider, {
9562
9945
  value: value,
@@ -9658,7 +10041,7 @@ function logOnce(messages, logged) {
9658
10041
  for (const message of messages) logged.has(message) || (logged.add(message), console.warn(`[workflow-studio-plugin] ${message}`));
9659
10042
  }
9660
10043
 
9661
- function AssigneesInput({value: value, onChange: onChange}) {
10044
+ function AssigneesInput({value: value, onChange: onChange, eligibleRoles: eligibleRoles, roleAliases: roleAliases}) {
9662
10045
  const [open, setOpen] = react.useState(!1), remove = index => onChange(value.filter((_, i) => i !== index));
9663
10046
  /* @__PURE__ */
9664
10047
  return jsxRuntime.jsxs(ui.Stack, {
@@ -9692,7 +10075,9 @@ function AssigneesInput({value: value, onChange: onChange}) {
9692
10075
  wrap: "wrap",
9693
10076
  children: /* @__PURE__ */ jsxRuntime.jsx(DismissablePopover, {
9694
10077
  content: /* @__PURE__ */ jsxRuntime.jsx(AssigneePicker, {
10078
+ eligibleRoles: eligibleRoles,
9695
10079
  onChange: next => onChange([ ...next ]),
10080
+ roleAliases: roleAliases,
9696
10081
  value: value
9697
10082
  }),
9698
10083
  onDismiss: () => setOpen(!1),
@@ -9761,11 +10146,29 @@ function ChecklistInput({value: value, onChange: onChange}) {
9761
10146
  });
9762
10147
  }
9763
10148
 
9764
- function ReleasePicker({value: value, onChange: onChange}) {
9765
- const {binding: binding} = useWorkflowContext(), {data: data, loading: loading, error: error} = sanity.useActiveReleases(), releases = react.useMemo(() => data.filter(release => release.state === "active").map(release => ({
10149
+ function titleOf(release) {
10150
+ return release.metadata.title ?? release.name;
10151
+ }
10152
+
10153
+ function useReleaseTitles() {
10154
+ const {data: data} = sanity.useActiveReleases();
10155
+ return react.useMemo(() => new Map(data.map(release => [ release.name, titleOf(release) ])), [ data ]);
10156
+ }
10157
+
10158
+ function useActiveReleaseOptions() {
10159
+ const {data: data, loading: loading, error: error} = sanity.useActiveReleases(), releases = react.useMemo(() => data.filter(release => release.state === "active").map(release => ({
9766
10160
  name: release.name,
9767
- title: release.metadata.title ?? release.name
9768
- })).toSorted((a, b) => a.title.localeCompare(b.title)), [ data ]), labelOf = react.useMemo(() => {
10161
+ title: titleOf(release)
10162
+ })).toSorted((a, b) => a.title.localeCompare(b.title)), [ data ]);
10163
+ return {
10164
+ error: error,
10165
+ loading: loading,
10166
+ releases: releases
10167
+ };
10168
+ }
10169
+
10170
+ function ReleasePicker({value: value, onChange: onChange}) {
10171
+ const {binding: binding} = useWorkflowContext(), {error: error, loading: loading, releases: releases} = useActiveReleaseOptions(), labelOf = react.useMemo(() => {
9769
10172
  const m = new Map(releases.map(r => [ r.name, r.title ]));
9770
10173
  return name => m.get(name) ?? name;
9771
10174
  }, [ releases ]);
@@ -10033,8 +10436,10 @@ const docRefInput = ({entry: entry, docType: docType, releaseId: releaseId, valu
10033
10436
  onChange: onChange,
10034
10437
  value: value ?? []
10035
10438
  }),
10036
- assignees: ({value: value, onChange: onChange}) => /* @__PURE__ */ jsxRuntime.jsx(AssigneesInput, {
10439
+ assignees: ({entry: entry, roleAliases: roleAliases, value: value, onChange: onChange}) => /* @__PURE__ */ jsxRuntime.jsx(AssigneesInput, {
10440
+ eligibleRoles: entry.roles,
10037
10441
  onChange: onChange,
10442
+ roleAliases: roleAliases,
10038
10443
  value: value ?? []
10039
10444
  }),
10040
10445
  array: ({entry: entry, value: value, onChange: onChange}) => {
@@ -10076,7 +10481,7 @@ function FieldInput(props) {
10076
10481
  });
10077
10482
  }
10078
10483
 
10079
- function InitFieldRow({entry: entry, docType: docType, releaseId: releaseId, value: value, onChange: onChange}) {
10484
+ function InitFieldRow({entry: entry, docType: docType, releaseId: releaseId, roleAliases: roleAliases, value: value, onChange: onChange}) {
10080
10485
  const label = entry.title ?? entry.name;
10081
10486
  /* @__PURE__ */
10082
10487
  return jsxRuntime.jsxs(ui.Stack, {
@@ -10100,6 +10505,7 @@ function InitFieldRow({entry: entry, docType: docType, releaseId: releaseId, val
10100
10505
  entry: entry,
10101
10506
  onChange: onChange,
10102
10507
  releaseId: releaseId,
10508
+ roleAliases: roleAliases,
10103
10509
  value: value
10104
10510
  }) ]
10105
10511
  });
@@ -10149,7 +10555,7 @@ function StartFooter({canStart: canStart, starting: starting, startLabel: startL
10149
10555
  });
10150
10556
  }
10151
10557
 
10152
- function StartInputList({initFields: initFields, mapping: mapping, releaseId: releaseId, values: values, onChange: onChange}) {
10558
+ function StartInputList({initFields: initFields, mapping: mapping, releaseId: releaseId, roleAliases: roleAliases, values: values, onChange: onChange}) {
10153
10559
  return initFields.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
10154
10560
  muted: !0,
10155
10561
  size: 1,
@@ -10160,6 +10566,7 @@ function StartInputList({initFields: initFields, mapping: mapping, releaseId: re
10160
10566
  entry: entry,
10161
10567
  onChange: v => onChange(entry.name, v),
10162
10568
  releaseId: releaseId,
10569
+ roleAliases: roleAliases,
10163
10570
  value: values[entry.name] ?? null
10164
10571
  }, entry.name))
10165
10572
  });
@@ -10290,6 +10697,7 @@ function StartWorkflowForm({request: request, onClose: onClose}) {
10290
10697
  mapping: mapping,
10291
10698
  onChange: setValue,
10292
10699
  releaseId: releaseId,
10700
+ roleAliases: definition.roleAliases,
10293
10701
  values: values
10294
10702
  }),
10295
10703
  /* @__PURE__ */ jsxRuntime.jsx(StartFooter, {
@@ -10344,11 +10752,12 @@ function AutoStartInputDialog(props) {
10344
10752
  }),
10345
10753
  /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
10346
10754
  gap: 4,
10347
- children: inputs.map(({entry: entry}) => /* @__PURE__ */ jsxRuntime.jsx(InitFieldRow, {
10755
+ children: inputs.map(({entry: entry, roleAliases: roleAliases}) => /* @__PURE__ */ jsxRuntime.jsx(InitFieldRow, {
10348
10756
  docType: docType,
10349
10757
  entry: entry,
10350
10758
  onChange: value => setValue(entry.name, value),
10351
10759
  releaseId: void 0,
10760
+ roleAliases: roleAliases,
10352
10761
  value: values[entry.name] ?? null
10353
10762
  }, entry.name))
10354
10763
  }),
@@ -10377,23 +10786,65 @@ function subjectEntry(definition) {
10377
10786
  return documentSubjectEntry(definition);
10378
10787
  }
10379
10788
 
10380
- function autoStartInputs(args) {
10789
+ function autoStartInputPlan(args) {
10381
10790
  const byName = /* @__PURE__ */ new Map;
10382
10791
  for (const name of args.workflows) {
10383
10792
  const definition = args.definitions.get(name);
10384
10793
  if (definition !== void 0) for (const entry of extraRequiredInputs(definition)) {
10385
- const existing = byName.get(entry.name);
10386
- existing === void 0 ? byName.set(entry.name, {
10794
+ const conflict = mergeAutoStartInput(byName, {
10387
10795
  entry: entry,
10388
- workflows: [ name ]
10389
- }) : existing.workflows.push(name);
10796
+ workflow: name,
10797
+ roleAliases: definition.roleAliases
10798
+ });
10799
+ if (conflict !== void 0) return {
10800
+ inputs: [],
10801
+ conflict: conflict
10802
+ };
10390
10803
  }
10391
10804
  }
10392
- return [ ...byName.values() ];
10805
+ return {
10806
+ inputs: [ ...byName.values() ]
10807
+ };
10808
+ }
10809
+
10810
+ function mergeAutoStartInput(byName, args) {
10811
+ const existing = byName.get(args.entry.name);
10812
+ if (existing === void 0) {
10813
+ byName.set(args.entry.name, {
10814
+ entry: args.entry,
10815
+ workflows: [ args.workflow ],
10816
+ ...args.roleAliases === void 0 ? {} : {
10817
+ roleAliases: args.roleAliases
10818
+ }
10819
+ });
10820
+ return;
10821
+ }
10822
+ 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`;
10823
+ existing.workflows.push(args.workflow), sameRoleAliases(existing.roleAliases, args.roleAliases) || delete existing.roleAliases;
10824
+ }
10825
+
10826
+ function sameRoleSet(left, right) {
10827
+ return roleSetKey(left) === roleSetKey(right);
10828
+ }
10829
+
10830
+ function roleSetKey(roles) {
10831
+ return roles === void 0 ? void 0 : JSON.stringify(sortedUnique(roles));
10832
+ }
10833
+
10834
+ function sameRoleAliases(left, right) {
10835
+ return roleAliasesKey(left) === roleAliasesKey(right);
10836
+ }
10837
+
10838
+ function roleAliasesKey(aliases) {
10839
+ if (aliases !== void 0) return JSON.stringify(Object.entries(aliases).map(([role, fulfillers]) => [ role, sortedUnique(fulfillers) ]).toSorted(([left], [right]) => left.localeCompare(right)));
10840
+ }
10841
+
10842
+ function sortedUnique(values) {
10843
+ return [ ...new Set(values) ].toSorted();
10393
10844
  }
10394
10845
 
10395
10846
  function autoStartAction(args) {
10396
- return args.fired ? args.settled ? "reveal" : "starting" : args.pendingCount === 0 ? "reveal" : args.resolved ? args.needsInput ? "collect" : "silent-start" : "wait";
10847
+ return args.fired ? args.settled ? "reveal" : "starting" : args.blocked || args.pendingCount === 0 ? "reveal" : args.resolved ? args.needsInput ? "collect" : "silent-start" : "wait";
10397
10848
  }
10398
10849
 
10399
10850
  function pendingWorkflows(workflows, live) {
@@ -10450,10 +10901,14 @@ function AutoStartDriver(props) {
10450
10901
  engine: engine
10451
10902
  }), {selectedReleaseId: selectedReleaseId} = sanity.usePerspective(), {entries: entries} = useWorkflowsForDocument(rawDocId), docId = csm.getPublishedId(rawDocId), valueRef = react.useRef(value);
10452
10903
  valueRef.current = value;
10453
- const pending = react.useMemo(() => pendingWorkflows(workflows, new Set(entries.map(entry => entry.instance.definition))), [ workflows, entries ]), inputs = react.useMemo(() => autoStartInputs({
10904
+ const pending = react.useMemo(() => pendingWorkflows(workflows, new Set(entries.map(entry => entry.instance.definition))), [ workflows, entries ]), inputPlan = react.useMemo(() => autoStartInputPlan({
10454
10905
  workflows: pending,
10455
10906
  definitions: autoStartDefinitions
10456
- }), [ pending, autoStartDefinitions ]), definitionsResolved = workflows.every(name => autoStartDefinitions.has(name)), pendingRef = react.useRef(pending);
10907
+ }), [ pending, autoStartDefinitions ]), inputs = inputPlan.inputs;
10908
+ react.useEffect(() => {
10909
+ inputPlan.conflict !== void 0 && console.warn(`[Workflows] Auto-start skipped. ${inputPlan.conflict}`);
10910
+ }, [ inputPlan.conflict ]);
10911
+ const definitionsResolved = workflows.every(name => autoStartDefinitions.has(name)), pendingRef = react.useRef(pending);
10457
10912
  pendingRef.current = pending;
10458
10913
  const instanceIds = react.useRef(/* @__PURE__ */ new Map), instanceIdFor = react.useCallback(workflow => {
10459
10914
  const held = instanceIds.current.get(workflow);
@@ -10505,6 +10960,7 @@ function AutoStartDriver(props) {
10505
10960
  resolved: definitionsResolved && isDocResolved(docId),
10506
10961
  pendingCount: pending.length,
10507
10962
  needsInput: inputs.length > 0,
10963
+ blocked: inputPlan.conflict !== void 0,
10508
10964
  fired: fired,
10509
10965
  settled: settled
10510
10966
  });
@@ -10747,6 +11203,28 @@ function unassignedLineHint(args) {
10747
11203
  };
10748
11204
  }
10749
11205
 
11206
+ function taskCountState(args) {
11207
+ const {entry: entry, identity: identity} = args, freshness = entry.invalid === void 0 ? evaluationFreshness({
11208
+ committed: entry.committed,
11209
+ evaluation: entry.evaluation
11210
+ }) : void 0;
11211
+ return identity.kind === "unavailable" ? {
11212
+ kind: "unknowable"
11213
+ } : identity.kind === "pending" || freshness === "pending" ? {
11214
+ kind: "pending"
11215
+ } : freshness === "stale" ? {
11216
+ kind: "stale"
11217
+ } : {
11218
+ kind: "ready",
11219
+ tasks: assignedTaskCount({
11220
+ instance: entry.instance,
11221
+ committed: entry.committed,
11222
+ evaluation: entry.evaluation,
11223
+ who: identity.identity
11224
+ })
11225
+ };
11226
+ }
11227
+
10750
11228
  function WorkflowFormStrip(props) {
10751
11229
  const {mappings: mappings} = props, {isDocResolved: isDocResolved, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {docId: docId, entries: entries} = useWorkflowsForDocument(props.value?._id ?? null), {params: params, setParams: setParams} = structure.usePaneRouter(), telemetry2 = workflowReact.useWorkflowTelemetry(), openWorkflowsView = focusId => {
10752
11230
  telemetry2.log(WorkflowFormStripClicked), setParams(focusedViewParams(params, focusId));
@@ -10834,7 +11312,7 @@ function InstanceLine({entry: entry, onOpen: onOpen}) {
10834
11312
  aside: unprimed ? void 0 : /* @__PURE__ */ jsxRuntime.jsx(TaskCountSpinner, {
10835
11313
  entry: entry
10836
11314
  }),
10837
- hint: lineHint(entry, identity),
11315
+ hint: lineHint(entry, identityOf(identity)),
10838
11316
  onOpen: onOpen,
10839
11317
  children: [
10840
11318
  /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
@@ -10893,28 +11371,16 @@ function finishedHint(instance) {
10893
11371
  }
10894
11372
 
10895
11373
  function useTaskCountState(entry) {
10896
- const identity = useAssignmentIdentity(), freshness = entry.invalid === void 0 ? evaluationFreshness({
10897
- committed: entry.committed,
10898
- evaluation: entry.evaluation
10899
- }) : void 0;
10900
- return !identity || freshness === "pending" ? {
10901
- kind: "pending"
10902
- } : freshness === "stale" ? {
10903
- kind: "stale"
10904
- } : {
10905
- kind: "ready",
10906
- tasks: assignedTaskCount({
10907
- instance: entry.instance,
10908
- committed: entry.committed,
10909
- evaluation: entry.evaluation,
10910
- who: identity
10911
- })
10912
- };
11374
+ const identity = useAssignmentIdentity();
11375
+ return taskCountState({
11376
+ entry: entry,
11377
+ identity: identity
11378
+ });
10913
11379
  }
10914
11380
 
10915
11381
  function TaskCountText({entry: entry}) {
10916
11382
  const state = useTaskCountState(entry);
10917
- return state.kind === "pending" || state.kind === "ready" && state.tasks === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
11383
+ return state.kind === "pending" || state.kind === "unknowable" || state.kind === "ready" && state.tasks === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
10918
11384
  muted: !0,
10919
11385
  size: 1,
10920
11386
  children: state.kind === "stale" ? "Updating…" : pluralize__default.default("assigned task", state.tasks, !0)
@@ -10940,19 +11406,13 @@ function StartWorkflowDialogHost() {
10940
11406
  }
10941
11407
 
10942
11408
  const TOOL_TAB_LABELS = {
10943
- overview: "Workflows",
10944
- "all-documents": "All documents",
10945
- "for-me": "For me"
11409
+ overview: "Overview",
11410
+ "for-me": "For me",
11411
+ definitions: "Definitions"
10946
11412
  }, TOOL_TAB_VALUES = Object.keys(TOOL_TAB_LABELS), TOOL_TABS = TOOL_TAB_VALUES.map(value => ({
10947
11413
  value: value,
10948
11414
  label: TOOL_TAB_LABELS[value]
10949
- })), LANDING_TAB = "overview", TASKS_TAB = "all-documents", WORKFLOW_PAGE_LABELS = {
10950
- workflow: "Documents",
10951
- definition: "Definition"
10952
- }, 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 => ({
10953
- value: value,
10954
- label: WORKFLOW_PAGE_LABELS[value]
10955
- })), toolRouter = router.route.create("/", [ router.route.create("/instance/:instanceId"), router.route.create(`/${LANDING_TAB}/:workflowName`, [ router.route.create("/:workflowPage") ]), router.route.create("/:workflowsTab") ]);
11415
+ })), LANDING_TAB = "overview", DEFINITIONS_TAB = "definitions", toolRouter = router.route.create("/", [ router.route.create(`/${DEFINITIONS_TAB}/:workflowName`), router.route.create("/:workflowsTab") ]);
10956
11416
 
10957
11417
  function toolTabState(tab) {
10958
11418
  return {
@@ -10970,39 +11430,17 @@ function workflowState(workflowName) {
10970
11430
  };
10971
11431
  }
10972
11432
 
10973
- function instanceState(instanceId) {
10974
- return {
10975
- instanceId: instanceId
10976
- };
10977
- }
10978
-
10979
11433
  function tabSelectionNavigates(current, tab) {
10980
11434
  return !(current.kind === "home" && tab === current.tab);
10981
11435
  }
10982
11436
 
10983
- function toolTabLabel(tab) {
10984
- return TOOL_TAB_LABELS[tab];
10985
- }
10986
-
10987
- function backToTabLabel(tab) {
10988
- return `Back to ${toolTabLabel(tab)}`;
10989
- }
10990
-
10991
- function workflowPageState(page, workflowName) {
10992
- return page === DEFAULT_WORKFLOW_PAGE ? workflowState(workflowName) : {
10993
- ...workflowState(workflowName),
10994
- workflowPage: page
10995
- };
10996
- }
10997
-
10998
11437
  function toolRoute(state) {
10999
- const {instanceId: instanceId, workflowName: workflowName} = state;
11000
- if (typeof instanceId == "string") return {
11001
- kind: "instance",
11002
- instanceId: instanceId,
11003
- tab: TASKS_TAB
11438
+ const {workflowName: workflowName} = state;
11439
+ if (typeof workflowName == "string") return {
11440
+ kind: "definition",
11441
+ workflowName: workflowName,
11442
+ tab: DEFINITIONS_TAB
11004
11443
  };
11005
- if (typeof workflowName == "string") return workflowRoute(workflowName, state.workflowPage);
11006
11444
  const tab = pathSegmentName({
11007
11445
  segment: state.workflowsTab,
11008
11446
  names: TOOL_TAB_VALUES
@@ -11016,24 +11454,11 @@ function toolRoute(state) {
11016
11454
  };
11017
11455
  }
11018
11456
 
11019
- function workflowRoute(workflowName, pageSegment) {
11020
- if (pageSegment === void 0) return {
11021
- kind: "workflow",
11022
- workflowName: workflowName,
11023
- tab: LANDING_TAB
11024
- };
11025
- const page = pathSegmentName({
11026
- segment: pageSegment,
11027
- names: WORKFLOW_PAGES
11028
- });
11029
- return page === void 0 ? {
11030
- kind: "unnamed",
11031
- tab: LANDING_TAB
11032
- } : {
11033
- kind: page,
11034
- workflowName: workflowName,
11035
- tab: LANDING_TAB
11457
+ function definitionsRouteOf(route2) {
11458
+ if (route2.kind === "definition") return {
11459
+ workflowName: route2.workflowName
11036
11460
  };
11461
+ if (route2.kind === "home" && route2.tab === "definitions") return {};
11037
11462
  }
11038
11463
 
11039
11464
  const WorkflowsToolRoot = react.lazy(() => Promise.resolve().then(function() {
@@ -11153,13 +11578,13 @@ exports.ActivityDetailDialog = ActivityDetailDialog;
11153
11578
 
11154
11579
  exports.ActivityLog = ActivityLog;
11155
11580
 
11156
- exports.ActivityRow = ActivityRow;
11581
+ exports.AgoStamp = AgoStamp;
11157
11582
 
11158
- exports.BreadcrumbTail = BreadcrumbTail;
11583
+ exports.AlertGlyph = AlertGlyph;
11159
11584
 
11160
11585
  exports.CodeChip = CodeChip;
11161
11586
 
11162
- exports.CountedLabel = CountedLabel;
11587
+ exports.CountedHeading = CountedHeading;
11163
11588
 
11164
11589
  exports.DismissablePopover = DismissablePopover;
11165
11590
 
@@ -11169,35 +11594,31 @@ exports.DocRefFace = DocRefFace;
11169
11594
 
11170
11595
  exports.EmptyState = EmptyState;
11171
11596
 
11172
- exports.ForMeEmptyState = ForMeEmptyState;
11173
-
11174
- exports.Hairline = Hairline;
11175
-
11176
11597
  exports.HoverHint = HoverHint;
11177
11598
 
11599
+ exports.InstanceFaultNote = InstanceFaultNote;
11600
+
11178
11601
  exports.InstanceSnapshotBody = InstanceSnapshotBody;
11179
11602
 
11180
11603
  exports.InvalidDocNotice = InvalidDocNotice;
11181
11604
 
11182
- exports.LinkChip = LinkChip;
11183
-
11184
11605
  exports.LoadingRow = LoadingRow;
11185
11606
 
11186
11607
  exports.LogEventOnMount = LogEventOnMount;
11187
11608
 
11188
11609
  exports.MetaRow = MetaRow;
11189
11610
 
11190
- exports.NoteBanner = NoteBanner;
11611
+ exports.MetricPair = MetricPair;
11191
11612
 
11192
- exports.RoleAssignedNotice = RoleAssignedNotice;
11613
+ exports.NoteBanner = NoteBanner;
11193
11614
 
11194
- exports.SpinnerSlot = SpinnerSlot;
11615
+ exports.RUN_SEARCH_PARAM = RUN_SEARCH_PARAM;
11195
11616
 
11196
- exports.StageFace = StageFace;
11617
+ exports.RoleAssignedNotice = RoleAssignedNotice;
11197
11618
 
11198
- exports.StaleLock = StaleLock;
11619
+ exports.RowAssignees = RowAssignees;
11199
11620
 
11200
- exports.TOAST_ID = TOAST_ID;
11621
+ exports.SpinnerSlot = SpinnerSlot;
11201
11622
 
11202
11623
  exports.TOOL_TABS = TOOL_TABS;
11203
11624
 
@@ -11207,35 +11628,23 @@ exports.UnreadableDocsNote = UnreadableDocsNote;
11207
11628
 
11208
11629
  exports.WORKFLOW_API_VERSION = WORKFLOW_API_VERSION;
11209
11630
 
11210
- exports.WORKFLOW_PAGE_TABS = WORKFLOW_PAGE_TABS;
11211
-
11212
11631
  exports.WorkflowBoardWorkflowSelected = WorkflowBoardWorkflowSelected;
11213
11632
 
11214
11633
  exports.WorkflowDefinitionDetailViewed = WorkflowDefinitionDetailViewed;
11215
11634
 
11216
11635
  exports.WorkflowInstanceDetailViewed = WorkflowInstanceDetailViewed;
11217
11636
 
11218
- exports.WorkflowTaskFiltersApplied = WorkflowTaskFiltersApplied;
11219
-
11220
11637
  exports.WorkflowTitleSeedDrifted = WorkflowTitleSeedDrifted;
11221
11638
 
11222
11639
  exports.WorkflowToolOpened = WorkflowToolOpened;
11223
11640
 
11224
- exports.activityRowProps = activityRowProps;
11225
-
11226
11641
  exports.assigneesOf = assigneesOf;
11227
11642
 
11228
- exports.backToTabLabel = backToTabLabel;
11229
-
11230
- exports.committedRowFace = committedRowFace;
11231
-
11232
- exports.createSubscribers = createSubscribers;
11233
-
11234
- exports.datesOf = datesOf;
11643
+ exports.countBy = countBy;
11235
11644
 
11236
11645
  exports.definitionFingerprint = definitionFingerprint;
11237
11646
 
11238
- exports.definitionSnapshotOf = definitionSnapshotOf;
11647
+ exports.definitionsRouteOf = definitionsRouteOf;
11239
11648
 
11240
11649
  exports.describeError = describeError;
11241
11650
 
@@ -11243,23 +11652,19 @@ exports.dueDatesOf = dueDatesOf;
11243
11652
 
11244
11653
  exports.findActivity = findActivity;
11245
11654
 
11246
- exports.findActivityNode = findActivityNode;
11247
-
11248
- exports.formatDate = formatDate;
11249
-
11250
11655
  exports.formatShortDateTime = formatShortDateTime;
11251
11656
 
11252
11657
  exports.formatTimeAgo = formatTimeAgo;
11253
11658
 
11254
11659
  exports.gdrLocality = gdrLocality;
11255
11660
 
11256
- exports.instanceBreadcrumb = instanceBreadcrumb;
11661
+ exports.identityOf = identityOf;
11257
11662
 
11258
- exports.instanceState = instanceState;
11663
+ exports.instanceBreadcrumb = instanceBreadcrumb;
11259
11664
 
11260
11665
  exports.instanceTitle = instanceTitle;
11261
11666
 
11262
- exports.isActivityAssignedTo = isActivityAssignedTo;
11667
+ exports.isDueDatePast = isDueDatePast;
11263
11668
 
11264
11669
  exports.isEvaluationStale = isEvaluationStale;
11265
11670
 
@@ -11267,8 +11672,6 @@ exports.isLiveEntry = isLiveEntry;
11267
11672
 
11268
11673
  exports.isOpenActivityStatus = isOpenActivityStatus;
11269
11674
 
11270
- exports.isRoleAssignedTo = isRoleAssignedTo;
11271
-
11272
11675
  exports.landingState = landingState;
11273
11676
 
11274
11677
  exports.mappingIssueDetail = mappingIssueDetail;
@@ -11283,14 +11686,8 @@ exports.parseStoredDateValue = parseStoredDateValue;
11283
11686
 
11284
11687
  exports.readDeployedDefinitions = readDeployedDefinitions;
11285
11688
 
11286
- exports.rowAssignControlState = rowAssignControlState;
11287
-
11288
- exports.rowDateControlState = rowDateControlState;
11289
-
11290
11689
  exports.stageTitle = stageTitle;
11291
11690
 
11292
- exports.stateByActivity = stateByActivity;
11293
-
11294
11691
  exports.tabSelectionNavigates = tabSelectionNavigates;
11295
11692
 
11296
11693
  exports.toolRoute = toolRoute;
@@ -11299,32 +11696,38 @@ exports.toolTabState = toolTabState;
11299
11696
 
11300
11697
  exports.useAssignmentIdentity = useAssignmentIdentity;
11301
11698
 
11302
- exports.useBadgeCapTrim = useBadgeCapTrim;
11699
+ exports.useAvatarSize = useAvatarSize;
11700
+
11701
+ exports.useCapTrimFor = useCapTrimFor;
11303
11702
 
11304
11703
  exports.useContainerToken = useContainerToken;
11305
11704
 
11306
- exports.useDefinition = useDefinition;
11705
+ exports.useCurrentUserId = useCurrentUserId;
11307
11706
 
11308
- exports.useDelayedFlag = useDelayedFlag;
11707
+ exports.useDefinition = useDefinition;
11309
11708
 
11310
11709
  exports.useLogEventOnMount = useLogEventOnMount;
11311
11710
 
11711
+ exports.useMinuteClock = useMinuteClock;
11712
+
11312
11713
  exports.useProjectMembers = useProjectMembers;
11313
11714
 
11715
+ exports.useRadiusToken = useRadiusToken;
11716
+
11717
+ exports.useReleaseTitles = useReleaseTitles;
11718
+
11314
11719
  exports.useSpaceToken = useSpaceToken;
11315
11720
 
11721
+ exports.useTextIconSize = useTextIconSize;
11722
+
11316
11723
  exports.useUserDisplays = useUserDisplays;
11317
11724
 
11318
11725
  exports.useWorkflowContext = useWorkflowContext;
11319
11726
 
11320
11727
  exports.useWorkflowInstanceEntry = useWorkflowInstanceEntry;
11321
11728
 
11322
- exports.useWorkflowToast = useWorkflowToast;
11323
-
11324
11729
  exports.workflowDefaultDocumentNode = workflowDefaultDocumentNode;
11325
11730
 
11326
- exports.workflowPageState = workflowPageState;
11327
-
11328
11731
  exports.workflowState = workflowState;
11329
11732
 
11330
11733
  exports.workflowStudioPlugin = workflowStudioPlugin;