ListlessMac/Views/SyncDiagnosticsView.swift (3627B)
1import SwiftUI
2
3struct SyncDiagnosticsView: View {
4 @ObservedObject var syncMonitor: CloudKitSyncMonitor
5
6 private static let timestampFormatter: DateFormatter = {
7 let formatter = DateFormatter()
8 formatter.dateStyle = .none
9 formatter.timeStyle = .medium
10 return formatter
11 }()
12
13 var body: some View {
14 List {
15 Section("Status") {
16 row("Transient Banner", syncMonitor.transientErrorMessage ?? "None")
17 row("Last Error Domain", syncMonitor.lastCloudKitErrorDomain ?? "None")
18 row("Last Error Code", syncMonitor.lastCloudKitErrorCode.map(String.init) ?? "None")
19 row("Last Error Description", syncMonitor.lastCloudKitErrorDescription ?? "None")
20 row(
21 "Last Success",
22 syncMonitor.lastSuccessfulSyncDate.map(Self.timestampFormatter.string(from:)) ?? "None"
23 )
24 }
25
26 Section("Recent Events") {
27 if syncMonitor.recentDiagnostics.isEmpty {
28 Text("No events captured yet.")
29 .foregroundStyle(.secondary)
30 } else {
31 ForEach(syncMonitor.recentDiagnostics.reversed()) { entry in
32 VStack(alignment: .leading, spacing: 4) {
33 Text(
34 "\(Self.timestampFormatter.string(from: entry.timestamp)) [\(entry.level.uppercased())]"
35 )
36 .font(.caption.monospaced())
37 .foregroundStyle(.secondary)
38
39 Text(entry.message)
40 .font(.caption.monospaced())
41 .textSelection(.enabled)
42 }
43 .padding(.vertical, 2)
44 }
45 }
46 }
47 }
48 .textSelection(.enabled)
49 .toolbar {
50 ToolbarItem {
51 Button("Copy") {
52 let pasteboard = NSPasteboard.general
53 pasteboard.clearContents()
54 pasteboard.setString(diagnosticDump, forType: .string)
55 }
56 }
57 }
58 }
59
60 @ViewBuilder
61 private func row(_ title: String, _ value: String) -> some View {
62 VStack(alignment: .leading, spacing: 4) {
63 Text(title)
64 .font(.caption)
65 .foregroundStyle(.secondary)
66 Text(value)
67 .font(.body.monospaced())
68 .textSelection(.enabled)
69 }
70 .padding(.vertical, 2)
71 }
72
73 private var diagnosticDump: String {
74 var lines: [String] = []
75 lines.append("Transient Banner: \(syncMonitor.transientErrorMessage ?? "None")")
76 lines.append("Last Error Domain: \(syncMonitor.lastCloudKitErrorDomain ?? "None")")
77 lines.append("Last Error Code: \(syncMonitor.lastCloudKitErrorCode.map(String.init) ?? "None")")
78 lines.append("Last Error Description: \(syncMonitor.lastCloudKitErrorDescription ?? "None")")
79 lines.append(
80 "Last Success: \(syncMonitor.lastSuccessfulSyncDate.map(Self.timestampFormatter.string(from:)) ?? "None")"
81 )
82 lines.append("")
83 lines.append("Recent Events:")
84 for entry in syncMonitor.recentDiagnostics {
85 lines.append(
86 "\(Self.timestampFormatter.string(from: entry.timestamp)) [\(entry.level.uppercased())] \(entry.message)"
87 )
88 }
89 return lines.joined(separator: "\n")
90 }
91}