@sanity/workflow-studio-plugin 0.23.0 → 0.24.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"), workflowDiagram = require("@sanity/workflow-diagram"), sanity = require("sanity"), workflowEngine = require("@sanity/workflow-engine"), InfoOutline = require("@sanity/icons/InfoOutline"), pluralize = require("pluralize-esm"), workflowStudio = require("@sanity/workflow-studio"), ArrowLeft = require("@sanity/icons/ArrowLeft"), Clock = require("@sanity/icons/Clock"), Cog = require("@sanity/icons/Cog"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal"), 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"), Calendar = require("@sanity/icons/Calendar");
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"), Search = require("@sanity/icons/Search"), workflowEngine = require("@sanity/workflow-engine"), ChevronDown = require("@sanity/icons/ChevronDown"), Inline = require("@sanity/icons/Inline"), StackCompact = require("@sanity/icons/StackCompact"), reactDom = require("react-dom"), types = require("@sanity/types"), sanity = require("sanity"), workflowStudio = require("@sanity/workflow-studio"), WarningOutline = require("@sanity/icons/WarningOutline"), workflowDiagram = require("@sanity/workflow-diagram"), ArrowLeft = require("@sanity/icons/ArrowLeft"), InfoOutline = require("@sanity/icons/InfoOutline"), Transfer = require("@sanity/icons/Transfer"), pluralize = require("pluralize-esm"), Clock = require("@sanity/icons/Clock"), Cog = require("@sanity/icons/Cog"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal"), Add = require("@sanity/icons/Add"), 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"), Calendar = require("@sanity/icons/Calendar");
4
4
 
5
5
  function _interopDefaultCompat(e) {
6
6
  return e && typeof e == "object" && "default" in e ? e : {
@@ -22,25 +22,1235 @@ function MutedNote({text: text}) {
22
22
  });
23
23
  }
24
24
 
25
+ function TabStatusRow({children: children}) {
26
+ /* @__PURE__ */
27
+ return jsxRuntime.jsx(ui.Box, {
28
+ paddingLeft: 2,
29
+ paddingY: 3,
30
+ children: children
31
+ });
32
+ }
33
+
34
+ const TOOL_PANEL_PADDING = 3, UNREADABLE = {
35
+ readable: !1,
36
+ raw: void 0
37
+ };
38
+
39
+ function defaultStorage() {
40
+ try {
41
+ return globalThis.localStorage;
42
+ } catch {
43
+ return;
44
+ }
45
+ }
46
+
47
+ function readItem(storage, key) {
48
+ if (!storage) return UNREADABLE;
49
+ try {
50
+ const raw = storage.getItem(key);
51
+ return {
52
+ readable: !0,
53
+ raw: raw === null ? void 0 : raw
54
+ };
55
+ } catch {
56
+ return UNREADABLE;
57
+ }
58
+ }
59
+
60
+ function writeItem(args) {
61
+ if (!args.storage) return !1;
62
+ try {
63
+ return args.storage.setItem(args.key, args.value), !0;
64
+ } catch {
65
+ return !1;
66
+ }
67
+ }
68
+
69
+ const BAND_DEFAULT_OPEN = !0, UNGROUPED_BAND_KEY = "section:no-document", PAYLOAD_VERSION = 2, MAX_ENTRIES = 200;
70
+
71
+ function bandStateStorageKey(scope) {
72
+ return `sanity.workflows.tool.bands:${scope}`;
73
+ }
74
+
75
+ function isRecord(value) {
76
+ return typeof value == "object" && value !== null && !Array.isArray(value);
77
+ }
78
+
79
+ function bandEntryOf(value) {
80
+ if (!isRecord(value)) return;
81
+ const {open: open, touched: touched} = value;
82
+ if (!(typeof open != "boolean" || typeof touched != "number") && Number.isFinite(touched)) return {
83
+ open: open,
84
+ touched: touched
85
+ };
86
+ }
87
+
88
+ function parsePayload(raw) {
89
+ try {
90
+ const parsed = JSON.parse(raw);
91
+ if (!isRecord(parsed) || parsed.version !== PAYLOAD_VERSION || !isRecord(parsed.bands)) return;
92
+ const entries = [];
93
+ for (const [key, value] of Object.entries(parsed.bands)) {
94
+ const entry = bandEntryOf(value);
95
+ if (entry === void 0) return;
96
+ entries.push([ key, entry ]);
97
+ }
98
+ return Object.fromEntries(entries);
99
+ } catch {
100
+ return;
101
+ }
102
+ }
103
+
104
+ function readStored(storage, storageKey) {
105
+ const {readable: readable, raw: raw} = readItem(storage, storageKey);
106
+ if (!readable) return {
107
+ entries: void 0,
108
+ corrupt: !1
109
+ };
110
+ if (raw === void 0) return {
111
+ entries: {},
112
+ corrupt: !1
113
+ };
114
+ const entries = parsePayload(raw);
115
+ return {
116
+ entries: entries,
117
+ corrupt: entries === void 0
118
+ };
119
+ }
120
+
121
+ function writeStored(args) {
122
+ return writeItem({
123
+ storage: args.storage,
124
+ key: args.storageKey,
125
+ value: JSON.stringify({
126
+ version: PAYLOAD_VERSION,
127
+ bands: args.entries
128
+ })
129
+ });
130
+ }
131
+
132
+ function baseEntries(args) {
133
+ return args.stored === void 0 ? args.held : args.unpersisted ? mergeByTouched(args.stored, args.held) : args.stored;
134
+ }
135
+
136
+ function mergeByTouched(stored, held) {
137
+ const merged = {
138
+ ...stored
139
+ };
140
+ for (const [key, entry] of Object.entries(held)) {
141
+ const persisted = merged[key];
142
+ (persisted === void 0 || entry.touched >= persisted.touched) && (merged[key] = entry);
143
+ }
144
+ return merged;
145
+ }
146
+
147
+ function prune(entries) {
148
+ const held = Object.entries(entries);
149
+ return held.length <= MAX_ENTRIES ? entries : Object.fromEntries(held.sort(([, a], [, b]) => b.touched - a.touched).slice(0, MAX_ENTRIES));
150
+ }
151
+
152
+ function togglesOf(entries) {
153
+ return new Map(Object.entries(entries).map(([key, entry]) => [ key, entry.open ]));
154
+ }
155
+
156
+ function createBandStateStore(args) {
157
+ const storage = args.storage ?? defaultStorage(), initial = readStored(storage, args.storageKey);
158
+ initial.corrupt && writeStored({
159
+ storage: storage,
160
+ storageKey: args.storageKey,
161
+ entries: {}
162
+ });
163
+ let held = initial.entries ?? {}, toggles = togglesOf(held);
164
+ const subscribers = index.createSubscribers();
165
+ let unpersisted = !1;
166
+ return {
167
+ subscribe: subscribers.subscribe,
168
+ read: () => toggles,
169
+ setOpen(key, open) {
170
+ const stored = readStored(storage, args.storageKey).entries, base = baseEntries({
171
+ stored: stored,
172
+ held: held,
173
+ unpersisted: unpersisted
174
+ });
175
+ held = prune({
176
+ ...base,
177
+ [key]: {
178
+ open: open,
179
+ touched: Date.now()
180
+ }
181
+ }), toggles = togglesOf(held), unpersisted = !writeStored({
182
+ storage: storage,
183
+ storageKey: args.storageKey,
184
+ entries: held
185
+ }), subscribers.notify();
186
+ }
187
+ };
188
+ }
189
+
190
+ function useBandOpenState(args) {
191
+ const {segment: segment, scope: scope} = args, store = react.useMemo(() => createBandStateStore({
192
+ storageKey: bandStateStorageKey(scope)
193
+ }), [ scope ]);
194
+ return react.useMemo(() => ({
195
+ segment: segment,
196
+ store: store
197
+ }), [ segment, store ]);
198
+ }
199
+
200
+ function openOf(store, qualified) {
201
+ return store.read().get(qualified) ?? BAND_DEFAULT_OPEN;
202
+ }
203
+
204
+ function useBandOpen(bands, key) {
205
+ const {segment: segment, store: store} = bands, qualified = `${segment}/${key}`;
206
+ return {
207
+ open: react.useSyncExternalStore(store.subscribe, react.useCallback(() => openOf(store, qualified), [ store, qualified ])),
208
+ toggle: () => store.setOpen(qualified, !openOf(store, qualified))
209
+ };
210
+ }
211
+
212
+ const lastRead = /* @__PURE__ */ new WeakMap, issuedReads = /* @__PURE__ */ new WeakMap;
213
+
214
+ function useDeployedDefinitions() {
215
+ const {engine: engine} = index.useWorkflowContext(), [definitions, setDefinitions] = react.useState(() => lastRead.get(engine)?.rows), [error, setError] = react.useState(void 0);
216
+ return react.useEffect(() => {
217
+ let cancelled = !1;
218
+ setDefinitions(lastRead.get(engine)?.rows), setError(void 0);
219
+ const seq = (issuedReads.get(engine) ?? 0) + 1;
220
+ return issuedReads.set(engine, seq), (async () => {
221
+ const rows = await index.readDeployedDefinitions(engine), latest = workflowEngine.latestDeployedDefinitions(rows), held = lastRead.get(engine);
222
+ (held === void 0 || seq > held.seq) && lastRead.set(engine, {
223
+ seq: seq,
224
+ rows: latest
225
+ }), cancelled || (setDefinitions(latest), setError(void 0));
226
+ })().catch(err => {
227
+ cancelled || setError(index.describeError(err));
228
+ }), () => {
229
+ cancelled = !0;
230
+ };
231
+ }, [ engine ]), {
232
+ definitions: definitions,
233
+ error: error
234
+ };
235
+ }
236
+
237
+ const REVEAL_MAX_WAIT_MS = 2e3;
238
+
239
+ function useRevealGate(args) {
240
+ const {loading: loading, ready: ready} = args, settled = !loading && ready, expired = index.useDelayedFlag(!loading && !ready, REVEAL_MAX_WAIT_MS), [revealed, setRevealed] = react.useState(!1);
241
+ return react.useEffect(() => {
242
+ !revealed && (settled || expired) && setRevealed(!0);
243
+ }, [ expired, revealed, settled ]), revealed || settled || expired;
244
+ }
245
+
246
+ function documentRefsOf(instance) {
247
+ const seen = /* @__PURE__ */ new Map;
248
+ for (const ref of workflowEngine.entryDocRefs(instance.fields)) seen.has(ref.id) || seen.set(ref.id, ref);
249
+ return [ ...seen.values() ];
250
+ }
251
+
252
+ function byTitleThenName(a, b) {
253
+ return a.title.localeCompare(b.title) || a.name.localeCompare(b.name);
254
+ }
255
+
256
+ function byResolvedTitle(titleOf, keyOf) {
257
+ return (a, b) => {
258
+ const aTitle = titleOf(a), bTitle = titleOf(b);
259
+ return aTitle !== void 0 && bTitle !== void 0 ? aTitle.localeCompare(bTitle) || keyOf(a).localeCompare(keyOf(b)) : aTitle !== void 0 ? -1 : bTitle !== void 0 ? 1 : keyOf(a).localeCompare(keyOf(b));
260
+ };
261
+ }
262
+
263
+ const BOARD_EMPTY_STAGE_NOTE = "No items";
264
+
265
+ function faceOf(instance) {
266
+ const document = documentRefsOf(instance)[0];
267
+ return document ? {
268
+ kind: "document",
269
+ document: document
270
+ } : {
271
+ kind: "instance",
272
+ title: index.instanceTitle(instance, index.definitionSnapshotOf(instance))
273
+ };
274
+ }
275
+
276
+ function boardRuns(instances) {
277
+ return instances.filter(instance => workflowEngine.terminalState(instance) !== "aborted");
278
+ }
279
+
280
+ function groupsOf(args) {
281
+ const declared = args.definition.stages.map(stage => stage.name), retired = [ ...args.byStage.keys() ].filter(stage => !declared.includes(stage)).sort();
282
+ return [ ...declared, ...retired ].map(stage => ({
283
+ stage: stage,
284
+ title: index.stageTitle(args.definition, stage),
285
+ cards: args.byStage.get(stage) ?? [],
286
+ retired: !declared.includes(stage)
287
+ }));
288
+ }
289
+
290
+ function deriveBoard(args) {
291
+ const byStage = /* @__PURE__ */ new Map;
292
+ for (const instance of boardRuns(args.instances)) {
293
+ const card = {
294
+ instanceId: instance._id,
295
+ face: faceOf(instance)
296
+ }, held = byStage.get(instance.currentStage);
297
+ held ? held.push(card) : byStage.set(instance.currentStage, [ card ]);
298
+ }
299
+ return {
300
+ groups: groupsOf({
301
+ definition: args.definition,
302
+ byStage: byStage
303
+ })
304
+ };
305
+ }
306
+
307
+ function orderBoardByPreviewTitle(board, titles) {
308
+ const compare = byResolvedTitle(card => card.face.kind === "instance" ? card.face.title : titles.get(card.face.document.id), card => card.instanceId);
309
+ return {
310
+ groups: board.groups.map(group => ({
311
+ ...group,
312
+ cards: [ ...group.cards ].sort(compare)
313
+ }))
314
+ };
315
+ }
316
+
317
+ function boardDocuments(board) {
318
+ return board.groups.flatMap(group => group.cards.flatMap(card => card.face.kind === "document" ? [ card.face.document ] : []));
319
+ }
320
+
321
+ function BoardCard({card: card, size: size}) {
322
+ return size === "slim" ? card.face.kind === "instance" ? /* @__PURE__ */ jsxRuntime.jsx(InstanceFace, {
323
+ size: size,
324
+ title: card.face.title
325
+ }) : /* @__PURE__ */ jsxRuntime.jsx(index.DocPreviewLink, {
326
+ gdr: card.face.document,
327
+ layout: "row",
328
+ source: "board-card"
329
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
330
+ border: !0,
331
+ radius: 3,
332
+ children: card.face.kind === "instance" ? /* @__PURE__ */ jsxRuntime.jsx(InstanceFace, {
333
+ size: size,
334
+ title: card.face.title
335
+ }) : /* @__PURE__ */ jsxRuntime.jsx(index.DocPreviewLink, {
336
+ gdr: card.face.document,
337
+ layout: "bare",
338
+ source: "board-card"
339
+ })
340
+ });
341
+ }
342
+
343
+ function InstanceFace({size: size, title: title}) {
344
+ const fill = size === "slim";
345
+ /* @__PURE__ */
346
+ return jsxRuntime.jsx(ui.Box, {
347
+ padding: fill ? index.chipPadding(!0) : index.NOTICE_PADDING,
348
+ style: fill ? {
349
+ width: "100%"
350
+ } : {},
351
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
352
+ size: 1,
353
+ textOverflow: "ellipsis",
354
+ weight: "medium",
355
+ children: title
356
+ })
357
+ });
358
+ }
359
+
360
+ function StageGroupHeading({group: group}) {
361
+ /* @__PURE__ */
362
+ return jsxRuntime.jsxs(ui.Flex, {
363
+ align: "center",
364
+ gap: 2,
365
+ children: [
366
+ /* @__PURE__ */ jsxRuntime.jsx(index.CountedLabel, {
367
+ count: group.cards.length,
368
+ label: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
369
+ size: 1,
370
+ weight: "medium",
371
+ children: group.title
372
+ })
373
+ }), group.retired ?
374
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
375
+ muted: !0,
376
+ size: 1,
377
+ children: "No longer in this workflow"
378
+ }) : null ]
379
+ });
380
+ }
381
+
382
+ const COLUMN_WIDTH = 320, PANEL_FADE = "linear-gradient(to bottom, rgb(0 0 0) 50%, rgb(0 0 0 / 0) 75%)";
383
+
384
+ function StageColumn({group: group}) {
385
+ /* @__PURE__ */
386
+ return jsxRuntime.jsxs(ui.Box, {
387
+ flex: "none",
388
+ style: {
389
+ display: "flex",
390
+ flexDirection: "column",
391
+ position: "relative",
392
+ width: COLUMN_WIDTH
393
+ },
394
+ children: [
395
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
396
+ "aria-hidden": !0,
397
+ radius: 3,
398
+ style: {
399
+ inset: 0,
400
+ maskImage: PANEL_FADE,
401
+ position: "absolute",
402
+ WebkitMaskImage: PANEL_FADE
403
+ },
404
+ tone: "transparent"
405
+ }),
406
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
407
+ flex: "none",
408
+ paddingX: 3,
409
+ paddingY: 4,
410
+ style: {
411
+ position: "relative"
412
+ },
413
+ children: /* @__PURE__ */ jsxRuntime.jsx(StageGroupHeading, {
414
+ group: group
415
+ })
416
+ }), group.cards.length === 0 ?
417
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
418
+ align: "center",
419
+ flex: 1,
420
+ justify: "center",
421
+ padding: 2,
422
+ style: {
423
+ position: "relative"
424
+ },
425
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
426
+ muted: !0,
427
+ size: 1,
428
+ children: BOARD_EMPTY_STAGE_NOTE
429
+ })
430
+ }) :
431
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
432
+ flex: 1,
433
+ overflow: "auto",
434
+ paddingBottom: 2,
435
+ paddingX: 2,
436
+ style: {
437
+ minHeight: 0,
438
+ position: "relative"
439
+ },
440
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
441
+ gap: 1,
442
+ children: group.cards.map(card => /* @__PURE__ */ jsxRuntime.jsx(BoardCard, {
443
+ card: card,
444
+ size: "block"
445
+ }, card.instanceId))
446
+ })
447
+ }) ]
448
+ });
449
+ }
450
+
451
+ function StageGroups({board: board}) {
452
+ const topGap = index.useSpaceToken(TOOL_PANEL_PADDING);
453
+ /* @__PURE__ */
454
+ return jsxRuntime.jsx(ui.Box, {
455
+ flex: 1,
456
+ style: {
457
+ minHeight: 0,
458
+ position: "relative"
459
+ },
460
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
461
+ style: {
462
+ bottom: 0,
463
+ left: 0,
464
+ overflowX: "auto",
465
+ overflowY: "hidden",
466
+ position: "absolute",
467
+ right: 0,
468
+ top: topGap
469
+ },
470
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
471
+ align: "stretch",
472
+ gap: 2,
473
+ paddingX: TOOL_PANEL_PADDING,
474
+ style: {
475
+ boxSizing: "border-box",
476
+ height: "100%",
477
+ width: "max-content"
478
+ },
479
+ children: board.groups.map(group => /* @__PURE__ */ jsxRuntime.jsx(StageColumn, {
480
+ group: group
481
+ }, group.stage))
482
+ })
483
+ })
484
+ });
485
+ }
486
+
487
+ function isPlainClick(event) {
488
+ return !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey;
489
+ }
490
+
491
+ const BOARD_LAYOUTS = [ "columns", "stack" ], BOARD_DEFAULT_LAYOUT = "columns";
492
+
493
+ function boardLayoutStorageKey(scope) {
494
+ return `sanity.workflows.tool.board-layout:${scope}`;
495
+ }
496
+
497
+ function layoutOf(raw) {
498
+ return BOARD_LAYOUTS.find(layout => layout === raw) ?? BOARD_DEFAULT_LAYOUT;
499
+ }
500
+
501
+ function createBoardLayoutStore(args) {
502
+ const storage = args.storage ?? defaultStorage();
503
+ return {
504
+ read: () => layoutOf(readItem(storage, args.storageKey).raw),
505
+ set(layout) {
506
+ return writeItem({
507
+ storage: storage,
508
+ key: args.storageKey,
509
+ value: layout
510
+ }), layout;
511
+ }
512
+ };
513
+ }
514
+
515
+ const PICKER_RADIUS = 3;
516
+
517
+ function pickerLabel(face) {
518
+ return face.kind === "named" ? face.title : face.kind === "loading" ? "Loading" : "Select a workflow";
519
+ }
520
+
521
+ function WorkflowPicker({face: face, onSelect: onSelect, options: options}) {
522
+ const empty = options.length === 0, innerRadius = index.useRadiusToken(PICKER_RADIUS) - 1;
523
+ /* @__PURE__ */
524
+ return jsxRuntime.jsx(ui.Card, {
525
+ border: !0,
526
+ radius: PICKER_RADIUS,
527
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
528
+ align: "center",
529
+ children: [
530
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
531
+ paddingLeft: 2,
532
+ paddingRight: 2,
533
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
534
+ muted: !0,
535
+ size: 1,
536
+ children: "Workflow"
537
+ })
538
+ }),
539
+ /* @__PURE__ */ jsxRuntime.jsx(ui.MenuButton, {
540
+ button: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
541
+ fontSize: 1,
542
+ gap: 2,
543
+ iconRight: ChevronDown.ChevronDownIcon,
544
+ mode: "bleed",
545
+ disabled: empty,
546
+ padding: 2,
547
+ radius: 0,
548
+ style: {
549
+ borderRadius: `0 ${innerRadius}px ${innerRadius}px 0`
550
+ },
551
+ text: pickerLabel(face)
552
+ }),
553
+ id: "workflows-board-picker",
554
+ menu: /* @__PURE__ */ jsxRuntime.jsx(ui.Menu, {
555
+ children: options.map(option => /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
556
+ fontSize: 1,
557
+ onClick: () => onSelect(option.name),
558
+ pressed: face.kind === "named" && option.name === face.name,
559
+ text: option.title
560
+ }, option.name))
561
+ }),
562
+ popover: {
563
+ constrainSize: !0,
564
+ fallbackPlacements: [ "top-start" ],
565
+ placement: "bottom-start",
566
+ portal: !0
567
+ }
568
+ }) ]
569
+ })
570
+ });
571
+ }
572
+
573
+ const LAYOUT_LABELS = {
574
+ columns: {
575
+ icon: Inline.InlineIcon,
576
+ label: "Show as boards"
577
+ },
578
+ stack: {
579
+ icon: StackCompact.StackCompactIcon,
580
+ label: "Show as list"
581
+ }
582
+ };
583
+
584
+ function LayoutToggle({layout: layout, onChange: onChange}) {
585
+ /* @__PURE__ */
586
+ return jsxRuntime.jsx(ui.Flex, {
587
+ align: "center",
588
+ gap: 1,
589
+ children: BOARD_LAYOUTS.map(value => {
590
+ const {icon: icon, label: label} = LAYOUT_LABELS[value];
591
+ /* @__PURE__ */
592
+ return jsxRuntime.jsx(index.HoverHint, {
593
+ text: label,
594
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
595
+ "aria-label": label,
596
+ "aria-pressed": value === layout,
597
+ fontSize: 1,
598
+ icon: icon,
599
+ mode: "bleed",
600
+ onClick: () => onChange(value),
601
+ padding: 2,
602
+ selected: value === layout
603
+ })
604
+ }, value);
605
+ })
606
+ });
607
+ }
608
+
609
+ function DefinitionLink({name: name, onOpen: onOpen}) {
610
+ const {resolvePathFromState: resolvePathFromState} = router.useRouter();
611
+ /* @__PURE__ */
612
+ return jsxRuntime.jsx(ui.Button, {
613
+ as: "a",
614
+ fontSize: 1,
615
+ href: resolvePathFromState({
616
+ definitionName: name
617
+ }),
618
+ mode: "bleed",
619
+ onClick: event => {
620
+ isPlainClick(event) && (event.preventDefault(), onOpen(name));
621
+ },
622
+ padding: 2,
623
+ text: "View definition"
624
+ });
625
+ }
626
+
627
+ function BoardControlsPortal({definitionName: definitionName, layout: layout, onChangeLayout: onChangeLayout, face: face, onOpenDefinition: onOpenDefinition, onSelectWorkflow: onSelectWorkflow, options: options, slot: slot}) {
628
+ return slot ? reactDom.createPortal(
629
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
630
+ align: "center",
631
+ gap: 2,
632
+ children: [
633
+ /* @__PURE__ */ jsxRuntime.jsx(WorkflowPicker, {
634
+ face: face,
635
+ onSelect: onSelectWorkflow,
636
+ options: options
637
+ }), definitionName === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(DefinitionLink, {
638
+ name: definitionName,
639
+ onOpen: onOpenDefinition
640
+ }),
641
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
642
+ flex: 1
643
+ }),
644
+ /* @__PURE__ */ jsxRuntime.jsx(LayoutToggle, {
645
+ layout: layout,
646
+ onChange: onChangeLayout
647
+ }) ]
648
+ }), slot) : null;
649
+ }
650
+
651
+ function boardWorkflowOptions(definitions) {
652
+ return definitions.map(definition => ({
653
+ name: definition.name,
654
+ title: definition.title
655
+ })).sort(byTitleThenName);
656
+ }
657
+
658
+ function boardLandingName(selection) {
659
+ if (!(selection.kind !== "resolved" || selection.via !== "default")) return selection.definition.name;
660
+ }
661
+
662
+ function boardPickerFace(args) {
663
+ const {selection: selection, routeName: routeName} = args;
664
+ if (selection.kind === "resolved") {
665
+ const {name: name, title: title} = selection.definition;
666
+ return {
667
+ kind: "named",
668
+ name: name,
669
+ title: title
670
+ };
671
+ }
672
+ return selection.kind === "loading" && routeName !== void 0 ? {
673
+ kind: "loading"
674
+ } : {
675
+ kind: "none"
676
+ };
677
+ }
678
+
679
+ function boardSelectedPayload(selection) {
680
+ if (selection.kind === "resolved") return {
681
+ via: selection.via,
682
+ ...index.definitionFingerprint(selection.definition)
683
+ };
684
+ }
685
+
686
+ function resolveBoardSelection(args) {
687
+ const {definitions: definitions, routeName: routeName} = args;
688
+ if (definitions === void 0) return {
689
+ kind: "loading"
690
+ };
691
+ if (routeName !== void 0) {
692
+ const named = definitions.find(definition => definition.name === routeName);
693
+ return named ? {
694
+ kind: "resolved",
695
+ definition: named,
696
+ via: "address"
697
+ } : {
698
+ kind: "unknown",
699
+ name: routeName
700
+ };
701
+ }
702
+ const [landing] = [ ...definitions ].sort(byTitleThenName);
703
+ return landing ? {
704
+ kind: "resolved",
705
+ definition: landing,
706
+ via: "default"
707
+ } : {
708
+ kind: "empty"
709
+ };
710
+ }
711
+
712
+ function StageBand({bands: bands, group: group}) {
713
+ const {open: open, toggle: toggle} = useBandOpen(bands, group.stage);
714
+ /* @__PURE__ */
715
+ return jsxRuntime.jsx(index.CollapsibleBand, {
716
+ background: !0,
717
+ onToggle: toggle,
718
+ open: open,
719
+ sticky: !0,
720
+ title: group.title,
721
+ header: /* @__PURE__ */ jsxRuntime.jsx(StageGroupHeading, {
722
+ group: group
723
+ }),
724
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
725
+ gap: 1,
726
+ paddingLeft: 5,
727
+ children: group.cards.length === 0 ?
728
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
729
+ padding: 3,
730
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
731
+ muted: !0,
732
+ size: 1,
733
+ children: BOARD_EMPTY_STAGE_NOTE
734
+ })
735
+ }) : group.cards.map(card => /* @__PURE__ */ jsxRuntime.jsx(BoardCard, {
736
+ card: card,
737
+ size: "slim"
738
+ }, card.instanceId))
739
+ })
740
+ });
741
+ }
742
+
743
+ function BoardStack({bands: bands, board: board}) {
744
+ /* @__PURE__ */
745
+ return jsxRuntime.jsx(ui.Stack, {
746
+ gap: 1,
747
+ paddingBottom: 4,
748
+ paddingTop: 3,
749
+ paddingX: TOOL_PANEL_PADDING,
750
+ children: board.groups.map(group => /* @__PURE__ */ jsxRuntime.jsx(StageBand, {
751
+ bands: bands,
752
+ group: group
753
+ }, group.stage))
754
+ });
755
+ }
756
+
757
+ const ALIAS_PREFIX = "__seed_", MAX_PATH_DEPTH = 3, IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
758
+
759
+ function previewSelectOf(schemaType) {
760
+ const select = schemaType.preview?.select;
761
+ if (!(select === void 0 || Object.keys(select).length === 0)) return select;
762
+ }
763
+
764
+ function fieldTypeOf(parent, name) {
765
+ if (!(!("fields" in parent) || !Array.isArray(parent.fields))) return parent.fields.find(field => field.name === name)?.type;
766
+ }
767
+
768
+ function stepOf(args) {
769
+ const {parent: parent, segment: segment, terminal: terminal} = args, fieldType = fieldTypeOf(parent, segment);
770
+ if (fieldType !== void 0) {
771
+ if (terminal) return {
772
+ part: segment,
773
+ next: fieldType
774
+ };
775
+ if (types.isReferenceSchemaType(fieldType)) {
776
+ const target = fieldType.to.length === 1 ? fieldType.to[0] : void 0;
777
+ return target === void 0 ? void 0 : {
778
+ part: `${segment}->`,
779
+ next: target
780
+ };
781
+ }
782
+ return {
783
+ part: `${segment}.`,
784
+ next: fieldType
785
+ };
786
+ }
787
+ }
788
+
789
+ function compilePath(root, path) {
790
+ const segments = path.split(".");
791
+ if (segments.length > MAX_PATH_DEPTH || segments.some(s => !IDENTIFIER.test(s))) return;
792
+ let parent = root;
793
+ const parts = [];
794
+ for (const [index2, segment] of segments.entries()) {
795
+ const step = stepOf({
796
+ parent: parent,
797
+ segment: segment,
798
+ terminal: index2 === segments.length - 1
799
+ });
800
+ if (step === void 0) return;
801
+ parts.push(step.part), parent = step.next;
802
+ }
803
+ return parts.join("");
804
+ }
805
+
806
+ function selectArmOf(schemaType) {
807
+ const select = previewSelectOf(schemaType);
808
+ if (select === void 0) return;
809
+ const entries = [];
810
+ for (const [key, path] of Object.entries(select)) {
811
+ const compiled = compilePath(schemaType, path);
812
+ if (compiled === void 0) return;
813
+ entries.push(`${JSON.stringify(ALIAS_PREFIX + key)}: ${compiled}`);
814
+ }
815
+ return `_type == ${JSON.stringify(schemaType.name)} => {${entries.join(", ")}}`;
816
+ }
817
+
818
+ function planTitleSeed(types2) {
819
+ const seedable = /* @__PURE__ */ new Map, arms = [];
820
+ for (const type of types2) {
821
+ const arm = selectArmOf(type);
822
+ arm !== void 0 && (seedable.set(type.name, type), arms.push(arm));
823
+ }
824
+ return {
825
+ query: `*[_id in $ids]{_id, _type${arms.length > 0 ? `, ...select(${arms.join(", ")})` : ""}}`,
826
+ seedable: seedable
827
+ };
828
+ }
829
+
830
+ function isSeedRow(value) {
831
+ if (typeof value != "object" || value === null) return !1;
832
+ const row = value;
833
+ return typeof row._id == "string" && typeof row._type == "string";
834
+ }
835
+
836
+ function seededTitleOf(row, schemaType) {
837
+ const select = previewSelectOf(schemaType);
838
+ if (select === void 0) return;
839
+ const selected = {};
840
+ for (const key of Object.keys(select)) selected[key] = row[ALIAS_PREFIX + key];
841
+ const prepare = schemaType.preview?.prepare;
842
+ let title;
843
+ if (prepare === void 0) title = selected.title; else try {
844
+ title = prepare(selected).title;
845
+ } catch (err) {
846
+ console.error(`[workflow-studio-plugin] preview prepare for "${schemaType.name}" failed on seed:`, err);
847
+ return;
848
+ }
849
+ return typeof title == "string" ? title : void 0;
850
+ }
851
+
852
+ function seedFromRows(args) {
853
+ const rows = args.rows.filter(isSeedRow), byBareId = new Map(rows.map(row => [ row._id, row ])), types2 = new Map(rows.map(row => [ row._id, row._type ])), titles = /* @__PURE__ */ new Map, resolved = /* @__PURE__ */ new Set;
854
+ for (const {gdrId: gdrId, bareId: bareId} of args.docs) {
855
+ const row = byBareId.get(bareId);
856
+ if (row === void 0) continue;
857
+ const schemaType = args.seedable.get(row._type);
858
+ if (schemaType === void 0) continue;
859
+ resolved.add(gdrId);
860
+ const title = seededTitleOf(row, schemaType);
861
+ title !== void 0 && titles.set(gdrId, title);
862
+ }
863
+ return {
864
+ types: types2,
865
+ titles: {
866
+ titles: titles,
867
+ resolved: resolved
868
+ }
869
+ };
870
+ }
871
+
872
+ function titleDrift(seed, live) {
873
+ const drifted = [];
874
+ for (const gdrId of seed.resolved) {
875
+ if (!live.resolved.has(gdrId)) continue;
876
+ const seeded = seed.titles.get(gdrId), observed = live.titles.get(gdrId);
877
+ seeded !== observed && drifted.push({
878
+ gdrId: gdrId,
879
+ seeded: seeded,
880
+ observed: observed
881
+ });
882
+ }
883
+ return drifted;
884
+ }
885
+
886
+ function mergeSeededTitles(live, seed) {
887
+ if (seed.resolved.size === 0) return live;
888
+ const titles = new Map(live.titles), resolved = new Set(live.resolved);
889
+ for (const gdrId of seed.resolved) {
890
+ if (live.resolved.has(gdrId)) continue;
891
+ resolved.add(gdrId);
892
+ const title = seed.titles.get(gdrId);
893
+ title !== void 0 && titles.set(gdrId, title);
894
+ }
895
+ return {
896
+ titles: titles,
897
+ resolved: resolved
898
+ };
899
+ }
900
+
901
+ const NO_TITLES = {
902
+ titles: /* @__PURE__ */ new Map,
903
+ resolved: /* @__PURE__ */ new Set
904
+ };
905
+
906
+ function useDocPreviewTitles(documents) {
907
+ const previewStore = sanity.useDocumentPreviewStore(), schema = sanity.useSchema(), {perspectiveStack: perspectiveStack} = sanity.usePerspective(), {actualTypes: actualTypes, binding: binding} = index.useWorkflowContext(), [state, setState] = react.useState(NO_TITLES), key = documents.map(doc => doc.id).join("|");
908
+ return react.useEffect(() => {
909
+ const titles = /* @__PURE__ */ new Map, resolved = /* @__PURE__ */ new Set, previews = /* @__PURE__ */ new Map, publish = () => setState({
910
+ titles: new Map(titles),
911
+ resolved: new Set(resolved)
912
+ }), localDocs = documents.flatMap(doc => {
913
+ const {parsed: parsed, local: local} = index.gdrLocality(doc.id, binding.contentResource);
914
+ return !parsed || !local ? (resolved.add(doc.id), []) : [ {
915
+ doc: doc,
916
+ bareId: parsed.documentId
917
+ } ];
918
+ }), untracks = localDocs.map(({bareId: bareId}) => actualTypes.track(bareId)), dropPreview = docId => {
919
+ previews.get(docId)?.unsubscribe(), previews.delete(docId), titles.delete(docId);
920
+ }, observe = ({docId: docId, bareId: bareId, typeName: typeName}) => {
921
+ const schemaType = index.openableSchemaType(schema, typeName);
922
+ if (!schemaType) return !1;
923
+ if (previews.get(docId)?.typeName === typeName) return !0;
924
+ dropPreview(docId), resolved.delete(docId);
925
+ const subscription = previewStore.observeForPreview({
926
+ _id: bareId,
927
+ _type: schemaType.name
928
+ }, schemaType, {
929
+ perspective: perspectiveStack
930
+ }).subscribe({
931
+ next: event => {
932
+ resolved.add(docId);
933
+ const title = event.snapshot?.title;
934
+ typeof title == "string" ? titles.set(docId, title) : titles.delete(docId), publish();
935
+ },
936
+ error: err => {
937
+ console.error(`[workflow-studio-plugin] preview for "${docId}" failed:`, err), resolved.add(docId),
938
+ publish();
939
+ }
940
+ });
941
+ return previews.set(docId, {
942
+ typeName: typeName,
943
+ unsubscribe: () => subscription.unsubscribe()
944
+ }), !0;
945
+ }, evaluate = () => {
946
+ for (const {doc: doc, bareId: bareId} of localDocs) {
947
+ const probe = actualTypes.read(bareId);
948
+ if (probe === null) {
949
+ dropPreview(doc.id), resolved.add(doc.id);
950
+ continue;
951
+ }
952
+ !observe({
953
+ docId: doc.id,
954
+ bareId: bareId,
955
+ typeName: probe ?? doc.type
956
+ }) && typeof probe == "string" && (dropPreview(doc.id), resolved.add(doc.id));
957
+ }
958
+ publish();
959
+ }, unsubscribeProbes = actualTypes.subscribe(evaluate);
960
+ return evaluate(), () => {
961
+ unsubscribeProbes(), untracks.forEach(untrack => untrack()), previews.forEach(preview => preview.unsubscribe());
962
+ };
963
+ }, [ key, previewStore, schema, actualTypes, binding.contentResource.id, perspectiveStack.join("|") ]),
964
+ state;
965
+ }
966
+
967
+ function usePreviewSeed(documents) {
968
+ const client = sanity.useClient({
969
+ apiVersion: index.WORKFLOW_API_VERSION
970
+ }), schema = sanity.useSchema(), {perspectiveStack: perspectiveStack} = sanity.usePerspective(), {actualTypes: actualTypes, binding: binding} = index.useWorkflowContext(), [seed, setSeed] = react.useState(NO_TITLES), docs = react.useMemo(() => documents.flatMap(doc => {
971
+ const {parsed: parsed, local: local} = index.gdrLocality(doc.id, binding.contentResource);
972
+ return parsed === void 0 || !local ? [] : [ {
973
+ gdrId: doc.id,
974
+ bareId: parsed.documentId
975
+ } ];
976
+ }), [ documents, binding.contentResource ]), key = docs.map(doc => doc.gdrId).join("|");
977
+ return react.useEffect(() => {
978
+ if (setSeed(NO_TITLES), key === "") return;
979
+ const plan = planTitleSeed(schema.getTypeNames().flatMap(name => index.openableSchemaType(schema, name) ?? []));
980
+ let cancelled = !1;
981
+ return client.fetch(plan.query, {
982
+ ids: docs.map(doc => doc.bareId)
983
+ }, {
984
+ tag: "workflows.preview-seed",
985
+ ...perspectiveStack.length > 0 ? {
986
+ perspective: [ ...perspectiveStack ]
987
+ } : {}
988
+ }).then(rows => {
989
+ if (cancelled) return;
990
+ const result = seedFromRows({
991
+ rows: rows,
992
+ docs: docs,
993
+ seedable: plan.seedable
994
+ });
995
+ actualTypes.seed(result.types), setSeed(result.titles);
996
+ }).catch(err => {
997
+ cancelled || console.error("[workflow-studio-plugin] preview seed failed:", err);
998
+ }), () => {
999
+ cancelled = !0;
1000
+ };
1001
+ }, [ key, client, schema, actualTypes, binding.contentResource.id, perspectiveStack.join("|") ]),
1002
+ seed;
1003
+ }
1004
+
1005
+ function useSeededTitles(args) {
1006
+ const {ordering: ordering, warm: warm} = args, seed = usePreviewSeed(warm), live = useDocPreviewTitles(ordering), telemetry = workflowReact.useWorkflowTelemetry(), warned = react.useRef(/* @__PURE__ */ new Set);
1007
+ return react.useEffect(() => {
1008
+ const fresh = titleDrift(seed, live).filter(drift => !warned.current.has(drift.gdrId));
1009
+ if (fresh.length !== 0) {
1010
+ warned.current.size === 0 && telemetry.log(index.WorkflowTitleSeedDrifted, {
1011
+ driftCount: fresh.length
1012
+ });
1013
+ for (const drift of fresh) warned.current.add(drift.gdrId), console.warn(`[workflow-studio-plugin] seeded preview title for "${drift.gdrId}" diverged from the live pipeline — the seed projection or prepare mirroring is drifting from Studio preview resolution`);
1014
+ }
1015
+ }, [ seed, live, telemetry ]), react.useMemo(() => {
1016
+ const merged = mergeSeededTitles(live, seed);
1017
+ return {
1018
+ ...merged,
1019
+ ready: ordering.every(doc => merged.resolved.has(doc.id))
1020
+ };
1021
+ }, [ ordering, live, seed ]);
1022
+ }
1023
+
1024
+ function useGroupTitles(groups) {
1025
+ const ordering = react.useMemo(() => groups.withDocuments.map(group => group.document), [ groups ]), warm = react.useMemo(() => groups.withDocuments.flatMap(group => group.documents), [ groups ]);
1026
+ return useSeededTitles({
1027
+ ordering: ordering,
1028
+ warm: warm
1029
+ });
1030
+ }
1031
+
1032
+ const NO_DOCUMENTS = [];
1033
+
1034
+ function useBoard(args) {
1035
+ const {definition: definition, instances: instances} = args, derived = react.useMemo(() => definition ? deriveBoard({
1036
+ instances: instances,
1037
+ definition: definition
1038
+ }) : void 0, [ definition, instances ]), documents = react.useMemo(() => derived ? boardDocuments(derived) : NO_DOCUMENTS, [ derived ]), {titles: titles, ready: ready} = useSeededTitles({
1039
+ ordering: documents,
1040
+ warm: documents
1041
+ }), board = react.useMemo(() => derived ? orderBoardByPreviewTitle(derived, titles) : void 0, [ derived, titles ]);
1042
+ return react.useMemo(() => ({
1043
+ board: board,
1044
+ ready: ready
1045
+ }), [ board, ready ]);
1046
+ }
1047
+
1048
+ const INSTANCE_CAP = 200;
1049
+
1050
+ function useCappedInstances(filter) {
1051
+ const {engine: engine} = index.useWorkflowContext(), capped = react.useMemo(() => ({
1052
+ ...filter,
1053
+ limit: INSTANCE_CAP
1054
+ }), [ filter ]), {instances: instances, loading: loading, unreadable: unreadable} = workflowStudio.useWorkflowInstances({
1055
+ engine: engine,
1056
+ filter: capped
1057
+ });
1058
+ return react.useMemo(() => {
1059
+ const rows = instances ?? [];
1060
+ return {
1061
+ rows: rows,
1062
+ truncated: rows.length + unreadable.length === INSTANCE_CAP,
1063
+ loading: loading,
1064
+ unreadable: unreadable
1065
+ };
1066
+ }, [ instances, loading, unreadable ]);
1067
+ }
1068
+
1069
+ const BOARD_CAP_NOTE = `Showing the latest ${INSTANCE_CAP} workflows — older ones aren’t on the board.`, NOTHING = {
1070
+ ids: []
1071
+ };
1072
+
1073
+ function useBoardInstances(definitionName) {
1074
+ const filter = react.useMemo(() => definitionName === void 0 ? NOTHING : {
1075
+ definition: definitionName,
1076
+ includeCompleted: !0
1077
+ }, [ definitionName ]);
1078
+ return useCappedInstances(filter);
1079
+ }
1080
+
1081
+ function useBoardLayout(scope) {
1082
+ const store = react.useMemo(() => createBoardLayoutStore({
1083
+ storageKey: boardLayoutStorageKey(scope)
1084
+ }), [ scope ]), [held, setHeld] = react.useState(() => ({
1085
+ store: store,
1086
+ layout: store.read()
1087
+ }));
1088
+ return held.store !== store && setHeld({
1089
+ store: store,
1090
+ layout: store.read()
1091
+ }), {
1092
+ layout: held.layout,
1093
+ setLayout: layout => setHeld({
1094
+ store: store,
1095
+ layout: store.set(layout)
1096
+ })
1097
+ };
1098
+ }
1099
+
1100
+ function DocumentsTab({filterSlot: filterSlot, onOpenDefinition: onOpenDefinition, onSelectWorkflow: onSelectWorkflow, routeDefinition: routeDefinition}) {
1101
+ const {binding: binding} = index.useWorkflowContext(), telemetry = workflowReact.useWorkflowTelemetry(), {definitions: definitions, error: error} = useDeployedDefinitions(), {layout: layout, setLayout: setLayout} = useBoardLayout(binding.engineResource.id), bands = useBandOpenState({
1102
+ segment: "documents",
1103
+ scope: binding.engineResource.id
1104
+ }), selection = resolveBoardSelection({
1105
+ definitions: definitions,
1106
+ routeName: routeDefinition
1107
+ }), selected = selection.kind === "resolved" ? selection.definition : void 0, instances = useBoardInstances(selected?.name), {board: board, ready: ready} = useBoard({
1108
+ definition: selected,
1109
+ instances: instances.rows
1110
+ }), revealed = useRevealGate({
1111
+ loading: instances.loading,
1112
+ ready: ready
1113
+ }), landing = boardLandingName(selection);
1114
+ react.useEffect(() => {
1115
+ landing !== void 0 && onSelectWorkflow(landing, {
1116
+ replace: !0
1117
+ });
1118
+ }, [ landing, onSelectWorkflow ]);
1119
+ const selectedPayload = boardSelectedPayload(selection);
1120
+ /* @__PURE__ */
1121
+ return jsxRuntime.jsxs(ui.Flex, {
1122
+ direction: "column",
1123
+ flex: 1,
1124
+ style: {
1125
+ minHeight: 0
1126
+ },
1127
+ children: [
1128
+ /* @__PURE__ */ jsxRuntime.jsx(BoardControlsPortal, {
1129
+ definitionName: selected?.name,
1130
+ layout: layout,
1131
+ onOpenDefinition: onOpenDefinition,
1132
+ onChangeLayout: next => {
1133
+ next !== layout && (telemetry.log(index.WorkflowBoardLayoutChanged, {
1134
+ layout: next
1135
+ }), setLayout(next));
1136
+ },
1137
+ onSelectWorkflow: onSelectWorkflow,
1138
+ options: boardWorkflowOptions(definitions ?? []),
1139
+ face: boardPickerFace({
1140
+ selection: selection,
1141
+ routeName: routeDefinition
1142
+ }),
1143
+ slot: filterSlot
1144
+ }),
1145
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1146
+ direction: "column",
1147
+ flex: 1,
1148
+ gap: 2,
1149
+ style: {
1150
+ minHeight: 0
1151
+ },
1152
+ children: [ instances.unreadable.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1153
+ paddingX: TOOL_PANEL_PADDING,
1154
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.UnreadableDocsNote, {
1155
+ unreadable: instances.unreadable
1156
+ })
1157
+ }),
1158
+ /* @__PURE__ */ jsxRuntime.jsx(BoardBody, {
1159
+ bands: bands,
1160
+ board: board,
1161
+ error: error,
1162
+ layout: layout,
1163
+ loading: !revealed,
1164
+ selection: selection
1165
+ }), instances.truncated ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1166
+ paddingX: TOOL_PANEL_PADDING,
1167
+ children: /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1168
+ text: BOARD_CAP_NOTE
1169
+ })
1170
+ }) : null ]
1171
+ }), selectedPayload === void 0 ? null :
1172
+ /* @__PURE__ */ jsxRuntime.jsx(index.LogEventOnMount, {
1173
+ data: selectedPayload,
1174
+ event: index.WorkflowBoardWorkflowSelected
1175
+ }, selected?.name) ]
1176
+ });
1177
+ }
1178
+
1179
+ function BoardBody({bands: bands, board: board, error: error, layout: layout, loading: loading, selection: selection}) {
1180
+ return error !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(StatusBox, {
1181
+ children: /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1182
+ text: `Could not load the deployed workflows: ${error}`
1183
+ })
1184
+ }) : selection.kind === "loading" ? /* @__PURE__ */ jsxRuntime.jsx(StatusBox, {
1185
+ children: /* @__PURE__ */ jsxRuntime.jsx(TabStatusRow, {
1186
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1187
+ label: "Loading workflows…"
1188
+ })
1189
+ })
1190
+ }) : selection.kind === "empty" ? /* @__PURE__ */ jsxRuntime.jsx(StatusBox, {
1191
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.EmptyState, {
1192
+ description: "Deploy a workflow to see its documents here",
1193
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Search.SearchIcon, {}),
1194
+ title: "No workflows deployed"
1195
+ })
1196
+ }) : selection.kind === "unknown" ? /* @__PURE__ */ jsxRuntime.jsx(StatusBox, {
1197
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.EmptyState, {
1198
+ description: `No deployed workflow is named “${selection.name}” — pick one above`,
1199
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Search.SearchIcon, {}),
1200
+ title: "Workflow not found"
1201
+ })
1202
+ }) : loading || board === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(StatusBox, {
1203
+ children: /* @__PURE__ */ jsxRuntime.jsx(TabStatusRow, {
1204
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1205
+ label: "Loading documents…"
1206
+ })
1207
+ })
1208
+ }) : layout === "stack" ? /* @__PURE__ */ jsxRuntime.jsx(BoardStack, {
1209
+ bands: bands,
1210
+ board: board
1211
+ }) : /* @__PURE__ */ jsxRuntime.jsx(StageGroups, {
1212
+ board: board
1213
+ });
1214
+ }
1215
+
1216
+ function StatusBox({children: children}) {
1217
+ /* @__PURE__ */
1218
+ return jsxRuntime.jsx(ui.Flex, {
1219
+ direction: "column",
1220
+ flex: 1,
1221
+ paddingX: TOOL_PANEL_PADDING,
1222
+ children: children
1223
+ });
1224
+ }
1225
+
25
1226
  function definitionCatalog(args) {
1227
+ const rejected = [ ...args.issues.values() ];
26
1228
  return args.definitions.map(definition => ({
27
1229
  name: definition.name,
28
1230
  title: definition.title ?? definition.name,
29
1231
  description: definition.description,
30
1232
  activeCount: args.inFlight.filter(instance => instance.definition === definition.name).length,
31
- docTypes: [ ...new Set(args.mappings.filter(mapping => mapping.definition === definition.name).map(mapping => mapping.docType)) ]
32
- })).sort((a, b) => a.title.localeCompare(b.title) || a.name.localeCompare(b.name));
1233
+ docTypes: [ ...new Set(args.mappings.filter(mapping => mapping.definition === definition.name).map(mapping => mapping.docType)) ],
1234
+ brokenBindings: rejected.filter(issue => issue.definition === definition.name && issue.version === definition.version).map(issue => ({
1235
+ docType: issue.docType,
1236
+ detail: index.mappingIssueDetail(issue)
1237
+ })).sort((a, b) => a.docType.localeCompare(b.docType))
1238
+ })).sort(byTitleThenName);
1239
+ }
1240
+
1241
+ function undeployedWorkflowNotices(args) {
1242
+ return [ ...args.issues.values() ].filter(index.namesNoDeployedDefinition).sort((a, b) => a.definition.localeCompare(b.definition) || a.docType.localeCompare(b.docType)).map(issue => `“${issue.definition}” is set up to run on ${args.titleOf(issue.docType) ?? issue.docType} documents, but no deployed workflow has that name`);
33
1243
  }
34
1244
 
35
1245
  function docTypeLabels(docTypes, titleOf) {
36
1246
  return docTypes.map(name => ({
37
1247
  name: name,
38
1248
  title: titleOf(name) ?? name
39
- })).sort((a, b) => a.title.localeCompare(b.title) || a.name.localeCompare(b.name));
1249
+ })).sort(byTitleThenName);
40
1250
  }
41
1251
 
42
1252
  function startPhrase(definition) {
43
- if (definition.lifecycle === "child") return "Spawned by a parent workflow";
1253
+ if (definition.lifecycle === "child") return "Started by a parent workflow";
44
1254
  if (workflowEngine.startKindOf(definition) === "autonomous") return "Starts automatically";
45
1255
  const subject = (definition.fields ?? []).find(workflowEngine.isSubjectEntry);
46
1256
  return subject !== void 0 && workflowEngine.isInputSourced(subject) ? "Started manually, from a document" : "Started manually";
@@ -50,6 +1260,37 @@ function deployedAtOf(definition) {
50
1260
  return typeof definition._updatedAt == "string" ? definition._updatedAt : void 0;
51
1261
  }
52
1262
 
1263
+ function deployedPhrase(definition) {
1264
+ const deployedAt = deployedAtOf(definition);
1265
+ if (deployedAt === void 0) return `v${definition.version}`;
1266
+ const ago = index.formatTimeAgo(deployedAt);
1267
+ return ago === "" ? index.formatShortDateTime(deployedAt) : `${index.formatShortDateTime(deployedAt)} (${ago})`;
1268
+ }
1269
+
1270
+ function DetailNotFound({back: back, message: message}) {
1271
+ /* @__PURE__ */
1272
+ return jsxRuntime.jsxs(ui.Stack, {
1273
+ gap: 4,
1274
+ children: [
1275
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1276
+ muted: !0,
1277
+ size: 1,
1278
+ children: message
1279
+ }),
1280
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
1281
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1282
+ fontSize: 1,
1283
+ gap: 2,
1284
+ icon: ArrowLeft.ArrowLeftIcon,
1285
+ mode: "ghost",
1286
+ onClick: back.go,
1287
+ padding: 2,
1288
+ text: back.label
1289
+ })
1290
+ }) ]
1291
+ });
1292
+ }
1293
+
53
1294
  function DetailTitle({busy: busy = !1, documentGdr: documentGdr, title: title}) {
54
1295
  const chipPullback = -index.useSpaceToken(1);
55
1296
  /* @__PURE__ */
@@ -84,7 +1325,8 @@ function DetailTitle({busy: busy = !1, documentGdr: documentGdr, title: title})
84
1325
  },
85
1326
  children: /* @__PURE__ */ jsxRuntime.jsx(index.DocPreviewLink, {
86
1327
  gdr: documentGdr,
87
- layout: "inline"
1328
+ layout: "inline",
1329
+ source: "detail-title"
88
1330
  })
89
1331
  }) ]
90
1332
  }),
@@ -94,46 +1336,59 @@ function DetailTitle({busy: busy = !1, documentGdr: documentGdr, title: title})
94
1336
  });
95
1337
  }
96
1338
 
97
- const lastRead = /* @__PURE__ */ new WeakMap, issuedReads = /* @__PURE__ */ new WeakMap;
1339
+ const INSTANCE_CAP_COUNT_NOTE = `Counts from the latest ${INSTANCE_CAP} workflows older ones aren’t counted.`, NEWEST_INSTANCES = {
1340
+ includeCompleted: !0
1341
+ };
98
1342
 
99
- function useDeployedDefinitions() {
100
- const {engine: engine} = index.useWorkflowContext(), [definitions, setDefinitions] = react.useState(() => lastRead.get(engine)?.rows), [error, setError] = react.useState(void 0);
101
- return react.useEffect(() => {
102
- let cancelled = !1;
103
- setDefinitions(lastRead.get(engine)?.rows), setError(void 0);
104
- const seq = (issuedReads.get(engine) ?? 0) + 1;
105
- return issuedReads.set(engine, seq), (async () => {
106
- const rows = await index.readDeployedDefinitions(engine), latest = workflowEngine.latestDeployedDefinitions(rows), held = lastRead.get(engine);
107
- (held === void 0 || seq > held.seq) && lastRead.set(engine, {
108
- seq: seq,
109
- rows: latest
110
- }), cancelled || (setDefinitions(latest), setError(void 0));
111
- })().catch(err => {
112
- cancelled || setError(index.describeError(err));
113
- }), () => {
114
- cancelled = !0;
115
- };
116
- }, [ engine ]), {
117
- definitions: definitions,
118
- error: error
119
- };
1343
+ function useToolInstances() {
1344
+ const {rows: rows, truncated: truncated, loading: loading, unreadable: unreadable} = useCappedInstances(NEWEST_INSTANCES);
1345
+ return react.useMemo(() => ({
1346
+ inFlight: rows.filter(instance => workflowEngine.terminalState(instance) === "in-flight"),
1347
+ settled: rows.filter(instance => workflowEngine.terminalState(instance) !== "in-flight"),
1348
+ truncated: truncated,
1349
+ loading: loading,
1350
+ unreadable: unreadable
1351
+ }), [ rows, truncated, loading, unreadable ]);
120
1352
  }
121
1353
 
122
- function DefinitionDetail({definitionName: definitionName}) {
123
- const {definitions: definitions, error: error} = useDeployedDefinitions(), snapshotWidth = index.useContainerToken(2), definition = definitions?.find(row => row.name === definitionName);
124
- /* @__PURE__ */
125
- return jsxRuntime.jsxs(ui.Flex, {
1354
+ function DefinitionDetail({definitionName: definitionName, backToList: backToList}) {
1355
+ const {definitions: definitions, error: error} = useDeployedDefinitions(), definition = definitions?.find(row => row.name === definitionName), loading = definitions === void 0 && error === void 0, viewed = loading || error !== void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(index.LogEventOnMount, {
1356
+ data: {
1357
+ found: definition !== void 0,
1358
+ ...index.definitionFingerprint(definition)
1359
+ },
1360
+ event: index.WorkflowDefinitionDetailViewed
1361
+ });
1362
+ return !loading && error === void 0 && definition === void 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Box, {
1363
+ paddingX: 3,
1364
+ children: [ viewed,
1365
+ /* @__PURE__ */ jsxRuntime.jsx(TabStatusRow, {
1366
+ children: /* @__PURE__ */ jsxRuntime.jsx(DetailNotFound, {
1367
+ back: backToList,
1368
+ message: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
1369
+ children: [ "Unable to find a definition with name “",
1370
+ /* @__PURE__ */ jsxRuntime.jsx("strong", {
1371
+ children: definitionName
1372
+ }), "”." ]
1373
+ })
1374
+ })
1375
+ }) ]
1376
+ }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
126
1377
  direction: "column",
127
1378
  height: "fill",
128
1379
  overflow: "hidden",
129
- children: [
1380
+ children: [ viewed,
130
1381
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
131
1382
  flex: "none",
132
1383
  paddingBottom: 2,
133
1384
  paddingTop: 3,
134
1385
  paddingX: 3,
135
- children: /* @__PURE__ */ jsxRuntime.jsx(DetailTitle, {
136
- busy: definitions === void 0 && error === void 0,
1386
+ children: loading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1387
+ paddingLeft: 2,
1388
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1389
+ label: "Loading definition…"
1390
+ })
1391
+ }) : /* @__PURE__ */ jsxRuntime.jsx(DetailTitle, {
137
1392
  title: definition?.title ?? definitionName
138
1393
  })
139
1394
  }),
@@ -141,125 +1396,176 @@ function DefinitionDetail({definitionName: definitionName}) {
141
1396
  flex: 1,
142
1397
  overflow: "auto",
143
1398
  paddingX: 3,
144
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1399
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
1400
+ direction: "column",
145
1401
  paddingBottom: 5,
146
1402
  paddingLeft: 2,
147
- paddingTop: 2,
1403
+ paddingTop: 4,
148
1404
  style: {
149
- maxWidth: snapshotWidth
1405
+ boxSizing: "border-box",
1406
+ minHeight: "100%"
150
1407
  },
151
1408
  children: /* @__PURE__ */ jsxRuntime.jsx(DefinitionBody, {
152
1409
  definition: definition,
153
- error: error,
154
- loaded: definitions !== void 0
1410
+ error: error
155
1411
  })
156
1412
  })
157
1413
  }) ]
158
1414
  });
159
1415
  }
160
1416
 
161
- function DefinitionBody({definition: definition, error: error, loaded: loaded}) {
1417
+ function DefinitionBody({definition: definition, error: error}) {
162
1418
  return error !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
163
1419
  text: `Could not load the deployed workflows: ${error}`
164
- }) : loaded ? definition === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
165
- text: "No deployed workflow definition with this name — it may have been retracted."
166
- }) : /* @__PURE__ */ jsxRuntime.jsx(DefinitionFacts, {
1420
+ }) : definition === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(DefinitionFacts, {
167
1421
  definition: definition
168
- }) : /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
169
- label: "Loading definition…"
1422
+ });
1423
+ }
1424
+
1425
+ function SetupIssues({bindings: bindings}) {
1426
+ const schema = sanity.useSchema();
1427
+ return bindings.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
1428
+ padding: 3,
1429
+ radius: 3,
1430
+ tone: "caution",
1431
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1432
+ align: "flex-start",
1433
+ gap: 3,
1434
+ children: [
1435
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1436
+ size: 1,
1437
+ children: /* @__PURE__ */ jsxRuntime.jsx(WarningOutline.WarningOutlineIcon, {})
1438
+ }),
1439
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1440
+ flex: 1,
1441
+ gap: 3,
1442
+ children: [
1443
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1444
+ size: 1,
1445
+ weight: "semibold",
1446
+ children: "Setup issue"
1447
+ }), bindings.map(binding => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1448
+ size: 1,
1449
+ children: `${schema.get(binding.docType)?.title ?? binding.docType}: ${binding.detail}.`
1450
+ }, binding.docType)),
1451
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1452
+ muted: !0,
1453
+ size: 1,
1454
+ children: "Editors see this workflow disabled on these document types until a developer fixes it."
1455
+ }) ]
1456
+ }) ]
1457
+ })
170
1458
  });
171
1459
  }
172
1460
 
173
1461
  function DefinitionFacts({definition: definition}) {
174
- const {binding: binding, mappings: mappings} = index.useWorkflowContext(), schema = sanity.useSchema(), deployedAt = deployedAtOf(definition), [row] = definitionCatalog({
1462
+ const {mappingIssues: mappingIssues, mappings: mappings} = index.useWorkflowContext(), schema = sanity.useSchema(), instances = useToolInstances(), snapshotWidth = index.useContainerToken(2), [row] = definitionCatalog({
175
1463
  definitions: [ definition ],
176
1464
  mappings: mappings,
177
- inFlight: []
1465
+ inFlight: instances.inFlight,
1466
+ issues: mappingIssues
178
1467
  }), runsOn = docTypeLabels(row?.docTypes ?? [], name => schema.get(name)?.title).map(docType => docType.title);
179
1468
  /* @__PURE__ */
180
- return jsxRuntime.jsxs(ui.Stack, {
1469
+ return jsxRuntime.jsxs(ui.Flex, {
1470
+ direction: "column",
1471
+ flex: 1,
181
1472
  gap: 4,
182
- children: [
183
- /* @__PURE__ */ jsxRuntime.jsx(index.MetaRow, {
184
- label: "Deployed",
185
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
186
- size: 1,
187
- children: [ deployedAt === void 0 ? `v${definition.version}` : index.formatDate(deployedAt), " ·", " ", binding.tag ]
188
- })
189
- }),
190
- /* @__PURE__ */ jsxRuntime.jsx(index.MetaRow, {
191
- label: "Trigger",
192
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
193
- size: 1,
194
- children: startPhrase(definition)
1473
+ children: [ row === void 0 || row.brokenBindings.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1474
+ flex: "none",
1475
+ children: /* @__PURE__ */ jsxRuntime.jsx(SetupIssues, {
1476
+ bindings: row.brokenBindings
195
1477
  })
196
1478
  }),
197
- /* @__PURE__ */ jsxRuntime.jsx(index.MetaRow, {
198
- label: "Document types",
199
- children: runsOn.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
200
- muted: !0,
201
- size: 1,
202
- children: "No document types in this Studio"
203
- }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
204
- size: 1,
205
- children: runsOn.join(", ")
1479
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1480
+ flex: "none",
1481
+ style: {
1482
+ maxWidth: snapshotWidth
1483
+ },
1484
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1485
+ gap: 4,
1486
+ children: [
1487
+ /* @__PURE__ */ jsxRuntime.jsx(index.MetaRow, {
1488
+ label: "Deployed",
1489
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1490
+ size: 1,
1491
+ children: deployedPhrase(definition)
1492
+ })
1493
+ }),
1494
+ /* @__PURE__ */ jsxRuntime.jsx(index.MetaRow, {
1495
+ label: "Active instances",
1496
+ children: instances.loading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1497
+ muted: !0,
1498
+ size: 1,
1499
+ children: "—"
1500
+ }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1501
+ gap: 2,
1502
+ children: [
1503
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1504
+ size: 1,
1505
+ children: row?.activeCount ?? 0
1506
+ }), instances.truncated ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1507
+ muted: !0,
1508
+ size: 1,
1509
+ children: INSTANCE_CAP_COUNT_NOTE
1510
+ }) : null ]
1511
+ })
1512
+ }),
1513
+ /* @__PURE__ */ jsxRuntime.jsx(index.MetaRow, {
1514
+ label: "Trigger",
1515
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1516
+ size: 1,
1517
+ children: startPhrase(definition)
1518
+ })
1519
+ }),
1520
+ /* @__PURE__ */ jsxRuntime.jsx(index.MetaRow, {
1521
+ label: "Document types",
1522
+ children: runsOn.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1523
+ muted: !0,
1524
+ size: 1,
1525
+ children: "No document types in this Studio"
1526
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1527
+ size: 1,
1528
+ children: runsOn.join(", ")
1529
+ })
1530
+ }),
1531
+ /* @__PURE__ */ jsxRuntime.jsx(index.MetaRow, {
1532
+ label: "Description",
1533
+ children: definition.description === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1534
+ muted: !0,
1535
+ size: 1,
1536
+ children: /* @__PURE__ */ jsxRuntime.jsx("em", {
1537
+ children: "No description"
1538
+ })
1539
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1540
+ size: 1,
1541
+ style: {
1542
+ whiteSpace: "pre-wrap"
1543
+ },
1544
+ children: definition.description
1545
+ })
1546
+ }) ]
206
1547
  })
207
1548
  }),
208
- /* @__PURE__ */ jsxRuntime.jsx(index.MetaRow, {
209
- label: "Description",
210
- children: definition.description === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
211
- muted: !0,
212
- size: 1,
213
- children: /* @__PURE__ */ jsxRuntime.jsx("em", {
214
- children: "No description"
1549
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
1550
+ direction: "column",
1551
+ flex: 1,
1552
+ style: {
1553
+ minHeight: 340
1554
+ },
1555
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.MetaRow, {
1556
+ fillHeight: !0,
1557
+ label: "Stages",
1558
+ children: /* @__PURE__ */ jsxRuntime.jsx(workflowDiagram.WorkflowDiagram, {
1559
+ definition: definition,
1560
+ explain: !0,
1561
+ fill: !0,
1562
+ height: "100%"
215
1563
  })
216
- }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
217
- size: 1,
218
- style: {
219
- whiteSpace: "pre-wrap"
220
- },
221
- children: definition.description
222
1564
  })
223
- }),
224
- /* @__PURE__ */ jsxRuntime.jsx(workflowDiagram.WorkflowDiagram, {
225
- definition: definition,
226
- explain: !0,
227
- height: 340
228
1565
  }) ]
229
1566
  });
230
1567
  }
231
1568
 
232
- function TabStatusRow({children: children}) {
233
- /* @__PURE__ */
234
- return jsxRuntime.jsx(ui.Box, {
235
- paddingLeft: 2,
236
- paddingY: 3,
237
- children: children
238
- });
239
- }
240
-
241
- const INSTANCE_CAP = 200, NEWEST_INSTANCES = {
242
- includeCompleted: !0,
243
- limit: INSTANCE_CAP
244
- };
245
-
246
- function useToolInstances() {
247
- const {engine: engine} = index.useWorkflowContext(), {instances: instances, loading: loading, unreadable: unreadable} = workflowStudio.useWorkflowInstances({
248
- engine: engine,
249
- filter: NEWEST_INSTANCES
250
- });
251
- return react.useMemo(() => {
252
- const rows = instances ?? [];
253
- return {
254
- inFlight: rows.filter(instance => workflowEngine.terminalState(instance) === "in-flight"),
255
- settled: rows.filter(instance => workflowEngine.terminalState(instance) !== "in-flight"),
256
- truncated: rows.length + unreadable.length === INSTANCE_CAP,
257
- loading: loading,
258
- unreadable: unreadable
259
- };
260
- }, [ instances, loading, unreadable ]);
261
- }
262
-
263
1569
  const MAX_VISIBLE_DOC_TYPES = 4, COLUMNS = {
264
1570
  definition: 6,
265
1571
  instances: 2,
@@ -276,7 +1582,12 @@ function Col({flex: flex, children: children}) {
276
1582
  }
277
1583
 
278
1584
  function DefinitionsTab({onOpenDefinition: onOpenDefinition}) {
279
- const {definitions: definitions, error: error} = useDeployedDefinitions(), {mappings: mappings} = index.useWorkflowContext(), instances = useToolInstances();
1585
+ const {definitions: definitions, error: error} = useDeployedDefinitions(), {mappingIssues: mappingIssues, mappings: mappings} = index.useWorkflowContext(), schema = sanity.useSchema(), instances = useToolInstances(), undeployed = /* @__PURE__ */ jsxRuntime.jsx(UndeployedWorkflows, {
1586
+ notices: undeployedWorkflowNotices({
1587
+ issues: mappingIssues,
1588
+ titleOf: name => schema.get(name)?.title
1589
+ })
1590
+ });
280
1591
  if (error !== void 0) /* @__PURE__ */
281
1592
  return jsxRuntime.jsx(TabStatusRow, {
282
1593
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
@@ -292,25 +1603,32 @@ function DefinitionsTab({onOpenDefinition: onOpenDefinition}) {
292
1603
  })
293
1604
  });
294
1605
  if (definitions.length === 0) /* @__PURE__ */
295
- return jsxRuntime.jsx(TabStatusRow, {
296
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
297
- muted: !0,
298
- size: 1,
299
- children: "No workflow definitions are deployed"
300
- })
1606
+ return jsxRuntime.jsxs(ui.Flex, {
1607
+ direction: "column",
1608
+ flex: 1,
1609
+ gap: 3,
1610
+ paddingBottom: 4,
1611
+ paddingTop: 3,
1612
+ children: [ undeployed,
1613
+ /* @__PURE__ */ jsxRuntime.jsx(index.EmptyState, {
1614
+ description: "Workflow definitions deployed to this project will appear here",
1615
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Transfer.TransferIcon, {}),
1616
+ title: "No definitions deployed"
1617
+ }) ]
301
1618
  });
302
1619
  const rows = definitionCatalog({
303
1620
  definitions: definitions,
304
1621
  mappings: mappings,
305
- inFlight: instances.inFlight
1622
+ inFlight: instances.inFlight,
1623
+ issues: mappingIssues
306
1624
  }), active = rows.reduce((sum, row) => sum + row.activeCount, 0);
307
1625
  /* @__PURE__ */
308
1626
  return jsxRuntime.jsxs(ui.Stack, {
309
1627
  gap: 3,
310
1628
  paddingBottom: 4,
311
- children: [
1629
+ paddingTop: 3,
1630
+ children: [ undeployed,
312
1631
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
313
- paddingTop: 3,
314
1632
  paddingX: 2,
315
1633
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
316
1634
  align: "center",
@@ -397,7 +1715,7 @@ function DefinitionsTab({onOpenDefinition: onOpenDefinition}) {
397
1715
  }, row.name))
398
1716
  }) ]
399
1717
  }), instances.truncated ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
400
- text: `Counts from the latest ${INSTANCE_CAP} workflows — older ones aren't counted.`
1718
+ text: INSTANCE_CAP_COUNT_NOTE
401
1719
  }) : null ]
402
1720
  });
403
1721
  }
@@ -406,88 +1724,277 @@ function activeSummary(active) {
406
1724
  return `${pluralize__default.default("active instance", active, !0)}`;
407
1725
  }
408
1726
 
1727
+ const ROW_ISSUES_HINT = "This definition has issues";
1728
+
1729
+ function UndeployedWorkflows({notices: notices}) {
1730
+ return notices.length === 0 ? null :
1731
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
1732
+ gap: 2,
1733
+ paddingBottom: 1,
1734
+ children: notices.map(notice => /* @__PURE__ */ jsxRuntime.jsx(index.CautionNote, {
1735
+ label: notice
1736
+ }, notice))
1737
+ });
1738
+ }
1739
+
409
1740
  function DefinitionRow({countPending: countPending, onOpen: onOpen, row: row}) {
410
- /* @__PURE__ */
411
- return jsxRuntime.jsx(ui.Card, {
1741
+ const flagged = row.brokenBindings.length > 0, card = /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
412
1742
  as: "button",
413
1743
  onClick: onOpen,
414
1744
  paddingX: 2,
415
1745
  paddingY: 3,
416
1746
  radius: 2,
417
1747
  style: {
418
- textAlign: "left"
1748
+ textAlign: "left",
1749
+ width: "100%"
419
1750
  },
1751
+ ...flagged ? {
1752
+ tone: "caution"
1753
+ } : {},
420
1754
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
421
1755
  align: "flex-start",
422
1756
  gap: 4,
423
1757
  children: [
424
1758
  /* @__PURE__ */ jsxRuntime.jsx(Col, {
425
1759
  flex: COLUMNS.definition,
426
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1760
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1761
+ align: "flex-start",
1762
+ gap: 3,
1763
+ paddingLeft: flagged ? 1 : 0,
1764
+ children: [ flagged ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1765
+ size: 1,
1766
+ children: /* @__PURE__ */ jsxRuntime.jsx(WarningOutline.WarningOutlineIcon, {})
1767
+ }) : null,
1768
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1769
+ flex: 1,
1770
+ gap: 3,
1771
+ style: {
1772
+ minWidth: 0
1773
+ },
1774
+ children: [
1775
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1776
+ size: 1,
1777
+ weight: "semibold",
1778
+ children: row.title
1779
+ }), row.description === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1780
+ muted: !0,
1781
+ size: 1,
1782
+ children: /* @__PURE__ */ jsxRuntime.jsx("em", {
1783
+ children: "No description"
1784
+ })
1785
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1786
+ muted: !0,
1787
+ size: 1,
1788
+ children: row.description
1789
+ }) ]
1790
+ }) ]
1791
+ })
1792
+ }),
1793
+ /* @__PURE__ */ jsxRuntime.jsx(Col, {
1794
+ flex: COLUMNS.instances,
1795
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1796
+ muted: !0,
1797
+ size: 1,
1798
+ children: countPending ? "—" : `${row.activeCount} active`
1799
+ })
1800
+ }),
1801
+ /* @__PURE__ */ jsxRuntime.jsx(Col, {
1802
+ flex: COLUMNS.docTypes,
1803
+ children: /* @__PURE__ */ jsxRuntime.jsx(DocTypeChips, {
1804
+ docTypes: row.docTypes
1805
+ })
1806
+ }) ]
1807
+ })
1808
+ });
1809
+ return flagged ? /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
1810
+ fill: !0,
1811
+ text: ROW_ISSUES_HINT,
1812
+ children: card
1813
+ }) : card;
1814
+ }
1815
+
1816
+ function DocTypeChips({docTypes: docTypes}) {
1817
+ const schema = sanity.useSchema(), badgeTrim = index.useBadgeCapTrim();
1818
+ if (docTypes.length === 0) /* @__PURE__ */
1819
+ return jsxRuntime.jsx(ui.Text, {
1820
+ muted: !0,
1821
+ size: 1,
1822
+ children: "—"
1823
+ });
1824
+ const labels = docTypeLabels(docTypes, name => schema.get(name)?.title), shown = labels.slice(0, MAX_VISIBLE_DOC_TYPES), overflow = labels.length - shown.length;
1825
+ /* @__PURE__ */
1826
+ return jsxRuntime.jsxs(ui.Flex, {
1827
+ align: "center",
1828
+ gap: 2,
1829
+ style: badgeTrim,
1830
+ wrap: "wrap",
1831
+ children: [ shown.map(docType =>
1832
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
1833
+ fontSize: 1,
1834
+ style: {
1835
+ minWidth: 0
1836
+ },
1837
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", {
1838
+ style: {
1839
+ display: "block",
1840
+ overflow: "hidden",
1841
+ textOverflow: "ellipsis",
1842
+ whiteSpace: "nowrap"
1843
+ },
1844
+ children: docType.title
1845
+ })
1846
+ }, docType.name)), overflow > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
1847
+ muted: !0,
1848
+ size: 1,
1849
+ children: [ "+", overflow, " more" ]
1850
+ }) : null ]
1851
+ });
1852
+ }
1853
+
1854
+ function normalizeAbortReason(raw) {
1855
+ const trimmed = raw.trim();
1856
+ return trimmed === "" ? void 0 : trimmed;
1857
+ }
1858
+
1859
+ function abortedToast(title) {
1860
+ return {
1861
+ id: index.TOAST_ID.abort,
1862
+ status: "info",
1863
+ title: `Aborted “${title}”`
1864
+ };
1865
+ }
1866
+
1867
+ function abortAlreadySettledToast(title) {
1868
+ return {
1869
+ id: index.TOAST_ID.abort,
1870
+ status: "info",
1871
+ title: `“${title}” had already finished`,
1872
+ description: "Nothing was aborted — the workflow reached a terminal state first."
1873
+ };
1874
+ }
1875
+
1876
+ function abortUnconfirmedToast(args) {
1877
+ return {
1878
+ id: index.TOAST_ID.abort,
1879
+ status: "warning",
1880
+ title: `Couldn’t confirm “${args.title}” was aborted`,
1881
+ description: `${index.describeError(args.err)} — the abort may still have committed, so check the workflow’s state before trying again.`
1882
+ };
1883
+ }
1884
+
1885
+ function AbortWorkflowDialog({entry: entry, onClose: onClose}) {
1886
+ const {engine: engine} = index.useWorkflowContext(), definition = index.useDefinition(entry), toast = index.useWorkflowToast(), [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}`, confirm = async () => {
1887
+ if (reason !== void 0) {
1888
+ setPending(!0);
1889
+ try {
1890
+ const {changed: changed} = await engine.abortInstance({
1891
+ instanceId: instance._id,
1892
+ reason: reason
1893
+ });
1894
+ toast.push(changed ? abortedToast(title) : abortAlreadySettledToast(title)), onClose();
1895
+ } catch (err) {
1896
+ toast.push(abortUnconfirmedToast({
1897
+ title: title,
1898
+ err: err
1899
+ })), setPending(!1);
1900
+ }
1901
+ }
1902
+ };
1903
+ /* @__PURE__ */
1904
+ return jsxRuntime.jsx(ui.Dialog, {
1905
+ footer: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1906
+ padding: 3,
1907
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1908
+ gap: 2,
1909
+ justify: "flex-end",
1910
+ children: [
1911
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1912
+ disabled: pending,
1913
+ fontSize: 1,
1914
+ mode: "bleed",
1915
+ onClick: onClose,
1916
+ padding: 2,
1917
+ text: "Cancel"
1918
+ }),
1919
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1920
+ disabled: reason === void 0,
1921
+ fontSize: 1,
1922
+ loading: pending,
1923
+ onClick: () => {
1924
+ confirm();
1925
+ },
1926
+ padding: 2,
1927
+ text: "Abort workflow",
1928
+ tone: "critical"
1929
+ }) ]
1930
+ })
1931
+ }),
1932
+ header: "Abort workflow",
1933
+ id: `abort-workflow-${instance._id}`,
1934
+ onClose: () => {
1935
+ pending || onClose();
1936
+ },
1937
+ width: 0,
1938
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1939
+ padding: 4,
1940
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1941
+ gap: 5,
1942
+ children: [
1943
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
1944
+ size: 1,
1945
+ children: [ "Abort the ",
1946
+ /* @__PURE__ */ jsxRuntime.jsx("strong", {
1947
+ children: title
1948
+ }), " workflow? This cannot be undone." ]
1949
+ }),
1950
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
427
1951
  gap: 3,
428
- style: {
429
- minWidth: 0
430
- },
431
1952
  children: [
432
1953
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1954
+ as: "label",
1955
+ htmlFor: reasonInputId,
433
1956
  size: 1,
434
- weight: "semibold",
435
- children: row.title
436
- }), row.description === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
437
- muted: !0,
438
- size: 1,
439
- children: /* @__PURE__ */ jsxRuntime.jsx("em", {
440
- children: "No description"
441
- })
442
- }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
443
- muted: !0,
444
- size: 1,
445
- children: row.description
1957
+ weight: "medium",
1958
+ children: "Reason"
1959
+ }),
1960
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, {
1961
+ autoFocus: !0,
1962
+ fontSize: 1,
1963
+ id: reasonInputId,
1964
+ onChange: event => setTypedReason(event.currentTarget.value),
1965
+ placeholder: "Why is this workflow being stopped?",
1966
+ value: typedReason
446
1967
  }) ]
447
- })
448
- }),
449
- /* @__PURE__ */ jsxRuntime.jsx(Col, {
450
- flex: COLUMNS.instances,
451
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
452
- muted: !0,
453
- size: 1,
454
- children: countPending ? "—" : `${row.activeCount} active`
455
- })
456
- }),
457
- /* @__PURE__ */ jsxRuntime.jsx(Col, {
458
- flex: COLUMNS.docTypes,
459
- children: /* @__PURE__ */ jsxRuntime.jsx(DocTypeChips, {
460
- docTypes: row.docTypes
461
- })
462
- }) ]
1968
+ }) ]
1969
+ })
463
1970
  })
464
1971
  });
465
1972
  }
466
1973
 
467
- function DocTypeChips({docTypes: docTypes}) {
468
- const schema = sanity.useSchema(), badgeTrim = index.useBadgeCapTrim();
469
- if (docTypes.length === 0) /* @__PURE__ */
470
- return jsxRuntime.jsx(ui.Text, {
471
- muted: !0,
472
- size: 1,
473
- children: "—"
474
- });
475
- const labels = docTypeLabels(docTypes, name => schema.get(name)?.title), shown = labels.slice(0, MAX_VISIBLE_DOC_TYPES), overflow = labels.length - shown.length;
476
- /* @__PURE__ */
477
- return jsxRuntime.jsxs(ui.Flex, {
478
- align: "center",
479
- gap: 2,
480
- style: badgeTrim,
481
- wrap: "wrap",
482
- children: [ shown.map(docType => /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
483
- fontSize: 1,
484
- children: docType.title
485
- }, docType.name)), overflow > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
486
- muted: !0,
1974
+ function AbortWorkflowSection({entry: entry}) {
1975
+ const [aborting, setAborting] = react.useState(!1);
1976
+ return index.isLiveEntry(entry) ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1977
+ gap: 4,
1978
+ children: [
1979
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
487
1980
  size: 1,
488
- children: [ "+", overflow, " more" ]
1981
+ weight: "semibold",
1982
+ children: "Manage workflow"
1983
+ }),
1984
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
1985
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1986
+ fontSize: 1,
1987
+ mode: "ghost",
1988
+ onClick: () => setAborting(!0),
1989
+ padding: 2,
1990
+ text: "Abort workflow…",
1991
+ tone: "critical"
1992
+ })
1993
+ }), aborting ? /* @__PURE__ */ jsxRuntime.jsx(AbortWorkflowDialog, {
1994
+ entry: entry,
1995
+ onClose: () => setAborting(!1)
489
1996
  }) : null ]
490
- });
1997
+ }) : null;
491
1998
  }
492
1999
 
493
2000
  function makeTitleResolver(definition) {
@@ -562,19 +2069,21 @@ function historyLine(h, titles) {
562
2069
  }
563
2070
 
564
2071
  function PendingEffectRows({instance: instance, now: now}) {
565
- const {drainEffectsFor: drainEffectsFor, completeEffectFor: completeEffectFor, effectHandlers: effectHandlers} = index.useWorkflowContext(), toast = index.useClosableToast(), [busy, setBusy] = react.useState(!1), pending = instance.pendingEffects ?? [];
2072
+ const {drainEffectsFor: drainEffectsFor, completeEffectFor: completeEffectFor, effectHandlers: effectHandlers} = index.useWorkflowContext(), toast = index.useWorkflowToast(), [busy, setBusy] = react.useState(!1), pending = instance.pendingEffects ?? [];
566
2073
  if (pending.length === 0) return null;
567
2074
  const runRegistered = async () => {
568
2075
  setBusy(!0);
569
2076
  try {
570
2077
  await drainEffectsFor(instance._id), toast.push({
571
- status: "success",
572
- title: "Ran registered effect handlers"
2078
+ id: index.TOAST_ID.effectsDrain,
2079
+ status: "info",
2080
+ title: "Ran the registered handlers"
573
2081
  });
574
2082
  } catch (err) {
575
2083
  toast.push({
2084
+ id: index.TOAST_ID.effectsDrain,
576
2085
  status: "error",
577
- title: "Drain failed",
2086
+ title: "Failed to run pending effects",
578
2087
  description: index.describeError(err)
579
2088
  });
580
2089
  } finally {
@@ -588,13 +2097,15 @@ function PendingEffectRows({instance: instance, now: now}) {
588
2097
  status: status,
589
2098
  detail: "Resolved in Studio"
590
2099
  }), toast.push({
591
- status: "success",
592
- title: `Effect marked ${status}`
2100
+ id: index.TOAST_ID.effectResolve,
2101
+ status: "info",
2102
+ title: `Effect marked as ${status}`
593
2103
  });
594
2104
  } catch (err) {
595
2105
  toast.push({
2106
+ id: index.TOAST_ID.effectResolve,
596
2107
  status: "error",
597
- title: "Couldn’t resolve effect",
2108
+ title: "Failed to resolve the effect",
598
2109
  description: index.describeError(err)
599
2110
  });
600
2111
  } finally {
@@ -824,13 +2335,7 @@ function FeedGlyph({hint: hint, icon: icon}) {
824
2335
  });
825
2336
  }
826
2337
 
827
- function documentRefsOf(instance) {
828
- const seen = /* @__PURE__ */ new Map;
829
- for (const ref of workflowEngine.entryDocRefs(instance.fields)) seen.has(ref.id) || seen.set(ref.id, ref);
830
- return [ ...seen.values() ];
831
- }
832
-
833
- function WorkflowInstanceDetail({instanceId: instanceId, onBack: onBack}) {
2338
+ function WorkflowInstanceDetail({instanceId: instanceId, backToList: backToList}) {
834
2339
  const entry = index.useWorkflowInstanceEntry(instanceId);
835
2340
  index.useLogEventOnMount(index.WorkflowInstanceDetailViewed, {
836
2341
  instanceId: instanceId
@@ -849,9 +2354,14 @@ function WorkflowInstanceDetail({instanceId: instanceId, onBack: onBack}) {
849
2354
  }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
850
2355
  paddingX: 3,
851
2356
  children: /* @__PURE__ */ jsxRuntime.jsx(TabStatusRow, {
852
- children: graceOver ? /* @__PURE__ */ jsxRuntime.jsx(NotFound, {
853
- id: instanceId,
854
- onBack: onBack
2357
+ children: graceOver ? /* @__PURE__ */ jsxRuntime.jsx(DetailNotFound, {
2358
+ back: backToList,
2359
+ message: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
2360
+ children: [ "Unable to find workflow with id “",
2361
+ /* @__PURE__ */ jsxRuntime.jsx("strong", {
2362
+ children: instanceId
2363
+ }), "”." ]
2364
+ })
855
2365
  }) : /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
856
2366
  label: "Loading workflow…"
857
2367
  })
@@ -859,29 +2369,6 @@ function WorkflowInstanceDetail({instanceId: instanceId, onBack: onBack}) {
859
2369
  });
860
2370
  }
861
2371
 
862
- function NotFound({id: id, onBack: onBack}) {
863
- /* @__PURE__ */
864
- return jsxRuntime.jsxs(ui.Stack, {
865
- gap: 4,
866
- children: [
867
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
868
- muted: !0,
869
- size: 1,
870
- children: [ "No workflow with id “", id, "” — it may have been deleted." ]
871
- }),
872
- /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
873
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
874
- fontSize: 1,
875
- icon: ArrowLeft.ArrowLeftIcon,
876
- mode: "ghost",
877
- onClick: onBack,
878
- padding: 2,
879
- text: "Back to workflows"
880
- })
881
- }) ]
882
- });
883
- }
884
-
885
2372
  function InstanceDetailPanel({entry: entry}) {
886
2373
  const definition = index.useDefinition(entry), snapshotWidth = index.useContainerToken(2), [openActivity, setOpenActivity] = react.useState(null), {instance: instance, ready: ready} = entry;
887
2374
  return react.useEffect(() => {
@@ -924,7 +2411,11 @@ function InstanceDetailPanel({entry: entry}) {
924
2411
  onOpenActivity: name => setOpenActivity({
925
2412
  stage: instance.currentStage,
926
2413
  activityName: name
927
- })
2414
+ }),
2415
+ surface: "tool-instance-detail"
2416
+ }),
2417
+ /* @__PURE__ */ jsxRuntime.jsx(AbortWorkflowSection, {
2418
+ entry: entry
928
2419
  }),
929
2420
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
930
2421
  gap: 4,
@@ -1028,217 +2519,76 @@ function DefinitionPickerPanel({onPick: onPick}) {
1028
2519
  overflowY: "auto"
1029
2520
  },
1030
2521
  children: visible.map(definition => /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
1031
- as: "button",
1032
- onClick: () => onPick(definition),
1033
- padding: 3,
1034
- radius: 2,
1035
- style: {
1036
- cursor: "pointer",
1037
- textAlign: "left"
1038
- },
1039
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1040
- gap: 2,
1041
- children: [
1042
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1043
- size: 1,
1044
- weight: "medium",
1045
- children: definition.title
1046
- }), definition.description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1047
- muted: !0,
1048
- size: 1,
1049
- textOverflow: "ellipsis",
1050
- children: definition.description
1051
- }) : null ]
1052
- })
1053
- }, definition.name))
1054
- }) ]
1055
- })
1056
- });
1057
- }
1058
-
1059
- function ToolActivityDialog({target: target, onClose: onClose}) {
1060
- const entry = index.useWorkflowInstanceEntry(target.instanceId), definition = index.useDefinition(entry), resolvedOnce = react.useRef(!1);
1061
- return entry && (resolvedOnce.current = !0), react.useEffect(() => {
1062
- resolvedOnce.current && index.openActivityGone({
1063
- entry: entry,
1064
- target: target
1065
- }) && onClose();
1066
- }, [ entry, target, onClose ]), entry?.invalid ? /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, {
1067
- header: "Workflow unavailable",
1068
- id: "workflow-activity-detail",
1069
- onClose: onClose,
1070
- width: 1,
1071
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1072
- padding: 4,
1073
- children: /* @__PURE__ */ jsxRuntime.jsx(index.InvalidDocNotice, {
1074
- invalid: entry.invalid
1075
- })
1076
- })
1077
- }) : entry?.evaluation ? /* @__PURE__ */ jsxRuntime.jsx(index.ActivityDetailDialog, {
1078
- activityName: target.activityName,
1079
- breadcrumb: index.instanceBreadcrumb(entry.instance, definition),
1080
- definition: definition,
1081
- document: documentRefsOf(entry.instance)[0],
1082
- entry: entry,
1083
- onClose: onClose,
1084
- source: "tool-task-list"
1085
- }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, {
1086
- header: "Loading activity…",
1087
- id: "workflow-activity-detail",
1088
- onClose: onClose,
1089
- width: 1,
1090
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1091
- padding: 4,
1092
- children: /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1093
- label: "Fetching the live workflow state…"
1094
- })
1095
- })
1096
- });
1097
- }
1098
-
1099
- const GROUP_DEFAULT_OPEN = !0;
1100
-
1101
- function instanceBandDefaultOpen(segment) {
1102
- return segment === "for-me";
1103
- }
1104
-
1105
- const UNGROUPED_BAND_KEY = "section:no-document", PAYLOAD_VERSION = 1, MAX_ENTRIES = 200;
1106
-
1107
- function bandStateStorageKey(scope) {
1108
- return `sanity.workflows.tool.bands:${scope}`;
1109
- }
1110
-
1111
- function isRecord(value) {
1112
- return typeof value == "object" && value !== null && !Array.isArray(value);
1113
- }
1114
-
1115
- function bandEntryOf(value) {
1116
- if (!isRecord(value)) return;
1117
- const {open: open, touched: touched} = value;
1118
- if (!(typeof open != "boolean" || typeof touched != "number") && Number.isFinite(touched)) return {
1119
- open: open,
1120
- touched: touched
1121
- };
1122
- }
1123
-
1124
- function parsePayload(raw) {
1125
- try {
1126
- const parsed = JSON.parse(raw);
1127
- if (!isRecord(parsed) || parsed.version !== PAYLOAD_VERSION || !isRecord(parsed.bands)) return;
1128
- const entries = [];
1129
- for (const [key, value] of Object.entries(parsed.bands)) {
1130
- const entry = bandEntryOf(value);
1131
- if (entry === void 0) return;
1132
- entries.push([ key, entry ]);
1133
- }
1134
- return Object.fromEntries(entries);
1135
- } catch {
1136
- return;
1137
- }
1138
- }
1139
-
1140
- function readStored(storage, storageKey) {
1141
- if (!storage) return {
1142
- entries: void 0,
1143
- corrupt: !1
1144
- };
1145
- let raw;
1146
- try {
1147
- raw = storage.getItem(storageKey);
1148
- } catch {
1149
- return {
1150
- entries: void 0,
1151
- corrupt: !1
1152
- };
1153
- }
1154
- if (raw === null) return {
1155
- entries: {},
1156
- corrupt: !1
1157
- };
1158
- const entries = parsePayload(raw);
1159
- return {
1160
- entries: entries,
1161
- corrupt: entries === void 0
1162
- };
1163
- }
1164
-
1165
- function writeStored(args) {
1166
- if (!args.storage) return !1;
1167
- try {
1168
- return args.storage.setItem(args.storageKey, JSON.stringify({
1169
- version: PAYLOAD_VERSION,
1170
- bands: args.entries
1171
- })), !0;
1172
- } catch {
1173
- return !1;
1174
- }
1175
- }
1176
-
1177
- function baseEntries(args) {
1178
- return args.stored === void 0 ? args.held : args.unpersisted ? mergeByTouched(args.stored, args.held) : args.stored;
1179
- }
1180
-
1181
- function mergeByTouched(stored, held) {
1182
- const merged = {
1183
- ...stored
1184
- };
1185
- for (const [key, entry] of Object.entries(held)) {
1186
- const persisted = merged[key];
1187
- (persisted === void 0 || entry.touched >= persisted.touched) && (merged[key] = entry);
1188
- }
1189
- return merged;
1190
- }
1191
-
1192
- function prune(entries) {
1193
- const held = Object.entries(entries);
1194
- return held.length <= MAX_ENTRIES ? entries : Object.fromEntries(held.sort(([, a], [, b]) => b.touched - a.touched).slice(0, MAX_ENTRIES));
1195
- }
1196
-
1197
- function togglesOf(entries) {
1198
- return new Map(Object.entries(entries).map(([key, entry]) => [ key, entry.open ]));
1199
- }
1200
-
1201
- function defaultStorage() {
1202
- try {
1203
- return globalThis.localStorage;
1204
- } catch {
1205
- return;
1206
- }
2522
+ as: "button",
2523
+ onClick: () => onPick(definition),
2524
+ padding: 3,
2525
+ radius: 2,
2526
+ style: {
2527
+ cursor: "pointer",
2528
+ textAlign: "left"
2529
+ },
2530
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
2531
+ gap: 2,
2532
+ children: [
2533
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2534
+ size: 1,
2535
+ weight: "medium",
2536
+ children: definition.title
2537
+ }), definition.description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2538
+ muted: !0,
2539
+ size: 1,
2540
+ textOverflow: "ellipsis",
2541
+ children: definition.description
2542
+ }) : null ]
2543
+ })
2544
+ }, definition.name))
2545
+ }) ]
2546
+ })
2547
+ });
1207
2548
  }
1208
2549
 
1209
- function createBandStateStore(args) {
1210
- const storage = args.storage ?? defaultStorage(), initial = readStored(storage, args.storageKey);
1211
- initial.corrupt && writeStored({
1212
- storage: storage,
1213
- storageKey: args.storageKey,
1214
- entries: {}
2550
+ function ToolActivityDialog({target: target, onClose: onClose}) {
2551
+ const entry = index.useWorkflowInstanceEntry(target.instanceId), definition = index.useDefinition(entry), resolvedOnce = react.useRef(!1);
2552
+ return entry && (resolvedOnce.current = !0), react.useEffect(() => {
2553
+ resolvedOnce.current && index.openActivityGone({
2554
+ entry: entry,
2555
+ target: target
2556
+ }) && onClose();
2557
+ }, [ entry, target, onClose ]), entry?.invalid ? /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, {
2558
+ header: "Workflow unavailable",
2559
+ id: "workflow-activity-detail",
2560
+ onClose: onClose,
2561
+ width: 1,
2562
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
2563
+ padding: 4,
2564
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.InvalidDocNotice, {
2565
+ invalid: entry.invalid
2566
+ })
2567
+ })
2568
+ }) : entry?.evaluation ? /* @__PURE__ */ jsxRuntime.jsx(index.ActivityDetailDialog, {
2569
+ activityName: target.activityName,
2570
+ breadcrumb: index.instanceBreadcrumb(entry.instance, definition),
2571
+ definition: definition,
2572
+ document: documentRefsOf(entry.instance)[0],
2573
+ entry: entry,
2574
+ onClose: onClose,
2575
+ source: "tool-task-list"
2576
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, {
2577
+ header: "Loading activity…",
2578
+ id: "workflow-activity-detail",
2579
+ onClose: onClose,
2580
+ width: 1,
2581
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
2582
+ padding: 4,
2583
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
2584
+ label: "Fetching the live workflow state…"
2585
+ })
2586
+ })
1215
2587
  });
1216
- let held = initial.entries ?? {}, unpersisted = !1;
1217
- return {
1218
- read: () => togglesOf(held),
1219
- setOpen(key, open) {
1220
- const stored = readStored(storage, args.storageKey).entries, base = baseEntries({
1221
- stored: stored,
1222
- held: held,
1223
- unpersisted: unpersisted
1224
- });
1225
- return held = prune({
1226
- ...base,
1227
- [key]: {
1228
- open: open,
1229
- touched: Date.now()
1230
- }
1231
- }), unpersisted = !writeStored({
1232
- storage: storage,
1233
- storageKey: args.storageKey,
1234
- entries: held
1235
- }), togglesOf(held);
1236
- }
1237
- };
1238
2588
  }
1239
2589
 
1240
2590
  function datesOf(state) {
1241
- return state.filter(entry => entry._type === "date" || entry._type === "datetime").map(entry => entry.value).filter(value => typeof value == "string" && value !== "");
2591
+ return state.filter(entry => index.isDateFieldKind(entry._type)).map(entry => entry.value).filter(value => typeof value == "string" && value !== "");
1242
2592
  }
1243
2593
 
1244
2594
  function spawnOrigins(instances) {
@@ -1339,10 +2689,7 @@ function deriveTaskGroups(args) {
1339
2689
  }
1340
2690
 
1341
2691
  function orderGroupsByPreviewTitle(groups, titles) {
1342
- const withDocuments = [ ...groups.withDocuments ].sort((a, b) => {
1343
- const aTitle = titles.get(a.document.id), bTitle = titles.get(b.document.id);
1344
- return aTitle !== void 0 && bTitle !== void 0 ? aTitle.localeCompare(bTitle) || a.key.localeCompare(b.key) : aTitle !== void 0 ? -1 : bTitle !== void 0 ? 1 : a.key.localeCompare(b.key);
1345
- });
2692
+ const withDocuments = [ ...groups.withDocuments ].sort(byResolvedTitle(group => titles.get(group.document.id), group => group.key));
1346
2693
  return {
1347
2694
  ...groups,
1348
2695
  withDocuments: withDocuments
@@ -1494,11 +2841,13 @@ function workflowTally(workflows) {
1494
2841
  function settleToast(outcome) {
1495
2842
  const total = outcome.settled + outcome.skipped.length + outcome.failures.length;
1496
2843
  if (outcome.skipped.length === 0 && outcome.failures.length === 0) return {
1497
- status: "success",
2844
+ id: index.TOAST_ID.orphanSettle,
2845
+ status: "info",
1498
2846
  title: outcome.settled === 1 ? "Cleaned up 1 workflow" : `Cleaned up ${outcome.settled} workflows`
1499
2847
  };
1500
2848
  const lines = [ ...outcome.skipped.map(workflow => `${workflow.title}: its document exists again — left running`), ...outcome.failures.map(failure => `${failure.workflow.title}: ${failure.message}`) ];
1501
2849
  return {
2850
+ id: index.TOAST_ID.orphanSettle,
1502
2851
  status: "warning",
1503
2852
  title: `Cleaned up ${outcome.settled} of ${total} workflows`,
1504
2853
  description: lines.join(`\n`)
@@ -1507,9 +2856,10 @@ function settleToast(outcome) {
1507
2856
 
1508
2857
  function recheckFailedToast(err) {
1509
2858
  return {
2859
+ id: index.TOAST_ID.orphanSettle,
1510
2860
  status: "error",
1511
- title: "Nothing was cleaned up — the documents could not be re-checked",
1512
- description: index.describeError(err)
2861
+ title: "Nothing was cleaned up",
2862
+ description: `The documents could not be re-checked: ${index.describeError(err)}`
1513
2863
  };
1514
2864
  }
1515
2865
 
@@ -1539,7 +2889,7 @@ function OrphanedWorkflowsNote({orphans: orphans}) {
1539
2889
  function SettleOrphansDialog({workflows: workflows, onClose: onClose}) {
1540
2890
  const {engine: engine} = index.useWorkflowContext(), client = sanity.useClient({
1541
2891
  apiVersion: index.WORKFLOW_API_VERSION
1542
- }), toast = index.useClosableToast(), [pending, setPending] = react.useState(!1), confirm = async () => {
2892
+ }), toast = index.useWorkflowToast(), [pending, setPending] = react.useState(!1), confirm = async () => {
1543
2893
  setPending(!0);
1544
2894
  try {
1545
2895
  const outcome = await settleOrphans({
@@ -1784,72 +3134,6 @@ function applyTaskFilters(args) {
1784
3134
  });
1785
3135
  }
1786
3136
 
1787
- const NOTHING = {
1788
- titles: /* @__PURE__ */ new Map,
1789
- resolved: /* @__PURE__ */ new Set
1790
- };
1791
-
1792
- function useDocPreviewTitles(documents) {
1793
- const previewStore = sanity.useDocumentPreviewStore(), schema = sanity.useSchema(), {perspectiveStack: perspectiveStack} = sanity.usePerspective(), {actualTypes: actualTypes, binding: binding} = index.useWorkflowContext(), [state, setState] = react.useState(NOTHING), key = documents.map(doc => doc.id).join("|");
1794
- return react.useEffect(() => {
1795
- const titles = /* @__PURE__ */ new Map, resolved = /* @__PURE__ */ new Set, previews = /* @__PURE__ */ new Map, publish = () => setState({
1796
- titles: new Map(titles),
1797
- resolved: new Set(resolved)
1798
- }), localDocs = documents.flatMap(doc => {
1799
- const {parsed: parsed, local: local} = index.gdrLocality(doc.id, binding.contentResource);
1800
- return !parsed || !local ? (resolved.add(doc.id), []) : [ {
1801
- doc: doc,
1802
- bareId: parsed.documentId
1803
- } ];
1804
- }), untracks = localDocs.map(({bareId: bareId}) => actualTypes.track(bareId)), dropPreview = docId => {
1805
- previews.get(docId)?.unsubscribe(), previews.delete(docId), titles.delete(docId);
1806
- }, observe = ({docId: docId, bareId: bareId, typeName: typeName}) => {
1807
- const schemaType = index.openableSchemaType(schema, typeName);
1808
- if (!schemaType) return !1;
1809
- if (previews.get(docId)?.typeName === typeName) return !0;
1810
- dropPreview(docId), resolved.delete(docId);
1811
- const subscription = previewStore.observeForPreview({
1812
- _id: bareId,
1813
- _type: schemaType.name
1814
- }, schemaType, {
1815
- perspective: perspectiveStack
1816
- }).subscribe({
1817
- next: event => {
1818
- resolved.add(docId);
1819
- const title = event.snapshot?.title;
1820
- typeof title == "string" ? titles.set(docId, title) : titles.delete(docId), publish();
1821
- },
1822
- error: err => {
1823
- console.error(`[workflow-studio-plugin] preview for "${docId}" failed:`, err), resolved.add(docId),
1824
- publish();
1825
- }
1826
- });
1827
- return previews.set(docId, {
1828
- typeName: typeName,
1829
- unsubscribe: () => subscription.unsubscribe()
1830
- }), !0;
1831
- }, evaluate = () => {
1832
- for (const {doc: doc, bareId: bareId} of localDocs) {
1833
- const probe = actualTypes.read(bareId);
1834
- if (probe === null) {
1835
- dropPreview(doc.id), resolved.add(doc.id);
1836
- continue;
1837
- }
1838
- !observe({
1839
- docId: doc.id,
1840
- bareId: bareId,
1841
- typeName: probe ?? doc.type
1842
- }) && typeof probe == "string" && (dropPreview(doc.id), resolved.add(doc.id));
1843
- }
1844
- publish();
1845
- }, unsubscribeProbes = actualTypes.subscribe(evaluate);
1846
- return evaluate(), () => {
1847
- unsubscribeProbes(), untracks.forEach(untrack => untrack()), previews.forEach(preview => preview.unsubscribe());
1848
- };
1849
- }, [ key, previewStore, schema, actualTypes, binding.contentResource.id, perspectiveStack.join("|") ]),
1850
- state;
1851
- }
1852
-
1853
3137
  const HITS_CAP = 6;
1854
3138
 
1855
3139
  function TaskFilterMenu({filters: filters, options: options, onChange: onChange}) {
@@ -2348,8 +3632,10 @@ function FilterRowPortal({filters: filters, onChange: onChange, options: options
2348
3632
  }), slot) : null;
2349
3633
  }
2350
3634
 
2351
- function ActivityDateControl({instanceId: instanceId, state: state, dates: dates}) {
2352
- const {editFieldFor: editFieldFor} = index.useWorkflowContext(), {save: save} = index.useSaveField(), [open, setOpen] = react.useState(!1), raw = index.dateControlValue({
3635
+ function ActivityDateControl({instanceId: instanceId, state: state, surface: surface, dates: dates}) {
3636
+ const editField = index.useEditField(surface), {save: save} = index.useSaveField({
3637
+ instanceId: instanceId
3638
+ }), [open, setOpen] = react.useState(!1), raw = index.dateControlValue({
2353
3639
  state: state,
2354
3640
  dates: dates
2355
3641
  }), display = raw === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
@@ -2372,15 +3658,16 @@ function ActivityDateControl({instanceId: instanceId, state: state, dates: dates
2372
3658
  })
2373
3659
  });
2374
3660
  const kind = index.dateControlKind(state), commit = (mode, value) => {
2375
- state.kind === "editable" && save(() => editFieldFor(instanceId, {
2376
- target: workflowReact.editFieldTarget(state.field),
3661
+ state.kind === "editable" && save(() => editField({
3662
+ instanceId: instanceId,
3663
+ field: state.field,
2377
3664
  ...mode === "set" ? {
2378
3665
  mode: mode,
2379
3666
  value: value
2380
3667
  } : {
2381
3668
  mode: mode
2382
3669
  }
2383
- }).then(() => {}));
3670
+ }));
2384
3671
  }, handleClick = e => {
2385
3672
  e.stopPropagation(), setOpen(v => !v);
2386
3673
  };
@@ -2397,9 +3684,9 @@ function ActivityDateControl({instanceId: instanceId, state: state, dates: dates
2397
3684
  setOpen(!1), commit("unset");
2398
3685
  },
2399
3686
  onPick: next => {
2400
- kind === "date" && setOpen(!1), commit("set", index.serializeDateFieldValue(next, kind));
3687
+ index.hasTimeOfDay(kind) || setOpen(!1), commit("set", index.serializeDateFieldValue(next, kind));
2401
3688
  },
2402
- selectTime: kind === "datetime",
3689
+ selectTime: index.hasTimeOfDay(kind),
2403
3690
  value: index.parseDateFieldValue(state.field.value, kind)
2404
3691
  })
2405
3692
  }),
@@ -2463,7 +3750,8 @@ function RefChips({refs: refs, max: max = 2}) {
2463
3750
  wrap: "wrap",
2464
3751
  children: [ shown.map(ref => /* @__PURE__ */ jsxRuntime.jsx(index.DocPreviewLink, {
2465
3752
  gdr: ref,
2466
- layout: "inline"
3753
+ layout: "inline",
3754
+ source: "doc-ref-chip"
2467
3755
  }, ref.id)), overflow > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
2468
3756
  muted: !0,
2469
3757
  size: 1,
@@ -2474,54 +3762,16 @@ function RefChips({refs: refs, max: max = 2}) {
2474
3762
 
2475
3763
  const UNGROUPED_TITLE = "Workflows without Studio documents";
2476
3764
 
2477
- function TaskGroupList({bands: bands, groups: groups, instanceDefaultOpen: instanceDefaultOpen, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
3765
+ function TaskGroupList({groups: groups, ...wiring}) {
2478
3766
  /* @__PURE__ */
2479
3767
  return jsxRuntime.jsxs(ui.Stack, {
2480
3768
  gap: 1,
2481
- children: [ groups.withDocuments.map(group => /* @__PURE__ */ jsxRuntime.jsx(index.CollapsibleBand, {
2482
- background: !0,
2483
- onToggle: () => bands.toggle(group.key, GROUP_DEFAULT_OPEN),
2484
- open: bands.isOpen(group.key, GROUP_DEFAULT_OPEN),
2485
- sticky: !0,
2486
- title: workflowEngine.toBareId(group.document.id),
2487
- header: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
2488
- align: "center",
2489
- flex: "none",
2490
- style: {
2491
- maxWidth: "60%"
2492
- },
2493
- children: /* @__PURE__ */ jsxRuntime.jsx(RefChips, {
2494
- max: 2,
2495
- refs: group.documents
2496
- })
2497
- }),
2498
- children: /* @__PURE__ */ jsxRuntime.jsx(InstanceBands, {
2499
- bands: bands,
2500
- instanceDefaultOpen: instanceDefaultOpen,
2501
- instances: group.instances,
2502
- onOpenTask: onOpenTask,
2503
- onOpenWorkflow: onOpenWorkflow,
2504
- showTerminalActions: showTerminalActions
2505
- })
2506
- }, group.key)), groups.withoutDocuments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(index.CollapsibleBand, {
2507
- background: !0,
2508
- onToggle: () => bands.toggle(UNGROUPED_BAND_KEY, GROUP_DEFAULT_OPEN),
2509
- open: bands.isOpen(UNGROUPED_BAND_KEY, GROUP_DEFAULT_OPEN),
2510
- sticky: !0,
2511
- title: UNGROUPED_TITLE,
2512
- header: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2513
- size: 1,
2514
- weight: "semibold",
2515
- children: UNGROUPED_TITLE
2516
- }),
2517
- children: /* @__PURE__ */ jsxRuntime.jsx(InstanceBands, {
2518
- bands: bands,
2519
- instanceDefaultOpen: instanceDefaultOpen,
2520
- instances: groups.withoutDocuments,
2521
- onOpenTask: onOpenTask,
2522
- onOpenWorkflow: onOpenWorkflow,
2523
- showTerminalActions: showTerminalActions
2524
- })
3769
+ children: [ groups.withDocuments.map(group => /* @__PURE__ */ jsxRuntime.jsx(DocumentBand, {
3770
+ group: group,
3771
+ ...wiring
3772
+ }, group.key)), groups.withoutDocuments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(UngroupedBand, {
3773
+ instances: groups.withoutDocuments,
3774
+ ...wiring
2525
3775
  }) : null ]
2526
3776
  });
2527
3777
  }
@@ -2534,38 +3784,81 @@ function targetOf(row) {
2534
3784
  };
2535
3785
  }
2536
3786
 
2537
- function InstanceBands({bands: bands, instanceDefaultOpen: instanceDefaultOpen, instances: instances, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
3787
+ function DocumentBand({group: group, ...wiring}) {
3788
+ const {open: open, toggle: toggle} = useBandOpen(wiring.bands, group.key);
3789
+ /* @__PURE__ */
3790
+ return jsxRuntime.jsx(index.CollapsibleBand, {
3791
+ background: !0,
3792
+ onToggle: toggle,
3793
+ open: open,
3794
+ sticky: !0,
3795
+ title: workflowEngine.toBareId(group.document.id),
3796
+ header: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
3797
+ align: "center",
3798
+ flex: "none",
3799
+ style: {
3800
+ maxWidth: "60%"
3801
+ },
3802
+ children: /* @__PURE__ */ jsxRuntime.jsx(RefChips, {
3803
+ max: 2,
3804
+ refs: group.documents
3805
+ })
3806
+ }),
3807
+ children: /* @__PURE__ */ jsxRuntime.jsx(InstanceBands, {
3808
+ instances: group.instances,
3809
+ ...wiring
3810
+ })
3811
+ });
3812
+ }
3813
+
3814
+ function UngroupedBand({instances: instances, ...wiring}) {
3815
+ const {open: open, toggle: toggle} = useBandOpen(wiring.bands, UNGROUPED_BAND_KEY);
3816
+ /* @__PURE__ */
3817
+ return jsxRuntime.jsx(index.CollapsibleBand, {
3818
+ background: !0,
3819
+ onToggle: toggle,
3820
+ open: open,
3821
+ sticky: !0,
3822
+ title: UNGROUPED_TITLE,
3823
+ header: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
3824
+ size: 1,
3825
+ weight: "semibold",
3826
+ children: UNGROUPED_TITLE
3827
+ }),
3828
+ children: /* @__PURE__ */ jsxRuntime.jsx(InstanceBands, {
3829
+ instances: instances,
3830
+ ...wiring
3831
+ })
3832
+ });
3833
+ }
3834
+
3835
+ function InstanceBands({instances: instances, ...wiring}) {
2538
3836
  /* @__PURE__ */
2539
3837
  return jsxRuntime.jsx(ui.Stack, {
2540
3838
  gap: 1,
2541
- children: instances.map(band => /* @__PURE__ */ jsxRuntime.jsx(InstanceBand, {
2542
- band: band,
2543
- bands: bands,
2544
- instanceDefaultOpen: instanceDefaultOpen,
2545
- onOpenTask: onOpenTask,
2546
- onOpenWorkflow: onOpenWorkflow,
2547
- showTerminalActions: showTerminalActions
2548
- }, band.instanceId))
3839
+ children: instances.map(instance => /* @__PURE__ */ jsxRuntime.jsx(InstanceBand, {
3840
+ instance: instance,
3841
+ ...wiring
3842
+ }, instance.instanceId))
2549
3843
  });
2550
3844
  }
2551
3845
 
2552
- function InstanceBand({band: band, bands: bands, instanceDefaultOpen: instanceDefaultOpen, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
2553
- const {resolvePathFromState: resolvePathFromState} = router.useRouter();
3846
+ function InstanceBand({bands: bands, instance: instance, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
3847
+ const {open: open, toggle: toggle} = useBandOpen(bands, instance.instanceId), {resolvePathFromState: resolvePathFromState} = router.useRouter();
2554
3848
  /* @__PURE__ */
2555
3849
  return jsxRuntime.jsx(index.CollapsibleBand, {
2556
- onToggle: () => bands.toggle(band.instanceId, instanceDefaultOpen),
2557
- open: bands.isOpen(band.instanceId, instanceDefaultOpen),
2558
- title: band.title,
3850
+ onToggle: toggle,
3851
+ open: open,
3852
+ title: instance.title,
2559
3853
  header: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
2560
3854
  children: [
2561
3855
  /* @__PURE__ */ jsxRuntime.jsx(index.LinkChip, {
2562
3856
  hint: "View workflow",
2563
3857
  href: resolvePathFromState({
2564
- instanceId: band.instanceId
3858
+ instanceId: instance.instanceId
2565
3859
  }),
2566
3860
  onClick: event => {
2567
- event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || (event.preventDefault(),
2568
- onOpenWorkflow(band.instanceId));
3861
+ isPlainClick(event) && (event.preventDefault(), onOpenWorkflow(instance.instanceId));
2569
3862
  },
2570
3863
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
2571
3864
  align: "center",
@@ -2581,12 +3874,12 @@ function InstanceBand({band: band, bands: bands, instanceDefaultOpen: instanceDe
2581
3874
  },
2582
3875
  textOverflow: "ellipsis",
2583
3876
  weight: "medium",
2584
- children: band.title
2585
- }), band.breadcrumb === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(index.BreadcrumbTail, {
2586
- segments: [ band.breadcrumb ]
3877
+ children: instance.title
3878
+ }), instance.breadcrumb === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(index.BreadcrumbTail, {
3879
+ segments: [ instance.breadcrumb ]
2587
3880
  }),
2588
3881
  /* @__PURE__ */ jsxRuntime.jsx(index.StageFace, {
2589
- title: band.stageTitle
3882
+ title: instance.stageTitle
2590
3883
  }) ]
2591
3884
  })
2592
3885
  }),
@@ -2596,7 +3889,7 @@ function InstanceBand({band: band, bands: bands, instanceDefaultOpen: instanceDe
2596
3889
  paddingLeft: 5,
2597
3890
  children: /* @__PURE__ */ jsxRuntime.jsx(TaskRows, {
2598
3891
  onOpenTask: onOpenTask,
2599
- rows: band.rows,
3892
+ rows: instance.rows,
2600
3893
  showTerminalActions: showTerminalActions
2601
3894
  })
2602
3895
  })
@@ -2630,40 +3923,19 @@ function TaskActivityRow({row: row, onOpen: onOpen, showTerminalActions: showTer
2630
3923
  dateControl: /* @__PURE__ */ jsxRuntime.jsx(ActivityDateControl, {
2631
3924
  dates: render.dates,
2632
3925
  instanceId: row.instanceId,
2633
- state: render.dateState
3926
+ state: render.dateState,
3927
+ surface: "tool-task-list"
2634
3928
  }),
2635
3929
  face: render.face,
2636
3930
  instanceId: row.instanceId,
2637
3931
  onOpen: onOpen,
3932
+ surface: "tool-task-list",
2638
3933
  terminalActions: showTerminalActions ? render.terminalActions : []
2639
3934
  })
2640
3935
  })
2641
3936
  });
2642
3937
  }
2643
3938
 
2644
- function useBandOpenState(args) {
2645
- const {segment: segment, scope: scope} = args, store = react.useMemo(() => createBandStateStore({
2646
- storageKey: bandStateStorageKey(scope)
2647
- }), [ scope ]), [held, setHeld] = react.useState(() => ({
2648
- store: store,
2649
- toggles: store.read()
2650
- }));
2651
- held.store !== store && setHeld({
2652
- store: store,
2653
- toggles: store.read()
2654
- });
2655
- const qualify = key => `${segment}/${key}`, isOpen = (key, defaultOpen) => held.toggles.get(qualify(key)) ?? defaultOpen;
2656
- return {
2657
- isOpen: isOpen,
2658
- toggle: (key, defaultOpen) => {
2659
- setHeld({
2660
- store: store,
2661
- toggles: store.setOpen(qualify(key), !isOpen(key, defaultOpen))
2662
- });
2663
- }
2664
- };
2665
- }
2666
-
2667
3939
  const NO_MISSING = /* @__PURE__ */ new Set;
2668
3940
 
2669
3941
  function sameIdSet(a, b) {
@@ -2703,15 +3975,18 @@ function TasksTab({filterSlot: filterSlot, instances: instances, loading: loadin
2703
3975
  }), [filters, setFilters] = react.useState({}), groups = react.useMemo(() => deriveTaskGroups({
2704
3976
  instances: instances,
2705
3977
  identity: identity
2706
- }), [ instances, identity ]), orphans = useOrphanedWorkflows(groups), listed = react.useMemo(() => hideOrphanedGroups(groups, orphans.groupKeys), [ groups, orphans.groupKeys ]), options = react.useMemo(() => taskFilterOptions(listed), [ listed ]), leadDocuments = react.useMemo(() => listed.withDocuments.map(group => group.document), [ listed ]), previewTitles = useDocPreviewTitles(leadDocuments), visible = react.useMemo(() => {
3978
+ }), [ instances, identity ]), orphans = useOrphanedWorkflows(groups), listed = react.useMemo(() => hideOrphanedGroups(groups, orphans.groupKeys), [ groups, orphans.groupKeys ]), options = react.useMemo(() => taskFilterOptions(listed), [ listed ]), titles = useGroupTitles(listed), revealed = useRevealGate({
3979
+ loading: loading,
3980
+ ready: titles.ready
3981
+ }), visible = react.useMemo(() => {
2707
3982
  const filtered = applyTaskFilters({
2708
3983
  groups: listed,
2709
3984
  filters: filters,
2710
3985
  now: /* @__PURE__ */ new Date
2711
3986
  }), cut = mineOnly ? onlyMyTasks(filtered) : filtered;
2712
- return orderGroupsByPreviewTitle(cut, previewTitles.titles);
2713
- }, [ listed, filters, mineOnly, previewTitles ]);
2714
- if (loading) /* @__PURE__ */
3987
+ return orderGroupsByPreviewTitle(cut, titles.titles);
3988
+ }, [ listed, filters, mineOnly, titles ]);
3989
+ if (loading || !revealed) /* @__PURE__ */
2715
3990
  return jsxRuntime.jsx(TabStatusRow, {
2716
3991
  children: /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
2717
3992
  label: "Loading tasks…"
@@ -2719,16 +3994,13 @@ function TasksTab({filterSlot: filterSlot, instances: instances, loading: loadin
2719
3994
  });
2720
3995
  const empty = visible.withDocuments.length === 0 && visible.withoutDocuments.length === 0;
2721
3996
  let body;
2722
- return empty ? mineOnly ? body = /* @__PURE__ */ jsxRuntime.jsx(index.ForMeEmptyState, {}) : body = /* @__PURE__ */ jsxRuntime.jsx(TabStatusRow, {
2723
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2724
- muted: !0,
2725
- size: 1,
2726
- children: "No tasks match — adjust the filters, or start a workflow"
2727
- })
3997
+ return empty ? mineOnly ? body = /* @__PURE__ */ jsxRuntime.jsx(index.ForMeEmptyState, {}) : body = /* @__PURE__ */ jsxRuntime.jsx(index.EmptyState, {
3998
+ description: "Adjust the filters, or start a workflow",
3999
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Search.SearchIcon, {}),
4000
+ title: "No tasks match"
2728
4001
  }) : body = /* @__PURE__ */ jsxRuntime.jsx(TaskGroupList, {
2729
4002
  bands: bands,
2730
4003
  groups: visible,
2731
- instanceDefaultOpen: instanceBandDefaultOpen(segment),
2732
4004
  onOpenTask: onOpenTask,
2733
4005
  onOpenWorkflow: onOpenWorkflow,
2734
4006
  showTerminalActions: mineOnly
@@ -2754,52 +4026,106 @@ function TasksTab({filterSlot: filterSlot, instances: instances, loading: loadin
2754
4026
  /* @__PURE__ */ jsxRuntime.jsx(OrphanedWorkflowsNote, {
2755
4027
  orphans: orphans
2756
4028
  }), body, truncated ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
2757
- text: `Tasks from the latest ${INSTANCE_CAP} workflows — older ones aren't listed.`
4029
+ text: `Tasks from the latest ${INSTANCE_CAP} workflows — older ones arent listed.`
2758
4030
  }) : null ]
2759
4031
  }) ]
2760
4032
  });
2761
4033
  }
2762
4034
 
2763
- const TOOL_TABS = [ {
2764
- value: "overview",
2765
- label: "Overview"
2766
- }, {
2767
- value: "definitions",
2768
- label: "Definitions"
2769
- }, {
2770
- value: "for-me",
2771
- label: "For me"
2772
- } ], TOOL_TAB_VALUES = TOOL_TABS.map(tab => tab.value);
4035
+ const TOOL_TAB_LABELS = {
4036
+ definitions: "Definitions",
4037
+ documents: "Documents",
4038
+ tasks: "Tasks",
4039
+ "for-me": "For me"
4040
+ }, TOOL_TAB_VALUES = Object.keys(TOOL_TAB_LABELS), TOOL_TABS = TOOL_TAB_VALUES.map(value => ({
4041
+ value: value,
4042
+ label: TOOL_TAB_LABELS[value]
4043
+ })), DEFAULT_TOOL_TAB = "definitions";
4044
+
4045
+ function toolTabState(tab) {
4046
+ return tab === DEFAULT_TOOL_TAB ? {} : {
4047
+ workflowsTab: tab
4048
+ };
4049
+ }
4050
+
4051
+ function boardState(definitionName) {
4052
+ return {
4053
+ boardDefinition: definitionName
4054
+ };
4055
+ }
4056
+
4057
+ function tabSelectionNavigates(route, tab) {
4058
+ return !(route.kind === "home" && tab === route.tab);
4059
+ }
4060
+
4061
+ function backToAllLabel(tab) {
4062
+ return `Back to all ${TOOL_TAB_LABELS[tab].toLowerCase()}`;
4063
+ }
4064
+
4065
+ function toolRoute(state) {
4066
+ const {instanceId: instanceId, definitionName: definitionName, boardDefinition: boardDefinition} = state;
4067
+ if (typeof instanceId == "string") return {
4068
+ kind: "instance",
4069
+ instanceId: instanceId,
4070
+ tab: "tasks"
4071
+ };
4072
+ if (typeof definitionName == "string") return {
4073
+ kind: "definition",
4074
+ definitionName: definitionName,
4075
+ tab: "definitions"
4076
+ };
4077
+ if (typeof boardDefinition == "string") return {
4078
+ kind: "home",
4079
+ tab: "documents",
4080
+ boardDefinition: boardDefinition
4081
+ };
4082
+ const tab = index.pathSegmentTab({
4083
+ segment: state.workflowsTab,
4084
+ tabs: TOOL_TAB_VALUES,
4085
+ fallback: DEFAULT_TOOL_TAB
4086
+ });
4087
+ return tab === void 0 ? {
4088
+ kind: "not-found",
4089
+ tab: void 0
4090
+ } : {
4091
+ kind: "home",
4092
+ tab: tab,
4093
+ boardDefinition: void 0
4094
+ };
4095
+ }
2773
4096
 
2774
4097
  function InstanceStreamKeepAlive() {
2775
4098
  return useToolInstances(), null;
2776
4099
  }
2777
4100
 
2778
4101
  function WorkflowsToolRoot() {
2779
- const router$1 = router.useRouter(), {seedInstance: seedInstance} = index.useWorkflowContext(), telemetry = workflowReact.useWorkflowTelemetry(), [filterSlot, setFilterSlot] = react.useState(null), goHome = () => router$1.navigate({}), instanceId = typeof router$1.state.instanceId == "string" ? router$1.state.instanceId : void 0, definitionName = typeof router$1.state.definitionName == "string" ? router$1.state.definitionName : void 0, isHome = instanceId === void 0 && definitionName === void 0, [instanceStreamLatched, setInstanceStreamLatched] = react.useState(isHome);
4102
+ const router$1 = router.useRouter(), {seedInstance: seedInstance} = index.useWorkflowContext(), telemetry = workflowReact.useWorkflowTelemetry(), [filterSlot, setFilterSlot] = react.useState(null), route = toolRoute(router$1.state), isHome = route.kind === "home" || route.kind === "not-found", [instanceStreamLatched, setInstanceStreamLatched] = react.useState(isHome);
2780
4103
  react.useEffect(() => {
2781
4104
  isHome && setInstanceStreamLatched(!0);
2782
4105
  }, [ isHome ]);
2783
- let tab;
2784
- instanceId !== void 0 ? tab = "overview" : definitionName !== void 0 ? tab = "definitions" : tab = index.pathSegmentTab({
2785
- segment: router$1.state.workflowsTab,
2786
- tabs: TOOL_TAB_VALUES,
2787
- fallback: "overview"
2788
- });
2789
- const setTab = next => router$1.navigate(next === "overview" ? {} : {
2790
- workflowsTab: next
2791
- });
2792
- index.useLogEventOnMount(index.WorkflowToolOpened, {
2793
- tab: tab,
4106
+ const setTab = next => router$1.navigate(toolTabState(next)), selectBoardWorkflow = react.useCallback((name, opts) => router$1.navigate(boardState(name), opts?.replace === !0 ? {
4107
+ replace: !0
4108
+ } : {}), [ router$1 ]), backToList = tab => ({
4109
+ label: backToAllLabel(tab),
4110
+ go: () => setTab(tab)
4111
+ }), badSegment = route.kind === "not-found";
4112
+ react.useEffect(() => {
4113
+ badSegment && router$1.navigate({}, {
4114
+ replace: !0
4115
+ });
4116
+ }, [ badSegment, router$1 ]), index.useLogEventOnMount(index.WorkflowToolOpened, {
4117
+ tab: route.tab ?? "not-found",
2794
4118
  via: "open"
2795
4119
  });
2796
4120
  let panel;
2797
- return instanceId !== void 0 ? panel = /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceDetail, {
2798
- instanceId: instanceId,
2799
- onBack: goHome
2800
- }, instanceId) : definitionName !== void 0 ? panel = /* @__PURE__ */ jsxRuntime.jsx(DefinitionDetail, {
2801
- definitionName: definitionName
2802
- }, definitionName) : panel = /* @__PURE__ */ jsxRuntime.jsx(WorkflowsHome, {
4121
+ return route.kind === "instance" ? panel = /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceDetail, {
4122
+ instanceId: route.instanceId,
4123
+ backToList: backToList(route.tab)
4124
+ }, route.instanceId) : route.kind === "definition" ? panel = /* @__PURE__ */ jsxRuntime.jsx(DefinitionDetail, {
4125
+ definitionName: route.definitionName,
4126
+ backToList: backToList(route.tab)
4127
+ }, route.definitionName) : route.kind === "not-found" ? panel = null : panel = /* @__PURE__ */ jsxRuntime.jsx(WorkflowsHome, {
4128
+ boardDefinition: route.boardDefinition,
2803
4129
  filterSlot: filterSlot,
2804
4130
  onOpenDefinition: name => router$1.navigate({
2805
4131
  definitionName: name
@@ -2809,7 +4135,8 @@ function WorkflowsToolRoot() {
2809
4135
  instanceId: typeof instance == "string" ? instance : instance._id
2810
4136
  });
2811
4137
  },
2812
- tab: tab
4138
+ onSelectWorkflow: selectBoardWorkflow,
4139
+ tab: route.tab
2813
4140
  }),
2814
4141
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Card, {
2815
4142
  height: "fill",
@@ -2844,13 +4171,13 @@ function WorkflowsToolRoot() {
2844
4171
  ariaControls: "workflows-tool-panel",
2845
4172
  idPrefix: "workflows-tool-tab",
2846
4173
  onSelect: next => {
2847
- next !== tab && telemetry.log(index.WorkflowToolOpened, {
4174
+ tabSelectionNavigates(route, next) && (next !== route.tab && telemetry.log(index.WorkflowToolOpened, {
2848
4175
  tab: next,
2849
4176
  via: "tab-switch"
2850
- }), setTab(next);
4177
+ }), setTab(next));
2851
4178
  },
2852
4179
  options: TOOL_TABS,
2853
- selected: tab
4180
+ selected: route.tab
2854
4181
  }),
2855
4182
  /* @__PURE__ */ jsxRuntime.jsx("div", {
2856
4183
  ref: setFilterSlot
@@ -2866,17 +4193,20 @@ function WorkflowsToolRoot() {
2866
4193
  });
2867
4194
  }
2868
4195
 
2869
- function WorkflowsHome({filterSlot: filterSlot, onOpenDefinition: onOpenDefinition, onOpenInstance: onOpenInstance, tab: tab}) {
4196
+ function WorkflowsHome({boardDefinition: boardDefinition, filterSlot: filterSlot, onOpenDefinition: onOpenDefinition, onOpenInstance: onOpenInstance, onSelectWorkflow: onSelectWorkflow, tab: tab}) {
2870
4197
  const instances = useToolInstances(), identity = index.useAssignmentIdentity(), [openTask, setOpenTask] = react.useState(null), openInstance = target => {
2871
4198
  if (typeof target != "string") return onOpenInstance(target);
2872
4199
  const held = [ ...instances.inFlight, ...instances.settled ].find(i => i._id === target);
2873
4200
  onOpenInstance(held ?? target);
2874
4201
  }, scrollerRef = react.useRef(null);
2875
- return react.useEffect(() => {
4202
+ react.useEffect(() => {
2876
4203
  scrollerRef.current?.scrollTo({
2877
4204
  top: 0
2878
4205
  });
2879
- }, [ tab ]), /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
4206
+ }, [ tab ]);
4207
+ const bleeds = tab === "documents";
4208
+ /* @__PURE__ */
4209
+ return jsxRuntime.jsxs(ui.Flex, {
2880
4210
  direction: "column",
2881
4211
  height: "fill",
2882
4212
  overflow: "hidden",
@@ -2885,9 +4215,9 @@ function WorkflowsHome({filterSlot: filterSlot, onOpenDefinition: onOpenDefiniti
2885
4215
  ref: scrollerRef,
2886
4216
  flex: 1,
2887
4217
  overflow: "auto",
2888
- paddingX: 3,
4218
+ paddingX: bleeds ? 0 : TOOL_PANEL_PADDING,
2889
4219
  style: {
2890
- scrollbarGutter: "stable"
4220
+ scrollbarGutter: bleeds ? "auto" : "stable"
2891
4221
  },
2892
4222
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.TabPanel, {
2893
4223
  "aria-labelledby": `workflows-tool-tab-${tab}`,
@@ -2900,18 +4230,19 @@ function WorkflowsHome({filterSlot: filterSlot, onOpenDefinition: onOpenDefiniti
2900
4230
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
2901
4231
  direction: "column",
2902
4232
  flex: 1,
2903
- children: tab === "definitions" ? /* @__PURE__ */ jsxRuntime.jsx(DefinitionsTab, {
2904
- onOpenDefinition: onOpenDefinition
2905
- }) : /* @__PURE__ */ jsxRuntime.jsx(TasksTab, {
4233
+ style: {
4234
+ minHeight: 0
4235
+ },
4236
+ children: /* @__PURE__ */ jsxRuntime.jsx(HomePanel, {
4237
+ boardDefinition: boardDefinition,
2906
4238
  filterSlot: filterSlot,
2907
4239
  identity: identity,
2908
- instances: [ ...instances.inFlight, ...instances.settled ],
2909
- loading: instances.loading,
4240
+ instances: instances,
4241
+ onOpenDefinition: onOpenDefinition,
2910
4242
  onOpenTask: setOpenTask,
2911
4243
  onOpenWorkflow: openInstance,
2912
- segment: tab,
2913
- truncated: instances.truncated,
2914
- unreadable: instances.unreadable
4244
+ onSelectWorkflow: onSelectWorkflow,
4245
+ tab: tab
2915
4246
  })
2916
4247
  })
2917
4248
  })
@@ -2922,4 +4253,25 @@ function WorkflowsHome({filterSlot: filterSlot, onOpenDefinition: onOpenDefiniti
2922
4253
  });
2923
4254
  }
2924
4255
 
4256
+ function HomePanel({boardDefinition: boardDefinition, filterSlot: filterSlot, identity: identity, instances: instances, onOpenDefinition: onOpenDefinition, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, onSelectWorkflow: onSelectWorkflow, tab: tab}) {
4257
+ return tab === "definitions" ? /* @__PURE__ */ jsxRuntime.jsx(DefinitionsTab, {
4258
+ onOpenDefinition: onOpenDefinition
4259
+ }) : tab === "documents" ? /* @__PURE__ */ jsxRuntime.jsx(DocumentsTab, {
4260
+ filterSlot: filterSlot,
4261
+ onOpenDefinition: onOpenDefinition,
4262
+ onSelectWorkflow: onSelectWorkflow,
4263
+ routeDefinition: boardDefinition
4264
+ }) : /* @__PURE__ */ jsxRuntime.jsx(TasksTab, {
4265
+ filterSlot: filterSlot,
4266
+ identity: identity,
4267
+ instances: [ ...instances.inFlight, ...instances.settled ],
4268
+ loading: instances.loading,
4269
+ onOpenTask: onOpenTask,
4270
+ onOpenWorkflow: onOpenWorkflow,
4271
+ segment: tab,
4272
+ truncated: instances.truncated,
4273
+ unreadable: instances.unreadable
4274
+ });
4275
+ }
4276
+
2925
4277
  exports.default = WorkflowsToolRoot;