Skip to content

Conversation

@plyght
Copy link
Contributor

@plyght plyght commented Jul 3, 2025

No description provided.

@plyght plyght requested a review from yujonglee July 3, 2025 21:40
@coderabbitai
Copy link

coderabbitai bot commented Jul 3, 2025

📝 Walkthrough

Walkthrough

This change introduces a comprehensive microphone device selection feature across the desktop application and backend. It adds UI components for selecting and switching microphone devices, extends backend APIs and configuration to persist and update the selected device, and implements robust device enumeration and switching logic. Localization and permissions are updated to support the new functionality.

Changes

File(s) / Area Change Summary
apps/desktop/src/components/editor-area/note-header/listen-button.tsx Added a new MicControlWithDropdown component for microphone control with device selection UI and integrated it into the main audio control button logic.
apps/desktop/src/components/settings/views/general.tsx, plugins/db/js/bindings.gen.ts Updated general config handling to include selected_microphone_device in both frontend and type definitions.
apps/desktop/src/components/settings/views/sound.tsx Added microphone device selection dropdown, device queries, and mutation logic to sound settings.
apps/desktop/src/locales/en/messages.po, apps/desktop/src/locales/ko/messages.po, apps/docs/data/i18n.json Added and updated translation strings and references for microphone device selection and related UI.
apps/desktop/src/utils/microphone-devices.ts Added interfaces and utility functions to parse and validate microphone device data.
crates/audio/src/mic.rs Replaced external mic input with custom implementation: device selection, validation, async streaming, and resampling.
crates/audio/src/errors.rs, crates/audio/src/lib.rs Added new AudioError enum, updated error handling, and added device-specific audio input creation.
crates/db-user/src/config_types.rs Added optional selected_microphone_device field to config struct.
plugins/db/src/ext.rs Added async method to set config, supporting microphone device persistence.
plugins/listener/build.rs, plugins/listener/js/bindings.gen.ts, plugins/listener/src/commands.rs, plugins/listener/src/ext.rs, plugins/listener/src/lib.rs Replaced device listing command with get_selected_microphone_device and set_selected_microphone_device commands; implemented logic for device selection, validation, and dynamic switching.
plugins/listener/permissions/autogenerated/commands/…, plugins/listener/permissions/default.toml, plugins/listener/permissions/schemas/schema.json, plugins/listener/permissions/autogenerated/reference.md Added new permission definitions and documentation for getting/setting selected microphone device.
plugins/listener/src/fsm.rs Added support for dynamic microphone device switching during active sessions, including new state event and resource management methods.
plugins/listener/src/error.rs Added new error variant for missing user and wrapped audio errors.
crates/audio/Cargo.toml, plugins/listener/Cargo.toml Updated dependencies to support new audio and device handling logic.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI (Desktop)
    participant Backend (Listener Plugin)
    participant Audio Layer

    User->>UI (Desktop): Open mic device dropdown/select device
    UI (Desktop)->>Backend (Listener Plugin): setSelectedMicrophoneDevice(deviceName)
    Backend (Listener Plugin)->>Audio Layer: Validate device
    Audio Layer-->>Backend (Listener Plugin): Validation result
    Backend (Listener Plugin)->>Backend (Listener Plugin): Update config and/or switch device (if session active)
    Backend (Listener Plugin)-->>UI (Desktop): Success/Error response
    UI (Desktop)-->>User: Show toast/feedback, update UI
Loading
sequenceDiagram
    participant UI (Desktop)
    participant Backend (Listener Plugin)

    UI (Desktop)->>Backend (Listener Plugin): getSelectedMicrophoneDevice()
    Backend (Listener Plugin)->>Backend (Listener Plugin): Retrieve config, enumerate devices
    Backend (Listener Plugin)-->>UI (Desktop): Return selected device and device list
    UI (Desktop)-->>User: Show current selection in UI
Loading

Possibly related issues

  • Microphone input selector #965: Implements a microphone input selector UI and backend, matching the new device selection and switching functionality introduced.

Possibly related PRs

  • Overlay floating control [Improvements to #892] #907: Both PRs modify listen-button.tsx for microphone controls, with this PR adding device selection and the referenced PR handling mute state synchronization; they are related by overlapping component changes but implement distinct features.

Suggested reviewers

  • yujonglee

📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2579869 and 472904c.

📒 Files selected for processing (4)
  • apps/desktop/src/components/editor-area/note-header/listen-button.tsx (3 hunks)
  • apps/desktop/src/locales/en/messages.po (21 hunks)
  • apps/desktop/src/locales/ko/messages.po (21 hunks)
  • apps/docs/data/i18n.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/docs/data/i18n.json
  • apps/desktop/src/components/editor-area/note-header/listen-button.tsx
  • apps/desktop/src/locales/ko/messages.po
  • apps/desktop/src/locales/en/messages.po
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci (windows, windows-latest)
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 10

🔭 Outside diff range comments (3)
crates/audio/src/errors.rs (1)

25-27: Remove the unused empty Error enum.

The empty Error enum serves no purpose and could cause confusion with the new AudioError enum. Consider removing it to keep the error module clean.

-#[derive(thiserror::Error, Debug)]
-pub enum Error {}
-
crates/audio/src/lib.rs (1)

101-101: Handle speaker creation errors in the constructor.

The from_speaker constructor uses unwrap() on SpeakerInput::new(), which could panic if speaker initialization fails. Consider returning a Result or at least documenting the panic condition.

-    pub fn from_speaker(sample_rate_override: Option<u32>) -> Self {
+    pub fn from_speaker(sample_rate_override: Option<u32>) -> Result<Self, AudioError> {
         Self {
             source: AudioSource::RealtimeSpeaker,
             mic: None,
-            speaker: Some(SpeakerInput::new(sample_rate_override).unwrap()),
+            speaker: Some(SpeakerInput::new(sample_rate_override)?),
             data: None,
-        }
+        }.into()
     }
plugins/listener/src/ext.rs (1)

197-201: Use the selected microphone device for permission checks.

The permission check and request methods use the default device instead of the user's selected device. This could cause incorrect permission status if the selected device requires different permissions.

For check_microphone_access:

         #[cfg(not(target_os = "macos"))]
         {
-            // Use default device for permission check
-            let mut mic_sample_stream = hypr_audio::AudioInput::from_mic().stream();
+            // Use selected device for permission check
+            let selected_device = self.get_selected_microphone_device().await.ok().flatten();
+            let mut mic_sample_stream = hypr_audio::AudioInput::from_mic_device(selected_device).stream();
             let sample = mic_sample_stream.next().await;
             Ok(sample.is_some())
         }

For request_microphone_access:

         #[cfg(not(target_os = "macos"))]
         {
-            // Use default device for permission request
-            let mut mic_sample_stream = hypr_audio::AudioInput::from_mic().stream();
+            // Use selected device for permission request
+            let selected_device = self.get_selected_microphone_device().await.ok().flatten();
+            let mut mic_sample_stream = hypr_audio::AudioInput::from_mic_device(selected_device).stream();
             mic_sample_stream.next().await;
         }

Also applies to: 228-230

🧹 Nitpick comments (8)
plugins/listener/Cargo.toml (1)

19-19: Remove duplicated uuid entry to avoid unnecessary rebuilds

uuid is declared in both [dev-dependencies] (line 19) and [dependencies] (line 55).
Because Cargo unifies feature sets across sections, the dev-dependency entry is redundant and slightly slows incremental builds.

-[dev-dependencies]
-
-uuid = { workspace = true }

Keep only the [dependencies] declaration with features = ["v4"].

Also applies to: 55-55

apps/docs/data/i18n.json (1)

4-6: Consider adding a CI check for translation count drift

The manual total / missing counters are easy to desynchronise from the real key set.
A tiny script in CI that recomputes these numbers from the JSON files would eliminate future inconsistencies.

Also applies to: 9-10

plugins/listener/src/fsm.rs (1)

600-645: Consider refactoring to reduce code duplication.

The device change handler duplicates cleanup logic from cleanup_resources() method (lines 618-631 vs 419-436). Consider calling the existing method to maintain DRY principle.

Apply this refactor:

                    // Quick cleanup - abort all tasks immediately
-                   if let Some(tx) = self.silence_stream_tx.take() {
-                       let _ = tx.send(());
-                   }
-                   if let Some(mut tasks) = self.tasks.take() {
-                       tasks.abort_all();
-                       // Don't wait for tasks to finish - abort and move on
-                   }
-
-                   // Reset channels but keep session_id
-                   self.mic_muted_tx = None;
-                   self.mic_muted_rx = None;
-                   self.speaker_muted_tx = None;
-                   self.speaker_muted_rx = None;
-                   self.session_state_tx = None;
+                   self.cleanup_resources().await;
apps/desktop/src/components/editor-area/note-header/listen-button.tsx (2)

340-364: Add type safety for parsed data.

The function parses JSON but doesn't validate the structure. Consider adding type guards to ensure data integrity.

+interface DeviceData {
+  devices: string[];
+  selected?: string[];
+}
+
+function isDeviceData(data: unknown): data is DeviceData {
+  return (
+    typeof data === 'object' &&
+    data !== null &&
+    'devices' in data &&
+    Array.isArray((data as DeviceData).devices) &&
+    (data as DeviceData).devices.every(d => typeof d === 'string')
+  );
+}
+
-function parseDeviceData(result: string | null | undefined): { devices: string[]; selected?: string[] } | null {
+function parseDeviceData(result: string | null | undefined): DeviceData | null {
  if (!result || !result.startsWith("DEVICES:")) {
    return null;
  }

  const devicesJson = result.substring(8);
  try {
    const parsedData = JSON.parse(devicesJson);

    // Check if it's the new format with devices and selected
-   if (parsedData && typeof parsedData === "object" && parsedData.devices) {
+   if (isDeviceData(parsedData)) {
      return parsedData;
    }

    // Fallback to old format (array of devices)
    if (Array.isArray(parsedData)) {
      return { devices: parsedData };
    }

    return null;
  } catch (e) {
    console.error("Failed to parse device data:", e);
    return null;
  }
}

427-431: Optimize refetch calls.

Both success and error handlers call the same three refetch methods. Consider using React Query's invalidation pattern for better performance.

Instead of manual refetches, use query invalidation:

+import { useQueryClient } from "@tanstack/react-query";
+
function MicControlWithDropdown({
  isMuted,
  onMuteClick,
  disabled,
}: {
  isMuted?: boolean;
  onMuteClick: () => void;
  disabled?: boolean;
}) {
  const { t } = useLingui();
+ const queryClient = useQueryClient();

  // ... existing code ...

  const updateSelectedDevice = useMutation({
    mutationFn: (deviceName: string | null) => listenerCommands.setSelectedMicrophoneDevice(deviceName),
    onSuccess: (_, deviceName) => {
      const displayName = deviceName === null ? t`System Default` : deviceName;

      sonnerToast.success(t`Microphone switched to ${displayName}`, {
        duration: 2000,
      });

-     // Force immediate refetch to ensure UI updates instantly
-     deviceQuery.refetch();
-     microphoneDevices.refetch();
-     selectedDevice.refetch();
+     // Invalidate all microphone-related queries
+     queryClient.invalidateQueries({ queryKey: ["microphoneDeviceInfo"] });
+     queryClient.invalidateQueries({ queryKey: ["microphoneDevices"] });
+     queryClient.invalidateQueries({ queryKey: ["selectedMicrophoneDevice"] });
    },
    onError: (error, deviceName) => {
      const displayName = deviceName === null ? t`System Default` : deviceName;

      sonnerToast.error(t`Failed to switch to ${displayName}`, {
        description: t`Please try again or check your microphone permissions.`,
        duration: 4000,
      });

-     // Even on error, refresh to show correct state
-     deviceQuery.refetch();
-     microphoneDevices.refetch();
-     selectedDevice.refetch();
+     // Refresh state even on error
+     queryClient.invalidateQueries({ queryKey: ["microphoneDeviceInfo"] });
+     queryClient.invalidateQueries({ queryKey: ["microphoneDevices"] });
+     queryClient.invalidateQueries({ queryKey: ["selectedMicrophoneDevice"] });
    },
  });

Also applies to: 440-443

apps/desktop/src/components/settings/views/sound.tsx (1)

102-102: Use optional chaining as suggested by static analysis.

Static analysis correctly identifies opportunities for optional chaining.

-if (result && result.startsWith("DEVICES:")) {
+if (result?.startsWith("DEVICES:")) {
-if (result && result.startsWith("DEVICES:")) {
+if (result?.startsWith("DEVICES:")) {

Also applies to: 118-118

apps/desktop/src/locales/ko/messages.po (1)

302-304: Korean translations need to be added.

All new microphone device-related message IDs have been added but Korean translations are missing (empty msgstr). This is expected but should be tracked for completion.

Would you like me to create an issue to track the missing Korean translations for the microphone device features?

Also applies to: 599-606, 732-743, 782-797, 1024-1029, 1098-1104

crates/audio/src/mic.rs (1)

408-428: Consider simplifying the thread shutdown logic.

The current implementation spawns a new thread just to join the audio thread with a timeout. This adds unnecessary complexity.

Consider using a simpler approach with a direct timeout check or async runtime facilities if available.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b170c9 and fa32f6f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (26)
  • apps/desktop/src/components/editor-area/note-header/listen-button.tsx (4 hunks)
  • apps/desktop/src/components/settings/views/general.tsx (1 hunks)
  • apps/desktop/src/components/settings/views/sound.tsx (2 hunks)
  • apps/desktop/src/locales/en/messages.po (32 hunks)
  • apps/desktop/src/locales/ko/messages.po (32 hunks)
  • apps/docs/data/i18n.json (1 hunks)
  • crates/audio/Cargo.toml (1 hunks)
  • crates/audio/src/errors.rs (1 hunks)
  • crates/audio/src/lib.rs (3 hunks)
  • crates/audio/src/mic.rs (2 hunks)
  • crates/db-user/src/config_types.rs (2 hunks)
  • plugins/db/js/bindings.gen.ts (1 hunks)
  • plugins/db/src/ext.rs (2 hunks)
  • plugins/listener/Cargo.toml (1 hunks)
  • plugins/listener/build.rs (1 hunks)
  • plugins/listener/js/bindings.gen.ts (1 hunks)
  • plugins/listener/permissions/autogenerated/commands/get_selected_microphone_device.toml (1 hunks)
  • plugins/listener/permissions/autogenerated/commands/set_selected_microphone_device.toml (1 hunks)
  • plugins/listener/permissions/autogenerated/reference.md (3 hunks)
  • plugins/listener/permissions/default.toml (1 hunks)
  • plugins/listener/permissions/schemas/schema.json (3 hunks)
  • plugins/listener/src/commands.rs (2 hunks)
  • plugins/listener/src/error.rs (1 hunks)
  • plugins/listener/src/ext.rs (5 hunks)
  • plugins/listener/src/fsm.rs (5 hunks)
  • plugins/listener/src/lib.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{js,ts,tsx,rs}`: 1. No error handling. 2. No unused imports, variables, or functions. 3. For comments, keep it minimal. It should be about "Why", not "What".

**/*.{js,ts,tsx,rs}: 1. No error handling.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

⚙️ Source: CodeRabbit Configuration File

List of files the instruction was applied to:

  • plugins/listener/build.rs
  • crates/db-user/src/config_types.rs
  • apps/desktop/src/components/settings/views/general.tsx
  • plugins/listener/src/error.rs
  • plugins/listener/src/lib.rs
  • plugins/db/js/bindings.gen.ts
  • crates/audio/src/errors.rs
  • plugins/db/src/ext.rs
  • plugins/listener/js/bindings.gen.ts
  • crates/audio/src/lib.rs
  • plugins/listener/src/commands.rs
  • apps/desktop/src/components/editor-area/note-header/listen-button.tsx
  • plugins/listener/src/ext.rs
  • apps/desktop/src/components/settings/views/sound.tsx
  • crates/audio/src/mic.rs
  • plugins/listener/src/fsm.rs
🧬 Code Graph Analysis (1)
apps/desktop/src/components/settings/views/sound.tsx (1)
packages/ui/src/components/ui/select.tsx (5)
  • Select (174-174)
  • SelectTrigger (174-174)
  • SelectValue (174-174)
  • SelectContent (174-174)
  • SelectItem (174-174)
🪛 Biome (1.9.4)
apps/desktop/src/components/settings/views/sound.tsx

[error] 102-102: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 118-118: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: ci
  • GitHub Check: ci (macos, macos-latest)
  • GitHub Check: ci (windows, windows-latest)
  • GitHub Check: ci (windows, windows-latest)
🔇 Additional comments (24)
crates/audio/Cargo.toml (1)

17-18: Verify feature unification for Tokio and new tokio-stream crate

Adding the sync feature and the extra tokio-stream dependency is fine, but both crates rely on Tokio’s version/feature unification at workspace level.
Please run cargo tree -e features | rg tokio to confirm that:

  1. No other crate pulls incompatible feature sets (e.g. io-util, time) that could silently turn on the heavy full feature group.
  2. tokio-stream resolves to the same Tokio version; a mismatched minor patch will cause duplicated compile artefacts.

If a mismatch appears, bump the workspace‐pinned version or add the missing features here instead of pulling them in transitively.

plugins/listener/build.rs (1)

2-3: Command list update looks good

The new command strings follow the existing naming convention and keep the array in logical group order.
No further action needed.

plugins/listener/src/error.rs (1)

19-20: Variant addition is consistent

NoneUser cleanly extends the enum and is automatically covered by the custom Serialize impl.
No issues spotted.

plugins/listener/permissions/default.toml (1)

4-5: LGTM! Clean permission additions.

The new permissions align with the microphone device management functionality and follow the established naming convention.

plugins/listener/src/lib.rs (1)

31-32: LGTM! Command registration follows established patterns.

The new microphone device management commands are properly registered with the correct type parameters and placement.

plugins/listener/permissions/autogenerated/commands/set_selected_microphone_device.toml (1)

1-14: LGTM! Autogenerated permissions file is properly structured.

The file follows the standard format for Tauri command permissions with both allow and deny entries correctly configured.

crates/db-user/src/config_types.rs (1)

59-59: LGTM! Default value correctly updated.

The default implementation properly initializes the new field to None, maintaining consistency with the field type.

apps/desktop/src/components/settings/views/general.tsx (1)

129-130: LGTM! Safe field access with backwards compatibility.

The optional chaining and null coalescing operators properly handle both the new selected_microphone_device field and the legacy selected_template_id field, ensuring robust migration support.

plugins/listener/permissions/autogenerated/commands/get_selected_microphone_device.toml (1)

1-14: LGTM! Permission configuration is properly structured.

The TOML file correctly defines both allow and deny permissions for the get_selected_microphone_device command with appropriate identifiers and descriptions.

plugins/db/js/bindings.gen.ts (1)

154-154: LGTM! Type definition correctly supports microphone device selection.

The addition of the optional selected_microphone_device field properly supports the new microphone device selection feature with appropriate null handling.

plugins/db/src/ext.rs (2)

20-23: LGTM! Trait method signature is consistent with existing patterns.

The async method signature follows the established pattern in the trait and properly complements the existing db_get_config method.


139-146: LGTM! Implementation follows established patterns and has proper error handling.

The implementation correctly follows the same pattern as other methods in the trait with appropriate state management, locking, database reference validation, and error handling.

plugins/listener/permissions/autogenerated/reference.md (3)

7-8: LGTM! Default permission set correctly updated.

The new microphone device selection permissions are properly added to the default permission set.


115-136: LGTM! Permission table entries are correctly formatted.

The get_selected_microphone_device permission entries follow the established documentation format with proper identifiers and descriptions.


401-422: LGTM! Permission table entries are correctly formatted.

The set_selected_microphone_device permission entries follow the established documentation format with proper identifiers and descriptions.

plugins/listener/js/bindings.gen.ts (1)

10-15: LGTM! Command bindings are properly implemented.

The new microphone device selection methods have correct signatures and properly invoke the corresponding Tauri plugin commands. The return types appropriately handle null values for device selection.

plugins/listener/permissions/schemas/schema.json (1)

333-476: Permission definitions look good!

The new microphone device selection permissions follow the existing pattern and are properly integrated into the default permission set.

plugins/listener/src/ext.rs (1)

44-114: Excellent error handling and device enumeration!

The improved list_microphone_devices implementation with comprehensive error handling, detailed logging, and fallback logic is well-designed and robust.

plugins/listener/src/fsm.rs (4)

52-54: LGTM!

The getter method for session ID is implemented correctly.


103-121: LGTM!

The microphone device selection logic is well-implemented with appropriate logging for debugging.


127-130: Good documentation of the delay removal rationale.

The comment clearly explains why the delay was removed and provides context with the commit reference.


416-437: LGTM!

The lightweight cleanup method effectively separates audio resource cleanup from session management, enabling fast device switching.

apps/desktop/src/locales/en/messages.po (1)

302-304: LGTM!

All new microphone device-related translations are properly added with correct English text.

Also applies to: 599-606, 732-743, 782-797, 1024-1029, 1098-1104

crates/audio/src/mic.rs (1)

59-120: Well-implemented device selection logic!

The fallback strategy from exact match → case-insensitive match → word-boundary partial match → default device is robust and user-friendly.

pub telemetry_consent: bool,
pub save_recordings: Option<bool>,
pub selected_template_id: Option<String>,
pub selected_microphone_device: Option<String>,
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify database migration for field rename.

The field rename from selected_template_id to selected_microphone_device is a breaking change that requires careful migration handling.


🏁 Script executed:

#!/bin/bash
# Verify migration strategy for the field rename
echo "Checking for database migrations related to the field rename..."
fd -t f -e sql . | xargs grep -l "selected_template_id\|selected_microphone_device" 2>/dev/null || echo "No SQL migration files found"

echo "Checking for references to the old field name..."
rg "selected_template_id" -A 2 -B 2 --type rust --type ts --type tsx

Length of output: 493


🏁 Script executed:

#!/bin/bash
# Check for a migrations directory and SQL files referencing the old/new field
if [ -d migrations ]; then
  echo "Searching SQL migrations in migrations/ for field rename references..."
  fd -t f -e sql migrations | xargs grep -Hl "selected_template_id\|selected_microphone_device" 2>/dev/null \
    || echo "No SQL migration references found in migrations/"
else
  echo "No migrations/ directory found"
fi

# Search across source for any lingering references to the old field name
echo "Searching codebase for 'selected_template_id' occurrences..."
rg "selected_template_id" --glob "*.rs" --glob "*.sql" --glob "*.ts" --glob "*.tsx" \
  || echo "No references to selected_template_id found in codebase"

Length of output: 1069


Action Required: Update lingering references and add migration

The rename from selected_template_id to selected_microphone_device breaks code in several places and no SQL migration exists to preserve user data. Please:

• Add a database migration that renames the selected_template_id column to selected_microphone_device (or backfills data into the new field)
• Update all code references to the old field:

  • plugins/db/js/bindings.gen.ts: remove selected_template_id from ConfigGeneral or regenerate bindings
  • apps/desktop/src/components/editor-area/index.tsx: replace config.general.selected_template_id with config.general.selected_microphone_device
  • apps/desktop/src/components/settings/views/general.tsx: update selected_template_idselected_microphone_device
  • apps/desktop/src/components/settings/views/templates.tsx: same replacements in both usage and initializers

Once these are in place, verify end-to-end that settings load correctly and existing user configurations aren’t lost.

🤖 Prompt for AI Agents
In crates/db-user/src/config_types.rs at line 47, the field renamed from
selected_template_id to selected_microphone_device requires a database migration
to rename the corresponding column or backfill data to preserve user settings.
Additionally, update all code references to selected_template_id in
plugins/db/js/bindings.gen.ts by regenerating bindings or removing the old
field, and replace selected_template_id with selected_microphone_device in
apps/desktop/src/components/editor-area/index.tsx,
apps/desktop/src/components/settings/views/general.tsx, and
apps/desktop/src/components/settings/views/templates.tsx in both usage and
initialization. After these changes, test thoroughly to ensure settings load
correctly and no user data is lost.

Comment on lines 97 to 112
const microphoneDevices = useQuery({
queryKey: ["microphoneDevices"],
queryFn: async () => {
const result = deviceQuery.data;
console.log("Processing device query result:", result);
if (result && result.startsWith("DEVICES:")) {
const devicesJson = result.substring(8);
console.log("Devices JSON:", devicesJson);
const devices = JSON.parse(devicesJson) as string[];
console.log("Parsed devices:", devices);
return devices;
}
return [];
},
enabled: micPermissionStatus.data === true && deviceQuery.data !== undefined,
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract shared device parsing logic.

The device parsing logic is duplicated between sound.tsx and listen-button.tsx. Consider extracting it to a shared utility.

Create a shared utility file:

// utils/microphone-devices.ts
export interface DeviceData {
  devices: string[];
  selected?: string[];
}

export function parseDeviceData(result: string | null | undefined): DeviceData | null {
  if (!result || !result.startsWith("DEVICES:")) {
    return null;
  }

  const devicesJson = result.substring(8);
  try {
    const parsedData = JSON.parse(devicesJson);

    // Check if it's the new format with devices and selected
    if (parsedData && typeof parsedData === "object" && parsedData.devices) {
      return parsedData;
    }

    // Fallback to old format (array of devices)
    if (Array.isArray(parsedData)) {
      return { devices: parsedData };
    }

    return null;
  } catch (e) {
    console.error("Failed to parse device data:", e);
    return null;
  }
}

Then use it in both components:

+import { parseDeviceData } from "@/utils/microphone-devices";

 const microphoneDevices = useQuery({
   queryKey: ["microphoneDevices"],
   queryFn: async () => {
     const result = deviceQuery.data;
-    console.log("Processing device query result:", result);
-    if (result && result.startsWith("DEVICES:")) {
-      const devicesJson = result.substring(8);
-      console.log("Devices JSON:", devicesJson);
-      const devices = JSON.parse(devicesJson) as string[];
-      console.log("Parsed devices:", devices);
-      return devices;
-    }
-    return [];
+    return parseDeviceData(result)?.devices || [];
   },
   enabled: micPermissionStatus.data === true && deviceQuery.data !== undefined,
 });

Also applies to: 340-364

🧰 Tools
🪛 Biome (1.9.4)

[error] 102-102: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🤖 Prompt for AI Agents
In apps/desktop/src/components/settings/views/sound.tsx around lines 97 to 112
and also lines 340 to 364, the device parsing logic is duplicated. Extract this
logic into a shared utility file (e.g., utils/microphone-devices.ts) with a
function that safely parses the device data string and returns a structured
object. Replace the inline parsing code in sound.tsx and listen-button.tsx with
calls to this new utility function to avoid duplication and improve
maintainability.

@yujonglee
Copy link
Contributor

@plyght can you make it compile

@plyght
Copy link
Contributor Author

plyght commented Jul 3, 2025

@yujonglee will fix everything 🫡

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

♻️ Duplicate comments (3)
crates/audio/src/lib.rs (1)

115-128: LGTM: Consistent error handling in stream creation.

The stream method now properly propagates errors from both microphone and speaker stream creation, addressing the previous review comment about inconsistent error handling.

apps/desktop/src/utils/microphone-devices.ts (1)

11-35: LGTM: Robust parsing with backward compatibility.

The parseDeviceData function handles both new and legacy formats gracefully, with proper error handling and fallback logic. This addresses the previous review comment about extracting shared device parsing logic.

plugins/listener/src/fsm.rs (1)

438-474: Method correctly addresses past review feedback.

The method has been renamed from switch_microphone_device_lightweight to update_microphone_device_config, which better reflects its current implementation. However, the TODO comment indicates incomplete functionality.

The comment indicates this is still incomplete as noted in previous reviews - it only updates config without implementing actual hot-swapping.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa32f6f and bad266f.

📒 Files selected for processing (15)
  • apps/desktop/src/components/editor-area/note-header/listen-button.tsx (3 hunks)
  • apps/desktop/src/components/settings/views/sound.tsx (2 hunks)
  • apps/desktop/src/locales/en/messages.po (32 hunks)
  • apps/desktop/src/locales/ko/messages.po (32 hunks)
  • apps/desktop/src/utils/microphone-devices.ts (1 hunks)
  • crates/audio/src/errors.rs (1 hunks)
  • crates/audio/src/lib.rs (3 hunks)
  • crates/audio/src/mic.rs (2 hunks)
  • crates/db-user/src/config_types.rs (2 hunks)
  • plugins/listener/Cargo.toml (1 hunks)
  • plugins/listener/js/bindings.gen.ts (1 hunks)
  • plugins/listener/src/commands.rs (2 hunks)
  • plugins/listener/src/error.rs (1 hunks)
  • plugins/listener/src/ext.rs (6 hunks)
  • plugins/listener/src/fsm.rs (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • plugins/listener/Cargo.toml
  • crates/db-user/src/config_types.rs
  • plugins/listener/js/bindings.gen.ts
  • apps/desktop/src/components/editor-area/note-header/listen-button.tsx
  • plugins/listener/src/commands.rs
  • apps/desktop/src/locales/ko/messages.po
  • plugins/listener/src/ext.rs
  • apps/desktop/src/locales/en/messages.po
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{js,ts,tsx,rs}`: 1. No error handling. 2. No unused imports, variables, or functions. 3. For comments, keep it minimal. It should be about "Why", not "What".

**/*.{js,ts,tsx,rs}: 1. No error handling.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

⚙️ Source: CodeRabbit Configuration File

List of files the instruction was applied to:

  • plugins/listener/src/error.rs
  • crates/audio/src/errors.rs
  • plugins/listener/src/fsm.rs
  • crates/audio/src/lib.rs
  • crates/audio/src/mic.rs
  • apps/desktop/src/utils/microphone-devices.ts
  • apps/desktop/src/components/settings/views/sound.tsx
🧬 Code Graph Analysis (1)
apps/desktop/src/utils/microphone-devices.ts (1)
plugins/listener/js/bindings.gen.ts (1)
  • MicrophoneDeviceInfo (80-83)
🪛 Biome (1.9.4)
apps/desktop/src/components/settings/views/sound.tsx

[error] 89-89: The catch clause that only rethrows the original error is useless.

An unnecessary catch clause can be confusing.
Unsafe fix: Remove the try/catch clause.

(lint/complexity/noUselessCatch)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: ci (macos, macos-latest)
  • GitHub Check: ci
  • GitHub Check: ci (windows, windows-latest)
🔇 Additional comments (26)
plugins/listener/src/error.rs (2)

17-18: LGTM: Proper error propagation from audio crate.

The AudioError variant correctly uses the #[from] attribute to enable automatic conversion from hypr_audio::AudioError, facilitating clean error propagation.


21-22: LGTM: Clear semantic error variant.

The NoneUser variant provides a clear, specific error case for user-related failures.

crates/audio/src/errors.rs (1)

1-25: LGTM: Comprehensive and well-structured error handling.

The AudioError enum provides excellent coverage of audio-related error scenarios with:

  • Proper use of thiserror::Error derive macro
  • Clear, descriptive error messages
  • Appropriate error wrapping from underlying crates (cpal, anyhow)
  • Good separation of concerns between different error types

This establishes a solid foundation for error handling throughout the audio crate.

crates/audio/src/lib.rs (3)

7-7: LGTM: Focused error export.

Narrowing the export from errors::* to just AudioError provides a cleaner, more focused API.


82-95: LGTM: Well-implemented device selection method.

The from_mic_device method provides flexible device selection with proper fallback to default when no device is specified.


97-104: LGTM: Proper error handling in speaker creation.

The method now returns Result<AudioInput, AudioError> and properly propagates errors from SpeakerInput::new.

apps/desktop/src/utils/microphone-devices.ts (2)

1-4: LGTM: Interface matches backend bindings.

The MicrophoneDeviceInfo interface correctly matches the TypeScript bindings from the plugin, ensuring type consistency across the application.


37-58: LGTM: Comprehensive type guards.

The type guard functions provide thorough validation of the data structures with proper type checking for all required fields.

apps/desktop/src/components/settings/views/sound.tsx (3)

108-116: LGTM: Well-structured device selection mutation.

The mutation properly handles the device selection update with appropriate mapping of "default" to null and triggers refetch on success.


118-130: LGTM: Robust selected device logic.

The getSelectedDevice function properly handles edge cases including missing devices and devices no longer available in the list.


155-199: LGTM: Well-implemented microphone device selection UI.

The conditional rendering of the device selection section is appropriately gated on microphone permission, and the dropdown implementation includes proper loading states and device mapping.

plugins/listener/src/fsm.rs (6)

52-54: LGTM - Simple accessor method.

Clean implementation for exposing the current session ID.


103-121: Good device selection implementation with proper fallback.

The logic correctly retrieves the selected microphone device from config and falls back to default when no device is specified. The logging provides good visibility into device selection.


416-436: Well-designed cleanup method for atomic device switching.

The cleanup_resources method provides fast resource cleanup while preserving session context, which is ideal for atomic device switching. The approach of aborting tasks and cleaning audio state while keeping session_id is sound.


574-574: LGTM - Clean event enum extension.

Adding MicrophoneDeviceChanged(Option<String>) variant is a clean way to handle device change events.


600-631: Robust atomic device switching implementation.

The event handling provides proper atomic restart logic:

  1. Updates config atomically
  2. Performs fast cleanup via cleanup_resources
  3. Immediately restarts with new device
  4. Handles errors gracefully by transitioning to inactive state

This addresses the performance concern about device switching being laggy.


127-131: Confirm absence of audio‐stability tests; manual validation required

I ran a repo-wide search for any references to “audio stability,” timing, or delay and found no tests or code depending on this 50 ms startup delay.

• File to check: plugins/listener/src/fsm.rs (lines 127–131)
• No existing tests guard against audio glitches on rapid device switches

Please manually verify that removing the delay doesn’t introduce audio dropouts when switching devices. If instability appears, consider reintroducing a small delay or adding automated tests to cover fast switching scenarios.

crates/audio/src/mic.rs (9)

1-11: LGTM - Comprehensive imports for audio implementation.

All imports are necessary for the cpal-based audio input implementation with proper async support.


28-55: Excellent device validation with timeout protection.

The validate_device method provides robust device testing with a 500ms timeout, preventing indefinite hangs when testing problematic devices. This is crucial for UX in device selection scenarios.


57-118: Comprehensive device matching strategy with good fallback behavior.

The device selection logic implements multiple matching strategies:

  1. Exact name match
  2. Case-insensitive match
  3. Word-boundary partial match

The fallback to default device with detailed logging is excellent for debugging device issues.


135-229: Good separation of stream building by sample format.

The separate functions for each sample format (f32, i16, u16) provide clean separation and type safety. The error callback logging is appropriate.


231-310: Proper error handling addresses past review feedback.

The audio sample send operations now include proper error logging as suggested in previous reviews, preventing silent audio loss.


350-403: Clean thread-based stream implementation addresses past review feedback.

The implementation successfully removes the unused _stream_handle field mentioned in past reviews and uses a dedicated thread approach. The synchronous setup before thread spawning is good practice.


410-430: Robust cleanup with timeout protection.

The Drop implementation includes timeout protection to prevent indefinite blocking during cleanup, which is essential for responsive UI behavior during device switching.


432-442: Correct futures Stream implementation.

The poll_next implementation properly forwards the tokio receiver's poll results, maintaining the async stream contract.


313-323: u16 normalization implementation is correct.

The current code

fn normalize_u16_sample(sample: u16) -> f32 {
    (sample as f32 - 32768.0) / 32768.0
}

follows the standard procedure for unsigned 16-bit PCM: subtract the midpoint (32768) to center around zero, then divide by 32768.0 to scale to –1.0…+1.0. No changes required.

@plyght
Copy link
Contributor Author

plyght commented Jul 4, 2025

@yujonglee could you help debug some issues i'm having with the selector?

@yujonglee
Copy link
Contributor

#1207

@yujonglee yujonglee closed this Jul 26, 2025
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.

2 participants