statelyai 0.12.2 → 0.12.4

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.
@@ -6877,13 +6877,13 @@ function getDefaultPathId(graph, node) {
6877
6877
  if (!node.parentId) return getRootEmittedId(graph, node) ?? "(machine)";
6878
6878
  const parentNode = graph.nodes.find((candidate) => candidate.id === node.parentId);
6879
6879
  if (!parentNode) return node.data.key;
6880
- return `${getResolvedNodeId(graph, parentNode)}.${node.data.key}`;
6880
+ return `${getDefaultPathId(graph, parentNode)}.${node.data.key}`;
6881
6881
  }
6882
6882
  function getEmittedNodeId(graph, node) {
6883
6883
  const authored = getAuthoredNodeId(node);
6884
6884
  if (!node.parentId) return getRootEmittedId(graph, node);
6885
6885
  if (!authored) return void 0;
6886
- return authored === getDefaultPathId(graph, node) ? void 0 : authored;
6886
+ return authored;
6887
6887
  }
6888
6888
  function getResolvedNodeId(graph, node) {
6889
6889
  return getEmittedNodeId(graph, node) ?? getDefaultPathId(graph, node);
@@ -6908,14 +6908,15 @@ function containsCodeExpression(value, seen = /* @__PURE__ */ new WeakSet()) {
6908
6908
  */
6909
6909
  const VALID_IDENT = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
6910
6910
  var RawCode = class {
6911
- constructor(code, sourceValue, sourceSlice) {
6911
+ constructor(code, sourceValue, sourceSlice, preserveWhitespace = false) {
6912
6912
  this.code = code;
6913
6913
  this.sourceValue = sourceValue;
6914
6914
  this.sourceSlice = sourceSlice;
6915
+ this.preserveWhitespace = preserveWhitespace;
6915
6916
  }
6916
6917
  };
6917
- function raw(code, sourceValue, sourceSlice) {
6918
- return new RawCode(code, sourceValue, sourceSlice);
6918
+ function raw(code, sourceValue, sourceSlice, preserveWhitespace = false) {
6919
+ return new RawCode(code, sourceValue, sourceSlice, preserveWhitespace);
6919
6920
  }
6920
6921
  const SERIALIZED_SOURCE_ID = "__stately_source_id__";
6921
6922
  function serializeJS(value, indent = 0, step = 2) {
@@ -6963,6 +6964,7 @@ function serializeObject(obj, indent, step) {
6963
6964
  }
6964
6965
  function preservesAuthoredWhitespace(value) {
6965
6966
  if (isCodeExpression(value)) return true;
6967
+ if (value instanceof RawCode && value.preserveWhitespace) return true;
6966
6968
  const sourceValue = value instanceof RawCode ? value.sourceValue : value;
6967
6969
  return Boolean(sourceValue && typeof sourceValue === "object" && SERIALIZED_SOURCE_ID in sourceValue);
6968
6970
  }
@@ -7057,7 +7059,7 @@ function resolveTransitionTarget(graph, sourceNode, targetNode) {
7057
7059
  const keys = [];
7058
7060
  let current = targetNode;
7059
7061
  while (current) {
7060
- keys.unshift(current.data.key);
7062
+ keys.unshift(current.data.key.replace(/\\/g, "\\\\").replace(/\./g, "\\."));
7061
7063
  if (current.parentId === baseId) {
7062
7064
  if (isRootSource) return `.${keys.join(".")}`;
7063
7065
  return keys.join(".");
@@ -7142,13 +7144,13 @@ function deepSimplify(config) {
7142
7144
  if (config.states && typeof config.states === "object") for (const stateConfig of Object.values(config.states)) deepSimplify(stateConfig);
7143
7145
  return config;
7144
7146
  }
7145
- function buildMeta(meta, color) {
7147
+ function buildMeta(meta, color, showStatelyMeta = true) {
7146
7148
  if (isCodeExpression(meta)) return meta;
7147
7149
  const result = {};
7148
7150
  if (meta) {
7149
- for (const [k, v] of Object.entries(meta)) if (v !== void 0 && v !== null) result[k] = v;
7151
+ for (const [k, v] of Object.entries(meta)) if (v !== void 0 && v !== null && (showStatelyMeta || !k.startsWith("@statelyai"))) result[k] = v;
7150
7152
  }
7151
- if (color) result["@statelyai.color"] = color;
7153
+ if (color && showStatelyMeta) result["@statelyai.color"] = color;
7152
7154
  return Object.keys(result).length > 0 ? result : void 0;
7153
7155
  }
7154
7156
  /** Format event param — supports { type: string } (new) and plain string (legacy) */
@@ -7444,8 +7446,11 @@ function tagToString(tag) {
7444
7446
  }
7445
7447
  function graphToMachineConfig(graph, options = {}) {
7446
7448
  const v6 = isXStateV6Profile(graph.data.profile);
7447
- const { showDescriptions = true, showMeta = true, inlineGuardsAsTypeRef = false, keepChooseStructured = false } = options;
7448
- const serializeAction = (action) => serializeActionItem(action, keepChooseStructured);
7449
+ const { showDescriptions = true, showMeta = true, showStatelyMeta = true, inlineGuardsAsTypeRef = false, keepChooseStructured = false, timerActionsAsTypeRef = false } = options;
7450
+ const serializeAction = (action) => timerActionsAsTypeRef && ["xstate.raise", "xstate.cancel"].includes(action.type) ? {
7451
+ type: action.type,
7452
+ params: action.params
7453
+ } : serializeActionItem(action, keepChooseStructured);
7449
7454
  const config = {};
7450
7455
  function getNodeConfig(node) {
7451
7456
  const nodeConfig = node.parentId ? {} : config;
@@ -7458,7 +7463,15 @@ function graphToMachineConfig(graph, options = {}) {
7458
7463
  "choice"
7459
7464
  ].includes(node.data.type) ? node.data.type : void 0;
7460
7465
  const tags = node.data.tags;
7461
- const stateMeta = showMeta ? buildMeta(node.data.meta, node.data.color) : void 0;
7466
+ const stateMeta = showMeta ? buildMeta({
7467
+ ...node.data.meta,
7468
+ ...node.data.assets?.length ? { "@statelyai.assets": node.data.assets.map(({ type, url, size }) => ({
7469
+ type,
7470
+ url,
7471
+ ...size ? { size } : {}
7472
+ })) } : {},
7473
+ ...node.data.appearance ? { "@statelyai.appearance": node.data.appearance } : {}
7474
+ }, node.data.color, showStatelyMeta) : void 0;
7462
7475
  Object.assign(nodeConfig, {
7463
7476
  id: resolvedNodeId,
7464
7477
  type: nodeType === "normal" ? void 0 : nodeType ?? void 0,
@@ -7490,6 +7503,8 @@ function graphToMachineConfig(graph, options = {}) {
7490
7503
  const edges = graph.edges.filter((edge) => edge.sourceId === node.id && !edge.data.temp);
7491
7504
  if (edges.length > 0) {
7492
7505
  const on = {};
7506
+ const transitionOrder = /* @__PURE__ */ new Map();
7507
+ const scxmlTransitions = [];
7493
7508
  const after = {};
7494
7509
  const alwaysArr = [];
7495
7510
  const choiceArr = [];
@@ -7505,7 +7520,15 @@ function graphToMachineConfig(graph, options = {}) {
7505
7520
  return additionalTarget ? [resolveTransitionTarget(graph, node, additionalTarget)] : [];
7506
7521
  })].filter(Boolean);
7507
7522
  const type = edge.data.eventType ?? "";
7508
- const transitionMeta = showMeta ? buildMeta(edge.data.meta, edge.data.color) : void 0;
7523
+ const transitionMeta = showMeta ? buildMeta({
7524
+ ...edge.data.meta,
7525
+ ...edge.data.assets?.length ? { "@statelyai.assets": edge.data.assets.map(({ type, url, size }) => ({
7526
+ type,
7527
+ url,
7528
+ ...size ? { size } : {}
7529
+ })) } : {},
7530
+ ...edge.data.appearance ? { "@statelyai.appearance": edge.data.appearance } : {}
7531
+ }, edge.data.color, showStatelyMeta) : void 0;
7509
7532
  const transitionObject = {
7510
7533
  ...edge.data.matches ? { matches: edge.data.matches } : void 0,
7511
7534
  target: edge.data.transitionType === "targetless" ? void 0 : resolvedTargets.length > 1 ? resolvedTargets : `${resolvedTarget}`,
@@ -7514,9 +7537,10 @@ function graphToMachineConfig(graph, options = {}) {
7514
7537
  ...edge.data.context !== void 0 ? { context: edge.data.context } : void 0,
7515
7538
  ...edge.data.input !== void 0 ? { input: edge.data.input } : void 0,
7516
7539
  actions: edge.data.actions?.length ? edge.data.actions.map(serializeAction) : void 0,
7517
- description: edge.data.description ?? void 0,
7540
+ description: showDescriptions ? edge.data.description ?? void 0 : void 0,
7518
7541
  ...transitionMeta ? { meta: transitionMeta } : void 0
7519
7542
  };
7543
+ transitionOrder.set(transitionObject, transitionOrder.size);
7520
7544
  const pushOn = (key) => {
7521
7545
  if (!on[key]) on[key] = [];
7522
7546
  on[key].push(transitionObject);
@@ -7559,7 +7583,11 @@ function graphToMachineConfig(graph, options = {}) {
7559
7583
  const delayKey = getDelayKeyFromEventType(type, edge.sourceId);
7560
7584
  if (!after[delayKey]) after[delayKey] = [];
7561
7585
  after[delayKey].push(transitionObject);
7562
- } else if (type === "*") pushOn("*");
7586
+ } else if (edge.data.meta?.["@statelyai.scxml.eventDescriptor"]) scxmlTransitions.push({
7587
+ descriptor: type,
7588
+ transition: transitionObject
7589
+ });
7590
+ else if (type === "*") pushOn("*");
7563
7591
  else if (type.startsWith("xstate.done.state")) {
7564
7592
  if (!onDone[type]) onDone[type] = [];
7565
7593
  onDone[type].push(transitionObject);
@@ -7570,6 +7598,16 @@ function graphToMachineConfig(graph, options = {}) {
7570
7598
  else pushOn(type);
7571
7599
  } else pushOn(type);
7572
7600
  }
7601
+ const descriptorBase = (descriptor) => descriptor.replace(/\.\*$/, "");
7602
+ const scxmlKeys = new Set([...Object.keys(on), ...scxmlTransitions.flatMap(({ descriptor }) => descriptor === "*" ? ["*"] : descriptorBase(descriptor) ? [descriptorBase(descriptor), `${descriptorBase(descriptor)}.*`] : [])]);
7603
+ for (const key of scxmlKeys) {
7604
+ const base = descriptorBase(key);
7605
+ const matching = scxmlTransitions.filter(({ descriptor }) => {
7606
+ const prefix = descriptorBase(descriptor);
7607
+ return descriptor === "*" || base === prefix || base.startsWith(`${prefix}.`);
7608
+ }).map(({ transition }) => transition);
7609
+ on[key] = [...new Set([...on[key] ?? [], ...matching])].sort((a, b) => (transitionOrder.get(a) ?? 0) - (transitionOrder.get(b) ?? 0));
7610
+ }
7573
7611
  if (Object.keys(on).length > 0) nodeConfig.on = singleOrArrayRecord(on);
7574
7612
  if (Object.keys(after).length > 0) nodeConfig.after = singleOrArrayRecord(after);
7575
7613
  if (alwaysArr.length > 0) nodeConfig.always = singleOrArray(alwaysArr);
@@ -8541,6 +8579,10 @@ function transitionFromValue(value, actions) {
8541
8579
  }
8542
8580
  if (!value || typeof value !== "object" || Array.isArray(value)) return actions.length ? { actions } : null;
8543
8581
  const record = { ...value };
8582
+ if ((Array.isArray(record.target) ? record.target : [record.target]).some(isCodeExpression)) record.meta = {
8583
+ ...record.meta,
8584
+ [XSTATE_V6_UNRESOLVED_TARGET_META]: true
8585
+ };
8544
8586
  if (actions.length) record.actions = mergeActions(Array.isArray(record.actions) ? record.actions : record.actions ? [record.actions] : void 0, actions);
8545
8587
  return record;
8546
8588
  }
@@ -8864,7 +8906,7 @@ function omitIdentityTransitionContext(transition, identity) {
8864
8906
  function normalizeFunctionTransition(fn, bindings, depth) {
8865
8907
  if (fn.body && ts.isBlock(fn.body) && hasUnsafeVariableInitializer(fn.body)) return fallbackFunctionTransition(fn);
8866
8908
  const branches = ts.isArrowFunction(fn) && !ts.isBlock(fn.body) ? expressionBranches(fn.body, bindings, depth + 1) : fn.body && ts.isBlock(fn.body) ? statementBranches(fn.body.statements, bindings, enqueueParamName(fn)) : [];
8867
- if (branches.some((branch) => branch.unknown)) return fallbackFunctionTransition(fn);
8909
+ if (branches.some((branch) => branch.unknown || isCodeExpression(branch.result))) return fallbackFunctionTransition(fn);
8868
8910
  const contextIdentity = transitionContextIdentity(fn);
8869
8911
  const transitions = branches.map((branch) => {
8870
8912
  let result = branch.result;
@@ -9300,6 +9342,7 @@ function expressionToValue(expression, bindings, context = {}, depth = 0) {
9300
9342
  if (builtIn) return builtIn;
9301
9343
  return inlineAction(node);
9302
9344
  }
9345
+ if (context.v6 && context.transitionValue) return inlineCodeExpression(resolvedExpressionText(node, bindings));
9303
9346
  const callee = node.expression.getText();
9304
9347
  const type = callee.includes(".") ? callee.split(".").pop() : callee;
9305
9348
  return { type: type === "assign" ? "xstate.assign" : type };
@@ -10107,8 +10150,9 @@ function mapV6MachineConfig(value, context = {}) {
10107
10150
  result[key] = mapV6StatesMap(item);
10108
10151
  continue;
10109
10152
  }
10110
- if (key === "choice") {
10111
- result[key] = buildV6TransitionFunction(Array.isArray(item) ? item : [item]) ?? mapV6MachineConfig(item, { transitionValue: true });
10153
+ if (key === "choice" && value.type === "choice") {
10154
+ const choices = Array.isArray(item) ? item : [item];
10155
+ result[key] = choices.length === 1 && opaqueTransitionSource(choices[0]) !== void 0 ? toV6TransitionValue(item) : buildV6TransitionFunction(choices.map(withoutOpaqueTransitionMeta), true) ?? toV6TransitionValue(item);
10112
10156
  continue;
10113
10157
  }
10114
10158
  if (key === "entry" || key === "exit" || key === "actions") {
@@ -10140,10 +10184,44 @@ function mapV6TransitionMap(value) {
10140
10184
  for (const [key, item] of Object.entries(value)) result[key] = mapV6MachineConfig(item, { transitionValue: true });
10141
10185
  return result;
10142
10186
  }
10187
+ /**
10188
+ * The authored source of a transition function the importer could not
10189
+ * analyze. It projects to one opaque edge, so the source is still exactly what
10190
+ * the edge represents.
10191
+ */
10192
+ function opaqueTransitionSource(value) {
10193
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
10194
+ const transition = value;
10195
+ const meta = transition.meta;
10196
+ const fnSource = meta?.[XSTATE_V6_FN_SOURCE_META];
10197
+ const hasGraphEdits = Object.entries(transition).some(([key, field]) => key !== "meta" && field !== void 0);
10198
+ return meta?.[XSTATE_V6_FN_TRANSITION_META] === true && typeof fnSource === "string" && !hasGraphEdits ? fnSource : void 0;
10199
+ }
10200
+ function withoutOpaqueTransitionMeta(value) {
10201
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
10202
+ const transition = value;
10203
+ const meta = transition.meta;
10204
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) return value;
10205
+ const { [XSTATE_V6_FN_TRANSITION_META]: _fnTransition, [XSTATE_V6_FN_SOURCE_META]: _fnSource, ...publicMeta } = meta;
10206
+ if (_fnTransition === void 0 && _fnSource === void 0) return value;
10207
+ return {
10208
+ ...transition,
10209
+ meta: Object.keys(publicMeta).length > 0 ? publicMeta : void 0
10210
+ };
10211
+ }
10143
10212
  function toV6TransitionValue(value) {
10144
- if (Array.isArray(value)) return buildV6TransitionFunction(value) ?? value.map((item) => toV6TransitionValue(item));
10213
+ const opaqueSource = opaqueTransitionSource(value);
10214
+ if (opaqueSource !== void 0) return raw(opaqueSource, value, void 0, true);
10215
+ if (Array.isArray(value)) {
10216
+ if (value.length === 1) {
10217
+ const onlyOpaqueSource = opaqueTransitionSource(value[0]);
10218
+ if (onlyOpaqueSource !== void 0) return raw(onlyOpaqueSource, value[0], void 0, true);
10219
+ }
10220
+ const transitions = value.map(withoutOpaqueTransitionMeta);
10221
+ return buildV6TransitionFunction(transitions) ?? transitions.map((item) => toV6TransitionValue(item));
10222
+ }
10145
10223
  if (!value || typeof value !== "object") return value;
10146
- const transition = value;
10224
+ const transition = withoutOpaqueTransitionMeta(value);
10147
10225
  if (transition.matches !== void 0) return buildV6TransitionFunction([transition]) ?? mapV6MachineConfig(transition);
10148
10226
  if (transition.guard) return buildV6TransitionFunction([transition]) ?? mapV6MachineConfig(transition);
10149
10227
  if (transition.description || transition.input !== void 0 && !containsCodeExpression(transition.input)) return mapV6MachineConfig(transition);
@@ -10294,11 +10372,11 @@ function mergeV6FirstParam(authoredFirstParam, inferred) {
10294
10372
  prelude: []
10295
10373
  };
10296
10374
  }
10297
- function buildV6TransitionFunction(transitions) {
10375
+ function buildV6TransitionFunction(transitions, choice = false) {
10298
10376
  const transitionObjects = transitions.every((item) => Boolean(item) && typeof item === "object" && !Array.isArray(item)) ? transitions : void 0;
10299
10377
  if (!transitionObjects?.length) return;
10300
10378
  const descriptions = transitionObjects.map((transition) => typeof transition.description === "string" ? transition.description : void 0);
10301
- const hasBranchDescriptions = transitionObjects.some((transition) => Object.prototype.hasOwnProperty.call(transition.meta ?? {}, XSTATE_V6_BRANCH_DESCRIPTION_META)) || descriptions.some((description) => description !== descriptions[0]);
10379
+ const hasBranchDescriptions = choice && descriptions.some((description) => description !== void 0) || transitionObjects.some((transition) => Object.prototype.hasOwnProperty.call(transition.meta ?? {}, XSTATE_V6_BRANCH_DESCRIPTION_META)) || descriptions.some((description) => description !== descriptions[0]);
10302
10380
  const branches = [];
10303
10381
  let authoredFirstParam;
10304
10382
  const commonDescription = descriptions[0];
@@ -10334,7 +10412,7 @@ function buildV6TransitionFunction(transitions) {
10334
10412
  });
10335
10413
  }
10336
10414
  if (branches.length === 0) return;
10337
- if (branches.slice(0, -1).some((branch) => branch.condition === "true")) return;
10415
+ if (!choice && branches.slice(0, -1).some((branch) => branch.condition === "true")) return;
10338
10416
  const generated = branches.flatMap((branch) => [branch.condition, ...branch.body]).join("\n");
10339
10417
  let renderedBranches = branches;
10340
10418
  let merged = mergeV6FirstParam(authoredFirstParam, inferV6ArgBindings(generated));
@@ -10347,7 +10425,7 @@ function buildV6TransitionFunction(transitions) {
10347
10425
  }
10348
10426
  if (!merged) return;
10349
10427
  const lines = merged.prelude.map((statement) => indentV6Code(statement, 2));
10350
- for (const branch of renderedBranches) if (branch.condition === "true") lines.push(...branch.body.map((line) => indentV6Code(line, 2)));
10428
+ for (const [index, branch] of renderedBranches.entries()) if (branch.condition === "true" && index === renderedBranches.length - 1) lines.push(...branch.body.map((line) => indentV6Code(line, 2)));
10351
10429
  else {
10352
10430
  lines.push(indentV6Code(`if (${branch.condition}) {`, 2));
10353
10431
  lines.push(...branch.body.map((line) => indentV6Code(line, 4)));
@@ -10937,6 +11015,51 @@ function isCompleteFunctionExpression(code) {
10937
11015
  if (!expression) return false;
10938
11016
  return ts.isFunctionExpression(expression) || ts.isArrowFunction(expression);
10939
11017
  }
11018
+ function isMachineCode(value) {
11019
+ return !!value && typeof value === "object" && typeof value["@code"] === "string" && typeof value["@lang"] === "string";
11020
+ }
11021
+ function hasMachineCode(value, seen = /* @__PURE__ */ new WeakSet()) {
11022
+ if (isMachineCode(value)) return true;
11023
+ if (!value || typeof value !== "object" || seen.has(value)) return false;
11024
+ seen.add(value);
11025
+ return Object.values(value).some((child) => hasMachineCode(child, seen));
11026
+ }
11027
+ function parse$2(source) {
11028
+ return ts.createSourceFile("inspection.ts", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
11029
+ }
11030
+ function expressionSource(source) {
11031
+ if (!parse$2(`const value = (${source});`).parseDiagnostics.length) return source;
11032
+ const methodFile = parse$2(`const value = {${source}};`);
11033
+ const statement = methodFile.statements[0];
11034
+ if (!methodFile.parseDiagnostics.length && statement && ts.isVariableStatement(statement)) {
11035
+ const initializer = statement.declarationList.declarations[0]?.initializer;
11036
+ const method = initializer && ts.isObjectLiteralExpression(initializer) ? initializer.properties[0] : void 0;
11037
+ if (method && ts.isMethodDeclaration(method) && method.body) return `${method.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword) ? "async " : ""}function${method.asteriskToken ? "*" : ""}(${method.parameters.map((param) => param.getText(methodFile)).join(", ")}) ${method.body.getText(methodFile)}`;
11038
+ }
11039
+ return "() => __stately_inspection_unknown__()";
11040
+ }
11041
+ /** Project runtime MachineJSON code through the same static parser as authored v6 source. */
11042
+ function projectMachineJSON(config) {
11043
+ if (!hasMachineCode(config)) return config;
11044
+ function expressions(value) {
11045
+ if (isMachineCode(value)) return raw(["ts", "js"].includes(value["@lang"]) ? expressionSource(value["@code"]) : "() => __stately_inspection_unknown__()");
11046
+ if (Array.isArray(value)) return value.map(expressions);
11047
+ if (!value || typeof value !== "object") return value;
11048
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, expressions(item)]));
11049
+ }
11050
+ const projected = xstateSourceToConfig(`createMachine(${serializeJS(expressions(config))});`, { profile: "xstate-v6-alpha" });
11051
+ function restoreSchemas(original, parsed) {
11052
+ if (!original || !parsed || typeof original !== "object" || typeof parsed !== "object") return;
11053
+ const source = original;
11054
+ const result = parsed;
11055
+ if (Object.hasOwn(source, "schemas")) result.schemas = source.schemas;
11056
+ const states = source.states;
11057
+ const parsedStates = result.states;
11058
+ for (const key of Object.keys(states ?? {})) restoreSchemas(states[key], parsedStates?.[key]);
11059
+ }
11060
+ restoreSchemas(config, projected);
11061
+ return projected;
11062
+ }
10940
11063
  /**
10941
11064
  * A compound state node with no usable `initial` cannot be simulated (XState
10942
11065
  * throws "No initial state specified for compound state node"). This walks a
@@ -11056,6 +11179,7 @@ function actorImplementationItems(value) {
11056
11179
  }));
11057
11180
  }
11058
11181
  function machineConfigToGraph(config, options = {}) {
11182
+ if (isXStateV6Profile(options.profile)) config = projectMachineJSON(config);
11059
11183
  const rootState = config;
11060
11184
  const rootConfig = config;
11061
11185
  const rootKey = String(rootState.id ?? "machine");
@@ -13027,31 +13151,9 @@ var ParseErrorCode;
13027
13151
  function modify(text, path, value, options) {
13028
13152
  return setProperty(text, path, value, options);
13029
13153
  }
13030
- /**
13031
- * Applies edits to an input string.
13032
- * @param text The input text
13033
- * @param edits Edit operations following the format described in {@linkcode EditResult}.
13034
- * @returns The text with the applied edits.
13035
- * @throws An error if the edit operations are not well-formed as described in {@linkcode EditResult}.
13036
- */
13037
- function applyEdits(text, edits) {
13038
- let sortedEdits = edits.slice(0).sort((a, b) => {
13039
- const diff = a.offset - b.offset;
13040
- if (diff === 0) return a.length - b.length;
13041
- return diff;
13042
- });
13043
- let lastModifiedOffset = text.length;
13044
- for (let i = sortedEdits.length - 1; i >= 0; i--) {
13045
- let e = sortedEdits[i];
13046
- if (e.offset + e.length <= lastModifiedOffset) text = applyEdit(text, e);
13047
- else throw new Error("Overlapping edit");
13048
- lastModifiedOffset = e.offset;
13049
- }
13050
- return text;
13051
- }
13052
13154
 
13053
13155
  //#endregion
13054
- //#region ../editor-sync/dist/adapters-gXgLug6Z.mjs
13156
+ //#region ../editor-sync/dist/adapters-Dt77BFUa.mjs
13055
13157
  const sourceFileUris = /* @__PURE__ */ new WeakMap();
13056
13158
  function collectXStateSourceLocations(sourceText, document, options = {}) {
13057
13159
  const sourceFile = ts$1.createSourceFile(document.fileName, sourceText, ts$1.ScriptTarget.Latest, true, getScriptKind$2$1(document.fileName));
@@ -13237,11 +13339,21 @@ function addImportedStaticValueBindings(bindings, sourceFile, uri, fileName, opt
13237
13339
  }, statement.moduleSpecifier.text);
13238
13340
  if (!importedDocument) continue;
13239
13341
  for (const element of namedBindings.elements) {
13240
- const binding = resolveExportedStaticValue(importedDocument, element.propertyName?.text ?? element.name.text, resolver, /* @__PURE__ */ new Set());
13241
- if (binding) bindings.set(element.name.text, {
13242
- ...binding,
13243
- symbol: element.name.text
13244
- });
13342
+ const importedName = element.propertyName?.text ?? element.name.text;
13343
+ const members = new Set([""]);
13344
+ const visit = (node) => {
13345
+ if (ts$1.isPropertyAccessExpression(node) && ts$1.isIdentifier(node.expression) && node.expression.text === element.name.text) members.add(`.${node.name.text}`);
13346
+ ts$1.forEachChild(node, visit);
13347
+ };
13348
+ visit(sourceFile);
13349
+ for (const member of members) {
13350
+ const binding = resolveExportedStaticValue(importedDocument, `${importedName}${member}`, resolver, /* @__PURE__ */ new Set());
13351
+ const symbol = `${element.name.text}${member}`;
13352
+ if (binding) bindings.set(symbol, {
13353
+ ...binding,
13354
+ symbol
13355
+ });
13356
+ }
13245
13357
  }
13246
13358
  }
13247
13359
  }
@@ -13254,15 +13366,21 @@ function resolveExportedStaticValue(document, exportName, resolver, visited) {
13254
13366
  sourceFileUris.set(sourceFile, document.uri);
13255
13367
  const localBindings = collectStaticValueBindings(sourceFile);
13256
13368
  const localBinding = localBindings.get(exportName);
13257
- if (localBinding && sourceFile.statements.some((statement) => ts$1.isVariableStatement(statement) && statement.modifiers?.some((modifier) => modifier.kind === ts$1.SyntaxKind.ExportKeyword) && statement.declarationList.declarations.some((declaration) => ts$1.isIdentifier(declaration.name) && declaration.name.text === exportName))) return localBinding;
13369
+ const [exportedSymbol] = exportName.split(".");
13370
+ const memberSuffix = exportName.slice(exportedSymbol.length);
13371
+ if (localBinding && sourceFile.statements.some((statement) => {
13372
+ if (!ts$1.isVariableStatement(statement) && !ts$1.isEnumDeclaration(statement)) return false;
13373
+ if (!statement.modifiers?.some((modifier) => modifier.kind === ts$1.SyntaxKind.ExportKeyword)) return false;
13374
+ return ts$1.isEnumDeclaration(statement) ? statement.name.text === exportedSymbol : statement.declarationList.declarations.some((declaration) => ts$1.isIdentifier(declaration.name) && declaration.name.text === exportName);
13375
+ })) return localBinding;
13258
13376
  const exports = sourceFile.statements.filter(ts$1.isExportDeclaration);
13259
13377
  for (const statement of exports) {
13260
13378
  if (!statement.exportClause || !ts$1.isNamedExports(statement.exportClause)) continue;
13261
13379
  const specifier = statement.moduleSpecifier && ts$1.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : null;
13262
13380
  const exportedDocument = specifier?.startsWith(".") ? resolver.resolveImport(document, specifier) : null;
13263
13381
  for (const element of statement.exportClause.elements) {
13264
- if (element.name.text !== exportName) continue;
13265
- const localName = element.propertyName?.text ?? element.name.text;
13382
+ if (element.name.text !== exportedSymbol) continue;
13383
+ const localName = `${element.propertyName?.text ?? element.name.text}${memberSuffix}`;
13266
13384
  if (exportedDocument) {
13267
13385
  const binding = resolveExportedStaticValue(exportedDocument, localName, resolver, visited);
13268
13386
  return binding && {
@@ -13298,15 +13416,17 @@ function resolveExportedStaticValue(document, exportName, resolver, visited) {
13298
13416
  }
13299
13417
  }
13300
13418
  function resolveImportedStaticValue(sourceFile, document, localName, resolver, visited) {
13419
+ const [localSymbol] = localName.split(".");
13420
+ const memberSuffix = localName.slice(localSymbol.length);
13301
13421
  for (const statement of sourceFile.statements) {
13302
13422
  if (!ts$1.isImportDeclaration(statement) || !ts$1.isStringLiteral(statement.moduleSpecifier) || !statement.moduleSpecifier.text.startsWith(".")) continue;
13303
13423
  const namedBindings = statement.importClause?.namedBindings;
13304
13424
  if (!namedBindings || !ts$1.isNamedImports(namedBindings)) continue;
13305
- const imported = namedBindings.elements.find((element) => element.name.text === localName);
13425
+ const imported = namedBindings.elements.find((element) => element.name.text === localSymbol);
13306
13426
  if (!imported) continue;
13307
13427
  const importedDocument = resolver.resolveImport(document, statement.moduleSpecifier.text);
13308
13428
  if (!importedDocument) return null;
13309
- return resolveExportedStaticValue(importedDocument, imported.propertyName?.text ?? imported.name.text, resolver, visited);
13429
+ return resolveExportedStaticValue(importedDocument, `${imported.propertyName?.text ?? imported.name.text}${memberSuffix}`, resolver, visited);
13310
13430
  }
13311
13431
  return null;
13312
13432
  }
@@ -14195,8 +14315,9 @@ async function applyMachineToSourceDocuments(machineConfig, document, options =
14195
14315
  let semanticReplacements = semanticSplit?.replacements ?? [];
14196
14316
  if (activePatches.length === 0) return finalize(semanticReplacements);
14197
14317
  const transitionSplit = await tryCreateTransitionPatchSplitForDocuments(document, sourceFile, machineConfigNode, machineConfig, activePatches, prevGraph, nextGraph, activeSourceLocations, options.openTextDocument);
14198
- if (transitionSplit && transitionSplit.remainingPatches.length === 0) return finalize([...semanticReplacements, ...transitionSplit.replacements]);
14199
- if (transitionSplit) semanticReplacements = [...semanticReplacements, ...transitionSplit.replacements];
14318
+ const transitionReplacements = transitionSplit ? combineSemanticAndTransitionReplacements(semanticReplacements, transitionSplit.replacements) : semanticReplacements;
14319
+ if (transitionSplit && transitionSplit.remainingPatches.length === 0) return finalize(transitionReplacements);
14320
+ if (transitionSplit) semanticReplacements = transitionReplacements;
14200
14321
  const implementationSplit = tryCreateImplementationPatchSplit(document, sourceFile, transitionSplit?.remainingPatches ?? activePatches, nextGraph, options.machineIndex);
14201
14322
  if (implementationSplit && implementationSplit.remainingPatches.length === 0) return finalize([...semanticReplacements, ...implementationSplit.replacements]);
14202
14323
  if (implementationSplit) semanticReplacements = [...semanticReplacements, ...implementationSplit.replacements];
@@ -14673,7 +14794,17 @@ async function createTransitionReplacementForDocuments(document, sourceFile, mac
14673
14794
  if (transitionSource && transitionTarget.previousEventType === transitionTarget.eventType) return [createPropertyValueReplacement(targetDocument, targetSourceFile, transitionSource.eventProperty, nextTransitionValue)];
14674
14795
  if (transitionSource) return [createPropertyAssignmentReplacement(targetDocument, targetSourceFile, transitionSource.eventProperty, transitionTarget.eventType, nextTransitionValue)];
14675
14796
  const onObject = getObjectLiteralProperty(stateObject, "on");
14676
- if (onObject) return createObjectLiteralPropertyInsertion(targetDocument, targetSourceFile, onObject, transitionTarget.eventType, nextTransitionValue);
14797
+ if (onObject) {
14798
+ const nextOn = getStateConfigByPath(machineConfig, transitionTarget.sourcePath)?.on;
14799
+ if (isMachineConfig(nextOn) && !onObject.properties.some((property) => {
14800
+ const key = property.name && getPropertyNameText$2(property.name);
14801
+ return !key || nextOn[key] !== void 0;
14802
+ })) return [createObjectLiteralPropertiesInsertion(targetDocument, targetSourceFile, onObject, [{
14803
+ key: transitionTarget.eventType,
14804
+ config: nextTransitionValue
14805
+ }], false)];
14806
+ return createObjectLiteralPropertyInsertion(targetDocument, targetSourceFile, onObject, transitionTarget.eventType, nextTransitionValue);
14807
+ }
14677
14808
  return createObjectLiteralPropertyInsertion(targetDocument, targetSourceFile, stateObject, "on", { [transitionTarget.eventType]: nextTransitionValue });
14678
14809
  }
14679
14810
  async function createV6BranchActionNameReplacementForDocuments(document, sourceFile, machineConfigNode, patch, transitionTarget, prevGraph, nextGraph, sourceLocations, openTextDocument) {
@@ -14820,7 +14951,10 @@ async function createStatePropertiesReplacementForDocuments(document, sourceFile
14820
14951
  config: nextSlotValue
14821
14952
  });
14822
14953
  }
14823
- if (insertedSlots.length > 0) replacements.push(createObjectLiteralPropertiesInsertion(targetDocument, targetSourceFile, existingMap, insertedSlots));
14954
+ if (insertedSlots.length > 0) replacements.push(createObjectLiteralPropertiesInsertion(targetDocument, targetSourceFile, existingMap, insertedSlots, existingMap.properties.some((property) => {
14955
+ const key = property.name && getPropertyNameText$2(property.name);
14956
+ return !key || !affectedSlots.has(key) || nextValue[key] !== void 0;
14957
+ })));
14824
14958
  continue;
14825
14959
  }
14826
14960
  }
@@ -15499,7 +15633,7 @@ function createStateMembershipInitialReplacements(document, sourceFile, parentOb
15499
15633
  config: nextInitial
15500
15634
  }])];
15501
15635
  }
15502
- function createObjectLiteralPropertiesInsertion(document, sourceFile, objectLiteral, entries) {
15636
+ function createObjectLiteralPropertiesInsertion(document, sourceFile, objectLiteral, entries, hasRetainedProperties = objectLiteral.properties.length > 0) {
15503
15637
  const formatting = getFormattingContext(document, sourceFile, objectLiteral);
15504
15638
  const lineSeparator = formatting.newLineKind === ts$1.NewLineKind.CarriageReturnLineFeed ? "\r\n" : "\n";
15505
15639
  const propertyTexts = entries.map((entry) => printObjectLiteralProperty(entry.key, entry.config, sourceFile, formatting));
@@ -15508,7 +15642,7 @@ function createObjectLiteralPropertiesInsertion(document, sourceFile, objectLite
15508
15642
  const startOffset = existingProperties.length === 0 ? closeBraceOffset : existingProperties[existingProperties.length - 1].getEnd();
15509
15643
  const start = document.positionAt(startOffset);
15510
15644
  const end = document.positionAt(closeBraceOffset);
15511
- const prefix = existingProperties.length === 0 ? lineSeparator : `,${lineSeparator}`;
15645
+ const prefix = hasRetainedProperties ? `,${lineSeparator}` : lineSeparator;
15512
15646
  const suffix = `${lineSeparator}${formatting.baseIndent}`;
15513
15647
  return {
15514
15648
  uri: getDocumentUri(document),
@@ -16219,8 +16353,8 @@ function createStateKeyRenameReplacementsForTarget(document, sourceFile, parentO
16219
16353
  }
16220
16354
  const supportedScope = new Set([rename.parentPath.join("."), ...getDirectChildStatePaths(nodeKind === "statesObject" ? initialOwnerObject : parentObject, rename.parentPath).map((path) => path.join("."))]);
16221
16355
  for (const target of targetLiterals) {
16222
- if (!referencesRenamedState(target.node.text, rename)) continue;
16223
- if (!referencesRenamedAbsoluteState(target.node.text, rename)) {
16356
+ if (!referencesRenamedState(target.value, rename)) continue;
16357
+ if (!referencesRenamedAbsoluteState(target.value, rename)) {
16224
16358
  const initialSourcePaths = target.initialSourcePaths ?? [];
16225
16359
  if (initialSourcePaths.length > 0) {
16226
16360
  if (!target.sourcePaths.every((sourcePath) => arePathsEqual(sourcePath, rename.parentPath))) {
@@ -16232,7 +16366,7 @@ function createStateKeyRenameReplacementsForTarget(document, sourceFile, parentO
16232
16366
  if (!(sourcePath.length === rename.parentPath.length || arePathsEqual(sourcePath.slice(0, -1), rename.parentPath)) || !supportedScope.has(sourceKey)) return null;
16233
16367
  }
16234
16368
  }
16235
- const replacement = createStringLiteralReplacement(target.document, target.sourceFile, target.node, renameStateReference(target.node.text, rename));
16369
+ const replacement = createStringLiteralReplacement(target.document, target.sourceFile, target.node, renameStateReference(target.value, rename));
16236
16370
  replacements.push(replacement);
16237
16371
  }
16238
16372
  return replacements;
@@ -16367,21 +16501,25 @@ function getDirectChildStatePaths(stateObject, parentPath) {
16367
16501
  return [[...parentPath, key]];
16368
16502
  });
16369
16503
  }
16370
- function collectTargetStringLiterals(machineConfigNode) {
16504
+ function collectTargetStringLiterals(machineConfigNode, sourceLocations, documentUri) {
16371
16505
  const literals = [];
16372
- const bindings = collectTopLevelExpressionBindings(machineConfigNode.getSourceFile());
16506
+ const bindings = collectTopLevelExpressionBindings(machineConfigNode.getSourceFile(), sourceLocations, documentUri);
16373
16507
  const visitStateObject = (stateObject, sourcePath) => {
16374
16508
  collectInitialTargetStringLiterals(stateObject, sourcePath, literals, bindings);
16375
16509
  for (const propertyName of [
16376
16510
  "always",
16377
16511
  "after",
16378
16512
  "on",
16379
- "onDone"
16513
+ "onDone",
16514
+ "onError",
16515
+ "onTimeout",
16516
+ "choice"
16380
16517
  ]) {
16381
16518
  const property = getPropertyAssignment(stateObject, propertyName);
16382
16519
  if (!property) continue;
16383
16520
  collectTargetStringLiteralsFromExpression(property.initializer, sourcePath, literals, bindings);
16384
16521
  }
16522
+ collectInvokeTargetStringLiterals(stateObject, sourcePath, literals, bindings);
16385
16523
  const statesObject = getObjectLiteralProperty(stateObject, "states");
16386
16524
  if (!statesObject) return;
16387
16525
  for (const property of statesObject.properties) {
@@ -16402,11 +16540,11 @@ function collectInitialTargetStringLiterals(stateObject, sourcePath, literals, b
16402
16540
  if (ts$1.isObjectLiteralExpression(initializer)) {
16403
16541
  const initialLiterals = [];
16404
16542
  collectTargetStringLiteralsFromExpression(initializer, sourcePath, initialLiterals, bindings);
16405
- for (const literal of initialLiterals) for (const literalSourcePath of literal.sourcePaths) addTargetLiteral(literals, literal.node, literalSourcePath, true);
16543
+ for (const literal of initialLiterals) for (const literalSourcePath of literal.sourcePaths) addTargetLiteral(literals, literal.node, literalSourcePath, true, literal.value);
16406
16544
  }
16407
16545
  }
16408
16546
  async function collectMappedTargetStringLiterals(rootDocument, rootSourceFile, machineConfigNode, sourceLocations, openTextDocument) {
16409
- const literals = collectTargetStringLiterals(machineConfigNode).map((target) => ({
16547
+ const literals = collectTargetStringLiterals(machineConfigNode, sourceLocations, getDocumentUri(rootDocument)).map((target) => ({
16410
16548
  ...target,
16411
16549
  document: rootDocument,
16412
16550
  sourceFile: rootSourceFile
@@ -16421,7 +16559,7 @@ async function collectMappedTargetStringLiterals(rootDocument, rootSourceFile, m
16421
16559
  const stateObject = findObjectLiteralByRange(targetSourceFile, location.range);
16422
16560
  if (!stateObject) continue;
16423
16561
  const stateLiterals = [];
16424
- collectTargetStringLiteralsFromStateObject(stateObject, location.path, stateLiterals, collectTopLevelExpressionBindings(targetSourceFile));
16562
+ collectTargetStringLiteralsFromStateObject(stateObject, location.path, stateLiterals, collectTopLevelExpressionBindings(targetSourceFile, sourceLocations, getDocumentUri(targetDocument)));
16425
16563
  for (const target of stateLiterals) {
16426
16564
  const key = getTargetLiteralReplacementKey(targetDocument, targetSourceFile, target.node);
16427
16565
  if (seen.has(key)) {
@@ -16472,7 +16610,7 @@ async function collectMappedTargetStringLiterals(rootDocument, rootSourceFile, m
16472
16610
  }
16473
16611
  }
16474
16612
  const functionLiterals = [];
16475
- const bindings = collectTopLevelExpressionBindings(targetSourceFile);
16613
+ const bindings = collectTopLevelExpressionBindings(targetSourceFile, sourceLocations, getDocumentUri(targetDocument));
16476
16614
  for (const sourcePath of location.sourcePaths) collectTargetStringLiteralsFromFunction(fn, sourcePath, functionLiterals, bindings, new Set([location.symbol]));
16477
16615
  for (const target of functionLiterals) {
16478
16616
  const key = getTargetLiteralReplacementKey(targetDocument, targetSourceFile, target.node);
@@ -16558,7 +16696,10 @@ function collectTransitionCallArgumentLiterals(machineConfigNode, symbol, target
16558
16696
  "always",
16559
16697
  "after",
16560
16698
  "on",
16561
- "onDone"
16699
+ "onDone",
16700
+ "onError",
16701
+ "onTimeout",
16702
+ "choice"
16562
16703
  ]) {
16563
16704
  const property = getPropertyAssignment(stateObject, propertyName);
16564
16705
  if (property) visitExpression(property.initializer, sourcePath);
@@ -16584,12 +16725,16 @@ function collectTargetStringLiteralsFromStateObject(stateObject, sourcePath, lit
16584
16725
  "always",
16585
16726
  "after",
16586
16727
  "on",
16587
- "onDone"
16728
+ "onDone",
16729
+ "onError",
16730
+ "onTimeout",
16731
+ "choice"
16588
16732
  ]) {
16589
16733
  const property = getPropertyAssignment(stateObject, propertyName);
16590
16734
  if (!property) continue;
16591
16735
  collectTargetStringLiteralsFromExpression(property.initializer, sourcePath, literals, bindings);
16592
16736
  }
16737
+ collectInvokeTargetStringLiterals(stateObject, sourcePath, literals, bindings);
16593
16738
  const statesObject = getObjectLiteralProperty(stateObject, "states");
16594
16739
  if (!statesObject) return;
16595
16740
  for (const property of statesObject.properties) {
@@ -16600,8 +16745,35 @@ function collectTargetStringLiteralsFromStateObject(stateObject, sourcePath, lit
16600
16745
  collectTargetStringLiteralsFromStateObject(childState, [...sourcePath, key], literals, bindings);
16601
16746
  }
16602
16747
  }
16748
+ function collectInvokeTargetStringLiterals(stateObject, sourcePath, literals, bindings) {
16749
+ const invoke = getPropertyAssignment(stateObject, "invoke");
16750
+ if (!invoke) return;
16751
+ const visit = (expression, seen = /* @__PURE__ */ new Set()) => {
16752
+ const current = unwrapExpression$2(expression);
16753
+ if (ts$1.isIdentifier(current)) {
16754
+ const binding = bindings.get(current.text);
16755
+ if (binding && ts$1.isExpression(binding) && !seen.has(current.text)) visit(binding, new Set([...seen, current.text]));
16756
+ } else if (ts$1.isArrayLiteralExpression(current)) {
16757
+ for (const item of current.elements) if (ts$1.isExpression(item)) visit(item, seen);
16758
+ } else if (ts$1.isObjectLiteralExpression(current)) for (const name of [
16759
+ "onDone",
16760
+ "onError",
16761
+ "onSnapshot",
16762
+ "onTimeout"
16763
+ ]) {
16764
+ const property = getPropertyAssignment(current, name);
16765
+ if (property) collectTargetStringLiteralsFromExpression(property.initializer, sourcePath, literals, bindings);
16766
+ }
16767
+ };
16768
+ visit(invoke.initializer);
16769
+ }
16603
16770
  function collectTargetStringLiteralsFromExpression(expression, sourcePath, literals, bindings, seenBindings = /* @__PURE__ */ new Set()) {
16604
16771
  const current = unwrapExpression$2(expression);
16772
+ const staticTarget = bindings.staticTargets?.get(`${current.getStart()}:${current.getEnd()}`);
16773
+ if (staticTarget !== void 0 && (ts$1.isIdentifier(current) || ts$1.isPropertyAccessExpression(current))) {
16774
+ addTargetLiteral(literals, current, sourcePath, false, staticTarget);
16775
+ return;
16776
+ }
16605
16777
  if (ts$1.isStringLiteralLike(current)) {
16606
16778
  addTargetLiteral(literals, current, sourcePath);
16607
16779
  return;
@@ -16624,6 +16796,7 @@ function collectTargetStringLiteralsFromExpression(expression, sourcePath, liter
16624
16796
  const binding = bindings.get(current.expression.text);
16625
16797
  if (binding && isTransitionFunctionBinding(binding)) {
16626
16798
  const scopedBindings = new Map(bindings);
16799
+ scopedBindings.staticTargets = bindings.staticTargets;
16627
16800
  for (const [index, parameter] of binding.parameters.entries()) {
16628
16801
  if (!ts$1.isIdentifier(parameter.name)) continue;
16629
16802
  const argument = current.arguments[index];
@@ -16686,8 +16859,9 @@ function collectTargetStringLiteralsFromFunction(fn, sourcePath, literals, bindi
16686
16859
  }
16687
16860
  collectTargetStringLiteralsFromExpression(fn.body, sourcePath, literals, bindings, seenBindings);
16688
16861
  }
16689
- function collectTopLevelExpressionBindings(sourceFile) {
16862
+ function collectTopLevelExpressionBindings(sourceFile, sourceLocations, documentUri) {
16690
16863
  const bindings = /* @__PURE__ */ new Map();
16864
+ bindings.staticTargets = new Map((sourceLocations?.staticValues ?? []).filter((location) => location.definitionUri && location.uri === documentUri).map((location) => [`${offsetAtRangePosition(sourceFile.text, location.range.startLine, location.range.startChar)}:${offsetAtRangePosition(sourceFile.text, location.range.endLine, location.range.endChar)}`, location.value]));
16691
16865
  for (const statement of sourceFile.statements) {
16692
16866
  if (ts$1.isFunctionDeclaration(statement) && statement.name) {
16693
16867
  bindings.set(statement.name.text, statement);
@@ -16698,7 +16872,7 @@ function collectTopLevelExpressionBindings(sourceFile) {
16698
16872
  }
16699
16873
  return bindings;
16700
16874
  }
16701
- function addTargetLiteral(literals, node, sourcePath, isInitialTarget = false) {
16875
+ function addTargetLiteral(literals, node, sourcePath, isInitialTarget = false, value = node.text) {
16702
16876
  const existing = literals.find((literal) => literal.node.getStart() === node.getStart() && literal.node.getEnd() === node.getEnd());
16703
16877
  if (existing) {
16704
16878
  mergeTargetSourcePaths(existing, [sourcePath]);
@@ -16707,6 +16881,7 @@ function addTargetLiteral(literals, node, sourcePath, isInitialTarget = false) {
16707
16881
  }
16708
16882
  literals.push({
16709
16883
  node,
16884
+ value,
16710
16885
  sourcePaths: [sourcePath],
16711
16886
  ...isInitialTarget ? { initialSourcePaths: [sourcePath] } : {}
16712
16887
  });
@@ -16877,6 +17052,10 @@ function compareReplacementRangesDescending(a, b) {
16877
17052
  function normalizeOverlappingReplacements(replacements) {
16878
17053
  return planTextReplacements(replacements);
16879
17054
  }
17055
+ function combineSemanticAndTransitionReplacements(semantic, transitions) {
17056
+ const compare = (leftLine, leftChar, rightLine, rightChar) => leftLine - rightLine || leftChar - rightChar;
17057
+ return [...semantic.filter((focused) => !transitions.some((owner) => owner.uri === focused.uri && compare(focused.range.startLine, focused.range.startChar, owner.range.startLine, owner.range.startChar) > 0 && compare(focused.range.endLine, focused.range.endChar, owner.range.endLine, owner.range.endChar) <= 0)), ...transitions];
17058
+ }
16880
17059
  function appendWholeStateReplacement(replacements, wholeState) {
16881
17060
  const compare = (leftLine, leftChar, rightLine, rightChar) => leftLine - rightLine || leftChar - rightChar;
16882
17061
  for (let index = replacements.length - 1; index >= 0; index--) {
@@ -17065,6 +17244,138 @@ function stripPresentationPatches(patches) {
17065
17244
  return [sourcePatch];
17066
17245
  });
17067
17246
  }
17247
+ const tokenKind = {
17248
+ comma: 5,
17249
+ lineComment: 12,
17250
+ blockComment: 13,
17251
+ lineBreak: 14,
17252
+ eof: 17
17253
+ };
17254
+ /** Move authored values and same-line comments together, retaining array trivia. */
17255
+ function reorderedActionArray(text, array, order, inserted = /* @__PURE__ */ new Map()) {
17256
+ const items = array.children ?? [];
17257
+ if (!items.length || order.some((index, position) => index < 0 ? !inserted.has(position) : !items[index])) return null;
17258
+ const prefixes = [text.slice(array.offset + 1, items[0].offset)];
17259
+ const comments = [];
17260
+ let closing = "";
17261
+ let trailingComma = false;
17262
+ for (let index = 0; index < items.length; index++) {
17263
+ const item = items[index];
17264
+ const end = items[index + 1]?.offset ?? array.offset + array.length - 1;
17265
+ const gap = text.slice(item.offset + item.length, end);
17266
+ const scanner = createScanner(gap, false);
17267
+ let comma = -1;
17268
+ let commentEnd = 0;
17269
+ let onSameLine = true;
17270
+ for (let token = scanner.scan(); token !== tokenKind.eof; token = scanner.scan()) {
17271
+ if (token === tokenKind.comma && comma < 0) comma = scanner.getTokenOffset();
17272
+ if (token === tokenKind.lineBreak) onSameLine = false;
17273
+ if (onSameLine && (token === tokenKind.lineComment || token === tokenKind.blockComment)) {
17274
+ commentEnd = scanner.getTokenOffset() + scanner.getTokenLength();
17275
+ if (scanner.getTokenValue().includes("\n")) onSameLine = false;
17276
+ }
17277
+ }
17278
+ const withoutComma = (value, offset) => comma >= offset && comma < offset + value.length ? value.slice(0, comma - offset) + value.slice(comma - offset + 1) : value;
17279
+ comments.push(withoutComma(gap.slice(0, commentEnd), 0));
17280
+ const remainder = withoutComma(gap.slice(commentEnd), commentEnd);
17281
+ if (index < items.length - 1) prefixes.push(remainder);
17282
+ else {
17283
+ closing = remainder;
17284
+ trailingComma = comma >= 0;
17285
+ }
17286
+ }
17287
+ const defaultPrefix = prefixes[0].match(/\r?\n[ \t]*$/)?.[0] ?? prefixes.find((prefix) => /^\s*$/.test(prefix)) ?? "";
17288
+ let interior = "";
17289
+ for (let index = 0; index < order.length; index++) {
17290
+ const oldIndex = order[index];
17291
+ const item = items[oldIndex];
17292
+ const ownPrefix = oldIndex > 0 ? prefixes[oldIndex] ?? defaultPrefix : defaultPrefix;
17293
+ let prefix = index === 0 ? prefixes[0] : ownPrefix;
17294
+ if (index === 0 && oldIndex > 0 && stripComments(ownPrefix) !== ownPrefix) prefix += ownPrefix.trimStart();
17295
+ interior += prefix + (item ? text.slice(item.offset, item.offset + item.length) : inserted.get(index));
17296
+ if (index < order.length - 1 || trailingComma) interior += ",";
17297
+ interior += comments[oldIndex] ?? "";
17298
+ }
17299
+ return {
17300
+ start: array.offset + 1,
17301
+ end: array.offset + array.length - 1,
17302
+ text: interior + closing
17303
+ };
17304
+ }
17305
+ /** Keep unchanged source spans as anchors while composing successive tree edits. */
17306
+ var PreservedText = class {
17307
+ pieces;
17308
+ constructor(original) {
17309
+ this.original = original;
17310
+ this.pieces = [{
17311
+ text: original,
17312
+ originalOffset: 0
17313
+ }];
17314
+ }
17315
+ replace(start, end, text) {
17316
+ const before = [];
17317
+ const after = [];
17318
+ let offset = 0;
17319
+ for (const piece of this.pieces) {
17320
+ const pieceEnd = offset + piece.text.length;
17321
+ if (offset < start) before.push({
17322
+ ...piece,
17323
+ text: piece.text.slice(0, Math.min(start, pieceEnd) - offset)
17324
+ });
17325
+ if (pieceEnd > end) {
17326
+ const skip = Math.max(end - offset, 0);
17327
+ after.push({
17328
+ text: piece.text.slice(skip),
17329
+ originalOffset: piece.originalOffset === void 0 ? void 0 : piece.originalOffset + skip
17330
+ });
17331
+ }
17332
+ offset = pieceEnd;
17333
+ }
17334
+ this.pieces = [
17335
+ ...before,
17336
+ ...text ? [{ text }] : [],
17337
+ ...after
17338
+ ];
17339
+ }
17340
+ replacements(document) {
17341
+ const edits = [];
17342
+ let cursor = 0;
17343
+ let pending = "";
17344
+ const flush = (end) => {
17345
+ let start = cursor;
17346
+ let replacement = pending;
17347
+ while (start < end && replacement && this.original[start] === replacement[0]) {
17348
+ start++;
17349
+ replacement = replacement.slice(1);
17350
+ }
17351
+ while (end > start && replacement && this.original[end - 1] === replacement.at(-1)) {
17352
+ end--;
17353
+ replacement = replacement.slice(0, -1);
17354
+ }
17355
+ if (start !== end || replacement) {
17356
+ const from = document.positionAt(start);
17357
+ const to = document.positionAt(end);
17358
+ edits.push({
17359
+ range: {
17360
+ startLine: from.line,
17361
+ startChar: from.character,
17362
+ endLine: to.line,
17363
+ endChar: to.character
17364
+ },
17365
+ newText: replacement
17366
+ });
17367
+ }
17368
+ pending = "";
17369
+ };
17370
+ for (const piece of this.pieces) if (piece.originalOffset === void 0) pending += piece.text;
17371
+ else {
17372
+ flush(piece.originalOffset);
17373
+ cursor = piece.originalOffset + piece.text.length;
17374
+ }
17375
+ flush(this.original.length);
17376
+ return edits;
17377
+ }
17378
+ };
17068
17379
  function isRecord$2(value) {
17069
17380
  return value !== null && typeof value === "object" && !Array.isArray(value);
17070
17381
  }
@@ -17128,6 +17439,36 @@ function collectPropertyRenames(previousGraph, nextGraph, patches) {
17128
17439
  }
17129
17440
  return renames;
17130
17441
  }
17442
+ /** Recover authored IDs and discard retained implicit runtime IDs using the source. */
17443
+ function recoverSourceNodeIds(graph, previousGraph, source, authoredIdChanges = /* @__PURE__ */ new Set()) {
17444
+ const previousById = new Map(previousGraph.nodes.map((node) => [node.id, node]));
17445
+ const defaultIds = /* @__PURE__ */ new Map();
17446
+ const implicitIds = /* @__PURE__ */ new Map();
17447
+ const authoredIds = /* @__PURE__ */ new Map();
17448
+ const resolve = (id) => {
17449
+ const cached = defaultIds.get(id);
17450
+ if (cached !== void 0) return cached;
17451
+ const node = previousById.get(id);
17452
+ const authoredId = findNodeAtLocation(source, [...node.parentId ? [...statePath(previousGraph, id), node.data.key] : [], "id"]);
17453
+ const implicitId = node.parentId ? `${resolve(node.parentId)}.${node.data.key}` : "(machine)";
17454
+ const defaultId = !node.parentId && authoredId ? getNodeValue(authoredId) : implicitId;
17455
+ defaultIds.set(id, defaultId);
17456
+ if (authoredId) authoredIds.set(id, getNodeValue(authoredId));
17457
+ else implicitIds.set(id, implicitId);
17458
+ return defaultId;
17459
+ };
17460
+ for (const node of previousGraph.nodes) resolve(node.id);
17461
+ const updates = /* @__PURE__ */ new Map();
17462
+ for (const node of graph.nodes) {
17463
+ if (authoredIdChanges.has(node.id)) continue;
17464
+ if (node.data.nodeId == null && authoredIds.has(node.id)) updates.set(node.id, authoredIds.get(node.id));
17465
+ else if (node.data.nodeId != null && node.data.nodeId === implicitIds.get(node.id)) updates.set(node.id, void 0);
17466
+ }
17467
+ if (!updates.size) return graph;
17468
+ const normalized = structuredClone(graph);
17469
+ for (const node of normalized.nodes) if (updates.has(node.id)) node.data.nodeId = updates.get(node.id);
17470
+ return normalized;
17471
+ }
17131
17472
  function onlyTargetChanged(previous, next) {
17132
17473
  if (typeof previous.target !== "string" || typeof next.target !== "string") return false;
17133
17474
  const { target: _previousTarget, ...previousRest } = previous;
@@ -17148,12 +17489,24 @@ function formattingOptions(text) {
17148
17489
  };
17149
17490
  }
17150
17491
  /** Apply graph-level semantic changes to the source JSON tree, not to an export. */
17151
- function preserveXStateJson(document, previousGraph, nextGraph, machineConfig, patches, preserveStatelyMetadata) {
17492
+ function preserveXStateJson(document, previousGraph, nextGraph, machineConfig, patches, preserveStatelyMetadata, authoredIdChanges) {
17152
17493
  if (!isGraph$1(previousGraph) || !isGraph$1(nextGraph) || !isRecord$2(machineConfig)) return null;
17153
17494
  const original = document.getText();
17154
17495
  let text = original;
17155
- const previousConfig = preserveStatelyMetadata ? graphToMachineConfig(previousGraph) : stripStatelyMetadata(graphToMachineConfig(previousGraph));
17156
- const nextConfig = preserveStatelyMetadata ? machineConfig : stripStatelyMetadata(machineConfig);
17496
+ const preservedText = new PreservedText(original);
17497
+ const replace = (start, end, value) => {
17498
+ preservedText.replace(start, end, value);
17499
+ text = text.slice(0, start) + value + text.slice(end);
17500
+ };
17501
+ const originalTree = parseTree(original, [], { allowTrailingComma: true });
17502
+ if (!originalTree) return null;
17503
+ const normalizedPrevious = recoverSourceNodeIds(previousGraph, previousGraph, originalTree);
17504
+ const previousIds = new Map(previousGraph.nodes.map((node) => [node.id, node.data.nodeId ?? null]));
17505
+ const intentionalIds = authoredIdChanges ?? nextGraph.nodes.filter((node) => previousIds.has(node.id) && previousIds.get(node.id) !== (node.data.nodeId ?? null)).map((node) => node.id);
17506
+ const normalizedNext = recoverSourceNodeIds(nextGraph, previousGraph, originalTree, new Set(intentionalIds));
17507
+ const previousConfig = preserveStatelyMetadata ? graphToMachineConfig(normalizedPrevious) : stripStatelyMetadata(graphToMachineConfig(normalizedPrevious));
17508
+ const nextMachineConfig = normalizedNext === nextGraph ? machineConfig : graphToMachineConfig(normalizedNext);
17509
+ const nextConfig = preserveStatelyMetadata ? nextMachineConfig : stripStatelyMetadata(nextMachineConfig);
17157
17510
  const renames = collectPropertyRenames(previousGraph, nextGraph, patches);
17158
17511
  const treeAt = (path) => {
17159
17512
  const errors = [];
@@ -17163,12 +17516,39 @@ function preserveXStateJson(document, previousGraph, nextGraph, machineConfig, p
17163
17516
  };
17164
17517
  if (!treeAt([])) return null;
17165
17518
  const setValue = (path, value) => {
17166
- text = applyEdits(text, modify(text, path, value, { formattingOptions: formattingOptions(text) }));
17519
+ let containerPath = path.slice(0, -1);
17520
+ let container = treeAt(containerPath);
17521
+ while (!container && containerPath.length) {
17522
+ containerPath = containerPath.slice(0, -1);
17523
+ container = treeAt(containerPath);
17524
+ }
17525
+ const compact = container && !text.slice(container.offset, container.offset + container.length).includes("\n");
17526
+ const edits = modify(text, path, value, { ...compact ? {} : { formattingOptions: formattingOptions(text) } });
17527
+ for (const edit of [...edits].sort((a, b) => b.offset - a.offset)) {
17528
+ let content = edit.content;
17529
+ if (compact && container) {
17530
+ const authored = text.slice(container.offset, container.offset + container.length);
17531
+ const colonSpace = authored.match(/"[^"\r\n]+"\s*:([ \t]*)/)?.[1] ?? "";
17532
+ const commaSpace = authored.match(/,([ \t]*)/)?.[1] ?? "";
17533
+ const scanner = createScanner(content, false);
17534
+ const insertions = [];
17535
+ for (let token = scanner.scan(); token !== 17; token = scanner.scan()) {
17536
+ const spacing = token === 6 ? colonSpace : token === 5 ? commaSpace : "";
17537
+ const offset = scanner.getTokenOffset() + scanner.getTokenLength();
17538
+ if (spacing && !/\s/.test(content[offset] ?? "")) insertions.push({
17539
+ offset,
17540
+ text: spacing
17541
+ });
17542
+ }
17543
+ for (const insertion of insertions.reverse()) content = content.slice(0, insertion.offset) + insertion.text + content.slice(insertion.offset);
17544
+ }
17545
+ replace(edit.offset, edit.offset + edit.length, content);
17546
+ }
17167
17547
  };
17168
17548
  const renameKey = (path, oldKey, newKey) => {
17169
17549
  const keyNode = treeAt([...path, oldKey])?.parent?.children?.[0];
17170
17550
  if (!keyNode || keyNode.type !== "string") return false;
17171
- text = `${text.slice(0, keyNode.offset)}${JSON.stringify(newKey)}${text.slice(keyNode.offset + keyNode.length)}`;
17551
+ replace(keyNode.offset, keyNode.offset + keyNode.length, JSON.stringify(newKey));
17172
17552
  return true;
17173
17553
  };
17174
17554
  const removePropertyWithoutFormatting = (path, key) => {
@@ -17187,14 +17567,14 @@ function preserveXStateJson(document, previousGraph, nextGraph, machineConfig, p
17187
17567
  const lineStart = text.lastIndexOf("\n", property.offset - 1) + 1;
17188
17568
  const nextLineStart = text.lastIndexOf("\n", next.offset - 1) + 1;
17189
17569
  if (nextLineStart <= lineStart || text.slice(lineStart, property.offset).trim() !== "" || !/^\s*,\s*$/.test(text.slice(propertyEnd, nextLineStart))) return false;
17190
- text = `${text.slice(0, lineStart)}${text.slice(nextLineStart)}`;
17570
+ replace(lineStart, nextLineStart, "");
17191
17571
  return true;
17192
17572
  }
17193
17573
  const previous = properties[index - 1];
17194
17574
  if (!previous || trailingLine.trim() !== "") return false;
17195
17575
  const previousEnd = previous.offset + previous.length;
17196
17576
  if (!/^\s*,\s*$/.test(text.slice(previousEnd, property.offset))) return false;
17197
- text = `${text.slice(0, previousEnd)}${text.slice(propertyEnd)}`;
17577
+ replace(previousEnd, propertyEnd, "");
17198
17578
  return true;
17199
17579
  };
17200
17580
  const removeArrayItem = (path, index) => {
@@ -17218,7 +17598,7 @@ function preserveXStateJson(document, previousGraph, nextGraph, machineConfig, p
17218
17598
  const nextLineStart = next && text.lastIndexOf("\n", next.offset - 1) + 1;
17219
17599
  const separatorOnNextLine = itemLineEnd >= 0 && nextLineStart !== void 0 && /^[ \t]*,[ \t]*\r?\n$/.test(text.slice(itemLineEnd + 1, nextLineStart));
17220
17600
  if (next && next.offset > lineEnd && (/^\s*,\s*\/\//.test(inlineSuffix) || /^\s*\/\//.test(inlineSuffix) && separatorOnNextLine)) {
17221
- text = `${text.slice(0, itemLineStart)}${text.slice(separatorOnNextLine ? nextLineStart : itemLineEnd < 0 ? lineEnd : lineEnd + 1)}`;
17601
+ replace(itemLineStart, separatorOnNextLine ? nextLineStart : itemLineEnd < 0 ? lineEnd : lineEnd + 1, "");
17222
17602
  return true;
17223
17603
  }
17224
17604
  } else {
@@ -17228,7 +17608,7 @@ function preserveXStateJson(document, previousGraph, nextGraph, machineConfig, p
17228
17608
  const withoutComma = beforeItem.replace(/^(\s*),/, "$1");
17229
17609
  if (previousEnd !== void 0 && itemLineEnd >= 0 && itemLineStart > previousEnd && withoutComma !== beforeItem && stripComments(withoutComma).trim() === "") {
17230
17610
  const separator = /^\s*$/.test(withoutComma) ? formattingOptions(text).eol : withoutComma;
17231
- text = `${text.slice(0, previousEnd)}${separator}${text.slice(itemLineEnd + 1)}`;
17611
+ replace(previousEnd, itemLineEnd + 1, separator);
17232
17612
  return true;
17233
17613
  }
17234
17614
  }
@@ -17255,7 +17635,7 @@ function preserveXStateJson(document, previousGraph, nextGraph, machineConfig, p
17255
17635
  end = nextLineStart;
17256
17636
  } else if (!text.slice(end, nextItem.offset).includes("\n") && text.slice(end, nextItem.offset).trim() === "") end = nextItem.offset;
17257
17637
  } else end = item.offset + item.length;
17258
- text = `${text.slice(0, start)}${text.slice(end)}`;
17638
+ replace(start, end, "");
17259
17639
  return true;
17260
17640
  };
17261
17641
  const appendArrayItemWithoutFormatting = (path, value) => {
@@ -17275,7 +17655,7 @@ function preserveXStateJson(document, previousGraph, nextGraph, machineConfig, p
17275
17655
  if (indentation.trim() !== "") return false;
17276
17656
  separator = `,${formattingOptions(text).eol}${indentation}`;
17277
17657
  }
17278
- text = `${text.slice(0, lastEnd)}${separator}${serialized}${text.slice(lastEnd)}`;
17658
+ replace(lastEnd, lastEnd, separator + serialized);
17279
17659
  return true;
17280
17660
  };
17281
17661
  const authoredBranchText = (path, index) => {
@@ -17292,6 +17672,39 @@ function preserveXStateJson(document, previousGraph, nextGraph, machineConfig, p
17292
17672
  }
17293
17673
  return `${leadingText}${text.slice(branchNode.offset, branchNode.offset + branchNode.length)}`;
17294
17674
  };
17675
+ const actionOrder = (previous, next, path) => {
17676
+ const group = path.at(-1);
17677
+ if (group !== "entry" && group !== "exit" && group !== "actions") return void 0;
17678
+ const owner = nextGraph.nodes.filter((node) => {
17679
+ return (node.parentId ? [...statePath(nextGraph, node.id), node.data.key] : []).every((key, index) => path[index] === key);
17680
+ }).sort((a, b) => (statePath(nextGraph, b.id)?.length ?? 0) - (statePath(nextGraph, a.id)?.length ?? 0))[0];
17681
+ if (!owner) return void 0;
17682
+ const previousNode = previousGraph.nodes.find((node) => node.id === owner.id);
17683
+ const serialize = (actions) => actions.map(({ type, params }) => ({
17684
+ type,
17685
+ ...params && Object.keys(params).length ? { params } : {}
17686
+ }));
17687
+ const normalized = (values) => values.map((value) => typeof value === "string" ? { type: value } : value);
17688
+ const ownerPath = owner.parentId ? [...statePath(nextGraph, owner.id), owner.data.key] : [];
17689
+ const transitionPath = path.slice(ownerPath.length);
17690
+ const event = transitionPath[0] === "on" ? transitionPath[1] : transitionPath[0] === "always" ? "" : void 0;
17691
+ const matchingEdges = nextGraph.edges.filter((edge) => edge.sourceId === owner.id && (event === void 0 || edge.data.eventType === event));
17692
+ const branchIndex = typeof path.at(-2) === "number" ? path.at(-2) : 0;
17693
+ const edgeId = event === void 0 ? void 0 : matchingEdges[branchIndex]?.id;
17694
+ const matches = (group === "actions" ? previousGraph.edges.filter((edge) => edge.sourceId === owner.id && (event === void 0 || edge.id === edgeId)).map((edge) => ({
17695
+ previous: edge.data.actions ?? [],
17696
+ next: nextGraph.edges.find((candidate) => candidate.id === edge.id)?.data.actions ?? []
17697
+ })) : [{
17698
+ previous: previousNode?.data[group] ?? [],
17699
+ next: owner.data[group] ?? []
17700
+ }]).filter((candidate) => sameValue(serialize(candidate.previous), normalized(previous)) && sameValue(serialize(candidate.next), normalized(next)));
17701
+ if (matches.length !== 1) return void 0;
17702
+ const candidate = matches[0];
17703
+ const ids = candidate.previous.map((action) => action.id);
17704
+ if (ids.some((id) => !id) || new Set(ids).size !== ids.length) return void 0;
17705
+ const order = candidate.next.map((action) => ids.indexOf(action.id));
17706
+ return candidate.next.every((action) => action.id) ? order : void 0;
17707
+ };
17295
17708
  const visit = (previous, next, path) => {
17296
17709
  if (sameValue(previous, next)) return true;
17297
17710
  const sourceNode = treeAt(path);
@@ -17311,10 +17724,15 @@ function preserveXStateJson(document, previousGraph, nextGraph, machineConfig, p
17311
17724
  const authoredAction = Object.keys(next).length === 1 ? next.type : next;
17312
17725
  if (text.slice(sourceNode.offset + 1, sourceNode.offset + sourceNode.length - 1) === "") {
17313
17726
  const insertion = sourceNode.offset + 1;
17314
- text = `${text.slice(0, insertion)}${JSON.stringify(authoredAction)}${text.slice(insertion)}`;
17727
+ replace(insertion, insertion, JSON.stringify(authoredAction));
17315
17728
  } else setValue(path, [authoredAction]);
17316
17729
  return true;
17317
17730
  }
17731
+ if (sourceNode?.type === "array" && sourceNode.children?.length === 1 && isRecord$2(previous) && Array.isArray(next) && [
17732
+ "entry",
17733
+ "exit",
17734
+ "actions"
17735
+ ].includes(String(path.at(-1)))) return visit([previous], next, path);
17318
17736
  if (sourceNode?.type === "array" && sourceNode.children?.length === 1 && isRecord$2(previous) && isRecord$2(next)) return visit(previous, next, [...path, 0]);
17319
17737
  if (isRecord$2(previous) && isRecord$2(next) && sourceNode?.type === "object") {
17320
17738
  const handledOld = /* @__PURE__ */ new Set();
@@ -17375,14 +17793,14 @@ function preserveXStateJson(document, previousGraph, nextGraph, machineConfig, p
17375
17793
  index
17376
17794
  ]);
17377
17795
  if (!insertedNode) return false;
17378
- text = `${text.slice(0, insertedNode.offset)}${authoredText}${text.slice(insertedNode.offset + insertedNode.length)}`;
17796
+ replace(insertedNode.offset, insertedNode.offset + insertedNode.length, authoredText);
17379
17797
  }
17380
17798
  continue;
17381
17799
  }
17382
17800
  if (!visit(previous[key], next[key], [...path, key])) return false;
17383
17801
  } else {
17384
17802
  const existing = key === "actions" ? treeAt([...path, key]) : void 0;
17385
- if (existing?.type === "array") text = `${text.slice(0, existing.offset)}[]${text.slice(existing.offset + existing.length)}`;
17803
+ if (existing?.type === "array") replace(existing.offset, existing.offset + existing.length, "[]");
17386
17804
  else if ((key === "entry" || key === "exit") && removePropertyWithoutFormatting(path, key)) {} else setValue([...path, key], void 0);
17387
17805
  }
17388
17806
  }
@@ -17395,11 +17813,33 @@ function preserveXStateJson(document, previousGraph, nextGraph, machineConfig, p
17395
17813
  if (originalBranch !== void 0) {
17396
17814
  const insertedNode = treeAt([...path, key]);
17397
17815
  if (!insertedNode) return false;
17398
- text = `${text.slice(0, insertedNode.offset)}${originalBranch}${text.slice(insertedNode.offset + insertedNode.length)}`;
17816
+ replace(insertedNode.offset, insertedNode.offset + insertedNode.length, originalBranch);
17399
17817
  }
17400
17818
  }
17401
17819
  return true;
17402
17820
  }
17821
+ if (Array.isArray(previous) && sourceNode?.type === "array" && (Array.isArray(next) || isRecord$2(next))) {
17822
+ const nextValues = Array.isArray(next) ? next : [next];
17823
+ const order = actionOrder(previous, nextValues, path);
17824
+ const pureDeletion = previous.length === nextValues.length + 1 && previous.some((_, index) => sameValue(previous.filter((_, candidate) => candidate !== index), nextValues));
17825
+ const lastSourceItem = sourceNode.children?.at(-1);
17826
+ const sourceSuffix = lastSourceItem ? text.slice(lastSourceItem.offset + lastSourceItem.length, sourceNode.offset + sourceNode.length - 1) : "";
17827
+ const pureAppend = !(stripComments(sourceSuffix) !== sourceSuffix) && order && nextValues.length > previous.length && order.slice(0, previous.length).every((oldIndex, index) => oldIndex === index);
17828
+ if (order && !pureDeletion && !pureAppend && (previous.length !== nextValues.length || order.some((oldIndex, index) => oldIndex !== index))) {
17829
+ for (const [index, oldIndex] of order.entries()) if (oldIndex >= 0 && !visit(previous[oldIndex], nextValues[index], [...path, oldIndex])) return false;
17830
+ const array = treeAt(path);
17831
+ if (!array) return false;
17832
+ const inserted = new Map(order.flatMap((oldIndex, index) => {
17833
+ if (oldIndex >= 0) return [];
17834
+ const value = nextValues[index];
17835
+ return [[index, JSON.stringify(value)]];
17836
+ }));
17837
+ const edit = reorderedActionArray(text, array, order, inserted);
17838
+ if (!edit) return false;
17839
+ replace(edit.start, edit.end, edit.text);
17840
+ return true;
17841
+ }
17842
+ }
17403
17843
  if (Array.isArray(previous) && isRecord$2(next) && sourceNode?.type === "array") {
17404
17844
  if (previous.length === 1 && isRecord$2(previous[0])) return visit(previous[0], next, [...path, 0]);
17405
17845
  const matches = previous.flatMap((value, index) => sameValue(value, next) ? [index] : []);
@@ -17431,25 +17871,7 @@ function preserveXStateJson(document, previousGraph, nextGraph, machineConfig, p
17431
17871
  parseTree(text, errors, { allowTrailingComma: true });
17432
17872
  if (errors.length) return null;
17433
17873
  if (text === original) return [];
17434
- let start = 0;
17435
- while (start < original.length && start < text.length && original[start] === text[start]) start++;
17436
- let oldEnd = original.length;
17437
- let newEnd = text.length;
17438
- while (oldEnd > start && newEnd > start && original[oldEnd - 1] === text[newEnd - 1]) {
17439
- oldEnd--;
17440
- newEnd--;
17441
- }
17442
- const startPosition = document.positionAt(start);
17443
- const endPosition = document.positionAt(oldEnd);
17444
- return [{
17445
- range: {
17446
- startLine: startPosition.line,
17447
- startChar: startPosition.character,
17448
- endLine: endPosition.line,
17449
- endChar: endPosition.character
17450
- },
17451
- newText: text.slice(start, newEnd)
17452
- }];
17874
+ return preservedText.replacements(document);
17453
17875
  }
17454
17876
  const xstateAdapter = {
17455
17877
  id: "xstate",
@@ -17538,7 +17960,7 @@ const xstateJsonAdapter = {
17538
17960
  if (options?.sourceUpdateStrategy === "rewrite") return createSerializedDocumentReplacement(document, options, "xstate-json");
17539
17961
  const patches = options?.preserveStatelyMetadata === false ? stripPresentationPatches(options.patches ?? []) : options?.patches ?? [];
17540
17962
  if (!options || !patches.length) return [];
17541
- return preserveXStateJson(document, options.prevGraph, options.nextGraph, machineConfig, patches, options.preserveStatelyMetadata ?? true) ?? [];
17963
+ return preserveXStateJson(document, options.prevGraph, options.nextGraph, machineConfig, patches, options.preserveStatelyMetadata ?? true, options.authoredIdChanges) ?? [];
17542
17964
  }
17543
17965
  };
17544
17966
  const asyncAdapter = createSerializedExportAdapter("async", (document, text) => isJavaScriptOrTypeScriptFile(document.fileName) && looksLikeAsyncWorkflow(text));
@@ -17821,7 +18243,7 @@ function diagnosticKey$1(diagnostic) {
17821
18243
  }
17822
18244
 
17823
18245
  //#endregion
17824
- //#region ../editor-sync/dist/localBackend-M-YkjWse.mjs
18246
+ //#region ../editor-sync/dist/localBackend-DH1qEsOz.mjs
17825
18247
  var LocalEditorSyncBackend = class {
17826
18248
  constructor(access) {
17827
18249
  this.access = access;
@@ -17960,7 +18382,7 @@ function toFileName(uriOrPath) {
17960
18382
  }
17961
18383
 
17962
18384
  //#endregion
17963
- //#region ../editor-sync/dist/pushMerge-BTyVbmbW.mjs
18385
+ //#region ../editor-sync/dist/pushMerge-piN5M1Ej.mjs
17964
18386
  const GEOMETRY_KEYS = [
17965
18387
  "x",
17966
18388
  "y",
@@ -17977,6 +18399,36 @@ const DEFAULT_ANNOTATION_SIZE = {
17977
18399
  };
17978
18400
  const NEW_NODE_GAP = 80;
17979
18401
  const MAX_CONCURRENT_MERGE_ATTEMPTS = 3;
18402
+ /** Match presentation events independently of converter-specific entity IDs. */
18403
+ function presentationMatchingGraph(graph) {
18404
+ const nodes = new Map(graph.nodes.map((node) => [node.id, node]));
18405
+ return {
18406
+ ...graph,
18407
+ edges: graph.edges.map((edge) => {
18408
+ let eventType = edge.data.eventType;
18409
+ if (typeof eventType !== "string") return edge;
18410
+ const sourceSuffix = `.${edge.sourceId}`;
18411
+ if (eventType.startsWith("xstate.after.") && eventType.endsWith(sourceSuffix)) eventType = eventType.slice(0, -sourceSuffix.length);
18412
+ else if (/^(?:@statelyai?\.state\.done\.|xstate\.done\.state(?:\.|$))/.test(eventType)) eventType = "xstate.done.state";
18413
+ else {
18414
+ const ref = /^@statelyai?\.invoke\.(done|error|snapshot|timeout)\.(.+)$/.exec(eventType);
18415
+ const literal = /^xstate\.(done|error|snapshot|timeout)\.actor\.(.+)$/.exec(eventType);
18416
+ const match = ref ?? literal;
18417
+ if (match) {
18418
+ const index = (nodes.get(edge.sourceId)?.data?.invokes ?? []).findIndex((invoke) => invoke.id === match[2] || "invocationId" in invoke && invoke.invocationId === match[2]);
18419
+ if (index >= 0) eventType = `@presentation.invoke.${match[1]}.${index}`;
18420
+ }
18421
+ }
18422
+ return {
18423
+ ...edge,
18424
+ data: {
18425
+ ...edge.data,
18426
+ eventType
18427
+ }
18428
+ };
18429
+ })
18430
+ };
18431
+ }
17980
18432
  function indexBySemanticId(normalized, original) {
17981
18433
  const bySemanticId = /* @__PURE__ */ new Map();
17982
18434
  normalized.forEach((entity, index) => {
@@ -18014,6 +18466,7 @@ function restoreEdgeFields(edges, remoteEdgesById, remoteEdgeIdByLocalId) {
18014
18466
  const remote = remoteId ? remoteEdgesById.get(remoteId) : void 0;
18015
18467
  if (remote) {
18016
18468
  if (remote.uniqueId !== void 0) edge.uniqueId = remote.uniqueId;
18469
+ edge.data.eventTypeData = structuredClone(remote.data.eventTypeData);
18017
18470
  if (remote.data.assets !== void 0) edge.data.assets = structuredClone(remote.data.assets);
18018
18471
  }
18019
18472
  }
@@ -18058,7 +18511,7 @@ function mergePresentationIntoRemoteDefinition(editedDefinition, remoteDefinitio
18058
18511
  remoteGraph.nodes = remoteGraph.nodes.filter((node) => !remoteAnnotationIds.has(node.id));
18059
18512
  editedGraph.edges = editedGraph.edges.filter((edge) => !editedAnnotationIds.has(edge.sourceId) && !editedAnnotationIds.has(edge.targetId));
18060
18513
  remoteGraph.edges = remoteGraph.edges.filter((edge) => !remoteAnnotationIds.has(edge.sourceId) && !remoteAnnotationIds.has(edge.targetId));
18061
- const { base, compare } = diffGraphsSemantically(remoteGraph, editedGraph);
18514
+ const { base, compare } = diffGraphsSemantically(presentationMatchingGraph(remoteGraph), presentationMatchingGraph(editedGraph));
18062
18515
  const remoteNodesBySemanticId = indexBySemanticId(base.nodes, remoteGraph.nodes);
18063
18516
  const editedNodesBySemanticId = indexBySemanticId(compare.nodes, editedGraph.nodes);
18064
18517
  const remoteEdgesBySemanticId = indexBySemanticId(base.edges, remoteGraph.edges);
@@ -18067,6 +18520,7 @@ function mergePresentationIntoRemoteDefinition(editedDefinition, remoteDefinitio
18067
18520
  const mergedNodesById = collectDigraphNodes(merged.rootNode, /* @__PURE__ */ new Map());
18068
18521
  const editedNodesById = collectDigraphNodes(editedDefinition.rootNode, /* @__PURE__ */ new Map());
18069
18522
  const editedNodeIdToRemoteNodeId = /* @__PURE__ */ new Map();
18523
+ const editedEdgeIdToRemoteEdgeId = /* @__PURE__ */ new Map();
18070
18524
  for (const [semanticId, remoteNode] of remoteNodesBySemanticId) {
18071
18525
  const editedNode = editedNodesBySemanticId.get(semanticId);
18072
18526
  const target = mergedNodesById.get(remoteNode.id);
@@ -18089,6 +18543,7 @@ function mergePresentationIntoRemoteDefinition(editedDefinition, remoteDefinitio
18089
18543
  const target = mergedEdgesById.get(remoteEdge.id);
18090
18544
  const source = editedEdge ? editedEdgesById.get(editedEdge.id) : void 0;
18091
18545
  if (!editedEdge || !target || !source) continue;
18546
+ editedEdgeIdToRemoteEdgeId.set(editedEdge.id, remoteEdge.id);
18092
18547
  for (const field of ["position", "size"]) replacePresentationField(target, source, field);
18093
18548
  for (const field of ["color", "assets"]) replacePresentationField(target.data, source.data, field);
18094
18549
  }
@@ -18097,7 +18552,10 @@ function mergePresentationIntoRemoteDefinition(editedDefinition, remoteDefinitio
18097
18552
  collectAnnotations(editedDefinition.rootNode, null, editedAnnotations);
18098
18553
  for (const annotation of editedAnnotations) {
18099
18554
  const remoteParentId = annotation.parentId ? editedNodeIdToRemoteNodeId.get(annotation.parentId) : merged.rootNode.id;
18100
- (remoteParentId ? mergedNodesById.get(remoteParentId) : void 0)?.nodes.push(structuredClone(annotation.node));
18555
+ const parent = remoteParentId ? mergedNodesById.get(remoteParentId) : void 0;
18556
+ const note = structuredClone(annotation.node);
18557
+ if (Array.isArray(note.data.data?.referenceIds)) note.data.data.referenceIds = note.data.data.referenceIds.map((id) => editedNodeIdToRemoteNodeId.get(id) ?? editedEdgeIdToRemoteEdgeId.get(id) ?? id);
18558
+ parent?.nodes.push(note);
18101
18559
  }
18102
18560
  return merged;
18103
18561
  }
@@ -18141,7 +18599,7 @@ function mergeLocalGraphIntoRemoteDefinition(localGraph, remoteDefinition) {
18141
18599
  const remoteGraph = fromStudioMachine(remoteDefinition);
18142
18600
  remoteGraph.nodes = remoteGraph.nodes.filter((node) => !annotationIds.has(node.id));
18143
18601
  remoteGraph.edges = remoteGraph.edges.filter((edge) => !annotationIds.has(edge.sourceId) && !annotationIds.has(edge.targetId));
18144
- const { base, compare, summary } = diffGraphsSemantically(remoteGraph, localGraph);
18602
+ const { base, compare, summary } = diffGraphsSemantically(presentationMatchingGraph(remoteGraph), presentationMatchingGraph(localGraph));
18145
18603
  const remoteNodesBySemanticId = indexBySemanticId(base.nodes, remoteGraph.nodes);
18146
18604
  const remoteEdgesBySemanticId = indexBySemanticId(base.edges, remoteGraph.edges);
18147
18605
  const merged = structuredClone(localGraph);
@@ -18165,7 +18623,10 @@ function mergeLocalGraphIntoRemoteDefinition(localGraph, remoteDefinition) {
18165
18623
  });
18166
18624
  const definition = toStudioMachine(merged);
18167
18625
  restoreNodeFields(definition.rootNode, remoteNodesById, remoteNodeIdByLocalId);
18168
- restoreEdgeFields(definition.edges, new Map(remoteDefinition.edges.map((edge) => [edge.id, edge])), remoteEdgeIdByLocalId);
18626
+ restoreEdgeFields(definition.edges, new Map(remoteDefinition.edges.map((edge) => [edge.id, edge])), new Map(definition.edges.flatMap((edge, index) => {
18627
+ const remoteId = remoteEdgeIdByLocalId.get(merged.edges[index].id);
18628
+ return remoteId ? [[edge.id, remoteId]] : [];
18629
+ })));
18169
18630
  ensureCompleteNodeGeometry(definition.rootNode);
18170
18631
  return {
18171
18632
  definition: {
@@ -18230,7 +18691,7 @@ async function updateMachineDefinition(client, machineId, localGraph) {
18230
18691
  }
18231
18692
 
18232
18693
  //#endregion
18233
- //#region ../editor-sync/dist/presentationMetadata-DH591jIC.mjs
18694
+ //#region ../editor-sync/dist/presentationMetadata-YX6iRP4T.mjs
18234
18695
  function createStudioPresentationMetadataStore(client, machineId) {
18235
18696
  const load = async () => {
18236
18697
  return {
@@ -18249,7 +18710,7 @@ function createStudioPresentationMetadataStore(client, machineId) {
18249
18710
  },
18250
18711
  async persist(_graph, context) {
18251
18712
  const serialized = context?.serialized?.digraph;
18252
- if (!serialized) return;
18713
+ if (!serialized) throw new Error("Studio presentation requires a serialized digraph. Presentation was not saved.");
18253
18714
  const editedDefinition = JSON.parse(serialized);
18254
18715
  const latest = await client.machines.get(machineId);
18255
18716
  if (!latest.updatedAt) throw new Error(`Studio machine "${machineId}" did not provide an updatedAt guard. Presentation was not saved.`);
@@ -18310,10 +18771,29 @@ function createLocalPresentationMetadataRepository(file) {
18310
18771
  };
18311
18772
  } };
18312
18773
  }
18313
- function applyPresentationInitializer(initializer, current) {
18774
+ function applyPresentationInitializer(initializer, current, convertStudioDefinition = fromStudioMachine) {
18314
18775
  if (initializer.type === "overlay") return applyPresentationMetadata(initializer.overlay, current);
18315
18776
  if (!isGraph(current)) return current;
18316
- return fromStudioMachine(mergeLocalGraphIntoRemoteDefinition(current, initializer.definition).definition);
18777
+ const presentationGraph = convertStudioDefinition(initializer.definition);
18778
+ if (!isGraph(presentationGraph)) return current;
18779
+ const overlay = createPresentationSnapshot(presentationGraph);
18780
+ const sourceIds = presentationIds(current);
18781
+ const studioIds = presentationIds(presentationGraph);
18782
+ const liveIds = /* @__PURE__ */ new Map();
18783
+ for (const kind of ["nodes", "edges"]) {
18784
+ const bySemanticId = new Map(current[kind].map((entity, index) => [sourceIds[kind][index], entity.id]));
18785
+ presentationGraph[kind].forEach((entity, index) => {
18786
+ const liveId = bySemanticId.get(studioIds[kind][index]);
18787
+ if (typeof liveId === "string") liveIds.set(entity.id, liveId);
18788
+ });
18789
+ }
18790
+ const sourceEntityIds = new Set([...current.nodes, ...current.edges].map((entity) => entity.id));
18791
+ overlay.annotations = (overlay.annotations ?? []).filter((annotation) => annotation.parentId == null || liveIds.has(annotation.parentId));
18792
+ for (const annotation of overlay.annotations) {
18793
+ annotation.parentId = liveIds.get(annotation.parentId) ?? annotation.parentId;
18794
+ if (Array.isArray(annotation.data?.referenceIds)) annotation.data.referenceIds = annotation.data.referenceIds.map((id) => liveIds.get(id) ?? id).filter((id) => sourceEntityIds.has(id));
18795
+ }
18796
+ return applyPresentationMetadata(overlay, current);
18317
18797
  }
18318
18798
  function applyPresentationMetadata(overlay, current) {
18319
18799
  if (!isPresentationOverlay(overlay) || !isGraph(current)) return current;
@@ -18321,6 +18801,7 @@ function applyPresentationMetadata(overlay, current) {
18321
18801
  const addedNodes = [];
18322
18802
  const addedEdges = [];
18323
18803
  const semanticIds = overlay.identity === "semantic-v1" ? presentationIds(result) : void 0;
18804
+ const legacyIds = semanticIds ? presentationIds(result, false) : void 0;
18324
18805
  const renamedGuardPresentation = semanticIds ? matchUniqueGuardRenames(semanticIds.edges, overlay.edges ?? {}) : /* @__PURE__ */ new Map();
18325
18806
  for (const [index, node] of result.nodes.entries()) {
18326
18807
  normalizeCanonicalGeometry(node);
@@ -18331,7 +18812,7 @@ function applyPresentationMetadata(overlay, current) {
18331
18812
  for (const [index, edge] of result.edges.entries()) {
18332
18813
  normalizeCanonicalGeometry(edge);
18333
18814
  const semanticId = semanticIds?.edges[index] ?? edge.id;
18334
- const presentation = overlay.edges?.[semanticId] ?? renamedGuardPresentation.get(semanticId);
18815
+ const presentation = overlay.edges?.[semanticId] ?? overlay.edges?.[legacyIds?.edges[index] ?? edge.id] ?? renamedGuardPresentation.get(semanticId);
18335
18816
  if (presentation) applyEntityPresentation(presentation, edge);
18336
18817
  else addedEdges.push(edge);
18337
18818
  }
@@ -18411,6 +18892,7 @@ function entityPresentationMap(entities, includeSourceSide, ids) {
18411
18892
  const presentation = compactDefined({
18412
18893
  ...pick(canonicalGeometry(entity), GEOMETRY_FIELDS),
18413
18894
  color: clone(entity.data?.color),
18895
+ layout: clone(entity.data?.layout),
18414
18896
  assets: clone(entity.data?.assets),
18415
18897
  sourceSide: includeSourceSide ? clone(entity.data?.sourceSide) : void 0
18416
18898
  });
@@ -18418,7 +18900,8 @@ function entityPresentationMap(entities, includeSourceSide, ids) {
18418
18900
  });
18419
18901
  return entries.length > 0 ? Object.fromEntries(entries) : void 0;
18420
18902
  }
18421
- function presentationIds(graph) {
18903
+ function presentationIds(graph, canonicalEvents = true) {
18904
+ const matchingGraph = canonicalEvents ? presentationMatchingGraph(graph) : graph;
18422
18905
  const normalized = normalizeGraphForSemanticDiff({
18423
18906
  id: graph.id ?? "presentation",
18424
18907
  nodes: graph.nodes.map((node) => ({
@@ -18426,7 +18909,7 @@ function presentationIds(graph) {
18426
18909
  parentId: node.parentId,
18427
18910
  data: { key: node.data?.key }
18428
18911
  })),
18429
- edges: graph.edges.map((edge) => ({
18912
+ edges: matchingGraph.edges.map((edge) => ({
18430
18913
  id: edge.id,
18431
18914
  sourceId: edge.sourceId,
18432
18915
  targetId: edge.targetId,
@@ -18459,10 +18942,12 @@ function createAnnotationSnapshot(annotations) {
18459
18942
  parentId: annotation.parentId,
18460
18943
  content: clone(annotation.content),
18461
18944
  ...pick(geometry, GEOMETRY_FIELDS),
18945
+ ...pick(annotation, ["width", "height"]),
18462
18946
  data: compactDefined({
18463
18947
  referenceIds: clone(annotation.data?.referenceIds),
18464
18948
  color: clone(annotation.data?.color),
18465
- textAlign: clone(annotation.data?.textAlign)
18949
+ textAlign: clone(annotation.data?.textAlign),
18950
+ placement: clone(annotation.data?.placement)
18466
18951
  })
18467
18952
  });
18468
18953
  });
@@ -18481,7 +18966,8 @@ function applyEntityPresentation(presentation, entity) {
18481
18966
  for (const field of [
18482
18967
  "color",
18483
18968
  "assets",
18484
- "sourceSide"
18969
+ "sourceSide",
18970
+ "layout"
18485
18971
  ]) copyField(presentation, entity.data, field);
18486
18972
  }
18487
18973
  function placeAddedNodes(added, allNodes) {
@@ -19004,7 +19490,7 @@ const editorSyncApplyResponseSchema = z$1.object({ replacements: z$1.array(edito
19004
19490
  const editorSyncAccessResponseSchema = z$1.object({ access: editorSyncAccessSchema });
19005
19491
 
19006
19492
  //#endregion
19007
- //#region ../editor-sync/dist/host-THez2yXn.mjs
19493
+ //#region ../editor-sync/dist/host-BX7mLyHj.mjs
19008
19494
  let diagnosticSequence = 0;
19009
19495
  function presentationsEqual(first, second) {
19010
19496
  return JSON.stringify(createPresentationSnapshot(first)) === JSON.stringify(createPresentationSnapshot(second));
@@ -19031,6 +19517,12 @@ var EditorSyncError = class extends Error {
19031
19517
  this.code = code;
19032
19518
  }
19033
19519
  };
19520
+ function isEditorSyncAuthenticationError(error) {
19521
+ return error instanceof Error && "status" in error && error.status === 401;
19522
+ }
19523
+ function authenticationRecoveryInstructions(recovery) {
19524
+ return recovery.host === "cli" ? "Run `npx statelyai login` in your terminal, then reload the editor." : "In the VS Code Command Palette, run “Stately: Sign In”, then reopen the visual editor.";
19525
+ }
19034
19526
  async function fetchEditorSyncAccess(options) {
19035
19527
  const fetchImpl = options.fetch ?? globalThis.fetch;
19036
19528
  const url = new URL("/api/editor-sync/access", `${options.editorUrl.replace(/\/+$/, "")}/`);
@@ -19124,12 +19616,14 @@ var EditorHostSession = class {
19124
19616
  await this.initialize();
19125
19617
  } catch (error) {
19126
19618
  const message = formatError(error, "Failed to initialize the visual editor.");
19619
+ const recovery = isEditorSyncAuthenticationError(error) ? this.options.authenticationRecovery : void 0;
19127
19620
  this.options.send({
19128
19621
  type: "@statelyai.error",
19129
19622
  code: "initialization_failed",
19130
- message
19623
+ message,
19624
+ ...recovery ? { authenticationRecovery: recovery } : {}
19131
19625
  });
19132
- this.options.showError?.(message);
19626
+ this.options.showError?.(recovery ? `${message}\n${authenticationRecoveryInstructions(recovery)}` : message);
19133
19627
  }
19134
19628
  break;
19135
19629
  case "@statelyai.loaded":
@@ -19330,7 +19824,7 @@ var EditorHostSession = class {
19330
19824
  return;
19331
19825
  }
19332
19826
  const documents = await this.options.source.collectDocuments(sourceLocationUris(this.currentSourceLocations));
19333
- const serialized = await this.getSerializedPayload();
19827
+ const serialized = await this.getSerializedPayload(message.serialized);
19334
19828
  const response = await this.options.sync.apply({
19335
19829
  rootUri: this.options.source.rootUri,
19336
19830
  rootFileName: this.options.source.rootFileName,
@@ -19379,7 +19873,7 @@ var EditorHostSession = class {
19379
19873
  });
19380
19874
  }
19381
19875
  async acknowledgeWithoutSourceChanges(message, serialized) {
19382
- await this.persistPresentation(message.graph, serialized ?? await this.getSerializedPayload());
19876
+ await this.persistPresentation(message.graph, serialized ?? await this.getSerializedPayload(message.serialized));
19383
19877
  this.currentGraph = message.graph;
19384
19878
  this.lastPersistedGraph = message.graph;
19385
19879
  this.options.send({
@@ -19477,11 +19971,12 @@ var EditorHostSession = class {
19477
19971
  this.watchedSourceUris.clear();
19478
19972
  for (const uri of sourceLocationUris(sourceLocations)) if (uri !== this.options.source.rootUri) this.watchedSourceUris.add(uri);
19479
19973
  }
19480
- async getSerializedPayload() {
19974
+ async getSerializedPayload(provided) {
19481
19975
  const format = this.currentFormat;
19482
- if (!format || format === "xstate") return void 0;
19976
+ const formats = [...new Set([...format && format !== "xstate" ? [format] : [], ...this.initialPresentation?.type === "studio" ? ["digraph"] : []])];
19977
+ if (formats.length === 0) return void 0;
19483
19978
  try {
19484
- return { [format]: await this.requestSerialized(format) };
19979
+ return Object.fromEntries(await Promise.all(formats.map(async (format) => [format, provided?.[format] ?? await this.requestSerialized(format)])));
19485
19980
  } catch {
19486
19981
  return;
19487
19982
  }
@@ -19856,14 +20351,31 @@ async function assertSourceSaveMatchesGraph(input) {
19856
20351
  const graph = convertMachineToGraph({
19857
20352
  machine: parsed.machine,
19858
20353
  format,
19859
- ...input.profile ? { profile: input.profile } : {}
20354
+ profile: input.profile ?? (format === "json" ? input.graph.data?.profile : void 0)
19860
20355
  }).graph;
20356
+ if (format === "json" && graph.data.schemas && input.machineConfig?.schemas !== void 0) {
20357
+ const root = graph.nodes.find((node) => !node.parentId);
20358
+ if (root) root.data.schemas = graph.data.schemas;
20359
+ }
19861
20360
  return graphToMachineConfig(graph);
19862
20361
  };
19863
20362
  const expectedConfig = comparableMachineConfig(stripStatelyMetadata(input.machineConfig));
19864
20363
  const actualConfig = comparableMachineConfig(stripStatelyMetadata(projectConfig(documents)));
20364
+ const baselineConfig = comparableMachineConfig(stripStatelyMetadata(projectConfig(input.documents)));
20365
+ const schemaEvents = (config) => config?.schemas?.events;
20366
+ const expectedEvents = schemaEvents(expectedConfig);
20367
+ const eventOrigins = input.graph.data?.schemaMeta?.events;
20368
+ let removedAutoEvent = false;
20369
+ for (const [event, origin] of Object.entries(eventOrigins ?? {})) if (origin.origin === "auto" && expectedEvents && JSON.stringify(expectedEvents[event]) === JSON.stringify(comparableMachineConfig({
20370
+ type: "object",
20371
+ properties: {},
20372
+ required: []
20373
+ })) && !Object.hasOwn(schemaEvents(baselineConfig) ?? {}, event) && !Object.hasOwn(schemaEvents(actualConfig) ?? {}, event)) {
20374
+ delete expectedEvents[event];
20375
+ removedAutoEvent = true;
20376
+ }
20377
+ if (removedAutoEvent && expectedEvents && Object.keys(expectedEvents).length === 0) delete expectedConfig.schemas.events;
19865
20378
  if (expectedConfig && typeof expectedConfig === "object" && !Array.isArray(expectedConfig) && actualConfig && typeof actualConfig === "object" && !Array.isArray(actualConfig) && !("id" in expectedConfig) && "id" in actualConfig) {
19866
- const baselineConfig = comparableMachineConfig(stripStatelyMetadata(projectConfig(input.documents)));
19867
20379
  if (baselineConfig && typeof baselineConfig === "object" && "id" in baselineConfig && actualConfig.id === baselineConfig.id) delete actualConfig.id;
19868
20380
  }
19869
20381
  if (JSON.stringify(expectedConfig) !== JSON.stringify(actualConfig)) throw new Error("The updated source does not match the requested machine.");
@@ -19945,7 +20457,7 @@ function sourceLocationUris(sourceLocations) {
19945
20457
 
19946
20458
  //#endregion
19947
20459
  //#region src/version.ts
19948
- const CLI_VERSION = "0.12.2";
20460
+ const CLI_VERSION = "0.12.4";
19949
20461
 
19950
20462
  //#endregion
19951
20463
  //#region src/cliHost.ts
@@ -19986,11 +20498,21 @@ function rawDataToBuffer(data) {
19986
20498
  var CliEditorSession = class {
19987
20499
  session;
19988
20500
  workspaceWatcher;
20501
+ readyMessage;
20502
+ loginPromise;
20503
+ loginController;
20504
+ disposed = false;
19989
20505
  constructor(options, sendMessage) {
19990
20506
  this.options = options;
20507
+ this.sendMessage = sendMessage;
20508
+ this.workspaceWatcher = createSharedWorkspaceWatcher(options.rootDir);
20509
+ this.session = this.createSession();
20510
+ }
20511
+ createSession() {
20512
+ const options = this.options;
19991
20513
  const send = (message) => {
19992
20514
  this.log("send", message.type, message);
19993
- sendMessage(message);
20515
+ this.sendMessage(message);
19994
20516
  };
19995
20517
  const machines = options.machines ?? [{
19996
20518
  id: `${options.rootUri}#0`,
@@ -20004,8 +20526,7 @@ var CliEditorSession = class {
20004
20526
  baseUrl: options.studioUrl,
20005
20527
  apiKey: options.apiKey
20006
20528
  });
20007
- this.workspaceWatcher = createSharedWorkspaceWatcher(options.rootDir);
20008
- let access;
20529
+ let access = options.access ? Promise.resolve(options.access) : void 0;
20009
20530
  const getAccess = () => {
20010
20531
  if (!access) {
20011
20532
  const request = fetchEditorSyncAccess({
@@ -20020,7 +20541,11 @@ var CliEditorSession = class {
20020
20541
  }
20021
20542
  return access;
20022
20543
  };
20023
- this.session = new EditorHostWorkspaceSession({
20544
+ return new EditorHostWorkspaceSession({
20545
+ authenticationRecovery: {
20546
+ host: "cli",
20547
+ canLogin: Boolean(options.onLogin)
20548
+ },
20024
20549
  machines: machines.map((machine) => ({
20025
20550
  id: machine.id,
20026
20551
  name: machine.name,
@@ -20056,14 +20581,75 @@ var CliEditorSession = class {
20056
20581
  showError: (message) => console.error(`Stately Editor: ${message}`)
20057
20582
  });
20058
20583
  }
20059
- receiveMessage(message) {
20584
+ async receiveMessage(message) {
20060
20585
  this.log("recv", message.type, message);
20586
+ if (this.disposed) return;
20587
+ if (message.type === "@statelyai.login") {
20588
+ if (!this.readyMessage || !this.options.onLogin) return;
20589
+ this.loginPromise ??= this.login().finally(() => {
20590
+ this.loginPromise = void 0;
20591
+ });
20592
+ return this.loginPromise;
20593
+ }
20594
+ if (this.loginPromise) {
20595
+ if (message.type !== "@statelyai.ready") return this.loginPromise;
20596
+ this.loginController?.abort();
20597
+ await this.loginPromise;
20598
+ if (this.disposed) return;
20599
+ }
20600
+ if (message.type === "@statelyai.ready") {
20601
+ this.readyMessage = message;
20602
+ try {
20603
+ const apiKey = this.options.getApiKey ? await this.options.getApiKey() : this.options.apiKey;
20604
+ if (this.disposed) return;
20605
+ if (apiKey !== this.options.apiKey) this.replaceCredential(apiKey);
20606
+ } catch (error) {
20607
+ this.reportLoginError(error);
20608
+ return;
20609
+ }
20610
+ }
20061
20611
  return this.session.receive(message);
20062
20612
  }
20063
20613
  dispose() {
20614
+ this.disposed = true;
20615
+ this.loginController?.abort();
20064
20616
  this.session.dispose();
20065
20617
  this.workspaceWatcher.dispose();
20066
20618
  }
20619
+ replaceCredential(apiKey) {
20620
+ this.session.dispose();
20621
+ this.options.apiKey = apiKey;
20622
+ this.options.access = void 0;
20623
+ this.session = this.createSession();
20624
+ }
20625
+ async login() {
20626
+ const controller = new AbortController();
20627
+ this.loginController = controller;
20628
+ try {
20629
+ const apiKey = await this.options.onLogin(controller.signal);
20630
+ if (this.disposed || controller.signal.aborted) return;
20631
+ this.replaceCredential(apiKey);
20632
+ await this.session.receive(this.readyMessage);
20633
+ } catch (error) {
20634
+ if (!this.disposed) this.reportLoginError(error);
20635
+ } finally {
20636
+ this.loginController = void 0;
20637
+ }
20638
+ }
20639
+ reportLoginError(error) {
20640
+ const message = error instanceof Error ? error.message : "Stately login failed. Try again.";
20641
+ const authenticationRecovery = {
20642
+ host: "cli",
20643
+ canLogin: Boolean(this.options.onLogin)
20644
+ };
20645
+ this.sendMessage({
20646
+ type: "@statelyai.error",
20647
+ code: "initialization_failed",
20648
+ message,
20649
+ authenticationRecovery
20650
+ });
20651
+ console.error(`Stately Editor: ${message}\n${authenticationRecoveryInstructions(authenticationRecovery)}`);
20652
+ }
20067
20653
  log(direction, type, payload) {
20068
20654
  if (this.options.debug) console.error(`[${direction}] ${type}`, redactDebugPayload(payload) ?? "");
20069
20655
  }
@@ -20165,6 +20751,31 @@ function redactDebugPayload(payload) {
20165
20751
  if (!payload || typeof payload !== "object") return payload;
20166
20752
  return Object.fromEntries(Object.entries(payload).map(([key, value]) => [key, /^(?:apiKey|accessToken|refreshToken|token|authorization)$/i.test(key) ? "[REDACTED]" : redactDebugPayload(value)]));
20167
20753
  }
20754
+ async function prepareEditorAuthentication(options) {
20755
+ const getAccess = (apiKey) => fetchEditorSyncAccess({
20756
+ editorUrl: options.editorUrl,
20757
+ apiKey,
20758
+ fetch: (input, init) => fetchEditorHost(input instanceof URL ? input : new URL(String(input)), init)
20759
+ });
20760
+ try {
20761
+ return {
20762
+ apiKey: options.apiKey,
20763
+ access: await getAccess(options.apiKey)
20764
+ };
20765
+ } catch (error) {
20766
+ if (!isEditorSyncAuthenticationError(error)) return { apiKey: options.apiKey };
20767
+ console.log("Log in to Stately to open this machine. Complete sign-in in your browser; the editor will open automatically.");
20768
+ const apiKey = await options.onLogin(new AbortController().signal);
20769
+ try {
20770
+ return {
20771
+ apiKey,
20772
+ access: await getAccess(apiKey)
20773
+ };
20774
+ } catch {
20775
+ return { apiKey };
20776
+ }
20777
+ }
20778
+ }
20168
20779
  async function openEditor(options) {
20169
20780
  const fileName = path$1.resolve(options.fileName);
20170
20781
  const machines = (options.machines ?? [{
@@ -20177,6 +20788,11 @@ async function openEditor(options) {
20177
20788
  }));
20178
20789
  await Promise.all([...new Set(machines.map((machine) => machine.fileName))].map((target) => fsPromises.access(target)));
20179
20790
  await assertEditorHostAvailable(options.editorUrl);
20791
+ if (options.shouldOpen && options.onLogin && process.stdin.isTTY && process.stdout.isTTY) Object.assign(options, await prepareEditorAuthentication({
20792
+ editorUrl: options.editorUrl,
20793
+ apiKey: options.apiKey,
20794
+ onLogin: options.onLogin
20795
+ }));
20180
20796
  const rootUri = fileNameToUri(fileName);
20181
20797
  const rootDir = path$1.resolve(options.rootDir ?? path$1.dirname(fileName));
20182
20798
  let activeClient;
@@ -20196,6 +20812,10 @@ async function openEditor(options) {
20196
20812
  socket.end("HTTP/1.1 404 Not Found\r\n\r\n");
20197
20813
  return;
20198
20814
  }
20815
+ if (request.headers.origin !== editorPageUrl || request.headers.host !== new URL(editorPageUrl).host) {
20816
+ socket.end("HTTP/1.1 403 Forbidden\r\n\r\n");
20817
+ return;
20818
+ }
20199
20819
  webSocketServer.handleUpgrade(request, socket, head, (webSocket) => {
20200
20820
  activeClient?.close();
20201
20821
  session?.dispose();
@@ -20218,7 +20838,11 @@ async function openEditor(options) {
20218
20838
  const editorPageUrl = `http://${options.host}:${port}`;
20219
20839
  console.log(`Stately visual editor: ${editorPageUrl}`);
20220
20840
  console.log(`Editing ${machines.length} machine${machines.length === 1 ? "" : "s"} in ${rootDir}`);
20221
- if (options.shouldOpen) openBrowser(editorPageUrl);
20841
+ if (options.shouldOpen) try {
20842
+ await openBrowser(editorPageUrl);
20843
+ } catch {
20844
+ console.warn("Could not open a browser automatically. Open the editor URL above.");
20845
+ }
20222
20846
  const shutdown = () => {
20223
20847
  session?.dispose();
20224
20848
  activeClient?.close();
@@ -20399,6 +21023,7 @@ function getCliWebviewContent(options) {
20399
21023
  ></iframe>
20400
21024
  <script>
20401
21025
  const iframe = document.getElementById('stately-editor');
21026
+ const editorOrigin = new URL(iframe.src).origin;
20402
21027
  const socket = new WebSocket(
20403
21028
  (location.protocol === 'https:' ? 'wss:' : 'ws:') + '//' + location.host + '/ws'
20404
21029
  );
@@ -20419,10 +21044,13 @@ function getCliWebviewContent(options) {
20419
21044
  });
20420
21045
 
20421
21046
  socket.addEventListener('message', (event) => {
20422
- iframe.contentWindow?.postMessage(JSON.parse(event.data), '*');
21047
+ iframe.contentWindow?.postMessage(JSON.parse(event.data), editorOrigin);
20423
21048
  });
20424
21049
 
20425
21050
  window.addEventListener('message', (event) => {
21051
+ if (event.source !== iframe.contentWindow || event.origin !== editorOrigin) {
21052
+ return;
21053
+ }
20426
21054
  if (!event.data?.type?.startsWith?.('@statelyai.')) {
20427
21055
  return;
20428
21056
  }
@@ -20455,8 +21083,8 @@ async function assertEditorHostAvailable(editorUrl) {
20455
21083
  function formatEditorHostError(baseUrl, status, cause) {
20456
21084
  return `Cannot reach a usable editor host at ${baseUrl}.${status ? ` HTTP ${status} from /embed.` : ""}${cause instanceof Error && cause.message ? ` ${cause.message}` : ""} The CLI loads editor URL defaults from your environment, including \`.env.local\`. Start the editor host, set \`STATELY_EDITOR_URL\`, or pass \`--editor-url <url>\`.`;
20457
21085
  }
20458
- function openBrowser(url) {
20459
- spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open", process.platform === "win32" ? [
21086
+ async function openBrowser(url) {
21087
+ const child = spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open", process.platform === "win32" ? [
20460
21088
  "/c",
20461
21089
  "start",
20462
21090
  "",
@@ -20464,7 +21092,12 @@ function openBrowser(url) {
20464
21092
  ] : [url], {
20465
21093
  detached: true,
20466
21094
  stdio: "ignore"
20467
- }).unref();
21095
+ });
21096
+ child.unref();
21097
+ await new Promise((resolve, reject) => {
21098
+ child.once("spawn", resolve);
21099
+ child.once("error", reject);
21100
+ });
20468
21101
  }
20469
21102
  function fileNameToUri(fileName) {
20470
21103
  return pathToFileURL(path$1.resolve(fileName)).toString();
@@ -20490,6 +21123,8 @@ async function createLocalOAuthCallbackServer(options) {
20490
21123
  resolveCode = resolve;
20491
21124
  rejectCode = reject;
20492
21125
  });
21126
+ codePromise.catch(() => {});
21127
+ const cancel = () => rejectCode(/* @__PURE__ */ new Error("Stately sign-in was canceled."));
20493
21128
  const server = http.createServer((req, res) => {
20494
21129
  const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
20495
21130
  const code = requestUrl.searchParams.get("code");
@@ -20517,12 +21152,17 @@ async function createLocalOAuthCallbackServer(options) {
20517
21152
  });
20518
21153
  const address = server.address();
20519
21154
  if (!address || typeof address === "string") throw new Error("Could not start OAuth callback server.");
21155
+ options.signal?.addEventListener("abort", cancel, { once: true });
21156
+ if (options.signal?.aborted) cancel();
20520
21157
  return {
20521
21158
  redirectUri: `http://127.0.0.1:${address.port}/callback`,
20522
21159
  waitForCode: () => codePromise,
20523
- close: () => new Promise((resolve) => {
20524
- server.close(() => resolve());
20525
- })
21160
+ close: () => {
21161
+ options.signal?.removeEventListener("abort", cancel);
21162
+ return new Promise((resolve) => {
21163
+ server.close(() => resolve());
21164
+ });
21165
+ }
20526
21166
  };
20527
21167
  }
20528
21168
 
@@ -20918,7 +21558,7 @@ function getScriptKind$1(fileName) {
20918
21558
  }
20919
21559
 
20920
21560
  //#endregion
20921
- //#region ../../node_modules/.pnpm/@statelyai+graph@2.1.0_@statelyai+layout@0.1.2_cytoscape@3.34.0_d3-force@3.0.0_d3-hiera_3600605a69f548f83255fd3d12957cf4/node_modules/@statelyai/graph/dist/index.mjs
21561
+ //#region ../../node_modules/.pnpm/@statelyai+graph@2.1.0_@statelyai+layout@0.3.0_cytoscape@3.34.0_d3-force@3.0.0_d3-hiera_7624816e5d9226bed30c68417318a8b7/node_modules/@statelyai/graph/dist/index.mjs
20922
21562
  const LAYOUT_KEYS = {
20923
21563
  node: [
20924
21564
  "x",
@@ -21781,9 +22421,10 @@ function getOAuthExpiresAt(credential) {
21781
22421
  function isTerminalOAuthRefreshError(error) {
21782
22422
  return error instanceof Error && /Token refresh failed with HTTP (?:400|401|403)\b/.test(error.message);
21783
22423
  }
22424
+ var ExpiredStatelyCredentialError = class extends Error {};
21784
22425
  async function invalidateExpiredOAuthCredential() {
21785
22426
  await deleteStoredApiKey();
21786
- throw new Error("Your Stately session expired. Run `npx statelyai login` to continue.");
22427
+ throw new ExpiredStatelyCredentialError("Your Stately session expired. Run `npx statelyai login` to continue.");
21787
22428
  }
21788
22429
  async function refreshStoredOAuthCredential(credential, options) {
21789
22430
  if (!credential.refreshToken) return invalidateExpiredOAuthCredential();
@@ -21817,7 +22458,7 @@ async function refreshStoredOAuthCredential(credential, options) {
21817
22458
  }
21818
22459
  }
21819
22460
  async function resolveApiKey(options = {}) {
21820
- const envApiKey = getEnvApiKey();
22461
+ const envApiKey = options.ignoreEnvironment ? void 0 : getEnvApiKey();
21821
22462
  if (envApiKey) return {
21822
22463
  apiKey: envApiKey.apiKey,
21823
22464
  source: "env",
@@ -22043,22 +22684,32 @@ function formatStoredCredentialSource(storedCredential) {
22043
22684
  return `Credential source: stored unknown (${describeCredentialBackend(storedCredential.backend, storedCredential.location)}).`;
22044
22685
  }
22045
22686
  async function runOAuthBrowserLogin(options) {
22687
+ options.signal?.throwIfAborted();
22046
22688
  const state = crypto.randomBytes(16).toString("base64url");
22047
- const callbackServer = await createLocalOAuthCallbackServer({ state });
22689
+ const callbackServer = await createLocalOAuthCallbackServer({
22690
+ state,
22691
+ signal: options.signal
22692
+ });
22693
+ const loginFetch = (input, init) => fetch(input, {
22694
+ ...init,
22695
+ signal: options.signal
22696
+ });
22048
22697
  try {
22049
22698
  const { authorizeUrl, codeVerifier, discovery } = await buildOAuthLoginStart({
22699
+ fetch: loginFetch,
22050
22700
  baseUrl: options.baseUrl,
22051
22701
  redirectUri: callbackServer.redirectUri,
22052
22702
  state
22053
22703
  });
22704
+ options.signal?.throwIfAborted();
22054
22705
  options.log(`Opening browser for ${discovery.resourceUrl}`);
22055
- options.log(authorizeUrl);
22056
22706
  let openedBrowser = false;
22057
22707
  try {
22058
- openBrowser(authorizeUrl);
22708
+ await openBrowser(authorizeUrl);
22059
22709
  openedBrowser = true;
22060
22710
  } catch {
22061
22711
  options.log("Could not open a browser automatically.");
22712
+ options.log(authorizeUrl);
22062
22713
  }
22063
22714
  const codePromise = callbackServer.waitForCode();
22064
22715
  const pastedCodePromise = !openedBrowser && process.stdin.isTTY && process.stdout.isTTY ? promptForOAuthCallback().then((value) => {
@@ -22067,13 +22718,16 @@ async function runOAuthBrowserLogin(options) {
22067
22718
  return callback.code;
22068
22719
  }) : void 0;
22069
22720
  const code = await (pastedCodePromise ? Promise.race([codePromise, pastedCodePromise]) : codePromise);
22070
- return setStoredCredential(createOAuthCredential(await exchangeCodeForToken({
22721
+ const tokenResponse = await exchangeCodeForToken({
22722
+ fetch: loginFetch,
22071
22723
  tokenEndpoint: discovery.tokenEndpoint,
22072
22724
  code,
22073
22725
  codeVerifier,
22074
22726
  redirectUri: callbackServer.redirectUri,
22075
22727
  clientId: discovery.clientId
22076
- }), {
22728
+ });
22729
+ options.signal?.throwIfAborted();
22730
+ return setStoredCredential(createOAuthCredential(tokenResponse, {
22077
22731
  clientId: discovery.clientId,
22078
22732
  tokenEndpoint: discovery.tokenEndpoint
22079
22733
  }));
@@ -22986,7 +23640,16 @@ var OpenCommand = class OpenCommand extends Command {
22986
23640
  };
22987
23641
  async run() {
22988
23642
  const { args, flags } = await this.parse(OpenCommand);
22989
- const apiKey = (await resolveApiKey()).apiKey;
23643
+ let hasLoggedIn = false;
23644
+ const getApiKey = async () => {
23645
+ try {
23646
+ return (await resolveApiKey({ ignoreEnvironment: hasLoggedIn })).apiKey;
23647
+ } catch (error) {
23648
+ if (error instanceof ExpiredStatelyCredentialError) return void 0;
23649
+ throw error;
23650
+ }
23651
+ };
23652
+ const apiKey = await getApiKey();
22990
23653
  const cwd = process.cwd();
22991
23654
  const discovered = args.file ? await discoverMachinesInFile(path.resolve(args.file)) : await scanProjectMachines({ cwd });
22992
23655
  if (discovered.length === 0) throw new Error(args.file ? `No machines found in ${path.resolve(args.file)}.` : `No machines found in ${cwd}.`);
@@ -23019,7 +23682,19 @@ var OpenCommand = class OpenCommand extends Command {
23019
23682
  sourceUpdateStrategy: flags["source-update"] ?? configuredSourceUpdateStrategy ?? "preserve",
23020
23683
  apiKey,
23021
23684
  studioUrl: resolveApiBaseUrl(configuredStudioUrl),
23022
- debug: flags.debug
23685
+ debug: flags.debug,
23686
+ getApiKey,
23687
+ onLogin: async (signal) => {
23688
+ const stored = await runOAuthBrowserLogin({
23689
+ baseUrl: flags["editor-url"] ?? getDefaultEditorUrl(),
23690
+ log: (message) => this.log(message),
23691
+ signal
23692
+ });
23693
+ if (stored.credential?.type !== "oauth") throw new Error("Stately login did not return an OAuth credential.");
23694
+ hasLoggedIn = true;
23695
+ this.log("Logged in to Stately. Loading your machine…");
23696
+ return stored.credential.accessToken;
23697
+ }
23023
23698
  });
23024
23699
  }
23025
23700
  };