Contracts and interaction

7. The small set of interfaces that holds the system together

Component definition and conformance

HLD — Definitions constrain both realizations. Conformance traces test meaning; they do not require identical pixels. Open the diagram for full size.

7.1 An interface is useful when it marks a real boundary

An architecture can become harder to understand when every implementation detail receives a new interface. The purpose of Surface's core interfaces is narrower. Each one separates responsibilities that need different owners, different implementations, or independent tests.

ComponentContract separates the meaning of a component from its realization. BehaviorKernel separates interaction decisions from toolkit event handling. GraphBinding separates a screen's required application capabilities from unrestricted access to the application graph. PresentationPolicy separates pane arrangement from navigation ownership. ComponentRecipe separates appearance from behavior. A renderer adapter brings these results together without becoming the owner of all of them.

Other boundaries concern execution. A SurfaceSession owns one mounted lifetime. A HostCommitSink accepts portable structure when a cross-authoring path requires it. TactileField coordinates local physical response. Resource and observation interfaces control assets and measurements. These are not independent frameworks; they are the seams between the parts of one framework.

Figure 9. The central interfaces form a dependency structure. Application bindings, semantic contracts and theme resolution are inputs to local presentation. Rendering consumes their results; it does not replace their owners. See the illustrated PDF for the diagram.

The interfaces below are architectural contracts. Concrete production packages can refine their naming and allocation strategy while retaining the ownership and behavioral rules. Platform-specific annotations and objects belong in adapters, not in the shared signatures.

7.2 Component meaning: ComponentContract

A component definition describes the values accepted by a component and the events it can emit. The type parameters separate properties P from events E. A field's current value is a property; a requested edit is an event. A button's enabled state is a property; activation is an event.

A stable component identity allows generated tools and renderers to agree on the same component. The version identifies a compatible contract family. The codecs describe boundary values, but using the component locally does not require encoding them. Direct Compose calls can pass typed Kotlin values without a byte buffer.

Slots describe the component's composition surface. Semantic obligations describe requirements such as an accessible name or a supported activation action. The behavior version records changes whose significance cannot be captured by field shape alone. A field that changes when it commits a value can break an application even if its property names remain unchanged.

7.3 Toolkit-independent behavior: BehaviorKernel

The behavior kernel is a pure state transition. It receives controlled properties, current local state and normalized input. It returns the next state, semantic events and feedback cues.

The kernel does not draw, start coroutines, call services or vibrate the device. It describes what should happen. The adapter decides how to execute its outputs in the owning runtime. This makes it possible to test a press-and-cancel sequence without launching a window.

Pure reduction does not mean every subsystem has to copy a large immutable object every frame. The public contract defines observable behavior. Hot implementations may use specialized storage internally, provided they preserve that behavior and expose coherent snapshots to tests and observers.

7.4 Application integration: GraphBinding

A mounted feature needs a view of application state and a set of operations. It should not discover those operations through arbitrary global lookups inside its atoms.

S is a presentation model derived from a graph-owned capability. A is a typed action interface, such as conversation operations. SurfaceAddress identifies the owner, optional route entry and mount. generation identifies the active implementation binding, not the user's message revision or the selected theme.

The binding is a projection of existing graph-owned ports. It is not another service registry. Its model remains observable, and its actions still enter the application's authoritative checks. A permission being valid when the binding was created does not authorize every future invocation indefinitely.

A versioned model is useful when several values must be read coherently. The title, selected item and available actions can refer to one application revision rather than exposing intermediate combinations from a transaction.

7.5 Appearance and arrangement

ThemeResolver turns a selection and environment into one checked snapshot. ComponentRecipe turns the relevant properties, local state and theme into an appearance specification. The appearance specification is neutral data or a qualified local extension; it is not a hidden route mutation.

Layout has a separate contract. PresentationPolicy reads navigation and constraints, then returns entry-to-pane assignments. It does not execute navigation commands during measurement.

The type U contains presentation preferences such as a desired inspector width. Keeping it separate from navigation prevents an ordinary pane resize from becoming a route change.

7.6 Execution and resource boundaries

A SurfaceSession represents one mounted presentation lifetime. It can become visible, focused or interactive, and it releases owned resources when closed. Those attributes are separate because an exiting view may still be visible but no longer interactive.

A resource lease establishes who must release an acquired object. It can wrap a decoded image, a native editor, an effect-field registration or a subscription. Closing a lease ends that ownership; whether an underlying object enters a bounded cache is a separate resource policy.

The remaining interfaces are introduced with their subsystems. They deliberately do not form one giant SurfaceManager containing navigation, rendering, physics, files, agents and networking. The central connection is the mounted boundary, which receives already-resolved dependencies and coordinates their lifetimes.

7.7 Renderer capability and the portable realization seam

The two renderer families expose a capability profile. It identifies supported component versions and the optional facilities available to a mount. The profile is metadata used for compatibility and fallback decisions; it is not a generic drawing API that hides every platform feature.

HostRenderer is used when a portable committed structure actually needs a realization. HostCommitSink validates structural publication first. The target then performs the toolkit work on the required thread. A rendering failure is handled by the mounted boundary's fallback; it is not permission to publish an unrelated partially valid application definition.

Direct CMP composition does not need to call this host interface for every control. Its renderer-local API can use composable functions. The React web/JS implementation uses React's component model. Their common obligations come from the catalogue and capability profile, while their local function signatures remain appropriate to their toolkit.

This distinction avoids a misleading universal method that takes arbitrary platform content as Any. Portable values have codecs and contracts. Local implementation objects remain local. A typed graph binding supplies application actions in either case.

The result is a small common architecture with two honest realization APIs. The shared layer states what must agree. Each renderer retains the mechanisms it needs to satisfy those requirements well.

8. Component definitions and the catalogue

8.1 Three different things are often called a schema

A component contract describes a component's interface. A composition program uses components to create a screen. A host message schema describes the values crossing a runtime boundary. Treating all three as one schema causes unnecessary restrictions.

For example, the contract for an event card can describe a title, image reference and allowed actions. A Kotlin composition can decide whether to show one card or several. The host protocol can encode the committed card instances. The component contract does not need a new language for loops, and the host protocol does not need to serialize arbitrary closures.

Shared contracts are defined in Kotlin so that types, generation and the broader Reaktor model have one authoritative origin. This does not require every application screen to be generated from data. Handwritten Kotlin and React composition remain normal programming models.

8.2 Properties should describe meaning before presentation mechanics

A useful button property is intent = Destructive. It tells every implementation that activation has a destructive meaning. A property such as shadowOffsetY = 4 tells an implementation how to draw, but says nothing about what the action means.

Both kinds of values can be useful, but they belong at different levels. The public semantic API defaults to intent, emphasis and size. The selected recipe resolves those into fills, text roles, borders and motion. Advanced local customization remains possible without making raw appearance the only language of the component.

Independent booleans are appropriate here because a control's availability and ongoing work are different facts. A button may remain focusable while a submission is busy, even if the interaction policy suppresses another activation. A single enum containing enabled, disabled, loading, error and success cannot represent all valid combinations without growing into a large cross-product.

Component-specific models are preferable to one universal state object. A tri-state checkbox needs checked, unchecked and indeterminate values. A menu needs an active item and expansion state. A text field needs editing and validation information. These contracts can share conventions without pretending their states are identical.

8.3 Portable values require explicit representation

A component contract should not use Compose Color, Dp or ImageVector as universal values. Those types already choose a toolkit representation. The shared contract instead uses color-space-aware values, logical lengths and asset identities. Renderer adapters perform the conversion.

Logical lengths need a stated meaning. A logical layout unit is not automatically a physical pixel. Text sizing must also respect the platform's scaling behavior rather than assuming one linear multiplier is sufficient for every target. A neutral value permits the adapter to make the correct conversion; it does not eliminate that work.

An asset reference identifies an image or icon through the resource system. It can carry semantic alternative text or decorative status through the component contract. A filename is not an accessible name. A decorative icon beside a visible label should not create a second spoken label accidentally.

Local-only slots remain useful. A direct CMP component can accept a composable lambda or a native integration handle. Such values are explicitly local. They are not put into a portable codec and silently expected to work in another runtime.

8.4 The catalogue connects definitions to implementations

The catalogue indexes versioned component contracts, supported implementations, examples and conformance requirements. It is the source used by code generation, the component laboratory, the visual editor and agent tooling.

Figure 10. A Kotlin-defined component contract produces platform-facing types, boundary validation and tooling metadata. Renderer implementations remain responsible for satisfying the shared behavior and semantics. See the illustrated PDF for the diagram.

A generated TypeScript interface should not be a separately maintained interpretation of Kotlin source. The generation step produces a traceable fingerprint. A host can then check whether an incoming region refers to the contract combination it supports.

Code generation should remove duplication without concealing implementation. It can produce property types, codecs and catalogue entries. It should not generate opaque keyboard behavior that no one can reason about. Platform adapters and rich component implementations remain handwritten where that makes behavior clearer.

The catalogue also contains examples for states that are easy to forget: empty content, invalid input, unavailable resources, keyboard focus and reduced motion. A component is not complete because its attractive default state has a preview.

8.5 Compatibility is behavioral

Suppose a field previously emitted an update on each edit but now emits only on focus loss. Its serialized properties may be unchanged, yet an autosave workflow can behave differently. That is a compatibility decision, not merely an implementation optimization.

Similarly, changing whether an Escape key dismisses a panel can affect a surrounding workflow. Removing an accessible action changes operation even when the component still looks identical. Surface versions therefore distinguish data shape from behavioral expectations.

Additive optional values can often be compatible. Required-slot changes, removed events and altered semantic timing require explicit migration or a new contract version. The catalogue records compatibility claims, and conformance tests exercise the behavior those claims depend on.

The goal is not to version every pixel. It is to ensure that application authors can rely on a control's meaning while its implementation improves.

9. Interaction machines that do not depend on Compose or React

Local feedback and admitted application actions

HLD — Arrows follow an input to local feedback or an admitted application operation. Drawing a pressed state never sends another command. Open the diagram for full size.

9.1 Why behavior belongs below the toolkit adapter

A press can begin, move outside a control, return, and end. A keyboard activation may not involve a pointer at all. An accessibility service can invoke the same semantic action through a different input path. A behavior model must represent the resulting meaning without being written as an assortment of toolkit callbacks.

Putting the model in a pure Kotlin kernel has two benefits. First, the behavior can be tested without the rendering system. Second, a future React web/JS adapter can reuse the same decisions or run the same conformance traces. The rendering backend changes, but the meaning of cancel or activate does not.

The kernel should not implement the entire operating system's input model. It receives normalized inputs from a qualified adapter. The adapter uses native control behavior where appropriate, then exposes the relevant state changes and semantic activation to Surface.

9.2 Focus, hover and press are concurrent axes

A control can be focused, hovered and pressed at the same time. Treating these as mutually exclusive states produces errors: entering the pressed state may accidentally forget focus, and releasing may incorrectly restore an idle state even though the pointer is still over the control.

Figure 11. Independent interaction axes can coexist. Creation and disposal belong to a lifecycle; sending or validating belongs to application activity. Neither should be forced into the same mutually exclusive enum as hover and focus. See the illustrated PDF for the diagram.

A button state can therefore contain focus flags, hover state and active press tokens. A drag operation may add a separate phase such as pending, dragging, settling or cancelled. The correct model is a small product of meaningful axes, not an exhaustive enumeration of every combination.

The state must also define what happens when controlled inputs change. Disabling a control during an active press should cancel its admitted contact behavior. It should not wait for a release event that may never arrive after the platform removes the control from interaction.

9.3 Separate contact tracking from activation

The reference button kernel recognizes Press, Release, Cancel, Focus, Hover and Activate. Release removes an active contact token. Activate is the only input that produces the button's semantic activation event.

This separation avoids a common double-dispatch error. A renderer can observe press interactions for animation while relying on its semantic click path for activation. It must not both synthesize activation on release and also forward the platform's click as a second event.

Figure 12. The platform adapter normalizes its input conventions. Contact state drives physical feedback, while one semantic activation path drives the application event. See the illustrated PDF for the diagram.

The activation sequence is local to the component's admitted event stream. A repeated delivery of the same sequence does not produce another button event. This is not a replacement for server-side idempotency. Network retries and duplicate business requests still require the application's own operation identity and policy.

9.4 The output separates state, events and cues

For a new press, the reducer returns updated contact state and a contact cue. It returns no application event. For release, it removes the token and can return a release cue. For an admitted activation, it returns a typed event that the composition owner maps to a graph action.

The distinction lets different consumers use the result safely. A recipe reads the state to choose the current appearance. A sensory adapter consumes cues to produce one-shot feedback. Application code handles semantic events. Repeatedly observing the state does not replay either of the event paths.

The reducer itself stays deterministic. The current time, pointer identity or event sequence enters through its input instead of being read from an uncontrolled global clock. Tests can then reproduce a cancellation or simultaneous-contact scenario exactly.

A production controller may retain a bounded queue of cues until the local runtime executes them. It must define how cancellation and disposal affect queued cues. A haptic for a control that no longer exists should not emerge long after its visual context has disappeared.

9.5 Shared behavior does not require identical internal machinery

A web button already has useful native semantics. A CMP implementation can reuse Foundation's interaction facilities. Surface should not replace these mechanisms with a handwritten raw-pointer detector simply to claim all input is “shared.” Compose documents interaction sources as a way to observe and respond to component interactions. 9

The shared contract is about the admitted behavior. The adapter maps platform events into that contract without losing normal conventions. Where a platform owns an essential detail, such as text composition or accessibility action dispatch, the adapter delegates rather than reimplements it incompletely.

A headless reducer test proves the reducer. It does not prove that the adapter maps every real platform event correctly. Both layers require tests. The adapter tests should include pointer cancellation, keyboard activation, focus visibility, disabled transitions and assistive invocation.

9.6 A concrete trace

Start with a focused control and no active contacts. A pointer enters, so hover becomes true. Press token 7 begins, so the state now contains focus, hover and token 7. A repeated press notification for token 7 has no additional effect. Release removes token 7 but leaves focus and hover intact.

A subsequent normalized activation sequence 10 produces one event. Delivering sequence 10 again produces none. If the control becomes busy before sequence 11 arrives, the kernel suppresses the next activation under the button's chosen busy policy.

This trace is small enough to understand and useful enough to expose real bugs. It tests independent axes, duplicate contact handling, activation deduplication and controlled-input changes. The same trace can be used when the React web/JS adapter is introduced, so Compose-first development does not turn behavior into a platform-specific assumption.

10. Input semantics, editing and accessible operation

10.1 A semantic control is not a visual layer

A tactile button can have a contact plate, a moving face, a border, an icon and a label. Those five visual pieces should usually describe one semantic control. If each piece becomes an accessibility node or focus target, the person encounters an interface that does not match its visible meaning.

Figure 13. Several drawing layers can realize one accessible button. The semantic projection carries its name, enabled state and activation action; decorative layers do not create extra operations. See the illustrated PDF for the diagram.

The renderer therefore maintains a semantic projection. It may merge appropriate descendants, expose relationships and attach actions to the correct node. A custom-drawn control requires the same care as a stock control. Drawing an attractive checkbox is not sufficient unless the checked state and toggle action are also operable.

Accessibility requirements belong in the component contract, while platform realization belongs in the adapter. The catalogue can require a name, value and action. The renderer must expose them through its platform's accessibility system and verify them with actual assistive technology.

10.2 Focus is a resource with rules

Focus identifies the destination of relevant keyboard or assistive input. It is not merely a colored outline. Opening a dialog must establish an appropriate initial focus destination. Closing it must return focus to a useful place. Reordering a list should not send focus to an unrelated item because it reused the same position.

Focus also has a scope. Two windows can show the same conversation while only one is the active keyboard destination. A graph-owned selected conversation does not imply globally shared caret or focus state.

A component distinguishes focus from focus-visible treatment. A platform can provide different focus indication rules for different input modes, but every required interaction must remain perceivable and usable. The visual recipe can change the indicator's style within its contract; it cannot remove the indicator without a conforming alternative.

10.3 Text editing needs a fast local owner

Text editing includes more than a string. It includes caret position, selection, composition ranges, undo behavior and communication with an input method. A renderer must respond locally to these operations. Waiting for a remote validator or application-wide actor before moving the caret would put avoidable latency into every keystroke.

The application still needs ownership of the draft. The solution is not to keep two unrelated copies forever. It is to define a revisioned editing session. The local editor publishes edits, the draft owner acknowledges them, and external updates carry enough identity to avoid overwriting newer local input.

Figure 14. The editor has already produced revision 22 when validation for revision 21 returns. The old acknowledgment can be recorded without replacing the newer local text. See the illustrated PDF for the diagram.

An acknowledgment means that a particular edit has been received or persisted according to the contract. It is not automatically replacement content. A validator's error for an older revision should not erase a newer valid edit. An external collaborative edit requires a declared merge or conflict policy rather than accidental last-writer behavior.

10.4 Form behavior should preserve work through failure

A form can be initially loading, locally invalid, submitting, rejected by the server, unavailable because of permission, or waiting for connectivity. These are different states with different recovery actions.

Initial loading may need a placeholder. A refresh can keep useful existing values visible. A server rejection should preserve the person's input and identify the affected fields. A permission change may disable submission while allowing a draft to be copied or retained according to product policy.

Submission should not be inferred from a visual transition. A button animation can finish before the server responds. A success message appears only after the authoritative operation reaches the state the message claims. Where an offline workflow accepts work locally, the UI explicitly distinguishes local recording from remote acceptance.

10.5 Internationalization changes geometry and semantics

A translated label can be longer than its source-language version. A right-to-left layout changes logical direction. Different scripts require suitable font coverage and shaping. These conditions affect measurement and behavior, not merely string replacement.

Surface uses start and end relationships in shared layout definitions. A directional message bubble can mirror, while a logo or photograph need not. Text roles refer to resources and typography policy rather than hard-coded line counts that assume one language.

Text scaling also needs deliberate support. A fixed-height field that clips its label is not adaptive merely because the surrounding window is wide. Component measurement must permit legitimate reflow, and the presentation policy may reduce simultaneous panes when larger content needs more space.

10.6 Accessibility policy is independent of agent access

The web accessibility target is WCAG 2.2 AA, evaluated across complete workflows rather than inferred from a component gallery. Native implementations require their corresponding platform tests. WCAG defines testable requirements for web content, but a conforming individual control does not certify every composition that uses it. 10

Agent observation is a separate policy. A private field may remain fully available to the person's screen reader while being redacted from a remote model's context. Removing accessibility semantics to hide data from an agent would confuse two different trust relationships.

This distinction is especially important for custom interfaces. An inspector can expose selected-object names and permitted commands to an agent without exporting every private property. The same control can provide richer accessibility information locally under the user's own interaction context.