@adeu/core 1.25.0 → 1.26.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.js CHANGED
@@ -1228,12 +1228,27 @@ function get_run_style_markers(run, is_heading = null) {
1228
1228
  }
1229
1229
  return [prefix, suffix];
1230
1230
  }
1231
+ function split_boundary_whitespace(text) {
1232
+ const core = text.trim();
1233
+ if (!core) return ["", "", text];
1234
+ const lead_len = text.length - text.trimStart().length;
1235
+ return [
1236
+ text.substring(0, lead_len),
1237
+ text.substring(lead_len, lead_len + core.length),
1238
+ text.substring(lead_len + core.length)
1239
+ ];
1240
+ }
1231
1241
  function apply_formatting_to_segments(text, prefix, suffix) {
1232
1242
  if (!prefix && !suffix) return text;
1233
1243
  if (!text) return "";
1234
- if (!text.includes("\n")) return `${prefix}${text}${suffix}`;
1244
+ const wrap = (segment) => {
1245
+ const [lead, core, trail] = split_boundary_whitespace(segment);
1246
+ if (!core) return segment;
1247
+ return `${lead}${prefix}${core}${suffix}${trail}`;
1248
+ };
1249
+ if (!text.includes("\n")) return wrap(text);
1235
1250
  const parts = text.split("\n");
1236
- return parts.map((p) => p ? `${prefix}${p}${suffix}` : "").join("\n");
1251
+ return parts.map((p) => p ? wrap(p) : "").join("\n");
1237
1252
  }
1238
1253
  function get_run_text(run) {
1239
1254
  let text = "";
@@ -1686,7 +1701,7 @@ ${header}`;
1686
1701
  this._add_virtual_text(s_tok, current, paragraph);
1687
1702
  current += s_tok.length;
1688
1703
  }
1689
- for (const [kind, txt, r_obj, i_id, d_id, c_ids] of pending_runs) {
1704
+ for (const [kind, txt, r_obj, r_off, i_id, d_id, c_ids] of pending_runs) {
1690
1705
  if (kind === "virtual") {
1691
1706
  this._add_virtual_text(txt, current, paragraph, active_hyperlink_id);
1692
1707
  } else {
@@ -1700,7 +1715,8 @@ ${header}`;
1700
1715
  del_id: d_id || void 0,
1701
1716
  hyperlink_id: active_hyperlink_id || void 0,
1702
1717
  comment_ids: c_ids.length > 0 ? c_ids : void 0,
1703
- part_index: this._current_part_index
1718
+ part_index: this._current_part_index,
1719
+ run_offset: r_off
1704
1720
  };
1705
1721
  this.spans.push(s);
1706
1722
  this._text_chunks.push(txt);
@@ -1735,20 +1751,42 @@ ${header}`;
1735
1751
  if (text === "" || /^\s*$/.test(text)) continue;
1736
1752
  leading_strip_active = false;
1737
1753
  }
1754
+ let run_local = 0;
1755
+ const append_wrapped = (segment) => {
1756
+ const [lead, core, trail] = split_boundary_whitespace(segment);
1757
+ if (!core) {
1758
+ run_parts.push(["real", segment, item, run_local]);
1759
+ run_local += segment.length;
1760
+ return;
1761
+ }
1762
+ if (lead) {
1763
+ run_parts.push(["real", lead, item, run_local]);
1764
+ run_local += lead.length;
1765
+ }
1766
+ if (prefix) run_parts.push(["virtual", prefix, null, 0]);
1767
+ run_parts.push(["real", core, item, run_local]);
1768
+ run_local += core.length;
1769
+ if (suffix) run_parts.push(["virtual", suffix, null, 0]);
1770
+ if (trail) {
1771
+ run_parts.push(["real", trail, item, run_local]);
1772
+ run_local += trail.length;
1773
+ }
1774
+ };
1738
1775
  if (text.includes("\n") && (prefix || suffix)) {
1739
1776
  const parts = text.split("\n");
1740
1777
  for (let idx = 0; idx < parts.length; idx++) {
1741
- if (idx > 0) run_parts.push(["real", "\n", item]);
1742
- if (parts[idx]) {
1743
- if (prefix) run_parts.push(["virtual", prefix, null]);
1744
- run_parts.push(["real", parts[idx], item]);
1745
- if (suffix) run_parts.push(["virtual", suffix, null]);
1778
+ if (idx > 0) {
1779
+ run_parts.push(["real", "\n", item, run_local]);
1780
+ run_local += 1;
1746
1781
  }
1782
+ if (parts[idx]) append_wrapped(parts[idx]);
1747
1783
  }
1784
+ } else if ((prefix || suffix) && text) {
1785
+ append_wrapped(text);
1748
1786
  } else {
1749
- if (prefix) run_parts.push(["virtual", prefix, null]);
1750
- if (text) run_parts.push(["real", text, item]);
1751
- if (suffix) run_parts.push(["virtual", suffix, null]);
1787
+ if (prefix) run_parts.push(["virtual", prefix, null, 0]);
1788
+ if (text) run_parts.push(["real", text, item, 0]);
1789
+ if (suffix) run_parts.push(["virtual", suffix, null, 0]);
1752
1790
  }
1753
1791
  if (this.clean_view && Object.keys(active_del).length > 0) {
1754
1792
  }
@@ -1772,7 +1810,7 @@ ${header}`;
1772
1810
  skip_leading_prefix = true;
1773
1811
  }
1774
1812
  const curr_comment_ids = Array.from(active_ids);
1775
- for (const [kind, txt, r_obj] of run_parts) {
1813
+ for (const [kind, txt, r_obj, r_off] of run_parts) {
1776
1814
  if (skip_leading_prefix && kind === "virtual" && txt === new_style[0]) {
1777
1815
  skip_leading_prefix = false;
1778
1816
  continue;
@@ -1781,6 +1819,7 @@ ${header}`;
1781
1819
  kind,
1782
1820
  txt,
1783
1821
  r_obj,
1822
+ r_off,
1784
1823
  curr_ins_id,
1785
1824
  curr_del_id,
1786
1825
  curr_comment_ids
@@ -1792,11 +1831,12 @@ ${header}`;
1792
1831
  current_wrappers = new_wrappers;
1793
1832
  current_style = new_style;
1794
1833
  const curr_comment_ids = Array.from(active_ids);
1795
- for (const [kind, txt, r_obj] of run_parts) {
1834
+ for (const [kind, txt, r_obj, r_off] of run_parts) {
1796
1835
  pending_runs.push([
1797
1836
  kind,
1798
1837
  txt,
1799
1838
  r_obj,
1839
+ r_off,
1800
1840
  curr_ins_id,
1801
1841
  curr_del_id,
1802
1842
  curr_comment_ids
@@ -2208,39 +2248,43 @@ ${header}`;
2208
2248
  (s) => s.end > start_idx && s.start < end_idx
2209
2249
  );
2210
2250
  if (affected_spans.length === 0) return [];
2211
- const working_runs = affected_spans.filter((s) => s.run !== null).map((s) => s.run);
2212
- if (working_runs.length === 0) return [];
2213
- let dom_modified = false;
2214
- const first_real_span = affected_spans.find((s) => s.run !== null);
2215
- let start_split_adjustment = 0;
2216
- if (first_real_span) {
2217
- const local_start = start_idx - first_real_span.start;
2218
- if (local_start > 0) {
2219
- const idx_in_working = 0;
2220
- const [, right_run] = this._split_run_at_index(
2221
- working_runs[idx_in_working],
2222
- local_start
2223
- );
2224
- working_runs[idx_in_working] = right_run;
2225
- dom_modified = true;
2226
- start_split_adjustment = local_start;
2251
+ const real_spans = affected_spans.filter((s) => s.run !== null);
2252
+ if (real_spans.length === 0) return [];
2253
+ const working_runs = [];
2254
+ for (const s of real_spans) {
2255
+ if (!working_runs.some((r) => r === s.run)) {
2256
+ working_runs.push(s.run);
2227
2257
  }
2228
2258
  }
2229
- const last_real_span = [...affected_spans].reverse().find((s) => s.run !== null);
2230
- if (last_real_span) {
2231
- const is_same_run = first_real_span === last_real_span;
2232
- const run_to_split = working_runs[working_runs.length - 1];
2233
- let overlap_end = Math.min(last_real_span.end, end_idx);
2234
- let local_end = overlap_end - last_real_span.start;
2235
- if (is_same_run && start_split_adjustment > 0) {
2236
- local_end -= start_split_adjustment;
2237
- }
2238
- const run_text = get_run_text(run_to_split);
2239
- if (local_end > 0 && local_end < run_text.length) {
2240
- const [left_run] = this._split_run_at_index(run_to_split, local_end);
2241
- working_runs[working_runs.length - 1] = left_run;
2242
- dom_modified = true;
2243
- }
2259
+ let dom_modified = false;
2260
+ const first_real_span = real_spans[0];
2261
+ let start_split_adjustment = 0;
2262
+ const local_start = start_idx - first_real_span.start + (first_real_span.run_offset || 0);
2263
+ if (local_start > 0) {
2264
+ const split_source = working_runs[0];
2265
+ const [, right_run] = this._split_run_at_index(
2266
+ split_source,
2267
+ local_start
2268
+ );
2269
+ for (let w = 0; w < working_runs.length; w++) {
2270
+ if (working_runs[w] === split_source) working_runs[w] = right_run;
2271
+ }
2272
+ dom_modified = true;
2273
+ start_split_adjustment = local_start;
2274
+ }
2275
+ const last_real_span = real_spans[real_spans.length - 1];
2276
+ const is_same_run = first_real_span.run === last_real_span.run;
2277
+ const run_to_split = working_runs[working_runs.length - 1];
2278
+ const overlap_end = Math.min(last_real_span.end, end_idx);
2279
+ let local_end = overlap_end - last_real_span.start + (last_real_span.run_offset || 0);
2280
+ if (is_same_run && start_split_adjustment > 0) {
2281
+ local_end -= start_split_adjustment;
2282
+ }
2283
+ const run_text = get_run_text(run_to_split);
2284
+ if (local_end > 0 && local_end < run_text.length) {
2285
+ const [left_run] = this._split_run_at_index(run_to_split, local_end);
2286
+ working_runs[working_runs.length - 1] = left_run;
2287
+ dom_modified = true;
2244
2288
  }
2245
2289
  if (dom_modified && rebuild_map) {
2246
2290
  this._build_map();
@@ -2277,7 +2321,7 @@ ${header}`;
2277
2321
  }
2278
2322
  return [null, span.paragraph];
2279
2323
  } else {
2280
- const offset = index - span.start;
2324
+ const offset = index - span.start + (span.run_offset || 0);
2281
2325
  const [left] = this._split_run_at_index(span.run, offset);
2282
2326
  if (rebuild_map) this._build_map();
2283
2327
  return [left, span.paragraph];
@@ -2348,6 +2392,7 @@ ${header}`;
2348
2392
  };
2349
2393
 
2350
2394
  // src/diff.ts
2395
+ import { unzipSync as unzipSync2 } from "fflate";
2351
2396
  import diff_match_patch from "diff-match-patch";
2352
2397
  function _count_standalone_underscores(s) {
2353
2398
  let count = 0;
@@ -2685,6 +2730,37 @@ function _drop_image_marker_hunks(edits, warnings) {
2685
2730
  }
2686
2731
  return kept;
2687
2732
  }
2733
+ function _drop_marker_interior_hunks(edits, text_orig, warnings) {
2734
+ const marker_spans = [];
2735
+ for (const m of text_orig.matchAll(_IMAGE_MARKER_RE)) {
2736
+ marker_spans.push([m.index, m.index + m[0].length]);
2737
+ }
2738
+ if (marker_spans.length === 0) return edits;
2739
+ const kept = [];
2740
+ let warned = false;
2741
+ for (const e of edits) {
2742
+ const idx = e._match_start_index;
2743
+ if (idx === void 0 || idx === null) {
2744
+ kept.push(e);
2745
+ continue;
2746
+ }
2747
+ const end = idx + (e.target_text || "").length;
2748
+ const cuts_into_marker = marker_spans.some(
2749
+ ([m_start, m_end]) => m_start < end && idx < m_end && !(idx <= m_start && m_end <= end)
2750
+ );
2751
+ if (!cuts_into_marker) {
2752
+ kept.push(e);
2753
+ continue;
2754
+ }
2755
+ if (!warned) {
2756
+ warnings.push(
2757
+ "An image's alternative text differs between the documents. Image markers (![alt](docx-image:N)) are read-only projections, so this difference cannot be expressed as a text edit \u2014 it was skipped; update the image or its alt text manually in Word."
2758
+ );
2759
+ warned = true;
2760
+ }
2761
+ }
2762
+ return kept;
2763
+ }
2688
2764
  function _rows_are_plain(text, table) {
2689
2765
  for (const row of table.rows) {
2690
2766
  if (text.substring(row.start, row.end) !== row.cells.join(" | ")) {
@@ -2826,8 +2902,12 @@ function generate_structured_edits(text_orig, struct_orig, text_mod, struct_mod)
2826
2902
  warnings.push(
2827
2903
  `The documents have different part layouts (${kinds_o.join(" + ") || "none"} vs ${kinds_m.join(" + ") || "none"}); comparing flattened text instead. Header/footer additions or removals cannot be expressed as text edits.`
2828
2904
  );
2829
- const flat = _drop_image_marker_hunks(
2830
- generate_edits_from_text(text_orig, text_mod),
2905
+ const flat = _drop_marker_interior_hunks(
2906
+ _drop_image_marker_hunks(
2907
+ generate_edits_from_text(text_orig, text_mod),
2908
+ warnings
2909
+ ),
2910
+ text_orig,
2831
2911
  warnings
2832
2912
  );
2833
2913
  return { edits: [...flat], warnings };
@@ -2931,12 +3011,63 @@ function generate_structured_edits(text_orig, struct_orig, text_mod, struct_mod)
2931
3011
  const modify_edits = edits.filter(
2932
3012
  (e) => e.type === "modify"
2933
3013
  );
2934
- const kept_modifies = new Set(_drop_image_marker_hunks(modify_edits, warnings));
3014
+ const kept_modifies = new Set(
3015
+ _drop_marker_interior_hunks(
3016
+ _drop_image_marker_hunks(modify_edits, warnings),
3017
+ text_orig,
3018
+ warnings
3019
+ )
3020
+ );
2935
3021
  const final_edits = edits.filter(
2936
3022
  (e) => e.type !== "modify" || kept_modifies.has(e)
2937
3023
  );
2938
3024
  return { edits: final_edits, warnings };
2939
3025
  }
3026
+ function collect_media_difference_warnings(original_docx, modified_docx) {
3027
+ const media_hashes = (data) => {
3028
+ const hashes = /* @__PURE__ */ new Map();
3029
+ try {
3030
+ const unzipped = unzipSync2(data);
3031
+ for (const [name, bytes] of Object.entries(unzipped)) {
3032
+ if (!name.startsWith("word/media/")) continue;
3033
+ let h1 = 2166136261;
3034
+ let h2 = 3421674724;
3035
+ for (let i = 0; i < bytes.length; i++) {
3036
+ h1 = Math.imul(h1 ^ bytes[i], 16777619) >>> 0;
3037
+ h2 = Math.imul(h2 ^ bytes[bytes.length - 1 - i], 16777619) >>> 0;
3038
+ }
3039
+ hashes.set(name, `${bytes.length}:${h1.toString(16)}:${h2.toString(16)}`);
3040
+ }
3041
+ } catch {
3042
+ return /* @__PURE__ */ new Map();
3043
+ }
3044
+ return hashes;
3045
+ };
3046
+ const hashes_orig = media_hashes(original_docx);
3047
+ const hashes_mod = media_hashes(modified_docx);
3048
+ const changed = [];
3049
+ const added = [];
3050
+ const removed = [];
3051
+ for (const [name, hash] of hashes_orig) {
3052
+ if (!hashes_mod.has(name)) removed.push(name);
3053
+ else if (hashes_mod.get(name) !== hash) changed.push(name);
3054
+ }
3055
+ for (const name of hashes_mod.keys()) {
3056
+ if (!hashes_orig.has(name)) added.push(name);
3057
+ }
3058
+ changed.sort();
3059
+ added.sort();
3060
+ removed.sort();
3061
+ if (changed.length + added.length + removed.length === 0) return [];
3062
+ const parts = [];
3063
+ if (changed.length) parts.push(`${changed.length} changed`);
3064
+ if (added.length) parts.push(`${added.length} added`);
3065
+ if (removed.length) parts.push(`${removed.length} removed`);
3066
+ const names = [...changed, ...added, ...removed].slice(0, 5).join(", ");
3067
+ return [
3068
+ `The documents' embedded media differ (${parts.join(", ")}: ${names}). This diff compares TEXT only \u2014 an empty edit list does not mean the documents are visually identical. Image changes must be applied manually in Word.`
3069
+ ];
3070
+ }
2940
3071
  function create_unified_diff(original_text, modified_text, context_lines = 3) {
2941
3072
  const dmp = new diff_match_patch.diff_match_patch();
2942
3073
  dmp.Diff_Timeout = 2;
@@ -3891,6 +4022,13 @@ var RedlineEngine = class _RedlineEngine {
3891
4022
  console.error("generate_edits_from_text failed, falling back to wholesale edit", e);
3892
4023
  raw_sub_edits = [];
3893
4024
  }
4025
+ const _marker_only = (text) => {
4026
+ const stripped = text.trim();
4027
+ return stripped.length > 0 && /^[*_]+$/.test(stripped);
4028
+ };
4029
+ raw_sub_edits = raw_sub_edits.filter(
4030
+ (e) => !((!e.target_text || _marker_only(e.target_text)) && (!e.new_text || _marker_only(e.new_text)) && (e.target_text || e.new_text))
4031
+ );
3894
4032
  if (!raw_sub_edits || raw_sub_edits.length === 0) {
3895
4033
  const fallback_edit = {
3896
4034
  type: "modify",
@@ -4671,7 +4809,7 @@ var RedlineEngine = class _RedlineEngine {
4671
4809
  *
4672
4810
  * Does NOT attach comments; callers handle that.
4673
4811
  */
4674
- _track_insert_multiline(text, anchor_run, anchor_paragraph, reuse_id, positional_anchor_el = null) {
4812
+ _track_insert_multiline(text, anchor_run, anchor_paragraph, reuse_id, positional_anchor_el = null, suppress_emphasis = false) {
4675
4813
  if (!text) {
4676
4814
  return {
4677
4815
  first_node: null,
@@ -4735,7 +4873,8 @@ var RedlineEngine = class _RedlineEngine {
4735
4873
  first_clean === lines[0] ? lines[0] : lines[0],
4736
4874
  anchor_run,
4737
4875
  reuse_id,
4738
- xmlDoc
4876
+ xmlDoc,
4877
+ suppress_emphasis
4739
4878
  );
4740
4879
  first_node = inline_ins;
4741
4880
  }
@@ -4799,7 +4938,8 @@ var RedlineEngine = class _RedlineEngine {
4799
4938
  clean_text,
4800
4939
  anchor_run,
4801
4940
  reuse_id,
4802
- xmlDoc
4941
+ xmlDoc,
4942
+ suppress_emphasis
4803
4943
  );
4804
4944
  if (content_ins) {
4805
4945
  new_p.appendChild(content_ins);
@@ -4825,7 +4965,7 @@ var RedlineEngine = class _RedlineEngine {
4825
4965
  * <w:r> elements representing the inline markdown segments of `line_text`.
4826
4966
  * Returns null if line_text is empty.
4827
4967
  */
4828
- _build_tracked_ins_for_line(line_text, anchor_run, reuse_id, xmlDoc) {
4968
+ _build_tracked_ins_for_line(line_text, anchor_run, reuse_id, xmlDoc, suppress_emphasis = false) {
4829
4969
  if (!line_text && line_text !== "") return null;
4830
4970
  const ins = this._create_track_change_tag("w:ins", "", reuse_id);
4831
4971
  const segments = this._parse_inline_markdown(line_text);
@@ -4838,15 +4978,11 @@ var RedlineEngine = class _RedlineEngine {
4838
4978
  const anchor_rPr = findChild(anchor_run._element, "w:rPr");
4839
4979
  if (anchor_rPr) {
4840
4980
  const clone = anchor_rPr.cloneNode(true);
4841
- for (const tag of [
4842
- "w:vanish",
4843
- "w:strike",
4844
- "w:dstrike",
4845
- "w:i",
4846
- "w:iCs",
4847
- "w:b",
4848
- "w:bCs"
4849
- ]) {
4981
+ const strip_tags = ["w:vanish", "w:strike", "w:dstrike", "w:i", "w:iCs"];
4982
+ if (suppress_emphasis) {
4983
+ strip_tags.push("w:b", "w:bCs");
4984
+ }
4985
+ for (const tag of strip_tags) {
4850
4986
  const found = findChild(clone, tag);
4851
4987
  if (found) clone.removeChild(found);
4852
4988
  }
@@ -4881,6 +5017,27 @@ var RedlineEngine = class _RedlineEngine {
4881
5017
  }
4882
5018
  return [text, null];
4883
5019
  }
5020
+ /**
5021
+ * True when this edit's target or replacement text carries explicit
5022
+ * bold/italic markers, making the markers AUTHORITATIVE for the inserted
5023
+ * runs' formatting. Replacing `**X**` with `_X_` must yield italic-only
5024
+ * text, and replacing `**X**` with `X` must yield plain text — inheriting
5025
+ * the replaced span's run properties on top of (or instead of) the
5026
+ * requested markers silently produces the wrong document while the report
5027
+ * claims success (QA 2026-07-19 F-02). Plain-text edits (no markers on
5028
+ * either side) keep inheriting the context style so partial replacements
5029
+ * inside a styled span never lose formatting.
5030
+ */
5031
+ _edit_declares_emphasis(edit) {
5032
+ for (const text of [edit?.target_text, edit?.new_text]) {
5033
+ if (!text || !text.includes("**") && !text.includes("_")) continue;
5034
+ const segments = this._parse_inline_markdown(text);
5035
+ if (segments.some(([, props]) => props && Object.keys(props).length > 0)) {
5036
+ return true;
5037
+ }
5038
+ }
5039
+ return false;
5040
+ }
4884
5041
  _parse_inline_markdown(text, baseStyle = {}) {
4885
5042
  if (!text) return [];
4886
5043
  const tokenPattern = /(\*\*.*?\*\*)|(_.*?_)/;
@@ -5147,6 +5304,16 @@ var RedlineEngine = class _RedlineEngine {
5147
5304
  continue;
5148
5305
  const is_regex = edit.regex || false;
5149
5306
  const match_mode = edit.match_mode || "strict";
5307
+ if (is_regex) {
5308
+ try {
5309
+ new RegExp(edit.target_text);
5310
+ } catch (regex_err) {
5311
+ errors.push(
5312
+ `- Edit ${i + 1 + index_offset} Failed: target_text is not a valid regular expression (${regex_err?.message ?? regex_err}). Fix the pattern, or set "regex": false to match the text literally.`
5313
+ );
5314
+ continue;
5315
+ }
5316
+ }
5150
5317
  let matches = this.mapper.find_all_match_indices(
5151
5318
  edit.target_text,
5152
5319
  is_regex
@@ -5726,6 +5893,14 @@ var RedlineEngine = class _RedlineEngine {
5726
5893
  actions_skipped: skipped_actions,
5727
5894
  edits_applied: applied_edits,
5728
5895
  edits_skipped: skipped_edits,
5896
+ // edits_applied counts change OBJECTS; this is the total number of
5897
+ // document occurrences they modified (match_mode="all" fan-out), so
5898
+ // automation never has to guess which of the two a count means
5899
+ // (QA 2026-07-19 F-21).
5900
+ occurrences_modified: edits_reports.reduce(
5901
+ (sum, r) => sum + (r.occurrences_modified || 0),
5902
+ 0
5903
+ ),
5729
5904
  skipped_details: this.skipped_details,
5730
5905
  edits: edits_reports,
5731
5906
  engine: "node",
@@ -5747,6 +5922,7 @@ var RedlineEngine = class _RedlineEngine {
5747
5922
  }
5748
5923
  edit._applied_status = false;
5749
5924
  edit._error_msg = null;
5925
+ edit._any_sub_failure = false;
5750
5926
  }
5751
5927
  for (const edit of edits) {
5752
5928
  if (typeof edit !== "object" || edit === null) continue;
@@ -5846,17 +6022,18 @@ var RedlineEngine = class _RedlineEngine {
5846
6022
  this.skipped_details.push(msg);
5847
6023
  edit._applied_status = false;
5848
6024
  edit._error_msg = msg;
6025
+ edit._any_sub_failure = true;
5849
6026
  const parent = edit._parent_edit_ref;
5850
6027
  if (parent) {
5851
6028
  parent._applied_status = false;
5852
6029
  parent._error_msg = msg;
6030
+ parent._any_sub_failure = true;
5853
6031
  }
5854
6032
  continue;
5855
6033
  }
5856
6034
  let success = false;
5857
6035
  if (edit.type === "modify") {
5858
- const rebuild = edit._split_group_id !== void 0 && edit._split_group_id !== null;
5859
- success = this._apply_single_edit_indexed(edit, orig_new, rebuild);
6036
+ success = this._apply_single_edit_indexed(edit, orig_new, false);
5860
6037
  } else if (edit.type === "insert_row" || edit.type === "delete_row") {
5861
6038
  success = this._apply_table_edit(edit, false);
5862
6039
  }
@@ -5906,8 +6083,10 @@ var RedlineEngine = class _RedlineEngine {
5906
6083
  this.skipped_details.push(msg);
5907
6084
  edit._applied_status = false;
5908
6085
  edit._error_msg = msg;
6086
+ edit._any_sub_failure = true;
5909
6087
  const parent = edit._parent_edit_ref;
5910
6088
  if (parent) {
6089
+ parent._any_sub_failure = true;
5911
6090
  if (!parent._applied_status) {
5912
6091
  parent._applied_status = false;
5913
6092
  parent._error_msg = msg;
@@ -5915,7 +6094,20 @@ var RedlineEngine = class _RedlineEngine {
5915
6094
  }
5916
6095
  }
5917
6096
  }
5918
- return [applied, skipped];
6097
+ let applied_logical = 0;
6098
+ let skipped_logical = 0;
6099
+ for (const input_edit of edits) {
6100
+ if (typeof input_edit !== "object" || input_edit === null) {
6101
+ skipped_logical++;
6102
+ continue;
6103
+ }
6104
+ if (input_edit._applied_status && !input_edit._any_sub_failure) {
6105
+ applied_logical++;
6106
+ } else {
6107
+ skipped_logical++;
6108
+ }
6109
+ }
6110
+ return [applied_logical, skipped_logical];
5919
6111
  }
5920
6112
  apply_review_actions(actions) {
5921
6113
  let applied = 0;
@@ -6408,6 +6600,7 @@ var RedlineEngine = class _RedlineEngine {
6408
6600
  op = "MODIFICATION";
6409
6601
  }
6410
6602
  }
6603
+ const suppress_emphasis = this._edit_declares_emphasis(edit);
6411
6604
  if (op === "STYLE_ONLY" || op === "STYLE_AND_TEXT") {
6412
6605
  const [anchor_run, anchor_para] = active_mapper.get_insertion_anchor(
6413
6606
  start_idx,
@@ -6608,7 +6801,8 @@ var RedlineEngine = class _RedlineEngine {
6608
6801
  clean_text,
6609
6802
  anchor_run,
6610
6803
  ins_id,
6611
- xmlDoc
6804
+ xmlDoc,
6805
+ suppress_emphasis
6612
6806
  );
6613
6807
  if (content_ins) new_p.appendChild(content_ins);
6614
6808
  body.insertBefore(new_p, _bug233_target_para);
@@ -6640,7 +6834,9 @@ var RedlineEngine = class _RedlineEngine {
6640
6834
  final_new_text,
6641
6835
  anchor_run,
6642
6836
  anchor_para,
6643
- ins_id
6837
+ ins_id,
6838
+ null,
6839
+ suppress_emphasis
6644
6840
  );
6645
6841
  if (!result.first_node) return false;
6646
6842
  const is_inline_first = result.first_node.tagName === "w:ins";
@@ -6791,7 +6987,8 @@ var RedlineEngine = class _RedlineEngine {
6791
6987
  ins_id,
6792
6988
  // The insertion physically follows the deletion block; the style
6793
6989
  // run was detached when the deletion cloned it into <w:del>.
6794
- last_del
6990
+ last_del,
6991
+ suppress_emphasis
6795
6992
  );
6796
6993
  if (result.first_node) {
6797
6994
  const is_inline_first = result.first_node.tagName === "w:ins";
@@ -6825,7 +7022,9 @@ var RedlineEngine = class _RedlineEngine {
6825
7022
  edit.new_text,
6826
7023
  anchor,
6827
7024
  first_span.paragraph,
6828
- ins_id
7025
+ ins_id,
7026
+ null,
7027
+ suppress_emphasis
6829
7028
  );
6830
7029
  if (result.first_node) {
6831
7030
  p1_el.appendChild(result.first_node);
@@ -7309,6 +7508,26 @@ function normalize_change_dates(doc) {
7309
7508
  }
7310
7509
  return count ? [`Track change timestamps: ${count} normalized`] : [];
7311
7510
  }
7511
+ function normalize_comment_dates(doc) {
7512
+ let count = 0;
7513
+ const fixed = "2025-01-01T00:00:00Z";
7514
+ for (const part of doc.pkg.parts) {
7515
+ if (!part.contentType.includes("comments")) continue;
7516
+ for (const el of findAllDescendants(part._element, "w:comment")) {
7517
+ if (el.hasAttribute("w:date")) {
7518
+ el.setAttribute("w:date", fixed);
7519
+ count++;
7520
+ }
7521
+ }
7522
+ for (const el of findAllDescendants(part._element, "w16cex:commentExtensible")) {
7523
+ if (el.hasAttribute("w16cex:dateUtc")) {
7524
+ el.setAttribute("w16cex:dateUtc", fixed);
7525
+ count++;
7526
+ }
7527
+ }
7528
+ }
7529
+ return count ? [`Comment timestamps: ${count} normalized`] : [];
7530
+ }
7312
7531
  function scrub_doc_properties(doc) {
7313
7532
  const lines = [];
7314
7533
  const corePart = doc.pkg.getPartByPath("docProps/core.xml");
@@ -8052,8 +8271,10 @@ function build_paragraph_text(paragraph, comments_map, cleanView, style_cache, d
8052
8271
  const new_wrappers = cleanView ? ["", ""] : _get_wrappers(active_ins, active_del, active_comments, active_fmt);
8053
8272
  const new_style = [prefix, suffix];
8054
8273
  if (pending_text && new_wrappers[0] === current_wrappers[0] && new_wrappers[1] === current_wrappers[1]) {
8055
- if (new_style[0] === current_style[0] && new_style[1] === current_style[1] && current_style[0] !== "" && pending_text.endsWith(current_style[1]) && seg.startsWith(new_style[0])) {
8056
- pending_text = pending_text.slice(0, -current_style[1].length) + seg.slice(new_style[0].length);
8274
+ const escaped_prefix = new_style[0].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8275
+ const lead_match = new_style[0] !== "" || new_style[1] !== "" ? seg.match(new RegExp("^(\\s*)" + escaped_prefix)) : null;
8276
+ if (new_style[0] === current_style[0] && new_style[1] === current_style[1] && current_style[0] !== "" && pending_text.endsWith(current_style[1]) && lead_match !== null) {
8277
+ pending_text = pending_text.slice(0, -current_style[1].length) + lead_match[1] + seg.slice(lead_match[0].length);
8057
8278
  } else {
8058
8279
  pending_text += seg;
8059
8280
  }
@@ -8820,7 +9041,7 @@ function _collect_footnote_ids_fast(owned_items) {
8820
9041
  }
8821
9042
 
8822
9043
  // src/sanitize/core.ts
8823
- import { strFromU8 as strFromU82, unzipSync as unzipSync2 } from "fflate";
9044
+ import { strFromU8 as strFromU82, unzipSync as unzipSync3 } from "fflate";
8824
9045
 
8825
9046
  // src/sanitize/report.ts
8826
9047
  var SanitizeReport = class {
@@ -8975,6 +9196,7 @@ async function finalize_document(doc, options) {
8975
9196
  for (const w of warnings) report.warnings.push(w);
8976
9197
  for (const w of detect_watermarks(doc)) report.warnings.push(w);
8977
9198
  report.add_transform_lines(normalize_change_dates(doc));
9199
+ report.add_transform_lines(normalize_comment_dates(doc));
8978
9200
  if (options.protection_mode === "read_only" || options.protection_mode === "encrypt") {
8979
9201
  if (options.protection_mode === "encrypt") {
8980
9202
  report.warnings.push("Encryption mode (AES compound wrappers) is strictly unsupported in the zero-dependency Node engine. Falling back to native Word Read-Only lock.");
@@ -9033,7 +9255,7 @@ var VERIFIED_CORE_FIELDS = [
9033
9255
  ["version", "version (cp:version)"]
9034
9256
  ];
9035
9257
  function verifySanitizedPackage(outBuffer) {
9036
- const unzipped = unzipSync2(new Uint8Array(outBuffer));
9258
+ const unzipped = unzipSync3(new Uint8Array(outBuffer));
9037
9259
  const names = Object.keys(unzipped);
9038
9260
  const problems = [];
9039
9261
  if (names.includes("docProps/custom.xml")) {
@@ -9072,6 +9294,7 @@ export {
9072
9294
  USER_PATTERN_TIMEOUT_MS,
9073
9295
  _extractTextFromDoc,
9074
9296
  apply_edits_to_markdown,
9297
+ collect_media_difference_warnings,
9075
9298
  create_unified_diff,
9076
9299
  create_word_patch_diff,
9077
9300
  describe_illegal_control_chars,