fix(apple/iOS): touch-first control sizing — toolbar circles + large sheet buttons
ci / rust (push) Has been cancelled

The iOS chrome inherited macOS dialog sizing and read as undersized on a phone:

- Toolbar: the two trailing actions shared one compact glass pill; on iOS 26+ each now
  gets its own full-size circle (explicit .topBarTrailing placements split by a fixed
  ToolbarSpacer — the system-app look, e.g. Files), with the grouped-pill fallback on
  iOS 17–18. The buttons are extracted so macOS keeps SettingsLink + .help untouched.
- Sheets and CTAs (AddHostSheet, PairSheet, trust card, empty-state Add Host) get
  .controlSize(.large) on iOS — proper touch targets instead of macOS dialog buttons.

Verified in the iPhone 17 simulator: two ~44 pt glass circles matching the Files app's
toolbar sizing; macOS suite and app build unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 11:47:30 +02:00
parent e1af4d57c6
commit 7c24832ad0
4 changed files with 69 additions and 24 deletions
@@ -34,6 +34,9 @@ struct AddHostSheet: View {
.keyboardShortcut(.defaultAction)
.disabled(address.trimmingCharacters(in: .whitespaces).isEmpty)
}
#if os(iOS)
.controlSize(.large)
#endif
.padding(16)
}
#if os(macOS)
@@ -104,28 +104,29 @@ struct ContentView: View {
}
.navigationTitle("punktfunk")
.toolbar {
#if os(iOS)
// Each action gets its own full-size glass circle (system-app style)
// instead of sharing one compact pill.
if #available(iOS 26.0, *) {
ToolbarItem(placement: .topBarTrailing) { settingsButton }
ToolbarSpacer(.fixed, placement: .topBarTrailing)
ToolbarItem(placement: .topBarTrailing) { addHostButton }
} else {
ToolbarItem { settingsButton }
ToolbarItem(placement: .primaryAction) { addHostButton }
}
#else
ToolbarItem(placement: .primaryAction) {
Button {
showAddHost = true
} label: {
Label("Add Host", systemImage: "plus")
}
.help("Add a host")
addHostButton
.help("Add a host")
}
ToolbarItem {
#if os(macOS)
SettingsLink {
Label("Settings", systemImage: "gearshape")
}
.help("Stream mode and settings")
#else
Button {
showSettings = true
} label: {
Label("Settings", systemImage: "gearshape")
}
#endif
}
#endif
}
}
#if os(macOS)
@@ -158,6 +159,24 @@ struct ContentView: View {
}
}
private var addHostButton: some View {
Button {
showAddHost = true
} label: {
Label("Add Host", systemImage: "plus")
}
}
#if os(iOS)
private var settingsButton: some View {
Button {
showSettings = true
} label: {
Label("Settings", systemImage: "gearshape")
}
}
#endif
private var emptyState: some View {
ContentUnavailableView {
Label("No Hosts", systemImage: "rectangle.connected.to.line.below")
@@ -166,6 +185,9 @@ struct ContentView: View {
} actions: {
Button("Add Host") { showAddHost = true }
.buttonStyle(.borderedProminent)
#if os(iOS)
.controlSize(.large)
#endif
}
}
@@ -260,6 +282,9 @@ struct ContentView: View {
.buttonStyle(.borderedProminent)
.keyboardShortcut(.defaultAction)
}
#if os(iOS)
.controlSize(.large)
#endif
// The verified alternative to eyeballing hex: drop this session (the host
// serves one connection at a time) and run the SPAKE2 PIN ceremony instead.
Button("Pair with PIN instead…") {
@@ -79,6 +79,9 @@ struct PairSheet: View {
.keyboardShortcut(.defaultAction)
.disabled(busy || pin.trimmingCharacters(in: .whitespaces).isEmpty)
}
#if os(iOS)
.controlSize(.large)
#endif
.padding(16)
}
#if os(macOS)