Renderers and interop

15. The Compose-first implementation

15.1 Keep the first production path direct

The first implementation should make ordinary CMP applications better without requiring React, a custom portable tree or a binary bridge. A Compose screen consumes a graph binding, enters a theme scope and calls Surface's Compose components. Those components combine shared contracts and behavior with Compose's existing rendering facilities.

This path is deliberately short. A local button call does not need to encode its properties and decode them again in the same runtime. A normal form does not need an arena-backed tree merely to use shared semantics. The design's portability is established by its contract boundaries, not by forcing every local operation through the most general transport.

A component can nevertheless be registered in the shared catalogue. Its definition, behavior traces and recipe roles are the same definitions used by React web. Direct execution is an implementation optimization, not a separate design system.

15.2 The adapter translates, rather than owns, application behavior

A Compose button adapter has a small sequence of responsibilities. It observes the current controlled properties. It maps toolkit interaction into the behavior kernel. It resolves the component's appearance from the effective theme. It renders through an appropriate Compose implementation. Finally, it forwards admitted semantic events to the bound application action.

The adapter does not start a conversation session because it drew a Send button. That session belongs to the graph feature. It does not maintain a private theme registry unrelated to the surrounding scope. It does not make network success depend on animation completion.

A representative direct call can remain simple:

The surface functions in this example belong to the direct CMP package. The product-level function receives already-bound models and actions. A portable package may provide corresponding declarations, but it cannot silently mix the two applier vocabularies inside one composition.

15.3 Remember identity, not every changing input

A component's interaction controller should survive ordinary property updates. Recreating it when the color changes would erase contact or focus state unnecessarily. Conversely, reusing the same controller for a different logical item can transfer state into the wrong control.

Stable component or collection keys determine identity. Controlled properties are supplied to the controller as changing inputs. A callback retained by an effect needs access to the latest applicable properties and handler rather than an old captured value. Compose's effect APIs distinguish effect lifetime from values that can change during that lifetime. 23

For example, disabling a pressed button should reconcile the active contact state. The controller should not be recreated solely to make that happen. The new properties enter the reducer, which cancels contact under its defined policy.

Theme data also remains an input. A recipe change updates visual resolution; it does not reset the behavior kernel unless the component implementation itself is being replaced under an explicit policy.

15.4 Observe work at the right phase

Compose separates composition, layout and drawing. A state read can affect the phase in which it occurs, so the placement of rapidly changing reads matters. 5

Figure 27. A structure change can require composition. A constraint change affects layout. A decorative face translation may be realized later in the pipeline. The adapter should measure which work actually occurs instead of treating all updates as the same kind of rerender. See the illustrated PDF for the diagram.

The direct CMP implementation can use this separation to keep tactile work local. A press response that only changes a graphical transform need not rebuild the full product layout each frame. A field whose text size changes does require measurement. The contract identifies the intent; the adapter chooses the correct phase-sensitive mechanism.

Avoid reading a whole animation state object in a high-level screen simply to pass one scalar to a child. Such a dependency can widen invalidation. Keep the observation near the component or layer that consumes it, while preserving a readable API.

15.5 Toolkit behavior remains valuable

Foundation and Material implementations provide useful control behavior. Surface can wrap or adapt them rather than replacing every control with a raw Box and pointer detector. A mature input field is especially valuable because text editing has complex platform interactions.

A custom tactile treatment may need a new visual structure while reusing the established semantic and interaction paths. The contact plate can be decorative. The face can animate locally. The control still has one activation and one appropriate focus target.

This is an important form of elegance: the framework shares the rules that must be shared and delegates machinery that a platform already implements well. A renderer-neutral behavior kernel does not require a renderer-neutral replacement for every native text service.

15.6 Compose-first must not mean Compose-shaped contracts

The easiest accidental coupling is to put Compose value types into public shared contracts because the first implementation uses them. That creates work later when React web is introduced. Neutral component inputs, semantic roles and resource identities must therefore be established before a large catalogue is built.

The same is true of lifecycle. A shared component contract should not mean “close the feature when this composable leaves composition.” It should describe the feature's state ownership and the view's attachment separately.

A small portability probe is useful before API freeze. It can take the same component definition, serialize and validate representative values, and run the behavior trace without Compose. A tiny custom-Applier experiment can verify the host vocabulary. Neither requires postponing production CMP components until a complete React renderer exists.

16. HostApplier and the portable composition boundary

Coherent host publication

HLD — Arrows follow candidate validation and coherent publication. The custom host is a proposal, not existing Recipe.kt behavior. Open the diagram for full size.

16.1 Why an Applier exists

Compose Runtime can maintain a tree without requiring that the tree be a Compose UI layout tree. The Applier interface is the bridge that applies operations to the chosen kind of tree. It tracks a current node, traverses down and up, inserts and removes children, moves them and applies changes. 28

A SurfaceHostApplier is an application of that idea to Surface's portable host vocabulary. It creates host nodes describing registered components and their typed properties. A renderer then realizes those nodes through CMP or React web.

The Applier is not a UI designer. It does not decide whether a page should be list-detail or which action is destructive. Composition and contracts already express those decisions. The Applier records structural operations correctly and hands committed output to the host boundary.

Figure 28. Direct Compose uses the normal Compose UI path. Portable Surface composition uses a custom Applier to produce a different kind of node tree. The two paths share contracts but do not use interchangeable internal nodes. See the illustrated PDF for the diagram.

16.2 A tiny tree makes the idea concrete

Consider a portable group containing two text nodes. The composition runtime determines that the group should exist and that one text value changed. The Applier receives operations describing the resulting tree changes. It does not need to interpret the original Kotlin conditional that selected those values.

An internal mutable node can be simple at first:

The production representation can evolve after measurement. An arena or specialized child buffer may eventually be useful, but is not required to understand ownership or validate the initial contract.

The node is local to the composition owner. It is not a graph node with an application coroutine scope. Its key relates it to the committed presentation instance and inspector, while its parent-child links describe UI structure.

16.3 Insert, move and remove are distinct operations

If a list changes from A, B, C to A, C, B, B should usually retain its identity. Recreating every row would lose useful local state and resource relationships. A move operation expresses the change without treating B as a different component.

Figure 29. The Applier applies a structural move while preserving the moved instance's key. That key is an identity within the owning boundary, not merely its current index. See the illustrated PDF for the diagram.

The AndroidX interface exposes both top-down and bottom-up insertion hooks, with insertion performed in one of them rather than both. Its move destination uses the specified pre-change index convention. A Surface implementation must follow those semantics exactly; guessing at index adjustments can corrupt reordered trees. 28

A minimal adapter can use a mutable list and explicit range operations. Tests should include adjacent moves, moving several siblings, removal of a subtree and repeated clear operations. These tests are more valuable early than choosing a sophisticated data structure before the semantics are stable.

16.4 Publication belongs at a coherent boundary

The privately owned desired tree can be updated while changes are being applied. Other consumers must not observe arbitrary intermediate combinations. Once the frontend has committed its changes, the host validates and publishes a coherent representation.

Immutability needs more than a val declaration. A val prevents reassignment of that reference, but a referenced byte array can still change. A read-only collection interface also does not prove that no other owner can mutate its backing storage. Publication therefore freezes or copies the relevant values, or transfers them under an explicit ownership contract. The simplest baseline copies committed property bytes; a leased low-copy representation can be introduced after its lifetime rules are established.

The initial representation can publish a full immutable snapshot. The sink checks the owning mount, generation, catalogue and base sequence before accepting it. More compact operation batches can be introduced later without changing the ownership model.

If publication fails, the last valid displayed tree remains. The frontend and host must then resynchronize or fail the affected boundary through its recovery path. It is unsafe to continue as though rejected changes had been accepted, because the frontend's desired structure and the displayed structure would have diverged.

16.5 The committed host tree is not the source program

The host tree records what has been committed, not why the program produced it. A Kotlin loop may produce ten rows. A React conditional may omit a panel. The output alone does not recover those source-level constructs.

This matters for Blueprint. A supported template declaration can be edited visually because it has an authoritative editable representation. Arbitrary source-owned composition is inspected and linked back to its source. A visual operation must be routed through that owner rather than silently changing the runtime output behind its back.

The same distinction prevents two structural writers. The Applier owns the tree it produces. A parent can mount a boundary and update its inputs. It cannot independently reorder the boundary's internal children while the Applier continues producing another order.

16.6 Derived renderer data remains derived

A renderer may need additional structures: resolved styles, layout metadata, dirty flags, hit-test indexes and accessibility projections. These can be derived from the committed host structure and runtime inputs.

They must not become a second editable authority. If a layout cache disagrees with the committed structure, the cache is invalidated or rebuilt. A full recomputation path provides a useful reference for testing incremental derivation.

This keeps optimization separate from meaning. A browser realization may use structures quite different from a native realization. Both can satisfy the same host contract without requiring an unnecessary C++ round trip or identical memory layout on every target.

17. React web/JS realization

17.1 What reconciliation contributes

React components calculate an element structure from props and state. Reconciliation relates that output to the previous structure so that the renderer can preserve appropriate identities and apply necessary changes. Commit is the point at which the resulting changes are applied to the host. 4

The useful analogy is editing a document rather than replacing the entire document file after every keystroke. Some content changes, some moves, and much remains the same. Keys help distinguish an existing item from a new occurrence occupying the same position.

Figure 30. Reconciliation preserves the identity of keyed items while finding structural changes. Host publication follows the frontend's commit boundary, not every speculative calculation. See the illustrated PDF for the diagram.

A React render is not a business transaction. Evaluating a component must not send a message simply because the Send control appears. That operation belongs to the event path. The same restriction applies to tactile cues: rendering a pressed appearance is not another contact event.

17.2 The React web / JS realization

The renderer target is React on the web in a JavaScript runtime. Manna's adapter uses Kotlin/JS and JetBrains kotlin-react wrappers to realize Surface components as ordinary React/DOM controls. Compile the pure Kotlin kernels for JS; lower typed recipe values into renderer-generated styles. There is no requirement to adopt React Strict DOM, React Native or react-reconciler.

This path does not need the custom React-to-Compose bridge. It adds a renderer implementation behind already-established contracts. The graph binding supplies state and actions, and the component receives only the capabilities it needs.

The state adapter must preserve snapshot identity when data has not changed. React's external-store interface provides an explicit subscription and snapshot contract. Surface's graph-to-React bridge should obey that contract rather than producing a fresh logically identical object on every read. 29

A resolved model can use narrow projections so that one changing item does not require every sibling to observe a new application-root object. Coherent multi-field changes still need an aggregate revision where consistency matters.

17.3 Sharing behavior across languages

The pure Kotlin behavior kernel can be compiled for a supported JavaScript target and exposed through generated wrappers. This avoids manually reinterpreting the machine in every component implementation.

Where a renderer uses a native behavior facility instead of calling the same kernel for every event, it must still satisfy the same contract traces. Shared source and shared observable behavior are related but not identical promises. A native field should keep its competent editing implementation while exposing the agreed value and event semantics.

Platform event normalization remains target-specific. Mouse events, touch interactions, keyboard activation and accessibility actions reach the semantic control differently. The React web adapter is responsible for mapping them once, preserving native control behavior and avoiding duplicate activation.

This is why the behavior kernel was separated before building the CMP catalogue. React adoption becomes an adapter and conformance project rather than a rewrite of the component's meaning.

17.4 One Kotlin composition with two realizations

The proposed Surface composition adapter depends on Compose Runtime and neutral contracts, not Compose UI. It publishes the portable component tree. Kotlin React subscribes to a cached immutable snapshot and maps registered node types to DOM controls. The CMP host maps the same vocabulary to existing Compose controls. Each runtime owns its local focus, IME, measurement, drawing and resource handles.

Only the composition owner changes structure. Neither React nor the CMP realization writes a competing screen tree or business store. Stable component keys, entry identity and mount generation scope handler lookup and retirement. This preserves one authored Manna screen while keeping target mechanics local.

React render evaluation is free of product effects. Attach and dispose engine resources at the committed lifecycle boundary; development remounts must not duplicate commands. A tentative render cannot register live callbacks against another active generation. DOM refs and Compose focus handles remain local.

This path uses public React APIs and a custom Compose Runtime Applier; it is not a custom React renderer. React Native and React-to-CMP reconciliation are deferred. The historical RSD investigation remains evidence about a web experiment, not a mandatory dependency or an additional target.

17.5 Bounded commits and event delivery

Separate observation from ordered intent

HLD — Observations and commands have different queues. Local pointer sampling must not become a structural commit per frame. Open the diagram for full size.

Figure 31. Validation precedes publication. Generation, base revision, types, slots and ownership must agree before a candidate tree becomes the interactive representation. See the illustrated PDF for the diagram.

Host values identify the mount and active generation. A stale callback or old structural commit cannot be applied to a newly activated region that happens to reuse a local numeric handle. The runtime resolves those handles only within their valid ownership scope.

Not all events have the same delivery semantics. A latest hover position can replace an older unconsumed hover position. A text edit or submission cannot be discarded under that policy. Ordered application operations and coalescible presentation observations need different queues and explicit backpressure.

A large collection also needs a collection contract rather than a huge serialized tree. The host can materialize demanded items with stable identities while local scrolling remains responsive. A faster codec does not make an unbounded amount of rendering work acceptable.

17.6 What minimal-change React support really means

Minimal change means the shared component definitions, behavior rules, theme roles, graph bindings and lifecycle model do not need to be redesigned. It does not mean the React web renderer appears automatically.

The React implementation still needs DOM controls, style lowering, resource integration, subscriptions, focus and accessibility testing. Manna also needs the portable composition host and its two leaf realizations. Existing TypeScript adapters and a local web probe do not establish this capability.

Direct CMP consumers can ship without the portable host. Manna's one-screen requirement makes both realizations mandatory in K1, before porting more features. Production breadth expands only after the new realization satisfies the same component and workflow obligations as the existing one. The Manna benchmark records the current gaps and required proof slices.