dsh-mobilecode 0.11.3 → 0.11.4

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 (3) hide show
  1. package/README.md +3 -2
  2. package/lib/client.js +32 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -156,8 +156,9 @@ card is **Device 1**; co-op docks a second, **Device 2**. It is produced
156
156
  independently. Each pane grants its own HMAC capability; closing one pane
157
157
  never stalls or disturbs the other stream. A presence watcher polls device
158
158
  liveness while streaming: if a device is powered off (⏻ off, crash, unplug)
159
- its pane drops the frozen frame and the Live badge within ~5 s, and the card
160
- stays idle instead of silently grabbing another device (0.11.3). Both stream
159
+ its pane drops the frozen frame and the Live badge within ~5 s, and stays
160
+ idle instead of silently grabbing another device both panes guard against
161
+ the re-grab (0.11.3–4). Both stream
161
162
  captions carry the running client version (`· v0.11.x`) so a page refresh is
162
163
  visibly proven.
163
164
  - **Security**: every stream route sits behind a loopback + trusted-browser
package/lib/client.js CHANGED
@@ -190,7 +190,7 @@ window.__ModuleLoader__.load({
190
190
  // Shown on both stream captions so a refresh visibly proves which client
191
191
  // bundle the browser is actually running. Kept in lockstep with
192
192
  // package.json by test/client.mjs.
193
- const MC_VERSION = "0.11.3";
193
+ const MC_VERSION = "0.11.4";
194
194
  // The panel shows cards for platforms the host can actually drive; the
195
195
  // server reports its OS in info.os (iOS tooling is darwin-only).
196
196
  const fallbackPlatforms = (os) => (os === "darwin" ? ["ios", "android"] : ["android"]);
@@ -676,7 +676,12 @@ window.__ModuleLoader__.load({
676
676
  const r = await streamPost("/stream/devices", {});
677
677
  setDevices(r.devices);
678
678
  setAvds(r.avds ?? []);
679
- const first = r.devices.find((d) => d.streaming)?.serial ?? r.devices[0]?.serial ?? "";
679
+ // Prefer emulators for the welcome auto-pick: a physical
680
+ // serial sorts first in adb order but has no ⏻ off control.
681
+ // (Serial prefix, not the avd field — avdName() can fail.)
682
+ const isEmu = (d) => d.serial.startsWith("emulator-");
683
+ const first = r.devices.find((d) => d.streaming && isEmu(d))?.serial ?? r.devices.find(isEmu)?.serial
684
+ ?? r.devices.find((d) => d.streaming)?.serial ?? r.devices[0]?.serial ?? "";
680
685
  const cur = serialRef.current;
681
686
  if (cur === "") {
682
687
  // Auto-stream once per page load (the welcome flow). After
@@ -870,22 +875,35 @@ window.__ModuleLoader__.load({
870
875
  const [error, setError] = useState("");
871
876
  const live = useLiveStream({ serial, onError: setError });
872
877
  const grant = (device) => live.grant(device).then((ok) => { if (ok) setError(""); });
873
- // Serial mirror for the interval (avoids stale-closure on setInterval).
878
+ // Serial mirror for the 5s poller + one-shot auto-pick guard: this
879
+ // pane may steal a partner while nothing is picked, but it must NOT
880
+ // silently switch to a third device after its own goes offline.
874
881
  const serialRef = useRef(serial);
875
882
  serialRef.current = serial;
883
+ const autoPicked = useRef(false);
876
884
 
877
885
  const refresh = async () => {
878
886
  try {
879
887
  const r = await streamPost("/stream/devices", {});
880
888
  setDevices(r.devices);
881
- const others = r.devices.filter((d) => d.serial !== excludeSerial);
882
- const next = (others.find((d) => d.streaming) ?? others[0])?.serial ?? "";
889
+ // Partner candidates: emulators only (serial prefix, not the
890
+ // flaky avd field) the co-op partner must be killable from
891
+ // the picker, and never Device 1's own serial.
892
+ const others = r.devices.filter((d) => d.serial !== excludeSerial && d.serial.startsWith("emulator-"));
883
893
  const cur = serialRef.current;
884
- // If the current device is still valid (online and not excluded), keep it.
885
- if (cur !== "" && cur !== excludeSerial && others.some((d) => d.serial === cur)) return;
886
- // On any change (death, exclusion, or manual), drop the stream so we
887
- // don't show a frozen frame under the new serial.
888
- if (next !== cur) { setSerial(next); live.reset(); }
894
+ if (cur !== "") {
895
+ // Keep any valid pick including a manually chosen physical
896
+ // device; only emulators are eligible for auto-pick. A dead
897
+ // or stolen pick clears to the placeholder (never re-grabs).
898
+ if (cur !== excludeSerial && r.devices.some((d) => d.serial === cur)) return;
899
+ autoPicked.current = true;
900
+ setSerial(""); live.reset();
901
+ return;
902
+ }
903
+ if (!autoPicked.current) {
904
+ const next = (others.find((d) => d.streaming) ?? others[0])?.serial ?? "";
905
+ if (next !== "") { autoPicked.current = true; setSerial(next); }
906
+ }
889
907
  } catch {
890
908
  /* the header keeps the last device list */
891
909
  }
@@ -905,7 +923,10 @@ window.__ModuleLoader__.load({
905
923
  live.streamUrl !== "" && h("span", { className: "mc-live-badge" }, h("span", { className: "mc-dot on" }), "Live"),
906
924
  h("span", { className: "sp" }),
907
925
  h("select", { className: "mc-input mc-coop-select", value: serial, onChange: (e) => { setSerial(e.target.value); live.reset(); } },
908
- devices.length === 0 && h("option", { value: "" }, "no device"),
926
+ // An honest empty option: a select with no matching option
927
+ // silently DISPLAYS devices[0] while state is still "" —
928
+ // that made the probe read a phantom pick (0.11.4).
929
+ !devices.some((d) => d.serial === serial) && h("option", { value: "" }, devices.length === 0 ? "no device" : "— pick device —"),
909
930
  devices.map((d) => h("option", { key: d.serial, value: d.serial }, d.serial + (d.serial === excludeSerial ? " (Device 1)" : d.streaming ? " ●" : ""))),
910
931
  ),
911
932
  ),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-mobilecode",
3
3
  "description": "MobileCode for the dsh web GUI: detect iOS/Android projects, run preview servers, and drive the simulator/emulator from the session — 37 agent tools (device_run, device_screen, device_ui_tree, device_ui_rows, device_tap_row, device_tap_element, device_wait_for, device_scroll_to, device_input, device_batch, device_intent, device_connect, device_pair_qr, device_perf, device_meminfo, device_backtrace, device_display, device_avd_create, device_pair_capture, device_app_info, device_install, device_uninstall, device_reboot, device_log, live screen stream, multimodal screenshots) plus a host-mediated co-op mesh hub (random callsigns, JSON pub/sub, tunable latency/jitter/drop/dup/throttle) so two virtual devices — and the AI watching them — can talk in real time. One classified adb boundary and a Wi-Fi connect/pair QR flow in the Connection settings tab. Hot-pluggable — mounted via the profile bundle list + cordis.patch.yml, no dsh source changes.",
4
- "version": "0.11.3",
4
+ "version": "0.11.4",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.22.0",
7
7
  "engines": {