@qontinui/ui-bridge-native 0.9.0 → 0.11.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/README.md +50 -2
- package/dist/core/index.js +13 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +13 -3
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/sha256.d.ts +29 -7
- package/dist/core/sha256.d.ts.map +1 -1
- package/dist/debug/index.js.map +1 -1
- package/dist/debug/index.mjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +988 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +978 -33
- package/dist/index.mjs.map +1 -1
- package/dist/react/UIBridgeNativeProvider.d.ts +25 -3
- package/dist/react/UIBridgeNativeProvider.d.ts.map +1 -1
- package/dist/react/index.js +959 -31
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +960 -32
- package/dist/react/index.mjs.map +1 -1
- package/dist/server/handlers.d.ts.map +1 -1
- package/dist/server/http-server.d.ts +120 -2
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +926 -15
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +908 -16
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/pairing.d.ts +295 -0
- package/dist/server/pairing.d.ts.map +1 -0
- package/dist/server/types.d.ts +20 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/transport/DeviceAnnouncer.d.ts +0 -2
- package/dist/transport/DeviceAnnouncer.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/core/sha256.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SHA-256
|
|
2
|
+
* SHA-256 and HMAC-SHA256, synchronous and dependency-free.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Two consumers. `ElementMedia.src_digest`, the digest of an image's source URL
|
|
5
|
+
* that vision-core compares to find duplicate images (qontinui-schemas
|
|
6
|
+
* `rust-vision-core/src/element_media.rs`, `src_digest_of`). And the device
|
|
7
|
+
* pairing signature (`server/pairing.ts`), which the runner's Rust signer has
|
|
8
|
+
* to reproduce byte for byte. Every producer must compute the SAME digest for
|
|
9
|
+
* the same input, so this is real SHA-256 — pinned against the NIST vectors and
|
|
10
|
+
* the cross-repo shared vector in its tests — not a stand-in hash.
|
|
10
11
|
*
|
|
11
12
|
* Why not a platform API: React Native's Hermes engine ships no WebCrypto
|
|
12
13
|
* (`crypto.subtle`) and no Node `crypto`, and the snapshot projection that
|
|
@@ -14,6 +15,27 @@
|
|
|
14
15
|
* falls back to a non-cryptographic FNV hash when neither API exists, which
|
|
15
16
|
* would silently disagree with every other producer.
|
|
16
17
|
*/
|
|
18
|
+
/**
|
|
19
|
+
* UTF-8 encode, by hand rather than through `TextEncoder` (not on every Hermes
|
|
20
|
+
* build). A lone surrogate becomes U+FFFD, exactly as `TextEncoder` and Rust's
|
|
21
|
+
* lossy conversion do, so a malformed string still digests the same everywhere.
|
|
22
|
+
*/
|
|
23
|
+
export declare function utf8Bytes(input: string): Uint8Array;
|
|
24
|
+
/** SHA-256 of raw bytes, as the 32-byte digest. */
|
|
25
|
+
export declare function sha256Bytes(bytes: Uint8Array): Uint8Array;
|
|
26
|
+
/** Lowercase hex of raw bytes. */
|
|
27
|
+
export declare function bytesToHex(bytes: Uint8Array): string;
|
|
17
28
|
/** Lowercase hex SHA-256 of `input`'s UTF-8 bytes. */
|
|
18
29
|
export declare function sha256Hex(input: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* HMAC-SHA256 over raw bytes (RFC 2104), as the 32-byte tag. Bytes rather than
|
|
32
|
+
* strings because a signer in another language has to agree on them exactly.
|
|
33
|
+
*/
|
|
34
|
+
export declare function hmacSha256(key: Uint8Array, message: Uint8Array): Uint8Array;
|
|
35
|
+
/**
|
|
36
|
+
* Compare two strings without an early exit on the first differing character.
|
|
37
|
+
* A length difference does return at once: every caller compares against a
|
|
38
|
+
* fixed-width value whose width is public.
|
|
39
|
+
*/
|
|
40
|
+
export declare function constantTimeEqual(a: string, b: string): boolean;
|
|
19
41
|
//# sourceMappingURL=sha256.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sha256.d.ts","sourceRoot":"","sources":["../../src/core/sha256.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sha256.d.ts","sourceRoot":"","sources":["../../src/core/sha256.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAmBH;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CA+BnD;AAID,mDAAmD;AACnD,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAkDzD;AAED,kCAAkC;AAClC,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAIpD;AAED,sDAAsD;AACtD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAKD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,GAAG,UAAU,CAY3E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAK/D"}
|
package/dist/debug/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/design-handlers.ts","../../src/react/UIBridgeNativeProvider.tsx","../../src/debug/inspector.tsx"],"names":["createContext","use","jsxs","TouchableOpacity","View","jsx","Text","ScrollView","Fragment","useState","useMemo","useCallback","Modal","Dimensions","StyleSheet"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * UI Bridge Native Design Review Handlers\n *\n * HTTP handlers for the 8 design review endpoints.\n * Maps registered element styles to the web SDK's design data format.\n */\n\nimport type { NativeUIBridgeRegistry } from '../core/registry';\nimport type { APIResponse, HandlerContext } from './types';\nimport type { ElementDesignData, StateStyles, ResponsiveSnapshot } from '../design/design-types';\nimport {\n mapNativeStyleToExtended,\n getNativeElementDesignData,\n captureNativeStateVariations,\n captureNativeResponsiveSnapshot,\n} from '../design/design-inspector-native';\n\n// Style guide and baseline stored in closure (same pattern as web SDK)\nlet loadedStyleGuide: unknown = null;\nlet savedBaseline: unknown = null;\n\n// Try to load the style validator from the web SDK (optional peer dep)\nlet styleValidator: {\n runStyleAudit: (\n elements: Array<{ elementId: string; styles: Record<string, string> }>,\n guide: unknown\n ) => unknown;\n} | null = null;\n\n// Try to load quality evaluator from the web SDK (optional peer dep)\nlet qualityEvaluator: {\n evaluateQuality: (\n elements: ElementDesignData[],\n viewport: { width: number; height: number },\n context?: unknown\n ) => unknown;\n listContexts: () => Array<{ name: string; description: string }>;\n createBaseline: (\n elements: ElementDesignData[],\n viewport: { width: number; height: number },\n label?: string\n ) => unknown;\n diffSnapshots: (baseline: unknown, current: ElementDesignData[]) => unknown;\n} | null = null;\n\ntry {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const specs = require('@qontinui/ui-bridge/specs');\n styleValidator = specs;\n qualityEvaluator = specs;\n} catch {\n // Optional dependency not installed — audit/evaluate endpoints will return an error\n}\n\nfunction success<T>(data: T): APIResponse<T> {\n return { success: true, data, timestamp: Date.now() };\n}\n\nfunction error<T = unknown>(message: string, code?: string): APIResponse<T> {\n return { success: false, error: message, code, timestamp: Date.now() };\n}\n\n/**\n * Design handler interface (subset of NativeServerHandlers)\n */\nexport interface NativeDesignHandlers {\n getElementStyles: (ctx: HandlerContext) => Promise<APIResponse<ElementDesignData>>;\n getElementStateStyles: (\n ctx: HandlerContext\n ) => Promise<APIResponse<{ elementId: string; stateStyles: StateStyles[] }>>;\n getDesignSnapshot: (\n ctx: HandlerContext\n ) => Promise<APIResponse<{ elements: ElementDesignData[]; timestamp: number }>>;\n getResponsiveSnapshots: (ctx: HandlerContext) => Promise<APIResponse<ResponsiveSnapshot[]>>;\n runDesignAudit: (ctx: HandlerContext) => Promise<APIResponse<unknown>>;\n loadStyleGuide: (ctx: HandlerContext) => Promise<APIResponse<{ loaded: boolean }>>;\n getStyleGuide: (ctx: HandlerContext) => Promise<APIResponse<unknown>>;\n clearStyleGuide: (ctx: HandlerContext) => Promise<APIResponse<{ cleared: boolean }>>;\n evaluateQuality: (ctx: HandlerContext) => Promise<APIResponse<unknown>>;\n getQualityContexts: (\n ctx: HandlerContext\n ) => Promise<APIResponse<Array<{ name: string; description: string }>>>;\n saveBaseline: (\n ctx: HandlerContext\n ) => Promise<APIResponse<{ saved: boolean; elementCount: number }>>;\n diffBaseline: (ctx: HandlerContext) => Promise<APIResponse<unknown>>;\n}\n\n/**\n * Create design review handlers.\n *\n * @param registry - the registered native element registry\n * @param viewportProvider - optional injected viewport getter. Same shape as\n * the `viewportProvider` consumed by `handlers.ts`/`getPageHealth`. This is\n * injected by `createServerHandlers` from the `NativeServerConfig` the host\n * passes to `UIBridgeNativeProvider`/`NativeUIBridgeServer`, which in turn\n * reads `Dimensions.get('screen')` (or `'window'`). The indirection exists\n * because `require('react-native')` from this file crashes the host RN\n * app the moment any design endpoint is invoked: Metro/Hermes raise\n * `unknownModuleError` past every try/catch and tear down the JS thread.\n * This was the same latent crash that took out 0.6.3/0.6.4 page-health\n * handler — the design handlers had it too, just hiding on unused\n * endpoints until `POST /design/responsive` was first called.\n *\n * When the injected provider is absent (tests, custom hosts) the design\n * handlers fall back to `{width:0, height:0}` — the responsive snapshot\n * degenerates to \"zero-dimension viewport\" rather than crashing.\n */\nexport function createDesignHandlers(\n registry: NativeUIBridgeRegistry,\n viewportProvider?: () => { width: number; height: number }\n): NativeDesignHandlers {\n // Helper to get screen dimensions via the injected provider.\n //\n // The 0.6.3/0.6.4 implementation called `require('react-native')` here\n // inside a try/catch; Metro/Hermes still surfaced `unknownModuleError`\n // up the stack and tore down the JS thread the moment any design endpoint\n // was touched. The fix is to take the viewport as an injected dependency\n // (parity with `getPageHealth`) so this file has no `react-native` dep\n // at all — see the doc comment on `createDesignHandlers` above and the\n // project memory entry `feedback_metro_require_gotcha.md`.\n function getScreenDimensions(): { width: number; height: number } {\n return viewportProvider?.() ?? { width: 0, height: 0 };\n }\n\n return {\n // 1. GET /design/element/:id/styles\n getElementStyles: async (ctx) => {\n const { id } = ctx.params;\n const element = registry.getElement(id);\n\n if (!element) {\n return error('Element not found: ' + id, 'ELEMENT_NOT_FOUND');\n }\n\n const state = element.getState();\n const data = getNativeElementDesignData(\n id,\n element.label,\n element.type,\n element.flatStyle,\n state.layout\n );\n\n return success(data);\n },\n\n // 2. POST /design/element/:id/state-styles\n getElementStateStyles: async (ctx) => {\n const { id } = ctx.params;\n const element = registry.getElement(id);\n\n if (!element) {\n return error('Element not found: ' + id, 'ELEMENT_NOT_FOUND');\n }\n\n const stateStyles = captureNativeStateVariations(element.flatStyle, element.stateStyles);\n\n return success({ elementId: id, stateStyles });\n },\n\n // 3. POST /design/snapshot\n getDesignSnapshot: async (ctx) => {\n const body = ctx.body as\n | {\n elementIds?: string[];\n includePseudoElements?: boolean;\n }\n | undefined;\n\n let elements = registry.getAllElements();\n\n // Filter to requested IDs if specified\n if (body?.elementIds && body.elementIds.length > 0) {\n const idSet = new Set(body.elementIds);\n elements = elements.filter((e) => idSet.has(e.id));\n }\n\n const designData: ElementDesignData[] = elements.map((el) => {\n const state = el.getState();\n return getNativeElementDesignData(el.id, el.label, el.type, el.flatStyle, state.layout);\n });\n\n return success({ elements: designData, timestamp: Date.now() });\n },\n\n // 4. POST /design/responsive\n getResponsiveSnapshots: async () => {\n // Platform limitation: RN cannot constrain screen width at runtime.\n // Returns a single snapshot at current device dimensions.\n const elements = registry.getAllElements();\n const elementData = elements.map((el) => {\n const state = el.getState();\n return {\n id: el.id,\n label: el.label,\n type: el.type,\n flatStyle: el.flatStyle,\n layout: state.layout,\n };\n });\n\n const snapshots = captureNativeResponsiveSnapshot(elementData, getScreenDimensions());\n return success(snapshots);\n },\n\n // 5. POST /design/audit\n runDesignAudit: async (ctx) => {\n const body = ctx.body as\n | {\n guide?: unknown;\n elementIds?: string[];\n }\n | undefined;\n\n const guide = body?.guide ?? loadedStyleGuide;\n\n if (!guide) {\n return error('No style guide loaded or provided', 'NO_STYLE_GUIDE');\n }\n\n if (!styleValidator) {\n return error(\n 'Install @qontinui/ui-bridge for style audit support. ' +\n 'Add it as a dependency: npm install @qontinui/ui-bridge',\n 'VALIDATOR_NOT_AVAILABLE'\n );\n }\n\n let elements = registry.getAllElements();\n if (body?.elementIds && body.elementIds.length > 0) {\n const idSet = new Set(body.elementIds);\n elements = elements.filter((e) => idSet.has(e.id));\n }\n\n // Map elements to the format expected by the validator\n const auditElements = elements.map((el) => ({\n elementId: el.id,\n styles: mapNativeStyleToExtended(el.flatStyle) as unknown as Record<string, string>,\n }));\n\n try {\n const report = styleValidator.runStyleAudit(auditElements, guide);\n return success(report);\n } catch (err) {\n return error(\n 'Audit failed: ' + (err instanceof Error ? err.message : String(err)),\n 'DESIGN_AUDIT_ERROR'\n );\n }\n },\n\n // 6. POST /design/style-guide/load\n loadStyleGuide: async (ctx) => {\n const body = ctx.body as { guide?: unknown } | undefined;\n\n if (!body?.guide) {\n return error('Style guide is required in request body', 'INVALID_REQUEST');\n }\n\n loadedStyleGuide = body.guide;\n return success({ loaded: true });\n },\n\n // 7. GET /design/style-guide\n getStyleGuide: async () => {\n return success(loadedStyleGuide);\n },\n\n // 8. DELETE /design/style-guide\n clearStyleGuide: async () => {\n loadedStyleGuide = null;\n return success({ cleared: true });\n },\n\n // 9. POST /design/evaluate\n evaluateQuality: async (ctx) => {\n if (!qualityEvaluator) {\n return error(\n 'Install @qontinui/ui-bridge for quality evaluation support. ' +\n 'Add it as a dependency: npm install @qontinui/ui-bridge',\n 'EVALUATOR_NOT_AVAILABLE'\n );\n }\n\n const body = ctx.body as\n | {\n context?: string;\n customContext?: unknown;\n elementIds?: string[];\n viewport?: { width: number; height: number };\n }\n | undefined;\n\n let elements = registry.getAllElements();\n if (body?.elementIds && body.elementIds.length > 0) {\n const idSet = new Set(body.elementIds);\n elements = elements.filter((e) => idSet.has(e.id));\n }\n\n const designData: ElementDesignData[] = elements.map((el) => {\n const state = el.getState();\n return getNativeElementDesignData(el.id, el.label, el.type, el.flatStyle, state.layout);\n });\n\n const viewport = body?.viewport ?? getScreenDimensions();\n\n // Resolve context: custom object > style guide context > built-in name\n let context: unknown = body?.customContext ?? body?.context ?? 'general';\n if (typeof context === 'string' && loadedStyleGuide) {\n const guide = loadedStyleGuide as { qualityContexts?: Record<string, unknown> };\n if (guide.qualityContexts?.[context]) {\n context = guide.qualityContexts[context];\n }\n }\n\n try {\n const report = qualityEvaluator.evaluateQuality(designData, viewport, context);\n return success(report);\n } catch (err) {\n return error(\n 'Evaluation failed: ' + (err instanceof Error ? err.message : String(err)),\n 'QUALITY_EVALUATION_ERROR'\n );\n }\n },\n\n // 10. GET /design/evaluate/contexts\n getQualityContexts: async () => {\n if (!qualityEvaluator) {\n return error(\n 'Install @qontinui/ui-bridge for quality evaluation support.',\n 'EVALUATOR_NOT_AVAILABLE'\n );\n }\n return success(qualityEvaluator.listContexts());\n },\n\n // 11. POST /design/evaluate/baseline\n saveBaseline: async (ctx) => {\n if (!qualityEvaluator) {\n return error(\n 'Install @qontinui/ui-bridge for quality evaluation support.',\n 'EVALUATOR_NOT_AVAILABLE'\n );\n }\n\n const body = ctx.body as\n | {\n label?: string;\n elementIds?: string[];\n }\n | undefined;\n\n let elements = registry.getAllElements();\n if (body?.elementIds && body.elementIds.length > 0) {\n const idSet = new Set(body.elementIds);\n elements = elements.filter((e) => idSet.has(e.id));\n }\n\n const designData: ElementDesignData[] = elements.map((el) => {\n const state = el.getState();\n return getNativeElementDesignData(el.id, el.label, el.type, el.flatStyle, state.layout);\n });\n\n const viewport = getScreenDimensions();\n savedBaseline = qualityEvaluator.createBaseline(designData, viewport, body?.label);\n return success({ saved: true, elementCount: designData.length });\n },\n\n // 12. POST /design/evaluate/diff\n diffBaseline: async (ctx) => {\n if (!qualityEvaluator) {\n return error(\n 'Install @qontinui/ui-bridge for quality evaluation support.',\n 'EVALUATOR_NOT_AVAILABLE'\n );\n }\n\n if (!savedBaseline) {\n return error('No baseline saved. Call saveBaseline first.', 'NO_BASELINE');\n }\n\n const body = ctx.body as { elementIds?: string[] } | undefined;\n\n let elements = registry.getAllElements();\n if (body?.elementIds && body.elementIds.length > 0) {\n const idSet = new Set(body.elementIds);\n elements = elements.filter((e) => idSet.has(e.id));\n }\n\n const designData: ElementDesignData[] = elements.map((el) => {\n const state = el.getState();\n return getNativeElementDesignData(el.id, el.label, el.type, el.flatStyle, state.layout);\n });\n\n try {\n const report = qualityEvaluator.diffSnapshots(savedBaseline, designData);\n return success(report);\n } catch (err) {\n return error(\n 'Diff failed: ' + (err instanceof Error ? err.message : String(err)),\n 'DIFF_BASELINE_ERROR'\n );\n }\n },\n };\n}\n","/**\n * UI Bridge Native Provider\n *\n * React context provider for UI Bridge Native functionality.\n */\n\nimport React, {\n createContext,\n use,\n useMemo,\n useEffect,\n useCallback,\n useRef,\n useState,\n} from 'react';\nimport { Dimensions, PixelRatio } from 'react-native';\nimport type {\n NativeUIBridgeFeatures,\n NativeUIBridgeConfig,\n RegisteredNativeElement,\n RegisteredNativeComponent,\n NativeBridgeSnapshot,\n BridgeEventType,\n BridgeEventListener,\n} from '../core/types';\nimport { NativeUIBridgeRegistry, setGlobalRegistry, resetGlobalRegistry } from '../core/registry';\nimport { createNativeActionExecutor } from '../control/action-executor';\nimport type { NativeActionExecutor } from '../control/types';\nimport {\n createNativeServer,\n type NativeUIBridgeServer,\n type ServerAdapter,\n type WebSocketServerAdapter,\n} from '../server/http-server';\nimport type { RouteProvider, KeepAwakeProvider } from '../server/types';\nimport { WebSocketEventBridge } from '../server/ws-event-bridge';\nimport { DeviceAnnouncer, type ZeroconfConstructor } from '../transport/DeviceAnnouncer';\nimport { CloudRelayClient, type CloudRelayConfig } from '../transport/CloudRelayClient';\nimport { ModalDetector } from '../modal/modal-detector';\nimport { ToastCapture } from '../toast/toast-capture';\nimport { UndoTracker } from '../undo/undo-tracker';\n\n/**\n * UI Bridge Native context value\n */\nexport interface UIBridgeNativeContextValue {\n /** Feature flags */\n features: NativeUIBridgeFeatures;\n /** Configuration */\n config: NativeUIBridgeConfig;\n /** Element registry */\n registry: NativeUIBridgeRegistry;\n /** Action executor */\n executor: NativeActionExecutor;\n /** Modal stack tracker (declarative pushModal/dismissModal) */\n modalDetector: ModalDetector;\n /** Toast/snackbar tracker (declarative recordToast/dismissToast) */\n toastCapture: ToastCapture;\n /** Undo/redo action-correlation tracker */\n undoTracker: UndoTracker;\n /** Get all registered elements */\n getElements: () => RegisteredNativeElement[];\n /** Get all registered components */\n getComponents: () => RegisteredNativeComponent[];\n /** Create a snapshot */\n createSnapshot: () => NativeBridgeSnapshot;\n /** Subscribe to events */\n on: <T = unknown>(type: BridgeEventType, listener: BridgeEventListener<T>) => () => void;\n /** Unsubscribe from events */\n off: <T = unknown>(type: BridgeEventType, listener: BridgeEventListener<T>) => void;\n /** Whether the provider is initialized */\n initialized: boolean;\n /** Server running status */\n serverRunning: boolean;\n /** Start the HTTP server */\n startServer: () => Promise<void>;\n /** Stop the HTTP server */\n stopServer: () => void;\n /** Get the current navigation route (if a RouteProvider is configured) */\n getCurrentRoute: () => string | null;\n}\n\n/**\n * UI Bridge Native context\n */\nconst UIBridgeNativeContext = createContext<UIBridgeNativeContextValue | null>(null);\n\n/**\n * Stable defaults for the two optional object props.\n *\n * Written as `features = {}` these were fresh literals on every render, so\n * `Object.is` failed for both entries of the `contextValue` `useMemo` deps below\n * and the context value was NEW on every provider render. `useUIElement`'s\n * auto-register effect has deps `[autoRegister, bridge, id]` where `bridge` IS\n * that context value, and its cleanup runs a real `registry.unregisterElement`\n * followed by a full `registerElement` — not an in-place mutate. So a host that\n * re-renders its provider (any `useState` in the root layout) churned the entire\n * element registry every time. `useUIComponent` (deps `[autoRegister, bridge]`)\n * had the identical exposure.\n *\n * The two sibling providers in this repo — `packages/ui-bridge/src/react/\n * UIBridgeProvider.tsx` and `packages/ui-bridge/src/native/react/\n * UIBridgeNativeProvider.tsx` — already carry exactly this idiom. Plain\n * literals, not `Object.freeze`d, to match them verbatim.\n */\nconst EMPTY_FEATURES: NativeUIBridgeFeatures = {};\nconst EMPTY_CONFIG: NativeUIBridgeConfig = {};\n\n/**\n * Hold an options object's identity steady for as long as its VALUE is\n * unchanged.\n *\n * Stable defaults alone only fix the host that OMITS the prop. This component's\n * own usage example passes inline literals —\n * `features={{ server: __DEV__, debug: __DEV__ }} config={{ serverPort: 8087 }}`\n * — which are freshly allocated on every render of the host's layout, so\n * `Object.is` fails and the context value churns exactly as before. That is the\n * documented pattern, so it is the one that has to work.\n *\n * Keyed on a JSON signature rather than an enumerated field list: both\n * `NativeUIBridgeFeatures` and `NativeUIBridgeConfig` are small, plain,\n * JSON-serialisable bags (booleans, a number, strings, and one nested `appInfo`\n * of strings — no functions, no cycles), and a field list would silently stop\n * covering a field the day one is added, with nothing to catch it. Cost is one\n * `JSON.stringify` of a handful of scalars per render.\n *\n * Two honest limits: a host that reorders its object's keys between renders\n * produces a different signature, and a host that MUTATES the object in place\n * without changing the reference will now be noticed rather than ignored. Both\n * degrade to the previous behaviour (a new context value), never to a stale one.\n */\nfunction useValueStable<T>(value: T): T {\n const signature = JSON.stringify(value);\n // Keyed on the VALUE signature on purpose: `value`'s identity is precisely\n // what this hook exists to discard, so listing it as a dep would defeat it.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return useMemo(() => value, [signature]);\n}\n\n/**\n * UI Bridge Native provider props\n */\nexport interface UIBridgeNativeProviderProps {\n /** Child components */\n children: React.ReactNode;\n /** Feature flags */\n features?: NativeUIBridgeFeatures;\n /** Configuration */\n config?: NativeUIBridgeConfig;\n /** Event handler */\n onEvent?: BridgeEventListener;\n /**\n * A ServerAdapter instance for the HTTP server.\n * The app is responsible for creating this using its preferred native TCP/HTTP library.\n * @see ServerAdapter interface in server/http-server.ts\n */\n serverAdapter?: ServerAdapter;\n /**\n * Navigation provider for programmatic route navigation via UI Bridge.\n * Pass Expo Router's push/back functions to enable `control/page/navigate`.\n */\n navigationProvider?: { navigate: (url: string) => void; back?: () => void };\n /**\n * Screenshot provider for native screen capture via UI Bridge.\n * Pass a function that captures the current screen as base64 PNG.\n */\n screenshotProvider?: {\n capture: () => Promise<{ base64: string; width: number; height: number }>;\n };\n /**\n * Keep-awake provider for native screen-wake control via UI Bridge.\n * Pass expo-keep-awake's activateKeepAwakeAsync/deactivateKeepAwake (wrapped\n * to the request/release shape) so an external runner never loses the device\n * to a screen lock. The SDK never imports expo-keep-awake directly.\n */\n keepAwakeProvider?: KeepAwakeProvider;\n /**\n * Route provider for exposing the current navigation route in snapshots.\n * Wire this to Expo Router's `usePathname()` / `useSegments()` via a module-level ref.\n */\n routeProvider?: RouteProvider;\n /**\n * Configuration for the cloud relay tunnel (enables remote device verification\n * when USB/LAN are not available). Omit `uiBridgeServer` — the provider wires\n * that automatically after the server starts.\n */\n cloudRelayConfig?: Omit<CloudRelayConfig, 'uiBridgeServer'>;\n /**\n * Enable mDNS advertisement so that runners on the same LAN can discover this\n * device automatically. Advertises `_uibridge._tcp` only when `zeroconf` is\n * also passed; without it the announcer logs that advertisement was skipped.\n */\n enableMdnsAnnounce?: boolean;\n /**\n * The `Zeroconf` constructor from `react-native-zeroconf`\n * (`import Zeroconf from 'react-native-zeroconf'` → `zeroconf={Zeroconf}`),\n * forwarded to `DeviceAnnouncer.startMdnsAdvertise`. The SDK never\n * `require()`s that package itself — Metro raises an uncatchable module-load\n * error when it is absent — so the host app, which knows it is installed,\n * hands the constructor in. Omitted, mDNS advertisement is skipped.\n *\n * Must be a STABLE reference — the module-level import, never an inline\n * wrapper — because it is a dependency of the transport effect: a new\n * identity per render tears down and re-publishes the advertisement (and the\n * cloud relay) on every render.\n */\n zeroconf?: ZeroconfConstructor;\n /**\n * Stable device identifier used for mDNS TXT records and cloud relay\n * registration. Typically sourced from expo-device or a UUID stored in\n * AsyncStorage.\n */\n deviceId?: string;\n}\n\n/**\n * UI Bridge Native Provider\n *\n * Provides UI Bridge Native context to child components.\n *\n * @example\n * ```tsx\n * // app/_layout.tsx\n * import { UIBridgeNativeProvider } from '@qontinui/ui-bridge-native';\n *\n * export default function RootLayout() {\n * return (\n * <UIBridgeNativeProvider\n * features={{ server: __DEV__, debug: __DEV__ }}\n * config={{ serverPort: 8087 }}\n * >\n * <Stack>{children}</Stack>\n * </UIBridgeNativeProvider>\n * );\n * }\n * ```\n */\n/**\n * The device window size in logical dp — the single definition, shared by every\n * consumer that needs it.\n *\n * Read through `react-native` HERE rather than inside the core registry or the\n * server: the require/import pattern there crashed the host RN app in\n * 0.6.3/0.6.4 (Metro/Hermes raised `unknownModuleError` past every try/catch).\n * This file already has a live react-native import, so it is the safe\n * injection point. See feedback_metro_require_gotcha for the full incident.\n *\n * Injected as a FUNCTION, not a value, so a rotation or split-screen resize is\n * picked up on the next read with nothing to keep in sync. Its three sinks —\n * `getPageHealth`, the design handlers, and the registry's visibility clipping\n * — must not each carry their own copy of this fact and drift apart.\n */\nfunction readWindowViewport(): { width: number; height: number } {\n const win = Dimensions.get('window');\n return { width: win.width, height: win.height };\n}\n\nexport function UIBridgeNativeProvider({\n children,\n features: featuresProp = EMPTY_FEATURES,\n config: configProp = EMPTY_CONFIG,\n onEvent,\n serverAdapter,\n navigationProvider,\n screenshotProvider,\n keepAwakeProvider,\n routeProvider,\n cloudRelayConfig,\n enableMdnsAnnounce,\n zeroconf,\n deviceId,\n}: UIBridgeNativeProviderProps) {\n // Identity held steady by VALUE — see `useValueStable`. Everything below,\n // including the `contextValue` memo, reads these rather than the raw props.\n const features = useValueStable(featuresProp);\n const config = useValueStable(configProp);\n\n const registryRef = useRef<NativeUIBridgeRegistry | null>(null);\n const executorRef = useRef<NativeActionExecutor | null>(null);\n const modalDetectorRef = useRef<ModalDetector | null>(null);\n const toastCaptureRef = useRef<ToastCapture | null>(null);\n const undoTrackerRef = useRef<UndoTracker | null>(null);\n const [serverRunning, setServerRunning] = useState(false);\n\n // Initialize on first render\n if (!registryRef.current) {\n registryRef.current = new NativeUIBridgeRegistry({\n verbose: config.verbose,\n onEvent,\n });\n // Inject the device pixel ratio so `createSnapshot` can project each\n // element's `state.layout` (logical dp) into a physical-pixel `bbox` the\n // runner's vision pipeline accepts. Done here (a React file that already\n // imports react-native) rather than inside the core registry, which must\n // stay free of react-native imports — see the `projectBbox` doc comment.\n registryRef.current.setPixelRatio(PixelRatio.get());\n // Inject the window bounds so reported `visibility` is clipped against the\n // viewport instead of meaning \"mounted and measured somewhere\". Same\n // injection rationale as `setPixelRatio` above — the core registry must\n // stay free of react-native imports. A PROVIDER rather than a value so a\n // rotation or a split-screen resize is picked up on the next read.\n registryRef.current.setViewportProvider(readWindowViewport);\n setGlobalRegistry(registryRef.current);\n }\n\n const registry = registryRef.current;\n\n // Create executor (memoized)\n if (!executorRef.current) {\n executorRef.current = createNativeActionExecutor(registry);\n }\n\n const executor = executorRef.current;\n\n // Trackers — lazy-init the same way as registry/executor so each provider\n // lifecycle has exactly one stable instance.\n if (!modalDetectorRef.current) modalDetectorRef.current = new ModalDetector();\n if (!toastCaptureRef.current) toastCaptureRef.current = new ToastCapture();\n if (!undoTrackerRef.current) undoTrackerRef.current = new UndoTracker();\n\n const modalDetector = modalDetectorRef.current;\n const toastCapture = toastCaptureRef.current;\n const undoTracker = undoTrackerRef.current;\n\n // Server instance (persisted across renders)\n const serverRef = useRef<NativeUIBridgeServer | null>(null);\n const eventBridgeRef = useRef<WebSocketEventBridge | null>(null);\n\n // Cloud / mDNS transport references\n const announcerRef = useRef<DeviceAnnouncer | null>(null);\n const cloudRelayRef = useRef<CloudRelayClient | null>(null);\n\n // Server management — uses injected serverAdapter if provided\n const startServer = useCallback(async () => {\n if (!features.server) return;\n\n if (!serverAdapter) {\n console.warn(\n `[ui-bridge-native] HTTP server not available: no serverAdapter prop provided. ` +\n `Pass a serverAdapter to UIBridgeNativeProvider to enable the HTTP server.`\n );\n return;\n }\n\n const server = createNativeServer(registry, executor, {\n serverPort: config.serverPort || 8087,\n cors: true,\n appInfo: config.appInfo,\n testHooks: features.testHooks === true,\n // Dedicated console/network-capture flag; the server defaults it to\n // the `testHooks` value when undefined, so passing it through verbatim\n // keeps existing consumers unchanged.\n observability: features.observability,\n // Injected device-viewport getter for POST /control/page-health and the\n // design handlers. Same source as the registry's clipping bounds — see\n // `readWindowViewport`.\n viewportProvider: readWindowViewport,\n });\n\n server.setAdapter(serverAdapter);\n\n // Wire navigation provider if supplied\n if (navigationProvider) {\n server.setNavigationProvider(navigationProvider);\n }\n\n // Wire screenshot provider if supplied\n if (screenshotProvider) {\n server.setScreenshotProvider(screenshotProvider);\n }\n\n // Wire keep-awake provider if supplied\n if (keepAwakeProvider) {\n server.setKeepAwakeProvider(keepAwakeProvider);\n }\n\n // Wire route provider if supplied (must be set AFTER navigationProvider\n // since both may override getSnapshot / pageNavigate handlers)\n if (routeProvider) {\n server.setRouteProvider(routeProvider);\n }\n\n // Wire up WebSocket event bridge if adapter supports it\n const wsAdapter = serverAdapter as WebSocketServerAdapter;\n if (typeof wsAdapter.broadcast === 'function') {\n const eventBridge = new WebSocketEventBridge(registry);\n server.setEventBridge(eventBridge);\n\n wsAdapter.onWebSocketConnect = (connId: string) => {\n // Create a lightweight connection proxy for the event bridge\n // The actual WebSocketConnection lives in the adapter\n eventBridge.addConnection({\n id: connId,\n subscriptions: new Set(),\n alive: true,\n isOpen: true,\n send: (msg: string) => wsAdapter.sendToConnection?.(connId, msg),\n sendEvent: (event: { event: string }) => {\n if (!eventBridge.isSubscribed(connId, event.event)) return;\n wsAdapter.sendToConnection?.(connId, JSON.stringify(event));\n },\n ping: () => {\n /* adapter handles heartbeat */\n },\n close: () => {\n /* adapter handles close */\n },\n destroy: () => {\n /* adapter handles destroy */\n },\n } as any); // eslint-disable-line @typescript-eslint/no-explicit-any\n };\n\n wsAdapter.onWebSocketDisconnect = (connId: string) => {\n // Abort any pending waiters (waitForElement / waitForCondition)\n // for this connection BEFORE removing it — stops leaked timers\n // and registry listeners.\n server.abortWaitersForConnection(connId);\n eventBridge.removeConnection(connId);\n };\n\n wsAdapter.onWebSocketMessage = async (connId: string, message: string) => {\n return server.handleWebSocketMessage(connId, message);\n };\n\n eventBridge.start();\n eventBridgeRef.current = eventBridge;\n }\n\n try {\n await server.start();\n serverRef.current = server;\n setServerRunning(true);\n } catch (err) {\n console.warn('[ui-bridge-native] Failed to start HTTP server:', err);\n }\n }, [\n features.server,\n features.testHooks,\n features.observability,\n config.serverPort,\n config.appInfo,\n registry,\n executor,\n serverAdapter,\n navigationProvider,\n screenshotProvider,\n keepAwakeProvider,\n routeProvider,\n ]);\n\n const stopServer = useCallback(() => {\n if (eventBridgeRef.current) {\n eventBridgeRef.current.stop();\n eventBridgeRef.current = null;\n }\n if (serverRef.current) {\n serverRef.current.stop().catch(() => {});\n serverRef.current = null;\n }\n setServerRunning(false);\n }, []);\n\n // Auto-start server if enabled\n useEffect(() => {\n if (features.server) {\n startServer();\n return () => stopServer();\n }\n }, [features.server, startServer, stopServer]);\n\n // Start mDNS announcement and cloud relay.\n //\n // The cloud relay path does NOT require the HTTP server to be running —\n // it calls `server.handleRequest()` directly, bypassing HTTP. This matters\n // for preview/production builds where `features.server` is typically\n // false (no native TCP server adapter available). We still need a\n // NativeUIBridgeServer instance to handle tunneled requests, so we create\n // a bare instance here if startServer() didn't already make one.\n //\n // mDNS also doesn't need the HTTP server — it just advertises on the\n // network so remote devices can discover this phone's address.\n useEffect(() => {\n // Everything here is best-effort — any exception must not crash the app.\n try {\n const needsBareServer = !serverRef.current && !!cloudRelayConfig;\n if (needsBareServer) {\n try {\n // Create a server instance for handleRequest() use only; no adapter,\n // no HTTP listener. This is safe without features.server.\n const bareServer = createNativeServer(registry, executor, {\n serverPort: config.serverPort || 8087,\n cors: true,\n appInfo: config.appInfo,\n testHooks: features.testHooks === true,\n observability: features.observability,\n viewportProvider: readWindowViewport,\n });\n if (navigationProvider) bareServer.setNavigationProvider(navigationProvider);\n if (screenshotProvider) bareServer.setScreenshotProvider(screenshotProvider);\n if (keepAwakeProvider) bareServer.setKeepAwakeProvider(keepAwakeProvider);\n if (routeProvider) bareServer.setRouteProvider(routeProvider);\n serverRef.current = bareServer;\n } catch (err) {\n console.warn('[ui-bridge-native] Failed to create bare server:', err);\n }\n }\n\n // mDNS advertisement\n if (enableMdnsAnnounce && deviceId) {\n try {\n const announcer = new DeviceAnnouncer({\n deviceId,\n appId: config.appInfo?.appId ?? 'unknown',\n port: config.serverPort ?? 8087,\n cloudRelayUrl: cloudRelayConfig?.relayUrl,\n cloudToken: cloudRelayConfig?.authToken,\n });\n announcerRef.current = announcer;\n void announcer.startMdnsAdvertise(zeroconf).catch((err) => {\n console.warn('[ui-bridge-native] mDNS advertise failed:', err);\n });\n } catch (err) {\n console.warn('[ui-bridge-native] Failed to start DeviceAnnouncer:', err);\n }\n }\n\n // Cloud relay tunnel (works without HTTP server)\n if (cloudRelayConfig && serverRef.current) {\n try {\n const relayClient = new CloudRelayClient({\n ...cloudRelayConfig,\n uiBridgeServer: serverRef.current,\n });\n cloudRelayRef.current = relayClient;\n relayClient.start();\n } catch (err) {\n console.warn('[ui-bridge-native] Failed to start CloudRelayClient:', err);\n }\n }\n } catch (err) {\n console.warn('[ui-bridge-native] Transport effect failed:', err);\n }\n\n return () => {\n try {\n if (announcerRef.current) {\n void announcerRef.current.stop().catch(() => {});\n announcerRef.current = null;\n }\n if (cloudRelayRef.current) {\n cloudRelayRef.current.stop();\n cloudRelayRef.current = null;\n }\n } catch {\n // cleanup must never throw\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n serverRunning,\n cloudRelayConfig?.relayUrl,\n cloudRelayConfig?.authToken,\n enableMdnsAnnounce,\n zeroconf,\n deviceId,\n ]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n stopServer();\n resetGlobalRegistry();\n };\n }, [stopServer]);\n\n // Context methods\n const getElements = useCallback(() => registry.getAllElements(), [registry]);\n\n const getComponents = useCallback(() => registry.getAllComponents(), [registry]);\n\n const createSnapshot = useCallback(() => registry.createSnapshot(), [registry]);\n\n const getCurrentRoute = useCallback(() => {\n return routeProvider?.getCurrentRoute() ?? null;\n }, [routeProvider]);\n\n // Mark the previous route's elements offscreen when the active route changes.\n // Without this, React Navigation's persistent-mount behavior leaves stale\n // layouts in the registry for tabs the user has left. Elements on the new\n // route re-fire `onLayout` and repopulate their layout.\n useEffect(() => {\n if (!routeProvider) return;\n\n let lastRoute: string | null = routeProvider.getCurrentRoute();\n\n return routeProvider.subscribe((current: string | null) => {\n if (current !== lastRoute && lastRoute != null) {\n registry.markRouteOffscreen(lastRoute);\n }\n lastRoute = current;\n });\n }, [routeProvider, registry]);\n\n // Wire trackers into the registry's snapshot enricher slots. Trackers are\n // stable refs, so this effect runs exactly once per provider lifecycle.\n useEffect(() => {\n registry.setEnrichers({ modalDetector, toastCapture, undoTracker });\n }, [registry, modalDetector, toastCapture, undoTracker]);\n\n // Mirror successful bridge actions into UndoTracker's rolling history so\n // snapshots can surface action-correlation undo context without each\n // action site needing to call recordAction manually.\n useEffect(() => {\n const unsubscribe = executor.onActionExecuted((event) => {\n if (!event.success) return;\n undoTracker.recordAction({\n id: event.requestId ?? `action-${event.timestamp}`,\n type: event.action,\n targetId: event.elementId,\n timestamp: event.timestamp,\n reversible: true,\n });\n });\n return unsubscribe;\n }, [executor, undoTracker]);\n\n const on = useCallback(\n <T = unknown,>(type: BridgeEventType, listener: BridgeEventListener<T>) =>\n registry.on(type, listener),\n [registry]\n );\n\n const off = useCallback(\n <T = unknown,>(type: BridgeEventType, listener: BridgeEventListener<T>) =>\n registry.off(type, listener),\n [registry]\n );\n\n const contextValue = useMemo<UIBridgeNativeContextValue>(\n () => ({\n features,\n config,\n registry,\n executor,\n modalDetector,\n toastCapture,\n undoTracker,\n getElements,\n getComponents,\n createSnapshot,\n on,\n off,\n initialized: true,\n serverRunning,\n startServer,\n stopServer,\n getCurrentRoute,\n }),\n [\n features,\n config,\n registry,\n executor,\n modalDetector,\n toastCapture,\n undoTracker,\n getElements,\n getComponents,\n createSnapshot,\n on,\n off,\n serverRunning,\n startServer,\n stopServer,\n getCurrentRoute,\n ]\n );\n\n return <UIBridgeNativeContext value={contextValue}>{children}</UIBridgeNativeContext>;\n}\n\n/**\n * useUIBridgeNative hook\n *\n * Access the UI Bridge Native context. Throws if used outside provider.\n */\nexport function useUIBridgeNative(): UIBridgeNativeContextValue {\n const context = use(UIBridgeNativeContext);\n if (!context) {\n throw new Error('useUIBridgeNative must be used within a UIBridgeNativeProvider');\n }\n return context;\n}\n\n/**\n * useUIBridgeNativeOptional hook\n *\n * Access the UI Bridge Native context, returning null if outside provider.\n */\nexport function useUIBridgeNativeOptional(): UIBridgeNativeContextValue | null {\n return use(UIBridgeNativeContext);\n}\n\n/**\n * useUIBridgeNativeRequired hook\n *\n * Alias for useUIBridgeNative (throws if outside provider).\n */\nexport const useUIBridgeNativeRequired = useUIBridgeNative;\n","/**\n * UI Bridge Native Debug Inspector\n *\n * An overlay component that shows registered elements and allows\n * inspection of the UI Bridge state.\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport {\n View,\n Text,\n StyleSheet,\n TouchableOpacity,\n ScrollView,\n Modal,\n Dimensions,\n} from 'react-native';\nimport { useUIBridgeNativeOptional } from '../react/UIBridgeNativeProvider';\nimport type { RegisteredNativeElement } from '../core/types';\n\n/**\n * Inspector props\n */\nexport interface UIBridgeInspectorProps {\n /** Whether to show the inspector */\n visible?: boolean;\n /** Callback when inspector is closed */\n onClose?: () => void;\n /** Position of the inspector toggle button */\n togglePosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n}\n\n/**\n * Element info card\n */\nfunction ElementCard({\n element,\n onPress,\n}: {\n element: RegisteredNativeElement;\n onPress: (element: RegisteredNativeElement) => void;\n}) {\n const state = element.getState();\n const identifier = element.getIdentifier();\n\n return (\n <TouchableOpacity style={styles.elementCard} onPress={() => onPress(element)}>\n <View style={styles.elementHeader}>\n <Text style={styles.elementId}>{element.id}</Text>\n <Text style={styles.elementType}>{element.type}</Text>\n </View>\n {element.label && <Text style={styles.elementLabel}>{element.label}</Text>}\n <View style={styles.stateRow}>\n <StateIndicator label=\"Mounted\" value={state.mounted} />\n <StateIndicator label=\"Visible\" value={state.visible} />\n <StateIndicator label=\"Enabled\" value={state.enabled} />\n </View>\n {identifier.testId && <Text style={styles.testId}>testID: {identifier.testId}</Text>}\n </TouchableOpacity>\n );\n}\n\n/**\n * State indicator\n */\nfunction StateIndicator({ label, value }: { label: string; value: boolean }) {\n return (\n <View style={styles.stateIndicator}>\n <View style={[styles.stateDot, { backgroundColor: value ? '#4CAF50' : '#F44336' }]} />\n <Text style={styles.stateLabel}>{label}</Text>\n </View>\n );\n}\n\n/**\n * Element detail view\n */\nfunction ElementDetail({\n element,\n onClose,\n}: {\n element: RegisteredNativeElement;\n onClose: () => void;\n}) {\n const state = element.getState();\n const identifier = element.getIdentifier();\n\n return (\n <View style={styles.detailContainer}>\n <View style={styles.detailHeader}>\n <Text style={styles.detailTitle}>{element.id}</Text>\n <TouchableOpacity onPress={onClose}>\n <Text style={styles.closeButton}>Close</Text>\n </TouchableOpacity>\n </View>\n\n <ScrollView style={styles.detailContent}>\n <Text style={styles.sectionTitle}>Type</Text>\n <Text style={styles.sectionValue}>{element.type}</Text>\n\n {element.label && (\n <>\n <Text style={styles.sectionTitle}>Label</Text>\n <Text style={styles.sectionValue}>{element.label}</Text>\n </>\n )}\n\n <Text style={styles.sectionTitle}>State</Text>\n <View style={styles.stateSection}>\n <Text style={styles.stateText}>Mounted: {String(state.mounted)}</Text>\n <Text style={styles.stateText}>Visible: {String(state.visible)}</Text>\n <Text style={styles.stateText}>Enabled: {String(state.enabled)}</Text>\n <Text style={styles.stateText}>Focused: {String(state.focused)}</Text>\n {state.value !== undefined && <Text style={styles.stateText}>Value: {state.value}</Text>}\n </View>\n\n {state.layout && (\n <>\n <Text style={styles.sectionTitle}>Layout</Text>\n <View style={styles.stateSection}>\n <Text style={styles.stateText}>\n Position: ({state.layout.x}, {state.layout.y})\n </Text>\n <Text style={styles.stateText}>\n Size: {state.layout.width} x {state.layout.height}\n </Text>\n <Text style={styles.stateText}>\n Page: ({state.layout.pageX}, {state.layout.pageY})\n </Text>\n </View>\n </>\n )}\n\n <Text style={styles.sectionTitle}>Identifier</Text>\n <View style={styles.stateSection}>\n {identifier.uiId && <Text style={styles.stateText}>uiId: {identifier.uiId}</Text>}\n {identifier.testId && <Text style={styles.stateText}>testId: {identifier.testId}</Text>}\n {identifier.accessibilityLabel && (\n <Text style={styles.stateText}>a11yLabel: {identifier.accessibilityLabel}</Text>\n )}\n <Text style={styles.stateText}>treePath: {identifier.treePath}</Text>\n </View>\n\n <Text style={styles.sectionTitle}>Actions</Text>\n <View style={styles.actionsSection}>\n {element.actions.map((action) => (\n <View key={action} style={styles.actionBadge}>\n <Text style={styles.actionText}>{action}</Text>\n </View>\n ))}\n </View>\n\n {element.customActions && Object.keys(element.customActions).length > 0 && (\n <>\n <Text style={styles.sectionTitle}>Custom Actions</Text>\n <View style={styles.actionsSection}>\n {Object.keys(element.customActions).map((action) => (\n <View key={action} style={[styles.actionBadge, styles.customActionBadge]}>\n <Text style={styles.actionText}>{action}</Text>\n </View>\n ))}\n </View>\n </>\n )}\n </ScrollView>\n </View>\n );\n}\n\n/**\n * UI Bridge Inspector component\n *\n * Shows an overlay with information about registered elements.\n * Useful for debugging and development.\n *\n * @example\n * ```tsx\n * function App() {\n * const [showInspector, setShowInspector] = useState(false);\n *\n * return (\n * <UIBridgeNativeProvider features={{ debug: __DEV__ }}>\n * <MainContent />\n * {__DEV__ && (\n * <UIBridgeInspector\n * visible={showInspector}\n * onClose={() => setShowInspector(false)}\n * />\n * )}\n * </UIBridgeNativeProvider>\n * );\n * }\n * ```\n */\nexport function UIBridgeInspector({\n visible = false,\n onClose,\n togglePosition = 'bottom-right',\n}: UIBridgeInspectorProps) {\n const bridge = useUIBridgeNativeOptional();\n const [showInspector, setShowInspector] = useState(visible);\n const [selectedElement, setSelectedElement] = useState<RegisteredNativeElement | null>(null);\n\n const elements = useMemo(() => (bridge ? bridge.getElements() : []), [bridge]);\n\n const components = useMemo(() => (bridge ? bridge.getComponents() : []), [bridge]);\n\n const handleToggle = useCallback(() => {\n setShowInspector((prev) => !prev);\n }, []);\n\n const handleClose = useCallback(() => {\n setShowInspector(false);\n onClose?.();\n }, [onClose]);\n\n const handleSelectElement = useCallback((element: RegisteredNativeElement) => {\n setSelectedElement(element);\n }, []);\n\n const handleCloseDetail = useCallback(() => {\n setSelectedElement(null);\n }, []);\n\n // Position style for toggle button\n const toggleStyle = useMemo(() => {\n switch (togglePosition) {\n case 'top-left':\n return { top: 50, left: 10 };\n case 'top-right':\n return { top: 50, right: 10 };\n case 'bottom-left':\n return { bottom: 50, left: 10 };\n case 'bottom-right':\n default:\n return { bottom: 50, right: 10 };\n }\n }, [togglePosition]);\n\n if (!bridge) {\n return null;\n }\n\n return (\n <>\n {/* Toggle button */}\n <TouchableOpacity style={[styles.toggleButton, toggleStyle]} onPress={handleToggle}>\n <Text style={styles.toggleText}>UI</Text>\n </TouchableOpacity>\n\n {/* Inspector modal */}\n <Modal\n visible={showInspector}\n animationType=\"slide\"\n transparent={true}\n onRequestClose={handleClose}\n >\n <View style={styles.modalContainer}>\n <View style={styles.inspectorContainer}>\n {/* Header */}\n <View style={styles.header}>\n <Text style={styles.headerTitle}>UI Bridge Inspector</Text>\n <TouchableOpacity onPress={handleClose}>\n <Text style={styles.closeButton}>X</Text>\n </TouchableOpacity>\n </View>\n\n {/* Stats */}\n <View style={styles.statsRow}>\n <View style={styles.stat}>\n <Text style={styles.statValue}>{elements.length}</Text>\n <Text style={styles.statLabel}>Elements</Text>\n </View>\n <View style={styles.stat}>\n <Text style={styles.statValue}>{components.length}</Text>\n <Text style={styles.statLabel}>Components</Text>\n </View>\n <View style={styles.stat}>\n <Text style={styles.statValue}>{bridge.serverRunning ? 'ON' : 'OFF'}</Text>\n <Text style={styles.statLabel}>Server</Text>\n </View>\n </View>\n\n {/* Element list or detail */}\n {selectedElement ? (\n <ElementDetail element={selectedElement} onClose={handleCloseDetail} />\n ) : (\n <ScrollView style={styles.elementList}>\n {elements.map((element) => (\n <ElementCard key={element.id} element={element} onPress={handleSelectElement} />\n ))}\n {elements.length === 0 && (\n <Text style={styles.emptyText}>No elements registered yet</Text>\n )}\n </ScrollView>\n )}\n </View>\n </View>\n </Modal>\n </>\n );\n}\n\nconst { height: screenHeight } = Dimensions.get('window');\n\nconst styles = StyleSheet.create({\n toggleButton: {\n position: 'absolute',\n width: 44,\n height: 44,\n borderRadius: 22,\n backgroundColor: '#2196F3',\n justifyContent: 'center',\n alignItems: 'center',\n shadowColor: '#000',\n shadowOffset: { width: 0, height: 2 },\n shadowOpacity: 0.25,\n shadowRadius: 4,\n elevation: 5,\n zIndex: 1000,\n },\n toggleText: {\n color: '#fff',\n fontWeight: 'bold',\n fontSize: 14,\n },\n modalContainer: {\n flex: 1,\n backgroundColor: 'rgba(0, 0, 0, 0.5)',\n justifyContent: 'flex-end',\n },\n inspectorContainer: {\n backgroundColor: '#1e1e1e',\n borderTopLeftRadius: 16,\n borderTopRightRadius: 16,\n maxHeight: screenHeight * 0.8,\n },\n header: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n padding: 16,\n borderBottomWidth: 1,\n borderBottomColor: '#333',\n },\n headerTitle: {\n color: '#fff',\n fontSize: 18,\n fontWeight: 'bold',\n },\n closeButton: {\n color: '#2196F3',\n fontSize: 16,\n fontWeight: 'bold',\n },\n statsRow: {\n flexDirection: 'row',\n padding: 12,\n borderBottomWidth: 1,\n borderBottomColor: '#333',\n },\n stat: {\n flex: 1,\n alignItems: 'center',\n },\n statValue: {\n color: '#fff',\n fontSize: 20,\n fontWeight: 'bold',\n },\n statLabel: {\n color: '#888',\n fontSize: 12,\n marginTop: 2,\n },\n elementList: {\n padding: 12,\n },\n elementCard: {\n backgroundColor: '#2d2d2d',\n borderRadius: 8,\n padding: 12,\n marginBottom: 8,\n },\n elementHeader: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n },\n elementId: {\n color: '#fff',\n fontSize: 14,\n fontWeight: 'bold',\n },\n elementType: {\n color: '#888',\n fontSize: 12,\n backgroundColor: '#444',\n paddingHorizontal: 8,\n paddingVertical: 2,\n borderRadius: 4,\n },\n elementLabel: {\n color: '#aaa',\n fontSize: 12,\n marginTop: 4,\n },\n stateRow: {\n flexDirection: 'row',\n marginTop: 8,\n },\n stateIndicator: {\n flexDirection: 'row',\n alignItems: 'center',\n marginRight: 12,\n },\n stateDot: {\n width: 8,\n height: 8,\n borderRadius: 4,\n marginRight: 4,\n },\n stateLabel: {\n color: '#888',\n fontSize: 10,\n },\n testId: {\n color: '#666',\n fontSize: 10,\n marginTop: 4,\n fontFamily: 'monospace',\n },\n emptyText: {\n color: '#888',\n textAlign: 'center',\n marginTop: 20,\n },\n detailContainer: {\n flex: 1,\n },\n detailHeader: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n padding: 12,\n borderBottomWidth: 1,\n borderBottomColor: '#333',\n },\n detailTitle: {\n color: '#fff',\n fontSize: 16,\n fontWeight: 'bold',\n },\n detailContent: {\n padding: 12,\n },\n sectionTitle: {\n color: '#888',\n fontSize: 12,\n marginTop: 12,\n marginBottom: 4,\n textTransform: 'uppercase',\n },\n sectionValue: {\n color: '#fff',\n fontSize: 14,\n },\n stateSection: {\n backgroundColor: '#2d2d2d',\n borderRadius: 8,\n padding: 8,\n },\n stateText: {\n color: '#ddd',\n fontSize: 12,\n fontFamily: 'monospace',\n marginBottom: 2,\n },\n actionsSection: {\n flexDirection: 'row',\n flexWrap: 'wrap',\n },\n actionBadge: {\n backgroundColor: '#2196F3',\n paddingHorizontal: 8,\n paddingVertical: 4,\n borderRadius: 4,\n marginRight: 6,\n marginBottom: 6,\n },\n customActionBadge: {\n backgroundColor: '#9C27B0',\n },\n actionText: {\n color: '#fff',\n fontSize: 12,\n },\n});\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/design-handlers.ts","../../src/react/UIBridgeNativeProvider.tsx","../../src/debug/inspector.tsx"],"names":["createContext","use","jsxs","TouchableOpacity","View","jsx","Text","ScrollView","Fragment","useState","useMemo","useCallback","Modal","Dimensions","StyleSheet"],"mappings":";;;;;;;;;;;;;;AAsBA,IAAI,cAAA,GAKO,IAAA;AAGX,IAAI,gBAAA,GAaO,IAAA;AAEX,IAAI;AAEF,EAAA,MAAM,KAAA,GAAQ,UAAQ,2BAA2B,CAAA;AACjD,EAAA,cAAA,GAAiB,KAAA;AACjB,EAAA,gBAAA,GAAmB,KAAA;AACrB,CAAA,CAAA,MAAQ;AAER;ACiDA,IAAM,qBAAA,GAAwBA,oBAAiD,IAAI,CAAA;AAmsB5E,SAAS,yBAAA,GAA+D;AAC7E,EAAA,OAAOC,UAAI,qBAAqB,CAAA;AAClC;ACvwBA,SAAS,WAAA,CAAY;AAAA,EACnB,OAAA;AAAA,EACA;AACF,CAAA,EAGG;AACD,EAAA,MAAM,KAAA,GAAQ,QAAQ,QAAA,EAAS;AAC/B,EAAA,MAAM,UAAA,GAAa,QAAQ,aAAA,EAAc;AAEzC,EAAA,uBACEC,eAAA,CAACC,gCAAiB,KAAA,EAAO,MAAA,CAAO,aAAa,OAAA,EAAS,MAAM,OAAA,CAAQ,OAAO,CAAA,EACzE,QAAA,EAAA;AAAA,oBAAAD,eAAA,CAACE,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,aAAA,EAClB,QAAA,EAAA;AAAA,sBAAAC,eAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,SAAA,EAAY,kBAAQ,EAAA,EAAG,CAAA;AAAA,sBAC3CD,cAAAA,CAACC,gBAAA,EAAA,EAAK,OAAO,MAAA,CAAO,WAAA,EAAc,kBAAQ,IAAA,EAAK;AAAA,KAAA,EACjD,CAAA;AAAA,IACC,OAAA,CAAQ,yBAASD,cAAAA,CAACC,oBAAK,KAAA,EAAO,MAAA,CAAO,YAAA,EAAe,QAAA,EAAA,OAAA,CAAQ,KAAA,EAAM,CAAA;AAAA,oBACnEJ,eAAA,CAACE,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,QAAA,EAClB,QAAA,EAAA;AAAA,sBAAAC,eAAC,cAAA,EAAA,EAAe,KAAA,EAAM,SAAA,EAAU,KAAA,EAAO,MAAM,OAAA,EAAS,CAAA;AAAA,sBACtDA,cAAAA,CAAC,cAAA,EAAA,EAAe,OAAM,SAAA,EAAU,KAAA,EAAO,MAAM,OAAA,EAAS,CAAA;AAAA,sBACtDA,cAAAA,CAAC,cAAA,EAAA,EAAe,OAAM,SAAA,EAAU,KAAA,EAAO,MAAM,OAAA,EAAS;AAAA,KAAA,EACxD,CAAA;AAAA,IACC,WAAW,MAAA,oBAAUH,eAAA,CAACI,gBAAA,EAAA,EAAK,KAAA,EAAO,OAAO,MAAA,EAAQ,QAAA,EAAA;AAAA,MAAA,UAAA;AAAA,MAAS,UAAA,CAAW;AAAA,KAAA,EAAO;AAAA,GAAA,EAC/E,CAAA;AAEJ;AAKA,SAAS,cAAA,CAAe,EAAE,KAAA,EAAO,KAAA,EAAM,EAAsC;AAC3E,EAAA,uBACEJ,eAAA,CAACE,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,cAAA,EAClB,QAAA,EAAA;AAAA,oBAAAC,cAAAA,CAACD,gBAAA,EAAA,EAAK,KAAA,EAAO,CAAC,MAAA,CAAO,QAAA,EAAU,EAAE,eAAA,EAAiB,KAAA,GAAQ,SAAA,GAAY,SAAA,EAAW,CAAA,EAAG,CAAA;AAAA,oBACpFC,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,YAAa,QAAA,EAAA,KAAA,EAAM;AAAA,GAAA,EACzC,CAAA;AAEJ;AAKA,SAAS,aAAA,CAAc;AAAA,EACrB,OAAA;AAAA,EACA;AACF,CAAA,EAGG;AACD,EAAA,MAAM,KAAA,GAAQ,QAAQ,QAAA,EAAS;AAC/B,EAAA,MAAM,UAAA,GAAa,QAAQ,aAAA,EAAc;AAEzC,EAAA,uBACEJ,eAAA,CAACE,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,eAAA,EAClB,QAAA,EAAA;AAAA,oBAAAF,eAAA,CAACE,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,YAAA,EAClB,QAAA,EAAA;AAAA,sBAAAC,eAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,WAAA,EAAc,kBAAQ,EAAA,EAAG,CAAA;AAAA,sBAC7CD,cAAAA,CAACF,4BAAA,EAAA,EAAiB,OAAA,EAAS,OAAA,EACzB,QAAA,kBAAAE,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,WAAA,EAAa,QAAA,EAAA,OAAA,EAAK,CAAA,EACxC;AAAA,KAAA,EACF,CAAA;AAAA,oBAEAJ,eAAA,CAACK,sBAAA,EAAA,EAAW,KAAA,EAAO,MAAA,CAAO,aAAA,EACxB,QAAA,EAAA;AAAA,sBAAAF,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,cAAc,QAAA,EAAA,MAAA,EAAI,CAAA;AAAA,sBACtCD,cAAAA,CAACC,gBAAA,EAAA,EAAK,OAAO,MAAA,CAAO,YAAA,EAAe,kBAAQ,IAAA,EAAK,CAAA;AAAA,MAE/C,OAAA,CAAQ,yBACPJ,eAAA,CAAAM,mBAAA,EAAA,EACE,QAAA,EAAA;AAAA,wBAAAH,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,cAAc,QAAA,EAAA,OAAA,EAAK,CAAA;AAAA,wBACvCD,cAAAA,CAACC,gBAAA,EAAA,EAAK,OAAO,MAAA,CAAO,YAAA,EAAe,kBAAQ,KAAA,EAAM;AAAA,OAAA,EACnD,CAAA;AAAA,sBAGFD,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,cAAc,QAAA,EAAA,OAAA,EAAK,CAAA;AAAA,sBACvCJ,eAAA,CAACE,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,YAAA,EAClB,QAAA,EAAA;AAAA,wBAAAF,eAAA,CAACI,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,SAAA,EAAW,QAAA,EAAA;AAAA,UAAA,WAAA;AAAA,UAAU,MAAA,CAAO,MAAM,OAAO;AAAA,SAAA,EAAE,CAAA;AAAA,wBAC/DJ,eAAA,CAACI,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,SAAA,EAAW,QAAA,EAAA;AAAA,UAAA,WAAA;AAAA,UAAU,MAAA,CAAO,MAAM,OAAO;AAAA,SAAA,EAAE,CAAA;AAAA,wBAC/DJ,eAAA,CAACI,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,SAAA,EAAW,QAAA,EAAA;AAAA,UAAA,WAAA;AAAA,UAAU,MAAA,CAAO,MAAM,OAAO;AAAA,SAAA,EAAE,CAAA;AAAA,wBAC/DJ,eAAA,CAACI,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,SAAA,EAAW,QAAA,EAAA;AAAA,UAAA,WAAA;AAAA,UAAU,MAAA,CAAO,MAAM,OAAO;AAAA,SAAA,EAAE,CAAA;AAAA,QAC9D,MAAM,KAAA,KAAU,MAAA,oCAAcA,gBAAA,EAAA,EAAK,KAAA,EAAO,OAAO,SAAA,EAAW,QAAA,EAAA;AAAA,UAAA,SAAA;AAAA,UAAQ,KAAA,CAAM;AAAA,SAAA,EAAM;AAAA,OAAA,EACnF,CAAA;AAAA,MAEC,KAAA,CAAM,0BACLJ,eAAA,CAAAM,mBAAA,EAAA,EACE,QAAA,EAAA;AAAA,wBAAAH,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,cAAc,QAAA,EAAA,QAAA,EAAM,CAAA;AAAA,wBACxCJ,eAAA,CAACE,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,YAAA,EAClB,QAAA,EAAA;AAAA,0BAAAF,eAAA,CAACI,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,SAAA,EAAW,QAAA,EAAA;AAAA,YAAA,aAAA;AAAA,YACjB,MAAM,MAAA,CAAO,CAAA;AAAA,YAAE,IAAA;AAAA,YAAG,MAAM,MAAA,CAAO,CAAA;AAAA,YAAE;AAAA,WAAA,EAC/C,CAAA;AAAA,0BACAJ,eAAA,CAACI,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,SAAA,EAAW,QAAA,EAAA;AAAA,YAAA,QAAA;AAAA,YACtB,MAAM,MAAA,CAAO,KAAA;AAAA,YAAM,KAAA;AAAA,YAAI,MAAM,MAAA,CAAO;AAAA,WAAA,EAC7C,CAAA;AAAA,0BACAJ,eAAA,CAACI,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,SAAA,EAAW,QAAA,EAAA;AAAA,YAAA,SAAA;AAAA,YACrB,MAAM,MAAA,CAAO,KAAA;AAAA,YAAM,IAAA;AAAA,YAAG,MAAM,MAAA,CAAO,KAAA;AAAA,YAAM;AAAA,WAAA,EACnD;AAAA,SAAA,EACF;AAAA,OAAA,EACF,CAAA;AAAA,sBAGFD,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,cAAc,QAAA,EAAA,YAAA,EAAU,CAAA;AAAA,sBAC5CJ,eAAA,CAACE,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,YAAA,EACjB,QAAA,EAAA;AAAA,QAAA,UAAA,CAAW,IAAA,oBAAQF,eAAA,CAACI,gBAAA,EAAA,EAAK,KAAA,EAAO,OAAO,SAAA,EAAW,QAAA,EAAA;AAAA,UAAA,QAAA;AAAA,UAAO,UAAA,CAAW;AAAA,SAAA,EAAK,CAAA;AAAA,QACzE,WAAW,MAAA,oBAAUJ,eAAA,CAACI,gBAAA,EAAA,EAAK,KAAA,EAAO,OAAO,SAAA,EAAW,QAAA,EAAA;AAAA,UAAA,UAAA;AAAA,UAAS,UAAA,CAAW;AAAA,SAAA,EAAO,CAAA;AAAA,QAC/E,WAAW,kBAAA,oBACVJ,eAAA,CAACI,gBAAA,EAAA,EAAK,KAAA,EAAO,OAAO,SAAA,EAAW,QAAA,EAAA;AAAA,UAAA,aAAA;AAAA,UAAY,UAAA,CAAW;AAAA,SAAA,EAAmB,CAAA;AAAA,wBAE3EJ,eAAA,CAACI,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,SAAA,EAAW,QAAA,EAAA;AAAA,UAAA,YAAA;AAAA,UAAW,UAAA,CAAW;AAAA,SAAA,EAAS;AAAA,OAAA,EAChE,CAAA;AAAA,sBAEAD,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,cAAc,QAAA,EAAA,SAAA,EAAO,CAAA;AAAA,sBACzCD,cAAAA,CAACD,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,cAAA,EACjB,QAAA,EAAA,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,qBACpBC,cAAAA,CAACD,gBAAA,EAAA,EAAkB,KAAA,EAAO,MAAA,CAAO,WAAA,EAC/B,QAAA,kBAAAC,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,UAAA,EAAa,QAAA,EAAA,MAAA,EAAO,CAAA,EAAA,EAD/B,MAEX,CACD,CAAA,EACH,CAAA;AAAA,MAEC,OAAA,CAAQ,iBAAiB,MAAA,CAAO,IAAA,CAAK,QAAQ,aAAa,CAAA,CAAE,MAAA,GAAS,CAAA,oBACpEJ,eAAA,CAAAM,mBAAA,EAAA,EACE,QAAA,EAAA;AAAA,wBAAAH,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,cAAc,QAAA,EAAA,gBAAA,EAAc,CAAA;AAAA,wBAChDD,cAAAA,CAACD,gBAAA,EAAA,EAAK,KAAA,EAAO,OAAO,cAAA,EACjB,QAAA,EAAA,MAAA,CAAO,IAAA,CAAK,OAAA,CAAQ,aAAa,CAAA,CAAE,GAAA,CAAI,CAAC,MAAA,qBACvCC,cAAAA,CAACD,gBAAA,EAAA,EAAkB,KAAA,EAAO,CAAC,MAAA,CAAO,WAAA,EAAa,MAAA,CAAO,iBAAiB,GACrE,QAAA,kBAAAC,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,OAAO,UAAA,EAAa,QAAA,EAAA,MAAA,EAAO,CAAA,EAAA,EAD/B,MAEX,CACD,CAAA,EACH;AAAA,OAAA,EACF;AAAA,KAAA,EAEJ;AAAA,GAAA,EACF,CAAA;AAEJ;AA2BO,SAAS,iBAAA,CAAkB;AAAA,EAChC,OAAA,GAAU,KAAA;AAAA,EACV,OAAA;AAAA,EACA,cAAA,GAAiB;AACnB,CAAA,EAA2B;AACzB,EAAA,MAAM,SAAS,yBAAA,EAA0B;AACzC,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAIG,eAAS,OAAO,CAAA;AAC1D,EAAA,MAAM,CAAC,eAAA,EAAiB,kBAAkB,CAAA,GAAIA,eAAyC,IAAI,CAAA;AAE3F,EAAA,MAAM,QAAA,GAAWC,aAAAA,CAAQ,MAAO,MAAA,GAAS,MAAA,CAAO,WAAA,EAAY,GAAI,EAAC,EAAI,CAAC,MAAM,CAAC,CAAA;AAE7E,EAAA,MAAM,UAAA,GAAaA,aAAAA,CAAQ,MAAO,MAAA,GAAS,MAAA,CAAO,aAAA,EAAc,GAAI,EAAC,EAAI,CAAC,MAAM,CAAC,CAAA;AAEjF,EAAA,MAAM,YAAA,GAAeC,kBAAY,MAAM;AACrC,IAAA,gBAAA,CAAiB,CAAC,IAAA,KAAS,CAAC,IAAI,CAAA;AAAA,EAClC,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,WAAA,GAAcA,kBAAY,MAAM;AACpC,IAAA,gBAAA,CAAiB,KAAK,CAAA;AACtB,IAAA,OAAA,IAAU;AAAA,EACZ,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,MAAM,mBAAA,GAAsBA,iBAAAA,CAAY,CAAC,OAAA,KAAqC;AAC5E,IAAA,kBAAA,CAAmB,OAAO,CAAA;AAAA,EAC5B,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,iBAAA,GAAoBA,kBAAY,MAAM;AAC1C,IAAA,kBAAA,CAAmB,IAAI,CAAA;AAAA,EACzB,CAAA,EAAG,EAAE,CAAA;AAGL,EAAA,MAAM,WAAA,GAAcD,cAAQ,MAAM;AAChC,IAAA,QAAQ,cAAA;AAAgB,MACtB,KAAK,UAAA;AACH,QAAA,OAAO,EAAE,GAAA,EAAK,EAAA,EAAI,IAAA,EAAM,EAAA,EAAG;AAAA,MAC7B,KAAK,WAAA;AACH,QAAA,OAAO,EAAE,GAAA,EAAK,EAAA,EAAI,KAAA,EAAO,EAAA,EAAG;AAAA,MAC9B,KAAK,aAAA;AACH,QAAA,OAAO,EAAE,MAAA,EAAQ,EAAA,EAAI,IAAA,EAAM,EAAA,EAAG;AAAA,MAChC,KAAK,cAAA;AAAA,MACL;AACE,QAAA,OAAO,EAAE,MAAA,EAAQ,EAAA,EAAI,KAAA,EAAO,EAAA,EAAG;AAAA;AACnC,EACF,CAAA,EAAG,CAAC,cAAc,CAAC,CAAA;AAEnB,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,uBACER,eAAA,CAAAM,mBAAA,EAAA,EAEE,QAAA,EAAA;AAAA,oBAAAH,eAACF,4BAAA,EAAA,EAAiB,KAAA,EAAO,CAAC,MAAA,CAAO,cAAc,WAAW,CAAA,EAAG,OAAA,EAAS,YAAA,EACpE,0BAAAE,cAAAA,CAACC,gBAAA,EAAA,EAAK,OAAO,MAAA,CAAO,UAAA,EAAY,gBAAE,CAAA,EACpC,CAAA;AAAA,oBAGAD,cAAAA;AAAA,MAACO,iBAAA;AAAA,MAAA;AAAA,QACC,OAAA,EAAS,aAAA;AAAA,QACT,aAAA,EAAc,OAAA;AAAA,QACd,WAAA,EAAa,IAAA;AAAA,QACb,cAAA,EAAgB,WAAA;AAAA,QAEhB,QAAA,kBAAAP,cAAAA,CAACD,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,gBAClB,QAAA,kBAAAF,eAAA,CAACE,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,kBAAA,EAElB,QAAA,EAAA;AAAA,0BAAAF,eAAA,CAACE,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,MAAA,EAClB,QAAA,EAAA;AAAA,4BAAAC,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,aAAa,QAAA,EAAA,qBAAA,EAAmB,CAAA;AAAA,4BACpDD,cAAAA,CAACF,4BAAA,EAAA,EAAiB,OAAA,EAAS,WAAA,EACzB,QAAA,kBAAAE,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,WAAA,EAAa,QAAA,EAAA,GAAA,EAAC,CAAA,EACpC;AAAA,WAAA,EACF,CAAA;AAAA,0BAGAJ,eAAA,CAACE,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,QAAA,EAClB,QAAA,EAAA;AAAA,4BAAAF,eAAA,CAACE,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,IAAA,EAClB,QAAA,EAAA;AAAA,8BAAAC,eAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,SAAA,EAAY,mBAAS,MAAA,EAAO,CAAA;AAAA,8BAChDD,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,WAAW,QAAA,EAAA,UAAA,EAAQ;AAAA,aAAA,EACzC,CAAA;AAAA,4BACAJ,eAAA,CAACE,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,IAAA,EAClB,QAAA,EAAA;AAAA,8BAAAC,eAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,SAAA,EAAY,qBAAW,MAAA,EAAO,CAAA;AAAA,8BAClDD,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,WAAW,QAAA,EAAA,YAAA,EAAU;AAAA,aAAA,EAC3C,CAAA;AAAA,4BACAJ,eAAA,CAACE,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,IAAA,EAClB,QAAA,EAAA;AAAA,8BAAAC,cAAAA,CAACC,oBAAK,KAAA,EAAO,MAAA,CAAO,WAAY,QAAA,EAAA,MAAA,CAAO,aAAA,GAAgB,OAAO,KAAA,EAAM,CAAA;AAAA,8BACpED,cAAAA,CAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,WAAW,QAAA,EAAA,QAAA,EAAM;AAAA,aAAA,EACvC;AAAA,WAAA,EACF,CAAA;AAAA,UAGC,eAAA,mBACCD,cAAAA,CAAC,aAAA,EAAA,EAAc,OAAA,EAAS,eAAA,EAAiB,OAAA,EAAS,iBAAA,EAAmB,CAAA,mBAErEH,eAAA,CAACK,sBAAA,EAAA,EAAW,KAAA,EAAO,OAAO,WAAA,EACvB,QAAA,EAAA;AAAA,YAAA,QAAA,CAAS,GAAA,CAAI,CAAC,OAAA,qBACbF,cAAAA,CAAC,WAAA,EAAA,EAA6B,OAAA,EAAkB,OAAA,EAAS,mBAAA,EAAA,EAAvC,OAAA,CAAQ,EAAoD,CAC/E,CAAA;AAAA,YACA,QAAA,CAAS,WAAW,CAAA,oBACnBA,eAACC,gBAAA,EAAA,EAAK,KAAA,EAAO,MAAA,CAAO,SAAA,EAAW,QAAA,EAAA,4BAAA,EAA0B;AAAA,WAAA,EAE7D;AAAA,SAAA,EAEJ,CAAA,EACF;AAAA;AAAA;AACF,GAAA,EACF,CAAA;AAEJ;AAEA,IAAM,EAAE,MAAA,EAAQ,YAAA,EAAa,GAAIO,sBAAAA,CAAW,IAAI,QAAQ,CAAA;AAExD,IAAM,MAAA,GAASC,uBAAW,MAAA,CAAO;AAAA,EAC/B,YAAA,EAAc;AAAA,IACZ,QAAA,EAAU,UAAA;AAAA,IACV,KAAA,EAAO,EAAA;AAAA,IACP,MAAA,EAAQ,EAAA;AAAA,IACR,YAAA,EAAc,EAAA;AAAA,IACd,eAAA,EAAiB,SAAA;AAAA,IACjB,cAAA,EAAgB,QAAA;AAAA,IAChB,UAAA,EAAY,QAAA;AAAA,IACZ,WAAA,EAAa,MAAA;AAAA,IACb,YAAA,EAAc,EAAE,KAAA,EAAO,CAAA,EAAG,QAAQ,CAAA,EAAE;AAAA,IACpC,aAAA,EAAe,IAAA;AAAA,IACf,YAAA,EAAc,CAAA;AAAA,IACd,SAAA,EAAW,CAAA;AAAA,IACX,MAAA,EAAQ;AAAA,GACV;AAAA,EACA,UAAA,EAAY;AAAA,IACV,KAAA,EAAO,MAAA;AAAA,IACP,UAAA,EAAY,MAAA;AAAA,IACZ,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,cAAA,EAAgB;AAAA,IACd,IAAA,EAAM,CAAA;AAAA,IACN,eAAA,EAAiB,oBAAA;AAAA,IACjB,cAAA,EAAgB;AAAA,GAClB;AAAA,EACA,kBAAA,EAAoB;AAAA,IAClB,eAAA,EAAiB,SAAA;AAAA,IACjB,mBAAA,EAAqB,EAAA;AAAA,IACrB,oBAAA,EAAsB,EAAA;AAAA,IACtB,WAAW,YAAA,GAAe;AAAA,GAC5B;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,aAAA,EAAe,KAAA;AAAA,IACf,cAAA,EAAgB,eAAA;AAAA,IAChB,UAAA,EAAY,QAAA;AAAA,IACZ,OAAA,EAAS,EAAA;AAAA,IACT,iBAAA,EAAmB,CAAA;AAAA,IACnB,iBAAA,EAAmB;AAAA,GACrB;AAAA,EACA,WAAA,EAAa;AAAA,IACX,KAAA,EAAO,MAAA;AAAA,IACP,QAAA,EAAU,EAAA;AAAA,IACV,UAAA,EAAY;AAAA,GACd;AAAA,EACA,WAAA,EAAa;AAAA,IACX,KAAA,EAAO,SAAA;AAAA,IACP,QAAA,EAAU,EAAA;AAAA,IACV,UAAA,EAAY;AAAA,GACd;AAAA,EACA,QAAA,EAAU;AAAA,IACR,aAAA,EAAe,KAAA;AAAA,IACf,OAAA,EAAS,EAAA;AAAA,IACT,iBAAA,EAAmB,CAAA;AAAA,IACnB,iBAAA,EAAmB;AAAA,GACrB;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,CAAA;AAAA,IACN,UAAA,EAAY;AAAA,GACd;AAAA,EACA,SAAA,EAAW;AAAA,IACT,KAAA,EAAO,MAAA;AAAA,IACP,QAAA,EAAU,EAAA;AAAA,IACV,UAAA,EAAY;AAAA,GACd;AAAA,EACA,SAAA,EAAW;AAAA,IACT,KAAA,EAAO,MAAA;AAAA,IACP,QAAA,EAAU,EAAA;AAAA,IACV,SAAA,EAAW;AAAA,GACb;AAAA,EACA,WAAA,EAAa;AAAA,IACX,OAAA,EAAS;AAAA,GACX;AAAA,EACA,WAAA,EAAa;AAAA,IACX,eAAA,EAAiB,SAAA;AAAA,IACjB,YAAA,EAAc,CAAA;AAAA,IACd,OAAA,EAAS,EAAA;AAAA,IACT,YAAA,EAAc;AAAA,GAChB;AAAA,EACA,aAAA,EAAe;AAAA,IACb,aAAA,EAAe,KAAA;AAAA,IACf,cAAA,EAAgB,eAAA;AAAA,IAChB,UAAA,EAAY;AAAA,GACd;AAAA,EACA,SAAA,EAAW;AAAA,IACT,KAAA,EAAO,MAAA;AAAA,IACP,QAAA,EAAU,EAAA;AAAA,IACV,UAAA,EAAY;AAAA,GACd;AAAA,EACA,WAAA,EAAa;AAAA,IACX,KAAA,EAAO,MAAA;AAAA,IACP,QAAA,EAAU,EAAA;AAAA,IACV,eAAA,EAAiB,MAAA;AAAA,IACjB,iBAAA,EAAmB,CAAA;AAAA,IACnB,eAAA,EAAiB,CAAA;AAAA,IACjB,YAAA,EAAc;AAAA,GAChB;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,KAAA,EAAO,MAAA;AAAA,IACP,QAAA,EAAU,EAAA;AAAA,IACV,SAAA,EAAW;AAAA,GACb;AAAA,EACA,QAAA,EAAU;AAAA,IACR,aAAA,EAAe,KAAA;AAAA,IACf,SAAA,EAAW;AAAA,GACb;AAAA,EACA,cAAA,EAAgB;AAAA,IACd,aAAA,EAAe,KAAA;AAAA,IACf,UAAA,EAAY,QAAA;AAAA,IACZ,WAAA,EAAa;AAAA,GACf;AAAA,EACA,QAAA,EAAU;AAAA,IACR,KAAA,EAAO,CAAA;AAAA,IACP,MAAA,EAAQ,CAAA;AAAA,IACR,YAAA,EAAc,CAAA;AAAA,IACd,WAAA,EAAa;AAAA,GACf;AAAA,EACA,UAAA,EAAY;AAAA,IACV,KAAA,EAAO,MAAA;AAAA,IACP,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,KAAA,EAAO,MAAA;AAAA,IACP,QAAA,EAAU,EAAA;AAAA,IACV,SAAA,EAAW,CAAA;AAAA,IACX,UAAA,EAAY;AAAA,GACd;AAAA,EACA,SAAA,EAAW;AAAA,IACT,KAAA,EAAO,MAAA;AAAA,IACP,SAAA,EAAW,QAAA;AAAA,IACX,SAAA,EAAW;AAAA,GACb;AAAA,EACA,eAAA,EAAiB;AAAA,IACf,IAAA,EAAM;AAAA,GACR;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,aAAA,EAAe,KAAA;AAAA,IACf,cAAA,EAAgB,eAAA;AAAA,IAChB,UAAA,EAAY,QAAA;AAAA,IACZ,OAAA,EAAS,EAAA;AAAA,IACT,iBAAA,EAAmB,CAAA;AAAA,IACnB,iBAAA,EAAmB;AAAA,GACrB;AAAA,EACA,WAAA,EAAa;AAAA,IACX,KAAA,EAAO,MAAA;AAAA,IACP,QAAA,EAAU,EAAA;AAAA,IACV,UAAA,EAAY;AAAA,GACd;AAAA,EACA,aAAA,EAAe;AAAA,IACb,OAAA,EAAS;AAAA,GACX;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,KAAA,EAAO,MAAA;AAAA,IACP,QAAA,EAAU,EAAA;AAAA,IACV,SAAA,EAAW,EAAA;AAAA,IACX,YAAA,EAAc,CAAA;AAAA,IACd,aAAA,EAAe;AAAA,GACjB;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,KAAA,EAAO,MAAA;AAAA,IACP,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,eAAA,EAAiB,SAAA;AAAA,IACjB,YAAA,EAAc,CAAA;AAAA,IACd,OAAA,EAAS;AAAA,GACX;AAAA,EACA,SAAA,EAAW;AAAA,IACT,KAAA,EAAO,MAAA;AAAA,IACP,QAAA,EAAU,EAAA;AAAA,IACV,UAAA,EAAY,WAAA;AAAA,IACZ,YAAA,EAAc;AAAA,GAChB;AAAA,EACA,cAAA,EAAgB;AAAA,IACd,aAAA,EAAe,KAAA;AAAA,IACf,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,WAAA,EAAa;AAAA,IACX,eAAA,EAAiB,SAAA;AAAA,IACjB,iBAAA,EAAmB,CAAA;AAAA,IACnB,eAAA,EAAiB,CAAA;AAAA,IACjB,YAAA,EAAc,CAAA;AAAA,IACd,WAAA,EAAa,CAAA;AAAA,IACb,YAAA,EAAc;AAAA,GAChB;AAAA,EACA,iBAAA,EAAmB;AAAA,IACjB,eAAA,EAAiB;AAAA,GACnB;AAAA,EACA,UAAA,EAAY;AAAA,IACV,KAAA,EAAO,MAAA;AAAA,IACP,QAAA,EAAU;AAAA;AAEd,CAAC,CAAA","file":"index.js","sourcesContent":["/**\n * UI Bridge Native Design Review Handlers\n *\n * HTTP handlers for the 8 design review endpoints.\n * Maps registered element styles to the web SDK's design data format.\n */\n\nimport type { NativeUIBridgeRegistry } from '../core/registry';\nimport type { APIResponse, HandlerContext } from './types';\nimport type { ElementDesignData, StateStyles, ResponsiveSnapshot } from '../design/design-types';\nimport {\n mapNativeStyleToExtended,\n getNativeElementDesignData,\n captureNativeStateVariations,\n captureNativeResponsiveSnapshot,\n} from '../design/design-inspector-native';\n\n// Style guide and baseline stored in closure (same pattern as web SDK)\nlet loadedStyleGuide: unknown = null;\nlet savedBaseline: unknown = null;\n\n// Try to load the style validator from the web SDK (optional peer dep)\nlet styleValidator: {\n runStyleAudit: (\n elements: Array<{ elementId: string; styles: Record<string, string> }>,\n guide: unknown\n ) => unknown;\n} | null = null;\n\n// Try to load quality evaluator from the web SDK (optional peer dep)\nlet qualityEvaluator: {\n evaluateQuality: (\n elements: ElementDesignData[],\n viewport: { width: number; height: number },\n context?: unknown\n ) => unknown;\n listContexts: () => Array<{ name: string; description: string }>;\n createBaseline: (\n elements: ElementDesignData[],\n viewport: { width: number; height: number },\n label?: string\n ) => unknown;\n diffSnapshots: (baseline: unknown, current: ElementDesignData[]) => unknown;\n} | null = null;\n\ntry {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const specs = require('@qontinui/ui-bridge/specs');\n styleValidator = specs;\n qualityEvaluator = specs;\n} catch {\n // Optional dependency not installed — audit/evaluate endpoints will return an error\n}\n\nfunction success<T>(data: T): APIResponse<T> {\n return { success: true, data, timestamp: Date.now() };\n}\n\nfunction error<T = unknown>(message: string, code?: string): APIResponse<T> {\n return { success: false, error: message, code, timestamp: Date.now() };\n}\n\n/**\n * Design handler interface (subset of NativeServerHandlers)\n */\nexport interface NativeDesignHandlers {\n getElementStyles: (ctx: HandlerContext) => Promise<APIResponse<ElementDesignData>>;\n getElementStateStyles: (\n ctx: HandlerContext\n ) => Promise<APIResponse<{ elementId: string; stateStyles: StateStyles[] }>>;\n getDesignSnapshot: (\n ctx: HandlerContext\n ) => Promise<APIResponse<{ elements: ElementDesignData[]; timestamp: number }>>;\n getResponsiveSnapshots: (ctx: HandlerContext) => Promise<APIResponse<ResponsiveSnapshot[]>>;\n runDesignAudit: (ctx: HandlerContext) => Promise<APIResponse<unknown>>;\n loadStyleGuide: (ctx: HandlerContext) => Promise<APIResponse<{ loaded: boolean }>>;\n getStyleGuide: (ctx: HandlerContext) => Promise<APIResponse<unknown>>;\n clearStyleGuide: (ctx: HandlerContext) => Promise<APIResponse<{ cleared: boolean }>>;\n evaluateQuality: (ctx: HandlerContext) => Promise<APIResponse<unknown>>;\n getQualityContexts: (\n ctx: HandlerContext\n ) => Promise<APIResponse<Array<{ name: string; description: string }>>>;\n saveBaseline: (\n ctx: HandlerContext\n ) => Promise<APIResponse<{ saved: boolean; elementCount: number }>>;\n diffBaseline: (ctx: HandlerContext) => Promise<APIResponse<unknown>>;\n}\n\n/**\n * Create design review handlers.\n *\n * @param registry - the registered native element registry\n * @param viewportProvider - optional injected viewport getter. Same shape as\n * the `viewportProvider` consumed by `handlers.ts`/`getPageHealth`. This is\n * injected by `createServerHandlers` from the `NativeServerConfig` the host\n * passes to `UIBridgeNativeProvider`/`NativeUIBridgeServer`, which in turn\n * reads `Dimensions.get('screen')` (or `'window'`). The indirection exists\n * because `require('react-native')` from this file crashes the host RN\n * app the moment any design endpoint is invoked: Metro/Hermes raise\n * `unknownModuleError` past every try/catch and tear down the JS thread.\n * This was the same latent crash that took out 0.6.3/0.6.4 page-health\n * handler — the design handlers had it too, just hiding on unused\n * endpoints until `POST /design/responsive` was first called.\n *\n * When the injected provider is absent (tests, custom hosts) the design\n * handlers fall back to `{width:0, height:0}` — the responsive snapshot\n * degenerates to \"zero-dimension viewport\" rather than crashing.\n */\nexport function createDesignHandlers(\n registry: NativeUIBridgeRegistry,\n viewportProvider?: () => { width: number; height: number }\n): NativeDesignHandlers {\n // Helper to get screen dimensions via the injected provider.\n //\n // The 0.6.3/0.6.4 implementation called `require('react-native')` here\n // inside a try/catch; Metro/Hermes still surfaced `unknownModuleError`\n // up the stack and tore down the JS thread the moment any design endpoint\n // was touched. The fix is to take the viewport as an injected dependency\n // (parity with `getPageHealth`) so this file has no `react-native` dep\n // at all — see the doc comment on `createDesignHandlers` above and the\n // project memory entry `feedback_metro_require_gotcha.md`.\n function getScreenDimensions(): { width: number; height: number } {\n return viewportProvider?.() ?? { width: 0, height: 0 };\n }\n\n return {\n // 1. GET /design/element/:id/styles\n getElementStyles: async (ctx) => {\n const { id } = ctx.params;\n const element = registry.getElement(id);\n\n if (!element) {\n return error('Element not found: ' + id, 'ELEMENT_NOT_FOUND');\n }\n\n const state = element.getState();\n const data = getNativeElementDesignData(\n id,\n element.label,\n element.type,\n element.flatStyle,\n state.layout\n );\n\n return success(data);\n },\n\n // 2. POST /design/element/:id/state-styles\n getElementStateStyles: async (ctx) => {\n const { id } = ctx.params;\n const element = registry.getElement(id);\n\n if (!element) {\n return error('Element not found: ' + id, 'ELEMENT_NOT_FOUND');\n }\n\n const stateStyles = captureNativeStateVariations(element.flatStyle, element.stateStyles);\n\n return success({ elementId: id, stateStyles });\n },\n\n // 3. POST /design/snapshot\n getDesignSnapshot: async (ctx) => {\n const body = ctx.body as\n | {\n elementIds?: string[];\n includePseudoElements?: boolean;\n }\n | undefined;\n\n let elements = registry.getAllElements();\n\n // Filter to requested IDs if specified\n if (body?.elementIds && body.elementIds.length > 0) {\n const idSet = new Set(body.elementIds);\n elements = elements.filter((e) => idSet.has(e.id));\n }\n\n const designData: ElementDesignData[] = elements.map((el) => {\n const state = el.getState();\n return getNativeElementDesignData(el.id, el.label, el.type, el.flatStyle, state.layout);\n });\n\n return success({ elements: designData, timestamp: Date.now() });\n },\n\n // 4. POST /design/responsive\n getResponsiveSnapshots: async () => {\n // Platform limitation: RN cannot constrain screen width at runtime.\n // Returns a single snapshot at current device dimensions.\n const elements = registry.getAllElements();\n const elementData = elements.map((el) => {\n const state = el.getState();\n return {\n id: el.id,\n label: el.label,\n type: el.type,\n flatStyle: el.flatStyle,\n layout: state.layout,\n };\n });\n\n const snapshots = captureNativeResponsiveSnapshot(elementData, getScreenDimensions());\n return success(snapshots);\n },\n\n // 5. POST /design/audit\n runDesignAudit: async (ctx) => {\n const body = ctx.body as\n | {\n guide?: unknown;\n elementIds?: string[];\n }\n | undefined;\n\n const guide = body?.guide ?? loadedStyleGuide;\n\n if (!guide) {\n return error('No style guide loaded or provided', 'NO_STYLE_GUIDE');\n }\n\n if (!styleValidator) {\n return error(\n 'Install @qontinui/ui-bridge for style audit support. ' +\n 'Add it as a dependency: npm install @qontinui/ui-bridge',\n 'VALIDATOR_NOT_AVAILABLE'\n );\n }\n\n let elements = registry.getAllElements();\n if (body?.elementIds && body.elementIds.length > 0) {\n const idSet = new Set(body.elementIds);\n elements = elements.filter((e) => idSet.has(e.id));\n }\n\n // Map elements to the format expected by the validator\n const auditElements = elements.map((el) => ({\n elementId: el.id,\n styles: mapNativeStyleToExtended(el.flatStyle) as unknown as Record<string, string>,\n }));\n\n try {\n const report = styleValidator.runStyleAudit(auditElements, guide);\n return success(report);\n } catch (err) {\n return error(\n 'Audit failed: ' + (err instanceof Error ? err.message : String(err)),\n 'DESIGN_AUDIT_ERROR'\n );\n }\n },\n\n // 6. POST /design/style-guide/load\n loadStyleGuide: async (ctx) => {\n const body = ctx.body as { guide?: unknown } | undefined;\n\n if (!body?.guide) {\n return error('Style guide is required in request body', 'INVALID_REQUEST');\n }\n\n loadedStyleGuide = body.guide;\n return success({ loaded: true });\n },\n\n // 7. GET /design/style-guide\n getStyleGuide: async () => {\n return success(loadedStyleGuide);\n },\n\n // 8. DELETE /design/style-guide\n clearStyleGuide: async () => {\n loadedStyleGuide = null;\n return success({ cleared: true });\n },\n\n // 9. POST /design/evaluate\n evaluateQuality: async (ctx) => {\n if (!qualityEvaluator) {\n return error(\n 'Install @qontinui/ui-bridge for quality evaluation support. ' +\n 'Add it as a dependency: npm install @qontinui/ui-bridge',\n 'EVALUATOR_NOT_AVAILABLE'\n );\n }\n\n const body = ctx.body as\n | {\n context?: string;\n customContext?: unknown;\n elementIds?: string[];\n viewport?: { width: number; height: number };\n }\n | undefined;\n\n let elements = registry.getAllElements();\n if (body?.elementIds && body.elementIds.length > 0) {\n const idSet = new Set(body.elementIds);\n elements = elements.filter((e) => idSet.has(e.id));\n }\n\n const designData: ElementDesignData[] = elements.map((el) => {\n const state = el.getState();\n return getNativeElementDesignData(el.id, el.label, el.type, el.flatStyle, state.layout);\n });\n\n const viewport = body?.viewport ?? getScreenDimensions();\n\n // Resolve context: custom object > style guide context > built-in name\n let context: unknown = body?.customContext ?? body?.context ?? 'general';\n if (typeof context === 'string' && loadedStyleGuide) {\n const guide = loadedStyleGuide as { qualityContexts?: Record<string, unknown> };\n if (guide.qualityContexts?.[context]) {\n context = guide.qualityContexts[context];\n }\n }\n\n try {\n const report = qualityEvaluator.evaluateQuality(designData, viewport, context);\n return success(report);\n } catch (err) {\n return error(\n 'Evaluation failed: ' + (err instanceof Error ? err.message : String(err)),\n 'QUALITY_EVALUATION_ERROR'\n );\n }\n },\n\n // 10. GET /design/evaluate/contexts\n getQualityContexts: async () => {\n if (!qualityEvaluator) {\n return error(\n 'Install @qontinui/ui-bridge for quality evaluation support.',\n 'EVALUATOR_NOT_AVAILABLE'\n );\n }\n return success(qualityEvaluator.listContexts());\n },\n\n // 11. POST /design/evaluate/baseline\n saveBaseline: async (ctx) => {\n if (!qualityEvaluator) {\n return error(\n 'Install @qontinui/ui-bridge for quality evaluation support.',\n 'EVALUATOR_NOT_AVAILABLE'\n );\n }\n\n const body = ctx.body as\n | {\n label?: string;\n elementIds?: string[];\n }\n | undefined;\n\n let elements = registry.getAllElements();\n if (body?.elementIds && body.elementIds.length > 0) {\n const idSet = new Set(body.elementIds);\n elements = elements.filter((e) => idSet.has(e.id));\n }\n\n const designData: ElementDesignData[] = elements.map((el) => {\n const state = el.getState();\n return getNativeElementDesignData(el.id, el.label, el.type, el.flatStyle, state.layout);\n });\n\n const viewport = getScreenDimensions();\n savedBaseline = qualityEvaluator.createBaseline(designData, viewport, body?.label);\n return success({ saved: true, elementCount: designData.length });\n },\n\n // 12. POST /design/evaluate/diff\n diffBaseline: async (ctx) => {\n if (!qualityEvaluator) {\n return error(\n 'Install @qontinui/ui-bridge for quality evaluation support.',\n 'EVALUATOR_NOT_AVAILABLE'\n );\n }\n\n if (!savedBaseline) {\n return error('No baseline saved. Call saveBaseline first.', 'NO_BASELINE');\n }\n\n const body = ctx.body as { elementIds?: string[] } | undefined;\n\n let elements = registry.getAllElements();\n if (body?.elementIds && body.elementIds.length > 0) {\n const idSet = new Set(body.elementIds);\n elements = elements.filter((e) => idSet.has(e.id));\n }\n\n const designData: ElementDesignData[] = elements.map((el) => {\n const state = el.getState();\n return getNativeElementDesignData(el.id, el.label, el.type, el.flatStyle, state.layout);\n });\n\n try {\n const report = qualityEvaluator.diffSnapshots(savedBaseline, designData);\n return success(report);\n } catch (err) {\n return error(\n 'Diff failed: ' + (err instanceof Error ? err.message : String(err)),\n 'DIFF_BASELINE_ERROR'\n );\n }\n },\n };\n}\n","/**\n * UI Bridge Native Provider\n *\n * React context provider for UI Bridge Native functionality.\n */\n\nimport React, {\n createContext,\n use,\n useMemo,\n useEffect,\n useCallback,\n useRef,\n useState,\n} from 'react';\nimport { Dimensions, PixelRatio } from 'react-native';\nimport type {\n NativeUIBridgeFeatures,\n NativeUIBridgeConfig,\n RegisteredNativeElement,\n RegisteredNativeComponent,\n NativeBridgeSnapshot,\n BridgeEventType,\n BridgeEventListener,\n} from '../core/types';\nimport { NativeUIBridgeRegistry, setGlobalRegistry, resetGlobalRegistry } from '../core/registry';\nimport { createNativeActionExecutor } from '../control/action-executor';\nimport type { NativeActionExecutor } from '../control/types';\nimport {\n createNativeServer,\n type HTTPRequest,\n type NativeUIBridgeServer,\n type ServerAdapter,\n type WebSocketServerAdapter,\n} from '../server/http-server';\nimport {\n PairingError,\n UNCONFIGURED_PAIRING_STATE,\n type ArmedPairingCode,\n type PairingState,\n type PairingStore,\n} from '../server/pairing';\nimport type { RouteProvider, KeepAwakeProvider } from '../server/types';\nimport { WebSocketEventBridge } from '../server/ws-event-bridge';\nimport { DeviceAnnouncer, type ZeroconfConstructor } from '../transport/DeviceAnnouncer';\nimport { CloudRelayClient, type CloudRelayConfig } from '../transport/CloudRelayClient';\nimport { ModalDetector } from '../modal/modal-detector';\nimport { ToastCapture } from '../toast/toast-capture';\nimport { UndoTracker } from '../undo/undo-tracker';\n\n/**\n * UI Bridge Native context value\n */\nexport interface UIBridgeNativeContextValue {\n /** Feature flags */\n features: NativeUIBridgeFeatures;\n /** Configuration */\n config: NativeUIBridgeConfig;\n /** Element registry */\n registry: NativeUIBridgeRegistry;\n /** Action executor */\n executor: NativeActionExecutor;\n /** Modal stack tracker (declarative pushModal/dismissModal) */\n modalDetector: ModalDetector;\n /** Toast/snackbar tracker (declarative recordToast/dismissToast) */\n toastCapture: ToastCapture;\n /** Undo/redo action-correlation tracker */\n undoTracker: UndoTracker;\n /** Get all registered elements */\n getElements: () => RegisteredNativeElement[];\n /** Get all registered components */\n getComponents: () => RegisteredNativeComponent[];\n /** Create a snapshot */\n createSnapshot: () => NativeBridgeSnapshot;\n /** Subscribe to events */\n on: <T = unknown>(type: BridgeEventType, listener: BridgeEventListener<T>) => () => void;\n /** Unsubscribe from events */\n off: <T = unknown>(type: BridgeEventType, listener: BridgeEventListener<T>) => void;\n /** Whether the provider is initialized */\n initialized: boolean;\n /** Server running status */\n serverRunning: boolean;\n /** Start the HTTP server */\n startServer: () => Promise<void>;\n /** Stop the HTTP server */\n stopServer: () => void;\n /** Get the current navigation route (if a RouteProvider is configured) */\n getCurrentRoute: () => string | null;\n /** Arm a 6-digit LAN pairing code to show the user. Rejects with a `PairingError`. */\n armPairingCode: () => Promise<ArmedPairingCode>;\n /** Cancel a live pairing code. */\n disarmPairingCode: () => void;\n /** What Settings shows, including the peer that consumed the last code. */\n getPairingState: () => PairingState;\n /** Replace the pairing token, killing every signature made with it. Rejects with a `PairingError`. */\n revokePairing: () => Promise<void>;\n}\n\n/**\n * UI Bridge Native context\n */\nconst UIBridgeNativeContext = createContext<UIBridgeNativeContextValue | null>(null);\n\n/**\n * Stable defaults for the two optional object props.\n *\n * Written as `features = {}` these were fresh literals on every render, so\n * `Object.is` failed for both entries of the `contextValue` `useMemo` deps below\n * and the context value was NEW on every provider render. `useUIElement`'s\n * auto-register effect has deps `[autoRegister, bridge, id]` where `bridge` IS\n * that context value, and its cleanup runs a real `registry.unregisterElement`\n * followed by a full `registerElement` — not an in-place mutate. So a host that\n * re-renders its provider (any `useState` in the root layout) churned the entire\n * element registry every time. `useUIComponent` (deps `[autoRegister, bridge]`)\n * had the identical exposure.\n *\n * The two sibling providers in this repo — `packages/ui-bridge/src/react/\n * UIBridgeProvider.tsx` and `packages/ui-bridge/src/native/react/\n * UIBridgeNativeProvider.tsx` — already carry exactly this idiom. Plain\n * literals, not `Object.freeze`d, to match them verbatim.\n */\nconst EMPTY_FEATURES: NativeUIBridgeFeatures = {};\nconst EMPTY_CONFIG: NativeUIBridgeConfig = {};\n\n/**\n * Hold an options object's identity steady for as long as its VALUE is\n * unchanged.\n *\n * Stable defaults alone only fix the host that OMITS the prop. This component's\n * own usage example passes inline literals —\n * `features={{ server: __DEV__, debug: __DEV__ }} config={{ serverPort: 8087 }}`\n * — which are freshly allocated on every render of the host's layout, so\n * `Object.is` fails and the context value churns exactly as before. That is the\n * documented pattern, so it is the one that has to work.\n *\n * Keyed on a JSON signature rather than an enumerated field list: both\n * `NativeUIBridgeFeatures` and `NativeUIBridgeConfig` are small, plain,\n * JSON-serialisable bags (booleans, a number, strings, and one nested `appInfo`\n * of strings — no functions, no cycles), and a field list would silently stop\n * covering a field the day one is added, with nothing to catch it. Cost is one\n * `JSON.stringify` of a handful of scalars per render.\n *\n * Two honest limits: a host that reorders its object's keys between renders\n * produces a different signature, and a host that MUTATES the object in place\n * without changing the reference will now be noticed rather than ignored. Both\n * degrade to the previous behaviour (a new context value), never to a stale one.\n */\nfunction useValueStable<T>(value: T): T {\n const signature = JSON.stringify(value);\n // Keyed on the VALUE signature on purpose: `value`'s identity is precisely\n // what this hook exists to discard, so listing it as a dep would defeat it.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return useMemo(() => value, [signature]);\n}\n\n/**\n * UI Bridge Native provider props\n */\nexport interface UIBridgeNativeProviderProps {\n /** Child components */\n children: React.ReactNode;\n /** Feature flags */\n features?: NativeUIBridgeFeatures;\n /** Configuration */\n config?: NativeUIBridgeConfig;\n /** Event handler */\n onEvent?: BridgeEventListener;\n /**\n * A ServerAdapter instance for the HTTP server.\n * The app is responsible for creating this using its preferred native TCP/HTTP library.\n * @see ServerAdapter interface in server/http-server.ts\n */\n serverAdapter?: ServerAdapter;\n /**\n * Navigation provider for programmatic route navigation via UI Bridge.\n * Pass Expo Router's push/back functions to enable `control/page/navigate`.\n */\n navigationProvider?: { navigate: (url: string) => void; back?: () => void };\n /**\n * Screenshot provider for native screen capture via UI Bridge.\n * Pass a function that captures the current screen as base64 PNG.\n */\n screenshotProvider?: {\n capture: () => Promise<{ base64: string; width: number; height: number }>;\n };\n /**\n * Keep-awake provider for native screen-wake control via UI Bridge.\n * Pass expo-keep-awake's activateKeepAwakeAsync/deactivateKeepAwake (wrapped\n * to the request/release shape) so an external runner never loses the device\n * to a screen lock. The SDK never imports expo-keep-awake directly.\n */\n keepAwakeProvider?: KeepAwakeProvider;\n /**\n * Route provider for exposing the current navigation route in snapshots.\n * Wire this to Expo Router's `usePathname()` / `useSegments()` via a module-level ref.\n */\n routeProvider?: RouteProvider;\n /**\n * Configuration for the cloud relay tunnel (enables remote device verification\n * when USB/LAN are not available). Omit `uiBridgeServer` — the provider wires\n * that automatically after the server starts.\n */\n cloudRelayConfig?: Omit<CloudRelayConfig, 'uiBridgeServer'>;\n /**\n * Enable mDNS advertisement so that runners on the same LAN can discover this\n * device automatically. Advertises `_uibridge._tcp` only when `zeroconf` is\n * also passed; without it the announcer logs that advertisement was skipped.\n */\n enableMdnsAnnounce?: boolean;\n /**\n * The `Zeroconf` constructor from `react-native-zeroconf`\n * (`import Zeroconf from 'react-native-zeroconf'` → `zeroconf={Zeroconf}`),\n * forwarded to `DeviceAnnouncer.startMdnsAdvertise`. The SDK never\n * `require()`s that package itself — Metro raises an uncatchable module-load\n * error when it is absent — so the host app, which knows it is installed,\n * hands the constructor in. Omitted, mDNS advertisement is skipped.\n *\n * Must be a STABLE reference — the module-level import, never an inline\n * wrapper — because it is a dependency of the transport effect: a new\n * identity per render tears down and re-publishes the advertisement (and the\n * cloud relay) on every render.\n */\n zeroconf?: ZeroconfConstructor;\n /**\n * Stable device identifier used for mDNS TXT records, cloud relay registration\n * and device pairing. Typically sourced from expo-device or a UUID stored in\n * AsyncStorage.\n */\n deviceId?: string;\n /**\n * Where the pairing token lives. Without one, every caller off this device\n * gets `401 PAIRING_UNCONFIGURED`, while a cable or a local emulator keeps\n * working. An inline object is fine: changing its identity does not restart\n * the server, and only going from absent to present (or back) does.\n */\n pairingStore?: PairingStore;\n /**\n * Admit HTTP callers off this device with no signature. For samples and fixtures\n * on a trusted network: anything that can reach the port can drive the app, but\n * a web page cannot, and a WebSocket upgrade stays loopback-only regardless.\n */\n allowUnauthenticatedNetwork?: boolean;\n}\n\n/**\n * UI Bridge Native Provider\n *\n * Provides UI Bridge Native context to child components.\n *\n * @example\n * ```tsx\n * // app/_layout.tsx\n * import { UIBridgeNativeProvider } from '@qontinui/ui-bridge-native';\n *\n * export default function RootLayout() {\n * return (\n * <UIBridgeNativeProvider\n * features={{ server: __DEV__, debug: __DEV__ }}\n * config={{ serverPort: 8087 }}\n * >\n * <Stack>{children}</Stack>\n * </UIBridgeNativeProvider>\n * );\n * }\n * ```\n */\n/**\n * The device window size in logical dp — the single definition, shared by every\n * consumer that needs it.\n *\n * Read through `react-native` HERE rather than inside the core registry or the\n * server: the require/import pattern there crashed the host RN app in\n * 0.6.3/0.6.4 (Metro/Hermes raised `unknownModuleError` past every try/catch).\n * This file already has a live react-native import, so it is the safe\n * injection point. See feedback_metro_require_gotcha for the full incident.\n *\n * Injected as a FUNCTION, not a value, so a rotation or split-screen resize is\n * picked up on the next read with nothing to keep in sync. Its three sinks —\n * `getPageHealth`, the design handlers, and the registry's visibility clipping\n * — must not each carry their own copy of this fact and drift apart.\n */\nfunction readWindowViewport(): { width: number; height: number } {\n const win = Dimensions.get('window');\n return { width: win.width, height: win.height };\n}\n\n/**\n * One store object for the provider's whole life, whatever identity the host's\n * prop has on a given render. The prop is a dependency of `startServer`, so a\n * host writing `pairingStore={{ ... }}` would otherwise restart the server, and\n * lose its sockets and any armed code, on every render. `useValueStable` cannot\n * do this: a store is functions, which JSON drops. Calls go to the latest prop.\n */\nfunction useStablePairingStore(store: PairingStore | undefined): PairingStore | undefined {\n const latestRef = useRef(store);\n useEffect(() => {\n latestRef.current = store;\n });\n const hasStore = store !== undefined;\n return useMemo(() => {\n if (!hasStore) return undefined;\n const current = (): PairingStore => {\n if (!latestRef.current) throw new Error('pairingStore was removed');\n return latestRef.current;\n };\n return {\n get: () => current().get(),\n set: (token) => current().set(token),\n clear: () => current().clear(),\n randomBytes: (n) => current().randomBytes(n),\n };\n }, [hasStore]);\n}\n\nexport function UIBridgeNativeProvider({\n children,\n features: featuresProp = EMPTY_FEATURES,\n config: configProp = EMPTY_CONFIG,\n onEvent,\n serverAdapter,\n navigationProvider,\n screenshotProvider,\n keepAwakeProvider,\n routeProvider,\n cloudRelayConfig,\n enableMdnsAnnounce,\n zeroconf,\n deviceId,\n pairingStore: pairingStoreProp,\n allowUnauthenticatedNetwork,\n}: UIBridgeNativeProviderProps) {\n // Identity held steady by VALUE — see `useValueStable`. Everything below,\n // including the `contextValue` memo, reads these rather than the raw props.\n const features = useValueStable(featuresProp);\n const config = useValueStable(configProp);\n const pairingStore = useStablePairingStore(pairingStoreProp);\n\n const registryRef = useRef<NativeUIBridgeRegistry | null>(null);\n const executorRef = useRef<NativeActionExecutor | null>(null);\n const modalDetectorRef = useRef<ModalDetector | null>(null);\n const toastCaptureRef = useRef<ToastCapture | null>(null);\n const undoTrackerRef = useRef<UndoTracker | null>(null);\n const [serverRunning, setServerRunning] = useState(false);\n\n // Initialize on first render\n if (!registryRef.current) {\n registryRef.current = new NativeUIBridgeRegistry({\n verbose: config.verbose,\n onEvent,\n });\n // Inject the device pixel ratio so `createSnapshot` can project each\n // element's `state.layout` (logical dp) into a physical-pixel `bbox` the\n // runner's vision pipeline accepts. Done here (a React file that already\n // imports react-native) rather than inside the core registry, which must\n // stay free of react-native imports — see the `projectBbox` doc comment.\n registryRef.current.setPixelRatio(PixelRatio.get());\n // Inject the window bounds so reported `visibility` is clipped against the\n // viewport instead of meaning \"mounted and measured somewhere\". Same\n // injection rationale as `setPixelRatio` above — the core registry must\n // stay free of react-native imports. A PROVIDER rather than a value so a\n // rotation or a split-screen resize is picked up on the next read.\n registryRef.current.setViewportProvider(readWindowViewport);\n setGlobalRegistry(registryRef.current);\n }\n\n const registry = registryRef.current;\n\n // Create executor (memoized)\n if (!executorRef.current) {\n executorRef.current = createNativeActionExecutor(registry);\n }\n\n const executor = executorRef.current;\n\n // Trackers — lazy-init the same way as registry/executor so each provider\n // lifecycle has exactly one stable instance.\n if (!modalDetectorRef.current) modalDetectorRef.current = new ModalDetector();\n if (!toastCaptureRef.current) toastCaptureRef.current = new ToastCapture();\n if (!undoTrackerRef.current) undoTrackerRef.current = new UndoTracker();\n\n const modalDetector = modalDetectorRef.current;\n const toastCapture = toastCaptureRef.current;\n const undoTracker = undoTrackerRef.current;\n\n // Server instance (persisted across renders)\n const serverRef = useRef<NativeUIBridgeServer | null>(null);\n const eventBridgeRef = useRef<WebSocketEventBridge | null>(null);\n\n // Cloud / mDNS transport references\n const announcerRef = useRef<DeviceAnnouncer | null>(null);\n const cloudRelayRef = useRef<CloudRelayClient | null>(null);\n\n // The device id usually loads after the server starts. Read through a ref, so\n // that arriving does not restart the server, and forwarded by the effect below.\n const deviceIdRef = useRef(deviceId);\n useEffect(() => {\n deviceIdRef.current = deviceId;\n serverRef.current?.setDeviceId(deviceId);\n }, [deviceId]);\n\n // Server management — uses injected serverAdapter if provided\n const startServer = useCallback(async () => {\n if (!features.server) return;\n\n if (!serverAdapter) {\n console.warn(\n `[ui-bridge-native] HTTP server not available: no serverAdapter prop provided. ` +\n `Pass a serverAdapter to UIBridgeNativeProvider to enable the HTTP server.`\n );\n return;\n }\n\n const server = createNativeServer(registry, executor, {\n serverPort: config.serverPort || 8087,\n cors: true,\n appInfo: config.appInfo,\n testHooks: features.testHooks === true,\n // Dedicated console/network-capture flag; the server defaults it to\n // the `testHooks` value when undefined, so passing it through verbatim\n // keeps existing consumers unchanged.\n observability: features.observability,\n // Injected device-viewport getter for POST /control/page-health and the\n // design handlers. Same source as the registry's clipping bounds — see\n // `readWindowViewport`.\n viewportProvider: readWindowViewport,\n pairingStore,\n allowUnauthenticatedNetwork,\n deviceId: deviceIdRef.current,\n });\n\n server.setAdapter(serverAdapter);\n\n // Wire navigation provider if supplied\n if (navigationProvider) {\n server.setNavigationProvider(navigationProvider);\n }\n\n // Wire screenshot provider if supplied\n if (screenshotProvider) {\n server.setScreenshotProvider(screenshotProvider);\n }\n\n // Wire keep-awake provider if supplied\n if (keepAwakeProvider) {\n server.setKeepAwakeProvider(keepAwakeProvider);\n }\n\n // Wire route provider if supplied (must be set AFTER navigationProvider\n // since both may override getSnapshot / pageNavigate handlers)\n if (routeProvider) {\n server.setRouteProvider(routeProvider);\n }\n\n // Wire up WebSocket event bridge if adapter supports it\n const wsAdapter = serverAdapter as WebSocketServerAdapter;\n if (typeof wsAdapter.broadcast === 'function') {\n const eventBridge = new WebSocketEventBridge(registry);\n server.setEventBridge(eventBridge);\n\n wsAdapter.onWebSocketConnect = (connId: string) => {\n // Create a lightweight connection proxy for the event bridge\n // The actual WebSocketConnection lives in the adapter\n eventBridge.addConnection({\n id: connId,\n subscriptions: new Set(),\n alive: true,\n isOpen: true,\n send: (msg: string) => wsAdapter.sendToConnection?.(connId, msg),\n sendEvent: (event: { event: string }) => {\n if (!eventBridge.isSubscribed(connId, event.event)) return;\n wsAdapter.sendToConnection?.(connId, JSON.stringify(event));\n },\n ping: () => {\n /* adapter handles heartbeat */\n },\n close: () => {\n /* adapter handles close */\n },\n destroy: () => {\n /* adapter handles destroy */\n },\n } as any); // eslint-disable-line @typescript-eslint/no-explicit-any\n };\n\n wsAdapter.onWebSocketDisconnect = (connId: string) => {\n // Abort any pending waiters (waitForElement / waitForCondition)\n // for this connection BEFORE removing it — stops leaked timers\n // and registry listeners.\n server.abortWaitersForConnection(connId);\n eventBridge.removeConnection(connId);\n };\n\n wsAdapter.onWebSocketUpgrade = (req: HTTPRequest) => server.handleWebSocketUpgrade(req);\n\n wsAdapter.onWebSocketMessage = async (connId: string, message: string) => {\n return server.handleNetworkWebSocketMessage(connId, message);\n };\n\n eventBridge.start();\n eventBridgeRef.current = eventBridge;\n }\n\n try {\n await server.start();\n serverRef.current = server;\n // The id may have loaded during the await, when there was no ref to forward to.\n server.setDeviceId(deviceIdRef.current);\n setServerRunning(true);\n } catch (err) {\n console.warn('[ui-bridge-native] Failed to start HTTP server:', err);\n }\n }, [\n features.server,\n features.testHooks,\n features.observability,\n config.serverPort,\n config.appInfo,\n registry,\n executor,\n serverAdapter,\n navigationProvider,\n screenshotProvider,\n keepAwakeProvider,\n routeProvider,\n pairingStore,\n allowUnauthenticatedNetwork,\n ]);\n\n const stopServer = useCallback(() => {\n if (eventBridgeRef.current) {\n eventBridgeRef.current.stop();\n eventBridgeRef.current = null;\n }\n if (serverRef.current) {\n serverRef.current.stop().catch(() => {});\n serverRef.current = null;\n }\n setServerRunning(false);\n }, []);\n\n // Auto-start server if enabled\n useEffect(() => {\n if (features.server) {\n startServer();\n return () => stopServer();\n }\n }, [features.server, startServer, stopServer]);\n\n // Start mDNS announcement and cloud relay.\n //\n // The cloud relay path does NOT require the HTTP server to be running —\n // it calls `server.handleRequest()` directly, bypassing HTTP. This matters\n // for preview/production builds where `features.server` is typically\n // false (no native TCP server adapter available). We still need a\n // NativeUIBridgeServer instance to handle tunneled requests, so we create\n // a bare instance here if startServer() didn't already make one.\n //\n // mDNS also doesn't need the HTTP server — it just advertises on the\n // network so remote devices can discover this phone's address.\n useEffect(() => {\n // Everything here is best-effort — any exception must not crash the app.\n try {\n const needsBareServer = !serverRef.current && !!cloudRelayConfig;\n if (needsBareServer) {\n try {\n // Create a server instance for handleRequest() use only; no adapter,\n // no HTTP listener. This is safe without features.server.\n const bareServer = createNativeServer(registry, executor, {\n serverPort: config.serverPort || 8087,\n cors: true,\n appInfo: config.appInfo,\n testHooks: features.testHooks === true,\n observability: features.observability,\n viewportProvider: readWindowViewport,\n });\n if (navigationProvider) bareServer.setNavigationProvider(navigationProvider);\n if (screenshotProvider) bareServer.setScreenshotProvider(screenshotProvider);\n if (keepAwakeProvider) bareServer.setKeepAwakeProvider(keepAwakeProvider);\n if (routeProvider) bareServer.setRouteProvider(routeProvider);\n serverRef.current = bareServer;\n } catch (err) {\n console.warn('[ui-bridge-native] Failed to create bare server:', err);\n }\n }\n\n // mDNS advertisement\n if (enableMdnsAnnounce && deviceId) {\n try {\n const announcer = new DeviceAnnouncer({\n deviceId,\n appId: config.appInfo?.appId ?? 'unknown',\n port: config.serverPort ?? 8087,\n cloudRelayUrl: cloudRelayConfig?.relayUrl,\n cloudToken: cloudRelayConfig?.authToken,\n });\n announcerRef.current = announcer;\n void announcer.startMdnsAdvertise(zeroconf).catch((err) => {\n console.warn('[ui-bridge-native] mDNS advertise failed:', err);\n });\n } catch (err) {\n console.warn('[ui-bridge-native] Failed to start DeviceAnnouncer:', err);\n }\n }\n\n // Cloud relay tunnel (works without HTTP server)\n if (cloudRelayConfig && serverRef.current) {\n try {\n const relayClient = new CloudRelayClient({\n ...cloudRelayConfig,\n uiBridgeServer: serverRef.current,\n });\n cloudRelayRef.current = relayClient;\n relayClient.start();\n } catch (err) {\n console.warn('[ui-bridge-native] Failed to start CloudRelayClient:', err);\n }\n }\n } catch (err) {\n console.warn('[ui-bridge-native] Transport effect failed:', err);\n }\n\n return () => {\n try {\n if (announcerRef.current) {\n void announcerRef.current.stop().catch(() => {});\n announcerRef.current = null;\n }\n if (cloudRelayRef.current) {\n cloudRelayRef.current.stop();\n cloudRelayRef.current = null;\n }\n } catch {\n // cleanup must never throw\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n serverRunning,\n cloudRelayConfig?.relayUrl,\n cloudRelayConfig?.authToken,\n enableMdnsAnnounce,\n zeroconf,\n deviceId,\n ]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n stopServer();\n resetGlobalRegistry();\n };\n }, [stopServer]);\n\n // Context methods\n const getElements = useCallback(() => registry.getAllElements(), [registry]);\n\n const getComponents = useCallback(() => registry.getAllComponents(), [registry]);\n\n const createSnapshot = useCallback(() => registry.createSnapshot(), [registry]);\n\n const getCurrentRoute = useCallback(() => {\n return routeProvider?.getCurrentRoute() ?? null;\n }, [routeProvider]);\n\n const armPairingCode = useCallback(async () => {\n if (!serverRef.current) {\n throw new PairingError('PAIRING_UNCONFIGURED', 'The UI Bridge server is not running.');\n }\n return serverRef.current.armPairingCode();\n }, []);\n\n const disarmPairingCode = useCallback(() => {\n serverRef.current?.disarmPairingCode();\n }, []);\n\n const getPairingState = useCallback(\n () => serverRef.current?.getPairingState() ?? UNCONFIGURED_PAIRING_STATE,\n []\n );\n\n const revokePairing = useCallback(async () => {\n if (!serverRef.current) {\n throw new PairingError('PAIRING_UNCONFIGURED', 'The UI Bridge server is not running.');\n }\n await serverRef.current.revokePairing();\n }, []);\n\n // Mark the previous route's elements offscreen when the active route changes.\n // Without this, React Navigation's persistent-mount behavior leaves stale\n // layouts in the registry for tabs the user has left. Elements on the new\n // route re-fire `onLayout` and repopulate their layout.\n useEffect(() => {\n if (!routeProvider) return;\n\n let lastRoute: string | null = routeProvider.getCurrentRoute();\n\n return routeProvider.subscribe((current: string | null) => {\n if (current !== lastRoute && lastRoute != null) {\n registry.markRouteOffscreen(lastRoute);\n }\n lastRoute = current;\n });\n }, [routeProvider, registry]);\n\n // Wire trackers into the registry's snapshot enricher slots. Trackers are\n // stable refs, so this effect runs exactly once per provider lifecycle.\n useEffect(() => {\n registry.setEnrichers({ modalDetector, toastCapture, undoTracker });\n }, [registry, modalDetector, toastCapture, undoTracker]);\n\n // Mirror successful bridge actions into UndoTracker's rolling history so\n // snapshots can surface action-correlation undo context without each\n // action site needing to call recordAction manually.\n useEffect(() => {\n const unsubscribe = executor.onActionExecuted((event) => {\n if (!event.success) return;\n undoTracker.recordAction({\n id: event.requestId ?? `action-${event.timestamp}`,\n type: event.action,\n targetId: event.elementId,\n timestamp: event.timestamp,\n reversible: true,\n });\n });\n return unsubscribe;\n }, [executor, undoTracker]);\n\n const on = useCallback(\n <T = unknown,>(type: BridgeEventType, listener: BridgeEventListener<T>) =>\n registry.on(type, listener),\n [registry]\n );\n\n const off = useCallback(\n <T = unknown,>(type: BridgeEventType, listener: BridgeEventListener<T>) =>\n registry.off(type, listener),\n [registry]\n );\n\n const contextValue = useMemo<UIBridgeNativeContextValue>(\n () => ({\n features,\n config,\n registry,\n executor,\n modalDetector,\n toastCapture,\n undoTracker,\n getElements,\n getComponents,\n createSnapshot,\n on,\n off,\n initialized: true,\n serverRunning,\n startServer,\n stopServer,\n getCurrentRoute,\n armPairingCode,\n disarmPairingCode,\n getPairingState,\n revokePairing,\n }),\n [\n features,\n config,\n registry,\n executor,\n modalDetector,\n toastCapture,\n undoTracker,\n getElements,\n getComponents,\n createSnapshot,\n on,\n off,\n serverRunning,\n startServer,\n stopServer,\n getCurrentRoute,\n armPairingCode,\n disarmPairingCode,\n getPairingState,\n revokePairing,\n ]\n );\n\n return <UIBridgeNativeContext value={contextValue}>{children}</UIBridgeNativeContext>;\n}\n\n/**\n * useUIBridgeNative hook\n *\n * Access the UI Bridge Native context. Throws if used outside provider.\n */\nexport function useUIBridgeNative(): UIBridgeNativeContextValue {\n const context = use(UIBridgeNativeContext);\n if (!context) {\n throw new Error('useUIBridgeNative must be used within a UIBridgeNativeProvider');\n }\n return context;\n}\n\n/**\n * useUIBridgeNativeOptional hook\n *\n * Access the UI Bridge Native context, returning null if outside provider.\n */\nexport function useUIBridgeNativeOptional(): UIBridgeNativeContextValue | null {\n return use(UIBridgeNativeContext);\n}\n\n/**\n * useUIBridgeNativeRequired hook\n *\n * Alias for useUIBridgeNative (throws if outside provider).\n */\nexport const useUIBridgeNativeRequired = useUIBridgeNative;\n","/**\n * UI Bridge Native Debug Inspector\n *\n * An overlay component that shows registered elements and allows\n * inspection of the UI Bridge state.\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport {\n View,\n Text,\n StyleSheet,\n TouchableOpacity,\n ScrollView,\n Modal,\n Dimensions,\n} from 'react-native';\nimport { useUIBridgeNativeOptional } from '../react/UIBridgeNativeProvider';\nimport type { RegisteredNativeElement } from '../core/types';\n\n/**\n * Inspector props\n */\nexport interface UIBridgeInspectorProps {\n /** Whether to show the inspector */\n visible?: boolean;\n /** Callback when inspector is closed */\n onClose?: () => void;\n /** Position of the inspector toggle button */\n togglePosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n}\n\n/**\n * Element info card\n */\nfunction ElementCard({\n element,\n onPress,\n}: {\n element: RegisteredNativeElement;\n onPress: (element: RegisteredNativeElement) => void;\n}) {\n const state = element.getState();\n const identifier = element.getIdentifier();\n\n return (\n <TouchableOpacity style={styles.elementCard} onPress={() => onPress(element)}>\n <View style={styles.elementHeader}>\n <Text style={styles.elementId}>{element.id}</Text>\n <Text style={styles.elementType}>{element.type}</Text>\n </View>\n {element.label && <Text style={styles.elementLabel}>{element.label}</Text>}\n <View style={styles.stateRow}>\n <StateIndicator label=\"Mounted\" value={state.mounted} />\n <StateIndicator label=\"Visible\" value={state.visible} />\n <StateIndicator label=\"Enabled\" value={state.enabled} />\n </View>\n {identifier.testId && <Text style={styles.testId}>testID: {identifier.testId}</Text>}\n </TouchableOpacity>\n );\n}\n\n/**\n * State indicator\n */\nfunction StateIndicator({ label, value }: { label: string; value: boolean }) {\n return (\n <View style={styles.stateIndicator}>\n <View style={[styles.stateDot, { backgroundColor: value ? '#4CAF50' : '#F44336' }]} />\n <Text style={styles.stateLabel}>{label}</Text>\n </View>\n );\n}\n\n/**\n * Element detail view\n */\nfunction ElementDetail({\n element,\n onClose,\n}: {\n element: RegisteredNativeElement;\n onClose: () => void;\n}) {\n const state = element.getState();\n const identifier = element.getIdentifier();\n\n return (\n <View style={styles.detailContainer}>\n <View style={styles.detailHeader}>\n <Text style={styles.detailTitle}>{element.id}</Text>\n <TouchableOpacity onPress={onClose}>\n <Text style={styles.closeButton}>Close</Text>\n </TouchableOpacity>\n </View>\n\n <ScrollView style={styles.detailContent}>\n <Text style={styles.sectionTitle}>Type</Text>\n <Text style={styles.sectionValue}>{element.type}</Text>\n\n {element.label && (\n <>\n <Text style={styles.sectionTitle}>Label</Text>\n <Text style={styles.sectionValue}>{element.label}</Text>\n </>\n )}\n\n <Text style={styles.sectionTitle}>State</Text>\n <View style={styles.stateSection}>\n <Text style={styles.stateText}>Mounted: {String(state.mounted)}</Text>\n <Text style={styles.stateText}>Visible: {String(state.visible)}</Text>\n <Text style={styles.stateText}>Enabled: {String(state.enabled)}</Text>\n <Text style={styles.stateText}>Focused: {String(state.focused)}</Text>\n {state.value !== undefined && <Text style={styles.stateText}>Value: {state.value}</Text>}\n </View>\n\n {state.layout && (\n <>\n <Text style={styles.sectionTitle}>Layout</Text>\n <View style={styles.stateSection}>\n <Text style={styles.stateText}>\n Position: ({state.layout.x}, {state.layout.y})\n </Text>\n <Text style={styles.stateText}>\n Size: {state.layout.width} x {state.layout.height}\n </Text>\n <Text style={styles.stateText}>\n Page: ({state.layout.pageX}, {state.layout.pageY})\n </Text>\n </View>\n </>\n )}\n\n <Text style={styles.sectionTitle}>Identifier</Text>\n <View style={styles.stateSection}>\n {identifier.uiId && <Text style={styles.stateText}>uiId: {identifier.uiId}</Text>}\n {identifier.testId && <Text style={styles.stateText}>testId: {identifier.testId}</Text>}\n {identifier.accessibilityLabel && (\n <Text style={styles.stateText}>a11yLabel: {identifier.accessibilityLabel}</Text>\n )}\n <Text style={styles.stateText}>treePath: {identifier.treePath}</Text>\n </View>\n\n <Text style={styles.sectionTitle}>Actions</Text>\n <View style={styles.actionsSection}>\n {element.actions.map((action) => (\n <View key={action} style={styles.actionBadge}>\n <Text style={styles.actionText}>{action}</Text>\n </View>\n ))}\n </View>\n\n {element.customActions && Object.keys(element.customActions).length > 0 && (\n <>\n <Text style={styles.sectionTitle}>Custom Actions</Text>\n <View style={styles.actionsSection}>\n {Object.keys(element.customActions).map((action) => (\n <View key={action} style={[styles.actionBadge, styles.customActionBadge]}>\n <Text style={styles.actionText}>{action}</Text>\n </View>\n ))}\n </View>\n </>\n )}\n </ScrollView>\n </View>\n );\n}\n\n/**\n * UI Bridge Inspector component\n *\n * Shows an overlay with information about registered elements.\n * Useful for debugging and development.\n *\n * @example\n * ```tsx\n * function App() {\n * const [showInspector, setShowInspector] = useState(false);\n *\n * return (\n * <UIBridgeNativeProvider features={{ debug: __DEV__ }}>\n * <MainContent />\n * {__DEV__ && (\n * <UIBridgeInspector\n * visible={showInspector}\n * onClose={() => setShowInspector(false)}\n * />\n * )}\n * </UIBridgeNativeProvider>\n * );\n * }\n * ```\n */\nexport function UIBridgeInspector({\n visible = false,\n onClose,\n togglePosition = 'bottom-right',\n}: UIBridgeInspectorProps) {\n const bridge = useUIBridgeNativeOptional();\n const [showInspector, setShowInspector] = useState(visible);\n const [selectedElement, setSelectedElement] = useState<RegisteredNativeElement | null>(null);\n\n const elements = useMemo(() => (bridge ? bridge.getElements() : []), [bridge]);\n\n const components = useMemo(() => (bridge ? bridge.getComponents() : []), [bridge]);\n\n const handleToggle = useCallback(() => {\n setShowInspector((prev) => !prev);\n }, []);\n\n const handleClose = useCallback(() => {\n setShowInspector(false);\n onClose?.();\n }, [onClose]);\n\n const handleSelectElement = useCallback((element: RegisteredNativeElement) => {\n setSelectedElement(element);\n }, []);\n\n const handleCloseDetail = useCallback(() => {\n setSelectedElement(null);\n }, []);\n\n // Position style for toggle button\n const toggleStyle = useMemo(() => {\n switch (togglePosition) {\n case 'top-left':\n return { top: 50, left: 10 };\n case 'top-right':\n return { top: 50, right: 10 };\n case 'bottom-left':\n return { bottom: 50, left: 10 };\n case 'bottom-right':\n default:\n return { bottom: 50, right: 10 };\n }\n }, [togglePosition]);\n\n if (!bridge) {\n return null;\n }\n\n return (\n <>\n {/* Toggle button */}\n <TouchableOpacity style={[styles.toggleButton, toggleStyle]} onPress={handleToggle}>\n <Text style={styles.toggleText}>UI</Text>\n </TouchableOpacity>\n\n {/* Inspector modal */}\n <Modal\n visible={showInspector}\n animationType=\"slide\"\n transparent={true}\n onRequestClose={handleClose}\n >\n <View style={styles.modalContainer}>\n <View style={styles.inspectorContainer}>\n {/* Header */}\n <View style={styles.header}>\n <Text style={styles.headerTitle}>UI Bridge Inspector</Text>\n <TouchableOpacity onPress={handleClose}>\n <Text style={styles.closeButton}>X</Text>\n </TouchableOpacity>\n </View>\n\n {/* Stats */}\n <View style={styles.statsRow}>\n <View style={styles.stat}>\n <Text style={styles.statValue}>{elements.length}</Text>\n <Text style={styles.statLabel}>Elements</Text>\n </View>\n <View style={styles.stat}>\n <Text style={styles.statValue}>{components.length}</Text>\n <Text style={styles.statLabel}>Components</Text>\n </View>\n <View style={styles.stat}>\n <Text style={styles.statValue}>{bridge.serverRunning ? 'ON' : 'OFF'}</Text>\n <Text style={styles.statLabel}>Server</Text>\n </View>\n </View>\n\n {/* Element list or detail */}\n {selectedElement ? (\n <ElementDetail element={selectedElement} onClose={handleCloseDetail} />\n ) : (\n <ScrollView style={styles.elementList}>\n {elements.map((element) => (\n <ElementCard key={element.id} element={element} onPress={handleSelectElement} />\n ))}\n {elements.length === 0 && (\n <Text style={styles.emptyText}>No elements registered yet</Text>\n )}\n </ScrollView>\n )}\n </View>\n </View>\n </Modal>\n </>\n );\n}\n\nconst { height: screenHeight } = Dimensions.get('window');\n\nconst styles = StyleSheet.create({\n toggleButton: {\n position: 'absolute',\n width: 44,\n height: 44,\n borderRadius: 22,\n backgroundColor: '#2196F3',\n justifyContent: 'center',\n alignItems: 'center',\n shadowColor: '#000',\n shadowOffset: { width: 0, height: 2 },\n shadowOpacity: 0.25,\n shadowRadius: 4,\n elevation: 5,\n zIndex: 1000,\n },\n toggleText: {\n color: '#fff',\n fontWeight: 'bold',\n fontSize: 14,\n },\n modalContainer: {\n flex: 1,\n backgroundColor: 'rgba(0, 0, 0, 0.5)',\n justifyContent: 'flex-end',\n },\n inspectorContainer: {\n backgroundColor: '#1e1e1e',\n borderTopLeftRadius: 16,\n borderTopRightRadius: 16,\n maxHeight: screenHeight * 0.8,\n },\n header: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n padding: 16,\n borderBottomWidth: 1,\n borderBottomColor: '#333',\n },\n headerTitle: {\n color: '#fff',\n fontSize: 18,\n fontWeight: 'bold',\n },\n closeButton: {\n color: '#2196F3',\n fontSize: 16,\n fontWeight: 'bold',\n },\n statsRow: {\n flexDirection: 'row',\n padding: 12,\n borderBottomWidth: 1,\n borderBottomColor: '#333',\n },\n stat: {\n flex: 1,\n alignItems: 'center',\n },\n statValue: {\n color: '#fff',\n fontSize: 20,\n fontWeight: 'bold',\n },\n statLabel: {\n color: '#888',\n fontSize: 12,\n marginTop: 2,\n },\n elementList: {\n padding: 12,\n },\n elementCard: {\n backgroundColor: '#2d2d2d',\n borderRadius: 8,\n padding: 12,\n marginBottom: 8,\n },\n elementHeader: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n },\n elementId: {\n color: '#fff',\n fontSize: 14,\n fontWeight: 'bold',\n },\n elementType: {\n color: '#888',\n fontSize: 12,\n backgroundColor: '#444',\n paddingHorizontal: 8,\n paddingVertical: 2,\n borderRadius: 4,\n },\n elementLabel: {\n color: '#aaa',\n fontSize: 12,\n marginTop: 4,\n },\n stateRow: {\n flexDirection: 'row',\n marginTop: 8,\n },\n stateIndicator: {\n flexDirection: 'row',\n alignItems: 'center',\n marginRight: 12,\n },\n stateDot: {\n width: 8,\n height: 8,\n borderRadius: 4,\n marginRight: 4,\n },\n stateLabel: {\n color: '#888',\n fontSize: 10,\n },\n testId: {\n color: '#666',\n fontSize: 10,\n marginTop: 4,\n fontFamily: 'monospace',\n },\n emptyText: {\n color: '#888',\n textAlign: 'center',\n marginTop: 20,\n },\n detailContainer: {\n flex: 1,\n },\n detailHeader: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n padding: 12,\n borderBottomWidth: 1,\n borderBottomColor: '#333',\n },\n detailTitle: {\n color: '#fff',\n fontSize: 16,\n fontWeight: 'bold',\n },\n detailContent: {\n padding: 12,\n },\n sectionTitle: {\n color: '#888',\n fontSize: 12,\n marginTop: 12,\n marginBottom: 4,\n textTransform: 'uppercase',\n },\n sectionValue: {\n color: '#fff',\n fontSize: 14,\n },\n stateSection: {\n backgroundColor: '#2d2d2d',\n borderRadius: 8,\n padding: 8,\n },\n stateText: {\n color: '#ddd',\n fontSize: 12,\n fontFamily: 'monospace',\n marginBottom: 2,\n },\n actionsSection: {\n flexDirection: 'row',\n flexWrap: 'wrap',\n },\n actionBadge: {\n backgroundColor: '#2196F3',\n paddingHorizontal: 8,\n paddingVertical: 4,\n borderRadius: 4,\n marginRight: 6,\n marginBottom: 6,\n },\n customActionBadge: {\n backgroundColor: '#9C27B0',\n },\n actionText: {\n color: '#fff',\n fontSize: 12,\n },\n});\n"]}
|