You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add warnings to CLAUDE.md, DEV.md (rules 9-10, known limitations,
adding new commands), and README.md (known issues) explaining:
- Tauri commands with State<AppState> must be async on macOS
- write_debug_log() must not be used in frequently-called sync commands
- Both patterns starve the macOS main-thread run-loop, breaking tray clicks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CLAUDE.md
+10Lines changed: 10 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -57,6 +57,16 @@ After making changes to frontend code (`src/`), config files, or docs, always ru
57
57
4.**Method comments**: Always add doc comments to all new functions and methods. Rust uses `///` doc comments; TypeScript/React uses `/** */` JSDoc comments. Every public function, Tauri command, React component, and non-trivial helper must have a comment describing what it does.
58
58
5.**CLI sidecar version bumps**: When updating `displayDjCliVersion` in `package.json`, always check the [display-dj-cli changelog and commits](https://github.com/synle/display-dj-cli) for upstream changes (new endpoints, changed response formats, removed features). Update our code to use any new APIs and remove usage of deprecated ones. Document the changes in CLAUDE.md and CONTRIBUTING.md.
59
59
60
+
## macOS Tray Icon Pitfall (Critical)
61
+
62
+
On macOS, two patterns in Tauri command handlers break the system tray icon — both left-click and right-click stop working entirely:
63
+
64
+
1.**Sync Tauri commands that access `AppState`**: Declaring a `#[tauri::command]` as `pub fn` (sync) instead of `pub async fn` causes Tauri to run it on a blocking thread that starves the macOS main-thread run-loop, preventing `on_tray_icon_event` from firing. All Tauri commands that access `State<'_, AppState>` must be `async`.
65
+
66
+
2.**`write_debug_log()` in frequently-called sync commands**: `write_debug_log()` locks `state.preferences` to check `debug_logging`. Using it in `get_preferences` (sync, called on every frontend render) creates enough mutex contention to starve the run-loop. Use `log::info!` instead in sync commands. `write_debug_log()` is safe in async/infrequent commands like `save_preferences`.
67
+
68
+
These are documented inline in `config.rs` with WARNING comments.
69
+
60
70
## Key Conventions
61
71
62
72
- All Rust structs sent to frontend use `#[serde(rename_all = "camelCase")]`
8.**The sidecar port is global.** Set once at startup in `AtomicU16`, accessed everywhere via `crate::server_port()`. All modules use `base_url()` to build HTTP URLs.
418
418
419
+
9.**Tauri commands accessing AppState must be `async` on macOS.** Sync `#[tauri::command]` functions that take `State<'_, AppState>` block the macOS main-thread run-loop, preventing tray icon click events from firing. This was the root cause of a hard-to-diagnose bug where both left-click and right-click on the tray icon stopped working. See `config.rs``save_preferences` for the documented warning.
420
+
421
+
10.**Do not use `write_debug_log()` in frequently-called sync commands.**`write_debug_log()` locks `state.preferences` to check the `debug_logging` flag. In sync Tauri commands called on every frontend render (like `get_preferences`), this mutex contention starves the macOS run-loop and breaks tray icon events. Use `log::info!` in those paths. `write_debug_log()` is safe in async or infrequently-called commands.
422
+
419
423
---
420
424
421
425
## Where to Edit
@@ -443,12 +447,15 @@ Quick reference for common tasks:
443
447
444
448
### 1. Define the Rust function
445
449
446
-
In the appropriate module (e.g., `display.rs`):
450
+
In the appropriate module (e.g., `display.rs`). **Use `async fn` if the command accesses `State<'_, AppState>`** — sync commands with state access break macOS tray icon events (see rule 9):
Copy file name to clipboardExpand all lines: README.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -80,6 +80,10 @@ Config files live in `~/Library/Application Support/display-dj/` (macOS), `%APPD
80
80
- Built-in HDMI on base M1/M2 Macs doesn't support DDC/CI -- use USB-C/DisplayPort
81
81
- Linux global shortcuts may not work under Wayland (X11 works fine)
82
82
83
+
### Developer Note: macOS Tray Icon
84
+
85
+
If tray icon clicks stop working on macOS after code changes, the most likely causes are: (1) a Tauri command was changed from `async` to sync, or (2) `write_debug_log()` was added to a frequently-called sync command. Both starve the macOS main-thread run-loop. See `config.rs` inline warnings and [DEV.md](DEV.md) rules 9-10 for details.
86
+
83
87
## Contributing
84
88
85
89
See **[CONTRIBUTING.md](CONTRIBUTING.md)** for the full development setup (per-platform), project structure, testing, and platform guides. See **[DEV.md](DEV.md)** for the architecture deep-dive: request lifecycle, layer-by-layer breakdown, data flow diagrams, and "where to edit" reference.
0 commit comments