Skip to content

feat: add file watchers for whisper models and settings#27

Merged
iguit0 merged 2 commits intomainfrom
feat/whisper-file-watchers
Feb 26, 2026
Merged

feat: add file watchers for whisper models and settings#27
iguit0 merged 2 commits intomainfrom
feat/whisper-file-watchers

Conversation

@iguit0
Copy link
Contributor

@iguit0 iguit0 commented Feb 26, 2026

Summary

  • Add notify-based file watchers for the Whisper models directory (~/.arandu/models/) and settings file (whisper-settings.json), emitting Tauri events so the frontend can reactively refresh without manual intervention
  • New whisper/watcher.rs module follows the existing WatcherState / create_file_watcher pattern from lib.rs
  • Frontend listeners auto-refresh the model list and settings fields when the whisper settings modal is open

Motivation

During PR #2 review, CodeRabbit suggested adding file watching for the Whisper models directory and settings file. This enables the app to stay in sync when:

  • A model is downloaded/deleted via CLI or external script
  • Settings are changed by another app instance
  • Models are managed by external tools

What changed

File Change
src-tauri/src/whisper/watcher.rs NewWhisperWatcherState, watcher creation helpers, init() function
src-tauri/src/whisper/mod.rs Add pub mod watcher export
src-tauri/src/lib.rs Register WhisperWatcherState in .manage(), call watcher::init() in setup()
src/main.js Add whisper:models-changed and whisper:settings-changed event listeners

Design decisions

  • No new dependencies — uses existing notify v7 crate; debounce uses std::time::Instant (no tokio::time or notify-debouncer-mini needed)
  • 500ms debounce — collapses rapid filesystem events (chunked writes, editor save patterns) while feeling instant to users
  • Smart event filtering — models watcher only fires on Create/Remove/Modify(Name) for .bin files, ignoring .tmp partial downloads; settings watcher fires on Create/Modify verified against the exact file path
  • Cross-platform settings watching — watches the parent directory instead of the file directly, since macOS FSEvents doesn't reliably watch individual files that may not exist yet
  • Graceful degradation — watcher init failures are logged but don't crash the app (matches IPC socket setup pattern)
  • Lazy UI refresh — frontend only refreshes when the whisper settings modal is actually visible; otherwise the next openWhisperSettings() call loads fresh data naturally
  • Self-triggered events accepted — when the app itself writes settings, the watcher fires back to the frontend (harmless re-read of the same values, avoids suppression complexity)

Test plan

  • Download a model via the app UI → verify model list updates
  • Manually add a .bin file to ~/.arandu/models/ → verify UI refreshes if modal is open
  • Delete a model file externally → verify UI updates the model list
  • Edit whisper-settings.json externally → verify shortcut/threshold fields update if modal is open
  • Verify no events fire during model download (.tmp writes should be filtered)
  • Verify app starts normally if models directory doesn't exist yet
  • Test on macOS and Linux (different notify backends: FSEvents vs inotify)

Closes #4

Summary by CodeRabbit

  • New Features

    • Automatic file monitoring for Whisper models and settings starts at app launch.
    • Whisper settings modal auto-refreshes when model files or configuration change externally, reflecting updates in real time while open.
  • Bug Fixes

    • Removed duplicate event registrations that could trigger redundant refreshes.

Watch the models directory and settings file for external changes,
emitting events to the frontend so the UI stays reactive without
manual refreshes. Includes 500ms debounce, .bin file filtering,
and cross-platform parent-directory watching for settings.

Closes CodeRabbit suggestion from PR #2.
@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 912105c and 7cebd6a.

📒 Files selected for processing (2)
  • apps/tauri/src-tauri/src/whisper/watcher.rs
  • apps/tauri/src/main.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/tauri/src/main.js

📝 Walkthrough

Walkthrough

Adds a Whisper file-watching subsystem: registers a new WhisperWatcherState with two mutex-protected watchers, initializes filesystem watchers for models and settings at startup, emits "whisper:models-changed" and "whisper:settings-changed" events, and adds frontend listeners that refresh UI when the whisper settings modal is open.

Changes

Cohort / File(s) Summary
Watcher State & Init
apps/tauri/src-tauri/src/lib.rs, apps/tauri/src-tauri/src/whisper/mod.rs, apps/tauri/src-tauri/src/whisper/watcher.rs
Adds WhisperWatcherState with models_watcher and settings_watcher (Mutex<Optionnotify::RecommendedWatcher>). Implements init(app) creating non-recursive models and settings watchers, debounce (500ms), predicates for relevant events, and emits whisper:models-changed / whisper:settings-changed. Registers state in Tauri builder.
Frontend listeners
apps/tauri/src/main.js
Registers whisper:models-changed and whisper:settings-changed handlers to reload model list and refresh settings UI only when the whisper settings modal is visible. Note: identical listeners are duplicated in the file (two registrations each).

Sequence Diagram(s)

sequenceDiagram
    participant FS as Filesystem
    participant Watcher as Whisper Watchers
    participant App as Tauri App
    participant UI as Renderer

    FS->>Watcher: filesystem event (.bin create/remove/rename or settings modify)
    activate Watcher
    Watcher->>Watcher: debounce (500ms) & filter relevant event
    Watcher-->>App: emit "whisper:models-changed" / "whisper:settings-changed"
    deactivate Watcher

    rect rgba(100, 200, 100, 0.5)
        App->>UI: deliver event to renderer
        UI->>UI: check if whisper settings modal is visible
        alt modal visible
            UI->>App: call loadModelList() or get_whisper_settings
            App-->>UI: respond with updated models/settings
            UI->>UI: update model list / inputs
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibble at file change light,
two watchers ready in the night.
Models shift and settings sing—
a gentle ping, the UI springs.
Debounced hops, the list takes flight. 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding file watchers for Whisper models and settings, which matches the core functionality introduced across all modified files.
Linked Issues check ✅ Passed The PR implementation addresses all coding requirements from issue #4: watch models directory, watch settings file, store watchers in app state, emit Tauri events (whisper:models-changed, whisper:settings-changed), debounce rapid events, filter irrelevant events (.tmp files), and integrate frontend listeners.
Out of Scope Changes check ✅ Passed All changes directly support the file-watching objectives: watcher.rs implements the core logic, mod.rs exposes it, lib.rs integrates into app state, and main.js adds frontend listeners. No unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/whisper-file-watchers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/tauri/src/main.js`:
- Around line 1105-1118: The settings-changed handler
(listen("whisper:settings-changed") / invoke("get_whisper_settings")) currently
only updates shortcut/threshold inputs; extend it to also refresh the modal's
model selection UI so external changes to active_model are reflected without
reopening. After fetching settings, locate the model buttons inside the modal
(e.g., elements with a class or data attribute used for model selection, such as
".model-btn" or data-model), remove any existing "active" state from them, and
add the "active" class/state to the button whose model id matches
settings.active_model (also handle a null/undefined active_model by clearing
active state). Ensure the logic runs only when modal && modal.style.display ===
"flex" to avoid unnecessary DOM ops.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4b67dd and 912105c.

📒 Files selected for processing (4)
  • apps/tauri/src-tauri/src/lib.rs
  • apps/tauri/src-tauri/src/whisper/mod.rs
  • apps/tauri/src-tauri/src/whisper/watcher.rs
  • apps/tauri/src/main.js

Comment on lines 1105 to 1118
listen("whisper:settings-changed", async () => {
const modal = document.getElementById("whisper-settings-modal");
if (modal && modal.style.display === "flex") {
try {
const settings = await invoke("get_whisper_settings");
const shortcutInput = document.getElementById("shortcut-input");
const thresholdInput = document.getElementById("threshold-input");
if (shortcutInput) shortcutInput.value = settings.shortcut || "Alt+Space";
if (thresholdInput) thresholdInput.value = settings.long_recording_threshold || 60;
} catch (e) {
console.warn("Failed to refresh whisper settings:", e);
}
}
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Refresh model activation UI on settings-file changes.

Line 1109 reloads settings, but this handler only updates shortcut/threshold inputs. If active_model changes externally, the modal’s model buttons can stay stale until reopening.

💡 Proposed fix
 listen("whisper:settings-changed", async () => {
   const modal = document.getElementById("whisper-settings-modal");
   if (modal && modal.style.display === "flex") {
     try {
       const settings = await invoke("get_whisper_settings");
       const shortcutInput = document.getElementById("shortcut-input");
       const thresholdInput = document.getElementById("threshold-input");
       if (shortcutInput) shortcutInput.value = settings.shortcut || "Alt+Space";
       if (thresholdInput) thresholdInput.value = settings.long_recording_threshold || 60;
+      await loadModelList();
     } catch (e) {
       console.warn("Failed to refresh whisper settings:", e);
     }
   }
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/tauri/src/main.js` around lines 1105 - 1118, The settings-changed
handler (listen("whisper:settings-changed") / invoke("get_whisper_settings"))
currently only updates shortcut/threshold inputs; extend it to also refresh the
modal's model selection UI so external changes to active_model are reflected
without reopening. After fetching settings, locate the model buttons inside the
modal (e.g., elements with a class or data attribute used for model selection,
such as ".model-btn" or data-model), remove any existing "active" state from
them, and add the "active" class/state to the button whose model id matches
settings.active_model (also handle a null/undefined active_model by clearing
active state). Ensure the logic runs only when modal && modal.style.display ===
"flex" to avoid unnecessary DOM ops.

- Remove unused `_models_dir` parameter from `create_models_watcher`
- Canonicalize settings file path for reliable macOS FSEvents matching
- Make models/settings watcher setup independent (one failing doesn't
  block the other)
- Use graceful mutex locking in `should_emit` (return false on poison
  instead of panicking the watcher thread)
- Extend settings-changed handler to also refresh model list so
  external active_model changes are reflected without reopening modal
- Add doc comments for debounce rationale and self-trigger behavior
@iguit0 iguit0 requested a review from wilcorrea February 26, 2026 00:46
@iguit0 iguit0 merged commit 6802367 into main Feb 26, 2026
1 check passed
@iguit0 iguit0 deleted the feat/whisper-file-watchers branch February 26, 2026 04:38
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.

feat: add file watching for Whisper models and settings

2 participants