@qontinui/ui-bridge-native 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/control/index.js +2 -0
- package/dist/control/index.js.map +1 -1
- package/dist/control/index.mjs +2 -0
- package/dist/control/index.mjs.map +1 -1
- package/dist/core/index.js +267 -15
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +267 -16
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/registry.d.ts +13 -0
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/sha256.d.ts +19 -0
- package/dist/core/sha256.d.ts.map +1 -0
- package/dist/core/types.d.ts +20 -7
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/vision-fields.d.ts +164 -11
- package/dist/core/vision-fields.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +285 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +280 -25
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.js +279 -24
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +279 -24
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/useRouteAwareness.d.ts.map +1 -1
- package/dist/server/handlers.d.ts.map +1 -1
- package/dist/server/index.js +268 -17
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +268 -17
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/registry.ts","../../src/core/element-identifier.ts","../../src/core/abortable.ts","../../src/core/param-schema.ts","../../src/control/action-executor.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Native Element and Component Registry\n *\n * Central registry for all UI elements and components registered with UI Bridge Native.\n * Adapted from ui-bridge for React Native environments.\n */\n\nimport type {\n RegisteredNativeElement,\n RegisteredNativeComponent,\n NativeElementState,\n NativeElementType,\n NativeStandardAction,\n NativeCustomAction,\n NativeAppInfo,\n NativeBridgeSnapshot,\n NativeElementBbox,\n NativeElementIdentifier,\n NativeElementRef,\n NativeRegistrationCoverage,\n NativeRouteProviderLike,\n NativeSnapshotEnrichers,\n NativeSnapshotEnricher,\n RouteInfo,\n Workflow,\n BridgeEvent,\n BridgeEventType,\n BridgeEventListener,\n IREffect,\n} from './types';\nimport { projectVisionFields } from './vision-fields';\nimport { serializeElementCustomActions } from './element-actions';\n\n/**\n * Options for registering an element\n */\nexport interface RegisterElementOptions {\n type?: NativeElementType;\n label?: string;\n actions?: NativeStandardAction[];\n customActions?: Record<string, NativeCustomAction>;\n props?: Record<string, unknown>;\n treePath?: string;\n testId?: string;\n accessibilityLabel?: string;\n /** Route path where the element was registered (for page-scoped filtering) */\n registrationRoute?: string | null;\n /**\n * Id of the registered scrollable container this element lives inside.\n *\n * When set, {@link computeVisibility} clips the element against that\n * container's measured frame as well as the window, so a row scrolled out of\n * its `ScrollView` / `FlatList` stops reporting `visibility: 'visible'`.\n *\n * DECLARED, not inferred, and that is deliberate. React Native gives the\n * registry no parent chain — `useUIElement` is a hook, so it never wraps its\n * element's children and cannot publish itself as their ancestor. Guessing\n * the ancestor geometrically is worse than not clipping: a full-width scroll\n * container horizontally contains the tab bar and the header too, and\n * clipping those against it would report on-screen chrome as off-screen.\n * A wrong `hidden` is a worse answer than a coarse `visible`.\n *\n * Unset (the default) leaves the window as the only clip region.\n */\n scrollAncestorId?: string;\n /** Flattened RN style (from StyleSheet.flatten) for design review */\n flatStyle?: Record<string, unknown>;\n /** State-specific style overrides for design review */\n stateStyles?: {\n pressed?: Record<string, unknown>;\n focused?: Record<string, unknown>;\n disabled?: Record<string, unknown>;\n };\n}\n\n/**\n * Options for registering a component\n */\nexport interface RegisterComponentOptions {\n name: string;\n description?: string;\n actions?: Array<{\n id: string;\n label?: string;\n description?: string;\n /**\n * Phase 2 (same plan): the action's published parameter schema. It was\n * ABSENT here and dropped by the object literal in `registerComponent`\n * below, so the field `ComponentAction` declares could never reach a\n * registered native component and the native invocation seam had nothing\n * to validate against. That is the silent-drop trap the plan warns about,\n * already realised.\n */\n paramSchema?: Record<string, unknown>;\n /**\n * Phase 4 (same plan): the safety annotation. Absent here, an author's\n * `effect: 'destructive'` would die at this hop exactly the way\n * `paramSchema` did before Phase 2 — the literal in `registerComponent`\n * has a closed field list and nothing type-checks the omission.\n */\n effect?: IREffect;\n /**\n * Phase 3 (plan 2026-08-20-ui-bridge-action-declaration-shape): the second\n * argument is the `ActionHandlerOptions` bag carrying the cancellation\n * signal. A 1-arity handler stays assignable.\n */\n handler: (params?: unknown, options?: { signal?: AbortSignal }) => unknown | Promise<unknown>;\n }>;\n elementIds?: string[];\n}\n\n/**\n * Options for {@link NativeUIBridgeRegistry.updateComponentMeta}.\n *\n * Every field is optional and `undefined` means \"leave the registered value\n * alone\" — the same partial-update semantics {@link RegisterComponentOptions}'s\n * sister {@link NativeUIBridgeRegistry.updateElementMeta} uses. A consequence\n * worth stating: a `description` cannot be CLEARED through this door, only\n * replaced. Pass `actions: []` to publish an empty action list; that is\n * distinguishable from omitting the field.\n */\nexport interface UpdateComponentMetaOptions {\n name?: string;\n description?: string;\n actions?: RegisterComponentOptions['actions'];\n elementIds?: string[];\n}\n\n/**\n * Map declared component actions into the registry's stored shape.\n *\n * ⚠ CLOSED FIELD LIST. Any `ComponentAction` field not named here is dropped at\n * registration time, silently: the literal stays assignable, the serializer\n * still runs, the field is simply never there. `paramSchema` was exactly that\n * until Phase 2, and `effect` is the Phase 4 addition that would have gone the\n * same way.\n *\n * It lives in ONE place on purpose. `registerComponent` and\n * `updateComponentMeta` both publish actions, and a second hand-rolled copy of\n * this list is precisely how the two Phase-2/Phase-4 drops happened — a field\n * added to the declaration type stays assignable at every re-wrap site and\n * simply never arrives.\n */\nfunction toRegisteredComponentActions(\n actions: NonNullable<RegisterComponentOptions['actions']>\n): RegisteredNativeComponent['actions'] {\n return actions.map((a) => ({\n id: a.id,\n label: a.label,\n description: a.description,\n paramSchema: a.paramSchema,\n effect: a.effect,\n handler: a.handler,\n }));\n}\n\n/**\n * Value signature of everything about an action list that reaches a bridge\n * consumer.\n *\n * `handler` is excluded deliberately: it is a function, so it is not\n * serialisable, it is never published, and call sites pass inline closures that\n * are freshly allocated on every render — including it would make\n * `updateComponentMeta` report a change every single time and defeat its\n * idempotence. Order is significant, because published order is the order an\n * agent reads the actions in.\n */\nfunction publishedActionSignature(actions: RegisteredNativeComponent['actions']): string {\n return JSON.stringify(\n actions.map((a) => [a.id, a.label, a.description, a.paramSchema, a.effect])\n );\n}\n\n/** Positional equality for the optional `elementIds` list. */\nfunction sameElementIds(a: string[] | undefined, b: string[] | undefined): boolean {\n if (a === b) return true;\n if (a === undefined || b === undefined) return false;\n return a.length === b.length && a.every((id, i) => id === b[i]);\n}\n\n/**\n * Registry configuration\n */\nexport interface NativeRegistryConfig {\n verbose?: boolean;\n onEvent?: BridgeEventListener;\n}\n\n/**\n * Extract handler function names from an element's props.\n * Returns names of props whose values are functions (e.g. ['onPress', 'onChangeText']).\n */\nexport function extractHandlerNames(props?: Record<string, unknown>): string[] {\n if (!props) return [];\n return Object.keys(props).filter((k) => typeof props[k] === 'function');\n}\n\n/**\n * `currentRouteOnly` filter predicate for `registrationRoute`.\n *\n * Returns `true` when an element should be INCLUDED in a current-route-only\n * view. Two classes of elements pass the filter:\n *\n * 1. Elements registered on the current route\n * (`registrationRoute === currentRoute`).\n * 2. Route-agnostic elements (`registrationRoute == null` or `''`) — these\n * are typically registered at the app root (tab bars, persistent\n * headers, modal hosts) and apply to every route. Excluding them by\n * default makes `currentRouteOnly=true` return empty on mobile where\n * most apps register chrome at the root and screens via routed stacks.\n *\n * The filter only DROPS elements whose `registrationRoute` is set to a\n * non-matching route — i.e. they belong to a different screen. This matches\n * the runner-side semantics: route-tagged DOM nodes that don't match the\n * active URL are scoped out; untagged nodes are always in scope.\n *\n * Surfaced 2026-05-23 (item 4 of the 0.6.6 robustness pass) — the prior\n * strict equality returned 0 elements on mobile screens that register tabs\n * at the root, because tab elements had `registrationRoute: null`.\n */\nexport function matchesCurrentRoute(\n registrationRoute: string | null | undefined,\n currentRoute: string\n): boolean {\n if (registrationRoute == null || registrationRoute === '') return true;\n return registrationRoute === currentRoute;\n}\n\n/**\n * Derive the snapshot's `activeTab` from an Expo Router segment list.\n *\n * Expo Router spells a layout group as a parenthesised segment and a dynamic\n * route as a bracketed one, so `usePathname()`/`useSegments()` on the \"runs\"\n * tab of a `(tabs)` layout yield `\"/(tabs)/runs\"` and `[\"(tabs)\", \"runs\"]`\n * (the shape the `RouteProvider` doc comment already documents). The tab a user\n * is looking at is therefore the segment immediately following the INNERMOST\n * group.\n *\n * Rules, all deliberately conservative — an unknown tab is reported as absent\n * rather than guessed:\n *\n * - `[\"(tabs)\", \"runs\"]` → `\"runs\"`\n * - `[\"(tabs)\", \"runs\", \"[id]\"]`→ `\"runs\"` (a detail screen is still *on* a tab)\n * - `[\"(tabs)\"]` → `\"index\"` (the group's own index route)\n * - `[\"(tabs)\", \"[id]\"]` → `undefined` (dynamic route, not a tab)\n * - `[\"settings\"]` → `undefined` (no group layout ⇒ no tabs)\n * - `[]` / `undefined` → `undefined`\n *\n * Exported so the rules are unit-testable without standing up a registry.\n */\nexport function deriveActiveTabFromSegments(segments?: string[]): string | undefined {\n if (!segments || segments.length === 0) return undefined;\n\n let innermostGroup = -1;\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n if (typeof segment === 'string' && segment.startsWith('(') && segment.endsWith(')')) {\n innermostGroup = i;\n }\n }\n // No layout group at all — the app has no tab shell, so claiming an active\n // tab would be an invention.\n if (innermostGroup === -1) return undefined;\n\n const next = segments[innermostGroup + 1];\n // The group IS the leaf: the router is showing that group's index route.\n if (next === undefined) return 'index';\n if (typeof next !== 'string' || next.length === 0) return undefined;\n // A dynamic segment is a detail screen reached from a tab, not a tab itself.\n if (next.startsWith('[') && next.endsWith(']')) return undefined;\n return next;\n}\n\n/**\n * Project an element's runtime state into a runner-native pixel-space `bbox`.\n *\n * Returns `{x, y, w, h}` in PHYSICAL pixels (RN `state.layout` is logical dp;\n * we multiply by `pixelRatio` — `PixelRatio.get()`, injected via\n * `registry.setPixelRatio` — so the box aligns with the runner's adb screencap\n * frame). See {@link NativeElementBbox} for why the shape is `{x,y,w,h}` and\n * not the web SDK's `{x,y,width,height}`.\n *\n * `pixelRatio` is INJECTED rather than read from `react-native` here: this is a\n * non-React core module, and a static `import { PixelRatio } from 'react-native'`\n * breaks both the vitest suite (\"Flow is not supported\") and risks the\n * Metro/Hermes `unknownModuleError` that bit `design-handlers`/`page-health`.\n * The provider wires the real ratio via `setPixelRatio`; callers/tests that\n * never wire it get the `1` default (dp ≈ px, still a usable box).\n *\n * Gating (avoid poisoning the snapshot — the flat registry mixes routes and\n * some elements carry stale off-screen coords):\n * - returns `undefined` unless `visibility === 'visible'` (visible + measured)\n * - returns `undefined` when `state.layout` is null/absent\n * Clamps every component to a non-negative integer.\n *\n * Uses `measureInWindow`'s absolute `pageX`/`pageY` for the origin. Falls back\n * to the layout-relative `x`/`y` only when `pageX`/`pageY` weren't measured\n * (rare; keeps a best-effort box rather than dropping the element).\n */\nexport function projectBbox(\n state: NativeElementState,\n visibility: 'visible' | 'likely-visible' | 'hidden',\n pixelRatio = 1\n): NativeElementBbox | undefined {\n if (visibility !== 'visible') return undefined;\n const layout = state.layout;\n if (!layout) return undefined;\n\n const ratio = pixelRatio > 0 && Number.isFinite(pixelRatio) ? pixelRatio : 1;\n const toPx = (v: number): number => Math.max(0, Math.round(v * ratio));\n\n const hasPage =\n typeof layout.pageX === 'number' &&\n typeof layout.pageY === 'number' &&\n Number.isFinite(layout.pageX) &&\n Number.isFinite(layout.pageY);\n const originX = hasPage ? layout.pageX : layout.x;\n const originY = hasPage ? layout.pageY : layout.y;\n\n if (\n typeof originX !== 'number' ||\n typeof originY !== 'number' ||\n typeof layout.width !== 'number' ||\n typeof layout.height !== 'number' ||\n !Number.isFinite(originX) ||\n !Number.isFinite(originY) ||\n !Number.isFinite(layout.width) ||\n !Number.isFinite(layout.height)\n ) {\n return undefined;\n }\n\n return {\n x: toPx(originX),\n y: toPx(originY),\n w: toPx(layout.width),\n h: toPx(layout.height),\n };\n}\n\n// ── Visibility ──────────────────────────────────────────────────────────────\n\n/**\n * A rectangle in window (page) coordinates, logical dp — the same space\n * `measureInWindow` reports `pageX`/`pageY` in.\n */\nexport interface NativePageRect {\n left: number;\n top: number;\n right: number;\n bottom: number;\n}\n\n/**\n * Why an element is not plainly visible.\n *\n * DUPLICATE of `@qontinui/ui-bridge` `VisibilityReason`\n * (`src/core/types.ts` — `'hidden' | 'off-screen' | 'occluded' | 'no-layout'`),\n * for the same reason the action types above are duplicated: `@qontinui/ui-bridge`\n * is an OPTIONAL peer of this package, so this module must not import from it.\n * KEEP IN SYNC — one vocabulary across both SDKs is the point; do not coin\n * native-only spellings.\n *\n * `'occluded'` is declared but never produced here: React Native gives the\n * registry no hit-test equivalent of `document.elementFromPoint`, so this SDK\n * cannot honestly claim occlusion. It stays in the union so a native reason\n * string is always a valid web reason string.\n */\nexport type NativeVisibilityReason = 'hidden' | 'off-screen' | 'occluded' | 'no-layout';\n\n/** The three-value verdict carried on every snapshot element. */\nexport type NativeVisibility = 'visible' | 'likely-visible' | 'hidden';\n\n/**\n * Convert an element's measured layout into a page-space rect, or `null` when\n * it has no TRUSTWORTHY page-space origin.\n *\n * STRICTER THAN {@link projectBbox} ON PURPOSE. `projectBbox` falls back to the\n * parent-relative `x`/`y` when `pageX`/`pageY` weren't measured, because a\n * slightly-wrong box is still a usable box. Clipping cannot take that trade: a\n * parent-relative origin compared against window coordinates silently mixes two\n * coordinate spaces and yields a wrong `hidden` verdict — the outcome\n * `RegisterElementOptions.scrollAncestorId` calls worse than a coarse\n * `visible`. So a layout flagged `pageOriginUnmeasured` returns `null` here,\n * and {@link computeVisibility} declines to clip rather than guess.\n */\nexport function pageRectOf(state: NativeElementState): NativePageRect | null {\n const layout = state.layout;\n if (!layout) return null;\n // The writer told us `pageX`/`pageY` are a parent-relative stand-in, not a\n // `measureInWindow` result. Not a page-space rect, and not guessable into one.\n if (layout.pageOriginUnmeasured === true) return null;\n\n const hasPage =\n typeof layout.pageX === 'number' &&\n typeof layout.pageY === 'number' &&\n Number.isFinite(layout.pageX) &&\n Number.isFinite(layout.pageY);\n if (!hasPage) return null;\n\n const left = layout.pageX;\n const top = layout.pageY;\n\n if (\n typeof layout.width !== 'number' ||\n typeof layout.height !== 'number' ||\n !Number.isFinite(layout.width) ||\n !Number.isFinite(layout.height)\n ) {\n return null;\n }\n\n return { left, top, right: left + layout.width, bottom: top + layout.height };\n}\n\n/** Do two 1-D spans share any extent? A zero-length span counts as touching. */\nfunction spansOverlap(aMin: number, aMax: number, bMin: number, bMax: number): boolean {\n if (aMin === aMax) return aMin >= bMin && aMin <= bMax;\n return aMin < bMax && aMax > bMin;\n}\n\n/**\n * Is this rect empty — nothing can overlap it?\n *\n * {@link intersectRects} returns an INVERTED rect (`right < left`) when the two\n * inputs are disjoint, which is the honest answer for \"the reachable region\",\n * but `spansOverlap`'s `aMin < bMax && aMax > bMin` reads an inverted span as\n * overlapping. Every consumer of an intersection must ask this first.\n */\nexport function isEmptyRect(rect: NativePageRect): boolean {\n return rect.right <= rect.left || rect.bottom <= rect.top;\n}\n\n/**\n * Intersect two page rects, or `null` when either is absent (`null` means\n * UNKNOWN, so it never narrows the other side).\n *\n * The result may be EMPTY — check {@link isEmptyRect}. That happens whenever\n * the two rects are disjoint, e.g. a scroll container that has itself been\n * scrolled out of the window: nothing inside it can be on screen, and an empty\n * clip is exactly how that is expressed.\n */\nexport function intersectRects(\n a: NativePageRect | null,\n b: NativePageRect | null\n): NativePageRect | null {\n if (!a) return b;\n if (!b) return a;\n return {\n left: Math.max(a.left, b.left),\n top: Math.max(a.top, b.top),\n right: Math.min(a.right, b.right),\n bottom: Math.min(a.bottom, b.bottom),\n };\n}\n\n/**\n * Decide what a snapshot should say about an element being on screen.\n *\n * THE DEFECT THIS CLOSES: visibility used to be derived from mount + `measure()`\n * alone — `state.visible ? (state.layout ? 'visible' : 'likely-visible') : 'hidden'`\n * — with no comparison against any bounds at all. Every mounted, measured\n * element reported `visible`, including rows scrolled far past the fold, so a\n * snapshot claimed things were on screen that were not.\n *\n * SCOPE — read this before citing it. This is a PURE PROJECTION: it reads\n * `state.visible` and never writes it. An off-screen element still carries\n * `state.visible === true`; only the snapshot's `visibility` /\n * `visibilityReason` change. That is a deliberate divergence from the web SDK,\n * which folds off-screen INTO the boolean (`@qontinui/ui-bridge`\n * `src/core/registry.ts` — `if (!inViewport) return { visible: false, reason:\n * 'off-screen' }`), and it means any consumer asserting on `state.visible`\n * alone — `/manual-test`'s \"PRESENT IS NOT VISIBLE\" gate among them — sees no\n * change on mobile and stays as vacuous as it was. Closing that needs either a\n * matching demotion of `state.visible` here or a consumer that reads\n * `visibility`; both are contract decisions beyond this projection, and\n * neither is done. The strings are shared with the web SDK; the SHAPE is not.\n *\n * `clip` is the region the element must reach to count as on screen — the\n * window, intersected with the measured frame of a declared\n * `scrollAncestorId`. **A `null`/absent clip means the bounds are UNKNOWN, and\n * an unknown never demotes**: with no viewport injected (every non-RN caller,\n * including this package's own suite) the answer is exactly what it was before.\n * Absence of evidence is not evidence of off-screen.\n *\n * Any overlap at all counts as visible. A half-scrolled row IS on screen, and\n * a ratio threshold would be a second, unshared policy knob for callers to\n * disagree about.\n */\nexport function computeVisibility(\n state: NativeElementState,\n clip?: NativePageRect | null\n): { visibility: NativeVisibility; visibilityReason?: NativeVisibilityReason } {\n if (!state.visible) {\n return { visibility: 'hidden', visibilityReason: 'hidden' };\n }\n if (state.layout === null) {\n // Registered and mounted but not yet measured. `'likely-visible'` already\n // is this SDK's honest-UNKNOWN value; `'no-layout'` names why.\n return { visibility: 'likely-visible', visibilityReason: 'no-layout' };\n }\n if (!clip) {\n return { visibility: 'visible' };\n }\n const rect = pageRectOf(state);\n if (!rect) {\n // Measured, but with no trustworthy page-space origin (see `pageRectOf`).\n // Report it exactly as before — an unknown must not manufacture a verdict.\n return { visibility: 'visible' };\n }\n // A disjoint intersection (a scroll container itself scrolled off the window)\n // inverts rather than zeroing, and `spansOverlap` would read an inverted span\n // as overlapping. Nothing can be inside an empty clip.\n if (isEmptyRect(clip)) {\n return { visibility: 'hidden', visibilityReason: 'off-screen' };\n }\n const onScreen =\n spansOverlap(rect.left, rect.right, clip.left, clip.right) &&\n spansOverlap(rect.top, rect.bottom, clip.top, clip.bottom);\n\n return onScreen\n ? { visibility: 'visible' }\n : { visibility: 'hidden', visibilityReason: 'off-screen' };\n}\n\n/**\n * Copy a {@link RouteInfo} one level deep (its `params` / `queryParams`\n * objects and `routeStack` array too), so neither the caller of\n * `setRouteInfo` nor a snapshot consumer can mutate the registry's slot or a\n * previously emitted snapshot through a shared reference. Absent fields stay\n * absent, so the emitted object carries no `undefined`-valued keys it was not\n * given.\n */\nfunction copyRouteInfo(info: RouteInfo): RouteInfo {\n const copy: RouteInfo = { ...info };\n if (info.params) copy.params = { ...info.params };\n if (info.queryParams) copy.queryParams = { ...info.queryParams };\n if (info.routeStack) copy.routeStack = [...info.routeStack];\n return copy;\n}\n\n/**\n * Infer available actions based on element type.\n *\n * `scrollIntoView` is advertised only when the element DECLARES a\n * `scrollAncestorId`. It is the one input the action cannot work without (the\n * registry keeps no parent chain, so there is nothing else to scroll), and the\n * runner refuses any action an element does not advertise — so advertising it\n * everywhere would put a verb on every element that answers `NOT_SUPPORTED`\n * for all but the declared few. An ancestor that turns out to be unregistered\n * or not a ScrollView is still answered honestly by the executor\n * (`NOT_SUPPORTED`).\n */\nfunction inferActions(type: NativeElementType, scrollAncestorId?: string): NativeStandardAction[] {\n const baseActions: NativeStandardAction[] = scrollAncestorId\n ? ['focus', 'blur', 'scrollIntoView']\n : ['focus', 'blur'];\n\n switch (type) {\n case 'button':\n case 'touchable':\n case 'pressable':\n return [...baseActions, 'click', 'press', 'longPress', 'doubleTap'];\n case 'input':\n return [...baseActions, 'click', 'press', 'type', 'setValue', 'clear'];\n case 'text':\n return [...baseActions, 'click', 'press', 'longPress'];\n case 'view':\n return [...baseActions, 'click', 'press'];\n case 'scroll':\n return [...baseActions, 'scroll', 'swipe'];\n case 'list':\n return [...baseActions, 'scroll', 'swipe'];\n case 'listItem':\n return [...baseActions, 'click', 'press', 'longPress', 'swipe'];\n case 'switch':\n case 'checkbox':\n return [...baseActions, 'click', 'press', 'toggle'];\n case 'radio':\n return [...baseActions, 'click', 'press'];\n case 'image':\n return [...baseActions, 'click', 'press', 'longPress'];\n case 'modal':\n return ['focus', 'blur'];\n case 'custom':\n default:\n return [...baseActions, 'click', 'press'];\n }\n}\n\n/**\n * Native UI Bridge Registry\n *\n * Manages registration and lookup of native UI elements and components.\n */\nexport class NativeUIBridgeRegistry {\n private elements = new Map<string, RegisteredNativeElement>();\n private components = new Map<string, RegisteredNativeComponent>();\n private workflows = new Map<string, Workflow>();\n private eventListeners = new Map<BridgeEventType, Set<BridgeEventListener>>();\n private config: NativeRegistryConfig;\n private enrichers: NativeSnapshotEnrichers = {};\n private snapshotExtras = new Map<string, NativeSnapshotEnricher>();\n /**\n * App identification metadata (appId/appName/appType/framework). Set via\n * `setAppInfo` — typically wired in the server constructor from the\n * `NativeUIBridgeConfig` the host passes to `UIBridgeNativeProvider`.\n *\n * When set, `createSnapshot` includes it on the resulting snapshot so\n * agents can identify the app from snapshot bytes alone (parity with the\n * `uiBridge` block on the `health` response). Snapshots created before\n * this is wired simply omit the field.\n */\n private appInfo: NativeAppInfo | null = null;\n /**\n * Optional route provider. When set, `createSnapshot` reads\n * `currentRoute`/`segments` from it whenever the caller does not pass an\n * explicit `routeInfo` argument. This lets the default `getSnapshot` HTTP\n * handler — which has no direct reference to the `NativeUIBridgeServer`\n * instance — still emit a populated `currentRoute` field.\n *\n * Wired by `NativeUIBridgeServer.setRouteProvider`, which forwards the\n * provider into the registry alongside the per-handler overrides it\n * already installs.\n */\n private routeProvider: NativeRouteProviderLike | null = null;\n /**\n * The route TEMPLATE the app last reported through `useRouteAwareness`\n * (via {@link setRouteInfo}), emitted on every snapshot as `page.route`.\n *\n * Deliberately NOT named `routeInfo`: `createSnapshot`'s parameter of that\n * name is the CONCRETE `{currentRoute, segments, activeTab}` read from the\n * route provider. The two must never be confused — the concrete path is\n * user input, and this slot is the router-asserted template.\n */\n private reportedRoute: RouteInfo | undefined = undefined;\n /**\n * Device pixel ratio used to project `state.layout` (logical dp) into the\n * physical-pixel `bbox` emitted on each snapshot element. Defaults to `1`\n * (dp ≈ px) so callers/tests that never inject a ratio still get a usable\n * box; the React provider wires the real `PixelRatio.get()` via\n * {@link setPixelRatio}. Read from a non-React core module on purpose — see\n * the `projectBbox` doc comment for why we don't import `react-native` here.\n */\n private pixelRatio = 1;\n /**\n * Injected getter for the device window size in logical dp, used to clip\n * reported visibility. Injected rather than read here for exactly the reason\n * `pixelRatio` is: a static `import { Dimensions } from 'react-native'` in\n * this non-React core module breaks the vitest suite and risks the\n * Metro/Hermes `unknownModuleError`. It is a PROVIDER, not a value, so a\n * rotation is picked up on the next snapshot with nothing to keep in sync.\n *\n * `null` means the window bounds are UNKNOWN, and {@link computeVisibility}\n * treats unknown bounds as \"cannot demote\" — never as an empty viewport.\n */\n private viewportProvider: (() => { width: number; height: number }) | null = null;\n /**\n * Sticky flag: flips `true` the first time any element is registered and\n * stays `true` even after elements are unregistered. Lets agents distinguish\n * \"this route never wired any elements\" from \"this route registered then\n * unmounted\".\n */\n private everHadRegistrations = false;\n\n constructor(config: NativeRegistryConfig = {}) {\n this.config = config;\n }\n\n // ============================================================================\n // App Info & Route Provider\n // ============================================================================\n\n /**\n * Set the app identification metadata returned alongside snapshots.\n * Idempotent — re-calling with the same shape is a no-op.\n * Pass `null` (or omit a previous call) to clear.\n */\n setAppInfo(info: NativeAppInfo | null | undefined): void {\n this.appInfo = info ?? null;\n }\n\n /** Read-only accessor for the registered app info, if any. */\n getAppInfo(): NativeAppInfo | null {\n return this.appInfo;\n }\n\n /**\n * Set a route provider whose `getCurrentRoute`/`getSegments` are read by\n * `createSnapshot` when no explicit `routeInfo` argument is supplied.\n *\n * Call with `null` to detach (e.g. when the host swaps providers on HMR).\n */\n setRouteProvider(provider: NativeRouteProviderLike | null | undefined): void {\n this.routeProvider = provider ?? null;\n }\n\n /** Read-only accessor for the registered route provider, if any. */\n getRouteProvider(): NativeRouteProviderLike | null {\n return this.routeProvider;\n }\n\n /**\n * Set the route info emitted as `page.route` on every snapshot — the\n * router-asserted TEMPLATE, normally reported by `useRouteAwareness`.\n * Pass `undefined` to clear it (the hook does so on unmount), after which\n * snapshots omit `page` again.\n *\n * The value is copied, so a later mutation by the caller cannot alter what\n * is stored or what an already-emitted snapshot holds.\n */\n setRouteInfo(info: RouteInfo | undefined): void {\n this.reportedRoute = info ? copyRouteInfo(info) : undefined;\n }\n\n /** Read-only accessor for the reported route info, if any (a copy). */\n getRouteInfo(): RouteInfo | undefined {\n return this.reportedRoute ? copyRouteInfo(this.reportedRoute) : undefined;\n }\n\n /**\n * Resolve the snapshot's `activeTab`: an explicit `getActiveTab()` on the\n * route provider wins, otherwise derive it from the Expo Router segments the\n * provider already exposes.\n *\n * A throwing or empty-string provider degrades to the derivation rather than\n * to `undefined` — the host's opt-in extra should never be able to make the\n * snapshot WORSE than it was without it. A blank derivation stays blank.\n */\n private resolveActiveTab(segments: string[] | undefined): string | undefined {\n const explicit = (() => {\n if (!this.routeProvider?.getActiveTab) return undefined;\n try {\n const value = this.routeProvider.getActiveTab();\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n } catch (error) {\n if (this.config.verbose) {\n console.warn(`[ui-bridge-native] routeProvider getActiveTab threw:`, error);\n }\n return undefined;\n }\n })();\n return explicit ?? deriveActiveTabFromSegments(segments);\n }\n\n /**\n * Set the device pixel ratio used to project per-element `bbox` geometry\n * into physical pixels. The React provider passes `PixelRatio.get()` here\n * once at startup. Ignores non-finite / non-positive values (keeps the\n * previous ratio) so a bad call can't zero out every bbox.\n */\n setPixelRatio(ratio: number): void {\n if (typeof ratio === 'number' && Number.isFinite(ratio) && ratio > 0) {\n this.pixelRatio = ratio;\n }\n }\n\n /** Read-only accessor for the device pixel ratio used for bbox projection. */\n getPixelRatio(): number {\n return this.pixelRatio;\n }\n\n /**\n * Set the getter for the device window size (logical dp) that reported\n * visibility is clipped against. The React provider passes\n * `() => Dimensions.get('window')` here at construction.\n *\n * Pass `null` to detach. While detached the window bounds are UNKNOWN and no\n * element is ever demoted for being outside them.\n */\n setViewportProvider(\n provider: (() => { width: number; height: number }) | null | undefined\n ): void {\n this.viewportProvider = provider ?? null;\n }\n\n /**\n * The window as a page-space rect, or `null` when it is unknown or the\n * provider returned something unusable (it throws in some RN teardown\n * paths, and a zero-size window would silently hide the entire screen).\n */\n getViewportRect(): NativePageRect | null {\n if (!this.viewportProvider) return null;\n let size: { width: number; height: number };\n try {\n size = this.viewportProvider();\n } catch (error) {\n if (this.config.verbose) {\n console.warn(`[ui-bridge-native] viewportProvider threw:`, error);\n }\n return null;\n }\n const { width, height } = size ?? {};\n if (\n typeof width !== 'number' ||\n typeof height !== 'number' ||\n !Number.isFinite(width) ||\n !Number.isFinite(height) ||\n width <= 0 ||\n height <= 0\n ) {\n return null;\n }\n return { left: 0, top: 0, right: width, bottom: height };\n }\n\n /**\n * The region `element` must reach to count as on screen: the window,\n * intersected with the measured frame of its declared `scrollAncestorId`.\n *\n * Returns `null` when neither bound is known — see {@link computeVisibility}\n * for why that means \"do not demote\" rather than \"nothing is visible\".\n * An ancestor that is not registered, or registered but not yet measured,\n * contributes nothing rather than hiding its subtree.\n */\n getClipRectFor(\n element: RegisteredNativeElement,\n viewport?: NativePageRect | null\n ): NativePageRect | null {\n // `undefined` = not supplied, read it now. `null` = the caller already\n // read it and it was UNKNOWN — don't re-read and disagree with them.\n const bounds = viewport === undefined ? this.getViewportRect() : viewport;\n const ancestorId = element.scrollAncestorId;\n if (!ancestorId) return bounds;\n\n // Single level by design: only the DECLARED ancestor is applied, and its\n // own `scrollAncestorId` is not followed. The window clip already covers\n // most of what a second level would add, and a recursive walk needs a\n // visited set to survive a declaration cycle. Revisit together.\n const ancestor = this.elements.get(ancestorId);\n if (!ancestor || ancestorId === element.id) return bounds;\n\n return intersectRects(bounds, pageRectOf(ancestor.getState()));\n }\n\n // ============================================================================\n // Snapshot Enricher Slots\n // ============================================================================\n\n /**\n * Register/replace canonical enrichers (modal/toast/undo). HMR-safe — calling\n * with a partial set merges into existing slots instead of clobbering them.\n */\n setEnrichers(e: Partial<NativeSnapshotEnrichers>): void {\n this.enrichers = { ...this.enrichers, ...e };\n }\n\n /**\n * Read-only accessor for the canonical enrichers slot. Returned object is\n * the live reference, so callers can detect \"no detector wired\" via\n * `getEnrichers().modalDetector === undefined`. Used by the test-hook HTTP\n * handlers (`control/modal/push`, `control/modal/dismiss/:id`) which need\n * to drive the ModalDetector directly without re-instantiating it.\n */\n getEnrichers(): Readonly<NativeSnapshotEnrichers> {\n return this.enrichers;\n }\n\n /**\n * Register a custom snapshot enricher. The returned object will be\n * `Object.assign`ed onto the snapshot, so use unique top-level keys to avoid\n * clobbering canonical fields. Returns a disposer.\n */\n registerSnapshotEnricher(name: string, fn: NativeSnapshotEnricher): () => void {\n this.snapshotExtras.set(name, fn);\n return () => this.unregisterSnapshotEnricher(name);\n }\n\n /** Remove a custom snapshot enricher by name */\n unregisterSnapshotEnricher(name: string): void {\n this.snapshotExtras.delete(name);\n }\n\n // ============================================================================\n // Element Management\n // ============================================================================\n\n /**\n * Register a native element\n */\n registerElement(\n id: string,\n ref: React.RefObject<NativeElementRef>,\n options: RegisterElementOptions = {}\n ): RegisteredNativeElement {\n const {\n type = 'custom',\n label,\n customActions,\n props,\n treePath = id,\n testId,\n accessibilityLabel,\n registrationRoute,\n scrollAncestorId,\n flatStyle,\n stateStyles,\n } = options;\n const actions = options.actions ?? inferActions(type, scrollAncestorId);\n\n // Create state getter\n const getState = (): NativeElementState => {\n const element = ref.current;\n if (!element) {\n return {\n mounted: false,\n visible: false,\n enabled: false,\n focused: false,\n layout: null,\n };\n }\n\n // State is populated by the element during onLayout\n // Here we return the stored state from the element's metadata\n const stored = this.elements.get(id);\n if (stored && stored.getState !== getState) {\n return stored.getState();\n }\n\n return {\n mounted: true,\n visible: true,\n enabled: true,\n focused: false,\n layout: null,\n };\n };\n\n // Create identifier getter\n const getIdentifier = (): NativeElementIdentifier => ({\n uiId: id,\n testId: testId || id,\n accessibilityLabel,\n treePath,\n });\n\n const registered: RegisteredNativeElement = {\n id,\n ref,\n type,\n label,\n actions,\n customActions,\n props,\n getState,\n getIdentifier,\n registeredAt: Date.now(),\n mounted: true,\n registrationRoute: registrationRoute ?? null,\n scrollAncestorId,\n flatStyle,\n stateStyles,\n };\n\n this.elements.set(id, registered);\n this.everHadRegistrations = true;\n\n this.emit('element:registered', { id, type, label });\n\n if (this.config.verbose) {\n console.log(`[ui-bridge-native] Registered element: ${id} (${type})`);\n }\n\n // Seed a sensible initial state so elements are immediately visible in snapshots\n // even before onLayout fires. Layout-measuring code can overwrite this later.\n //\n // For controlled-input elements (`type:'input'` with a string `props.value`),\n // also seed `state.value` from the prop so bridge consumers can read the\n // default value before the user has typed. See `updateElementProps` for\n // the corresponding re-sync path on prop changes.\n const seededState: Partial<NativeElementState> = {\n mounted: true,\n visible: true,\n enabled: true,\n focused: false,\n layout: null,\n };\n if (type === 'input' && typeof props?.value === 'string') {\n seededState.value = props.value;\n }\n // For `type:'text'` elements, surface the (dynamic) label as `state.value`\n // so bridge consumers can read text content with a uniform `state.value`\n // accessor instead of parsing the `label` field (\"Status: ...\"). The hook\n // already re-publishes the label via `updateElementMeta` when consumers\n // call `updateLabel(newLabel)` — that path also re-syncs `state.value`\n // (see `updateElementMeta`).\n if (type === 'text' && typeof label === 'string') {\n seededState.value = label;\n }\n this.updateElementState(id, seededState);\n\n return registered;\n }\n\n /**\n * Unregister an element\n */\n unregisterElement(id: string): void {\n const element = this.elements.get(id);\n if (element) {\n this.elements.delete(id);\n this.emit('element:unregistered', { id });\n\n if (this.config.verbose) {\n console.log(`[ui-bridge-native] Unregistered element: ${id}`);\n }\n }\n }\n\n /**\n * Get a registered element\n */\n getElement(id: string): RegisteredNativeElement | undefined {\n return this.elements.get(id);\n }\n\n /**\n * Get all registered elements\n */\n getAllElements(): RegisteredNativeElement[] {\n return Array.from(this.elements.values());\n }\n\n /**\n * Get all registered elements that are visible AND have a measured layout.\n *\n * This is the *strict* visibility filter: requires `state.visible === true`\n * AND `state.layout !== null`. Use this when you need both signals\n * (e.g. coord-based tap, where you need an actual rect to hit-test against).\n *\n * Callers driving an LLM agent typically want\n * {@link getMountedVisibleElements} instead — that variant accepts\n * elements that *should* be on screen (visible:true) but whose first\n * `onLayout` has not yet fired. Without it, a snapshot taken in the gap\n * between mount and the first layout pass returns 0 elements on a fully\n * populated screen.\n */\n getVisibleElements(): RegisteredNativeElement[] {\n return this.getAllElements().filter((e) => {\n const state = e.getState();\n return state.visible && state.layout !== null;\n });\n }\n\n /**\n * Get all registered elements marked as visible (regardless of layout).\n *\n * Looser than {@link getVisibleElements}: includes elements whose\n * `onLayout` callback has not yet fired (so `layout === null`). React\n * Native's `onLayout` is async — there's a one-tick window after mount\n * where elements are in the tree, the user *sees* them, but the\n * registry hasn't received their measurements yet. Excluding those\n * elements makes `getSnapshot?visibleOnly=true` return 0 on a populated\n * screen, which is a worse failure mode than including a few elements\n * we can't yet locate spatially.\n *\n * Used by `createSnapshot`'s `visibleOnly` filter and by the\n * `getSnapshot` / `getElements` handlers. Each emitted element carries a\n * `visibility` discriminator (`\"visible\"` / `\"likely-visible\"`) so\n * agents that *do* require a known rect can branch downstream.\n */\n getMountedVisibleElements(): RegisteredNativeElement[] {\n return this.getAllElements().filter((e) => e.getState().visible);\n }\n\n /**\n * Get elements registered on a specific route (for page-scoped filtering)\n */\n getElementsForRoute(route: string): RegisteredNativeElement[] {\n return this.getAllElements().filter((e) => e.registrationRoute === route);\n }\n\n /**\n * Mark elements registered on a route as off-screen (visible: false, layout: null).\n *\n * Use this when a screen loses focus but stays mounted — common in React Navigation\n * tab navigators where inactive tabs remain in the tree. Without this call, stale\n * `layout` data lingers in snapshots and makes off-screen elements look rendered.\n *\n * Does NOT unregister the elements — they stay registered so the user's next visit\n * re-measures them via `onLayout` without re-mount cost. Elements without a\n * `registrationRoute` (app-wide registrations) are untouched.\n */\n markRouteOffscreen(route: string): void {\n // Guard against accidental global wipes. Elements registered without a\n // route have `registrationRoute: null` — passing null/empty here would\n // match every globally-registered element and erase their layouts.\n if (route == null || route === '') {\n if (this.config.verbose) {\n console.warn(\n `[ui-bridge-native] markRouteOffscreen called with null/empty route — ignoring`\n );\n }\n return;\n }\n let cleared = 0;\n for (const element of this.elements.values()) {\n if (element.registrationRoute === route) {\n this.updateElementState(element.id, {\n visible: false,\n layout: null,\n });\n cleared++;\n }\n }\n if (this.config.verbose && cleared > 0) {\n console.log(`[ui-bridge-native] Marked ${cleared} elements offscreen for route: ${route}`);\n }\n }\n\n /**\n * Re-measure every mounted-visible element's geometry via its stored React\n * ref and write the fresh coordinates into the registry (\"measure-on-\n * snapshot\").\n *\n * Why: element geometry is otherwise captured ONCE at mount time\n * (`onLayout` + `measureInWindow` in `useUIElement`) and RN never re-fires\n * `onLayout` on pure translation — scrolling a virtualized list\n * (FlashList), recycling a cell, a keyboard shift or an orientation change\n * all move elements WITHOUT updating the stored `state.layout`. Snapshot\n * consumers (vision analyzers hit-testing `bbox`, coord-based tap) then\n * operate on stale positions. Re-measuring at READ time is correct\n * regardless of what moved the element and needs zero per-consumer wiring:\n * every registry entry already stores its ref (see `registerElement`).\n *\n * Per-element outcomes:\n * - non-zero dims → write a fresh full layout (same state shape as the\n * mount-time measure loop in `useUIElement`); counted as `measured`.\n * - zero dims AND the element previously had a non-null `state.layout` →\n * the view collapsed / left the screen; clear to\n * `{ visible: false, layout: null }` (same shape `markRouteOffscreen`\n * writes); counted as `cleared`.\n * - zero dims and it never had a layout → leave state untouched (element\n * in the mount→first-onLayout gap; don't demote `likely-visible`);\n * counted as `skipped`.\n * - no ref / no callable `measureInWindow` (test fixtures, web) → leave\n * untouched; counted as `skipped`.\n *\n * The whole sweep races an overall timeout (default 250 ms) so a dead ref\n * whose `measureInWindow` never calls back can never hang a snapshot: the\n * returned promise resolves at the deadline with whatever counts have\n * accumulated. Late callbacks may still write state afterwards — harmless,\n * freshest data wins. Never throws.\n *\n * Non-React core module on purpose — `measureInWindow` is duck-typed off\n * the stored ref rather than imported from `react-native` (see the\n * `projectBbox` doc comment for why this file must not import RN).\n */\n async refreshMeasurements(options?: {\n timeoutMs?: number;\n }): Promise<{ measured: number; cleared: number; skipped: number }> {\n const timeoutMs =\n typeof options?.timeoutMs === 'number' &&\n Number.isFinite(options.timeoutMs) &&\n options.timeoutMs > 0\n ? options.timeoutMs\n : 250;\n\n const counts = { measured: 0, cleared: 0, skipped: 0 };\n const pending: Array<Promise<void>> = [];\n\n for (const element of this.getMountedVisibleElements()) {\n const node = element.ref?.current as\n | (NativeElementRef & {\n measureInWindow?: (\n callback: (pageX: number, pageY: number, w: number, h: number) => void\n ) => void;\n })\n | null\n | undefined;\n\n if (!node || typeof node.measureInWindow !== 'function') {\n counts.skipped++;\n continue;\n }\n\n const id = element.id;\n pending.push(\n new Promise<void>((resolve) => {\n let settled = false;\n try {\n node.measureInWindow!((pageX: number, pageY: number, w: number, h: number) => {\n if (settled) return;\n settled = true;\n try {\n if (w > 0 && h > 0) {\n this.updateElementState(id, {\n mounted: true,\n visible: true,\n enabled: true,\n focused: false,\n layout: { x: pageX, y: pageY, width: w, height: h, pageX, pageY },\n });\n counts.measured++;\n } else if (this.elements.get(id)?.getState().layout != null) {\n // Collapsed / off-screen: it HAD a measured rect and now\n // reports zeros. Clear it so stale coords can't poison the\n // snapshot (same shape as markRouteOffscreen).\n this.updateElementState(id, { visible: false, layout: null });\n counts.cleared++;\n } else {\n counts.skipped++;\n }\n } finally {\n resolve();\n }\n });\n } catch {\n // A throwing measureInWindow must never break the sweep.\n if (!settled) {\n settled = true;\n counts.skipped++;\n resolve();\n }\n }\n })\n );\n }\n\n if (pending.length > 0) {\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, timeoutMs);\n void Promise.all(pending).then(() => {\n clearTimeout(timer);\n resolve();\n });\n });\n }\n\n return counts;\n }\n\n /**\n * Update element state\n */\n updateElementState(id: string, state: Partial<NativeElementState>): void {\n const element = this.elements.get(id);\n if (element) {\n // Create a new getState that includes the updated state\n const currentState = element.getState();\n const newState = { ...currentState, ...state };\n\n const updated: RegisteredNativeElement = {\n ...element,\n getState: () => newState,\n };\n\n this.elements.set(id, updated);\n this.emit('element:stateChanged', { id, state: newState });\n }\n }\n\n /**\n * Update an element's descriptive metadata (label / accessibilityLabel /\n * testId). Sister to {@link updateElementState} (which carries layout +\n * visibility) and {@link updateElementProps} (which carries event handlers).\n *\n * Idempotent — if every passed field equals the existing value, the call\n * returns without mutating the entry or emitting an event. This keeps\n * `useUIElement().updateLabel(...)` cheap when consumers re-publish from a\n * render path that produces the same string on every tick.\n *\n * Returns `true` when the entry was mutated, `false` otherwise. Callers\n * that want to gate a `console.warn` on no-op invocations can use the\n * return value instead of recomputing equality.\n */\n updateElementMeta(\n id: string,\n meta: { label?: string; accessibilityLabel?: string; testId?: string }\n ): boolean {\n const element = this.elements.get(id);\n if (!element) return false;\n\n // The `testId` on a registered element lives inside the identifier\n // closure (see registerElement's `getIdentifier`). To rebuild it we\n // need the current value — read it via the existing identifier and\n // overlay the new one if provided.\n const currentIdentifier = element.getIdentifier();\n const nextLabel = meta.label !== undefined ? meta.label : element.label;\n const nextAccessibilityLabel =\n meta.accessibilityLabel !== undefined\n ? meta.accessibilityLabel\n : currentIdentifier.accessibilityLabel;\n const nextTestId = meta.testId !== undefined ? meta.testId : currentIdentifier.testId;\n\n const labelChanged = nextLabel !== element.label;\n const accessibilityLabelChanged =\n nextAccessibilityLabel !== currentIdentifier.accessibilityLabel;\n const testIdChanged = nextTestId !== currentIdentifier.testId;\n\n if (!labelChanged && !accessibilityLabelChanged && !testIdChanged) {\n return false;\n }\n\n const updated: RegisteredNativeElement = {\n ...element,\n label: nextLabel,\n getIdentifier: (): NativeElementIdentifier => ({\n uiId: id,\n testId: nextTestId,\n accessibilityLabel: nextAccessibilityLabel,\n treePath: currentIdentifier.treePath,\n }),\n };\n this.elements.set(id, updated);\n\n // For text-type elements, mirror the new label into `state.value` so\n // bridge consumers can read text content with a uniform `state.value`\n // accessor. Initial seed lives in `registerElement`; this keeps the\n // mirror in sync when consumers call `updateLabel(newLabel)` from a\n // state-driven render path (e.g. `Status: ${connected ? 'online' : 'offline'}`).\n if (element.type === 'text' && labelChanged && typeof nextLabel === 'string') {\n this.updateElementState(id, { value: nextLabel });\n }\n\n // Reuse `element:registered` rather than minting a new event — downstream\n // consumers already subscribe to it for label/identifier diffs, and\n // adding a new BridgeEventType ('element:metaChanged') would force a\n // SemVer-major bump on every snapshot consumer.\n this.emit('element:registered', {\n id,\n type: element.type,\n label: nextLabel,\n });\n\n return true;\n }\n\n /**\n * Update element props (for action execution).\n *\n * Side effect for `type: 'input'` elements: when `props.value` is a string,\n * mirror it into `state.value` so AI drivers reading the registry don't see\n * `state.value === undefined` until the user types. Before this mirror,\n * controlled-input consumers (`<TextInput value={x} onChangeText={setX} />`\n * with `captureProps({ value: x, onChangeText: setX })`) registered an\n * input whose `state.value` only became defined after the FIRST\n * `onChangeText` call — bridge consumers couldn't read the default value\n * (e.g. a pre-populated API-URL input) until someone typed into it.\n *\n * Re-runs on every `updateElementProps` so a parent passing a fresh\n * `value` prop (controlled re-render) keeps the registry in sync. The\n * mirror is gated on `type === 'input'` to avoid touching non-input\n * elements that may carry an unrelated `value` prop (e.g. a Switch's\n * boolean value, which lives on `state.checked`).\n */\n updateElementProps(id: string, props: Record<string, unknown>): void {\n const element = this.elements.get(id);\n if (element) {\n const updated: RegisteredNativeElement = {\n ...element,\n props: { ...element.props, ...props },\n };\n this.elements.set(id, updated);\n\n // Controlled-input value mirror — see doc comment above.\n if (\n element.type === 'input' &&\n Object.prototype.hasOwnProperty.call(props, 'value') &&\n typeof props.value === 'string'\n ) {\n const currentState = updated.getState();\n if (currentState.value !== props.value) {\n this.updateElementState(id, { value: props.value });\n }\n }\n }\n }\n\n /**\n * Update element style for design review\n */\n updateElementStyle(\n id: string,\n flatStyle: Record<string, unknown>,\n stateStyles?: {\n pressed?: Record<string, unknown>;\n focused?: Record<string, unknown>;\n disabled?: Record<string, unknown>;\n }\n ): void {\n const element = this.elements.get(id);\n if (element) {\n const updated: RegisteredNativeElement = {\n ...element,\n flatStyle,\n ...(stateStyles !== undefined ? { stateStyles } : {}),\n };\n this.elements.set(id, updated);\n }\n }\n\n /**\n * Get element style for design review\n */\n getElementStyle(id: string): Record<string, unknown> | null {\n const element = this.elements.get(id);\n return element?.flatStyle ?? null;\n }\n\n /**\n * Find element by testID\n */\n findByTestId(testId: string): RegisteredNativeElement | undefined {\n for (const element of this.elements.values()) {\n const identifier = element.getIdentifier();\n if (identifier.testId === testId) {\n return element;\n }\n }\n return undefined;\n }\n\n /**\n * Find elements by type\n */\n findByType(type: NativeElementType): RegisteredNativeElement[] {\n return Array.from(this.elements.values()).filter((e) => e.type === type);\n }\n\n // ============================================================================\n // Component Management\n // ============================================================================\n\n /**\n * Register a component\n */\n registerComponent(id: string, options: RegisterComponentOptions): RegisteredNativeComponent {\n const { name, description, actions = [], elementIds } = options;\n\n const registered: RegisteredNativeComponent = {\n id,\n name,\n description,\n // Closed field list, applied once — see `toRegisteredComponentActions`.\n actions: toRegisteredComponentActions(actions),\n elementIds,\n registeredAt: Date.now(),\n mounted: true,\n };\n\n this.components.set(id, registered);\n\n this.emit('component:registered', { id, name });\n\n if (this.config.verbose) {\n console.log(`[ui-bridge-native] Registered component: ${id} (${name})`);\n }\n\n return registered;\n }\n\n /**\n * Update a registered component's declaration in place — name, description,\n * actions, elementIds.\n *\n * Sister to {@link updateElementMeta}, and it exists for the same reason one\n * level up. `registerComponent` publishes the declaration exactly once, and\n * `useUIComponent` registers on mount and never again, so an action whose\n * `label` carries a count, an action that only exists while a row is\n * selected, a `paramSchema` that widens once options load, or an `elementIds`\n * list that grows after mount, all published their mount-time shape to\n * `/control/components` forever. PR #176 closed that hole for an ELEMENT's\n * label; this closes it for a COMPONENT's declaration.\n *\n * **Idempotent by VALUE over the PUBLISHED shape.** `handler` identity is\n * excluded from the comparison — call sites pass inline closures that are\n * freshly allocated every render, so including it would report a change on\n * every call and make the door useless for a render-path publisher. The\n * newest handlers are still STORED whenever `actions` is supplied, which is\n * the other half of the same defect: without it the registry keeps the\n * closure from the render that registered the component, so a handler reading\n * component state read the state as it was at mount.\n *\n * Returns `true` when the published shape changed and an event was emitted,\n * `false` otherwise — the same contract as `updateElementMeta`, so a caller\n * can gate a warning or a state write on a real change.\n */\n updateComponentMeta(id: string, meta: UpdateComponentMetaOptions): boolean {\n const component = this.components.get(id);\n if (!component) return false;\n\n const nextName = meta.name !== undefined ? meta.name : component.name;\n const nextDescription =\n meta.description !== undefined ? meta.description : component.description;\n const nextActions =\n meta.actions !== undefined ? toRegisteredComponentActions(meta.actions) : component.actions;\n const nextElementIds = meta.elementIds !== undefined ? meta.elementIds : component.elementIds;\n\n const changed =\n nextName !== component.name ||\n nextDescription !== component.description ||\n !sameElementIds(nextElementIds, component.elementIds) ||\n publishedActionSignature(nextActions) !== publishedActionSignature(component.actions);\n\n // Nothing published changed AND no fresh handlers were offered: leave the\n // stored entry untouched so its identity stays stable for consumers holding\n // a reference.\n if (!changed && meta.actions === undefined) return false;\n\n // Store even when `changed` is false, provided actions were supplied: the\n // handlers in `nextActions` are the CURRENT render's closures. Keeping the\n // old ones is exactly how a component action ends up reading state from the\n // render it was first declared in.\n this.components.set(id, {\n ...component,\n name: nextName,\n description: nextDescription,\n actions: nextActions,\n elementIds: nextElementIds,\n });\n\n if (!changed) return false;\n\n // Reuse `component:registered` rather than minting a\n // 'component:metaChanged' BridgeEventType — same reasoning as\n // `updateElementMeta`'s reuse of `element:registered`: a new event type is a\n // breaking change for every consumer switching exhaustively over the union.\n this.emit('component:registered', { id, name: nextName });\n\n if (this.config.verbose) {\n console.log(`[ui-bridge-native] Updated component: ${id} (${nextName})`);\n }\n\n return true;\n }\n\n /**\n * Unregister a component\n */\n unregisterComponent(id: string): void {\n const component = this.components.get(id);\n if (component) {\n this.components.delete(id);\n this.emit('component:unregistered', { id });\n\n if (this.config.verbose) {\n console.log(`[ui-bridge-native] Unregistered component: ${id}`);\n }\n }\n }\n\n /**\n * Get a registered component\n */\n getComponent(id: string): RegisteredNativeComponent | undefined {\n return this.components.get(id);\n }\n\n /**\n * Get all registered components\n */\n getAllComponents(): RegisteredNativeComponent[] {\n return Array.from(this.components.values());\n }\n\n // ============================================================================\n // Workflow Management\n // ============================================================================\n\n /**\n * Register a workflow\n */\n registerWorkflow(workflow: Workflow): void {\n this.workflows.set(workflow.id, workflow);\n\n if (this.config.verbose) {\n console.log(`[ui-bridge-native] Registered workflow: ${workflow.id}`);\n }\n }\n\n /**\n * Unregister a workflow\n */\n unregisterWorkflow(id: string): void {\n this.workflows.delete(id);\n }\n\n /**\n * Get a workflow\n */\n getWorkflow(id: string): Workflow | undefined {\n return this.workflows.get(id);\n }\n\n /**\n * Get all workflows\n */\n getAllWorkflows(): Workflow[] {\n return Array.from(this.workflows.values());\n }\n\n // ============================================================================\n // Event System\n // ============================================================================\n\n /**\n * Subscribe to events\n */\n on<T = unknown>(type: BridgeEventType, listener: BridgeEventListener<T>): () => void {\n if (!this.eventListeners.has(type)) {\n this.eventListeners.set(type, new Set());\n }\n this.eventListeners.get(type)!.add(listener as BridgeEventListener);\n\n // Return unsubscribe function\n return () => this.off(type, listener);\n }\n\n /**\n * Unsubscribe from events\n */\n off<T = unknown>(type: BridgeEventType, listener: BridgeEventListener<T>): void {\n this.eventListeners.get(type)?.delete(listener as BridgeEventListener);\n }\n\n /**\n * Emit an event\n */\n emit(type: BridgeEventType, data: unknown): void {\n const event: BridgeEvent = {\n type,\n timestamp: Date.now(),\n data,\n };\n\n // Notify listeners\n const listeners = this.eventListeners.get(type);\n if (listeners) {\n for (const listener of listeners) {\n try {\n listener(event);\n } catch (error) {\n console.error(`[ui-bridge-native] Event listener error:`, error);\n }\n }\n }\n\n // Notify global handler\n if (this.config.onEvent) {\n try {\n this.config.onEvent(event);\n } catch (error) {\n console.error(`[ui-bridge-native] Global event handler error:`, error);\n }\n }\n }\n\n // ============================================================================\n // Snapshots\n // ============================================================================\n\n /**\n * Create a snapshot of the current state.\n *\n * Route resolution: `routeInfo` wins when provided. Otherwise — and this\n * is the path the default `getSnapshot` HTTP handler takes — we fall back\n * to the registered `routeProvider`. This means callers that never wire a\n * server-level override (the bare-server / cloud-relay path) still get a\n * populated `currentRoute` field as long as `setRouteProvider` was called\n * on the registry.\n *\n * `activeTab` resolves through {@link resolveActiveTab} on BOTH branches. An\n * explicit `routeInfo.activeTab` wins outright; otherwise a registered\n * provider's `getActiveTab()` answers and the Expo Router derivation is the\n * fallback. Deriving straight from `routeInfo.segments` here instead — which\n * is what this branch did when `activeTab` was introduced — silently ignored\n * `RouteProvider.getActiveTab` on the ONLY path that serves\n * `control/snapshot` once `setRouteProvider` is wired (the server installs a\n * `getSnapshot` override that passes `routeInfo`), so the opt-in hook for an\n * app whose visible pane is decoupled from the router reached no consumer.\n */\n createSnapshot(\n routeInfo?: {\n currentRoute?: string | null;\n segments?: string[];\n /**\n * Explicit active tab for callers that own the answer outright. Omit to\n * let the registered `routeProvider` / the segment derivation resolve it.\n */\n activeTab?: string | null;\n },\n options?: { visibleOnly?: boolean; currentRouteOnly?: boolean }\n ): NativeBridgeSnapshot {\n // Resolve route info: prefer the explicit argument, fall back to a\n // registered route provider. We treat an explicitly-passed `routeInfo`\n // (even if both fields are null/undefined) as \"the caller is in charge\"\n // so server-layer overrides that pass `{currentRoute: null}` keep working.\n const resolvedRoute: {\n currentRoute: string | null;\n segments: string[] | undefined;\n activeTab: string | undefined;\n } = (() => {\n if (routeInfo !== undefined) {\n const explicit =\n typeof routeInfo.activeTab === 'string' && routeInfo.activeTab.length > 0\n ? routeInfo.activeTab\n : undefined;\n return {\n currentRoute: routeInfo.currentRoute ?? null,\n segments: routeInfo.segments,\n activeTab: explicit ?? this.resolveActiveTab(routeInfo.segments),\n };\n }\n if (this.routeProvider) {\n try {\n const segments = this.routeProvider.getSegments?.();\n return {\n currentRoute: this.routeProvider.getCurrentRoute() ?? null,\n segments,\n activeTab: this.resolveActiveTab(segments),\n };\n } catch (error) {\n if (this.config.verbose) {\n console.warn(`[ui-bridge-native] routeProvider getCurrentRoute threw:`, error);\n }\n return { currentRoute: null, segments: undefined, activeTab: undefined };\n }\n }\n return { currentRoute: null, segments: undefined, activeTab: undefined };\n })();\n\n // visibleOnly uses the *looser* mounted-visible filter so a snapshot\n // taken in the gap between mount and the first `onLayout` doesn't\n // return 0 elements on a populated screen. Each element in the\n // payload carries a `visibility` field so agents can branch on\n // measured vs unmeasured.\n let elements = options?.visibleOnly ? this.getMountedVisibleElements() : this.getAllElements();\n\n // Filter to elements that belong on the current route. Route-agnostic\n // elements (no `registrationRoute`) pass through too — see the\n // `matchesCurrentRoute` doc comment for the rationale (tab bars, modal\n // hosts and other app-root registrations have a null route and apply to\n // every screen).\n if (options?.currentRouteOnly && resolvedRoute.currentRoute) {\n const currentRoute = resolvedRoute.currentRoute;\n elements = elements.filter((e) => matchesCurrentRoute(e.registrationRoute, currentRoute));\n }\n\n // Read ONCE per snapshot, not once per element: it is a `Dimensions.get`\n // call behind a try/catch, and a rotation landing mid-map would otherwise\n // give elements in the SAME snapshot different clip regions.\n const viewport = this.getViewportRect();\n\n const snapshot: NativeBridgeSnapshot = {\n timestamp: Date.now(),\n elements: elements.map((e) => {\n const handlers = extractHandlerNames(e.props);\n const state = e.getState();\n const { visibility, visibilityReason } = computeVisibility(\n state,\n this.getClipRectFor(e, viewport)\n );\n const bbox = projectBbox(state, visibility, this.pixelRatio);\n const vision = projectVisionFields({\n type: e.type,\n label: e.label,\n // Real interactivity signal: the names of props that are actually\n // functions (onPress/onChangeText/...). NOT `e.actions`, which\n // `inferActions` synthesizes press/click onto for every element —\n // using it would mark plain text/view nodes interactable.\n handlerNames: handlers,\n state,\n flatStyle: e.flatStyle,\n });\n return {\n id: e.id,\n type: e.type,\n label: e.label,\n identifier: e.getIdentifier(),\n state,\n actions: e.actions,\n customActions: serializeElementCustomActions(e.customActions),\n registeredHandlers: handlers.length > 0 ? handlers : undefined,\n registrationRoute: e.registrationRoute,\n visibility,\n ...(visibilityReason ? { visibilityReason } : {}),\n ...(bbox ? { bbox } : {}),\n ...vision,\n };\n }),\n components: this.getAllComponents().map((c) => ({\n id: c.id,\n name: c.name,\n description: c.description,\n actions: c.actions.map((a) => a.id),\n elementIds: c.elementIds,\n })),\n workflows: this.getAllWorkflows().map((w) => ({\n id: w.id,\n name: w.name,\n description: w.description,\n stepCount: w.steps.length,\n })),\n currentRoute: resolvedRoute.currentRoute,\n segments: resolvedRoute.segments,\n // Canonical aliases, spelled the way the web SDK's `BridgeSnapshot`\n // spells them so a cross-platform consumer reads one field name on both\n // platforms. Omitted rather than nulled when unknown — same convention\n // as the web fields, and it keeps the emitted shape byte-identical for\n // hosts that wire no route provider.\n ...(resolvedRoute.currentRoute ? { route: resolvedRoute.currentRoute } : {}),\n ...(resolvedRoute.activeTab ? { activeTab: resolvedRoute.activeTab } : {}),\n // The router-asserted route TEMPLATE, spelled as the web SDK's\n // `snapshot.page.route`. Emitted on the ONE assembly point both route\n // branches above share (the explicit-`routeInfo` branch the server's\n // `getSnapshot` override uses, and the provider fallback). Omitted, not\n // nulled, when unset — the same convention as the aliases above.\n ...(this.reportedRoute ? { page: { route: copyRouteInfo(this.reportedRoute) } } : {}),\n registration: this.getRegistrationCoverage(),\n };\n\n // Include app identification metadata so consumers can identify which\n // app produced this snapshot without a separate health probe.\n if (this.appInfo) {\n snapshot.appInfo = this.appInfo;\n }\n\n // Canonical enrichers — each in its own try/catch so a misbehaving tracker\n // can never break the rest of the snapshot.\n if (this.enrichers.modalDetector) {\n try {\n snapshot.modalStack = this.enrichers.modalDetector.getSnapshotModalContext();\n } catch (error) {\n if (this.config.verbose) {\n console.warn(`[ui-bridge-native] modalDetector enricher threw:`, error);\n }\n }\n }\n if (this.enrichers.toastCapture) {\n try {\n snapshot.toasts = this.enrichers.toastCapture.getSnapshotToastContext();\n } catch (error) {\n if (this.config.verbose) {\n console.warn(`[ui-bridge-native] toastCapture enricher threw:`, error);\n }\n }\n }\n if (this.enrichers.undoTracker) {\n try {\n snapshot.undoRedo = this.enrichers.undoTracker.getSnapshotUndoContext();\n } catch (error) {\n if (this.config.verbose) {\n console.warn(`[ui-bridge-native] undoTracker enricher threw:`, error);\n }\n }\n }\n\n // Custom enrichers — keys assign-merged onto the snapshot\n if (this.snapshotExtras.size > 0) {\n const ctx = { elements, currentRoute: resolvedRoute.currentRoute };\n for (const [name, fn] of this.snapshotExtras) {\n try {\n const extra = fn(ctx);\n if (extra && typeof extra === 'object') {\n Object.assign(snapshot, extra);\n }\n } catch (error) {\n if (this.config.verbose) {\n console.warn(`[ui-bridge-native] snapshot enricher \"${name}\" threw:`, error);\n }\n }\n }\n // Custom enrichers `Object.assign` their keys, so one returning a `page`\n // key would displace the canonical, router-asserted `page.route`.\n // Re-apply it so it always wins. When no route info is set, an\n // enricher's `page` is left exactly as the enricher wrote it.\n if (this.reportedRoute) {\n snapshot.page = { route: copyRouteInfo(this.reportedRoute) };\n }\n }\n\n return snapshot;\n }\n\n /**\n * Get registry statistics\n */\n getStats(): { elements: number; components: number; workflows: number } {\n return {\n elements: this.elements.size,\n components: this.components.size,\n workflows: this.workflows.size,\n };\n }\n\n /**\n * Compute registration coverage metadata for the current registry state.\n *\n * Groups currently-registered elements by `registrationRoute`, bucketing\n * elements without a route under `'?'`. `everHadRegistrations` is sticky —\n * once true, it stays true for the lifetime of the registry instance.\n */\n getRegistrationCoverage(): NativeRegistrationCoverage {\n const byRoute: Record<string, number> = {};\n for (const element of this.elements.values()) {\n const key =\n element.registrationRoute == null || element.registrationRoute === ''\n ? '?'\n : element.registrationRoute;\n byRoute[key] = (byRoute[key] ?? 0) + 1;\n }\n return {\n totalRegistered: this.elements.size,\n everHadRegistrations: this.everHadRegistrations,\n byRoute,\n };\n }\n\n /**\n * Clear all registrations\n */\n clear(): void {\n this.elements.clear();\n this.components.clear();\n this.workflows.clear();\n\n if (this.config.verbose) {\n console.log(`[ui-bridge-native] Registry cleared`);\n }\n }\n}\n\n// ============================================================================\n// Global Registry\n// ============================================================================\n\nlet globalRegistry: NativeUIBridgeRegistry | null = null;\n\n/**\n * Set the global registry\n */\nexport function setGlobalRegistry(registry: NativeUIBridgeRegistry): void {\n globalRegistry = registry;\n}\n\n/**\n * Get the global registry\n */\nexport function getGlobalRegistry(): NativeUIBridgeRegistry | null {\n return globalRegistry;\n}\n\n/**\n * Reset the global registry\n */\nexport function resetGlobalRegistry(): void {\n globalRegistry?.clear();\n globalRegistry = null;\n}\n","/**\n * Native Element Identifier\n *\n * Utilities for identifying and finding native elements.\n * In React Native, we use testID, accessibilityLabel, and tree paths\n * instead of XPath/CSS selectors.\n */\n\nimport type { NativeElementIdentifier, RegisteredNativeElement } from './types';\nimport { getGlobalRegistry } from './registry';\n\n/**\n * Create an element identifier for a native element\n */\nexport function createNativeElementIdentifier(\n id: string,\n options: {\n testId?: string;\n accessibilityLabel?: string;\n accessibilityHint?: string;\n treePath?: string;\n } = {}\n): NativeElementIdentifier {\n return {\n uiId: id,\n testId: options.testId || id,\n accessibilityLabel: options.accessibilityLabel,\n accessibilityHint: options.accessibilityHint,\n treePath: options.treePath || id,\n };\n}\n\n/**\n * Find an element by its identifier in the global registry\n */\nexport function findElementByIdentifier(\n identifier: string | NativeElementIdentifier\n): RegisteredNativeElement | null {\n const registry = getGlobalRegistry();\n if (!registry) return null;\n\n // If string, try different lookup strategies\n if (typeof identifier === 'string') {\n // Try direct ID lookup\n const byId = registry.getElement(identifier);\n if (byId) return byId;\n\n // Try testID lookup\n const byTestId = registry.findByTestId(identifier);\n if (byTestId) return byTestId;\n\n // Try pattern matching\n return findByPattern(registry, identifier);\n }\n\n // If identifier object, try each strategy\n if (identifier.uiId) {\n const byId = registry.getElement(identifier.uiId);\n if (byId) return byId;\n }\n\n if (identifier.testId) {\n const byTestId = registry.findByTestId(identifier.testId);\n if (byTestId) return byTestId;\n }\n\n return null;\n}\n\n/**\n * Find element by pattern (supports wildcards)\n */\nfunction findByPattern(\n registry: ReturnType<typeof getGlobalRegistry>,\n pattern: string\n): RegisteredNativeElement | null {\n if (!registry) return null;\n\n // Convert pattern to regex\n const regexPattern = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // Escape special chars\n .replace(/\\*/g, '.*') // Convert * to .*\n .replace(/\\?/g, '.'); // Convert ? to .\n\n const regex = new RegExp(`^${regexPattern}$`, 'i');\n\n for (const element of registry.getAllElements()) {\n const identifier = element.getIdentifier();\n\n // Match against testId\n if (identifier.testId && regex.test(identifier.testId)) {\n return element;\n }\n\n // Match against uiId\n if (identifier.uiId && regex.test(identifier.uiId)) {\n return element;\n }\n\n // Match against treePath\n if (identifier.treePath && regex.test(identifier.treePath)) {\n return element;\n }\n\n // Match against accessibilityLabel\n if (identifier.accessibilityLabel && regex.test(identifier.accessibilityLabel)) {\n return element;\n }\n }\n\n return null;\n}\n\n/**\n * Find all elements matching a pattern\n */\nexport function findAllByPattern(pattern: string): RegisteredNativeElement[] {\n const registry = getGlobalRegistry();\n if (!registry) return [];\n\n // Convert pattern to regex\n const regexPattern = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.');\n\n const regex = new RegExp(`^${regexPattern}$`, 'i');\n const results: RegisteredNativeElement[] = [];\n\n for (const element of registry.getAllElements()) {\n const identifier = element.getIdentifier();\n\n if (\n (identifier.testId && regex.test(identifier.testId)) ||\n (identifier.uiId && regex.test(identifier.uiId)) ||\n (identifier.treePath && regex.test(identifier.treePath)) ||\n (identifier.accessibilityLabel && regex.test(identifier.accessibilityLabel))\n ) {\n results.push(element);\n }\n }\n\n return results;\n}\n\n/**\n * Build a tree path for an element based on its position in the component tree\n */\nexport function buildTreePath(componentPath: string[], elementIndex?: number): string {\n let path = componentPath.join('/');\n if (elementIndex !== undefined) {\n path += `[${elementIndex}]`;\n }\n return path;\n}\n\n/**\n * Parse a tree path into its components\n */\nexport function parseTreePath(treePath: string): { components: string[]; index?: number } {\n const indexMatch = treePath.match(/\\[(\\d+)\\]$/);\n const index = indexMatch ? parseInt(indexMatch[1], 10) : undefined;\n const pathWithoutIndex = treePath.replace(/\\[\\d+\\]$/, '');\n const components = pathWithoutIndex.split('/').filter(Boolean);\n\n return { components, index };\n}\n\n/**\n * Check if an identifier matches criteria\n */\nexport function matchesIdentifier(\n identifier: NativeElementIdentifier,\n criteria: Partial<NativeElementIdentifier>\n): boolean {\n if (criteria.uiId && identifier.uiId !== criteria.uiId) {\n return false;\n }\n if (criteria.testId && identifier.testId !== criteria.testId) {\n return false;\n }\n if (\n criteria.accessibilityLabel &&\n identifier.accessibilityLabel !== criteria.accessibilityLabel\n ) {\n return false;\n }\n if (criteria.treePath && identifier.treePath !== criteria.treePath) {\n return false;\n }\n return true;\n}\n","/**\n * Abandonment primitive for action invocation.\n *\n * Plan `2026-08-20-ui-bridge-action-declaration-shape`, Phase 3.\n *\n * `ActionHandler`'s `options.signal` is **cooperative**: a well-behaved handler\n * observes it and returns early. Cooperation cannot be enforced, and a handler\n * that hangs is exactly the case cancellation exists for — so the executor\n * never trusts the handler to observe the signal. It races the handler promise\n * against the abort and reports the outcome either way.\n *\n * DUPLICATE OF `@qontinui/ui-bridge` `src/core/abortable.ts` — DELIBERATELY NOT\n * AN IMPORT. `@qontinui/ui-bridge` is an OPTIONAL peerDependency of this\n * package and is absent from `dependencies`, so a consumer may install\n * `@qontinui/ui-bridge-native` alone; importing from it would resolve inside\n * this monorepo and then fail for that consumer. See the same note on\n * `core/types.ts` `ActionHandler`.\n *\n * KEEP IN SYNC with `@qontinui/ui-bridge` `src/core/abortable.ts` — including\n * `MAX_ACTION_TIMEOUT_MS` and `normalizeActionTimeoutMs` below. The module has\n * no imports and touches no DOM API — only `AbortController` and `setTimeout`,\n * both present in React Native.\n */\n\n/** Which of the two cancellation sources fired. */\nexport type AbortReason = 'signal' | 'timeout';\n\n/**\n * Ceiling applied to a caller-supplied `timeoutMs` — **24 hours**.\n *\n * `ComponentActionRequest.timeoutMs` crosses the wire, so it is attacker- (or\n * typo-) controlled: an HTTP or WebSocket caller can put any JSON number in\n * it and it would otherwise land in `setTimeout` unchecked. Two concrete\n * hazards that ceiling closes:\n *\n * - **`setTimeout`'s 32-bit delay overflow.** Any delay above 2147483647ms\n * wraps to a *negative* 32-bit int in every major runtime and the timer\n * fires **immediately** — so `timeoutMs: 2147483648` (\"about 25 days,\n * effectively no timeout\") would abandon the action on the next tick, the\n * exact opposite of what the caller asked for. Clamping below that boundary\n * makes the semantics monotonic: a bigger number never means a shorter wait.\n * - **A pinned timer per in-flight request.** Nothing here is unbounded work,\n * but a 100-year timeout is indistinguishable from \"no timeout\" while still\n * holding the handler's abandonment machinery alive, so there is nothing to\n * buy above a day.\n *\n * Clamping (rather than rejecting) is right at the top end because the intent\n * is unambiguous — \"effectively forever\" — and the clamped value honours it as\n * closely as the platform can. The malformed cases below are rejected instead,\n * because there the intent is NOT recoverable.\n */\nexport const MAX_ACTION_TIMEOUT_MS = 86_400_000;\n\n/**\n * Outcome of {@link normalizeActionTimeoutMs}. Discriminated on `ok` so a\n * rejected value cannot be read as a timeout by accident.\n */\nexport type TimeoutNormalization =\n | {\n ok: true;\n /** `undefined` ≡ no timeout. Always a finite integer in `[0, MAX]`. */\n timeoutMs: number | undefined;\n /** True iff the caller's value was above {@link MAX_ACTION_TIMEOUT_MS}. */\n clamped: boolean;\n }\n | { ok: false; reason: string };\n\n/**\n * Validate and clamp a wire-supplied `timeoutMs` before it can reach a timer.\n *\n * The policy, stated once so all three executors share it:\n *\n * | Supplied | Result | Why |\n * |---|---|---|\n * | absent / `null` | no timeout | the documented \"omitted ⇒ no timeout\" default |\n * | `0` | abandon on the next tick | a coherent request (\"don't let it run\"), and what `setTimeout(…, 0)` already means |\n * | `1 … MAX` | honoured, floored to an integer | fractional ms is not a thing a timer can express |\n * | `> MAX` | clamped to {@link MAX_ACTION_TIMEOUT_MS} | see that constant — above 2^31-1 the platform timer silently inverts |\n * | negative | **rejected** | `setTimeout` would treat it as `0`, i.e. a sign typo would abandon every call instantly and silently |\n * | `NaN` / `±Infinity` | **rejected** | `setTimeout(NaN)` is `setTimeout(0)` — same silent-instant-abandon trap |\n * | not a number (`\"5000\"`, `{}`, `true`) | **rejected** | JSON has numbers; a string here means the caller built the body wrong, and coercing would hide it |\n *\n * Rejection is loud on purpose: every rejected case is one where *ignoring*\n * the field fails open (the action runs uncancellable) and *coercing* it fails\n * closed at zero. Neither is what the caller asked for, and both are silent —\n * which is the defect class this whole plan exists to remove.\n */\nexport function normalizeActionTimeoutMs(raw: unknown): TimeoutNormalization {\n if (raw === undefined || raw === null) {\n return { ok: true, timeoutMs: undefined, clamped: false };\n }\n if (typeof raw !== 'number') {\n return {\n ok: false,\n reason: `timeoutMs must be a number of milliseconds, received ${typeof raw === 'string' ? JSON.stringify(raw) : typeof raw}`,\n };\n }\n if (!Number.isFinite(raw)) {\n return {\n ok: false,\n reason: `timeoutMs must be a finite number of milliseconds, received ${String(raw)}`,\n };\n }\n if (raw < 0) {\n return {\n ok: false,\n reason: `timeoutMs must not be negative, received ${String(raw)}`,\n };\n }\n if (raw > MAX_ACTION_TIMEOUT_MS) {\n return { ok: true, timeoutMs: MAX_ACTION_TIMEOUT_MS, clamped: true };\n }\n return { ok: true, timeoutMs: Math.floor(raw), clamped: false };\n}\n\n/**\n * Result of {@link runAbortable}. Discriminated on `aborted` so the caller\n * cannot read `result` off an abandoned invocation.\n */\nexport type AbortableOutcome<T> =\n | { aborted: false; result: T }\n | { aborted: true; reason: AbortReason };\n\n/** Cancellation sources composed by {@link runAbortable}. */\nexport interface RunAbortableOptions {\n /**\n * An in-process caller's signal. Cannot cross the wire — that is what\n * `timeoutMs` is for.\n */\n signal?: AbortSignal;\n /**\n * Milliseconds after which the invocation is abandoned. Serializable, so\n * this is the arm an HTTP/WebSocket caller can actually use.\n *\n * Expected to have been through {@link normalizeActionTimeoutMs} already —\n * every executor seam runs it, because this value arrives from the wire.\n * The `Number.isFinite` / `>= 0` guard below is defence in depth for a\n * direct in-process caller, and ignores anything malformed rather than\n * letting it reach `setTimeout`.\n */\n timeoutMs?: number;\n}\n\n/**\n * A fresh `AbortSignal` that is never aborted.\n *\n * `ActionHandlerOptions` promises the bag is *always* supplied, so a handler\n * written the way the docs describe — `(params, { signal }) => …` — must never\n * be called with `undefined` as its second argument. Not every invocation seam\n * has a cancellation source to give it though: an ELEMENT custom action\n * (`ControlActionRequest`) carries no `timeoutMs` and the executor holds no\n * controller for it, so there is nothing that could ever abort. Those seams\n * pass this instead of `undefined`, which keeps the destructure safe and keeps\n * `signal.aborted` honestly `false`.\n *\n * **Fresh per call, deliberately.** A shared singleton would accumulate one\n * listener per handler that calls `signal.addEventListener('abort', …)` and\n * never releases them — a leak on a hot path. A `AbortController` allocation\n * is a few bytes and custom element actions are not a hot path.\n *\n * When element actions grow a cancellation source, replace the call sites with\n * a real `runAbortable` race — the handler contract does not change.\n */\nexport function inertAbortSignal(): AbortSignal {\n return new AbortController().signal;\n}\n\n/**\n * Invoke `invoke` with a derived `AbortSignal`, abandoning it if either the\n * caller's signal aborts or `timeoutMs` elapses.\n *\n * - Returns `{ aborted: true, reason }` **without invoking at all** when the\n * caller's signal is already aborted.\n * - Returns `{ aborted: false, result }` on normal completion.\n * - **Rejects** if `invoke` throws/rejects while the race is still live, so a\n * genuine handler error stays distinguishable from a cancellation.\n * - A rejection that arrives *after* an abort already won is swallowed:\n * `Promise.race` has a rejection handler attached, so the abandoned handler\n * cannot surface as an unhandled rejection.\n */\nexport async function runAbortable<T>(\n invoke: (signal: AbortSignal) => T | Promise<T>,\n options: RunAbortableOptions = {}\n): Promise<AbortableOutcome<T>> {\n const callerSignal = options.signal;\n const timeoutMs = options.timeoutMs;\n\n if (callerSignal?.aborted) {\n return { aborted: true, reason: 'signal' };\n }\n\n const controller = new AbortController();\n // Set before `controller.abort()` in both arms, so the abort listener reads\n // the reason that actually fired.\n let reason: AbortReason = 'signal';\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const onCallerAbort = (): void => {\n reason = 'signal';\n controller.abort();\n };\n\n callerSignal?.addEventListener('abort', onCallerAbort, { once: true });\n\n if (typeof timeoutMs === 'number' && Number.isFinite(timeoutMs) && timeoutMs >= 0) {\n // The clamp is applied AGAIN here, at the only place a delay actually\n // reaches a timer. `normalizeActionTimeoutMs` already did it for every\n // wire path, but a direct in-process caller reaches `runAbortable`\n // without passing through an executor — and above 2^31-1 `setTimeout`\n // wraps negative and fires immediately, turning \"wait a century\" into\n // \"abandon now\". See MAX_ACTION_TIMEOUT_MS.\n timer = setTimeout(\n () => {\n reason = 'timeout';\n controller.abort();\n },\n Math.min(timeoutMs, MAX_ACTION_TIMEOUT_MS)\n );\n }\n\n try {\n const abandoned = new Promise<AbortableOutcome<T>>((resolve) => {\n controller.signal.addEventListener('abort', () => resolve({ aborted: true, reason }), {\n once: true,\n });\n });\n\n const completed = (async (): Promise<AbortableOutcome<T>> => ({\n aborted: false,\n result: await invoke(controller.signal),\n }))();\n\n return await Promise.race([completed, abandoned]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n callerSignal?.removeEventListener('abort', onCallerAbort);\n }\n}\n","/**\n * Parameter-schema validation for component actions.\n *\n * Plan `2026-08-20-ui-bridge-action-declaration-shape`, Phase 2.\n *\n * `ComponentAction.paramSchema` is **published to agents** — it is spread onto\n * `/control/components` and `/control/component/:id` by\n * `server/handlers.ts` `annotateComponentWithInvocationPaths`, and four live\n * runner consumers read it off the wire. Until Phase 2 nothing enforced it, so\n * an agent that read the schema and sent conforming params had no guarantee\n * the handler agreed. That is worse than publishing nothing: it invites trust\n * it cannot honour. This module is what makes the declaration enforceable.\n *\n * ---------------------------------------------------------------------------\n * THE SUPPORTED SUBSET — this is a CROSS-REPO CONTRACT\n * ---------------------------------------------------------------------------\n *\n * `qontinui-runner` reads these same schemas\n * (`workflow_generation/wrapper_manifest.rs`, `commands/command_interpreter.rs`,\n * `bin/wrappers_mcp.rs`), and `command_interpreter.rs` feeds one to an LLM and\n * asks it to produce conforming args. So the grammar below is not a private\n * implementation detail — changing it changes what those consumers may emit.\n *\n * **No dependency.** `@qontinui/ui-bridge` ships to browsers and has exactly\n * one runtime dependency (`dom-accessibility-api`). A full JSON Schema\n * validator (`ajv` and friends) would be the SDK's first heavyweight runtime\n * dep, which the plan rejected on bundle size. Hence a hand-implemented subset,\n * documented here rather than inferred from the code.\n *\n * **Two accepted top-level shapes**, because `useUIComponent`'s own author-\n * facing doc has always sanctioned both:\n *\n * 1. **Object-schema form** — the shape `paramSchemaOf()`\n * (`@qontinui/ui-bridge-wrapper`) emits:\n *\n * ```jsonc\n * { \"type\": \"object\",\n * \"properties\": { \"username\": { \"type\": \"string\" },\n * \"remember\": { \"type\": \"boolean\" } },\n * \"required\": [\"username\"],\n * \"additionalProperties\": false }\n * ```\n *\n * 2. **Map form** — the lightweight shorthand the doc describes as\n * \"a map of `{ paramName: \"string\" | \"number\" | ... }`\":\n *\n * ```jsonc\n * { \"username\": \"string\", \"count\": { \"type\": \"integer\", \"minimum\": 1 } }\n * ```\n *\n * **Disambiguation rule** (deterministic, no heuristics). The schema is read as\n * form 1 **iff** either:\n *\n * - it carries a `properties` object; or\n * - `type === \"object\"` **and every other top-level key is a JSON Schema\n * object-applicable keyword** ({@link OBJECT_SCHEMA_KEYWORDS}).\n *\n * Everything else is form 2. So `{ \"type\": \"string\" }` is a map declaring one\n * param named `type` of type `string`, not a top-level string schema — a\n * params bag is always an object, so a non-object top-level `type` cannot be\n * describing the bag.\n *\n * ⚠️ **The second clause is load-bearing, and a naive `type === \"object\"` test\n * is a silent-validation-loss bug.** Consider the map form\n * `{ \"type\": \"object\", \"id\": \"string\" }` — an action taking a param literally\n * named `type` whose hint is `\"object\"`, plus an `id`. Reading that as form 1\n * would find no `properties`, express no constraint, and **silently drop every\n * sibling declaration in the map**: `validateActionParams(schema, { id: 12345 })`\n * would answer `{ valid: true }` for a schema that plainly says `id` is a\n * string. `id` is not an object keyword, so the keyword test sends it to form 2\n * and both params validate. A param named `type` is not exotic — it is one of\n * the most common names there is.\n *\n * The one genuinely ambiguous input is a bare `{ \"type\": \"object\" }`: it is\n * both an empty object schema and a one-entry map declaring a param named\n * `type` of type `object`. It is read as form 1 (the empty object schema),\n * which constrains nothing either way, so the choice is unobservable except in\n * combination with `additionalProperties: false` — where form 1 is clearly the\n * intended reading.\n *\n * ### Map form is a TYPE hint, never a REQUIREDNESS hint\n *\n * A bare string value in map form is read as a type name **only when it is one\n * of the seven JSON Schema primitive names** — `string`, `number`, `integer`,\n * `boolean`, `object`, `array`, `null` — which is exactly `paramSchemaOf`'s\n * own `PRIMITIVE_TYPES` set. **Any other string is prose and constrains\n * nothing.**\n *\n * That is not defensive coding, it is the corpus: a census of every literal\n * `paramSchema` in the fleet (2026-08-22) found ~50 map-form declarations in\n * `qontinui-runner` alone whose values are human-readable hints, not types —\n * `{ count: \"number (>= 1, defaults to 1)\", account: \"string — either a Claude\n * account label … or the literal \\\"best\\\"\" }`. Reading `\"number (>= 1,\n * defaults to 1)\"` as a type name would make every one of them unvalidatable\n * noise. The file that declares them says so itself: *\"loose `Record<string,\n * unknown>` with hint strings, no runtime validation\"*.\n *\n * For the same reason, **map form never marks anything required.** It has no\n * `required` array and no other way to express optionality, and most of those\n * ~50 real declarations describe params that are explicitly optional in their\n * own prose (\"defaults to 1\", \"optional initial prompt\"). Requiring by default\n * would report a violation on nearly every real call. An author who needs\n * required params writes form 1, which has a keyword for it.\n *\n * **Optionality in form 1** is exactly `required`: a property is required iff\n * listed there. Note `paramSchemaOf` **omits `required` entirely** when every\n * field is optional, so its absence means \"nothing required\", never \"malformed\".\n *\n * `optional: true` is not a JSON Schema keyword — it is this monorepo's\n * convention on `paramSchemaOf`'s *input* descriptors, and `paramSchemaOf`\n * strips it before the schema reaches the wire. It is honoured here (form 2\n * only) purely so an unstripped input-shaped object is read the same way.\n *\n * **Recognised property keywords** — everything else is IGNORED:\n *\n * | Keyword | Applies to | Meaning |\n * |---|---|---|\n * | `type` | any | `\"string\" \\| \"number\" \\| \"integer\" \\| \"boolean\" \\| \"object\" \\| \"array\" \\| \"null\"`, or an array of those (union — a value matching any member passes). Any OTHER type name constrains nothing (`paramSchemaOf` passes unknown names through verbatim, so they do occur) |\n * | `enum` | any | value must equal one of the listed JSON values |\n * | `const` | any | value must equal this JSON value |\n * | `properties` | objects | per-key sub-schemas, applied recursively |\n * | `required` | objects | array of key names that must be present |\n * | `additionalProperties` | objects | when exactly `false`, keys absent from `properties` are rejected — **including when the schema declares no `properties` at all**, which is the natural spelling of \"takes no params\". Any other value is ignored |\n * | `items` | arrays | one sub-schema applied to every element, recursively |\n * | `minimum` / `maximum` | numbers | inclusive bounds |\n * | `minLength` / `maxLength` | strings | inclusive bounds on `.length` |\n * | `pattern` | strings | ECMAScript regex, unanchored. An uncompilable **or catastrophically-backtracking** pattern is ignored, never a failure — see {@link MAX_PATTERN_LENGTH} |\n *\n * **⚠ Anything outside this table is IGNORED, never rejected.** `description`,\n * `default`, `title`, `format`, `examples`, `$schema`, `$ref`, `oneOf`,\n * `anyOf`, `allOf`, `not`, `patternProperties`, `minItems`, `exclusiveMinimum`\n * … all pass silently. This is deliberate and load-bearing: a schema outside\n * this subset is still *valid JSON Schema*, and rejecting an unrecognised\n * keyword would convert a documentation nicety into a hard invocation failure\n * for every author who wrote a richer schema than this validator reads. An\n * unreadable schema means \"no constraint expressed here\", not \"invalid\".\n * The same rule applies one level up: a `paramSchema` that is not a plain\n * object at all (`null`, an array, a string) expresses no constraint and\n * validates everything.\n *\n * **No coercion.** `\"5\"` does not satisfy `{\"type\":\"number\"}`. The runner's\n * LLM router is already instructed to \"coerce numeric tokens to numbers\"\n * (`command_interpreter.rs`), so coercing here would hide that contract rather\n * than enforce it.\n *\n * ### A validator must return a verdict, never throw\n *\n * {@link validateActionParams} is called at an invocation seam whose only\n * generic failure label is `UB-ACTION-FAILED` — \"the handler failed\". A\n * validator that throws would therefore be reported as a *handler* fault, and\n * the author would go debug code that never ran. Two inputs could do it, and\n * both are **author-supplied schema data**, not caller params:\n *\n * - **A self-referential schema.** `const s = { type: 'object', properties: {} };\n * s.properties.self = s;` recurses until `RangeError: Maximum call stack size\n * exceeded`. Bounded by {@link MAX_SCHEMA_DEPTH}: past that depth the walk\n * stops and expresses no further constraint (the ignore-don't-reject rule,\n * applied to depth).\n * - **A catastrophically-backtracking `pattern`.** This one does not throw at\n * all — it *hangs*, which no `try`/`catch` can rescue. Guarded before\n * compilation by {@link MAX_PATTERN_LENGTH} and a nested-quantifier test,\n * the same pair `control/action-executor.ts` already applies to a\n * caller-supplied `srcPattern`.\n *\n * The seams wrap the call anyway (belt and braces) and report a validator\n * fault distinguishably from a handler fault — see the executors.\n *\n * ---------------------------------------------------------------------------\n * DUPLICATE — deliberately not an import\n * ---------------------------------------------------------------------------\n *\n * DUPLICATE OF `@qontinui/ui-bridge` `src/core/param-schema.ts` — DELIBERATELY\n * NOT AN IMPORT, for the same reason as `core/abortable.ts` beside it:\n * `@qontinui/ui-bridge` is an OPTIONAL peerDependency of this package and is\n * absent from `dependencies`, so a consumer may install\n * `@qontinui/ui-bridge-native` alone; importing from it would resolve inside\n * this monorepo and then fail for that consumer.\n *\n * KEEP IN SYNC with `@qontinui/ui-bridge` `src/core/param-schema.ts`. The\n * module has no imports and touches no DOM or Node API — only `RegExp`,\n * `JSON`, `Set` and `Number`, all present in React Native. The documented\n * subset above is a cross-repo contract, so the two copies drifting is a\n * contract break, not a style nit.\n */\n\n/** Which rule rejected a value. Machine-readable; pairs with `message`. */\nexport type ParamSchemaKeyword =\n | 'required'\n | 'type'\n | 'enum'\n | 'const'\n | 'additionalProperties'\n | 'minimum'\n | 'maximum'\n | 'minLength'\n | 'maxLength'\n | 'pattern';\n\n/**\n * One rejected parameter. `path` is the whole point — an agent told only\n * \"invalid params\" cannot fix anything, so every issue names the param it is\n * about.\n */\nexport interface ParamSchemaIssue {\n /**\n * Location of the offending value, in dotted/bracketed form relative to the\n * params object: `\"username\"`, `\"filter.status\"`, `\"ids[2]\"`. Empty string\n * means the params object itself.\n */\n path: string;\n /** Which rule rejected it. */\n keyword: ParamSchemaKeyword;\n /** What the schema demanded, rendered for a human/agent reader. */\n expected: string;\n /** What was actually supplied, rendered compactly. Absent for `required`. */\n received?: string;\n /** One-line explanation naming both the param and the reason. */\n message: string;\n}\n\n/** Outcome of {@link validateActionParams}. */\nexport interface ParamValidationResult {\n valid: boolean;\n /** Empty iff `valid`. */\n issues: ParamSchemaIssue[];\n}\n\n/**\n * What the executor does with a schema violation.\n *\n * - `'enforce'` — reject the invocation before the handler runs, with\n * `errorCode: 'UB-ACTION-REJECTED'` and the issue list attached.\n * - `'warn'` — log the violation and invoke the handler anyway.\n * - `'off'` — skip validation entirely (no schema walk, no allocation).\n */\nexport type ParamValidationMode = 'enforce' | 'warn' | 'off';\n\n/**\n * **`'warn'`.** Decided on the plan's priority order — *powerful features →\n * scalability → robustness → clean code* — not on backward compatibility,\n * which is explicitly not a factor on this project.\n *\n * The feature Phase 2 delivers is *an enforceable declaration*, and warn mode\n * delivers all of it except the refusal: the schema is walked, the violating\n * param is named, and the violation is reported. Enforcement is one call to\n * {@link setDefaultParamValidationMode} away, per deployment.\n *\n * What arming `'enforce'` by default would buy is the refusal — and the\n * population it would refuse first is not attackers, it is the fleet's own\n * schemas. Nothing has ever validated these declarations, so on the balance of\n * evidence some existing `paramSchema` values are wrong about their own\n * handlers rather than the callers being wrong about the schema; a validator\n * armed on day one reports that as the caller's failure. Worse, the largest\n * live producer of action params is a *model*:\n * `qontinui-runner/src-tauri/src/commands/command_interpreter.rs` asks an LLM\n * for \"args whose keys match the chosen action's `paramSchema`\", and that\n * failure surfaces to a user running a slash command. Defaulting to enforce\n * would convert a documentation defect into a user-visible outage on a\n * population that was never measured.\n *\n * Robustness — third in the order, and the tiebreaker here — argues the same\n * way: the robust move is to collect violations from a validator that has\n * never seen production traffic before letting it refuse anything. A default\n * that fails open and is *observable* beats a default that fails closed and is\n * *unmeasured*.\n */\nexport const DEFAULT_PARAM_VALIDATION_MODE: ParamValidationMode = 'warn';\n\nlet currentDefaultMode: ParamValidationMode = DEFAULT_PARAM_VALIDATION_MODE;\n\n/**\n * Arm or disarm validation process-wide. Call once at app boot; each\n * invocation may still override it via `executeComponentAction`'s\n * `{ paramValidation }` option.\n */\nexport function setDefaultParamValidationMode(mode: ParamValidationMode): void {\n currentDefaultMode = mode;\n}\n\n/** The mode an invocation gets when it does not pass one. */\nexport function getDefaultParamValidationMode(): ParamValidationMode {\n return currentDefaultMode;\n}\n\n/** Test/teardown helper: restore {@link DEFAULT_PARAM_VALIDATION_MODE}. */\nexport function resetDefaultParamValidationMode(): void {\n currentDefaultMode = DEFAULT_PARAM_VALIDATION_MODE;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\ntype PlainObject = Record<string, unknown>;\n\n/**\n * The only strings read as type names. Byte-identical to `paramSchemaOf`'s\n * `PRIMITIVE_TYPES` (`@qontinui/ui-bridge-wrapper` `helpers/schema.ts`) —\n * KEEP IN SYNC. Anything else is prose.\n */\nconst PRIMITIVE_TYPE_NAMES = new Set([\n 'string',\n 'number',\n 'integer',\n 'boolean',\n 'object',\n 'array',\n 'null',\n]);\n\n/**\n * Top-level keys that a JSON Schema *object* schema may legitimately carry\n * alongside `type: \"object\"`. Used only by the form-1/form-2 disambiguation\n * (see the module header): a top level whose non-`type` keys are all in here\n * is an object schema; anything else is a map of param declarations.\n *\n * Deliberately generous — it lists keywords this validator does not implement\n * (`oneOf`, `patternProperties`, `$defs`, …) because the question it answers\n * is \"was this written as a schema?\", not \"can we enforce it?\". A schema\n * richer than the subset must still be *recognised* as a schema, or its\n * `properties` would be re-read as param names.\n */\nconst OBJECT_SCHEMA_KEYWORDS = new Set([\n 'type',\n 'properties',\n 'required',\n 'additionalProperties',\n 'patternProperties',\n 'propertyNames',\n 'minProperties',\n 'maxProperties',\n 'dependentRequired',\n 'dependentSchemas',\n 'unevaluatedProperties',\n 'allOf',\n 'anyOf',\n 'oneOf',\n 'not',\n 'if',\n 'then',\n 'else',\n 'enum',\n 'const',\n 'default',\n 'examples',\n 'title',\n 'description',\n 'deprecated',\n 'readOnly',\n 'writeOnly',\n '$schema',\n '$id',\n '$ref',\n '$comment',\n '$defs',\n 'definitions',\n]);\n\n/**\n * Depth ceiling for the recursive schema walk. A schema nested deeper than\n * this expresses no further constraint here (ignore-don't-reject), which is\n * what keeps a self-referential `paramSchema` from becoming a `RangeError` at\n * the invocation seam.\n *\n * 32 is far past any hand-written declaration — the deepest real `paramSchema`\n * in the fleet is 2 levels — and far short of a default stack.\n */\nconst MAX_SCHEMA_DEPTH = 32;\n\n/**\n * Longest `pattern` this validator will compile, and the companion\n * nested-quantifier test below.\n *\n * A `pattern` is author-supplied but it is evaluated against caller-supplied\n * strings, so `(a+)+$` against a long non-matching input is a hang, not an\n * exception — unrescuable by `try`/`catch` because the engine never returns.\n * The pair (length cap + nested-quantifier reject) is the same guard\n * `control/action-executor.ts` applies to a caller-supplied `srcPattern`; over\n * the cap or on a risky construct the keyword is IGNORED, exactly like an\n * uncompilable pattern.\n */\nconst MAX_PATTERN_LENGTH = 200;\n\n/**\n * Nested-quantifier detector — a group that itself contains a quantifier and\n * is then quantified (`(a+)+`, `(a*)*`, `(?:a{2,})+`), the classic\n * catastrophic-backtracking shape.\n *\n * A deliberate duplicate of `@qontinui/ui-bridge` `control/action-executor.ts`\n * `hasNestedQuantifiers`: this module imports nothing so that `src/native/*`\n * and the standalone React Native package can share it (see the header), while\n * `action-executor.ts` pulls in the DOM. KEEP THE COPIES IN SYNC.\n */\nfunction hasNestedQuantifiers(pattern: string): boolean {\n return /(\\((?:[^()]*[+*}])[^()]*\\))[+*?]|\\(\\?:[^()]*[+*}][^()]*\\)[+*?]/.test(pattern);\n}\n\nfunction isPlainObject(value: unknown): value is PlainObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** `Object.prototype.hasOwnProperty`, safely — see {@link hasOwn}. */\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\n\n/**\n * Own-property test.\n *\n * **Not `key in value`.** `in` walks the prototype chain, so a param named\n * `toString`, `valueOf`, `constructor` or `hasOwnProperty` is never reported\n * missing (it \"exists\" on `Object.prototype`) and is then validated against\n * `Object.prototype`'s member instead of the caller's value — a required\n * param that was never supplied silently passes, and a type constraint on it\n * is checked against a function. `standardActionEffect` already spells this\n * correctly; this is the same rule.\n */\nfunction hasOwn(value: object, key: string): boolean {\n return hasOwnProperty.call(value, key);\n}\n\n/** Compact rendering of a supplied value, for the `received` field. */\nfunction render(value: unknown): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (Array.isArray(value)) return `array(${value.length})`;\n const t = typeof value;\n if (t === 'string') return JSON.stringify(value);\n if (t === 'number' || t === 'boolean') return String(value);\n if (t === 'object') return 'object';\n return t;\n}\n\n/** Longest JSON rendering {@link renderJson} will emit before truncating. */\nconst MAX_RENDERED_JSON_LENGTH = 120;\n\n/**\n * Rendering for `enum` / `const`, where the *identity* of the value is the\n * whole message.\n *\n * {@link render} collapses every object to the literal string `\"object\"`,\n * which turns a `const` violation into **\"must equal object, received\n * object.\"** — a message that tells an agent nothing, in a module whose stated\n * purpose is that \"invalid params\" tells an agent nothing. So these two\n * keywords render structurally instead, capped so a large schema value cannot\n * blow up a log line.\n */\nfunction renderJson(value: unknown): string {\n if (value === undefined) return 'undefined';\n let text: string;\n try {\n text = JSON.stringify(value) ?? String(value);\n } catch {\n // Cyclic or a BigInt — fall back to the compact rendering.\n return render(value);\n }\n return text.length > MAX_RENDERED_JSON_LENGTH\n ? `${text.slice(0, MAX_RENDERED_JSON_LENGTH)}…`\n : text;\n}\n\n/** The JSON Schema type name a runtime value would be reported as. */\nfunction jsonTypeOf(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n const t = typeof value;\n if (t === 'number') return Number.isInteger(value) ? 'integer' : 'number';\n if (t === 'string' || t === 'boolean' || t === 'object') return t;\n return t;\n}\n\nfunction matchesType(value: unknown, typeName: string): boolean {\n switch (typeName) {\n case 'string':\n return typeof value === 'string';\n case 'number':\n return typeof value === 'number' && Number.isFinite(value);\n case 'integer':\n return typeof value === 'number' && Number.isInteger(value);\n case 'boolean':\n return typeof value === 'boolean';\n case 'null':\n return value === null;\n case 'array':\n return Array.isArray(value);\n case 'object':\n return isPlainObject(value);\n default:\n // Unrecognised type name — outside the subset, so it constrains nothing.\n return true;\n }\n}\n\n/**\n * Structural equality for JSON `enum` / `const` members.\n *\n * **Not `JSON.stringify(a) === JSON.stringify(b)`.** That comparison is\n * key-order sensitive, so `const: { a: 1, b: 2 }` would reject `{ b: 2, a: 1 }`\n * — two renderings of the same JSON value. Object key order is not semantic in\n * JSON, and the caller does not control it (it is whatever the producing\n * serializer emitted), so an order-sensitive test produces a false failure on\n * a value that IS the declared constant.\n *\n * Depth-bounded for the same reason the schema walk is: a cyclic `const` in a\n * hand-built (non-JSON) schema would otherwise recurse forever. Past the\n * bound the two are treated as unequal rather than throwing.\n */\nfunction jsonEquals(a: unknown, b: unknown, depth = 0): boolean {\n if (a === b) return true;\n if (depth > MAX_SCHEMA_DEPTH) return false;\n if (a === null || b === null || a === undefined || b === undefined) return false;\n if (typeof a !== typeof b) return false;\n if (typeof a !== 'object') return false;\n\n const aIsArray = Array.isArray(a);\n if (aIsArray !== Array.isArray(b)) return false;\n if (aIsArray) {\n const bArr = b as unknown[];\n const aArr = a as unknown[];\n if (aArr.length !== bArr.length) return false;\n return aArr.every((item, i) => jsonEquals(item, bArr[i], depth + 1));\n }\n\n const aObj = a as PlainObject;\n const bObj = b as PlainObject;\n const aKeys = Object.keys(aObj);\n const bKeys = Object.keys(bObj);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) => hasOwn(bObj, key) && jsonEquals(aObj[key], bObj[key], depth + 1));\n}\n\nfunction joinPath(base: string, key: string): string {\n return base === '' ? key : `${base}.${key}`;\n}\n\n/**\n * Read a `type` keyword into the list of accepted type names, or `null` when\n * the keyword is absent or shaped outside the subset (in which case it\n * constrains nothing).\n */\nfunction readTypes(schema: PlainObject): string[] | null {\n const raw = schema['type'];\n if (typeof raw === 'string') return [raw];\n if (Array.isArray(raw)) {\n const names = raw.filter((t): t is string => typeof t === 'string');\n return names.length > 0 ? names : null;\n }\n return null;\n}\n\n/** Validate one value against one property schema, appending to `issues`. */\nfunction validateValue(\n value: unknown,\n schema: unknown,\n path: string,\n issues: ParamSchemaIssue[],\n depth: number\n): void {\n // Depth bound — a self-referential `paramSchema` would otherwise recurse to\n // `RangeError`, which the seam can only report as a HANDLER failure. Past\n // the bound the schema expresses no further constraint. See MAX_SCHEMA_DEPTH.\n if (depth > MAX_SCHEMA_DEPTH) return;\n\n // A property schema that is not an object expresses no constraint. A bare\n // PRIMITIVE type-name string IS a constraint though, and is how map form\n // spells one; any other string is prose (see the module header).\n if (typeof schema === 'string') {\n if (PRIMITIVE_TYPE_NAMES.has(schema) && !matchesType(value, schema)) {\n issues.push({\n path,\n keyword: 'type',\n expected: schema,\n received: render(value),\n message: `Parameter \"${path}\" must be of type ${schema}, received ${jsonTypeOf(value)} (${render(value)}).`,\n });\n }\n return;\n }\n if (!isPlainObject(schema)) return;\n\n const types = readTypes(schema);\n if (types !== null && !types.some((t) => matchesType(value, t))) {\n const expected = types.join(' | ');\n issues.push({\n path,\n keyword: 'type',\n expected,\n received: render(value),\n message: `Parameter \"${path}\" must be of type ${expected}, received ${jsonTypeOf(value)} (${render(value)}).`,\n });\n // A wrong-typed value cannot meaningfully fail the type-specific keywords\n // below; reporting those too would bury the one issue that matters.\n return;\n }\n\n if (hasOwn(schema, 'const') && !jsonEquals(value, schema['const'])) {\n // `renderJson`, not `render`: the identity of the value IS the message\n // here, and `render` would collapse both sides to the word \"object\".\n const expected = renderJson(schema['const']);\n const received = renderJson(value);\n issues.push({\n path,\n keyword: 'const',\n expected,\n received,\n message: `Parameter \"${path}\" must equal ${expected}, received ${received}.`,\n });\n return;\n }\n\n const enumValues = schema['enum'];\n if (Array.isArray(enumValues) && !enumValues.some((allowed) => jsonEquals(value, allowed))) {\n const expected = enumValues.map((v) => renderJson(v)).join(', ');\n const received = renderJson(value);\n issues.push({\n path,\n keyword: 'enum',\n expected,\n received,\n message: `Parameter \"${path}\" must be one of [${expected}], received ${received}.`,\n });\n return;\n }\n\n if (typeof value === 'number') {\n const min = schema['minimum'];\n if (typeof min === 'number' && value < min) {\n issues.push({\n path,\n keyword: 'minimum',\n expected: `>= ${min}`,\n received: render(value),\n message: `Parameter \"${path}\" must be >= ${min}, received ${value}.`,\n });\n }\n const max = schema['maximum'];\n if (typeof max === 'number' && value > max) {\n issues.push({\n path,\n keyword: 'maximum',\n expected: `<= ${max}`,\n received: render(value),\n message: `Parameter \"${path}\" must be <= ${max}, received ${value}.`,\n });\n }\n }\n\n if (typeof value === 'string') {\n const minLength = schema['minLength'];\n if (typeof minLength === 'number' && value.length < minLength) {\n issues.push({\n path,\n keyword: 'minLength',\n expected: `length >= ${minLength}`,\n received: `length ${value.length}`,\n message: `Parameter \"${path}\" must be at least ${minLength} characters, received ${value.length}.`,\n });\n }\n const maxLength = schema['maxLength'];\n if (typeof maxLength === 'number' && value.length > maxLength) {\n issues.push({\n path,\n keyword: 'maxLength',\n expected: `length <= ${maxLength}`,\n received: `length ${value.length}`,\n message: `Parameter \"${path}\" must be at most ${maxLength} characters, received ${value.length}.`,\n });\n }\n const pattern = schema['pattern'];\n // ReDoS guard, BEFORE compilation: a catastrophically-backtracking pattern\n // does not throw, it hangs — no `try`/`catch` can rescue it, so the only\n // defence is not to run it. Same pair of tests `action-executor.ts` applies\n // to a caller-supplied `srcPattern`. Over the cap, or a nested-quantifier\n // construct, and the keyword is ignored like an uncompilable one.\n if (\n typeof pattern === 'string' &&\n pattern.length <= MAX_PATTERN_LENGTH &&\n !hasNestedQuantifiers(pattern)\n ) {\n // Declared WITHOUT an initializer on purpose: both arms below assign\n // before any read, so an initializer here would be dead (eslint\n // `no-useless-assignment`). The regex carries no `g` flag and is rebuilt\n // per call, so there is no `lastIndex` state to carry between values.\n let re: RegExp | null;\n try {\n re = new RegExp(pattern);\n } catch {\n // An uncompilable pattern expresses no constraint we can read. Ignored,\n // never a failure — same rule as an unrecognised keyword.\n re = null;\n }\n if (re !== null && !re.test(value)) {\n issues.push({\n path,\n keyword: 'pattern',\n expected: `match /${pattern}/`,\n received: render(value),\n message: `Parameter \"${path}\" must match /${pattern}/, received ${render(value)}.`,\n });\n }\n }\n }\n\n if (Array.isArray(value) && hasOwn(schema, 'items')) {\n const items = schema['items'];\n value.forEach((element, index) => {\n validateValue(element, items, `${path}[${index}]`, issues, depth + 1);\n });\n }\n\n if (\n isPlainObject(value) &&\n (isPlainObject(schema['properties']) ||\n hasOwn(schema, 'required') ||\n schema['additionalProperties'] === false)\n ) {\n validateObject(value, schema, path, issues, depth);\n }\n}\n\n/**\n * Validate a plain object against the `properties` / `required` /\n * `additionalProperties` triple. Shared by the top level and by nested\n * `type: \"object\"` properties.\n */\nfunction validateObject(\n value: PlainObject,\n schema: PlainObject,\n basePath: string,\n issues: ParamSchemaIssue[],\n depth: number\n): void {\n if (depth > MAX_SCHEMA_DEPTH) return;\n\n const properties = isPlainObject(schema['properties']) ? schema['properties'] : {};\n const requiredRaw = schema['required'];\n const required = Array.isArray(requiredRaw)\n ? requiredRaw.filter((k): k is string => typeof k === 'string')\n : [];\n\n for (const key of required) {\n // `hasOwn`, not `key in value` — see `hasOwn`. With `in`, a required param\n // named `toString` is never reported missing.\n if (!hasOwn(value, key) || value[key] === undefined) {\n const at = joinPath(basePath, key);\n issues.push({\n path: at,\n keyword: 'required',\n expected: 'present',\n message: `Required parameter \"${at}\" is missing.`,\n });\n }\n }\n\n if (schema['additionalProperties'] === false) {\n const declared = new Set(Object.keys(properties));\n // With no `properties` at all the schema says \"no params are accepted\" —\n // say that, rather than the empty \"one of []\" the generic phrasing gives.\n const expected = declared.size === 0 ? 'no parameters' : `one of [${[...declared].join(', ')}]`;\n for (const key of Object.keys(value)) {\n if (!declared.has(key)) {\n const at = joinPath(basePath, key);\n issues.push({\n path: at,\n keyword: 'additionalProperties',\n expected,\n received: render(value[key]),\n message: `Parameter \"${at}\" is not declared by the schema, which sets additionalProperties: false.`,\n });\n }\n }\n }\n\n for (const [key, propSchema] of Object.entries(properties)) {\n // `hasOwn`, not `key in value` — with `in`, a declared param named\n // `valueOf` would be \"present\" and validated against `Object.prototype`'s\n // function instead of the caller's (absent) value.\n if (!hasOwn(value, key) || value[key] === undefined) continue;\n validateValue(value[key], propSchema, joinPath(basePath, key), issues, depth + 1);\n }\n}\n\n/**\n * The form-1/form-2 disambiguation, in one place. See the module header for\n * the rule and for why the `type === 'object'` half is not enough on its own.\n *\n * @returns `true` for object-schema form (form 1), `false` for map form.\n */\nfunction isObjectSchemaForm(schema: PlainObject): boolean {\n if (isPlainObject(schema['properties'])) return true;\n if (schema['type'] !== 'object') return false;\n // `type: \"object\"` with NO `properties`. Object schema only if everything\n // else present is a schema keyword; a single non-keyword key (`id`,\n // `username`, …) means this is a map that happens to declare a param called\n // `type`, and reading it as a schema would silently drop every sibling\n // declaration in it.\n return Object.keys(schema).every((key) => OBJECT_SCHEMA_KEYWORDS.has(key));\n}\n\n/**\n * Normalise map form into the object-schema form so one walker serves both.\n * Returns `null` when `schema` is already object form.\n */\nfunction normalizeMapForm(schema: PlainObject): PlainObject | null {\n if (isObjectSchemaForm(schema)) return null;\n\n const properties: PlainObject = {};\n for (const [key, raw] of Object.entries(schema)) {\n if (raw === null || raw === undefined) continue;\n if (typeof raw === 'string') {\n // Type name, or prose? Only the seven primitive names are types. Every\n // other string is a human hint — ~50 live declarations in\n // `qontinui-runner` are exactly that — and expresses no constraint.\n properties[key] = PRIMITIVE_TYPE_NAMES.has(raw) ? { type: raw } : {};\n continue;\n }\n if (isPlainObject(raw)) {\n // `optional` is this monorepo's non-standard marker, not JSON Schema.\n // Nothing in map form is required, so it only needs stripping.\n const { optional: _optional, ...rest } = raw as PlainObject & { optional?: unknown };\n properties[key] = rest;\n continue;\n }\n // A value that is neither a type name nor a schema object expresses no\n // constraint — ignored, per the unrecognised-keyword rule.\n }\n // NO `required`: map form cannot express requiredness. See the module header.\n return { type: 'object', properties };\n}\n\n/**\n * Validate `params` against an action's `paramSchema`.\n *\n * Returns `{ valid: true, issues: [] }` for anything the documented subset\n * cannot read — that is the ignore-don't-reject rule, and it is why a schema\n * this validator does not understand can never fail an invocation.\n *\n * @param schema the action's declared `paramSchema` (any shape; unreadable\n * shapes constrain nothing)\n * @param params the params the caller supplied. `undefined` is treated as an\n * empty object, so a schema with required properties still reports them.\n */\nexport function validateActionParams(\n schema: unknown,\n params: unknown\n): ParamValidationResult {\n if (!isPlainObject(schema)) return { valid: true, issues: [] };\n\n const normalized = normalizeMapForm(schema) ?? schema;\n\n const hasProperties = isPlainObject(normalized['properties']);\n const hasRequired = Array.isArray(normalized['required']);\n // `additionalProperties: false` is a constraint ON ITS OWN, even with no\n // `properties` key: `{ type: 'object', additionalProperties: false }` is the\n // natural way to spell \"this action takes no params at all\", and the keyword\n // table above documents it. Omitting it from this test made that schema\n // accept every payload — the same silent-drop defect as the map-form one.\n const forbidsExtras = normalized['additionalProperties'] === false;\n if (!hasProperties && !hasRequired && !forbidsExtras) {\n // e.g. a bare `{ type: \"object\" }`. No constraint expressed.\n //\n // (The map-form branch cannot reach here: `normalizeMapForm` always\n // returns a `properties` object, empty or not — so `hasProperties` is\n // true for every map form, including one whose every entry was prose.)\n return { valid: true, issues: [] };\n }\n\n const issues: ParamSchemaIssue[] = [];\n\n if (params === undefined || params === null) {\n validateObject({}, normalized, '', issues, 0);\n return { valid: issues.length === 0, issues };\n }\n\n if (!isPlainObject(params)) {\n issues.push({\n path: '',\n keyword: 'type',\n expected: 'object',\n received: render(params),\n message: `Action params must be an object, received ${jsonTypeOf(params)} (${render(params)}).`,\n });\n return { valid: false, issues };\n }\n\n validateObject(params, normalized, '', issues, 0);\n return { valid: issues.length === 0, issues };\n}\n\n/**\n * One-line summary of a violation set, naming the action and every offending\n * param. Used as the `error` / `message` string at every invocation seam so\n * the three trees phrase the rejection identically.\n */\nexport function formatParamValidationFailure(\n componentId: string,\n actionId: string,\n issues: ParamSchemaIssue[]\n): string {\n const detail = issues.map((i) => i.message).join(' ');\n return `Action \"${actionId}\" on component \"${componentId}\" was rejected: params do not match the action's declared paramSchema. ${detail}`;\n}\n","/**\n * Native Action Executor\n *\n * Executes actions on registered native elements and components.\n * In React Native, we execute actions by calling prop handlers directly\n * (onPress, onChangeText, etc.) rather than simulating DOM events.\n */\n\nimport type { NativeUIBridgeRegistry } from '../core/registry';\nimport type {\n NativeStandardAction,\n NativeFindRequest,\n NativeFindResponse,\n DiscoveredNativeElement,\n WaitOptions,\n} from '../core/types';\nimport { findElementByIdentifier } from '../core/element-identifier';\nimport { pageRectOf, isEmptyRect, intersectRects } from '../core/registry';\nimport type { NativePageRect } from '../core/registry';\n// Phase 3 (plan 2026-08-20-ui-bridge-action-declaration-shape). A justified\n// duplicate of the web SDK's primitive — see the header of `core/abortable.ts`\n// for why this package cannot import it.\nimport { runAbortable, normalizeActionTimeoutMs, inertAbortSignal } from '../core/abortable';\n// Phase 2 (same plan). A justified duplicate of the web SDK's validator — see\n// the header of `core/param-schema.ts` for why this package cannot import it.\nimport {\n validateActionParams,\n formatParamValidationFailure,\n getDefaultParamValidationMode,\n} from '../core/param-schema';\nimport type {\n ControlActionRequest,\n ControlActionResponse,\n ComponentActionRequest,\n ComponentActionResponse,\n ComponentActionInvokeOptions,\n WaitResult,\n NativeActionExecutor,\n NativeActionEvent,\n NativeActionListener,\n TypeActionParams,\n ScrollActionParams,\n ScrollIntoViewActionParams,\n SwipeActionParams,\n PressActionParams,\n} from './types';\n\n/**\n * Default wait options\n */\nconst DEFAULT_WAIT_OPTIONS: Required<WaitOptions> = {\n visible: true,\n enabled: true,\n focused: false,\n state: {},\n timeout: 10000,\n interval: 100,\n};\n\n/**\n * An action failure that carries a machine-readable code of its own, so the\n * HTTP handler can report it as that code instead of the generic\n * `ACTION_FAILED`. Used for `NOT_SUPPORTED` — \"this element cannot do that\",\n * which a caller must not retry with a different request shape.\n */\nexport class NativeActionError extends Error {\n constructor(\n message: string,\n readonly code: string\n ) {\n super(message);\n this.name = 'NativeActionError';\n }\n}\n\n/** Default gap (logical dp) left above an element scrolled into view. */\nconst DEFAULT_SCROLL_INTO_VIEW_PADDING = 16;\n\n/** Upper bound on waiting for `measureLayout` to call back. */\nconst SCROLL_INTO_VIEW_MEASURE_TIMEOUT_MS = 1000;\n\n/** The slice of a React Native `ScrollView` instance `scrollIntoView` drives. */\ninterface ScrollContainerRef {\n scrollTo: (options: { x?: number; y?: number; animated?: boolean }) => void;\n getInnerViewRef?: () => unknown;\n getInnerViewNode?: () => unknown;\n /** VirtualizedList family (FlatList / SectionList): the underlying ScrollView. */\n getNativeScrollRef?: () => unknown;\n getScrollResponder?: () => unknown;\n}\n\n/** Upper bound on the targeted `measureInWindow` used for the visibility check. */\nconst SCROLL_INTO_VIEW_WINDOW_MEASURE_TIMEOUT_MS = 250;\n\ntype WindowMeasurable = {\n measureInWindow?: (cb: (x: number, y: number, w: number, h: number) => void) => void;\n};\n\n/**\n * The page rect of one registered element, measured NOW where its ref allows.\n *\n * A ref with `measureInWindow` is measured directly (bounded); a timeout,\n * throw, or zero-size answer is UNKNOWN (`null`). A ref without it (a test\n * fixture, a non-host wrapper) falls back to the registry's stored layout —\n * the same evidence the snapshot route would report for it.\n */\nfunction measurePageRectNow(\n element: NonNullable<ReturnType<NativeUIBridgeRegistry['getElement']>>,\n preferred?: unknown\n): Promise<NativePageRect | null> {\n // `preferred` lets a caller measure a better node than the registered ref —\n // the ScrollView a FlatList wraps, whose class instance has no measureInWindow.\n const preferredNode = preferred as WindowMeasurable | null | undefined;\n const node =\n preferredNode && typeof preferredNode.measureInWindow === 'function'\n ? preferredNode\n : (element.ref.current as unknown as WindowMeasurable | null);\n if (!node || typeof node.measureInWindow !== 'function') {\n const state = element.getState();\n return Promise.resolve(state.visible ? pageRectOf(state) : null);\n }\n return new Promise((resolve) => {\n let settled = false;\n const done = (rect: NativePageRect | null) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(rect);\n };\n const timer = setTimeout(() => done(null), SCROLL_INTO_VIEW_WINDOW_MEASURE_TIMEOUT_MS);\n try {\n node.measureInWindow!((x, y, w, h) =>\n done(\n [x, y, w, h].every(Number.isFinite) && w > 0 && h > 0\n ? { left: x, top: y, right: x + w, bottom: y + h }\n : null\n )\n );\n } catch {\n done(null);\n }\n });\n}\n\n/** The slice of a React Native host view `scrollIntoView` measures with. */\ninterface MeasurableRef {\n measureLayout: (\n relativeTo: unknown,\n onSuccess: (x: number, y: number, width: number, height: number) => void,\n onFail?: () => void\n ) => void;\n}\n\n/**\n * The ScrollView-shaped object behind a container ref: the ref itself when it\n * has `scrollTo`, else the ScrollView a VirtualizedList (FlatList /\n * SectionList) wraps. `null` when neither yields one.\n */\nfunction resolveScrollView(\n current: unknown\n): (Partial<ScrollContainerRef> & Pick<ScrollContainerRef, 'scrollTo'>) | null {\n const c = current as Partial<ScrollContainerRef> | null | undefined;\n if (!c) return null;\n if (typeof c.scrollTo === 'function')\n return c as Partial<ScrollContainerRef> & Pick<ScrollContainerRef, 'scrollTo'>;\n for (const unwrap of [c.getNativeScrollRef, c.getScrollResponder]) {\n if (typeof unwrap !== 'function') continue;\n let inner: Partial<ScrollContainerRef> | null | undefined;\n try {\n inner = unwrap.call(c) as Partial<ScrollContainerRef> | null | undefined;\n } catch {\n inner = null;\n }\n if (inner && typeof inner.scrollTo === 'function')\n return inner as Partial<ScrollContainerRef> & Pick<ScrollContainerRef, 'scrollTo'>;\n }\n return null;\n}\n\n/**\n * Is the container visibly declared `horizontal`? Checks captured registry\n * props, then a class-component ref's own `props` (FlatList / SectionList\n * instances), then the resolved ScrollView's. See the LIMIT note at the call\n * site — an invisible declaration cannot be detected.\n */\nfunction isDeclaredHorizontal(\n registeredProps: Record<string, unknown> | undefined,\n ...refs: unknown[]\n): boolean {\n if (registeredProps?.horizontal === true) return true;\n return refs.some((r) => {\n const props = (r as { props?: { horizontal?: unknown } } | null | undefined)?.props;\n return props !== null && typeof props === 'object' && props.horizontal === true;\n });\n}\n\n/**\n * Sleep for a duration\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Default native action executor implementation\n */\nexport class DefaultNativeActionExecutor implements NativeActionExecutor {\n private actionListeners = new Set<NativeActionListener>();\n\n constructor(private registry: NativeUIBridgeRegistry) {}\n\n onActionExecuted(listener: NativeActionListener): () => void {\n this.actionListeners.add(listener);\n return () => {\n this.actionListeners.delete(listener);\n };\n }\n\n /** Notify listeners out-of-band; isolate each invocation so one bad listener can't poison the rest. */\n private emitActionEvent(event: NativeActionEvent): void {\n for (const listener of this.actionListeners) {\n try {\n listener(event);\n } catch (error) {\n console.warn(`[ui-bridge-native] Action listener error:`, error);\n }\n }\n }\n\n /**\n * Execute an action on an element\n */\n async executeAction(\n elementId: string,\n request: ControlActionRequest\n ): Promise<ControlActionResponse> {\n const startTime = Date.now();\n let waitDurationMs = 0;\n\n try {\n // Find the element\n let registered = this.registry.getElement(elementId);\n\n // If not in registry, try by identifier\n if (!registered) {\n registered = findElementByIdentifier(elementId) ?? undefined;\n }\n\n if (!registered) {\n const failTime = Date.now();\n const errorMsg = `Element not found: ${elementId}`;\n this.emitActionEvent({\n elementId,\n action: request.action,\n params: request.params,\n success: false,\n error: errorMsg,\n timestamp: failTime,\n requestId: request.requestId,\n durationMs: failTime - startTime,\n });\n return {\n success: false,\n error: errorMsg,\n durationMs: failTime - startTime,\n timestamp: failTime,\n requestId: request.requestId,\n };\n }\n\n // Wait for conditions if specified\n if (request.waitOptions) {\n const waitResult = await this.waitForElementInternal(registered.id, request.waitOptions);\n waitDurationMs = waitResult.waitedMs;\n if (!waitResult.met) {\n const failTime = Date.now();\n const errorMsg = waitResult.error || 'Wait condition not met';\n this.emitActionEvent({\n elementId,\n action: request.action,\n params: request.params,\n success: false,\n error: errorMsg,\n timestamp: failTime,\n requestId: request.requestId,\n durationMs: failTime - startTime,\n });\n return {\n success: false,\n error: errorMsg,\n durationMs: failTime - startTime,\n timestamp: failTime,\n requestId: request.requestId,\n waitDurationMs,\n };\n }\n }\n\n // Execute the action\n const result = await this.performAction(registered, request.action, request.params);\n\n const successTime = Date.now();\n this.emitActionEvent({\n elementId,\n action: request.action,\n params: request.params,\n success: true,\n timestamp: successTime,\n requestId: request.requestId,\n durationMs: successTime - startTime,\n });\n // Re-fetch the element from the registry rather than reusing the\n // `registered` reference captured before `performAction`. Mutating\n // actions (`type`/`setValue`/`clear`/`focus`/...) call\n // `registry.updateElementState`, which REPLACES the map entry with a\n // fresh object carrying a new `getState` closure — the captured\n // `registered.getState` is stale and omits the just-set `value`. Without\n // this re-fetch the action response's `elementState` disagrees with a\n // subsequent `GET /control/element/:id` and `/control/snapshot`, which is\n // the inconsistent read-path the manual-test session flagged.\n const fresh = this.registry.getElement(registered.id) ?? registered;\n return {\n success: true,\n elementState: fresh.getState(),\n result,\n durationMs: successTime - startTime,\n timestamp: successTime,\n requestId: request.requestId,\n waitDurationMs,\n };\n } catch (error) {\n const failTime = Date.now();\n const errorMsg = error instanceof Error ? error.message : String(error);\n this.emitActionEvent({\n elementId,\n action: request.action,\n params: request.params,\n success: false,\n error: errorMsg,\n timestamp: failTime,\n requestId: request.requestId,\n durationMs: failTime - startTime,\n });\n return {\n success: false,\n error: errorMsg,\n ...(error instanceof NativeActionError ? { code: error.code } : {}),\n stack: error instanceof Error ? error.stack : undefined,\n durationMs: failTime - startTime,\n timestamp: failTime,\n requestId: request.requestId,\n waitDurationMs,\n };\n }\n }\n\n /**\n * Perform an action on an element\n */\n private async performAction(\n element: ReturnType<NativeUIBridgeRegistry['getElement']>,\n action: NativeStandardAction | string,\n params?: Record<string, unknown>\n ): Promise<unknown> {\n if (!element) {\n throw new Error('Element not found');\n }\n\n const props = element.props || {};\n\n // Check for custom action first\n if (element.customActions && action in element.customActions) {\n // The options bag is ALWAYS supplied — `ActionHandlerOptions` promises\n // it, and a handler written the documented way (`(params, { signal }) =>\n // …`) throws on `undefined`. An element action carries no cancellation\n // source, so the signal is inert; see `inertAbortSignal`.\n return element.customActions[action].handler(params, { signal: inertAbortSignal() });\n }\n\n // Execute standard actions\n switch (action) {\n case 'click': // Web UI Bridge compatibility alias\n case 'press':\n return this.performPress(props, params as PressActionParams | undefined);\n\n case 'longPress':\n return this.performLongPress(props, params as PressActionParams | undefined);\n\n case 'doubleTap':\n return this.performDoubleTap(props);\n\n case 'type':\n return this.performType(element, props, params as unknown as TypeActionParams);\n\n case 'setValue':\n return this.performSetValue(element, props, params as unknown as TypeActionParams);\n\n case 'clear':\n return this.performClear(element, props);\n\n case 'focus':\n return this.performFocus(element);\n\n case 'blur':\n return this.performBlur(element);\n\n case 'scroll':\n return this.performScroll(props, params as ScrollActionParams | undefined);\n\n case 'scrollIntoView':\n return this.performScrollIntoView(\n element,\n params as ScrollIntoViewActionParams | undefined\n );\n\n case 'swipe':\n return this.performSwipe(props, params as unknown as SwipeActionParams);\n\n case 'toggle':\n return this.performToggle(props);\n\n default:\n throw new Error(`Unknown action: ${action}`);\n }\n }\n\n /**\n * Perform press action\n */\n private async performPress(\n props: Record<string, unknown>,\n params?: PressActionParams\n ): Promise<void> {\n // Try different press handlers in order of preference\n const handlers = ['onPress', 'onPressIn', 'onResponderRelease'];\n\n for (const handler of handlers) {\n if (typeof props[handler] === 'function') {\n // Create a synthetic event object\n const event = this.createPressEvent(params);\n (props[handler] as (event: unknown) => void)(event);\n return;\n }\n }\n\n throw new Error('No press handler found on element');\n }\n\n /**\n * Perform long press action\n */\n private async performLongPress(\n props: Record<string, unknown>,\n params?: PressActionParams\n ): Promise<void> {\n if (typeof props.onLongPress === 'function') {\n const event = this.createPressEvent(params);\n (props.onLongPress as (event: unknown) => void)(event);\n return;\n }\n\n throw new Error('No long press handler found on element');\n }\n\n /**\n * Perform double tap action\n */\n private async performDoubleTap(props: Record<string, unknown>): Promise<void> {\n // First try dedicated double tap handler\n if (typeof props.onDoubleTap === 'function') {\n (props.onDoubleTap as () => void)();\n return;\n }\n\n // Fall back to calling press twice\n if (typeof props.onPress === 'function') {\n const event = this.createPressEvent();\n (props.onPress as (event: unknown) => void)(event);\n await sleep(50);\n (props.onPress as (event: unknown) => void)(event);\n return;\n }\n\n throw new Error('No press handler found for double tap');\n }\n\n /**\n * Perform type action\n */\n private async performType(\n element: ReturnType<NativeUIBridgeRegistry['getElement']>,\n props: Record<string, unknown>,\n params: TypeActionParams\n ): Promise<void> {\n if (!params?.text) {\n throw new Error('Type action requires text parameter');\n }\n\n // Clear first if requested. Accept either `clearFirst` (native SDK\n // convention) or `clear` (runner UI Bridge convention) — so agents\n // using the same action across platforms don't hit a naming mismatch.\n if (params.clearFirst || params.clear) {\n await this.performClear(element, props);\n }\n\n // Type character by character if delay specified\n if (params.delay && params.delay > 0) {\n const currentValue = (element?.getState().value || '') as string;\n for (const char of params.text) {\n const newValue = currentValue + char;\n if (typeof props.onChangeText === 'function') {\n (props.onChangeText as (text: string) => void)(newValue);\n }\n await sleep(params.delay);\n }\n } else {\n // Type all at once\n if (typeof props.onChangeText === 'function') {\n (props.onChangeText as (text: string) => void)(params.text);\n } else if (typeof props.onChange === 'function') {\n (props.onChange as (event: { nativeEvent: { text: string } }) => void)({\n nativeEvent: { text: params.text },\n });\n } else {\n throw new Error('No text change handler found on element');\n }\n }\n\n // Update element state\n if (element) {\n this.registry.updateElementState(element.id, { value: params.text });\n }\n }\n\n /**\n * Perform setValue action.\n *\n * Web/runner UI Bridge parity: `setValue` replaces the input's value\n * atomically in a single call (no per-character keystroke simulation),\n * mirroring the web bridge's `setValue` semantics. On native there is no\n * DOM value to assign, so we drive the controlled-input handler\n * (`onChangeText`, falling back to `onChange`) with the full string. The\n * registry's `state.value` is then synced so a subsequent GET on the\n * element reflects the new value.\n *\n * Unlike `type`, this does not append to existing content and ignores\n * `delay` — it is the deterministic \"set the field to exactly this\" action.\n */\n private async performSetValue(\n element: ReturnType<NativeUIBridgeRegistry['getElement']>,\n props: Record<string, unknown>,\n params: TypeActionParams\n ): Promise<void> {\n // `text` is the canonical key; accept `value` as an alias so callers using\n // the web bridge's `{ value }` body shape work without translation.\n const next =\n typeof params?.text === 'string'\n ? params.text\n : typeof (params as { value?: unknown })?.value === 'string'\n ? ((params as { value?: string }).value as string)\n : undefined;\n\n if (typeof next !== 'string') {\n throw new Error('setValue action requires a string \"text\" (or \"value\") parameter');\n }\n\n if (typeof props.onChangeText === 'function') {\n (props.onChangeText as (text: string) => void)(next);\n } else if (typeof props.onChange === 'function') {\n (props.onChange as (event: { nativeEvent: { text: string } }) => void)({\n nativeEvent: { text: next },\n });\n } else {\n throw new Error('No text change handler found on element');\n }\n\n if (element) {\n this.registry.updateElementState(element.id, { value: next });\n }\n }\n\n /**\n * Perform clear action\n */\n private async performClear(\n element: ReturnType<NativeUIBridgeRegistry['getElement']>,\n props: Record<string, unknown>\n ): Promise<void> {\n if (typeof props.onChangeText === 'function') {\n (props.onChangeText as (text: string) => void)('');\n } else if (typeof props.onChange === 'function') {\n (props.onChange as (event: { nativeEvent: { text: string } }) => void)({\n nativeEvent: { text: '' },\n });\n }\n\n // Update element state\n if (element) {\n this.registry.updateElementState(element.id, { value: '' });\n }\n }\n\n /**\n * Perform focus action\n */\n private async performFocus(\n element: ReturnType<NativeUIBridgeRegistry['getElement']>\n ): Promise<void> {\n if (element?.ref.current && 'focus' in element.ref.current) {\n (element.ref.current as { focus: () => void }).focus();\n }\n\n // Update element state\n if (element) {\n this.registry.updateElementState(element.id, { focused: true });\n }\n }\n\n /**\n * Perform blur action\n */\n private async performBlur(\n element: ReturnType<NativeUIBridgeRegistry['getElement']>\n ): Promise<void> {\n if (element?.ref.current && 'blur' in element.ref.current) {\n (element.ref.current as { blur: () => void }).blur();\n }\n\n // Update element state\n if (element) {\n this.registry.updateElementState(element.id, { focused: false });\n }\n }\n\n /**\n * Perform scroll action\n */\n private async performScroll(\n props: Record<string, unknown>,\n params?: ScrollActionParams\n ): Promise<void> {\n if (typeof props.onScroll === 'function') {\n const event = {\n nativeEvent: {\n contentOffset: params?.offset || { x: 0, y: 0 },\n },\n };\n (props.onScroll as (event: unknown) => void)(event);\n }\n }\n\n /**\n * Perform scrollIntoView: scroll the element's DECLARED scroll container so\n * the element sits `padding` dp below the top of its viewport.\n *\n * The container comes from `scrollAncestorId` and nowhere else — the\n * registry keeps no parent chain (`RegisterElementOptions.scrollAncestorId`),\n * so there is no \"nearest ScrollView\" to find. Nothing here tracks a\n * ScrollView's content offset either (`performScroll` only fakes an\n * `onScroll` event), so the target is measured CONTENT-relative with\n * `measureLayout` against the container's inner content view: stateless,\n * and immune to a stale-offset race.\n *\n * Result shape mirrors the web executor's `scrollIntoView`\n * (`@qontinui/ui-bridge` `control/action-executor.ts`):\n * `{ alreadyVisible, scrolled }`, short-circuiting when the element is\n * already fully inside its clip region. \"Fully visible\" is only claimed when\n * it is MEASURED — a page-space rect for the element and a known,\n * non-empty clip (window ∩ declared container), both measured at call\n * time. Anything unknown scrolls.\n *\n * Containers: a ScrollView, or a VirtualizedList (FlatList / SectionList)\n * unwrapped through `getNativeScrollRef()` / `getScrollResponder()`.\n * Vertical only — a `horizontal` container is NOT_SUPPORTED. The action\n * returns once `scrollTo` is dispatched; positions settle on the next frame.\n *\n * Prerequisites are checked BEFORE the visibility short-circuit, so whether\n * the element supports the action does not depend on where it happens to be\n * on screen. Each missing prerequisite is a `NOT_SUPPORTED` failure.\n */\n private async performScrollIntoView(\n element: NonNullable<ReturnType<NativeUIBridgeRegistry['getElement']>>,\n params?: ScrollIntoViewActionParams\n ): Promise<{ alreadyVisible: boolean; scrolled: boolean }> {\n const padding = params?.padding ?? DEFAULT_SCROLL_INTO_VIEW_PADDING;\n if (typeof padding !== 'number' || !Number.isFinite(padding) || padding < 0) {\n throw new Error('scrollIntoView \"padding\" must be a finite, non-negative number');\n }\n\n const ancestorId = element.scrollAncestorId;\n if (!ancestorId) {\n throw new NativeActionError(\n `scrollIntoView is not supported on element \"${element.id}\": it declares no scrollAncestorId, ` +\n 'and the registry keeps no parent chain to find its scroll container from',\n 'NOT_SUPPORTED'\n );\n }\n const ancestor = ancestorId === element.id ? undefined : this.registry.getElement(ancestorId);\n if (!ancestor) {\n throw new NativeActionError(\n `scrollIntoView is not supported on element \"${element.id}\": its declared scrollAncestorId ` +\n `\"${ancestorId}\" is not a registered element`,\n 'NOT_SUPPORTED'\n );\n }\n const container = resolveScrollView(ancestor.ref.current);\n if (!container) {\n throw new NativeActionError(\n `scrollIntoView is not supported on element \"${element.id}\": its scroll ancestor ` +\n `\"${ancestorId}\" has no scrollTo (not a mounted ScrollView, and no ` +\n 'getNativeScrollRef/getScrollResponder yielding one)',\n 'NOT_SUPPORTED'\n );\n }\n if (isDeclaredHorizontal(ancestor.props, ancestor.ref.current, container)) {\n // `scrollTo` here moves only y; RN sends `x || 0`, so on a horizontal\n // ScrollView this would jump the content to its start and still report\n // success. Refuse rather than lie.\n //\n // LIMIT: detected only where `horizontal` is visible to the bridge —\n // captured props (`captureProps({ horizontal: true })` / a direct\n // `registerElement` with `props`) or a class-component ref carrying\n // `props.horizontal` (FlatList / SectionList instances). A horizontal\n // ScrollView registered through `useUIElement` without captured props\n // is NOT detected; the action is documented as vertical-only.\n throw new NativeActionError(\n `scrollIntoView is not supported on element \"${element.id}\": its scroll ancestor ` +\n `\"${ancestorId}\" is horizontal (only vertical containers are supported)`,\n 'NOT_SUPPORTED'\n );\n }\n // `getInnerViewRef()` returns a host instance on both architectures and is\n // the primary path. `getInnerViewNode()` returns a numeric tag, which only\n // the legacy architecture's `measureLayout` accepts (Fabric ignores it and\n // never calls back — the timeout below then names that cause).\n const innerViewRef = container.getInnerViewRef?.();\n const innerView = innerViewRef ?? container.getInnerViewNode?.();\n const innerViewIsTag = innerViewRef == null && typeof innerView === 'number';\n if (innerView === undefined || innerView === null) {\n throw new NativeActionError(\n `scrollIntoView is not supported on element \"${element.id}\": its scroll ancestor ` +\n `\"${ancestorId}\" exposes no inner content view to measure against`,\n 'NOT_SUPPORTED'\n );\n }\n const target = element.ref.current as unknown as Partial<MeasurableRef> | null;\n if (!target || typeof target.measureLayout !== 'function') {\n throw new NativeActionError(\n `scrollIntoView is not supported on element \"${element.id}\": its ref has no measureLayout ` +\n '(not a mounted host view)',\n 'NOT_SUPPORTED'\n );\n }\n\n // Already-visible short-circuit — only on MEASURED evidence: the target\n // AND its declared container are both measured now (targeted, bounded —\n // not a registry-wide sweep), and the target lies fully inside\n // window ∩ container. An unknown container rect must NOT fall back to the\n // window alone: a row below a short ScrollView's bottom edge is still\n // inside the window. Anything unknown scrolls.\n const [rect, ancestorRect] = await Promise.all([\n measurePageRectNow(element),\n measurePageRectNow(ancestor, container),\n ]);\n // With no viewport provider the window is UNKNOWN and only the container\n // bounds the check — the same answer `getClipRectFor` gives.\n const viewport = this.registry.getViewportRect();\n const clip = ancestorRect ? intersectRects(viewport, ancestorRect) : null;\n if (\n rect &&\n clip &&\n !isEmptyRect(clip) &&\n rect.right > rect.left &&\n rect.bottom > rect.top &&\n rect.top >= clip.top &&\n rect.left >= clip.left &&\n rect.bottom <= clip.bottom &&\n rect.right <= clip.right\n ) {\n return { alreadyVisible: true, scrolled: false };\n }\n\n const measured = await new Promise<{ x: number; y: number; width: number }>(\n (resolve, reject) => {\n let settled = false;\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n reject(\n new Error(\n `scrollIntoView: measureLayout did not call back within ${SCROLL_INTO_VIEW_MEASURE_TIMEOUT_MS}ms` +\n (innerViewIsTag\n ? ' (measured against a numeric inner-view tag from getInnerViewNode, which the new architecture does not support)'\n : '')\n )\n );\n }, SCROLL_INTO_VIEW_MEASURE_TIMEOUT_MS);\n const finish = (fn: () => void) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n fn();\n };\n try {\n target.measureLayout!(\n innerView,\n (x, y, width) =>\n finish(() =>\n Number.isFinite(y)\n ? resolve({ x, y, width })\n : reject(new Error('scrollIntoView: measureLayout returned a non-finite y'))\n ),\n () =>\n finish(() =>\n reject(\n new Error(\n `scrollIntoView: measureLayout failed for \"${element.id}\" relative to \"${ancestorId}\"`\n )\n )\n )\n );\n } catch (error) {\n finish(() => reject(error));\n }\n }\n );\n\n // Undeclared-horizontal backstop: content laid out sideways past the\n // container's measured width means scrolling only y cannot reach it (and\n // RN's `x || 0` would reset the x offset). Refuse rather than report a\n // scroll that did not bring the element into view. Skipped when the\n // container width is unknown.\n if (\n ancestorRect &&\n Number.isFinite(measured.x) &&\n Number.isFinite(measured.width) &&\n measured.x + measured.width > ancestorRect.right - ancestorRect.left + 1\n ) {\n throw new NativeActionError(\n `scrollIntoView is not supported on element \"${element.id}\": it is laid out beyond the ` +\n `horizontal extent of its scroll ancestor \"${ancestorId}\" (sideways overflow, a horizontal ` +\n 'container, or a nested horizontal container — declare that inner container as its ' +\n 'scrollAncestorId); only vertical scrolling is supported',\n 'NOT_SUPPORTED'\n );\n }\n\n container.scrollTo({ y: Math.max(0, measured.y - padding), animated: false });\n return { alreadyVisible: false, scrolled: true };\n }\n\n /**\n * Perform swipe action\n */\n private async performSwipe(\n props: Record<string, unknown>,\n params: SwipeActionParams\n ): Promise<void> {\n if (!params?.direction) {\n throw new Error('Swipe action requires direction parameter');\n }\n\n // Try dedicated swipe handlers\n const handlerMap: Record<string, string> = {\n left: 'onSwipeLeft',\n right: 'onSwipeRight',\n up: 'onSwipeUp',\n down: 'onSwipeDown',\n };\n\n const handler = handlerMap[params.direction];\n if (handler && typeof props[handler] === 'function') {\n (props[handler] as () => void)();\n return;\n }\n\n // Fall back to generic swipe handler\n if (typeof props.onSwipe === 'function') {\n (props.onSwipe as (direction: string) => void)(params.direction);\n }\n }\n\n /**\n * Perform toggle action\n */\n private async performToggle(props: Record<string, unknown>): Promise<void> {\n // For Switch components\n if (typeof props.onValueChange === 'function') {\n const currentValue = props.value as boolean;\n (props.onValueChange as (value: boolean) => void)(!currentValue);\n return;\n }\n\n // Fall back to press\n if (typeof props.onPress === 'function') {\n (props.onPress as () => void)();\n return;\n }\n\n throw new Error('No toggle handler found on element');\n }\n\n /**\n * Create a synthetic press event\n */\n private createPressEvent(params?: PressActionParams): object {\n return {\n nativeEvent: {\n locationX: params?.position?.x ?? 0,\n locationY: params?.position?.y ?? 0,\n timestamp: Date.now(),\n },\n persist: () => {},\n };\n }\n\n /**\n * Execute a component action\n */\n async executeComponentAction(\n componentId: string,\n request: ComponentActionRequest,\n options: ComponentActionInvokeOptions = {}\n ): Promise<ComponentActionResponse> {\n const startTime = Date.now();\n\n try {\n const component = this.registry.getComponent(componentId);\n\n if (!component) {\n return {\n success: false,\n error: `Component not found: ${componentId}`,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n }\n\n // Find the action\n const action = component.actions.find((a) => a.id === request.action);\n\n if (!action) {\n return {\n success: false,\n error: `Action not found: ${request.action}`,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n }\n\n // `timeoutMs` is WIRE data — the HTTP entry point forwards it verbatim\n // from a JSON body — so it is validated and clamped here, before it can\n // reach a timer. See `normalizeActionTimeoutMs` for the policy (0\n // abandons on the next tick; negative / NaN / non-numeric are refused;\n // anything above 24h is clamped, because past 2^31-1 `setTimeout` wraps\n // negative and fires immediately).\n const timeout = normalizeActionTimeoutMs(request.timeoutMs);\n if (!timeout.ok) {\n return {\n success: false,\n error: `Action \"${request.action}\" on component \"${componentId}\" was rejected: ${timeout.reason}.`,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n }\n const timeoutMs = timeout.timeoutMs;\n\n // Phase 2: `paramSchema` is published to agents, so it has to mean\n // something. Validate BEFORE the handler runs — a rejection after a\n // side-effect is not a rejection.\n const validationMode = options.paramValidation ?? getDefaultParamValidationMode();\n if (validationMode !== 'off' && action.paramSchema !== undefined) {\n // The validator walks AUTHOR-supplied schema data. It is bounded\n // against both known fault routes (`param-schema.ts` MAX_SCHEMA_DEPTH /\n // MAX_PATTERN_LENGTH), but it is wrapped anyway: without this, a\n // validator fault would fall into the generic catch below and be\n // reported as a HANDLER failure, for a handler that never ran.\n let validation: ReturnType<typeof validateActionParams>;\n try {\n validation = validateActionParams(action.paramSchema, request.params);\n } catch (validatorFault) {\n const detail =\n validatorFault instanceof Error ? validatorFault.message : String(validatorFault);\n const faultMessage = `Action \"${request.action}\" on component \"${componentId}\": its declared paramSchema could not be evaluated (${detail}).`;\n if (validationMode === 'enforce') {\n // A gate that cannot be evaluated has not been cleared. The prose\n // names the SCHEMA, so it is not mistaken for a handler fault.\n return {\n success: false,\n error: faultMessage,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n }\n console.warn(`[ui-bridge-native] ${faultMessage}`);\n validation = { valid: true, issues: [] };\n }\n if (!validation.valid) {\n const message = formatParamValidationFailure(\n componentId,\n request.action,\n validation.issues\n );\n if (validationMode === 'enforce') {\n // Prose `error` rather than structured `failureDetails` — this\n // tree's response type has no such field. See the note on\n // `ComponentActionInvokeOptions.paramValidation`.\n return {\n success: false,\n error: message,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n }\n console.warn(`[ui-bridge-native] ${message}`);\n }\n }\n\n // The handler is *given* a signal (cooperative cancellation) and is\n // *raced* against it (enforced abandonment) — a handler that ignores its\n // signal must still be abandonable at the caller. Phase 3 of plan\n // 2026-08-20-ui-bridge-action-declaration-shape.\n const outcome = await runAbortable((signal) => action.handler(request.params, { signal }), {\n signal: options.signal,\n timeoutMs,\n });\n\n if (outcome.aborted) {\n // NOTE: this tree's `ComponentActionResponse` has no `failureDetails`\n // field and this package ships no diagnostics module, so the\n // cancellation surfaces as a prose `error` rather than through\n // `buildActionFailureDetails('UB-ACTION-FAILED', ...)` the way the web\n // seam does. Giving the native channel structured failure details is a\n // separate change, not Phase 3.\n return {\n success: false,\n error:\n outcome.reason === 'timeout'\n ? `Action \"${request.action}\" on component \"${componentId}\" was abandoned after its ${timeoutMs}ms timeout elapsed.`\n : `Action \"${request.action}\" on component \"${componentId}\" was cancelled by the caller's abort signal.`,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n }\n\n return {\n success: true,\n result: outcome.result,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n }\n }\n\n /**\n * Find elements\n */\n async find(request: NativeFindRequest): Promise<NativeFindResponse> {\n const startTime = Date.now();\n\n const allElements = this.registry.getAllElements();\n let filtered = allElements;\n\n // Filter by type\n if (request.types && request.types.length > 0) {\n filtered = filtered.filter((e) => request.types!.includes(e.type));\n }\n\n // Filter by testID pattern\n if (request.testIdPattern) {\n const regex = new RegExp(request.testIdPattern.replace(/\\*/g, '.*').replace(/\\?/g, '.'));\n filtered = filtered.filter((e) => {\n const identifier = e.getIdentifier();\n return identifier.testId && regex.test(identifier.testId);\n });\n }\n\n // Filter by accessibility label pattern\n if (request.accessibilityLabelPattern) {\n const regex = new RegExp(\n request.accessibilityLabelPattern.replace(/\\*/g, '.*').replace(/\\?/g, '.')\n );\n filtered = filtered.filter((e) => {\n const identifier = e.getIdentifier();\n return identifier.accessibilityLabel && regex.test(identifier.accessibilityLabel);\n });\n }\n\n // Filter by visibility\n if (request.visibleOnly) {\n filtered = filtered.filter((e) => e.getState().visible);\n }\n\n // Apply limit\n if (request.limit && request.limit > 0) {\n filtered = filtered.slice(0, request.limit);\n }\n\n // Map to discovered elements\n const elements: DiscoveredNativeElement[] = filtered.map((e) => ({\n id: e.id,\n type: e.type,\n identifier: e.getIdentifier(),\n state: e.getState(),\n actions: e.actions,\n label: e.label,\n }));\n\n return {\n elements,\n total: elements.length,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n };\n }\n\n /**\n * Wait for element conditions\n */\n async waitForElement(elementId: string, options: WaitOptions): Promise<WaitResult> {\n return this.waitForElementInternal(elementId, options);\n }\n\n /**\n * Internal wait implementation\n */\n private async waitForElementInternal(\n elementId: string,\n options: WaitOptions\n ): Promise<WaitResult> {\n const opts = { ...DEFAULT_WAIT_OPTIONS, ...options };\n const startTime = Date.now();\n\n while (Date.now() - startTime < opts.timeout) {\n const element = this.registry.getElement(elementId);\n\n if (!element) {\n await sleep(opts.interval);\n continue;\n }\n\n const state = element.getState();\n\n // Check conditions\n let conditionsMet = true;\n\n if (opts.visible && !state.visible) {\n conditionsMet = false;\n }\n\n if (opts.enabled && !state.enabled) {\n conditionsMet = false;\n }\n\n if (opts.focused && !state.focused) {\n conditionsMet = false;\n }\n\n // Check custom state conditions\n if (opts.state && Object.keys(opts.state).length > 0) {\n const stateRecord = state as unknown as Record<string, unknown>;\n for (const [key, value] of Object.entries(opts.state)) {\n if (stateRecord[key] !== value) {\n conditionsMet = false;\n break;\n }\n }\n }\n\n if (conditionsMet) {\n return {\n met: true,\n waitedMs: Date.now() - startTime,\n state,\n };\n }\n\n await sleep(opts.interval);\n }\n\n return {\n met: false,\n waitedMs: Date.now() - startTime,\n error: `Timeout waiting for conditions on element: ${elementId}`,\n };\n }\n}\n\n/**\n * Create a native action executor\n */\nexport function createNativeActionExecutor(registry: NativeUIBridgeRegistry): NativeActionExecutor {\n return new DefaultNativeActionExecutor(registry);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/registry.ts","../../src/core/element-identifier.ts","../../src/core/abortable.ts","../../src/core/param-schema.ts","../../src/control/action-executor.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Native Element and Component Registry\n *\n * Central registry for all UI elements and components registered with UI Bridge Native.\n * Adapted from ui-bridge for React Native environments.\n */\n\nimport type {\n RegisteredNativeElement,\n RegisteredNativeComponent,\n NativeElementState,\n NativeElementType,\n NativeStandardAction,\n NativeCustomAction,\n NativeAppInfo,\n NativeBridgeSnapshot,\n NativeElementBbox,\n NativeElementIdentifier,\n NativeElementRef,\n NativeRegistrationCoverage,\n NativeRouteProviderLike,\n NativeSnapshotEnrichers,\n NativeSnapshotEnricher,\n RouteInfo,\n Workflow,\n BridgeEvent,\n BridgeEventType,\n BridgeEventListener,\n IREffect,\n} from './types';\nimport { projectVisionFields } from './vision-fields';\nimport { serializeElementCustomActions } from './element-actions';\n\n/**\n * Options for registering an element\n */\nexport interface RegisterElementOptions {\n type?: NativeElementType;\n label?: string;\n actions?: NativeStandardAction[];\n customActions?: Record<string, NativeCustomAction>;\n props?: Record<string, unknown>;\n treePath?: string;\n testId?: string;\n accessibilityLabel?: string;\n /** Route path where the element was registered (for page-scoped filtering) */\n registrationRoute?: string | null;\n /**\n * Id of the registered scrollable container this element lives inside.\n *\n * When set, {@link computeVisibility} clips the element against that\n * container's measured frame as well as the window, so a row scrolled out of\n * its `ScrollView` / `FlatList` stops reporting `visibility: 'visible'`.\n *\n * DECLARED, not inferred, and that is deliberate. React Native gives the\n * registry no parent chain — `useUIElement` is a hook, so it never wraps its\n * element's children and cannot publish itself as their ancestor. Guessing\n * the ancestor geometrically is worse than not clipping: a full-width scroll\n * container horizontally contains the tab bar and the header too, and\n * clipping those against it would report on-screen chrome as off-screen.\n * A wrong `hidden` is a worse answer than a coarse `visible`.\n *\n * Unset (the default) leaves the window as the only clip region.\n */\n scrollAncestorId?: string;\n /** Flattened RN style (from StyleSheet.flatten) for design review */\n flatStyle?: Record<string, unknown>;\n /** State-specific style overrides for design review */\n stateStyles?: {\n pressed?: Record<string, unknown>;\n focused?: Record<string, unknown>;\n disabled?: Record<string, unknown>;\n };\n}\n\n/**\n * Options for registering a component\n */\nexport interface RegisterComponentOptions {\n name: string;\n description?: string;\n actions?: Array<{\n id: string;\n label?: string;\n description?: string;\n /**\n * Phase 2 (same plan): the action's published parameter schema. It was\n * ABSENT here and dropped by the object literal in `registerComponent`\n * below, so the field `ComponentAction` declares could never reach a\n * registered native component and the native invocation seam had nothing\n * to validate against. That is the silent-drop trap the plan warns about,\n * already realised.\n */\n paramSchema?: Record<string, unknown>;\n /**\n * Phase 4 (same plan): the safety annotation. Absent here, an author's\n * `effect: 'destructive'` would die at this hop exactly the way\n * `paramSchema` did before Phase 2 — the literal in `registerComponent`\n * has a closed field list and nothing type-checks the omission.\n */\n effect?: IREffect;\n /**\n * Phase 3 (plan 2026-08-20-ui-bridge-action-declaration-shape): the second\n * argument is the `ActionHandlerOptions` bag carrying the cancellation\n * signal. A 1-arity handler stays assignable.\n */\n handler: (params?: unknown, options?: { signal?: AbortSignal }) => unknown | Promise<unknown>;\n }>;\n elementIds?: string[];\n}\n\n/**\n * Options for {@link NativeUIBridgeRegistry.updateComponentMeta}.\n *\n * Every field is optional and `undefined` means \"leave the registered value\n * alone\" — the same partial-update semantics {@link RegisterComponentOptions}'s\n * sister {@link NativeUIBridgeRegistry.updateElementMeta} uses. A consequence\n * worth stating: a `description` cannot be CLEARED through this door, only\n * replaced. Pass `actions: []` to publish an empty action list; that is\n * distinguishable from omitting the field.\n */\nexport interface UpdateComponentMetaOptions {\n name?: string;\n description?: string;\n actions?: RegisterComponentOptions['actions'];\n elementIds?: string[];\n}\n\n/**\n * Map declared component actions into the registry's stored shape.\n *\n * ⚠ CLOSED FIELD LIST. Any `ComponentAction` field not named here is dropped at\n * registration time, silently: the literal stays assignable, the serializer\n * still runs, the field is simply never there. `paramSchema` was exactly that\n * until Phase 2, and `effect` is the Phase 4 addition that would have gone the\n * same way.\n *\n * It lives in ONE place on purpose. `registerComponent` and\n * `updateComponentMeta` both publish actions, and a second hand-rolled copy of\n * this list is precisely how the two Phase-2/Phase-4 drops happened — a field\n * added to the declaration type stays assignable at every re-wrap site and\n * simply never arrives.\n */\nfunction toRegisteredComponentActions(\n actions: NonNullable<RegisterComponentOptions['actions']>\n): RegisteredNativeComponent['actions'] {\n return actions.map((a) => ({\n id: a.id,\n label: a.label,\n description: a.description,\n paramSchema: a.paramSchema,\n effect: a.effect,\n handler: a.handler,\n }));\n}\n\n/**\n * Value signature of everything about an action list that reaches a bridge\n * consumer.\n *\n * `handler` is excluded deliberately: it is a function, so it is not\n * serialisable, it is never published, and call sites pass inline closures that\n * are freshly allocated on every render — including it would make\n * `updateComponentMeta` report a change every single time and defeat its\n * idempotence. Order is significant, because published order is the order an\n * agent reads the actions in.\n */\nfunction publishedActionSignature(actions: RegisteredNativeComponent['actions']): string {\n return JSON.stringify(\n actions.map((a) => [a.id, a.label, a.description, a.paramSchema, a.effect])\n );\n}\n\n/** Positional equality for the optional `elementIds` list. */\nfunction sameElementIds(a: string[] | undefined, b: string[] | undefined): boolean {\n if (a === b) return true;\n if (a === undefined || b === undefined) return false;\n return a.length === b.length && a.every((id, i) => id === b[i]);\n}\n\n/**\n * Registry configuration\n */\nexport interface NativeRegistryConfig {\n verbose?: boolean;\n onEvent?: BridgeEventListener;\n}\n\n/**\n * Extract handler function names from an element's props.\n * Returns names of props whose values are functions (e.g. ['onPress', 'onChangeText']).\n */\nexport function extractHandlerNames(props?: Record<string, unknown>): string[] {\n if (!props) return [];\n return Object.keys(props).filter((k) => typeof props[k] === 'function');\n}\n\n/**\n * `currentRouteOnly` filter predicate for `registrationRoute`.\n *\n * Returns `true` when an element should be INCLUDED in a current-route-only\n * view. Two classes of elements pass the filter:\n *\n * 1. Elements registered on the current route\n * (`registrationRoute === currentRoute`).\n * 2. Route-agnostic elements (`registrationRoute == null` or `''`) — these\n * are typically registered at the app root (tab bars, persistent\n * headers, modal hosts) and apply to every route. Excluding them by\n * default makes `currentRouteOnly=true` return empty on mobile where\n * most apps register chrome at the root and screens via routed stacks.\n *\n * The filter only DROPS elements whose `registrationRoute` is set to a\n * non-matching route — i.e. they belong to a different screen. This matches\n * the runner-side semantics: route-tagged DOM nodes that don't match the\n * active URL are scoped out; untagged nodes are always in scope.\n *\n * Surfaced 2026-05-23 (item 4 of the 0.6.6 robustness pass) — the prior\n * strict equality returned 0 elements on mobile screens that register tabs\n * at the root, because tab elements had `registrationRoute: null`.\n */\nexport function matchesCurrentRoute(\n registrationRoute: string | null | undefined,\n currentRoute: string\n): boolean {\n if (registrationRoute == null || registrationRoute === '') return true;\n return registrationRoute === currentRoute;\n}\n\n/**\n * Derive the snapshot's `activeTab` from an Expo Router segment list.\n *\n * Expo Router spells a layout group as a parenthesised segment and a dynamic\n * route as a bracketed one, so `usePathname()`/`useSegments()` on the \"runs\"\n * tab of a `(tabs)` layout yield `\"/(tabs)/runs\"` and `[\"(tabs)\", \"runs\"]`\n * (the shape the `RouteProvider` doc comment already documents). The tab a user\n * is looking at is therefore the segment immediately following the INNERMOST\n * group.\n *\n * Rules, all deliberately conservative — an unknown tab is reported as absent\n * rather than guessed:\n *\n * - `[\"(tabs)\", \"runs\"]` → `\"runs\"`\n * - `[\"(tabs)\", \"runs\", \"[id]\"]`→ `\"runs\"` (a detail screen is still *on* a tab)\n * - `[\"(tabs)\"]` → `\"index\"` (the group's own index route)\n * - `[\"(tabs)\", \"[id]\"]` → `undefined` (dynamic route, not a tab)\n * - `[\"settings\"]` → `undefined` (no group layout ⇒ no tabs)\n * - `[]` / `undefined` → `undefined`\n *\n * Exported so the rules are unit-testable without standing up a registry.\n */\nexport function deriveActiveTabFromSegments(segments?: string[]): string | undefined {\n if (!segments || segments.length === 0) return undefined;\n\n let innermostGroup = -1;\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n if (typeof segment === 'string' && segment.startsWith('(') && segment.endsWith(')')) {\n innermostGroup = i;\n }\n }\n // No layout group at all — the app has no tab shell, so claiming an active\n // tab would be an invention.\n if (innermostGroup === -1) return undefined;\n\n const next = segments[innermostGroup + 1];\n // The group IS the leaf: the router is showing that group's index route.\n if (next === undefined) return 'index';\n if (typeof next !== 'string' || next.length === 0) return undefined;\n // A dynamic segment is a detail screen reached from a tab, not a tab itself.\n if (next.startsWith('[') && next.endsWith(']')) return undefined;\n return next;\n}\n\n/**\n * Project an element's runtime state into a runner-native pixel-space `bbox`.\n *\n * Returns `{x, y, w, h}` in PHYSICAL pixels (RN `state.layout` is logical dp;\n * we multiply by `pixelRatio` — `PixelRatio.get()`, injected via\n * `registry.setPixelRatio` — so the box aligns with the runner's adb screencap\n * frame). See {@link NativeElementBbox} for why the shape is `{x,y,w,h}` and\n * not the web SDK's `{x,y,width,height}`.\n *\n * `pixelRatio` is INJECTED rather than read from `react-native` here: this is a\n * non-React core module, and a static `import { PixelRatio } from 'react-native'`\n * breaks both the vitest suite (\"Flow is not supported\") and risks the\n * Metro/Hermes `unknownModuleError` that bit `design-handlers`/`page-health`.\n * The provider wires the real ratio via `setPixelRatio`; callers/tests that\n * never wire it get the `1` default (dp ≈ px, still a usable box).\n *\n * Gating (avoid poisoning the snapshot — the flat registry mixes routes and\n * some elements carry stale off-screen coords):\n * - returns `undefined` unless `visibility === 'visible'` (visible + measured)\n * - returns `undefined` when `state.layout` is null/absent\n * Clamps every component to a non-negative integer.\n *\n * Uses `measureInWindow`'s absolute `pageX`/`pageY` for the origin. Falls back\n * to the layout-relative `x`/`y` only when `pageX`/`pageY` weren't measured\n * (rare; keeps a best-effort box rather than dropping the element).\n */\nexport function projectBbox(\n state: NativeElementState,\n visibility: 'visible' | 'likely-visible' | 'hidden',\n pixelRatio = 1\n): NativeElementBbox | undefined {\n if (visibility !== 'visible') return undefined;\n const layout = state.layout;\n if (!layout) return undefined;\n\n const ratio = pixelRatio > 0 && Number.isFinite(pixelRatio) ? pixelRatio : 1;\n const toPx = (v: number): number => Math.max(0, Math.round(v * ratio));\n\n const hasPage =\n typeof layout.pageX === 'number' &&\n typeof layout.pageY === 'number' &&\n Number.isFinite(layout.pageX) &&\n Number.isFinite(layout.pageY);\n const originX = hasPage ? layout.pageX : layout.x;\n const originY = hasPage ? layout.pageY : layout.y;\n\n if (\n typeof originX !== 'number' ||\n typeof originY !== 'number' ||\n typeof layout.width !== 'number' ||\n typeof layout.height !== 'number' ||\n !Number.isFinite(originX) ||\n !Number.isFinite(originY) ||\n !Number.isFinite(layout.width) ||\n !Number.isFinite(layout.height)\n ) {\n return undefined;\n }\n\n return {\n x: toPx(originX),\n y: toPx(originY),\n w: toPx(layout.width),\n h: toPx(layout.height),\n };\n}\n\n// ── Visibility ──────────────────────────────────────────────────────────────\n\n/**\n * A rectangle in window (page) coordinates, logical dp — the same space\n * `measureInWindow` reports `pageX`/`pageY` in.\n */\nexport interface NativePageRect {\n left: number;\n top: number;\n right: number;\n bottom: number;\n}\n\n/**\n * Why an element is not plainly visible.\n *\n * DUPLICATE of `@qontinui/ui-bridge` `VisibilityReason`\n * (`src/core/types.ts` — `'hidden' | 'off-screen' | 'occluded' | 'no-layout'`),\n * for the same reason the action types above are duplicated: `@qontinui/ui-bridge`\n * is an OPTIONAL peer of this package, so this module must not import from it.\n * KEEP IN SYNC — one vocabulary across both SDKs is the point; do not coin\n * native-only spellings.\n *\n * `'occluded'` is declared but never produced here: React Native gives the\n * registry no hit-test equivalent of `document.elementFromPoint`, so this SDK\n * cannot honestly claim occlusion. It stays in the union so a native reason\n * string is always a valid web reason string.\n */\nexport type NativeVisibilityReason = 'hidden' | 'off-screen' | 'occluded' | 'no-layout';\n\n/** The three-value verdict carried on every snapshot element. */\nexport type NativeVisibility = 'visible' | 'likely-visible' | 'hidden';\n\n/**\n * Convert an element's measured layout into a page-space rect, or `null` when\n * it has no TRUSTWORTHY page-space origin.\n *\n * STRICTER THAN {@link projectBbox} ON PURPOSE. `projectBbox` falls back to the\n * parent-relative `x`/`y` when `pageX`/`pageY` weren't measured, because a\n * slightly-wrong box is still a usable box. Clipping cannot take that trade: a\n * parent-relative origin compared against window coordinates silently mixes two\n * coordinate spaces and yields a wrong `hidden` verdict — the outcome\n * `RegisterElementOptions.scrollAncestorId` calls worse than a coarse\n * `visible`. So a layout flagged `pageOriginUnmeasured` returns `null` here,\n * and {@link computeVisibility} declines to clip rather than guess.\n */\nexport function pageRectOf(state: NativeElementState): NativePageRect | null {\n const layout = state.layout;\n if (!layout) return null;\n // The writer told us `pageX`/`pageY` are a parent-relative stand-in, not a\n // `measureInWindow` result. Not a page-space rect, and not guessable into one.\n if (layout.pageOriginUnmeasured === true) return null;\n\n const hasPage =\n typeof layout.pageX === 'number' &&\n typeof layout.pageY === 'number' &&\n Number.isFinite(layout.pageX) &&\n Number.isFinite(layout.pageY);\n if (!hasPage) return null;\n\n const left = layout.pageX;\n const top = layout.pageY;\n\n if (\n typeof layout.width !== 'number' ||\n typeof layout.height !== 'number' ||\n !Number.isFinite(layout.width) ||\n !Number.isFinite(layout.height)\n ) {\n return null;\n }\n\n return { left, top, right: left + layout.width, bottom: top + layout.height };\n}\n\n/** Do two 1-D spans share any extent? A zero-length span counts as touching. */\nfunction spansOverlap(aMin: number, aMax: number, bMin: number, bMax: number): boolean {\n if (aMin === aMax) return aMin >= bMin && aMin <= bMax;\n return aMin < bMax && aMax > bMin;\n}\n\n/**\n * Is this rect empty — nothing can overlap it?\n *\n * {@link intersectRects} returns an INVERTED rect (`right < left`) when the two\n * inputs are disjoint, which is the honest answer for \"the reachable region\",\n * but `spansOverlap`'s `aMin < bMax && aMax > bMin` reads an inverted span as\n * overlapping. Every consumer of an intersection must ask this first.\n */\nexport function isEmptyRect(rect: NativePageRect): boolean {\n return rect.right <= rect.left || rect.bottom <= rect.top;\n}\n\n/**\n * Intersect two page rects, or `null` when either is absent (`null` means\n * UNKNOWN, so it never narrows the other side).\n *\n * The result may be EMPTY — check {@link isEmptyRect}. That happens whenever\n * the two rects are disjoint, e.g. a scroll container that has itself been\n * scrolled out of the window: nothing inside it can be on screen, and an empty\n * clip is exactly how that is expressed.\n */\nexport function intersectRects(\n a: NativePageRect | null,\n b: NativePageRect | null\n): NativePageRect | null {\n if (!a) return b;\n if (!b) return a;\n return {\n left: Math.max(a.left, b.left),\n top: Math.max(a.top, b.top),\n right: Math.min(a.right, b.right),\n bottom: Math.min(a.bottom, b.bottom),\n };\n}\n\n/**\n * Decide what a snapshot should say about an element being on screen.\n *\n * THE DEFECT THIS CLOSES: visibility used to be derived from mount + `measure()`\n * alone — `state.visible ? (state.layout ? 'visible' : 'likely-visible') : 'hidden'`\n * — with no comparison against any bounds at all. Every mounted, measured\n * element reported `visible`, including rows scrolled far past the fold, so a\n * snapshot claimed things were on screen that were not.\n *\n * SCOPE — read this before citing it. This is a PURE PROJECTION: it reads\n * `state.visible` and never writes it. An off-screen element still carries\n * `state.visible === true`; only the snapshot's `visibility` /\n * `visibilityReason` change. That is a deliberate divergence from the web SDK,\n * which folds off-screen INTO the boolean (`@qontinui/ui-bridge`\n * `src/core/registry.ts` — `if (!inViewport) return { visible: false, reason:\n * 'off-screen' }`), and it means any consumer asserting on `state.visible`\n * alone — `/manual-test`'s \"PRESENT IS NOT VISIBLE\" gate among them — sees no\n * change on mobile and stays as vacuous as it was. Closing that needs either a\n * matching demotion of `state.visible` here or a consumer that reads\n * `visibility`; both are contract decisions beyond this projection, and\n * neither is done. The strings are shared with the web SDK; the SHAPE is not.\n *\n * `clip` is the region the element must reach to count as on screen — the\n * window, intersected with the measured frame of a declared\n * `scrollAncestorId`. **A `null`/absent clip means the bounds are UNKNOWN, and\n * an unknown never demotes**: with no viewport injected (every non-RN caller,\n * including this package's own suite) the answer is exactly what it was before.\n * Absence of evidence is not evidence of off-screen.\n *\n * Any overlap at all counts as visible. A half-scrolled row IS on screen, and\n * a ratio threshold would be a second, unshared policy knob for callers to\n * disagree about.\n */\nexport function computeVisibility(\n state: NativeElementState,\n clip?: NativePageRect | null\n): { visibility: NativeVisibility; visibilityReason?: NativeVisibilityReason } {\n if (!state.visible) {\n return { visibility: 'hidden', visibilityReason: 'hidden' };\n }\n if (state.layout === null) {\n // Registered and mounted but not yet measured. `'likely-visible'` already\n // is this SDK's honest-UNKNOWN value; `'no-layout'` names why.\n return { visibility: 'likely-visible', visibilityReason: 'no-layout' };\n }\n if (!clip) {\n return { visibility: 'visible' };\n }\n const rect = pageRectOf(state);\n if (!rect) {\n // Measured, but with no trustworthy page-space origin (see `pageRectOf`).\n // Report it exactly as before — an unknown must not manufacture a verdict.\n return { visibility: 'visible' };\n }\n return rectReachesClip(rect, clip)\n ? { visibility: 'visible' }\n : { visibility: 'hidden', visibilityReason: 'off-screen' };\n}\n\n/** Does any of `rect` lie inside `clip`? Any overlap counts (see {@link computeVisibility}). */\nfunction rectReachesClip(rect: NativePageRect, clip: NativePageRect): boolean {\n // A disjoint intersection (a scroll container itself scrolled off the window)\n // inverts rather than zeroing, and `spansOverlap` would read an inverted span\n // as overlapping. Nothing can be inside an empty clip.\n if (isEmptyRect(clip)) return false;\n return (\n spansOverlap(rect.left, rect.right, clip.left, clip.right) &&\n spansOverlap(rect.top, rect.bottom, clip.top, clip.bottom)\n );\n}\n\n/**\n * Whether a shown element's measured rect intersects its clip region — the\n * value vision-core's `ElementMedia.in_viewport` carries. `undefined` whenever\n * that cannot be MEASURED: the element is not shown (its rect may be a stale\n * one from another route), it has no page-space rect yet, or the clip bounds\n * are unknown.\n *\n * Deliberately stricter than {@link computeVisibility}, which answers\n * `'visible'` when the clip is unknown because an unknown must never DEMOTE an\n * element. The converse holds here: an unknown must never PROMOTE one to \"on\n * screen\", or an off-screen check would pass on an image nobody located.\n */\nexport function computeInViewport(\n state: NativeElementState,\n clip?: NativePageRect | null\n): boolean | undefined {\n if (!state.visible || !clip) return undefined;\n const rect = pageRectOf(state);\n if (!rect) return undefined;\n return rectReachesClip(rect, clip);\n}\n\n/**\n * Copy a {@link RouteInfo} one level deep (its `params` / `queryParams`\n * objects and `routeStack` array too), so neither the caller of\n * `setRouteInfo` nor a snapshot consumer can mutate the registry's slot or a\n * previously emitted snapshot through a shared reference. Absent fields stay\n * absent, so the emitted object carries no `undefined`-valued keys it was not\n * given.\n */\nfunction copyRouteInfo(info: RouteInfo): RouteInfo {\n const copy: RouteInfo = { ...info };\n if (info.params) copy.params = { ...info.params };\n if (info.queryParams) copy.queryParams = { ...info.queryParams };\n if (info.routeStack) copy.routeStack = [...info.routeStack];\n return copy;\n}\n\n/**\n * Infer available actions based on element type.\n *\n * `scrollIntoView` is advertised only when the element DECLARES a\n * `scrollAncestorId`. It is the one input the action cannot work without (the\n * registry keeps no parent chain, so there is nothing else to scroll), and the\n * runner refuses any action an element does not advertise — so advertising it\n * everywhere would put a verb on every element that answers `NOT_SUPPORTED`\n * for all but the declared few. An ancestor that turns out to be unregistered\n * or not a ScrollView is still answered honestly by the executor\n * (`NOT_SUPPORTED`).\n */\nfunction inferActions(type: NativeElementType, scrollAncestorId?: string): NativeStandardAction[] {\n const baseActions: NativeStandardAction[] = scrollAncestorId\n ? ['focus', 'blur', 'scrollIntoView']\n : ['focus', 'blur'];\n\n switch (type) {\n case 'button':\n case 'touchable':\n case 'pressable':\n return [...baseActions, 'click', 'press', 'longPress', 'doubleTap'];\n case 'input':\n return [...baseActions, 'click', 'press', 'type', 'setValue', 'clear'];\n case 'text':\n return [...baseActions, 'click', 'press', 'longPress'];\n case 'view':\n return [...baseActions, 'click', 'press'];\n case 'scroll':\n return [...baseActions, 'scroll', 'swipe'];\n case 'list':\n return [...baseActions, 'scroll', 'swipe'];\n case 'listItem':\n return [...baseActions, 'click', 'press', 'longPress', 'swipe'];\n case 'switch':\n case 'checkbox':\n return [...baseActions, 'click', 'press', 'toggle'];\n case 'radio':\n return [...baseActions, 'click', 'press'];\n case 'image':\n return [...baseActions, 'click', 'press', 'longPress'];\n case 'modal':\n return ['focus', 'blur'];\n case 'custom':\n default:\n return [...baseActions, 'click', 'press'];\n }\n}\n\n/**\n * Native UI Bridge Registry\n *\n * Manages registration and lookup of native UI elements and components.\n */\nexport class NativeUIBridgeRegistry {\n private elements = new Map<string, RegisteredNativeElement>();\n private components = new Map<string, RegisteredNativeComponent>();\n private workflows = new Map<string, Workflow>();\n private eventListeners = new Map<BridgeEventType, Set<BridgeEventListener>>();\n private config: NativeRegistryConfig;\n private enrichers: NativeSnapshotEnrichers = {};\n private snapshotExtras = new Map<string, NativeSnapshotEnricher>();\n /**\n * App identification metadata (appId/appName/appType/framework). Set via\n * `setAppInfo` — typically wired in the server constructor from the\n * `NativeUIBridgeConfig` the host passes to `UIBridgeNativeProvider`.\n *\n * When set, `createSnapshot` includes it on the resulting snapshot so\n * agents can identify the app from snapshot bytes alone (parity with the\n * `uiBridge` block on the `health` response). Snapshots created before\n * this is wired simply omit the field.\n */\n private appInfo: NativeAppInfo | null = null;\n /**\n * Optional route provider. When set, `createSnapshot` reads\n * `currentRoute`/`segments` from it whenever the caller does not pass an\n * explicit `routeInfo` argument. This lets the default `getSnapshot` HTTP\n * handler — which has no direct reference to the `NativeUIBridgeServer`\n * instance — still emit a populated `currentRoute` field.\n *\n * Wired by `NativeUIBridgeServer.setRouteProvider`, which forwards the\n * provider into the registry alongside the per-handler overrides it\n * already installs.\n */\n private routeProvider: NativeRouteProviderLike | null = null;\n /**\n * The route TEMPLATE the app last reported through `useRouteAwareness`\n * (via {@link setRouteInfo}), emitted on every snapshot as `page.route`.\n *\n * Deliberately NOT named `routeInfo`: `createSnapshot`'s parameter of that\n * name is the CONCRETE `{currentRoute, segments, activeTab}` read from the\n * route provider. The two must never be confused — the concrete path is\n * user input, and this slot is the router-asserted template.\n */\n private reportedRoute: RouteInfo | undefined = undefined;\n /**\n * Device pixel ratio used to project `state.layout` (logical dp) into the\n * physical-pixel `bbox` emitted on each snapshot element. Defaults to `1`\n * (dp ≈ px) so callers/tests that never inject a ratio still get a usable\n * box; the React provider wires the real `PixelRatio.get()` via\n * {@link setPixelRatio}. Read from a non-React core module on purpose — see\n * the `projectBbox` doc comment for why we don't import `react-native` here.\n */\n private pixelRatio = 1;\n /**\n * Injected getter for the device window size in logical dp, used to clip\n * reported visibility. Injected rather than read here for exactly the reason\n * `pixelRatio` is: a static `import { Dimensions } from 'react-native'` in\n * this non-React core module breaks the vitest suite and risks the\n * Metro/Hermes `unknownModuleError`. It is a PROVIDER, not a value, so a\n * rotation is picked up on the next snapshot with nothing to keep in sync.\n *\n * `null` means the window bounds are UNKNOWN, and {@link computeVisibility}\n * treats unknown bounds as \"cannot demote\" — never as an empty viewport.\n */\n private viewportProvider: (() => { width: number; height: number }) | null = null;\n /**\n * Sticky flag: flips `true` the first time any element is registered and\n * stays `true` even after elements are unregistered. Lets agents distinguish\n * \"this route never wired any elements\" from \"this route registered then\n * unmounted\".\n */\n private everHadRegistrations = false;\n\n constructor(config: NativeRegistryConfig = {}) {\n this.config = config;\n }\n\n // ============================================================================\n // App Info & Route Provider\n // ============================================================================\n\n /**\n * Set the app identification metadata returned alongside snapshots.\n * Idempotent — re-calling with the same shape is a no-op.\n * Pass `null` (or omit a previous call) to clear.\n */\n setAppInfo(info: NativeAppInfo | null | undefined): void {\n this.appInfo = info ?? null;\n }\n\n /** Read-only accessor for the registered app info, if any. */\n getAppInfo(): NativeAppInfo | null {\n return this.appInfo;\n }\n\n /**\n * Set a route provider whose `getCurrentRoute`/`getSegments` are read by\n * `createSnapshot` when no explicit `routeInfo` argument is supplied.\n *\n * Call with `null` to detach (e.g. when the host swaps providers on HMR).\n */\n setRouteProvider(provider: NativeRouteProviderLike | null | undefined): void {\n this.routeProvider = provider ?? null;\n }\n\n /** Read-only accessor for the registered route provider, if any. */\n getRouteProvider(): NativeRouteProviderLike | null {\n return this.routeProvider;\n }\n\n /**\n * Set the route info emitted as `page.route` on every snapshot — the\n * router-asserted TEMPLATE, normally reported by `useRouteAwareness`.\n * Pass `undefined` to clear it (the hook does so on unmount), after which\n * snapshots omit `page` again.\n *\n * The value is copied, so a later mutation by the caller cannot alter what\n * is stored or what an already-emitted snapshot holds.\n */\n setRouteInfo(info: RouteInfo | undefined): void {\n this.reportedRoute = info ? copyRouteInfo(info) : undefined;\n }\n\n /** Read-only accessor for the reported route info, if any (a copy). */\n getRouteInfo(): RouteInfo | undefined {\n return this.reportedRoute ? copyRouteInfo(this.reportedRoute) : undefined;\n }\n\n /**\n * Resolve the snapshot's `activeTab`: an explicit `getActiveTab()` on the\n * route provider wins, otherwise derive it from the Expo Router segments the\n * provider already exposes.\n *\n * A throwing or empty-string provider degrades to the derivation rather than\n * to `undefined` — the host's opt-in extra should never be able to make the\n * snapshot WORSE than it was without it. A blank derivation stays blank.\n */\n private resolveActiveTab(segments: string[] | undefined): string | undefined {\n const explicit = (() => {\n if (!this.routeProvider?.getActiveTab) return undefined;\n try {\n const value = this.routeProvider.getActiveTab();\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n } catch (error) {\n if (this.config.verbose) {\n console.warn(`[ui-bridge-native] routeProvider getActiveTab threw:`, error);\n }\n return undefined;\n }\n })();\n return explicit ?? deriveActiveTabFromSegments(segments);\n }\n\n /**\n * Set the device pixel ratio used to project per-element `bbox` geometry\n * into physical pixels. The React provider passes `PixelRatio.get()` here\n * once at startup. Ignores non-finite / non-positive values (keeps the\n * previous ratio) so a bad call can't zero out every bbox.\n */\n setPixelRatio(ratio: number): void {\n if (typeof ratio === 'number' && Number.isFinite(ratio) && ratio > 0) {\n this.pixelRatio = ratio;\n }\n }\n\n /** Read-only accessor for the device pixel ratio used for bbox projection. */\n getPixelRatio(): number {\n return this.pixelRatio;\n }\n\n /**\n * Set the getter for the device window size (logical dp) that reported\n * visibility is clipped against. The React provider passes\n * `() => Dimensions.get('window')` here at construction.\n *\n * Pass `null` to detach. While detached the window bounds are UNKNOWN and no\n * element is ever demoted for being outside them.\n */\n setViewportProvider(\n provider: (() => { width: number; height: number }) | null | undefined\n ): void {\n this.viewportProvider = provider ?? null;\n }\n\n /**\n * The window as a page-space rect, or `null` when it is unknown or the\n * provider returned something unusable (it throws in some RN teardown\n * paths, and a zero-size window would silently hide the entire screen).\n */\n getViewportRect(): NativePageRect | null {\n if (!this.viewportProvider) return null;\n let size: { width: number; height: number };\n try {\n size = this.viewportProvider();\n } catch (error) {\n if (this.config.verbose) {\n console.warn(`[ui-bridge-native] viewportProvider threw:`, error);\n }\n return null;\n }\n const { width, height } = size ?? {};\n if (\n typeof width !== 'number' ||\n typeof height !== 'number' ||\n !Number.isFinite(width) ||\n !Number.isFinite(height) ||\n width <= 0 ||\n height <= 0\n ) {\n return null;\n }\n return { left: 0, top: 0, right: width, bottom: height };\n }\n\n /**\n * The region `element` must reach to count as on screen: the window,\n * intersected with the measured frame of its declared `scrollAncestorId`.\n *\n * Returns `null` when neither bound is known — see {@link computeVisibility}\n * for why that means \"do not demote\" rather than \"nothing is visible\".\n * An ancestor that is not registered, or registered but not yet measured,\n * contributes nothing rather than hiding its subtree.\n */\n getClipRectFor(\n element: RegisteredNativeElement,\n viewport?: NativePageRect | null\n ): NativePageRect | null {\n // `undefined` = not supplied, read it now. `null` = the caller already\n // read it and it was UNKNOWN — don't re-read and disagree with them.\n const bounds = viewport === undefined ? this.getViewportRect() : viewport;\n const ancestorId = element.scrollAncestorId;\n if (!ancestorId) return bounds;\n\n // Single level by design: only the DECLARED ancestor is applied, and its\n // own `scrollAncestorId` is not followed. The window clip already covers\n // most of what a second level would add, and a recursive walk needs a\n // visited set to survive a declaration cycle. Revisit together.\n const ancestor = this.elements.get(ancestorId);\n if (!ancestor || ancestorId === element.id) return bounds;\n\n return intersectRects(bounds, pageRectOf(ancestor.getState()));\n }\n\n // ============================================================================\n // Snapshot Enricher Slots\n // ============================================================================\n\n /**\n * Register/replace canonical enrichers (modal/toast/undo). HMR-safe — calling\n * with a partial set merges into existing slots instead of clobbering them.\n */\n setEnrichers(e: Partial<NativeSnapshotEnrichers>): void {\n this.enrichers = { ...this.enrichers, ...e };\n }\n\n /**\n * Read-only accessor for the canonical enrichers slot. Returned object is\n * the live reference, so callers can detect \"no detector wired\" via\n * `getEnrichers().modalDetector === undefined`. Used by the test-hook HTTP\n * handlers (`control/modal/push`, `control/modal/dismiss/:id`) which need\n * to drive the ModalDetector directly without re-instantiating it.\n */\n getEnrichers(): Readonly<NativeSnapshotEnrichers> {\n return this.enrichers;\n }\n\n /**\n * Register a custom snapshot enricher. The returned object will be\n * `Object.assign`ed onto the snapshot, so use unique top-level keys to avoid\n * clobbering canonical fields. Returns a disposer.\n */\n registerSnapshotEnricher(name: string, fn: NativeSnapshotEnricher): () => void {\n this.snapshotExtras.set(name, fn);\n return () => this.unregisterSnapshotEnricher(name);\n }\n\n /** Remove a custom snapshot enricher by name */\n unregisterSnapshotEnricher(name: string): void {\n this.snapshotExtras.delete(name);\n }\n\n // ============================================================================\n // Element Management\n // ============================================================================\n\n /**\n * Register a native element\n */\n registerElement(\n id: string,\n ref: React.RefObject<NativeElementRef>,\n options: RegisterElementOptions = {}\n ): RegisteredNativeElement {\n const {\n type = 'custom',\n label,\n customActions,\n props,\n treePath = id,\n testId,\n accessibilityLabel,\n registrationRoute,\n scrollAncestorId,\n flatStyle,\n stateStyles,\n } = options;\n const actions = options.actions ?? inferActions(type, scrollAncestorId);\n\n // Create state getter\n const getState = (): NativeElementState => {\n const element = ref.current;\n if (!element) {\n return {\n mounted: false,\n visible: false,\n enabled: false,\n focused: false,\n layout: null,\n };\n }\n\n // State is populated by the element during onLayout\n // Here we return the stored state from the element's metadata\n const stored = this.elements.get(id);\n if (stored && stored.getState !== getState) {\n return stored.getState();\n }\n\n return {\n mounted: true,\n visible: true,\n enabled: true,\n focused: false,\n layout: null,\n };\n };\n\n // Create identifier getter\n const getIdentifier = (): NativeElementIdentifier => ({\n uiId: id,\n testId: testId || id,\n accessibilityLabel,\n treePath,\n });\n\n const registered: RegisteredNativeElement = {\n id,\n ref,\n type,\n label,\n actions,\n customActions,\n props,\n getState,\n getIdentifier,\n registeredAt: Date.now(),\n mounted: true,\n registrationRoute: registrationRoute ?? null,\n scrollAncestorId,\n flatStyle,\n stateStyles,\n };\n\n this.elements.set(id, registered);\n this.everHadRegistrations = true;\n\n this.emit('element:registered', { id, type, label });\n\n if (this.config.verbose) {\n console.log(`[ui-bridge-native] Registered element: ${id} (${type})`);\n }\n\n // Seed a sensible initial state so elements are immediately visible in snapshots\n // even before onLayout fires. Layout-measuring code can overwrite this later.\n //\n // For controlled-input elements (`type:'input'` with a string `props.value`),\n // also seed `state.value` from the prop so bridge consumers can read the\n // default value before the user has typed. See `updateElementProps` for\n // the corresponding re-sync path on prop changes.\n const seededState: Partial<NativeElementState> = {\n mounted: true,\n visible: true,\n enabled: true,\n focused: false,\n layout: null,\n };\n if (type === 'input' && typeof props?.value === 'string') {\n seededState.value = props.value;\n }\n // For `type:'text'` elements, surface the (dynamic) label as `state.value`\n // so bridge consumers can read text content with a uniform `state.value`\n // accessor instead of parsing the `label` field (\"Status: ...\"). The hook\n // already re-publishes the label via `updateElementMeta` when consumers\n // call `updateLabel(newLabel)` — that path also re-syncs `state.value`\n // (see `updateElementMeta`).\n if (type === 'text' && typeof label === 'string') {\n seededState.value = label;\n }\n this.updateElementState(id, seededState);\n\n return registered;\n }\n\n /**\n * Unregister an element\n */\n unregisterElement(id: string): void {\n const element = this.elements.get(id);\n if (element) {\n this.elements.delete(id);\n this.emit('element:unregistered', { id });\n\n if (this.config.verbose) {\n console.log(`[ui-bridge-native] Unregistered element: ${id}`);\n }\n }\n }\n\n /**\n * Get a registered element\n */\n getElement(id: string): RegisteredNativeElement | undefined {\n return this.elements.get(id);\n }\n\n /**\n * Get all registered elements\n */\n getAllElements(): RegisteredNativeElement[] {\n return Array.from(this.elements.values());\n }\n\n /**\n * Get all registered elements that are visible AND have a measured layout.\n *\n * This is the *strict* visibility filter: requires `state.visible === true`\n * AND `state.layout !== null`. Use this when you need both signals\n * (e.g. coord-based tap, where you need an actual rect to hit-test against).\n *\n * Callers driving an LLM agent typically want\n * {@link getMountedVisibleElements} instead — that variant accepts\n * elements that *should* be on screen (visible:true) but whose first\n * `onLayout` has not yet fired. Without it, a snapshot taken in the gap\n * between mount and the first layout pass returns 0 elements on a fully\n * populated screen.\n */\n getVisibleElements(): RegisteredNativeElement[] {\n return this.getAllElements().filter((e) => {\n const state = e.getState();\n return state.visible && state.layout !== null;\n });\n }\n\n /**\n * Get all registered elements marked as visible (regardless of layout).\n *\n * Looser than {@link getVisibleElements}: includes elements whose\n * `onLayout` callback has not yet fired (so `layout === null`). React\n * Native's `onLayout` is async — there's a one-tick window after mount\n * where elements are in the tree, the user *sees* them, but the\n * registry hasn't received their measurements yet. Excluding those\n * elements makes `getSnapshot?visibleOnly=true` return 0 on a populated\n * screen, which is a worse failure mode than including a few elements\n * we can't yet locate spatially.\n *\n * Used by `createSnapshot`'s `visibleOnly` filter and by the\n * `getSnapshot` / `getElements` handlers. Each emitted element carries a\n * `visibility` discriminator (`\"visible\"` / `\"likely-visible\"`) so\n * agents that *do* require a known rect can branch downstream.\n */\n getMountedVisibleElements(): RegisteredNativeElement[] {\n return this.getAllElements().filter((e) => e.getState().visible);\n }\n\n /**\n * Get elements registered on a specific route (for page-scoped filtering)\n */\n getElementsForRoute(route: string): RegisteredNativeElement[] {\n return this.getAllElements().filter((e) => e.registrationRoute === route);\n }\n\n /**\n * Mark elements registered on a route as off-screen (visible: false, layout: null).\n *\n * Use this when a screen loses focus but stays mounted — common in React Navigation\n * tab navigators where inactive tabs remain in the tree. Without this call, stale\n * `layout` data lingers in snapshots and makes off-screen elements look rendered.\n *\n * Does NOT unregister the elements — they stay registered so the user's next visit\n * re-measures them via `onLayout` without re-mount cost. Elements without a\n * `registrationRoute` (app-wide registrations) are untouched.\n */\n markRouteOffscreen(route: string): void {\n // Guard against accidental global wipes. Elements registered without a\n // route have `registrationRoute: null` — passing null/empty here would\n // match every globally-registered element and erase their layouts.\n if (route == null || route === '') {\n if (this.config.verbose) {\n console.warn(\n `[ui-bridge-native] markRouteOffscreen called with null/empty route — ignoring`\n );\n }\n return;\n }\n let cleared = 0;\n for (const element of this.elements.values()) {\n if (element.registrationRoute === route) {\n this.updateElementState(element.id, {\n visible: false,\n layout: null,\n });\n cleared++;\n }\n }\n if (this.config.verbose && cleared > 0) {\n console.log(`[ui-bridge-native] Marked ${cleared} elements offscreen for route: ${route}`);\n }\n }\n\n /**\n * Re-measure every mounted-visible element's geometry via its stored React\n * ref and write the fresh coordinates into the registry (\"measure-on-\n * snapshot\").\n *\n * Why: element geometry is otherwise captured ONCE at mount time\n * (`onLayout` + `measureInWindow` in `useUIElement`) and RN never re-fires\n * `onLayout` on pure translation — scrolling a virtualized list\n * (FlashList), recycling a cell, a keyboard shift or an orientation change\n * all move elements WITHOUT updating the stored `state.layout`. Snapshot\n * consumers (vision analyzers hit-testing `bbox`, coord-based tap) then\n * operate on stale positions. Re-measuring at READ time is correct\n * regardless of what moved the element and needs zero per-consumer wiring:\n * every registry entry already stores its ref (see `registerElement`).\n *\n * Per-element outcomes:\n * - non-zero dims → write a fresh full layout (same state shape as the\n * mount-time measure loop in `useUIElement`); counted as `measured`.\n * - zero dims AND the element previously had a non-null `state.layout` →\n * the view collapsed / left the screen; clear to\n * `{ visible: false, layout: null }` (same shape `markRouteOffscreen`\n * writes); counted as `cleared`.\n * - zero dims and it never had a layout → leave state untouched (element\n * in the mount→first-onLayout gap; don't demote `likely-visible`);\n * counted as `skipped`.\n * - no ref / no callable `measureInWindow` (test fixtures, web) → leave\n * untouched; counted as `skipped`.\n *\n * The whole sweep races an overall timeout (default 250 ms) so a dead ref\n * whose `measureInWindow` never calls back can never hang a snapshot: the\n * returned promise resolves at the deadline with whatever counts have\n * accumulated. Late callbacks may still write state afterwards — harmless,\n * freshest data wins. Never throws.\n *\n * Non-React core module on purpose — `measureInWindow` is duck-typed off\n * the stored ref rather than imported from `react-native` (see the\n * `projectBbox` doc comment for why this file must not import RN).\n */\n async refreshMeasurements(options?: {\n timeoutMs?: number;\n }): Promise<{ measured: number; cleared: number; skipped: number }> {\n const timeoutMs =\n typeof options?.timeoutMs === 'number' &&\n Number.isFinite(options.timeoutMs) &&\n options.timeoutMs > 0\n ? options.timeoutMs\n : 250;\n\n const counts = { measured: 0, cleared: 0, skipped: 0 };\n const pending: Array<Promise<void>> = [];\n\n for (const element of this.getMountedVisibleElements()) {\n const node = element.ref?.current as\n | (NativeElementRef & {\n measureInWindow?: (\n callback: (pageX: number, pageY: number, w: number, h: number) => void\n ) => void;\n })\n | null\n | undefined;\n\n if (!node || typeof node.measureInWindow !== 'function') {\n counts.skipped++;\n continue;\n }\n\n const id = element.id;\n pending.push(\n new Promise<void>((resolve) => {\n let settled = false;\n try {\n node.measureInWindow!((pageX: number, pageY: number, w: number, h: number) => {\n if (settled) return;\n settled = true;\n try {\n if (w > 0 && h > 0) {\n this.updateElementState(id, {\n mounted: true,\n visible: true,\n enabled: true,\n focused: false,\n layout: { x: pageX, y: pageY, width: w, height: h, pageX, pageY },\n });\n counts.measured++;\n } else if (this.elements.get(id)?.getState().layout != null) {\n // Collapsed / off-screen: it HAD a measured rect and now\n // reports zeros. Clear it so stale coords can't poison the\n // snapshot (same shape as markRouteOffscreen).\n this.updateElementState(id, { visible: false, layout: null });\n counts.cleared++;\n } else {\n counts.skipped++;\n }\n } finally {\n resolve();\n }\n });\n } catch {\n // A throwing measureInWindow must never break the sweep.\n if (!settled) {\n settled = true;\n counts.skipped++;\n resolve();\n }\n }\n })\n );\n }\n\n if (pending.length > 0) {\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, timeoutMs);\n void Promise.all(pending).then(() => {\n clearTimeout(timer);\n resolve();\n });\n });\n }\n\n return counts;\n }\n\n /**\n * Update element state\n */\n updateElementState(id: string, state: Partial<NativeElementState>): void {\n const element = this.elements.get(id);\n if (element) {\n // Create a new getState that includes the updated state\n const currentState = element.getState();\n const newState = { ...currentState, ...state };\n\n const updated: RegisteredNativeElement = {\n ...element,\n getState: () => newState,\n };\n\n this.elements.set(id, updated);\n this.emit('element:stateChanged', { id, state: newState });\n }\n }\n\n /**\n * Update an element's descriptive metadata (label / accessibilityLabel /\n * testId). Sister to {@link updateElementState} (which carries layout +\n * visibility) and {@link updateElementProps} (which carries event handlers).\n *\n * Idempotent — if every passed field equals the existing value, the call\n * returns without mutating the entry or emitting an event. This keeps\n * `useUIElement().updateLabel(...)` cheap when consumers re-publish from a\n * render path that produces the same string on every tick.\n *\n * Returns `true` when the entry was mutated, `false` otherwise. Callers\n * that want to gate a `console.warn` on no-op invocations can use the\n * return value instead of recomputing equality.\n */\n updateElementMeta(\n id: string,\n meta: { label?: string; accessibilityLabel?: string; testId?: string }\n ): boolean {\n const element = this.elements.get(id);\n if (!element) return false;\n\n // The `testId` on a registered element lives inside the identifier\n // closure (see registerElement's `getIdentifier`). To rebuild it we\n // need the current value — read it via the existing identifier and\n // overlay the new one if provided.\n const currentIdentifier = element.getIdentifier();\n const nextLabel = meta.label !== undefined ? meta.label : element.label;\n const nextAccessibilityLabel =\n meta.accessibilityLabel !== undefined\n ? meta.accessibilityLabel\n : currentIdentifier.accessibilityLabel;\n const nextTestId = meta.testId !== undefined ? meta.testId : currentIdentifier.testId;\n\n const labelChanged = nextLabel !== element.label;\n const accessibilityLabelChanged =\n nextAccessibilityLabel !== currentIdentifier.accessibilityLabel;\n const testIdChanged = nextTestId !== currentIdentifier.testId;\n\n if (!labelChanged && !accessibilityLabelChanged && !testIdChanged) {\n return false;\n }\n\n const updated: RegisteredNativeElement = {\n ...element,\n label: nextLabel,\n getIdentifier: (): NativeElementIdentifier => ({\n uiId: id,\n testId: nextTestId,\n accessibilityLabel: nextAccessibilityLabel,\n treePath: currentIdentifier.treePath,\n }),\n };\n this.elements.set(id, updated);\n\n // For text-type elements, mirror the new label into `state.value` so\n // bridge consumers can read text content with a uniform `state.value`\n // accessor. Initial seed lives in `registerElement`; this keeps the\n // mirror in sync when consumers call `updateLabel(newLabel)` from a\n // state-driven render path (e.g. `Status: ${connected ? 'online' : 'offline'}`).\n if (element.type === 'text' && labelChanged && typeof nextLabel === 'string') {\n this.updateElementState(id, { value: nextLabel });\n }\n\n // Reuse `element:registered` rather than minting a new event — downstream\n // consumers already subscribe to it for label/identifier diffs, and\n // adding a new BridgeEventType ('element:metaChanged') would force a\n // SemVer-major bump on every snapshot consumer.\n this.emit('element:registered', {\n id,\n type: element.type,\n label: nextLabel,\n });\n\n return true;\n }\n\n /**\n * Update element props (for action execution).\n *\n * Side effect for `type: 'input'` elements: when `props.value` is a string,\n * mirror it into `state.value` so AI drivers reading the registry don't see\n * `state.value === undefined` until the user types. Before this mirror,\n * controlled-input consumers (`<TextInput value={x} onChangeText={setX} />`\n * with `captureProps({ value: x, onChangeText: setX })`) registered an\n * input whose `state.value` only became defined after the FIRST\n * `onChangeText` call — bridge consumers couldn't read the default value\n * (e.g. a pre-populated API-URL input) until someone typed into it.\n *\n * Re-runs on every `updateElementProps` so a parent passing a fresh\n * `value` prop (controlled re-render) keeps the registry in sync. The\n * mirror is gated on `type === 'input'` to avoid touching non-input\n * elements that may carry an unrelated `value` prop (e.g. a Switch's\n * boolean value, which lives on `state.checked`).\n */\n updateElementProps(id: string, props: Record<string, unknown>): void {\n const element = this.elements.get(id);\n if (element) {\n const updated: RegisteredNativeElement = {\n ...element,\n props: { ...element.props, ...props },\n };\n this.elements.set(id, updated);\n\n // Controlled-input value mirror — see doc comment above.\n if (\n element.type === 'input' &&\n Object.prototype.hasOwnProperty.call(props, 'value') &&\n typeof props.value === 'string'\n ) {\n const currentState = updated.getState();\n if (currentState.value !== props.value) {\n this.updateElementState(id, { value: props.value });\n }\n }\n }\n }\n\n /**\n * Update element style for design review\n */\n updateElementStyle(\n id: string,\n flatStyle: Record<string, unknown>,\n stateStyles?: {\n pressed?: Record<string, unknown>;\n focused?: Record<string, unknown>;\n disabled?: Record<string, unknown>;\n }\n ): void {\n const element = this.elements.get(id);\n if (element) {\n const updated: RegisteredNativeElement = {\n ...element,\n flatStyle,\n ...(stateStyles !== undefined ? { stateStyles } : {}),\n };\n this.elements.set(id, updated);\n }\n }\n\n /**\n * Get element style for design review\n */\n getElementStyle(id: string): Record<string, unknown> | null {\n const element = this.elements.get(id);\n return element?.flatStyle ?? null;\n }\n\n /**\n * Find element by testID\n */\n findByTestId(testId: string): RegisteredNativeElement | undefined {\n for (const element of this.elements.values()) {\n const identifier = element.getIdentifier();\n if (identifier.testId === testId) {\n return element;\n }\n }\n return undefined;\n }\n\n /**\n * Find elements by type\n */\n findByType(type: NativeElementType): RegisteredNativeElement[] {\n return Array.from(this.elements.values()).filter((e) => e.type === type);\n }\n\n // ============================================================================\n // Component Management\n // ============================================================================\n\n /**\n * Register a component\n */\n registerComponent(id: string, options: RegisterComponentOptions): RegisteredNativeComponent {\n const { name, description, actions = [], elementIds } = options;\n\n const registered: RegisteredNativeComponent = {\n id,\n name,\n description,\n // Closed field list, applied once — see `toRegisteredComponentActions`.\n actions: toRegisteredComponentActions(actions),\n elementIds,\n registeredAt: Date.now(),\n mounted: true,\n };\n\n this.components.set(id, registered);\n\n this.emit('component:registered', { id, name });\n\n if (this.config.verbose) {\n console.log(`[ui-bridge-native] Registered component: ${id} (${name})`);\n }\n\n return registered;\n }\n\n /**\n * Update a registered component's declaration in place — name, description,\n * actions, elementIds.\n *\n * Sister to {@link updateElementMeta}, and it exists for the same reason one\n * level up. `registerComponent` publishes the declaration exactly once, and\n * `useUIComponent` registers on mount and never again, so an action whose\n * `label` carries a count, an action that only exists while a row is\n * selected, a `paramSchema` that widens once options load, or an `elementIds`\n * list that grows after mount, all published their mount-time shape to\n * `/control/components` forever. PR #176 closed that hole for an ELEMENT's\n * label; this closes it for a COMPONENT's declaration.\n *\n * **Idempotent by VALUE over the PUBLISHED shape.** `handler` identity is\n * excluded from the comparison — call sites pass inline closures that are\n * freshly allocated every render, so including it would report a change on\n * every call and make the door useless for a render-path publisher. The\n * newest handlers are still STORED whenever `actions` is supplied, which is\n * the other half of the same defect: without it the registry keeps the\n * closure from the render that registered the component, so a handler reading\n * component state read the state as it was at mount.\n *\n * Returns `true` when the published shape changed and an event was emitted,\n * `false` otherwise — the same contract as `updateElementMeta`, so a caller\n * can gate a warning or a state write on a real change.\n */\n updateComponentMeta(id: string, meta: UpdateComponentMetaOptions): boolean {\n const component = this.components.get(id);\n if (!component) return false;\n\n const nextName = meta.name !== undefined ? meta.name : component.name;\n const nextDescription =\n meta.description !== undefined ? meta.description : component.description;\n const nextActions =\n meta.actions !== undefined ? toRegisteredComponentActions(meta.actions) : component.actions;\n const nextElementIds = meta.elementIds !== undefined ? meta.elementIds : component.elementIds;\n\n const changed =\n nextName !== component.name ||\n nextDescription !== component.description ||\n !sameElementIds(nextElementIds, component.elementIds) ||\n publishedActionSignature(nextActions) !== publishedActionSignature(component.actions);\n\n // Nothing published changed AND no fresh handlers were offered: leave the\n // stored entry untouched so its identity stays stable for consumers holding\n // a reference.\n if (!changed && meta.actions === undefined) return false;\n\n // Store even when `changed` is false, provided actions were supplied: the\n // handlers in `nextActions` are the CURRENT render's closures. Keeping the\n // old ones is exactly how a component action ends up reading state from the\n // render it was first declared in.\n this.components.set(id, {\n ...component,\n name: nextName,\n description: nextDescription,\n actions: nextActions,\n elementIds: nextElementIds,\n });\n\n if (!changed) return false;\n\n // Reuse `component:registered` rather than minting a\n // 'component:metaChanged' BridgeEventType — same reasoning as\n // `updateElementMeta`'s reuse of `element:registered`: a new event type is a\n // breaking change for every consumer switching exhaustively over the union.\n this.emit('component:registered', { id, name: nextName });\n\n if (this.config.verbose) {\n console.log(`[ui-bridge-native] Updated component: ${id} (${nextName})`);\n }\n\n return true;\n }\n\n /**\n * Unregister a component\n */\n unregisterComponent(id: string): void {\n const component = this.components.get(id);\n if (component) {\n this.components.delete(id);\n this.emit('component:unregistered', { id });\n\n if (this.config.verbose) {\n console.log(`[ui-bridge-native] Unregistered component: ${id}`);\n }\n }\n }\n\n /**\n * Get a registered component\n */\n getComponent(id: string): RegisteredNativeComponent | undefined {\n return this.components.get(id);\n }\n\n /**\n * Get all registered components\n */\n getAllComponents(): RegisteredNativeComponent[] {\n return Array.from(this.components.values());\n }\n\n // ============================================================================\n // Workflow Management\n // ============================================================================\n\n /**\n * Register a workflow\n */\n registerWorkflow(workflow: Workflow): void {\n this.workflows.set(workflow.id, workflow);\n\n if (this.config.verbose) {\n console.log(`[ui-bridge-native] Registered workflow: ${workflow.id}`);\n }\n }\n\n /**\n * Unregister a workflow\n */\n unregisterWorkflow(id: string): void {\n this.workflows.delete(id);\n }\n\n /**\n * Get a workflow\n */\n getWorkflow(id: string): Workflow | undefined {\n return this.workflows.get(id);\n }\n\n /**\n * Get all workflows\n */\n getAllWorkflows(): Workflow[] {\n return Array.from(this.workflows.values());\n }\n\n // ============================================================================\n // Event System\n // ============================================================================\n\n /**\n * Subscribe to events\n */\n on<T = unknown>(type: BridgeEventType, listener: BridgeEventListener<T>): () => void {\n if (!this.eventListeners.has(type)) {\n this.eventListeners.set(type, new Set());\n }\n this.eventListeners.get(type)!.add(listener as BridgeEventListener);\n\n // Return unsubscribe function\n return () => this.off(type, listener);\n }\n\n /**\n * Unsubscribe from events\n */\n off<T = unknown>(type: BridgeEventType, listener: BridgeEventListener<T>): void {\n this.eventListeners.get(type)?.delete(listener as BridgeEventListener);\n }\n\n /**\n * Emit an event\n */\n emit(type: BridgeEventType, data: unknown): void {\n const event: BridgeEvent = {\n type,\n timestamp: Date.now(),\n data,\n };\n\n // Notify listeners\n const listeners = this.eventListeners.get(type);\n if (listeners) {\n for (const listener of listeners) {\n try {\n listener(event);\n } catch (error) {\n console.error(`[ui-bridge-native] Event listener error:`, error);\n }\n }\n }\n\n // Notify global handler\n if (this.config.onEvent) {\n try {\n this.config.onEvent(event);\n } catch (error) {\n console.error(`[ui-bridge-native] Global event handler error:`, error);\n }\n }\n }\n\n // ============================================================================\n // Snapshots\n // ============================================================================\n\n /**\n * Create a snapshot of the current state.\n *\n * Route resolution: `routeInfo` wins when provided. Otherwise — and this\n * is the path the default `getSnapshot` HTTP handler takes — we fall back\n * to the registered `routeProvider`. This means callers that never wire a\n * server-level override (the bare-server / cloud-relay path) still get a\n * populated `currentRoute` field as long as `setRouteProvider` was called\n * on the registry.\n *\n * `activeTab` resolves through {@link resolveActiveTab} on BOTH branches. An\n * explicit `routeInfo.activeTab` wins outright; otherwise a registered\n * provider's `getActiveTab()` answers and the Expo Router derivation is the\n * fallback. Deriving straight from `routeInfo.segments` here instead — which\n * is what this branch did when `activeTab` was introduced — silently ignored\n * `RouteProvider.getActiveTab` on the ONLY path that serves\n * `control/snapshot` once `setRouteProvider` is wired (the server installs a\n * `getSnapshot` override that passes `routeInfo`), so the opt-in hook for an\n * app whose visible pane is decoupled from the router reached no consumer.\n */\n createSnapshot(\n routeInfo?: {\n currentRoute?: string | null;\n segments?: string[];\n /**\n * Explicit active tab for callers that own the answer outright. Omit to\n * let the registered `routeProvider` / the segment derivation resolve it.\n */\n activeTab?: string | null;\n },\n options?: { visibleOnly?: boolean; currentRouteOnly?: boolean }\n ): NativeBridgeSnapshot {\n // Resolve route info: prefer the explicit argument, fall back to a\n // registered route provider. We treat an explicitly-passed `routeInfo`\n // (even if both fields are null/undefined) as \"the caller is in charge\"\n // so server-layer overrides that pass `{currentRoute: null}` keep working.\n const resolvedRoute: {\n currentRoute: string | null;\n segments: string[] | undefined;\n activeTab: string | undefined;\n } = (() => {\n if (routeInfo !== undefined) {\n const explicit =\n typeof routeInfo.activeTab === 'string' && routeInfo.activeTab.length > 0\n ? routeInfo.activeTab\n : undefined;\n return {\n currentRoute: routeInfo.currentRoute ?? null,\n segments: routeInfo.segments,\n activeTab: explicit ?? this.resolveActiveTab(routeInfo.segments),\n };\n }\n if (this.routeProvider) {\n try {\n const segments = this.routeProvider.getSegments?.();\n return {\n currentRoute: this.routeProvider.getCurrentRoute() ?? null,\n segments,\n activeTab: this.resolveActiveTab(segments),\n };\n } catch (error) {\n if (this.config.verbose) {\n console.warn(`[ui-bridge-native] routeProvider getCurrentRoute threw:`, error);\n }\n return { currentRoute: null, segments: undefined, activeTab: undefined };\n }\n }\n return { currentRoute: null, segments: undefined, activeTab: undefined };\n })();\n\n // visibleOnly uses the *looser* mounted-visible filter so a snapshot\n // taken in the gap between mount and the first `onLayout` doesn't\n // return 0 elements on a populated screen. Each element in the\n // payload carries a `visibility` field so agents can branch on\n // measured vs unmeasured.\n let elements = options?.visibleOnly ? this.getMountedVisibleElements() : this.getAllElements();\n\n // Filter to elements that belong on the current route. Route-agnostic\n // elements (no `registrationRoute`) pass through too — see the\n // `matchesCurrentRoute` doc comment for the rationale (tab bars, modal\n // hosts and other app-root registrations have a null route and apply to\n // every screen).\n if (options?.currentRouteOnly && resolvedRoute.currentRoute) {\n const currentRoute = resolvedRoute.currentRoute;\n elements = elements.filter((e) => matchesCurrentRoute(e.registrationRoute, currentRoute));\n }\n\n // Read ONCE per snapshot, not once per element: it is a `Dimensions.get`\n // call behind a try/catch, and a rotation landing mid-map would otherwise\n // give elements in the SAME snapshot different clip regions.\n const viewport = this.getViewportRect();\n\n const snapshot: NativeBridgeSnapshot = {\n timestamp: Date.now(),\n elements: elements.map((e) => {\n const handlers = extractHandlerNames(e.props);\n const state = e.getState();\n const identifier = e.getIdentifier();\n const clip = this.getClipRectFor(e, viewport);\n const { visibility, visibilityReason } = computeVisibility(state, clip);\n const bbox = projectBbox(state, visibility, this.pixelRatio);\n const vision = projectVisionFields({\n type: e.type,\n label: e.label,\n // Real interactivity signal: the names of props that are actually\n // functions (onPress/onChangeText/...). NOT `e.actions`, which\n // `inferActions` synthesizes press/click onto for every element —\n // using it would mark plain text/view nodes interactable.\n handlerNames: handlers,\n state,\n flatStyle: e.flatStyle,\n props: e.props,\n accessibilityLabel: identifier.accessibilityLabel,\n inViewport: computeInViewport(state, clip),\n });\n return {\n id: e.id,\n type: e.type,\n label: e.label,\n identifier,\n state,\n actions: e.actions,\n customActions: serializeElementCustomActions(e.customActions),\n registeredHandlers: handlers.length > 0 ? handlers : undefined,\n registrationRoute: e.registrationRoute,\n visibility,\n ...(visibilityReason ? { visibilityReason } : {}),\n ...(bbox ? { bbox } : {}),\n ...vision,\n };\n }),\n components: this.getAllComponents().map((c) => ({\n id: c.id,\n name: c.name,\n description: c.description,\n actions: c.actions.map((a) => a.id),\n elementIds: c.elementIds,\n })),\n workflows: this.getAllWorkflows().map((w) => ({\n id: w.id,\n name: w.name,\n description: w.description,\n stepCount: w.steps.length,\n })),\n currentRoute: resolvedRoute.currentRoute,\n segments: resolvedRoute.segments,\n // Canonical aliases, spelled the way the web SDK's `BridgeSnapshot`\n // spells them so a cross-platform consumer reads one field name on both\n // platforms. Omitted rather than nulled when unknown — same convention\n // as the web fields, and it keeps the emitted shape byte-identical for\n // hosts that wire no route provider.\n ...(resolvedRoute.currentRoute ? { route: resolvedRoute.currentRoute } : {}),\n ...(resolvedRoute.activeTab ? { activeTab: resolvedRoute.activeTab } : {}),\n // The router-asserted route TEMPLATE, spelled as the web SDK's\n // `snapshot.page.route`. Emitted on the ONE assembly point both route\n // branches above share (the explicit-`routeInfo` branch the server's\n // `getSnapshot` override uses, and the provider fallback). Omitted, not\n // nulled, when unset — the same convention as the aliases above.\n ...(this.reportedRoute ? { page: { route: copyRouteInfo(this.reportedRoute) } } : {}),\n registration: this.getRegistrationCoverage(),\n };\n\n // Include app identification metadata so consumers can identify which\n // app produced this snapshot without a separate health probe.\n if (this.appInfo) {\n snapshot.appInfo = this.appInfo;\n }\n\n // Canonical enrichers — each in its own try/catch so a misbehaving tracker\n // can never break the rest of the snapshot.\n if (this.enrichers.modalDetector) {\n try {\n snapshot.modalStack = this.enrichers.modalDetector.getSnapshotModalContext();\n } catch (error) {\n if (this.config.verbose) {\n console.warn(`[ui-bridge-native] modalDetector enricher threw:`, error);\n }\n }\n }\n if (this.enrichers.toastCapture) {\n try {\n snapshot.toasts = this.enrichers.toastCapture.getSnapshotToastContext();\n } catch (error) {\n if (this.config.verbose) {\n console.warn(`[ui-bridge-native] toastCapture enricher threw:`, error);\n }\n }\n }\n if (this.enrichers.undoTracker) {\n try {\n snapshot.undoRedo = this.enrichers.undoTracker.getSnapshotUndoContext();\n } catch (error) {\n if (this.config.verbose) {\n console.warn(`[ui-bridge-native] undoTracker enricher threw:`, error);\n }\n }\n }\n\n // Custom enrichers — keys assign-merged onto the snapshot\n if (this.snapshotExtras.size > 0) {\n const ctx = { elements, currentRoute: resolvedRoute.currentRoute };\n for (const [name, fn] of this.snapshotExtras) {\n try {\n const extra = fn(ctx);\n if (extra && typeof extra === 'object') {\n Object.assign(snapshot, extra);\n }\n } catch (error) {\n if (this.config.verbose) {\n console.warn(`[ui-bridge-native] snapshot enricher \"${name}\" threw:`, error);\n }\n }\n }\n // Custom enrichers `Object.assign` their keys, so one returning a `page`\n // key would displace the canonical, router-asserted `page.route`.\n // Re-apply it so it always wins. When no route info is set, an\n // enricher's `page` is left exactly as the enricher wrote it.\n if (this.reportedRoute) {\n snapshot.page = { route: copyRouteInfo(this.reportedRoute) };\n }\n }\n\n return snapshot;\n }\n\n /**\n * Get registry statistics\n */\n getStats(): { elements: number; components: number; workflows: number } {\n return {\n elements: this.elements.size,\n components: this.components.size,\n workflows: this.workflows.size,\n };\n }\n\n /**\n * Compute registration coverage metadata for the current registry state.\n *\n * Groups currently-registered elements by `registrationRoute`, bucketing\n * elements without a route under `'?'`. `everHadRegistrations` is sticky —\n * once true, it stays true for the lifetime of the registry instance.\n */\n getRegistrationCoverage(): NativeRegistrationCoverage {\n const byRoute: Record<string, number> = {};\n for (const element of this.elements.values()) {\n const key =\n element.registrationRoute == null || element.registrationRoute === ''\n ? '?'\n : element.registrationRoute;\n byRoute[key] = (byRoute[key] ?? 0) + 1;\n }\n return {\n totalRegistered: this.elements.size,\n everHadRegistrations: this.everHadRegistrations,\n byRoute,\n };\n }\n\n /**\n * Clear all registrations\n */\n clear(): void {\n this.elements.clear();\n this.components.clear();\n this.workflows.clear();\n\n if (this.config.verbose) {\n console.log(`[ui-bridge-native] Registry cleared`);\n }\n }\n}\n\n// ============================================================================\n// Global Registry\n// ============================================================================\n\nlet globalRegistry: NativeUIBridgeRegistry | null = null;\n\n/**\n * Set the global registry\n */\nexport function setGlobalRegistry(registry: NativeUIBridgeRegistry): void {\n globalRegistry = registry;\n}\n\n/**\n * Get the global registry\n */\nexport function getGlobalRegistry(): NativeUIBridgeRegistry | null {\n return globalRegistry;\n}\n\n/**\n * Reset the global registry\n */\nexport function resetGlobalRegistry(): void {\n globalRegistry?.clear();\n globalRegistry = null;\n}\n","/**\n * Native Element Identifier\n *\n * Utilities for identifying and finding native elements.\n * In React Native, we use testID, accessibilityLabel, and tree paths\n * instead of XPath/CSS selectors.\n */\n\nimport type { NativeElementIdentifier, RegisteredNativeElement } from './types';\nimport { getGlobalRegistry } from './registry';\n\n/**\n * Create an element identifier for a native element\n */\nexport function createNativeElementIdentifier(\n id: string,\n options: {\n testId?: string;\n accessibilityLabel?: string;\n accessibilityHint?: string;\n treePath?: string;\n } = {}\n): NativeElementIdentifier {\n return {\n uiId: id,\n testId: options.testId || id,\n accessibilityLabel: options.accessibilityLabel,\n accessibilityHint: options.accessibilityHint,\n treePath: options.treePath || id,\n };\n}\n\n/**\n * Find an element by its identifier in the global registry\n */\nexport function findElementByIdentifier(\n identifier: string | NativeElementIdentifier\n): RegisteredNativeElement | null {\n const registry = getGlobalRegistry();\n if (!registry) return null;\n\n // If string, try different lookup strategies\n if (typeof identifier === 'string') {\n // Try direct ID lookup\n const byId = registry.getElement(identifier);\n if (byId) return byId;\n\n // Try testID lookup\n const byTestId = registry.findByTestId(identifier);\n if (byTestId) return byTestId;\n\n // Try pattern matching\n return findByPattern(registry, identifier);\n }\n\n // If identifier object, try each strategy\n if (identifier.uiId) {\n const byId = registry.getElement(identifier.uiId);\n if (byId) return byId;\n }\n\n if (identifier.testId) {\n const byTestId = registry.findByTestId(identifier.testId);\n if (byTestId) return byTestId;\n }\n\n return null;\n}\n\n/**\n * Find element by pattern (supports wildcards)\n */\nfunction findByPattern(\n registry: ReturnType<typeof getGlobalRegistry>,\n pattern: string\n): RegisteredNativeElement | null {\n if (!registry) return null;\n\n // Convert pattern to regex\n const regexPattern = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // Escape special chars\n .replace(/\\*/g, '.*') // Convert * to .*\n .replace(/\\?/g, '.'); // Convert ? to .\n\n const regex = new RegExp(`^${regexPattern}$`, 'i');\n\n for (const element of registry.getAllElements()) {\n const identifier = element.getIdentifier();\n\n // Match against testId\n if (identifier.testId && regex.test(identifier.testId)) {\n return element;\n }\n\n // Match against uiId\n if (identifier.uiId && regex.test(identifier.uiId)) {\n return element;\n }\n\n // Match against treePath\n if (identifier.treePath && regex.test(identifier.treePath)) {\n return element;\n }\n\n // Match against accessibilityLabel\n if (identifier.accessibilityLabel && regex.test(identifier.accessibilityLabel)) {\n return element;\n }\n }\n\n return null;\n}\n\n/**\n * Find all elements matching a pattern\n */\nexport function findAllByPattern(pattern: string): RegisteredNativeElement[] {\n const registry = getGlobalRegistry();\n if (!registry) return [];\n\n // Convert pattern to regex\n const regexPattern = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.');\n\n const regex = new RegExp(`^${regexPattern}$`, 'i');\n const results: RegisteredNativeElement[] = [];\n\n for (const element of registry.getAllElements()) {\n const identifier = element.getIdentifier();\n\n if (\n (identifier.testId && regex.test(identifier.testId)) ||\n (identifier.uiId && regex.test(identifier.uiId)) ||\n (identifier.treePath && regex.test(identifier.treePath)) ||\n (identifier.accessibilityLabel && regex.test(identifier.accessibilityLabel))\n ) {\n results.push(element);\n }\n }\n\n return results;\n}\n\n/**\n * Build a tree path for an element based on its position in the component tree\n */\nexport function buildTreePath(componentPath: string[], elementIndex?: number): string {\n let path = componentPath.join('/');\n if (elementIndex !== undefined) {\n path += `[${elementIndex}]`;\n }\n return path;\n}\n\n/**\n * Parse a tree path into its components\n */\nexport function parseTreePath(treePath: string): { components: string[]; index?: number } {\n const indexMatch = treePath.match(/\\[(\\d+)\\]$/);\n const index = indexMatch ? parseInt(indexMatch[1], 10) : undefined;\n const pathWithoutIndex = treePath.replace(/\\[\\d+\\]$/, '');\n const components = pathWithoutIndex.split('/').filter(Boolean);\n\n return { components, index };\n}\n\n/**\n * Check if an identifier matches criteria\n */\nexport function matchesIdentifier(\n identifier: NativeElementIdentifier,\n criteria: Partial<NativeElementIdentifier>\n): boolean {\n if (criteria.uiId && identifier.uiId !== criteria.uiId) {\n return false;\n }\n if (criteria.testId && identifier.testId !== criteria.testId) {\n return false;\n }\n if (\n criteria.accessibilityLabel &&\n identifier.accessibilityLabel !== criteria.accessibilityLabel\n ) {\n return false;\n }\n if (criteria.treePath && identifier.treePath !== criteria.treePath) {\n return false;\n }\n return true;\n}\n","/**\n * Abandonment primitive for action invocation.\n *\n * Plan `2026-08-20-ui-bridge-action-declaration-shape`, Phase 3.\n *\n * `ActionHandler`'s `options.signal` is **cooperative**: a well-behaved handler\n * observes it and returns early. Cooperation cannot be enforced, and a handler\n * that hangs is exactly the case cancellation exists for — so the executor\n * never trusts the handler to observe the signal. It races the handler promise\n * against the abort and reports the outcome either way.\n *\n * DUPLICATE OF `@qontinui/ui-bridge` `src/core/abortable.ts` — DELIBERATELY NOT\n * AN IMPORT. `@qontinui/ui-bridge` is an OPTIONAL peerDependency of this\n * package and is absent from `dependencies`, so a consumer may install\n * `@qontinui/ui-bridge-native` alone; importing from it would resolve inside\n * this monorepo and then fail for that consumer. See the same note on\n * `core/types.ts` `ActionHandler`.\n *\n * KEEP IN SYNC with `@qontinui/ui-bridge` `src/core/abortable.ts` — including\n * `MAX_ACTION_TIMEOUT_MS` and `normalizeActionTimeoutMs` below. The module has\n * no imports and touches no DOM API — only `AbortController` and `setTimeout`,\n * both present in React Native.\n */\n\n/** Which of the two cancellation sources fired. */\nexport type AbortReason = 'signal' | 'timeout';\n\n/**\n * Ceiling applied to a caller-supplied `timeoutMs` — **24 hours**.\n *\n * `ComponentActionRequest.timeoutMs` crosses the wire, so it is attacker- (or\n * typo-) controlled: an HTTP or WebSocket caller can put any JSON number in\n * it and it would otherwise land in `setTimeout` unchecked. Two concrete\n * hazards that ceiling closes:\n *\n * - **`setTimeout`'s 32-bit delay overflow.** Any delay above 2147483647ms\n * wraps to a *negative* 32-bit int in every major runtime and the timer\n * fires **immediately** — so `timeoutMs: 2147483648` (\"about 25 days,\n * effectively no timeout\") would abandon the action on the next tick, the\n * exact opposite of what the caller asked for. Clamping below that boundary\n * makes the semantics monotonic: a bigger number never means a shorter wait.\n * - **A pinned timer per in-flight request.** Nothing here is unbounded work,\n * but a 100-year timeout is indistinguishable from \"no timeout\" while still\n * holding the handler's abandonment machinery alive, so there is nothing to\n * buy above a day.\n *\n * Clamping (rather than rejecting) is right at the top end because the intent\n * is unambiguous — \"effectively forever\" — and the clamped value honours it as\n * closely as the platform can. The malformed cases below are rejected instead,\n * because there the intent is NOT recoverable.\n */\nexport const MAX_ACTION_TIMEOUT_MS = 86_400_000;\n\n/**\n * Outcome of {@link normalizeActionTimeoutMs}. Discriminated on `ok` so a\n * rejected value cannot be read as a timeout by accident.\n */\nexport type TimeoutNormalization =\n | {\n ok: true;\n /** `undefined` ≡ no timeout. Always a finite integer in `[0, MAX]`. */\n timeoutMs: number | undefined;\n /** True iff the caller's value was above {@link MAX_ACTION_TIMEOUT_MS}. */\n clamped: boolean;\n }\n | { ok: false; reason: string };\n\n/**\n * Validate and clamp a wire-supplied `timeoutMs` before it can reach a timer.\n *\n * The policy, stated once so all three executors share it:\n *\n * | Supplied | Result | Why |\n * |---|---|---|\n * | absent / `null` | no timeout | the documented \"omitted ⇒ no timeout\" default |\n * | `0` | abandon on the next tick | a coherent request (\"don't let it run\"), and what `setTimeout(…, 0)` already means |\n * | `1 … MAX` | honoured, floored to an integer | fractional ms is not a thing a timer can express |\n * | `> MAX` | clamped to {@link MAX_ACTION_TIMEOUT_MS} | see that constant — above 2^31-1 the platform timer silently inverts |\n * | negative | **rejected** | `setTimeout` would treat it as `0`, i.e. a sign typo would abandon every call instantly and silently |\n * | `NaN` / `±Infinity` | **rejected** | `setTimeout(NaN)` is `setTimeout(0)` — same silent-instant-abandon trap |\n * | not a number (`\"5000\"`, `{}`, `true`) | **rejected** | JSON has numbers; a string here means the caller built the body wrong, and coercing would hide it |\n *\n * Rejection is loud on purpose: every rejected case is one where *ignoring*\n * the field fails open (the action runs uncancellable) and *coercing* it fails\n * closed at zero. Neither is what the caller asked for, and both are silent —\n * which is the defect class this whole plan exists to remove.\n */\nexport function normalizeActionTimeoutMs(raw: unknown): TimeoutNormalization {\n if (raw === undefined || raw === null) {\n return { ok: true, timeoutMs: undefined, clamped: false };\n }\n if (typeof raw !== 'number') {\n return {\n ok: false,\n reason: `timeoutMs must be a number of milliseconds, received ${typeof raw === 'string' ? JSON.stringify(raw) : typeof raw}`,\n };\n }\n if (!Number.isFinite(raw)) {\n return {\n ok: false,\n reason: `timeoutMs must be a finite number of milliseconds, received ${String(raw)}`,\n };\n }\n if (raw < 0) {\n return {\n ok: false,\n reason: `timeoutMs must not be negative, received ${String(raw)}`,\n };\n }\n if (raw > MAX_ACTION_TIMEOUT_MS) {\n return { ok: true, timeoutMs: MAX_ACTION_TIMEOUT_MS, clamped: true };\n }\n return { ok: true, timeoutMs: Math.floor(raw), clamped: false };\n}\n\n/**\n * Result of {@link runAbortable}. Discriminated on `aborted` so the caller\n * cannot read `result` off an abandoned invocation.\n */\nexport type AbortableOutcome<T> =\n | { aborted: false; result: T }\n | { aborted: true; reason: AbortReason };\n\n/** Cancellation sources composed by {@link runAbortable}. */\nexport interface RunAbortableOptions {\n /**\n * An in-process caller's signal. Cannot cross the wire — that is what\n * `timeoutMs` is for.\n */\n signal?: AbortSignal;\n /**\n * Milliseconds after which the invocation is abandoned. Serializable, so\n * this is the arm an HTTP/WebSocket caller can actually use.\n *\n * Expected to have been through {@link normalizeActionTimeoutMs} already —\n * every executor seam runs it, because this value arrives from the wire.\n * The `Number.isFinite` / `>= 0` guard below is defence in depth for a\n * direct in-process caller, and ignores anything malformed rather than\n * letting it reach `setTimeout`.\n */\n timeoutMs?: number;\n}\n\n/**\n * A fresh `AbortSignal` that is never aborted.\n *\n * `ActionHandlerOptions` promises the bag is *always* supplied, so a handler\n * written the way the docs describe — `(params, { signal }) => …` — must never\n * be called with `undefined` as its second argument. Not every invocation seam\n * has a cancellation source to give it though: an ELEMENT custom action\n * (`ControlActionRequest`) carries no `timeoutMs` and the executor holds no\n * controller for it, so there is nothing that could ever abort. Those seams\n * pass this instead of `undefined`, which keeps the destructure safe and keeps\n * `signal.aborted` honestly `false`.\n *\n * **Fresh per call, deliberately.** A shared singleton would accumulate one\n * listener per handler that calls `signal.addEventListener('abort', …)` and\n * never releases them — a leak on a hot path. A `AbortController` allocation\n * is a few bytes and custom element actions are not a hot path.\n *\n * When element actions grow a cancellation source, replace the call sites with\n * a real `runAbortable` race — the handler contract does not change.\n */\nexport function inertAbortSignal(): AbortSignal {\n return new AbortController().signal;\n}\n\n/**\n * Invoke `invoke` with a derived `AbortSignal`, abandoning it if either the\n * caller's signal aborts or `timeoutMs` elapses.\n *\n * - Returns `{ aborted: true, reason }` **without invoking at all** when the\n * caller's signal is already aborted.\n * - Returns `{ aborted: false, result }` on normal completion.\n * - **Rejects** if `invoke` throws/rejects while the race is still live, so a\n * genuine handler error stays distinguishable from a cancellation.\n * - A rejection that arrives *after* an abort already won is swallowed:\n * `Promise.race` has a rejection handler attached, so the abandoned handler\n * cannot surface as an unhandled rejection.\n */\nexport async function runAbortable<T>(\n invoke: (signal: AbortSignal) => T | Promise<T>,\n options: RunAbortableOptions = {}\n): Promise<AbortableOutcome<T>> {\n const callerSignal = options.signal;\n const timeoutMs = options.timeoutMs;\n\n if (callerSignal?.aborted) {\n return { aborted: true, reason: 'signal' };\n }\n\n const controller = new AbortController();\n // Set before `controller.abort()` in both arms, so the abort listener reads\n // the reason that actually fired.\n let reason: AbortReason = 'signal';\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const onCallerAbort = (): void => {\n reason = 'signal';\n controller.abort();\n };\n\n callerSignal?.addEventListener('abort', onCallerAbort, { once: true });\n\n if (typeof timeoutMs === 'number' && Number.isFinite(timeoutMs) && timeoutMs >= 0) {\n // The clamp is applied AGAIN here, at the only place a delay actually\n // reaches a timer. `normalizeActionTimeoutMs` already did it for every\n // wire path, but a direct in-process caller reaches `runAbortable`\n // without passing through an executor — and above 2^31-1 `setTimeout`\n // wraps negative and fires immediately, turning \"wait a century\" into\n // \"abandon now\". See MAX_ACTION_TIMEOUT_MS.\n timer = setTimeout(\n () => {\n reason = 'timeout';\n controller.abort();\n },\n Math.min(timeoutMs, MAX_ACTION_TIMEOUT_MS)\n );\n }\n\n try {\n const abandoned = new Promise<AbortableOutcome<T>>((resolve) => {\n controller.signal.addEventListener('abort', () => resolve({ aborted: true, reason }), {\n once: true,\n });\n });\n\n const completed = (async (): Promise<AbortableOutcome<T>> => ({\n aborted: false,\n result: await invoke(controller.signal),\n }))();\n\n return await Promise.race([completed, abandoned]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n callerSignal?.removeEventListener('abort', onCallerAbort);\n }\n}\n","/**\n * Parameter-schema validation for component actions.\n *\n * Plan `2026-08-20-ui-bridge-action-declaration-shape`, Phase 2.\n *\n * `ComponentAction.paramSchema` is **published to agents** — it is spread onto\n * `/control/components` and `/control/component/:id` by\n * `server/handlers.ts` `annotateComponentWithInvocationPaths`, and four live\n * runner consumers read it off the wire. Until Phase 2 nothing enforced it, so\n * an agent that read the schema and sent conforming params had no guarantee\n * the handler agreed. That is worse than publishing nothing: it invites trust\n * it cannot honour. This module is what makes the declaration enforceable.\n *\n * ---------------------------------------------------------------------------\n * THE SUPPORTED SUBSET — this is a CROSS-REPO CONTRACT\n * ---------------------------------------------------------------------------\n *\n * `qontinui-runner` reads these same schemas\n * (`workflow_generation/wrapper_manifest.rs`, `commands/command_interpreter.rs`,\n * `bin/wrappers_mcp.rs`), and `command_interpreter.rs` feeds one to an LLM and\n * asks it to produce conforming args. So the grammar below is not a private\n * implementation detail — changing it changes what those consumers may emit.\n *\n * **No dependency.** `@qontinui/ui-bridge` ships to browsers and has exactly\n * one runtime dependency (`dom-accessibility-api`). A full JSON Schema\n * validator (`ajv` and friends) would be the SDK's first heavyweight runtime\n * dep, which the plan rejected on bundle size. Hence a hand-implemented subset,\n * documented here rather than inferred from the code.\n *\n * **Two accepted top-level shapes**, because `useUIComponent`'s own author-\n * facing doc has always sanctioned both:\n *\n * 1. **Object-schema form** — the shape `paramSchemaOf()`\n * (`@qontinui/ui-bridge-wrapper`) emits:\n *\n * ```jsonc\n * { \"type\": \"object\",\n * \"properties\": { \"username\": { \"type\": \"string\" },\n * \"remember\": { \"type\": \"boolean\" } },\n * \"required\": [\"username\"],\n * \"additionalProperties\": false }\n * ```\n *\n * 2. **Map form** — the lightweight shorthand the doc describes as\n * \"a map of `{ paramName: \"string\" | \"number\" | ... }`\":\n *\n * ```jsonc\n * { \"username\": \"string\", \"count\": { \"type\": \"integer\", \"minimum\": 1 } }\n * ```\n *\n * **Disambiguation rule** (deterministic, no heuristics). The schema is read as\n * form 1 **iff** either:\n *\n * - it carries a `properties` object; or\n * - `type === \"object\"` **and every other top-level key is a JSON Schema\n * object-applicable keyword** ({@link OBJECT_SCHEMA_KEYWORDS}).\n *\n * Everything else is form 2. So `{ \"type\": \"string\" }` is a map declaring one\n * param named `type` of type `string`, not a top-level string schema — a\n * params bag is always an object, so a non-object top-level `type` cannot be\n * describing the bag.\n *\n * ⚠️ **The second clause is load-bearing, and a naive `type === \"object\"` test\n * is a silent-validation-loss bug.** Consider the map form\n * `{ \"type\": \"object\", \"id\": \"string\" }` — an action taking a param literally\n * named `type` whose hint is `\"object\"`, plus an `id`. Reading that as form 1\n * would find no `properties`, express no constraint, and **silently drop every\n * sibling declaration in the map**: `validateActionParams(schema, { id: 12345 })`\n * would answer `{ valid: true }` for a schema that plainly says `id` is a\n * string. `id` is not an object keyword, so the keyword test sends it to form 2\n * and both params validate. A param named `type` is not exotic — it is one of\n * the most common names there is.\n *\n * The one genuinely ambiguous input is a bare `{ \"type\": \"object\" }`: it is\n * both an empty object schema and a one-entry map declaring a param named\n * `type` of type `object`. It is read as form 1 (the empty object schema),\n * which constrains nothing either way, so the choice is unobservable except in\n * combination with `additionalProperties: false` — where form 1 is clearly the\n * intended reading.\n *\n * ### Map form is a TYPE hint, never a REQUIREDNESS hint\n *\n * A bare string value in map form is read as a type name **only when it is one\n * of the seven JSON Schema primitive names** — `string`, `number`, `integer`,\n * `boolean`, `object`, `array`, `null` — which is exactly `paramSchemaOf`'s\n * own `PRIMITIVE_TYPES` set. **Any other string is prose and constrains\n * nothing.**\n *\n * That is not defensive coding, it is the corpus: a census of every literal\n * `paramSchema` in the fleet (2026-08-22) found ~50 map-form declarations in\n * `qontinui-runner` alone whose values are human-readable hints, not types —\n * `{ count: \"number (>= 1, defaults to 1)\", account: \"string — either a Claude\n * account label … or the literal \\\"best\\\"\" }`. Reading `\"number (>= 1,\n * defaults to 1)\"` as a type name would make every one of them unvalidatable\n * noise. The file that declares them says so itself: *\"loose `Record<string,\n * unknown>` with hint strings, no runtime validation\"*.\n *\n * For the same reason, **map form never marks anything required.** It has no\n * `required` array and no other way to express optionality, and most of those\n * ~50 real declarations describe params that are explicitly optional in their\n * own prose (\"defaults to 1\", \"optional initial prompt\"). Requiring by default\n * would report a violation on nearly every real call. An author who needs\n * required params writes form 1, which has a keyword for it.\n *\n * **Optionality in form 1** is exactly `required`: a property is required iff\n * listed there. Note `paramSchemaOf` **omits `required` entirely** when every\n * field is optional, so its absence means \"nothing required\", never \"malformed\".\n *\n * `optional: true` is not a JSON Schema keyword — it is this monorepo's\n * convention on `paramSchemaOf`'s *input* descriptors, and `paramSchemaOf`\n * strips it before the schema reaches the wire. It is honoured here (form 2\n * only) purely so an unstripped input-shaped object is read the same way.\n *\n * **Recognised property keywords** — everything else is IGNORED:\n *\n * | Keyword | Applies to | Meaning |\n * |---|---|---|\n * | `type` | any | `\"string\" \\| \"number\" \\| \"integer\" \\| \"boolean\" \\| \"object\" \\| \"array\" \\| \"null\"`, or an array of those (union — a value matching any member passes). Any OTHER type name constrains nothing (`paramSchemaOf` passes unknown names through verbatim, so they do occur) |\n * | `enum` | any | value must equal one of the listed JSON values |\n * | `const` | any | value must equal this JSON value |\n * | `properties` | objects | per-key sub-schemas, applied recursively |\n * | `required` | objects | array of key names that must be present |\n * | `additionalProperties` | objects | when exactly `false`, keys absent from `properties` are rejected — **including when the schema declares no `properties` at all**, which is the natural spelling of \"takes no params\". Any other value is ignored |\n * | `items` | arrays | one sub-schema applied to every element, recursively |\n * | `minimum` / `maximum` | numbers | inclusive bounds |\n * | `minLength` / `maxLength` | strings | inclusive bounds on `.length` |\n * | `pattern` | strings | ECMAScript regex, unanchored. An uncompilable **or catastrophically-backtracking** pattern is ignored, never a failure — see {@link MAX_PATTERN_LENGTH} |\n *\n * **⚠ Anything outside this table is IGNORED, never rejected.** `description`,\n * `default`, `title`, `format`, `examples`, `$schema`, `$ref`, `oneOf`,\n * `anyOf`, `allOf`, `not`, `patternProperties`, `minItems`, `exclusiveMinimum`\n * … all pass silently. This is deliberate and load-bearing: a schema outside\n * this subset is still *valid JSON Schema*, and rejecting an unrecognised\n * keyword would convert a documentation nicety into a hard invocation failure\n * for every author who wrote a richer schema than this validator reads. An\n * unreadable schema means \"no constraint expressed here\", not \"invalid\".\n * The same rule applies one level up: a `paramSchema` that is not a plain\n * object at all (`null`, an array, a string) expresses no constraint and\n * validates everything.\n *\n * **No coercion.** `\"5\"` does not satisfy `{\"type\":\"number\"}`. The runner's\n * LLM router is already instructed to \"coerce numeric tokens to numbers\"\n * (`command_interpreter.rs`), so coercing here would hide that contract rather\n * than enforce it.\n *\n * ### A validator must return a verdict, never throw\n *\n * {@link validateActionParams} is called at an invocation seam whose only\n * generic failure label is `UB-ACTION-FAILED` — \"the handler failed\". A\n * validator that throws would therefore be reported as a *handler* fault, and\n * the author would go debug code that never ran. Two inputs could do it, and\n * both are **author-supplied schema data**, not caller params:\n *\n * - **A self-referential schema.** `const s = { type: 'object', properties: {} };\n * s.properties.self = s;` recurses until `RangeError: Maximum call stack size\n * exceeded`. Bounded by {@link MAX_SCHEMA_DEPTH}: past that depth the walk\n * stops and expresses no further constraint (the ignore-don't-reject rule,\n * applied to depth).\n * - **A catastrophically-backtracking `pattern`.** This one does not throw at\n * all — it *hangs*, which no `try`/`catch` can rescue. Guarded before\n * compilation by {@link MAX_PATTERN_LENGTH} and a nested-quantifier test,\n * the same pair `control/action-executor.ts` already applies to a\n * caller-supplied `srcPattern`.\n *\n * The seams wrap the call anyway (belt and braces) and report a validator\n * fault distinguishably from a handler fault — see the executors.\n *\n * ---------------------------------------------------------------------------\n * DUPLICATE — deliberately not an import\n * ---------------------------------------------------------------------------\n *\n * DUPLICATE OF `@qontinui/ui-bridge` `src/core/param-schema.ts` — DELIBERATELY\n * NOT AN IMPORT, for the same reason as `core/abortable.ts` beside it:\n * `@qontinui/ui-bridge` is an OPTIONAL peerDependency of this package and is\n * absent from `dependencies`, so a consumer may install\n * `@qontinui/ui-bridge-native` alone; importing from it would resolve inside\n * this monorepo and then fail for that consumer.\n *\n * KEEP IN SYNC with `@qontinui/ui-bridge` `src/core/param-schema.ts`. The\n * module has no imports and touches no DOM or Node API — only `RegExp`,\n * `JSON`, `Set` and `Number`, all present in React Native. The documented\n * subset above is a cross-repo contract, so the two copies drifting is a\n * contract break, not a style nit.\n */\n\n/** Which rule rejected a value. Machine-readable; pairs with `message`. */\nexport type ParamSchemaKeyword =\n | 'required'\n | 'type'\n | 'enum'\n | 'const'\n | 'additionalProperties'\n | 'minimum'\n | 'maximum'\n | 'minLength'\n | 'maxLength'\n | 'pattern';\n\n/**\n * One rejected parameter. `path` is the whole point — an agent told only\n * \"invalid params\" cannot fix anything, so every issue names the param it is\n * about.\n */\nexport interface ParamSchemaIssue {\n /**\n * Location of the offending value, in dotted/bracketed form relative to the\n * params object: `\"username\"`, `\"filter.status\"`, `\"ids[2]\"`. Empty string\n * means the params object itself.\n */\n path: string;\n /** Which rule rejected it. */\n keyword: ParamSchemaKeyword;\n /** What the schema demanded, rendered for a human/agent reader. */\n expected: string;\n /** What was actually supplied, rendered compactly. Absent for `required`. */\n received?: string;\n /** One-line explanation naming both the param and the reason. */\n message: string;\n}\n\n/** Outcome of {@link validateActionParams}. */\nexport interface ParamValidationResult {\n valid: boolean;\n /** Empty iff `valid`. */\n issues: ParamSchemaIssue[];\n}\n\n/**\n * What the executor does with a schema violation.\n *\n * - `'enforce'` — reject the invocation before the handler runs, with\n * `errorCode: 'UB-ACTION-REJECTED'` and the issue list attached.\n * - `'warn'` — log the violation and invoke the handler anyway.\n * - `'off'` — skip validation entirely (no schema walk, no allocation).\n */\nexport type ParamValidationMode = 'enforce' | 'warn' | 'off';\n\n/**\n * **`'warn'`.** Decided on the plan's priority order — *powerful features →\n * scalability → robustness → clean code* — not on backward compatibility,\n * which is explicitly not a factor on this project.\n *\n * The feature Phase 2 delivers is *an enforceable declaration*, and warn mode\n * delivers all of it except the refusal: the schema is walked, the violating\n * param is named, and the violation is reported. Enforcement is one call to\n * {@link setDefaultParamValidationMode} away, per deployment.\n *\n * What arming `'enforce'` by default would buy is the refusal — and the\n * population it would refuse first is not attackers, it is the fleet's own\n * schemas. Nothing has ever validated these declarations, so on the balance of\n * evidence some existing `paramSchema` values are wrong about their own\n * handlers rather than the callers being wrong about the schema; a validator\n * armed on day one reports that as the caller's failure. Worse, the largest\n * live producer of action params is a *model*:\n * `qontinui-runner/src-tauri/src/commands/command_interpreter.rs` asks an LLM\n * for \"args whose keys match the chosen action's `paramSchema`\", and that\n * failure surfaces to a user running a slash command. Defaulting to enforce\n * would convert a documentation defect into a user-visible outage on a\n * population that was never measured.\n *\n * Robustness — third in the order, and the tiebreaker here — argues the same\n * way: the robust move is to collect violations from a validator that has\n * never seen production traffic before letting it refuse anything. A default\n * that fails open and is *observable* beats a default that fails closed and is\n * *unmeasured*.\n */\nexport const DEFAULT_PARAM_VALIDATION_MODE: ParamValidationMode = 'warn';\n\nlet currentDefaultMode: ParamValidationMode = DEFAULT_PARAM_VALIDATION_MODE;\n\n/**\n * Arm or disarm validation process-wide. Call once at app boot; each\n * invocation may still override it via `executeComponentAction`'s\n * `{ paramValidation }` option.\n */\nexport function setDefaultParamValidationMode(mode: ParamValidationMode): void {\n currentDefaultMode = mode;\n}\n\n/** The mode an invocation gets when it does not pass one. */\nexport function getDefaultParamValidationMode(): ParamValidationMode {\n return currentDefaultMode;\n}\n\n/** Test/teardown helper: restore {@link DEFAULT_PARAM_VALIDATION_MODE}. */\nexport function resetDefaultParamValidationMode(): void {\n currentDefaultMode = DEFAULT_PARAM_VALIDATION_MODE;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\ntype PlainObject = Record<string, unknown>;\n\n/**\n * The only strings read as type names. Byte-identical to `paramSchemaOf`'s\n * `PRIMITIVE_TYPES` (`@qontinui/ui-bridge-wrapper` `helpers/schema.ts`) —\n * KEEP IN SYNC. Anything else is prose.\n */\nconst PRIMITIVE_TYPE_NAMES = new Set([\n 'string',\n 'number',\n 'integer',\n 'boolean',\n 'object',\n 'array',\n 'null',\n]);\n\n/**\n * Top-level keys that a JSON Schema *object* schema may legitimately carry\n * alongside `type: \"object\"`. Used only by the form-1/form-2 disambiguation\n * (see the module header): a top level whose non-`type` keys are all in here\n * is an object schema; anything else is a map of param declarations.\n *\n * Deliberately generous — it lists keywords this validator does not implement\n * (`oneOf`, `patternProperties`, `$defs`, …) because the question it answers\n * is \"was this written as a schema?\", not \"can we enforce it?\". A schema\n * richer than the subset must still be *recognised* as a schema, or its\n * `properties` would be re-read as param names.\n */\nconst OBJECT_SCHEMA_KEYWORDS = new Set([\n 'type',\n 'properties',\n 'required',\n 'additionalProperties',\n 'patternProperties',\n 'propertyNames',\n 'minProperties',\n 'maxProperties',\n 'dependentRequired',\n 'dependentSchemas',\n 'unevaluatedProperties',\n 'allOf',\n 'anyOf',\n 'oneOf',\n 'not',\n 'if',\n 'then',\n 'else',\n 'enum',\n 'const',\n 'default',\n 'examples',\n 'title',\n 'description',\n 'deprecated',\n 'readOnly',\n 'writeOnly',\n '$schema',\n '$id',\n '$ref',\n '$comment',\n '$defs',\n 'definitions',\n]);\n\n/**\n * Depth ceiling for the recursive schema walk. A schema nested deeper than\n * this expresses no further constraint here (ignore-don't-reject), which is\n * what keeps a self-referential `paramSchema` from becoming a `RangeError` at\n * the invocation seam.\n *\n * 32 is far past any hand-written declaration — the deepest real `paramSchema`\n * in the fleet is 2 levels — and far short of a default stack.\n */\nconst MAX_SCHEMA_DEPTH = 32;\n\n/**\n * Longest `pattern` this validator will compile, and the companion\n * nested-quantifier test below.\n *\n * A `pattern` is author-supplied but it is evaluated against caller-supplied\n * strings, so `(a+)+$` against a long non-matching input is a hang, not an\n * exception — unrescuable by `try`/`catch` because the engine never returns.\n * The pair (length cap + nested-quantifier reject) is the same guard\n * `control/action-executor.ts` applies to a caller-supplied `srcPattern`; over\n * the cap or on a risky construct the keyword is IGNORED, exactly like an\n * uncompilable pattern.\n */\nconst MAX_PATTERN_LENGTH = 200;\n\n/**\n * Nested-quantifier detector — a group that itself contains a quantifier and\n * is then quantified (`(a+)+`, `(a*)*`, `(?:a{2,})+`), the classic\n * catastrophic-backtracking shape.\n *\n * A deliberate duplicate of `@qontinui/ui-bridge` `control/action-executor.ts`\n * `hasNestedQuantifiers`: this module imports nothing so that `src/native/*`\n * and the standalone React Native package can share it (see the header), while\n * `action-executor.ts` pulls in the DOM. KEEP THE COPIES IN SYNC.\n */\nfunction hasNestedQuantifiers(pattern: string): boolean {\n return /(\\((?:[^()]*[+*}])[^()]*\\))[+*?]|\\(\\?:[^()]*[+*}][^()]*\\)[+*?]/.test(pattern);\n}\n\nfunction isPlainObject(value: unknown): value is PlainObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** `Object.prototype.hasOwnProperty`, safely — see {@link hasOwn}. */\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\n\n/**\n * Own-property test.\n *\n * **Not `key in value`.** `in` walks the prototype chain, so a param named\n * `toString`, `valueOf`, `constructor` or `hasOwnProperty` is never reported\n * missing (it \"exists\" on `Object.prototype`) and is then validated against\n * `Object.prototype`'s member instead of the caller's value — a required\n * param that was never supplied silently passes, and a type constraint on it\n * is checked against a function. `standardActionEffect` already spells this\n * correctly; this is the same rule.\n */\nfunction hasOwn(value: object, key: string): boolean {\n return hasOwnProperty.call(value, key);\n}\n\n/** Compact rendering of a supplied value, for the `received` field. */\nfunction render(value: unknown): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (Array.isArray(value)) return `array(${value.length})`;\n const t = typeof value;\n if (t === 'string') return JSON.stringify(value);\n if (t === 'number' || t === 'boolean') return String(value);\n if (t === 'object') return 'object';\n return t;\n}\n\n/** Longest JSON rendering {@link renderJson} will emit before truncating. */\nconst MAX_RENDERED_JSON_LENGTH = 120;\n\n/**\n * Rendering for `enum` / `const`, where the *identity* of the value is the\n * whole message.\n *\n * {@link render} collapses every object to the literal string `\"object\"`,\n * which turns a `const` violation into **\"must equal object, received\n * object.\"** — a message that tells an agent nothing, in a module whose stated\n * purpose is that \"invalid params\" tells an agent nothing. So these two\n * keywords render structurally instead, capped so a large schema value cannot\n * blow up a log line.\n */\nfunction renderJson(value: unknown): string {\n if (value === undefined) return 'undefined';\n let text: string;\n try {\n text = JSON.stringify(value) ?? String(value);\n } catch {\n // Cyclic or a BigInt — fall back to the compact rendering.\n return render(value);\n }\n return text.length > MAX_RENDERED_JSON_LENGTH\n ? `${text.slice(0, MAX_RENDERED_JSON_LENGTH)}…`\n : text;\n}\n\n/** The JSON Schema type name a runtime value would be reported as. */\nfunction jsonTypeOf(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n const t = typeof value;\n if (t === 'number') return Number.isInteger(value) ? 'integer' : 'number';\n if (t === 'string' || t === 'boolean' || t === 'object') return t;\n return t;\n}\n\nfunction matchesType(value: unknown, typeName: string): boolean {\n switch (typeName) {\n case 'string':\n return typeof value === 'string';\n case 'number':\n return typeof value === 'number' && Number.isFinite(value);\n case 'integer':\n return typeof value === 'number' && Number.isInteger(value);\n case 'boolean':\n return typeof value === 'boolean';\n case 'null':\n return value === null;\n case 'array':\n return Array.isArray(value);\n case 'object':\n return isPlainObject(value);\n default:\n // Unrecognised type name — outside the subset, so it constrains nothing.\n return true;\n }\n}\n\n/**\n * Structural equality for JSON `enum` / `const` members.\n *\n * **Not `JSON.stringify(a) === JSON.stringify(b)`.** That comparison is\n * key-order sensitive, so `const: { a: 1, b: 2 }` would reject `{ b: 2, a: 1 }`\n * — two renderings of the same JSON value. Object key order is not semantic in\n * JSON, and the caller does not control it (it is whatever the producing\n * serializer emitted), so an order-sensitive test produces a false failure on\n * a value that IS the declared constant.\n *\n * Depth-bounded for the same reason the schema walk is: a cyclic `const` in a\n * hand-built (non-JSON) schema would otherwise recurse forever. Past the\n * bound the two are treated as unequal rather than throwing.\n */\nfunction jsonEquals(a: unknown, b: unknown, depth = 0): boolean {\n if (a === b) return true;\n if (depth > MAX_SCHEMA_DEPTH) return false;\n if (a === null || b === null || a === undefined || b === undefined) return false;\n if (typeof a !== typeof b) return false;\n if (typeof a !== 'object') return false;\n\n const aIsArray = Array.isArray(a);\n if (aIsArray !== Array.isArray(b)) return false;\n if (aIsArray) {\n const bArr = b as unknown[];\n const aArr = a as unknown[];\n if (aArr.length !== bArr.length) return false;\n return aArr.every((item, i) => jsonEquals(item, bArr[i], depth + 1));\n }\n\n const aObj = a as PlainObject;\n const bObj = b as PlainObject;\n const aKeys = Object.keys(aObj);\n const bKeys = Object.keys(bObj);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) => hasOwn(bObj, key) && jsonEquals(aObj[key], bObj[key], depth + 1));\n}\n\nfunction joinPath(base: string, key: string): string {\n return base === '' ? key : `${base}.${key}`;\n}\n\n/**\n * Read a `type` keyword into the list of accepted type names, or `null` when\n * the keyword is absent or shaped outside the subset (in which case it\n * constrains nothing).\n */\nfunction readTypes(schema: PlainObject): string[] | null {\n const raw = schema['type'];\n if (typeof raw === 'string') return [raw];\n if (Array.isArray(raw)) {\n const names = raw.filter((t): t is string => typeof t === 'string');\n return names.length > 0 ? names : null;\n }\n return null;\n}\n\n/** Validate one value against one property schema, appending to `issues`. */\nfunction validateValue(\n value: unknown,\n schema: unknown,\n path: string,\n issues: ParamSchemaIssue[],\n depth: number\n): void {\n // Depth bound — a self-referential `paramSchema` would otherwise recurse to\n // `RangeError`, which the seam can only report as a HANDLER failure. Past\n // the bound the schema expresses no further constraint. See MAX_SCHEMA_DEPTH.\n if (depth > MAX_SCHEMA_DEPTH) return;\n\n // A property schema that is not an object expresses no constraint. A bare\n // PRIMITIVE type-name string IS a constraint though, and is how map form\n // spells one; any other string is prose (see the module header).\n if (typeof schema === 'string') {\n if (PRIMITIVE_TYPE_NAMES.has(schema) && !matchesType(value, schema)) {\n issues.push({\n path,\n keyword: 'type',\n expected: schema,\n received: render(value),\n message: `Parameter \"${path}\" must be of type ${schema}, received ${jsonTypeOf(value)} (${render(value)}).`,\n });\n }\n return;\n }\n if (!isPlainObject(schema)) return;\n\n const types = readTypes(schema);\n if (types !== null && !types.some((t) => matchesType(value, t))) {\n const expected = types.join(' | ');\n issues.push({\n path,\n keyword: 'type',\n expected,\n received: render(value),\n message: `Parameter \"${path}\" must be of type ${expected}, received ${jsonTypeOf(value)} (${render(value)}).`,\n });\n // A wrong-typed value cannot meaningfully fail the type-specific keywords\n // below; reporting those too would bury the one issue that matters.\n return;\n }\n\n if (hasOwn(schema, 'const') && !jsonEquals(value, schema['const'])) {\n // `renderJson`, not `render`: the identity of the value IS the message\n // here, and `render` would collapse both sides to the word \"object\".\n const expected = renderJson(schema['const']);\n const received = renderJson(value);\n issues.push({\n path,\n keyword: 'const',\n expected,\n received,\n message: `Parameter \"${path}\" must equal ${expected}, received ${received}.`,\n });\n return;\n }\n\n const enumValues = schema['enum'];\n if (Array.isArray(enumValues) && !enumValues.some((allowed) => jsonEquals(value, allowed))) {\n const expected = enumValues.map((v) => renderJson(v)).join(', ');\n const received = renderJson(value);\n issues.push({\n path,\n keyword: 'enum',\n expected,\n received,\n message: `Parameter \"${path}\" must be one of [${expected}], received ${received}.`,\n });\n return;\n }\n\n if (typeof value === 'number') {\n const min = schema['minimum'];\n if (typeof min === 'number' && value < min) {\n issues.push({\n path,\n keyword: 'minimum',\n expected: `>= ${min}`,\n received: render(value),\n message: `Parameter \"${path}\" must be >= ${min}, received ${value}.`,\n });\n }\n const max = schema['maximum'];\n if (typeof max === 'number' && value > max) {\n issues.push({\n path,\n keyword: 'maximum',\n expected: `<= ${max}`,\n received: render(value),\n message: `Parameter \"${path}\" must be <= ${max}, received ${value}.`,\n });\n }\n }\n\n if (typeof value === 'string') {\n const minLength = schema['minLength'];\n if (typeof minLength === 'number' && value.length < minLength) {\n issues.push({\n path,\n keyword: 'minLength',\n expected: `length >= ${minLength}`,\n received: `length ${value.length}`,\n message: `Parameter \"${path}\" must be at least ${minLength} characters, received ${value.length}.`,\n });\n }\n const maxLength = schema['maxLength'];\n if (typeof maxLength === 'number' && value.length > maxLength) {\n issues.push({\n path,\n keyword: 'maxLength',\n expected: `length <= ${maxLength}`,\n received: `length ${value.length}`,\n message: `Parameter \"${path}\" must be at most ${maxLength} characters, received ${value.length}.`,\n });\n }\n const pattern = schema['pattern'];\n // ReDoS guard, BEFORE compilation: a catastrophically-backtracking pattern\n // does not throw, it hangs — no `try`/`catch` can rescue it, so the only\n // defence is not to run it. Same pair of tests `action-executor.ts` applies\n // to a caller-supplied `srcPattern`. Over the cap, or a nested-quantifier\n // construct, and the keyword is ignored like an uncompilable one.\n if (\n typeof pattern === 'string' &&\n pattern.length <= MAX_PATTERN_LENGTH &&\n !hasNestedQuantifiers(pattern)\n ) {\n // Declared WITHOUT an initializer on purpose: both arms below assign\n // before any read, so an initializer here would be dead (eslint\n // `no-useless-assignment`). The regex carries no `g` flag and is rebuilt\n // per call, so there is no `lastIndex` state to carry between values.\n let re: RegExp | null;\n try {\n re = new RegExp(pattern);\n } catch {\n // An uncompilable pattern expresses no constraint we can read. Ignored,\n // never a failure — same rule as an unrecognised keyword.\n re = null;\n }\n if (re !== null && !re.test(value)) {\n issues.push({\n path,\n keyword: 'pattern',\n expected: `match /${pattern}/`,\n received: render(value),\n message: `Parameter \"${path}\" must match /${pattern}/, received ${render(value)}.`,\n });\n }\n }\n }\n\n if (Array.isArray(value) && hasOwn(schema, 'items')) {\n const items = schema['items'];\n value.forEach((element, index) => {\n validateValue(element, items, `${path}[${index}]`, issues, depth + 1);\n });\n }\n\n if (\n isPlainObject(value) &&\n (isPlainObject(schema['properties']) ||\n hasOwn(schema, 'required') ||\n schema['additionalProperties'] === false)\n ) {\n validateObject(value, schema, path, issues, depth);\n }\n}\n\n/**\n * Validate a plain object against the `properties` / `required` /\n * `additionalProperties` triple. Shared by the top level and by nested\n * `type: \"object\"` properties.\n */\nfunction validateObject(\n value: PlainObject,\n schema: PlainObject,\n basePath: string,\n issues: ParamSchemaIssue[],\n depth: number\n): void {\n if (depth > MAX_SCHEMA_DEPTH) return;\n\n const properties = isPlainObject(schema['properties']) ? schema['properties'] : {};\n const requiredRaw = schema['required'];\n const required = Array.isArray(requiredRaw)\n ? requiredRaw.filter((k): k is string => typeof k === 'string')\n : [];\n\n for (const key of required) {\n // `hasOwn`, not `key in value` — see `hasOwn`. With `in`, a required param\n // named `toString` is never reported missing.\n if (!hasOwn(value, key) || value[key] === undefined) {\n const at = joinPath(basePath, key);\n issues.push({\n path: at,\n keyword: 'required',\n expected: 'present',\n message: `Required parameter \"${at}\" is missing.`,\n });\n }\n }\n\n if (schema['additionalProperties'] === false) {\n const declared = new Set(Object.keys(properties));\n // With no `properties` at all the schema says \"no params are accepted\" —\n // say that, rather than the empty \"one of []\" the generic phrasing gives.\n const expected = declared.size === 0 ? 'no parameters' : `one of [${[...declared].join(', ')}]`;\n for (const key of Object.keys(value)) {\n if (!declared.has(key)) {\n const at = joinPath(basePath, key);\n issues.push({\n path: at,\n keyword: 'additionalProperties',\n expected,\n received: render(value[key]),\n message: `Parameter \"${at}\" is not declared by the schema, which sets additionalProperties: false.`,\n });\n }\n }\n }\n\n for (const [key, propSchema] of Object.entries(properties)) {\n // `hasOwn`, not `key in value` — with `in`, a declared param named\n // `valueOf` would be \"present\" and validated against `Object.prototype`'s\n // function instead of the caller's (absent) value.\n if (!hasOwn(value, key) || value[key] === undefined) continue;\n validateValue(value[key], propSchema, joinPath(basePath, key), issues, depth + 1);\n }\n}\n\n/**\n * The form-1/form-2 disambiguation, in one place. See the module header for\n * the rule and for why the `type === 'object'` half is not enough on its own.\n *\n * @returns `true` for object-schema form (form 1), `false` for map form.\n */\nfunction isObjectSchemaForm(schema: PlainObject): boolean {\n if (isPlainObject(schema['properties'])) return true;\n if (schema['type'] !== 'object') return false;\n // `type: \"object\"` with NO `properties`. Object schema only if everything\n // else present is a schema keyword; a single non-keyword key (`id`,\n // `username`, …) means this is a map that happens to declare a param called\n // `type`, and reading it as a schema would silently drop every sibling\n // declaration in it.\n return Object.keys(schema).every((key) => OBJECT_SCHEMA_KEYWORDS.has(key));\n}\n\n/**\n * Normalise map form into the object-schema form so one walker serves both.\n * Returns `null` when `schema` is already object form.\n */\nfunction normalizeMapForm(schema: PlainObject): PlainObject | null {\n if (isObjectSchemaForm(schema)) return null;\n\n const properties: PlainObject = {};\n for (const [key, raw] of Object.entries(schema)) {\n if (raw === null || raw === undefined) continue;\n if (typeof raw === 'string') {\n // Type name, or prose? Only the seven primitive names are types. Every\n // other string is a human hint — ~50 live declarations in\n // `qontinui-runner` are exactly that — and expresses no constraint.\n properties[key] = PRIMITIVE_TYPE_NAMES.has(raw) ? { type: raw } : {};\n continue;\n }\n if (isPlainObject(raw)) {\n // `optional` is this monorepo's non-standard marker, not JSON Schema.\n // Nothing in map form is required, so it only needs stripping.\n const { optional: _optional, ...rest } = raw as PlainObject & { optional?: unknown };\n properties[key] = rest;\n continue;\n }\n // A value that is neither a type name nor a schema object expresses no\n // constraint — ignored, per the unrecognised-keyword rule.\n }\n // NO `required`: map form cannot express requiredness. See the module header.\n return { type: 'object', properties };\n}\n\n/**\n * Validate `params` against an action's `paramSchema`.\n *\n * Returns `{ valid: true, issues: [] }` for anything the documented subset\n * cannot read — that is the ignore-don't-reject rule, and it is why a schema\n * this validator does not understand can never fail an invocation.\n *\n * @param schema the action's declared `paramSchema` (any shape; unreadable\n * shapes constrain nothing)\n * @param params the params the caller supplied. `undefined` is treated as an\n * empty object, so a schema with required properties still reports them.\n */\nexport function validateActionParams(\n schema: unknown,\n params: unknown\n): ParamValidationResult {\n if (!isPlainObject(schema)) return { valid: true, issues: [] };\n\n const normalized = normalizeMapForm(schema) ?? schema;\n\n const hasProperties = isPlainObject(normalized['properties']);\n const hasRequired = Array.isArray(normalized['required']);\n // `additionalProperties: false` is a constraint ON ITS OWN, even with no\n // `properties` key: `{ type: 'object', additionalProperties: false }` is the\n // natural way to spell \"this action takes no params at all\", and the keyword\n // table above documents it. Omitting it from this test made that schema\n // accept every payload — the same silent-drop defect as the map-form one.\n const forbidsExtras = normalized['additionalProperties'] === false;\n if (!hasProperties && !hasRequired && !forbidsExtras) {\n // e.g. a bare `{ type: \"object\" }`. No constraint expressed.\n //\n // (The map-form branch cannot reach here: `normalizeMapForm` always\n // returns a `properties` object, empty or not — so `hasProperties` is\n // true for every map form, including one whose every entry was prose.)\n return { valid: true, issues: [] };\n }\n\n const issues: ParamSchemaIssue[] = [];\n\n if (params === undefined || params === null) {\n validateObject({}, normalized, '', issues, 0);\n return { valid: issues.length === 0, issues };\n }\n\n if (!isPlainObject(params)) {\n issues.push({\n path: '',\n keyword: 'type',\n expected: 'object',\n received: render(params),\n message: `Action params must be an object, received ${jsonTypeOf(params)} (${render(params)}).`,\n });\n return { valid: false, issues };\n }\n\n validateObject(params, normalized, '', issues, 0);\n return { valid: issues.length === 0, issues };\n}\n\n/**\n * One-line summary of a violation set, naming the action and every offending\n * param. Used as the `error` / `message` string at every invocation seam so\n * the three trees phrase the rejection identically.\n */\nexport function formatParamValidationFailure(\n componentId: string,\n actionId: string,\n issues: ParamSchemaIssue[]\n): string {\n const detail = issues.map((i) => i.message).join(' ');\n return `Action \"${actionId}\" on component \"${componentId}\" was rejected: params do not match the action's declared paramSchema. ${detail}`;\n}\n","/**\n * Native Action Executor\n *\n * Executes actions on registered native elements and components.\n * In React Native, we execute actions by calling prop handlers directly\n * (onPress, onChangeText, etc.) rather than simulating DOM events.\n */\n\nimport type { NativeUIBridgeRegistry } from '../core/registry';\nimport type {\n NativeStandardAction,\n NativeFindRequest,\n NativeFindResponse,\n DiscoveredNativeElement,\n WaitOptions,\n} from '../core/types';\nimport { findElementByIdentifier } from '../core/element-identifier';\nimport { pageRectOf, isEmptyRect, intersectRects } from '../core/registry';\nimport type { NativePageRect } from '../core/registry';\n// Phase 3 (plan 2026-08-20-ui-bridge-action-declaration-shape). A justified\n// duplicate of the web SDK's primitive — see the header of `core/abortable.ts`\n// for why this package cannot import it.\nimport { runAbortable, normalizeActionTimeoutMs, inertAbortSignal } from '../core/abortable';\n// Phase 2 (same plan). A justified duplicate of the web SDK's validator — see\n// the header of `core/param-schema.ts` for why this package cannot import it.\nimport {\n validateActionParams,\n formatParamValidationFailure,\n getDefaultParamValidationMode,\n} from '../core/param-schema';\nimport type {\n ControlActionRequest,\n ControlActionResponse,\n ComponentActionRequest,\n ComponentActionResponse,\n ComponentActionInvokeOptions,\n WaitResult,\n NativeActionExecutor,\n NativeActionEvent,\n NativeActionListener,\n TypeActionParams,\n ScrollActionParams,\n ScrollIntoViewActionParams,\n SwipeActionParams,\n PressActionParams,\n} from './types';\n\n/**\n * Default wait options\n */\nconst DEFAULT_WAIT_OPTIONS: Required<WaitOptions> = {\n visible: true,\n enabled: true,\n focused: false,\n state: {},\n timeout: 10000,\n interval: 100,\n};\n\n/**\n * An action failure that carries a machine-readable code of its own, so the\n * HTTP handler can report it as that code instead of the generic\n * `ACTION_FAILED`. Used for `NOT_SUPPORTED` — \"this element cannot do that\",\n * which a caller must not retry with a different request shape.\n */\nexport class NativeActionError extends Error {\n constructor(\n message: string,\n readonly code: string\n ) {\n super(message);\n this.name = 'NativeActionError';\n }\n}\n\n/** Default gap (logical dp) left above an element scrolled into view. */\nconst DEFAULT_SCROLL_INTO_VIEW_PADDING = 16;\n\n/** Upper bound on waiting for `measureLayout` to call back. */\nconst SCROLL_INTO_VIEW_MEASURE_TIMEOUT_MS = 1000;\n\n/** The slice of a React Native `ScrollView` instance `scrollIntoView` drives. */\ninterface ScrollContainerRef {\n scrollTo: (options: { x?: number; y?: number; animated?: boolean }) => void;\n getInnerViewRef?: () => unknown;\n getInnerViewNode?: () => unknown;\n /** VirtualizedList family (FlatList / SectionList): the underlying ScrollView. */\n getNativeScrollRef?: () => unknown;\n getScrollResponder?: () => unknown;\n}\n\n/** Upper bound on the targeted `measureInWindow` used for the visibility check. */\nconst SCROLL_INTO_VIEW_WINDOW_MEASURE_TIMEOUT_MS = 250;\n\ntype WindowMeasurable = {\n measureInWindow?: (cb: (x: number, y: number, w: number, h: number) => void) => void;\n};\n\n/**\n * The page rect of one registered element, measured NOW where its ref allows.\n *\n * A ref with `measureInWindow` is measured directly (bounded); a timeout,\n * throw, or zero-size answer is UNKNOWN (`null`). A ref without it (a test\n * fixture, a non-host wrapper) falls back to the registry's stored layout —\n * the same evidence the snapshot route would report for it.\n */\nfunction measurePageRectNow(\n element: NonNullable<ReturnType<NativeUIBridgeRegistry['getElement']>>,\n preferred?: unknown\n): Promise<NativePageRect | null> {\n // `preferred` lets a caller measure a better node than the registered ref —\n // the ScrollView a FlatList wraps, whose class instance has no measureInWindow.\n const preferredNode = preferred as WindowMeasurable | null | undefined;\n const node =\n preferredNode && typeof preferredNode.measureInWindow === 'function'\n ? preferredNode\n : (element.ref.current as unknown as WindowMeasurable | null);\n if (!node || typeof node.measureInWindow !== 'function') {\n const state = element.getState();\n return Promise.resolve(state.visible ? pageRectOf(state) : null);\n }\n return new Promise((resolve) => {\n let settled = false;\n const done = (rect: NativePageRect | null) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(rect);\n };\n const timer = setTimeout(() => done(null), SCROLL_INTO_VIEW_WINDOW_MEASURE_TIMEOUT_MS);\n try {\n node.measureInWindow!((x, y, w, h) =>\n done(\n [x, y, w, h].every(Number.isFinite) && w > 0 && h > 0\n ? { left: x, top: y, right: x + w, bottom: y + h }\n : null\n )\n );\n } catch {\n done(null);\n }\n });\n}\n\n/** The slice of a React Native host view `scrollIntoView` measures with. */\ninterface MeasurableRef {\n measureLayout: (\n relativeTo: unknown,\n onSuccess: (x: number, y: number, width: number, height: number) => void,\n onFail?: () => void\n ) => void;\n}\n\n/**\n * The ScrollView-shaped object behind a container ref: the ref itself when it\n * has `scrollTo`, else the ScrollView a VirtualizedList (FlatList /\n * SectionList) wraps. `null` when neither yields one.\n */\nfunction resolveScrollView(\n current: unknown\n): (Partial<ScrollContainerRef> & Pick<ScrollContainerRef, 'scrollTo'>) | null {\n const c = current as Partial<ScrollContainerRef> | null | undefined;\n if (!c) return null;\n if (typeof c.scrollTo === 'function')\n return c as Partial<ScrollContainerRef> & Pick<ScrollContainerRef, 'scrollTo'>;\n for (const unwrap of [c.getNativeScrollRef, c.getScrollResponder]) {\n if (typeof unwrap !== 'function') continue;\n let inner: Partial<ScrollContainerRef> | null | undefined;\n try {\n inner = unwrap.call(c) as Partial<ScrollContainerRef> | null | undefined;\n } catch {\n inner = null;\n }\n if (inner && typeof inner.scrollTo === 'function')\n return inner as Partial<ScrollContainerRef> & Pick<ScrollContainerRef, 'scrollTo'>;\n }\n return null;\n}\n\n/**\n * Is the container visibly declared `horizontal`? Checks captured registry\n * props, then a class-component ref's own `props` (FlatList / SectionList\n * instances), then the resolved ScrollView's. See the LIMIT note at the call\n * site — an invisible declaration cannot be detected.\n */\nfunction isDeclaredHorizontal(\n registeredProps: Record<string, unknown> | undefined,\n ...refs: unknown[]\n): boolean {\n if (registeredProps?.horizontal === true) return true;\n return refs.some((r) => {\n const props = (r as { props?: { horizontal?: unknown } } | null | undefined)?.props;\n return props !== null && typeof props === 'object' && props.horizontal === true;\n });\n}\n\n/**\n * Sleep for a duration\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Default native action executor implementation\n */\nexport class DefaultNativeActionExecutor implements NativeActionExecutor {\n private actionListeners = new Set<NativeActionListener>();\n\n constructor(private registry: NativeUIBridgeRegistry) {}\n\n onActionExecuted(listener: NativeActionListener): () => void {\n this.actionListeners.add(listener);\n return () => {\n this.actionListeners.delete(listener);\n };\n }\n\n /** Notify listeners out-of-band; isolate each invocation so one bad listener can't poison the rest. */\n private emitActionEvent(event: NativeActionEvent): void {\n for (const listener of this.actionListeners) {\n try {\n listener(event);\n } catch (error) {\n console.warn(`[ui-bridge-native] Action listener error:`, error);\n }\n }\n }\n\n /**\n * Execute an action on an element\n */\n async executeAction(\n elementId: string,\n request: ControlActionRequest\n ): Promise<ControlActionResponse> {\n const startTime = Date.now();\n let waitDurationMs = 0;\n\n try {\n // Find the element\n let registered = this.registry.getElement(elementId);\n\n // If not in registry, try by identifier\n if (!registered) {\n registered = findElementByIdentifier(elementId) ?? undefined;\n }\n\n if (!registered) {\n const failTime = Date.now();\n const errorMsg = `Element not found: ${elementId}`;\n this.emitActionEvent({\n elementId,\n action: request.action,\n params: request.params,\n success: false,\n error: errorMsg,\n timestamp: failTime,\n requestId: request.requestId,\n durationMs: failTime - startTime,\n });\n return {\n success: false,\n error: errorMsg,\n durationMs: failTime - startTime,\n timestamp: failTime,\n requestId: request.requestId,\n };\n }\n\n // Wait for conditions if specified\n if (request.waitOptions) {\n const waitResult = await this.waitForElementInternal(registered.id, request.waitOptions);\n waitDurationMs = waitResult.waitedMs;\n if (!waitResult.met) {\n const failTime = Date.now();\n const errorMsg = waitResult.error || 'Wait condition not met';\n this.emitActionEvent({\n elementId,\n action: request.action,\n params: request.params,\n success: false,\n error: errorMsg,\n timestamp: failTime,\n requestId: request.requestId,\n durationMs: failTime - startTime,\n });\n return {\n success: false,\n error: errorMsg,\n durationMs: failTime - startTime,\n timestamp: failTime,\n requestId: request.requestId,\n waitDurationMs,\n };\n }\n }\n\n // Execute the action\n const result = await this.performAction(registered, request.action, request.params);\n\n const successTime = Date.now();\n this.emitActionEvent({\n elementId,\n action: request.action,\n params: request.params,\n success: true,\n timestamp: successTime,\n requestId: request.requestId,\n durationMs: successTime - startTime,\n });\n // Re-fetch the element from the registry rather than reusing the\n // `registered` reference captured before `performAction`. Mutating\n // actions (`type`/`setValue`/`clear`/`focus`/...) call\n // `registry.updateElementState`, which REPLACES the map entry with a\n // fresh object carrying a new `getState` closure — the captured\n // `registered.getState` is stale and omits the just-set `value`. Without\n // this re-fetch the action response's `elementState` disagrees with a\n // subsequent `GET /control/element/:id` and `/control/snapshot`, which is\n // the inconsistent read-path the manual-test session flagged.\n const fresh = this.registry.getElement(registered.id) ?? registered;\n return {\n success: true,\n elementState: fresh.getState(),\n result,\n durationMs: successTime - startTime,\n timestamp: successTime,\n requestId: request.requestId,\n waitDurationMs,\n };\n } catch (error) {\n const failTime = Date.now();\n const errorMsg = error instanceof Error ? error.message : String(error);\n this.emitActionEvent({\n elementId,\n action: request.action,\n params: request.params,\n success: false,\n error: errorMsg,\n timestamp: failTime,\n requestId: request.requestId,\n durationMs: failTime - startTime,\n });\n return {\n success: false,\n error: errorMsg,\n ...(error instanceof NativeActionError ? { code: error.code } : {}),\n stack: error instanceof Error ? error.stack : undefined,\n durationMs: failTime - startTime,\n timestamp: failTime,\n requestId: request.requestId,\n waitDurationMs,\n };\n }\n }\n\n /**\n * Perform an action on an element\n */\n private async performAction(\n element: ReturnType<NativeUIBridgeRegistry['getElement']>,\n action: NativeStandardAction | string,\n params?: Record<string, unknown>\n ): Promise<unknown> {\n if (!element) {\n throw new Error('Element not found');\n }\n\n const props = element.props || {};\n\n // Check for custom action first\n if (element.customActions && action in element.customActions) {\n // The options bag is ALWAYS supplied — `ActionHandlerOptions` promises\n // it, and a handler written the documented way (`(params, { signal }) =>\n // …`) throws on `undefined`. An element action carries no cancellation\n // source, so the signal is inert; see `inertAbortSignal`.\n return element.customActions[action].handler(params, { signal: inertAbortSignal() });\n }\n\n // Execute standard actions\n switch (action) {\n case 'click': // Web UI Bridge compatibility alias\n case 'press':\n return this.performPress(props, params as PressActionParams | undefined);\n\n case 'longPress':\n return this.performLongPress(props, params as PressActionParams | undefined);\n\n case 'doubleTap':\n return this.performDoubleTap(props);\n\n case 'type':\n return this.performType(element, props, params as unknown as TypeActionParams);\n\n case 'setValue':\n return this.performSetValue(element, props, params as unknown as TypeActionParams);\n\n case 'clear':\n return this.performClear(element, props);\n\n case 'focus':\n return this.performFocus(element);\n\n case 'blur':\n return this.performBlur(element);\n\n case 'scroll':\n return this.performScroll(props, params as ScrollActionParams | undefined);\n\n case 'scrollIntoView':\n return this.performScrollIntoView(\n element,\n params as ScrollIntoViewActionParams | undefined\n );\n\n case 'swipe':\n return this.performSwipe(props, params as unknown as SwipeActionParams);\n\n case 'toggle':\n return this.performToggle(props);\n\n default:\n throw new Error(`Unknown action: ${action}`);\n }\n }\n\n /**\n * Perform press action\n */\n private async performPress(\n props: Record<string, unknown>,\n params?: PressActionParams\n ): Promise<void> {\n // Try different press handlers in order of preference\n const handlers = ['onPress', 'onPressIn', 'onResponderRelease'];\n\n for (const handler of handlers) {\n if (typeof props[handler] === 'function') {\n // Create a synthetic event object\n const event = this.createPressEvent(params);\n (props[handler] as (event: unknown) => void)(event);\n return;\n }\n }\n\n throw new Error('No press handler found on element');\n }\n\n /**\n * Perform long press action\n */\n private async performLongPress(\n props: Record<string, unknown>,\n params?: PressActionParams\n ): Promise<void> {\n if (typeof props.onLongPress === 'function') {\n const event = this.createPressEvent(params);\n (props.onLongPress as (event: unknown) => void)(event);\n return;\n }\n\n throw new Error('No long press handler found on element');\n }\n\n /**\n * Perform double tap action\n */\n private async performDoubleTap(props: Record<string, unknown>): Promise<void> {\n // First try dedicated double tap handler\n if (typeof props.onDoubleTap === 'function') {\n (props.onDoubleTap as () => void)();\n return;\n }\n\n // Fall back to calling press twice\n if (typeof props.onPress === 'function') {\n const event = this.createPressEvent();\n (props.onPress as (event: unknown) => void)(event);\n await sleep(50);\n (props.onPress as (event: unknown) => void)(event);\n return;\n }\n\n throw new Error('No press handler found for double tap');\n }\n\n /**\n * Perform type action\n */\n private async performType(\n element: ReturnType<NativeUIBridgeRegistry['getElement']>,\n props: Record<string, unknown>,\n params: TypeActionParams\n ): Promise<void> {\n if (!params?.text) {\n throw new Error('Type action requires text parameter');\n }\n\n // Clear first if requested. Accept either `clearFirst` (native SDK\n // convention) or `clear` (runner UI Bridge convention) — so agents\n // using the same action across platforms don't hit a naming mismatch.\n if (params.clearFirst || params.clear) {\n await this.performClear(element, props);\n }\n\n // Type character by character if delay specified\n if (params.delay && params.delay > 0) {\n const currentValue = (element?.getState().value || '') as string;\n for (const char of params.text) {\n const newValue = currentValue + char;\n if (typeof props.onChangeText === 'function') {\n (props.onChangeText as (text: string) => void)(newValue);\n }\n await sleep(params.delay);\n }\n } else {\n // Type all at once\n if (typeof props.onChangeText === 'function') {\n (props.onChangeText as (text: string) => void)(params.text);\n } else if (typeof props.onChange === 'function') {\n (props.onChange as (event: { nativeEvent: { text: string } }) => void)({\n nativeEvent: { text: params.text },\n });\n } else {\n throw new Error('No text change handler found on element');\n }\n }\n\n // Update element state\n if (element) {\n this.registry.updateElementState(element.id, { value: params.text });\n }\n }\n\n /**\n * Perform setValue action.\n *\n * Web/runner UI Bridge parity: `setValue` replaces the input's value\n * atomically in a single call (no per-character keystroke simulation),\n * mirroring the web bridge's `setValue` semantics. On native there is no\n * DOM value to assign, so we drive the controlled-input handler\n * (`onChangeText`, falling back to `onChange`) with the full string. The\n * registry's `state.value` is then synced so a subsequent GET on the\n * element reflects the new value.\n *\n * Unlike `type`, this does not append to existing content and ignores\n * `delay` — it is the deterministic \"set the field to exactly this\" action.\n */\n private async performSetValue(\n element: ReturnType<NativeUIBridgeRegistry['getElement']>,\n props: Record<string, unknown>,\n params: TypeActionParams\n ): Promise<void> {\n // `text` is the canonical key; accept `value` as an alias so callers using\n // the web bridge's `{ value }` body shape work without translation.\n const next =\n typeof params?.text === 'string'\n ? params.text\n : typeof (params as { value?: unknown })?.value === 'string'\n ? ((params as { value?: string }).value as string)\n : undefined;\n\n if (typeof next !== 'string') {\n throw new Error('setValue action requires a string \"text\" (or \"value\") parameter');\n }\n\n if (typeof props.onChangeText === 'function') {\n (props.onChangeText as (text: string) => void)(next);\n } else if (typeof props.onChange === 'function') {\n (props.onChange as (event: { nativeEvent: { text: string } }) => void)({\n nativeEvent: { text: next },\n });\n } else {\n throw new Error('No text change handler found on element');\n }\n\n if (element) {\n this.registry.updateElementState(element.id, { value: next });\n }\n }\n\n /**\n * Perform clear action\n */\n private async performClear(\n element: ReturnType<NativeUIBridgeRegistry['getElement']>,\n props: Record<string, unknown>\n ): Promise<void> {\n if (typeof props.onChangeText === 'function') {\n (props.onChangeText as (text: string) => void)('');\n } else if (typeof props.onChange === 'function') {\n (props.onChange as (event: { nativeEvent: { text: string } }) => void)({\n nativeEvent: { text: '' },\n });\n }\n\n // Update element state\n if (element) {\n this.registry.updateElementState(element.id, { value: '' });\n }\n }\n\n /**\n * Perform focus action\n */\n private async performFocus(\n element: ReturnType<NativeUIBridgeRegistry['getElement']>\n ): Promise<void> {\n if (element?.ref.current && 'focus' in element.ref.current) {\n (element.ref.current as { focus: () => void }).focus();\n }\n\n // Update element state\n if (element) {\n this.registry.updateElementState(element.id, { focused: true });\n }\n }\n\n /**\n * Perform blur action\n */\n private async performBlur(\n element: ReturnType<NativeUIBridgeRegistry['getElement']>\n ): Promise<void> {\n if (element?.ref.current && 'blur' in element.ref.current) {\n (element.ref.current as { blur: () => void }).blur();\n }\n\n // Update element state\n if (element) {\n this.registry.updateElementState(element.id, { focused: false });\n }\n }\n\n /**\n * Perform scroll action\n */\n private async performScroll(\n props: Record<string, unknown>,\n params?: ScrollActionParams\n ): Promise<void> {\n if (typeof props.onScroll === 'function') {\n const event = {\n nativeEvent: {\n contentOffset: params?.offset || { x: 0, y: 0 },\n },\n };\n (props.onScroll as (event: unknown) => void)(event);\n }\n }\n\n /**\n * Perform scrollIntoView: scroll the element's DECLARED scroll container so\n * the element sits `padding` dp below the top of its viewport.\n *\n * The container comes from `scrollAncestorId` and nowhere else — the\n * registry keeps no parent chain (`RegisterElementOptions.scrollAncestorId`),\n * so there is no \"nearest ScrollView\" to find. Nothing here tracks a\n * ScrollView's content offset either (`performScroll` only fakes an\n * `onScroll` event), so the target is measured CONTENT-relative with\n * `measureLayout` against the container's inner content view: stateless,\n * and immune to a stale-offset race.\n *\n * Result shape mirrors the web executor's `scrollIntoView`\n * (`@qontinui/ui-bridge` `control/action-executor.ts`):\n * `{ alreadyVisible, scrolled }`, short-circuiting when the element is\n * already fully inside its clip region. \"Fully visible\" is only claimed when\n * it is MEASURED — a page-space rect for the element and a known,\n * non-empty clip (window ∩ declared container), both measured at call\n * time. Anything unknown scrolls.\n *\n * Containers: a ScrollView, or a VirtualizedList (FlatList / SectionList)\n * unwrapped through `getNativeScrollRef()` / `getScrollResponder()`.\n * Vertical only — a `horizontal` container is NOT_SUPPORTED. The action\n * returns once `scrollTo` is dispatched; positions settle on the next frame.\n *\n * Prerequisites are checked BEFORE the visibility short-circuit, so whether\n * the element supports the action does not depend on where it happens to be\n * on screen. Each missing prerequisite is a `NOT_SUPPORTED` failure.\n */\n private async performScrollIntoView(\n element: NonNullable<ReturnType<NativeUIBridgeRegistry['getElement']>>,\n params?: ScrollIntoViewActionParams\n ): Promise<{ alreadyVisible: boolean; scrolled: boolean }> {\n const padding = params?.padding ?? DEFAULT_SCROLL_INTO_VIEW_PADDING;\n if (typeof padding !== 'number' || !Number.isFinite(padding) || padding < 0) {\n throw new Error('scrollIntoView \"padding\" must be a finite, non-negative number');\n }\n\n const ancestorId = element.scrollAncestorId;\n if (!ancestorId) {\n throw new NativeActionError(\n `scrollIntoView is not supported on element \"${element.id}\": it declares no scrollAncestorId, ` +\n 'and the registry keeps no parent chain to find its scroll container from',\n 'NOT_SUPPORTED'\n );\n }\n const ancestor = ancestorId === element.id ? undefined : this.registry.getElement(ancestorId);\n if (!ancestor) {\n throw new NativeActionError(\n `scrollIntoView is not supported on element \"${element.id}\": its declared scrollAncestorId ` +\n `\"${ancestorId}\" is not a registered element`,\n 'NOT_SUPPORTED'\n );\n }\n const container = resolveScrollView(ancestor.ref.current);\n if (!container) {\n throw new NativeActionError(\n `scrollIntoView is not supported on element \"${element.id}\": its scroll ancestor ` +\n `\"${ancestorId}\" has no scrollTo (not a mounted ScrollView, and no ` +\n 'getNativeScrollRef/getScrollResponder yielding one)',\n 'NOT_SUPPORTED'\n );\n }\n if (isDeclaredHorizontal(ancestor.props, ancestor.ref.current, container)) {\n // `scrollTo` here moves only y; RN sends `x || 0`, so on a horizontal\n // ScrollView this would jump the content to its start and still report\n // success. Refuse rather than lie.\n //\n // LIMIT: detected only where `horizontal` is visible to the bridge —\n // captured props (`captureProps({ horizontal: true })` / a direct\n // `registerElement` with `props`) or a class-component ref carrying\n // `props.horizontal` (FlatList / SectionList instances). A horizontal\n // ScrollView registered through `useUIElement` without captured props\n // is NOT detected; the action is documented as vertical-only.\n throw new NativeActionError(\n `scrollIntoView is not supported on element \"${element.id}\": its scroll ancestor ` +\n `\"${ancestorId}\" is horizontal (only vertical containers are supported)`,\n 'NOT_SUPPORTED'\n );\n }\n // `getInnerViewRef()` returns a host instance on both architectures and is\n // the primary path. `getInnerViewNode()` returns a numeric tag, which only\n // the legacy architecture's `measureLayout` accepts (Fabric ignores it and\n // never calls back — the timeout below then names that cause).\n const innerViewRef = container.getInnerViewRef?.();\n const innerView = innerViewRef ?? container.getInnerViewNode?.();\n const innerViewIsTag = innerViewRef == null && typeof innerView === 'number';\n if (innerView === undefined || innerView === null) {\n throw new NativeActionError(\n `scrollIntoView is not supported on element \"${element.id}\": its scroll ancestor ` +\n `\"${ancestorId}\" exposes no inner content view to measure against`,\n 'NOT_SUPPORTED'\n );\n }\n const target = element.ref.current as unknown as Partial<MeasurableRef> | null;\n if (!target || typeof target.measureLayout !== 'function') {\n throw new NativeActionError(\n `scrollIntoView is not supported on element \"${element.id}\": its ref has no measureLayout ` +\n '(not a mounted host view)',\n 'NOT_SUPPORTED'\n );\n }\n\n // Already-visible short-circuit — only on MEASURED evidence: the target\n // AND its declared container are both measured now (targeted, bounded —\n // not a registry-wide sweep), and the target lies fully inside\n // window ∩ container. An unknown container rect must NOT fall back to the\n // window alone: a row below a short ScrollView's bottom edge is still\n // inside the window. Anything unknown scrolls.\n const [rect, ancestorRect] = await Promise.all([\n measurePageRectNow(element),\n measurePageRectNow(ancestor, container),\n ]);\n // With no viewport provider the window is UNKNOWN and only the container\n // bounds the check — the same answer `getClipRectFor` gives.\n const viewport = this.registry.getViewportRect();\n const clip = ancestorRect ? intersectRects(viewport, ancestorRect) : null;\n if (\n rect &&\n clip &&\n !isEmptyRect(clip) &&\n rect.right > rect.left &&\n rect.bottom > rect.top &&\n rect.top >= clip.top &&\n rect.left >= clip.left &&\n rect.bottom <= clip.bottom &&\n rect.right <= clip.right\n ) {\n return { alreadyVisible: true, scrolled: false };\n }\n\n const measured = await new Promise<{ x: number; y: number; width: number }>(\n (resolve, reject) => {\n let settled = false;\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n reject(\n new Error(\n `scrollIntoView: measureLayout did not call back within ${SCROLL_INTO_VIEW_MEASURE_TIMEOUT_MS}ms` +\n (innerViewIsTag\n ? ' (measured against a numeric inner-view tag from getInnerViewNode, which the new architecture does not support)'\n : '')\n )\n );\n }, SCROLL_INTO_VIEW_MEASURE_TIMEOUT_MS);\n const finish = (fn: () => void) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n fn();\n };\n try {\n target.measureLayout!(\n innerView,\n (x, y, width) =>\n finish(() =>\n Number.isFinite(y)\n ? resolve({ x, y, width })\n : reject(new Error('scrollIntoView: measureLayout returned a non-finite y'))\n ),\n () =>\n finish(() =>\n reject(\n new Error(\n `scrollIntoView: measureLayout failed for \"${element.id}\" relative to \"${ancestorId}\"`\n )\n )\n )\n );\n } catch (error) {\n finish(() => reject(error));\n }\n }\n );\n\n // Undeclared-horizontal backstop: content laid out sideways past the\n // container's measured width means scrolling only y cannot reach it (and\n // RN's `x || 0` would reset the x offset). Refuse rather than report a\n // scroll that did not bring the element into view. Skipped when the\n // container width is unknown.\n if (\n ancestorRect &&\n Number.isFinite(measured.x) &&\n Number.isFinite(measured.width) &&\n measured.x + measured.width > ancestorRect.right - ancestorRect.left + 1\n ) {\n throw new NativeActionError(\n `scrollIntoView is not supported on element \"${element.id}\": it is laid out beyond the ` +\n `horizontal extent of its scroll ancestor \"${ancestorId}\" (sideways overflow, a horizontal ` +\n 'container, or a nested horizontal container — declare that inner container as its ' +\n 'scrollAncestorId); only vertical scrolling is supported',\n 'NOT_SUPPORTED'\n );\n }\n\n container.scrollTo({ y: Math.max(0, measured.y - padding), animated: false });\n return { alreadyVisible: false, scrolled: true };\n }\n\n /**\n * Perform swipe action\n */\n private async performSwipe(\n props: Record<string, unknown>,\n params: SwipeActionParams\n ): Promise<void> {\n if (!params?.direction) {\n throw new Error('Swipe action requires direction parameter');\n }\n\n // Try dedicated swipe handlers\n const handlerMap: Record<string, string> = {\n left: 'onSwipeLeft',\n right: 'onSwipeRight',\n up: 'onSwipeUp',\n down: 'onSwipeDown',\n };\n\n const handler = handlerMap[params.direction];\n if (handler && typeof props[handler] === 'function') {\n (props[handler] as () => void)();\n return;\n }\n\n // Fall back to generic swipe handler\n if (typeof props.onSwipe === 'function') {\n (props.onSwipe as (direction: string) => void)(params.direction);\n }\n }\n\n /**\n * Perform toggle action\n */\n private async performToggle(props: Record<string, unknown>): Promise<void> {\n // For Switch components\n if (typeof props.onValueChange === 'function') {\n const currentValue = props.value as boolean;\n (props.onValueChange as (value: boolean) => void)(!currentValue);\n return;\n }\n\n // Fall back to press\n if (typeof props.onPress === 'function') {\n (props.onPress as () => void)();\n return;\n }\n\n throw new Error('No toggle handler found on element');\n }\n\n /**\n * Create a synthetic press event\n */\n private createPressEvent(params?: PressActionParams): object {\n return {\n nativeEvent: {\n locationX: params?.position?.x ?? 0,\n locationY: params?.position?.y ?? 0,\n timestamp: Date.now(),\n },\n persist: () => {},\n };\n }\n\n /**\n * Execute a component action\n */\n async executeComponentAction(\n componentId: string,\n request: ComponentActionRequest,\n options: ComponentActionInvokeOptions = {}\n ): Promise<ComponentActionResponse> {\n const startTime = Date.now();\n\n try {\n const component = this.registry.getComponent(componentId);\n\n if (!component) {\n return {\n success: false,\n error: `Component not found: ${componentId}`,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n }\n\n // Find the action\n const action = component.actions.find((a) => a.id === request.action);\n\n if (!action) {\n return {\n success: false,\n error: `Action not found: ${request.action}`,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n }\n\n // `timeoutMs` is WIRE data — the HTTP entry point forwards it verbatim\n // from a JSON body — so it is validated and clamped here, before it can\n // reach a timer. See `normalizeActionTimeoutMs` for the policy (0\n // abandons on the next tick; negative / NaN / non-numeric are refused;\n // anything above 24h is clamped, because past 2^31-1 `setTimeout` wraps\n // negative and fires immediately).\n const timeout = normalizeActionTimeoutMs(request.timeoutMs);\n if (!timeout.ok) {\n return {\n success: false,\n error: `Action \"${request.action}\" on component \"${componentId}\" was rejected: ${timeout.reason}.`,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n }\n const timeoutMs = timeout.timeoutMs;\n\n // Phase 2: `paramSchema` is published to agents, so it has to mean\n // something. Validate BEFORE the handler runs — a rejection after a\n // side-effect is not a rejection.\n const validationMode = options.paramValidation ?? getDefaultParamValidationMode();\n if (validationMode !== 'off' && action.paramSchema !== undefined) {\n // The validator walks AUTHOR-supplied schema data. It is bounded\n // against both known fault routes (`param-schema.ts` MAX_SCHEMA_DEPTH /\n // MAX_PATTERN_LENGTH), but it is wrapped anyway: without this, a\n // validator fault would fall into the generic catch below and be\n // reported as a HANDLER failure, for a handler that never ran.\n let validation: ReturnType<typeof validateActionParams>;\n try {\n validation = validateActionParams(action.paramSchema, request.params);\n } catch (validatorFault) {\n const detail =\n validatorFault instanceof Error ? validatorFault.message : String(validatorFault);\n const faultMessage = `Action \"${request.action}\" on component \"${componentId}\": its declared paramSchema could not be evaluated (${detail}).`;\n if (validationMode === 'enforce') {\n // A gate that cannot be evaluated has not been cleared. The prose\n // names the SCHEMA, so it is not mistaken for a handler fault.\n return {\n success: false,\n error: faultMessage,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n }\n console.warn(`[ui-bridge-native] ${faultMessage}`);\n validation = { valid: true, issues: [] };\n }\n if (!validation.valid) {\n const message = formatParamValidationFailure(\n componentId,\n request.action,\n validation.issues\n );\n if (validationMode === 'enforce') {\n // Prose `error` rather than structured `failureDetails` — this\n // tree's response type has no such field. See the note on\n // `ComponentActionInvokeOptions.paramValidation`.\n return {\n success: false,\n error: message,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n }\n console.warn(`[ui-bridge-native] ${message}`);\n }\n }\n\n // The handler is *given* a signal (cooperative cancellation) and is\n // *raced* against it (enforced abandonment) — a handler that ignores its\n // signal must still be abandonable at the caller. Phase 3 of plan\n // 2026-08-20-ui-bridge-action-declaration-shape.\n const outcome = await runAbortable((signal) => action.handler(request.params, { signal }), {\n signal: options.signal,\n timeoutMs,\n });\n\n if (outcome.aborted) {\n // NOTE: this tree's `ComponentActionResponse` has no `failureDetails`\n // field and this package ships no diagnostics module, so the\n // cancellation surfaces as a prose `error` rather than through\n // `buildActionFailureDetails('UB-ACTION-FAILED', ...)` the way the web\n // seam does. Giving the native channel structured failure details is a\n // separate change, not Phase 3.\n return {\n success: false,\n error:\n outcome.reason === 'timeout'\n ? `Action \"${request.action}\" on component \"${componentId}\" was abandoned after its ${timeoutMs}ms timeout elapsed.`\n : `Action \"${request.action}\" on component \"${componentId}\" was cancelled by the caller's abort signal.`,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n }\n\n return {\n success: true,\n result: outcome.result,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n requestId: request.requestId,\n };\n }\n }\n\n /**\n * Find elements\n */\n async find(request: NativeFindRequest): Promise<NativeFindResponse> {\n const startTime = Date.now();\n\n const allElements = this.registry.getAllElements();\n let filtered = allElements;\n\n // Filter by type\n if (request.types && request.types.length > 0) {\n filtered = filtered.filter((e) => request.types!.includes(e.type));\n }\n\n // Filter by testID pattern\n if (request.testIdPattern) {\n const regex = new RegExp(request.testIdPattern.replace(/\\*/g, '.*').replace(/\\?/g, '.'));\n filtered = filtered.filter((e) => {\n const identifier = e.getIdentifier();\n return identifier.testId && regex.test(identifier.testId);\n });\n }\n\n // Filter by accessibility label pattern\n if (request.accessibilityLabelPattern) {\n const regex = new RegExp(\n request.accessibilityLabelPattern.replace(/\\*/g, '.*').replace(/\\?/g, '.')\n );\n filtered = filtered.filter((e) => {\n const identifier = e.getIdentifier();\n return identifier.accessibilityLabel && regex.test(identifier.accessibilityLabel);\n });\n }\n\n // Filter by visibility\n if (request.visibleOnly) {\n filtered = filtered.filter((e) => e.getState().visible);\n }\n\n // Apply limit\n if (request.limit && request.limit > 0) {\n filtered = filtered.slice(0, request.limit);\n }\n\n // Map to discovered elements\n const elements: DiscoveredNativeElement[] = filtered.map((e) => ({\n id: e.id,\n type: e.type,\n identifier: e.getIdentifier(),\n state: e.getState(),\n actions: e.actions,\n label: e.label,\n }));\n\n return {\n elements,\n total: elements.length,\n durationMs: Date.now() - startTime,\n timestamp: Date.now(),\n };\n }\n\n /**\n * Wait for element conditions\n */\n async waitForElement(elementId: string, options: WaitOptions): Promise<WaitResult> {\n return this.waitForElementInternal(elementId, options);\n }\n\n /**\n * Internal wait implementation\n */\n private async waitForElementInternal(\n elementId: string,\n options: WaitOptions\n ): Promise<WaitResult> {\n const opts = { ...DEFAULT_WAIT_OPTIONS, ...options };\n const startTime = Date.now();\n\n while (Date.now() - startTime < opts.timeout) {\n const element = this.registry.getElement(elementId);\n\n if (!element) {\n await sleep(opts.interval);\n continue;\n }\n\n const state = element.getState();\n\n // Check conditions\n let conditionsMet = true;\n\n if (opts.visible && !state.visible) {\n conditionsMet = false;\n }\n\n if (opts.enabled && !state.enabled) {\n conditionsMet = false;\n }\n\n if (opts.focused && !state.focused) {\n conditionsMet = false;\n }\n\n // Check custom state conditions\n if (opts.state && Object.keys(opts.state).length > 0) {\n const stateRecord = state as unknown as Record<string, unknown>;\n for (const [key, value] of Object.entries(opts.state)) {\n if (stateRecord[key] !== value) {\n conditionsMet = false;\n break;\n }\n }\n }\n\n if (conditionsMet) {\n return {\n met: true,\n waitedMs: Date.now() - startTime,\n state,\n };\n }\n\n await sleep(opts.interval);\n }\n\n return {\n met: false,\n waitedMs: Date.now() - startTime,\n error: `Timeout waiting for conditions on element: ${elementId}`,\n };\n }\n}\n\n/**\n * Create a native action executor\n */\nexport function createNativeActionExecutor(registry: NativeUIBridgeRegistry): NativeActionExecutor {\n return new DefaultNativeActionExecutor(registry);\n}\n"]}
|