Skip to content

feat(ios): iOS app (App Store) - #715

Merged
kipavy merged 39 commits into
devfrom
ci/ios
Oct 28, 2026
Merged

kipavy merged 39 commits into
devfrom
ci/ios

Conversation

@kipavy

@kipavy kipavy commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Draft: iOS port (App Store build). Opened to run the full CI on the branch; not ready to merge.

  • ios.yml: signed App Store package (bundle id app.voltius), simulator run with screenshots, TestFlight upload on demand.
  • Mobile shell on iOS (isMobileShell() covers iOS), Face ID, data-protection keychain, SFTP downloads to the Files app, native share sheet and haptics (ios_native.rs), serial enumeration and MCP off on iOS.
  • asc.yml: App Store Connect API (status, listing).

kipavy added 30 commits October 24, 2026 19:10
First probe of the iOS build: bundle id app.voltius, manual signing with the
Apple Distribution certificate and App Store profile, purchases hidden
(VITE_DISTRIBUTION=appstore).
- system_auth: the LocalAuthentication backend also runs on iOS.
- Register the protected store of apple-native-keyring-store on iOS, so
  secrets survive restarts.
- Info.ios.plist: NSFaceIDUsageDescription (iOS kills the app without it).
Also turn the MCP server off on iOS, as on Android.
On iOS, download_dir_get points at the app Documents folder and download_publish moves the finished download there (with a (2), (3)... suffix on name clashes). UIFileSharingEnabled and LSSupportsOpeningDocumentsInPlace make the folder visible in Files.
Appium (XCUITest) taps the screen and reads the WebView DOM of a debug
build; a throwaway asyncssh server on the runner plays the remote host.
Each step leaves a screenshot and a DOM dump in the voltius-ios-e2e
artifact, with a summary of passed and failed steps.
The phone UI was gated on "android" only, so iOS loaded the desktop shell.

- platform.ts: isMobilePlatform (android or ios), useIsMobile, useIsIOS;
  isMobileShell and App.tsx now pick MobileShell on both phones.
- Layout and UX checks move from useIsAndroid to useIsMobile (settings rows,
  sheets, pickers, notes, toasts, import/export, hosts toolbar, no local
  shells). Android-only native bridges stay on useIsAndroid: soft keyboard
  overlay, selection touch feed, Material You palette, system bars,
  volume-down Ctrl, SAF folder picker, file sharing.
- Desktop-only features also skip iOS: ~/.ssh/config sync, MCP server push.
  Passkey sign-in returns through the voltius:// link on both phones.
- iOS fallbacks: a tap on the terminal focuses xterm so the system keyboard
  opens; SFTP downloads take the Android temp -> publish path (the native side
  publishes into the app Documents folder, so the settings show the folder
  read-only, without the picker); text sharing uses the Web Share API; text
  exports are written into Documents.
- Face ID / Touch ID / passcode wording for the system unlock prompt.
- Test mocks of @/utils/platform learn the new helpers; tests for the
  platform helpers and the iOS share path.
ios_native.rs calls UIKit through the Objective-C runtime: UIActivityViewController for staged files (the Web Share API needs a fresh tap, which the download before sharing outlives) and UIFeedbackGenerator for haptics. Both commands are no-ops elsewhere.
The previous/next/done bar above the iOS keyboard pushed the terminal extra-keys row up; WKContentView inputAccessoryView now returns nil (as Capacitor does). The app targets iPhone only (TARGETED_DEVICE_FAMILY = 1).
workflow_dispatch store=true builds with VITE_DEMO_SEED=1 (window.__voltiusDemoSeed, left out of normal bundles), runs on a 6.9-inch iPhone with a clean status bar, opens the shell and SFTP in a demo folder and saves the hosts, terminal, SFTP, snippets and more screens.
iOS zooms in when an input with text under 16px gets focus and keeps the page zoomed and shifted afterwards. maximum-scale=1 stops it (the app has its own UI scale setting). The store run goes back to the tab bar and retries the shell when the first handshake stalls.
`tauri ios init --ci` regenerates the Xcode project on every build with
Tauri's default AppIcon set, so the App Store build carried the Tauri logo.
scripts/ios/use-app-icons.sh copies src-tauri/icons/ios into the generated
asset catalog and fails the build if any icon the catalog lists is not ours;
all three iOS jobs call it.

The iOS icons are now opaque RGB with the white corners filled with the
icon's navy: App Store Connect rejects an app icon with an alpha channel,
and iOS applies its own corner mask, so white corners would show.
tag-release now calls ios.yml with the release tag. The package goes to
TestFlight, then scripts/asc/release.mjs waits for Apple to process the
build, creates the App Store version (or reuses the editable one), sets
"What's New" from CHANGELOG.md in every language (skipped on the app's
first version, where Apple refuses it), attaches the build and submits it
for review. The version is released automatically once approved.

- The build number is now <run number>.<attempt>, so re-running a failed
  release uploads a new build instead of colliding with the first one.
- Release runs are never cancelled by a newer push, and skip the simulator
  job.
- The submission stops with a clear error when the build has no export
  compliance answer: add ITSAppUsesNonExemptEncryption to
  src-tauri/Info.ios.plist once the encryption question is answered.

The notes come from scripts/store/release-notes.mjs, added on dev by the
Google Play change; this branch needs dev merged in before a release runs.
@kipavy
kipavy marked this pull request as ready for review October 28, 2026 22:30
@kipavy
kipavy merged commit e3b04cf into dev Oct 28, 2026
9 checks passed
@kipavy
kipavy deleted the ci/ios branch October 28, 2026 22:31
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.

1 participant