@sanity/workflow-studio-plugin 0.22.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,26 +1,52 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
 
3
- import { Stack, Box, Flex, Text, Button, Card, TextInput, Dialog, Badge, useTheme_v2, Container, TabPanel } from "@sanity/ui";
3
+ import { Box, Text, Card, Flex, Stack, MenuButton, Menu, MenuItem, Button, Badge, Dialog, TextInput, useTheme_v2, TabPanel } from "@sanity/ui";
4
4
 
5
- import { useWorkflowTelemetry, editFieldTarget } from "@sanity/workflow-react";
5
+ import { useWorkflowTelemetry } from "@sanity/workflow-react";
6
6
 
7
- import { useState, useEffect, useMemo, useRef } from "react";
7
+ import { useMemo, useSyncExternalStore, useCallback, useState, useEffect, useRef } from "react";
8
8
 
9
9
  import { useRouter } from "sanity/router";
10
10
 
11
- import { instanceNotice, StaleLock, ActivitiesList, TodoItemsList, isEvaluationStale, useWorkflowInstanceEntry, useLogEventOnMount, WorkflowInstanceDetailViewed, InvalidDocNotice, LoadingRow, useDefinition, openActivityGone, GroupHeading, ActivityDetailDialog, instanceBreadcrumb, HoverHint, instanceTitle, formatDate, StageChip, stageTitle, AbortedBadge, SpinnerSlot, DocPreviewLink, useWorkflowContext, readDeployedDefinitions, describeError, DismissablePopover, definitionSnapshotOf, findActivity, stateByActivity, findActivityNode, rowDateControlState, rowAssignControlState, isActivityAssignedTo, assigneeUserIdsOf, isOpenActivityStatus, committedRowFace, activityRowProps, gdrLocality, CautionNote, WORKFLOW_API_VERSION, useClosableToast, parseStoredDateValue, useUserDisplays, useProjectMembers, WorkflowTaskFiltersApplied, DocRefFace, useSaveField, dateControlValue, stopRowMouseDown, stopRowClick, parseDateFieldValue, serializeDateFieldValue, dateControlKind, CollapsibleBand, LinkChip, BreadcrumbTail, StageFace, TrailingHairline, ActivityRow, UnreadableDocsNote, useAssignmentIdentity, TabSwitch, WorkflowToolOpened, workflowsTabCodec } from "./index.js";
11
+ import { createSubscribers, useWorkflowContext, readDeployedDefinitions, describeError, useDelayedFlag, instanceTitle, definitionSnapshotOf, stageTitle, DocPreviewLink, NOTICE_PADDING, chipPadding, CountedLabel, useSpaceToken, useRadiusToken, HoverHint, definitionFingerprint, CollapsibleBand, gdrLocality, openableSchemaType, WORKFLOW_API_VERSION, WorkflowTitleSeedDrifted, WorkflowBoardLayoutChanged, UnreadableDocsNote, LogEventOnMount, WorkflowBoardWorkflowSelected, LoadingRow, EmptyState, mappingIssueDetail, formatTimeAgo, formatShortDateTime, namesNoDeployedDefinition, SpinnerSlot, WorkflowDefinitionDetailViewed, useContainerToken, MetaRow, CautionNote, useBadgeCapTrim, TOAST_ID, useDefinition, useWorkflowToast, isLiveEntry, UserAvatar, formatShortAgo, formatDateTime, useAvatarSize, HintedMenuButton, useUserDisplay, useWorkflowInstanceEntry, useLogEventOnMount, WorkflowInstanceDetailViewed, InvalidDocNotice, openActivityGone, isEvaluationStale, InstanceSnapshotBody, ActivityDetailDialog, instanceBreadcrumb, DismissablePopover, findActivity, stateByActivity, findActivityNode, rowDateControlState, rowAssignControlState, isActivityAssignedTo, assigneeUserIdsOf, isOpenActivityStatus, isDateFieldKind, committedRowFace, activityRowProps, formatDate, parseStoredDateValue, useUserDisplays, useProjectMembers, WorkflowTaskFiltersApplied, DocRefFace, useEditField, useSaveField, dateControlValue, stopRowMouseDown, stopRowClick, dateControlKind, parseDateFieldValue, hasTimeOfDay, serializeDateFieldValue, LinkChip, BreadcrumbTail, StageFace, TrailingHairline, StaleLock, ActivityRow, ForMeEmptyState, pathSegmentTab, TabSwitch, WorkflowToolOpened, useAssignmentIdentity } from "./index.js";
12
12
 
13
- import { ArrowLeftIcon } from "@sanity/icons/ArrowLeft";
13
+ import { SearchIcon } from "@sanity/icons/Search";
14
+
15
+ import { latestDeployedDefinitions, entryDocRefs, terminalState, startKindOf, isSubjectEntry, isInputSourced, isStartableDefinition, toBareId, findOpenStageEntry } from "@sanity/workflow-engine";
16
+
17
+ import { ChevronDownIcon } from "@sanity/icons/ChevronDown";
18
+
19
+ import { InlineIcon } from "@sanity/icons/Inline";
20
+
21
+ import { StackCompactIcon } from "@sanity/icons/StackCompact";
22
+
23
+ import { createPortal } from "react-dom";
24
+
25
+ import { isReferenceSchemaType } from "@sanity/types";
26
+
27
+ import { useDocumentPreviewStore, useSchema, usePerspective, useClient } from "sanity";
28
+
29
+ import { useWorkflowInstances } from "@sanity/workflow-studio";
30
+
31
+ import { WarningOutlineIcon } from "@sanity/icons/WarningOutline";
14
32
 
15
33
  import { WorkflowDiagram } from "@sanity/workflow-diagram";
16
34
 
17
- import { entryDocRefs, terminalState, latestDeployedDefinitions, isStartableDefinition, toBareId, findOpenStageEntry } from "@sanity/workflow-engine";
35
+ import { ArrowLeftIcon } from "@sanity/icons/ArrowLeft";
18
36
 
19
- import { AddIcon } from "@sanity/icons/Add";
37
+ import { InfoOutlineIcon } from "@sanity/icons/InfoOutline";
20
38
 
21
- import { SearchIcon } from "@sanity/icons/Search";
39
+ import { TransferIcon } from "@sanity/icons/Transfer";
22
40
 
23
- import { useClient, useDocumentPreviewStore, useSchema } from "sanity";
41
+ import pluralize from "pluralize-esm";
42
+
43
+ import { ClockIcon } from "@sanity/icons/Clock";
44
+
45
+ import { CogIcon } from "@sanity/icons/Cog";
46
+
47
+ import { EllipsisHorizontalIcon } from "@sanity/icons/EllipsisHorizontal";
48
+
49
+ import { AddIcon } from "@sanity/icons/Add";
24
50
 
25
51
  import { CheckmarkIcon } from "@sanity/icons/Checkmark";
26
52
 
@@ -32,88 +58,2369 @@ import { MemberAvatar, DatePicker, ClearableDatePicker } from "@sanity/workflow-
32
58
 
33
59
  import { endOfDay } from "date-fns/endOfDay";
34
60
 
35
- import { createPortal } from "react-dom";
36
-
37
61
  import { CalendarIcon } from "@sanity/icons/Calendar";
38
62
 
39
- import { useWorkflowInstances } from "@sanity/workflow-studio";
63
+ function MutedNote({text: text}) {
64
+ /* @__PURE__ */
65
+ return jsx(Box, {
66
+ paddingY: 3,
67
+ children: /* @__PURE__ */ jsx(Text, {
68
+ muted: !0,
69
+ size: 1,
70
+ children: text
71
+ })
72
+ });
73
+ }
40
74
 
41
- function documentRefsOf(instance) {
42
- const seen = /* @__PURE__ */ new Map;
43
- for (const ref of entryDocRefs(instance.fields)) seen.has(ref.id) || seen.set(ref.id, ref);
44
- return [ ...seen.values() ];
75
+ function TabStatusRow({children: children}) {
76
+ /* @__PURE__ */
77
+ return jsx(Box, {
78
+ paddingLeft: 2,
79
+ paddingY: 3,
80
+ children: children
81
+ });
45
82
  }
46
83
 
47
- function InstanceTaskLists({entry: entry, onOpenActivity: onOpenActivity}) {
48
- return instanceNotice(entry) || /* @__PURE__ */ jsx(StaleLock, {
49
- stale: isEvaluationStale(entry),
50
- children: /* @__PURE__ */ jsxs(Stack, {
51
- children: [
52
- /* @__PURE__ */ jsx(ActivitiesList, {
53
- entry: entry,
54
- onOpenActivity: onOpenActivity
55
- }),
56
- /* @__PURE__ */ jsx(TodoItemsList, {
57
- entry: entry,
58
- surface: "tool-instance-detail"
59
- }) ]
84
+ const TOOL_PANEL_PADDING = 3, UNREADABLE = {
85
+ readable: !1,
86
+ raw: void 0
87
+ };
88
+
89
+ function defaultStorage() {
90
+ try {
91
+ return globalThis.localStorage;
92
+ } catch {
93
+ return;
94
+ }
95
+ }
96
+
97
+ function readItem(storage, key) {
98
+ if (!storage) return UNREADABLE;
99
+ try {
100
+ const raw = storage.getItem(key);
101
+ return {
102
+ readable: !0,
103
+ raw: raw === null ? void 0 : raw
104
+ };
105
+ } catch {
106
+ return UNREADABLE;
107
+ }
108
+ }
109
+
110
+ function writeItem(args) {
111
+ if (!args.storage) return !1;
112
+ try {
113
+ return args.storage.setItem(args.key, args.value), !0;
114
+ } catch {
115
+ return !1;
116
+ }
117
+ }
118
+
119
+ const BAND_DEFAULT_OPEN = !0, UNGROUPED_BAND_KEY = "section:no-document", PAYLOAD_VERSION = 2, MAX_ENTRIES = 200;
120
+
121
+ function bandStateStorageKey(scope) {
122
+ return `sanity.workflows.tool.bands:${scope}`;
123
+ }
124
+
125
+ function isRecord(value) {
126
+ return typeof value == "object" && value !== null && !Array.isArray(value);
127
+ }
128
+
129
+ function bandEntryOf(value) {
130
+ if (!isRecord(value)) return;
131
+ const {open: open, touched: touched} = value;
132
+ if (!(typeof open != "boolean" || typeof touched != "number") && Number.isFinite(touched)) return {
133
+ open: open,
134
+ touched: touched
135
+ };
136
+ }
137
+
138
+ function parsePayload(raw) {
139
+ try {
140
+ const parsed = JSON.parse(raw);
141
+ if (!isRecord(parsed) || parsed.version !== PAYLOAD_VERSION || !isRecord(parsed.bands)) return;
142
+ const entries = [];
143
+ for (const [key, value] of Object.entries(parsed.bands)) {
144
+ const entry = bandEntryOf(value);
145
+ if (entry === void 0) return;
146
+ entries.push([ key, entry ]);
147
+ }
148
+ return Object.fromEntries(entries);
149
+ } catch {
150
+ return;
151
+ }
152
+ }
153
+
154
+ function readStored(storage, storageKey) {
155
+ const {readable: readable, raw: raw} = readItem(storage, storageKey);
156
+ if (!readable) return {
157
+ entries: void 0,
158
+ corrupt: !1
159
+ };
160
+ if (raw === void 0) return {
161
+ entries: {},
162
+ corrupt: !1
163
+ };
164
+ const entries = parsePayload(raw);
165
+ return {
166
+ entries: entries,
167
+ corrupt: entries === void 0
168
+ };
169
+ }
170
+
171
+ function writeStored(args) {
172
+ return writeItem({
173
+ storage: args.storage,
174
+ key: args.storageKey,
175
+ value: JSON.stringify({
176
+ version: PAYLOAD_VERSION,
177
+ bands: args.entries
60
178
  })
61
179
  });
62
180
  }
63
181
 
64
- function WorkflowInstanceDetail({instanceId: instanceId, onBack: onBack}) {
65
- const entry = useWorkflowInstanceEntry(instanceId);
66
- useLogEventOnMount(WorkflowInstanceDetailViewed, {
67
- instanceId: instanceId
182
+ function baseEntries(args) {
183
+ return args.stored === void 0 ? args.held : args.unpersisted ? mergeByTouched(args.stored, args.held) : args.stored;
184
+ }
185
+
186
+ function mergeByTouched(stored, held) {
187
+ const merged = {
188
+ ...stored
189
+ };
190
+ for (const [key, entry] of Object.entries(held)) {
191
+ const persisted = merged[key];
192
+ (persisted === void 0 || entry.touched >= persisted.touched) && (merged[key] = entry);
193
+ }
194
+ return merged;
195
+ }
196
+
197
+ function prune(entries) {
198
+ const held = Object.entries(entries);
199
+ return held.length <= MAX_ENTRIES ? entries : Object.fromEntries(held.sort(([, a], [, b]) => b.touched - a.touched).slice(0, MAX_ENTRIES));
200
+ }
201
+
202
+ function togglesOf(entries) {
203
+ return new Map(Object.entries(entries).map(([key, entry]) => [ key, entry.open ]));
204
+ }
205
+
206
+ function createBandStateStore(args) {
207
+ const storage = args.storage ?? defaultStorage(), initial = readStored(storage, args.storageKey);
208
+ initial.corrupt && writeStored({
209
+ storage: storage,
210
+ storageKey: args.storageKey,
211
+ entries: {}
68
212
  });
69
- const [graceOver, setGraceOver] = useState(!1);
213
+ let held = initial.entries ?? {}, toggles = togglesOf(held);
214
+ const subscribers = createSubscribers();
215
+ let unpersisted = !1;
216
+ return {
217
+ subscribe: subscribers.subscribe,
218
+ read: () => toggles,
219
+ setOpen(key, open) {
220
+ const stored = readStored(storage, args.storageKey).entries, base = baseEntries({
221
+ stored: stored,
222
+ held: held,
223
+ unpersisted: unpersisted
224
+ });
225
+ held = prune({
226
+ ...base,
227
+ [key]: {
228
+ open: open,
229
+ touched: Date.now()
230
+ }
231
+ }), toggles = togglesOf(held), unpersisted = !writeStored({
232
+ storage: storage,
233
+ storageKey: args.storageKey,
234
+ entries: held
235
+ }), subscribers.notify();
236
+ }
237
+ };
238
+ }
239
+
240
+ function useBandOpenState(args) {
241
+ const {segment: segment, scope: scope} = args, store = useMemo(() => createBandStateStore({
242
+ storageKey: bandStateStorageKey(scope)
243
+ }), [ scope ]);
244
+ return useMemo(() => ({
245
+ segment: segment,
246
+ store: store
247
+ }), [ segment, store ]);
248
+ }
249
+
250
+ function openOf(store, qualified) {
251
+ return store.read().get(qualified) ?? BAND_DEFAULT_OPEN;
252
+ }
253
+
254
+ function useBandOpen(bands, key) {
255
+ const {segment: segment, store: store} = bands, qualified = `${segment}/${key}`;
256
+ return {
257
+ open: useSyncExternalStore(store.subscribe, useCallback(() => openOf(store, qualified), [ store, qualified ])),
258
+ toggle: () => store.setOpen(qualified, !openOf(store, qualified))
259
+ };
260
+ }
261
+
262
+ const lastRead = /* @__PURE__ */ new WeakMap, issuedReads = /* @__PURE__ */ new WeakMap;
263
+
264
+ function useDeployedDefinitions() {
265
+ const {engine: engine} = useWorkflowContext(), [definitions, setDefinitions] = useState(() => lastRead.get(engine)?.rows), [error, setError] = useState(void 0);
70
266
  return useEffect(() => {
71
- const timer = setTimeout(() => setGraceOver(!0), 2500);
72
- return () => clearTimeout(timer);
73
- }, [ instanceId ]), entry?.invalid ? /* @__PURE__ */ jsx(Box, {
74
- padding: 4,
75
- children: /* @__PURE__ */ jsx(InvalidDocNotice, {
76
- invalid: entry.invalid
267
+ let cancelled = !1;
268
+ setDefinitions(lastRead.get(engine)?.rows), setError(void 0);
269
+ const seq = (issuedReads.get(engine) ?? 0) + 1;
270
+ return issuedReads.set(engine, seq), (async () => {
271
+ const rows = await readDeployedDefinitions(engine), latest = latestDeployedDefinitions(rows), held = lastRead.get(engine);
272
+ (held === void 0 || seq > held.seq) && lastRead.set(engine, {
273
+ seq: seq,
274
+ rows: latest
275
+ }), cancelled || (setDefinitions(latest), setError(void 0));
276
+ })().catch(err => {
277
+ cancelled || setError(describeError(err));
278
+ }), () => {
279
+ cancelled = !0;
280
+ };
281
+ }, [ engine ]), {
282
+ definitions: definitions,
283
+ error: error
284
+ };
285
+ }
286
+
287
+ const REVEAL_MAX_WAIT_MS = 2e3;
288
+
289
+ function useRevealGate(args) {
290
+ const {loading: loading, ready: ready} = args, settled = !loading && ready, expired = useDelayedFlag(!loading && !ready, REVEAL_MAX_WAIT_MS), [revealed, setRevealed] = useState(!1);
291
+ return useEffect(() => {
292
+ !revealed && (settled || expired) && setRevealed(!0);
293
+ }, [ expired, revealed, settled ]), revealed || settled || expired;
294
+ }
295
+
296
+ function documentRefsOf(instance) {
297
+ const seen = /* @__PURE__ */ new Map;
298
+ for (const ref of entryDocRefs(instance.fields)) seen.has(ref.id) || seen.set(ref.id, ref);
299
+ return [ ...seen.values() ];
300
+ }
301
+
302
+ function byTitleThenName(a, b) {
303
+ return a.title.localeCompare(b.title) || a.name.localeCompare(b.name);
304
+ }
305
+
306
+ function byResolvedTitle(titleOf, keyOf) {
307
+ return (a, b) => {
308
+ const aTitle = titleOf(a), bTitle = titleOf(b);
309
+ 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));
310
+ };
311
+ }
312
+
313
+ const BOARD_EMPTY_STAGE_NOTE = "No items";
314
+
315
+ function faceOf(instance) {
316
+ const document = documentRefsOf(instance)[0];
317
+ return document ? {
318
+ kind: "document",
319
+ document: document
320
+ } : {
321
+ kind: "instance",
322
+ title: instanceTitle(instance, definitionSnapshotOf(instance))
323
+ };
324
+ }
325
+
326
+ function boardRuns(instances) {
327
+ return instances.filter(instance => terminalState(instance) !== "aborted");
328
+ }
329
+
330
+ function groupsOf(args) {
331
+ const declared = args.definition.stages.map(stage => stage.name), retired = [ ...args.byStage.keys() ].filter(stage => !declared.includes(stage)).sort();
332
+ return [ ...declared, ...retired ].map(stage => ({
333
+ stage: stage,
334
+ title: stageTitle(args.definition, stage),
335
+ cards: args.byStage.get(stage) ?? [],
336
+ retired: !declared.includes(stage)
337
+ }));
338
+ }
339
+
340
+ function deriveBoard(args) {
341
+ const byStage = /* @__PURE__ */ new Map;
342
+ for (const instance of boardRuns(args.instances)) {
343
+ const card = {
344
+ instanceId: instance._id,
345
+ face: faceOf(instance)
346
+ }, held = byStage.get(instance.currentStage);
347
+ held ? held.push(card) : byStage.set(instance.currentStage, [ card ]);
348
+ }
349
+ return {
350
+ groups: groupsOf({
351
+ definition: args.definition,
352
+ byStage: byStage
77
353
  })
78
- }) : entry ? /* @__PURE__ */ jsx(InstanceDetailPanel, {
79
- entry: entry,
80
- onBack: onBack
81
- }) : /* @__PURE__ */ jsx(Box, {
82
- padding: 4,
83
- children: graceOver ? /* @__PURE__ */ jsx(NotFound, {
84
- id: instanceId,
85
- onBack: onBack
86
- }) : /* @__PURE__ */ jsx(LoadingRow, {
87
- label: "Loading workflow…"
354
+ };
355
+ }
356
+
357
+ function orderBoardByPreviewTitle(board, titles) {
358
+ const compare = byResolvedTitle(card => card.face.kind === "instance" ? card.face.title : titles.get(card.face.document.id), card => card.instanceId);
359
+ return {
360
+ groups: board.groups.map(group => ({
361
+ ...group,
362
+ cards: [ ...group.cards ].sort(compare)
363
+ }))
364
+ };
365
+ }
366
+
367
+ function boardDocuments(board) {
368
+ return board.groups.flatMap(group => group.cards.flatMap(card => card.face.kind === "document" ? [ card.face.document ] : []));
369
+ }
370
+
371
+ function BoardCard({card: card, size: size}) {
372
+ return size === "slim" ? card.face.kind === "instance" ? /* @__PURE__ */ jsx(InstanceFace, {
373
+ size: size,
374
+ title: card.face.title
375
+ }) : /* @__PURE__ */ jsx(DocPreviewLink, {
376
+ gdr: card.face.document,
377
+ layout: "row",
378
+ source: "board-card"
379
+ }) : /* @__PURE__ */ jsx(Card, {
380
+ border: !0,
381
+ radius: 3,
382
+ children: card.face.kind === "instance" ? /* @__PURE__ */ jsx(InstanceFace, {
383
+ size: size,
384
+ title: card.face.title
385
+ }) : /* @__PURE__ */ jsx(DocPreviewLink, {
386
+ gdr: card.face.document,
387
+ layout: "bare",
388
+ source: "board-card"
88
389
  })
89
390
  });
90
391
  }
91
392
 
92
- function NotFound({id: id, onBack: onBack}) {
393
+ function InstanceFace({size: size, title: title}) {
394
+ const fill = size === "slim";
93
395
  /* @__PURE__ */
94
- return jsxs(Stack, {
95
- gap: 4,
96
- children: [
97
- /* @__PURE__ */ jsxs(Text, {
98
- muted: !0,
396
+ return jsx(Box, {
397
+ padding: fill ? chipPadding(!0) : NOTICE_PADDING,
398
+ style: fill ? {
399
+ width: "100%"
400
+ } : {},
401
+ children: /* @__PURE__ */ jsx(Text, {
99
402
  size: 1,
100
- children: [ "No workflow with id “", id, "” — it may have been deleted." ]
403
+ textOverflow: "ellipsis",
404
+ weight: "medium",
405
+ children: title
406
+ })
407
+ });
408
+ }
409
+
410
+ function StageGroupHeading({group: group}) {
411
+ /* @__PURE__ */
412
+ return jsxs(Flex, {
413
+ align: "center",
414
+ gap: 2,
415
+ children: [
416
+ /* @__PURE__ */ jsx(CountedLabel, {
417
+ count: group.cards.length,
418
+ label: /* @__PURE__ */ jsx(Text, {
419
+ size: 1,
420
+ weight: "medium",
421
+ children: group.title
422
+ })
423
+ }), group.retired ?
424
+ /* @__PURE__ */ jsx(Text, {
425
+ muted: !0,
426
+ size: 1,
427
+ children: "No longer in this workflow"
428
+ }) : null ]
429
+ });
430
+ }
431
+
432
+ const COLUMN_WIDTH = 320, PANEL_FADE = "linear-gradient(to bottom, rgb(0 0 0) 50%, rgb(0 0 0 / 0) 75%)";
433
+
434
+ function StageColumn({group: group}) {
435
+ /* @__PURE__ */
436
+ return jsxs(Box, {
437
+ flex: "none",
438
+ style: {
439
+ display: "flex",
440
+ flexDirection: "column",
441
+ position: "relative",
442
+ width: COLUMN_WIDTH
443
+ },
444
+ children: [
445
+ /* @__PURE__ */ jsx(Card, {
446
+ "aria-hidden": !0,
447
+ radius: 3,
448
+ style: {
449
+ inset: 0,
450
+ maskImage: PANEL_FADE,
451
+ position: "absolute",
452
+ WebkitMaskImage: PANEL_FADE
453
+ },
454
+ tone: "transparent"
455
+ }),
456
+ /* @__PURE__ */ jsx(Box, {
457
+ flex: "none",
458
+ paddingX: 3,
459
+ paddingY: 4,
460
+ style: {
461
+ position: "relative"
462
+ },
463
+ children: /* @__PURE__ */ jsx(StageGroupHeading, {
464
+ group: group
465
+ })
466
+ }), group.cards.length === 0 ?
467
+ /* @__PURE__ */ jsx(Flex, {
468
+ align: "center",
469
+ flex: 1,
470
+ justify: "center",
471
+ padding: 2,
472
+ style: {
473
+ position: "relative"
474
+ },
475
+ children: /* @__PURE__ */ jsx(Text, {
476
+ muted: !0,
477
+ size: 1,
478
+ children: BOARD_EMPTY_STAGE_NOTE
479
+ })
480
+ }) :
481
+ /* @__PURE__ */ jsx(Box, {
482
+ flex: 1,
483
+ overflow: "auto",
484
+ paddingBottom: 2,
485
+ paddingX: 2,
486
+ style: {
487
+ minHeight: 0,
488
+ position: "relative"
489
+ },
490
+ children: /* @__PURE__ */ jsx(Stack, {
491
+ gap: 1,
492
+ children: group.cards.map(card => /* @__PURE__ */ jsx(BoardCard, {
493
+ card: card,
494
+ size: "block"
495
+ }, card.instanceId))
496
+ })
497
+ }) ]
498
+ });
499
+ }
500
+
501
+ function StageGroups({board: board}) {
502
+ const topGap = useSpaceToken(TOOL_PANEL_PADDING);
503
+ /* @__PURE__ */
504
+ return jsx(Box, {
505
+ flex: 1,
506
+ style: {
507
+ minHeight: 0,
508
+ position: "relative"
509
+ },
510
+ children: /* @__PURE__ */ jsx(Box, {
511
+ style: {
512
+ bottom: 0,
513
+ left: 0,
514
+ overflowX: "auto",
515
+ overflowY: "hidden",
516
+ position: "absolute",
517
+ right: 0,
518
+ top: topGap
519
+ },
520
+ children: /* @__PURE__ */ jsx(Flex, {
521
+ align: "stretch",
522
+ gap: 2,
523
+ paddingX: TOOL_PANEL_PADDING,
524
+ style: {
525
+ boxSizing: "border-box",
526
+ height: "100%",
527
+ width: "max-content"
528
+ },
529
+ children: board.groups.map(group => /* @__PURE__ */ jsx(StageColumn, {
530
+ group: group
531
+ }, group.stage))
532
+ })
533
+ })
534
+ });
535
+ }
536
+
537
+ function isPlainClick(event) {
538
+ return !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey;
539
+ }
540
+
541
+ const BOARD_LAYOUTS = [ "columns", "stack" ], BOARD_DEFAULT_LAYOUT = "columns";
542
+
543
+ function boardLayoutStorageKey(scope) {
544
+ return `sanity.workflows.tool.board-layout:${scope}`;
545
+ }
546
+
547
+ function layoutOf(raw) {
548
+ return BOARD_LAYOUTS.find(layout => layout === raw) ?? BOARD_DEFAULT_LAYOUT;
549
+ }
550
+
551
+ function createBoardLayoutStore(args) {
552
+ const storage = args.storage ?? defaultStorage();
553
+ return {
554
+ read: () => layoutOf(readItem(storage, args.storageKey).raw),
555
+ set(layout) {
556
+ return writeItem({
557
+ storage: storage,
558
+ key: args.storageKey,
559
+ value: layout
560
+ }), layout;
561
+ }
562
+ };
563
+ }
564
+
565
+ const PICKER_RADIUS = 3;
566
+
567
+ function pickerLabel(face) {
568
+ return face.kind === "named" ? face.title : face.kind === "loading" ? "Loading" : "Select a workflow";
569
+ }
570
+
571
+ function WorkflowPicker({face: face, onSelect: onSelect, options: options}) {
572
+ const empty = options.length === 0, innerRadius = useRadiusToken(PICKER_RADIUS) - 1;
573
+ /* @__PURE__ */
574
+ return jsx(Card, {
575
+ border: !0,
576
+ radius: PICKER_RADIUS,
577
+ children: /* @__PURE__ */ jsxs(Flex, {
578
+ align: "center",
579
+ children: [
580
+ /* @__PURE__ */ jsx(Box, {
581
+ paddingLeft: 2,
582
+ paddingRight: 2,
583
+ children: /* @__PURE__ */ jsx(Text, {
584
+ muted: !0,
585
+ size: 1,
586
+ children: "Workflow"
587
+ })
588
+ }),
589
+ /* @__PURE__ */ jsx(MenuButton, {
590
+ button: /* @__PURE__ */ jsx(Button, {
591
+ fontSize: 1,
592
+ gap: 2,
593
+ iconRight: ChevronDownIcon,
594
+ mode: "bleed",
595
+ disabled: empty,
596
+ padding: 2,
597
+ radius: 0,
598
+ style: {
599
+ borderRadius: `0 ${innerRadius}px ${innerRadius}px 0`
600
+ },
601
+ text: pickerLabel(face)
602
+ }),
603
+ id: "workflows-board-picker",
604
+ menu: /* @__PURE__ */ jsx(Menu, {
605
+ children: options.map(option => /* @__PURE__ */ jsx(MenuItem, {
606
+ fontSize: 1,
607
+ onClick: () => onSelect(option.name),
608
+ pressed: face.kind === "named" && option.name === face.name,
609
+ text: option.title
610
+ }, option.name))
611
+ }),
612
+ popover: {
613
+ constrainSize: !0,
614
+ fallbackPlacements: [ "top-start" ],
615
+ placement: "bottom-start",
616
+ portal: !0
617
+ }
618
+ }) ]
619
+ })
620
+ });
621
+ }
622
+
623
+ const LAYOUT_LABELS = {
624
+ columns: {
625
+ icon: InlineIcon,
626
+ label: "Show as boards"
627
+ },
628
+ stack: {
629
+ icon: StackCompactIcon,
630
+ label: "Show as list"
631
+ }
632
+ };
633
+
634
+ function LayoutToggle({layout: layout, onChange: onChange}) {
635
+ /* @__PURE__ */
636
+ return jsx(Flex, {
637
+ align: "center",
638
+ gap: 1,
639
+ children: BOARD_LAYOUTS.map(value => {
640
+ const {icon: icon, label: label} = LAYOUT_LABELS[value];
641
+ /* @__PURE__ */
642
+ return jsx(HoverHint, {
643
+ text: label,
644
+ children: /* @__PURE__ */ jsx(Button, {
645
+ "aria-label": label,
646
+ "aria-pressed": value === layout,
647
+ fontSize: 1,
648
+ icon: icon,
649
+ mode: "bleed",
650
+ onClick: () => onChange(value),
651
+ padding: 2,
652
+ selected: value === layout
653
+ })
654
+ }, value);
655
+ })
656
+ });
657
+ }
658
+
659
+ function DefinitionLink({name: name, onOpen: onOpen}) {
660
+ const {resolvePathFromState: resolvePathFromState} = useRouter();
661
+ /* @__PURE__ */
662
+ return jsx(Button, {
663
+ as: "a",
664
+ fontSize: 1,
665
+ href: resolvePathFromState({
666
+ definitionName: name
667
+ }),
668
+ mode: "bleed",
669
+ onClick: event => {
670
+ isPlainClick(event) && (event.preventDefault(), onOpen(name));
671
+ },
672
+ padding: 2,
673
+ text: "View definition"
674
+ });
675
+ }
676
+
677
+ function BoardControlsPortal({definitionName: definitionName, layout: layout, onChangeLayout: onChangeLayout, face: face, onOpenDefinition: onOpenDefinition, onSelectWorkflow: onSelectWorkflow, options: options, slot: slot}) {
678
+ return slot ? createPortal(
679
+ /* @__PURE__ */ jsxs(Flex, {
680
+ align: "center",
681
+ gap: 2,
682
+ children: [
683
+ /* @__PURE__ */ jsx(WorkflowPicker, {
684
+ face: face,
685
+ onSelect: onSelectWorkflow,
686
+ options: options
687
+ }), definitionName === void 0 ? null : /* @__PURE__ */ jsx(DefinitionLink, {
688
+ name: definitionName,
689
+ onOpen: onOpenDefinition
690
+ }),
691
+ /* @__PURE__ */ jsx(Box, {
692
+ flex: 1
693
+ }),
694
+ /* @__PURE__ */ jsx(LayoutToggle, {
695
+ layout: layout,
696
+ onChange: onChangeLayout
697
+ }) ]
698
+ }), slot) : null;
699
+ }
700
+
701
+ function boardWorkflowOptions(definitions) {
702
+ return definitions.map(definition => ({
703
+ name: definition.name,
704
+ title: definition.title
705
+ })).sort(byTitleThenName);
706
+ }
707
+
708
+ function boardLandingName(selection) {
709
+ if (!(selection.kind !== "resolved" || selection.via !== "default")) return selection.definition.name;
710
+ }
711
+
712
+ function boardPickerFace(args) {
713
+ const {selection: selection, routeName: routeName} = args;
714
+ if (selection.kind === "resolved") {
715
+ const {name: name, title: title} = selection.definition;
716
+ return {
717
+ kind: "named",
718
+ name: name,
719
+ title: title
720
+ };
721
+ }
722
+ return selection.kind === "loading" && routeName !== void 0 ? {
723
+ kind: "loading"
724
+ } : {
725
+ kind: "none"
726
+ };
727
+ }
728
+
729
+ function boardSelectedPayload(selection) {
730
+ if (selection.kind === "resolved") return {
731
+ via: selection.via,
732
+ ...definitionFingerprint(selection.definition)
733
+ };
734
+ }
735
+
736
+ function resolveBoardSelection(args) {
737
+ const {definitions: definitions, routeName: routeName} = args;
738
+ if (definitions === void 0) return {
739
+ kind: "loading"
740
+ };
741
+ if (routeName !== void 0) {
742
+ const named = definitions.find(definition => definition.name === routeName);
743
+ return named ? {
744
+ kind: "resolved",
745
+ definition: named,
746
+ via: "address"
747
+ } : {
748
+ kind: "unknown",
749
+ name: routeName
750
+ };
751
+ }
752
+ const [landing] = [ ...definitions ].sort(byTitleThenName);
753
+ return landing ? {
754
+ kind: "resolved",
755
+ definition: landing,
756
+ via: "default"
757
+ } : {
758
+ kind: "empty"
759
+ };
760
+ }
761
+
762
+ function StageBand({bands: bands, group: group}) {
763
+ const {open: open, toggle: toggle} = useBandOpen(bands, group.stage);
764
+ /* @__PURE__ */
765
+ return jsx(CollapsibleBand, {
766
+ background: !0,
767
+ onToggle: toggle,
768
+ open: open,
769
+ sticky: !0,
770
+ title: group.title,
771
+ header: /* @__PURE__ */ jsx(StageGroupHeading, {
772
+ group: group
773
+ }),
774
+ children: /* @__PURE__ */ jsx(Stack, {
775
+ gap: 1,
776
+ paddingLeft: 5,
777
+ children: group.cards.length === 0 ?
778
+ /* @__PURE__ */ jsx(Box, {
779
+ padding: 3,
780
+ children: /* @__PURE__ */ jsx(Text, {
781
+ muted: !0,
782
+ size: 1,
783
+ children: BOARD_EMPTY_STAGE_NOTE
784
+ })
785
+ }) : group.cards.map(card => /* @__PURE__ */ jsx(BoardCard, {
786
+ card: card,
787
+ size: "slim"
788
+ }, card.instanceId))
789
+ })
790
+ });
791
+ }
792
+
793
+ function BoardStack({bands: bands, board: board}) {
794
+ /* @__PURE__ */
795
+ return jsx(Stack, {
796
+ gap: 1,
797
+ paddingBottom: 4,
798
+ paddingTop: 3,
799
+ paddingX: TOOL_PANEL_PADDING,
800
+ children: board.groups.map(group => /* @__PURE__ */ jsx(StageBand, {
801
+ bands: bands,
802
+ group: group
803
+ }, group.stage))
804
+ });
805
+ }
806
+
807
+ const ALIAS_PREFIX = "__seed_", MAX_PATH_DEPTH = 3, IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
808
+
809
+ function previewSelectOf(schemaType) {
810
+ const select = schemaType.preview?.select;
811
+ if (!(select === void 0 || Object.keys(select).length === 0)) return select;
812
+ }
813
+
814
+ function fieldTypeOf(parent, name) {
815
+ if (!(!("fields" in parent) || !Array.isArray(parent.fields))) return parent.fields.find(field => field.name === name)?.type;
816
+ }
817
+
818
+ function stepOf(args) {
819
+ const {parent: parent, segment: segment, terminal: terminal} = args, fieldType = fieldTypeOf(parent, segment);
820
+ if (fieldType !== void 0) {
821
+ if (terminal) return {
822
+ part: segment,
823
+ next: fieldType
824
+ };
825
+ if (isReferenceSchemaType(fieldType)) {
826
+ const target = fieldType.to.length === 1 ? fieldType.to[0] : void 0;
827
+ return target === void 0 ? void 0 : {
828
+ part: `${segment}->`,
829
+ next: target
830
+ };
831
+ }
832
+ return {
833
+ part: `${segment}.`,
834
+ next: fieldType
835
+ };
836
+ }
837
+ }
838
+
839
+ function compilePath(root, path) {
840
+ const segments = path.split(".");
841
+ if (segments.length > MAX_PATH_DEPTH || segments.some(s => !IDENTIFIER.test(s))) return;
842
+ let parent = root;
843
+ const parts = [];
844
+ for (const [index, segment] of segments.entries()) {
845
+ const step = stepOf({
846
+ parent: parent,
847
+ segment: segment,
848
+ terminal: index === segments.length - 1
849
+ });
850
+ if (step === void 0) return;
851
+ parts.push(step.part), parent = step.next;
852
+ }
853
+ return parts.join("");
854
+ }
855
+
856
+ function selectArmOf(schemaType) {
857
+ const select = previewSelectOf(schemaType);
858
+ if (select === void 0) return;
859
+ const entries = [];
860
+ for (const [key, path] of Object.entries(select)) {
861
+ const compiled = compilePath(schemaType, path);
862
+ if (compiled === void 0) return;
863
+ entries.push(`${JSON.stringify(ALIAS_PREFIX + key)}: ${compiled}`);
864
+ }
865
+ return `_type == ${JSON.stringify(schemaType.name)} => {${entries.join(", ")}}`;
866
+ }
867
+
868
+ function planTitleSeed(types) {
869
+ const seedable = /* @__PURE__ */ new Map, arms = [];
870
+ for (const type of types) {
871
+ const arm = selectArmOf(type);
872
+ arm !== void 0 && (seedable.set(type.name, type), arms.push(arm));
873
+ }
874
+ return {
875
+ query: `*[_id in $ids]{_id, _type${arms.length > 0 ? `, ...select(${arms.join(", ")})` : ""}}`,
876
+ seedable: seedable
877
+ };
878
+ }
879
+
880
+ function isSeedRow(value) {
881
+ if (typeof value != "object" || value === null) return !1;
882
+ const row = value;
883
+ return typeof row._id == "string" && typeof row._type == "string";
884
+ }
885
+
886
+ function seededTitleOf(row, schemaType) {
887
+ const select = previewSelectOf(schemaType);
888
+ if (select === void 0) return;
889
+ const selected = {};
890
+ for (const key of Object.keys(select)) selected[key] = row[ALIAS_PREFIX + key];
891
+ const prepare = schemaType.preview?.prepare;
892
+ let title;
893
+ if (prepare === void 0) title = selected.title; else try {
894
+ title = prepare(selected).title;
895
+ } catch (err) {
896
+ console.error(`[workflow-studio-plugin] preview prepare for "${schemaType.name}" failed on seed:`, err);
897
+ return;
898
+ }
899
+ return typeof title == "string" ? title : void 0;
900
+ }
901
+
902
+ function seedFromRows(args) {
903
+ const rows = args.rows.filter(isSeedRow), byBareId = new Map(rows.map(row => [ row._id, row ])), types = new Map(rows.map(row => [ row._id, row._type ])), titles = /* @__PURE__ */ new Map, resolved = /* @__PURE__ */ new Set;
904
+ for (const {gdrId: gdrId, bareId: bareId} of args.docs) {
905
+ const row = byBareId.get(bareId);
906
+ if (row === void 0) continue;
907
+ const schemaType = args.seedable.get(row._type);
908
+ if (schemaType === void 0) continue;
909
+ resolved.add(gdrId);
910
+ const title = seededTitleOf(row, schemaType);
911
+ title !== void 0 && titles.set(gdrId, title);
912
+ }
913
+ return {
914
+ types: types,
915
+ titles: {
916
+ titles: titles,
917
+ resolved: resolved
918
+ }
919
+ };
920
+ }
921
+
922
+ function titleDrift(seed, live) {
923
+ const drifted = [];
924
+ for (const gdrId of seed.resolved) {
925
+ if (!live.resolved.has(gdrId)) continue;
926
+ const seeded = seed.titles.get(gdrId), observed = live.titles.get(gdrId);
927
+ seeded !== observed && drifted.push({
928
+ gdrId: gdrId,
929
+ seeded: seeded,
930
+ observed: observed
931
+ });
932
+ }
933
+ return drifted;
934
+ }
935
+
936
+ function mergeSeededTitles(live, seed) {
937
+ if (seed.resolved.size === 0) return live;
938
+ const titles = new Map(live.titles), resolved = new Set(live.resolved);
939
+ for (const gdrId of seed.resolved) {
940
+ if (live.resolved.has(gdrId)) continue;
941
+ resolved.add(gdrId);
942
+ const title = seed.titles.get(gdrId);
943
+ title !== void 0 && titles.set(gdrId, title);
944
+ }
945
+ return {
946
+ titles: titles,
947
+ resolved: resolved
948
+ };
949
+ }
950
+
951
+ const NO_TITLES = {
952
+ titles: /* @__PURE__ */ new Map,
953
+ resolved: /* @__PURE__ */ new Set
954
+ };
955
+
956
+ function useDocPreviewTitles(documents) {
957
+ const previewStore = useDocumentPreviewStore(), schema = useSchema(), {perspectiveStack: perspectiveStack} = usePerspective(), {actualTypes: actualTypes, binding: binding} = useWorkflowContext(), [state, setState] = useState(NO_TITLES), key = documents.map(doc => doc.id).join("|");
958
+ return useEffect(() => {
959
+ const titles = /* @__PURE__ */ new Map, resolved = /* @__PURE__ */ new Set, previews = /* @__PURE__ */ new Map, publish = () => setState({
960
+ titles: new Map(titles),
961
+ resolved: new Set(resolved)
962
+ }), localDocs = documents.flatMap(doc => {
963
+ const {parsed: parsed, local: local} = gdrLocality(doc.id, binding.contentResource);
964
+ return !parsed || !local ? (resolved.add(doc.id), []) : [ {
965
+ doc: doc,
966
+ bareId: parsed.documentId
967
+ } ];
968
+ }), untracks = localDocs.map(({bareId: bareId}) => actualTypes.track(bareId)), dropPreview = docId => {
969
+ previews.get(docId)?.unsubscribe(), previews.delete(docId), titles.delete(docId);
970
+ }, observe = ({docId: docId, bareId: bareId, typeName: typeName}) => {
971
+ const schemaType = openableSchemaType(schema, typeName);
972
+ if (!schemaType) return !1;
973
+ if (previews.get(docId)?.typeName === typeName) return !0;
974
+ dropPreview(docId), resolved.delete(docId);
975
+ const subscription = previewStore.observeForPreview({
976
+ _id: bareId,
977
+ _type: schemaType.name
978
+ }, schemaType, {
979
+ perspective: perspectiveStack
980
+ }).subscribe({
981
+ next: event => {
982
+ resolved.add(docId);
983
+ const title = event.snapshot?.title;
984
+ typeof title == "string" ? titles.set(docId, title) : titles.delete(docId), publish();
985
+ },
986
+ error: err => {
987
+ console.error(`[workflow-studio-plugin] preview for "${docId}" failed:`, err), resolved.add(docId),
988
+ publish();
989
+ }
990
+ });
991
+ return previews.set(docId, {
992
+ typeName: typeName,
993
+ unsubscribe: () => subscription.unsubscribe()
994
+ }), !0;
995
+ }, evaluate = () => {
996
+ for (const {doc: doc, bareId: bareId} of localDocs) {
997
+ const probe = actualTypes.read(bareId);
998
+ if (probe === null) {
999
+ dropPreview(doc.id), resolved.add(doc.id);
1000
+ continue;
1001
+ }
1002
+ !observe({
1003
+ docId: doc.id,
1004
+ bareId: bareId,
1005
+ typeName: probe ?? doc.type
1006
+ }) && typeof probe == "string" && (dropPreview(doc.id), resolved.add(doc.id));
1007
+ }
1008
+ publish();
1009
+ }, unsubscribeProbes = actualTypes.subscribe(evaluate);
1010
+ return evaluate(), () => {
1011
+ unsubscribeProbes(), untracks.forEach(untrack => untrack()), previews.forEach(preview => preview.unsubscribe());
1012
+ };
1013
+ }, [ key, previewStore, schema, actualTypes, binding.contentResource.id, perspectiveStack.join("|") ]),
1014
+ state;
1015
+ }
1016
+
1017
+ function usePreviewSeed(documents) {
1018
+ const client = useClient({
1019
+ apiVersion: WORKFLOW_API_VERSION
1020
+ }), schema = useSchema(), {perspectiveStack: perspectiveStack} = usePerspective(), {actualTypes: actualTypes, binding: binding} = useWorkflowContext(), [seed, setSeed] = useState(NO_TITLES), docs = useMemo(() => documents.flatMap(doc => {
1021
+ const {parsed: parsed, local: local} = gdrLocality(doc.id, binding.contentResource);
1022
+ return parsed === void 0 || !local ? [] : [ {
1023
+ gdrId: doc.id,
1024
+ bareId: parsed.documentId
1025
+ } ];
1026
+ }), [ documents, binding.contentResource ]), key = docs.map(doc => doc.gdrId).join("|");
1027
+ return useEffect(() => {
1028
+ if (setSeed(NO_TITLES), key === "") return;
1029
+ const plan = planTitleSeed(schema.getTypeNames().flatMap(name => openableSchemaType(schema, name) ?? []));
1030
+ let cancelled = !1;
1031
+ return client.fetch(plan.query, {
1032
+ ids: docs.map(doc => doc.bareId)
1033
+ }, {
1034
+ tag: "workflows.preview-seed",
1035
+ ...perspectiveStack.length > 0 ? {
1036
+ perspective: [ ...perspectiveStack ]
1037
+ } : {}
1038
+ }).then(rows => {
1039
+ if (cancelled) return;
1040
+ const result = seedFromRows({
1041
+ rows: rows,
1042
+ docs: docs,
1043
+ seedable: plan.seedable
1044
+ });
1045
+ actualTypes.seed(result.types), setSeed(result.titles);
1046
+ }).catch(err => {
1047
+ cancelled || console.error("[workflow-studio-plugin] preview seed failed:", err);
1048
+ }), () => {
1049
+ cancelled = !0;
1050
+ };
1051
+ }, [ key, client, schema, actualTypes, binding.contentResource.id, perspectiveStack.join("|") ]),
1052
+ seed;
1053
+ }
1054
+
1055
+ function useSeededTitles(args) {
1056
+ const {ordering: ordering, warm: warm} = args, seed = usePreviewSeed(warm), live = useDocPreviewTitles(ordering), telemetry = useWorkflowTelemetry(), warned = useRef(/* @__PURE__ */ new Set);
1057
+ return useEffect(() => {
1058
+ const fresh = titleDrift(seed, live).filter(drift => !warned.current.has(drift.gdrId));
1059
+ if (fresh.length !== 0) {
1060
+ warned.current.size === 0 && telemetry.log(WorkflowTitleSeedDrifted, {
1061
+ driftCount: fresh.length
1062
+ });
1063
+ 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`);
1064
+ }
1065
+ }, [ seed, live, telemetry ]), useMemo(() => {
1066
+ const merged = mergeSeededTitles(live, seed);
1067
+ return {
1068
+ ...merged,
1069
+ ready: ordering.every(doc => merged.resolved.has(doc.id))
1070
+ };
1071
+ }, [ ordering, live, seed ]);
1072
+ }
1073
+
1074
+ function useGroupTitles(groups) {
1075
+ const ordering = useMemo(() => groups.withDocuments.map(group => group.document), [ groups ]), warm = useMemo(() => groups.withDocuments.flatMap(group => group.documents), [ groups ]);
1076
+ return useSeededTitles({
1077
+ ordering: ordering,
1078
+ warm: warm
1079
+ });
1080
+ }
1081
+
1082
+ const NO_DOCUMENTS = [];
1083
+
1084
+ function useBoard(args) {
1085
+ const {definition: definition, instances: instances} = args, derived = useMemo(() => definition ? deriveBoard({
1086
+ instances: instances,
1087
+ definition: definition
1088
+ }) : void 0, [ definition, instances ]), documents = useMemo(() => derived ? boardDocuments(derived) : NO_DOCUMENTS, [ derived ]), {titles: titles, ready: ready} = useSeededTitles({
1089
+ ordering: documents,
1090
+ warm: documents
1091
+ }), board = useMemo(() => derived ? orderBoardByPreviewTitle(derived, titles) : void 0, [ derived, titles ]);
1092
+ return useMemo(() => ({
1093
+ board: board,
1094
+ ready: ready
1095
+ }), [ board, ready ]);
1096
+ }
1097
+
1098
+ const INSTANCE_CAP = 200;
1099
+
1100
+ function useCappedInstances(filter) {
1101
+ const {engine: engine} = useWorkflowContext(), capped = useMemo(() => ({
1102
+ ...filter,
1103
+ limit: INSTANCE_CAP
1104
+ }), [ filter ]), {instances: instances, loading: loading, unreadable: unreadable} = useWorkflowInstances({
1105
+ engine: engine,
1106
+ filter: capped
1107
+ });
1108
+ return useMemo(() => {
1109
+ const rows = instances ?? [];
1110
+ return {
1111
+ rows: rows,
1112
+ truncated: rows.length + unreadable.length === INSTANCE_CAP,
1113
+ loading: loading,
1114
+ unreadable: unreadable
1115
+ };
1116
+ }, [ instances, loading, unreadable ]);
1117
+ }
1118
+
1119
+ const BOARD_CAP_NOTE = `Showing the latest ${INSTANCE_CAP} workflows — older ones aren’t on the board.`, NOTHING = {
1120
+ ids: []
1121
+ };
1122
+
1123
+ function useBoardInstances(definitionName) {
1124
+ const filter = useMemo(() => definitionName === void 0 ? NOTHING : {
1125
+ definition: definitionName,
1126
+ includeCompleted: !0
1127
+ }, [ definitionName ]);
1128
+ return useCappedInstances(filter);
1129
+ }
1130
+
1131
+ function useBoardLayout(scope) {
1132
+ const store = useMemo(() => createBoardLayoutStore({
1133
+ storageKey: boardLayoutStorageKey(scope)
1134
+ }), [ scope ]), [held, setHeld] = useState(() => ({
1135
+ store: store,
1136
+ layout: store.read()
1137
+ }));
1138
+ return held.store !== store && setHeld({
1139
+ store: store,
1140
+ layout: store.read()
1141
+ }), {
1142
+ layout: held.layout,
1143
+ setLayout: layout => setHeld({
1144
+ store: store,
1145
+ layout: store.set(layout)
1146
+ })
1147
+ };
1148
+ }
1149
+
1150
+ function DocumentsTab({filterSlot: filterSlot, onOpenDefinition: onOpenDefinition, onSelectWorkflow: onSelectWorkflow, routeDefinition: routeDefinition}) {
1151
+ const {binding: binding} = useWorkflowContext(), telemetry = useWorkflowTelemetry(), {definitions: definitions, error: error} = useDeployedDefinitions(), {layout: layout, setLayout: setLayout} = useBoardLayout(binding.engineResource.id), bands = useBandOpenState({
1152
+ segment: "documents",
1153
+ scope: binding.engineResource.id
1154
+ }), selection = resolveBoardSelection({
1155
+ definitions: definitions,
1156
+ routeName: routeDefinition
1157
+ }), selected = selection.kind === "resolved" ? selection.definition : void 0, instances = useBoardInstances(selected?.name), {board: board, ready: ready} = useBoard({
1158
+ definition: selected,
1159
+ instances: instances.rows
1160
+ }), revealed = useRevealGate({
1161
+ loading: instances.loading,
1162
+ ready: ready
1163
+ }), landing = boardLandingName(selection);
1164
+ useEffect(() => {
1165
+ landing !== void 0 && onSelectWorkflow(landing, {
1166
+ replace: !0
1167
+ });
1168
+ }, [ landing, onSelectWorkflow ]);
1169
+ const selectedPayload = boardSelectedPayload(selection);
1170
+ /* @__PURE__ */
1171
+ return jsxs(Flex, {
1172
+ direction: "column",
1173
+ flex: 1,
1174
+ style: {
1175
+ minHeight: 0
1176
+ },
1177
+ children: [
1178
+ /* @__PURE__ */ jsx(BoardControlsPortal, {
1179
+ definitionName: selected?.name,
1180
+ layout: layout,
1181
+ onOpenDefinition: onOpenDefinition,
1182
+ onChangeLayout: next => {
1183
+ next !== layout && (telemetry.log(WorkflowBoardLayoutChanged, {
1184
+ layout: next
1185
+ }), setLayout(next));
1186
+ },
1187
+ onSelectWorkflow: onSelectWorkflow,
1188
+ options: boardWorkflowOptions(definitions ?? []),
1189
+ face: boardPickerFace({
1190
+ selection: selection,
1191
+ routeName: routeDefinition
1192
+ }),
1193
+ slot: filterSlot
1194
+ }),
1195
+ /* @__PURE__ */ jsxs(Flex, {
1196
+ direction: "column",
1197
+ flex: 1,
1198
+ gap: 2,
1199
+ style: {
1200
+ minHeight: 0
1201
+ },
1202
+ children: [ instances.unreadable.length === 0 ? null : /* @__PURE__ */ jsx(Box, {
1203
+ paddingX: TOOL_PANEL_PADDING,
1204
+ children: /* @__PURE__ */ jsx(UnreadableDocsNote, {
1205
+ unreadable: instances.unreadable
1206
+ })
1207
+ }),
1208
+ /* @__PURE__ */ jsx(BoardBody, {
1209
+ bands: bands,
1210
+ board: board,
1211
+ error: error,
1212
+ layout: layout,
1213
+ loading: !revealed,
1214
+ selection: selection
1215
+ }), instances.truncated ? /* @__PURE__ */ jsx(Box, {
1216
+ paddingX: TOOL_PANEL_PADDING,
1217
+ children: /* @__PURE__ */ jsx(MutedNote, {
1218
+ text: BOARD_CAP_NOTE
1219
+ })
1220
+ }) : null ]
1221
+ }), selectedPayload === void 0 ? null :
1222
+ /* @__PURE__ */ jsx(LogEventOnMount, {
1223
+ data: selectedPayload,
1224
+ event: WorkflowBoardWorkflowSelected
1225
+ }, selected?.name) ]
1226
+ });
1227
+ }
1228
+
1229
+ function BoardBody({bands: bands, board: board, error: error, layout: layout, loading: loading, selection: selection}) {
1230
+ return error !== void 0 ? /* @__PURE__ */ jsx(StatusBox, {
1231
+ children: /* @__PURE__ */ jsx(MutedNote, {
1232
+ text: `Could not load the deployed workflows: ${error}`
1233
+ })
1234
+ }) : selection.kind === "loading" ? /* @__PURE__ */ jsx(StatusBox, {
1235
+ children: /* @__PURE__ */ jsx(TabStatusRow, {
1236
+ children: /* @__PURE__ */ jsx(LoadingRow, {
1237
+ label: "Loading workflows…"
1238
+ })
1239
+ })
1240
+ }) : selection.kind === "empty" ? /* @__PURE__ */ jsx(StatusBox, {
1241
+ children: /* @__PURE__ */ jsx(EmptyState, {
1242
+ description: "Deploy a workflow to see its documents here",
1243
+ icon: /* @__PURE__ */ jsx(SearchIcon, {}),
1244
+ title: "No workflows deployed"
1245
+ })
1246
+ }) : selection.kind === "unknown" ? /* @__PURE__ */ jsx(StatusBox, {
1247
+ children: /* @__PURE__ */ jsx(EmptyState, {
1248
+ description: `No deployed workflow is named “${selection.name}” — pick one above`,
1249
+ icon: /* @__PURE__ */ jsx(SearchIcon, {}),
1250
+ title: "Workflow not found"
1251
+ })
1252
+ }) : loading || board === void 0 ? /* @__PURE__ */ jsx(StatusBox, {
1253
+ children: /* @__PURE__ */ jsx(TabStatusRow, {
1254
+ children: /* @__PURE__ */ jsx(LoadingRow, {
1255
+ label: "Loading documents…"
1256
+ })
1257
+ })
1258
+ }) : layout === "stack" ? /* @__PURE__ */ jsx(BoardStack, {
1259
+ bands: bands,
1260
+ board: board
1261
+ }) : /* @__PURE__ */ jsx(StageGroups, {
1262
+ board: board
1263
+ });
1264
+ }
1265
+
1266
+ function StatusBox({children: children}) {
1267
+ /* @__PURE__ */
1268
+ return jsx(Flex, {
1269
+ direction: "column",
1270
+ flex: 1,
1271
+ paddingX: TOOL_PANEL_PADDING,
1272
+ children: children
1273
+ });
1274
+ }
1275
+
1276
+ function definitionCatalog(args) {
1277
+ const rejected = [ ...args.issues.values() ];
1278
+ return args.definitions.map(definition => ({
1279
+ name: definition.name,
1280
+ title: definition.title ?? definition.name,
1281
+ description: definition.description,
1282
+ activeCount: args.inFlight.filter(instance => instance.definition === definition.name).length,
1283
+ docTypes: [ ...new Set(args.mappings.filter(mapping => mapping.definition === definition.name).map(mapping => mapping.docType)) ],
1284
+ brokenBindings: rejected.filter(issue => issue.definition === definition.name && issue.version === definition.version).map(issue => ({
1285
+ docType: issue.docType,
1286
+ detail: mappingIssueDetail(issue)
1287
+ })).sort((a, b) => a.docType.localeCompare(b.docType))
1288
+ })).sort(byTitleThenName);
1289
+ }
1290
+
1291
+ function undeployedWorkflowNotices(args) {
1292
+ return [ ...args.issues.values() ].filter(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`);
1293
+ }
1294
+
1295
+ function docTypeLabels(docTypes, titleOf) {
1296
+ return docTypes.map(name => ({
1297
+ name: name,
1298
+ title: titleOf(name) ?? name
1299
+ })).sort(byTitleThenName);
1300
+ }
1301
+
1302
+ function startPhrase(definition) {
1303
+ if (definition.lifecycle === "child") return "Started by a parent workflow";
1304
+ if (startKindOf(definition) === "autonomous") return "Starts automatically";
1305
+ const subject = (definition.fields ?? []).find(isSubjectEntry);
1306
+ return subject !== void 0 && isInputSourced(subject) ? "Started manually, from a document" : "Started manually";
1307
+ }
1308
+
1309
+ function deployedAtOf(definition) {
1310
+ return typeof definition._updatedAt == "string" ? definition._updatedAt : void 0;
1311
+ }
1312
+
1313
+ function deployedPhrase(definition) {
1314
+ const deployedAt = deployedAtOf(definition);
1315
+ if (deployedAt === void 0) return `v${definition.version}`;
1316
+ const ago = formatTimeAgo(deployedAt);
1317
+ return ago === "" ? formatShortDateTime(deployedAt) : `${formatShortDateTime(deployedAt)} (${ago})`;
1318
+ }
1319
+
1320
+ function DetailNotFound({back: back, message: message}) {
1321
+ /* @__PURE__ */
1322
+ return jsxs(Stack, {
1323
+ gap: 4,
1324
+ children: [
1325
+ /* @__PURE__ */ jsx(Text, {
1326
+ muted: !0,
1327
+ size: 1,
1328
+ children: message
1329
+ }),
1330
+ /* @__PURE__ */ jsx(Flex, {
1331
+ children: /* @__PURE__ */ jsx(Button, {
1332
+ fontSize: 1,
1333
+ gap: 2,
1334
+ icon: ArrowLeftIcon,
1335
+ mode: "ghost",
1336
+ onClick: back.go,
1337
+ padding: 2,
1338
+ text: back.label
1339
+ })
1340
+ }) ]
1341
+ });
1342
+ }
1343
+
1344
+ function DetailTitle({busy: busy = !1, documentGdr: documentGdr, title: title}) {
1345
+ const chipPullback = -useSpaceToken(1);
1346
+ /* @__PURE__ */
1347
+ return jsxs(Flex, {
1348
+ align: "center",
1349
+ gap: 2,
1350
+ paddingLeft: 2,
1351
+ children: [
1352
+ /* @__PURE__ */ jsx(Box, {
1353
+ style: {
1354
+ minWidth: 0
1355
+ },
1356
+ children: /* @__PURE__ */ jsx(Text, {
1357
+ size: 1,
1358
+ textOverflow: "ellipsis",
1359
+ weight: "semibold",
1360
+ children: title
1361
+ })
1362
+ }), documentGdr === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, {
1363
+ children: [
1364
+ /* @__PURE__ */ jsx(Text, {
1365
+ muted: !0,
1366
+ size: 1,
1367
+ children: "in"
1368
+ }),
1369
+ /* @__PURE__ */ jsx(Flex, {
1370
+ align: "center",
1371
+ flex: "none",
1372
+ style: {
1373
+ height: 0,
1374
+ marginLeft: chipPullback
1375
+ },
1376
+ children: /* @__PURE__ */ jsx(DocPreviewLink, {
1377
+ gdr: documentGdr,
1378
+ layout: "inline",
1379
+ source: "detail-title"
1380
+ })
1381
+ }) ]
1382
+ }),
1383
+ /* @__PURE__ */ jsx(SpinnerSlot, {
1384
+ busy: busy
1385
+ }) ]
1386
+ });
1387
+ }
1388
+
1389
+ const INSTANCE_CAP_COUNT_NOTE = `Counts from the latest ${INSTANCE_CAP} workflows — older ones aren’t counted.`, NEWEST_INSTANCES = {
1390
+ includeCompleted: !0
1391
+ };
1392
+
1393
+ function useToolInstances() {
1394
+ const {rows: rows, truncated: truncated, loading: loading, unreadable: unreadable} = useCappedInstances(NEWEST_INSTANCES);
1395
+ return useMemo(() => ({
1396
+ inFlight: rows.filter(instance => terminalState(instance) === "in-flight"),
1397
+ settled: rows.filter(instance => terminalState(instance) !== "in-flight"),
1398
+ truncated: truncated,
1399
+ loading: loading,
1400
+ unreadable: unreadable
1401
+ }), [ rows, truncated, loading, unreadable ]);
1402
+ }
1403
+
1404
+ function DefinitionDetail({definitionName: definitionName, backToList: backToList}) {
1405
+ 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__ */ jsx(LogEventOnMount, {
1406
+ data: {
1407
+ found: definition !== void 0,
1408
+ ...definitionFingerprint(definition)
1409
+ },
1410
+ event: WorkflowDefinitionDetailViewed
1411
+ });
1412
+ return !loading && error === void 0 && definition === void 0 ? /* @__PURE__ */ jsxs(Box, {
1413
+ paddingX: 3,
1414
+ children: [ viewed,
1415
+ /* @__PURE__ */ jsx(TabStatusRow, {
1416
+ children: /* @__PURE__ */ jsx(DetailNotFound, {
1417
+ back: backToList,
1418
+ message: /* @__PURE__ */ jsxs(Fragment, {
1419
+ children: [ "Unable to find a definition with name “",
1420
+ /* @__PURE__ */ jsx("strong", {
1421
+ children: definitionName
1422
+ }), "”." ]
1423
+ })
1424
+ })
1425
+ }) ]
1426
+ }) : /* @__PURE__ */ jsxs(Flex, {
1427
+ direction: "column",
1428
+ height: "fill",
1429
+ overflow: "hidden",
1430
+ children: [ viewed,
1431
+ /* @__PURE__ */ jsx(Box, {
1432
+ flex: "none",
1433
+ paddingBottom: 2,
1434
+ paddingTop: 3,
1435
+ paddingX: 3,
1436
+ children: loading ? /* @__PURE__ */ jsx(Box, {
1437
+ paddingLeft: 2,
1438
+ children: /* @__PURE__ */ jsx(LoadingRow, {
1439
+ label: "Loading definition…"
1440
+ })
1441
+ }) : /* @__PURE__ */ jsx(DetailTitle, {
1442
+ title: definition?.title ?? definitionName
1443
+ })
1444
+ }),
1445
+ /* @__PURE__ */ jsx(Box, {
1446
+ flex: 1,
1447
+ overflow: "auto",
1448
+ paddingX: 3,
1449
+ children: /* @__PURE__ */ jsx(Flex, {
1450
+ direction: "column",
1451
+ paddingBottom: 5,
1452
+ paddingLeft: 2,
1453
+ paddingTop: 4,
1454
+ style: {
1455
+ boxSizing: "border-box",
1456
+ minHeight: "100%"
1457
+ },
1458
+ children: /* @__PURE__ */ jsx(DefinitionBody, {
1459
+ definition: definition,
1460
+ error: error
1461
+ })
1462
+ })
1463
+ }) ]
1464
+ });
1465
+ }
1466
+
1467
+ function DefinitionBody({definition: definition, error: error}) {
1468
+ return error !== void 0 ? /* @__PURE__ */ jsx(MutedNote, {
1469
+ text: `Could not load the deployed workflows: ${error}`
1470
+ }) : definition === void 0 ? null : /* @__PURE__ */ jsx(DefinitionFacts, {
1471
+ definition: definition
1472
+ });
1473
+ }
1474
+
1475
+ function SetupIssues({bindings: bindings}) {
1476
+ const schema = useSchema();
1477
+ return bindings.length === 0 ? null : /* @__PURE__ */ jsx(Card, {
1478
+ padding: 3,
1479
+ radius: 3,
1480
+ tone: "caution",
1481
+ children: /* @__PURE__ */ jsxs(Flex, {
1482
+ align: "flex-start",
1483
+ gap: 3,
1484
+ children: [
1485
+ /* @__PURE__ */ jsx(Text, {
1486
+ size: 1,
1487
+ children: /* @__PURE__ */ jsx(WarningOutlineIcon, {})
1488
+ }),
1489
+ /* @__PURE__ */ jsxs(Stack, {
1490
+ flex: 1,
1491
+ gap: 3,
1492
+ children: [
1493
+ /* @__PURE__ */ jsx(Text, {
1494
+ size: 1,
1495
+ weight: "semibold",
1496
+ children: "Setup issue"
1497
+ }), bindings.map(binding => /* @__PURE__ */ jsx(Text, {
1498
+ size: 1,
1499
+ children: `${schema.get(binding.docType)?.title ?? binding.docType}: ${binding.detail}.`
1500
+ }, binding.docType)),
1501
+ /* @__PURE__ */ jsx(Text, {
1502
+ muted: !0,
1503
+ size: 1,
1504
+ children: "Editors see this workflow disabled on these document types until a developer fixes it."
1505
+ }) ]
1506
+ }) ]
1507
+ })
1508
+ });
1509
+ }
1510
+
1511
+ function DefinitionFacts({definition: definition}) {
1512
+ const {mappingIssues: mappingIssues, mappings: mappings} = useWorkflowContext(), schema = useSchema(), instances = useToolInstances(), snapshotWidth = useContainerToken(2), [row] = definitionCatalog({
1513
+ definitions: [ definition ],
1514
+ mappings: mappings,
1515
+ inFlight: instances.inFlight,
1516
+ issues: mappingIssues
1517
+ }), runsOn = docTypeLabels(row?.docTypes ?? [], name => schema.get(name)?.title).map(docType => docType.title);
1518
+ /* @__PURE__ */
1519
+ return jsxs(Flex, {
1520
+ direction: "column",
1521
+ flex: 1,
1522
+ gap: 4,
1523
+ children: [ row === void 0 || row.brokenBindings.length === 0 ? null : /* @__PURE__ */ jsx(Box, {
1524
+ flex: "none",
1525
+ children: /* @__PURE__ */ jsx(SetupIssues, {
1526
+ bindings: row.brokenBindings
1527
+ })
1528
+ }),
1529
+ /* @__PURE__ */ jsx(Box, {
1530
+ flex: "none",
1531
+ style: {
1532
+ maxWidth: snapshotWidth
1533
+ },
1534
+ children: /* @__PURE__ */ jsxs(Stack, {
1535
+ gap: 4,
1536
+ children: [
1537
+ /* @__PURE__ */ jsx(MetaRow, {
1538
+ label: "Deployed",
1539
+ children: /* @__PURE__ */ jsx(Text, {
1540
+ size: 1,
1541
+ children: deployedPhrase(definition)
1542
+ })
1543
+ }),
1544
+ /* @__PURE__ */ jsx(MetaRow, {
1545
+ label: "Active instances",
1546
+ children: instances.loading ? /* @__PURE__ */ jsx(Text, {
1547
+ muted: !0,
1548
+ size: 1,
1549
+ children: "—"
1550
+ }) : /* @__PURE__ */ jsxs(Stack, {
1551
+ gap: 2,
1552
+ children: [
1553
+ /* @__PURE__ */ jsx(Text, {
1554
+ size: 1,
1555
+ children: row?.activeCount ?? 0
1556
+ }), instances.truncated ? /* @__PURE__ */ jsx(Text, {
1557
+ muted: !0,
1558
+ size: 1,
1559
+ children: INSTANCE_CAP_COUNT_NOTE
1560
+ }) : null ]
1561
+ })
1562
+ }),
1563
+ /* @__PURE__ */ jsx(MetaRow, {
1564
+ label: "Trigger",
1565
+ children: /* @__PURE__ */ jsx(Text, {
1566
+ size: 1,
1567
+ children: startPhrase(definition)
1568
+ })
1569
+ }),
1570
+ /* @__PURE__ */ jsx(MetaRow, {
1571
+ label: "Document types",
1572
+ children: runsOn.length === 0 ? /* @__PURE__ */ jsx(Text, {
1573
+ muted: !0,
1574
+ size: 1,
1575
+ children: "No document types in this Studio"
1576
+ }) : /* @__PURE__ */ jsx(Text, {
1577
+ size: 1,
1578
+ children: runsOn.join(", ")
1579
+ })
1580
+ }),
1581
+ /* @__PURE__ */ jsx(MetaRow, {
1582
+ label: "Description",
1583
+ children: definition.description === void 0 ? /* @__PURE__ */ jsx(Text, {
1584
+ muted: !0,
1585
+ size: 1,
1586
+ children: /* @__PURE__ */ jsx("em", {
1587
+ children: "No description"
1588
+ })
1589
+ }) : /* @__PURE__ */ jsx(Text, {
1590
+ size: 1,
1591
+ style: {
1592
+ whiteSpace: "pre-wrap"
1593
+ },
1594
+ children: definition.description
1595
+ })
1596
+ }) ]
1597
+ })
1598
+ }),
1599
+ /* @__PURE__ */ jsx(Flex, {
1600
+ direction: "column",
1601
+ flex: 1,
1602
+ style: {
1603
+ minHeight: 340
1604
+ },
1605
+ children: /* @__PURE__ */ jsx(MetaRow, {
1606
+ fillHeight: !0,
1607
+ label: "Stages",
1608
+ children: /* @__PURE__ */ jsx(WorkflowDiagram, {
1609
+ definition: definition,
1610
+ explain: !0,
1611
+ fill: !0,
1612
+ height: "100%"
1613
+ })
1614
+ })
1615
+ }) ]
1616
+ });
1617
+ }
1618
+
1619
+ const MAX_VISIBLE_DOC_TYPES = 4, COLUMNS = {
1620
+ definition: 6,
1621
+ instances: 2,
1622
+ docTypes: 2
1623
+ };
1624
+
1625
+ function Col({flex: flex, children: children}) {
1626
+ /* @__PURE__ */
1627
+ return jsx(Box, {
1628
+ flex: flex,
1629
+ paddingRight: 3,
1630
+ children: children
1631
+ });
1632
+ }
1633
+
1634
+ function DefinitionsTab({onOpenDefinition: onOpenDefinition}) {
1635
+ const {definitions: definitions, error: error} = useDeployedDefinitions(), {mappingIssues: mappingIssues, mappings: mappings} = useWorkflowContext(), schema = useSchema(), instances = useToolInstances(), undeployed = /* @__PURE__ */ jsx(UndeployedWorkflows, {
1636
+ notices: undeployedWorkflowNotices({
1637
+ issues: mappingIssues,
1638
+ titleOf: name => schema.get(name)?.title
1639
+ })
1640
+ });
1641
+ if (error !== void 0) /* @__PURE__ */
1642
+ return jsx(TabStatusRow, {
1643
+ children: /* @__PURE__ */ jsx(Text, {
1644
+ muted: !0,
1645
+ size: 1,
1646
+ children: `Could not load the deployed workflows: ${error}`
1647
+ })
1648
+ });
1649
+ if (definitions === void 0) /* @__PURE__ */
1650
+ return jsx(TabStatusRow, {
1651
+ children: /* @__PURE__ */ jsx(LoadingRow, {
1652
+ label: "Loading definitions…"
1653
+ })
1654
+ });
1655
+ if (definitions.length === 0) /* @__PURE__ */
1656
+ return jsxs(Flex, {
1657
+ direction: "column",
1658
+ flex: 1,
1659
+ gap: 3,
1660
+ paddingBottom: 4,
1661
+ paddingTop: 3,
1662
+ children: [ undeployed,
1663
+ /* @__PURE__ */ jsx(EmptyState, {
1664
+ description: "Workflow definitions deployed to this project will appear here",
1665
+ icon: /* @__PURE__ */ jsx(TransferIcon, {}),
1666
+ title: "No definitions deployed"
1667
+ }) ]
1668
+ });
1669
+ const rows = definitionCatalog({
1670
+ definitions: definitions,
1671
+ mappings: mappings,
1672
+ inFlight: instances.inFlight,
1673
+ issues: mappingIssues
1674
+ }), active = rows.reduce((sum, row) => sum + row.activeCount, 0);
1675
+ /* @__PURE__ */
1676
+ return jsxs(Stack, {
1677
+ gap: 3,
1678
+ paddingBottom: 4,
1679
+ paddingTop: 3,
1680
+ children: [ undeployed,
1681
+ /* @__PURE__ */ jsx(Box, {
1682
+ paddingX: 2,
1683
+ children: /* @__PURE__ */ jsxs(Flex, {
1684
+ align: "center",
1685
+ gap: 2,
1686
+ children: [
1687
+ /* @__PURE__ */ jsx(Text, {
1688
+ size: 1,
1689
+ weight: "semibold",
1690
+ children: pluralize("definition", rows.length, !0)
1691
+ }),
1692
+ /* @__PURE__ */ jsx(Text, {
1693
+ muted: !0,
1694
+ size: 1,
1695
+ children: instances.loading ? "counting active instances…" : activeSummary(active)
1696
+ }) ]
1697
+ })
1698
+ }),
1699
+ /* @__PURE__ */ jsxs(Stack, {
1700
+ gap: 2,
1701
+ children: [
1702
+ /* @__PURE__ */ jsx(Card, {
1703
+ paddingX: 2,
1704
+ paddingY: 2,
1705
+ style: {
1706
+ position: "sticky",
1707
+ top: 0,
1708
+ zIndex: 1
1709
+ },
1710
+ children: /* @__PURE__ */ jsxs(Flex, {
1711
+ align: "center",
1712
+ gap: 4,
1713
+ children: [
1714
+ /* @__PURE__ */ jsx(Col, {
1715
+ flex: COLUMNS.definition,
1716
+ children: /* @__PURE__ */ jsx(Text, {
1717
+ muted: !0,
1718
+ size: 1,
1719
+ weight: "medium",
1720
+ children: "Definition"
1721
+ })
1722
+ }),
1723
+ /* @__PURE__ */ jsx(Col, {
1724
+ flex: COLUMNS.instances,
1725
+ children: /* @__PURE__ */ jsxs(Flex, {
1726
+ align: "center",
1727
+ gap: 2,
1728
+ children: [
1729
+ /* @__PURE__ */ jsx(Text, {
1730
+ muted: !0,
1731
+ size: 1,
1732
+ weight: "medium",
1733
+ children: "Active instances"
1734
+ }),
1735
+ /* @__PURE__ */ jsx(HoverHint, {
1736
+ text: "Counts only active instances — completed and aborted workflows are not included",
1737
+ children: /* @__PURE__ */ jsx(Text, {
1738
+ muted: !0,
1739
+ size: 1,
1740
+ style: {
1741
+ opacity: .7
1742
+ },
1743
+ children: /* @__PURE__ */ jsx(InfoOutlineIcon, {})
1744
+ })
1745
+ }) ]
1746
+ })
1747
+ }),
1748
+ /* @__PURE__ */ jsx(Col, {
1749
+ flex: COLUMNS.docTypes,
1750
+ children: /* @__PURE__ */ jsx(Text, {
1751
+ muted: !0,
1752
+ size: 1,
1753
+ weight: "medium",
1754
+ children: "Document types"
1755
+ })
1756
+ }) ]
1757
+ })
1758
+ }),
1759
+ /* @__PURE__ */ jsx(Stack, {
1760
+ gap: 1,
1761
+ children: rows.map(row => /* @__PURE__ */ jsx(DefinitionRow, {
1762
+ countPending: instances.loading,
1763
+ onOpen: () => onOpenDefinition(row.name),
1764
+ row: row
1765
+ }, row.name))
1766
+ }) ]
1767
+ }), instances.truncated ? /* @__PURE__ */ jsx(MutedNote, {
1768
+ text: INSTANCE_CAP_COUNT_NOTE
1769
+ }) : null ]
1770
+ });
1771
+ }
1772
+
1773
+ function activeSummary(active) {
1774
+ return `${pluralize("active instance", active, !0)}`;
1775
+ }
1776
+
1777
+ const ROW_ISSUES_HINT = "This definition has issues";
1778
+
1779
+ function UndeployedWorkflows({notices: notices}) {
1780
+ return notices.length === 0 ? null :
1781
+ /* @__PURE__ */ jsx(Stack, {
1782
+ gap: 2,
1783
+ paddingBottom: 1,
1784
+ children: notices.map(notice => /* @__PURE__ */ jsx(CautionNote, {
1785
+ label: notice
1786
+ }, notice))
1787
+ });
1788
+ }
1789
+
1790
+ function DefinitionRow({countPending: countPending, onOpen: onOpen, row: row}) {
1791
+ const flagged = row.brokenBindings.length > 0, card = /* @__PURE__ */ jsx(Card, {
1792
+ as: "button",
1793
+ onClick: onOpen,
1794
+ paddingX: 2,
1795
+ paddingY: 3,
1796
+ radius: 2,
1797
+ style: {
1798
+ textAlign: "left",
1799
+ width: "100%"
1800
+ },
1801
+ ...flagged ? {
1802
+ tone: "caution"
1803
+ } : {},
1804
+ children: /* @__PURE__ */ jsxs(Flex, {
1805
+ align: "flex-start",
1806
+ gap: 4,
1807
+ children: [
1808
+ /* @__PURE__ */ jsx(Col, {
1809
+ flex: COLUMNS.definition,
1810
+ children: /* @__PURE__ */ jsxs(Flex, {
1811
+ align: "flex-start",
1812
+ gap: 3,
1813
+ paddingLeft: flagged ? 1 : 0,
1814
+ children: [ flagged ? /* @__PURE__ */ jsx(Text, {
1815
+ size: 1,
1816
+ children: /* @__PURE__ */ jsx(WarningOutlineIcon, {})
1817
+ }) : null,
1818
+ /* @__PURE__ */ jsxs(Stack, {
1819
+ flex: 1,
1820
+ gap: 3,
1821
+ style: {
1822
+ minWidth: 0
1823
+ },
1824
+ children: [
1825
+ /* @__PURE__ */ jsx(Text, {
1826
+ size: 1,
1827
+ weight: "semibold",
1828
+ children: row.title
1829
+ }), row.description === void 0 ? /* @__PURE__ */ jsx(Text, {
1830
+ muted: !0,
1831
+ size: 1,
1832
+ children: /* @__PURE__ */ jsx("em", {
1833
+ children: "No description"
1834
+ })
1835
+ }) : /* @__PURE__ */ jsx(Text, {
1836
+ muted: !0,
1837
+ size: 1,
1838
+ children: row.description
1839
+ }) ]
1840
+ }) ]
1841
+ })
1842
+ }),
1843
+ /* @__PURE__ */ jsx(Col, {
1844
+ flex: COLUMNS.instances,
1845
+ children: /* @__PURE__ */ jsx(Text, {
1846
+ muted: !0,
1847
+ size: 1,
1848
+ children: countPending ? "—" : `${row.activeCount} active`
1849
+ })
1850
+ }),
1851
+ /* @__PURE__ */ jsx(Col, {
1852
+ flex: COLUMNS.docTypes,
1853
+ children: /* @__PURE__ */ jsx(DocTypeChips, {
1854
+ docTypes: row.docTypes
1855
+ })
1856
+ }) ]
1857
+ })
1858
+ });
1859
+ return flagged ? /* @__PURE__ */ jsx(HoverHint, {
1860
+ fill: !0,
1861
+ text: ROW_ISSUES_HINT,
1862
+ children: card
1863
+ }) : card;
1864
+ }
1865
+
1866
+ function DocTypeChips({docTypes: docTypes}) {
1867
+ const schema = useSchema(), badgeTrim = useBadgeCapTrim();
1868
+ if (docTypes.length === 0) /* @__PURE__ */
1869
+ return jsx(Text, {
1870
+ muted: !0,
1871
+ size: 1,
1872
+ children: "—"
1873
+ });
1874
+ const labels = docTypeLabels(docTypes, name => schema.get(name)?.title), shown = labels.slice(0, MAX_VISIBLE_DOC_TYPES), overflow = labels.length - shown.length;
1875
+ /* @__PURE__ */
1876
+ return jsxs(Flex, {
1877
+ align: "center",
1878
+ gap: 2,
1879
+ style: badgeTrim,
1880
+ wrap: "wrap",
1881
+ children: [ shown.map(docType =>
1882
+ /* @__PURE__ */ jsx(Badge, {
1883
+ fontSize: 1,
1884
+ style: {
1885
+ minWidth: 0
1886
+ },
1887
+ children: /* @__PURE__ */ jsx("span", {
1888
+ style: {
1889
+ display: "block",
1890
+ overflow: "hidden",
1891
+ textOverflow: "ellipsis",
1892
+ whiteSpace: "nowrap"
1893
+ },
1894
+ children: docType.title
1895
+ })
1896
+ }, docType.name)), overflow > 0 ? /* @__PURE__ */ jsxs(Text, {
1897
+ muted: !0,
1898
+ size: 1,
1899
+ children: [ "+", overflow, " more" ]
1900
+ }) : null ]
1901
+ });
1902
+ }
1903
+
1904
+ function normalizeAbortReason(raw) {
1905
+ const trimmed = raw.trim();
1906
+ return trimmed === "" ? void 0 : trimmed;
1907
+ }
1908
+
1909
+ function abortedToast(title) {
1910
+ return {
1911
+ id: TOAST_ID.abort,
1912
+ status: "info",
1913
+ title: `Aborted “${title}”`
1914
+ };
1915
+ }
1916
+
1917
+ function abortAlreadySettledToast(title) {
1918
+ return {
1919
+ id: TOAST_ID.abort,
1920
+ status: "info",
1921
+ title: `“${title}” had already finished`,
1922
+ description: "Nothing was aborted — the workflow reached a terminal state first."
1923
+ };
1924
+ }
1925
+
1926
+ function abortUnconfirmedToast(args) {
1927
+ return {
1928
+ id: TOAST_ID.abort,
1929
+ status: "warning",
1930
+ title: `Couldn’t confirm “${args.title}” was aborted`,
1931
+ description: `${describeError(args.err)} — the abort may still have committed, so check the workflow’s state before trying again.`
1932
+ };
1933
+ }
1934
+
1935
+ function AbortWorkflowDialog({entry: entry, onClose: onClose}) {
1936
+ const {engine: engine} = useWorkflowContext(), definition = useDefinition(entry), toast = useWorkflowToast(), [typedReason, setTypedReason] = useState(""), [pending, setPending] = useState(!1), {instance: instance} = entry, title = instanceTitle(instance, definition), reason = normalizeAbortReason(typedReason), reasonInputId = `abort-workflow-reason-${instance._id}`, confirm = async () => {
1937
+ if (reason !== void 0) {
1938
+ setPending(!0);
1939
+ try {
1940
+ const {changed: changed} = await engine.abortInstance({
1941
+ instanceId: instance._id,
1942
+ reason: reason
1943
+ });
1944
+ toast.push(changed ? abortedToast(title) : abortAlreadySettledToast(title)), onClose();
1945
+ } catch (err) {
1946
+ toast.push(abortUnconfirmedToast({
1947
+ title: title,
1948
+ err: err
1949
+ })), setPending(!1);
1950
+ }
1951
+ }
1952
+ };
1953
+ /* @__PURE__ */
1954
+ return jsx(Dialog, {
1955
+ footer: /* @__PURE__ */ jsx(Box, {
1956
+ padding: 3,
1957
+ children: /* @__PURE__ */ jsxs(Flex, {
1958
+ gap: 2,
1959
+ justify: "flex-end",
1960
+ children: [
1961
+ /* @__PURE__ */ jsx(Button, {
1962
+ disabled: pending,
1963
+ fontSize: 1,
1964
+ mode: "bleed",
1965
+ onClick: onClose,
1966
+ padding: 2,
1967
+ text: "Cancel"
1968
+ }),
1969
+ /* @__PURE__ */ jsx(Button, {
1970
+ disabled: reason === void 0,
1971
+ fontSize: 1,
1972
+ loading: pending,
1973
+ onClick: () => {
1974
+ confirm();
1975
+ },
1976
+ padding: 2,
1977
+ text: "Abort workflow",
1978
+ tone: "critical"
1979
+ }) ]
1980
+ })
1981
+ }),
1982
+ header: "Abort workflow",
1983
+ id: `abort-workflow-${instance._id}`,
1984
+ onClose: () => {
1985
+ pending || onClose();
1986
+ },
1987
+ width: 0,
1988
+ children: /* @__PURE__ */ jsx(Box, {
1989
+ padding: 4,
1990
+ children: /* @__PURE__ */ jsxs(Stack, {
1991
+ gap: 5,
1992
+ children: [
1993
+ /* @__PURE__ */ jsxs(Text, {
1994
+ size: 1,
1995
+ children: [ "Abort the ",
1996
+ /* @__PURE__ */ jsx("strong", {
1997
+ children: title
1998
+ }), " workflow? This cannot be undone." ]
1999
+ }),
2000
+ /* @__PURE__ */ jsxs(Stack, {
2001
+ gap: 3,
2002
+ children: [
2003
+ /* @__PURE__ */ jsx(Text, {
2004
+ as: "label",
2005
+ htmlFor: reasonInputId,
2006
+ size: 1,
2007
+ weight: "medium",
2008
+ children: "Reason"
2009
+ }),
2010
+ /* @__PURE__ */ jsx(TextInput, {
2011
+ autoFocus: !0,
2012
+ fontSize: 1,
2013
+ id: reasonInputId,
2014
+ onChange: event => setTypedReason(event.currentTarget.value),
2015
+ placeholder: "Why is this workflow being stopped?",
2016
+ value: typedReason
2017
+ }) ]
2018
+ }) ]
2019
+ })
2020
+ })
2021
+ });
2022
+ }
2023
+
2024
+ function AbortWorkflowSection({entry: entry}) {
2025
+ const [aborting, setAborting] = useState(!1);
2026
+ return isLiveEntry(entry) ? /* @__PURE__ */ jsxs(Stack, {
2027
+ gap: 4,
2028
+ children: [
2029
+ /* @__PURE__ */ jsx(Text, {
2030
+ size: 1,
2031
+ weight: "semibold",
2032
+ children: "Manage workflow"
101
2033
  }),
102
2034
  /* @__PURE__ */ jsx(Flex, {
103
2035
  children: /* @__PURE__ */ jsx(Button, {
104
2036
  fontSize: 1,
105
- icon: ArrowLeftIcon,
106
2037
  mode: "ghost",
107
- onClick: onBack,
2038
+ onClick: () => setAborting(!0),
108
2039
  padding: 2,
109
- text: "Back to workflows"
2040
+ text: "Abort workflow…",
2041
+ tone: "critical"
2042
+ })
2043
+ }), aborting ? /* @__PURE__ */ jsx(AbortWorkflowDialog, {
2044
+ entry: entry,
2045
+ onClose: () => setAborting(!1)
2046
+ }) : null ]
2047
+ }) : null;
2048
+ }
2049
+
2050
+ function makeTitleResolver(definition) {
2051
+ const activities = (definition?.stages ?? []).flatMap(s => s.activities ?? []);
2052
+ return {
2053
+ action: (activityName, actionName) => activities.find(t => t.name === activityName)?.actions?.find(a => a.name === actionName)?.title ?? actionName,
2054
+ activity: name => activities.find(t => t.name === name)?.title ?? name,
2055
+ stage: name => stageTitle(definition, name)
2056
+ };
2057
+ }
2058
+
2059
+ function attributed(actor, ...parts) {
2060
+ if (actor) return {
2061
+ actorId: actor.id,
2062
+ parts: parts
2063
+ };
2064
+ const [first, ...rest] = parts;
2065
+ return typeof first != "string" ? {
2066
+ actorId: void 0,
2067
+ parts: parts
2068
+ } : {
2069
+ actorId: void 0,
2070
+ parts: [ first.charAt(0).toUpperCase() + first.slice(1), ...rest ]
2071
+ };
2072
+ }
2073
+
2074
+ function standalone(...parts) {
2075
+ return {
2076
+ actorId: void 0,
2077
+ parts: parts
2078
+ };
2079
+ }
2080
+
2081
+ const historyLiner = {
2082
+ stageEntered: (h, titles) => attributed(h.actor, `entered ${titles.stage(h.stage)}${h.fromStage ? ` from ${titles.stage(h.fromStage)}` : ""}`),
2083
+ stageExited: (h, titles) => attributed(h.actor, `left ${titles.stage(h.stage)} for ${titles.stage(h.toStage)}`),
2084
+ activityStatusChanged: (h, titles) => attributed(h.actor, `changed ${titles.activity(h.activity)}: ${h.from} → ${h.to}`),
2085
+ actionFired: (h, titles) => {
2086
+ const phrase = `fired ${titles.action(h.activity, h.action)} on ${titles.activity(h.activity)}`;
2087
+ return h.triggered ? standalone(`The workflow ${phrase}`) : attributed(h.actor, phrase);
2088
+ },
2089
+ transitionFired: (h, titles) => attributed(h.actor, `moved ${titles.stage(h.fromStage)} → ${titles.stage(h.toStage)}`),
2090
+ effectQueued: h => standalone("Effect ", {
2091
+ code: h.effect
2092
+ }, " queued"),
2093
+ effectCompleted: h => {
2094
+ const detail = h.detail ? ` — ${h.detail}` : "";
2095
+ return h.actor ? attributed(h.actor, "resolved effect ", {
2096
+ code: h.effect
2097
+ }, ` — ${h.status}${detail}`) : standalone("Effect ", {
2098
+ code: h.effect
2099
+ }, ` ${h.status}${detail}`);
2100
+ },
2101
+ effectClaimReleased: h => attributed(h.actor, "released a stale claim on effect ", {
2102
+ code: h.effect
2103
+ }, ` ${h.via === "sweep" ? "via the sweeper" : "via a drain takeover"}`),
2104
+ spawned: (h, titles) => attributed(h.actor, `spawned a child workflow from ${titles.activity(h.activity)}`),
2105
+ subworkflowAdopted: (h, titles) => standalone(`Re-adopted child workflow into ${titles.activity(h.activity)} on re-entering ${titles.stage(h.stage)}`),
2106
+ subworkflowResolved: (h, titles) => standalone(`Child workflow of ${titles.activity(h.activity)} ${h.status === "done" ? "completed" : "was aborted"}`),
2107
+ subworkflowOrphaned: h => standalone(`Orphaned child workflow reached terminal — ${h.detail}`),
2108
+ aborted: (h, titles) => attributed(h.actor, `aborted the workflow at ${titles.stage(h.stage)}${h.reason ? ` — ${h.reason}` : ""}`),
2109
+ opApplied: (h, titles) => h.action ? attributed(h.actor, `changed state via action ${titles.action(h.activity ?? "", h.action)}`) : h.activity ? attributed(h.actor, `changed state via activity ${titles.activity(h.activity)}`) : attributed(h.actor, "changed state via ", {
2110
+ code: h.opType
2111
+ }),
2112
+ fieldQueryDiscarded: h => standalone({
2113
+ code: JSON.stringify(h)
2114
+ })
2115
+ };
2116
+
2117
+ function historyLine(h, titles) {
2118
+ return historyLiner[h._type](h, titles);
2119
+ }
2120
+
2121
+ function PendingEffectRows({instance: instance, now: now}) {
2122
+ const {drainEffectsFor: drainEffectsFor, completeEffectFor: completeEffectFor, effectHandlers: effectHandlers} = useWorkflowContext(), toast = useWorkflowToast(), [busy, setBusy] = useState(!1), pending = instance.pendingEffects ?? [];
2123
+ if (pending.length === 0) return null;
2124
+ const runRegistered = async () => {
2125
+ setBusy(!0);
2126
+ try {
2127
+ await drainEffectsFor(instance._id), toast.push({
2128
+ id: TOAST_ID.effectsDrain,
2129
+ status: "info",
2130
+ title: "Ran the registered handlers"
2131
+ });
2132
+ } catch (err) {
2133
+ toast.push({
2134
+ id: TOAST_ID.effectsDrain,
2135
+ status: "error",
2136
+ title: "Failed to run pending effects",
2137
+ description: describeError(err)
2138
+ });
2139
+ } finally {
2140
+ setBusy(!1);
2141
+ }
2142
+ }, resolve = async (effectKey, status) => {
2143
+ setBusy(!0);
2144
+ try {
2145
+ await completeEffectFor(instance._id, {
2146
+ effectKey: effectKey,
2147
+ status: status,
2148
+ detail: "Resolved in Studio"
2149
+ }), toast.push({
2150
+ id: TOAST_ID.effectResolve,
2151
+ status: "info",
2152
+ title: `Effect marked as ${status}`
2153
+ });
2154
+ } catch (err) {
2155
+ toast.push({
2156
+ id: TOAST_ID.effectResolve,
2157
+ status: "error",
2158
+ title: "Failed to resolve the effect",
2159
+ description: describeError(err)
2160
+ });
2161
+ } finally {
2162
+ setBusy(!1);
2163
+ }
2164
+ };
2165
+ /* @__PURE__ */
2166
+ return jsx(Fragment, {
2167
+ children: pending.map(fx => /* @__PURE__ */ jsx(PendingEffectRow, {
2168
+ busy: busy,
2169
+ effect: fx,
2170
+ instanceId: instance._id,
2171
+ now: now,
2172
+ onResolve: status => resolve(fx._key, status),
2173
+ onRunHandlers: runRegistered,
2174
+ registered: fx.name in effectHandlers
2175
+ }, fx._key))
2176
+ });
2177
+ }
2178
+
2179
+ function PendingEffectRow({busy: busy, effect: effect, instanceId: instanceId, now: now, onResolve: onResolve, onRunHandlers: onRunHandlers, registered: registered}) {
2180
+ const label = effect.title ?? effect.name, avatarSize = useAvatarSize();
2181
+ /* @__PURE__ */
2182
+ return jsxs(Flex, {
2183
+ align: "center",
2184
+ gap: 2,
2185
+ children: [
2186
+ /* @__PURE__ */ jsx(FeedGlyph, {
2187
+ hint: "Waiting to run",
2188
+ icon: /* @__PURE__ */ jsx(ClockIcon, {})
2189
+ }),
2190
+ /* @__PURE__ */ jsxs(Flex, {
2191
+ align: "center",
2192
+ flex: 1,
2193
+ gap: 1,
2194
+ style: {
2195
+ minWidth: 0
2196
+ },
2197
+ children: [
2198
+ /* @__PURE__ */ jsx(Box, {
2199
+ style: {
2200
+ minWidth: 0
2201
+ },
2202
+ children: /* @__PURE__ */ jsxs(Text, {
2203
+ size: 1,
2204
+ textOverflow: "ellipsis",
2205
+ children: [ effect.title ?? /* @__PURE__ */ jsx(CodeChip, {
2206
+ value: effect.name
2207
+ }), registered ? " — ready to run in Studio" : " — waiting for the runtime" ]
2208
+ })
2209
+ }),
2210
+ /* @__PURE__ */ jsx(Text, {
2211
+ muted: !0,
2212
+ size: 1,
2213
+ children: "·"
2214
+ }),
2215
+ /* @__PURE__ */ jsx(HoverHint, {
2216
+ text: formatDateTime(effect.queuedAt),
2217
+ children: /* @__PURE__ */ jsx(Text, {
2218
+ muted: !0,
2219
+ size: 1,
2220
+ style: {
2221
+ whiteSpace: "nowrap"
2222
+ },
2223
+ children: formatShortAgo(effect.queuedAt, now)
2224
+ })
2225
+ }) ]
2226
+ }),
2227
+ /* @__PURE__ */ jsx(Flex, {
2228
+ align: "center",
2229
+ flex: "none",
2230
+ style: {
2231
+ height: avatarSize
2232
+ },
2233
+ children: /* @__PURE__ */ jsx(HintedMenuButton, {
2234
+ button: /* @__PURE__ */ jsx(Button, {
2235
+ "aria-label": `Resolve ${label}`,
2236
+ disabled: busy,
2237
+ fontSize: 1,
2238
+ icon: EllipsisHorizontalIcon,
2239
+ mode: "bleed",
2240
+ padding: 2
2241
+ }),
2242
+ hint: "Resolve",
2243
+ id: `pending-effect-menu-${instanceId}-${effect._key}`,
2244
+ menu: /* @__PURE__ */ jsxs(Menu, {
2245
+ children: [ registered ? /* @__PURE__ */ jsx(MenuItem, {
2246
+ onClick: onRunHandlers,
2247
+ text: "Run registered handlers"
2248
+ }) : null,
2249
+ /* @__PURE__ */ jsx(MenuItem, {
2250
+ onClick: () => onResolve("done"),
2251
+ text: "Mark done"
2252
+ }),
2253
+ /* @__PURE__ */ jsx(MenuItem, {
2254
+ onClick: () => onResolve("failed"),
2255
+ text: "Mark failed",
2256
+ tone: "critical"
2257
+ }) ]
2258
+ }),
2259
+ popover: {
2260
+ placement: "bottom-end",
2261
+ portal: !0
2262
+ }
2263
+ })
2264
+ }) ]
2265
+ });
2266
+ }
2267
+
2268
+ function ActivityLog({instance: instance, definition: definition}) {
2269
+ const titles = useMemo(() => makeTitleResolver(definition), [ definition ]), entries = useMemo(() => instance.history.toReversed(), [ instance.history ]), [now, setNow] = useState(() => /* @__PURE__ */ new Date);
2270
+ return useEffect(() => {
2271
+ const timer = setInterval(() => setNow(/* @__PURE__ */ new Date), 6e4);
2272
+ return () => clearInterval(timer);
2273
+ }, []), entries.length === 0 && (instance.pendingEffects ?? []).length === 0 ? /* @__PURE__ */ jsx(Text, {
2274
+ muted: !0,
2275
+ size: 1,
2276
+ children: "No activity yet"
2277
+ }) : /* @__PURE__ */ jsxs(Stack, {
2278
+ gap: 3,
2279
+ children: [
2280
+ /* @__PURE__ */ jsx(PendingEffectRows, {
2281
+ instance: instance,
2282
+ now: now
2283
+ }), entries.map(h => /* @__PURE__ */ jsx(HistoryRow, {
2284
+ entry: h,
2285
+ now: now,
2286
+ titles: titles
2287
+ }, h._key)) ]
2288
+ });
2289
+ }
2290
+
2291
+ function HistoryRow({entry: entry, now: now, titles: titles}) {
2292
+ const line = historyLine(entry, titles);
2293
+ /* @__PURE__ */
2294
+ return jsxs(Flex, {
2295
+ align: "center",
2296
+ gap: 2,
2297
+ children: [ line.actorId === void 0 ? /* @__PURE__ */ jsx(FeedGlyph, {
2298
+ hint: "The workflow",
2299
+ icon: /* @__PURE__ */ jsx(CogIcon, {})
2300
+ }) : /* @__PURE__ */ jsx(UserAvatar, {
2301
+ id: line.actorId
2302
+ }),
2303
+ /* @__PURE__ */ jsxs(Flex, {
2304
+ align: "center",
2305
+ flex: 1,
2306
+ gap: 1,
2307
+ style: {
2308
+ minWidth: 0
2309
+ },
2310
+ children: [
2311
+ /* @__PURE__ */ jsx(Box, {
2312
+ style: {
2313
+ minWidth: 0
2314
+ },
2315
+ children: /* @__PURE__ */ jsxs(Text, {
2316
+ size: 1,
2317
+ textOverflow: "ellipsis",
2318
+ children: [ line.actorId === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, {
2319
+ children: [
2320
+ /* @__PURE__ */ jsx(ActorNameSpan, {
2321
+ id: line.actorId
2322
+ }), " " ]
2323
+ }), line.parts.map((part, index) => typeof part == "string" ? part : /* @__PURE__ */ jsx(CodeChip, {
2324
+ value: part.code
2325
+ }, index)) ]
2326
+ })
2327
+ }),
2328
+ /* @__PURE__ */ jsx(Text, {
2329
+ muted: !0,
2330
+ size: 1,
2331
+ children: "·"
2332
+ }),
2333
+ /* @__PURE__ */ jsx(HoverHint, {
2334
+ text: formatDateTime(entry.at),
2335
+ children: /* @__PURE__ */ jsx(Text, {
2336
+ muted: !0,
2337
+ size: 1,
2338
+ style: {
2339
+ whiteSpace: "nowrap"
2340
+ },
2341
+ children: formatShortAgo(entry.at, now)
2342
+ })
2343
+ }) ]
2344
+ }) ]
2345
+ });
2346
+ }
2347
+
2348
+ function ActorNameSpan({id: id}) {
2349
+ const {name: name} = useUserDisplay(id), {font: font} = useTheme_v2();
2350
+ /* @__PURE__ */
2351
+ return jsx("span", {
2352
+ style: {
2353
+ fontWeight: font.text.weights.medium
2354
+ },
2355
+ children: name
2356
+ });
2357
+ }
2358
+
2359
+ function CodeChip({value: value}) {
2360
+ /* @__PURE__ */
2361
+ return jsx("code", {
2362
+ children: value
2363
+ });
2364
+ }
2365
+
2366
+ function FeedGlyph({hint: hint, icon: icon}) {
2367
+ const size = useAvatarSize();
2368
+ /* @__PURE__ */
2369
+ return jsx(HoverHint, {
2370
+ text: hint,
2371
+ children: /* @__PURE__ */ jsx(Flex, {
2372
+ align: "center",
2373
+ flex: "none",
2374
+ justify: "center",
2375
+ style: {
2376
+ height: size,
2377
+ width: size
2378
+ },
2379
+ children: /* @__PURE__ */ jsx(Text, {
2380
+ muted: !0,
2381
+ size: 1,
2382
+ children: icon
2383
+ })
2384
+ })
2385
+ });
2386
+ }
2387
+
2388
+ function WorkflowInstanceDetail({instanceId: instanceId, backToList: backToList}) {
2389
+ const entry = useWorkflowInstanceEntry(instanceId);
2390
+ useLogEventOnMount(WorkflowInstanceDetailViewed, {
2391
+ instanceId: instanceId
2392
+ });
2393
+ const [graceOver, setGraceOver] = useState(!1);
2394
+ return useEffect(() => {
2395
+ const timer = setTimeout(() => setGraceOver(!0), 2500);
2396
+ return () => clearTimeout(timer);
2397
+ }, [ instanceId ]), entry?.invalid ? /* @__PURE__ */ jsx(Box, {
2398
+ padding: 4,
2399
+ children: /* @__PURE__ */ jsx(InvalidDocNotice, {
2400
+ invalid: entry.invalid
2401
+ })
2402
+ }) : entry ? /* @__PURE__ */ jsx(InstanceDetailPanel, {
2403
+ entry: entry
2404
+ }) : /* @__PURE__ */ jsx(Box, {
2405
+ paddingX: 3,
2406
+ children: /* @__PURE__ */ jsx(TabStatusRow, {
2407
+ children: graceOver ? /* @__PURE__ */ jsx(DetailNotFound, {
2408
+ back: backToList,
2409
+ message: /* @__PURE__ */ jsxs(Fragment, {
2410
+ children: [ "Unable to find workflow with id “",
2411
+ /* @__PURE__ */ jsx("strong", {
2412
+ children: instanceId
2413
+ }), "”." ]
2414
+ })
2415
+ }) : /* @__PURE__ */ jsx(LoadingRow, {
2416
+ label: "Loading workflow…"
110
2417
  })
111
- }) ]
2418
+ })
112
2419
  });
113
2420
  }
114
2421
 
115
- function InstanceDetailPanel({entry: entry, onBack: onBack}) {
116
- const definition = useDefinition(entry), [openActivity, setOpenActivity] = useState(null), {instance: instance} = entry;
2422
+ function InstanceDetailPanel({entry: entry}) {
2423
+ const definition = useDefinition(entry), snapshotWidth = useContainerToken(2), [openActivity, setOpenActivity] = useState(null), {instance: instance, ready: ready} = entry;
117
2424
  return useEffect(() => {
118
2425
  openActivity && openActivityGone({
119
2426
  entry: entry,
@@ -124,36 +2431,56 @@ function InstanceDetailPanel({entry: entry, onBack: onBack}) {
124
2431
  height: "fill",
125
2432
  overflow: "hidden",
126
2433
  children: [
127
- /* @__PURE__ */ jsx(DetailHeader, {
128
- definition: definition,
129
- entry: entry,
130
- onBack: onBack
2434
+ /* @__PURE__ */ jsx(Box, {
2435
+ flex: "none",
2436
+ paddingBottom: 2,
2437
+ paddingTop: 3,
2438
+ paddingX: 3,
2439
+ children: /* @__PURE__ */ jsx(DetailTitle, {
2440
+ busy: !ready || isEvaluationStale(entry),
2441
+ documentGdr: documentRefsOf(instance)[0],
2442
+ title: instanceTitle(instance, definition)
2443
+ })
131
2444
  }),
132
2445
  /* @__PURE__ */ jsx(Box, {
133
2446
  flex: 1,
134
2447
  overflow: "auto",
135
- paddingX: 4,
136
- children: /* @__PURE__ */ jsxs(Stack, {
137
- gap: 4,
2448
+ paddingX: 3,
2449
+ children: /* @__PURE__ */ jsx(Box, {
138
2450
  paddingBottom: 5,
139
- children: [
140
- /* @__PURE__ */ jsx(StudioDocumentsSection, {
141
- instance: instance
142
- }),
143
- /* @__PURE__ */ jsx(StageDiagramSection, {
144
- definition: definition,
145
- entry: entry
146
- }),
147
- /* @__PURE__ */ jsx(GroupHeading, {
148
- title: "All tasks"
149
- }),
150
- /* @__PURE__ */ jsx(InstanceTaskLists, {
151
- entry: entry,
152
- onOpenActivity: name => setOpenActivity({
153
- stage: instance.currentStage,
154
- activityName: name
155
- })
156
- }) ]
2451
+ paddingLeft: 2,
2452
+ paddingTop: 2,
2453
+ style: {
2454
+ maxWidth: snapshotWidth
2455
+ },
2456
+ children: /* @__PURE__ */ jsxs(Stack, {
2457
+ gap: 6,
2458
+ children: [
2459
+ /* @__PURE__ */ jsx(InstanceSnapshotBody, {
2460
+ entry: entry,
2461
+ onOpenActivity: name => setOpenActivity({
2462
+ stage: instance.currentStage,
2463
+ activityName: name
2464
+ }),
2465
+ surface: "tool-instance-detail"
2466
+ }),
2467
+ /* @__PURE__ */ jsx(AbortWorkflowSection, {
2468
+ entry: entry
2469
+ }),
2470
+ /* @__PURE__ */ jsxs(Stack, {
2471
+ gap: 4,
2472
+ children: [
2473
+ /* @__PURE__ */ jsx(Text, {
2474
+ size: 1,
2475
+ weight: "semibold",
2476
+ children: "History"
2477
+ }),
2478
+ /* @__PURE__ */ jsx(ActivityLog, {
2479
+ definition: definition,
2480
+ instance: instance
2481
+ }) ]
2482
+ }) ]
2483
+ })
157
2484
  })
158
2485
  }), openActivity ? /* @__PURE__ */ jsx(ActivityDetailDialog, {
159
2486
  activityName: openActivity.activityName,
@@ -166,130 +2493,14 @@ function InstanceDetailPanel({entry: entry, onBack: onBack}) {
166
2493
  });
167
2494
  }
168
2495
 
169
- function DetailHeader({entry: entry, definition: definition, onBack: onBack}) {
170
- const {instance: instance, ready: ready} = entry;
171
- /* @__PURE__ */
172
- return jsx(Card, {
173
- flex: "none",
174
- paddingX: 4,
175
- paddingY: 3,
176
- children: /* @__PURE__ */ jsxs(Flex, {
177
- align: "center",
178
- gap: 3,
179
- wrap: "wrap",
180
- children: [
181
- /* @__PURE__ */ jsx(HoverHint, {
182
- text: "Back to workflows",
183
- children: /* @__PURE__ */ jsx(Button, {
184
- "aria-label": "Back to workflows",
185
- fontSize: 1,
186
- icon: ArrowLeftIcon,
187
- mode: "bleed",
188
- onClick: onBack,
189
- padding: 2
190
- })
191
- }),
192
- /* @__PURE__ */ jsxs(Stack, {
193
- gap: 2,
194
- children: [
195
- /* @__PURE__ */ jsx(Text, {
196
- size: 1,
197
- weight: "semibold",
198
- children: instanceTitle(instance, definition)
199
- }),
200
- /* @__PURE__ */ jsxs(Text, {
201
- muted: !0,
202
- size: 1,
203
- children: [ "Started ", formatDate(instance.startedAt) ]
204
- }) ]
205
- }), terminalState(instance) === "in-flight" ? /* @__PURE__ */ jsx(StageChip, {
206
- title: stageTitle(definition, instance.currentStage)
207
- }) : /* @__PURE__ */ jsx(AbortedBadge, {
208
- instance: instance
209
- }),
210
- /* @__PURE__ */ jsx(SpinnerSlot, {
211
- busy: !ready || isEvaluationStale(entry)
212
- }) ]
213
- })
214
- });
215
- }
216
-
217
- function StudioDocumentsSection({instance: instance}) {
218
- const refs = documentRefsOf(instance);
219
- return refs.length === 0 ? null : /* @__PURE__ */ jsxs(Stack, {
220
- gap: 3,
221
- marginTop: 3,
222
- children: [
223
- /* @__PURE__ */ jsx(Text, {
224
- muted: !0,
225
- size: 1,
226
- weight: "medium",
227
- children: refs.length === 1 ? "Studio document" : "Studio documents"
228
- }),
229
- /* @__PURE__ */ jsx(Stack, {
230
- gap: 2,
231
- children: refs.map(ref => /* @__PURE__ */ jsx(DocPreviewLink, {
232
- gdr: ref
233
- }, ref.id))
234
- }) ]
235
- });
236
- }
237
-
238
- function StageDiagramSection({entry: entry, definition: definition}) {
239
- const {instance: instance} = entry;
240
- return definition ?
241
- /* @__PURE__ */ jsx(WorkflowDiagram, {
242
- currentStage: terminalState(instance) === "in-flight" ? instance.currentStage : void 0,
243
- definition: definition,
244
- evaluation: entry.evaluation,
245
- explain: !0,
246
- guardCount: entry.guards?.length,
247
- height: 340,
248
- history: instance.history
249
- }, instance._id) : /* @__PURE__ */ jsx(Card, {
250
- border: !0,
251
- marginTop: 3,
252
- padding: 3,
253
- radius: 2,
254
- tone: "caution",
255
- children: /* @__PURE__ */ jsx(Text, {
256
- muted: !0,
257
- size: 1,
258
- children: "Couldn’t load this workflow’s full details — the stage diagram is unavailable, and technical names show instead of titles."
259
- })
260
- });
261
- }
262
-
263
- function MutedNote({text: text}) {
264
- /* @__PURE__ */
265
- return jsx(Box, {
266
- paddingY: 3,
267
- children: /* @__PURE__ */ jsx(Text, {
268
- muted: !0,
269
- size: 1,
270
- children: text
271
- })
272
- });
273
- }
274
-
275
2496
  function useStartableDefinitions() {
276
- const {engine: engine} = useWorkflowContext(), [definitions, setDefinitions] = useState(void 0), [error, setError] = useState(void 0);
277
- return useEffect(() => {
278
- let cancelled = !1;
279
- return (async () => {
280
- const rows = await readDeployedDefinitions(engine), startable = latestDeployedDefinitions(rows).filter(row => isStartableDefinition(row)).map(row => ({
281
- name: row.name,
282
- title: row.title ?? row.name,
283
- description: row.description
284
- }));
285
- cancelled || (setDefinitions(startable), setError(void 0));
286
- })().catch(err => {
287
- cancelled || setError(describeError(err));
288
- }), () => {
289
- cancelled = !0;
290
- };
291
- }, [ engine ]), {
292
- definitions: definitions,
2497
+ const {definitions: definitions, error: error} = useDeployedDefinitions();
2498
+ return {
2499
+ definitions: useMemo(() => definitions?.filter(row => isStartableDefinition(row)).map(row => ({
2500
+ name: row.name,
2501
+ title: row.title ?? row.name,
2502
+ description: row.description
2503
+ })), [ definitions ]),
293
2504
  error: error
294
2505
  };
295
2506
  }
@@ -378,197 +2589,56 @@ function DefinitionPickerPanel({onPick: onPick}) {
378
2589
  size: 1,
379
2590
  textOverflow: "ellipsis",
380
2591
  children: definition.description
381
- }) : null ]
382
- })
383
- }, definition.name))
384
- }) ]
385
- })
386
- });
387
- }
388
-
389
- function ToolActivityDialog({target: target, onClose: onClose}) {
390
- const entry = useWorkflowInstanceEntry(target.instanceId), definition = useDefinition(entry), resolvedOnce = useRef(!1);
391
- return entry && (resolvedOnce.current = !0), useEffect(() => {
392
- resolvedOnce.current && openActivityGone({
393
- entry: entry,
394
- target: target
395
- }) && onClose();
396
- }, [ entry, target, onClose ]), entry?.invalid ? /* @__PURE__ */ jsx(Dialog, {
397
- header: "Workflow unavailable",
398
- id: "workflow-activity-detail",
399
- onClose: onClose,
400
- width: 1,
401
- children: /* @__PURE__ */ jsx(Box, {
402
- padding: 4,
403
- children: /* @__PURE__ */ jsx(InvalidDocNotice, {
404
- invalid: entry.invalid
405
- })
406
- })
407
- }) : entry?.evaluation ? /* @__PURE__ */ jsx(ActivityDetailDialog, {
408
- activityName: target.activityName,
409
- breadcrumb: instanceBreadcrumb(entry.instance, definition),
410
- definition: definition,
411
- document: documentRefsOf(entry.instance)[0],
412
- entry: entry,
413
- onClose: onClose,
414
- source: "tool-task-list"
415
- }) : /* @__PURE__ */ jsx(Dialog, {
416
- header: "Loading activity…",
417
- id: "workflow-activity-detail",
418
- onClose: onClose,
419
- width: 1,
420
- children: /* @__PURE__ */ jsx(Box, {
421
- padding: 4,
422
- children: /* @__PURE__ */ jsx(LoadingRow, {
423
- label: "Fetching the live workflow state…"
424
- })
425
- })
426
- });
427
- }
428
-
429
- const GROUP_DEFAULT_OPEN = !0;
430
-
431
- function instanceBandDefaultOpen(segment) {
432
- return segment === "for-me";
433
- }
434
-
435
- const UNGROUPED_BAND_KEY = "section:no-document", PAYLOAD_VERSION = 1, MAX_ENTRIES = 200;
436
-
437
- function bandStateStorageKey(scope) {
438
- return `sanity.workflows.tool.bands:${scope}`;
439
- }
440
-
441
- function isRecord(value) {
442
- return typeof value == "object" && value !== null && !Array.isArray(value);
443
- }
444
-
445
- function bandEntryOf(value) {
446
- if (!isRecord(value)) return;
447
- const {open: open, touched: touched} = value;
448
- if (!(typeof open != "boolean" || typeof touched != "number") && Number.isFinite(touched)) return {
449
- open: open,
450
- touched: touched
451
- };
452
- }
453
-
454
- function parsePayload(raw) {
455
- try {
456
- const parsed = JSON.parse(raw);
457
- if (!isRecord(parsed) || parsed.version !== PAYLOAD_VERSION || !isRecord(parsed.bands)) return;
458
- const entries = [];
459
- for (const [key, value] of Object.entries(parsed.bands)) {
460
- const entry = bandEntryOf(value);
461
- if (entry === void 0) return;
462
- entries.push([ key, entry ]);
463
- }
464
- return Object.fromEntries(entries);
465
- } catch {
466
- return;
467
- }
468
- }
469
-
470
- function readStored(storage, storageKey) {
471
- if (!storage) return {
472
- entries: void 0,
473
- corrupt: !1
474
- };
475
- let raw;
476
- try {
477
- raw = storage.getItem(storageKey);
478
- } catch {
479
- return {
480
- entries: void 0,
481
- corrupt: !1
482
- };
483
- }
484
- if (raw === null) return {
485
- entries: {},
486
- corrupt: !1
487
- };
488
- const entries = parsePayload(raw);
489
- return {
490
- entries: entries,
491
- corrupt: entries === void 0
492
- };
493
- }
494
-
495
- function writeStored(args) {
496
- if (!args.storage) return !1;
497
- try {
498
- return args.storage.setItem(args.storageKey, JSON.stringify({
499
- version: PAYLOAD_VERSION,
500
- bands: args.entries
501
- })), !0;
502
- } catch {
503
- return !1;
504
- }
505
- }
506
-
507
- function baseEntries(args) {
508
- return args.stored === void 0 ? args.held : args.unpersisted ? mergeByTouched(args.stored, args.held) : args.stored;
509
- }
510
-
511
- function mergeByTouched(stored, held) {
512
- const merged = {
513
- ...stored
514
- };
515
- for (const [key, entry] of Object.entries(held)) {
516
- const persisted = merged[key];
517
- (persisted === void 0 || entry.touched >= persisted.touched) && (merged[key] = entry);
518
- }
519
- return merged;
520
- }
521
-
522
- function prune(entries) {
523
- const held = Object.entries(entries);
524
- return held.length <= MAX_ENTRIES ? entries : Object.fromEntries(held.sort(([, a], [, b]) => b.touched - a.touched).slice(0, MAX_ENTRIES));
525
- }
526
-
527
- function togglesOf(entries) {
528
- return new Map(Object.entries(entries).map(([key, entry]) => [ key, entry.open ]));
529
- }
530
-
531
- function defaultStorage() {
532
- try {
533
- return globalThis.localStorage;
534
- } catch {
535
- return;
536
- }
2592
+ }) : null ]
2593
+ })
2594
+ }, definition.name))
2595
+ }) ]
2596
+ })
2597
+ });
537
2598
  }
538
2599
 
539
- function createBandStateStore(args) {
540
- const storage = args.storage ?? defaultStorage(), initial = readStored(storage, args.storageKey);
541
- initial.corrupt && writeStored({
542
- storage: storage,
543
- storageKey: args.storageKey,
544
- entries: {}
2600
+ function ToolActivityDialog({target: target, onClose: onClose}) {
2601
+ const entry = useWorkflowInstanceEntry(target.instanceId), definition = useDefinition(entry), resolvedOnce = useRef(!1);
2602
+ return entry && (resolvedOnce.current = !0), useEffect(() => {
2603
+ resolvedOnce.current && openActivityGone({
2604
+ entry: entry,
2605
+ target: target
2606
+ }) && onClose();
2607
+ }, [ entry, target, onClose ]), entry?.invalid ? /* @__PURE__ */ jsx(Dialog, {
2608
+ header: "Workflow unavailable",
2609
+ id: "workflow-activity-detail",
2610
+ onClose: onClose,
2611
+ width: 1,
2612
+ children: /* @__PURE__ */ jsx(Box, {
2613
+ padding: 4,
2614
+ children: /* @__PURE__ */ jsx(InvalidDocNotice, {
2615
+ invalid: entry.invalid
2616
+ })
2617
+ })
2618
+ }) : entry?.evaluation ? /* @__PURE__ */ jsx(ActivityDetailDialog, {
2619
+ activityName: target.activityName,
2620
+ breadcrumb: instanceBreadcrumb(entry.instance, definition),
2621
+ definition: definition,
2622
+ document: documentRefsOf(entry.instance)[0],
2623
+ entry: entry,
2624
+ onClose: onClose,
2625
+ source: "tool-task-list"
2626
+ }) : /* @__PURE__ */ jsx(Dialog, {
2627
+ header: "Loading activity…",
2628
+ id: "workflow-activity-detail",
2629
+ onClose: onClose,
2630
+ width: 1,
2631
+ children: /* @__PURE__ */ jsx(Box, {
2632
+ padding: 4,
2633
+ children: /* @__PURE__ */ jsx(LoadingRow, {
2634
+ label: "Fetching the live workflow state…"
2635
+ })
2636
+ })
545
2637
  });
546
- let held = initial.entries ?? {}, unpersisted = !1;
547
- return {
548
- read: () => togglesOf(held),
549
- setOpen(key, open) {
550
- const stored = readStored(storage, args.storageKey).entries, base = baseEntries({
551
- stored: stored,
552
- held: held,
553
- unpersisted: unpersisted
554
- });
555
- return held = prune({
556
- ...base,
557
- [key]: {
558
- open: open,
559
- touched: Date.now()
560
- }
561
- }), unpersisted = !writeStored({
562
- storage: storage,
563
- storageKey: args.storageKey,
564
- entries: held
565
- }), togglesOf(held);
566
- }
567
- };
568
2638
  }
569
2639
 
570
2640
  function datesOf(state) {
571
- return state.filter(entry => entry._type === "date" || entry._type === "datetime").map(entry => entry.value).filter(value => typeof value == "string" && value !== "");
2641
+ return state.filter(entry => isDateFieldKind(entry._type)).map(entry => entry.value).filter(value => typeof value == "string" && value !== "");
572
2642
  }
573
2643
 
574
2644
  function spawnOrigins(instances) {
@@ -668,6 +2738,14 @@ function deriveTaskGroups(args) {
668
2738
  };
669
2739
  }
670
2740
 
2741
+ function orderGroupsByPreviewTitle(groups, titles) {
2742
+ const withDocuments = [ ...groups.withDocuments ].sort(byResolvedTitle(group => titles.get(group.document.id), group => group.key));
2743
+ return {
2744
+ ...groups,
2745
+ withDocuments: withDocuments
2746
+ };
2747
+ }
2748
+
671
2749
  function cutTaskGroups(groups, cut) {
672
2750
  const cutBands = bands => bands.filter(band => cut.keepBand?.(band) ?? !0).map(band => ({
673
2751
  ...band,
@@ -813,11 +2891,13 @@ function workflowTally(workflows) {
813
2891
  function settleToast(outcome) {
814
2892
  const total = outcome.settled + outcome.skipped.length + outcome.failures.length;
815
2893
  if (outcome.skipped.length === 0 && outcome.failures.length === 0) return {
816
- status: "success",
2894
+ id: TOAST_ID.orphanSettle,
2895
+ status: "info",
817
2896
  title: outcome.settled === 1 ? "Cleaned up 1 workflow" : `Cleaned up ${outcome.settled} workflows`
818
2897
  };
819
2898
  const lines = [ ...outcome.skipped.map(workflow => `${workflow.title}: its document exists again — left running`), ...outcome.failures.map(failure => `${failure.workflow.title}: ${failure.message}`) ];
820
2899
  return {
2900
+ id: TOAST_ID.orphanSettle,
821
2901
  status: "warning",
822
2902
  title: `Cleaned up ${outcome.settled} of ${total} workflows`,
823
2903
  description: lines.join(`\n`)
@@ -826,9 +2906,10 @@ function settleToast(outcome) {
826
2906
 
827
2907
  function recheckFailedToast(err) {
828
2908
  return {
2909
+ id: TOAST_ID.orphanSettle,
829
2910
  status: "error",
830
- title: "Nothing was cleaned up — the documents could not be re-checked",
831
- description: describeError(err)
2911
+ title: "Nothing was cleaned up",
2912
+ description: `The documents could not be re-checked: ${describeError(err)}`
832
2913
  };
833
2914
  }
834
2915
 
@@ -858,7 +2939,7 @@ function OrphanedWorkflowsNote({orphans: orphans}) {
858
2939
  function SettleOrphansDialog({workflows: workflows, onClose: onClose}) {
859
2940
  const {engine: engine} = useWorkflowContext(), client = useClient({
860
2941
  apiVersion: WORKFLOW_API_VERSION
861
- }), toast = useClosableToast(), [pending, setPending] = useState(!1), confirm = async () => {
2942
+ }), toast = useWorkflowToast(), [pending, setPending] = useState(!1), confirm = async () => {
862
2943
  setPending(!0);
863
2944
  try {
864
2945
  const outcome = await settleOrphans({
@@ -1103,38 +3184,6 @@ function applyTaskFilters(args) {
1103
3184
  });
1104
3185
  }
1105
3186
 
1106
- const NOTHING = {
1107
- titles: /* @__PURE__ */ new Map,
1108
- resolved: /* @__PURE__ */ new Set
1109
- };
1110
-
1111
- function useDocPreviewTitles(documents) {
1112
- const previewStore = useDocumentPreviewStore(), schema = useSchema(), {binding: binding} = useWorkflowContext(), [state, setState] = useState(NOTHING), key = documents.map(doc => doc.id).join("|");
1113
- return useEffect(() => {
1114
- const titles = /* @__PURE__ */ new Map, resolved = /* @__PURE__ */ new Set, publish = () => setState({
1115
- titles: new Map(titles),
1116
- resolved: new Set(resolved)
1117
- }), subscriptions = documents.flatMap(doc => {
1118
- const {parsed: parsedRef, local: local} = gdrLocality(doc.id, binding.contentResource), schemaType = schema.get(doc.type);
1119
- return !parsedRef || !local || !schemaType ? (resolved.add(doc.id), []) : [ previewStore.observeForPreview({
1120
- _type: "reference",
1121
- _ref: parsedRef.documentId
1122
- }, schemaType).subscribe({
1123
- next: event => {
1124
- resolved.add(doc.id);
1125
- const title = event.snapshot?.title;
1126
- typeof title == "string" && titles.set(doc.id, title), publish();
1127
- },
1128
- error: err => {
1129
- console.error(`[workflow-studio-plugin] preview for "${doc.id}" failed:`, err),
1130
- resolved.add(doc.id), publish();
1131
- }
1132
- }) ];
1133
- });
1134
- return publish(), () => subscriptions.forEach(sub => sub.unsubscribe());
1135
- }, [ key, previewStore, schema, binding.contentResource.id ]), state;
1136
- }
1137
-
1138
3187
  const HITS_CAP = 6;
1139
3188
 
1140
3189
  function TaskFilterMenu({filters: filters, options: options, onChange: onChange}) {
@@ -1633,8 +3682,10 @@ function FilterRowPortal({filters: filters, onChange: onChange, options: options
1633
3682
  }), slot) : null;
1634
3683
  }
1635
3684
 
1636
- function ActivityDateControl({instanceId: instanceId, state: state, dates: dates}) {
1637
- const {editFieldFor: editFieldFor} = useWorkflowContext(), {save: save} = useSaveField(), [open, setOpen] = useState(!1), raw = dateControlValue({
3685
+ function ActivityDateControl({instanceId: instanceId, state: state, surface: surface, dates: dates}) {
3686
+ const editField = useEditField(surface), {save: save} = useSaveField({
3687
+ instanceId: instanceId
3688
+ }), [open, setOpen] = useState(!1), raw = dateControlValue({
1638
3689
  state: state,
1639
3690
  dates: dates
1640
3691
  }), display = raw === void 0 ? null : /* @__PURE__ */ jsx(Text, {
@@ -1657,15 +3708,16 @@ function ActivityDateControl({instanceId: instanceId, state: state, dates: dates
1657
3708
  })
1658
3709
  });
1659
3710
  const kind = dateControlKind(state), commit = (mode, value) => {
1660
- state.kind === "editable" && save(() => editFieldFor(instanceId, {
1661
- target: editFieldTarget(state.field),
3711
+ state.kind === "editable" && save(() => editField({
3712
+ instanceId: instanceId,
3713
+ field: state.field,
1662
3714
  ...mode === "set" ? {
1663
3715
  mode: mode,
1664
3716
  value: value
1665
3717
  } : {
1666
3718
  mode: mode
1667
3719
  }
1668
- }).then(() => {}));
3720
+ }));
1669
3721
  }, handleClick = e => {
1670
3722
  e.stopPropagation(), setOpen(v => !v);
1671
3723
  };
@@ -1682,9 +3734,9 @@ function ActivityDateControl({instanceId: instanceId, state: state, dates: dates
1682
3734
  setOpen(!1), commit("unset");
1683
3735
  },
1684
3736
  onPick: next => {
1685
- kind === "date" && setOpen(!1), commit("set", serializeDateFieldValue(next, kind));
3737
+ hasTimeOfDay(kind) || setOpen(!1), commit("set", serializeDateFieldValue(next, kind));
1686
3738
  },
1687
- selectTime: kind === "datetime",
3739
+ selectTime: hasTimeOfDay(kind),
1688
3740
  value: parseDateFieldValue(state.field.value, kind)
1689
3741
  })
1690
3742
  }),
@@ -1748,7 +3800,8 @@ function RefChips({refs: refs, max: max = 2}) {
1748
3800
  wrap: "wrap",
1749
3801
  children: [ shown.map(ref => /* @__PURE__ */ jsx(DocPreviewLink, {
1750
3802
  gdr: ref,
1751
- layout: "inline"
3803
+ layout: "inline",
3804
+ source: "doc-ref-chip"
1752
3805
  }, ref.id)), overflow > 0 ? /* @__PURE__ */ jsxs(Text, {
1753
3806
  muted: !0,
1754
3807
  size: 1,
@@ -1757,57 +3810,18 @@ function RefChips({refs: refs, max: max = 2}) {
1757
3810
  });
1758
3811
  }
1759
3812
 
1760
- const EMPTY_NOTE = "No tasks match — adjust the filters, or start a workflow", UNGROUPED_TITLE = "Workflows without Studio documents";
3813
+ const UNGROUPED_TITLE = "Workflows without Studio documents";
1761
3814
 
1762
- function TaskGroupList({bands: bands, groups: groups, instanceDefaultOpen: instanceDefaultOpen, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
1763
- return groups.withDocuments.length === 0 && groups.withoutDocuments.length === 0 ? /* @__PURE__ */ jsx(MutedNote, {
1764
- text: EMPTY_NOTE
1765
- }) : /* @__PURE__ */ jsxs(Stack, {
1766
- gap: 2,
1767
- children: [ groups.withDocuments.map(group => /* @__PURE__ */ jsx(CollapsibleBand, {
1768
- background: !0,
1769
- onToggle: () => bands.toggle(group.key, GROUP_DEFAULT_OPEN),
1770
- open: bands.isOpen(group.key, GROUP_DEFAULT_OPEN),
1771
- sticky: !0,
1772
- title: toBareId(group.document.id),
1773
- header: /* @__PURE__ */ jsx(Flex, {
1774
- align: "center",
1775
- flex: "none",
1776
- style: {
1777
- maxWidth: "60%"
1778
- },
1779
- children: /* @__PURE__ */ jsx(RefChips, {
1780
- max: 2,
1781
- refs: group.documents
1782
- })
1783
- }),
1784
- children: /* @__PURE__ */ jsx(InstanceBands, {
1785
- bands: bands,
1786
- instanceDefaultOpen: instanceDefaultOpen,
1787
- instances: group.instances,
1788
- onOpenTask: onOpenTask,
1789
- onOpenWorkflow: onOpenWorkflow,
1790
- showTerminalActions: showTerminalActions
1791
- })
1792
- }, group.key)), groups.withoutDocuments.length > 0 ? /* @__PURE__ */ jsx(CollapsibleBand, {
1793
- background: !0,
1794
- onToggle: () => bands.toggle(UNGROUPED_BAND_KEY, GROUP_DEFAULT_OPEN),
1795
- open: bands.isOpen(UNGROUPED_BAND_KEY, GROUP_DEFAULT_OPEN),
1796
- sticky: !0,
1797
- title: UNGROUPED_TITLE,
1798
- header: /* @__PURE__ */ jsx(Text, {
1799
- size: 1,
1800
- weight: "semibold",
1801
- children: UNGROUPED_TITLE
1802
- }),
1803
- children: /* @__PURE__ */ jsx(InstanceBands, {
1804
- bands: bands,
1805
- instanceDefaultOpen: instanceDefaultOpen,
1806
- instances: groups.withoutDocuments,
1807
- onOpenTask: onOpenTask,
1808
- onOpenWorkflow: onOpenWorkflow,
1809
- showTerminalActions: showTerminalActions
1810
- })
3815
+ function TaskGroupList({groups: groups, ...wiring}) {
3816
+ /* @__PURE__ */
3817
+ return jsxs(Stack, {
3818
+ gap: 1,
3819
+ children: [ groups.withDocuments.map(group => /* @__PURE__ */ jsx(DocumentBand, {
3820
+ group: group,
3821
+ ...wiring
3822
+ }, group.key)), groups.withoutDocuments.length > 0 ? /* @__PURE__ */ jsx(UngroupedBand, {
3823
+ instances: groups.withoutDocuments,
3824
+ ...wiring
1811
3825
  }) : null ]
1812
3826
  });
1813
3827
  }
@@ -1820,38 +3834,81 @@ function targetOf(row) {
1820
3834
  };
1821
3835
  }
1822
3836
 
1823
- function InstanceBands({bands: bands, instanceDefaultOpen: instanceDefaultOpen, instances: instances, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
3837
+ function DocumentBand({group: group, ...wiring}) {
3838
+ const {open: open, toggle: toggle} = useBandOpen(wiring.bands, group.key);
3839
+ /* @__PURE__ */
3840
+ return jsx(CollapsibleBand, {
3841
+ background: !0,
3842
+ onToggle: toggle,
3843
+ open: open,
3844
+ sticky: !0,
3845
+ title: toBareId(group.document.id),
3846
+ header: /* @__PURE__ */ jsx(Flex, {
3847
+ align: "center",
3848
+ flex: "none",
3849
+ style: {
3850
+ maxWidth: "60%"
3851
+ },
3852
+ children: /* @__PURE__ */ jsx(RefChips, {
3853
+ max: 2,
3854
+ refs: group.documents
3855
+ })
3856
+ }),
3857
+ children: /* @__PURE__ */ jsx(InstanceBands, {
3858
+ instances: group.instances,
3859
+ ...wiring
3860
+ })
3861
+ });
3862
+ }
3863
+
3864
+ function UngroupedBand({instances: instances, ...wiring}) {
3865
+ const {open: open, toggle: toggle} = useBandOpen(wiring.bands, UNGROUPED_BAND_KEY);
3866
+ /* @__PURE__ */
3867
+ return jsx(CollapsibleBand, {
3868
+ background: !0,
3869
+ onToggle: toggle,
3870
+ open: open,
3871
+ sticky: !0,
3872
+ title: UNGROUPED_TITLE,
3873
+ header: /* @__PURE__ */ jsx(Text, {
3874
+ size: 1,
3875
+ weight: "semibold",
3876
+ children: UNGROUPED_TITLE
3877
+ }),
3878
+ children: /* @__PURE__ */ jsx(InstanceBands, {
3879
+ instances: instances,
3880
+ ...wiring
3881
+ })
3882
+ });
3883
+ }
3884
+
3885
+ function InstanceBands({instances: instances, ...wiring}) {
1824
3886
  /* @__PURE__ */
1825
3887
  return jsx(Stack, {
1826
3888
  gap: 1,
1827
- children: instances.map(band => /* @__PURE__ */ jsx(InstanceBand, {
1828
- band: band,
1829
- bands: bands,
1830
- instanceDefaultOpen: instanceDefaultOpen,
1831
- onOpenTask: onOpenTask,
1832
- onOpenWorkflow: onOpenWorkflow,
1833
- showTerminalActions: showTerminalActions
1834
- }, band.instanceId))
3889
+ children: instances.map(instance => /* @__PURE__ */ jsx(InstanceBand, {
3890
+ instance: instance,
3891
+ ...wiring
3892
+ }, instance.instanceId))
1835
3893
  });
1836
3894
  }
1837
3895
 
1838
- function InstanceBand({band: band, bands: bands, instanceDefaultOpen: instanceDefaultOpen, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
1839
- const {resolvePathFromState: resolvePathFromState} = useRouter();
3896
+ function InstanceBand({bands: bands, instance: instance, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
3897
+ const {open: open, toggle: toggle} = useBandOpen(bands, instance.instanceId), {resolvePathFromState: resolvePathFromState} = useRouter();
1840
3898
  /* @__PURE__ */
1841
3899
  return jsx(CollapsibleBand, {
1842
- onToggle: () => bands.toggle(band.instanceId, instanceDefaultOpen),
1843
- open: bands.isOpen(band.instanceId, instanceDefaultOpen),
1844
- title: band.title,
3900
+ onToggle: toggle,
3901
+ open: open,
3902
+ title: instance.title,
1845
3903
  header: /* @__PURE__ */ jsxs(Fragment, {
1846
3904
  children: [
1847
3905
  /* @__PURE__ */ jsx(LinkChip, {
1848
3906
  hint: "View workflow",
1849
3907
  href: resolvePathFromState({
1850
- instanceId: band.instanceId
3908
+ instanceId: instance.instanceId
1851
3909
  }),
1852
3910
  onClick: event => {
1853
- event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || (event.preventDefault(),
1854
- onOpenWorkflow(band.instanceId));
3911
+ isPlainClick(event) && (event.preventDefault(), onOpenWorkflow(instance.instanceId));
1855
3912
  },
1856
3913
  children: /* @__PURE__ */ jsxs(Flex, {
1857
3914
  align: "center",
@@ -1867,12 +3924,12 @@ function InstanceBand({band: band, bands: bands, instanceDefaultOpen: instanceDe
1867
3924
  },
1868
3925
  textOverflow: "ellipsis",
1869
3926
  weight: "medium",
1870
- children: band.title
1871
- }), band.breadcrumb === void 0 ? null : /* @__PURE__ */ jsx(BreadcrumbTail, {
1872
- segments: [ band.breadcrumb ]
3927
+ children: instance.title
3928
+ }), instance.breadcrumb === void 0 ? null : /* @__PURE__ */ jsx(BreadcrumbTail, {
3929
+ segments: [ instance.breadcrumb ]
1873
3930
  }),
1874
3931
  /* @__PURE__ */ jsx(StageFace, {
1875
- title: band.stageTitle
3932
+ title: instance.stageTitle
1876
3933
  }) ]
1877
3934
  })
1878
3935
  }),
@@ -1882,7 +3939,7 @@ function InstanceBand({band: band, bands: bands, instanceDefaultOpen: instanceDe
1882
3939
  paddingLeft: 5,
1883
3940
  children: /* @__PURE__ */ jsx(TaskRows, {
1884
3941
  onOpenTask: onOpenTask,
1885
- rows: band.rows,
3942
+ rows: instance.rows,
1886
3943
  showTerminalActions: showTerminalActions
1887
3944
  })
1888
3945
  })
@@ -1916,40 +3973,19 @@ function TaskActivityRow({row: row, onOpen: onOpen, showTerminalActions: showTer
1916
3973
  dateControl: /* @__PURE__ */ jsx(ActivityDateControl, {
1917
3974
  dates: render.dates,
1918
3975
  instanceId: row.instanceId,
1919
- state: render.dateState
3976
+ state: render.dateState,
3977
+ surface: "tool-task-list"
1920
3978
  }),
1921
3979
  face: render.face,
1922
3980
  instanceId: row.instanceId,
1923
3981
  onOpen: onOpen,
3982
+ surface: "tool-task-list",
1924
3983
  terminalActions: showTerminalActions ? render.terminalActions : []
1925
3984
  })
1926
3985
  })
1927
3986
  });
1928
3987
  }
1929
3988
 
1930
- function useBandOpenState(args) {
1931
- const {segment: segment, scope: scope} = args, store = useMemo(() => createBandStateStore({
1932
- storageKey: bandStateStorageKey(scope)
1933
- }), [ scope ]), [held, setHeld] = useState(() => ({
1934
- store: store,
1935
- toggles: store.read()
1936
- }));
1937
- held.store !== store && setHeld({
1938
- store: store,
1939
- toggles: store.read()
1940
- });
1941
- const qualify = key => `${segment}/${key}`, isOpen = (key, defaultOpen) => held.toggles.get(qualify(key)) ?? defaultOpen;
1942
- return {
1943
- isOpen: isOpen,
1944
- toggle: (key, defaultOpen) => {
1945
- setHeld({
1946
- store: store,
1947
- toggles: store.setOpen(qualify(key), !isOpen(key, defaultOpen))
1948
- });
1949
- }
1950
- };
1951
- }
1952
-
1953
3989
  const NO_MISSING = /* @__PURE__ */ new Set;
1954
3990
 
1955
3991
  function sameIdSet(a, b) {
@@ -1982,28 +4018,6 @@ function useOrphanedWorkflows(groups) {
1982
4018
  }), [ groups, contentResource, missing ]);
1983
4019
  }
1984
4020
 
1985
- const INSTANCE_CAP = 200, NEWEST_INSTANCES = {
1986
- includeCompleted: !0,
1987
- limit: INSTANCE_CAP
1988
- };
1989
-
1990
- function useToolInstances() {
1991
- const {engine: engine} = useWorkflowContext(), {instances: instances, loading: loading, unreadable: unreadable} = useWorkflowInstances({
1992
- engine: engine,
1993
- filter: NEWEST_INSTANCES
1994
- });
1995
- return useMemo(() => {
1996
- const rows = instances ?? [];
1997
- return {
1998
- inFlight: rows.filter(instance => terminalState(instance) === "in-flight"),
1999
- settled: rows.filter(instance => terminalState(instance) !== "in-flight"),
2000
- truncated: rows.length + unreadable.length === INSTANCE_CAP,
2001
- loading: loading,
2002
- unreadable: unreadable
2003
- };
2004
- }, [ instances, loading, unreadable ]);
2005
- }
2006
-
2007
4021
  function TasksTab({filterSlot: filterSlot, instances: instances, loading: loading, identity: identity, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, segment: segment, truncated: truncated, unreadable: unreadable}) {
2008
4022
  const mineOnly = segment === "for-me", {binding: binding} = useWorkflowContext(), bands = useBandOpenState({
2009
4023
  segment: segment,
@@ -2011,21 +4025,38 @@ function TasksTab({filterSlot: filterSlot, instances: instances, loading: loadin
2011
4025
  }), [filters, setFilters] = useState({}), groups = useMemo(() => deriveTaskGroups({
2012
4026
  instances: instances,
2013
4027
  identity: identity
2014
- }), [ instances, identity ]), orphans = useOrphanedWorkflows(groups), listed = useMemo(() => hideOrphanedGroups(groups, orphans.groupKeys), [ groups, orphans.groupKeys ]), options = useMemo(() => taskFilterOptions(listed), [ listed ]), visible = useMemo(() => {
4028
+ }), [ instances, identity ]), orphans = useOrphanedWorkflows(groups), listed = useMemo(() => hideOrphanedGroups(groups, orphans.groupKeys), [ groups, orphans.groupKeys ]), options = useMemo(() => taskFilterOptions(listed), [ listed ]), titles = useGroupTitles(listed), revealed = useRevealGate({
4029
+ loading: loading,
4030
+ ready: titles.ready
4031
+ }), visible = useMemo(() => {
2015
4032
  const filtered = applyTaskFilters({
2016
4033
  groups: listed,
2017
4034
  filters: filters,
2018
4035
  now: /* @__PURE__ */ new Date
2019
- });
2020
- return mineOnly ? onlyMyTasks(filtered) : filtered;
2021
- }, [ listed, filters, mineOnly ]);
2022
- return loading ?
2023
- /* @__PURE__ */ jsx(Box, {
2024
- paddingLeft: 2,
4036
+ }), cut = mineOnly ? onlyMyTasks(filtered) : filtered;
4037
+ return orderGroupsByPreviewTitle(cut, titles.titles);
4038
+ }, [ listed, filters, mineOnly, titles ]);
4039
+ if (loading || !revealed) /* @__PURE__ */
4040
+ return jsx(TabStatusRow, {
2025
4041
  children: /* @__PURE__ */ jsx(LoadingRow, {
2026
4042
  label: "Loading tasks…"
2027
4043
  })
2028
- }) : /* @__PURE__ */ jsxs(Box, {
4044
+ });
4045
+ const empty = visible.withDocuments.length === 0 && visible.withoutDocuments.length === 0;
4046
+ let body;
4047
+ return empty ? mineOnly ? body = /* @__PURE__ */ jsx(ForMeEmptyState, {}) : body = /* @__PURE__ */ jsx(EmptyState, {
4048
+ description: "Adjust the filters, or start a workflow",
4049
+ icon: /* @__PURE__ */ jsx(SearchIcon, {}),
4050
+ title: "No tasks match"
4051
+ }) : body = /* @__PURE__ */ jsx(TaskGroupList, {
4052
+ bands: bands,
4053
+ groups: visible,
4054
+ onOpenTask: onOpenTask,
4055
+ onOpenWorkflow: onOpenWorkflow,
4056
+ showTerminalActions: mineOnly
4057
+ }), /* @__PURE__ */ jsxs(Flex, {
4058
+ direction: "column",
4059
+ flex: 1,
2029
4060
  children: [
2030
4061
  /* @__PURE__ */ jsx(FilterRowPortal, {
2031
4062
  filters: filters,
@@ -2033,91 +4064,133 @@ function TasksTab({filterSlot: filterSlot, instances: instances, loading: loadin
2033
4064
  options: options,
2034
4065
  slot: filterSlot
2035
4066
  }),
2036
- /* @__PURE__ */ jsxs(Stack, {
4067
+ /* @__PURE__ */ jsxs(Flex, {
4068
+ direction: "column",
4069
+ flex: 1,
2037
4070
  gap: 2,
4071
+ paddingBottom: 4,
2038
4072
  children: [
2039
4073
  /* @__PURE__ */ jsx(UnreadableDocsNote, {
2040
4074
  unreadable: unreadable
2041
4075
  }),
2042
4076
  /* @__PURE__ */ jsx(OrphanedWorkflowsNote, {
2043
4077
  orphans: orphans
2044
- }),
2045
- /* @__PURE__ */ jsx(TaskGroupList, {
2046
- bands: bands,
2047
- groups: visible,
2048
- instanceDefaultOpen: instanceBandDefaultOpen(segment),
2049
- onOpenTask: onOpenTask,
2050
- onOpenWorkflow: onOpenWorkflow,
2051
- showTerminalActions: mineOnly
2052
- }) ]
2053
- }), truncated ? /* @__PURE__ */ jsx(MutedNote, {
2054
- text: `Tasks from the latest ${INSTANCE_CAP} workflows — older ones aren't listed.`
2055
- }) : null ]
4078
+ }), body, truncated ? /* @__PURE__ */ jsx(MutedNote, {
4079
+ text: `Tasks from the latest ${INSTANCE_CAP} workflows — older ones aren’t listed.`
4080
+ }) : null ]
4081
+ }) ]
2056
4082
  });
2057
4083
  }
2058
4084
 
2059
- const TOOL_TABS = [ {
2060
- value: "overview",
2061
- label: "Overview"
2062
- }, {
2063
- value: "for-me",
2064
- label: "For me"
2065
- } ], [firstToolTab, ...restToolTabs] = TOOL_TABS, TOOL_TAB_CODEC = workflowsTabCodec([ firstToolTab.value, ...restToolTabs.map(tab => tab.value) ]);
4085
+ const TOOL_TAB_LABELS = {
4086
+ definitions: "Definitions",
4087
+ documents: "Documents",
4088
+ tasks: "Tasks",
4089
+ "for-me": "For me"
4090
+ }, TOOL_TAB_VALUES = Object.keys(TOOL_TAB_LABELS), TOOL_TABS = TOOL_TAB_VALUES.map(value => ({
4091
+ value: value,
4092
+ label: TOOL_TAB_LABELS[value]
4093
+ })), DEFAULT_TOOL_TAB = "definitions";
4094
+
4095
+ function toolTabState(tab) {
4096
+ return tab === DEFAULT_TOOL_TAB ? {} : {
4097
+ workflowsTab: tab
4098
+ };
4099
+ }
2066
4100
 
2067
- function useToolTab() {
2068
- const router = useRouter(), params = Object.fromEntries(router.state._searchParams ?? []);
4101
+ function boardState(definitionName) {
2069
4102
  return {
2070
- tab: TOOL_TAB_CODEC.read(params),
2071
- setTab: next => router.navigate({
2072
- _searchParams: definedEntries(TOOL_TAB_CODEC.write(params, next))
2073
- })
4103
+ boardDefinition: definitionName
2074
4104
  };
2075
4105
  }
2076
4106
 
2077
- function definedEntries(params) {
2078
- return Object.entries(params).filter(entry => entry[1] !== void 0);
4107
+ function tabSelectionNavigates(route, tab) {
4108
+ return !(route.kind === "home" && tab === route.tab);
4109
+ }
4110
+
4111
+ function backToAllLabel(tab) {
4112
+ return `Back to all ${TOOL_TAB_LABELS[tab].toLowerCase()}`;
2079
4113
  }
2080
4114
 
2081
- function carryingSearchParams(state, searchParams) {
2082
- return searchParams === void 0 ? state : {
2083
- ...state,
2084
- _searchParams: searchParams
4115
+ function toolRoute(state) {
4116
+ const {instanceId: instanceId, definitionName: definitionName, boardDefinition: boardDefinition} = state;
4117
+ if (typeof instanceId == "string") return {
4118
+ kind: "instance",
4119
+ instanceId: instanceId,
4120
+ tab: "tasks"
4121
+ };
4122
+ if (typeof definitionName == "string") return {
4123
+ kind: "definition",
4124
+ definitionName: definitionName,
4125
+ tab: "definitions"
4126
+ };
4127
+ if (typeof boardDefinition == "string") return {
4128
+ kind: "home",
4129
+ tab: "documents",
4130
+ boardDefinition: boardDefinition
4131
+ };
4132
+ const tab = pathSegmentTab({
4133
+ segment: state.workflowsTab,
4134
+ tabs: TOOL_TAB_VALUES,
4135
+ fallback: DEFAULT_TOOL_TAB
4136
+ });
4137
+ return tab === void 0 ? {
4138
+ kind: "not-found",
4139
+ tab: void 0
4140
+ } : {
4141
+ kind: "home",
4142
+ tab: tab,
4143
+ boardDefinition: void 0
2085
4144
  };
2086
4145
  }
2087
4146
 
4147
+ function InstanceStreamKeepAlive() {
4148
+ return useToolInstances(), null;
4149
+ }
4150
+
2088
4151
  function WorkflowsToolRoot() {
2089
- const router = useRouter(), {seedInstance: seedInstance} = useWorkflowContext(), {tab: tab} = useToolTab();
2090
- useLogEventOnMount(WorkflowToolOpened, {
2091
- tab: tab,
4152
+ const router = useRouter(), {seedInstance: seedInstance} = useWorkflowContext(), telemetry = useWorkflowTelemetry(), [filterSlot, setFilterSlot] = useState(null), route = toolRoute(router.state), isHome = route.kind === "home" || route.kind === "not-found", [instanceStreamLatched, setInstanceStreamLatched] = useState(isHome);
4153
+ useEffect(() => {
4154
+ isHome && setInstanceStreamLatched(!0);
4155
+ }, [ isHome ]);
4156
+ const setTab = next => router.navigate(toolTabState(next)), selectBoardWorkflow = useCallback((name, opts) => router.navigate(boardState(name), opts?.replace === !0 ? {
4157
+ replace: !0
4158
+ } : {}), [ router ]), backToList = tab => ({
4159
+ label: backToAllLabel(tab),
4160
+ go: () => setTab(tab)
4161
+ }), badSegment = route.kind === "not-found";
4162
+ useEffect(() => {
4163
+ badSegment && router.navigate({}, {
4164
+ replace: !0
4165
+ });
4166
+ }, [ badSegment, router ]), useLogEventOnMount(WorkflowToolOpened, {
4167
+ tab: route.tab ?? "not-found",
2092
4168
  via: "open"
2093
4169
  });
2094
- const instanceId = typeof router.state.instanceId == "string" ? router.state.instanceId : void 0;
2095
- return instanceId ? /* @__PURE__ */ jsx(WorkflowInstanceDetail, {
2096
- instanceId: instanceId,
2097
- onBack: () => router.navigate(carryingSearchParams({}, router.state._searchParams))
2098
- }, instanceId) : /* @__PURE__ */ jsx(WorkflowsHome, {
4170
+ let panel;
4171
+ return route.kind === "instance" ? panel = /* @__PURE__ */ jsx(WorkflowInstanceDetail, {
4172
+ instanceId: route.instanceId,
4173
+ backToList: backToList(route.tab)
4174
+ }, route.instanceId) : route.kind === "definition" ? panel = /* @__PURE__ */ jsx(DefinitionDetail, {
4175
+ definitionName: route.definitionName,
4176
+ backToList: backToList(route.tab)
4177
+ }, route.definitionName) : route.kind === "not-found" ? panel = null : panel = /* @__PURE__ */ jsx(WorkflowsHome, {
4178
+ boardDefinition: route.boardDefinition,
4179
+ filterSlot: filterSlot,
4180
+ onOpenDefinition: name => router.navigate({
4181
+ definitionName: name
4182
+ }),
2099
4183
  onOpenInstance: instance => {
2100
- typeof instance != "string" && seedInstance(instance), router.navigate(carryingSearchParams({
4184
+ typeof instance != "string" && seedInstance(instance), router.navigate({
2101
4185
  instanceId: typeof instance == "string" ? instance : instance._id
2102
- }, router.state._searchParams));
2103
- }
2104
- });
2105
- }
2106
-
2107
- function WorkflowsHome({onOpenInstance: onOpenInstance}) {
2108
- const {tab: tab, setTab: setTab} = useToolTab(), telemetry = useWorkflowTelemetry(), instances = useToolInstances(), identity = useAssignmentIdentity(), [openTask, setOpenTask] = useState(null), openInstance = target => {
2109
- if (typeof target != "string") return onOpenInstance(target);
2110
- const held = [ ...instances.inFlight, ...instances.settled ].find(i => i._id === target);
2111
- onOpenInstance(held ?? target);
2112
- }, [filterSlot, setFilterSlot] = useState(null), scrollerRef = useRef(null);
2113
- return useEffect(() => {
2114
- scrollerRef.current?.scrollTo({
2115
- top: 0
2116
- });
2117
- }, [ tab ]),
4186
+ });
4187
+ },
4188
+ onSelectWorkflow: selectBoardWorkflow,
4189
+ tab: route.tab
4190
+ }),
2118
4191
  /* @__PURE__ */ jsxs(Card, {
2119
4192
  height: "fill",
2120
- children: [
4193
+ children: [ instanceStreamLatched ? /* @__PURE__ */ jsx(InstanceStreamKeepAlive, {}) : null,
2121
4194
  /* @__PURE__ */ jsxs(Flex, {
2122
4195
  direction: "column",
2123
4196
  height: "fill",
@@ -2126,75 +4199,103 @@ function WorkflowsHome({onOpenInstance: onOpenInstance}) {
2126
4199
  /* @__PURE__ */ jsx(Box, {
2127
4200
  flex: "none",
2128
4201
  padding: 3,
2129
- children: /* @__PURE__ */ jsx(Container, {
2130
- width: 2,
2131
- children: /* @__PURE__ */ jsxs(Stack, {
4202
+ children: /* @__PURE__ */ jsxs(Stack, {
4203
+ gap: 3,
4204
+ children: [
4205
+ /* @__PURE__ */ jsxs(Flex, {
4206
+ align: "center",
2132
4207
  gap: 3,
4208
+ paddingLeft: 2,
2133
4209
  children: [
2134
- /* @__PURE__ */ jsxs(Flex, {
2135
- align: "center",
2136
- gap: 3,
2137
- paddingLeft: 2,
2138
- children: [
2139
- /* @__PURE__ */ jsx(Text, {
2140
- size: 2,
2141
- weight: "semibold",
2142
- children: "Workflows"
2143
- }),
2144
- /* @__PURE__ */ jsx(Box, {
2145
- flex: 1
2146
- }),
2147
- /* @__PURE__ */ jsx(NewWorkflowButton, {}) ]
4210
+ /* @__PURE__ */ jsx(Text, {
4211
+ size: 1,
4212
+ weight: "semibold",
4213
+ children: "Workflows"
2148
4214
  }),
2149
- /* @__PURE__ */ jsx(TabSwitch, {
2150
- ariaControls: "workflows-tool-panel",
2151
- idPrefix: "workflows-tool-tab",
2152
- onSelect: next => {
2153
- next !== tab && telemetry.log(WorkflowToolOpened, {
2154
- tab: next,
2155
- via: "tab-switch"
2156
- }), setTab(next);
2157
- },
2158
- options: TOOL_TABS,
2159
- selected: tab
4215
+ /* @__PURE__ */ jsx(Box, {
4216
+ flex: 1
2160
4217
  }),
2161
- /* @__PURE__ */ jsx("div", {
2162
- ref: setFilterSlot
2163
- }) ]
2164
- })
4218
+ /* @__PURE__ */ jsx(NewWorkflowButton, {}) ]
4219
+ }),
4220
+ /* @__PURE__ */ jsx(TabSwitch, {
4221
+ ariaControls: "workflows-tool-panel",
4222
+ idPrefix: "workflows-tool-tab",
4223
+ onSelect: next => {
4224
+ tabSelectionNavigates(route, next) && (next !== route.tab && telemetry.log(WorkflowToolOpened, {
4225
+ tab: next,
4226
+ via: "tab-switch"
4227
+ }), setTab(next));
4228
+ },
4229
+ options: TOOL_TABS,
4230
+ selected: route.tab
4231
+ }),
4232
+ /* @__PURE__ */ jsx("div", {
4233
+ ref: setFilterSlot
4234
+ }) ]
2165
4235
  })
2166
4236
  }),
2167
4237
  /* @__PURE__ */ jsx(Box, {
2168
- ref: scrollerRef,
2169
4238
  flex: 1,
2170
- overflow: "auto",
2171
- paddingX: 3,
4239
+ overflow: "hidden",
4240
+ children: panel
4241
+ }) ]
4242
+ }) ]
4243
+ });
4244
+ }
4245
+
4246
+ function WorkflowsHome({boardDefinition: boardDefinition, filterSlot: filterSlot, onOpenDefinition: onOpenDefinition, onOpenInstance: onOpenInstance, onSelectWorkflow: onSelectWorkflow, tab: tab}) {
4247
+ const instances = useToolInstances(), identity = useAssignmentIdentity(), [openTask, setOpenTask] = useState(null), openInstance = target => {
4248
+ if (typeof target != "string") return onOpenInstance(target);
4249
+ const held = [ ...instances.inFlight, ...instances.settled ].find(i => i._id === target);
4250
+ onOpenInstance(held ?? target);
4251
+ }, scrollerRef = useRef(null);
4252
+ useEffect(() => {
4253
+ scrollerRef.current?.scrollTo({
4254
+ top: 0
4255
+ });
4256
+ }, [ tab ]);
4257
+ const bleeds = tab === "documents";
4258
+ /* @__PURE__ */
4259
+ return jsxs(Flex, {
4260
+ direction: "column",
4261
+ height: "fill",
4262
+ overflow: "hidden",
4263
+ children: [
4264
+ /* @__PURE__ */ jsx(Box, {
4265
+ ref: scrollerRef,
4266
+ flex: 1,
4267
+ overflow: "auto",
4268
+ paddingX: bleeds ? 0 : TOOL_PANEL_PADDING,
4269
+ style: {
4270
+ scrollbarGutter: bleeds ? "auto" : "stable"
4271
+ },
4272
+ children: /* @__PURE__ */ jsx(TabPanel, {
4273
+ "aria-labelledby": `workflows-tool-tab-${tab}`,
4274
+ id: "workflows-tool-panel",
2172
4275
  style: {
2173
- scrollbarGutter: "stable"
4276
+ display: "flex",
4277
+ flexDirection: "column",
4278
+ minHeight: "100%"
2174
4279
  },
2175
- children: /* @__PURE__ */ jsx(Container, {
2176
- width: 2,
2177
- children: /* @__PURE__ */ jsx(TabPanel, {
2178
- "aria-labelledby": `workflows-tool-tab-${tab}`,
2179
- id: "workflows-tool-panel",
2180
- children: /* @__PURE__ */ jsx(Box, {
2181
- paddingBottom: 4,
2182
- paddingTop: 1,
2183
- children: /* @__PURE__ */ jsx(TasksTab, {
2184
- filterSlot: filterSlot,
2185
- identity: identity,
2186
- instances: [ ...instances.inFlight, ...instances.settled ],
2187
- loading: instances.loading,
2188
- onOpenTask: setOpenTask,
2189
- onOpenWorkflow: openInstance,
2190
- segment: tab,
2191
- truncated: instances.truncated,
2192
- unreadable: instances.unreadable
2193
- })
2194
- })
4280
+ children: /* @__PURE__ */ jsx(Flex, {
4281
+ direction: "column",
4282
+ flex: 1,
4283
+ style: {
4284
+ minHeight: 0
4285
+ },
4286
+ children: /* @__PURE__ */ jsx(HomePanel, {
4287
+ boardDefinition: boardDefinition,
4288
+ filterSlot: filterSlot,
4289
+ identity: identity,
4290
+ instances: instances,
4291
+ onOpenDefinition: onOpenDefinition,
4292
+ onOpenTask: setOpenTask,
4293
+ onOpenWorkflow: openInstance,
4294
+ onSelectWorkflow: onSelectWorkflow,
4295
+ tab: tab
2195
4296
  })
2196
4297
  })
2197
- }) ]
4298
+ })
2198
4299
  }), openTask ? /* @__PURE__ */ jsx(ToolActivityDialog, {
2199
4300
  onClose: () => setOpenTask(null),
2200
4301
  target: openTask
@@ -2202,4 +4303,25 @@ function WorkflowsHome({onOpenInstance: onOpenInstance}) {
2202
4303
  });
2203
4304
  }
2204
4305
 
4306
+ function HomePanel({boardDefinition: boardDefinition, filterSlot: filterSlot, identity: identity, instances: instances, onOpenDefinition: onOpenDefinition, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, onSelectWorkflow: onSelectWorkflow, tab: tab}) {
4307
+ return tab === "definitions" ? /* @__PURE__ */ jsx(DefinitionsTab, {
4308
+ onOpenDefinition: onOpenDefinition
4309
+ }) : tab === "documents" ? /* @__PURE__ */ jsx(DocumentsTab, {
4310
+ filterSlot: filterSlot,
4311
+ onOpenDefinition: onOpenDefinition,
4312
+ onSelectWorkflow: onSelectWorkflow,
4313
+ routeDefinition: boardDefinition
4314
+ }) : /* @__PURE__ */ jsx(TasksTab, {
4315
+ filterSlot: filterSlot,
4316
+ identity: identity,
4317
+ instances: [ ...instances.inFlight, ...instances.settled ],
4318
+ loading: instances.loading,
4319
+ onOpenTask: onOpenTask,
4320
+ onOpenWorkflow: onOpenWorkflow,
4321
+ segment: tab,
4322
+ truncated: instances.truncated,
4323
+ unreadable: instances.unreadable
4324
+ });
4325
+ }
4326
+
2205
4327
  export { WorkflowsToolRoot as default };