Kill the NavigationStack on the list — that was the floating-chrome bug

iOS 26's NavigationStack reserves space for its floating Liquid Glass nav bar
even with `.toolbar(.hidden, for: .navigationBar)` — that's why "Screens"
landed ~180pt down from the dynamic island with a wedge of black above it.

Removed the NavigationStack from the list entirely. Layout is now a plain
top-anchored VStack: top chrome (gear + Screens + plus + search) flush with
the safe-area top, then a ScrollView with LazyVStack of cards filling the
rest of the screen. Sessions present via fullScreenCover instead of
NavigationLink, so we don't need NavigationStack here at all.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-04-16 21:33:50 -05:00
parent fcdd19ceb9
commit 6b50184bcc

View File

@@ -6,67 +6,45 @@ public struct ConnectionListView: View {
@Environment(\.modelContext) private var modelContext
@Environment(\.openWindow) private var openWindow
@Query(sort: \SavedConnection.displayName) private var connections: [SavedConnection]
@State private var discovery = DiscoveryService()
@State private var showingAdd = false
@State private var showingSettings = false
@State private var addPrefill: AddConnectionPrefill?
@State private var editingConnection: SavedConnection?
@State private var path: [SessionRoute] = []
@State private var sessionConnection: SavedConnection?
@State private var resolvingHostID: String?
@State private var search = ""
public init() {}
public var body: some View {
NavigationStack(path: $path) {
ZStack(alignment: .top) {
backgroundGradient.ignoresSafeArea()
ScrollView {
VStack(spacing: 22) {
topChrome
if !discovery.hosts.isEmpty || discovery.isBrowsing {
discoveredSection
}
savedSection
}
.padding(.horizontal, 16)
.padding(.top, 8)
.padding(.bottom, 32)
}
#if os(iOS)
.scrollIndicators(.hidden)
#endif
}
#if os(iOS)
.toolbar(.hidden, for: .navigationBar)
#endif
.sheet(isPresented: $showingAdd) {
AddConnectionView(prefill: addPrefill)
}
.sheet(item: $editingConnection) { connection in
AddConnectionView(editing: connection)
}
.sheet(isPresented: $showingSettings) {
SettingsView()
}
.navigationDestination(for: SessionRoute.self) { route in
if let connection = connection(with: route.connectionID) {
SessionView(connection: connection)
} else {
ContentUnavailableView("Connection unavailable",
systemImage: "exclamationmark.triangle")
}
}
.task {
discovery.start()
}
.onDisappear {
discovery.stop()
}
VStack(spacing: 0) {
topChrome
contentList
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.background(backgroundGradient.ignoresSafeArea())
.sheet(isPresented: $showingAdd) {
AddConnectionView(prefill: addPrefill)
}
.sheet(item: $editingConnection) { connection in
AddConnectionView(editing: connection)
}
.sheet(isPresented: $showingSettings) {
SettingsView()
}
#if os(iOS)
.fullScreenCover(item: $sessionConnection) { connection in
SessionView(connection: connection)
}
#else
.sheet(item: $sessionConnection) { connection in
SessionView(connection: connection)
}
#endif
.task { discovery.start() }
.onDisappear { discovery.stop() }
}
// MARK: Background
@@ -81,29 +59,28 @@ public struct ConnectionListView: View {
startPoint: .top,
endPoint: .bottom
)
// soft accent bloom up top so the floating buttons feel anchored
RadialGradient(
colors: [Color.blue.opacity(0.22), .clear],
center: .init(x: 0.5, y: 0.0),
startRadius: 20,
endRadius: 320
colors: [Color.blue.opacity(0.20), .clear],
center: .init(x: 0.5, y: 0.05),
startRadius: 10,
endRadius: 360
)
.blendMode(.screen)
.allowsHitTesting(false)
}
}
// MARK: Top chrome (custom replaces nav bar)
// MARK: Top chrome flush with safe area top
private var topChrome: some View {
VStack(spacing: 14) {
VStack(spacing: 12) {
HStack(alignment: .center) {
circularGlassButton(systemName: "gearshape", label: "Settings") {
showingSettings = true
}
Spacer()
Text("Screens")
.font(.system(size: 28, weight: .bold, design: .rounded))
.font(.system(size: 26, weight: .bold, design: .rounded))
.foregroundStyle(.white)
Spacer()
circularGlassButton(systemName: "plus", label: "Add connection") {
@@ -111,10 +88,11 @@ public struct ConnectionListView: View {
showingAdd = true
}
}
searchField
}
.padding(.top, 4)
.padding(.horizontal, 16)
.padding(.top, 8)
.padding(.bottom, 12)
}
private func circularGlassButton(systemName: String,
@@ -124,7 +102,7 @@ public struct ConnectionListView: View {
Image(systemName: systemName)
.font(.callout.weight(.semibold))
.foregroundStyle(.white)
.frame(width: 40, height: 40)
.frame(width: 38, height: 38)
}
.buttonStyle(.plain)
.glassSurface(in: Circle())
@@ -160,6 +138,26 @@ public struct ConnectionListView: View {
.glassSurface(in: Capsule())
}
// MARK: Content
private var contentList: some View {
ScrollView {
LazyVStack(spacing: 22) {
if !discovery.hosts.isEmpty || discovery.isBrowsing {
discoveredSection
}
savedSection
Color.clear.frame(height: 1) // bottom anchor
}
.padding(.horizontal, 16)
.padding(.top, 4)
.padding(.bottom, 24)
}
#if os(iOS)
.scrollIndicators(.hidden)
#endif
}
// MARK: Discovered
private var discoveredSection: some View {
@@ -257,7 +255,7 @@ public struct ConnectionListView: View {
Text("No saved connections")
.font(.headline)
.foregroundStyle(.white)
Text("Tap the + button up top to add a Mac, Linux box, or Raspberry Pi.")
Text("Tap + at the top to add a Mac, Linux box, or Raspberry Pi.")
.font(.callout)
.foregroundStyle(.white.opacity(0.65))
.multilineTextAlignment(.center)
@@ -299,7 +297,9 @@ public struct ConnectionListView: View {
}
private func connectionRow(_ connection: SavedConnection) -> some View {
NavigationLink(value: SessionRoute(connectionID: connection.id)) {
Button {
sessionConnection = connection
} label: {
ConnectionCard(connection: connection)
.padding(14)
}
@@ -379,10 +379,6 @@ public struct ConnectionListView: View {
showingAdd = true
}
private func connection(with id: UUID) -> SavedConnection? {
connections.first(where: { $0.id == id })
}
private func delete(_ connection: SavedConnection) {
try? KeychainService().deletePassword(account: connection.keychainTag)
modelContext.delete(connection)