@limrun/api 0.57.0 → 0.58.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/src/ios-client.ts CHANGED
@@ -253,14 +253,18 @@ export type LsofEntry = {
253
253
  path: string;
254
254
  };
255
255
 
256
+ export type DuoDisplay = 'outer' | 'inner';
257
+
256
258
  export type FoldDisplay = {
257
- id: 'outer' | 'inner';
259
+ id: DuoDisplay;
258
260
  screenId: number;
259
261
  trackId: string;
260
262
  width: number;
261
263
  height: number;
262
264
  scale: number;
263
265
  orientation: number;
266
+ /** True when this display presents content. Omitted if native activity is unavailable; transitions may activate both panels. */
267
+ active?: boolean;
264
268
  };
265
269
 
266
270
  export type DuoOrientation = 'portrait' | 'pud' | 'landscape-left' | 'landscape-right';
@@ -495,6 +499,8 @@ export type AppInstallationOptions = {
495
499
  * HID primitives (`touchDown`/`touchMove`/`touchUp`, `keyDown`/`keyUp`,
496
500
  * `buttonDown`/`buttonUp`) are deliberately unpaired so callers can build
497
501
  * their own gestures (e.g. long-press = `touchDown` + `wait` + `touchUp`).
502
+ * Supply x2 and y2 together for a second touch, keeping the same contact count
503
+ * through down, move, and up. Both contacts use the selected display space.
498
504
  */
499
505
  export type PerformAction =
500
506
  | { type: 'tap'; x: number; y: number; screenWidth?: number; screenHeight?: number }
@@ -518,9 +524,33 @@ export type PerformAction =
518
524
  | { type: 'openUrl'; url: string }
519
525
  | { type: 'setOrientation'; orientation: 'Portrait' | 'Landscape' }
520
526
  | { type: 'wait'; durationMs: number }
521
- | { type: 'touchDown'; x: number; y: number; screenWidth?: number; screenHeight?: number }
522
- | { type: 'touchMove'; x: number; y: number; screenWidth?: number; screenHeight?: number }
523
- | { type: 'touchUp'; x: number; y: number; screenWidth?: number; screenHeight?: number }
527
+ | {
528
+ type: 'touchDown';
529
+ x: number;
530
+ y: number;
531
+ x2?: number;
532
+ y2?: number;
533
+ screenWidth?: number;
534
+ screenHeight?: number;
535
+ }
536
+ | {
537
+ type: 'touchMove';
538
+ x: number;
539
+ y: number;
540
+ x2?: number;
541
+ y2?: number;
542
+ screenWidth?: number;
543
+ screenHeight?: number;
544
+ }
545
+ | {
546
+ type: 'touchUp';
547
+ x: number;
548
+ y: number;
549
+ x2?: number;
550
+ y2?: number;
551
+ screenWidth?: number;
552
+ screenHeight?: number;
553
+ }
524
554
  | { type: 'keyDown'; keyCode: number }
525
555
  | { type: 'keyUp'; keyCode: number }
526
556
  | {
@@ -577,7 +607,7 @@ export type IosMicrophoneStatus = {
577
607
  */
578
608
  export type InstanceClient = {
579
609
  /**
580
- * Take a screenshot of the current screen
610
+ * Take a screenshot of the active screen
581
611
  * @returns A promise that resolves to the screenshot data with base64 image and dimensions
582
612
  */
583
613
  screenshot: () => Promise<ScreenshotData>;
@@ -597,7 +627,7 @@ export type InstanceClient = {
597
627
  elementTreeRaw: (point?: AccessibilityPoint) => Promise<string>;
598
628
 
599
629
  /**
600
- * Tap at the specified coordinates (uses device's native screen dimensions)
630
+ * Tap at the specified coordinates on the active screen (uses native screen dimensions)
601
631
  * @param x X coordinate in points
602
632
  * @param y Y coordinate in points
603
633
  */
@@ -764,13 +794,14 @@ export type InstanceClient = {
764
794
  * @param direction Direction content moves: "up", "down", "left", "right"
765
795
  * @param pixels Total pixels to scroll (finger movement distance)
766
796
  * @param options Optional scroll options
797
+ * @param options.display Duo display to scroll, in upright screenshotDisplay points. Omit to use the active display.
767
798
  * @param options.coordinate Starting coordinate [x, y]. Defaults to screen center.
768
799
  * @param options.momentum 0.0-1.0 controlling scroll speed and inertia. 0 (default) = slow scroll, no momentum. 1 = fastest with max inertia.
769
800
  */
770
801
  scroll: (
771
802
  direction: 'up' | 'down' | 'left' | 'right',
772
803
  pixels: number,
773
- options?: { coordinate?: [number, number]; momentum?: number },
804
+ options?: { coordinate?: [number, number]; momentum?: number; display?: DuoDisplay },
774
805
  ) => Promise<void>;
775
806
 
776
807
  /**
@@ -793,23 +824,31 @@ export type InstanceClient = {
793
824
  * `options.timeoutMs` to override.
794
825
  *
795
826
  * @param actions The actions to run in order.
827
+ * @param options.display Duo display for coordinate taps, scrolls, and raw touches.
828
+ * Uses upright screenshotDisplay points. Omit to use the active display. Element actions follow the active display.
796
829
  * @param options.timeoutMs Custom client-side timeout in milliseconds.
797
830
  * @throws If any action fails — subsequent actions are not executed.
798
831
  */
799
832
  performActions: (
800
833
  actions: PerformAction[],
801
- options?: { timeoutMs?: number },
834
+ options?: { timeoutMs?: number; display?: DuoDisplay },
802
835
  ) => Promise<PerformActionsResult>;
803
836
 
804
837
  /**
805
- * Start recording simulator video. Use stopRecording() to stop the recording.
838
+ * Start recording the active screen. Use stopRecording() to stop the recording.
839
+ * On Duo, follows native display activity and fits each panel into the initial
840
+ * canvas without stretching. Pass display to keep recording one panel.
806
841
  * When provided, `quality` must be one of `5`, `6`, `7`, `8`, `9`, or `10`.
807
842
  * The server default is `5`.
808
843
  * With `persist`, the completed recording is uploaded to Limrun's bucket
809
844
  * when the recording stops or the instance terminates; list it with
810
845
  * `iosInstances.listRecordings`.
811
846
  */
812
- startRecording: (options?: { quality?: RecordingQuality; persist?: PersistOption }) => Promise<void>;
847
+ startRecording: (options?: {
848
+ quality?: RecordingQuality;
849
+ persist?: PersistOption;
850
+ display?: DuoDisplay;
851
+ }) => Promise<void>;
813
852
 
814
853
  /**
815
854
  * Stop the active server-side recording.
@@ -2108,12 +2147,9 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2108
2147
  };
2109
2148
 
2110
2149
  const tap = (x: number, y: number): Promise<void> => {
2111
- return sendRequest<void>('tap', {
2112
- x,
2113
- y,
2114
- screenWidth: cachedDeviceInfo?.screenWidth,
2115
- screenHeight: cachedDeviceInfo?.screenHeight,
2116
- });
2150
+ // Connection-time dimensions can describe a different panel or orientation.
2151
+ // Native-point taps leave geometry selection to the server.
2152
+ return sendRequest<void>('tap', { x, y });
2117
2153
  };
2118
2154
 
2119
2155
  /**
@@ -2187,14 +2223,29 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2187
2223
  // anchored drag lands in the wrong place in landscape. The server
2188
2224
  // starts center-screen in rotated space, which halves the effective
2189
2225
  // page but stays correct in every orientation.
2190
- const pagePixels = Math.round(cachedDeviceInfo.screenHeight * 0.6);
2226
+ let pageHeight = cachedDeviceInfo.screenHeight;
2227
+ let display: DuoDisplay | undefined;
2228
+ if (cachedDeviceInfo.model === 'iPhone Duo') {
2229
+ const state = await getFoldState();
2230
+ const activeDisplays = state?.displays.filter((candidate) => candidate.active) ?? [];
2231
+ if (activeDisplays.length !== 1) {
2232
+ throw new Error(
2233
+ 'Scroll search requires exactly one active Duo display; retry after the fold settles.',
2234
+ );
2235
+ }
2236
+ const active = activeDisplays[0]!;
2237
+ display = active.id;
2238
+ const rotated = active.orientation === 3 || active.orientation === 4;
2239
+ pageHeight = (rotated ? active.width : active.height) / active.scale;
2240
+ }
2241
+ const pagePixels = Math.round(pageHeight * 0.6);
2191
2242
  for (const [direction, count] of [
2192
2243
  ['down', 3],
2193
2244
  ['up', 6],
2194
2245
  ] as const) {
2195
2246
  for (let page = 0; page < count; page += 1) {
2196
2247
  if (Date.now() >= deadline) break;
2197
- await scroll(direction, pagePixels);
2248
+ await scroll(direction, pagePixels, display ? { display } : undefined);
2198
2249
  // Let lazily-materializing cells appear before re-reading.
2199
2250
  await sleep(300);
2200
2251
  try {
@@ -2386,33 +2437,45 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2386
2437
  const scroll = (
2387
2438
  direction: 'up' | 'down' | 'left' | 'right',
2388
2439
  pixels: number,
2389
- options?: { coordinate?: [number, number]; momentum?: number },
2440
+ options?: { coordinate?: [number, number]; momentum?: number; display?: DuoDisplay },
2390
2441
  ): Promise<void> => {
2391
2442
  return sendRequest<void>('scroll', {
2392
2443
  direction,
2393
2444
  pixels,
2394
2445
  coordinate: options?.coordinate,
2395
2446
  momentum: options?.momentum,
2447
+ display: options?.display,
2396
2448
  });
2397
2449
  };
2398
2450
 
2399
2451
  const performActions = (
2400
2452
  actions: PerformAction[],
2401
- options?: { timeoutMs?: number },
2453
+ options?: { timeoutMs?: number; display?: DuoDisplay },
2402
2454
  ): Promise<PerformActionsResult> => {
2403
2455
  // Batch duration is unbounded (user-supplied `wait` durations, plus
2404
2456
  // per-action server work), so we grow the timeout with the batch
2405
2457
  // rather than sticking to sendRequest's 30s default.
2406
2458
  const waitMs = actions.reduce((acc, a) => acc + (a.type === 'wait' ? Math.max(0, a.durationMs) : 0), 0);
2407
2459
  const timeoutMs = options?.timeoutMs ?? 30_000 + waitMs + actions.length * 2_000;
2408
- return sendRequest<PerformActionsResult>('performActions', { actions }, undefined, timeoutMs);
2460
+ return sendRequest<PerformActionsResult>(
2461
+ 'performActions',
2462
+ { actions, display: options?.display },
2463
+ undefined,
2464
+ timeoutMs,
2465
+ );
2409
2466
  };
2410
2467
 
2411
2468
  const startRecording = async (opts?: {
2412
2469
  quality?: RecordingQuality;
2413
2470
  persist?: PersistOption;
2471
+ display?: DuoDisplay;
2414
2472
  }): Promise<void> => {
2415
- const request: { quality?: RecordingQuality; persist?: boolean; ttlSeconds?: number } = {
2473
+ const request: {
2474
+ quality?: RecordingQuality;
2475
+ persist?: boolean;
2476
+ ttlSeconds?: number;
2477
+ display?: DuoDisplay;
2478
+ } = {
2416
2479
  ...persistFields(opts?.persist),
2417
2480
  };
2418
2481
  if (opts?.quality !== undefined) {
@@ -2421,6 +2484,7 @@ export async function createInstanceClient(options: InstanceClientOptions): Prom
2421
2484
  }
2422
2485
  request.quality = opts.quality;
2423
2486
  }
2487
+ if (opts?.display !== undefined) request.display = opts.display;
2424
2488
  await sendRequest<void>('startVideoRecording', request);
2425
2489
  };
2426
2490
 
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.57.0'; // x-release-please-version
1
+ export const VERSION = '0.58.0'; // x-release-please-version
package/version.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.57.0";
1
+ export declare const VERSION = "0.58.0";
2
2
  //# sourceMappingURL=version.d.mts.map
package/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.57.0";
1
+ export declare const VERSION = "0.58.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
- exports.VERSION = '0.57.0'; // x-release-please-version
4
+ exports.VERSION = '0.58.0'; // x-release-please-version
5
5
  //# sourceMappingURL=version.js.map
package/version.mjs CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.57.0'; // x-release-please-version
1
+ export const VERSION = '0.58.0'; // x-release-please-version
2
2
  //# sourceMappingURL=version.mjs.map