## Changes - Created new Debug tab in sidebar (⚙️ icon) - Moved "iCloud Debug" card from offline meter detail into dedicated Debug view - Added debug cards for: - Device Info (Device ID, Device Name) - CloudKit Status (Container, meter counts) - Connected Meters (per-meter MAC/connection details) ## Removed - `offlineDebugText()` function (no longer needed) - Debug card from `offlineMeterDetail()` view ## Result - Offline meter details are now cleaner (focused on device info) - All debug information centralized in Debug tab - Easier access to system diagnostics ✅ Build: Mac Catalyst SUCCESS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@@ -16,6 +16,7 @@ struct ContentView: View {
|
||
| 16 | 16 |
private enum SidebarItem: Hashable {
|
| 17 | 17 |
case overview |
| 18 | 18 |
case meter(String) |
| 19 |
+ case debug |
|
| 19 | 20 |
case bluetoothHelp |
| 20 | 21 |
case deviceChecklist |
| 21 | 22 |
case discoveryChecklist |
@@ -120,6 +121,14 @@ struct ContentView: View {
|
||
| 120 | 121 |
} |
| 121 | 122 |
} |
| 122 | 123 |
} |
| 124 |
+ |
|
| 125 |
+ Section(header: Text("Debug")) {
|
|
| 126 |
+ NavigationLink(tag: SidebarItem.debug, selection: $selectedSidebarItem) {
|
|
| 127 |
+ debugView |
|
| 128 |
+ } label: {
|
|
| 129 |
+ Label("Debug Info", systemImage: "wrench.and.screwdriver")
|
|
| 130 |
+ } |
|
| 131 |
+ } |
|
| 123 | 132 |
} |
| 124 | 133 |
.listStyle(SidebarListStyle()) |
| 125 | 134 |
.navigationTitle("USB Meters")
|
@@ -139,6 +148,8 @@ struct ContentView: View {
|
||
| 139 | 148 |
} else {
|
| 140 | 149 |
unavailableMeterDetail |
| 141 | 150 |
} |
| 151 |
+ case .debug: |
|
| 152 |
+ debugView |
|
| 142 | 153 |
case .bluetoothHelp: |
| 143 | 154 |
appData.bluetoothManager.managerState.helpView |
| 144 | 155 |
case .deviceChecklist: |
@@ -246,27 +257,6 @@ struct ContentView: View {
|
||
| 246 | 257 |
.padding(18) |
| 247 | 258 |
.meterCard(tint: meter.meterColor, fillOpacity: 0.14, strokeOpacity: 0.22) |
| 248 | 259 |
|
| 249 |
- VStack(alignment: .leading, spacing: 10) {
|
|
| 250 |
- HStack {
|
|
| 251 |
- Text("iCloud Debug")
|
|
| 252 |
- .font(.headline) |
|
| 253 |
- Spacer() |
|
| 254 |
- Button {
|
|
| 255 |
- UIPasteboard.general.string = offlineDebugText(for: meter, known: known, isConnectedElsewhere: isConnectedElsewhere) |
|
| 256 |
- } label: {
|
|
| 257 |
- Label("Copy", systemImage: "doc.on.doc")
|
|
| 258 |
- .font(.caption.weight(.semibold)) |
|
| 259 |
- } |
|
| 260 |
- .buttonStyle(.plain) |
|
| 261 |
- } |
|
| 262 |
- Text(offlineDebugText(for: meter, known: known, isConnectedElsewhere: isConnectedElsewhere)) |
|
| 263 |
- .font(.system(.footnote, design: .monospaced)) |
|
| 264 |
- .textSelection(.enabled) |
|
| 265 |
- .frame(maxWidth: .infinity, alignment: .leading) |
|
| 266 |
- } |
|
| 267 |
- .padding(18) |
|
| 268 |
- .meterCard(tint: .indigo, fillOpacity: 0.14, strokeOpacity: 0.22) |
|
| 269 |
- |
|
| 270 | 260 |
Text("When this meter appears over BLE on this device, the live Meter View opens automatically from the BT layer and no CloudKit state will override it.")
|
| 271 | 261 |
.font(.footnote) |
| 272 | 262 |
.foregroundColor(.secondary) |
@@ -344,26 +334,6 @@ struct ContentView: View {
|
||
| 344 | 334 |
return value.formatted(date: .abbreviated, time: .standard) |
| 345 | 335 |
} |
| 346 | 336 |
|
| 347 |
- private func offlineDebugText(for meter: MeterSidebarEntry, known: KnownMeterCatalogItem, isConnectedElsewhere: Bool) -> String {
|
|
| 348 |
- [ |
|
| 349 |
- "Local Device ID: \(AppData.myDeviceID)", |
|
| 350 |
- "Local Device Name: \(AppData.myDeviceName)", |
|
| 351 |
- "Now: \(formatDate(Date()))", |
|
| 352 |
- "MAC: \(meter.macAddress)", |
|
| 353 |
- "Display Name: \(meter.displayName)", |
|
| 354 |
- "Model: \(meter.modelSummary)", |
|
| 355 |
- "Connected By Device ID: \(known.connectedByDeviceID ?? "(empty)")", |
|
| 356 |
- "Connected By Device Name: \(known.connectedByDeviceName ?? "(empty)")", |
|
| 357 |
- "Connected At: \(formatDate(known.connectedAt))", |
|
| 358 |
- "Connected Expiry: \(formatDate(known.connectedExpiryAt))", |
|
| 359 |
- "Last Seen At: \(formatDate(known.lastSeenAt))", |
|
| 360 |
- "Last Seen By Device ID: \(known.lastSeenByDeviceID ?? "(empty)")", |
|
| 361 |
- "Last Seen By Device Name: \(known.lastSeenByDeviceName ?? "(empty)")", |
|
| 362 |
- "Last Seen Peripheral Name: \(known.lastSeenPeripheralName ?? "(empty)")", |
|
| 363 |
- "Connected Elsewhere Decision: \(isConnectedElsewhere ? "true (foreign device + valid expiry)" : "false (missing foreign owner or expired claim)")" |
|
| 364 |
- ].joined(separator: "\n") |
|
| 365 |
- } |
|
| 366 |
- |
|
| 367 | 337 |
private var visibleMeterIDs: [String] {
|
| 368 | 338 |
visibleMeters.map(\.id) |
| 369 | 339 |
} |
@@ -421,7 +391,7 @@ struct ContentView: View {
|
||
| 421 | 391 |
if !shouldShowDiscoveryChecklistEntry {
|
| 422 | 392 |
self.selectedSidebarItem = .overview |
| 423 | 393 |
} |
| 424 |
- case .overview: |
|
| 394 |
+ case .overview, .debug: |
|
| 425 | 395 |
break |
| 426 | 396 |
} |
| 427 | 397 |
} |
@@ -573,6 +543,93 @@ struct ContentView: View {
|
||
| 573 | 543 |
.padding(16) |
| 574 | 544 |
.meterCard(tint: tint, fillOpacity: 0.14, strokeOpacity: 0.20) |
| 575 | 545 |
} |
| 546 |
+ |
|
| 547 |
+ private var debugView: some View {
|
|
| 548 |
+ ScrollView {
|
|
| 549 |
+ VStack(alignment: .leading, spacing: 14) {
|
|
| 550 |
+ VStack(alignment: .leading, spacing: 8) {
|
|
| 551 |
+ Text("Debug Information")
|
|
| 552 |
+ .font(.system(.title2, design: .rounded).weight(.bold)) |
|
| 553 |
+ Text("System and CloudKit details for troubleshooting.")
|
|
| 554 |
+ .font(.footnote) |
|
| 555 |
+ .foregroundColor(.secondary) |
|
| 556 |
+ } |
|
| 557 |
+ .frame(maxWidth: .infinity, alignment: .leading) |
|
| 558 |
+ .padding(18) |
|
| 559 |
+ .meterCard(tint: .purple, fillOpacity: 0.16, strokeOpacity: 0.22) |
|
| 560 |
+ |
|
| 561 |
+ debugCard(title: "Device Info", content: [ |
|
| 562 |
+ "Device ID: \(AppData.myDeviceID)", |
|
| 563 |
+ "Device Name: \(AppData.myDeviceName)" |
|
| 564 |
+ ]) |
|
| 565 |
+ |
|
| 566 |
+ debugCard(title: "CloudKit Status", content: [ |
|
| 567 |
+ "Container: iCloud.ro.xdev.USB-Meter", |
|
| 568 |
+ "Total Meters: \(appData.knownMetersByMAC.count)", |
|
| 569 |
+ "Live Meters: \(appData.meters.count)" |
|
| 570 |
+ ]) |
|
| 571 |
+ |
|
| 572 |
+ if !appData.knownMetersByMAC.isEmpty {
|
|
| 573 |
+ VStack(alignment: .leading, spacing: 10) {
|
|
| 574 |
+ Text("Connected Meters")
|
|
| 575 |
+ .font(.headline) |
|
| 576 |
+ ForEach(Array(appData.knownMetersByMAC.values), id: \.macAddress) { known in
|
|
| 577 |
+ meterDebugCard(for: known) |
|
| 578 |
+ } |
|
| 579 |
+ } |
|
| 580 |
+ .frame(maxWidth: .infinity, alignment: .leading) |
|
| 581 |
+ .padding(18) |
|
| 582 |
+ .meterCard(tint: .cyan, fillOpacity: 0.14, strokeOpacity: 0.22) |
|
| 583 |
+ } |
|
| 584 |
+ } |
|
| 585 |
+ .padding() |
|
| 586 |
+ } |
|
| 587 |
+ .background( |
|
| 588 |
+ LinearGradient( |
|
| 589 |
+ colors: [.purple.opacity(0.08), Color.clear], |
|
| 590 |
+ startPoint: .topLeading, |
|
| 591 |
+ endPoint: .bottomTrailing |
|
| 592 |
+ ) |
|
| 593 |
+ .ignoresSafeArea() |
|
| 594 |
+ ) |
|
| 595 |
+ .navigationBarTitle(Text("Debug Info"), displayMode: .inline)
|
|
| 596 |
+ } |
|
| 597 |
+ |
|
| 598 |
+ private func debugCard(title: String, content: [String]) -> some View {
|
|
| 599 |
+ VStack(alignment: .leading, spacing: 8) {
|
|
| 600 |
+ Text(title) |
|
| 601 |
+ .font(.headline) |
|
| 602 |
+ VStack(alignment: .leading, spacing: 4) {
|
|
| 603 |
+ ForEach(content, id: \.self) { line in
|
|
| 604 |
+ Text(line) |
|
| 605 |
+ .font(.system(.footnote, design: .monospaced)) |
|
| 606 |
+ .textSelection(.enabled) |
|
| 607 |
+ } |
|
| 608 |
+ } |
|
| 609 |
+ } |
|
| 610 |
+ .frame(maxWidth: .infinity, alignment: .leading) |
|
| 611 |
+ .padding(12) |
|
| 612 |
+ .meterCard(tint: .purple, fillOpacity: 0.08, strokeOpacity: 0.16) |
|
| 613 |
+ } |
|
| 614 |
+ |
|
| 615 |
+ private func meterDebugCard(for known: KnownMeterCatalogItem) -> some View {
|
|
| 616 |
+ VStack(alignment: .leading, spacing: 6) {
|
|
| 617 |
+ Text(known.displayName) |
|
| 618 |
+ .font(.subheadline.weight(.semibold)) |
|
| 619 |
+ VStack(alignment: .leading, spacing: 2) {
|
|
| 620 |
+ Text(known.macAddress) |
|
| 621 |
+ .font(.system(.caption, design: .monospaced)) |
|
| 622 |
+ if let connectedBy = known.connectedByDeviceName, !connectedBy.isEmpty {
|
|
| 623 |
+ Text("Connected: \(connectedBy)")
|
|
| 624 |
+ .font(.system(.caption2, design: .default)) |
|
| 625 |
+ .foregroundColor(.secondary) |
|
| 626 |
+ } |
|
| 627 |
+ } |
|
| 628 |
+ } |
|
| 629 |
+ .frame(maxWidth: .infinity, alignment: .leading) |
|
| 630 |
+ .padding(10) |
|
| 631 |
+ .meterCard(tint: .cyan, fillOpacity: 0.08, strokeOpacity: 0.12) |
|
| 632 |
+ } |
|
| 576 | 633 |
} |
| 577 | 634 |
|
| 578 | 635 |
private struct DiscoveryChecklistView: View {
|