Render Runtime Contract
This document defines the real runtime contract for FDO plugins built with@anikitenko/fdo-sdk.
Short Version
- Your plugin class runs in the plugin backend/runtime.
- Your plugin UI runs in a sandboxed iframe host managed by FDO.
render()returns UI source for the FDO iframe pipeline.- That output is not the same thing as raw
innerHTMLinserted directly into the host page. - SDK DOM helpers generate raw HTML strings intended for that iframe-hosted render pipeline.
Two Runtimes
Backend Runtime
This is where your plugin class and backend logic run. Typical backend/runtime responsibilities:init()- handler registration
- storage access
- logging
- filesystem or process-side work
- error handling outside the iframe UI
Iframe UI Runtime
This is where your plugin UI code runs after FDO consumesrender() output.
Typical iframe/runtime capabilities:
- browser DOM access
window.createBackendReq(...)window.waitForElement(...)window.executeInjectedScript(...)window.addGlobalEventListener(...)window.removeGlobalEventListener(...)window.applyClassToSelector(...)- injected UI libraries such as goober, ACE, Highlight.js, Notyf, FontAwesome, and Split Grid
What render() Really Means
render() returns a string, but that string should be understood as UI source for the FDO iframe host pipeline.
At a high level, FDO:
- requests plugin render output
- prepares and validates the payload
- wraps the render content for the iframe host
- transforms it for the plugin page runtime
- mounts it inside a React-hosted sandboxed iframe
- “plain HTML string” is an incomplete mental model
- “React component source” is also not quite right from the plugin author’s perspective
- the safest description is: JSX-like UI strings for the FDO iframe host pipeline
- helper APIs may accept compatibility aliases like
className,htmlFor, andreadOnly - emitted helper output should still use raw HTML attribute names like
class,for, andreadonly - when both forms are supplied, the native HTML form wins explicitly so output does not depend on object key iteration order
JSX-Compatibility Rules For render()
In practice, the FDO host transform treats render() output as JSX-compatible UI source, not as unconstrained raw innerHTML.
That means some markup that would be valid in loose HTML can still fail in the host render pipeline.
Common failure cases:
- raw void tags such as
<br>instead of<br /> - raw
<style>blocks insiderender() - literal JavaScript/object syntax inside
<code>blocks without escaping JSX-sensitive characters - other raw text that contains unescaped
{/}in JSX-visible positions - display-only strings that match host fail-fast guard patterns, for example sensitive runtime tokens such as
process.or other blocked runtime-access markers - raw JSON or object-literal content embedded directly into JSX-visible markup, especially inside
<pre>blocks
- prefer JSX-safe void tags such as
<br /> - avoid inline
<style>blocks inrender(); prefer DOM helpers plusrenderHTML(...), host CSS classes, or inline element styles when needed - if you show code samples in
<code>or<pre>blocks, escape literal braces as{and} - avoid embedding raw guard-sensitive runtime tokens in display text when a plain-language description is enough
- if you want to show structured JSON results, render a safe placeholder first and populate the result panel after iframe initialization through backend/UI calls
- do not assume “browser HTML parsing would accept this” means the FDO host transform will accept it
process. token and reject the render source.
For JSON/result panels, prefer this shape:
render():
render()returns the plugin’s UI stringrenderOnLoad()optionally returns:- source string
- function (
() => void) defineRenderOnLoad(...)module
- serialization for the host transport is handled by explicit SDK methods rather than by mutating plugin lifecycle methods at construction time
Supported Lifecycle Contract
The supported public contract in the SDK is:init()performs plugin setuprender()synchronously returns a stringrenderOnLoad()optionally synchronously returns a string, function, ordefineRenderOnLoad(...)moduleserializeRender()andserializeRenderOnLoad()are transport helpers used by the SDK/host boundary
verify
defineRenderOnLoadActions(...) as the default path for UI event wiring. Use plain defineRenderOnLoad(...) when you need heavily custom runtime logic that does not fit selector/event bindings.
For host/editor template pickers, use:
listRenderOnLoadTemplates(...)getRenderOnLoadTemplate(id)
runtime-source vs plugin-method) and language metadata for deterministic editor UX.
Minimal runtime-check snippet used by docs CI:
verify runtime
init()render()- optionally
renderOnLoad()
Plugin Metadata Contract
metadatais part of the host-facing contract, not just plugin-internal data.metadata.iconmust be a valid BlueprintJS v6 icon name because the FDO host uses BlueprintJS v6.- Keep metadata values deterministic and serializable.
What The SDK DOM Helpers Do
Classes such asDOMText, DOMButton, DOMTable, DOMNested, and related helpers build strings for plugin UI composition.
You should treat them as:
- helpers for the FDO UI render pipeline
- not proof that the host inserts raw HTML directly
- not proof that every browser/runtime feature is available everywhere
renderHTML(...) is mandatory on the final render output. Without it, the helper-generated class names can be present while the extracted CSS is missing from the returned UI string.
Practical rule:
- helper-composed styled output:
return helper.renderHTML(content) - plain manual JSX-like markup with no helper-generated styling: return the markup directly
Trusted Markup vs Safe Text
The DOM builder supports two different content models:- trusted markup composition: generic helpers like
DOM.createElement(...)acceptchildrenas already-formed JSX-like fragments - safe text composition:
DOMTextAPIs escape JSX-sensitive characters for text-node contexts
- use
DOMText.createText(...)and relatedDOMTextmethods for user-provided or untrusted text - use raw
childrenin generic DOM helpers only for trusted plugin-authored markup fragments
children strings as a security boundary by itself. Host-side iframe sandboxing and message validation remain the boundary.
Runtime Safety Rules
Safe Backend Assumptions
These are generally backend/runtime concerns:- registering handlers in
init() - reading or writing store data
- logging and diagnostics
- backend error handling
Safe Iframe/UI Assumptions
These are generally iframe/runtime concerns:- DOM interaction
- event listeners on
windowordocument window.createBackendReq(...)- goober styling used by rendered UI behavior
- UI-only injected libraries
Third-Party Imports In UI Runtime
render() and renderOnLoad() run inside the FDO iframe runtime wrapper. They are not a general npm module loader.
Rules:
- Do not assume arbitrary third-party
import/requireworks in iframe UI code. - Use only host-injected globals and helpers documented by FDO/SDK.
- If a new UI library is needed, it must be added by FDO host injection policy first, then documented.
Unsafe Assumption To Avoid
Do not assume iframe-injected libraries exist in:- plugin constructors
- class field initializers
- backend/bootstrap paths
- backend render-error fallbacks
- non-UI utility modules unless the current workspace explicitly proves that runtime
Error Handling Guidance
If you useerrorUIRenderer or any custom render fallback:
- keep it runtime-safe
- avoid depending on iframe-only helpers unless you know the fallback runs in the iframe UI path
- prefer simple fallback UI over brittle styling dependencies
Practical Authoring Guidance
- Use
init()for setup and handler registration. - Use
render()to provide UI for the iframe host pipeline. - Use injected
window.*helpers only from UI-facing code paths. - Use SDK DOM helpers when they match the existing workspace style.
- When DOM helpers generate styled output, call
renderHTML(...)on the final helper markup before returning fromrender(). - Do not move UI-runtime assumptions into backend/bootstrap code.
Guidance For AI Tools
When generating or refactoring FDO plugins:- do not describe the plugin UI as only “plain HTML”
- do not claim unrestricted React/Electron/Node access from UI code
- preserve the current workspace’s render convention
- distinguish backend runtime from iframe runtime explicitly