@harbour-enterprises/superdoc 1.17.0-next.11 → 1.17.0-next.13

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.
Files changed (33) hide show
  1. package/dist/chunks/{SuperConverter-D0cZ7DBR.es.js → SuperConverter-0JaBYpRa.es.js} +1 -1
  2. package/dist/chunks/{SuperConverter-DOQaFkjd.cjs → SuperConverter-BgHHB-Z2.cjs} +1 -1
  3. package/dist/chunks/{src-DQKyE7kq.cjs → src-ByCDwKn7.cjs} +458 -129
  4. package/dist/chunks/{src-bsYwK29H.es.js → src-C52EYA_H.es.js} +452 -130
  5. package/dist/super-editor/converter.cjs +1 -1
  6. package/dist/super-editor/converter.es.js +1 -1
  7. package/dist/super-editor/src/document-api-adapters/assemble-adapters.d.ts.map +1 -1
  8. package/dist/super-editor/src/document-api-adapters/capabilities-adapter.d.ts.map +1 -1
  9. package/dist/super-editor/src/document-api-adapters/errors.d.ts +1 -1
  10. package/dist/super-editor/src/document-api-adapters/errors.d.ts.map +1 -1
  11. package/dist/super-editor/src/document-api-adapters/helpers/node-address-resolver.d.ts +12 -0
  12. package/dist/super-editor/src/document-api-adapters/helpers/node-address-resolver.d.ts.map +1 -1
  13. package/dist/super-editor/src/document-api-adapters/index.d.ts.map +1 -1
  14. package/dist/super-editor/src/document-api-adapters/plan-engine/blocks-wrappers.d.ts +4 -0
  15. package/dist/super-editor/src/document-api-adapters/plan-engine/blocks-wrappers.d.ts.map +1 -0
  16. package/dist/super-editor/src/document-api-adapters/plan-engine/compiler.d.ts +14 -0
  17. package/dist/super-editor/src/document-api-adapters/plan-engine/compiler.d.ts.map +1 -1
  18. package/dist/super-editor/src/document-api-adapters/plan-engine/create-insertion.d.ts +16 -0
  19. package/dist/super-editor/src/document-api-adapters/plan-engine/create-insertion.d.ts.map +1 -0
  20. package/dist/super-editor/src/document-api-adapters/plan-engine/create-wrappers.d.ts.map +1 -1
  21. package/dist/super-editor/src/document-api-adapters/plan-engine/executor.d.ts.map +1 -1
  22. package/dist/super-editor/src/document-api-adapters/plan-engine/plan-wrappers.d.ts.map +1 -1
  23. package/dist/super-editor/src/document-api-adapters/plan-engine/preview.d.ts.map +1 -1
  24. package/dist/super-editor/src/document-api-adapters/plan-engine/query-match-adapter.d.ts.map +1 -1
  25. package/dist/super-editor/src/document-api-adapters/plan-engine/revision-tracker.d.ts.map +1 -1
  26. package/dist/super-editor/src/document-api-adapters/plan-engine/style-resolver.d.ts.map +1 -1
  27. package/dist/super-editor.cjs +2 -2
  28. package/dist/super-editor.es.js +2 -2
  29. package/dist/superdoc.cjs +3 -3
  30. package/dist/superdoc.es.js +3 -3
  31. package/dist/superdoc.umd.js +459 -137
  32. package/dist/superdoc.umd.js.map +1 -1
  33. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-Dp2H1eGw.cjs");
2
- const require_SuperConverter = require("./SuperConverter-DOQaFkjd.cjs");
2
+ const require_SuperConverter = require("./SuperConverter-BgHHB-Z2.cjs");
3
3
  const require_jszip = require("./jszip-DCT9QYaK.cjs");
4
4
  const require_uuid = require("./uuid-CHj_rjgt.cjs");
5
5
  const require_constants = require("./constants-CpniKo9Z.cjs");
@@ -261,7 +261,7 @@ var DEFAULT_ENDPOINT = "https://ingest.superdoc.dev/v1/collect";
261
261
  const COMMUNITY_LICENSE_KEY = "community-and-eval-agplv3";
262
262
  function getSuperdocVersion() {
263
263
  try {
264
- return "1.17.0-next.11";
264
+ return "1.17.0-next.13";
265
265
  } catch {
266
266
  return "unknown";
267
267
  }
@@ -20734,7 +20734,7 @@ const canUseDOM = () => {
20734
20734
  return false;
20735
20735
  }
20736
20736
  };
20737
- var summaryVersion = "1.17.0-next.11";
20737
+ var summaryVersion = "1.17.0-next.13";
20738
20738
  var nodeKeys = [
20739
20739
  "group",
20740
20740
  "content",
@@ -21113,6 +21113,13 @@ const BLOCK_NODE_TYPES = [
21113
21113
  "image",
21114
21114
  "sdt"
21115
21115
  ];
21116
+ const DELETABLE_BLOCK_NODE_TYPES = [
21117
+ "paragraph",
21118
+ "heading",
21119
+ "listItem",
21120
+ "table",
21121
+ "sdt"
21122
+ ];
21116
21123
  const INLINE_NODE_TYPES = [
21117
21124
  "run",
21118
21125
  "bookmark",
@@ -21204,6 +21211,9 @@ var T_PLAN_ENGINE = [
21204
21211
  "TARGET_MOVED",
21205
21212
  "PLAN_CONFLICT_OVERLAP",
21206
21213
  "INVALID_STEP_COMBINATION",
21214
+ "REVISION_CHANGED_SINCE_COMPILE",
21215
+ "INVALID_INSERTION_CONTEXT",
21216
+ "DOCUMENT_IDENTITY_CONFLICT",
21207
21217
  "CAPABILITY_UNAVAILABLE"
21208
21218
  ];
21209
21219
  var T_QUERY_MATCH = [
@@ -21305,6 +21315,27 @@ const OPERATION_DEFINITIONS = {
21305
21315
  referenceDocPath: "delete.mdx",
21306
21316
  referenceGroup: "core"
21307
21317
  },
21318
+ "blocks.delete": {
21319
+ memberPath: "blocks.delete",
21320
+ description: "Delete an entire block node (paragraph, heading, list item, table, image, or sdt) deterministically.",
21321
+ requiresDocumentContext: true,
21322
+ metadata: mutationOperation({
21323
+ idempotency: "conditional",
21324
+ supportsDryRun: true,
21325
+ supportsTrackedMode: false,
21326
+ possibleFailureCodes: NONE_FAILURES,
21327
+ throws: [
21328
+ "TARGET_NOT_FOUND",
21329
+ "AMBIGUOUS_TARGET",
21330
+ "CAPABILITY_UNAVAILABLE",
21331
+ "INVALID_TARGET",
21332
+ "INVALID_INPUT",
21333
+ "INTERNAL_ERROR"
21334
+ ]
21335
+ }),
21336
+ referenceDocPath: "blocks/delete.mdx",
21337
+ referenceGroup: "blocks"
21338
+ },
21308
21339
  "format.apply": {
21309
21340
  memberPath: "format.apply",
21310
21341
  description: "Apply explicit inline style changes (bold, italic, underline, strike) to the target range using boolean patch semantics.",
@@ -21879,6 +21910,7 @@ function ref$1(name) {
21879
21910
  }
21880
21911
  var nodeTypeValues = NODE_TYPES;
21881
21912
  var blockNodeTypeValues = BLOCK_NODE_TYPES;
21913
+ var deletableBlockNodeTypeValues = DELETABLE_BLOCK_NODE_TYPES;
21882
21914
  var inlineNodeTypeValues = INLINE_NODE_TYPES;
21883
21915
  var SHARED_DEFS = {
21884
21916
  Range: objectSchema({
@@ -21938,6 +21970,15 @@ var SHARED_DEFS = {
21938
21970
  "nodeType",
21939
21971
  "nodeId"
21940
21972
  ]),
21973
+ DeletableBlockNodeAddress: objectSchema({
21974
+ kind: { const: "block" },
21975
+ nodeType: { enum: [...deletableBlockNodeTypeValues] },
21976
+ nodeId: { type: "string" }
21977
+ }, [
21978
+ "kind",
21979
+ "nodeType",
21980
+ "nodeId"
21981
+ ]),
21941
21982
  ParagraphAddress: objectSchema({
21942
21983
  kind: { const: "block" },
21943
21984
  nodeType: { const: "paragraph" },
@@ -22105,6 +22146,7 @@ ref$1("TargetKind");
22105
22146
  var textAddressSchema = ref$1("TextAddress");
22106
22147
  var textTargetSchema = ref$1("TextTarget");
22107
22148
  var blockNodeAddressSchema = ref$1("BlockNodeAddress");
22149
+ var deletableBlockNodeAddressSchema = ref$1("DeletableBlockNodeAddress");
22108
22150
  var paragraphAddressSchema = ref$1("ParagraphAddress");
22109
22151
  var headingAddressSchema = ref$1("HeadingAddress");
22110
22152
  var listItemAddressSchema = ref$1("ListItemAddress");
@@ -22481,6 +22523,7 @@ var trackChangesListResultSchema = discoveryResultSchema(discoveryItemSchema({
22481
22523
  }, ["address", "type"]));
22482
22524
  var capabilityReasonsSchema = arraySchema({ enum: [
22483
22525
  "COMMAND_UNAVAILABLE",
22526
+ "HELPER_UNAVAILABLE",
22484
22527
  "OPERATION_UNAVAILABLE",
22485
22528
  "TRACKED_MODE_UNAVAILABLE",
22486
22529
  "DRY_RUN_UNAVAILABLE",
@@ -22639,6 +22682,18 @@ var operationSchemas = {
22639
22682
  success: textMutationSuccessSchema,
22640
22683
  failure: textMutationFailureSchemaFor("format.align")
22641
22684
  },
22685
+ "blocks.delete": {
22686
+ input: objectSchema({ target: deletableBlockNodeAddressSchema }, ["target"]),
22687
+ output: objectSchema({
22688
+ success: { const: true },
22689
+ deleted: deletableBlockNodeAddressSchema
22690
+ }, ["success", "deleted"]),
22691
+ success: objectSchema({
22692
+ success: { const: true },
22693
+ deleted: deletableBlockNodeAddressSchema
22694
+ }, ["success", "deleted"]),
22695
+ failure: preApplyFailureResultSchemaFor("blocks.delete")
22696
+ },
22642
22697
  "create.paragraph": {
22643
22698
  input: objectSchema({
22644
22699
  at: { oneOf: [
@@ -22941,6 +22996,11 @@ var GROUP_METADATA = {
22941
22996
  description: "Primary read and write operations.",
22942
22997
  pagePath: "core/index.mdx"
22943
22998
  },
22999
+ blocks: {
23000
+ title: "Blocks",
23001
+ description: "Block-level structural operations.",
23002
+ pagePath: "blocks/index.mdx"
23003
+ },
22944
23004
  capabilities: {
22945
23005
  title: "Capabilities",
22946
23006
  description: "Runtime support discovery for capability-aware branching.",
@@ -22989,6 +23049,7 @@ const REFERENCE_OPERATION_GROUPS = Object.keys(GROUP_METADATA).map((key$1) => ({
22989
23049
  }));
22990
23050
  const CAPABILITY_REASON_CODES = [
22991
23051
  "COMMAND_UNAVAILABLE",
23052
+ "HELPER_UNAVAILABLE",
22992
23053
  "OPERATION_UNAVAILABLE",
22993
23054
  "TRACKED_MODE_UNAVAILABLE",
22994
23055
  "DRY_RUN_UNAVAILABLE",
@@ -23286,6 +23347,27 @@ function executeCreateHeading(adapter, input, options) {
23286
23347
  validateCreateLocation(normalized.at, "create.heading");
23287
23348
  return adapter.heading(normalized, normalizeMutationOptions(options));
23288
23349
  }
23350
+ var SUPPORTED_DELETE_NODE_TYPES = new Set(DELETABLE_BLOCK_NODE_TYPES);
23351
+ var REJECTED_DELETE_NODE_TYPES = new Set(["tableRow", "tableCell"]);
23352
+ function validateBlocksDeleteInput(input) {
23353
+ if (!input || typeof input !== "object") throw new DocumentApiValidationError("INVALID_INPUT", "blocks.delete requires an input object.", { fields: ["input"] });
23354
+ if (!input.target) throw new DocumentApiValidationError("INVALID_INPUT", "blocks.delete requires a target.", { fields: ["target"] });
23355
+ if (input.target.kind !== "block") throw new DocumentApiValidationError("INVALID_INPUT", "blocks.delete target must have kind \"block\".", { fields: ["target.kind"] });
23356
+ if (!input.target.nodeId || typeof input.target.nodeId !== "string") throw new DocumentApiValidationError("INVALID_INPUT", "blocks.delete target requires a nodeId string.", { fields: ["target.nodeId"] });
23357
+ const { nodeType } = input.target;
23358
+ if (REJECTED_DELETE_NODE_TYPES.has(nodeType)) throw new DocumentApiValidationError("INVALID_TARGET", `blocks.delete does not support "${nodeType}" targets. Table row/column operations are out of scope.`, {
23359
+ fields: ["target.nodeType"],
23360
+ nodeType
23361
+ });
23362
+ if (!SUPPORTED_DELETE_NODE_TYPES.has(nodeType)) throw new DocumentApiValidationError("INVALID_TARGET", `blocks.delete does not support "${nodeType}" targets.`, {
23363
+ fields: ["target.nodeType"],
23364
+ nodeType
23365
+ });
23366
+ }
23367
+ function executeBlocksDelete(adapter, input, options) {
23368
+ validateBlocksDeleteInput(input);
23369
+ return adapter.delete(input, normalizeMutationOptions(options));
23370
+ }
23289
23371
  function executeTrackChangesList(adapter, input) {
23290
23372
  return adapter.list(input);
23291
23373
  }
@@ -23329,6 +23411,7 @@ function buildDispatchTable(api) {
23329
23411
  insert: (input, options) => api.insert(input, options),
23330
23412
  replace: (input, options) => api.replace(input, options),
23331
23413
  delete: (input, options) => api.delete(input, options),
23414
+ "blocks.delete": (input, options) => api.blocks.delete(input, options),
23332
23415
  "format.apply": (input, options) => api.format.apply(input, options),
23333
23416
  "format.fontSize": (input, options) => api.format.fontSize(input, options),
23334
23417
  "format.fontFamily": (input, options) => api.format.fontFamily(input, options),
@@ -23454,6 +23537,9 @@ function createDocumentApi(adapters) {
23454
23537
  return executeTrackChangesDecide(adapters.trackChanges, input, options);
23455
23538
  }
23456
23539
  },
23540
+ blocks: { delete(input, options) {
23541
+ return executeBlocksDelete(adapters.blocks, input, options);
23542
+ } },
23457
23543
  create: {
23458
23544
  paragraph(input, options) {
23459
23545
  return executeCreateParagraph(adapters.create, input, options);
@@ -23538,6 +23624,7 @@ var REQUIRED_COMMANDS = {
23538
23624
  "lists.outdent": ["setTextSelection", "decreaseListIndent"],
23539
23625
  "lists.restart": ["setTextSelection", "restartNumbering"],
23540
23626
  "lists.exit": ["exitListItemAt"],
23627
+ "blocks.delete": ["deleteBlockNodeById"],
23541
23628
  "comments.create": [
23542
23629
  "addComment",
23543
23630
  "setTextSelection",
@@ -23566,6 +23653,12 @@ function hasAllCommands(editor, operationId) {
23566
23653
  if (!required || required.length === 0) return true;
23567
23654
  return required.every((command$1) => hasCommand(editor, command$1));
23568
23655
  }
23656
+ var REQUIRED_HELPERS = { "blocks.delete": (editor) => typeof editor.helpers?.blockNode?.getBlockNodeById === "function" };
23657
+ function hasRequiredHelpers(editor, operationId) {
23658
+ const check = REQUIRED_HELPERS[operationId];
23659
+ if (!check) return true;
23660
+ return check(editor);
23661
+ }
23569
23662
  function hasMarkCapability(editor, markName) {
23570
23663
  return Boolean(editor.schema?.marks?.[markName]);
23571
23664
  }
@@ -23611,7 +23704,7 @@ function pushReason(reasons, reason) {
23611
23704
  function isOperationAvailable(editor, operationId) {
23612
23705
  if (operationId === "format.apply") return MARK_KEYS.some((key$1) => hasMarkCapability(editor, STYLE_MARK_SCHEMA_NAMES[key$1] ?? key$1));
23613
23706
  if (INLINE_FORMAT_OPERATIONS.has(operationId)) return hasAllCommands(editor, operationId) && hasMarkCapability(editor, "textStyle");
23614
- return hasAllCommands(editor, operationId);
23707
+ return hasAllCommands(editor, operationId) && hasRequiredHelpers(editor, operationId);
23615
23708
  }
23616
23709
  function isCommandBackedAvailability(operationId) {
23617
23710
  return !isMarkBackedOperation(operationId) && !INLINE_FORMAT_OPERATIONS.has(operationId);
@@ -23625,7 +23718,10 @@ function buildOperationCapabilities(editor) {
23625
23718
  const dryRun = metadata.supportsDryRun && available;
23626
23719
  const reasons = [];
23627
23720
  if (!available) {
23628
- if (isCommandBackedAvailability(operationId)) pushReason(reasons, "COMMAND_UNAVAILABLE");
23721
+ if (isCommandBackedAvailability(operationId)) {
23722
+ if (!hasAllCommands(editor, operationId)) pushReason(reasons, "COMMAND_UNAVAILABLE");
23723
+ if (!hasRequiredHelpers(editor, operationId)) pushReason(reasons, "HELPER_UNAVAILABLE");
23724
+ }
23629
23725
  pushReason(reasons, "OPERATION_UNAVAILABLE");
23630
23726
  }
23631
23727
  if (metadata.supportsTrackedMode && !tracked) pushReason(reasons, "TRACKED_MODE_UNAVAILABLE");
@@ -24115,13 +24211,21 @@ function buildBlockIndex(editor) {
24115
24211
  });
24116
24212
  return {
24117
24213
  candidates,
24118
- byId
24214
+ byId,
24215
+ ambiguous
24119
24216
  };
24120
24217
  }
24121
24218
  function findBlockById(index, address) {
24122
24219
  if (address.kind !== "block") return void 0;
24123
24220
  return index.byId.get(`${address.nodeType}:${address.nodeId}`);
24124
24221
  }
24222
+ function findBlockByIdStrict(index, address) {
24223
+ const key$1 = `${address.nodeType}:${address.nodeId}`;
24224
+ if (index.ambiguous.has(key$1)) throw new DocumentApiAdapterError("AMBIGUOUS_TARGET", `Multiple blocks share key "${key$1}".`, { target: address });
24225
+ const candidate = index.byId.get(key$1);
24226
+ if (!candidate) throw new DocumentApiAdapterError("TARGET_NOT_FOUND", `Block "${key$1}" was not found.`, { target: address });
24227
+ return candidate;
24228
+ }
24125
24229
  function isTextBlockCandidate(candidate) {
24126
24230
  const node = candidate.node;
24127
24231
  return Boolean(node?.inlineContent || node?.isTextblock);
@@ -24207,9 +24311,11 @@ function trackRevisions(editor) {
24207
24311
  function checkRevision(editor, expectedRevision) {
24208
24312
  if (expectedRevision === void 0) return;
24209
24313
  const current = getRevision(editor);
24210
- if (expectedRevision !== current) throw new PlanError("REVISION_MISMATCH", `REVISION_MISMATCH — expected revision "${expectedRevision}" but document is at "${current}"`, void 0, {
24314
+ if (expectedRevision !== current) throw new PlanError("REVISION_MISMATCH", `REVISION_MISMATCH — expected revision "${expectedRevision}" but document is at "${current}". Re-run query.match to obtain a fresh ref.`, void 0, {
24211
24315
  expectedRevision,
24212
- currentRevision: current
24316
+ currentRevision: current,
24317
+ refStability: "ephemeral",
24318
+ remediation: "Re-run query.match() to obtain a fresh ref valid for the current revision."
24213
24319
  });
24214
24320
  }
24215
24321
  function resolveSegmentPosition(targetOffset, segmentStart, segmentLength, docFrom, docTo) {
@@ -24459,7 +24565,9 @@ function captureRunsInRange(editor, blockPos, from$1, to) {
24459
24565
  return;
24460
24566
  }
24461
24567
  if (node.isLeaf) {
24568
+ const start$1 = offset$1;
24462
24569
  offset$1 += 1;
24570
+ maybePushRun(start$1, offset$1, []);
24463
24571
  return;
24464
24572
  }
24465
24573
  let isFirstChild = true;
@@ -24859,12 +24967,62 @@ function executeBlockSelector(index, query, diagnostics) {
24859
24967
  function isAssertStep(step) {
24860
24968
  return step.op === "assert";
24861
24969
  }
24970
+ function isCreateOp(op) {
24971
+ return op === "create.heading" || op === "create.paragraph";
24972
+ }
24973
+ var VALID_CREATE_POSITIONS = ["before", "after"];
24862
24974
  function isSelectWhere(where) {
24863
24975
  return where.by === "select";
24864
24976
  }
24865
24977
  function isRefWhere(where) {
24866
24978
  return where.by === "ref";
24867
24979
  }
24980
+ function validateCreateStepPosition(step) {
24981
+ const args$1 = step.args;
24982
+ if (args$1.position === void 0 || args$1.position === null) {
24983
+ args$1.position = "after";
24984
+ return;
24985
+ }
24986
+ if (!VALID_CREATE_POSITIONS.includes(args$1.position)) throw planError("INVALID_INPUT", `create step requires args.position to be 'before' or 'after'`, step.id, {
24987
+ receivedPosition: args$1.position,
24988
+ allowedValues: [...VALID_CREATE_POSITIONS],
24989
+ default: "after"
24990
+ });
24991
+ }
24992
+ function resolveCreateAnchorFromTargets(targets, position, stepId) {
24993
+ const target = targets[0];
24994
+ if (!target) throw planError("INVALID_INPUT", "create step has no resolved targets", stepId);
24995
+ if (target.kind === "range") return target.blockId;
24996
+ const segments = target.segments;
24997
+ if (!segments.length) throw planError("INVALID_INPUT", "span target has no segments", stepId);
24998
+ return position === "before" ? segments[0].blockId : segments[segments.length - 1].blockId;
24999
+ }
25000
+ function validateInsertionContext(editor, index, step, stepIndex, anchorBlockId, position) {
25001
+ const candidate = index.candidates.find((c$3) => c$3.nodeId === anchorBlockId);
25002
+ if (!candidate) return;
25003
+ const paragraphType = editor.state.schema?.nodes?.paragraph;
25004
+ if (!paragraphType) return;
25005
+ const resolvedPos = editor.state.doc.resolve(candidate.pos);
25006
+ const parent = resolvedPos.parent;
25007
+ const anchorIndex = resolvedPos.index();
25008
+ const insertionIndex = position === "before" ? anchorIndex : anchorIndex + 1;
25009
+ if (!(typeof parent.canReplaceWith === "function" ? parent.canReplaceWith(insertionIndex, insertionIndex, paragraphType) : parent.type.contentMatch.matchType(paragraphType))) {
25010
+ const allowedChildTypes = [];
25011
+ const match$1 = parent.type.contentMatch;
25012
+ for (const nodeType of Object.values(editor.state.schema.nodes)) if (match$1.matchType(nodeType)) allowedChildTypes.push(nodeType.name);
25013
+ throw planError("INVALID_INSERTION_CONTEXT", `Cannot create ${step.op} inside ${parent.type.name}`, step.id, {
25014
+ stepIndex,
25015
+ stepId: step.id,
25016
+ operation: step.op,
25017
+ anchorBlockId,
25018
+ parentType: parent.type.name,
25019
+ allowedChildTypes,
25020
+ insertionIndex,
25021
+ requestedChildType: "paragraph",
25022
+ requestedSemanticType: step.op === "create.heading" ? "heading" : "paragraph"
25023
+ });
25024
+ }
25025
+ }
24868
25026
  function isV3Ref(payload) {
24869
25027
  return typeof payload === "object" && payload !== null && "v" in payload && payload.v === 3;
24870
25028
  }
@@ -25077,9 +25235,13 @@ function decodeTextRefPayload(encoded, stepId) {
25077
25235
  }
25078
25236
  function resolveV3TextRef(editor, index, step, refData) {
25079
25237
  const currentRevision = getRevision(editor);
25080
- if (refData.rev !== currentRevision) throw planError("REVISION_MISMATCH", `text ref was created at revision "${refData.rev}" but document is at "${currentRevision}"`, step.id, {
25238
+ if (refData.rev !== currentRevision) throw planError("REVISION_MISMATCH", `Text ref is ephemeral and revision-scoped. Re-run query.match to obtain a fresh handle.ref for revision ${currentRevision}.`, step.id, {
25081
25239
  refRevision: refData.rev,
25082
- currentRevision
25240
+ currentRevision,
25241
+ refStability: "ephemeral",
25242
+ refScope: refData.scope,
25243
+ blockId: refData.segments?.[0]?.blockId,
25244
+ remediation: `Re-run query.match() to obtain a fresh ref valid for the current revision.`
25083
25245
  });
25084
25246
  if (!refData.segments?.length) return [];
25085
25247
  const segments = refData.segments.map((s) => ({
@@ -25178,7 +25340,11 @@ function resolveStepTargets(editor, index, step) {
25178
25340
  return t.blockId !== prev.blockId || t.from !== prev.from || t.to !== prev.to;
25179
25341
  });
25180
25342
  if (refWhere) {
25181
- if (targets.length === 0) throw planError("MATCH_NOT_FOUND", `ref "${refWhere.ref}" did not resolve to any targets`, step.id);
25343
+ if (targets.length === 0) throw planError("MATCH_NOT_FOUND", `ref "${refWhere.ref}" did not resolve to any targets`, step.id, {
25344
+ selectorType: "ref",
25345
+ selectorPattern: refWhere.ref,
25346
+ candidateCount: 0
25347
+ });
25182
25348
  if (targets.length > 1) throw planError("AMBIGUOUS_MATCH", `ref "${refWhere.ref}" resolved to ${targets.length} targets`, step.id, { matchCount: targets.length });
25183
25349
  return targets;
25184
25350
  }
@@ -25187,57 +25353,157 @@ function resolveStepTargets(editor, index, step) {
25187
25353
  if (selectWhere.require === "first" && targets.length > 1) targets = [targets[0]];
25188
25354
  return targets;
25189
25355
  }
25356
+ function buildMatchNotFoundDetails(step) {
25357
+ const where = step.where;
25358
+ const select = "select" in where ? where.select : void 0;
25359
+ const within$1 = "within" in where ? where.within : void 0;
25360
+ return {
25361
+ selectorType: select?.type ?? "unknown",
25362
+ selectorPattern: select?.pattern ?? "",
25363
+ selectorMode: select?.mode ?? "contains",
25364
+ searchScope: within$1?.blockId ?? "document",
25365
+ candidateCount: 0
25366
+ };
25367
+ }
25190
25368
  function applyCardinalityCheck(step, targets) {
25191
25369
  const where = step.where;
25192
25370
  if (!("require" in where) || where.require === void 0) return;
25193
25371
  const require$1 = where.require;
25194
25372
  if (require$1 === "first") {
25195
- if (targets.length === 0) throw planError("MATCH_NOT_FOUND", "selector matched zero ranges", step.id);
25373
+ if (targets.length === 0) throw planError("MATCH_NOT_FOUND", "selector matched zero ranges", step.id, buildMatchNotFoundDetails(step));
25196
25374
  } else if (require$1 === "exactlyOne") {
25197
- if (targets.length === 0) throw planError("MATCH_NOT_FOUND", "selector matched zero ranges", step.id);
25375
+ if (targets.length === 0) throw planError("MATCH_NOT_FOUND", "selector matched zero ranges", step.id, buildMatchNotFoundDetails(step));
25198
25376
  if (targets.length > 1) throw planError("AMBIGUOUS_MATCH", `selector matched ${targets.length} ranges, expected exactly one`, step.id, { matchCount: targets.length });
25199
25377
  } else if (require$1 === "all") {
25200
- if (targets.length === 0) throw planError("MATCH_NOT_FOUND", "selector matched zero ranges", step.id);
25201
- }
25202
- }
25203
- function detectOverlaps(steps) {
25204
- const rangesByBlock = /* @__PURE__ */ new Map();
25205
- for (const compiled of steps) for (const target of compiled.targets) if (target.kind === "range") addRange(rangesByBlock, target.blockId, target.stepId, target.from, target.to);
25206
- else for (const seg of target.segments) addRange(rangesByBlock, seg.blockId, target.stepId, seg.from, seg.to);
25207
- for (const [blockId, ranges] of rangesByBlock) {
25208
- ranges.sort((a, b$1) => a.from - b$1.from);
25209
- for (let i$1 = 1; i$1 < ranges.length; i$1++) {
25210
- const prev = ranges[i$1 - 1];
25211
- const curr = ranges[i$1];
25212
- if (prev.stepId !== curr.stepId && prev.to > curr.from) throw planError("PLAN_CONFLICT_OVERLAP", `steps "${prev.stepId}" and "${curr.stepId}" target overlapping ranges in block "${blockId}"`, curr.stepId, {
25378
+ if (targets.length === 0) throw planError("MATCH_NOT_FOUND", "selector matched zero ranges", step.id, buildMatchNotFoundDetails(step));
25379
+ }
25380
+ }
25381
+ const STEP_INTERACTION_MATRIX = new Map([
25382
+ ["text.rewrite::format.apply::same_target", "allow"],
25383
+ ["text.rewrite::text.rewrite::same_target", "reject"],
25384
+ ["text.rewrite::text.delete::overlapping", "reject"],
25385
+ ["text.rewrite::create.*::same_block", "allow"],
25386
+ ["text.rewrite::text.insert::same_target", "reject"],
25387
+ ["format.apply::format.apply::same_target", "allow"],
25388
+ ["format.apply::text.rewrite::same_target", "reject"],
25389
+ ["format.apply::text.delete::overlapping", "reject"],
25390
+ ["format.apply::create.*::same_block", "allow"],
25391
+ ["format.apply::text.insert::same_target", "allow"],
25392
+ ["text.delete::text.rewrite::overlapping", "reject"],
25393
+ ["text.delete::text.delete::overlapping", "reject"],
25394
+ ["text.delete::format.apply::overlapping", "reject"],
25395
+ ["text.delete::create.*::same_block", "allow"],
25396
+ ["text.delete::text.insert::overlapping", "reject"],
25397
+ ["create.*::text.rewrite::same_block", "allow"],
25398
+ ["create.*::format.apply::same_block", "allow"],
25399
+ ["create.*::text.delete::same_block", "allow"],
25400
+ ["create.*::create.*::same_block", "allow"],
25401
+ ["create.*::text.insert::same_block", "allow"],
25402
+ ["text.insert::format.apply::same_target", "allow"],
25403
+ ["text.insert::text.rewrite::same_target", "reject"],
25404
+ ["text.insert::text.delete::overlapping", "reject"],
25405
+ ["text.insert::create.*::same_block", "allow"],
25406
+ ["text.insert::text.insert::same_target", "reject"]
25407
+ ]);
25408
+ const MATRIX_EXEMPT_OPS = new Set(["assert"]);
25409
+ function normalizeOpForMatrix(op) {
25410
+ return op.startsWith("create.") ? "create.*" : op;
25411
+ }
25412
+ function classifyOverlap(stepA, stepB) {
25413
+ const rangesA = extractBlockRanges(stepA);
25414
+ const rangesB = extractBlockRanges(stepB);
25415
+ const opA = normalizeOpForMatrix(stepA.step.op);
25416
+ const opB = normalizeOpForMatrix(stepB.step.op);
25417
+ const isCreateA = opA === "create.*";
25418
+ const isCreateB = opB === "create.*";
25419
+ for (const [blockId, aEntries] of rangesA) {
25420
+ const bEntries = rangesB.get(blockId);
25421
+ if (!bEntries) continue;
25422
+ for (const a of aEntries) for (const b$1 of bEntries) {
25423
+ if (isCreateA || isCreateB) return {
25424
+ overlapClass: "same_block",
25213
25425
  blockId,
25214
- rangeA: {
25215
- from: prev.from,
25216
- to: prev.to
25217
- },
25218
- rangeB: {
25219
- from: curr.from,
25220
- to: curr.to
25221
- }
25222
- });
25426
+ rangeA: a,
25427
+ rangeB: b$1
25428
+ };
25429
+ if (a.to <= b$1.from || b$1.to <= a.from) continue;
25430
+ if (a.from === b$1.from && a.to === b$1.to) return {
25431
+ overlapClass: "same_target",
25432
+ blockId,
25433
+ rangeA: a,
25434
+ rangeB: b$1
25435
+ };
25436
+ return {
25437
+ overlapClass: "overlapping",
25438
+ blockId,
25439
+ rangeA: a,
25440
+ rangeB: b$1
25441
+ };
25223
25442
  }
25224
25443
  }
25225
25444
  }
25226
- function addRange(map$2, blockId, stepId, from$1, to) {
25227
- let blockRanges = map$2.get(blockId);
25228
- if (!blockRanges) {
25229
- blockRanges = [];
25230
- map$2.set(blockId, blockRanges);
25445
+ function extractBlockRanges(compiled) {
25446
+ const result = /* @__PURE__ */ new Map();
25447
+ for (const target of compiled.targets) if (target.kind === "range") pushBlockRange(result, target.blockId, target.from, target.to);
25448
+ else for (const seg of target.segments) pushBlockRange(result, seg.blockId, seg.from, seg.to);
25449
+ return result;
25450
+ }
25451
+ function pushBlockRange(map$2, blockId, from$1, to) {
25452
+ let entries = map$2.get(blockId);
25453
+ if (!entries) {
25454
+ entries = [];
25455
+ map$2.set(blockId, entries);
25231
25456
  }
25232
- blockRanges.push({
25233
- stepId,
25457
+ entries.push({
25234
25458
  from: from$1,
25235
25459
  to
25236
25460
  });
25237
25461
  }
25462
+ function validateStepInteractions(steps) {
25463
+ for (let i$1 = 0; i$1 < steps.length; i$1++) for (let j = i$1 + 1; j < steps.length; j++) {
25464
+ const stepA = steps[i$1];
25465
+ const stepB = steps[j];
25466
+ if (MATRIX_EXEMPT_OPS.has(stepA.step.op) || MATRIX_EXEMPT_OPS.has(stepB.step.op)) continue;
25467
+ const overlap = classifyOverlap(stepA, stepB);
25468
+ if (!overlap) continue;
25469
+ const matrixKey = `${normalizeOpForMatrix(stepA.step.op)}::${normalizeOpForMatrix(stepB.step.op)}::${overlap.overlapClass}`;
25470
+ const verdict = STEP_INTERACTION_MATRIX.get(matrixKey) ?? "reject";
25471
+ if (verdict === "reject") throw planError("PLAN_CONFLICT_OVERLAP", `steps "${stepA.step.id}" and "${stepB.step.id}" target overlapping ranges in block "${overlap.blockId}"`, stepB.step.id, {
25472
+ blockId: overlap.blockId,
25473
+ stepIdA: stepA.step.id,
25474
+ stepIdB: stepB.step.id,
25475
+ opKeyA: stepA.step.op,
25476
+ opKeyB: stepB.step.op,
25477
+ rangeA: overlap.rangeA,
25478
+ rangeB: overlap.rangeB,
25479
+ overlapRegion: {
25480
+ from: Math.max(overlap.rangeA.from, overlap.rangeB.from),
25481
+ to: Math.min(overlap.rangeA.to, overlap.rangeB.to)
25482
+ },
25483
+ matrixVerdict: verdict,
25484
+ matrixKey
25485
+ });
25486
+ }
25487
+ }
25488
+ function assertNoDuplicateBlockIds(index) {
25489
+ const seen = /* @__PURE__ */ new Map();
25490
+ const duplicates = [];
25491
+ for (const candidate of index.candidates) {
25492
+ const count = seen.get(candidate.nodeId) ?? 0;
25493
+ seen.set(candidate.nodeId, count + 1);
25494
+ if (count === 1) duplicates.push(candidate.nodeId);
25495
+ }
25496
+ if (duplicates.length > 0) throw planError("DOCUMENT_IDENTITY_CONFLICT", "Document contains blocks with duplicate identities. This must be resolved before mutations can be applied.", void 0, {
25497
+ duplicateBlockIds: duplicates,
25498
+ blockCount: duplicates.length,
25499
+ remediation: "Re-import the document or call document.repair() to assign unique identities."
25500
+ });
25501
+ }
25238
25502
  function compilePlan(editor, steps) {
25239
25503
  if (steps.length > 200) throw planError("INVALID_INPUT", `plan contains ${steps.length} steps, maximum is 200`);
25504
+ const compiledRevision = getRevision(editor);
25240
25505
  const index = getBlockIndex(editor);
25506
+ assertNoDuplicateBlockIds(index);
25241
25507
  const mutationSteps = [];
25242
25508
  const assertSteps = [];
25243
25509
  const seenIds = /* @__PURE__ */ new Set();
@@ -25247,26 +25513,41 @@ function compilePlan(editor, steps) {
25247
25513
  seenIds.add(step.id);
25248
25514
  }
25249
25515
  let totalTargets = 0;
25516
+ let stepIndex = 0;
25250
25517
  for (const step of steps) {
25251
25518
  if (isAssertStep(step)) {
25252
25519
  assertSteps.push(step);
25520
+ stepIndex++;
25253
25521
  continue;
25254
25522
  }
25255
25523
  if (!hasStepExecutor(step.op)) throw planError("INVALID_INPUT", `unknown step op "${step.op}"`, step.id);
25524
+ if (isCreateOp(step.op)) validateCreateStepPosition(step);
25256
25525
  const targets = resolveStepTargets(editor, index, step);
25526
+ if (isCreateOp(step.op) && targets.length > 0) {
25527
+ const position = step.args.position ?? "after";
25528
+ const anchorBlockId = resolveCreateAnchorFromTargets(targets, position, step.id);
25529
+ validateInsertionContext(editor, index, step, stepIndex, anchorBlockId, position);
25530
+ }
25257
25531
  totalTargets += targets.length;
25258
25532
  mutationSteps.push({
25259
25533
  step,
25260
25534
  targets
25261
25535
  });
25536
+ stepIndex++;
25262
25537
  }
25263
25538
  if (totalTargets > 500) throw planError("INVALID_INPUT", `plan resolved ${totalTargets} total targets, maximum is 500`);
25264
- detectOverlaps(mutationSteps);
25539
+ validateStepInteractions(mutationSteps);
25265
25540
  return {
25266
25541
  mutationSteps,
25267
- assertSteps
25542
+ assertSteps,
25543
+ compiledRevision
25268
25544
  };
25269
25545
  }
25546
+ function resolveBlockInsertionPos(editor, anchorBlockId, position, stepId) {
25547
+ const candidate = getBlockIndex(editor).candidates.find((c$3) => c$3.nodeId === anchorBlockId);
25548
+ if (!candidate) throw planError("TARGET_NOT_FOUND", `block "${anchorBlockId}" not found`, stepId);
25549
+ return position === "before" ? candidate.pos : candidate.end;
25550
+ }
25270
25551
  function applyDirectMutationMeta(tr) {
25271
25552
  tr.setMeta("inputType", "programmatic");
25272
25553
  tr.setMeta("skipTrackChanges", true);
@@ -25311,10 +25592,28 @@ function buildMarksFromSetMarks(editor, setMarks) {
25311
25592
  if (setMarks.strike && schema.marks.strike) marks.push(schema.marks.strike.create());
25312
25593
  return marks;
25313
25594
  }
25314
- function toAbsoluteBlockInsertPos(editor, blockId, offset$1, stepId) {
25315
- const candidate = getBlockIndex(editor).candidates.find((c$3) => c$3.nodeId === blockId);
25316
- if (!candidate) throw planError("TARGET_NOT_FOUND", `block "${blockId}" not found`, stepId);
25317
- return candidate.pos + offset$1;
25595
+ function applyInlineMarkPatches(editor, tr, absFrom, absTo, inline) {
25596
+ const { schema } = editor.state;
25597
+ let changed = false;
25598
+ const markEntries = [
25599
+ [inline.bold, schema.marks.bold],
25600
+ [inline.italic, schema.marks.italic],
25601
+ [inline.underline, schema.marks.underline],
25602
+ [inline.strike, schema.marks.strike]
25603
+ ];
25604
+ for (const [value, markType] of markEntries) {
25605
+ if (value === void 0 || !markType) continue;
25606
+ if (value) tr.addMark(absFrom, absTo, markType.create());
25607
+ else tr.removeMark(absFrom, absTo, markType);
25608
+ changed = true;
25609
+ }
25610
+ return changed;
25611
+ }
25612
+ function resolveCreateAnchorBlockId(target, position, stepId) {
25613
+ if (target.kind === "range") return target.blockId;
25614
+ const segments = target.segments;
25615
+ if (!segments.length) throw planError("INVALID_INPUT", "span target has no segments", stepId);
25616
+ return position === "before" ? segments[0].blockId : segments[segments.length - 1].blockId;
25318
25617
  }
25319
25618
  function executeTextRewrite(editor, tr, target, step, mapping) {
25320
25619
  const absFrom = mapping.map(target.absFrom);
@@ -25348,39 +25647,7 @@ function executeTextDelete(_editor, tr, target, _step, mapping) {
25348
25647
  return { changed: true };
25349
25648
  }
25350
25649
  function executeStyleApply(editor, tr, target, step, mapping) {
25351
- const absFrom = mapping.map(target.absFrom);
25352
- const absTo = mapping.map(target.absTo);
25353
- const { schema } = editor.state;
25354
- let changed = false;
25355
- const markEntries = [
25356
- [
25357
- "bold",
25358
- step.args.inline.bold,
25359
- schema.marks.bold
25360
- ],
25361
- [
25362
- "italic",
25363
- step.args.inline.italic,
25364
- schema.marks.italic
25365
- ],
25366
- [
25367
- "underline",
25368
- step.args.inline.underline,
25369
- schema.marks.underline
25370
- ],
25371
- [
25372
- "strike",
25373
- step.args.inline.strike,
25374
- schema.marks.strike
25375
- ]
25376
- ];
25377
- for (const [, value, markType] of markEntries) {
25378
- if (value === void 0 || !markType) continue;
25379
- if (value) tr.addMark(absFrom, absTo, markType.create());
25380
- else tr.removeMark(absFrom, absTo, markType);
25381
- changed = true;
25382
- }
25383
- return { changed };
25650
+ return { changed: applyInlineMarkPatches(editor, tr, mapping.map(target.absFrom), mapping.map(target.absTo), step.args.inline) };
25384
25651
  }
25385
25652
  function validateMappedSpanContiguity(target, mapping, stepId) {
25386
25653
  let lastMappedEnd = -1;
@@ -25441,41 +25708,9 @@ function executeSpanTextDelete(_editor, tr, target, step, mapping) {
25441
25708
  }
25442
25709
  function executeSpanStyleApply(editor, tr, target, step, mapping) {
25443
25710
  validateMappedSpanContiguity(target, mapping, step.id);
25444
- const { schema } = editor.state;
25445
- let changed = false;
25446
25711
  const firstSeg = target.segments[0];
25447
25712
  const lastSeg = target.segments[target.segments.length - 1];
25448
- const absFrom = mapping.map(firstSeg.absFrom, 1);
25449
- const absTo = mapping.map(lastSeg.absTo, -1);
25450
- const markEntries = [
25451
- [
25452
- "bold",
25453
- step.args.inline.bold,
25454
- schema.marks.bold
25455
- ],
25456
- [
25457
- "italic",
25458
- step.args.inline.italic,
25459
- schema.marks.italic
25460
- ],
25461
- [
25462
- "underline",
25463
- step.args.inline.underline,
25464
- schema.marks.underline
25465
- ],
25466
- [
25467
- "strike",
25468
- step.args.inline.strike,
25469
- schema.marks.strike
25470
- ]
25471
- ];
25472
- for (const [, value, markType] of markEntries) {
25473
- if (value === void 0 || !markType) continue;
25474
- if (value) tr.addMark(absFrom, absTo, markType.create());
25475
- else tr.removeMark(absFrom, absTo, markType);
25476
- changed = true;
25477
- }
25478
- return { changed };
25713
+ return { changed: applyInlineMarkPatches(editor, tr, mapping.map(firstSeg.absFrom, 1), mapping.map(lastSeg.absTo, -1), step.args.inline) };
25479
25714
  }
25480
25715
  function getReplacementText(replacement) {
25481
25716
  if (replacement.blocks !== void 0) return replacement.blocks.map((b$1) => b$1.text).join("\n\n");
@@ -25587,7 +25822,8 @@ function buildAssertIndex(doc$2) {
25587
25822
  });
25588
25823
  return {
25589
25824
  candidates,
25590
- byId
25825
+ byId,
25826
+ ambiguous
25591
25827
  };
25592
25828
  }
25593
25829
  function resolveAssertScope(index, select, within$1) {
@@ -25645,9 +25881,11 @@ function executeAssertStep(_editor, tr, step) {
25645
25881
  }
25646
25882
  function executeCreateStep(editor, tr, step, targets, mapping) {
25647
25883
  const target = targets[0];
25648
- if (!target || target.kind !== "range") throw planError("INVALID_INPUT", `${step.op} step requires exactly one range target`, step.id);
25884
+ if (!target) throw planError("INVALID_INPUT", `${step.op} step requires at least one target`, step.id);
25649
25885
  const args$1 = step.args;
25650
- const pos = mapping.map(toAbsoluteBlockInsertPos(editor, target.blockId, target.from, step.id));
25886
+ const position = args$1.position ?? "after";
25887
+ const anchorPos = resolveBlockInsertionPos(editor, resolveCreateAnchorBlockId(target, position, step.id), position, step.id);
25888
+ const pos = mapping.map(anchorPos);
25651
25889
  const paragraphType = editor.state.schema?.nodes?.paragraph;
25652
25890
  if (!paragraphType) throw planError("INVALID_INPUT", "paragraph node type not in schema", step.id);
25653
25891
  const sdBlockId = args$1.sdBlockId;
@@ -25664,6 +25902,7 @@ function executeCreateStep(editor, tr, step, targets, mapping) {
25664
25902
  const node = paragraphType.createAndFill(attrs, textNode ?? void 0) ?? paragraphType.create(attrs, textNode ? [textNode] : void 0);
25665
25903
  if (!node) throw planError("INVALID_INPUT", `could not create ${step.op} node`, step.id);
25666
25904
  tr.insert(pos, node);
25905
+ assertNoPostInsertDuplicateIds(tr.doc, step.id);
25667
25906
  return {
25668
25907
  stepId: step.id,
25669
25908
  op: step.op,
@@ -25675,6 +25914,27 @@ function executeCreateStep(editor, tr, step, targets, mapping) {
25675
25914
  }
25676
25915
  };
25677
25916
  }
25917
+ function assertNoPostInsertDuplicateIds(doc$2, stepId) {
25918
+ const seen = /* @__PURE__ */ new Set();
25919
+ const duplicateSet = /* @__PURE__ */ new Set();
25920
+ doc$2.descendants((node) => {
25921
+ if (!node.isTextblock) return true;
25922
+ const attrs = node.attrs ?? {};
25923
+ const id = typeof attrs.paraId === "string" && attrs.paraId || typeof attrs.sdBlockId === "string" && attrs.sdBlockId || typeof attrs.nodeId === "string" && attrs.nodeId;
25924
+ if (!id) return true;
25925
+ if (seen.has(id)) duplicateSet.add(id);
25926
+ else seen.add(id);
25927
+ return true;
25928
+ });
25929
+ if (duplicateSet.size > 0) {
25930
+ const duplicates = [...duplicateSet];
25931
+ throw planError("INTERNAL_ERROR", `create step produced duplicate block identities: [${duplicates.join(", ")}]`, stepId, {
25932
+ source: "executor:checkPostInsertIdentityUniqueness",
25933
+ invariant: "post-insert block IDs must be unique",
25934
+ duplicateBlockIds: duplicates
25935
+ });
25936
+ }
25937
+ }
25678
25938
  function runMutationsOnTransaction(editor, tr, compiled, options) {
25679
25939
  const mapping = tr.mapping;
25680
25940
  const stepOutcomes = [];
@@ -25730,6 +25990,13 @@ function executeCompiledPlan(editor, compiled, options = {}) {
25730
25990
  const startTime = performance.now();
25731
25991
  const revisionBefore = getRevision(editor);
25732
25992
  checkRevision(editor, options.expectedRevision);
25993
+ if (compiled.compiledRevision !== revisionBefore) throw planError("REVISION_CHANGED_SINCE_COMPILE", `Document revision changed between compile and execute. Compiled at "${compiled.compiledRevision}", now at "${revisionBefore}".`, void 0, {
25994
+ compiledRevision: compiled.compiledRevision,
25995
+ currentRevision: revisionBefore,
25996
+ stepCount: compiled.mutationSteps.length,
25997
+ failedAtStep: "pre-execution",
25998
+ remediation: "Re-compile the plan against the current document state."
25999
+ });
25733
26000
  const tr = editor.state.tr;
25734
26001
  if ((options.changeMode ?? "direct") === "tracked") applyTrackedMutationMeta(tr);
25735
26002
  else applyDirectMutationMeta(tr);
@@ -25938,7 +26205,8 @@ function executeDomainCommand(editor, handler, options) {
25938
26205
  },
25939
26206
  targets: []
25940
26207
  }],
25941
- assertSteps: []
26208
+ assertSteps: [],
26209
+ compiledRevision: getRevision(editor)
25942
26210
  }, { expectedRevision: options?.expectedRevision });
25943
26211
  }
25944
26212
  function validateWriteRequest(request, resolved) {
@@ -26025,7 +26293,8 @@ function writeWrapper(editor, request, options) {
26025
26293
  step: stepDef,
26026
26294
  targets: [toCompiledTarget(stepId, op, resolved)]
26027
26295
  }],
26028
- assertSteps: []
26296
+ assertSteps: [],
26297
+ compiledRevision: getRevision(editor)
26029
26298
  }, {
26030
26299
  changeMode: mode,
26031
26300
  expectedRevision: options?.expectedRevision
@@ -26091,7 +26360,8 @@ function styleApplyWrapper(editor, input, options) {
26091
26360
  marks: []
26092
26361
  }]
26093
26362
  }],
26094
- assertSteps: []
26363
+ assertSteps: [],
26364
+ compiledRevision: getRevision(editor)
26095
26365
  }, {
26096
26366
  changeMode: mode,
26097
26367
  expectedRevision: options?.expectedRevision
@@ -26857,6 +27127,51 @@ function createCommentsWrapper(editor) {
26857
27127
  list: (query) => listCommentsHandler(editor, query)
26858
27128
  };
26859
27129
  }
27130
+ var SUPPORTED_NODE_TYPES = new Set(DELETABLE_BLOCK_NODE_TYPES);
27131
+ var REJECTED_NODE_TYPES = new Set(["tableRow", "tableCell"]);
27132
+ function validateTargetNodeType(nodeType) {
27133
+ if (REJECTED_NODE_TYPES.has(nodeType)) throw new DocumentApiAdapterError("INVALID_TARGET", `blocks.delete does not support "${nodeType}" targets. Table row/column operations are out of scope.`, { nodeType });
27134
+ if (!SUPPORTED_NODE_TYPES.has(nodeType)) throw new DocumentApiAdapterError("INVALID_TARGET", `blocks.delete does not support "${nodeType}" targets.`, { nodeType });
27135
+ }
27136
+ function resolveSdBlockId(candidate) {
27137
+ const sdBlockId = candidate.node.attrs?.sdBlockId;
27138
+ if (typeof sdBlockId === "string" && sdBlockId.length > 0) return sdBlockId;
27139
+ throw new DocumentApiAdapterError("INTERNAL_ERROR", "Resolved block candidate is missing sdBlockId attribute. This indicates a schema/extension invariant violation.", { attrs: candidate.node.attrs });
27140
+ }
27141
+ function validateCommandLayerUniqueness(editor, sdBlockId) {
27142
+ const getBlockNodeById = editor.helpers?.blockNode?.getBlockNodeById;
27143
+ if (typeof getBlockNodeById !== "function") throw new DocumentApiAdapterError("CAPABILITY_UNAVAILABLE", "blocks.delete requires the blockNode helper to be registered.", { reason: "missing_helper" });
27144
+ const matches = getBlockNodeById(sdBlockId);
27145
+ if (!matches || Array.isArray(matches) && matches.length === 0) throw new DocumentApiAdapterError("TARGET_NOT_FOUND", `Block with sdBlockId "${sdBlockId}" was not found at the command layer.`, { sdBlockId });
27146
+ if (Array.isArray(matches) && matches.length > 1) throw new DocumentApiAdapterError("AMBIGUOUS_TARGET", `Multiple blocks share sdBlockId "${sdBlockId}" at the command layer.`, {
27147
+ sdBlockId,
27148
+ count: matches.length
27149
+ });
27150
+ }
27151
+ function blocksDeleteWrapper(editor, input, options) {
27152
+ rejectTrackedMode("blocks.delete", options);
27153
+ const candidate = findBlockByIdStrict(getBlockIndex(editor), input.target);
27154
+ validateTargetNodeType(candidate.nodeType);
27155
+ const sdBlockId = resolveSdBlockId(candidate);
27156
+ const deleteBlockNodeById = requireEditorCommand(editor.commands?.deleteBlockNodeById, "blocks.delete");
27157
+ validateCommandLayerUniqueness(editor, sdBlockId);
27158
+ if (options?.dryRun) return {
27159
+ success: true,
27160
+ deleted: input.target
27161
+ };
27162
+ if (executeDomainCommand(editor, () => {
27163
+ const didApply = deleteBlockNodeById(sdBlockId);
27164
+ if (didApply) clearIndexCache(editor);
27165
+ return didApply;
27166
+ }, { expectedRevision: options?.expectedRevision }).steps[0]?.effect !== "changed") throw new DocumentApiAdapterError("INTERNAL_ERROR", "blocks.delete command returned false despite passing all pre-apply checks. This is an internal invariant violation.", {
27167
+ sdBlockId,
27168
+ target: input.target
27169
+ });
27170
+ return {
27171
+ success: true,
27172
+ deleted: input.target
27173
+ };
27174
+ }
26860
27175
  var DERIVED_ID_LENGTH = 24;
26861
27176
  function getRawTrackedMarks(editor) {
26862
27177
  try {
@@ -26980,9 +27295,7 @@ function resolveCreateInsertPosition(editor, at, operationLabel) {
26980
27295
  const location$1 = at ?? { kind: "documentEnd" };
26981
27296
  if (location$1.kind === "documentStart") return 0;
26982
27297
  if (location$1.kind === "documentEnd") return editor.state.doc.content.size;
26983
- const target = findBlockById(getBlockIndex(editor), location$1.target);
26984
- if (!target) throw new DocumentApiAdapterError("TARGET_NOT_FOUND", `Create ${operationLabel} target block was not found.`, { target: location$1.target });
26985
- return location$1.kind === "before" ? target.pos : target.end;
27298
+ return resolveBlockInsertionPos(editor, location$1.target.nodeId, location$1.kind);
26986
27299
  }
26987
27300
  function resolveCreatedBlock(editor, nodeType, blockId) {
26988
27301
  const index = getBlockIndex(editor);
@@ -27084,7 +27397,9 @@ function createParagraphWrapper(editor, input, options) {
27084
27397
  try {
27085
27398
  const paragraph = resolveCreatedBlock(editor, "paragraph", paragraphId);
27086
27399
  if (mode === "tracked") trackedChangeRefs = collectTrackInsertRefsInRange(editor, paragraph.pos, paragraph.end);
27087
- } catch {}
27400
+ } catch (e) {
27401
+ if (!(e instanceof DocumentApiAdapterError)) throw e;
27402
+ }
27088
27403
  }
27089
27404
  return didApply;
27090
27405
  }, { expectedRevision: options?.expectedRevision }).steps[0]?.effect !== "changed") return {
@@ -27146,7 +27461,9 @@ function createHeadingWrapper(editor, input, options) {
27146
27461
  try {
27147
27462
  const heading = resolveCreatedBlock(editor, "heading", headingId);
27148
27463
  if (mode === "tracked") trackedChangeRefs = collectTrackInsertRefsInRange(editor, heading.pos, heading.end);
27149
- } catch {}
27464
+ } catch (e) {
27465
+ if (!(e instanceof DocumentApiAdapterError)) throw e;
27466
+ }
27150
27467
  }
27151
27468
  return didApply;
27152
27469
  }, { expectedRevision: options?.expectedRevision }).steps[0]?.effect !== "changed") return {
@@ -28358,14 +28675,15 @@ function trackChangesRejectAllWrapper(editor, _input, options) {
28358
28675
  return { success: true };
28359
28676
  }
28360
28677
  function previewPlan(editor, input) {
28361
- const evaluatedRevision = getRevision(editor);
28362
28678
  checkRevision(editor, input.expectedRevision);
28363
28679
  if (!input.steps?.length) throw planError("INVALID_INPUT", "plan must contain at least one step");
28364
28680
  const failures = [];
28365
28681
  const stepPreviews = [];
28366
28682
  let currentPhase = "compile";
28683
+ let evaluatedRevision = getRevision(editor);
28367
28684
  try {
28368
28685
  const compiled = compilePlan(editor, input.steps);
28686
+ evaluatedRevision = compiled.compiledRevision;
28369
28687
  currentPhase = "execute";
28370
28688
  const tr = editor.state.tr;
28371
28689
  const { stepOutcomes, assertFailures } = runMutationsOnTransaction(editor, tr, compiled, { throwOnAssertFailure: false });
@@ -28868,14 +29186,24 @@ function queryMatchAdapter(editor, input) {
28868
29186
  const userOffset = input.offset ?? 0;
28869
29187
  const paginatedMatches = isTextSelector ? applyPagination(rawMatches, userOffset, input.limit) : rawMatches;
28870
29188
  if (require$1 === "first" || require$1 === "exactlyOne" || require$1 === "all") {
28871
- if (totalMatches === 0) throw planError("MATCH_NOT_FOUND", "selector matched zero ranges");
29189
+ if (totalMatches === 0) throw planError("MATCH_NOT_FOUND", "selector matched zero ranges", void 0, {
29190
+ selectorType: input.select?.type ?? "unknown",
29191
+ selectorPattern: input.select?.pattern ?? "",
29192
+ selectorMode: input.select?.mode ?? "contains",
29193
+ searchScope: (input.within?.kind === "block" ? input.within.nodeId : void 0) ?? "document",
29194
+ candidateCount: 0
29195
+ });
28872
29196
  }
28873
29197
  if (require$1 === "exactlyOne" && totalMatches > 1) throw planError("AMBIGUOUS_MATCH", `selector matched ${totalMatches} ranges, expected exactly one`, void 0, { matchCount: totalMatches });
28874
29198
  const matchItems = paginatedMatches.map((raw, pageIdx) => {
28875
29199
  const id = `m:${userOffset + pageIdx}`;
28876
29200
  if (isTextSelector && raw.textRanges?.length) {
28877
29201
  const blocks = buildMatchBlocks(editor, raw.textRanges, evaluatedRevision, id);
28878
- if (blocks.length === 0) throw planError("INTERNAL_ERROR", `text match produced no blocks for ${id}`);
29202
+ if (blocks.length === 0) throw planError("INTERNAL_ERROR", `text match produced no blocks for ${id}`, void 0, {
29203
+ source: "query-match-adapter:buildMatchEntries",
29204
+ invariant: "text match must have at least one block after zero-width filtering",
29205
+ context: { matchId: id }
29206
+ });
28879
29207
  const snippetResult = buildBlocksSnippet(editor, blocks);
28880
29208
  return {
28881
29209
  id,
@@ -29088,6 +29416,7 @@ function getDocumentApiAdapters(editor) {
29088
29416
  acceptAll: (input, options) => trackChangesAcceptAllWrapper(editor, input, options),
29089
29417
  rejectAll: (input, options) => trackChangesRejectAllWrapper(editor, input, options)
29090
29418
  },
29419
+ blocks: { delete: (input, options) => blocksDeleteWrapper(editor, input, options) },
29091
29420
  create: {
29092
29421
  paragraph: (input, options) => createParagraphWrapper(editor, input, options),
29093
29422
  heading: (input, options) => createHeadingWrapper(editor, input, options)
@@ -30479,7 +30808,7 @@ var Editor = class Editor extends EventEmitter$1 {
30479
30808
  return migrations.length > 0;
30480
30809
  }
30481
30810
  processCollaborationMigrations() {
30482
- console.debug("[checkVersionMigrations] Current editor version", "1.17.0-next.11");
30811
+ console.debug("[checkVersionMigrations] Current editor version", "1.17.0-next.13");
30483
30812
  if (!this.options.ydoc) return;
30484
30813
  let docVersion = this.options.ydoc.getMap("meta").get("version");
30485
30814
  if (!docVersion) docVersion = "initial";