5.0 KiB
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 Status (shown first), Notes, Workflows 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.
The phone holds the client connection (Remote) like a Mac, so agents can show it pages and files and drive its Page view (show_page, show_file, drive_page); it answers every other client request with an error. It sends Byre-Device: iPhone (Macs send Mac), and the server signs commits and runs byre client-exec without --client only on Macs.
Left out on the phone: the Server tab and running byre locally, the shell, file, hands and signing client requests, 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 is the Mac app's (docs/operations.md, "Releases"). App Store Connect refuses a build number that isn't higher than every earlier upload, so a second upload of the same commit fails; a commit count wouldn't do, since it 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.
The simulator's window (Xcode's Device Hub) lists the app's own accessibility elements, so hands' ax_tree and act press drive the app without a mouse. Hands' mouse steps (click, drag, scroll) refuse that window, because Device Hub reports no process id; a gesture such as dragging the keyboard away needs mouse events posted with CGEvent instead. xcrun simctl io booted screenshot --display LCD <file> captures the app at full resolution.
Platform facts
- CloudKit on iOS requires the
remote-notificationbackground mode inInfo.plist, even though the app sets up no push of its own. - The phone's tailnet node is always
byre-app-iphone; the phone's own name stays inside byre.Byre-Host, which the server shows for the phone, takes its name fromUIDevice.current.name, which iOS reads only on the main actor (so the app delegate reads it first) and returns as justiPhoneunless the app has thecom.apple.developer.device-information.user-assigned-device-nameentitlement. Apple grants that entitlement on request (developer.apple.com/contact/request/user-assigned-device-name); it then has to be enabled on the App ID and added tomac/phone/byre.entitlements. The simulator returns the simulator device's own name without it. - SwiftTerm's iOS
TerminalViewDelegaterequiresrequestOpenLink, which its macOS one provides a default for.