brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
mobile-ionic-capacitorUse this skill when the user says 'Ionic', 'Capacitor', 'Ionic app', 'hybrid mobile', 'web-to-mobile', 'Capacitor plugin', 'Ionic framework', 'Ionic React', 'Ionic Angular', 'Ionic Vue'. Build hybrid mobile apps using Ionic UI components and Capacitor native bridge with web-to-native access. Do NOT use for: native mobile development (KMP/MAUI) or pure web a…
Independent GitHub snapshot · Automated classification · Original documentation governs
Guide for building hybrid mobile apps with Ionic framework and Capacitor native bridge.
Phrases: "Ionic", "Capacitor", "Ionic app", "hybrid mobile", "web-to-mobile", "Capacitor plugin", "Ionic framework", "Ionic React", "Ionic Angular", "Ionic Vue"
Ionic project with: Capacitor config, native plugin integrations, custom plugin code, build/deploy pipeline scripts.
<ionic-capacitor>
<project>{framework, capacitor config, structure}</project>
<plugins>{installed plugins, config, permissions}</plugins>
<custom-plugin>{swift/kotlin, call pattern}</custom-plugin>
<build>{sync, xcode, android-studio steps}</build>
</ionic-capacitor>
No preamble. No postamble. No explanations. No filler/hedging/transitions. Compress output — why use many token when few do trick.
ionic build succeeds with zero errorsnpx cap sync copies web assets to both platforms8000 tokens
┌─────────────────────────────────────────────┐
│ Web Application (SPA) │
│ Ionic UI Components | Framework (React/ │
│ Angular/Vue) | App Logic │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ Capacitor Bridge (WKWebView/ │
│ Android WebView) │
│ • Plugin registry: maps JS calls to native │
│ • JSON serialization/deserialization │
│ • Error propagation via Promise reject │
│ • Event emitter for native-to-JS events │
└───────┬─────────────────────┬───────────────┘
│ │
┌───────▼─────────┐ ┌───────▼───────────────┐
│ iOS Runtime │ │ Android Runtime │
│ (Swift/Obj-C) │ │ (Kotlin/Java) │
│ CAPPlugin │ │ CAPPlugin │
└─────────────────┘ └───────────────────────┘
Team expertise?
├── React developers → Ionic React
│ Pros: hooks, largest ecosystem, TypeScript-first
│ Cons: routing less mature than Angular
├── Angular developers → Ionic Angular
│ Pros: mature routing (lazy loading, guards), full-featured
│ Cons: more boilerplate, steeper learning curve for new devs
└── Vue developers → Ionic Vue
Pros: lightweight, composition API, growing ecosystem
Cons: fewer community plugins, smaller talent pool
Need native device access?
├── Common feature (camera, geolocation, storage)
│ → Use official @capacitor/* plugin
│ → Configure permissions in native project files
├── Niche but exists (bluetooth, NFC, health kit)
│ → Search npm for @capacitor-community/* or capacitor-*
│ → Evaluate maintenance status, open issues, last update
├── Existing Cordova plugin
│ → Use @capacitor/cordova-plugin-compat
│ → Test thoroughly — not all Cordova patterns migrate cleanly
└── Completely custom native requirement
→ Write custom plugin (Swift + Kotlin)
→ Keep plugin surface small, test on real device
Ionic framework overview — Ionic is a UI toolkit for building cross-platform mobile apps using web technologies (HTML, CSS, JS). It provides a library of mobile-optimized UI components (ion-*), gestures, and animations that mimic native platform conventions. Three framework integrations: Ionic React (hooks, fast iteration, largest ecosystem), Ionic Angular (NgModule + standalone, mature routing, full-featured), Ionic Vue (composition API, lightweight, growing ecosystem). All share @ionic/core components and CSS custom properties theming.
Capacitor vs Cordova — Capacitor is the successor to Cordova with key advantages: modern plugin API (Promise-based instead of callback), native project files committed to repo (full Xcode/Android Studio control), Swift/Kotlin plugin development (instead of Java/Obj-C), HMR support during development, PWA fallback for web, and unified config (capacitor.config.ts). Cordova plugins are compatible via @capacitor/cordova-plugin-compat. Migration path: npx cap init on existing Cordova project, then replace cordova plugin add with npm install @capacitor/plugin-name.
Project creation — ionic start myApp blank --type=react-ts creates an Ionic React TypeScript project with Capacitor pre-configured. Key files: capacitor.config.ts (app ID, name, server URL for live reload), ionic.config.json (project type, integrations), src/ (web app code), ios/ (Xcode project after npx cap add ios), android/ (Android Studio project after npx cap add android). The web app is the single source of truth — native projects are generated artifacts.
Live reload development — ionic serve for web-only HMR. For device live reload: ionic cap run ios -l --external starts a dev server with your machine's IP, updates capacitor.config.ts with server.url, then opens Xcode. The device loads web assets from the dev server over the network. Hot Module Replacement preserves component state. For Android: ionic cap run android -l --external. Requires device on same network as dev machine. Disable server.url for production builds.
Capacitor plugin system — Plugins are npm packages that expose native APIs to JavaScript. Architecture: TypeScript API definition (@capacitor/plugin-name) -> iOS implementation (Swift, CAPPlugin subclass) -> Android implementation (Kotlin, CAPPlugin subclass) -> optional web fallback. Plugin calls are serialized via JSON through the WebView bridge. npx cap sync installs plugin pods (iOS) and copies plugin code (Android). Permissions must be configured in native project files before plugin use.
PWA conversion — Capacitor apps are PWAs by default. The same web code works as a standalone PWA when served over HTTPS. Add a manifest.json with icons, service-worker.js for caching. Capacitor plugins gracefully degrade: check Capacitor.isPluginAvailable('Camera') before calling native APIs. PWA mode runs in the browser, not WebView. Use @capacitor/filesystem and @capacitor/storage as they have web fallbacks.
Build and deploy pipeline — ionic build --prod generates optimized web assets in www/. npx cap copy copies to native platforms, npx cap sync also installs native dependencies. Code signing: iOS via Xcode Automatic signing (requires Apple Developer account), Android via keystore (keytool -genkey then ./gradlew bundleRelease). Distribution: App Store Connect (iOS) via Xcode Organizer or Transporter, Google Play Console (Android) via signed AAB upload. Appflow and EAS Build for cloud CI/CD.
Custom plugin development — When an official plugin does not exist, create a custom plugin. Structure: npx cap plugin:generate my-plugin scaffolds the plugin template. The generated plugin has src/definitions.ts (TypeScript interface), src/web.ts (web fallback), ios/Plugin/Plugin.swift (iOS native), android/src/main/.../Plugin.kt (Android native). Implement @objc annotated methods on iOS and @PluginMethod annotated methods on Android. Return data via call.resolve() and errors via call.reject(). Test the plugin in a sample Ionic app before publishing.
Deep linking and universal links — Configure deep links to open the app from URLs. iOS: configure apple-app-site-association file on your server, add associated domain in Xcode, handle in AppDelegate. Android: configure intent filters in AndroidManifest.xml for URL scheme. Capacitor's App.addListener('appUrlOpen') catches incoming URLs in the web layer. Map URL paths to navigation routes. Test with xcrun simctl openurl for iOS and adb shell am start for Android.
Push notifications — Set up push notifications via @capacitor/push-notifications. iOS: configure APNs certificate in Apple Developer Portal, add Push Notifications capability in Xcode. Android: set up Firebase Cloud Messaging, upload server key to Firebase Console. Register for notifications: PushNotifications.requestPermissions() then PushNotifications.register(). Handle foreground notifications with PushNotifications.addListener('pushNotificationReceived'). Handle background tap actions with PushNotifications.addListener('pushNotificationActionPerformed'). Test with curl to APNs/FCM endpoints or use Pusher/PushCompanion tools.
| Feature | iOS | Android | Web/PWA |
|---|---|---|---|
| Core UI components | Full | Full | Full |
| Native plugins | All | All | Graceful fallback |
| Push notifications | APNs | FCM | Not supported |
| Background mode | Limited | Yes | Not supported |
| Live reload | Yes | Yes | Dev only |
| HMR | Via server | Via server | Built-in |
| Camera access | Full | Full | Browser limited |
| Geolocation | Full | Full | Browser limited |
| Biometric auth | Face ID/Touch ID | Fingerprint/Biometric | Not supported |
| File system | Full | Full | Sandboxed |
ion- components exclusively — avoid mixing with platform-specific UIios/ and android/ directories to version controlnpx cap sync after every npm dependency change — not just npx cap copycapacitor.config.ts environment-aware: different server.url for dev/prodion-content scroll assistance with keyboard plugin to handle keyboard overlapSplashScreen.backgroundColor to match app background color to prevent white flashCapacitor.isPluginAvailable() before calling any plugin method in web contextCapacitor.convertFileSrc() for filesystem paths to ensure correct URL schemenpx cap sync must run after every plugin install or npm update.@capacitor/http for production API calls.@capacitor/keyboard with ion-content scroll assistance to handle keyboard show/hide.backgroundColor in capacitor.config.ts to match splash color — prevents white flash.call.resolve() in async callback.window.performance.memory.server.cleartext: true — never ship this to production apps.npx capacitor-assets generate for consistent asset generation.| Approach | Use Case | Tradeoff |
|---|---|---|
| Ionic + Capacitor | Hybrid apps, web-to-mobile migration | WebView performance ceiling, native API gap |
| React Native | Near-native perf, large ecosystem | Separate RN component lib, thicker bridge |
| Flutter | High-performance cross-platform | Dart language, custom rendering engine |
| Kotlin Multiplatform | Native UI, shared logic | Two UI codebases, less mature |
| MAUI (.NET) | .NET ecosystem, enterprise | Windows-focused, smaller mobile community |
| Pure PWA | No app store, instant updates | Limited device API access, no push on iOS |
| Native (Swift/Kotlin) | Full platform access | Two codebases, higher maintenance cost |
ion-img with lazy loading (Intersection Observer-based) instead of <img> tagswindow.performance.memory@ionic/core tree-shaking with Vite or webpackion-content uses native scrolling on iOS and synthetic scrolling on Android — virtual scrolling (ion-virtual-scroll) for lists over 100 items| Tool | Category | Purpose |
|---|---|---|
| Ionic CLI | Project management | Start, build, serve, cap commands |
| Capacitor CLI | Native bridge management | Add platforms, sync, copy, open |
| Appflow | CI/CD | Cloud build, live deploy, package |
| EAS Build (Expo) | CI/CD alternative | Cloud builds for Capacitor apps |
| capacitor-assets | Asset generation | Auto-generate icons and splash screens |
| Portals (Ionic) | Micro-frontends | Embed web apps in native apps |
| Cordova Plugin Compat | Migration | Run Cordova plugins in Capacitor |
| Safari Web Inspector | Debugging | iOS WebView JS console, network, elements |
| Chrome DevTools | Debugging | Android WebView JS console, network, elements |
| xcodebuild / Gradle | Native build | Platform-specific compilation and signing |
| fastlane | Automation | Certificate management, beta deployment, screenshots |
| Maestro / Detox | E2E testing | Mobile UI testing for hybrid apps |
// capacitor.config.ts
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'MyApp',
webDir: 'www',
server: {
url: process.env.NODE_ENV === 'development' ? 'http://192.168.1.100:8100' : undefined,
cleartext: true,
},
plugins: {
SplashScreen: {
launchShowDuration: 3000,
backgroundColor: '#ffffff',
},
PushNotifications: {
presentationOptions: ['badge', 'sound', 'alert'],
},
},
ios: { contentInset: 'always' },
android: { allowMixedContent: true },
};
export default config;
ios/ and android/ directories must be committed to version control — they are the source of truth for native configurationnpx cap sync must be run after every npm dependency change — not just npx cap copyCapacitor.isPluginAvailable() when the app also runs as a PWAserver.url in capacitor.config.ts must be set for development and removed for production buildsnpx capacitor-assets generate — never manually resizedionic build --prod with optimizations enabled (ahead-of-time compilation, tree shaking)The WebView is the bottleneck in Ionic apps. Optimize for 60fps scrolling and smooth animations:
FastDom or wrapper that schedules writes after reads. Measure with Chrome DevTools Performance tab — look for "Layout" events >10ms.contain: layout style paint on off-screen components prevents the browser from recalculating layout/style/paint for elements outside the viewport. Apply to static sidebars, headers, footers.ion-img over <img>: Ionic's image component uses IntersectionObserver for lazy loading — images outside viewport don't load. Set --lazy-load-threshold for pre-load buffer. Fallback: loading="lazy" attribute on native <img>.ion-virtual-scroll renders only visible items + buffer. For lists >100 items, always use virtual scrolling. Configure approxItemHeight for smoother scroll. Replaced with ion-list + virtual-scroll in newer Ionic versions; consider cdk-virtual-scroll-viewport from Angular CDK for Angular projects.<div> chains with CSS Grid/Flexbox single-layer layouts. Use ion-item directly without wrapping in extra <div> elements.import { IonButton } from '@ionic/react' instead of importing everything. Audit with vite-plugin-inspect or webpack-bundle-analyzer.loadChildren: () => import('./orders/orders.module').then(m => m.OrdersModule). React: React.lazy(() => import('./OrdersPage')). Vue: () => import('./OrdersPage.vue').build.gradle dependencies, iOS Pods) always add binary size. Remove unused plugins from package.json and run npx cap sync.<link rel="modulepreload"> for the app shell and first-route modules. Reduces waterfall of module loading. Configure in vite.config.ts via optimizeDeps.include.runOutsideAngular (Ionic Angular): Plugin callbacks outside Angular's zone reduce change detection cycles. Wrap plugin calls:
import { NgZone } from '@angular/core';
constructor(private zone: NgZone) {}
async callPlugin() {
const result = await this.zone.runOutsideAngular(() => Camera.getPhoto({...}));
// Re-enter zone only for UI updates
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) in Swift, Handler(Looper.getMainLooper()).postDelayed({}, 100) in Kotlin.async/await everywhere. Never use Capacitor.convertFileSrc() synchronously — it's fine for path conversion but the underlying file may not be ready.@capacitor-community/native-audio preloading: For audio assets, preload in native layer during app initialization, trigger play with minimal JS overhead. Audio files in WebView have ~200ms startup latency; native playback is near-instant.@capacitor/filesystem cache directory: Use Directory.Cache for temporary files, Directory.Data for persistent app data. Clear cache on app version upgrade. Monitor with window.performance.memory?.usedJSHeapSize.@capacitor/cache or native image loading libraries (SDWebImage for iOS, Glide for Android) via custom plugin for production apps. WebView image cache is limited and cleared frequently.ion-content scroll events: Scroll event listeners fire at 60fps — throttle to 100-200ms for non-critical work (lazy loading, analytics). Use requestAnimationFrame for visual updates, setTimeout for data operations.ionic build --prod && npx cap sync android && cd android && ./gradlew bundleReleaseandroid/app/build.gradle signing configs:
android {
signingConfigs {
release {
storeFile file(System.getenv("KEYSTORE_PATH"))
storePassword System.getenv("KEYSTORE_PASS")
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASS")
}
}
buildTypes {
release { signingConfig signingConfigs.release }
}
}
ionic build --prod && npx cap sync ios && cd ios && xcodebuild -workspace App.xcworkspace -scheme App -archivePath App.xcarchive archivefastlane match to manage certificates and provisioning profiles across machines.# fastlane/Fastfile
lane :deploy_ios do
match(type: "appstore") # sync certificates
build_ios_app(scheme: "App")
upload_to_app_store(skip_metadata: true, skip_screenshots: true)
end
lane :deploy_android do
gradle(task: "bundleRelease")
upload_to_play_store(track: "internal")
end
server.url (live reload) vs production. Never ship production build with server.url set — it weakens security by allowing external content loading.// src/environments/environment.prod.ts
export const environment = {
production: true,
apiUrl: 'https://api.example.com',
appVersion: '1.2.3',
};
@objc(BackgroundGeolocation)
class BackgroundGeolocation: CAPPlugin {
private var locationManager = CLLocationManager()
@objc func startTracking(_ call: CAPPluginCall) {
locationManager.delegate = self
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.startUpdatingLocation()
call.resolve()
}
@objc func stopTracking(_ call: CAPPluginCall) {
locationManager.stopUpdatingLocation()
call.resolve()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
notifyListeners("locationUpdate", data: [
"latitude": location.coordinate.latitude,
"longitude": location.coordinate.longitude,
"accuracy": location.horizontalAccuracy
])
}
}
@CapacitorPlugin(name = "BackgroundGeolocation")
class BackgroundGeolocation : CAPPlugin() {
private val locationManager by lazy {
activity?.getSystemService(Context.LOCATION_SERVICE) as? LocationManager
}
@PluginMethod
fun startTracking(call: PluginCall) {
if (ActivityCompat.checkSelfPermission(
activity, Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
call.reject("Location permission not granted")
return
}
locationManager?.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 5000L, 10f,
object : LocationListener {
override fun onLocationChanged(location: Location) {
notifyListeners("locationUpdate", JSObject().apply {
put("latitude", location.latitude)
put("longitude", location.longitude)
put("accuracy", location.accuracy)
})
}
}
)
call.resolve()
}
@PluginMethod
fun stopTracking(call: PluginCall) {
locationManager?.removeUpdates(this)
call.resolve()
}
}
// src/web.ts
import { WebPlugin } from '@capacitor/core';
import type { BackgroundGeolocationPlugin } from './definitions';
export class BackgroundGeolocationWeb
extends WebPlugin
implements BackgroundGeolocationPlugin
{
async startTracking(): Promise<void> {
console.warn('BackgroundGeolocation not available on web');
// Fallback: use browser Geolocation API
navigator.geolocation.watchPosition(
(pos) => this.notifyListeners('locationUpdate', {
latitude: pos.coords.latitude,
longitude: pos.coords.longitude,
accuracy: pos.coords.accuracy,
}),
(err) => console.error(err),
{ enableHighAccuracy: true, timeout: 10000 }
);
}
async stopTracking(): Promise<void> {
// cleanup
}
}
CAPPluginCall mock.Hand off to native iOS/Android skills when custom plugin development needs deep platform API access beyond Capacitor's bridge.
` interface EventObserver { onEvent(event: T): Promise; }
class EventBus { private observers: Set<EventObserver> = new Set(); subscribe(observer: EventObserver): void { this.observers.add(observer); } unsubscribe(observer: EventObserver): void { this.observers.delete(observer); } async emit(event: T): Promise { const results = Array.from(this.observers).map(o => o.onEvent(event)); await Promise.allSettled(results); } } `
config: defaults: timeout: 30s retryCount: 3 overrides: production: timeout: 60s retryCount: 5 development: timeout: 300s retryCount: 1
| Metric | Threshold | Severity | Action |
|---|---|---|---|
| Error rate | > 1% over 5min | Critical | Page on-call |
| p99 latency | > 2s over 5min | Warning | Investigate |
| Throughput drop | > 50% over 1min | Critical | Check upstream |
| Queue depth | > 1000 over 1min | Warning | Scale consumers |
| Disk usage | > 85% | Warning | Clean or expand |
| Memory usage | > 90% heap | Critical | Restart or scale |
| Anti-Pattern | Symptom | Root Cause | Solution |
|---|---|---|---|
| Premature optimization | Complex code for no measured benefit | Guessing instead of profiling | Measure first, optimize based on data |
| Copy-paste reuse | Duplicate code across codebase | Lack of abstraction | Extract shared logic into libraries |
| Gold-plating | Features with no current requirement | Over-engineering | YAGNI — build what's needed now |
| Magical thinking | Assumptions without validation | Skipping error handling | Handle all failure modes explicitly |
Cache hierarchy: L1 (in-memory local) → L2 (distributed Redis/Memcached) → L3 (CDN/Edge). Cache invalidation: TTL-based (simple, stale), event-based (complex, fresh), write-through (consistent, higher write latency), write-behind (fast writes, eventual consistency).