- performFullSync was requesting listCollections with limit: 250 —
Outline caps pagination at 100 and rejects anything over that
outright, which was the actual "Synced with 1 error" (now visible
in the UI as of the last fix: "Pagination limit is too large (max
100)"). Paginate collections in increments of 100 the same way
documents already were, continuing until a short page signals the
end — the protocol doesn't expose a total count to ask for up front,
so this is the only way to know when to stop. 2 new regression tests.
- OfflineBanner only ever reflected NetworkMonitor (a real dropped
connection) — turning on the manual "Offline Mode" toggle did
nothing to it, since that's a separate AppStorage flag the banner
never read. ContentView_macOS's sidebar now shows the banner for
either condition, with distinct copy for "you turned this on" vs
"the network's actually down".
- NetworkMonitor: the previous fix (weak self only on the inner Task)
traded one Swift 6 error for another ("'weak' ownership of capture
'self' differs from implicitly-captured strong reference in outer
scope") since the inner closure's capture forced the outer one to
implicitly capture self too. Standard fix: weak capture on the outer
closure, guard-let into a strong local immediately, let the inner
Task closure capture that plain local instead.
37 lines
1.2 KiB
Swift
37 lines
1.2 KiB
Swift
import Foundation
|
|
import Network
|
|
import Observation
|
|
|
|
/// Backs the sidebar's offline badge — purely a UI signal. Unrelated to
|
|
/// `CachingOutlineAPIClient`'s own fallback logic, which reacts to actual
|
|
/// request failures rather than pre-checking reachability.
|
|
@Observable
|
|
@MainActor
|
|
final class NetworkMonitor {
|
|
private(set) var isOnline = true
|
|
|
|
private let monitor = NWPathMonitor()
|
|
private let queue = DispatchQueue(label: "com.outpost.network-monitor")
|
|
|
|
init() {
|
|
// Weak on the outer closure (it's held by `monitor` for the object's
|
|
// whole lifetime, so a strong capture here would be a retain cycle),
|
|
// then unwrapped once into a plain strong local — the inner `Task`
|
|
// closure capturing that local `self` is what a nested closure needs
|
|
// to be consistent about capture semantics with its enclosing one.
|
|
monitor.pathUpdateHandler = { [weak self] path in
|
|
guard let self else { return }
|
|
|
|
let online = path.status == .satisfied
|
|
Task { @MainActor in
|
|
self.isOnline = online
|
|
}
|
|
}
|
|
monitor.start(queue: queue)
|
|
}
|
|
|
|
deinit {
|
|
monitor.cancel()
|
|
}
|
|
}
|