Showing 7 changed files with 234 additions and 156 deletions
+82 -80
USB Meter.xcodeproj/project.pbxproj
@@ -10,17 +10,6 @@
10 10
 		3407A133FADB8858DC2A1FED /* MeterNameStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7396C8BB36F4E7F8E0CD8FF8 /* MeterNameStore.swift */; };
11 11
 		4308CF8624176CAB0002E80B /* DataGroupRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4308CF8524176CAB0002E80B /* DataGroupRowView.swift */; };
12 12
 		4308CF882417770D0002E80B /* DataGroupsSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4308CF872417770D0002E80B /* DataGroupsSheetView.swift */; };
13
-		C10000013C8E4A7A00A10001 /* ChargeInsightsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000113C8E4A7A00A10011 /* ChargeInsightsModel.swift */; };
14
-		C10000023C8E4A7A00A10002 /* ChargeInsightsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000123C8E4A7A00A10012 /* ChargeInsightsStore.swift */; };
15
-		C10000033C8E4A7A00A10003 /* ChargedDeviceQRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000133C8E4A7A00A10013 /* ChargedDeviceQRCodeView.swift */; };
16
-		C10000043C8E4A7A00A10004 /* ChargedDeviceEditorSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000143C8E4A7A00A10014 /* ChargedDeviceEditorSheetView.swift */; };
17
-		C10000053C8E4A7A00A10005 /* ChargedDeviceLibrarySheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000153C8E4A7A00A10015 /* ChargedDeviceLibrarySheetView.swift */; };
18
-		C10000063C8E4A7A00A10006 /* ChargedDeviceDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000163C8E4A7A00A10016 /* ChargedDeviceDetailView.swift */; };
19
-		C10000073C8E4A7A00A10007 /* SidebarChargedDevicesSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000173C8E4A7A00A10017 /* SidebarChargedDevicesSectionView.swift */; };
20
-		C10000083C8E4A7A00A10008 /* BatteryCheckpointEditorSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000183C8E4A7A00A10018 /* BatteryCheckpointEditorSheetView.swift */; };
21
-		C10000093C8E4A7A00A10009 /* CKModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C10000213C8E4A7A00A10021 /* CKModel.xcdatamodeld */; };
22
-		B0A000113C8F000100A10011 /* ChargerStandbyPowerStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0A000013C8F000100A10001 /* ChargerStandbyPowerStore.swift */; };
23
-		B0A000123C8F000100A10012 /* ChargerStandbyPowerWizardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0A000023C8F000100A10002 /* ChargerStandbyPowerWizardView.swift */; };
24 13
 		430CB4FC245E07EB006525C2 /* ChevronView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430CB4FB245E07EB006525C2 /* ChevronView.swift */; };
25 14
 		4311E63A241384960080EA59 /* DeviceHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4311E639241384960080EA59 /* DeviceHelpView.swift */; };
26 15
 		4327461B24619CED0009BE4B /* MeterRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4327461A24619CED0009BE4B /* MeterRowView.swift */; };
@@ -62,12 +51,21 @@
62 51
 		43F7792B2465AE1600745DF4 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F7792A2465AE1600745DF4 /* UIView.swift */; };
63 52
 		AAD5F9A72B1CAC0700F8E4F9 /* MeterDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD5F9A32B1CAC0700F8E4F9 /* MeterDetailView.swift */; };
64 53
 		AAD5F9B12B1CAC7A00F8E4F9 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD5F9B22B1CAC7A00F8E4F9 /* SidebarView.swift */; };
54
+		B0A000113C8F000100A10011 /* ChargerStandbyPowerStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0A000013C8F000100A10001 /* ChargerStandbyPowerStore.swift */; };
55
+		B0A000123C8F000100A10012 /* ChargerStandbyPowerWizardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0A000023C8F000100A10002 /* ChargerStandbyPowerWizardView.swift */; };
56
+		C10000013C8E4A7A00A10001 /* ChargeInsightsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000113C8E4A7A00A10011 /* ChargeInsightsModel.swift */; };
57
+		C10000023C8E4A7A00A10002 /* ChargeInsightsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000123C8E4A7A00A10012 /* ChargeInsightsStore.swift */; };
58
+		C10000033C8E4A7A00A10003 /* ChargedDeviceQRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000133C8E4A7A00A10013 /* ChargedDeviceQRCodeView.swift */; };
59
+		C10000043C8E4A7A00A10004 /* ChargedDeviceEditorSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000143C8E4A7A00A10014 /* ChargedDeviceEditorSheetView.swift */; };
60
+		C10000053C8E4A7A00A10005 /* ChargedDeviceLibrarySheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000153C8E4A7A00A10015 /* ChargedDeviceLibrarySheetView.swift */; };
61
+		C10000063C8E4A7A00A10006 /* ChargedDeviceDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000163C8E4A7A00A10016 /* ChargedDeviceDetailView.swift */; };
62
+		C10000073C8E4A7A00A10007 /* SidebarChargedDevicesSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000173C8E4A7A00A10017 /* SidebarChargedDevicesSectionView.swift */; };
63
+		C10000083C8E4A7A00A10008 /* BatteryCheckpointEditorSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10000183C8E4A7A00A10018 /* BatteryCheckpointEditorSheetView.swift */; };
64
+		C10000093C8E4A7A00A10009 /* CKModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C10000213C8E4A7A00A10021 /* CKModel.xcdatamodeld */; };
65 65
 		D28F11013C8E4A7A00A10011 /* MeterHomeTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11023C8E4A7A00A10012 /* MeterHomeTabView.swift */; };
66 66
 		D28F11033C8E4A7A00A10013 /* MeterLiveTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11043C8E4A7A00A10014 /* MeterLiveTabView.swift */; };
67 67
 		D28F11053C8E4A7A00A10015 /* MeterChartTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11063C8E4A7A00A10016 /* MeterChartTabView.swift */; };
68 68
 		D28F11073C8E4A7A00A10017 /* MeterSettingsTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11083C8E4A7A00A10018 /* MeterSettingsTabView.swift */; };
69
-		D28F11413C8E4A7A00A10051 /* MeterChargeRecordTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11423C8E4A7A00A10052 /* MeterChargeRecordTabView.swift */; };
70
-		D28F11433C8E4A7A00A10053 /* MeterDataGroupsTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11443C8E4A7A00A10054 /* MeterDataGroupsTabView.swift */; };
71 69
 		D28F11113C8E4A7A00A10021 /* MeterInfoCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11123C8E4A7A00A10022 /* MeterInfoCardView.swift */; };
72 70
 		D28F11133C8E4A7A00A10023 /* MeterInfoRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11143C8E4A7A00A10024 /* MeterInfoRowView.swift */; };
73 71
 		D28F11153C8E4A7A00A10025 /* MeterNameEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11163C8E4A7A00A10026 /* MeterNameEditorView.swift */; };
@@ -81,6 +79,8 @@
81 79
 		D28F11393C8E4A7A00A10049 /* MeterConnectionStatusBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F113A3C8E4A7A00A1004A /* MeterConnectionStatusBadgeView.swift */; };
82 80
 		D28F113B3C8E4A7A00A1004B /* MeterConnectionActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F113C3C8E4A7A00A1004C /* MeterConnectionActionView.swift */; };
83 81
 		D28F113D3C8E4A7A00A1004D /* MeterOverviewSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F113E3C8E4A7A00A1004E /* MeterOverviewSectionView.swift */; };
82
+		D28F11413C8E4A7A00A10051 /* MeterChargeRecordTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11423C8E4A7A00A10052 /* MeterChargeRecordTabView.swift */; };
83
+		D28F11433C8E4A7A00A10053 /* MeterDataGroupsTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28F11443C8E4A7A00A10054 /* MeterDataGroupsTabView.swift */; };
84 84
 		E430FB6B7CB3E0D4189F6D7D /* MeterMappingDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56BA4CE53B6B2C4EBA42C81A /* MeterMappingDebugView.swift */; };
85 85
 /* End PBXBuildFile section */
86 86
 
@@ -118,23 +118,6 @@
118 118
 		1C6B6BB42A2D4F5100A0B001 /* HM-10 and DX-BT18 Module Working Summary.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "HM-10 and DX-BT18 Module Working Summary.md"; sourceTree = "<group>"; };
119 119
 		4308CF8524176CAB0002E80B /* DataGroupRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataGroupRowView.swift; sourceTree = "<group>"; };
120 120
 		4308CF872417770D0002E80B /* DataGroupsSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataGroupsSheetView.swift; sourceTree = "<group>"; };
121
-		C10000113C8E4A7A00A10011 /* ChargeInsightsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargeInsightsModel.swift; sourceTree = "<group>"; };
122
-		C10000123C8E4A7A00A10012 /* ChargeInsightsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargeInsightsStore.swift; sourceTree = "<group>"; };
123
-		C10000133C8E4A7A00A10013 /* ChargedDeviceQRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargedDeviceQRCodeView.swift; sourceTree = "<group>"; };
124
-		C10000143C8E4A7A00A10014 /* ChargedDeviceEditorSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargedDeviceEditorSheetView.swift; sourceTree = "<group>"; };
125
-		C10000153C8E4A7A00A10015 /* ChargedDeviceLibrarySheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargedDeviceLibrarySheetView.swift; sourceTree = "<group>"; };
126
-		C10000163C8E4A7A00A10016 /* ChargedDeviceDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargedDeviceDetailView.swift; sourceTree = "<group>"; };
127
-		C10000173C8E4A7A00A10017 /* SidebarChargedDevicesSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarChargedDevicesSectionView.swift; sourceTree = "<group>"; };
128
-		C10000183C8E4A7A00A10018 /* BatteryCheckpointEditorSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryCheckpointEditorSheetView.swift; sourceTree = "<group>"; };
129
-		C10000193C8E4A7A00A10019 /* USB_Meter 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "USB_Meter 4.xcdatamodel"; sourceTree = "<group>"; };
130
-		C100001A3C8E4A7A00A1001A /* USB_Meter 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "USB_Meter 5.xcdatamodel"; sourceTree = "<group>"; };
131
-		C100001B3C8E4A7A00A1001B /* USB_Meter 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "USB_Meter 6.xcdatamodel"; sourceTree = "<group>"; };
132
-		C100001C3C8E4A7A00A1001C /* USB_Meter 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "USB_Meter 7.xcdatamodel"; sourceTree = "<group>"; };
133
-		C100001D3C8E4A7A00A1001D /* USB_Meter 8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "USB_Meter 8.xcdatamodel"; sourceTree = "<group>"; };
134
-		C100001E3C8E4A7A00A1001E /* USB_Meter 9.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "USB_Meter 9.xcdatamodel"; sourceTree = "<group>"; };
135
-		C10000223C8E4A7A00A10022 /* USB_Meter 10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "USB_Meter 10.xcdatamodel"; sourceTree = "<group>"; };
136
-		B0A000013C8F000100A10001 /* ChargerStandbyPowerStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargerStandbyPowerStore.swift; sourceTree = "<group>"; };
137
-		B0A000023C8F000100A10002 /* ChargerStandbyPowerWizardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargerStandbyPowerWizardView.swift; sourceTree = "<group>"; };
138 121
 		430CB4FB245E07EB006525C2 /* ChevronView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChevronView.swift; sourceTree = "<group>"; };
139 122
 		4311E639241384960080EA59 /* DeviceHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceHelpView.swift; sourceTree = "<group>"; };
140 123
 		4327461A24619CED0009BE4B /* MeterRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterRowView.swift; sourceTree = "<group>"; };
@@ -180,12 +163,27 @@
180 163
 		7396C8BB36F4E7F8E0CD8FF8 /* MeterNameStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterNameStore.swift; sourceTree = "<group>"; };
181 164
 		AAD5F9A32B1CAC0700F8E4F9 /* MeterDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterDetailView.swift; sourceTree = "<group>"; };
182 165
 		AAD5F9B22B1CAC7A00F8E4F9 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = "<group>"; };
166
+		B0A000013C8F000100A10001 /* ChargerStandbyPowerStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargerStandbyPowerStore.swift; sourceTree = "<group>"; };
167
+		B0A000023C8F000100A10002 /* ChargerStandbyPowerWizardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargerStandbyPowerWizardView.swift; sourceTree = "<group>"; };
168
+		C10000113C8E4A7A00A10011 /* ChargeInsightsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargeInsightsModel.swift; sourceTree = "<group>"; };
169
+		C10000123C8E4A7A00A10012 /* ChargeInsightsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargeInsightsStore.swift; sourceTree = "<group>"; };
170
+		C10000133C8E4A7A00A10013 /* ChargedDeviceQRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargedDeviceQRCodeView.swift; sourceTree = "<group>"; };
171
+		C10000143C8E4A7A00A10014 /* ChargedDeviceEditorSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargedDeviceEditorSheetView.swift; sourceTree = "<group>"; };
172
+		C10000153C8E4A7A00A10015 /* ChargedDeviceLibrarySheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargedDeviceLibrarySheetView.swift; sourceTree = "<group>"; };
173
+		C10000163C8E4A7A00A10016 /* ChargedDeviceDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChargedDeviceDetailView.swift; sourceTree = "<group>"; };
174
+		C10000173C8E4A7A00A10017 /* SidebarChargedDevicesSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarChargedDevicesSectionView.swift; sourceTree = "<group>"; };
175
+		C10000183C8E4A7A00A10018 /* BatteryCheckpointEditorSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryCheckpointEditorSheetView.swift; sourceTree = "<group>"; };
176
+		C10000193C8E4A7A00A10019 /* USB_Meter 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "USB_Meter 4.xcdatamodel"; sourceTree = "<group>"; };
177
+		C100001A3C8E4A7A00A1001A /* USB_Meter 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "USB_Meter 5.xcdatamodel"; sourceTree = "<group>"; };
178
+		C100001B3C8E4A7A00A1001B /* USB_Meter 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "USB_Meter 6.xcdatamodel"; sourceTree = "<group>"; };
179
+		C100001C3C8E4A7A00A1001C /* USB_Meter 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "USB_Meter 7.xcdatamodel"; sourceTree = "<group>"; };
180
+		C100001D3C8E4A7A00A1001D /* USB_Meter 8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "USB_Meter 8.xcdatamodel"; sourceTree = "<group>"; };
181
+		C100001E3C8E4A7A00A1001E /* USB_Meter 9.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "USB_Meter 9.xcdatamodel"; sourceTree = "<group>"; };
182
+		C10000223C8E4A7A00A10022 /* USB_Meter 10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "USB_Meter 10.xcdatamodel"; sourceTree = "<group>"; };
183 183
 		D28F11023C8E4A7A00A10012 /* MeterHomeTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterHomeTabView.swift; sourceTree = "<group>"; };
184 184
 		D28F11043C8E4A7A00A10014 /* MeterLiveTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterLiveTabView.swift; sourceTree = "<group>"; };
185 185
 		D28F11063C8E4A7A00A10016 /* MeterChartTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterChartTabView.swift; sourceTree = "<group>"; };
186 186
 		D28F11083C8E4A7A00A10018 /* MeterSettingsTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterSettingsTabView.swift; sourceTree = "<group>"; };
187
-		D28F11423C8E4A7A00A10052 /* MeterChargeRecordTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterChargeRecordTabView.swift; sourceTree = "<group>"; };
188
-		D28F11443C8E4A7A00A10054 /* MeterDataGroupsTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterDataGroupsTabView.swift; sourceTree = "<group>"; };
189 187
 		D28F11123C8E4A7A00A10022 /* MeterInfoCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterInfoCardView.swift; sourceTree = "<group>"; };
190 188
 		D28F11143C8E4A7A00A10024 /* MeterInfoRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterInfoRowView.swift; sourceTree = "<group>"; };
191 189
 		D28F11163C8E4A7A00A10026 /* MeterNameEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterNameEditorView.swift; sourceTree = "<group>"; };
@@ -199,6 +197,8 @@
199 197
 		D28F113A3C8E4A7A00A1004A /* MeterConnectionStatusBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterConnectionStatusBadgeView.swift; sourceTree = "<group>"; };
200 198
 		D28F113C3C8E4A7A00A1004C /* MeterConnectionActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterConnectionActionView.swift; sourceTree = "<group>"; };
201 199
 		D28F113E3C8E4A7A00A1004E /* MeterOverviewSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterOverviewSectionView.swift; sourceTree = "<group>"; };
200
+		D28F11423C8E4A7A00A10052 /* MeterChargeRecordTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterChargeRecordTabView.swift; sourceTree = "<group>"; };
201
+		D28F11443C8E4A7A00A10054 /* MeterDataGroupsTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeterDataGroupsTabView.swift; sourceTree = "<group>"; };
202 202
 /* End PBXFileReference section */
203 203
 
204 204
 /* Begin PBXFileSystemSynchronizedRootGroup section */
@@ -454,19 +454,6 @@
454 454
 			path = Views;
455 455
 			sourceTree = "<group>";
456 456
 		};
457
-		C10000203C8E4A7A00A10020 /* ChargedDevices */ = {
458
-			isa = PBXGroup;
459
-			children = (
460
-				C10000163C8E4A7A00A10016 /* ChargedDeviceDetailView.swift */,
461
-				C10000143C8E4A7A00A10014 /* ChargedDeviceEditorSheetView.swift */,
462
-				C10000153C8E4A7A00A10015 /* ChargedDeviceLibrarySheetView.swift */,
463
-				C10000133C8E4A7A00A10013 /* ChargedDeviceQRCodeView.swift */,
464
-				C10000183C8E4A7A00A10018 /* BatteryCheckpointEditorSheetView.swift */,
465
-				C10000173C8E4A7A00A10017 /* SidebarChargedDevicesSectionView.swift */,
466
-			);
467
-			path = ChargedDevices;
468
-			sourceTree = "<group>";
469
-		};
470 457
 		43CBF67F240D14AC00255B8B /* Extensions */ = {
471 458
 			isa = PBXGroup;
472 459
 			children = (
@@ -492,6 +479,19 @@
492 479
 			path = Sidebar;
493 480
 			sourceTree = "<group>";
494 481
 		};
482
+		C10000203C8E4A7A00A10020 /* ChargedDevices */ = {
483
+			isa = PBXGroup;
484
+			children = (
485
+				C10000163C8E4A7A00A10016 /* ChargedDeviceDetailView.swift */,
486
+				C10000143C8E4A7A00A10014 /* ChargedDeviceEditorSheetView.swift */,
487
+				C10000153C8E4A7A00A10015 /* ChargedDeviceLibrarySheetView.swift */,
488
+				C10000133C8E4A7A00A10013 /* ChargedDeviceQRCodeView.swift */,
489
+				C10000183C8E4A7A00A10018 /* BatteryCheckpointEditorSheetView.swift */,
490
+				C10000173C8E4A7A00A10017 /* SidebarChargedDevicesSectionView.swift */,
491
+			);
492
+			path = ChargedDevices;
493
+			sourceTree = "<group>";
494
+		};
495 495
 		D28F10013C8E4A7A00A10001 /* Sheets */ = {
496 496
 			isa = PBXGroup;
497 497
 			children = (
@@ -568,22 +568,6 @@
568 568
 			path = Settings;
569 569
 			sourceTree = "<group>";
570 570
 		};
571
-		D28F11453C8E4A7A00A10055 /* ChargeRecord */ = {
572
-			isa = PBXGroup;
573
-			children = (
574
-				D28F11423C8E4A7A00A10052 /* MeterChargeRecordTabView.swift */,
575
-			);
576
-			path = ChargeRecord;
577
-			sourceTree = "<group>";
578
-		};
579
-		D28F11463C8E4A7A00A10056 /* DataGroups */ = {
580
-			isa = PBXGroup;
581
-			children = (
582
-				D28F11443C8E4A7A00A10054 /* MeterDataGroupsTabView.swift */,
583
-			);
584
-			path = DataGroups;
585
-			sourceTree = "<group>";
586
-		};
587 571
 		D28F111B3C8E4A7A00A1002B /* Subviews */ = {
588 572
 			isa = PBXGroup;
589 573
 			children = (
@@ -652,6 +636,22 @@
652 636
 			path = Components;
653 637
 			sourceTree = "<group>";
654 638
 		};
639
+		D28F11453C8E4A7A00A10055 /* ChargeRecord */ = {
640
+			isa = PBXGroup;
641
+			children = (
642
+				D28F11423C8E4A7A00A10052 /* MeterChargeRecordTabView.swift */,
643
+			);
644
+			path = ChargeRecord;
645
+			sourceTree = "<group>";
646
+		};
647
+		D28F11463C8E4A7A00A10056 /* DataGroups */ = {
648
+			isa = PBXGroup;
649
+			children = (
650
+				D28F11443C8E4A7A00A10054 /* MeterDataGroupsTabView.swift */,
651
+			);
652
+			path = DataGroups;
653
+			sourceTree = "<group>";
654
+		};
655 655
 /* End PBXGroup section */
656 656
 
657 657
 /* Begin PBXNativeTarget section */
@@ -828,25 +828,6 @@
828 828
 		};
829 829
 /* End PBXVariantGroup section */
830 830
 
831
-/* Begin XCVersionGroup section */
832
-		C10000213C8E4A7A00A10021 /* CKModel.xcdatamodeld */ = {
833
-			isa = XCVersionGroup;
834
-			children = (
835
-				C10000193C8E4A7A00A10019 /* USB_Meter 4.xcdatamodel */,
836
-				C100001A3C8E4A7A00A1001A /* USB_Meter 5.xcdatamodel */,
837
-				C100001B3C8E4A7A00A1001B /* USB_Meter 6.xcdatamodel */,
838
-				C100001C3C8E4A7A00A1001C /* USB_Meter 7.xcdatamodel */,
839
-				C100001D3C8E4A7A00A1001D /* USB_Meter 8.xcdatamodel */,
840
-				C100001E3C8E4A7A00A1001E /* USB_Meter 9.xcdatamodel */,
841
-				C10000223C8E4A7A00A10022 /* USB_Meter 10.xcdatamodel */,
842
-			);
843
-			currentVersion = C10000223C8E4A7A00A10022 /* USB_Meter 10.xcdatamodel */;
844
-			path = CKModel.xcdatamodeld;
845
-			sourceTree = "<group>";
846
-			versionGroupType = wrapper.xcdatamodel;
847
-		};
848
-/* End XCVersionGroup section */
849
-
850 831
 /* Begin XCBuildConfiguration section */
851 832
 		43CBF671240BF3ED00255B8B /* Debug */ = {
852 833
 			isa = XCBuildConfiguration;
@@ -981,6 +962,7 @@
981 962
 				ENABLE_PREVIEWS = YES;
982 963
 				ENABLE_RESOURCE_ACCESS_BLUETOOTH = YES;
983 964
 				INFOPLIST_FILE = "USB Meter/Info.plist";
965
+				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
984 966
 				LD_RUNPATH_SEARCH_PATHS = (
985 967
 					"$(inherited)",
986 968
 					"@executable_path/Frameworks",
@@ -1008,6 +990,7 @@
1008 990
 				ENABLE_PREVIEWS = YES;
1009 991
 				ENABLE_RESOURCE_ACCESS_BLUETOOTH = YES;
1010 992
 				INFOPLIST_FILE = "USB Meter/Info.plist";
993
+				INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
1011 994
 				LD_RUNPATH_SEARCH_PATHS = (
1012 995
 					"$(inherited)",
1013 996
 					"@executable_path/Frameworks",
@@ -1065,6 +1048,25 @@
1065 1048
 			productName = CryptoSwift;
1066 1049
 		};
1067 1050
 /* End XCSwiftPackageProductDependency section */
1051
+
1052
+/* Begin XCVersionGroup section */
1053
+		C10000213C8E4A7A00A10021 /* CKModel.xcdatamodeld */ = {
1054
+			isa = XCVersionGroup;
1055
+			children = (
1056
+				C10000193C8E4A7A00A10019 /* USB_Meter 4.xcdatamodel */,
1057
+				C100001A3C8E4A7A00A1001A /* USB_Meter 5.xcdatamodel */,
1058
+				C100001B3C8E4A7A00A1001B /* USB_Meter 6.xcdatamodel */,
1059
+				C100001C3C8E4A7A00A1001C /* USB_Meter 7.xcdatamodel */,
1060
+				C100001D3C8E4A7A00A1001D /* USB_Meter 8.xcdatamodel */,
1061
+				C100001E3C8E4A7A00A1001E /* USB_Meter 9.xcdatamodel */,
1062
+				C10000223C8E4A7A00A10022 /* USB_Meter 10.xcdatamodel */,
1063
+			);
1064
+			currentVersion = C10000223C8E4A7A00A10022 /* USB_Meter 10.xcdatamodel */;
1065
+			path = CKModel.xcdatamodeld;
1066
+			sourceTree = "<group>";
1067
+			versionGroupType = wrapper.xcdatamodel;
1068
+		};
1069
+/* End XCVersionGroup section */
1068 1070
 	};
1069 1071
 	rootObject = 43CBF654240BF3EB00255B8B /* Project object */;
1070 1072
 }
+39 -13
USB Meter/Model/AppData.swift
@@ -497,15 +497,10 @@ final class AppData : ObservableObject {
497 497
     }
498 498
 
499 499
     @discardableResult
500
-    func stopChargeSession(
501
-        sessionID: UUID,
502
-        finalBatteryPercent: Double,
503
-        label: String? = "Final"
504
-    ) -> Bool {
500
+    func stopChargeSession(sessionID: UUID, finalBatteryPercent: Double) -> Bool {
505 501
         let didSave = chargeInsightsStore?.stopSession(
506 502
             id: sessionID,
507
-            finalBatteryPercent: finalBatteryPercent,
508
-            label: label
503
+            finalBatteryPercent: finalBatteryPercent
509 504
         ) ?? false
510 505
         if didSave {
511 506
             reloadChargedDevices()
@@ -524,7 +519,7 @@ final class AppData : ObservableObject {
524 519
     }
525 520
 
526 521
     @discardableResult
527
-    func addBatteryCheckpoint(percent: Double, label: String?, for meter: Meter) -> Bool {
522
+    func addBatteryCheckpoint(percent: Double, for meter: Meter) -> Bool {
528 523
         observeChargeSnapshot(from: meter)
529 524
 
530 525
         let activeSession = activeChargeSessionSummary(for: meter.btSerial.macAddress.description)
@@ -533,7 +528,6 @@ final class AppData : ObservableObject {
533 528
 
534 529
         let didSave = chargeInsightsStore?.addBatteryCheckpoint(
535 530
             percent: percent,
536
-            label: label,
537 531
             for: meter.btSerial.macAddress.description,
538 532
             measuredEnergyWh: checkpointEnergyWh,
539 533
             measuredChargeAh: checkpointChargeAh
@@ -547,10 +541,13 @@ final class AppData : ObservableObject {
547 541
     }
548 542
 
549 543
     @discardableResult
550
-    func addBatteryCheckpoint(percent: Double, label: String?, for sessionID: UUID) -> Bool {
544
+    func addBatteryCheckpoint(percent: Double, for sessionID: UUID) -> Bool {
545
+        guard canAddBatteryCheckpoint(to: sessionID) else {
546
+            return false
547
+        }
548
+
551 549
         let didSave = chargeInsightsStore?.addBatteryCheckpoint(
552 550
             percent: percent,
553
-            label: label,
554 551
             for: sessionID
555 552
         ) ?? false
556 553
 
@@ -564,14 +561,16 @@ final class AppData : ObservableObject {
564 561
     @discardableResult
565 562
     func addBatteryCheckpoint(
566 563
         percent: Double,
567
-        label: String?,
568 564
         for sessionID: UUID,
569 565
         measuredEnergyWh: Double?,
570 566
         measuredChargeAh: Double?
571 567
     ) -> Bool {
568
+        guard canAddBatteryCheckpoint(to: sessionID) else {
569
+            return false
570
+        }
571
+
572 572
         let didSave = chargeInsightsStore?.addBatteryCheckpoint(
573 573
             percent: percent,
574
-            label: label,
575 574
             for: sessionID,
576 575
             measuredEnergyWh: measuredEnergyWh,
577 576
             measuredChargeAh: measuredChargeAh
@@ -584,6 +583,33 @@ final class AppData : ObservableObject {
584 583
         return didSave
585 584
     }
586 585
 
586
+    func canAddBatteryCheckpoint(to sessionID: UUID) -> Bool {
587
+        guard let session = chargeSessionSummary(id: sessionID),
588
+              session.status.isOpen,
589
+              let meterMACAddress = session.meterMACAddress else {
590
+            return false
591
+        }
592
+
593
+        return meter(for: meterMACAddress) != nil
594
+    }
595
+
596
+    func batteryCheckpointCaptureRequirementMessage(for sessionID: UUID) -> String? {
597
+        guard let session = chargeSessionSummary(id: sessionID) else {
598
+            return "Battery checkpoints are available only while the charge session is still active."
599
+        }
600
+
601
+        guard session.status.isOpen else {
602
+            return "Battery checkpoints are available only while the charge session is still active."
603
+        }
604
+
605
+        guard let meterMACAddress = session.meterMACAddress,
606
+              meter(for: meterMACAddress) != nil else {
607
+            return "Add battery checkpoints only on the device that is actively monitoring this charging session. Devices following the session through iCloud may not have data that is fresh or precise enough."
608
+        }
609
+
610
+        return nil
611
+    }
612
+
587 613
     func batteryCheckpointPlausibilityWarning(
588 614
         percent: Double,
589 615
         for sessionID: UUID,
+50 -2
USB Meter/Model/ChargeInsightsModel.swift
@@ -322,6 +322,55 @@ struct ChargeCheckpointSummary: Identifiable, Hashable {
322 322
     let currentAmps: Double
323 323
     let voltageVolts: Double?
324 324
     let label: String?
325
+
326
+    var flag: ChargeCheckpointFlag {
327
+        ChargeCheckpointFlag.fromStoredLabel(label)
328
+    }
329
+}
330
+
331
+enum ChargeCheckpointFlag: String, CaseIterable {
332
+    case initial
333
+    case intermediate
334
+    case final
335
+
336
+    var title: String {
337
+        switch self {
338
+        case .initial:
339
+            return "Initial"
340
+        case .intermediate:
341
+            return "Intermediate"
342
+        case .final:
343
+            return "Final"
344
+        }
345
+    }
346
+
347
+    var anchorDescription: String {
348
+        switch self {
349
+        case .initial:
350
+            return "initial checkpoint"
351
+        case .intermediate:
352
+            return "intermediate checkpoint"
353
+        case .final:
354
+            return "final checkpoint"
355
+        }
356
+    }
357
+
358
+    static func fromStoredLabel(_ label: String?) -> ChargeCheckpointFlag {
359
+        let normalized = label?
360
+            .trimmingCharacters(in: .whitespacesAndNewlines)
361
+            .lowercased()
362
+
363
+        switch normalized {
364
+        case "initial", "start":
365
+            return .initial
366
+        case "final", "end":
367
+            return .final
368
+        case "intermediate", nil, "":
369
+            return .intermediate
370
+        default:
371
+            return .intermediate
372
+        }
373
+    }
325 374
 }
326 375
 
327 376
 struct ChargeSessionSampleSummary: Identifiable, Hashable {
@@ -1000,12 +1049,11 @@ struct ChargedDeviceSummary: Identifiable, Hashable {
1000 1049
                     checkpoint.batteryPercent >= 0
1001 1050
                 }
1002 1051
                 .map { checkpoint in
1003
-                    let trimmedLabel = checkpoint.label?.trimmingCharacters(in: .whitespacesAndNewlines)
1004 1052
                     return Anchor(
1005 1053
                         percent: checkpoint.batteryPercent,
1006 1054
                         energyWh: checkpoint.measuredEnergyWh,
1007 1055
                         timestamp: checkpoint.timestamp,
1008
-                        description: trimmedLabel.map { "checkpoint \($0)" } ?? "last checkpoint",
1056
+                        description: checkpoint.flag.anchorDescription,
1009 1057
                         isCheckpoint: true
1010 1058
                     )
1011 1059
                 }
+11 -20
USB Meter/Model/ChargeInsightsStore.swift
@@ -414,7 +414,7 @@ final class ChargeInsightsStore {
414 414
             } else if let initialBatteryPercent {
415 415
                 guard insertBatteryCheckpoint(
416 416
                     percent: initialBatteryPercent,
417
-                    label: "Start",
417
+                    flag: .initial,
418 418
                     timestamp: snapshot.observedAt,
419 419
                     to: session
420 420
                 ) != nil else {
@@ -467,7 +467,6 @@ final class ChargeInsightsStore {
467 467
                     session,
468 468
                     observedAt: pausedAt.addingTimeInterval(pausedSessionTimeout),
469 469
                     finalBatteryPercent: nil,
470
-                    label: nil,
471 470
                     status: .completed
472 471
                 )
473 472
                 guard saveContext() else {
@@ -508,8 +507,7 @@ final class ChargeInsightsStore {
508 507
     @discardableResult
509 508
     func stopSession(
510 509
         id sessionID: UUID,
511
-        finalBatteryPercent: Double,
512
-        label: String?
510
+        finalBatteryPercent: Double
513 511
     ) -> Bool {
514 512
         guard finalBatteryPercent.isFinite, finalBatteryPercent >= 0, finalBatteryPercent <= 100 else {
515 513
             return false
@@ -530,7 +528,6 @@ final class ChargeInsightsStore {
530 528
                 session,
531 529
                 observedAt: observedAt,
532 530
                 finalBatteryPercent: finalBatteryPercent,
533
-                label: label,
534 531
                 status: .completed
535 532
             )
536 533
 
@@ -551,7 +548,6 @@ final class ChargeInsightsStore {
551 548
     @discardableResult
552 549
     func addBatteryCheckpoint(
553 550
         percent: Double,
554
-        label: String?,
555 551
         for meterMACAddress: String,
556 552
         measuredEnergyWh: Double? = nil,
557 553
         measuredChargeAh: Double? = nil
@@ -568,9 +564,9 @@ final class ChargeInsightsStore {
568 564
 
569 565
             didSave = addBatteryCheckpoint(
570 566
                 percent: percent,
571
-                label: label,
572 567
                 measuredEnergyWh: measuredEnergyWh,
573 568
                 measuredChargeAh: measuredChargeAh,
569
+                flag: .intermediate,
574 570
                 to: session
575 571
             )
576 572
         }
@@ -580,7 +576,6 @@ final class ChargeInsightsStore {
580 576
     @discardableResult
581 577
     func addBatteryCheckpoint(
582 578
         percent: Double,
583
-        label: String?,
584 579
         for sessionID: UUID,
585 580
         measuredEnergyWh: Double? = nil,
586 581
         measuredChargeAh: Double? = nil
@@ -597,9 +592,9 @@ final class ChargeInsightsStore {
597 592
 
598 593
             didSave = addBatteryCheckpoint(
599 594
                 percent: percent,
600
-                label: label,
601 595
                 measuredEnergyWh: measuredEnergyWh,
602 596
                 measuredChargeAh: measuredChargeAh,
597
+                flag: .intermediate,
603 598
                 to: session
604 599
             )
605 600
         }
@@ -670,7 +665,6 @@ final class ChargeInsightsStore {
670 665
                 session,
671 666
                 observedAt: dateValue(session, key: "lastObservedAt") ?? Date(),
672 667
                 finalBatteryPercent: nil,
673
-                label: nil,
674 668
                 status: .completed
675 669
             )
676 670
 
@@ -1245,7 +1239,6 @@ final class ChargeInsightsStore {
1245 1239
                         session,
1246 1240
                         observedAt: snapshot.observedAt,
1247 1241
                         finalBatteryPercent: nil,
1248
-                        label: nil,
1249 1242
                         status: .completed
1250 1243
                     )
1251 1244
                 }
@@ -1399,7 +1392,6 @@ final class ChargeInsightsStore {
1399 1392
             session,
1400 1393
             observedAt: pausedAt.addingTimeInterval(pausedSessionTimeout),
1401 1394
             finalBatteryPercent: nil,
1402
-            label: nil,
1403 1395
             status: .completed
1404 1396
         )
1405 1397
 
@@ -1447,13 +1439,12 @@ final class ChargeInsightsStore {
1447 1439
         _ session: NSManagedObject,
1448 1440
         observedAt: Date,
1449 1441
         finalBatteryPercent: Double?,
1450
-        label: String?,
1451 1442
         status: ChargeSessionStatus
1452 1443
     ) {
1453 1444
         if let finalBatteryPercent {
1454 1445
             _ = insertBatteryCheckpoint(
1455 1446
                 percent: finalBatteryPercent,
1456
-                label: label,
1447
+                flag: .final,
1457 1448
                 timestamp: observedAt,
1458 1449
                 to: session
1459 1450
             )
@@ -1616,7 +1607,7 @@ final class ChargeInsightsStore {
1616 1607
     @discardableResult
1617 1608
     private func insertBatteryCheckpoint(
1618 1609
         percent: Double,
1619
-        label: String?,
1610
+        flag: ChargeCheckpointFlag,
1620 1611
         timestamp: Date = Date(),
1621 1612
         measuredEnergyWhOverride: Double? = nil,
1622 1613
         measuredChargeAhOverride: Double? = nil,
@@ -1648,7 +1639,7 @@ final class ChargeInsightsStore {
1648 1639
             chargingTransportMode(for: session) == .wired ? optionalDoubleValue(session, key: "lastObservedVoltageVolts") : nil,
1649 1640
             forKey: "voltageVolts"
1650 1641
         )
1651
-        checkpoint.setValue(normalizedOptionalText(label), forKey: "label")
1642
+        checkpoint.setValue(flag.rawValue, forKey: "label")
1652 1643
         checkpoint.setValue(timestamp, forKey: "createdAt")
1653 1644
 
1654 1645
         let existingStartBatteryPercent = optionalDoubleValue(session, key: "startBatteryPercent")
@@ -1686,9 +1677,9 @@ final class ChargeInsightsStore {
1686 1677
     @discardableResult
1687 1678
     private func addBatteryCheckpoint(
1688 1679
         percent: Double,
1689
-        label: String?,
1690 1680
         measuredEnergyWh: Double? = nil,
1691 1681
         measuredChargeAh: Double? = nil,
1682
+        flag: ChargeCheckpointFlag,
1692 1683
         to session: NSManagedObject,
1693 1684
         timestamp: Date = Date()
1694 1685
     ) -> Bool {
@@ -1701,7 +1692,7 @@ final class ChargeInsightsStore {
1701 1692
 
1702 1693
         guard let chargedDeviceID = insertBatteryCheckpoint(
1703 1694
             percent: percent,
1704
-            label: label,
1695
+            flag: flag,
1705 1696
             timestamp: timestamp,
1706 1697
             measuredEnergyWhOverride: measuredEnergyWh,
1707 1698
             measuredChargeAhOverride: measuredChargeAh,
@@ -1894,7 +1885,7 @@ final class ChargeInsightsStore {
1894 1885
                         measuredChargeAh: 0,
1895 1886
                         currentAmps: 0,
1896 1887
                         voltageVolts: nil,
1897
-                        label: "Start"
1888
+                        label: ChargeCheckpointFlag.initial.rawValue
1898 1889
                     )
1899 1890
                 )
1900 1891
             }
@@ -1911,7 +1902,7 @@ final class ChargeInsightsStore {
1911 1902
                         measuredChargeAh: session.measuredChargeAh,
1912 1903
                         currentAmps: 0,
1913 1904
                         voltageVolts: nil,
1914
-                        label: "End"
1905
+                        label: ChargeCheckpointFlag.final.rawValue
1915 1906
                     )
1916 1907
                 )
1917 1908
             }
+0 -5
USB Meter/Views/ChargedDevices/BatteryCheckpointEditorSheetView.swift
@@ -18,7 +18,6 @@ struct BatteryCheckpointEditorContentView: View {
18 18
     let onSaved: (() -> Void)?
19 19
 
20 20
     @State private var batteryPercent = ""
21
-    @State private var label = ""
22 21
     @State private var showsWarningPopover = false
23 22
 
24 23
     private var plausibilityWarning: BatteryCheckpointPlausibilityWarning? {
@@ -83,9 +82,6 @@ struct BatteryCheckpointEditorContentView: View {
83 82
                 TextField("Battery %", text: $batteryPercent)
84 83
                     .keyboardType(.decimalPad)
85 84
                     .textFieldStyle(.roundedBorder)
86
-
87
-                TextField("Label (optional)", text: $label)
88
-                    .textFieldStyle(.roundedBorder)
89 85
             }
90 86
 
91 87
             HStack(spacing: 10) {
@@ -119,7 +115,6 @@ struct BatteryCheckpointEditorContentView: View {
119 115
 
120 116
         if appData.addBatteryCheckpoint(
121 117
             percent: percent,
122
-            label: label,
123 118
             for: sessionID,
124 119
             measuredEnergyWh: effectiveEnergyWhOverride,
125 120
             measuredChargeAh: measuredChargeAhOverride
+25 -16
USB Meter/Views/ChargedDevices/ChargedDeviceDetailView.swift
@@ -442,23 +442,29 @@ struct ChargedDeviceDetailView: View {
442 442
                 )
443 443
             }
444 444
 
445
-            Button(showsInlineCheckpointEditor ? "Hide Checkpoint Editor" : "Add Battery Checkpoint") {
446
-                showsInlineCheckpointEditor.toggle()
447
-            }
448
-            .frame(maxWidth: .infinity)
449
-            .padding(.vertical, 10)
450
-            .meterCard(tint: .green, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
451
-            .buttonStyle(.plain)
445
+            if appData.canAddBatteryCheckpoint(to: activeSession.id) {
446
+                Button(showsInlineCheckpointEditor ? "Hide Checkpoint Editor" : "Add Battery Checkpoint") {
447
+                    showsInlineCheckpointEditor.toggle()
448
+                }
449
+                .frame(maxWidth: .infinity)
450
+                .padding(.vertical, 10)
451
+                .meterCard(tint: .green, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
452
+                .buttonStyle(.plain)
452 453
 
453
-            if showsInlineCheckpointEditor {
454
-                BatteryCheckpointEditorContentView(
455
-                    sessionID: activeSession.id,
456
-                    message: "The checkpoint is stored on the active charge session and is used for capacity estimation and charge-level prediction.",
457
-                    effectiveEnergyWhOverride: nil,
458
-                    measuredChargeAhOverride: nil,
459
-                    onCancel: { showsInlineCheckpointEditor = false },
460
-                    onSaved: { showsInlineCheckpointEditor = false }
461
-                )
454
+                if showsInlineCheckpointEditor {
455
+                    BatteryCheckpointEditorContentView(
456
+                        sessionID: activeSession.id,
457
+                        message: "The checkpoint is stored on the active charge session and is used for capacity estimation and charge-level prediction.",
458
+                        effectiveEnergyWhOverride: nil,
459
+                        measuredChargeAhOverride: nil,
460
+                        onCancel: { showsInlineCheckpointEditor = false },
461
+                        onSaved: { showsInlineCheckpointEditor = false }
462
+                    )
463
+                }
464
+            } else if let checkpointEditingMessage = appData.batteryCheckpointCaptureRequirementMessage(for: activeSession.id) {
465
+                Text(checkpointEditingMessage)
466
+                    .font(.caption2)
467
+                    .foregroundColor(.secondary)
462 468
             }
463 469
 
464 470
             Button(activeSession.targetBatteryPercent == nil ? "Set Target Notification" : "Change Target Notification") {
@@ -569,6 +575,9 @@ struct ChargedDeviceDetailView: View {
569 575
                     Text(checkpoint.timestamp.format())
570 576
                         .font(.caption2)
571 577
                         .foregroundColor(.secondary)
578
+                    Text(checkpoint.flag.title)
579
+                        .font(.caption2.weight(.semibold))
580
+                        .foregroundColor(.secondary)
572 581
                     Spacer()
573 582
                     Text("\(checkpoint.batteryPercent.format(decimalDigits: 0))%")
574 583
                         .font(.caption.weight(.semibold))
+27 -20
USB Meter/Views/Meter/Tabs/ChargeRecord/MeterChargeRecordTabView.swift
@@ -727,6 +727,7 @@ struct MeterChargeRecordContentView: View {
727 727
     private func chargingMonitorCard(_ openChargeSession: ChargeSessionSummary) -> some View {
728 728
         let displayedEnergyWh = displayedSessionEnergyWh(for: openChargeSession)
729 729
         let displayedChargeAh = displayedSessionChargeAh(for: openChargeSession)
730
+        let canAddCheckpoint = appData.canAddBatteryCheckpoint(to: openChargeSession.id)
730 731
         return VStack(alignment: .leading, spacing: 12) {
731 732
             HStack(spacing: 8) {
732 733
                 Text("Charging Monitor")
@@ -800,23 +801,29 @@ struct MeterChargeRecordContentView: View {
800 801
                 )
801 802
             }
802 803
 
803
-            Button(showsInlineCheckpointEditor ? "Hide Checkpoint Editor" : "Add Battery Checkpoint") {
804
-                showsInlineCheckpointEditor.toggle()
805
-            }
806
-            .frame(maxWidth: .infinity)
807
-            .padding(.vertical, 10)
808
-            .meterCard(tint: .green, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
809
-            .buttonStyle(.plain)
804
+            if canAddCheckpoint {
805
+                Button(showsInlineCheckpointEditor ? "Hide Checkpoint Editor" : "Add Battery Checkpoint") {
806
+                    showsInlineCheckpointEditor.toggle()
807
+                }
808
+                .frame(maxWidth: .infinity)
809
+                .padding(.vertical, 10)
810
+                .meterCard(tint: .green, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
811
+                .buttonStyle(.plain)
810 812
 
811
-            if showsInlineCheckpointEditor {
812
-                BatteryCheckpointEditorContentView(
813
-                    sessionID: openChargeSession.id,
814
-                    message: "The checkpoint is stored on the active charge session and later used for capacity estimation and the typical charge curve.",
815
-                    effectiveEnergyWhOverride: displayedEnergyWh,
816
-                    measuredChargeAhOverride: displayedChargeAh,
817
-                    onCancel: { showsInlineCheckpointEditor = false },
818
-                    onSaved: { showsInlineCheckpointEditor = false }
819
-                )
813
+                if showsInlineCheckpointEditor {
814
+                    BatteryCheckpointEditorContentView(
815
+                        sessionID: openChargeSession.id,
816
+                        message: "The checkpoint is stored on the active charge session and later used for capacity estimation and the typical charge curve.",
817
+                        effectiveEnergyWhOverride: displayedEnergyWh,
818
+                        measuredChargeAhOverride: displayedChargeAh,
819
+                        onCancel: { showsInlineCheckpointEditor = false },
820
+                        onSaved: { showsInlineCheckpointEditor = false }
821
+                    )
822
+                }
823
+            } else if let checkpointEditingMessage = appData.batteryCheckpointCaptureRequirementMessage(for: openChargeSession.id) {
824
+                Text(checkpointEditingMessage)
825
+                    .font(.caption)
826
+                    .foregroundColor(.secondary)
820 827
             }
821 828
 
822 829
             Button(openChargeSession.targetBatteryPercent == nil ? "Set Target Notification" : "Change Target Notification") {
@@ -1059,6 +1066,9 @@ struct MeterChargeRecordContentView: View {
1059 1066
                     Text(checkpoint.timestamp.format())
1060 1067
                         .font(.caption2)
1061 1068
                         .foregroundColor(.secondary)
1069
+                    Text(checkpoint.flag.title)
1070
+                        .font(.caption2.weight(.semibold))
1071
+                        .foregroundColor(.secondary)
1062 1072
                     Spacer()
1063 1073
                     Text("\(checkpoint.batteryPercent.format(decimalDigits: 0))%")
1064 1074
                         .font(.caption.weight(.semibold))
@@ -1228,7 +1238,6 @@ struct ChargeSessionCompletionSheetView: View {
1228 1238
     let explanation: String
1229 1239
 
1230 1240
     @State private var batteryPercent = ""
1231
-    @State private var label = "Final"
1232 1241
 
1233 1242
     var body: some View {
1234 1243
         NavigationView {
@@ -1241,7 +1250,6 @@ struct ChargeSessionCompletionSheetView: View {
1241 1250
                 ) {
1242 1251
                     TextField("Battery %", text: $batteryPercent)
1243 1252
                         .keyboardType(.decimalPad)
1244
-                    TextField("Label", text: $label)
1245 1253
                 }
1246 1254
 
1247 1255
                 Section {
@@ -1272,8 +1280,7 @@ struct ChargeSessionCompletionSheetView: View {
1272 1280
 
1273 1281
                         if appData.stopChargeSession(
1274 1282
                             sessionID: sessionID,
1275
-                            finalBatteryPercent: batteryPercent,
1276
-                            label: label.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "Final" : label
1283
+                            finalBatteryPercent: batteryPercent
1277 1284
                         ) {
1278 1285
                             dismiss()
1279 1286
                         }