diff --git a/TODO.md b/TODO.md index 2c9584d..5ecbf77 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,4 @@ # Bugs -- Enable the iPhone device-name entitlement - Once Apple grants User Assigned Device Name to the App ID `run.fc.byre.ios` (Certificates, Identifiers & Profiles, Capability Requests), turn it on there and add `com.apple.developer.device-information.user-assigned-device-name` to `mac/phone/byre.entitlements`. Until then `UIDevice.current.name` returns `iPhone` on a real phone. Check on a TestFlight build that the Status tab lists the phone by its own name. # M1 — One agent end to end diff --git a/docs/iphone.md b/docs/iphone.md index c941e53..0596a00 100644 --- a/docs/iphone.md +++ b/docs/iphone.md @@ -17,5 +17,5 @@ The simulator's window (Xcode's Device Hub) lists the app's own accessibility el ## 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. -- 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 from `UIDevice.current.name`, which iOS reads only on the main actor (so the app delegate reads it first) and returns as just `iPhone` unless the app has the `com.apple.developer.device-information.user-assigned-device-name` entitlement. 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 to `mac/phone/byre.entitlements`. The simulator returns the simulator device's own name without it. +- The phone's name is the one typed in Settings, kept in the phone's own `UserDefaults` (not synced through iCloud, since each phone has its own), and `iPhone` until one is typed. It goes in `Byre-Host` and the tailnet node's name, both fixed when the app starts. `UIDevice.current.name` returns just `iPhone` without Apple's `com.apple.developer.device-information.user-assigned-device-name` entitlement, which Apple refused because byre shows the name to other people and sends it to Tailscale and to the agents' model vendors. - SwiftTerm's iOS `TerminalViewDelegate` requires `requestOpenLink`, which its macOS one provides a default for. diff --git a/mac/Sources/Byre/API.swift b/mac/Sources/Byre/API.swift index ab25b09..8d89896 100644 --- a/mac/Sources/Byre/API.swift +++ b/mac/Sources/Byre/API.swift @@ -107,7 +107,8 @@ private let clientID = newID("cli") #if os(macOS) let deviceName = Host.current().localizedName ?? ProcessInfo.processInfo.hostName #else -let deviceName = MainActor.assumeIsolated { UIDevice.current.name } +let deviceNameKey = "deviceName" +let deviceName = UserDefaults.standard.string(forKey: deviceNameKey) ?? deviceKind #endif final class API: Sendable { diff --git a/mac/Sources/Byre/Phone.swift b/mac/Sources/Byre/Phone.swift index e4be11a..0e74d86 100644 --- a/mac/Sources/Byre/Phone.swift +++ b/mac/Sources/Byre/Phone.swift @@ -3,8 +3,6 @@ import SwiftUI final class PhoneDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { - // deviceName reads UIDevice, which is main-actor only, so it must first be read here. - _ = deviceName Notifier.shared.install() UITextView.appearance().textContainerInset = .zero return true diff --git a/mac/Sources/Byre/Settings.swift b/mac/Sources/Byre/Settings.swift index de4099b..834df85 100644 --- a/mac/Sources/Byre/Settings.swift +++ b/mac/Sources/Byre/Settings.swift @@ -22,6 +22,13 @@ struct SettingsView: View { var body: some View { ScrollView { VStack(alignment: .leading, spacing: 0) { + #if os(iOS) + heading("This iPhone's name") + Text("byre shows this iPhone by this name, and names its tailnet node after it. A new name takes effect the next time byre opens.") + .foregroundStyle(Palette.muted) + .fixedSize(horizontal: false, vertical: true) + DeviceNameField() + #endif heading("Window color") Text("Windows for this server have a border in this color, on every device you use.") .foregroundStyle(Palette.muted) @@ -79,6 +86,40 @@ struct SettingsView: View { } } +#if os(iOS) +private struct DeviceNameField: View { + @ViewState private var name = UserDefaults.standard.string(forKey: deviceNameKey) ?? deviceKind + @ViewState private var saved = UserDefaults.standard.string(forKey: deviceNameKey) ?? deviceKind + + var body: some View { + let trimmed = name.trimmingCharacters(in: .whitespaces) + let valid = !slug(trimmed).isEmpty && trimmed != saved + HStack(spacing: 6) { + TextField("Name", text: $name) + .modifier(FieldStyle()) + .onSubmit { + save(trimmed, valid) + } + Button("Save") { + save(trimmed, valid) + } + .buttonStyle(WebButtonStyle()) + .disabled(!valid) + } + .padding(.top, 8) + } + + private func save(_ trimmed: String, _ valid: Bool) { + if !valid { + return + } + UserDefaults.standard.set(trimmed, forKey: deviceNameKey) + saved = trimmed + name = trimmed + } +} +#endif + private struct ServerColorPicker: View { @Environment(Store.self) private var store diff --git a/mac/Sources/Byre/Tailnet.swift b/mac/Sources/Byre/Tailnet.swift index 8633672..7a238f5 100644 --- a/mac/Sources/Byre/Tailnet.swift +++ b/mac/Sources/Byre/Tailnet.swift @@ -26,11 +26,7 @@ func slug(_ text: String) -> String { final class TailnetNode: Sendable { let sd: tailscale - #if os(macOS) let hostname = "byre-app-\(slug(deviceName))" - #else - let hostname = "byre-app-\(slug(deviceKind))" - #endif private let socks = TailnetProxy() init() {