feat: add render graph driven renderer architecture

Amp-Thread-ID: https://ampcode.com/threads/T-019f9d91-77c1-7206-a60f-ed6554ce92ab

Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-07-27 02:53:44 +00:00
co-authored by heaust
parent 8a8369706b
commit d4e8634f67
290 changed files with 48804 additions and 1995 deletions
+5
View File
@@ -0,0 +1,5 @@
# Accessibility
The editor is bitmap Canvas. Its nodes, sockets, labels, controls, and relationships are not semantic accessibility-tree objects. fxnode makes **no WCAG conformance claim** and must not be the only interface when assistive access is required.
Applications should provide an equivalent semantic DOM workflow, status announcements, instructions, and controls. Keyboard support alone is not accessibility. An application-owned DOM add-node dialog can implement combobox/listbox semantics and focus restoration, but that does not make the Canvas editor accessible.
+20
View File
@@ -0,0 +1,20 @@
# Browser host
`createFxNode` needs application identity/version, resource policies, and optionally a worker URL/history limit. It creates the shared root and worker without a canvas. `root.attachView()` needs a canvas, logical CSS-pixel viewport plus DPR, and optionally an initial camera. The **application owns the DOM and canvas dimensions**. Before attachment, measure the initial layout and set the backing dimensions. For a runtime resize, await `view.setViewport(next)` before updating the backing dimensions. Attach and remove your own listeners.
fxnode creates one module worker per root and never creates a resize observer, menu, modal, or file picker. A root may have no views or multiple views. Convert pointer/keyboard/wheel events to each view's `feedInput()` values. The worker performs authoritative hit testing and may issue view-scoped `add-node-menu` or `resource-open` host requests; your DOM decides presentation and ordering.
Use root methods for composition, state, persistence, subscriptions, and context-free commands. Use view methods for input, viewport changes, selection actions, resource responses, and render checkpoints. A canvas can have only one live view. On teardown, remove application listeners and observers first, detach each view, then destroy the root.
The repository example host defaults to `lifecycle: "explicit"`, so `host.destroy()` removes only host-owned policy
and never detaches its view. Opt in with `lifecycle: "detach-on-disconnect"` for component-style examples. That mode
requires an initially connected canvas and `MutationObserver`; hosts share one observer per document. A removal is
confirmed in a microtask (so a same-task remove/reinsert or reparent survives), then host resources are synchronously
removed before `view.detach()` is requested. Moving the canvas to another document counts as disconnection; hiding it
or giving it zero layout size does not.
Resize observations are coalesced while a viewport request is in flight. The host updates canvas backing dimensions
only after `setViewport()` acknowledges that request. A rejection preserves the prior backing store, reports through
`onError`, and a later observation can retry.
Install composition before initial state. Imported/historical data belongs in `load()`, not `setState()`. See [state and persistence](../concepts/state-and-persistence).
+7
View File
@@ -0,0 +1,7 @@
# Browser support
The certified functional matrix is Chromium and Firefox from Playwright 1.61.1 on desktop Linux. Chromium image goldens are regression tests, not cross-engine or Blender parity tests. WebKit, Safari-branded builds, and mobile are not certified.
The main thread requires module `Worker`, `crypto.randomUUID`, and Canvas 2D. The worker requires `OffscreenCanvas`, a 2D context, cropped `createImageBitmap`, and `ImageBitmap.close`; there is no fallback. Named capability errors identify missing features.
Cross-origin isolation enables an optional `SharedArrayBuffer` pointer lane per view. Without it, normal `postMessage` transport remains functional. Limits include 16 attached views, DPR 4, 8192 logical pixels per dimension, 16,777,216 device pixels per view, 67,108,864 device pixels across all views, and history 1,000.
+5
View File
@@ -0,0 +1,5 @@
# Content Security Policy
fxnode starts a same-origin ES module worker with no blob, classic-worker, or main-thread fallback. Permit it with an appropriate `worker-src 'self'` and `script-src`, and serve JavaScript with the correct MIME type. Pass `workerUrl` if assets move independently.
For optional shared-memory input use `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp`; embedded cross-origin resources must satisfy CORS or CORP. Otherwise fxnode automatically uses messages. Worker construction blocked synchronously reports `worker.construct`; a worker script/network/module load failure reports `worker.load`; failure to complete startup in time reports `worker.timeout`. None silently downgrade to main-thread execution.
+5
View File
@@ -0,0 +1,5 @@
# Integration guides
For browser/platform engineers turning an editor bootstrap into a production integration. Start with the [browser host](./browser-host), then wire [interactions](./interactions) and [rendering and lifecycle](./rendering-and-lifecycle). These establish canvas ownership, input forwarding, render checkpoints, and teardown.
Before release, review [browser support](./browser-support), [CSP](./csp), and [accessibility](./accessibility), in that order. The outcome is a host with explicit capability fallbacks, deployable worker policy, accessible DOM-owned controls, and no leaked listeners or workers.
+7
View File
@@ -0,0 +1,7 @@
# Interactions
The host translates DOM input; the worker owns gesture state and commits. Supported editor gestures include movement, box selection, resize, link creation/replacement, Ctrl-right-click cutting, Ctrl-Alt-right-click muting, `M` node mute, `H` collapse, `G` modal move, and undo/redo.
Plain right-click on eligible empty canvas may request an add-node menu. The host owns its HTML, search, grouping, focus, dismissal, and calls `addNode`. Controls follow composition `ui` order. Scrubbing modifiers, text commit/cancel, and reset all become atomic commands.
Do not derive behavior from projected pixels or retain a parallel graph. Subscribe to committed events when application UI needs updates.
@@ -0,0 +1,7 @@
# Rendering and lifecycle
Each view's `whenRendered()` synchronizes a frame for that view. Attach another view when a second canvas needs an independent camera or selection over the same graph; graph mutations schedule all attached views. Set the initial backing dimensions before attachment. At runtime, await that view's `setViewport(next)` first, then update the host canvas backing dimensions.
Keep every listener, observer, menu, and focus behavior in an application-owned cleanup object. On unmount/page teardown, remove those resources, await `view.detach()` for each view, then call `root.destroy()`. The example browser host keeps this explicit by default; its opt-in disconnect policy can perform host cleanup and request detachment when a connected canvas is removed. Destroying that host alone never detaches. View detachment is idempotent and rejects subsequent view work with `FxNodeViewDetachedError`. Root destruction is idempotent, detaches all remaining views, and makes pending and future work reject with `FxNodeDestroyedError`. Fatal startup/protocol failures also release resources and make future calls reject the stored terminal error.
Do not use rendering completion as graph execution completion: fxnode never executes graphs.