Repository: hrzlgnm/mdns-browser
Pull request #1085: feat: start with system theme
Author: login: hrzlgnm
Labels: name: enhancement
description: New feature or request
Created: 2025-05-05T20:44:23Z | Merged: 2025-05-05T21:24:28Z

Closes #1079

## Summary by Sourcery

Implement system theme detection and theme switching functionality

New Features:
- Add ability to detect and use the system's default theme
- Implement theme change event listening

Enhancements:
- Modify event listening mechanism to support optional subscription
- Update theme switching logic to use system theme

Chores:
- Add new model for theme changed events

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added the ability to fetch and display the current theme from the desktop or mobile app.
  - Theme state is now automatically synchronized and updated in real time when changed externally.
- **Improvements**
  - Enhanced event listening functions to allow optional subscription commands, improving flexibility for event handling.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Linked issue #1079: Theme: Start with system theme if supported

Diff:
diff --git a/models/src/lib.rs b/models/src/lib.rs
index 3efabe4b..06275f7b 100644
--- a/models/src/lib.rs
+++ b/models/src/lib.rs
@@ -165,6 +165,11 @@ pub struct ServiceRemovedEventRes {
     pub at_ms: u64,
 }
 
+#[derive(Deserialize, Serialize, Clone, Debug)]
+pub struct ThemeChangedEventRes {
+    pub theme: String,
+}
+
 #[derive(Deserialize, Serialize, Clone, Debug)]
 pub struct CanBrowseChangedEventRes {
     pub can_browse: bool,
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 899cd894..f53108f0 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -19,7 +19,7 @@ use std::{
         Arc, Mutex,
     },
 };
-use tauri::{AppHandle, Emitter, Manager, State, Window};
+use tauri::{AppHandle, Emitter, Manager, State, Theme, Window};
 use tauri_plugin_clipboard_manager::ClipboardExt;
 use tauri_plugin_opener::OpenerExt;
 
@@ -531,6 +531,17 @@ fn copy_to_clipboard(window: Window, contents: String) -> Result<(), String> {
     Ok(())
 }
 
+#[tauri::command]
+fn theme(window: Window) -> Theme {
+    match window.theme() {
+        Ok(theme) => theme,
+        Err(err) => {
+            log::error!("Failed to get theme: {:?}, using dark", err);
+            Theme::Dark
+        }
+    }
+}
+
 #[cfg(desktop)]
 mod foreign_crate {
     #[derive(Copy, Clone, PartialEq, Eq, Debug)]
@@ -727,6 +738,7 @@ pub fn run() {
             subscribe_can_browse,
             subscribe_metrics,
             stop_browse,
+            theme,
             verify,
             version,
         ])
@@ -755,6 +767,7 @@ pub fn run_mobile() {
             subscribe_can_browse,
             subscribe_metrics,
             stop_browse,
+            theme,
             verify,
         ])
         .run(tauri::generate_context!())
diff --git a/src/app/browse.rs b/src/app/browse.rs
index 330f8f34..a62020ee 100644
--- a/src/app/browse.rs
+++ b/src/app/browse.rs
@@ -51,7 +51,7 @@ async fn listen_for_service_type_events(event_writer: WriteSignal<ServiceTypes>)
 async fn listen_for_can_browse_change_events(event_writer: WriteSignal<bool>) {
     listen_events(
         "can-browse-changed",
-        "subscribe_can_browse",
+        Some("subscribe_can_browse"),
         move |event: CanBrowseChangedEventRes| {
             event_writer.update(|evt| *evt = event.can_browse);
         },
diff --git a/src/app/listen.rs b/src/app/listen.rs
index f599864d..c576abe8 100644
--- a/src/app/listen.rs
+++ b/src/app/listen.rs
@@ -11,7 +11,7 @@ use tauri_sys::event::listen;
 ///
 /// # Parameters
 /// * `event_name` - The name of the event to listen for
-/// * `subscriber` - The name of the command to invoke for subscription
+/// * `subscriber` - Optional name of the command to invoke for subscription
 /// * `process_event` - Closure that will be called for each received event
 ///
 /// # Type Parameters
@@ -22,7 +22,7 @@ use tauri_sys::event::listen;
 /// Logs an error and returns early if event subscription fails.
 pub async fn listen_events<T, F>(
     event_name: &str,
-    subscriber: impl Into<String>,
+    subscriber: Option<impl Into<String>>,
     mut process_event: F,
 ) where
     T: DeserializeOwned + 'static + std::fmt::Debug,
@@ -40,7 +40,9 @@ pub async fn listen_events<T, F>(
         }
     };
 
-    spawn_local(invoke_no_args(subscriber.into()));
+    if let Some(subscriber) = subscriber {
+        spawn_local(invoke_no_args(subscriber.into()));
+    }
 
     while let Some(event) = events.next().await {
         log::debug!("Received event {}: {:#?}", event_name, event.payload);
diff --git a/src/app/metrics.rs b/src/app/metrics.rs
index 11e4bf12..26f976f9 100644
--- a/src/app/metrics.rs
+++ b/src/app/metrics.rs
@@ -10,7 +10,7 @@ use super::listen::listen_events;
 async fn listen_for_metrics_event(event_writer: RwSignal<Vec<(String, i64)>>) {
     listen_events(
         "metrics",
-        "subscribe_metrics",
+        Some("subscribe_metrics"),
         move |event: MetricsEventRes| {
             event_writer.update(|evts| {
                 *evts = event.metrics.into_iter().collect::<Vec<_>>();
diff --git a/src/app/theme_switcher.rs b/src/app/theme_switcher.rs
index e0e27940..b5cb83e1 100644
--- a/src/app/theme_switcher.rs
+++ b/src/app/theme_switcher.rs
@@ -1,9 +1,29 @@
 use leptos::prelude::*;
+use models::ThemeChangedEventRes;
+use tauri_sys::core::invoke;
 use thaw::{Icon, Theme};
 
+use super::listen::listen_events;
+
 #[component]
 pub fn ThemeSwitcher(theme: RwSignal<Theme>) -> impl IntoView {
+    LocalResource::new(move || async move {
+        let theme_str = invoke::<String>("theme", &()).await;
+        log::debug!("Got theme: {:?}", theme_str);
+        theme.update(|v| *v = Theme::from(theme_str));
+
+        listen_events(
+            "theme-changed",
+            None::<String>,
+            move |event: ThemeChangedEventRes| {
+                theme.update(|v| *v = Theme::from(event.theme));
+            },
+        )
+        .await;
+    });
+
     let dark = Memo::new(move |_| theme.get().name.eq("dark"));
+
     let on_switch_click = move |_| {
         if dark.get() {
             theme.set(Theme::light());


Reviews:
user: login: sourcery-ai[bot]
body: Hey @hrzlgnm - I've reviewed your changes - here's some feedback:

- Correct the typo in the function name `get_themen_and_listen_for_theme_changed_event`.
- The `theme` command defaults to 'Dark' on error; ensure this fallback provides the intended user experience.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟡 **General issues**: 1 issue found
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟡 **Complexity**: 1 issue found
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T20:45:51Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>src/app/theme_switcher.rs (3)</summary><blockquote>

`13-23`: **Fix typo in function name.**

There's a typo in the function name: "themen" should be "theme".


```diff
-async fn get_themen_and_listen_for_theme_changed_event(event_writer: WriteSignal<Theme>) {
+async fn get_theme_and_listen_for_theme_changed_event(event_writer: WriteSignal<Theme>) {
```

Also, consider a shorter, more concise name like `initialize_theme_with_listener` for better readability.

---

`27-27`: **Update function reference after renaming.**

If you rename the function as suggested, be sure to update this reference as well.


```diff
-    LocalResource::new(move || get_themen_and_listen_for_theme_changed_event(theme.write_only()));
+    LocalResource::new(move || get_theme_and_listen_for_theme_changed_event(theme.write_only()));
```

---

`18-20`: **Add logging for theme change events.**

Consider adding debug logging when theme change events are received to maintain consistent logging patterns.


```diff
    move |event: ThemeChangedEventRes| {
+        log::debug!("Theme changed to: {}", event.theme);
        event_writer.update(|v| *v = Theme::from(event.theme));
    },
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 540689a43f0283d4c0d4370e3829d2f40a953e99 and ce64ee4ddfd0328fc55b313faffbcc09cae4975a.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `models/src/lib.rs` (1 hunks)
* `src-tauri/src/lib.rs` (4 hunks)
* `src/app/browse.rs` (1 hunks)
* `src/app/listen.rs` (3 hunks)
* `src/app/metrics.rs` (1 hunks)
* `src/app/theme_switcher.rs` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>src/app/listen.rs (1)</summary><blockquote>

<details>
<summary>src/app/invoke.rs (1)</summary>

* `invoke_no_args` (5-10)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: pr / 🪟
* GitHub Check: pr / 🐧
* GitHub Check: pr / 🍏
* GitHub Check: pr / 📦
* GitHub Check: SonarQube

</details>

<details>
<summary>🔇 Additional comments (9)</summary><blockquote>

<details>
<summary>models/src/lib.rs (1)</summary>

`168-171`: **Good addition of the ThemeChangedEventRes struct**

The new struct follows the project's established patterns and naming conventions while providing the necessary fields for theme change event handling.

</details>
<details>
<summary>src/app/browse.rs (1)</summary>

`54-54`: **Properly updated to use optional subscriber parameter**

The change correctly wraps the subscriber string with `Some()` to match the updated function signature in the listen module.

</details>
<details>
<summary>src/app/metrics.rs (1)</summary>

`13-13`: **Properly updated to use optional subscriber parameter**

The change correctly wraps the subscriber string with `Some()` to match the updated function signature in the listen module.

</details>
<details>
<summary>src-tauri/src/lib.rs (4)</summary>

`22-22`: **Appropriate Tauri imports for theme functionality**

The import now includes the necessary `Theme` type for the new theme command.

---

`534-543`: **Well-implemented theme command with proper error handling**

The new `theme` command correctly retrieves the system theme with good error handling. If retrieving the theme fails, it logs the error and falls back to the dark theme.

---

`741-741`: **Correctly registered theme command in desktop invoke handler**

The theme command is properly registered in the desktop invoke handler list.

---

`770-770`: **Correctly registered theme command in mobile invoke handler**

The theme command is also properly registered in the mobile invoke handler list, ensuring consistent functionality across platforms.

</details>
<details>
<summary>src/app/listen.rs (1)</summary>

`14-15`: **LGTM: Well-executed change to make subscriber optional.**

The modification to make the subscriber parameter optional is cleanly implemented and well-documented. This change maintains backward compatibility while enabling the new theme event subscription pattern in theme_switcher.rs.



Also applies to: 25-26, 43-45

</details>
<details>
<summary>src/app/theme_switcher.rs (1)</summary>

`25-47`: **Consider resource cleanup for theme event listener.**

The current implementation creates an event listener that runs for the lifetime of the component. You might want to consider resource cleanup to prevent potential leaks if the component mounts/unmounts frequently.


For a production-ready implementation, you might want to research if leptos's `LocalResource` provides a way to clean up resources when components are destroyed, or if you need to implement a custom solution.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T20:47:04Z

Review comments:
diff_hunk: @@ -1,8 +1,30 @@
+use super::listen::listen_events;
 use leptos::prelude::*;
+use models::ThemeChangedEventRes;
+use tauri_sys::core::invoke;
 use thaw::{Icon, Theme};
 
+async fn get_theme(writer: WriteSignal<Theme>) {
+    let theme = invoke::<String>("theme", &()).await;
+    log::debug!("Got theme: {:?}", theme);
+    writer.update(|v| *v = Theme::from(theme));
+}
+
+async fn get_themen_and_listen_for_theme_changed_event(event_writer: WriteSignal<Theme>) {
path: src/app/theme_switcher.rs
user: login: sourcery-ai[bot]
body: **nitpick (typo):** Function name typo: 'get_themen_and_listen_for_theme_changed_event' might be intended as 'get_theme_and_listen_for_theme_changed_event'.

Consider renaming it for consistency unless the misspelling was intentional.

Suggested implementation:

```rust
async fn get_theme_and_listen_for_theme_changed_event(event_writer: WriteSignal<Theme>) {

```

If this function is referenced anywhere else in your codebase, make sure you update those references to the new function name.
created_at: 2025-05-05T20:45:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,8 +1,30 @@
+use super::listen::listen_events;
 use leptos::prelude::*;
+use models::ThemeChangedEventRes;
+use tauri_sys::core::invoke;
 use thaw::{Icon, Theme};
 
+async fn get_theme(writer: WriteSignal<Theme>) {
+    let theme = invoke::<String>("theme", &()).await;
+    log::debug!("Got theme: {:?}", theme);
+    writer.update(|v| *v = Theme::from(theme));
+}
+
+async fn get_themen_and_listen_for_theme_changed_event(event_writer: WriteSignal<Theme>) {
+    get_theme(event_writer).await;
+    listen_events(
+        "theme-changed",
+        None::<String>,
+        move |event: ThemeChangedEventRes| {
+            event_writer.update(|v| *v = Theme::from(event.theme));
+        },
+    )
+    .await;
+}
+
 #[component]
path: src/app/theme_switcher.rs
user: login: sourcery-ai[bot]
body: **issue (complexity):** Consider inlining the `get_theme` and `get_themen_and_listen_for_theme_changed_event` functions within the `LocalResource::new` closure to reduce complexity.

The extra helper functions are adding an abstraction layer that might be overkill if they're only used within the component. Consider inlining their logic to flatten the async flow. For example, you could replace the two helper functions with an inline async block inside the `LocalResource::new` closure:

```rust
#[component]
pub fn ThemeSwitcher(theme: RwSignal<Theme>) -> impl IntoView {
    let writer = theme.write_only();
    LocalResource::new(move || async move {
        // Retrieve theme and update writer.
        let theme_str = invoke::<String>("theme", &()).await;
        log::debug!("Got theme: {:?}", theme_str);
        writer.update(|v| *v = Theme::from(theme_str));

        // Listen for theme-changed event and update writer.
        listen_events(
            "theme-changed",
            None::<String>,
            move |event: ThemeChangedEventRes| {
                writer.update(|v| *v = Theme::from(event.theme));
            },
        )
        .await;
    });

    let dark = Memo::new(move |_| theme.get().name.eq("dark"));
    let on_switch_click = move |_| {
        if dark.get() {
            theme.set(Theme::light());
        } else {
            theme.set(Theme::dark());
        }
    };
    let icon = Memo::new(move |_| { /* ... */ });

    // rest of your component view...
}
```

This approach keeps the functionality intact while reducing the number of nested async functions and improves readability by consolidating related operations.
created_at: 2025-05-05T20:45:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,8 +1,30 @@
+use super::listen::listen_events;
 use leptos::prelude::*;
+use models::ThemeChangedEventRes;
+use tauri_sys::core::invoke;
 use thaw::{Icon, Theme};
 
+async fn get_theme(writer: WriteSignal<Theme>) {
+    let theme = invoke::<String>("theme", &()).await;
+    log::debug!("Got theme: {:?}", theme);
+    writer.update(|v| *v = Theme::from(theme));
+}
path: src/app/theme_switcher.rs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add error handling for the theme command invocation.**

The function doesn't handle potential errors from the invoke call. If the command fails, the theme might not be set correctly.


```diff
async fn get_theme(writer: WriteSignal<Theme>) {
-    let theme = invoke::<String>("theme", &()).await;
-    log::debug!("Got theme: {:?}", theme);
-    writer.update(|v| *v = Theme::from(theme));
+    match invoke::<String>("theme", &()).await {
+        Ok(theme) => {
+            log::debug!("Got theme: {}", theme);
+            writer.update(|v| *v = Theme::from(theme));
+        }
+        Err(err) => {
+            log::error!("Failed to get theme: {:?}", err);
+            // Optionally set a default theme
+            writer.update(|v| *v = Theme::dark());
+        }
+    }
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
async fn get_theme(writer: WriteSignal<Theme>) {
    match invoke::<String>("theme", &()).await {
        Ok(theme) => {
            log::debug!("Got theme: {}", theme);
            writer.update(|v| *v = Theme::from(theme));
        }
        Err(err) => {
            log::error!("Failed to get theme: {:?}", err);
            // Optionally set a default theme
            writer.update(|v| *v = Theme::dark());
        }
    }
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-05T20:47:03Z
author_association: CONTRIBUTOR

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-05-05T20:44:26Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide

This PR implements system theme integration by adding a Tauri command (`theme`) to fetch the initial theme from the OS and utilizing Tauri's built-in `theme-changed` event to reactively update the UI. The `ThemeSwitcher` component now calls the new command on load and listens for theme change events. The generic `listen_events` function was updated to allow listening without needing a specific subscription command.

#### Sequence Diagram for Initial Theme Fetch and Update

```mermaid
sequenceDiagram
    participant FS as Frontend (ThemeSwitcher)
    participant TF as Tauri Frontend (API)
    participant TB as Tauri Backend
    participant OS as Operating System

    FS->>TF: Mount component, trigger LocalResource
    TF->>TB: invoke("theme")
    TB->>OS: Get system theme
    OS-->>TB: Return theme (e.g., "dark")
    TB-->>TF: Return theme string
    TF->>FS: Update theme signal (initial theme)

    TF->>TB: listen("theme-changed")
    Note over TB,OS: OS theme changes later
    OS->>TB: Notify theme change
    TB->>TF: Emit "theme-changed" event (payload: {theme: "light"})
    TF->>FS: Receive event, trigger callback
    FS->>FS: Update theme signal
```

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Fetch initial system theme via a new Tauri command. | <ul><li>Added `theme` Tauri command to read window theme.</li><li>Registered `theme` command.</li><li>Invoked `theme` command on frontend component load to set initial theme.</li></ul> | `src-tauri/src/lib.rs`<br/>`src/app/theme_switcher.rs` |
| Reactively update theme based on system changes. | <ul><li>Added `ThemeChangedEventRes` model for event payload.</li><li>Listened for Tauri's `theme-changed` event.</li><li>Updated theme signal (`RwSignal<Theme>`) upon receiving the event.</li></ul> | `src/app/theme_switcher.rs`<br/>`models/src/lib.rs` |
| Modified event listening utility for flexibility. | <ul><li>Made subscription command optional in `listen_events` function.</li><li>Updated existing `listen_events` call sites to use `Some()`.</li></ul> | `src/app/listen.rs`<br/>`src/app/browse.rs`<br/>`src/app/metrics.rs` |

### Assessment against linked issues

| Issue | Objective | Addressed | Explanation |
| ------ | ------- | ----- | ----- |
| #1079 | The application should start with the system theme if supported. | ✅ |  |
| #1079 | The application should listen for system theme changes and update the theme accordingly. | ✅ |  |

### Possibly linked issues

- **#1079**: The PR adds functionality to detect and use the system theme, directly addressing the issue's goal.

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-05-05T20:44:29Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: rate limited by coderabbit.ai -->

> [!WARNING]
> ## Rate limit exceeded
> 
> @hrzlgnm has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait **17 minutes and 45 seconds** before requesting another review.
> 
> <details>
> <summary>⌛ How to resolve this issue?</summary>
> 
> After the wait time has elapsed, a review can be triggered using the `@coderabbitai review` command as a PR comment. Alternatively, push new commits to this PR.
> 
> We recommend that you space out your commits to avoid hitting the rate limit.
> 
> </details>
> 
> 
> <details>
> <summary>🚦 How do rate limits work?</summary>
> 
> CodeRabbit enforces hourly rate limits for each developer per organization.
> 
> Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.
> 
> Please see our [FAQ](https://docs.coderabbit.ai/faq) for further information.
> 
> </details>
> 
> <details>
> <summary>📥 Commits</summary>
> 
> Reviewing files that changed from the base of the PR and between ce64ee4ddfd0328fc55b313faffbcc09cae4975a and 8e2189cdbc9287cb0a9eb68c2b8f91885bab4d84.
> 
> </details>
> 
> <details>
> <summary>📒 Files selected for processing (1)</summary>
> 
> * `src/app/theme_switcher.rs` (1 hunks)
> 
> </details>

<!-- end of auto-generated comment: rate limited by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

A new backend event response struct for theme changes was added. The Tauri backend now exposes a `theme` command to retrieve the current theme, defaulting to dark on error. The frontend's theme switcher and related listeners were updated to fetch and react to theme changes asynchronously, with event listening logic generalized for optional subscription.

## Changes

| File(s)                                                                 | Change Summary                                                                                                      |
|-------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|
| models/src/lib.rs                                                       | Added `ThemeChangedEventRes` struct for theme change events.                                                       |
| src-tauri/src/lib.rs                                                    | Introduced `theme` Tauri command to retrieve the current window theme; registered the command for frontend use.    |
| src/app/listen.rs                                                       | Updated `listen_events` to accept an optional subscriber command.                                                  |
| src/app/browse.rs, src/app/metrics.rs                                   | Changed `listen_events` calls to pass subscriber as `Some("...")` instead of a string literal.                     |
| src/app/theme_switcher.rs                                               | Added async functions to fetch and react to theme changes; updated `ThemeSwitcher` to initialize and listen for changes. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Frontend
    participant TauriBackend
    participant Window

    Frontend->>TauriBackend: invoke("theme")
    TauriBackend->>Window: window.theme()
    Window-->>TauriBackend: Theme (or error)
    TauriBackend-->>Frontend: Theme (default Dark on error)
    Frontend->>Frontend: Update theme state

    Frontend->>TauriBackend: Listen for "theme-changed" events
    TauriBackend-->>Frontend: theme-changed event with new theme
    Frontend->>Frontend: Update theme state
```

## Assessment against linked issues

| Objective                                                                 | Addressed | Explanation |
|---------------------------------------------------------------------------|-----------|-------------|
| Start with system theme if supported (#1079)                              | ✅        |             |

## Possibly related PRs

- hrzlgnm/mdns-browser#1078: Refactors the ThemeSwitcher component to isolate theme toggle logic; both PRs modify theme management, but this PR adds backend integration and event handling.

## Suggested labels

`enhancement`

## Poem

> A rabbit hops in code-lit night,  
> Now themes can change with just a byte.  
> Listen close, the colors shift,  
> With backend help, the moods uplift.  
> From system dark to sunlight's gleam—  
> The app now starts with your device's theme!  
> 🐇✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=hrzlgnm/mdns-browser&utm_content=1085):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- 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 generate unit testing code for this file.`
  -	`@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 generate unit testing code.`
  - `@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](https://www.coderabbit.ai/contact-us/support) 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](https://docs.coderabbit.ai/finishing-touches/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.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-05-05T20:52:54Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=hrzlgnm_mdns-browser&pullRequest=1085) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=hrzlgnm_mdns-browser&pullRequest=1085&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=hrzlgnm_mdns-browser&pullRequest=1085&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=hrzlgnm_mdns-browser&pullRequest=1085&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=hrzlgnm_mdns-browser&pullRequest=1085&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=hrzlgnm_mdns-browser&pullRequest=1085&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=hrzlgnm_mdns-browser&pullRequest=1085)


author_association: NONE