Two native-only fixes behind the "transcription failed [object Object]"
report on the APK:
1. Model download. engineImpl.native.loadModel assumed the ggml .bin was
already on disk (the downloader was a TODO), so on a real device it
rejected — and since in-app recording is web-only, no model was ever
present. Now loadModel fetches the model from Hugging Face
(ggerganov/whisper.cpp) into <documentDirectory>/models on first use,
reporting 0..1 progress through the existing "Loading model… X%" UI.
Downloads to a .part file and renames on success so an interrupted
download can't leave a truncated model; if initWhisper still rejects,
the file is deleted so the next try re-downloads cleanly. Model URLs
verified (tiny.en 77.7MB / base.en 148MB / small.en 488MB).
2. Error surfacing. transcribeStore did `String(err)`, which renders a
non-Error native rejection (whisper.rn / file system throw plain
objects) as "[object Object]". New shared errorMessage() pulls
message/reason/code (or JSON) out of whatever was thrown, so failures
are actionable instead of opaque.
Progress downloads aren't in the new expo-file-system OO API yet, so the
fetch uses the still-supported expo-file-system/legacy resumable
downloader; native-only, not bundled on web.
Validated: tsc clean, 279 tests pass, web export unaffected (legacy not
in the web bundle), arm64 APK builds and the native JS bundle resolves
the legacy import. On-device download + transcribe still needs a real
phone to confirm end to end.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>