The IOSync model

IOSync keeps the HTML source in the RTCode.io editor and the live page in the output in sync, in both directions, without reloading the page. An edit in the editor reaches the running page as a small patch, so form values, focus, scroll position and everything your scripts built survive. A change the page makes to itself can travel back and become an exact edit of the source. This article explains the model: what is mapped to what, what happens to an edit, what survives, and where the guarantees end. The product calls the feature Instant I ⇄ O Sync and describes it as "Edit live in either direction, run a one-shot Sync when you choose, or use Load for a clean refresh".

Overview

Two documents take part. The input is the HTML text in the editor. The output is the live page, which runs in its own origin. IOSync binds the text to the output's <body> with a source map, a two-way index between ranges of text and live nodes. The io direction carries edits from the input to the output. The oi direction carries changes from the output to the input.

Editor sourceiooiIOSyncpatchmutationsOutput DOM

These are the operations that move changes along the two arrows. Each has its own command, key and menu item:

OperationDirectionTrigger and keysEffect
Live syncioAs you type. Alt+I / ⌃+⌥+I toggles it; it is on by default.A hot patch. Output state is preserved, except when the edit needs a reload.
Sync onceioF4The same hot patch, on demand, also when live sync is off.
LoadioF5A full reload of the output. State is not preserved.
Live syncoiAs the DOM changes. Alt+O / ⌥+O toggles it; it is on by default.Exact source edits, offered to the editor and applied while the editor does not have focus.
Sync onceoiF6Pulls the current output into the editor once.
Shadow Source MaterializationoiF7Writes runtime open shadow roots into the source as declarative shadow roots. Off by default.
LocatebothF8Boxes over the output for the editor's cursors, and source ranges for the elements you point at. It writes nothing into the output.

The Live sync and reloads guide covers the controls. The rest of this article covers the ideas behind them.

Source-bound and runtime DOM

IOSync classifies every live node and attribute. Source-bound nodes and attributes come from your text, and IOSync finds them by matching the live DOM against the source. Runtime nodes and attributes were created by scripts, components or the user.

Source-boundRuntime
Comes fromYour HTML text.Scripts, components and the user.
An edit in the editorMorphs the live node in place.Leaves it alone. Runtime nodes, attributes and shadow roots act as fixed anchors, and survive inserts, deletes and reorders of their source siblings.
A change in the pageIs written back to its exact range of the source.Light-DOM insertions are adopted into the source while live output → input sync is on. Changes inside runtime shadow roots are ignored.

The output wraps your source in its own <html>, <head> and <body> and parses it as body content. So <meta>, <title>, <link> and <style> in your source live inside <body>. That is why the playground style leaves out <!doctype>, <html>, <head> and <body>. IOSync binds and observes only document.body and the shadow roots inside it: what scripts do to <head> or to the attributes of <html> never syncs back.

From edit to patch

Every editor change goes to one queue, the sync lane. The lane holds at most one pending job: a newer edit replaces the pending one, and the newest snapshot of the source wins. A drain runs on the next animation frame or after 50 ms, whichever comes first, and edits that arrive while a sync is running collapse into one follow-up sync.

For each edit, IOSync takes the cheapest path that is safe. Edits that add or remove < or >, or that touch a tag, are first validated against an HTML parser.

PathUsed whenEffect
TextThe change contains no < or &, is not inside a tag, and lies inside one source-bound text node.Patches that node's data, or a <textarea>'s value. Raw text in script, style, textarea and title is respected.
AttributeThe change is 512 characters or fewer, contains no < or >, and replaces the value of one attribute whose name has no :.Sets that attribute directly and syncs the state of the matching live control.
ExactThe change is 512 characters or fewer, contains no < or >, and lies outside a tag.Patches the one changed text, comment, CDATA or attribute record in place. It commits only if the new source map binds completely, and rolls back otherwise.
MorphEverything else.Builds a full model of the new source and morphs it into the live DOM, with verification.

Unfinished markup is held back

While you type, the source is often not valid yet. IOSync holds such an edit back: the output keeps its last good state, and sync resumes on the first valid keystroke. The reasons are an unclosed tag, a tag opened inside a tag, an unclosed attribute quote, comment, CDATA section or declaration, and a <template> without its end tag. An unclosed <script>, <style>, <textarea> or <title> is valid: the rest of the source becomes its text.

Every morph is atomic

IOSync journals the accepted output before it morphs. If verification fails or anything throws, it restores the output DOM to the last accepted state and keeps the last accepted source. The editor is never changed by a failed input → output sync, and nothing falls back to a quiet innerHTML replacement.

Hot patches never run scripts

The engine parses with DOMParser and inserts the imported nodes, so a newly inserted <script> does not execute, and changing the text of an existing script never runs it again. Edits to executable scripts therefore reload the output instead, unless the script is marked pure Experimental. See <script>.

What updates live

Everything that is not an executable script or a reload trigger updates live, with no reload: text, attributes, inline style, <style> text, <link href>, inline event-handler attributes, <title>, the contents of a <template>, and data-block scripts such as type=application/json.

What survives an edit

A hot patch changes only what the edit changed. IOSync treats the source as an expression of intent: if a source field did not change, its runtime value survives. A script-changed attribute or text, or a source element that a script removed, stays as the script left it. Only a field that the edit actually changes overrides the live value.

Form state follows the same rule:

Live stateOverwritten only when
<input> valueThe source value attribute changes. Typed text survives other edits.
<input> checkedThe source checked attribute changes. Toggles survive other edits.
<option> selectedIts selected attribute changes.
<select> selected optionThe set of selected options in the source changes.
<textarea> valueIts source text changes.

Focus, selection, media playback, canvas pixels, scroll position, JavaScript variables, event listeners and the internals of custom elements are not touched by hot patches. Where the browser has moveBefore() (Chrome and Edge 133, Firefox 144), IOSync uses it for state-preserving moves, and it falls back to insertBefore() elsewhere.

When the output reloads

A full reload, also called Load, is the exception, and it is deliberate. It resets JavaScript state, the DOM, form values and runtime DOM. The output keeps its scroll position, its output settings and the origin's storage. IOSync reloads the output when:

Exported files (o:= files, worker:fs assets and worker code) reach the output's service worker only on full reloads. The Live sync and reloads guide lists every trigger and shows how to declare custom elements, with <meta io:sync>, so they do not reload the output.

From the page back to the source

In the other direction, IOSync observes document.body and every open shadow root it can reach with one MutationObserver. Closed shadow roots are invisible to it. It filters the mutation records and turns the rest into exact text patches of the source.

What is written back

  • Elements, text and comments that scripts or the user inserted are adopted: they are written into the source at the matching position.
  • Removals of source-owned nodes delete their range of the source.
  • Attribute changes are written back, including the ones the browser makes itself, such as open on <details> and <dialog>, and the attributes and classes a component sets on its host.
  • Text changes are written back, for example typing in a contenteditable element.

The patched source is validated before it is committed, and a result that fails validation is dropped.

What never syncs back

  • Writes that do not change a value.
  • Changes inside regions marked oi:exclude.
  • Changes inside runtime shadow roots, unless Shadow Source Materialization is on, or under a host with oi:exclude=shadowroot.
  • Mutations that IOSync's own hot patches cause.
  • Live form properties: a typed input.value, a checked toggled by the user and <select> choices change no attribute, so there is nothing to write.
  • Anything in <head> or on <html>.

Proposals, focus and echo control

Each output offers its changes to the editor as proposals. The editor holds a proposal while it has focus, and applies it once the editor loses focus. A change that comes back from one output is applied to the editor and hot-patched into the other outputs, and the output that produced it is not morphed again, so changes never echo.

The new text replaces the whole editor model in one edit. Scroll position and selections are restored, but folds can be lost. Live commits merge into the previous undo step, while F6 creates its own, and undoing a reverse commit is an ordinary editor change that hot-patches the outputs back.

Turning on live output → input sync forces one full snapshot of the output, and so does every output initialization while it is on, including after each Load. As a result, DOM that your scripts create while the page loads is adopted into the source as soon as the editor lets the proposal in. Writing IOSync-safe components shows how to build elements that do not do that.

Source fidelity

Only the changed ranges are rewritten. Untouched whitespace, attribute quoting, comments, entity spellings and templates stay byte-identical. The parts that do change follow fixed rules:

  • Attribute values use minimal quoting: an empty value is a bare name, and a value is unquoted unless it contains whitespace, a quote, a backtick, =, < or >.
  • A new attribute goes just before the start tag's >, and a removed attribute leaves with its leading whitespace.
  • New elements are serialized compactly in place: lowercase tag names, no added newlines or indentation, and void elements without an end tag or slash.
  • A changed text node is rewritten whole, with &, < and > escaped, so an entity such as &nbsp; in that node becomes the literal character.

Shadow roots

Declarative shadow roots in your source, <template shadowrootmode=open> inside a host, are first-class. Edits inside the template morph the live shadow root in place, runtime nodes inside it are preserved, and changes inside it sync back through the template.

Runtime shadow roots are the open roots that scripts create with attachShadow(). IOSync can reach them, but it does not write them to the source, and it ignores mutations inside them, unless Shadow Source Materialization (F7) is on. With it on, reverse sync writes each reachable runtime open shadow root into the source as a <template shadowrootmode=open> placed directly after the host's start tag, and later edits inside it are ordinary source-bound edits. Closed shadow roots are never visible to IOSync.

Two structural limits apply. Adding a shadow root to a host that is already live is refused, and you press F5 to apply it. Removing a source-declared shadow root from an existing host replaces the host element, so its identity and state are lost. Writing IOSync-safe components has the details and examples.

Locate

Locate turns the source map into a pointer. Each cursor in the editor gets a box over the element it is in, and hovering or clicking an element in the output reveals its source range in the editor. Locate reads the map and never writes into the output DOM. See Locating source with F8.

Engines and the published test record

The blog post that introduced the engines (published 2026-06-10) names three: IOSync, the full source-mapped engine; FastPath, a verified hot path for text and attribute edits that falls through to IOSync; and FastPath Strict, FastPath with no fallback. It calls them "proprietary two-way sync engines between HTML source and the live DOM". The playground runs IOSync, with the verified fast path for text and attribute edits built in.

The figures below are quoted from that post. They describe the master record of June 2026, run in headless Chrome.

EngineEdit steps passed
IOSync182,789 / 182,789
FastPath162,025 / 162,025 applicable
FastPath Strict162,025 / 162,025 applicable

These numbers describe test suites, not every page you can write. The sections above list where IOSync reloads, holds an edit back or refuses it. Fast typing on a heavy page shows one more limit: the lane coalesces edits, so intermediate output states can be skipped, and the final state always arrives.

Guarantees and limits

What IOSync guarantees

  • A hot patch never runs a script.
  • A morph that fails verification, or throws, is rolled back to the last accepted output, and the editor is left as it was.
  • Unfinished markup does not reach the output.
  • Runtime nodes survive edits to their source siblings, and a field you did not edit keeps the value your script gave it.
  • Hot patches do not touch focus, selection, scroll position, media playback, canvas pixels, listeners or custom-element internals, and form input you typed survives unless you edit that field in the source.
  • Reverse sync rewrites only the ranges that changed, and a change never echoes back to the output that made it.
  • Locate never writes into the output DOM.

Limits

  • Coalescing. Fast typing or key repeat on a heavy page can skip intermediate output states. The final state always arrives.
  • Reloads. Edits to executable scripts, any edit while an undeclared custom element or a reload marker is present, and F5 reload the output and reset its state.
  • Exports. Exported files, worker:fs assets and worker code refresh only on full reloads.
  • Focus. Output → input changes wait while the editor has focus, and applying them can lose folds.
  • Scope. IOSync binds document.body and the shadow roots in it. It does not see <head>, closed shadow roots or form properties. A page that navigates inside the output to an exported .html page is served exactly as stored, so IOSync, reverse sync and Locate are absent on that page.
  • Playground only. IOSync runs in the playground. Deployed pages on RTEdge.net contain none of it, and the sync attributes have no effect there. See Playground and edge differences.
Architecture

How the editor, the output, the console and the edge fit together.

Playground and edge differences

What exists only in the playground, and what changes when you deploy.

Agent Folder Share

How the editor text and a file on disk stay in sync, and how output → input sync behaves while a folder is shared.

Live sync and reloads

The controls: menus, keys, reload triggers and how to steer them.

Writing IOSync-safe components

How to build custom elements that stay in sync with the source.

See also