The DevTools DOM Mirror is an optional debugging surface that projects the retained EffinDom tree into browser DOM nodes so normal browser DevTools can inspect the UI structure.
It is separate from the semantic projection. The semantic projection serves accessibility, browser find, text selection, and browser services. The DevTools DOM Mirror is debug tooling only.
Runtime config accepts:
window.__effindomRuntime = Object.assign({}, window.__effindomRuntime, {
buildMode: "debug",
devToolsDomMirror: "on-requested",
});
buildMode can be:
"debug""release"devToolsDomMirror can be:
"disabled": never create the mirror."enabled": create and keep the mirror active."on-requested": create the mirror only after a user/debug request.If devToolsDomMirror is omitted, debug builds default to "on-requested" and
release builds default to "disabled".
Generated create-fui-as-app templates emit only buildMode. That keeps the
template harness simple while still allowing users to override
window.__effindomRuntime.devToolsDomMirror before the bridge loads.
Harness code can also opt in explicitly:
startHarness({
wasmPath: "./app.wasm",
devToolsDomMirror: "enabled",
});
Routed apps use the same option:
startRoutedHarness({
shellId: "fui-routes",
routeBase: "/",
routes,
devToolsDomMirror: "on-requested",
run(exports, route) {
exports.__runApp();
},
});
When the runtime is loaded, the bridge exposes:
EffinDomBrowserBridge.devTools.enableDomMirror()
EffinDomBrowserBridge.devTools.disableDomMirror()
EffinDomBrowserBridge.devTools.toggleDomMirror()
EffinDomBrowserBridge.devTools.isDomMirrorEnabled()
EffinDomBrowserBridge.devTools.openDebugDialog()
EffinDomBrowserBridge.devTools.closeDebugDialog()
EffinDomBrowserBridge.devTools.toggleDebugDialog()
EffinDomBrowserBridge.devTools.isDebugDialogOpen()
EffinDomBrowserBridge.devTools.selectHandle(handle)
EffinDomBrowserBridge.devTools.clearSelection()
EffinDomBrowserBridge.devTools.getSelectedHandle()
Shift+Meta+F12 toggles the debug dialog. The dialog can enable the DOM Mirror
and toggle Inspect Mode. Escape exits Inspect Mode first, then closes the
dialog when appropriate.
The console APIs return false when the current mode disallows the operation,
for example when devToolsDomMirror is "disabled" or when a selected handle
does not exist in the latest retained-tree snapshot.
Inspect Mode uses EffinDom hit testing rather than DOM hit testing. Hovering the canvas highlights the retained node under the pointer; clicking selects it and prevents the app from receiving that click.
Selecting or inspecting a node does not scroll the app. Offscreen retained nodes can exist in the mirror tree, but highlight overlays are pinned to the rendered canvas and only show currently visible geometry.
The mirror root is:
<div id="effindom-devtools-dom-mirror" data-fui-devtools-dom-mirror="true">
...
</div>
The overlay root is:
<div id="effindom-devtools-overlay" data-fui-devtools-overlay="true"></div>
The debug dialog root is:
<div id="effindom-devtools-debug-dialog" data-fui-devtools-debug-dialog="true"></div>
Mirror elements use readable custom tags where the runtime has enough type or semantic information:
fui-buttonfui-textboxfui-linkfui-headingfui-formfui-listfui-list-itemfui-imagefui-dialogfui-checkboxfui-radiofui-radio-groupfui-switchfui-sliderfui-combo-boxfui-flex-boxfui-textfui-svgfui-scroll-viewfui-gridfui-pathfui-node fallbackUseful mirror attributes include:
data-fui-handledata-fui-parent-handledata-fui-node-iddata-fui-typedata-fui-render-node-typedata-fui-node-typedata-fui-semantic-roledata-fui-semantic-role-namedata-fui-semantic-labeldata-fui-boundsdata-fui-visible-boundsdata-fui-clippeddata-fui-scrolldata-fui-scroll-ancestordata-fui-interactivedata-fui-focusabledata-fui-editabledata-fui-custom-drawabledata-fui-portaldata-fui-selecteddata-fui-inspect-hoveredBounds are logical canvas coordinates, not device-pixel-multiplied backing store coordinates.
The mirror is marked as debug infrastructure, not app content. It is hidden from accessibility and should not be used as a public DOM, styling, automation, or accessibility contract.
The generated templates use:
npm run dev: debug build mode, mirror defaults to on-requested.npm run build: debug build mode, mirror defaults to on-requested.npm run publish: release build mode, mirror defaults to disabled.To override the default in a generated app, set the field before bridge.js
loads:
<script>
window.__effindomRuntime = Object.assign({}, window.__effindomRuntime, {
devToolsDomMirror: "enabled",
});
</script>
@effindomv2/runtime exports:
BuildModeDevToolsDomMirrorModecreateRuntimeConfig(...)applyRuntimeConfig(...)createRuntimeConfigScript(...)normalizeBuildMode(...)normalizeDevToolsDomMirrorMode(...)normalizeRuntimeConfig(...)resolveDevToolsDomMirrorConfig(...)resolveDevToolsDomMirrorConfig(...) applies the defaulting rule: debug implies
on-requested, release implies disabled, and an explicit mirror mode wins.
For browser-bridge changes:
npm run typecheck:v2
npm run build:v2:browser-bridge
npx playwright test -c v2/browser-bridge/playwright.config.ts tests/devtools-dom-mirror.spec.ts
For FUI-AS demo integration:
npm run build:v2:fui-as
cd v2/fui-as && npx playwright test -c playwright.config.ts tests/demo-devtools-dom-mirror.spec.ts