Break, then Forge: 182,789 Edits Against Our Two-Way HTML Sync Engines
We waged about 45 adversarial campaign pairs and 182,789 edit steps against our own two-way HTML sync engines, fired the same attacks at six open-source libraries, and embedded live demos so you can watch the difference in your browser.
The bug was almost too small to write down. A test document held several identical anonymous tags, throwaway markup no real page would ever ship. Someone typed style=zoom:2 into one of them. On the live page, an element obediently doubled in size. The wrong one.
The diagnosis took longer than the fix. The sync engine had been locating elements partly by occurrence order, and any engine that targets "the third identical tag" is not tracking the document. It is guessing about it. The guess worked until the day two anonymous elements traded places mid-edit.
That one-line bug became constitutional law: occurrence-based targeting is forbidden in our engines, permanently, and every edit target must be proven through source ownership and live DOM validation. It is one of dozens of laws that came out of a roughly 45-pair internal war. We were building three engines and trying to destroy them at the same time. This is the story of IOSync, FastPath, and FastPath Strict, and of the campaign that earned the right to trust them. Before it is over, you will watch a respected open-source library wipe out everything you typed into a form. Live, in your browser, no screenshots involved.
The easy job and the hard job
Every DOM morphing library does the same honorable job: make this DOM look like that HTML. One direction, one shot. The library receives a target document, walks the live tree, and mutates until the two agree. That contract is well scoped, and the best libraries honor it in a few kilobytes.
Our engines signed a different contract. Keep HTML source text and a live DOM agreeing with each other, in both directions, while both sides keep changing. Someone is typing into the source, through unfinished tags and half-typed attributes, and the live page follows every keystroke without reloading. Scripts, web components, and users are mutating the live page, and those changes flow back into the source as exact text patches. The engines run in production today behind RTCode.io, our live HTML playground, where the feature has a public name: Instant I ⇄ O Sync.
One direction is a transformation. Both directions, at typing speed, with neither side owning the truth, is a different engineering problem entirely. The rest of this post is what it took.
Every character has an address
IOSync is the full engine. It parses the source with exact source positions and owns a two-way map binding every live DOM node and attribute to the precise range of text it came from. Ask the map which characters produced this node, and it answers. Ask which node these characters became, and it answers that too. The map is the authority in both directions.
The reverse direction is where most of the engineering lives. When the live page changes and the change syncs back, the engine does not serialize the document and overwrite your file. It derives exact source patches from source-map ownership, applies them to the editor with undo history intact, and rebinds the new map to the output it just accepted, so nothing echoes. Untouched formatting stays untouched: whitespace, unquoted attributes, and existing templates are not normalized just because a sync ran.

Shadow DOM lives in the map too. A source-declared <template shadowrootmode="open"> is first-class: edits inside it morph the live ShadowRoot in place, runtime nodes inside the shadow root are preserved, and sync-back serializes through the template instead of losing it. Shadow DOM no longer disappears when the page turns back into text.
Fast that refuses to lie
FastPath is the verified hot path layered in front of IOSync. The editor already knows exactly which ranges of the source changed, so for common text and attribute edits FastPath patches the corresponding DOM directly instead of reparsing and remapping the whole document. It refuses to trust the shape of an edit alone. Changes are replayed against the old source and must reproduce the new source byte for byte before FastPath touches the DOM; a batch validates whole or applies not at all; anything structural or ambiguous falls through to full IOSync.
FastPath Strict is FastPath with the safety net removed. It shares the parser, the source map, and the morph primitives, but it is not allowed to fall back to the full engine. It exists to prove that the hot path stands on its own, that fast is not quietly leaning on slow. So it is stress-tested at exactly the boundaries where a fallback would have been tempting: held-back invalid source states and the recovery after them, structural moves, document-level head and body edits, raw-text edits, batches far larger than any editor emits.

The laws
The strictest rules in the engines' design have nothing to do with speed.
First law: no fingerprints. Engine bookkeeping must never appear in your document. No data-* markers, no helper classes, no comment sentinels, no wrapper nodes, no reordered children for convenience, and no temporary attributes that get cleaned up later. The internal rulebook calls all of it pollution and declares it invalid even if every report passes. Tracking lives in engine-owned structures and two-way maps, never in the DOM you see and serialize.
Second law: no silent fallback. A morph is verified atomically; if the result cannot be verified, the output rolls back to the last accepted state and the engine reports an error. There is no quiet innerHTML replace behind your back. Any tool that "succeeds" by replacing everything has just destroyed your form state, your component internals, and your scroll position. It succeeded loudly at the wrong job.
Third law: Chrome is the parser oracle. While you type, the source is invalid most of the time, and the engines' validator is not a spec lawyer. Anything Chrome's parser can recover from is morphed to exactly what Chrome would build. Genuinely unfinished states, a half-typed raw-text block, an unterminated declaration, are held back; recoverable structural edits are held only when the live page contains runtime or stateful DOM that a transient parser-recovery rebuild would destroy. The page keeps the last good state and catches up the moment the source makes sense again.
Fourth law: what scripts build, edits must not destroy. Every node and attribute in the live document is classified as source-bound, owned by the text, or runtime-owned, created by scripts, component libraries, or the user. Source edits morph source-bound DOM while runtime DOM holds its place as a fixed anchor. Form values, selection, media state, and custom element internals stay live across edits.
That fourth law sounds abstract. It is not, and you can test it right now.
Watch it happen
Interactive demo (web only). On the web version of this post you can type into a small form, let morphdom, idiomorph, or nanomorph apply an unrelated one-word update, and watch whether your typed text, checkbox, and focus survive. Recorded outcome for this scenario class in the June 2026 benchmark: every open-source library fails at least one preservation check, while IOSync, FastPath, and FastPath Strict pass all of them.
Here is the calibrated truth behind that widget: when morphdom, idiomorph, or nanomorph applies that one-word build bump, your typed text and the checkbox state are gone. All three keep focus in the field, because the input carries an id and these libraries match elements by id; the field stays focused and freshly emptied. The pass marks are computed live from the DOM after each run, so if a library preserves something on your machine, the widget says so.
These libraries are doing exactly their documented job, and that matters. A one-way morpher makes the DOM match the new HTML, and unmanaged input state is not part of the new HTML. Nothing in that widget is a bug in morphdom, idiomorph, or nanomorph. It is the contract difference, demonstrated. Our contract says your half-typed name survives a build-number tick. In the June 2026 benchmark, the FormBreak suite produced roughly 300 to 350 form-state failures per library out of 660 steps, from 295 for diffHTML up to 350 for diffDOM, and zero across the three engines.
The mission had codenames
Trusting all of this took a testing regime whose internal docs read like a demolition log. We call it Break, then Forge. An adversarial campaign builds a generated suite designed to break the three engines, and it is not allowed to conclude that nothing broke until escalation has genuinely tried: the internal floor is fifty distinct fuzzing approaches before a no-failure verdict counts. Then a corrective campaign fixes whatever broke and re-proves the full regression gate, which grows with every pair and never shrinks. Failed runs are archived as evidence, never deleted.
About 45 pairs ran. The names are their own war diary: ParserBreak and ParserForge. FormBreak. MutationBreak. RecoveryBreak. IdentityDriftBreak. AdoptionCycleBreak. BatchStormBreak. StrictPathBreak, aimed solely at FastPath Strict's no-fallback boundaries. CadenceScaleBreak, which types faster than humans on documents larger than ours. BundleDietBreak, which attacked storage pressure and then put the engine bundle on a diet. Standing alongside them are permanent suites with names like Shatterglass, FastPath Havoc, and FastPath Human-Edit Hell, the last one built specifically to feed FastPath the multi-cursor, half-typed, parser-hostile edits real humans produce.
Each suite is also fired at six established open-source DOM morphing libraries: morphdom, idiomorph, nanomorph, diffDOM, diffHTML, and morphlex. Not to gloat. A suite that breaks nothing proves nothing, and their breakage is the calibration that our attacks are real.

Our engines lost. Repeatedly.
The campaigns worked, which is to say they hurt. The parser-recovery pair failed 720 steps per engine on its first serious run. The form-state pair failed 450 to 500 steps per engine before the preservation rules existed. The extended preservation suite opened with the most honest number in the whole archive: 0 passed, 264 failed. A reverse-sync campaign caught 40 overlapping reverse patches nesting projected output inside stale source ranges. An identity campaign caught 78 cases of duplicate-id clones being adopted as source-owned nodes.
Every one of those numbers is still in the archive, attached to the report that produced it. Each was then fixed, re-proved, and welded into the regression gate so it can never quietly come back.
The campaigns also surfaced the web platform's sharpest edges. One design system's components synthesize attributes as a side effect of cloneNode during upgrade. The verification clone grew a tabindex the live tree never had, and a perfectly correct morph kept failing verification. The fix was to build verification clones in an inert document, where custom elements never upgrade and cloning has no side effects. You do not learn that from a spec. You learn it from a gate that loads real component libraries from real CDNs, which is what the gate does: Shoelace, Material Web, UI5, Ionic, Spectrum, Vaadin, and WebAwesome all run inside it, web-native, with no framework wrappers.
Their scenarios, our checks
The fairest ground for comparison is the scenarios the six libraries chose for themselves. We adapted six suites from their own repositories and test corpora, replayed them through our step harness, and scored them with our stricter checks: exact output equality, node identity, and live state, assertions the originals never make. To be clear: these are our adaptations under our pass criteria, not their CI, and all six projects pass their own test suites.
Under those checks, the six libraries pass 86 to 97 percent of their own adapted steps: idiomorph 174 of 202 steps, morphdom 56 of 62, morphlex 365 of 380, nanomorph 98 of 102, diffDOM 107 of 112, diffHTML 112 of 115. IOSync, FastPath, and FastPath Strict pass all six suites at 100 percent.
Two individual steps from those suites are worth naming. In a paired edit adapted from morphdom's own browser tests, all six libraries produce a result our stricter checks reject, morphdom itself in 0.3 milliseconds, while IOSync passes in 2.6 milliseconds, FastPath in 2.2, and Strict in 1.9. And in the scenario we adapted from idiomorph's own focus-preservation test, all six libraries fail under our checks while the three engines pass in under 2 milliseconds. There are 18 such all-six-fail steps inside the idiomorph-derived suite alone. The margins are narrow on this ground. They do not stay narrow.
One report, 182,789 steps
Everything above feeds a single integrated gate, the DOM Diff Master Report, and the June 2026 run is the snapshot this entire post draws from. IOSync passed 182,789 of 182,789 edit steps. FastPath and FastPath Strict passed 162,025 of 162,025 applicable steps each; the remainder are reverse-direction and direct-DOM-mutation steps that an input-to-output hot path skips by design. Each step runs hundreds of individual assertions for correctness, identity preservation, and live state, so the report represents tens of millions of checks, all passing. Median engine time per step is under 2 milliseconds for all three, with zero timeouts.

The six comparison libraries ran on suites that mostly exercise behavior none of them promise; the caveats section below says this properly. Each of the six ran between 166,788 and 182,725 steps. Their pass rates ranged from 14.6 percent (diffHTML) to 42.8 percent (nanomorph). Between them they logged 109,855 two-second timeouts. On 85,798 steps, 46.9 percent of the entire report, every one of the six produced a wrong result or timed out while all three of our engines passed.
Two of those steps deserve close-ups. In a cadence scenario that replays rapid footer edits on a large page, all six libraries hit their two-second timeout on initialization, while the engines initialized the same page in 22.5 to 23.3 milliseconds. And in a Shatterglass scenario built to cross boundaries, the source deletes a button while a script-created aside has to survive. All six fail it; wrong answers stay cheap. The engines pass in about 2.2 milliseconds. That aside should sound familiar, because you can run its little sibling right here:
Interactive demo (web only). On the web version of this post, a script injects a chat bubble that is not part of the page source, you edit the source, and morphdom, idiomorph, or nanomorph applies your edit. The bubble does not survive. Recorded outcome for the matching benchmark scenario (a Shatterglass cross-boundary storm where the source deletes a button while a runtime aside must survive): all six open-source libraries fail it, while IOSync, FastPath, and FastPath Strict pass.
Same outcome, your browser, pinned builds pulled straight from the CDN: the bubble dies the moment any of the three applies your edit.
Step times complicate the story, and the chart below shows it honestly. The libraries' median step is faster than ours, 0.4 to 0.7 milliseconds against 1.8 to 1.9, because a wrong answer costs nothing to produce. The difference lives in the tails: the engines' 95th percentile stays under 67 milliseconds, while the libraries' sits pinned at the two-second timeout ceiling.
The shadow mission: wrap the universe in shadow
The most ambitious test in the program was not a test at all. It was a transformation. The shadow mission made declarative shadow roots first-class citizens of the source map, and instead of writing bespoke shadow tests, the team wrapped the existing corpus, all of it, in source-declared shadow roots at depths one, two, and three. The dedicated proof run rebuilt 11,911 scenarios into 35,733 and replayed 133,113 edit steps through shadow boundaries, every projection morphed in place, every template serialized back. All of them passed, with perfect node correctness, identity, and live-state retention.
Inside the master report, that transformed corpus, grown slightly along with the rest of the gate, became the largest suite by far, covering nearly three quarters of every step in the run. It is where the gap stops being a margin and becomes a chasm. diffDOM, the strongest of the six by steps passed there, managed 47,386 of its 136,314 steps, which is 34.8 percent. diffHTML passed 3,375 of 136,314, which is 2.5 percent. IOSync passed 136,362 of 136,362, and FastPath and FastPath Strict passed 120,789 of 120,789 each. If there is one place where the engines are not incrementally better but light years ahead, it is morphing through shadow boundaries, a job none of the six ever signed up for. And it is most of the report.

What this comparison is not
This is not a claim that morphdom, idiomorph, nanomorph, diffDOM, diffHTML, or morphlex are bad software. The caveats deserve their own section, stated plainly.
The adversarial suites are ours, built to stress behavior those libraries never claim: source binding, runtime-state preservation, parser-recovery semantics, and morphing through declarative shadow roots, with that last one alone covering nearly three quarters of all steps. The timeout budgets were asymmetric, 2 seconds per step for the comparison libraries against 10 for ours, although the slowest first-party step in the whole report finished in 1.98 seconds, under even the third-party budget. The own-corpus runs are our adaptations under stricter checks, not their CI. And inside their intended job the six libraries remain excellent and dramatically smaller, 1.8 to 15.5 KB gzipped against roughly 150 KB for an engine that carries a full two-way source map. If one-way morphing is your problem, they are the right tool, and the live demos above show them doing exactly what they document. The June 2026 run pinned each library at a fixed upstream commit recorded in the report; the live demos in this post pin morphdom 2.7.8, idiomorph 0.7.4, and nanomorph 5.4.3.
The report also contains the libraries' wins, and they stay in. idiomorph passed the ParserBreak suite perfectly, 620 of 620 steps. Five of the six were flawless on the locate-latency suite. The gap in this post is not that the libraries fail everywhere. It is that the engines fail nowhere.
The comparison exists for one reason: it is the only honest way to measure a contract that did not exist before.
Suspect the glue
Performance gates are part of correctness, not a separate scoreboard. Typing at a 16-millisecond cadence, faster than key repeat, must land every intermediate state in the live DOM, in order, with zero misses, zero duplicates, and zero reloads. That gate either passes or the build fails.
Speed bugs also hide in surprising places. Pointing at an element to reveal its exact source range, which the playground ships as F8 Locate, used to take about a second on large documents. The natural suspect was the engine. The engine's reverse lookup took 0.032 milliseconds. Nearly all of that second lived in the wiring around the engine, and with the wiring fixed, the median answer now arrives in about 12 milliseconds, an 83x improvement that cost the engine nothing. The lesson became a habit: measure the whole path, suspect the glue.
The forge stays lit
Somewhere in the archive sits a report that reads 0 passed, 264 failed. It will never be deleted. It is the reason the zeros in this post mean something.
All three engines are proprietary and not yet generally available. They are live behind RTCode.io right now, carrying every scar this post described. The names are final. The release shape is not. And somewhere in the suite list, a new Break campaign is already trying to make this post out of date.