@sanity/workflow-studio-plugin 0.26.0 → 0.28.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,22 +1,16 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
 
3
- import { Text, Dialog, Box, Stack, TextInput, Flex, Button, Menu, MenuItem, useTheme_v2, Card, Grid, Badge, MenuButton, TabPanel } from "@sanity/ui";
3
+ import { Text, Stack, Flex, Button, Box, Card, Grid, TextInput, Dialog, Badge, useTheme_v2, MenuButton, Menu, MenuItem, TabPanel } from "@sanity/ui";
4
4
 
5
5
  import { useWorkflowTelemetry } from "@sanity/workflow-react";
6
6
 
7
- import { useState, useMemo, useEffect, useRef, useSyncExternalStore, useCallback } from "react";
7
+ import { useState, useEffect, useMemo, useRef, useSyncExternalStore, useCallback } from "react";
8
8
 
9
9
  import { useRouter } from "sanity/router";
10
10
 
11
- import { TOAST_ID, describeError, useWorkflowContext, useDefinition, useWorkflowToast, useLogEventOnMount, WorkflowAbortDialogOpened, instanceTitle, WorkflowAbortDialogSubmitted, isLiveEntry, stageTitle, UserAvatar, useAvatarSize, HintedMenuButton, HoverHint, useUserDisplay, formatShortAgo, formatDateTime, isEvaluationStale, useSpaceToken, DocPreviewLink, SpinnerSlot, useWorkflowInstanceEntry, WorkflowInstanceDetailViewed, LoadingRow, InvalidDocNotice, useContainerToken, openActivityGone, InstanceSnapshotBody, ActivityDetailDialog, instanceBreadcrumb, namesNoDeployedDefinition, mappingIssueDetail, formatTimeAgo, formatShortDateTime, readDeployedDefinitions, EmptyState, CautionNote, DismissablePopover, createSubscribers, useDelayedFlag, definitionSnapshotOf, findActivity, stateByActivity, findActivityNode, rowDateControlState, rowAssignControlState, isActivityAssignedTo, assigneeUserIdsOf, dueDatesOf, datesOf, isOpenActivityStatus, committedRowFace, activityRowProps, gdrLocality, WORKFLOW_API_VERSION, openableSchemaType, formatDate, parseStoredDateValue, useUserDisplays, useProjectMembers, WorkflowTaskFiltersApplied, DocRefFace, CollapsibleBand, LinkChip, BreadcrumbTail, StageFace, TrailingHairline, StaleLock, ActivityRow, WorkflowTitleSeedDrifted, UnreadableDocsNote, ForMeEmptyState, useBadgeCapTrim, CountedLabel, MetaRow, TabSwitch, WORKFLOW_PAGE_TABS, definitionFingerprint, Hairline, LogEventOnMount, WorkflowBoardWorkflowSelected, WorkflowDefinitionDetailViewed, toolRoute, landingState, TOOL_TABS, tabSelectionNavigates, WorkflowToolOpened, toolTabState, workflowPageState, instanceState, backToTabLabel, useAssignmentIdentity, workflowState } from "./index.js";
11
+ import { isLiveEntry, AbortWorkflowDialog, isEvaluationStale, useSpaceToken, DocPreviewLink, SpinnerSlot, useWorkflowInstanceEntry, useLogEventOnMount, WorkflowInstanceDetailViewed, LoadingRow, InvalidDocNotice, useDefinition, useContainerToken, openActivityGone, InstanceSnapshotBody, ActivityLog, ActivityDetailDialog, instanceBreadcrumb, CodeChip, instanceTitle, namesNoDeployedDefinition, mappingIssueDetail, formatTimeAgo, formatShortDateTime, useWorkflowContext, readDeployedDefinitions, describeError, NoteBanner, EmptyState, HoverHint, DismissablePopover, createSubscribers, useDelayedFlag, definitionSnapshotOf, findActivity, stageTitle, stateByActivity, findActivityNode, rowDateControlState, rowAssignControlState, isRoleAssignedTo, isActivityAssignedTo, assigneesOf, dueDatesOf, datesOf, isOpenActivityStatus, committedRowFace, activityRowProps, gdrLocality, TOAST_ID, WORKFLOW_API_VERSION, useWorkflowToast, openableSchemaType, formatDate, parseStoredDateValue, useUserDisplays, useProjectMembers, WorkflowTaskFiltersApplied, DocRefFace, Hairline, LinkChip, BreadcrumbTail, StageFace, StaleLock, ActivityRow, WorkflowTitleSeedDrifted, UnreadableDocsNote, RoleAssignedNotice, ForMeEmptyState, useBadgeCapTrim, CountedLabel, MetaRow, TabSwitch, WORKFLOW_PAGE_TABS, definitionFingerprint, LogEventOnMount, WorkflowBoardWorkflowSelected, WorkflowDefinitionDetailViewed, toolRoute, landingState, TOOL_TABS, tabSelectionNavigates, WorkflowToolOpened, toolTabState, workflowPageState, instanceState, backToTabLabel, useAssignmentIdentity, workflowState } from "./index.js";
12
12
 
13
- import { ClockIcon } from "@sanity/icons/Clock";
14
-
15
- import { CogIcon } from "@sanity/icons/Cog";
16
-
17
- import { EllipsisHorizontalIcon } from "@sanity/icons/EllipsisHorizontal";
18
-
19
- import { entryDocRefs, startKindOf, isSubjectEntry, isInputSourced, latestDeployedDefinitions, terminalState, isStartableDefinition, toBareId, findOpenStageEntry } from "@sanity/workflow-engine";
13
+ import { entryDocRefs, startKindOf, isSubjectEntry, isInputSourced, errorMessage, latestDeployedDefinitions, terminalState, isStartableDefinition, toBareId, findOpenStageEntry } from "@sanity/workflow-engine";
20
14
 
21
15
  import { ArrowLeftIcon } from "@sanity/icons/ArrowLeft";
22
16
 
@@ -46,24 +40,16 @@ import { endOfDay } from "date-fns/endOfDay";
46
40
 
47
41
  import { createPortal } from "react-dom";
48
42
 
43
+ import { ChevronDownIcon } from "@sanity/icons/ChevronDown";
44
+
45
+ import { ChevronRightIcon } from "@sanity/icons/ChevronRight";
46
+
49
47
  import { isReferenceSchemaType } from "@sanity/types";
50
48
 
51
49
  import { WorkflowDiagram } from "@sanity/workflow-diagram";
52
50
 
53
- import { ChevronDownIcon } from "@sanity/icons/ChevronDown";
54
-
55
51
  import { useReducedMotion, motion, useSpring, useMotionValueEvent } from "motion/react";
56
52
 
57
- function CodeChip({value: value}) {
58
- /* @__PURE__ */
59
- return jsx("code", {
60
- style: {
61
- overflowWrap: "anywhere"
62
- },
63
- children: value
64
- });
65
- }
66
-
67
53
  function SectionHeading({children: children}) {
68
54
  /* @__PURE__ */
69
55
  return jsx(Text, {
@@ -73,146 +59,6 @@ function SectionHeading({children: children}) {
73
59
  });
74
60
  }
75
61
 
76
- function normalizeAbortReason(raw) {
77
- const trimmed = raw.trim();
78
- return trimmed === "" ? void 0 : trimmed;
79
- }
80
-
81
- function abortCommitReport(args) {
82
- return args.changed ? {
83
- outcome: "aborted",
84
- toast: abortedToast(args.title)
85
- } : {
86
- outcome: "already-settled",
87
- toast: abortAlreadySettledToast(args.title)
88
- };
89
- }
90
-
91
- function abortedToast(title) {
92
- return {
93
- id: TOAST_ID.abort,
94
- status: "info",
95
- title: `Aborted “${title}”`
96
- };
97
- }
98
-
99
- function abortAlreadySettledToast(title) {
100
- return {
101
- id: TOAST_ID.abort,
102
- status: "info",
103
- title: `“${title}” had already finished`,
104
- description: "Nothing was aborted — the workflow reached a terminal state first."
105
- };
106
- }
107
-
108
- function abortUnconfirmedToast(args) {
109
- return {
110
- id: TOAST_ID.abort,
111
- status: "warning",
112
- title: `Couldn’t confirm “${args.title}” was aborted`,
113
- description: `${describeError(args.err)} — the abort may still have committed, so check the workflow’s state before trying again.`
114
- };
115
- }
116
-
117
- function AbortWorkflowDialog({entry: entry, onClose: onClose}) {
118
- const {engine: engine} = useWorkflowContext(), definition = useDefinition(entry), toast = useWorkflowToast(), telemetry = useWorkflowTelemetry(), [typedReason, setTypedReason] = useState(""), [pending, setPending] = useState(!1), {instance: instance} = entry, title = instanceTitle(instance, definition), reason = normalizeAbortReason(typedReason), reasonInputId = `abort-workflow-reason-${instance._id}`;
119
- useLogEventOnMount(WorkflowAbortDialogOpened, {
120
- instanceId: instance._id
121
- });
122
- const confirm = async () => {
123
- if (reason === void 0) return;
124
- const log = outcome => telemetry.log(WorkflowAbortDialogSubmitted, {
125
- instanceId: instance._id,
126
- outcome: outcome
127
- });
128
- setPending(!0);
129
- try {
130
- const {changed: changed} = await engine.abortInstance({
131
- instanceId: instance._id,
132
- reason: reason
133
- }), report = abortCommitReport({
134
- title: title,
135
- changed: changed
136
- });
137
- toast.push(report.toast), log(report.outcome), onClose();
138
- } catch (err) {
139
- toast.push(abortUnconfirmedToast({
140
- title: title,
141
- err: err
142
- })), log("unconfirmed"), setPending(!1);
143
- }
144
- };
145
- /* @__PURE__ */
146
- return jsx(Dialog, {
147
- footer: /* @__PURE__ */ jsx(Box, {
148
- padding: 3,
149
- children: /* @__PURE__ */ jsxs(Flex, {
150
- gap: 2,
151
- justify: "flex-end",
152
- children: [
153
- /* @__PURE__ */ jsx(Button, {
154
- disabled: pending,
155
- fontSize: 1,
156
- mode: "bleed",
157
- onClick: onClose,
158
- padding: 2,
159
- text: "Cancel"
160
- }),
161
- /* @__PURE__ */ jsx(Button, {
162
- disabled: reason === void 0,
163
- fontSize: 1,
164
- loading: pending,
165
- onClick: () => {
166
- confirm();
167
- },
168
- padding: 2,
169
- text: "Abort workflow",
170
- tone: "critical"
171
- }) ]
172
- })
173
- }),
174
- header: "Abort workflow",
175
- id: `abort-workflow-${instance._id}`,
176
- onClose: () => {
177
- pending || onClose();
178
- },
179
- width: 0,
180
- children: /* @__PURE__ */ jsx(Box, {
181
- padding: 4,
182
- children: /* @__PURE__ */ jsxs(Stack, {
183
- gap: 5,
184
- children: [
185
- /* @__PURE__ */ jsxs(Text, {
186
- size: 1,
187
- children: [ "Abort the ",
188
- /* @__PURE__ */ jsx("strong", {
189
- children: title
190
- }), " workflow? This cannot be undone." ]
191
- }),
192
- /* @__PURE__ */ jsxs(Stack, {
193
- gap: 3,
194
- children: [
195
- /* @__PURE__ */ jsx(Text, {
196
- as: "label",
197
- htmlFor: reasonInputId,
198
- size: 1,
199
- weight: "medium",
200
- children: "Reason"
201
- }),
202
- /* @__PURE__ */ jsx(TextInput, {
203
- autoFocus: !0,
204
- fontSize: 1,
205
- id: reasonInputId,
206
- onChange: event => setTypedReason(event.currentTarget.value),
207
- placeholder: "Why is this workflow being stopped?",
208
- value: typedReason
209
- }) ]
210
- }) ]
211
- })
212
- })
213
- });
214
- }
215
-
216
62
  function AbortWorkflowSection({entry: entry}) {
217
63
  const [aborting, setAborting] = useState(!1);
218
64
  return isLiveEntry(entry) ? /* @__PURE__ */ jsxs(Stack, {
@@ -237,361 +83,6 @@ function AbortWorkflowSection({entry: entry}) {
237
83
  }) : null;
238
84
  }
239
85
 
240
- function makeTitleResolver(definition) {
241
- const activities = (definition?.stages ?? []).flatMap(s => s.activities ?? []);
242
- return {
243
- action: (activityName, actionName) => activities.find(t => t.name === activityName)?.actions?.find(a => a.name === actionName)?.title ?? actionName,
244
- activity: name => activities.find(t => t.name === name)?.title ?? name,
245
- stage: name => stageTitle(definition, name)
246
- };
247
- }
248
-
249
- function attributed(actor, ...parts) {
250
- if (actor) return {
251
- actorId: actor.id,
252
- parts: parts
253
- };
254
- const [first, ...rest] = parts;
255
- return typeof first != "string" ? {
256
- actorId: void 0,
257
- parts: parts
258
- } : {
259
- actorId: void 0,
260
- parts: [ first.charAt(0).toUpperCase() + first.slice(1), ...rest ]
261
- };
262
- }
263
-
264
- function standalone(...parts) {
265
- return {
266
- actorId: void 0,
267
- parts: parts
268
- };
269
- }
270
-
271
- const historyLiner = {
272
- stageEntered: (h, titles) => attributed(h.actor, `entered ${titles.stage(h.stage)}${h.fromStage ? ` from ${titles.stage(h.fromStage)}` : ""}`),
273
- stageExited: (h, titles) => attributed(h.actor, `left ${titles.stage(h.stage)} for ${titles.stage(h.toStage)}`),
274
- activityStatusChanged: (h, titles) => attributed(h.actor, `changed ${titles.activity(h.activity)}: ${h.from} → ${h.to}`),
275
- actionFired: (h, titles) => {
276
- const phrase = `fired ${titles.action(h.activity, h.action)} on ${titles.activity(h.activity)}`;
277
- return h.triggered ? standalone(`The workflow ${phrase}`) : attributed(h.actor, phrase);
278
- },
279
- transitionFired: (h, titles) => attributed(h.actor, `moved ${titles.stage(h.fromStage)} → ${titles.stage(h.toStage)}`),
280
- effectQueued: h => standalone("Effect ", {
281
- code: h.effect
282
- }, " queued"),
283
- effectCompleted: h => {
284
- const detail = h.detail ? ` — ${h.detail}` : "";
285
- return h.actor ? attributed(h.actor, "resolved effect ", {
286
- code: h.effect
287
- }, ` — ${h.status}${detail}`) : standalone("Effect ", {
288
- code: h.effect
289
- }, ` ${h.status}${detail}`);
290
- },
291
- effectClaimReleased: h => attributed(h.actor, "released a stale claim on effect ", {
292
- code: h.effect
293
- }, ` ${h.via === "sweep" ? "via the sweeper" : "via a drain takeover"}`),
294
- spawned: (h, titles) => attributed(h.actor, `spawned a child workflow from ${titles.activity(h.activity)}`),
295
- subworkflowAdopted: (h, titles) => standalone(`Re-adopted child workflow into ${titles.activity(h.activity)} on re-entering ${titles.stage(h.stage)}`),
296
- subworkflowResolved: (h, titles) => standalone(`Child workflow of ${titles.activity(h.activity)} ${h.status === "done" ? "completed" : "was aborted"}`),
297
- subworkflowOrphaned: h => standalone(`Orphaned child workflow reached terminal — ${h.detail}`),
298
- aborted: (h, titles) => attributed(h.actor, `aborted the workflow at ${titles.stage(h.stage)}${h.reason ? ` — ${h.reason}` : ""}`),
299
- 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 ", {
300
- code: h.opType
301
- }),
302
- fieldQueryDiscarded: h => standalone({
303
- code: JSON.stringify(h)
304
- })
305
- };
306
-
307
- function historyLine(h, titles) {
308
- return historyLiner[h._type](h, titles);
309
- }
310
-
311
- function historyWindow(args) {
312
- const {entries: entries, expanded: expanded} = args;
313
- return {
314
- overflows: entries.length > 6,
315
- shown: expanded ? entries : entries.slice(0, 6)
316
- };
317
- }
318
-
319
- function PendingEffectRows({instance: instance, now: now}) {
320
- const {drainEffectsFor: drainEffectsFor, completeEffectFor: completeEffectFor, effectHandlers: effectHandlers} = useWorkflowContext(), toast = useWorkflowToast(), [busy, setBusy] = useState(!1), pending = instance.pendingEffects ?? [];
321
- if (pending.length === 0) return null;
322
- const runRegistered = async () => {
323
- setBusy(!0);
324
- try {
325
- await drainEffectsFor(instance._id), toast.push({
326
- id: TOAST_ID.effectsDrain,
327
- status: "info",
328
- title: "Ran the registered handlers"
329
- });
330
- } catch (err) {
331
- toast.push({
332
- id: TOAST_ID.effectsDrain,
333
- status: "error",
334
- title: "Failed to run pending effects",
335
- description: describeError(err)
336
- });
337
- } finally {
338
- setBusy(!1);
339
- }
340
- }, resolve = async (effectKey, status) => {
341
- setBusy(!0);
342
- try {
343
- await completeEffectFor(instance._id, {
344
- effectKey: effectKey,
345
- status: status,
346
- detail: "Resolved in Studio"
347
- }), toast.push({
348
- id: TOAST_ID.effectResolve,
349
- status: "info",
350
- title: `Effect marked as ${status}`
351
- });
352
- } catch (err) {
353
- toast.push({
354
- id: TOAST_ID.effectResolve,
355
- status: "error",
356
- title: "Failed to resolve the effect",
357
- description: describeError(err)
358
- });
359
- } finally {
360
- setBusy(!1);
361
- }
362
- };
363
- /* @__PURE__ */
364
- return jsx(Fragment, {
365
- children: pending.map(fx => /* @__PURE__ */ jsx(PendingEffectRow, {
366
- busy: busy,
367
- effect: fx,
368
- instanceId: instance._id,
369
- now: now,
370
- onResolve: status => resolve(fx._key, status),
371
- onRunHandlers: runRegistered,
372
- registered: fx.name in effectHandlers
373
- }, fx._key))
374
- });
375
- }
376
-
377
- function PendingEffectRow({busy: busy, effect: effect, instanceId: instanceId, now: now, onResolve: onResolve, onRunHandlers: onRunHandlers, registered: registered}) {
378
- const label = effect.title ?? effect.name, avatarSize = useAvatarSize();
379
- /* @__PURE__ */
380
- return jsxs(Flex, {
381
- align: "center",
382
- gap: 2,
383
- children: [
384
- /* @__PURE__ */ jsx(FeedGlyph, {
385
- hint: "Waiting to run",
386
- icon: /* @__PURE__ */ jsx(ClockIcon, {})
387
- }),
388
- /* @__PURE__ */ jsxs(Flex, {
389
- align: "center",
390
- flex: 1,
391
- gap: 1,
392
- style: {
393
- minWidth: 0
394
- },
395
- children: [
396
- /* @__PURE__ */ jsx(Box, {
397
- style: {
398
- minWidth: 0
399
- },
400
- children: /* @__PURE__ */ jsxs(Text, {
401
- size: 1,
402
- textOverflow: "ellipsis",
403
- children: [ effect.title ?? /* @__PURE__ */ jsx(CodeChip, {
404
- value: effect.name
405
- }), registered ? " — ready to run in Studio" : " — waiting for the runtime" ]
406
- })
407
- }),
408
- /* @__PURE__ */ jsx(FeedStamp, {
409
- at: effect.queuedAt,
410
- now: now
411
- }) ]
412
- }),
413
- /* @__PURE__ */ jsx(Flex, {
414
- align: "center",
415
- flex: "none",
416
- style: {
417
- height: avatarSize
418
- },
419
- children: /* @__PURE__ */ jsx(HintedMenuButton, {
420
- button: /* @__PURE__ */ jsx(Button, {
421
- "aria-label": `Resolve ${label}`,
422
- disabled: busy,
423
- fontSize: 1,
424
- icon: EllipsisHorizontalIcon,
425
- mode: "bleed",
426
- padding: 2
427
- }),
428
- hint: "Resolve",
429
- id: `pending-effect-menu-${instanceId}-${effect._key}`,
430
- menu: /* @__PURE__ */ jsxs(Menu, {
431
- children: [ registered ? /* @__PURE__ */ jsx(MenuItem, {
432
- onClick: onRunHandlers,
433
- text: "Run registered handlers"
434
- }) : null,
435
- /* @__PURE__ */ jsx(MenuItem, {
436
- onClick: () => onResolve("done"),
437
- text: "Mark done"
438
- }),
439
- /* @__PURE__ */ jsx(MenuItem, {
440
- onClick: () => onResolve("failed"),
441
- text: "Mark failed",
442
- tone: "critical"
443
- }) ]
444
- }),
445
- popover: {
446
- placement: "bottom-end",
447
- portal: !0
448
- }
449
- })
450
- }) ]
451
- });
452
- }
453
-
454
- function ActivityLog({instance: instance, definition: definition}) {
455
- const titles = useMemo(() => makeTitleResolver(definition), [ definition ]), entries = useMemo(() => instance.history.toReversed(), [ instance.history ]), [now, setNow] = useState(() => /* @__PURE__ */ new Date), [expanded, setExpanded] = useState(!1);
456
- useEffect(() => {
457
- const timer = setInterval(() => setNow(/* @__PURE__ */ new Date), 6e4);
458
- return () => clearInterval(timer);
459
- }, []);
460
- const history = historyWindow({
461
- entries: entries,
462
- expanded: expanded
463
- });
464
- return entries.length === 0 && (instance.pendingEffects ?? []).length === 0 ? /* @__PURE__ */ jsx(Text, {
465
- muted: !0,
466
- size: 1,
467
- children: "No activity yet"
468
- }) : /* @__PURE__ */ jsxs(Stack, {
469
- gap: 3,
470
- children: [
471
- /* @__PURE__ */ jsx(PendingEffectRows, {
472
- instance: instance,
473
- now: now
474
- }), history.shown.map(h => /* @__PURE__ */ jsx(HistoryRow, {
475
- entry: h,
476
- now: now,
477
- titles: titles
478
- }, h._key)), history.overflows ? /* @__PURE__ */ jsx(Flex, {
479
- children: /* @__PURE__ */ jsx(Button, {
480
- fontSize: 1,
481
- mode: "bleed",
482
- onClick: () => setExpanded(!expanded),
483
- padding: 2,
484
- text: expanded ? "Show less history" : "Show all history"
485
- })
486
- }) : null ]
487
- });
488
- }
489
-
490
- function FeedStamp({at: at, now: now}) {
491
- /* @__PURE__ */
492
- return jsxs(Flex, {
493
- align: "center",
494
- flex: "none",
495
- gap: 1,
496
- children: [
497
- /* @__PURE__ */ jsx(Text, {
498
- muted: !0,
499
- size: 1,
500
- children: "·"
501
- }),
502
- /* @__PURE__ */ jsx(HoverHint, {
503
- text: formatDateTime(at),
504
- children: /* @__PURE__ */ jsx(Text, {
505
- muted: !0,
506
- size: 1,
507
- style: {
508
- whiteSpace: "nowrap"
509
- },
510
- children: formatShortAgo(at, now)
511
- })
512
- }) ]
513
- });
514
- }
515
-
516
- function HistoryRow({entry: entry, now: now, titles: titles}) {
517
- const line = historyLine(entry, titles);
518
- /* @__PURE__ */
519
- return jsxs(Flex, {
520
- align: "center",
521
- gap: 2,
522
- children: [ line.actorId === void 0 ? /* @__PURE__ */ jsx(FeedGlyph, {
523
- hint: "The workflow",
524
- icon: /* @__PURE__ */ jsx(CogIcon, {})
525
- }) : /* @__PURE__ */ jsx(UserAvatar, {
526
- id: line.actorId
527
- }),
528
- /* @__PURE__ */ jsxs(Flex, {
529
- align: "center",
530
- flex: 1,
531
- gap: 1,
532
- style: {
533
- minWidth: 0
534
- },
535
- children: [
536
- /* @__PURE__ */ jsx(Box, {
537
- style: {
538
- minWidth: 0
539
- },
540
- children: /* @__PURE__ */ jsxs(Text, {
541
- muted: !0,
542
- size: 1,
543
- textOverflow: "ellipsis",
544
- children: [ line.actorId === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, {
545
- children: [
546
- /* @__PURE__ */ jsx(ActorNameSpan, {
547
- id: line.actorId
548
- }), " " ]
549
- }), line.parts.map((part, index) => typeof part == "string" ? part : /* @__PURE__ */ jsx(CodeChip, {
550
- value: part.code
551
- }, index)) ]
552
- })
553
- }),
554
- /* @__PURE__ */ jsx(FeedStamp, {
555
- at: entry.at,
556
- now: now
557
- }) ]
558
- }) ]
559
- });
560
- }
561
-
562
- function ActorNameSpan({id: id}) {
563
- const {name: name} = useUserDisplay(id), {font: font} = useTheme_v2();
564
- /* @__PURE__ */
565
- return jsx("span", {
566
- style: {
567
- fontWeight: font.text.weights.medium
568
- },
569
- children: name
570
- });
571
- }
572
-
573
- function FeedGlyph({hint: hint, icon: icon}) {
574
- const size = useAvatarSize();
575
- /* @__PURE__ */
576
- return jsx(HoverHint, {
577
- text: hint,
578
- children: /* @__PURE__ */ jsx(Flex, {
579
- align: "center",
580
- flex: "none",
581
- justify: "center",
582
- style: {
583
- height: size,
584
- width: size
585
- },
586
- children: /* @__PURE__ */ jsx(Text, {
587
- muted: !0,
588
- size: 1,
589
- children: icon
590
- })
591
- })
592
- });
593
- }
594
-
595
86
  function documentRefsOf(instance) {
596
87
  const seen = /* @__PURE__ */ new Map;
597
88
  for (const ref of entryDocRefs(instance.fields)) seen.has(ref.id) || seen.set(ref.id, ref);
@@ -985,13 +476,45 @@ function deployedPhrase(definition) {
985
476
  return ago === "" ? formatShortDateTime(deployedAt) : `${formatShortDateTime(deployedAt)} (${ago})`;
986
477
  }
987
478
 
988
- const TOOL_PANEL_PADDING = 3, TOOL_HEADING_PADDING = 2;
479
+ const TOOL_PANEL_PADDING = 3, TOOL_HEADING_PADDING = 2, logged = /* @__PURE__ */ new WeakSet;
989
480
 
990
- function catalogReadError(error) {
991
- return `Could not load the deployed workflows: ${error}`;
481
+ function logReadFailure(what, error) {
482
+ if (typeof error == "object" && error !== null) {
483
+ if (logged.has(error)) return;
484
+ logged.add(error);
485
+ }
486
+ console.error(`[workflow-studio-plugin] could not load ${what}:`, error);
992
487
  }
993
488
 
994
- const lastRead = /* @__PURE__ */ new WeakMap, issuedReads = /* @__PURE__ */ new WeakMap;
489
+ function useReadFailureLog(what, error) {
490
+ useEffect(() => {
491
+ error !== void 0 && logReadFailure(what, error);
492
+ }, [ what, error ]);
493
+ }
494
+
495
+ const INSTANCE_CAP = 200, INSTANCES_READ_FAILED = "Couldn’t load workflow data. Try again later.";
496
+
497
+ function useCappedInstances(filter) {
498
+ const {engine: engine} = useWorkflowContext(), capped = useMemo(() => ({
499
+ ...filter,
500
+ limit: INSTANCE_CAP
501
+ }), [ filter ]), {instances: instances, loading: loading, unreadable: unreadable, error: error} = useWorkflowInstances({
502
+ engine: engine,
503
+ filter: capped
504
+ });
505
+ return useReadFailureLog("workflow data", error), useMemo(() => {
506
+ const rows = instances ?? [];
507
+ return {
508
+ rows: rows,
509
+ truncated: rows.length + unreadable.length === INSTANCE_CAP,
510
+ loading: loading,
511
+ unreadable: unreadable,
512
+ error: error === void 0 ? void 0 : errorMessage(error)
513
+ };
514
+ }, [ instances, loading, unreadable, error ]);
515
+ }
516
+
517
+ const CATALOG_READ_FAILED = "Couldn’t load the deployed workflows. Try again later.", lastRead = /* @__PURE__ */ new WeakMap, issuedReads = /* @__PURE__ */ new WeakMap;
995
518
 
996
519
  function useDeployedDefinitions() {
997
520
  const {engine: engine} = useWorkflowContext(), [definitions, setDefinitions] = useState(() => lastRead.get(engine)?.rows), [error, setError] = useState(void 0);
@@ -1006,7 +529,7 @@ function useDeployedDefinitions() {
1006
529
  rows: latest
1007
530
  }), cancelled || (setDefinitions(latest), setError(void 0));
1008
531
  })().catch(err => {
1009
- cancelled || setError(describeError(err));
532
+ logReadFailure("the deployed workflows", err), cancelled || setError(describeError(err));
1010
533
  }), () => {
1011
534
  cancelled = !0;
1012
535
  };
@@ -1016,46 +539,30 @@ function useDeployedDefinitions() {
1016
539
  };
1017
540
  }
1018
541
 
1019
- const INSTANCE_CAP = 200;
1020
-
1021
- function useCappedInstances(filter) {
1022
- const {engine: engine} = useWorkflowContext(), capped = useMemo(() => ({
1023
- ...filter,
1024
- limit: INSTANCE_CAP
1025
- }), [ filter ]), {instances: instances, loading: loading, unreadable: unreadable} = useWorkflowInstances({
1026
- engine: engine,
1027
- filter: capped
1028
- });
1029
- return useMemo(() => {
1030
- const rows = instances ?? [];
1031
- return {
1032
- rows: rows,
1033
- truncated: rows.length + unreadable.length === INSTANCE_CAP,
1034
- loading: loading,
1035
- unreadable: unreadable
1036
- };
1037
- }, [ instances, loading, unreadable ]);
1038
- }
1039
-
1040
542
  const INSTANCE_CAP_COUNT_NOTE = `Counts from the latest ${INSTANCE_CAP} workflows — older ones aren’t counted.`, NEWEST_INSTANCES = {
1041
543
  includeCompleted: !0
1042
544
  };
1043
545
 
1044
546
  function useToolInstances() {
1045
- const {rows: rows, truncated: truncated, loading: loading, unreadable: unreadable} = useCappedInstances(NEWEST_INSTANCES);
547
+ const {rows: rows, truncated: truncated, loading: loading, unreadable: unreadable, error: error} = useCappedInstances(NEWEST_INSTANCES);
1046
548
  return useMemo(() => ({
1047
549
  inFlight: rows.filter(instance => terminalState(instance) === "in-flight"),
1048
550
  settled: rows.filter(instance => terminalState(instance) !== "in-flight"),
1049
551
  truncated: truncated,
1050
552
  loading: loading,
1051
- unreadable: unreadable
1052
- }), [ rows, truncated, loading, unreadable ]);
553
+ unreadable: unreadable,
554
+ error: error
555
+ }), [ rows, truncated, loading, unreadable, error ]);
1053
556
  }
1054
557
 
1055
558
  function activeSummary(active) {
1056
559
  return active === 0 ? "No active instances" : pluralize("active instance", active, !0);
1057
560
  }
1058
561
 
562
+ function activeHeadline(args) {
563
+ if (args.error === void 0) return args.loading ? "counting active instances…" : activeSummary(args.active);
564
+ }
565
+
1059
566
  const CARD_MIN_WIDTH = 280, CARD_RADIUS$1 = 3, CARD_PADDING = 4, CARD_MIN_HEIGHT = 180;
1060
567
 
1061
568
  function OverviewTab({onOpenWorkflow: onOpenWorkflow}) {
@@ -1065,13 +572,9 @@ function OverviewTab({onOpenWorkflow: onOpenWorkflow}) {
1065
572
  titleOf: name => schema.get(name)?.title
1066
573
  })
1067
574
  });
1068
- if (error !== void 0) /* @__PURE__ */
1069
- return jsx(TabStatusRow, {
1070
- children: /* @__PURE__ */ jsx(Text, {
1071
- muted: !0,
1072
- size: 1,
1073
- children: catalogReadError(error)
1074
- })
575
+ if (error !== void 0) /* @__PURE__ */ return jsx(NoteBanner, {
576
+ label: CATALOG_READ_FAILED,
577
+ tone: "critical"
1075
578
  });
1076
579
  if (definitions === void 0) /* @__PURE__ */
1077
580
  return jsx(TabStatusRow, {
@@ -1098,13 +601,20 @@ function OverviewTab({onOpenWorkflow: onOpenWorkflow}) {
1098
601
  mappings: mappings,
1099
602
  inFlight: instances.inFlight,
1100
603
  issues: mappingIssues
1101
- }), active = rows.reduce((sum, row) => sum + row.activeCount, 0);
604
+ }), active = rows.reduce((sum, row) => sum + row.activeCount, 0), activity = activeHeadline({
605
+ active: active,
606
+ error: instances.error,
607
+ loading: instances.loading
608
+ });
1102
609
  /* @__PURE__ */
1103
610
  return jsxs(Stack, {
1104
611
  gap: 3,
1105
612
  paddingBottom: 4,
1106
613
  paddingTop: 3,
1107
- children: [ undeployed,
614
+ children: [ undeployed, instances.error === void 0 ? null : /* @__PURE__ */ jsx(NoteBanner, {
615
+ label: INSTANCES_READ_FAILED,
616
+ tone: "critical"
617
+ }),
1108
618
  /* @__PURE__ */ jsxs(Flex, {
1109
619
  align: "center",
1110
620
  gap: 2,
@@ -1114,11 +624,10 @@ function OverviewTab({onOpenWorkflow: onOpenWorkflow}) {
1114
624
  size: 1,
1115
625
  weight: "semibold",
1116
626
  children: pluralize("workflow", rows.length, !0)
1117
- }),
1118
- /* @__PURE__ */ jsx(Text, {
627
+ }), activity === void 0 ? null : /* @__PURE__ */ jsx(Text, {
1119
628
  muted: !0,
1120
629
  size: 1,
1121
- children: instances.loading ? "counting active instances…" : activeSummary(active)
630
+ children: activity
1122
631
  }) ]
1123
632
  }),
1124
633
  /* @__PURE__ */ jsx(Grid, {
@@ -1128,7 +637,7 @@ function OverviewTab({onOpenWorkflow: onOpenWorkflow}) {
1128
637
  gridTemplateColumns: `repeat(auto-fill, minmax(${CARD_MIN_WIDTH}px, 1fr))`
1129
638
  },
1130
639
  children: rows.map(row => /* @__PURE__ */ jsx(WorkflowCard, {
1131
- countPending: instances.loading,
640
+ countKnown: instances.error === void 0 && !instances.loading,
1132
641
  onOpen: () => onOpenWorkflow(row.name),
1133
642
  row: row
1134
643
  }, row.name))
@@ -1145,13 +654,13 @@ function UndeployedWorkflows({notices: notices}) {
1145
654
  /* @__PURE__ */ jsx(Stack, {
1146
655
  gap: 2,
1147
656
  paddingBottom: 1,
1148
- children: notices.map(notice => /* @__PURE__ */ jsx(CautionNote, {
657
+ children: notices.map(notice => /* @__PURE__ */ jsx(NoteBanner, {
1149
658
  label: notice
1150
659
  }, notice))
1151
660
  });
1152
661
  }
1153
662
 
1154
- function WorkflowCard({countPending: countPending, onOpen: onOpen, row: row}) {
663
+ function WorkflowCard({countKnown: countKnown, onOpen: onOpen, row: row}) {
1155
664
  const flagged = row.brokenBindings.length > 0, card = /* @__PURE__ */ jsx(Card, {
1156
665
  as: "button",
1157
666
  onClick: onOpen,
@@ -1186,11 +695,11 @@ function WorkflowCard({countPending: countPending, onOpen: onOpen, row: row}) {
1186
695
  size: 1,
1187
696
  weight: "semibold",
1188
697
  children: row.title
1189
- }), countPending ? null : /* @__PURE__ */ jsx(Text, {
698
+ }), countKnown ? /* @__PURE__ */ jsx(Text, {
1190
699
  muted: !0,
1191
700
  size: 1,
1192
701
  children: activeSummary(row.activeCount)
1193
- }) ]
702
+ }) : null ]
1194
703
  }) ]
1195
704
  })
1196
705
  });
@@ -1244,7 +753,7 @@ function DefinitionPickerPanel({onPick: onPick}) {
1244
753
  const {definitions: definitions, error: error} = useStartableDefinitions(), [query, setQuery] = useState(""), visible = useMemo(() => {
1245
754
  const q = query.trim().toLowerCase();
1246
755
  return definitions ? q ? definitions.filter(d => `${d.title} ${d.name}`.toLowerCase().includes(q)) : definitions : [];
1247
- }, [ definitions, query ]);
756
+ }, [ definitions, query ]), settled = error === void 0 ? definitions : void 0;
1248
757
  /* @__PURE__ */
1249
758
  return jsx(Box, {
1250
759
  padding: 2,
@@ -1261,13 +770,14 @@ function DefinitionPickerPanel({onPick: onPick}) {
1261
770
  onChange: event => setQuery(event.currentTarget.value),
1262
771
  placeholder: "Filter workflows…",
1263
772
  value: query
1264
- }), error ? /* @__PURE__ */ jsx(MutedNote, {
1265
- text: catalogReadError(error)
773
+ }), error ? /* @__PURE__ */ jsx(NoteBanner, {
774
+ label: CATALOG_READ_FAILED,
775
+ tone: "critical"
1266
776
  }) : null, !definitions && !error ? /* @__PURE__ */ jsx(LoadingRow, {
1267
777
  label: "Loading workflows…"
1268
- }) : null, definitions && definitions.length === 0 ? /* @__PURE__ */ jsx(MutedNote, {
778
+ }) : null, settled?.length === 0 ? /* @__PURE__ */ jsx(MutedNote, {
1269
779
  text: "No startable workflows are deployed"
1270
- }) : null, definitions && definitions.length > 0 && visible.length === 0 ? /* @__PURE__ */ jsx(MutedNote, {
780
+ }) : null, settled !== void 0 && settled.length > 0 && visible.length === 0 ? /* @__PURE__ */ jsx(MutedNote, {
1271
781
  text: "No workflows match"
1272
782
  }) : null,
1273
783
  /* @__PURE__ */ jsx(Stack, {
@@ -1593,11 +1103,15 @@ function taskRowsOf(args) {
1593
1103
  open: isOpenActivityStatus(activity.status),
1594
1104
  dates: datesOf(fields),
1595
1105
  dueDates: dueDatesOf(fields),
1596
- assigneeIds: assigneeUserIdsOf(fields),
1106
+ assignees: assigneesOf(fields),
1597
1107
  assignedToMe: identity !== void 0 && isActivityAssignedTo({
1598
1108
  state: fields,
1599
1109
  who: identity
1600
1110
  }),
1111
+ roleMatched: identity !== void 0 && isRoleAssignedTo({
1112
+ state: fields,
1113
+ who: identity
1114
+ }),
1601
1115
  assignControl: rowAssignControlState(controlArgs),
1602
1116
  dateControl: rowDateControlState(controlArgs)
1603
1117
  };
@@ -1680,6 +1194,11 @@ function onlyMyTasks(groups) {
1680
1194
  });
1681
1195
  }
1682
1196
 
1197
+ function hasRoleMatchedTasks(groups) {
1198
+ const bandHasOne = band => band.rows.some(row => row.roleMatched);
1199
+ return groups.withDocuments.some(group => group.instances.some(bandHasOne)) || groups.withoutDocuments.some(bandHasOne);
1200
+ }
1201
+
1683
1202
  function rowActivityEval(entry, row) {
1684
1203
  const evaluation = entry?.evaluation;
1685
1204
  if (!(evaluation === void 0 || evaluation.currentStage.stage.name !== row.stage)) return evaluation.currentStage.activities.find(a => a.activity.name === row.activityName);
@@ -1690,7 +1209,7 @@ function taskRowRender(row, entry) {
1690
1209
  return entry?.evaluation === void 0 || activityEval === void 0 ? {
1691
1210
  face: committedRowFace(row),
1692
1211
  assignState: row.assignControl,
1693
- assigneeIds: row.assigneeIds,
1212
+ assignees: row.assignees,
1694
1213
  dateState: row.dateControl,
1695
1214
  dueDates: row.dueDates,
1696
1215
  terminalActions: [],
@@ -1823,7 +1342,7 @@ function OrphanedWorkflowsNote({orphans: orphans}) {
1823
1342
  /* @__PURE__ */
1824
1343
  return jsxs(Fragment, {
1825
1344
  children: [
1826
- /* @__PURE__ */ jsx(CautionNote, {
1345
+ /* @__PURE__ */ jsx(NoteBanner, {
1827
1346
  action: /* @__PURE__ */ jsx(Button, {
1828
1347
  fontSize: 1,
1829
1348
  mode: "ghost",
@@ -2127,7 +1646,7 @@ function statusArm(row, statuses) {
2127
1646
 
2128
1647
  function assigneeArm(row, assigneeIds) {
2129
1648
  const arm = activeArm(assigneeIds);
2130
- return arm ? arm.some(id => row.assigneeIds.includes(id)) : !0;
1649
+ return arm ? row.assignees.some(item => item.type === "user" && arm.includes(item.id)) : !0;
2131
1650
  }
2132
1651
 
2133
1652
  function rowMatches(args) {
@@ -2651,6 +2170,55 @@ function FilterRowPortal({filters: filters, onChange: onChange, options: options
2651
2170
  }), slot) : null;
2652
2171
  }
2653
2172
 
2173
+ function CollapsibleBand({header: header, children: children, background: background = !1, defaultOpen: defaultOpen = !0, onToggle: onToggle, open: controlledOpen, sticky: sticky = !1, title: title}) {
2174
+ const [heldOpen, setHeldOpen] = useState(defaultOpen), open = controlledOpen ?? heldOpen, toggle = onToggle ?? (() => setHeldOpen(value => !value)), verb = open ? "Collapse" : "Expand";
2175
+ /* @__PURE__ */
2176
+ return jsxs(Stack, {
2177
+ gap: 1,
2178
+ children: [
2179
+ /* @__PURE__ */ jsx(Card, {
2180
+ onDoubleClick: event => {
2181
+ event.target instanceof Element && event.target.closest("a, button") || toggle();
2182
+ },
2183
+ padding: 1,
2184
+ radius: 3,
2185
+ style: {
2186
+ userSelect: "none",
2187
+ ...sticky ? {
2188
+ position: "sticky",
2189
+ top: 0,
2190
+ zIndex: 1
2191
+ } : {}
2192
+ },
2193
+ tone: background ? "transparent" : "inherit",
2194
+ children: /* @__PURE__ */ jsxs(Flex, {
2195
+ align: "center",
2196
+ children: [
2197
+ /* @__PURE__ */ jsx(Button, {
2198
+ "aria-label": `${verb} ${title}`,
2199
+ fontSize: 1,
2200
+ icon: open ? ChevronDownIcon : ChevronRightIcon,
2201
+ mode: "bleed",
2202
+ onClick: toggle,
2203
+ padding: 2
2204
+ }), header ]
2205
+ })
2206
+ }), open ? children : null ]
2207
+ });
2208
+ }
2209
+
2210
+ function TrailingHairline() {
2211
+ /* @__PURE__ */
2212
+ return jsx(Box, {
2213
+ flex: 1,
2214
+ paddingLeft: 1,
2215
+ paddingRight: 1,
2216
+ children: /* @__PURE__ */ jsx(Hairline, {
2217
+ weight: .5
2218
+ })
2219
+ });
2220
+ }
2221
+
2654
2222
  function isPlainClick(event) {
2655
2223
  return !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey;
2656
2224
  }
@@ -2856,7 +2424,7 @@ function TaskActivityRow({row: row, onOpen: onOpen, showTerminalActions: showTer
2856
2424
  stale: render.stale,
2857
2425
  children: /* @__PURE__ */ jsx(ActivityRow, {
2858
2426
  assignState: render.assignState,
2859
- assigneeIds: render.assigneeIds,
2427
+ assignees: render.assignees,
2860
2428
  dateState: render.dateState,
2861
2429
  dueDates: render.dueDates,
2862
2430
  face: render.face,
@@ -3110,7 +2678,7 @@ function useOrphanedWorkflows(groups) {
3110
2678
  }), [ groups, contentResource, missing ]);
3111
2679
  }
3112
2680
 
3113
- function TasksTab({filterSlot: filterSlot, instances: instances, loading: loading, identity: identity, onOpenInstance: onOpenInstance, onOpenTask: onOpenTask, tab: tab, truncated: truncated, unreadable: unreadable}) {
2681
+ function TasksTab({error: error, filterSlot: filterSlot, instances: instances, loading: loading, identity: identity, onOpenInstance: onOpenInstance, onOpenTask: onOpenTask, tab: tab, truncated: truncated, unreadable: unreadable}) {
3114
2682
  const mineOnly = tab === "for-me", {binding: binding} = useWorkflowContext(), bands = useBandOpenState({
3115
2683
  segment: TASK_BAND_SEGMENTS[tab],
3116
2684
  scope: binding.engineResource.id
@@ -3128,6 +2696,10 @@ function TasksTab({filterSlot: filterSlot, instances: instances, loading: loadin
3128
2696
  }), cut = mineOnly ? onlyMyTasks(filtered) : filtered;
3129
2697
  return orderGroupsByPreviewTitle(cut, titles.titles);
3130
2698
  }, [ listed, filters, mineOnly, titles ]);
2699
+ if (error !== void 0) /* @__PURE__ */ return jsx(NoteBanner, {
2700
+ label: INSTANCES_READ_FAILED,
2701
+ tone: "critical"
2702
+ });
3131
2703
  if (loading || !revealed) /* @__PURE__ */
3132
2704
  return jsx(TabStatusRow, {
3133
2705
  children: /* @__PURE__ */ jsx(LoadingRow, {
@@ -3167,7 +2739,7 @@ function TasksTab({filterSlot: filterSlot, instances: instances, loading: loadin
3167
2739
  }),
3168
2740
  /* @__PURE__ */ jsx(OrphanedWorkflowsNote, {
3169
2741
  orphans: orphans
3170
- }), body, truncated ? /* @__PURE__ */ jsx(MutedNote, {
2742
+ }), mineOnly && hasRoleMatchedTasks(visible) ? /* @__PURE__ */ jsx(RoleAssignedNotice, {}) : null, body, truncated ? /* @__PURE__ */ jsx(MutedNote, {
3171
2743
  text: `Tasks from the latest ${INSTANCE_CAP} workflows — older ones aren’t listed.`
3172
2744
  }) : null ]
3173
2745
  }) ]
@@ -3545,6 +3117,16 @@ function useBoardInstances(definitionName) {
3545
3117
  return useCappedInstances(filter);
3546
3118
  }
3547
3119
 
3120
+ function activeCountFace(args) {
3121
+ return args.error !== void 0 || args.loading ? {
3122
+ count: void 0,
3123
+ note: void 0
3124
+ } : {
3125
+ count: args.count,
3126
+ note: args.truncated ? INSTANCE_CAP_COUNT_NOTE : void 0
3127
+ };
3128
+ }
3129
+
3548
3130
  function DefinitionPanel({definition: definition}) {
3549
3131
  /* @__PURE__ */
3550
3132
  return jsx(Box, {
@@ -3607,6 +3189,23 @@ function SetupIssues({bindings: bindings}) {
3607
3189
  });
3608
3190
  }
3609
3191
 
3192
+ function ActiveCount({face: face}) {
3193
+ /* @__PURE__ */
3194
+ return jsxs(Stack, {
3195
+ gap: 2,
3196
+ children: [
3197
+ /* @__PURE__ */ jsx(Text, {
3198
+ muted: face.count === void 0,
3199
+ size: 1,
3200
+ children: face.count ?? "—"
3201
+ }), face.note === void 0 ? null : /* @__PURE__ */ jsx(Text, {
3202
+ muted: !0,
3203
+ size: 1,
3204
+ children: face.note
3205
+ }) ]
3206
+ });
3207
+ }
3208
+
3610
3209
  function DefinitionFacts({definition: definition}) {
3611
3210
  const {mappingIssues: mappingIssues, mappings: mappings} = useWorkflowContext(), schema = useSchema(), instances = useToolInstances(), snapshotWidth = useContainerToken(2), [row] = definitionCatalog({
3612
3211
  definitions: [ definition ],
@@ -3624,6 +3223,12 @@ function DefinitionFacts({definition: definition}) {
3624
3223
  children: /* @__PURE__ */ jsx(SetupIssues, {
3625
3224
  bindings: row.brokenBindings
3626
3225
  })
3226
+ }), instances.error === void 0 ? null : /* @__PURE__ */ jsx(Box, {
3227
+ flex: "none",
3228
+ children: /* @__PURE__ */ jsx(NoteBanner, {
3229
+ label: INSTANCES_READ_FAILED,
3230
+ tone: "critical"
3231
+ })
3627
3232
  }),
3628
3233
  /* @__PURE__ */ jsx(Box, {
3629
3234
  flex: "none",
@@ -3642,21 +3247,13 @@ function DefinitionFacts({definition: definition}) {
3642
3247
  }),
3643
3248
  /* @__PURE__ */ jsx(MetaRow, {
3644
3249
  label: "Active instances",
3645
- children: instances.loading ? /* @__PURE__ */ jsx(Text, {
3646
- muted: !0,
3647
- size: 1,
3648
- children: "—"
3649
- }) : /* @__PURE__ */ jsxs(Stack, {
3650
- gap: 2,
3651
- children: [
3652
- /* @__PURE__ */ jsx(Text, {
3653
- size: 1,
3654
- children: row?.activeCount ?? 0
3655
- }), instances.truncated ? /* @__PURE__ */ jsx(Text, {
3656
- muted: !0,
3657
- size: 1,
3658
- children: INSTANCE_CAP_COUNT_NOTE
3659
- }) : null ]
3250
+ children: /* @__PURE__ */ jsx(ActiveCount, {
3251
+ face: activeCountFace({
3252
+ count: row?.activeCount ?? 0,
3253
+ error: instances.error,
3254
+ loading: instances.loading,
3255
+ truncated: instances.truncated
3256
+ })
3660
3257
  })
3661
3258
  }),
3662
3259
  /* @__PURE__ */ jsx(MetaRow, {
@@ -4193,11 +3790,11 @@ function usePageSelection(page) {
4193
3790
  }
4194
3791
 
4195
3792
  function WorkflowPage({onOpenWorkflow: onOpenWorkflow, onSelectPage: onSelectPage, page: page, workflowName: workflowName}) {
4196
- const {definitions: definitions, error: error} = useDeployedDefinitions(), selection = resolveWorkflowSelection({
3793
+ const {definitions: definitions, error: catalogError} = useDeployedDefinitions(), selection = resolveWorkflowSelection({
4197
3794
  definitions: definitions,
4198
3795
  routeName: workflowName
4199
3796
  }), selected = resolvedDefinition(selection), view = workflowPageView({
4200
- error: error,
3797
+ error: catalogError,
4201
3798
  page: page,
4202
3799
  selection: selection
4203
3800
  }), instances = useBoardInstances(view.streamName), {board: board, ready: ready, titles: titles} = useBoard({
@@ -4209,9 +3806,9 @@ function WorkflowPage({onOpenWorkflow: onOpenWorkflow, onSelectPage: onSelectPag
4209
3806
  })), revealed = useRevealGate({
4210
3807
  loading: instances.loading,
4211
3808
  ready: ready
4212
- }), waiting = useDelayedFlag(selection.kind === "loading" && error === void 0, WAIT_CUE_DELAY_MS);
3809
+ }), waiting = useDelayedFlag(selection.kind === "loading" && catalogError === void 0, WAIT_CUE_DELAY_MS);
4213
3810
  if (selection.kind === "loading") /* @__PURE__ */ return jsx(UnresolvedPage, {
4214
- error: error,
3811
+ error: catalogError,
4215
3812
  waiting: waiting
4216
3813
  });
4217
3814
  const wiring = {
@@ -4270,7 +3867,8 @@ function WorkflowPage({onOpenWorkflow: onOpenWorkflow, onSelectPage: onSelectPag
4270
3867
  /* @__PURE__ */ jsx(PageBody, {
4271
3868
  board: board,
4272
3869
  catalogEmpty: (definitions ?? []).length === 0,
4273
- error: error,
3870
+ catalogError: catalogError,
3871
+ instancesError: instances.error,
4274
3872
  loading: !revealed,
4275
3873
  page: page,
4276
3874
  selection: selection,
@@ -4308,8 +3906,8 @@ function UnresolvedPage({error: error, waiting: waiting}) {
4308
3906
  overflow: "hidden",
4309
3907
  children: error === void 0 ? /* @__PURE__ */ jsx(WaitingFace, {
4310
3908
  waiting: waiting
4311
- }) : /* @__PURE__ */ jsx(CatalogErrorFace, {
4312
- error: error
3909
+ }) : /* @__PURE__ */ jsx(ReadErrorFace, {
3910
+ text: CATALOG_READ_FAILED
4313
3911
  })
4314
3912
  });
4315
3913
  }
@@ -4325,18 +3923,24 @@ function WaitingFace({waiting: waiting}) {
4325
3923
  });
4326
3924
  }
4327
3925
 
4328
- function CatalogErrorFace({error: error}) {
3926
+ const BANNER_LANE_INSET = 2;
3927
+
3928
+ function ReadErrorFace({text: text}) {
4329
3929
  /* @__PURE__ */
4330
3930
  return jsx(StatusBox, {
4331
- children: /* @__PURE__ */ jsx(MutedNote, {
4332
- text: catalogReadError(error)
3931
+ children: /* @__PURE__ */ jsx(Box, {
3932
+ paddingTop: BANNER_LANE_INSET,
3933
+ children: /* @__PURE__ */ jsx(NoteBanner, {
3934
+ label: text,
3935
+ tone: "critical"
3936
+ })
4333
3937
  })
4334
3938
  });
4335
3939
  }
4336
3940
 
4337
- function PageBody({board: board, catalogEmpty: catalogEmpty, error: error, loading: loading, page: page, selection: selection, wiring: wiring}) {
4338
- if (error !== void 0) /* @__PURE__ */ return jsx(CatalogErrorFace, {
4339
- error: error
3941
+ function PageBody({board: board, catalogEmpty: catalogEmpty, catalogError: catalogError, instancesError: instancesError, loading: loading, page: page, selection: selection, wiring: wiring}) {
3942
+ if (catalogError !== void 0) /* @__PURE__ */ return jsx(ReadErrorFace, {
3943
+ text: CATALOG_READ_FAILED
4340
3944
  });
4341
3945
  if (selection.kind === "unknown") {
4342
3946
  const face = unknownWorkflowFace({
@@ -4354,6 +3958,8 @@ function PageBody({board: board, catalogEmpty: catalogEmpty, error: error, loadi
4354
3958
  }
4355
3959
  return page === "definition" ? /* @__PURE__ */ jsx(DefinitionPanel, {
4356
3960
  definition: selection.definition
3961
+ }) : instancesError !== void 0 ? /* @__PURE__ */ jsx(ReadErrorFace, {
3962
+ text: INSTANCES_READ_FAILED
4357
3963
  }) : loading || board === void 0 ? /* @__PURE__ */ jsx(StatusBox, {
4358
3964
  children: /* @__PURE__ */ jsx(Box, {
4359
3965
  paddingTop: LANE_PADDING,
@@ -4520,6 +4126,7 @@ function HomePanel({filterSlot: filterSlot, identity: identity, instances: insta
4520
4126
  return tab === "overview" ? /* @__PURE__ */ jsx(OverviewTab, {
4521
4127
  onOpenWorkflow: onOpenWorkflow
4522
4128
  }) : /* @__PURE__ */ jsx(TasksTab, {
4129
+ error: instances.error,
4523
4130
  filterSlot: filterSlot,
4524
4131
  identity: identity,
4525
4132
  instances: [ ...instances.inFlight, ...instances.settled ],