Conversation
This commit adds comprehensive Windows system tray support with feature parity to macOS: Features: - Tray icon appears in Windows system tray with green gauge icon - Left-click toggles window visibility (show/hide) - Right-click shows context menu with live provider usage data - Menu shows provider percentages (e.g., 'Cursor: 65%') and auto-updates - Window positioned above tray icon with proper monitor clamping - Platform-specific arrow: points DOWN on Windows (toward taskbar), UP on macOS - Backend-managed tray icon (frontend won't override) Technical Changes: - Added window_manager.rs for platform abstraction (Windows vs macOS) - Updated tray.rs with Windows-specific icon loading and click handlers - Modified lib.rs to store probe results and trigger menu updates - Updated tauri.conf.json with Windows window config (decorations: false, transparent) - Frontend detects platform and renders correct arrow direction - Added CSS for .tray-arrow-down pointing toward bottom taskbar - Moved tauri-nspanel to macOS-only dependencies (was breaking Windows build) - Added tauri-plugin-os for platform detection Files Changed: - src-tauri/src/tray.rs: Windows tray implementation - src-tauri/src/window_manager.rs: Platform window management (NEW) - src-tauri/src/lib.rs: Probe result storage, tray refresh integration - src-tauri/Cargo.toml: Platform-specific dependencies - src-tauri/tauri.conf.json: Window config, resources - src/App.tsx: Platform detection, arrow positioning - src/index.css: .tray-arrow-down styles, transparent fixes - package.json: Added @tauri-apps/plugin-os Closes robinebers#77
Improve Windows CI/release coverage and document updater signing limits.
Add conditional cert import step and owner follow-up checklist.
| bun-version: latest | ||
|
|
||
| - name: Install Rust toolchain | ||
| uses: dtolnay/rust-action@stable |
There was a problem hiding this comment.
🔴 CI workflow uses non-existent GitHub Action dtolnay/rust-action
The CI workflow references dtolnay/rust-action@stable which does not exist. The correct action name is dtolnay/rust-toolchain@stable, as correctly used in the publish workflow at .github/workflows/publish.yml:37.
Impact
The CI workflow will fail on every PR for both ubuntu-latest and windows-latest matrix entries because the Install Rust toolchain step will fail to find the action. This blocks all CI checks.
| uses: dtolnay/rust-action@stable | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
Was this helpful? React with 👍 or 👎 to provide feedback.
Restore tray updates, Linux tray clicks, safer window clamp, and tighten env access.
There was a problem hiding this comment.
🔴 Copilot plugin does not retry with gh-cli fallback when vault-cached token is stale
On Windows, if the vault-cached Copilot token becomes invalid (401/403), the plugin throws immediately instead of falling back to the gh CLI token.
Root Cause
The 401/403 retry logic at plugins/copilot/plugin.js:255 only enters the fallback path when source === "keychain":
if (source === "keychain") {
clearCachedToken(ctx);
const fallback = loadTokenFromGhCli(ctx);
...
}But the new loadTokenFromVault function (plugins/copilot/plugin.js:83-98) returns { source: "vault" }. When a vault-cached token is stale, source is "vault", so the retry block is skipped entirely. The code falls through to line 275 which throws "Token invalid" without ever attempting the gh-cli fallback.
The pre-existing code handled "keychain" → gh-cli fallback. The new vault path should have the same retry behavior: clear the vault, try gh-cli, and persist the working token.
Impact: On Windows, users with a stale vault token will see "Token invalid" errors even though a valid gh CLI token may be available. They'd need to manually gh auth login again instead of the plugin self-healing.
(Refers to line 255)
Was this helpful? React with 👍 or 👎 to provide feedback.
| function loadApiKey(ctx) { | ||
| function loadApiKey(ctx, variant) { | ||
| try { | ||
| var stateDb = ctx.app.platform === "windows" ? getStateDbPath(ctx) : variant.stateDb |
There was a problem hiding this comment.
🔴 Windsurf plugin ignores variant-specific SQLite path on Windows
On Windows, the Windsurf Next variant reads API keys from the wrong SQLite database, returning data for the regular Windsurf install instead.
Root Cause
The loadApiKey function at plugins/windsurf/plugin.js:65 has:
var stateDb = ctx.app.platform === "windows" ? getStateDbPath(ctx) : variant.stateDbOn Windows, it calls getStateDbPath(ctx) which only probes ~/AppData/Roaming/Windsurf/User and ~/AppData/Local/Windsurf/User (plugins/windsurf/plugin.js:29-31). It never checks Windsurf - Next paths. The variant.stateDb parameter (which contains the correct variant-specific path like Windsurf - Next/User/globalStorage/state.vscdb) is ignored on Windows.
This means both the "windsurf" and "windsurf-next" variants will read API keys from the same Windsurf database on Windows. If a user has Windsurf Next installed (but not regular Windsurf), the probe will either fail to find the database or return wrong credentials.
Impact: Windsurf Next users on Windows get incorrect or missing API key lookups, leading to probe failures or cross-variant data leakage.
Prompt for agents
In plugins/windsurf/plugin.js, the getStateDbPath function (line 27) needs to accept a variant parameter so it can probe variant-specific Windows paths (e.g. ~/AppData/Roaming/Windsurf - Next/User for the windsurf-next variant). Similarly, the probe() function's Windows existence check at line 246 should also be variant-aware. Update getStateDbPath to accept a variant object and use its name/stateDb to construct Windows candidate paths. Then update all call sites: loadApiKey (line 65), and probe() (line 246).
Was this helpful? React with 👍 or 👎 to provide feedback.
| setPluginSettings(normalized) | ||
| pluginSettingsRef.current = normalized | ||
| setAutoUpdateInterval(storedInterval) | ||
| setThemeMode(storedThemeMode) | ||
| setDisplayMode(storedDisplayMode) |
There was a problem hiding this comment.
🟡 Duplicate state setter block causes redundant re-renders during initialization
The app initialization block in src/App.tsx contains duplicated state setter calls, causing every setter to fire twice.
Root Cause
Lines 646-655 are an exact duplicate of lines 646-650 plus additional lines. The block:
setPluginSettings(normalized) // line 646
pluginSettingsRef.current = normalized
setAutoUpdateInterval(storedInterval)
setThemeMode(storedThemeMode)
setDisplayMode(storedDisplayMode)
setPluginSettings(normalized) // line 651 (duplicate)
pluginSettingsRef.current = normalized
setAutoUpdateInterval(storedInterval)
setThemeMode(storedThemeMode)
setDisplayMode(storedDisplayMode)The first five lines (646-650) and the next five (651-655) are identical. This triggers unnecessary React state updates and re-renders during app startup. While React batches these, it still performs redundant work and makes the code confusing.
Impact: Redundant re-renders on startup. Functionally harmless but degrades startup performance and code clarity.
| setPluginSettings(normalized) | |
| pluginSettingsRef.current = normalized | |
| setAutoUpdateInterval(storedInterval) | |
| setThemeMode(storedThemeMode) | |
| setDisplayMode(storedDisplayMode) |
Was this helpful? React with 👍 or 👎 to provide feedback.
…ebers#176) * fix(analytics): reduce noisy event volume with dedupe guards Drop page_viewed tracking, dedupe provider_fetch_error for 60 minutes, and gate app_started to once per version/day so analytics stays meaningful within quota limits. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(codex): surface GPT-5.3-Codex-Spark per-model rate limits The Codex usage API now returns `additional_rate_limits` with per-model session/weekly windows. Parse the array generically so future models auto-appear as detail-scoped progress lines. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(codex): guard limit_name to string before .replace() Handles malformed API entries where limit_name could be a non-string value (number, object) that would crash on .replace(). Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Resolved conflicts: - plugins/claude/plugin.json: kept both os and links fields - plugins/codex/plugin.json: kept both os and links fields - plugins/cursor/plugin.json: kept both os and links fields - src-tauri/src/plugin_engine/host_api.rs: merged Windows DPAPI and terminal env cache code - src-tauri/src/plugin_engine/manifest.rs: kept both os and links fields - src-tauri/src/plugin_engine/runtime.rs: kept both os and links fields in test - src/App.tsx: merged Windows imports and autostart imports
| <<<<<<< HEAD | ||
| let value: Option<String> = | ||
| get.call((name.to_string(),)).expect("get whitelisted var"); | ||
| assert_eq!( | ||
| value, | ||
| std::env::var(name).ok(), | ||
| "{name} should match process env" | ||
| ); | ||
| ======= | ||
| let expected = read_env_from_interactive_zsh(name); | ||
| let value: Option<String> = get.call((name.to_string(),)).expect("get whitelisted var"); | ||
| assert_eq!(value, expected, "{name} should match interactive zsh env"); | ||
| >>>>>>> upstream/main |
There was a problem hiding this comment.
🔴 Unresolved git merge conflict markers in host_api.rs will cause compilation failure
The file src-tauri/src/plugin_engine/host_api.rs contains unresolved git merge conflict markers (<<<<<<< HEAD, =======, >>>>>>> upstream/main) at lines 1653-1665 and 1671-1677. This will cause a Rust compilation error.
Root Cause
A git merge or rebase was not fully resolved. The conflict markers are literal text in the Rust source file inside the env_api_respects_allowlist_in_host_and_js test function. The Rust compiler will fail to parse these lines, preventing the entire crate from building.
Impact: The project cannot compile at all. This blocks all builds, tests, and releases.
Prompt for agents
Resolve the git merge conflict in src-tauri/src/plugin_engine/host_api.rs at lines 1653-1677. Remove all conflict markers (<<<<<<< HEAD, =======, >>>>>>> upstream/main) and choose one version of the test assertions. Since the PR is about Windows support and the env resolution uses resolve_env_from_terminal_zsh_cache (which calls interactive zsh), the upstream/main version using read_env_from_interactive_zsh is likely the correct one for macOS. However, on Windows, zsh won't be available, so the test may need to be cfg-gated or use std::env::var as the HEAD version does.
Was this helpful? React with 👍 or 👎 to provide feedback.
| let sensitive_params = [ | ||
| let sensitive_params = [ |
There was a problem hiding this comment.
🔴 Duplicate let sensitive_params declaration in redact_url causes compilation error
In src-tauri/src/plugin_engine/host_api.rs, the redact_url function has a duplicate let sensitive_params = [ statement at lines 78-79. This is a syntax error that will prevent compilation.
Root Cause
Line 78 reads let sensitive_params = [ and line 79 reads let sensitive_params = [ again. This appears to be a merge artifact where the same line was duplicated. The first let sensitive_params = [ on line 78 is an incomplete statement (no closing ] or ;), and the second one on line 79 starts the actual array. The Rust compiler will reject this as a syntax error.
Impact: Compilation failure — the project cannot build.
| let sensitive_params = [ | |
| let sensitive_params = [ | |
| let sensitive_params = [ | |
Was this helpful? React with 👍 or 👎 to provide feedback.
| "Go to Settings", | ||
| true, | ||
| None::<&str>, | ||
| )?; |
There was a problem hiding this comment.
🔴 Duplicate code block in tray.rs build_tray_menu creates orphaned expression
In src-tauri/src/tray.rs, lines 74-77 contain a duplicate fragment of the go_to_settings MenuItem::with_id call. After the first )?; on line 73, there's a second orphaned "Go to Settings", true, None::<&str>, )?; block that will cause a compilation error.
Root Cause
Lines 67-73 correctly create the go_to_settings menu item. Lines 74-77 then repeat the trailing arguments and closing of a MenuItem::with_id call without the opening. This is likely a copy-paste or merge error. The Rust compiler will fail because "Go to Settings" is an expression statement that doesn't match any valid syntax in this context.
Impact: Compilation failure on all platforms.
| "Go to Settings", | |
| true, | |
| None::<&str>, | |
| )?; |
Was this helpful? React with 👍 or 👎 to provide feedback.
| use tauri_nspanel::ManagerExt; | ||
| use tauri_plugin_store::StoreExt; | ||
|
|
||
| use crate::panel::{get_or_init_panel, position_panel_at_tray_icon, show_panel}; |
There was a problem hiding this comment.
🔴 tray.rs unconditionally imports macOS-only panel module, breaking Windows/Linux builds
In src-tauri/src/tray.rs line 11, use crate::panel::{get_or_init_panel, position_panel_at_tray_icon, show_panel}; is an unconditional import, but the panel module is declared as #[cfg(target_os = "macos")] in src-tauri/src/lib.rs:3-4. This means the import will fail on Windows and Linux.
Root Cause
The panel module only exists when compiling for macOS (#[cfg(target_os = "macos")] in lib.rs:3-4). The tray.rs file imports from it unconditionally at line 11. While the actual usage of these imports inside tray.rs is gated behind #[cfg(target_os = "macos")] blocks, the use statement itself is not gated, so the compiler will fail to resolve the import on non-macOS platforms.
Impact: Windows and Linux builds will fail with an unresolved import error.
| use crate::panel::{get_or_init_panel, position_panel_at_tray_icon, show_panel}; | |
| #[cfg(target_os = "macos")] | |
| use crate::panel::{get_or_init_panel, position_panel_at_tray_icon, show_panel}; |
Was this helpful? React with 👍 or 👎 to provide feedback.
| @@ -97,11 +101,30 @@ fn handle_global_shortcut(app: &tauri::AppHandle, event: tauri_plugin_global_sho | |||
| } | |||
There was a problem hiding this comment.
🔴 handle_global_shortcut calls macOS-only panel::toggle_panel unconditionally on desktop
In src-tauri/src/lib.rs:99, handle_global_shortcut calls panel::toggle_panel(app) under #[cfg(desktop)], but the panel module is only available on macOS (#[cfg(target_os = "macos")]). On Windows/Linux desktop builds, this will fail to compile.
Root Cause
The function at lib.rs:95-101 is gated with #[cfg(desktop)] which includes Windows and Linux. However, it calls panel::toggle_panel(app) at line 99, and the panel module is only compiled on macOS (line 3-4). This means on Windows/Linux desktop builds, the compiler cannot find panel::toggle_panel.
Impact: Windows and Linux desktop builds will fail to compile.
(Refers to lines 95-101)
Prompt for agents
Change the cfg gate on handle_global_shortcut from #[cfg(desktop)] to #[cfg(target_os = "macos")] since it calls panel::toggle_panel which is macOS-only. Alternatively, add platform-specific implementations: on macOS use panel::toggle_panel, on Windows/Linux use WindowManager::show/hide toggle logic.
Was this helpful? React with 👍 or 👎 to provide feedback.
- Add Windows certificate env vars to tauri-action step - Create OWNER_GUIDE.md for Windows certificate setup
There was a problem hiding this comment.
🔴 host.env.get() always returns null on Windows due to hard-coded zsh dependency
The inject_env function at src-tauri/src/plugin_engine/host_api.rs:312-325 resolves environment variables exclusively through resolve_env_from_terminal_zsh_cache, which invokes /bin/zsh -ilc printenv <name> (src-tauri/src/plugin_engine/host_api.rs:38-41). On Windows, /bin/zsh does not exist, so Command::new("/bin/zsh") always fails, returning None. The None result is then cached permanently, meaning all subsequent calls also return None.
Root Cause
This is a pre-existing design for macOS where GUI apps don't inherit shell environment variables. However, this PR adds Windows support and plugins like codex rely on host.env.get("CODEX_HOME") to locate auth files. On Windows, environment variables ARE inherited by the process, so std::env::var() would work correctly—but it's never tried.
The codex plugin has a fallback path (getWindowsAuthPaths()) when CODEX_HOME is null, so this doesn't crash. However, if a Windows user explicitly sets CODEX_HOME, the plugin ignores it and falls through to default path probing, which may locate the wrong file or fail.
Actual: ctx.host.env.get("CODEX_HOME") returns null on Windows even when CODEX_HOME is set.
Expected: Returns the value of the CODEX_HOME environment variable on Windows.
(Refers to lines 312-325)
Prompt for agents
In src-tauri/src/plugin_engine/host_api.rs, the inject_env function (around line 312-325) calls resolve_env_from_terminal_zsh_cache which only works on macOS/Linux (uses /bin/zsh). On Windows, it should fall back to std::env::var(). Change the env.get closure to first try std::env::var() on non-macOS platforms, and only use the zsh cache on macOS. For example, add a platform check: on Windows, use std::env::var(&name).ok() directly; on macOS, keep the existing resolve_env_from_terminal_zsh_cache behavior; on Linux, try std::env::var first then fall back to zsh cache.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Testing