ReviewOS

craft-native/craft

registerVoiceAction writes a row nothing reads and replies to a global nothing assigns

#169
Closed chris opened this 9 days ago ·
9 days ago

registerVoiceAction and removeVoiceAction are @JavascriptInterface methods with three separate dead links between them. Each one alone would be a small bug; together they mean the feature cannot work from either end.

1. The row it writes is read by nothing

@JavascriptInterface
fun registerVoiceAction(phrase: String, action: String) {
    // App Actions are defined in shortcuts.xml, not dynamically
    // This stores the action for handling incoming intents
    val prefs = activity.getSharedPreferences("craft_voice_actions", Context.MODE_PRIVATE)
    prefs.edit().putString(action, phrase).apply()
    ...

The comment is honest about what it is not doing — the Assistant learns an app's actions from the manifest at install time, so nothing here could register one at runtime.

But the stated purpose of the row is "handling incoming intents", and handleVoiceAction never opens this file:

fun handleVoiceAction(intent: Intent?) {
    intent?.let {
        val action = it.action
        val data = it.dataString
        if (action != null) { ...dispatch craftVoiceAction... }
    }
}

It reads the intent's own action and data. craft_voice_actions is written in exactly two places and read in zero across the whole repository.

2. The reply goes to a global nothing assigns

"window._craftVoiceResolve && window._craftVoiceResolve({registered: true, ...})"

_craftVoiceResolve appears three times in this repository, all of them reads inside that guard. The injected bridge script has no voice namespace — there is no craft.voice.register that assigns the global inside a promise executor the way shortcuts.set does for _craftShortcutsResolve.

So the guard is always false and the reply is discarded. Not a hang: there is no promise waiting either, because there is nothing to construct one.

3. Neither method is declared or reachable through the SDK

registerVoiceAction and removeVoiceAction appear nowhere in craft.d.ts, nowhere in the injected JS, and have no iOS counterpart. The only way to call them is CraftAndroid.registerVoiceAction(...) against the raw injected object.

What is real here

The event works and should not be touched: handleVoiceAction dispatches craftVoiceAction, CraftVoiceActionEvent is declared in craft.d.ts:1527, and it is cross-platform — that is the type #135 restored after I deleted it on an iOS-only scan.

So the fix is not "delete the voice code". It is deciding what these two methods are for:

  • If App Actions are meant to be reachable from a page, handleVoiceAction should look the incoming action up in craft_voice_actions and include the registered phrase in the event detail, and the injected JS needs a voice namespace assigning the globals.
  • If they are not, both methods and the preferences file should go, and shortcuts.xml is the documented place to declare App Actions.

Not migrated to Zig

I wrote the port and then deleted it. Migrating an action whose write is read by nothing and whose reply reaches nothing would have moved the silence rather than removed it — the same reason setBadge is not migrated (#148).

Related: #140 (declared and unimplemented), #148, #157, #159, #165.

9 days ago

Implemented and pushed in b8812db (fix(android): remove unreachable voice action registration). The two raw-only methods and unread craft_voice_actions preference writes are removed; Android docs and the generated test page now point to static shortcuts.xml declarations and the existing craftVoiceAction event, which remains intact. Added generator regression coverage and lowered the Android conformance ratchet from 32 to 30. Verified locally with the Android generator suite (16/16), Android package build, Zig API/conformance and full Zig tests, Zig formatting, pickier, and diff checks. CI, SBOM, Binary Size, and Benchmark are all green on the exact commit.

Sign in to comment on this issue.