1. Core thesis
Non-negotiable principles
- Never depend on private Fiber or SlotTable layout. Connect at host mutation boundaries.
- Every subtree has one structural owner. React owns React islands; Compose owns Compose islands; Reaktor owns the kernel.
- Batch before crossing runtime boundaries. Avoid one JSI/native call per host mutation.
- Node identity is explicit and stable. NodeId survives renderer changes and powers profiling/tooling.
- ShadowTree is derived, not authoritative. Canonical HostTree is the source of truth.
- Low-level structures are hidden. Gap buffers, arenas, slabs, and dirty bitsets are internal implementation details.
2. Target architecture
Runtime layers
| Layer | Purpose | Core artifacts |
|---|---|---|
| Authoring frontends | Let users write idiomatic UI in React, Compose, Blueprint, or server-generated form. | React components, composables, visual graph nodes, server UI specs |
| Runtime adapters | Translate reconciler-specific host operations into Reaktor mutations. | React HostConfig, Compose Applier, Blueprint compiler |
| Mutation buffer | Collect, compact, validate, and flush structural/prop/event changes. | TreeOp, TreeMutationBatch, binary op stream, frame transaction |
| HostTree | Canonical retained tree with stable ids, props, event refs, ownership, and children. | Arena store, NodeRecord, ChildBuffer, PropsRef, EventRef |
| ShadowTree | Derived renderer-facing tree with layout/style/event caches and dirty propagation. | ShadowNode, DirtyFlags, LayoutBox, MeasureCache, hit-test tree |
| Renderers | Project shadow snapshots to platform UI. | Compose renderer, React renderer, DOM renderer, native renderer, inspector renderer |
| Tooling | Expose the tree to debugger, graph visualizer, perf traces, AI agents, and Blueprint editor. | Inspector protocol, trace spans, snapshot export, graph diff protocol |
Important internal handles
React Fiber host instance ┐
├── ReaktorHostNodeHandle(NodeId, BoundaryId)
Compose Applier node ────┘
NodeId
→ arena index + generation
→ HostNode
→ ShadowNode
→ optional renderer handle
→ inspector/profiler identity
Ownership island model
A parent runtime may mount or unmount a boundary, pass props/resources into it, and receive events from it. It should not directly mutate the child structure inside another owner’s boundary.
3. Build roadmap
The phases are ordered to minimize unknowns. The early goal is not product completeness; it is to prove the central performance path: React/Compose → batched ops → HostTree → ShadowTree → renderer.
Goal: eliminate dangerous assumptions before writing the kernel.
- Build tiny probes for
react-reconcilerHostConfig lifecycle: create, append, insert, remove, update, commit. - Build tiny Compose custom Applier sample that emits mutations into a fake tree.
- Read React Native Fabric concepts: ShadowTree, ShadowNode, surface, mounting, JSI, codegen.
- Define forbidden dependencies: no private Fiber field coupling, no SlotTable assumptions, no renderer-specific public APIs.
- Decide initial platform target: Android first, then iOS/Desktop, then Web.
Goal: build the canonical retained tree with stable identity and fast mutation.
@JvmInline value class NodeId(val raw: Long)
data class HostNode(
val id: NodeId,
var type: HostType,
var parent: NodeId?,
val children: ChildBuffer<NodeId>,
var propsRef: PropsRef,
var eventRef: EventRef?,
var owner: BoundaryId,
var flags: DirtyFlags
)
- Implement NodeId as index + generation to detect stale handles.
- Implement arena/slab storage with free list and generation counter.
- Implement adaptive child storage: empty, inline small array, gap buffer, chunked vector.
- Implement structural invariants: no cycles, valid parent pointers, boundary ownership.
- Implement immutable
TreeSnapshotview for tooling and tests. - Implement
TreeVisitorandTreeDiffprimitives.
Goal: make React, Compose, Blueprint, and server UI all speak the same low-level operation language.
sealed interface TreeOp {
data class CreateNode(val id: NodeId, val type: HostType) : TreeOp
data class InsertChild(val parent: NodeId, val child: NodeId, val index: Int) : TreeOp
data class MoveChild(val parent: NodeId, val from: Int, val to: Int, val count: Int) : TreeOp
data class RemoveChild(val parent: NodeId, val index: Int, val count: Int) : TreeOp
data class UpdateProps(val id: NodeId, val patch: PropsPatch) : TreeOp
data class DeleteNode(val id: NodeId) : TreeOp
}
- Implement
TreeMutationBatchwith validation, compaction, and deterministic application order. - Support frame transactions: begin, append ops, validate, commit, publish
TreeCommit. - Add op coalescing: repeated prop updates collapse; remove+insert can become move where safe.
- Add debug-mode invariant checks and release-mode fast path.
- Design binary encoding path, initially simple; leave room for FlatBuffers/FlexBuffers later.
Goal: prove Compose can author into the Reaktor HostTree.
class ReaktorApplier(
root: ReaktorComposeNode,
private val buffer: MutationBuffer
) : AbstractApplier<ReaktorComposeNode>(root) {
override fun insertTopDown(index: Int, instance: ReaktorComposeNode) {
buffer.insert(current.nodeId, instance.nodeId, index)
}
override fun remove(index: Int, count: Int) {
buffer.remove(current.nodeId, index, count)
}
override fun move(from: Int, to: Int, count: Int) {
buffer.move(current.nodeId, from, to, count)
}
override fun onClear() {
buffer.clearBoundary(current.boundaryId)
}
}
- Create primitive composables:
RText,RBox,RRow,RColumn,RButton. - Map composable calls to HostType + props + events.
- Support keys and boundary identity explicitly.
- Validate recomposition produces minimal op sequences.
- Build an Android demo where Compose-authored Reaktor UI updates the HostTree.
Goal: render HostTree through Compose while keeping ShadowTree as the renderer-facing derived structure.
- Define
ShadowNode: source id, resolved style, layout box, dirty flags, event mask, renderer handle. - Implement dirty propagation: structure, props, style, measure, layout, paint, semantics, events.
- Build Compose renderer for common primitives.
- Keep actual text input and platform components delegated to Compose initially.
- Expose snapshot inspection for visualizer/debug tools.
Goal: React components commit into the same HostTree through a custom renderer.
type ReaktorHostInstance = {
nodeId: number
boundaryId: number
type: string
}
const hostConfig = {
createInstance(type, props, root, hostContext) {
const id = ids.next()
buffer.create(id, type)
buffer.updateProps(id, encodeProps(type, props))
return { nodeId: id, boundaryId: root.boundaryId, type }
},
appendChild(parent, child) {
buffer.insert(parent.nodeId, child.nodeId, parent.childCount++)
},
commitUpdate(instance, updatePayload, type, oldProps, newProps) {
buffer.updateProps(instance.nodeId, encodePatch(type, oldProps, newProps))
}
}
- Start with a local JS-only renderer committing into a Kotlin/JVM or TS HostTree test implementation.
- Move to Android via JSI/TurboModule-style bridge after lifecycle is stable.
- Batch all host mutations until commit boundary; avoid per-op native calls.
- Support text nodes, prop diffing, event refs, refs, boundary roots, and unmount cleanup.
- Build demo: React-authored card rendered by Compose renderer through Reaktor HostTree.
Goal: replace slow JS/native serialization with a compact batched commit path.
- Implement C++/Kotlin native runtime boundary for mutation batches.
- Represent batch as ArrayBuffer or FlatBuffer-backed payload.
- Use generated prop encoders for hot primitives.
- Use opaque event handler ids instead of passing callbacks through every node.
- Build bidirectional event batch: native hit-test → event queue → JS/Compose owner boundary.
- Measure JSI commit overhead vs classic bridge-like JSON batch.
Goal: let React and Compose coexist safely in one tree.
- Implement
RuntimeBoundary: owner, root node, scheduler, event sink, resource scope. - Implement event delegation through ShadowTree hit-testing and EventRef routing.
- Implement shared
StateCellabstraction for durable shared state. - Support React island inside Compose parent and Compose island inside React parent.
- Forbid structural mutation across boundary ownership unless mediated by explicit commands.
Goal: coordinate cross-runtime work without pretending React and Compose have one scheduler.
enum class ReaktorPriority {
ImmediateInput,
Animation,
VisibleUi,
BackgroundUi,
Data,
Idle
}
- Map React-originated commits to Reaktor priorities.
- Map Compose invalidations/frame updates to Reaktor priorities.
- Introduce frame scheduler: input → animation → visible UI → background UI → data → idle.
- Add backpressure: large React batch should not starve input or animation work.
- Add offscreen/precompute support for hidden screens and upcoming routes.
Goal: turn Reaktor’s runtime tree into a product moat.
- Expose HostTree + ShadowTree snapshots through a debugger protocol.
- Show owner boundary, NodeId, props schema, dirty flags, layout bounds, event refs, and state dependencies.
- Export trace events compatible with Perfetto-style timelines.
- Build Blueprint editor integration: selected graph node maps to rendered bounds.
- Build AI-agent diff format: typed graph/tree commands, not text patches.
Goal: make it usable without exposing the internal complexity.
- Publish stable primitive set: layout, text, image, button, input, list, surface, navigation, modal.
- Create docs for authoring in Compose, authoring in React, and mixed ownership islands.
- Add migration guide for Reaktor apps: native Compose first, React dynamic islands second.
- Write a “performance contract” page documenting what is stable, internal, and experimental.
- Build sample apps: chat screen, feed, form builder, visual editor, dynamic campaign card.
4. Proposed module map
| Module | Responsibility | Stability target |
|---|---|---|
reaktor-tree-core |
NodeId, TreeOp, TreeCommit, TreeSnapshot, visitors, boundaries. | Stable early |
reaktor-tree-storage |
Arena/slab, adaptive child buffers, gap/chunked storage, invariant checks. | Internal |
reaktor-ui-core |
HostType, PropsRef, EventRef, StateCell, ResourceRef, CapabilityRef. | Stable after primitive set settles |
reaktor-ui-shadow |
ShadowNode, DirtyFlags, style resolver, hit-test, layout metadata. | Mostly internal |
reaktor-ui-compose-applier |
Compose authoring frontend that emits Reaktor host mutations. | Experimental |
reaktor-ui-compose-renderer |
Renderer that projects Reaktor tree to Compose UI. | Stable for Android first |
reaktor-ui-react-renderer |
Custom React renderer HostConfig for Reaktor runtime. | Experimental due to react-reconciler instability |
reaktor-ui-jsi |
JSI/native bridge, ArrayBuffer/FlatBuffer batch transport, event return path. | Internal/experimental |
reaktor-ui-devtools |
Inspector protocol, snapshot viewer, performance trace export. | Public tooling |
reaktor-ui-benchmarks |
JMH/macrobenchmark/Perfetto/JS benchmarks and regression suite. | Internal but visible |
5. Internal data structures
Arena-backed nodes
Use NodeId as index + generation. This gives compact storage, low allocation churn, stale-handle detection, and cache-friendlier traversal than object graphs.
Adaptive child buffers
Do not use one child list strategy everywhere. Small array for small nodes, gap buffer for clustered insert/remove, chunked vector for large mutable lists, virtual children for feeds.
Props table
Use schema-aware props with patches. Hot primitives should use generated encoders. Avoid generic maps in hot paths.
Dirty bitsets
Dirty flags should distinguish structure, props, style, measure, layout, paint, semantics, and events. This is the key to avoiding full-tree recomputation.
ChildBuffer strategy
| Use case | Structure | Reason |
|---|---|---|
| 0 children | EmptyChildren |
No allocation. |
| 1–4 children | InlineChildren |
Avoid heap churn and indirection for common simple nodes. |
| 5–128, local edits | GapChildren |
Good for clustered insert/remove/move around a local index. |
| Large dynamic subtree | ChunkedChildren |
Better for large lists and non-local edits. |
| Feed / lazy list | VirtualChildren |
Do not materialize every row as a full node when not visible. |
Dirty propagation matrix
| Change | Dirty flags | Renderer effect |
|---|---|---|
| Text content changed | Props, measure, layout, paint, semantics | Re-measure text and parent layout if size changed. |
| Color changed | Props, paint | No layout recomputation. |
| Child inserted | Structure, layout, semantics, hit-test | Parent and affected ancestors may re-layout. |
| Event handler changed | Events | Update event routing only. |
| Visibility changed | Style, layout, paint, hit-test, semantics | May remove from hit-test/accessibility tree. |
6. Benchmark and profiling plan
Benchmarks should be built before the React and JSI paths are “done”. Otherwise the API will ossify around unmeasured assumptions.
| Benchmark | Scenario | Primary metric | Why it matters |
|---|---|---|---|
| Structural mutation | Insert/move/remove 10k nodes under mixed parents. | ops/ms, allocation count | Tests HostTree + ChildBuffer design. |
| Prop patch | Patch style/text/event props on 50k nodes. | bytes/op, ns/op | Tests schema-aware props and dirty flags. |
| React batch commit | React renders dynamic feed updates through custom renderer. | JS time, native commit time, frame misses | Tests HostConfig and batch boundary. |
| Compose applier commit | Compose recomposes list/form updates into HostTree. | recomposition time, commit time | Tests Compose adapter efficiency. |
| Mixed ownership screen | Compose parent with React card island and server modal. | event latency, frame stability | Tests boundary routing and scheduler priorities. |
| Cold start / mount | Mount a 1k-node screen from React and Compose. | time to first useful render | Critical for app UX. |
| Text editing | TextField and rich text buffer operations. | input latency, memory | Tests gap/piece/rope strategy where actual text is involved. |
Instrumentation checklist
- Add trace slices for
ReactCommit,ComposeApply,TreeStoreApply,ShadowDirty,RenderCommit, andEventDispatch. - Expose NodeId and BoundaryId in traces.
- Count allocations and bytes moved per mutation batch.
- Keep golden benchmark screens in CI.
- Use Android Macrobenchmark + Perfetto for device-level traces.
- Use JMH or Kotlin benchmarks for HostTree and ChildBuffer microbenchmarks.
7. Study plan
This is organized as a build-aligned curriculum. Each track has a reason: learn only what directly improves the runtime.
12-week focused plan
| Weeks | Study | Build artifact |
|---|---|---|
| 1–2 | React reconciliation, keys, custom renderer HostConfig, commit phase, renderer examples. | Tiny React renderer that logs create/insert/remove/update ops. |
| 2–3 | Compose runtime, Applier, Composer, custom node trees, snapshot state, recomposition basics. | Tiny Compose Applier that logs tree operations. |
| 3–4 | Arenas, generational ids, gap buffers, chunked vectors, persistent snapshots. | Arena HostTree with adaptive child buffers. |
| 5 | Dirty propagation, retained UI trees, shadow tree responsibilities. | ShadowTree v0 with dirty flags and snapshot export. |
| 6 | Compose layout/performance, modifier nodes, text/input boundaries. | Compose renderer for basic primitives. |
| 7–8 | React Native New Architecture, Fabric, JSI, TurboModules, Codegen, ShadowTree concepts. | JSI-backed batch commit prototype. |
| 9 | Yoga/Flexbox layout, measurement, hit-testing, accessibility tree basics. | Hit-test + event routing through ShadowTree. |
| 10 | Perfetto, Android tracing, OpenTelemetry concepts, flame charts. | Trace export for mutation/layout/render pipeline. |
| 11 | FlatBuffers/FlexBuffers and schema evolution. | Binary mutation batch format v0. |
| 12 | DevTools Protocol ideas, inspector APIs, graph visualization UX. | Mini inspector showing HostTree + ShadowTree live. |
Deep study tracks
React internals
- Reconciliation and state preservation
- Custom renderers with HostConfig
- Commit vs render phase
- Refs, text nodes, event systems
Compose internals
- Runtime and compiler relationship
- Applier and custom target trees
- Snapshot state and recomposition
- Layout, modifier nodes, performance
Native runtime
- C++/Kotlin boundaries
- JSI and ArrayBuffer transport
- Arenas and memory ownership
- Platform event loops
Data structures
- Gap buffers
- Ropes and piece tables
- Chunked vectors/B-trees
- Generational arenas
Rendering/layout
- Shadow trees
- Yoga/Flexbox
- Text measurement
- Hit-testing and accessibility
Tooling/perf
- Perfetto traces
- OpenTelemetry concepts
- Chrome DevTools Protocol ideas
- Regression benchmark design
8. Learning resources and references
Official / primary resources
| Topic | Resource | What to extract |
|---|---|---|
| React reconciliation | React docs: Preserving and Resetting State | Identity, position, key semantics, state preservation. |
| React custom renderer | react-reconciler package and React reconciler README | HostConfig, experimental status, renderer boundary design. |
| React Native New Architecture | React Native architecture overview | JSI, bridge removal, C++ references, native interop principles. |
| Fabric renderer | React Native Fabric docs | ShadowTree, C++ render logic, host interoperability. |
| RN glossary | React Native architecture glossary | Surface, ShadowTree, ShadowNode, host component vocabulary. |
| Turbo Native Modules | React Native Turbo Native Modules | Typed specs and codegen approach for JS/native APIs. |
| Fabric Native Components | React Native Fabric Native Components | How RN exposes native host components in the new architecture. |
| Compose runtime | Compose Runtime release/API page | Runtime scope, compiler target, state/composition building blocks. |
| Compose Applier | Applier API reference and Applier source | Custom target tree operations and operation ordering. |
| Compose performance | Jetpack Compose performance docs | Recomposition, layout, draw phases, optimization rules. |
| Compose Multiplatform | Compose Multiplatform | Cross-platform renderer target strategy. |
| Yoga layout | Yoga docs and Yoga GitHub | Portable layout engine boundaries: computes boxes, does not draw UI. |
| FlatBuffers | FlatBuffers docs and schema evolution | Zero-copy-ish access, schema evolution, binary batch design. |
| Perfetto | Perfetto docs and Android Perfetto tool | System/app tracing for performance root-cause analysis. |
| OpenTelemetry | OpenTelemetry | Tracing vocabulary and vendor-neutral instrumentation model. |
| DevTools protocol | Chrome DevTools Protocol | Inspector protocol inspiration: domains, methods, events, tracing. |
High-value non-official resources
| Resource | Use it for |
|---|---|
| React Fiber Architecture by Andrew Clark | Mental model of Fiber as units of work and reconciliation as renderer-agnostic tree updating. |
| Awesome React Renderer | Examples of custom renderers and learning paths. |
| Craft of Text Editing — Buffer Gap | Classic explanation of gap buffers and text editor internals. |
| Gap Buffers vs Ropes | Tradeoffs for local vs non-local editing patterns. |
| Data Structures for Text Sequences — Charles Crowley | Canonical comparison of sequence data structures for text. |
Books worth reading alongside implementation
- Designing Data-Intensive Applications — for logs, materialized views, snapshots, and state/dataflow thinking.
- Game Engine Architecture — for runtime loops, memory ownership, update pipelines, and engine modularity.
- Data-Oriented Design — for cache behavior, layout of data, and batch-oriented runtime design.
- Computer Systems: A Programmer’s Perspective — for memory, linking, runtime, and performance fundamentals.
- Crafting Interpreters — for VM/runtime thinking and readable implementation style.
- Engineering a Compiler — useful later if Reaktor’s Blueprint/DSL compilers become central.
9. Risks and mitigations
| Risk | Why dangerous | Mitigation |
|---|---|---|
| Coupling to React Fiber internals | Fiber internals are not a stable public integration surface. | Only depend on custom renderer HostConfig boundary; isolate in experimental module. |
| Coupling to Compose SlotTable internals | SlotTable is compiler/runtime implementation detail. | Use Applier and public runtime APIs only. |
| Two runtimes mutate same subtree | Breaks identity, state preservation, lifecycle, and event ownership. | Strict RuntimeBoundary ownership; cross-boundary mutations become commands. |
| Abstracting layout too early | Text/input/layout correctness is a huge project. | Let Compose own actual layout/rendering in v1; build Reaktor layout later. |
| Overusing binary serialization too early | Can slow iteration and harden the wrong protocol. | Start with clear object model; add binary batch after mutation semantics stabilize. |
| Tooling delayed until too late | Runtime complexity becomes invisible and hard to debug. | Build snapshot export and trace events from Phase 2 onward. |
| Public API leaks internal structures | Locks you into gap buffers/arenas/dirty flags forever. | Expose semantic primitives, commands, and snapshots; keep storage internal. |
10. Codex-ready implementation tasks
Task group A — HostTree kernel
- Create
NodeIdas packed index + generation. - Create
ArenaTreeStorewith allocate/free/get/validate. - Create
ChildBuffer<T>interface with inline, gap, and chunked implementations. - Create
TreeOp,TreeMutationBatch, andTreeCommit. - Add property-based tests for random insert/remove/move/delete.
- Add JMH benchmark for child buffer strategies.
Task group B — Compose path
- Create
ReaktorComposeNodehandle with NodeId and BoundaryId. - Implement
ReaktorApplieremitting TreeOps. - Create composables for RText/RBox/RRow/RColumn/RButton.
- Build test composition that updates state and verifies minimal mutation batch.
- Build Compose renderer consuming HostTree snapshots.
Task group C — React path
- Create local JS
reaktor-react-rendererusingreact-reconciler. - Implement HostConfig for create/append/insert/remove/update/text.
- Add prop diffing and event handler id registration.
- Add batch flushing at commit boundary.
- Build demo rendering React-authored UI into in-memory HostTree.
Task group D — JSI and performance
- Define batch binary format v0.
- Implement ArrayBuffer-based native commit API.
- Implement event batch return path.
- Add trace events for every major pipeline stage.
- Compare JSON batch vs binary batch vs direct in-process test path.
Task group E — Tooling
- Create snapshot JSON export with nodes, children, props schema ids, owner boundaries, and dirty flags.
- Create visual tree inspector page.
- Create trace export viewer integration.
- Create graph projection: HostTree nodes → Reaktor graph nodes and edges.
11. Final recommended north star
The first visible win should be a small but impressive demo:
This is narrow enough to ship, deep enough to prove the moat, and aligned with Reaktor’s larger thesis: typed graph/runtime infrastructure where code, UI, state, events, tools, AI agents, and deployment all meet.