diff --git a/llm-fe/.gitignore b/llm-fe/.gitignore index 38d2786..e3a67d3 100644 --- a/llm-fe/.gitignore +++ b/llm-fe/.gitignore @@ -23,6 +23,18 @@ *.keystore *.jks +# Capacitor / iOS CocoaPods + build artifacts (keep ios/ source; ignore Pods / DerivedData) +/ios/App/Pods/ +/ios/App/build/ +/ios/App/output/ +/ios/App/App/public/ +/ios/DerivedData/ +/ios/**/xcuserdata/ +*.mobileprovision +*.p12 +*.cer +ExportOptions.plist + # misc .DS_Store .env.local diff --git a/llm-fe/ANDROID.md b/llm-fe/ANDROID.md index 77bc97a..2dcfbd2 100644 --- a/llm-fe/ANDROID.md +++ b/llm-fe/ANDROID.md @@ -1,9 +1,10 @@ # Android (Capacitor) Ship the CRA web build as an Android app via [Capacitor](https://capacitorjs.com/). -One codebase: `llm-fe/` → web + Android WebView. +One codebase: `llm-fe/` → web + Android + iOS WebView. **Build & Play deploy steps:** [`android/README.md`](android/README.md). +**iOS sibling:** [`IOS.md`](IOS.md) / [`ios/README.md`](ios/README.md). Prerequisites for blockers already merged: JWT-only auth (#22), WebSocket lifecycle (#23), HashRouter (#24). @@ -63,6 +64,8 @@ Regenerate Android densities after changing source art: npm run assets:generate ``` +Same command also regenerates iOS `Assets.xcassets` (see [`IOS.md`](IOS.md)). + ## Signing (upload keystore) **Never commit keystores.** Store outside the repo (e.g. host secrets / password manager). diff --git a/llm-fe/IOS.md b/llm-fe/IOS.md new file mode 100644 index 0000000..3655f09 --- /dev/null +++ b/llm-fe/IOS.md @@ -0,0 +1,111 @@ +# iOS (Capacitor) + +Ship the CRA web build as an iOS app via [Capacitor](https://capacitorjs.com/) +WKWebView. One codebase: `llm-fe/` → web + Android + iOS. + +**Build & Xcode steps:** [`ios/README.md`](ios/README.md). + +Prerequisites already merged: JWT-only auth (#22), WebSocket lifecycle (#23), +HashRouter (#24), Android Capacitor scaffolding (#20). + +## Prerequisites + +- **macOS** with **Xcode** 15+ (CI runners are Linux — iOS builds are **manual** + on a Mac until a hosted macOS runner is added) +- **Node.js 20** (matches CI; Capacitor **7.x**) +- **CocoaPods** (`sudo gem install cocoapods` or Homebrew `pod`) +- Apple Developer Program membership for device / TestFlight / App Store signing + (simulator debug runs without a paid membership using automatic signing) + +## One-time / day-to-day + +```bash +cd llm-fe +npm ci +npm run build:mobile # .env.mobile → CRA build → cap sync (android + ios) +npm run ios:open # opens ios/App/App.xcworkspace in Xcode +``` + +Or sync only after an existing `build/`: + +```bash +npm run ios:sync +``` + +**Always open the `.xcworkspace`**, not the `.xcodeproj` (CocoaPods). + +## Environment + +Same as Android — see [`ANDROID.md`](ANDROID.md) § Environment. + +| File | Purpose | +|------|---------| +| `.env.mobile` | Endpoints baked into the **mobile** bundle (`build:mobile`) | +| `.env.mobile.local` | Optional gitignored override | + +Default `.env.mobile` points at production (`chatbackend.aimloperations.com`). + +WebView origin is `capacitor://localhost`. Backend CORS/CSRF already trusts it +(`chat_backend` `CAPACITOR_WEBVIEW_ORIGINS`). Auth is header JWT — no cookies. + +## Versioning + +In Xcode target **App** → General, or `ios/App/App.xcodeproj/project.pbxproj`: + +| Field | Xcode / build setting | Align with | +|-------|----------------------|------------| +| Version | `MARKETING_VERSION` (`CFBundleShortVersionString`) | `package.json` `version` + Android `versionName` (currently `0.1.0`) | +| Build | `CURRENT_PROJECT_VERSION` (`CFBundleVersion`) | Android `versionCode` (integer; **must increase** every TestFlight / App Store upload) | + +## Icons / splash + +Source art in `llm-fe/assets/` (same as Android): + +- `icon.png` / `splash.png` / `splash-dark.png` + +Regenerate iOS + Android densities: + +```bash +npm run assets:generate +``` + +## Signing + +**Never commit certificates, profiles, or `.p12` files.** + +1. Xcode → Signing & Capabilities → Team (Automatic for debug / internal). +2. Bundle ID: **`ai.hesychia.chat`** (matches Android `applicationId` / Capacitor `appId`). +3. For distribution: App Store Connect app, distribution cert, provisioning profile + — keep credentials outside the repo (password manager / CI secrets later). + +## Manual QA checklist + +On **simulator and physical iPhone**: + +- [ ] Login / logout / password reset +- [ ] Chat streaming + markdown / code blocks (selection + copy) +- [ ] Charts (`recharts`) +- [ ] Dark / light theme +- [ ] Keyboard does not cover compose input; notch / Dynamic Island / home indicator OK +- [ ] No rubber-band overscroll on chat shell +- [ ] Background → resume keeps or reconnects WebSocket (`appStateChange` + visibility) +- [ ] Cellular ↔ Wi‑Fi switch reconnects socket +- [ ] Airplane mode → restore reconnects + +## TestFlight / App Store (follow-on) + +Out of band (not automated here; separate deploy tickets): + +1. Apple Developer Program + App ID `ai.hesychia.chat` +2. Privacy policy URL + App Privacy questionnaire +3. In-app account deletion if sign-up is offered (guideline) +4. Enough native shell value that review does not reject as 4.2 “repackaged website” +5. Archive → Upload → TestFlight internal → App Store + +Push notifications, offline cache, Sign in with Apple — out of scope for this wrap. + +## Related issues + +- [#21](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/21) — this iOS wrap +- [#20](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/20) — Android scaffolding +- [#22](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/22) / [#23](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/23) / [#24](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/24) — auth / WS / routing diff --git a/llm-fe/android/app/src/main/res/drawable-land-hdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-land-hdpi/splash.png index 5472660..2589714 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-land-hdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-land-hdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-land-ldpi/splash.png b/llm-fe/android/app/src/main/res/drawable-land-ldpi/splash.png index a13fdb6..116c938 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-land-ldpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-land-ldpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-land-mdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-land-mdpi/splash.png index f281383..7561132 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-land-mdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-land-mdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-land-night-hdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-land-night-hdpi/splash.png index 5472660..2589714 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-land-night-hdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-land-night-hdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-land-night-ldpi/splash.png b/llm-fe/android/app/src/main/res/drawable-land-night-ldpi/splash.png index a13fdb6..116c938 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-land-night-ldpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-land-night-ldpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-land-night-mdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-land-night-mdpi/splash.png index f281383..7561132 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-land-night-mdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-land-night-mdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-land-night-xhdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-land-night-xhdpi/splash.png index 33257ba..7fd97df 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-land-night-xhdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-land-night-xhdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-land-night-xxhdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-land-night-xxhdpi/splash.png index 5b65b74..3242cfc 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-land-night-xxhdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-land-night-xxhdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-land-night-xxxhdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-land-night-xxxhdpi/splash.png index 9612e97..f927388 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-land-night-xxxhdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-land-night-xxxhdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-land-xhdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-land-xhdpi/splash.png index 33257ba..7fd97df 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-land-xhdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-land-xhdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-land-xxhdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-land-xxhdpi/splash.png index 5b65b74..3242cfc 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-land-xxhdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-land-xxhdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-land-xxxhdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-land-xxxhdpi/splash.png index 9612e97..f927388 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-land-xxxhdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-land-xxxhdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-night/splash.png b/llm-fe/android/app/src/main/res/drawable-night/splash.png index a13fdb6..116c938 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-night/splash.png and b/llm-fe/android/app/src/main/res/drawable-night/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-port-hdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-port-hdpi/splash.png index 0e092bf..4a77579 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-port-hdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-port-hdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-port-ldpi/splash.png b/llm-fe/android/app/src/main/res/drawable-port-ldpi/splash.png index c179fc0..d715795 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-port-ldpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-port-ldpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-port-mdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-port-mdpi/splash.png index d7b6c76..3dfbe14 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-port-mdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-port-mdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-port-night-hdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-port-night-hdpi/splash.png index 0e092bf..4a77579 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-port-night-hdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-port-night-hdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-port-night-ldpi/splash.png b/llm-fe/android/app/src/main/res/drawable-port-night-ldpi/splash.png index c179fc0..d715795 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-port-night-ldpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-port-night-ldpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-port-night-mdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-port-night-mdpi/splash.png index d7b6c76..3dfbe14 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-port-night-mdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-port-night-mdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-port-night-xhdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-port-night-xhdpi/splash.png index beb40f2..8750274 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-port-night-xhdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-port-night-xhdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-port-night-xxhdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-port-night-xxhdpi/splash.png index 8de66ea..5897535 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-port-night-xxhdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-port-night-xxhdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-port-night-xxxhdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-port-night-xxxhdpi/splash.png index c6a1487..d4f2303 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-port-night-xxxhdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-port-night-xxxhdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-port-xhdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-port-xhdpi/splash.png index beb40f2..8750274 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-port-xhdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-port-xhdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-port-xxhdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-port-xxhdpi/splash.png index 8de66ea..5897535 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-port-xxhdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-port-xxhdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable-port-xxxhdpi/splash.png b/llm-fe/android/app/src/main/res/drawable-port-xxxhdpi/splash.png index c6a1487..d4f2303 100644 Binary files a/llm-fe/android/app/src/main/res/drawable-port-xxxhdpi/splash.png and b/llm-fe/android/app/src/main/res/drawable-port-xxxhdpi/splash.png differ diff --git a/llm-fe/android/app/src/main/res/drawable/splash.png b/llm-fe/android/app/src/main/res/drawable/splash.png index d7b6c76..3dfbe14 100644 Binary files a/llm-fe/android/app/src/main/res/drawable/splash.png and b/llm-fe/android/app/src/main/res/drawable/splash.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/llm-fe/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index ae536e4..6e0dc10 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/llm-fe/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/llm-fe/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index 5eab23b..e2a25fd 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/llm-fe/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/llm-fe/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index ba05630..c6c7ef3 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/llm-fe/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher.png b/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher.png index 86e2b27..af28664 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher.png and b/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher_background.png b/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher_background.png index b999324..34ff3d4 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher_background.png and b/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher_background.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.png b/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.png index b9a3143..bbbf59b 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.png and b/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png b/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png index 4242e19..cac0df8 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png and b/llm-fe/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/llm-fe/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 2ba12ae..d767535 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/llm-fe/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/llm-fe/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index 089cd23..4ad664e 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/llm-fe/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/llm-fe/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index c2fd266..92246ee 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/llm-fe/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/llm-fe/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 6e17742..645d82f 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/llm-fe/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/llm-fe/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index 611b7a6..e1cc36a 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/llm-fe/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/llm-fe/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index fdf7c9c..3087769 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/llm-fe/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/llm-fe/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index e8100b4..daed96e 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/llm-fe/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/llm-fe/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index 0725172..c536e7b 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/llm-fe/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/llm-fe/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index fd24006..af9841f 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/llm-fe/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/llm-fe/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 6beeb4f..ef06319 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/llm-fe/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/llm-fe/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index ae285f2..224f8ae 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/llm-fe/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/llm-fe/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/llm-fe/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index e5a8849..d209379 100644 Binary files a/llm-fe/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/llm-fe/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/llm-fe/capacitor.config.ts b/llm-fe/capacitor.config.ts index 0ef0ed8..5222160 100644 --- a/llm-fe/capacitor.config.ts +++ b/llm-fe/capacitor.config.ts @@ -6,11 +6,20 @@ const config: CapacitorConfig = { webDir: 'build', server: { // Bundled assets; HashRouter (#24) handles deep links without a rewrite server. + // Android: https://localhost — iOS: capacitor://localhost (CORS trusted in chat_backend). androidScheme: 'https', + iosScheme: 'capacitor', }, android: { allowMixedContent: false, }, + ios: { + // Prefer content-driven insets; CSS env(safe-area-inset-*) handles notch / home indicator. + contentInset: 'automatic', + // Keep WebView scroll on; rubber-band bounce disabled in AppDelegate after load. + scrollEnabled: true, + preferredContentMode: 'mobile', + }, plugins: { SplashScreen: { launchAutoHide: true, diff --git a/llm-fe/ios/.gitignore b/llm-fe/ios/.gitignore new file mode 100644 index 0000000..514825e --- /dev/null +++ b/llm-fe/ios/.gitignore @@ -0,0 +1,20 @@ +App/build +App/Pods +App/output +App/App/public +DerivedData +xcuserdata + +# Cordova plugins for Capacitor +capacitor-cordova-ios-plugins + +# Generated Config files +App/App/capacitor.config.json +App/App/config.xml + +# Signing / export artifacts — never commit +*.mobileprovision +*.p12 +*.cer +ExportOptions.plist +*.ipa diff --git a/llm-fe/ios/App/App.xcodeproj/project.pbxproj b/llm-fe/ios/App/App.xcodeproj/project.pbxproj new file mode 100644 index 0000000..4ab4b50 --- /dev/null +++ b/llm-fe/ios/App/App.xcodeproj/project.pbxproj @@ -0,0 +1,408 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; }; + 50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; }; + 504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; }; + 504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; }; + 504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; }; + 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; }; + 50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; }; + A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = ""; }; + 50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = ""; }; + 504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; + AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = ""; }; + FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 504EC3011FED79650016851F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 27E2DDA53C4D2A4D1A88CE4A /* Frameworks */ = { + isa = PBXGroup; + children = ( + AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 504EC2FB1FED79650016851F = { + isa = PBXGroup; + children = ( + 504EC3061FED79650016851F /* App */, + 504EC3051FED79650016851F /* Products */, + 7F8756D8B27F46E3366F6CEA /* Pods */, + 27E2DDA53C4D2A4D1A88CE4A /* Frameworks */, + ); + sourceTree = ""; + }; + 504EC3051FED79650016851F /* Products */ = { + isa = PBXGroup; + children = ( + 504EC3041FED79650016851F /* App.app */, + ); + name = Products; + sourceTree = ""; + }; + 504EC3061FED79650016851F /* App */ = { + isa = PBXGroup; + children = ( + 50379B222058CBB4000EE86E /* capacitor.config.json */, + 504EC3071FED79650016851F /* AppDelegate.swift */, + 504EC30B1FED79650016851F /* Main.storyboard */, + 504EC30E1FED79650016851F /* Assets.xcassets */, + 504EC3101FED79650016851F /* LaunchScreen.storyboard */, + 504EC3131FED79650016851F /* Info.plist */, + 2FAD9762203C412B000D30F8 /* config.xml */, + 50B271D01FEDC1A000F3C39B /* public */, + ); + path = App; + sourceTree = ""; + }; + 7F8756D8B27F46E3366F6CEA /* Pods */ = { + isa = PBXGroup; + children = ( + FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */, + AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 504EC3031FED79650016851F /* App */ = { + isa = PBXNativeTarget; + buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */; + buildPhases = ( + 6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */, + 504EC3001FED79650016851F /* Sources */, + 504EC3011FED79650016851F /* Frameworks */, + 504EC3021FED79650016851F /* Resources */, + 9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = App; + productName = App; + productReference = 504EC3041FED79650016851F /* App.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 504EC2FC1FED79650016851F /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 920; + LastUpgradeCheck = 920; + TargetAttributes = { + 504EC3031FED79650016851F = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 504EC2FB1FED79650016851F; + packageReferences = ( + ); + productRefGroup = 504EC3051FED79650016851F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 504EC3031FED79650016851F /* App */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 504EC3021FED79650016851F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */, + 50B271D11FEDC1A000F3C39B /* public in Resources */, + 504EC30F1FED79650016851F /* Assets.xcassets in Resources */, + 50379B232058CBB4000EE86E /* capacitor.config.json in Resources */, + 504EC30D1FED79650016851F /* Main.storyboard in Resources */, + 2FAD9763203C412B000D30F8 /* config.xml in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 504EC3001FED79650016851F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 504EC3081FED79650016851F /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 504EC30B1FED79650016851F /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 504EC30C1FED79650016851F /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 504EC3101FED79650016851F /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 504EC3111FED79650016851F /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 504EC3141FED79650016851F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 504EC3151FED79650016851F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 504EC3171FED79650016851F /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + INFOPLIST_FILE = App/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 0.1.0; + OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; + PRODUCT_BUNDLE_IDENTIFIER = ai.hesychia.chat; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 504EC3181FED79650016851F /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + INFOPLIST_FILE = App/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 0.1.0; + PRODUCT_BUNDLE_IDENTIFIER = ai.hesychia.chat; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 504EC3141FED79650016851F /* Debug */, + 504EC3151FED79650016851F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 504EC3171FED79650016851F /* Debug */, + 504EC3181FED79650016851F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 504EC2FC1FED79650016851F /* Project object */; +} diff --git a/llm-fe/ios/App/App.xcworkspace/contents.xcworkspacedata b/llm-fe/ios/App/App.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..b301e82 --- /dev/null +++ b/llm-fe/ios/App/App.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/llm-fe/ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/llm-fe/ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/llm-fe/ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/llm-fe/ios/App/App/AppDelegate.swift b/llm-fe/ios/App/App/AppDelegate.swift new file mode 100644 index 0000000..1a350a3 --- /dev/null +++ b/llm-fe/ios/App/App/AppDelegate.swift @@ -0,0 +1,64 @@ +import UIKit +import Capacitor + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Disable WKWebView rubber-band overscroll so the MUI chat layout feels native. + NotificationCenter.default.addObserver( + self, + selector: #selector(disableWebViewBounce), + name: UIApplication.didBecomeActiveNotification, + object: nil + ) + return true + } + + @objc private func disableWebViewBounce() { + guard let root = window?.rootViewController else { return } + applyNoBounce(to: root) + } + + private func applyNoBounce(to controller: UIViewController) { + if let bridge = controller as? CAPBridgeViewController, + let scrollView = bridge.webView?.scrollView { + scrollView.bounces = false + scrollView.alwaysBounceVertical = false + scrollView.alwaysBounceHorizontal = false + } + for child in controller.children { + applyNoBounce(to: child) + } + if let presented = controller.presentedViewController { + applyNoBounce(to: presented) + } + } + + func applicationWillResignActive(_ application: UIApplication) { + } + + func applicationDidEnterBackground(_ application: UIApplication) { + } + + func applicationWillEnterForeground(_ application: UIApplication) { + } + + func applicationDidBecomeActive(_ application: UIApplication) { + disableWebViewBounce() + } + + func applicationWillTerminate(_ application: UIApplication) { + } + + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + return ApplicationDelegateProxy.shared.application(app, open: url, options: options) + } + + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) + } + +} diff --git a/llm-fe/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png b/llm-fe/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png new file mode 100644 index 0000000..a82fd55 Binary files /dev/null and b/llm-fe/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png differ diff --git a/llm-fe/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json b/llm-fe/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..b1a8483 --- /dev/null +++ b/llm-fe/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images": [ + { + "idiom": "universal", + "size": "1024x1024", + "filename": "AppIcon-512@2x.png", + "platform": "ios" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/llm-fe/ios/App/App/Assets.xcassets/Contents.json b/llm-fe/ios/App/App/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/llm-fe/ios/App/App/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json b/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json new file mode 100644 index 0000000..84a416d --- /dev/null +++ b/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json @@ -0,0 +1,56 @@ +{ + "images": [ + { + "idiom": "universal", + "filename": "Default@1x~universal~anyany.png", + "scale": "1x" + }, + { + "idiom": "universal", + "filename": "Default@2x~universal~anyany.png", + "scale": "2x" + }, + { + "idiom": "universal", + "filename": "Default@3x~universal~anyany.png", + "scale": "3x" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "universal", + "scale": "1x", + "filename": "Default@1x~universal~anyany-dark.png" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "universal", + "scale": "2x", + "filename": "Default@2x~universal~anyany-dark.png" + }, + { + "appearances": [ + { + "appearance": "luminosity", + "value": "dark" + } + ], + "idiom": "universal", + "scale": "3x", + "filename": "Default@3x~universal~anyany-dark.png" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} \ No newline at end of file diff --git a/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany-dark.png b/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany-dark.png new file mode 100644 index 0000000..e1cab9a Binary files /dev/null and b/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany-dark.png differ diff --git a/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany.png b/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany.png new file mode 100644 index 0000000..e1cab9a Binary files /dev/null and b/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany.png differ diff --git a/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany-dark.png b/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany-dark.png new file mode 100644 index 0000000..e1cab9a Binary files /dev/null and b/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany-dark.png differ diff --git a/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany.png b/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany.png new file mode 100644 index 0000000..e1cab9a Binary files /dev/null and b/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany.png differ diff --git a/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany-dark.png b/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany-dark.png new file mode 100644 index 0000000..e1cab9a Binary files /dev/null and b/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany-dark.png differ diff --git a/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany.png b/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany.png new file mode 100644 index 0000000..e1cab9a Binary files /dev/null and b/llm-fe/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany.png differ diff --git a/llm-fe/ios/App/App/Base.lproj/LaunchScreen.storyboard b/llm-fe/ios/App/App/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..e7ae5d7 --- /dev/null +++ b/llm-fe/ios/App/App/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/llm-fe/ios/App/App/Base.lproj/Main.storyboard b/llm-fe/ios/App/App/Base.lproj/Main.storyboard new file mode 100644 index 0000000..b44df7b --- /dev/null +++ b/llm-fe/ios/App/App/Base.lproj/Main.storyboard @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/llm-fe/ios/App/App/Info.plist b/llm-fe/ios/App/App/Info.plist new file mode 100644 index 0000000..cdd0c7e --- /dev/null +++ b/llm-fe/ios/App/App/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + Hesychia + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/llm-fe/ios/App/Podfile b/llm-fe/ios/App/Podfile new file mode 100644 index 0000000..311631e --- /dev/null +++ b/llm-fe/ios/App/Podfile @@ -0,0 +1,27 @@ +require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers' + +platform :ios, '14.0' +use_frameworks! + +# workaround to avoid Xcode caching of Pods that requires +# Product -> Clean Build Folder after new Cordova plugins installed +# Requires CocoaPods 1.6 or newer +install! 'cocoapods', :disable_input_output_paths => true + +def capacitor_pods + pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' + pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' + pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app' + pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard' + pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences' + pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar' +end + +target 'App' do + capacitor_pods + # Add your Pods here +end + +post_install do |installer| + assertDeploymentTarget(installer) +end diff --git a/llm-fe/ios/App/Podfile.lock b/llm-fe/ios/App/Podfile.lock new file mode 100644 index 0000000..89980d8 --- /dev/null +++ b/llm-fe/ios/App/Podfile.lock @@ -0,0 +1,46 @@ +PODS: + - Capacitor (7.6.8): + - CapacitorCordova + - CapacitorApp (7.1.2): + - Capacitor + - CapacitorCordova (7.6.8) + - CapacitorKeyboard (7.0.6): + - Capacitor + - CapacitorPreferences (7.0.4): + - Capacitor + - CapacitorStatusBar (7.0.6): + - Capacitor + +DEPENDENCIES: + - "Capacitor (from `../../node_modules/@capacitor/ios`)" + - "CapacitorApp (from `../../node_modules/@capacitor/app`)" + - "CapacitorCordova (from `../../node_modules/@capacitor/ios`)" + - "CapacitorKeyboard (from `../../node_modules/@capacitor/keyboard`)" + - "CapacitorPreferences (from `../../node_modules/@capacitor/preferences`)" + - "CapacitorStatusBar (from `../../node_modules/@capacitor/status-bar`)" + +EXTERNAL SOURCES: + Capacitor: + :path: "../../node_modules/@capacitor/ios" + CapacitorApp: + :path: "../../node_modules/@capacitor/app" + CapacitorCordova: + :path: "../../node_modules/@capacitor/ios" + CapacitorKeyboard: + :path: "../../node_modules/@capacitor/keyboard" + CapacitorPreferences: + :path: "../../node_modules/@capacitor/preferences" + CapacitorStatusBar: + :path: "../../node_modules/@capacitor/status-bar" + +SPEC CHECKSUMS: + Capacitor: ff6bf01336ac353098378828aff465095a89e459 + CapacitorApp: f01a913211780e0718dae9750442c3e23f96e106 + CapacitorCordova: e61ee8c40101b8cd011d0224261606a290c082cf + CapacitorKeyboard: a2e0869edd229490ce36aed2549c0d6b95e27ee8 + CapacitorPreferences: 69d9991307507aeab8ef8019c10b9babfda0e9ca + CapacitorStatusBar: 416e9e53fd6397e668d4a181cd2131617d949bd6 + +PODFILE CHECKSUM: c2e7da979a3cb960e70090cd1956727cb3ca3413 + +COCOAPODS: 1.16.2 diff --git a/llm-fe/ios/README.md b/llm-fe/ios/README.md new file mode 100644 index 0000000..10c3750 --- /dev/null +++ b/llm-fe/ios/README.md @@ -0,0 +1,89 @@ +# Hesychia — iOS + +Native Capacitor project for `ai.hesychia.chat`. Web UI comes from the CRA +`build/` output in the parent `llm-fe/` package (synced with `npx cap sync`). + +Broader Capacitor notes (env files, icons, QA, TestFlight): [`../IOS.md`](../IOS.md). + +## Prerequisites + +- macOS + **Xcode** 15+ +- Node.js **20** +- **CocoaPods** (`pod --version`) +- Open **`App.xcworkspace`** (not `.xcodeproj`) + +## Build the web bundle and sync + +From the **npm root** (`llm-fe/`, one level up): + +```bash +cd .. +npm ci +npm run build:mobile # .env.mobile → react-scripts build → cap sync +``` + +Open in Xcode: + +```bash +npm run ios:open +# or: npx cap open ios +``` + +Re-sync after changing web code (with an existing `build/`): + +```bash +npm run ios:sync +``` + +Backend URLs are baked in at web-build time via `../.env.mobile` (prod by default). + +After cloning, if Pods are missing: + +```bash +cd App +pod install +``` + +## Run on a simulator / device + +1. Open `App/App.xcworkspace` in Xcode. +2. Pick a simulator or a paired iPhone (Signing & Capabilities → your Team). +3. Product → Run (⌘R). + +CLI (optional): + +```bash +xcodebuild -workspace App/App.xcworkspace \ + -scheme App \ + -destination 'platform=iOS Simulator,name=iPhone 16' \ + build +``` + +## Version before a store release + +Edit target **App** → General, or `App.xcodeproj` build settings: + +| Field | Build setting | Rule | +|-------|---------------|------| +| Version | `MARKETING_VERSION` | Keep in sync with `../package.json` `version` + Android `versionName` | +| Build | `CURRENT_PROJECT_VERSION` | Integer; **must increase** for every TestFlight / App Store upload | + +Currently: version **0.1.0**, build **1**. + +## Signing + +**Do not commit** `.p12`, `.mobileprovision`, or export option files with secrets. + +Debug / simulator: Automatic signing with a personal team is enough. + +Distribution: App Store Connect + distribution certificate + profile — credentials +live outside this repo. CI/TestFlight automation is a follow-on ticket. + +## Useful paths + +| Path | Purpose | +|------|---------| +| `App/App/AppDelegate.swift` | Disables WKWebView rubber-band bounce | +| `App/App/Info.plist` | Display name **Hesychia**, version keys | +| `App/App/Assets.xcassets` | App icon + splash (regenerate via `npm run assets:generate`) | +| `App/Podfile` / `Podfile.lock` | CocoaPods (commit lockfile; Pods/ is gitignored) | diff --git a/llm-fe/package-lock.json b/llm-fe/package-lock.json index 068dcf3..73d8435 100644 --- a/llm-fe/package-lock.json +++ b/llm-fe/package-lock.json @@ -12,6 +12,7 @@ "@capacitor/app": "^7.1.2", "@capacitor/cli": "^7.6.8", "@capacitor/core": "^7.6.8", + "@capacitor/ios": "^7.6.8", "@capacitor/keyboard": "^7.0.6", "@capacitor/preferences": "^7.0.4", "@capacitor/status-bar": "^7.0.6", @@ -2465,6 +2466,15 @@ "tslib": "^2.1.0" } }, + "node_modules/@capacitor/ios": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@capacitor/ios/-/ios-7.6.8.tgz", + "integrity": "sha512-TAM1FdI1Cfl8e/wLBo7x5m61V/J1fMEpyxU2psZzHzsa436k2pwEME6CQoBRYdw6Q6gud0jZSPwOVrNn+MI7Ag==", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": "^7.6.0" + } + }, "node_modules/@capacitor/keyboard": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-7.0.6.tgz", diff --git a/llm-fe/package.json b/llm-fe/package.json index d6ece11..74247a9 100644 --- a/llm-fe/package.json +++ b/llm-fe/package.json @@ -7,6 +7,7 @@ "@capacitor/app": "^7.1.2", "@capacitor/cli": "^7.6.8", "@capacitor/core": "^7.6.8", + "@capacitor/ios": "^7.6.8", "@capacitor/keyboard": "^7.0.6", "@capacitor/preferences": "^7.0.4", "@capacitor/status-bar": "^7.0.6", @@ -55,10 +56,12 @@ "build": "NODE_ENV=production react-scripts build", "build:prod": "NODE_ENV=production react-scripts build && mkdir -p /var/www/prod.chat.aimloperations/html && cp -r ./build/* /var/www/prod.chat.aimloperations/html/", "build:beta": "bash -c 'set -a; source .env.beta; set +a; NODE_ENV=production react-scripts build' && mkdir -p /var/www/beta.chat.aimloperations/html && cp -r ./build/* /var/www/beta.chat.aimloperations/html/", - "build:mobile": "bash -c 'set -a; source .env.mobile; set +a; NODE_ENV=production react-scripts build' && npx cap sync android", + "build:mobile": "bash -c 'set -a; source .env.mobile; set +a; NODE_ENV=production react-scripts build' && npx cap sync", "android:open": "npx cap open android", "android:sync": "npx cap sync android", - "assets:generate": "npx capacitor-assets generate --android", + "ios:open": "npx cap open ios", + "ios:sync": "npx cap sync ios", + "assets:generate": "npx capacitor-assets generate --ios --android", "test": "NODE_ENV=development react-scripts test", "test:ci": "CI=true NODE_ENV=development react-scripts test --watchAll=false --coverage=false", "eject": "react-scripts eject" diff --git a/llm-fe/src/llm-fe/platform/nativeChrome.js b/llm-fe/src/llm-fe/platform/nativeChrome.js index 53a4b29..fac383c 100644 --- a/llm-fe/src/llm-fe/platform/nativeChrome.js +++ b/llm-fe/src/llm-fe/platform/nativeChrome.js @@ -1,6 +1,6 @@ /** - * Android hardware back button + keyboard / status-bar insets for Capacitor (#20). - * No-ops on web. + * Native chrome for Capacitor shells (#20 Android, #21 iOS). + * Hardware back (Android), status bar, keyboard resize. No-ops on web. */ import { isNativePlatform } from './nativePlatform'; diff --git a/llm-fe/src/llm-fe/platform/nativeSafeArea.css b/llm-fe/src/llm-fe/platform/nativeSafeArea.css index 2dbcf33..de3b3ce 100644 --- a/llm-fe/src/llm-fe/platform/nativeSafeArea.css +++ b/llm-fe/src/llm-fe/platform/nativeSafeArea.css @@ -1,7 +1,9 @@ -/* Capacitor / notch safe-area insets (#20). Harmless on desktop web. */ +/* Capacitor / notch safe-area insets (#20 / #21). Harmless on desktop web. */ html { padding: env(safe-area-inset-top, 0) env(safe-area-inset-right, 0) env(safe-area-inset-bottom, 0) env(safe-area-inset-left, 0); + /* Kill rubber-band overscroll at the document level (iOS WKWebView). */ + overscroll-behavior: none; } body { @@ -10,11 +12,15 @@ body { /* Full-bleed page shells must never create a sideways scroll on phones. */ max-width: 100%; overflow-x: hidden; + overscroll-behavior: none; + /* iOS Safari / WKWebView: avoid pull-to-refresh style bounce on body. */ + -webkit-overflow-scrolling: touch; } #root { min-height: 100%; max-width: 100%; + overscroll-behavior: none; } *,