Skip to content

feat(ios): relocate Capacitor iOS shell as apps/ios peer (LUM-1544)#31302

Merged
ashleeradka merged 5 commits into
mainfrom
claude/relocate-capacitor-ios-0yskP
May 20, 2026
Merged

feat(ios): relocate Capacitor iOS shell as apps/ios peer (LUM-1544)#31302
ashleeradka merged 5 commits into
mainfrom
claude/relocate-capacitor-ios-0yskP

Conversation

@ashleeradka

Copy link
Copy Markdown
Contributor

Summary

Part of Web App Repo Move: platform → vellum-assistant. Closes LUM-1544.

Relocates the Capacitor iOS shell from vellum-assistant-platform/web/ios/ to vellum-assistant/apps/ios/, sitting as a peer of apps/web/ instead of nested inside it. The platform copy is intentionally untouched — TestFlight releases continue to build from vellum-assistant-platform/web/ios/ until the release pipeline is migrated in a follow-up.

What moved

From (platform) To (this repo)
web/ios/ apps/ios/
web/capacitor.config.ts apps/web/capacitor.config.ts (with ios.path: "../ios")
web/capacitor-shell/ apps/web/capacitor-shell/

apps/web changes

  • New deps: @capacitor/cli (dev), @capacitor/ios, @capacitor/file-viewer, @capacitor/haptics, @capacitor/push-notifications, capacitor-plugin-safe-area — pinned exact, matching the existing iOS shell's plugin set so cap sync ios regenerates the same Package.swift.
  • ios:setup / ios:open scripts mirror the platform-side scripts but target the new relative paths (../ios/App/...).
  • bun.lock regenerated.

apps/ios changes

  • App/CapApp-SPM/Package.swift — node_modules paths rebased from ../../../node_modules/... to ../../../web/node_modules/... (the new tree puts node_modules in apps/web/, not at the iOS shell's grandparent). capacitor-swift-pm pin bumped to 8.3.4 to match @capacitor/core already on this repo's apps/web.
  • App/project.yml — prebuild guard's error message references apps/ios/App/project.yml and apps/web/ instead of web/ios/... / web/.
  • README.md — paths updated throughout; added a migration callout noting that the release pipeline lives in vellum-assistant-platform until cutover.

What's deliberately not in scope

  • Platform-side delete. vellum-assistant-platform/web/ios/, web/capacitor.config.ts, web/capacitor-shell/, and .github/workflows/release-ios.yaml stay put. They're the live TestFlight build source until we cut over.
  • Release workflow migration. The cross-repo repository_dispatch from vellum-assistantvellum-assistant-platform/release-ios.yaml is unchanged. Pointing it at the new directory is a follow-up — needs new Linear issue if one doesn't exist.

Test plan

  • cd apps/web && bun install (CI runs this with --frozen-lockfile)
  • cd apps/web && bun run lint — clean
  • cd apps/web && bun run typecheck — clean
  • cd apps/web && bun run build — passes (covered by pr-web.yaml)
  • Local macOS verification (not runnable in CI):
    • cd apps/web && bun run ios:setup → regenerates apps/ios/App/App/capacitor.config.json, apps/ios/App/App/public/, and apps/ios/App/App.xcodeproj/ without errors
    • bun run ios:open opens apps/ios/App/App.xcodeproj in Xcode
    • App Dev scheme builds and runs in iOS simulator, loads dev-assistant.vellum.ai
    • Chat streams token-by-token (confirms CapacitorHttp is still off and SSE works through the relocated shell)

Generated by Claude Code

Brings the Capacitor iOS wrapper into the new repo layout alongside
apps/web, ahead of the platform → vellum-assistant deployment cutover.
The platform copy at vellum-assistant-platform/web/ios remains the
build source for TestFlight releases until the release pipeline is
migrated in a follow-up.

- apps/ios/ mirrors web/ios/ from the platform repo
- apps/web/capacitor.config.ts configures ios.path: "../ios"
- apps/web/ gains ios:setup / ios:open scripts and the @capacitor/cli +
  @capacitor/ios + plugin deps needed for cap sync to regenerate
  CapApp-SPM/Package.swift in the new tree
- Package.swift and project.yml path references updated for the new
  apps/ios + apps/web layout (node_modules now resolves via
  ../../../web/node_modules from apps/ios/App/CapApp-SPM/)
@linear

linear Bot commented May 20, 2026

Copy link
Copy Markdown

LUM-1544

claude added 2 commits May 20, 2026 17:46
The Scaffolding-only line is no longer accurate now that apps/ios and
apps/web exist. The iOS release dispatch section now links to the
canonical README (apps/ios/) with a follow-up note that TestFlight
still builds from platform until LUM-1721 lands.
…itor-ios-0yskP

# Conflicts:
#	apps/web/bun.lock
@ashleeradka ashleeradka marked this pull request as ready for review May 20, 2026 17:52

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment thread apps/ios/README.md Outdated
Comment on lines +436 to +437
│ │ ├── AppDelegate.swift # Default Capacitor template, no edits
│ │ └── Info.plist

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 README structure diagram mischaracterizes AppDelegate.swift as unmodified and omits key custom Swift files

The README structure diagram at line 436 labels AppDelegate.swift as "Default Capacitor template, no edits", but the actual file (apps/ios/App/App/AppDelegate.swift) contains significant custom code: Universal Link navigation via navigateWebView(to:), APNs device-token forwarding to Capacitor's NotificationCenter, and deep-link handling in application(_:continue:restorationHandler:). Additionally, the structure diagram omits MyViewController.swift, NativeAuthPlugin.swift, and NativeBiometricPlugin.swift entirely — these are the most important custom files in the iOS shell. A developer relying on this documentation would incorrectly assume the AppDelegate is stock and would be unaware of the three other custom Swift files.

Suggested change
│ │ ├── AppDelegate.swift # Default Capacitor template, no edits
│ │ └── Info.plist
│ │ ├── AppDelegate.swift # Universal Links + APNs token forwarding
│ │ ├── MyViewController.swift # CAPBridgeViewController subclass (plugin registration, viewport-fit, zoom prevention)
│ │ ├── NativeAuthPlugin.swift # ASWebAuthenticationSession OIDC login flow
│ │ ├── NativeBiometricPlugin.swift # Face ID / Touch ID Keychain token storage
│ │ └── Info.plist
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +5 to +6
<key>aps-environment</key>
<string>development</string>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 Dev target entitlements use aps-environment:development — correct for local builds, potential issue for future TestFlight

App-Dev.entitlements sets aps-environment: development while App.entitlements (used by both production and staging targets) sets aps-environment: production. For the current state where TestFlight builds come from the platform repo, this is irrelevant. However, when the LUM-1721 cutover points the CI pipeline at these files, the dev target's aps-environment: development could cause push notifications to fail in TestFlight builds (TestFlight uses Apple's production APNs gateway). The CI pipeline may need to override entitlements or use separate Debug/Release entitlements files when the cutover happens.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch but not changing it in this PR — these entitlements files are 1:1 copies of platform's web/ios/App/App/*.entitlements, so the same aps-environment mismatch exists in current TestFlight dev builds today. It's not a regression introduced by relocating the source tree.

The actionable piece ("CI may need to override entitlements or use separate Debug/Release entitlements files") is exactly LUM-1721 cutover scope. Noted there: https://linear.app/vellum/issue/LUM-1721#comment-de32a533-e517-4d99-ad2e-c31019a08f4f. That issue can also confirm whether the backend APNs gateway selection currently matches what TestFlight dev builds register against — independent of this PR.

Also worth noting: TestFlight respects whichever environment the entitlement specifies; the actual constraint is that the backend must send pushes to the matching gateway for the registered device token (aps-environment docs).


Generated by Claude Code

AppDelegate.swift is not stock — it handles Universal Link navigation
and APNs device-token forwarding. The diagram also omitted
MyViewController, NativeAuthPlugin, and NativeBiometricPlugin, which
are the most important custom files in the shell.
vex-assistant-bot[bot]
vex-assistant-bot Bot previously approved these changes May 20, 2026

@vex-assistant-bot vex-assistant-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

APPROVE

Value: Establishes apps/ios/ as a first-class sibling to apps/web/ in the monorepo, completing the Capacitor shell relocation and making the iOS app buildable from a single bun run ios:open in the new repo — prerequisite for the LUM-1721 release pipeline cutover.

What this does: Moves the Capacitor iOS shell from vellum-assistant-platform/web/ios/ to apps/ios/, rebases all node_modules paths in Package.swift for the new tree structure, adds the required Capacitor deps to apps/web/package.json, and updates AGENTS.md to reflect the new canonical locations. Platform copy is intentionally left untouched until LUM-1721 cuts over the release pipeline.


CI: 39 pre-existing failures — none introduced by this PR.

  • edit-chat-session > *ReferenceError: window is not defined at line 14 (window.sessionStorage.clear()). Bun test runner has no window global. This file predates the PR; no test files were touched.
  • subscribeChatEvents idle watchdog > * (3 failures) — known 200ms timing sensitivity in the idle watchdog tests.
  • Lint ✅ · Type Check ✅ · Build ✅ · FlexFrame Lint ✅ · Socket Security ✅

apps/ios/App/CapApp-SPM/Package.swift — paths correct.
From apps/ios/App/CapApp-SPM/, three .. steps reach apps/; ../../../web/node_modules/ is exactly where bun install in apps/web/ deposits packages. Every plugin in package.json has a corresponding dependencies + targets entry. capacitor-swift-pm pinned to exact: "8.3.4" matches @capacitor/core. ✅

apps/web/capacitor.config.ts — clean.
ios.path: "../ios" points correctly to apps/ios/. contentInset: "never" comment fully explains the env(safe-area-inset-*) interaction (PRs #4821/#4832). SERVER_URL env routing is correct. appId placeholder comment is accurate about why the value diverges from the real bundle ID. ✅

NativeAuthPlugin.swift — security properties all intact.

  • Host validation (isAllowedBaseURL): exact match against VellumAssociatedDomain Info.plist — per-target, rejects unsubstituted $ vars. Dev build can't drive www.vellum.ai SSO (ATL-425). ✅
  • State CSRF: SecRandomCopyBytes with 32 bytes → 256 bits of entropy, base64url-encoded. Refuses rather than downgrading if RNG fails. ✅
  • error param checked before state — server-side auth errors surface correctly even when state is absent. ✅
  • Double-tap safety: cancel-then-nil before new session, completion deliberately doesn't clear authSession to avoid race with replacement session. The comment explaining why is correct. ✅
  • prefersEphemeralWebBrowserSession = true — prevents stale WorkOS cookie loops. ✅
  • Callback scheme: read from CFBundleURLTypes, rejects unsubstituted $ vars, fallback to "vellum-assistant". ✅

NativeBiometricPlugin.swift — solid.

  • Preflight with interactionNotAllowed = true prevents spurious Face ID when no token is stored — correct pattern. ✅
  • kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly + .userPresence: device-only (no iCloud Keychain sync), biometric + passcode fallback. The comment justifying .userPresence over .biometryCurrentSet (server-side token expiry makes enrollment-change lockout redundant) is accurate. ✅
  • storeToken deletes existing item before adding — avoids errSecDuplicateItem. ✅
  • deleteToken treats errSecItemNotFound as success — idempotent. ✅
  • opticID case in biometry switch for visionOS completeness + @unknown default. ✅

MyViewController.swift — correct.
Both WKUserScript injections use injectionTime: .atDocumentStart, which runs before WebKit parses the page's own <meta> — the only injection time that reliably locks in viewport-fit=cover before first layout. max(16px, 1em) for input zoom prevention is the right form (respects em scaling). Both plugins registered in capacitorDidLoad(). ✅

AppDelegate.swift — clean.
navigateWebView(to:) covers root CAPBridgeViewController, nav controller, and child hierarchy. APNs forwarding via NotificationCenter is the standard Capacitor pattern. ✅

apps/ios/App/project.yml — clean.
App.xcodeproj/ gitignored (no pbxproj churn). Three targets (App / App Staging / App Dev) each pick up their own AppIcon via xcconfig. optional: true on cap-managed resources handles the pre-cap sync state correctly. Pre-build staleness guard (project.yml -nt project.pbxproj) is a nice DX touch. ✅

AGENTS.md — accurate.
Root description now correctly lists both apps/web/ and apps/ios/. iOS release section updated with the LUM-1721 cutover framing. ✅


Non-blocking note: AGENTS.md hardcodes LUM-1721 as the release pipeline cutover ticket. The PR description flags that this Linear issue may not exist yet. If it doesn't, worth filing before this PR merges so the reference isn't a dead link — the cutover scope (wiring release-ios.yaml to target apps/ios/ instead of vellum-assistant-platform/web/ios/) is well-defined enough to ticket now.

Vellum Constitution — Trust-seeking: bringing iOS source-of-truth into the same repo as the web app makes the build surface auditable and reproducible from a single bun run ios:open, closing the gap between where the code lives and where it ships.

vellum-assistant is an open-source repo and external contributors don't
have access to Linear. Replace specific issue references with plain
descriptions of the work; the pre-existing "Notes for Vellum team
members" section in AGENTS.md (line 90) keeps its general Linear
workflow note, which is the intentional carve-out for internal devs.
@ashleeradka ashleeradka merged commit d053c75 into main May 20, 2026
6 of 7 checks passed
@ashleeradka ashleeradka deleted the claude/relocate-capacitor-ios-0yskP branch May 20, 2026 18:10
ashleeradka added a commit that referenced this pull request May 22, 2026
Adds a Debugging section to apps/ios/README.md covering Safari Web
Inspector (for the WKWebView contents — JS/CSS/network/console.log) and
the Xcode debugger (for native Swift code), plus common recipes for
plugin, streaming, and load-failure debugging.

Also fixes three stale `web/ios/...` path references left over from the
relocation to `apps/ios/` (#31302):
- apps/ios/.gitignore — drop reference to deleted build-ios-slot.yaml
- apps/web/src/runtime/native-auth.ts — update plugin file path
- apps/web/src/runtime/native-biometric.ts — update plugin file path

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants