From 0fcae2b92e67dfe260bfc35b58d982463fbef62d Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 25 Jun 2026 22:11:31 -0700 Subject: [PATCH 1/3] iOS Computers: add "Add Computer" row at end of list Mirror the top-left toolbar add button as an end-of-list row so users who scroll past their Macs can add another without scrolling back up. Both entrypoints share one addComputer() action path. Reuses the existing localized mobile.computers.add string (en + ja). Co-Authored-By: Claude Opus 4.8 --- .../CmuxMobileShellUI/DeviceTreeView.swift | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeviceTreeView.swift b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeviceTreeView.swift index fb6a0ed5b8ff..c6729cb64c30 100644 --- a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeviceTreeView.swift +++ b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeviceTreeView.swift @@ -80,6 +80,9 @@ struct DeviceTreeView: View { confirmRemove: { _ in confirmComputerRemoval() } ) } + if showAddDevice != nil { + addComputerRow + } } footer: { Text(L10n.string( "mobile.computers.footer", @@ -97,10 +100,7 @@ struct DeviceTreeView: View { .toolbar { if showAddDevice != nil { ToolbarItem(placement: .topBarLeading) { - Button { - showAddDevice?() - dismiss() - } label: { + Button(action: addComputer) { Image(systemName: "plus") } .accessibilityLabel(L10n.string("mobile.computers.add", defaultValue: "Add Computer")) @@ -134,6 +134,26 @@ struct DeviceTreeView: View { .accessibilityIdentifier("MobileDeviceTree") } + /// End-of-list affordance mirroring the top-left toolbar button, so users who + /// scroll past their Macs can add another without scrolling back up. Same + /// action path (`addComputer`) as the toolbar button. + private var addComputerRow: some View { + Button(action: addComputer) { + Label( + L10n.string("mobile.computers.add", defaultValue: "Add Computer"), + systemImage: "plus" + ) + } + .accessibilityIdentifier("MobileComputersAddRow") + } + + /// Present the add-device (pairing) flow, then dismiss this screen. Shared by + /// the top-left toolbar button and the end-of-list row. + private func addComputer() { + showAddDevice?() + dismiss() + } + @ViewBuilder private var emptySection: some View { Section { From d48ae7069e84ab54159708738787bc4e617697ea Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 25 Jun 2026 23:15:10 -0700 Subject: [PATCH 2/3] iOS: add "Add Computer" item to the Choose Mac picker The workspace title "Choose Mac" menu now has an "Add Computer" action below the Mac list (after a divider), invoking the same showAddDevice pairing flow. Shown only when the add affordance is available. Reuses the existing localized mobile.computers.add string. Co-Authored-By: Claude Opus 4.8 --- .../WorkspaceListView+MacSelection.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+MacSelection.swift b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+MacSelection.swift index 5c53420f821d..73dc5b80e81b 100644 --- a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+MacSelection.swift +++ b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+MacSelection.swift @@ -119,6 +119,18 @@ extension WorkspaceListView { } } .labelsVisibility(.visible) + if let showAddDevice { + Divider() + Button { + showAddDevice() + } label: { + Label( + L10n.string("mobile.computers.add", defaultValue: "Add Computer"), + systemImage: "plus" + ) + } + .accessibilityIdentifier("MobileWorkspaceMacPickerAdd") + } } label: { WorkspaceMacTitlePickerLabel(title: macTitlePickerTitle) } From 53144ea1bb10b000ead91262520748a28ee0c242 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 25 Jun 2026 23:56:51 -0700 Subject: [PATCH 3/3] iOS: rename user-facing "Add device" to "Add Computer" Pairing sheet title/nav title, form accessibility label, the disconnected + connection-status add buttons, and the LAN setup-help body now read "Add Computer" for consistency with the new entrypoints. Updated both Swift defaultValues and the xcstrings catalog (en + ja). Localization keys and code symbols are unchanged (not user-facing). Co-Authored-By: Claude Opus 4.8 --- .../DisconnectedWorkspaceShellView.swift | 4 ++-- .../MobileMacConnectionStatusRow.swift | 2 +- .../Sources/CmuxMobileShellUI/PairingView.swift | 6 +++--- .../Sources/CmuxMobileShellUI/SetupHelpView.swift | 2 +- ios/cmux/Resources/Localizable.xcstrings | 12 ++++++------ 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DisconnectedWorkspaceShellView.swift b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DisconnectedWorkspaceShellView.swift index 9ae11441a717..373f780239cc 100644 --- a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DisconnectedWorkspaceShellView.swift +++ b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DisconnectedWorkspaceShellView.swift @@ -91,7 +91,7 @@ struct DisconnectedWorkspaceShellView: View { Button(action: showAddDevice) { Text( savedMacs.isEmpty - ? L10n.string("mobile.addDevice.title", defaultValue: "Add device") + ? L10n.string("mobile.addDevice.title", defaultValue: "Add Computer") : L10n.string("mobile.addDevice.another", defaultValue: "Add another Mac") ) } @@ -199,7 +199,7 @@ struct DisconnectedWorkspaceShellView: View { Button(action: showAddDevice) { Image(systemName: "plus") } - .accessibilityLabel(L10n.string("mobile.addDevice.title", defaultValue: "Add device")) + .accessibilityLabel(L10n.string("mobile.addDevice.title", defaultValue: "Add Computer")) .accessibilityIdentifier("MobileShowAddDeviceToolbarButton") } } diff --git a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileMacConnectionStatusRow.swift b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileMacConnectionStatusRow.swift index 1b63388c3970..8ff26bc3530f 100644 --- a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileMacConnectionStatusRow.swift +++ b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileMacConnectionStatusRow.swift @@ -67,7 +67,7 @@ struct MobileMacConnectionStatusRow: View { if let addDevice { Button(action: addDevice) { - Text(L10n.string("mobile.addDevice.title", defaultValue: "Add device")) + Text(L10n.string("mobile.addDevice.title", defaultValue: "Add Computer")) } .buttonStyle(.bordered) .controlSize(.small) diff --git a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PairingView.swift b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PairingView.swift index e82bc79cc097..49cdad774262 100644 --- a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PairingView.swift +++ b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PairingView.swift @@ -78,7 +78,7 @@ struct PairingView: View { .addDeviceInputBehavior(.number) .accessibilityIdentifier("MobileAddDevicePortField") } header: { - Text(L10n.string("mobile.addDevice.title", defaultValue: "Add device")) + Text(L10n.string("mobile.addDevice.title", defaultValue: "Add Computer")) } footer: { Text(L10n.string("mobile.addDevice.help", defaultValue: "Enter a Tailscale, LAN, or local host and port. QR/link pairing from that computer is still the safest setup path.")) } @@ -88,7 +88,7 @@ struct PairingView: View { Color.clear .frame(width: 1, height: 1) .accessibilityElement(children: .ignore) - .accessibilityLabel(L10n.string("mobile.addDevice.formAccessibilityLabel", defaultValue: "Add device form")) + .accessibilityLabel(L10n.string("mobile.addDevice.formAccessibilityLabel", defaultValue: "Add Computer form")) .accessibilityIdentifier("MobileAddDeviceForm") } #endif @@ -221,7 +221,7 @@ struct PairingView: View { .ignoresSafeArea(edges: .bottom) } } - .navigationTitle(L10n.string("mobile.addDevice.title", defaultValue: "Add device")) + .navigationTitle(L10n.string("mobile.addDevice.title", defaultValue: "Add Computer")) .mobileInlineNavigationTitle() .toolbar { #if os(iOS) diff --git a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SetupHelpView.swift b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SetupHelpView.swift index 66c0973c8b1a..6f79c425f923 100644 --- a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SetupHelpView.swift +++ b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SetupHelpView.swift @@ -152,7 +152,7 @@ struct SetupHelpView: View { Text(L10n.string( "mobile.setupHelp.lanBody", - defaultValue: "No Tailscale? On the same Wi-Fi you can still connect by typing the Mac's local address and port by hand in Add device. That link is unencrypted, so only use it on a network you trust." + defaultValue: "No Tailscale? On the same Wi-Fi you can still connect by typing the Mac's local address and port by hand in Add Computer. That link is unencrypted, so only use it on a network you trust." )) .font(.footnote) .foregroundStyle(.secondary) diff --git a/ios/cmux/Resources/Localizable.xcstrings b/ios/cmux/Resources/Localizable.xcstrings index 1c3860dc4edf..88d720946f69 100644 --- a/ios/cmux/Resources/Localizable.xcstrings +++ b/ios/cmux/Resources/Localizable.xcstrings @@ -415,13 +415,13 @@ "en": { "stringUnit": { "state": "translated", - "value": "Add device form" + "value": "Add Computer form" } }, "ja": { "stringUnit": { "state": "translated", - "value": "デバイス追加フォーム" + "value": "コンピュータ追加フォーム" } } } @@ -670,13 +670,13 @@ "en": { "stringUnit": { "state": "translated", - "value": "Add device" + "value": "Add Computer" } }, "ja": { "stringUnit": { "state": "translated", - "value": "デバイスを追加" + "value": "コンピュータを追加" } } } @@ -4138,13 +4138,13 @@ "en": { "stringUnit": { "state": "translated", - "value": "No Tailscale? On the same Wi-Fi you can still connect by typing the Mac's local address and port by hand in Add device. That link is unencrypted, so only use it on a network you trust." + "value": "No Tailscale? On the same Wi-Fi you can still connect by typing the Mac's local address and port by hand in Add Computer. That link is unencrypted, so only use it on a network you trust." } }, "ja": { "stringUnit": { "state": "translated", - "value": "Tailscaleがない場合でも、同じWi-Fiなら、「デバイスを追加」でMacのローカルアドレスとポートを手入力して接続できます。その接続は暗号化されないため、信頼できるネットワークでのみ使用してください。" + "value": "Tailscaleがない場合でも、同じWi-Fiなら、「コンピュータを追加」でMacのローカルアドレスとポートを手入力して接続できます。その接続は暗号化されないため、信頼できるネットワークでのみ使用してください。" } } }