peachsim 0.1.627 → 0.1.628

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 (92) hide show
  1. package/LICENSE +1 -1
  2. package/cli/bin.ts +6 -2
  3. package/cli/bridge-flow-runner.ts +79 -72
  4. package/cli/commands/cleanup.ts +6 -6
  5. package/cli/commands/control.ts +4 -5
  6. package/cli/commands/detox.ts +25 -11
  7. package/cli/commands/flow.ts +3 -2
  8. package/cli/commands/inspect.ts +22 -37
  9. package/cli/commands/mode.ts +11 -7
  10. package/cli/commands/report-issue.ts +1 -1
  11. package/cli/commands/setup.ts +16 -11
  12. package/cli/commands/show-touches-mode.ts +33 -0
  13. package/cli/commands/skills.ts +16 -0
  14. package/cli/commands/test.ts +8 -1
  15. package/cli/commands/welcome.ts +3 -3
  16. package/cli/drivers/playwright-sim-host.ts +6 -5
  17. package/cli/help-core.ts +9 -4
  18. package/cli/internal-child.ts +14 -2
  19. package/cli/levenshtein.ts +27 -0
  20. package/cli/main.ts +28 -18
  21. package/cli/ws-bridge.ts +3 -2
  22. package/detox/index.ts +234 -7
  23. package/dist-lib/agent-daemon-client.cjs +1 -1
  24. package/dist-lib/agent-events.cjs +1 -1
  25. package/dist-lib/agent-identity.cjs +1 -1
  26. package/dist-lib/agent-sessions.cjs +1 -1
  27. package/dist-lib/attached-projects.cjs +1 -1
  28. package/dist-lib/auth/shared-session.cjs +1 -1
  29. package/dist-lib/backend-origin.cjs +1 -1
  30. package/dist-lib/beta.cjs +1 -1
  31. package/dist-lib/beta.mjs +1 -1
  32. package/dist-lib/bridge-constants.cjs +1 -1
  33. package/dist-lib/bridge-contract-input.cjs +1 -1
  34. package/dist-lib/bridge-contract-input.mjs +1 -1
  35. package/dist-lib/bridge-contract.cjs +1 -1
  36. package/dist-lib/bridge-contract.mjs +1 -1
  37. package/dist-lib/capture-contract.cjs +1 -1
  38. package/dist-lib/capture-contract.mjs +1 -1
  39. package/dist-lib/cli-constants.cjs +1 -1
  40. package/dist-lib/cloud-contract.cjs +1 -1
  41. package/dist-lib/cloud-contract.mjs +1 -1
  42. package/dist-lib/cloud-test-args.cjs +1 -1
  43. package/dist-lib/cloud-test-args.mjs +1 -1
  44. package/dist-lib/cloud.cjs +1 -1
  45. package/dist-lib/cloud.mjs +1 -1
  46. package/dist-lib/config.cjs +1 -1
  47. package/dist-lib/detox/index.cjs +170 -13
  48. package/dist-lib/dev-bundle-resolution.cjs +2 -2
  49. package/dist-lib/home-paths.cjs +1 -1
  50. package/dist-lib/host/bridge-host.cjs +42 -37
  51. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  52. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  53. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  54. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  55. package/dist-lib/host/websocket-proxy.cjs +1 -1
  56. package/dist-lib/index.cjs +44 -5
  57. package/dist-lib/jump-to-source-babel.cjs +1 -1
  58. package/dist-lib/jump-to-source-native.cjs +1 -1
  59. package/dist-lib/menu.cjs +7 -1
  60. package/dist-lib/menu.mjs +7 -1
  61. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  62. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  63. package/dist-lib/metro-production-bundle.cjs +1 -1
  64. package/dist-lib/metro-production-bundle.mjs +1 -1
  65. package/dist-lib/metro.cjs +43 -4
  66. package/dist-lib/profiles.cjs +1 -1
  67. package/dist-lib/public-brand.cjs +1 -1
  68. package/dist-lib/react-native-host-modules.cjs +1 -1
  69. package/dist-lib/react-native-host-modules.mjs +1 -1
  70. package/dist-lib/render-mode.cjs +1 -1
  71. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  72. package/dist-lib/sdk.cjs +1 -1
  73. package/dist-lib/sdk.mjs +1 -1
  74. package/dist-lib/skills.cjs +140 -149
  75. package/dist-lib/swift-compile.cjs +1 -1
  76. package/dist-lib/swift-compile.mjs +1 -1
  77. package/dist-lib/swift-package.cjs +1 -1
  78. package/dist-lib/swift-package.mjs +1 -1
  79. package/dist-lib/swift-submit.cjs +1 -1
  80. package/dist-lib/swift-submit.mjs +1 -1
  81. package/dist-lib/swift.cjs +7234 -7069
  82. package/dist-lib/vite-kotlin.cjs +1 -1
  83. package/dist-lib/vite-swift.cjs +1 -1
  84. package/dist-lib/vite.cjs +43 -4
  85. package/package.json +1 -1
  86. package/skills/contrast/SKILL.md +1 -1
  87. package/src/dev-bundle-resolution.ts +1 -1
  88. package/src/host/bridge-host.ts +2 -34
  89. package/src/menu.ts +7 -0
  90. package/src/native-seam-manifest.ts +4 -16
  91. package/src/runtime-assets.ts +59 -2
  92. package/src/vite-plugin.ts +723 -25
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.627 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.627 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.627 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -661,7 +661,11 @@ var MIME_TYPES = {
661
661
  ".woff": "font/woff",
662
662
  ".woff2": "font/woff2",
663
663
  ".mp3": "audio/mpeg",
664
- ".wav": "audio/wav"
664
+ ".wav": "audio/wav",
665
+ ".mp4": "video/mp4",
666
+ ".m4v": "video/mp4",
667
+ ".mov": "video/quicktime",
668
+ ".webm": "video/webm"
665
669
  };
666
670
  var ROOT_RUNTIME_PATHS = [
667
671
  "/assets/",
@@ -675,7 +679,7 @@ var ROOT_RUNTIME_PATHS = [
675
679
  "/spike/",
676
680
  "/test-wallpaper.jpg",
677
681
  "/preview-sw.js",
678
- "/sootsim.svg",
682
+ "/peach.svg",
679
683
  "/bedrock-sqlite.wasm",
680
684
  "/bedrock-sqlite.js",
681
685
  "/sootsim/"
@@ -759,7 +763,42 @@ function serveRuntimeFile(req, res, fullPath) {
759
763
  res.end();
760
764
  return;
761
765
  }
762
- import_fs.default.createReadStream(fullPath).pipe(res);
766
+ sendFileWithRanges(req, res, fullPath, stat.size, {});
767
+ }
768
+ function sendFileWithRanges(req, res, filePath, total, headers) {
769
+ const head = (req?.method || "GET").toUpperCase() === "HEAD";
770
+ const range = /^bytes=(\d*)-(\d*)$/.exec(req?.headers?.range || "");
771
+ const invalid = range && range[1] && range[2] && Number(range[2]) < Number(range[1]);
772
+ if (range && (range[1] || range[2]) && !invalid) {
773
+ const [, startRaw, endRaw] = range;
774
+ const start = startRaw ? Number(startRaw) : Math.max(0, total - Number(endRaw));
775
+ const end = startRaw && endRaw ? Math.min(Number(endRaw), total - 1) : total - 1;
776
+ if (start > end || start >= total) {
777
+ res.writeHead(416, { ...headers, "Content-Range": `bytes */${total}` });
778
+ res.end();
779
+ return;
780
+ }
781
+ res.writeHead(206, {
782
+ ...headers,
783
+ "Content-Range": `bytes ${start}-${end}/${total}`,
784
+ "Accept-Ranges": "bytes",
785
+ "Content-Length": String(end - start + 1)
786
+ });
787
+ if (head) res.end();
788
+ else streamFile(res, import_fs.default.createReadStream(filePath, { start, end }));
789
+ return;
790
+ }
791
+ res.writeHead(200, {
792
+ ...headers,
793
+ "Accept-Ranges": "bytes",
794
+ "Content-Length": String(total)
795
+ });
796
+ if (head || total === 0) res.end();
797
+ else streamFile(res, import_fs.default.createReadStream(filePath, { start: 0, end: total - 1 }));
798
+ }
799
+ function streamFile(res, stream) {
800
+ stream.on("error", (error) => res.destroy(error));
801
+ stream.pipe(res);
763
802
  }
764
803
 
765
804
  // src/vite-plugin-one.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peachsim",
3
- "version": "0.1.627",
3
+ "version": "0.1.628",
4
4
  "description": "Vite and Metro plugins, testing drivers, and SDK exports for peach.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -35,7 +35,7 @@ automation, inspecting the render tree, and managing sims.
35
35
  | `peach do` | drive the app (tap, type, scroll, swipe, key, reload, …) |
36
36
  | `peach wait` | block until the runtime reaches a known state |
37
37
  | `peach shell` | drive shell state and simulator chrome |
38
- | `peach mode` | toggle screenshot and 3D display modes |
38
+ | `peach mode` | toggle screenshot, touch indicators, and 3D display modes |
39
39
  | `peach device` | inspect or change the live device preset for a sim |
40
40
  | `peach network` | inspect live network traffic from the running peach worker |
41
41
  | `peach logs` | inspect live console output from the running peach worker |
@@ -20,7 +20,7 @@ export interface ResolvedDevBundle {
20
20
 
21
21
  function buildUnresolvedTargetError(targetLabel: string): Error {
22
22
  return new Error(
23
- `could not resolve a native bundle for ${targetLabel}. pass an explicit bundle URL or open Connect and choose the app there.`,
23
+ `could not resolve a native bundle for ${targetLabel}. start the app's dev server (for example npx expo start), pass an explicit bundle URL, or open Connect and choose the app there.`,
24
24
  )
25
25
  }
26
26
 
@@ -41,6 +41,7 @@ import {
41
41
  resolveDevCheckoutRuntimeRoot,
42
42
  resolveRuntimeRootForHostname,
43
43
  runtimeVersionFromHostname,
44
+ sendFileWithRanges,
44
45
  } from '../runtime-assets.ts'
45
46
  import { rnxRuntime } from '../runtime-delivery.ts'
46
47
  import { AgentHost, type AgentHostOptions } from './agent-host.ts'
@@ -1304,43 +1305,10 @@ export class SootSimBridgeHost {
1304
1305
 
1305
1306
  const contentType =
1306
1307
  HTTP_MIME_TYPES[path.extname(filePath).toLowerCase()] || 'application/octet-stream'
1307
- const total = stats.size
1308
- const range = /^bytes=(\d*)-(\d*)$/.exec(req.headers.range || '')
1309
- if (range) {
1310
- const [, startRaw, endRaw] = range
1311
- const start = startRaw ? Number(startRaw) : 0
1312
- const end = endRaw ? Math.min(Number(endRaw), total - 1) : total - 1
1313
- if (!Number.isFinite(start) || start > end || start >= total) {
1314
- res.writeHead(416, { ...cors, 'Content-Range': `bytes */${total}` })
1315
- res.end()
1316
- return
1317
- }
1318
- res.writeHead(206, {
1319
- ...cors,
1320
- 'Content-Type': contentType,
1321
- 'Content-Range': `bytes ${start}-${end}/${total}`,
1322
- 'Accept-Ranges': 'bytes',
1323
- 'Content-Length': String(end - start + 1),
1324
- })
1325
- if (method === 'HEAD') {
1326
- res.end()
1327
- return
1328
- }
1329
- fs.createReadStream(filePath, { start, end }).pipe(res)
1330
- return
1331
- }
1332
-
1333
- res.writeHead(200, {
1308
+ sendFileWithRanges(req, res, filePath, stats.size, {
1334
1309
  ...cors,
1335
1310
  'Content-Type': contentType,
1336
- 'Accept-Ranges': 'bytes',
1337
- 'Content-Length': String(total),
1338
1311
  })
1339
- if (method === 'HEAD') {
1340
- res.end()
1341
- return
1342
- }
1343
- fs.createReadStream(filePath).pipe(res)
1344
1312
  }
1345
1313
 
1346
1314
  private handleServerScan(res: ServerResponse) {
package/src/menu.ts CHANGED
@@ -80,6 +80,7 @@ export interface SootSimMenuState {
80
80
  // can reach.
81
81
  developmentMode: boolean
82
82
  showBezels: boolean
83
+ showTouches: boolean
83
84
  showMenuBar: boolean
84
85
  showRail: boolean
85
86
  solidWindow: boolean
@@ -493,6 +494,12 @@ function windowMenu(state: SootSimMenuState): SootSimMenuDef {
493
494
  checked: state.showBezels,
494
495
  action: 'toggle-bezels',
495
496
  },
497
+ {
498
+ label: 'Show Touches',
499
+ type: 'checkbox',
500
+ checked: state.showTouches,
501
+ action: 'toggle-touches',
502
+ },
496
503
  {
497
504
  label: 'Show Menu Bar',
498
505
  type: 'checkbox',
@@ -275,25 +275,13 @@ export const NATIVE_SEAM_MANIFEST: NativeSeamEntry[] = [
275
275
  },
276
276
  {
277
277
  pkg: 'expo-image',
278
- // upstream splits the native surface across two files:
279
- // - src/ExpoImage.tsx → requireNativeViewManager('ExpoImage')
280
- // - src/ImageModule.ts → requireNativeModule('ExpoImage')
281
- // the view-component seam translates expo-image's prop shape
282
- // (contentFit, tintColor, source) onto sootsim's existing
283
- // <sootsim-image> host. no new canvas component is needed — sootsim's
284
- // image-loader + canvaskit-renderer already handle resizeMode,
285
- // tintColor, and onLoad/onError lifecycle.
286
- seamBasenames: ['ExpoImage'],
287
- target: seamPath('expo-image'),
288
- notes:
289
- 'expo-image native view — thin prop translator onto sootsim-image host (contentFit→resizeMode, tintColor passthrough); upstream Image.tsx + ImageBackground + useImage resolve from node_modules unchanged',
290
- },
291
- {
292
- pkg: 'expo-image',
278
+ // src/ImageModule.ts → requireNativeModule('ExpoImage'). the native view
279
+ // is src/ExpoImage.tsx's requireNativeViewManager('ExpoImage') call inside
280
+ // a real JS component, so it comes from expo-image-register instead.
293
281
  seamBasenames: ['ImageModule'],
294
282
  target: seamPath('expo-image-module'),
295
283
  notes:
296
- 'expo-image native module — prefetch/loadAsync route through sootsim image-loader; cache controls noop (browser owns HTTP cache); blurhash encode not yet implemented',
284
+ 'expo-image native module — prefetch/loadAsync/readFromCacheAsync route through the engine image loader, blurhash and thumbhash generation run the swift encoders, cache configuration has nothing to set in a browser',
297
285
  },
298
286
  {
299
287
  pkg: '@notifee/react-native',
@@ -7,6 +7,7 @@ import path from 'path'
7
7
  import { fileURLToPath } from 'url'
8
8
  import { isValidRuntimeVersion } from '@contrast/runtime-delivery'
9
9
  import { readActiveRuntime, runtimeDir } from './home-paths.ts'
10
+ import type { ServerResponse } from 'http'
10
11
 
11
12
  export const SOOTSIM_RUNTIME_MISSING_MESSAGE =
12
13
  '[peach] no engine runtime installed. run `peach runtime install`'
@@ -97,6 +98,10 @@ const MIME_TYPES: Record<string, string> = {
97
98
  '.woff2': 'font/woff2',
98
99
  '.mp3': 'audio/mpeg',
99
100
  '.wav': 'audio/wav',
101
+ '.mp4': 'video/mp4',
102
+ '.m4v': 'video/mp4',
103
+ '.mov': 'video/quicktime',
104
+ '.webm': 'video/webm',
100
105
  }
101
106
 
102
107
  const ROOT_RUNTIME_PATHS = [
@@ -111,7 +116,7 @@ const ROOT_RUNTIME_PATHS = [
111
116
  '/spike/',
112
117
  '/test-wallpaper.jpg',
113
118
  '/preview-sw.js',
114
- '/sootsim.svg',
119
+ '/peach.svg',
115
120
  '/bedrock-sqlite.wasm',
116
121
  '/bedrock-sqlite.js',
117
122
  '/sootsim/',
@@ -238,5 +243,57 @@ export function serveRuntimeFile(req: any, res: any, fullPath: string) {
238
243
  res.end()
239
244
  return
240
245
  }
241
- fs.createReadStream(fullPath).pipe(res)
246
+ sendFileWithRanges(req, res, fullPath, stat.size, {})
247
+ }
248
+
249
+ // a file body that honours a single `bytes=` range. a media element can only
250
+ // seek a resource whose server answers ranges, so any file a <video> or
251
+ // <audio> might load goes through here.
252
+ export function sendFileWithRanges(
253
+ req: { method?: string; headers?: { range?: string } } | undefined,
254
+ res: ServerResponse,
255
+ filePath: string,
256
+ total: number,
257
+ headers: Record<string, string>,
258
+ ) {
259
+ const head = (req?.method || 'GET').toUpperCase() === 'HEAD'
260
+ const range = /^bytes=(\d*)-(\d*)$/.exec(req?.headers?.range || '')
261
+ // a last byte before the first makes the header invalid, which a server
262
+ // ignores (RFC 9110 14.1.1)
263
+ const invalid = range && range[1] && range[2] && Number(range[2]) < Number(range[1])
264
+ if (range && (range[1] || range[2]) && !invalid) {
265
+ const [, startRaw, endRaw] = range
266
+ // a suffix range (bytes=-N) is the last N bytes
267
+ const start = startRaw ? Number(startRaw) : Math.max(0, total - Number(endRaw))
268
+ const end = startRaw && endRaw ? Math.min(Number(endRaw), total - 1) : total - 1
269
+ if (start > end || start >= total) {
270
+ res.writeHead(416, { ...headers, 'Content-Range': `bytes */${total}` })
271
+ res.end()
272
+ return
273
+ }
274
+ res.writeHead(206, {
275
+ ...headers,
276
+ 'Content-Range': `bytes ${start}-${end}/${total}`,
277
+ 'Accept-Ranges': 'bytes',
278
+ 'Content-Length': String(end - start + 1),
279
+ })
280
+ if (head) res.end()
281
+ else streamFile(res, fs.createReadStream(filePath, { start, end }))
282
+ return
283
+ }
284
+ res.writeHead(200, {
285
+ ...headers,
286
+ 'Accept-Ranges': 'bytes',
287
+ 'Content-Length': String(total),
288
+ })
289
+ // the body stops at the stat'd size so a file a rebuild is still growing
290
+ // cannot run past its Content-Length
291
+ if (head || total === 0) res.end()
292
+ else streamFile(res, fs.createReadStream(filePath, { start: 0, end: total - 1 }))
293
+ }
294
+
295
+ // the status line is already out, so a read failure can only cut the body
296
+ function streamFile(res: ServerResponse, stream: fs.ReadStream) {
297
+ stream.on('error', (error) => res.destroy(error))
298
+ stream.pipe(res)
242
299
  }