These cases in CraftApp.swift's dispatch switch settle the page's promise
only from inside an if let guard, with no else:
| line | action | guard |
|---|---|---|
| 821 | setBadge | body["count"] as? Int |
| 835 | setFlashlight | body["enabled"] as? Bool |
| 848 | openURL | body["url"] as? String, URL(string:) |
| 1256 | setShortcuts | body["shortcuts"] as? [[String: Any]] |
| 1270 | getSharedItem | body["key"] as? String |
| 1275 | removeSharedItem | body["key"] as? String |
| 1282 | setBiometricPersistence | body["enabled"] as? Bool |
| 1366 | updateWidget | body["data"] as? [String: Any] |
| 1379 | removeSiriShortcut | body["identifier"] as? String |
| 1385 | sendToWatch | body["message"] as? [String: Any] |
| 1389 | updateWatchContext | body["context"] as? [String: Any] |
When the guard fails the case falls out of the switch and nothing is sent, so the promise stays pending forever. From the page it is indistinguishable from a slow native call, which is the worst version of this: no error, no timeout, no way to tell.
Reachable without doing anything unusual. window.craft.setBadge() with no
argument posts count: undefined, WebKit drops the key, body["count"] is
nil, promise dropped. setBadge(1.5) gives an NSNumber that as? Int
refuses. openURL('') — URL(string: "") is nil.
Observed on a booted simulator while building #199: setBadge(3) did not
settle within the harness's per-case timeout. (That particular call was in
fact waiting on the notification-authorization prompt, not on this bug — but
looking at why is what turned it up.)
This is a different family from the capability gates. Those already answer
with CAPABILITY_DISABLED, and the Zig dispatcher answers them too
(ios_config.gateFor). This is the argument-validation path on actions that
are not gated at all, and rejectCallback(callbackId, error:, code:
"INVALID_ARGUMENT") is the shape the switch already uses for it —
case "share" does exactly that when it has nothing to share
(CraftApp.swift:672).
Related: packages/ios/fixtures/zig-slice/build-and-run.sh:251-259 notes the
Swift spec drops the callback on 60 of its 65 gated cases; this is the
ungated remainder.