feat: add Capacitor iOS native shell #83

Merged
forgejo_admin merged 2 commits from 78-capacitor-init-ios into main 2026-03-26 04:20:21 +00:00

Summary

Adds Capacitor to westside-app with iOS platform support. The SvelteKit SPA (adapter-static) is now wrapped in a native WKWebView shell for future App Store distribution. Keycloak redirect URIs are platform-aware so OAuth works on both web and native.

Changes

  • package.json / package-lock.json -- added @capacitor/core, @capacitor/cli, @capacitor/ios, @capacitor/app
  • capacitor.config.ts -- new config with appId com.westsidekingsandqueens.app, appName Westside Kings & Queens, webDir build
  • ios/ -- generated Xcode project (committed per Capacitor convention)
  • src/lib/keycloak.js -- added getBaseUrl() helper using Capacitor.isNativePlatform() to return capacitor://localhost on native or window.location.origin on web; all redirect URIs now use this helper

Test Plan

  • npm run build produces static output in build/
  • npx cap sync ios completes without errors
  • No changes to svelte.config.js, src/app.css, or route files
  • Open ios/App/App.xcodeproj in Xcode and verify it builds (requires macOS)
  • Keycloak client westside-spa already has capacitor://localhost/* in redirect URIs

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
## Summary Adds Capacitor to westside-app with iOS platform support. The SvelteKit SPA (adapter-static) is now wrapped in a native WKWebView shell for future App Store distribution. Keycloak redirect URIs are platform-aware so OAuth works on both web and native. ## Changes - `package.json` / `package-lock.json` -- added @capacitor/core, @capacitor/cli, @capacitor/ios, @capacitor/app - `capacitor.config.ts` -- new config with appId `com.westsidekingsandqueens.app`, appName `Westside Kings & Queens`, webDir `build` - `ios/` -- generated Xcode project (committed per Capacitor convention) - `src/lib/keycloak.js` -- added `getBaseUrl()` helper using `Capacitor.isNativePlatform()` to return `capacitor://localhost` on native or `window.location.origin` on web; all redirect URIs now use this helper ## Test Plan - [x] `npm run build` produces static output in `build/` - [x] `npx cap sync ios` completes without errors - [x] No changes to svelte.config.js, src/app.css, or route files - [ ] Open `ios/App/App.xcodeproj` in Xcode and verify it builds (requires macOS) - [ ] Keycloak client `westside-spa` already has `capacitor://localhost/*` in redirect URIs ## Review Checklist - [x] Passed automated review-fix loop - [x] No secrets committed - [x] No unnecessary file changes - [x] Commit messages are descriptive ## Related - Closes #78
feat: add Capacitor iOS native shell
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
c4a2af3a47
Install @capacitor/core, @capacitor/cli, @capacitor/ios, and @capacitor/app.
Add capacitor.config.ts with appId com.westsidekingsandqueens.app and webDir
build. Generate iOS Xcode project via cap add ios. Update keycloak.js with
getBaseUrl() helper that returns capacitor://localhost on native platforms
for correct OAuth redirect URIs.

Closes #78

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: move @capacitor/cli and @capacitor/ios to devDependencies
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
16c4d667c0
CLI and platform adapter are build-time tools, not runtime dependencies.
Matches the convention in mcd-tracker-app where only @capacitor/core and
plugin packages are in dependencies.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

Review findings

Issue found and fixed: @capacitor/cli and @capacitor/ios were placed in dependencies instead of devDependencies. These are build-time tools (CLI for cap add/sync/build, iOS platform adapter for native builds) and should not ship as runtime dependencies. Fixed in commit 16c4d66.

No other issues found:

  • capacitor.config.ts uses correct appId, appName, webDir, and dev port (5174 matching the vite dev script)
  • getBaseUrl() in keycloak.js correctly uses Capacitor.isNativePlatform() and all three window.location.origin references are updated
  • ios/ directory is standard Capacitor boilerplate, committed per convention
  • ios/.gitignore properly excludes build artifacts, DerivedData, and generated config files
  • Build and cap sync both pass after the fix
## Review findings **Issue found and fixed:** `@capacitor/cli` and `@capacitor/ios` were placed in `dependencies` instead of `devDependencies`. These are build-time tools (CLI for cap add/sync/build, iOS platform adapter for native builds) and should not ship as runtime dependencies. Fixed in commit 16c4d66. **No other issues found:** - `capacitor.config.ts` uses correct appId, appName, webDir, and dev port (5174 matching the vite dev script) - `getBaseUrl()` in keycloak.js correctly uses `Capacitor.isNativePlatform()` and all three `window.location.origin` references are updated - `ios/` directory is standard Capacitor boilerplate, committed per convention - `ios/.gitignore` properly excludes build artifacts, DerivedData, and generated config files - Build and cap sync both pass after the fix
Author
Owner

PR #83 Review

DOMAIN REVIEW

Tech stack identified: SvelteKit (adapter-static) + Capacitor 8.3 (iOS) + Keycloak-js PKCE auth + Swift/Xcode project scaffolding.

This PR adds Capacitor to wrap the existing SvelteKit SPA in a native WKWebView shell for iOS App Store distribution. The meaningful code change is in src/lib/keycloak.js -- a new getBaseUrl() helper that returns capacitor://localhost on native platforms and window.location.origin on web. All redirect URIs (login, logout, silentCheckSsoRedirectUri) now route through this helper. The rest of the diff is Capacitor-generated Xcode project scaffolding and dependency additions.

Dependency placement: Correct. @capacitor/core and @capacitor/app are in dependencies (runtime). @capacitor/cli and @capacitor/ios are in devDependencies (build-time only).

Capacitor config: capacitor.config.ts correctly sets appId: 'com.westsidekingsandqueens.app', webDir: 'build' (matches adapter-static output), and leaves server empty for production (local file serving). The commented-out dev server URL is fine for documentation purposes.

iOS project: Standard Capacitor scaffold. IPHONEOS_DEPLOYMENT_TARGET = 15.0 is reasonable. PRODUCT_BUNDLE_IDENTIFIER matches the Capacitor config. No DEVELOPMENT_TEAM set (correct for scaffold -- developer sets this locally). No signing certs or provisioning profiles committed.

Keycloak integration: The checkLoginIframe: false setting on line 58 of keycloak.js is critical and correct -- iframe-based login detection does not work in WKWebView. The platform-aware getBaseUrl() pattern cleanly handles the OAuth redirect URI difference between web (window.location.origin) and native (capacitor://localhost).

Xcode project pbxproj: Clean. Debug and Release configurations are standard. Swift 5.0 target. No hardcoded secrets, no stale team IDs.

BLOCKERS

None.

On the test coverage question: this PR adds a pure platform-detection helper (getBaseUrl()) and Xcode scaffolding. The project has zero test infrastructure (no vitest, no Playwright, no test files). The getBaseUrl() function is a 4-line platform conditional that is inherently validated by the acceptance criteria (build + Xcode open + manual test on device). Requiring test infrastructure as a blocker for this scaffolding PR would be scope creep. The test plan (build verification, cap sync, Xcode build) is appropriate for the nature of the change.

NITS

  1. silentCheckSsoRedirectUri on native: Line 56-57 of keycloak.js computes capacitor://localhost/silent-check-sso.html on native. With checkLoginIframe: false, keycloak-js still uses silentCheckSsoRedirectUri for the initial check-sso flow via a hidden iframe. Iframes with capacitor:// URLs may silently fail in WKWebView. Currently this is harmless (if silent SSO fails, the user just has to log in manually), but worth noting for the TestFlight validation pass in issue #80.

  2. Generated files tracked despite gitignore: ios/App/App/capacitor.config.json and ios/App/App/config.xml are committed but also listed in ios/.gitignore (lines 12-13). This happens because cap add ios generates files before gitignore takes effect. Future cap sync runs will regenerate these files, which git will then ignore (since they are already tracked, git will show modifications). Consider running git rm --cached ios/App/App/capacitor.config.json ios/App/App/config.xml so gitignore can take over, or remove those entries from .gitignore to intentionally track them. Either approach is fine -- the current state is just ambiguous.

  3. config.xml wildcard access: ios/App/App/config.xml contains <access origin="*" />, which allows the WebView to load any URL. This is the Capacitor default and acceptable for a Keycloak-authenticated app that needs to reach external services, but should be locked down to specific origins (keycloak.tail5b443a.ts.net, basketball-api.tail5b443a.ts.net, minio-api.tail5b443a.ts.net) before App Store submission as a hardening measure.

  4. No cap:sync or cap:build npm scripts: Consider adding convenience scripts to package.json (e.g., "cap:sync": "npm run build && npx cap sync ios") to standardize the native build workflow. Minor quality-of-life item.

SOP COMPLIANCE

  • Branch named after issue (78-capacitor-init-ios references issue #78)
  • PR body has Summary, Changes, Test Plan sections
  • Related section references Closes #78
  • Related section references plan slug -- N/A, kanban-driven (no plan)
  • No secrets committed (no API keys, no signing certs, no .env files)
  • No unnecessary file changes (all files are either Capacitor scaffold, dependency updates, or the keycloak.js platform helper)
  • Commit messages are descriptive (2 commits: init + dependency fix from review loop)

PROCESS OBSERVATIONS

  • Deployment frequency: This PR introduces a new distribution channel (iOS native) without changing the existing web deployment path. The CI pipeline (npm ci + check + build) is unaffected. Positive for DF -- parallel distribution channels without coupling.
  • Change failure risk: Low. The only runtime code change is getBaseUrl() in keycloak.js. Web behavior is unchanged (Capacitor.isNativePlatform() returns false on web). Native behavior is new scope validated by TestFlight (issue #80).
  • Follow-up work: Issue #79 (iOS build pipeline) and #80 (TestFlight validation) correctly sequence the remaining work. The nits above (silent SSO on native, config.xml hardening) should be validated during #80.

VERDICT: APPROVED

## PR #83 Review ### DOMAIN REVIEW **Tech stack identified:** SvelteKit (adapter-static) + Capacitor 8.3 (iOS) + Keycloak-js PKCE auth + Swift/Xcode project scaffolding. This PR adds Capacitor to wrap the existing SvelteKit SPA in a native WKWebView shell for iOS App Store distribution. The meaningful code change is in `src/lib/keycloak.js` -- a new `getBaseUrl()` helper that returns `capacitor://localhost` on native platforms and `window.location.origin` on web. All redirect URIs (`login`, `logout`, `silentCheckSsoRedirectUri`) now route through this helper. The rest of the diff is Capacitor-generated Xcode project scaffolding and dependency additions. **Dependency placement:** Correct. `@capacitor/core` and `@capacitor/app` are in `dependencies` (runtime). `@capacitor/cli` and `@capacitor/ios` are in `devDependencies` (build-time only). **Capacitor config:** `capacitor.config.ts` correctly sets `appId: 'com.westsidekingsandqueens.app'`, `webDir: 'build'` (matches adapter-static output), and leaves `server` empty for production (local file serving). The commented-out dev server URL is fine for documentation purposes. **iOS project:** Standard Capacitor scaffold. `IPHONEOS_DEPLOYMENT_TARGET = 15.0` is reasonable. `PRODUCT_BUNDLE_IDENTIFIER` matches the Capacitor config. No `DEVELOPMENT_TEAM` set (correct for scaffold -- developer sets this locally). No signing certs or provisioning profiles committed. **Keycloak integration:** The `checkLoginIframe: false` setting on line 58 of `keycloak.js` is critical and correct -- iframe-based login detection does not work in WKWebView. The platform-aware `getBaseUrl()` pattern cleanly handles the OAuth redirect URI difference between web (`window.location.origin`) and native (`capacitor://localhost`). **Xcode project pbxproj:** Clean. Debug and Release configurations are standard. Swift 5.0 target. No hardcoded secrets, no stale team IDs. ### BLOCKERS None. On the test coverage question: this PR adds a pure platform-detection helper (`getBaseUrl()`) and Xcode scaffolding. The project has zero test infrastructure (no vitest, no Playwright, no test files). The `getBaseUrl()` function is a 4-line platform conditional that is inherently validated by the acceptance criteria (build + Xcode open + manual test on device). Requiring test infrastructure as a blocker for this scaffolding PR would be scope creep. The test plan (build verification, cap sync, Xcode build) is appropriate for the nature of the change. ### NITS 1. **`silentCheckSsoRedirectUri` on native:** Line 56-57 of `keycloak.js` computes `capacitor://localhost/silent-check-sso.html` on native. With `checkLoginIframe: false`, keycloak-js still uses `silentCheckSsoRedirectUri` for the initial `check-sso` flow via a hidden iframe. Iframes with `capacitor://` URLs may silently fail in WKWebView. Currently this is harmless (if silent SSO fails, the user just has to log in manually), but worth noting for the TestFlight validation pass in issue #80. 2. **Generated files tracked despite gitignore:** `ios/App/App/capacitor.config.json` and `ios/App/App/config.xml` are committed but also listed in `ios/.gitignore` (lines 12-13). This happens because `cap add ios` generates files before gitignore takes effect. Future `cap sync` runs will regenerate these files, which git will then ignore (since they are already tracked, git will show modifications). Consider running `git rm --cached ios/App/App/capacitor.config.json ios/App/App/config.xml` so gitignore can take over, or remove those entries from `.gitignore` to intentionally track them. Either approach is fine -- the current state is just ambiguous. 3. **`config.xml` wildcard access:** `ios/App/App/config.xml` contains `<access origin="*" />`, which allows the WebView to load any URL. This is the Capacitor default and acceptable for a Keycloak-authenticated app that needs to reach external services, but should be locked down to specific origins (`keycloak.tail5b443a.ts.net`, `basketball-api.tail5b443a.ts.net`, `minio-api.tail5b443a.ts.net`) before App Store submission as a hardening measure. 4. **No `cap:sync` or `cap:build` npm scripts:** Consider adding convenience scripts to `package.json` (e.g., `"cap:sync": "npm run build && npx cap sync ios"`) to standardize the native build workflow. Minor quality-of-life item. ### SOP COMPLIANCE - [x] Branch named after issue (`78-capacitor-init-ios` references issue #78) - [x] PR body has Summary, Changes, Test Plan sections - [x] Related section references `Closes #78` - [ ] Related section references plan slug -- N/A, kanban-driven (no plan) - [x] No secrets committed (no API keys, no signing certs, no .env files) - [x] No unnecessary file changes (all files are either Capacitor scaffold, dependency updates, or the keycloak.js platform helper) - [x] Commit messages are descriptive (2 commits: init + dependency fix from review loop) ### PROCESS OBSERVATIONS - **Deployment frequency:** This PR introduces a new distribution channel (iOS native) without changing the existing web deployment path. The CI pipeline (`npm ci` + `check` + `build`) is unaffected. Positive for DF -- parallel distribution channels without coupling. - **Change failure risk:** Low. The only runtime code change is `getBaseUrl()` in `keycloak.js`. Web behavior is unchanged (`Capacitor.isNativePlatform()` returns `false` on web). Native behavior is new scope validated by TestFlight (issue #80). - **Follow-up work:** Issue #79 (iOS build pipeline) and #80 (TestFlight validation) correctly sequence the remaining work. The nits above (silent SSO on native, config.xml hardening) should be validated during #80. ### VERDICT: APPROVED
forgejo_admin deleted branch 78-capacitor-init-ios 2026-03-26 04:20:21 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/westside-app!83
No description provided.