rnxsim 0.1.452 → 0.1.454

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 (63) hide show
  1. package/cli/bridge-flow-runner.ts +41 -13
  2. package/cli/cloud-client.ts +10 -0
  3. package/cli/commands/control.ts +52 -7
  4. package/cli/commands/flow.ts +1 -5
  5. package/cli/commands/inspect/settling.ts +35 -3
  6. package/cli/drivers/playwright.ts +2 -4
  7. package/cli/ws-bridge.ts +72 -5
  8. package/detox/proof-frame.cjs +2 -5
  9. package/dist-lib/agent-daemon-client.cjs +1 -1
  10. package/dist-lib/agent-events.cjs +1 -1
  11. package/dist-lib/agent-identity.cjs +1 -1
  12. package/dist-lib/agent-sessions.cjs +1 -1
  13. package/dist-lib/attached-projects.cjs +1 -1
  14. package/dist-lib/auth/shared-session.cjs +1 -1
  15. package/dist-lib/backend-origin.cjs +1 -1
  16. package/dist-lib/beta.cjs +1 -1
  17. package/dist-lib/beta.mjs +1 -1
  18. package/dist-lib/bridge-constants.cjs +1 -1
  19. package/dist-lib/bridge-contract-input.cjs +1 -1
  20. package/dist-lib/bridge-contract-input.mjs +1 -1
  21. package/dist-lib/bridge-contract.cjs +1 -1
  22. package/dist-lib/bridge-contract.mjs +1 -1
  23. package/dist-lib/capture-contract.cjs +1 -1
  24. package/dist-lib/capture-contract.mjs +1 -1
  25. package/dist-lib/cli-constants.cjs +1 -1
  26. package/dist-lib/cloud-contract.cjs +1 -1
  27. package/dist-lib/cloud-contract.mjs +1 -1
  28. package/dist-lib/cloud.cjs +1 -1
  29. package/dist-lib/cloud.mjs +1 -1
  30. package/dist-lib/config.cjs +1 -1
  31. package/dist-lib/detox/index.cjs +2 -4
  32. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  33. package/dist-lib/home-paths.cjs +1 -1
  34. package/dist-lib/host/bridge-host.cjs +1 -1
  35. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  36. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  37. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  38. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  39. package/dist-lib/host/websocket-proxy.cjs +1 -1
  40. package/dist-lib/index.cjs +89 -8
  41. package/dist-lib/jump-to-source-babel.cjs +1 -1
  42. package/dist-lib/jump-to-source-native.cjs +1 -1
  43. package/dist-lib/menu.cjs +1 -1
  44. package/dist-lib/menu.mjs +1 -1
  45. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  46. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  47. package/dist-lib/metro-production-bundle.cjs +1 -1
  48. package/dist-lib/metro-production-bundle.mjs +1 -1
  49. package/dist-lib/metro.cjs +1 -1
  50. package/dist-lib/profiles.cjs +1 -1
  51. package/dist-lib/public-brand.cjs +1 -1
  52. package/dist-lib/react-native-host-modules.cjs +1 -1
  53. package/dist-lib/react-native-host-modules.mjs +1 -1
  54. package/dist-lib/render-mode.cjs +1 -1
  55. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  56. package/dist-lib/sdk.cjs +35 -5
  57. package/dist-lib/sdk.mjs +35 -5
  58. package/dist-lib/skills.cjs +190 -44
  59. package/dist-lib/vite.cjs +1 -1
  60. package/package.json +1 -1
  61. package/src/flow-timeout-scale.ts +15 -0
  62. package/src/playwright-host-log.ts +16 -0
  63. package/src/sim-client.ts +4 -0
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.452 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.454 | (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;
@@ -1274,8 +1274,8 @@ var init_capability_inventory_coverage = __esm({
1274
1274
  ios: {
1275
1275
  combined: {
1276
1276
  raw: {
1277
- covered: 235,
1278
- ratio: 0.168700646088,
1277
+ covered: 233,
1278
+ ratio: 0.167264895908,
1279
1279
  total: 1393
1280
1280
  },
1281
1281
  weighted: {
@@ -1286,8 +1286,8 @@ var init_capability_inventory_coverage = __esm({
1286
1286
  },
1287
1287
  semantic: {
1288
1288
  raw: {
1289
- covered: 235,
1290
- ratio: 0.235707121364,
1289
+ covered: 233,
1290
+ ratio: 0.23370110331,
1291
1291
  total: 997
1292
1292
  },
1293
1293
  weighted: {
@@ -2715,13 +2715,13 @@ var init_capability_outcomes2 = __esm({
2715
2715
  assessed: 0
2716
2716
  },
2717
2717
  ios: {
2718
- passedWeight: 8418,
2718
+ passedWeight: 8422,
2719
2719
  failedWeight: 0,
2720
- unassessedWeight: 24032,
2720
+ unassessedWeight: 24028,
2721
2721
  totalWeight: 32450,
2722
- assessedWeight: 8418,
2722
+ assessedWeight: 8422,
2723
2723
  compatibility: 1,
2724
- assessed: 0.2594144838212635
2724
+ assessed: 0.259537750385208
2725
2725
  }
2726
2726
  },
2727
2727
  byWeighting: {
@@ -2736,13 +2736,13 @@ var init_capability_outcomes2 = __esm({
2736
2736
  assessed: 0
2737
2737
  },
2738
2738
  ios: {
2739
- passedWeight: 180,
2739
+ passedWeight: 181,
2740
2740
  failedWeight: 0,
2741
- unassessedWeight: 1025,
2741
+ unassessedWeight: 1024,
2742
2742
  totalWeight: 1205,
2743
- assessedWeight: 180,
2743
+ assessedWeight: 181,
2744
2744
  compatibility: 1,
2745
- assessed: 0.14937759336099585
2745
+ assessed: 0.15020746887966804
2746
2746
  }
2747
2747
  },
2748
2748
  usage: {
@@ -2756,13 +2756,13 @@ var init_capability_outcomes2 = __esm({
2756
2756
  assessed: 0
2757
2757
  },
2758
2758
  ios: {
2759
- passedWeight: 8418,
2759
+ passedWeight: 8422,
2760
2760
  failedWeight: 0,
2761
- unassessedWeight: 24032,
2761
+ unassessedWeight: 24028,
2762
2762
  totalWeight: 32450,
2763
- assessedWeight: 8418,
2763
+ assessedWeight: 8422,
2764
2764
  compatibility: 1,
2765
- assessed: 0.2594144838212635
2765
+ assessed: 0.259537750385208
2766
2766
  }
2767
2767
  }
2768
2768
  }
@@ -25730,6 +25730,9 @@ var init_cloud_client = __esm({
25730
25730
  focusSim() {
25731
25731
  return Promise.reject(new Error("remote simulators have no browser window to focus"));
25732
25732
  }
25733
+ resolveReloadedSim() {
25734
+ return Promise.resolve(null);
25735
+ }
25733
25736
  closeSim() {
25734
25737
  return Promise.reject(
25735
25738
  new Error("remote simulator close uses the simulator DELETE route")
@@ -25796,6 +25799,9 @@ var init_cloud_client = __esm({
25796
25799
  focusSim() {
25797
25800
  return Promise.reject(new Error("a box simulator has no browser window to focus"));
25798
25801
  }
25802
+ resolveReloadedSim() {
25803
+ return Promise.resolve(null);
25804
+ }
25799
25805
  closeSim() {
25800
25806
  return Promise.reject(new Error("close a box simulator with rnx box stop"));
25801
25807
  }
@@ -28611,6 +28617,15 @@ function createBridge(wsPort, opts = {}) {
28611
28617
  });
28612
28618
  let warnedAboutContention = false;
28613
28619
  let noticedTarget = false;
28620
+ let pinnedHostPid = null;
28621
+ let lineageLookupInFlight = false;
28622
+ function pickReloadedSuccessor(sims, retiredSimId) {
28623
+ if (pinnedHostPid === null) return null;
28624
+ const successors = (Array.isArray(sims) ? sims : []).filter(
28625
+ (sim) => sim.id !== retiredSimId && sim.readyState === "open" && sim.meta?.sootsimHostPid === pinnedHostPid
28626
+ );
28627
+ return successors.length === 1 ? successors[0].id : null;
28628
+ }
28614
28629
  function maybeNoticeTarget(cmdType, simId) {
28615
28630
  if (noticedTarget) return;
28616
28631
  if (process.env.SOOTSIM_QUIET_TARGET_NOTICE === "1") return;
@@ -28666,7 +28681,7 @@ function createBridge(wsPort, opts = {}) {
28666
28681
  plane: "local",
28667
28682
  async send(cmd, callOpts) {
28668
28683
  const effectiveTimeoutMs = callOpts?.timeoutMs ?? commandTimeoutMs;
28669
- const sendOnce = async (defaultSimId) => {
28684
+ const sendOnce = async (defaultSimId, cmdOverride) => {
28670
28685
  await ready;
28671
28686
  const id = nextId++;
28672
28687
  return new Promise((resolve9, reject) => {
@@ -28688,7 +28703,7 @@ function createBridge(wsPort, opts = {}) {
28688
28703
  reject(e);
28689
28704
  }
28690
28705
  });
28691
- const payload = { ...cmd, id };
28706
+ const payload = { ...cmdOverride ?? cmd, id };
28692
28707
  if (payload.simId === void 0 && defaultSimId) {
28693
28708
  payload.simId = defaultSimId;
28694
28709
  }
@@ -28698,11 +28713,36 @@ function createBridge(wsPort, opts = {}) {
28698
28713
  };
28699
28714
  const sentSimId = opts.simIdSource === "flag" ? opts.simId : readCurrentSimId() ?? opts.simId;
28700
28715
  try {
28701
- return await sendOnce(sentSimId);
28716
+ const result = await sendOnce(sentSimId);
28717
+ if (sentSimId && pinnedHostPid === null && !lineageLookupInFlight) {
28718
+ lineageLookupInFlight = true;
28719
+ void sendOnce(void 0, { type: "bridge:list-sims" }).then((sims) => {
28720
+ if (!Array.isArray(sims)) return;
28721
+ const pinned = sims.find((sim) => sim.id === sentSimId);
28722
+ const hostPid = pinned?.meta?.sootsimHostPid;
28723
+ if (typeof hostPid === "number") pinnedHostPid = hostPid;
28724
+ }).catch(() => {
28725
+ }).finally(() => {
28726
+ lineageLookupInFlight = false;
28727
+ });
28728
+ }
28729
+ return result;
28702
28730
  } catch (error) {
28703
28731
  const message = error instanceof Error ? error.message : String(error);
28704
28732
  const missingSimMessage = `no sim connected with id ${sentSimId}`;
28705
28733
  if (opts.simIdSource !== "flag" && sentSimId && cmd.simId === void 0 && (message === missingSimMessage || message.startsWith(`${missingSimMessage};`))) {
28734
+ const successor = pickReloadedSuccessor(
28735
+ await sendOnce(void 0, { type: "bridge:list-sims" }),
28736
+ sentSimId
28737
+ );
28738
+ if (successor) {
28739
+ saveCurrentSimId(successor);
28740
+ process.stderr.write(
28741
+ ` note: sim ${sentSimId} reloaded and reconnected as ${successor}; following it
28742
+ `
28743
+ );
28744
+ return sendOnce(successor);
28745
+ }
28706
28746
  clearCurrentSimId();
28707
28747
  throw new Error(
28708
28748
  `saved sim ${sentSimId} is gone${message.slice(missingSimMessage.length)}; run \`rnx list\` and \`rnx use <sim>\`, or open a fresh sim`
@@ -28715,6 +28755,13 @@ function createBridge(wsPort, opts = {}) {
28715
28755
  const sims = await this.send({ type: "bridge:list-sims" });
28716
28756
  return Array.isArray(sims) ? sims : [];
28717
28757
  },
28758
+ // a caller holding its own pinned sim id (a flow runner sets simId on every
28759
+ // command, so it never reaches the pin-following path in send) asks here
28760
+ // after a reload retires that id. null means nothing proves what replaced
28761
+ // it, and the caller must fail rather than guess.
28762
+ async resolveReloadedSim(retiredSimId) {
28763
+ return pickReloadedSuccessor(await this.listSims(), retiredSimId);
28764
+ },
28718
28765
  async focusSim(simId) {
28719
28766
  return this.send({ type: "focus", simId });
28720
28767
  },
@@ -29107,6 +29154,18 @@ var init_dev_bundle_resolution = __esm({
29107
29154
  }
29108
29155
  });
29109
29156
 
29157
+ // src/flow-timeout-scale.ts
29158
+ function flowTimeoutScale() {
29159
+ return Math.min(10, Math.max(1, Number(process.env[FLOW_TIMEOUT_SCALE_ENV]) || 1));
29160
+ }
29161
+ var FLOW_TIMEOUT_SCALE_ENV;
29162
+ var init_flow_timeout_scale = __esm({
29163
+ "src/flow-timeout-scale.ts"() {
29164
+ "use strict";
29165
+ FLOW_TIMEOUT_SCALE_ENV = "SOOTSIM_FLOW_TIMEOUT_SCALE";
29166
+ }
29167
+ });
29168
+
29110
29169
  // src/profiles.ts
29111
29170
  function normalizeProfileId(input) {
29112
29171
  const id = input.trim();
@@ -30613,6 +30672,18 @@ var init_electron = __esm({
30613
30672
  }
30614
30673
  });
30615
30674
 
30675
+ // src/playwright-host-log.ts
30676
+ function playwrightHostLogName(nowMs, pid) {
30677
+ return `${PLAYWRIGHT_HOST_LOG_PREFIX}${nowMs.toString(36)}-${pid}.log`;
30678
+ }
30679
+ var PLAYWRIGHT_HOST_LOG_PREFIX;
30680
+ var init_playwright_host_log = __esm({
30681
+ "src/playwright-host-log.ts"() {
30682
+ "use strict";
30683
+ PLAYWRIGHT_HOST_LOG_PREFIX = "rnx-playwright-host-";
30684
+ }
30685
+ });
30686
+
30616
30687
  // cli/internal-child.ts
30617
30688
  var RNX_INTERNAL_COMMAND, RNX_INTERNAL_CHILDREN;
30618
30689
  var init_internal_child = __esm({
@@ -30822,10 +30893,7 @@ async function launch2(opts) {
30822
30893
  message: `playwright browser provisioning failed: ${provisioning.message}`
30823
30894
  };
30824
30895
  }
30825
- const errLog = (0, import_path7.join)(
30826
- (0, import_os2.tmpdir)(),
30827
- `rnx-playwright-host-${Date.now().toString(36)}-${process.pid}.log`
30828
- );
30896
+ const errLog = (0, import_path7.join)((0, import_os2.tmpdir)(), playwrightHostLogName(Date.now(), process.pid));
30829
30897
  const connectAckFile = `${errLog}.connected`;
30830
30898
  const errFd = (0, import_fs8.openSync)(errLog, "a");
30831
30899
  try {
@@ -30900,6 +30968,7 @@ var init_playwright = __esm({
30900
30968
  import_fs8 = require("fs");
30901
30969
  import_os2 = require("os");
30902
30970
  import_path7 = require("path");
30971
+ init_playwright_host_log();
30903
30972
  init_profiles();
30904
30973
  init_internal_child();
30905
30974
  init_parent_pid();
@@ -31792,9 +31861,11 @@ async function buildOpenUrl(target, baseUrl, openToken, opts = {}) {
31792
31861
  return url.toString();
31793
31862
  }
31794
31863
  async function waitForSimReady(wsPort, commandTimeoutMs, simId, opts = {}) {
31795
- const attempts = opts.attempts ?? 30;
31796
31864
  const intervalMs = opts.intervalMs ?? 500;
31865
+ const attempts = opts.attempts ?? Math.round(30 * flowTimeoutScale());
31797
31866
  const minNodeCount = opts.minNodeCount ?? 10;
31867
+ let lastCount;
31868
+ let lastError = "";
31798
31869
  for (let i = 0; i < attempts; i++) {
31799
31870
  const bridge = createBridge(wsPort, {
31800
31871
  commandTimeoutMs,
@@ -31804,17 +31875,20 @@ async function waitForSimReady(wsPort, commandTimeoutMs, simId, opts = {}) {
31804
31875
  try {
31805
31876
  const count = await bridge.send({
31806
31877
  type: "evaluate",
31807
- code: "(async () => (await window.__sootsimTest?.getNodeCount()) || 0)()"
31878
+ code: "(async () => (await window.__sootsimTest?.getNodeCount()) ?? null)()"
31808
31879
  });
31880
+ lastCount = count;
31881
+ lastError = "";
31809
31882
  if (typeof count === "number" && count > minNodeCount) {
31810
31883
  return { bridge, count };
31811
31884
  }
31812
- } catch {
31885
+ } catch (err) {
31886
+ lastError = err instanceof Error ? err.message : String(err);
31813
31887
  }
31814
31888
  bridge.close();
31815
31889
  await sleep2(intervalMs);
31816
31890
  }
31817
- return null;
31891
+ return { bridge: null, attempts, intervalMs, minNodeCount, lastCount, lastError };
31818
31892
  }
31819
31893
  async function waitForSimMatch(wsPort, commandTimeoutMs, predicate, opts = {}) {
31820
31894
  const attempts = opts.attempts ?? 30;
@@ -32306,6 +32380,7 @@ async function runOpenCommand(args, opts = {}) {
32306
32380
  const bridge = createBridgeFromParsed(parsed);
32307
32381
  const simHint = parsed.simId ? ` --sim ${parsed.simId}` : "";
32308
32382
  let fallBackToNewSim = false;
32383
+ let preNavSimIds = /* @__PURE__ */ new Set();
32309
32384
  try {
32310
32385
  let targetSim = null;
32311
32386
  let targetShellUrl = null;
@@ -32355,6 +32430,7 @@ async function runOpenCommand(args, opts = {}) {
32355
32430
  }
32356
32431
  }
32357
32432
  if (!fallBackToNewSim && targetSim) {
32433
+ preNavSimIds = new Set(sims.map((sim) => sim.id));
32358
32434
  const resolvedBaseUrl = hasExplicitBaseUrl || looksLikeSootsimUrl(target) ? baseUrl : deriveCurrentSimBaseUrl(targetSim, baseUrl);
32359
32435
  const shellUrl = targetShellUrl ?? await buildShellUrl(
32360
32436
  target,
@@ -32381,13 +32457,31 @@ async function runOpenCommand(args, opts = {}) {
32381
32457
  }
32382
32458
  if (!fallBackToNewSim && targetSim) {
32383
32459
  await sleep2(1500);
32460
+ const navSim = targetSim;
32461
+ const reloadedSim = await waitForSimMatch(
32462
+ parsed.wsPort,
32463
+ parsed.commandTimeoutMs,
32464
+ (sim) => sim.readyState === "open" && simUsesDriver(sim, driverId) && (sim.id === navSim.id || !preNavSimIds.has(sim.id) && sim.isPrimary),
32465
+ { attempts: Math.round(30 * flowTimeoutScale()) }
32466
+ );
32467
+ if (reloadedSim && reloadedSim.id !== navSim.id) {
32468
+ targetSim = reloadedSim;
32469
+ }
32384
32470
  const ready = await waitForSimReady(
32385
32471
  parsed.wsPort,
32386
32472
  parsed.commandTimeoutMs,
32387
32473
  targetSim.id
32388
32474
  );
32389
- if (!ready) {
32390
- console.error(" timed out waiting for current sim to load target");
32475
+ if (!ready.bridge) {
32476
+ const observed = ready.lastError ? `last bridge error: ${ready.lastError}` : ready.lastCount === null ? "guest never installed window.__sootsimTest" : `last node count: ${String(ready.lastCount)} (needs > ${ready.minNodeCount})`;
32477
+ console.error(
32478
+ ` timed out waiting for current sim to load target after ${ready.attempts} polls over ${Math.round(ready.attempts * ready.intervalMs / 1e3)}s; ${observed}`
32479
+ );
32480
+ await printBridgeFailureDiagnostics(bridge, {
32481
+ errorsCommand: `rnx get errors 5${simHint}`,
32482
+ warningsCommand: `rnx get warnings 5${simHint}`,
32483
+ requestsCommand: `rnx get requests 5${simHint}`
32484
+ });
32391
32485
  rnxExit(1);
32392
32486
  }
32393
32487
  ready.bridge.close();
@@ -32787,6 +32881,7 @@ var init_control = __esm({
32787
32881
  init_bridge_constants();
32788
32882
  init_config();
32789
32883
  init_dev_bundle_resolution();
32884
+ init_flow_timeout_scale();
32790
32885
  init_home_paths();
32791
32886
  init_native_dev_bundle_url();
32792
32887
  init_profiles();
@@ -32847,9 +32942,7 @@ var require_proof_frame = __commonJS({
32847
32942
  throw new Error("proof content measurement needs RGBA pixels and dimensions");
32848
32943
  }
32849
32944
  const yStart = Math.floor(height * PROOF_CONTENT_DEFINITION.contentBandInsetFraction);
32850
- const yEnd = Math.ceil(
32851
- height * (1 - PROOF_CONTENT_DEFINITION.contentBandInsetFraction)
32852
- );
32945
+ const yEnd = Math.ceil(height * (1 - PROOF_CONTENT_DEFINITION.contentBandInsetFraction));
32853
32946
  const bucketCounts = new Uint32Array(32 * 32 * 32);
32854
32947
  let totalPixels = 0;
32855
32948
  for (let y = yStart; y < yEnd; y++) {
@@ -33350,8 +33443,19 @@ async function waitForSootsimIdle({
33350
33443
  let lastRequestTotal = -1
33351
33444
  let stable = 0
33352
33445
  let visuallyStillSince = 0
33446
+ // counts across the whole budget: a flag that is true on every poll is a
33447
+ // different bug from one that is true intermittently, and the two need
33448
+ // opposite fixes (engine vs budget).
33449
+ let polls = 0
33450
+ let dirtyPolls = 0
33451
+ let animatingPolls = 0
33452
+ let layoutChangedPolls = 0
33353
33453
  while (Date.now() < deadline) {
33354
33454
  const snapshot = await readSnapshot()
33455
+ polls++
33456
+ if (snapshot.layoutDirty) dirtyPolls++
33457
+ if (snapshot.animating) animatingPolls++
33458
+ if (polls > 1 && snapshot.layout !== lastLayout) layoutChangedPolls++
33355
33459
  const strictOk =
33356
33460
  !strict ||
33357
33461
  (snapshot.renderStatsAvailable && !snapshot.animating && !snapshot.layoutDirty)
@@ -33372,7 +33476,7 @@ async function waitForSootsimIdle({
33372
33476
  stable >= requiredStablePolls &&
33373
33477
  (networkQuiet || Date.now() - visuallyStillSince >= networkQuietGraceMs)
33374
33478
  ) {
33375
- return { settled: true, elapsed: Date.now() - start }
33479
+ return { settled: true, elapsed: Date.now() - start, blockedBy: '' }
33376
33480
  }
33377
33481
  } else {
33378
33482
  stable = 0
@@ -33382,17 +33486,33 @@ async function waitForSootsimIdle({
33382
33486
  lastRequestTotal = snapshot.requestTotal
33383
33487
  await sleep(pollMs)
33384
33488
  }
33385
- return { settled: false, elapsed: Date.now() - start }
33489
+ const last = await readSnapshot()
33490
+ const blockers = []
33491
+ if (strict && !last.renderStatsAvailable) blockers.push('render stats unavailable')
33492
+ if (last.animating) blockers.push('animations still running')
33493
+ if (last.layoutDirty) blockers.push('layout still dirty')
33494
+ if (last.pendingFetches > 0) blockers.push(last.pendingFetches + ' image fetches pending')
33495
+ if (last.layout !== lastLayout) blockers.push('layout still changing')
33496
+ if (last.requestInFlight > 0) blockers.push(last.requestInFlight + ' requests in flight')
33497
+ return {
33498
+ settled: false,
33499
+ elapsed: Date.now() - start,
33500
+ blockedBy:
33501
+ (blockers.length ? blockers.join(', ') : 'unknown') +
33502
+ ' over ' + polls + ' polls (layout changed ' + layoutChangedPolls +
33503
+ 'x, layoutDirty ' + dirtyPolls + 'x, animating ' + animatingPolls + 'x)',
33504
+ }
33386
33505
  })()`
33387
33506
  },
33388
33507
  {
33389
33508
  timeoutMs: maxMs + 1e3
33390
33509
  }
33391
33510
  );
33392
- const { elapsed, settled } = result ?? {};
33511
+ const { elapsed, settled, blockedBy } = result ?? {};
33393
33512
  return {
33394
33513
  elapsed: typeof elapsed === "number" ? elapsed : maxMs,
33395
- settled: settled === true
33514
+ settled: settled === true,
33515
+ blockedBy: typeof blockedBy === "string" ? blockedBy : "unknown"
33396
33516
  };
33397
33517
  }
33398
33518
  var init_settling = __esm({
@@ -33869,17 +33989,16 @@ var init_bridge_flow_runner = __esm({
33869
33989
  import_settings3 = require("sootsim-engine/settings");
33870
33990
  import_yaml = __toESM(require("yaml"), 1);
33871
33991
  import_proof_frame = __toESM(require_proof_frame(), 1);
33992
+ init_flow_timeout_scale();
33872
33993
  init_frame_compose();
33873
33994
  init_core();
33874
33995
  init_settling();
33875
33996
  init_shared();
33997
+ init_current_sim();
33876
33998
  init_maestro_js();
33877
33999
  init_recording_access();
33878
34000
  DEFAULT_TIMEOUT = 1e4;
33879
- FLOW_TIMEOUT_SCALE = Math.min(
33880
- 10,
33881
- Math.max(1, Number(process.env.SOOTSIM_FLOW_TIMEOUT_SCALE) || 1)
33882
- );
34001
+ FLOW_TIMEOUT_SCALE = flowTimeoutScale();
33883
34002
  scaledTimeout = (ms) => Math.round(ms * FLOW_TIMEOUT_SCALE);
33884
34003
  SCREEN_W = 393;
33885
34004
  SCREEN_H = 852;
@@ -33942,11 +34061,32 @@ var init_bridge_flow_runner = __esm({
33942
34061
  });
33943
34062
  }
33944
34063
  async waitForTree(timeout = DEFAULT_TIMEOUT) {
33945
- const status = await inspectWaitReady(this.bridge, scaledTimeout(timeout));
34064
+ let status = await inspectWaitReady(this.bridge, scaledTimeout(timeout));
33946
34065
  if (status.ready) return;
33947
34066
  if (status.externalError) {
33948
34067
  throw new Error(`app failed to load: ${status.externalError}`);
33949
34068
  }
34069
+ if (status.bridgeError) {
34070
+ const pinned = this.opts.simId;
34071
+ let successor = null;
34072
+ if (pinned) {
34073
+ const deadline = Date.now() + scaledTimeout(15e3);
34074
+ do {
34075
+ successor = await this.bridge.resolveReloadedSim(pinned);
34076
+ if (successor) break;
34077
+ await sleep3(500);
34078
+ } while (Date.now() < deadline);
34079
+ }
34080
+ if (successor) {
34081
+ console.error(
34082
+ ` note: sim ${pinned} reloaded and reconnected as ${successor}; following it`
34083
+ );
34084
+ this.opts.simId = successor;
34085
+ saveCurrentSimId(successor);
34086
+ status = await inspectWaitReady(this.bridge, scaledTimeout(timeout));
34087
+ if (status.ready) return;
34088
+ }
34089
+ }
33950
34090
  if (status.bridgeError) {
33951
34091
  throw new Error(
33952
34092
  `target sim is not responding to the bridge (${status.bridgeError}). it is likely a stale orphan; run \`rnx list\`, then close it with \`rnx close <id>\` or start a fresh one with \`rnx open --new <port>\`.`
@@ -35077,7 +35217,9 @@ var init_bridge_flow_runner = __esm({
35077
35217
  strict: true
35078
35218
  });
35079
35219
  if (!result.settled) {
35080
- throw new Error(`visual state did not settle within ${result.elapsed}ms`);
35220
+ throw new Error(
35221
+ `visual state did not settle within ${result.elapsed}ms (${result.blockedBy})`
35222
+ );
35081
35223
  }
35082
35224
  }
35083
35225
  resolveShellLaunchAppId(opts, state) {
@@ -35306,7 +35448,11 @@ var init_bridge_flow_runner = __esm({
35306
35448
  try {
35307
35449
  const hasExplicitWait = !!(step.extendedWaitUntil || step.waitFor || step.scrollUntilVisible || step.launchApp || step.runFlow || // runScript http calls are synchronous with a 5-minute upstream
35308
35450
  // timeout — the 10s watchdog would kill legitimate setup scripts.
35309
- step.runScript);
35451
+ step.runScript || // takeScreenshot self-governs: it retries a bounded visual settle that
35452
+ // already fails with its own reason. sharing the watchdog's 10s budget
35453
+ // meant the watchdog always won that race, reporting "bridge probably
35454
+ // hung" for a screen that was merely still animating.
35455
+ step.takeScreenshot);
35310
35456
  const STEP_WATCHDOG_MS = 1e4;
35311
35457
  const body = this.runStepInner(step);
35312
35458
  let screenshotPath;
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.452 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.454 | (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.452",
3
+ "version": "0.1.454",
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
+ // hosted-runner previews reach first-render much slower than local dev: the
2
+ // guest bundle (often tens of MB) re-evals and re-hydrates on every launchApp
3
+ // hard reload, and it is served over a cloudflared tunnel. run.sh sets
4
+ // SOOTSIM_FLOW_TIMEOUT_SCALE for the recording lane so a slow-but-working app
5
+ // clears its waits without per-app timeouts. off (1x) locally, clamped to 1..10.
6
+ //
7
+ // every deadline a hosted launchApp has to clear must use this, or the lane is
8
+ // only half-scaled: the flow runner's own waits scaled while the CLI's
9
+ // post-navigation readiness poll kept a flat ceiling, so a launchApp that was
10
+ // working but slow failed the flow.
11
+ export const FLOW_TIMEOUT_SCALE_ENV = 'SOOTSIM_FLOW_TIMEOUT_SCALE'
12
+
13
+ export function flowTimeoutScale(): number {
14
+ return Math.min(10, Math.max(1, Number(process.env[FLOW_TIMEOUT_SCALE_ENV]) || 1))
15
+ }
@@ -0,0 +1,16 @@
1
+ // the playwright driver writes its browser-host stderr to a tmpdir log, and
2
+ // that log carries the only hardware-WebGL proof the hosted recorder accepts.
3
+ // the driver names the file and the recorder scans for it, so the prefix lives
4
+ // here rather than as a literal in both. it was already renamed once
5
+ // (sootsim- -> rnx-) without the recorder following, which silently failed
6
+ // every hosted job with "run completed without hardware WebGL proof" even when
7
+ // the preview itself uploaded fine.
8
+ export const PLAYWRIGHT_HOST_LOG_PREFIX = 'rnx-playwright-host-'
9
+
10
+ export function playwrightHostLogName(nowMs: number, pid: number): string {
11
+ return `${PLAYWRIGHT_HOST_LOG_PREFIX}${nowMs.toString(36)}-${pid}.log`
12
+ }
13
+
14
+ export function isPlaywrightHostLog(name: string): boolean {
15
+ return name.startsWith(PLAYWRIGHT_HOST_LOG_PREFIX) && name.endsWith('.log')
16
+ }
package/src/sim-client.ts CHANGED
@@ -261,6 +261,10 @@ export class SimClient implements WsBridge, InspectBridge {
261
261
  return this.bridge.listSims()
262
262
  }
263
263
 
264
+ resolveReloadedSim(retiredSimId: string): Promise<string | null> {
265
+ return this.bridge.resolveReloadedSim(retiredSimId)
266
+ }
267
+
264
268
  focusSim(simId: string = this.simId): Promise<any> {
265
269
  return this.bridge.focusSim(simId)
266
270
  }