Skip to content

feat(desktop): the Import form says which program is missing, with Try again, and an import waits for a download (#1053) - #2003

Merged
mbeisser1 merged 19 commits into
mainfrom
feat/1053-import-form-download
Oct 8, 2026
Merged

mbeisser1 merged 19 commits into
mainfrom
feat/1053-import-form-download

Conversation

@mbeisser1

@mbeisser1 mbeisser1 commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Feature Description

Step 4 of #1053: the Import form says which program the chosen import needs and can't use, with Try again, and an import started while its program is still downloading waits for it.

  • WhatsApp: while wtsexporter is missing, failed to download or can't be used, the form names it, gives the reason (the download's failure, or that it hasn't been downloaded) and blocks Import. Try again runs the check and download now. A link goes to Troubleshooting (#import-cant-find-wtsexporter).
  • A program the app has no download of for this computer shows as unavailable: "not found, and the app has no download of it for this computer". The form offers no Try again for it, because only a copy put in the Tools Directory by hand is used.
  • Convert / Compress: the same message for ffmpeg and ffprobe, with Try again and a link to #ffmpeg-or-ffprobe-not-found. Nothing is blocked: the import still runs. The Staging Review keeps its existing message. It now has Try again beside it, and it reads the live status, so the review unblocks once the programs arrive.
  • A program still downloading doesn't block anything. The form shows its progress and says the import waits. In the run, the WhatsApp run's setup step or the Media row (Convert/Compress) reads "Waiting for the wtsexporter download: 12 MB of 30 MB (40%)", or "12 MB so far" when the server sent no size. The form and Settings write a download's progress the same way. When the download ends found the run goes on. When it fails, the run fails with the reason and names the troubleshooting section.
  • The notice during a download is deliberate. The form shows a program's download progress before a run starts, though the issue says nothing is shown until it matters, because the run about to start will wait for that download.

User Story

As a person importing a WhatsApp backup on a computer where the wtsexporter download failed, I want the Import form to say so and let me download it again from there, so that I don't have to restart the app or read Settings to find out why the import can't start.

Implementation Details

  • The wait is in the Rust run path, not polled from the web. ToolDownloads now holds a Condvar beside its Mutex. Every set and clear notifies it. ToolDownloads::wait_for(programs, cancelled, waiting) blocks while any of programs is Downloading. It wakes on each change, or every 250 ms to read the cancel flag, and it reports progress through waiting. It returns Ok when no program it waited for is downloading, WaitError::Failed { reason } when a download it waited for ended failed, and Cancelled on Cancel. A program that wasn't downloading when the run started isn't waited for. The lookup then reports it as before. commands::tools::wait_for_downloads sends the wait as extract:progress events with no counts, the program in a new waiting field, and the bytes in bytes_done and bytes_total: one when the wait starts, and one each time the program changes or its download moves by 256 KiB. The window writes the line from them (waitingLine in web/src/lib/toolStatusCopy.ts). extract waits for wtsexporter on a WhatsApp source (step setup), and transcode_staging waits for ffmpeg and ffprobe when Media converts (step media).
  • A check that ends mid-download fails the download as interrupted. Each check is a CheckRun owned by its thread. When the run is dropped, at the end of the check or on a panic, every program it marked that is still downloading becomes failed, so an import waiting for it wakes.
  • The retry command retry_tool_downloads returns at once, with what it did: started, alreadyRunning (another check holds the lock: the start-up check, an earlier Try again, or another app's check), noToolsDirectory, or couldNotStart (the check's thread could not be started, and each program it would have downloaded fails with that reason). tool_downloads::retry takes the .check.lock on the calling thread. Otherwise it marks the programs the window reports as not found as Downloading, then runs the same check as at start on its own thread, with the lock moved in. The check's run goes to that thread only once it has started, so a failed start never shows the programs as interrupted. download_missing and retry share one check. That check now clears the state of a program it finds in place (kept, adopted, or ffmpeg on PATH), so a stale failure or a mark from a retry never sticks.
  • The status carries checking: whether a check runs in this process now. A program the check has not looked at yet shows as missing, so the window keeps asking while checking is true. Another app's check is not counted.
  • Web: useToolsStatus / useRetryToolDownloads (web/src/lib/useToolsStatus.ts) are the one query on keys.desktopToolsStatus.all, polled each second while checking or downloading. After Try again answers alreadyRunning the form says a check is already running, whatever checking says, and the status is also asked for every 2 s for two minutes, because another app's check is not counted in checking. Settings now uses the same hook and is otherwise unchanged. ffmpegMissing no longer counts a program that is downloading, because Media waits for it.

Key Files Changed

  • src-tauri/src/tool_downloads.rs - Condvar, wait_for, WaitError, CheckRun, retry and its Retry result, shared check
  • src-tauri/src/commands/tools.rs - retry_tool_downloads and RetryResult, wait_for_downloads, the checking and unavailable status
  • src-tauri/src/commands/events.rs, web/src/lib/types.ts - the waiting field on extract:progress
  • web/src/lib/toolStatusCopy.ts - one status line per program state, the progress format, waitingLine, the troubleshooting sentence
  • src-tauri/src/commands/extract.rs, staging.rs - the waits
  • web/src/screens/import/MissingProgramNotice.tsx - the message and Try again
  • web/src/screens/import/ImportFormFields.tsx - the WhatsApp block and the notices
  • web/src/screens/import/ImportRunView.tsx - Try again at the Staging Review, live status
  • web/src/screens/settings/SystemSection.tsx - the same status lines as the form
  • docs/src/content/docs/docs/user/features/owner/troubleshooting.md, features/settings/system.md, import-sources/whatsapp.md, features/messages/attachments-and-media.md - Try again

HTTP API Changes

  • None. One new Tauri command: retry_tool_downloads.

Database Changes

  • No schema changes
  • schema/sql/*.sql changed (every database rebuilds empty and re-imports; the fingerprint is computed, nothing to bump)

How to Test

  1. cargo test --manifest-path src-tauri/Cargo.toml tool_downloads. The new tests cover: a wait that goes on when the state is cleared; a wait that fails with the reason; no wait when nothing downloads; Cancel ending the wait; Try again running the check once and returning before a 500 ms download; Try again while the lock is held starting nothing; a Try again whose thread can't start failing with that reason and never as interrupted; a check that panics mid-download failing the download; and a program found in place clearing an earlier failure.
  2. cd web && npx vitest run src/screens/import. Form tests cover missing, downloadFailed, unusable and downloading for wtsexporter, the ffmpeg notice that doesn't block, and Try again calling the command and polling until the program is found, including when another app's check holds the Tools Directory and checking is false. A useImportJob test covers the Media row's waiting line.
  3. In the desktop app: rename ~/message-crate/tools/wtsexporter, start the app offline, choose WhatsApp, and see the message and the disabled Import. Go online, select Try again, and see the progress, then Import enabled.

Checklist

  • Code follows the project's style guidelines
  • Self-reviewed the code
  • Added unit tests for new functionality
  • Added integration tests where applicable
  • Existing tests pass locally
  • Updated documentation
  • ./scripts/check-pr.sh passes and formatter rewrites are committed

Related Issues

Part of #1053 (step 4). Step 5 (the user guide) stays open. Four guide pages changed here, because the behaviour they describe changed: troubleshooting.md, system.md, whatsapp.md and attachments-and-media.md each name Try again.

Screenshots / Demo

No browser check was run. The Vitest tests render the form with isTauri and the Tauri commands mocked.

Deployment Notes

None.

🤖 Generated with Claude Code

https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1

…y again, and an import waits for a download (#1053)

A WhatsApp import needs wtsexporter, and Convert and Compress need ffmpeg
and ffprobe. When one is missing, can't be used or failed to download, the
Import form names it, gives the reason and offers Try again, which runs the
check and download now through the new retry_tool_downloads command. A
WhatsApp import can't start without wtsexporter; Convert and Compress still
start, and the Staging Review keeps its message, now with Try again beside
it and read from the live status.

An import started while its program is still downloading waits in the Rust
run path on the ToolDownloads state, which now carries a condition variable,
and its progress line reads "Waiting for the wtsexporter download (12.0 MB
of 30.0 MB)". A failed download fails the run with its reason.

Part of #1053 (step 4).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1

@mbeisser1 mbeisser1 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of f03f803 (up to date with main). Findings whose line is outside the diff follow; the rest are line comments.

  • docs/src/content/docs/docs/user/features/owner/troubleshooting.md — Standards · rule — Docs follow behaviour: the notice links to troubleshooting.md:97 and :107, but neither section mentions Try again. settings/system.md:75 still says "A failed download is tried again the next time the app starts", and SystemSection.tsx:58 DOWNLOAD_RETRIED says the same; both are now incomplete (Try again on the Import form runs it now).
  • src-tauri/src/tool_downloads.rs — Standards · judgement — waiting_line: two byte formats in one flow. The form says "12 MB of 30 MB (40%)" and the progress line "12.0 MB of 30.0 MB", because one comes from media::format_bytes and the other from the web formatBytes. Pick one format for both.
  • docs/src/content/docs/docs/user/import-sources/whatsapp.md — Standards · judgement — whatsapp.md:11 says nothing new: neither that the import can't start without wtsexporter nor that it waits for a download in progress, both of which the CHANGELOG states. One sentence each.

Comment thread src-tauri/src/tool_downloads.rs Outdated
Comment thread src-tauri/src/commands/tools.rs Outdated
Comment thread web/src/screens/import/MissingProgramNotice.tsx Outdated
Comment thread web/src/screens/import/MissingProgramNotice.tsx Outdated
Comment thread web/src/screens/import/useImportJob.ts Outdated
Comment thread src-tauri/src/tool_downloads.rs
Comment thread src-tauri/src/tool_downloads.rs
Comment thread web/src/screens/import/MissingProgramNotice.tsx Outdated
Comment thread web/src/screens/import/MissingProgramNotice.tsx Outdated
Comment thread web/src/lib/useToolsStatus.ts Outdated
mbeisser1 and others added 18 commits October 8, 2026 13:32
…the window polls while a check runs (#1053)

A check's thread now owns a guard. While it exists, tools_status reports
checking: true, and the web hook keeps polling. A program the check has
not looked at yet shows as missing, and the form no longer stops polling
on that answer. When the guard drops, at the end of the check or on a
panic, every program the check marked as downloading that still is
becomes failed ("The download was interrupted."). A waiting import wakes
and the form offers Try again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
…that arrived (#1053)

A program fails only when the lookup does not find it. When the download
arrived and runs but manifest.json could not be written, the check now
writes a line to the process log and leaves the program found. Before,
the check set it to failed. A waiting WhatsApp import or Media stage could
then fail, depending on timing, for a program that works, while the form
offered no Try again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
…rograms it marked (#1053)

When the check's thread fails to start, the programs Try again marked as
downloading now fail with "The download could not be started: ...". These
are the programs in not_found that have a pin on this platform, the same
filter as the marking loop. Before, every program in not_found failed with
"could not be written to the Tools Directory". That reason was false, and
it also covered a program with no download for this computer.

retry_tool_downloads and invokeRetryToolDownloads now name all three
cases that return false.

download_missing is private now and takes the check's run. The tests call
it through check_now. The previous commit left it public with no caller
outside the tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
…ith no Try again (#1053)

Linux on ARM has no wtsexporter file. tools_status now reports a program
that is missing and has no pin for this computer as "unavailable". The
Import form says the app has no download of it for this computer and
links to the troubleshooting section without offering Try again, because
Try again can't bring it. Settings says to put a copy in the Tools
Directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
retry_tool_downloads returns false when another check holds the lock,
and the notice ignored the result: Try again did nothing and said
nothing. When the result is false and the status reports checking, the
notice now says a check of the Tools Directory is already running. The
status keeps polling while checking is true, so the form takes the
result when that check ends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
…ies bytes, and the window formats them (#1053)

The waiting progress event now names the program in an explicit waiting
field and carries the bytes in bytes_done and bytes_total. Before, the
web took any event with 0/0 counts and a status as a wait. The web
writes the line with the same downloadProgress the Import form and
Settings use: "Waiting for the wtsexporter download: 12 MB of 30 MB (40%)".
Before, the progress line used media::format_bytes ("12.0 MB", base
1000) and the form used formatBytes ("12 MB", base 1024). The desktop
process sends a wait event when the wait starts, when the program or the
size changes, and when the download moves by 256 KiB.

The new tests:
- programs_run_by for every source;
- the JSON shape of waiting_event;
- the Setup-row line and the Media-row line, built from that JSON.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
…shooting section (#1053)

The run's error for a failed download now ends with the sentence the
app already used for a wtsexporter that doesn't run: See "Import can't
find wtsexporter" (or "ffmpeg or ffprobe not found") in Troubleshooting
at messagecrate.app. Settings' failed-download line ends with the same
sentence, and the section's name links to it. The sentence for a
wtsexporter that doesn't run now comes from those two places only, so
nothing names the section twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
… Import form, and the notice names the programs (#1053)

toolStatusLine in lib/toolStatusCopy.ts is now the only per-state switch.
Settings and the Import form's notice both use it. Settings adds its own
fixes after the line: where a missing program goes, and, for a failed
download, that it is tried again and the troubleshooting section. One
ToolName type in lib/tauri.ts replaces Settings' ToolName and the
notice's ProgramName. downloadProgress moved beside its two callers and
is no longer exported. useToolsStatus.ts now holds only the two hooks.

The notice names the programs: "A WhatsApp import can't start without
wtsexporter.", "The import waits for wtsexporter.", "Media waits for
ffmpeg." Before, "it" had no clear referent when both ffmpeg and ffprobe
downloaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
…reached mid-download stays open (#1053)

Two tests at the review read the live tools status:
- Try again there downloads the missing ffprobe and enables Convert
  media once ffprobe arrives;
- with ffmpeg downloading, Convert media is enabled and no Try again
  shows, although the run found ffmpeg missing when it reached the review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
…Settings and the WhatsApp page (#1053)

- Troubleshooting, "Import can't find wtsexporter" and "ffmpeg or ffprobe
  not found": Try again on the Import form downloads the program at once.
  A computer the app has no wtsexporter download for takes a copy put in
  the Tools Directory by hand.
- Settings -> System, on the page and in DOWNLOAD_RETRIED: a failed
  download is tried again at the next start or with Try again on the
  Import form. The page also gains the unavailable line, the interrupted
  and could-not-start reasons, and the troubleshooting link.
- Attachments and media: the review paragraph says one idea per sentence,
  and its last sentence no longer says "again".
- WhatsApp: the import can't start without wtsexporter, and it waits for
  a download in progress.
- CHANGELOG: the progress line in the new byte format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
… Directory, and asks until it ends (#1053)

`retry_tool_downloads` returns what it did: started, alreadyRunning,
noToolsDirectory or couldNotStart. `checking` counts only this process's
checks, so the Import form showed nothing and stopped asking when another
app held the lock. The form now shows the "already running" line for
alreadyRunning whatever `checking` says, and asks for the status every 2 s
for two minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
…ograms as interrupted (#1053)

The check's run went into the thread's closure, so a failed start dropped
it there: its drop marked each program interrupted and woke a waiting
import before `retry` set the real reason. The run now goes to the thread
over a channel once the thread has started. On a failed start each program
fails with CouldNotStart first, and the run's drop leaves them. `retry_with`
takes the spawner, so a test makes the start fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
… drop said plainly (#1053)

`check` takes `&run.downloads.clone()` in one line, with the reason for
the clone: `run` is borrowed mutably later to mark programs downloading.
The `CheckRun` doc says every program it marked that is still downloading
becomes failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
…bleshootingSentence (#1053)

`troubleshootingSentence` returns the sentence split around the section's
heading, so Settings sets the heading as a link and builds the label from
the same parts, instead of writing the sentence a second time by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
… on its type (#1053)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
After Try again answered "already running", the line stayed for good: not
`checking` going false, nor the two-minute deadline, took it away. Now it
goes once this process's check has been seen and ends, or when the two
minutes for another app's check are up, with a timer to re-render then.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gbWRfTT5AEWW1D6hTNDr1
@mbeisser1
mbeisser1 marked this pull request as ready for review October 8, 2026 18:19

@mbeisser1 mbeisser1 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of the fix commits (Standards and Correctness, step 4 of the review), pinned to 5be1a49. Every finding below is fixed in this push.

Correctness · bug web/src/screens/import/MissingProgramNotice.tsx:51: when another process held the Tools Directory lock, Try again returned false, checking stayed false, and nothing showed or polled. Fixed in 0c8d078: retry_tool_downloads returns started | alreadyRunning | noToolsDirectory | couldNotStart; on alreadyRunning the notice says a check is already running whatever checking says and the status is asked every 2 s for two minutes. Both doc comments say checking counts this process's checks only.
Correctness · bug (low) src-tauri/src/tool_downloads.rs:855: on a spawn failure the dropped guard marked programs interrupted and woke waiters before the real cause was set. Fixed in df63696: the run reaches its thread over a channel only after the thread starts; on a start failure each program is set to could-not-start before the run drops, and the drop leaves failed programs alone; a test with a failing spawner.
Correctness · bug MissingProgramNotice.tsx:56: the "already running" line never went away. Fixed in f86693e: it goes once this process's check has been seen and ends, or when the two minutes for another app's check are up; test.
Standards · judgement commands/tools.rs:137, tauri.ts:531: the doc claimed checking follows another app's check. Fixed in 0c8d078.
Standards · judgement tauri.ts:509: ToolName sat between a doc comment and its type. Fixed in f657f17.
Standards · judgement SystemSection.tsx:135: the troubleshooting sentence written by hand beside troubleshootingSentence. Fixed in 2d46755: the function returns the parts and Settings builds the label and the link from them.
Standards · judgement tool_downloads.rs:876: the downloads shadow. Fixed in 8a9ca6b, with the reason for the clone.
Standards · judgement tool_downloads.rs:856: the hard-to-follow comment. Fixed in df63696.
Standards · judgement tool_downloads.rs:587: the garden-path sentence. Fixed in 8a9ca6b.
Standards · judgement PR body stale. Fixed: rewritten for the final progress-line format, the waiting event field, the checking and unavailable states, the retry result, and the docs pages changed.

Also in this push: 5be1a49 merges origin/main (#2004, the docs lockfile) with no conflict.

@mbeisser1

Copy link
Copy Markdown
Member Author

Review summary for 5be1a49.

@mbeisser1
mbeisser1 merged commit dcfcfc0 into main Oct 8, 2026
28 checks passed
@mbeisser1
mbeisser1 deleted the feat/1053-import-form-download branch October 8, 2026 18:34
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.

1 participant