push

fun push(scope: Map<String, JsonElement> = EmptyJsonObject, screen: String? = null, section: String? = null, parent: ScopeHandle? = null): ScopeHandle

Pushes a frame contributing scope properties (low precedence — an explicit call-site property always wins over them) and/or a screen/section (reserved keys, high precedence). Returns a ScopeHandle to update or remove it with. A frame may carry scope only (an AutographScope analogue), screen only (a TrackedScreen analogue), or both.

A frame that names a screen owns its section: pushing screen = "X" with no section means "screen X, no section", and an inner screen that declares none does not inherit the section of an outer one still on the stack. A frame with a section but no screen is a section-only marker that refines the surrounding screen instead.

parent declares this frame's enclosing frame, forming a lineage the scope merge follows: an enclosing scope contributes to a nested one, but two frames that are neither's ancestor (siblings mounted at once — a list's rows, split-pane, a sheet over content) are ambiguous, and current then drops those rather than guessing between them, keeping whatever encloses them all — a route scope above ambiguous rows still attributes (see resolveScope). Pass the ScopeHandle of the enclosing frame; null (the default) marks a root. Lineage is framework-independent — a native surface declares it the same way — so this does not tie the stack to Compose. It affects only scope; screen/section still resolve by insertion order ambiently (the origin-taking current additionally ranks a container with its content).


fun push(scope: Map<String, JsonElement> = EmptyJsonObject, screen: String? = null, section: String? = null, parent: ScopeHandle? = null, boundary: Boolean): ScopeHandle

push for a frame that is also an attribution boundary: the pipeline pushing it can tell, for every event it captures, whether the event happened inside this frame, and resolves such events through current with the frame — or one beneath it — as the event's origin.

A boundary changes what an origin-based resolution sees, in both directions. What is declared beneath a boundary is visible only to events whose origin is at or beneath it, never to an event resolved from outside: a native surface's frame is a boundary, so the screen a fragment names, or the mask an unnamed one raises, reaches the taps that land in that fragment and not the taps on the surface hosting it beside it. And a boundary is where the descent of current stops: an event whose origin is a surface does not pick up the declarations of the surfaces nested inside it, because its pipeline has already established the event is not in them. See current for the exact rule.

Which frames should be boundaries follows from that: those a pipeline can localize an event to and that stand for a surface of their own. Every surface a native capture reserves a frame for is one (an Activity, a fragment). The root frame a Compose provider pushes for its composition is deliberately not, although the observer can localize a tap to it: a composition is how the surface hosting it declares its screen, not a surface of its own, so a native tap on a toolbar beside the ComposeView must still see the TrackedScreen inside it. A plain push is the right call for any declaration that is not a surface — and a frame under no boundary at all applies to every event, whatever its origin (see current).

Static for the life of the frame; update does not revise it. boundary has no default so that a call spelling none of the arguments still resolves to the plain push overload.