Skip to content

feat(mobile): iOS and Android support for Wails v3 (experimental)#5562

Merged
leaanthony merged 64 commits into
masterfrom
feat/mobile-support
Jun 13, 2026
Merged

feat(mobile): iOS and Android support for Wails v3 (experimental)#5562
leaanthony merged 64 commits into
masterfrom
feat/mobile-support

Conversation

@leaanthony

@leaanthony leaanthony commented Jun 10, 2026

Copy link
Copy Markdown
Member

Draft / experimental. Mobile (iOS + Android) support for Wails v3, verified live on the iOS Simulator and the Android emulator. Opening as a draft for review.

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 same main.go builds for desktop, iOS and Android.

  • iOS — native UIKit app, WKWebView, assets in-process over a wails:// scheme.
  • Android — native app, Android WebView, assets in-process via WebViewAssetLoader; Go compiled as libwails.so (JNI) and hosted by a small Java MainActivity/WailsBridge.

Both platforms are flagged Experimental in the docs.

What works (verified on simulator / emulator)

Area iOS Android
In-process asset serving wails:// (WKURLSchemeHandler) WebViewAssetLoader
Service bindings (JS→Go) ✅ (JavascriptInterface transport)
Events (Go→JS / JS→Go)
Message dialogs ✅ UIAlertController ✅ AlertDialog
Open file/files ✅ UIDocumentPicker ✅ Storage Access Framework
Open dir / save file dir ✅ / save ❌ (errors) both ❌ (error with guidance)
Clipboard ✅ UIPasteboard ✅ ClipboardManager
Screens (incl. safe-area / insets) ✅ UIScreen ✅ WindowMetrics
Lifecycle events events.IOS.* events.Android.*
Device builds + signing ✅ devicectl / IPA ✅ keystore-signed APK / :fat

Highlights

Compile healthGOOS=ios and GOOS=android build again (debug + production). Key gotcha fixed: GOOS=ios implies the darwin tag and GOOS=android implies the linux tag, so desktop-only files were leaking into mobile builds (build-tag sweep across pkg/application and several services).

Runtime correctness

  • iOS: the page no longer loads twice at startup, so the wails:runtime:ready handshake can't be lost; removed a fixed 2s startup sleep and a per-call C-string leak.
  • Android: the WebView does not deliver fetch() POST bodies to shouldInterceptRequest, so the default HTTP runtime transport crashed on a nil body — added a JavascriptInterface transport (nativeHandleRuntimeCall, responses via window._wailsAndroidCallback); the bundled runtime auto-installs it when the bridge is present.

Eventstasks/events/generate.go now emits both iOS and Android event mappings from events.txt (a latent var iOS in the template that would re-break GOOS=ios on regen is fixed to var IOS).

Tooling & docswails3 doctor reports Xcode/iOS SDKs and the Android SDK/NDK/JDK; new IOS.md / ANDROID.md and 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

  • Only the first window is displayed (mobile is single-window/fullscreen); window geometry, menus and system tray are intentional no-ops.
  • Save-file dialogs (both platforms) and choose-directory (Android) return an explicit error — write into the app sandbox instead.
  • wails3 generate bindings for mobile emits cosmetic [warn] typecheck noise (host tags bleeding into the cross-compile typecheck); bindings generate correctly.
  • App Store / Play Store submission uses the generated Xcode project (iOS) or your own keystore / Android Studio for an .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

    • First-class iOS & Android support: simulator/emulator + device workflows, APK/IPA packaging & signing, example mobile app, native dialogs, file pickers, clipboard, haptics/vibrate, toast, and main-thread UI callbacks.
    • Runtime: device info, real screen metrics, typed Android lifecycle & WebView events, Android JS bridge transport.
  • Bug Fixes

    • Multiple stability and startup fixes for iOS/Android runtimes, event delivery, and JS↔native messaging.
  • Documentation

    • Added comprehensive iOS/Android guides, examples, porting notes, and tooling doctor updates.

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

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

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

Changes

Mobile platform support rollout

Layer / File(s) Summary
Docs, guides, and changelog
docs/astro.config.mjs, docs/src/content/docs/guides/build/*, v3/ANDROID.md, v3/IOS.md, v3/UNRELEASED_CHANGELOG.md, v3/examples/*/README.md
Adds iOS/Android guide pages, updates docs sidebar navigation, and records unreleased changelog entries.
Android examples & tooling
v3/examples/android/*, v3/internal/commands/android/*, v3/internal/commands/build_assets/android/*, v3/internal/commands/android/app/*
Adds Android example project, Taskfiles, Gradle scaffolding, wrappers, manifests, resources, proguard rules, and install/build/deploy tasks.
Android app runtime & bridge
v3/internal/commands/android/app/src/main/java/com/wails/app/*, v3/internal/runtime/desktop/@wailsio/runtime/src/*, v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/*
Implements MainActivity, WebView asset handling, WailsBridge, WailsJSBridge, path handler, JS callback transport, file picker, dialogs, clipboard, vibration, and device/screen info integration.
iOS examples & tooling
v3/examples/ios/*, v3/internal/commands/ios/*, v3/internal/commands/build_assets/ios/*, v3/internal/commands/updatable_build_assets/ios/*
Adds iOS example updates, Taskfiles, Xcode project templates/asset catalogs, plist/entitlements templating, build scripts, and iOS dependency installers.
iOS native runtime & bridge
v3/internal/commands/ios/main.m, v3/internal/commands/ios/project.pbxproj, v3/pkg/application/*ios*
Adds iOS main entry, exported C/Obj‑C bridge functions, document picker, message dialogs, clipboard, screen metrics, and runtime readiness signaling.
Shared runtime, events, and doctor updates
v3/pkg/events/*, v3/tasks/events/*, v3/internal/doctor/*, v3/pkg/application/*
Updates event registry (adds Android/iOS events), event generator templates, doctor checks for Android/iOS SDKs, and adjusts many build tags and platform-gated files to compile correctly across targets.

Sequence Diagram(s)

Estimated code review effort
🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • wailsapp/wails#5103: Overlapping changes to darwin build constraints and related platform gating.

Suggested labels
Enhancement, Documentation, runtime, go, MacOS, templates, cli, bindings, v3-alpha, size:XXL

Suggested reviewers

  • taliesin-ai
  • atterpac

Poem

🐰 I hopped through code both near and far,
Added mobiles, bridges, docs—what a bazaar!
Android hums, iOS dreams, builds take flight,
Guides glow new in the doc-site night.
Tiny paws applaud each merged delight.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mobile-support

@leaanthony leaanthony force-pushed the feat/mobile-support branch from 67737fe to e423867 Compare June 10, 2026 10:33
…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).
@leaanthony leaanthony force-pushed the feat/mobile-support branch from e423867 to ba68676 Compare June 10, 2026 10:36
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.
@leaanthony leaanthony marked this pull request as ready for review June 10, 2026 11:31
Copilot AI review requested due to automatic review settings June 10, 2026 11:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Comment thread v3/pkg/application/dialogs_ios.go
Comment thread v3/pkg/application/application_ios.m Outdated
Comment thread v3/examples/android/frontend/package-lock.json
Comment thread v3/internal/commands/ios/icon.png Outdated
Comment thread v3/internal/commands/android/app/src/main/AndroidManifest.xml Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 lift

Preserve the original request metadata when proxying Android asset requests.

nativeServeAsset() accepts jheaders, but serveAssetForAndroid() rebuilds a fresh http.Request from only method and path and then forces req.Body = http.NoBody. That means any non-/wails/runtime handler behind app.assets will see empty bodies and none of the original headers on Android, so same-origin fetch() 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 | 🟠 Major

Pin WebView navigation/origin before exposing window.wails (MainActivity.java)

MainActivity initially loads https://wails.localhost/, but the WebViewClient only intercepts wails.localhost requests and does not implement shouldOverrideUrlLoading/any origin allowlist. That means any navigation/redirect to a remote origin will still have webView.addJavascriptInterface(..., "wails") available. Also, the /wails/* responses returned from shouldInterceptRequest include Access-Control-Allow-Origin: *, letting an off-origin page fetch wails.localhost/wails/* endpoints.

Block external navigations (or strictly allow only wails.localhost) and gate/remove the wails JS interface when the loaded page is not on WAILS_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

escapeJsString doesn'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 result or error contains 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 | 🟠 Major

Remove/limit android:usesCleartextTraffic="true" in AndroidManifest templates

  • android:usesCleartextTraffic="true" is set in all provided AndroidManifest templates, but the app code loads https://wails.localhost/ (WAILS_SCHEME = "https") and serves content via WebViewAssetLoader/shouldInterceptRequest—there’s no cleartext HTTP dependency in the template flow.
  • Set usesCleartextTraffic to false by 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 win

Handle JSON marshaling errors.

The json.Marshal error 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 win

Handle JSON marshaling errors.

The json.Marshal error 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 win

Handle JSON marshaling errors.

The json.Marshal error 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 win

AVD selection may fail if emulator -list-avds output ends with a blank line.

Line 196 uses tail -1 to pick the last line from the AVD list. If the emulator command's output ends with a newline (common in shell utilities), AVD_NAME will 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 | 🟡 Minor

Add fail-fast validation for Android release keystore env vars
v3/internal/commands/android/app/build.gradle enables signingConfigs.release when only ANDROID_KEYSTORE_FILE is set, but it always reads ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, and ANDROID_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.md provides 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 win

Fail fast when device-only tasks are invoked with simulator defaults.

With IOS_PLATFORM=simulator and CODESIGN_IDENTITY=- as the defaults, package:ipa can zip a simulator bundle, and deploy-device can try to install whatever stale .app happens to be in bin. Add explicit preconditions for IOS_PLATFORM=device plus a real signing identity, and make deploy-device build/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 win

Hardcoded "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 encounter xcrun simctl create errors 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 | 🟡 Minor

Reframe XSS risk in WailsBridge.executeJavaScript: sink is raw JS, but key Go payloads are JSON-encoded

WailsBridge.executeJavaScript(String js) forwards js directly to WebView.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 %s is CustomEvent.ToJSON() (uses json.Marshal)
  • WebviewWindow.InitiateFrontendDropProcessing: handlePlatformFileDrop(%s, ...) where %s is json.Marshal(filenames) (inserted as a JSON expression)

Action: audit remaining ExecJS/execJS call paths to ensure any user-controlled string content is JSON-encoded/escaped before it’s interpolated into the JS string passed to executeJavaScript.

🤖 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 win

Add language specifier to fenced code block.

The fenced code block is missing a language identifier. Add a language specifier (e.g., text or tree) 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 tradeoff

Host 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) use arm64 architecture. The NDK provides both darwin-x86_64 and darwin-arm64 toolchains. 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 deprecated onBackPressed() with OnBackPressedCallback (OnBackPressedDispatcher).

v3/internal/commands/android/app/src/main/java/com/wails/app/MainActivity.java overrides onBackPressed() (lines 327-332), while the app targets targetSdk 34 (v3/internal/commands/android/app/build.gradle), so this will trigger deprecation/lint on API 33+. Use an OnBackPressedCallback (registered in onCreate) to run webView.goBack() when canGoBack(), 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 win

Avoid creating a raw thread per runtime call.

invokeAsync() sits on the main JS→native transport path, and this spawns an unbounded Thread for every call. Prefer a shared executor, or call bridge.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 value

Consider 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 simctl output 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 win

Consider validating MinIOSVersion format.

The code copies in.IOS.MinIOSVersion into cfg.MinIOSVersion without 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 the Info.plist templates (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 regexp at 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 win

Removed 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:

  1. Documenting the minimal entitlements set in IOS.md or ANDROID.md and instructing users to add additional entitlements as needed, or
  2. 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 value

Fragile 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 simctl versions
  • 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 --json for 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 win

Comment-code mismatch: "first" vs last AVD.

The comment says "Get first available AVD" but tail -1 retrieves the last AVD from the list. Consider updating the comment to match the implementation, or use head -1 if 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 tradeoff

Background 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 ExecutorService that can be shut down in onDestroy(), 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 win

Clipboard 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 value

Consider 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 win

Remove 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 dev and wails3 build on 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

📥 Commits

Reviewing files that changed from the base of the PR and between 27d74c3 and 591e897.

⛔ Files ignored due to path filters (14)
  • v3/examples/android/frontend/package-lock.json is excluded by !**/package-lock.json
  • v3/examples/ios/frontend/package-lock.json is excluded by !**/package-lock.json
  • v3/internal/commands/android/app/src/main/res/mipmap-hdpi/ic_launcher.png is excluded by !**/*.png
  • v3/internal/commands/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png is excluded by !**/*.png
  • v3/internal/commands/android/app/src/main/res/mipmap-mdpi/ic_launcher.png is excluded by !**/*.png
  • v3/internal/commands/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png is excluded by !**/*.png
  • v3/internal/commands/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png is excluded by !**/*.png
  • v3/internal/commands/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png is excluded by !**/*.png
  • v3/internal/commands/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png is excluded by !**/*.png
  • v3/internal/commands/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png is excluded by !**/*.png
  • v3/internal/commands/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png is excluded by !**/*.png
  • v3/internal/commands/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png is excluded by !**/*.png
  • v3/internal/commands/android/gradle/wrapper/gradle-wrapper.jar is excluded by !**/*.jar
  • v3/internal/commands/ios/icon.png is excluded by !**/*.png
📒 Files selected for processing (141)
  • docs/astro.config.mjs
  • docs/src/content/docs/guides/build/android.mdx
  • docs/src/content/docs/guides/build/ios.mdx
  • v3/ANDROID.md
  • v3/ANDROID_ARCHITECTURE.md
  • v3/IOS.md
  • v3/IOS_ARCHITECTURE.md
  • v3/IOS_FEATURES_TODO.md
  • v3/IOS_RUNTIME.md
  • v3/UNRELEASED_CHANGELOG.md
  • v3/build_ios.sh
  • v3/examples/android/.gitignore
  • v3/examples/android/.task/checksum/android-common-generate-icons
  • v3/examples/android/.task/checksum/android-common-install-frontend-deps
  • v3/examples/android/.task/checksum/android-generate-android-bindings
  • v3/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.md
  • v3/examples/android/build/android/Taskfile.yml
  • v3/examples/android/build/android/app/build.gradle
  • v3/examples/android/build/android/app/src/main/AndroidManifest.xml
  • v3/examples/android/build/android/app/src/main/java/com/wails/app/MainActivity.java
  • v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsBridge.java
  • v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java
  • v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsPathHandler.java
  • v3/examples/android/build/android/build/reports/problems/problems-report.html
  • v3/examples/android/frontend/package.json
  • v3/examples/ios/README.md
  • v3/examples/ios/build/ios/Taskfile.yml
  • v3/examples/ios/build/ios/entitlements.plist
  • v3/examples/ios/build/ios/xcode/main/Info.plist
  • v3/examples/ios/build/ios/xcode/main/LaunchScreen.storyboard
  • v3/examples/ios/build/ios/xcode/main/main.m
  • v3/examples/ios/build/ios/xcode/overlay.json
  • v3/examples/ios/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventcreate.js
  • v3/examples/ios/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts
  • v3/examples/ios/frontend/bindings/ios-example/greetservice.js
  • v3/examples/ios/frontend/bindings/ios-example/index.js
  • v3/examples/ios/frontend/main.js
  • v3/fix-darwin-ios-constraints.sh
  • v3/internal/commands/android/Taskfile.yml
  • v3/internal/commands/android/app/build.gradle
  • v3/internal/commands/android/app/proguard-rules.pro
  • v3/internal/commands/android/app/src/main/AndroidManifest.xml
  • v3/internal/commands/android/app/src/main/java/com/wails/app/MainActivity.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/WailsJSBridge.java
  • v3/internal/commands/android/app/src/main/java/com/wails/app/WailsPathHandler.java
  • v3/internal/commands/android/app/src/main/res/layout/activity_main.xml
  • v3/internal/commands/android/app/src/main/res/values/colors.xml
  • v3/internal/commands/android/app/src/main/res/values/strings.xml
  • v3/internal/commands/android/app/src/main/res/values/themes.xml
  • v3/internal/commands/android/build.gradle
  • v3/internal/commands/android/gradle.properties
  • v3/internal/commands/android/gradle/wrapper/gradle-wrapper.properties
  • v3/internal/commands/android/gradlew
  • v3/internal/commands/android/gradlew.bat
  • v3/internal/commands/android/main_android.go
  • v3/internal/commands/android/scripts/deps/install_deps.go
  • v3/internal/commands/android/settings.gradle
  • v3/internal/commands/build-assets.go
  • v3/internal/commands/build_assets/android/Taskfile.yml
  • v3/internal/commands/build_assets/android/app/build.gradle
  • v3/internal/commands/build_assets/android/app/src/main/AndroidManifest.xml
  • v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/MainActivity.java
  • v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsBridge.java
  • v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsJSBridge.java
  • v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsPathHandler.java
  • v3/internal/commands/build_assets/ios/Taskfile.yml
  • v3/internal/commands/ios/Assets.xcassets
  • v3/internal/commands/ios/Info.dev.plist
  • v3/internal/commands/ios/Info.plist
  • v3/internal/commands/ios/LaunchScreen.storyboard
  • v3/internal/commands/ios/Taskfile.yml
  • v3/internal/commands/ios/app_options_default.go
  • v3/internal/commands/ios/app_options_ios.go
  • v3/internal/commands/ios/build.sh
  • v3/internal/commands/ios/entitlements.plist
  • v3/internal/commands/ios/main.m
  • v3/internal/commands/ios/main_ios.go
  • v3/internal/commands/ios/project.pbxproj
  • v3/internal/commands/ios/scripts/deps/install_deps.go
  • v3/internal/commands/ios_xcode_gen.go
  • v3/internal/commands/updatable_build_assets/ios/Info.dev.plist.tmpl
  • v3/internal/commands/updatable_build_assets/ios/Info.plist.tmpl
  • v3/internal/commands/updatable_build_assets/ios/entitlements.plist.tmpl
  • v3/internal/doctor/doctor_common.go
  • v3/internal/doctor/doctor_darwin.go
  • v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/hierarchy.js
  • v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/navigation.js
  • v3/internal/runtime/desktop/@wailsio/runtime/docs/assets/search.js
  • v3/internal/runtime/desktop/@wailsio/runtime/docs/hierarchy.html
  • v3/internal/runtime/desktop/@wailsio/runtime/docs/variables/Events.Types.html
  • v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts
  • v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts
  • v3/pkg/application/android_logging_debug.go
  • v3/pkg/application/android_logging_production.go
  • v3/pkg/application/application_android.go
  • v3/pkg/application/application_android_nocgo.go
  • v3/pkg/application/application_ios.go
  • v3/pkg/application/application_ios.h
  • v3/pkg/application/application_ios.m
  • v3/pkg/application/application_ios_delegate.m
  • v3/pkg/application/clipboard_android.go
  • v3/pkg/application/clipboard_ios.go
  • v3/pkg/application/dialogs_android.go
  • v3/pkg/application/dialogs_ios.go
  • v3/pkg/application/environment_linux.go
  • v3/pkg/application/events_common_android.go
  • v3/pkg/application/ios_logging_debug.go
  • v3/pkg/application/ios_logging_production.go
  • v3/pkg/application/linux_cgo.c
  • v3/pkg/application/linux_cgo.h
  • v3/pkg/application/mainthread_android.go
  • v3/pkg/application/messageprocessor_android.go
  • v3/pkg/application/messageprocessor_ios.go
  • v3/pkg/application/screen_android.go
  • v3/pkg/application/screen_ios.go
  • v3/pkg/application/webview_window_android.go
  • v3/pkg/application/webview_window_darwin_production.go
  • v3/pkg/application/webview_window_ios.go
  • v3/pkg/application/webview_window_ios.m
  • v3/pkg/doctor-ng/platform_darwin.go
  • v3/pkg/doctor-ng/platform_ios.go
  • v3/pkg/events/events.go
  • v3/pkg/events/events.txt
  • v3/pkg/events/events_android.go
  • v3/pkg/events/events_ios.go
  • v3/pkg/events/events_linux.go
  • v3/pkg/events/known_events.go
  • v3/pkg/mac/mac.go
  • v3/pkg/services/dock/dock_darwin.go
  • v3/pkg/services/notifications/notifications_darwin.m
  • v3/pkg/updater/updater_darwin.go
  • v3/pkg/updater/updater_darwin_test.go
  • v3/pkg/updater/updater_notdarwin.go
  • v3/tasks/events/generate.go
  • v3/test-assets/index.html
  • v3/test-ios-compilation.go
  • v3/test-new-ios-build.sh
  • v3/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

Comment thread v3/examples/ios/build/ios/xcode/overlay.json Outdated
Comment thread v3/internal/commands/android/app/src/main/AndroidManifest.xml Outdated
Comment thread v3/internal/commands/ios/scripts/deps/install_deps.go
Comment thread v3/internal/doctor/doctor_common.go Outdated
Comment thread v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts
Comment thread v3/pkg/application/application_ios.m
Comment thread v3/pkg/application/webview_window_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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 win

Comment says "first" but code gets last AVD.

The comment at line 199 states "Get first available AVD", but tail -1 retrieves the last line from the AVD list, not the first. If the intent is to select the newest/last AVD, update the comment. Otherwise, use head -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 tradeoff

SDK_ROOT fallback silently accepts non-existent paths.

When ANDROID_HOME and ANDROID_SDK_ROOT are both unset and neither default path exists, SDK_ROOT will be set to a non-existent directory without error. Downstream tasks (e.g., compile:go:shared at 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

📥 Commits

Reviewing files that changed from the base of the PR and between 591e897 and 8c4894a.

⛔ Files ignored due to path filters (3)
  • v3/examples/android/frontend/package-lock.json is excluded by !**/package-lock.json
  • v3/examples/ios/build/ios/icon.png is excluded by !**/*.png
  • v3/internal/commands/ios/icon.png is excluded by !**/*.png
📒 Files selected for processing (28)
  • v3/examples/android/build/android/Taskfile.yml
  • v3/examples/android/build/android/app/build.gradle
  • v3/examples/android/build/android/app/src/main/AndroidManifest.xml
  • v3/examples/android/build/android/app/src/main/java/com/wails/app/MainActivity.java
  • v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsBridge.java
  • v3/examples/android/build/android/app/src/main/java/com/wails/app/WailsPathHandler.java
  • v3/examples/ios/.gitignore
  • v3/examples/ios/build/ios/project.pbxproj
  • v3/examples/ios/build/ios/xcode/main.xcodeproj/project.pbxproj
  • v3/examples/ios/build/ios/xcode/overlay.json
  • v3/internal/commands/android/Taskfile.yml
  • v3/internal/commands/android/app/build.gradle
  • v3/internal/commands/android/app/src/main/AndroidManifest.xml
  • v3/internal/commands/android/app/src/main/java/com/wails/app/MainActivity.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/WailsPathHandler.java
  • v3/internal/commands/build_assets/android/Taskfile.yml
  • v3/internal/commands/build_assets/android/app/build.gradle
  • v3/internal/commands/build_assets/android/app/src/main/AndroidManifest.xml
  • v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/MainActivity.java
  • v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsBridge.java
  • v3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsPathHandler.java
  • v3/internal/commands/ios/project.pbxproj
  • v3/internal/doctor/doctor_common.go
  • v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.ts
  • v3/pkg/application/application_ios.m
  • v3/pkg/application/dialogs_ios.go
  • v3/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

Comment thread v3/examples/ios/build/ios/project.pbxproj Outdated
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).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 value

Consider more comprehensive JavaScript string escaping.

The current escapeJsString handles \, ', \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 tradeoff

Emulator 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 win

Consider 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 win

Resource cleanup on early returns.

The copyUriToCache method 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 value

System 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8c4894a and 9752d52.

⛔ Files ignored due to path filters (32)
  • v3/examples/mobile/build/android/app/src/main/res/mipmap-hdpi/ic_launcher.png is excluded by !**/*.png
  • v3/examples/mobile/build/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png is excluded by !**/*.png
  • v3/examples/mobile/build/android/app/src/main/res/mipmap-mdpi/ic_launcher.png is excluded by !**/*.png
  • v3/examples/mobile/build/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png is excluded by !**/*.png
  • v3/examples/mobile/build/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png is excluded by !**/*.png
  • v3/examples/mobile/build/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png is excluded by !**/*.png
  • v3/examples/mobile/build/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png is excluded by !**/*.png
  • v3/examples/mobile/build/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png is excluded by !**/*.png
  • v3/examples/mobile/build/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png is excluded by !**/*.png
  • v3/examples/mobile/build/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png is excluded by !**/*.png
  • v3/examples/mobile/build/android/gradle/wrapper/gradle-wrapper.jar is excluded by !**/*.jar
  • v3/examples/mobile/build/appicon.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/icon.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-1024.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-76.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png is excluded by !**/*.png
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png is excluded by !**/*.png
  • v3/examples/mobile/build/windows/icon.ico is excluded by !**/*.ico
  • v3/examples/mobile/frontend/package-lock.json is excluded by !**/package-lock.json
  • v3/examples/mobile/frontend/public/wails.png is excluded by !**/*.png
  • v3/examples/mobile/go.sum is excluded by !**/*.sum
📒 Files selected for processing (79)
  • v3/examples/mobile/.gitignore
  • v3/examples/mobile/README.md
  • v3/examples/mobile/Taskfile.yml
  • v3/examples/mobile/build/Taskfile.yml
  • v3/examples/mobile/build/android/Taskfile.yml
  • v3/examples/mobile/build/android/app/build.gradle
  • v3/examples/mobile/build/android/app/proguard-rules.pro
  • v3/examples/mobile/build/android/app/src/main/AndroidManifest.xml
  • v3/examples/mobile/build/android/app/src/main/java/com/wails/app/MainActivity.java
  • v3/examples/mobile/build/android/app/src/main/java/com/wails/app/WailsBridge.java
  • v3/examples/mobile/build/android/app/src/main/java/com/wails/app/WailsJSBridge.java
  • v3/examples/mobile/build/android/app/src/main/java/com/wails/app/WailsPathHandler.java
  • v3/examples/mobile/build/android/app/src/main/res/layout/activity_main.xml
  • v3/examples/mobile/build/android/app/src/main/res/values/colors.xml
  • v3/examples/mobile/build/android/app/src/main/res/values/strings.xml
  • v3/examples/mobile/build/android/app/src/main/res/values/themes.xml
  • v3/examples/mobile/build/android/build.gradle
  • v3/examples/mobile/build/android/gradle.properties
  • v3/examples/mobile/build/android/gradle/wrapper/gradle-wrapper.properties
  • v3/examples/mobile/build/android/gradlew
  • v3/examples/mobile/build/android/gradlew.bat
  • v3/examples/mobile/build/android/scripts/deps/install_deps.go
  • v3/examples/mobile/build/android/settings.gradle
  • v3/examples/mobile/build/config.yml
  • v3/examples/mobile/build/darwin/Info.dev.plist
  • v3/examples/mobile/build/darwin/Info.plist
  • v3/examples/mobile/build/darwin/Taskfile.yml
  • v3/examples/mobile/build/darwin/icons.icns
  • v3/examples/mobile/build/ios/Assets.xcassets
  • v3/examples/mobile/build/ios/Info.dev.plist
  • v3/examples/mobile/build/ios/Info.plist
  • v3/examples/mobile/build/ios/LaunchScreen.storyboard
  • v3/examples/mobile/build/ios/Taskfile.yml
  • v3/examples/mobile/build/ios/app_options_default.go
  • v3/examples/mobile/build/ios/app_options_ios.go
  • v3/examples/mobile/build/ios/build.sh
  • v3/examples/mobile/build/ios/entitlements.plist
  • v3/examples/mobile/build/ios/main.m
  • v3/examples/mobile/build/ios/main_ios.go
  • v3/examples/mobile/build/ios/project.pbxproj
  • v3/examples/mobile/build/ios/scripts/deps/install_deps.go
  • v3/examples/mobile/build/ios/xcode/main/Assets.xcassets/AppIcon.appiconset/Contents.json
  • v3/examples/mobile/build/ios/xcode/main/Info.plist
  • v3/examples/mobile/build/ios/xcode/main/LaunchScreen.storyboard
  • v3/examples/mobile/build/ios/xcode/main/main.m
  • v3/examples/mobile/build/linux/Taskfile.yml
  • v3/examples/mobile/build/linux/appimage/build.sh
  • v3/examples/mobile/build/linux/desktop
  • v3/examples/mobile/build/linux/nfpm/nfpm.yaml
  • v3/examples/mobile/build/linux/nfpm/scripts/postinstall.sh
  • v3/examples/mobile/build/linux/nfpm/scripts/postremove.sh
  • v3/examples/mobile/build/linux/nfpm/scripts/preinstall.sh
  • v3/examples/mobile/build/linux/nfpm/scripts/preremove.sh
  • v3/examples/mobile/build/windows/Taskfile.yml
  • v3/examples/mobile/build/windows/info.json
  • v3/examples/mobile/build/windows/msix/app_manifest.xml
  • v3/examples/mobile/build/windows/msix/template.xml
  • v3/examples/mobile/build/windows/nsis/project.nsi
  • v3/examples/mobile/build/windows/nsis/wails_tools.nsh
  • v3/examples/mobile/build/windows/wails.exe.manifest
  • v3/examples/mobile/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventcreate.js
  • v3/examples/mobile/frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts
  • v3/examples/mobile/frontend/bindings/mobile-example/index.js
  • v3/examples/mobile/frontend/bindings/mobile-example/models.js
  • v3/examples/mobile/frontend/bindings/mobile-example/systemservice.js
  • v3/examples/mobile/frontend/index.html
  • v3/examples/mobile/frontend/main.js
  • v3/examples/mobile/frontend/package.json
  • v3/examples/mobile/frontend/public/style.css
  • v3/examples/mobile/frontend/vite.config.js
  • v3/examples/mobile/go.mod
  • v3/examples/mobile/greetservice.go
  • v3/examples/mobile/ios_runtime_events_ios.go
  • v3/examples/mobile/ios_runtime_events_stub.go
  • v3/examples/mobile/main.go
  • v3/examples/mobile/main_android.go
  • v3/internal/runtime/desktop/@wailsio/runtime/src/android.ts
  • v3/internal/runtime/desktop/@wailsio/runtime/src/index.ts
  • v3/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

Comment thread v3/examples/mobile/build/android/app/build.gradle
Comment thread v3/examples/mobile/build/android/app/src/main/AndroidManifest.xml
Comment thread v3/examples/mobile/build/android/scripts/deps/install_deps.go
Comment thread v3/examples/mobile/build/Taskfile.yml
Comment thread v3/examples/mobile/build/Taskfile.yml
Comment thread v3/examples/mobile/build/windows/nsis/project.nsi
Comment thread v3/examples/mobile/build/windows/Taskfile.yml
Comment thread v3/examples/mobile/frontend/public/style.css Outdated
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).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 win

Xcode cannot track the c-archive output for incremental builds.

The outputPaths array is empty. Xcode uses output paths to determine whether a build phase needs to re-run. Without declaring bin/{{.ProductName}}.a as 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 value

Overlay generation silently ignores failures.

The overlay generation command uses || true to 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9752d52 and fa490fd.

📒 Files selected for processing (1)
  • v3/internal/commands/updatable_build_assets/ios/project.pbxproj.tmpl

Comment thread v3/internal/commands/updatable_build_assets/ios/project.pbxproj.tmpl Outdated
…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.
@leaanthony

Copy link
Copy Markdown
Member Author

@coderabbitai Do another review

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

@leaanthony Sure! I'll kick off a fresh review of the PR now to cover all the changes including the fixes in commit 745b3cd.

✅ Action performed

Full 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).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

…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().
@leaanthony leaanthony merged commit 07bc6f1 into master Jun 13, 2026
13 of 15 checks passed
@leaanthony leaanthony deleted the feat/mobile-support branch June 13, 2026 07:01
leaanthony added a commit that referenced this pull request Jun 14, 2026
…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
leaanthony added a commit that referenced this pull request Jun 15, 2026
…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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants