@sanity/workflow-studio-plugin 0.0.0 → 0.1.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.
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { latestDeployedDefinitions, missingRequiredInputs, gdrRef, releaseRef, rejectedRefTypes, findOpenStageEntry, ENGINE_API_VERSION, WORKFLOW_INSTANCE_TYPE, WORKFLOW_DEFINITION_TYPE, GUARD_DOC_TYPE, isTodoListEntry, isTodoListItem, ActionDisabledError, ActionParamsInvalidError, errorMessage, MutationGuardDeniedError, isTerminalActivityStatus, findCurrentActivityEntry, tryParseGdr, resourceFromParsed, resolveFieldEntry, isNotesEntry, extractDocumentId, parseDefinitionSnapshot, terminalState, parentRef, gdrFromResource, instanceWatchesDocument, definitionsListGroq, definitionLookupGroq, documentActionDenials, deniedGuardLabels, deniedGuardRefs, isTerminalStage } from "@sanity/workflow-engine";
1
+ import { latestDeployedDefinitions, missingRequiredInputs, gdrRef, releaseRef, rejectedRefTypes, findOpenStageEntry, ENGINE_API_VERSION, WORKFLOW_INSTANCE_TYPE, WORKFLOW_DEFINITION_TYPE, GUARD_DOC_TYPE, isTodoListEntry, isTodoListItem, ActionDisabledError, ActionParamsInvalidError, errorMessage, MutationGuardDeniedError, isTerminalActivityStatus, findCurrentActivityEntry, tryParseGdr, resourceFromParsed, resolveFieldEntry, isNotesEntry, extractDocumentId, parseDefinitionSnapshot, terminalState, parentRef, gdrFromResource, instanceWatchesDocument, aclPathForResource, definitionsListGroq, definitionLookupGroq, documentActionDenials, deniedGuardLabels, deniedGuardRefs, isTerminalStage } from "@sanity/workflow-engine";
2
2
 
3
3
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
4
4
 
5
- import { Tooltip, Box, Text, Button, Card, Flex, useClickOutsideEvent, Popover, useTheme_v2, Spinner, Avatar, TextInput, AvatarStack, Checkbox, useToast, Stack, Badge, Switch, Code, TextArea, Autocomplete, Dialog, TabList, Tab, Select, TabPanel, MenuButton, Menu, MenuItem } from "@sanity/ui";
5
+ import { Tooltip, Box, Text, Button, Card, Flex, useClickOutsideEvent, Popover, useTheme_v2, Spinner, Avatar, TextInput, AvatarStack, Checkbox, useToast, Stack, Badge, TextArea, Switch, Code, Autocomplete, Dialog, TabList, Tab, Select, TabPanel, MenuButton, Menu, MenuItem } from "@sanity/ui";
6
6
 
7
7
  import { createContext, useEffect, useContext, Fragment as Fragment$1, useRef, useState, useMemo, useCallback, Component } from "react";
8
8
 
@@ -40,6 +40,8 @@ import { RemoveCircleIcon } from "@sanity/icons/RemoveCircle";
40
40
 
41
41
  import { ClockIcon } from "@sanity/icons/Clock";
42
42
 
43
+ import { BoltIcon } from "@sanity/icons/Bolt";
44
+
43
45
  import { EditIcon } from "@sanity/icons/Edit";
44
46
 
45
47
  import { randomKey } from "@sanity/util/content";
@@ -366,7 +368,8 @@ const activityNotActiveSentence = {
366
368
  "instance-completed": () => "This workflow is already complete.",
367
369
  "mutation-guard-denied": () => "An earlier step is holding this document.",
368
370
  "requirements-unmet": () => "This step isn't ready yet — a required condition hasn't been met.",
369
- "stage-terminal": () => "This stage is finished."
371
+ "stage-terminal": () => "This stage is finished.",
372
+ "subject-permission-denied": () => "You don't have permission to change the document this step works on."
370
373
  };
371
374
 
372
375
  function describeDisabledReason(reason) {
@@ -878,20 +881,9 @@ function UserAvatarGroup({ids: ids}) {
878
881
  });
879
882
  }
880
883
 
881
- function TodoCheckbox({busy: busy, checked: checked, canClick: canClick, onToggle: onToggle}) {
882
- return busy ? /* @__PURE__ */ jsx(Flex, {
883
- align: "center",
884
- "aria-label": "Saving…",
885
- justify: "center",
886
- style: {
887
- width: 17,
888
- height: 17
889
- },
890
- children: /* @__PURE__ */ jsx(Spinner, {
891
- muted: !0,
892
- size: 1
893
- })
894
- }) : /* @__PURE__ */ jsx(Checkbox, {
884
+ function TodoCheckbox({checked: checked, canClick: canClick, onToggle: onToggle}) {
885
+ /* @__PURE__ */
886
+ return jsx(Checkbox, {
895
887
  checked: checked,
896
888
  disabled: !canClick,
897
889
  onChange: onToggle,
@@ -1034,10 +1026,10 @@ function AssigneeControl({item: item, editable: editable, onPatch: onPatch}) {
1034
1026
  }) : chip;
1035
1027
  }
1036
1028
 
1037
- function AddItemControl({busy: busy, onAdd: onAdd, button: button}) {
1029
+ function AddItemControl({onAdd: onAdd, button: button, busy: busy}) {
1038
1030
  const [open, setOpen] = useState(!1), [label, setLabel] = useState(""), submit = () => {
1039
1031
  const trimmed = label.trim();
1040
- trimmed && (setOpen(!1), setLabel(""), onAdd(trimmed));
1032
+ !trimmed || busy || (setOpen(!1), setLabel(""), onAdd(trimmed));
1041
1033
  };
1042
1034
  /* @__PURE__ */
1043
1035
  return jsx(DismissablePopover, {
@@ -1069,13 +1061,11 @@ function AddItemControl({busy: busy, onAdd: onAdd, button: button}) {
1069
1061
  open: open,
1070
1062
  placement: "bottom-start",
1071
1063
  children: /* @__PURE__ */ jsx(Box, {
1072
- onClick: () => !busy && setOpen(v => !v),
1064
+ onClick: () => setOpen(v => !v),
1073
1065
  style: {
1074
1066
  display: "inline-flex"
1075
1067
  },
1076
- children: busy ? /* @__PURE__ */ jsx(Spinner, {
1077
- muted: !0
1078
- }) : button
1068
+ children: button
1079
1069
  })
1080
1070
  });
1081
1071
  }
@@ -1104,7 +1094,7 @@ function rowInteractivity(row) {
1104
1094
  };
1105
1095
  }
1106
1096
 
1107
- function ActionItemRowView({row: row, busy: busy, showBreadcrumb: showBreadcrumb, onToggle: onToggle, onPatch: onPatch}) {
1097
+ function ActionItemRowView({row: row, showBreadcrumb: showBreadcrumb, onToggle: onToggle, onPatch: onPatch}) {
1108
1098
  const {canClick: canClick, hint: hint} = rowInteractivity(row), editable = row.editTarget !== void 0, body = /* @__PURE__ */ jsxs(Flex, {
1109
1099
  align: "center",
1110
1100
  gap: 2,
@@ -1115,7 +1105,6 @@ function ActionItemRowView({row: row, busy: busy, showBreadcrumb: showBreadcrumb
1115
1105
  flexShrink: 0
1116
1106
  },
1117
1107
  children: /* @__PURE__ */ jsx(TodoCheckbox, {
1118
- busy: busy,
1119
1108
  canClick: canClick,
1120
1109
  checked: isTodoDone(row.item),
1121
1110
  onToggle: onToggle
@@ -1186,12 +1175,10 @@ function RowBreadcrumb({row: row}) {
1186
1175
  });
1187
1176
  }
1188
1177
 
1189
- const ADD_KEY = "__add__";
1190
-
1191
1178
  function ActionItemsList({entry: entry, assignedToMeOnly: assignedToMeOnly = !1, identity: identity, activityFilter: activityFilter, variant: variant = "section"}) {
1192
- const {instance: instance} = entry, {editFieldFor: editFieldFor, fireActionFor: fireActionFor} = useWorkflowContext(), toast = useToast(), [busyKey, setBusyKey] = useState(null), derivation = deriveActionItems(entry), rows = derivation.rows.filter(row => activityFilter === void 0 || row.activityName === activityFilter || row.tick?.activity === activityFilter).filter(row => !assignedToMeOnly || identity !== void 0 && isItemAssignedTo(row.item, identity)), appendTarget = activityFilter === void 0 ? derivation.appendTargets[0] : derivation.appendTargets.find(t => t.activity === activityFilter), run = async (key, op) => {
1193
- if (!busyKey) {
1194
- setBusyKey(key);
1179
+ const {instance: instance} = entry, {editFieldFor: editFieldFor, fireActionFor: fireActionFor} = useWorkflowContext(), toast = useToast(), [busy, setBusy] = useState(!1), derivation = deriveActionItems(entry), rows = derivation.rows.filter(row => activityFilter === void 0 || row.activityName === activityFilter || row.tick?.activity === activityFilter).filter(row => !assignedToMeOnly || identity !== void 0 && isItemAssignedTo(row.item, identity)), appendTarget = activityFilter === void 0 ? derivation.appendTargets[0] : derivation.appendTargets.find(t => t.activity === activityFilter), run = async op => {
1180
+ if (!busy) {
1181
+ setBusy(!0);
1195
1182
  try {
1196
1183
  await op();
1197
1184
  } catch (err) {
@@ -1201,7 +1188,7 @@ function ActionItemsList({entry: entry, assignedToMeOnly: assignedToMeOnly = !1,
1201
1188
  description: describeError(err)
1202
1189
  });
1203
1190
  } finally {
1204
- setBusyKey(null);
1191
+ setBusy(!1);
1205
1192
  }
1206
1193
  }
1207
1194
  }, patchItem = (row, patch) => {
@@ -1210,7 +1197,7 @@ function ActionItemsList({entry: entry, assignedToMeOnly: assignedToMeOnly = !1,
1210
1197
  ...it,
1211
1198
  ...patch
1212
1199
  } : it);
1213
- return run(row.item._key, () => editFieldFor(instance._id, {
1200
+ return run(() => editFieldFor(instance._id, {
1214
1201
  target: target,
1215
1202
  mode: "set",
1216
1203
  value: next
@@ -1220,11 +1207,11 @@ function ActionItemsList({entry: entry, assignedToMeOnly: assignedToMeOnly = !1,
1220
1207
  status: toggledTodoStatus(row.item)
1221
1208
  });
1222
1209
  const tick = row.tick;
1223
- return tick && tick.actionEval.allowed && !isTodoDone(row.item) ? run(row.item._key, () => fireActionFor(instance._id, {
1210
+ return tick && tick.actionEval.allowed && !isTodoDone(row.item) ? run(() => fireActionFor(instance._id, {
1224
1211
  activity: tick.activity,
1225
1212
  action: tick.actionEval.action.name
1226
1213
  })) : Promise.resolve();
1227
- }, addItem = label => appendTarget ? run(ADD_KEY, () => editFieldFor(instance._id, {
1214
+ }, addItem = label => appendTarget ? run(() => editFieldFor(instance._id, {
1228
1215
  target: appendTarget,
1229
1216
  mode: "append",
1230
1217
  value: {
@@ -1234,7 +1221,6 @@ function ActionItemsList({entry: entry, assignedToMeOnly: assignedToMeOnly = !1,
1234
1221
  })) : Promise.resolve();
1235
1222
  if (rows.length === 0 && !appendTarget) return null;
1236
1223
  const rowKey = row => `${row.scope}:${row.activityName ?? ""}:${row.fieldName}:${row.item._key}`, rowProps = row => ({
1237
- busy: busyKey === row.item._key,
1238
1224
  onPatch: patch => {
1239
1225
  patchItem(row, patch);
1240
1226
  },
@@ -1253,7 +1239,7 @@ function ActionItemsList({entry: entry, assignedToMeOnly: assignedToMeOnly = !1,
1253
1239
  }, rowKey(row)))
1254
1240
  }), appendTarget ? /* @__PURE__ */ jsx(Flex, {
1255
1241
  children: /* @__PURE__ */ jsx(AddItemControl, {
1256
- busy: busyKey === ADD_KEY,
1242
+ busy: busy,
1257
1243
  button: /* @__PURE__ */ jsx(Button, {
1258
1244
  fontSize: 1,
1259
1245
  icon: AddIcon,
@@ -1279,7 +1265,7 @@ function ActionItemsList({entry: entry, assignedToMeOnly: assignedToMeOnly = !1,
1279
1265
  /* @__PURE__ */ jsx(Box, {
1280
1266
  paddingRight: 2,
1281
1267
  children: /* @__PURE__ */ jsx(AddItemControl, {
1282
- busy: busyKey === ADD_KEY,
1268
+ busy: busy,
1283
1269
  button: /* @__PURE__ */ jsx(HoverHint, {
1284
1270
  text: "Add action item",
1285
1271
  children: /* @__PURE__ */ jsx(Button, {
@@ -1406,7 +1392,7 @@ function groupEditableByActivity(fields) {
1406
1392
  }
1407
1393
 
1408
1394
  function deriveActivityPresentation(args) {
1409
- const {activity: activity, status: status, actions: actions, kind: kind} = args.activityEval, editableNames = new Set(args.editableFields.map(f => f.name)), dates = args.state.filter(s => s._type === "datetime" && !!s.value), writtenNames = new Set(actions.flatMap(a => writtenFieldNames(a.action))), todoListNames = new Set(args.state.filter(isTodoListEntry).map(s => s.name)), bodyFields = args.state.filter(s => s._type !== "assignees" && s._type !== "datetime" && !writtenNames.has(s.name) && !editableNames.has(s.name) && !todoListNames.has(s.name)), visibleActions = actions.filter(a => {
1395
+ const {activity: activity, status: status, actions: actions, kind: kind} = args.activityEval, editableNames = new Set(args.editableFields.map(f => f.name)), dates = args.state.filter(s => (s._type === "date" || s._type === "datetime") && !!s.value && !editableNames.has(s.name)), writtenNames = new Set(actions.flatMap(a => writtenFieldNames(a.action))), todoListNames = new Set(args.state.filter(isTodoListEntry).map(s => s.name)), bodyFields = args.state.filter(s => s._type !== "assignees" && s._type !== "date" && s._type !== "datetime" && !writtenNames.has(s.name) && !editableNames.has(s.name) && !todoListNames.has(s.name)), visibleActions = actions.filter(a => {
1410
1396
  const writes = writtenFieldNames(a.action);
1411
1397
  return !(writes.length > 0 && writes.every(name => editableNames.has(name)));
1412
1398
  }), blockedReason = status === "active" && actions.length > 0 && actions.every(a => !a.allowed) ? actions.find(a => a.disabledReason)?.disabledReason : void 0, ticks = tickActionNames({
@@ -1683,8 +1669,8 @@ function activityStateOf(instance, activityName) {
1683
1669
  return findCurrentActivityEntry(instance, activityName)?.fields ?? [];
1684
1670
  }
1685
1671
 
1686
- function assigneeEntriesOf(state, assignField) {
1687
- return state.filter(s => s._type === "assignees" && (s.value.length > 0 || s.name === assignField?.name));
1672
+ function assigneeEntriesOf(state, assignFields) {
1673
+ return state.filter(s => s._type === "assignees" && (s.value.length > 0 || assignFields.has(s.name)));
1688
1674
  }
1689
1675
 
1690
1676
  function hasActivityActionItems(entry, activityName) {
@@ -1700,19 +1686,39 @@ function deriveActivityDetail(args) {
1700
1686
  state: state,
1701
1687
  workflowFields: instance.fields ?? [],
1702
1688
  editableFields: activityEditables
1703
- }), assignField = activityEditables.find(f => f.type === "assignees");
1689
+ }), assignFields = new Map(activityEditables.filter(f => f.type === "assignees").map(f => [ f.name, f ]));
1704
1690
  return {
1705
1691
  activityEval: activityEval,
1706
1692
  state: state,
1707
1693
  presentation: presentation,
1708
1694
  title: activityEval.activity.title ?? activityEval.activity.name,
1709
- assignField: assignField,
1710
- assigneeEntries: assigneeEntriesOf(state, assignField),
1695
+ assignFields: assignFields,
1696
+ assigneeEntries: assigneeEntriesOf(state, assignFields),
1711
1697
  hasActionItems: hasActivityActionItems(entry, activityName),
1712
1698
  hasActions: presentation.manualTarget !== void 0 || presentation.hasVisibleActions
1713
1699
  };
1714
1700
  }
1715
1701
 
1702
+ function stageTitle(definition, stageName) {
1703
+ return definition?.stages.find(stage => stage.name === stageName)?.title ?? stageName;
1704
+ }
1705
+
1706
+ function isHotField(evaluation, fieldName) {
1707
+ return (insightFor(evaluation, fieldName)?.involvedIn.length ?? 0) > 0;
1708
+ }
1709
+
1710
+ function advanceTargetTitle(evaluation, fieldName) {
1711
+ if (evaluation === void 0) return;
1712
+ const involved = new Set((insightFor(evaluation, fieldName)?.involvedIn ?? []).flatMap(site => site.kind === "transition" ? [ site.transition ] : []));
1713
+ if (involved.size === 0) return;
1714
+ const halting = evaluation.currentStage.transitions.find(t => t.filterSatisfied || t.unevaluable);
1715
+ if (!(halting === void 0 || !halting.filterSatisfied) && involved.has(halting.transition.name)) return stageTitle(evaluation.definition, halting.transition.to);
1716
+ }
1717
+
1718
+ function insightFor(evaluation, fieldName) {
1719
+ return evaluation?.fieldInsights.find(insight => insight.field === fieldName);
1720
+ }
1721
+
1716
1722
  function noteRow(args) {
1717
1723
  return {
1718
1724
  _key: randomKey(),
@@ -1733,6 +1739,14 @@ function FieldInput$1({field: field, value: value, onChange: onChange, onEnter:
1733
1739
  onChange: e => onChange(e.currentTarget.checked)
1734
1740
  });
1735
1741
 
1742
+ case "date":
1743
+ /* @__PURE__ */
1744
+ return jsx(TextInput, {
1745
+ onChange: e => onChange(e.currentTarget.value === "" ? null : e.currentTarget.value),
1746
+ type: "date",
1747
+ value: typeof value == "string" ? value : ""
1748
+ });
1749
+
1736
1750
  case "datetime":
1737
1751
  /* @__PURE__ */
1738
1752
  return jsx(TextInput, {
@@ -1750,6 +1764,14 @@ function FieldInput$1({field: field, value: value, onChange: onChange, onEnter:
1750
1764
  value: value == null ? "" : String(value)
1751
1765
  });
1752
1766
 
1767
+ case "text":
1768
+ /* @__PURE__ */
1769
+ return jsx(TextArea, {
1770
+ onChange: e => onChange(e.currentTarget.value),
1771
+ rows: 3,
1772
+ value: value == null ? "" : String(value)
1773
+ });
1774
+
1753
1775
  default:
1754
1776
  /* @__PURE__ */
1755
1777
  return jsx(TextInput, {
@@ -2129,7 +2151,7 @@ function ValueDisplay({field: field, value: value}) {
2129
2151
  }
2130
2152
 
2131
2153
  function EditableFieldControl({instanceId: instanceId, field: field, hideLabel: hideLabel}) {
2132
- const {editFieldFor: editFieldFor, byInstance: byInstance} = useWorkflowContext(), me = useCurrentUser(), instance = byInstance.get(instanceId)?.instance, entry = instance ? resolveFieldEntry(instance, field) : void 0;
2154
+ 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), advanceTo = advanceTargetTitle(evaluation, field.name);
2133
2155
  /* @__PURE__ */
2134
2156
  return jsx(EditableField, {
2135
2157
  field: field,
@@ -2139,17 +2161,27 @@ function EditableFieldControl({instanceId: instanceId, field: field, hideLabel:
2139
2161
  ...hideLabel === void 0 ? {} : {
2140
2162
  hideLabel: hideLabel
2141
2163
  },
2164
+ hot: isHotField(evaluation, field.name),
2165
+ ...advanceTo === void 0 ? {} : {
2166
+ advanceTo: advanceTo
2167
+ },
2142
2168
  onSave: value => editFieldFor(instanceId, {
2143
- target: editTargetOf(field),
2169
+ target: target,
2144
2170
  mode: "set",
2145
2171
  value: value
2146
2172
  }).then(() => {}),
2173
+ onPreview: value => previewFieldFor(instanceId, {
2174
+ target: target,
2175
+ mode: "set",
2176
+ value: value
2177
+ }),
2178
+ onDiscardPreview: () => discardFieldPreviewFor(instanceId, target),
2147
2179
  onUnset: () => editFieldFor(instanceId, {
2148
- target: editTargetOf(field),
2180
+ target: target,
2149
2181
  mode: "unset"
2150
2182
  }).then(() => {}),
2151
2183
  onAppend: body => editFieldFor(instanceId, {
2152
- target: editTargetOf(field),
2184
+ target: target,
2153
2185
  mode: "append",
2154
2186
  value: noteRow({
2155
2187
  body: body,
@@ -2216,21 +2248,28 @@ function FieldLabel({label: label, hide: hide}) {
2216
2248
  });
2217
2249
  }
2218
2250
 
2219
- function useSaveField(args) {
2220
- const toast = useToast(), [saving, setSaving] = useState(!1);
2251
+ function useSaveFailureToast() {
2252
+ const toast = useToast();
2253
+ return err => {
2254
+ console.error("[workflow-studio-plugin] field save failed:", err), toast.push({
2255
+ status: "error",
2256
+ title: "Saving failed",
2257
+ description: describeError(err)
2258
+ });
2259
+ };
2260
+ }
2261
+
2262
+ function useSaveField() {
2263
+ const reportFailure = useSaveFailureToast(), [saving, setSaving] = useState(!1);
2221
2264
  return {
2222
2265
  saving: saving,
2223
2266
  save: async commit => {
2224
2267
  if (!saving) {
2225
2268
  setSaving(!0);
2226
2269
  try {
2227
- await commit(), args.onSaved?.();
2270
+ await commit();
2228
2271
  } catch (err) {
2229
- console.error("[workflow-studio-plugin] field save failed:", err), toast.push({
2230
- status: "error",
2231
- title: "Saving failed",
2232
- description: describeError(err)
2233
- });
2272
+ reportFailure(err);
2234
2273
  } finally {
2235
2274
  setSaving(!1);
2236
2275
  }
@@ -2240,7 +2279,7 @@ function useSaveField(args) {
2240
2279
  }
2241
2280
 
2242
2281
  function ActorField({field: field, label: label, hideLabel: hideLabel, onSave: onSave, onUnset: onUnset}) {
2243
- const [open, setOpen] = useState(!1), {saving: saving, save: save} = useSaveField({}), cur = field.value, pick = member => {
2282
+ const [open, setOpen] = useState(!1), {saving: saving, save: save} = useSaveField(), cur = field.value, pick = member => {
2244
2283
  setOpen(!1), !saving && (member && member.id !== cur?.id ? save(() => onSave(personActor(member))) : onUnset !== void 0 && cur && save(onUnset));
2245
2284
  }, value = cur ? /* @__PURE__ */ jsx(ActorName, {
2246
2285
  id: cur.id
@@ -2323,10 +2362,14 @@ function ArrayField({field: field, label: label, hideLabel: hideLabel}) {
2323
2362
  }
2324
2363
 
2325
2364
  function NotesField({field: field, label: label, hideLabel: hideLabel, onAppend: onAppend}) {
2326
- const [body, setBody] = useState(""), {saving: saving, save: save} = useSaveField({
2327
- onSaved: () => setBody("")
2328
- }), rows = arrayRowsOf(field.value), trimmed = body.trim(), add = () => {
2329
- trimmed !== "" && save(() => onAppend(trimmed));
2365
+ const [body, setBody] = useState(""), {saving: saving, save: save} = useSaveField(), rows = arrayRowsOf(field.value), trimmed = body.trim(), add = () => {
2366
+ saving || trimmed === "" || (setBody(""), save(async () => {
2367
+ try {
2368
+ await onAppend(trimmed);
2369
+ } catch (err) {
2370
+ throw setBody(trimmed), err;
2371
+ }
2372
+ }));
2330
2373
  };
2331
2374
  /* @__PURE__ */
2332
2375
  return jsxs(Stack, {
@@ -2345,7 +2388,6 @@ function NotesField({field: field, label: label, hideLabel: hideLabel, onAppend:
2345
2388
  /* @__PURE__ */ jsx(Box, {
2346
2389
  flex: 1,
2347
2390
  children: /* @__PURE__ */ jsx(TextArea, {
2348
- disabled: saving,
2349
2391
  fontSize: 1,
2350
2392
  onChange: e => setBody(e.currentTarget.value),
2351
2393
  placeholder: "Add an entry…",
@@ -2354,9 +2396,8 @@ function NotesField({field: field, label: label, hideLabel: hideLabel, onAppend:
2354
2396
  })
2355
2397
  }),
2356
2398
  /* @__PURE__ */ jsx(Button, {
2357
- disabled: saving || trimmed === "",
2399
+ disabled: trimmed === "",
2358
2400
  fontSize: 1,
2359
- loading: saving,
2360
2401
  onClick: add,
2361
2402
  padding: 2,
2362
2403
  text: "Add",
@@ -2367,7 +2408,7 @@ function NotesField({field: field, label: label, hideLabel: hideLabel, onAppend:
2367
2408
  }
2368
2409
 
2369
2410
  function BooleanSwitchField({field: field, label: label, hideLabel: hideLabel, onSave: onSave}) {
2370
- const {saving: saving, save: save} = useSaveField({}), [optimistic, setOptimistic] = useState(null), committed = field.value === !0;
2411
+ const {save: save} = useSaveField(), committed = field.value === !0;
2371
2412
  /* @__PURE__ */
2372
2413
  return jsxs(Flex, {
2373
2414
  align: "center",
@@ -2379,68 +2420,56 @@ function BooleanSwitchField({field: field, label: label, hideLabel: hideLabel, o
2379
2420
  label: label
2380
2421
  }),
2381
2422
  /* @__PURE__ */ jsx(Switch, {
2382
- checked: optimistic ?? committed,
2383
- disabled: saving,
2423
+ checked: committed,
2384
2424
  onChange: () => {
2385
- const next = !committed;
2386
- setOptimistic(next), save(() => onSave(next)).finally(() => setOptimistic(null));
2425
+ save(() => onSave(!committed));
2387
2426
  }
2388
2427
  }) ]
2389
2428
  });
2390
2429
  }
2391
2430
 
2392
- function GenericField({field: field, label: label, hideLabel: hideLabel, onSave: onSave}) {
2393
- const [editing, setEditing] = useState(!1), [draft, setDraft] = useState(field.value), {saving: saving, save: save} = useSaveField({
2394
- onSaved: () => setEditing(!1)
2395
- });
2431
+ function sameValue(a, b) {
2432
+ return (a ?? void 0) === (b ?? void 0);
2433
+ }
2434
+
2435
+ function GenericField({field: field, label: label, hideLabel: hideLabel, onSave: onSave, onPreview: onPreview, onDiscardPreview: onDiscardPreview}) {
2436
+ const reportFailure = useSaveFailureToast(), [draft, setDraft] = useState(field.value), [baseline, setBaseline] = useState(field.value), dirty = !sameValue(draft, baseline), focused = useRef(!1), previewed = useRef(!1), commit = () => {
2437
+ if (!dirty) return;
2438
+ const value = draft, prev = baseline;
2439
+ setBaseline(value), previewed.current = !1, onSave(value).catch(err => {
2440
+ reportFailure(err), setBaseline(current => sameValue(current, value) ? prev : current),
2441
+ setDraft(current => sameValue(current, value) ? prev : current);
2442
+ });
2443
+ }, settle = () => {
2444
+ dirty ? commit() : previewed.current && (previewed.current = !1, onDiscardPreview?.());
2445
+ }, settleRef = useRef(settle);
2446
+ settleRef.current = settle, useEffect(() => () => settleRef.current(), []);
2447
+ const adopted = useRef(field.value);
2396
2448
  return useEffect(() => {
2397
- setDraft(field.value);
2398
- }, [ field.value ]), editing ? /* @__PURE__ */ jsxs(Stack, {
2449
+ sameValue(adopted.current, field.value) || focused.current || dirty || (adopted.current = field.value,
2450
+ setBaseline(field.value), setDraft(field.value));
2451
+ }, [ field.value, dirty ]), field.editable ? /* @__PURE__ */ jsxs(Stack, {
2399
2452
  gap: 2,
2400
2453
  children: [
2401
2454
  /* @__PURE__ */ jsx(FieldLabel, {
2402
2455
  hide: hideLabel,
2403
2456
  label: label
2404
2457
  }),
2405
- /* @__PURE__ */ jsxs(Flex, {
2406
- align: "center",
2407
- gap: 2,
2408
- children: [
2409
- /* @__PURE__ */ jsx(Box, {
2410
- flex: 1,
2411
- children: /* @__PURE__ */ jsx(FieldInput$1, {
2412
- field: field,
2413
- onChange: setDraft,
2414
- onEnter: () => save(() => onSave(draft)),
2415
- value: draft
2416
- })
2417
- }),
2418
- /* @__PURE__ */ jsx(HoverHint, {
2419
- text: "Save",
2420
- children: /* @__PURE__ */ jsx(Button, {
2421
- "aria-label": "Save",
2422
- fontSize: 1,
2423
- icon: CheckmarkIcon,
2424
- loading: saving,
2425
- onClick: () => save(() => onSave(draft)),
2426
- padding: 2,
2427
- tone: "primary"
2428
- })
2429
- }),
2430
- /* @__PURE__ */ jsx(HoverHint, {
2431
- text: "Cancel",
2432
- children: /* @__PURE__ */ jsx(Button, {
2433
- "aria-label": "Cancel",
2434
- disabled: saving,
2435
- fontSize: 1,
2436
- icon: CloseIcon,
2437
- mode: "bleed",
2438
- onClick: () => {
2439
- setDraft(field.value), setEditing(!1);
2440
- },
2441
- padding: 2
2442
- })
2443
- }) ]
2458
+ /* @__PURE__ */ jsx(Box, {
2459
+ onBlur: () => {
2460
+ focused.current = !1, settle();
2461
+ },
2462
+ onFocus: () => {
2463
+ focused.current = !0;
2464
+ },
2465
+ children: /* @__PURE__ */ jsx(FieldInput$1, {
2466
+ field: field,
2467
+ onChange: value => {
2468
+ setDraft(value), previewed.current = !0, onPreview?.(value);
2469
+ },
2470
+ onEnter: commit,
2471
+ value: draft
2472
+ })
2444
2473
  }) ]
2445
2474
  }) : /* @__PURE__ */ jsxs(Flex, {
2446
2475
  align: "center",
@@ -2450,14 +2479,7 @@ function GenericField({field: field, label: label, hideLabel: hideLabel, onSave:
2450
2479
  /* @__PURE__ */ jsx(FieldLabel, {
2451
2480
  hide: hideLabel,
2452
2481
  label: label
2453
- }), field.editable ? /* @__PURE__ */ jsx(EditableValueButton, {
2454
- hint: "Edit",
2455
- onClick: () => setEditing(!0),
2456
- children: /* @__PURE__ */ jsx(ValueDisplay, {
2457
- field: field,
2458
- value: field.value
2459
- })
2460
- }) :
2482
+ }),
2461
2483
  /* @__PURE__ */ jsxs(Flex, {
2462
2484
  align: "center",
2463
2485
  gap: 2,
@@ -2474,13 +2496,33 @@ function GenericField({field: field, label: label, hideLabel: hideLabel, onSave:
2474
2496
  });
2475
2497
  }
2476
2498
 
2477
- function EditableField({field: field, entry: entry, onSave: onSave, onUnset: onUnset, onAppend: onAppend, hideLabel: hideLabel = !1}) {
2478
- const label = field.title ?? field.name, arm = {
2479
- field: field,
2480
- label: label,
2481
- hideLabel: hideLabel,
2482
- onSave: onSave
2483
- };
2499
+ function HotFieldHint() {
2500
+ /* @__PURE__ */
2501
+ return jsx(HoverHint, {
2502
+ text: "Editing this can move the workflow",
2503
+ children: /* @__PURE__ */ jsx(Text, {
2504
+ "aria-label": "Editing this can move the workflow",
2505
+ muted: !0,
2506
+ size: 1,
2507
+ style: {
2508
+ cursor: "help"
2509
+ },
2510
+ children: /* @__PURE__ */ jsx(BoltIcon, {})
2511
+ })
2512
+ });
2513
+ }
2514
+
2515
+ function AdvanceHint({stageTitle: stageTitle2}) {
2516
+ /* @__PURE__ */
2517
+ return jsxs(Text, {
2518
+ muted: !0,
2519
+ size: 1,
2520
+ children: [ "Committing will advance to ", stageTitle2 ]
2521
+ });
2522
+ }
2523
+
2524
+ function fieldArm({arm: arm, entry: entry, onUnset: onUnset, onAppend: onAppend, onPreview: onPreview, onDiscardPreview: onDiscardPreview}) {
2525
+ const {field: field} = arm;
2484
2526
  return field.type === "actor" ? /* @__PURE__ */ jsx(ActorField, {
2485
2527
  ...arm,
2486
2528
  ...onUnset === void 0 ? {} : {
@@ -2496,7 +2538,45 @@ function EditableField({field: field, entry: entry, onSave: onSave, onUnset: onU
2496
2538
  }) : field.type === "boolean" && field.editable ? /* @__PURE__ */ jsx(BooleanSwitchField, {
2497
2539
  ...arm
2498
2540
  }) : /* @__PURE__ */ jsx(GenericField, {
2499
- ...arm
2541
+ ...arm,
2542
+ ...onPreview === void 0 ? {} : {
2543
+ onPreview: onPreview
2544
+ },
2545
+ ...onDiscardPreview === void 0 ? {} : {
2546
+ onDiscardPreview: onDiscardPreview
2547
+ }
2548
+ });
2549
+ }
2550
+
2551
+ function EditableField({field: field, entry: entry, onSave: onSave, onUnset: onUnset, onAppend: onAppend, onPreview: onPreview, onDiscardPreview: onDiscardPreview, hot: hot = !1, advanceTo: advanceTo, hideLabel: hideLabel = !1}) {
2552
+ const label = field.title ?? field.name, content = fieldArm({
2553
+ arm: {
2554
+ field: field,
2555
+ label: label,
2556
+ hideLabel: hideLabel,
2557
+ onSave: onSave
2558
+ },
2559
+ entry: entry,
2560
+ onUnset: onUnset,
2561
+ onAppend: onAppend,
2562
+ onPreview: onPreview,
2563
+ onDiscardPreview: onDiscardPreview
2564
+ }), decorated = field.editable;
2565
+ /* @__PURE__ */
2566
+ return jsxs(Stack, {
2567
+ gap: 2,
2568
+ children: [
2569
+ /* @__PURE__ */ jsxs(Flex, {
2570
+ align: "center",
2571
+ gap: 2,
2572
+ children: [
2573
+ /* @__PURE__ */ jsx(Box, {
2574
+ flex: 1,
2575
+ children: content
2576
+ }), decorated && hot ? /* @__PURE__ */ jsx(HotFieldHint, {}) : null ]
2577
+ }), decorated && advanceTo !== void 0 ? /* @__PURE__ */ jsx(AdvanceHint, {
2578
+ stageTitle: advanceTo
2579
+ }) : null ]
2500
2580
  });
2501
2581
  }
2502
2582
 
@@ -3264,7 +3344,7 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
3264
3344
  }
3265
3345
 
3266
3346
  function DetailRows({detail: detail, entry: entry, instanceId: instanceId, activityName: activityName}) {
3267
- const {activityEval: activityEval, presentation: presentation, assignField: assignField} = detail;
3347
+ const {activityEval: activityEval, presentation: presentation, assignFields: assignFields} = detail;
3268
3348
  /* @__PURE__ */
3269
3349
  return jsxs(Fragment, {
3270
3350
  children: [
@@ -3282,21 +3362,25 @@ function DetailRows({detail: detail, entry: entry, instanceId: instanceId, activ
3282
3362
  children: ACTIVITY_STATUS_LABEL[activityEval.status]
3283
3363
  }) ]
3284
3364
  })
3285
- }), detail.assigneeEntries.map(entry_ => /* @__PURE__ */ jsx(LabeledRow, {
3286
- label: entry_.title ?? "Assignees",
3287
- children: /* @__PURE__ */ jsxs(Flex, {
3288
- align: "center",
3289
- gap: 2,
3290
- wrap: "wrap",
3291
- children: [
3292
- /* @__PURE__ */ jsx(FieldValue, {
3293
- field: entry_
3294
- }), assignField?.name === entry_.name ? /* @__PURE__ */ jsx(AssignActivityControl, {
3295
- field: assignField,
3296
- instanceId: instanceId
3297
- }) : null ]
3298
- })
3299
- }, entry_._key)), presentation.dates.map(d => /* @__PURE__ */ jsx(LabeledRow, {
3365
+ }), detail.assigneeEntries.map(entry_ => {
3366
+ const assignField = assignFields.get(entry_.name);
3367
+ /* @__PURE__ */
3368
+ return jsx(LabeledRow, {
3369
+ label: entry_.title ?? "Assignees",
3370
+ children: /* @__PURE__ */ jsxs(Flex, {
3371
+ align: "center",
3372
+ gap: 2,
3373
+ wrap: "wrap",
3374
+ children: [
3375
+ /* @__PURE__ */ jsx(FieldValue, {
3376
+ field: entry_
3377
+ }), assignField ? /* @__PURE__ */ jsx(AssignActivityControl, {
3378
+ field: assignField,
3379
+ instanceId: instanceId
3380
+ }) : null ]
3381
+ })
3382
+ }, entry_._key);
3383
+ }), presentation.dates.map(d => /* @__PURE__ */ jsx(LabeledRow, {
3300
3384
  label: d.title ?? d.name,
3301
3385
  children: /* @__PURE__ */ jsx(BareValue, {
3302
3386
  children: /* @__PURE__ */ jsx(Text, {
@@ -3459,10 +3543,6 @@ function useInstanceTitle(entry) {
3459
3543
  return instanceTitle(entry.instance, definition);
3460
3544
  }
3461
3545
 
3462
- function stageTitle(instance, definition) {
3463
- return definition?.stages.find(s => s.name === instance.currentStage)?.title ?? instance.currentStage;
3464
- }
3465
-
3466
3546
  function InstanceStatusBadge({instance: instance, definition: definition}) {
3467
3547
  const state = terminalState(instance);
3468
3548
  return state === "aborted" ? /* @__PURE__ */ jsx(Badge, {
@@ -3476,7 +3556,7 @@ function InstanceStatusBadge({instance: instance, definition: definition}) {
3476
3556
  }) : /* @__PURE__ */ jsx(Badge, {
3477
3557
  fontSize: 1,
3478
3558
  tone: "primary",
3479
- children: stageTitle(instance, definition)
3559
+ children: stageTitle(definition, instance.currentStage)
3480
3560
  });
3481
3561
  }
3482
3562
 
@@ -3516,7 +3596,6 @@ function StageGlyph(props) {
3516
3596
  }
3517
3597
 
3518
3598
  function CurrentStageIndicator({instance: instance, definition: definition}) {
3519
- const stage = definition?.stages.find(s => s.name === instance.currentStage);
3520
3599
  /* @__PURE__ */
3521
3600
  return jsxs(Flex, {
3522
3601
  align: "center",
@@ -3529,7 +3608,7 @@ function CurrentStageIndicator({instance: instance, definition: definition}) {
3529
3608
  /* @__PURE__ */ jsx(Text, {
3530
3609
  size: 1,
3531
3610
  weight: "medium",
3532
- children: stage?.title ?? instance.currentStage
3611
+ children: stageTitle(definition, instance.currentStage)
3533
3612
  }) ]
3534
3613
  });
3535
3614
  }
@@ -3871,11 +3950,11 @@ function ActivityTab({instance: instance, definition: definition}) {
3871
3950
  }
3872
3951
 
3873
3952
  function makeTitleResolver(definition) {
3874
- const stages = definition?.stages ?? [], activities = stages.flatMap(s => s.activities ?? []);
3953
+ const activities = (definition?.stages ?? []).flatMap(s => s.activities ?? []);
3875
3954
  return {
3876
3955
  action: (activityName, actionName) => activities.find(t => t.name === activityName)?.actions?.find(a => a.name === actionName)?.title ?? actionName,
3877
3956
  activity: name => activities.find(t => t.name === name)?.title ?? name,
3878
- stage: name => stages.find(s => s.name === name)?.title ?? name
3957
+ stage: name => stageTitle(definition, name)
3879
3958
  };
3880
3959
  }
3881
3960
 
@@ -3955,8 +4034,7 @@ function InstanceMenuItem({entry: entry, selected: selected, onSelect: onSelect}
3955
4034
 
3956
4035
  function instanceNotice(entry) {
3957
4036
  const {instance: instance, committed: committed, evaluation: evaluation} = entry, state = terminalState(instance);
3958
- if (state !== "in-flight") /* @__PURE__ */
3959
- return jsx(Card, {
4037
+ return state !== "in-flight" ? /* @__PURE__ */ jsx(Card, {
3960
4038
  border: !0,
3961
4039
  padding: 3,
3962
4040
  radius: 2,
@@ -3965,24 +4043,27 @@ function instanceNotice(entry) {
3965
4043
  size: 1,
3966
4044
  children: state === "aborted" ? "This workflow was aborted — no open tasks." : "This workflow is complete — no open tasks."
3967
4045
  })
3968
- });
3969
- const freshness = evaluationFreshness({
4046
+ }) : evaluationFreshness({
3970
4047
  committed: committed,
3971
4048
  evaluation: evaluation
3972
- });
3973
- return freshness === "pending" ? /* @__PURE__ */ jsx(LoadingRow, {
4049
+ }) === "pending" ? /* @__PURE__ */ jsx(LoadingRow, {
3974
4050
  label: "Evaluating…",
3975
4051
  padding: 4
3976
- }) : freshness === "stale" ? /* @__PURE__ */ jsx(LoadingRow, {
3977
- label: "Updating…",
3978
- padding: 4
3979
4052
  }) : null;
3980
4053
  }
3981
4054
 
4055
+ function evaluationSyncing(entry) {
4056
+ const {instance: instance, committed: committed, evaluation: evaluation} = entry;
4057
+ return terminalState(instance) === "in-flight" && evaluationFreshness({
4058
+ committed: committed,
4059
+ evaluation: evaluation
4060
+ }) === "stale";
4061
+ }
4062
+
3982
4063
  const NO_EDITABLES = /* @__PURE__ */ new Map;
3983
4064
 
3984
4065
  function InstanceStateTab({entry: entry}) {
3985
- const {instance: instance} = entry, definition = useDefinition(entry), stageTitleOf = name => definition?.stages.find(s => s.name === name)?.title ?? name, editables = (entry.evaluation?.editableFields ?? []).filter(f => f.scope !== "activity"), editablesByName = scope => new Map(editables.filter(f => f.scope === scope).map(f => [ f.name, f ])), anyState = instance.fields.length > 0 || instance.stages.some(s => s.fields.length > 0);
4066
+ const {instance: instance} = entry, definition = useDefinition(entry), editables = (entry.evaluation?.editableFields ?? []).filter(f => f.scope !== "activity"), editablesByName = scope => new Map(editables.filter(f => f.scope === scope).map(f => [ f.name, f ])), anyState = instance.fields.length > 0 || instance.stages.some(s => s.fields.length > 0);
3986
4067
  /* @__PURE__ */
3987
4068
  return jsxs(Stack, {
3988
4069
  gap: 4,
@@ -4000,7 +4081,7 @@ function InstanceStateTab({entry: entry}) {
4000
4081
  return jsx(ScopeGroup, {
4001
4082
  editables: current ? editablesByName("stage") : NO_EDITABLES,
4002
4083
  fields: st.fields ?? [],
4003
- heading: `Stage · ${stageTitleOf(st.name)}${suffix}`,
4084
+ heading: `Stage · ${stageTitle(definition, st.name)}${suffix}`,
4004
4085
  instanceId: instance._id
4005
4086
  }, `s-${i}`);
4006
4087
  }) ]
@@ -4204,7 +4285,7 @@ function StageIndicator({entry: entry, definition: definition}) {
4204
4285
  }
4205
4286
 
4206
4287
  function InstancePanel({entry: entry, entries: entries, onSelect: onSelect}) {
4207
- const definition = useDefinition(entry), {instance: instance, ready: ready} = entry, identity = useAssignmentIdentity(), [assignedToMe, setAssignedToMe] = useState(!1), [openActivity, setOpenActivity] = useState(null), [segment, setSegment] = useState("state"), stageTitle2 = definition?.stages.find(s => s.name === instance.currentStage)?.title ?? instance.currentStage, notice = instanceNotice(entry);
4288
+ const definition = useDefinition(entry), {instance: instance, ready: ready} = entry, identity = useAssignmentIdentity(), [assignedToMe, setAssignedToMe] = useState(!1), [openActivity, setOpenActivity] = useState(null), [segment, setSegment] = useState("state"), currentStageTitle = stageTitle(definition, instance.currentStage), notice = instanceNotice(entry), syncing = evaluationSyncing(entry);
4208
4289
  /* @__PURE__ */
4209
4290
  return jsxs(Flex, {
4210
4291
  direction: "column",
@@ -4231,8 +4312,18 @@ function InstancePanel({entry: entry, entries: entries, onSelect: onSelect}) {
4231
4312
  }),
4232
4313
  /* @__PURE__ */ jsx(PendingWorkBadge, {
4233
4314
  instance: instance
4234
- }), ready ? null : /* @__PURE__ */ jsx(Spinner, {
4235
- muted: !0
4315
+ }),
4316
+ /* @__PURE__ */ jsx(Flex, {
4317
+ align: "center",
4318
+ flex: "none",
4319
+ justify: "center",
4320
+ style: {
4321
+ width: 19,
4322
+ height: 19
4323
+ },
4324
+ children: !ready || syncing ? /* @__PURE__ */ jsx(Spinner, {
4325
+ muted: !0
4326
+ }) : null
4236
4327
  }),
4237
4328
  /* @__PURE__ */ jsx(Box, {
4238
4329
  flex: 1
@@ -4292,20 +4383,29 @@ function InstancePanel({entry: entry, entries: entries, onSelect: onSelect}) {
4292
4383
  size: 1,
4293
4384
  children: "Couldn’t load this workflow’s definition — stage and action titles fall back to raw names."
4294
4385
  })
4295
- }), notice ?? /* @__PURE__ */ jsxs(Stack, {
4296
- children: [
4297
- /* @__PURE__ */ jsx(ActivitiesList, {
4298
- assignedToMeOnly: assignedToMe,
4299
- entry: entry,
4300
- identity: identity,
4301
- onOpenActivity: setOpenActivity,
4302
- stageTitle: stageTitle2
4303
- }),
4304
- /* @__PURE__ */ jsx(ActionItemsList, {
4305
- assignedToMeOnly: assignedToMe,
4306
- entry: entry,
4307
- identity: identity
4308
- }) ]
4386
+ }), notice ??
4387
+ /* @__PURE__ */ jsx(Box, {
4388
+ style: {
4389
+ opacity: syncing ? .5 : 1,
4390
+ pointerEvents: syncing ? "none" : void 0,
4391
+ transition: "opacity 150ms ease-in"
4392
+ },
4393
+ children: /* @__PURE__ */ jsxs(Stack, {
4394
+ "aria-busy": syncing,
4395
+ children: [
4396
+ /* @__PURE__ */ jsx(ActivitiesList, {
4397
+ assignedToMeOnly: assignedToMe,
4398
+ entry: entry,
4399
+ identity: identity,
4400
+ onOpenActivity: setOpenActivity,
4401
+ stageTitle: currentStageTitle
4402
+ }),
4403
+ /* @__PURE__ */ jsx(ActionItemsList, {
4404
+ assignedToMeOnly: assignedToMe,
4405
+ entry: entry,
4406
+ identity: identity
4407
+ }) ]
4408
+ })
4309
4409
  }),
4310
4410
  /* @__PURE__ */ jsx(Card, {
4311
4411
  border: !0,
@@ -4348,7 +4448,7 @@ function InstancePanel({entry: entry, entries: entries, onSelect: onSelect}) {
4348
4448
  })
4349
4449
  }), openActivity ? /* @__PURE__ */ jsx(ActivityDetailDialog, {
4350
4450
  activityName: openActivity,
4351
- breadcrumb: [ instanceTitle(instance, definition), stageTitle2 ],
4451
+ breadcrumb: [ instanceTitle(instance, definition), currentStageTitle ],
4352
4452
  entry: entry,
4353
4453
  onClose: () => setOpenActivity(null)
4354
4454
  }) : null ]
@@ -4409,7 +4509,7 @@ function useRefcountedIds() {
4409
4509
  };
4410
4510
  }
4411
4511
 
4412
- const REPORT_KEYS = [ "evaluation", "ready", "guards", "fireAction", "editField" ];
4512
+ const REPORT_KEYS = [ "evaluation", "ready", "guards", "fireAction", "editField", "previewField", "discardFieldPreview" ];
4413
4513
 
4414
4514
  function sameReport(a, b) {
4415
4515
  return REPORT_KEYS.every(key => a[key] === b[key]);
@@ -4453,7 +4553,7 @@ class InstanceEvaluatorBoundary extends Component {
4453
4553
  }
4454
4554
 
4455
4555
  function InstanceEvaluator(props) {
4456
- const {engine: engine, instanceId: instanceId, grantsFromPath: grantsFromPath, onReport: onReport} = props, {evaluation: evaluation, ready: ready, guards: guards, fireAction: fireAction, editField: editField} = useWorkflowSession({
4556
+ const {engine: engine, instanceId: instanceId, grantsFromPath: grantsFromPath, onReport: onReport} = props, {evaluation: evaluation, ready: ready, guards: guards, fireAction: fireAction, editField: editField, previewField: previewField, discardFieldPreview: discardFieldPreview} = useWorkflowSession({
4457
4557
  engine: engine,
4458
4558
  instanceId: instanceId,
4459
4559
  grantsFromPath: grantsFromPath
@@ -4464,9 +4564,11 @@ function InstanceEvaluator(props) {
4464
4564
  ready: ready,
4465
4565
  guards: guards,
4466
4566
  fireAction: fireAction,
4467
- editField: editField
4567
+ editField: editField,
4568
+ previewField: previewField,
4569
+ discardFieldPreview: discardFieldPreview
4468
4570
  });
4469
- }, [ instanceId, evaluation, ready, guards, fireAction, editField, onReport ]),
4571
+ }, [ instanceId, evaluation, ready, guards, fireAction, editField, previewField, discardFieldPreview, onReport ]),
4470
4572
  useEffect(() => () => onReport(instanceId, void 0), [ instanceId, onReport ]), null;
4471
4573
  }
4472
4574
 
@@ -4529,7 +4631,10 @@ function WorkflowProvider(props) {
4529
4631
  ...config.effectHandlers ? {
4530
4632
  effectHandlers: config.effectHandlers
4531
4633
  } : {}
4532
- }), grantsFromPath = `/projects/${projectId}/datasets/${workflowDataset}/acl`, {ids: registered, acquire: register, release: unregister} = useRefcountedIds(), {ids: requested, acquire: requestInstance, release: releaseInstance} = useRefcountedIds(), [seeded, setSeeded] = useState(/* @__PURE__ */ new Map), seedInstance = useCallback(instance => {
4634
+ }), grantsFromPath = aclPathForResource({
4635
+ type: "dataset",
4636
+ id: `${projectId}.${workflowDataset}`
4637
+ }), {ids: registered, acquire: register, release: unregister} = useRefcountedIds(), {ids: requested, acquire: requestInstance, release: releaseInstance} = useRefcountedIds(), [seeded, setSeeded] = useState(/* @__PURE__ */ new Map), seedInstance = useCallback(instance => {
4533
4638
  setSeeded(prev => new Map(prev).set(instance._id, instance));
4534
4639
  }, []), [startRequest, setStartRequest] = useState(void 0), openStartDialog = useCallback(r => setStartRequest(r), []), closeStartDialog = useCallback(() => setStartRequest(void 0), []), [reports, setReports] = useState(/* @__PURE__ */ new Map), onReport = useCallback((instanceId, report) => {
4535
4640
  setReports(prev => nextReports(prev, {
@@ -4559,7 +4664,11 @@ function WorkflowProvider(props) {
4559
4664
  const report = reportsRef.current.get(instanceId);
4560
4665
  return report ? report.editField(args).then(async res => (await drainCommitted(instanceId),
4561
4666
  res)) : notReady(args);
4562
- }, [ drainCommitted ]), drainEffectsFor = useCallback(async instanceId => {
4667
+ }, [ drainCommitted ]), previewFieldFor = useCallback((instanceId, args) => {
4668
+ reportsRef.current.get(instanceId)?.previewField(args);
4669
+ }, []), discardFieldPreviewFor = useCallback((instanceId, target) => {
4670
+ reportsRef.current.get(instanceId)?.discardFieldPreview(target);
4671
+ }, []), drainEffectsFor = useCallback(async instanceId => {
4563
4672
  await engine.drainEffects({
4564
4673
  instanceId: instanceId
4565
4674
  });
@@ -4629,6 +4738,8 @@ function WorkflowProvider(props) {
4629
4738
  loading: loading,
4630
4739
  fireActionFor: fireActionFor,
4631
4740
  editFieldFor: editFieldFor,
4741
+ previewFieldFor: previewFieldFor,
4742
+ discardFieldPreviewFor: discardFieldPreviewFor,
4632
4743
  drainEffectsFor: drainEffectsFor,
4633
4744
  completeEffectFor: completeEffectFor,
4634
4745
  engine: engine,
@@ -4636,7 +4747,7 @@ function WorkflowProvider(props) {
4636
4747
  mappings: config.mappings,
4637
4748
  mappingIssues: issues,
4638
4749
  effectHandlers: engine.effectHandlers
4639
- }), [ register, unregister, isDocResolved, byDocument, byInstance, requestInstance, releaseInstance, seedInstance, openStartDialog, closeStartDialog, startRequest, loading, fireActionFor, editFieldFor, drainEffectsFor, completeEffectFor, engine, binding, config.mappings, issues ]);
4750
+ }), [ register, unregister, isDocResolved, byDocument, byInstance, requestInstance, releaseInstance, seedInstance, openStartDialog, closeStartDialog, startRequest, loading, fireActionFor, editFieldFor, previewFieldFor, discardFieldPreviewFor, drainEffectsFor, completeEffectFor, engine, binding, config.mappings, issues ]);
4640
4751
  /* @__PURE__ */
4641
4752
  return jsxs(WorkflowContext.Provider, {
4642
4753
  value: value,