Skip to content

Commit 8630793

Browse files
committed
Fix reconnect 'No Mac found' + add real demo notification
ConnectFlowView: Replace the incremental fetchChanges() polling loop in begin() with forceFetchAll(). After disconnectCurrentMac() clears local stores, the CKSyncEngine still holds an old server token, so incremental fetches return zero records (Mac's MacStatus was already 'seen'). Calling forceFetchAll() wipes the token, reinitialises the engine, and awaits a full CloudKit re-fetch before returning — so byMacId is already populated by the time we check. A small 3-iteration fallback loop handles the case where the Mac published its heartbeat after our initial import window. DemoView: The 'Preview a notification' button now fires a real UNNotificationRequest with a 2-second trigger instead of showing a static in-app mock sheet. The user can background the app and see the actual system banner, giving a genuine preview of what agent alerts look like. Permission flow: authorized -> schedule; notDetermined -> request then schedule if granted; denied -> fall back to existing visual mock sheet.
1 parent 28175fb commit 8630793

2 files changed

Lines changed: 46 additions & 4 deletions

File tree

DoomCoderCompanion/DoomCoderCompanion/UI/ConnectFlowView.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,19 @@ struct ConnectFlowView: View {
161161
let available = await isiCloudAvailable()
162162
guard available else { step = .icloudNeeded; return }
163163
step = .searching
164-
// Poll a few times so a freshly-launched Mac has time to publish.
165-
for _ in 0..<6 {
166-
await CompanionSyncEngine.shared.fetchChanges()
164+
// After disconnect, the sync engine holds a stale incremental token that
165+
// causes fetchChanges() to return zero records (the Mac's MacStatus was
166+
// already "seen" before the disconnect). forceFetchAll() wipes the token
167+
// and re-initialises the engine, then awaits a full CloudKit re-fetch so
168+
// all current records (including the Mac heartbeat) arrive via the
169+
// delegate before this call returns.
170+
await CompanionSyncEngine.shared.forceFetchAll()
171+
// Fallback: if the Mac published its heartbeat after our initial import
172+
// window (e.g. just restarted), poll a couple more times.
173+
for _ in 0..<3 {
167174
if !macStore.byMacId.isEmpty { break }
168175
try? await Task.sleep(for: .seconds(2))
176+
await CompanionSyncEngine.shared.fetchChanges()
169177
}
170178
switch macStore.byMacId.count {
171179
case 0:

DoomCoderCompanion/DoomCoderCompanion/UI/DemoView.swift

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
// Everything here is local sample data; nothing is written to CloudKit.
88

99
import SwiftUI
10+
import UserNotifications
1011
import DoomCoderCore
1112

1213
struct DemoView: View {
@@ -45,7 +46,7 @@ struct DemoView: View {
4546
section(title: "Notifications", subtitle: "See how an agent alert looks on your device.") {
4647
Button {
4748
Haptics.tap()
48-
showNotificationPreview = true
49+
Task { await previewNotification() }
4950
} label: {
5051
Label("Preview a notification", systemImage: "bell.badge")
5152
.font(.headline)
@@ -118,6 +119,39 @@ struct DemoView: View {
118119
.frame(maxWidth: .infinity, alignment: .leading)
119120
}
120121

122+
// MARK: - Notification preview
123+
124+
private func previewNotification() async {
125+
let center = UNUserNotificationCenter.current()
126+
let settings = await center.notificationSettings()
127+
switch settings.authorizationStatus {
128+
case .authorized, .provisional, .ephemeral:
129+
scheduleDemoLocalNotification()
130+
case .notDetermined:
131+
let granted = (try? await center.requestAuthorization(options: [.alert, .sound])) ?? false
132+
if granted { scheduleDemoLocalNotification() } else { showNotificationPreview = true }
133+
default:
134+
// Permission denied — fall back to the in-app visual preview.
135+
showNotificationPreview = true
136+
}
137+
}
138+
139+
/// Schedules a real local notification with a 2-second delay so the user can
140+
/// background the app and see the system banner appear on their device.
141+
private func scheduleDemoLocalNotification() {
142+
let content = UNMutableNotificationContent()
143+
content.title = "Claude needs approval"
144+
content.body = "Wants to run `git push` in ~/Projects/api"
145+
content.sound = .default
146+
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false)
147+
let request = UNNotificationRequest(
148+
identifier: "doomcoder.demo.\(UUID().uuidString)",
149+
content: content,
150+
trigger: trigger
151+
)
152+
UNUserNotificationCenter.current().add(request)
153+
}
154+
121155
// MARK: - Sample data
122156

123157
struct SampleAgent {

0 commit comments

Comments
 (0)