@sanity/workflow-studio-plugin 0.4.0 → 0.20.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,45 +1,45 @@
1
- import { tryParseGdr, resourceFromParsed, latestDeployedDefinitions, gdrRef, isSubjectEntry, isInputSourced, missingRequiredInputs, initialFieldIssues, releaseRef, refKindAcceptsTypes, rejectedRefTypes, ActionDisabledError, MutationGuardDeniedError, errorMessage, findOpenStageEntry, actionRendering, terminalState, ENGINE_API_VERSION, WORKFLOW_INSTANCE_TYPE, WORKFLOW_DEFINITION_TYPE, GUARD_DOC_TYPE, isTodoListEntry, isTodoListItem, isTerminalActivityStatus, describeCondition, sentenceCase, checklistLines, findCurrentActivityEntry, scalarValidationIssues, extractDocumentId, isSingleDocRefEntry, isGdr, resolveFieldEntry, isSingleDocRefKind, isNotesEntry, parseDefinitionSnapshot, StartNotSettledError, StartNotAllowedError, isUnprimed, unboundAllowedReads, explainStartAllowed, evaluateStartFilter, startKindOf, readsRootDocument, instancesQuery, assertReadableModel, isTerminalStage, DEFAULT_TRANSITION_WHEN, isStartableDefinition, acceptsDocumentType, definitionsListGroq, gdrFromResource, instanceWatchesDocument, aclPathForResource, definitionLookupGroq, instanceDocId, documentActionDenials } from "@sanity/workflow-engine";
2
-
3
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
4
2
 
5
3
  import { TimelineIcon } from "@sanity/icons/Timeline";
6
4
 
7
- import { Tooltip, Box, Stack, Text, useClickOutsideEvent, Popover, Flex, TextInput, Button, Select, Checkbox, TextSkeleton, Autocomplete, Card, TextArea, useToast, Dialog, MenuButton, Menu, MenuItem, useTheme_v2, Switch, Code, Badge, TabList, Tab, Spinner, TabPanel, MenuDivider } from "@sanity/ui";
5
+ import { Tooltip, Box, Stack, Text, useClickOutsideEvent, Popover, Flex, TextInput, Button, Select, Checkbox, Autocomplete, Card, TextArea, useToast, Dialog, Switch, TextSkeleton, Code, Spinner, useTheme_v2, Badge, MenuButton, Menu, MenuItem, useElementSize, TabList, Tab, TabPanel, MenuDivider } from "@sanity/ui";
8
6
 
9
- import { createContext, useContext, useEffect, useMemo, useRef, useState, useCallback, useId, Fragment as Fragment$1, Component, lazy, Suspense } from "react";
7
+ import { createContext, useContext, useEffect, useSyncExternalStore, useCallback, useMemo, useRef, useState, Fragment as Fragment$1, createElement, isValidElement, useId, useLayoutEffect, Component, lazy, Suspense } from "react";
10
8
 
11
9
  import { usePaneRouter } from "sanity/structure";
12
10
 
11
+ import { tryParseGdr, resourceFromParsed, latestDeployedDefinitions, isStartableDefinition, isSubjectEntry, isInputSourced, acceptsDocumentType, missingRequiredInputs, initialFieldIssues, gdrRef, releaseRef, refKindAcceptsTypes, rejectedRefTypes, ActionDisabledError, MutationGuardDeniedError, errorMessage, findOpenStageEntry, actionRendering, terminalState, ENGINE_API_VERSION, WORKFLOW_INSTANCE_TYPE, WORKFLOW_DEFINITION_TYPE, GUARD_DOC_TYPE, isTodoListEntry, isTodoListItem, isTerminalActivityStatus, describeCondition, sentenceCase, checklistLines, findCurrentActivityEntry, scalarValidationIssues, toBareId, isSingleDocRefEntry, isGdr, resolveFieldEntry, isSingleDocRefKind, isNotesEntry, parseDefinitionSnapshot, isUnprimed, StartNotSettledError, StartNotAllowedError, unboundAllowedReads, explainStartAllowed, evaluateStartFilter, startKindOf, readsRootDocument, instancesQuery, assertReadableModel, isTerminalStage, DEFAULT_TRANSITION_WHEN, definitionsListGroq, gdrFromResource, subscriptionDocumentsForInstance, aclPathForResource, definitionLookupGroq, instanceDocId, documentActionDenials } from "@sanity/workflow-engine";
12
+
13
+ import { CheckmarkIcon } from "@sanity/icons/Checkmark";
14
+
15
+ import { CloseIcon } from "@sanity/icons/Close";
16
+
13
17
  import { formatDistanceToNow } from "date-fns/formatDistanceToNow";
14
18
 
15
19
  import { getPublishedId } from "@sanity/client/csm";
16
20
 
17
21
  import { useStudioProjectUsers, useStudioObserver, useWorkflowEngine, useWorkflowInstances, useWorkflowSession } from "@sanity/workflow-studio";
18
22
 
19
- import { useCurrentUser, useValuePreview, SanityDefaultPreview, Preview, useClient, useSchema, useWorkspace, TextWithTone, usePerspective, useIsReleaseActive, useActiveReleases, useTemplates, useInitialValueResolverContext, resolveInitialValue, definePlugin, isObjectInputProps } from "sanity";
23
+ import { useCurrentUser, SanityDefaultPreview, Preview, useValuePreview, useClient, useSchema, useWorkspace, TextWithTone, usePerspective, useIsReleaseActive, useActiveReleases, useTemplates, useInitialValueResolverContext, resolveInitialValue, definePlugin, isObjectInputProps } from "sanity";
20
24
 
21
25
  import { CalendarIcon } from "@sanity/icons/Calendar";
22
26
 
23
- import { ClearableDatePicker, MemberPicker as MemberPicker$1, AssigneePicker as AssigneePicker$1, MemberAvatar, AssigneeBadges as AssigneeBadges$1, AssigneeBadge as AssigneeBadge$1, MemberAvatarGroup, DatePicker, roleListLabel } from "@sanity/workflow-components";
27
+ import { ClearableDatePicker, MemberPicker as MemberPicker$1, AssigneePicker as AssigneePicker$1, MemberAvatar, AssigneeBadges as AssigneeBadges$1, AssigneeBadge as AssigneeBadge$1, ProgressBar, MemberAvatarGroup, clampProgress, progressCaption, DatePicker, roleListLabel } from "@sanity/workflow-components";
24
28
 
25
29
  import { UserIcon } from "@sanity/icons/User";
26
30
 
27
- import { CloseIcon } from "@sanity/icons/Close";
28
-
29
31
  import { SearchIcon } from "@sanity/icons/Search";
30
32
 
31
- import { ChevronDownIcon } from "@sanity/icons/ChevronDown";
32
-
33
33
  import { ChevronRightIcon } from "@sanity/icons/ChevronRight";
34
34
 
35
- import { CheckmarkIcon } from "@sanity/icons/Checkmark";
36
-
37
35
  import { EditIcon } from "@sanity/icons/Edit";
38
36
 
39
37
  import { randomKey } from "@sanity/util/content";
40
38
 
41
39
  import { InfoOutlineIcon } from "@sanity/icons/InfoOutline";
42
40
 
41
+ import { DocumentIcon } from "@sanity/icons/Document";
42
+
43
43
  import { WarningOutlineIcon } from "@sanity/icons/WarningOutline";
44
44
 
45
45
  import { IntentLink, useRouter, route } from "sanity/router";
@@ -58,9 +58,9 @@ import { AddIcon } from "@sanity/icons/Add";
58
58
 
59
59
  import { BoltIcon } from "@sanity/icons/Bolt";
60
60
 
61
- import { CheckmarkCircleIcon } from "@sanity/icons/CheckmarkCircle";
61
+ import { ChevronDownIcon } from "@sanity/icons/ChevronDown";
62
62
 
63
- import { DocumentIcon } from "@sanity/icons/Document";
63
+ import { CheckmarkCircleIcon } from "@sanity/icons/CheckmarkCircle";
64
64
 
65
65
  import { UlistIcon } from "@sanity/icons/Ulist";
66
66
 
@@ -87,20 +87,58 @@ function gdrLocality(id, contentResource) {
87
87
  };
88
88
  }
89
89
 
90
- function mappingsForDocType(mappings, docType) {
91
- return mappings.filter(m => m.docType === docType);
90
+ function assertUniqueWorkflowMappings(mappings) {
91
+ const keys = /* @__PURE__ */ new Set;
92
+ for (const mapping of mappings) {
93
+ const key = mappingKey(mapping);
94
+ if (keys.has(key)) throw new Error(`Duplicate workflow mapping for ${key}`);
95
+ keys.add(key);
96
+ }
97
+ }
98
+
99
+ function discoverWorkflowMappings(args) {
100
+ assertUniqueWorkflowMappings(args.overrides ?? []);
101
+ const discovered = latestDeployedDefinitions(args.definitions).flatMap(definition => {
102
+ if (!isStartableDefinition(definition)) return [];
103
+ const subject = (definition.fields ?? []).find(isSubjectEntry);
104
+ if (subject === void 0 || !isInputSourced(subject)) return [];
105
+ const source = {
106
+ fields: [ ...definition.fields ?? [] ]
107
+ };
108
+ return [ ...args.schemaContentTypes ].filter(docType => acceptsDocumentType(source, docType)).map(docType => ({
109
+ docType: docType,
110
+ definition: definition.name,
111
+ label: definition.title ?? definition.name
112
+ }));
113
+ }), byKey = new Map(discovered.map(mapping => [ mappingKey(mapping), mapping ]));
114
+ for (const override of args.overrides ?? []) {
115
+ const key = mappingKey(override);
116
+ byKey.set(key, override);
117
+ }
118
+ return [ ...byKey.values() ];
119
+ }
120
+
121
+ function mappingAutoStartMap(mappings) {
122
+ const byType = /* @__PURE__ */ Object.create(null);
123
+ for (const mapping of mappings) {
124
+ if (!mapping.autoStart) continue;
125
+ const definitions = byType[mapping.docType] ?? [];
126
+ byType[mapping.docType] = [ ...definitions, mapping.definition ];
127
+ }
128
+ return byType;
92
129
  }
93
130
 
94
- function mappingForDocType(mappings, docType) {
95
- return mappingsForDocType(mappings, docType)[0];
131
+ function mappingsForDocType(mappings, docType) {
132
+ return mappings.filter(m => m.docType === docType);
96
133
  }
97
134
 
98
135
  function mappingKey(mapping) {
99
136
  return `${mapping.docType}::${mapping.definition}`;
100
137
  }
101
138
 
102
- function workflowDocTypes(mappings) {
103
- return new Set(mappings.map(m => m.docType));
139
+ function documentSubjectEntry(definition) {
140
+ const fields = definition.fields ?? [];
141
+ return fields.find(isSubjectEntry) ?? fields.find(entry => entry.type === "doc.ref" && entry.name === "subject");
104
142
  }
105
143
 
106
144
  function mappingStartBlocks(args) {
@@ -234,7 +272,49 @@ function mappingIssues(args) {
234
272
  return issues;
235
273
  }
236
274
 
237
- const WORKFLOWS_VIEW_ID = "workflows", WORKFLOWS_TAB_PARAM = "workflowsTab", WORKFLOW_INTENT = "workflow";
275
+ const WORKFLOWS_VIEW_ID = "workflows", WORKFLOWS_TAB_PARAM = "workflowsTab";
276
+
277
+ function withoutWorkflowsTab(params) {
278
+ const {[WORKFLOWS_TAB_PARAM]: _previous, ...rest} = params ?? {};
279
+ return rest;
280
+ }
281
+
282
+ function workflowsTabCodec(tabs) {
283
+ const [defaultTab] = tabs;
284
+ return {
285
+ read: params => tabs.find(tab => tab === params?.[WORKFLOWS_TAB_PARAM]) ?? defaultTab,
286
+ write: (params, tab) => {
287
+ const rest = withoutWorkflowsTab(params);
288
+ return tab === defaultTab ? rest : {
289
+ ...rest,
290
+ [WORKFLOWS_TAB_PARAM]: tab
291
+ };
292
+ }
293
+ };
294
+ }
295
+
296
+ const WORKFLOWS_FOCUS_PARAM = "workflowsFocus";
297
+
298
+ function focusedViewParams(params, focusId) {
299
+ return {
300
+ ...withoutWorkflowsTab(params),
301
+ view: WORKFLOWS_VIEW_ID,
302
+ [WORKFLOWS_FOCUS_PARAM]: focusId
303
+ };
304
+ }
305
+
306
+ function focusedLanding(focus, instanceIds) {
307
+ if (!(focus === void 0 || !instanceIds.includes(focus))) return {
308
+ focusedId: focus,
309
+ instanceIds: instanceIds
310
+ };
311
+ }
312
+
313
+ function landingDefaultOpen(landing, instanceId) {
314
+ if (!(landing === void 0 || !landing.instanceIds.includes(instanceId))) return instanceId === landing.focusedId;
315
+ }
316
+
317
+ const WORKFLOW_INTENT = "workflow";
238
318
 
239
319
  function handlesWorkflowIntent(intent, params) {
240
320
  return workflowIntentState(intent, params) !== null;
@@ -307,6 +387,16 @@ function formatTimeAgo(value) {
307
387
  });
308
388
  }
309
389
 
390
+ function stampFace(verb, at) {
391
+ const ago = formatTimeAgo(at);
392
+ return ago === "" ? {
393
+ lead: `${verb} ${formatShortDateTime(at)}`
394
+ } : {
395
+ lead: `${verb} ${ago}`,
396
+ detail: formatShortDateTime(at)
397
+ };
398
+ }
399
+
310
400
  function formatDate(value) {
311
401
  if (!value) return "—";
312
402
  const d = parseDateFieldValue(value, "date") ?? new Date(value);
@@ -356,6 +446,10 @@ function hrefIfHttp(value) {
356
446
  }
357
447
  }
358
448
 
449
+ function unreadableDocsReport(docs) {
450
+ return docs.map(doc => `Unreadable workflow document\nID: ${doc.documentId}\nReason: ${doc.reason}\nDetail: ${doc.message}`).join(`\n\n`);
451
+ }
452
+
359
453
  function isButton(actionEval) {
360
454
  return actionRendering(actionEval) === "button";
361
455
  }
@@ -365,7 +459,7 @@ function stateByActivity(instance) {
365
459
  }
366
460
 
367
461
  function evaluationFreshness(args) {
368
- return args.evaluation ? args.committed.lastChangedAt > args.evaluation.instance.lastChangedAt ? "stale" : "current" : "pending";
462
+ return args.evaluation ? Date.parse(args.committed.lastChangedAt) > Date.parse(args.evaluation.instance.lastChangedAt) ? "stale" : "current" : "pending";
369
463
  }
370
464
 
371
465
  function isEvaluationCurrent(args) {
@@ -412,20 +506,44 @@ function useWorkflowContext() {
412
506
  }
413
507
 
414
508
  function useWorkflowsForDocument(rawId) {
415
- const {register: register, unregister: unregister, byDocument: byDocument} = useWorkflowContext(), docId = rawId ? getPublishedId(rawId) : void 0;
416
- return useEffect(() => {
509
+ return useDocumentWorkflowEntries(rawId, !0);
510
+ }
511
+
512
+ function useCommittedWorkflowsForDocument(rawId) {
513
+ return useDocumentWorkflowEntries(rawId, !1);
514
+ }
515
+
516
+ function useDocumentWorkflowEntries(rawId, evaluate) {
517
+ const {register: register, unregister: unregister, requestEvaluation: requestEvaluation, releaseEvaluation: releaseEvaluation, entries: entries} = useWorkflowContext(), docId = rawId ? getPublishedId(rawId) : void 0;
518
+ useEffect(() => {
417
519
  if (docId) return register(docId), () => unregister(docId);
418
- }, [ docId, register, unregister ]), {
520
+ }, [ docId, register, unregister ]);
521
+ const list = useSyncExternalStore(entries.subscribe, useCallback(() => docId ? entries.getDocument(docId) : EMPTY_ENTRIES, [ entries, docId ])), evaluationKey = evaluate ? list.map(entry => entry.instance._id).join("\0") : "";
522
+ return useEffect(() => {
523
+ if (!evaluate) return;
524
+ const ids = evaluationKey === "" ? [] : evaluationKey.split("\0");
525
+ for (const id of ids) requestEvaluation(id);
526
+ return () => {
527
+ for (const id of ids) releaseEvaluation(id);
528
+ };
529
+ }, [ evaluate, evaluationKey, requestEvaluation, releaseEvaluation ]), {
419
530
  docId: docId,
420
- entries: docId ? byDocument.get(docId) ?? EMPTY_ENTRIES : EMPTY_ENTRIES
531
+ entries: list
421
532
  };
422
533
  }
423
534
 
424
- function useWorkflowInstanceEntry(instanceId) {
425
- const {byInstance: byInstance, requestInstance: requestInstance, releaseInstance: releaseInstance} = useWorkflowContext();
426
- if (useEffect(() => {
535
+ function useHeldWorkflowEntry(instanceId) {
536
+ const {entries: entries} = useWorkflowContext();
537
+ return useSyncExternalStore(entries.subscribe, useCallback(() => instanceId ? entries.getInstance(instanceId) : void 0, [ entries, instanceId ]));
538
+ }
539
+
540
+ function useWorkflowInstanceEntry(instanceId, options = {}) {
541
+ const {requestInstance: requestInstance, releaseInstance: releaseInstance, requestEvaluation: requestEvaluation, releaseEvaluation: releaseEvaluation} = useWorkflowContext(), evaluate = options.evaluate ?? !0;
542
+ return useEffect(() => {
427
543
  if (instanceId) return requestInstance(instanceId), () => releaseInstance(instanceId);
428
- }, [ instanceId, requestInstance, releaseInstance ]), !!instanceId) return byInstance.get(instanceId);
544
+ }, [ instanceId, requestInstance, releaseInstance ]), useEffect(() => {
545
+ if (!(!instanceId || !evaluate)) return requestEvaluation(instanceId), () => releaseEvaluation(instanceId);
546
+ }, [ instanceId, evaluate, requestEvaluation, releaseEvaluation ]), useHeldWorkflowEntry(instanceId);
429
547
  }
430
548
 
431
549
  const isTodoDone = item => item.status === "done", toggledTodoStatus = item => isTodoDone(item) ? "open" : "done";
@@ -694,14 +812,33 @@ function changesOwnStatus(action, activityName) {
694
812
  return ownStatusOps(action, activityName).length > 0;
695
813
  }
696
814
 
697
- function actionButtonTone(args) {
698
- const status = actionAdvanceStatus(args.action, args.activityName);
699
- return status === void 0 ? {
700
- mode: "ghost",
701
- tone: "default"
702
- } : {
703
- mode: "default",
704
- tone: status === "failed" ? "critical" : "default"
815
+ function concludesHere(actionEval, activityName) {
816
+ return changesOwnStatus(actionEval.action, activityName) || actionEval.firing?.exitsStage === !0;
817
+ }
818
+
819
+ const SEMANTIC_FACE = {
820
+ "decision.accept": {
821
+ tone: "positive",
822
+ icon: CheckmarkIcon
823
+ },
824
+ "decision.decline": {
825
+ tone: "caution",
826
+ icon: CloseIcon
827
+ }
828
+ };
829
+
830
+ function actionButtonFace(args) {
831
+ const mode = concludesHere(args.actionEval, args.activityName) ? "default" : "ghost";
832
+ if (actionAdvanceStatus(args.actionEval.action, args.activityName) === "failed") return {
833
+ mode: mode,
834
+ tone: "critical",
835
+ icon: void 0
836
+ };
837
+ const [semantic] = args.actionEval.semantics ?? [], face = semantic === void 0 ? void 0 : SEMANTIC_FACE[semantic];
838
+ return {
839
+ mode: mode,
840
+ tone: face?.tone ?? "default",
841
+ icon: face?.icon
705
842
  };
706
843
  }
707
844
 
@@ -763,6 +900,11 @@ function actionRowKind(actionEval) {
763
900
  };
764
901
  }
765
902
 
903
+ function actionTriggerLabel(actionEval) {
904
+ const label = actionLabel(actionEval.action);
905
+ return actionRowKind(actionEval).kind === "params-form" ? `${label}…` : label;
906
+ }
907
+
766
908
  function tickActionNames(args) {
767
909
  const arrayFieldNames = new Set(args.state.flatMap(f => f._type === "array" ? [ f.name ] : [])), names = /* @__PURE__ */ new Set;
768
910
  for (const a of args.actions) for (const o of tickOpsOf(a.action)) arrayFieldNames.has(o.target.field) && tickedItemKey(o.where) !== void 0 && names.add(a.action.name);
@@ -792,8 +934,8 @@ function deriveActivityPresentation(args) {
792
934
  settled: settled,
793
935
  triggeredActions: settled ? [] : args.activityEval.actions.filter(a => a.triggered === !0),
794
936
  manualTarget: kind === "manual" ? manualLink(activity) : void 0,
795
- actions: buttonActions.filter(a => !changesOwnStatus(a.action, activityName)),
796
- terminalActions: buttonActions.filter(a => changesOwnStatus(a.action, activityName))
937
+ actions: buttonActions.filter(a => !concludesHere(a, activityName)),
938
+ terminalActions: buttonActions.filter(a => concludesHere(a, activityName))
797
939
  };
798
940
  }
799
941
 
@@ -806,15 +948,78 @@ function rowTerminalActions(args) {
806
948
  }).terminalActions;
807
949
  }
808
950
 
809
- function rowAssignField(args) {
810
- return (groupEditableByActivity(args.editableFields ?? []).get(args.activityName) ?? []).find(f => f.type === "assignees" && f.editable);
951
+ function isDeclaredEditable(field) {
952
+ return field.editable !== void 0;
953
+ }
954
+
955
+ function rowControlState(args) {
956
+ const {kinds: kinds, activity: activity, status: status, editableFields: editableFields} = args;
957
+ if (!(activity.fields ?? []).some(field => kinds.includes(field.type) && isDeclaredEditable(field))) return {
958
+ kind: "none"
959
+ };
960
+ if (editableFields !== void 0) {
961
+ const field = (groupEditableByActivity(editableFields).get(activity.name) ?? []).find(f => kinds.includes(f.type));
962
+ return field?.editable ? {
963
+ kind: "editable",
964
+ field: field
965
+ } : field?.disabledReason ? {
966
+ kind: "closed",
967
+ reason: describeEditDisabledReason(field.disabledReason)
968
+ } : {
969
+ kind: "closed",
970
+ reason: describeEditDisabledReason({
971
+ kind: "not-editable"
972
+ })
973
+ };
974
+ }
975
+ return isTerminalActivityStatus(status) ? {
976
+ kind: "closed",
977
+ reason: describeEditDisabledReason({
978
+ kind: "edit-window-closed",
979
+ detail: "activity settled"
980
+ })
981
+ } : {
982
+ kind: "loading"
983
+ };
984
+ }
985
+
986
+ function rowAssignControlState(args) {
987
+ return rowControlState({
988
+ kinds: [ "assignees" ],
989
+ ...args
990
+ });
991
+ }
992
+
993
+ function rowDateControlState(args) {
994
+ return rowControlState({
995
+ kinds: [ "date", "datetime" ],
996
+ ...args
997
+ });
998
+ }
999
+
1000
+ function committedRowFace(row) {
1001
+ return {
1002
+ activityName: row.activityName,
1003
+ title: row.title,
1004
+ status: row.status,
1005
+ automated: !1,
1006
+ blocked: !1
1007
+ };
811
1008
  }
812
1009
 
813
1010
  function activityRowProps(args) {
814
- const {activityEval: activityEval, activityState: activityState, editableFields: editableFields} = args, activityName = activityEval.activity.name, state = activityState.get(activityName) ?? [];
1011
+ const {activityEval: activityEval, activityState: activityState, editableFields: editableFields} = args, state = activityState.get(activityEval.activity.name) ?? [];
815
1012
  return {
816
- assignField: rowAssignField({
817
- activityName: activityName,
1013
+ face: {
1014
+ activityName: activityEval.activity.name,
1015
+ title: activityLabel(activityEval.activity),
1016
+ status: activityEval.status,
1017
+ automated: showsAutomatedPill(activityEval),
1018
+ blocked: showsBlockedTag(activityEval)
1019
+ },
1020
+ assignState: rowAssignControlState({
1021
+ activity: activityEval.activity,
1022
+ status: activityEval.status,
818
1023
  editableFields: editableFields
819
1024
  }),
820
1025
  assigneeIds: assigneeUserIdsOf(state),
@@ -826,6 +1031,15 @@ function activityRowProps(args) {
826
1031
  };
827
1032
  }
828
1033
 
1034
+ function dateControlKind(state) {
1035
+ if (state.kind === "editable" && (state.field.type === "date" || state.field.type === "datetime")) return state.field.type;
1036
+ }
1037
+
1038
+ function dateControlValue(args) {
1039
+ const {state: state, dates: dates} = args;
1040
+ return state.kind === "editable" ? typeof state.field.value == "string" && state.field.value !== "" ? state.field.value : void 0 : dates.find(value => parseStoredDateValue(value) !== void 0);
1041
+ }
1042
+
829
1043
  function activityStateOf(instance, activityName) {
830
1044
  return findCurrentActivityEntry(instance, activityName)?.fields ?? [];
831
1045
  }
@@ -852,10 +1066,17 @@ function isEmptyValue(value) {
852
1066
  return value == null || value === "" || Array.isArray(value) && value.length === 0;
853
1067
  }
854
1068
 
1069
+ function effectiveValidation(kind, validation) {
1070
+ return kind !== "progress" ? validation : {
1071
+ min: validation?.min ?? 0,
1072
+ max: validation?.max ?? 100
1073
+ };
1074
+ }
1075
+
855
1076
  function scalarValidationIssue(args) {
856
1077
  const issues = scalarValidationIssues({
857
1078
  entryType: args.kind,
858
- validation: args.validation,
1079
+ validation: effectiveValidation(args.kind, args.validation),
859
1080
  value: args.value
860
1081
  });
861
1082
  return issues === void 0 ? void 0 : `${args.label}: ${issues.join("; ")}`;
@@ -872,10 +1093,19 @@ function personActor(user) {
872
1093
  };
873
1094
  }
874
1095
 
1096
+ const memberIndexes = /* @__PURE__ */ new WeakMap;
1097
+
1098
+ function memberIndex(members) {
1099
+ const cached = memberIndexes.get(members);
1100
+ if (cached) return cached;
1101
+ const created = new Map(members.map(member => [ member.id, member ]));
1102
+ return memberIndexes.set(members, created), created;
1103
+ }
1104
+
875
1105
  const selfUserFor = (id, me) => me != null && me.id === id ? me : void 0;
876
1106
 
877
1107
  function userDisplayFor(args) {
878
- const member = args.members.find(m => m.id === args.id), self = selfUserFor(args.id, args.me);
1108
+ const member = memberIndex(args.members).get(args.id), self = selfUserFor(args.id, args.me);
879
1109
  return {
880
1110
  id: args.id,
881
1111
  name: member?.displayName ?? self?.name ?? args.id,
@@ -885,19 +1115,20 @@ function userDisplayFor(args) {
885
1115
 
886
1116
  function useUserDisplays(ids) {
887
1117
  const {members: members} = useProjectMembers(), me = useCurrentUser();
888
- return ids.map(id => userDisplayFor({
1118
+ return useMemo(() => ids.map(id => userDisplayFor({
889
1119
  id: id,
890
1120
  members: members,
891
1121
  me: me
892
- }));
1122
+ })), [ ids, me, members ]);
893
1123
  }
894
1124
 
895
1125
  function useUserDisplay(id) {
896
- const [display] = useUserDisplays([ id ]);
897
- return display ?? {
898
- name: id,
899
- imageUrl: void 0
900
- };
1126
+ const {members: members} = useProjectMembers(), me = useCurrentUser();
1127
+ return useMemo(() => userDisplayFor({
1128
+ id: id,
1129
+ members: members,
1130
+ me: me
1131
+ }), [ id, me, members ]);
901
1132
  }
902
1133
 
903
1134
  function useAssignmentIdentity() {
@@ -908,8 +1139,12 @@ function useAssignmentIdentity() {
908
1139
  };
909
1140
  }
910
1141
 
1142
+ const projectMembersByUsers = /* @__PURE__ */ new WeakMap;
1143
+
911
1144
  function projectMembersFrom(users) {
912
- return users.map(({membership: membership, profile: profile}) => ({
1145
+ const cached = projectMembersByUsers.get(users);
1146
+ if (cached) return cached;
1147
+ const members = users.map(({membership: membership, profile: profile}) => ({
913
1148
  id: membership.id,
914
1149
  displayName: profile?.displayName ?? membership.id,
915
1150
  ...profile?.email ? {
@@ -920,6 +1155,7 @@ function projectMembersFrom(users) {
920
1155
  } : {},
921
1156
  roles: (membership.roles ?? []).map(role => role.name)
922
1157
  }));
1158
+ return projectMembersByUsers.set(users, members), members;
923
1159
  }
924
1160
 
925
1161
  function useProjectMembers() {
@@ -1063,8 +1299,19 @@ function HintBody(props) {
1063
1299
  });
1064
1300
  }
1065
1301
 
1302
+ function textInputFacet(kind) {
1303
+ return kind === "number" || kind === "progress" ? {
1304
+ type: "number"
1305
+ } : kind === "url" ? {
1306
+ type: "url"
1307
+ } : {};
1308
+ }
1309
+
1066
1310
  function scalarInputConstraints(kind, validation) {
1067
- return validation === void 0 ? {} : kind === "number" ? {
1311
+ return kind === "progress" ? {
1312
+ min: validation?.min ?? 0,
1313
+ max: validation?.max ?? 100
1314
+ } : validation === void 0 ? {} : kind === "number" ? {
1068
1315
  min: validation.min,
1069
1316
  max: validation.max
1070
1317
  } : kind === "string" || kind === "text" ? {
@@ -1177,7 +1424,7 @@ function ChoiceSelect({options: options, value: value, onChange: onChange, readO
1177
1424
  });
1178
1425
  }
1179
1426
 
1180
- const SCALAR_INPUT_KINDS = [ "string", "text", "url", "number", "date", "dateTime", "datetime", "boolean" ], SCALAR_INPUT_KIND_SET = new Set(SCALAR_INPUT_KINDS);
1427
+ const SCALAR_INPUT_KINDS = [ "string", "text", "url", "number", "progress", "date", "dateTime", "datetime", "boolean" ], SCALAR_INPUT_KIND_SET = new Set(SCALAR_INPUT_KINDS);
1181
1428
 
1182
1429
  function isScalarInputKind(kind) {
1183
1430
  return SCALAR_INPUT_KIND_SET.has(kind);
@@ -1188,29 +1435,19 @@ function textValueOf(value) {
1188
1435
  }
1189
1436
 
1190
1437
  function ScalarInput({kind: kind, value: value, onChange: onChange, options: options, validation: validation}) {
1191
- if (options !== void 0) /* @__PURE__ */
1192
- return jsx(ChoiceSelect, {
1438
+ return options !== void 0 ? /* @__PURE__ */ jsx(ChoiceSelect, {
1193
1439
  onChange: onChange,
1194
1440
  options: options,
1195
1441
  value: value
1196
- });
1197
- if (kind === "boolean") /* @__PURE__ */
1198
- return jsx(Checkbox, {
1442
+ }) : kind === "boolean" ? /* @__PURE__ */ jsx(Checkbox, {
1199
1443
  checked: value === !0,
1200
1444
  onChange: e => onChange(e.currentTarget.checked)
1201
- });
1202
- if (kind === "date" || kind === "dateTime" || kind === "datetime") /* @__PURE__ */
1203
- return jsx(DateFieldInput, {
1445
+ }) : kind === "date" || kind === "dateTime" || kind === "datetime" ? /* @__PURE__ */ jsx(DateFieldInput, {
1204
1446
  kind: kind === "date" ? "date" : "datetime",
1205
1447
  onChange: next => onChange(next ?? ""),
1206
1448
  value: value
1207
- });
1208
- const inputType = kind === "url" || kind === "number" ? kind : void 0;
1209
- /* @__PURE__ */
1210
- return jsx(TextInput, {
1211
- ...inputType ? {
1212
- type: inputType
1213
- } : {},
1449
+ }) : /* @__PURE__ */ jsx(TextInput, {
1450
+ ...textInputFacet(kind),
1214
1451
  ...scalarInputConstraints(kind, validation),
1215
1452
  fontSize: 1,
1216
1453
  onChange: e => onChange(e.currentTarget.value),
@@ -1298,25 +1535,26 @@ function MemberPickerButton({label: label = "Select member…", onChange: onChan
1298
1535
  });
1299
1536
  }
1300
1537
 
1301
- function StubPreview({id: id, layout: layout, schemaType: schemaType}) {
1538
+ function useStubValuePreview(id, schemaType) {
1302
1539
  const value = useMemo(() => ({
1303
1540
  _id: id,
1304
1541
  _type: schemaType.name
1305
- }), [ id, schemaType.name ]);
1306
- return useValuePreview({
1542
+ }), [ id, schemaType.name ]), preview = useValuePreview({
1307
1543
  schemaType: schemaType,
1308
1544
  value: value
1309
- })?.isLoading ? layout === "inline" ? /* @__PURE__ */ jsx(TextSkeleton, {
1310
- animated: !0,
1311
- radius: 1,
1312
- size: 1,
1313
- style: {
1314
- width: 96
1315
- }
1316
- }) : /* @__PURE__ */ jsx(SanityDefaultPreview, {
1545
+ });
1546
+ return {
1547
+ value: value,
1548
+ preview: preview
1549
+ };
1550
+ }
1551
+
1552
+ function StubPreview({id: id, schemaType: schemaType}) {
1553
+ const {value: value, preview: preview} = useStubValuePreview(id, schemaType);
1554
+ return preview.isLoading ? /* @__PURE__ */ jsx(SanityDefaultPreview, {
1317
1555
  isPlaceholder: !0
1318
1556
  }) : /* @__PURE__ */ jsx(Preview, {
1319
- layout: layout,
1557
+ layout: "default",
1320
1558
  schemaType: schemaType,
1321
1559
  skipVisibilityCheck: !0,
1322
1560
  value: value
@@ -1359,7 +1597,7 @@ function DocPicker({value: value, onChange: onChange, types: types, readOnly: re
1359
1597
  children: /* @__PURE__ */ jsx(Box, {
1360
1598
  flex: 1,
1361
1599
  children: /* @__PURE__ */ jsx(PreviewOrId, {
1362
- id: extractDocumentId(value.id),
1600
+ id: toBareId(value.id),
1363
1601
  type: value.type
1364
1602
  })
1365
1603
  })
@@ -1426,7 +1664,6 @@ function PreviewOrId({id: id, type: type, fallbackTitle: fallbackTitle}) {
1426
1664
  const schemaType = useSchema().get(type);
1427
1665
  return schemaType ? /* @__PURE__ */ jsx(StubPreview, {
1428
1666
  id: id,
1429
- layout: "default",
1430
1667
  schemaType: schemaType
1431
1668
  }) : /* @__PURE__ */ jsxs(Flex, {
1432
1669
  align: "center",
@@ -1456,7 +1693,7 @@ function DocRefsInput({value: value, onChange: onChange, types: types}) {
1456
1693
  /* @__PURE__ */ jsx(Box, {
1457
1694
  flex: 1,
1458
1695
  children: /* @__PURE__ */ jsx(PreviewOrId, {
1459
- id: extractDocumentId(ref.id),
1696
+ id: toBareId(ref.id),
1460
1697
  type: ref.type
1461
1698
  })
1462
1699
  }),
@@ -1560,12 +1797,30 @@ function useClosableToast() {
1560
1797
  }), [ toast ]);
1561
1798
  }
1562
1799
 
1800
+ const ActionClusterContext = createContext(void 0), ActionClusterProvider = ActionClusterContext.Provider;
1801
+
1802
+ function useActionClusterState() {
1803
+ const [pending, setPending] = useState(!1);
1804
+ return useMemo(() => ({
1805
+ pending: pending,
1806
+ onFireChange: setPending
1807
+ }), [ pending ]);
1808
+ }
1809
+
1810
+ function useActionClusterLock() {
1811
+ return useContext(ActionClusterContext);
1812
+ }
1813
+
1814
+ function useActionClusterPending() {
1815
+ return useActionClusterLock()?.pending === !0;
1816
+ }
1817
+
1563
1818
  function useFireAction(args) {
1564
- const {instanceId: instanceId, activity: activity, action: action, label: label} = args, {fireActionFor: fireActionFor} = useWorkflowContext(), toast = useClosableToast(), [pending, setPending] = useState(!1);
1819
+ const {instanceId: instanceId, activity: activity, action: action, label: label} = args, {fireActionFor: fireActionFor} = useWorkflowContext(), toast = useClosableToast(), cluster = useActionClusterLock(), [localPending, setLocalPending] = useState(!1), pending = localPending || cluster?.pending === !0;
1565
1820
  return {
1566
1821
  fire: useCallback(async params => {
1567
1822
  if (pending) return !1;
1568
- setPending(!0);
1823
+ setLocalPending(!0), cluster?.onFireChange(!0);
1569
1824
  try {
1570
1825
  return await fireActionFor(instanceId, {
1571
1826
  activity: activity,
@@ -1585,17 +1840,30 @@ function useFireAction(args) {
1585
1840
  description: describeError(err)
1586
1841
  }), !1;
1587
1842
  } finally {
1588
- setPending(!1);
1843
+ setLocalPending(!1), cluster?.onFireChange(!1);
1589
1844
  }
1590
- }, [ fireActionFor, instanceId, activity, action, label, pending, toast ]),
1845
+ }, [ fireActionFor, instanceId, activity, action, label, pending, toast, cluster ]),
1591
1846
  pending: pending
1592
1847
  };
1593
1848
  }
1594
1849
 
1850
+ const ACTION_ICON_GAP = 2;
1851
+
1852
+ function actionIconProps(icon) {
1853
+ return icon ? {
1854
+ gap: ACTION_ICON_GAP,
1855
+ icon: icon
1856
+ } : {};
1857
+ }
1858
+
1595
1859
  function stopRowMouseDown(e) {
1596
1860
  e.preventDefault(), e.stopPropagation();
1597
1861
  }
1598
1862
 
1863
+ function stopRowClick(e) {
1864
+ e.stopPropagation();
1865
+ }
1866
+
1599
1867
  function spanTriggerProps(chrome) {
1600
1868
  return chrome?.inButtonCard === !0 ? {
1601
1869
  as: "span",
@@ -1615,7 +1883,7 @@ function RowClickShield({active: active, children: children}) {
1615
1883
  });
1616
1884
  }
1617
1885
 
1618
- function FireButton({instanceId: instanceId, activity: activity, action: action, label: label, mode: mode = "default", chrome: chrome, tone: tone = "default"}) {
1886
+ function FireButton({instanceId: instanceId, activity: activity, action: action, label: label, mode: mode = "default", chrome: chrome, tone: tone = "default", icon: icon}) {
1619
1887
  const {fire: fire, pending: pending} = useFireAction({
1620
1888
  instanceId: instanceId,
1621
1889
  activity: activity,
@@ -1627,11 +1895,11 @@ function FireButton({instanceId: instanceId, activity: activity, action: action,
1627
1895
  /* @__PURE__ */
1628
1896
  return jsx(Button, {
1629
1897
  fontSize: 1,
1898
+ ...actionIconProps(icon),
1630
1899
  loading: pending,
1631
1900
  mode: chrome?.mode ?? mode,
1632
1901
  onClick: handleClick,
1633
1902
  padding: 2,
1634
- style: chrome?.style,
1635
1903
  text: label,
1636
1904
  tone: tone,
1637
1905
  ...spanTriggerProps(chrome),
@@ -1645,6 +1913,7 @@ function ActivityActionRow({actionEval: actionEval, instanceId: instanceId, acti
1645
1913
  const rowKind = actionRowKind(actionEval);
1646
1914
  return rowKind.kind === "disabled" ? /* @__PURE__ */ jsx(DisabledActionButton, {
1647
1915
  actionEval: actionEval,
1916
+ activity: activity,
1648
1917
  chrome: chrome
1649
1918
  }) : rowKind.kind === "plain" ? /* @__PURE__ */ jsx(PlainActionButton, {
1650
1919
  actionEval: actionEval,
@@ -1659,14 +1928,18 @@ function ActivityActionRow({actionEval: actionEval, instanceId: instanceId, acti
1659
1928
  });
1660
1929
  }
1661
1930
 
1662
- function DisabledActionButton({actionEval: actionEval, chrome: chrome}) {
1663
- const label = actionLabel(actionEval.action), button = /* @__PURE__ */ jsx(Button, {
1931
+ function DisabledActionButton({actionEval: actionEval, activity: activity, chrome: chrome}) {
1932
+ const label = actionTriggerLabel(actionEval), {tone: tone, icon: icon} = actionButtonFace({
1933
+ actionEval: actionEval,
1934
+ activityName: activity
1935
+ }), button = /* @__PURE__ */ jsx(Button, {
1664
1936
  fontSize: 1,
1937
+ ...actionIconProps(icon),
1665
1938
  mode: chrome?.mode ?? "ghost",
1666
1939
  onClick: e => e.stopPropagation(),
1667
1940
  padding: 2,
1668
- style: chrome?.style,
1669
1941
  text: label,
1942
+ tone: tone,
1670
1943
  ...spanTriggerProps(chrome),
1671
1944
  disabled: !0
1672
1945
  });
@@ -1677,11 +1950,12 @@ function DisabledActionButton({actionEval: actionEval, chrome: chrome}) {
1677
1950
  }
1678
1951
 
1679
1952
  function PlainActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, chrome: chrome}) {
1680
- const {action: action} = actionEval, label = actionLabel(action), btn = actionButtonTone({
1681
- action: action,
1953
+ const {action: action} = actionEval, label = actionLabel(action), btn = actionButtonFace({
1954
+ actionEval: actionEval,
1682
1955
  activityName: activity
1683
1956
  }), button = /* @__PURE__ */ jsx(FireButton, {
1684
1957
  action: action.name,
1958
+ icon: btn.icon,
1685
1959
  instanceId: instanceId,
1686
1960
  label: label,
1687
1961
  mode: btn.mode,
@@ -1756,8 +2030,8 @@ function ParamsActionDialog({actionEval: actionEval, instanceId: instanceId, act
1756
2030
  }
1757
2031
 
1758
2032
  function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, activity: activity, chrome: chrome}) {
1759
- const {action: action} = actionEval, label = actionLabel(action), [open, setOpen] = useState(!1), btn = actionButtonTone({
1760
- action: action,
2033
+ const [open, setOpen] = useState(!1), btn = actionButtonFace({
2034
+ actionEval: actionEval,
1761
2035
  activityName: activity
1762
2036
  });
1763
2037
  /* @__PURE__ */
@@ -1765,16 +2039,14 @@ function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, act
1765
2039
  children: [
1766
2040
  /* @__PURE__ */ jsx(Button, {
1767
2041
  fontSize: 1,
2042
+ ...actionIconProps(btn.icon),
1768
2043
  mode: chrome?.mode ?? btn.mode,
1769
2044
  onClick: e => {
1770
2045
  chrome?.inButtonCard === !0 && e.stopPropagation(), setOpen(!0);
1771
2046
  },
1772
2047
  padding: 2,
1773
- text: `${label}…`,
2048
+ text: actionTriggerLabel(actionEval),
1774
2049
  tone: btn.tone,
1775
- ...chrome?.style ? {
1776
- style: chrome.style
1777
- } : {},
1778
2050
  ...spanTriggerProps(chrome)
1779
2051
  }), open ? /* @__PURE__ */ jsx(RowClickShield, {
1780
2052
  active: chrome?.inButtonCard === !0,
@@ -1788,217 +2060,49 @@ function ParamsActionButton({actionEval: actionEval, instanceId: instanceId, act
1788
2060
  });
1789
2061
  }
1790
2062
 
1791
- function HintedMenuButton({hint: hint, wrapMenuButton: wrapMenuButton, ...menuButtonProps}) {
1792
- const [open, setOpen] = useState(!1), wrap = wrapMenuButton ?? (node => node);
1793
- return hint === void 0 ? wrap(/* @__PURE__ */ jsx(MenuButton, {
1794
- ...menuButtonProps
1795
- })) : /* @__PURE__ */ jsx(HoverHint, {
1796
- disabled: open,
1797
- text: hint,
1798
- children: wrap(
1799
- /* @__PURE__ */ jsx(MenuButton, {
1800
- ...menuButtonProps,
1801
- onClose: () => setOpen(!1),
1802
- onOpen: () => setOpen(!0)
1803
- }))
1804
- });
1805
- }
2063
+ const DIM = {
2064
+ opacity: .6
2065
+ };
1806
2066
 
1807
- function OverflowMenuItem({actionEval: actionEval, instanceId: instanceId, activity: activity, onCollectParams: onCollectParams}) {
1808
- const rowKind = actionRowKind(actionEval), label = actionLabel(actionEval.action), {fire: fire, pending: pending} = useFireAction({
1809
- instanceId: instanceId,
1810
- activity: activity,
1811
- action: actionEval.action.name,
1812
- label: label
1813
- });
1814
- return rowKind.kind === "disabled" ? /* @__PURE__ */ jsx(MenuItem, {
1815
- disabled: !0,
1816
- text: label
1817
- }) : rowKind.kind === "plain" ? /* @__PURE__ */ jsx(MenuItem, {
1818
- disabled: pending,
1819
- onClick: () => {
1820
- fire();
1821
- },
1822
- text: label
1823
- }) : /* @__PURE__ */ jsx(MenuItem, {
1824
- onClick: onCollectParams,
1825
- text: `${label}…`
2067
+ function Breadcrumb({segments: segments, emphasizeCurrent: emphasizeCurrent = !1}) {
2068
+ /* @__PURE__ */
2069
+ return jsx(Fragment, {
2070
+ children: segments.map((segment, i) => /* @__PURE__ */ jsxs(Fragment$1, {
2071
+ children: [ i > 0 ? /* @__PURE__ */ jsx(Separator, {}) : null,
2072
+ /* @__PURE__ */ jsx("span", {
2073
+ style: emphasizeCurrent && i < segments.length - 1 ? DIM : void 0,
2074
+ children: segment
2075
+ }) ]
2076
+ }, `${i}-${segment}`))
1826
2077
  });
1827
2078
  }
1828
2079
 
1829
- function rowShield(menuButton) {
2080
+ function Separator() {
1830
2081
  /* @__PURE__ */
1831
2082
  return jsx("span", {
1832
- onClick: e => e.stopPropagation(),
1833
- onMouseDown: stopRowMouseDown,
2083
+ "aria-hidden": !0,
1834
2084
  style: {
1835
- display: "contents"
2085
+ ...DIM,
2086
+ alignItems: "center",
2087
+ display: "inline-flex",
2088
+ height: "0.7em",
2089
+ margin: "0 0.25em",
2090
+ overflow: "hidden"
1836
2091
  },
1837
- children: menuButton
2092
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
1838
2093
  });
1839
2094
  }
1840
2095
 
1841
- function ActionOverflowMenu({actions: actions, instanceId: instanceId, activity: activity, tone: tone, chrome: chrome}) {
1842
- const [paramsAction, setParamsAction] = useState(void 0), menuId = useId(), inButtonCard = chrome?.inButtonCard === !0;
1843
- /* @__PURE__ */
1844
- return jsxs(Fragment, {
1845
- children: [
1846
- /* @__PURE__ */ jsx(HintedMenuButton, {
1847
- button: /* @__PURE__ */ jsx(Button, {
1848
- "aria-label": "More options",
1849
- fontSize: 1,
1850
- icon: ChevronDownIcon,
1851
- padding: 2,
1852
- tone: tone,
1853
- ...chrome?.mode ? {
1854
- mode: chrome.mode
1855
- } : {},
1856
- ...chrome?.style ? {
1857
- style: chrome.style
1858
- } : {},
1859
- ...inButtonCard ? {
1860
- as: "span"
1861
- } : {}
1862
- }),
1863
- hint: "More options",
1864
- id: `workflow-action-overflow-${menuId}`,
1865
- menu: /* @__PURE__ */ jsx(Menu, {
1866
- children: actions.map(a => /* @__PURE__ */ jsx(OverflowMenuItem, {
1867
- actionEval: a,
1868
- activity: activity,
1869
- instanceId: instanceId,
1870
- onCollectParams: () => setParamsAction(a)
1871
- }, a.action.name))
1872
- }),
1873
- popover: {
1874
- placement: "top-end",
1875
- portal: !0
1876
- },
1877
- ...inButtonCard ? {
1878
- wrapMenuButton: rowShield
1879
- } : {}
1880
- }), paramsAction ? /* @__PURE__ */ jsx(RowClickShield, {
1881
- active: inButtonCard,
1882
- children: /* @__PURE__ */ jsx(ParamsActionDialog, {
1883
- actionEval: paramsAction,
1884
- activity: activity,
1885
- instanceId: instanceId,
1886
- onClose: () => setParamsAction(void 0)
1887
- })
1888
- }) : null ]
1889
- });
2096
+ function stageTitle(definition, stageName) {
2097
+ return definition?.stages.find(stage => stage.name === stageName)?.title ?? stageName;
1890
2098
  }
1891
2099
 
1892
- const PRIMARY_SEGMENT = {
1893
- borderBottomRightRadius: 0,
1894
- borderTopRightRadius: 0
1895
- }, MENU_SEGMENT = {
1896
- borderBottomLeftRadius: 0,
1897
- borderTopLeftRadius: 0
1898
- };
1899
-
1900
- function TerminalActions({actions: actions, activity: activity, instanceId: instanceId, mode: mode = "default", inButtonCard: inButtonCard = !1}) {
1901
- const {button: button, color: color} = useTheme_v2(), [primary, ...overflow] = actions;
1902
- if (!primary) return null;
1903
- const ghost = mode === "ghost", embed = inButtonCard ? {
1904
- inButtonCard: !0
1905
- } : {};
1906
- if (overflow.length === 0) {
1907
- const chrome = {
1908
- ...ghost ? {
1909
- mode: "ghost"
1910
- } : {},
1911
- ...embed
1912
- };
1913
- /* @__PURE__ */
1914
- return jsx(ActivityActionRow, {
1915
- actionEval: primary,
1916
- activity: activity,
1917
- chrome: chrome,
1918
- instanceId: instanceId
1919
- });
1920
- }
1921
- const {tone: tone} = actionButtonTone({
1922
- action: primary.action,
1923
- activityName: activity
1924
- }), segment = ghost ? {
1925
- mode: "ghost"
1926
- } : {};
1927
- /* @__PURE__ */
1928
- return jsxs(Flex, {
1929
- children: [
1930
- /* @__PURE__ */ jsx(ActivityActionRow, {
1931
- actionEval: primary,
1932
- activity: activity,
1933
- chrome: {
1934
- ...segment,
1935
- style: PRIMARY_SEGMENT,
1936
- ...embed
1937
- },
1938
- instanceId: instanceId
1939
- }), ghost ? null : /* @__PURE__ */ jsx(Box, {
1940
- flex: "none",
1941
- style: {
1942
- background: color.bg,
1943
- width: 1
1944
- }
1945
- }),
1946
- /* @__PURE__ */ jsx(ActionOverflowMenu, {
1947
- actions: overflow,
1948
- activity: activity,
1949
- chrome: {
1950
- ...segment,
1951
- style: ghost ? {
1952
- ...MENU_SEGMENT,
1953
- marginLeft: -button.border.width
1954
- } : MENU_SEGMENT,
1955
- ...embed
1956
- },
1957
- instanceId: instanceId,
1958
- tone: tone
1959
- }) ]
1960
- });
1961
- }
1962
-
1963
- const DIM = {
1964
- opacity: .6
1965
- };
1966
-
1967
- function Breadcrumb({segments: segments, emphasizeCurrent: emphasizeCurrent = !1}) {
1968
- /* @__PURE__ */
1969
- return jsx(Fragment, {
1970
- children: segments.map((segment, i) => /* @__PURE__ */ jsxs(Fragment$1, {
1971
- children: [ i > 0 ? /* @__PURE__ */ jsx(Separator, {}) : null,
1972
- /* @__PURE__ */ jsx("span", {
1973
- style: emphasizeCurrent && i < segments.length - 1 ? DIM : void 0,
1974
- children: segment
1975
- }) ]
1976
- }, `${i}-${segment}`))
1977
- });
1978
- }
1979
-
1980
- function Separator() {
1981
- /* @__PURE__ */
1982
- return jsx("span", {
1983
- "aria-hidden": !0,
1984
- style: {
1985
- ...DIM,
1986
- alignItems: "center",
1987
- display: "inline-flex",
1988
- height: "0.7em",
1989
- margin: "0 0.25em",
1990
- overflow: "hidden"
1991
- },
1992
- children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
1993
- });
1994
- }
1995
-
1996
- function stageTitle(definition, stageName) {
1997
- return definition?.stages.find(stage => stage.name === stageName)?.title ?? stageName;
1998
- }
2100
+ function findActivityNode(args) {
2101
+ return args.definition?.stages.find(s => s.name === args.stageName)?.activities?.find(a => a.name === args.activityName);
2102
+ }
1999
2103
 
2000
2104
  function findActivity(args) {
2001
- const activity = args.definition?.stages.find(s => s.name === args.stageName)?.activities?.find(a => a.name === args.activityName);
2105
+ const activity = findActivityNode(args);
2002
2106
  return {
2003
2107
  title: activity?.title ?? args.activityName,
2004
2108
  description: activity?.description
@@ -2132,14 +2236,15 @@ function FieldInput$1({kind: kind, value: value, onChange: onChange, onEnter: on
2132
2236
  });
2133
2237
 
2134
2238
  case "number":
2239
+ case "progress":
2135
2240
  /* @__PURE__ */
2136
2241
  return jsx(TextInput, {
2242
+ ...textInputFacet(kind),
2243
+ ...scalarInputConstraints(kind, validation),
2137
2244
  fontSize: 1,
2138
2245
  onChange: e => onChange(e.currentTarget.value === "" ? null : Number(e.currentTarget.value)),
2139
2246
  onKeyDown: e => e.key === "Enter" && onEnter(),
2140
2247
  readOnly: readOnly,
2141
- ...scalarInputConstraints(kind, validation),
2142
- type: "number",
2143
2248
  value: textValueOf(value)
2144
2249
  });
2145
2250
 
@@ -2167,6 +2272,30 @@ function FieldInput$1({kind: kind, value: value, onChange: onChange, onEnter: on
2167
2272
  }
2168
2273
  }
2169
2274
 
2275
+ function LinkChip({hint: hint, children: children, as: as = "a", ...anchorProps}) {
2276
+ /* @__PURE__ */
2277
+ return jsx(HoverHint, {
2278
+ text: hint,
2279
+ children: /* @__PURE__ */ jsx(Card, {
2280
+ __unstable_focusRing: !0,
2281
+ as: as,
2282
+ "data-as": "a",
2283
+ padding: 2,
2284
+ radius: 3,
2285
+ style: {
2286
+ alignItems: "center",
2287
+ color: "inherit",
2288
+ display: "flex",
2289
+ minWidth: 0,
2290
+ textDecoration: "none"
2291
+ },
2292
+ tone: "inherit",
2293
+ ...anchorProps,
2294
+ children: children
2295
+ })
2296
+ });
2297
+ }
2298
+
2170
2299
  function docLinkState(input) {
2171
2300
  const {bareId: bareId, resource: resource} = input;
2172
2301
  if (bareId === void 0 || resource === void 0) return {
@@ -2276,7 +2405,6 @@ function DocRefFace({gdr: gdr}) {
2276
2405
  const {parsed: parsed2, local: local, schemaType: schemaType} = useLocalDocRef(gdr);
2277
2406
  return parsed2 && local && schemaType ? /* @__PURE__ */ jsx(StubPreview, {
2278
2407
  id: parsed2.documentId,
2279
- layout: "default",
2280
2408
  schemaType: schemaType
2281
2409
  }) : /* @__PURE__ */ jsxs(Flex, {
2282
2410
  align: "center",
@@ -2306,15 +2434,23 @@ function ForeignRefNotice({bareId: bareId, resource: resource}) {
2306
2434
  }
2307
2435
 
2308
2436
  function MissingDocNotice({bareId: bareId, layout: layout}) {
2309
- return layout === "inline" ? /* @__PURE__ */ jsx(Text, {
2310
- muted: !0,
2311
- size: 1,
2437
+ return layout === "inline" ?
2438
+ /* @__PURE__ */ jsx(Box, {
2439
+ padding: 2,
2312
2440
  children: /* @__PURE__ */ jsxs(Flex, {
2313
2441
  align: "center",
2314
- as: "span",
2315
- gap: 1,
2442
+ gap: 2,
2316
2443
  children: [
2317
- /* @__PURE__ */ jsx(WarningOutlineIcon, {}), " Document unavailable" ]
2444
+ /* @__PURE__ */ jsx(Text, {
2445
+ muted: !0,
2446
+ size: 1,
2447
+ children: /* @__PURE__ */ jsx(WarningOutlineIcon, {})
2448
+ }),
2449
+ /* @__PURE__ */ jsx(Text, {
2450
+ muted: !0,
2451
+ size: 1,
2452
+ children: "Document unavailable"
2453
+ }) ]
2318
2454
  })
2319
2455
  }) : /* @__PURE__ */ jsx(Card, {
2320
2456
  border: !0,
@@ -2347,44 +2483,73 @@ function UnopenableTypeNotice({actualType: actualType, bareId: bareId}) {
2347
2483
  });
2348
2484
  }
2349
2485
 
2486
+ function renderTypeIcon(icon) {
2487
+ return typeof icon == "function" ? createElement(icon) : isValidElement(icon) ? icon : /* @__PURE__ */ jsx(DocumentIcon, {});
2488
+ }
2489
+
2490
+ function InlineChipFace({bareId: bareId, schemaType: schemaType}) {
2491
+ const {preview: preview} = useStubValuePreview(bareId, schemaType);
2492
+ /* @__PURE__ */
2493
+ return jsxs(Flex, {
2494
+ align: "center",
2495
+ gap: 2,
2496
+ children: [
2497
+ /* @__PURE__ */ jsx(Text, {
2498
+ size: 1,
2499
+ children: renderTypeIcon(schemaType.icon)
2500
+ }), preview.isLoading ? /* @__PURE__ */ jsx(TextSkeleton, {
2501
+ animated: !0,
2502
+ radius: 1,
2503
+ size: 1,
2504
+ style: {
2505
+ width: 96
2506
+ }
2507
+ }) : /* @__PURE__ */ jsx(Text, {
2508
+ size: 1,
2509
+ textOverflow: "ellipsis",
2510
+ weight: "medium",
2511
+ children: preview.value?.title ?? /* @__PURE__ */ jsx("em", {
2512
+ children: "Untitled"
2513
+ })
2514
+ }) ]
2515
+ });
2516
+ }
2517
+
2350
2518
  function LinkedDocPreview({bareId: bareId, layout: layout, schemaType: schemaType}) {
2351
- return layout === "inline" ? /* @__PURE__ */ jsx(IntentLink, {
2352
- intent: "edit",
2519
+ const EditIntentLink = useMemo(() => function(linkProps) {
2520
+ /* @__PURE__ */
2521
+ return jsx(IntentLink, {
2522
+ ...linkProps,
2523
+ intent: "edit",
2524
+ params: {
2525
+ id: bareId
2526
+ }
2527
+ });
2528
+ }, [ bareId ]);
2529
+ return layout === "inline" ? /* @__PURE__ */ jsx(LinkChip, {
2530
+ as: EditIntentLink,
2531
+ hint: "Open document",
2353
2532
  onClick: e => e.stopPropagation(),
2354
- params: {
2355
- id: bareId
2356
- },
2357
- style: {
2358
- color: "inherit",
2359
- textDecoration: "none",
2360
- display: "flex",
2361
- alignItems: "center"
2362
- },
2363
- children: /* @__PURE__ */ jsx(StubPreview, {
2364
- id: bareId,
2365
- layout: "inline",
2533
+ children: /* @__PURE__ */ jsx(InlineChipFace, {
2534
+ bareId: bareId,
2366
2535
  schemaType: schemaType
2367
2536
  })
2368
- }) : /* @__PURE__ */ jsx(IntentLink, {
2369
- intent: "edit",
2537
+ }) : /* @__PURE__ */ jsx(Card, {
2538
+ __unstable_focusRing: !0,
2539
+ as: EditIntentLink,
2540
+ border: !0,
2541
+ "data-as": "a",
2370
2542
  onClick: e => e.stopPropagation(),
2371
- params: {
2372
- id: bareId
2373
- },
2543
+ padding: 1,
2544
+ radius: 3,
2374
2545
  style: {
2375
2546
  color: "inherit",
2376
2547
  textDecoration: "none",
2377
2548
  display: "block"
2378
2549
  },
2379
- children: /* @__PURE__ */ jsx(Card, {
2380
- border: !0,
2381
- padding: 1,
2382
- radius: 3,
2383
- children: /* @__PURE__ */ jsx(StubPreview, {
2384
- id: bareId,
2385
- layout: "default",
2386
- schemaType: schemaType
2387
- })
2550
+ children: /* @__PURE__ */ jsx(StubPreview, {
2551
+ id: bareId,
2552
+ schemaType: schemaType
2388
2553
  })
2389
2554
  });
2390
2555
  }
@@ -2528,6 +2693,10 @@ const fieldValueRenderer = {
2528
2693
  }, ref.id))
2529
2694
  }) : /* @__PURE__ */ jsx(Empty, {}),
2530
2695
  number: renderTextValue,
2696
+ progress: field => /* @__PURE__ */ jsx(ProgressBar, {
2697
+ label: field.title ?? field.name,
2698
+ value: field.value
2699
+ }),
2531
2700
  object: field => field.value === null ? /* @__PURE__ */ jsx(Empty, {}) : /* @__PURE__ */ jsx(Code, {
2532
2701
  size: 1,
2533
2702
  style: {
@@ -2696,7 +2865,7 @@ function ReadOnlyDocRefs({entry: entry}) {
2696
2865
  padding: 2,
2697
2866
  radius: 2,
2698
2867
  children: /* @__PURE__ */ jsx(PreviewOrId, {
2699
- id: extractDocumentId(ref.id),
2868
+ id: toBareId(ref.id),
2700
2869
  type: ref.type
2701
2870
  })
2702
2871
  }, ref.id))
@@ -2779,7 +2948,7 @@ function useFieldDraft(args) {
2779
2948
  }
2780
2949
 
2781
2950
  function EditableFieldControl({instanceId: instanceId, field: field}) {
2782
- const {editFieldFor: editFieldFor, previewFieldFor: previewFieldFor, discardFieldPreviewFor: discardFieldPreviewFor, byInstance: byInstance} = useWorkflowContext(), me = useCurrentUser(), held = byInstance.get(instanceId), entry = held ? resolveFieldEntry(held.instance, field) : void 0, evaluation = held?.evaluation, target = editTargetOf(field), consequence = fieldConsequence(evaluation, field.name), advanceTo = advanceTargetTitle(evaluation, field.name);
2951
+ const {editFieldFor: editFieldFor, previewFieldFor: previewFieldFor, discardFieldPreviewFor: discardFieldPreviewFor} = useWorkflowContext(), me = useCurrentUser(), held = useHeldWorkflowEntry(instanceId), entry = held ? resolveFieldEntry(held.instance, field) : void 0, evaluation = held?.evaluation, target = editTargetOf(field), consequence = fieldConsequence(evaluation, field.name), advanceTo = advanceTargetTitle(evaluation, field.name);
2783
2952
  /* @__PURE__ */
2784
2953
  return jsx(EditableField, {
2785
2954
  field: field,
@@ -3321,6 +3490,27 @@ function ActivityStatusIcon({status: status}) {
3321
3490
  });
3322
3491
  }
3323
3492
 
3493
+ function LoadingRow({label: label, padding: padding}) {
3494
+ /* @__PURE__ */
3495
+ return jsxs(Flex, {
3496
+ align: "center",
3497
+ gap: 2,
3498
+ ...padding === void 0 ? {} : {
3499
+ padding: padding
3500
+ },
3501
+ children: [
3502
+ /* @__PURE__ */ jsx(Spinner, {
3503
+ muted: !0,
3504
+ size: 1
3505
+ }),
3506
+ /* @__PURE__ */ jsx(Text, {
3507
+ muted: !0,
3508
+ size: 1,
3509
+ children: label
3510
+ }) ]
3511
+ });
3512
+ }
3513
+
3324
3514
  function useSpaceToken(index) {
3325
3515
  const value = useTheme_v2().space[index];
3326
3516
  if (value === void 0) throw new Error(`theme is missing space token ${index}`);
@@ -3351,6 +3541,27 @@ function useBadgeCapTrim() {
3351
3541
  };
3352
3542
  }
3353
3543
 
3544
+ function AvatarDisplay({avatar: avatar, "aria-label": ariaLabel}) {
3545
+ const trim = useAvatarCapTrim();
3546
+ /* @__PURE__ */
3547
+ return jsx(Box, {
3548
+ "aria-label": ariaLabel,
3549
+ as: "span",
3550
+ padding: 2,
3551
+ style: {
3552
+ display: "inline-flex"
3553
+ },
3554
+ children: /* @__PURE__ */ jsx(Flex, {
3555
+ align: "center",
3556
+ justify: "center",
3557
+ style: {
3558
+ margin: trim
3559
+ },
3560
+ children: avatar
3561
+ })
3562
+ });
3563
+ }
3564
+
3354
3565
  function AvatarButton({avatar: avatar, onClick: onClick, onMouseDown: onMouseDown, as: as, "aria-label": ariaLabel, ref: ref}) {
3355
3566
  const trim = useAvatarCapTrim();
3356
3567
  /* @__PURE__ */
@@ -3393,10 +3604,13 @@ function UserAvatar({id: id}) {
3393
3604
  });
3394
3605
  }
3395
3606
 
3396
- function UserAvatarGroup({ids: ids}) {
3607
+ function UserAvatarGroup({ids: ids, hint: hint}) {
3397
3608
  const displays = useUserDisplays(ids);
3398
3609
  return displays.length === 0 ? null : /* @__PURE__ */ jsx(HoverHint, {
3399
3610
  text: displays.map(d => d.name).join(", "),
3611
+ ...hint === void 0 ? {} : {
3612
+ description: hint
3613
+ },
3400
3614
  children: /* @__PURE__ */ jsx(MemberAvatarGroup, {
3401
3615
  members: displays.map(display => ({
3402
3616
  id: display.id,
@@ -3407,9 +3621,26 @@ function UserAvatarGroup({ids: ids}) {
3407
3621
  });
3408
3622
  }
3409
3623
 
3410
- function AssignActivityControl({instanceId: instanceId, field: field, assigneeIds: assigneeIds = []}) {
3411
- const {editFieldFor: editFieldFor} = useWorkflowContext(), toast = useClosableToast(), [open, setOpen] = useState(!1), [busy, setBusy] = useState(!1), members = field.value ?? [], selectedIds = new Set(members.flatMap(m => m.type === "user" ? [ m.id ] : [])), toggle = async memberId => {
3412
- if (!busy) {
3624
+ function AssignActivityControl({instanceId: instanceId, state: state, assigneeIds: assigneeIds = []}) {
3625
+ const {editFieldFor: editFieldFor} = useWorkflowContext(), toast = useClosableToast(), [open, setOpen] = useState(!1), [busy, setBusy] = useState(!1);
3626
+ if (state.kind === "none") return assigneeIds.length === 0 ? null : /* @__PURE__ */ jsx(AvatarDisplay, {
3627
+ "aria-label": "Assignees",
3628
+ avatar: /* @__PURE__ */ jsx(UserAvatarGroup, {
3629
+ ids: assigneeIds
3630
+ })
3631
+ });
3632
+ if (state.kind === "closed") return assigneeIds.length === 0 ? null : /* @__PURE__ */ jsx(AvatarButton, {
3633
+ "aria-label": "Assignees",
3634
+ as: "span",
3635
+ avatar: /* @__PURE__ */ jsx(UserAvatarGroup, {
3636
+ hint: state.reason,
3637
+ ids: assigneeIds
3638
+ }),
3639
+ onClick: stopRowClick,
3640
+ onMouseDown: stopRowMouseDown
3641
+ });
3642
+ const field = state.kind === "editable" ? state.field : void 0, members = field?.value ?? [], selectedIds = new Set(members.flatMap(m => m.type === "user" ? [ m.id ] : [])), toggle = async memberId => {
3643
+ if (!(busy || field === void 0)) {
3413
3644
  setBusy(!0);
3414
3645
  try {
3415
3646
  selectedIds.has(memberId) ? await editFieldFor(instanceId, {
@@ -3462,8 +3693,11 @@ function AssignActivityControl({instanceId: instanceId, field: field, assigneeId
3462
3693
  /* @__PURE__ */
3463
3694
  return jsx(DismissablePopover, {
3464
3695
  content: /* @__PURE__ */ jsx(Box, {
3465
- onClick: e => e.stopPropagation(),
3466
- children: /* @__PURE__ */ jsx(MemberPicker, {
3696
+ onClick: stopRowClick,
3697
+ children: state.kind === "loading" ? /* @__PURE__ */ jsx(LoadingRow, {
3698
+ label: "Loading…",
3699
+ padding: 3
3700
+ }) : /* @__PURE__ */ jsx(MemberPicker, {
3467
3701
  onSelect: member => member && void toggle(member.id),
3468
3702
  selectedIds: selectedIds
3469
3703
  })
@@ -3751,9 +3985,9 @@ function BreadcrumbTail({segments: segments, style: style}) {
3751
3985
  }
3752
3986
 
3753
3987
  function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3754
- const frame = /* @__PURE__ */ jsxs(Flex, {
3988
+ const frame =
3989
+ /* @__PURE__ */ jsxs(Flex, {
3755
3990
  align: "center",
3756
- gap: 1,
3757
3991
  children: [
3758
3992
  /* @__PURE__ */ jsx(Flex, {
3759
3993
  align: "center",
@@ -3767,6 +4001,7 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3767
4001
  align: "center",
3768
4002
  flex: 1,
3769
4003
  gap: 2,
4004
+ paddingLeft: 2,
3770
4005
  style: {
3771
4006
  minWidth: 0
3772
4007
  },
@@ -3774,6 +4009,7 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3774
4009
  }), end === void 0 ? null : /* @__PURE__ */ jsx(Flex, {
3775
4010
  align: "center",
3776
4011
  gap: 2,
4012
+ paddingLeft: 2,
3777
4013
  style: {
3778
4014
  flexShrink: 0
3779
4015
  },
@@ -3782,14 +4018,14 @@ function WorkRow({lead: lead, children: children, end: end, onOpen: onOpen}) {
3782
4018
  });
3783
4019
  return onOpen === void 0 ? /* @__PURE__ */ jsx(Card, {
3784
4020
  padding: 1,
3785
- radius: 2,
4021
+ radius: 3,
3786
4022
  children: frame
3787
4023
  }) :
3788
4024
  /* @__PURE__ */ jsx(Card, {
3789
4025
  as: "button",
3790
4026
  onClick: onOpen,
3791
4027
  padding: 1,
3792
- radius: 2,
4028
+ radius: 3,
3793
4029
  style: {
3794
4030
  textAlign: "left"
3795
4031
  },
@@ -3976,6 +4212,222 @@ function AutomationActionRow({actionEval: actionEval, definition: definition}) {
3976
4212
  });
3977
4213
  }
3978
4214
 
4215
+ function inlineActionCount(args) {
4216
+ const {available: available, actionWidths: actionWidths, menuWidth: menuWidth, gap: gap} = args, max = Math.min(3, actionWidths.length);
4217
+ for (let count = max; count > 0; count--) {
4218
+ const buttons = actionWidths.slice(0, count).reduce((sum, w) => sum + w, 0) + gap * (count - 1), menu = count === actionWidths.length ? 0 : menuWidth + gap;
4219
+ if (buttons + menu <= available) return count;
4220
+ }
4221
+ return 0;
4222
+ }
4223
+
4224
+ const ACTIONS_MENU_TRIGGER = {
4225
+ fontSize: 1,
4226
+ gap: 1,
4227
+ iconRight: ChevronDownIcon,
4228
+ padding: 2
4229
+ };
4230
+
4231
+ function ActionMenuItem({actionEval: actionEval, instanceId: instanceId, activity: activity, onCollectParams: onCollectParams}) {
4232
+ const rowKind = actionRowKind(actionEval), label = actionTriggerLabel(actionEval), {tone: tone, icon: icon} = actionButtonFace({
4233
+ actionEval: actionEval,
4234
+ activityName: activity
4235
+ }), iconProp = icon ? {
4236
+ icon: icon
4237
+ } : {}, {fire: fire, pending: pending} = useFireAction({
4238
+ instanceId: instanceId,
4239
+ activity: activity,
4240
+ action: actionEval.action.name,
4241
+ label: label
4242
+ });
4243
+ return rowKind.kind === "disabled" ? /* @__PURE__ */ jsx(MenuItem, {
4244
+ disabled: !0,
4245
+ ...iconProp,
4246
+ text: label,
4247
+ tone: tone
4248
+ }) : rowKind.kind === "plain" ? /* @__PURE__ */ jsx(MenuItem, {
4249
+ disabled: pending,
4250
+ ...iconProp,
4251
+ onClick: () => {
4252
+ fire();
4253
+ },
4254
+ text: label,
4255
+ tone: tone
4256
+ }) : /* @__PURE__ */ jsx(MenuItem, {
4257
+ disabled: pending,
4258
+ ...iconProp,
4259
+ onClick: onCollectParams,
4260
+ text: label,
4261
+ tone: tone
4262
+ });
4263
+ }
4264
+
4265
+ function rowShield(menuButton) {
4266
+ /* @__PURE__ */
4267
+ return jsx("span", {
4268
+ onClick: e => e.stopPropagation(),
4269
+ onMouseDown: stopRowMouseDown,
4270
+ style: {
4271
+ display: "contents"
4272
+ },
4273
+ children: menuButton
4274
+ });
4275
+ }
4276
+
4277
+ function ActionsMenuButton({actions: actions, instanceId: instanceId, activity: activity, label: label, mode: mode = "ghost", inButtonCard: inButtonCard = !1}) {
4278
+ const [paramsAction, setParamsAction] = useState(void 0), clusterPending = useActionClusterPending(), menuId = useId(), menuButton = /* @__PURE__ */ jsx(MenuButton, {
4279
+ button: /* @__PURE__ */ jsx(Button, {
4280
+ ...ACTIONS_MENU_TRIGGER,
4281
+ mode: mode,
4282
+ text: label,
4283
+ ...inButtonCard ? {
4284
+ as: "span"
4285
+ } : {},
4286
+ ...clusterPending ? {
4287
+ disabled: !0
4288
+ } : {}
4289
+ }),
4290
+ id: `workflow-actions-menu-${menuId}`,
4291
+ menu: /* @__PURE__ */ jsx(Menu, {
4292
+ children: actions.map(a => /* @__PURE__ */ jsx(ActionMenuItem, {
4293
+ actionEval: a,
4294
+ activity: activity,
4295
+ instanceId: instanceId,
4296
+ onCollectParams: () => setParamsAction(a)
4297
+ }, a.action.name))
4298
+ }),
4299
+ popover: {
4300
+ placement: "top-end",
4301
+ portal: !0
4302
+ }
4303
+ });
4304
+ /* @__PURE__ */
4305
+ return jsxs(Fragment, {
4306
+ children: [ inButtonCard ? rowShield(menuButton) : menuButton, paramsAction ? /* @__PURE__ */ jsx(RowClickShield, {
4307
+ active: inButtonCard,
4308
+ children: /* @__PURE__ */ jsx(ParamsActionDialog, {
4309
+ actionEval: paramsAction,
4310
+ activity: activity,
4311
+ instanceId: instanceId,
4312
+ onClose: () => setParamsAction(void 0)
4313
+ })
4314
+ }) : null ]
4315
+ });
4316
+ }
4317
+
4318
+ const FOOTER_GAP_PX = 8, MORE_ACTIONS_LABEL = "More actions";
4319
+
4320
+ function TerminalFooter({actions: actions, activity: activity, instanceId: instanceId}) {
4321
+ const lock = useActionClusterState(), [only, ...rest] = actions;
4322
+ return only ? /* @__PURE__ */ jsx(ActionClusterProvider, {
4323
+ value: lock,
4324
+ children: rest.length === 0 ? /* @__PURE__ */ jsx(FooterRow, {
4325
+ children: /* @__PURE__ */ jsx(ActivityActionRow, {
4326
+ actionEval: only,
4327
+ activity: activity,
4328
+ instanceId: instanceId
4329
+ })
4330
+ }) : /* @__PURE__ */ jsx(FittedActions, {
4331
+ actions: actions,
4332
+ activity: activity,
4333
+ instanceId: instanceId
4334
+ })
4335
+ }) : null;
4336
+ }
4337
+
4338
+ function FooterRow({children: children, onElement: onElement}) {
4339
+ /* @__PURE__ */
4340
+ return jsx(Box, {
4341
+ padding: 3,
4342
+ children: /* @__PURE__ */ jsx(Flex, {
4343
+ align: "center",
4344
+ justify: "flex-end",
4345
+ ...onElement ? {
4346
+ ref: onElement
4347
+ } : {},
4348
+ style: {
4349
+ gap: FOOTER_GAP_PX
4350
+ },
4351
+ children: children
4352
+ })
4353
+ });
4354
+ }
4355
+
4356
+ function FittedActions({actions: actions, activity: activity, instanceId: instanceId}) {
4357
+ const [footerEl, setFooterEl] = useState(null), [measureEl, setMeasureEl] = useState(null), footerSize = useElementSize(footerEl), measureSize = useElementSize(measureEl), [inline, setInline] = useState(void 0);
4358
+ useLayoutEffect(() => {
4359
+ if (measureEl === null || footerSize === null || measureSize === null) return;
4360
+ const widths = Array.from(measureEl.children, clone => clone.getBoundingClientRect().width);
4361
+ setInline(inlineActionCount({
4362
+ available: footerSize.border.width,
4363
+ actionWidths: widths.slice(0, -1),
4364
+ menuWidth: widths.at(-1) ?? 0,
4365
+ gap: FOOTER_GAP_PX
4366
+ }));
4367
+ }, [ actions, footerSize, measureEl, measureSize ]);
4368
+ const overflow = inline === void 0 ? [] : actions.slice(inline);
4369
+ /* @__PURE__ */
4370
+ return jsxs(Fragment, {
4371
+ children: [
4372
+ /* @__PURE__ */ jsx(MeasureRow, {
4373
+ actions: actions,
4374
+ activity: activity,
4375
+ onElement: setMeasureEl
4376
+ }),
4377
+ /* @__PURE__ */ jsxs(FooterRow, {
4378
+ onElement: setFooterEl,
4379
+ children: [ inline === void 0 ? null : actions.slice(0, inline).map(a => /* @__PURE__ */ jsx(ActivityActionRow, {
4380
+ actionEval: a,
4381
+ activity: activity,
4382
+ instanceId: instanceId
4383
+ }, a.action.name)), overflow.length > 0 ? /* @__PURE__ */ jsx(ActionsMenuButton, {
4384
+ actions: overflow,
4385
+ activity: activity,
4386
+ instanceId: instanceId,
4387
+ label: MORE_ACTIONS_LABEL,
4388
+ mode: "default"
4389
+ }) : null ]
4390
+ }) ]
4391
+ });
4392
+ }
4393
+
4394
+ function MeasureRow({actions: actions, activity: activity, onElement: onElement}) {
4395
+ /* @__PURE__ */
4396
+ return jsx("div", {
4397
+ "aria-hidden": !0,
4398
+ style: {
4399
+ height: 0,
4400
+ overflow: "hidden"
4401
+ },
4402
+ children: /* @__PURE__ */ jsxs("div", {
4403
+ ref: onElement,
4404
+ style: {
4405
+ display: "inline-flex",
4406
+ whiteSpace: "nowrap"
4407
+ },
4408
+ children: [ actions.map(a => {
4409
+ const {icon: icon} = actionButtonFace({
4410
+ actionEval: a,
4411
+ activityName: activity
4412
+ });
4413
+ /* @__PURE__ */
4414
+ return jsx(Button, {
4415
+ as: "span",
4416
+ fontSize: 1,
4417
+ ...actionIconProps(icon),
4418
+ padding: 2,
4419
+ text: actionTriggerLabel(a)
4420
+ }, a.action.name);
4421
+ }),
4422
+ /* @__PURE__ */ jsx(Button, {
4423
+ ...ACTIONS_MENU_TRIGGER,
4424
+ as: "span",
4425
+ text: MORE_ACTIONS_LABEL
4426
+ }) ]
4427
+ })
4428
+ });
4429
+ }
4430
+
3979
4431
  function ActivityDetailDialog({entry: entry, activityName: activityName, breadcrumb: breadcrumb, definition: definition, document: document, onClose: onClose}) {
3980
4432
  const detail = deriveActivityDetail({
3981
4433
  entry: entry,
@@ -3985,12 +4437,11 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
3985
4437
  const instanceId = entry.instance._id, notice = blockedNotice(detail), hasBody = detail.state.length > 0 || notice !== void 0;
3986
4438
  /* @__PURE__ */
3987
4439
  return jsxs(Dialog, {
3988
- footer: /* @__PURE__ */ jsx(TerminalFooter, {
3989
- activityName: activityName,
3990
- detail: detail,
3991
- instanceId: instanceId,
3992
- onClose: onClose
3993
- }),
4440
+ footer: detail.presentation.terminalActions.length > 0 ? /* @__PURE__ */ jsx(TerminalFooter, {
4441
+ actions: detail.presentation.terminalActions,
4442
+ activity: activityName,
4443
+ instanceId: instanceId
4444
+ }) : void 0,
3994
4445
  header: /* @__PURE__ */ jsx(Breadcrumb, {
3995
4446
  emphasizeCurrent: !0,
3996
4447
  segments: [ ...breadcrumb, detail.title ]
@@ -4159,8 +4610,11 @@ function FieldRow({field: field, detail: detail, entry: entry, activityName: act
4159
4610
  /* @__PURE__ */ jsx(FieldValue, {
4160
4611
  field: field
4161
4612
  }), assignField ? /* @__PURE__ */ jsx(AssignActivityControl, {
4162
- field: assignField,
4163
- instanceId: instanceId
4613
+ instanceId: instanceId,
4614
+ state: {
4615
+ kind: "editable",
4616
+ field: assignField
4617
+ }
4164
4618
  }) : null ]
4165
4619
  }) ]
4166
4620
  });
@@ -4193,31 +4647,6 @@ function blockedNotice(detail) {
4193
4647
  });
4194
4648
  }
4195
4649
 
4196
- function TerminalFooter({detail: detail, instanceId: instanceId, activityName: activityName, onClose: onClose}) {
4197
- /* @__PURE__ */
4198
- return jsx(Box, {
4199
- padding: 3,
4200
- children: /* @__PURE__ */ jsxs(Flex, {
4201
- align: "center",
4202
- gap: 2,
4203
- justify: "flex-end",
4204
- children: [
4205
- /* @__PURE__ */ jsx(Button, {
4206
- fontSize: 1,
4207
- mode: "bleed",
4208
- onClick: onClose,
4209
- padding: 2,
4210
- text: "Cancel"
4211
- }),
4212
- /* @__PURE__ */ jsx(TerminalActions, {
4213
- actions: detail.presentation.terminalActions,
4214
- activity: activityName,
4215
- instanceId: instanceId
4216
- }) ]
4217
- })
4218
- });
4219
- }
4220
-
4221
4650
  function EmptyState({action: action, description: description, icon: icon, title: title}) {
4222
4651
  /* @__PURE__ */
4223
4652
  return jsx(Card, {
@@ -4271,26 +4700,104 @@ function CountedLabel({count: count, label: label}) {
4271
4700
  });
4272
4701
  }
4273
4702
 
4274
- function GroupHeading({count: count, title: title, end: end}) {
4703
+ function SpinnerSlot({busy: busy, size: size}) {
4704
+ /* @__PURE__ */
4705
+ return jsx(Flex, {
4706
+ align: "center",
4707
+ flex: "none",
4708
+ justify: "center",
4709
+ style: {
4710
+ width: 19,
4711
+ height: 19
4712
+ },
4713
+ children: busy ? /* @__PURE__ */ jsx(Spinner, {
4714
+ muted: !0,
4715
+ ...size !== void 0 ? {
4716
+ size: size
4717
+ } : {}
4718
+ }) : null
4719
+ });
4720
+ }
4721
+
4722
+ function GroupHeading({busy: busy, count: count, title: title, end: end}) {
4723
+ /* @__PURE__ */
4724
+ return jsxs(Flex, {
4725
+ align: "center",
4726
+ gap: 3,
4727
+ paddingLeft: 2,
4728
+ paddingY: end === void 0 ? 3 : 1,
4729
+ children: [
4730
+ /* @__PURE__ */ jsx(CountedLabel, {
4731
+ count: count,
4732
+ label: /* @__PURE__ */ jsx(Text, {
4733
+ size: 1,
4734
+ weight: "semibold",
4735
+ children: title
4736
+ })
4737
+ }), busy === void 0 ? null : /* @__PURE__ */ jsx(SpinnerSlot, {
4738
+ busy: busy,
4739
+ size: 1
4740
+ }), end === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, {
4741
+ children: [
4742
+ /* @__PURE__ */ jsx(Flex, {
4743
+ flex: 1
4744
+ }), end ]
4745
+ }) ]
4746
+ });
4747
+ }
4748
+
4749
+ function CopyDetailsButton({unreadable: unreadable}) {
4750
+ const toast = useClosableToast();
4751
+ /* @__PURE__ */
4752
+ return jsx(Button, {
4753
+ fontSize: 1,
4754
+ mode: "ghost",
4755
+ onClick: () => {
4756
+ navigator.clipboard.writeText(unreadableDocsReport(unreadable)).then(() => toast.push({
4757
+ status: "success",
4758
+ title: "Details copied"
4759
+ }), () => toast.push({
4760
+ status: "error",
4761
+ title: "Could not copy the details"
4762
+ }));
4763
+ },
4764
+ padding: 2,
4765
+ text: "Copy details"
4766
+ });
4767
+ }
4768
+
4769
+ function UnreadableDocsNote({unreadable: unreadable}) {
4770
+ if (unreadable.length === 0) return null;
4771
+ const label = unreadable.length === 1 ? "1 workflow document isn’t listed — this Studio can’t read it. Share the details with your Studio maintainers." : `${unreadable.length} workflow documents aren’t listed — this Studio can’t read them. Share the details with your Studio maintainers.`;
4275
4772
  /* @__PURE__ */
4276
- return jsxs(Flex, {
4277
- align: "center",
4278
- gap: 3,
4279
- paddingLeft: 2,
4280
- paddingY: end === void 0 ? 3 : 1,
4773
+ return jsxs(Card, {
4774
+ padding: 2,
4775
+ paddingLeft: 3,
4776
+ radius: 3,
4777
+ style: {
4778
+ alignItems: "center",
4779
+ display: "flex"
4780
+ },
4781
+ tone: "caution",
4281
4782
  children: [
4282
- /* @__PURE__ */ jsx(CountedLabel, {
4283
- count: count,
4284
- label: /* @__PURE__ */ jsx(Text, {
4285
- size: 1,
4286
- weight: "semibold",
4287
- children: title
4288
- })
4289
- }), end === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, {
4783
+ /* @__PURE__ */ jsxs(Flex, {
4784
+ align: "center",
4785
+ flex: 1,
4786
+ gap: 3,
4290
4787
  children: [
4291
- /* @__PURE__ */ jsx(Flex, {
4292
- flex: 1
4293
- }), end ]
4788
+ /* @__PURE__ */ jsx(Text, {
4789
+ muted: !0,
4790
+ size: 1,
4791
+ children: /* @__PURE__ */ jsx(WarningOutlineIcon, {})
4792
+ }),
4793
+ /* @__PURE__ */ jsx(Text, {
4794
+ muted: !0,
4795
+ size: 1,
4796
+ children: label
4797
+ }) ]
4798
+ }),
4799
+ /* @__PURE__ */ jsx(CopyDetailsButton, {
4800
+ unreadable: unreadable
4294
4801
  }) ]
4295
4802
  });
4296
4803
  }
@@ -4301,7 +4808,7 @@ const HEADLINES = {
4301
4808
  };
4302
4809
 
4303
4810
  function remediation(invalid) {
4304
- return invalid.reason === "model-ahead" ? `"${invalid.documentId}" was written by a newer engine data model. Upgrade @sanity/workflow-engine (and this plugin) to work with it.` : `"${invalid.documentId}" has a corrupt stored shape and cannot be read. Inspect the document, or reset the workflow dataset.`;
4811
+ return invalid.reason === "model-ahead" ? `"${invalid.documentId}" was created by a newer version of the workflow engine than this Studio can read. It needs a plugin update that supports the newer data model — share the details with your Studio maintainers.` : `This version of the Studio can't read "${invalid.documentId}" — it was likely created by a newer or different version of the workflow tooling. Share the details with your Studio maintainers.`;
4305
4812
  }
4306
4813
 
4307
4814
  function InvalidDocNotice({invalid: invalid}) {
@@ -4312,20 +4819,22 @@ function InvalidDocNotice({invalid: invalid}) {
4312
4819
  radius: 2,
4313
4820
  tone: "caution",
4314
4821
  children: /* @__PURE__ */ jsxs(Stack, {
4315
- space: 2,
4822
+ space: 3,
4316
4823
  children: [
4317
- /* @__PURE__ */ jsx(HoverHint, {
4318
- text: invalid.message,
4319
- children: /* @__PURE__ */ jsx(Text, {
4320
- size: 1,
4321
- weight: "medium",
4322
- children: HEADLINES[invalid.reason]
4323
- })
4824
+ /* @__PURE__ */ jsx(Text, {
4825
+ size: 1,
4826
+ weight: "medium",
4827
+ children: HEADLINES[invalid.reason]
4324
4828
  }),
4325
4829
  /* @__PURE__ */ jsx(Text, {
4326
4830
  muted: !0,
4327
4831
  size: 1,
4328
4832
  children: remediation(invalid)
4833
+ }),
4834
+ /* @__PURE__ */ jsx(Flex, {
4835
+ children: /* @__PURE__ */ jsx(CopyDetailsButton, {
4836
+ unreadable: [ invalid ]
4837
+ })
4329
4838
  }) ]
4330
4839
  })
4331
4840
  });
@@ -4350,6 +4859,18 @@ function TabSwitch({ariaControls: ariaControls, idPrefix: idPrefix, options: opt
4350
4859
  });
4351
4860
  }
4352
4861
 
4862
+ function useDelayedFlag(active, delayMs) {
4863
+ const [held, setHeld] = useState(!1);
4864
+ return useEffect(() => {
4865
+ if (!active) {
4866
+ setHeld(!1);
4867
+ return;
4868
+ }
4869
+ const timer = setTimeout(() => setHeld(!0), delayMs);
4870
+ return () => clearTimeout(timer);
4871
+ }, [ active, delayMs ]), held && active;
4872
+ }
4873
+
4353
4874
  function instanceTitle(instance, definition) {
4354
4875
  return definition?.title ?? instance.definition;
4355
4876
  }
@@ -4365,6 +4886,30 @@ function openActivityGone(args) {
4365
4886
  return evaluation !== void 0 && !evaluation.currentStage.activities.some(a => a.activity.name === target.activityName);
4366
4887
  }
4367
4888
 
4889
+ function HintedMenuButton({hint: hint, ...menuButtonProps}) {
4890
+ const [open, setOpen] = useState(!1), popover = {
4891
+ tone: "default",
4892
+ ...menuButtonProps.popover?.placement === void 0 ? {
4893
+ placement: "bottom-end",
4894
+ fallbackPlacements: [ "top-end" ]
4895
+ } : {},
4896
+ ...menuButtonProps.popover
4897
+ };
4898
+ return hint === void 0 ? /* @__PURE__ */ jsx(MenuButton, {
4899
+ ...menuButtonProps,
4900
+ popover: popover
4901
+ }) : /* @__PURE__ */ jsx(HoverHint, {
4902
+ disabled: open,
4903
+ text: hint,
4904
+ children: /* @__PURE__ */ jsx(MenuButton, {
4905
+ ...menuButtonProps,
4906
+ onClose: () => setOpen(!1),
4907
+ onOpen: () => setOpen(!0),
4908
+ popover: popover
4909
+ })
4910
+ });
4911
+ }
4912
+
4368
4913
  function subjectGdr(ctx) {
4369
4914
  return !ctx.subjectDocId || !ctx.mapping ? null : subjectGdrRef({
4370
4915
  mapping: ctx.mapping,
@@ -4412,6 +4957,10 @@ function missingInitFields(initFields, values) {
4412
4957
  return initFields.filter(entry => gatesStart(entry) && !isFieldFilled(entry, values[entry.name] ?? null));
4413
4958
  }
4414
4959
 
4960
+ function numericFilled(value) {
4961
+ return value !== null && value !== "" && Number.isFinite(Number(value));
4962
+ }
4963
+
4415
4964
  const filledByKind = {
4416
4965
  actor: value => value !== null,
4417
4966
  array: value => Array.isArray(value) && value.length > 0,
@@ -4419,7 +4968,8 @@ const filledByKind = {
4419
4968
  boolean: () => !0,
4420
4969
  "doc.ref": value => value !== null,
4421
4970
  "doc.refs": value => Array.isArray(value) && value.length > 0,
4422
- number: value => value !== null && value !== "" && Number.isFinite(Number(value)),
4971
+ number: numericFilled,
4972
+ progress: numericFilled,
4423
4973
  "release.ref": value => value !== null,
4424
4974
  subject: value => value !== null
4425
4975
  };
@@ -4437,7 +4987,14 @@ const asInitial = candidate => candidate, scalarInitialValue = (entry, value) =>
4437
4987
  type: entry.type,
4438
4988
  name: entry.name,
4439
4989
  value: value
4440
- }), initialValueByKind = {
4990
+ }), numericInitialValue = (entry, value) => {
4991
+ const n = Number(value);
4992
+ return Number.isFinite(n) ? asInitial({
4993
+ type: entry.type,
4994
+ name: entry.name,
4995
+ value: n
4996
+ }) : null;
4997
+ }, initialValueByKind = {
4441
4998
  actor: (entry, value) => asInitial({
4442
4999
  type: entry.type,
4443
5000
  name: entry.name,
@@ -4462,14 +5019,8 @@ const asInitial = candidate => candidate, scalarInitialValue = (entry, value) =>
4462
5019
  value: value
4463
5020
  }),
4464
5021
  subject: singleRefInitialValue,
4465
- number: (entry, value) => {
4466
- const n = Number(value);
4467
- return Number.isFinite(n) ? asInitial({
4468
- type: entry.type,
4469
- name: entry.name,
4470
- value: n
4471
- }) : null;
4472
- },
5022
+ number: numericInitialValue,
5023
+ progress: numericInitialValue,
4473
5024
  "release.ref": (entry, value) => asInitial({
4474
5025
  type: entry.type,
4475
5026
  name: entry.name,
@@ -4948,6 +5499,58 @@ function StartWorkflowMenuItem({mapping: mapping, docId: docId, initialValue: in
4948
5499
  }) : item;
4949
5500
  }
4950
5501
 
5502
+ function settledDim(settled) {
5503
+ return settled ? {
5504
+ opacity: .66
5505
+ } : void 0;
5506
+ }
5507
+
5508
+ function ActivityStatusLead({status: status}) {
5509
+ /* @__PURE__ */
5510
+ return jsx(HoverHint, {
5511
+ text: ACTIVITY_STATUS_LABEL[status],
5512
+ children: /* @__PURE__ */ jsx(ActivityStatusIcon, {
5513
+ status: status
5514
+ })
5515
+ });
5516
+ }
5517
+
5518
+ function ActivityRowTitle({dimmed: dimmed, children: children}) {
5519
+ /* @__PURE__ */
5520
+ return jsx(Text, {
5521
+ size: 1,
5522
+ style: {
5523
+ ...dimmed,
5524
+ minWidth: 0
5525
+ },
5526
+ textOverflow: "ellipsis",
5527
+ weight: "medium",
5528
+ children: children
5529
+ });
5530
+ }
5531
+
5532
+ function RowTerminalActions({actions: actions, activity: activity, instanceId: instanceId}) {
5533
+ const lock = useActionClusterState(), [only, ...rest] = actions;
5534
+ return only ? /* @__PURE__ */ jsx(ActionClusterProvider, {
5535
+ value: lock,
5536
+ children: rest.length === 0 ? /* @__PURE__ */ jsx(ActivityActionRow, {
5537
+ actionEval: only,
5538
+ activity: activity,
5539
+ chrome: {
5540
+ mode: "ghost",
5541
+ inButtonCard: !0
5542
+ },
5543
+ instanceId: instanceId
5544
+ }) : /* @__PURE__ */ jsx(ActionsMenuButton, {
5545
+ actions: actions,
5546
+ activity: activity,
5547
+ inButtonCard: !0,
5548
+ instanceId: instanceId,
5549
+ label: "Select action"
5550
+ })
5551
+ }) : null;
5552
+ }
5553
+
4951
5554
  function TitleTag({hint: hint, tone: tone, children: children}) {
4952
5555
  const badgeTrim = useBadgeCapTrim();
4953
5556
  /* @__PURE__ */
@@ -4964,54 +5567,34 @@ function TitleTag({hint: hint, tone: tone, children: children}) {
4964
5567
  });
4965
5568
  }
4966
5569
 
4967
- function ActivityRow({activityEval: activityEval, breadcrumb: breadcrumb, assigneeIds: assigneeIds, assignField: assignField, instanceId: instanceId, onOpen: onOpen, terminalActions: terminalActions = []}) {
4968
- const {activity: activity, status: status} = activityEval, settled = isTerminalActivityStatus(status), dimmed = settled ? {
4969
- opacity: .66
4970
- } : void 0;
4971
- let assignmentControl = null;
4972
- return assignField ? assignmentControl = /* @__PURE__ */ jsx(AssignActivityControl, {
4973
- assigneeIds: assigneeIds,
4974
- field: assignField,
4975
- instanceId: instanceId
4976
- }) : assigneeIds.length > 0 && (assignmentControl = /* @__PURE__ */ jsx(AvatarButton, {
4977
- "aria-label": "Assignees",
4978
- as: "span",
4979
- avatar: /* @__PURE__ */ jsx(UserAvatarGroup, {
4980
- ids: assigneeIds
4981
- })
4982
- })),
4983
- /* @__PURE__ */ jsxs(WorkRow, {
5570
+ function ActivityRow({face: face, breadcrumb: breadcrumb, assigneeIds: assigneeIds, assignState: assignState, dateControl: dateControl, instanceId: instanceId, onOpen: onOpen, terminalActions: terminalActions = []}) {
5571
+ const settled = isTerminalActivityStatus(face.status), dimmed = settledDim(settled);
5572
+ /* @__PURE__ */
5573
+ return jsxs(WorkRow, {
4984
5574
  end: /* @__PURE__ */ jsxs(Fragment, {
4985
- children: [ settled || terminalActions.length === 0 ? null : /* @__PURE__ */ jsx(TerminalActions, {
5575
+ children: [ settled || terminalActions.length === 0 ? null : /* @__PURE__ */ jsx(RowTerminalActions, {
4986
5576
  actions: terminalActions,
4987
- activity: activity.name,
4988
- inButtonCard: !0,
5577
+ activity: face.activityName,
5578
+ instanceId: instanceId
5579
+ }), dateControl,
5580
+ /* @__PURE__ */ jsx(AssignActivityControl, {
5581
+ assigneeIds: assigneeIds,
4989
5582
  instanceId: instanceId,
4990
- mode: "ghost"
4991
- }), assignmentControl ]
5583
+ state: assignState
5584
+ }) ]
4992
5585
  }),
4993
- lead:
4994
- /* @__PURE__ */ jsx(HoverHint, {
4995
- text: ACTIVITY_STATUS_LABEL[status],
4996
- children: /* @__PURE__ */ jsx(ActivityStatusIcon, {
4997
- status: status
4998
- })
5586
+ lead: /* @__PURE__ */ jsx(ActivityStatusLead, {
5587
+ status: face.status
4999
5588
  }),
5000
5589
  onOpen: onOpen,
5001
5590
  children: [
5002
- /* @__PURE__ */ jsx(Text, {
5003
- size: 1,
5004
- style: {
5005
- ...dimmed,
5006
- minWidth: 0
5007
- },
5008
- textOverflow: "ellipsis",
5009
- weight: "medium",
5010
- children: activityLabel(activity)
5011
- }), showsAutomatedPill(activityEval) ? /* @__PURE__ */ jsx(TitleTag, {
5591
+ /* @__PURE__ */ jsx(ActivityRowTitle, {
5592
+ dimmed: dimmed,
5593
+ children: face.title
5594
+ }), face.automated ? /* @__PURE__ */ jsx(TitleTag, {
5012
5595
  hint: "Completes on its own — no user action is needed",
5013
5596
  children: "Automated"
5014
- }) : null, showsBlockedTag(activityEval) ? /* @__PURE__ */ jsx(TitleTag, {
5597
+ }) : null, face.blocked ? /* @__PURE__ */ jsx(TitleTag, {
5015
5598
  hint: "Nothing can be done on this activity right now — open it to see why",
5016
5599
  tone: "caution",
5017
5600
  children: "Blocked"
@@ -5052,7 +5635,6 @@ function ForMeList({entry: entry, work: work, identity: identity, onOpenActivity
5052
5635
  activityState: activityState,
5053
5636
  editableFields: entry.evaluation?.editableFields
5054
5637
  }),
5055
- activityEval: activityEval,
5056
5638
  breadcrumb: breadcrumb,
5057
5639
  instanceId: instance._id,
5058
5640
  onOpen: () => onOpenActivity(activityEval.activity.name)
@@ -5096,33 +5678,12 @@ function ActivitiesList({entry: entry, onOpenActivity: onOpenActivity}) {
5096
5678
  activityState: activityState,
5097
5679
  editableFields: entry.evaluation?.editableFields
5098
5680
  }),
5099
- activityEval: t,
5100
5681
  instanceId: entry.instance._id,
5101
5682
  onOpen: () => onOpenActivity(t.activity.name)
5102
5683
  }, t.activity.name))
5103
5684
  });
5104
5685
  }
5105
5686
 
5106
- function LoadingRow({label: label, padding: padding}) {
5107
- /* @__PURE__ */
5108
- return jsxs(Flex, {
5109
- align: "center",
5110
- gap: 2,
5111
- ...padding === void 0 ? {} : {
5112
- padding: padding
5113
- },
5114
- children: [
5115
- /* @__PURE__ */ jsx(Spinner, {
5116
- muted: !0
5117
- }),
5118
- /* @__PURE__ */ jsx(Text, {
5119
- muted: !0,
5120
- size: 1,
5121
- children: label
5122
- }) ]
5123
- });
5124
- }
5125
-
5126
5687
  function instanceNotice(entry) {
5127
5688
  const {instance: instance, committed: committed, evaluation: evaluation} = entry, state = terminalState(instance);
5128
5689
  if (state !== "in-flight")
@@ -5148,9 +5709,16 @@ function instanceNotice(entry) {
5148
5709
  size: 1,
5149
5710
  children: "This workflow didn’t finish starting — use Start workflow to continue it"
5150
5711
  })
5151
- }) : pending ? /* @__PURE__ */ jsx(LoadingRow, {
5152
- label: "Loading…",
5153
- padding: 4
5712
+ }) : pending ? /* @__PURE__ */ jsx(WorkRow, {
5713
+ lead: /* @__PURE__ */ jsx(Spinner, {
5714
+ muted: !0,
5715
+ size: 1
5716
+ }),
5717
+ children: /* @__PURE__ */ jsx(Text, {
5718
+ muted: !0,
5719
+ size: 1,
5720
+ children: "Loading…"
5721
+ })
5154
5722
  }) : null;
5155
5723
  }
5156
5724
 
@@ -5162,6 +5730,7 @@ const EDITOR_BY_KIND = {
5162
5730
  date: "scalar",
5163
5731
  datetime: "scalar",
5164
5732
  number: "scalar",
5733
+ progress: "scalar",
5165
5734
  string: "scalar",
5166
5735
  text: "scalar",
5167
5736
  url: "scalar"
@@ -5224,9 +5793,16 @@ function faceOf(args) {
5224
5793
  } : isSingleDocRefEntry(entry) && entry.value !== null ? {
5225
5794
  kind: "docRef",
5226
5795
  gdr: entry.value
5796
+ } : entry._type === "progress" && entry.value !== null ? {
5797
+ kind: "progress",
5798
+ value: entry.value
5227
5799
  } : todoShaped && entry._type === "array" ? textFace(todoTextOf(entry)) : textFace(textOf(entry));
5228
5800
  }
5229
5801
 
5802
+ function progressFillPercent(face) {
5803
+ if (face.kind === "progress") return clampProgress(face.value);
5804
+ }
5805
+
5230
5806
  function deriveFieldPills(args) {
5231
5807
  const {scope: scope, definition: definition, instance: instance, committed: committed, evaluation: evaluation} = args, declared = scope === "workflow" ? definition?.fields ?? [] : definition?.stages.find(s => s.name === instance.currentStage)?.fields ?? [], entries = scope === "workflow" ? instance.fields : findOpenStageEntry(instance)?.fields ?? [], entriesByName = new Map(entries.map(entry => [ entry.name, entry ])), editables = isEvaluationCurrent({
5232
5808
  committed: committed,
@@ -5367,6 +5943,15 @@ function SetFace({face: face}) {
5367
5943
  })
5368
5944
  }) : face.kind === "assignees" ? /* @__PURE__ */ jsx(AssigneesFace, {
5369
5945
  assignees: face.assignees
5946
+ }) : face.kind === "progress" ?
5947
+ /* @__PURE__ */ jsx(Text, {
5948
+ size: 1,
5949
+ style: {
5950
+ fontVariantNumeric: "tabular-nums",
5951
+ minWidth: "4ch",
5952
+ textAlign: "right"
5953
+ },
5954
+ children: progressCaption(face.value)
5370
5955
  }) : /* @__PURE__ */ jsx(FaceText, {
5371
5956
  children: face.kind === "text" ? face.text : ""
5372
5957
  });
@@ -5420,15 +6005,21 @@ function AssigneesFace({assignees: assignees}) {
5420
6005
  });
5421
6006
  }
5422
6007
 
5423
- function usePillChrome() {
5424
- const {color: color} = useTheme_v2();
6008
+ function usePillChrome(face) {
6009
+ const {color: color} = useTheme_v2(), fillPercent = face === void 0 ? void 0 : progressFillPercent(face), fill = `color-mix(in srgb, ${color.focusRing} 24%, transparent)`, progressFill = fillPercent === void 0 ? {} : {
6010
+ backgroundImage: `linear-gradient(${fill}, ${fill})`,
6011
+ backgroundRepeat: "no-repeat",
6012
+ backgroundSize: `${fillPercent}% 100%`,
6013
+ transition: "background-size 300ms ease"
6014
+ };
5425
6015
  return {
5426
6016
  mode: "bleed",
5427
6017
  padding: 2,
5428
6018
  radius: 6,
5429
6019
  style: {
5430
6020
  boxShadow: `inset 0 0 0 1px ${color.button.ghost.default.enabled.border}`,
5431
- maxWidth: "100%"
6021
+ maxWidth: "100%",
6022
+ ...progressFill
5432
6023
  }
5433
6024
  };
5434
6025
  }
@@ -5459,7 +6050,7 @@ function PillFaceContent({children: children}) {
5459
6050
  }
5460
6051
 
5461
6052
  function PillButton({children: children, pill: pill, hintDisabled: hintDisabled = !1, onClick: onClick}) {
5462
- const chrome = usePillChrome();
6053
+ const chrome = usePillChrome(pill.face);
5463
6054
  /* @__PURE__ */
5464
6055
  return jsx(PillHint, {
5465
6056
  disabled: hintDisabled,
@@ -5492,7 +6083,23 @@ function useFieldCommit({entry: entry, editability: editability}) {
5492
6083
  function PillPopover({pill: pill, content: content, open: open, onToggle: onToggle, onDismiss: onDismiss}) {
5493
6084
  /* @__PURE__ */
5494
6085
  return jsx(DismissablePopover, {
5495
- content: content,
6086
+ content: /* @__PURE__ */ jsxs(Stack, {
6087
+ gap: 1,
6088
+ children: [
6089
+ /* @__PURE__ */ jsx(Box, {
6090
+ padding: 3,
6091
+ paddingBottom: 0,
6092
+ style: {
6093
+ maxWidth: 360
6094
+ },
6095
+ children: /* @__PURE__ */ jsx(FieldHead, {
6096
+ label: pill.title,
6097
+ ...pill.description === void 0 ? {} : {
6098
+ description: pill.description
6099
+ }
6100
+ })
6101
+ }), content ]
6102
+ }),
5496
6103
  fallbackPlacements: [ "top-start" ],
5497
6104
  onDismiss: onDismiss,
5498
6105
  open: open,
@@ -5792,6 +6399,66 @@ function FieldPill({entry: entry, pill: pill, scope: scope}) {
5792
6399
  });
5793
6400
  }
5794
6401
 
6402
+ function Hairline({weight: weight, style: style}) {
6403
+ const {color: color} = useTheme_v2();
6404
+ /* @__PURE__ */
6405
+ return jsx("div", {
6406
+ style: {
6407
+ borderTop: `${weight}px solid ${color.border}`,
6408
+ ...style
6409
+ }
6410
+ });
6411
+ }
6412
+
6413
+ function CollapsibleBand({header: header, children: children, background: background = !1, defaultOpen: defaultOpen = !0, sticky: sticky = !1, title: title}) {
6414
+ const [open, setOpen] = useState(defaultOpen), verb = open ? "Collapse" : "Expand";
6415
+ /* @__PURE__ */
6416
+ return jsxs(Stack, {
6417
+ gap: 1,
6418
+ children: [
6419
+ /* @__PURE__ */ jsx(Card, {
6420
+ onDoubleClick: event => {
6421
+ event.target instanceof Element && event.target.closest("a, button") || setOpen(v => !v);
6422
+ },
6423
+ padding: 1,
6424
+ radius: 3,
6425
+ style: {
6426
+ userSelect: "none",
6427
+ ...sticky ? {
6428
+ position: "sticky",
6429
+ top: 0,
6430
+ zIndex: 1
6431
+ } : {}
6432
+ },
6433
+ tone: background ? "transparent" : "inherit",
6434
+ children: /* @__PURE__ */ jsxs(Flex, {
6435
+ align: "center",
6436
+ children: [
6437
+ /* @__PURE__ */ jsx(Button, {
6438
+ "aria-label": `${verb} ${title}`,
6439
+ fontSize: 1,
6440
+ icon: open ? ChevronDownIcon : ChevronRightIcon,
6441
+ mode: "bleed",
6442
+ onClick: () => setOpen(v => !v),
6443
+ padding: 2
6444
+ }), header ]
6445
+ })
6446
+ }), open ? children : null ]
6447
+ });
6448
+ }
6449
+
6450
+ function TrailingHairline() {
6451
+ /* @__PURE__ */
6452
+ return jsx(Box, {
6453
+ flex: 1,
6454
+ paddingLeft: 1,
6455
+ paddingRight: 1,
6456
+ children: /* @__PURE__ */ jsx(Hairline, {
6457
+ weight: .5
6458
+ })
6459
+ });
6460
+ }
6461
+
5795
6462
  const HISTORY_KIND_GROUPS = {
5796
6463
  Stages: [ "stageEntered", "stageExited", "transitionFired", "aborted" ],
5797
6464
  Activities: [ "activityStatusChanged" ],
@@ -6114,76 +6781,60 @@ function InstanceDebugMenu({entry: entry}) {
6114
6781
  });
6115
6782
  }
6116
6783
 
6117
- function InstanceAccordion({entry: entry, children: children}) {
6118
- const definition = useDefinition(entry), {instance: instance, ready: ready} = entry, settled = !isLiveEntry(entry), [open, setOpen] = useState(!settled), title = instanceTitle(instance, definition), badgeTrim = useBadgeCapTrim(), status = /* @__PURE__ */ jsxs(Flex, {
6119
- align: "center",
6120
- gap: 2,
6121
- style: badgeTrim,
6122
- children: [
6123
- /* @__PURE__ */ jsx(AbortedBadge, {
6124
- instance: instance
6125
- }), settled ? null : /* @__PURE__ */ jsx(OlderDefinitionBadge, {
6126
- instance: instance
6127
- }), !ready && entry.invalid === void 0 ? /* @__PURE__ */ jsx(Spinner, {
6128
- muted: !0
6129
- }) : null ]
6130
- });
6784
+ function InstanceAccordion({entry: entry, children: children, defaultOpen: defaultOpen}) {
6785
+ const definition = useDefinition(entry), {instance: instance} = entry, settled = !isLiveEntry(entry), title = instanceTitle(instance, definition), badgeTrim = useBadgeCapTrim();
6131
6786
  /* @__PURE__ */
6132
- return jsxs(Stack, {
6133
- gap: 2,
6134
- children: [
6135
- /* @__PURE__ */ jsx(Card, {
6136
- style: {
6137
- position: "sticky",
6138
- top: 0,
6139
- zIndex: 1
6140
- },
6141
- children: /* @__PURE__ */ jsxs(Flex, {
6787
+ return jsx(CollapsibleBand, {
6788
+ defaultOpen: defaultOpen ?? !settled,
6789
+ title: title,
6790
+ header: /* @__PURE__ */ jsxs(Fragment, {
6791
+ children: [
6792
+ /* @__PURE__ */ jsxs(Flex, {
6142
6793
  align: "center",
6143
- gap: 1,
6794
+ gap: 2,
6795
+ paddingLeft: 2,
6796
+ style: {
6797
+ minWidth: 0
6798
+ },
6144
6799
  children: [
6145
- /* @__PURE__ */ jsx(Box, {
6146
- flex: 1,
6147
- children: /* @__PURE__ */ jsx(Button, {
6148
- "aria-label": open ? `Collapse ${title}` : `Expand ${title}`,
6149
- mode: "bleed",
6150
- onClick: () => setOpen(v => !v),
6151
- paddingX: 2,
6152
- paddingY: 3,
6153
- radius: 3,
6154
- width: "fill",
6155
- children: /* @__PURE__ */ jsxs(Flex, {
6156
- align: "center",
6157
- gap: 2,
6158
- children: [
6159
- /* @__PURE__ */ jsx(Text, {
6160
- size: 1,
6161
- children: open ? /* @__PURE__ */ jsx(ChevronDownIcon, {}) : /* @__PURE__ */ jsx(ChevronRightIcon, {})
6162
- }),
6163
- /* @__PURE__ */ jsx(Text, {
6164
- size: 1,
6165
- weight: "medium",
6166
- children: title
6167
- }), status ]
6168
- })
6169
- })
6800
+ /* @__PURE__ */ jsx(Text, {
6801
+ size: 1,
6802
+ style: {
6803
+ minWidth: 0
6804
+ },
6805
+ textOverflow: "ellipsis",
6806
+ weight: "medium",
6807
+ children: title
6170
6808
  }),
6171
- /* @__PURE__ */ jsx(InstanceDebugMenu, {
6172
- entry: entry
6809
+ /* @__PURE__ */ jsxs(Flex, {
6810
+ align: "center",
6811
+ gap: 2,
6812
+ style: badgeTrim,
6813
+ children: [
6814
+ /* @__PURE__ */ jsx(AbortedBadge, {
6815
+ instance: instance
6816
+ }), settled ? null : /* @__PURE__ */ jsx(OlderDefinitionBadge, {
6817
+ instance: instance
6818
+ }) ]
6173
6819
  }) ]
6174
- })
6175
- }), open ?
6176
- /* @__PURE__ */ jsx(Stack, {
6820
+ }),
6821
+ /* @__PURE__ */ jsx(TrailingHairline, {}),
6822
+ /* @__PURE__ */ jsx(InstanceDebugMenu, {
6823
+ entry: entry
6824
+ }) ]
6825
+ }),
6826
+ sticky: !0,
6827
+ children: /* @__PURE__ */ jsx(Stack, {
6177
6828
  gap: 3,
6178
6829
  paddingBottom: 2,
6179
6830
  paddingLeft: 5,
6180
6831
  children: children
6181
- }) : null ]
6832
+ })
6182
6833
  });
6183
6834
  }
6184
6835
 
6185
6836
  function PartOfBand({instance: instance}) {
6186
- const parentRef = instance.ancestors.at(-1), parentId = parentRef ? extractDocumentId(parentRef.id) : null, parentEntry = useWorkflowInstanceEntry(parentId), parentDefinition = useDefinition(parentEntry);
6837
+ const parentRef = instance.ancestors.at(-1), parentId = parentRef ? toBareId(parentRef.id) : null, parentEntry = useWorkflowInstanceEntry(parentId), parentDefinition = useDefinition(parentEntry);
6187
6838
  if (!parentId) return null;
6188
6839
  const title = parentEntry ? instanceTitle(parentEntry.instance, parentDefinition) : parentId;
6189
6840
  /* @__PURE__ */
@@ -6434,18 +7085,6 @@ const NODE_ICON = {
6434
7085
  done: CheckmarkCircleIcon
6435
7086
  };
6436
7087
 
6437
- function Hairline({style: style}) {
6438
- const {color: color} = useTheme_v2();
6439
- /* @__PURE__ */
6440
- return jsx(Card, {
6441
- style: {
6442
- height: "1px",
6443
- background: color.border,
6444
- ...style
6445
- }
6446
- });
6447
- }
6448
-
6449
7088
  function WorkflowSpine({definition: definition, evaluation: evaluation, instance: instance}) {
6450
7089
  const nodes = deriveSpine({
6451
7090
  definition: definition,
@@ -6464,7 +7103,8 @@ function WorkflowSpine({definition: definition, evaluation: evaluation, instance
6464
7103
  style: {
6465
7104
  transform: "translateY(-0.5px)",
6466
7105
  width: connectorWidth
6467
- }
7106
+ },
7107
+ weight: 1
6468
7108
  }) : null,
6469
7109
  /* @__PURE__ */ jsx(SpineNodeView, {
6470
7110
  definition: definition,
@@ -6569,7 +7209,9 @@ function StageOverviewCard({overview: overview}) {
6569
7209
  })
6570
7210
  }), hasLiveZone ? /* @__PURE__ */ jsxs(Fragment, {
6571
7211
  children: [
6572
- /* @__PURE__ */ jsx(Hairline, {}),
7212
+ /* @__PURE__ */ jsx(Hairline, {
7213
+ weight: 1
7214
+ }),
6573
7215
  /* @__PURE__ */ jsx(Box, {
6574
7216
  padding: 2,
6575
7217
  children: /* @__PURE__ */ jsxs(Stack, {
@@ -6622,31 +7264,32 @@ function AdvanceCard({advance: advance}) {
6622
7264
  }
6623
7265
 
6624
7266
  function StampLines({stamp: stamp}) {
6625
- const verb = stamp.kind === "entered" ? "Entered" : "Completed", ago = formatTimeAgo(stamp.at), absolute = formatShortDateTime(stamp.at);
6626
- return ago === "" ? /* @__PURE__ */ jsxs(Text, {
7267
+ const face = stampFace(stamp.kind === "entered" ? "Entered" : "Completed", stamp.at);
7268
+ return face.detail === void 0 ? /* @__PURE__ */ jsx(Text, {
6627
7269
  muted: !0,
6628
7270
  size: 1,
6629
- children: [ verb, " ", absolute ]
7271
+ children: face.lead
6630
7272
  }) : /* @__PURE__ */ jsxs(Stack, {
6631
7273
  gap: 3,
6632
7274
  children: [
6633
- /* @__PURE__ */ jsxs(Text, {
7275
+ /* @__PURE__ */ jsx(Text, {
6634
7276
  muted: !0,
6635
7277
  size: 1,
6636
- children: [ verb, " ", ago ]
7278
+ children: face.lead
6637
7279
  }),
6638
7280
  /* @__PURE__ */ jsx(Text, {
6639
7281
  muted: !0,
6640
7282
  size: 1,
6641
- children: absolute
7283
+ children: face.detail
6642
7284
  }) ]
6643
7285
  });
6644
7286
  }
6645
7287
 
6646
- function WorkflowInstanceSection({entry: entry, onOpenActivity: onOpenActivity}) {
7288
+ function WorkflowInstanceSection({defaultOpen: defaultOpen, entry: entry, onOpenActivity: onOpenActivity}) {
6647
7289
  const definition = useDefinition(entry), {instance: instance} = entry;
6648
7290
  /* @__PURE__ */
6649
7291
  return jsxs(InstanceAccordion, {
7292
+ defaultOpen: defaultOpen,
6650
7293
  entry: entry,
6651
7294
  children: [ instance.ancestors.length > 0 ? /* @__PURE__ */ jsx(PartOfBand, {
6652
7295
  instance: instance
@@ -6706,7 +7349,7 @@ function StageSection({entry: entry, onOpenActivity: onOpenActivity}) {
6706
7349
  });
6707
7350
  }
6708
7351
 
6709
- const WorkflowsView = props => {
7352
+ const VIEW_TAB_CODEC = workflowsTabCodec([ "overview", "for-me" ]), WorkflowsView = props => {
6710
7353
  const {documentId: documentId, schemaType: schemaType} = props, {isDocResolved: isDocResolved, mappings: mappings, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {entries: entries} = useWorkflowsForDocument(documentId), resolved = isDocResolved(documentId), docTypeMappings = mappingsForDocType(mappings, schemaType.name);
6711
7354
  if (entries.length === 0) return discoveryInvalid !== void 0 ? /* @__PURE__ */ jsx(Box, {
6712
7355
  padding: 4,
@@ -6748,11 +7391,9 @@ const WorkflowsView = props => {
6748
7391
  };
6749
7392
 
6750
7393
  function DiscoverySpinner() {
6751
- const [visible, setVisible] = useState(!1);
6752
- return useEffect(() => {
6753
- const timer = setTimeout(() => setVisible(!0), 1e3);
6754
- return () => clearTimeout(timer);
6755
- }, []), /* @__PURE__ */ jsx(Flex, {
7394
+ const visible = useDelayedFlag(!0, 1e3);
7395
+ /* @__PURE__ */
7396
+ return jsx(Flex, {
6756
7397
  align: "center",
6757
7398
  height: "fill",
6758
7399
  justify: "center",
@@ -6762,19 +7403,13 @@ function DiscoverySpinner() {
6762
7403
  });
6763
7404
  }
6764
7405
 
6765
- const GROUP_GAP = 1;
7406
+ const BOOT_CUE_DELAY_MS = 400, GROUP_GAP = 1;
6766
7407
 
6767
7408
  function useWorkflowsTab() {
6768
7409
  const {params: params, setParams: setParams} = usePaneRouter();
6769
7410
  return {
6770
- view: params?.[WORKFLOWS_TAB_PARAM] === "for-me" ? "for-me" : "overview",
6771
- setView: next => {
6772
- const {[WORKFLOWS_TAB_PARAM]: _tab, ...rest} = params ?? {};
6773
- setParams(next === "overview" ? rest : {
6774
- ...rest,
6775
- [WORKFLOWS_TAB_PARAM]: next
6776
- });
6777
- }
7411
+ view: VIEW_TAB_CODEC.read(params),
7412
+ setView: next => setParams(VIEW_TAB_CODEC.write(params, next))
6778
7413
  };
6779
7414
  }
6780
7415
 
@@ -6793,7 +7428,8 @@ function useOpenActivity(entries) {
6793
7428
  }
6794
7429
 
6795
7430
  function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappings: docTypeMappings, initialValue: initialValue}) {
6796
- const identity = useAssignmentIdentity(), panelId = useId(), {view: view, setView: setView} = useWorkflowsTab(), {openActivity: openActivity, setOpenActivity: setOpenActivity, dialogEntry: dialogEntry} = useOpenActivity(entries), active = entries.filter(isLiveEntry), finished = entries.filter(e => !isLiveEntry(e)), forMe = forMeWorkOf(active, identity), assignedWorkUnknown = active.some(e => e.invalid !== void 0), sectionFor = e => /* @__PURE__ */ jsx(WorkflowInstanceSection, {
7431
+ const identity = useAssignmentIdentity(), panelId = useId(), {params: params} = usePaneRouter(), {view: view, setView: setView} = useWorkflowsTab(), {openActivity: openActivity, setOpenActivity: setOpenActivity, dialogEntry: dialogEntry} = useOpenActivity(entries), active = entries.filter(isLiveEntry), finished = entries.filter(e => !isLiveEntry(e)), forMe = forMeWorkOf(active, identity), [landing] = useState(() => focusedLanding(params?.[WORKFLOWS_FOCUS_PARAM], entries.map(e => e.instance._id))), assignedWorkUnknown = active.some(e => e.invalid !== void 0), booting = useDelayedFlag(active.some(e => !e.ready && e.invalid === void 0), BOOT_CUE_DELAY_MS), sectionFor = e => /* @__PURE__ */ jsx(WorkflowInstanceSection, {
7432
+ defaultOpen: landingDefaultOpen(landing, e.instance._id),
6797
7433
  entry: e,
6798
7434
  onOpenActivity: activityName => setOpenActivity({
6799
7435
  instanceId: e.instance._id,
@@ -6813,6 +7449,7 @@ function WorkflowsPanel({entries: entries, documentId: documentId, docTypeMappin
6813
7449
  padding: 3,
6814
7450
  children: [
6815
7451
  /* @__PURE__ */ jsx(GroupHeading, {
7452
+ busy: booting,
6816
7453
  count: active.length,
6817
7454
  end:
6818
7455
  /* @__PURE__ */ jsx(Box, {
@@ -6948,10 +7585,8 @@ function workflowsView(S) {
6948
7585
  return S.view.component(WorkflowsView).id(WORKFLOWS_VIEW_ID).title("Workflows");
6949
7586
  }
6950
7587
 
6951
- function workflowDefaultDocumentNode(options) {
6952
- return (S, ctx) => {
6953
- if (mappingForDocType(options.mappings, ctx.schemaType)) return S.document().views([ S.view.form(), workflowsView(S) ]);
6954
- };
7588
+ function workflowDefaultDocumentNode() {
7589
+ return S => S.document().views([ S.view.form(), workflowsView(S) ]);
6955
7590
  }
6956
7591
 
6957
7592
  function lockTitleFor(args) {
@@ -6964,26 +7599,12 @@ function lockTitleFor(args) {
6964
7599
  return workflows.length === 1 ? `The ${workflowList} workflow is holding this document.` : `The ${workflowList} workflows are holding this document.`;
6965
7600
  }
6966
7601
 
6967
- const EMPTY = {
6968
- byType: /* @__PURE__ */ new Map,
6969
- definitions: /* @__PURE__ */ new Map,
6970
- warnings: []
6971
- };
6972
-
6973
7602
  function resolveAutoStart(args) {
6974
- const {autoStart: autoStart, context: context, knownDocTypes: knownDocTypes, definitions: definitions} = args;
6975
- if (autoStart === void 0) return EMPTY;
6976
- const evaluated = evaluateMap(autoStart, context);
6977
- if ("warning" in evaluated) return {
6978
- byType: /* @__PURE__ */ new Map,
6979
- definitions: /* @__PURE__ */ new Map,
6980
- warnings: [ evaluated.warning ]
6981
- };
6982
- const map = evaluated.map, latestByName = new Map(latestDeployedDefinitions(definitions).map(d => [ d.name, d ])), byType = /* @__PURE__ */ new Map, resolvedDefinitions = /* @__PURE__ */ new Map, warnings = [];
6983
- for (const [docType, spec] of Object.entries(map)) {
7603
+ const {autoStart: autoStart, knownDocTypes: knownDocTypes, definitions: definitions} = args, latestByName = new Map(latestDeployedDefinitions(definitions).map(d => [ d.name, d ])), byType = /* @__PURE__ */ new Map, resolvedDefinitions = /* @__PURE__ */ new Map, warnings = [];
7604
+ for (const [docType, names] of Object.entries(autoStart)) {
6984
7605
  const entry = resolveEntry({
6985
7606
  docType: docType,
6986
- spec: spec,
7607
+ names: names,
6987
7608
  knownDocTypes: knownDocTypes,
6988
7609
  latestByName: latestByName
6989
7610
  });
@@ -6999,35 +7620,15 @@ function resolveAutoStart(args) {
6999
7620
  };
7000
7621
  }
7001
7622
 
7002
- function evaluateMap(autoStart, context) {
7003
- let map;
7004
- try {
7005
- map = typeof autoStart == "function" ? autoStart(context) : autoStart;
7006
- } catch (err) {
7007
- return {
7008
- warning: `autoStart: config function threw — ignoring it (${describeError(err)}).`
7009
- };
7010
- }
7011
- return typeof map != "object" || map === null ? {
7012
- warning: "autoStart: config did not resolve to an object — ignoring it."
7013
- } : {
7014
- map: map
7015
- };
7016
- }
7017
-
7018
- function specNames(spec) {
7019
- return typeof spec == "string" ? [ spec ] : Array.isArray(spec) ? spec : [];
7020
- }
7021
-
7022
7623
  function resolveEntry(args) {
7023
- const {docType: docType, spec: spec, knownDocTypes: knownDocTypes, latestByName: latestByName} = args;
7624
+ const {docType: docType, names: names, knownDocTypes: knownDocTypes, latestByName: latestByName} = args;
7024
7625
  if (!knownDocTypes.has(docType)) return {
7025
7626
  valid: [],
7026
7627
  definitions: [],
7027
7628
  warnings: [ `autoStart: no schema type "${docType}" — entry skipped.` ]
7028
7629
  };
7029
7630
  const valid = [], definitions = [], warnings = [];
7030
- for (const name of specNames(spec)) {
7631
+ for (const name of names) {
7031
7632
  const definition = latestByName.get(name), issue = workflowIssue({
7032
7633
  name: name,
7033
7634
  docType: docType,
@@ -7053,11 +7654,9 @@ function workflowIssue(args) {
7053
7654
  const {name: name, docType: docType, definition: definition} = args;
7054
7655
  if (definition === void 0) return `autoStart: "${docType}" → workflow "${name}" is not deployed — skipped.`;
7055
7656
  if (!isStartableDefinition(definition)) return `autoStart: "${docType}" → workflow "${name}" is spawn-only, not startable — skipped.`;
7056
- if (!acceptsDocumentType({
7057
- fields: [ ...definition.fields ?? [] ]
7058
- }, docType)) return `autoStart: "${docType}" → workflow "${name}" has no subject accepting "${docType}" — skipped.`;
7059
- const subject = (definition.fields ?? []).find(isSubjectEntry);
7060
- if (subject !== void 0 && !isInputSourced(subject)) return `autoStart: "${docType}" → workflow "${name}" has a self-filling subject (not caller-provided), so the document can't be its subject — skipped.`;
7657
+ const subject = documentSubjectEntry(definition), acceptsType = subject?.types === void 0 || subject.types.includes(docType);
7658
+ if (subject === void 0 || !acceptsType) return `autoStart: "${docType}" → workflow "${name}" has no document subject accepting "${docType}" — skipped.`;
7659
+ if (!isInputSourced(subject)) return `autoStart: "${docType}" → workflow "${name}" has a self-filling subject (not caller-provided), so the document can't be its subject — skipped.`;
7061
7660
  }
7062
7661
 
7063
7662
  function contentDocumentTypes(schema) {
@@ -7077,20 +7676,31 @@ const EMPTY_SNAPSHOT = {
7077
7676
  };
7078
7677
 
7079
7678
  function useDiscoveryState(args) {
7080
- const {registered: registered, requested: requested, contentResource: contentResource} = args, watching = registered.length + requested.length > 0, [latest, setLatest] = useState(EMPTY_SNAPSHOT), [resolved, setResolved] = useState(EMPTY_SNAPSHOT), onSnapshot = useCallback(snapshot => {
7081
- setLatest(snapshot), snapshot.instances !== void 0 && setResolved(snapshot);
7082
- }, []);
7083
- useEffect(() => {
7084
- watching || (setLatest(EMPTY_SNAPSHOT), setResolved(EMPTY_SNAPSHOT));
7085
- }, [ watching ]);
7086
- const filter = useMemo(() => ({
7087
- documents: registered.map(id => gdrFromResource(contentResource, id)),
7679
+ const {observed: observed, requested: requested, contentResource: contentResource} = args, documentWatching = observed.length > 0, requestedWatching = requested.length > 0, documents = useDiscoveryStream(documentWatching), explicit = useDiscoveryStream(requestedWatching), documentFilter = useMemo(() => documentWatching ? {
7680
+ documents: observed.map(id => gdrFromResource(contentResource, id)),
7681
+ includeCompleted: !0
7682
+ } : void 0, [ documentWatching, observed, contentResource ]), requestedFilter = useMemo(() => requestedWatching ? {
7088
7683
  ids: requested,
7089
7684
  includeCompleted: !0
7090
- }), [ registered, requested, contentResource ]);
7685
+ } : void 0, [ requestedWatching, requested ]), resolved = useMemo(() => mergeResolved(documents.resolved, explicit.resolved), [ documents.resolved, explicit.resolved ]);
7091
7686
  return {
7092
- watching: watching,
7093
- filter: filter,
7687
+ documentFilter: documentFilter,
7688
+ requestedFilter: requestedFilter,
7689
+ loading: documents.loading || explicit.loading,
7690
+ invalid: documents.invalid ?? explicit.invalid,
7691
+ resolved: resolved,
7692
+ onDocumentSnapshot: documents.onSnapshot,
7693
+ onRequestedSnapshot: explicit.onSnapshot
7694
+ };
7695
+ }
7696
+
7697
+ function useDiscoveryStream(watching) {
7698
+ const [latest, setLatest] = useState(EMPTY_SNAPSHOT), [resolved, setResolved] = useState(EMPTY_SNAPSHOT), onSnapshot = useCallback(snapshot => {
7699
+ setLatest(snapshot), snapshot.instances !== void 0 && setResolved(snapshot);
7700
+ }, []);
7701
+ return useEffect(() => {
7702
+ watching || (setLatest(EMPTY_SNAPSHOT), setResolved(EMPTY_SNAPSHOT));
7703
+ }, [ watching ]), {
7094
7704
  loading: watching && latest.instances === void 0 && latest.invalid === void 0,
7095
7705
  invalid: watching ? latest.invalid : void 0,
7096
7706
  resolved: resolved,
@@ -7098,32 +7708,107 @@ function useDiscoveryState(args) {
7098
7708
  };
7099
7709
  }
7100
7710
 
7101
- function indexDiscoveredInstances(args) {
7102
- const {instances: instances, registered: registered, contentResource: contentResource} = args, ordered = [ ...instances ].sort((a, b) => b.startedAt.localeCompare(a.startedAt)), index = /* @__PURE__ */ new Map;
7103
- for (const bareId of registered) {
7104
- const uri = gdrFromResource(contentResource, bareId), watching = ordered.filter(inst => instanceWatchesDocument(inst, uri));
7105
- watching.length > 0 && index.set(bareId, watching.map(inst => inst._id));
7711
+ function mergeResolved(documents, explicit) {
7712
+ const rows = [ ...documents.instances ?? [], ...explicit.instances ?? [] ], freshest = /* @__PURE__ */ new Map;
7713
+ for (const instance of rows) {
7714
+ const current = freshest.get(instance._id);
7715
+ (current === void 0 || Date.parse(instance.lastChangedAt) > Date.parse(current.lastChangedAt)) && freshest.set(instance._id, instance);
7106
7716
  }
7717
+ return {
7718
+ instances: documents.instances === void 0 && explicit.instances === void 0 ? void 0 : [ ...freshest.values() ],
7719
+ invalid: documents.invalid ?? explicit.invalid,
7720
+ registered: documents.registered
7721
+ };
7722
+ }
7723
+
7724
+ function createDiscoveryIndexer(deriveWatchSet = subscriptionDocumentsForInstance) {
7725
+ const cached = /* @__PURE__ */ new Map;
7726
+ return {
7727
+ index(args) {
7728
+ const liveIds = new Set(args.instances.map(instance => instance._id));
7729
+ for (const id of cached.keys()) liveIds.has(id) || cached.delete(id);
7730
+ const watchedById = /* @__PURE__ */ new Map;
7731
+ for (const instance of args.instances) {
7732
+ let row = cached.get(instance._id);
7733
+ row?.source !== instance && (row = {
7734
+ source: instance,
7735
+ globalDocumentIds: [ ...new Set(deriveWatchSet(instance).documents.map(document => document.globalDocumentId)) ]
7736
+ }, cached.set(instance._id, row)), watchedById.set(instance._id, row.globalDocumentIds);
7737
+ }
7738
+ return buildDiscoveryIndex(args, watchedById);
7739
+ }
7740
+ };
7741
+ }
7742
+
7743
+ function buildDiscoveryIndex(args, watchedById) {
7744
+ const {instances: instances, registered: registered, contentResource: contentResource} = args, ordered = [ ...instances ].sort((a, b) => b.startedAt.localeCompare(a.startedAt)), index = /* @__PURE__ */ new Map, registeredByUri = new Map(registered.map(bareId => [ gdrFromResource(contentResource, bareId), bareId ]));
7745
+ for (const instance of ordered) appendInstanceWatches({
7746
+ index: index,
7747
+ registeredByUri: registeredByUri,
7748
+ instanceId: instance._id,
7749
+ watched: watchedById.get(instance._id) ?? []
7750
+ });
7107
7751
  return {
7108
7752
  instances: new Map(ordered.map(inst => [ inst._id, inst ])),
7109
7753
  index: index
7110
7754
  };
7111
7755
  }
7112
7756
 
7113
- function mergeEntries(args) {
7114
- const map = /* @__PURE__ */ new Map, merged = new Map([ ...args.seeded, ...args.discovered ]);
7115
- for (const [instanceId, inst] of merged) {
7116
- const report = args.reports.get(instanceId);
7117
- map.set(instanceId, {
7118
- instance: report?.evaluation?.instance ?? inst,
7119
- committed: inst,
7120
- evaluation: report?.evaluation,
7121
- ready: report?.ready ?? !1,
7122
- invalid: report?.invalid,
7123
- guards: report?.guards
7124
- });
7757
+ function appendInstanceWatches(args) {
7758
+ const {index: index, registeredByUri: registeredByUri, instanceId: instanceId, watched: watched} = args;
7759
+ for (const uri of watched) {
7760
+ const bareId = registeredByUri.get(uri);
7761
+ if (bareId === void 0) continue;
7762
+ const ids = index.get(bareId);
7763
+ ids === void 0 ? index.set(bareId, [ instanceId ]) : ids.push(instanceId);
7764
+ }
7765
+ }
7766
+
7767
+ function sameInstance(a, b) {
7768
+ return a === b || a._id === b._id && a._rev === b._rev;
7769
+ }
7770
+
7771
+ function sameEntry(a, b) {
7772
+ return sameInstance(a.instance, b.instance) && sameInstance(a.committed, b.committed) && a.evaluation === b.evaluation && a.ready === b.ready && a.invalid === b.invalid && a.guards === b.guards;
7773
+ }
7774
+
7775
+ function reconciled(args) {
7776
+ const {entries: entries, previous: previous, same: same} = args, map = /* @__PURE__ */ new Map;
7777
+ let allReused = !0;
7778
+ for (const [key, next] of entries) {
7779
+ const prev = previous?.get(key), reusable = prev !== void 0 && same(prev, next);
7780
+ reusable || (allReused = !1), map.set(key, reusable ? prev : next);
7125
7781
  }
7126
- return map;
7782
+ return previous !== void 0 && allReused && previous.size === map.size ? previous : map;
7783
+ }
7784
+
7785
+ function entryOf(inst, report) {
7786
+ return {
7787
+ instance: report?.evaluation?.instance ?? inst,
7788
+ committed: inst,
7789
+ evaluation: report?.evaluation,
7790
+ ready: report?.ready ?? terminalState(inst) !== "in-flight",
7791
+ invalid: report?.invalid,
7792
+ guards: report?.guards
7793
+ };
7794
+ }
7795
+
7796
+ function mergeEntries(args) {
7797
+ const {seeded: seeded, discovered: discovered, reports: reports, previous: previous} = args, merged = new Map([ ...seeded, ...discovered ]);
7798
+ return reconciled({
7799
+ entries: [ ...merged ].map(([instanceId, inst]) => [ instanceId, entryOf(inst, reports.get(instanceId)) ]),
7800
+ previous: previous,
7801
+ same: sameEntry
7802
+ });
7803
+ }
7804
+
7805
+ function deriveByDocument(args) {
7806
+ const {docIndex: docIndex, byInstance: byInstance, previous: previous} = args;
7807
+ return reconciled({
7808
+ entries: [ ...docIndex ].map(([docId, instanceIds]) => [ docId, instanceIds.map(id => byInstance.get(id)).filter(entry => entry !== void 0) ]),
7809
+ previous: previous,
7810
+ same: (a, b) => a.length === b.length && b.every((entry, i) => entry === a[i])
7811
+ });
7127
7812
  }
7128
7813
 
7129
7814
  const REPORT_KEYS = [ "evaluation", "ready", "invalid", "guards", "fireAction", "editField", "previewField", "discardFieldPreview" ];
@@ -7144,7 +7829,70 @@ function nextReports(prev, update) {
7144
7829
  return next.set(instanceId, report), next;
7145
7830
  }
7146
7831
 
7147
- const notReady = (..._args) => Promise.reject(new Error("workflow session not ready yet"));
7832
+ const REPORT_FLUSH_MS = 50, EMPTY_COMMITTED = {
7833
+ seeded: /* @__PURE__ */ new Map,
7834
+ discovered: /* @__PURE__ */ new Map,
7835
+ docIndex: /* @__PURE__ */ new Map
7836
+ };
7837
+
7838
+ function createEntriesStore() {
7839
+ let committed = EMPTY_COMMITTED, reports = /* @__PURE__ */ new Map, byInstance = /* @__PURE__ */ new Map, byDocument = /* @__PURE__ */ new Map;
7840
+ const listeners = /* @__PURE__ */ new Set;
7841
+ let flushTimer;
7842
+ const recompute = () => {
7843
+ flushTimer !== void 0 && (clearTimeout(flushTimer), flushTimer = void 0);
7844
+ const nextByInstance = mergeEntries({
7845
+ ...committed,
7846
+ reports: reports,
7847
+ previous: byInstance
7848
+ }), nextByDocument = deriveByDocument({
7849
+ docIndex: committed.docIndex,
7850
+ byInstance: nextByInstance,
7851
+ previous: byDocument
7852
+ });
7853
+ if (!(nextByInstance === byInstance && nextByDocument === byDocument)) {
7854
+ byInstance = nextByInstance, byDocument = nextByDocument;
7855
+ for (const listener of [ ...listeners ]) listener();
7856
+ }
7857
+ };
7858
+ return {
7859
+ setCommitted(inputs) {
7860
+ committed = inputs, recompute();
7861
+ },
7862
+ report(instanceId, report) {
7863
+ const next = nextReports(reports, {
7864
+ instanceId: instanceId,
7865
+ report: report
7866
+ });
7867
+ next !== reports && (reports = next, flushTimer ??= setTimeout(recompute, REPORT_FLUSH_MS));
7868
+ },
7869
+ getReport: instanceId => reports.get(instanceId),
7870
+ getInstance: instanceId => byInstance.get(instanceId),
7871
+ getDocument: docId => byDocument.get(docId) ?? EMPTY_ENTRIES,
7872
+ subscribe(listener) {
7873
+ return listeners.add(listener), () => {
7874
+ listeners.delete(listener);
7875
+ };
7876
+ },
7877
+ dispose() {
7878
+ flushTimer !== void 0 && clearTimeout(flushTimer), flushTimer = void 0, listeners.clear();
7879
+ }
7880
+ };
7881
+ }
7882
+
7883
+ function sessionPlan(args) {
7884
+ const demandedIds = new Set(args.demanded), evaluated = [ ...args.discovered.values() ].filter(inst => demandedIds.has(inst._id));
7885
+ return {
7886
+ evaluated: evaluated,
7887
+ guardScope: evaluated.map(inst => inst._id).sort()
7888
+ };
7889
+ }
7890
+
7891
+ function useDefaultLayout(props) {
7892
+ return useMemo(() => props.renderDefault(props), [ props ]);
7893
+ }
7894
+
7895
+ const notReady = (..._args) => Promise.reject(new Error("workflow session not ready yet")), NO_REGISTERED_DOCUMENTS = [], DEMAND_RELEASE_GRACE_MS = 500;
7148
7896
 
7149
7897
  function workspaceParts(config) {
7150
7898
  if (!config.projectId || !config.dataset) throw new Error("workflow plugin requires a workspace with projectId and dataset");
@@ -7172,10 +7920,11 @@ class InstanceEvaluatorBoundary extends Component {
7172
7920
  }
7173
7921
 
7174
7922
  function InstanceEvaluator(props) {
7175
- const {engine: engine, instanceId: instanceId, grantsFromPath: grantsFromPath, onReport: onReport} = props, {evaluation: evaluation, ready: ready, invalid: invalid, guards: guards, fireAction: fireAction, editField: editField, previewField: previewField, discardFieldPreview: discardFieldPreview} = useWorkflowSession({
7923
+ const {engine: engine, instanceId: instanceId, grantsFromPath: grantsFromPath, guardScope: guardScope, onReport: onReport} = props, {evaluation: evaluation, ready: ready, invalid: invalid, guards: guards, fireAction: fireAction, editField: editField, previewField: previewField, discardFieldPreview: discardFieldPreview} = useWorkflowSession({
7176
7924
  engine: engine,
7177
7925
  instanceId: instanceId,
7178
- grantsFromPath: grantsFromPath
7926
+ grantsFromPath: grantsFromPath,
7927
+ guardScope: guardScope
7179
7928
  });
7180
7929
  return useEffect(() => {
7181
7930
  onReport(instanceId, {
@@ -7193,10 +7942,10 @@ function InstanceEvaluator(props) {
7193
7942
  }
7194
7943
 
7195
7944
  function DiscoverySubscription(props) {
7196
- const {engine: engine, registered: registered, filter: filter, onSnapshot: onSnapshot} = props, {instances: instances, invalid: invalid} = useWorkflowInstances({
7945
+ const {engine: engine, registered: registered, filter: filter, onSnapshot: onSnapshot} = props, {instances: instances, unreadable: unreadable} = useWorkflowInstances({
7197
7946
  engine: engine,
7198
7947
  filter: filter
7199
- });
7948
+ }), invalid = unreadable[0];
7200
7949
  return useEffect(() => {
7201
7950
  onSnapshot({
7202
7951
  instances: instances,
@@ -7218,9 +7967,9 @@ function engineOptionsFrom(config) {
7218
7967
  }
7219
7968
 
7220
7969
  function WorkflowProvider(props) {
7221
- const {config: config} = props, studioClient = useClient({
7970
+ const {config: config} = props, defaultLayout = useDefaultLayout(props), studioClient = useClient({
7222
7971
  apiVersion: WORKFLOW_API_VERSION
7223
- }), schema = useSchema(), {name: workspaceName} = useWorkspace(), {projectId: projectId, dataset: contentDataset} = workspaceParts(studioClient.config()), workflowDataset = config.workflowDataset ?? contentDataset, binding = useMemo(() => ({
7972
+ }), schema = useSchema(), {projectId: projectId, dataset: contentDataset} = workspaceParts(studioClient.config()), workflowDataset = config.workflowDataset ?? contentDataset, binding = useMemo(() => ({
7224
7973
  engineResource: {
7225
7974
  type: "dataset",
7226
7975
  id: `${projectId}.${workflowDataset}`
@@ -7238,16 +7987,21 @@ function WorkflowProvider(props) {
7238
7987
  }), grantsFromPath = aclPathForResource({
7239
7988
  type: "dataset",
7240
7989
  id: `${projectId}.${workflowDataset}`
7241
- }), {ids: registered, acquire: register, release: unregister} = useRefcountedIds(), {ids: requested, acquire: requestInstance, release: releaseInstance} = useRefcountedIds(), [seeded, setSeeded] = useState(/* @__PURE__ */ new Map), seedInstance = useCallback(instance => {
7990
+ }), {ids: registered, acquire: registerActive, release: unregisterActive} = useRefcountedIds(), {ids: observed, acquire: retainRegistration, release: releaseRegistration} = useRefcountedIds({
7991
+ releaseDelayMs: DEMAND_RELEASE_GRACE_MS
7992
+ }), register = useCallback(id => {
7993
+ registerActive(id), retainRegistration(id);
7994
+ }, [ registerActive, retainRegistration ]), unregister = useCallback(id => {
7995
+ unregisterActive(id), releaseRegistration(id);
7996
+ }, [ unregisterActive, releaseRegistration ]), {ids: requested, acquire: requestInstance, release: releaseInstance} = useRefcountedIds(), {ids: evaluationDemand, acquire: requestEvaluation, release: releaseEvaluation} = useRefcountedIds({
7997
+ releaseDelayMs: DEMAND_RELEASE_GRACE_MS
7998
+ }), [seeded, setSeeded] = useState(/* @__PURE__ */ new Map), seedInstance = useCallback(instance => {
7242
7999
  setSeeded(prev => new Map(prev).set(instance._id, instance));
7243
- }, []), [startRequest2, setStartRequest] = useState(void 0), openStartDialog = useCallback(r => setStartRequest(r), []), closeStartDialog = useCallback(() => setStartRequest(void 0), []), [reports, setReports] = useState(/* @__PURE__ */ new Map), onReport = useCallback((instanceId, report) => {
7244
- setReports(prev => nextReports(prev, {
7245
- instanceId: instanceId,
7246
- report: report
7247
- }));
7248
- }, []), reportsRef = useRef(reports);
7249
- reportsRef.current = reports;
7250
- const drainRef = useRef(void 0), drainCommitted = useCallback(async instanceId => {
8000
+ }, []), [startRequest2, setStartRequest] = useState(void 0), openStartDialog = useCallback(r => setStartRequest(r), []), closeStartDialog = useCallback(() => setStartRequest(void 0), []), [entriesStore] = useState(createEntriesStore);
8001
+ useEffect(() => () => entriesStore.dispose(), [ entriesStore ]);
8002
+ const onReport = useCallback((instanceId, report) => {
8003
+ entriesStore.report(instanceId, report);
8004
+ }, [ entriesStore ]), drainRef = useRef(void 0), drainCommitted = useCallback(async instanceId => {
7251
8005
  try {
7252
8006
  await (drainRef.current?.(instanceId));
7253
8007
  } catch (err) {
@@ -7256,7 +8010,7 @@ function WorkflowProvider(props) {
7256
8010
  });
7257
8011
  }
7258
8012
  }, []), fireActionFor = useCallback((instanceId, args) => {
7259
- const report = reportsRef.current.get(instanceId);
8013
+ const report = entriesStore.getReport(instanceId);
7260
8014
  return report ? report.fireAction({
7261
8015
  activity: args.activity,
7262
8016
  action: args.action,
@@ -7264,15 +8018,15 @@ function WorkflowProvider(props) {
7264
8018
  params: args.params
7265
8019
  } : {}
7266
8020
  }).then(async res => (await drainCommitted(instanceId), res)) : notReady(args);
7267
- }, [ drainCommitted ]), editFieldFor = useCallback((instanceId, args) => {
7268
- const report = reportsRef.current.get(instanceId);
8021
+ }, [ entriesStore, drainCommitted ]), editFieldFor = useCallback((instanceId, args) => {
8022
+ const report = entriesStore.getReport(instanceId);
7269
8023
  return report ? report.editField(args).then(async res => (await drainCommitted(instanceId),
7270
8024
  res)) : notReady(args);
7271
- }, [ drainCommitted ]), previewFieldFor = useCallback((instanceId, args) => {
7272
- reportsRef.current.get(instanceId)?.previewField(args);
7273
- }, []), discardFieldPreviewFor = useCallback((instanceId, target) => {
7274
- reportsRef.current.get(instanceId)?.discardFieldPreview(target);
7275
- }, []), drainEffectsFor = useCallback(async instanceId => {
8025
+ }, [ entriesStore, drainCommitted ]), previewFieldFor = useCallback((instanceId, args) => {
8026
+ entriesStore.getReport(instanceId)?.previewField(args);
8027
+ }, [ entriesStore ]), discardFieldPreviewFor = useCallback((instanceId, target) => {
8028
+ entriesStore.getReport(instanceId)?.discardFieldPreview(target);
8029
+ }, [ entriesStore ]), drainEffectsFor = useCallback(async instanceId => {
7276
8030
  await engine.drainEffects({
7277
8031
  instanceId: instanceId
7278
8032
  });
@@ -7292,53 +8046,46 @@ function WorkflowProvider(props) {
7292
8046
  });
7293
8047
  }, [ engine ]);
7294
8048
  drainRef.current = drainEffectsFor;
7295
- const {watching: watching, filter: filter, loading: loading, invalid: discoveryInvalid, resolved: resolved, onSnapshot: onSnapshot} = useDiscoveryState({
7296
- registered: registered,
8049
+ const {documentFilter: documentFilter, requestedFilter: requestedFilter, loading: loading, invalid: discoveryInvalid, resolved: resolved, onDocumentSnapshot: onDocumentSnapshot, onRequestedSnapshot: onRequestedSnapshot} = useDiscoveryState({
8050
+ observed: observed,
7297
8051
  requested: requested,
7298
8052
  contentResource: binding.contentResource
7299
- }), {instances: discovered, index: docIndex} = useMemo(() => indexDiscoveredInstances({
8053
+ }), discoveryIndexer = useMemo(createDiscoveryIndexer, []), {instances: discovered, index: docIndex} = useMemo(() => discoveryIndexer.index({
7300
8054
  instances: resolved.instances ?? [],
7301
8055
  registered: resolved.registered,
7302
8056
  contentResource: binding.contentResource
7303
- }), [ resolved, binding.contentResource ]), resolvedDocs = useMemo(() => new Set(resolved.registered), [ resolved.registered ]);
8057
+ }), [ discoveryIndexer, resolved, binding.contentResource ]), {evaluated: evaluated, guardScope: guardScope} = useMemo(() => sessionPlan({
8058
+ discovered: discovered,
8059
+ demanded: evaluationDemand
8060
+ }), [ discovered, evaluationDemand ]), resolvedDocs = useMemo(() => new Set(resolved.registered), [ resolved.registered ]);
7304
8061
  useEffect(() => {
7305
8062
  setSeeded(prev => {
7306
8063
  const kept = [ ...prev ].filter(([id]) => !discovered.has(id));
7307
8064
  return kept.length === prev.size ? prev : new Map(kept);
7308
8065
  });
7309
- }, [ discovered ]);
7310
- const byInstance = useMemo(() => mergeEntries({
7311
- seeded: seeded,
7312
- discovered: discovered,
7313
- reports: reports
7314
- }), [ discovered, seeded, reports ]), byDocument = useMemo(() => {
7315
- const map = /* @__PURE__ */ new Map;
7316
- for (const [bareDocId, instanceIds] of docIndex) {
7317
- const entries = [];
7318
- for (const instanceId of instanceIds) {
7319
- const entry = byInstance.get(instanceId);
7320
- entry && entries.push(entry);
7321
- }
7322
- map.set(bareDocId, entries);
7323
- }
7324
- return map;
7325
- }, [ byInstance, docIndex ]), isDocResolved = useCallback(docId => resolvedDocs.has(docId), [ resolvedDocs ]), schemaContentTypes = useMemo(() => new Set(contentDocumentTypes(schema)), [ schema ]), {issues: issues, starts: starts, fields: fields, latestVersions: latestVersions, autoStartByType: autoStartByType, autoStartDefinitions: autoStartDefinitions} = useDeployedDefinitions({
8066
+ }, [ discovered ]), useLayoutEffect(() => {
8067
+ entriesStore.setCommitted({
8068
+ seeded: seeded,
8069
+ discovered: discovered,
8070
+ docIndex: docIndex
8071
+ });
8072
+ }, [ entriesStore, seeded, discovered, docIndex ]);
8073
+ const isDocResolved = useCallback(docId => resolvedDocs.has(docId), [ resolvedDocs ]), schemaContentTypes = useMemo(() => new Set(contentDocumentTypes(schema)), [ schema ]), {mappings: mappings, issues: issues, starts: starts, fields: fields, latestVersions: latestVersions, autoStartByType: autoStartByType, autoStartDefinitions: autoStartDefinitions} = useDeployedDefinitions({
7326
8074
  engine: engine,
7327
- mappings: config.mappings,
8075
+ overrides: config.mappings,
7328
8076
  contentResource: binding.contentResource,
7329
8077
  schemaContentTypes: schemaContentTypes,
7330
8078
  startRequest: startRequest2,
7331
- autoStart: config.autoStart,
7332
- schema: schema,
7333
- workspaceName: workspaceName
8079
+ schema: schema
7334
8080
  }), value = useMemo(() => ({
7335
8081
  register: register,
7336
8082
  unregister: unregister,
7337
8083
  isDocResolved: isDocResolved,
7338
- byDocument: byDocument,
7339
- byInstance: byInstance,
8084
+ entries: entriesStore,
7340
8085
  requestInstance: requestInstance,
7341
8086
  releaseInstance: releaseInstance,
8087
+ requestEvaluation: requestEvaluation,
8088
+ releaseEvaluation: releaseEvaluation,
7342
8089
  seedInstance: seedInstance,
7343
8090
  openStartDialog: openStartDialog,
7344
8091
  closeStartDialog: closeStartDialog,
@@ -7353,7 +8100,7 @@ function WorkflowProvider(props) {
7353
8100
  completeEffectFor: completeEffectFor,
7354
8101
  engine: engine,
7355
8102
  binding: binding,
7356
- mappings: config.mappings,
8103
+ mappings: mappings,
7357
8104
  mappingIssues: issues,
7358
8105
  mappingStarts: starts,
7359
8106
  mappingFields: fields,
@@ -7361,24 +8108,30 @@ function WorkflowProvider(props) {
7361
8108
  effectHandlers: engine.effectHandlers,
7362
8109
  autoStartByType: autoStartByType,
7363
8110
  autoStartDefinitions: autoStartDefinitions
7364
- }), [ register, unregister, isDocResolved, byDocument, byInstance, requestInstance, releaseInstance, seedInstance, openStartDialog, closeStartDialog, startRequest2, loading, discoveryInvalid, fireActionFor, editFieldFor, previewFieldFor, discardFieldPreviewFor, drainEffectsFor, completeEffectFor, engine, binding, config.mappings, issues, starts, latestVersions, autoStartByType, autoStartDefinitions ]);
8111
+ }), [ register, unregister, isDocResolved, entriesStore, requestInstance, releaseInstance, requestEvaluation, releaseEvaluation, seedInstance, openStartDialog, closeStartDialog, startRequest2, loading, discoveryInvalid, fireActionFor, editFieldFor, previewFieldFor, discardFieldPreviewFor, drainEffectsFor, completeEffectFor, engine, binding, mappings, issues, starts, fields, latestVersions, autoStartByType, autoStartDefinitions ]);
7365
8112
  /* @__PURE__ */
7366
8113
  return jsxs(WorkflowContext.Provider, {
7367
8114
  value: value,
7368
- children: [ watching ? /* @__PURE__ */ jsx(DiscoverySubscription, {
8115
+ children: [ documentFilter ? /* @__PURE__ */ jsx(DiscoverySubscription, {
7369
8116
  engine: engine,
7370
- filter: filter,
7371
- onSnapshot: onSnapshot,
8117
+ filter: documentFilter,
8118
+ onSnapshot: onDocumentSnapshot,
7372
8119
  registered: registered
7373
- }) : null, [ ...discovered.values() ].map(inst => /* @__PURE__ */ jsx(InstanceEvaluatorBoundary, {
8120
+ }) : null, requestedFilter ? /* @__PURE__ */ jsx(DiscoverySubscription, {
8121
+ engine: engine,
8122
+ filter: requestedFilter,
8123
+ onSnapshot: onRequestedSnapshot,
8124
+ registered: NO_REGISTERED_DOCUMENTS
8125
+ }) : null, evaluated.map(inst => /* @__PURE__ */ jsx(InstanceEvaluatorBoundary, {
7374
8126
  instanceId: inst._id,
7375
8127
  children: /* @__PURE__ */ jsx(InstanceEvaluator, {
7376
8128
  engine: engine,
7377
8129
  grantsFromPath: grantsFromPath,
8130
+ guardScope: guardScope,
7378
8131
  instanceId: inst._id,
7379
8132
  onReport: onReport
7380
8133
  })
7381
- }, inst._id)), props.renderDefault(props), props.overlay ]
8134
+ }, inst._id)), defaultLayout, props.overlay ]
7382
8135
  });
7383
8136
  }
7384
8137
 
@@ -7388,14 +8141,25 @@ function sameEntries(a, b) {
7388
8141
  return !0;
7389
8142
  }
7390
8143
 
8144
+ function sameMappings(a, b) {
8145
+ return a.length === b.length && a.every((mapping, index) => {
8146
+ const other = b[index];
8147
+ return other !== void 0 && mappingKey(mapping) === mappingKey(other) && mapping.label === other.label && mapping.autoStart === other.autoStart && mapping.initialStateBuilder === other.initialStateBuilder && mapping.contextBuilder === other.contextBuilder && mapping.perspectiveField === other.perspectiveField;
8148
+ });
8149
+ }
8150
+
7391
8151
  function useDeployedDefinitions(args) {
7392
- const {engine: engine, mappings: mappings, contentResource: contentResource, schemaContentTypes: schemaContentTypes, startRequest: startRequest2, autoStart: autoStart, schema: schema, workspaceName: workspaceName} = args, [issues, setIssues] = useState(/* @__PURE__ */ new Map), [starts, setStarts] = useState(/* @__PURE__ */ new Map), [fields, setFields] = useState(/* @__PURE__ */ new Map), [latestVersions, setLatestVersions] = useState(/* @__PURE__ */ new Map), [autoStartByType, setAutoStartByType] = useState(
8152
+ const {engine: engine, overrides: overrides, contentResource: contentResource, schemaContentTypes: schemaContentTypes, startRequest: startRequest2, schema: schema} = args, [mappings, setMappings] = useState([]), [issues, setIssues] = useState(/* @__PURE__ */ new Map), [starts, setStarts] = useState(/* @__PURE__ */ new Map), [fields, setFields] = useState(/* @__PURE__ */ new Map), [latestVersions, setLatestVersions] = useState(/* @__PURE__ */ new Map), [autoStartByType, setAutoStartByType] = useState(
7393
8153
  /* @__PURE__ */ new Map), [autoStartDefinitions, setAutoStartDefinitions] = useState(/* @__PURE__ */ new Map), logged = useRef(/* @__PURE__ */ new Set);
7394
8154
  return useEffect(() => {
7395
8155
  let cancelled = !1;
7396
8156
  return (async () => {
7397
- const definitions = await readDeployedDefinitions(engine), found = mappingIssues({
7398
- mappings: mappings,
8157
+ const definitions = await readDeployedDefinitions(engine), effectiveMappings = discoverWorkflowMappings({
8158
+ definitions: definitions,
8159
+ schemaContentTypes: schemaContentTypes,
8160
+ overrides: overrides
8161
+ }), found = mappingIssues({
8162
+ mappings: effectiveMappings,
7399
8163
  definitions: definitions,
7400
8164
  contentResource: contentResource,
7401
8165
  schemaContentTypes: schemaContentTypes
@@ -7405,32 +8169,30 @@ function useDeployedDefinitions(args) {
7405
8169
  logged.current.has(signature) || (logged.current.add(signature), console.error(`[workflow-studio-plugin] ${message}`));
7406
8170
  }
7407
8171
  const resolvedAutoStart = resolveAutoStart({
7408
- autoStart: autoStart,
7409
- context: {
7410
- workspaceName: workspaceName,
7411
- schema: schema
7412
- },
8172
+ autoStart: mappingAutoStartMap(effectiveMappings),
7413
8173
  knownDocTypes: new Set(schema.getTypeNames()),
7414
8174
  definitions: definitions
7415
8175
  });
7416
8176
  if (logOnce(resolvedAutoStart.warnings, logged.current), cancelled) return;
8177
+ setMappings(prev => sameMappings(prev, effectiveMappings) ? prev : effectiveMappings),
7417
8178
  setIssues(prev => sameEntries(prev, found) ? prev : found), setStarts(mappingStartBlocks({
7418
- mappings: mappings,
8179
+ mappings: effectiveMappings,
7419
8180
  definitions: definitions
7420
8181
  })), setFields(mappingDeclaredFields({
7421
- mappings: mappings,
8182
+ mappings: effectiveMappings,
7422
8183
  definitions: definitions
7423
8184
  }));
7424
8185
  const versions = new Map(latestDeployedDefinitions(definitions).map(d => [ d.name, d.version ]));
7425
8186
  setLatestVersions(prev => sameEntries(prev, versions) ? prev : versions), setAutoStartByType(resolvedAutoStart.byType),
7426
8187
  setAutoStartDefinitions(resolvedAutoStart.definitions);
7427
8188
  })().catch(err => {
7428
- console.error("[workflow-studio-plugin] mapping validation could not read the deployed definitions — keeping the previous verdicts:", err);
8189
+ console.error("[workflow-studio-plugin] mapping discovery failed — keeping the last resolved workflow state:", err);
7429
8190
  }), () => {
7430
8191
  cancelled = !0;
7431
8192
  };
7432
- }, [ engine, mappings, contentResource, schemaContentTypes, startRequest2, autoStart, schema, workspaceName ]),
8193
+ }, [ engine, overrides, contentResource, schemaContentTypes, startRequest2, schema ]),
7433
8194
  {
8195
+ mappings: mappings,
7434
8196
  issues: issues,
7435
8197
  starts: starts,
7436
8198
  fields: fields,
@@ -8216,7 +8978,7 @@ function isFreshDocument(value) {
8216
8978
  }
8217
8979
 
8218
8980
  function subjectEntry(definition) {
8219
- return (definition.fields ?? []).find(isSubjectEntry);
8981
+ return documentSubjectEntry(definition);
8220
8982
  }
8221
8983
 
8222
8984
  function autoStartInputs(args) {
@@ -8243,11 +9005,11 @@ function pendingWorkflows(workflows, live) {
8243
9005
  }
8244
9006
 
8245
9007
  function extraRequiredInputs(definition) {
8246
- const fields = definition.fields ?? [], subjectNames = new Set(fields.filter(isSubjectEntry).map(entry => entry.name)), requiredNames = new Set(missingRequiredInputs({
9008
+ const fields = definition.fields ?? [], subjectName = subjectEntry(definition)?.name, requiredNames = new Set(missingRequiredInputs({
8247
9009
  entryDefs: fields,
8248
9010
  initialFields: []
8249
9011
  }).map(input => input.name));
8250
- return fields.filter(entry => requiredNames.has(entry.name) && !subjectNames.has(entry.name));
9012
+ return fields.filter(entry => requiredNames.has(entry.name) && entry.name !== subjectName);
8251
9013
  }
8252
9014
 
8253
9015
  function buildAutoStartRequests(args) {
@@ -8295,7 +9057,7 @@ function AutoStartDriver(props) {
8295
9057
  const pending = useMemo(() => pendingWorkflows(workflows, new Set(entries.map(entry => entry.instance.definition))), [ workflows, entries ]), inputs = useMemo(() => autoStartInputs({
8296
9058
  workflows: pending,
8297
9059
  definitions: autoStartDefinitions
8298
- }), [ pending, autoStartDefinitions ]), pendingRef = useRef(pending);
9060
+ }), [ pending, autoStartDefinitions ]), definitionsResolved = workflows.every(name => autoStartDefinitions.has(name)), pendingRef = useRef(pending);
8299
9061
  pendingRef.current = pending;
8300
9062
  const instanceIds = useRef(/* @__PURE__ */ new Map), instanceIdFor = useCallback(workflow => {
8301
9063
  const held = instanceIds.current.get(workflow);
@@ -8339,7 +9101,7 @@ function AutoStartDriver(props) {
8339
9101
  }, [ autoStartDefinitions, docId, docType, binding, engine, observer, seedInstance, instanceIdFor, selectedReleaseId ]), retry = useCallback(() => {
8340
9102
  firedRef.current = !1, setFired(!1), setSettled(!1), setFailure(void 0);
8341
9103
  }, []), action = autoStartAction({
8342
- resolved: isDocResolved(docId),
9104
+ resolved: definitionsResolved && isDocResolved(docId),
8343
9105
  pendingCount: pending.length,
8344
9106
  needsInput: inputs.length > 0,
8345
9107
  fired: fired,
@@ -8564,19 +9326,32 @@ function TodoGlance({row: row}) {
8564
9326
  });
8565
9327
  }
8566
9328
 
9329
+ function finishedLinesOf(entries) {
9330
+ const activeTypes = new Set(entries.filter(isLiveEntry).map(entry => entry.instance.definition)), settled = entries.flatMap(entry => {
9331
+ const stamp = entry.instance.completedAt;
9332
+ return stamp === void 0 || activeTypes.has(entry.instance.definition) ? [] : [ {
9333
+ entry: entry,
9334
+ at: Date.parse(stamp)
9335
+ } ];
9336
+ }), latestByType = /* @__PURE__ */ new Map;
9337
+ for (const candidate of settled) {
9338
+ const best = latestByType.get(candidate.entry.instance.definition);
9339
+ (best === void 0 || candidate.at > best.at) && latestByType.set(candidate.entry.instance.definition, candidate);
9340
+ }
9341
+ return [ ...latestByType.values() ].sort((a, b) => b.at - a.at).map(({entry: entry}) => entry);
9342
+ }
9343
+
8567
9344
  function WorkflowFormStrip(props) {
8568
- const {mappings: mappings} = props, {isDocResolved: isDocResolved, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {docId: docId, entries: entries} = useWorkflowsForDocument(props.value?._id ?? null), {params: params, setParams: setParams} = usePaneRouter(), openWorkflowsView = () => setParams({
8569
- ...params,
8570
- view: WORKFLOWS_VIEW_ID
8571
- }), active = entries.filter(isLiveEntry), invalid = surfacedInvalid(discoveryInvalid, entries), resolved = docId !== void 0 && isDocResolved(docId);
9345
+ const {mappings: mappings} = props, {isDocResolved: isDocResolved, discoveryInvalid: discoveryInvalid} = useWorkflowContext(), {docId: docId, entries: entries} = useWorkflowsForDocument(props.value?._id ?? null), {params: params, setParams: setParams} = usePaneRouter(), openWorkflowsView = focusId => setParams(focusedViewParams(params, focusId)), active = entries.filter(isLiveEntry), finished = finishedLinesOf(entries), invalid = surfacedInvalid(discoveryInvalid, entries), resolved = docId !== void 0 && isDocResolved(docId);
8572
9346
  /* @__PURE__ */
8573
9347
  return jsxs(Stack, {
8574
9348
  gap: 4,
8575
9349
  children: [ invalid !== void 0 ? /* @__PURE__ */ jsx(InvalidDocNotice, {
8576
9350
  invalid: invalid
8577
- }) : null, active.length > 0 || resolved ? /* @__PURE__ */ jsx(StripLines, {
9351
+ }) : null, active.length > 0 || finished.length > 0 || resolved ? /* @__PURE__ */ jsx(StripLines, {
8578
9352
  active: active,
8579
9353
  docId: docId,
9354
+ finished: finished,
8580
9355
  initialValue: props.value ?? null,
8581
9356
  mappings: mappings,
8582
9357
  onOpen: openWorkflowsView,
@@ -8585,32 +9360,17 @@ function WorkflowFormStrip(props) {
8585
9360
  });
8586
9361
  }
8587
9362
 
8588
- function StripLines({active: active, docId: docId, initialValue: initialValue, mappings: mappings, onOpen: onOpen, resolved: resolved}) {
8589
- const identity = useAssignmentIdentity(), single = active.length === 1 ? active[0] : void 0;
9363
+ function StripLines({active: active, docId: docId, finished: finished, initialValue: initialValue, mappings: mappings, onOpen: onOpen, resolved: resolved}) {
8590
9364
  /* @__PURE__ */
8591
9365
  return jsxs(Stack, {
8592
9366
  gap: 1,
8593
- children: [ single !== void 0 ? /* @__PURE__ */ jsx(InstanceLine, {
8594
- entry: single,
8595
- onOpen: onOpen
8596
- }) : null, active.length > 1 ? /* @__PURE__ */ jsxs(StripLine, {
8597
- aside: /* @__PURE__ */ jsx(TaskCountSpinner, {
8598
- entries: active
8599
- }),
8600
- hint: lineHint(identity, forMeWorkOf(active, identity)),
8601
- onOpen: onOpen,
8602
- children: [
8603
- /* @__PURE__ */ jsx(Flex, {
8604
- children: /* @__PURE__ */ jsxs(Text, {
8605
- size: 1,
8606
- weight: "medium",
8607
- children: [ active.length, " active workflows" ]
8608
- })
8609
- }),
8610
- /* @__PURE__ */ jsx(TaskCountText, {
8611
- entries: active
8612
- }) ]
8613
- }) : null, resolved ? /* @__PURE__ */ jsx(Flex, {
9367
+ children: [ active.map(entry => /* @__PURE__ */ jsx(InstanceLine, {
9368
+ entry: entry,
9369
+ onOpen: () => onOpen(entry.instance._id)
9370
+ }, entry.instance._id)), finished.map(entry => /* @__PURE__ */ jsx(FinishedLine, {
9371
+ entry: entry,
9372
+ onOpen: () => onOpen(entry.instance._id)
9373
+ }, entry.instance._id)), resolved ? /* @__PURE__ */ jsx(Flex, {
8614
9374
  children: /* @__PURE__ */ jsx(StartWorkflowControl, {
8615
9375
  docId: docId,
8616
9376
  initialValue: initialValue,
@@ -8664,7 +9424,7 @@ function InstanceLine({entry: entry, onOpen: onOpen}) {
8664
9424
  /* @__PURE__ */
8665
9425
  return jsxs(StripLine, {
8666
9426
  aside: unprimed ? void 0 : /* @__PURE__ */ jsx(TaskCountSpinner, {
8667
- entries: [ entry ]
9427
+ entry: entry
8668
9428
  }),
8669
9429
  hint: lineHint(identity, forMeWorkOf([ entry ], identity)),
8670
9430
  onOpen: onOpen,
@@ -8683,34 +9443,69 @@ function InstanceLine({entry: entry, onOpen: onOpen}) {
8683
9443
  title: stageTitle(definition, entry.instance.currentStage)
8684
9444
  }),
8685
9445
  /* @__PURE__ */ jsx(TaskCountText, {
8686
- entries: [ entry ]
9446
+ entry: entry
8687
9447
  }) ]
8688
9448
  }) ]
8689
9449
  });
8690
9450
  }
8691
9451
 
8692
- function useTaskCountState(entries) {
8693
- const identity = useAssignmentIdentity(), freshness = entries.filter(e => e.invalid === void 0).map(e => evaluationFreshness({
8694
- committed: e.committed,
8695
- evaluation: e.evaluation
8696
- }));
8697
- return !identity || freshness.includes("pending") ? {
9452
+ function FinishedLine({entry: entry, onOpen: onOpen}) {
9453
+ const definition = useDefinition(entry), badgeTrim = useBadgeCapTrim(), {instance: instance} = entry;
9454
+ /* @__PURE__ */
9455
+ return jsxs(StripLine, {
9456
+ hint: finishedHint(instance),
9457
+ onOpen: onOpen,
9458
+ children: [
9459
+ /* @__PURE__ */ jsx(Flex, {
9460
+ children: /* @__PURE__ */ jsx(Text, {
9461
+ size: 1,
9462
+ weight: "medium",
9463
+ children: instanceTitle(instance, definition)
9464
+ })
9465
+ }), terminalState(instance) === "aborted" ? /* @__PURE__ */ jsx(Flex, {
9466
+ style: badgeTrim,
9467
+ children: /* @__PURE__ */ jsx(AbortedBadge, {
9468
+ instance: instance
9469
+ })
9470
+ }) : /* @__PURE__ */ jsx(StageFace, {
9471
+ completed: !0,
9472
+ title: stageTitle(definition, instance.currentStage)
9473
+ }) ]
9474
+ });
9475
+ }
9476
+
9477
+ function finishedHint(instance) {
9478
+ const verb = terminalState(instance) === "aborted" ? "Aborted" : "Completed", face = stampFace(verb, instance.completedAt ?? "");
9479
+ return {
9480
+ text: face.lead,
9481
+ ...face.detail === void 0 ? {} : {
9482
+ description: face.detail
9483
+ }
9484
+ };
9485
+ }
9486
+
9487
+ function useTaskCountState(entry) {
9488
+ const identity = useAssignmentIdentity(), freshness = entry.invalid === void 0 ? evaluationFreshness({
9489
+ committed: entry.committed,
9490
+ evaluation: entry.evaluation
9491
+ }) : void 0;
9492
+ return !identity || freshness === "pending" ? {
8698
9493
  kind: "pending"
8699
- } : freshness.includes("stale") ? {
9494
+ } : freshness === "stale" ? {
8700
9495
  kind: "stale"
8701
9496
  } : {
8702
9497
  kind: "ready",
8703
- tasks: entries.reduce((n, e) => n + assignedTaskCount({
8704
- instance: e.instance,
8705
- committed: e.committed,
8706
- evaluation: e.evaluation,
9498
+ tasks: assignedTaskCount({
9499
+ instance: entry.instance,
9500
+ committed: entry.committed,
9501
+ evaluation: entry.evaluation,
8707
9502
  who: identity
8708
- }), 0)
9503
+ })
8709
9504
  };
8710
9505
  }
8711
9506
 
8712
- function TaskCountText({entries: entries}) {
8713
- const state = useTaskCountState(entries);
9507
+ function TaskCountText({entry: entry}) {
9508
+ const state = useTaskCountState(entry);
8714
9509
  return state.kind === "pending" || state.kind === "ready" && state.tasks === 0 ? null : /* @__PURE__ */ jsx(Text, {
8715
9510
  muted: !0,
8716
9511
  size: 1,
@@ -8718,8 +9513,8 @@ function TaskCountText({entries: entries}) {
8718
9513
  });
8719
9514
  }
8720
9515
 
8721
- function TaskCountSpinner({entries: entries}) {
8722
- return useTaskCountState(entries).kind !== "pending" ? null : /* @__PURE__ */ jsx(Flex, {
9516
+ function TaskCountSpinner({entry: entry}) {
9517
+ return useTaskCountState(entry).kind !== "pending" ? null : /* @__PURE__ */ jsx(Flex, {
8723
9518
  paddingLeft: 1,
8724
9519
  children: /* @__PURE__ */ jsx(Spinner, {
8725
9520
  muted: !0,
@@ -8728,6 +9523,61 @@ function TaskCountSpinner({entries: entries}) {
8728
9523
  });
8729
9524
  }
8730
9525
 
9526
+ function stagePillState(instance) {
9527
+ const definition = definitionSnapshotOf(instance), stage = definition?.stages.find(candidate => candidate.name === instance.currentStage), state = terminalState(instance);
9528
+ return {
9529
+ title: stageTitle(definition, instance.currentStage),
9530
+ terminal: stage !== void 0 && isTerminalStage(stage) || state !== "in-flight",
9531
+ state: state,
9532
+ stageKnown: stage !== void 0
9533
+ };
9534
+ }
9535
+
9536
+ function WorkflowStagePreview(props) {
9537
+ const {mappings: mappings} = useWorkflowContext(), mapped = props.schemaType !== void 0 && mappingsForDocType(mappings, props.schemaType.name).length > 0, {entries: entries} = useCommittedWorkflowsForDocument(mapped ? props._id ?? null : null), lead = entries.find(isLiveEntry) ?? entries[0], extraLive = entries.filter(e => e !== lead && isLiveEntry(e)).length;
9538
+ return props.renderDefault({
9539
+ ...props,
9540
+ status: lead ? /* @__PURE__ */ jsx(StagePill, {
9541
+ entry: lead,
9542
+ extraLive: extraLive
9543
+ }) : props.status
9544
+ });
9545
+ }
9546
+
9547
+ function StagePill({entry: entry, extraLive: extraLive}) {
9548
+ /* @__PURE__ */
9549
+ return jsxs(Flex, {
9550
+ align: "center",
9551
+ gap: 2,
9552
+ children: [
9553
+ /* @__PURE__ */ jsx(LeadBadge, {
9554
+ entry: entry
9555
+ }), extraLive > 0 ? /* @__PURE__ */ jsxs(Badge, {
9556
+ fontSize: 1,
9557
+ mode: "outline",
9558
+ tone: "default",
9559
+ children: [ "+", extraLive ]
9560
+ }) : null ]
9561
+ });
9562
+ }
9563
+
9564
+ function LeadBadge({entry: entry}) {
9565
+ const {instance: instance} = entry;
9566
+ if (isUnprimed(instance)) /* @__PURE__ */ return jsx(StartIncompleteBadge, {});
9567
+ const pill = stagePillState(instance);
9568
+ /* @__PURE__ */
9569
+ return jsxs(Badge, {
9570
+ fontSize: 1,
9571
+ mode: pill.terminal ? "default" : "outline",
9572
+ tone: pillTone(pill),
9573
+ children: [ pill.title, pill.state === "completed" ? " ✓" : "" ]
9574
+ });
9575
+ }
9576
+
9577
+ function pillTone(args) {
9578
+ return args.state === "aborted" ? "critical" : args.terminal ? "positive" : args.stageKnown ? "primary" : "default";
9579
+ }
9580
+
8731
9581
  function StartWorkflowDialogHost() {
8732
9582
  const {startRequest: startRequest2, closeStartDialog: closeStartDialog} = useWorkflowContext();
8733
9583
  return startRequest2 ? /* @__PURE__ */ jsx(StartWorkflowDialog, {
@@ -8800,7 +9650,8 @@ function withWorkflowLock(Action, guardAction) {
8800
9650
  Locked;
8801
9651
  }
8802
9652
 
8803
- const workflowStudioPlugin = definePlugin(config => ({
9653
+ const workflowStudioPlugin = definePlugin(config => (assertUniqueWorkflowMappings(config.mappings ?? []),
9654
+ {
8804
9655
  name: "workflow-studio-plugin",
8805
9656
  tools: prev => [ ...prev, workflowsTool ],
8806
9657
  studio: {
@@ -8814,82 +9665,35 @@ const workflowStudioPlugin = definePlugin(config => ({
8814
9665
  },
8815
9666
  form: {
8816
9667
  components: {
8817
- input: props => {
8818
- if (props.id === "root" && isObjectInputProps(props)) {
8819
- const docTypeMappings = mappingsForDocType(config.mappings, props.schemaType.name), fallback = docTypeMappings.length > 0 ? /* @__PURE__ */ jsx(WorkflowFormStrip, {
8820
- ...props,
8821
- mappings: docTypeMappings
8822
- }) : props.renderDefault(props);
8823
- /* @__PURE__ */
8824
- return jsx(AutoStartGate, {
8825
- ...props,
8826
- fallback: fallback
8827
- });
8828
- }
8829
- return props.renderDefault(props);
8830
- }
9668
+ preview: WorkflowStagePreview,
9669
+ input: props => props.id === "root" && isObjectInputProps(props) ? /* @__PURE__ */ jsx(WorkflowRootInput, {
9670
+ ...props
9671
+ }) : props.renderDefault(props)
8831
9672
  }
8832
9673
  },
8833
9674
  document: {
8834
9675
  badges: (prev, ctx) => {
8835
- if (!mappingForDocType(config.mappings, ctx.schemaType) || !ctx.documentId) return prev;
9676
+ if (!ctx.documentId) return prev;
8836
9677
  const docId = ctx.documentId;
8837
9678
  return [ makeActiveWorkflowsBadge(docId), makePerspectiveBadge(docId), ...prev ];
8838
9679
  },
8839
- actions: (prev, ctx) => mappingForDocType(config.mappings, ctx.schemaType) ? prev.map(Action => {
9680
+ actions: prev => prev.map(Action => {
8840
9681
  const guardAction = Action.action ? LOCKABLE_ACTIONS.get(Action.action) : void 0;
8841
9682
  return guardAction ? withWorkflowLock(Action, guardAction) : Action;
8842
- }) : prev
9683
+ })
8843
9684
  }
8844
9685
  }));
8845
9686
 
8846
- function WorkflowStagePreview(props) {
8847
- const {entries: entries} = useWorkflowsForDocument(props.documentId ?? props._id ?? null), lead = entries.find(isLiveEntry) ?? entries[0], extraLive = entries.filter(e => e !== lead && isLiveEntry(e)).length;
8848
- return props.renderDefault({
9687
+ function WorkflowRootInput(props) {
9688
+ const {mappings: mappings} = useWorkflowContext(), docTypeMappings = mappingsForDocType(mappings, props.schemaType.name), fallback = docTypeMappings.length > 0 ? /* @__PURE__ */ jsx(WorkflowFormStrip, {
8849
9689
  ...props,
8850
- status: lead ? /* @__PURE__ */ jsx(StagePill, {
8851
- entry: lead,
8852
- extraLive: extraLive
8853
- }) : props.status
8854
- });
8855
- }
8856
-
8857
- function StagePill({entry: entry, extraLive: extraLive}) {
8858
- /* @__PURE__ */
8859
- return jsxs(Flex, {
8860
- align: "center",
8861
- gap: 2,
8862
- children: [
8863
- /* @__PURE__ */ jsx(LeadBadge, {
8864
- entry: entry
8865
- }), extraLive > 0 ? /* @__PURE__ */ jsxs(Badge, {
8866
- fontSize: 1,
8867
- mode: "outline",
8868
- tone: "default",
8869
- children: [ "+", extraLive ]
8870
- }) : null ]
8871
- });
8872
- }
8873
-
8874
- function LeadBadge({entry: entry}) {
8875
- const {instance: instance, evaluation: evaluation} = entry;
8876
- if (isUnprimed(instance)) /* @__PURE__ */ return jsx(StartIncompleteBadge, {});
8877
- const stage = evaluation?.currentStage?.stage, title = stage?.title ?? stageTitle(definitionSnapshotOf(instance), instance.currentStage), state = terminalState(instance), isTerminal = (stage ? isTerminalStage(stage) : !1) || state !== "in-flight";
9690
+ mappings: docTypeMappings
9691
+ }) : props.renderDefault(props);
8878
9692
  /* @__PURE__ */
8879
- return jsxs(Badge, {
8880
- fontSize: 1,
8881
- mode: isTerminal ? "default" : "outline",
8882
- tone: pillTone({
8883
- state: state,
8884
- isTerminal: isTerminal,
8885
- stageKnown: stage !== void 0
8886
- }),
8887
- children: [ title, state === "completed" ? " ✓" : "" ]
9693
+ return jsx(AutoStartGate, {
9694
+ ...props,
9695
+ fallback: fallback
8888
9696
  });
8889
9697
  }
8890
9698
 
8891
- function pillTone(args) {
8892
- return args.state === "aborted" ? "critical" : args.isTerminal ? "positive" : args.stageKnown ? "primary" : "default";
8893
- }
8894
-
8895
- export { AbortedBadge, ActivitiesList, ActivityDetailDialog, ActivityStatusIcon, DismissablePopover, DocPreviewLink, DocRefFace, GroupHeading, HintedMenuButton, HoverHint, InvalidDocNotice, LoadingRow, StageChip, StageGlyph, StaleLock, TodoItemsList, UserAvatarGroup, WorkflowStagePreview, assigneeUserIdsOf, definitionSnapshotOf, describeError, findActivity, formatDate, gdrLocality, instanceBreadcrumb, instanceNotice, instanceTitle, isActivityAssignedTo, isEvaluationStale, isOpenActivityStatus, mappingForDocType, openActivityGone, parseStoredDateValue, readDeployedDefinitions, stageTitle, stateByActivity, useAssignmentIdentity, useDefinition, useDocLink, useProjectMembers, useUserDisplays, useWorkflowContext, useWorkflowInstanceEntry, workflowDefaultDocumentNode, workflowDocTypes, workflowStudioPlugin, workflowsView };
9699
+ export { AbortedBadge, ActivitiesList, ActivityDetailDialog, ActivityRow, BreadcrumbTail, CollapsibleBand, DismissablePopover, DocPreviewLink, DocRefFace, GroupHeading, HoverHint, InvalidDocNotice, LinkChip, LoadingRow, SpinnerSlot, StageChip, StageFace, StaleLock, TabSwitch, TodoItemsList, TrailingHairline, UnreadableDocsNote, activityRowProps, assigneeUserIdsOf, committedRowFace, dateControlKind, dateControlValue, definitionSnapshotOf, describeError, editTargetOf, findActivity, findActivityNode, formatDate, gdrLocality, instanceBreadcrumb, instanceNotice, instanceTitle, isActivityAssignedTo, isEvaluationStale, isOpenActivityStatus, openActivityGone, parseDateFieldValue, parseStoredDateValue, readDeployedDefinitions, rowAssignControlState, rowDateControlState, serializeDateFieldValue, stageTitle, stateByActivity, stopRowClick, stopRowMouseDown, useAssignmentIdentity, useDefinition, useProjectMembers, useSaveField, useUserDisplays, useWorkflowContext, useWorkflowInstanceEntry, workflowDefaultDocumentNode, workflowStudioPlugin, workflowsTabCodec, workflowsView };