Skip to content

feat(darwin): Add NSPanel support for Spotlight-like windows#5024

Open
Grantmartin2002 wants to merge 21 commits intowailsapp:v3-alphafrom
Grantmartin2002:v3-alpha-feature/issues-5023
Open

feat(darwin): Add NSPanel support for Spotlight-like windows#5024
Grantmartin2002 wants to merge 21 commits intowailsapp:v3-alphafrom
Grantmartin2002:v3-alpha-feature/issues-5023

Conversation

@Grantmartin2002
Copy link
Copy Markdown

@Grantmartin2002 Grantmartin2002 commented Feb 27, 2026

This adds support for creating NSPanel-based windows instead of NSWindow, enabling auxiliary windows that can appear over fullscreen apps without activating the application.

Key changes

  • Add WindowClass MacWindowClass to MacWindow configuration with MacWindowClassWindow (default) and MacWindowClassPanel options
  • Add MacPanelPreferences struct with panel-specific options:
    • FloatingPanel - float above other windows
    • BecomesKeyOnlyIfNeeded - become key only when needed
    • NonactivatingPanel - receive input without activating the app
    • UtilityWindow - utility window style
  • Implement WebviewPanel class (NSPanel subclass) with same functionality as WebviewWindow
  • Add panelNew() function to create panels with configurable style masks

The NSWindowStyleMaskNonactivatingPanel style allows the panel to receive keyboard input without activating the owning application.

Fixes #5023

Type of change

Please select the option that is relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

  • Windows
  • macOS
  • Linux
┌──────────────────────────────────────────────────┐
| Name          | MacOS                            |
| Version       | 15.7.1                           |
| ID            | 24G231                           |
| Branding      | Sequoia                          |
| Platform      | darwin                           |
| Architecture  | arm64                            |
| Apple Silicon | true                             |
| CPU           | Apple M4 Pro                     |
| CPU 1         | Apple M4 Pro                     |
| CPU 2         | Apple M4 Pro                     |
| GPU           | 20 cores, Metal Support: Metal 3 |
| Memory        | 48 GB                            |
└──────────────────────────────────────────────────┘

# Build Environment 

┌────────────────────────────────┐
| Wails CLI    | v3.0.0-alpha.71 |
| Go Version   | go1.25.1        |
| -buildmode   | exe             |
| -compiler    | gc              |
| CGO_CFLAGS   |                 |
| CGO_CPPFLAGS |                 |
| CGO_CXXFLAGS |                 |
| CGO_ENABLED  | 1               |
| CGO_LDFLAGS  |                 |
| GOARCH       | arm64           |
| GOARM64      | v8.0            |
| GOOS         | darwin          |
└────────────────────────────────┘

# Dependencies 

┌───────────────────────────────────────────────────────────────────────────────────┐
| Xcode cli tools | 2410                                                            |
| npm             | 11.5.1                                                          |
| *NSIS           | v3.11                                                           |
| docker          | *Docker version 28.2.2, build e6534b4 (cross-compilation ready) |
|                                                                                   |
└───────────────────────────── * - Optional Dependency ─────────────────────────────┘

# Checking for issues 

 SUCCESS  No issues found

# Diagnosis 

 SUCCESS  Your system is ready for Wails development!

Need documentation? Run: wails3 docs
 ♥   If Wails is useful to you or your company, please consider sponsoring the project: wails3 sponsor

Checklist:

  • I have updated website/src/pages/changelog.mdx with details of this PR
  • My code follows the general coding style of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Summary by CodeRabbit

  • New Features

    • Added macOS Panel (NSPanel) as an alternative window class with per-window selection.
    • Added panel preferences: floating, becomes-key-only-if-needed, non-activating, utility.
    • Unified webview configuration and improved keyboard event handling for windows and panels.
  • Documentation

    • Changelog and docs updated with Panel options, examples (including a Spotlight-like panel), and usage guidance.

grantmartin2002-oss and others added 3 commits February 27, 2026 15:27
This adds support for creating NSPanel-based windows instead of NSWindow,
enabling Spotlight-like auxiliary windows that can appear over fullscreen
apps without yanking the user out of fullscreen.

Key changes:
- Add UsePanel and PanelOptions to MacWindow configuration
- Implement WebviewPanel class (NSPanel subclass) with same functionality as WebviewWindow
- Add panelNew() function to create panels with NonactivatingPanel style
- Override sendEvent: on both WebviewWindow and WebviewPanel to ensure
  KeyBindings work regardless of WKWebView first responder state
- Add collection behavior and window level configuration options

The NSPanel with NSWindowStyleMaskNonactivatingPanel allows the panel to
receive keyboard input without activating the owning application, which
is the key feature for Spotlight/Alfred-like interfaces.

Fixes wailsapp#5023

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

coderabbitai bot commented Feb 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds macOS NSPanel support: new WebviewPanel (NSPanel subclass) with keyboard interception, C API panelNew(...), Go-side branching to call panelNew when WindowClass == MacWindowClassPanel, and new Mac.WindowClass / Mac.PanelPreferences options surfaced in docs and changelog.

Changes

Cohort / File(s) Summary
Core Panel Objective-C
v3/pkg/application/webview_window_darwin.h, v3/pkg/application/webview_window_darwin.m
Adds WebviewPanel (NSPanel subclass) and shared key-event utilities; implements sendEvent/keyDown interception, responder methods, delegate management, and mirrors WebviewWindow behavior for panels.
C & Go window creation
v3/pkg/application/webview_window_darwin.go
Adds panelNew C export and PanelPreferences struct, centralizes WKWebView configuration helper, and branches run logic to call panelNew for panel windows (passes panel preferences).
Options / API surface
v3/pkg/application/webview_window_options.go
Adds MacWindowClass enum (MacWindowClassWindow, MacWindowClassPanel) and MacPanelPreferences with FloatingPanel, BecomesKeyOnlyIfNeeded, NonactivatingPanel, UtilityWindow fields on MacWindow.
Docs & Changelog
website/src/pages/changelog.mdx, docs/src/content/docs/features/windows/options.mdx
Documents NSPanel support, adds examples (Spotlight-like panel), documents WindowClass and PanelPreferences, and notes activation/fullscreen behavior differences.

Sequence Diagram(s)

sequenceDiagram
    participant App as Go App
    participant Router as Window Creation Router
    participant PanelC as panelNew (C)
    participant Panel as WebviewPanel
    participant Delegate as Window Delegate
    participant WebKit as WKWebView

    App->>Router: Request window with WindowClass=Panel
    Router->>PanelC: Call panelNew with PanelPreferences + WebviewPreferences
    PanelC->>Panel: Instantiate WebviewPanel with styleMask & panel flags
    PanelC->>WebKit: Create WKWebView and apply WebviewPreferences
    Panel->>Panel: Embed WKWebView into content view
    PanelC->>Delegate: Set delegate and register drag types
    PanelC->>App: Return panel/window identifier

    Note over Panel: Keyboard event flow
    App->>Panel: User keydown
    Panel->>Panel: sendEvent -> intercept keydown
    Panel->>Panel: keyDown -> keyStringFromEvent
    Panel->>Delegate: processWindowKeyDownEvent(mod+key)
    Delegate->>App: Route to Go-side handler
Loading
sequenceDiagram
    participant Comparison as Creation Paths

    rect rgba(100,150,200,0.5)
        Note over Comparison: windowNew (NSWindow)
        participant WindowC as windowNew (C)
        participant Window as WebviewWindow
        WindowC->>Window: Create NSWindow, set WKWebView, delegate
    end

    rect rgba(150,100,200,0.5)
        Note over Comparison: panelNew (NSPanel) — NEW
        participant PanelC2 as panelNew (C)
        participant Panel2 as WebviewPanel
        PanelC2->>Panel2: Create NSPanel with panel-specific flags, set WKWebView, delegate
    end

    Note over Comparison: Both converge on delegate, WKWebView setup, and keyboard interception
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Enhancement, Documentation, MacOS, v3-alpha, size:XXL

Suggested reviewers

  • leaanthony
  • atterpac

Poem

🐰 I hop in code with a tiny panel bright,
Floating above full-screen, soft as moonlight.
Keys click and events dance through my fur,
Spotlight pops open — I give a little purr.
A rabbit cheers: panels take flight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive The sendEvent: override on both WebviewWindow and WebviewPanel provides key binding functionality regardless of first responder state, which extends beyond core NSPanel support and the author explicitly requests feedback on potential side effects. Clarify whether the sendEvent: override approach is necessary or if an optional configuration flag would be more appropriate to avoid unintended keyboard input conflicts in text fields.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: adding NSPanel support for Spotlight-like windows on macOS.
Linked Issues check ✅ Passed The PR successfully implements NSPanel support with nonactivating panel style (NSWindowStyleMaskNonactivatingPanel) to allow panels to receive keyboard input without activating the app, fully addressing the requirements in issue #5023.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The pull request description is comprehensive and well-structured, addressing all key template requirements including issue reference, type of change, testing details, and checklist completion.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@Grantmartin2002 Grantmartin2002 force-pushed the v3-alpha-feature/issues-5023 branch from d8ef125 to ffe6e91 Compare February 28, 2026 00:18
@Grantmartin2002 Grantmartin2002 marked this pull request as ready for review February 28, 2026 00:41
Copy link
Copy Markdown
Contributor

@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: 3

🧹 Nitpick comments (2)
v3/pkg/application/webview_window_darwin.m (1)

279-411: Extract shared key mapping/serialization for window + panel.

WebviewPanel and WebviewWindow now duplicate the full key parsing map. Consider a shared helper to avoid future divergence.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v3/pkg/application/webview_window_darwin.m` around lines 279 - 411, Both
WebviewPanel and WebviewWindow duplicate the full key parsing/map logic; extract
the mapping and serialization into a shared helper to prevent divergence. Create
a shared utility (e.g., KeyMapper or WebviewKeyHelper) that exports a method
like keyStringFromEvent:(NSEvent*)event and a serializer that builds the
"modifier+key" string (used by keyDown:); replace the local implementations in
WebviewWindow (keyStringFromEvent and keyDown:) and the analogous methods in
WebviewPanel to call the shared helper, keeping existing method names (keyDown:,
keyStringFromEvent:) as thin wrappers so callers/delegates (e.g.,
WebviewWindowDelegate, processWindowKeyDownEvent) continue to work unchanged.
v3/pkg/application/webview_window_darwin.go (1)

183-289: panelNew() and windowNew() are now heavily duplicated.

This is likely to drift over time (preferences, delegates, drag-drop, and webview setup). Consider extracting a shared builder/helper for common setup and keep only style-mask/class-specific bits separate.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v3/pkg/application/webview_window_darwin.go` around lines 183 - 289,
panelNew() and windowNew() contain nearly identical webview/delegate/preferences
setup (WebviewWindowDelegate, WKWebViewConfiguration config,
WKUserContentController, WKWebView webView, drag/drop wiring), so extract the
common logic into a shared helper (eg. configureWebviewContent or
buildWebviewForContainer) that accepts the container view/panel/window, id,
WebviewPreferences, fraudulentWebsiteWarningEnabled, enableDragAndDrop and
returns or assigns the configured WKWebView and delegate; leave only the style
mask and panel-specific bits (frameless, floatingPanel, WebviewPanel creation)
in panelNew(), and call the new helper to set preferences, set URL scheme
handler, userContentController setup, assign navigation/UIDelegate, and attach
drag view when requested.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@v3/pkg/application/webview_window_darwin.go`:
- Around line 202-203: The panel's floating state set in panelNew() via [panel
setFloatingPanel:YES] is being overridden later by an unconditional [window
setLevel:MacWindowLevelNormal] call; update the logic that sets default window
level (the code paths around setWindowLevel / setLevel and MacWindowLevelNormal)
to skip applying a default level when WindowClass == NSPanel and
PanelOptions.FloatingPanel is true (or alternatively detect when
setFloatingPanel was explicitly set and avoid calling setLevel), ensuring
panelNew's setFloatingPanel remains effective; look for functions/methods named
panelNew, setFloatingPanel, setWindowLevel/setLevel, and references to
PanelOptions.FloatingPanel, WindowClass/NSPanel and MacWindowLevelNormal to
apply the conditional.

In `@v3/pkg/application/webview_window_darwin.m`:
- Around line 33-41: sendEvent: is causing duplicate key handling by
unconditionally invoking [self keyDown:event] before [super sendEvent:event];
update the sendEvent: implementations in WebviewWindow and WebviewPanel to only
call [self keyDown:event] when the window's firstResponder is not the WKWebView
(or a subview of it) — i.e., obtain the firstResponder from the window and skip
the manual keyDown dispatch if it isKindOfClass: WKWebView (or resides within
the WKWebView), otherwise call [self keyDown:event] and then [super
sendEvent:event] as before.

In `@website/src/pages/changelog.mdx`:
- Line 18: Fix the spelling typo in the changelog entry: replace the misspelled
word "auxillary" with "auxiliary" in the sentence describing NSPanel support
(the line mentioning "Panels serve as auxillary windows..."); ensure the rest of
the text (references to `NSWindowStyleMaskNonactivatingPanel`,
`Mac.WindowClass`, and `Mac.PanelOptions`) remains unchanged.

---

Nitpick comments:
In `@v3/pkg/application/webview_window_darwin.go`:
- Around line 183-289: panelNew() and windowNew() contain nearly identical
webview/delegate/preferences setup (WebviewWindowDelegate,
WKWebViewConfiguration config, WKUserContentController, WKWebView webView,
drag/drop wiring), so extract the common logic into a shared helper (eg.
configureWebviewContent or buildWebviewForContainer) that accepts the container
view/panel/window, id, WebviewPreferences, fraudulentWebsiteWarningEnabled,
enableDragAndDrop and returns or assigns the configured WKWebView and delegate;
leave only the style mask and panel-specific bits (frameless, floatingPanel,
WebviewPanel creation) in panelNew(), and call the new helper to set
preferences, set URL scheme handler, userContentController setup, assign
navigation/UIDelegate, and attach drag view when requested.

In `@v3/pkg/application/webview_window_darwin.m`:
- Around line 279-411: Both WebviewPanel and WebviewWindow duplicate the full
key parsing/map logic; extract the mapping and serialization into a shared
helper to prevent divergence. Create a shared utility (e.g., KeyMapper or
WebviewKeyHelper) that exports a method like keyStringFromEvent:(NSEvent*)event
and a serializer that builds the "modifier+key" string (used by keyDown:);
replace the local implementations in WebviewWindow (keyStringFromEvent and
keyDown:) and the analogous methods in WebviewPanel to call the shared helper,
keeping existing method names (keyDown:, keyStringFromEvent:) as thin wrappers
so callers/delegates (e.g., WebviewWindowDelegate, processWindowKeyDownEvent)
continue to work unchanged.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 972bb6f and cc5af72.

📒 Files selected for processing (5)
  • v3/pkg/application/webview_window_darwin.go
  • v3/pkg/application/webview_window_darwin.h
  • v3/pkg/application/webview_window_darwin.m
  • v3/pkg/application/webview_window_options.go
  • website/src/pages/changelog.mdx

Comment thread v3/pkg/application/webview_window_darwin.go Outdated
Comment thread v3/pkg/application/webview_window_darwin.m Outdated
Comment thread website/src/pages/changelog.mdx Outdated
Copy link
Copy Markdown
Contributor

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/src/content/docs/features/windows/options.mdx (1)

831-864: ⚠️ Potential issue | 🔴 Critical

Unescaped {} in prose will break MDX parsing (lines 831, 864).

In Astro Starlight .mdx files, a left curly brace { starts a JavaScript expression. Wrap these code references in backticks to render them literally.

Proposed fix
-**Note:** NSWindow fullscreen overlay requires application.Options{ Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory } }
+**Note:** NSWindow fullscreen overlay requires `application.Options{ Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory } }`

-**Note:** Unlike NSWindow, NSPanel fullscreen overlay does NOT require application.Options{ Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory } }
+**Note:** Unlike NSWindow, NSPanel fullscreen overlay does **not** require `application.Options{ Mac: application.MacOptions{ ActivationPolicy: application.ActivationPolicyAccessory } }`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/src/content/docs/features/windows/options.mdx` around lines 831 - 864,
The MDX contains unescaped curly-brace expressions that break parsing; wrap the
literal references containing braces in inline code ticks (e.g.,
`application.Options{ Mac: application.MacOptions{ ActivationPolicy:
application.ActivationPolicyAccessory } }`) and similarly backtick the examples
and type names that include braces or look like struct literals (references such
as `Mac: application.MacWindow{ ... }`,
`application.MacWindowCollectionBehaviorCanJoinAllSpaces |
application.MacWindowCollectionBehaviorFullScreenAuxiliary`,
`application.MacWindowLevelFloating`, `MacWindowClass`, `NSWindow`, `NSPanel`,
`MacPanelOptions`, and the panel option names) so the `{}` are rendered
literally and MDX parsing is preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@docs/src/content/docs/features/windows/options.mdx`:
- Around line 831-864: The MDX contains unescaped curly-brace expressions that
break parsing; wrap the literal references containing braces in inline code
ticks (e.g., `application.Options{ Mac: application.MacOptions{
ActivationPolicy: application.ActivationPolicyAccessory } }`) and similarly
backtick the examples and type names that include braces or look like struct
literals (references such as `Mac: application.MacWindow{ ... }`,
`application.MacWindowCollectionBehaviorCanJoinAllSpaces |
application.MacWindowCollectionBehaviorFullScreenAuxiliary`,
`application.MacWindowLevelFloating`, `MacWindowClass`, `NSWindow`, `NSPanel`,
`MacPanelOptions`, and the panel option names) so the `{}` are rendered
literally and MDX parsing is preserved.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc5af72 and 3884498.

📒 Files selected for processing (1)
  • docs/src/content/docs/features/windows/options.mdx

grantmartin2002-oss and others added 3 commits March 2, 2026 16:10
- Fix FloatingPanel level override: skip setting default WindowLevel
  when NSPanel has FloatingPanel=true, preserving setFloatingPanel
  behavior
- Clean up sendEvent override comments
- Fix typo in changelog (auxillary -> auxiliary)

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

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@v3/pkg/application/webview_window_darwin.go`:
- Around line 1365-1371: Calls to w.setAlwaysOnTop(options.AlwaysOnTop) are
overwriting a floating panel's level (setting NSNormalWindowLevel when
AlwaysOnTop=false) and undoing PanelOptions.FloatingPanel; update the logic so
setAlwaysOnTop does not force MacWindowLevelNormal for windows that are panels
with PanelOptions.FloatingPanel==true (i.e., when macOptions.WindowClass ==
NSPanel && macOptions.PanelOptions.FloatingPanel). Concretely, change the places
that call w.setAlwaysOnTop (the two sites that currently force normal level) to
check the same guard used for setWindowLevel (or update setAlwaysOnTop to
early-return when the window is a floating panel) so floating panels retain
NSFloatingWindowLevel.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3884498 and 58299cc.

📒 Files selected for processing (4)
  • docs/src/content/docs/features/windows/options.mdx
  • v3/pkg/application/webview_window_darwin.go
  • v3/pkg/application/webview_window_darwin.m
  • website/src/pages/changelog.mdx
🚧 Files skipped from review as they are similar to previous changes (2)
  • website/src/pages/changelog.mdx
  • docs/src/content/docs/features/windows/options.mdx

Comment thread v3/pkg/application/webview_window_darwin.go
Copy link
Copy Markdown
Member

@leaanthony leaanthony left a comment

Choose a reason for hiding this comment

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

Hey @Grantmartin2002 👋

Thanks for this! NSPanel support is something people have been asking about and this is a solid start. Really nice job on the docs too — the Spotlight-like panel example and the note about ActivationPolicyAccessory not being needed for panels is great 🙏

I've got some feedback before we can merge this:

1. sendEvent: override on WebviewWindow is a breaking change

This is the big one. The sendEvent: override is being added to both WebviewPanel and WebviewWindow. On WebviewWindow this changes behavior for all existing users — every keystroke will now fire Go keybindings regardless of first responder state, which means typing in a <input> field would also trigger keybindings. You called this out yourself in the PR description (good instinct!).

This needs to be opt-in, not default. Please remove the sendEvent: override from WebviewWindow (keep the existing keyDown: behavior). For WebviewPanel it's fine since it's new, but ideally add an option for it there too so users can control it.

2. Double-dispatch of key events

With sendEvent: calling [self keyDown:event] and then [super sendEvent:event], the normal event dispatch chain will call keyDown: again when the window is first responder — so processWindowKeyDownEvent fires twice per keystroke.

If you keep sendEvent: (for panels), don't call [super sendEvent:event] after handling key-down events — that should prevent the double-fire.

3. Inconsistent memory management

In windowNew() the delegate gets autoreleased, but WebviewPanel has a custom setDelegate: that does retain/release and a dealloc cleanup. These two patterns conflict — the autorelease in panelNew() combined with the retain in setDelegate: will over-retain.

I'd prefer we stick with the autorelease pattern consistently unless you have a specific reason for the explicit retain/release approach? Also worth noting that WebviewWindow is missing the userContentController cleanup that you added to WebviewPanel.dealloc — that's likely a pre-existing leak, so nice catch if intentional!

4. Rename NSWindow/NSPanel constants

Using NSWindow and NSPanel as Go constant names masks the actual Cocoa class names, which gets confusing. Please rename these — something like MacWindowClassWindow and MacWindowClassPanel would work, or whatever feels right as long as they don't shadow the Cocoa types.

5. Code duplication in .m — consider a protocol or macro

The WebviewPanel implementation is nearly identical to WebviewWindow (init, responder methods, etc). The refactoring of keyStringFromEventkeyStringFromKeyEvent and dispatchKeyDownEvent as static helpers was a good move. Could you look into doing something similar for the remaining duplicated bits? Even just a comment acknowledging the duplication with a TODO would be fine if it's tricky to extract cleanly.

6. panelNew has a lot of bool parameters

void* panelNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, 
               bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences,
               bool floatingPanel, bool becomesKeyOnlyIfNeeded, bool nonactivatingPanel, bool utilityWindow)

4 trailing bools is easy to get wrong at call sites. Could you wrap the panel-specific ones in a struct PanelPreferences (similar to the existing WebviewPreferences)? That way the call site is self-documenting.

7. Docs

Great job here overall! Will just need a minor update once the constant names change (point 4).


Happy to answer any questions on these. The core feature is really useful — just want to make sure we get the details right before it lands. Thanks again for the contribution! 🙌

Copy link
Copy Markdown
Contributor

@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

♻️ Duplicate comments (1)
v3/pkg/application/webview_window_darwin.go (1)

1467-1479: ⚠️ Potential issue | 🟠 Major

Floating NSPanel is still reset to normal level after first navigation.

Line 1469 and Line 1479 call w.setAlwaysOnTop(options.AlwaysOnTop) unconditionally. When AlwaysOnTop is false, windowSetAlwaysOnTop forces NSNormalWindowLevel, which undoes a previously configured floating panel level.

🛠️ Suggested fix
 		// We need to wait for the HTML to load before we can execute the javascript
+		shouldApplyAlwaysOnTop := !(macOptions.WindowClass == MacWindowClassPanel &&
+			macOptions.PanelPreferences.FloatingPanel.IsSet() &&
+			macOptions.PanelPreferences.FloatingPanel.Get() &&
+			!options.AlwaysOnTop)
+
 		w.parent.OnWindowEvent(events.Mac.WebViewDidFinishNavigation, func(_ *WindowEvent) {
 			InvokeAsync(func() {
@@
 				if !options.Hidden {
 					w.parent.Show()
 					w.setHasShadow(!options.Mac.DisableShadow)
-					w.setAlwaysOnTop(options.AlwaysOnTop)
+					if shouldApplyAlwaysOnTop {
+						w.setAlwaysOnTop(options.AlwaysOnTop)
+					}
 				} else {
@@
 							}
 							w.setHasShadow(!options.Mac.DisableShadow)
-							w.setAlwaysOnTop(options.AlwaysOnTop)
+							if shouldApplyAlwaysOnTop {
+								w.setAlwaysOnTop(options.AlwaysOnTop)
+							}
 							cancel()
 						})
 					})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v3/pkg/application/webview_window_darwin.go` around lines 1467 - 1479, The
floating NSPanel level is being reset because
w.setAlwaysOnTop(options.AlwaysOnTop) is called unconditionally; when
options.AlwaysOnTop is false the underlying windowSetAlwaysOnTop forces
NSNormalWindowLevel and overwrites a floating panel level. Fix by only invoking
w.setAlwaysOnTop(...) when options.AlwaysOnTop is true (i.e., guard both calls
at the top-level and inside the OnWindowEvent cancel handler), leaving the
window level untouched when AlwaysOnTop is false so previously configured
floating panel levels are preserved; update references around the calls to
w.setAlwaysOnTop and any logic that depends on windowSetAlwaysOnTop accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@v3/pkg/application/webview_window_options.go`:
- Around line 492-507: The comments use the term "PanelOptions" but the exported
type/field is named "PanelPreferences" (see MacPanelPreferences and any
references in the MacWindowClass comment block); update all comment text that
mentions "PanelOptions" to "PanelPreferences" so the documentation matches the
actual exported symbol names (e.g., change "PanelOptions contains options for
NSPanel windows" to "PanelPreferences contains options for NSPanel windows" and
any other occurrences).

---

Duplicate comments:
In `@v3/pkg/application/webview_window_darwin.go`:
- Around line 1467-1479: The floating NSPanel level is being reset because
w.setAlwaysOnTop(options.AlwaysOnTop) is called unconditionally; when
options.AlwaysOnTop is false the underlying windowSetAlwaysOnTop forces
NSNormalWindowLevel and overwrites a floating panel level. Fix by only invoking
w.setAlwaysOnTop(...) when options.AlwaysOnTop is true (i.e., guard both calls
at the top-level and inside the OnWindowEvent cancel handler), leaving the
window level untouched when AlwaysOnTop is false so previously configured
floating panel levels are preserved; update references around the calls to
w.setAlwaysOnTop and any logic that depends on windowSetAlwaysOnTop accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: eb44af40-07ad-41ad-88d6-82c534a6ca52

📥 Commits

Reviewing files that changed from the base of the PR and between 58299cc and e82c1a7.

📒 Files selected for processing (2)
  • v3/pkg/application/webview_window_darwin.go
  • v3/pkg/application/webview_window_options.go

Comment thread v3/pkg/application/webview_window_options.go Outdated
Copy link
Copy Markdown
Contributor

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

♻️ Duplicate comments (1)
v3/pkg/application/webview_window_darwin.go (1)

1379-1385: ⚠️ Potential issue | 🟠 Major

Floating panels can still be downgraded during initial show flow.

The guard here helps, but w.setAlwaysOnTop(options.AlwaysOnTop) at Line 1455 and Line 1465 still sets normal level when AlwaysOnTop == false, which cancels PanelPreferences.FloatingPanel == true.

Suggested fix
@@
-		w.parent.OnWindowEvent(events.Mac.WebViewDidFinishNavigation, func(_ *WindowEvent) {
+		shouldApplyAlwaysOnTop := !(macOptions.WindowClass == MacWindowClassPanel &&
+			macOptions.PanelPreferences.FloatingPanel &&
+			!options.AlwaysOnTop)
+
+		w.parent.OnWindowEvent(events.Mac.WebViewDidFinishNavigation, func(_ *WindowEvent) {
@@
 				if !options.Hidden {
 					w.parent.Show()
 					w.setHasShadow(!options.Mac.DisableShadow)
-					w.setAlwaysOnTop(options.AlwaysOnTop)
+					if shouldApplyAlwaysOnTop {
+						w.setAlwaysOnTop(options.AlwaysOnTop)
+					}
 				} else {
@@
 							}
 							w.setHasShadow(!options.Mac.DisableShadow)
-							w.setAlwaysOnTop(options.AlwaysOnTop)
+							if shouldApplyAlwaysOnTop {
+								w.setAlwaysOnTop(options.AlwaysOnTop)
+							}
 							cancel()
 						})
 					})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@v3/pkg/application/webview_window_darwin.go` around lines 1379 - 1385,
w.setAlwaysOnTop currently forces the window level to normal when
options.AlwaysOnTop is false, which overrides a floating panel set earlier;
change the logic in the code that calls w.setAlwaysOnTop (the branch handling
macOptions.AlwaysOnTop) so it does not downgrade a panel with
macOptions.WindowClass == MacWindowClassPanel and
macOptions.PanelPreferences.FloatingPanel == true (or when the current level is
already a floating level). Instead, only raise the level when AlwaysOnTop is
true, and skip setting the normal level when AlwaysOnTop is false for floating
panels — keep using setWindowLevel only when a change is actually needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@v3/pkg/application/webview_window_darwin.go`:
- Around line 1379-1385: w.setAlwaysOnTop currently forces the window level to
normal when options.AlwaysOnTop is false, which overrides a floating panel set
earlier; change the logic in the code that calls w.setAlwaysOnTop (the branch
handling macOptions.AlwaysOnTop) so it does not downgrade a panel with
macOptions.WindowClass == MacWindowClassPanel and
macOptions.PanelPreferences.FloatingPanel == true (or when the current level is
already a floating level). Instead, only raise the level when AlwaysOnTop is
true, and skip setting the normal level when AlwaysOnTop is false for floating
panels — keep using setWindowLevel only when a change is actually needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5fde02ee-d1ac-4858-96df-43b1add07fd7

📥 Commits

Reviewing files that changed from the base of the PR and between e82c1a7 and 267bed3.

📒 Files selected for processing (4)
  • docs/src/content/docs/features/windows/options.mdx
  • v3/pkg/application/webview_window_darwin.go
  • v3/pkg/application/webview_window_options.go
  • website/src/pages/changelog.mdx

@Grantmartin2002
Copy link
Copy Markdown
Author

Grantmartin2002 commented Mar 6, 2026

@leaanthony Thank you for the thorough review! Few things:

1. sendEvent: override on WebviewWindow is a breaking change

Agreed. Did it originally to keep behavior consistent and considered an opt in option, but honestly not necessary. will remove

2. Double-dispatch of key events

  • This send event override is only necessary when NonactivatingPanel is set to true. Something about the event propagation changes with this setting, causing my keybinds to no longer work without the override
  • I do not observe your alleged double dispatch for panels. dispatchKeyDownEvent is called once. Tested with nonactivatingpanel attribute both on and off.
  • One (mild/moderate) problem this double event does cause though is, if called within text input, the text input and the WKWebView will both handle the event. ie, if i have a keybind on a, and type a, both the keybind logic and textinput typing will occur. This differs from window behavior (without sendevent override), which does NOT type the letter and only activates the keybind logic. I've spent a few hours exploring alternatives to replicate the window logic exactly, but haven't found a clean, consistent way that accounts for all edge cases.
  • If we do not call [super sendEvent:event], then keys no longer work in text input fields.

3. Inconsistent memory management

can you point me to the exact lines you are referring to? Memory management looks identical to me

4. Rename NSWindow/NSPanel constants

Done

5. Code duplication in .m — consider a protocol or macro

I should be able to do something for consolodiating panelNew and windowNew (EDIT: DONE). For the interface implementation, I could use a macro, but honestly its not that much duplicated code here. Probably not worth it due to readibility issues imo. What do you think? Will do what you think best :)

6. panelNew has a lot of bool parameters

Done. Also renamed "options" to "preferences" to pattern match.

7. Dodcs

Done.

Thank you again!!!!

Copy link
Copy Markdown
Member

@leaanthony leaanthony left a comment

Choose a reason for hiding this comment

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

Thanks for your patience on this 🙏

I've added a couple of comments on filenames but not seeing any major issues here!


@end

@interface WebviewPanel : NSPanel
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this should live in webview_panel_darwin.h

}
@end

@implementation WebviewPanel
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this should live in webview_panel_darwin.h

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

hmmm, we will have to duplicate dispatchKeyDownEvent in that case. are you ok with that?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

also, i consolidated windowPanel and windowNew to just use windowNew with a flag check to avoid having to maintain two methods if/when we add properties to general window behavior. With the change you propose, I'd also have to make a webview_panel_darwin.go right? So i'd have to separate the methods again and duplicate more logic.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not having the dupes makes sense to me

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If they are using the same build flags then it won't be dupes? I'm not sure I understand the issue here. I was only suggesting moving some of the code to different named files.

leaanthony and others added 2 commits April 14, 2026 21:13
Split WebviewPanel interface and implementation out of
webview_window_darwin.{h,m} into new webview_panel_darwin.{h,m}.
Remove `static` from the shared key event helpers so they can be
called from the panel file without duplication — one definition in
webview_window_darwin.m, declared in the shared header.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@leaanthony
Copy link
Copy Markdown
Member

Hey @Grantmartin2002 — the "dupes" concern can be resolved without any code duplication. The two helper functions (keyStringFromKeyEvent and dispatchKeyDownEvent) are static, which is why splitting files seemed to require copying them. The fix is to remove static, declare them in the shared header, and the panel file can call them via the import.

I've pushed a branch showing the approach: wailsapp/wails:v3-alpha-feature/issues-5023

Changes:

  • webview_panel_darwin.h — new file with @interface WebviewPanel : NSPanel
  • webview_panel_darwin.m — new file with @implementation WebviewPanel, imports webview_window_darwin.h and calls dispatchKeyDownEvent directly (no copy)
  • webview_window_darwin.h — removed WebviewPanel interface, added declarations for the two shared helpers
  • webview_window_darwin.m — removed static from both helpers, removed @implementation WebviewPanel
  • webview_window_darwin.go — added #include "webview_panel_darwin.h" to the CGo preamble

Feel free to pull those commits into your fork if you want to keep this PR going, or I can merge from there.

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.

3 participants