# add_clause after_search Fails After tracked_replace — Use Direct lxml Insertion

## Problem (2026-07-01 凤雅幼儿园劳务派遣协议)

After calling `ed.tracked_replace(old, new)` on multiple paragraphs, subsequent `ed.add_clause(text, after_search="...")` calls silently fail — the new paragraph doesn't appear in the output. The function returns without error but the clause is not inserted.

## Root Cause

`add_clause`'s `after_search` parameter searches paragraph text by concatenating all `<w:t>` elements. After `tracked_replace`, the paragraph's XML contains interleaved `<w:del>` and `<w:ins>` elements. The `after_search` text-matching logic may:

1. Include both old (del) and new (ins) text in the concatenation, so neither the old NOR new text matches cleanly
2. Match the wrong paragraph if the search string appears in unexpected combinations of del+ins text

## Solution: Direct lxml `addnext` Insertion

After all `tracked_replace` calls, insert new clauses directly using lxml:

```python
# Find reference paragraph by index or by scanning accepted-view text
paras = ed.body.findall(f'{WNS}p')
ref_para = paras[target_index]  # e.g., P68

# Build INS paragraph
new_p = etree.Element(f'{WNS}p')
new_p.append(copy.deepcopy(ref_ppr))  # Clone paragraph formatting

ins = etree.SubElement(new_p, f'{WNS}ins')
ins.set(f'{WNS}id', next_rev_id())
ins.set(f'{WNS}author', 'WB')
ins.set(f'{WNS}date', rev_date)

r = etree.SubElement(ins, f'{WNS}r')
r.set(f'{WNS}rsidR', rsid)
r.insert(0, copy.deepcopy(ref_rpr))

t = etree.SubElement(r, f'{WNS}t')
t.set(XML_SPACE, 'preserve')
t.text = clause_text

# Mark paragraph itself as inserted (pPr/rPr/ins)
ppr = new_p.find(f'{WNS}pPr')
ppr_rpr = etree.SubElement(ppr, f'{WNS}rPr')
ppr_ins = etree.SubElement(ppr_rpr, f'{WNS}ins')
ppr_ins.set(f'{WNS}id', next_rev_id())
ppr_ins.set(f'{WNS}author', 'WB')
ppr_ins.set(f'{WNS}date', rev_date)

# Insert after reference
ref_para.addnext(new_p)
ref_para = new_p  # Chain subsequent inserts
```

## When This Applies

- You need to both modify existing clauses (tracked_replace) AND add new clauses in the same editing session
- The `after_search` text has been altered by prior tracked_replace calls

## Correct Operation Order

1. All `ed.tracked_replace(...)` calls first
2. Then find target paragraphs by scanning the body with accepted-view text extraction
3. Insert new paragraphs directly via `addnext`
4. `ed.validate()` + `ed.save()`

## Verification

After save, scan paragraphs and confirm new clauses appear in accepted-view text at the expected positions.
