Skip to content

feat(move): relocate blocks in place with docx move (untracked, first slice of #38) - #50

Open
kklimuk wants to merge 1 commit into
mainfrom
claude/triage-issue-38-move
Open

kklimuk wants to merge 1 commit into
mainfrom
claude/triage-issue-38-move

Conversation

@kklimuk

@kklimuk kklimuk commented Nov 6, 2026

Copy link
Copy Markdown
Owner

🤖 docx-cli triage

What was wrong

There was no way to move content. The only route was read → insert a copy → delete the original, and that loses things. Running the issue's repro on main (docx move m.docx --at p3 --after p4) gives Unknown command: move. The workaround leaves the comment with no anchor and turns [^fn1] into literal text.

What changed

This is the first slice of #38: untracked docx move.

  • docx move FILE --at SRC (--after|--before) DEST [--subtree] and --batch moves.jsonl ({at, after|before, subtree?}). The command moves the live XML nodes themselves, so comment ranges, footnote references, bookmarks, fields, images and text boxes go with them unchanged. It prints the moved blocks' new locators.
  • What it can move: pN, pN-pM (any tables in between come along), tN, a cell paragraph tN:rRcC:pK, and a text-box paragraph tbxN:pK. With --subtree, a heading moves together with everything under it.
  • Where it can put them: next to any block, or at the start/end of a bare cell, the same as insert. Moving into an empty cell fills Word's blank paragraph. A cell left with nothing gets one.
  • Refused with a nonzero exit and nothing written:
    • the destination is inside the source
    • the move would leave the blocks where they already are
    • the source paragraph carries a section break
    • a comment, bookmark, permission or move range has one end inside the source and one outside
    • the move crosses between the body and a text box
    • overlapping sources in one batch, or one entry anchoring on a block another entry moves
    • any move while track changes is on
  • Code: src/core/move/ (the Move lens: plan then apply) and src/cli/move/ (the thin parser). headingLevel is now shared with outline. Body.locatorsOf is also used by insert.
  • Docs: top-level help line, REFERENCE.md, src/cli/CLAUDE.md, root CLAUDE.md, the skill (SKILL.md regenerated) and references/commands.md. The footnotes-mutations fixture now builds its endnote paragraph using docx move.

Not in this PR (still open on #38): tracked moves (w:moveFrom/w:moveTo), pairing moveFrom/moveTo in track-changes list/accept/reject, tables move-row, and the [^fn1] copy-back bug. The tracked shape is supposed to be captured from the Word oracle (scripts/word-redlines.sh, macOS + Word), and that isn't available here. So this uses Refs, not Fixes, and #38 stays open.

Verification

  • After: docx move m.docx --at p3 --after p4 → p4. The paragraph's XML is byte-identical, comments list anchors c0 on p4, read shows …Delaware[^fn1]. with its definition, and validate is clean.
  • 19 new tests in tests/cli/move.test.ts: the repro, range, subtree, table, into/out of a cell, a text-box story (Choice and Fallback both reordered), every refusal leaving the file unchanged, dry-run, and batch ordering and refusals.
  • bun run check ✅ bun run test:unit ✅ (2140 tests)
  • /simplify: one pass (no subagents), 3 small cleanups (reused findDescendant, one destinationInside helper, a BATCH_KEYS constant). /code-review xhigh: the forked run reviewed the wrong scope (main...HEAD in the shared checkout). I discarded its edits and reviewed this diff by hand instead. That found one bug, fixed with a regression test: a batch that moves an empty cell's paragraph out and also moves content into that cell tried to reuse the detached paragraph.
  • Not run here: the LibreOffice round-trip and the weak-agent harness.

Decisions for you

  • Moves while tracking is on are refused for now. The hint offers either insert + delete (both show as redlines) or turning tracking off around the move. Is that acceptable until moveFrom/moveTo lands, or should the tracked half block shipping move at all?
  • Comment ranges that straddle the source edge are refused (issue question 1). Keep that, or shrink the range?
  • A moved list item keeps its numPr, as Word does (issue question 2), and doesn't join the list next to its new spot.
  • Batch refuses an entry that anchors on a block another entry moves, as the issue specified. That also rules out chains like "p5 after p1, p6 after p5". Keep it strict?
  • There's no --track flag yet. It would only ever error, so I left it out.
  • I didn't add a weak-agent scenario. The contract-redlining scenario runs with tracking on, and this slice refuses moves under tracking. Fold it in once tracked moves exist.

Refs #38


Generated by Claude Code

@kklimuk

kklimuk commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

🤖 docx-cli triage

CI note: Integration Tests (LibreOffice) failed on 93ec3a5, but it never reached a test. The Install LibreOffice step hit its 5-minute timeout (##[error]The action 'Install LibreOffice' has timed out after 5 minutes.). The same job passed on main (e830c0c) and on PRs #48/#49 in this same window, so this is a slow install on the runner and not this PR's code. Every other check on this head is green or was still running when I checked.

The routine never re-runs workflow runs, so I haven't re-run it. The PR summary (what docx move adds and its decision checklist) is on #38.

Action needed: approve/merge — re-run the failed "Integration Tests (LibreOffice)" job on #50 once. If it goes green, review and merge, and answer the decision checklist in the PR description. If the install step keeps timing out, the 5-minute cap added in e830c0c may be too tight for a cold cache.


Generated by Claude Code

kklimuk added a commit that referenced this pull request Nov 6, 2026
…ror (#55)

Three PRs (#50, #51, #52) went red on the integration job's Install
LibreOffice step, killed at its 5-minute cap. Not a code failure: the
runner's azure.archive.ubuntu.com mirror served the ~100 MB of .debs at
~35 KB/s while metadata stayed fast. Acquire::Retries can't help, since a
slow transfer never errors.

- Resolve the exact package set after `apt-get update` and key an
  actions/cache entry on the .deb filenames (name_version_arch, not URIs,
  so a mirror switch doesn't change the key). restore-keys gives partial
  hits; apt fetches only what's missing and verifies every cached .deb
  against the signed index.
- Download-only with a per-attempt timeout; a stalled first attempt
  rewrites the mirrorlist to archive.ubuntu.com and retries. Install then
  runs with --no-download, so a timeout never interrupts dpkg.
- Save right after download (not post-job) so a red test run still warms
  the cache; autoclean before saving so old versions don't accumulate, and
  re-key from the lists actually downloaded against.
- Job-level `shell: bash` for pipefail, so a failed --print-uris fails the
  step instead of keying on an empty list.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Add `docx move FILE --at SRC (--after|--before) DEST [--subtree] [--batch]`,
a pure splice of the live source nodes, so comment ranges, note references,
bookmarks, fields, drawings and text boxes ride along instead of being lost
in the read -> insert-a-copy -> delete workaround.

- core/move: Move lens (plan resolves + validates as read; apply splices by
  live indexOf, per-anchor offsets, one CellInsertionCursor per bare cell)
- refusals (nonzero, nothing written): destination inside source, no-op,
  section-break carrier, straddling comment/bookmark/perm/move ranges,
  body <-> text-box, overlapping batch sources, anchors on moved blocks,
  and any move while track changes is on (tracked moves not yet written)
- --subtree shares headingLevel with outline; Body.locatorsOf reports the
  moved blocks' new locators
- footnotes-mutations fixture dogfoods move; docs + skill updated

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LkctENj7B2nTmUttTgfBhs
Triage-Routine: true
@kklimuk
kklimuk force-pushed the claude/triage-issue-38-move branch from 93ec3a5 to 76faa2c Compare November 6, 2026 17:39

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants