@sanity/workflow-studio-plugin 0.4.0 → 0.5.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,18 +1,22 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
 
3
- import { Box, Text, Flex, Button, Badge, Stack, Card, useTheme_v2, Spinner, TextInput, Dialog, Menu, MenuItem, TabList, Tab, Container } from "@sanity/ui";
3
+ import { Flex, Spinner, Stack, Box, Text, Button, Card, TextInput, Dialog, Badge, useTheme_v2, Container, TabPanel } from "@sanity/ui";
4
4
 
5
- import { useMemo, useState, useEffect, useRef } from "react";
5
+ import { useState, useEffect, useMemo, useRef } from "react";
6
6
 
7
7
  import { useRouter } from "sanity/router";
8
8
 
9
- import { DocPreviewLink, findActivity, definitionSnapshotOf, instanceTitle, stateByActivity, isActivityAssignedTo, assigneeUserIdsOf, isOpenActivityStatus, stageTitle, formatDate, parseStoredDateValue, useWorkflowContext, gdrLocality, useUserDisplays, useProjectMembers, DismissablePopover, HoverHint, DocRefFace, useAssignmentIdentity, LoadingRow, AbortedBadge, instanceNotice, StaleLock, ActivitiesList, TodoItemsList, isEvaluationStale, useWorkflowInstanceEntry, InvalidDocNotice, useDefinition, openActivityGone, GroupHeading, ActivityDetailDialog, instanceBreadcrumb, StageChip, readDeployedDefinitions, describeError, StageGlyph, useDocLink, HintedMenuButton, ActivityStatusIcon, UserAvatarGroup } from "./index.js";
9
+ import { instanceNotice, StaleLock, ActivitiesList, TodoItemsList, isEvaluationStale, useWorkflowInstanceEntry, InvalidDocNotice, LoadingRow, useDefinition, openActivityGone, GroupHeading, ActivityDetailDialog, instanceBreadcrumb, HoverHint, instanceTitle, formatDate, StageChip, stageTitle, AbortedBadge, DocPreviewLink, useWorkflowContext, readDeployedDefinitions, describeError, DismissablePopover, definitionSnapshotOf, findActivity, stateByActivity, findActivityNode, rowDateControlState, rowAssignControlState, isActivityAssignedTo, assigneeUserIdsOf, isOpenActivityStatus, committedRowFace, activityRowProps, parseStoredDateValue, gdrLocality, useUserDisplays, useProjectMembers, DocRefFace, useSaveField, dateControlValue, stopRowMouseDown, stopRowClick, parseDateFieldValue, serializeDateFieldValue, dateControlKind, editTargetOf, CollapsibleBand, LinkChip, BreadcrumbTail, StageFace, TrailingHairline, ActivityRow, UnreadableDocsNote, useAssignmentIdentity, TabSwitch, workflowsTabCodec } from "./index.js";
10
10
 
11
- import { ChevronDownIcon } from "@sanity/icons/ChevronDown";
11
+ import { ArrowLeftIcon } from "@sanity/icons/ArrowLeft";
12
+
13
+ import { WorkflowDiagram } from "@sanity/workflow-diagram";
14
+
15
+ import { entryDocRefs, terminalState, latestDeployedDefinitions, isStartableDefinition, extractDocumentId, findOpenStageEntry } from "@sanity/workflow-engine";
12
16
 
13
- import { ChevronRightIcon } from "@sanity/icons/ChevronRight";
17
+ import { AddIcon } from "@sanity/icons/Add";
14
18
 
15
- import { entryDocRefs, extractDocumentId, findOpenStageEntry, terminalState, latestDeployedDefinitions, isStartableDefinition } from "@sanity/workflow-engine";
19
+ import { SearchIcon } from "@sanity/icons/Search";
16
20
 
17
21
  import { CheckmarkIcon } from "@sanity/icons/Checkmark";
18
22
 
@@ -20,33 +24,31 @@ import { CloseIcon } from "@sanity/icons/Close";
20
24
 
21
25
  import { FilterIcon } from "@sanity/icons/Filter";
22
26
 
23
- import { MemberAvatar, DatePicker } from "@sanity/workflow-components";
27
+ import { MemberAvatar, DatePicker, ClearableDatePicker } from "@sanity/workflow-components";
24
28
 
25
29
  import { endOfDay } from "date-fns/endOfDay";
26
30
 
27
- import { useDocumentPreviewStore, useSchema } from "sanity";
28
-
29
- import { useWorkflowInstances } from "@sanity/workflow-studio";
30
-
31
- import { ArrowLeftIcon } from "@sanity/icons/ArrowLeft";
32
-
33
- import { WorkflowDiagram } from "@sanity/workflow-diagram";
31
+ import { createPortal } from "react-dom";
34
32
 
35
- import { AddIcon } from "@sanity/icons/Add";
33
+ import { useDocumentPreviewStore, useSchema } from "sanity";
36
34
 
37
- import { SearchIcon } from "@sanity/icons/Search";
35
+ import { CalendarIcon } from "@sanity/icons/Calendar";
38
36
 
39
- import { EllipsisHorizontalIcon } from "@sanity/icons/EllipsisHorizontal";
37
+ import { useWorkflowInstances } from "@sanity/workflow-studio";
40
38
 
41
- function MutedNote({text: text}) {
39
+ function SpinnerSlot({busy: busy}) {
42
40
  /* @__PURE__ */
43
- return jsx(Box, {
44
- paddingY: 3,
45
- children: /* @__PURE__ */ jsx(Text, {
46
- muted: !0,
47
- size: 1,
48
- children: text
49
- })
41
+ return jsx(Flex, {
42
+ align: "center",
43
+ flex: "none",
44
+ justify: "center",
45
+ style: {
46
+ width: 19,
47
+ height: 19
48
+ },
49
+ children: busy ? /* @__PURE__ */ jsx(Spinner, {
50
+ muted: !0
51
+ }) : null
50
52
  });
51
53
  }
52
54
 
@@ -56,201 +58,605 @@ function documentRefsOf(instance) {
56
58
  return [ ...seen.values() ];
57
59
  }
58
60
 
59
- function RefChips({refs: refs, max: max = 2}) {
60
- if (refs.length === 0) return null;
61
- const shown = refs.slice(0, max), overflow = refs.length - shown.length;
62
- /* @__PURE__ */
63
- return jsxs(Flex, {
64
- align: "center",
65
- gap: 3,
66
- wrap: "wrap",
67
- children: [ shown.map(ref => /* @__PURE__ */ jsx(DocPreviewLink, {
68
- gdr: ref,
69
- layout: "inline"
70
- }, ref.id)), overflow > 0 ? /* @__PURE__ */ jsxs(Text, {
71
- muted: !0,
72
- size: 1,
73
- children: [ "+", overflow, " more" ]
74
- }) : null ]
61
+ function InstanceTaskLists({entry: entry, onOpenActivity: onOpenActivity}) {
62
+ return instanceNotice(entry) || /* @__PURE__ */ jsx(StaleLock, {
63
+ stale: isEvaluationStale(entry),
64
+ children: /* @__PURE__ */ jsxs(Stack, {
65
+ children: [
66
+ /* @__PURE__ */ jsx(ActivitiesList, {
67
+ entry: entry,
68
+ onOpenActivity: onOpenActivity
69
+ }),
70
+ /* @__PURE__ */ jsx(TodoItemsList, {
71
+ entry: entry
72
+ }) ]
73
+ })
75
74
  });
76
75
  }
77
76
 
78
- function DocRefChips({instance: instance, max: max = 2}) {
79
- const refs = useMemo(() => documentRefsOf(instance), [ instance ]);
80
- /* @__PURE__ */
81
- return jsx(RefChips, {
82
- max: max,
83
- refs: refs
77
+ function WorkflowInstanceDetail({instanceId: instanceId, onBack: onBack}) {
78
+ const entry = useWorkflowInstanceEntry(instanceId), [graceOver, setGraceOver] = useState(!1);
79
+ return useEffect(() => {
80
+ const timer = setTimeout(() => setGraceOver(!0), 2500);
81
+ return () => clearTimeout(timer);
82
+ }, [ instanceId ]), entry?.invalid ? /* @__PURE__ */ jsx(Box, {
83
+ padding: 4,
84
+ children: /* @__PURE__ */ jsx(InvalidDocNotice, {
85
+ invalid: entry.invalid
86
+ })
87
+ }) : entry ? /* @__PURE__ */ jsx(InstanceDetailPanel, {
88
+ entry: entry,
89
+ onBack: onBack
90
+ }) : /* @__PURE__ */ jsx(Box, {
91
+ padding: 4,
92
+ children: graceOver ? /* @__PURE__ */ jsx(NotFound, {
93
+ id: instanceId,
94
+ onBack: onBack
95
+ }) : /* @__PURE__ */ jsx(LoadingRow, {
96
+ label: "Loading workflow…"
97
+ })
84
98
  });
85
99
  }
86
100
 
87
- function datesOf(state) {
88
- return state.filter(entry => entry._type === "date" || entry._type === "datetime").map(entry => entry.value).filter(value => typeof value == "string" && value !== "");
89
- }
90
-
91
- function spawnOrigins(instances) {
92
- const origins = /* @__PURE__ */ new Map;
93
- for (const parent of instances) for (const row of parent.subworkflows ?? []) origins.set(extractDocumentId(row.ref.id), {
94
- parent: parent,
95
- activityName: row.activity,
96
- cohortStage: row.cohortStage
101
+ function NotFound({id: id, onBack: onBack}) {
102
+ /* @__PURE__ */
103
+ return jsxs(Stack, {
104
+ gap: 4,
105
+ children: [
106
+ /* @__PURE__ */ jsxs(Text, {
107
+ muted: !0,
108
+ size: 1,
109
+ children: [ "No workflow with id “", id, "” — it may have been deleted." ]
110
+ }),
111
+ /* @__PURE__ */ jsx(Flex, {
112
+ children: /* @__PURE__ */ jsx(Button, {
113
+ fontSize: 1,
114
+ icon: ArrowLeftIcon,
115
+ mode: "ghost",
116
+ onClick: onBack,
117
+ padding: 2,
118
+ text: "Back to workflows"
119
+ })
120
+ }) ]
97
121
  });
98
- return origins;
99
- }
100
-
101
- function rootOf(instance, byId) {
102
- const rootRef = instance.ancestors[0];
103
- return rootRef ? byId.get(extractDocumentId(rootRef.id)) ?? instance : instance;
104
122
  }
105
123
 
106
- function taskRowsOf(args) {
107
- const {instance: instance, breadcrumb: breadcrumb, identity: identity} = args, open = findOpenStageEntry(instance);
108
- if (!open) return [];
109
- const definition = definitionSnapshotOf(instance), state = stateByActivity(instance), rows = (open.activities ?? []).map(activity => {
110
- const fields = state.get(activity.name) ?? [], declared = findActivity({
124
+ function InstanceDetailPanel({entry: entry, onBack: onBack}) {
125
+ const definition = useDefinition(entry), [openActivity, setOpenActivity] = useState(null), {instance: instance} = entry;
126
+ return useEffect(() => {
127
+ openActivity && openActivityGone({
128
+ entry: entry,
129
+ target: openActivity
130
+ }) && setOpenActivity(null);
131
+ }, [ entry, openActivity ]), /* @__PURE__ */ jsxs(Flex, {
132
+ direction: "column",
133
+ height: "fill",
134
+ overflow: "hidden",
135
+ children: [
136
+ /* @__PURE__ */ jsx(DetailHeader, {
111
137
  definition: definition,
112
- stageName: open.name,
113
- activityName: activity.name
114
- });
115
- return {
116
- instanceId: instance._id,
117
- activityName: activity.name,
118
- title: declared.title,
119
- description: declared.description,
120
- breadcrumb: breadcrumb,
121
- stage: open.name,
122
- stageTitle: stageTitle(definition, open.name),
123
- status: activity.status,
124
- open: isOpenActivityStatus(activity.status),
125
- dates: datesOf(fields),
126
- assigneeIds: assigneeUserIdsOf(fields),
127
- assignedToMe: identity !== void 0 && isActivityAssignedTo({
128
- state: fields,
129
- who: identity
138
+ entry: entry,
139
+ onBack: onBack
140
+ }),
141
+ /* @__PURE__ */ jsx(Box, {
142
+ flex: 1,
143
+ overflow: "auto",
144
+ paddingX: 4,
145
+ children: /* @__PURE__ */ jsxs(Stack, {
146
+ gap: 4,
147
+ paddingBottom: 5,
148
+ children: [
149
+ /* @__PURE__ */ jsx(StudioDocumentsSection, {
150
+ instance: instance
151
+ }),
152
+ /* @__PURE__ */ jsx(StageDiagramSection, {
153
+ definition: definition,
154
+ entry: entry
155
+ }),
156
+ /* @__PURE__ */ jsx(GroupHeading, {
157
+ title: "All tasks"
158
+ }),
159
+ /* @__PURE__ */ jsx(InstanceTaskLists, {
160
+ entry: entry,
161
+ onOpenActivity: name => setOpenActivity({
162
+ stage: instance.currentStage,
163
+ activityName: name
164
+ })
165
+ }) ]
130
166
  })
131
- };
167
+ }), openActivity ? /* @__PURE__ */ jsx(ActivityDetailDialog, {
168
+ activityName: openActivity.activityName,
169
+ breadcrumb: instanceBreadcrumb(instance, definition),
170
+ definition: definition,
171
+ entry: entry,
172
+ onClose: () => setOpenActivity(null)
173
+ }) : null ]
132
174
  });
133
- return [ ...rows.filter(r => r.open), ...rows.filter(r => !r.open) ];
134
175
  }
135
176
 
136
- function deriveTaskGroups(args) {
137
- const {instances: instances, identity: identity} = args, byId = new Map(instances.map(instance => [ instance._id, instance ])), origins = spawnOrigins(instances), groups = /* @__PURE__ */ new Map, ordered = [ ...instances ].sort((a, b) => a.startedAt.localeCompare(b.startedAt));
138
- for (const instance of ordered) {
139
- const origin = origins.get(instance._id), breadcrumb = origin ? findActivity({
140
- definition: definitionSnapshotOf(origin.parent),
141
- stageName: origin.cohortStage,
142
- activityName: origin.activityName
143
- }).title : void 0, rows = taskRowsOf({
144
- instance: instance,
145
- breadcrumb: breadcrumb,
146
- identity: identity
147
- });
148
- if (rows.length === 0) continue;
149
- const root = rootOf(instance, byId), documents = documentRefsOf(root), document = documents[0], key = document ? `doc:${document.id}` : `def:${root.definition}`, held = groups.get(key);
150
- if (held) {
151
- held.rows.push(...rows);
152
- continue;
153
- }
154
- const definition = definitionSnapshotOf(root);
155
- groups.set(key, {
156
- group: {
157
- key: key,
158
- document: document,
159
- documents: documents,
160
- instanceId: root._id,
161
- workflowName: root.definition,
162
- workflowTitle: instanceTitle(root, definition),
163
- workflowDescription: definition?.description
164
- },
165
- rows: rows
166
- });
167
- }
168
- const assembled = [ ...groups.values() ].map(({group: group, rows: rows}) => ({
169
- ...group,
170
- rows: rows
171
- }));
172
- return {
173
- withDocuments: assembled.filter(g => g.document !== void 0),
174
- withoutDocuments: assembled.filter(g => g.document === void 0)
175
- };
177
+ function DetailHeader({entry: entry, definition: definition, onBack: onBack}) {
178
+ const {instance: instance, ready: ready} = entry;
179
+ /* @__PURE__ */
180
+ return jsx(Card, {
181
+ flex: "none",
182
+ paddingX: 4,
183
+ paddingY: 3,
184
+ children: /* @__PURE__ */ jsxs(Flex, {
185
+ align: "center",
186
+ gap: 3,
187
+ wrap: "wrap",
188
+ children: [
189
+ /* @__PURE__ */ jsx(HoverHint, {
190
+ text: "Back to workflows",
191
+ children: /* @__PURE__ */ jsx(Button, {
192
+ "aria-label": "Back to workflows",
193
+ fontSize: 1,
194
+ icon: ArrowLeftIcon,
195
+ mode: "bleed",
196
+ onClick: onBack,
197
+ padding: 2
198
+ })
199
+ }),
200
+ /* @__PURE__ */ jsxs(Stack, {
201
+ gap: 2,
202
+ children: [
203
+ /* @__PURE__ */ jsx(Text, {
204
+ size: 1,
205
+ weight: "semibold",
206
+ children: instanceTitle(instance, definition)
207
+ }),
208
+ /* @__PURE__ */ jsxs(Text, {
209
+ muted: !0,
210
+ size: 1,
211
+ children: [ "Started ", formatDate(instance.startedAt) ]
212
+ }) ]
213
+ }), terminalState(instance) === "in-flight" ? /* @__PURE__ */ jsx(StageChip, {
214
+ title: stageTitle(definition, instance.currentStage)
215
+ }) : /* @__PURE__ */ jsx(AbortedBadge, {
216
+ instance: instance
217
+ }),
218
+ /* @__PURE__ */ jsx(SpinnerSlot, {
219
+ busy: !ready || isEvaluationStale(entry)
220
+ }) ]
221
+ })
222
+ });
176
223
  }
177
224
 
178
- function onlyMyTasks(groups) {
179
- const cut = list => list.map(group => ({
180
- ...group,
181
- rows: group.rows.filter(row => row.assignedToMe)
182
- })).filter(group => group.rows.length > 0);
183
- return {
184
- withDocuments: cut(groups.withDocuments),
185
- withoutDocuments: cut(groups.withoutDocuments)
186
- };
225
+ function StudioDocumentsSection({instance: instance}) {
226
+ const refs = documentRefsOf(instance);
227
+ return refs.length === 0 ? null : /* @__PURE__ */ jsxs(Stack, {
228
+ gap: 3,
229
+ marginTop: 3,
230
+ children: [
231
+ /* @__PURE__ */ jsx(Text, {
232
+ muted: !0,
233
+ size: 1,
234
+ weight: "medium",
235
+ children: refs.length === 1 ? "Studio document" : "Studio documents"
236
+ }),
237
+ /* @__PURE__ */ jsx(Stack, {
238
+ gap: 2,
239
+ children: refs.map(ref => /* @__PURE__ */ jsx(DocPreviewLink, {
240
+ gdr: ref
241
+ }, ref.id))
242
+ }) ]
243
+ });
187
244
  }
188
245
 
189
- function taskFilterOptions(groups) {
190
- const all = [ ...groups.withDocuments, ...groups.withoutDocuments ], workflows = /* @__PURE__ */ new Map, documents = /* @__PURE__ */ new Map;
191
- for (const group of all) workflows.set(group.workflowName, group.workflowTitle),
192
- group.document && documents.set(group.document.id, group.document);
193
- return {
194
- workflows: [ ...workflows ].map(([name, title]) => ({
195
- name: name,
196
- title: title
197
- })),
198
- documents: [ ...documents.values() ]
199
- };
246
+ function StageDiagramSection({entry: entry, definition: definition}) {
247
+ const {instance: instance} = entry;
248
+ return definition ?
249
+ /* @__PURE__ */ jsx(WorkflowDiagram, {
250
+ currentStage: terminalState(instance) === "in-flight" ? instance.currentStage : void 0,
251
+ definition: definition,
252
+ evaluation: entry.evaluation,
253
+ explain: !0,
254
+ guardCount: entry.guards?.length,
255
+ height: 340,
256
+ history: instance.history
257
+ }, instance._id) : /* @__PURE__ */ jsx(Card, {
258
+ border: !0,
259
+ marginTop: 3,
260
+ padding: 3,
261
+ radius: 2,
262
+ tone: "caution",
263
+ children: /* @__PURE__ */ jsx(Text, {
264
+ muted: !0,
265
+ size: 1,
266
+ children: "Couldn’t load this workflow’s full details — the stage diagram is unavailable, and technical names show instead of titles."
267
+ })
268
+ });
200
269
  }
201
270
 
202
- function hasActiveFilters(filters) {
203
- return Object.values(filters).some(value => value !== void 0 && (!Array.isArray(value) || value.length > 0));
271
+ function MutedNote({text: text}) {
272
+ /* @__PURE__ */
273
+ return jsx(Box, {
274
+ paddingY: 3,
275
+ children: /* @__PURE__ */ jsx(Text, {
276
+ muted: !0,
277
+ size: 1,
278
+ children: text
279
+ })
280
+ });
204
281
  }
205
282
 
206
- function toggledValue(args) {
207
- const {filters: filters, key: key, value: value} = args, current = filters[key] ?? [], next = current.includes(value) ? current.filter(v => v !== value) : [ ...current, value ];
208
- if (next.length > 0) return {
209
- ...filters,
210
- [key]: next
283
+ function useStartableDefinitions() {
284
+ const {engine: engine} = useWorkflowContext(), [definitions, setDefinitions] = useState(void 0), [error, setError] = useState(void 0);
285
+ return useEffect(() => {
286
+ let cancelled = !1;
287
+ return (async () => {
288
+ const rows = await readDeployedDefinitions(engine), startable = latestDeployedDefinitions(rows).filter(row => isStartableDefinition(row)).map(row => ({
289
+ name: row.name,
290
+ title: row.title ?? row.name,
291
+ description: row.description
292
+ }));
293
+ cancelled || (setDefinitions(startable), setError(void 0));
294
+ })().catch(err => {
295
+ cancelled || setError(describeError(err));
296
+ }), () => {
297
+ cancelled = !0;
298
+ };
299
+ }, [ engine ]), {
300
+ definitions: definitions,
301
+ error: error
211
302
  };
212
- const {[key]: _emptied, ...rest} = filters;
213
- return rest;
214
303
  }
215
304
 
216
- const STATUS_LABEL = {
217
- open: "Open",
218
- completed: "Completed"
219
- }, DATE_PRESET_LABEL = {
220
- past: "In the past",
221
- none: "No date"
222
- };
305
+ function NewWorkflowButton() {
306
+ const {openStartDialog: openStartDialog} = useWorkflowContext(), [open, setOpen] = useState(!1);
307
+ /* @__PURE__ */
308
+ return jsx(DismissablePopover, {
309
+ content: open ? /* @__PURE__ */ jsx(DefinitionPickerPanel, {
310
+ onPick: definition => {
311
+ setOpen(!1), openStartDialog({
312
+ definition: definition.name,
313
+ label: definition.title
314
+ });
315
+ }
316
+ }) : null,
317
+ onDismiss: () => setOpen(!1),
318
+ open: open,
319
+ children: /* @__PURE__ */ jsx(Button, {
320
+ fontSize: 1,
321
+ gap: 2,
322
+ icon: AddIcon,
323
+ onClick: () => setOpen(v => !v),
324
+ padding: 2,
325
+ selected: open,
326
+ text: "New workflow"
327
+ })
328
+ });
329
+ }
223
330
 
224
- function dateLabel(date) {
225
- return typeof date == "string" ? DATE_PRESET_LABEL[date] : `Before ${formatDate(date.before)}`;
331
+ function DefinitionPickerPanel({onPick: onPick}) {
332
+ const {definitions: definitions, error: error} = useStartableDefinitions(), [query, setQuery] = useState(""), visible = useMemo(() => {
333
+ const q = query.trim().toLowerCase();
334
+ return definitions ? q ? definitions.filter(d => `${d.title} ${d.name}`.toLowerCase().includes(q)) : definitions : [];
335
+ }, [ definitions, query ]);
336
+ /* @__PURE__ */
337
+ return jsx(Box, {
338
+ padding: 2,
339
+ style: {
340
+ width: 340
341
+ },
342
+ children: /* @__PURE__ */ jsxs(Stack, {
343
+ gap: 2,
344
+ children: [
345
+ /* @__PURE__ */ jsx(TextInput, {
346
+ autoFocus: !0,
347
+ fontSize: 1,
348
+ icon: SearchIcon,
349
+ onChange: event => setQuery(event.currentTarget.value),
350
+ placeholder: "Filter workflows…",
351
+ value: query
352
+ }), error ? /* @__PURE__ */ jsx(MutedNote, {
353
+ text: `Could not load the deployed workflows: ${error}`
354
+ }) : null, !definitions && !error ? /* @__PURE__ */ jsx(LoadingRow, {
355
+ label: "Loading workflows…"
356
+ }) : null, definitions && definitions.length === 0 ? /* @__PURE__ */ jsx(MutedNote, {
357
+ text: "No startable workflows are deployed"
358
+ }) : null, definitions && definitions.length > 0 && visible.length === 0 ? /* @__PURE__ */ jsx(MutedNote, {
359
+ text: "No workflows match"
360
+ }) : null,
361
+ /* @__PURE__ */ jsx(Stack, {
362
+ gap: 1,
363
+ style: {
364
+ maxHeight: 320,
365
+ overflowY: "auto"
366
+ },
367
+ children: visible.map(definition => /* @__PURE__ */ jsx(Card, {
368
+ as: "button",
369
+ onClick: () => onPick(definition),
370
+ padding: 3,
371
+ radius: 2,
372
+ style: {
373
+ cursor: "pointer",
374
+ textAlign: "left"
375
+ },
376
+ children: /* @__PURE__ */ jsxs(Stack, {
377
+ gap: 2,
378
+ children: [
379
+ /* @__PURE__ */ jsx(Text, {
380
+ size: 1,
381
+ weight: "medium",
382
+ children: definition.title
383
+ }), definition.description ? /* @__PURE__ */ jsx(Text, {
384
+ muted: !0,
385
+ size: 1,
386
+ textOverflow: "ellipsis",
387
+ children: definition.description
388
+ }) : null ]
389
+ })
390
+ }, definition.name))
391
+ }) ]
392
+ })
393
+ });
226
394
  }
227
395
 
228
- function withoutChip(filters, chip) {
229
- if (chip.key === "date") {
230
- const {date: _cleared, ...rest} = filters;
231
- return rest;
232
- }
233
- return chip.value === void 0 ? filters : toggledValue({
234
- filters: filters,
235
- key: chip.key,
236
- value: chip.value
396
+ function ToolActivityDialog({target: target, onClose: onClose}) {
397
+ const entry = useWorkflowInstanceEntry(target.instanceId), definition = useDefinition(entry), resolvedOnce = useRef(!1);
398
+ return entry && (resolvedOnce.current = !0), useEffect(() => {
399
+ resolvedOnce.current && openActivityGone({
400
+ entry: entry,
401
+ target: target
402
+ }) && onClose();
403
+ }, [ entry, target, onClose ]), entry?.invalid ? /* @__PURE__ */ jsx(Dialog, {
404
+ header: "Workflow unavailable",
405
+ id: "workflow-activity-detail",
406
+ onClose: onClose,
407
+ width: 1,
408
+ children: /* @__PURE__ */ jsx(Box, {
409
+ padding: 4,
410
+ children: /* @__PURE__ */ jsx(InvalidDocNotice, {
411
+ invalid: entry.invalid
412
+ })
413
+ })
414
+ }) : entry?.evaluation ? /* @__PURE__ */ jsx(ActivityDetailDialog, {
415
+ activityName: target.activityName,
416
+ breadcrumb: instanceBreadcrumb(entry.instance, definition),
417
+ definition: definition,
418
+ document: documentRefsOf(entry.instance)[0],
419
+ entry: entry,
420
+ onClose: onClose
421
+ }) : /* @__PURE__ */ jsx(Dialog, {
422
+ header: "Loading activity…",
423
+ id: "workflow-activity-detail",
424
+ onClose: onClose,
425
+ width: 1,
426
+ children: /* @__PURE__ */ jsx(Box, {
427
+ padding: 4,
428
+ children: /* @__PURE__ */ jsx(LoadingRow, {
429
+ label: "Fetching the live workflow state…"
430
+ })
431
+ })
237
432
  });
238
433
  }
239
434
 
240
- function chipsFor(args) {
241
- const {key: key, values: values, labelOf: labelOf} = args;
242
- return (values ?? []).map(value => ({
243
- key: key,
244
- value: value,
245
- label: labelOf(value)
246
- }));
435
+ function datesOf(state) {
436
+ return state.filter(entry => entry._type === "date" || entry._type === "datetime").map(entry => entry.value).filter(value => typeof value == "string" && value !== "");
247
437
  }
248
438
 
249
- function shownHits(hits, cap) {
250
- return hits.length <= cap + 1 ? [ ...hits ] : hits.slice(0, cap);
439
+ function spawnOrigins(instances) {
440
+ const origins = /* @__PURE__ */ new Map;
441
+ for (const parent of instances) for (const row of parent.subworkflows ?? []) origins.set(extractDocumentId(row.ref.id), {
442
+ parent: parent,
443
+ activityName: row.activity,
444
+ cohortStage: row.cohortStage
445
+ });
446
+ return origins;
251
447
  }
252
448
 
253
- const PENDING_LABEL = "…";
449
+ function rootOf(instance, byId) {
450
+ const rootRef = instance.ancestors[0];
451
+ return rootRef ? byId.get(extractDocumentId(rootRef.id)) ?? instance : instance;
452
+ }
453
+
454
+ function taskRowsOf(args) {
455
+ const {instance: instance, identity: identity} = args, open = findOpenStageEntry(instance);
456
+ if (!open) return [];
457
+ const definition = definitionSnapshotOf(instance), state = stateByActivity(instance), rows = (open.activities ?? []).map(activity => {
458
+ const fields = state.get(activity.name) ?? [], declared = findActivityNode({
459
+ definition: definition,
460
+ stageName: open.name,
461
+ activityName: activity.name
462
+ }) ?? {
463
+ name: activity.name
464
+ }, controlArgs = {
465
+ activity: declared,
466
+ status: activity.status,
467
+ editableFields: void 0
468
+ };
469
+ return {
470
+ instanceId: instance._id,
471
+ activityName: activity.name,
472
+ title: declared.title ?? activity.name,
473
+ stage: open.name,
474
+ status: activity.status,
475
+ open: isOpenActivityStatus(activity.status),
476
+ dates: datesOf(fields),
477
+ assigneeIds: assigneeUserIdsOf(fields),
478
+ assignedToMe: identity !== void 0 && isActivityAssignedTo({
479
+ state: fields,
480
+ who: identity
481
+ }),
482
+ assignControl: rowAssignControlState(controlArgs),
483
+ dateControl: rowDateControlState(controlArgs)
484
+ };
485
+ });
486
+ return [ ...rows.filter(r => r.open), ...rows.filter(r => !r.open) ];
487
+ }
488
+
489
+ function deriveTaskGroups(args) {
490
+ const {instances: instances, identity: identity} = args, byId = new Map(instances.map(instance => [ instance._id, instance ])), origins = spawnOrigins(instances), docGroups = /* @__PURE__ */ new Map, withoutDocuments = [], ordered = [ ...instances ].sort((a, b) => a.startedAt.localeCompare(b.startedAt));
491
+ for (const instance of ordered) {
492
+ const rows = taskRowsOf({
493
+ instance: instance,
494
+ identity: identity
495
+ }), first = rows[0];
496
+ if (!first) continue;
497
+ const origin = origins.get(instance._id), definition = definitionSnapshotOf(instance), entry = {
498
+ instanceId: instance._id,
499
+ workflowName: instance.definition,
500
+ title: instanceTitle(instance, definition),
501
+ stageTitle: stageTitle(definition, first.stage),
502
+ breadcrumb: origin ? findActivity({
503
+ definition: definitionSnapshotOf(origin.parent),
504
+ stageName: origin.cohortStage,
505
+ activityName: origin.activityName
506
+ }).title : void 0,
507
+ rows: rows
508
+ }, root = rootOf(instance, byId), documents = documentRefsOf(root), document = documents[0];
509
+ if (!document) {
510
+ withoutDocuments.push(entry);
511
+ continue;
512
+ }
513
+ const key = `doc:${document.id}`, held = docGroups.get(key);
514
+ if (held) {
515
+ held.instances.push(entry);
516
+ continue;
517
+ }
518
+ docGroups.set(key, {
519
+ group: {
520
+ key: key,
521
+ document: document,
522
+ documents: documents
523
+ },
524
+ instances: [ entry ]
525
+ });
526
+ }
527
+ return {
528
+ withDocuments: [ ...docGroups.values() ].map(({group: group, instances: bands}) => ({
529
+ ...group,
530
+ instances: bands
531
+ })),
532
+ withoutDocuments: withoutDocuments
533
+ };
534
+ }
535
+
536
+ function cutTaskGroups(groups, cut) {
537
+ const cutBands = bands => bands.filter(band => cut.keepBand?.(band) ?? !0).map(band => ({
538
+ ...band,
539
+ rows: band.rows.filter(cut.keepRow)
540
+ })).filter(band => band.rows.length > 0);
541
+ return {
542
+ withDocuments: groups.withDocuments.filter(group => cut.keepGroup?.(group) ?? !0).map(group => ({
543
+ ...group,
544
+ instances: cutBands(group.instances)
545
+ })).filter(group => group.instances.length > 0),
546
+ withoutDocuments: cut.dropUngrouped ? [] : cutBands(groups.withoutDocuments)
547
+ };
548
+ }
549
+
550
+ function onlyMyTasks(groups) {
551
+ return cutTaskGroups(groups, {
552
+ keepRow: row => row.assignedToMe
553
+ });
554
+ }
555
+
556
+ function rowActivityEval(entry, row) {
557
+ const evaluation = entry?.evaluation;
558
+ if (!(evaluation === void 0 || evaluation.currentStage.stage.name !== row.stage)) return evaluation.currentStage.activities.find(a => a.activity.name === row.activityName);
559
+ }
560
+
561
+ function taskRowRender(row, entry) {
562
+ const activityEval = rowActivityEval(entry, row);
563
+ if (entry?.evaluation === void 0 || activityEval === void 0) return {
564
+ face: committedRowFace(row),
565
+ assignState: row.assignControl,
566
+ assigneeIds: row.assigneeIds,
567
+ dates: row.dates,
568
+ dateState: row.dateControl,
569
+ terminalActions: [],
570
+ stale: !1
571
+ };
572
+ const activityState = stateByActivity(entry.instance), {face: face, assignState: assignState, assigneeIds: assigneeIds, terminalActions: terminalActions} = activityRowProps({
573
+ activityEval: activityEval,
574
+ activityState: activityState,
575
+ editableFields: entry.evaluation.editableFields
576
+ });
577
+ return {
578
+ face: face,
579
+ assignState: assignState,
580
+ assigneeIds: assigneeIds,
581
+ dates: datesOf(activityState.get(row.activityName) ?? []),
582
+ dateState: rowDateControlState({
583
+ activity: activityEval.activity,
584
+ status: activityEval.status,
585
+ editableFields: entry.evaluation.editableFields
586
+ }),
587
+ terminalActions: terminalActions,
588
+ stale: isEvaluationStale(entry)
589
+ };
590
+ }
591
+
592
+ function taskFilterOptions(groups) {
593
+ const workflows = /* @__PURE__ */ new Map, documents = /* @__PURE__ */ new Map;
594
+ for (const group of groups.withDocuments) {
595
+ documents.set(group.document.id, group.document);
596
+ for (const band of group.instances) workflows.set(band.workflowName, band.title);
597
+ }
598
+ for (const band of groups.withoutDocuments) workflows.set(band.workflowName, band.title);
599
+ return {
600
+ workflows: [ ...workflows ].map(([name, title]) => ({
601
+ name: name,
602
+ title: title
603
+ })),
604
+ documents: [ ...documents.values() ]
605
+ };
606
+ }
607
+
608
+ function hasActiveFilters(filters) {
609
+ return Object.values(filters).some(value => value !== void 0 && (!Array.isArray(value) || value.length > 0));
610
+ }
611
+
612
+ function toggledValue(args) {
613
+ const {filters: filters, key: key, value: value} = args, current = filters[key] ?? [], next = current.includes(value) ? current.filter(v => v !== value) : [ ...current, value ];
614
+ if (next.length > 0) return {
615
+ ...filters,
616
+ [key]: next
617
+ };
618
+ const {[key]: _emptied, ...rest} = filters;
619
+ return rest;
620
+ }
621
+
622
+ const STATUS_LABEL = {
623
+ open: "Open",
624
+ completed: "Completed"
625
+ }, DATE_PRESET_LABEL = {
626
+ past: "In the past",
627
+ none: "No date"
628
+ };
629
+
630
+ function dateLabel(date) {
631
+ return typeof date == "string" ? DATE_PRESET_LABEL[date] : `Before ${formatDate(date.before)}`;
632
+ }
633
+
634
+ function withoutChip(filters, chip) {
635
+ if (chip.key === "date") {
636
+ const {date: _cleared, ...rest} = filters;
637
+ return rest;
638
+ }
639
+ return chip.value === void 0 ? filters : toggledValue({
640
+ filters: filters,
641
+ key: chip.key,
642
+ value: chip.value
643
+ });
644
+ }
645
+
646
+ function chipsFor(args) {
647
+ const {key: key, values: values, labelOf: labelOf} = args;
648
+ return (values ?? []).map(value => ({
649
+ key: key,
650
+ value: value,
651
+ label: labelOf(value)
652
+ }));
653
+ }
654
+
655
+ function shownHits(hits, cap) {
656
+ return hits.length <= cap + 1 ? [ ...hits ] : hits.slice(0, cap);
657
+ }
658
+
659
+ const PENDING_LABEL = "…";
254
660
 
255
661
  function activeFilterChips(args) {
256
662
  const {filters: filters, options: options, assigneeNames: assigneeNames, documentTitles: documentTitles} = args, chips = [ ...chipsFor({
@@ -306,72 +712,18 @@ function rowMatches(args) {
306
712
  }));
307
713
  }
308
714
 
309
- function groupMatches(group, filters) {
310
- const workflows = activeArm(filters.workflows);
311
- if (workflows && !workflows.includes(group.workflowName)) return !1;
312
- const documentIds = activeArm(filters.documentIds);
313
- return !(documentIds && (!group.document || !documentIds.includes(group.document.id)));
314
- }
315
-
316
715
  function applyTaskFilters(args) {
317
- const {groups: groups, filters: filters, now: now} = args, cut = list => list.filter(group => groupMatches(group, filters)).map(group => ({
318
- ...group,
319
- rows: group.rows.filter(row => rowMatches({
716
+ const {groups: groups, filters: filters, now: now} = args, documentIds = activeArm(filters.documentIds), workflows = activeArm(filters.workflows);
717
+ return cutTaskGroups(groups, {
718
+ keepRow: row => rowMatches({
320
719
  row: row,
321
720
  filters: filters,
322
721
  now: now
323
- }))
324
- })).filter(group => group.rows.length > 0);
325
- return {
326
- withDocuments: cut(groups.withDocuments),
327
- withoutDocuments: cut(groups.withoutDocuments)
328
- };
329
- }
330
-
331
- function instanceFilterOptions(rows) {
332
- const workflows = /* @__PURE__ */ new Map, documents = /* @__PURE__ */ new Map;
333
- for (const row of rows) {
334
- workflows.set(row.instance.definition, row.workflowTitle);
335
- for (const doc of row.documents) documents.set(doc.id, doc);
336
- }
337
- return {
338
- workflows: [ ...workflows ].map(([name, title]) => ({
339
- name: name,
340
- title: title
341
- })),
342
- documents: [ ...documents.values() ]
343
- };
344
- }
345
-
346
- function applyInstanceFilters(args) {
347
- const {rows: rows, filters: filters} = args, workflows = activeArm(filters.workflows), documentIds = activeArm(filters.documentIds);
348
- return rows.filter(row => !workflows || workflows.includes(row.instance.definition)).filter(row => !documentIds || row.documents.some(doc => documentIds.includes(doc.id))).map(row => row.instance);
349
- }
350
-
351
- function hasTaskArms(filters) {
352
- return (filters.assigneeIds?.length ?? 0) > 0 || filters.date !== void 0;
353
- }
354
-
355
- function instanceStatusMatches(instance, statuses) {
356
- const arm = activeArm(statuses);
357
- if (!arm) return !0;
358
- const state = terminalState(instance);
359
- return state === "in-flight" ? arm.includes("open") : state === "completed" && arm.includes("completed");
360
- }
361
-
362
- function instanceIdsMatchingTaskArms(args) {
363
- const {instances: instances, identity: identity, filters: filters, now: now} = args;
364
- if (!hasTaskArms(filters)) return null;
365
- const groups = deriveTaskGroups({
366
- instances: instances,
367
- identity: identity
368
- }), {statuses: _workflowLevel, ...taskArms} = filters, cut = applyTaskFilters({
369
- groups: groups,
370
- filters: taskArms,
371
- now: now
372
- }), ids = /* @__PURE__ */ new Set;
373
- for (const group of [ ...cut.withDocuments, ...cut.withoutDocuments ]) for (const row of group.rows) ids.add(row.instanceId);
374
- return ids;
722
+ }),
723
+ keepBand: band => !workflows || workflows.includes(band.workflowName),
724
+ keepGroup: group => !documentIds || documentIds.includes(group.document.id),
725
+ dropUngrouped: documentIds !== void 0
726
+ });
375
727
  }
376
728
 
377
729
  const NOTHING = {
@@ -488,1044 +840,532 @@ function FilterPanel({filters: filters, options: options, onChange: onChange}) {
488
840
  }),
489
841
  /* @__PURE__ */ jsx(DateField, {
490
842
  ...fieldProps("date"),
491
- date: filters.date,
492
- onChange: next => {
493
- const {date: _cleared, ...rest} = filters;
494
- onChange(next === void 0 ? rest : {
495
- ...rest,
496
- date: next
497
- });
498
- }
499
- }), hasActiveFilters(filters) ? /* @__PURE__ */ jsx(Flex, {
500
- justify: "flex-end",
501
- children: /* @__PURE__ */ jsx(Button, {
502
- fontSize: 1,
503
- mode: "ghost",
504
- onClick: () => onChange({}),
505
- padding: 2,
506
- text: "Clear filters"
507
- })
508
- }) : null ]
509
- })
510
- });
511
- }
512
-
513
- const statusRows = () => Object.keys(STATUS_LABEL).map(key => ({
514
- key: key,
515
- text: STATUS_LABEL[key]
516
- }));
517
-
518
- function FieldLabel({text: text}) {
519
- /* @__PURE__ */
520
- return jsx(Text, {
521
- muted: !0,
522
- size: 1,
523
- weight: "medium",
524
- children: text
525
- });
526
- }
527
-
528
- function FilterField({label: label, placeholder: placeholder, pills: pills, onClearPill: onClearPill, onFocus: onFocus, onQuery: onQuery, query: query, open: open, onOpen: onOpen, children: children}) {
529
- const rootRef = useRef(null), inputRef = useRef(null);
530
- /* @__PURE__ */
531
- return jsxs(Stack, {
532
- gap: 2,
533
- children: [
534
- /* @__PURE__ */ jsx(FieldLabel, {
535
- text: label
536
- }),
537
- /* @__PURE__ */ jsxs("div", {
538
- ref: rootRef,
539
- style: {
540
- position: "relative"
541
- },
542
- children: [
543
- /* @__PURE__ */ jsx(TagBox, {
544
- inputRef: inputRef,
545
- label: label,
546
- onBlur: event => {
547
- rootRef.current?.contains(event.relatedTarget) || onOpen(!1);
548
- },
549
- onClearPill: onClearPill,
550
- onFocus: onFocus,
551
- onQuery: onQuery,
552
- pills: pills,
553
- placeholder: placeholder,
554
- query: query
555
- }),
556
- /* @__PURE__ */ jsx(FieldOverlay, {
557
- open: open,
558
- children: children
559
- }) ]
560
- }) ]
561
- });
562
- }
563
-
564
- function TagField({label: label, placeholder: placeholder, rows: rows, activeKeys: activeKeys, onToggle: onToggle, pillLabel: pillLabel, open: open, onOpen: onOpen, emptyNote: emptyNote = "Nothing matches"}) {
565
- const [query, setQuery] = useState(""), q = query.trim().toLowerCase(), hits = q ? rows.filter(row => row.text.toLowerCase().includes(q)) : rows, shown = shownHits(hits, HITS_CAP);
566
- /* @__PURE__ */
567
- return jsx(FilterField, {
568
- label: label,
569
- onClearPill: onToggle,
570
- onFocus: () => {
571
- setQuery(""), onOpen(!0);
572
- },
573
- onOpen: onOpen,
574
- onQuery: setQuery,
575
- open: open,
576
- pills: activeKeys.map(key => ({
577
- key: key,
578
- label: pillLabel(key)
579
- })),
580
- placeholder: placeholder,
581
- query: open ? query : "",
582
- children: /* @__PURE__ */ jsx(OptionsList, {
583
- activeKeys: activeKeys,
584
- emptyNote: emptyNote,
585
- hits: hits,
586
- onToggle: onToggle,
587
- shown: shown
588
- })
589
- });
590
- }
591
-
592
- function FieldOverlay({open: open, children: children}) {
593
- return open ? /* @__PURE__ */ jsx(Card, {
594
- radius: 2,
595
- shadow: 3,
596
- style: {
597
- insetInline: 0,
598
- position: "absolute",
599
- top: "calc(100% + 4px)",
600
- zIndex: 2
601
- },
602
- children: children
603
- }) : null;
604
- }
605
-
606
- function useTagInputStyle() {
607
- const {font: font} = useTheme_v2(), textSize = font.text.sizes[1];
608
- if (!textSize) throw new Error("useTagInputStyle: theme is missing text size 1");
609
- return {
610
- background: "transparent",
611
- border: "none",
612
- color: "var(--card-fg-color)",
613
- fontFamily: font.text.family,
614
- fontSize: textSize.fontSize,
615
- outline: "none",
616
- padding: "4px 6px",
617
- width: "100%"
618
- };
619
- }
620
-
621
- function TagBox({pills: pills, onClearPill: onClearPill, query: query, onQuery: onQuery, placeholder: placeholder, label: label, onFocus: onFocus, onBlur: onBlur, inputRef: inputRef}) {
622
- const inputStyle = useTagInputStyle();
623
- /* @__PURE__ */
624
- return jsx(Card, {
625
- border: !0,
626
- onClick: () => inputRef.current?.focus(),
627
- padding: 1,
628
- radius: 2,
629
- style: {
630
- cursor: "text"
631
- },
632
- children: /* @__PURE__ */ jsxs(Flex, {
633
- align: "center",
634
- gap: 1,
635
- wrap: "wrap",
636
- children: [ pills.map(pill => /* @__PURE__ */ jsx(FilterPill, {
637
- label: pill.label,
638
- onClear: () => onClearPill(pill.key)
639
- }, pill.key)),
640
- /* @__PURE__ */ jsx(Box, {
641
- flex: 1,
642
- style: {
643
- minWidth: 90
644
- },
645
- children: /* @__PURE__ */ jsx("input", {
646
- "aria-label": label,
647
- onBlur: onBlur,
648
- onChange: event => onQuery(event.currentTarget.value),
649
- onFocus: onFocus,
650
- placeholder: pills.length === 0 ? placeholder : void 0,
651
- ref: inputRef,
652
- style: inputStyle,
653
- value: query
654
- })
655
- }) ]
656
- })
657
- });
658
- }
659
-
660
- function OptionsList({hits: hits, shown: shown, activeKeys: activeKeys, onToggle: onToggle, emptyNote: emptyNote}) {
661
- /* @__PURE__ */
662
- return jsxs(Stack, {
663
- space: 1,
664
- children: [ hits.length === 0 ? /* @__PURE__ */ jsx(Box, {
665
- padding: 2,
666
- children: /* @__PURE__ */ jsx(MutedNote, {
667
- text: emptyNote
668
- })
669
- }) : null, shown.map(row => /* @__PURE__ */ jsx(OptionRow, {
670
- active: activeKeys.includes(row.key),
671
- onPick: () => onToggle(row.key),
672
- children: row.node ?? /* @__PURE__ */ jsx(Text, {
673
- size: 1,
674
- children: row.text
675
- })
676
- }, row.key)), hits.length > shown.length ? /* @__PURE__ */ jsx(Box, {
677
- padding: 2,
678
- children: /* @__PURE__ */ jsx(MutedNote, {
679
- text: `…and ${hits.length - shown.length} more — keep typing to narrow`
680
- })
681
- }) : null ]
682
- });
683
- }
684
-
685
- function FilterPill({label: label, onClear: onClear}) {
686
- /* @__PURE__ */
687
- return jsx(Badge, {
688
- fontSize: 1,
689
- mode: "outline",
690
- radius: "full",
691
- children: /* @__PURE__ */ jsxs(Flex, {
692
- align: "center",
693
- gap: 1,
694
- children: [ label,
695
- /* @__PURE__ */ jsx(Button, {
696
- "aria-label": `Clear ${label}`,
697
- fontSize: 0,
698
- icon: CloseIcon,
699
- mode: "bleed",
700
- onClick: onClear,
701
- onMouseDown: event => event.preventDefault(),
702
- padding: 1
703
- }) ]
704
- })
705
- });
706
- }
707
-
708
- function OptionRow({active: active, onPick: onPick, children: children}) {
709
- /* @__PURE__ */
710
- return jsx(Card, {
711
- as: "button",
712
- onMouseDown: event => {
713
- event.preventDefault(), onPick();
714
- },
715
- padding: 2,
716
- radius: 2,
717
- style: {
718
- cursor: "pointer",
719
- textAlign: "left"
720
- },
721
- tone: active ? "primary" : "default",
722
- children: /* @__PURE__ */ jsxs(Flex, {
723
- align: "center",
724
- gap: 2,
725
- children: [
726
- /* @__PURE__ */ jsx(Box, {
727
- flex: 1,
728
- children: children
729
- }), active ? /* @__PURE__ */ jsx(Text, {
730
- size: 1,
731
- children: /* @__PURE__ */ jsx(CheckmarkIcon, {})
732
- }) : null ]
733
- })
734
- });
735
- }
736
-
737
- function AssigneeField({activeIds: activeIds, onToggle: onToggle, open: open, onOpen: onOpen}) {
738
- const {members: members, loading: loading} = useProjectMembers(), displays = useUserDisplays(activeIds), names = new Map(displays.map(d => [ d.id, d.name ])), rows = members.map(member => ({
739
- key: member.id,
740
- text: `${member.displayName} ${member.email ?? ""}`.trim(),
741
- node: /* @__PURE__ */ jsxs(Flex, {
742
- align: "center",
743
- gap: 2,
744
- children: [
745
- /* @__PURE__ */ jsx(MemberAvatar, {
746
- imageUrl: member.imageUrl,
747
- name: member.displayName
748
- }),
749
- /* @__PURE__ */ jsx(Text, {
750
- size: 1,
751
- children: member.displayName
752
- }) ]
753
- })
754
- }));
755
- /* @__PURE__ */
756
- return jsx(TagField, {
757
- activeKeys: activeIds,
758
- label: "Assignee",
759
- onOpen: onOpen,
760
- onToggle: onToggle,
761
- open: open,
762
- pillLabel: id => loading ? PENDING_LABEL : names.get(id) ?? id,
763
- placeholder: "Anyone",
764
- rows: rows
765
- });
766
- }
767
-
768
- function DocumentField({documents: documents, activeIds: activeIds, onToggle: onToggle, open: open, onOpen: onOpen}) {
769
- const labels = useDocumentChipLabels(activeIds, documents), {titles: titles} = useDocPreviewTitles(documents);
770
- if (documents.length === 0) return null;
771
- const rows = documents.map(doc => ({
772
- key: doc.id,
773
- text: `${titles.get(doc.id) ?? ""} ${doc.type} ${extractDocumentId(doc.id)}`,
774
- node: /* @__PURE__ */ jsx(DocRefFace, {
775
- gdr: doc
776
- })
777
- }));
778
- /* @__PURE__ */
779
- return jsx(TagField, {
780
- activeKeys: activeIds,
781
- emptyNote: "No documents match",
782
- label: "Document",
783
- onOpen: onOpen,
784
- onToggle: onToggle,
785
- open: open,
786
- pillLabel: id => labels.get(id) ?? PENDING_LABEL,
787
- placeholder: "Any document",
788
- rows: rows
789
- });
790
- }
791
-
792
- function DateField({date: date, onChange: onChange, open: open, onOpen: onOpen}) {
793
- const pickedDate = typeof date == "object" ? new Date(date.before) : void 0, pick = next => {
794
- onChange(next), onOpen(!1);
795
- };
796
- /* @__PURE__ */
797
- return jsx(FilterField, {
798
- label: "Date",
799
- onClearPill: () => onChange(void 0),
800
- onFocus: () => onOpen(!0),
801
- onOpen: onOpen,
802
- onQuery: () => {},
803
- open: open,
804
- pills: date === void 0 ? [] : [ {
805
- key: "date",
806
- label: dateLabel(date)
807
- } ],
808
- placeholder: "Any time",
809
- query: "",
810
- children: /* @__PURE__ */ jsxs(Stack, {
811
- space: 1,
812
- children: [
813
- /* @__PURE__ */ jsx(OptionRow, {
814
- active: date === "past",
815
- onPick: () => pick("past"),
816
- children: /* @__PURE__ */ jsx(Text, {
817
- size: 1,
818
- children: "In the past"
819
- })
820
- }),
821
- /* @__PURE__ */ jsx(OptionRow, {
822
- active: date === "none",
823
- onPick: () => pick("none"),
824
- children: /* @__PURE__ */ jsx(Text, {
825
- size: 1,
826
- children: "No date"
827
- })
828
- }),
829
- /* @__PURE__ */ jsx(Box, {
830
- paddingTop: 1,
831
- paddingX: 2,
832
- children: /* @__PURE__ */ jsx(Text, {
833
- muted: !0,
834
- size: 1,
835
- children: "Before…"
843
+ date: filters.date,
844
+ onChange: next => {
845
+ const {date: _cleared, ...rest} = filters;
846
+ onChange(next === void 0 ? rest : {
847
+ ...rest,
848
+ date: next
849
+ });
850
+ }
851
+ }), hasActiveFilters(filters) ? /* @__PURE__ */ jsx(Flex, {
852
+ justify: "flex-end",
853
+ children: /* @__PURE__ */ jsx(Button, {
854
+ fontSize: 1,
855
+ mode: "ghost",
856
+ onClick: () => onChange({}),
857
+ padding: 2,
858
+ text: "Clear filters"
836
859
  })
837
- }),
838
- /* @__PURE__ */ jsx(DatePicker, {
839
- onSelect: next => pick({
840
- before: endOfDayIso(next)
841
- }),
842
- value: pickedDate
843
- }) ]
860
+ }) : null ]
844
861
  })
845
862
  });
846
863
  }
847
864
 
848
- const endOfDayIso = date => endOfDay(date).toISOString();
849
-
850
- function schemaTypeTitle(schema, type) {
851
- return schema.get(type)?.title;
852
- }
865
+ const statusRows = () => Object.keys(STATUS_LABEL).map(key => ({
866
+ key: key,
867
+ text: STATUS_LABEL[key]
868
+ }));
853
869
 
854
- function useDocumentChipLabels(documentIds, documents) {
855
- const schema = useSchema(), activeDocs = documents.filter(doc => documentIds.includes(doc.id)), {titles: titles, resolved: resolved} = useDocPreviewTitles(activeDocs), labels = /* @__PURE__ */ new Map;
856
- for (const doc of activeDocs) {
857
- if (!resolved.has(doc.id)) continue;
858
- const label = titles.get(doc.id) ?? schemaTypeTitle(schema, doc.type) ?? extractDocumentId(doc.id);
859
- labels.set(doc.id, label);
860
- }
861
- return labels;
870
+ function FieldLabel({text: text}) {
871
+ /* @__PURE__ */
872
+ return jsx(Text, {
873
+ muted: !0,
874
+ size: 1,
875
+ weight: "medium",
876
+ children: text
877
+ });
862
878
  }
863
879
 
864
- function ActiveFilterChips({filters: filters, options: options, onChange: onChange}) {
865
- const displays = useUserDisplays(filters.assigneeIds ?? []), {loading: membersLoading} = useProjectMembers(), documentTitles = useDocumentChipLabels(filters.documentIds ?? [], options.documents), chips = activeFilterChips({
866
- filters: filters,
867
- options: options,
868
- assigneeNames: membersLoading ? /* @__PURE__ */ new Map : new Map(displays.map(d => [ d.id, d.name ])),
869
- documentTitles: documentTitles
870
- });
871
- return chips.length === 0 ? null : /* @__PURE__ */ jsx(Flex, {
872
- align: "center",
880
+ function FilterField({label: label, placeholder: placeholder, pills: pills, onClearPill: onClearPill, onFocus: onFocus, onQuery: onQuery, query: query, open: open, onOpen: onOpen, children: children}) {
881
+ const rootRef = useRef(null), inputRef = useRef(null);
882
+ /* @__PURE__ */
883
+ return jsxs(Stack, {
873
884
  gap: 2,
874
- wrap: "wrap",
875
- children: chips.map(chip => /* @__PURE__ */ jsx(FilterPill, {
876
- label: chip.label,
877
- onClear: () => onChange(withoutChip(filters, chip))
878
- }, `${chip.key}:${chip.value ?? ""}`))
885
+ children: [
886
+ /* @__PURE__ */ jsx(FieldLabel, {
887
+ text: label
888
+ }),
889
+ /* @__PURE__ */ jsxs("div", {
890
+ ref: rootRef,
891
+ style: {
892
+ position: "relative"
893
+ },
894
+ children: [
895
+ /* @__PURE__ */ jsx(TagBox, {
896
+ inputRef: inputRef,
897
+ label: label,
898
+ onBlur: event => {
899
+ rootRef.current?.contains(event.relatedTarget) || onOpen(!1);
900
+ },
901
+ onClearPill: onClearPill,
902
+ onFocus: onFocus,
903
+ onQuery: onQuery,
904
+ pills: pills,
905
+ placeholder: placeholder,
906
+ query: query
907
+ }),
908
+ /* @__PURE__ */ jsx(FieldOverlay, {
909
+ open: open,
910
+ children: children
911
+ }) ]
912
+ }) ]
879
913
  });
880
914
  }
881
915
 
882
- const INSTANCE_CAP = 200, NEWEST_INSTANCES = {
883
- includeCompleted: !0,
884
- limit: INSTANCE_CAP
885
- };
886
-
887
- function useToolInstances() {
888
- const {engine: engine} = useWorkflowContext(), {instances: instances, loading: loading, invalid: invalid} = useWorkflowInstances({
889
- engine: engine,
890
- filter: NEWEST_INSTANCES
916
+ function TagField({label: label, placeholder: placeholder, rows: rows, activeKeys: activeKeys, onToggle: onToggle, pillLabel: pillLabel, open: open, onOpen: onOpen, emptyNote: emptyNote = "Nothing matches"}) {
917
+ const [query, setQuery] = useState(""), q = query.trim().toLowerCase(), hits = q ? rows.filter(row => row.text.toLowerCase().includes(q)) : rows, shown = shownHits(hits, HITS_CAP);
918
+ /* @__PURE__ */
919
+ return jsx(FilterField, {
920
+ label: label,
921
+ onClearPill: onToggle,
922
+ onFocus: () => {
923
+ setQuery(""), onOpen(!0);
924
+ },
925
+ onOpen: onOpen,
926
+ onQuery: setQuery,
927
+ open: open,
928
+ pills: activeKeys.map(key => ({
929
+ key: key,
930
+ label: pillLabel(key)
931
+ })),
932
+ placeholder: placeholder,
933
+ query: open ? query : "",
934
+ children: /* @__PURE__ */ jsx(OptionsList, {
935
+ activeKeys: activeKeys,
936
+ emptyNote: emptyNote,
937
+ hits: hits,
938
+ onToggle: onToggle,
939
+ shown: shown
940
+ })
891
941
  });
892
- return useMemo(() => {
893
- const rows = instances ?? [];
894
- return {
895
- inFlight: rows.filter(instance => terminalState(instance) === "in-flight"),
896
- settled: rows.filter(instance => terminalState(instance) !== "in-flight"),
897
- truncated: rows.length === INSTANCE_CAP,
898
- loading: loading,
899
- invalid: invalid
900
- };
901
- }, [ instances, loading, invalid ]);
902
942
  }
903
943
 
904
- function toFilterRows(instances) {
905
- return instances.map(instance => ({
906
- instance: instance,
907
- workflowTitle: instanceTitle(instance, definitionSnapshotOf(instance)),
908
- documents: documentRefsOf(instance)
909
- }));
944
+ function FieldOverlay({open: open, children: children}) {
945
+ return open ? /* @__PURE__ */ jsx(Card, {
946
+ radius: 2,
947
+ shadow: 3,
948
+ style: {
949
+ insetInline: 0,
950
+ position: "absolute",
951
+ top: "calc(100% + 4px)",
952
+ zIndex: 2
953
+ },
954
+ children: children
955
+ }) : null;
910
956
  }
911
957
 
912
- function cutInstances(args) {
913
- const {rows: rows, filters: filters, taskArmIds: taskArmIds} = args, kept = applyInstanceFilters({
914
- rows: rows,
915
- filters: filters
916
- }).filter(instance => instanceStatusMatches(instance, filters.statuses));
917
- return taskArmIds === null ? kept : kept.filter(instance => taskArmIds.has(instance._id));
958
+ function useTagInputStyle() {
959
+ const {font: font} = useTheme_v2(), textSize = font.text.sizes[1];
960
+ if (!textSize) throw new Error("useTagInputStyle: theme is missing text size 1");
961
+ return {
962
+ background: "transparent",
963
+ border: "none",
964
+ color: "var(--card-fg-color)",
965
+ fontFamily: font.text.family,
966
+ fontSize: textSize.fontSize,
967
+ outline: "none",
968
+ padding: "4px 6px",
969
+ width: "100%"
970
+ };
918
971
  }
919
972
 
920
- function WorkflowsDashboard({instances: instances, onOpenInstance: onOpenInstance}) {
921
- const {inFlight: inFlight, settled: settled, truncated: truncated, loading: loading} = instances, [filters, setFilters] = useState({}), identity = useAssignmentIdentity(), inFlightRows = useMemo(() => toFilterRows(inFlight), [ inFlight ]), settledRows = useMemo(() => toFilterRows(settled), [ settled ]), options = useMemo(() => instanceFilterOptions([ ...inFlightRows, ...settledRows ]), [ inFlightRows, settledRows ]), taskArmIds = useMemo(() => instanceIdsMatchingTaskArms({
922
- instances: [ ...inFlight, ...settled ],
923
- identity: identity,
924
- filters: filters,
925
- now: /* @__PURE__ */ new Date
926
- }), [ inFlight, settled, identity, filters ]), visibleInFlight = useMemo(() => cutInstances({
927
- rows: inFlightRows,
928
- filters: filters,
929
- taskArmIds: taskArmIds
930
- }), [ inFlightRows, filters, taskArmIds ]), visibleSettled = useMemo(() => cutInstances({
931
- rows: settledRows,
932
- filters: filters,
933
- taskArmIds: taskArmIds
934
- }), [ settledRows, filters, taskArmIds ]);
935
- return loading ? /* @__PURE__ */ jsx(LoadingRow, {
936
- label: "Loading workflows…"
937
- }) : inFlight.length === 0 && settled.length === 0 ? /* @__PURE__ */ jsx(MutedNote, {
938
- text: "No workflows yet — start one with “New workflow”"
939
- }) : /* @__PURE__ */ jsxs(Stack, {
940
- gap: 2,
941
- children: [
942
- /* @__PURE__ */ jsxs(Flex, {
973
+ function TagBox({pills: pills, onClearPill: onClearPill, query: query, onQuery: onQuery, placeholder: placeholder, label: label, onFocus: onFocus, onBlur: onBlur, inputRef: inputRef}) {
974
+ const inputStyle = useTagInputStyle();
975
+ /* @__PURE__ */
976
+ return jsx(Card, {
977
+ border: !0,
978
+ onClick: () => inputRef.current?.focus(),
979
+ padding: 1,
980
+ radius: 2,
981
+ style: {
982
+ cursor: "text"
983
+ },
984
+ children: /* @__PURE__ */ jsxs(Flex, {
943
985
  align: "center",
944
- gap: 2,
945
- justify: "flex-end",
946
- children: [
947
- /* @__PURE__ */ jsx(ActiveFilterChips, {
948
- filters: filters,
949
- onChange: setFilters,
950
- options: options
951
- }),
952
- /* @__PURE__ */ jsx(TaskFilterMenu, {
953
- filters: filters,
954
- onChange: setFilters,
955
- options: options
986
+ gap: 1,
987
+ wrap: "wrap",
988
+ children: [ pills.map(pill => /* @__PURE__ */ jsx(FilterPill, {
989
+ label: pill.label,
990
+ onClear: () => onClearPill(pill.key)
991
+ }, pill.key)),
992
+ /* @__PURE__ */ jsx(Box, {
993
+ flex: 1,
994
+ style: {
995
+ minWidth: 90
996
+ },
997
+ children: /* @__PURE__ */ jsx("input", {
998
+ "aria-label": label,
999
+ onBlur: onBlur,
1000
+ onChange: event => onQuery(event.currentTarget.value),
1001
+ onFocus: onFocus,
1002
+ placeholder: pills.length === 0 ? placeholder : void 0,
1003
+ ref: inputRef,
1004
+ style: inputStyle,
1005
+ value: query
1006
+ })
956
1007
  }) ]
957
- }), visibleInFlight.length === 0 ? /* @__PURE__ */ jsx(MutedNote, {
958
- text: "No running workflows match"
959
- }) : /* @__PURE__ */ jsx(InstanceRows, {
960
- instances: visibleInFlight,
961
- onOpenInstance: onOpenInstance
962
- }),
963
- /* @__PURE__ */ jsx(SettledRows, {
964
- defaultShown: filters.statuses?.includes("completed") ?? !1,
965
- instances: visibleSettled,
966
- onOpenInstance: onOpenInstance
967
- }), truncated ? /* @__PURE__ */ jsx(MutedNote, {
968
- text: `Showing the latest ${INSTANCE_CAP} workflows.`
969
- }) : null ]
1008
+ })
970
1009
  });
971
1010
  }
972
1011
 
973
- function InstanceRows({instances: instances, onOpenInstance: onOpenInstance}) {
1012
+ function OptionsList({hits: hits, shown: shown, activeKeys: activeKeys, onToggle: onToggle, emptyNote: emptyNote}) {
974
1013
  /* @__PURE__ */
975
- return jsx(Fragment, {
976
- children: instances.map(instance => /* @__PURE__ */ jsx(InstanceRow, {
977
- instance: instance,
978
- onOpen: () => onOpenInstance(instance)
979
- }, instance._id))
980
- });
981
- }
982
-
983
- function SettledRows({instances: instances, onOpenInstance: onOpenInstance, defaultShown: defaultShown}) {
984
- const [toggled, setToggled] = useState(void 0), shown = toggled ?? defaultShown;
985
- return instances.length === 0 ? null : /* @__PURE__ */ jsxs(Stack, {
986
- gap: 2,
987
- marginTop: 3,
988
- children: [
989
- /* @__PURE__ */ jsx(Flex, {
990
- children: /* @__PURE__ */ jsx(Button, {
991
- fontSize: 1,
992
- icon: shown ? ChevronDownIcon : ChevronRightIcon,
993
- mode: "bleed",
994
- onClick: () => setToggled(!shown),
995
- padding: 2,
996
- text: `${shown ? "Hide" : "Show"} ${instances.length} finished`
1014
+ return jsxs(Stack, {
1015
+ space: 1,
1016
+ children: [ hits.length === 0 ? /* @__PURE__ */ jsx(Box, {
1017
+ padding: 2,
1018
+ children: /* @__PURE__ */ jsx(MutedNote, {
1019
+ text: emptyNote
997
1020
  })
998
- }), shown ? /* @__PURE__ */ jsx(InstanceRows, {
999
- instances: instances,
1000
- onOpenInstance: onOpenInstance
1001
- }) : null ]
1002
- });
1003
- }
1004
-
1005
- function InstanceRow({instance: instance, onOpen: onOpen}) {
1006
- const definition = useMemo(() => definitionSnapshotOf(instance), [ instance ]);
1007
- /* @__PURE__ */
1008
- return jsxs(Flex, {
1009
- align: "center",
1010
- gap: 2,
1011
- children: [
1012
- /* @__PURE__ */ jsx(Card, {
1013
- as: "button",
1014
- flex: 1,
1015
- onClick: onOpen,
1016
- padding: 3,
1017
- paddingLeft: 4,
1018
- radius: 2,
1019
- style: {
1020
- cursor: "pointer",
1021
- textAlign: "left"
1022
- },
1023
- children: /* @__PURE__ */ jsxs(Flex, {
1024
- align: "center",
1025
- gap: 3,
1026
- children: [
1027
- /* @__PURE__ */ jsx(Text, {
1028
- size: 1,
1029
- weight: "medium",
1030
- children: instanceTitle(instance, definition)
1031
- }),
1032
- /* @__PURE__ */ jsxs(Text, {
1033
- muted: !0,
1034
- size: 1,
1035
- children: [ "Started ", formatDate(instance.startedAt) ]
1036
- }),
1037
- /* @__PURE__ */ jsx(Flex, {
1038
- flex: 1
1039
- }),
1040
- /* @__PURE__ */ jsx(AbortedBadge, {
1041
- instance: instance
1042
- }) ]
1021
+ }) : null, shown.map(row => /* @__PURE__ */ jsx(OptionRow, {
1022
+ active: activeKeys.includes(row.key),
1023
+ onPick: () => onToggle(row.key),
1024
+ children: row.node ?? /* @__PURE__ */ jsx(Text, {
1025
+ size: 1,
1026
+ children: row.text
1043
1027
  })
1044
- }),
1045
- /* @__PURE__ */ jsx(DocRefChips, {
1046
- instance: instance
1047
- }) ]
1028
+ }, row.key)), hits.length > shown.length ? /* @__PURE__ */ jsx(Box, {
1029
+ padding: 2,
1030
+ children: /* @__PURE__ */ jsx(MutedNote, {
1031
+ text: `…and ${hits.length - shown.length} more — keep typing to narrow`
1032
+ })
1033
+ }) : null ]
1048
1034
  });
1049
1035
  }
1050
1036
 
1051
- function SpinnerSlot({busy: busy}) {
1037
+ function FilterPill({label: label, onClear: onClear}) {
1052
1038
  /* @__PURE__ */
1053
- return jsx(Flex, {
1054
- align: "center",
1055
- flex: "none",
1056
- justify: "center",
1039
+ return jsx(Badge, {
1040
+ fontSize: 1,
1041
+ mode: "outline",
1042
+ radius: "full",
1043
+ children: /* @__PURE__ */ jsxs(Flex, {
1044
+ align: "center",
1045
+ gap: 1,
1046
+ children: [ label,
1047
+ /* @__PURE__ */ jsx(Button, {
1048
+ "aria-label": `Clear ${label}`,
1049
+ fontSize: 0,
1050
+ icon: CloseIcon,
1051
+ mode: "bleed",
1052
+ onClick: onClear,
1053
+ onMouseDown: event => event.preventDefault(),
1054
+ padding: 1
1055
+ }) ]
1056
+ })
1057
+ });
1058
+ }
1059
+
1060
+ function OptionRow({active: active, onPick: onPick, children: children}) {
1061
+ /* @__PURE__ */
1062
+ return jsx(Card, {
1063
+ as: "button",
1064
+ onMouseDown: event => {
1065
+ event.preventDefault(), onPick();
1066
+ },
1067
+ padding: 2,
1068
+ radius: 2,
1057
1069
  style: {
1058
- width: 19,
1059
- height: 19
1070
+ cursor: "pointer",
1071
+ textAlign: "left"
1060
1072
  },
1061
- children: busy ? /* @__PURE__ */ jsx(Spinner, {
1062
- muted: !0
1063
- }) : null
1073
+ tone: active ? "primary" : "default",
1074
+ children: /* @__PURE__ */ jsxs(Flex, {
1075
+ align: "center",
1076
+ gap: 2,
1077
+ children: [
1078
+ /* @__PURE__ */ jsx(Box, {
1079
+ flex: 1,
1080
+ children: children
1081
+ }), active ? /* @__PURE__ */ jsx(Text, {
1082
+ size: 1,
1083
+ children: /* @__PURE__ */ jsx(CheckmarkIcon, {})
1084
+ }) : null ]
1085
+ })
1064
1086
  });
1065
1087
  }
1066
1088
 
1067
- function InstanceTaskLists({entry: entry, onOpenActivity: onOpenActivity}) {
1068
- return instanceNotice(entry) || /* @__PURE__ */ jsx(StaleLock, {
1069
- stale: isEvaluationStale(entry),
1070
- children: /* @__PURE__ */ jsxs(Stack, {
1089
+ function AssigneeField({activeIds: activeIds, onToggle: onToggle, open: open, onOpen: onOpen}) {
1090
+ const {members: members, loading: loading} = useProjectMembers(), displays = useUserDisplays(activeIds), names = new Map(displays.map(d => [ d.id, d.name ])), rows = members.map(member => ({
1091
+ key: member.id,
1092
+ text: `${member.displayName} ${member.email ?? ""}`.trim(),
1093
+ node: /* @__PURE__ */ jsxs(Flex, {
1094
+ align: "center",
1095
+ gap: 2,
1071
1096
  children: [
1072
- /* @__PURE__ */ jsx(ActivitiesList, {
1073
- entry: entry,
1074
- onOpenActivity: onOpenActivity
1097
+ /* @__PURE__ */ jsx(MemberAvatar, {
1098
+ imageUrl: member.imageUrl,
1099
+ name: member.displayName
1075
1100
  }),
1076
- /* @__PURE__ */ jsx(TodoItemsList, {
1077
- entry: entry
1101
+ /* @__PURE__ */ jsx(Text, {
1102
+ size: 1,
1103
+ children: member.displayName
1078
1104
  }) ]
1079
1105
  })
1106
+ }));
1107
+ /* @__PURE__ */
1108
+ return jsx(TagField, {
1109
+ activeKeys: activeIds,
1110
+ label: "Assignee",
1111
+ onOpen: onOpen,
1112
+ onToggle: onToggle,
1113
+ open: open,
1114
+ pillLabel: id => loading ? PENDING_LABEL : names.get(id) ?? id,
1115
+ placeholder: "Anyone",
1116
+ rows: rows
1080
1117
  });
1081
1118
  }
1082
1119
 
1083
- function WorkflowInstanceDetail({instanceId: instanceId, onBack: onBack}) {
1084
- const entry = useWorkflowInstanceEntry(instanceId), [graceOver, setGraceOver] = useState(!1);
1085
- return useEffect(() => {
1086
- const timer = setTimeout(() => setGraceOver(!0), 2500);
1087
- return () => clearTimeout(timer);
1088
- }, [ instanceId ]), entry?.invalid ? /* @__PURE__ */ jsx(Box, {
1089
- padding: 4,
1090
- children: /* @__PURE__ */ jsx(InvalidDocNotice, {
1091
- invalid: entry.invalid
1092
- })
1093
- }) : entry ? /* @__PURE__ */ jsx(InstanceDetailPanel, {
1094
- entry: entry,
1095
- onBack: onBack
1096
- }) : /* @__PURE__ */ jsx(Box, {
1097
- padding: 4,
1098
- children: graceOver ? /* @__PURE__ */ jsx(NotFound, {
1099
- id: instanceId,
1100
- onBack: onBack
1101
- }) : /* @__PURE__ */ jsx(LoadingRow, {
1102
- label: "Loading workflow…"
1120
+ function DocumentField({documents: documents, activeIds: activeIds, onToggle: onToggle, open: open, onOpen: onOpen}) {
1121
+ const labels = useDocumentChipLabels(activeIds, documents), {titles: titles} = useDocPreviewTitles(documents);
1122
+ if (documents.length === 0) return null;
1123
+ const rows = documents.map(doc => ({
1124
+ key: doc.id,
1125
+ text: `${titles.get(doc.id) ?? ""} ${doc.type} ${extractDocumentId(doc.id)}`,
1126
+ node: /* @__PURE__ */ jsx(DocRefFace, {
1127
+ gdr: doc
1103
1128
  })
1104
- });
1105
- }
1106
-
1107
- function NotFound({id: id, onBack: onBack}) {
1129
+ }));
1108
1130
  /* @__PURE__ */
1109
- return jsxs(Stack, {
1110
- gap: 4,
1111
- children: [
1112
- /* @__PURE__ */ jsxs(Text, {
1113
- muted: !0,
1114
- size: 1,
1115
- children: [ "No workflow with id “", id, "” — it may have been deleted." ]
1116
- }),
1117
- /* @__PURE__ */ jsx(Flex, {
1118
- children: /* @__PURE__ */ jsx(Button, {
1119
- fontSize: 1,
1120
- icon: ArrowLeftIcon,
1121
- mode: "ghost",
1122
- onClick: onBack,
1123
- padding: 2,
1124
- text: "Back to workflows"
1125
- })
1126
- }) ]
1127
- });
1128
- }
1129
-
1130
- function InstanceDetailPanel({entry: entry, onBack: onBack}) {
1131
- const definition = useDefinition(entry), [openActivity, setOpenActivity] = useState(null), {instance: instance} = entry;
1132
- return useEffect(() => {
1133
- openActivity && openActivityGone({
1134
- entry: entry,
1135
- target: openActivity
1136
- }) && setOpenActivity(null);
1137
- }, [ entry, openActivity ]), /* @__PURE__ */ jsxs(Flex, {
1138
- direction: "column",
1139
- height: "fill",
1140
- overflow: "hidden",
1141
- children: [
1142
- /* @__PURE__ */ jsx(DetailHeader, {
1143
- definition: definition,
1144
- entry: entry,
1145
- onBack: onBack
1146
- }),
1147
- /* @__PURE__ */ jsx(Box, {
1148
- flex: 1,
1149
- overflow: "auto",
1150
- paddingX: 4,
1151
- children: /* @__PURE__ */ jsxs(Stack, {
1152
- gap: 4,
1153
- paddingBottom: 5,
1154
- children: [
1155
- /* @__PURE__ */ jsx(StudioDocumentsSection, {
1156
- instance: instance
1157
- }),
1158
- /* @__PURE__ */ jsx(StageDiagramSection, {
1159
- definition: definition,
1160
- entry: entry
1161
- }),
1162
- /* @__PURE__ */ jsx(GroupHeading, {
1163
- title: "All tasks"
1164
- }),
1165
- /* @__PURE__ */ jsx(InstanceTaskLists, {
1166
- entry: entry,
1167
- onOpenActivity: name => setOpenActivity({
1168
- stage: instance.currentStage,
1169
- activityName: name
1170
- })
1171
- }) ]
1172
- })
1173
- }), openActivity ? /* @__PURE__ */ jsx(ActivityDetailDialog, {
1174
- activityName: openActivity.activityName,
1175
- breadcrumb: instanceBreadcrumb(instance, definition),
1176
- definition: definition,
1177
- entry: entry,
1178
- onClose: () => setOpenActivity(null)
1179
- }) : null ]
1131
+ return jsx(TagField, {
1132
+ activeKeys: activeIds,
1133
+ emptyNote: "No documents match",
1134
+ label: "Document",
1135
+ onOpen: onOpen,
1136
+ onToggle: onToggle,
1137
+ open: open,
1138
+ pillLabel: id => labels.get(id) ?? PENDING_LABEL,
1139
+ placeholder: "Any document",
1140
+ rows: rows
1180
1141
  });
1181
1142
  }
1182
1143
 
1183
- function DetailHeader({entry: entry, definition: definition, onBack: onBack}) {
1184
- const {instance: instance, ready: ready} = entry;
1144
+ function DateField({date: date, onChange: onChange, open: open, onOpen: onOpen}) {
1145
+ const pickedDate = typeof date == "object" ? new Date(date.before) : void 0, pick = next => {
1146
+ onChange(next), onOpen(!1);
1147
+ };
1185
1148
  /* @__PURE__ */
1186
- return jsx(Card, {
1187
- flex: "none",
1188
- paddingX: 4,
1189
- paddingY: 3,
1190
- children: /* @__PURE__ */ jsxs(Flex, {
1191
- align: "center",
1192
- gap: 3,
1193
- wrap: "wrap",
1149
+ return jsx(FilterField, {
1150
+ label: "Date",
1151
+ onClearPill: () => onChange(void 0),
1152
+ onFocus: () => onOpen(!0),
1153
+ onOpen: onOpen,
1154
+ onQuery: () => {},
1155
+ open: open,
1156
+ pills: date === void 0 ? [] : [ {
1157
+ key: "date",
1158
+ label: dateLabel(date)
1159
+ } ],
1160
+ placeholder: "Any time",
1161
+ query: "",
1162
+ children: /* @__PURE__ */ jsxs(Stack, {
1163
+ space: 1,
1194
1164
  children: [
1195
- /* @__PURE__ */ jsx(HoverHint, {
1196
- text: "Back to workflows",
1197
- children: /* @__PURE__ */ jsx(Button, {
1198
- "aria-label": "Back to workflows",
1199
- fontSize: 1,
1200
- icon: ArrowLeftIcon,
1201
- mode: "bleed",
1202
- onClick: onBack,
1203
- padding: 2
1165
+ /* @__PURE__ */ jsx(OptionRow, {
1166
+ active: date === "past",
1167
+ onPick: () => pick("past"),
1168
+ children: /* @__PURE__ */ jsx(Text, {
1169
+ size: 1,
1170
+ children: "In the past"
1204
1171
  })
1205
1172
  }),
1206
- /* @__PURE__ */ jsxs(Stack, {
1207
- gap: 2,
1208
- children: [
1209
- /* @__PURE__ */ jsx(Text, {
1173
+ /* @__PURE__ */ jsx(OptionRow, {
1174
+ active: date === "none",
1175
+ onPick: () => pick("none"),
1176
+ children: /* @__PURE__ */ jsx(Text, {
1210
1177
  size: 1,
1211
- weight: "semibold",
1212
- children: instanceTitle(instance, definition)
1213
- }),
1214
- /* @__PURE__ */ jsxs(Text, {
1178
+ children: "No date"
1179
+ })
1180
+ }),
1181
+ /* @__PURE__ */ jsx(Box, {
1182
+ paddingTop: 1,
1183
+ paddingX: 2,
1184
+ children: /* @__PURE__ */ jsx(Text, {
1215
1185
  muted: !0,
1216
1186
  size: 1,
1217
- children: [ "Started ", formatDate(instance.startedAt) ]
1218
- }) ]
1219
- }), terminalState(instance) === "in-flight" ? /* @__PURE__ */ jsx(StageChip, {
1220
- title: stageTitle(definition, instance.currentStage)
1221
- }) : /* @__PURE__ */ jsx(AbortedBadge, {
1222
- instance: instance
1187
+ children: "Before…"
1188
+ })
1223
1189
  }),
1224
- /* @__PURE__ */ jsx(SpinnerSlot, {
1225
- busy: !ready || isEvaluationStale(entry)
1190
+ /* @__PURE__ */ jsx(DatePicker, {
1191
+ onSelect: next => pick({
1192
+ before: endOfDayIso(next)
1193
+ }),
1194
+ value: pickedDate
1226
1195
  }) ]
1227
1196
  })
1228
1197
  });
1229
1198
  }
1230
1199
 
1231
- function StudioDocumentsSection({instance: instance}) {
1232
- const refs = documentRefsOf(instance);
1233
- return refs.length === 0 ? null : /* @__PURE__ */ jsxs(Stack, {
1234
- gap: 3,
1235
- marginTop: 3,
1236
- children: [
1237
- /* @__PURE__ */ jsx(Text, {
1238
- muted: !0,
1239
- size: 1,
1240
- weight: "medium",
1241
- children: refs.length === 1 ? "Studio document" : "Studio documents"
1242
- }),
1243
- /* @__PURE__ */ jsx(Stack, {
1244
- gap: 2,
1245
- children: refs.map(ref => /* @__PURE__ */ jsx(DocPreviewLink, {
1246
- gdr: ref
1247
- }, ref.id))
1248
- }) ]
1249
- });
1250
- }
1200
+ const endOfDayIso = date => endOfDay(date).toISOString();
1251
1201
 
1252
- function StageDiagramSection({entry: entry, definition: definition}) {
1253
- const {instance: instance} = entry;
1254
- return definition ?
1255
- /* @__PURE__ */ jsx(WorkflowDiagram, {
1256
- currentStage: terminalState(instance) === "in-flight" ? instance.currentStage : void 0,
1257
- definition: definition,
1258
- evaluation: entry.evaluation,
1259
- explain: !0,
1260
- guardCount: entry.guards?.length,
1261
- height: 340,
1262
- history: instance.history
1263
- }, instance._id) : /* @__PURE__ */ jsx(Card, {
1264
- border: !0,
1265
- marginTop: 3,
1266
- padding: 3,
1267
- radius: 2,
1268
- tone: "caution",
1269
- children: /* @__PURE__ */ jsx(Text, {
1270
- muted: !0,
1271
- size: 1,
1272
- children: "Couldn’t load this workflow’s full details — the stage diagram is unavailable, and technical names show instead of titles."
1273
- })
1274
- });
1202
+ function schemaTypeTitle(schema, type) {
1203
+ return schema.get(type)?.title;
1275
1204
  }
1276
1205
 
1277
- function useStartableDefinitions() {
1278
- const {engine: engine} = useWorkflowContext(), [definitions, setDefinitions] = useState(void 0), [error, setError] = useState(void 0);
1279
- return useEffect(() => {
1280
- let cancelled = !1;
1281
- return (async () => {
1282
- const rows = await readDeployedDefinitions(engine), startable = latestDeployedDefinitions(rows).filter(row => isStartableDefinition(row)).map(row => ({
1283
- name: row.name,
1284
- title: row.title ?? row.name,
1285
- description: row.description
1286
- }));
1287
- cancelled || (setDefinitions(startable), setError(void 0));
1288
- })().catch(err => {
1289
- cancelled || setError(describeError(err));
1290
- }), () => {
1291
- cancelled = !0;
1292
- };
1293
- }, [ engine ]), {
1294
- definitions: definitions,
1295
- error: error
1296
- };
1206
+ function useDocumentChipLabels(documentIds, documents) {
1207
+ const schema = useSchema(), activeDocs = documents.filter(doc => documentIds.includes(doc.id)), {titles: titles, resolved: resolved} = useDocPreviewTitles(activeDocs), labels = /* @__PURE__ */ new Map;
1208
+ for (const doc of activeDocs) {
1209
+ if (!resolved.has(doc.id)) continue;
1210
+ const label = titles.get(doc.id) ?? schemaTypeTitle(schema, doc.type) ?? extractDocumentId(doc.id);
1211
+ labels.set(doc.id, label);
1212
+ }
1213
+ return labels;
1297
1214
  }
1298
1215
 
1299
- function NewWorkflowButton() {
1300
- const {openStartDialog: openStartDialog} = useWorkflowContext(), [open, setOpen] = useState(!1);
1301
- /* @__PURE__ */
1302
- return jsx(DismissablePopover, {
1303
- content: open ? /* @__PURE__ */ jsx(DefinitionPickerPanel, {
1304
- onPick: definition => {
1305
- setOpen(!1), openStartDialog({
1306
- definition: definition.name,
1307
- label: definition.title
1308
- });
1309
- }
1310
- }) : null,
1311
- onDismiss: () => setOpen(!1),
1312
- open: open,
1313
- children: /* @__PURE__ */ jsx(Button, {
1314
- fontSize: 1,
1315
- icon: AddIcon,
1316
- onClick: () => setOpen(v => !v),
1317
- padding: 2,
1318
- selected: open,
1319
- text: "New workflow",
1320
- tone: "primary"
1321
- })
1216
+ function ActiveFilterChips({filters: filters, options: options, onChange: onChange}) {
1217
+ const displays = useUserDisplays(filters.assigneeIds ?? []), {loading: membersLoading} = useProjectMembers(), documentTitles = useDocumentChipLabels(filters.documentIds ?? [], options.documents), chips = activeFilterChips({
1218
+ filters: filters,
1219
+ options: options,
1220
+ assigneeNames: membersLoading ? /* @__PURE__ */ new Map : new Map(displays.map(d => [ d.id, d.name ])),
1221
+ documentTitles: documentTitles
1322
1222
  });
1323
- }
1324
-
1325
- function DefinitionPickerPanel({onPick: onPick}) {
1326
- const {definitions: definitions, error: error} = useStartableDefinitions(), [query, setQuery] = useState(""), visible = useMemo(() => {
1327
- const q = query.trim().toLowerCase();
1328
- return definitions ? q ? definitions.filter(d => `${d.title} ${d.name}`.toLowerCase().includes(q)) : definitions : [];
1329
- }, [ definitions, query ]);
1330
- /* @__PURE__ */
1331
- return jsx(Box, {
1332
- padding: 2,
1333
- style: {
1334
- width: 340
1335
- },
1336
- children: /* @__PURE__ */ jsxs(Stack, {
1337
- gap: 2,
1338
- children: [
1339
- /* @__PURE__ */ jsx(TextInput, {
1340
- autoFocus: !0,
1341
- fontSize: 1,
1342
- icon: SearchIcon,
1343
- onChange: event => setQuery(event.currentTarget.value),
1344
- placeholder: "Filter workflows…",
1345
- value: query
1346
- }), error ? /* @__PURE__ */ jsx(MutedNote, {
1347
- text: `Could not load the deployed workflows: ${error}`
1348
- }) : null, !definitions && !error ? /* @__PURE__ */ jsx(LoadingRow, {
1349
- label: "Loading workflows…"
1350
- }) : null, definitions && definitions.length === 0 ? /* @__PURE__ */ jsx(MutedNote, {
1351
- text: "No startable workflows are deployed"
1352
- }) : null, definitions && definitions.length > 0 && visible.length === 0 ? /* @__PURE__ */ jsx(MutedNote, {
1353
- text: "No workflows match"
1354
- }) : null,
1355
- /* @__PURE__ */ jsx(Stack, {
1356
- gap: 1,
1357
- style: {
1358
- maxHeight: 320,
1359
- overflowY: "auto"
1360
- },
1361
- children: visible.map(definition => /* @__PURE__ */ jsx(Card, {
1362
- as: "button",
1363
- onClick: () => onPick(definition),
1364
- padding: 3,
1365
- radius: 2,
1366
- style: {
1367
- cursor: "pointer",
1368
- textAlign: "left"
1369
- },
1370
- children: /* @__PURE__ */ jsxs(Stack, {
1371
- gap: 2,
1372
- children: [
1373
- /* @__PURE__ */ jsx(Text, {
1374
- size: 1,
1375
- weight: "medium",
1376
- children: definition.title
1377
- }), definition.description ? /* @__PURE__ */ jsx(Text, {
1378
- muted: !0,
1379
- size: 1,
1380
- textOverflow: "ellipsis",
1381
- children: definition.description
1382
- }) : null ]
1383
- })
1384
- }, definition.name))
1385
- }) ]
1386
- })
1223
+ return chips.length === 0 ? null : /* @__PURE__ */ jsx(Flex, {
1224
+ align: "center",
1225
+ gap: 2,
1226
+ wrap: "wrap",
1227
+ children: chips.map(chip => /* @__PURE__ */ jsx(FilterPill, {
1228
+ label: chip.label,
1229
+ onClear: () => onChange(withoutChip(filters, chip))
1230
+ }, `${chip.key}:${chip.value ?? ""}`))
1387
1231
  });
1388
1232
  }
1389
1233
 
1390
- function ToolActivityDialog({target: target, onClose: onClose}) {
1391
- const entry = useWorkflowInstanceEntry(target.instanceId), definition = useDefinition(entry), resolvedOnce = useRef(!1);
1392
- return entry && (resolvedOnce.current = !0), useEffect(() => {
1393
- resolvedOnce.current && openActivityGone({
1394
- entry: entry,
1395
- target: target
1396
- }) && onClose();
1397
- }, [ entry, target, onClose ]), entry?.invalid ? /* @__PURE__ */ jsx(Dialog, {
1398
- header: "Workflow unavailable",
1399
- id: "workflow-activity-detail",
1400
- onClose: onClose,
1401
- width: 1,
1402
- children: /* @__PURE__ */ jsx(Box, {
1403
- padding: 4,
1404
- children: /* @__PURE__ */ jsx(InvalidDocNotice, {
1405
- invalid: entry.invalid
1406
- })
1407
- })
1408
- }) : entry?.evaluation ? /* @__PURE__ */ jsx(ActivityDetailDialog, {
1409
- activityName: target.activityName,
1410
- breadcrumb: instanceBreadcrumb(entry.instance, definition),
1411
- definition: definition,
1412
- document: documentRefsOf(entry.instance)[0],
1413
- entry: entry,
1414
- onClose: onClose
1415
- }) : /* @__PURE__ */ jsx(Dialog, {
1416
- header: "Loading activity…",
1417
- id: "workflow-activity-detail",
1418
- onClose: onClose,
1419
- width: 1,
1420
- children: /* @__PURE__ */ jsx(Box, {
1421
- padding: 4,
1422
- children: /* @__PURE__ */ jsx(LoadingRow, {
1423
- label: "Fetching the live workflow state…"
1424
- })
1425
- })
1426
- });
1234
+ function FilterRowPortal({filters: filters, onChange: onChange, options: options, slot: slot}) {
1235
+ return slot ? createPortal(
1236
+ /* @__PURE__ */ jsxs(Flex, {
1237
+ align: "center",
1238
+ gap: 2,
1239
+ justify: "flex-end",
1240
+ children: [
1241
+ /* @__PURE__ */ jsx(ActiveFilterChips, {
1242
+ filters: filters,
1243
+ onChange: onChange,
1244
+ options: options
1245
+ }),
1246
+ /* @__PURE__ */ jsx(TaskFilterMenu, {
1247
+ filters: filters,
1248
+ onChange: onChange,
1249
+ options: options
1250
+ }) ]
1251
+ }), slot) : null;
1427
1252
  }
1428
1253
 
1429
- function TaskGroupList({groups: groups, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow}) {
1430
- return groups.withDocuments.length === 0 && groups.withoutDocuments.length === 0 ? /* @__PURE__ */ jsx(MutedNote, {
1431
- text: "No tasks match — adjust the filters, or start a workflow"
1432
- }) : /* @__PURE__ */ jsxs(Stack, {
1433
- gap: 2,
1434
- children: [ groups.withDocuments.map(group => /* @__PURE__ */ jsx(DocumentTaskGroup, {
1435
- group: group,
1436
- onOpenTask: onOpenTask,
1437
- onOpenWorkflow: onOpenWorkflow
1438
- }, group.key)), groups.withoutDocuments.length > 0 ? /* @__PURE__ */ jsx(CollapsibleGroup, {
1439
- header: /* @__PURE__ */ jsx(GroupTitle, {
1440
- text: "Workflows without Studio documents"
1441
- }),
1442
- children: /* @__PURE__ */ jsx(Stack, {
1443
- gap: 2,
1444
- paddingLeft: 2,
1445
- children: groups.withoutDocuments.map(group => /* @__PURE__ */ jsx(CollapsibleGroup, {
1446
- header: /* @__PURE__ */ jsxs(Fragment, {
1447
- children: [
1448
- /* @__PURE__ */ jsx(Text, {
1449
- muted: !0,
1450
- size: 1,
1451
- children: /* @__PURE__ */ jsx(StageGlyph, {})
1452
- }),
1453
- /* @__PURE__ */ jsx(WorkflowBadgeTitle, {
1454
- group: group
1455
- }),
1456
- /* @__PURE__ */ jsx(Box, {
1457
- flex: 1
1458
- }),
1459
- /* @__PURE__ */ jsx(GroupMenu, {
1460
- group: group,
1461
- onOpenWorkflow: onOpenWorkflow
1462
- }) ]
1463
- }),
1464
- children: /* @__PURE__ */ jsx(TaskRows, {
1465
- onOpenTask: onOpenTask,
1466
- rows: group.rows
1467
- })
1468
- }, group.key))
1254
+ function ActivityDateControl({instanceId: instanceId, state: state, dates: dates}) {
1255
+ const {editFieldFor: editFieldFor} = useWorkflowContext(), {save: save} = useSaveField(), [open, setOpen] = useState(!1), raw = dateControlValue({
1256
+ state: state,
1257
+ dates: dates
1258
+ }), display = raw === void 0 ? null : /* @__PURE__ */ jsx(Text, {
1259
+ muted: !0,
1260
+ size: 1,
1261
+ children: formatDate(raw)
1262
+ });
1263
+ if (state.kind === "none") return display;
1264
+ if (state.kind === "closed") return display === null ? null : /* @__PURE__ */ jsx(HoverHint, {
1265
+ text: state.reason,
1266
+ children: /* @__PURE__ */ jsx(Button, {
1267
+ "aria-label": "Date",
1268
+ as: "span",
1269
+ fontSize: 1,
1270
+ mode: "bleed",
1271
+ onClick: stopRowClick,
1272
+ onMouseDown: stopRowMouseDown,
1273
+ padding: 2,
1274
+ children: display
1275
+ })
1276
+ });
1277
+ const kind = dateControlKind(state), commit = (mode, value) => {
1278
+ state.kind === "editable" && save(() => editFieldFor(instanceId, {
1279
+ target: editTargetOf(state.field),
1280
+ ...mode === "set" ? {
1281
+ mode: mode,
1282
+ value: value
1283
+ } : {
1284
+ mode: mode
1285
+ }
1286
+ }).then(() => {}));
1287
+ }, handleClick = e => {
1288
+ e.stopPropagation(), setOpen(v => !v);
1289
+ };
1290
+ /* @__PURE__ */
1291
+ return jsx(DismissablePopover, {
1292
+ content: /* @__PURE__ */ jsx(Box, {
1293
+ onClick: stopRowClick,
1294
+ children: state.kind === "loading" || kind === void 0 ? /* @__PURE__ */ jsx(LoadingRow, {
1295
+ label: "Loading…",
1296
+ padding: 3
1297
+ }) : /* @__PURE__ */ jsx(ClearableDatePicker, {
1298
+ clearLabel: "Clear date",
1299
+ onClear: () => {
1300
+ setOpen(!1), commit("unset");
1301
+ },
1302
+ onPick: next => {
1303
+ kind === "date" && setOpen(!1), commit("set", serializeDateFieldValue(next, kind));
1304
+ },
1305
+ selectTime: kind === "datetime",
1306
+ value: parseDateFieldValue(state.field.value, kind)
1469
1307
  })
1470
- }) : null ]
1308
+ }),
1309
+ onDismiss: () => setOpen(!1),
1310
+ open: open,
1311
+ children: display ? /* @__PURE__ */ jsx(Button, {
1312
+ "aria-label": "Change date",
1313
+ as: "span",
1314
+ fontSize: 1,
1315
+ mode: "bleed",
1316
+ onClick: handleClick,
1317
+ onMouseDown: stopRowMouseDown,
1318
+ padding: 2,
1319
+ children: display
1320
+ }) : /* @__PURE__ */ jsx(HoverHint, {
1321
+ disabled: open,
1322
+ text: "Set date",
1323
+ children: /* @__PURE__ */ jsx(Button, {
1324
+ "aria-label": "Set date",
1325
+ as: "span",
1326
+ fontSize: 1,
1327
+ icon: CalendarIcon,
1328
+ mode: "bleed",
1329
+ onClick: handleClick,
1330
+ onMouseDown: stopRowMouseDown,
1331
+ padding: 2
1332
+ })
1333
+ })
1471
1334
  });
1472
1335
  }
1473
1336
 
1474
- function GroupTitle({text: text}) {
1337
+ function RefChips({refs: refs, max: max = 2}) {
1338
+ if (refs.length === 0) return null;
1339
+ const shown = refs.slice(0, max), overflow = refs.length - shown.length;
1475
1340
  /* @__PURE__ */
1476
- return jsx(Text, {
1477
- size: 1,
1478
- weight: "semibold",
1479
- children: text
1341
+ return jsxs(Flex, {
1342
+ align: "center",
1343
+ gap: 3,
1344
+ wrap: "wrap",
1345
+ children: [ shown.map(ref => /* @__PURE__ */ jsx(DocPreviewLink, {
1346
+ gdr: ref,
1347
+ layout: "inline"
1348
+ }, ref.id)), overflow > 0 ? /* @__PURE__ */ jsxs(Text, {
1349
+ muted: !0,
1350
+ size: 1,
1351
+ children: [ "+", overflow, " more" ]
1352
+ }) : null ]
1480
1353
  });
1481
1354
  }
1482
1355
 
1483
- function WorkflowBadgeTitle({group: group}) {
1484
- const badge = /* @__PURE__ */ jsx(Badge, {
1485
- fontSize: 1,
1486
- mode: "outline",
1487
- children: group.workflowTitle
1488
- });
1489
- return group.workflowDescription ? /* @__PURE__ */ jsx(HoverHint, {
1490
- text: group.workflowDescription,
1491
- children: badge
1492
- }) : badge;
1493
- }
1356
+ const EMPTY_NOTE = "No tasks match — adjust the filters, or start a workflow", UNGROUPED_TITLE = "Workflows without Studio documents";
1494
1357
 
1495
- function CollapsibleGroup({header: header, children: children}) {
1496
- const [open, setOpen] = useState(!0);
1497
- /* @__PURE__ */
1498
- return jsxs(Stack, {
1358
+ function TaskGroupList({groups: groups, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
1359
+ return groups.withDocuments.length === 0 && groups.withoutDocuments.length === 0 ? /* @__PURE__ */ jsx(MutedNote, {
1360
+ text: EMPTY_NOTE
1361
+ }) : /* @__PURE__ */ jsxs(Stack, {
1499
1362
  gap: 2,
1500
- children: [
1501
- /* @__PURE__ */ jsx(Card, {
1502
- paddingX: 2,
1503
- paddingY: 2,
1504
- radius: 2,
1505
- tone: "transparent",
1506
- children: /* @__PURE__ */ jsxs(Flex, {
1363
+ children: [ groups.withDocuments.map(group => /* @__PURE__ */ jsx(CollapsibleBand, {
1364
+ background: !0,
1365
+ sticky: !0,
1366
+ title: extractDocumentId(group.document.id),
1367
+ header: /* @__PURE__ */ jsx(Flex, {
1507
1368
  align: "center",
1508
- gap: 2,
1509
- children: [
1510
- /* @__PURE__ */ jsx(Button, {
1511
- "aria-label": open ? "Collapse" : "Expand",
1512
- fontSize: 1,
1513
- icon: open ? ChevronDownIcon : ChevronRightIcon,
1514
- mode: "bleed",
1515
- onClick: () => setOpen(v => !v),
1516
- padding: 1
1517
- }), header ]
1518
- })
1519
- }), open ? children : null ]
1520
- });
1521
- }
1522
-
1523
- function DocumentTaskGroup({group: group, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow}) {
1524
- const document = group.document;
1525
- return document ? /* @__PURE__ */ jsx(CollapsibleGroup, {
1526
- header: /* @__PURE__ */ jsxs(Fragment, {
1527
- children: [
1528
- /* @__PURE__ */ jsx(Box, {
1529
1369
  flex: "none",
1530
1370
  style: {
1531
1371
  maxWidth: "60%"
@@ -1534,323 +1374,333 @@ function DocumentTaskGroup({group: group, onOpenTask: onOpenTask, onOpenWorkflow
1534
1374
  max: 2,
1535
1375
  refs: group.documents
1536
1376
  })
1537
- }),
1538
- /* @__PURE__ */ jsx(WorkflowBadgeTitle, {
1539
- group: group
1540
- }),
1541
- /* @__PURE__ */ jsx(Box, {
1542
- flex: 1
1543
- }),
1544
- /* @__PURE__ */ jsx(GroupMenu, {
1545
- document: document,
1546
- group: group,
1547
- onOpenWorkflow: onOpenWorkflow
1548
- }) ]
1549
- }),
1550
- children: /* @__PURE__ */ jsx(TaskRows, {
1377
+ }),
1378
+ children: /* @__PURE__ */ jsx(InstanceBands, {
1379
+ instances: group.instances,
1380
+ onOpenTask: onOpenTask,
1381
+ onOpenWorkflow: onOpenWorkflow,
1382
+ showTerminalActions: showTerminalActions
1383
+ })
1384
+ }, group.key)), groups.withoutDocuments.length > 0 ? /* @__PURE__ */ jsx(CollapsibleBand, {
1385
+ background: !0,
1386
+ sticky: !0,
1387
+ title: UNGROUPED_TITLE,
1388
+ header: /* @__PURE__ */ jsx(Text, {
1389
+ size: 1,
1390
+ weight: "semibold",
1391
+ children: UNGROUPED_TITLE
1392
+ }),
1393
+ children: /* @__PURE__ */ jsx(InstanceBands, {
1394
+ instances: groups.withoutDocuments,
1395
+ onOpenTask: onOpenTask,
1396
+ onOpenWorkflow: onOpenWorkflow,
1397
+ showTerminalActions: showTerminalActions
1398
+ })
1399
+ }) : null ]
1400
+ });
1401
+ }
1402
+
1403
+ function targetOf(row) {
1404
+ return {
1405
+ instanceId: row.instanceId,
1406
+ stage: row.stage,
1407
+ activityName: row.activityName
1408
+ };
1409
+ }
1410
+
1411
+ function InstanceBands({instances: instances, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
1412
+ /* @__PURE__ */
1413
+ return jsx(Stack, {
1414
+ gap: 1,
1415
+ children: instances.map(band => /* @__PURE__ */ jsx(InstanceBand, {
1416
+ band: band,
1551
1417
  onOpenTask: onOpenTask,
1552
- rows: group.rows
1553
- })
1554
- }) : null;
1418
+ onOpenWorkflow: onOpenWorkflow,
1419
+ showTerminalActions: showTerminalActions
1420
+ }, band.instanceId))
1421
+ });
1555
1422
  }
1556
1423
 
1557
- function GroupMenu({group: group, document: document, onOpenWorkflow: onOpenWorkflow}) {
1558
- const router = useRouter(), state = useDocLink(document);
1424
+ function InstanceBand({band: band, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
1425
+ const {resolvePathFromState: resolvePathFromState} = useRouter();
1559
1426
  /* @__PURE__ */
1560
- return jsx(HintedMenuButton, {
1561
- button: /* @__PURE__ */ jsx(Button, {
1562
- "aria-label": "Group actions",
1563
- fontSize: 1,
1564
- icon: EllipsisHorizontalIcon,
1565
- mode: "bleed",
1566
- padding: 2
1567
- }),
1568
- hint: "Group actions",
1569
- id: `task-group-menu-${group.key}`,
1570
- menu: /* @__PURE__ */ jsxs(Menu, {
1427
+ return jsx(CollapsibleBand, {
1428
+ title: band.title,
1429
+ header: /* @__PURE__ */ jsxs(Fragment, {
1571
1430
  children: [
1572
- /* @__PURE__ */ jsx(MenuItem, {
1573
- onClick: () => onOpenWorkflow(group.instanceId),
1574
- text: "View workflow"
1575
- }), state.kind === "linked" ? /* @__PURE__ */ jsx(MenuItem, {
1576
- onClick: () => router.navigateIntent("edit", {
1577
- id: state.bareId
1431
+ /* @__PURE__ */ jsx(LinkChip, {
1432
+ hint: "View workflow",
1433
+ href: resolvePathFromState({
1434
+ instanceId: band.instanceId
1578
1435
  }),
1579
- text: "Open document"
1580
- }) : null ]
1436
+ onClick: event => {
1437
+ event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || (event.preventDefault(),
1438
+ onOpenWorkflow(band.instanceId));
1439
+ },
1440
+ children: /* @__PURE__ */ jsxs(Flex, {
1441
+ align: "center",
1442
+ gap: 2,
1443
+ style: {
1444
+ minWidth: 0
1445
+ },
1446
+ children: [
1447
+ /* @__PURE__ */ jsx(Text, {
1448
+ size: 1,
1449
+ style: {
1450
+ minWidth: 0
1451
+ },
1452
+ textOverflow: "ellipsis",
1453
+ weight: "medium",
1454
+ children: band.title
1455
+ }), band.breadcrumb === void 0 ? null : /* @__PURE__ */ jsx(BreadcrumbTail, {
1456
+ segments: [ band.breadcrumb ]
1457
+ }),
1458
+ /* @__PURE__ */ jsx(StageFace, {
1459
+ title: band.stageTitle
1460
+ }) ]
1461
+ })
1462
+ }),
1463
+ /* @__PURE__ */ jsx(TrailingHairline, {}) ]
1581
1464
  }),
1582
- popover: {
1583
- portal: !0
1584
- }
1465
+ children: /* @__PURE__ */ jsx(Box, {
1466
+ paddingLeft: 5,
1467
+ children: /* @__PURE__ */ jsx(TaskRows, {
1468
+ onOpenTask: onOpenTask,
1469
+ rows: band.rows,
1470
+ showTerminalActions: showTerminalActions
1471
+ })
1472
+ })
1585
1473
  });
1586
1474
  }
1587
1475
 
1588
- function TaskRows({rows: rows, onOpenTask: onOpenTask}) {
1476
+ function TaskRows({rows: rows, onOpenTask: onOpenTask, showTerminalActions: showTerminalActions}) {
1589
1477
  /* @__PURE__ */
1590
1478
  return jsx(Stack, {
1591
- children: rows.map(row => /* @__PURE__ */ jsx(TaskRowButton, {
1592
- onOpen: () => onOpenTask({
1593
- instanceId: row.instanceId,
1594
- stage: row.stage,
1595
- activityName: row.activityName
1596
- }),
1597
- row: row
1479
+ gap: 1,
1480
+ children: rows.map(row => /* @__PURE__ */ jsx(TaskActivityRow, {
1481
+ onOpen: () => onOpenTask(targetOf(row)),
1482
+ row: row,
1483
+ showTerminalActions: showTerminalActions
1598
1484
  }, `${row.instanceId}:${row.activityName}`))
1599
1485
  });
1600
1486
  }
1601
1487
 
1602
- function RowDate({dates: dates}) {
1603
- const first = dates.find(value => parseStoredDateValue(value) !== void 0);
1604
- return first === void 0 ? null : /* @__PURE__ */ jsx(Text, {
1605
- muted: !0,
1606
- size: 1,
1607
- children: formatDate(first)
1608
- });
1609
- }
1610
-
1611
- function RowTitle({row: row}) {
1612
- const title = /* @__PURE__ */ jsx(Text, {
1613
- size: 1,
1614
- style: row.open ? void 0 : {
1615
- opacity: .66
1616
- },
1617
- weight: "medium",
1618
- children: row.title
1488
+ function TaskActivityRow({row: row, onOpen: onOpen, showTerminalActions: showTerminalActions}) {
1489
+ const entry = useWorkflowInstanceEntry(row.instanceId), render = taskRowRender(row, entry);
1490
+ /* @__PURE__ */
1491
+ return jsx(StaleLock, {
1492
+ stale: render.stale,
1493
+ children: /* @__PURE__ */ jsx(ActivityRow, {
1494
+ assignState: render.assignState,
1495
+ assigneeIds: render.assigneeIds,
1496
+ dateControl: /* @__PURE__ */ jsx(ActivityDateControl, {
1497
+ dates: render.dates,
1498
+ instanceId: row.instanceId,
1499
+ state: render.dateState
1500
+ }),
1501
+ face: render.face,
1502
+ instanceId: row.instanceId,
1503
+ onOpen: onOpen,
1504
+ terminalActions: showTerminalActions ? render.terminalActions : []
1505
+ })
1619
1506
  });
1620
- return row.description ? /* @__PURE__ */ jsx(HoverHint, {
1621
- text: row.description,
1622
- children: title
1623
- }) : title;
1624
1507
  }
1625
1508
 
1626
- function RowBreadcrumb({breadcrumb: breadcrumb}) {
1627
- return breadcrumb ? /* @__PURE__ */ jsxs(Flex, {
1628
- align: "center",
1629
- gap: 2,
1630
- children: [
1631
- /* @__PURE__ */ jsx(Text, {
1632
- muted: !0,
1633
- size: 1,
1634
- children: breadcrumb
1635
- }),
1636
- /* @__PURE__ */ jsx(Text, {
1637
- muted: !0,
1638
- size: 1,
1639
- children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
1640
- }) ]
1641
- }) : null;
1642
- }
1509
+ const INSTANCE_CAP = 200, NEWEST_INSTANCES = {
1510
+ includeCompleted: !0,
1511
+ limit: INSTANCE_CAP
1512
+ };
1643
1513
 
1644
- function TaskRowButton({row: row, onOpen: onOpen}) {
1645
- /* @__PURE__ */
1646
- return jsx(Card, {
1647
- as: "button",
1648
- onClick: onOpen,
1649
- padding: 3,
1650
- paddingLeft: 4,
1651
- radius: 2,
1652
- style: {
1653
- cursor: "pointer",
1654
- textAlign: "left"
1655
- },
1656
- children: /* @__PURE__ */ jsxs(Flex, {
1657
- align: "center",
1658
- gap: 3,
1659
- children: [
1660
- /* @__PURE__ */ jsx(ActivityStatusIcon, {
1661
- status: row.status
1662
- }),
1663
- /* @__PURE__ */ jsx(RowBreadcrumb, {
1664
- breadcrumb: row.breadcrumb
1665
- }),
1666
- /* @__PURE__ */ jsx(RowTitle, {
1667
- row: row
1668
- }),
1669
- /* @__PURE__ */ jsx(Badge, {
1670
- fontSize: 1,
1671
- mode: "outline",
1672
- tone: "default",
1673
- children: row.stageTitle
1674
- }),
1675
- /* @__PURE__ */ jsx(Flex, {
1676
- flex: 1
1677
- }),
1678
- /* @__PURE__ */ jsx(RowDate, {
1679
- dates: row.dates
1680
- }), row.assigneeIds.length > 0 ? /* @__PURE__ */ jsx(UserAvatarGroup, {
1681
- ids: row.assigneeIds
1682
- }) : null ]
1683
- })
1514
+ function useToolInstances() {
1515
+ const {engine: engine} = useWorkflowContext(), {instances: instances, loading: loading, unreadable: unreadable} = useWorkflowInstances({
1516
+ engine: engine,
1517
+ filter: NEWEST_INSTANCES
1684
1518
  });
1519
+ return useMemo(() => {
1520
+ const rows = instances ?? [];
1521
+ return {
1522
+ inFlight: rows.filter(instance => terminalState(instance) === "in-flight"),
1523
+ settled: rows.filter(instance => terminalState(instance) !== "in-flight"),
1524
+ truncated: rows.length + unreadable.length === INSTANCE_CAP,
1525
+ loading: loading,
1526
+ unreadable: unreadable
1527
+ };
1528
+ }, [ instances, loading, unreadable ]);
1685
1529
  }
1686
1530
 
1687
- function TasksTab({instances: instances, loading: loading, identity: identity, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, truncated: truncated}) {
1688
- const [segment, setSegment] = useState("mine"), [filters, setFilters] = useState({}), groups = useMemo(() => deriveTaskGroups({
1531
+ function TasksTab({filterSlot: filterSlot, instances: instances, loading: loading, identity: identity, mineOnly: mineOnly, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, truncated: truncated, unreadable: unreadable}) {
1532
+ const [filters, setFilters] = useState({}), groups = useMemo(() => deriveTaskGroups({
1689
1533
  instances: instances,
1690
1534
  identity: identity
1691
1535
  }), [ instances, identity ]), options = useMemo(() => taskFilterOptions(groups), [ groups ]), visible = useMemo(() => {
1692
- const segmented = segment === "mine" ? onlyMyTasks(groups) : groups;
1693
- return applyTaskFilters({
1694
- groups: segmented,
1536
+ const filtered = applyTaskFilters({
1537
+ groups: groups,
1695
1538
  filters: filters,
1696
1539
  now: /* @__PURE__ */ new Date
1697
1540
  });
1698
- }, [ groups, segment, filters ]);
1699
- return loading ? /* @__PURE__ */ jsx(LoadingRow, {
1700
- label: "Loading tasks…"
1541
+ return mineOnly ? onlyMyTasks(filtered) : filtered;
1542
+ }, [ groups, filters, mineOnly ]);
1543
+ return loading ?
1544
+ /* @__PURE__ */ jsx(Box, {
1545
+ paddingLeft: 2,
1546
+ children: /* @__PURE__ */ jsx(LoadingRow, {
1547
+ label: "Loading tasks…"
1548
+ })
1701
1549
  }) : /* @__PURE__ */ jsxs(Box, {
1702
1550
  children: [
1703
- /* @__PURE__ */ jsxs(Flex, {
1704
- align: "center",
1551
+ /* @__PURE__ */ jsx(FilterRowPortal, {
1552
+ filters: filters,
1553
+ onChange: setFilters,
1554
+ options: options,
1555
+ slot: filterSlot
1556
+ }),
1557
+ /* @__PURE__ */ jsxs(Stack, {
1705
1558
  gap: 2,
1706
- paddingBottom: 3,
1707
1559
  children: [
1708
- /* @__PURE__ */ jsxs(TabList, {
1709
- gap: 1,
1710
- children: [
1711
- /* @__PURE__ */ jsx(Tab, {
1712
- "aria-controls": "workflow-tasks-panel",
1713
- fontSize: 1,
1714
- id: "workflow-tasks-mine",
1715
- label: "My tasks",
1716
- onClick: () => setSegment("mine"),
1717
- padding: 2,
1718
- selected: segment === "mine"
1719
- }),
1720
- /* @__PURE__ */ jsx(Tab, {
1721
- "aria-controls": "workflow-tasks-panel",
1722
- fontSize: 1,
1723
- id: "workflow-tasks-all",
1724
- label: "All tasks",
1725
- onClick: () => setSegment("all"),
1726
- padding: 2,
1727
- selected: segment === "all"
1728
- }) ]
1729
- }),
1730
- /* @__PURE__ */ jsx(Box, {
1731
- flex: 1
1732
- }),
1733
- /* @__PURE__ */ jsx(ActiveFilterChips, {
1734
- filters: filters,
1735
- onChange: setFilters,
1736
- options: options
1560
+ /* @__PURE__ */ jsx(UnreadableDocsNote, {
1561
+ unreadable: unreadable
1737
1562
  }),
1738
- /* @__PURE__ */ jsx(TaskFilterMenu, {
1739
- filters: filters,
1740
- onChange: setFilters,
1741
- options: options
1742
- }) ]
1743
- }),
1744
- /* @__PURE__ */ jsxs(Box, {
1745
- id: "workflow-tasks-panel",
1746
- children: [
1747
1563
  /* @__PURE__ */ jsx(TaskGroupList, {
1748
1564
  groups: visible,
1749
1565
  onOpenTask: onOpenTask,
1750
- onOpenWorkflow: onOpenWorkflow
1751
- }), truncated ? /* @__PURE__ */ jsx(MutedNote, {
1752
- text: `Tasks from the latest ${INSTANCE_CAP} workflows — older ones aren't listed.`
1753
- }) : null ]
1754
- }) ]
1566
+ onOpenWorkflow: onOpenWorkflow,
1567
+ showTerminalActions: mineOnly
1568
+ }) ]
1569
+ }), truncated ? /* @__PURE__ */ jsx(MutedNote, {
1570
+ text: `Tasks from the latest ${INSTANCE_CAP} workflows — older ones aren't listed.`
1571
+ }) : null ]
1755
1572
  });
1756
1573
  }
1757
1574
 
1575
+ const TOOL_TABS = [ {
1576
+ value: "overview",
1577
+ label: "Overview"
1578
+ }, {
1579
+ value: "for-me",
1580
+ label: "For me"
1581
+ } ], [firstToolTab, ...restToolTabs] = TOOL_TABS, TOOL_TAB_CODEC = workflowsTabCodec([ firstToolTab.value, ...restToolTabs.map(tab => tab.value) ]);
1582
+
1583
+ function useToolTab() {
1584
+ const router = useRouter(), params = Object.fromEntries(router.state._searchParams ?? []);
1585
+ return {
1586
+ tab: TOOL_TAB_CODEC.read(params),
1587
+ setTab: next => router.navigate({
1588
+ _searchParams: definedEntries(TOOL_TAB_CODEC.write(params, next))
1589
+ })
1590
+ };
1591
+ }
1592
+
1593
+ function definedEntries(params) {
1594
+ return Object.entries(params).filter(entry => entry[1] !== void 0);
1595
+ }
1596
+
1597
+ function carryingSearchParams(state, searchParams) {
1598
+ return searchParams === void 0 ? state : {
1599
+ ...state,
1600
+ _searchParams: searchParams
1601
+ };
1602
+ }
1603
+
1758
1604
  function WorkflowsToolRoot() {
1759
1605
  const router = useRouter(), {seedInstance: seedInstance} = useWorkflowContext(), instanceId = typeof router.state.instanceId == "string" ? router.state.instanceId : void 0;
1760
1606
  return instanceId ? /* @__PURE__ */ jsx(WorkflowInstanceDetail, {
1761
1607
  instanceId: instanceId,
1762
- onBack: () => router.navigate({})
1608
+ onBack: () => router.navigate(carryingSearchParams({}, router.state._searchParams))
1763
1609
  }, instanceId) : /* @__PURE__ */ jsx(WorkflowsHome, {
1764
1610
  onOpenInstance: instance => {
1765
- typeof instance != "string" && seedInstance(instance), router.navigate({
1611
+ typeof instance != "string" && seedInstance(instance), router.navigate(carryingSearchParams({
1766
1612
  instanceId: typeof instance == "string" ? instance : instance._id
1767
- });
1613
+ }, router.state._searchParams));
1768
1614
  }
1769
1615
  });
1770
1616
  }
1771
1617
 
1772
- function ToolPanel({identity: identity, instances: instances, onOpenInstance: onOpenInstance, onOpenTask: onOpenTask, tab: tab}) {
1773
- return instances.invalid ? /* @__PURE__ */ jsx(InvalidDocNotice, {
1774
- invalid: instances.invalid
1775
- }) : tab === "tasks" ? /* @__PURE__ */ jsx(TasksTab, {
1776
- identity: identity,
1777
- instances: [ ...instances.inFlight, ...instances.settled ],
1778
- loading: instances.loading,
1779
- onOpenTask: onOpenTask,
1780
- onOpenWorkflow: onOpenInstance,
1781
- truncated: instances.truncated
1782
- }) : /* @__PURE__ */ jsx(WorkflowsDashboard, {
1783
- instances: instances,
1784
- onOpenInstance: onOpenInstance
1785
- });
1786
- }
1787
-
1788
1618
  function WorkflowsHome({onOpenInstance: onOpenInstance}) {
1789
- const [tab, setTab] = useState("tasks"), instances = useToolInstances(), identity = useAssignmentIdentity(), [openTask, setOpenTask] = useState(null);
1790
- /* @__PURE__ */
1791
- return jsxs(Card, {
1619
+ const {tab: tab, setTab: setTab} = useToolTab(), instances = useToolInstances(), identity = useAssignmentIdentity(), [openTask, setOpenTask] = useState(null), openInstance = target => {
1620
+ if (typeof target != "string") return onOpenInstance(target);
1621
+ const held = [ ...instances.inFlight, ...instances.settled ].find(i => i._id === target);
1622
+ onOpenInstance(held ?? target);
1623
+ }, [filterSlot, setFilterSlot] = useState(null), scrollerRef = useRef(null);
1624
+ return useEffect(() => {
1625
+ scrollerRef.current?.scrollTo({
1626
+ top: 0
1627
+ });
1628
+ }, [ tab ]),
1629
+ /* @__PURE__ */ jsxs(Card, {
1792
1630
  height: "fill",
1793
- overflow: "auto",
1794
1631
  children: [
1795
- /* @__PURE__ */ jsx(Box, {
1796
- padding: 4,
1797
- children: /* @__PURE__ */ jsx(Container, {
1798
- width: 2,
1799
- children: /* @__PURE__ */ jsxs(Stack, {
1800
- gap: 4,
1801
- children: [
1802
- /* @__PURE__ */ jsxs(Flex, {
1803
- align: "center",
1632
+ /* @__PURE__ */ jsxs(Flex, {
1633
+ direction: "column",
1634
+ height: "fill",
1635
+ overflow: "hidden",
1636
+ children: [
1637
+ /* @__PURE__ */ jsx(Box, {
1638
+ flex: "none",
1639
+ padding: 3,
1640
+ children: /* @__PURE__ */ jsx(Container, {
1641
+ width: 2,
1642
+ children: /* @__PURE__ */ jsxs(Stack, {
1804
1643
  gap: 3,
1805
1644
  children: [
1806
- /* @__PURE__ */ jsx(Text, {
1807
- size: 2,
1808
- weight: "semibold",
1809
- children: "Workflows"
1810
- }),
1811
- /* @__PURE__ */ jsx(Box, {
1812
- flex: 1
1645
+ /* @__PURE__ */ jsxs(Flex, {
1646
+ align: "center",
1647
+ gap: 3,
1648
+ paddingLeft: 2,
1649
+ children: [
1650
+ /* @__PURE__ */ jsx(Text, {
1651
+ size: 2,
1652
+ weight: "semibold",
1653
+ children: "Workflows"
1654
+ }),
1655
+ /* @__PURE__ */ jsx(Box, {
1656
+ flex: 1
1657
+ }),
1658
+ /* @__PURE__ */ jsx(NewWorkflowButton, {}) ]
1813
1659
  }),
1814
- /* @__PURE__ */ jsx(NewWorkflowButton, {}) ]
1815
- }),
1816
- /* @__PURE__ */ jsxs(TabList, {
1817
- gap: 1,
1818
- children: [
1819
- /* @__PURE__ */ jsx(Tab, {
1820
- "aria-controls": "workflows-tool-panel",
1821
- fontSize: 1,
1822
- id: "workflows-tool-tab-tasks",
1823
- label: "Tasks",
1824
- onClick: () => setTab("tasks"),
1825
- padding: 2,
1826
- selected: tab === "tasks"
1660
+ /* @__PURE__ */ jsx(TabSwitch, {
1661
+ ariaControls: "workflows-tool-panel",
1662
+ idPrefix: "workflows-tool-tab",
1663
+ onSelect: setTab,
1664
+ options: TOOL_TABS,
1665
+ selected: tab
1827
1666
  }),
1828
- /* @__PURE__ */ jsx(Tab, {
1829
- "aria-controls": "workflows-tool-panel",
1830
- fontSize: 1,
1831
- id: "workflows-tool-tab-workflows",
1832
- label: "All workflows",
1833
- onClick: () => setTab("workflows"),
1834
- padding: 2,
1835
- selected: tab === "workflows"
1667
+ /* @__PURE__ */ jsx("div", {
1668
+ ref: setFilterSlot
1836
1669
  }) ]
1837
- }),
1838
- /* @__PURE__ */ jsx(Box, {
1670
+ })
1671
+ })
1672
+ }),
1673
+ /* @__PURE__ */ jsx(Box, {
1674
+ ref: scrollerRef,
1675
+ flex: 1,
1676
+ overflow: "auto",
1677
+ paddingX: 3,
1678
+ style: {
1679
+ scrollbarGutter: "stable"
1680
+ },
1681
+ children: /* @__PURE__ */ jsx(Container, {
1682
+ width: 2,
1683
+ children: /* @__PURE__ */ jsx(TabPanel, {
1684
+ "aria-labelledby": `workflows-tool-tab-${tab}`,
1839
1685
  id: "workflows-tool-panel",
1840
- children: /* @__PURE__ */ jsx(ToolPanel, {
1841
- identity: identity,
1842
- instances: instances,
1843
- onOpenInstance: target => {
1844
- if (typeof target != "string") return onOpenInstance(target);
1845
- const held = [ ...instances.inFlight, ...instances.settled ].find(i => i._id === target);
1846
- onOpenInstance(held ?? target);
1847
- },
1848
- onOpenTask: setOpenTask,
1849
- tab: tab
1686
+ children: /* @__PURE__ */ jsx(Box, {
1687
+ paddingBottom: 4,
1688
+ paddingTop: 1,
1689
+ children: /* @__PURE__ */ jsx(TasksTab, {
1690
+ filterSlot: filterSlot,
1691
+ identity: identity,
1692
+ instances: [ ...instances.inFlight, ...instances.settled ],
1693
+ loading: instances.loading,
1694
+ mineOnly: tab === "for-me",
1695
+ onOpenTask: setOpenTask,
1696
+ onOpenWorkflow: openInstance,
1697
+ truncated: instances.truncated,
1698
+ unreadable: instances.unreadable
1699
+ })
1850
1700
  })
1851
- }) ]
1701
+ })
1852
1702
  })
1853
- })
1703
+ }) ]
1854
1704
  }), openTask ? /* @__PURE__ */ jsx(ToolActivityDialog, {
1855
1705
  onClose: () => setOpenTask(null),
1856
1706
  target: openTask