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 reusable cleanup-releases workflow, remove draft cleanup from cleanup-artifacts
Use synle/gha-workflows cleanup-releases reusable workflow for draft and
incomplete release cleanup. Remove the inline draft release cleanup from
cleanup-artifacts.yml to avoid duplication.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-110Lines changed: 1 addition & 110 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,62 +6,7 @@ Cross-platform desktop system tray application for controlling monitor brightnes
6
6
7
7
Display and dark mode operations are delegated to the [display-dj CLI](https://github.com/synle/display-dj-cli), which runs as a bundled HTTP server sidecar. The Tauri backend makes HTTP requests to it. Volume control remains platform-specific in Rust.
8
8
9
-
## Architecture
10
-
11
-
### Frontend (`src/`)
12
-
13
-
- React 18 + TypeScript, bundled with Vite
14
-
- Communicates with backend via `invoke()` from `@tauri-apps/api/core`
15
-
- Listens for backend events via `listen()` from `@tauri-apps/api/event`
-`lib.rs` — Tauri app setup, plugin init, sidecar launch (display-dj HTTP server), port discovery, window management, dock hiding (macOS), night mode schedule checker
24
-
-`display.rs` — Monitor brightness and contrast via HTTP requests to the display-dj server
25
-
-`dark_mode.rs` — Dark mode detection and toggling via HTTP requests to the display-dj server
26
-
-`volume.rs` — System volume get/set (platform-specific, not via display-dj)
27
-
-`config.rs` — Preferences persistence (JSON file in OS config dir, includes per-monitor metadata), night mode schedule, min brightness with absolute floor, migration from legacy `monitor-configs.json`, reset to defaults
28
-
-`tray.rs` — System tray menu, window positioning, global keyboard shortcuts
The [display-dj CLI](https://github.com/synle/display-dj-cli) is bundled as a Tauri sidecar. On app startup, `lib.rs` finds an available port (starting from 51337) and spawns `display-dj-server serve <port>`. All display and dark mode operations go through its HTTP API at `http://127.0.0.1:<port>/`.
33
-
34
-
Key HTTP routes used:
35
-
36
-
-`GET /get_all` — list all displays with live brightness and contrast
37
-
-`GET /set_one/<id>/<level>` — set one display's brightness
38
-
-`GET /set_all/<level>` — set all displays' brightness
39
-
-`GET /set_contrast_one/<id>/<level>` — set one display's contrast (DDC-only, 0-100)
40
-
-`GET /set_contrast_all/<level>` — set all displays' contrast (DDC-only, 0-100)
-`GET /debug` — full diagnostics: version, OS/arch, display enumeration, active tests (brightness/contrast per display, volume, theme). Restores all settings after testing. Returns JSON.
45
-
46
-
**Sidecar lifecycle:** The `CommandChild` handle is stored in `AppState.sidecar_child`. On app exit, the `RunEvent::Exit` handler in `lib.rs::run()` calls `child.kill()` to terminate the sidecar server. This prevents orphaned `display-dj-server` processes after the main app closes.
display-dj-server-x86_64-pc-windows-msvc.exe # Windows x64
55
-
display-dj-server-x86_64-unknown-linux-gnu # Linux x64
56
-
```
57
-
58
-
### Volume (platform-specific)
59
-
60
-
Volume is the only module with platform-specific code, as the display-dj CLI does not handle volume:
61
-
62
-
-**macOS**: `osascript` (CoreAudio)
63
-
-**Windows**: PowerShell + WASAPI COM interop
64
-
-**Linux**: `pactl` (PulseAudio/PipeWire)
9
+
For full architecture details, request lifecycle, layer-by-layer breakdown, data flow diagrams, and "where to edit" reference, see **[DEV.md](DEV.md)**.
65
10
66
11
## Build Commands
67
12
@@ -100,23 +45,6 @@ cd src-tauri && cargo test # Run all Rust backend tests
100
45
101
46
GitHub Actions (`build.yml`) runs `npm test` and `cargo test` on all platforms (macOS ARM/Intel, Windows, Linux) for every push and PR.
Files: `preferences.json` (includes per-monitor metadata — labels, sort order — as `monitorConfigs` array)
110
-
111
-
## Monitor Identity (UID scheme)
112
-
113
-
Each monitor is identified by a composite UID: `{api_id}::{api_model_name}` (e.g. `"1::Dell U2723QE"`, `"builtin::Built-in Display"`). This is more stable than the raw integer ID from the sidecar API, which can collide when monitors are swapped.
114
-
115
-
-`Monitor.id` — raw API id, used for sidecar HTTP calls (`/set_one/{id}/{value}`)
116
-
-`Monitor.uid` — composite key, used for config lookups, React keys, rename/reorder operations
117
-
-`MonitorMetadata` entries in `preferences.monitorConfigs` are **append-only** — new monitors are added on first detection, never removed on unplug. This preserves labels and sort order across plug/unplug cycles.
118
-
- On startup, a one-time migration converts old `monitor-configs.json` entries into `MonitorMetadata` format within preferences.
119
-
120
48
## Formatting
121
49
122
50
After making changes to frontend code (`src/`), config files, or docs, always run `npx prettier --write` on the changed files before considering the task done. The prettier hook in `.claude/settings.json` handles this automatically for Edit/Write tool calls, but if you create or modify files via other means, run prettier manually.
@@ -139,43 +67,6 @@ After making changes to frontend code (`src/`), config files, or docs, always ru
139
67
- Brightness values are clamped to `effective_min_brightness()` which enforces an absolute floor of 5
140
68
- Contrast is DDC-only (`Option<u32>` / `number | null`): built-in displays return `null`. The contrast slider is hidden by default and toggled via the `showContrast` preference in Settings
141
69
142
-
## Window Positioning (multi-monitor DPI)
143
-
144
-
The tray popup window must appear next to the tray icon, which can be on any monitor with any DPI scale factor. This is deceptively hard because of how Tauri handles coordinates on macOS. **Read the doc comment on `position_window_near_tray` in `tray.rs` before modifying positioning code.**
|`tray.rect()`|`PhysicalPosition` / `PhysicalSize`| Global physical pixels |
151
-
|`monitor.position()` / `monitor.size()`|`PhysicalPosition` / `PhysicalSize`| Global physical pixels |
152
-
|`window.set_position(PhysicalPosition)`| — | Tauri divides by `window.scale_factor()` to get macOS points |
153
-
|`window.scale_factor()`|`f64`| Scale of the monitor the window is **currently** on |
154
-
155
-
### The pitfall
156
-
157
-
`window.scale_factor()` reflects the **current** monitor, not the target. When the window is on a 1× display and the tray is clicked on a 2× display, Tauri's `set_position` divides by 1 instead of 2, placing the window at double the intended macOS-point coordinate (off-screen).
158
-
159
-
**Attempted fix that does NOT work:** moving the hidden window to the target monitor before positioning. `scale_factor()` does not update synchronously after `set_position`.
160
-
161
-
### The fix: scale compensation
162
-
163
-
All positioning math runs in the global physical pixel space using `target_scale` (the tray's monitor). Before calling `set_position`, multiply by `window_scale / target_scale`:
164
-
165
-
```
166
-
Tauri does: point = arg / window_scale
167
-
We need: point = physical / target_scale
168
-
So we pass: arg = physical × window_scale / target_scale
169
-
```
170
-
171
-
When both scales match (same monitor), the compensation is 1× (no-op).
172
-
173
-
### Debug logging
174
-
175
-
Enable debug logging via the tray menu → "Debug" → "Enable Logging" to write positioning data to `debug.log` in the config directory (auto-truncated at 1 MB, keeps last 80% when limit is hit). Open via tray menu → "Debug" → "Open Debug Log". Each tray click logs: tray rect, all monitors (position/size/scale), target selection, window scale, computed position, compensation factor, and final `set_position` arguments.
176
-
177
-
When debug logging is enabled, the app also calls the sidecar's `/debug` endpoint on startup and prepends the full diagnostic dump (version, OS, display enumeration, active brightness/contrast/volume/theme tests) to the debug log. This is useful for troubleshooting display detection issues. You can also hit the endpoint directly: `curl http://127.0.0.1:<port>/debug`.
178
-
179
70
## Related Projects
180
71
181
72
-**[display-dj-cli](https://github.com/synle/display-dj-cli)** — The Rust CLI/HTTP server that handles all display operations (brightness, contrast, dark mode). Bundled as a Tauri sidecar. Source at `/Users/syle/Downloads/display-dj-cli`. When bumping the sidecar version, always review upstream changes in that repo.
0 commit comments