@sanity/workflow-studio-plugin 0.25.0 → 0.27.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"), ui = require("@sanity/ui"), workflowReact = require("@sanity/workflow-react"), react = require("react"), router = require("sanity/router"), index = require("./index.cjs"), Clock = require("@sanity/icons/Clock"), Cog = require("@sanity/icons/Cog"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal"), workflowEngine = require("@sanity/workflow-engine"), ArrowLeft = require("@sanity/icons/ArrowLeft"), Transfer = require("@sanity/icons/Transfer"), WarningOutline = require("@sanity/icons/WarningOutline"), pluralize = require("pluralize-esm"), sanity = require("sanity"), workflowStudio = require("@sanity/workflow-studio"), Add = require("@sanity/icons/Add"), Search = require("@sanity/icons/Search"), Checkmark = require("@sanity/icons/Checkmark"), Close = require("@sanity/icons/Close"), Filter = require("@sanity/icons/Filter"), workflowComponents = require("@sanity/workflow-components"), endOfDay = require("date-fns/endOfDay"), reactDom = require("react-dom"), types = require("@sanity/types"), workflowDiagram = require("@sanity/workflow-diagram"), ChevronDown = require("@sanity/icons/ChevronDown"), react$1 = require("motion/react");
3
+ var jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), workflowReact = require("@sanity/workflow-react"), react = require("react"), router = require("sanity/router"), index = require("./index.cjs"), workflowEngine = require("@sanity/workflow-engine"), ArrowLeft = require("@sanity/icons/ArrowLeft"), Transfer = require("@sanity/icons/Transfer"), WarningOutline = require("@sanity/icons/WarningOutline"), pluralize = require("pluralize-esm"), sanity = require("sanity"), workflowStudio = require("@sanity/workflow-studio"), Add = require("@sanity/icons/Add"), Search = require("@sanity/icons/Search"), Checkmark = require("@sanity/icons/Checkmark"), Close = require("@sanity/icons/Close"), Filter = require("@sanity/icons/Filter"), workflowComponents = require("@sanity/workflow-components"), endOfDay = require("date-fns/endOfDay"), reactDom = require("react-dom"), ChevronDown = require("@sanity/icons/ChevronDown"), ChevronRight = require("@sanity/icons/ChevronRight"), types = require("@sanity/types"), workflowDiagram = require("@sanity/workflow-diagram"), react$1 = require("motion/react");
4
4
 
5
5
  function _interopDefaultCompat(e) {
6
6
  return e && typeof e == "object" && "default" in e ? e : {
@@ -10,16 +10,6 @@ function _interopDefaultCompat(e) {
10
10
 
11
11
  var pluralize__default = /* @__PURE__ */ _interopDefaultCompat(pluralize);
12
12
 
13
- function CodeChip({value: value}) {
14
- /* @__PURE__ */
15
- return jsxRuntime.jsx("code", {
16
- style: {
17
- overflowWrap: "anywhere"
18
- },
19
- children: value
20
- });
21
- }
22
-
23
13
  function SectionHeading({children: children}) {
24
14
  /* @__PURE__ */
25
15
  return jsxRuntime.jsx(ui.Text, {
@@ -29,146 +19,6 @@ function SectionHeading({children: children}) {
29
19
  });
30
20
  }
31
21
 
32
- function normalizeAbortReason(raw) {
33
- const trimmed = raw.trim();
34
- return trimmed === "" ? void 0 : trimmed;
35
- }
36
-
37
- function abortCommitReport(args) {
38
- return args.changed ? {
39
- outcome: "aborted",
40
- toast: abortedToast(args.title)
41
- } : {
42
- outcome: "already-settled",
43
- toast: abortAlreadySettledToast(args.title)
44
- };
45
- }
46
-
47
- function abortedToast(title) {
48
- return {
49
- id: index.TOAST_ID.abort,
50
- status: "info",
51
- title: `Aborted “${title}”`
52
- };
53
- }
54
-
55
- function abortAlreadySettledToast(title) {
56
- return {
57
- id: index.TOAST_ID.abort,
58
- status: "info",
59
- title: `“${title}” had already finished`,
60
- description: "Nothing was aborted — the workflow reached a terminal state first."
61
- };
62
- }
63
-
64
- function abortUnconfirmedToast(args) {
65
- return {
66
- id: index.TOAST_ID.abort,
67
- status: "warning",
68
- title: `Couldn’t confirm “${args.title}” was aborted`,
69
- description: `${index.describeError(args.err)} — the abort may still have committed, so check the workflow’s state before trying again.`
70
- };
71
- }
72
-
73
- function AbortWorkflowDialog({entry: entry, onClose: onClose}) {
74
- const {engine: engine} = index.useWorkflowContext(), definition = index.useDefinition(entry), toast = index.useWorkflowToast(), telemetry = workflowReact.useWorkflowTelemetry(), [typedReason, setTypedReason] = react.useState(""), [pending, setPending] = react.useState(!1), {instance: instance} = entry, title = index.instanceTitle(instance, definition), reason = normalizeAbortReason(typedReason), reasonInputId = `abort-workflow-reason-${instance._id}`;
75
- index.useLogEventOnMount(index.WorkflowAbortDialogOpened, {
76
- instanceId: instance._id
77
- });
78
- const confirm = async () => {
79
- if (reason === void 0) return;
80
- const log = outcome => telemetry.log(index.WorkflowAbortDialogSubmitted, {
81
- instanceId: instance._id,
82
- outcome: outcome
83
- });
84
- setPending(!0);
85
- try {
86
- const {changed: changed} = await engine.abortInstance({
87
- instanceId: instance._id,
88
- reason: reason
89
- }), report = abortCommitReport({
90
- title: title,
91
- changed: changed
92
- });
93
- toast.push(report.toast), log(report.outcome), onClose();
94
- } catch (err) {
95
- toast.push(abortUnconfirmedToast({
96
- title: title,
97
- err: err
98
- })), log("unconfirmed"), setPending(!1);
99
- }
100
- };
101
- /* @__PURE__ */
102
- return jsxRuntime.jsx(ui.Dialog, {
103
- footer: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
104
- padding: 3,
105
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
106
- gap: 2,
107
- justify: "flex-end",
108
- children: [
109
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
110
- disabled: pending,
111
- fontSize: 1,
112
- mode: "bleed",
113
- onClick: onClose,
114
- padding: 2,
115
- text: "Cancel"
116
- }),
117
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
118
- disabled: reason === void 0,
119
- fontSize: 1,
120
- loading: pending,
121
- onClick: () => {
122
- confirm();
123
- },
124
- padding: 2,
125
- text: "Abort workflow",
126
- tone: "critical"
127
- }) ]
128
- })
129
- }),
130
- header: "Abort workflow",
131
- id: `abort-workflow-${instance._id}`,
132
- onClose: () => {
133
- pending || onClose();
134
- },
135
- width: 0,
136
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
137
- padding: 4,
138
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
139
- gap: 5,
140
- children: [
141
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
142
- size: 1,
143
- children: [ "Abort the ",
144
- /* @__PURE__ */ jsxRuntime.jsx("strong", {
145
- children: title
146
- }), " workflow? This cannot be undone." ]
147
- }),
148
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
149
- gap: 3,
150
- children: [
151
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
152
- as: "label",
153
- htmlFor: reasonInputId,
154
- size: 1,
155
- weight: "medium",
156
- children: "Reason"
157
- }),
158
- /* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, {
159
- autoFocus: !0,
160
- fontSize: 1,
161
- id: reasonInputId,
162
- onChange: event => setTypedReason(event.currentTarget.value),
163
- placeholder: "Why is this workflow being stopped?",
164
- value: typedReason
165
- }) ]
166
- }) ]
167
- })
168
- })
169
- });
170
- }
171
-
172
22
  function AbortWorkflowSection({entry: entry}) {
173
23
  const [aborting, setAborting] = react.useState(!1);
174
24
  return index.isLiveEntry(entry) ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
@@ -186,368 +36,13 @@ function AbortWorkflowSection({entry: entry}) {
186
36
  text: "Abort workflow…",
187
37
  tone: "critical"
188
38
  })
189
- }), aborting ? /* @__PURE__ */ jsxRuntime.jsx(AbortWorkflowDialog, {
39
+ }), aborting ? /* @__PURE__ */ jsxRuntime.jsx(index.AbortWorkflowDialog, {
190
40
  entry: entry,
191
41
  onClose: () => setAborting(!1)
192
42
  }) : null ]
193
43
  }) : null;
194
44
  }
195
45
 
196
- function makeTitleResolver(definition) {
197
- const activities = (definition?.stages ?? []).flatMap(s => s.activities ?? []);
198
- return {
199
- action: (activityName, actionName) => activities.find(t => t.name === activityName)?.actions?.find(a => a.name === actionName)?.title ?? actionName,
200
- activity: name => activities.find(t => t.name === name)?.title ?? name,
201
- stage: name => index.stageTitle(definition, name)
202
- };
203
- }
204
-
205
- function attributed(actor, ...parts) {
206
- if (actor) return {
207
- actorId: actor.id,
208
- parts: parts
209
- };
210
- const [first, ...rest] = parts;
211
- return typeof first != "string" ? {
212
- actorId: void 0,
213
- parts: parts
214
- } : {
215
- actorId: void 0,
216
- parts: [ first.charAt(0).toUpperCase() + first.slice(1), ...rest ]
217
- };
218
- }
219
-
220
- function standalone(...parts) {
221
- return {
222
- actorId: void 0,
223
- parts: parts
224
- };
225
- }
226
-
227
- const historyLiner = {
228
- stageEntered: (h, titles) => attributed(h.actor, `entered ${titles.stage(h.stage)}${h.fromStage ? ` from ${titles.stage(h.fromStage)}` : ""}`),
229
- stageExited: (h, titles) => attributed(h.actor, `left ${titles.stage(h.stage)} for ${titles.stage(h.toStage)}`),
230
- activityStatusChanged: (h, titles) => attributed(h.actor, `changed ${titles.activity(h.activity)}: ${h.from} → ${h.to}`),
231
- actionFired: (h, titles) => {
232
- const phrase = `fired ${titles.action(h.activity, h.action)} on ${titles.activity(h.activity)}`;
233
- return h.triggered ? standalone(`The workflow ${phrase}`) : attributed(h.actor, phrase);
234
- },
235
- transitionFired: (h, titles) => attributed(h.actor, `moved ${titles.stage(h.fromStage)} → ${titles.stage(h.toStage)}`),
236
- effectQueued: h => standalone("Effect ", {
237
- code: h.effect
238
- }, " queued"),
239
- effectCompleted: h => {
240
- const detail = h.detail ? ` — ${h.detail}` : "";
241
- return h.actor ? attributed(h.actor, "resolved effect ", {
242
- code: h.effect
243
- }, ` — ${h.status}${detail}`) : standalone("Effect ", {
244
- code: h.effect
245
- }, ` ${h.status}${detail}`);
246
- },
247
- effectClaimReleased: h => attributed(h.actor, "released a stale claim on effect ", {
248
- code: h.effect
249
- }, ` ${h.via === "sweep" ? "via the sweeper" : "via a drain takeover"}`),
250
- spawned: (h, titles) => attributed(h.actor, `spawned a child workflow from ${titles.activity(h.activity)}`),
251
- subworkflowAdopted: (h, titles) => standalone(`Re-adopted child workflow into ${titles.activity(h.activity)} on re-entering ${titles.stage(h.stage)}`),
252
- subworkflowResolved: (h, titles) => standalone(`Child workflow of ${titles.activity(h.activity)} ${h.status === "done" ? "completed" : "was aborted"}`),
253
- subworkflowOrphaned: h => standalone(`Orphaned child workflow reached terminal — ${h.detail}`),
254
- aborted: (h, titles) => attributed(h.actor, `aborted the workflow at ${titles.stage(h.stage)}${h.reason ? ` — ${h.reason}` : ""}`),
255
- opApplied: (h, titles) => h.action ? attributed(h.actor, `changed state via action ${titles.action(h.activity ?? "", h.action)}`) : h.activity ? attributed(h.actor, `changed state via activity ${titles.activity(h.activity)}`) : attributed(h.actor, "changed state via ", {
256
- code: h.opType
257
- }),
258
- fieldQueryDiscarded: h => standalone({
259
- code: JSON.stringify(h)
260
- })
261
- };
262
-
263
- function historyLine(h, titles) {
264
- return historyLiner[h._type](h, titles);
265
- }
266
-
267
- function historyWindow(args) {
268
- const {entries: entries, expanded: expanded} = args;
269
- return {
270
- overflows: entries.length > 6,
271
- shown: expanded ? entries : entries.slice(0, 6)
272
- };
273
- }
274
-
275
- function PendingEffectRows({instance: instance, now: now}) {
276
- const {drainEffectsFor: drainEffectsFor, completeEffectFor: completeEffectFor, effectHandlers: effectHandlers} = index.useWorkflowContext(), toast = index.useWorkflowToast(), [busy, setBusy] = react.useState(!1), pending = instance.pendingEffects ?? [];
277
- if (pending.length === 0) return null;
278
- const runRegistered = async () => {
279
- setBusy(!0);
280
- try {
281
- await drainEffectsFor(instance._id), toast.push({
282
- id: index.TOAST_ID.effectsDrain,
283
- status: "info",
284
- title: "Ran the registered handlers"
285
- });
286
- } catch (err) {
287
- toast.push({
288
- id: index.TOAST_ID.effectsDrain,
289
- status: "error",
290
- title: "Failed to run pending effects",
291
- description: index.describeError(err)
292
- });
293
- } finally {
294
- setBusy(!1);
295
- }
296
- }, resolve = async (effectKey, status) => {
297
- setBusy(!0);
298
- try {
299
- await completeEffectFor(instance._id, {
300
- effectKey: effectKey,
301
- status: status,
302
- detail: "Resolved in Studio"
303
- }), toast.push({
304
- id: index.TOAST_ID.effectResolve,
305
- status: "info",
306
- title: `Effect marked as ${status}`
307
- });
308
- } catch (err) {
309
- toast.push({
310
- id: index.TOAST_ID.effectResolve,
311
- status: "error",
312
- title: "Failed to resolve the effect",
313
- description: index.describeError(err)
314
- });
315
- } finally {
316
- setBusy(!1);
317
- }
318
- };
319
- /* @__PURE__ */
320
- return jsxRuntime.jsx(jsxRuntime.Fragment, {
321
- children: pending.map(fx => /* @__PURE__ */ jsxRuntime.jsx(PendingEffectRow, {
322
- busy: busy,
323
- effect: fx,
324
- instanceId: instance._id,
325
- now: now,
326
- onResolve: status => resolve(fx._key, status),
327
- onRunHandlers: runRegistered,
328
- registered: fx.name in effectHandlers
329
- }, fx._key))
330
- });
331
- }
332
-
333
- function PendingEffectRow({busy: busy, effect: effect, instanceId: instanceId, now: now, onResolve: onResolve, onRunHandlers: onRunHandlers, registered: registered}) {
334
- const label = effect.title ?? effect.name, avatarSize = index.useAvatarSize();
335
- /* @__PURE__ */
336
- return jsxRuntime.jsxs(ui.Flex, {
337
- align: "center",
338
- gap: 2,
339
- children: [
340
- /* @__PURE__ */ jsxRuntime.jsx(FeedGlyph, {
341
- hint: "Waiting to run",
342
- icon: /* @__PURE__ */ jsxRuntime.jsx(Clock.ClockIcon, {})
343
- }),
344
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
345
- align: "center",
346
- flex: 1,
347
- gap: 1,
348
- style: {
349
- minWidth: 0
350
- },
351
- children: [
352
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
353
- style: {
354
- minWidth: 0
355
- },
356
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
357
- size: 1,
358
- textOverflow: "ellipsis",
359
- children: [ effect.title ?? /* @__PURE__ */ jsxRuntime.jsx(CodeChip, {
360
- value: effect.name
361
- }), registered ? " — ready to run in Studio" : " — waiting for the runtime" ]
362
- })
363
- }),
364
- /* @__PURE__ */ jsxRuntime.jsx(FeedStamp, {
365
- at: effect.queuedAt,
366
- now: now
367
- }) ]
368
- }),
369
- /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
370
- align: "center",
371
- flex: "none",
372
- style: {
373
- height: avatarSize
374
- },
375
- children: /* @__PURE__ */ jsxRuntime.jsx(index.HintedMenuButton, {
376
- button: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
377
- "aria-label": `Resolve ${label}`,
378
- disabled: busy,
379
- fontSize: 1,
380
- icon: EllipsisHorizontal.EllipsisHorizontalIcon,
381
- mode: "bleed",
382
- padding: 2
383
- }),
384
- hint: "Resolve",
385
- id: `pending-effect-menu-${instanceId}-${effect._key}`,
386
- menu: /* @__PURE__ */ jsxRuntime.jsxs(ui.Menu, {
387
- children: [ registered ? /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
388
- onClick: onRunHandlers,
389
- text: "Run registered handlers"
390
- }) : null,
391
- /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
392
- onClick: () => onResolve("done"),
393
- text: "Mark done"
394
- }),
395
- /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
396
- onClick: () => onResolve("failed"),
397
- text: "Mark failed",
398
- tone: "critical"
399
- }) ]
400
- }),
401
- popover: {
402
- placement: "bottom-end",
403
- portal: !0
404
- }
405
- })
406
- }) ]
407
- });
408
- }
409
-
410
- function ActivityLog({instance: instance, definition: definition}) {
411
- 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);
412
- react.useEffect(() => {
413
- const timer = setInterval(() => setNow(/* @__PURE__ */ new Date), 6e4);
414
- return () => clearInterval(timer);
415
- }, []);
416
- const history = historyWindow({
417
- entries: entries,
418
- expanded: expanded
419
- });
420
- return entries.length === 0 && (instance.pendingEffects ?? []).length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
421
- muted: !0,
422
- size: 1,
423
- children: "No activity yet"
424
- }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
425
- gap: 3,
426
- children: [
427
- /* @__PURE__ */ jsxRuntime.jsx(PendingEffectRows, {
428
- instance: instance,
429
- now: now
430
- }), history.shown.map(h => /* @__PURE__ */ jsxRuntime.jsx(HistoryRow, {
431
- entry: h,
432
- now: now,
433
- titles: titles
434
- }, h._key)), history.overflows ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
435
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
436
- fontSize: 1,
437
- mode: "bleed",
438
- onClick: () => setExpanded(!expanded),
439
- padding: 2,
440
- text: expanded ? "Show less history" : "Show all history"
441
- })
442
- }) : null ]
443
- });
444
- }
445
-
446
- function FeedStamp({at: at, now: now}) {
447
- /* @__PURE__ */
448
- return jsxRuntime.jsxs(ui.Flex, {
449
- align: "center",
450
- flex: "none",
451
- gap: 1,
452
- children: [
453
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
454
- muted: !0,
455
- size: 1,
456
- children: "·"
457
- }),
458
- /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
459
- text: index.formatDateTime(at),
460
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
461
- muted: !0,
462
- size: 1,
463
- style: {
464
- whiteSpace: "nowrap"
465
- },
466
- children: index.formatShortAgo(at, now)
467
- })
468
- }) ]
469
- });
470
- }
471
-
472
- function HistoryRow({entry: entry, now: now, titles: titles}) {
473
- const line = historyLine(entry, titles);
474
- /* @__PURE__ */
475
- return jsxRuntime.jsxs(ui.Flex, {
476
- align: "center",
477
- gap: 2,
478
- children: [ line.actorId === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(FeedGlyph, {
479
- hint: "The workflow",
480
- icon: /* @__PURE__ */ jsxRuntime.jsx(Cog.CogIcon, {})
481
- }) : /* @__PURE__ */ jsxRuntime.jsx(index.UserAvatar, {
482
- id: line.actorId
483
- }),
484
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
485
- align: "center",
486
- flex: 1,
487
- gap: 1,
488
- style: {
489
- minWidth: 0
490
- },
491
- children: [
492
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
493
- style: {
494
- minWidth: 0
495
- },
496
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
497
- muted: !0,
498
- size: 1,
499
- textOverflow: "ellipsis",
500
- children: [ line.actorId === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
501
- children: [
502
- /* @__PURE__ */ jsxRuntime.jsx(ActorNameSpan, {
503
- id: line.actorId
504
- }), " " ]
505
- }), line.parts.map((part, index2) => typeof part == "string" ? part : /* @__PURE__ */ jsxRuntime.jsx(CodeChip, {
506
- value: part.code
507
- }, index2)) ]
508
- })
509
- }),
510
- /* @__PURE__ */ jsxRuntime.jsx(FeedStamp, {
511
- at: entry.at,
512
- now: now
513
- }) ]
514
- }) ]
515
- });
516
- }
517
-
518
- function ActorNameSpan({id: id}) {
519
- const {name: name} = index.useUserDisplay(id), {font: font} = ui.useTheme_v2();
520
- /* @__PURE__ */
521
- return jsxRuntime.jsx("span", {
522
- style: {
523
- fontWeight: font.text.weights.medium
524
- },
525
- children: name
526
- });
527
- }
528
-
529
- function FeedGlyph({hint: hint, icon: icon}) {
530
- const size = index.useAvatarSize();
531
- /* @__PURE__ */
532
- return jsxRuntime.jsx(index.HoverHint, {
533
- text: hint,
534
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
535
- align: "center",
536
- flex: "none",
537
- justify: "center",
538
- style: {
539
- height: size,
540
- width: size
541
- },
542
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
543
- muted: !0,
544
- size: 1,
545
- children: icon
546
- })
547
- })
548
- });
549
- }
550
-
551
46
  function documentRefsOf(instance) {
552
47
  const seen = /* @__PURE__ */ new Map;
553
48
  for (const ref of workflowEngine.entryDocRefs(instance.fields)) seen.has(ref.id) || seen.set(ref.id, ref);
@@ -781,7 +276,7 @@ function NotFoundNote({id: id}) {
781
276
  /* @__PURE__ */
782
277
  return jsxRuntime.jsxs(jsxRuntime.Fragment, {
783
278
  children: [ "Unable to find workflow with id “",
784
- /* @__PURE__ */ jsxRuntime.jsx(CodeChip, {
279
+ /* @__PURE__ */ jsxRuntime.jsx(index.CodeChip, {
785
280
  value: id
786
281
  }), "”." ]
787
282
  });
@@ -836,7 +331,7 @@ function InstanceDetailBody({entry: entry, framed: framed, subject: subject}) {
836
331
  /* @__PURE__ */ jsxRuntime.jsx(SectionHeading, {
837
332
  children: "History"
838
333
  }),
839
- /* @__PURE__ */ jsxRuntime.jsx(ActivityLog, {
334
+ /* @__PURE__ */ jsxRuntime.jsx(index.ActivityLog, {
840
335
  definition: definition,
841
336
  instance: instance
842
337
  }) ]
@@ -1549,7 +1044,7 @@ function taskRowsOf(args) {
1549
1044
  open: index.isOpenActivityStatus(activity.status),
1550
1045
  dates: index.datesOf(fields),
1551
1046
  dueDates: index.dueDatesOf(fields),
1552
- assigneeIds: index.assigneeUserIdsOf(fields),
1047
+ assignees: index.assigneesOf(fields),
1553
1048
  assignedToMe: identity !== void 0 && index.isActivityAssignedTo({
1554
1049
  state: fields,
1555
1050
  who: identity
@@ -1646,7 +1141,7 @@ function taskRowRender(row, entry) {
1646
1141
  return entry?.evaluation === void 0 || activityEval === void 0 ? {
1647
1142
  face: index.committedRowFace(row),
1648
1143
  assignState: row.assignControl,
1649
- assigneeIds: row.assigneeIds,
1144
+ assignees: row.assignees,
1650
1145
  dateState: row.dateControl,
1651
1146
  dueDates: row.dueDates,
1652
1147
  terminalActions: [],
@@ -2083,7 +1578,7 @@ function statusArm(row, statuses) {
2083
1578
 
2084
1579
  function assigneeArm(row, assigneeIds) {
2085
1580
  const arm = activeArm(assigneeIds);
2086
- return arm ? arm.some(id => row.assigneeIds.includes(id)) : !0;
1581
+ return arm ? row.assignees.some(item => item.type === "user" && arm.includes(item.id)) : !0;
2087
1582
  }
2088
1583
 
2089
1584
  function rowMatches(args) {
@@ -2607,6 +2102,55 @@ function FilterRowPortal({filters: filters, onChange: onChange, options: options
2607
2102
  }), slot) : null;
2608
2103
  }
2609
2104
 
2105
+ function CollapsibleBand({header: header, children: children, background: background = !1, defaultOpen: defaultOpen = !0, onToggle: onToggle, open: controlledOpen, sticky: sticky = !1, title: title}) {
2106
+ const [heldOpen, setHeldOpen] = react.useState(defaultOpen), open = controlledOpen ?? heldOpen, toggle = onToggle ?? (() => setHeldOpen(value => !value)), verb = open ? "Collapse" : "Expand";
2107
+ /* @__PURE__ */
2108
+ return jsxRuntime.jsxs(ui.Stack, {
2109
+ gap: 1,
2110
+ children: [
2111
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
2112
+ onDoubleClick: event => {
2113
+ event.target instanceof Element && event.target.closest("a, button") || toggle();
2114
+ },
2115
+ padding: 1,
2116
+ radius: 3,
2117
+ style: {
2118
+ userSelect: "none",
2119
+ ...sticky ? {
2120
+ position: "sticky",
2121
+ top: 0,
2122
+ zIndex: 1
2123
+ } : {}
2124
+ },
2125
+ tone: background ? "transparent" : "inherit",
2126
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
2127
+ align: "center",
2128
+ children: [
2129
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
2130
+ "aria-label": `${verb} ${title}`,
2131
+ fontSize: 1,
2132
+ icon: open ? ChevronDown.ChevronDownIcon : ChevronRight.ChevronRightIcon,
2133
+ mode: "bleed",
2134
+ onClick: toggle,
2135
+ padding: 2
2136
+ }), header ]
2137
+ })
2138
+ }), open ? children : null ]
2139
+ });
2140
+ }
2141
+
2142
+ function TrailingHairline() {
2143
+ /* @__PURE__ */
2144
+ return jsxRuntime.jsx(ui.Box, {
2145
+ flex: 1,
2146
+ paddingLeft: 1,
2147
+ paddingRight: 1,
2148
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.Hairline, {
2149
+ weight: .5
2150
+ })
2151
+ });
2152
+ }
2153
+
2610
2154
  function isPlainClick(event) {
2611
2155
  return !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey;
2612
2156
  }
@@ -2680,7 +2224,7 @@ function targetOf(row) {
2680
2224
  function DocumentBand({group: group, ...wiring}) {
2681
2225
  const {open: open, toggle: toggle} = useBandOpen(wiring.bands, group.key);
2682
2226
  /* @__PURE__ */
2683
- return jsxRuntime.jsx(index.CollapsibleBand, {
2227
+ return jsxRuntime.jsx(CollapsibleBand, {
2684
2228
  background: !0,
2685
2229
  onToggle: toggle,
2686
2230
  open: open,
@@ -2707,7 +2251,7 @@ function DocumentBand({group: group, ...wiring}) {
2707
2251
  function UngroupedBand({instances: instances, ...wiring}) {
2708
2252
  const {open: open, toggle: toggle} = useBandOpen(wiring.bands, UNGROUPED_BAND_KEY);
2709
2253
  /* @__PURE__ */
2710
- return jsxRuntime.jsx(index.CollapsibleBand, {
2254
+ return jsxRuntime.jsx(CollapsibleBand, {
2711
2255
  background: !0,
2712
2256
  onToggle: toggle,
2713
2257
  open: open,
@@ -2739,7 +2283,7 @@ function InstanceBands({instances: instances, ...wiring}) {
2739
2283
  function InstanceBand({bands: bands, instance: instance, onOpenInstance: onOpenInstance, onOpenTask: onOpenTask, showTerminalActions: showTerminalActions}) {
2740
2284
  const {open: open, toggle: toggle} = useBandOpen(bands, instance.instanceId), {resolvePathFromState: resolvePathFromState} = router.useRouter();
2741
2285
  /* @__PURE__ */
2742
- return jsxRuntime.jsx(index.CollapsibleBand, {
2286
+ return jsxRuntime.jsx(CollapsibleBand, {
2743
2287
  onToggle: toggle,
2744
2288
  open: open,
2745
2289
  title: instance.title,
@@ -2776,7 +2320,7 @@ function InstanceBand({bands: bands, instance: instance, onOpenInstance: onOpenI
2776
2320
  }) ]
2777
2321
  })
2778
2322
  }),
2779
- /* @__PURE__ */ jsxRuntime.jsx(index.TrailingHairline, {}) ]
2323
+ /* @__PURE__ */ jsxRuntime.jsx(TrailingHairline, {}) ]
2780
2324
  }),
2781
2325
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
2782
2326
  paddingLeft: 5,
@@ -2812,7 +2356,7 @@ function TaskActivityRow({row: row, onOpen: onOpen, showTerminalActions: showTer
2812
2356
  stale: render.stale,
2813
2357
  children: /* @__PURE__ */ jsxRuntime.jsx(index.ActivityRow, {
2814
2358
  assignState: render.assignState,
2815
- assigneeIds: render.assigneeIds,
2359
+ assignees: render.assignees,
2816
2360
  dateState: render.dateState,
2817
2361
  dueDates: render.dueDates,
2818
2362
  face: render.face,