Files
Outpost/Outpost/Support/NetworkMonitor.swift
T
Puranjay Savar Mattas ce235d656d fix(offline): pagination-limit bug in full sync, missing manual-mode badge, capture error
- 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.
2026-08-15 00:45:09 +01:00

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()
}
}