( parsed: ParsedDocument, parent: HfId | null, index: number, html: string, )
| 688 | } |
| 689 | |
| 690 | function handleAddElement( |
| 691 | parsed: ParsedDocument, |
| 692 | parent: HfId | null, |
| 693 | index: number, |
| 694 | html: string, |
| 695 | ): MutationResult { |
| 696 | // Resolve parent element (null → document body). Narrow rather than assert: |
| 697 | // _dispatch does not run validateOp, so a bad parent id must not crash here. |
| 698 | const parentEl = |
| 699 | parent === null |
| 700 | ? ((parsed.document as Document & { body?: Element | null }).body ?? null) |
| 701 | : resolveScoped(parsed.document, parent); |
| 702 | if (!parentEl) return EMPTY; |
| 703 | |
| 704 | const node = parseInsertableFragment(parsed.document, html); |
| 705 | if (!node) return EMPTY; |
| 706 | |
| 707 | // Mint ids against the LIVE doc's existing id set (the #1 landmine — a fresh |
| 708 | // ensureHfIds(fragment) is blind to existing doc ids and can collide). |
| 709 | // Order: mint → capture outerHTML → insert → build patch (id needed for path). |
| 710 | const assigned = collectDocumentHfIds(parsed.document); |
| 711 | const newId = mintFragmentIds(node, assigned); |
| 712 | const stampedHtml = node.outerHTML; |
| 713 | |
| 714 | // Insert at `index` — append if index >= childCount (RFC-6902 insert semantics). |
| 715 | const ref = Array.from(parentEl.children)[index] ?? null; |
| 716 | parentEl.insertBefore(node, ref); |
| 717 | |
| 718 | // parentId for the inverse/replay patch: preserve the caller's id verbatim |
| 719 | // (scoped "hf-host/hf-leaf" path or composition id), not the bare data-hf-id — |
| 720 | // apply-patches resolves it via findById→resolveScoped, so dropping the host |
| 721 | // prefix would re-insert under the wrong (canonical) parent on redo/replay. |
| 722 | const parentId = parent; |
| 723 | |
| 724 | const path = elementPath(newId); |
| 725 | return { |
| 726 | forward: [patchAdd(path, { html: stampedHtml, parentId, siblingIndex: index })], |
| 727 | inverse: [patchRemove(path)], |
| 728 | meta: { newId }, |
| 729 | }; |
| 730 | } |
| 731 | |
| 732 | function handleReorderElements( |
| 733 | parsed: ParsedDocument, |
no test coverage detected