@sanity/workflow-studio-plugin 0.30.0 → 0.31.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"), ChevronDown = require("@sanity/icons/ChevronDown"), ChevronRight = require("@sanity/icons/ChevronRight"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal"), Launch = require("@sanity/icons/Launch"), ui = require("@sanity/ui"), workflowReact = require("@sanity/workflow-react"), react = require("react"), router = require("sanity/router"), index = require("./index.cjs"), Transfer = require("@sanity/icons/Transfer"), WarningOutline = require("@sanity/icons/WarningOutline"), sanity = require("sanity"), workflowEngine = require("@sanity/workflow-engine"), workflowStudio = require("@sanity/workflow-studio"), pluralize = require("pluralize-esm"), reactVirtual = require("@tanstack/react-virtual"), ErrorOutline = require("@sanity/icons/ErrorOutline"), types = require("@sanity/types"), reactDom = require("react-dom"), Close = require("@sanity/icons/Close"), User = require("@sanity/icons/User"), workflowComponents = require("@sanity/workflow-components"), Add = require("@sanity/icons/Add"), Checkmark = require("@sanity/icons/Checkmark"), styledComponents = require("styled-components"), Search = require("@sanity/icons/Search"), react$1 = require("motion/react"), ChevronLeft = require("@sanity/icons/ChevronLeft"), Expand = require("@sanity/icons/Expand"), workflowDiagram = require("@sanity/workflow-diagram");
3
+ var jsxRuntime = require("react/jsx-runtime"), ChevronDown = require("@sanity/icons/ChevronDown"), ChevronRight = require("@sanity/icons/ChevronRight"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal"), Launch = require("@sanity/icons/Launch"), ui = require("@sanity/ui"), workflowReact = require("@sanity/workflow-react"), react = require("react"), router = require("sanity/router"), index = require("./index.cjs"), Transfer = require("@sanity/icons/Transfer"), WarningOutline = require("@sanity/icons/WarningOutline"), sanity = require("sanity"), workflowEngine = require("@sanity/workflow-engine"), workflowStudio = require("@sanity/workflow-studio"), pluralize = require("pluralize-esm"), reactVirtual = require("@tanstack/react-virtual"), User = require("@sanity/icons/User"), workflowComponents = require("@sanity/workflow-components"), Calendar = require("@sanity/icons/Calendar"), ErrorOutline = require("@sanity/icons/ErrorOutline"), types = require("@sanity/types"), reactDom = require("react-dom"), styledComponents = require("styled-components"), Close = require("@sanity/icons/Close"), Add = require("@sanity/icons/Add"), Checkmark = require("@sanity/icons/Checkmark"), Search = require("@sanity/icons/Search"), react$1 = require("motion/react"), ChevronLeft = require("@sanity/icons/ChevronLeft"), Expand = require("@sanity/icons/Expand"), workflowDiagram = require("@sanity/workflow-diagram");
4
4
 
5
5
  function _interopDefaultCompat(e) {
6
6
  return e && typeof e == "object" && "default" in e ? e : {
@@ -41,14 +41,17 @@ function overdueTasks(instance, now) {
41
41
  return openActivitiesOf(instance).filter(activity => index.dueDatesOf(activity.fields ?? []).some(due => index.isDueDatePast(due, now)));
42
42
  }
43
43
 
44
- function overdueTaskCount(instance, now) {
45
- return overdueTasks(instance, now).length;
44
+ function earliestPastDue(activity, now) {
45
+ return earliestPastDueAcross([ activity ], now);
46
46
  }
47
47
 
48
- function earliestPastDue(activity, now) {
49
- const past = index.dueDatesOf(activity.fields ?? []).filter(due => index.isDueDatePast(due, now));
48
+ function earliestPastDueAcross(activities, now) {
49
+ return earliestOf(activities.flatMap(activity => index.dueDatesOf(activity.fields ?? []).filter(due => index.isDueDatePast(due, now))));
50
+ }
51
+
52
+ function earliestOf(values) {
50
53
  let earliest;
51
- for (const value of past) {
54
+ for (const value of values) {
52
55
  const time = index.parseStoredDateValue(value)?.getTime();
53
56
  time !== void 0 && (earliest === void 0 || time < earliest.time) && (earliest = {
54
57
  time: time,
@@ -58,6 +61,135 @@ function earliestPastDue(activity, now) {
58
61
  return earliest?.value;
59
62
  }
60
63
 
64
+ const AUTONOMY_BY_DEFINITION = /* @__PURE__ */ new WeakMap;
65
+
66
+ function cachedAutonomy(definition) {
67
+ const hit = AUTONOMY_BY_DEFINITION.get(definition);
68
+ if (hit !== void 0) return hit;
69
+ const derived = workflowEngine.deriveWorkflowAutonomy(definition);
70
+ return AUTONOMY_BY_DEFINITION.set(definition, derived), derived;
71
+ }
72
+
73
+ function isAutomatedActivity(args) {
74
+ const stage = args.autonomy?.stages.find(entry => entry.stage === args.stageName);
75
+ return index.isHandsOff({
76
+ classification: workflowEngine.deriveExecutorClassification(args.activity),
77
+ completesWithoutCaller: stage?.activities[args.activity.name]?.completesWithoutCaller
78
+ });
79
+ }
80
+
81
+ function taskOf(args) {
82
+ const {entry: entry, now: now, title: title} = args;
83
+ return {
84
+ name: entry.name,
85
+ title: title,
86
+ assignees: index.assigneesOf(entry.fields ?? []),
87
+ overdueSince: earliestPastDue(entry, now)
88
+ };
89
+ }
90
+
91
+ function humanTask(args) {
92
+ const {activity: activity, autonomy: autonomy, entry: entry, now: now, stageName: stageName} = args;
93
+ return entry === void 0 ? [] : isAutomatedActivity({
94
+ activity: activity,
95
+ autonomy: autonomy,
96
+ stageName: stageName
97
+ }) ? [] : [ taskOf({
98
+ entry: entry,
99
+ now: now,
100
+ title: index.activityLabel(activity)
101
+ }) ];
102
+ }
103
+
104
+ function undeclaredTasks(args) {
105
+ const {definition: definition, now: now, open: open, seen: seen, stageName: stageName} = args;
106
+ return open.filter(entry => !seen.has(entry.name)).map(entry => taskOf({
107
+ entry: entry,
108
+ now: now,
109
+ title: index.findActivity({
110
+ activityName: entry.name,
111
+ definition: definition,
112
+ stageName: stageName
113
+ }).title
114
+ }));
115
+ }
116
+
117
+ function autonomyForPinned(definition, pinnedVersion) {
118
+ if (!(definition === void 0 || definition.version !== pinnedVersion)) return cachedAutonomy(definition);
119
+ }
120
+
121
+ function stageTasksOf(args) {
122
+ const {definition: definition, instance: instance, now: now} = args, open = openActivitiesOf(instance);
123
+ if (open.length === 0) return [];
124
+ const autonomy = autonomyForPinned(definition, instance.pinnedVersion), stageName = instance.currentStage, openByName = new Map(open.map(entry => [ entry.name, entry ])), declared = workflowEngine.findStageNode({
125
+ definition: definition,
126
+ stageName: stageName
127
+ })?.activities ?? [];
128
+ return [ ...declared.flatMap(activity => humanTask({
129
+ activity: activity,
130
+ autonomy: autonomy,
131
+ entry: openByName.get(activity.name),
132
+ now: now,
133
+ stageName: stageName
134
+ })), ...undeclaredTasks({
135
+ definition: definition,
136
+ now: now,
137
+ open: open,
138
+ seen: new Set(declared.map(activity => activity.name)),
139
+ stageName: stageName
140
+ }) ];
141
+ }
142
+
143
+ function groupWidthOf(task, metrics) {
144
+ const stacked = Math.max(1, task.assignees.length);
145
+ return metrics.face + (stacked - 1) * (metrics.face - metrics.overlap);
146
+ }
147
+
148
+ function rowWidthOf(tasks, metrics) {
149
+ return tasks.reduce((total, task) => total + groupWidthOf(task, metrics), 0) + Math.max(0, tasks.length - 1) * metrics.gap;
150
+ }
151
+
152
+ function countWithin(args) {
153
+ const {budget: budget, metrics: metrics, tasks: tasks} = args;
154
+ let used = 0, count = 0;
155
+ for (const task of tasks) {
156
+ const next = used + (count === 0 ? 0 : metrics.gap) + groupWidthOf(task, metrics);
157
+ if (next > budget) break;
158
+ used = next, count += 1;
159
+ }
160
+ return Math.max(1, count);
161
+ }
162
+
163
+ function visibleStageTasks(args) {
164
+ const {metrics: metrics, room: room, tasks: tasks} = args;
165
+ if (rowWidthOf(tasks, metrics) <= room) return {
166
+ overflow: 0,
167
+ visible: tasks
168
+ };
169
+ const shown = countWithin({
170
+ budget: room - metrics.gap - metrics.counter,
171
+ metrics: metrics,
172
+ tasks: tasks
173
+ });
174
+ return {
175
+ overflow: tasks.length - shown,
176
+ visible: tasks.slice(0, shown)
177
+ };
178
+ }
179
+
180
+ function stackedAssigneesOf(assignees) {
181
+ return [ ...assignees.filter(assignee => assignee.type === "role"), ...assignees.filter(assignee => assignee.type === "user") ];
182
+ }
183
+
184
+ function taskHoldersOf(assignees) {
185
+ return assignees.length === 0 ? [ {
186
+ kind: "unassigned"
187
+ } ] : stackedAssigneesOf(assignees).map(assignee => ({
188
+ kind: "held",
189
+ assignee: assignee
190
+ }));
191
+ }
192
+
61
193
  function runsScopeOf(row) {
62
194
  return row.endedAt === void 0 ? "open" : "closed";
63
195
  }
@@ -75,7 +207,7 @@ function runCountsByWorkflow(rows) {
75
207
  }
76
208
 
77
209
  function runCountsByAssignee(rows) {
78
- return index.countBy(rows, row => new Set(row.assignees.flatMap(assignee => assignee.type === "user" ? [ assignee.id ] : [])));
210
+ return index.countBy(rows, row => new Set(index.userIdsOf(row.assignees)));
79
211
  }
80
212
 
81
213
  function runCountsByRole(rows) {
@@ -144,12 +276,17 @@ function runRowOf(args) {
144
276
  attention: attentionKindOf(attentionDetail),
145
277
  attentionDetail: attentionDetail,
146
278
  assignees: held.assignees,
147
- unassignedOpenCount: held.unassignedCount
279
+ unassignedOpenCount: held.unassignedCount,
280
+ tasks: settled ? [] : stageTasksOf({
281
+ definition: definition,
282
+ instance: preview,
283
+ now: now
284
+ })
148
285
  };
149
286
  }
150
287
 
151
288
  function attentionKindOf(detail) {
152
- if (detail !== void 0) return detail.kind === "overdue" || detail.kind === "overdue-many" ? "overdue" : "blocked";
289
+ if (detail !== void 0) return detail.kind === "overdue" ? "overdue" : "blocked";
153
290
  }
154
291
 
155
292
  function outcomeOf(preview, definition) {
@@ -160,6 +297,7 @@ function outcomeOf(preview, definition) {
160
297
  function blockedRunDetail(args) {
161
298
  const {definition: definition, preview: preview} = args, {stage: stage, titleOf: titleOf} = activityTitleResolver(preview, definition), failed = (stage?.activities ?? []).filter(activity => activity.status === "failed");
162
299
  if (failed.length > 1) return {
300
+ count: failed.length,
163
301
  kind: "blocked-many"
164
302
  };
165
303
  const only = failed[0];
@@ -167,19 +305,29 @@ function blockedRunDetail(args) {
167
305
  kind: "blocked",
168
306
  task: titleOf(only.name)
169
307
  };
170
- const effect = stuckEffectOf({
308
+ const stuck = stuckEffectOf({
171
309
  definition: definition,
172
310
  preview: preview,
173
311
  stage: stage
174
312
  });
175
- return effect === void 0 ? void 0 : {
176
- effect: effect.title ?? effect.name,
313
+ return stuck === void 0 ? void 0 : {
314
+ effect: stuck.effect.title ?? stuck.effect.name,
315
+ failed: stuck.failed,
177
316
  kind: "blocked-effect"
178
317
  };
179
318
  }
180
319
 
181
320
  function stuckEffectOf(args) {
182
- return failedDeclaredEffect(args) ?? args.preview.claimedEffects[0];
321
+ const reported = failedDeclaredEffect(args);
322
+ if (reported !== void 0) return {
323
+ effect: reported,
324
+ failed: !0
325
+ };
326
+ const claimed = args.preview.claimedEffects[0];
327
+ return claimed === void 0 ? void 0 : {
328
+ effect: claimed,
329
+ failed: !1
330
+ };
183
331
  }
184
332
 
185
333
  function failedDeclaredEffect(args) {
@@ -195,13 +343,12 @@ function failedDeclaredEffect(args) {
195
343
  function overdueRunDetail(args) {
196
344
  const {definition: definition, now: now, preview: preview} = args, late = overdueTasks(preview, now);
197
345
  if (late.length === 0) return;
198
- const {titleOf: titleOf} = activityTitleResolver(preview, definition), only = late.length === 1 ? late[0] : void 0, due = only === void 0 ? void 0 : earliestPastDue(only, now);
199
- return only === void 0 || due === void 0 ? {
200
- kind: "overdue-many"
201
- } : {
202
- due: due,
346
+ const {titleOf: titleOf} = activityTitleResolver(preview, definition), only = late.length === 1 ? late[0] : void 0;
347
+ return {
348
+ count: late.length,
349
+ due: earliestPastDueAcross(late, now),
203
350
  kind: "overdue",
204
- task: titleOf(only.name)
351
+ task: only === void 0 ? void 0 : titleOf(only.name)
205
352
  };
206
353
  }
207
354
 
@@ -222,10 +369,6 @@ const NO_ASSIGNEES = {
222
369
  unassignedCount: 0
223
370
  };
224
371
 
225
- function openRunAssignees(preview) {
226
- return openAssignees(preview).assignees;
227
- }
228
-
229
372
  function openAssignees(instance) {
230
373
  const users = /* @__PURE__ */ new Map, roles = /* @__PURE__ */ new Map;
231
374
  let unassignedCount = 0;
@@ -397,7 +540,7 @@ function isRunAttention(value) {
397
540
  }
398
541
 
399
542
  function assigneeIdsOf(rows) {
400
- const ids = rows.flatMap(row => row.assignees.flatMap(assignee => assignee.type === "user" ? [ assignee.id ] : []));
543
+ const ids = rows.flatMap(row => index.userIdsOf(row.assignees));
401
544
  return [ ...new Set(ids) ];
402
545
  }
403
546
 
@@ -1222,20 +1365,26 @@ function tiebreak(a, b) {
1222
1365
  }
1223
1366
 
1224
1367
  function cardFacts(args) {
1225
- return new Map(args.previews.map(preview => [ preview._id, {
1226
- assignees: openRunAssignees(preview),
1227
- overdue: overdueTaskCount(preview, args.now),
1228
- overdueDetail: overdueRunDetail({
1229
- definition: args.definition,
1230
- now: args.now,
1231
- preview: preview
1232
- }),
1233
- blocked: blockedRunDetail({
1234
- definition: args.definition,
1235
- preview: preview
1236
- }),
1237
- settled: workflowEngine.terminalState(preview) !== "in-flight"
1238
- } ]));
1368
+ return new Map(args.previews.map(preview => {
1369
+ const settled = workflowEngine.terminalState(preview) !== "in-flight";
1370
+ return [ preview._id, {
1371
+ tasks: settled ? [] : stageTasksOf({
1372
+ definition: args.definition,
1373
+ instance: preview,
1374
+ now: args.now
1375
+ }),
1376
+ overdueDetail: overdueRunDetail({
1377
+ definition: args.definition,
1378
+ now: args.now,
1379
+ preview: preview
1380
+ }),
1381
+ blocked: blockedRunDetail({
1382
+ definition: args.definition,
1383
+ preview: preview
1384
+ }),
1385
+ settled: settled
1386
+ } ];
1387
+ }));
1239
1388
  }
1240
1389
 
1241
1390
  function laneTotals(cards, facts) {
@@ -1244,7 +1393,7 @@ function laneTotals(cards, facts) {
1244
1393
  const held = facts.get(card.instanceId);
1245
1394
  if (held === void 0) continue;
1246
1395
  const key = card.face.kind === "document" ? card.face.document.id : `run:${card.instanceId}`;
1247
- held.overdue > 0 && overdue.add(key), held.blocked !== void 0 && stuck.add(key);
1396
+ held.overdueDetail !== void 0 && overdue.add(key), held.blocked !== void 0 && stuck.add(key);
1248
1397
  }
1249
1398
  return {
1250
1399
  overdue: overdue.size,
@@ -1310,6 +1459,237 @@ function boardDocuments(board) {
1310
1459
  return board.groups.flatMap(group => group.cards.flatMap(card => card.face.kind === "document" ? [ card.face.document ] : []));
1311
1460
  }
1312
1461
 
1462
+ const LATE_FACE = {
1463
+ Icon: Calendar.CalendarIcon,
1464
+ tone: "caution"
1465
+ };
1466
+
1467
+ function LateSpan({now: now, since: since}) {
1468
+ /* @__PURE__ */
1469
+ return jsxRuntime.jsx(index.MetricLockup, {
1470
+ Icon: LATE_FACE.Icon,
1471
+ tone: LATE_FACE.tone,
1472
+ value: index.shortSpanOf(index.parseStoredDateValue(since), now)
1473
+ });
1474
+ }
1475
+
1476
+ const HOLDER_OVERLAP = 7, HOLDER_RING = 1.5, GROUP_GAP_TOKEN = 3;
1477
+
1478
+ function useDisplayById(ids) {
1479
+ const displays = index.useUserDisplays(index.uniqueIds(ids)), labels = index.disambiguatedNames(displays);
1480
+ return new Map(displays.map((display, index2) => [ display.id, {
1481
+ ...display,
1482
+ label: labels[index2] ?? display.name
1483
+ } ]));
1484
+ }
1485
+
1486
+ function holderKey(assignee, index2) {
1487
+ return assignee.type === "user" ? assignee.id : `${assignee.role}-${index2}`;
1488
+ }
1489
+
1490
+ function stageTasksFace(args) {
1491
+ return {
1492
+ content: /* @__PURE__ */ jsxRuntime.jsx(StageTasksCard, {
1493
+ now: args.now,
1494
+ tasks: args.tasks
1495
+ }),
1496
+ flush: !0,
1497
+ wide: !0
1498
+ };
1499
+ }
1500
+
1501
+ function StageTaskGroups({now: now, room: room, tasks: tasks}) {
1502
+ const gap = index.useSpaceToken(GROUP_GAP_TOKEN), face = index.useAvatarSize(), {overflow: overflow, visible: visible} = visibleStageTasks({
1503
+ metrics: {
1504
+ counter: face,
1505
+ face: face,
1506
+ gap: gap,
1507
+ overlap: HOLDER_OVERLAP
1508
+ },
1509
+ room: room ?? face * 3 + gap * 2,
1510
+ tasks: tasks
1511
+ });
1512
+ return tasks.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
1513
+ ...stageTasksFace({
1514
+ now: now,
1515
+ tasks: tasks
1516
+ }),
1517
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1518
+ align: "center",
1519
+ "aria-label": "Tasks",
1520
+ style: {
1521
+ columnGap: gap
1522
+ },
1523
+ children: [ visible.map(task => /* @__PURE__ */ jsxRuntime.jsx(TaskGroupFace, {
1524
+ assignees: task.assignees
1525
+ }, task.name)), overflow === 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
1526
+ muted: !0,
1527
+ size: 1,
1528
+ children: [ "+", overflow ]
1529
+ }) ]
1530
+ })
1531
+ });
1532
+ }
1533
+
1534
+ function TaskGroupFace({assignees: assignees}) {
1535
+ return assignees.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(UnassignedMark, {}) : /* @__PURE__ */ jsxRuntime.jsx(OverlappedHolders, {
1536
+ assignees: assignees
1537
+ });
1538
+ }
1539
+
1540
+ function HolderFace({assignee: assignee, displayById: displayById}) {
1541
+ const vocabulary = index.useProjectMembers();
1542
+ if (assignee.type === "role") /* @__PURE__ */
1543
+ return jsxRuntime.jsx(workflowComponents.RoleAvatar, {
1544
+ role: workflowComponents.memberRoleFor(vocabulary, assignee.role),
1545
+ size: 0
1546
+ });
1547
+ const display = displayById.get(assignee.id);
1548
+ /* @__PURE__ */
1549
+ return jsxRuntime.jsx(workflowComponents.MemberAvatar, {
1550
+ imageUrl: display?.imageUrl,
1551
+ name: display?.name ?? assignee.id,
1552
+ size: 0
1553
+ });
1554
+ }
1555
+
1556
+ function OverlappedHolders({assignees: assignees}) {
1557
+ const {color: color} = ui.useTheme_v2(), stacked = stackedAssigneesOf(assignees), displayById = useDisplayById(index.userIdsOf(stacked)), ring = `0 0 0 ${HOLDER_RING}px ${color.bg}`;
1558
+ /* @__PURE__ */
1559
+ return jsxRuntime.jsx(ui.Flex, {
1560
+ align: "center",
1561
+ style: {
1562
+ flexShrink: 0,
1563
+ isolation: "isolate"
1564
+ },
1565
+ children: stacked.map((assignee, index2) => /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1566
+ style: {
1567
+ borderRadius: assignee.type === "role" ? void 0 : "50%",
1568
+ boxShadow: ring,
1569
+ flexShrink: 0,
1570
+ marginLeft: index2 === 0 ? 0 : -HOLDER_OVERLAP,
1571
+ position: "relative",
1572
+ zIndex: index2
1573
+ },
1574
+ children: /* @__PURE__ */ jsxRuntime.jsx(HolderFace, {
1575
+ assignee: assignee,
1576
+ displayById: displayById
1577
+ })
1578
+ }, holderKey(assignee, index2)))
1579
+ });
1580
+ }
1581
+
1582
+ function UnassignedMark() {
1583
+ const size = index.useAvatarSize(), {color: color} = ui.useTheme_v2();
1584
+ /* @__PURE__ */
1585
+ return jsxRuntime.jsx(ui.Flex, {
1586
+ align: "center",
1587
+ justify: "center",
1588
+ style: {
1589
+ border: `1px dashed ${color.border}`,
1590
+ borderRadius: "50%",
1591
+ boxSizing: "border-box",
1592
+ flexShrink: 0,
1593
+ height: size,
1594
+ width: size
1595
+ },
1596
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1597
+ size: 0,
1598
+ children: /* @__PURE__ */ jsxRuntime.jsx(User.UserIcon, {
1599
+ style: {
1600
+ color: color.input.default.enabled.placeholder
1601
+ }
1602
+ })
1603
+ })
1604
+ });
1605
+ }
1606
+
1607
+ function StageTasksCard({now: now, tasks: tasks}) {
1608
+ const displayById = useDisplayById(tasks.flatMap(task => index.userIdsOf(task.assignees)));
1609
+ /* @__PURE__ */
1610
+ return jsxRuntime.jsxs(ui.Stack, {
1611
+ gap: 4,
1612
+ padding: 3,
1613
+ children: [
1614
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1615
+ muted: !0,
1616
+ size: 0,
1617
+ style: {
1618
+ letterSpacing: "0.06em",
1619
+ textTransform: "uppercase"
1620
+ },
1621
+ weight: "semibold",
1622
+ children: "Tasks in this stage"
1623
+ }),
1624
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
1625
+ gap: 4,
1626
+ children: tasks.map(task => /* @__PURE__ */ jsxRuntime.jsx(TaskCardRow, {
1627
+ displayById: displayById,
1628
+ now: now,
1629
+ task: task
1630
+ }, task.name))
1631
+ }) ]
1632
+ });
1633
+ }
1634
+
1635
+ function TaskCardRow({displayById: displayById, now: now, task: task}) {
1636
+ /* @__PURE__ */
1637
+ return jsxRuntime.jsxs(ui.Stack, {
1638
+ gap: 3,
1639
+ children: [
1640
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1641
+ align: "center",
1642
+ gap: 3,
1643
+ children: [
1644
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1645
+ size: 1,
1646
+ children: task.title
1647
+ }), task.overdueSince === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(LateSpan, {
1648
+ now: now,
1649
+ since: task.overdueSince
1650
+ }) ]
1651
+ }),
1652
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
1653
+ gap: 2,
1654
+ children: taskHoldersOf(task.assignees).map((holder, index2) => /* @__PURE__ */ jsxRuntime.jsx(HolderLine, {
1655
+ displayById: displayById,
1656
+ holder: holder
1657
+ }, holder.kind === "held" ? holderKey(holder.assignee, index2) : "unassigned"))
1658
+ }) ]
1659
+ });
1660
+ }
1661
+
1662
+ function HolderLine({displayById: displayById, holder: holder}) {
1663
+ const vocabulary = index.useProjectMembers();
1664
+ if (holder.kind === "unassigned") /* @__PURE__ */
1665
+ return jsxRuntime.jsxs(ui.Flex, {
1666
+ align: "center",
1667
+ gap: 2,
1668
+ children: [
1669
+ /* @__PURE__ */ jsxRuntime.jsx(UnassignedMark, {}),
1670
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1671
+ muted: !0,
1672
+ size: 1,
1673
+ children: "Unassigned"
1674
+ }) ]
1675
+ });
1676
+ const {assignee: assignee} = holder, label = assignee.type === "user" ? displayById.get(assignee.id)?.label ?? assignee.id : workflowComponents.roleLabel(workflowComponents.memberRoleFor(vocabulary, assignee.role));
1677
+ /* @__PURE__ */
1678
+ return jsxRuntime.jsxs(ui.Flex, {
1679
+ align: "center",
1680
+ gap: 2,
1681
+ children: [
1682
+ /* @__PURE__ */ jsxRuntime.jsx(HolderFace, {
1683
+ assignee: assignee,
1684
+ displayById: displayById
1685
+ }),
1686
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1687
+ size: 1,
1688
+ children: label
1689
+ }) ]
1690
+ });
1691
+ }
1692
+
1313
1693
  function overdueAttentionHint(documents) {
1314
1694
  const task = documents === 1 ? "an overdue task" : "overdue tasks";
1315
1695
  return `${pluralize__default.default("document", documents, !0)} ${documents === 1 ? "has" : "have"} ${task}`;
@@ -1319,32 +1699,69 @@ function blockedAttentionHint(documents) {
1319
1699
  return `${pluralize__default.default("document", documents, !0)} ${documents === 1 ? "is" : "are"} blocked`;
1320
1700
  }
1321
1701
 
1702
+ function overdueMark(detail, now) {
1703
+ const span = overdueSpan(detail.due, now), tasks = pluralize__default.default("task", detail.count, !0);
1704
+ return detail.count === 1 && detail.task !== void 0 ? {
1705
+ hint: `“${detail.task}” — ${span === void 0 ? "overdue" : `${span} overdue`}`,
1706
+ value: span ?? tasks
1707
+ } : {
1708
+ hint: span === void 0 ? `${tasks} overdue` : `${tasks} overdue — the longest by ${span}`,
1709
+ value: tasks
1710
+ };
1711
+ }
1712
+
1713
+ function overdueSpan(due, now) {
1714
+ const from = index.parseStoredDateValue(due);
1715
+ if (from !== void 0) return index.shortSpanOf(from, now);
1716
+ }
1717
+
1718
+ function overdueMarkOpensPanel(tasks) {
1719
+ return tasks.some(task => task.overdueSince !== void 0);
1720
+ }
1721
+
1722
+ function blockedMarkHint(detail) {
1723
+ return detail.kind === "blocked-many" ? `${pluralize__default.default("task", detail.count, !0)} were sent back` : detail.kind === "blocked-effect" ? `Automation “${detail.effect}” ${detail.failed ? "failed" : "didn’t finish"}` : detail.task === void 0 ? "A task was sent back" : `“${detail.task}” was sent back`;
1724
+ }
1725
+
1322
1726
  const ATTENTION_FACES = {
1323
1727
  blocked: {
1324
1728
  Icon: ErrorOutline.ErrorOutlineIcon,
1325
1729
  tone: "critical"
1326
1730
  },
1327
- overdue: {
1328
- Icon: WarningOutline.WarningOutlineIcon,
1329
- tone: "caution"
1330
- }
1731
+ overdue: LATE_FACE
1331
1732
  };
1332
1733
 
1333
- function AttentionGlyph({detail: detail}) {
1334
- const overdue = detail.kind === "overdue" || detail.kind === "overdue-many", face = ATTENTION_FACES[overdue ? "overdue" : "blocked"], words = overdue ? overdueAttentionHint(1) : blockedAttentionHint(1);
1734
+ function AttentionMark({detail: detail, now: now, tasks: tasks}) {
1735
+ if (detail.kind === "overdue") {
1736
+ const {hint: hint2, value: value} = overdueMark(detail, now);
1737
+ /* @__PURE__ */
1738
+ return jsxRuntime.jsx(index.MetricPair, {
1739
+ Icon: ATTENTION_FACES.overdue.Icon,
1740
+ face: overdueMarkOpensPanel(tasks) ? stageTasksFace({
1741
+ now: now,
1742
+ tasks: tasks
1743
+ }) : {
1744
+ text: hint2
1745
+ },
1746
+ label: hint2,
1747
+ tone: ATTENTION_FACES.overdue.tone,
1748
+ value: value
1749
+ });
1750
+ }
1751
+ const hint = blockedMarkHint(detail);
1335
1752
  /* @__PURE__ */
1336
- return jsxRuntime.jsx(index.AlertGlyph, {
1337
- Icon: face.Icon,
1338
- hint: words,
1339
- label: words,
1340
- tone: face.tone
1753
+ return jsxRuntime.jsx(index.AlertChip, {
1754
+ Icon: ATTENTION_FACES.blocked.Icon,
1755
+ hint: hint,
1756
+ label: hint,
1757
+ tone: ATTENTION_FACES.blocked.tone
1341
1758
  });
1342
1759
  }
1343
1760
 
1344
1761
  const CARD_RADIUS = 3;
1345
1762
 
1346
1763
  function BoardCard({card: card, wiring: wiring}) {
1347
- const {facts: facts, onSelect: onSelect, selectedInstanceId: selectedInstanceId} = wiring, {color: color} = ui.useTheme_v2(), faceInset = index.useSpaceToken(2), selected = card.instanceId === selectedInstanceId, held = facts.get(card.instanceId);
1764
+ const {facts: facts, now: now, onSelect: onSelect, selectedInstanceId: selectedInstanceId} = wiring, {color: color} = ui.useTheme_v2(), faceInset = index.useSpaceToken(2), selected = card.instanceId === selectedInstanceId, held = facts.get(card.instanceId);
1348
1765
  /* @__PURE__ */
1349
1766
  return jsxRuntime.jsx(ui.Card, {
1350
1767
  "aria-pressed": selected,
@@ -1380,22 +1797,24 @@ function BoardCard({card: card, wiring: wiring}) {
1380
1797
  })
1381
1798
  })
1382
1799
  }), held === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(CardMetrics, {
1383
- facts: held
1800
+ facts: held,
1801
+ now: now
1384
1802
  }) ]
1385
1803
  })
1386
1804
  });
1387
1805
  }
1388
1806
 
1389
- function CardMetrics({facts: facts}) {
1390
- const markOverhang = -index.useCapTrimFor(index.useTextIconSize()), bare = facts.assignees.length === 0 && facts.overdueDetail === void 0 && facts.blocked === void 0;
1807
+ function CardMetrics({facts: facts, now: now}) {
1808
+ const markOverhang = -index.useCapTrimFor(index.useTextIconSize()), bare = facts.tasks.length === 0 && facts.overdueDetail === void 0 && facts.blocked === void 0;
1391
1809
  return facts.settled || bare ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1392
1810
  padding: 1,
1393
1811
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1394
1812
  align: "center",
1395
1813
  gap: 3,
1396
1814
  children: [
1397
- /* @__PURE__ */ jsxRuntime.jsx(index.RowAssignees, {
1398
- assignees: facts.assignees
1815
+ /* @__PURE__ */ jsxRuntime.jsx(StageTaskGroups, {
1816
+ now: now,
1817
+ tasks: facts.tasks
1399
1818
  }),
1400
1819
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1401
1820
  align: "center",
@@ -1405,10 +1824,14 @@ function CardMetrics({facts: facts}) {
1405
1824
  style: {
1406
1825
  paddingRight: markOverhang
1407
1826
  },
1408
- children: [ facts.overdueDetail === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(AttentionGlyph, {
1409
- detail: facts.overdueDetail
1410
- }), facts.blocked === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(AttentionGlyph, {
1411
- detail: facts.blocked
1827
+ children: [ facts.overdueDetail === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(AttentionMark, {
1828
+ detail: facts.overdueDetail,
1829
+ now: now,
1830
+ tasks: facts.tasks
1831
+ }), facts.blocked === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(AttentionMark, {
1832
+ detail: facts.blocked,
1833
+ now: now,
1834
+ tasks: facts.tasks
1412
1835
  }) ]
1413
1836
  }) ]
1414
1837
  })
@@ -1909,6 +2332,7 @@ function RunsBoard({onSelect: onSelect, panelOpen: panelOpen, selectedInstanceId
1909
2332
  now: now,
1910
2333
  previews: shown
1911
2334
  }),
2335
+ now: now,
1912
2336
  onSelect: onSelect,
1913
2337
  selectedInstanceId: selectedInstanceId
1914
2338
  }
@@ -2841,6 +3265,12 @@ function SegmentDivider() {
2841
3265
  });
2842
3266
  }
2843
3267
 
3268
+ const SurfaceButton = styledComponents.styled(ui.Button)`
3269
+ &:not(:hover):not(:active):not([data-selected]) {
3270
+ --card-bg-color: ${({$surface: $surface}) => $surface};
3271
+ }
3272
+ `;
3273
+
2844
3274
  function RunsFilterRow({boardMode: boardMode, definition: definition, definitions: definitions, displayToggle: displayToggle, onView: onView, ready: ready, scoped: scoped, slot: slot, view: view, workflowCounts: workflowCounts, workflowScoped: workflowScoped}) {
2845
3275
  return ready ? slot === null ? null : reactDom.createPortal(
2846
3276
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
@@ -2850,9 +3280,7 @@ function RunsFilterRow({boardMode: boardMode, definition: definition, definition
2850
3280
  children: [
2851
3281
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
2852
3282
  align: "center",
2853
- flex: 1,
2854
3283
  gap: 2,
2855
- wrap: "wrap",
2856
3284
  children: [
2857
3285
  /* @__PURE__ */ jsxRuntime.jsx(WorkflowSelect, {
2858
3286
  counts: workflowCounts,
@@ -2863,7 +3291,15 @@ function RunsFilterRow({boardMode: boardMode, definition: definition, definition
2863
3291
  }), boardMode ? null : /* @__PURE__ */ jsxRuntime.jsx(ScopeSelect, {
2864
3292
  onView: onView,
2865
3293
  view: view
2866
- }),
3294
+ }) ]
3295
+ }),
3296
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
3297
+ align: "center",
3298
+ flex: 1,
3299
+ gap: 2,
3300
+ justify: "flex-end",
3301
+ wrap: "wrap",
3302
+ children: [
2867
3303
  /* @__PURE__ */ jsxRuntime.jsx(RunsFilterChips, {
2868
3304
  definition: definition,
2869
3305
  onView: onView,
@@ -2978,13 +3414,14 @@ function PickerLineGhost() {
2978
3414
  }
2979
3415
 
2980
3416
  function ScopeSelect({onView: onView, view: view}) {
2981
- const pick = scope => onView({
3417
+ const {color: color} = ui.useTheme_v2(), pick = scope => onView({
2982
3418
  scope: scope,
2983
3419
  filters: view.filters
2984
3420
  });
2985
3421
  /* @__PURE__ */
2986
3422
  return jsxRuntime.jsx(ui.MenuButton, {
2987
- button: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
3423
+ button: /* @__PURE__ */ jsxRuntime.jsx(SurfaceButton, {
3424
+ $surface: color.bg,
2988
3425
  fontSize: 1,
2989
3426
  iconRight: ChevronDown.ChevronDownIcon,
2990
3427
  mode: "ghost",
@@ -4026,7 +4463,17 @@ const TIME_WIDTH = 112, WORKFLOW_WIDTH = {
4026
4463
  key: "ended",
4027
4464
  label: "Closed",
4028
4465
  width: TIME_WIDTH
4029
- } ], ASSIGNEES_WIDTH = 90, ATTENTION_WIDTH = 48;
4466
+ } ], TASKS_WIDTH = 120, TASKS_WIDTH_ROOMY = 168, ROOMY_TABLE_WIDTH = 1040;
4467
+
4468
+ function tasksWidthOf(tableWidth) {
4469
+ return tableWidth === void 0 ? TASKS_WIDTH : tableWidth >= ROOMY_TABLE_WIDTH ? TASKS_WIDTH_ROOMY : TASKS_WIDTH;
4470
+ }
4471
+
4472
+ function taskRoomOf(args) {
4473
+ return tasksWidthOf(args.tableWidth) - args.inset * 2;
4474
+ }
4475
+
4476
+ const ATTENTION_WIDTH = 96;
4030
4477
 
4031
4478
  function useRowWindow(args) {
4032
4479
  const {element: element, keyAt: keyAt, rowCount: rowCount} = args, [scroller, setScroller] = react.useState(null);
@@ -4072,7 +4519,7 @@ function SpacerRow({height: height}) {
4072
4519
  function documentWidthOf(args) {
4073
4520
  const {columns: columns, scope: scope, tableWidth: tableWidth} = args;
4074
4521
  if (tableWidth === void 0) return;
4075
- const trailing = scope === "open" ? ASSIGNEES_WIDTH + ATTENTION_WIDTH : 0, fixed = columns.reduce((sum, column) => {
4522
+ const trailing = scope === "open" ? tasksWidthOf(tableWidth) + ATTENTION_WIDTH : 0, fixed = columns.reduce((sum, column) => {
4076
4523
  const width = resolveWidth(column.width, tableWidth);
4077
4524
  return typeof width == "number" ? sum + width : sum;
4078
4525
  }, trailing);
@@ -4080,7 +4527,7 @@ function documentWidthOf(args) {
4080
4527
  }
4081
4528
 
4082
4529
  function RunsTable({now: now, onSelectRun: onSelectRun, onSort: onSort, rows: rows, scope: scope, scopedToWorkflow: scopedToWorkflow, selectedId: selectedId, sort: sort}) {
4083
- const columns = (scope === "closed" ? CLOSED_COLUMNS : OPEN_COLUMNS).filter(column => !(scopedToWorkflow && column.key === "workflow")), styles = useTableStyles(), [element, setElement] = react.useState(null), rect = ui.useElementRect(element), window2 = useRowWindow({
4530
+ const columns = (scope === "closed" ? CLOSED_COLUMNS : OPEN_COLUMNS).filter(column => !(scopedToWorkflow && column.key === "workflow")), styles = useTableStyles(), cellInset = index.useSpaceToken(2), [element, setElement] = react.useState(null), rect = ui.useElementRect(element), window2 = useRowWindow({
4084
4531
  element: element,
4085
4532
  keyAt: index2 => rows[index2]?.id ?? index2,
4086
4533
  rowCount: rows.length
@@ -4129,14 +4576,14 @@ function RunsTable({now: now, onSelectRun: onSelectRun, onSort: onSort, rows: ro
4129
4576
  /* @__PURE__ */ jsxRuntime.jsx("th", {
4130
4577
  style: {
4131
4578
  ...styles.head,
4132
- width: ASSIGNEES_WIDTH
4579
+ width: tasksWidthOf(rect?.width)
4133
4580
  },
4134
4581
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
4135
4582
  padding: 2,
4136
4583
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
4137
4584
  muted: !0,
4138
4585
  size: 1,
4139
- children: "Assignees"
4586
+ children: "Tasks"
4140
4587
  })
4141
4588
  })
4142
4589
  }),
@@ -4164,7 +4611,11 @@ function RunsTable({now: now, onSelectRun: onSelectRun, onSort: onSort, rows: ro
4164
4611
  row: row,
4165
4612
  scope: scope,
4166
4613
  scopedToWorkflow: scopedToWorkflow,
4167
- selected: row.id === selectedId
4614
+ selected: row.id === selectedId,
4615
+ taskRoom: taskRoomOf({
4616
+ inset: cellInset,
4617
+ tableWidth: rect?.width
4618
+ })
4168
4619
  }, item.key);
4169
4620
  }),
4170
4621
  /* @__PURE__ */ jsxRuntime.jsx(SpacerRow, {
@@ -4175,7 +4626,7 @@ function RunsTable({now: now, onSelectRun: onSelectRun, onSort: onSort, rows: ro
4175
4626
  });
4176
4627
  }
4177
4628
 
4178
- const RunTableRow = react.memo(function({dataIndex: dataIndex, measureRow: measureRow, now: now, onSelectRun: onSelectRun, row: row, scope: scope, scopedToWorkflow: scopedToWorkflow, selected: selected}) {
4629
+ const RunTableRow = react.memo(function({dataIndex: dataIndex, measureRow: measureRow, now: now, onSelectRun: onSelectRun, row: row, scope: scope, scopedToWorkflow: scopedToWorkflow, selected: selected, taskRoom: taskRoom}) {
4179
4630
  const styles = useTableStyles(), documentInset = index.useSpaceToken(1);
4180
4631
  /* @__PURE__ */
4181
4632
  return jsxRuntime.jsxs(TableRowCard, {
@@ -4205,7 +4656,8 @@ const RunTableRow = react.memo(function({dataIndex: dataIndex, measureRow: measu
4205
4656
  row: row
4206
4657
  }) : /* @__PURE__ */ jsxRuntime.jsx(OpenCells, {
4207
4658
  now: now,
4208
- row: row
4659
+ row: row,
4660
+ taskRoom: taskRoom
4209
4661
  }) ]
4210
4662
  });
4211
4663
  });
@@ -4231,7 +4683,7 @@ function DocumentCell({row: row}) {
4231
4683
  });
4232
4684
  }
4233
4685
 
4234
- function OpenCells({now: now, row: row}) {
4686
+ function OpenCells({now: now, row: row, taskRoom: taskRoom}) {
4235
4687
  const styles = useTableStyles();
4236
4688
  /* @__PURE__ */
4237
4689
  return jsxRuntime.jsxs(jsxRuntime.Fragment, {
@@ -4252,17 +4704,23 @@ function OpenCells({now: now, row: row}) {
4252
4704
  })
4253
4705
  }),
4254
4706
  /* @__PURE__ */ jsxRuntime.jsx("td", {
4255
- style: styles.cell,
4707
+ style: {
4708
+ ...styles.cell,
4709
+ overflow: "hidden"
4710
+ },
4256
4711
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4257
4712
  align: "center",
4258
- children: /* @__PURE__ */ jsxRuntime.jsx(index.RowAssignees, {
4259
- assignees: row.assignees
4713
+ children: /* @__PURE__ */ jsxRuntime.jsx(StageTaskGroups, {
4714
+ now: now,
4715
+ room: taskRoom,
4716
+ tasks: row.tasks
4260
4717
  })
4261
4718
  })
4262
4719
  }),
4263
4720
  /* @__PURE__ */ jsxRuntime.jsx("td", {
4264
4721
  style: styles.cell,
4265
- children: /* @__PURE__ */ jsxRuntime.jsx(AttentionMark, {
4722
+ children: /* @__PURE__ */ jsxRuntime.jsx(AttentionCell, {
4723
+ now: now,
4266
4724
  row: row
4267
4725
  })
4268
4726
  }) ]
@@ -4299,12 +4757,14 @@ function ClosedCells({now: now, row: row}) {
4299
4757
  });
4300
4758
  }
4301
4759
 
4302
- function AttentionMark({row: row}) {
4760
+ function AttentionCell({now: now, row: row}) {
4303
4761
  return row.attentionDetail === void 0 ? null :
4304
4762
  /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4305
4763
  align: "center",
4306
- children: /* @__PURE__ */ jsxRuntime.jsx(AttentionGlyph, {
4307
- detail: row.attentionDetail
4764
+ children: /* @__PURE__ */ jsxRuntime.jsx(AttentionMark, {
4765
+ detail: row.attentionDetail,
4766
+ now: now,
4767
+ tasks: row.tasks
4308
4768
  })
4309
4769
  });
4310
4770
  }
@@ -5095,7 +5555,7 @@ function StatusBox({children: children}) {
5095
5555
  });
5096
5556
  }
5097
5557
 
5098
- const EDITORIAL_WORKFLOWS_DOCS_URL = "https://www.sanity.io/docs/editorial-workflows", WORK_TABS = index.TOOL_TABS.filter(tab => tab.value !== "definitions");
5558
+ const WORKFLOWS_DOCS_URL = "https://www.sanity.io/docs/workflows", WORK_TABS = index.TOOL_TABS.filter(tab => tab.value !== "definitions");
5099
5559
 
5100
5560
  function WorkflowsToolRoot() {
5101
5561
  const router$1 = router.useRouter(), telemetry = workflowReact.useWorkflowTelemetry(), {color: color} = ui.useTheme_v2(), [filterSlot, setFilterSlot] = react.useState(null), route = index.toolRoute(router$1.state), previews = useToolPreviews(), setTab = next => router$1.navigate(index.toolTabState(next)), selectTab = next => {
@@ -5210,7 +5670,7 @@ function ToolOverflowMenu({onOpenDefinitions: onOpenDefinitions}) {
5210
5670
  /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
5211
5671
  as: "a",
5212
5672
  fontSize: 1,
5213
- href: EDITORIAL_WORKFLOWS_DOCS_URL,
5673
+ href: WORKFLOWS_DOCS_URL,
5214
5674
  iconRight: Launch.LaunchIcon,
5215
5675
  rel: "noopener noreferrer",
5216
5676
  target: "_blank",