@adeu/core 1.17.1 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1384,7 +1384,19 @@ ${header}`;
1384
1384
  this._add_virtual_text(" | ", current, null);
1385
1385
  current += 3;
1386
1386
  }
1387
+ const cell_start = current;
1387
1388
  current = this._map_blocks(cell, current);
1389
+ if (!this.clean_view && !this.original_view) {
1390
+ const firstP = cell._element.getElementsByTagName("w:p")[0];
1391
+ const paraId = firstP ? firstP.getAttribute("w14:paraId") : null;
1392
+ if (paraId && firstP) {
1393
+ const cellPara = new Paragraph(firstP, cell);
1394
+ this._add_virtual_text("", current, cellPara);
1395
+ const anchor = `{#cell:${paraId}}`;
1396
+ this._add_virtual_text(anchor, current, cellPara);
1397
+ current += anchor.length;
1398
+ }
1399
+ }
1388
1400
  cells_processed += 1;
1389
1401
  }
1390
1402
  if (ins && !this.clean_view && !this.original_view) {
@@ -1761,7 +1773,7 @@ ${header}`;
1761
1773
  target_text = this._strip_markdown_formatting(target_text);
1762
1774
  target_text = this._replace_smart_quotes(target_text);
1763
1775
  const parts = [];
1764
- const token_pattern = /(\[_+\])|(\s+)|(['"])|([.,;:\/])/g;
1776
+ const token_pattern = /(\[_+\])|(\s+)|(['"])|([.,;:\/\-\[\](){}+=$?*!|#^<>\\%&@~`_])/g;
1765
1777
  let last_idx = 0;
1766
1778
  let match;
1767
1779
  const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -2647,7 +2659,7 @@ function _refine_match_boundaries(text, start, end) {
2647
2659
  function _make_fuzzy_regex(target_text) {
2648
2660
  target_text = _replace_smart_quotes(target_text);
2649
2661
  const parts = [];
2650
- const token_pattern = /(_+)|(\s+)|(['"])|([.,;:\/])/g;
2662
+ const token_pattern = /(_+)|(\s+)|(['"])|([.,;:\/\-\[\](){}+=$?*!|#^<>\\%&@~`])/g;
2651
2663
  const md_noise = "[*_]*";
2652
2664
  const structural_noise = "(?:\\s*(?:[*+\\->]|\\d+\\.)\\s+|\\s*\\n\\s*)";
2653
2665
  const start_list_marker = "(?:[ \\t]*(?:[*+\\->]|\\d+\\.)\\s+)?";
@@ -2869,6 +2881,51 @@ function insertAfter(newNode, refNode) {
2869
2881
  refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
2870
2882
  }
2871
2883
  }
2884
+ function takeSnapshot(doc) {
2885
+ const parts = [...doc.pkg.parts];
2886
+ const unzipped = { ...doc.pkg.unzipped };
2887
+ const rels = /* @__PURE__ */ new Map();
2888
+ const elements = /* @__PURE__ */ new Map();
2889
+ for (const part of parts) {
2890
+ rels.set(part, new Map(part.rels));
2891
+ if (part._element) {
2892
+ elements.set(part, part._element.cloneNode(true));
2893
+ }
2894
+ }
2895
+ return { parts, unzipped, rels, elements };
2896
+ }
2897
+ function restoreSnapshot(doc, snapshot) {
2898
+ doc.pkg.parts = [...snapshot.parts];
2899
+ for (const key of Object.keys(doc.pkg.unzipped)) {
2900
+ delete doc.pkg.unzipped[key];
2901
+ }
2902
+ for (const [key, val] of Object.entries(snapshot.unzipped)) {
2903
+ doc.pkg.unzipped[key] = val;
2904
+ }
2905
+ for (const part of snapshot.parts) {
2906
+ part.rels = new Map(snapshot.rels.get(part));
2907
+ const originalEl = snapshot.elements.get(part);
2908
+ if (originalEl && part._element) {
2909
+ const xmlDoc = part._element.ownerDocument;
2910
+ if (xmlDoc && xmlDoc.documentElement) {
2911
+ xmlDoc.replaceChild(originalEl, xmlDoc.documentElement);
2912
+ }
2913
+ part._element = originalEl;
2914
+ }
2915
+ }
2916
+ }
2917
+ function stripMatchingHeadingHashes(target, newText) {
2918
+ if (!target || !newText) return [target, newText];
2919
+ const targetMatch = target.match(/^(#+)\s+/);
2920
+ const newMatch = newText.match(/^(#+)\s+/);
2921
+ if (targetMatch && newMatch && targetMatch[1] === newMatch[1]) {
2922
+ const hashes = targetMatch[1];
2923
+ const targetClean = target.substring(hashes.length).trimStart();
2924
+ const newClean = newText.substring(hashes.length).trimStart();
2925
+ return [targetClean, newClean];
2926
+ }
2927
+ return [target, newText];
2928
+ }
2872
2929
  var BatchValidationError = class extends Error {
2873
2930
  errors;
2874
2931
  constructor(errors) {
@@ -2877,7 +2934,7 @@ var BatchValidationError = class extends Error {
2877
2934
  this.errors = errors;
2878
2935
  }
2879
2936
  };
2880
- function validate_edit_strings(edits) {
2937
+ function validate_edit_strings(edits, index_offset = 0) {
2881
2938
  const errors = [];
2882
2939
  for (let i = 0; i < edits.length; i++) {
2883
2940
  const edit = edits[i];
@@ -2885,7 +2942,7 @@ function validate_edit_strings(edits) {
2885
2942
  const n_text = edit.new_text || "";
2886
2943
  if (n_text.includes("{++") || n_text.includes("{--") || n_text.includes("{>>") || n_text.includes("{==")) {
2887
2944
  errors.push(
2888
- `- Edit ${i + 1} Failed: Do not manually write CriticMarkup tags ({++, {--, {>>, {==) in \`new_text\`. The engine handles redlining automatically. To add a comment, use the \`comment\` parameter.`
2945
+ `- Edit ${i + 1 + index_offset} Failed: Do not manually write CriticMarkup tags ({++, {--, {>>, {==) in \`new_text\`. The engine handles redlining automatically. To add a comment, use the \`comment\` parameter.`
2889
2946
  );
2890
2947
  }
2891
2948
  if (t_text.includes("[^") || n_text.includes("[^")) {
@@ -2896,11 +2953,11 @@ function validate_edit_strings(edits) {
2896
2953
  (f) => n_fns.filter((x) => x === f).length > t_fns.filter((x) => x === f).length
2897
2954
  )) {
2898
2955
  errors.push(
2899
- `- Edit ${i + 1} Failed: Cannot insert footnote/endnote markers via text replace. Markers like \`[^fn-N]\` are read-only projections. Use Word's References menu.`
2956
+ `- Edit ${i + 1 + index_offset} Failed: Cannot insert footnote/endnote markers via text replace. Markers like \`[^fn-N]\` are read-only projections. Use Word's References menu.`
2900
2957
  );
2901
2958
  } else {
2902
2959
  errors.push(
2903
- `- Edit ${i + 1} Failed: Cannot delete footnote/endnote references via text replace. The marker corresponds to a structural XML element.`
2960
+ `- Edit ${i + 1 + index_offset} Failed: Cannot delete footnote/endnote references via text replace. The marker corresponds to a structural XML element.`
2904
2961
  );
2905
2962
  }
2906
2963
  }
@@ -2911,16 +2968,16 @@ function validate_edit_strings(edits) {
2911
2968
  if (t_links.length !== n_links.length) {
2912
2969
  if (n_links.length > t_links.length) {
2913
2970
  errors.push(
2914
- `- Edit ${i + 1} Failed: Cannot insert hyperlinks via text replace. Use a dedicated structural operation.`
2971
+ `- Edit ${i + 1 + index_offset} Failed: Cannot insert hyperlinks via text replace. Use a dedicated structural operation.`
2915
2972
  );
2916
2973
  } else {
2917
2974
  errors.push(
2918
- `- Edit ${i + 1} Failed: Cannot delete hyperlinks via text replace. The marker corresponds to a structural XML element.`
2975
+ `- Edit ${i + 1 + index_offset} Failed: Cannot delete hyperlinks via text replace. The marker corresponds to a structural XML element.`
2919
2976
  );
2920
2977
  }
2921
2978
  } else if (t_links.length > 1 && JSON.stringify(t_links) !== JSON.stringify(n_links)) {
2922
2979
  errors.push(
2923
- `- Edit ${i + 1} Failed: Can only edit or retarget one hyperlink per text replacement. Please split into multiple edits.`
2980
+ `- Edit ${i + 1 + index_offset} Failed: Can only edit or retarget one hyperlink per text replacement. Please split into multiple edits.`
2924
2981
  );
2925
2982
  }
2926
2983
  }
@@ -2930,17 +2987,17 @@ function validate_edit_strings(edits) {
2930
2987
  if (t_xrefs.length !== n_xrefs.length) {
2931
2988
  if (n_xrefs.length > t_xrefs.length) {
2932
2989
  errors.push(
2933
- `- Edit ${i + 1} Failed: Cannot insert cross-references via text replace. Markers are read-only projections.`
2990
+ `- Edit ${i + 1 + index_offset} Failed: Cannot insert cross-references via text replace. Markers are read-only projections.`
2934
2991
  );
2935
2992
  } else {
2936
2993
  errors.push(
2937
- `- Edit ${i + 1} Failed: Cannot delete cross-references via text replace. The marker corresponds to a structural XML element.`
2994
+ `- Edit ${i + 1 + index_offset} Failed: Cannot delete cross-references via text replace. The marker corresponds to a structural XML element.`
2938
2995
  );
2939
2996
  }
2940
2997
  } else {
2941
2998
  if (JSON.stringify(t_xrefs) !== JSON.stringify(n_xrefs)) {
2942
2999
  errors.push(
2943
- `- Edit ${i + 1} Failed: Modifying or retargeting cross-reference markers is disallowed to prevent dependency corruption.`
3000
+ `- Edit ${i + 1 + index_offset} Failed: Modifying or retargeting cross-reference markers is disallowed to prevent dependency corruption.`
2944
3001
  );
2945
3002
  }
2946
3003
  }
@@ -2951,7 +3008,7 @@ function validate_edit_strings(edits) {
2951
3008
  for (const a of n_anchors) {
2952
3009
  if (n_anchors.filter((x) => x === a).length > t_anchors.filter((x) => x === a).length) {
2953
3010
  errors.push(
2954
- `- Edit ${i + 1} Failed: Cannot modify or insert internal anchor markers (\`{#...}\`). These represent structural XML bookmarks.`
3011
+ `- Edit ${i + 1 + index_offset} Failed: Cannot modify or insert internal anchor markers (\`{#...}\`). These represent structural XML bookmarks.`
2955
3012
  );
2956
3013
  break;
2957
3014
  }
@@ -2965,16 +3022,16 @@ function validate_edit_strings(edits) {
2965
3022
  const level = stripped.length - stripped.replace(/^#+/, "").length;
2966
3023
  if (stripped.substring(level).startsWith(" ") || stripped.substring(level) === "") {
2967
3024
  errors.push(
2968
- `- Edit ${i + 1} Failed: Heading level ${level} is not supported (maximum is 6).`
3025
+ `- Edit ${i + 1 + index_offset} Failed: Heading level ${level} is not supported (maximum is 6).`
2969
3026
  );
2970
3027
  break;
2971
3028
  }
2972
3029
  }
2973
3030
  }
2974
3031
  }
2975
- if (t_text.includes("READONLY_BOUNDARY_START") || n_text.includes("READONLY_BOUNDARY_START") || t_text.includes("# Document Structure (Read-Only)") || n_text.includes("# Document Structure (Read-Only)")) {
3032
+ if (t_text.includes("READONLY_BOUNDARY_START") || n_text.includes("READONLY_BOUNDARY_START") || t_text.includes("# Document Structure (Read-Only)") || n_text.includes("# Document Structure (Read-Only)") || t_text.includes("Document Structure (Read-Only)") || n_text.includes("Document Structure (Read-Only)")) {
2976
3033
  errors.push(
2977
- `- Edit ${i + 1} Failed: Modification targets the read-only boundary (Structural Appendix). This section cannot be edited.`
3034
+ `- Edit ${i + 1 + index_offset} Failed: Modification targets the read-only boundary (Structural Appendix). This section cannot be edited.`
2978
3035
  );
2979
3036
  }
2980
3037
  }
@@ -2996,7 +3053,7 @@ var RedlineEngine = class {
2996
3053
  this.timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
2997
3054
  const w16du_ns = "http://schemas.microsoft.com/office/word/2023/wordml/word16du";
2998
3055
  for (const part of this.doc.pkg.parts) {
2999
- if (part === this.doc.part || part.contentType.includes("wordprocessingml") && part.contentType.endsWith("+xml")) {
3056
+ if (part === this.doc.part) {
3000
3057
  if (!part._element.hasAttribute("xmlns:w16du")) {
3001
3058
  part._element.setAttribute("xmlns:w16du", w16du_ns);
3002
3059
  }
@@ -3013,15 +3070,59 @@ var RedlineEngine = class {
3013
3070
  }
3014
3071
  return null;
3015
3072
  }
3073
+ /**
3074
+ * Best-effort "did you mean" hint for a failed target. The common loop trap
3075
+ * (observed in the field) is an anchored regex like `^\( x \)$` against a
3076
+ * mid-document string: ^/$ bind to the whole full_text, so it never matches
3077
+ * even though the literal `( x )` is present. We strip regex anchoring/escapes
3078
+ * and probe full_text for a literal occurrence; if found, we tell the model
3079
+ * the exact literal that WOULD match so it drops the anchors instead of
3080
+ * escalating the regex further.
3081
+ */
3082
+ _nearest_match_hint(target_text, is_regex) {
3083
+ if (!target_text) return "";
3084
+ let probe = target_text;
3085
+ if (is_regex) {
3086
+ probe = probe.replace(/^\^/, "").replace(/\$$/, "");
3087
+ probe = probe.replace(/^\\s\*/, "").replace(/\\s\*$/, "");
3088
+ probe = probe.replace(/\\([.^$*+?()[\]{}|\\/])/g, "$1");
3089
+ }
3090
+ probe = probe.trim();
3091
+ if (!probe || probe === target_text) {
3092
+ if (!is_regex) return "";
3093
+ }
3094
+ const idx = this.mapper.full_text.indexOf(probe);
3095
+ if (idx !== -1) {
3096
+ const ctx_start = Math.max(0, idx - 15);
3097
+ const ctx_end = Math.min(
3098
+ this.mapper.full_text.length,
3099
+ idx + probe.length + 15
3100
+ );
3101
+ const ctx = this.mapper.full_text.substring(ctx_start, ctx_end).replace(/\n/g, " ");
3102
+ return `
3103
+ Did you mean the literal "${probe}"? It appears in the document (\u2026${ctx}\u2026). If you used a regex, drop the ^/$ anchors \u2014 they match the start/end of the entire document, not a line.`;
3104
+ }
3105
+ return "";
3106
+ }
3016
3107
  _build_edit_context_previews(edit) {
3017
3108
  if (edit.type !== "modify") return [null, null];
3018
3109
  if (edit._resolved_proxy_edit) {
3019
3110
  edit = edit._resolved_proxy_edit;
3020
3111
  }
3021
- const start_idx = edit._resolved_start_idx;
3112
+ let start_idx = edit._resolved_start_idx;
3022
3113
  if (start_idx === void 0 || start_idx === null) return [null, null];
3023
- const target_text = edit.target_text || "";
3024
- const new_text = edit.new_text || "";
3114
+ let target_text = edit.target_text || "";
3115
+ let new_text = edit.new_text || "";
3116
+ const [clean_target, target_style] = this._parse_markdown_style(target_text);
3117
+ if (target_style && target_style.startsWith("Heading")) {
3118
+ const prefix_len = target_text.length - clean_target.length;
3119
+ start_idx += prefix_len;
3120
+ target_text = clean_target;
3121
+ }
3122
+ const [clean_new, new_style] = this._parse_markdown_style(new_text);
3123
+ if (new_style && new_style.startsWith("Heading")) {
3124
+ new_text = clean_new;
3125
+ }
3025
3126
  const length = target_text.length;
3026
3127
  const active_mapper = edit._active_mapper_ref || this.mapper;
3027
3128
  const full_text = active_mapper.full_text;
@@ -3245,6 +3346,93 @@ var RedlineEngine = class {
3245
3346
  }
3246
3347
  }
3247
3348
  }
3349
+ /**
3350
+ * Revert every tracked change, returning the document to the state it had
3351
+ * before any revision was proposed. The exact inverse of
3352
+ * accept_all_revisions:
3353
+ *
3354
+ * - <w:ins> -> removed together with all of its content (the proposed
3355
+ * insertion never existed); an inserted row (<w:ins> in
3356
+ * <w:trPr>) drops the whole row.
3357
+ * - <w:del> -> unwrapped, restoring the original text (<w:delText> becomes
3358
+ * <w:t> again); a row-deletion mark in <w:trPr> is removed so
3359
+ * the row survives.
3360
+ * - paragraph-mark <w:del> in pPr/rPr -> removed, undoing a proposed merge.
3361
+ *
3362
+ * Comments are annotations, not revisions, so standalone comments are left in
3363
+ * place; only anchors stranded inside a rejected insertion are cleaned up.
3364
+ *
3365
+ * Insertions are reverted before deletions are restored so a deletion nested
3366
+ * inside a foreign author's insertion is removed wholesale with the insertion
3367
+ * — the contingent text disappears rather than being promoted to committed
3368
+ * body text.
3369
+ *
3370
+ * Known limitation: tracked paragraph STRUCTURE changes (a split recorded as a
3371
+ * pilcrow <w:ins>, or a merge recorded as a pilcrow <w:del>) are reverted only
3372
+ * to the extent of dropping/keeping the mark; the original paragraph boundary
3373
+ * is not reconstructed, because the merge protocol coalesces paragraphs
3374
+ * destructively at edit time. Reverting run-level insertions/deletions (the
3375
+ * common case) is exact. This limitation is shared with the Python engine.
3376
+ */
3377
+ reject_all_revisions() {
3378
+ const parts_to_process = [this.doc.element];
3379
+ for (const part of this.doc.pkg.parts) {
3380
+ if (part === this.doc.part) continue;
3381
+ if (part.contentType.includes("wordprocessingml") && part.contentType.endsWith("+xml")) {
3382
+ parts_to_process.push(part._element);
3383
+ }
3384
+ }
3385
+ for (const root_element of parts_to_process) {
3386
+ const insNodes = findAllDescendants(root_element, "w:ins");
3387
+ for (const ins of insNodes) {
3388
+ const parent = ins.parentNode;
3389
+ if (!parent) continue;
3390
+ this._clean_wrapping_comments(ins);
3391
+ this._delete_comments_in_element(ins);
3392
+ if (parent.tagName === "w:trPr") {
3393
+ const row = parent.parentNode;
3394
+ if (row && row.parentNode) {
3395
+ row.parentNode.removeChild(row);
3396
+ }
3397
+ } else {
3398
+ parent.removeChild(ins);
3399
+ }
3400
+ }
3401
+ const pNodes = findAllDescendants(root_element, "w:p");
3402
+ for (const p of pNodes) {
3403
+ const pPr = findChild(p, "w:pPr");
3404
+ if (pPr) {
3405
+ const rPr = findChild(pPr, "w:rPr");
3406
+ const delMark = rPr ? findChild(rPr, "w:del") : null;
3407
+ if (rPr && delMark) {
3408
+ rPr.removeChild(delMark);
3409
+ }
3410
+ }
3411
+ }
3412
+ const delNodes = findAllDescendants(root_element, "w:del");
3413
+ for (const d of delNodes) {
3414
+ const parent = d.parentNode;
3415
+ if (!parent) continue;
3416
+ this._clean_wrapping_comments(d);
3417
+ if (parent.tagName === "w:trPr") {
3418
+ parent.removeChild(d);
3419
+ continue;
3420
+ }
3421
+ const delTexts = Array.from(d.getElementsByTagName("w:delText"));
3422
+ for (const dt of delTexts) {
3423
+ const t = d.ownerDocument.createElement("w:t");
3424
+ t.textContent = dt.textContent;
3425
+ if (dt.hasAttribute("xml:space"))
3426
+ t.setAttribute("xml:space", "preserve");
3427
+ dt.parentNode?.replaceChild(t, dt);
3428
+ }
3429
+ while (d.firstChild) {
3430
+ parent.insertBefore(d.firstChild, d);
3431
+ }
3432
+ parent.removeChild(d);
3433
+ }
3434
+ }
3435
+ }
3248
3436
  _getNextId() {
3249
3437
  this.current_id++;
3250
3438
  return this.current_id.toString();
@@ -3773,28 +3961,34 @@ var RedlineEngine = class {
3773
3961
  }
3774
3962
  }
3775
3963
  }
3776
- validate_edits(edits) {
3964
+ validate_edits(edits, index_offset = 0) {
3777
3965
  const errors = [];
3778
3966
  if (!this.mapper.full_text) this.mapper["_build_map"]();
3779
- errors.push(...validate_edit_strings(edits));
3967
+ errors.push(...validate_edit_strings(edits, index_offset));
3780
3968
  for (let i = 0; i < edits.length; i++) {
3781
3969
  const edit = edits[i];
3782
3970
  if (typeof edit !== "object" || edit === null) {
3783
3971
  errors.push(
3784
- `- Edit ${i + 1} Failed: Invalid change format. Expected a JSON object, but received a primitive ${typeof edit}. Do not pass raw strings.`
3972
+ `- Edit ${i + 1 + index_offset} Failed: Invalid change format. Expected a JSON object, but received a primitive ${typeof edit}. Do not pass raw strings.`
3785
3973
  );
3786
3974
  continue;
3787
3975
  }
3788
3976
  if (!edit.target_text) continue;
3789
3977
  const is_regex = edit.regex || false;
3790
3978
  const match_mode = edit.match_mode || "strict";
3791
- let matches = this.mapper.find_all_match_indices(edit.target_text, is_regex);
3979
+ let matches = this.mapper.find_all_match_indices(
3980
+ edit.target_text,
3981
+ is_regex
3982
+ );
3792
3983
  let activeText = this.mapper.full_text;
3793
3984
  let target_mapper = this.mapper;
3794
3985
  if (matches.length === 0) {
3795
3986
  if (!this.clean_mapper)
3796
3987
  this.clean_mapper = new DocumentMapper(this.doc, true);
3797
- matches = this.clean_mapper.find_all_match_indices(edit.target_text, is_regex);
3988
+ matches = this.clean_mapper.find_all_match_indices(
3989
+ edit.target_text,
3990
+ is_regex
3991
+ );
3798
3992
  if (matches.length > 0) {
3799
3993
  activeText = this.clean_mapper.full_text;
3800
3994
  target_mapper = this.clean_mapper;
@@ -3816,7 +4010,10 @@ var RedlineEngine = class {
3816
4010
  if (!this.original_mapper) {
3817
4011
  this.original_mapper = new DocumentMapper(this.doc, false, true);
3818
4012
  }
3819
- const orig_matches = this.original_mapper.find_all_match_indices(edit.target_text, is_regex);
4013
+ const orig_matches = this.original_mapper.find_all_match_indices(
4014
+ edit.target_text,
4015
+ is_regex
4016
+ );
3820
4017
  if (orig_matches.length > 0) {
3821
4018
  is_deleted_text = true;
3822
4019
  for (const [start, length] of orig_matches) {
@@ -3845,27 +4042,32 @@ var RedlineEngine = class {
3845
4042
  if (is_deleted_text) {
3846
4043
  const author_phrase = deleted_authors.size > 0 ? `by ${Array.from(deleted_authors).sort().join(", ")}` : "by an existing revision";
3847
4044
  errors.push(
3848
- `- Edit ${i + 1} Failed: Target text matches text inside a tracked deletion ${author_phrase}. Reject/accept that change first or target the active replacement text instead.`
4045
+ `- Edit ${i + 1 + index_offset} Failed: Target text matches text inside a tracked deletion ${author_phrase}. Reject/accept that change first or target the active replacement text instead.`
3849
4046
  );
3850
4047
  } else {
4048
+ const hint = this._nearest_match_hint(edit.target_text, is_regex);
3851
4049
  errors.push(
3852
- `- Edit ${i + 1} Failed: Target text not found in document:
3853
- "${edit.target_text}"`
4050
+ `- Edit ${i + 1 + index_offset} Failed: Target text not found in document:
4051
+ "${edit.target_text}"${hint}`
3854
4052
  );
3855
4053
  }
3856
4054
  } else if (matches.length > 1 && match_mode === "strict") {
3857
- const positions = matches.map(([start, length]) => [
3858
- start,
3859
- start + length
3860
- ]);
3861
- errors.push(
3862
- format_ambiguity_error(
3863
- i + 1,
3864
- edit.target_text,
3865
- activeText,
3866
- positions
3867
- )
3868
- );
4055
+ if (edit.target_text.includes("|")) {
4056
+ matches = matches.slice(0, 1);
4057
+ } else {
4058
+ const positions = matches.map(([start, length]) => [
4059
+ start,
4060
+ start + length
4061
+ ]);
4062
+ errors.push(
4063
+ format_ambiguity_error(
4064
+ i + 1 + index_offset,
4065
+ edit.target_text,
4066
+ activeText,
4067
+ positions
4068
+ )
4069
+ );
4070
+ }
3869
4071
  }
3870
4072
  if (matches.length === 1) {
3871
4073
  const [m_start, m_len] = matches[0];
@@ -3878,29 +4080,36 @@ var RedlineEngine = class {
3878
4080
  (edit.new_text || "").length - sfx
3879
4081
  );
3880
4082
  if (final_target.includes("\n\n")) {
3881
- if (final_new.includes("\n\n")) {
3882
- const parts = matched.split("\n\n");
3883
- if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
3884
- errors.push(
3885
- `- Edit ${i + 1} Failed: target_text spans a paragraph boundary with body text on both sides. The paragraph break is a structural element, not literal text, so it cannot be replaced as a single span without corrupting the document. Split this into one edit per paragraph.`
3886
- );
3887
- }
3888
- } else {
3889
- const parts = final_target.split("\n\n");
3890
- if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
3891
- errors.push(
3892
- `- Edit ${i + 1} Failed: target_text spans a paragraph boundary with body text on both sides. The paragraph break is a structural element, not literal text, so it cannot be replaced as a single span without corrupting the document. Split this into one edit per paragraph.`
3893
- );
4083
+ const balanced = matched.split("\n\n").length === (edit.new_text || "").split("\n\n").length;
4084
+ if (!balanced) {
4085
+ if (final_new.includes("\n\n")) {
4086
+ const parts = matched.split("\n\n");
4087
+ if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
4088
+ errors.push(
4089
+ `- Edit ${i + 1 + index_offset} Failed: target_text spans a paragraph boundary with body text on both sides. The paragraph break is a structural element, not literal text, so it cannot be replaced as a single span without corrupting the document. Split this into one edit per paragraph.`
4090
+ );
4091
+ }
4092
+ } else {
4093
+ const parts = final_target.split("\n\n");
4094
+ if (parts.length >= 2 && parts[0].trim() !== "" && parts[parts.length - 1].trim() !== "") {
4095
+ errors.push(
4096
+ `- Edit ${i + 1 + index_offset} Failed: target_text spans a paragraph boundary with body text on both sides. The paragraph break is a structural element, not literal text, so it cannot be replaced as a single span without corrupting the document. Split this into one edit per paragraph.`
4097
+ );
4098
+ }
3894
4099
  }
3895
4100
  }
3896
4101
  }
3897
4102
  }
3898
4103
  for (const [start, length] of matches) {
3899
- const spans = this.mapper.spans.filter(
4104
+ const spans = target_mapper.spans.filter(
3900
4105
  (s) => s.end > start && s.start < start + length
3901
4106
  );
3902
- const nestedAuthors = /* @__PURE__ */ new Set();
4107
+ const insAuthors = /* @__PURE__ */ new Set();
4108
+ const commentAuthors = /* @__PURE__ */ new Set();
4109
+ let hasNonForeignRealText = false;
3903
4110
  for (const s of spans) {
4111
+ if (s.run === null) continue;
4112
+ let isForeignIns = false;
3904
4113
  if (s.ins_id) {
3905
4114
  const insNodes = findAllDescendants(
3906
4115
  this.doc.element,
@@ -3908,21 +4117,35 @@ var RedlineEngine = class {
3908
4117
  ).filter((n) => n.getAttribute("w:id") === s.ins_id);
3909
4118
  if (insNodes.length > 0) {
3910
4119
  const auth = insNodes[0].getAttribute("w:author");
3911
- if (auth && auth !== this.author) nestedAuthors.add(auth);
4120
+ if (auth && auth !== this.author) {
4121
+ insAuthors.add(auth);
4122
+ isForeignIns = true;
4123
+ }
3912
4124
  }
3913
4125
  }
4126
+ if (!isForeignIns) hasNonForeignRealText = true;
4127
+ }
4128
+ for (const s of spans) {
3914
4129
  if (s.comment_ids) {
3915
4130
  for (const cid of s.comment_ids) {
3916
4131
  const c_data = this.mapper.comments_map[cid];
3917
4132
  if (c_data && c_data.author && c_data.author !== this.author) {
3918
- nestedAuthors.add(c_data.author);
4133
+ commentAuthors.add(c_data.author);
3919
4134
  }
3920
4135
  }
3921
4136
  }
3922
4137
  }
3923
- if (nestedAuthors.size > 0) {
4138
+ if (insAuthors.size > 0 || commentAuthors.size > 0) {
4139
+ const fullyWithinForeignIns = insAuthors.size > 0 && !hasNonForeignRealText && commentAuthors.size === 0;
4140
+ if ((match_mode === "strict" || match_mode === "first") && fullyWithinForeignIns) {
4141
+ continue;
4142
+ }
4143
+ const nestedAuthors = /* @__PURE__ */ new Set([
4144
+ ...insAuthors,
4145
+ ...commentAuthors
4146
+ ]);
3924
4147
  errors.push(
3925
- `- Edit ${i + 1} Failed: Modification targets an active insertion from another author (${Array.from(nestedAuthors).join(", ")}). Accept that change first or scope your edit outside of it.`
4148
+ `- Edit ${i + 1 + index_offset} Failed: Modification targets an active insertion from another author (${Array.from(nestedAuthors).join(", ")}). Accept that change first or scope your edit outside of it.`
3926
4149
  );
3927
4150
  }
3928
4151
  }
@@ -3984,22 +4207,13 @@ var RedlineEngine = class {
3984
4207
  });
3985
4208
  }
3986
4209
  if (dry_run) {
3987
- const baselines = /* @__PURE__ */ new Map();
3988
- for (const part of this.doc.pkg.parts) {
3989
- if (part._element) {
3990
- baselines.set(part, part._element.cloneNode(true));
3991
- }
3992
- }
4210
+ const snapshot = takeSnapshot(this.doc);
4211
+ const originalCurrentId = this.current_id;
3993
4212
  try {
3994
4213
  return this._process_batch_internal(changes, true);
3995
4214
  } finally {
3996
- for (const [part, originalEl] of baselines.entries()) {
3997
- const doc = part._element.ownerDocument;
3998
- if (doc && doc.documentElement) {
3999
- doc.replaceChild(originalEl, doc.documentElement);
4000
- }
4001
- part._element = originalEl;
4002
- }
4215
+ restoreSnapshot(this.doc, snapshot);
4216
+ this.current_id = originalCurrentId;
4003
4217
  this.mapper = new DocumentMapper(this.doc);
4004
4218
  this.comments_manager = new CommentsManager(this.doc);
4005
4219
  this.clean_mapper = null;
@@ -4009,6 +4223,13 @@ var RedlineEngine = class {
4009
4223
  }
4010
4224
  }
4011
4225
  _process_batch_internal(changes, dry_run_mode = false) {
4226
+ for (const c of changes) {
4227
+ if (c && typeof c === "object" && c.type === "modify" && c.target_text && c.new_text) {
4228
+ const [strippedTarget, strippedNew] = stripMatchingHeadingHashes(c.target_text, c.new_text);
4229
+ c.target_text = strippedTarget;
4230
+ c.new_text = strippedNew;
4231
+ }
4232
+ }
4012
4233
  this.skipped_details = [];
4013
4234
  const actions = changes.filter(
4014
4235
  (c) => c !== null && typeof c === "object" && ["accept", "reject", "reply"].includes(c.type)
@@ -4017,13 +4238,13 @@ var RedlineEngine = class {
4017
4238
  (c) => c === null || typeof c !== "object" || !["accept", "reject", "reply"].includes(c.type)
4018
4239
  );
4019
4240
  if (!dry_run_mode) {
4020
- const all_errors = [];
4021
- if (actions.length > 0) {
4022
- all_errors.push(...this.validate_review_actions(actions));
4023
- }
4024
- if (edits.length > 0) {
4025
- all_errors.push(...this.validate_edits(edits));
4026
- }
4241
+ const action_errors = actions.length > 0 ? this.validate_review_actions(actions) : [];
4242
+ const validate_edits_now = edits.length > 0 && action_errors.length > 0;
4243
+ const edit_errors = validate_edits_now ? this.validate_edits(edits) : [];
4244
+ const all_errors = [
4245
+ ...action_errors,
4246
+ ...edit_errors
4247
+ ];
4027
4248
  if (all_errors.length > 0) {
4028
4249
  throw new BatchValidationError(all_errors);
4029
4250
  }
@@ -4057,8 +4278,9 @@ var RedlineEngine = class {
4057
4278
  let skipped_edits = 0;
4058
4279
  if (edits.length > 0) {
4059
4280
  if (dry_run_mode) {
4060
- for (const edit of edits) {
4061
- const single_errors = this.validate_edits([edit]);
4281
+ for (let i = 0; i < edits.length; i++) {
4282
+ const edit = edits[i];
4283
+ const single_errors = this.validate_edits([edit], i);
4062
4284
  const warning = this._check_punctuation_warning(
4063
4285
  edit.target_text || ""
4064
4286
  );
@@ -4092,6 +4314,8 @@ var RedlineEngine = class {
4092
4314
  occurrences_modified: edit._occurrences_modified || 0,
4093
4315
  match_mode: edit.match_mode || "strict"
4094
4316
  });
4317
+ this.mapper = new DocumentMapper(this.doc);
4318
+ this.clean_mapper = null;
4095
4319
  } else {
4096
4320
  skipped_edits++;
4097
4321
  const error_msg = this.skipped_details.length > 0 ? this.skipped_details[this.skipped_details.length - 1] : "Failed to apply edit";
@@ -4107,15 +4331,37 @@ var RedlineEngine = class {
4107
4331
  }
4108
4332
  }
4109
4333
  } else {
4110
- const errors = this.validate_edits(edits);
4111
- if (errors.length > 0) {
4112
- throw new BatchValidationError(errors);
4113
- }
4114
- const cloned_edits = edits.map((e) => JSON.parse(JSON.stringify(e)));
4115
- const res = this.apply_edits(cloned_edits, page_offsets);
4116
- applied_edits = cloned_edits.filter((e) => e._applied_status).length;
4117
- skipped_edits = cloned_edits.length - applied_edits;
4118
- for (const edit of cloned_edits) {
4334
+ const snapshot = takeSnapshot(this.doc);
4335
+ const originalCurrentId = this.current_id;
4336
+ try {
4337
+ const sequential_errors = [];
4338
+ for (let i = 0; i < edits.length; i++) {
4339
+ const edit = edits[i];
4340
+ const single_errors = this.validate_edits([edit], i);
4341
+ if (single_errors.length > 0) {
4342
+ sequential_errors.push(...single_errors);
4343
+ } else {
4344
+ this.apply_edits([edit], page_offsets);
4345
+ this.mapper = new DocumentMapper(this.doc);
4346
+ this.clean_mapper = null;
4347
+ }
4348
+ }
4349
+ if (sequential_errors.length > 0) {
4350
+ throw new BatchValidationError(sequential_errors);
4351
+ }
4352
+ } catch (err) {
4353
+ restoreSnapshot(this.doc, snapshot);
4354
+ this.current_id = originalCurrentId;
4355
+ this.mapper = new DocumentMapper(this.doc);
4356
+ this.comments_manager = new CommentsManager(this.doc);
4357
+ this.clean_mapper = null;
4358
+ throw err;
4359
+ }
4360
+ applied_edits = edits.filter(
4361
+ (e) => e._applied_status
4362
+ ).length;
4363
+ skipped_edits = edits.length - applied_edits;
4364
+ for (const edit of edits) {
4119
4365
  const success = edit._applied_status || false;
4120
4366
  const error_msg = edit._error_msg || null;
4121
4367
  const warning = this._check_punctuation_warning(
@@ -4234,6 +4480,7 @@ var RedlineEngine = class {
4234
4480
  (a, b) => (b[0]._resolved_start_idx || 0) - (a[0]._resolved_start_idx || 0)
4235
4481
  );
4236
4482
  const occupied_ranges = [];
4483
+ const counted_split_groups = /* @__PURE__ */ new Set();
4237
4484
  for (const [edit, orig_new] of resolved_edits) {
4238
4485
  const start = edit._resolved_start_idx || 0;
4239
4486
  const end = start + (edit.target_text ? edit.target_text.length : 0);
@@ -4262,21 +4509,38 @@ var RedlineEngine = class {
4262
4509
  success = this._apply_table_edit(edit, false);
4263
4510
  }
4264
4511
  if (success) {
4265
- applied++;
4512
+ const group_id = edit._split_group_id;
4513
+ const first_in_group = group_id === void 0 || group_id === null || !counted_split_groups.has(group_id);
4514
+ if (first_in_group && group_id !== void 0 && group_id !== null) {
4515
+ counted_split_groups.add(group_id);
4516
+ }
4517
+ if (first_in_group) applied++;
4266
4518
  occupied_ranges.push([start, end]);
4267
4519
  edit._applied_status = true;
4268
4520
  const parent = edit._parent_edit_ref;
4269
4521
  if (parent) {
4270
4522
  parent._applied_status = true;
4271
- parent._occurrences_modified = (parent._occurrences_modified || 0) + 1;
4272
- const [path, page] = this._get_heading_path_and_page(start, this.mapper.full_text, page_offsets);
4523
+ if (first_in_group) {
4524
+ parent._occurrences_modified = (parent._occurrences_modified || 0) + 1;
4525
+ }
4526
+ const [path, page] = this._get_heading_path_and_page(
4527
+ start,
4528
+ this.mapper.full_text,
4529
+ page_offsets
4530
+ );
4273
4531
  const pages = parent._pages || [];
4274
4532
  if (!pages.includes(page)) pages.unshift(page);
4275
4533
  parent._pages = pages;
4276
4534
  parent._heading_path = path;
4277
4535
  } else {
4278
- edit._occurrences_modified = (edit._occurrences_modified || 0) + 1;
4279
- const [path, page] = this._get_heading_path_and_page(start, this.mapper.full_text, page_offsets);
4536
+ if (first_in_group) {
4537
+ edit._occurrences_modified = (edit._occurrences_modified || 0) + 1;
4538
+ }
4539
+ const [path, page] = this._get_heading_path_and_page(
4540
+ start,
4541
+ this.mapper.full_text,
4542
+ page_offsets
4543
+ );
4280
4544
  const pages = edit._pages || [];
4281
4545
  if (!pages.includes(page)) pages.unshift(page);
4282
4546
  edit._pages = pages;
@@ -4437,12 +4701,18 @@ var RedlineEngine = class {
4437
4701
  if (!edit.target_text) return null;
4438
4702
  const is_regex = edit.regex || false;
4439
4703
  const match_mode = edit.match_mode || "strict";
4440
- let matches = this.mapper.find_all_match_indices(edit.target_text, is_regex);
4704
+ let matches = this.mapper.find_all_match_indices(
4705
+ edit.target_text,
4706
+ is_regex
4707
+ );
4441
4708
  let use_clean_map = false;
4442
4709
  if (matches.length === 0) {
4443
4710
  if (!this.clean_mapper)
4444
4711
  this.clean_mapper = new DocumentMapper(this.doc, true);
4445
- matches = this.clean_mapper.find_all_match_indices(edit.target_text, is_regex);
4712
+ matches = this.clean_mapper.find_all_match_indices(
4713
+ edit.target_text,
4714
+ is_regex
4715
+ );
4446
4716
  if (matches.length > 0) use_clean_map = true;
4447
4717
  else return null;
4448
4718
  }
@@ -4467,14 +4737,49 @@ var RedlineEngine = class {
4467
4737
  start_idx + match_len
4468
4738
  );
4469
4739
  let current_effective_new_text = edit.new_text || "";
4740
+ if (/^\{#cell:[^}]+\}$/.test(actual_doc_text.trim())) {
4741
+ let ins_text = current_effective_new_text;
4742
+ ins_text = ins_text.split(actual_doc_text.trim()).join("");
4743
+ if (ins_text) {
4744
+ all_sub_edits.push({
4745
+ type: "modify",
4746
+ target_text: "",
4747
+ new_text: ins_text,
4748
+ comment: edit.comment,
4749
+ // Insert at the anchor token's start so the new run lands inside
4750
+ // the cell paragraph that get_insertion_anchor resolves there.
4751
+ _match_start_index: start_idx,
4752
+ _internal_op: "INSERTION",
4753
+ _active_mapper_ref: active_mapper
4754
+ });
4755
+ } else if (edit.comment) {
4756
+ all_sub_edits.push({
4757
+ type: "modify",
4758
+ target_text: "",
4759
+ new_text: "",
4760
+ comment: edit.comment,
4761
+ _match_start_index: start_idx,
4762
+ _internal_op: "COMMENT_ONLY",
4763
+ _active_mapper_ref: active_mapper
4764
+ });
4765
+ }
4766
+ continue;
4767
+ }
4470
4768
  if (is_regex && current_effective_new_text) {
4471
4769
  try {
4472
- current_effective_new_text = actual_doc_text.replace(new RegExp(edit.target_text), current_effective_new_text);
4770
+ current_effective_new_text = actual_doc_text.replace(
4771
+ new RegExp(edit.target_text),
4772
+ current_effective_new_text
4773
+ );
4473
4774
  } catch (e) {
4474
4775
  }
4475
4776
  }
4476
- const [edit_target_clean, edit_target_style] = this._parse_markdown_style(edit.target_text);
4477
- const [edit_new_clean, edit_new_style] = this._parse_markdown_style(current_effective_new_text);
4777
+ const [edit_target_clean, edit_target_style] = this._parse_markdown_style(
4778
+ edit.target_text
4779
+ );
4780
+ const [edit_new_clean, edit_new_style] = this._parse_markdown_style(
4781
+ current_effective_new_text
4782
+ );
4478
4783
  if (edit_target_style !== edit_new_style) {
4479
4784
  const [actual_clean] = this._parse_markdown_style(actual_doc_text);
4480
4785
  const final_target2 = actual_clean;
@@ -4513,7 +4818,9 @@ var RedlineEngine = class {
4513
4818
  let effective_start_idx = start_idx;
4514
4819
  if (current_effective_new_text.startsWith(actual_doc_text)) {
4515
4820
  effective_op = "INSERTION";
4516
- final_new = current_effective_new_text.substring(actual_doc_text.length);
4821
+ final_new = current_effective_new_text.substring(
4822
+ actual_doc_text.length
4823
+ );
4517
4824
  effective_start_idx = start_idx + match_len;
4518
4825
  } else {
4519
4826
  const [prefix_len, suffix_len] = trim_common_context(
@@ -4525,6 +4832,51 @@ var RedlineEngine = class {
4525
4832
  final_target = actual_doc_text.substring(prefix_len, t_end);
4526
4833
  final_new = current_effective_new_text.substring(prefix_len, n_end);
4527
4834
  effective_start_idx = start_idx + prefix_len;
4835
+ const target_segs = actual_doc_text.split("\n\n");
4836
+ const new_segs = current_effective_new_text.split("\n\n");
4837
+ if (actual_doc_text.includes("\n\n") && target_segs.length === new_segs.length) {
4838
+ const split_sub_edits = [];
4839
+ let seg_offset = start_idx;
4840
+ let comment_assigned = false;
4841
+ for (let k = 0; k < target_segs.length; k++) {
4842
+ const t_seg = target_segs[k];
4843
+ const n_seg = new_segs[k];
4844
+ if (t_seg !== n_seg) {
4845
+ const [seg_prefix, seg_suffix] = trim_common_context(t_seg, n_seg);
4846
+ const seg_target = t_seg.substring(
4847
+ seg_prefix,
4848
+ t_seg.length - seg_suffix
4849
+ );
4850
+ const seg_new = n_seg.substring(
4851
+ seg_prefix,
4852
+ n_seg.length - seg_suffix
4853
+ );
4854
+ const seg_start = seg_offset + seg_prefix;
4855
+ let seg_op;
4856
+ if (!seg_target && seg_new) seg_op = "INSERTION";
4857
+ else if (seg_target && !seg_new) seg_op = "DELETION";
4858
+ else if (seg_target && seg_new) seg_op = "MODIFICATION";
4859
+ else seg_op = "COMMENT_ONLY";
4860
+ const seg_comment = edit.comment && !comment_assigned ? edit.comment : null;
4861
+ if (seg_comment) comment_assigned = true;
4862
+ split_sub_edits.push({
4863
+ type: "modify",
4864
+ target_text: seg_target,
4865
+ new_text: seg_new,
4866
+ comment: seg_comment,
4867
+ _match_start_index: seg_start,
4868
+ _internal_op: seg_op,
4869
+ _active_mapper_ref: active_mapper,
4870
+ _split_group_id: start_idx
4871
+ });
4872
+ }
4873
+ seg_offset += t_seg.length + 2;
4874
+ }
4875
+ if (split_sub_edits.length > 0) {
4876
+ for (const sub of split_sub_edits) all_sub_edits.push(sub);
4877
+ continue;
4878
+ }
4879
+ }
4528
4880
  if (!final_target && final_new) effective_op = "INSERTION";
4529
4881
  else if (final_target && !final_new) effective_op = "DELETION";
4530
4882
  else if (final_target && final_new) effective_op = "MODIFICATION";
@@ -4544,6 +4896,35 @@ var RedlineEngine = class {
4544
4896
  if (match_mode === "all" || all_sub_edits.length > 1) return all_sub_edits;
4545
4897
  return all_sub_edits[0];
4546
4898
  }
4899
+ /**
4900
+ * Split a <w:ins> so that everything up to and INCLUDING split_after stays in
4901
+ * a left <w:ins>, new_elem is placed between, and the remainder moves to a
4902
+ * right <w:ins> — all at the grandparent level. Used when revising another
4903
+ * author's pending insertion: the <w:del> stays nested in their <w:ins> while
4904
+ * our replacement <w:ins> lands as a sibling, so we never nest <w:ins> in
4905
+ * <w:ins>.
4906
+ */
4907
+ _insert_and_split_ins(parent_ins, split_after, new_elem) {
4908
+ const grandparent = parent_ins.parentNode;
4909
+ if (!grandparent) return;
4910
+ const left = parent_ins.cloneNode(false);
4911
+ const right = parent_ins.cloneNode(false);
4912
+ let toRight = false;
4913
+ for (const kid of Array.from(parent_ins.childNodes)) {
4914
+ parent_ins.removeChild(kid);
4915
+ if (!toRight) {
4916
+ left.appendChild(kid);
4917
+ if (kid === split_after) toRight = true;
4918
+ } else {
4919
+ right.appendChild(kid);
4920
+ }
4921
+ }
4922
+ if (left.childNodes.length > 0) grandparent.insertBefore(left, parent_ins);
4923
+ grandparent.insertBefore(new_elem, parent_ins);
4924
+ if (right.childNodes.length > 0)
4925
+ grandparent.insertBefore(right, parent_ins);
4926
+ grandparent.removeChild(parent_ins);
4927
+ }
4547
4928
  _apply_single_edit_indexed(edit, orig_new, rebuild_map) {
4548
4929
  let op = edit._internal_op;
4549
4930
  const active_mapper = edit._active_mapper_ref || this.mapper;
@@ -4594,7 +4975,12 @@ var RedlineEngine = class {
4594
4975
  const first_anchor = ascend_to_paragraph_child(first_el, start_p);
4595
4976
  const last_anchor = ascend_to_paragraph_child(last_el, end_p);
4596
4977
  if (start_p === end_p) {
4597
- this._attach_comment(start_p, first_anchor, last_anchor, edit.comment);
4978
+ this._attach_comment(
4979
+ start_p,
4980
+ first_anchor,
4981
+ last_anchor,
4982
+ edit.comment
4983
+ );
4598
4984
  } else {
4599
4985
  this._attach_comment_spanning(
4600
4986
  start_p,
@@ -4746,7 +5132,16 @@ var RedlineEngine = class {
4746
5132
  const is_inline_first = result.first_node.tagName === "w:ins";
4747
5133
  if (is_inline_first) {
4748
5134
  if (anchor_run) {
4749
- insertAfter(result.first_node, anchor_run._element);
5135
+ const anchor_parent = anchor_run._element.parentNode;
5136
+ if (anchor_parent && anchor_parent.tagName === "w:ins") {
5137
+ this._insert_and_split_ins(
5138
+ anchor_parent,
5139
+ anchor_run._element,
5140
+ result.first_node
5141
+ );
5142
+ } else {
5143
+ insertAfter(result.first_node, anchor_run._element);
5144
+ }
4750
5145
  } else if (anchor_para) {
4751
5146
  anchor_para._element.appendChild(result.first_node);
4752
5147
  }
@@ -4856,7 +5251,12 @@ var RedlineEngine = class {
4856
5251
  if (result.first_node) {
4857
5252
  const is_inline_first = result.first_node.tagName === "w:ins";
4858
5253
  if (is_inline_first) {
4859
- insertAfter(result.first_node, last_del);
5254
+ const del_parent = last_del.parentNode;
5255
+ if (del_parent && del_parent.tagName === "w:ins") {
5256
+ this._insert_and_split_ins(del_parent, last_del, result.first_node);
5257
+ } else {
5258
+ insertAfter(result.first_node, last_del);
5259
+ }
4860
5260
  ins_elem = result.first_node;
4861
5261
  } else {
4862
5262
  ins_elem = result.last_ins;
@@ -5883,13 +6283,21 @@ function extract_table(table, comments_map, cleanView, cursor, paragraph_offsets
5883
6283
  if (seen_cells.has(cell)) continue;
5884
6284
  seen_cells.add(cell);
5885
6285
  if (!first_cell) cell_cursor += 3;
5886
- const cell_content = _extract_blocks(
6286
+ let cell_content = _extract_blocks(
5887
6287
  cell,
5888
6288
  comments_map,
5889
6289
  cleanView,
5890
6290
  cell_cursor,
5891
6291
  paragraph_offsets
5892
6292
  );
6293
+ if (!cleanView) {
6294
+ const firstP = cell._element.getElementsByTagName("w:p")[0];
6295
+ const paraId = firstP ? firstP.getAttribute("w14:paraId") : null;
6296
+ if (paraId) {
6297
+ const anchor = `{#cell:${paraId}}`;
6298
+ cell_content = cell_content + anchor;
6299
+ }
6300
+ }
5893
6301
  cell_texts.push(cell_content);
5894
6302
  cell_cursor += cell_content.length;
5895
6303
  first_cell = false;