Skip to content

Reconciler

Diffs successive element trees and applies the smallest set of native mutations that bring the on-screen view tree in line with the new description. The reconciler is platform-agnostic; it talks to native widgets exclusively through a backend such as the NativeViewRegistry or the in-memory FakeBackend.

Reconciler package: turns element trees into batched native mutations.

The public surface is Reconciler and VNode; the remaining modules are implementation detail:

  • core: render, diff, commit.
  • boundaries: ErrorBoundary and Suspense.
  • layout_pass: the flexbox pass and frame diffing.
  • children: minimal move planning for keyed child lists.
  • vnode: the mounted-tree node and pure helpers.

Core

The Reconciler: element trees in, native mutations out.

One reconciler owns a mounted application tree or headless test render. Logical screens, overlays, and mounted list rows share that tree. Each pass runs the same phases in order:

  1. Render: component bodies run for the subtrees that need it, producing Element descriptions. Hook state is installed for the duration of each body.
  2. Diff and stage: elements are compared with the mounted VNode in their slot. Creates, prop updates, attach/detach, and destroys are staged into an op list instead of being applied immediately.
  3. Commit: the staged ops go to the backend in one batch, refs are populated, the layout pass runs (once a viewport is known), then effects flush: use_layout_effect first, use_effect after.

A render requested while a pass is in flight (an effect setting state, a boundary reset) is queued and drained before the call returns, bounded so runaway update loops surface as an error rather than a hang. Hosts never observe a half-committed tree.

The class is assembled from mixins to keep each concern readable: this module holds the render/diff/commit pipeline, boundaries handles ErrorBoundary and Suspense, and layout_pass runs flexbox.

Classes:

Name Description
Reconciler

Owns one mounted tree and translates element diffs into native mutations.

Reconciler

Reconciler(backend: Any)

Bases: BoundaryMixin, LayoutMixin

Owns one mounted tree and translates element diffs into native mutations.

Parameters:

Name Type Description Default
backend Any

An object implementing the registry protocol (apply_mutations, resolve_view, measure_intrinsic, command). PythonNative ships Android, iOS, and browser registries; tests use FakeBackend.

required

Attributes:

Name Type Description
backend

The backend passed at construction.

on_render_requested Optional[Callable[[], None]]

Optional callback hosts set to be told a re-render is wanted (to hop onto the UI thread or guard with a red box). When None the reconciler re-renders inline. Whatever the callback does, it should end up calling flush_dirty.

on_back_registered Optional[Callable[[], None]]

Optional callback fired when the first use_back_handler registers, so hosts can enable hardware back interception.

transitions

Deferred-render queue behind use_transition; owned per reconciler so screens never delay each other.

Methods:

Name Description
root_view

The root native view object, or None before mount.

mount

Build native views for element and return the root native view.

reconcile

Diff element against the mounted tree and patch native views.

flush_dirty

Re-render only the components whose state changed, then commit.

unmount

Tear down the mounted tree, running effect cleanups and destroying native views.

dispatch_command

Run an imperative command against the view registered under tag.

dispatch_back_press

Offer the system back action to registered handlers, newest first.

walk

Yield every mounted node in depth-first, document order.

mark_dirty

Queue vnode (a component) for a local re-render on the next flush.

request_render

Ask for a flush: via the host callback when set, inline otherwise.

register_back_handler

Register a back-press handler; returns an unregister callable.

set_viewport_size

Update the viewport size and re-run layout if it changed.

compute_layout_for_test

Build and compute a layout tree without touching the backend.

root_tag property

root_tag: Optional[int]

Tag of the root native view, or None when nothing is mounted.

viewport_size property

viewport_size: Tuple[float, float]

The last viewport size supplied by the host, (0, 0) before layout is possible.

root_view

root_view() -> Any

The root native view object, or None before mount.

mount

mount(element: Element) -> Any

Build native views for element and return the root native view.

Any previously mounted tree is unmounted first.

reconcile

reconcile(element: Element) -> Any

Diff element against the mounted tree and patch native views.

Pending state updates are drained as part of the pass, so hosts only call this when the root element itself changed (new props from outside the tree).

flush_dirty

flush_dirty() -> Any

Re-render only the components whose state changed, then commit.

This is the hot path for state-driven updates: each dirty component re-runs its own body and reconciles its subtree in place; the batch commits as one native transaction. Returns the (possibly replaced) root native view.

unmount

unmount() -> None

Tear down the mounted tree, running effect cleanups and destroying native views.

dispatch_command

dispatch_command(
    tag: Optional[int],
    name: str,
    args: Optional[Dict[str, Any]] = None,
) -> Any

Run an imperative command against the view registered under tag.

dispatch_back_press

dispatch_back_press() -> bool

Offer the system back action to registered handlers, newest first.

Returns True if a handler consumed the event (the platform should not run its default behavior).

walk

walk() -> Iterator[VNode]

Yield every mounted node in depth-first, document order.

mark_dirty

mark_dirty(vnode: VNode) -> None

Queue vnode (a component) for a local re-render on the next flush.

request_render

request_render() -> None

Ask for a flush: via the host callback when set, inline otherwise.

register_back_handler

register_back_handler(
    handler: Callable[[], bool],
) -> Callable[[], None]

Register a back-press handler; returns an unregister callable.

set_viewport_size

set_viewport_size(width: float, height: float) -> None

Update the viewport size and re-run layout if it changed.

Called by the screen host whenever the platform reports a new container size. The first call after mount triggers the initial layout pass; identical sizes are no-ops.

compute_layout_for_test

compute_layout_for_test(
    viewport_width: float, viewport_height: float
) -> Optional[LayoutNode]

Build and compute a layout tree without touching the backend.

Returns the synthetic viewport LayoutNode with all descendants positioned, or None if nothing is mounted.

Mounted nodes

Mounted-tree nodes and the pure helpers the reconciler shares.

VNode pairs an Element with the identity of the native view it produced (its tag) plus the bookkeeping the reconciler needs across passes (layout cache, boundary state, hook state).

Everything in this module is side-effect free with respect to the native layer.

Classes:

Name Description
VNode

A mounted Element plus its native identity.

Functions:

Name Description
next_tag

Allocate a fresh, process-unique view tag.

shallow_equal_props

Return whether two prop dicts are equal under shallow comparison.

normalize_children

Normalize arbitrary render output into a flat list of Elements.

VNode

VNode(
    element: Element,
    children: Optional[List["VNode"]] = None,
    tag: Optional[int] = None,
)

A mounted Element plus its native identity.

The reconciler walks parallel trees of VNode and incoming Element to compute the minimal set of native mutations.

Attributes:

Name Type Description
element

The Element last rendered into this slot.

tag

Integer identity of the underlying native view. Native elements own a fresh tag; transparent wrappers (components, providers, boundaries, keyed fragments) delegate the tag of their first native root. None when the subtree renders no native view.

native_view Any

The platform-native view object, resolved from the backend after commit. None for wrappers that rendered nothing.

children List[VNode]

Ordered list of child VNode instances.

parent Optional[VNode]

The owning VNode, or None for the tree root.

hook_state Optional[HookState]

The component's HookState when the node wraps a component, otherwise None.

mounted bool

False once the node has been destroyed, so stale entries in the reconciler's dirty set are skipped.

Methods:

Name Description
depth

Return the number of ancestors above this node (the root has depth 0).

is_native property

is_native: bool

Whether this node owns a native view.

is_component property

is_component: bool

Whether this node renders a user component (a @component function).

is_provider property

is_provider: bool

Whether this node is a context provider.

is_error_boundary property

is_error_boundary: bool

Whether this node is an ErrorBoundary.

is_suspense property

is_suspense: bool

Whether this node is a Suspense boundary.

label property

label: str

Human-readable name of the element type (for diagnostics and tree dumps).

depth

depth() -> int

Return the number of ancestors above this node (the root has depth 0).

next_tag

next_tag() -> int

Allocate a fresh, process-unique view tag.

shallow_equal_props

shallow_equal_props(old: dict, new: dict) -> bool

Return whether two prop dicts are equal under shallow comparison.

Used by memo to skip re-rendering when none of a component's props changed identity. Callables only count as equal if they're the same object; fresh closures always invalidate the memo (matching React's behavior; pair with use_callback when stability matters).

normalize_children

normalize_children(
    children: Any, owner: str = ""
) -> List[Element]

Normalize arbitrary render output into a flat list of Elements.

Accepts a single element, None, True/False (both skipped, enabling inline conditionals like cond and Text(...)), lists/tuples/generators (flattened recursively), and unkeyed Fragments (expanded inline so they never touch the native tree). Keyed Fragments are preserved so they can participate in keyed reconciliation as a unit.

Non-Element values other than the above are dropped with a dev-mode warning.

Keyed children

Pure planning of native child-list moves.

Given the tags a native container currently holds and the tags it should hold after a pass, plan_child_moves returns the minimal sequence of (tag, index) insertions (with move-aware insert_child semantics: a child already attached is re-positioned) that transforms one into the other.

The algorithm keeps the longest increasing subsequence of surviving children in place and moves only the rest, processing right to left so every moved child is inserted directly before an already-settled neighbor. That yields n - len(LIS) operations, the optimum for a move-aware insert primitive.

Functions:

Name Description
longest_increasing_subsequence

Return the indices (into values) of one longest strictly increasing subsequence.

plan_child_moves

Compute (tag, index) insert ops turning before into after.

longest_increasing_subsequence

longest_increasing_subsequence(
    values: Sequence[int],
) -> List[int]

Return the indices (into values) of one longest strictly increasing subsequence.

plan_child_moves

plan_child_moves(
    before: Sequence[int], after: Sequence[int]
) -> List[Tuple[int, int]]

Compute (tag, index) insert ops turning before into after.

Parameters:

Name Type Description Default
before Sequence[int]

Tags currently attached to the container, in order. Tags absent from after are assumed to be detached already (the reconciler destroys them first) and must not appear here.

required
after Sequence[int]

Desired child tags, in order. Tags absent from before are fresh children.

required

Returns:

Type Description
List[Tuple[int, int]]

Ordered insert operations. Applying each as "detach tag if

List[Tuple[int, int]]

attached, then insert at index" reproduces after.

Next steps