Skip to content

fix(bookmarks): keep bookmarks through whole-paragraph edit; pair fresh raw bookmark ids - #49

Open
kklimuk wants to merge 1 commit into
mainfrom
claude/triage-issue-40-bookmark-integrity
Open

kklimuk wants to merge 1 commit into
mainfrom
claude/triage-issue-40-bookmark-integrity

Conversation

@kklimuk

@kklimuk kklimuk commented Nov 6, 2026

Copy link
Copy Markdown
Owner

🤖 docx-cli triage

What was wrong

A first slice of #40: the two bookmark-integrity bugs that don't depend on the field model (#41). On tests/fixtures/multi-column.docx, p103 holds _Ref279752219, which p37 and p93 cite:

  • edit --at p103 "Psy. 2013. *Gangnam Style*. Video." dropped the bookmark (17 → 16 pairs).
  • edit --at p103 --text "…" kept the bookmarkStart but dropped the bookmarkEnd, which sat inside the rebuilt <w:hyperlink> (17 starts, 16 ends).

Both exit 0 and pass validate, but every REF to the bookmark prints "Error! Bookmark not defined."

Separately, raw insert --xml '<w:p><w:bookmarkStart w:id="100" …/>…<w:bookmarkEnd w:id="100"/></w:p>' landed as start id 100 and end id 0.

What changed

  • New src/core/bookmarks/markers.ts. It lifts a paragraph's bookmark markers (direct children, or inside run-bearing wrappers) and restores them: a start goes right after <w:pPr>, an end after the last child. It keeps the original nodes, so every attribute survives.
  • Edit.paragraph (single and batch, --markdown/--runs/--text rebuild):
    • It lifts the markers and puts them back on the first new paragraph. That is the same carrier lifted object runs use.
    • Under tracking they go onto the transition paragraph, outside the <w:del>/<w:ins>, so both accept and reject keep the bookmark.
    • On the --text word-diff path, markers that survived stay where they are, and only casualties come back at the paragraph boundary.
    • A cross-paragraph bookmark keeps its half on the edited paragraph.
  • remintBookmarkIds (src/core/raw/references.ts): the first time it sees a free id, it records an identity mapping, so the end marker reuses its start's id.
  • Tests: tests/cli/edit-bookmarks.test.ts (6 tests) and one raw-gate test. The 4 untracked edit tests and the raw test fail on main.

Verification

  • Before: e1 starts=16 ends=16, e2 starts=17 ends=16. After: all 17/17, with w:id="13" paired on p103. This holds for untracked, --track + accept, and --track + reject, and validate passes on all of them.
  • Raw insert: start and end are both w:id="100".
  • bun run check ✅ bun run test:unit ✅ (2098 tests)
  • /simplify (one pass in this context, without the 4-agent fan-out): rewrote lift as collect-then-remove. /code-review xhigh: the forked run reviewed the wrong checkout, not this diff, so I did a manual review instead. That review led to two changes: restore now keys markers by tag+id, so a cloned wrapper can't duplicate a marker, and removal now splices in place.

Decisions for you

Part of #40 (not a full fix; the issue stays open).


Generated by Claude Code

…sh raw bookmark ids

A whole-paragraph `edit` dropped the paragraph's bookmark (`--markdown`
rebuild) or just its `<w:bookmarkEnd>` when it sat inside a rebuilt
`<w:hyperlink>` (`--text`), silently breaking every cross-reference to it
("Error! Bookmark not defined." in print). The edit now lifts the
paragraph's bookmark markers and puts them back around the new content
(outside the `<w:del>`/`<w:ins>` pair under tracking), and the `--text`
word diff restores any marker it rebuilt away.

`raw insert` split every fresh bookmark pair: `remintBookmarkIds` treated
the end marker as colliding with its own start and re-minted it. The first
sight of a free id now maps to itself, so both halves share it.

First, field-independent slice of #40.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LkctENj7B2nTmUttTgfBhs
Triage-Routine: true

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