git:20260501.ed3e92e to git:20261011.86834d4

92 added, 419 removed. Audit A to B.

---
name: debugging-capacitor
- description: Comprehensive debugging guide for Capacitor applications. Covers WebView debugging, native debugging, crash analysis, network inspection, and common issues. Use this skill when users report bugs, crashes, or need help diagnosing issues.
+ description: Diagnoses and fixes runtime bugs in Capacitor iOS/Android apps - white/blank screen on launch, startup crashes, `"X" plugin is not implemented on ios/android`, `net::ERR_CLEARTEXT_NOT_PERMITTED`, App Transport Security blocks, CORS on capacitor://localhost, permission denials, live reload not connecting, and Capacitor 8.5+ UIScene regressions (appUrlOpen, universal links, pause/resume silent because AppDelegate callbacks stopped). Covers Safari Web Inspector, chrome://inspect, Xcode/LLDB, Android Studio, `npx cap run --url` (Capacitor 9) vs `-l --host` (8), `xcrun simctl openurl|privacy|push`, `xcrun devicectl device process launch|openURL`, `xctrace`. Do not use for only reading device logs (ios-android-logs), the UIScene migration itself (capacitor-uiscene-migration), deep-link setup (capacitor-deep-linking), speed (capacitor-performance), crash SDKs (capacitor-native-observability), tests (capacitor-testing), or upgrades/build errors (capacitor-app-upgrades, cocoapods-to-spm).
---
- # Debugging Capacitor Applications
-
- Complete guide to debugging Capacitor apps on iOS and Android.
-
- ## When to Use This Skill
-
- - User reports app crashes
- - User needs to debug WebView/JavaScript
- - User needs to debug native code
- - User has network/API issues
- - User sees unexpected behavior
- - User asks how to debug
-
- ## Quick Reference: Debugging Tools
-
- | Platform | WebView Debug | Native Debug | Logs |
- |----------|--------------|--------------|------|
- | iOS | Safari Web Inspector | Xcode Debugger | Console.app |
- | Android | Chrome DevTools | Android Studio | adb logcat |
-
- ## WebView Debugging
-
- ### iOS: Safari Web Inspector
-
- 1. **Enable on device**:
- - Settings > Safari > Advanced > Web Inspector: ON
- - Settings > Safari > Advanced > JavaScript: ON
-
- 2. **Enable in Xcode** (capacitor.config.ts):
- ```typescript
- const config: CapacitorConfig = {
- ios: {
- webContentsDebuggingEnabled: true, // Required for iOS 16.4+
- },
- };
- ```
-
- 3. **Connect Safari**:
- - Open Safari on Mac
- - Develop menu > [Device Name] > [App Name]
- - If no Develop menu: Safari > Settings > Advanced > Show Develop menu
-
- 4. **Debug**:
- - Console: View JavaScript logs
- - Network: Inspect API calls
- - Elements: Inspect DOM
- - Sources: Set breakpoints
-
- ### Android: Chrome DevTools
-
- 1. **Enable in config** (capacitor.config.ts):
- ```typescript
- const config: CapacitorConfig = {
- android: {
- webContentsDebuggingEnabled: true,
- },
- };
- ```
-
- 2. **Connect Chrome**:
- - Open Chrome on computer
- - Navigate to `chrome://inspect`
- - Your device/emulator should appear
- - Click "inspect" under your app
-
- 3. **Debug features**:
- - Console: JavaScript logs
- - Network: API requests
- - Performance: Profiling
- - Application: Storage, cookies
-
- ### Remote Debugging with VS Code
-
- Install "Debugger for Chrome" extension:
-
- ```json
- // .vscode/launch.json
- {
- "version": "0.2.0",
- "configurations": [
- {
- "type": "chrome",
- "request": "attach",
- "name": "Attach to Android WebView",
- "port": 9222,
- "webRoot": "${workspaceFolder}/dist"
- }
- ]
- }
- ```
-
- ## Native Debugging
-
- ### iOS: Xcode Debugger
-
- 1. **Open in Xcode**:
- ```bash
- npx cap open ios
- ```
-
- 2. **Set breakpoints**:
- - Click line number in Swift/Obj-C files
- - Or use `breakpoint set --name methodName` in LLDB
-
- 3. **Run with debugger**:
- - Product > Run (Cmd + R)
- - Or click Play button
-
- 4. **LLDB Console commands**:
- ```lldb
- # Print variable
- po myVariable
-
- # Print object description
- p myObject
-
- # Continue execution
- continue
-
- # Step over
- next
-
- # Step into
- step
-
- # Print backtrace
- bt
- ```
-
- 5. **View crash logs**:
- - Window > Devices and Simulators
- - Select device > View Device Logs
-
- ### Android: Android Studio Debugger
-
- 1. **Open in Android Studio**:
- ```bash
- npx cap open android
- ```
-
- 2. **Attach debugger**:
- - Run > Attach Debugger to Android Process
- - Select your app
-
- 3. **Set breakpoints**:
- - Click line number in Java/Kotlin files
-
- 4. **Debug console**:
- ```
- # Evaluate expression
- myVariable
-
- # Run method
- myObject.toString()
- ```
-
- 5. **Logcat shortcuts**:
- - View > Tool Windows > Logcat
- - Filter by package: `package:com.yourapp`
-
- ## Console Logging
-
- ### JavaScript Side
-
- ```typescript
- // Basic logging
- console.log('Debug info:', data);
- console.warn('Warning:', issue);
- console.error('Error:', error);
-
- // Grouped logs
- console.group('API Call');
- console.log('URL:', url);
- console.log('Response:', response);
- console.groupEnd();
-
- // Table format
- console.table(arrayOfObjects);
-
- // Timing
- console.time('operation');
- // ... operation
- console.timeEnd('operation');
- ```
-
- ### Native Side (iOS)
-
- ```swift
- import os.log
-
- let logger = Logger(subsystem: "com.yourapp", category: "MyPlugin")
-
- // Log levels
- logger.debug("Debug message")
- logger.info("Info message")
- logger.warning("Warning message")
- logger.error("Error message")
-
- // With data
- logger.info("User ID: \(userId)")
-
- // Legacy NSLog (shows in Console.app)
- NSLog("Legacy log: %@", message)
- ```
-
- ### Native Side (Android)
-
- ```kotlin
- import android.util.Log
-
- // Log levels
- Log.v("MyPlugin", "Verbose message")
- Log.d("MyPlugin", "Debug message")
- Log.i("MyPlugin", "Info message")
- Log.w("MyPlugin", "Warning message")
- Log.e("MyPlugin", "Error message")
-
- // With exception
- Log.e("MyPlugin", "Error occurred", exception)
- ```
-
- ## Common Issues and Solutions
-
- ### Issue: App Crashes on Startup
-
- **Diagnosis**:
- ```bash
- # iOS - Check crash logs
- xcrun simctl spawn booted log stream --level debug | grep -i crash
-
- # Android - Check logcat
- adb logcat *:E | grep -i "fatal\|crash"
- ```
-
- **Common causes**:
- 1. Missing plugin registration
- 2. Invalid capacitor.config
- 3. Missing native dependencies
-
- **Solution checklist**:
- - [ ] Run `npx cap sync`
- - [ ] iOS: `cd ios/App && pod install`
- - [ ] Check Info.plist permissions
- - [ ] Check AndroidManifest.xml permissions
-
- ### Issue: Plugin Method Not Found
-
- **Error**: `Error: "MyPlugin" plugin is not implemented on ios/android`
-
- **Diagnosis**:
- ```typescript
- import { Capacitor } from '@capacitor/core';
-
- // Check if plugin exists
- console.log('Plugins:', Capacitor.Plugins);
- console.log('MyPlugin available:', !!Capacitor.Plugins.MyPlugin);
- ```
-
- **Solutions**:
- 1. Ensure plugin is installed: `npm install @capgo/plugin-name`
- 2. Run sync: `npx cap sync`
- 3. Check plugin is registered (native code)
-
- ### Issue: Network Requests Failing
-
- **Diagnosis**:
- ```typescript
- // Add request interceptor
- const originalFetch = window.fetch;
- window.fetch = async (...args) => {
- console.log('Fetch:', args[0]);
- try {
- const response = await originalFetch(...args);
- console.log('Response status:', response.status);
- return response;
- } catch (error) {
- console.error('Fetch error:', error);
- throw error;
- }
- };
- ```
-
- **Common causes**:
- 1. **iOS ATS blocking HTTP**: Add to Info.plist:
- ```xml
- <key>NSAppTransportSecurity</key>
- <dict>
- <key>NSAllowsArbitraryLoads</key>
- <true/>
- </dict>
- ```
-
- 2. **Android cleartext blocked**: Add to capacitor.config.ts:
- ```typescript
- server: {
- cleartext: true, // Only for development!
- }
- ```
-
- 3. **CORS issues**: Use native HTTP:
- ```typescript
- import { CapacitorHttp } from '@capacitor/core';
-
- const response = await CapacitorHttp.request({
- method: 'GET',
- url: 'https://api.example.com/data',
- });
- ```
-
- ### Issue: Permission Denied
-
- **Diagnosis**:
- ```typescript
- import { Permissions } from '@capacitor/core';
-
- // Check permission status
- const status = await Permissions.query({ name: 'camera' });
- console.log('Camera permission:', status.state);
- ```
-
- **iOS**: Check Info.plist has usage descriptions:
- ```xml
- <key>NSCameraUsageDescription</key>
- <string>We need camera access to scan documents</string>
- ```
-
- **Android**: Check AndroidManifest.xml:
- ```xml
- <uses-permission android:name="android.permission.CAMERA" />
- ```
-
- ### Issue: White Screen on Launch
-
- **Diagnosis**:
- 1. Check WebView console for errors (Safari/Chrome)
- 2. Check if `dist/` folder exists
- 3. Verify `webDir` in capacitor.config.ts
-
- **Solutions**:
- ```bash
- # Rebuild web assets
- npm run build
-
- # Sync to native
- npx cap sync
-
- # Check config
- cat capacitor.config.ts
- ```
-
- ### Issue: Deep Links Not Working
-
- **Diagnosis**:
- ```typescript
- import { App } from '@capacitor/app';
-
- App.addListener('appUrlOpen', (event) => {
- console.log('Deep link:', event.url);
- });
- ```
-
- **iOS**: Check Associated Domains entitlement and apple-app-site-association file.
+ # Debugging Capacitor Apps
- **Android**: Check intent filters in AndroidManifest.xml.
+ Find which layer is broken (web, bridge, native, OS config), prove it with the right inspector, then fix the smallest thing.
- ## Performance Debugging
+ ## When to Use
- ### JavaScript Performance
+ TRIGGER when:
+ - App shows a white/blank screen, crashes on launch, or freezes after a native call.
+ - Error `"<Name>" plugin is not implemented on ios|android` or `"<Name>.<method>()" is not implemented on ...`.
+ - Network calls fail on device only: `net::ERR_CLEARTEXT_NOT_PERMITTED`, ATS "requires the use of a secure connection", CORS errors.
+ - Permission prompts never appear or always deny.
+ - Live reload (`cap run --url`, `server.url`) shows a blank page or "Webpage not available".
+ - After updating to Capacitor 8.5+ / Xcode 27: `appUrlOpen` silent, universal links open the app but do nothing, JS `pause`/`resume` or custom AppDelegate code no longer runs.
+ - User asks "how do I debug / inspect my Capacitor app".
- ```typescript
- // Mark performance
- performance.mark('start');
- // ... operation
- performance.mark('end');
- performance.measure('operation', 'start', 'end');
+ Do not use:
+ - Only need to stream, filter, or save logs or crash reports -> `ios-android-logs`.
+ - Performing the AppDelegate -> SceneDelegate migration itself -> `capacitor-uiscene-migration`.
+ - Setting up deep links, AASA, assetlinks from scratch -> `capacitor-deep-linking`.
+ - Jank, slow startup, bundle size, memory tuning -> `capacitor-performance`.
+ - Sentry / Crashlytics / Datadog in production -> `capacitor-native-observability`.
+ - Writing unit/E2E tests -> `capacitor-testing`. AI/MCP device automation -> `capacitor-mcp`.
+ - Gradle/Xcode build failures from version bumps -> `capacitor-app-upgrades`; Pods/SPM resolution -> `cocoapods-to-spm`.
+ - Push token/delivery issues -> `capacitor-push-notifications`; keyboard overlap -> `capacitor-keyboard`; notch/insets -> `safe-area-handling`.
- const measures = performance.getEntriesByName('operation');
- console.log('Duration:', measures[0].duration);
- ```
+ ## Triage Workflow
- ### iOS Performance (Instruments)
+ 1. **Collect facts before touching code**: platform(s), simulator vs device, debug vs release, Capacitor version (`npx cap doctor`), what changed last (upgrade, plugin added, config edit), exact error text.
+ 2. **Classify the layer**:
+ - JS exception, failed fetch, bad route -> web layer: open the WebView inspector (Safari / `chrome://inspect`). See `references/webview-inspection.md`.
+ - `plugin is not implemented` -> bridge/registration: the native code is not in the binary or JS cannot be injected. See Error Handling below.
+ - Process dies / `EXC_BAD_ACCESS` / `FATAL EXCEPTION` -> native layer: run from Xcode / Android Studio with the debugger attached. See `references/native-debugging.md`.
+ - Works on Android but not iOS after 8.5/Xcode 27, link/lifecycle related -> UIScene. See `references/uiscene-regressions.md`.
+ 3. **Reproduce with a fresh sync**: `npm run build && npx cap sync` then rebuild native. Stale `public/` assets and un-synced plugins cause most "works in browser, broken on device" reports.
+ 4. **Read the native console** for Capacitor's own messages. iOS prints `⚡️ Loading app at ...`, `⚡️ [log] - ...`, `⚡️ ERROR: Unable to load ...` to **stdout** (visible in Xcode's console, `simctl launch --console-pty`, `devicectl ... launch --console`; not in Console.app). Android logs under tag `Capacitor` and JS console under `Capacitor/Console`. Log commands live in `ios-android-logs`.
+ 5. **Report the root cause and proposed fix to the user before invasive changes** (deleting `ios/`/`android/`, editing `project.pbxproj`, disabling ATS globally).
+ 6. Apply the fix, then run the Verification steps.
- 1. Product > Profile (Cmd + I)
- 2. Choose template:
- - Time Profiler: CPU usage
- - Allocations: Memory usage
- - Network: Network activity
+ ## Tool Matrix
- ### Android Performance (Profiler)
+ | Need | iOS | Android |
+ |------|-----|---------|
+ | Inspect DOM/JS/network | Safari > Develop > device > app | `chrome://inspect/#devices` |
+ | Native breakpoints | Xcode (`npx cap open ios`), LLDB | Android Studio debugger (`npx cap open android`) |
+ | Run on target from CLI | `npx cap run ios --target <id>` | `npx cap run android --target <id>` |
+ | List targets | `npx cap run ios --list` | `npx cap run android --list` |
+ | Open URL / deep link | `xcrun simctl openurl booted <url>`; device: `xcrun devicectl device process openURL --device <id> <url>` | `adb shell am start -W -a android.intent.action.VIEW -d "<url>" <appId>` |
+ | Reset/grant permissions (sim) | `xcrun simctl privacy booted reset all <appId>` | `adb shell pm revoke <appId> android.permission.CAMERA` / `adb shell pm clear <appId>` |
+ | Fake push (sim) | `xcrun simctl push booted <appId> payload.json` | send a real FCM message |
+ | Launch with stdout | `xcrun simctl launch --console-pty booted <appId>`; device: `xcrun devicectl device process launch --console --device <id> <appId>` | `adb logcat` |
+ | Profile | Instruments / `xcrun xctrace record --template 'Time Profiler'` | Android Studio Profiler |
- 1. View > Tool Windows > Profiler
- 2. Select:
- - CPU: Method tracing
- - Memory: Heap analysis
- - Network: Request timeline
+ Details and caveats: `references/device-cli.md`.
- ## Memory Debugging
+ ## Live Reload Quick Path
- ### JavaScript Memory Leaks
+ - Capacitor 9 (`@next`): `npx cap run ios --url http://192.168.1.68:5173`. The old `-l/--live-reload`, `--host`, `--port`, `--https` flags are removed.
+ - Capacitor 8.5 (stable): `npx cap run ios -l --host 192.168.1.68 --port 5173` (add `--https` for an https dev server).
+ - Android emulator/USB: `--forwardPorts 5173:5173` runs `adb reverse` so `http://localhost:5173` works.
+ - Dev server must bind to `0.0.0.0`; phone and Mac on the same Wi-Fi; iOS may ask for Local Network access; if denied the page never loads (re-enable in Settings > Privacy & Security > Local Network).
+ - Never ship `server.url` / `server.cleartext` in `capacitor.config.*`; grep before release.
- Use Chrome DevTools Memory tab:
- 1. Take heap snapshot
- 2. Perform action
- 3. Take another snapshot
- 4. Compare snapshots
+ ## Error Handling
- ### iOS Memory (Instruments)
+ | Error / symptom | Likely cause | Fix |
+ |---|---|---|
+ | `"Foo" plugin is not implemented on ios` | Plugin not in native project, or JS injection blocked | `npm i <plugin>` -> `npx cap sync ios`. SPM: check `ios/App/CapApp-SPM/Package.swift` lists it; CocoaPods: check `ios/App/Podfile`. Remove `WKAppBoundDomains` from Info.plist or set `ios.limitsNavigationsToAppBoundDomains: true`. |
+ | `"Foo" plugin is not implemented on android` | Gradle not synced, or a service worker intercepts the page | `npx cap sync android`, then File > Sync Project with Gradle Files. Disable/unregister service workers in the native build. |
+ | `"Foo.bar()" is not implemented on web` | Calling a native-only method in the browser | Guard with `Capacitor.isNativePlatform()` / `Capacitor.isPluginAvailable('Foo')`. |
+ | `⚡️ ERROR: Unable to load .../index.html` / white screen | `webDir` wrong or never copied | Build web, verify `webDir` matches output folder, `npx cap copy`. |
+ | White screen, no native error | JS crash before first render, or bad `<base href>` / absolute asset paths | Inspect WebView console; use relative/root paths that resolve under `capacitor://localhost` / `https://localhost`. |
+ | `net::ERR_CLEARTEXT_NOT_PERMITTED` | Android blocks HTTP (API 28+) | Use HTTPS. For dev only: `server.cleartext: true` or a `network_security_config.xml` scoped to the dev host. |
+ | "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." | iOS ATS | Use HTTPS. For dev only: `NSExceptionDomains` for the dev host, not global `NSAllowsArbitraryLoads`. |
+ | CORS error from API on device only | Origin is `capacitor://localhost` (iOS) / `https://localhost` (Android) | Allow those origins server-side, or enable `CapacitorHttp` (`plugins.CapacitorHttp.enabled: true`) to route `fetch`/XHR natively. |
+ | Crash on permission request (iOS, `This app has crashed because it attempted to access privacy-sensitive data without a usage description`) | Missing `NS*UsageDescription` | Add the key to `ios/App/App/Info.plist`. |
+ | Permission always `denied` on Android | Missing `<uses-permission>` or user chose "Don't allow" twice | Add to `AndroidManifest.xml`; guide user to settings. |
+ | `No such module 'Capacitor'` (Xcode) | Opened `.xcodeproj` with CocoaPods, or SPM packages not resolved | Pods: open `App.xcworkspace`, `npx cap sync ios`. SPM: File > Packages > Reset Package Caches. Deeper -> `cocoapods-to-spm`. |
+ | `Unable to boot device in current state: Booted` | Simulator already running | Not an error; use `booted` as the device id. |
+ | `appUrlOpen` / universal links silent on iOS only after 8.5 | AppDelegate `open url` / `continue userActivity` no longer called | See `references/uiscene-regressions.md`. |
+ | JS `pause`/`resume` stop firing | Custom SceneDelegate missing proxy forwarding, or logic left in `applicationDidEnterBackground` | See `references/uiscene-regressions.md`. |
- ```bash
- # Run with Leaks instrument
- xcrun instruments -t Leaks -D output.trace YourApp.app
- ```
+ More symptom-specific fixes: `references/common-failures.md`.
- ### Android Memory (LeakCanary)
+ ## Verification
- Add to build.gradle:
- ```groovy
- debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
- ```
+ After any fix, prove it:
+ 1. `npx cap doctor` shows matching `@capacitor/core`, `cli`, `ios`, `android` versions.
+ 2. `npm run build && npx cap sync` completes with no warnings about missing plugins.
+ 3. Native console shows `⚡️ Loading app at capacitor://localhost...` (iOS) / no `Capacitor/Console` errors (Android).
+ 4. WebView inspector console is clean on launch.
+ 5. Re-run the exact repro on both a simulator/emulator and a physical device, in a **release** build if the bug was release-only (`cap run` builds Debug by default; iOS accepts `--configuration Release`, Android release builds go through Android Studio or Gradle).
+ 6. For link/lifecycle fixes: test cold (app killed) and warm (app backgrounded) launch with `simctl openurl` / `adb shell am start`, and background/foreground once to see `pause` and `resume`.
+ 7. Before shipping: `grep -n "server" capacitor.config.*` shows no `url`/`cleartext` left over.
- ## Debugging Checklist
+ ## References
- When debugging issues:
+ Only load a reference when its topic is in play.
- - [ ] Check WebView console (Safari/Chrome DevTools)
- - [ ] Check native logs (Xcode Console/Logcat)
- - [ ] Verify plugin is installed and synced
- - [ ] Check permissions (Info.plist/AndroidManifest)
- - [ ] Test on real device (not just simulator)
- - [ ] Try clean build (`rm -rf node_modules && npm install`)
- - [ ] Verify capacitor.config.ts settings
- - [ ] Check for version mismatches (capacitor packages)
+ | File | Load when |
+ |------|-----------|
+ | `references/webview-inspection.md` | Attaching Safari/Chrome inspectors, inspector shows no app, debugging release builds, early-startup JS errors |
+ | `references/native-debugging.md` | Native crash, breakpoints in plugins, LLDB, Android Studio debugger, Instruments/xctrace, memory leaks |
+ | `references/device-cli.md` | Driving simulators/devices from the terminal: install, launch, open URLs, permissions, push, app containers |
+ | `references/uiscene-regressions.md` | iOS deep links, universal links, cold-start URL, pause/resume, or AppDelegate code broken after Capacitor 8.5 / Xcode 27 |
+ | `references/common-failures.md` | Detailed fixes for white screen, plugin not implemented, networking, permissions, live reload |
## Resources
- - Capacitor Debugging Guide: https://capacitorjs.com/docs/guides/debugging
- - Safari Web Inspector: https://webkit.org/web-inspector
- - Chrome DevTools: https://developer.chrome.com/docs/devtools
- - Xcode Debugging: https://developer.apple.com/documentation/xcode/debugging
- - Android Studio Debugging: https://developer.android.com/studio/debug
+ - https://capacitorjs.com/docs/ios/troubleshooting
+ - https://capacitorjs.com/docs/android/troubleshooting
+ - https://capacitorjs.com/docs/guides/live-reload
+ - https://capacitorjs.com/docs/updating/8-5
+ - https://capacitorjs.com/docs/cli/commands/run