Skip to content

fix(whisper): repair Android download verification and live Download Manager visibility#418

Merged
alichherawalla merged 2 commits into
mainfrom
fix/whisper-download-fixes
Jun 26, 2026
Merged

fix(whisper): repair Android download verification and live Download Manager visibility#418
alichherawalla merged 2 commits into
mainfrom
fix/whisper-download-fixes

Conversation

@Anurag-Wednesday

Copy link
Copy Markdown
Collaborator

What

Fixes two Whisper (speech-to-text) model download bugs.

1. Android: every Whisper model failed with "verification failed"

On a fresh download, WorkerDownload.calculateTotalBytes clamped the server's real Content-Length up to the rounded-MB size seeded from JS (model.size * 1024 * 1024) via .coerceAtLeast(existingTotal).

Example — tiny.en:

  • Seeded expected: 75 * 1024 * 1024 = 78,643,200 bytes
  • Real file (server Content-Length): 77,704,715 bytes

The completion size check (0.1% tolerance) then saw actual (77.7MB) < expected (78.6MB), a 1.19% delta, and — because Whisper models ship no SHA-256 to vouch for the file — deleted it as FILE_CORRUPTED. Every model failed because every WHISPER_MODELS.size is a rounded approximation that can't equal the exact byte count.

Fix: a successful (2xx) response's Content-Length is authoritative and now wins; the seeded estimate is used only when the server omits a length (contentLength <= 0). Size/truncation validation stays active — a genuinely short download is still caught.

2. Whisper downloads were invisible in the Download Manager until a screen switch

whisperService.downloadModel started the download via backgroundDownloadService.downloadFileTo but never registered an entry in useDownloadStore (unlike text/image downloads). The manager's active list subscribes to that store, so an in-flight STT download didn't appear until navigating away and back re-scanned disk.

Fix: register an stt entry in useDownloadStore when the download starts (progress is then driven by the existing global onAnyProgress listener) and remove it on completion/failure. Completed models continue to be listed from disk by useVoiceDownloadItems, so there is no duplicate row, and a stale entry can't block a re-download.

Why

  • Whisper / transcription model downloads were completely broken on Android.
  • The Download Manager didn't reflect in-progress STT downloads, so users had no feedback that a download was running.

Tests

  • 5 Kotlin unit tests for calculateTotalBytes (in DownloadManagerModuleTest), including a regression for the exact 77.7MB-vs-78.6MB case, plus server-omits-length fallback and 206 ranged-resume behaviour.
  • 2 JS unit tests for whisperService.downloadModel asserting the store entry is registered while in flight and cleared on both success and failure.
  • Full JS suite green locally (eslint, tsc --noEmit, jest).

Note for reviewers

CI's Kotlin pre-push gate could not run in the author's local environment due to an unrelated Gradle toolchain issue (foojay-resolver references JvmVendorSpec.IBM_SEMERU, removed in Gradle 9.1.0). The Kotlin change is a small, self-contained pure-function fix with unit tests; please confirm it compiles in CI.

Anurag-Wednesday and others added 2 commits June 26, 2026 19:55
…Manager visibility

Two Whisper (STT) download bugs:

1. Android "verification failed" on every model. WorkerDownload.calculateTotalBytes
   clamped the server's real Content-Length up to the rounded-MB seed passed from
   JS (model.size * 1024 * 1024) via coerceAtLeast(existingTotal). The completion
   size check (0.1% tolerance) then saw actual < expected and, with no SHA-256 to
   vouch for the file, deleted it as FILE_CORRUPTED. Fix: trust the server's
   Content-Length on 2xx responses; fall back to the seeded estimate only when the
   server omits a length. Size/truncation validation stays active.

2. Whisper downloads were invisible in the Download Manager until a screen switch
   re-scanned disk. whisperService.downloadModel never registered the in-flight
   download in useDownloadStore (unlike text/image downloads). Fix: register an
   'stt' entry on start (progress driven by the global onAnyProgress listener) and
   remove it on completion/failure (completed models are listed from disk).

Tests: 5 Kotlin unit tests for calculateTotalBytes (incl. a regression for the
exact 77.7MB-vs-78.6MB case) and 2 JS tests for the store registration/cleanup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018nMsnbckMZTUZowdCuJ5Uk
…activation

The chat 'Pro Tools' button routed to the upsell until an app restart after a
license activated. Root cause: AppNavigator rendered dynamically-registered
screens via a non-reactive getRegisteredScreens() read, so a screen registered
at runtime (loadProFeatures on activation) never became a real route, and
navigate('McpServers') silently no-op'd.

- screenRegistry: add reactive useRegisteredScreens() (immutable list, mirrors
  slotRegistry's useSlot pattern, no version counter).
- AppNavigator: consume it so runtime-registered screens mount as live routes
  without a restart.
- ChatMessageArea: subscribe via useIsProActive() so the Pro Tools badge/count
  refreshes live on activation.

Co-Authored-By: Dishit Karia <hanmadishit74@gmail.com>
@alichherawalla alichherawalla merged commit 7ae7f8b into main Jun 26, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants