Skip to content

Conversation

@yujonglee
Copy link
Contributor

No description provided.

@netlify
Copy link

netlify bot commented Nov 26, 2025

Deploy Preview for hyprnote-storybook ready!

Name Link
🔨 Latest commit 2cc38cd
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/692659a7d488400008038dc5
😎 Deploy Preview https://deploy-preview-1904--hyprnote-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Nov 26, 2025

Deploy Preview for hyprnote ready!

Name Link
🔨 Latest commit 2cc38cd
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/692659a71f7f4d0008e26b90
😎 Deploy Preview https://deploy-preview-1904--hyprnote.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

📝 Walkthrough

Walkthrough

This PR replaces a controller-based actor architecture with a session-supervised architecture. The ControllerActor and ControllerParams are deleted, replaced by SessionParams and SessionContext. Public APIs migrate to accept SessionParams. Plugin initialization is simplified by removing InitOptions. Audio buffering is introduced in SourceActor, and session supervision is now explicit using Supervisor with named child actors.

Changes

Cohort / File(s) Summary
Plugin Initialization
apps/desktop/src-tauri/src/lib.rs
Removed InitOptions parameter from tauri_plugin_listener initialization call, simplifying from init(InitOptions { parent_supervisor: ... }) to init().
Store Type Updates
apps/desktop/src/store/zustand/listener/general.ts
Replaced ControllerParams with SessionParams in GeneralActions.start signature, startSessionEffect, and imports from @hypr/plugin-listener.
Controller Actor Deletion
plugins/listener/src/actors/controller.rs
Deleted entire controller module including ControllerMsg enum, ControllerParams/Args/State structs, ControllerActor with full lifecycle management, sub-actor orchestration, and mic-related state handling.
Actors Module Re-exports
plugins/listener/src/actors/mod.rs
Removed public re-export of controller module; made AudioChunk.data field public.
Source Actor Refactoring
plugins/listener/src/actors/source.rs
Replaced GetMode message variant with GetSessionId; removed token field from SourceArgs/SourceState; added session_id to SourceState; introduced MAX_BUFFER_CHUNKS constant and AudioBuffer struct for buffering audio when ListenerActor unavailable; added flush_buffer_to_listener and send_to_listener methods; adjusted audio data paths to use buffered data.
Plugin Commands
plugins/listener/src/commands.rs
Updated start_session import from actors::ControllerParams to supervisor::SessionParams and adjusted function signature accordingly.
Plugin Extension Layer
plugins/listener/src/ext.rs
Replaced ControllerActor/ControllerMsg lookups with SourceActor/SourceMsg for device queries; updated start_session to accept SessionParams; refactored session lifecycle to create SessionContext, spawn supervised session, and manage session_supervisor reference; updated stop_session to retrieve session_id and emit appropriate events; added state guard checking for concurrent session prevention.
Plugin Library Root
plugins/listener/src/lib.rs
Changed public exports from SupervisorHandle/SupervisorRef/SUPERVISOR_NAME to session_supervisor_name/SessionContext/SessionParams; replaced listener_supervisor and SupervisorHandle fields with session_supervisor (ActorCell) and supervisor_handle (JoinHandle); removed InitOptions struct; simplified init() to remove parameterization; changed get_state from async to synchronous with registry-based lookup replaced by session_supervisor presence check.
Supervisor Architecture
plugins/listener/src/supervisor.rs
Replaced DynamicSupervisor with session-scoped Supervisor; added SessionParams and SessionContext structs; introduced SESSION_SUPERVISOR_PREFIX constant and session_supervisor_name helper; added spawn_session_supervisor function building static supervisor with SourceActor, ListenerActor, and conditional RecorderActor; added make_listener_backoff and make_supervisor_options helpers; removed previous SupervisorRef/SupervisorHandle/SUPERVISOR_NAME exports.

Sequence Diagram(s)

sequenceDiagram
    participant App as Tauri App
    participant Ext as ListenerPluginExt
    participant Sup as SessionSupervisor
    participant Src as SourceActor
    participant Lst as ListenerActor
    participant Rec as RecorderActor

    App->>Ext: start_session(SessionParams)
    Ext->>Ext: Check guard.session_supervisor
    Ext->>Ext: Resolve app_dir
    Ext->>Ext: Create SessionContext
    Ext->>Sup: spawn_session_supervisor(ctx)
    Sup->>Src: Initialize SourceActor
    Sup->>Lst: Initialize ListenerActor<br/>(with backoff strategy)
    Sup->>Rec: Initialize RecorderActor<br/>(if enabled)
    Src-->>Ext: SourceActor running
    Lst-->>Ext: ListenerActor running
    Rec-->>Ext: RecorderActor running
    Ext->>Ext: Store supervisor & handle
    Ext->>App: Emit RunningActive event
    
    rect rgba(100, 200, 150, 0.3)
    Note over Src,Lst: Audio Buffering Path
    Src->>Src: Generate audio chunks
    Src->>Lst: Send audio
    alt Listener unavailable
        Src->>Src: Buffer to AudioBuffer
        Src->>Src: Log buffering event
    else Listener recovered
        Src->>Src: Flush buffer_to_listener
        Src->>Lst: Send buffered chunks
    end
    end
    
    App->>Ext: stop_session()
    Ext->>Src: GetSessionId message
    Src-->>Ext: Return session_id
    Ext->>App: Emit Finalizing event
    Ext->>Sup: Stop supervisor
    Ext->>Ext: Await handle completion
    Ext->>App: Emit Inactive event
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • plugins/listener/src/supervisor.rs: High logic density with new session supervisor construction, supervisor options, backoff strategies, and spawn logic—requires careful verification of actor initialization order and error handling paths.
  • plugins/listener/src/ext.rs: Significant refactoring of session lifecycle management, including state guard checks, SessionContext creation, and multi-step async coordination between plugin and supervisor—logic flow changes require close inspection.
  • plugins/listener/src/actors/source.rs: Audio buffering mechanism introduction with multiple new methods (flush_buffer_to_listener, send_to_listener) and conditional buffering logic paths—buffer lifecycle and flushing conditions need verification.
  • plugins/listener/src/lib.rs: Public API surface changes and state structure modifications; async-to-sync get_state conversion requires verification of actor registry removal implications.
  • ControllerActor deletion: Verify all references to removed ControllerParams, ControllerMsg, and related types are successfully migrated to SessionParams/SourceMsg throughout the codebase.

Possibly related PRs

  • add root supervisor in desktop app #1881: Directly reverses this PR's removal of InitOptions by re-introducing parameterized init—closely related to the initialization flow changes.
  • Refactor session actor #1485: Refactors session/supervisor types and actor layer with SessionParams/SessionContext introduction, directly overlapping with this PR's session architecture migration.
  • fixes before beta release #1692: Modifies controller.rs recorder path resolution—superseded by this PR's complete controller deletion, creating code-level conflict requiring resolution.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.26% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether a description relates to the changeset. Add a description explaining the motivation for introducing the RestForOne supervisor strategy and how it improves the listener plugin architecture and session management.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'RestForAll supervisor in listener plugin' accurately describes the main architectural change: replacing the dynamic supervisor with a session-scoped static supervisor using a RestForOne strategy in the listener plugin.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch yl-branch-63

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

Copy link
Contributor

@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: 3

🧹 Nitpick comments (6)
plugins/listener/src/actors/mod.rs (1)

9-12: Redundant conditional compilation for identical values.

Both branches of the #[cfg] attribute define SAMPLE_RATE as 16 * 1000. If the value is the same regardless of the target OS, the conditional compilation is unnecessary.

-#[cfg(target_os = "macos")]
-pub const SAMPLE_RATE: u32 = 16 * 1000;
-#[cfg(not(target_os = "macos"))]
-pub const SAMPLE_RATE: u32 = 16 * 1000;
+pub const SAMPLE_RATE: u32 = 16 * 1000;
plugins/listener/src/ext.rs (4)

131-135: Consider handling potential emit failures gracefully.

The .unwrap() on SessionEvent::RunningActive.emit() could panic if the app handle is in an invalid state. While unlikely, consider using .ok() or logging the error instead.

-            SessionEvent::RunningActive {
-                session_id: params.session_id,
-            }
-            .emit(&guard.app)
-            .unwrap();
+            if let Err(e) = SessionEvent::RunningActive {
+                session_id: params.session_id,
+            }
+            .emit(&guard.app)
+            {
+                tracing::error!(error = ?e, "failed_to_emit_running_active");
+            }

160-164: Same concern with Finalizing event emission.

-        SessionEvent::Finalizing { session_id }
-            .emit(&guard.app)
-            .unwrap();
+        if let Err(e) = SessionEvent::Finalizing { session_id }.emit(&guard.app) {
+            tracing::error!(error = ?e, "failed_to_emit_finalizing");
+        }

179-183: Same concern with Inactive event emission.

-        SessionEvent::Inactive { session_id }
-            .emit(&guard.app)
-            .unwrap();
+        if let Err(e) = SessionEvent::Inactive { session_id }.emit(&guard.app) {
+            tracing::error!(error = ?e, "failed_to_emit_inactive");
+        }

170-172: JoinHandle error is silently ignored.

If the supervisor task panicked, handle.await would return a JoinError. Currently this is discarded with let _ = .... Consider logging if the supervisor terminated unexpectedly.

         if let Some(handle) = guard.supervisor_handle.take() {
-            let _ = handle.await;
+            if let Err(e) = handle.await {
+                tracing::warn!(error = ?e, "supervisor_task_join_error");
+            }
         }
plugins/listener/src/supervisor.rs (1)

100-100: Consider making ChannelMode configurable via SessionParams.

ChannelMode::Dual is hardcoded here. If this is intentional for all sessions, consider adding a comment explaining why. Otherwise, consider adding it to SessionParams for flexibility.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b84150 and 2cc38cd.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • plugins/listener/js/bindings.gen.ts is excluded by !**/*.gen.ts
📒 Files selected for processing (9)
  • apps/desktop/src-tauri/src/lib.rs (1 hunks)
  • apps/desktop/src/store/zustand/listener/general.ts (4 hunks)
  • plugins/listener/src/actors/controller.rs (0 hunks)
  • plugins/listener/src/actors/mod.rs (1 hunks)
  • plugins/listener/src/actors/source.rs (7 hunks)
  • plugins/listener/src/commands.rs (2 hunks)
  • plugins/listener/src/ext.rs (5 hunks)
  • plugins/listener/src/lib.rs (3 hunks)
  • plugins/listener/src/supervisor.rs (1 hunks)
💤 Files with no reviewable changes (1)
  • plugins/listener/src/actors/controller.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • apps/desktop/src/store/zustand/listener/general.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/desktop/src/store/zustand/listener/general.ts
🧬 Code graph analysis (4)
apps/desktop/src-tauri/src/lib.rs (1)
plugins/listener/src/lib.rs (1)
  • init (54-75)
plugins/listener/src/ext.rs (2)
plugins/listener/src/supervisor.rs (1)
  • spawn_session_supervisor (60-162)
plugins/listener/src/actors/source.rs (2)
  • name (111-113)
  • handle (154-203)
plugins/listener/src/supervisor.rs (1)
plugins/local-stt/src/server/supervisor.rs (1)
  • make_supervisor_options (19-26)
plugins/listener/src/lib.rs (1)
plugins/listener/src/supervisor.rs (1)
  • session_supervisor_name (37-39)
⏰ 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). (4)
  • GitHub Check: ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: ci (macos, depot-macos-14)
  • GitHub Check: ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: fmt
🔇 Additional comments (21)
plugins/listener/src/actors/mod.rs (1)

20-23: LGTM!

Making data public is appropriate to support the new audio buffering mechanism in SourceActor.

plugins/listener/src/actors/source.rs (3)

539-545: Buffer overflow drops audio without recovery option.

When the buffer exceeds MAX_BUFFER_CHUNKS (150), the oldest chunk is silently dropped. While the warning log is good, consider whether 150 chunks is sufficient for your expected ListenerActor unavailability duration, or if alternative strategies (e.g., writing to disk) should be considered for longer outages.

How long can ListenerActor realistically be unavailable? With typical chunk sizes, 150 chunks may represent only a few seconds of audio.


29-37: LGTM on the GetSessionId message addition.

The replacement of GetMode with GetSessionId aligns with the session-based architecture refactoring.


526-558: Clean AudioBuffer implementation.

The ring-buffer-style AudioBuffer with bounded size and overflow handling is well-structured. The use of VecDeque is appropriate for FIFO semantics.

plugins/listener/src/commands.rs (2)

1-1: LGTM!

Import correctly updated to use the new SessionParams type from the supervisor module.


52-58: LGTM!

The start_session command signature properly updated to accept SessionParams, consistent with the API migration across the codebase.

apps/desktop/src-tauri/src/lib.rs (1)

83-84: LGTM!

The simplified tauri_plugin_listener::init() call aligns with the new session-scoped supervision model. The plugin now manages its own session supervisor internally rather than accepting a parent supervisor.

apps/desktop/src/store/zustand/listener/general.ts (3)

8-14: LGTM!

Import correctly updated to use SessionParams from @hypr/plugin-listener.


49-53: LGTM!

The GeneralActions.start type signature properly updated to accept SessionParams.


83-84: LGTM!

The startSessionEffect function signature correctly updated to match the new parameter type.

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

92-146: Well-structured session lifecycle management.

The start_session implementation properly:

  • Guards against concurrent sessions
  • Resolves the app directory with error handling
  • Manages UI state (tray) on both success and failure paths
  • Creates a comprehensive SessionContext with timing information

40-48: LGTM on SourceActor migration.

The controller actor references are correctly replaced with SourceActor/SourceMsg, and the error variant properly updated.

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

15-15: LGTM!

The export change aligns with the shift from a global dynamic supervisor to session-scoped supervisors.


21-25: LGTM!

Using ActorCell for the session supervisor is appropriate when you only need lifecycle control rather than typed message passing.


27-35: LGTM!

The synchronous implementation is appropriate here. The is_some() check is trivial and doesn't require async.


54-75: LGTM!

The simplified initialization properly defers supervisor spawning to session start, which aligns with the session-scoped architecture.

plugins/listener/src/supervisor.rs (5)

1-12: LGTM!

The imports are well-organized and appropriate for the session supervisor functionality.


28-39: LGTM!

The SessionContext struct appropriately encapsulates runtime context for the session. Using Instant for internal timing is correct.


41-48: Supervision strategy choice looks appropriate.

RestForOne correctly models the dependency chain: if SourceActor fails, all downstream actors restart; if ListenerActor fails, it and the RecorderActor restart. The stricter restart limits (10 in 30s vs the 100 in 180s in local-stt) seem intentional for session-scoped fail-fast behavior.


50-58: LGTM!

The exponential backoff with no delay for the first restart and capped at 16 seconds provides a good balance between fast recovery and preventing restart storms.


154-162: LGTM!

The supervisor spawning is clean, and the return type correctly provides both the ActorCell for lifecycle control and the JoinHandle for awaiting completion.

@yujonglee yujonglee merged commit c95c221 into main Nov 26, 2025
21 checks passed
@yujonglee yujonglee deleted the yl-branch-63 branch November 26, 2025 01:45
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