bun-docx 0.13.0 → 0.14.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.
Files changed (3) hide show
  1. package/README.md +13 -5
  2. package/dist/index.js +1258 -690
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -16632,6 +16632,7 @@ var init_jsx = __esm(() => {
16632
16632
  "rFonts",
16633
16633
  "br",
16634
16634
  "tab",
16635
+ "tabs",
16635
16636
  "drawing",
16636
16637
  "sectPr",
16637
16638
  "sectPrChange",
@@ -21616,6 +21617,15 @@ function applyParagraphProperties(document2, paragraph, paragraphProperties) {
21616
21617
  paragraph.alignment = value;
21617
21618
  }
21618
21619
  }
21620
+ const tabsNode = paragraphProperties.findChild("w:tabs");
21621
+ if (tabsNode) {
21622
+ const stops = tabsNode.findChildren("w:tab").map((tab) => ({
21623
+ align: tab.getAttribute("w:val") ?? "left",
21624
+ pos: Number(tab.getAttribute("w:pos") ?? "0")
21625
+ })).filter((stop) => Number.isFinite(stop.pos));
21626
+ if (stops.length > 0)
21627
+ paragraph.tabStops = stops;
21628
+ }
21619
21629
  const numberingProperties = paragraphProperties.findChild("w:numPr");
21620
21630
  if (numberingProperties) {
21621
21631
  const indentLevel = numberingProperties.findChild("w:ilvl");
@@ -34067,6 +34077,7 @@ function Paragraph({
34067
34077
  alignment,
34068
34078
  list,
34069
34079
  taskState,
34080
+ tabs,
34070
34081
  text: text2,
34071
34082
  runs,
34072
34083
  ...formatting
@@ -34075,7 +34086,7 @@ function Paragraph({
34075
34086
  return /* @__PURE__ */ jsxDEV(w.p, {
34076
34087
  children: [
34077
34088
  /* @__PURE__ */ jsxDEV(ParagraphProperties, {
34078
- options: { style, alignment, list }
34089
+ options: { style, alignment, list, tabs }
34079
34090
  }, undefined, false, undefined, this),
34080
34091
  taskState && /* @__PURE__ */ jsxDEV(TaskCheckbox, {
34081
34092
  checked: taskState === "checked"
@@ -34131,13 +34142,26 @@ function RunElement({ run }) {
34131
34142
  return null;
34132
34143
  }
34133
34144
  function applyParagraphOptionsInPlace(rebuilt, options) {
34134
- if (!options.style && !options.alignment)
34145
+ if (!options.style && !options.alignment && !options.tabs)
34135
34146
  return;
34136
34147
  let pPr = rebuilt.find((child2) => child2.tag === "w:pPr");
34137
34148
  if (!pPr) {
34138
34149
  pPr = new XmlNode2("w:pPr");
34139
34150
  rebuilt.unshift(pPr);
34140
34151
  }
34152
+ if (options.tabs) {
34153
+ pPr.children = pPr.children.filter((child2) => child2.tag !== "w:tabs");
34154
+ if (options.tabs.length > 0) {
34155
+ const tabsNode = new XmlNode2("w:tabs");
34156
+ for (const stop of options.tabs) {
34157
+ tabsNode.children.push(new XmlNode2("w:tab", {
34158
+ "w:val": stop.align,
34159
+ "w:pos": String(Math.round(stop.pos))
34160
+ }));
34161
+ }
34162
+ insertPprChildInOrder(pPr, tabsNode);
34163
+ }
34164
+ }
34141
34165
  if (options.style) {
34142
34166
  const existingStyle = pPr.findChild("w:pStyle");
34143
34167
  if (existingStyle) {
@@ -34244,7 +34268,8 @@ function RunProperties({ run }) {
34244
34268
  function ParagraphProperties({
34245
34269
  options
34246
34270
  }) {
34247
- if (!options.style && !options.alignment && !options.list)
34271
+ const hasTabs = options.tabs !== undefined && options.tabs.length > 0;
34272
+ if (!options.style && !options.alignment && !options.list && !hasTabs)
34248
34273
  return null;
34249
34274
  return /* @__PURE__ */ jsxDEV(w.pPr, {
34250
34275
  children: [
@@ -34261,6 +34286,12 @@ function ParagraphProperties({
34261
34286
  }, undefined, false, undefined, this)
34262
34287
  ]
34263
34288
  }, undefined, true, undefined, this),
34289
+ hasTabs && /* @__PURE__ */ jsxDEV(w.tabs, {
34290
+ children: options.tabs?.map((stop) => /* @__PURE__ */ jsxDEV(w.tab, {
34291
+ "w-val": stop.align,
34292
+ "w-pos": String(Math.round(stop.pos))
34293
+ }, undefined, false, undefined, this))
34294
+ }, undefined, false, undefined, this),
34264
34295
  options.alignment && /* @__PURE__ */ jsxDEV(w.jc, {
34265
34296
  "w-val": options.alignment
34266
34297
  }, undefined, false, undefined, this)
@@ -49400,7 +49431,7 @@ function collectRunText(run) {
49400
49431
  function tokenize(text2) {
49401
49432
  if (text2.length === 0)
49402
49433
  return [];
49403
- return text2.match(/\S+|\s+/g) ?? [];
49434
+ return text2.match(/\S+|\t+|[^\S\t]+/g) ?? [];
49404
49435
  }
49405
49436
  function diffTokens(oldTokens, newTokens) {
49406
49437
  const m2 = oldTokens.length;
@@ -49461,6 +49492,10 @@ function demoteOrphanWhitespaceKeeps(ops) {
49461
49492
  out.push(op2);
49462
49493
  continue;
49463
49494
  }
49495
+ if (op2.old.text.includes("\t")) {
49496
+ out.push(op2);
49497
+ continue;
49498
+ }
49464
49499
  const prev = ops[index - 1];
49465
49500
  const next = ops[index + 1];
49466
49501
  const prevIsEdit = prev !== undefined && prev.kind !== "keep";
@@ -49549,25 +49584,45 @@ function inheritedRprForInsert(ops, index, fallbackRpr) {
49549
49584
  break;
49550
49585
  groupEnd++;
49551
49586
  }
49552
- const deletes = [];
49553
- let insertOrdinal = 0;
49554
- let myInsertOrdinal = -1;
49587
+ const targetText = (() => {
49588
+ const op2 = ops[index];
49589
+ return op2 && op2.kind === "insert" ? op2.text : "";
49590
+ })();
49591
+ const targetIsWord = !isWhitespaceOnly(targetText);
49592
+ const allDeletes = [];
49593
+ const wordDeletes = [];
49594
+ let allInsertOrdinal = 0;
49595
+ let wordInsertOrdinal = 0;
49596
+ let myAllOrdinal = -1;
49597
+ let myWordOrdinal = -1;
49555
49598
  for (let cursor = groupStart;cursor <= groupEnd; cursor++) {
49556
49599
  const op2 = ops[cursor];
49557
49600
  if (!op2)
49558
49601
  continue;
49559
49602
  if (op2.kind === "delete") {
49560
- deletes.push(op2.old.rPr);
49603
+ allDeletes.push(op2.old.rPr);
49604
+ if (!isWhitespaceOnly(op2.old.text))
49605
+ wordDeletes.push(op2.old.rPr);
49561
49606
  continue;
49562
49607
  }
49563
49608
  if (op2.kind === "insert") {
49564
- if (cursor === index)
49565
- myInsertOrdinal = insertOrdinal;
49566
- insertOrdinal++;
49609
+ const isWord = !isWhitespaceOnly(op2.text);
49610
+ if (cursor === index) {
49611
+ myAllOrdinal = allInsertOrdinal;
49612
+ myWordOrdinal = isWord ? wordInsertOrdinal : -1;
49613
+ }
49614
+ allInsertOrdinal++;
49615
+ if (isWord)
49616
+ wordInsertOrdinal++;
49567
49617
  }
49568
49618
  }
49569
- if (deletes.length > 0 && myInsertOrdinal >= 0) {
49570
- const paired = myInsertOrdinal < deletes.length ? deletes[myInsertOrdinal] : deletes[deletes.length - 1];
49619
+ if (targetIsWord && wordDeletes.length > 0 && myWordOrdinal >= 0) {
49620
+ const paired = myWordOrdinal < wordDeletes.length ? wordDeletes[myWordOrdinal] : wordDeletes[wordDeletes.length - 1];
49621
+ if (paired !== undefined)
49622
+ return paired;
49623
+ }
49624
+ if (!targetIsWord && allDeletes.length > 0 && myAllOrdinal >= 0) {
49625
+ const paired = myAllOrdinal < allDeletes.length ? allDeletes[myAllOrdinal] : allDeletes[allDeletes.length - 1];
49571
49626
  if (paired !== undefined)
49572
49627
  return paired;
49573
49628
  }
@@ -49964,6 +50019,14 @@ class Edit {
49964
50019
  }
49965
50020
  return anchorTarget ?? blockRef.node;
49966
50021
  }
50022
+ paragraphProperties(blockRef, options) {
50023
+ if (blockRef.node.tag !== "w:p") {
50024
+ throw new EditError("USAGE", "--style/--alignment alone restyle a paragraph; this locator is not a paragraph.");
50025
+ }
50026
+ this.document.ensureStyles().ensureReferencedStyle(options.style);
50027
+ applyParagraphOptionsInPlace(blockRef.node.children, options);
50028
+ return blockRef.node;
50029
+ }
49967
50030
  span(blockRef, span, replacement, opts = {}) {
49968
50031
  if (blockRef.node.tag !== "w:p") {
49969
50032
  throw new EditError("USAGE", "A character-span locator (pN:S-E) edits text inside a paragraph; this locator does not resolve to a paragraph.");
@@ -80368,16 +80431,20 @@ async function respond(payload) {
80368
80431
  function setVerboseAck(verbose) {
80369
80432
  verboseAck = verbose;
80370
80433
  }
80371
- async function respondAck(payload) {
80434
+ async function respondAck(payload, layoutHint) {
80372
80435
  if (verboseAck) {
80373
80436
  await respond(payload);
80374
80437
  return;
80375
80438
  }
80376
- const line = summarizeAck(payload);
80377
- if (line)
80378
- await writeStdout(`${line}
80439
+ const parts = [summarizeAck(payload), layoutHint].filter((part) => Boolean(part));
80440
+ if (parts.length > 0)
80441
+ await writeStdout(`${parts.join(`
80442
+ `)}
80379
80443
  `);
80380
80444
  }
80445
+ function renderVerifyHint(path2) {
80446
+ return `\u21B3 layout changed \u2014 verify it renders right: docx render ${path2} --out pages/ (read shows text/structure, NOT page layout: columns, wraps, image sizing)`;
80447
+ }
80381
80448
  function summarizeAck(payload) {
80382
80449
  if (!payload || typeof payload !== "object")
80383
80450
  return null;
@@ -80425,13 +80492,16 @@ function ackTarget(ack) {
80425
80492
  return str(ack.path);
80426
80493
  return null;
80427
80494
  }
80428
- async function respondMinted(locators, verbosePayload) {
80495
+ async function respondMinted(locators, verbosePayload, layoutHint) {
80429
80496
  if (verboseAck) {
80430
80497
  await respond(verbosePayload);
80431
80498
  return;
80432
80499
  }
80433
- if (locators.length > 0)
80434
- await writeStdout(`${locators.join(`
80500
+ const parts = [...locators];
80501
+ if (layoutHint)
80502
+ parts.push(layoutHint);
80503
+ if (parts.length > 0)
80504
+ await writeStdout(`${parts.join(`
80435
80505
  `)}
80436
80506
  `);
80437
80507
  }
@@ -80571,254 +80641,36 @@ var init_respond = __esm(() => {
80571
80641
  };
80572
80642
  });
80573
80643
 
80574
- // src/cli/columns/index.tsx
80575
- var exports_columns = {};
80576
- __export(exports_columns, {
80577
- run: () => run
80578
- });
80579
- async function run(args) {
80580
- const parsed = await tryParseArgs(args, OPTION_SPEC, HELP);
80581
- if (typeof parsed === "number")
80582
- return parsed;
80583
- if (parsed.values.help) {
80584
- await writeStdout(HELP);
80585
- return EXIT2.OK;
80586
- }
80587
- setVerboseAck(Boolean(parsed.values.verbose));
80588
- const filePath = parsed.positionals[0];
80589
- if (!filePath)
80590
- return fail("USAGE", "Missing FILE argument", HELP);
80591
- const at = parsed.values.at;
80592
- if (!at) {
80593
- return fail("USAGE", "Missing --at LOCATOR (a range pN-pM or a section sN)", HELP);
80594
- }
80595
- const count = parseCount(parsed.values.count);
80596
- if (typeof count === "number" && Number.isNaN(count)) {
80597
- return fail("USAGE", `--count must be a positive integer`, HELP);
80598
- }
80599
- if (count === undefined)
80600
- return fail("USAGE", "Missing --count N", HELP);
80601
- const typeRaw = parsed.values.type;
80602
- if (typeRaw !== undefined && !isSectionType(typeRaw)) {
80603
- return fail("USAGE", `Invalid --type: ${typeRaw}`, "Valid values: continuous, nextPage, evenPage, oddPage, nextColumn");
80604
- }
80605
- let locator;
80606
- try {
80607
- locator = parseLocator(at);
80608
- } catch (error) {
80609
- if (error instanceof LocatorParseError) {
80610
- return fail("INVALID_LOCATOR", error.message, HELP);
80611
- }
80612
- throw error;
80613
- }
80614
- const document4 = await openOrFail(filePath);
80615
- if (typeof document4 === "number")
80616
- return document4;
80617
- const opts = {
80618
- filePath,
80619
- count,
80620
- sectionType: typeRaw,
80621
- authorFlag: parsed.values.author,
80622
- trackFlag: Boolean(parsed.values.track),
80623
- outputPath: parsed.values.output,
80624
- dryRun: Boolean(parsed.values["dry-run"])
80625
- };
80626
- if (locator.kind === "blockRange") {
80627
- const range = await resolveBlockRangeOrFail(document4, at);
80628
- if (typeof range === "number")
80629
- return range;
80630
- return wrapRange(document4, range.parent, range.startIndex, range.endIndex, at, opts);
80631
- }
80632
- const blockRef = await resolveBlockOrFail(document4, at);
80633
- if (typeof blockRef === "number")
80634
- return blockRef;
80635
- if (blockRef.node.tag === "w:sectPr") {
80636
- return editSection(document4, blockRef, at, opts);
80637
- }
80638
- if (blockRef.node.tag === "w:p") {
80639
- const index2 = blockRef.parent.indexOf(blockRef.node);
80640
- return wrapRange(document4, blockRef.parent, index2, index2, at, opts);
80641
- }
80642
- return fail("INVALID_LOCATOR", `columns needs a section (sN) or a paragraph range (pN-pM); ${at} is neither`, HELP);
80643
- }
80644
- async function editSection(document4, blockRef, at, opts) {
80645
- try {
80646
- new Edit(document4).section(blockRef, { columns: opts.count, sectionType: opts.sectionType }, { authorFlag: opts.authorFlag, track: opts.trackFlag });
80647
- } catch (error) {
80648
- if (error instanceof EditError)
80649
- return fail(error.code, error.message);
80650
- throw error;
80651
- }
80652
- return commit(document4, opts, { at, mode: "section" });
80653
- }
80654
- async function wrapRange(document4, parent, startIndex, endIndex, at, opts) {
80655
- if (parent !== document4.body.findBodyChildren()) {
80656
- return fail("USAGE", `columns can only wrap body-level paragraphs; ${at} is inside a table cell`, "Section breaks carry column layout and cannot live in a table cell.");
80657
- }
80658
- for (let index2 = startIndex;index2 <= endIndex; index2++) {
80659
- if (containsSectionBreak(parent[index2])) {
80660
- return fail("USAGE", `The range ${at} already contains a section break`, "Target the existing section directly with `--at sN`, or pick a range without one.");
80661
- }
80662
- }
80663
- const governing = governingColumns(parent, endIndex + 1);
80664
- const track = resolveTracked(document4, opts.trackFlag);
80665
- const insert = new Insert(document4);
80666
- const allocator = track ? new TrackChanges(document4).createAllocator() : undefined;
80667
- const endNode = parent[endIndex];
80668
- if (!endNode) {
80669
- return fail("BLOCK_NOT_FOUND", "Range end is stale (parent does not contain it)");
80670
- }
80671
- const startNode = startIndex > 0 ? parent[startIndex] : undefined;
80672
- if (startIndex > 0 && !startNode) {
80673
- return fail("BLOCK_NOT_FOUND", "Range start is stale (parent does not contain it)");
80674
- }
80675
- let afterBlocks;
80676
- let beforeBlocks = [];
80677
- try {
80678
- afterBlocks = await insert.paragraph({ node: endNode, parent }, {
80679
- kind: "section",
80680
- columns: opts.count,
80681
- sectionType: opts.sectionType ?? "continuous"
80682
- }, {}, { placement: "after", authorFlag: opts.authorFlag, track, allocator });
80683
- if (startNode) {
80684
- beforeBlocks = await insert.paragraph({ node: startNode, parent }, {
80685
- kind: "section",
80686
- ...governing > 1 ? { columns: governing } : {},
80687
- sectionType: "continuous"
80688
- }, {}, { placement: "before", authorFlag: opts.authorFlag, track, allocator });
80689
- }
80690
- } catch (error) {
80691
- if (error instanceof InsertError)
80692
- return fail(error.code, error.message, error.hint);
80693
- throw error;
80694
- }
80695
- if (opts.dryRun) {
80696
- return commit(document4, opts, { at, mode: "range", dryRunOnly: true });
80697
- }
80698
- parent.splice(endIndex + 1, 0, ...afterBlocks);
80699
- parent.splice(startIndex, 0, ...beforeBlocks);
80700
- return commit(document4, opts, { at, mode: "range" });
80701
- }
80702
- function containsSectionBreak(node2) {
80703
- if (!node2)
80704
- return false;
80705
- if (node2.tag === "w:sectPr")
80706
- return true;
80707
- if (node2.tag !== "w:p")
80708
- return false;
80709
- return Boolean(node2.findChild("w:pPr")?.findChild("w:sectPr"));
80710
- }
80711
- function governingColumns(parent, fromIndex) {
80712
- for (let index2 = fromIndex;index2 < parent.length; index2++) {
80713
- const node2 = parent[index2];
80714
- if (!node2)
80715
- continue;
80716
- if (node2.tag === "w:sectPr")
80717
- return columnsOf(node2);
80718
- if (node2.tag === "w:p") {
80719
- const inline = node2.findChild("w:pPr")?.findChild("w:sectPr");
80720
- if (inline)
80721
- return columnsOf(inline);
80722
- }
80723
- }
80724
- return 1;
80644
+ // src/cli/parse-helpers.ts
80645
+ function detectMarkdownInPlainText(text6) {
80646
+ if (/\*\*[^*\n]+\*\*/.test(text6))
80647
+ return "bold (**\u2026**)";
80648
+ if (/__[^_\n]+__/.test(text6))
80649
+ return "bold (__\u2026__)";
80650
+ if (/(^|\n) {0,3}#{1,6}\s/.test(text6))
80651
+ return "a heading (#\u2026)";
80652
+ if (/\[[^\]\n]+\]\([^)\n]+\)/.test(text6))
80653
+ return "a link ([text](url))";
80654
+ return null;
80725
80655
  }
80726
- function columnsOf(sectPr) {
80727
- const raw = sectPr.findChild("w:cols")?.getAttribute("w:num");
80728
- const parsed = raw ? Number.parseInt(raw, 10) : 1;
80729
- return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
80656
+ async function rejectMarkdownInText(text6, help) {
80657
+ const found = detectMarkdownInPlainText(text6);
80658
+ if (!found)
80659
+ return null;
80660
+ return await fail("USAGE", `--text writes literal characters, but this value looks like markdown: ${found}. It would be baked in verbatim (e.g. literal ** around the word), not rendered.`, `Use --markdown to parse it (handles ${found}, headings, lists, links), --bold/--italic for a uniformly-emphasized run, or --runs for literal text that really contains these characters. Help:
80661
+ ${help}`);
80730
80662
  }
80731
- function parseCount(raw) {
80732
- if (raw === undefined)
80733
- return;
80734
- if (!/^\d+$/.test(raw.trim()))
80735
- return Number.NaN;
80736
- const parsed = Number.parseInt(raw, 10);
80737
- if (!Number.isFinite(parsed) || parsed < 1)
80738
- return Number.NaN;
80739
- return parsed;
80663
+ function detectShellMangledCurrency(text6) {
80664
+ const match = text6.match(/(?<![\d$])([.,]\d{2,})/);
80665
+ return match?.[1] ?? null;
80740
80666
  }
80741
- async function commit(document4, opts, meta) {
80742
- if (opts.dryRun || meta.dryRunOnly) {
80743
- await respond({
80744
- operation: "columns",
80745
- dryRun: true,
80746
- path: opts.filePath,
80747
- locator: meta.at,
80748
- columnCount: opts.count,
80749
- mode: meta.mode,
80750
- ...opts.outputPath ? { output: opts.outputPath } : {}
80751
- });
80752
- return EXIT2.OK;
80753
- }
80754
- await document4.save(opts.outputPath);
80755
- await respondAck({
80756
- ok: true,
80757
- operation: "columns",
80758
- path: opts.outputPath ?? opts.filePath,
80759
- locator: meta.at,
80760
- columnCount: opts.count,
80761
- mode: meta.mode
80762
- });
80763
- return EXIT2.OK;
80667
+ async function rejectShellMangledValue(text6, help, label = "this value") {
80668
+ const fragment = detectShellMangledCurrency(text6);
80669
+ if (!fragment)
80670
+ return null;
80671
+ return await fail("USAGE", `${label} contains "${fragment}" \u2014 a number with no integer part, the signature of a "$" amount gutted by the shell (bash turns double-quoted "$300.00" into ".00" and "$10,000" into ",000"). docx would write the corrupted value verbatim.`, `Wrap any "$"-bearing value in SINGLE quotes ('$300.00') so bash leaves it alone, or supply it via --batch FILE (JSONL never touches the shell). If you really mean "${fragment}", write its integer part (0${fragment}) or use --batch. Help:
80672
+ ${help}`);
80764
80673
  }
80765
- var HELP = `docx columns \u2014 lay text out in multiple columns
80766
-
80767
- Usage:
80768
- docx columns FILE --at LOCATOR --count N [options]
80769
-
80770
- The intuitive verb for column layout, so you don't have to hand-build section
80771
- breaks. Two addressing modes:
80772
-
80773
- --at pN-pM Wrap the paragraph range pN\u2026pM in its own N-column section
80774
- (inserts the bounding continuous section breaks for you). Also
80775
- accepts a single paragraph (--at pN).
80776
- --at sN Set the column count on an EXISTING section break sN (the section
80777
- whose content ENDS at sN). Equivalent to \`edit --at sN --columns N\`.
80778
-
80779
- Options:
80780
- --at LOCATOR Paragraph range (pN-pM), single paragraph (pN), or section (sN)
80781
- --count N Number of columns (>= 1; use 1 to collapse back to single column)
80782
- --type T Section type for the wrapping break: continuous (default),
80783
- nextPage, evenPage, oddPage, nextColumn. Only meaningful with
80784
- a pN-pM/pN range; with sN it overrides the section's type.
80785
- --author NAME Author for tracked changes (default: $DOCX_AUTHOR)
80786
- --track Record as a tracked change even when the doc toggle is off
80787
- -o, --output PATH Write to PATH instead of overwriting FILE
80788
- --dry-run Print what would change; do not write the file
80789
- -v, --verbose Print the full success ack JSON
80790
- -h, --help Show this help
80791
-
80792
- Agent tip: VERIFY LAYOUT VISUALLY. \`docx read\` shows the section as a
80793
- \`<!-- docx:section \u2026 cols="N" -->\` annotation but NOT how the columns actually
80794
- flow on the page (balance, overflow). After setting columns, render and look:
80795
- docx render FILE --out pages/
80796
- Adjust the range and re-render until the columns read the way you intended.
80797
-
80798
- Output:
80799
- Prints a one-line confirmation on success (exit 0); --verbose prints {ok:true, \u2026}. Positional ids shift
80800
- after a range wrap (two section breaks are inserted), so re-read before further
80801
- edits. Errors print {code, error, hint?} + nonzero exit.
80802
-
80803
- Examples:
80804
- docx columns doc.docx --at p4-p9 --count 2
80805
- docx columns doc.docx --at p4-p9 --count 3 --type continuous
80806
- docx columns doc.docx --at s2 --count 1 # collapse section s2 to one column
80807
- `, OPTION_SPEC;
80808
- var init_columns = __esm(() => {
80809
- init_core2();
80810
- init_respond();
80811
- OPTION_SPEC = {
80812
- at: { type: "string" },
80813
- count: { type: "string" },
80814
- type: { type: "string" },
80815
- author: { type: "string" },
80816
- track: { type: "boolean" },
80817
- ...SAVE_FLAGS
80818
- };
80819
- });
80820
-
80821
- // src/cli/parse-helpers.ts
80822
80674
  function parseTaskFlag(value) {
80823
80675
  const normalized = value.toLowerCase();
80824
80676
  if (normalized === "checked" || normalized === "true" || normalized === "1")
@@ -80849,9 +80701,9 @@ async function parseRunsArg(json2) {
80849
80701
  return fail("USAGE", "--runs must be a JSON array of Run objects");
80850
80702
  }
80851
80703
  const runs = parsed;
80852
- for (const run2 of runs) {
80853
- if (run2 !== null && typeof run2 === "object" && run2.type === "text") {
80854
- const invalid = firstInvalidRunFormat(run2);
80704
+ for (const run of runs) {
80705
+ if (run !== null && typeof run === "object" && run.type === "text") {
80706
+ const invalid = firstInvalidRunFormat(run);
80855
80707
  if (invalid) {
80856
80708
  return fail("USAGE", `Invalid ${invalid.field} "${invalid.value}" in a --runs text run`, `Use ${invalid.valid}.`);
80857
80709
  }
@@ -80937,9 +80789,9 @@ var init_parse_helpers = __esm(() => {
80937
80789
  // src/cli/comments/add.tsx
80938
80790
  var exports_add = {};
80939
80791
  __export(exports_add, {
80940
- run: () => run2
80792
+ run: () => run
80941
80793
  });
80942
- async function run2(args) {
80794
+ async function run(args) {
80943
80795
  const parsed = await tryParseArgs(args, {
80944
80796
  at: { type: "string" },
80945
80797
  anchor: { type: "string" },
@@ -80950,17 +80802,17 @@ async function run2(args) {
80950
80802
  current: { type: "boolean" },
80951
80803
  baseline: { type: "boolean" },
80952
80804
  ...SAVE_FLAGS
80953
- }, HELP2);
80805
+ }, HELP);
80954
80806
  if (typeof parsed === "number")
80955
80807
  return parsed;
80956
80808
  if (parsed.values.help) {
80957
- await writeStdout(HELP2);
80809
+ await writeStdout(HELP);
80958
80810
  return EXIT2.OK;
80959
80811
  }
80960
80812
  setVerboseAck(Boolean(parsed.values.verbose));
80961
80813
  const path2 = parsed.positionals[0];
80962
80814
  if (!path2)
80963
- return fail("USAGE", "Missing FILE argument", HELP2);
80815
+ return fail("USAGE", "Missing FILE argument", HELP);
80964
80816
  const atInput = parsed.values.at;
80965
80817
  const anchorInput = parsed.values.anchor;
80966
80818
  const batchInput = parsed.values.batch;
@@ -80975,10 +80827,10 @@ async function run2(args) {
80975
80827
  }
80976
80828
  const anchorCount = (atInput ? 1 : 0) + (anchorInput ? 1 : 0) + (batchInput ? 1 : 0);
80977
80829
  if (anchorCount === 0) {
80978
- return fail("USAGE", "Specify exactly one of --at, --anchor, or --batch", HELP2);
80830
+ return fail("USAGE", "Specify exactly one of --at, --anchor, or --batch", HELP);
80979
80831
  }
80980
80832
  if (anchorCount > 1) {
80981
- return fail("USAGE", "--at, --anchor, and --batch are mutually exclusive", HELP2);
80833
+ return fail("USAGE", "--at, --anchor, and --batch are mutually exclusive", HELP);
80982
80834
  }
80983
80835
  const document4 = await openOrFail(path2);
80984
80836
  if (typeof document4 === "number")
@@ -80986,7 +80838,7 @@ async function run2(args) {
80986
80838
  let rawEntries;
80987
80839
  if (batchInput) {
80988
80840
  if (text6 !== undefined || atInput || anchorInput || occurrenceRaw) {
80989
- return fail("USAGE", "--batch reads each entry's at/anchor/text/author from JSONL \u2014 do not pass them on the CLI", HELP2);
80841
+ return fail("USAGE", "--batch reads each entry's at/anchor/text/author from JSONL \u2014 do not pass them on the CLI", HELP);
80990
80842
  }
80991
80843
  try {
80992
80844
  rawEntries = await readJsonlObjects(batchInput);
@@ -80999,7 +80851,7 @@ async function run2(args) {
80999
80851
  }
81000
80852
  } else {
81001
80853
  if (!text6)
81002
- return fail("USAGE", "Missing --text TEXT", HELP2);
80854
+ return fail("USAGE", "Missing --text TEXT", HELP);
81003
80855
  const occurrence = occurrenceRaw === undefined ? undefined : Number(occurrenceRaw);
81004
80856
  if (occurrence !== undefined && (!Number.isInteger(occurrence) || occurrence < 1)) {
81005
80857
  return fail("USAGE", `--occurrence must be a positive integer (1-indexed), got "${occurrenceRaw}"`);
@@ -81173,14 +81025,14 @@ function resolveAnchorEntry(document4, anchor, occurrence, occurrenceExplicit, t
81173
81025
  locatorString
81174
81026
  };
81175
81027
  }
81176
- var AT_FORMS, HELP2, EntryError;
81028
+ var AT_FORMS, HELP, EntryError;
81177
81029
  var init_add = __esm(() => {
81178
81030
  init_core2();
81179
81031
  init_find();
81180
81032
  init_parse_helpers();
81181
81033
  init_respond();
81182
81034
  AT_FORMS = describeForms(["paragraph", "span", "crossSpan", "cellParagraph", "cellSpan"], " ");
81183
- HELP2 = `docx comments add \u2014 anchor a new comment to a locator or phrase
81035
+ HELP = `docx comments add \u2014 anchor a new comment to a locator or phrase
81184
81036
 
81185
81037
  Usage:
81186
81038
  docx comments add FILE --at LOCATOR --text TEXT [options]
@@ -81254,31 +81106,31 @@ Batch JSONL example:
81254
81106
  // src/cli/comments/delete.ts
81255
81107
  var exports_delete = {};
81256
81108
  __export(exports_delete, {
81257
- run: () => run3
81109
+ run: () => run2
81258
81110
  });
81259
- async function run3(args) {
81111
+ async function run2(args) {
81260
81112
  const parsed = await tryParseArgs(args, {
81261
81113
  at: { type: "string", multiple: true },
81262
81114
  batch: { type: "string" },
81263
81115
  ...SAVE_FLAGS
81264
- }, HELP3);
81116
+ }, HELP2);
81265
81117
  if (typeof parsed === "number")
81266
81118
  return parsed;
81267
81119
  if (parsed.values.help) {
81268
- await writeStdout(HELP3);
81120
+ await writeStdout(HELP2);
81269
81121
  return EXIT2.OK;
81270
81122
  }
81271
81123
  setVerboseAck(Boolean(parsed.values.verbose));
81272
81124
  const path2 = parsed.positionals[0];
81273
81125
  if (!path2)
81274
- return fail("USAGE", "Missing FILE argument", HELP3);
81126
+ return fail("USAGE", "Missing FILE argument", HELP2);
81275
81127
  const atValues = parsed.values.at ?? [];
81276
81128
  const batchInput = parsed.values.batch;
81277
81129
  if (atValues.length > 0 && batchInput) {
81278
- return fail("USAGE", "--at and --batch are mutually exclusive", HELP3);
81130
+ return fail("USAGE", "--at and --batch are mutually exclusive", HELP2);
81279
81131
  }
81280
81132
  if (atValues.length === 0 && !batchInput) {
81281
- return fail("USAGE", "Specify --at cN (repeatable) or --batch FILE", HELP3);
81133
+ return fail("USAGE", "Specify --at cN (repeatable) or --batch FILE", HELP2);
81282
81134
  }
81283
81135
  let rawIds;
81284
81136
  if (batchInput) {
@@ -81333,7 +81185,7 @@ async function run3(args) {
81333
81185
  });
81334
81186
  return EXIT2.OK;
81335
81187
  }
81336
- var HELP3 = `docx comments delete \u2014 remove one or more comments
81188
+ var HELP2 = `docx comments delete \u2014 remove one or more comments
81337
81189
 
81338
81190
  Usage:
81339
81191
  docx comments delete FILE --at cN [--at cM ...] [options]
@@ -81372,23 +81224,23 @@ var init_delete = __esm(() => {
81372
81224
  // src/cli/comments/list.ts
81373
81225
  var exports_list = {};
81374
81226
  __export(exports_list, {
81375
- run: () => run4
81227
+ run: () => run3
81376
81228
  });
81377
- async function run4(args) {
81229
+ async function run3(args) {
81378
81230
  const parsed = await tryParseArgs(args, {
81379
81231
  "include-resolved": { type: "boolean" },
81380
81232
  thread: { type: "string" },
81381
81233
  help: { type: "boolean", short: "h" }
81382
- }, HELP4);
81234
+ }, HELP3);
81383
81235
  if (typeof parsed === "number")
81384
81236
  return parsed;
81385
81237
  if (parsed.values.help) {
81386
- await writeStdout(HELP4);
81238
+ await writeStdout(HELP3);
81387
81239
  return EXIT2.OK;
81388
81240
  }
81389
81241
  const path2 = parsed.positionals[0];
81390
81242
  if (!path2)
81391
- return fail("USAGE", "Missing FILE argument", HELP4);
81243
+ return fail("USAGE", "Missing FILE argument", HELP3);
81392
81244
  const document4 = await openOrFail(path2);
81393
81245
  if (typeof document4 === "number")
81394
81246
  return document4;
@@ -81399,7 +81251,7 @@ async function run4(args) {
81399
81251
  await respond(comments);
81400
81252
  return EXIT2.OK;
81401
81253
  }
81402
- var HELP4 = `docx comments list \u2014 print existing comments as JSON
81254
+ var HELP3 = `docx comments list \u2014 print existing comments as JSON
81403
81255
 
81404
81256
  Usage:
81405
81257
  docx comments list FILE [options]
@@ -81427,31 +81279,31 @@ var init_list3 = __esm(() => {
81427
81279
  // src/cli/comments/reply.tsx
81428
81280
  var exports_reply = {};
81429
81281
  __export(exports_reply, {
81430
- run: () => run5
81282
+ run: () => run4
81431
81283
  });
81432
- async function run5(args) {
81284
+ async function run4(args) {
81433
81285
  const parsed = await tryParseArgs(args, {
81434
81286
  at: { type: "string" },
81435
81287
  text: { type: "string" },
81436
81288
  author: { type: "string" },
81437
81289
  ...SAVE_FLAGS
81438
- }, HELP5);
81290
+ }, HELP4);
81439
81291
  if (typeof parsed === "number")
81440
81292
  return parsed;
81441
81293
  if (parsed.values.help) {
81442
- await writeStdout(HELP5);
81294
+ await writeStdout(HELP4);
81443
81295
  return EXIT2.OK;
81444
81296
  }
81445
81297
  setVerboseAck(Boolean(parsed.values.verbose));
81446
81298
  const path2 = parsed.positionals[0];
81447
81299
  if (!path2)
81448
- return fail("USAGE", "Missing FILE argument", HELP5);
81300
+ return fail("USAGE", "Missing FILE argument", HELP4);
81449
81301
  const parentInput = parsed.values.at;
81450
81302
  const text6 = parsed.values.text;
81451
81303
  if (!parentInput)
81452
- return fail("USAGE", "Missing --at cN", HELP5);
81304
+ return fail("USAGE", "Missing --at cN", HELP4);
81453
81305
  if (!text6)
81454
- return fail("USAGE", "Missing --text TEXT", HELP5);
81306
+ return fail("USAGE", "Missing --text TEXT", HELP4);
81455
81307
  const document4 = await openOrFail(path2);
81456
81308
  if (typeof document4 === "number")
81457
81309
  return document4;
@@ -81492,7 +81344,7 @@ async function run5(args) {
81492
81344
  });
81493
81345
  return EXIT2.OK;
81494
81346
  }
81495
- var HELP5 = `docx comments reply \u2014 reply to an existing comment
81347
+ var HELP4 = `docx comments reply \u2014 reply to an existing comment
81496
81348
 
81497
81349
  Usage:
81498
81350
  docx comments reply FILE --at cN --text TEXT [options]
@@ -81525,33 +81377,33 @@ var init_reply = __esm(() => {
81525
81377
  // src/cli/comments/resolve.ts
81526
81378
  var exports_resolve = {};
81527
81379
  __export(exports_resolve, {
81528
- run: () => run6
81380
+ run: () => run5
81529
81381
  });
81530
- async function run6(args) {
81382
+ async function run5(args) {
81531
81383
  const parsed = await tryParseArgs(args, {
81532
81384
  at: { type: "string", multiple: true },
81533
81385
  batch: { type: "string" },
81534
81386
  unset: { type: "boolean" },
81535
81387
  ...SAVE_FLAGS
81536
- }, HELP6);
81388
+ }, HELP5);
81537
81389
  if (typeof parsed === "number")
81538
81390
  return parsed;
81539
81391
  if (parsed.values.help) {
81540
- await writeStdout(HELP6);
81392
+ await writeStdout(HELP5);
81541
81393
  return EXIT2.OK;
81542
81394
  }
81543
81395
  setVerboseAck(Boolean(parsed.values.verbose));
81544
81396
  const path2 = parsed.positionals[0];
81545
81397
  if (!path2)
81546
- return fail("USAGE", "Missing FILE argument", HELP6);
81398
+ return fail("USAGE", "Missing FILE argument", HELP5);
81547
81399
  const atValues = parsed.values.at ?? [];
81548
81400
  const batchInput = parsed.values.batch;
81549
81401
  const resolved = !parsed.values.unset;
81550
81402
  if (atValues.length > 0 && batchInput) {
81551
- return fail("USAGE", "--at and --batch are mutually exclusive", HELP6);
81403
+ return fail("USAGE", "--at and --batch are mutually exclusive", HELP5);
81552
81404
  }
81553
81405
  if (atValues.length === 0 && !batchInput) {
81554
- return fail("USAGE", "Specify --at cN (repeatable) or --batch FILE", HELP6);
81406
+ return fail("USAGE", "Specify --at cN (repeatable) or --batch FILE", HELP5);
81555
81407
  }
81556
81408
  let rawIds;
81557
81409
  if (batchInput) {
@@ -81608,7 +81460,7 @@ async function run6(args) {
81608
81460
  });
81609
81461
  return EXIT2.OK;
81610
81462
  }
81611
- var HELP6 = `docx comments resolve \u2014 mark one or more comments resolved
81463
+ var HELP5 = `docx comments resolve \u2014 mark one or more comments resolved
81612
81464
 
81613
81465
  Usage:
81614
81466
  docx comments resolve FILE --at cN [--at cM ...] [options]
@@ -81649,12 +81501,12 @@ var init_resolve2 = __esm(() => {
81649
81501
  // src/cli/comments/index.ts
81650
81502
  var exports_comments = {};
81651
81503
  __export(exports_comments, {
81652
- run: () => run7
81504
+ run: () => run6
81653
81505
  });
81654
- async function run7(args) {
81506
+ async function run6(args) {
81655
81507
  const verb = args[0];
81656
81508
  if (!verb || verb === "--help" || verb === "-h" || verb === "help") {
81657
- await writeStdout(HELP7);
81509
+ await writeStdout(HELP6);
81658
81510
  return verb ? 0 : 2;
81659
81511
  }
81660
81512
  const loader = SUBCOMMANDS[verb];
@@ -81664,7 +81516,7 @@ async function run7(args) {
81664
81516
  const module_ = await loader();
81665
81517
  return module_.run(args.slice(1));
81666
81518
  }
81667
- var SUBCOMMANDS, HELP7 = `docx comments \u2014 manage Word comments
81519
+ var SUBCOMMANDS, HELP6 = `docx comments \u2014 manage Word comments
81668
81520
 
81669
81521
  Usage:
81670
81522
  docx comments <verb> FILE [options]
@@ -81984,9 +81836,9 @@ var init_create = __esm(() => {
81984
81836
  // src/cli/create/index.tsx
81985
81837
  var exports_create = {};
81986
81838
  __export(exports_create, {
81987
- run: () => run8
81839
+ run: () => run7
81988
81840
  });
81989
- async function run8(args) {
81841
+ async function run7(args) {
81990
81842
  const parsed = await tryParseArgs(args, {
81991
81843
  title: { type: "string" },
81992
81844
  author: { type: "string" },
@@ -81996,17 +81848,17 @@ async function run8(args) {
81996
81848
  "dry-run": { type: "boolean" },
81997
81849
  verbose: { type: "boolean", short: "v" },
81998
81850
  help: { type: "boolean", short: "h" }
81999
- }, HELP8);
81851
+ }, HELP7);
82000
81852
  if (typeof parsed === "number")
82001
81853
  return parsed;
82002
81854
  if (parsed.values.help) {
82003
- await writeStdout(HELP8);
81855
+ await writeStdout(HELP7);
82004
81856
  return EXIT2.OK;
82005
81857
  }
82006
81858
  setVerboseAck(Boolean(parsed.values.verbose));
82007
81859
  const path2 = parsed.positionals[0];
82008
81860
  if (!path2) {
82009
- return fail("USAGE", "Missing FILE argument", HELP8);
81861
+ return fail("USAGE", "Missing FILE argument", HELP7);
82010
81862
  }
82011
81863
  const text6 = parsed.values.text;
82012
81864
  const fromPath = parsed.values.from;
@@ -82078,7 +81930,7 @@ async function applyMarkdownToBody(docxPath, markdownPath) {
82078
81930
  await document4.save();
82079
81931
  return { blockCount: blocks.length };
82080
81932
  }
82081
- var HELP8 = `docx create \u2014 create a new minimal .docx
81933
+ var HELP7 = `docx create \u2014 create a new minimal .docx
82082
81934
 
82083
81935
  Usage:
82084
81936
  docx create FILE [options]
@@ -82220,27 +82072,27 @@ var init_batch = __esm(() => {
82220
82072
  // src/cli/delete/index.tsx
82221
82073
  var exports_delete2 = {};
82222
82074
  __export(exports_delete2, {
82223
- run: () => run9
82075
+ run: () => run8
82224
82076
  });
82225
- async function run9(args) {
82226
- const parsed = await tryParseArgs(args, OPTION_SPEC2, HELP9);
82077
+ async function run8(args) {
82078
+ const parsed = await tryParseArgs(args, OPTION_SPEC, HELP8);
82227
82079
  if (typeof parsed === "number")
82228
82080
  return parsed;
82229
82081
  if (parsed.values.help) {
82230
- await writeStdout(HELP9);
82082
+ await writeStdout(HELP8);
82231
82083
  return EXIT2.OK;
82232
82084
  }
82233
82085
  setVerboseAck(Boolean(parsed.values.verbose));
82234
82086
  const filePath = parsed.positionals[0];
82235
82087
  if (!filePath)
82236
- return fail("USAGE", "Missing FILE argument", HELP9);
82088
+ return fail("USAGE", "Missing FILE argument", HELP8);
82237
82089
  const batchSource = parsed.values.batch;
82238
82090
  if (batchSource !== undefined) {
82239
82091
  return runDeleteBatch(filePath, batchSource, parsed.values);
82240
82092
  }
82241
82093
  const locator = parsed.values.at;
82242
82094
  if (!locator)
82243
- return fail("USAGE", "Missing --at LOCATOR (or --batch FILE)", HELP9);
82095
+ return fail("USAGE", "Missing --at LOCATOR (or --batch FILE)", HELP8);
82244
82096
  const opts = {
82245
82097
  filePath,
82246
82098
  locator,
@@ -82358,7 +82210,7 @@ function findBodyChildren(document4) {
82358
82210
  return null;
82359
82211
  return body.children;
82360
82212
  }
82361
- var HELP9 = `docx delete \u2014 remove a block at a locator
82213
+ var HELP8 = `docx delete \u2014 remove a block at a locator
82362
82214
 
82363
82215
  Usage:
82364
82216
  docx delete FILE --at LOCATOR [options]
@@ -82397,7 +82249,7 @@ Tracked behavior:
82397
82249
  the paragraph mark as deleted (accept removes the paragraph by merging it
82398
82250
  forward). Section deletion under tracking emits a [docx-cli] audit comment
82399
82251
  on the owning paragraph if it has runs to anchor on; otherwise (sentinel
82400
- paragraphs from "insert --section" have no runs) the mutation is silent.
82252
+ section sentinel paragraphs have no runs) the mutation is silent.
82401
82253
  delete --at tN under tracking is rejected (tracked table-row deletion is
82402
82254
  not supported).
82403
82255
 
@@ -82410,7 +82262,7 @@ Examples:
82410
82262
  docx delete doc.docx --at t0
82411
82263
  docx delete doc.docx --at s2
82412
82264
  docx delete doc.docx --batch drop.jsonl # {"at":"p26"} per line
82413
- `, OPTION_SPEC2;
82265
+ `, OPTION_SPEC;
82414
82266
  var init_delete2 = __esm(() => {
82415
82267
  init_core2();
82416
82268
  init_comments2();
@@ -82418,7 +82270,7 @@ var init_delete2 = __esm(() => {
82418
82270
  init_replace();
82419
82271
  init_respond();
82420
82272
  init_batch();
82421
- OPTION_SPEC2 = {
82273
+ OPTION_SPEC = {
82422
82274
  at: { type: "string" },
82423
82275
  batch: { type: "string" },
82424
82276
  author: { type: "string" },
@@ -82427,6 +82279,42 @@ var init_delete2 = __esm(() => {
82427
82279
  };
82428
82280
  });
82429
82281
 
82282
+ // src/cli/edit/tabs.ts
82283
+ function parseTabsValue(value) {
82284
+ const normalized = value.trim().toLowerCase();
82285
+ if (normalized === "right" || normalized === "right-margin")
82286
+ return { kind: "right-margin" };
82287
+ if (normalized === "clear" || normalized === "none" || normalized === "off")
82288
+ return { kind: "clear" };
82289
+ const stops = [];
82290
+ for (const token of value.split(",")) {
82291
+ const match = token.trim().match(/^(left|right|center)@([\d.]+)in$/i);
82292
+ const align = (match?.[1] ?? "").toLowerCase();
82293
+ const inches = Number(match?.[2] ?? Number.NaN);
82294
+ if (!match || !TAB_ALIGNS.has(align) || !Number.isFinite(inches)) {
82295
+ return {
82296
+ error: `Invalid --tabs value: "${token.trim()}"`,
82297
+ hint: TABS_HINT
82298
+ };
82299
+ }
82300
+ stops.push({ align, pos: Math.round(inches * 1440) });
82301
+ }
82302
+ return { kind: "explicit", stops };
82303
+ }
82304
+ function resolveTabsDirective(directive, document4) {
82305
+ if (directive.kind === "clear")
82306
+ return [];
82307
+ if (directive.kind === "explicit")
82308
+ return directive.stops;
82309
+ const marginTwips = Math.round(getPageContentWidthEmu(document4) / 635);
82310
+ return [{ align: "right", pos: marginTwips }];
82311
+ }
82312
+ var TAB_ALIGNS, TABS_HINT = "Use `right` (a right tab at the margin \u2014 cures the wrapping LEFT-tab `docx:layout` warning from `read`), `clear`, or explicit stops like `right@7.5in` / `left@1in,right@7.5in`.";
82313
+ var init_tabs = __esm(() => {
82314
+ init_core2();
82315
+ TAB_ALIGNS = new Set(["left", "right", "center"]);
82316
+ });
82317
+
82430
82318
  // src/cli/edit/batch.ts
82431
82319
  async function runEditBatch(filePath, batchSource, values2) {
82432
82320
  const conflicting = SINGLE_SHOT_FLAGS.find((flag) => values2[flag] !== undefined && values2[flag] !== false);
@@ -82512,13 +82400,14 @@ async function runEditBatch(filePath, batchSource, values2) {
82512
82400
  async function resolveEntry2(document4, raw, index2, opts) {
82513
82401
  const present = CONTENT_KEYS.filter((key) => raw[key] !== undefined);
82514
82402
  const hasClear = raw.clear !== undefined;
82515
- if (present.length === 0 && !hasClear) {
82516
- throw new EntryError2("USAGE", `entry ${index2}: no content \u2014 provide one of ${CONTENT_KEYS.join(", ")}, or "clear"`);
82403
+ const hasProps = raw.style !== undefined || raw.alignment !== undefined || raw.tabs !== undefined;
82404
+ if (present.length === 0 && !hasClear && !hasProps) {
82405
+ throw new EntryError2("USAGE", `entry ${index2}: no content \u2014 provide one of ${CONTENT_KEYS.join(", ")}, "clear", or "style"/"alignment"/"tabs" to adjust in place`);
82517
82406
  }
82518
82407
  if (present.length > 1) {
82519
82408
  throw new EntryError2("USAGE", `entry ${index2}: provide exactly one content field, got ${present.join(", ")}`);
82520
82409
  }
82521
- const kind = present[0] ?? "clear";
82410
+ const kind = present[0] ?? (hasClear ? "clear" : "props");
82522
82411
  const at = raw.at;
82523
82412
  if (typeof at !== "string" || at.length === 0) {
82524
82413
  throw new EntryError2("USAGE", `entry ${index2}: "at" is required`);
@@ -82557,6 +82446,13 @@ async function resolveEntry2(document4, raw, index2, opts) {
82557
82446
  async function buildApply(document4, raw, index2, kind, blockRef, span, opts) {
82558
82447
  const author = typeof raw.author === "string" ? raw.author : opts.authorFlag;
82559
82448
  const clearTags = raw.clear !== undefined ? resolveClearOrThrow(raw.clear, index2) : null;
82449
+ if (kind === "props") {
82450
+ if (span) {
82451
+ throw new EntryError2("USAGE", `entry ${index2}: a character span (${raw.at}) can't take "style"/"alignment" \u2014 restyle the whole paragraph (pN).`);
82452
+ }
82453
+ const paragraphOptions = readParagraphOptions(document4, raw, index2);
82454
+ return () => void new Edit(document4).paragraphProperties(blockRef, paragraphOptions);
82455
+ }
82560
82456
  if (kind === "clear") {
82561
82457
  const tags = clearTags ?? new Set;
82562
82458
  return () => new Edit(document4).clearFormatting(blockRef, span, tags);
@@ -82591,9 +82487,12 @@ async function buildApply(document4, raw, index2, kind, blockRef, span, opts) {
82591
82487
  };
82592
82488
  }
82593
82489
  async function buildWholeParagraphContent(document4, raw, index2, kind, blockRef, author, opts) {
82594
- const paragraphOptions = readParagraphOptions(raw, index2);
82490
+ const paragraphOptions = readParagraphOptions(document4, raw, index2);
82595
82491
  if (kind === "text") {
82596
82492
  const text6 = requireString(raw.text, index2, "text");
82493
+ if (text6 === "") {
82494
+ throw new EntryError2("USAGE", `entry ${index2}: empty "text" leaves a blank paragraph in place, it doesn't remove the line (${raw.at}).`, 'Remove lines with `docx delete --batch` ({ "at": "pN" } per line). To keep an empty spacer, use "runs": [] instead.');
82495
+ }
82597
82496
  const format = readTextFormat(raw, index2);
82598
82497
  return () => new Edit(document4).paragraph(blockRef, { kind: "text", text: text6, format, paragraphOptions }, {
82599
82498
  authorFlag: author,
@@ -82691,8 +82590,8 @@ function rejectSpanFormatFlags(raw, index2) {
82691
82590
  if (raw.color !== undefined || raw.bold !== undefined || raw.italic !== undefined) {
82692
82591
  throw new EntryError2("USAGE", `entry ${index2}: --color/--bold/--italic aren't supported on a character span \u2014 the replacement inherits the run's formatting`);
82693
82592
  }
82694
- if (raw.style !== undefined || raw.alignment !== undefined) {
82695
- throw new EntryError2("USAGE", `entry ${index2}: style/alignment apply to a whole paragraph, not a character span`);
82593
+ if (raw.style !== undefined || raw.alignment !== undefined || raw.tabs !== undefined) {
82594
+ throw new EntryError2("USAGE", `entry ${index2}: style/alignment/tabs apply to a whole paragraph, not a character span`);
82696
82595
  }
82697
82596
  }
82698
82597
  function readTextFormat(raw, index2) {
@@ -82709,7 +82608,7 @@ function readTextFormat(raw, index2) {
82709
82608
  out.italic = Boolean(raw.italic);
82710
82609
  return out;
82711
82610
  }
82712
- function readParagraphOptions(raw, index2) {
82611
+ function readParagraphOptions(document4, raw, index2) {
82713
82612
  const out = {};
82714
82613
  if (raw.style !== undefined) {
82715
82614
  if (typeof raw.style !== "string") {
@@ -82724,15 +82623,25 @@ function readParagraphOptions(raw, index2) {
82724
82623
  }
82725
82624
  out.alignment = alignment;
82726
82625
  }
82626
+ if (raw.tabs !== undefined) {
82627
+ if (typeof raw.tabs !== "string") {
82628
+ throw new EntryError2("USAGE", `entry ${index2}: "tabs" must be a string`);
82629
+ }
82630
+ const parsed = parseTabsValue(raw.tabs);
82631
+ if ("error" in parsed) {
82632
+ throw new EntryError2("USAGE", `entry ${index2}: ${parsed.error}`, parsed.hint);
82633
+ }
82634
+ out.tabs = resolveTabsDirective(parsed, document4);
82635
+ }
82727
82636
  return out;
82728
82637
  }
82729
82638
  function readRuns(value, index2) {
82730
82639
  if (!Array.isArray(value)) {
82731
82640
  throw new EntryError2("USAGE", `entry ${index2}: "runs" must be a JSON array of Run objects`);
82732
82641
  }
82733
- for (const run10 of value) {
82734
- if (run10 !== null && typeof run10 === "object" && run10.type === "text") {
82735
- const invalid = firstInvalidRunFormat(run10);
82642
+ for (const run9 of value) {
82643
+ if (run9 !== null && typeof run9 === "object" && run9.type === "text") {
82644
+ const invalid = firstInvalidRunFormat(run9);
82736
82645
  if (invalid) {
82737
82646
  throw new EntryError2("USAGE", `entry ${index2}: invalid ${invalid.field} "${invalid.value}" in a run`, `Use ${invalid.valid}.`);
82738
82647
  }
@@ -82761,6 +82670,7 @@ var init_batch2 = __esm(() => {
82761
82670
  init_run_formatting();
82762
82671
  init_parse_helpers();
82763
82672
  init_respond();
82673
+ init_tabs();
82764
82674
  SINGLE_SHOT_FLAGS = [
82765
82675
  "at",
82766
82676
  "text",
@@ -82778,6 +82688,7 @@ var init_batch2 = __esm(() => {
82778
82688
  "type",
82779
82689
  "style",
82780
82690
  "alignment",
82691
+ "tabs",
82781
82692
  "color",
82782
82693
  "bold",
82783
82694
  "italic"
@@ -82798,20 +82709,20 @@ var init_batch2 = __esm(() => {
82798
82709
  // src/cli/edit/index.tsx
82799
82710
  var exports_edit = {};
82800
82711
  __export(exports_edit, {
82801
- run: () => run10
82712
+ run: () => run9
82802
82713
  });
82803
- async function run10(args) {
82804
- const parsed = await tryParseArgs(args, OPTION_SPEC3, HELP10);
82714
+ async function run9(args) {
82715
+ const parsed = await tryParseArgs(args, OPTION_SPEC2, HELP9);
82805
82716
  if (typeof parsed === "number")
82806
82717
  return parsed;
82807
82718
  if (parsed.values.help) {
82808
- await writeStdout(HELP10);
82719
+ await writeStdout(HELP9);
82809
82720
  return EXIT2.OK;
82810
82721
  }
82811
82722
  setVerboseAck(Boolean(parsed.values.verbose));
82812
82723
  const filePath = parsed.positionals[0];
82813
82724
  if (!filePath)
82814
- return fail("USAGE", "Missing FILE argument", HELP10);
82725
+ return fail("USAGE", "Missing FILE argument", HELP9);
82815
82726
  const batchInput = parsed.values.batch;
82816
82727
  if (batchInput !== undefined) {
82817
82728
  return runEditBatch(filePath, batchInput, parsed.values);
@@ -82822,9 +82733,12 @@ async function run10(args) {
82822
82733
  const document4 = await openOrFail(opts.filePath);
82823
82734
  if (typeof document4 === "number")
82824
82735
  return document4;
82736
+ if (opts.tabsDirective) {
82737
+ injectTabsIntoSpec(opts.spec, resolveTabsDirective(opts.tabsDirective, document4));
82738
+ }
82825
82739
  if (isBlockRangeLocator2(opts.locator)) {
82826
82740
  if (opts.spec.kind === "section") {
82827
- return fail("USAGE", "Range locators (pN-pM) don't accept --columns/--type \u2014 use sN for section edits", HELP10);
82741
+ return fail("USAGE", "Range locators (pN-pM) don't accept --columns/--type \u2014 use sN for section edits", HELP9);
82828
82742
  }
82829
82743
  return commitRangeEdit(document4, opts);
82830
82744
  }
@@ -82857,14 +82771,14 @@ function spanLocatorTarget(locator) {
82857
82771
  async function commitSpanEdit(document4, spanTarget, opts) {
82858
82772
  const spec = opts.spec;
82859
82773
  if (spec.kind !== "text" && spec.kind !== "clear") {
82860
- return fail("USAGE", "A character-span locator (pN:S-E) supports --text or --clear. Use a whole-paragraph locator (pN) for --markdown/--runs/--code.", HELP10);
82774
+ return fail("USAGE", "A character-span locator (pN:S-E) supports --text or --clear. Use a whole-paragraph locator (pN) for --markdown/--runs/--code.", HELP9);
82861
82775
  }
82862
82776
  if (spec.kind === "text") {
82863
82777
  if (spec.format.color || spec.format.bold || spec.format.italic) {
82864
- return fail("USAGE", "--color/--bold/--italic aren't supported on a character span \u2014 the replacement inherits the existing run's formatting. Edit the whole paragraph (pN) to set uniform run formatting.", HELP10);
82778
+ return fail("USAGE", "--color/--bold/--italic aren't supported on a character span \u2014 the replacement inherits the existing run's formatting. Edit the whole paragraph (pN) to set uniform run formatting.", HELP9);
82865
82779
  }
82866
82780
  if (spec.paragraphOptions.style || spec.paragraphOptions.alignment) {
82867
- return fail("USAGE", "--style/--alignment apply to a whole paragraph, not a character span (pN:S-E).", HELP10);
82781
+ return fail("USAGE", "--style/--alignment apply to a whole paragraph, not a character span (pN:S-E).", HELP9);
82868
82782
  }
82869
82783
  }
82870
82784
  const blockRef = await resolveBlockOrFail(document4, spanTarget.blockId);
@@ -82928,6 +82842,8 @@ async function commitBlockEdit(document4, blockRef, opts) {
82928
82842
  });
82929
82843
  } else if (opts.spec.kind === "clear") {
82930
82844
  edit.clearFormatting(blockRef, null, opts.spec.tags);
82845
+ } else if (opts.spec.kind === "paragraphProps") {
82846
+ resultNode = edit.paragraphProperties(blockRef, opts.spec.paragraphOptions);
82931
82847
  } else {
82932
82848
  return fail("USAGE", "Unsupported edit spec for single-block locator");
82933
82849
  }
@@ -82945,13 +82861,16 @@ async function commitBlockEdit(document4, blockRef, opts) {
82945
82861
  }
82946
82862
  async function commitRangeEdit(document4, opts) {
82947
82863
  if (opts.spec.kind === "section") {
82948
- return fail("USAGE", "Section edits don't support range locators", HELP10);
82864
+ return fail("USAGE", "Section edits don't support range locators", HELP9);
82949
82865
  }
82950
82866
  if (opts.spec.kind === "task") {
82951
- return fail("USAGE", "--task takes a single paragraph locator (pN), not a range", HELP10);
82867
+ return fail("USAGE", "--task takes a single paragraph locator (pN), not a range", HELP9);
82952
82868
  }
82953
82869
  if (opts.spec.kind === "equation") {
82954
- return fail("USAGE", "--equation takes a single equation locator (eqN), not a paragraph range", HELP10);
82870
+ return fail("USAGE", "--equation takes a single equation locator (eqN), not a paragraph range", HELP9);
82871
+ }
82872
+ if (opts.spec.kind === "paragraphProps") {
82873
+ return commitRangeProps(document4, opts, opts.spec.paragraphOptions);
82955
82874
  }
82956
82875
  const rangeRef = await resolveBlockRangeOrFail(document4, opts.locator);
82957
82876
  if (typeof rangeRef === "number")
@@ -82983,6 +82902,53 @@ async function commitRangeEdit(document4, opts) {
82983
82902
  await document4.save(opts.outputPath);
82984
82903
  return emitEditAck(opts);
82985
82904
  }
82905
+ async function commitRangeProps(document4, opts, options) {
82906
+ const rangeRef = await resolveBlockRangeOrFail(document4, opts.locator);
82907
+ if (typeof rangeRef === "number")
82908
+ return rangeRef;
82909
+ if (opts.dryRun)
82910
+ return respondDryRun2(opts);
82911
+ let applied = 0;
82912
+ try {
82913
+ const edit = new Edit(document4);
82914
+ for (let index2 = rangeRef.startIndex;index2 <= rangeRef.endIndex; index2++) {
82915
+ const node2 = rangeRef.parent[index2];
82916
+ if (!node2 || node2.tag !== "w:p")
82917
+ continue;
82918
+ const perParagraph = scopeRangeProps(node2, options);
82919
+ if (!perParagraph)
82920
+ continue;
82921
+ edit.paragraphProperties({ node: node2, parent: rangeRef.parent }, perParagraph);
82922
+ applied++;
82923
+ }
82924
+ } catch (error) {
82925
+ if (error instanceof EditError) {
82926
+ return fail(error.code, error.message, error.hint);
82927
+ }
82928
+ throw error;
82929
+ }
82930
+ if (applied === 0) {
82931
+ return fail("BLOCK_NOT_FOUND", options.tabs !== undefined ? `No paragraphs with tab stops in ${opts.locator} \u2014 --tabs only adjusts tab-using lines (the ones \`read\` flags with docx:layout).` : `No paragraphs in ${opts.locator} to restyle.`);
82932
+ }
82933
+ await document4.save(opts.outputPath);
82934
+ return emitEditAck(opts);
82935
+ }
82936
+ function scopeRangeProps(node2, options) {
82937
+ const out = {};
82938
+ if (options.style !== undefined)
82939
+ out.style = options.style;
82940
+ if (options.alignment !== undefined)
82941
+ out.alignment = options.alignment;
82942
+ if (options.tabs !== undefined && paragraphHasTabStops(node2)) {
82943
+ out.tabs = options.tabs;
82944
+ }
82945
+ if (out.style === undefined && out.alignment === undefined && out.tabs === undefined)
82946
+ return null;
82947
+ return out;
82948
+ }
82949
+ function paragraphHasTabStops(node2) {
82950
+ return node2.findChild("w:pPr")?.findChild("w:tabs") !== undefined;
82951
+ }
82986
82952
  async function commitEquationEdit(document4, spec, opts) {
82987
82953
  if (spec.latex === undefined && spec.display === undefined) {
82988
82954
  return fail("USAGE", "--equation requires --equation NEW_LATEX, --display, or --inline");
@@ -83013,10 +82979,17 @@ async function commitEquationEdit(document4, spec, opts) {
83013
82979
  async function validateSingleShotOptions(filePath, values2) {
83014
82980
  const locator = values2.at;
83015
82981
  if (!locator)
83016
- return fail("USAGE", "Missing --at LOCATOR", HELP10);
82982
+ return fail("USAGE", "Missing --at LOCATOR", HELP9);
83017
82983
  const paragraphOptions = await parseParagraphOptions(values2);
83018
82984
  if (typeof paragraphOptions === "number")
83019
82985
  return paragraphOptions;
82986
+ let tabsDirective;
82987
+ if (values2.tabs !== undefined) {
82988
+ const parsed = parseTabsValue(values2.tabs);
82989
+ if ("error" in parsed)
82990
+ return fail("USAGE", parsed.error, parsed.hint);
82991
+ tabsDirective = parsed;
82992
+ }
83020
82993
  const isSectionLocator = /^s\d+$/.test(locator);
83021
82994
  const spec = isSectionLocator ? await validateSectionEdit(values2) : await validateParagraphEdit(values2, paragraphOptions);
83022
82995
  if (typeof spec === "number")
@@ -83037,15 +83010,16 @@ async function validateSingleShotOptions(filePath, values2) {
83037
83010
  outputPath: values2.output,
83038
83011
  dryRun: Boolean(values2["dry-run"]),
83039
83012
  noFormatting: Boolean(values2["no-formatting"]),
83040
- ...clearTags ? { clearTags } : {}
83013
+ ...clearTags ? { clearTags } : {},
83014
+ ...tabsDirective ? { tabsDirective } : {}
83041
83015
  };
83042
83016
  }
83043
83017
  async function validateSectionEdit(values2) {
83044
83018
  if (values2.text !== undefined || values2.runs !== undefined) {
83045
- return fail("USAGE", "Section locators (sN) take --columns and --type, not --text/--runs", HELP10);
83019
+ return fail("USAGE", "Section locators (sN) take --columns and --type, not --text/--runs", HELP9);
83046
83020
  }
83047
83021
  if (values2.columns === undefined && values2.type === undefined) {
83048
- return fail("USAGE", "Section edit requires --columns and/or --type", HELP10);
83022
+ return fail("USAGE", "Section edit requires --columns and/or --type", HELP9);
83049
83023
  }
83050
83024
  const sectionFlags = await parseSectionFlags(values2);
83051
83025
  if (typeof sectionFlags === "number")
@@ -83056,22 +83030,22 @@ async function parseClearTagsOrFail(clearFlag) {
83056
83030
  const raw = Array.isArray(clearFlag) ? clearFlag : [clearFlag];
83057
83031
  const names = raw.flatMap((entry) => entry.split(",")).map((name) => name.trim().toLowerCase()).filter(Boolean);
83058
83032
  if (names.length === 0) {
83059
- return fail("USAGE", "--clear needs an attribute name, or 'all'", HELP10);
83033
+ return fail("USAGE", "--clear needs an attribute name, or 'all'", HELP9);
83060
83034
  }
83061
83035
  const tags = resolveClearTags(names);
83062
83036
  if (!tags) {
83063
- return fail("USAGE", `--clear: unknown attribute in "${raw.join(",")}". Valid: ${CLEARABLE_ATTRS.join(", ")}, all`, HELP10);
83037
+ return fail("USAGE", `--clear: unknown attribute in "${raw.join(",")}". Valid: ${CLEARABLE_ATTRS.join(", ")}, all`, HELP9);
83064
83038
  }
83065
83039
  return tags;
83066
83040
  }
83067
83041
  async function validateParagraphEdit(values2, paragraphOptions) {
83068
83042
  if (values2.columns !== undefined || values2.type !== undefined) {
83069
- return fail("USAGE", "--columns and --type require a section locator (sN)", HELP10);
83043
+ return fail("USAGE", "--columns and --type require a section locator (sN)", HELP9);
83070
83044
  }
83071
83045
  const clearFlag = values2.clear;
83072
83046
  if (clearFlag !== undefined) {
83073
83047
  if (values2.equation !== undefined || values2.display === true || values2.inline === true) {
83074
- return fail("USAGE", "--clear can't be combined with --equation/--display/--inline", HELP10);
83048
+ return fail("USAGE", "--clear can't be combined with --equation/--display/--inline", HELP9);
83075
83049
  }
83076
83050
  const hasContent = [
83077
83051
  "text",
@@ -83108,10 +83082,10 @@ async function validateParagraphEdit(values2, paragraphOptions) {
83108
83082
  taskFlag !== undefined
83109
83083
  ].filter(Boolean).length;
83110
83084
  if (otherContent > 0) {
83111
- return fail("USAGE", "--equation / --display / --inline cannot be combined with --text/--runs/--code/--code-file/--task", HELP10);
83085
+ return fail("USAGE", "--equation / --display / --inline cannot be combined with --text/--runs/--code/--code-file/--task", HELP9);
83112
83086
  }
83113
83087
  if (displayFlag && inlineFlag) {
83114
- return fail("USAGE", "--display and --inline are mutually exclusive", HELP10);
83088
+ return fail("USAGE", "--display and --inline are mutually exclusive", HELP9);
83115
83089
  }
83116
83090
  const displayMode = displayFlag ? true : inlineFlag ? false : undefined;
83117
83091
  return {
@@ -83129,11 +83103,11 @@ async function validateParagraphEdit(values2, paragraphOptions) {
83129
83103
  codeFile !== undefined
83130
83104
  ].filter(Boolean).length;
83131
83105
  if (otherFlags > 0) {
83132
- return fail("USAGE", "--task cannot be combined with --text, --runs, --code, or --code-file", HELP10);
83106
+ return fail("USAGE", "--task cannot be combined with --text, --runs, --code, or --code-file", HELP9);
83133
83107
  }
83134
83108
  const checked = parseTaskFlag(taskFlag);
83135
83109
  if (checked === null) {
83136
- return fail("USAGE", `--task must be "checked" or "unchecked", got "${taskFlag}"`, HELP10);
83110
+ return fail("USAGE", `--task must be "checked" or "unchecked", got "${taskFlag}"`, HELP9);
83137
83111
  }
83138
83112
  return { kind: "task", checked };
83139
83113
  }
@@ -83148,15 +83122,29 @@ async function validateParagraphEdit(values2, paragraphOptions) {
83148
83122
  markdownFile !== undefined
83149
83123
  ].filter(Boolean).length;
83150
83124
  if (contentFlags === 0) {
83151
- return fail("USAGE", "Missing content: pass --text, --runs, --code, --code-file, --markdown, --markdown-file, --task, or --equation", HELP10);
83125
+ if (paragraphOptions.style || paragraphOptions.alignment || values2.tabs !== undefined) {
83126
+ return { kind: "paragraphProps", paragraphOptions };
83127
+ }
83128
+ return fail("USAGE", "Missing content: pass --text, --runs, --code, --code-file, --markdown, --markdown-file, --task, or --equation \u2014 or --style/--alignment/--tabs to adjust the paragraph in place", HELP9);
83152
83129
  }
83153
83130
  if (contentFlags > 1) {
83154
- return fail("USAGE", "Pass only one of --text, --runs, --code, --code-file, --markdown, --markdown-file", HELP10);
83131
+ return fail("USAGE", "Pass only one of --text, --runs, --code, --code-file, --markdown, --markdown-file", HELP9);
83155
83132
  }
83156
83133
  if (language !== undefined && codeInline === undefined && codeFile === undefined) {
83157
- return fail("USAGE", "--language requires --code or --code-file", HELP10);
83134
+ return fail("USAGE", "--language requires --code or --code-file", HELP9);
83158
83135
  }
83159
83136
  if (text6 !== undefined) {
83137
+ const at = values2.at;
83138
+ if (text6 === "" && !(at && spanLocatorTarget(at))) {
83139
+ return fail("USAGE", `Empty --text leaves a blank paragraph in place, it doesn't remove the line.`, `To DELETE the paragraph: \`docx delete --at ${at ?? "pN"}\` (or \`docx delete --batch\` for many). To delete just SOME characters, use a span locator (\`--at pN:S-E --text ""\`). To blank the paragraph but keep an empty spacer, pass --runs '[]'. Help:
83140
+ ${HELP9}`);
83141
+ }
83142
+ const rejected = await rejectMarkdownInText(text6, HELP9);
83143
+ if (typeof rejected === "number")
83144
+ return rejected;
83145
+ const mangled = await rejectShellMangledValue(text6, HELP9, "--text");
83146
+ if (typeof mangled === "number")
83147
+ return mangled;
83160
83148
  return {
83161
83149
  kind: "text",
83162
83150
  text: text6,
@@ -83182,7 +83170,7 @@ async function validateParagraphEdit(values2, paragraphOptions) {
83182
83170
  if (markdownInline !== undefined || markdownFile !== undefined) {
83183
83171
  const conflict = MARKDOWN_INCOMPATIBLE_FLAGS.find((flag) => values2[flag] !== undefined);
83184
83172
  if (conflict) {
83185
- return fail("USAGE", `--${conflict} can't be combined with --markdown / --markdown-file (the markdown source controls block-level styling)`, HELP10);
83173
+ return fail("USAGE", `--${conflict} can't be combined with --markdown / --markdown-file (the markdown source controls block-level styling)`, HELP9);
83186
83174
  }
83187
83175
  const source2 = markdownInline !== undefined ? markdownInline : await loadMarkdownFile(markdownFile);
83188
83176
  if (typeof source2 === "number")
@@ -83239,6 +83227,11 @@ async function parseParagraphOptions(values2) {
83239
83227
  }
83240
83228
  return out;
83241
83229
  }
83230
+ function injectTabsIntoSpec(spec, tabs) {
83231
+ if (spec.kind === "text" || spec.kind === "runs" || spec.kind === "code" || spec.kind === "markdown" || spec.kind === "paragraphProps") {
83232
+ spec.paragraphOptions.tabs = tabs;
83233
+ }
83234
+ }
83242
83235
  async function respondDryRun2(opts) {
83243
83236
  await respond({
83244
83237
  operation: "edit",
@@ -83258,13 +83251,14 @@ async function emitEditAck(opts) {
83258
83251
  });
83259
83252
  return EXIT2.OK;
83260
83253
  }
83261
- var AT_FORMS2, HELP10, MARKDOWN_INCOMPATIBLE_FLAGS, OPTION_SPEC3;
83254
+ var AT_FORMS2, HELP9, MARKDOWN_INCOMPATIBLE_FLAGS, OPTION_SPEC2;
83262
83255
  var init_edit2 = __esm(() => {
83263
83256
  init_core2();
83264
83257
  init_equation();
83265
83258
  init_parse_helpers();
83266
83259
  init_respond();
83267
83260
  init_batch2();
83261
+ init_tabs();
83268
83262
  AT_FORMS2 = describeForms([
83269
83263
  "paragraph",
83270
83264
  "span",
@@ -83274,7 +83268,7 @@ var init_edit2 = __esm(() => {
83274
83268
  "section",
83275
83269
  "equation"
83276
83270
  ], " ");
83277
- HELP10 = `docx edit \u2014 replace a paragraph (or paragraph range), a section, or an equation
83271
+ HELP9 = `docx edit \u2014 replace a paragraph (or paragraph range), a section, or an equation
83278
83272
 
83279
83273
  Usage:
83280
83274
  docx edit FILE --at LOCATOR <content> [options]
@@ -83291,7 +83285,8 @@ ${AT_FORMS2}
83291
83285
  inheriting the existing run's formatting \u2014 paste a locator
83292
83286
  straight from \`docx find\`. See \`docx info locators\`.
83293
83287
 
83294
- Paragraph content (one required for paragraph / range locators):
83288
+ Paragraph content (one required for paragraph / range locators \u2014 UNLESS you pass
83289
+ only --style/--alignment below, which restyle the paragraph in place):
83295
83290
  --text TEXT Replace with a single-run paragraph
83296
83291
  --runs JSON Replace with custom runs (Run[] JSON)
83297
83292
  --code TEXT Replace with a code block \u2014 newlines split into one
@@ -83334,6 +83329,25 @@ Equation editing (requires --at eqN):
83334
83329
  Paragraph options:
83335
83330
  --style NAME Paragraph style (e.g., Heading1)
83336
83331
  --alignment ALIGN left | center | right | justify
83332
+ --tabs SPEC Replace the paragraph's tab stops. SPEC is:
83333
+ right \u2014 a single RIGHT tab flush at the text margin.
83334
+ This is the CURE for the \`docx:layout \u2026 warn=\u2026\`
83335
+ hint \`read\` prints on a line whose trailing
83336
+ content sits on a fixed LEFT tab: a long value
83337
+ (e.g. "San Francisco, CA") overflows the margin
83338
+ and WRAPS to a second line. A right tab at the
83339
+ margin right-aligns it instead, so it never wraps.
83340
+ clear \u2014 remove the paragraph's tab stops.
83341
+ <list> \u2014 explicit stops, e.g. \`right@7.5in\` or
83342
+ \`left@1in,right@7.5in\` (ALIGN@POSin, comma list).
83343
+ Pass any of --style/--alignment/--tabs ALONE (no content flag) to adjust the
83344
+ paragraph in place, keeping its text/runs: \`docx edit doc.docx --at p4 --style
83345
+ Heading1\`, \`docx edit doc.docx --at p9 --tabs right\`. --tabs also rides along
83346
+ with --text (fill the line AND fix its tab in one call), and works per-entry in
83347
+ --batch ({"at":"p9","text":"Harvard University\\tCambridge, MA","tabs":"right"}).
83348
+ ONE-CALL cure: a RANGE locator fixes every wrapping tab line at once \u2014
83349
+ \`docx edit doc.docx --at p9-p38 --tabs right\` (the exact "fix-all" command
83350
+ \`read\` prints at the top when lines wrap; it skips paragraphs with no tab stops).
83337
83351
 
83338
83352
  Run options (only with --text):
83339
83353
  --color HEX Run color, hex (e.g., 800080 for purple)
@@ -83413,7 +83427,7 @@ Batch JSONL example (one edit per line):
83413
83427
  {"at": "p1", "markdown": "## Revised heading"}
83414
83428
  `;
83415
83429
  MARKDOWN_INCOMPATIBLE_FLAGS = ["style", "alignment"];
83416
- OPTION_SPEC3 = {
83430
+ OPTION_SPEC2 = {
83417
83431
  at: { type: "string" },
83418
83432
  batch: { type: "string" },
83419
83433
  text: { type: "string" },
@@ -83432,6 +83446,7 @@ Batch JSONL example (one edit per line):
83432
83446
  type: { type: "string" },
83433
83447
  style: { type: "string" },
83434
83448
  alignment: { type: "string" },
83449
+ tabs: { type: "string" },
83435
83450
  color: { type: "string" },
83436
83451
  bold: { type: "boolean" },
83437
83452
  italic: { type: "boolean" },
@@ -83446,7 +83461,7 @@ Batch JSONL example (one edit per line):
83446
83461
  var exports_add2 = {};
83447
83462
  __export(exports_add2, {
83448
83463
  runAddNote: () => runAddNote,
83449
- run: () => run11
83464
+ run: () => run10
83450
83465
  });
83451
83466
  function helpFor(kind) {
83452
83467
  const verb = kind === "footnote" ? "footnotes" : "endnotes";
@@ -83712,8 +83727,8 @@ function splitMarkdownBlocks(blocks) {
83712
83727
  }
83713
83728
  function runsToXml(runs) {
83714
83729
  const out = [];
83715
- for (const run11 of runs) {
83716
- const element = RunElement({ run: run11 });
83730
+ for (const run10 of runs) {
83731
+ const element = RunElement({ run: run10 });
83717
83732
  if (element)
83718
83733
  out.push(element);
83719
83734
  }
@@ -83751,7 +83766,7 @@ function parseAnchor(input) {
83751
83766
  }
83752
83767
  return null;
83753
83768
  }
83754
- async function run11(args) {
83769
+ async function run10(args) {
83755
83770
  return runAddNote(args, "footnote");
83756
83771
  }
83757
83772
  var ANCHOR_FORMS;
@@ -83773,7 +83788,7 @@ var init_add2 = __esm(() => {
83773
83788
  var exports_delete3 = {};
83774
83789
  __export(exports_delete3, {
83775
83790
  runDeleteNote: () => runDeleteNote,
83776
- run: () => run12
83791
+ run: () => run11
83777
83792
  });
83778
83793
  function helpFor2(kind) {
83779
83794
  const verb = kind === "footnote" ? "footnotes" : "endnotes";
@@ -83882,7 +83897,7 @@ async function runDeleteNote(args, kind) {
83882
83897
  });
83883
83898
  return EXIT2.OK;
83884
83899
  }
83885
- async function run12(args) {
83900
+ async function run11(args) {
83886
83901
  return runDeleteNote(args, "footnote");
83887
83902
  }
83888
83903
  var init_delete3 = __esm(() => {
@@ -83895,7 +83910,7 @@ var init_delete3 = __esm(() => {
83895
83910
  var exports_edit2 = {};
83896
83911
  __export(exports_edit2, {
83897
83912
  runEditNote: () => runEditNote,
83898
- run: () => run13
83913
+ run: () => run12
83899
83914
  });
83900
83915
  function helpFor3(kind) {
83901
83916
  const verb = kind === "footnote" ? "footnotes" : "endnotes";
@@ -84086,8 +84101,8 @@ function splitMarkdownBlocks2(blocks) {
84086
84101
  }
84087
84102
  function runsToXml2(runs) {
84088
84103
  const out = [];
84089
- for (const run13 of runs) {
84090
- const element = RunElement({ run: run13 });
84104
+ for (const run12 of runs) {
84105
+ const element = RunElement({ run: run12 });
84091
84106
  if (element)
84092
84107
  out.push(element);
84093
84108
  }
@@ -84136,7 +84151,7 @@ function NoteParagraph({
84136
84151
  ]
84137
84152
  }, undefined, true, undefined, this);
84138
84153
  }
84139
- async function run13(args) {
84154
+ async function run12(args) {
84140
84155
  return runEditNote(args, "footnote");
84141
84156
  }
84142
84157
  var init_edit3 = __esm(() => {
@@ -84154,7 +84169,7 @@ var init_edit3 = __esm(() => {
84154
84169
  var exports_list2 = {};
84155
84170
  __export(exports_list2, {
84156
84171
  runListNotes: () => runListNotes,
84157
- run: () => run14
84172
+ run: () => run13
84158
84173
  });
84159
84174
  function helpFor4(kind) {
84160
84175
  const verb = kind === "footnote" ? "footnotes" : "endnotes";
@@ -84196,7 +84211,7 @@ async function runListNotes(args, kind) {
84196
84211
  await respond(notes);
84197
84212
  return EXIT2.OK;
84198
84213
  }
84199
- async function run14(args) {
84214
+ async function run13(args) {
84200
84215
  return runListNotes(args, "footnote");
84201
84216
  }
84202
84217
  var init_list4 = __esm(() => {
@@ -84206,12 +84221,12 @@ var init_list4 = __esm(() => {
84206
84221
  // src/cli/endnotes/index.ts
84207
84222
  var exports_endnotes = {};
84208
84223
  __export(exports_endnotes, {
84209
- run: () => run15
84224
+ run: () => run14
84210
84225
  });
84211
- async function run15(args) {
84226
+ async function run14(args) {
84212
84227
  const verb = args[0];
84213
84228
  if (!verb || verb === "--help" || verb === "-h" || verb === "help") {
84214
- await writeStdout(HELP11);
84229
+ await writeStdout(HELP10);
84215
84230
  return verb ? 0 : 2;
84216
84231
  }
84217
84232
  const handler = SUBCOMMANDS2[verb];
@@ -84220,7 +84235,7 @@ async function run15(args) {
84220
84235
  }
84221
84236
  return handler(args.slice(1));
84222
84237
  }
84223
- var SUBCOMMANDS2, HELP11 = `docx endnotes \u2014 author endnotes
84238
+ var SUBCOMMANDS2, HELP10 = `docx endnotes \u2014 author endnotes
84224
84239
 
84225
84240
  Usage:
84226
84241
  docx endnotes <verb> FILE [options]
@@ -84250,7 +84265,7 @@ var init_endnotes = __esm(() => {
84250
84265
  // src/cli/find/index.ts
84251
84266
  var exports_find = {};
84252
84267
  __export(exports_find, {
84253
- run: () => run16
84268
+ run: () => run15
84254
84269
  });
84255
84270
  function withBareHighlightAsAny(args) {
84256
84271
  const out = [];
@@ -84272,7 +84287,7 @@ function withBareHighlightAsAny(args) {
84272
84287
  }
84273
84288
  return out;
84274
84289
  }
84275
- async function run16(args) {
84290
+ async function run15(args) {
84276
84291
  const parsed = await tryParseArgs(withBareHighlightAsAny(args), {
84277
84292
  regex: { type: "boolean" },
84278
84293
  "ignore-case": { type: "boolean" },
@@ -84288,17 +84303,17 @@ async function run16(args) {
84288
84303
  underline: { type: "boolean" },
84289
84304
  json: { type: "boolean" },
84290
84305
  help: { type: "boolean", short: "h" }
84291
- }, HELP12);
84306
+ }, HELP11);
84292
84307
  if (typeof parsed === "number")
84293
84308
  return parsed;
84294
84309
  if (parsed.values.help) {
84295
- await writeStdout(HELP12);
84310
+ await writeStdout(HELP11);
84296
84311
  return EXIT2.OK;
84297
84312
  }
84298
84313
  const path2 = parsed.positionals[0];
84299
84314
  const query = parsed.positionals[1];
84300
84315
  if (!path2)
84301
- return fail("USAGE", "Missing FILE argument", HELP12);
84316
+ return fail("USAGE", "Missing FILE argument", HELP11);
84302
84317
  const formatFilter = {};
84303
84318
  if (parsed.values.highlight !== undefined)
84304
84319
  formatFilter.highlight = parsed.values.highlight;
@@ -84312,10 +84327,10 @@ async function run16(args) {
84312
84327
  formatFilter.underline = true;
84313
84328
  const hasFormatFilter = Object.keys(formatFilter).length > 0;
84314
84329
  if (query == null && !hasFormatFilter) {
84315
- return fail("USAGE", "Missing QUERY (or a --highlight/--color/--bold/--italic/--underline filter)", HELP12);
84330
+ return fail("USAGE", "Missing QUERY (or a --highlight/--color/--bold/--italic/--underline filter)", HELP11);
84316
84331
  }
84317
84332
  if (query != null && hasFormatFilter) {
84318
- return fail("USAGE", "Pass a text QUERY or formatting filters (--highlight/--color/...), not both", HELP12);
84333
+ return fail("USAGE", "Pass a text QUERY or formatting filters (--highlight/--color/...), not both", HELP11);
84319
84334
  }
84320
84335
  const ignoreCase = Boolean(parsed.values["ignore-case"]);
84321
84336
  const useRegex = Boolean(parsed.values.regex);
@@ -84390,7 +84405,7 @@ async function run16(args) {
84390
84405
  }
84391
84406
  return EXIT2.OK;
84392
84407
  }
84393
- var HELP12 = `docx find \u2014 locate text spans and return their locators
84408
+ var HELP11 = `docx find \u2014 locate text spans and return their locators
84394
84409
 
84395
84410
  Usage:
84396
84411
  docx find FILE QUERY [options]
@@ -84463,12 +84478,12 @@ var init_find2 = __esm(() => {
84463
84478
  // src/cli/footnotes/index.ts
84464
84479
  var exports_footnotes = {};
84465
84480
  __export(exports_footnotes, {
84466
- run: () => run17
84481
+ run: () => run16
84467
84482
  });
84468
- async function run17(args) {
84483
+ async function run16(args) {
84469
84484
  const verb = args[0];
84470
84485
  if (!verb || verb === "--help" || verb === "-h" || verb === "help") {
84471
- await writeStdout(HELP13);
84486
+ await writeStdout(HELP12);
84472
84487
  return verb ? 0 : 2;
84473
84488
  }
84474
84489
  const loader = SUBCOMMANDS3[verb];
@@ -84478,7 +84493,7 @@ async function run17(args) {
84478
84493
  const module_ = await loader();
84479
84494
  return module_.run(args.slice(1));
84480
84495
  }
84481
- var SUBCOMMANDS3, HELP13 = `docx footnotes \u2014 author footnotes
84496
+ var SUBCOMMANDS3, HELP12 = `docx footnotes \u2014 author footnotes
84482
84497
 
84483
84498
  Usage:
84484
84499
  docx footnotes <verb> FILE [options]
@@ -84502,7 +84517,7 @@ var init_footnotes = __esm(() => {
84502
84517
  });
84503
84518
 
84504
84519
  // src/core/hyperlinks/wrap.tsx
84505
- function wrapSpanInHyperlink(paragraph2, span, relationshipId) {
84520
+ function wrapSpanInHyperlink(paragraph2, span, relationshipId, applyStyle = true) {
84506
84521
  if (span.start >= span.end) {
84507
84522
  throw new HyperlinkWrapError(`Empty or inverted span ${span.start}-${span.end}`);
84508
84523
  }
@@ -84513,6 +84528,9 @@ function wrapSpanInHyperlink(paragraph2, span, relationshipId) {
84513
84528
  const placeWrapper = () => {
84514
84529
  if (placed || wrappedRuns.length === 0)
84515
84530
  return;
84531
+ if (applyStyle)
84532
+ for (const run17 of wrappedRuns)
84533
+ applyHyperlinkRunStyle(run17);
84516
84534
  newChildren.push(hyperlinkWrapper(relationshipId, wrappedRuns));
84517
84535
  wrappedRuns.length = 0;
84518
84536
  placed = true;
@@ -84573,6 +84591,20 @@ function hyperlinkWrapper(relationshipId, runs) {
84573
84591
  children: runs
84574
84592
  }, undefined, false, undefined, this);
84575
84593
  }
84594
+ function applyHyperlinkRunStyle(run17) {
84595
+ if (run17.tag !== "w:r")
84596
+ return;
84597
+ let rPr = run17.findChild("w:rPr");
84598
+ if (!rPr) {
84599
+ rPr = /* @__PURE__ */ jsxDEV(w.rPr, {}, undefined, false, undefined, this);
84600
+ run17.children.unshift(rPr);
84601
+ }
84602
+ if (rPr.findChild("w:rStyle"))
84603
+ return;
84604
+ rPr.children.unshift(/* @__PURE__ */ jsxDEV(w.rStyle, {
84605
+ "w-val": "Hyperlink"
84606
+ }, undefined, false, undefined, this));
84607
+ }
84576
84608
  var HyperlinkWrapError;
84577
84609
  var init_wrap = __esm(() => {
84578
84610
  init_jsx();
@@ -84594,7 +84626,10 @@ class Hyperlinks {
84594
84626
  }
84595
84627
  add(paragraph2, span, url, options = {}) {
84596
84628
  const relationshipId = this.document.relationships.addHyperlink(url);
84597
- wrapSpanInHyperlink(paragraph2, span, relationshipId);
84629
+ const applyStyle = options.style ?? true;
84630
+ if (applyStyle)
84631
+ this.document.ensureStyles().ensureStyle("Hyperlink");
84632
+ wrapSpanInHyperlink(paragraph2, span, relationshipId, applyStyle);
84598
84633
  this.document.relationships.hyperlinksByRelationshipId.set(relationshipId, {
84599
84634
  url
84600
84635
  });
@@ -84697,31 +84732,32 @@ var init_hyperlinks = __esm(() => {
84697
84732
  // src/cli/hyperlinks/add.tsx
84698
84733
  var exports_add3 = {};
84699
84734
  __export(exports_add3, {
84700
- run: () => run18
84735
+ run: () => run17
84701
84736
  });
84702
- async function run18(args) {
84737
+ async function run17(args) {
84703
84738
  const parsed = await tryParseArgs(args, {
84704
84739
  at: { type: "string" },
84705
84740
  url: { type: "string" },
84706
84741
  author: { type: "string" },
84742
+ "no-style": { type: "boolean" },
84707
84743
  ...SAVE_FLAGS
84708
- }, HELP14);
84744
+ }, HELP13);
84709
84745
  if (typeof parsed === "number")
84710
84746
  return parsed;
84711
84747
  if (parsed.values.help) {
84712
- await writeStdout(HELP14);
84748
+ await writeStdout(HELP13);
84713
84749
  return EXIT2.OK;
84714
84750
  }
84715
84751
  setVerboseAck(Boolean(parsed.values.verbose));
84716
84752
  const path2 = parsed.positionals[0];
84717
84753
  if (!path2)
84718
- return fail("USAGE", "Missing FILE argument", HELP14);
84754
+ return fail("USAGE", "Missing FILE argument", HELP13);
84719
84755
  const atInput = parsed.values.at;
84720
84756
  const url = parsed.values.url;
84721
84757
  if (!atInput)
84722
- return fail("USAGE", "Missing --at LOCATOR", HELP14);
84758
+ return fail("USAGE", "Missing --at LOCATOR", HELP13);
84723
84759
  if (!url)
84724
- return fail("USAGE", "Missing --url URL", HELP14);
84760
+ return fail("USAGE", "Missing --url URL", HELP13);
84725
84761
  let locator;
84726
84762
  try {
84727
84763
  locator = parseLocator(atInput);
@@ -84755,7 +84791,8 @@ async function run18(args) {
84755
84791
  }
84756
84792
  try {
84757
84793
  new Hyperlinks(document4).add(paragraphRef.node, target.span, url, {
84758
- author: parsed.values.author
84794
+ author: parsed.values.author,
84795
+ style: !parsed.values["no-style"]
84759
84796
  });
84760
84797
  } catch (error) {
84761
84798
  if (error instanceof HyperlinkWrapError) {
@@ -84784,27 +84821,27 @@ async function findMintedHyperlink(savedPath, blockId, spanStart) {
84784
84821
  let offset2 = 0;
84785
84822
  let foundId;
84786
84823
  let text6 = "";
84787
- for (const run19 of block.runs) {
84788
- if (run19.type !== "text")
84824
+ for (const run18 of block.runs) {
84825
+ if (run18.type !== "text")
84789
84826
  continue;
84790
- const runEnd = offset2 + run19.text.length;
84791
- if (foundId === undefined && run19.hyperlink && spanStart < runEnd) {
84792
- foundId = run19.hyperlink.id;
84827
+ const runEnd = offset2 + run18.text.length;
84828
+ if (foundId === undefined && run18.hyperlink && spanStart < runEnd) {
84829
+ foundId = run18.hyperlink.id;
84793
84830
  }
84794
- if (foundId !== undefined && run19.hyperlink?.id === foundId) {
84795
- text6 += run19.text;
84831
+ if (foundId !== undefined && run18.hyperlink?.id === foundId) {
84832
+ text6 += run18.text;
84796
84833
  }
84797
84834
  offset2 = runEnd;
84798
84835
  }
84799
84836
  return foundId === undefined ? undefined : { id: foundId, text: text6 };
84800
84837
  }
84801
- var AT_FORMS3, HELP14;
84838
+ var AT_FORMS3, HELP13;
84802
84839
  var init_add3 = __esm(() => {
84803
84840
  init_core2();
84804
84841
  init_hyperlinks();
84805
84842
  init_respond();
84806
84843
  AT_FORMS3 = describeForms(["span", "cellSpan"], " ");
84807
- HELP14 = `docx hyperlinks add \u2014 wrap an existing span in a hyperlink
84844
+ HELP13 = `docx hyperlinks add \u2014 wrap an existing span in a hyperlink
84808
84845
 
84809
84846
  Usage:
84810
84847
  docx hyperlinks add FILE --at LOCATOR --url URL [options]
@@ -84818,6 +84855,9 @@ ${AT_FORMS3}
84818
84855
  --url URL Target URL
84819
84856
 
84820
84857
  Optional:
84858
+ --no-style Don't apply Word's "Hyperlink" character style (blue +
84859
+ underline). By default the wrapped text is styled so the link
84860
+ is visibly a link; pass this to keep the original run look.
84821
84861
  --author NAME Author for the audit comment when track-changes is on
84822
84862
  (default: $DOCX_AUTHOR)
84823
84863
  -o, --output PATH Write to PATH instead of overwriting FILE
@@ -84851,27 +84891,27 @@ Examples:
84851
84891
  // src/cli/hyperlinks/delete.ts
84852
84892
  var exports_delete4 = {};
84853
84893
  __export(exports_delete4, {
84854
- run: () => run19
84894
+ run: () => run18
84855
84895
  });
84856
- async function run19(args) {
84896
+ async function run18(args) {
84857
84897
  const parsed = await tryParseArgs(args, {
84858
84898
  at: { type: "string" },
84859
84899
  author: { type: "string" },
84860
84900
  ...SAVE_FLAGS
84861
- }, HELP15);
84901
+ }, HELP14);
84862
84902
  if (typeof parsed === "number")
84863
84903
  return parsed;
84864
84904
  if (parsed.values.help) {
84865
- await writeStdout(HELP15);
84905
+ await writeStdout(HELP14);
84866
84906
  return EXIT2.OK;
84867
84907
  }
84868
84908
  setVerboseAck(Boolean(parsed.values.verbose));
84869
84909
  const path2 = parsed.positionals[0];
84870
84910
  if (!path2)
84871
- return fail("USAGE", "Missing FILE argument", HELP15);
84911
+ return fail("USAGE", "Missing FILE argument", HELP14);
84872
84912
  const targetId = parsed.values.at;
84873
84913
  if (!targetId)
84874
- return fail("USAGE", "Missing --at linkN", HELP15);
84914
+ return fail("USAGE", "Missing --at linkN", HELP14);
84875
84915
  const document4 = await openOrFail(path2);
84876
84916
  if (typeof document4 === "number")
84877
84917
  return document4;
@@ -84912,13 +84952,13 @@ async function run19(args) {
84912
84952
  });
84913
84953
  return EXIT2.OK;
84914
84954
  }
84915
- var AT_FORMS4, HELP15;
84955
+ var AT_FORMS4, HELP14;
84916
84956
  var init_delete4 = __esm(() => {
84917
84957
  init_core2();
84918
84958
  init_hyperlinks();
84919
84959
  init_respond();
84920
84960
  AT_FORMS4 = describeForms(["hyperlink"], " ");
84921
- HELP15 = `docx hyperlinks delete \u2014 unwrap a hyperlink (keep the text)
84961
+ HELP14 = `docx hyperlinks delete \u2014 unwrap a hyperlink (keep the text)
84922
84962
 
84923
84963
  Usage:
84924
84964
  docx hyperlinks delete FILE --at linkN [options]
@@ -84957,21 +84997,21 @@ Examples:
84957
84997
  // src/cli/hyperlinks/list.ts
84958
84998
  var exports_list3 = {};
84959
84999
  __export(exports_list3, {
84960
- run: () => run20
85000
+ run: () => run19
84961
85001
  });
84962
- async function run20(args) {
85002
+ async function run19(args) {
84963
85003
  const parsed = await tryParseArgs(args, {
84964
85004
  help: { type: "boolean", short: "h" }
84965
- }, HELP16);
85005
+ }, HELP15);
84966
85006
  if (typeof parsed === "number")
84967
85007
  return parsed;
84968
85008
  if (parsed.values.help) {
84969
- await writeStdout(HELP16);
85009
+ await writeStdout(HELP15);
84970
85010
  return EXIT2.OK;
84971
85011
  }
84972
85012
  const path2 = parsed.positionals[0];
84973
85013
  if (!path2)
84974
- return fail("USAGE", "Missing FILE argument", HELP16);
85014
+ return fail("USAGE", "Missing FILE argument", HELP15);
84975
85015
  const document4 = await openOrFail(path2);
84976
85016
  if (typeof document4 === "number")
84977
85017
  return document4;
@@ -84984,10 +85024,10 @@ function collectHyperlinks(blocks, entries) {
84984
85024
  for (const block of iterateBlocks(blocks)) {
84985
85025
  if (block.type !== "paragraph")
84986
85026
  continue;
84987
- for (const run21 of block.runs) {
84988
- if (run21.type !== "text" || !run21.hyperlink)
85027
+ for (const run20 of block.runs) {
85028
+ if (run20.type !== "text" || !run20.hyperlink)
84989
85029
  continue;
84990
- addToEntry(entries, run21.hyperlink, block.id, run21.text);
85030
+ addToEntry(entries, run20.hyperlink, block.id, run20.text);
84991
85031
  }
84992
85032
  }
84993
85033
  }
@@ -85010,7 +85050,7 @@ function addToEntry(entries, hyperlink, blockId, text6) {
85010
85050
  entry.tooltip = hyperlink.tooltip;
85011
85051
  entries.set(hyperlink.id, entry);
85012
85052
  }
85013
- var HELP16 = `docx hyperlinks list \u2014 print hyperlink manifest as JSON
85053
+ var HELP15 = `docx hyperlinks list \u2014 print hyperlink manifest as JSON
85014
85054
 
85015
85055
  Usage:
85016
85056
  docx hyperlinks list FILE [options]
@@ -85036,31 +85076,31 @@ var init_list5 = __esm(() => {
85036
85076
  // src/cli/hyperlinks/replace.ts
85037
85077
  var exports_replace = {};
85038
85078
  __export(exports_replace, {
85039
- run: () => run21
85079
+ run: () => run20
85040
85080
  });
85041
- async function run21(args) {
85081
+ async function run20(args) {
85042
85082
  const parsed = await tryParseArgs(args, {
85043
85083
  at: { type: "string" },
85044
85084
  with: { type: "string" },
85045
85085
  author: { type: "string" },
85046
85086
  ...SAVE_FLAGS
85047
- }, HELP17);
85087
+ }, HELP16);
85048
85088
  if (typeof parsed === "number")
85049
85089
  return parsed;
85050
85090
  if (parsed.values.help) {
85051
- await writeStdout(HELP17);
85091
+ await writeStdout(HELP16);
85052
85092
  return EXIT2.OK;
85053
85093
  }
85054
85094
  setVerboseAck(Boolean(parsed.values.verbose));
85055
85095
  const path2 = parsed.positionals[0];
85056
85096
  if (!path2)
85057
- return fail("USAGE", "Missing FILE argument", HELP17);
85097
+ return fail("USAGE", "Missing FILE argument", HELP16);
85058
85098
  const targetId = parsed.values.at;
85059
85099
  if (!targetId)
85060
- return fail("USAGE", "Missing --at linkN", HELP17);
85100
+ return fail("USAGE", "Missing --at linkN", HELP16);
85061
85101
  const newUrl = parsed.values.with;
85062
85102
  if (!newUrl)
85063
- return fail("USAGE", "Missing --with URL", HELP17);
85103
+ return fail("USAGE", "Missing --with URL", HELP16);
85064
85104
  const document4 = await openOrFail(path2);
85065
85105
  if (typeof document4 === "number")
85066
85106
  return document4;
@@ -85099,13 +85139,13 @@ async function run21(args) {
85099
85139
  });
85100
85140
  return EXIT2.OK;
85101
85141
  }
85102
- var AT_FORMS5, HELP17;
85142
+ var AT_FORMS5, HELP16;
85103
85143
  var init_replace2 = __esm(() => {
85104
85144
  init_core2();
85105
85145
  init_hyperlinks();
85106
85146
  init_respond();
85107
85147
  AT_FORMS5 = describeForms(["hyperlink"], " ");
85108
- HELP17 = `docx hyperlinks replace \u2014 change a hyperlink's URL
85148
+ HELP16 = `docx hyperlinks replace \u2014 change a hyperlink's URL
85109
85149
 
85110
85150
  Usage:
85111
85151
  docx hyperlinks replace FILE --at linkN --with URL [options]
@@ -85146,12 +85186,12 @@ Examples:
85146
85186
  // src/cli/hyperlinks/index.ts
85147
85187
  var exports_hyperlinks = {};
85148
85188
  __export(exports_hyperlinks, {
85149
- run: () => run22
85189
+ run: () => run21
85150
85190
  });
85151
- async function run22(args) {
85191
+ async function run21(args) {
85152
85192
  const verb = args[0];
85153
85193
  if (!verb || verb === "--help" || verb === "-h" || verb === "help") {
85154
- await writeStdout(HELP18);
85194
+ await writeStdout(HELP17);
85155
85195
  return verb ? 0 : 2;
85156
85196
  }
85157
85197
  const loader = SUBCOMMANDS4[verb];
@@ -85161,7 +85201,7 @@ async function run22(args) {
85161
85201
  const module_ = await loader();
85162
85202
  return module_.run(args.slice(1));
85163
85203
  }
85164
- var SUBCOMMANDS4, HELP18 = `docx hyperlinks \u2014 manage hyperlinks
85204
+ var SUBCOMMANDS4, HELP17 = `docx hyperlinks \u2014 manage hyperlinks
85165
85205
 
85166
85206
  Usage:
85167
85207
  docx hyperlinks <verb> FILE [options]
@@ -85380,24 +85420,24 @@ var init_batch3 = __esm(() => {
85380
85420
  // src/cli/insert/index.tsx
85381
85421
  var exports_insert = {};
85382
85422
  __export(exports_insert, {
85383
- run: () => run23,
85423
+ run: () => run22,
85384
85424
  parseTargetPlacement: () => parseTargetPlacement,
85385
85425
  parseParagraphOptions: () => parseParagraphOptions2,
85386
85426
  chooseContentSpec: () => chooseContentSpec,
85387
85427
  MARKDOWN_INCOMPATIBLE_FLAGS: () => MARKDOWN_INCOMPATIBLE_FLAGS2
85388
85428
  });
85389
- async function run23(args) {
85390
- const parsed = await tryParseArgs(args, OPTION_SPEC4, HELP19);
85429
+ async function run22(args) {
85430
+ const parsed = await tryParseArgs(args, OPTION_SPEC3, HELP18);
85391
85431
  if (typeof parsed === "number")
85392
85432
  return parsed;
85393
85433
  if (parsed.values.help) {
85394
- await writeStdout(HELP19);
85434
+ await writeStdout(HELP18);
85395
85435
  return EXIT2.OK;
85396
85436
  }
85397
85437
  setVerboseAck(Boolean(parsed.values.verbose));
85398
85438
  const filePath = parsed.positionals[0];
85399
85439
  if (!filePath)
85400
- return fail("USAGE", "Missing FILE argument", HELP19);
85440
+ return fail("USAGE", "Missing FILE argument", HELP18);
85401
85441
  const batchInput = parsed.values.batch;
85402
85442
  if (batchInput !== undefined) {
85403
85443
  return runInsertBatch(filePath, batchInput, parsed.values);
@@ -85452,16 +85492,20 @@ async function commitInsert(document4, blockRef, blocks, opts) {
85452
85492
  if (insertedNodes.has(reference.node))
85453
85493
  locators.push(blockId);
85454
85494
  }
85495
+ const destination = opts.outputPath ?? opts.filePath;
85455
85496
  await respondMinted(locators, {
85456
85497
  ok: true,
85457
85498
  operation: "insert",
85458
- path: opts.outputPath ?? opts.filePath,
85499
+ path: destination,
85459
85500
  locators,
85460
85501
  anchor: opts.placement.locator,
85461
85502
  placement: opts.placement.mode
85462
- });
85503
+ }, isLayoutAffecting(opts.spec) ? renderVerifyHint(destination) : undefined);
85463
85504
  return EXIT2.OK;
85464
85505
  }
85506
+ function isLayoutAffecting(spec) {
85507
+ return spec.kind === "section" || spec.kind === "image" || spec.kind === "table" || spec.kind === "break";
85508
+ }
85465
85509
  async function buildSingleShotOptions(filePath, values2) {
85466
85510
  const placement = await parseTargetPlacement(values2);
85467
85511
  if (typeof placement === "number")
@@ -85469,10 +85513,18 @@ async function buildSingleShotOptions(filePath, values2) {
85469
85513
  const spec = await chooseContentSpec(values2);
85470
85514
  if (typeof spec === "number")
85471
85515
  return spec;
85516
+ if (spec.kind === "text") {
85517
+ const rejected = await rejectMarkdownInText(values2.text, HELP18);
85518
+ if (typeof rejected === "number")
85519
+ return rejected;
85520
+ const mangled = await rejectShellMangledValue(values2.text, HELP18, "--text");
85521
+ if (typeof mangled === "number")
85522
+ return mangled;
85523
+ }
85472
85524
  if (spec.kind === "markdown") {
85473
85525
  const conflict = MARKDOWN_INCOMPATIBLE_FLAGS2.find((flag) => values2[flag] !== undefined);
85474
85526
  if (conflict) {
85475
- return fail("USAGE", `--${conflict} can't be combined with --markdown / --markdown-file (the markdown source controls block-level styling)`, HELP19);
85527
+ return fail("USAGE", `--${conflict} can't be combined with --markdown / --markdown-file (the markdown source controls block-level styling)`, HELP18);
85476
85528
  }
85477
85529
  }
85478
85530
  const paragraphOptions = await parseParagraphOptions2(values2);
@@ -85493,23 +85545,26 @@ async function parseTargetPlacement(values2) {
85493
85545
  const after = values2.after;
85494
85546
  const before = values2.before;
85495
85547
  if (!after && !before) {
85496
- return fail("USAGE", "Missing locator: pass --after or --before", HELP19);
85548
+ return fail("USAGE", "Missing locator: pass --after or --before", HELP18);
85497
85549
  }
85498
85550
  if (after && before) {
85499
- return fail("USAGE", "Pass either --after or --before, not both", HELP19);
85551
+ return fail("USAGE", "Pass either --after or --before, not both", HELP18);
85500
85552
  }
85501
85553
  if (after !== undefined)
85502
85554
  return { mode: "after", locator: after };
85503
85555
  return { mode: "before", locator: before };
85504
85556
  }
85505
85557
  async function chooseContentSpec(values2) {
85558
+ if (values2.section !== undefined || values2.columns !== undefined || values2.type !== undefined) {
85559
+ return fail("USAGE", "insert no longer creates section/column layout \u2014 use `docx sections`", "To put paragraphs pN\u2026pM in N columns: `docx sections --at pN-pM --columns N`. To recount an existing section: `docx sections --at sN --columns N`.");
85560
+ }
85506
85561
  const present = CONTENT_KINDS.filter((kind) => values2[kind.flag] !== undefined);
85507
85562
  if (present.length > 1) {
85508
- return fail("USAGE", `Pass only one of ${CONTENT_FLAG_LIST}`, HELP19);
85563
+ return fail("USAGE", `Pass only one of ${CONTENT_FLAG_LIST}`, HELP18);
85509
85564
  }
85510
85565
  const chosen = present[0];
85511
85566
  if (!chosen) {
85512
- return fail("USAGE", `Missing content: pass ${CONTENT_FLAG_LIST}`, HELP19);
85567
+ return fail("USAGE", `Missing content: pass ${CONTENT_FLAG_LIST}`, HELP18);
85513
85568
  }
85514
85569
  const chosenSubFlags = new Set(chosen.subFlags);
85515
85570
  for (const kind of CONTENT_KINDS) {
@@ -85517,7 +85572,7 @@ async function chooseContentSpec(values2) {
85517
85572
  continue;
85518
85573
  const orphan = kind.subFlags.find((flag) => values2[flag] !== undefined && !chosenSubFlags.has(flag));
85519
85574
  if (orphan) {
85520
- return fail("USAGE", `--${orphan} requires --${kind.flag}`, HELP19);
85575
+ return fail("USAGE", `--${orphan} requires --${kind.flag}`, HELP18);
85521
85576
  }
85522
85577
  }
85523
85578
  switch (chosen.flag) {
@@ -85531,10 +85586,6 @@ async function chooseContentSpec(values2) {
85531
85586
  return { kind: "break", breakKind: "page" };
85532
85587
  case "column-break":
85533
85588
  return { kind: "break", breakKind: "column" };
85534
- case "section": {
85535
- const flags = await parseSectionFlags(values2);
85536
- return typeof flags === "number" ? flags : { kind: "section", ...flags };
85537
- }
85538
85589
  case "table": {
85539
85590
  const flags = await parseTableFlags(values2);
85540
85591
  return typeof flags === "number" ? flags : { kind: "table", ...flags };
@@ -85589,7 +85640,7 @@ async function resolveCodeSpec(values2, flag) {
85589
85640
  async function parseImageFlags(values2) {
85590
85641
  const src = values2.image;
85591
85642
  if (!src)
85592
- return fail("USAGE", "--image requires a SRC argument", HELP19);
85643
+ return fail("USAGE", "--image requires a SRC argument", HELP18);
85593
85644
  const out = { src };
85594
85645
  const alt = values2.alt;
85595
85646
  if (alt !== undefined)
@@ -85632,7 +85683,7 @@ async function parseTableFlags(values2) {
85632
85683
  const rowsRaw = values2.rows;
85633
85684
  const colsRaw = values2.cols;
85634
85685
  if (rowsRaw === undefined || colsRaw === undefined) {
85635
- return fail("USAGE", "--table requires --rows and --cols", HELP19);
85686
+ return fail("USAGE", "--table requires --rows and --cols", HELP18);
85636
85687
  }
85637
85688
  const rows = Number.parseInt(rowsRaw, 10);
85638
85689
  const cols = Number.parseInt(colsRaw, 10);
@@ -85709,18 +85760,18 @@ async function parseParagraphOptions2(values2) {
85709
85760
  const listValue = values2.list;
85710
85761
  const listLevelValue = values2["list-level"];
85711
85762
  if (taskValue !== undefined && listValue !== undefined) {
85712
- return fail("USAGE", "--task and --list are mutually exclusive (--task already implies a bullet list)", HELP19);
85763
+ return fail("USAGE", "--task and --list are mutually exclusive (--task already implies a bullet list)", HELP18);
85713
85764
  }
85714
85765
  if (taskValue !== undefined) {
85715
85766
  const checked = parseTaskFlag(taskValue);
85716
85767
  if (checked === null) {
85717
- return fail("USAGE", `--task must be "checked" or "unchecked", got "${taskValue}"`, HELP19);
85768
+ return fail("USAGE", `--task must be "checked" or "unchecked", got "${taskValue}"`, HELP18);
85718
85769
  }
85719
85770
  out.taskState = checked ? "checked" : "unchecked";
85720
85771
  }
85721
85772
  if (listValue !== undefined) {
85722
85773
  if (listValue !== "bullet" && listValue !== "ordered") {
85723
- return fail("USAGE", `--list must be "bullet" or "ordered", got "${listValue}"`, HELP19);
85774
+ return fail("USAGE", `--list must be "bullet" or "ordered", got "${listValue}"`, HELP18);
85724
85775
  }
85725
85776
  out.list = { level: 0, numId: -1 };
85726
85777
  out.listKind = listValue;
@@ -85728,7 +85779,7 @@ async function parseParagraphOptions2(values2) {
85728
85779
  if (listLevelValue !== undefined) {
85729
85780
  const level = Number(listLevelValue);
85730
85781
  if (!Number.isInteger(level) || level < 0 || level > 8) {
85731
- return fail("USAGE", `--list-level must be an integer 0-8, got "${listLevelValue}"`, HELP19);
85782
+ return fail("USAGE", `--list-level must be an integer 0-8, got "${listLevelValue}"`, HELP18);
85732
85783
  }
85733
85784
  if (out.list)
85734
85785
  out.list.level = level;
@@ -85736,14 +85787,14 @@ async function parseParagraphOptions2(values2) {
85736
85787
  }
85737
85788
  return out;
85738
85789
  }
85739
- var ANCHOR_FORMS2, HELP19, OPTION_SPEC4, MARKDOWN_INCOMPATIBLE_FLAGS2, CONTENT_KINDS, CONTENT_FLAG_LIST;
85790
+ var ANCHOR_FORMS2, HELP18, OPTION_SPEC3, MARKDOWN_INCOMPATIBLE_FLAGS2, CONTENT_KINDS, CONTENT_FLAG_LIST;
85740
85791
  var init_insert2 = __esm(() => {
85741
85792
  init_core2();
85742
85793
  init_parse_helpers();
85743
85794
  init_respond();
85744
85795
  init_batch3();
85745
85796
  ANCHOR_FORMS2 = describeForms(["paragraph", "table", "section", "cellParagraph"], " ");
85746
- HELP19 = `docx insert \u2014 insert a block (paragraph, table, image, \u2026) at a locator
85797
+ HELP18 = `docx insert \u2014 insert a block (paragraph, table, image, \u2026) at a locator
85747
85798
 
85748
85799
  Usage:
85749
85800
  docx insert FILE (--after | --before) LOCATOR <content> [options]
@@ -85762,7 +85813,6 @@ Content (one required):
85762
85813
  --runs JSON Insert a paragraph with custom runs (Run[] JSON)
85763
85814
  --page-break Insert an empty paragraph containing a page break
85764
85815
  --column-break Insert an empty paragraph containing a column break
85765
- --section Insert a section boundary (sentinel paragraph w/ inline sectPr)
85766
85816
  --table Insert an empty rows\xD7cols table (requires --rows and --cols)
85767
85817
  --image SRC Insert an image (SRC is a file path, data: URI, or http(s) URL)
85768
85818
  --code TEXT Insert a multi-line code block. Newlines split into one
@@ -85808,19 +85858,20 @@ Run options (only with --text):
85808
85858
  --italic Italic
85809
85859
  --url URL Wrap the inserted text in a hyperlink to URL
85810
85860
 
85811
- Section options (only with --section):
85812
- --columns N Number of columns for the section ending at this boundary
85813
- --type T continuous | nextPage | evenPage | oddPage | nextColumn
85861
+ Column / section layout: NOT here \u2014 use \`docx sections\`. Name the range and it
85862
+ inserts the bounding breaks correctly: \`docx sections --at p6-p16 --columns 2\`.
85863
+ (A raw section break formats the content ABOVE it, the off-by-one that made
85864
+ \`insert --section\` a footgun, so it was removed.)
85814
85865
 
85815
85866
  Agent tip: VERIFY LAYOUT VISUALLY: \`docx read\` shows text and structure as
85816
- Markdown, but NOT how the page actually looks \u2014 multi-column sections, page
85817
- breaks, image sizing, and where content lands on the page do not appear there.
85818
- After inserting layout-affecting content (--section/--columns, --page-break,
85819
- --image, --table), render the document to images and look at them:
85867
+ Markdown, but NOT how the page actually looks \u2014 page breaks, image sizing, and
85868
+ where content lands on the page do not appear there. After inserting
85869
+ layout-affecting content (--page-break, --image, --table), render the document to
85870
+ images and look at them:
85820
85871
  docx render FILE --out pages/ # writes page-001.png, page-002.png, \u2026
85821
- Read the PNGs, check the layout reads the way you intended (columns balanced, no
85822
- stray blank page, figure sized sensibly), and adjust placement + re-render until
85823
- it looks right. Don't assume a section/column insert looks good without seeing it.
85872
+ Read the PNGs, check the layout reads the way you intended (no stray blank page,
85873
+ figure sized sensibly), and adjust placement + re-render until it looks right.
85874
+ Don't assume a layout-affecting insert looks good without seeing it.
85824
85875
 
85825
85876
  Table options (only with --table):
85826
85877
  --rows N Number of rows (required, >= 1)
@@ -85869,7 +85920,6 @@ Examples:
85869
85920
  docx insert doc.docx --after p2 --runs '[{"type":"text","text":"X","bold":true}]'
85870
85921
  docx insert doc.docx --after p3 --text "click here" --url https://example.com
85871
85922
  docx insert doc.docx --after p3 --page-break
85872
- docx insert doc.docx --after p9 --section --columns 2 --type continuous
85873
85923
  docx insert doc.docx --after p3 --table --rows 3 --cols 2
85874
85924
  docx insert doc.docx --after p3 --table --rows 2 --cols 3 --widths 1440,2880,4320
85875
85925
  docx insert doc.docx --after p3 --image ./diagram.png --alt "System diagram"
@@ -85887,7 +85937,7 @@ Batch JSONL example (keys mirror the flags; one insert per line):
85887
85937
  {"before": "p0", "text": "ALERT", "color": "CC0000", "bold": true}
85888
85938
  {"after": "p5", "markdown": "## Summary\\n\\n- point a\\n- point b"}
85889
85939
  `;
85890
- OPTION_SPEC4 = {
85940
+ OPTION_SPEC3 = {
85891
85941
  after: { type: "string" },
85892
85942
  before: { type: "string" },
85893
85943
  batch: { type: "string" },
@@ -85942,7 +85992,6 @@ Batch JSONL example (keys mirror the flags; one insert per line):
85942
85992
  { flag: "runs", subFlags: [] },
85943
85993
  { flag: "page-break", subFlags: [] },
85944
85994
  { flag: "column-break", subFlags: [] },
85945
- { flag: "section", subFlags: ["columns", "type"] },
85946
85995
  {
85947
85996
  flag: "table",
85948
85997
  subFlags: ["rows", "cols", "widths", "table-width", "borders", "layout"]
@@ -85960,16 +86009,16 @@ Batch JSONL example (keys mirror the flags; one insert per line):
85960
86009
  // src/cli/images/add.ts
85961
86010
  var exports_add4 = {};
85962
86011
  __export(exports_add4, {
85963
- run: () => run24
86012
+ run: () => run23
85964
86013
  });
85965
- async function run24(args) {
86014
+ async function run23(args) {
85966
86015
  if (args[0] === "-h" || args[0] === "--help") {
85967
- await writeStdout(HELP20);
86016
+ await writeStdout(HELP19);
85968
86017
  return 0;
85969
86018
  }
85970
- return run23(args);
86019
+ return run22(args);
85971
86020
  }
85972
- var HELP20 = `docx images add \u2014 insert an image (an alias for \`docx insert --image\`)
86021
+ var HELP19 = `docx images add \u2014 insert an image (an alias for \`docx insert --image\`)
85973
86022
 
85974
86023
  Usage:
85975
86024
  docx images add FILE --image PATH --after pN [options]
@@ -85993,28 +86042,28 @@ var init_add4 = __esm(() => {
85993
86042
  // src/cli/images/delete.tsx
85994
86043
  var exports_delete5 = {};
85995
86044
  __export(exports_delete5, {
85996
- run: () => run25
86045
+ run: () => run24
85997
86046
  });
85998
- async function run25(args) {
86047
+ async function run24(args) {
85999
86048
  const parsed = await tryParseArgs(args, {
86000
86049
  at: { type: "string" },
86001
86050
  author: { type: "string" },
86002
86051
  track: { type: "boolean" },
86003
86052
  ...SAVE_FLAGS
86004
- }, HELP21);
86053
+ }, HELP20);
86005
86054
  if (typeof parsed === "number")
86006
86055
  return parsed;
86007
86056
  if (parsed.values.help) {
86008
- await writeStdout(HELP21);
86057
+ await writeStdout(HELP20);
86009
86058
  return EXIT2.OK;
86010
86059
  }
86011
86060
  setVerboseAck(Boolean(parsed.values.verbose));
86012
86061
  const path2 = parsed.positionals[0];
86013
86062
  if (!path2)
86014
- return fail("USAGE", "Missing FILE argument", HELP21);
86063
+ return fail("USAGE", "Missing FILE argument", HELP20);
86015
86064
  const targetId = parsed.values.at;
86016
86065
  if (!targetId)
86017
- return fail("USAGE", "Missing --at imgN", HELP21);
86066
+ return fail("USAGE", "Missing --at imgN", HELP20);
86018
86067
  const document4 = await openOrFail(path2);
86019
86068
  if (typeof document4 === "number")
86020
86069
  return document4;
@@ -86084,7 +86133,7 @@ function pruneIfUnreferenced(document4, relationshipId) {
86084
86133
  }
86085
86134
  return true;
86086
86135
  }
86087
- var HELP21 = `docx images delete \u2014 remove an embedded image
86136
+ var HELP20 = `docx images delete \u2014 remove an embedded image
86088
86137
 
86089
86138
  Usage:
86090
86139
  docx images delete FILE --at imgN [options]
@@ -86129,27 +86178,27 @@ var init_delete5 = __esm(() => {
86129
86178
  // src/cli/images/extract.ts
86130
86179
  var exports_extract = {};
86131
86180
  __export(exports_extract, {
86132
- run: () => run26
86181
+ run: () => run25
86133
86182
  });
86134
86183
  import { join as join6 } from "path";
86135
- async function run26(args) {
86184
+ async function run25(args) {
86136
86185
  const parsed = await tryParseArgs(args, {
86137
86186
  to: { type: "string" },
86138
86187
  at: { type: "string" },
86139
86188
  help: { type: "boolean", short: "h" }
86140
- }, HELP22);
86189
+ }, HELP21);
86141
86190
  if (typeof parsed === "number")
86142
86191
  return parsed;
86143
86192
  if (parsed.values.help) {
86144
- await writeStdout(HELP22);
86193
+ await writeStdout(HELP21);
86145
86194
  return EXIT2.OK;
86146
86195
  }
86147
86196
  const path2 = parsed.positionals[0];
86148
86197
  if (!path2)
86149
- return fail("USAGE", "Missing FILE argument", HELP22);
86198
+ return fail("USAGE", "Missing FILE argument", HELP21);
86150
86199
  const outputDir = parsed.values.to;
86151
86200
  if (!outputDir)
86152
- return fail("USAGE", "Missing --to DIR", HELP22);
86201
+ return fail("USAGE", "Missing --to DIR", HELP21);
86153
86202
  const targetId = parsed.values.at;
86154
86203
  const document4 = await openOrFail(path2);
86155
86204
  if (typeof document4 === "number")
@@ -86190,7 +86239,7 @@ function extensionFor(contentType, partName) {
86190
86239
  const fromName = partName.split(".").pop()?.toLowerCase();
86191
86240
  return fromName ?? "bin";
86192
86241
  }
86193
- var HELP22 = `docx images extract \u2014 dump image bytes to a directory
86242
+ var HELP21 = `docx images extract \u2014 dump image bytes to a directory
86194
86243
 
86195
86244
  Usage:
86196
86245
  docx images extract FILE --to DIR [options]
@@ -86223,21 +86272,21 @@ var init_extract = __esm(() => {
86223
86272
  // src/cli/images/list.ts
86224
86273
  var exports_list4 = {};
86225
86274
  __export(exports_list4, {
86226
- run: () => run27
86275
+ run: () => run26
86227
86276
  });
86228
- async function run27(args) {
86277
+ async function run26(args) {
86229
86278
  const parsed = await tryParseArgs(args, {
86230
86279
  help: { type: "boolean", short: "h" }
86231
- }, HELP23);
86280
+ }, HELP22);
86232
86281
  if (typeof parsed === "number")
86233
86282
  return parsed;
86234
86283
  if (parsed.values.help) {
86235
- await writeStdout(HELP23);
86284
+ await writeStdout(HELP22);
86236
86285
  return EXIT2.OK;
86237
86286
  }
86238
86287
  const path2 = parsed.positionals[0];
86239
86288
  if (!path2)
86240
- return fail("USAGE", "Missing FILE argument", HELP23);
86289
+ return fail("USAGE", "Missing FILE argument", HELP22);
86241
86290
  const document4 = await openOrFail(path2);
86242
86291
  if (typeof document4 === "number")
86243
86292
  return document4;
@@ -86245,7 +86294,7 @@ async function run27(args) {
86245
86294
  await respond(flattenImageRuns(document4.body.blocks));
86246
86295
  return EXIT2.OK;
86247
86296
  }
86248
- var HELP23 = `docx images list \u2014 print image manifest as JSON
86297
+ var HELP22 = `docx images list \u2014 print image manifest as JSON
86249
86298
 
86250
86299
  Usage:
86251
86300
  docx images list FILE [options]
@@ -86271,31 +86320,31 @@ var init_list6 = __esm(() => {
86271
86320
  // src/cli/images/replace.ts
86272
86321
  var exports_replace2 = {};
86273
86322
  __export(exports_replace2, {
86274
- run: () => run28
86323
+ run: () => run27
86275
86324
  });
86276
- async function run28(args) {
86325
+ async function run27(args) {
86277
86326
  const parsed = await tryParseArgs(args, {
86278
86327
  at: { type: "string" },
86279
86328
  with: { type: "string" },
86280
86329
  author: { type: "string" },
86281
86330
  ...SAVE_FLAGS
86282
- }, HELP24);
86331
+ }, HELP23);
86283
86332
  if (typeof parsed === "number")
86284
86333
  return parsed;
86285
86334
  if (parsed.values.help) {
86286
- await writeStdout(HELP24);
86335
+ await writeStdout(HELP23);
86287
86336
  return EXIT2.OK;
86288
86337
  }
86289
86338
  setVerboseAck(Boolean(parsed.values.verbose));
86290
86339
  const path2 = parsed.positionals[0];
86291
86340
  if (!path2)
86292
- return fail("USAGE", "Missing FILE argument", HELP24);
86341
+ return fail("USAGE", "Missing FILE argument", HELP23);
86293
86342
  const targetId = parsed.values.at;
86294
86343
  if (!targetId)
86295
- return fail("USAGE", "Missing --at imgN", HELP24);
86344
+ return fail("USAGE", "Missing --at imgN", HELP23);
86296
86345
  const sourcePath = parsed.values.with;
86297
86346
  if (!sourcePath)
86298
- return fail("USAGE", "Missing --with PATH", HELP24);
86347
+ return fail("USAGE", "Missing --with PATH", HELP23);
86299
86348
  const sourceFile = Bun.file(sourcePath);
86300
86349
  if (!await sourceFile.exists()) {
86301
86350
  return fail("FILE_NOT_FOUND", `Replacement file not found: ${sourcePath}`);
@@ -86386,7 +86435,7 @@ function renameExtension(partName, newExtension) {
86386
86435
  function relativeTargetFor(partName) {
86387
86436
  return partName.startsWith("word/") ? partName.slice("word/".length) : partName;
86388
86437
  }
86389
- var HELP24 = `docx images replace \u2014 swap an image's bytes
86438
+ var HELP23 = `docx images replace \u2014 swap an image's bytes
86390
86439
 
86391
86440
  Usage:
86392
86441
  docx images replace FILE --at imgN --with PATH [options]
@@ -86431,12 +86480,12 @@ var init_replace3 = __esm(() => {
86431
86480
  // src/cli/images/index.ts
86432
86481
  var exports_images = {};
86433
86482
  __export(exports_images, {
86434
- run: () => run29
86483
+ run: () => run28
86435
86484
  });
86436
- async function run29(args) {
86485
+ async function run28(args) {
86437
86486
  const verb = args[0];
86438
86487
  if (!verb || verb === "--help" || verb === "-h" || verb === "help") {
86439
- await writeStdout(HELP25);
86488
+ await writeStdout(HELP24);
86440
86489
  return verb ? 0 : 2;
86441
86490
  }
86442
86491
  const loader = SUBCOMMANDS5[verb];
@@ -86446,7 +86495,7 @@ async function run29(args) {
86446
86495
  const module_ = await loader();
86447
86496
  return module_.run(args.slice(1));
86448
86497
  }
86449
- var SUBCOMMANDS5, HELP25 = `docx images \u2014 manage embedded images
86498
+ var SUBCOMMANDS5, HELP24 = `docx images \u2014 manage embedded images
86450
86499
 
86451
86500
  Usage:
86452
86501
  docx images <verb> FILE [options]
@@ -86501,6 +86550,13 @@ export type Paragraph = {
86501
86550
  * them at top level, breaking the quote at that point. See
86502
86551
  * [src/core/markdown/CLAUDE.md](../markdown/CLAUDE.md). */
86503
86552
  quoteDepth?: number;
86553
+ /** Explicit tab stops from \`<w:pPr><w:tabs>\`, each \`{ align, pos }\` (pos in
86554
+ * twips). Surfaced so \`read\` can flag a fragile right-alignment: a LEFT (or
86555
+ * center) tab stop near the right margin pushes content rightward but \u2014 unlike
86556
+ * a RIGHT tab \u2014 wraps anything wider than the gap to the margin (the r\xE9sum\xE9
86557
+ * "San / Francisco, CA" break). Present only when the paragraph declares
86558
+ * explicit tab stops. */
86559
+ tabStops?: { align: string; pos: number }[];
86504
86560
  runs: Run[];
86505
86561
  };
86506
86562
 
@@ -86806,18 +86862,18 @@ var init_types3 = () => {};
86806
86862
  // src/cli/info/schema.ts
86807
86863
  var exports_schema = {};
86808
86864
  __export(exports_schema, {
86809
- run: () => run30
86865
+ run: () => run29
86810
86866
  });
86811
- async function run30(args) {
86867
+ async function run29(args) {
86812
86868
  const parsed = await tryParseArgs(args, {
86813
86869
  json: { type: "boolean" },
86814
86870
  ts: { type: "boolean" },
86815
86871
  help: { type: "boolean", short: "h" }
86816
- }, HELP26);
86872
+ }, HELP25);
86817
86873
  if (typeof parsed === "number")
86818
86874
  return parsed;
86819
86875
  if (parsed.values.help) {
86820
- await writeStdout(HELP26);
86876
+ await writeStdout(HELP25);
86821
86877
  return EXIT2.OK;
86822
86878
  }
86823
86879
  if (parsed.values.ts) {
@@ -86827,7 +86883,7 @@ async function run30(args) {
86827
86883
  await respond(JSON_SCHEMA);
86828
86884
  return EXIT2.OK;
86829
86885
  }
86830
- var HELP26 = `docx info schema \u2014 print the AST type definitions
86886
+ var HELP25 = `docx info schema \u2014 print the AST type definitions
86831
86887
 
86832
86888
  Usage:
86833
86889
  docx info schema [options]
@@ -86901,6 +86957,17 @@ var init_schema = __esm(() => {
86901
86957
  }
86902
86958
  },
86903
86959
  taskState: { enum: ["checked", "unchecked"] },
86960
+ tabStops: {
86961
+ type: "array",
86962
+ items: {
86963
+ type: "object",
86964
+ required: ["align", "pos"],
86965
+ properties: {
86966
+ align: { type: "string" },
86967
+ pos: { type: "number" }
86968
+ }
86969
+ }
86970
+ },
86904
86971
  runs: { type: "array", items: { $ref: "#/$defs/Run" } }
86905
86972
  }
86906
86973
  },
@@ -87155,7 +87222,7 @@ var init_schema = __esm(() => {
87155
87222
  // src/cli/info/locators.ts
87156
87223
  var exports_locators = {};
87157
87224
  __export(exports_locators, {
87158
- run: () => run31
87225
+ run: () => run30
87159
87226
  });
87160
87227
  function renderReference() {
87161
87228
  const sections = GROUPS.map((group) => {
@@ -87286,15 +87353,15 @@ function renderJson() {
87286
87353
  ]
87287
87354
  };
87288
87355
  }
87289
- async function run31(args) {
87356
+ async function run30(args) {
87290
87357
  const parsed = await tryParseArgs(args, {
87291
87358
  json: { type: "boolean" },
87292
87359
  help: { type: "boolean", short: "h" }
87293
- }, HELP27);
87360
+ }, HELP26);
87294
87361
  if (typeof parsed === "number")
87295
87362
  return parsed;
87296
87363
  if (parsed.values.help) {
87297
- await writeStdout(HELP27);
87364
+ await writeStdout(HELP26);
87298
87365
  return EXIT2.OK;
87299
87366
  }
87300
87367
  if (parsed.values.json) {
@@ -87305,7 +87372,7 @@ async function run31(args) {
87305
87372
  await writeStdout(renderReference());
87306
87373
  return EXIT2.OK;
87307
87374
  }
87308
- var HELP27 = `docx info locators \u2014 print the locator grammar reference
87375
+ var HELP26 = `docx info locators \u2014 print the locator grammar reference
87309
87376
 
87310
87377
  Usage:
87311
87378
  docx info locators [options]
@@ -87357,12 +87424,12 @@ var init_locators2 = __esm(() => {
87357
87424
  // src/cli/info/index.ts
87358
87425
  var exports_info = {};
87359
87426
  __export(exports_info, {
87360
- run: () => run32
87427
+ run: () => run31
87361
87428
  });
87362
- async function run32(args) {
87429
+ async function run31(args) {
87363
87430
  const topic = args[0];
87364
87431
  if (!topic || topic === "--help" || topic === "-h" || topic === "help") {
87365
- await writeStdout(HELP28);
87432
+ await writeStdout(HELP27);
87366
87433
  return topic ? 0 : 2;
87367
87434
  }
87368
87435
  const loader = SUBCOMMANDS6[topic];
@@ -87372,7 +87439,7 @@ async function run32(args) {
87372
87439
  const module_ = await loader();
87373
87440
  return module_.run(args.slice(1));
87374
87441
  }
87375
- var SUBCOMMANDS6, HELP28 = `docx info \u2014 print reference material about the CLI
87442
+ var SUBCOMMANDS6, HELP27 = `docx info \u2014 print reference material about the CLI
87376
87443
 
87377
87444
  Usage:
87378
87445
  docx info <topic> [options]
@@ -87452,23 +87519,23 @@ var init_build = __esm(() => {
87452
87519
  // src/cli/outline/index.ts
87453
87520
  var exports_outline = {};
87454
87521
  __export(exports_outline, {
87455
- run: () => run33
87522
+ run: () => run32
87456
87523
  });
87457
- async function run33(args) {
87524
+ async function run32(args) {
87458
87525
  const parsed = await tryParseArgs(args, {
87459
87526
  "style-prefix": { type: "string" },
87460
87527
  json: { type: "boolean" },
87461
87528
  help: { type: "boolean", short: "h" }
87462
- }, HELP29);
87529
+ }, HELP28);
87463
87530
  if (typeof parsed === "number")
87464
87531
  return parsed;
87465
87532
  if (parsed.values.help) {
87466
- await writeStdout(HELP29);
87533
+ await writeStdout(HELP28);
87467
87534
  return EXIT2.OK;
87468
87535
  }
87469
87536
  const path2 = parsed.positionals[0];
87470
87537
  if (!path2)
87471
- return fail("USAGE", "Missing FILE argument", HELP29);
87538
+ return fail("USAGE", "Missing FILE argument", HELP28);
87472
87539
  const stylePrefix = parsed.values["style-prefix"] ?? "Heading";
87473
87540
  if (stylePrefix.length === 0) {
87474
87541
  return fail("USAGE", "--style-prefix cannot be empty");
@@ -87496,7 +87563,7 @@ function renderOutlineText(entries, depth = 0) {
87496
87563
  }
87497
87564
  return lines;
87498
87565
  }
87499
- var HELP29 = `docx outline \u2014 list headings as a hierarchical tree
87566
+ var HELP28 = `docx outline \u2014 list headings as a hierarchical tree
87500
87567
 
87501
87568
  Usage:
87502
87569
  docx outline FILE [options]
@@ -87564,7 +87631,9 @@ function renderMarkdown(doc, options = {}) {
87564
87631
  referencedEndnoteIds: new Set,
87565
87632
  referencedTrackedChanges: new Map,
87566
87633
  orderedCounters: new Map,
87567
- contentWidthEmu: contentWidthEmu(documentGeometry(blocks))
87634
+ contentWidthEmu: contentWidthEmu(documentGeometry(blocks)),
87635
+ governingColumns: computeGoverningColumns(blocks),
87636
+ wrappingTabLines: []
87568
87637
  };
87569
87638
  const parts = [];
87570
87639
  let cursor = 0;
@@ -87591,7 +87660,7 @@ function renderMarkdown(doc, options = {}) {
87591
87660
  cursor = lookahead2;
87592
87661
  continue;
87593
87662
  }
87594
- const rendered = renderBlock(block, ctx);
87663
+ const rendered = renderBlock(block, ctx, blocks, cursor);
87595
87664
  if (rendered !== null)
87596
87665
  parts.push(rendered);
87597
87666
  cursor++;
@@ -87618,7 +87687,9 @@ function renderMarkdown(doc, options = {}) {
87618
87687
  return "";
87619
87688
  const baseNote = formatBaseNote(dominant);
87620
87689
  const pageNote = formatPageNote(documentGeometry(blocks));
87621
- const headLines = [baseNote, pageNote].filter((line) => line.length > 0);
87690
+ const trackNote = options.trackChangesOn ? formatNote("track-changes", [], ["on"]) : "";
87691
+ const wrapSummary = formatWrappingTabSummary(ctx.wrappingTabLines);
87692
+ const headLines = [baseNote, pageNote, trackNote, wrapSummary].filter((line) => line.length > 0);
87622
87693
  const head = headLines.length > 0 ? `${headLines.join(`
87623
87694
  `)}
87624
87695
 
@@ -87633,17 +87704,17 @@ function detectFormatBaseline(blocks) {
87633
87704
  const sizeChars = new Map;
87634
87705
  let total = 0;
87635
87706
  for (const paragraph2 of flattenParagraphs(blocks)) {
87636
- for (const run34 of paragraph2.runs) {
87637
- if (run34.type !== "text")
87707
+ for (const run33 of paragraph2.runs) {
87708
+ if (run33.type !== "text")
87638
87709
  continue;
87639
- const length = run34.text.length;
87710
+ const length = run33.text.length;
87640
87711
  if (length === 0)
87641
87712
  continue;
87642
87713
  total += length;
87643
- if (run34.font)
87644
- fontChars.set(run34.font, (fontChars.get(run34.font) ?? 0) + length);
87645
- if (run34.sizeHalfPoints !== undefined) {
87646
- sizeChars.set(run34.sizeHalfPoints, (sizeChars.get(run34.sizeHalfPoints) ?? 0) + length);
87714
+ if (run33.font)
87715
+ fontChars.set(run33.font, (fontChars.get(run33.font) ?? 0) + length);
87716
+ if (run33.sizeHalfPoints !== undefined) {
87717
+ sizeChars.set(run33.sizeHalfPoints, (sizeChars.get(run33.sizeHalfPoints) ?? 0) + length);
87647
87718
  }
87648
87719
  }
87649
87720
  }
@@ -87677,8 +87748,8 @@ function emptyCommentIndex() {
87677
87748
  orderedIds: []
87678
87749
  };
87679
87750
  }
87680
- function isRunVisible(run34, view) {
87681
- const kind = run34.trackedChange?.kind;
87751
+ function isRunVisible(run33, view) {
87752
+ const kind = run33.trackedChange?.kind;
87682
87753
  if (!kind)
87683
87754
  return true;
87684
87755
  if (view === "accepted" && (kind === "del" || kind === "moveFrom"))
@@ -87693,13 +87764,13 @@ function buildCommentIndex(blocks, options) {
87693
87764
  const spanText = new Map;
87694
87765
  const orderedIds = [];
87695
87766
  for (const paragraph2 of flattenParagraphs(blocks)) {
87696
- paragraph2.runs.forEach((run34, index2) => {
87697
- const comments = runComments(run34);
87767
+ paragraph2.runs.forEach((run33, index2) => {
87768
+ const comments = runComments(run33);
87698
87769
  if (!comments)
87699
87770
  return;
87700
- if (run34.type === "text" && !isRunVisible(run34, view))
87771
+ if (run33.type === "text" && !isRunVisible(run33, view))
87701
87772
  return;
87702
- const spanContribution = run34.type === "text" ? run34.text : run34.type === "equation" ? run34.text : "";
87773
+ const spanContribution = run33.type === "text" ? run33.text : run33.type === "equation" ? run33.text : "";
87703
87774
  for (const commentId of comments) {
87704
87775
  if (!spanText.has(commentId))
87705
87776
  orderedIds.push(commentId);
@@ -87719,32 +87790,121 @@ function buildCommentIndex(blocks, options) {
87719
87790
  }
87720
87791
  return { endingsByRun, spanText, orderedIds };
87721
87792
  }
87722
- function runComments(run34) {
87723
- if (run34.type === "text")
87724
- return run34.comments;
87725
- if (run34.type === "equation")
87726
- return run34.comments;
87793
+ function runComments(run33) {
87794
+ if (run33.type === "text")
87795
+ return run33.comments;
87796
+ if (run33.type === "equation")
87797
+ return run33.comments;
87727
87798
  return;
87728
87799
  }
87729
87800
  function slotKey(paragraphId, runIndex) {
87730
87801
  return `${paragraphId}#${runIndex}`;
87731
87802
  }
87732
- function renderBlock(block, ctx) {
87803
+ function renderBlock(block, ctx, blocks, index2) {
87733
87804
  if (block.type === "paragraph")
87734
87805
  return renderParagraph(block, ctx);
87735
87806
  if (block.type === "table")
87736
87807
  return renderTable(block, ctx);
87737
- if (block.type === "sectionBreak")
87738
- return renderSectionBreak(block);
87808
+ if (block.type === "sectionBreak") {
87809
+ return renderSectionBreak(block, governedRange(blocks, index2));
87810
+ }
87739
87811
  return null;
87740
87812
  }
87741
- function renderSectionBreak(block) {
87813
+ function computeGoverningColumns(blocks) {
87814
+ const map3 = new Map;
87815
+ let cols = 1;
87816
+ for (let index2 = blocks.length - 1;index2 >= 0; index2--) {
87817
+ const block = blocks[index2];
87818
+ if (!block)
87819
+ continue;
87820
+ if (block.type === "sectionBreak") {
87821
+ cols = block.columns ?? 1;
87822
+ continue;
87823
+ }
87824
+ map3.set(block.id, cols);
87825
+ }
87826
+ return map3;
87827
+ }
87828
+ function formatWrappingTabSummary(ids) {
87829
+ if (ids.length === 0)
87830
+ return "";
87831
+ const count = `${ids.length} line${ids.length > 1 ? "s" : ""}`;
87832
+ const range = tabCureRange(ids);
87833
+ const fix = range ? `edit FILE --at ${range} --tabs right` : ids.map((id) => `edit FILE --at ${id} --tabs right`).join(" ; ");
87834
+ return formatNote("layout", [
87835
+ ["wrap", count],
87836
+ [
87837
+ "warn",
87838
+ "tab-aligned content (e.g. dates/locations) overflows the right margin and WRAPS in render until cured"
87839
+ ],
87840
+ ["fix-all", fix]
87841
+ ], ids);
87842
+ }
87843
+ function tabCureRange(ids) {
87844
+ const nums = ids.map((id) => /^p(\d+)$/.exec(id)?.[1]).filter((value) => value !== undefined).map(Number);
87845
+ if (nums.length === 0)
87846
+ return null;
87847
+ const min = Math.min(...nums);
87848
+ const max = Math.max(...nums);
87849
+ return min === max ? `p${min}` : `p${min}-p${max}`;
87850
+ }
87851
+ function layoutHazardNote(paragraph2, ctx) {
87852
+ if (!paragraph2.runs.some((run33) => run33.type === "tab"))
87853
+ return "";
87854
+ const cols = ctx.governingColumns.get(paragraph2.id) ?? 1;
87855
+ if (cols > 1) {
87856
+ return ` ${formatNote("layout", [
87857
+ ["cols", cols],
87858
+ [
87859
+ "warn",
87860
+ `tab alignment can wrap mid-line in this ${cols}-column section; render to verify`
87861
+ ]
87862
+ ], [paragraph2.id])}`;
87863
+ }
87864
+ const tabs = paragraph2.tabStops ?? [];
87865
+ if (tabs.some((tab) => tab.align === "right"))
87866
+ return "";
87867
+ const textWidthTwips = Math.round(ctx.contentWidthEmu / EMU_PER_TWIP2);
87868
+ const fragile = tabs.find((tab) => (tab.align === "left" || tab.align === "center" || tab.align === "") && tab.pos > textWidthTwips * 0.7);
87869
+ if (!fragile)
87870
+ return "";
87871
+ ctx.wrappingTabLines.push(paragraph2.id);
87872
+ const gapIn = twipsToInches(Math.max(0, textWidthTwips - fragile.pos));
87873
+ return ` ${formatNote("layout", [
87874
+ ["tab", `${fragile.align || "left"}@${twipsToInches(fragile.pos)}in`],
87875
+ [
87876
+ "warn",
87877
+ `right content on a LEFT tab ~${gapIn}in from the margin \u2014 wraps when longer (cure: --tabs right; see the docx:layout fix-all at top)`
87878
+ ]
87879
+ ], [paragraph2.id])}`;
87880
+ }
87881
+ function governedRange(blocks, index2) {
87882
+ let first;
87883
+ let last;
87884
+ for (let cursor = index2 - 1;cursor >= 0; cursor--) {
87885
+ const block = blocks[cursor];
87886
+ if (!block)
87887
+ continue;
87888
+ if (block.type === "sectionBreak")
87889
+ break;
87890
+ first = block.id;
87891
+ if (last === undefined)
87892
+ last = block.id;
87893
+ }
87894
+ if (!first || !last)
87895
+ return;
87896
+ return first === last ? first : `${first}..${last}`;
87897
+ }
87898
+ function renderSectionBreak(block, governs) {
87742
87899
  const pairs = [];
87743
87900
  if (block.columns !== undefined && block.columns > 1) {
87744
87901
  pairs.push(["cols", block.columns]);
87745
87902
  }
87746
87903
  if (block.sectionType !== undefined)
87747
87904
  pairs.push(["type", block.sectionType]);
87905
+ if (governs !== undefined && pairs.length > 0) {
87906
+ pairs.push(["applies-to", `${governs} (above)`]);
87907
+ }
87748
87908
  return formatNote("section", pairs, [block.id]);
87749
87909
  }
87750
87910
  function contentWidthEmu(geometry) {
@@ -87755,25 +87915,25 @@ function contentWidthEmu(geometry) {
87755
87915
  const fallback = (DEFAULT_PAGE.width - 2 * DEFAULT_PAGE.margin) * EMU_PER_TWIP2;
87756
87916
  return contentTwips > 0 ? contentTwips * EMU_PER_TWIP2 : fallback;
87757
87917
  }
87758
- function formatImageNote(run34, contentEmu) {
87918
+ function formatImageNote(run33, contentEmu) {
87759
87919
  const pairs = [];
87760
- if (run34.widthEmu && run34.heightEmu) {
87920
+ if (run33.widthEmu && run33.heightEmu) {
87761
87921
  pairs.push([
87762
87922
  "size",
87763
- `${emuToInches(run34.widthEmu)}x${emuToInches(run34.heightEmu)}in`
87923
+ `${emuToInches(run33.widthEmu)}x${emuToInches(run33.heightEmu)}in`
87764
87924
  ]);
87765
87925
  }
87766
- if (run34.floating)
87926
+ if (run33.floating)
87767
87927
  pairs.push(["float", "yes"]);
87768
- if (run34.wrap)
87769
- pairs.push(["wrap", run34.wrap]);
87770
- if (run34.align)
87771
- pairs.push(["align", run34.align]);
87772
- if (run34.widthEmu && run34.widthEmu > contentEmu)
87928
+ if (run33.wrap)
87929
+ pairs.push(["wrap", run33.wrap]);
87930
+ if (run33.align)
87931
+ pairs.push(["align", run33.align]);
87932
+ if (run33.widthEmu && run33.widthEmu > contentEmu)
87773
87933
  pairs.push(["overflow", "yes"]);
87774
87934
  if (pairs.length === 0)
87775
87935
  return "";
87776
- return ` ${formatNote("image", pairs, [run34.id])}`;
87936
+ return ` ${formatNote("image", pairs, [run33.id])}`;
87777
87937
  }
87778
87938
  function documentGeometry(blocks) {
87779
87939
  for (let index2 = blocks.length - 1;index2 >= 0; index2--) {
@@ -87825,14 +87985,14 @@ function isCodeBlockParagraph(block) {
87825
87985
  function renderCodeBlockGroup(paragraphs, ctx) {
87826
87986
  if (ctx.options.view !== "baseline" && ctx.options.view !== "accepted") {
87827
87987
  for (const paragraph2 of paragraphs) {
87828
- for (const run34 of paragraph2.runs) {
87829
- if (run34.type === "text" && run34.trackedChange) {
87830
- ctx.referencedTrackedChanges.set(run34.trackedChange.id, run34.trackedChange);
87988
+ for (const run33 of paragraph2.runs) {
87989
+ if (run33.type === "text" && run33.trackedChange) {
87990
+ ctx.referencedTrackedChanges.set(run33.trackedChange.id, run33.trackedChange);
87831
87991
  }
87832
87992
  }
87833
87993
  }
87834
87994
  }
87835
- const lines = paragraphs.map((paragraph2) => paragraph2.runs.filter((run34) => run34.type === "text" && typeof run34.text === "string").map((run34) => run34.text).join(""));
87995
+ const lines = paragraphs.map((paragraph2) => paragraph2.runs.filter((run33) => run33.type === "text" && typeof run33.text === "string").map((run33) => run33.text).join(""));
87836
87996
  const firstId = paragraphs[0]?.id ?? "";
87837
87997
  const lastId = paragraphs[paragraphs.length - 1]?.id ?? firstId;
87838
87998
  const language = codeBlockLanguageFromStyleId(paragraphs[0]?.style) ?? "";
@@ -87852,7 +88012,7 @@ function renderParagraph(paragraph2, ctx) {
87852
88012
  const body = rendered.replace(/[ \t]+$/, "");
87853
88013
  const separator = isDisplayEquationOnly(body) ? `
87854
88014
  ` : " ";
87855
- return `${prefix}${body}${separator}<!-- ${paragraph2.id} -->${formatParagraphNote(paragraph2)}`;
88015
+ return `${prefix}${body}${separator}<!-- ${paragraph2.id} -->${formatParagraphNote(paragraph2)}${layoutHazardNote(paragraph2, ctx)}`;
87856
88016
  }
87857
88017
  function formatParagraphNote(paragraph2) {
87858
88018
  const pairs = [];
@@ -87925,10 +88085,10 @@ function headingLevelFor(style) {
87925
88085
  function renderRuns(paragraphId, runs, ctx, mask, baseOffset) {
87926
88086
  const view = ctx.options.view ?? "accepted";
87927
88087
  const visibleEntries = [];
87928
- runs.forEach((run34, index2) => {
87929
- if (run34.type === "text" && !isRunVisible(run34, view))
88088
+ runs.forEach((run33, index2) => {
88089
+ if (run33.type === "text" && !isRunVisible(run33, view))
87930
88090
  return;
87931
- visibleEntries.push({ run: run34, originalIndex: index2 });
88091
+ visibleEntries.push({ run: run33, originalIndex: index2 });
87932
88092
  });
87933
88093
  let out = "";
87934
88094
  let cursor = 0;
@@ -87939,14 +88099,14 @@ function renderRuns(paragraphId, runs, ctx, mask, baseOffset) {
87939
88099
  cursor++;
87940
88100
  continue;
87941
88101
  }
87942
- const { run: run34 } = entry;
87943
- if (run34.type === "text") {
88102
+ const { run: run33 } = entry;
88103
+ if (run33.type === "text") {
87944
88104
  let lookahead2 = cursor + 1;
87945
88105
  while (lookahead2 < visibleEntries.length) {
87946
88106
  const next = visibleEntries[lookahead2];
87947
88107
  if (!next || next.run.type !== "text")
87948
88108
  break;
87949
- if (!sameDecoration(run34, next.run))
88109
+ if (!sameDecoration(run33, next.run))
87950
88110
  break;
87951
88111
  lookahead2++;
87952
88112
  }
@@ -87967,29 +88127,29 @@ function renderRuns(paragraphId, runs, ctx, mask, baseOffset) {
87967
88127
  cursor = lookahead2;
87968
88128
  continue;
87969
88129
  }
87970
- if (run34.type === "image") {
87971
- const alt = sanitizeAltText(run34.alt ?? run34.id);
87972
- const extension2 = extensionForImageMime(run34.contentType) ?? "bin";
87973
- out += `![${alt}](${run34.hash}.${extension2})`;
87974
- out += formatImageNote(run34, ctx.contentWidthEmu);
87975
- } else if (run34.type === "break") {
87976
- if (run34.kind === "line")
88130
+ if (run33.type === "image") {
88131
+ const alt = sanitizeAltText(run33.alt ?? run33.id);
88132
+ const extension2 = extensionForImageMime(run33.contentType) ?? "bin";
88133
+ out += `![${alt}](${run33.hash}.${extension2})`;
88134
+ out += formatImageNote(run33, ctx.contentWidthEmu);
88135
+ } else if (run33.type === "break") {
88136
+ if (run33.kind === "line")
87977
88137
  out += `
87978
88138
  `;
87979
- } else if (run34.type === "tab") {
88139
+ } else if (run33.type === "tab") {
87980
88140
  out += "\t";
87981
- } else if (run34.type === "equation") {
87982
- const body = run34.latex.length > 0 ? run34.latex : run34.text;
87983
- out += run34.display ? `$$${body}$$` : `$${body}$`;
88141
+ } else if (run33.type === "equation") {
88142
+ const body = run33.latex.length > 0 ? run33.latex : run33.text;
88143
+ out += run33.display ? `$$${body}$$` : `$${body}$`;
87984
88144
  out += commentEndingsFor(paragraphId, [{ originalIndex: cursor }], ctx.commentIndex);
87985
- } else if (run34.type === "noteRef") {
87986
- if (run34.kind === "footnote")
87987
- ctx.referencedFootnoteIds.add(run34.id);
88145
+ } else if (run33.type === "noteRef") {
88146
+ if (run33.kind === "footnote")
88147
+ ctx.referencedFootnoteIds.add(run33.id);
87988
88148
  else
87989
- ctx.referencedEndnoteIds.add(run34.id);
87990
- out += `[^${run34.id}]`;
87991
- } else if (run34.type === "chart") {
87992
- out += `\`[${run34.kind}]\``;
88149
+ ctx.referencedEndnoteIds.add(run33.id);
88150
+ out += `[^${run33.id}]`;
88151
+ } else if (run33.type === "chart") {
88152
+ out += `\`[${run33.kind}]\``;
87993
88153
  }
87994
88154
  cursor++;
87995
88155
  }
@@ -88023,7 +88183,7 @@ function sameCommentSet(left, right) {
88023
88183
  return true;
88024
88184
  }
88025
88185
  function renderTextSegment(runs, view, baseline, mask, offset2) {
88026
- const text6 = runs.map((run34) => run34.text).join("");
88186
+ const text6 = runs.map((run33) => run33.text).join("");
88027
88187
  if (text6.length === 0)
88028
88188
  return "";
88029
88189
  const first = runs[0];
@@ -88067,33 +88227,33 @@ function criticMarkerFor(kind) {
88067
88227
  return "++";
88068
88228
  return "--";
88069
88229
  }
88070
- function needsHtmlWrap(run34, baseline) {
88071
- return Boolean(run34.color && !isDefaultColor(run34.color) || run34.colorTheme && !isDefaultThemeColor(run34) || run34.shade || run34.font && run34.font !== baseline.font || run34.sizeHalfPoints !== undefined && run34.sizeHalfPoints !== baseline.sizeHalfPoints || run34.smallCaps || run34.allCaps || run34.underline || run34.vertAlign === "superscript" || run34.vertAlign === "subscript" || run34.highlight);
88230
+ function needsHtmlWrap(run33, baseline) {
88231
+ return Boolean(run33.color && !isDefaultColor(run33.color) || run33.colorTheme && !isDefaultThemeColor(run33) || run33.shade || run33.font && run33.font !== baseline.font || run33.sizeHalfPoints !== undefined && run33.sizeHalfPoints !== baseline.sizeHalfPoints || run33.smallCaps || run33.allCaps || run33.underline || run33.vertAlign === "superscript" || run33.vertAlign === "subscript" || run33.highlight);
88072
88232
  }
88073
- function wrapRunFormatting(body, run34, baseline) {
88233
+ function wrapRunFormatting(body, run33, baseline) {
88074
88234
  const styles = [];
88075
88235
  const attrs = [];
88076
- if (run34.color && !isDefaultColor(run34.color))
88077
- styles.push(`color:#${run34.color}`);
88078
- if (run34.shade)
88079
- styles.push(`background-color:#${run34.shade}`);
88080
- if (run34.font && run34.font !== baseline.font) {
88081
- styles.push(`font-family:${cssFontFamily(run34.font)}`);
88236
+ if (run33.color && !isDefaultColor(run33.color))
88237
+ styles.push(`color:#${run33.color}`);
88238
+ if (run33.shade)
88239
+ styles.push(`background-color:#${run33.shade}`);
88240
+ if (run33.font && run33.font !== baseline.font) {
88241
+ styles.push(`font-family:${cssFontFamily(run33.font)}`);
88082
88242
  }
88083
- if (run34.sizeHalfPoints !== undefined && run34.sizeHalfPoints !== baseline.sizeHalfPoints) {
88084
- styles.push(`font-size:${run34.sizeHalfPoints / 2}pt`);
88243
+ if (run33.sizeHalfPoints !== undefined && run33.sizeHalfPoints !== baseline.sizeHalfPoints) {
88244
+ styles.push(`font-size:${run33.sizeHalfPoints / 2}pt`);
88085
88245
  }
88086
- if (run34.smallCaps)
88246
+ if (run33.smallCaps)
88087
88247
  styles.push("font-variant:small-caps");
88088
- if (run34.allCaps)
88248
+ if (run33.allCaps)
88089
88249
  styles.push("text-transform:uppercase");
88090
- if (run34.colorTheme && !isDefaultThemeColor(run34)) {
88091
- attrs.push(htmlAttr("data-color-theme", run34.colorTheme));
88092
- if (run34.colorThemeTint) {
88093
- attrs.push(htmlAttr("data-color-theme-tint", run34.colorThemeTint));
88250
+ if (run33.colorTheme && !isDefaultThemeColor(run33)) {
88251
+ attrs.push(htmlAttr("data-color-theme", run33.colorTheme));
88252
+ if (run33.colorThemeTint) {
88253
+ attrs.push(htmlAttr("data-color-theme-tint", run33.colorThemeTint));
88094
88254
  }
88095
- if (run34.colorThemeShade) {
88096
- attrs.push(htmlAttr("data-color-theme-shade", run34.colorThemeShade));
88255
+ if (run33.colorThemeShade) {
88256
+ attrs.push(htmlAttr("data-color-theme-shade", run33.colorThemeShade));
88097
88257
  }
88098
88258
  }
88099
88259
  let out = body;
@@ -88102,18 +88262,18 @@ function wrapRunFormatting(body, run34, baseline) {
88102
88262
  const attrPart = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
88103
88263
  out = `<span${stylePart}${attrPart}>${out}</span>`;
88104
88264
  }
88105
- if (run34.underline === "single") {
88265
+ if (run33.underline === "single") {
88106
88266
  out = `<u>${out}</u>`;
88107
- } else if (run34.underline) {
88108
- const color2 = run34.underlineColor ? ` ${htmlAttr("data-underline-color", run34.underlineColor)}` : "";
88109
- out = `<u ${htmlAttr("data-underline", run34.underline)}${color2}>${out}</u>`;
88267
+ } else if (run33.underline) {
88268
+ const color2 = run33.underlineColor ? ` ${htmlAttr("data-underline-color", run33.underlineColor)}` : "";
88269
+ out = `<u ${htmlAttr("data-underline", run33.underline)}${color2}>${out}</u>`;
88110
88270
  }
88111
- if (run34.vertAlign === "superscript")
88271
+ if (run33.vertAlign === "superscript")
88112
88272
  out = `<sup>${out}</sup>`;
88113
- else if (run34.vertAlign === "subscript")
88273
+ else if (run33.vertAlign === "subscript")
88114
88274
  out = `<sub>${out}</sub>`;
88115
- if (run34.highlight) {
88116
- const named = run34.highlight === "yellow" ? "" : ` ${htmlAttr("data-highlight", run34.highlight)}`;
88275
+ if (run33.highlight) {
88276
+ const named = run33.highlight === "yellow" ? "" : ` ${htmlAttr("data-highlight", run33.highlight)}`;
88117
88277
  out = `<mark${named}>${out}</mark>`;
88118
88278
  }
88119
88279
  return out;
@@ -88122,8 +88282,8 @@ function isDefaultColor(color2) {
88122
88282
  const normalized = color2.toLowerCase();
88123
88283
  return normalized === "000000" || normalized === "auto";
88124
88284
  }
88125
- function isDefaultThemeColor(run34) {
88126
- return (run34.colorTheme === "text1" || run34.colorTheme === "dark1") && !run34.colorThemeTint && !run34.colorThemeShade;
88285
+ function isDefaultThemeColor(run33) {
88286
+ return (run33.colorTheme === "text1" || run33.colorTheme === "dark1") && !run33.colorThemeTint && !run33.colorThemeShade;
88127
88287
  }
88128
88288
  function cssFontFamily(font) {
88129
88289
  return /\s/.test(font) ? `'${font}'` : font;
@@ -88140,19 +88300,19 @@ function applyEscapeMask(text6, mask, offset2) {
88140
88300
  }
88141
88301
  function paragraphContent(runs, view) {
88142
88302
  let content3 = "";
88143
- for (const run34 of runs) {
88144
- if (run34.type !== "text")
88303
+ for (const run33 of runs) {
88304
+ if (run33.type !== "text")
88145
88305
  continue;
88146
- if (run34.runStyle === "Code")
88306
+ if (run33.runStyle === "Code")
88147
88307
  continue;
88148
- if (!isRunVisible(run34, view))
88308
+ if (!isRunVisible(run33, view))
88149
88309
  continue;
88150
- content3 += run34.text;
88310
+ content3 += run33.text;
88151
88311
  }
88152
88312
  return content3;
88153
88313
  }
88154
88314
  function hasEquationRun(runs) {
88155
- return runs.some((run34) => run34.type === "equation");
88315
+ return runs.some((run33) => run33.type === "equation");
88156
88316
  }
88157
88317
  function renderTable(table, ctx) {
88158
88318
  const view = ctx.options.view ?? "accepted";
@@ -88438,9 +88598,9 @@ var init_markdown3 = __esm(() => {
88438
88598
  // src/cli/read/index.ts
88439
88599
  var exports_read = {};
88440
88600
  __export(exports_read, {
88441
- run: () => run34
88601
+ run: () => run33
88442
88602
  });
88443
- async function run34(args) {
88603
+ async function run33(args) {
88444
88604
  const parsed = await tryParseArgs(args, {
88445
88605
  ast: { type: "boolean" },
88446
88606
  from: { type: "string" },
@@ -88450,16 +88610,16 @@ async function run34(args) {
88450
88610
  current: { type: "boolean" },
88451
88611
  comments: { type: "boolean" },
88452
88612
  help: { type: "boolean", short: "h" }
88453
- }, HELP30);
88613
+ }, HELP29);
88454
88614
  if (typeof parsed === "number")
88455
88615
  return parsed;
88456
88616
  if (parsed.values.help) {
88457
- await writeStdout(HELP30);
88617
+ await writeStdout(HELP29);
88458
88618
  return EXIT2.OK;
88459
88619
  }
88460
88620
  const path2 = parsed.positionals[0];
88461
88621
  if (!path2)
88462
- return fail("USAGE", "Missing FILE argument", HELP30);
88622
+ return fail("USAGE", "Missing FILE argument", HELP29);
88463
88623
  const ast = Boolean(parsed.values.ast);
88464
88624
  const from = parsed.values.from;
88465
88625
  const to = parsed.values.to;
@@ -88468,11 +88628,11 @@ async function run34(args) {
88468
88628
  const current = Boolean(parsed.values.current);
88469
88629
  const showComments = Boolean(parsed.values.comments);
88470
88630
  if (ast && (from || to || accepted || baseline || current || showComments)) {
88471
- return fail("USAGE", "--from, --to, --accepted, --baseline, --current, and --comments are Markdown-only and cannot be combined with --ast", HELP30);
88631
+ return fail("USAGE", "--from, --to, --accepted, --baseline, --current, and --comments are Markdown-only and cannot be combined with --ast", HELP29);
88472
88632
  }
88473
88633
  const view = resolveView({ accepted, baseline, current });
88474
88634
  if (!view) {
88475
- return fail("USAGE", "--accepted, --baseline, and --current are mutually exclusive", HELP30);
88635
+ return fail("USAGE", "--accepted, --baseline, and --current are mutually exclusive", HELP29);
88476
88636
  }
88477
88637
  const docView = await openOrFail(path2);
88478
88638
  if (typeof docView === "number")
@@ -88488,7 +88648,8 @@ async function run34(args) {
88488
88648
  to,
88489
88649
  view,
88490
88650
  showComments,
88491
- defaultSizeHalfPoints: docView.styles?.defaultSizeHalfPoints()
88651
+ defaultSizeHalfPoints: docView.styles?.defaultSizeHalfPoints(),
88652
+ trackChangesOn: docView.isTrackChangesEnabled()
88492
88653
  });
88493
88654
  await writeStdout(rendered);
88494
88655
  return EXIT2.OK;
@@ -88499,7 +88660,7 @@ async function run34(args) {
88499
88660
  throw err;
88500
88661
  }
88501
88662
  }
88502
- var HELP30 = `docx read \u2014 render document body as Markdown, or print AST as JSON
88663
+ var HELP29 = `docx read \u2014 render document body as Markdown, or print AST as JSON
88503
88664
 
88504
88665
  Usage:
88505
88666
  docx read FILE [options]
@@ -88589,20 +88750,20 @@ function parsePagesSpec(spec) {
88589
88750
  // src/cli/render/index.ts
88590
88751
  var exports_render = {};
88591
88752
  __export(exports_render, {
88592
- run: () => run35
88753
+ run: () => run34
88593
88754
  });
88594
88755
  import { basename as basename2, extname as extname2, resolve as resolve2 } from "path";
88595
- async function run35(args) {
88596
- const parsed = await tryParseArgs(args, OPTION_SPEC5, HELP31);
88756
+ async function run34(args) {
88757
+ const parsed = await tryParseArgs(args, OPTION_SPEC4, HELP30);
88597
88758
  if (typeof parsed === "number")
88598
88759
  return parsed;
88599
88760
  if (parsed.values.help) {
88600
- await writeStdout(HELP31);
88761
+ await writeStdout(HELP30);
88601
88762
  return EXIT2.OK;
88602
88763
  }
88603
88764
  const filePath = parsed.positionals[0];
88604
88765
  if (!filePath)
88605
- return fail("USAGE", "Missing FILE argument", HELP31);
88766
+ return fail("USAGE", "Missing FILE argument", HELP30);
88606
88767
  if (!await Bun.file(filePath).exists()) {
88607
88768
  return fail("FILE_NOT_FOUND", `File not found: ${filePath}`);
88608
88769
  }
@@ -88682,7 +88843,7 @@ function availabilityHint(installed) {
88682
88843
  }
88683
88844
  return `Detected engines: ${installed.join(", ")} \u2014 but none chose auto-select; pass --engine explicitly.`;
88684
88845
  }
88685
- var HELP31 = `docx render \u2014 render each page of a .docx as a PNG/JPG image
88846
+ var HELP30 = `docx render \u2014 render each page of a .docx as a PNG/JPG image
88686
88847
 
88687
88848
  Usage:
88688
88849
  docx render FILE [options]
@@ -88734,11 +88895,11 @@ Runtime dependencies:
88734
88895
  \u2014 no extra system tools (poppler / pdftoppm / ImageMagick) required.
88735
88896
 
88736
88897
  Run "docx render --help" or "docx --help" for the full command list.
88737
- `, OPTION_SPEC5;
88898
+ `, OPTION_SPEC4;
88738
88899
  var init_render2 = __esm(() => {
88739
88900
  init_core2();
88740
88901
  init_respond();
88741
- OPTION_SPEC5 = {
88902
+ OPTION_SPEC4 = {
88742
88903
  out: { type: "string" },
88743
88904
  engine: { type: "string" },
88744
88905
  dpi: { type: "string" },
@@ -88899,9 +89060,9 @@ var init_batch4 = __esm(() => {
88899
89060
  // src/cli/replace/index.ts
88900
89061
  var exports_replace3 = {};
88901
89062
  __export(exports_replace3, {
88902
- run: () => run36
89063
+ run: () => run35
88903
89064
  });
88904
- async function run36(args) {
89065
+ async function run35(args) {
88905
89066
  const parsed = await tryParseArgs(args, {
88906
89067
  batch: { type: "string" },
88907
89068
  regex: { type: "boolean" },
@@ -88914,30 +89075,30 @@ async function run36(args) {
88914
89075
  baseline: { type: "boolean" },
88915
89076
  exact: { type: "boolean" },
88916
89077
  ...SAVE_FLAGS
88917
- }, HELP32);
89078
+ }, HELP31);
88918
89079
  if (typeof parsed === "number")
88919
89080
  return parsed;
88920
89081
  if (parsed.values.help) {
88921
- await writeStdout(HELP32);
89082
+ await writeStdout(HELP31);
88922
89083
  return EXIT2.OK;
88923
89084
  }
88924
89085
  setVerboseAck(Boolean(parsed.values.verbose));
88925
89086
  const path2 = parsed.positionals[0];
88926
89087
  if (!path2)
88927
- return fail("USAGE", "Missing FILE argument", HELP32);
89088
+ return fail("USAGE", "Missing FILE argument", HELP31);
88928
89089
  const batchInput = parsed.values.batch;
88929
89090
  if (batchInput !== undefined) {
88930
89091
  if (parsed.positionals.length > 1) {
88931
- return fail("USAGE", "--batch reads pattern/replacement from the JSONL file; don't also pass them as positionals", HELP32);
89092
+ return fail("USAGE", "--batch reads pattern/replacement from the JSONL file; don't also pass them as positionals", HELP31);
88932
89093
  }
88933
89094
  return runReplaceBatch(path2, batchInput, parsed.values);
88934
89095
  }
88935
89096
  const pattern = parsed.positionals[1];
88936
89097
  const replacement = parsed.positionals[2];
88937
89098
  if (pattern == null)
88938
- return fail("USAGE", "Missing PATTERN argument", HELP32);
89099
+ return fail("USAGE", "Missing PATTERN argument", HELP31);
88939
89100
  if (replacement == null) {
88940
- return fail("USAGE", "Missing REPLACEMENT argument", HELP32);
89101
+ return fail("USAGE", "Missing REPLACEMENT argument", HELP31);
88941
89102
  }
88942
89103
  const ignoreCase = Boolean(parsed.values["ignore-case"]);
88943
89104
  const useRegex = Boolean(parsed.values.regex);
@@ -89041,6 +89202,8 @@ async function run36(args) {
89041
89202
  replaceSpanInParagraph(blockRef.node, { start: match.start, end: match.end }, concreteReplacement, tracked, findView);
89042
89203
  }
89043
89204
  await document4.save(outputPath);
89205
+ const remaining = allMatches.length - selected.length;
89206
+ const partialHint = remaining > 0 ? `\u21B3 ${remaining} more match${remaining === 1 ? "" : "es"} left unreplaced (${selected.length} of ${allMatches.length} done) \u2014 pass --all to replace every match, or --limit N for a specific count.` : undefined;
89044
89207
  await respondAck({
89045
89208
  ok: true,
89046
89209
  operation: "replace",
@@ -89054,10 +89217,10 @@ async function run36(args) {
89054
89217
  replaced: selected.length,
89055
89218
  matches: matchesPayload,
89056
89219
  ...normalizationFields
89057
- });
89220
+ }, partialHint);
89058
89221
  return EXIT2.OK;
89059
89222
  }
89060
- var HELP32 = `docx replace \u2014 substitute text spans (sed for docx)
89223
+ var HELP31 = `docx replace \u2014 substitute text spans (sed for docx)
89061
89224
 
89062
89225
  Usage:
89063
89226
  docx replace FILE PATTERN REPLACEMENT [options]
@@ -89135,6 +89298,258 @@ var init_replace4 = __esm(() => {
89135
89298
  init_batch4();
89136
89299
  });
89137
89300
 
89301
+ // src/cli/sections/index.tsx
89302
+ var exports_sections = {};
89303
+ __export(exports_sections, {
89304
+ run: () => run36
89305
+ });
89306
+ async function run36(args) {
89307
+ const parsed = await tryParseArgs(args, OPTION_SPEC5, HELP32);
89308
+ if (typeof parsed === "number")
89309
+ return parsed;
89310
+ if (parsed.values.help) {
89311
+ await writeStdout(HELP32);
89312
+ return EXIT2.OK;
89313
+ }
89314
+ setVerboseAck(Boolean(parsed.values.verbose));
89315
+ const filePath = parsed.positionals[0];
89316
+ if (!filePath)
89317
+ return fail("USAGE", "Missing FILE argument", HELP32);
89318
+ const at = parsed.values.at;
89319
+ if (!at) {
89320
+ return fail("USAGE", "Missing --at LOCATOR (a range pN-pM or a section sN)", HELP32);
89321
+ }
89322
+ const count = parseCount(parsed.values.columns);
89323
+ if (typeof count === "number" && Number.isNaN(count)) {
89324
+ return fail("USAGE", `--columns must be a positive integer`, HELP32);
89325
+ }
89326
+ if (count === undefined)
89327
+ return fail("USAGE", "Missing --columns N", HELP32);
89328
+ const typeRaw = parsed.values.type;
89329
+ if (typeRaw !== undefined && !isSectionType(typeRaw)) {
89330
+ return fail("USAGE", `Invalid --type: ${typeRaw}`, "Valid values: continuous, nextPage, evenPage, oddPage, nextColumn");
89331
+ }
89332
+ let locator;
89333
+ try {
89334
+ locator = parseLocator(at);
89335
+ } catch (error) {
89336
+ if (error instanceof LocatorParseError) {
89337
+ return fail("INVALID_LOCATOR", error.message, HELP32);
89338
+ }
89339
+ throw error;
89340
+ }
89341
+ const document4 = await openOrFail(filePath);
89342
+ if (typeof document4 === "number")
89343
+ return document4;
89344
+ const opts = {
89345
+ filePath,
89346
+ count,
89347
+ sectionType: typeRaw,
89348
+ authorFlag: parsed.values.author,
89349
+ trackFlag: Boolean(parsed.values.track),
89350
+ outputPath: parsed.values.output,
89351
+ dryRun: Boolean(parsed.values["dry-run"])
89352
+ };
89353
+ if (locator.kind === "blockRange") {
89354
+ const range = await resolveBlockRangeOrFail(document4, at);
89355
+ if (typeof range === "number")
89356
+ return range;
89357
+ return wrapRange(document4, range.parent, range.startIndex, range.endIndex, at, opts);
89358
+ }
89359
+ const blockRef = await resolveBlockOrFail(document4, at);
89360
+ if (typeof blockRef === "number")
89361
+ return blockRef;
89362
+ if (blockRef.node.tag === "w:sectPr") {
89363
+ return editSection(document4, blockRef, at, opts);
89364
+ }
89365
+ if (blockRef.node.tag === "w:p") {
89366
+ const index2 = blockRef.parent.indexOf(blockRef.node);
89367
+ return wrapRange(document4, blockRef.parent, index2, index2, at, opts);
89368
+ }
89369
+ return fail("INVALID_LOCATOR", `columns needs a section (sN) or a paragraph range (pN-pM); ${at} is neither`, HELP32);
89370
+ }
89371
+ async function editSection(document4, blockRef, at, opts) {
89372
+ try {
89373
+ new Edit(document4).section(blockRef, { columns: opts.count, sectionType: opts.sectionType }, { authorFlag: opts.authorFlag, track: opts.trackFlag });
89374
+ } catch (error) {
89375
+ if (error instanceof EditError)
89376
+ return fail(error.code, error.message);
89377
+ throw error;
89378
+ }
89379
+ return commit(document4, opts, { at, mode: "section" });
89380
+ }
89381
+ async function wrapRange(document4, parent, startIndex, endIndex, at, opts) {
89382
+ if (parent !== document4.body.findBodyChildren()) {
89383
+ return fail("USAGE", `columns can only wrap body-level paragraphs; ${at} is inside a table cell`, "Section breaks carry column layout and cannot live in a table cell.");
89384
+ }
89385
+ for (let index2 = startIndex;index2 <= endIndex; index2++) {
89386
+ if (containsSectionBreak(parent[index2])) {
89387
+ return fail("USAGE", `The range ${at} already contains a section break`, "Target the existing section directly with `--at sN`, or pick a range without one.");
89388
+ }
89389
+ }
89390
+ const governing = governingColumns(parent, endIndex + 1);
89391
+ const track = resolveTracked(document4, opts.trackFlag);
89392
+ const insert = new Insert(document4);
89393
+ const allocator = track ? new TrackChanges(document4).createAllocator() : undefined;
89394
+ const endNode = parent[endIndex];
89395
+ if (!endNode) {
89396
+ return fail("BLOCK_NOT_FOUND", "Range end is stale (parent does not contain it)");
89397
+ }
89398
+ const startNode = startIndex > 0 ? parent[startIndex] : undefined;
89399
+ if (startIndex > 0 && !startNode) {
89400
+ return fail("BLOCK_NOT_FOUND", "Range start is stale (parent does not contain it)");
89401
+ }
89402
+ let afterBlocks;
89403
+ let beforeBlocks = [];
89404
+ try {
89405
+ afterBlocks = await insert.paragraph({ node: endNode, parent }, {
89406
+ kind: "section",
89407
+ columns: opts.count,
89408
+ sectionType: opts.sectionType ?? "continuous"
89409
+ }, {}, { placement: "after", authorFlag: opts.authorFlag, track, allocator });
89410
+ if (startNode) {
89411
+ beforeBlocks = await insert.paragraph({ node: startNode, parent }, {
89412
+ kind: "section",
89413
+ ...governing > 1 ? { columns: governing } : {},
89414
+ sectionType: "continuous"
89415
+ }, {}, { placement: "before", authorFlag: opts.authorFlag, track, allocator });
89416
+ }
89417
+ } catch (error) {
89418
+ if (error instanceof InsertError)
89419
+ return fail(error.code, error.message, error.hint);
89420
+ throw error;
89421
+ }
89422
+ if (opts.dryRun) {
89423
+ return commit(document4, opts, { at, mode: "range", dryRunOnly: true });
89424
+ }
89425
+ parent.splice(endIndex + 1, 0, ...afterBlocks);
89426
+ parent.splice(startIndex, 0, ...beforeBlocks);
89427
+ return commit(document4, opts, { at, mode: "range" });
89428
+ }
89429
+ function containsSectionBreak(node2) {
89430
+ if (!node2)
89431
+ return false;
89432
+ if (node2.tag === "w:sectPr")
89433
+ return true;
89434
+ if (node2.tag !== "w:p")
89435
+ return false;
89436
+ return Boolean(node2.findChild("w:pPr")?.findChild("w:sectPr"));
89437
+ }
89438
+ function governingColumns(parent, fromIndex) {
89439
+ for (let index2 = fromIndex;index2 < parent.length; index2++) {
89440
+ const node2 = parent[index2];
89441
+ if (!node2)
89442
+ continue;
89443
+ if (node2.tag === "w:sectPr")
89444
+ return columnsOf(node2);
89445
+ if (node2.tag === "w:p") {
89446
+ const inline = node2.findChild("w:pPr")?.findChild("w:sectPr");
89447
+ if (inline)
89448
+ return columnsOf(inline);
89449
+ }
89450
+ }
89451
+ return 1;
89452
+ }
89453
+ function columnsOf(sectPr) {
89454
+ const raw = sectPr.findChild("w:cols")?.getAttribute("w:num");
89455
+ const parsed = raw ? Number.parseInt(raw, 10) : 1;
89456
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
89457
+ }
89458
+ function parseCount(raw) {
89459
+ if (raw === undefined)
89460
+ return;
89461
+ if (!/^\d+$/.test(raw.trim()))
89462
+ return Number.NaN;
89463
+ const parsed = Number.parseInt(raw, 10);
89464
+ if (!Number.isFinite(parsed) || parsed < 1)
89465
+ return Number.NaN;
89466
+ return parsed;
89467
+ }
89468
+ async function commit(document4, opts, meta) {
89469
+ if (opts.dryRun || meta.dryRunOnly) {
89470
+ await respond({
89471
+ operation: "sections",
89472
+ dryRun: true,
89473
+ path: opts.filePath,
89474
+ locator: meta.at,
89475
+ columnCount: opts.count,
89476
+ mode: meta.mode,
89477
+ ...opts.outputPath ? { output: opts.outputPath } : {}
89478
+ });
89479
+ return EXIT2.OK;
89480
+ }
89481
+ await document4.save(opts.outputPath);
89482
+ const destination = opts.outputPath ?? opts.filePath;
89483
+ await respondAck({
89484
+ ok: true,
89485
+ operation: "sections",
89486
+ path: destination,
89487
+ locator: meta.at,
89488
+ columnCount: opts.count,
89489
+ mode: meta.mode
89490
+ }, renderVerifyHint(destination));
89491
+ return EXIT2.OK;
89492
+ }
89493
+ var HELP32 = `docx sections \u2014 multi-column layout & section breaks
89494
+
89495
+ Usage:
89496
+ docx sections FILE --at LOCATOR --columns N [options]
89497
+
89498
+ The verb for section layout \u2014 multi-column flow and section breaks \u2014 so you
89499
+ don't have to hand-build them with the right OOXML semantics. This is the ONLY
89500
+ way to add/change column layout; \`insert\` no longer takes --section (a raw
89501
+ section break formats the content ABOVE it, which is the classic off-by-one).
89502
+ Two addressing modes:
89503
+
89504
+ --at pN-pM Wrap the paragraph range pN\u2026pM in its own N-column section
89505
+ (inserts the bounding continuous section breaks for you, so the
89506
+ columns land on EXACTLY pN\u2026pM). Also accepts a single paragraph
89507
+ (--at pN). THIS is how you put text in columns \u2014 name the range.
89508
+ --at sN Set the column count on an EXISTING section break sN (the section
89509
+ whose content ENDS at sN). Equivalent to \`edit --at sN --columns N\`.
89510
+
89511
+ Options:
89512
+ --at LOCATOR Paragraph range (pN-pM), single paragraph (pN), or section (sN)
89513
+ --columns N Number of columns (>= 1; use 1 to collapse back to single column)
89514
+ --type T Section type for the wrapping break: continuous (default),
89515
+ nextPage, evenPage, oddPage, nextColumn. Only meaningful with
89516
+ a pN-pM/pN range; with sN it overrides the section's type.
89517
+ --author NAME Author for tracked changes (default: $DOCX_AUTHOR)
89518
+ --track Record as a tracked change even when the doc toggle is off
89519
+ -o, --output PATH Write to PATH instead of overwriting FILE
89520
+ --dry-run Print what would change; do not write the file
89521
+ -v, --verbose Print the full success ack JSON
89522
+ -h, --help Show this help
89523
+
89524
+ Agent tip: VERIFY LAYOUT VISUALLY. \`docx read\` shows the section as a
89525
+ \`<!-- docx:section \u2026 cols="N" -->\` annotation but NOT how the columns actually
89526
+ flow on the page (balance, overflow). After setting columns, render and look:
89527
+ docx render FILE --out pages/
89528
+ Adjust the range and re-render until the columns read the way you intended.
89529
+
89530
+ Output:
89531
+ Prints a one-line confirmation on success (exit 0); --verbose prints {ok:true, \u2026}. Positional ids shift
89532
+ after a range wrap (two section breaks are inserted), so re-read before further
89533
+ edits. Errors print {code, error, hint?} + nonzero exit.
89534
+
89535
+ Examples:
89536
+ docx sections doc.docx --at p4-p9 --columns 2
89537
+ docx sections doc.docx --at p4-p9 --columns 3 --type continuous
89538
+ docx sections doc.docx --at s2 --columns 1 # collapse section s2 to one column
89539
+ `, OPTION_SPEC5;
89540
+ var init_sections2 = __esm(() => {
89541
+ init_core2();
89542
+ init_respond();
89543
+ OPTION_SPEC5 = {
89544
+ at: { type: "string" },
89545
+ columns: { type: "string" },
89546
+ type: { type: "string" },
89547
+ author: { type: "string" },
89548
+ track: { type: "boolean" },
89549
+ ...SAVE_FLAGS
89550
+ };
89551
+ });
89552
+
89138
89553
  // src/cli/styles/index.ts
89139
89554
  var exports_styles = {};
89140
89555
  __export(exports_styles, {
@@ -89366,8 +89781,15 @@ async function run38(args) {
89366
89781
  return fail("USAGE", `--position must be an integer in 0..${grid.rows.length}`);
89367
89782
  }
89368
89783
  const cellTexts = parsed.values.cells ? parsed.values.cells.split(",") : [];
89369
- if (cellTexts.length > grid.colCount) {
89370
- return fail("USAGE", `--cells has ${cellTexts.length} entries but table ${tableId} has ${grid.colCount} columns`);
89784
+ for (const cell of cellTexts) {
89785
+ const mangled = await rejectShellMangledValue(cell, HELP34, "--cells");
89786
+ if (typeof mangled === "number")
89787
+ return mangled;
89788
+ }
89789
+ const reference = referenceRow(grid, position2);
89790
+ const logicalCols = reference ? reference.cells.length : grid.colCount;
89791
+ if (cellTexts.length > logicalCols) {
89792
+ return fail("USAGE", `--cells has ${cellTexts.length} entries but the row has ${logicalCols} columns (a merged cell counts once)`);
89371
89793
  }
89372
89794
  const newRow = buildRow(grid, position2, cellTexts);
89373
89795
  const tracking = resolveTracked(document4, parsed.values.track);
@@ -89409,9 +89831,16 @@ function resolveRowPosition(raw, rowCount) {
89409
89831
  return null;
89410
89832
  return value;
89411
89833
  }
89834
+ function referenceRow(grid, position2) {
89835
+ const above = position2 > 0 ? grid.rows[position2 - 1] : undefined;
89836
+ const below = position2 < grid.rows.length ? grid.rows[position2] : undefined;
89837
+ return above ?? below;
89838
+ }
89412
89839
  function buildRow(grid, position2, cellTexts) {
89413
89840
  const below = position2 > 0 && position2 < grid.rows.length ? grid.rows[position2] : undefined;
89841
+ const spans = referenceRow(grid, position2);
89414
89842
  const cells = [];
89843
+ let logical = 0;
89415
89844
  for (let col = 0;col < grid.colCount; ) {
89416
89845
  const continued = below ? cellAt(below, col) : undefined;
89417
89846
  if (continued?.vMerge === "continue") {
@@ -89425,12 +89854,15 @@ function buildRow(grid, position2, cellTexts) {
89425
89854
  col += continued.colSpan;
89426
89855
  continue;
89427
89856
  }
89857
+ const refSpan = spans ? cellAt(spans, col)?.colSpan ?? 1 : 1;
89428
89858
  cells.push(/* @__PURE__ */ jsxDEV(TableCell, {
89859
+ gridSpan: refSpan > 1 ? refSpan : undefined,
89429
89860
  children: /* @__PURE__ */ jsxDEV(Paragraph, {
89430
- text: cellTexts[col] ?? ""
89861
+ text: cellTexts[logical] ?? ""
89431
89862
  }, undefined, false, undefined, this)
89432
89863
  }, undefined, false, undefined, this));
89433
- col += 1;
89864
+ logical += 1;
89865
+ col += refSpan;
89434
89866
  }
89435
89867
  return /* @__PURE__ */ jsxDEV(w.tr, {
89436
89868
  children: cells
@@ -89450,6 +89882,7 @@ var init_insert_row = __esm(() => {
89450
89882
  init_jsx();
89451
89883
  init_locators();
89452
89884
  init_table();
89885
+ init_parse_helpers();
89453
89886
  init_respond();
89454
89887
  init_jsx_dev_runtime();
89455
89888
  AT_FORMS6 = describeForms(["table"], " ");
@@ -89465,8 +89898,11 @@ ${AT_FORMS6}
89465
89898
 
89466
89899
  Optional:
89467
89900
  --position INDEX 0-based row index to insert at (default: append at end)
89468
- --cells "a,b,c" Comma-separated text for the new cells (default: empty).
89469
- Must not exceed the table's column count.
89901
+ --cells "a,b,c" Comma-separated text, one value per VISIBLE cell left to
89902
+ right (default: empty). On a table with merged cells the new
89903
+ row copies the neighbor row's gridSpan pattern, so you pass
89904
+ one value per logical column (a spanned cell counts once),
89905
+ NOT one per underlying grid column.
89470
89906
  --author NAME Author for tracked insertion (default: $DOCX_AUTHOR)
89471
89907
  -o, --output PATH Write to PATH instead of overwriting FILE
89472
89908
  --dry-run Print what would change; do not write
@@ -89999,10 +90435,13 @@ async function run42(args) {
89999
90435
  const cols = grid.tblGrid.findChildren("w:gridCol");
90000
90436
  let twips = [];
90001
90437
  if (!auto) {
90002
- const resolved = resolveWidths(widthsSpec, cols);
90438
+ const resolved = resolveWidths(widthsSpec, cols, grid);
90003
90439
  if (typeof resolved === "string")
90004
90440
  return fail("USAGE", resolved);
90005
90441
  twips = resolved;
90442
+ const tooNarrow = findTooNarrowCell(grid, twips);
90443
+ if (tooNarrow)
90444
+ return fail("USAGE", tooNarrow);
90006
90445
  }
90007
90446
  const outputPath = parsed.values.output;
90008
90447
  if (parsed.values["dry-run"]) {
@@ -90038,20 +90477,24 @@ async function run42(args) {
90038
90477
  appendTblGridChange(grid.tblGrid, priorCols, new TrackChanges(document4).mintMeta(author));
90039
90478
  }
90040
90479
  await document4.save(outputPath);
90480
+ const destination = outputPath ?? path2;
90481
+ const echo = auto ? "" : `${describeColumnWidths(twips)}
90482
+ `;
90041
90483
  await respondAck({
90042
90484
  ok: true,
90043
90485
  operation: "tables.set-widths",
90044
- path: outputPath ?? path2,
90486
+ path: destination,
90045
90487
  table: tableId,
90046
90488
  layout: auto ? "autofit" : "fixed",
90047
90489
  widths: auto ? "auto" : twips
90048
- });
90490
+ }, `${echo}${renderVerifyHint(destination)}`);
90049
90491
  return EXIT2.OK;
90050
90492
  }
90051
- function resolveWidths(spec, cols) {
90493
+ function resolveWidths(spec, cols, grid) {
90052
90494
  const tokens = spec.split(",").map((token) => token.trim());
90053
90495
  if (tokens.length !== cols.length) {
90054
- return `--widths has ${tokens.length} entries but the table has ${cols.length} columns`;
90496
+ const base = `--widths has ${tokens.length} entries but the table has ${cols.length} grid columns`;
90497
+ return hasMergedColumns(grid) ? `${base}. This table has merged cells, so some visible columns span multiple grid columns and the grid has more columns than any single row shows. Supply one width per GRID column (${cols.length} values), left-to-right; a merged cell takes the sum of the grid columns it covers. Inspect the gridSpan layout with \`docx read --ast\`.` : base;
90055
90498
  }
90056
90499
  const percentage = tokens.every((token) => token.endsWith("%"));
90057
90500
  const anyPercent = tokens.some((token) => token.endsWith("%"));
@@ -90103,7 +90546,30 @@ function cellWidth(cell, twips) {
90103
90546
  }
90104
90547
  return width;
90105
90548
  }
90106
- var AT_FORMS10, HELP38;
90549
+ function findTooNarrowCell(grid, twips) {
90550
+ for (const row of grid.rows) {
90551
+ for (const cell of row.cells) {
90552
+ const width = cellWidth(cell, twips);
90553
+ if (width >= MIN_COL_TWIPS)
90554
+ continue;
90555
+ const text6 = cell.node.collectText().trim();
90556
+ if (text6.length === 0)
90557
+ continue;
90558
+ const where = cell.colSpan > 1 ? `grid columns ${cell.colStart}\u2013${cell.colStart + cell.colSpan - 1}` : `grid column ${cell.colStart}`;
90559
+ const sample = text6.length > 24 ? `${text6.slice(0, 24)}\u2026` : text6;
90560
+ return `--widths collapses ${where} to ${twipsToInches(width)}in (${width} twips); that cell holds "${sample}" but ~0.15in goes to cell margins, leaving under one character \u2014 Word wraps it one char per line. Widen it and lower a wider column to compensate.`;
90561
+ }
90562
+ }
90563
+ return null;
90564
+ }
90565
+ function hasMergedColumns(grid) {
90566
+ return grid.rows.some((row) => row.cells.some((cell) => cell.colSpan > 1));
90567
+ }
90568
+ function describeColumnWidths(twips) {
90569
+ const cells = twips.map((value, index2) => `g${index2}=${twipsToInches(value)}in`);
90570
+ return `widths: ${cells.join(" ")}`;
90571
+ }
90572
+ var AT_FORMS10, HELP38, MIN_COL_TWIPS = 288;
90107
90573
  var init_set_widths = __esm(() => {
90108
90574
  init_core2();
90109
90575
  init_locators();
@@ -90136,6 +90602,15 @@ cell's <w:tcW>. Under track-changes the resize is recorded as a real revision
90136
90602
  (<w:tblGridChange> for the grid plus a per-cell <w:tcPrChange>), so it can be
90137
90603
  accepted or rejected \u2014 matching what Word emits for a width change.
90138
90604
 
90605
+ Widths map one value per GRID column, not per visible column. On a table with
90606
+ merged cells (gridSpan), the grid has MORE columns than a single row shows, so
90607
+ the count you pass must match the grid (run \`docx read --ast\` to see it). A
90608
+ cell that HOLDS TEXT but lands narrower than ~0.2in is refused \u2014 after ~0.15in
90609
+ of cell margin it fits under one character, so Word wraps it one char per line
90610
+ (empty/spacer columns that thin are fine, nothing to wrap). The success line
90611
+ echoes the resulting per-column widths; since layout changes don't show in
90612
+ \`read\`, render to verify.
90613
+
90139
90614
  Output:
90140
90615
  Prints a one-line confirmation on success (exit 0). --verbose prints {ok:true, operation, path, table,
90141
90616
  layout, widths}. --dry-run prints the preview object (no ok field). Errors
@@ -90677,6 +91152,64 @@ var init_tables = __esm(() => {
90677
91152
  };
90678
91153
  });
90679
91154
 
91155
+ // src/cli/track-changes/groups.ts
91156
+ function revisionGroups(changes) {
91157
+ const sorted = [...changes].sort((a2, b) => tcIndex(a2.id) - tcIndex(b.id));
91158
+ const membersOf = new Map;
91159
+ const revOf = new Map;
91160
+ let next = 0;
91161
+ let index2 = 0;
91162
+ while (index2 < sorted.length) {
91163
+ const a2 = sorted[index2];
91164
+ const b = sorted[index2 + 1];
91165
+ if (a2 && b && isTextReplacePair(a2, b)) {
91166
+ const rev = `rev${next++}`;
91167
+ membersOf.set(rev, [a2.id, b.id]);
91168
+ revOf.set(a2.id, rev);
91169
+ revOf.set(b.id, rev);
91170
+ index2 += 2;
91171
+ continue;
91172
+ }
91173
+ index2 += 1;
91174
+ }
91175
+ return { membersOf, revOf };
91176
+ }
91177
+ function isTextReplacePair(a2, b) {
91178
+ if (a2.blockId === undefined || a2.blockId !== b.blockId)
91179
+ return false;
91180
+ const pair = `${a2.kind}+${b.kind}`;
91181
+ return pair === "del+ins" || pair === "ins+del";
91182
+ }
91183
+ function expandRevisionTargets(targets, groups) {
91184
+ const out = [];
91185
+ for (const target of targets) {
91186
+ if (/^rev\d+$/.test(target)) {
91187
+ const members = groups.membersOf.get(target);
91188
+ if (!members)
91189
+ throw new UnknownRevisionError(target);
91190
+ out.push(...members);
91191
+ continue;
91192
+ }
91193
+ out.push(target);
91194
+ }
91195
+ return out;
91196
+ }
91197
+ function tcIndex(id) {
91198
+ const match = id.match(/^tc(\d+)$/);
91199
+ return match?.[1] ? Number(match[1]) : 0;
91200
+ }
91201
+ var UnknownRevisionError;
91202
+ var init_groups = __esm(() => {
91203
+ UnknownRevisionError = class UnknownRevisionError extends Error {
91204
+ revision;
91205
+ constructor(revision) {
91206
+ super(`Unknown revision group "${revision}"`);
91207
+ this.revision = revision;
91208
+ this.name = "UnknownRevisionError";
91209
+ }
91210
+ };
91211
+ });
91212
+
90680
91213
  // src/cli/track-changes/list.ts
90681
91214
  var exports_list5 = {};
90682
91215
  __export(exports_list5, {
@@ -90737,6 +91270,12 @@ async function run47(args) {
90737
91270
  byId.set(change.id, record);
90738
91271
  }
90739
91272
  const sorted = [...byId.values()].sort((a2, b) => trackedChangeIndex(a2.id) - trackedChangeIndex(b.id));
91273
+ const { revOf } = revisionGroups(sorted);
91274
+ for (const record of sorted) {
91275
+ const group = revOf.get(record.id);
91276
+ if (group)
91277
+ record.group = group;
91278
+ }
90740
91279
  await respond(sorted);
90741
91280
  return EXIT2.OK;
90742
91281
  }
@@ -90761,7 +91300,11 @@ apart.
90761
91300
 
90762
91301
  Output: a bare JSON array of { id, kind, author, date, revisionId, blockId,
90763
91302
  text } sorted by id (document order). Each item's "id" (e.g. tc0) is its
90764
- addressable handle \u2014 pass it to \`accept\`/\`reject --at tcN\`. Errors print
91303
+ addressable handle \u2014 pass it to \`accept\`/\`reject --at tcN\`. When a del and an
91304
+ ins are an adjacent REPLACE pair on the same paragraph, BOTH carry a shared
91305
+ "group": "revN" \u2014 accept/reject the whole logical change in one call with
91306
+ \`--at revN\` instead of accepting each half separately (tcN ids renumber after
91307
+ each single accept, so the revN handle avoids the re-list ping-pong). Errors print
90765
91308
  {code, error, hint?} with a nonzero exit. kind is one of: "ins", "del", "moveFrom",
90766
91309
  "moveTo", "sectPrChange", "rowIns", "rowDel", "cellIns", "cellDel",
90767
91310
  "tblGridChange", "tblPrChange", "tcPrChange", "checkboxToggle". Paragraph-mark
@@ -90790,6 +91333,7 @@ var init_list7 = __esm(() => {
90790
91333
  init_core2();
90791
91334
  init_track_changes();
90792
91335
  init_respond();
91336
+ init_groups();
90793
91337
  });
90794
91338
 
90795
91339
  // src/cli/track-changes/apply.ts
@@ -90820,8 +91364,20 @@ async function runApply(args, verb, help) {
90820
91364
  const document4 = await openOrFail(path2);
90821
91365
  if (typeof document4 === "number")
90822
91366
  return document4;
90823
- const target = all2 ? "all" : atRaw ?? [];
90824
91367
  const trackChanges = new TrackChanges(document4);
91368
+ let target;
91369
+ if (all2) {
91370
+ target = "all";
91371
+ } else {
91372
+ try {
91373
+ target = expandRevisionTargets(atRaw ?? [], revisionGroups(trackChanges.list()));
91374
+ } catch (error) {
91375
+ if (error instanceof UnknownRevisionError) {
91376
+ return fail("TRACKED_CHANGE_NOT_FOUND", error.message, "Run 'docx track-changes list FILE' \u2014 revN handles appear as the `group` field on paired changes.");
91377
+ }
91378
+ throw error;
91379
+ }
91380
+ }
90825
91381
  const outputPath = parsed.values.output;
90826
91382
  try {
90827
91383
  if (parsed.values["dry-run"]) {
@@ -90853,6 +91409,7 @@ async function runApply(args, verb, help) {
90853
91409
  var init_apply2 = __esm(() => {
90854
91410
  init_track_changes();
90855
91411
  init_respond();
91412
+ init_groups();
90856
91413
  });
90857
91414
 
90858
91415
  // src/cli/track-changes/accept.ts
@@ -90899,6 +91456,10 @@ Target (one required, mutually exclusive):
90899
91456
  batch is not a concern. Supports:
90900
91457
  ${AT_FORMS14}
90901
91458
  See \`docx info locators\`.
91459
+ --at revN Accept a del+ins REPLACE pair in one call (both halves of one
91460
+ logical change). \`list\` tags the two tcNs with a shared
91461
+ "group": "revN"; addressing the revN saves the accept-relist-
91462
+ accept ping-pong (tcN ids renumber after each single accept).
90902
91463
  --all Accept every tracked change.
90903
91464
 
90904
91465
  Options:
@@ -90952,7 +91513,7 @@ were in effect before the tracked edit.
90952
91513
 
90953
91514
  Paragraph-mark trackings (<w:ins>/<w:del> inside <w:pPr><w:rPr>): rejecting
90954
91515
  a paragraph-mark insertion removes the entire owning paragraph (the inserted
90955
- break disappears \u2014 for sentinels created by "insert --section" this also
91516
+ break disappears \u2014 for sentinels created by "docx sections" this also
90956
91517
  removes the section break the sentinel was carrying). Rejecting a
90957
91518
  paragraph-mark deletion just removes the marker (the paragraph stays).
90958
91519
 
@@ -90970,6 +91531,10 @@ Target (one required, mutually exclusive):
90970
91531
  batch is not a concern. Supports:
90971
91532
  ${AT_FORMS15}
90972
91533
  See \`docx info locators\`.
91534
+ --at revN Reject a del+ins REPLACE pair in one call (both halves of one
91535
+ logical change). \`list\` tags the two tcNs with a shared
91536
+ "group": "revN"; addressing the revN saves the reject-relist-
91537
+ reject ping-pong (tcN ids renumber after each single reject).
90973
91538
  --all Reject every tracked change.
90974
91539
 
90975
91540
  Options:
@@ -91520,7 +92085,7 @@ Examples:
91520
92085
  // package.json
91521
92086
  var package_default = {
91522
92087
  name: "bun-docx",
91523
- version: "0.13.0",
92088
+ version: "0.14.0",
91524
92089
  description: "CLI for AI agents (Claude, Codex) to read, edit, and comment on .docx files with full format fidelity.",
91525
92090
  keywords: [
91526
92091
  "docx",
@@ -91611,13 +92176,13 @@ Commands (each one-liner names capabilities you'd otherwise miss; see <command>
91611
92176
  create FILE Create a new .docx (--from PATH.md | --from - builds from Markdown; --force to overwrite)
91612
92177
  read FILE Render as Markdown with pN locators; --from/--to to slice, --accepted (default)/--current/--baseline tracked views, --comments, --ast for JSON-AST
91613
92178
  edit FILE Replace or strip text/formatting at pN, pN:S-E, pN-pM, sN, eqN, or table-cell locators (--clear to strip formatting, --track to redline, --batch for many edits in one read)
91614
- insert FILE Insert a paragraph, image, table, equation, code block, markdown, or structural break (--after/--before LOCATOR; --track; --batch for many inserts in one read)
92179
+ insert FILE Insert a paragraph, image, table, equation, code block, markdown, or page break (--after/--before LOCATOR; --track; --batch for many inserts in one read). For COLUMN layout use "docx sections", not insert.
91615
92180
  delete FILE Remove a paragraph, range, table, or section break (--at LOCATOR; --track for tracked deletion; --batch to remove many in one read)
91616
92181
  find FILE [QUERY] Find spans by text, OR by formatting (--highlight/--color/--bold/--italic/--underline); returns locators for --at
91617
92182
  replace FILE PATTERN REPL Substitute text spans, sed-style (--regex, --track to redline, --dry-run to preview, --batch for a multi-pattern script)
91618
92183
  wc FILE [LOCATOR] Count words in the doc or a slice (--accepted/--baseline/--current tracked view, --json)
91619
92184
  outline FILE List headings as a locator tree (pN feeds --at / read --from; --style-prefix, --json)
91620
- columns FILE Lay a paragraph range out in N columns (--at pN-pM --count N) or recount a section (--at sN)
92185
+ sections FILE Multi-column layout & section breaks \u2014 put a paragraph range in N columns (--at pN-pM --columns N) or recount a section (--at sN). The ONLY way to do columns; insert does not.
91621
92186
  styles FILE List the styles you can apply (--used for ones in use; --at ID to describe one) \u2014 the catalog isn't in the body
91622
92187
  render FILE Visual page verification \u2014 render each page as PNG/JPG via Word or LibreOffice
91623
92188
  comments \u2026 Add (--at LOCATOR | --anchor PHRASE | --batch), reply, resolve (--unset to reopen), delete, list (--thread cN)
@@ -91645,7 +92210,7 @@ render (each render spins up Word and is slow). Render only for what Markdown ca
91645
92210
  multi-column sections, page/section breaks, image sizing/placement, table geometry \u2014 and
91646
92211
  then ONCE at the end (not after every edit), or one final time if you're genuinely unsure
91647
92212
  it looks right: "docx render FILE --out pages/" writes page-001.png, \u2026 which you can read.
91648
- (A multi-column section's columns apply to content BEFORE the break \u2014 see "docx insert --help".)
92213
+ (To put text in columns, name the range: "docx sections --at pN-pM --columns N" \u2014 it inserts the bounding breaks so the columns land on exactly that range. A raw section break's columns apply to the content BEFORE it, which is why insert no longer takes --section.)
91649
92214
 
91650
92215
  Environment:
91651
92216
  DOCX_AUTHOR Default author for comments and tracked-change attribution
@@ -91666,7 +92231,6 @@ async function printTopHelp() {
91666
92231
  // src/cli/index.ts
91667
92232
  init_respond();
91668
92233
  var COMMANDS = {
91669
- columns: () => Promise.resolve().then(() => (init_columns(), exports_columns)),
91670
92234
  comments: () => Promise.resolve().then(() => (init_comments3(), exports_comments)),
91671
92235
  create: () => Promise.resolve().then(() => (init_create2(), exports_create)),
91672
92236
  delete: () => Promise.resolve().then(() => (init_delete2(), exports_delete2)),
@@ -91682,6 +92246,7 @@ var COMMANDS = {
91682
92246
  read: () => Promise.resolve().then(() => (init_read3(), exports_read)),
91683
92247
  render: () => Promise.resolve().then(() => (init_render2(), exports_render)),
91684
92248
  replace: () => Promise.resolve().then(() => (init_replace4(), exports_replace3)),
92249
+ sections: () => Promise.resolve().then(() => (init_sections2(), exports_sections)),
91685
92250
  styles: () => Promise.resolve().then(() => (init_styles2(), exports_styles)),
91686
92251
  tables: () => Promise.resolve().then(() => (init_tables(), exports_tables)),
91687
92252
  "track-changes": () => Promise.resolve().then(() => (init_track_changes2(), exports_track_changes)),
@@ -91711,6 +92276,9 @@ Run "docx --help" for available commands.
91711
92276
  }
91712
92277
  return 0;
91713
92278
  }
92279
+ if (cmd === "columns") {
92280
+ return fail("USAGE", "`docx columns` was renamed to `docx sections`", "Run `docx sections --at pN-pM --columns N` (or `docx sections --help`).");
92281
+ }
91714
92282
  const loader = COMMANDS[cmd];
91715
92283
  if (!loader) {
91716
92284
  return fail("USAGE", `Unknown command: ${cmd}`, 'Run "docx --help" for the list of commands.');