@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.cjs CHANGED
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: !0
5
5
  });
6
6
 
7
- var workflowEngine = require("@sanity/workflow-engine"), jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), react = require("react"), structure = require("sanity/structure"), Add = require("@sanity/icons/Add"), csm = require("@sanity/client/csm"), ChevronRight = require("@sanity/icons/ChevronRight"), Calendar = require("@sanity/icons/Calendar"), Close = require("@sanity/icons/Close"), User = require("@sanity/icons/User"), Checkmark = require("@sanity/icons/Checkmark"), Empty$1 = require("@sanity/icons/Empty"), sanity = require("sanity"), ErrorOutline = require("@sanity/icons/ErrorOutline"), WarningOutline = require("@sanity/icons/WarningOutline"), CheckmarkCircle = require("@sanity/icons/CheckmarkCircle"), Circle = require("@sanity/icons/Circle"), CloseCircle = require("@sanity/icons/CloseCircle"), RemoveCircle = require("@sanity/icons/RemoveCircle"), Clock = require("@sanity/icons/Clock"), Edit = require("@sanity/icons/Edit"), content = require("@sanity/util/content"), router = require("sanity/router"), Launch = require("@sanity/icons/Launch"), InfoOutline = require("@sanity/icons/InfoOutline"), Search = require("@sanity/icons/Search"), workflowStudio = require("@sanity/workflow-studio"), ChevronDown = require("@sanity/icons/ChevronDown"), SplitVertical = require("@sanity/icons/SplitVertical");
7
+ var workflowEngine = require("@sanity/workflow-engine"), jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), react = require("react"), structure = require("sanity/structure"), Add = require("@sanity/icons/Add"), csm = require("@sanity/client/csm"), ChevronRight = require("@sanity/icons/ChevronRight"), Calendar = require("@sanity/icons/Calendar"), Close = require("@sanity/icons/Close"), User = require("@sanity/icons/User"), Checkmark = require("@sanity/icons/Checkmark"), Empty$1 = require("@sanity/icons/Empty"), sanity = require("sanity"), ErrorOutline = require("@sanity/icons/ErrorOutline"), WarningOutline = require("@sanity/icons/WarningOutline"), CheckmarkCircle = require("@sanity/icons/CheckmarkCircle"), Circle = require("@sanity/icons/Circle"), CloseCircle = require("@sanity/icons/CloseCircle"), RemoveCircle = require("@sanity/icons/RemoveCircle"), Clock = require("@sanity/icons/Clock"), Bolt = require("@sanity/icons/Bolt"), Edit = require("@sanity/icons/Edit"), content = require("@sanity/util/content"), router = require("sanity/router"), Launch = require("@sanity/icons/Launch"), InfoOutline = require("@sanity/icons/InfoOutline"), Search = require("@sanity/icons/Search"), workflowStudio = require("@sanity/workflow-studio"), ChevronDown = require("@sanity/icons/ChevronDown"), SplitVertical = require("@sanity/icons/SplitVertical");
8
8
 
9
9
  function mappingForDocType(mappings, docType) {
10
10
  return mappings.find(m => m.docType === docType);
@@ -314,7 +314,8 @@ const activityNotActiveSentence = {
314
314
  "instance-completed": () => "This workflow is already complete.",
315
315
  "mutation-guard-denied": () => "An earlier step is holding this document.",
316
316
  "requirements-unmet": () => "This step isn't ready yet — a required condition hasn't been met.",
317
- "stage-terminal": () => "This stage is finished."
317
+ "stage-terminal": () => "This stage is finished.",
318
+ "subject-permission-denied": () => "You don't have permission to change the document this step works on."
318
319
  };
319
320
 
320
321
  function describeDisabledReason(reason) {
@@ -826,20 +827,9 @@ function UserAvatarGroup({ids: ids}) {
826
827
  });
827
828
  }
828
829
 
829
- function TodoCheckbox({busy: busy, checked: checked, canClick: canClick, onToggle: onToggle}) {
830
- return busy ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
831
- align: "center",
832
- "aria-label": "Saving…",
833
- justify: "center",
834
- style: {
835
- width: 17,
836
- height: 17
837
- },
838
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
839
- muted: !0,
840
- size: 1
841
- })
842
- }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Checkbox, {
830
+ function TodoCheckbox({checked: checked, canClick: canClick, onToggle: onToggle}) {
831
+ /* @__PURE__ */
832
+ return jsxRuntime.jsx(ui.Checkbox, {
843
833
  checked: checked,
844
834
  disabled: !canClick,
845
835
  onChange: onToggle,
@@ -982,10 +972,10 @@ function AssigneeControl({item: item, editable: editable, onPatch: onPatch}) {
982
972
  }) : chip;
983
973
  }
984
974
 
985
- function AddItemControl({busy: busy, onAdd: onAdd, button: button}) {
975
+ function AddItemControl({onAdd: onAdd, button: button, busy: busy}) {
986
976
  const [open, setOpen] = react.useState(!1), [label, setLabel] = react.useState(""), submit = () => {
987
977
  const trimmed = label.trim();
988
- trimmed && (setOpen(!1), setLabel(""), onAdd(trimmed));
978
+ !trimmed || busy || (setOpen(!1), setLabel(""), onAdd(trimmed));
989
979
  };
990
980
  /* @__PURE__ */
991
981
  return jsxRuntime.jsx(DismissablePopover, {
@@ -1017,13 +1007,11 @@ function AddItemControl({busy: busy, onAdd: onAdd, button: button}) {
1017
1007
  open: open,
1018
1008
  placement: "bottom-start",
1019
1009
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1020
- onClick: () => !busy && setOpen(v => !v),
1010
+ onClick: () => setOpen(v => !v),
1021
1011
  style: {
1022
1012
  display: "inline-flex"
1023
1013
  },
1024
- children: busy ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
1025
- muted: !0
1026
- }) : button
1014
+ children: button
1027
1015
  })
1028
1016
  });
1029
1017
  }
@@ -1052,7 +1040,7 @@ function rowInteractivity(row) {
1052
1040
  };
1053
1041
  }
1054
1042
 
1055
- function ActionItemRowView({row: row, busy: busy, showBreadcrumb: showBreadcrumb, onToggle: onToggle, onPatch: onPatch}) {
1043
+ function ActionItemRowView({row: row, showBreadcrumb: showBreadcrumb, onToggle: onToggle, onPatch: onPatch}) {
1056
1044
  const {canClick: canClick, hint: hint} = rowInteractivity(row), editable = row.editTarget !== void 0, body = /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1057
1045
  align: "center",
1058
1046
  gap: 2,
@@ -1063,7 +1051,6 @@ function ActionItemRowView({row: row, busy: busy, showBreadcrumb: showBreadcrumb
1063
1051
  flexShrink: 0
1064
1052
  },
1065
1053
  children: /* @__PURE__ */ jsxRuntime.jsx(TodoCheckbox, {
1066
- busy: busy,
1067
1054
  canClick: canClick,
1068
1055
  checked: isTodoDone(row.item),
1069
1056
  onToggle: onToggle
@@ -1134,12 +1121,10 @@ function RowBreadcrumb({row: row}) {
1134
1121
  });
1135
1122
  }
1136
1123
 
1137
- const ADD_KEY = "__add__";
1138
-
1139
1124
  function ActionItemsList({entry: entry, assignedToMeOnly: assignedToMeOnly = !1, identity: identity, activityFilter: activityFilter, variant: variant = "section"}) {
1140
- const {instance: instance} = entry, {editFieldFor: editFieldFor, fireActionFor: fireActionFor} = useWorkflowContext(), toast = ui.useToast(), [busyKey, setBusyKey] = react.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) => {
1141
- if (!busyKey) {
1142
- setBusyKey(key);
1125
+ const {instance: instance} = entry, {editFieldFor: editFieldFor, fireActionFor: fireActionFor} = useWorkflowContext(), toast = ui.useToast(), [busy, setBusy] = react.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 => {
1126
+ if (!busy) {
1127
+ setBusy(!0);
1143
1128
  try {
1144
1129
  await op();
1145
1130
  } catch (err) {
@@ -1149,7 +1134,7 @@ function ActionItemsList({entry: entry, assignedToMeOnly: assignedToMeOnly = !1,
1149
1134
  description: describeError(err)
1150
1135
  });
1151
1136
  } finally {
1152
- setBusyKey(null);
1137
+ setBusy(!1);
1153
1138
  }
1154
1139
  }
1155
1140
  }, patchItem = (row, patch) => {
@@ -1158,7 +1143,7 @@ function ActionItemsList({entry: entry, assignedToMeOnly: assignedToMeOnly = !1,
1158
1143
  ...it,
1159
1144
  ...patch
1160
1145
  } : it);
1161
- return run(row.item._key, () => editFieldFor(instance._id, {
1146
+ return run(() => editFieldFor(instance._id, {
1162
1147
  target: target,
1163
1148
  mode: "set",
1164
1149
  value: next
@@ -1168,11 +1153,11 @@ function ActionItemsList({entry: entry, assignedToMeOnly: assignedToMeOnly = !1,
1168
1153
  status: toggledTodoStatus(row.item)
1169
1154
  });
1170
1155
  const tick = row.tick;
1171
- return tick && tick.actionEval.allowed && !isTodoDone(row.item) ? run(row.item._key, () => fireActionFor(instance._id, {
1156
+ return tick && tick.actionEval.allowed && !isTodoDone(row.item) ? run(() => fireActionFor(instance._id, {
1172
1157
  activity: tick.activity,
1173
1158
  action: tick.actionEval.action.name
1174
1159
  })) : Promise.resolve();
1175
- }, addItem = label => appendTarget ? run(ADD_KEY, () => editFieldFor(instance._id, {
1160
+ }, addItem = label => appendTarget ? run(() => editFieldFor(instance._id, {
1176
1161
  target: appendTarget,
1177
1162
  mode: "append",
1178
1163
  value: {
@@ -1182,7 +1167,6 @@ function ActionItemsList({entry: entry, assignedToMeOnly: assignedToMeOnly = !1,
1182
1167
  })) : Promise.resolve();
1183
1168
  if (rows.length === 0 && !appendTarget) return null;
1184
1169
  const rowKey = row => `${row.scope}:${row.activityName ?? ""}:${row.fieldName}:${row.item._key}`, rowProps = row => ({
1185
- busy: busyKey === row.item._key,
1186
1170
  onPatch: patch => {
1187
1171
  patchItem(row, patch);
1188
1172
  },
@@ -1201,7 +1185,7 @@ function ActionItemsList({entry: entry, assignedToMeOnly: assignedToMeOnly = !1,
1201
1185
  }, rowKey(row)))
1202
1186
  }), appendTarget ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
1203
1187
  children: /* @__PURE__ */ jsxRuntime.jsx(AddItemControl, {
1204
- busy: busyKey === ADD_KEY,
1188
+ busy: busy,
1205
1189
  button: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1206
1190
  fontSize: 1,
1207
1191
  icon: Add.AddIcon,
@@ -1227,7 +1211,7 @@ function ActionItemsList({entry: entry, assignedToMeOnly: assignedToMeOnly = !1,
1227
1211
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1228
1212
  paddingRight: 2,
1229
1213
  children: /* @__PURE__ */ jsxRuntime.jsx(AddItemControl, {
1230
- busy: busyKey === ADD_KEY,
1214
+ busy: busy,
1231
1215
  button: /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
1232
1216
  text: "Add action item",
1233
1217
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
@@ -1354,7 +1338,7 @@ function groupEditableByActivity(fields) {
1354
1338
  }
1355
1339
 
1356
1340
  function deriveActivityPresentation(args) {
1357
- 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(workflowEngine.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 => {
1341
+ 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(workflowEngine.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 => {
1358
1342
  const writes = writtenFieldNames(a.action);
1359
1343
  return !(writes.length > 0 && writes.every(name => editableNames.has(name)));
1360
1344
  }), blockedReason = status === "active" && actions.length > 0 && actions.every(a => !a.allowed) ? actions.find(a => a.disabledReason)?.disabledReason : void 0, ticks = tickActionNames({
@@ -1631,8 +1615,8 @@ function activityStateOf(instance, activityName) {
1631
1615
  return workflowEngine.findCurrentActivityEntry(instance, activityName)?.fields ?? [];
1632
1616
  }
1633
1617
 
1634
- function assigneeEntriesOf(state, assignField) {
1635
- return state.filter(s => s._type === "assignees" && (s.value.length > 0 || s.name === assignField?.name));
1618
+ function assigneeEntriesOf(state, assignFields) {
1619
+ return state.filter(s => s._type === "assignees" && (s.value.length > 0 || assignFields.has(s.name)));
1636
1620
  }
1637
1621
 
1638
1622
  function hasActivityActionItems(entry, activityName) {
@@ -1648,19 +1632,39 @@ function deriveActivityDetail(args) {
1648
1632
  state: state,
1649
1633
  workflowFields: instance.fields ?? [],
1650
1634
  editableFields: activityEditables
1651
- }), assignField = activityEditables.find(f => f.type === "assignees");
1635
+ }), assignFields = new Map(activityEditables.filter(f => f.type === "assignees").map(f => [ f.name, f ]));
1652
1636
  return {
1653
1637
  activityEval: activityEval,
1654
1638
  state: state,
1655
1639
  presentation: presentation,
1656
1640
  title: activityEval.activity.title ?? activityEval.activity.name,
1657
- assignField: assignField,
1658
- assigneeEntries: assigneeEntriesOf(state, assignField),
1641
+ assignFields: assignFields,
1642
+ assigneeEntries: assigneeEntriesOf(state, assignFields),
1659
1643
  hasActionItems: hasActivityActionItems(entry, activityName),
1660
1644
  hasActions: presentation.manualTarget !== void 0 || presentation.hasVisibleActions
1661
1645
  };
1662
1646
  }
1663
1647
 
1648
+ function stageTitle(definition, stageName) {
1649
+ return definition?.stages.find(stage => stage.name === stageName)?.title ?? stageName;
1650
+ }
1651
+
1652
+ function isHotField(evaluation, fieldName) {
1653
+ return (insightFor(evaluation, fieldName)?.involvedIn.length ?? 0) > 0;
1654
+ }
1655
+
1656
+ function advanceTargetTitle(evaluation, fieldName) {
1657
+ if (evaluation === void 0) return;
1658
+ const involved = new Set((insightFor(evaluation, fieldName)?.involvedIn ?? []).flatMap(site => site.kind === "transition" ? [ site.transition ] : []));
1659
+ if (involved.size === 0) return;
1660
+ const halting = evaluation.currentStage.transitions.find(t => t.filterSatisfied || t.unevaluable);
1661
+ if (!(halting === void 0 || !halting.filterSatisfied) && involved.has(halting.transition.name)) return stageTitle(evaluation.definition, halting.transition.to);
1662
+ }
1663
+
1664
+ function insightFor(evaluation, fieldName) {
1665
+ return evaluation?.fieldInsights.find(insight => insight.field === fieldName);
1666
+ }
1667
+
1664
1668
  function noteRow(args) {
1665
1669
  return {
1666
1670
  _key: content.randomKey(),
@@ -1681,6 +1685,14 @@ function FieldInput$1({field: field, value: value, onChange: onChange, onEnter:
1681
1685
  onChange: e => onChange(e.currentTarget.checked)
1682
1686
  });
1683
1687
 
1688
+ case "date":
1689
+ /* @__PURE__ */
1690
+ return jsxRuntime.jsx(ui.TextInput, {
1691
+ onChange: e => onChange(e.currentTarget.value === "" ? null : e.currentTarget.value),
1692
+ type: "date",
1693
+ value: typeof value == "string" ? value : ""
1694
+ });
1695
+
1684
1696
  case "datetime":
1685
1697
  /* @__PURE__ */
1686
1698
  return jsxRuntime.jsx(ui.TextInput, {
@@ -1698,6 +1710,14 @@ function FieldInput$1({field: field, value: value, onChange: onChange, onEnter:
1698
1710
  value: value == null ? "" : String(value)
1699
1711
  });
1700
1712
 
1713
+ case "text":
1714
+ /* @__PURE__ */
1715
+ return jsxRuntime.jsx(ui.TextArea, {
1716
+ onChange: e => onChange(e.currentTarget.value),
1717
+ rows: 3,
1718
+ value: value == null ? "" : String(value)
1719
+ });
1720
+
1701
1721
  default:
1702
1722
  /* @__PURE__ */
1703
1723
  return jsxRuntime.jsx(ui.TextInput, {
@@ -2077,7 +2097,7 @@ function ValueDisplay({field: field, value: value}) {
2077
2097
  }
2078
2098
 
2079
2099
  function EditableFieldControl({instanceId: instanceId, field: field, hideLabel: hideLabel}) {
2080
- const {editFieldFor: editFieldFor, byInstance: byInstance} = useWorkflowContext(), me = sanity.useCurrentUser(), instance = byInstance.get(instanceId)?.instance, entry = instance ? workflowEngine.resolveFieldEntry(instance, field) : void 0;
2100
+ const {editFieldFor: editFieldFor, previewFieldFor: previewFieldFor, discardFieldPreviewFor: discardFieldPreviewFor, byInstance: byInstance} = useWorkflowContext(), me = sanity.useCurrentUser(), held = byInstance.get(instanceId), entry = held ? workflowEngine.resolveFieldEntry(held.instance, field) : void 0, evaluation = held?.evaluation, target = editTargetOf(field), advanceTo = advanceTargetTitle(evaluation, field.name);
2081
2101
  /* @__PURE__ */
2082
2102
  return jsxRuntime.jsx(EditableField, {
2083
2103
  field: field,
@@ -2087,17 +2107,27 @@ function EditableFieldControl({instanceId: instanceId, field: field, hideLabel:
2087
2107
  ...hideLabel === void 0 ? {} : {
2088
2108
  hideLabel: hideLabel
2089
2109
  },
2110
+ hot: isHotField(evaluation, field.name),
2111
+ ...advanceTo === void 0 ? {} : {
2112
+ advanceTo: advanceTo
2113
+ },
2090
2114
  onSave: value => editFieldFor(instanceId, {
2091
- target: editTargetOf(field),
2115
+ target: target,
2092
2116
  mode: "set",
2093
2117
  value: value
2094
2118
  }).then(() => {}),
2119
+ onPreview: value => previewFieldFor(instanceId, {
2120
+ target: target,
2121
+ mode: "set",
2122
+ value: value
2123
+ }),
2124
+ onDiscardPreview: () => discardFieldPreviewFor(instanceId, target),
2095
2125
  onUnset: () => editFieldFor(instanceId, {
2096
- target: editTargetOf(field),
2126
+ target: target,
2097
2127
  mode: "unset"
2098
2128
  }).then(() => {}),
2099
2129
  onAppend: body => editFieldFor(instanceId, {
2100
- target: editTargetOf(field),
2130
+ target: target,
2101
2131
  mode: "append",
2102
2132
  value: noteRow({
2103
2133
  body: body,
@@ -2164,21 +2194,28 @@ function FieldLabel({label: label, hide: hide}) {
2164
2194
  });
2165
2195
  }
2166
2196
 
2167
- function useSaveField(args) {
2168
- const toast = ui.useToast(), [saving, setSaving] = react.useState(!1);
2197
+ function useSaveFailureToast() {
2198
+ const toast = ui.useToast();
2199
+ return err => {
2200
+ console.error("[workflow-studio-plugin] field save failed:", err), toast.push({
2201
+ status: "error",
2202
+ title: "Saving failed",
2203
+ description: describeError(err)
2204
+ });
2205
+ };
2206
+ }
2207
+
2208
+ function useSaveField() {
2209
+ const reportFailure = useSaveFailureToast(), [saving, setSaving] = react.useState(!1);
2169
2210
  return {
2170
2211
  saving: saving,
2171
2212
  save: async commit => {
2172
2213
  if (!saving) {
2173
2214
  setSaving(!0);
2174
2215
  try {
2175
- await commit(), args.onSaved?.();
2216
+ await commit();
2176
2217
  } catch (err) {
2177
- console.error("[workflow-studio-plugin] field save failed:", err), toast.push({
2178
- status: "error",
2179
- title: "Saving failed",
2180
- description: describeError(err)
2181
- });
2218
+ reportFailure(err);
2182
2219
  } finally {
2183
2220
  setSaving(!1);
2184
2221
  }
@@ -2188,7 +2225,7 @@ function useSaveField(args) {
2188
2225
  }
2189
2226
 
2190
2227
  function ActorField({field: field, label: label, hideLabel: hideLabel, onSave: onSave, onUnset: onUnset}) {
2191
- const [open, setOpen] = react.useState(!1), {saving: saving, save: save} = useSaveField({}), cur = field.value, pick = member => {
2228
+ const [open, setOpen] = react.useState(!1), {saving: saving, save: save} = useSaveField(), cur = field.value, pick = member => {
2192
2229
  setOpen(!1), !saving && (member && member.id !== cur?.id ? save(() => onSave(personActor(member))) : onUnset !== void 0 && cur && save(onUnset));
2193
2230
  }, value = cur ? /* @__PURE__ */ jsxRuntime.jsx(ActorName, {
2194
2231
  id: cur.id
@@ -2271,10 +2308,14 @@ function ArrayField({field: field, label: label, hideLabel: hideLabel}) {
2271
2308
  }
2272
2309
 
2273
2310
  function NotesField({field: field, label: label, hideLabel: hideLabel, onAppend: onAppend}) {
2274
- const [body, setBody] = react.useState(""), {saving: saving, save: save} = useSaveField({
2275
- onSaved: () => setBody("")
2276
- }), rows = arrayRowsOf(field.value), trimmed = body.trim(), add = () => {
2277
- trimmed !== "" && save(() => onAppend(trimmed));
2311
+ const [body, setBody] = react.useState(""), {saving: saving, save: save} = useSaveField(), rows = arrayRowsOf(field.value), trimmed = body.trim(), add = () => {
2312
+ saving || trimmed === "" || (setBody(""), save(async () => {
2313
+ try {
2314
+ await onAppend(trimmed);
2315
+ } catch (err) {
2316
+ throw setBody(trimmed), err;
2317
+ }
2318
+ }));
2278
2319
  };
2279
2320
  /* @__PURE__ */
2280
2321
  return jsxRuntime.jsxs(ui.Stack, {
@@ -2293,7 +2334,6 @@ function NotesField({field: field, label: label, hideLabel: hideLabel, onAppend:
2293
2334
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
2294
2335
  flex: 1,
2295
2336
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.TextArea, {
2296
- disabled: saving,
2297
2337
  fontSize: 1,
2298
2338
  onChange: e => setBody(e.currentTarget.value),
2299
2339
  placeholder: "Add an entry…",
@@ -2302,9 +2342,8 @@ function NotesField({field: field, label: label, hideLabel: hideLabel, onAppend:
2302
2342
  })
2303
2343
  }),
2304
2344
  /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
2305
- disabled: saving || trimmed === "",
2345
+ disabled: trimmed === "",
2306
2346
  fontSize: 1,
2307
- loading: saving,
2308
2347
  onClick: add,
2309
2348
  padding: 2,
2310
2349
  text: "Add",
@@ -2315,7 +2354,7 @@ function NotesField({field: field, label: label, hideLabel: hideLabel, onAppend:
2315
2354
  }
2316
2355
 
2317
2356
  function BooleanSwitchField({field: field, label: label, hideLabel: hideLabel, onSave: onSave}) {
2318
- const {saving: saving, save: save} = useSaveField({}), [optimistic, setOptimistic] = react.useState(null), committed = field.value === !0;
2357
+ const {save: save} = useSaveField(), committed = field.value === !0;
2319
2358
  /* @__PURE__ */
2320
2359
  return jsxRuntime.jsxs(ui.Flex, {
2321
2360
  align: "center",
@@ -2327,68 +2366,56 @@ function BooleanSwitchField({field: field, label: label, hideLabel: hideLabel, o
2327
2366
  label: label
2328
2367
  }),
2329
2368
  /* @__PURE__ */ jsxRuntime.jsx(ui.Switch, {
2330
- checked: optimistic ?? committed,
2331
- disabled: saving,
2369
+ checked: committed,
2332
2370
  onChange: () => {
2333
- const next = !committed;
2334
- setOptimistic(next), save(() => onSave(next)).finally(() => setOptimistic(null));
2371
+ save(() => onSave(!committed));
2335
2372
  }
2336
2373
  }) ]
2337
2374
  });
2338
2375
  }
2339
2376
 
2340
- function GenericField({field: field, label: label, hideLabel: hideLabel, onSave: onSave}) {
2341
- const [editing, setEditing] = react.useState(!1), [draft, setDraft] = react.useState(field.value), {saving: saving, save: save} = useSaveField({
2342
- onSaved: () => setEditing(!1)
2343
- });
2377
+ function sameValue(a, b) {
2378
+ return (a ?? void 0) === (b ?? void 0);
2379
+ }
2380
+
2381
+ function GenericField({field: field, label: label, hideLabel: hideLabel, onSave: onSave, onPreview: onPreview, onDiscardPreview: onDiscardPreview}) {
2382
+ const reportFailure = useSaveFailureToast(), [draft, setDraft] = react.useState(field.value), [baseline, setBaseline] = react.useState(field.value), dirty = !sameValue(draft, baseline), focused = react.useRef(!1), previewed = react.useRef(!1), commit = () => {
2383
+ if (!dirty) return;
2384
+ const value = draft, prev = baseline;
2385
+ setBaseline(value), previewed.current = !1, onSave(value).catch(err => {
2386
+ reportFailure(err), setBaseline(current => sameValue(current, value) ? prev : current),
2387
+ setDraft(current => sameValue(current, value) ? prev : current);
2388
+ });
2389
+ }, settle = () => {
2390
+ dirty ? commit() : previewed.current && (previewed.current = !1, onDiscardPreview?.());
2391
+ }, settleRef = react.useRef(settle);
2392
+ settleRef.current = settle, react.useEffect(() => () => settleRef.current(), []);
2393
+ const adopted = react.useRef(field.value);
2344
2394
  return react.useEffect(() => {
2345
- setDraft(field.value);
2346
- }, [ field.value ]), editing ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
2395
+ sameValue(adopted.current, field.value) || focused.current || dirty || (adopted.current = field.value,
2396
+ setBaseline(field.value), setDraft(field.value));
2397
+ }, [ field.value, dirty ]), field.editable ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
2347
2398
  gap: 2,
2348
2399
  children: [
2349
2400
  /* @__PURE__ */ jsxRuntime.jsx(FieldLabel, {
2350
2401
  hide: hideLabel,
2351
2402
  label: label
2352
2403
  }),
2353
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
2354
- align: "center",
2355
- gap: 2,
2356
- children: [
2357
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
2358
- flex: 1,
2359
- children: /* @__PURE__ */ jsxRuntime.jsx(FieldInput$1, {
2360
- field: field,
2361
- onChange: setDraft,
2362
- onEnter: () => save(() => onSave(draft)),
2363
- value: draft
2364
- })
2365
- }),
2366
- /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
2367
- text: "Save",
2368
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
2369
- "aria-label": "Save",
2370
- fontSize: 1,
2371
- icon: Checkmark.CheckmarkIcon,
2372
- loading: saving,
2373
- onClick: () => save(() => onSave(draft)),
2374
- padding: 2,
2375
- tone: "primary"
2376
- })
2377
- }),
2378
- /* @__PURE__ */ jsxRuntime.jsx(HoverHint, {
2379
- text: "Cancel",
2380
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
2381
- "aria-label": "Cancel",
2382
- disabled: saving,
2383
- fontSize: 1,
2384
- icon: Close.CloseIcon,
2385
- mode: "bleed",
2386
- onClick: () => {
2387
- setDraft(field.value), setEditing(!1);
2388
- },
2389
- padding: 2
2390
- })
2391
- }) ]
2404
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
2405
+ onBlur: () => {
2406
+ focused.current = !1, settle();
2407
+ },
2408
+ onFocus: () => {
2409
+ focused.current = !0;
2410
+ },
2411
+ children: /* @__PURE__ */ jsxRuntime.jsx(FieldInput$1, {
2412
+ field: field,
2413
+ onChange: value => {
2414
+ setDraft(value), previewed.current = !0, onPreview?.(value);
2415
+ },
2416
+ onEnter: commit,
2417
+ value: draft
2418
+ })
2392
2419
  }) ]
2393
2420
  }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
2394
2421
  align: "center",
@@ -2398,14 +2425,7 @@ function GenericField({field: field, label: label, hideLabel: hideLabel, onSave:
2398
2425
  /* @__PURE__ */ jsxRuntime.jsx(FieldLabel, {
2399
2426
  hide: hideLabel,
2400
2427
  label: label
2401
- }), field.editable ? /* @__PURE__ */ jsxRuntime.jsx(EditableValueButton, {
2402
- hint: "Edit",
2403
- onClick: () => setEditing(!0),
2404
- children: /* @__PURE__ */ jsxRuntime.jsx(ValueDisplay, {
2405
- field: field,
2406
- value: field.value
2407
- })
2408
- }) :
2428
+ }),
2409
2429
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
2410
2430
  align: "center",
2411
2431
  gap: 2,
@@ -2422,13 +2442,33 @@ function GenericField({field: field, label: label, hideLabel: hideLabel, onSave:
2422
2442
  });
2423
2443
  }
2424
2444
 
2425
- function EditableField({field: field, entry: entry, onSave: onSave, onUnset: onUnset, onAppend: onAppend, hideLabel: hideLabel = !1}) {
2426
- const label = field.title ?? field.name, arm = {
2427
- field: field,
2428
- label: label,
2429
- hideLabel: hideLabel,
2430
- onSave: onSave
2431
- };
2445
+ function HotFieldHint() {
2446
+ /* @__PURE__ */
2447
+ return jsxRuntime.jsx(HoverHint, {
2448
+ text: "Editing this can move the workflow",
2449
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
2450
+ "aria-label": "Editing this can move the workflow",
2451
+ muted: !0,
2452
+ size: 1,
2453
+ style: {
2454
+ cursor: "help"
2455
+ },
2456
+ children: /* @__PURE__ */ jsxRuntime.jsx(Bolt.BoltIcon, {})
2457
+ })
2458
+ });
2459
+ }
2460
+
2461
+ function AdvanceHint({stageTitle: stageTitle2}) {
2462
+ /* @__PURE__ */
2463
+ return jsxRuntime.jsxs(ui.Text, {
2464
+ muted: !0,
2465
+ size: 1,
2466
+ children: [ "Committing will advance to ", stageTitle2 ]
2467
+ });
2468
+ }
2469
+
2470
+ function fieldArm({arm: arm, entry: entry, onUnset: onUnset, onAppend: onAppend, onPreview: onPreview, onDiscardPreview: onDiscardPreview}) {
2471
+ const {field: field} = arm;
2432
2472
  return field.type === "actor" ? /* @__PURE__ */ jsxRuntime.jsx(ActorField, {
2433
2473
  ...arm,
2434
2474
  ...onUnset === void 0 ? {} : {
@@ -2444,7 +2484,45 @@ function EditableField({field: field, entry: entry, onSave: onSave, onUnset: onU
2444
2484
  }) : field.type === "boolean" && field.editable ? /* @__PURE__ */ jsxRuntime.jsx(BooleanSwitchField, {
2445
2485
  ...arm
2446
2486
  }) : /* @__PURE__ */ jsxRuntime.jsx(GenericField, {
2447
- ...arm
2487
+ ...arm,
2488
+ ...onPreview === void 0 ? {} : {
2489
+ onPreview: onPreview
2490
+ },
2491
+ ...onDiscardPreview === void 0 ? {} : {
2492
+ onDiscardPreview: onDiscardPreview
2493
+ }
2494
+ });
2495
+ }
2496
+
2497
+ function EditableField({field: field, entry: entry, onSave: onSave, onUnset: onUnset, onAppend: onAppend, onPreview: onPreview, onDiscardPreview: onDiscardPreview, hot: hot = !1, advanceTo: advanceTo, hideLabel: hideLabel = !1}) {
2498
+ const label = field.title ?? field.name, content2 = fieldArm({
2499
+ arm: {
2500
+ field: field,
2501
+ label: label,
2502
+ hideLabel: hideLabel,
2503
+ onSave: onSave
2504
+ },
2505
+ entry: entry,
2506
+ onUnset: onUnset,
2507
+ onAppend: onAppend,
2508
+ onPreview: onPreview,
2509
+ onDiscardPreview: onDiscardPreview
2510
+ }), decorated = field.editable;
2511
+ /* @__PURE__ */
2512
+ return jsxRuntime.jsxs(ui.Stack, {
2513
+ gap: 2,
2514
+ children: [
2515
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
2516
+ align: "center",
2517
+ gap: 2,
2518
+ children: [
2519
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
2520
+ flex: 1,
2521
+ children: content2
2522
+ }), decorated && hot ? /* @__PURE__ */ jsxRuntime.jsx(HotFieldHint, {}) : null ]
2523
+ }), decorated && advanceTo !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(AdvanceHint, {
2524
+ stageTitle: advanceTo
2525
+ }) : null ]
2448
2526
  });
2449
2527
  }
2450
2528
 
@@ -3212,7 +3290,7 @@ function ActivityDetailDialog({entry: entry, activityName: activityName, breadcr
3212
3290
  }
3213
3291
 
3214
3292
  function DetailRows({detail: detail, entry: entry, instanceId: instanceId, activityName: activityName}) {
3215
- const {activityEval: activityEval, presentation: presentation, assignField: assignField} = detail;
3293
+ const {activityEval: activityEval, presentation: presentation, assignFields: assignFields} = detail;
3216
3294
  /* @__PURE__ */
3217
3295
  return jsxRuntime.jsxs(jsxRuntime.Fragment, {
3218
3296
  children: [
@@ -3230,21 +3308,25 @@ function DetailRows({detail: detail, entry: entry, instanceId: instanceId, activ
3230
3308
  children: ACTIVITY_STATUS_LABEL[activityEval.status]
3231
3309
  }) ]
3232
3310
  })
3233
- }), detail.assigneeEntries.map(entry_ => /* @__PURE__ */ jsxRuntime.jsx(LabeledRow, {
3234
- label: entry_.title ?? "Assignees",
3235
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
3236
- align: "center",
3237
- gap: 2,
3238
- wrap: "wrap",
3239
- children: [
3240
- /* @__PURE__ */ jsxRuntime.jsx(FieldValue, {
3241
- field: entry_
3242
- }), assignField?.name === entry_.name ? /* @__PURE__ */ jsxRuntime.jsx(AssignActivityControl, {
3243
- field: assignField,
3244
- instanceId: instanceId
3245
- }) : null ]
3246
- })
3247
- }, entry_._key)), presentation.dates.map(d => /* @__PURE__ */ jsxRuntime.jsx(LabeledRow, {
3311
+ }), detail.assigneeEntries.map(entry_ => {
3312
+ const assignField = assignFields.get(entry_.name);
3313
+ /* @__PURE__ */
3314
+ return jsxRuntime.jsx(LabeledRow, {
3315
+ label: entry_.title ?? "Assignees",
3316
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
3317
+ align: "center",
3318
+ gap: 2,
3319
+ wrap: "wrap",
3320
+ children: [
3321
+ /* @__PURE__ */ jsxRuntime.jsx(FieldValue, {
3322
+ field: entry_
3323
+ }), assignField ? /* @__PURE__ */ jsxRuntime.jsx(AssignActivityControl, {
3324
+ field: assignField,
3325
+ instanceId: instanceId
3326
+ }) : null ]
3327
+ })
3328
+ }, entry_._key);
3329
+ }), presentation.dates.map(d => /* @__PURE__ */ jsxRuntime.jsx(LabeledRow, {
3248
3330
  label: d.title ?? d.name,
3249
3331
  children: /* @__PURE__ */ jsxRuntime.jsx(BareValue, {
3250
3332
  children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
@@ -3407,10 +3489,6 @@ function useInstanceTitle(entry) {
3407
3489
  return instanceTitle(entry.instance, definition);
3408
3490
  }
3409
3491
 
3410
- function stageTitle(instance, definition) {
3411
- return definition?.stages.find(s => s.name === instance.currentStage)?.title ?? instance.currentStage;
3412
- }
3413
-
3414
3492
  function InstanceStatusBadge({instance: instance, definition: definition}) {
3415
3493
  const state = workflowEngine.terminalState(instance);
3416
3494
  return state === "aborted" ? /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
@@ -3424,7 +3502,7 @@ function InstanceStatusBadge({instance: instance, definition: definition}) {
3424
3502
  }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
3425
3503
  fontSize: 1,
3426
3504
  tone: "primary",
3427
- children: stageTitle(instance, definition)
3505
+ children: stageTitle(definition, instance.currentStage)
3428
3506
  });
3429
3507
  }
3430
3508
 
@@ -3464,7 +3542,6 @@ function StageGlyph(props) {
3464
3542
  }
3465
3543
 
3466
3544
  function CurrentStageIndicator({instance: instance, definition: definition}) {
3467
- const stage = definition?.stages.find(s => s.name === instance.currentStage);
3468
3545
  /* @__PURE__ */
3469
3546
  return jsxRuntime.jsxs(ui.Flex, {
3470
3547
  align: "center",
@@ -3477,7 +3554,7 @@ function CurrentStageIndicator({instance: instance, definition: definition}) {
3477
3554
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
3478
3555
  size: 1,
3479
3556
  weight: "medium",
3480
- children: stage?.title ?? instance.currentStage
3557
+ children: stageTitle(definition, instance.currentStage)
3481
3558
  }) ]
3482
3559
  });
3483
3560
  }
@@ -3819,11 +3896,11 @@ function ActivityTab({instance: instance, definition: definition}) {
3819
3896
  }
3820
3897
 
3821
3898
  function makeTitleResolver(definition) {
3822
- const stages = definition?.stages ?? [], activities = stages.flatMap(s => s.activities ?? []);
3899
+ const activities = (definition?.stages ?? []).flatMap(s => s.activities ?? []);
3823
3900
  return {
3824
3901
  action: (activityName, actionName) => activities.find(t => t.name === activityName)?.actions?.find(a => a.name === actionName)?.title ?? actionName,
3825
3902
  activity: name => activities.find(t => t.name === name)?.title ?? name,
3826
- stage: name => stages.find(s => s.name === name)?.title ?? name
3903
+ stage: name => stageTitle(definition, name)
3827
3904
  };
3828
3905
  }
3829
3906
 
@@ -3903,8 +3980,7 @@ function InstanceMenuItem({entry: entry, selected: selected, onSelect: onSelect}
3903
3980
 
3904
3981
  function instanceNotice(entry) {
3905
3982
  const {instance: instance, committed: committed, evaluation: evaluation} = entry, state = workflowEngine.terminalState(instance);
3906
- if (state !== "in-flight") /* @__PURE__ */
3907
- return jsxRuntime.jsx(ui.Card, {
3983
+ return state !== "in-flight" ? /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
3908
3984
  border: !0,
3909
3985
  padding: 3,
3910
3986
  radius: 2,
@@ -3913,24 +3989,27 @@ function instanceNotice(entry) {
3913
3989
  size: 1,
3914
3990
  children: state === "aborted" ? "This workflow was aborted — no open tasks." : "This workflow is complete — no open tasks."
3915
3991
  })
3916
- });
3917
- const freshness = evaluationFreshness({
3992
+ }) : evaluationFreshness({
3918
3993
  committed: committed,
3919
3994
  evaluation: evaluation
3920
- });
3921
- return freshness === "pending" ? /* @__PURE__ */ jsxRuntime.jsx(LoadingRow, {
3995
+ }) === "pending" ? /* @__PURE__ */ jsxRuntime.jsx(LoadingRow, {
3922
3996
  label: "Evaluating…",
3923
3997
  padding: 4
3924
- }) : freshness === "stale" ? /* @__PURE__ */ jsxRuntime.jsx(LoadingRow, {
3925
- label: "Updating…",
3926
- padding: 4
3927
3998
  }) : null;
3928
3999
  }
3929
4000
 
4001
+ function evaluationSyncing(entry) {
4002
+ const {instance: instance, committed: committed, evaluation: evaluation} = entry;
4003
+ return workflowEngine.terminalState(instance) === "in-flight" && evaluationFreshness({
4004
+ committed: committed,
4005
+ evaluation: evaluation
4006
+ }) === "stale";
4007
+ }
4008
+
3930
4009
  const NO_EDITABLES = /* @__PURE__ */ new Map;
3931
4010
 
3932
4011
  function InstanceStateTab({entry: entry}) {
3933
- 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);
4012
+ 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);
3934
4013
  /* @__PURE__ */
3935
4014
  return jsxRuntime.jsxs(ui.Stack, {
3936
4015
  gap: 4,
@@ -3948,7 +4027,7 @@ function InstanceStateTab({entry: entry}) {
3948
4027
  return jsxRuntime.jsx(ScopeGroup, {
3949
4028
  editables: current ? editablesByName("stage") : NO_EDITABLES,
3950
4029
  fields: st.fields ?? [],
3951
- heading: `Stage · ${stageTitleOf(st.name)}${suffix}`,
4030
+ heading: `Stage · ${stageTitle(definition, st.name)}${suffix}`,
3952
4031
  instanceId: instance._id
3953
4032
  }, `s-${i}`);
3954
4033
  }) ]
@@ -4152,7 +4231,7 @@ function StageIndicator({entry: entry, definition: definition}) {
4152
4231
  }
4153
4232
 
4154
4233
  function InstancePanel({entry: entry, entries: entries, onSelect: onSelect}) {
4155
- const definition = useDefinition(entry), {instance: instance, ready: ready} = entry, identity = useAssignmentIdentity(), [assignedToMe, setAssignedToMe] = react.useState(!1), [openActivity, setOpenActivity] = react.useState(null), [segment, setSegment] = react.useState("state"), stageTitle2 = definition?.stages.find(s => s.name === instance.currentStage)?.title ?? instance.currentStage, notice = instanceNotice(entry);
4234
+ const definition = useDefinition(entry), {instance: instance, ready: ready} = entry, identity = useAssignmentIdentity(), [assignedToMe, setAssignedToMe] = react.useState(!1), [openActivity, setOpenActivity] = react.useState(null), [segment, setSegment] = react.useState("state"), currentStageTitle = stageTitle(definition, instance.currentStage), notice = instanceNotice(entry), syncing = evaluationSyncing(entry);
4156
4235
  /* @__PURE__ */
4157
4236
  return jsxRuntime.jsxs(ui.Flex, {
4158
4237
  direction: "column",
@@ -4179,8 +4258,18 @@ function InstancePanel({entry: entry, entries: entries, onSelect: onSelect}) {
4179
4258
  }),
4180
4259
  /* @__PURE__ */ jsxRuntime.jsx(PendingWorkBadge, {
4181
4260
  instance: instance
4182
- }), ready ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
4183
- muted: !0
4261
+ }),
4262
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
4263
+ align: "center",
4264
+ flex: "none",
4265
+ justify: "center",
4266
+ style: {
4267
+ width: 19,
4268
+ height: 19
4269
+ },
4270
+ children: !ready || syncing ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
4271
+ muted: !0
4272
+ }) : null
4184
4273
  }),
4185
4274
  /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
4186
4275
  flex: 1
@@ -4240,20 +4329,29 @@ function InstancePanel({entry: entry, entries: entries, onSelect: onSelect}) {
4240
4329
  size: 1,
4241
4330
  children: "Couldn’t load this workflow’s definition — stage and action titles fall back to raw names."
4242
4331
  })
4243
- }), notice ?? /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
4244
- children: [
4245
- /* @__PURE__ */ jsxRuntime.jsx(ActivitiesList, {
4246
- assignedToMeOnly: assignedToMe,
4247
- entry: entry,
4248
- identity: identity,
4249
- onOpenActivity: setOpenActivity,
4250
- stageTitle: stageTitle2
4251
- }),
4252
- /* @__PURE__ */ jsxRuntime.jsx(ActionItemsList, {
4253
- assignedToMeOnly: assignedToMe,
4254
- entry: entry,
4255
- identity: identity
4256
- }) ]
4332
+ }), notice ??
4333
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
4334
+ style: {
4335
+ opacity: syncing ? .5 : 1,
4336
+ pointerEvents: syncing ? "none" : void 0,
4337
+ transition: "opacity 150ms ease-in"
4338
+ },
4339
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
4340
+ "aria-busy": syncing,
4341
+ children: [
4342
+ /* @__PURE__ */ jsxRuntime.jsx(ActivitiesList, {
4343
+ assignedToMeOnly: assignedToMe,
4344
+ entry: entry,
4345
+ identity: identity,
4346
+ onOpenActivity: setOpenActivity,
4347
+ stageTitle: currentStageTitle
4348
+ }),
4349
+ /* @__PURE__ */ jsxRuntime.jsx(ActionItemsList, {
4350
+ assignedToMeOnly: assignedToMe,
4351
+ entry: entry,
4352
+ identity: identity
4353
+ }) ]
4354
+ })
4257
4355
  }),
4258
4356
  /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
4259
4357
  border: !0,
@@ -4296,7 +4394,7 @@ function InstancePanel({entry: entry, entries: entries, onSelect: onSelect}) {
4296
4394
  })
4297
4395
  }), openActivity ? /* @__PURE__ */ jsxRuntime.jsx(ActivityDetailDialog, {
4298
4396
  activityName: openActivity,
4299
- breadcrumb: [ instanceTitle(instance, definition), stageTitle2 ],
4397
+ breadcrumb: [ instanceTitle(instance, definition), currentStageTitle ],
4300
4398
  entry: entry,
4301
4399
  onClose: () => setOpenActivity(null)
4302
4400
  }) : null ]
@@ -4357,7 +4455,7 @@ function useRefcountedIds() {
4357
4455
  };
4358
4456
  }
4359
4457
 
4360
- const REPORT_KEYS = [ "evaluation", "ready", "guards", "fireAction", "editField" ];
4458
+ const REPORT_KEYS = [ "evaluation", "ready", "guards", "fireAction", "editField", "previewField", "discardFieldPreview" ];
4361
4459
 
4362
4460
  function sameReport(a, b) {
4363
4461
  return REPORT_KEYS.every(key => a[key] === b[key]);
@@ -4401,7 +4499,7 @@ class InstanceEvaluatorBoundary extends react.Component {
4401
4499
  }
4402
4500
 
4403
4501
  function InstanceEvaluator(props) {
4404
- const {engine: engine, instanceId: instanceId, grantsFromPath: grantsFromPath, onReport: onReport} = props, {evaluation: evaluation, ready: ready, guards: guards, fireAction: fireAction, editField: editField} = workflowStudio.useWorkflowSession({
4502
+ const {engine: engine, instanceId: instanceId, grantsFromPath: grantsFromPath, onReport: onReport} = props, {evaluation: evaluation, ready: ready, guards: guards, fireAction: fireAction, editField: editField, previewField: previewField, discardFieldPreview: discardFieldPreview} = workflowStudio.useWorkflowSession({
4405
4503
  engine: engine,
4406
4504
  instanceId: instanceId,
4407
4505
  grantsFromPath: grantsFromPath
@@ -4412,9 +4510,11 @@ function InstanceEvaluator(props) {
4412
4510
  ready: ready,
4413
4511
  guards: guards,
4414
4512
  fireAction: fireAction,
4415
- editField: editField
4513
+ editField: editField,
4514
+ previewField: previewField,
4515
+ discardFieldPreview: discardFieldPreview
4416
4516
  });
4417
- }, [ instanceId, evaluation, ready, guards, fireAction, editField, onReport ]),
4517
+ }, [ instanceId, evaluation, ready, guards, fireAction, editField, previewField, discardFieldPreview, onReport ]),
4418
4518
  react.useEffect(() => () => onReport(instanceId, void 0), [ instanceId, onReport ]),
4419
4519
  null;
4420
4520
  }
@@ -4478,7 +4578,10 @@ function WorkflowProvider(props) {
4478
4578
  ...config.effectHandlers ? {
4479
4579
  effectHandlers: config.effectHandlers
4480
4580
  } : {}
4481
- }), grantsFromPath = `/projects/${projectId}/datasets/${workflowDataset}/acl`, {ids: registered, acquire: register, release: unregister} = useRefcountedIds(), {ids: requested, acquire: requestInstance, release: releaseInstance} = useRefcountedIds(), [seeded, setSeeded] = react.useState(/* @__PURE__ */ new Map), seedInstance = react.useCallback(instance => {
4581
+ }), grantsFromPath = workflowEngine.aclPathForResource({
4582
+ type: "dataset",
4583
+ id: `${projectId}.${workflowDataset}`
4584
+ }), {ids: registered, acquire: register, release: unregister} = useRefcountedIds(), {ids: requested, acquire: requestInstance, release: releaseInstance} = useRefcountedIds(), [seeded, setSeeded] = react.useState(/* @__PURE__ */ new Map), seedInstance = react.useCallback(instance => {
4482
4585
  setSeeded(prev => new Map(prev).set(instance._id, instance));
4483
4586
  }, []), [startRequest, setStartRequest] = react.useState(void 0), openStartDialog = react.useCallback(r => setStartRequest(r), []), closeStartDialog = react.useCallback(() => setStartRequest(void 0), []), [reports, setReports] = react.useState(/* @__PURE__ */ new Map), onReport = react.useCallback((instanceId, report) => {
4484
4587
  setReports(prev => nextReports(prev, {
@@ -4508,7 +4611,11 @@ function WorkflowProvider(props) {
4508
4611
  const report = reportsRef.current.get(instanceId);
4509
4612
  return report ? report.editField(args).then(async res => (await drainCommitted(instanceId),
4510
4613
  res)) : notReady(args);
4511
- }, [ drainCommitted ]), drainEffectsFor = react.useCallback(async instanceId => {
4614
+ }, [ drainCommitted ]), previewFieldFor = react.useCallback((instanceId, args) => {
4615
+ reportsRef.current.get(instanceId)?.previewField(args);
4616
+ }, []), discardFieldPreviewFor = react.useCallback((instanceId, target) => {
4617
+ reportsRef.current.get(instanceId)?.discardFieldPreview(target);
4618
+ }, []), drainEffectsFor = react.useCallback(async instanceId => {
4512
4619
  await engine.drainEffects({
4513
4620
  instanceId: instanceId
4514
4621
  });
@@ -4578,6 +4685,8 @@ function WorkflowProvider(props) {
4578
4685
  loading: loading,
4579
4686
  fireActionFor: fireActionFor,
4580
4687
  editFieldFor: editFieldFor,
4688
+ previewFieldFor: previewFieldFor,
4689
+ discardFieldPreviewFor: discardFieldPreviewFor,
4581
4690
  drainEffectsFor: drainEffectsFor,
4582
4691
  completeEffectFor: completeEffectFor,
4583
4692
  engine: engine,
@@ -4585,7 +4694,7 @@ function WorkflowProvider(props) {
4585
4694
  mappings: config.mappings,
4586
4695
  mappingIssues: issues,
4587
4696
  effectHandlers: engine.effectHandlers
4588
- }), [ register, unregister, isDocResolved, byDocument, byInstance, requestInstance, releaseInstance, seedInstance, openStartDialog, closeStartDialog, startRequest, loading, fireActionFor, editFieldFor, drainEffectsFor, completeEffectFor, engine, binding, config.mappings, issues ]);
4697
+ }), [ register, unregister, isDocResolved, byDocument, byInstance, requestInstance, releaseInstance, seedInstance, openStartDialog, closeStartDialog, startRequest, loading, fireActionFor, editFieldFor, previewFieldFor, discardFieldPreviewFor, drainEffectsFor, completeEffectFor, engine, binding, config.mappings, issues ]);
4589
4698
  /* @__PURE__ */
4590
4699
  return jsxRuntime.jsxs(WorkflowContext.Provider, {
4591
4700
  value: value,