sootsim 0.1.296 → 0.1.298

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 (151) hide show
  1. package/detox/index.ts +380 -28
  2. package/detox/motion-change.cjs +109 -0
  3. package/detox/motion-change.d.cts +35 -0
  4. package/dist-cli/bin.js +5 -5
  5. package/dist-cli/chunks/{agent-3F4K6MQK.js → agent-U3HXOT6B.js} +2 -2
  6. package/dist-cli/chunks/{agent-wrapper-5LLVCQRZ.js → agent-wrapper-LJOPJUAN.js} +2 -2
  7. package/dist-cli/chunks/{app-fonts-LPTY7A5G.js → app-fonts-4EVLV2BX.js} +2 -2
  8. package/dist-cli/chunks/{assert-R2ZUXEMS.js → assert-NNKBTEO7.js} +2 -2
  9. package/dist-cli/chunks/{auth-CA62Q6IS.js → auth-SF2WR2K2.js} +2 -2
  10. package/dist-cli/chunks/{beta-4AXSOQLU.js → beta-SCLWNOHI.js} +2 -2
  11. package/dist-cli/chunks/{camera-Y4RBIONE.js → camera-TZJ2Z3TZ.js} +2 -2
  12. package/dist-cli/chunks/{chunk-MDBR4XTV.js → chunk-3R4W2SQB.js} +3 -3
  13. package/dist-cli/chunks/{chunk-X7B5KJ6N.js → chunk-4H3Y3TGT.js} +1 -1
  14. package/dist-cli/chunks/{chunk-4MDLYOEI.js → chunk-5VGTEGDT.js} +2 -2
  15. package/dist-cli/chunks/{chunk-F2MS365Z.js → chunk-6YIZZDG2.js} +2 -2
  16. package/dist-cli/chunks/{chunk-7V3JNANH.js → chunk-7ZOI5KJZ.js} +2 -2
  17. package/dist-cli/chunks/{chunk-QIDFGREY.js → chunk-AHMMVYVE.js} +1 -1
  18. package/dist-cli/chunks/{chunk-ZIBKGZJD.js → chunk-ANFOERLP.js} +1 -1
  19. package/dist-cli/chunks/{chunk-GG6XOQ5P.js → chunk-APNG6X2R.js} +2 -2
  20. package/dist-cli/chunks/{chunk-54TQ3ZGI.js → chunk-AT3NTZPP.js} +3 -3
  21. package/dist-cli/chunks/{chunk-CAAADPRH.js → chunk-BUM4RR4G.js} +2 -2
  22. package/dist-cli/chunks/{chunk-PEUWZRXA.js → chunk-CAEQ7AOI.js} +3 -3
  23. package/dist-cli/chunks/{chunk-C462URRQ.js → chunk-CK3MTK2D.js} +2 -2
  24. package/dist-cli/chunks/{chunk-NEARYPSC.js → chunk-CXSMZLI6.js} +3 -3
  25. package/dist-cli/chunks/{chunk-HSGS2U7P.js → chunk-EH4B5ZQ4.js} +2 -2
  26. package/dist-cli/chunks/{chunk-74FOMZP7.js → chunk-EYBWIEDO.js} +2 -2
  27. package/dist-cli/chunks/{chunk-B2FB6IYX.js → chunk-FTMTIUBK.js} +2 -2
  28. package/dist-cli/chunks/{chunk-FUPQIX4U.js → chunk-G3NMMKBL.js} +2 -2
  29. package/dist-cli/chunks/{chunk-MQ6QX46T.js → chunk-GM2TRB5Z.js} +2 -2
  30. package/dist-cli/chunks/{chunk-CS23DQB2.js → chunk-GR7USTCW.js} +2 -2
  31. package/dist-cli/chunks/{chunk-AUBP44CY.js → chunk-GXVG5LJG.js} +2 -2
  32. package/dist-cli/chunks/{chunk-7GQWF7HE.js → chunk-HAJSNFUY.js} +1 -1
  33. package/dist-cli/chunks/{chunk-222Y7NQA.js → chunk-HG63AS7K.js} +1 -1
  34. package/dist-cli/chunks/{chunk-LHUY46GE.js → chunk-HGSYUEQP.js} +2 -2
  35. package/dist-cli/chunks/{chunk-TIOPG6QZ.js → chunk-HOQD6TZN.js} +1 -1
  36. package/dist-cli/chunks/{chunk-CDCVWGNU.js → chunk-IBMZ7A6M.js} +2 -2
  37. package/dist-cli/chunks/{chunk-EISE56VD.js → chunk-IRMKZSWK.js} +2 -2
  38. package/dist-cli/chunks/{chunk-SUJSGGSS.js → chunk-J42Y66IM.js} +2 -2
  39. package/dist-cli/chunks/{chunk-FROCFR77.js → chunk-JM36NHNU.js} +1 -1
  40. package/dist-cli/chunks/{chunk-WJVBD2UL.js → chunk-KVYZYYSY.js} +1 -1
  41. package/dist-cli/chunks/{chunk-3PAZJSIG.js → chunk-MFMFNCBK.js} +2 -2
  42. package/dist-cli/chunks/{chunk-DUYKBCM7.js → chunk-MHCSRMK4.js} +1 -1
  43. package/dist-cli/chunks/{chunk-S5Z4HKWX.js → chunk-MTL4DPUK.js} +1 -1
  44. package/dist-cli/chunks/{chunk-ZPBIHFQW.js → chunk-NRC73NVF.js} +2 -2
  45. package/dist-cli/chunks/{chunk-6C24WCDK.js → chunk-PLEZAZTJ.js} +2 -2
  46. package/dist-cli/chunks/{chunk-IR3DUBRF.js → chunk-PYUI2PHI.js} +2 -2
  47. package/dist-cli/chunks/{chunk-R65XMN4D.js → chunk-QZCVY5BX.js} +1 -1
  48. package/dist-cli/chunks/{chunk-5FNPAMP4.js → chunk-SQR7NH32.js} +1 -1
  49. package/dist-cli/chunks/{chunk-XMVVZCIM.js → chunk-TB77ANGH.js} +2 -2
  50. package/dist-cli/chunks/chunk-THIBWEML.js +4 -0
  51. package/dist-cli/chunks/{chunk-N3FZRJT7.js → chunk-TTFO75GL.js} +1 -1
  52. package/dist-cli/chunks/{chunk-4NFGOGB7.js → chunk-U5TY536V.js} +2 -2
  53. package/dist-cli/chunks/{chunk-7YQXQSG7.js → chunk-UTUBMEFS.js} +1 -1
  54. package/dist-cli/chunks/{chunk-B2LDO55K.js → chunk-VFI23QUE.js} +1 -1
  55. package/dist-cli/chunks/{chunk-BVWIR4CD.js → chunk-WDKLXBQR.js} +1 -1
  56. package/dist-cli/chunks/{chunk-2XUKWIAL.js → chunk-WV6CBQYZ.js} +1 -1
  57. package/dist-cli/chunks/{chunk-2LQOLMRK.js → chunk-X3STT7I3.js} +1 -1
  58. package/dist-cli/chunks/{chunk-GG5PWLGC.js → chunk-XITV33JC.js} +2 -2
  59. package/dist-cli/chunks/{chunk-EJ6BAJKX.js → chunk-ZBETYTSD.js} +1 -1
  60. package/dist-cli/chunks/{chunk-G2SO5FPN.js → chunk-ZCHO67PX.js} +2 -2
  61. package/dist-cli/chunks/{cleanup-4RSKYPQ7.js → cleanup-YZPCNPWE.js} +2 -2
  62. package/dist-cli/chunks/{cli-version-VZFF7GGM.js → cli-version-ECA6G6JQ.js} +2 -2
  63. package/dist-cli/chunks/{compat-FABLN4UI.js → compat-OM7ODBMO.js} +2 -2
  64. package/dist-cli/chunks/{config-74J37LKC.js → config-ZIX6LPFG.js} +2 -2
  65. package/dist-cli/chunks/{control-VBK32Y7F.js → control-BY6UAMKN.js} +2 -2
  66. package/dist-cli/chunks/daemon-BQ2T53ZA.js +4 -0
  67. package/dist-cli/chunks/{debug-AOYA2CLR.js → debug-MVPFRIQA.js} +3 -3
  68. package/dist-cli/chunks/{desktop-G7XWDCTM.js → desktop-ZXH7NRHW.js} +3 -3
  69. package/dist-cli/chunks/{detox-QXJ3L2ZI.js → detox-4RZ5UER3.js} +2 -2
  70. package/dist-cli/chunks/{device-NZZ5ZT3Q.js → device-7R4BSFMS.js} +2 -2
  71. package/dist-cli/chunks/{diagnose-5CAEJ42M.js → diagnose-4JAFIXWD.js} +2 -2
  72. package/dist-cli/chunks/disk-cleanup-3E7GYLGW.js +4 -0
  73. package/dist-cli/chunks/drivers-2OW33GOL.js +4 -0
  74. package/dist-cli/chunks/{film-C25VRUNC.js → film-Q7KZIGBH.js} +3 -3
  75. package/dist-cli/chunks/flow-QTOSAQ5X.js +4 -0
  76. package/dist-cli/chunks/help-UE32FDEY.js +4 -0
  77. package/dist-cli/chunks/{hidden-runtime-alias-NSLIK6XT.js → hidden-runtime-alias-4ZUCLCVJ.js} +2 -2
  78. package/dist-cli/chunks/{home-paths-OC6JZXIM.js → home-paths-PVZPREN7.js} +2 -2
  79. package/dist-cli/chunks/inspect-QWR2U3DK.js +4 -0
  80. package/dist-cli/chunks/install-desktop-R2ZM5ZJH.js +4 -0
  81. package/dist-cli/chunks/{login-LB4TMM4T.js → login-UMP65DJG.js} +4 -4
  82. package/dist-cli/chunks/{logout-33IOBD4X.js → logout-F45WBVJV.js} +2 -2
  83. package/dist-cli/chunks/{maestro-EEVXX536.js → maestro-33RGEXP4.js} +3 -3
  84. package/dist-cli/chunks/{maestro-generate-ICPWVXGA.js → maestro-generate-5SLZMOMB.js} +3 -3
  85. package/dist-cli/chunks/{mode-6IXOZDK3.js → mode-LZORZYWP.js} +2 -2
  86. package/dist-cli/chunks/{optional-demo-registry-KBMC7SNI.js → optional-demo-registry-DIWO7DG5.js} +2 -2
  87. package/dist-cli/chunks/{perf-CTTJSAIJ.js → perf-VW3UTYCK.js} +2 -2
  88. package/dist-cli/chunks/{permissions-BFQS4DIY.js → permissions-7JIR6QPO.js} +2 -2
  89. package/dist-cli/chunks/{record-MJR5ELHE.js → record-A4MRKJ2E.js} +3 -3
  90. package/dist-cli/chunks/{report-issue-FMJRNDKH.js → report-issue-JUWTB66Z.js} +2 -2
  91. package/dist-cli/chunks/runtime-7LSOODXL.js +4 -0
  92. package/dist-cli/chunks/{screenshot-command-MGNASAXR.js → screenshot-command-6LROQJPJ.js} +3 -3
  93. package/dist-cli/chunks/{screenshots-capture-N6PANAVT.js → screenshots-capture-FMYE24EW.js} +2 -2
  94. package/dist-cli/chunks/{serve-RS6TIO7U.js → serve-64SRDCU5.js} +2 -2
  95. package/dist-cli/chunks/{setup-MFYGYR2F.js → setup-2CUXSDQH.js} +2 -2
  96. package/dist-cli/chunks/{skills-TD35LDKK.js → skills-IILJVUMS.js} +2 -2
  97. package/dist-cli/chunks/{storage-VCFXDJNO.js → storage-BUQQJAMD.js} +2 -2
  98. package/dist-cli/chunks/store-3Z2GSFGY.js +4 -0
  99. package/dist-cli/chunks/telemetry-2UMKQXU7.js +4 -0
  100. package/dist-cli/chunks/{timeline-UJCZAWJV.js → timeline-62RWUHSF.js} +2 -2
  101. package/dist-cli/chunks/{upgrade-S24NH4YG.js → upgrade-GHXDTO2A.js} +2 -2
  102. package/dist-cli/chunks/upload-H6RTYSLO.js +4 -0
  103. package/dist-cli/chunks/{version-IVPWT2ZQ.js → version-F4CKBMB6.js} +2 -2
  104. package/dist-cli/chunks/{web-6BMWAWJL.js → web-YM6GIFOU.js} +2 -2
  105. package/dist-cli/chunks/{what-happened-HRHB6ETL.js → what-happened-UKBSKIGA.js} +2 -2
  106. package/dist-lib/agent-daemon-client.cjs +1 -1
  107. package/dist-lib/agent-events.cjs +1 -1
  108. package/dist-lib/agent-identity.cjs +1 -1
  109. package/dist-lib/agent-sessions.cjs +1 -1
  110. package/dist-lib/attached-projects.cjs +1 -1
  111. package/dist-lib/auth/shared-session.cjs +1 -1
  112. package/dist-lib/backend-origin.cjs +1 -1
  113. package/dist-lib/beta.cjs +1 -1
  114. package/dist-lib/beta.mjs +1 -1
  115. package/dist-lib/bridge-constants.cjs +6 -1
  116. package/dist-lib/cli-constants.cjs +1 -1
  117. package/dist-lib/config.cjs +1 -1
  118. package/dist-lib/detox/index.cjs +312 -27
  119. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  120. package/dist-lib/home-paths.cjs +1 -1
  121. package/dist-lib/host/bridge-host.cjs +1 -1
  122. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  123. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  124. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  125. package/dist-lib/host/websocket-proxy.cjs +1 -1
  126. package/dist-lib/index.cjs +1 -1
  127. package/dist-lib/jump-to-source-babel.cjs +1 -1
  128. package/dist-lib/menu.cjs +1 -1
  129. package/dist-lib/menu.mjs +1 -1
  130. package/dist-lib/metro.cjs +1 -1
  131. package/dist-lib/profiles.cjs +1 -1
  132. package/dist-lib/render-mode.cjs +1 -1
  133. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  134. package/dist-lib/sdk.cjs +1 -1
  135. package/dist-lib/sdk.mjs +1 -1
  136. package/dist-lib/skills.cjs +15 -11
  137. package/dist-lib/vite.cjs +1 -1
  138. package/package.json +1 -1
  139. package/src/bridge-constants.ts +9 -0
  140. package/dist-cli/chunks/chunk-P7HBJBHH.js +0 -4
  141. package/dist-cli/chunks/daemon-VFO7LRR4.js +0 -4
  142. package/dist-cli/chunks/disk-cleanup-GZSUZJ6Y.js +0 -4
  143. package/dist-cli/chunks/drivers-OLJCUSPO.js +0 -4
  144. package/dist-cli/chunks/flow-IW3WUF5D.js +0 -4
  145. package/dist-cli/chunks/help-GEVJF2DA.js +0 -4
  146. package/dist-cli/chunks/inspect-KXJNI7CU.js +0 -4
  147. package/dist-cli/chunks/install-desktop-IYWPRFPB.js +0 -4
  148. package/dist-cli/chunks/runtime-PV4HH2OB.js +0 -4
  149. package/dist-cli/chunks/store-VNSTKYBQ.js +0 -4
  150. package/dist-cli/chunks/telemetry-GPNHSYFF.js +0 -4
  151. package/dist-cli/chunks/upload-CCKAO2M5.js +0 -4
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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;
@@ -7,6 +7,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
8
8
  var __getProtoOf = Object.getPrototypeOf;
9
9
  var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __commonJS = (cb, mod) => function __require() {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ };
10
13
  var __export = (target, all) => {
11
14
  for (var name in all)
12
15
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -29,6 +32,83 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
32
  ));
30
33
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
31
34
 
35
+ // detox/motion-change.cjs
36
+ var require_motion_change = __commonJS({
37
+ "detox/motion-change.cjs"(exports2, module2) {
38
+ "use strict";
39
+ var MOTION_CHANGE_DEFINITION2 = Object.freeze({
40
+ channelThreshold: 24,
41
+ minimumChangePercent: 0.1
42
+ });
43
+ function measureMotionChange2(input) {
44
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
45
+ throw new Error("motion change measurement requires an input object");
46
+ }
47
+ if (input.installGlobal === true) {
48
+ globalThis.__sootsimMeasureMotionChange = measureMotionChange2;
49
+ return null;
50
+ }
51
+ const definition = input.definition;
52
+ if (!definition || typeof definition !== "object" || !Number.isInteger(definition.channelThreshold) || definition.channelThreshold < 0 || definition.channelThreshold > 255 || !Number.isFinite(definition.minimumChangePercent) || definition.minimumChangePercent <= 0 || definition.minimumChangePercent >= 100) {
53
+ throw new Error(
54
+ "motion change definition needs a channel threshold from 0 to 255 and a minimum greater than 0% and less than 100%"
55
+ );
56
+ }
57
+ const label = typeof input.label === "string" && input.label.length > 0 ? input.label : "motion change";
58
+ let changePixels = input.changePixels;
59
+ let totalPixels = input.totalPixels;
60
+ if (input.baseline !== void 0 || input.candidate !== void 0) {
61
+ const baseline = input.baseline;
62
+ const candidate = input.candidate;
63
+ const width = input.width;
64
+ const height = input.height;
65
+ if (!baseline || !candidate || typeof baseline.length !== "number" || typeof candidate.length !== "number" || !Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {
66
+ throw new Error(`${label} needs two RGBA pixel arrays and positive dimensions`);
67
+ }
68
+ totalPixels = width * height;
69
+ const expectedBytes = totalPixels * 4;
70
+ if (baseline.length !== expectedBytes || candidate.length !== expectedBytes) {
71
+ throw new Error(
72
+ `${label} pixel data has ${baseline.length}/${candidate.length} bytes; expected ${expectedBytes}`
73
+ );
74
+ }
75
+ changePixels = 0;
76
+ for (let offset = 0; offset < expectedBytes; offset += 4) {
77
+ if (Math.abs(baseline[offset] - candidate[offset]) > definition.channelThreshold || Math.abs(baseline[offset + 1] - candidate[offset + 1]) > definition.channelThreshold || Math.abs(baseline[offset + 2] - candidate[offset + 2]) > definition.channelThreshold || Math.abs(baseline[offset + 3] - candidate[offset + 3]) > definition.channelThreshold) {
78
+ changePixels++;
79
+ }
80
+ }
81
+ }
82
+ if (!Number.isInteger(totalPixels) || totalPixels <= 0) {
83
+ throw new Error(`${label} has no positive measured region area`);
84
+ }
85
+ if (!Number.isInteger(changePixels) || changePixels < 0 || changePixels > totalPixels) {
86
+ throw new Error(
87
+ `${label} changed pixel count ${changePixels} is outside its ${totalPixels}px measured region`
88
+ );
89
+ }
90
+ const changePercent = changePixels / totalPixels * 100;
91
+ return {
92
+ changePercent,
93
+ changePixels,
94
+ passedMinimum: changePercent >= definition.minimumChangePercent,
95
+ totalPixels
96
+ };
97
+ }
98
+ function measureProofMotionChange(input) {
99
+ return measureMotionChange2({
100
+ ...input,
101
+ definition: MOTION_CHANGE_DEFINITION2
102
+ });
103
+ }
104
+ module2.exports = {
105
+ MOTION_CHANGE_DEFINITION: MOTION_CHANGE_DEFINITION2,
106
+ measureMotionChange: measureMotionChange2,
107
+ measureProofMotionChange
108
+ };
109
+ }
110
+ });
111
+
32
112
  // detox/index.ts
33
113
  var detox_exports = {};
34
114
  __export(detox_exports, {
@@ -1157,6 +1237,7 @@ var by = {
1157
1237
  };
1158
1238
 
1159
1239
  // detox/index.ts
1240
+ var import_motion_change = __toESM(require_motion_change(), 1);
1160
1241
  var BASE_URL = process.env.SOOTSIM_URL || "http://localhost:5173";
1161
1242
  var SCREENSHOT_DIR = process.env.SOOTSIM_SCREENSHOT_DIR || path.join(process.cwd(), "test", "detox-driver", "screenshots");
1162
1243
  var DETOX_PLATFORM = process.env.SOOTSIM_PLATFORM === "android" ? "android" : "ios";
@@ -1582,6 +1663,19 @@ function decodePngDataUrl(dataUrl) {
1582
1663
  }
1583
1664
  return Buffer.from(match[1], "base64");
1584
1665
  }
1666
+ function writeFastScreenshotFrames(namePrefix, frames) {
1667
+ ensureScreenshotDir();
1668
+ return frames.map((frame) => {
1669
+ const safeName = `${namePrefix}-${frame.label}`.replace(/[^a-zA-Z0-9._-]+/g, "-");
1670
+ const filePath = path.join(SCREENSHOT_DIR, `${safeName}.png`);
1671
+ fs.writeFileSync(filePath, decodePngDataUrl(frame.dataUrl));
1672
+ const { dataUrl: _dataUrl, ...metadata } = frame;
1673
+ return {
1674
+ ...metadata,
1675
+ path: filePath
1676
+ };
1677
+ });
1678
+ }
1585
1679
  async function captureSootsimPngFast(opts) {
1586
1680
  const page = getPage();
1587
1681
  const dataUrl = await page.evaluate(
@@ -2142,8 +2236,221 @@ var device = {
2142
2236
  async setMenuAnimOverride(progress) {
2143
2237
  await dispatchSootsimEventInPage("sootsim:menuAnimOverride", { value: progress });
2144
2238
  },
2145
- async setBottomTabAnimOverride(value) {
2146
- await dispatchSootsimEventInPage("sootsim:bottomTabAnimOverride", { value });
2239
+ // park every render clock before the interaction, detect the first changed
2240
+ // stepped frame inside the named component, then advance exact 60fps frames
2241
+ // to each requested offset. native extraction also chooses the first frame
2242
+ // at or after an offset, so both sides use the same rounding convention.
2243
+ async takeScreenshotStagesFromVisibleChangeFast(namePrefix, stages, options) {
2244
+ const page = getPage();
2245
+ const token = `motion-${process.pid}-${Date.now()}-${Math.random()}`;
2246
+ await page.evaluate(import_motion_change.measureMotionChange, {
2247
+ definition: import_motion_change.MOTION_CHANGE_DEFINITION,
2248
+ installGlobal: true
2249
+ });
2250
+ const capturePromise = page.evaluate(
2251
+ async (input) => {
2252
+ const captureWindow = window;
2253
+ const live = captureWindow.SootSim?.bridges?.liveComposite;
2254
+ const lockstep = captureWindow.SootSim?.bridges?.lockstepCapture;
2255
+ if (!live || typeof live.captureFresh !== "function") {
2256
+ throw new Error("SootSim live composite bridge is not installed");
2257
+ }
2258
+ if (!lockstep || typeof lockstep.begin !== "function") {
2259
+ throw new Error("SootSim lockstep capture bridge is not installed");
2260
+ }
2261
+ const registry = captureWindow.__sootsimVisibleChangeCaptures ?? (captureWindow.__sootsimVisibleChangeCaptures = {});
2262
+ const state = {
2263
+ cancelled: false,
2264
+ ready: false,
2265
+ triggerCompleted: false
2266
+ };
2267
+ registry[input.token] = state;
2268
+ const measureChange = captureWindow.__sootsimMeasureMotionChange;
2269
+ if (!measureChange) {
2270
+ throw new Error("SootSim motion change definition is not installed");
2271
+ }
2272
+ const captureBitmap = async () => {
2273
+ const { canvas } = await live.captureFresh();
2274
+ if (!canvas) throw new Error("SootSim live composite produced no canvas");
2275
+ return createImageBitmap(canvas);
2276
+ };
2277
+ const boundsFor = (bitmap) => {
2278
+ const scale = input.rootFrame && input.rootFrame.width > 0 ? bitmap.width / input.rootFrame.width : 1;
2279
+ return {
2280
+ height: input.frame ? Math.max(
2281
+ 1,
2282
+ Math.min(
2283
+ bitmap.height,
2284
+ Math.ceil((input.frame.y + input.frame.height) * scale)
2285
+ ) - Math.max(0, Math.floor(input.frame.y * scale))
2286
+ ) : bitmap.height,
2287
+ left: input.frame ? Math.max(0, Math.floor(input.frame.x * scale)) : 0,
2288
+ top: input.frame ? Math.max(0, Math.floor(input.frame.y * scale)) : 0,
2289
+ width: input.frame ? Math.max(
2290
+ 1,
2291
+ Math.min(
2292
+ bitmap.width,
2293
+ Math.ceil((input.frame.x + input.frame.width) * scale)
2294
+ ) - Math.max(0, Math.floor(input.frame.x * scale))
2295
+ ) : bitmap.width
2296
+ };
2297
+ };
2298
+ const regionPixels = (bitmap) => {
2299
+ const bounds = boundsFor(bitmap);
2300
+ const canvas = document.createElement("canvas");
2301
+ canvas.width = bounds.width;
2302
+ canvas.height = bounds.height;
2303
+ const context = canvas.getContext("2d", { willReadFrequently: true });
2304
+ if (!context) throw new Error("SootSim motion detector needs a 2d canvas");
2305
+ context.drawImage(
2306
+ bitmap,
2307
+ bounds.left,
2308
+ bounds.top,
2309
+ bounds.width,
2310
+ bounds.height,
2311
+ 0,
2312
+ 0,
2313
+ bounds.width,
2314
+ bounds.height
2315
+ );
2316
+ return context.getImageData(0, 0, bounds.width, bounds.height).data;
2317
+ };
2318
+ const encode = (snapshot) => {
2319
+ const canvas = document.createElement("canvas");
2320
+ canvas.width = snapshot.bitmap.width;
2321
+ canvas.height = snapshot.bitmap.height;
2322
+ const context = canvas.getContext("2d");
2323
+ if (!context) throw new Error("SootSim motion capture needs a 2d canvas");
2324
+ context.drawImage(snapshot.bitmap, 0, 0);
2325
+ snapshot.bitmap.close();
2326
+ return {
2327
+ actualMs: snapshot.actualMs,
2328
+ dataUrl: canvas.toDataURL("image/png"),
2329
+ label: snapshot.label,
2330
+ offsetMs: snapshot.offsetMs,
2331
+ frameIndex: snapshot.frameIndex,
2332
+ virtualTimestampMs: snapshot.virtualTimestampMs
2333
+ };
2334
+ };
2335
+ const controller = await lockstep.begin(60);
2336
+ try {
2337
+ const baselineBitmap = await captureBitmap();
2338
+ const baseline = regionPixels(baselineBitmap);
2339
+ baselineBitmap.close();
2340
+ state.ready = true;
2341
+ while (!state.triggerCompleted) {
2342
+ if (state.cancelled) throw new Error("SootSim motion capture was cancelled");
2343
+ await new Promise((resolve) => setTimeout(resolve, 0));
2344
+ }
2345
+ let visibleStart = null;
2346
+ let detectedMeasurement = null;
2347
+ const detectionFrameLimit = Math.ceil(input.detectionTimeoutMs * 60 / 1e3);
2348
+ for (let frame = 0; frame < detectionFrameLimit; frame++) {
2349
+ if (state.cancelled) throw new Error("SootSim motion capture was cancelled");
2350
+ const receipt = await controller.stepFrame();
2351
+ const candidateBitmap = await captureBitmap();
2352
+ const bounds = boundsFor(candidateBitmap);
2353
+ const measurement = measureChange({
2354
+ baseline,
2355
+ candidate: regionPixels(candidateBitmap),
2356
+ definition: input.motionChangeDefinition,
2357
+ height: bounds.height,
2358
+ label: "SootSim visible-change detector",
2359
+ width: bounds.width
2360
+ });
2361
+ candidateBitmap.close();
2362
+ if (measurement.passedMinimum) {
2363
+ visibleStart = receipt;
2364
+ detectedMeasurement = measurement;
2365
+ break;
2366
+ }
2367
+ }
2368
+ if (!visibleStart || !detectedMeasurement) {
2369
+ throw new Error(
2370
+ `SootSim motion had no visible change before ${input.detectionTimeoutMs}ms`
2371
+ );
2372
+ }
2373
+ const snapshots = [];
2374
+ for (const stage of input.stages) {
2375
+ if (state.cancelled) throw new Error("SootSim motion capture was cancelled");
2376
+ const receipt = await controller.stepToOffset(visibleStart, stage.offsetMs);
2377
+ snapshots.push({
2378
+ actualMs: receipt.virtualTimestampMs - visibleStart.virtualTimestampMs,
2379
+ bitmap: await captureBitmap(),
2380
+ frameIndex: receipt.frameIndex,
2381
+ label: stage.label,
2382
+ offsetMs: stage.offsetMs,
2383
+ virtualTimestampMs: receipt.virtualTimestampMs
2384
+ });
2385
+ }
2386
+ return {
2387
+ frames: snapshots.map(encode),
2388
+ visibleChangeDetection: {
2389
+ actualMs: visibleStart.frameIndex * 1e3 / 60,
2390
+ changedPixelPercent: detectedMeasurement.changePercent,
2391
+ changedPixels: detectedMeasurement.changePixels,
2392
+ channelThreshold: input.motionChangeDefinition.channelThreshold,
2393
+ minimumChangePercent: input.motionChangeDefinition.minimumChangePercent,
2394
+ totalPixels: detectedMeasurement.totalPixels,
2395
+ frameIndex: visibleStart.frameIndex,
2396
+ virtualTimestampMs: visibleStart.virtualTimestampMs
2397
+ }
2398
+ };
2399
+ } finally {
2400
+ controller.end();
2401
+ delete registry[input.token];
2402
+ }
2403
+ },
2404
+ {
2405
+ detectionTimeoutMs: options.detectionTimeoutMs ?? 3e3,
2406
+ frame: options.frame ?? null,
2407
+ motionChangeDefinition: import_motion_change.MOTION_CHANGE_DEFINITION,
2408
+ rootFrame: options.rootFrame ?? null,
2409
+ stages,
2410
+ token
2411
+ }
2412
+ );
2413
+ await page.waitForFunction(
2414
+ (captureToken) => {
2415
+ const registry = window.__sootsimVisibleChangeCaptures;
2416
+ return registry?.[captureToken]?.ready === true;
2417
+ },
2418
+ token,
2419
+ { timeout: 5e3 }
2420
+ );
2421
+ const triggerStartMs = Date.now();
2422
+ let triggerCompletedMs = 0;
2423
+ try {
2424
+ await Promise.resolve(options.trigger());
2425
+ triggerCompletedMs = Date.now();
2426
+ await page.evaluate((captureToken) => {
2427
+ const registry = window.__sootsimVisibleChangeCaptures;
2428
+ const capture2 = registry?.[captureToken];
2429
+ if (!capture2) throw new Error("SootSim motion capture disarmed before trigger");
2430
+ capture2.triggerCompleted = true;
2431
+ }, token);
2432
+ const capture = await capturePromise;
2433
+ return {
2434
+ frames: writeFastScreenshotFrames(namePrefix, capture.frames),
2435
+ triggerCompletedMs,
2436
+ triggerStartMs,
2437
+ visibleChangeDetection: {
2438
+ ...capture.visibleChangeDetection,
2439
+ frame: options.frame ?? null,
2440
+ rootFrame: options.rootFrame ?? null
2441
+ }
2442
+ };
2443
+ } catch (error) {
2444
+ await page.evaluate((captureToken) => {
2445
+ const registry = window.__sootsimVisibleChangeCaptures;
2446
+ const capture = registry?.[captureToken];
2447
+ if (capture) capture.cancelled = true;
2448
+ }, token).catch(() => {
2449
+ });
2450
+ await capturePromise.catch(() => {
2451
+ });
2452
+ throw error;
2453
+ }
2147
2454
  },
2148
2455
  // FAST multi-stage capture for sub-second animation diffs. each stage
2149
2456
  // schedules a setTimeout at its requested offsetMs and at that moment
@@ -2236,18 +2543,7 @@ var device = {
2236
2543
  },
2237
2544
  { stages, skipWarmup: options?.skipWarmup === true }
2238
2545
  );
2239
- ensureScreenshotDir();
2240
- return frames.map((f) => {
2241
- const safeName = `${namePrefix}-${f.label}`.replace(/[^a-zA-Z0-9._-]+/g, "-");
2242
- const filePath = path.join(SCREENSHOT_DIR, `${safeName}.png`);
2243
- fs.writeFileSync(filePath, decodePngDataUrl(f.dataUrl));
2244
- return {
2245
- label: f.label,
2246
- offsetMs: f.offsetMs,
2247
- actualMs: f.actualMs,
2248
- path: filePath
2249
- };
2250
- });
2546
+ return writeFastScreenshotFrames(namePrefix, frames);
2251
2547
  },
2252
2548
  // batched multi-stage capture for animation diffs. all N captures happen
2253
2549
  // inside one page.evaluate round trip so browser-side timing controls the
@@ -2277,18 +2573,7 @@ var device = {
2277
2573
  },
2278
2574
  { stages }
2279
2575
  );
2280
- ensureScreenshotDir();
2281
- return frames.map((f) => {
2282
- const safeName = `${namePrefix}-${f.label}`.replace(/[^a-zA-Z0-9._-]+/g, "-");
2283
- const filePath = path.join(SCREENSHOT_DIR, `${safeName}.png`);
2284
- fs.writeFileSync(filePath, decodePngDataUrl(f.dataUrl));
2285
- return {
2286
- label: f.label,
2287
- offsetMs: f.offsetMs,
2288
- actualMs: f.actualMs,
2289
- path: filePath
2290
- };
2291
- });
2576
+ return writeFastScreenshotFrames(namePrefix, frames);
2292
2577
  },
2293
2578
  async shake() {
2294
2579
  },
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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 __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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 __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/host/fetch-proxy-overrides.ts
4
4
  var FETCH_PROXY_BROWSER_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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 __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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 __defProp = Object.defineProperty;
package/dist-lib/menu.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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 __defProp = Object.defineProperty;
package/dist-lib/menu.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/menu.ts
4
4
  var SOOTSIM_ENGINE_EVENT_NAME = "sootsim:engine-event";
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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 __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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/sdk.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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 __defProp = Object.defineProperty;
package/dist-lib/sdk.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // cli/commands/inspect/core.ts
4
4
  function resolveMaxMsFlag(args, fallbackMs) {
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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;
@@ -1518,10 +1518,10 @@ var init_registry = __esm({
1518
1518
  versions: [
1519
1519
  {
1520
1520
  range: ">=1.0.0",
1521
- coverage: 0.93,
1522
- note: "navigator.clipboard for text + ClipboardItem for images on browsers that expose it (Chromium-based, Safari 13.4+). getImage/getImagePNG/getImageJPG read via Clipboard.read + ClipboardItem.getType and convert to base64 matching upstream's contract; setImage decodes base64 \u2192 Blob (sniffing PNG magic for MIME) and writes via Clipboard.write([new ClipboardItem(...)]). On Firefox without Clipboard.read, image methods return empty/false matching the documented absent shape.",
1523
- working: "getString (readText), setString (writeText), getImage / getImagePNG / getImageJPG (real ClipboardItem read \u2192 base64), setImage (real ClipboardItem write), hasString (length check), hasImage (Clipboard.read items inspection), hasURL (regex test), useClipboard hook (state + setter + fetcher)",
1524
- missing: "addListener/removeAllListeners (browser API has no clipboard-change event for external apps; in-page copy event isn't covered yet on this stub), Firefox image read (no Clipboard.read support)"
1521
+ coverage: 0.85,
1522
+ note: "native-seam only: the published Clipboard object, NativeEventEmitter wiring, and useClipboard hook run from the bundle. RNCClipboard routes text through navigator.clipboard and images through ClipboardItem where the browser exposes it.",
1523
+ working: "published Clipboard object and two-item useClipboard hook, getString/setString/hasString, getImage/getImagePNG/getImageJPG/setImage/hasImage, hasURL/hasWebURL/hasNumber, load-safe listener methods",
1524
+ missing: "external-app clipboard-change events because browsers expose no matching event; setStrings joins values and getStrings returns one browser text item rather than preserving multiple pasteboard strings; Firefox image read because Clipboard.read is unavailable"
1525
1525
  }
1526
1526
  ]
1527
1527
  },
@@ -3881,9 +3881,9 @@ var init_registry = __esm({
3881
3881
  {
3882
3882
  range: ">=0.30.0",
3883
3883
  coverage: 0.3,
3884
- note: "proxy framework \u2014 modules using nitro keep loading, but no native bindings actually run",
3885
- working: "NitroModules.createHybridObject returns chainable proxy (no crash), getHostObject returns same proxy, HybridObject type export, proxy toString/toPrimitive",
3886
- missing: "NitroModules.hasHybridObject always false, all real native JSI bindings (every call is a noop proxy \u2014 no actual native module functionality)"
3884
+ note: "native-seam only: the published Nitro Modules entrypoint installs a load-safe framework proxy, but no native JSI bindings run",
3885
+ working: "published entrypoint and type exports, NitroModules metadata, createHybridObject chainable proxy, hasHybridObject/getAllHybridObjectNames unavailable state, isRuntimeAlive false, callback wrapper, getHybridObjectConstructor",
3886
+ missing: "the native JSI framework implementation; package-specific HybridObjects work only where a separate compat seam registers them, and native memory accounting, ArrayBuffer ownership, cross-runtime boxed HybridObjects, and Nitro Views remain absent"
3887
3887
  }
3888
3888
  ]
3889
3889
  },
@@ -4174,10 +4174,10 @@ var init_registry = __esm({
4174
4174
  versions: [
4175
4175
  {
4176
4176
  range: ">=2.0.0",
4177
- coverage: 0.9,
4177
+ coverage: 0.85,
4178
4178
  note: "pure JS WHATWG URL/URLSearchParams polyfill; NativeModules.BlobModule access is guarded",
4179
4179
  working: "URL, URLSearchParams, setupURLPolyfill, auto.js web guard, canParse, global URL/URLSearchParams installation",
4180
- missing: "React Native BlobModule-specific createObjectURL path is absent when BlobModule is unavailable; browser Blob URLs should use the native URL implementation"
4180
+ missing: "URLSearchParams.sort does not reorder keys in the published 3.0.0 dependency; React Native BlobModule-specific createObjectURL is absent when BlobModule is unavailable, so browser Blob URLs should use the native URL implementation"
4181
4181
  }
4182
4182
  ]
4183
4183
  },
@@ -4555,7 +4555,7 @@ var init_registry = __esm({
4555
4555
  range: ">=3.0.0",
4556
4556
  coverage: 0.95,
4557
4557
  note: "pure JS React error boundary with RN fallback UI",
4558
- working: "ErrorBoundary component, FallbackComponent/fallbackRender/FallbackComponent prop paths, onError/onReset, resetKeys, useErrorBoundary",
4558
+ working: "ErrorBoundary component, bundled fallback UI, custom FallbackComponent, onError callback, resetError recovery",
4559
4559
  missing: "fallback rendering fidelity follows core RN View/Text/Button primitives"
4560
4560
  }
4561
4561
  ]
@@ -10713,6 +10713,7 @@ __export(bridge_constants_exports, {
10713
10713
  DEFAULT_SOOTSIM_BRIDGE_URL: () => DEFAULT_SOOTSIM_BRIDGE_URL,
10714
10714
  SOOTSIM_BRIDGE_SIM_CLOSE_CODE: () => SOOTSIM_BRIDGE_SIM_CLOSE_CODE,
10715
10715
  SOOTSIM_BRIDGE_SIM_CLOSE_REASON: () => SOOTSIM_BRIDGE_SIM_CLOSE_REASON,
10716
+ isSootsimBridgeHealth: () => isSootsimBridgeHealth,
10716
10717
  resolveSootsimBridgePort: () => resolveSootsimBridgePort,
10717
10718
  resolveSootsimBridgePortForHttpPort: () => resolveSootsimBridgePortForHttpPort,
10718
10719
  resolveSootsimBridgePortForRuntimeLocation: () => resolveSootsimBridgePortForRuntimeLocation,
@@ -10809,6 +10810,9 @@ function resolveSootsimBridgeWebSocketUrl(input) {
10809
10810
  href: input.href
10810
10811
  });
10811
10812
  }
10813
+ function isSootsimBridgeHealth(value, expectedPort) {
10814
+ return typeof value === "object" && value !== null && Reflect.get(value, "ok") === true && Reflect.get(value, "bridgePort") === expectedPort;
10815
+ }
10812
10816
  var DEFAULT_SOOTSIM_BRIDGE_PORT, DEFAULT_SOOTSIM_BRIDGE_URL, CONTRAST_DEV_HTTP_BASE_PORT, SOOTSIM_SITE_DEV_HTTP_BASE_PORT, SOOTSIM_SHELL_DEV_HTTP_BASE_PORT, LOCAL_DEV_HTTP_BASE_PORTS, MAX_LOCAL_DEV_PORT_OFFSET, SOOTSIM_BRIDGE_SIM_CLOSE_CODE, SOOTSIM_BRIDGE_SIM_CLOSE_REASON;
10813
10817
  var init_bridge_constants = __esm({
10814
10818
  "src/bridge-constants.ts"() {
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.298 | (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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sootsim",
3
- "version": "0.1.296",
3
+ "version": "0.1.298",
4
4
  "description": "sootsim CLI + vite/metro plugins + skills registry. bridge client for driving the proprietary sootsim-engine over WebSocket.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "MIT",
@@ -140,6 +140,15 @@ export function resolveSootsimBridgeWebSocketUrl(input: {
140
140
  })
141
141
  }
142
142
 
143
+ export function isSootsimBridgeHealth(value: unknown, expectedPort: number): boolean {
144
+ return (
145
+ typeof value === 'object' &&
146
+ value !== null &&
147
+ Reflect.get(value, 'ok') === true &&
148
+ Reflect.get(value, 'bridgePort') === expectedPort
149
+ )
150
+ }
151
+
143
152
  // private websocket close code used when the daemon intentionally closes a
144
153
  // sim. browser clients treat it as terminal and skip their normal reconnect
145
154
  // loop; playwright-owned sims use it to close their hosting context.