Skip to content

Windows support groundwork (#77)#1

Open
Noisemaker111 wants to merge 18 commits intomainfrom
Windows-Support
Open

Windows support groundwork (#77)#1
Noisemaker111 wants to merge 18 commits intomainfrom
Windows-Support

Conversation

@Noisemaker111
Copy link
Owner

@Noisemaker111 Noisemaker111 commented Feb 9, 2026

Summary

  • land Windows tray + window management UX updates and OS-specific plugin gating
  • extend CI/release workflows for Windows builds and updater artifacts
  • document Windows updater signing limitation and Windows-specific provider notes

Testing

  • not run (docs/workflow changes only)

Open with Devin

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.
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

View 10 additional findings in Devin Review.

Open in Devin Review

bun-version: latest

- name: Install Rust toolchain
uses: dtolnay/rust-action@stable

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.

Suggested change
uses: dtolnay/rust-action@stable
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 17 additional findings in Devin Review.

Open in Devin Review

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 23 additional findings in Devin Review.

Open in Devin Review

function loadApiKey(ctx) {
function loadApiKey(ctx, variant) {
try {
var stateDb = ctx.app.platform === "windows" ? getStateDbPath(ctx) : variant.stateDb

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.stateDb

On 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).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +651 to 655
setPluginSettings(normalized)
pluginSettingsRef.current = normalized
setAutoUpdateInterval(storedInterval)
setThemeMode(storedThemeMode)
setDisplayMode(storedDisplayMode)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Suggested change
setPluginSettings(normalized)
pluginSettingsRef.current = normalized
setAutoUpdateInterval(storedInterval)
setThemeMode(storedThemeMode)
setDisplayMode(storedDisplayMode)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

robinebers and others added 4 commits February 13, 2026 16:04
…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
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 6 new potential issues.

View 23 additional findings in Devin Review.

Open in Devin Review

Comment on lines +1653 to +1665
<<<<<<< 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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +78 to 79
let sensitive_params = [
let sensitive_params = [

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.

Suggested change
let sensitive_params = [
let sensitive_params = [
let sensitive_params = [
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +74 to +77
"Go to Settings",
true,
None::<&str>,
)?;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.

Suggested change
"Go to Settings",
true,
None::<&str>,
)?;
Open in Devin Review

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};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.

Suggested change
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};
Open in Devin Review

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
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.
Open in Devin Review

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
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 30 additional findings in Devin Review.

Open in Devin Review

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants