Skip to content

Commit fef3a86

Browse files
committed
Fix Mac offline UI consistency: red dot, correct status line, forceFetchAll on Try Again
MacControlCard: Add isOffline parameter (computed from >10 min staleness, same threshold as MacReachabilityBanner). When offline: - Status dot turns red instead of gray - Status line shows 'Mac unreachable, commands will apply on reconnect' instead of the stale 'Idle, sleeps when agents finish' message MacReachabilityBanner: 'Try again' now calls forceFetchAll() when the Mac is in .offline state. Incremental fetchChanges() is useless here because the stale heartbeat was already seen by the sync engine; forceFetchAll() wipes the token and does a full re-fetch so the Mac's record is returned even if it has not changed since the last successful sync. HomeView: Pull-to-refresh also uses forceFetchAll() so a user manually refreshing the home screen when the Mac is unreachable gets a real re-fetch. SettingsView: Rename 'Last sync' to 'iCloud sync' in the About section to clearly distinguish it from 'Last seen' (Mac heartbeat age) in the Connection section. 'iCloud sync: 51s' next to 'Last seen: 10 min' was misleading -- it looked like everything was fine. DemoView: Pass isOffline: false to MacControlCard (demo is always reachable).
1 parent 8630793 commit fef3a86

5 files changed

Lines changed: 24 additions & 4 deletions

File tree

DoomCoderCompanion/DoomCoderCompanion/UI/DemoView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ struct DemoView: View {
6161
macName: "Sample Mac",
6262
lastSeen: nil,
6363
isDemo: true,
64+
isOffline: false,
6465
mode: demoMode,
6566
screen: demoScreen,
6667
timerHours: demoTimer,

DoomCoderCompanion/DoomCoderCompanion/UI/HomeView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ struct HomeView: View {
2929
.background(Color(.systemGroupedBackground))
3030
.navigationTitle("DoomCoder")
3131
.refreshable {
32-
await CompanionSyncEngine.shared.fetchChanges()
32+
await CompanionSyncEngine.shared.forceFetchAll()
3333
}
3434
.sheet(isPresented: $showConnect) {
3535
ConnectFlowView(onFinished: {})

DoomCoderCompanion/DoomCoderCompanion/UI/MacControlView.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ struct MacControlCard: View {
2121
let macName: String
2222
let lastSeen: Date?
2323
let isDemo: Bool
24+
let isOffline: Bool // true when Mac heartbeat is >10 min stale
2425

2526
let mode: KeepAwakeMode
2627
let screen: ScreenMode
@@ -100,7 +101,7 @@ struct MacControlCard: View {
100101
}
101102
Spacer()
102103
Circle()
103-
.fill(awakeActive ? Color.green : Color.secondary.opacity(0.4))
104+
.fill(isOffline ? Color.red : (awakeActive ? Color.green : Color.secondary.opacity(0.4)))
104105
.frame(width: 10, height: 10)
105106
.accessibilityHidden(true)
106107
}
@@ -146,6 +147,10 @@ struct MacControlCard: View {
146147
if waiting {
147148
ProgressView().controlSize(.small)
148149
Text("Sent — waiting for your Mac to check in")
150+
} else if isOffline {
151+
Image(systemName: "wifi.exclamationmark")
152+
.foregroundStyle(.red)
153+
Text("Mac unreachable — commands will apply on reconnect")
149154
} else {
150155
Image(systemName: statusSymbol)
151156
.foregroundStyle(awakeActive ? .green : .secondary)
@@ -195,13 +200,18 @@ struct MacControlView: View {
195200
@State private var waitingCommandId: String?
196201
@State private var waitTimeout: Task<Void, Never>?
197202

203+
/// Matches the .offline threshold in MacReachabilityBanner (10 minutes).
204+
private let offlineThreshold: TimeInterval = 600
205+
198206
var body: some View {
199207
Group {
200208
if let mac = macStore.primary {
209+
let offline = Date().timeIntervalSince(mac.lastSeen) >= offlineThreshold
201210
MacControlCard(
202211
macName: mac.name,
203212
lastSeen: mac.lastSeen,
204213
isDemo: false,
214+
isOffline: offline,
205215
mode: mode,
206216
screen: screen,
207217
timerHours: timerHours,

DoomCoderCompanion/DoomCoderCompanion/UI/MacReachabilityBanner.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,16 @@ struct MacReachabilityBanner: View {
103103

104104
private func refresh() async {
105105
isRefreshing = true
106-
await engine.fetchChanges()
106+
// When the Mac is in .offline state, the stale heartbeat was already seen
107+
// by the sync engine. An incremental fetchChanges() won't return it. Use
108+
// forceFetchAll() to wipe the token and do a fresh full fetch so that the
109+
// Mac's latest record is retrieved even if it hasn't changed since our last
110+
// sync. For .stale (3-10 min), incremental fetch is sufficient.
111+
if let mac = macStore.primary, staleness(for: mac) == .offline {
112+
await engine.forceFetchAll()
113+
} else {
114+
await engine.fetchChanges()
115+
}
107116
now = Date()
108117
isRefreshing = false
109118
}

DoomCoderCompanion/DoomCoderCompanion/UI/SettingsView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ struct SettingsView: View {
142142
LabeledContent("Build") {
143143
Text(Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "")
144144
}
145-
LabeledContent("Last sync") {
145+
LabeledContent("iCloud sync") {
146146
if let ts = sync.lastSyncAt {
147147
Text(ts, style: .relative).foregroundStyle(.secondary)
148148
} else {

0 commit comments

Comments
 (0)