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 BridgeBackend or the FakeBackend tests use. Renders are batched automatically (one flush per callback, effect, or task step), and a flush that re-schedules itself more than fifty times raises RuntimeError("Too many re-renders") through the nearest ErrorBoundary.

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: publishing the frames the renderer core lays out.
  • 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. An effect that raises is routed to the nearest ErrorBoundary, exactly like a render-time exception.

State setters never render inline. request_render schedules one flush on the application loop with call_soon, so every setter call within one callback, effect, or task step coalesces into one pass on every host. A render requested while a pass is in flight (an effect setting state, a boundary reset) is drained before the pass returns, so hosts never observe a half-committed tree. A flush that keeps re-scheduling itself more than MAX_RENDER_PASSES times raises RuntimeError("Too many re-renders") from the component that keeps dirtying itself, routed through the nearest boundary like any render error.

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.

Attributes:

Name Type Description
MAX_RENDER_PASSES

How many times one flush may re-schedule itself before it is a render storm.

MAX_RENDER_PASSES module-attribute

MAX_RENDER_PASSES = 50

How many times one flush may re-schedule itself before it is a render storm.

A component that sets state unconditionally in an effect, or a setter called on every render, keeps the tree dirty forever. After this many drain iterations inside one flush the reconciler raises RuntimeError("Too many re-renders") from the offending component and routes it through the nearest ErrorBoundary.

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 backend protocol (apply_mutations, resolve_view, compute_layout, command). PythonNative ships the bridge backend for iOS, Android, and the browser preview; 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 flush is due (to hop onto the UI thread or guard with a red box). It runs on the application loop, one turn after the first state change that scheduled it. When None the reconciler calls flush_dirty itself; 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

Schedule one flush on the application loop (automatic batching).

register_back_handler

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

defer_input

Queue state writes during mounting, preserving their functional order.

wait_for_commit

Wait until mounting and its queued state updates have settled.

set_viewport_size

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

root_tag property

root_tag: Optional[int]

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

commit_pending property

commit_pending: bool

Whether native acknowledgement is holding the surface's next render.

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

Schedule one flush on the application loop (automatic batching).

Never renders inline: every request made during one callback, effect, or task step lands in the same call_soon flush. Requests made while a pass is in flight are drained by that pass instead.

register_back_handler

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

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

defer_input

defer_input(callback: Callable[[], None]) -> bool

Queue state writes during mounting, preserving their functional order.

wait_for_commit async

wait_for_commit() -> None

Wait until mounting and its queued state updates have settled.

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.

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.

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.

normalize_children

normalize_children(
    children: Any, owner: str = "", *, dynamic: bool = False
) -> 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.

Parameters:

Name Type Description Default
children Any

The value to normalize.

required
owner str

Label of the element or component the children belong to, for diagnostics.

''
dynamic bool

Whether children itself is a list a component returned (as opposed to a container's positional children). In dev mode, a dynamic list of two or more elements without keys is reported once per owner, as is any nested list found among positional children.

False

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