feat(mobile): iOS and Android support for Wails v3 (experimental)#5562
Conversation
Baseline iOS/Android command-template directories that the mobile support commits build upon (wails3 project scaffolding for mobile targets).
- export events.IOS (was unexported iOS; events_common_ios.go references it) - define androidMethodNames stub on ios (messageprocessor.go references it unconditionally; mobile_stub is !ios && !android) - examples/ios: bindings generate to ./bindings/ios-example (module name), not the template default ./bindings/changeme
Core fixes: - single initial navigation: drop hardcoded loadRequest in viewDidLoad and manual loadView/viewDidLoad calls (page loaded twice, losing the wails:runtime:ready handshake); Go's window run() now loads the start URL - runtime-ready hardening: first /wails/runtime request marks the window ready, so Go->JS events/ExecJS work even if the postMessage handshake is dropped during load - replace the 2s startup sleep with a launch signal from the app delegate; ApplicationDidFinishLaunching is emitted Go-side after listeners are wired - real hasListeners backed by a Go registry fed from app/window .on(); delete pkg/events events_ios.go (duplicate C symbol + OOB array) - free the C string in execJS (leaked on every Go->JS call) Features: - dialogs: UIAlertController message dialogs with button callbacks; UIDocumentPickerViewController open file/files/directory (files imported as sandbox copies); save dialogs return an explicit unsupported error - clipboard: UIPasteboard-backed setText/text - screens: real UIScreen metrics (points, pixels, scale, safe-area work area) Hygiene: - framework diagnostics gated behind debug builds (Go const + native WailsVLog); production builds are quiet - GOOS=ios now compiles all of ./pkg/...: notifications/dock/updater/mac/ doctor-ng darwin files no longer leak into iOS builds (ios implies the darwin build tag); added iOS fallbacks where needed
…ectl deploy - Taskfile platform parameterisation: IOS_PLATFORM=simulator|device picks SDK (iphonesimulator|iphoneos), target triple and version-min flags; MIN_IOS_VERSION configurable (also via ios.minIOSVersion in config.yml, templated into Info.plist); armv7 dropped from UIRequiredDeviceCapabilities - CODESIGN_IDENTITY (default ad-hoc), optional PROVISIONING_PROFILE embedded into device bundles, entitlements applied to device builds only (simulator bundles with entitlements are refused by SpringBoard - found the example's stale macOS-keyed entitlements.plist doing exactly that; replaced template and example with iOS-appropriate content) - package now links the executable (the old flow shipped whatever stale binary happened to be in bin/), package:ipa produces a Payload zip, deploy-device installs+launches via xcrun devicectl - fix webview_window_darwin_production.go build tag: production iOS builds never compiled (tag matched ios via implied darwin) - ensure-simulator no longer hardcodes 'iPhone 15 Pro' when creating a device Verified: simulator production package installs+launches with working events/dialogs/bridge; device package produces LC_BUILD_VERSION platform 2 (PLATFORM_IOS) minos 15.0 binary; ipa contains signed Payload bundle.
…h scripts - IOS.md: accurate status matrix, simulator/device quickstart, signing and IPA flow, config keys, porting notes; replaces the drifted IOS_ARCHITECTURE/IOS_RUNTIME/IOS_FEATURES_TODO docs - examples/ios README: iOS quickstart section - wails3 doctor now reports Xcode and iOS simulator/device SDKs on macOS - delete repo-root scratch artifacts: build_ios.sh, test-new-ios-build.sh, verify-ios-setup.sh, fix-darwin-ios-constraints.sh, test-ios-compilation.go, test-assets/
- new Building & Packaging guide at /guides/build/ios (experimental badge): simulator/device workflow, signing, IPA, configuration, status matrix, porting notes; registered in the sidebar - iOS entries in UNRELEASED_CHANGELOG.md (Added + Fixed) Verified: astro build succeeds (1387 pages) and emits /guides/build/ios/.
…/haptics - events: generate iOS+Android event mappings from events.txt (generate:events now emits var IOS/var Android blocks, android: entries in eventToJS and event_types.ts; .m post-processing preserves blank lines; template carried the old 'var iOS' name that broke GOOS=ios builds on regen) - fix build-tag leaks into GOOS=android: linux_cgo.c/.h, events_linux.go/.h, environment_linux.go now carry !android (android implies the linux tag); delete dead pkg/events/events_android.go (out-of-bounds MAX_EVENTS array) - application_android.go rewrite: generic JNI call layer (attach/detach safe, exception-checked, leak-free), logcat-routed logging gated by androidVerboseLogging (debug/production consts), typed lifecycle events (ActivityCreated/Started/Resumed/Paused/Stopped/LowMemory, WebViewPage*), setupCommonEvents wired in the cgo path, launch event emitted after listener registration - runtime handshake fixed: JS invoke messages route into windowMessageBuffer (plain 'wails:runtime:ready' passthrough mirrors iOS HandleJSMessage) + synthetic ready on first /wails/runtime request; the old broken runtime.Core() injection at onPageFinished is gone (the bundled runtime self-detects the Android JavascriptInterface at document start) - real dialogs (AlertDialog with button-role mapping + >3-button list, SAF document picker with copy-to-cache for real paths; save dialogs error with guidance), clipboard (ClipboardManager), screens (WindowMetrics + system-bar insets, dp/px split), main-thread dispatch (main looper), haptics/toast/device-info runtime methods - Java bridge: lifecycle forwarding (onStart/Stop/LowMemory), file picker via ACTION_OPEN_DOCUMENT, debug-gated logging, VIBRATE permission; synced across internal/commands, build_assets and examples - nocgo variant kept API-identical for tooling builds (bindings generation)
… wiring
The Android WebView does not deliver fetch() POST bodies to
shouldInterceptRequest, so the default HTTP runtime transport never reached
Go (a bindings call SIGSEGV'd reading a nil request body). Add an Android
transport that routes runtime calls through window.wails.invokeAsync to a new
nativeHandleRuntimeCall JNI export, with responses delivered via
window._wailsAndroidCallback. The runtime auto-installs it when the
JavascriptInterface bridge is detected (internal/runtime .../runtime.ts).
- nativeHandleRuntimeCall → handleRuntimeCallForAndroid: parses the call
envelope and drives a per-app MessageProcessor, returning
{ok,data|text|error}
- serveAssetForAndroid now sets req.Body = http.NoBody (handlers reached via
ServeHTTP assume the server's non-nil-Body guarantee)
- WailsJSBridge.invokeAsync rewired to handleRuntimeCall; callback target is
window._wailsAndroidCallback
- populate ScreenManager at startup + lazily (LayoutScreens), matching the
desktop platforms — Screens.GetAll returned nil before; screen_android.go
now supplies PhysicalWorkArea so WorkArea scales correctly
Verified on the API 34 arm64 emulator: assets load, JS→Go bindings render
'Hello Android!', Go→JS time events tick, native AlertDialog shows, clipboard
round-trips, Device.Info + Screens.GetAll return real data (412x915 dp,
WorkArea 412x839).
- ANDROID.md: status matrix, quickstart, signing/release, porting notes, architecture; replaces the pre-implementation ANDROID_ARCHITECTURE.md scratch doc - docs site: guides/build/android page (Experimental badge) + sidebar entry - wails3 doctor: report Android SDK, platform-tools, NDK and JDK (cross-platform) - examples/android: README pointing at ANDROID.md - changelog: Added/Fixed Android entries
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds Android and iOS docs, example projects, build tooling, native bridges (Java/Obj‑C), Go runtime integrations, event registry and generator updates, and doctor/tooling checks. ChangesMobile platform support rollout
Sequence Diagram(s)Estimated code review effort Possibly related PRs
Suggested labels Suggested reviewers
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
67737fe to
e423867
Compare
…ignore The Task runner cache (.task/) and the Gradle problems report (build/android/build/) were accidentally tracked; ignore them and remove them from version control (the files remain on disk).
e423867 to
ba68676
Compare
The iOS Info.plist templates reference {{.MinIOSVersion}}, but the generic
build-asset generator renders all templates against BuildConfig/UpdateConfig,
which lacked the field — failing TestGenerateBuildAssets/TestUpdateBuildAssets
(internal/commands) on all platforms. Add MinIOSVersion (default 15.0) to
BuildAssetsOptions and UpdateBuildAssetsOptions so the templates resolve.
There was a problem hiding this comment.
Pull request overview
Adds experimental mobile targets (iOS + Android) to Wails v3, aiming to keep the same runtime surface as desktop (bindings, events, dialogs, clipboard, screens) while fixing cross-compile leakage caused by ios→darwin and android→linux build-tag implications.
Changes:
- Introduces iOS + Android runtime support (events, dialogs, clipboard, screens, lifecycle) and associated build-tag sweeps to keep desktop-only code out of mobile builds.
- Adds Android Java host + bridge integration and updates the bundled JS runtime transport to use a JavascriptInterface path when available.
- Adds new iOS/Android documentation and updates doctor output/build assets/templates to support the new targets.
Reviewed changes
Copilot reviewed 135 out of 155 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| v3/verify-ios-setup.sh | Removes an iOS verification helper script. |
| v3/UNRELEASED_CHANGELOG.md | Documents mobile features/fixes in the unreleased changelog. |
| v3/test-new-ios-build.sh | Removes an iOS build-asset test script. |
| v3/test-ios-compilation.go | Removes an iOS compilation test harness. |
| v3/test-assets/index.html | Removes iOS test HTML asset. |
| v3/tasks/events/generate.go | Extends event generator for Android + fixes iOS naming + stabilizes delegate rewriting. |
| v3/pkg/updater/updater_notdarwin.go | Adjusts build tags so iOS uses the non-darwin stub. |
| v3/pkg/updater/updater_darwin.go | Excludes iOS from darwin updater implementation. |
| v3/pkg/updater/updater_darwin_test.go | Excludes iOS from darwin updater tests. |
| v3/pkg/services/notifications/notifications_darwin.m | Excludes iOS from macOS notification Objective-C source. |
| v3/pkg/services/dock/dock_darwin.go | Excludes iOS from macOS dock service. |
| v3/pkg/mac/mac.go | Excludes iOS from macOS-only helper package build. |
| v3/pkg/events/known_events.go | Registers known iOS + Android event strings. |
| v3/pkg/events/events.txt | Adds Android events to the source list for generation. |
| v3/pkg/events/events.go | Renames iOS to IOS and adds generated Android event IDs/mapping. |
| v3/pkg/events/events_linux.go | Excludes Android from linux-only events implementation. |
| v3/pkg/events/events_ios.go | Removes old iOS event listener implementation. |
| v3/pkg/events/events_android.go | Removes old Android event listener implementation. |
| v3/pkg/doctor-ng/platform_ios.go | Adds iOS stubs so doctor-ng compiles under GOOS=ios. |
| v3/pkg/doctor-ng/platform_darwin.go | Excludes iOS from darwin doctor-ng implementation. |
| v3/pkg/application/webview_window_ios.m | Removes double initial navigation; replaces noisy logs with gated verbose logging. |
| v3/pkg/application/webview_window_ios.go | Fixes C-string leak in execJS + moves single initial navigation to Go side. |
| v3/pkg/application/webview_window_darwin_production.go | Excludes iOS from darwin production webview file. |
| v3/pkg/application/webview_window_android.go | Improves screen metrics integration + reduces noisy logging + wires listener registration. |
| v3/pkg/application/screen_ios.go | Implements real iOS screen metrics via native JSON + safe-area work area + fallback. |
| v3/pkg/application/screen_android.go | Implements Android screen metrics from bridge JSON + dp/px + insets + fallback. |
| v3/pkg/application/messageprocessor_ios.go | Adds empty androidMethodNames map for shared debug logging references on iOS. |
| v3/pkg/application/messageprocessor_android.go | Adds empty iosMethodNames map + implements Android haptics/device/toast via bridge. |
| v3/pkg/application/mainthread_android.go | Routes main-thread dispatch through bridge + adds JNI callback entrypoint. |
| v3/pkg/application/linux_cgo.h | Excludes Android from linux CGO header. |
| v3/pkg/application/linux_cgo.c | Excludes Android from linux CGO source. |
| v3/pkg/application/ios_logging_production.go | Adds production constant to compile out iOS verbose diagnostics. |
| v3/pkg/application/ios_logging_debug.go | Enables iOS verbose diagnostics for non-production builds. |
| v3/pkg/application/events_common_android.go | Removes noisy event forwarding log. |
| v3/pkg/application/environment_linux.go | Excludes Android from linux environment implementation. |
| v3/pkg/application/dialogs_ios.go | Implements iOS message + open-file dialogs; makes save-file return explicit error. |
| v3/pkg/application/dialogs_android.go | Implements Android message + open-file dialogs; explicit errors for save/dir picks. |
| v3/pkg/application/clipboard_ios.go | Implements iOS clipboard via native bridge. |
| v3/pkg/application/clipboard_android.go | Implements Android clipboard via bridge. |
| v3/pkg/application/application_ios.m | Adds verbose logging toggle + screen metrics + clipboard + dialogs + document picker. |
| v3/pkg/application/application_ios.h | Declares new iOS native APIs for logging/screen/clipboard/dialogs/picker callbacks. |
| v3/pkg/application/application_ios_delegate.m | Defers didFinishLaunching event emission to Go runtime via iosApplicationDidLaunch. |
| v3/pkg/application/application_android_nocgo.go | Refactors non-cgo Android stubs to keep tooling builds compiling; adds listener registry. |
| v3/pkg/application/android_logging_production.go | Disables Android verbose diagnostics in production builds. |
| v3/pkg/application/android_logging_debug.go | Enables Android verbose diagnostics in debug builds. |
| v3/IOS.md | Adds iOS guide (experimental) and usage notes. |
| v3/IOS_RUNTIME.md | Removes obsolete iOS runtime feature plan doc. |
| v3/IOS_FEATURES_TODO.md | Removes obsolete iOS TODO list doc. |
| v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts | Adds Android JavascriptInterface transport with callback plumbing. |
| v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts | Adds Android event types to the runtime TS mapping. |
| v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/navigation.js | Updates docs-site navigation asset bundle. |
| v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/hierarchy.js | Updates docs-site hierarchy asset bundle. |
| v3/internal/doctor/doctor_darwin.go | Reports Xcode + iOS SDK availability on macOS. |
| v3/internal/doctor/doctor_common.go | Adds Android SDK/NDK/JDK reporting to doctor. |
| v3/internal/commands/updatable_build_assets/ios/Info.plist.tmpl | Templates MinimumOSVersion; removes armv7 capability. |
| v3/internal/commands/updatable_build_assets/ios/Info.dev.plist.tmpl | Templates MinimumOSVersion; removes armv7 capability. |
| v3/internal/commands/updatable_build_assets/ios/entitlements.plist.tmpl | Simplifies entitlements template + clarifies device-only application. |
| v3/internal/commands/ios/main.m | Adds iOS bootstrap main (calls exported Go entry on background queue). |
| v3/internal/commands/ios/main_ios.go | Adds exported Go entrypoint wrapper for iOS builds. |
| v3/internal/commands/ios/LaunchScreen.storyboard | Adds iOS launch screen storyboard template. |
| v3/internal/commands/ios/Info.plist | Adds a sample iOS Info.plist for internal iOS command assets. |
| v3/internal/commands/ios/Info.dev.plist | Adds a sample dev Info.plist for internal iOS command assets. |
| v3/internal/commands/ios/icon.png | Adds an iOS icon placeholder file (currently text content). |
| v3/internal/commands/ios/entitlements.plist | Adds a sample iOS entitlements file for internal iOS command assets. |
| v3/internal/commands/ios/build.sh | Adds a sample iOS build script for internal iOS command assets. |
| v3/internal/commands/ios/Assets.xcassets | Adds an asset catalog description for iOS icons. |
| v3/internal/commands/ios/app_options_ios.go | Adjusts options on iOS builds (disables default signal handler). |
| v3/internal/commands/ios/app_options_default.go | No-op option modifier for non-iOS builds. |
| v3/internal/commands/ios_xcode_gen.go | Adds config support for minimum iOS version in generated projects. |
| v3/internal/commands/build-assets.go | Adds MinIOSVersion option to build assets/update build assets commands. |
| v3/internal/commands/build_assets/android/Taskfile.yml | Improves Android task pipeline (host-arch selection, NDK discovery, release signing). |
| v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsPathHandler.java | Reduces debug logging noise; adds DEBUG guard. |
| v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsJSBridge.java | Adds async runtime call transport + callback wiring; debug log gating. |
| v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/MainActivity.java | Adds file picker, lifecycle forwarding, and runtime injection adjustments. |
| v3/internal/commands/build_assets/android/app/src/main/AndroidManifest.xml | Adds VIBRATE permission for haptics. |
| v3/internal/commands/build_assets/android/app/build.gradle | Adds env-driven release signing fallback to debug keystore. |
| v3/internal/commands/android/settings.gradle | Adds Gradle settings for Android template project. |
| v3/internal/commands/android/scripts/deps/install_deps.go | Adds Android dependency installer/check script. |
| v3/internal/commands/android/main_android.go | Registers Android main function for c-shared mode. |
| v3/internal/commands/android/gradlew.bat | Adds Windows Gradle wrapper script. |
| v3/internal/commands/android/gradle/wrapper/gradle-wrapper.properties | Adds Gradle wrapper distribution configuration. |
| v3/internal/commands/android/gradle.properties | Adds standard Gradle properties (AndroidX, JVM args, etc.). |
| v3/internal/commands/android/build.gradle | Adds top-level Android Gradle build file with AGP plugin version. |
| v3/internal/commands/android/app/src/main/res/values/themes.xml | Adds Android theme resources. |
| v3/internal/commands/android/app/src/main/res/values/strings.xml | Adds Android string resources (app name). |
| v3/internal/commands/android/app/src/main/res/values/colors.xml | Adds Android color resources. |
| v3/internal/commands/android/app/src/main/res/layout/activity_main.xml | Adds Android activity layout containing the WebView. |
| v3/internal/commands/android/app/src/main/java/com/wails/app/WailsPathHandler.java | Adds Android asset loader path handler template. |
| v3/internal/commands/android/app/src/main/java/com/wails/app/WailsJSBridge.java | Adds Android JavascriptInterface bridge template. |
| v3/internal/commands/android/app/src/main/AndroidManifest.xml | Adds Android manifest template for the generated project. |
| v3/internal/commands/android/app/proguard-rules.pro | Adds ProGuard rules to keep bridge/native methods. |
| v3/internal/commands/android/app/build.gradle | Adds Android app Gradle module build file (SDK/ABIs/signing). |
| v3/fix-darwin-ios-constraints.sh | Removes build-constraint sweep helper script. |
| v3/examples/ios/README.md | Updates iOS example README with iOS task usage. |
| v3/examples/ios/frontend/package-lock.json | Updates runtime dependency resolution for the iOS example frontend. |
| v3/examples/ios/frontend/main.js | Fixes example bindings import path. |
| v3/examples/ios/frontend/bindings/ios-example/index.js | Adds generated bindings for the iOS example. |
| v3/examples/ios/frontend/bindings/ios-example/greetservice.js | Adds generated bindings for the iOS example service. |
| v3/examples/ios/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts | Adds generated binding stub. |
| v3/examples/ios/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventcreate.js | Adds generated event create helper. |
| v3/examples/ios/build/ios/xcode/overlay.json | Updates example overlay.json paths. |
| v3/examples/ios/build/ios/xcode/main/main.m | Adds iOS build tag header to example main.m. |
| v3/examples/ios/build/ios/xcode/main/LaunchScreen.storyboard | Normalizes whitespace in storyboard output. |
| v3/examples/ios/build/ios/xcode/main/Info.plist | Removes armv7 capability + normalizes whitespace. |
| v3/examples/ios/build/ios/entitlements.plist | Simplifies entitlements template + clarifies usage. |
| v3/examples/android/README.md | Adds Android example README and task usage. |
| v3/examples/android/frontend/package.json | Formatting-only change (closing brace). |
| v3/examples/android/frontend/package-lock.json | Switches runtime dependency to local file link in lockfile. |
| v3/examples/android/build/android/Taskfile.yml | Updates example Android Taskfile (NDK discovery, signing, emulator tooling). |
| v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsPathHandler.java | Reduces debug logging noise in example build output. |
| v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java | Updates async runtime transport + callback wiring in example build output. |
| v3/examples/android/build/android/app/src/main/java/com/wails/app/MainActivity.java | Adds file picker + lifecycle forwarding + runtime injection adjustments in example build output. |
| v3/examples/android/build/android/app/src/main/AndroidManifest.xml | Adds VIBRATE permission in example build output. |
| v3/examples/android/build/android/app/build.gradle | Adds env-driven release signing fallback in example build output. |
| v3/examples/android/.task/checksum/generate-bindings--BUILD_FLAGS--tags-android-debug--buildvcs-false--gcflags-all---l-- | Removes task cache artifact from repo. |
| v3/examples/android/.task/checksum/build-frontend--PRODUCTION-- | Removes task cache artifact from repo. |
| v3/examples/android/.task/checksum/android-generate-android-bindings | Removes task cache artifact from repo. |
| v3/examples/android/.task/checksum/android-common-install-frontend-deps | Removes task cache artifact from repo. |
| v3/examples/android/.task/checksum/android-common-generate-icons | Removes task cache artifact from repo. |
| v3/examples/android/.gitignore | Ignores additional Android build output + task cache. |
| v3/build_ios.sh | Removes obsolete iOS build script. |
| v3/ANDROID.md | Adds Android guide (experimental) and usage notes. |
| docs/src/content/docs/guides/build/ios.mdx | Adds docs-site iOS build guide (experimental). |
| docs/src/content/docs/guides/build/android.mdx | Adds docs-site Android build guide (experimental). |
| docs/astro.config.mjs | Adds iOS + Android links into docs navigation. |
Files not reviewed (2)
- v3/examples/android/frontend/package-lock.json: Language not supported
- v3/examples/ios/frontend/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 18
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
v3/pkg/application/application_android.go (1)
658-683:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftPreserve the original request metadata when proxying Android asset requests.
nativeServeAsset()acceptsjheaders, butserveAssetForAndroid()rebuilds a freshhttp.Requestfrom onlymethodandpathand then forcesreq.Body = http.NoBody. That means any non-/wails/runtimehandler behindapp.assetswill see empty bodies and none of the original headers on Android, so same-originfetch()APIs diverge from desktop/iOS.Also applies to: 780-808
🤖 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/application_android.go` around lines 658 - 683, nativeServeAsset currently drops the incoming request headers and body because it only passes method and path into serveAssetForAndroid and forces req.Body = http.NoBody; modify nativeServeAsset and serveAssetForAndroid so the JNI-provided jheaders (and any request body bytes if present) are parsed and rehydrated onto the proxied *http.Request: convert jheaders into an http.Header and assign to req.Header, restore the original Content-Length/Transfer-Encoding and a non-empty req.Body when the incoming request carried one, and ensure special headers like Host and Content-Type are preserved; update both Java_com_wails_app_WailsBridge_nativeServeAsset and serveAssetForAndroid (and related request-construction logic referenced around lines with serveAssetForAndroid and the 780-808 region) to forward the full request metadata instead of discarding it.v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/MainActivity.java (1)
88-153:⚠️ Potential issue | 🟠 MajorPin WebView navigation/origin before exposing
window.wails(MainActivity.java)
MainActivityinitially loadshttps://wails.localhost/, but theWebViewClientonly interceptswails.localhostrequests and does not implementshouldOverrideUrlLoading/any origin allowlist. That means any navigation/redirect to a remote origin will still havewebView.addJavascriptInterface(..., "wails")available. Also, the/wails/*responses returned fromshouldInterceptRequestincludeAccess-Control-Allow-Origin: *, letting an off-origin page fetchwails.localhost/wails/*endpoints.Block external navigations (or strictly allow only
wails.localhost) and gate/remove thewailsJS interface when the loaded page is not onWAILS_HOST.🤖 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/internal/commands/build_assets/android/app/src/main/java/com/wails/app/MainActivity.java` around lines 88 - 153, The WebView exposes window.wails to any origin and also returns CORS headers allowing any origin; fix by gating the JS interface and tightening navigation and CORS: implement WebViewClient.shouldOverrideUrlLoading to block or redirect any navigation whose request.getUrl().getHost() is not WAILS_HOST (only allow wails.localhost), only call webView.addJavascriptInterface(new WailsJSBridge(...), "wails") after verifying the initial/page URL host equals WAILS_HOST (use onPageFinished to add the interface if host==WAILS_HOST and call webView.removeJavascriptInterface("wails") when host!=WAILS_HOST), and change the headers returned by bridge.serveAsset(...) in shouldInterceptRequest to set Access-Control-Allow-Origin to the exact WAILS_HOST origin (or omit the wildcard) for /wails/* responses instead of "*" so off-origin pages cannot fetch API endpoints. Use the existing symbols WAILS_HOST, WebViewClient.shouldInterceptRequest, shouldOverrideUrlLoading, onPageFinished, webView.addJavascriptInterface, webView.removeJavascriptInterface, WailsJSBridge, bridge, and assetLoader to locate and apply these changes.v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java (1)
136-142:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
escapeJsStringdoesn't escape Unicode line terminators U+2028 and U+2029.JavaScript treats U+2028 (LINE SEPARATOR) and U+2029 (PARAGRAPH SEPARATOR) as line terminators even inside string literals, but JSON allows them. If
resultorerrorcontains these characters (e.g., from user input that was JSON-encoded Go-side), the generated JavaScript at lines 121 or 127 will have a syntax error, breaking the callback and potentially creating an injection vector.🛡️ Proposed fix to escape Unicode line terminators
private String escapeJsString(String str) { if (str == null) return ""; return str.replace("\\", "\\\\") .replace("'", "\\'") .replace("\n", "\\n") - .replace("\r", "\\r"); + .replace("\r", "\\r") + .replace("\u2028", "\\u2028") + .replace("\u2029", "\\u2029"); }🤖 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/android/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java` around lines 136 - 142, escapeJsString currently only escapes backslashes, single quotes, CR and LF but misses Unicode line terminators U+2028 and U+2029, which can break the generated JS callbacks; update escapeJsString to also replace '\u2028' with "\\u2028" and '\u2029' with "\\u2029" so any callers (e.g., where result or error values are injected into JS in WailsJSBridge) produce safe string literals; ensure the replacement order preserves existing escapes (keep backslash escaping first) and add a unit test or sample input to verify both terminators are correctly escaped.v3/examples/android/build/android/app/src/main/AndroidManifest.xml (1)
16-16:⚠️ Potential issue | 🟠 MajorRemove/limit
android:usesCleartextTraffic="true"in AndroidManifest templates
android:usesCleartextTraffic="true"is set in all provided AndroidManifest templates, but the app code loadshttps://wails.localhost/(WAILS_SCHEME = "https") and serves content viaWebViewAssetLoader/shouldInterceptRequest—there’s no cleartext HTTP dependency in the template flow.- Set
usesCleartextTraffictofalseby default (or apply only to debug/local builds if there’s a specific dev-only need).🤖 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/android/build/android/app/src/main/AndroidManifest.xml` at line 16, The AndroidManifest currently enables android:usesCleartextTraffic="true" but the app uses WAILS_SCHEME = "https" and WebViewAssetLoader/shouldInterceptRequest so cleartext is unnecessary; change the manifest templates to set android:usesCleartextTraffic="false" by default (or conditionally enable it only for debug/local builds), update any manifest template files where android:usesCleartextTraffic appears, and if a dev-only HTTP need exists, wrap the true setting behind a build-variant or manifest-merger override rather than leaving it enabled in the shipped templates.
🟡 Minor comments (9)
v3/pkg/application/dialogs_android.go-66-70 (1)
66-70:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winHandle JSON marshaling errors.
The
json.Marshalerror is ignored. If marshaling fails, invalid JSON will be passed to the native bridge, potentially causing a crash or unexpected dialog behavior.🛡️ Proposed fix to handle marshaling errors
- optionsJSON, _ := json.Marshal(androidDialogOptions{ + optionsJSON, err := json.Marshal(androidDialogOptions{ Title: title, Message: d.dialog.Message, Buttons: buttons, }) + if err != nil { + // Log or handle the error appropriately + return + }🤖 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/dialogs_android.go` around lines 66 - 70, The json.Marshal call for androidDialogOptions currently ignores errors; update the code that builds optionsJSON (the json.Marshal(androidDialogOptions{ Title: title, Message: d.dialog.Message, Buttons: buttons })) to capture the error, handle it (e.g., log via the existing logger or return the error from the surrounding function), and avoid calling the native bridge with invalid JSON—ensure you check the err variable and either return or gracefully fail before using optionsJSON.v3/pkg/application/dialogs_ios.go-59-59 (1)
59-59:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winHandle JSON marshaling errors.
The
json.Marshalerror is ignored. If marshaling fails, invalid JSON will be passed to the native iOS bridge, potentially causing a crash or unexpected dialog behavior.🛡️ Proposed fix to handle marshaling errors
- buttonsJSON, _ := json.Marshal(buttons) + buttonsJSON, err := json.Marshal(buttons) + if err != nil { + // Log or handle the error appropriately + return + }🤖 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/dialogs_ios.go` at line 59, The code currently ignores the error from json.Marshal when creating buttonsJSON, which can pass invalid JSON to the iOS bridge; update the function containing the json.Marshal(buttons) call to check the returned error, handle it by logging the error (or returning it to the caller) and aborting or using a safe fallback instead of sending invalid data; specifically, after json.Marshal(buttons) validate err != nil, use the enclosing function (the dialog construction/sending routine) to call processLogger or return the error so the native bridge is not invoked with a bad buttonsJSON.v3/pkg/application/dialogs_android.go-131-133 (1)
131-133:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winHandle JSON marshaling errors.
The
json.Marshalerror is ignored. If marshaling fails, invalid JSON will be passed to the native bridge, potentially causing incorrect file picker behavior.🛡️ Proposed fix to handle marshaling errors
- optionsJSON, _ := json.Marshal(androidFilePickerOptions{ + optionsJSON, err := json.Marshal(androidFilePickerOptions{ Multiple: d.dialog.allowsMultipleSelection, }) + if err != nil { + return nil, fmt.Errorf("failed to marshal file picker options: %w", err) + }🤖 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/dialogs_android.go` around lines 131 - 133, The json.Marshal call for androidFilePickerOptions ignores errors and can send invalid JSON to the native bridge; update the code around optionsJSON := json.Marshal(androidFilePickerOptions{Multiple: d.dialog.allowsMultipleSelection}) to check the returned error, and on error either return it (propagate from the surrounding function) or log and abort the native call so you don't pass invalid JSON to the bridge; reference the androidFilePickerOptions type, the optionsJSON variable, and d.dialog.allowsMultipleSelection when locating the statement to add error handling.v3/internal/commands/android/Taskfile.yml-196-201 (1)
196-201:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAVD selection may fail if
emulator -list-avdsoutput ends with a blank line.Line 196 uses
tail -1to pick the last line from the AVD list. If theemulatorcommand's output ends with a newline (common in shell utilities),AVD_NAMEwill be empty and the error message on line 198 will be shown even when AVDs exist.🛠️ Suggested fix to filter blank lines
# Get first available AVD - AVD_NAME=$("{{.EMULATOR}}" -list-avds | tail -1) + AVD_NAME=$("{{.EMULATOR}}" -list-avds | grep -v '^$' | tail -1) if [ -z "$AVD_NAME" ]; then🤖 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/internal/commands/android/Taskfile.yml` around lines 196 - 201, The AVD selection can return an empty string when "{{.EMULATOR}} -list-avds" outputs a trailing blank line; update the AVD_NAME assignment to filter out empty lines before taking the last entry (i.e., run the emulator list through a filter such as grep -v '^$' or sed '/^$/d' or awk 'NF' and then use tail -n1) so AVD_NAME reliably contains the last non-empty AVD name and the subsequent check using AVD_NAME works as intended.v3/internal/commands/android/app/build.gradle-30-37 (1)
30-37:⚠️ Potential issue | 🟡 MinorAdd fail-fast validation for Android release keystore env vars
v3/internal/commands/android/app/build.gradleenablessigningConfigs.releasewhen onlyANDROID_KEYSTORE_FILEis set, but it always readsANDROID_KEYSTORE_PASSWORD,ANDROID_KEY_ALIAS, andANDROID_KEY_PASSWORD; if any are missing, the release build can fail later with a confusing error. Add validation for all four variables (or clearly document they must be set together—v3/ANDROID.mdprovides an example with all of them).🤖 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/internal/commands/android/app/build.gradle` around lines 30 - 37, The signingConfigs.release block reads multiple keystore env vars but only checks ANDROID_KEYSTORE_FILE; update signingConfigs.release to perform fail-fast validation that all four env vars (ANDROID_KEYSTORE_FILE, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD) are present before using them (or skip configuring release signing if any are missing), and emit a clear error/log message when they are partially provided; locate the signingConfigs.release block in build.gradle to add the validation and error handling.v3/internal/commands/build_assets/ios/Taskfile.yml-85-95 (1)
85-95:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFail fast when device-only tasks are invoked with simulator defaults.
With
IOS_PLATFORM=simulatorandCODESIGN_IDENTITY=-as the defaults,package:ipacan zip a simulator bundle, anddeploy-devicecan try to install whatever stale.apphappens to be inbin. Add explicit preconditions forIOS_PLATFORM=deviceplus a real signing identity, and makedeploy-devicebuild/package the device app first.Also applies to: 157-166, 177-190
🤖 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/internal/commands/build_assets/ios/Taskfile.yml` around lines 85 - 95, The package:ipa Taskfile task currently runs unguarded and can package simulator builds; update the package:ipa task to fail fast unless IOS_PLATFORM equals "device" and CODESIGN_IDENTITY is not "-" (or empty) by adding an explicit precondition check at the top of the task (use the task's preconditions/vars mechanism), and update the deploy-device task to ensure it first invokes the device build/package steps (e.g., call the existing package or build task that produces a device-signed .app/.ipa) before attempting installation; apply the same precondition enforcement to the other device-only tasks referenced around the same area (lines 157-166 and 177-190) to prevent running with simulator defaults.v3/internal/commands/ios/scripts/deps/install_deps.go-241-241 (1)
241-241:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winHardcoded "iPhone 15 Pro" simulator may not exist in older Xcode versions.
Line 241 creates an "iPhone 15 Pro" simulator via
xcrun simctl create "iPhone 15 Pro" "iPhone 15 Pro" .... The device type identifier "iPhone 15 Pro" was introduced in Xcode 15. Users running Xcode 14 or earlier will encounterxcrun simctl createerrors like "Invalid device type: iPhone 15 Pro."Proposed fix
Query available device types dynamically and select a fallback:
// Get available device types deviceOut, err := exec.Command("xcrun", "simctl", "list", "devicetypes").Output() if err == nil { lines := strings.Split(string(deviceOut), "\n") var deviceType string // Prefer iPhone 15 Pro, fallback to any iPhone for _, line := range lines { if strings.Contains(line, "iPhone 15 Pro") { deviceType = "iPhone 15 Pro" break } else if strings.Contains(line, "iPhone") && deviceType == "" { // Extract first available iPhone type parts := strings.Fields(line) if len(parts) > 0 { deviceType = parts[0] } } } if deviceType != "" { createCmd := []string{"xcrun", "simctl", "create", deviceType, deviceType, latestRuntime} // ... proceed } }🤖 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/internal/commands/ios/scripts/deps/install_deps.go` at line 241, The hardcoded simulator device type "iPhone 15 Pro" (the createCmd variable used to run xcrun simctl create with latestRuntime) can fail on older Xcode; modify the logic that builds createCmd so it first queries available device types via `xcrun simctl list devicetypes`, prefer "iPhone 15 Pro" if present, otherwise pick a suitable fallback iPhone device type (extracting the device identifier/token from the list) and then construct createCmd using that chosen deviceType and latestRuntime; ensure you handle errors from the devicetypes query and only run the create command when a valid deviceType was found.v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsBridge.java-207-214 (1)
207-214:⚠️ Potential issue | 🟡 MinorReframe XSS risk in
WailsBridge.executeJavaScript: sink is raw JS, but key Go payloads are JSON-encoded
WailsBridge.executeJavaScript(String js)forwardsjsdirectly toWebView.evaluateJavascript(js, null)with no sanitization, so any caller that interpolates untrusted text into the JS string would be an XSS/JS-injection surface.The main Android-reachable dynamic call sites in Go we found avoid raw string interpolation by embedding JSON:
WebviewWindow.DispatchWailsEvent:dispatchWailsEvent(%s)where%sisCustomEvent.ToJSON()(usesjson.Marshal)WebviewWindow.InitiateFrontendDropProcessing:handlePlatformFileDrop(%s, ...)where%sisjson.Marshal(filenames)(inserted as a JSON expression)Action: audit remaining
ExecJS/execJScall paths to ensure any user-controlled string content is JSON-encoded/escaped before it’s interpolated into the JS string passed toexecuteJavaScript.🤖 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/android/build/android/app/src/main/java/com/wails/app/WailsBridge.java` around lines 207 - 214, WailsBridge.executeJavaScript currently forwards raw JS to WebView.evaluateJavascript, creating a JS-injection/XSS sink when callers interpolate untrusted data; audit all callers of executeJavaScript/ExecJS/execJS (notably WebviewWindow.DispatchWailsEvent and WebviewWindow.InitiateFrontendDropProcessing) and ensure any user-controlled strings are JSON-encoded (e.g., via json.Marshal or equivalent) before being embedded in the JS expression, or refactor call sites to pass structured JSON values into a fixed JS wrapper rather than concatenating raw strings; update any unsafe call sites to perform proper JSON encoding/escaping before calling executeJavaScript.v3/ANDROID.md-162-175 (1)
162-175:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd language specifier to fenced code block.
The fenced code block is missing a language identifier. Add a language specifier (e.g.,
textortree) to silence the linter warning.📝 Proposed fix
-``` +```text build/android/ app/🤖 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/ANDROID.md` around lines 162 - 175, The fenced code block showing the build/android tree in v3/ANDROID.md is missing a language specifier; update the opening triple-backticks that precede the "build/android/" block to include a language like text or tree (e.g., change ``` to ```text) so the linter warning is resolved while preserving the block contents.Source: Linters/SAST tools
🧹 Nitpick comments (12)
v3/internal/commands/android/Taskfile.yml (1)
69-73: ⚖️ Poor tradeoffHost toolchain detection doesn't account for Apple Silicon.
Line 70 hardcodes
HOST_TAG="darwin-x86_64"for all macOS hosts, but Apple Silicon Macs (M1/M2/M3) usearm64architecture. The NDK provides bothdarwin-x86_64anddarwin-arm64toolchains. On Apple Silicon, the wrong toolchain may cause performance issues (Rosetta translation) or build failures.🔧 Suggested fix to detect the correct NDK toolchain
# Determine toolchain based on host OS case "$(uname -s)" in - Darwin) HOST_TAG="darwin-x86_64" ;; + Darwin) + if [ "$(uname -m)" = "arm64" ]; then + HOST_TAG="darwin-arm64" + else + HOST_TAG="darwin-x86_64" + fi + ;; Linux) HOST_TAG="linux-x86_64" ;; *) echo "Unsupported host OS"; exit 1 ;; esac🤖 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/internal/commands/android/Taskfile.yml` around lines 69 - 73, The host OS detection hardcodes HOST_TAG="darwin-x86_64" and doesn't handle Apple Silicon; update the case logic around the HOST_TAG assignment (the case "$(uname -s)" block) to inspect architecture (e.g., uname -m) on Darwin and set HOST_TAG to "darwin-arm64" when on arm64 (M1/M2/M3), keep "darwin-x86_64" for x86_64, and preserve the existing Linux and default branches; ensure a clear fallback path if an unknown architecture is found.v3/internal/commands/android/app/src/main/java/com/wails/app/MainActivity.java (1)
327-332: Replace deprecatedonBackPressed()withOnBackPressedCallback(OnBackPressedDispatcher).
v3/internal/commands/android/app/src/main/java/com/wails/app/MainActivity.javaoverridesonBackPressed()(lines 327-332), while the app targetstargetSdk 34(v3/internal/commands/android/app/build.gradle), so this will trigger deprecation/lint on API 33+. Use anOnBackPressedCallback(registered inonCreate) to runwebView.goBack()whencanGoBack(), and otherwise delegate to the dispatcher’s default back behavior.🤖 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/internal/commands/android/app/src/main/java/com/wails/app/MainActivity.java` around lines 327 - 332, Replace the deprecated override of onBackPressed in MainActivity with a registered OnBackPressedCallback on the activity’s OnBackPressedDispatcher (register it in onCreate); inside the callback check webView != null && webView.canGoBack() and call webView.goBack() when true, otherwise call requireActivity().getOnBackPressedDispatcher().onBackPressed() (or disable the callback to let the default dispatcher handle it) so the back behavior is delegated correctly; remove the onBackPressed override after adding the callback.v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsJSBridge.java (1)
52-61: ⚡ Quick winAvoid creating a raw thread per runtime call.
invokeAsync()sits on the main JS→native transport path, and this spawns an unboundedThreadfor every call. Prefer a shared executor, or callbridge.handleRuntimeCall()directly if this method is already off the UI thread, so bursty frontend traffic does not turn into thread explosion.🤖 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/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsJSBridge.java` around lines 52 - 61, The current invokeAsync path spawns a raw Thread per call (the new Thread(...) block that calls bridge.handleRuntimeCall and sendCallback), which can exhaust threads under bursty traffic; replace this with a shared ExecutorService (e.g., a static cached or fixed thread pool) and submit Runnables to it, or, if invokeAsync is already guaranteed to run off the main/UI thread, call bridge.handleRuntimeCall(payload) directly and then sendCallback(callbackId, response, null) without creating a new Thread; update the code around invokeAsync/bridge.handleRuntimeCall/sendCallback to use the shared executor (or direct call) and ensure exceptions are caught and logged via Log.e(TAG, ...).v3/internal/commands/ios/Taskfile.yml (1)
229-229: 💤 Low valueConsider a more precise UUID pattern for robustness.
The current pattern
[A-F0-9-]\{36\}matches any 36-character sequence of hex digits and dashes, which could theoretically match malformed strings. A more precise UUID pattern would be:grep -oE '[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}'This ensures the UUID structure (8-4-4-4-12) is validated. However, since
simctloutput is well-formatted, the current pattern should work in practice.🤖 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/internal/commands/ios/Taskfile.yml` at line 229, The DEVICE_ID extraction uses a loose regex `[A-F0-9-]\{36\}` which can match malformed sequences; update the command that sets DEVICE_ID to use a precise UUID regex (8-4-4-4-12) via an extended regex (e.g., use grep -oE with the pattern '[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}') so the variable assigned in the DEVICE_ID assignment (the line using xcrun simctl list devices available | grep "iPhone" | ... ) only captures properly formatted UUIDs.v3/internal/commands/ios_xcode_gen.go (1)
161-163: ⚡ Quick winConsider validating
MinIOSVersionformat.The code copies
in.IOS.MinIOSVersionintocfg.MinIOSVersionwithout validating the format. Users may provide invalid values like"15"(missing minor version),"iOS 15.0"(prefixed), or"latest"(non-numeric). These will be embedded directly into theInfo.plisttemplates (lines 22 of both plist.tmpl files) and the Xcode build settings, potentially causing App Store rejection or runtime crashes.Suggested validation
Add a regex check after line 163:
if in.IOS.MinIOSVersion != "" { cfg.MinIOSVersion = in.IOS.MinIOSVersion // Validate format: major.minor (e.g., "15.0", "16.2") matched, _ := regexp.MatchString(`^\d+\.\d+$`, cfg.MinIOSVersion) if !matched { return fmt.Errorf("invalid ios.minIOSVersion: %q (expected format: major.minor, e.g., 15.0)", cfg.MinIOSVersion) } }Import
regexpat the top of the file.🤖 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/internal/commands/ios_xcode_gen.go` around lines 161 - 163, The code assigns in.IOS.MinIOSVersion to cfg.MinIOSVersion without validating format; add a validation step after setting cfg.MinIOSVersion that enforces a major.minor numeric pattern (e.g., "15.0") using regexp (e.g., `^\d+\.\d+$`), return an error (fmt.Errorf) if it doesn't match, and import the regexp package at the top of the file; reference the variables in.IOS.MinIOSVersion and cfg.MinIOSVersion where you add the check.v3/internal/commands/updatable_build_assets/ios/entitlements.plist.tmpl (1)
5-7: ⚡ Quick winRemoved entitlements may be required by some apps.
The AI summary notes that app sandbox (
com.apple.security.app-sandbox), network client (com.apple.security.network.client), and user-selected file access (com.apple.security.files.user-selected.read-only) entitlements were removed. While this simplifies the template, apps making network requests or accessing user files will need to manually add these entitlements back, or they may fail at runtime with security/capability errors.Since this PR is marked experimental, consider either:
- Documenting the minimal entitlements set in
IOS.mdorANDROID.mdand instructing users to add additional entitlements as needed, or- Making entitlements configurable via
build/config.yml(e.g.,ios.entitlements: [network, files]) and conditionally rendering them in the template.🤖 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/internal/commands/updatable_build_assets/ios/entitlements.plist.tmpl` around lines 5 - 7, The entitlements template removed important keys (com.apple.security.app-sandbox, com.apple.security.network.client, com.apple.security.files.user-selected.read-only) which some apps need; either document the minimal entitlements in IOS.md (add a section listing these three keys and instructions to re-enable them) or make entitlements configurable via build/config.yml (add a key like ios.entitlements: [sandbox, network-client, user-files]) and update entitlements.plist.tmpl to conditionally render the corresponding plist keys (com.apple.security.app-sandbox, com.apple.security.network.client, com.apple.security.files.user-selected.read-only) when those config flags are present.v3/internal/commands/ios/scripts/deps/install_deps.go (1)
262-264: 💤 Low valueFragile string matching for counting iPhone simulators.
Lines 262-264 count simulators by checking
strings.Contains(line, "iPhone") && !strings.Contains(line, "unavailable"). This approach can miscount if:
- A line contains "iPhone" in a label/comment but isn't a device entry
- Device names or states change format in future
simctlversions- Non-iPhone devices (e.g., "iPhone Simulator Host") appear in output
More robust parsing
Parse the structured output or use a more specific pattern:
// Look for lines matching typical device format: // " iPhone 15 Pro (UUID) (Booted)" count := 0 for _, line := range lines { trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "iPhone") && strings.Contains(trimmed, "(") { count++ } }Alternatively, parse
xcrun simctl list devices --jsonfor structured output.🤖 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/internal/commands/ios/scripts/deps/install_deps.go` around lines 262 - 264, The current simulator counting loop that checks strings.Contains(line, "iPhone") && !strings.Contains(line, "unavailable") is fragile; update the logic in the loop that populates count (the code iterating over lines and incrementing count) to more reliably detect actual iPhone device entries — either (preferred) call "xcrun simctl list devices --json" and parse the JSON to count devices with name starting with "iPhone" and state != "unavailable"/"Shutdown", or (simpler) trim each line and check that it HasPrefix("iPhone") and contains a device UUID/token like "(" to avoid matching labels/comments before incrementing count; replace the existing string.Contains checks in that loop with one of these approaches.v3/examples/android/build/android/Taskfile.yml (1)
196-196: ⚡ Quick winComment-code mismatch: "first" vs last AVD.
The comment says "Get first available AVD" but
tail -1retrieves the last AVD from the list. Consider updating the comment to match the implementation, or usehead -1if the first AVD is actually preferred.📝 Suggested comment fix
- # Get first available AVD + # Get last available AVD AVD_NAME=$("{{.EMULATOR}}" -list-avds | tail -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/android/build/android/Taskfile.yml` at line 196, The AVD selection line assigns AVD_NAME using "{{.EMULATOR}} -list-avds | tail -1" which picks the last AVD but the comment says "Get first available AVD"; either update the comment to reflect that AVD_NAME picks the last AVD, or change the pipeline to use head -1 to actually select the first AVD; locate the AVD_NAME assignment (the variable AVD_NAME and the command string "{{.EMULATOR}} -list-avds | tail -1") and make the appropriate change so comment and implementation match.v3/examples/android/build/android/app/src/main/java/com/wails/app/MainActivity.java (1)
212-220: ⚖️ Poor tradeoffBackground thread may leak if activity is destroyed during file copy.
The file-copying thread started at line 212 is not tracked or interrupted when the activity is destroyed. If the user navigates away or the system destroys the activity while files are being copied, the thread continues running, holding references to the activity context (via
getContentResolver(),getCacheDir()), potentially causing a memory leak and preventing timely garbage collection.Consider using an
ExecutorServicethat can be shut down inonDestroy(), or tracking the thread and interrupting it during cleanup. For most use cases the leak is minor (short-lived copy operation), but could accumulate if users repeatedly trigger pickers then navigate away.🤖 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/android/build/android/app/src/main/java/com/wails/app/MainActivity.java` around lines 212 - 220, The background Thread that runs copyUriToCache(...) and calls bridge.filePickerResult(...) / bridge.filePickerDone(...) can leak the Activity because it uses getContentResolver() and getCacheDir(); instead replace the raw Thread with a managed ExecutorService (e.g., a single-threaded ExecutorService) stored as a field and submit the Runnable to it, and shut it down (or call shutdownNow()) in onDestroy() to cancel ongoing work; additionally, make copyUriToCache(...) context-independent where possible or ensure it checks Thread.interrupted() and returns early so pending tasks can stop promptly when the ExecutorService is shut down.v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsBridge.java (1)
309-321: ⚡ Quick winClipboard read may silently fail on Android 10+ without input focus.
The comment at line 298 mentions that clipboard reads on Android 10+ require input focus, but
getClipboardText()doesn't verify this precondition or provide any indication to the caller that the read failed due to lack of focus. It returns an empty string both when the clipboard is genuinely empty and when access is denied, making it impossible for the Go side to distinguish between these cases or provide user feedback.Consider logging a warning when clipboard access is attempted on Android 10+ without focus, or document this limitation clearly for developers.
🤖 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/android/build/android/app/src/main/java/com/wails/app/WailsBridge.java` around lines 309 - 321, Detect Android 10+ focus loss before attempting clipboard access in getClipboardText: check android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q and !activity.hasWindowFocus(), and in that case Log.w(TAG, "clipboard read requires input focus on Android 10+; access denied") and return null (instead of empty string) so the caller can distinguish denied access from an empty clipboard; keep the existing try/catch fallback for other errors and update any callers that expect empty-string to handle a null return as "access denied".v3/examples/ios/build/ios/Taskfile.yml (1)
220-258: 💤 Low valueConsider parameterizing the fallback iPhone device type.
Line 235 hardcodes
"iPhone 15"as the fallback when no device type is detected. This will become outdated as newer Xcode versions ship with newer default simulators.♻️ Parameterize the fallback device type
+ FALLBACK_IPHONE_DEVICE: '{{.FALLBACK_IPHONE_DEVICE | default "iPhone 15"}}'Then reference it on line 235:
- DEVICE_ID=$(xcrun simctl create "Wails iPhone" "${DEVICE_TYPE:-iPhone 15}" "$RUNTIME") + DEVICE_ID=$(xcrun simctl create "Wails iPhone" "${DEVICE_TYPE:-{{.FALLBACK_IPHONE_DEVICE}}}" "$RUNTIME")🤖 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/ios/build/ios/Taskfile.yml` around lines 220 - 258, The ensure-simulator task currently hardcodes the fallback device type "iPhone 15" when DEVICE_TYPE detection fails; introduce a configurable fallback variable (e.g., FALLBACK_DEVICE_TYPE or a Taskfile variable referenced in the ensure-simulator task) and use that variable in place of the literal "iPhone 15" in the DEVICE_ID creation step so maintainers can update the default without editing the script; update the task to read a passed-in env/Taskfile variable (and document its name) and replace the "${DEVICE_TYPE:-iPhone 15}" usage with the new variable reference (e.g., "${DEVICE_TYPE:-${FALLBACK_DEVICE_TYPE}}").v3/examples/ios/README.md (1)
18-76: ⚡ Quick winRemove or update the generic template content.
The old "Welcome to Your New Wails3 Project" template content (lines 18-76) remains below the iOS-specific introduction. For an iOS example project, this generic guidance is potentially confusing — especially commands like
wails3 devandwails3 buildon lines 29 and 37, which don't represent the iOS workflow shown above.Consider either removing this section entirely or replacing it with iOS-specific next steps.
🤖 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/ios/README.md` around lines 18 - 76, Remove or replace the generic "Welcome to Your New Wails3 Project!" section (the block including the headings and the commands `wails3 dev` and `wails3 build`) because it conflicts with the iOS-specific introduction; either delete that entire section or rewrite it with iOS-specific next steps (e.g., how to run the iOS example, any Xcode/simulator instructions, and links to iOS-focused docs) so the README's content aligns with the iOS example.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2c1fc0c4-30fd-41d3-92bf-9338cee51c61
⛔ Files ignored due to path filters (14)
v3/examples/android/frontend/package-lock.jsonis excluded by!**/package-lock.jsonv3/examples/ios/frontend/package-lock.jsonis excluded by!**/package-lock.jsonv3/internal/commands/android/app/src/main/res/mipmap-hdpi/ic_launcher.pngis excluded by!**/*.pngv3/internal/commands/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/internal/commands/android/app/src/main/res/mipmap-mdpi/ic_launcher.pngis excluded by!**/*.pngv3/internal/commands/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/internal/commands/android/app/src/main/res/mipmap-xhdpi/ic_launcher.pngis excluded by!**/*.pngv3/internal/commands/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/internal/commands/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngis excluded by!**/*.pngv3/internal/commands/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/internal/commands/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngis excluded by!**/*.pngv3/internal/commands/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/internal/commands/android/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jarv3/internal/commands/ios/icon.pngis excluded by!**/*.png
📒 Files selected for processing (141)
docs/astro.config.mjsdocs/src/content/docs/guides/build/android.mdxdocs/src/content/docs/guides/build/ios.mdxv3/ANDROID.mdv3/ANDROID_ARCHITECTURE.mdv3/IOS.mdv3/IOS_ARCHITECTURE.mdv3/IOS_FEATURES_TODO.mdv3/IOS_RUNTIME.mdv3/UNRELEASED_CHANGELOG.mdv3/build_ios.shv3/examples/android/.gitignorev3/examples/android/.task/checksum/android-common-generate-iconsv3/examples/android/.task/checksum/android-common-install-frontend-depsv3/examples/android/.task/checksum/android-generate-android-bindingsv3/examples/android/.task/checksum/build-frontend--PRODUCTION--v3/examples/android/.task/checksum/generate-bindings--BUILD_FLAGS--tags-android-debug--buildvcs-false--gcflags-all---l--v3/examples/android/README.mdv3/examples/android/build/android/Taskfile.ymlv3/examples/android/build/android/app/build.gradlev3/examples/android/build/android/app/src/main/AndroidManifest.xmlv3/examples/android/build/android/app/src/main/java/com/wails/app/MainActivity.javav3/examples/android/build/android/app/src/main/java/com/wails/app/WailsBridge.javav3/examples/android/build/android/app/src/main/java/com/wails/app/WailsJSBridge.javav3/examples/android/build/android/app/src/main/java/com/wails/app/WailsPathHandler.javav3/examples/android/build/android/build/reports/problems/problems-report.htmlv3/examples/android/frontend/package.jsonv3/examples/ios/README.mdv3/examples/ios/build/ios/Taskfile.ymlv3/examples/ios/build/ios/entitlements.plistv3/examples/ios/build/ios/xcode/main/Info.plistv3/examples/ios/build/ios/xcode/main/LaunchScreen.storyboardv3/examples/ios/build/ios/xcode/main/main.mv3/examples/ios/build/ios/xcode/overlay.jsonv3/examples/ios/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventcreate.jsv3/examples/ios/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.tsv3/examples/ios/frontend/bindings/ios-example/greetservice.jsv3/examples/ios/frontend/bindings/ios-example/index.jsv3/examples/ios/frontend/main.jsv3/fix-darwin-ios-constraints.shv3/internal/commands/android/Taskfile.ymlv3/internal/commands/android/app/build.gradlev3/internal/commands/android/app/proguard-rules.prov3/internal/commands/android/app/src/main/AndroidManifest.xmlv3/internal/commands/android/app/src/main/java/com/wails/app/MainActivity.javav3/internal/commands/android/app/src/main/java/com/wails/app/WailsBridge.javav3/internal/commands/android/app/src/main/java/com/wails/app/WailsJSBridge.javav3/internal/commands/android/app/src/main/java/com/wails/app/WailsPathHandler.javav3/internal/commands/android/app/src/main/res/layout/activity_main.xmlv3/internal/commands/android/app/src/main/res/values/colors.xmlv3/internal/commands/android/app/src/main/res/values/strings.xmlv3/internal/commands/android/app/src/main/res/values/themes.xmlv3/internal/commands/android/build.gradlev3/internal/commands/android/gradle.propertiesv3/internal/commands/android/gradle/wrapper/gradle-wrapper.propertiesv3/internal/commands/android/gradlewv3/internal/commands/android/gradlew.batv3/internal/commands/android/main_android.gov3/internal/commands/android/scripts/deps/install_deps.gov3/internal/commands/android/settings.gradlev3/internal/commands/build-assets.gov3/internal/commands/build_assets/android/Taskfile.ymlv3/internal/commands/build_assets/android/app/build.gradlev3/internal/commands/build_assets/android/app/src/main/AndroidManifest.xmlv3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/MainActivity.javav3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsBridge.javav3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsJSBridge.javav3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsPathHandler.javav3/internal/commands/build_assets/ios/Taskfile.ymlv3/internal/commands/ios/Assets.xcassetsv3/internal/commands/ios/Info.dev.plistv3/internal/commands/ios/Info.plistv3/internal/commands/ios/LaunchScreen.storyboardv3/internal/commands/ios/Taskfile.ymlv3/internal/commands/ios/app_options_default.gov3/internal/commands/ios/app_options_ios.gov3/internal/commands/ios/build.shv3/internal/commands/ios/entitlements.plistv3/internal/commands/ios/main.mv3/internal/commands/ios/main_ios.gov3/internal/commands/ios/project.pbxprojv3/internal/commands/ios/scripts/deps/install_deps.gov3/internal/commands/ios_xcode_gen.gov3/internal/commands/updatable_build_assets/ios/Info.dev.plist.tmplv3/internal/commands/updatable_build_assets/ios/Info.plist.tmplv3/internal/commands/updatable_build_assets/ios/entitlements.plist.tmplv3/internal/doctor/doctor_common.gov3/internal/doctor/doctor_darwin.gov3/internal/runtime/desktop/@wailsio/runtime/docs/assets/hierarchy.jsv3/internal/runtime/desktop/@wailsio/runtime/docs/assets/navigation.jsv3/internal/runtime/desktop/@wailsio/runtime/docs/assets/search.jsv3/internal/runtime/desktop/@wailsio/runtime/docs/hierarchy.htmlv3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Events.Types.htmlv3/internal/runtime/desktop/@wailsio/runtime/src/event_types.tsv3/internal/runtime/desktop/@wailsio/runtime/src/runtime.tsv3/pkg/application/android_logging_debug.gov3/pkg/application/android_logging_production.gov3/pkg/application/application_android.gov3/pkg/application/application_android_nocgo.gov3/pkg/application/application_ios.gov3/pkg/application/application_ios.hv3/pkg/application/application_ios.mv3/pkg/application/application_ios_delegate.mv3/pkg/application/clipboard_android.gov3/pkg/application/clipboard_ios.gov3/pkg/application/dialogs_android.gov3/pkg/application/dialogs_ios.gov3/pkg/application/environment_linux.gov3/pkg/application/events_common_android.gov3/pkg/application/ios_logging_debug.gov3/pkg/application/ios_logging_production.gov3/pkg/application/linux_cgo.cv3/pkg/application/linux_cgo.hv3/pkg/application/mainthread_android.gov3/pkg/application/messageprocessor_android.gov3/pkg/application/messageprocessor_ios.gov3/pkg/application/screen_android.gov3/pkg/application/screen_ios.gov3/pkg/application/webview_window_android.gov3/pkg/application/webview_window_darwin_production.gov3/pkg/application/webview_window_ios.gov3/pkg/application/webview_window_ios.mv3/pkg/doctor-ng/platform_darwin.gov3/pkg/doctor-ng/platform_ios.gov3/pkg/events/events.gov3/pkg/events/events.txtv3/pkg/events/events_android.gov3/pkg/events/events_ios.gov3/pkg/events/events_linux.gov3/pkg/events/known_events.gov3/pkg/mac/mac.gov3/pkg/services/dock/dock_darwin.gov3/pkg/services/notifications/notifications_darwin.mv3/pkg/updater/updater_darwin.gov3/pkg/updater/updater_darwin_test.gov3/pkg/updater/updater_notdarwin.gov3/tasks/events/generate.gov3/test-assets/index.htmlv3/test-ios-compilation.gov3/test-new-ios-build.shv3/verify-ios-setup.sh
💤 Files with no reviewable changes (18)
- v3/examples/android/.task/checksum/android-generate-android-bindings
- v3/examples/android/.task/checksum/generate-bindings--BUILD_FLAGS--tags-android-debug--buildvcs-false--gcflags-all---l--
- v3/examples/android/.task/checksum/build-frontend--PRODUCTION--
- v3/examples/android/.task/checksum/android-common-install-frontend-deps
- v3/test-ios-compilation.go
- v3/build_ios.sh
- v3/IOS_ARCHITECTURE.md
- v3/verify-ios-setup.sh
- v3/examples/android/.task/checksum/android-common-generate-icons
- v3/IOS_RUNTIME.md
- v3/pkg/events/events_android.go
- v3/fix-darwin-ios-constraints.sh
- v3/test-new-ios-build.sh
- v3/IOS_FEATURES_TODO.md
- v3/test-assets/index.html
- v3/ANDROID_ARCHITECTURE.md
- v3/pkg/events/events_ios.go
- v3/pkg/application/events_common_android.go
Artifacts:
- examples/ios: untrack generated build/ios/xcode/overlay.json + gen/ (they
held an absolute developer path) and gitignore them
- replace the text-placeholder icon.png files with a real 1024x1024 PNG
- regenerate the android example package-lock to match package.json ("latest");
it had pinned a local file: path, breaking npm ci
Security / correctness:
- android: drop usesCleartextTraffic from the manifest (assets are served
in-process over https://wails.localhost)
- ios: nil-guard the document-picker path callback (iosOpenFileCallback)
- ios: only take security-scoped access for directory (in-place) picks, not
for asCopy file imports (avoids leaking scoped-resource handles)
- android: getScreen() returns an explicit error instead of (nil, nil)
- android: synchronize the file-picker check-and-set (MainActivity)
- android: guard native callbacks (dialog/file-picker/main-thread) so they
no-op after shutdown() tears down the Go side
- android: treat an empty ANDROID_KEYSTORE_FILE as "unsigned" in build.gradle
- runtime(android transport): delete the pending entry if invokeAsync throws
synchronously, so the callback map can't leak
Cross-platform / cleanup:
- doctor: detect the Android SDK on Windows (LOCALAPPDATA) and look for
adb.exe; android Taskfile SDK fallback now covers Linux (~/Android/Sdk)
- ios Xcode project: SDKROOT=iphoneos (was iphonesimulator, which blocked
device builds from Xcode) and always rebuild the Go c-archive (the old
existence check served stale binaries when .go sources changed)
- android: remove the dead getMimeType() helper in WailsPathHandler
Android Java/manifest/gradle/Taskfile changes are synced across the example,
internal/commands, and build_assets copies.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
v3/examples/android/build/android/Taskfile.yml (1)
199-200:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winComment says "first" but code gets last AVD.
The comment at line 199 states "Get first available AVD", but
tail -1retrieves the last line from the AVD list, not the first. If the intent is to select the newest/last AVD, update the comment. Otherwise, usehead -1.🔧 Proposed fix
- # Get first available AVD - AVD_NAME=$("{{.EMULATOR}}" -list-avds | tail -1) + # Get last available AVD (prefer newest) + AVD_NAME=$("{{.EMULATOR}}" -list-avds | tail -1)Or if first is truly intended:
- # Get first available AVD - AVD_NAME=$("{{.EMULATOR}}" -list-avds | tail -1) + # Get first available AVD + AVD_NAME=$("{{.EMULATOR}}" -list-avds | head -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/android/build/android/Taskfile.yml` around lines 199 - 200, The comment says "Get first available AVD" but the command assigned to AVD_NAME uses ("{{.EMULATOR}}" -list-avds | tail -1) which picks the last AVD; either update the comment to reflect "Get last/newest available AVD" or change the pipeline to use head -1 to actually select the first AVD from "{{.EMULATOR}} -list-avds" so AVD_NAME's behavior matches the comment.
🧹 Nitpick comments (1)
v3/examples/android/build/android/Taskfile.yml (1)
13-16: ⚖️ Poor tradeoffSDK_ROOT fallback silently accepts non-existent paths.
When
ANDROID_HOMEandANDROID_SDK_ROOTare both unset and neither default path exists,SDK_ROOTwill be set to a non-existent directory without error. Downstream tasks (e.g.,compile:go:sharedat line 63) will fail with cryptic messages when they attempt to use the invalid path.♻️ Optional: Add validation in dependent tasks
Consider adding a precondition to tasks that depend on
SDK_ROOT:compile:go:shared: preconditions: - sh: '[ -d "{{.SDK_ROOT}}" ]' msg: "Android SDK not found. Set ANDROID_HOME or install to default location" # ... rest of task🤖 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/android/build/android/Taskfile.yml` around lines 13 - 16, The SDK_ROOT expression silently returns a non-existent path when ANDROID_HOME and ANDROID_SDK_ROOT are unset, so add an explicit existence check and fail fast: either change the SDK_ROOT derivation to validate the chosen path exists and exit with a clear message if not, or add a precondition to dependent tasks (e.g., compile:go:shared) that runs '[ -d "{{.SDK_ROOT}}" ]' and prints "Android SDK not found. Set ANDROID_HOME or install to default location" on failure; reference the SDK_ROOT variable and the compile:go:shared task when making the change.
🤖 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/ios/build/ios/project.pbxproj`:
- Line 158: The prebuild shellScript currently invokes xcrun --sdk
iphonesimulator and uses -target arm64-apple-ios15.0-simulator (and builds
bin/My Product.a) while the Xcode build configurations set SDKROOT = iphoneos,
causing a simulator/device SDK mismatch; fix by making these targets consistent
across the three project files: either change the shellScript's xcrun and CGO
flags to use iphoneos and a device target (replace --sdk iphonesimulator with
--sdk iphoneos and -target arm64-apple-ios15.0-simulator → -target
arm64-apple-ios15.0) and adjust any simulator-specific flags, or change SDKROOT
values to iphonesimulator to match the existing script; ensure the go build
-buildmode=c-archive -overlay ... command and resulting artifact name remain
correct for the chosen SDK.
---
Outside diff comments:
In `@v3/examples/android/build/android/Taskfile.yml`:
- Around line 199-200: The comment says "Get first available AVD" but the
command assigned to AVD_NAME uses ("{{.EMULATOR}}" -list-avds | tail -1) which
picks the last AVD; either update the comment to reflect "Get last/newest
available AVD" or change the pipeline to use head -1 to actually select the
first AVD from "{{.EMULATOR}} -list-avds" so AVD_NAME's behavior matches the
comment.
---
Nitpick comments:
In `@v3/examples/android/build/android/Taskfile.yml`:
- Around line 13-16: The SDK_ROOT expression silently returns a non-existent
path when ANDROID_HOME and ANDROID_SDK_ROOT are unset, so add an explicit
existence check and fail fast: either change the SDK_ROOT derivation to validate
the chosen path exists and exit with a clear message if not, or add a
precondition to dependent tasks (e.g., compile:go:shared) that runs '[ -d
"{{.SDK_ROOT}}" ]' and prints "Android SDK not found. Set ANDROID_HOME or
install to default location" on failure; reference the SDK_ROOT variable and the
compile:go:shared task when making the change.
🪄 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: 1e292a98-0984-4d26-a0d3-b4c25a3185b2
⛔ Files ignored due to path filters (3)
v3/examples/android/frontend/package-lock.jsonis excluded by!**/package-lock.jsonv3/examples/ios/build/ios/icon.pngis excluded by!**/*.pngv3/internal/commands/ios/icon.pngis excluded by!**/*.png
📒 Files selected for processing (28)
v3/examples/android/build/android/Taskfile.ymlv3/examples/android/build/android/app/build.gradlev3/examples/android/build/android/app/src/main/AndroidManifest.xmlv3/examples/android/build/android/app/src/main/java/com/wails/app/MainActivity.javav3/examples/android/build/android/app/src/main/java/com/wails/app/WailsBridge.javav3/examples/android/build/android/app/src/main/java/com/wails/app/WailsPathHandler.javav3/examples/ios/.gitignorev3/examples/ios/build/ios/project.pbxprojv3/examples/ios/build/ios/xcode/main.xcodeproj/project.pbxprojv3/examples/ios/build/ios/xcode/overlay.jsonv3/internal/commands/android/Taskfile.ymlv3/internal/commands/android/app/build.gradlev3/internal/commands/android/app/src/main/AndroidManifest.xmlv3/internal/commands/android/app/src/main/java/com/wails/app/MainActivity.javav3/internal/commands/android/app/src/main/java/com/wails/app/WailsBridge.javav3/internal/commands/android/app/src/main/java/com/wails/app/WailsPathHandler.javav3/internal/commands/build_assets/android/Taskfile.ymlv3/internal/commands/build_assets/android/app/build.gradlev3/internal/commands/build_assets/android/app/src/main/AndroidManifest.xmlv3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/MainActivity.javav3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsBridge.javav3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsPathHandler.javav3/internal/commands/ios/project.pbxprojv3/internal/doctor/doctor_common.gov3/internal/runtime/desktop/@wailsio/runtime/src/runtime.tsv3/pkg/application/application_ios.mv3/pkg/application/dialogs_ios.gov3/pkg/application/webview_window_android.go
💤 Files with no reviewable changes (7)
- v3/internal/commands/android/app/src/main/AndroidManifest.xml
- v3/examples/android/build/android/app/src/main/AndroidManifest.xml
- v3/examples/ios/build/ios/xcode/overlay.json
- v3/internal/commands/android/app/src/main/java/com/wails/app/WailsPathHandler.java
- v3/internal/commands/build_assets/android/app/src/main/AndroidManifest.xml
- v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsPathHandler.java
- v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsPathHandler.java
✅ Files skipped from review due to trivial changes (1)
- v3/examples/ios/.gitignore
🚧 Files skipped from review as they are similar to previous changes (15)
- v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts
- v3/internal/commands/build_assets/android/app/build.gradle
- v3/examples/android/build/android/app/build.gradle
- v3/internal/doctor/doctor_common.go
- v3/pkg/application/webview_window_android.go
- v3/pkg/application/dialogs_ios.go
- v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/MainActivity.java
- v3/examples/android/build/android/app/src/main/java/com/wails/app/MainActivity.java
- v3/pkg/application/application_ios.m
- v3/internal/commands/android/Taskfile.yml
- v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsBridge.java
- v3/internal/commands/android/app/src/main/java/com/wails/app/WailsBridge.java
- v3/internal/commands/android/app/src/main/java/com/wails/app/MainActivity.java
- v3/internal/commands/build_assets/android/Taskfile.yml
- v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsBridge.java
A single Wails v3 app that runs on iOS, Android and desktop from one main.go and one adaptive frontend, exercising the full runtime surface: - Bindings: JS->Go service calls (values, structs, error propagation) - Events: Go->JS (time clock) and JS->Go->JS (ping/pong) - Dialogs: native Info/Warning/Error/Question with callbacks - System: clipboard round-trip, screen metrics, device info - Native: iOS haptics + WKWebView toggles, Android vibrate + toast The UI feature-detects the platform and shows only the controls each one supports; navigation uses an in-page tab bar so the UX is identical everywhere (no native iOS tab bar to avoid a second, divergent nav). Also adds the Android runtime JS module (Runtime.Android.Haptics.Vibrate, Device.Info, Toast.Show) mirroring Runtime.IOS, plus objectNames.Android, so the frontend can call Android-native methods the same way as iOS. Verified: built + launched on the API 34 Android emulator (every feature exercised via CDP, including Go-error propagation and the native AlertDialog callback) and on the iOS 26.5 simulator (renders, platform detection, runtime bindings).
There was a problem hiding this comment.
Actionable comments posted: 19
🧹 Nitpick comments (5)
v3/examples/mobile/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java (1)
136-142: 💤 Low valueConsider more comprehensive JavaScript string escaping.
The current
escapeJsStringhandles\,',\n, and\r, but doesn't escape other potentially problematic characters like\t(tab),\f(form feed),\b(backspace), or control characters that may appear in JSON responses from Go.♻️ More robust escaping
private String escapeJsString(String str) { if (str == null) return ""; return str.replace("\\", "\\\\") .replace("'", "\\'") .replace("\n", "\\n") - .replace("\r", "\\r"); + .replace("\r", "\\r") + .replace("\t", "\\t") + .replace("\f", "\\f") + .replace("\b", "\\b"); }🤖 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/mobile/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java` around lines 136 - 142, The escapeJsString method currently only handles backslash, single quote, newline and carriage return; update escapeJsString to comprehensively escape control and special characters by iterating the input and replacing tab (\t), backspace (\b), form feed (\f), vertical tab (\u000B) and any control chars (char <= 0x1F) with their JS escape sequences or a \\uXXXX hex escape, and also consider escaping backticks and double quotes if the string could be injected into different JS contexts; implement this in the escapeJsString(String str) method (use a StringBuilder and switch on characters) so all problematic/control characters are safely encoded for JS.v3/examples/mobile/build/android/Taskfile.yml (1)
192-224: ⚖️ Poor tradeoffEmulator boot check could be more robust.
The emulator boot detection uses a fixed 120-second timeout with 1-second polling. While functional, consider adding feedback messages during the wait or checking for emulator failures that might cause an indefinite wait.
💡 Optional enhancement
for i in $(seq 1 120); do + if [ $((i % 10)) -eq 0 ]; then + echo "Still waiting... ($i/120 seconds)" + fi BOOT_COMPLETED=$("{{.ADB}}" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')This provides user feedback during longer boot times without changing the core logic.
🤖 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/mobile/build/android/Taskfile.yml` around lines 192 - 224, The emulator boot detector in the start script (the AVD selection and boot loop using AVD_NAME, "{{.EMULATOR}}", and "{{.ADB}}") should be made more robust by: increasing or making the 120-second timeout configurable, emitting periodic progress feedback inside the for-loop (e.g., print a message every N seconds or every X iterations with remaining time), and adding a failure check (e.g., verify the emulator process is still running or check for "{{.ADB}} devices" / emulator state such as "offline" to bail early). Update the wait loop around "{{.ADB}} wait-for-device" and the for-seq loop to include these checks and messages so users see progress and the script exits promptly on emulator failure.v3/examples/mobile/build/ios/scripts/deps/install_deps.go (1)
189-191: ⚡ Quick winConsider logging command output on failure.
When the fix command fails (line 189-191), the script exits immediately without showing the command's stderr, which might contain helpful diagnostic information for the user.
📝 Optional improvement
fmt.Println("Running command...") cmd := exec.Command(dep.InstallCmd[0], dep.InstallCmd[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Stdin = os.Stdin if err := cmd.Run(); err != nil { - fmt.Printf("Command failed: %v\n", err) + fmt.Printf("Command failed: %v\n", err) + fmt.Println("Please run the command manually and check the error output.") os.Exit(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/mobile/build/ios/scripts/deps/install_deps.go` around lines 189 - 191, The failure handler for the command invocation currently just prints the error from cmd.Run() and exits; update the block around cmd.Run() to capture and log the command's stderr/stdout (e.g., use cmd.CombinedOutput() or attach bytes.Buffer to cmd.Stdout/Stderr) and print that output alongside the error before calling os.Exit(1) so failing commands (in install_deps.go where cmd.Run() is invoked) surface diagnostic output.v3/examples/mobile/build/android/app/src/main/java/com/wails/app/MainActivity.java (1)
229-263: ⚡ Quick winResource cleanup on early returns.
The
copyUriToCachemethod has multiple early returns (lines 244, 250) that leave the directory created on line 242 in place. While the cache directory will eventually be cleaned by the system, explicitly cleaning up on failure would be better.♻️ Optional cleanup improvement
`@Nullable` private String copyUriToCache(Uri uri) { String name = "document"; try (Cursor cursor = getContentResolver().query(uri, null, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { int idx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); if (idx >= 0 && cursor.getString(idx) != null) { name = new File(cursor.getString(idx)).getName(); } } } catch (Exception ignored) { } + File dir = null; try { - File dir = new File(getCacheDir(), "wails-picker/" + System.nanoTime()); + dir = new File(getCacheDir(), "wails-picker/" + System.nanoTime()); if (!dir.mkdirs()) { return null; } File out = new File(dir, name); try (InputStream in = getContentResolver().openInputStream(uri); OutputStream os = new FileOutputStream(out)) { if (in == null) { return null; } byte[] buf = new byte[64 * 1024]; int n; while ((n = in.read(buf)) > 0) { os.write(buf, 0, n); } } return out.getAbsolutePath(); } catch (Exception e) { Log.e(TAG, "Failed to copy picked document", e); + if (dir != null && dir.exists()) { + deleteRecursive(dir); + } return null; } } + + private void deleteRecursive(File file) { + if (file.isDirectory()) { + File[] children = file.listFiles(); + if (children != null) { + for (File child : children) { + deleteRecursive(child); + } + } + } + file.delete(); + }🤖 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/mobile/build/android/app/src/main/java/com/wails/app/MainActivity.java` around lines 229 - 263, The copyUriToCache method creates a temporary directory (dir) but returns early on failures (when mkdirs() fails or when openInputStream returns null) without deleting that directory; update copyUriToCache to ensure dir and any partially written file (out) are deleted on all early-return paths by restructuring the logic (use a try/finally or explicit cleanup before each return) so that if mkdirs() fails you remove the created dir (if any), and if in == null or an exception occurs you delete out and dir before returning; reference the copyUriToCache method, the dir and out File variables, and the InputStream in to locate where to add the cleanup.v3/examples/mobile/build/android/app/src/main/java/com/wails/app/WailsBridge.java (1)
229-247: 💤 Low valueSystem bar insets query may fail on older Android versions.
On Android API < 30 (line 241-246), the code doesn't query system bar insets, leaving them at 0. While this is a reasonable fallback, documenting this limitation would help users understand safe-area behavior on older devices.
📝 Optional documentation
} else { + // Note: System bar insets are not available before Android R (API 30). + // Apps targeting older versions will receive 0 for all inset values. DisplayMetrics metrics = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics); widthPx = metrics.widthPixels; heightPx = metrics.heightPixels; density = metrics.density; }🤖 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/mobile/build/android/app/src/main/java/com/wails/app/WailsBridge.java` around lines 229 - 247, The system-bar inset values are left at 0 for API < 30 which can be surprising; update the WailsBridge code that computes window metrics to explicitly document and communicate this fallback: add a concise comment and/or Javadoc on the method in class WailsBridge explaining that for Build.VERSION.SDK_INT < Build.VERSION_CODES.R the Insets are unavailable so insetTop/insetBottom/insetLeft/insetRight remain 0 (and that callers should treat them as unknown), and optionally emit a debug log when falling back to the older DisplayMetrics path to make the limitation observable at runtime.
🤖 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/mobile/build/android/app/build.gradle`:
- Around line 34-39: The signing block uses hasKeystore/keystoreFile but does
not validate ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, and
ANDROID_KEY_PASSWORD before assigning to storePassword, keyAlias, and
keyPassword; update the logic so you explicitly read each env var, verify it is
non-null/non-empty, and either (a) only call storePassword/keyAlias/keyPassword
when their corresponding env var is present, or (b) fail the Gradle build with a
clear error if keystore file exists but required env variables are missing;
reference the hasKeystore/keystoreFile/storeFile and the
storePassword/keyAlias/keyPassword assignments when implementing the checks.
In `@v3/examples/mobile/build/android/app/src/main/AndroidManifest.xml`:
- Line 10: The AndroidManifest currently sets android:allowBackup="true"; change
this to a safer default (android:allowBackup="false") or make it configurable
via a gradle manifestPlaceholder/boolean so downstream apps can override it;
update the <application> attribute (android:allowBackup) in AndroidManifest.xml
and add a short comment in the file indicating developers should review/override
the backup setting for production.
In
`@v3/examples/mobile/build/android/app/src/main/java/com/wails/app/MainActivity.java`:
- Around line 328-334: Replace the deprecated onBackPressed() override in
MainActivity with an OnBackPressedCallback registered on the activity's
OnBackPressedDispatcher: create a new OnBackPressedCallback(true) that checks
webView != null && webView.canGoBack() and calls webView.goBack(), otherwise
calls finish() or delegates to super by calling
requireActivity().onBackPressedDispatcher or disabling the callback to allow
default behavior; register this callback in onCreate() (or equivalent init) and
ensure you remove or disable it on destroy if necessary so back navigation works
on API 33+ without using the deprecated onBackPressed().
In
`@v3/examples/mobile/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java`:
- Around line 48-62: The invokeAsync method in WailsJSBridge creates a new
Thread per call; replace this unbounded creation with a shared ExecutorService:
add a static final ExecutorService (e.g., executor) at class scope (use
Executors.newCachedThreadPool() or a bounded pool per your needs) and change the
new Thread(...).start() call inside invokeAsync to submit the Runnable to
executor (executor.submit(() -> { ... })). Ensure the executor is static/final
so all invokeAsync calls reuse the pool and avoid unbounded thread growth.
In `@v3/examples/mobile/build/android/scripts/deps/install_deps.go`:
- Around line 133-137: The current runtime.GOOS branch prints Unix "export"
lines for all non-macOS systems; add an explicit runtime.GOOS == "windows"
branch that prints Windows-style instructions instead of Unix export lines.
Update the conditional around runtime.GOOS and replace the else case with three
branches: darwin (keep the existing export), windows (print example Windows CMD
and PowerShell environment variable commands and a typical Windows Android SDK
path), and a default else (keep the existing Linux/Unix export and path). Modify
the fmt.Println calls so they emit the appropriate OS-specific guidance
depending on runtime.GOOS.
In `@v3/examples/mobile/build/android/Taskfile.yml`:
- Line 254: The grep in the Taskfile command string '"{{.ADB}}" logcat -v time |
grep -E "(Wails|{{.APP_NAME}})"' will return a non‑zero exit code when no lines
match and can cause the task to fail; update that command to tolerate no matches
by appending '|| true' to the pipeline or replacing grep with 'grep
--line-buffered -E "(Wails|{{.APP_NAME}})"' to both avoid non‑zero exit on no
matches and ensure immediate output; modify the Taskfile.yml entry that defines
the adb logcat pipeline accordingly (look for the exact string containing
"{{.ADB}} logcat -v time | grep -E "(Wails|{{.APP_NAME}})"").
In `@v3/examples/mobile/build/darwin/Info.plist`:
- Around line 9-11: Update the hardcoded CFBundleExecutable and
CFBundleIdentifier entries so they match the actual app binary name rather than
"ios": change the CFBundleExecutable value and CFBundleIdentifier to use the
example's app name (or a template placeholder) and modify the darwin Taskfile to
substitute {{.APP_NAME}} when copying/generating Info.plist so the produced .app
bundle looks for the correct executable name; ensure the plist keys
CFBundleExecutable and CFBundleIdentifier are aligned with the generated/actual
binary name.
In `@v3/examples/mobile/build/ios/build.sh`:
- Around line 5-6: The app executable name in build.sh (APP_NAME="ios") does not
match Info.plist's CFBundleExecutable ("mobile"); change APP_NAME to "mobile" so
the created executable and bundle match Info.plist (ensure the executable
creation step that uses APP_NAME still produces the binary named by that
variable), or alternatively update Info.plist's CFBundleExecutable to "ios" if
the intended executable name is "ios"—make one consistent change and verify
APP_NAME and CFBundleExecutable refer to the same string.
In `@v3/examples/mobile/build/ios/Info.dev.plist`:
- Around line 25-29: The UIRequiredDeviceCapabilities array currently lists both
"armv7" and "arm64", which conflicts with the MinimumOSVersion of 15.0; remove
the "armv7" entry so UIRequiredDeviceCapabilities only contains "arm64" (update
the Info.dev.plist key UIRequiredDeviceCapabilities to drop the "armv7" string
and leave "arm64" only).
In `@v3/examples/mobile/build/ios/scripts/deps/install_deps.go`:
- Around line 224-234: The extracted runtime identifier stored in latestRuntime
(parsed from runtimeOut) must be validated and sanitized before being
interpolated into a shell command; update the parsing block that sets
latestRuntime to only accept identifiers matching the expected simulator runtime
pattern (e.g., a regex like ^com\.apple\.CoreSimulator\.SimRuntime\.iOS[^\s]+$
or a conservative allowed-char set), ensure latestRuntime is non-empty, and fail
with a clear error if validation fails, and when invoking exec.Command or shell
commands (where latestRuntime is used) pass it as an argv element rather than
via shell interpolation or otherwise properly escape/quote it to prevent
injection.
In `@v3/examples/mobile/build/ios/xcode/main/Info.plist`:
- Around line 25-29: Remove the obsolete 32-bit capability entry from the
Info.plist by deleting the "armv7" item under the UIRequiredDeviceCapabilities
array; leave "arm64" (or other needed entries) intact and ensure the
UIRequiredDeviceCapabilities key only lists architectures compatible with
MinimumOSVersion 15.0 to avoid App Store rejection.
In `@v3/examples/mobile/build/linux/appimage/build.sh`:
- Line 34: The mv invocation using the quoted glob "${APP_NAME}*.AppImage" is
fragile: it will not expand when quoted and will fail if zero or multiple
matches are present; update the build.sh logic (around the APP_NAME variable and
the mv call) to explicitly glob and handle results defensively—enable nullglob
(or use an unquoted glob into an array), collect matches into a variable/array,
check count (error if zero, if >1 choose the correct one or fail), then mv the
single resolved filename to "${APP_NAME}.AppImage" and log errors accordingly so
mv is only called with a single concrete path.
In `@v3/examples/mobile/build/linux/desktop`:
- Line 6: The desktop file incorrectly references the iOS binary path
("Exec=/usr/local/bin/ios" and matching Icon="ios")—replace the hardcoded iOS
binary and icon with the correct Linux mobile example binary name and icon used
by this example (update the Exec= and Icon= entries), remove the absolute
/usr/local/bin path, and ensure the Exec target matches the actual built binary
output for this mobile example (also update the same pattern on the other
affected lines 9-11).
In `@v3/examples/mobile/build/linux/nfpm/nfpm.yaml`:
- Line 6: The nfpm manifest currently hardcodes name: "ios" (and related binary
and desktop entries), which mismatches the mobile example; update the name,
binary path and desktop file entries (the fields under name:, binary paths, and
the desktop file reference used later around the same block) to use the template
variable {{.APP_NAME}} (or otherwise match the actual example app name) so nfpm
will resolve ./bin/{{.APP_NAME}} and the correct desktop file instead of
./bin/ios.
In `@v3/examples/mobile/build/Taskfile.yml`:
- Around line 163-165: The script uses the Bash-only array PIPESTATUS in the
Taskfile step (check around PIPESTATUS, xcodebuild and XCB_ARGS), which will
fail under /bin/sh; either force Bash for this Taskfile step (run the command
under bash -lc or add an explicit bash shebang and enable set -euo pipefail) or
rewrite the logic to be POSIX-safe by capturing the exit status of the first
command immediately (store $? into a variable) or using a conditional/||
fallback so you can run xcodebuild "${XCB_ARGS[@]}" build when the first command
fails without relying on PIPESTATUS.
- Line 71: The sed inplace edit is not portable (macOS vs GNU); update the
Taskfile.yml line that runs sed against the INDEX_HTML variable to use a
portable pattern — e.g., replace the single sed -E -i'' invocation with a
two-attempt fallback (first try macOS-style: sed -E -i ''
's/class=\"btn\"/class=\"p-btn p-prim-col\"/g' "$INDEX_HTML" 2>/dev/null || then
try GNU-style: sed -E -i 's/class=\"btn\"/class=\"p-btn p-prim-col\"/g'
"$INDEX_HTML") or switch to a portable alternative such as perl -pi -e or
creating a temp file (mktemp) then mv; modify the sed invocation referencing
INDEX_HTML in Taskfile.yml accordingly.
In `@v3/examples/mobile/build/windows/nsis/project.nsi`:
- Line 22: The comment points out that INFO_PROJECTNAME is commented out in
project.nsi and the build falls back to the default defined in wails_tools.nsh
as "ios"; update project.nsi to either uncomment and set a neutral default like
"mobile" or "my-project" for INFO_PROJECTNAME or modify the inline comment next
to the commented line to explicitly state that the fallback in wails_tools.nsh
is "ios" and that it’s just an example name for legacy reasons—refer to the
INFO_PROJECTNAME definition in project.nsi and the fallback in wails_tools.nsh
when making the change.
In `@v3/examples/mobile/build/windows/Taskfile.yml`:
- Around line 33-43: The package task currently embeds YAML task keys inside a
shell if block (invalid shell syntax) so replace those YAML-style invocations
with explicit task CLI calls: inside the package task's cmds use a shell if that
runs the task binary (e.g., "task create:msix:package" and "task
create:nsis:installer") based on the FORMAT var, or split into two tasks and
have package delegate to create:msix:package or create:nsis:installer; update
the package task's cmds to call the task CLI instead of using the YAML "task:"
key and ensure FORMAT remains defined as before.
In `@v3/examples/mobile/frontend/public/style.css`:
- Line 139: Replace the deprecated CSS declaration "word-break: break-word" with
the modern equivalent "overflow-wrap: break-word" where it appears (search for
the exact "word-break: break-word" token) to preserve the same wrapping
behavior; update the rule to use overflow-wrap: break-word (optionally keep
"word-wrap: break-word" for legacy support) so browsers stop using the
deprecated value.
---
Nitpick comments:
In
`@v3/examples/mobile/build/android/app/src/main/java/com/wails/app/MainActivity.java`:
- Around line 229-263: The copyUriToCache method creates a temporary directory
(dir) but returns early on failures (when mkdirs() fails or when openInputStream
returns null) without deleting that directory; update copyUriToCache to ensure
dir and any partially written file (out) are deleted on all early-return paths
by restructuring the logic (use a try/finally or explicit cleanup before each
return) so that if mkdirs() fails you remove the created dir (if any), and if in
== null or an exception occurs you delete out and dir before returning;
reference the copyUriToCache method, the dir and out File variables, and the
InputStream in to locate where to add the cleanup.
In
`@v3/examples/mobile/build/android/app/src/main/java/com/wails/app/WailsBridge.java`:
- Around line 229-247: The system-bar inset values are left at 0 for API < 30
which can be surprising; update the WailsBridge code that computes window
metrics to explicitly document and communicate this fallback: add a concise
comment and/or Javadoc on the method in class WailsBridge explaining that for
Build.VERSION.SDK_INT < Build.VERSION_CODES.R the Insets are unavailable so
insetTop/insetBottom/insetLeft/insetRight remain 0 (and that callers should
treat them as unknown), and optionally emit a debug log when falling back to the
older DisplayMetrics path to make the limitation observable at runtime.
In
`@v3/examples/mobile/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java`:
- Around line 136-142: The escapeJsString method currently only handles
backslash, single quote, newline and carriage return; update escapeJsString to
comprehensively escape control and special characters by iterating the input and
replacing tab (\t), backspace (\b), form feed (\f), vertical tab (\u000B) and
any control chars (char <= 0x1F) with their JS escape sequences or a \\uXXXX hex
escape, and also consider escaping backticks and double quotes if the string
could be injected into different JS contexts; implement this in the
escapeJsString(String str) method (use a StringBuilder and switch on characters)
so all problematic/control characters are safely encoded for JS.
In `@v3/examples/mobile/build/android/Taskfile.yml`:
- Around line 192-224: The emulator boot detector in the start script (the AVD
selection and boot loop using AVD_NAME, "{{.EMULATOR}}", and "{{.ADB}}") should
be made more robust by: increasing or making the 120-second timeout
configurable, emitting periodic progress feedback inside the for-loop (e.g.,
print a message every N seconds or every X iterations with remaining time), and
adding a failure check (e.g., verify the emulator process is still running or
check for "{{.ADB}} devices" / emulator state such as "offline" to bail early).
Update the wait loop around "{{.ADB}} wait-for-device" and the for-seq loop to
include these checks and messages so users see progress and the script exits
promptly on emulator failure.
In `@v3/examples/mobile/build/ios/scripts/deps/install_deps.go`:
- Around line 189-191: The failure handler for the command invocation currently
just prints the error from cmd.Run() and exits; update the block around
cmd.Run() to capture and log the command's stderr/stdout (e.g., use
cmd.CombinedOutput() or attach bytes.Buffer to cmd.Stdout/Stderr) and print that
output alongside the error before calling os.Exit(1) so failing commands (in
install_deps.go where cmd.Run() is invoked) surface diagnostic output.
🪄 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: 2ebda077-5b80-4890-8abb-7a599a4e6013
⛔ Files ignored due to path filters (32)
v3/examples/mobile/build/android/app/src/main/res/mipmap-hdpi/ic_launcher.pngis excluded by!**/*.pngv3/examples/mobile/build/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/examples/mobile/build/android/app/src/main/res/mipmap-mdpi/ic_launcher.pngis excluded by!**/*.pngv3/examples/mobile/build/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/examples/mobile/build/android/app/src/main/res/mipmap-xhdpi/ic_launcher.pngis excluded by!**/*.pngv3/examples/mobile/build/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/examples/mobile/build/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngis excluded by!**/*.pngv3/examples/mobile/build/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/examples/mobile/build/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngis excluded by!**/*.pngv3/examples/mobile/build/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngis excluded by!**/*.pngv3/examples/mobile/build/android/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jarv3/examples/mobile/build/appicon.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/icon.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-1024.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20@2x.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20@3x.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29@2x.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29@3x.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40@2x.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40@3x.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-60@2x.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-60@3x.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-76.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-76@2x.pngis excluded by!**/*.pngv3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.pngis excluded by!**/*.pngv3/examples/mobile/build/windows/icon.icois excluded by!**/*.icov3/examples/mobile/frontend/package-lock.jsonis excluded by!**/package-lock.jsonv3/examples/mobile/frontend/public/wails.pngis excluded by!**/*.pngv3/examples/mobile/go.sumis excluded by!**/*.sum
📒 Files selected for processing (79)
v3/examples/mobile/.gitignorev3/examples/mobile/README.mdv3/examples/mobile/Taskfile.ymlv3/examples/mobile/build/Taskfile.ymlv3/examples/mobile/build/android/Taskfile.ymlv3/examples/mobile/build/android/app/build.gradlev3/examples/mobile/build/android/app/proguard-rules.prov3/examples/mobile/build/android/app/src/main/AndroidManifest.xmlv3/examples/mobile/build/android/app/src/main/java/com/wails/app/MainActivity.javav3/examples/mobile/build/android/app/src/main/java/com/wails/app/WailsBridge.javav3/examples/mobile/build/android/app/src/main/java/com/wails/app/WailsJSBridge.javav3/examples/mobile/build/android/app/src/main/java/com/wails/app/WailsPathHandler.javav3/examples/mobile/build/android/app/src/main/res/layout/activity_main.xmlv3/examples/mobile/build/android/app/src/main/res/values/colors.xmlv3/examples/mobile/build/android/app/src/main/res/values/strings.xmlv3/examples/mobile/build/android/app/src/main/res/values/themes.xmlv3/examples/mobile/build/android/build.gradlev3/examples/mobile/build/android/gradle.propertiesv3/examples/mobile/build/android/gradle/wrapper/gradle-wrapper.propertiesv3/examples/mobile/build/android/gradlewv3/examples/mobile/build/android/gradlew.batv3/examples/mobile/build/android/scripts/deps/install_deps.gov3/examples/mobile/build/android/settings.gradlev3/examples/mobile/build/config.ymlv3/examples/mobile/build/darwin/Info.dev.plistv3/examples/mobile/build/darwin/Info.plistv3/examples/mobile/build/darwin/Taskfile.ymlv3/examples/mobile/build/darwin/icons.icnsv3/examples/mobile/build/ios/Assets.xcassetsv3/examples/mobile/build/ios/Info.dev.plistv3/examples/mobile/build/ios/Info.plistv3/examples/mobile/build/ios/LaunchScreen.storyboardv3/examples/mobile/build/ios/Taskfile.ymlv3/examples/mobile/build/ios/app_options_default.gov3/examples/mobile/build/ios/app_options_ios.gov3/examples/mobile/build/ios/build.shv3/examples/mobile/build/ios/entitlements.plistv3/examples/mobile/build/ios/main.mv3/examples/mobile/build/ios/main_ios.gov3/examples/mobile/build/ios/project.pbxprojv3/examples/mobile/build/ios/scripts/deps/install_deps.gov3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/Contents.jsonv3/examples/mobile/build/ios/xcode/main/Info.plistv3/examples/mobile/build/ios/xcode/main/LaunchScreen.storyboardv3/examples/mobile/build/ios/xcode/main/main.mv3/examples/mobile/build/linux/Taskfile.ymlv3/examples/mobile/build/linux/appimage/build.shv3/examples/mobile/build/linux/desktopv3/examples/mobile/build/linux/nfpm/nfpm.yamlv3/examples/mobile/build/linux/nfpm/scripts/postinstall.shv3/examples/mobile/build/linux/nfpm/scripts/postremove.shv3/examples/mobile/build/linux/nfpm/scripts/preinstall.shv3/examples/mobile/build/linux/nfpm/scripts/preremove.shv3/examples/mobile/build/windows/Taskfile.ymlv3/examples/mobile/build/windows/info.jsonv3/examples/mobile/build/windows/msix/app_manifest.xmlv3/examples/mobile/build/windows/msix/template.xmlv3/examples/mobile/build/windows/nsis/project.nsiv3/examples/mobile/build/windows/nsis/wails_tools.nshv3/examples/mobile/build/windows/wails.exe.manifestv3/examples/mobile/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventcreate.jsv3/examples/mobile/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.tsv3/examples/mobile/frontend/bindings/mobile-example/index.jsv3/examples/mobile/frontend/bindings/mobile-example/models.jsv3/examples/mobile/frontend/bindings/mobile-example/systemservice.jsv3/examples/mobile/frontend/index.htmlv3/examples/mobile/frontend/main.jsv3/examples/mobile/frontend/package.jsonv3/examples/mobile/frontend/public/style.cssv3/examples/mobile/frontend/vite.config.jsv3/examples/mobile/go.modv3/examples/mobile/greetservice.gov3/examples/mobile/ios_runtime_events_ios.gov3/examples/mobile/ios_runtime_events_stub.gov3/examples/mobile/main.gov3/examples/mobile/main_android.gov3/internal/runtime/desktop/@wailsio/runtime/src/android.tsv3/internal/runtime/desktop/@wailsio/runtime/src/index.tsv3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts
✅ Files skipped from review due to trivial changes (29)
- v3/examples/mobile/build/linux/nfpm/scripts/preinstall.sh
- v3/examples/mobile/build/linux/nfpm/scripts/postremove.sh
- v3/examples/mobile/build/android/app/src/main/res/values/colors.xml
- v3/examples/mobile/build/ios/entitlements.plist
- v3/examples/mobile/build/windows/msix/app_manifest.xml
- v3/examples/mobile/.gitignore
- v3/examples/mobile/ios_runtime_events_stub.go
- v3/examples/mobile/build/linux/nfpm/scripts/postinstall.sh
- v3/examples/mobile/build/darwin/Info.dev.plist
- v3/examples/mobile/build/ios/app_options_ios.go
- v3/examples/mobile/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventcreate.js
- v3/examples/mobile/build/windows/msix/template.xml
- v3/examples/mobile/go.mod
- v3/examples/mobile/frontend/vite.config.js
- v3/examples/mobile/README.md
- v3/examples/mobile/frontend/package.json
- v3/examples/mobile/Taskfile.yml
- v3/examples/mobile/build/linux/nfpm/scripts/preremove.sh
- v3/examples/mobile/frontend/bindings/mobile-example/systemservice.js
- v3/examples/mobile/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts
- v3/examples/mobile/build/ios/xcode/main/LaunchScreen.storyboard
- v3/examples/mobile/greetservice.go
- v3/examples/mobile/build/windows/wails.exe.manifest
- v3/examples/mobile/frontend/bindings/mobile-example/index.js
- v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/Contents.json
- v3/examples/mobile/build/ios/app_options_default.go
- v3/examples/mobile/build/ios/LaunchScreen.storyboard
- v3/examples/mobile/build/android/gradlew
- v3/examples/mobile/build/android/gradle.properties
The earlier review fix edited the static internal/commands/ios/project.pbxproj, but `wails3 ios xcode:gen` actually renders the project from updatable_build_assets/ios/project.pbxproj.tmpl. Apply the same fixes there so generated projects get them: SDKROOT=iphoneos (Xcode auto-selects the simulator SDK for simulator destinations; the old hardcoded iphonesimulator blocked device builds) and always rebuild the Go c-archive (the old existence-only check served stale binaries after .go edits).
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
v3/internal/commands/updatable_build_assets/ios/project.pbxproj.tmpl (1)
154-155:⚠️ Potential issue | 🟠 Major | ⚡ Quick winXcode cannot track the c-archive output for incremental builds.
The
outputPathsarray is empty. Xcode uses output paths to determine whether a build phase needs to re-run. Without declaringbin/{{.ProductName}}.aas an output, Xcode cannot properly track dependencies, which may cause it to skip rebuilding the Go c-archive when inputs change or unnecessarily re-run the phase when outputs haven't changed.📦 Proposed fix to declare the c-archive as an output
outputFileListPaths = ( ); outputPaths = ( + "$(PROJECT_DIR)/../../../bin/{{.ProductName}}.a", );🤖 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/internal/commands/updatable_build_assets/ios/project.pbxproj.tmpl` around lines 154 - 155, The outputPaths array for the c-archive build phase is empty so Xcode can't track the produced artifact; update the template in project.pbxproj.tmpl to declare the c-archive output by adding "bin/{{.ProductName}}.a" (or the exact generated archive name used by the c-archive build phase) to the outputPaths entry for that build phase so Xcode can detect when the Go c-archive needs to be rebuilt.
🧹 Nitpick comments (1)
v3/internal/commands/updatable_build_assets/ios/project.pbxproj.tmpl (1)
158-158: 💤 Low valueOverlay generation silently ignores failures.
The overlay generation command uses
|| trueto suppress errors. While this may be intentional to handle cases where the overlay doesn't exist yet, it could hide legitimate failures (e.g., invalid config.yml, wails3 binary not in PATH, or I/O errors).Consider logging a warning when overlay generation fails, or verifying that the overlay file exists afterward:
🔍 Optional improvement to detect overlay generation failures
if [ ! -f build/ios/xcode/overlay.json ]; then - wails3 ios overlay:gen -out build/ios/xcode/overlay.json -config build/config.yml || true + wails3 ios overlay:gen -out build/ios/xcode/overlay.json -config build/config.yml || echo "Warning: overlay generation failed, proceeding without overlay" fi🤖 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/internal/commands/updatable_build_assets/ios/project.pbxproj.tmpl` at line 158, The shell script in the project.pbxproj.tmpl currently suppresses errors from the overlay generator by appending "|| true" to the wails3 ios overlay:gen command; update the shellScript so that instead of silencing failures you run the overlay generation command without "|| true", capture its exit status (or immediately check for the existence of build/ios/xcode/overlay.json), and if generation fails emit a clear warning or fail the build (use the surrounding context: the shellScript string that runs wails3 ios overlay:gen and references build/ios/xcode/overlay.json and build/config.yml). Ensure the script logs a warning message including the command context when wails3 fails or the overlay file is missing so legitimate errors aren’t hidden.
🤖 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/internal/commands/updatable_build_assets/ios/project.pbxproj.tmpl`:
- Line 158: The Prebuild PBXShellScriptBuildPhase is building the Go c-archive
for the simulator (xcrun --sdk iphonesimulator and -target ...-simulator flags)
while the XCBuildConfiguration SDKROOT is iphoneos, its outputPaths is empty,
and the overlay generation uses "|| true" which hides failures; update the
PBXShellScriptBuildPhase (shellScript) to choose the SDK/CGO flags based on the
Xcode build SDK (use iphoneos vs iphonesimulator dynamically or read
SDKROOT/PLATFORM_NAME), add a proper outputPaths entry for
bin/{{.ProductName}}.a so Xcode can track incremental builds, and remove the "||
true" from the overlay generation command so failures surface (or explicitly
check and log/exit on overlay generation failure) to ensure the go build runs
with the correct SDK and fails loudly on overlay errors.
---
Outside diff comments:
In `@v3/internal/commands/updatable_build_assets/ios/project.pbxproj.tmpl`:
- Around line 154-155: The outputPaths array for the c-archive build phase is
empty so Xcode can't track the produced artifact; update the template in
project.pbxproj.tmpl to declare the c-archive output by adding
"bin/{{.ProductName}}.a" (or the exact generated archive name used by the
c-archive build phase) to the outputPaths entry for that build phase so Xcode
can detect when the Go c-archive needs to be rebuilt.
---
Nitpick comments:
In `@v3/internal/commands/updatable_build_assets/ios/project.pbxproj.tmpl`:
- Line 158: The shell script in the project.pbxproj.tmpl currently suppresses
errors from the overlay generator by appending "|| true" to the wails3 ios
overlay:gen command; update the shellScript so that instead of silencing
failures you run the overlay generation command without "|| true", capture its
exit status (or immediately check for the existence of
build/ios/xcode/overlay.json), and if generation fails emit a clear warning or
fail the build (use the surrounding context: the shellScript string that runs
wails3 ios overlay:gen and references build/ios/xcode/overlay.json and
build/config.yml). Ensure the script logs a warning message including the
command context when wails3 fails or the overlay file is missing so legitimate
errors aren’t hidden.
🪄 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: 7f7830d5-a537-4d81-9365-c96b59e81de6
📒 Files selected for processing (1)
v3/internal/commands/updatable_build_assets/ios/project.pbxproj.tmpl
…ction The mobile demo's iOS scroll toggle defaulted on but the page had nothing to scroll, so scroll/bounce could not be exercised. Default scroll off and, when it is turned on, append a tall filler section to the bottom of the page so there is real content to scroll and rubber-band against; turning scroll off removes the section (and the native scrollView) again.
The Greet/Add/Divide buttons sized to their label text, so the button column and the inputs beside them did not line up. Pin those three buttons to a uniform width; with flex inputs the single Greet input and the Add/ Divide input pair then share the same left and right edges.
Screens.GetAll()/GetPrimary() read from the ScreenManager, which is filled by LayoutScreens(). Desktop platforms call that from their event loop and Android from startup, but iOS never did — so Screens.* runtime calls returned null. Populate it in the iOS app's run(), mirroring Android; getScreens() always yields at least a fallback screen so this is safe.
On Android the app process keeps running in the background, so anything left active keeps draining the battery; iOS suspends the app but the torch is hardware state that persists. Pause battery-consuming work when the app leaves the foreground and restore it on return: - Accelerometer + proximity sensor are unregistered on background and re-enabled on foreground only if the user still has them switched on (iOS via UIApplication background/foreground notifications; Android via onStop/onStart). Verified on the Android emulator: a sensor change while backgrounded is ignored and picked up again after returning. - Torch is switched off on background (it would otherwise keep the LED on while the app is suspended/hidden). - Android system-event receivers (battery/network/screen) register in onStart and unregister in onStop instead of onCreate/onDestroy, so background broadcasts no longer wake the app. - The example's 1 Hz clock ticker pauses while backgrounded (gated on the lifecycle events) instead of pushing an event into a hidden webview every second. Verified: the clock freezes when backgrounded and resumes on return.
Expose pauseClockInBackground (default true) so it's clear how to let the ticker keep running when backgrounded, with a comment on the iOS suspension / Android Doze + foreground-service limits that govern whether it actually will.
|
@coderabbitai Do another review |
|
✅ Action performedFull review finished. |
…S background-modes config Framework additions (Phase E): - iOS: IOSCapturePhoto/IOSCaptureVideo (UIImagePickerController → native:capture with a base64 thumbnail), IOSBeginBackgroundTask/IOSEndBackgroundTask (a UIApplication background-task window → native:backgroundTask) - Android: AndroidCapturePhoto/Video and AndroidStart/StopForegroundService (exported bridge calls) - iOS background modes are now configurable: ios.backgroundModes in build/config.yml templates UIBackgroundModes into the generated Info.plist - iOS camera/microphone usage strings added to the iOS Info.plist templates
…ground task New Camera tab capturing photos/videos with the system camera (FileProvider + onActivityResult on Android; UIImagePickerController on iOS), shown back as a base64 thumbnail. Background-execution controls demo the Android foreground service (WailsForegroundService + ongoing notification) and the iOS background-task window. Verified on the Android emulator end-to-end: a real photo capture writes the JPEG to cache and renders its thumbnail, and the foreground service starts/stops (isForeground + ongoing notification).
…affolding Sync WailsBridge (capturePhoto/captureVideo, start/stopForegroundService), the new WailsForegroundService, the camera capture handling in MainActivity, the FileProvider + camera <queries> + service manifest entries, and file_paths.xml into both Android templates so generated apps inherit the capabilities.
The Camera tab now uses a Photo/Video pill toggle driving one capture button and a switched preview area: photos render in an <img>, recorded videos play back in a <video controls> element. Captures return the recording inline as a data:video/mp4 (Android) / data:video/quicktime (iOS) URL (size-capped) so it can play without serving cache files to the webview, and Android video capture falls back to copying the result content URI when a camera app ignores EXTRA_OUTPUT. Verified on the Android emulator: the pill toggle switches modes/labels/area, and a data-URL video loads and plays back in the <video> (readyState 4, plays).
Recorded clips now play back by streaming from the app cache over the webview's own origin, so any-length video works (no 12 MB inline cap) and seeking is supported: - Android: shouldInterceptRequest serves /__capture__/<cache-relative-path> with HTTP Range (206 Partial Content) from a path-traversal-guarded cache file. - iOS: the wails:// scheme handler streams /__capture__/<name> from the temp dir with Range support. - native:capture returns a streamUrl; the frontend prefers it over a data URL. Verified on the Android emulator: a 5s clip loads via /__capture__, plays, and SEEKS to 3.5s with no error (which requires working Range support).
…ender paths
The shared updatable_build_assets/ios/Info.plist.tmpl is rendered by three
config types, but {{if .BackgroundModes}} only existed on iOSProjectConfig — so
GenerateBuildAssets (BuildConfig) and UpdateBuildAssets (UpdateConfig) failed
with 'can't evaluate field BackgroundModes'. Add the field to the shared
TemplateEnrichment (embedded by both) so the template evaluates everywhere;
it's populated only via the iOS Xcode generator, empty otherwise.
…tignore tool_package_test emits these into the package dir; they were committed by accident. Untrack them and ignore internal/commands/myapp.* so they don't re-pollute the tree.
These 47 files were not go:embed'd or referenced by any code — the CLI generates
iOS/Android assets from internal/commands/{build_assets,updatable_build_assets}.
They were redundant committed copies; removing to slim the PR.
… the source tree It built BuildAssetsOptions without Dir; the default:"build" tag only applies via CLI flag parsing, so Dir was "" and GenerateBuildAssets wrote the whole build-assets tree (ios/android/darwin/linux/windows/...) into internal/commands/. The test skips on amd64 (so CI stayed clean) but ran on arm64 (Apple Silicon) devs, which is how those generated dirs got committed. Set Dir: t.TempDir().
…dia type mfPresentCamera set picker.mediaTypes to public.movie/public.image without checking the camera actually offers that type. On the iOS Simulator (and any device whose camera reports available but does not support video) the virtual camera exposes stills only, so requesting video made -[UIImagePickerController setMediaTypes:] throw an uncaught NSException -> SIGABRT. Check availableMediaTypesForSourceType: before configuring the picker and emit a graceful common:capture error instead, with an @try/@catch safety net. Pre- existing since #5562; surfaced by the Xcode 26 simulator's virtual camera. Refs #5602
…mobile docs (#5602) * docs: add Mobile to MorphText cycling words on landing page * docs: add first-class mobile documentation suite - New Mobile section in sidebar (promoted out of Building & Packaging) - Mobile overview page: how it works, prereqs table, feature matrix, build tag rules - 'Your First Mobile App' tutorial: iOS Simulator + Android Emulator paths, step-by-step from zero to running app, responsive CSS, platform detection, haptics, production builds, troubleshooting - iOS and Android guides moved to guides/mobile/; old paths redirect - Landing page card updated: 'Mobile coming soon...' → iOS + Android listed - Both platform guides link back to the first-mobile-app tutorial for newcomers * docs(mobile): promote kitchen sink example with run instructions; note screenshot gap * docs(landing): make mobile a headline feature with zero-changes pitch * docs(mobile): remove redirect stubs, no remaining links to old paths * docs(mobile): explain platform-gating pattern for desktop/mobile shared codebase * refactor(v3/mobile): move native features to IOS/Android managers, rename events Convert the iOS and Android native-feature free functions into methods on platform manager singletons: application.IOSShare(...) -> application.IOS.Share(...) application.AndroidHaptic() -> application.Android.Haptic(...) application.IOSSetScrollEnabled(...) -> application.IOS.SetScrollEnabled(...) `application.IOS` / `application.Android` are zero-size singletons that only exist on their respective builds; all callers live in //go:build ios/android files. Rename the mobile bridge events away from the `native:*` prefix: - cross-platform events -> `common:*` (common:haptic, common:location, …) - iOS-only -> `ios:*` (ios:beginBackgroundTask, ios:backgroundTask) - Android-only -> `android:*` (android:foregroundService) The frontend keeps a single listener per event under `common:*`, matching the shared-codebase model. Updated the Objective-C/C bridge, the Android Java bridge (template + committed example build copy), the kitchen-sink example (Go handlers + frontend), IOS.md/ANDROID.md, the docs-site mobile guides and the unreleased changelog. Refs #5602 * fix(v3/mobile): emit iOS orientation as a map, not via jsonToMap IOS.GetOrientation() returns a plain string ('portrait'/'landscape'/…), not JSON, so jsonToMap() failed to parse it and emitted an empty map. Wrap it under the 'orientation' key the frontend reads (matching the Android handler). Caught in CodeRabbit review of #5602. Refs #5602 * fix(v3/ios): prevent crash when camera lacks the requested capture media type mfPresentCamera set picker.mediaTypes to public.movie/public.image without checking the camera actually offers that type. On the iOS Simulator (and any device whose camera reports available but does not support video) the virtual camera exposes stills only, so requesting video made -[UIImagePickerController setMediaTypes:] throw an uncaught NSException -> SIGABRT. Check availableMediaTypesForSourceType: before configuring the picker and emit a graceful common:capture error instead, with an @try/@catch safety net. Pre- existing since #5562; surfaced by the Xcode 26 simulator's virtual camera. Refs #5602 * fix(v3/ios): rework camera fix — guard setMediaTypes, don't pre-check media list Follow-up to ec23d47: the previous fix pre-checked availableMediaTypesForSourceType:, but that list is unreliable on the iOS Simulator — it returns empty even when the camera is authorized and can present, so the pre-check would wrongly block capture. Verified on-simulator: the list stays empty across authorization states and a 3s poll, yet photo capture presents fine. Instead: photos use the picker's default media type (public.image, never throws); video opts into public.movie inside an @try/@catch so the 'No available types for source 1' NSException degrades to a graceful common:capture error instead of SIGABRT. Real devices list public.movie and record normally. Refs #5602 * fix(v3/ios): quote product-name paths in generated Xcode project The pbxproj template left the c-archive PBXFileReference path and the build phase's 'go build -o' path unquoted, so a productName containing a space (the default config ships 'My Product') produced an invalid project.pbxproj — Xcode reported 'The project main is damaged ... parse error'. Quote both paths. Also give the mobile example a real, space-free productName (KitchenSink) and identifier. Refs #5602 * fix(v3/mobile example): add NSFaceIDUsageDescription; show feedback per control group - Add NSFaceIDUsageDescription to the iOS Info.plist(s). Tapping Authenticate called LAContext biometric evaluation, which hard-crashes on a real device without this key (same class as the camera/mic keys). The crash left the app white-screening on relaunch; a clean reinstall + this key resolve it. - Move result feedback out of one box at the bottom of each section into a compact inline output beside each control group (Share, Screen, Status bar, Security, Secure storage, Haptics, Location, Motion, Speech, Keyboard, Camera, Background), so results appear next to the control you tapped. Refs #5602 * fix(v3/ios): reload WebView when iOS terminates the WebContent process WKWebView's renderer (WebContent) process is reaped by iOS under memory pressure or after repeated background/foreground and launch cycles. With no webViewWebContentProcessDidTerminate: handler the view stayed permanently blank — the 'white screen on the 3rd+ launch' reported on device. Implement the delegate method to reload and respawn the renderer. Framework-level fix; affects all Wails v3 iOS apps. Refs #5602 * fix(v3/ios): fix cold-launch white screen (double WebView) + launch flash Two iOS fixes: - ios_create_webview_with_id manually called -loadView and -viewDidLoad, so UIKit ALSO loaded the view automatically — two WebViews/viewDidLoad passes per cold launch. Content loaded into one while the blank one was displayed → intermittent white screen after force-quit + relaunch. Load the view once via -loadViewIfNeeded. Verified on-device: one viewDidLoad per launch, renders reliably across repeated force-quit relaunches. - Also fix the missing withError: parameter on didFailProvisionalNavigation (the old selector never matched, so load failures were swallowed) and add a didFailNavigation:withError: handler. Flash: any non-zero IOSOptions.BackgroundColour is now applied to the app window automatically (no need to also set AppBackgroundColourSet), so the window matches the web background instead of flashing white before the WebView paints. The mobile example sets it to its web background (#1b2636). Refs #5602 * refactor(v3/ios): remove IOSOptions.AppBackgroundColourSet The flag only existed to distinguish a deliberate fully-transparent app window ({0,0,0,0}) from the unset zero value — but a transparent app-window background is meaningless (the window needs an opaque backing), so the distinction had no real use. Drop it and treat any non-zero BackgroundColour as set; the zero value means unset (defaults to white). Simpler API, one field instead of two. Refs #5602 * fix(v3/ios): remove white launch flash (launch screen + initial window) The app declared UILaunchStoryboardName but the build never bundled a compiled storyboard, so iOS showed a white default launch screen; and the window was created white in the delegate before Go could apply a background colour. Both caused a white flash on (cold) launch. - Add a LaunchBackground colour asset (#1b2636) and switch Info.plist(s) to UILaunchScreen { UIColorName = LaunchBackground } — bundled via the asset catalog the build already compiles. - Colour the initial UIWindow/root view from the same LaunchBackground asset in didFinishLaunching (timing-independent; falls back to white if absent). Refs #5602 * fix(v3/ios): show local notifications while app is in foreground iOS suppresses a local notification's banner while the app that posted it is in the foreground unless the app registers a UNUserNotificationCenterDelegate and opts into presentation. Wails registered no such delegate, so notifications fired but were delivered silently to Notification Center with no banner. Add an MFNotificationDelegate (UNUserNotificationCenterDelegate) whose willPresentNotification returns banner+list+sound (alert+sound pre-iOS 14) and didReceiveNotificationResponse handles taps. Register it via ios_notifications_init() — both at launch (see delegate change) and defensively at the top of ios_post_notification so it is set regardless of build path. The mobile example surfaces the new common:notification {presented}/{tapped} signals so foreground delivery is visible in the UI. * fix(v3/ios): finalize cold-launch fix and clean up iOS build templates Start the Go runtime from the app delegate's didFinishLaunchingWithOptions (after UIKit has launched) instead of concurrently with UIApplicationMain, which intermittently corrupted the FrontBoard launch handshake on device (blank cold launch / 0x8BADF00D). main.m is now a minimal bootstrap; because it no longer references a Go symbol the archive must be linked with -Wl,-force_load (set in all iOS Taskfiles) or the WailsAppDelegate class is dead-stripped. Also: register the notification delegate before launch finishes; remove the dead, archive-less build.sh.tmpl (+ rendered copies); align the older examples/ios bootstrap with the verified examples/mobile pattern; tidy the example go.mod. * fix(v3/ios): make ios:run/package work in a normal (non-repo) project Three bugs prevented a plain 'wails3 init' app from building/running on iOS, while the in-repo examples masked them: - overlay:gen read its main_ios.go template from the wails SOURCE TREE via repoRoot(); outside the wails repo that path doesn't exist, so it failed with 'file does not exist'. Read it from the embedded build_assets FS instead (same approach as xcode:gen). - run/deploy-simulator/deploy-device hardcoded a com.wails.<app>[.dev] bundle id for simctl/devicectl, but the real id comes from build/config.yml (e.g. com.example.<app>). simctl install succeeded but launch failed (code 4). Read CFBundleIdentifier from the built bundle's Info.plist instead. - the iOS build depended on common:generate:icons (icns/ico — not used by iOS, and its dir:build broke through the nested include) and a redundant generate:ios:bindings that raced build:frontend's own bindings -clean. Both removed; iOS icons come from Assets.xcassets via actool and bindings from build:frontend. Verified end-to-end: 'wails3 init' + 'wails3 task ios:run' builds, installs and launches a default app on the iOS Simulator. * docs(ios): document the IOS manager pattern + common/ios events; add verified simulator screenshot - ios.mdx: replace the inaccurate runtime blurb (which mixed Go and JS APIs and referenced a non-existent IOS.Scroll.SetEnabled JS call) with proper sections: * 'Native device features — the IOS manager': the Go application.IOS.* pattern (one-shot actions, JSON query helpers) with correct Haptic values. * 'Asynchronous results: common:* and ios:* events': the event model with a full table mapping each event to the manager call that triggers it and its payload (common:* = shared with Android, ios:* = iOS-only). * 'Runtime WebView controls': the Go SetScrollEnabled/... toggles plus the real frontend runtime (IOS.Haptics.Impact, IOS.Device.Info). - first-mobile-app.mdx: add a screenshot of a default 'wails3 init' app running on the iOS Simulator, captured by actually running the tutorial end to end. The tutorial's command sequence (wails3 init -> wails3 task ios:run -> ios:logs:dev -> ios:xcode) was verified by running it against a fresh project on the Simulator. * fix(v3/templates): make the default app templates mobile-responsive On a phone the starter app's 'Wails + <Framework>' heading (font-size: 3.2em) overflowed and wrapped onto two lines, and content ran to the screen edges under the notch. Applied the same responsive treatment to every template's style.css and index.html: - h1 uses clamp(1.9rem, 8vw, 3.2rem) so it fits one line on phones and stays 3.2rem on desktop. - .container gets box-sizing, a 720px max-width and safe-area-aware padding (env(safe-area-inset-*)) so content clears the notch/home indicator. - logos cap at 40vw and shrink on <=480px viewports. - viewport meta gains viewport-fit=cover so the safe-area insets apply on iOS. Verified on the iOS Simulator with a fresh 'wails3 init' app: the heading now renders on a single line. The docs tutorial screenshot is updated to match. * docs(mobile): correct Go version to 1.25+ and simplify first-run steps - The mobile guides stated 'Go 1.24+', but the generated project's go.mod (and wails/v3) require go 1.25.0. Corrected to 1.25+ in ios, android, first-mobile-app and the index requirements table. - first-mobile-app: replaced the six-bullet technical breakdown of 'wails3 task ios:run' (Xcode scaffold, c-archive, GOOS=ios, ad-hoc signing, ...) with a single beginner-friendly sentence plus a short 'first run is slow' tip. Verified against the code: config.yml keys (bundleID/displayName/version/ minIOSVersion), IOSOptions fields, and the what-works table (save-file dialog error, UIPasteboard clipboard, UIDocumentPicker) are all accurate. * docs(ios): use dark-mode simulator screenshot in the first-app tutorial * docs(ios): update first-app tutorial simulator screenshot * fix(v3/android): make android:run work in a normal project (like iOS) A plain 'wails3 init' app could not run on Android. Four bugs, the same class as the iOS ones (masked by the in-repo example): - ensure-emulator had a go-task template with backslash-escaped quotes ({{if eq .HOST_ARCH \"arm64\"}}) that fails to parse ('unexpected \ in operand'), aborting any android task. Replaced with a precomputed ANDROID_ABI var. - gradlew lost its executable bit when extracted from the embedded build assets ('permission denied', exit 127). chmod +x before invoking it. - the app launched but showed ERR_CONNECTION_REFUSED: the c-shared library has no main registered because main_android.go (which calls application.RegisterAndroidMain in init) was only extracted to build/android/, not the main package. Added 'wails3 android overlay:gen' (mirrors iOS overlay:gen) to inject main_android.gen.go into the main package via -overlay, wired into the c-shared build. - dropped the bogus common:generate:icons (unused on android, breaks through the nested include) and redundant generate:android:bindings (races build:frontend's own bindings) deps. Verified end to end: a fresh 'wails3 init' app builds, installs and renders on the Android emulator. * fix(v3/android): robust emulator auto-boot + sync example to overlay approach - ensure-emulator: start the emulator via 'nohup sh -c "... &"' so it is reparented to launchd and survives go-task reaping background jobs (a bare 'emulator &' is killed before the install/launch steps run); also tolerate the logcat grep exiting non-zero. - example: bring build/android/Taskfile.yml in line with the template (overlay wiring, gradlew chmod, dropped bogus deps) and remove the hand-written root main_android.go — the build overlay now injects main registration, so the example matches what a fresh project generates. Update main.go's comment to describe the ios/android overlay mechanism. Verified on the Android emulator: both a fresh 'wails3 init' app and the kitchen-sink example build, install and render (dark mode). * chore(v3/mobile): gitignore generated mobile overlay artifacts The iOS/Android build overlays (overlay.json + gen/) contain per-machine absolute paths and are regenerated each build, so they must not be committed. The example already ignored the iOS ones; add the Android equivalents, and add both to the fresh-project template .gitignore so 'wails3 init' projects ignore them too. * fix(v3/android): robust emulator auto-boot + sync example android Taskfile (Completes the previous commit, whose git add aborted on an already-removed path and silently dropped these files.) - ensure-emulator: daemonize the emulator via 'nohup sh -c "... &"' so it survives go-task reaping background jobs; tolerate the logcat grep exiting non-zero. - example: sync build/android/Taskfile.yml with the template (overlay wiring, gradlew chmod, dropped bogus deps) and update main.go's comment to describe the ios/android overlay mechanism (root main_android.go was already removed).
Summary
Brings iOS and Android from partially-scaffolded to working, verified mobile targets with the same runtime surface as desktop — service bindings, events (both directions), dialogs, clipboard and screens all route through the standard
@wailsio/runtime. The samemain.gobuilds for desktop, iOS and Android.WKWebView, assets in-process over awails://scheme.WebView, assets in-process viaWebViewAssetLoader; Go compiled aslibwails.so(JNI) and hosted by a small JavaMainActivity/WailsBridge.Both platforms are flagged Experimental in the docs.
What works (verified on simulator / emulator)
wails://(WKURLSchemeHandler)WebViewAssetLoaderevents.IOS.*events.Android.*:fatHighlights
Compile health —
GOOS=iosandGOOS=androidbuild again (debug + production). Key gotcha fixed:GOOS=iosimplies thedarwintag andGOOS=androidimplies thelinuxtag, so desktop-only files were leaking into mobile builds (build-tag sweep acrosspkg/applicationand several services).Runtime correctness
wails:runtime:readyhandshake can't be lost; removed a fixed 2s startup sleep and a per-call C-string leak.fetch()POST bodies toshouldInterceptRequest, so the default HTTP runtime transport crashed on a nil body — added a JavascriptInterface transport (nativeHandleRuntimeCall, responses viawindow._wailsAndroidCallback); the bundled runtime auto-installs it when the bridge is present.Events —
tasks/events/generate.gonow emits both iOS and Android event mappings fromevents.txt(a latentvar iOSin the template that would re-breakGOOS=ioson regen is fixed tovar IOS).Tooling & docs —
wails3 doctorreports Xcode/iOS SDKs and the Android SDK/NDK/JDK; newIOS.md/ANDROID.mdand docs-site guides; device/release build tasks (iOS signing/entitlements/IPA, Android keystore signing + fat APK).Commits
The first commit seeds the iOS/Android command-template scaffolding; the remaining 9 are the iOS (6) and Android (3) feature commits.
Notes / known limitations
wails3 generate bindingsfor mobile emits cosmetic[warn]typecheck noise (host tags bleeding into the cross-compile typecheck); bindings generate correctly..aab(Android).Verification
iOS Simulator (Xcode 26.5) and Android emulator (API 34, arm64): assets load, JS→Go bindings render, Go→JS events tick, native dialogs show, clipboard round-trips, device/screen info return real data, and stripped production builds install and launch.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation