/** * Mutable document — linkedom Document wrapper for Phase 3 editing. * * The linkedom Document IS the mutable backing store. All dispatch mutations * go here. serialize() walks the live DOM; no separate mutable tree to sync. */ import { parseHTML } from "linkedom"; import { ensureHfIds } from "@hyperframes/core/hf-ids"; export interface ParsedDocument { document: Document; /** True when the input was a fragment (no shell) and was wrapped. */ wrapped: boolean; /** ensureHfIds-stamped original HTML — used as fallback / diff base. */ stamped: string; } export function parseMutable(html: string): ParsedDocument { const stamped = ensureHfIds(html); const hasShell = /]/i.test(stamped); const wrapped = !hasShell; const { document } = wrapped ? parseHTML(`${stamped}`) : parseHTML(stamped); return { document: document as unknown as Document, wrapped, stamped }; } // ─── Element lookup ─────────────────────────────────────────────────────────── export function findById(document: Document, id: string): Element | null { // Delegate to resolveScoped so patch replay (undo/redo, override-set apply) // resolves an id the SAME way forward dispatch does: canonical-first for an // ambiguous bare id, and scoped-path ("hf-host/hf-leaf") aware. Otherwise the // two paths disagree on which duplicate a bare id targets and undo reverts the // wrong element. (function declaration is hoisted.) return resolveScoped(document, id); } export function escapeHfId(id: string): string { return id.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); } /** * querySelectorAll that also descends into COMPOSITION `