ScopeStack
The ambient scope + screen context of the currently visible UI, as a stack of pushed frames.
This is the non-Compose home for what AutographScope and screen context express inside Compose via CompositionLocal: a place that autocapture pipelines can read at the moment a tap or screen view is observed, regardless of which UI framework produced it (Compose, UIKit/SwiftUI, or the Android View system). Each surface pushes a frame when a screen or scope comes into view and removes it when it leaves; the capture pipeline calls current and AmbientContext.enrich to attribute the event.
Read this only from auto-capture code, not from explicit track calls. The stack answers "what was on screen when the user acted", which is the right question for an autocaptured tap but the wrong one for an arbitrary track(...) — a background event (e.g. a network response that completes after navigation) must not silently inherit whatever screen happens to be visible. Explicit instrumentation keeps its own lexical scope; see the autograph-compose decorator.
Own an instance; don't share one globally. Scope a ScopeStack to the tracker it feeds so context can't leak across trackers or outlive a tracker swap on logout — screenHistory rides along and inherits that scoping, which is what resets previous_screen when the tracker is replaced. Sharing one instance across the surfaces of a single hybrid app is the exception, and the point: pass it to AutographProvider so the Compose and native pipelines attribute against the same context and share one previous_screen chain. That stack is then yours to replace when the tracker is — the provider will not swap a caller-supplied stack out from under the native side.
Threading. push, update, and remove must be called from the main thread (push and remove mutate the frame list; update mutates a frame's contents). current is lock-free and safe from any thread: it returns an immutable snapshot that is republished atomically on every mutation, so a background reader always sees a whole, consistent context — never a half-applied one.
Functions
The current merged ambient context. Lock-free; safe from any thread.
Records name as the current screen and emits a Screen Viewed for it, carrying the screen it replaced as previous_screen. The one place that couples recording with emitting, so every native screen-capture caller — the iOS UIKit swizzle, the iOS explicit SwiftUI path, and the Android Activity/Fragment lifecycle capture — cannot drift on the order or on the self-previous guard.
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.
Removes the frame handle refers to, by identity and independent of position — screen transitions (a Compose Crossfade, an iOS interactive-pop that the user cancels) do not guarantee frames leave in push order, so a positional pop would remove the wrong one. Removing a handle that was already removed, or one from another stack, is a no-op.
Replaces the contents of the frame handle refers to, in place — keeping its position, and thus its precedence, in the stack. Use this instead of remove + push when a still-mounted frame's scope/screen/section changes: re-pushing would move the frame to the top and let it wrongly override inner frames that are still on the stack. A no-op (and no snapshot churn) if the contents are unchanged, the handle was already removed, or it belongs to another stack.