rnxsim 0.1.446 → 0.1.448

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 (55) hide show
  1. package/README.md +3 -0
  2. package/cli/bridge-flow-runner.ts +43 -59
  3. package/dist-lib/agent-daemon-client.cjs +1 -1
  4. package/dist-lib/agent-events.cjs +1 -1
  5. package/dist-lib/agent-identity.cjs +1 -1
  6. package/dist-lib/agent-sessions.cjs +1 -1
  7. package/dist-lib/attached-projects.cjs +1 -1
  8. package/dist-lib/auth/shared-session.cjs +1 -1
  9. package/dist-lib/backend-origin.cjs +1 -1
  10. package/dist-lib/beta.cjs +1 -1
  11. package/dist-lib/beta.mjs +1 -1
  12. package/dist-lib/bridge-constants.cjs +1 -1
  13. package/dist-lib/bridge-contract-input.cjs +1 -1
  14. package/dist-lib/bridge-contract-input.mjs +1 -1
  15. package/dist-lib/bridge-contract.cjs +1 -1
  16. package/dist-lib/bridge-contract.mjs +1 -1
  17. package/dist-lib/capture-contract.cjs +1 -1
  18. package/dist-lib/capture-contract.mjs +1 -1
  19. package/dist-lib/cli-constants.cjs +1 -1
  20. package/dist-lib/cloud-contract.cjs +1 -1
  21. package/dist-lib/cloud-contract.mjs +1 -1
  22. package/dist-lib/cloud.cjs +1 -1
  23. package/dist-lib/cloud.mjs +1 -1
  24. package/dist-lib/config.cjs +1 -1
  25. package/dist-lib/detox/index.cjs +1 -1
  26. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  27. package/dist-lib/home-paths.cjs +1 -1
  28. package/dist-lib/host/bridge-host.cjs +1 -1
  29. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  30. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  31. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  32. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  33. package/dist-lib/host/websocket-proxy.cjs +1 -1
  34. package/dist-lib/index.cjs +1 -1
  35. package/dist-lib/jump-to-source-babel.cjs +1 -1
  36. package/dist-lib/menu.cjs +1 -1
  37. package/dist-lib/menu.mjs +1 -1
  38. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  39. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  40. package/dist-lib/metro-production-bundle.cjs +1 -1
  41. package/dist-lib/metro-production-bundle.mjs +1 -1
  42. package/dist-lib/metro.cjs +1 -1
  43. package/dist-lib/profiles.cjs +1 -1
  44. package/dist-lib/public-brand.cjs +1 -1
  45. package/dist-lib/react-native-host-modules.cjs +1 -1
  46. package/dist-lib/react-native-host-modules.mjs +1 -1
  47. package/dist-lib/render-mode.cjs +1 -1
  48. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  49. package/dist-lib/sdk.cjs +1 -1
  50. package/dist-lib/sdk.mjs +1 -1
  51. package/dist-lib/skills.cjs +49 -51
  52. package/dist-lib/vite.cjs +1 -1
  53. package/package.json +1 -1
  54. package/src/shell-bridge-identity.ts +15 -0
  55. package/src/vite-plugin.ts +85 -52
package/README.md CHANGED
@@ -174,6 +174,9 @@ During source development, each Vite bridge owns one
174
174
  `~/.rnx/dev-bridges/<port>.json` heartbeat record. CLI defaults select the
175
175
  current `PORT_OFFSET`, then the current checkout, then the canonical `:7668`
176
176
  bridge, so concurrent worktrees cannot replace or delete one another's address.
177
+ Each Vite shell also publishes its exact bridge at `/__sootsim/bridge`, so a
178
+ launcher that reuses a shell from another isolated `RNX_HOME` stays in that
179
+ shell's bridge world without reading the other process's lockfile directory.
177
180
 
178
181
  **The sim** is a browser/Electron *page* launched by a CLI driver. Inside that
179
182
  page run three workers: the **shell worker** owns device and native state, the
@@ -719,25 +719,17 @@ export class SootSimBridgeFlowRunner {
719
719
  )
720
720
  }
721
721
 
722
- private async findElement(opts: {
723
- text?: string
724
- id?: string
725
- index?: number
726
- childOf?: { id?: string; text?: string }
727
- }) {
728
- // scoped matchers (maestro `index:` / `childOf:`) need the FULL candidate
729
- // set, not the single best match findByText/findByTestId return. route them
730
- // through queryAll: collect every match, optionally keep only those inside
731
- // the childOf container's bounds (geometric containment mirrors "the X
732
- // inside post Y" for vertically-stacked lists), then pick the nth.
733
- if (opts.childOf != null || typeof opts.index === 'number') {
722
+ private async findElement(opts: FlowTargetSelector) {
723
+ // patterns and combined selectors need the full candidate set so id and
724
+ // text constrain the same node. literal single selectors keep the direct lookup.
725
+ if (
726
+ opts.childOf != null ||
727
+ typeof opts.index === 'number' ||
728
+ (opts.id && opts.text) ||
729
+ [opts.id, opts.text].some((value) => value && /[.*+?^$()[\]{}|\\]/.test(value))
730
+ ) {
734
731
  return this.findElementScoped(opts)
735
732
  }
736
- // maestro text selectors are REGEXPs (e.g. "users[,]? or feeds",
737
- // ".*Reply 1.*"). only treat a selector as a pattern when it carries regex
738
- // metacharacters AND the literal/a11y-label lookups miss, so ordinary copy
739
- // ("Repost") never accidentally over-matches via regex.
740
- const wantRegex = !!opts.text && /[.*+?^$()[\]{}|\\]/.test(opts.text)
741
733
  return this.evaluate<any>(`(async () => {
742
734
  const test = window.__sootsimTest
743
735
  if (!test) return null
@@ -762,23 +754,6 @@ export class SootSimBridgeFlowRunner {
762
754
  labelled
763
755
  }
764
756
  }
765
- if (!node && ${JSON.stringify(wantRegex)} && test.queryAll) {
766
- // regex selector fallback: match the pattern against each visible
767
- // node's text; prefer the narrowest matching node so a tight <text>
768
- // wins over a wide container that merely contains it.
769
- let re = null
770
- try { re = new RegExp(${JSON.stringify(opts.text || '')}) } catch (e) { re = null }
771
- if (re) {
772
- const all = (await test.queryAll({ pruneHidden: true })) || []
773
- const hits = all.filter(
774
- (n) =>
775
- n && n.absolutePosition && n.layout &&
776
- typeof n.text === 'string' && re.test(n.text),
777
- )
778
- hits.sort((a, b) => (a.text || '').length - (b.text || '').length)
779
- if (hits[0]) node = hits[0]
780
- }
781
- }
782
757
  }
783
758
  if (!node && ${JSON.stringify(!!opts.id)}) {
784
759
  node = (await test.findByTestId(${JSON.stringify(opts.id || '')})) || (await test.findById(${JSON.stringify(opts.id || '')}))
@@ -799,30 +774,32 @@ export class SootSimBridgeFlowRunner {
799
774
  // maestro `index:` (nth match) and `childOf:` (match scoped to a container)
800
775
  // resolver. queryAll returns every matching node in tree order; childOf keeps
801
776
  // only candidates whose center sits inside the container's bounds.
802
- private async findElementScoped(opts: {
803
- text?: string
804
- id?: string
805
- index?: number
806
- childOf?: { id?: string; text?: string }
807
- }) {
777
+ private async findElementScoped(opts: FlowTargetSelector) {
808
778
  const index = typeof opts.index === 'number' ? opts.index : 0
809
779
  return this.evaluate<any>(`(async () => {
810
780
  const test = window.__sootsimTest
811
781
  if (!test || !test.queryAll) return null
812
- // NOT pruneHidden: that over-filters (it dropped every postDropdownBtn in a
813
- // feed and made index:0 resolve to nothing — worse than findByTestId). reach
814
- // for the same set findByTestId sees, then PREFER on-screen ones below.
815
- const filter = {}
816
- if (${JSON.stringify(!!opts.id)}) filter.hasId = ${JSON.stringify(opts.id || '')}
817
- if (${JSON.stringify(!!opts.text)}) filter.hasText = ${JSON.stringify(opts.text || '')}
818
- let candidates = (await test.queryAll(filter)) || []
819
- // a11y-label fallback (icon-only buttons) for text matchers, mirroring
820
- // findElement's simple path.
821
- if (${JSON.stringify(!!opts.text)} && candidates.length === 0) {
822
- const all = (await test.queryAll({})) || []
823
- candidates = all.filter((n) => (n.accessibilityLabel || '') === ${JSON.stringify(opts.text || '')})
824
- }
825
- candidates = candidates.filter((n) => n && n.absolutePosition && n.layout)
782
+ const compile = (value) => {
783
+ if (!value) return null
784
+ let pattern = null
785
+ try { pattern = new RegExp(value) } catch {}
786
+ return (candidate) => typeof candidate === 'string' &&
787
+ (candidate === value || !!pattern?.test(candidate))
788
+ }
789
+ const matchesId = compile(${JSON.stringify(opts.id)})
790
+ const matchesText = compile(${JSON.stringify(opts.text)})
791
+ const all = ((await test.queryAll({})) || []).filter(
792
+ (n) => n && n.absolutePosition && n.layout,
793
+ )
794
+ let candidates = all.filter((n) =>
795
+ (!matchesId || matchesId(n.testID) || matchesId(n.id)) &&
796
+ (!matchesText || matchesText(n.text) || matchesText(n.accessibilityLabel)),
797
+ )
798
+ const literalText = candidates.filter((n) =>
799
+ n.text === ${JSON.stringify(opts.text)} ||
800
+ n.accessibilityLabel === ${JSON.stringify(opts.text)},
801
+ )
802
+ if (matchesText && literalText.length > 0) candidates = literalText
826
803
  // prefer on-screen matches (findByTestId's visible-first behavior); only
827
804
  // fall back to off-screen ones when nothing is on-screen, so index: still
828
805
  // resolves a target that needs scrolling into view.
@@ -834,11 +811,12 @@ export class SootSimBridgeFlowRunner {
834
811
  ${
835
812
  opts.childOf
836
813
  ? `
837
- const container = ${
838
- opts.childOf.id
839
- ? `(await test.findByTestId(${JSON.stringify(opts.childOf.id)})) || (await test.findById(${JSON.stringify(opts.childOf.id)}))`
840
- : `await test.findByText(${JSON.stringify(opts.childOf.text || '')})`
841
- }
814
+ const matchesContainerId = compile(${JSON.stringify(opts.childOf.id)})
815
+ const matchesContainerText = compile(${JSON.stringify(opts.childOf.text)})
816
+ const container = all.find((n) =>
817
+ (!matchesContainerId || matchesContainerId(n.testID) || matchesContainerId(n.id)) &&
818
+ (!matchesContainerText || matchesContainerText(n.text) || matchesContainerText(n.accessibilityLabel)),
819
+ )
842
820
  if (!container || !container.absolutePosition || !container.layout) return null
843
821
  const x0 = container.absolutePosition.x
844
822
  const y0 = container.absolutePosition.y
@@ -851,6 +829,9 @@ export class SootSimBridgeFlowRunner {
851
829
  })`
852
830
  : ''
853
831
  }
832
+ if (${JSON.stringify(opts.index === undefined && !!opts.text && !opts.id)}) {
833
+ candidates.sort((a, b) => (a.text || '').length - (b.text || '').length)
834
+ }
854
835
  const node = candidates[${index}]
855
836
  if (!node) return null
856
837
  return {
@@ -1922,6 +1903,9 @@ export class SootSimBridgeFlowRunner {
1922
1903
  if (!this.firstLaunchDone) {
1923
1904
  this.firstLaunchDone = true
1924
1905
  if (wantsClear) {
1906
+ // bundle evaluation temporarily installs a reload bridge without the
1907
+ // storage contract. wait for the app before its first data reset.
1908
+ if (!this.appStopped) await this.waitForTree(120000)
1925
1909
  await this.resetGuestAppData({
1926
1910
  launchArguments: hasLaunchArguments ? launchArguments : null,
1927
1911
  initialUrl: null,
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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/beta.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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/beta.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/beta.ts
4
4
  var IS_BETA = true;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
4
  var SIM_LONG_PRESS_MAX_MS = 5e3;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
4
  var SIM_LONG_PRESS_DEFAULT_MS = 500;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/capture-contract.ts
4
4
  var RNX_SCREEN_CAPTURE_VERSION = 1;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/cloud-contract.ts
4
4
  var RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/public-brand.ts
4
4
  var name = "rnx";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/metro-fingerprint-registry.ts
4
4
  var RNX_METRO_FINGERPRINT_SCHEMA_VERSION = 2;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/metro-production-bundle.ts
4
4
  var RNXSIM_METRO_MODULE_PATHS_PREFIX = "globalThis.__sootsimModulePaths=";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/react-native-host-modules.ts
4
4
  var REACT_NATIVE_PASSTHROUGH_SPECIFIERS = [
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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;
@@ -693,25 +693,25 @@ var init_capability_inventory_coverage = __esm({
693
693
  ios: {
694
694
  combined: {
695
695
  raw: {
696
- covered: 213,
697
- ratio: 0.153127246585,
696
+ covered: 233,
697
+ ratio: 0.167505391804,
698
698
  total: 1391
699
699
  },
700
700
  weighted: {
701
- covered: 1.38498343762,
702
- ratio: 0.289659691768,
701
+ covered: 1.63749741651,
702
+ ratio: 0.34247124121,
703
703
  total: 4.78141583721
704
704
  }
705
705
  },
706
706
  semantic: {
707
707
  raw: {
708
- covered: 213,
709
- ratio: 0.213855421687,
708
+ covered: 233,
709
+ ratio: 0.233935742972,
710
710
  total: 996
711
711
  },
712
712
  weighted: {
713
- covered: 1.38498343762,
714
- ratio: 0.578085386368,
713
+ covered: 1.63749741651,
714
+ ratio: 0.683483499504,
715
715
  total: 2.39581119032
716
716
  }
717
717
  },
@@ -1334,13 +1334,13 @@ var init_capability_outcomes2 = __esm({
1334
1334
  assessed: 0
1335
1335
  },
1336
1336
  ios: {
1337
- passedWeight: 0,
1338
- failedWeight: 0,
1339
- unassessedWeight: 0.957145688695477,
1340
- totalWeight: 0.957145688695477,
1341
- assessedWeight: 0,
1342
- compatibility: null,
1343
- assessed: 0
1337
+ passedWeight: 0.32201435438317577,
1338
+ failedWeight: 22084925244e-15,
1339
+ unassessedWeight: 0.6351092493870464,
1340
+ totalWeight: 0.9571456886954661,
1341
+ assessedWeight: 0.3220364393084198,
1342
+ compatibility: 0.9999314210364161,
1343
+ assessed: 0.3364549860192514
1344
1344
  }
1345
1345
  }
1346
1346
  },
@@ -1749,7 +1749,7 @@ var init_supported_native_packages = __esm({
1749
1749
  "posthog-react-native-session-replay",
1750
1750
  // preset+coverage
1751
1751
  "react-native",
1752
- // preset+visual-proof+coverage
1752
+ // preset+visual-proof
1753
1753
  "react-native-actions-shortcuts",
1754
1754
  // coverage
1755
1755
  "react-native-adjust",
@@ -32174,10 +32174,9 @@ var init_bridge_flow_runner = __esm({
32174
32174
  );
32175
32175
  }
32176
32176
  async findElement(opts) {
32177
- if (opts.childOf != null || typeof opts.index === "number") {
32177
+ if (opts.childOf != null || typeof opts.index === "number" || opts.id && opts.text || [opts.id, opts.text].some((value) => value && /[.*+?^$()[\]{}|\\]/.test(value))) {
32178
32178
  return this.findElementScoped(opts);
32179
32179
  }
32180
- const wantRegex = !!opts.text && /[.*+?^$()[\]{}|\\]/.test(opts.text);
32181
32180
  return this.evaluate(`(async () => {
32182
32181
  const test = window.__sootsimTest
32183
32182
  if (!test) return null
@@ -32202,23 +32201,6 @@ var init_bridge_flow_runner = __esm({
32202
32201
  labelled
32203
32202
  }
32204
32203
  }
32205
- if (!node && ${JSON.stringify(wantRegex)} && test.queryAll) {
32206
- // regex selector fallback: match the pattern against each visible
32207
- // node's text; prefer the narrowest matching node so a tight <text>
32208
- // wins over a wide container that merely contains it.
32209
- let re = null
32210
- try { re = new RegExp(${JSON.stringify(opts.text || "")}) } catch (e) { re = null }
32211
- if (re) {
32212
- const all = (await test.queryAll({ pruneHidden: true })) || []
32213
- const hits = all.filter(
32214
- (n) =>
32215
- n && n.absolutePosition && n.layout &&
32216
- typeof n.text === 'string' && re.test(n.text),
32217
- )
32218
- hits.sort((a, b) => (a.text || '').length - (b.text || '').length)
32219
- if (hits[0]) node = hits[0]
32220
- }
32221
- }
32222
32204
  }
32223
32205
  if (!node && ${JSON.stringify(!!opts.id)}) {
32224
32206
  node = (await test.findByTestId(${JSON.stringify(opts.id || "")})) || (await test.findById(${JSON.stringify(opts.id || "")}))
@@ -32243,20 +32225,27 @@ var init_bridge_flow_runner = __esm({
32243
32225
  return this.evaluate(`(async () => {
32244
32226
  const test = window.__sootsimTest
32245
32227
  if (!test || !test.queryAll) return null
32246
- // NOT pruneHidden: that over-filters (it dropped every postDropdownBtn in a
32247
- // feed and made index:0 resolve to nothing \u2014 worse than findByTestId). reach
32248
- // for the same set findByTestId sees, then PREFER on-screen ones below.
32249
- const filter = {}
32250
- if (${JSON.stringify(!!opts.id)}) filter.hasId = ${JSON.stringify(opts.id || "")}
32251
- if (${JSON.stringify(!!opts.text)}) filter.hasText = ${JSON.stringify(opts.text || "")}
32252
- let candidates = (await test.queryAll(filter)) || []
32253
- // a11y-label fallback (icon-only buttons) for text matchers, mirroring
32254
- // findElement's simple path.
32255
- if (${JSON.stringify(!!opts.text)} && candidates.length === 0) {
32256
- const all = (await test.queryAll({})) || []
32257
- candidates = all.filter((n) => (n.accessibilityLabel || '') === ${JSON.stringify(opts.text || "")})
32258
- }
32259
- candidates = candidates.filter((n) => n && n.absolutePosition && n.layout)
32228
+ const compile = (value) => {
32229
+ if (!value) return null
32230
+ let pattern = null
32231
+ try { pattern = new RegExp(value) } catch {}
32232
+ return (candidate) => typeof candidate === 'string' &&
32233
+ (candidate === value || !!pattern?.test(candidate))
32234
+ }
32235
+ const matchesId = compile(${JSON.stringify(opts.id)})
32236
+ const matchesText = compile(${JSON.stringify(opts.text)})
32237
+ const all = ((await test.queryAll({})) || []).filter(
32238
+ (n) => n && n.absolutePosition && n.layout,
32239
+ )
32240
+ let candidates = all.filter((n) =>
32241
+ (!matchesId || matchesId(n.testID) || matchesId(n.id)) &&
32242
+ (!matchesText || matchesText(n.text) || matchesText(n.accessibilityLabel)),
32243
+ )
32244
+ const literalText = candidates.filter((n) =>
32245
+ n.text === ${JSON.stringify(opts.text)} ||
32246
+ n.accessibilityLabel === ${JSON.stringify(opts.text)},
32247
+ )
32248
+ if (matchesText && literalText.length > 0) candidates = literalText
32260
32249
  // prefer on-screen matches (findByTestId's visible-first behavior); only
32261
32250
  // fall back to off-screen ones when nothing is on-screen, so index: still
32262
32251
  // resolves a target that needs scrolling into view.
@@ -32266,7 +32255,12 @@ var init_bridge_flow_runner = __esm({
32266
32255
  })
32267
32256
  if (onScreen.length > 0) candidates = onScreen
32268
32257
  ${opts.childOf ? `
32269
- const container = ${opts.childOf.id ? `(await test.findByTestId(${JSON.stringify(opts.childOf.id)})) || (await test.findById(${JSON.stringify(opts.childOf.id)}))` : `await test.findByText(${JSON.stringify(opts.childOf.text || "")})`}
32258
+ const matchesContainerId = compile(${JSON.stringify(opts.childOf.id)})
32259
+ const matchesContainerText = compile(${JSON.stringify(opts.childOf.text)})
32260
+ const container = all.find((n) =>
32261
+ (!matchesContainerId || matchesContainerId(n.testID) || matchesContainerId(n.id)) &&
32262
+ (!matchesContainerText || matchesContainerText(n.text) || matchesContainerText(n.accessibilityLabel)),
32263
+ )
32270
32264
  if (!container || !container.absolutePosition || !container.layout) return null
32271
32265
  const x0 = container.absolutePosition.x
32272
32266
  const y0 = container.absolutePosition.y
@@ -32277,6 +32271,9 @@ var init_bridge_flow_runner = __esm({
32277
32271
  const cy = n.absolutePosition.y + n.layout.height / 2
32278
32272
  return cx >= x0 && cx <= x1 && cy >= y0 && cy <= y1
32279
32273
  })` : ""}
32274
+ if (${JSON.stringify(opts.index === void 0 && !!opts.text && !opts.id)}) {
32275
+ candidates.sort((a, b) => (a.text || '').length - (b.text || '').length)
32276
+ }
32280
32277
  const node = candidates[${index}]
32281
32278
  if (!node) return null
32282
32279
  return {
@@ -33071,6 +33068,7 @@ var init_bridge_flow_runner = __esm({
33071
33068
  if (!this.firstLaunchDone) {
33072
33069
  this.firstLaunchDone = true;
33073
33070
  if (wantsClear) {
33071
+ if (!this.appStopped) await this.waitForTree(12e4);
33074
33072
  await this.resetGuestAppData({
33075
33073
  launchArguments: hasLaunchArguments ? launchArguments : null,
33076
33074
  initialUrl: null
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.446 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.448 | (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": "rnxsim",
3
- "version": "0.1.446",
3
+ "version": "0.1.448",
4
4
  "description": "Vite and Metro plugins, testing drivers, and SDK exports for rnx.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -0,0 +1,15 @@
1
+ export const SHELL_BRIDGE_IDENTITY_PATH = '/__sootsim/bridge'
2
+
3
+ export interface ShellBridgeIdentity {
4
+ schema: 1
5
+ bridgePort: number
6
+ }
7
+
8
+ export function isShellBridgeIdentity(value: unknown): value is ShellBridgeIdentity {
9
+ if (typeof value !== 'object' || value === null) return false
10
+ return (
11
+ Reflect.get(value, 'schema') === 1 &&
12
+ Number.isInteger(Reflect.get(value, 'bridgePort')) &&
13
+ Reflect.get(value, 'bridgePort') > 0
14
+ )
15
+ }
@@ -23,6 +23,10 @@ import {
23
23
  reactNativeDeepStubsForBuildResolver,
24
24
  } from '../../compat/src/stub-manifest.ts'
25
25
  import { resolveSootsimBridgePort } from './bridge-constants.ts'
26
+ import {
27
+ SHELL_BRIDGE_IDENTITY_PATH,
28
+ type ShellBridgeIdentity,
29
+ } from './shell-bridge-identity.ts'
26
30
  import {
27
31
  REANIMATED_WORKLETIZATION_REGEX,
28
32
  shouldApplyWorkletsPlugin,
@@ -842,63 +846,92 @@ export function wsBridgePlugin(options: { ownedBridgePort?: number } = {}): Plug
842
846
  name: 'sootsim-ws-bridge',
843
847
  apply: 'serve',
844
848
  configureServer(server) {
845
- if (boundPort) return
846
- const preferredPort = resolveSootsimBridgePort({
847
- explicitPort: process.env.SOOTSIM_BRIDGE_PORT,
848
- portOffset: process.env.PORT_OFFSET,
849
- })
850
- const readShellPort = () => {
851
- const addr = server.httpServer?.address()
852
- if (addr && typeof addr === 'object') return addr.port
853
- return server.config.server.port || null
854
- }
855
- const existing = viteBridgeProcesses.get(preferredPort)
856
- if (existing) {
857
- existing.owners.add(owner)
858
- existing.shellPort.read = readShellPort
859
- boundPort = existing.boundPort
860
- } else {
861
- // lazy import to avoid pulling ws into the main bundle.
862
- // note: the .ts extension is required on vite 8 / node ESM — without
863
- // it, import() throws "Cannot find module" and the bridge silently
864
- // never starts.
865
- const shellPort = { read: readShellPort }
866
- const host = import('./host/bridge-host.ts').then(({ SootSimBridgeHost }) => {
867
- return new SootSimBridgeHost({
868
- port: preferredPort,
869
- writeDevLockfile: true,
870
- getShellPort: () => shellPort.read(),
871
- })
849
+ if (!boundPort) {
850
+ const preferredPort = resolveSootsimBridgePort({
851
+ explicitPort: process.env.SOOTSIM_BRIDGE_PORT,
852
+ portOffset: process.env.PORT_OFFSET,
872
853
  })
873
- let entry: ViteBridgeProcessEntry
874
- boundPort = host
875
- .then((bridgeHost) => bridgeHost.startAsync({ silent: true }))
876
- .catch((err: unknown) => {
877
- if (viteBridgeProcesses.get(preferredPort) === entry) {
878
- viteBridgeProcesses.delete(preferredPort)
879
- }
880
- const message = err instanceof Error ? err.message : String(err)
881
- console.warn('[rnx] ws bridge failed to start:', message)
882
- return null
854
+ const readShellPort = () => {
855
+ const addr = server.httpServer?.address()
856
+ if (addr && typeof addr === 'object') return addr.port
857
+ return server.config.server.port || null
858
+ }
859
+ const existing = viteBridgeProcesses.get(preferredPort)
860
+ if (existing) {
861
+ existing.owners.add(owner)
862
+ existing.shellPort.read = readShellPort
863
+ boundPort = existing.boundPort
864
+ } else {
865
+ // lazy import to avoid pulling ws into the main bundle.
866
+ // note: the .ts extension is required on vite 8 / node ESM — without
867
+ // it, import() throws "Cannot find module" and the bridge silently
868
+ // never starts.
869
+ const shellPort = { read: readShellPort }
870
+ const host = import('./host/bridge-host.ts').then(({ SootSimBridgeHost }) => {
871
+ return new SootSimBridgeHost({
872
+ port: preferredPort,
873
+ writeDevLockfile: true,
874
+ getShellPort: () => shellPort.read(),
875
+ })
883
876
  })
884
- entry = {
885
- boundPort,
886
- close: async () => {
887
- try {
888
- await (await host).close()
889
- } catch {}
890
- },
891
- owners: new Set([owner]),
892
- shellPort,
877
+ let entry: ViteBridgeProcessEntry
878
+ boundPort = host
879
+ .then((bridgeHost) => bridgeHost.startAsync({ silent: true }))
880
+ .catch((err: unknown) => {
881
+ if (viteBridgeProcesses.get(preferredPort) === entry) {
882
+ viteBridgeProcesses.delete(preferredPort)
883
+ }
884
+ const message = err instanceof Error ? err.message : String(err)
885
+ console.warn('[rnx] ws bridge failed to start:', message)
886
+ return null
887
+ })
888
+ entry = {
889
+ boundPort,
890
+ close: async () => {
891
+ try {
892
+ await (await host).close()
893
+ } catch {}
894
+ },
895
+ owners: new Set([owner]),
896
+ shellPort,
897
+ }
898
+ viteBridgeProcesses.set(preferredPort, entry)
893
899
  }
894
- viteBridgeProcesses.set(preferredPort, entry)
900
+
901
+ server.httpServer?.once('close', () => {
902
+ const entry = viteBridgeProcesses.get(preferredPort)
903
+ if (!entry || !entry.owners.delete(owner) || entry.owners.size > 0) return
904
+ viteBridgeProcesses.delete(preferredPort)
905
+ void entry.close()
906
+ })
895
907
  }
896
908
 
897
- server.httpServer?.once('close', () => {
898
- const entry = viteBridgeProcesses.get(preferredPort)
899
- if (!entry || !entry.owners.delete(owner) || entry.owners.size > 0) return
900
- viteBridgeProcesses.delete(preferredPort)
901
- void entry.close()
909
+ if (!boundPort) throw new Error('rnx bridge identity was not initialized')
910
+ const identityPort = boundPort
911
+ server.middlewares.use((req, res, next) => {
912
+ if (req.url?.split('?')[0] !== SHELL_BRIDGE_IDENTITY_PATH) {
913
+ next()
914
+ return
915
+ }
916
+ void identityPort
917
+ .then((bridgePort) => {
918
+ if (bridgePort === null) {
919
+ res.statusCode = 503
920
+ res.end('rnx bridge unavailable')
921
+ return
922
+ }
923
+ const identity = {
924
+ schema: 1,
925
+ bridgePort,
926
+ } satisfies ShellBridgeIdentity
927
+ res.setHeader('Content-Type', 'application/json')
928
+ res.setHeader('Cache-Control', 'no-store')
929
+ res.end(JSON.stringify(identity))
930
+ })
931
+ .catch(() => {
932
+ res.statusCode = 503
933
+ res.end('rnx bridge unavailable')
934
+ })
902
935
  })
903
936
  },
904
937
  // publish the bound port to the page. head-prepend so it lands before the