@harbour-enterprises/superdoc 1.17.0-next.12 → 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 (24) hide show
  1. package/dist/chunks/{SuperConverter-B8jWQriR.es.js → SuperConverter-0JaBYpRa.es.js} +1 -1
  2. package/dist/chunks/{SuperConverter-BSNvGA-_.cjs → SuperConverter-BgHHB-Z2.cjs} +1 -1
  3. package/dist/chunks/{src-D6cAnOaK.cjs → src-ByCDwKn7.cjs} +306 -125
  4. package/dist/chunks/{src-BI6hYDgO.es.js → src-C52EYA_H.es.js} +306 -125
  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/plan-engine/compiler.d.ts +14 -0
  8. package/dist/super-editor/src/document-api-adapters/plan-engine/compiler.d.ts.map +1 -1
  9. package/dist/super-editor/src/document-api-adapters/plan-engine/create-insertion.d.ts +16 -0
  10. package/dist/super-editor/src/document-api-adapters/plan-engine/create-insertion.d.ts.map +1 -0
  11. package/dist/super-editor/src/document-api-adapters/plan-engine/create-wrappers.d.ts.map +1 -1
  12. package/dist/super-editor/src/document-api-adapters/plan-engine/executor.d.ts.map +1 -1
  13. package/dist/super-editor/src/document-api-adapters/plan-engine/plan-wrappers.d.ts.map +1 -1
  14. package/dist/super-editor/src/document-api-adapters/plan-engine/preview.d.ts.map +1 -1
  15. package/dist/super-editor/src/document-api-adapters/plan-engine/query-match-adapter.d.ts.map +1 -1
  16. package/dist/super-editor/src/document-api-adapters/plan-engine/revision-tracker.d.ts.map +1 -1
  17. package/dist/super-editor/src/document-api-adapters/plan-engine/style-resolver.d.ts.map +1 -1
  18. package/dist/super-editor.cjs +2 -2
  19. package/dist/super-editor.es.js +2 -2
  20. package/dist/superdoc.cjs +3 -3
  21. package/dist/superdoc.es.js +3 -3
  22. package/dist/superdoc.umd.js +313 -132
  23. package/dist/superdoc.umd.js.map +1 -1
  24. 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-BSNvGA-_.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.12";
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.12";
20737
+ var summaryVersion = "1.17.0-next.13";
20738
20738
  var nodeKeys = [
20739
20739
  "group",
20740
20740
  "content",
@@ -21211,6 +21211,9 @@ var T_PLAN_ENGINE = [
21211
21211
  "TARGET_MOVED",
21212
21212
  "PLAN_CONFLICT_OVERLAP",
21213
21213
  "INVALID_STEP_COMBINATION",
21214
+ "REVISION_CHANGED_SINCE_COMPILE",
21215
+ "INVALID_INSERTION_CONTEXT",
21216
+ "DOCUMENT_IDENTITY_CONFLICT",
21214
21217
  "CAPABILITY_UNAVAILABLE"
21215
21218
  ];
21216
21219
  var T_QUERY_MATCH = [
@@ -24308,9 +24311,11 @@ function trackRevisions(editor) {
24308
24311
  function checkRevision(editor, expectedRevision) {
24309
24312
  if (expectedRevision === void 0) return;
24310
24313
  const current = getRevision(editor);
24311
- 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, {
24312
24315
  expectedRevision,
24313
- currentRevision: current
24316
+ currentRevision: current,
24317
+ refStability: "ephemeral",
24318
+ remediation: "Re-run query.match() to obtain a fresh ref valid for the current revision."
24314
24319
  });
24315
24320
  }
24316
24321
  function resolveSegmentPosition(targetOffset, segmentStart, segmentLength, docFrom, docTo) {
@@ -24560,7 +24565,9 @@ function captureRunsInRange(editor, blockPos, from$1, to) {
24560
24565
  return;
24561
24566
  }
24562
24567
  if (node.isLeaf) {
24568
+ const start$1 = offset$1;
24563
24569
  offset$1 += 1;
24570
+ maybePushRun(start$1, offset$1, []);
24564
24571
  return;
24565
24572
  }
24566
24573
  let isFirstChild = true;
@@ -24960,12 +24967,62 @@ function executeBlockSelector(index, query, diagnostics) {
24960
24967
  function isAssertStep(step) {
24961
24968
  return step.op === "assert";
24962
24969
  }
24970
+ function isCreateOp(op) {
24971
+ return op === "create.heading" || op === "create.paragraph";
24972
+ }
24973
+ var VALID_CREATE_POSITIONS = ["before", "after"];
24963
24974
  function isSelectWhere(where) {
24964
24975
  return where.by === "select";
24965
24976
  }
24966
24977
  function isRefWhere(where) {
24967
24978
  return where.by === "ref";
24968
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
+ }
24969
25026
  function isV3Ref(payload) {
24970
25027
  return typeof payload === "object" && payload !== null && "v" in payload && payload.v === 3;
24971
25028
  }
@@ -25178,9 +25235,13 @@ function decodeTextRefPayload(encoded, stepId) {
25178
25235
  }
25179
25236
  function resolveV3TextRef(editor, index, step, refData) {
25180
25237
  const currentRevision = getRevision(editor);
25181
- 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, {
25182
25239
  refRevision: refData.rev,
25183
- 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.`
25184
25245
  });
25185
25246
  if (!refData.segments?.length) return [];
25186
25247
  const segments = refData.segments.map((s) => ({
@@ -25279,7 +25340,11 @@ function resolveStepTargets(editor, index, step) {
25279
25340
  return t.blockId !== prev.blockId || t.from !== prev.from || t.to !== prev.to;
25280
25341
  });
25281
25342
  if (refWhere) {
25282
- 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
+ });
25283
25348
  if (targets.length > 1) throw planError("AMBIGUOUS_MATCH", `ref "${refWhere.ref}" resolved to ${targets.length} targets`, step.id, { matchCount: targets.length });
25284
25349
  return targets;
25285
25350
  }
@@ -25288,57 +25353,157 @@ function resolveStepTargets(editor, index, step) {
25288
25353
  if (selectWhere.require === "first" && targets.length > 1) targets = [targets[0]];
25289
25354
  return targets;
25290
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
+ }
25291
25368
  function applyCardinalityCheck(step, targets) {
25292
25369
  const where = step.where;
25293
25370
  if (!("require" in where) || where.require === void 0) return;
25294
25371
  const require$1 = where.require;
25295
25372
  if (require$1 === "first") {
25296
- 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));
25297
25374
  } else if (require$1 === "exactlyOne") {
25298
- 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));
25299
25376
  if (targets.length > 1) throw planError("AMBIGUOUS_MATCH", `selector matched ${targets.length} ranges, expected exactly one`, step.id, { matchCount: targets.length });
25300
25377
  } else if (require$1 === "all") {
25301
- if (targets.length === 0) throw planError("MATCH_NOT_FOUND", "selector matched zero ranges", step.id);
25302
- }
25303
- }
25304
- function detectOverlaps(steps) {
25305
- const rangesByBlock = /* @__PURE__ */ new Map();
25306
- 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);
25307
- else for (const seg of target.segments) addRange(rangesByBlock, seg.blockId, target.stepId, seg.from, seg.to);
25308
- for (const [blockId, ranges] of rangesByBlock) {
25309
- ranges.sort((a, b$1) => a.from - b$1.from);
25310
- for (let i$1 = 1; i$1 < ranges.length; i$1++) {
25311
- const prev = ranges[i$1 - 1];
25312
- const curr = ranges[i$1];
25313
- 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",
25314
25425
  blockId,
25315
- rangeA: {
25316
- from: prev.from,
25317
- to: prev.to
25318
- },
25319
- rangeB: {
25320
- from: curr.from,
25321
- to: curr.to
25322
- }
25323
- });
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
+ };
25324
25442
  }
25325
25443
  }
25326
25444
  }
25327
- function addRange(map$2, blockId, stepId, from$1, to) {
25328
- let blockRanges = map$2.get(blockId);
25329
- if (!blockRanges) {
25330
- blockRanges = [];
25331
- 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);
25332
25456
  }
25333
- blockRanges.push({
25334
- stepId,
25457
+ entries.push({
25335
25458
  from: from$1,
25336
25459
  to
25337
25460
  });
25338
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
+ }
25339
25502
  function compilePlan(editor, steps) {
25340
25503
  if (steps.length > 200) throw planError("INVALID_INPUT", `plan contains ${steps.length} steps, maximum is 200`);
25504
+ const compiledRevision = getRevision(editor);
25341
25505
  const index = getBlockIndex(editor);
25506
+ assertNoDuplicateBlockIds(index);
25342
25507
  const mutationSteps = [];
25343
25508
  const assertSteps = [];
25344
25509
  const seenIds = /* @__PURE__ */ new Set();
@@ -25348,26 +25513,41 @@ function compilePlan(editor, steps) {
25348
25513
  seenIds.add(step.id);
25349
25514
  }
25350
25515
  let totalTargets = 0;
25516
+ let stepIndex = 0;
25351
25517
  for (const step of steps) {
25352
25518
  if (isAssertStep(step)) {
25353
25519
  assertSteps.push(step);
25520
+ stepIndex++;
25354
25521
  continue;
25355
25522
  }
25356
25523
  if (!hasStepExecutor(step.op)) throw planError("INVALID_INPUT", `unknown step op "${step.op}"`, step.id);
25524
+ if (isCreateOp(step.op)) validateCreateStepPosition(step);
25357
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
+ }
25358
25531
  totalTargets += targets.length;
25359
25532
  mutationSteps.push({
25360
25533
  step,
25361
25534
  targets
25362
25535
  });
25536
+ stepIndex++;
25363
25537
  }
25364
25538
  if (totalTargets > 500) throw planError("INVALID_INPUT", `plan resolved ${totalTargets} total targets, maximum is 500`);
25365
- detectOverlaps(mutationSteps);
25539
+ validateStepInteractions(mutationSteps);
25366
25540
  return {
25367
25541
  mutationSteps,
25368
- assertSteps
25542
+ assertSteps,
25543
+ compiledRevision
25369
25544
  };
25370
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
+ }
25371
25551
  function applyDirectMutationMeta(tr) {
25372
25552
  tr.setMeta("inputType", "programmatic");
25373
25553
  tr.setMeta("skipTrackChanges", true);
@@ -25412,10 +25592,28 @@ function buildMarksFromSetMarks(editor, setMarks) {
25412
25592
  if (setMarks.strike && schema.marks.strike) marks.push(schema.marks.strike.create());
25413
25593
  return marks;
25414
25594
  }
25415
- function toAbsoluteBlockInsertPos(editor, blockId, offset$1, stepId) {
25416
- const candidate = getBlockIndex(editor).candidates.find((c$3) => c$3.nodeId === blockId);
25417
- if (!candidate) throw planError("TARGET_NOT_FOUND", `block "${blockId}" not found`, stepId);
25418
- 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;
25419
25617
  }
25420
25618
  function executeTextRewrite(editor, tr, target, step, mapping) {
25421
25619
  const absFrom = mapping.map(target.absFrom);
@@ -25449,39 +25647,7 @@ function executeTextDelete(_editor, tr, target, _step, mapping) {
25449
25647
  return { changed: true };
25450
25648
  }
25451
25649
  function executeStyleApply(editor, tr, target, step, mapping) {
25452
- const absFrom = mapping.map(target.absFrom);
25453
- const absTo = mapping.map(target.absTo);
25454
- const { schema } = editor.state;
25455
- let changed = false;
25456
- const markEntries = [
25457
- [
25458
- "bold",
25459
- step.args.inline.bold,
25460
- schema.marks.bold
25461
- ],
25462
- [
25463
- "italic",
25464
- step.args.inline.italic,
25465
- schema.marks.italic
25466
- ],
25467
- [
25468
- "underline",
25469
- step.args.inline.underline,
25470
- schema.marks.underline
25471
- ],
25472
- [
25473
- "strike",
25474
- step.args.inline.strike,
25475
- schema.marks.strike
25476
- ]
25477
- ];
25478
- for (const [, value, markType] of markEntries) {
25479
- if (value === void 0 || !markType) continue;
25480
- if (value) tr.addMark(absFrom, absTo, markType.create());
25481
- else tr.removeMark(absFrom, absTo, markType);
25482
- changed = true;
25483
- }
25484
- return { changed };
25650
+ return { changed: applyInlineMarkPatches(editor, tr, mapping.map(target.absFrom), mapping.map(target.absTo), step.args.inline) };
25485
25651
  }
25486
25652
  function validateMappedSpanContiguity(target, mapping, stepId) {
25487
25653
  let lastMappedEnd = -1;
@@ -25542,41 +25708,9 @@ function executeSpanTextDelete(_editor, tr, target, step, mapping) {
25542
25708
  }
25543
25709
  function executeSpanStyleApply(editor, tr, target, step, mapping) {
25544
25710
  validateMappedSpanContiguity(target, mapping, step.id);
25545
- const { schema } = editor.state;
25546
- let changed = false;
25547
25711
  const firstSeg = target.segments[0];
25548
25712
  const lastSeg = target.segments[target.segments.length - 1];
25549
- const absFrom = mapping.map(firstSeg.absFrom, 1);
25550
- const absTo = mapping.map(lastSeg.absTo, -1);
25551
- const markEntries = [
25552
- [
25553
- "bold",
25554
- step.args.inline.bold,
25555
- schema.marks.bold
25556
- ],
25557
- [
25558
- "italic",
25559
- step.args.inline.italic,
25560
- schema.marks.italic
25561
- ],
25562
- [
25563
- "underline",
25564
- step.args.inline.underline,
25565
- schema.marks.underline
25566
- ],
25567
- [
25568
- "strike",
25569
- step.args.inline.strike,
25570
- schema.marks.strike
25571
- ]
25572
- ];
25573
- for (const [, value, markType] of markEntries) {
25574
- if (value === void 0 || !markType) continue;
25575
- if (value) tr.addMark(absFrom, absTo, markType.create());
25576
- else tr.removeMark(absFrom, absTo, markType);
25577
- changed = true;
25578
- }
25579
- return { changed };
25713
+ return { changed: applyInlineMarkPatches(editor, tr, mapping.map(firstSeg.absFrom, 1), mapping.map(lastSeg.absTo, -1), step.args.inline) };
25580
25714
  }
25581
25715
  function getReplacementText(replacement) {
25582
25716
  if (replacement.blocks !== void 0) return replacement.blocks.map((b$1) => b$1.text).join("\n\n");
@@ -25747,9 +25881,11 @@ function executeAssertStep(_editor, tr, step) {
25747
25881
  }
25748
25882
  function executeCreateStep(editor, tr, step, targets, mapping) {
25749
25883
  const target = targets[0];
25750
- 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);
25751
25885
  const args$1 = step.args;
25752
- 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);
25753
25889
  const paragraphType = editor.state.schema?.nodes?.paragraph;
25754
25890
  if (!paragraphType) throw planError("INVALID_INPUT", "paragraph node type not in schema", step.id);
25755
25891
  const sdBlockId = args$1.sdBlockId;
@@ -25766,6 +25902,7 @@ function executeCreateStep(editor, tr, step, targets, mapping) {
25766
25902
  const node = paragraphType.createAndFill(attrs, textNode ?? void 0) ?? paragraphType.create(attrs, textNode ? [textNode] : void 0);
25767
25903
  if (!node) throw planError("INVALID_INPUT", `could not create ${step.op} node`, step.id);
25768
25904
  tr.insert(pos, node);
25905
+ assertNoPostInsertDuplicateIds(tr.doc, step.id);
25769
25906
  return {
25770
25907
  stepId: step.id,
25771
25908
  op: step.op,
@@ -25777,6 +25914,27 @@ function executeCreateStep(editor, tr, step, targets, mapping) {
25777
25914
  }
25778
25915
  };
25779
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
+ }
25780
25938
  function runMutationsOnTransaction(editor, tr, compiled, options) {
25781
25939
  const mapping = tr.mapping;
25782
25940
  const stepOutcomes = [];
@@ -25832,6 +25990,13 @@ function executeCompiledPlan(editor, compiled, options = {}) {
25832
25990
  const startTime = performance.now();
25833
25991
  const revisionBefore = getRevision(editor);
25834
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
+ });
25835
26000
  const tr = editor.state.tr;
25836
26001
  if ((options.changeMode ?? "direct") === "tracked") applyTrackedMutationMeta(tr);
25837
26002
  else applyDirectMutationMeta(tr);
@@ -26040,7 +26205,8 @@ function executeDomainCommand(editor, handler, options) {
26040
26205
  },
26041
26206
  targets: []
26042
26207
  }],
26043
- assertSteps: []
26208
+ assertSteps: [],
26209
+ compiledRevision: getRevision(editor)
26044
26210
  }, { expectedRevision: options?.expectedRevision });
26045
26211
  }
26046
26212
  function validateWriteRequest(request, resolved) {
@@ -26127,7 +26293,8 @@ function writeWrapper(editor, request, options) {
26127
26293
  step: stepDef,
26128
26294
  targets: [toCompiledTarget(stepId, op, resolved)]
26129
26295
  }],
26130
- assertSteps: []
26296
+ assertSteps: [],
26297
+ compiledRevision: getRevision(editor)
26131
26298
  }, {
26132
26299
  changeMode: mode,
26133
26300
  expectedRevision: options?.expectedRevision
@@ -26193,7 +26360,8 @@ function styleApplyWrapper(editor, input, options) {
26193
26360
  marks: []
26194
26361
  }]
26195
26362
  }],
26196
- assertSteps: []
26363
+ assertSteps: [],
26364
+ compiledRevision: getRevision(editor)
26197
26365
  }, {
26198
26366
  changeMode: mode,
26199
26367
  expectedRevision: options?.expectedRevision
@@ -27127,9 +27295,7 @@ function resolveCreateInsertPosition(editor, at, operationLabel) {
27127
27295
  const location$1 = at ?? { kind: "documentEnd" };
27128
27296
  if (location$1.kind === "documentStart") return 0;
27129
27297
  if (location$1.kind === "documentEnd") return editor.state.doc.content.size;
27130
- const target = findBlockById(getBlockIndex(editor), location$1.target);
27131
- if (!target) throw new DocumentApiAdapterError("TARGET_NOT_FOUND", `Create ${operationLabel} target block was not found.`, { target: location$1.target });
27132
- return location$1.kind === "before" ? target.pos : target.end;
27298
+ return resolveBlockInsertionPos(editor, location$1.target.nodeId, location$1.kind);
27133
27299
  }
27134
27300
  function resolveCreatedBlock(editor, nodeType, blockId) {
27135
27301
  const index = getBlockIndex(editor);
@@ -27231,7 +27397,9 @@ function createParagraphWrapper(editor, input, options) {
27231
27397
  try {
27232
27398
  const paragraph = resolveCreatedBlock(editor, "paragraph", paragraphId);
27233
27399
  if (mode === "tracked") trackedChangeRefs = collectTrackInsertRefsInRange(editor, paragraph.pos, paragraph.end);
27234
- } catch {}
27400
+ } catch (e) {
27401
+ if (!(e instanceof DocumentApiAdapterError)) throw e;
27402
+ }
27235
27403
  }
27236
27404
  return didApply;
27237
27405
  }, { expectedRevision: options?.expectedRevision }).steps[0]?.effect !== "changed") return {
@@ -27293,7 +27461,9 @@ function createHeadingWrapper(editor, input, options) {
27293
27461
  try {
27294
27462
  const heading = resolveCreatedBlock(editor, "heading", headingId);
27295
27463
  if (mode === "tracked") trackedChangeRefs = collectTrackInsertRefsInRange(editor, heading.pos, heading.end);
27296
- } catch {}
27464
+ } catch (e) {
27465
+ if (!(e instanceof DocumentApiAdapterError)) throw e;
27466
+ }
27297
27467
  }
27298
27468
  return didApply;
27299
27469
  }, { expectedRevision: options?.expectedRevision }).steps[0]?.effect !== "changed") return {
@@ -28505,14 +28675,15 @@ function trackChangesRejectAllWrapper(editor, _input, options) {
28505
28675
  return { success: true };
28506
28676
  }
28507
28677
  function previewPlan(editor, input) {
28508
- const evaluatedRevision = getRevision(editor);
28509
28678
  checkRevision(editor, input.expectedRevision);
28510
28679
  if (!input.steps?.length) throw planError("INVALID_INPUT", "plan must contain at least one step");
28511
28680
  const failures = [];
28512
28681
  const stepPreviews = [];
28513
28682
  let currentPhase = "compile";
28683
+ let evaluatedRevision = getRevision(editor);
28514
28684
  try {
28515
28685
  const compiled = compilePlan(editor, input.steps);
28686
+ evaluatedRevision = compiled.compiledRevision;
28516
28687
  currentPhase = "execute";
28517
28688
  const tr = editor.state.tr;
28518
28689
  const { stepOutcomes, assertFailures } = runMutationsOnTransaction(editor, tr, compiled, { throwOnAssertFailure: false });
@@ -29015,14 +29186,24 @@ function queryMatchAdapter(editor, input) {
29015
29186
  const userOffset = input.offset ?? 0;
29016
29187
  const paginatedMatches = isTextSelector ? applyPagination(rawMatches, userOffset, input.limit) : rawMatches;
29017
29188
  if (require$1 === "first" || require$1 === "exactlyOne" || require$1 === "all") {
29018
- 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
+ });
29019
29196
  }
29020
29197
  if (require$1 === "exactlyOne" && totalMatches > 1) throw planError("AMBIGUOUS_MATCH", `selector matched ${totalMatches} ranges, expected exactly one`, void 0, { matchCount: totalMatches });
29021
29198
  const matchItems = paginatedMatches.map((raw, pageIdx) => {
29022
29199
  const id = `m:${userOffset + pageIdx}`;
29023
29200
  if (isTextSelector && raw.textRanges?.length) {
29024
29201
  const blocks = buildMatchBlocks(editor, raw.textRanges, evaluatedRevision, id);
29025
- 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
+ });
29026
29207
  const snippetResult = buildBlocksSnippet(editor, blocks);
29027
29208
  return {
29028
29209
  id,
@@ -30627,7 +30808,7 @@ var Editor = class Editor extends EventEmitter$1 {
30627
30808
  return migrations.length > 0;
30628
30809
  }
30629
30810
  processCollaborationMigrations() {
30630
- console.debug("[checkVersionMigrations] Current editor version", "1.17.0-next.12");
30811
+ console.debug("[checkVersionMigrations] Current editor version", "1.17.0-next.13");
30631
30812
  if (!this.options.ydoc) return;
30632
30813
  let docVersion = this.options.ydoc.getMap("meta").get("version");
30633
30814
  if (!docVersion) docVersion = "initial";