@limrun/ui 0.9.0-rc.4 → 0.9.0-rc.5

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.
Files changed (78) hide show
  1. package/README.md +0 -9
  2. package/dist/components/inspect-overlay.d.ts +33 -0
  3. package/dist/components/remote-control.d.ts +86 -0
  4. package/dist/core/ax-fetcher.d.ts +49 -0
  5. package/dist/core/ax-tree.d.ts +99 -0
  6. package/dist/index.cjs +1 -1
  7. package/dist/index.css +1 -1
  8. package/dist/index.d.ts +3 -2
  9. package/dist/index.js +1491 -777
  10. package/package.json +8 -17
  11. package/src/components/inspect-overlay.css +223 -0
  12. package/src/components/inspect-overlay.tsx +437 -0
  13. package/src/components/remote-control.tsx +547 -9
  14. package/src/core/ax-fetcher.test.ts +418 -0
  15. package/src/core/ax-fetcher.ts +377 -0
  16. package/src/core/ax-tree.test.ts +491 -0
  17. package/src/core/ax-tree.ts +416 -0
  18. package/src/demo.tsx +93 -10
  19. package/src/index.ts +17 -2
  20. package/vite.config.ts +2 -6
  21. package/vitest.config.ts +23 -0
  22. package/dist/components/device-install/device-install-dialog.d.ts +0 -5
  23. package/dist/components/device-install/index.d.ts +0 -2
  24. package/dist/core/device-install/apple/client.d.ts +0 -17
  25. package/dist/core/device-install/apple/crypto.d.ts +0 -20
  26. package/dist/core/device-install/apple/gsa-srp.d.ts +0 -26
  27. package/dist/core/device-install/apple/index.d.ts +0 -5
  28. package/dist/core/device-install/apple/provisioning.d.ts +0 -161
  29. package/dist/core/device-install/apple/relay.d.ts +0 -29
  30. package/dist/core/device-install/index.d.ts +0 -4
  31. package/dist/core/device-install/operations/index.d.ts +0 -6
  32. package/dist/core/device-install/operations/limbuild-client.d.ts +0 -28
  33. package/dist/core/device-install/operations/operations.d.ts +0 -32
  34. package/dist/core/device-install/operations/relay-client.d.ts +0 -25
  35. package/dist/core/device-install/operations/relay-protocol.d.ts +0 -27
  36. package/dist/core/device-install/operations/usbmux.d.ts +0 -32
  37. package/dist/core/device-install/operations/webusb.d.ts +0 -21
  38. package/dist/core/device-install/storage/browser-storage.d.ts +0 -44
  39. package/dist/core/device-install/storage/index.d.ts +0 -1
  40. package/dist/core/device-install/types.d.ts +0 -48
  41. package/dist/device-install/index.cjs +0 -1
  42. package/dist/device-install/index.d.ts +0 -3
  43. package/dist/device-install/index.js +0 -78
  44. package/dist/device-install/react.cjs +0 -1
  45. package/dist/device-install/react.d.ts +0 -1
  46. package/dist/device-install/react.js +0 -4
  47. package/dist/device-install-dialog-86RDdoK9.js +0 -2
  48. package/dist/device-install-dialog-CnyDWf0q.mjs +0 -462
  49. package/dist/device-install-dialog.css +0 -1
  50. package/dist/hooks/index.d.ts +0 -1
  51. package/dist/hooks/use-device-install.d.ts +0 -73
  52. package/dist/use-device-install-CbGVvwPp.js +0 -31
  53. package/dist/use-device-install-j1Gekpl4.mjs +0 -13623
  54. package/src/components/device-install/device-install-dialog.css +0 -325
  55. package/src/components/device-install/device-install-dialog.tsx +0 -513
  56. package/src/components/device-install/index.ts +0 -2
  57. package/src/core/device-install/apple/client.ts +0 -152
  58. package/src/core/device-install/apple/crypto.ts +0 -202
  59. package/src/core/device-install/apple/gsa-srp.ts +0 -127
  60. package/src/core/device-install/apple/index.ts +0 -5
  61. package/src/core/device-install/apple/provisioning.ts +0 -298
  62. package/src/core/device-install/apple/relay.ts +0 -221
  63. package/src/core/device-install/index.ts +0 -4
  64. package/src/core/device-install/operations/index.ts +0 -6
  65. package/src/core/device-install/operations/limbuild-client.ts +0 -104
  66. package/src/core/device-install/operations/operations.ts +0 -217
  67. package/src/core/device-install/operations/relay-client.ts +0 -255
  68. package/src/core/device-install/operations/relay-protocol.ts +0 -71
  69. package/src/core/device-install/operations/usbmux.ts +0 -270
  70. package/src/core/device-install/operations/webusb-dom.d.ts +0 -54
  71. package/src/core/device-install/operations/webusb.ts +0 -105
  72. package/src/core/device-install/storage/browser-storage.ts +0 -263
  73. package/src/core/device-install/storage/index.ts +0 -1
  74. package/src/core/device-install/types.ts +0 -65
  75. package/src/device-install/index.ts +0 -3
  76. package/src/device-install/react.ts +0 -1
  77. package/src/hooks/index.ts +0 -1
  78. package/src/hooks/use-device-install.ts +0 -1210
package/README.md CHANGED
@@ -4,15 +4,6 @@
4
4
 
5
5
  See [examples](../../examples/) to see how it can be used.
6
6
 
7
- ## Real Device Installation
8
-
9
- `@limrun/ui` also includes a browser-based iPhone installation flow:
10
-
11
- - `@limrun/ui/device-install/react` exports the headless `useDeviceInstall` hook for clients that want to render their own UI.
12
- - `@limrun/ui/device-install` exports the guided `DeviceInstallDialog` UI, which walks users through a signed device build, USB access, browser pairing, and installation.
13
-
14
- WebUSB requires a Chromium browser and a secure context. Pair records and signing assets, including the `.p12` password, are stored in the browser's IndexedDB.
15
-
16
7
  ### Releasing
17
8
 
18
9
  This package is not part of generated SDK, hence you need to publish it manually in GitHub Actions.
@@ -0,0 +1,33 @@
1
+ import { default as React } from 'react';
2
+ import { AxElement, AxSnapshot } from '../core/ax-tree';
3
+ export interface InspectOverlayGeometry {
4
+ left: number;
5
+ top: number;
6
+ width: number;
7
+ height: number;
8
+ }
9
+ export type InspectMode = 'select' | 'hover-only';
10
+ export interface InspectOverlayProps {
11
+ snapshot: AxSnapshot | null;
12
+ geometry: InspectOverlayGeometry | null;
13
+ highlightedId: string | null;
14
+ selectedId: string | null;
15
+ mode: InspectMode;
16
+ cursorPosition: {
17
+ x: number;
18
+ y: number;
19
+ } | null;
20
+ frozenCursorPosition: {
21
+ x: number;
22
+ y: number;
23
+ } | null;
24
+ onSelectChange: (element: AxElement | null, clickPosition?: {
25
+ x: number;
26
+ y: number;
27
+ } | null) => void;
28
+ onTapElement: (element: AxElement, tapAt: {
29
+ x: number;
30
+ y: number;
31
+ }) => void;
32
+ }
33
+ export declare const InspectOverlay: React.NamedExoticComponent<InspectOverlayProps>;
@@ -1,4 +1,6 @@
1
1
  import { default as React } from 'react';
2
+ import { AxStatus } from '../core/ax-fetcher';
3
+ import { AxElement, AxSnapshot } from '../core/ax-tree';
2
4
  declare global {
3
5
  interface Window {
4
6
  debugRemoteControl?: boolean;
@@ -12,6 +14,85 @@ interface RemoteControlProps {
12
14
  openUrl?: string;
13
15
  showFrame?: boolean;
14
16
  autoReconnect?: boolean;
17
+ /**
18
+ * Enable the inspect overlay. When set, the component starts polling the
19
+ * accessibility tree and draws boxes over each element on top of the
20
+ * video stream.
21
+ *
22
+ * - `true` — Select mode. Boxes are clickable, click pins a selection
23
+ * with action buttons (Tap / Copy selector / Copy id), ESC clears.
24
+ * Device input is blocked while in this mode.
25
+ * - `'hover-only'` — Boxes follow the cursor as a visual preview. Device
26
+ * input still passes through, so you can drive the simulator while
27
+ * inspecting.
28
+ * - `undefined` / `false` (default) — overlay disabled, no polling.
29
+ */
30
+ inspectMode?: boolean | 'hover-only';
31
+ /**
32
+ * Fires whenever a fresh accessibility snapshot is delivered.
33
+ *
34
+ * Customers use this to drive their own side panels, agent prompts,
35
+ * analytics, etc. The built-in overlay does not require this callback —
36
+ * it renders from internal state regardless.
37
+ *
38
+ * Identical-to-previous snapshots (per `axSnapshotsEqual`) are NOT
39
+ * re-emitted, so a stable UI doesn't generate callback noise.
40
+ *
41
+ * Invoked in a microtask so customer code doesn't run synchronously
42
+ * inside React's commit phase.
43
+ */
44
+ onAxSnapshotChange?: (snapshot: AxSnapshot | null) => void;
45
+ /**
46
+ * Fires when the user clicks an overlay element (only emitted when
47
+ * `inspectMode === true`). `null` indicates a deselection (ESC, click
48
+ * outside any box, or programmatic clear).
49
+ *
50
+ * The `snapshot` field is the snapshot active at the moment of the
51
+ * click — useful for capturing context without races against the next
52
+ * poll cycle.
53
+ */
54
+ onInspectSelectionChange?: (selection: {
55
+ element: AxElement;
56
+ snapshot: AxSnapshot;
57
+ } | null) => void;
58
+ /**
59
+ * Fires whenever the accessibility subsystem changes coarse-grained
60
+ * status. Useful for rendering readiness indicators or error banners in
61
+ * a customer-built side panel.
62
+ *
63
+ * Transitions are deduplicated; no self-loops are emitted. The `error`
64
+ * argument is populated when status is `error` or `unavailable`.
65
+ *
66
+ * Lifecycle: `idle` → `starting` → `ready` (or `unavailable` / `error`).
67
+ * Recovery from `error` / `unavailable` is automatic — the fetcher
68
+ * keeps polling and transitions back to `ready` on the next success.
69
+ */
70
+ onAxStatusChange?: (status: AxStatus, error?: string) => void;
71
+ /**
72
+ * Base interval (ms) between successful AX-tree fetches.
73
+ *
74
+ * The fetcher will:
75
+ * - Wait `axPollIntervalMs` after a successful fetch with NEW data.
76
+ * - Double the wait (up to `axMaxBackoffMs`) when consecutive snapshots
77
+ * are byte-identical (e.g. static screen).
78
+ * - Wait 5 s when the server reports AX is unavailable.
79
+ *
80
+ * In addition, after user input (taps, scrolls, keypresses, openUrl,
81
+ * terminateApp, orientation flips), the fetcher enters a short
82
+ * "activity boost" window (~1.2 s) during which fetches happen at
83
+ * ~250 ms regardless of this setting. This captures mid-animation UI
84
+ * changes without you having to manually call `refreshAxTree`.
85
+ *
86
+ * @default 500
87
+ */
88
+ axPollIntervalMs?: number;
89
+ /**
90
+ * Maximum backoff (ms) for the AX-tree polling loop when consecutive
91
+ * snapshots are unchanged.
92
+ *
93
+ * @default 2000
94
+ */
95
+ axMaxBackoffMs?: number;
15
96
  }
16
97
  interface ScreenshotData {
17
98
  dataUri: string;
@@ -30,6 +111,11 @@ export interface RemoteControlHandle {
30
111
  screenshot: () => Promise<ScreenshotData>;
31
112
  terminateApp: (bundleId: string) => Promise<void>;
32
113
  reconnect: () => void;
114
+ refreshAxTree: () => Promise<AxSnapshot>;
115
+ getAxSnapshot: () => AxSnapshot | null;
116
+ setInspectHighlight: (element: AxElement | null) => void;
117
+ setInspectSelection: (element: AxElement | null) => void;
118
+ getAxStatus: () => AxStatus;
33
119
  }
34
120
  export declare const RemoteControl: React.ForwardRefExoticComponent<RemoteControlProps & React.RefAttributes<RemoteControlHandle>>;
35
121
  export {};
@@ -0,0 +1,49 @@
1
+ import { AxPlatform, AxSnapshot } from './ax-tree';
2
+ export type AxStatus = 'idle' | 'starting' | 'ready' | 'unavailable' | 'error';
3
+ export type AxFetcherSendFn = (payload: Record<string, unknown>) => boolean;
4
+ export interface AxFetcherOptions {
5
+ platform: AxPlatform;
6
+ send: AxFetcherSendFn;
7
+ onSnapshot: (snapshot: AxSnapshot | null) => void;
8
+ onStatusChange?: (status: AxStatus, error?: string) => void;
9
+ baseIntervalMs?: number;
10
+ maxBackoffMs?: number;
11
+ }
12
+ export declare class AxFetcher {
13
+ private readonly platform;
14
+ private readonly send;
15
+ private readonly onSnapshot;
16
+ private readonly onStatusChange?;
17
+ private readonly baseIntervalMs;
18
+ private readonly maxBackoffMs;
19
+ private readonly pending;
20
+ private running;
21
+ private timer;
22
+ private currentInterval;
23
+ private lastSnapshot;
24
+ private inflight;
25
+ private lastBumpAtMs;
26
+ private boostUntilMs;
27
+ private status;
28
+ constructor(opts: AxFetcherOptions);
29
+ start(): void;
30
+ stop(): void;
31
+ getStatus(): AxStatus;
32
+ private setStatus;
33
+ bumpActivity(): void;
34
+ refresh(): Promise<AxSnapshot>;
35
+ getLatest(): AxSnapshot | null;
36
+ handleMessage(message: {
37
+ type?: string;
38
+ id?: string;
39
+ [k: string]: unknown;
40
+ }): boolean;
41
+ private buildRequest;
42
+ private requestOnce;
43
+ private runOnce;
44
+ private deliver;
45
+ private scheduleNext;
46
+ private settleResolve;
47
+ private settleReject;
48
+ private failAllPending;
49
+ }
@@ -0,0 +1,99 @@
1
+ export interface AxRect {
2
+ x: number;
3
+ y: number;
4
+ width: number;
5
+ height: number;
6
+ }
7
+ export interface AxSelectors {
8
+ AXUniqueId?: string;
9
+ AXLabel?: string;
10
+ resourceId?: string;
11
+ contentDesc?: string;
12
+ text?: string;
13
+ className?: string;
14
+ }
15
+ export interface AxElement {
16
+ id: string;
17
+ path: string;
18
+ label: string;
19
+ value: string;
20
+ role: string;
21
+ type: string;
22
+ enabled: boolean;
23
+ focused: boolean;
24
+ frame: AxRect;
25
+ selectors: AxSelectors;
26
+ raw: Record<string, unknown>;
27
+ }
28
+ export type AxPlatform = 'ios' | 'android';
29
+ export interface AxSnapshot {
30
+ platform: AxPlatform;
31
+ screen: {
32
+ width: number;
33
+ height: number;
34
+ };
35
+ elements: AxElement[];
36
+ capturedAt: number;
37
+ errors?: string[];
38
+ }
39
+ export declare const AX_UNAVAILABLE_ERROR = "Accessibility unavailable on this device.";
40
+ interface RawIosNode {
41
+ AXLabel?: string | null;
42
+ AXValue?: string | null;
43
+ AXUniqueId?: string | null;
44
+ frame?: {
45
+ x: number;
46
+ y: number;
47
+ width: number;
48
+ height: number;
49
+ };
50
+ role?: string;
51
+ role_description?: string;
52
+ type?: string;
53
+ subrole?: string | null;
54
+ title?: string | null;
55
+ enabled?: boolean;
56
+ focused?: boolean;
57
+ pid?: number;
58
+ traits?: string[];
59
+ children?: RawIosNode[];
60
+ [key: string]: unknown;
61
+ }
62
+ export declare function normalizeIosTree(roots: RawIosNode[] | RawIosNode): AxSnapshot;
63
+ interface RawAndroidParsedBounds {
64
+ left: number;
65
+ top: number;
66
+ right: number;
67
+ bottom: number;
68
+ centerX: number;
69
+ centerY: number;
70
+ }
71
+ interface RawAndroidNode {
72
+ index?: string;
73
+ text?: string;
74
+ resourceId?: string;
75
+ className?: string;
76
+ packageName?: string;
77
+ contentDesc?: string;
78
+ clickable?: boolean;
79
+ enabled?: boolean;
80
+ focusable?: boolean;
81
+ focused?: boolean;
82
+ scrollable?: boolean;
83
+ selected?: boolean;
84
+ bounds?: string;
85
+ parsedBounds?: RawAndroidParsedBounds;
86
+ [key: string]: unknown;
87
+ }
88
+ export declare function normalizeAndroidTree(nodes: RawAndroidNode[]): AxSnapshot;
89
+ export declare function clampAxFrameForScreen(frame: AxRect, screen: {
90
+ width: number;
91
+ height: number;
92
+ }): AxRect | null;
93
+ export declare function axElementsEqual(a: AxElement, b: AxElement): boolean;
94
+ export declare function axSnapshotsEqual(a: AxSnapshot | null, b: AxSnapshot | null): boolean;
95
+ export declare function axElementAtPoint(snapshot: AxSnapshot, x: number, y: number): AxElement | null;
96
+ export declare function axElementSelectorExpression(el: AxElement, platform: AxPlatform): string | null;
97
+ export declare function axElementRoleLabel(el: AxElement): string;
98
+ export declare function axElementSummary(el: AxElement): string;
99
+ export {};