Photos/Files bug: the lightbox download handed nativeSaveFile a bare
"/files/<id>" with no name, extension, or mime. Empty mime made
bzSaveToPhotos short-circuit as "notmedia" — so saveToAlbum (and its
permission prompt) never ran, and the image landed in the generic Files
folder as an extension-less blob. Now nativeSaveFile trusts the server's
Content-Type when the mime is unknown and appends a real extension
(bzExtForMime/bzEnsureExt), so images reach the Images folder AND Photos.
File preview: replace the @capacitor/share "share sheet" open with a new
native FileOpener plugin (QLPreviewController). bzOpenFile now prefers a
real Quick Look preview and only falls back to the share sheet if the
plugin isn't in the build. Wired file-opener into mobile/package.json and
the codemagic SPM diagnostics loop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause of the SPM runtime regressions (call/chat push when closed, photo save):
our local file: plugins didn't function because their package.json `files` array
omitted "Package.swift", so npm can drop it from node_modules on install → cap sync
can't wire the plugin into the CapApp-SPM package → the plugin isn't loaded at
runtime. Official plugins all list Package.swift in files; ours now do too.
Also add build-log diagnostics (node_modules symlink/copy + Package.swift presence,
and the generated CapApp-SPM/Package.swift) so the wiring is provable, not guessed.
Reapplies the SPM migration (Step B) that was reverted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move off CocoaPods (sunsetting; trunk goes read-only Dec 2026) to SPM, the Cap 8
default. This also ends the LiveKit git-pin hack — LiveKit becomes a real SPM dep.
- Each local plugin gets a Package.swift (swift-tools 5.9, iOS 15, capacitor-swift-pm
from 8.0.0, source path ios/Sources/<Plugin>). native-call additionally declares
LiveKit: .package(client-sdk-swift, exact 2.15.3) + product "LiveKit".
- native-call Swift: import LiveKitClient -> import LiveKit (SPM product name).
- codemagic.yaml: `cap add ios --packagemanager SPM`; removed the "Install CocoaPods"
(pod install) step; XCODE_WORKSPACE -> XCODE_PROJECT (App.xcodeproj); build-ipa
--workspace -> --project. Xcode resolves the Swift packages during archive.
- ios-patch.sh: removed the Podfile LiveKit git-pin injection + Podfile.lock deletion
(no Podfile under SPM). Info.plist/entitlements/AppDelegate/notif.wav patches stay.
- add-share-extension.rb is SPM-safe (operates on App.xcodeproj, no workspace/Pods refs).
Authored blind (no local Xcode) — expect build iterations on the first SPM build
(cap-add SPM layout, CapApp-SPM plugin wiring, LiveKit SPM resolution, signing).
Fully revertible: git revert -> back to Cap 8 + CocoaPods (working).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump to Capacitor 8 while staying on CocoaPods (decouples the major-version bump
from the SPM switch, per the verified plan). Changes:
- mobile/package.json: all @capacitor/* + @capacitor-community/safe-area + cli → ^8.0.0
(safe-area 8.0.1 verified Cap 8 compatible; @capacitor/assets stays 3.x, version-agnostic).
- 4 local plugins: @capacitor/core peer/dev dep → ^8.0.0; podspec ios deployment_target 14→15.
- codemagic.yaml: node 20→22 (Cap 8 requires Node 22+); `cap add ios --packagemanager Cocoapods`
(Cap 8 defaults to SPM — force CocoaPods); fail loudly if no Podfile is generated.
- add-share-extension.rb: fallback deployment target 14→15.
Requirements per the Cap 7→8 guide: Node 22+, Xcode 26+, iOS 15 min. capacitor.config has no
adjustMarginsForEdgeToEdge to remove. LiveKit git-pins + native calling unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Git pin now works (LiveKitClient 2.15.3 pre-downloaded), but the build box's
cached spec repo is stale: "Unable to find a specification for LiveKitUniFFI
(= 0.0.6)". Both LiveKitUniFFI 0.0.6 and LiveKitWebRTC 144.7559.11 are published
on the trunk, so --repo-update on the real pod install refreshes the repo and
resolves them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause of the intermittent dead-mic / no-audio / late-speaker: LiveKit's
automatic AVAudioSession config races CallKit's activation. The fix needs the
2.15+ audio API, which the build wasn't getting ('~> 2.0' resolved an older 2.x
from a stale spec cache). So:
- NativeCall.podspec: pin LiveKitClient '~> 2.15'.
- codemagic.yaml: 'pod install --repo-update' so the spec repo knows 2.15.x.
- Plugin: disable LiveKit auto audio-session config + keep the engine OFF; in
CXProvider didActivate set the session category and enable the engine; in
didDeactivate disable it. Request mic permission on connect so enabling the
engine in didActivate doesn't block on undetermined permission.
All AudioManager APIs verified against the raw 2.15.3 source (setEngineAvailability,
AudioEngineAvailability.default/.none, audioSession.isAutomaticConfigurationEnabled).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The archive keeps failing with only "Failed to archive" and exit 65 — no reason
shown. Cause: `xcode-project build-ipa` prints a prettified summary and swallows
xcodebuild's raw "error:" lines, which only survive in the /tmp/xcodebuild_logs
artifact. On failure we now grep that log for the signing/entitlement/compile
error and print it inline, so the next red build states WHY instead of just 65.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the reverse direction: share FROM Photos/Files/Safari INTO a Biz Connect
conversation. An app can only appear in the iOS share sheet as an app-extension
target, so this is real native work, not a web change.
Pieces:
- mobile/ios-share/ShareViewController.swift: a UI-less Share Extension. It stages
the shared items into the App Group container and opens bizconnect://share. It
deliberately does NOT reimplement the chat picker — that lives in the app, which
already has the chat list, search and upload progress. Appends to the manifest
(never overwrites), so sharing twice before opening the app loses nothing.
- mobile/scripts/add-share-extension.rb: injects the extension target into the
Capacitor-generated Xcode project on every CI build (Codemagic checks out fresh),
using the xcodeproj gem that ships with CocoaPods. Embeds it, sets the bundle id
<app>.share, and MERGES the App Group into the app's entitlements rather than
clobbering them (push's aps-environment must survive). Idempotent.
- mobile/plugins/share-inbox: getPending()/clear() to read that manifest — the App
Group container isn't one of Filesystem's known directories, so it needs a bridge.
- home.html: on bizconnect://share (and every resume, and cold-launch), read the
inbox and show a "Send to…" picker over the chat list; chosen files run the SAME
upload + /api/messages send as an in-app attachment. Reuses convertFileSrc to read
the staged bytes with no base64 marshalling.
- ios-patch.sh registers the bizconnect URL scheme; codemagic.yaml fetches a profile
for the .share bundle id too.
One-time manual gate (CI cannot toggle App capabilities): the App Group
group.com.bizgaze.connect must be created and enabled on both App IDs in the Apple
portal — documented in mobile/IOS_SETUP.md. Without it the two processes can't see
each other's files and sharing silently no-ops; everything else still works.
Validated cross-file: pod-name/jsName/method wiring for all three plugins, App
Group id identical in all 4 files, URL scheme consistent across extension/plist/web,
entitlement-merge preserves push. Needs a new iOS build (new targets + plugins).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Device screenshots proved the WebView reports env(safe-area-inset-*)=0, so no web/CSS/JS trick can
move content off the Dynamic Island — it must be fixed natively. Upgrade the Capacitor shell to v7 and
add @capacitor-community/safe-area (populates env() with real per-device insets, edge-to-edge) and
@capacitor/keyboard (resize:native so the composer sits directly on the keyboard, like Teams).
- mobile: Capacitor 6 -> 7 for all plugins; add safe-area + keyboard; SafeArea/Keyboard config.
- codemagic: npm ci -> npm install (dependency set changed); drop stale Cap6 lockfile.
- web: remove the temporary StatusBar/34px hacks (env() now works); keep var(--sat)/var(--sab) padding.
- share/connect: back link now respects env(safe-area-inset-top/left).
- add native-style slide in/out (push/pop) animation when opening/closing a chat on mobile.
Build marker -> 2026-07-17-batch92.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The IPA now builds, signs, uploads and processes on App Store Connect. Set
submit_to_testflight=false so the build stays green via internal testing; external
beta review (which needs Test Information + a demo login) is opt-in later. Also set
ITSAppUsesNonExemptEncryption=false in Info.plist so ASC stops prompting for export
compliance on every upload.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fetch-signing-files --create alone mints a throwaway distribution certificate whose
private key dies with the build VM; the next build then finds a cert it has no key
for and fails 'Cannot save Signing Certificates without certificate private key'.
Supplying our own fixed private key (secure var in the ios_signing group) makes the
cert reproducible and reusable across builds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The archive failed with 'App requires a provisioning profile' because use-profiles
ran inside the signing step before pod install created the workspace, so the
profile never bound to the App target. Move it to just before build-ipa, matching
Codemagic's Capacitor recipe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The environment.ios_signing block makes Codemagic fetch an EXISTING profile at
build init and fails with 'No matching profiles found' on a brand-new app. The
'Set up code signing' script already creates the cert+profile via
fetch-signing-files --create, so the block was both redundant and blocking.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The iOS app is a Capacitor shell over the live web UI — the web/server side is already fully
Capacitor-ready (nativePlatform() detects Capacitor; setupNativePush registers APNs tokens via
/api/v1/devices; the APNs sender is built into server/push.js, config-gated). So this adds only
the build/sign/upload path:
- codemagic.yaml: macOS-cloud workflow that generates the iOS project, patches Info.plist,
generates icons/splash, signs via an App Store Connect API key (automatic signing), archives,
and uploads to TestFlight. No Mac required.
- mobile/scripts/ios-patch.sh: adds the App-Review privacy usage strings (camera/mic/photos) +
display name to the generated Info.plist.
- mobile/IOS_SETUP.md: click-by-click runbook — ASC app record, API key, Codemagic integration,
first build, APNs key → server .env, and the public-submission checklist.
Bundle id com.bizgaze.connect. No secrets committed — Apple keys live in Codemagic + server .env.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>