feat(v3,macos): NSWindow tabbing mode configuration - #5708
Conversation
- Add MacWindowTabbingMode type and constants to webview_window_options.go - Add TabbingMode field to MacWindow struct - Implement windowSetTabbingMode C function with macOS 10.12+ availability check - Apply tabbing mode in macosWebviewWindow.run() method - Add test for MacWindowTabbingMode constants
…wed, use iota constants
…c/wails-fork into v3-alpha-feature/mac-window-tabbing
…mac-window-tabbing # Conflicts: # docs/src/content/docs/features/windows/options.mdx
# Conflicts: # docs/src/content/docs/features/windows/options.mdx
|
Caution Review failedAn error occurred during the review process. Please try again later. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
NSWindow tabbing is a macOS-only API with no Android/iOS equivalent, so the build/android + build/ios scaffolding (auto-generated by wails3 init) is irrelevant boilerplate. Remove both dirs (45 files), their Taskfile includes, and the trailing ios:device tasks — matching the desktop-only convention used by other examples (e.g. notifications: windows/darwin/linux only).
There was a problem hiding this comment.
Actionable comments posted: 14
🧹 Nitpick comments (10)
v3/examples/mac-window-tabs/frontend/package.json (1)
12-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider pinning
@wailsio/runtimeinstead of"latest".Using
"latest"makes builds non-reproducible across time; pinning to the current runtime version used elsewhere in the repo would improve consistency.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/examples/mac-window-tabs/frontend/package.json` around lines 12 - 14, The package.json dependency for `@wailsio/runtime` is using "latest", which makes installs non-reproducible. Update the dependency in the dependencies block to a pinned version that matches the runtime version used elsewhere in the repo, so this frontend stays consistent with the rest of the codebase.v3/examples/mac-window-tabs/frontend/public/style.css (2)
56-59: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicate
.resultselector.The
.resultrule is declared twice; the second declaration (Lines 103-108) overrides/duplicates the first (Lines 56-59). Consolidate into one rule.Also applies to: 103-108
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/examples/mac-window-tabs/frontend/public/style.css` around lines 56 - 59, The `.result` CSS rule is duplicated, with a second declaration later in the stylesheet overriding the first; consolidate the styles into a single `.result` block and remove the redundant one so the selector is defined only once. Update the shared `.result` rule in the stylesheet and keep the combined styles together to avoid conflicting declarations.
1-28: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueStylelint flags quoting/casing issues.
Static analysis reports: unnecessary quotes around font-family names (
"Inter","Roboto","Oxygen","Ubuntu","Cantarell") andoptimizeLegibilityshould be lowercased.🎨 Proposed fix
:root { - font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", - "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; ... - text-rendering: optimizeLegibility; + text-rendering: optimizelegibility; ... } `@font-face` { - font-family: "Inter"; + font-family: Inter;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/examples/mac-window-tabs/frontend/public/style.css` around lines 1 - 28, The CSS in the `:root` and `@font-face` blocks needs cleanup for Stylelint: remove unnecessary quotes from font-family values like the `font-family` stack and the `@font-face` `font-family` declaration, and change `text-rendering: optimizeLegibility` to the lowercase form expected by the linter. Update the `:root` styles in `style.css` so the font stack uses unquoted generic/custom names where allowed and the `@font-face` definition remains consistent with the same `Inter` naming.Source: Linters/SAST tools
v3/pkg/application/webview_window_darwin.go (1)
1498-1504: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDefault resolution duplicated between
run()andsetTabbingMode.
run()already normalizesMacWindowTabbingModeDefaulttoDisallowedbefore callingw.setTabbingMode, butsetTabbingMode(Line 1360-1362) performs the identical normalization again. This is harmless today but the duplicated defaulting logic could drift out of sync if only one call site is updated later.See related comment on
webview_window_options.goLines 594-609 regarding whether "Default → Disallowed" is the intended default-behavior change for this feature.♻️ Optional consolidation
- // Set tabbing mode (macOS 10.12+) - // Default to disallowed unless explicitly configured. - if macOptions.TabbingMode == MacWindowTabbingModeDefault { - macOptions.TabbingMode = MacWindowTabbingModeDisallowed - } - w.setTabbingMode(macOptions.TabbingMode) + // Set tabbing mode (macOS 10.12+); setTabbingMode normalizes the + // Default sentinel to Disallowed internally. + w.setTabbingMode(macOptions.TabbingMode)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/pkg/application/webview_window_darwin.go` around lines 1498 - 1504, The default-to-Disallowed normalization for MacWindowTabbingModeDefault is duplicated between run() and setTabbingMode, so consolidate it in one place only. Update run() and setTabbingMode in webview_window_darwin.go so that only one of them performs the Default → Disallowed fallback, and keep the other path assuming the mode is already normalized. Use the existing symbols MacWindowTabbingModeDefault, MacWindowTabbingModeDisallowed, and w.setTabbingMode to locate the logic and avoid future drift.docs/src/content/docs/features/windows/options.mdx (1)
809-809: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winDocs accurately reflect the implementation.
The new TabbingMode subsection's description of
MacWindowTabbingModeDefault"defaults to disallowing tabbing" correctly documents the implementation inwebview_window_darwin.go. See the related comment onwebview_window_options.goabout whether this default-disallow behavior (which diverges from macOS's nativeAutomaticdefault) is the intended tradeoff — if so, it may be worth calling out explicitly here that this changes default behavior versus a plain unmanagedNSWindow.Also applies to: 882-907
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/src/content/docs/features/windows/options.mdx` at line 809, The TabbingMode docs currently say `MacWindowTabbingModeDefault` “defaults to disallowing tabbing” but do not make clear that this differs from macOS’s native `Automatic` default. Update the `TabbingMode` subsection in the `options.mdx` docs to explicitly call out that the default behavior follows `application.MacWindowTabbingModeDisallowed` and is an intentional divergence from a plain unmanaged `NSWindow`, matching the implementation in `webview_window_darwin.go` and the default noted in `webview_window_options.go`.v3/examples/mac-window-tabs/build/Taskfile.yml (2)
46-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
frontend:vendor:puppertinotask appears unused.This task isn't listed as a dependency of
build:frontendor any other task in this file, so it will never run automatically. Also, sincesourcesandgeneratespoint to the exact same file, Task's checksum-based up-to-date check means once the file exists it will always be considered up-to-date and skipped, making the "attempt to fetch" logic largely dead outside the first manual run.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/examples/mac-window-tabs/build/Taskfile.yml` around lines 46 - 79, The frontend:vendor:puppertino task is effectively unreachable and self-skipping because nothing depends on it and its sources/generates point to the same file. Update the Taskfile so a relevant build task (such as build:frontend) depends on frontend:vendor:puppertino, and adjust its sources/generates to reflect the actual fetched artifact or remove the checksum trigger if the task should always attempt the fetch. Keep the task name and the build:frontend dependency wiring as the main symbols to locate and fix this.
71-78: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUnconditional
sed/awkmutation ofindex.htmlon every invocation.The script rewrites
index.htmlin-place (inserting a stylesheet link and replacingclass="btn"with Puppertino classes) whenever this task runs, with no idempotency guard beyond thegrep -qcheck for the link tag. If a developer customizesindex.htmland later re-runs this task, the blindsedreplacement ofbtnclasses could silently overwrite manual edits.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/examples/mac-window-tabs/build/Taskfile.yml` around lines 71 - 78, The Taskfile step is mutating index.html unconditionally by rewriting the file with awk/sed, which can overwrite manual changes. Update the logic around the index.html patching block to make the Puppertino injection idempotent and narrowly scoped, using the existing grep check plus an additional guard before the class replacement in the same task. In the task that edits INDEX_HTML, only transform the specific default markup when it matches the expected original content, and skip the sed replacement if the file has already been customized or already contains Puppertino classes.v3/examples/mac-window-tabs/build/config.yml (1)
62-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPlaceholder/broken doc link.
The URL
https://v3.wails.io/noit/done/yetis clearly a placeholder and not a real documentation page. This is likely inherited from the Wails scaffolding template rather than authored in this PR, but since it ships in this example's generated config, it should be corrected.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/examples/mac-window-tabs/build/config.yml` at line 62, The comment in the example config contains a placeholder Wails documentation URL that should be replaced with a real, valid link. Update the documentation reference in the generated config comment so it points to an actual Wails docs page, keeping the change within the mac-window-tabs build config example and preserving the surrounding comment structure.v3/examples/mac-window-tabs/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java (1)
47-61: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winUnbounded thread-per-call for
invokeAsync.Each
invokeAsyncspawns a brand-newThread; under bursty invoke traffic this can exhaust resources. Consider a shared boundedExecutorServiceinstead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/examples/mac-window-tabs/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java` around lines 47 - 61, The invokeAsync method in WailsJSBridge currently creates a new Thread for every call, which can exhaust resources under heavy traffic. Replace the per-call thread creation with a shared bounded ExecutorService (or similar pooled executor) and submit the message handling work there. Keep the existing async behavior in invokeAsync, but route bridge.handleMessage and sendCallback through the shared executor so concurrency is controlled.v3/examples/mac-window-tabs/build/android/app/src/main/java/com/wails/app/WailsPathHandler.java (1)
22-33: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winUnconditional debug logging.
Unlike the upstream template, this handler omits the
DEBUGflag guard onLog.d/Log.wcalls inhandle(), so every asset request is logged even in release builds.🔧 Proposed fix
public class WailsPathHandler implements WebViewAssetLoader.PathHandler { private static final String TAG = "WailsPathHandler"; + private static final boolean DEBUG = BuildConfig.DEBUG; private final WailsBridge bridge; public WailsPathHandler(WailsBridge bridge) { this.bridge = bridge; } `@Nullable` `@Override` public WebResourceResponse handle(`@NonNull` String path) { - Log.d(TAG, "Handling path: " + path); + if (DEBUG) Log.d(TAG, "Handling path: " + path); // Normalize path if (path.isEmpty() || path.equals("/")) { path = "/index.html"; } // Get asset from Go byte[] data = bridge.serveAsset(path, "GET", "{}"); if (data == null || data.length == 0) { - Log.w(TAG, "Asset not found: " + path); + if (DEBUG) Log.w(TAG, "Asset not found: " + path); return null; // Return null to let WebView handle 404 } // Determine MIME type String mimeType = bridge.getAssetMimeType(path); - Log.d(TAG, "Serving " + path + " with type " + mimeType + " (" + data.length + " bytes)"); + if (DEBUG) Log.d(TAG, "Serving " + path + " with type " + mimeType + " (" + data.length + " bytes)");Also applies to: 43-50
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/examples/mac-window-tabs/build/android/app/src/main/java/com/wails/app/WailsPathHandler.java` around lines 22 - 33, The logging in WailsPathHandler.handle() is missing the DEBUG guard, so asset requests are always written in release builds. Update the handle() method to wrap the Log.d and any Log.w calls with the same DEBUG flag check used by the upstream template, keeping the logging behavior only for debug builds while preserving the existing path handling logic in WailsPathHandler and its bridge usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@v3/examples/mac-window-tabs/build/android/app/src/main/AndroidManifest.xml`:
- Around line 8-16: The AndroidManifest application config is enabling app-wide
cleartext traffic unnecessarily. Remove the usesCleartextTraffic attribute from
the application entry in the manifest, since MainActivity.setupWebView serves
content through the virtual https://wails.localhost asset loader and does not
need permissive HTTP. Keep the rest of the application setup unchanged.
In
`@v3/examples/mac-window-tabs/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java`:
- Around line 70-89: Guard the WailsJSBridge.log() JavaScript bridge against a
null level before calling level.toLowerCase(), since `@JavascriptInterface`
callers can pass null/undefined and trigger an NPE. Update log(String level,
String message) to normalize a missing level to a safe default (for example the
existing "verbose" path) and keep the switch logic in place for the valid cases.
In `@v3/examples/mac-window-tabs/build/android/Taskfile.yml`:
- Around line 107-121: The package tasks are still producing the debug APK
instead of a production/release artifact. Update the Taskfile targets for
package and package:fat so they invoke the release Gradle variant through
assemble:apk, and make sure package:fat also passes PRODUCTION through
compile:go:all-archs. If the APK filename changes, adjust any references in
deploy-emulator and run from the debug name to the release name so the
downstream tasks use the correct artifact.
- Around line 21-45: The build task only passes ARCH into compile:go:shared, so
that nested task can ignore the parent’s PRODUCTION setting and resolve
BUILD_FLAGS incorrectly. Update the build command to forward BUILD_FLAGS or
PRODUCTION into the task: compile:go:shared invocation, using the existing
BUILD_FLAGS and PRODUCTION vars defined in build so the shared compile step
matches the Android package mode.
In `@v3/examples/mac-window-tabs/build/docker/Dockerfile.cross`:
- Around line 1-16: Add the Dockerfile frontend directive at the very top of
Dockerfile.cross so the heredoc-based COPY instructions are parsed consistently
across Docker/BuildKit versions. Update the file header before the existing FROM
line, keeping the rest of the cross-compile setup unchanged.
In `@v3/examples/mac-window-tabs/build/docker/Dockerfile.server`:
- Around line 25-41: The server image is still using the root-running distroless
base, so update the base image in Dockerfile.server to the nonroot distroless
variant while keeping the existing COPY, EXPOSE, ENV, and ENTRYPOINT behavior
unchanged. Use the current distroless stage as the anchor and switch it to the
matching nonroot image so the server container runs without root privileges.
In `@v3/examples/mac-window-tabs/build/ios/main.m`:
- Around line 16-21: `main()` is starting `WailsIOSMain()` too early and twice;
remove the asynchronous dispatch from `main.m` and let `WailsAppDelegate` invoke
`WailsIOSMain()` from `didFinishLaunchingWithOptions` as designed. Keep
`UIApplicationMain(argc, argv, nil, @"WailsAppDelegate")` as the only launch
path so Go startup happens exactly once and only after the app delegate is
ready.
In `@v3/examples/mac-window-tabs/build/ios/project.pbxproj`:
- Line 158: The prebuild shell script in the Xcode project uses the generated
archive path with a space unquoted, so the file check and build invocation can
break due to word splitting. Update the shellScript block to treat the output
path as a single argument in both the existence test and the go build command,
using the same bin/My Product.a location referenced there.
In `@v3/examples/mac-window-tabs/build/ios/Taskfile.yml`:
- Around line 6-7: The simctl bundle ID values used by the Taskfile do not match
the actual CFBundleIdentifier values shipped in the app plists, so
deploy-simulator and run can target the wrong app. Update the BUNDLE_ID default
and the hardcoded identifier in the ios tasks so they resolve to the same bundle
IDs defined in build/ios/Info.plist and Info.dev.plist. Use the existing
deploy-simulator and run task entries, along with the BUNDLE_ID var, to keep the
simulator commands aligned with the packaged app identifiers.
In `@v3/examples/mac-window-tabs/build/linux/appimage/build.sh`:
- Around line 33-35: The AppImage rename in the build script is using a quoted
glob, so `mv` cannot match the generated file from `linuxdeploy`. Update the
`mv` command in the AppImage packaging step to allow wildcard expansion when
renaming the output, using the existing `APP_NAME` variable so it can find the
arch-suffixed `.AppImage` file. Keep the change localized to the rename logic in
`build.sh`.
In `@v3/examples/mac-window-tabs/build/linux/desktop`:
- Line 3: The desktop entry Name field is still the scaffold placeholder “My
Product” while the rest of the entry uses mac-window-tabs. Update the Name value
in the desktop entry alongside Exec, Icon, and StartupWMClass so the launcher
label matches the actual application name. Use the desktop entry’s Name key as
the locator for this change.
In `@v3/examples/mac-window-tabs/build/linux/nfpm/nfpm.yaml`:
- Around line 33-44: The RPM override comment is stale and conflicts with the
actual dependency pinned in the rpm block. In nfpm.yaml, update the comment
under the rpm override to match the WebKit version required by the depends entry
(using the same version as the archlinux override if 4.1 is correct), so the
package metadata is consistent and unambiguous.
In `@v3/examples/mac-window-tabs/go.mod`:
- Around line 7-47: The example module still pins older indirect go-git
dependencies, so update the require entries for github.com/go-git/go-git/v5 and
github.com/go-git/go-billy/v5 to match the patched versions already used in v3.
Keep the change limited to the go.mod dependency list so this example aligns
with the newer secure versions and removes the advisory exposure.
In `@v3/pkg/application/webview_window_options.go`:
- Around line 594-609: The MacWindowTabbingModeDefault sentinel in
MacWindowTabbingMode currently resolves to Disallowed, which changes the
behavior for callers of webview window options that never set TabbingMode.
Update the MacWindowTabbingMode constants or the runtime mapping used by
webview_window_options.go so the zero value preserves macOS’s native automatic
tabbing behavior, or otherwise make the breaking change explicit in the
TabbingMode handling and related docs.
---
Nitpick comments:
In `@docs/src/content/docs/features/windows/options.mdx`:
- Line 809: The TabbingMode docs currently say `MacWindowTabbingModeDefault`
“defaults to disallowing tabbing” but do not make clear that this differs from
macOS’s native `Automatic` default. Update the `TabbingMode` subsection in the
`options.mdx` docs to explicitly call out that the default behavior follows
`application.MacWindowTabbingModeDisallowed` and is an intentional divergence
from a plain unmanaged `NSWindow`, matching the implementation in
`webview_window_darwin.go` and the default noted in `webview_window_options.go`.
In
`@v3/examples/mac-window-tabs/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java`:
- Around line 47-61: The invokeAsync method in WailsJSBridge currently creates a
new Thread for every call, which can exhaust resources under heavy traffic.
Replace the per-call thread creation with a shared bounded ExecutorService (or
similar pooled executor) and submit the message handling work there. Keep the
existing async behavior in invokeAsync, but route bridge.handleMessage and
sendCallback through the shared executor so concurrency is controlled.
In
`@v3/examples/mac-window-tabs/build/android/app/src/main/java/com/wails/app/WailsPathHandler.java`:
- Around line 22-33: The logging in WailsPathHandler.handle() is missing the
DEBUG guard, so asset requests are always written in release builds. Update the
handle() method to wrap the Log.d and any Log.w calls with the same DEBUG flag
check used by the upstream template, keeping the logging behavior only for debug
builds while preserving the existing path handling logic in WailsPathHandler and
its bridge usage.
In `@v3/examples/mac-window-tabs/build/config.yml`:
- Line 62: The comment in the example config contains a placeholder Wails
documentation URL that should be replaced with a real, valid link. Update the
documentation reference in the generated config comment so it points to an
actual Wails docs page, keeping the change within the mac-window-tabs build
config example and preserving the surrounding comment structure.
In `@v3/examples/mac-window-tabs/build/Taskfile.yml`:
- Around line 46-79: The frontend:vendor:puppertino task is effectively
unreachable and self-skipping because nothing depends on it and its
sources/generates point to the same file. Update the Taskfile so a relevant
build task (such as build:frontend) depends on frontend:vendor:puppertino, and
adjust its sources/generates to reflect the actual fetched artifact or remove
the checksum trigger if the task should always attempt the fetch. Keep the task
name and the build:frontend dependency wiring as the main symbols to locate and
fix this.
- Around line 71-78: The Taskfile step is mutating index.html unconditionally by
rewriting the file with awk/sed, which can overwrite manual changes. Update the
logic around the index.html patching block to make the Puppertino injection
idempotent and narrowly scoped, using the existing grep check plus an additional
guard before the class replacement in the same task. In the task that edits
INDEX_HTML, only transform the specific default markup when it matches the
expected original content, and skip the sed replacement if the file has already
been customized or already contains Puppertino classes.
In `@v3/examples/mac-window-tabs/frontend/package.json`:
- Around line 12-14: The package.json dependency for `@wailsio/runtime` is using
"latest", which makes installs non-reproducible. Update the dependency in the
dependencies block to a pinned version that matches the runtime version used
elsewhere in the repo, so this frontend stays consistent with the rest of the
codebase.
In `@v3/examples/mac-window-tabs/frontend/public/style.css`:
- Around line 56-59: The `.result` CSS rule is duplicated, with a second
declaration later in the stylesheet overriding the first; consolidate the styles
into a single `.result` block and remove the redundant one so the selector is
defined only once. Update the shared `.result` rule in the stylesheet and keep
the combined styles together to avoid conflicting declarations.
- Around line 1-28: The CSS in the `:root` and `@font-face` blocks needs cleanup
for Stylelint: remove unnecessary quotes from font-family values like the
`font-family` stack and the `@font-face` `font-family` declaration, and change
`text-rendering: optimizeLegibility` to the lowercase form expected by the
linter. Update the `:root` styles in `style.css` so the font stack uses unquoted
generic/custom names where allowed and the `@font-face` definition remains
consistent with the same `Inter` naming.
In `@v3/pkg/application/webview_window_darwin.go`:
- Around line 1498-1504: The default-to-Disallowed normalization for
MacWindowTabbingModeDefault is duplicated between run() and setTabbingMode, so
consolidate it in one place only. Update run() and setTabbingMode in
webview_window_darwin.go so that only one of them performs the Default →
Disallowed fallback, and keep the other path assuming the mode is already
normalized. Use the existing symbols MacWindowTabbingModeDefault,
MacWindowTabbingModeDisallowed, and w.setTabbingMode to locate the logic and
avoid future drift.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a6836ad4-0b18-4095-b88b-8d0682704d5d
⛔ Files ignored due to path filters (19)
v3/examples/mac-window-tabs/build/android/app/src/main/res/mipmap-hdpi/ic_launcher.pngis excluded by!**/*.pngv3/examples/mac-window-tabs/build/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/examples/mac-window-tabs/build/android/app/src/main/res/mipmap-mdpi/ic_launcher.pngis excluded by!**/*.pngv3/examples/mac-window-tabs/build/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/examples/mac-window-tabs/build/android/app/src/main/res/mipmap-xhdpi/ic_launcher.pngis excluded by!**/*.pngv3/examples/mac-window-tabs/build/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/examples/mac-window-tabs/build/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngis excluded by!**/*.pngv3/examples/mac-window-tabs/build/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/examples/mac-window-tabs/build/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngis excluded by!**/*.pngv3/examples/mac-window-tabs/build/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/examples/mac-window-tabs/build/android/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jarv3/examples/mac-window-tabs/build/appicon.icon/Assets/wails_icon_vector.svgis excluded by!**/*.svgv3/examples/mac-window-tabs/build/appicon.pngis excluded by!**/*.pngv3/examples/mac-window-tabs/build/ios/icon.pngis excluded by!**/*.pngv3/examples/mac-window-tabs/build/windows/icon.icois excluded by!**/*.icov3/examples/mac-window-tabs/frontend/public/Inter-Medium.ttfis excluded by!**/*.ttfv3/examples/mac-window-tabs/frontend/public/javascript.svgis excluded by!**/*.svgv3/examples/mac-window-tabs/frontend/public/wails.pngis excluded by!**/*.pngv3/examples/mac-window-tabs/go.sumis excluded by!**/*.sum
📒 Files selected for processing (75)
docs/src/content/docs/features/windows/options.mdxv3/examples/mac-window-tabs/.gitignorev3/examples/mac-window-tabs/README.mdv3/examples/mac-window-tabs/Taskfile.ymlv3/examples/mac-window-tabs/build/Taskfile.ymlv3/examples/mac-window-tabs/build/android/Taskfile.ymlv3/examples/mac-window-tabs/build/android/app/build.gradlev3/examples/mac-window-tabs/build/android/app/proguard-rules.prov3/examples/mac-window-tabs/build/android/app/src/main/AndroidManifest.xmlv3/examples/mac-window-tabs/build/android/app/src/main/java/com/wails/app/MainActivity.javav3/examples/mac-window-tabs/build/android/app/src/main/java/com/wails/app/WailsBridge.javav3/examples/mac-window-tabs/build/android/app/src/main/java/com/wails/app/WailsJSBridge.javav3/examples/mac-window-tabs/build/android/app/src/main/java/com/wails/app/WailsPathHandler.javav3/examples/mac-window-tabs/build/android/app/src/main/res/layout/activity_main.xmlv3/examples/mac-window-tabs/build/android/app/src/main/res/values/colors.xmlv3/examples/mac-window-tabs/build/android/app/src/main/res/values/strings.xmlv3/examples/mac-window-tabs/build/android/app/src/main/res/values/themes.xmlv3/examples/mac-window-tabs/build/android/build.gradlev3/examples/mac-window-tabs/build/android/gradle.propertiesv3/examples/mac-window-tabs/build/android/gradle/wrapper/gradle-wrapper.propertiesv3/examples/mac-window-tabs/build/android/gradlewv3/examples/mac-window-tabs/build/android/gradlew.batv3/examples/mac-window-tabs/build/android/main_android.gov3/examples/mac-window-tabs/build/android/scripts/deps/install_deps.gov3/examples/mac-window-tabs/build/android/settings.gradlev3/examples/mac-window-tabs/build/appicon.icon/icon.jsonv3/examples/mac-window-tabs/build/config.ymlv3/examples/mac-window-tabs/build/darwin/Assets.carv3/examples/mac-window-tabs/build/darwin/Info.dev.plistv3/examples/mac-window-tabs/build/darwin/Info.plistv3/examples/mac-window-tabs/build/darwin/Taskfile.ymlv3/examples/mac-window-tabs/build/darwin/icons.icnsv3/examples/mac-window-tabs/build/docker/Dockerfile.crossv3/examples/mac-window-tabs/build/docker/Dockerfile.serverv3/examples/mac-window-tabs/build/ios/Assets.xcassetsv3/examples/mac-window-tabs/build/ios/Info.dev.plistv3/examples/mac-window-tabs/build/ios/Info.plistv3/examples/mac-window-tabs/build/ios/LaunchScreen.storyboardv3/examples/mac-window-tabs/build/ios/Taskfile.ymlv3/examples/mac-window-tabs/build/ios/app_options_default.gov3/examples/mac-window-tabs/build/ios/app_options_ios.gov3/examples/mac-window-tabs/build/ios/build.shv3/examples/mac-window-tabs/build/ios/entitlements.plistv3/examples/mac-window-tabs/build/ios/main.mv3/examples/mac-window-tabs/build/ios/main_ios.gov3/examples/mac-window-tabs/build/ios/project.pbxprojv3/examples/mac-window-tabs/build/ios/scripts/deps/install_deps.gov3/examples/mac-window-tabs/build/linux/Taskfile.ymlv3/examples/mac-window-tabs/build/linux/appimage/build.shv3/examples/mac-window-tabs/build/linux/desktopv3/examples/mac-window-tabs/build/linux/nfpm/nfpm.yamlv3/examples/mac-window-tabs/build/linux/nfpm/scripts/postinstall.shv3/examples/mac-window-tabs/build/linux/nfpm/scripts/postremove.shv3/examples/mac-window-tabs/build/linux/nfpm/scripts/preinstall.shv3/examples/mac-window-tabs/build/linux/nfpm/scripts/preremove.shv3/examples/mac-window-tabs/build/windows/Taskfile.ymlv3/examples/mac-window-tabs/build/windows/info.jsonv3/examples/mac-window-tabs/build/windows/msix/app_manifest.xmlv3/examples/mac-window-tabs/build/windows/msix/template.xmlv3/examples/mac-window-tabs/build/windows/nsis/project.nsiv3/examples/mac-window-tabs/build/windows/nsis/wails_tools.nshv3/examples/mac-window-tabs/build/windows/wails.exe.manifestv3/examples/mac-window-tabs/frontend/Inter Font License.txtv3/examples/mac-window-tabs/frontend/index.htmlv3/examples/mac-window-tabs/frontend/package.jsonv3/examples/mac-window-tabs/frontend/public/style.cssv3/examples/mac-window-tabs/frontend/src/main.jsv3/examples/mac-window-tabs/frontend/tsconfig.jsonv3/examples/mac-window-tabs/frontend/vite.config.jsv3/examples/mac-window-tabs/go.modv3/examples/mac-window-tabs/main.gov3/examples/mac-window-tabs/windowservice.gov3/pkg/application/webview_window_darwin.gov3/pkg/application/webview_window_options.gov3/pkg/application/webview_window_options_test.go
| @JavascriptInterface | ||
| public void log(String level, String message) { | ||
| switch (level.toLowerCase()) { | ||
| case "debug": | ||
| Log.d(TAG + "/JS", message); | ||
| break; | ||
| case "info": | ||
| Log.i(TAG + "/JS", message); | ||
| break; | ||
| case "warn": | ||
| Log.w(TAG + "/JS", message); | ||
| break; | ||
| case "error": | ||
| Log.e(TAG + "/JS", message); | ||
| break; | ||
| default: | ||
| Log.v(TAG + "/JS", message); | ||
| break; | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
NPE risk: log() doesn't guard against a null level.
This method is exposed via @JavascriptInterface, so JS can call wails.log(null, msg) (or pass undefined, coerced to null), causing level.toLowerCase() to throw an NPE.
🐛 Proposed fix
`@JavascriptInterface`
public void log(String level, String message) {
- switch (level.toLowerCase()) {
+ switch (level == null ? "" : level.toLowerCase()) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @JavascriptInterface | |
| public void log(String level, String message) { | |
| switch (level.toLowerCase()) { | |
| case "debug": | |
| Log.d(TAG + "/JS", message); | |
| break; | |
| case "info": | |
| Log.i(TAG + "/JS", message); | |
| break; | |
| case "warn": | |
| Log.w(TAG + "/JS", message); | |
| break; | |
| case "error": | |
| Log.e(TAG + "/JS", message); | |
| break; | |
| default: | |
| Log.v(TAG + "/JS", message); | |
| break; | |
| } | |
| } | |
| `@JavascriptInterface` | |
| public void log(String level, String message) { | |
| switch (level == null ? "" : level.toLowerCase()) { | |
| case "debug": | |
| Log.d(TAG + "/JS", message); | |
| break; | |
| case "info": | |
| Log.i(TAG + "/JS", message); | |
| break; | |
| case "warn": | |
| Log.w(TAG + "/JS", message); | |
| break; | |
| case "error": | |
| Log.e(TAG + "/JS", message); | |
| break; | |
| default: | |
| Log.v(TAG + "/JS", message); | |
| break; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@v3/examples/mac-window-tabs/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java`
around lines 70 - 89, Guard the WailsJSBridge.log() JavaScript bridge against a
null level before calling level.toLowerCase(), since `@JavascriptInterface`
callers can pass null/undefined and trigger an NPE. Update log(String level,
String message) to normalize a missing level to a safe default (for example the
existing "verbose" path) and keep the switch logic in place for the valid cases.
| build: | ||
| summary: Creates a build of the application for Android | ||
| deps: | ||
| - task: common:go:mod:tidy | ||
| - task: generate:android:bindings | ||
| vars: | ||
| BUILD_FLAGS: | ||
| ref: .BUILD_FLAGS | ||
| - task: common:build:frontend | ||
| vars: | ||
| BUILD_FLAGS: | ||
| ref: .BUILD_FLAGS | ||
| PRODUCTION: | ||
| ref: .PRODUCTION | ||
| - task: common:generate:icons | ||
| cmds: | ||
| - echo "Building Android app {{.APP_NAME}}..." | ||
| - task: compile:go:shared | ||
| vars: | ||
| ARCH: '{{.ARCH | default "arm64"}}' | ||
| vars: | ||
| BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production,android -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags android,debug -buildvcs=false -gcflags=all="-l"{{end}}' | ||
| env: | ||
| PRODUCTION: '{{.PRODUCTION | default "false"}}' | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the relevant Taskfiles and inspect the referenced sections with line numbers.
git ls-files 'v3/examples/mac-window-tabs/build/Taskfile.yml' 'v3/examples/mac-window-tabs/build/android/Taskfile.yml' '**/Taskfile.yml' | sed -n '1,20p'
echo
echo '--- android Taskfile ---'
nl -ba v3/examples/mac-window-tabs/build/android/Taskfile.yml | sed -n '1,140p'
echo
echo '--- search for compile:go:shared ---'
rg -n --hidden --glob 'Taskfile.yml' 'compile:go:shared|BUILD_FLAGS|PRODUCTION' v3/examples/mac-window-tabsRepository: wailsapp/wails
Length of output: 1063
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- candidate files ---'
git ls-files 'v3/examples/android/build/android/Taskfile.yml' 'v3/examples/android/build/Taskfile.yml' 'v3/examples/android/Taskfile.yml' 'v3/examples/mac-window-tabs/build/android/Taskfile.yml' 2>/dev/null || true
echo
echo '--- android build/android Taskfile ---'
sed -n '1,180p' v3/examples/android/build/android/Taskfile.yml
echo
echo '--- search compile:go:shared references ---'
rg -n --hidden --glob 'Taskfile.yml' 'compile:go:shared|BUILD_FLAGS|PRODUCTION' v3/examples/androidRepository: wailsapp/wails
Length of output: 12000
🌐 Web query:
go-task nested task inherits vars env task: call parent task PRODUCTION BUILD_FLAGS documentation
💡 Result:
In Task, tasks do not automatically inherit variables or environment variables from their parent tasks [1][2][3]. Variables have defined scopes, and a subtask (or a called task) only receives variables that are explicitly passed to it [1][3][4]. To pass variables from a parent task to a subtask, you must define them within the vars block of the task: call [1][3][5]. For example, to pass a BUILD_FLAGS variable to a subtask named production: tasks: parent: vars: BUILD_FLAGS: "--verbose" cmds: - task: production vars: BUILD_FLAGS: "{{.BUILD_FLAGS}}" production: cmds: - echo "Running production with {{.BUILD_FLAGS}}" If you need to propagate these values into the subtask's environment (the env block), you must explicitly map them in the subtask definition or during the task call [1][6]. Because there is no native inheritance of the parent's environment, common strategies include: 1. Explicit Mapping: Map the variable within the subtask's env block using the passed-in variable: production: vars: BUILD_FLAGS: "" env: BUILD_FLAGS: "{{.BUILD_FLAGS}}" cmds: - echo "Environment variable BUILD_FLAGS is $BUILD_FLAGS" 2. YAML Anchors: Use YAML anchors/aliases if the variables and environment mapping are complex and shared within the same Taskfile to avoid duplication [1][6]. 3. Env Files: For more complex setups, consider using .env files, which can be loaded by tasks to provide a consistent environment without manual repetition [1]. Variable precedence in Task follows a specific order, where variables explicitly passed in the task: call override global or included variables, but are themselves overridden by variables declared directly in the subtask's own vars block [4][7].
Citations:
- 1: Propagate Parent Task Environment Variables to Subtasks go-task/task#1854
- 2: Inheriting environment variables from dependency tasks go-task/task#705
- 3: inherit vars from parent node go-task/task#322
- 4: https://taskfile.dev/docs/guide
- 5: Overriding vars in called task not working as expected go-task/task#376
- 6: Option to assign env from vars go-task/task#1863
- 7: https://github.com/go-task/task/blob/main/website/src/docs/guide.md
Pass BUILD_FLAGS into compile:go:shared
build only forwards ARCH to the nested task: compile:go:shared, so that task falls back to its own default .PRODUCTION value and uses the debug BUILD_FLAGS branch even when package sets PRODUCTION: "true". Add BUILD_FLAGS (or PRODUCTION) to this call.
🐛 Proposed fix
cmds:
- echo "Building Android app {{.APP_NAME}}..."
- task: compile:go:shared
vars:
ARCH: '{{.ARCH | default .HOST_ARCH}}'
+ BUILD_FLAGS:
+ ref: .BUILD_FLAGS🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/examples/mac-window-tabs/build/android/Taskfile.yml` around lines 21 - 45,
The build task only passes ARCH into compile:go:shared, so that nested task can
ignore the parent’s PRODUCTION setting and resolve BUILD_FLAGS incorrectly.
Update the build command to forward BUILD_FLAGS or PRODUCTION into the task:
compile:go:shared invocation, using the existing BUILD_FLAGS and PRODUCTION vars
defined in build so the shared compile step matches the Android package mode.
| package: | ||
| summary: Packages a production build of the application into an APK | ||
| deps: | ||
| - task: build | ||
| vars: | ||
| PRODUCTION: "true" | ||
| cmds: | ||
| - task: assemble:apk | ||
|
|
||
| package:fat: | ||
| summary: Packages a production build for all architectures (fat APK) | ||
| cmds: | ||
| - task: compile:go:all-archs | ||
| - task: assemble:apk | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
package/package:fat assemble the debug Gradle variant, not release.
package (Line 107) builds with PRODUCTION: "true" but then calls assemble:apk (Line 114), which runs ./gradlew assembleDebug and copies app-debug.apk — not assemble:apk:release. Same issue in package:fat (Line 116-121), which additionally never threads PRODUCTION into compile:go:all-archs. The net effect: tasks named/described as producing a "production build"/"packages ... into an APK" ship a debug-signed, debug-tagged artifact.
🐛 Proposed fix
package:
summary: Packages a production build of the application into an APK
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- - task: assemble:apk
+ - task: assemble:apk:release
package:fat:
summary: Packages a production build for all architectures (fat APK)
cmds:
- - task: compile:go:all-archs
- - task: assemble:apk
+ - task: compile:go:all-archs
+ vars:
+ PRODUCTION: "true"
+ - task: assemble:apk:releaseNote: deploy-emulator/run reference {{.APP_NAME}}.apk (debug filename); if switching package to the release variant, update those references to {{.APP_NAME}}-release.apk accordingly.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| package: | |
| summary: Packages a production build of the application into an APK | |
| deps: | |
| - task: build | |
| vars: | |
| PRODUCTION: "true" | |
| cmds: | |
| - task: assemble:apk | |
| package:fat: | |
| summary: Packages a production build for all architectures (fat APK) | |
| cmds: | |
| - task: compile:go:all-archs | |
| - task: assemble:apk | |
| package: | |
| summary: Packages a production build of the application into an APK | |
| deps: | |
| - task: build | |
| vars: | |
| PRODUCTION: "true" | |
| cmds: | |
| - task: assemble:apk:release | |
| package:fat: | |
| summary: Packages a production build for all architectures (fat APK) | |
| cmds: | |
| - task: compile:go:all-archs | |
| vars: | |
| PRODUCTION: "true" | |
| - task: assemble:apk:release |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/examples/mac-window-tabs/build/android/Taskfile.yml` around lines 107 -
121, The package tasks are still producing the debug APK instead of a
production/release artifact. Update the Taskfile targets for package and
package:fat so they invoke the release Gradle variant through assemble:apk, and
make sure package:fat also passes PRODUCTION through compile:go:all-archs. If
the APK filename changes, adjust any references in deploy-emulator and run from
the debug name to the release name so the downstream tasks use the correct
artifact.
| # Cross-compile Wails v3 apps to any platform | ||
| # | ||
| # Darwin: Zig + macOS SDK | ||
| # Linux: Native GCC when host matches target, Zig for cross-arch | ||
| # Windows: Zig + bundled mingw | ||
| # | ||
| # Usage: | ||
| # docker build -t wails-cross -f Dockerfile.cross . | ||
| # docker run --rm -v $(pwd):/app wails-cross darwin arm64 | ||
| # docker run --rm -v $(pwd):/app wails-cross darwin amd64 | ||
| # docker run --rm -v $(pwd):/app wails-cross linux amd64 | ||
| # docker run --rm -v $(pwd):/app wails-cross linux arm64 | ||
| # docker run --rm -v $(pwd):/app wails-cross windows amd64 | ||
| # docker run --rm -v $(pwd):/app wails-cross windows arm64 | ||
|
|
||
| FROM golang:1.25-bookworm |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
set -e
printf '\n== File ==\n'
cat -n v3/examples/mac-window-tabs/build/docker/Dockerfile.cross | sed -n '1,220p'
printf '\n== syntax directives in build/docker ==\n'
rg -n --glob 'Dockerfile*' '^# syntax=' v3/examples/mac-window-tabs/build/docker || trueRepository: wailsapp/wails
Length of output: 7154
🏁 Script executed:
cat -n v3/examples/mac-window-tabs/build/docker/Dockerfile.cross | sed -n '1,220p'Repository: wailsapp/wails
Length of output: 7101
Add # syntax=docker/dockerfile:1 at the top of v3/examples/mac-window-tabs/build/docker/Dockerfile.cross.
This file uses heredoc COPY instructions later on, and pinning the frontend avoids parser incompatibilities across Docker/BuildKit versions.
🧰 Tools
🪛 Trivy (0.69.3)
[error] 1-1: Image user should not be 'root'
Specify at least 1 USER command in Dockerfile with non-root user as argument
Rule: DS-0002
(IaC/Dockerfile)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/examples/mac-window-tabs/build/docker/Dockerfile.cross` around lines 1 -
16, Add the Dockerfile frontend directive at the very top of Dockerfile.cross so
the heredoc-based COPY instructions are parsed consistently across
Docker/BuildKit versions. Update the file header before the existing FROM line,
keeping the rest of the cross-compile setup unchanged.
| # Rename the generated AppImage | ||
| mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage" | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Quoted glob prevents mv from finding the AppImage.
Double-quoting "${APP_NAME}*.AppImage" disables glob expansion in bash — the * is matched literally, not as a wildcard. Since linuxdeploy outputs a file with an arch suffix (not a literal *), this mv will fail and (with set -e) abort the packaging step.
🐛 Proposed fix
# Rename the generated AppImage
-mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage"
+mv ${APP_NAME}*.AppImage "${APP_NAME}.AppImage"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Rename the generated AppImage | |
| mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage" | |
| # Rename the generated AppImage | |
| mv ${APP_NAME}*.AppImage "${APP_NAME}.AppImage" |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/examples/mac-window-tabs/build/linux/appimage/build.sh` around lines 33 -
35, The AppImage rename in the build script is using a quoted glob, so `mv`
cannot match the generated file from `linuxdeploy`. Update the `mv` command in
the AppImage packaging step to allow wildcard expansion when renaming the
output, using the existing `APP_NAME` variable so it can find the arch-suffixed
`.AppImage` file. Keep the change localized to the rename logic in `build.sh`.
| @@ -0,0 +1,13 @@ | |||
| [Desktop Entry] | |||
| Version=1.0 | |||
| Name=My Product | |||
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Desktop entry Name left as scaffold placeholder.
Every other field in this file (Exec, Icon, StartupWMClass) uses mac-window-tabs, but Name still says "My Product" — the unfilled template default. This is the label shown in Linux application launchers.
✏️ Proposed fix
-Name=My Product
+Name=Mac Window Tabs📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Name=My Product | |
| Name=Mac Window Tabs |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/examples/mac-window-tabs/build/linux/desktop` at line 3, The desktop entry
Name field is still the scaffold placeholder “My Product” while the rest of the
entry uses mac-window-tabs. Update the Name value in the desktop entry alongside
Exec, Icon, and StartupWMClass so the launcher label matches the actual
application name. Use the desktop entry’s Name key as the locator for this
change.
| overrides: | ||
| # RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0) | ||
| rpm: | ||
| depends: | ||
| - gtk3 | ||
| - webkit2gtk4.1 | ||
|
|
||
| # Arch Linux packages (WebKit 4.1) | ||
| archlinux: | ||
| depends: | ||
| - gtk3 | ||
| - webkit2gtk-4.1 |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
RPM override comment contradicts the pinned dependency.
The comment says (WebKit 4.0) but the RPM depends list pins webkit2gtk4.1 — the same version noted in the Arch override below. Confirm which WebKit version is actually required and fix the stale comment.
✏️ Proposed fix (if 4.1 is correct)
- # RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0)
+ # RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.1)
rpm:
depends:
- gtk3
- webkit2gtk4.1📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| overrides: | |
| # RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0) | |
| rpm: | |
| depends: | |
| - gtk3 | |
| - webkit2gtk4.1 | |
| # Arch Linux packages (WebKit 4.1) | |
| archlinux: | |
| depends: | |
| - gtk3 | |
| - webkit2gtk-4.1 | |
| overrides: | |
| # RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.1) | |
| rpm: | |
| depends: | |
| - gtk3 | |
| - webkit2gtk4.1 | |
| # Arch Linux packages (WebKit 4.1) | |
| archlinux: | |
| depends: | |
| - gtk3 | |
| - webkit2gtk-4.1 |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/examples/mac-window-tabs/build/linux/nfpm/nfpm.yaml` around lines 33 - 44,
The RPM override comment is stale and conflicts with the actual dependency
pinned in the rpm block. In nfpm.yaml, update the comment under the rpm override
to match the WebKit version required by the depends entry (using the same
version as the archlinux override if 4.1 is correct), so the package metadata is
consistent and unambiguous.
| require ( | ||
| dario.cat/mergo v1.0.2 // indirect | ||
| github.com/Microsoft/go-winio v0.6.2 // indirect | ||
| github.com/ProtonMail/go-crypto v1.3.0 // indirect | ||
| github.com/adrg/xdg v0.5.3 // indirect | ||
| github.com/bep/debounce v1.2.1 // indirect | ||
| github.com/cloudflare/circl v1.6.3 // indirect | ||
| github.com/coder/websocket v1.8.14 // indirect | ||
| github.com/cyphar/filepath-securejoin v0.6.1 // indirect | ||
| github.com/ebitengine/purego v0.9.1 // indirect | ||
| github.com/emirpasic/gods v1.18.1 // indirect | ||
| github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect | ||
| github.com/go-git/go-billy/v5 v5.7.0 // indirect | ||
| github.com/go-git/go-git/v5 v5.16.4 // indirect | ||
| github.com/go-ole/go-ole v1.3.0 // indirect | ||
| github.com/godbus/dbus/v5 v5.2.2 // indirect | ||
| github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect | ||
| github.com/google/uuid v1.6.0 // indirect | ||
| github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect | ||
| github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect | ||
| github.com/kevinburke/ssh_config v1.4.0 // indirect | ||
| github.com/klauspost/cpuid/v2 v2.3.0 // indirect | ||
| github.com/leaanthony/go-ansi-parser v1.6.1 // indirect | ||
| github.com/leaanthony/u v1.1.1 // indirect | ||
| github.com/lmittmann/tint v1.1.2 // indirect | ||
| github.com/mattn/go-colorable v0.1.14 // indirect | ||
| github.com/mattn/go-isatty v0.0.20 // indirect | ||
| github.com/pjbgf/sha1cd v0.5.0 // indirect | ||
| github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect | ||
| github.com/rivo/uniseg v0.4.7 // indirect | ||
| github.com/samber/lo v1.52.0 // indirect | ||
| github.com/sergi/go-diff v1.4.0 // indirect | ||
| github.com/skeema/knownhosts v1.3.2 // indirect | ||
| github.com/wailsapp/go-webview2 v1.0.23 // indirect | ||
| github.com/xanzy/ssh-agent v0.3.3 // indirect | ||
| golang.org/x/crypto v0.47.0 // indirect | ||
| golang.org/x/net v0.49.0 // indirect | ||
| golang.org/x/sys v0.40.0 // indirect | ||
| golang.org/x/text v0.33.0 // indirect | ||
| gopkg.in/warnings.v0 v0.1.2 // indirect | ||
| ) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check latest patched versions for go-git and go-billy
curl -s https://api.github.com/repos/go-git/go-git/releases/latest | jq '.tag_name'
curl -s https://api.github.com/repos/go-git/go-billy/releases/latest | jq '.tag_name' 2>/dev/nullRepository: wailsapp/wails
Length of output: 219
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== v3/examples/mac-window-tabs/go.mod =="
cat -n v3/examples/mac-window-tabs/go.mod | sed -n '1,120p'
echo
echo "== Search for go-git / go-billy usage in the repo =="
rg -n --hidden --glob '!*node_modules*' --glob '!*.sum' 'go-git|go-billy' v3 | sed -n '1,200p'
echo
echo "== Latest release tags from GitHub =="
python3 - <<'PY'
import json, urllib.request
repos = [
("go-git/go-git", "https://api.github.com/repos/go-git/go-git/releases/latest"),
("go-git/go-billy", "https://api.github.com/repos/go-git/go-billy/releases/latest"),
]
for name, url in repos:
try:
with urllib.request.urlopen(url, timeout=20) as r:
data = json.load(r)
print(name, data.get("tag_name"))
except Exception as e:
print(name, "ERROR:", e)
PY
echo
echo "== Proxy versions (fallback) =="
python3 - <<'PY'
import json, urllib.request
for module in ["github.com/go-git/go-git/v5", "github.com/go-git/go-billy/v5"]:
url = f"https://proxy.golang.org/{module}/@v/list"
try:
with urllib.request.urlopen(url, timeout=20) as r:
versions = r.read().decode().strip().splitlines()
print(module, versions[-5:])
except Exception as e:
print(module, "ERROR:", e)
PYRepository: wailsapp/wails
Length of output: 7478
Bump the indirect go-git/go-billy versions here
github.com/go-git/go-git/v5 v5.16.4 and github.com/go-git/go-billy/v5 v5.7.0 are older than the patched versions already used elsewhere in v3 (v5.19.1 / v5.9.0). Updating this example to match removes the known HIGH-severity advisories.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/examples/mac-window-tabs/go.mod` around lines 7 - 47, The example module
still pins older indirect go-git dependencies, so update the require entries for
github.com/go-git/go-git/v5 and github.com/go-git/go-billy/v5 to match the
patched versions already used in v3. Keep the change limited to the go.mod
dependency list so this example aligns with the newer secure versions and
removes the advisory exposure.
| // MacWindowTabbingMode controls window tabbing behavior (macOS 10.12+). | ||
| // Values map to NSWindowTabbingMode (offset by 1 so the zero value is an "unset" sentinel). | ||
| type MacWindowTabbingMode int | ||
|
|
||
| const ( | ||
| // MacWindowTabbingModeDefault is the zero-value sentinel meaning "not explicitly set". | ||
| // At runtime it resolves to Disallowed. | ||
| MacWindowTabbingModeDefault MacWindowTabbingMode = iota | ||
| // MacWindowTabbingModeAutomatic allows the system to determine tabbing behavior | ||
| MacWindowTabbingModeAutomatic | ||
| // MacWindowTabbingModePreferred indicates the window prefers to be in tabbing mode | ||
| MacWindowTabbingModePreferred | ||
| // MacWindowTabbingModeDisallowed prevents the window from being tabbed | ||
| MacWindowTabbingModeDisallowed | ||
| ) | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Does macOS window tabbing (NSWindow tabbingMode automatic) get enabled by default for non-document-based NSWindow subclasses without NSDocument support?
💡 Result:
Yes, macOS window tabbing (via NSWindow.TabbingMode.automatic) is enabled by default for all NSWindow instances, including those that are not document-based and do not use NSDocument [1][2]. When tabbingMode is set to its default value of .automatic, the system consults the user's global tabbing preference (configured in System Settings) to determine if a window should be allowed to join a tab group [1][2][3]. While the tabbing mechanism is enabled by default, successfully implementing a functional tabbed interface outside of the NSDocument framework requires additional development work to ensure the system-level interactions function correctly [4][5]: 1. Responder Chain: You must ensure that newWindowForTab(_:) is implemented somewhere in the responder chain (e.g., in your NSWindowController or NSApplicationDelegate) [6]. The system calls this method to create a new window when the user clicks the "plus" button in the tab bar; if it is not implemented, the "plus" button may not appear or may be non-functional [4][6]. 2. Window Management: Because the system treats each tab as an individual window, you are responsible for managing the lifecycle of the NSWindowController instances associated with those tabs to keep them alive and responsive to menu actions [4][5][7]. 3. Tabbing Identifier: To group specific types of windows together, you may need to set a common tabbingIdentifier on your windows [8]. In summary, while the platform-level support for "automatic" tabbing is active on all windows by default, the developer remains responsible for handling the creation of new windows and maintaining the responder chain to support the native tab bar interactions [4][6][7].
Citations:
- 1: https://sosumi.ai/documentation/appkit/nswindow/tabbingmode-swift.property
- 2: https://sosumi.ai/documentation/appkit/nswindow/tabbingmode-swift.enum/automatic
- 3: https://sosumi.ai/documentation/appkit/nswindow/usertabbingpreference-swift.type.property
- 4: https://christiantietze.de/posts/2019/01/programmatically-add-nswindow-tabs/
- 5: https://github.com/DivineDominion/NSWindow-Tabbing
- 6: https://developer.apple.com/documentation/appkit/nsresponder/newwindowfortab(_:)
- 7: https://christiantietze.de/posts/2019/07/nswindow-tabbing-multiple-nswindowcontroller/
- 8: https://stackoverflow.com/questions/48254397/nswindow-controlling-multiple-tabs
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the relevant symbols and nearby commentary.
rg -n "MacWindowTabbingMode|CollectionBehavior|tabbingMode|setTabbingMode|user tabbing" v3/pkg/application -S
# Show the specific file section around the new enum.
sed -n '560,640p' v3/pkg/application/webview_window_options.go
# Show the Darwin runtime handling around run() and any defaults commentary.
fd -a 'webview_window_darwin.go' v3/pkg/applicationRepository: wailsapp/wails
Length of output: 14346
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the relevant symbols and nearby commentary.
rg -n "MacWindowTabbingMode|CollectionBehavior|tabbingMode|setTabbingMode|user tabbing" v3/pkg/application -S
# Show the specific file section around the new enum.
sed -n '560,640p' v3/pkg/application/webview_window_options.go
# Find the Darwin implementation file.
fd -a 'webview_window_darwin.go' v3/pkg/applicationRepository: wailsapp/wails
Length of output: 14346
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "MacWindowTabbingMode|CollectionBehavior|tabbingMode|setTabbingMode|user tabbing" v3/pkg/application -S
printf '\n--- webview_window_options.go ---\n'
sed -n '560,640p' v3/pkg/application/webview_window_options.go
printf '\n--- webview_window_darwin.go path ---\n'
fd -a 'webview_window_darwin.go' v3/pkg/applicationRepository: wailsapp/wails
Length of output: 14420
Keep the zero value aligned with macOS tabbing defaults
MacWindowTabbingModeDefault now maps to Disallowed, so existing apps that never set TabbingMode will lose macOS’s automatic tabbing behavior on upgrade. Either preserve the native default or call this out as a breaking change.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/pkg/application/webview_window_options.go` around lines 594 - 609, The
MacWindowTabbingModeDefault sentinel in MacWindowTabbingMode currently resolves
to Disallowed, which changes the behavior for callers of webview window options
that never set TabbingMode. Update the MacWindowTabbingMode constants or the
runtime mapping used by webview_window_options.go so the zero value preserves
macOS’s native automatic tabbing behavior, or otherwise make the breaking change
explicit in the TabbingMode handling and related docs.
- go.mod: add replace => ../../ so the example builds against the local Wails module (MacWindowTabbingMode is not in a published release), matching the print/mobile examples. - build/Taskfile.yml: bind vite dev server to 127.0.0.1 so the wails3 dev tcp4 proxy connects (was: connection refused / blank webview). - README: run with `task dev` (generates bindings + builds frontend); `go run .` alone does not work. Document the four windows (2 tabbing-preferred, 2 disallowed) and that this is macOS-only. Verified: task dev builds against local v3, opens the four windows, content loads with 0 proxy errors.
…ndow Spawning four windows at launch was confusing: the two Preferred windows auto-tabbed and the two Disallowed ones stacked at the same frame, so the demo appeared to start with two windows and mis-counted after Merge All Windows. The bound WindowService buttons (Open tabbed / non-tabbed) were dead weight behind them. Open a single Preferred window at startup and drive the demo from the buttons, giving clear cause-and-effect: tabbed windows join the current window, non-tabbed windows stay standalone. Also rename the module from the default 'changeme' to 'mac-window-tabs'.
The example is a standalone module (own go.mod + replace => ../../) nested under v3/, which is a member of the repo root go.work. With the workspace active, 'wails3 generate bindings' resolved the dir as v3 and emitted 0 services (breaking the frontend import), and 'go mod tidy' would target v3 rather than the example. Set GOWORK=off at the Taskfile level so the example always builds as itself; it propagates through the wails3 dev/build tree and is a no-op outside the monorepo.
…dow tabbing mode configuration
|
Thanks for this @Eriyc 🙏 |
Adds
MacWindow.TabbingMode(MacWindowTabbingMode) to configure macOS window tabbing (NSWindowTabbingMode).Supersedes #4966 by @Eriyc — the original fork was deleted, so this re-homes that work on a maintainer branch (full credit to @Eriyc). De-conflicted against master's restructured options.mdx (kept both the new TabbingMode docs and master's WebviewPreferences section) and ran
go mod tidy. darwin build + options test pass; includes the mac-window-tabs example.Summary by CodeRabbit
TabbingModeoption (Default/Automatic/Preferred/Disallowed).TabbingModeconstant values.