AutographElementCapture
Explicit click instrumentation for Swift callers — the receiving end of the AutographUI Swift product's AutographButton and autograph.track(_:).
Why this exists at all
Tracker.track takes a Map<String, JsonElement> — the interface rather than JsonObject, since declaring the subtype made a Swift dictionary crash the process (#193) — and Swift still cannot build a JsonElement to put in it. Autograph.xcframework exports core/context/uikit/segment but not kotlinx-serialization-json, so JsonPrimitive has no Objective-C counterpart and JsonElement arrives as an opaque class with no initializer: a Swift caller can pass an empty properties dictionary and nothing else. Exporting the serialization library instead would put a third party's whole surface into this SDK's public Objective-C API, so the conversion lives here, on values Swift can actually express.
What it merges, and from where
The precedence is the one the Compose path applies (withScreenContext plus the scope decorator), so an event means the same thing on both platforms:
scope — the caller's ambient scope, lowest precedence
properties — the call site's own values, which win over the scope
target, thenscreen/section— reserved keys written on top
scope is passed in, not read from scopeStack, and that is deliberate. ScopeStack.resolveScope() drops sibling frames that are neither's ancestor, because an autocaptured tap carries no evidence of which sibling it hit. A list whose rows each own a scope is exactly that shape, so reading the stack here would silently drop the row's scope — even though an explicit call site knows precisely which row it is. Swift therefore accumulates the scope lexically (a SwiftUI Environment value, the analogue of Compose's ScopedTracker) and hands it over per call. Screen and section are read from the stack: at most one screen is current, so the ambiguity that forces the drop cannot arise, and the stack is the only place a SwiftUI screen name lives (see AutographScreenCapture).
That split is why ScopeStack's "read this only from auto-capture code" note does not apply here: the part this reads dynamically is the part that has no ambiguity, and the part that would be ambiguous is supplied lexically.
Threading and failure
Main thread, like the rest of the SwiftUI-facing surface. Nothing here throws into Swift — a Kotlin exception unwinding into a Swift caller with no @Throws crashes the app, and an analytics event is never worth a crash.
Functions
clicked for properties that are not all strings — numbers, booleans, nested objects — passed as a JSON object literal. The Swift side has no way to express a JsonObject, and a [String: String] dictionary cannot carry them, so this is the escape hatch; it mirrors the JsonObject overload the Compose API offers.