Fixes a regression I introduced in d92a01b (#134).
What broke
That change deleted CraftVoiceActionEvent on the grounds that the name "appears nowhere else in the repository". It appears in CraftBridge.kt.template:3731, which dispatches it:
"window.dispatchEvent(new CustomEvent('craftVoiceAction', {detail: {action: '$action', data: '$data'}}));",The scan behind that claim covered .swift, .zig, .ts and .js. It never opened a .kt.template, so a live Android event read as fiction and a real type went away.
The worse half
The check added alongside the deletion would have rejected the fix. craft.d.ts is the SDK's whole type surface — both platforms — and the test compared it against the iOS spec alone. A correct Android-only entry fails an iOS-only rule, so the guard's own error message argues for the bug.
That is the failure mode this whole line of work exists to catch, committed by the checking machinery itself: a scan whose scope silently disagrees with the thing it is scanning.
The fix
The vocabulary is now the union of what either bridge dispatches. CraftBridge.kt is embedded and scanned the same way CraftApp.swift already is:
sendEvent("craftX"— the Kotlin helperCustomEvent('craftX'— the literals the injected JS emits directly
Android's window._craftXResolve handles are deliberately not matched; those are promise plumbing, not events.
That surfaced a second Android event with no type at all: craftAppStateChange, dispatched by startAppStateMonitoring (:2075), which iOS answers by polling getAppState instead. Both events are typed now and marked Android-only.
Verified
mutation: delete craftVoiceAction from the map again | SdkDoesNotTypeAnEventThatFires ✓ |
zig build test:ios | 5 + 1067 + 21 green |
tsc --noEmit | clean |
Note on #127
The four dead subscriptions in that issue are still dead — and the Android bridge makes the case stronger, not weaker. craftShortcut, craftOTAProgress and craftOTAStatus are subscribed by both bridges' injected JS and dispatched by neither. craftARPlane is a fifth of the same shape on Android alone: subscribed there, dispatched only on iOS. I'll update that issue with the Android evidence separately.