@@ -28,7 +28,7 @@ There are no real deployments, only test installations. Existing prototype datab |
||
| 28 | 28 |
| SQLite archive | Archive v2 schema, snapshot-level observation grouping, differential write path, v2 verification/delete bookkeeping, daily aggregate rebuilds, integrity report, v2 record reads, SQL diff/count/aggregate/provenance/consolidation-evidence APIs, large synthetic diff pagination coverage, formal timing/memory metrics, and XCTest coverage are in place; the legacy `archive_samples` mirror has been removed | Move Snapshots/Data Types from SwiftData previews to archive/cache DTOs | |
| 29 | 29 |
| Core Data cache | Initial programmatic Core Data model, full-cache rebuild service, read DTOs for observation/type/health rows, and Dashboard archive-cache status wiring are in place | Move Snapshots/Data Types to cache DTOs and add targeted partial invalidation | |
| 30 | 30 |
| SwiftData cache | Exists; test builds now reset legacy prototype UI/archive/cache stores once for archive v2 so old SwiftData-only snapshots are not treated as backed-up observations | Treat as disposable prototype data; reset/ignore during v2 transition | |
| 31 |
-| UI | Prototype exists; Snapshots/Data Types now default to the local device timeline instead of a multi-device picker. Dashboard status prefers archive/cache observation rows and shows cache health; Snapshots timeline, snapshot detail summaries/type rows, and Data Types list prefer Core Data cache rows when archive observation ids exist; data type detail/drill-down uses SQLite `diffSummary`/`diffRecords`, with SwiftData detail cache as transition fallback | Finish remaining detail charts/export previews on paged SQLite DTOs | |
|
| 31 |
+| UI | Prototype exists; Snapshots/Data Types now default to the local device timeline instead of a multi-device picker. Dashboard status prefers archive/cache observation rows and shows cache health; Snapshots timeline, snapshot detail summaries/type rows, and Data Types list prefer Core Data cache rows when archive observation ids exist; data type detail/drill-down uses SQLite `diffSummary`/`diffRecords`; visible change labels now use neutral new/missing/change-review language, with SwiftData detail cache as transition fallback | Finish remaining detail charts/export previews on paged SQLite DTOs | |
|
| 32 | 32 |
| Diff/change explanation | Prototype/legacy anomaly logic exists | Move heavy diffing into SQLite and use neutral change classifications | |
| 33 | 33 |
| Export | Prototype scoped JSON export exists | Add recovery-compatible manifests and streaming/paged export | |
| 34 | 34 |
| Legacy device support | Not implemented | Remove SwiftData dependency and simplify heavy views for low-memory devices | |
@@ -40,7 +40,7 @@ Detailed checkable milestones live in [`Refactoring-Plan.md`](Refactoring-Plan.m |
||
| 40 | 40 |
|
| 41 | 41 |
1. Move Snapshots/Data Types from SwiftData model reads to Core Data/cache DTOs. |
| 42 | 42 |
2. Add targeted cache invalidation for affected observation/type ranges. |
| 43 |
-3. Update UI language from anomaly/status to observation/diff/export. |
|
| 43 |
+3. Finish remaining UI language cleanup from anomaly/status to observation/diff/export where legacy model names still leak into active flows. |
|
| 44 | 44 |
4. Add streaming exports with manifests. |
| 45 | 45 |
5. Validate on low-memory/legacy-class devices. |
| 46 | 46 |
|
@@ -65,7 +65,7 @@ Detailed checkable milestones live in [`Refactoring-Plan.md`](Refactoring-Plan.m |
||
| 65 | 65 |
- [x] Snapshots timeline rows use Core Data cached observation counts/change summaries when cache rows are available. |
| 66 | 66 |
- [x] Snapshot detail summary/type rows use Core Data cached summaries plus SQLite diff summaries when archive observation ids are available. |
| 67 | 67 |
- [x] Data Types list rows use Core Data cached counts plus SQLite diff summaries when archive observation ids are available. |
| 68 |
-- [x] Data type added/disappeared drill-down pages records from SQLite diff queries when archive observation ids are available. |
|
| 68 |
+- [x] Data type new/missing drill-down pages records from SQLite diff queries when archive observation ids are available. |
|
| 69 | 69 |
- [x] Expensive counts used by reports/UI are cached and rebuildable. |
| 70 | 70 |
- [x] Deleting Core Data cache and rebuilding from SQLite restores UI/report summaries. |
| 71 | 71 |
- [x] Dashboard surfaces SQLite/Core Data cache health, cache schema, cache errors, and latest archive observation counts. |
@@ -226,9 +226,9 @@ Checklist: |
||
| 226 | 226 |
- [x] Observation detail uses cached summary/type rows plus SQLite diff summaries when archive observation ids exist. |
| 227 | 227 |
- [x] Data Types list rows prefer Core Data cached counts plus SQLite `diffSummary` when archive observation ids exist. |
| 228 | 228 |
- [x] Data type detail uses SQLite `diffSummary` when archive observation ids exist. |
| 229 |
-- [x] Data type added/disappeared drill-down pages through SQLite `diffRecords` when archive observation ids exist. |
|
| 229 |
+- [x] Data type new/missing drill-down pages through SQLite `diffRecords` when archive observation ids exist. |
|
| 230 | 230 |
- [ ] Diff detail fully uses cached summary plus paged SQLite DTOs. |
| 231 |
-- [ ] Data type screens use target change labels. |
|
| 231 |
+- [x] Data type screens use target change labels. |
|
| 232 | 232 |
- [ ] Export preview uses export query/manifest APIs. |
| 233 | 233 |
- [x] Archive status reflects SQLite/Core Data cache health. |
| 234 | 234 |
- [ ] Legacy/small-device UI mode simplifies heavy visualizations. |
@@ -195,7 +195,7 @@ final class DataTypeRecordListViewModel {
|
||
| 195 | 195 |
case .disappeared(let typeID), .disappearedDiff(let typeID, _, _): |
| 196 | 196 |
return HealthArchiveReportRequest( |
| 197 | 197 |
reportID: UUID(), |
| 198 |
- title: "Disappeared Records - \(displayName)", |
|
| 198 |
+ title: "Missing Records - \(displayName)", |
|
| 199 | 199 |
typeIdentifierFilter: typeID, |
| 200 | 200 |
disappearedOnly: true |
| 201 | 201 |
) |
@@ -203,7 +203,7 @@ final class DataTypeRecordListViewModel {
|
||
| 203 | 203 |
.addedDiff(let typeID, let afterDate, let beforeDate, _, _): |
| 204 | 204 |
return HealthArchiveReportRequest( |
| 205 | 205 |
reportID: UUID(), |
| 206 |
- title: "Added Records - \(displayName)", |
|
| 206 |
+ title: "New Records - \(displayName)", |
|
| 207 | 207 |
typeIdentifierFilter: typeID, |
| 208 | 208 |
firstSeenAfter: afterDate, |
| 209 | 209 |
firstSeenBefore: beforeDate |
@@ -186,7 +186,7 @@ struct DashboardView: View {
|
||
| 186 | 186 |
|
| 187 | 187 |
private func failureImpact(_ reason: String) -> String {
|
| 188 | 188 |
switch reason {
|
| 189 |
- case "Not authorized": return "Excluded from checksum and anomaly detection" |
|
| 189 |
+ case "Not authorized": return "Excluded from checksum and change analysis" |
|
| 190 | 190 |
case "Timeout": return "Data unavailable — type skipped this run" |
| 191 | 191 |
case "Unsupported": return "Not supported on this device or OS version" |
| 192 | 192 |
default: return "Data unavailable" |
@@ -206,28 +206,28 @@ struct DashboardView: View {
|
||
| 206 | 206 |
} |
| 207 | 207 |
if !unauthorized.isEmpty {
|
| 208 | 208 |
items.append("Open iOS Settings, choose HealthProbe, and re-enable Health read access for the listed metrics.")
|
| 209 |
- items.append("After restoring access, create a new snapshot before relying on checksum or anomaly results.")
|
|
| 209 |
+ items.append("After restoring access, create a new snapshot before relying on checksum or change analysis.")
|
|
| 210 | 210 |
} |
| 211 | 211 |
if !timedOut.isEmpty {
|
| 212 | 212 |
items.append("Timeout reflects HealthProbe's configured limit, not missing Health data or permission.")
|
| 213 | 213 |
} |
| 214 | 214 |
if viewModel.snapshotProgress == .incomplete {
|
| 215 |
- items.append("Partial snapshots are excluded from anomaly detection. A complete snapshot is needed to resume integrity checks.")
|
|
| 215 |
+ items.append("Partial snapshots are excluded from change analysis. A complete snapshot is needed to resume integrity checks.")
|
|
| 216 | 216 |
} |
| 217 | 217 |
return items |
| 218 | 218 |
} |
| 219 | 219 |
|
| 220 | 220 |
private func ambiguousDisappearanceNotes() -> [String] {
|
| 221 | 221 |
[ |
| 222 |
- "All values disappeared for the listed metric. Apple API does not allow HealthProbe to verify Health read authorization status.", |
|
| 223 |
- "Possible causes: the metric data was deleted, or Health read authorization was withdrawn." |
|
| 222 |
+ "All values are missing for the listed metric in the current Health view. Apple API does not allow HealthProbe to verify Health read authorization status.", |
|
| 223 |
+ "Possible causes: Health read authorization was withdrawn, or the metric is genuinely absent from the current Health store." |
|
| 224 | 224 |
] |
| 225 | 225 |
} |
| 226 | 226 |
|
| 227 | 227 |
private func ambiguousDisappearanceActions() -> [String] {
|
| 228 | 228 |
[ |
| 229 | 229 |
"Treat the metric as unauthorized.", |
| 230 |
- "Treat the metric as totally deleted.", |
|
| 230 |
+ "Treat the metric as absent from the current Health store.", |
|
| 231 | 231 |
"Cancel saving this snapshot." |
| 232 | 232 |
] |
| 233 | 233 |
} |
@@ -366,9 +366,9 @@ struct DashboardView: View {
|
||
| 366 | 366 |
lines.append("INTERPRETATION_HINTS")
|
| 367 | 367 |
lines.append("Partial snapshot: \(isPartialSnapshot ? "true" : "false")")
|
| 368 | 368 |
lines.append("Requires user resolution: \(requiresResolution ? "true" : "false")")
|
| 369 |
- lines.append("Failed metrics are excluded from checksum/anomaly detection")
|
|
| 369 |
+ lines.append("Failed metrics are excluded from checksum/change analysis")
|
|
| 370 | 370 |
lines.append("Do not infer deletion from partial snapshots")
|
| 371 |
- lines.append("All-values-disappeared metrics require classification before saving")
|
|
| 371 |
+ lines.append("All-values-missing metrics require review before saving")
|
|
| 372 | 372 |
lines.append("Timeout means HealthProbe cancelled after configured timeout, not necessarily HealthKit denial")
|
| 373 | 373 |
lines.append("")
|
| 374 | 374 |
lines.append("CONFIGURATION")
|
@@ -595,7 +595,7 @@ struct DashboardView: View {
|
||
| 595 | 595 |
VStack(alignment: .leading, spacing: 2) {
|
| 596 | 596 |
Text(isComplete ? "Snapshot created successfully" : (requiresResolution ? "Snapshot needs review" : "Partial snapshot")) |
| 597 | 597 |
.font(.subheadline.weight(.semibold)) |
| 598 |
- Text(isComplete ? "All monitored metrics loaded" : (requiresResolution ? "\(viewModel.ambiguousDisappearedMetrics.count) metric\(viewModel.ambiguousDisappearedMetrics.count == 1 ? "" : "s") need classification" : "\(progress.failedCount) metric\(progress.failedCount == 1 ? "" : "s") failed")) |
|
| 598 |
+ Text(isComplete ? "All monitored metrics loaded" : (requiresResolution ? "\(viewModel.ambiguousDisappearedMetrics.count) metric\(viewModel.ambiguousDisappearedMetrics.count == 1 ? "" : "s") need review" : "\(progress.failedCount) metric\(progress.failedCount == 1 ? "" : "s") failed")) |
|
| 599 | 599 |
.font(.caption) |
| 600 | 600 |
.foregroundStyle(.secondary) |
| 601 | 601 |
} |
@@ -658,7 +658,7 @@ struct DashboardView: View {
|
||
| 658 | 658 |
Text(metric.displayName) |
| 659 | 659 |
.font(.subheadline.weight(.semibold)) |
| 660 | 660 |
Spacer() |
| 661 |
- Text("all values disappeared")
|
|
| 661 |
+ Text("all values missing")
|
|
| 662 | 662 |
.font(.caption) |
| 663 | 663 |
.foregroundStyle(.secondary) |
| 664 | 664 |
} |
@@ -1109,17 +1109,17 @@ struct DashboardView: View {
|
||
| 1109 | 1109 |
} |
| 1110 | 1110 |
.buttonStyle(.borderedProminent) |
| 1111 | 1111 |
.disabled(viewModel.isCreatingSnapshot) |
| 1112 |
- .accessibilityLabel("Treat disappeared metrics as unauthorized")
|
|
| 1112 |
+ .accessibilityLabel("Treat missing metrics as unauthorized")
|
|
| 1113 | 1113 |
|
| 1114 | 1114 |
Button {
|
| 1115 | 1115 |
Task { await viewModel.treatAmbiguousMetricsAsDeleted(context: modelContext) }
|
| 1116 | 1116 |
} label: {
|
| 1117 |
- Label("Treat as Deleted", systemImage: "trash")
|
|
| 1117 |
+ Label("Treat as Missing", systemImage: "minus.circle")
|
|
| 1118 | 1118 |
.frame(maxWidth: .infinity) |
| 1119 | 1119 |
} |
| 1120 | 1120 |
.buttonStyle(.bordered) |
| 1121 | 1121 |
.disabled(viewModel.isCreatingSnapshot) |
| 1122 |
- .accessibilityLabel("Treat disappeared metrics as deleted")
|
|
| 1122 |
+ .accessibilityLabel("Treat missing metrics as absent from the current Health store")
|
|
| 1123 | 1123 |
|
| 1124 | 1124 |
Button {
|
| 1125 | 1125 |
Task { await viewModel.discardSnapshot(context: modelContext) }
|
@@ -1436,9 +1436,9 @@ private struct AnomalySummarySection: View {
|
||
| 1436 | 1436 |
|
| 1437 | 1437 |
var body: some View {
|
| 1438 | 1438 |
if !unresolved.isEmpty {
|
| 1439 |
- Section("Anomalies") {
|
|
| 1439 |
+ Section("Change Review") {
|
|
| 1440 | 1440 |
if criticalCount > 0 {
|
| 1441 |
- Label("\(criticalCount) critical \(criticalCount == 1 ? "anomaly" : "anomalies")",
|
|
| 1441 |
+ Label("\(criticalCount) critical \(criticalCount == 1 ? "change" : "changes")",
|
|
| 1442 | 1442 |
systemImage: "exclamationmark.circle.fill") |
| 1443 | 1443 |
.foregroundStyle(Color.criticalRed) |
| 1444 | 1444 |
} |
@@ -1449,10 +1449,10 @@ private struct AnomalySummarySection: View {
|
||
| 1449 | 1449 |
} |
| 1450 | 1450 |
if !disappearedRecords.isEmpty {
|
| 1451 | 1451 |
VStack(alignment: .leading, spacing: 8) {
|
| 1452 |
- Label("Records disappeared", systemImage: "eye.slash")
|
|
| 1452 |
+ Label("Records missing from current view", systemImage: "eye.slash")
|
|
| 1453 | 1453 |
.font(.subheadline.weight(.semibold)) |
| 1454 | 1454 |
.foregroundStyle(Color.criticalRed) |
| 1455 |
- Text("Check Health read access for the affected metrics first. If you intentionally revoked access, confirm that here; otherwise investigate as possible data deletion.")
|
|
| 1455 |
+ Text("Check Health read access for the affected metrics first. If you intentionally revoked access, confirm that here; otherwise treat this as a change that needs external review.")
|
|
| 1456 | 1456 |
.font(.caption) |
| 1457 | 1457 |
.foregroundStyle(.secondary) |
| 1458 | 1458 |
ForEach(disappearedRecords.prefix(3)) { anomaly in
|
@@ -249,8 +249,8 @@ struct DataTypeTemporalDistributionView: View {
|
||
| 249 | 249 |
.foregroundStyle(.primary) |
| 250 | 250 |
|
| 251 | 251 |
HStack(spacing: 12) {
|
| 252 |
- statCard("Added", totalAdded, Color.healthyGreen)
|
|
| 253 |
- statCard("Disappeared", totalDisappeared, Color.criticalRed)
|
|
| 252 |
+ statCard("New", totalAdded, Color.healthyGreen)
|
|
| 253 |
+ statCard("Missing", totalDisappeared, Color.criticalRed)
|
|
| 254 | 254 |
statCard("Unchanged", totalUnchanged, Color.neutralGray)
|
| 255 | 255 |
} |
| 256 | 256 |
|
@@ -89,7 +89,7 @@ struct RecordChangeComparisonCard: View {
|
||
| 89 | 89 |
Image(systemName: "plus.circle.fill") |
| 90 | 90 |
.foregroundStyle(Color.healthyGreen) |
| 91 | 91 |
VStack(alignment: .leading, spacing: 2) {
|
| 92 |
- Text("Added")
|
|
| 92 |
+ Text("New")
|
|
| 93 | 93 |
.font(.caption) |
| 94 | 94 |
.foregroundStyle(.secondary) |
| 95 | 95 |
Text("\(addedCount)")
|
@@ -114,7 +114,7 @@ struct RecordChangeComparisonCard: View {
|
||
| 114 | 114 |
Image(systemName: "minus.circle.fill") |
| 115 | 115 |
.foregroundStyle(Color.criticalRed) |
| 116 | 116 |
VStack(alignment: .leading, spacing: 2) {
|
| 117 |
- Text("Disappeared")
|
|
| 117 |
+ Text("Missing")
|
|
| 118 | 118 |
.font(.caption) |
| 119 | 119 |
.foregroundStyle(.secondary) |
| 120 | 120 |
Text("\(disappearedCount)")
|
@@ -286,8 +286,8 @@ struct RecordChangeEvolutionChart: View {
|
||
| 286 | 286 |
|
| 287 | 287 |
private var changeLegend: some View {
|
| 288 | 288 |
HStack(spacing: 10) {
|
| 289 |
- legendItem(color: Color.healthyGreen, label: "Added") |
|
| 290 |
- legendItem(color: Color.criticalRed, label: "Disappeared") |
|
| 289 |
+ legendItem(color: Color.healthyGreen, label: "New") |
|
| 290 |
+ legendItem(color: Color.criticalRed, label: "Missing") |
|
| 291 | 291 |
Spacer(minLength: 0) |
| 292 | 292 |
} |
| 293 | 293 |
.font(.caption2) |
@@ -1,6 +1,6 @@ |
||
| 1 | 1 |
import SwiftUI |
| 2 | 2 |
|
| 3 |
-/// Visual indicator of record changes (added/disappeared) with navigation |
|
| 3 |
+/// Visual indicator of record changes with navigation. |
|
| 4 | 4 |
struct RecordChangeIndicator: View {
|
| 5 | 5 |
let addedCount: Int |
| 6 | 6 |
let disappearedCount: Int |
@@ -27,7 +27,7 @@ struct RecordChangeIndicator: View {
|
||
| 27 | 27 |
|
| 28 | 28 |
HStack(spacing: 12) {
|
| 29 | 29 |
changeButton( |
| 30 |
- title: "Added", |
|
| 30 |
+ title: "New", |
|
| 31 | 31 |
count: addedCount, |
| 32 | 32 |
percentage: addedPercentage, |
| 33 | 33 |
icon: "plus.circle.fill", |
@@ -36,7 +36,7 @@ struct RecordChangeIndicator: View {
|
||
| 36 | 36 |
) |
| 37 | 37 |
|
| 38 | 38 |
changeButton( |
| 39 |
- title: "Gone", |
|
| 39 |
+ title: "Missing", |
|
| 40 | 40 |
count: disappearedCount, |
| 41 | 41 |
percentage: disappearedPercentage, |
| 42 | 42 |
icon: "xmark.circle.fill", |
@@ -124,8 +124,8 @@ struct RecordChangeIndicator: View {
|
||
| 124 | 124 |
disappearedCount: 12, |
| 125 | 125 |
totalCount: 500, |
| 126 | 126 |
displayName: "Steps", |
| 127 |
- onAddedTap: { print("Added tapped") },
|
|
| 128 |
- onDisappearedTap: { print("Disappeared tapped") }
|
|
| 127 |
+ onAddedTap: { print("New tapped") },
|
|
| 128 |
+ onDisappearedTap: { print("Missing tapped") }
|
|
| 129 | 129 |
) |
| 130 | 130 |
|
| 131 | 131 |
RecordChangeIndicator( |
@@ -133,8 +133,8 @@ struct RecordChangeIndicator: View {
|
||
| 133 | 133 |
disappearedCount: 8, |
| 134 | 134 |
totalCount: 200, |
| 135 | 135 |
displayName: "Heart Rate", |
| 136 |
- onAddedTap: { print("Added tapped") },
|
|
| 137 |
- onDisappearedTap: { print("Disappeared tapped") }
|
|
| 136 |
+ onAddedTap: { print("New tapped") },
|
|
| 137 |
+ onDisappearedTap: { print("Missing tapped") }
|
|
| 138 | 138 |
) |
| 139 | 139 |
|
| 140 | 140 |
RecordChangeIndicator( |
@@ -142,8 +142,8 @@ struct RecordChangeIndicator: View {
|
||
| 142 | 142 |
disappearedCount: 0, |
| 143 | 143 |
totalCount: 1000, |
| 144 | 144 |
displayName: "Sleep", |
| 145 |
- onAddedTap: { print("Added tapped") },
|
|
| 146 |
- onDisappearedTap: { print("Disappeared tapped") }
|
|
| 145 |
+ onAddedTap: { print("New tapped") },
|
|
| 146 |
+ onDisappearedTap: { print("Missing tapped") }
|
|
| 147 | 147 |
) |
| 148 | 148 |
} |
| 149 | 149 |
.padding() |
@@ -59,7 +59,7 @@ struct TemporalDistributionChartView: View {
|
||
| 59 | 59 |
Circle() |
| 60 | 60 |
.fill(Color.healthyGreen.opacity(0.7)) |
| 61 | 61 |
.frame(width: 8, height: 8) |
| 62 |
- Text("Added")
|
|
| 62 |
+ Text("New")
|
|
| 63 | 63 |
.font(.caption) |
| 64 | 64 |
.foregroundStyle(.secondary) |
| 65 | 65 |
} |
@@ -68,7 +68,7 @@ struct TemporalDistributionChartView: View {
|
||
| 68 | 68 |
Circle() |
| 69 | 69 |
.fill(Color.criticalRed.opacity(0.7)) |
| 70 | 70 |
.frame(width: 8, height: 8) |
| 71 |
- Text("Disappeared")
|
|
| 71 |
+ Text("Missing")
|
|
| 72 | 72 |
.font(.caption) |
| 73 | 73 |
.foregroundStyle(.secondary) |
| 74 | 74 |
} |
@@ -233,11 +233,11 @@ struct TemporalDistributionChartView: View {
|
||
| 233 | 233 |
Divider() |
| 234 | 234 |
|
| 235 | 235 |
HStack {
|
| 236 |
- detailMetric("Added", bin.added, Color.healthyGreen)
|
|
| 236 |
+ detailMetric("New", bin.added, Color.healthyGreen)
|
|
| 237 | 237 |
Spacer() |
| 238 | 238 |
detailMetric("Unchanged", bin.unchanged, Color.neutralGray)
|
| 239 | 239 |
Spacer() |
| 240 |
- detailMetric("Disappeared", bin.disappeared, Color.criticalRed)
|
|
| 240 |
+ detailMetric("Missing", bin.disappeared, Color.criticalRed)
|
|
| 241 | 241 |
} |
| 242 | 242 |
} |
| 243 | 243 |
.padding(12) |
@@ -204,7 +204,7 @@ private struct DataTypeDetailRow<Content: View>: View {
|
||
| 204 | 204 |
#Preview {
|
| 205 | 205 |
NavigationStack {
|
| 206 | 206 |
DataTypeRecordListView( |
| 207 |
- title: "Disappeared Records", |
|
| 207 |
+ title: "Missing Records", |
|
| 208 | 208 |
displayName: "Step Count", |
| 209 | 209 |
totalCount: 1500, |
| 210 | 210 |
mode: .disappeared(typeIdentifier: "HKQuantityTypeIdentifierStepCount"), |
@@ -267,7 +267,7 @@ struct DataTypeSnapshotDetailView: View {
|
||
| 267 | 267 |
.navigationDestination(isPresented: $showAddedRecords) {
|
| 268 | 268 |
if let previous = previousSnapshot {
|
| 269 | 269 |
DataTypeRecordListView( |
| 270 |
- title: "Added Records", |
|
| 270 |
+ title: "New Records", |
|
| 271 | 271 |
displayName: displayName, |
| 272 | 272 |
totalCount: diff.addedCount, |
| 273 | 273 |
mode: addedRecordListMode(previous: previous), |
@@ -278,7 +278,7 @@ struct DataTypeSnapshotDetailView: View {
|
||
| 278 | 278 |
} |
| 279 | 279 |
.navigationDestination(isPresented: $showDisappearedRecords) {
|
| 280 | 280 |
DataTypeRecordListView( |
| 281 |
- title: "Disappeared Records", |
|
| 281 |
+ title: "Missing Records", |
|
| 282 | 282 |
displayName: displayName, |
| 283 | 283 |
totalCount: diff.disappearedCount, |
| 284 | 284 |
mode: disappearedRecordListMode(), |
@@ -302,11 +302,11 @@ struct DataTypeSnapshotDetailView: View {
|
||
| 302 | 302 |
|
| 303 | 303 |
HStack {
|
| 304 | 304 |
quickStat(label: "New", value: "\(quick.added)", color: .healthyGreen) |
| 305 |
- quickStat(label: "Disappeared", value: "\(quick.disappeared)", color: .criticalRed) |
|
| 305 |
+ quickStat(label: "Missing", value: "\(quick.disappeared)", color: .criticalRed) |
|
| 306 | 306 |
} |
| 307 | 307 |
|
| 308 | 308 |
if !quick.exact {
|
| 309 |
- Text("New/Disappeared are net values from snapshot delta. Exact split needs deep record analysis.")
|
|
| 309 |
+ Text("New/Missing are net values from observation delta. Exact split needs deep record analysis.")
|
|
| 310 | 310 |
.font(.caption2) |
| 311 | 311 |
.foregroundStyle(.secondary) |
| 312 | 312 |
} |
@@ -403,7 +403,7 @@ struct DataTypeSnapshotDetailView: View {
|
||
| 403 | 403 |
Text("Temporal Distribution")
|
| 404 | 404 |
.font(.headline.weight(.semibold)) |
| 405 | 405 |
.foregroundStyle(.primary) |
| 406 |
- Text("Added / disappeared by time bucket")
|
|
| 406 |
+ Text("New / missing by time bucket")
|
|
| 407 | 407 |
.font(.caption) |
| 408 | 408 |
.foregroundStyle(.secondary) |
| 409 | 409 |
} |
@@ -450,9 +450,9 @@ struct DataTypeSnapshotDetailView: View {
|
||
| 450 | 450 |
let prefix = typeDelta.countDelta > 0 ? "+" : "" |
| 451 | 451 |
return "Count delta: \(prefix)\(typeDelta.countDelta)." |
| 452 | 452 |
case .appeared: |
| 453 |
- return "Metric appeared in this snapshot." |
|
| 453 |
+ return "Metric is new in this observation." |
|
| 454 | 454 |
case .disappeared: |
| 455 |
- return "Metric disappeared in this snapshot." |
|
| 455 |
+ return "Metric is missing from this observation." |
|
| 456 | 456 |
} |
| 457 | 457 |
} |
| 458 | 458 |
|
@@ -522,10 +522,10 @@ private struct SnapshotArchiveTypeRowView: View {
|
||
| 522 | 522 |
private var changeLabel: String {
|
| 523 | 523 |
guard hasBaseline else { return "Stored" }
|
| 524 | 524 |
if row.disappearedCount > 0 {
|
| 525 |
- return "\(row.disappearedCount) disappeared" |
|
| 525 |
+ return "\(row.disappearedCount) missing" |
|
| 526 | 526 |
} |
| 527 | 527 |
if row.appearedCount > 0 {
|
| 528 |
- return "\(row.appearedCount) added" |
|
| 528 |
+ return "\(row.appearedCount) new" |
|
| 529 | 529 |
} |
| 530 | 530 |
if row.representationChangedCount > 0 {
|
| 531 | 531 |
return "\(row.representationChangedCount) changed" |
@@ -583,9 +583,9 @@ private struct SnapshotTypeDeltaRow: View {
|
||
| 583 | 583 |
let prefix = typeDelta.countDelta > 0 ? "+" : "" |
| 584 | 584 |
return "\(prefix)\(typeDelta.countDelta) records" |
| 585 | 585 |
case .appeared: |
| 586 |
- return "Appeared" |
|
| 586 |
+ return "New" |
|
| 587 | 587 |
case .disappeared: |
| 588 |
- return "Disappeared" |
|
| 588 |
+ return "Missing" |
|
| 589 | 589 |
case .unchanged: |
| 590 | 590 |
return "No changes" |
| 591 | 591 |
} |
@@ -263,8 +263,8 @@ private struct SnapshotRow: View {
|
||
| 263 | 263 |
guard total > 0 else { return "No record changes" }
|
| 264 | 264 |
|
| 265 | 265 |
var parts: [String] = [] |
| 266 |
- if appeared > 0 { parts.append("\(appeared) added") }
|
|
| 267 |
- if disappeared > 0 { parts.append("\(disappeared) disappeared") }
|
|
| 266 |
+ if appeared > 0 { parts.append("\(appeared) new") }
|
|
| 267 |
+ if disappeared > 0 { parts.append("\(disappeared) missing") }
|
|
| 268 | 268 |
if changed > 0 { parts.append("\(changed) changed") }
|
| 269 | 269 |
return parts.joined(separator: " • ") |
| 270 | 270 |
} |
@@ -282,11 +282,11 @@ private struct SnapshotRow: View {
|
||
| 282 | 282 |
} |
| 283 | 283 |
|
| 284 | 284 |
if deltaSummary.appearedMetricCount > 0 {
|
| 285 |
- parts.append("\(deltaSummary.appearedMetricCount) metric appeared")
|
|
| 285 |
+ parts.append("\(deltaSummary.appearedMetricCount) metric new")
|
|
| 286 | 286 |
} |
| 287 | 287 |
|
| 288 | 288 |
if deltaSummary.disappearedMetricCount > 0 {
|
| 289 |
- parts.append("\(deltaSummary.disappearedMetricCount) metric disappeared")
|
|
| 289 |
+ parts.append("\(deltaSummary.disappearedMetricCount) metric missing")
|
|
| 290 | 290 |
} |
| 291 | 291 |
|
| 292 | 292 |
if parts.isEmpty {
|
@@ -345,7 +345,7 @@ private struct SnapshotRow: View {
|
||
| 345 | 345 |
Image(systemName: "exclamationmark.triangle.fill") |
| 346 | 346 |
.foregroundStyle(Color.warningAmber) |
| 347 | 347 |
.font(.caption) |
| 348 |
- .accessibilityLabel("Has anomalies")
|
|
| 348 |
+ .accessibilityLabel("Has changes to review")
|
|
| 349 | 349 |
} |
| 350 | 350 |
} |
| 351 | 351 |
|