Ratchet 96 → 95, and the most interesting one so far: iOS declares this action .status = .unavailable, and that refusal is correct.
The same action, opposite verdicts, for a real reason
bridge_mobile_device.zig refuses getNetworkStatus on iOS because no synchronous iOS API can answer isConnected without fabricating it:
NWPathMonitordelivers on a background queue whereevaluateJavaScript:is illegalSCNetworkReachabilityis deprecated since 17.4, can't see wired ethernet, and reports the host Mac's reachability on the simulatorgetifaddrsknows which interfaces are up, not whether anything is reachable
The alternative there is {"isConnected":true,"type":"unknown"} — which a caller cannot tell from a real reading, making it strictly worse than an error.
Android has the API iOS lacks. getNetworkCapabilities is synchronous and answers null when nothing is connected, so isConnected is a fact here rather than a guess.
Worth stating plainly in the module, because the next person comparing the two platforms' tables will otherwise see one refusing what the other serves and assume someone gave up.
isConnected and type are not the same question
capabilities != null means something is connected. The transport is a separate lookup, and the Kotlin's when falls through to "none" on Bluetooth tethering, a VPN-only interface, and most emulators.
So {"isConnected":true,"type":"none"} is reachable and correct, not a contradiction to smooth over. Forcing type to "unknown" whenever connected, or isConnected false whenever the transport is unrecognised, would each be wrong in a way the page could not detect.
A comment that became a test
The module says the probes run in the Kotlin's order and stop at the first match, because when does. That was just a claim until a fake ConnectivityManager made it assertable:
a connection reporting both
WIFIandETHERNET— which a bridged emulator does — must answer"wifi"
Reordering the probes changes that answer without changing anything a reader would look at. Now it fails a test.
| mutation | result |
|---|---|
| probes reordered (ethernet first) | the two-transport test fails |
| null capabilities reported as connected | the offline test fails |
isWifi and isCellular are derived from the transport rather than read again — what the Kotlin does, and what stops the three disagreeing.
Verified
JNI gained CallBooleanMethodA. zig build test:android → 87 tests, 11/11 steps · both .so build · generator 10/10.
Correction to something I said earlier
I claimed the next Android candidates were structural walls. That was wrong — I'd looked at setKeepAwake and lockOrientation (which do need runOnUiThread, hence a Runnable) and generalised from two.
Measuring all 103: 32 need no evaluateJavascript, no runOnUiThread, no permission check and no listener. Eight are now migrated, so roughly two dozen remain reachable with the machinery already built — secureSet/Get/Remove/Clear, setBadge/clearBadge, vibrate, haptic, the notification cancels, and more. The walls are real but they are not next.