Skip to content

Comments

Fix macOS window close crash with NSPanel#13

Open
dannysmith wants to merge 1 commit intomainfrom
fix/macos-window-close-crash
Open

Fix macOS window close crash with NSPanel#13
dannysmith wants to merge 1 commit intomainfrom
fix/macos-window-close-crash

Conversation

@dannysmith
Copy link
Owner

@dannysmith dannysmith commented Jan 14, 2026

Summary

  • Fix crash when closing the app on macOS caused by window-state plugin trying to call is_maximized() on NSPanel
  • Add explicit window label for reliable state tracking
  • Increase default window size to 1200x800
  • Add cleanup handler for proper shutdown

Details

The tauri-plugin-window-state crashes when it iterates all windows on close and tries to call is_maximized() on the quick-pane NSPanel. This causes a "panic in a function that cannot unwind" error.

Fix: Use with_denylist(&["quick-pane"]) to exclude the NSPanel from window-state tracking.

See: tauri-apps/plugins-workspace#1546

Test plan

  • Run bun run tauri:dev
  • Resize the window
  • Press Cmd+Q to quit
  • Verify no crash occurs
  • Reopen and verify window size was remembered

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved application window state management and cleanup when closing the app.
    • Enhanced macOS quick-pane panel handling on application exit.
  • Improvements

    • Increased default application window size for better visibility and usability.

✏️ Tip: You can customize this high-level summary in your review settings.

The window-state plugin was crashing when trying to call is_maximized()
on the NSPanel (quick-pane), causing the app to panic on close.

Changes:
- Add explicit "main" label to window config for reliable state tracking
- Increase default window size to 1200x800
- Exclude quick-pane from window-state plugin via with_denylist()
- Add close handler to save window state and cleanup before exit

See: tauri-apps/plugins-workspace#1546

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 14, 2026

Walkthrough

The changes add lifecycle management for the main window, implementing cleanup operations on close including window state persistence, panel hiding on macOS, and global shortcut unregistration. Window configuration is also updated with a new label identifier and increased dimensions.

Changes

Cohort / File(s) Summary
Window lifecycle cleanup handler
src-tauri/src/lib.rs
Added RunEvent and WindowEvent imports. Introduced build()...run() pattern with on_window_close_requested handler that saves window state (desktop), hides quick-pane on macOS via tauri_nspanel, and unregisters global shortcuts (desktop), with logging at each step. Added "quick-pane" to window state plugin denylist.
Window configuration
src-tauri/tauri.conf.json
Added label: "main" identifier to main window. Increased window dimensions from 1000×700 to 1200×800 pixels.

Sequence Diagram

sequenceDiagram
    participant MainWindow as Main Window
    participant Runtime as Tauri Runtime
    participant StatePlugin as Window State Plugin
    participant Panel as tauri_nspanel
    participant Shortcuts as Global Shortcuts
    
    MainWindow->>Runtime: close request triggered
    Runtime->>StatePlugin: save window state (desktop only)
    StatePlugin-->>Runtime: state persisted
    Runtime->>Panel: hide quick-pane (macOS only)
    Panel-->>Runtime: panel hidden
    Runtime->>Shortcuts: unregister all shortcuts (desktop only)
    Shortcuts-->>Runtime: shortcuts unregistered
    Runtime->>Runtime: emit cleanup logs
    Runtime-->>MainWindow: close allowed
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A window closes with a gentle care,
State saved, shortcuts freed from air,
On Mac, the panel takes a bow,
Cleanup's done—the rabbit takes a vow! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically addresses the main problem being solved: a macOS window close crash related to NSPanel, which is the primary focus of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src-tauri/src/lib.rs`:
- Around line 140-187: The call to panel.hide() uses a removed/nonexistent API;
update calls obtained via app_handle.get_webview_panel("quick-pane") to use the
v2.1 API by converting the panel to a Window and closing it (e.g., replace
panel.hide() with panel.to_window().close() or panel.close() per tauri_nspanel
v2.1 semantics), and make the same replacement for all occurrences (e.g., the
panel.hide() uses in the RunEvent handler and in the quick_pane command
functions).
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47e704b and 6129070.

📒 Files selected for processing (2)
  • src-tauri/src/lib.rs
  • src-tauri/tauri.conf.json
🧰 Additional context used
📓 Path-based instructions (3)
{src-tauri/**/*.rs,src/**/*tauri*,vite.config.ts,tauri.conf.json}

📄 CodeRabbit inference engine (AGENTS.md)

Use only Tauri v2 documentation; avoid outdated v1 patterns

Files:

  • src-tauri/src/lib.rs
src-tauri/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

Use modern Rust formatting: format!("{variable}") instead of concatenation or println! macros

Files:

  • src-tauri/src/lib.rs
{src-tauri/**/*.rs,src/**/*.{ts,tsx}@(!(test|spec))}

📄 CodeRabbit inference engine (AGENTS.md)

Emit events from Rust to React using app.emit("event-name", data) and listen in React with listen("event-name", handler)

Files:

  • src-tauri/src/lib.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (3)
src-tauri/tauri.conf.json (1)

16-19: LGTM! Window configuration properly aligns with the Rust close handler.

The explicit "main" label is correctly referenced in lib.rs for single-instance focus handling (line 34) and close cleanup (line 149). The increased dimensions remain within valid bounds relative to minWidth/minHeight.

src-tauri/src/lib.rs (2)

12-12: LGTM! Correct imports for run loop event handling.

The RunEvent and WindowEvent imports are required for the .build(...).run(...) pattern introduced below.


42-51: Good fix with proper documentation.

The denylist correctly excludes the quick-pane NSPanel from window-state tracking, preventing the crash when the plugin iterates windows and calls is_maximized(). The issue reference is helpful for future maintainers.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +140 to +187
.build(tauri::generate_context!())
.expect("error while building tauri application")
.run(|app_handle, event| {
if let RunEvent::WindowEvent {
label,
event: WindowEvent::CloseRequested { .. },
..
} = &event
{
if label == "main" {
log::info!("Main window close requested - performing cleanup");

// Save window state before closing
#[cfg(desktop)]
{
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
if let Err(e) = app_handle.save_window_state(StateFlags::all()) {
log::warn!("Failed to save window state: {e}");
} else {
log::info!("Window state saved successfully");
}
}

// Hide the quick-pane panel before main window closes
#[cfg(target_os = "macos")]
{
use tauri_nspanel::ManagerExt;
if let Ok(panel) = app_handle.get_webview_panel("quick-pane") {
log::debug!("Hiding quick-pane panel before close");
panel.hide();
}
}

// Unregister global shortcuts
#[cfg(desktop)]
{
use tauri_plugin_global_shortcut::GlobalShortcutExt;
if let Err(e) = app_handle.global_shortcut().unregister_all() {
log::warn!("Failed to unregister global shortcuts: {e}");
} else {
log::debug!("Global shortcuts unregistered");
}
}

log::info!("Cleanup complete, allowing close to proceed");
}
}
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check the actual file to see imports and context
cat -n src-tauri/src/lib.rs | head -50

Repository: dannysmith/tauri-template

Length of output: 2202


🌐 Web query:

tauri_nspanel v2.1 panel hide method API

💡 Result:

Short answer: tauri-nspanel v2.1 doesn't expose a dedicated panel.hide() — use panel.show()/show_and_make_key() to show, and close() (via the panel -> window conversion) to hide/close the panel. If you need the panel fully released, call panel.set_released_when_closed(true) before closing. See README/docs. [1][2]

Sources:

  • README usage (show, show_and_make_key, close via to_window()). [1]
  • PanelBuilder API docs (builder options including released_when_closed). [2]

🏁 Script executed:

# Search for tauri_nspanel usage in the codebase
rg -n "tauri_nspanel" --type rust -A 3 -B 3

Repository: dannysmith/tauri-template

Length of output: 1513


🏁 Script executed:

# Check for panel.hide() or alternative methods in the codebase
rg -n "panel\.(hide|order_out|close|order_front)" --type rust -B 2 -A 2

Repository: dannysmith/tauri-template

Length of output: 1230


🌐 Web query:

Tauri v2 .build().run() event loop pattern documentation

💡 Result:

Short answer — use the Builder::build(...).run(...) callback which receives the AppHandle and RunEvent and matches on RunEvent variants (ExitRequested, WindowEvent, Ready, etc.). Typical pattern:

  • Configure with tauri::Builder::default().setup(...) (and other builders).
  • Call .build(tauri::generate_context!()).
  • Call .run(|app_handle, event| match event { ... }) to handle the event loop synchronously in-process (or use run_return / run_iteration variants when appropriate).

Examples and docs:

  • Official Tauri v2 "Calling Rust from the Frontend" / event-listening examples (setup + listen + .run usage). (v2.tauri.app)
  • tauri crate docs (App.run / run_return / run_iteration and RunEvent API). (docs.rs)
  • Plugin Builder.on_event example showing on_event callbacks for the same RunEvent variants. (docs.rs)

If you want, I can paste a minimal example main.rs using the .build().run(|app_handle, event| ...) pattern.

Citations:


Replace panel.hide() with the correct tauri_nspanel v2.1 API

The panel.hide() call does not exist in tauri_nspanel v2.1 documentation. According to the official API, hiding a panel should use panel.close() (via to_window() conversion if needed) or another supported method. This affects lines 169 in this file and also lines 99 and 281 in src-tauri/src/commands/quick_pane.rs. Replace all panel.hide() calls with the correct API method from the tauri_nspanel documentation.

The .build(...).run(...) pattern is correct for Tauri v2, and the cleanup sequence (state → panel → shortcuts) is logically sound.

🤖 Prompt for AI Agents
In `@src-tauri/src/lib.rs` around lines 140 - 187, The call to panel.hide() uses a
removed/nonexistent API; update calls obtained via
app_handle.get_webview_panel("quick-pane") to use the v2.1 API by converting the
panel to a Window and closing it (e.g., replace panel.hide() with
panel.to_window().close() or panel.close() per tauri_nspanel v2.1 semantics),
and make the same replacement for all occurrences (e.g., the panel.hide() uses
in the RunEvent handler and in the quick_pane command functions).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant