Add an iPhone client sharing the macOS app's SwiftUI code

This commit is contained in:
Ian Gulliver
2026-10-21 22:00:54 -07:00
parent 57d660c57f
commit 9e81e9107d
46 changed files with 1625 additions and 287 deletions
+17
View File
@@ -0,0 +1,17 @@
# byre for iPhone
The iPhone app is the same `Byre` target as the Mac app, built for iOS: platform differences are `#if os(macOS)` / `#if os(iOS)` blocks, with the small shared aliases (`PlatformImage`, `PlatformColor`, `PlatformFont`, `PlatformView`, `touchOnly`, `deviceKind`) in `mac/Sources/Byre/Platform.swift`. Only the layout is the phone's own (`Phone.swift`): a tab bar with Lead, Tasks and More. Tasks lists every project's active tasks with the Mac's own task rows; a task opens pushed, with the Mac's task bar and tab strip on top and the tab's view below in place of the Mac's attached sheet. Zoomed images and diagrams cover the screen. More holds Notes, Workflows, Status and Settings. Every action that focuses a task or project (a row, a link, a notification, the switcher) bumps `Store.reveals`, which brings the Tasks tab forward with the task open; the server's own focus changes don't.
Left out on the phone: the Server tab and running byre locally, the client connection (`Remote`: the phone is never a client for agents' client tools, so `list_clients` doesn't show it), the Sparkle updater, and keyboard shortcuts. Notifications come from the app's own event stream, so they only show while the app is open; push notifications would need APNs on the server.
## Building
`mac/phone.sh` builds the app with `xcodebuild` (a Swift package can't be built for iOS by `swift build`) in the same shared directory and under the same lock as `mac/bundle.sh`, assembles `Byre.app` itself (icon through `actool`, fonts, `Info.plist` from `mac/phone/Info.plist` plus the version, commit and `DT*` keys App Store Connect checks), signs it ad hoc with `mac/phone/byre.entitlements`, and wraps it in `mac/.build/phone/iphoneos/Byre.xcarchive`. Release only, like the Mac app. `xcodebuild -exportArchive -archivePath <archive> -exportOptionsPlist mac/phone/export.plist -exportPath <dir> -allowProvisioningUpdates` re-signs it for the App Store and uploads it to TestFlight, using the Apple account Xcode is signed into: Xcode makes the distribution certificate and profile itself, so none are kept in the repo. Uploading needs the App ID `run.fc.byre.ios` (iCloud with the container `iCloud.run.fc.byre.mac`, and Push Notifications) and its app record in App Store Connect; neither can be made through Apple's API. The version is the last release tag's (App Store Connect takes only numbers) and the build number the UTC build time, `YYYYMMDD.HHMMSS`: App Store Connect refuses a build number that isn't higher than every earlier upload, and a commit count can go down when a branch with many commits uploads before its squash merge lands on main.
`mac/phone.sh --simulator` builds an ad-hoc-signed app for the iOS simulator instead, at `mac/.build/phone/iphonesimulator/Byre.app`; install it with `xcrun simctl install <device> <app>` and start it with `xcrun simctl launch <device> run.fc.byre.ios`. The simulator ignores entitlements given to `codesign` and refuses to launch an ad-hoc app signed with restricted ones, so the script links `mac/phone/byre.entitlements` into the binary's `__TEXT,__entitlements` section, as Xcode does; without the iCloud entitlement CloudKit raises at launch. Its node signs in to Tailscale like any other: `xcrun simctl launch --console-pty` prints libtailscale's sign-in URL, which changes on every launch.
## Platform facts
- CloudKit on iOS requires the `remote-notification` background mode in `Info.plist`, even though the app sets up no push of its own.
- `ProcessInfo.hostName` is the Mac's name in the simulator and meaningless on a phone, so the phone's tailnet node and `Byre-Host` are named `iPhone`.
- SwiftTerm's iOS `TerminalViewDelegate` requires `requestOpenLink`, which its macOS one provides a default for.
+1 -1
View File
@@ -18,7 +18,7 @@ The project is a Swift package built with `swift build`, not an Xcode project. I
Only the release configuration is built. A debug build stops at an `#error` in `ByreApp.swift`, and even a failed one leaves the next release build in the same build directory recompiling every target, since the two configurations share it. Release targets build whole-module, so a one-file change to `Byre` recompiles the whole target.
`mac/bundle.sh` builds TailscaleKit from `github.com/tailscale/libtailscale` at the commit pinned in the script (`libtailscale=`), once per commit, and bundles it in `Contents/Frameworks` like Sparkle's. To move to a newer libtailscale, change the pinned commit.
`mac/tailscalekit.sh` builds TailscaleKit from `github.com/tailscale/libtailscale` at the commit pinned in the script (`commit=`), once per commit, as one xcframework for macOS, iOS and the iOS simulator; `mac/bundle.sh` and `mac/phone.sh` both run it, and the Mac app bundles it in `Contents/Frameworks` like Sparkle's. To move to a newer libtailscale, change the pinned commit.
## Signing