Bläddra i källkod

Merge pull request #488 from nightscout/onboarding-adjustments

Onboarding Part 5 — Adjustments (default settings, texts, hints)
Sam King 1 år sedan
förälder
incheckning
908ceac21b
30 ändrade filer med 428 tillägg och 116 borttagningar
  1. 12 4
      Trio.xcodeproj/project.pbxproj
  2. 1 1
      Trio/Resources/json/defaults/settings/basal_profile.json
  3. 2 2
      Trio/Resources/json/defaults/settings/bg_targets.json
  4. 1 1
      Trio/Resources/json/defaults/settings/carb_ratios.json
  5. 1 1
      Trio/Resources/json/defaults/settings/insulin_sensitivities.json
  6. 11 5
      Trio/Sources/Application/TrioApp.swift
  7. 83 0
      Trio/Sources/Localizations/Main/Localizable.xcstrings
  8. 1 1
      Trio/Sources/Modules/AutosensSettings/AutosensSettingsStateModel.swift
  9. 6 9
      Trio/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift
  10. 11 8
      Trio/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift
  11. 1 1
      Trio/Sources/Modules/Home/View/HomeRootView.swift
  12. 4 4
      Trio/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift
  13. 128 16
      Trio/Sources/Modules/Onboarding/OnboardingStateModel.swift
  14. 34 5
      Trio/Sources/Modules/Onboarding/View/OnboardingRootView.swift
  15. 17 17
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/AlgorithmSettings/AlgorithmSettingsSubstepView.swift
  16. 7 4
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/Nightscout/NightscoutImportStepView.swift
  17. 14 0
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/Nightscout/NightscoutSetupStepView.swift
  18. 29 1
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/StartupGuide/StartupReturningUserStepView.swift
  19. 0 0
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/TherapySettings/BasalProfileStepView.swift
  20. 0 0
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/TherapySettings/CarbRatioStepView.swift
  21. 0 0
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/TherapySettings/GlucoseTargetStepView.swift
  22. 8 10
      Trio/Sources/Modules/Onboarding/View/OnboardingSteps/InsulinSensitivityStepView.swift
  23. 13 10
      Trio/Sources/Modules/Onboarding/View/OnboardingView+AlgorithmUtil.swift
  24. 12 8
      Trio/Sources/Modules/Onboarding/View/OnboardingView+Util.swift
  25. 7 0
      Trio/Sources/Modules/Onboarding/View/TherapySettingEditorView.swift
  26. 2 2
      Trio/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift
  27. 8 2
      Trio/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift
  28. 12 1
      Trio/Sources/Modules/Settings/SettingItems.swift
  29. 2 2
      Trio/Sources/Modules/TargetBehavoir/View/TargetBehavoirRootView.swift
  30. 1 1
      Trio/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift

+ 12 - 4
Trio.xcodeproj/project.pbxproj

@@ -2835,6 +2835,7 @@
 		BD47FDD52D8B64AE0043966B /* OnboardingSteps */ = {
 			isa = PBXGroup;
 			children = (
+				DD3C47E42DC6CD8C003DD20D /* TherapySettings */,
 				DD6A4E4E2DBEBC7B008C4B26 /* StartupGuide */,
 				DDFF20302DB1D15500AB8A96 /* BluetoothPermissionStepView.swift */,
 				DDFF202E2DB1D14500AB8A96 /* NotificationPermissionStepView.swift */,
@@ -2846,10 +2847,6 @@
 				DD3F1F842D9DD83B00DCE7B3 /* DeliveryLimitsStepView.swift */,
 				DD3F1F822D9DC78300DCE7B3 /* UnitSelectionStepView.swift */,
 				BD47FD162D88AAEF0043966B /* CompletedStepView.swift */,
-				BD47FDDC2D8B65AD0043966B /* GlucoseTargetStepView.swift */,
-				BD47FDDA2D8B65960043966B /* BasalProfileStepView.swift */,
-				BD47FDD82D8B65730043966B /* InsulinSensitivityStepView.swift */,
-				BD47FDD62D8B64CC0043966B /* CarbRatioStepView.swift */,
 			);
 			path = OnboardingSteps;
 			sourceTree = "<group>";
@@ -3300,6 +3297,17 @@
 			path = Helper;
 			sourceTree = "<group>";
 		};
+		DD3C47E42DC6CD8C003DD20D /* TherapySettings */ = {
+			isa = PBXGroup;
+			children = (
+				BD47FDDC2D8B65AD0043966B /* GlucoseTargetStepView.swift */,
+				BD47FDDA2D8B65960043966B /* BasalProfileStepView.swift */,
+				BD47FDD82D8B65730043966B /* InsulinSensitivityStepView.swift */,
+				BD47FDD62D8B64CC0043966B /* CarbRatioStepView.swift */,
+			);
+			path = TherapySettings;
+			sourceTree = "<group>";
+		};
 		DD3F1F8E2D9E151200DCE7B3 /* Nightscout */ = {
 			isa = PBXGroup;
 			children = (

+ 1 - 1
Trio/Resources/json/defaults/settings/basal_profile.json

@@ -2,6 +2,6 @@
     {
         "start": "00:00:00",
         "minutes": 0,
-        "rate": 1.0
+        "rate": 0.1
     }
 ]

+ 2 - 2
Trio/Resources/json/defaults/settings/bg_targets.json

@@ -3,8 +3,8 @@
     "user_preferred_units": "mg/dL",
     "targets": [
         {
-            "low": 100,
-            "high": 100,
+            "low": 110,
+            "high": 110,
             "start": "00:00:00",
             "offset": 0
         }

+ 1 - 1
Trio/Resources/json/defaults/settings/carb_ratios.json

@@ -4,7 +4,7 @@
         {
             "start": "00:00:00",
             "offset": 0,
-            "ratio": 10
+            "ratio": 30
         }
     ]
 }

+ 1 - 1
Trio/Resources/json/defaults/settings/insulin_sensitivities.json

@@ -3,7 +3,7 @@
     "user_preferred_units": "mg/dL",
     "sensitivities": [
         {
-            "sensitivity": 54,
+            "sensitivity": 200,
             "offset": 0,
             "start": "00:00:00"
         }

+ 11 - 5
Trio/Sources/Application/TrioApp.swift

@@ -22,9 +22,10 @@ extension Notification.Name {
     let onboardingManager = OnboardingManager.shared
 
     class InitState {
-        var complete = false
-        var error = false
+        var complete: Bool = false
+        var error: Bool = false
         var migrationErrors: [String] = []
+        var migrationFailed: Bool = false
     }
 
     // We use both InitState and @State variables to track coreDataStack
@@ -207,6 +208,7 @@ extension Notification.Name {
         }
 
         initState.migrationErrors = importErrors
+        initState.migrationFailed = importErrors.isNotEmpty
     }
 
     /// Clears any legacy (Trio 0.2.x) delivered and pending notifications related to non-looping alerts.
@@ -299,9 +301,13 @@ extension Notification.Name {
                     }
                 } else if onboardingManager.shouldShowOnboarding {
                     // Show onboarding if needed
-                    Onboarding.RootView(resolver: resolver, onboardingManager: onboardingManager)
-                        .preferredColorScheme(colorScheme(for: .dark) ?? nil)
-                        .transition(.opacity)
+                    Onboarding.RootView(
+                        resolver: resolver,
+                        onboardingManager: onboardingManager,
+                        wasMigrationSuccessful: !initState.migrationFailed
+                    )
+                    .preferredColorScheme(colorScheme(for: .dark) ?? nil)
+                    .transition(.opacity)
                 } else {
                     Main.RootView(resolver: resolver)
                         .preferredColorScheme(colorScheme(for: colorSchemePreference) ?? nil)

+ 83 - 0
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -7309,6 +7309,16 @@
         }
       }
     },
+    "%@ %@ / %@ %@/%@ = %@ %@" : {
+      "localizations" : {
+        "en" : {
+          "stringUnit" : {
+            "state" : "new",
+            "value" : "%1$@ %2$@ / %3$@ %4$@/%5$@ = %6$@ %7$@"
+          }
+        }
+      }
+    },
     "%@ %%" : {
       "localizations" : {
         "bg" : {
@@ -10039,6 +10049,7 @@
       }
     },
     "• Basal Rates" : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -10340,6 +10351,7 @@
       }
     },
     "• Carb Ratios" : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -13042,6 +13054,7 @@
       }
     },
     "• Glucose Targets" : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -13543,6 +13556,7 @@
       }
     },
     "• Insulin Sensitivities" : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -14844,6 +14858,7 @@
       }
     },
     "• Omnipod Dash" : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -14943,6 +14958,9 @@
         }
       }
     },
+    "• Omnipod DASH" : {
+
+    },
     "• Omnipod Eros" : {
       "localizations" : {
         "bg" : {
@@ -33759,6 +33777,7 @@
       }
     },
     "Allow SMB when glucose is above the High BG Target value." : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -42993,6 +43012,9 @@
     "Autosens Max sets the maximum Sensitivity Ratio used by Autosens, Dynamic ISF, and Sigmoid Formula." : {
 
     },
+    "Autosens Maximum" : {
+      "comment" : "Autosens Max"
+    },
     "Autosens Min" : {
       "comment" : "Autosens Min",
       "localizations" : {
@@ -43197,6 +43219,9 @@
     "Autosens Min sets the minimum Sensitivity Ratio used by Autosens, Dynamic ISF, and Sigmoid Formula." : {
 
     },
+    "Autosens Minimum" : {
+      "comment" : "Autosens Min"
+    },
     "Autosens modifies Insulin Sensitivity Factor (ISF), basal rates, and target blood sugar levels. It doesn’t account for carbs but adjusts for insulin effectiveness based on patterns in your glucose data." : {
       "extractionState" : "stale",
       "localizations" : {
@@ -89434,6 +89459,7 @@
     },
     "Enable SMB With High BG" : {
       "comment" : "Enable SMB With High BG",
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -90246,6 +90272,9 @@
         }
       }
     },
+    "Enable UAM (Unannounced Meals)" : {
+      "comment" : "Enable UAM"
+    },
     "Enable Unannounced Meals SMB." : {
       "localizations" : {
         "bg" : {
@@ -91467,6 +91496,7 @@
       }
     },
     "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) when glucose reading is above the value set as High BG Target." : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -121615,6 +121645,9 @@
         }
       }
     },
+    "Information" : {
+
+    },
     "Information We Collect" : {
       "localizations" : {
         "bg" : {
@@ -134886,6 +134919,7 @@
 
     },
     "Lower target glucose when Autosens Ratio is <1." : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -134986,6 +135020,7 @@
       }
     },
     "Lower target glucose when Autosens Ratio is >1." : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -135085,6 +135120,9 @@
         }
       }
     },
+    "Lower target glucose when Autosens Ratio is greater than 1." : {
+
+    },
     "m" : {
       "comment" : "Abbreviation for Minutes\nabbreviation for minutes",
       "localizations" : {
@@ -136919,6 +136957,7 @@
     },
     "Max Bolus" : {
       "comment" : "Max setting",
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -138047,6 +138086,7 @@
     },
     "Max Delta-BG Threshold SMB" : {
       "comment" : "Max Delta-BG Threshold",
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -139877,6 +139917,9 @@
         }
       }
     },
+    "Max. Allowed Glucose Rise for SMB" : {
+      "comment" : "Max. Allowed Glucose Rise for SMB, formerly Max Delta-BG Threshold"
+    },
     "Maximum allowed positive percent change in glucose level to permit SMBs. If the difference in glucose is greater than this, Trio will disable SMBs." : {
       "localizations" : {
         "bg" : {
@@ -139977,6 +140020,9 @@
         }
       }
     },
+    "Maximum amount of active carbs considered by the algorithm." : {
+
+    },
     "Maximum amount of carbs still available if no absorption is detected." : {
       "comment" : "Mini hint for Remaining Carbs Cap",
       "localizations" : {
@@ -140078,7 +140124,17 @@
         }
       }
     },
+    "Maximum Basal Rate" : {
+
+    },
+    "Maximum Bolus" : {
+
+    },
+    "Maximum Carbs on Board (COB)" : {
+      "comment" : "Max COB"
+    },
     "Maximum Carbs On Board (COB) allowed." : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -140487,6 +140543,9 @@
         }
       }
     },
+    "Maximum Insulin on Board (IOB)" : {
+      "comment" : "Max IOB"
+    },
     "Maximum Meal Absorption Time" : {
       "localizations" : {
         "bg" : {
@@ -156215,6 +156274,7 @@
       }
     },
     "Omnipod Dash" : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -156314,6 +156374,9 @@
         }
       }
     },
+    "Omnipod DASH" : {
+
+    },
     "Omnipod Eros" : {
       "localizations" : {
         "bg" : {
@@ -158765,6 +158828,9 @@
         }
       }
     },
+    "Other third-party services, like Apple Health or Tidepool, can be added later through the settings menu." : {
+
+    },
     "Our strong recommendation is to " : {
       "localizations" : {
         "bg" : {
@@ -169115,6 +169181,7 @@
       }
     },
     "Raise target glucose if when Autosens Ratio is >1." : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -169215,6 +169282,7 @@
       }
     },
     "Raise target glucose when Autosens Ratio is <1." : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -169314,6 +169382,9 @@
         }
       }
     },
+    "Raise target glucose when Autosens Ratio is less than 1." : {
+
+    },
     "Random variation added to each reading to simulate real-world sensor noise." : {
       "localizations" : {
         "bg" : {
@@ -211250,6 +211321,9 @@
         }
       }
     },
+    "This setting helps prevent delivering too much insulin at once. It’s typically a value close to the amount you might need for a very high blood sugar and the biggest meal of your life combined." : {
+
+    },
     "This setting helps the system estimate how much glucose your body is absorbing, even when it's not immediately visible in your glucose data, ensuring more accurate insulin dosing during carb absorption." : {
       "localizations" : {
         "bg" : {
@@ -236402,6 +236476,9 @@
         }
       }
     },
+    "While onboarding, Trio continues to operate with your prior settings." : {
+
+    },
     "While upgrading Trio to the new version, we ran into an issue transferring some of your historical data." : {
 
     },
@@ -238842,6 +238919,9 @@
         }
       }
     },
+    "You can use Nightscout to import your existing therapy settings, or if you prefer, you can only connect to Nightscout, and configure therapy settings from scratch." : {
+
+    },
     "You have certain rights regarding your information, including:" : {
       "localizations" : {
         "bg" : {
@@ -239760,6 +239840,9 @@
     "Your last 24 hr of treatment data (pump events, carb entries, glucose trace, etc.) are migrated." : {
 
     },
+    "Your last 24 hr of treatment data (pump events, carb entries, glucose trace, etc.) are not migrated." : {
+
+    },
     "Your phone or app is not enabled for NFC communications, which is needed to pair to libre2 sensors" : {
       "extractionState" : "manual",
       "localizations" : {

+ 1 - 1
Trio/Sources/Modules/AutosensSettings/AutosensSettingsStateModel.swift

@@ -11,7 +11,7 @@ extension AutosensSettings {
         var units: GlucoseUnits = .mgdL
 
         private(set) var autosensISF: Decimal?
-        private(set) var autosensRatio: Decimal = 0
+        private(set) var autosensRatio: Decimal = 1
         @Published var determinationsFromPersistence: [OrefDetermination] = []
 
         let viewContext = CoreDataStack.shared.persistentContainer.viewContext

+ 6 - 9
Trio/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift

@@ -65,18 +65,15 @@ extension AutosensSettings {
                     let dynamicRatio = state.determinationsFromPersistence.first?.sensitivityRatio
                     let dynamicISF = state.determinationsFromPersistence.first?.insulinSensitivity
                     let newISF = state.autosensISF
+                    let decimalValue = !state.settingsManager.preferences.useNewFormula ? state
+                        .autosensRatio as NSDecimalNumber : dynamicRatio ?? 1
+                    let decimalValueText = rateFormatter
+                        .string(from: ((decimalValue as Decimal) * Decimal(100)) as NSNumber) ?? "100"
+
                     HStack {
                         Text("Sensitivity Ratio")
                         Spacer()
-                        Text(
-                            rateFormatter
-                                .string(from: (
-                                    (
-                                        !state.settingsManager.preferences.useNewFormula ? state
-                                            .autosensRatio as NSDecimalNumber : dynamicRatio
-                                    ) ?? 1
-                                ) as NSNumber) ?? "1"
-                        )
+                        Text("\(decimalValueText) \(String(localized: "%", comment: "Percentage symbol"))")
                     }.padding(.vertical)
                     HStack {
                         Text("Calculated Sensitivity")

+ 11 - 8
Trio/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift

@@ -36,12 +36,12 @@ extension UnitsLimitsSettings {
                         get: { selectedVerboseHint },
                         set: {
                             selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = String(localized: "Max IOB", comment: "Max IOB")
+                            hintLabel = String(localized: "Maximum Insulin on Board (IOB)", comment: "Max IOB")
                         }
                     ),
                     units: state.units,
                     type: .decimal("maxIOB"),
-                    label: String(localized: "Max IOB", comment: "Max IOB"),
+                    label: String(localized: "Maximum Insulin on Board (IOB)", comment: "Max IOB"),
                     miniHint: String(localized: "Maximum units of insulin allowed to be active."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
@@ -50,6 +50,9 @@ extension UnitsLimitsSettings {
                             "Warning: This must be greater than 0 for any automatic temporary basal rates or SMBs to be given."
                         ).bold()
                         Text(
+                            "This setting helps prevent delivering too much insulin at once. It’s typically a value close to the amount you might need for a very high blood sugar and the biggest meal of your life combined."
+                        )
+                        Text(
                             "This is the maximum amount of Insulin On Board (IOB) above profile basal rates from all sources - positive temporary basal rates, manual or meal boluses, and SMBs - that Trio is allowed to accumulate to address an above target glucose."
                         )
                         Text(
@@ -69,12 +72,12 @@ extension UnitsLimitsSettings {
                         get: { selectedVerboseHint },
                         set: {
                             selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = String(localized: "Max Bolus")
+                            hintLabel = String(localized: "Maximum Bolus")
                         }
                     ),
                     units: state.units,
                     type: .decimal("maxBolus"),
-                    label: String(localized: "Max Bolus"),
+                    label: String(localized: "Maximum Bolus"),
                     miniHint: String(localized: "Largest bolus of insulin allowed."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
@@ -100,7 +103,7 @@ extension UnitsLimitsSettings {
                     ),
                     units: state.units,
                     type: .decimal("maxBasal"),
-                    label: String(localized: "Max Basal Rate"),
+                    label: String(localized: "Maximum Basal Rate"),
                     miniHint: String(localized: "Largest basal rate allowed."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
@@ -122,13 +125,13 @@ extension UnitsLimitsSettings {
                         get: { selectedVerboseHint },
                         set: {
                             selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = String(localized: "Max COB", comment: "Max COB")
+                            hintLabel = String(localized: "Maximum Carbs on Board (COB)", comment: "Max COB")
                         }
                     ),
                     units: state.units,
                     type: .decimal("maxCOB"),
-                    label: String(localized: "Max COB", comment: "Max COB"),
-                    miniHint: String(localized: "Maximum Carbs On Board (COB) allowed."),
+                    label: String(localized: "Maximum Carbs on Board (COB)", comment: "Max COB"),
+                    miniHint: String(localized: "Maximum amount of active carbs considered by the algorithm."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: 120 grams of carbs").bold()

+ 1 - 1
Trio/Sources/Modules/Home/View/HomeRootView.swift

@@ -951,7 +951,7 @@ extension Home {
             .confirmationDialog("Pump Model", isPresented: $showPumpSelection) {
                 Button("Medtronic") { state.addPump(.minimed) }
                 Button("Omnipod Eros") { state.addPump(.omnipod) }
-                Button("Omnipod Dash") { state.addPump(.omnipodBLE) }
+                Button("Omnipod DASH") { state.addPump(.omnipodBLE) }
                 Button("Dana(RS/-i)") { state.addPump(.dana) }
                 Button("Pump Simulator") { state.addPump(.simulator) }
             } message: { Text("Select Pump Model") }

+ 4 - 4
Trio/Sources/Modules/ISFEditor/View/ISFEditorRootView.swift

@@ -208,8 +208,8 @@ extension ISFEditor {
                     ).foregroundStyle(
                         .linearGradient(
                             colors: [
-                                Color.red.opacity(0.6),
-                                Color.red.opacity(0.1)
+                                Color.cyan.opacity(0.6),
+                                Color.cyan.opacity(0.1)
                             ],
                             startPoint: .bottom,
                             endPoint: .top
@@ -217,10 +217,10 @@ extension ISFEditor {
                     ).alignsMarkStylesWithPlotArea()
 
                     LineMark(x: .value("End Date", startDate), y: .value("ISF", displayValueFloat ?? 0))
-                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.red)
+                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.cyan)
 
                     LineMark(x: .value("Start Date", endDate), y: .value("ISF", displayValueFloat ?? 0))
-                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.red)
+                        .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.cyan)
                 }
             }
             .chartXAxis {

+ 128 - 16
Trio/Sources/Modules/Onboarding/OnboardingStateModel.swift

@@ -1,8 +1,12 @@
 import Combine
+import DanaKit
 import FirebaseCrashlytics
 import Foundation
 import LoopKit
+import MinimedKit
 import Observation
+import OmniBLE
+import OmniKit
 import SwiftUI
 
 /// Model that holds the data collected during onboarding.
@@ -15,6 +19,7 @@ extension Onboarding {
         @ObservationIgnored @Injected() var nightscoutManager: NightscoutManager!
         @ObservationIgnored @Injected() var notificationsManager: UserNotificationsManager!
         @ObservationIgnored @Injected() var bluetoothManager: BluetoothStateManager!
+        @ObservationIgnored @Injected() var apsManager: APSManager!
 
         private let settingsProvider = PickerSettingsProvider.shared
 
@@ -23,6 +28,58 @@ extension Onboarding {
         var diagnosticsSharingOption: DiagnosticsSharingOption = .enabled
         var hasAcceptedPrivacyPolicy: Bool = false
 
+        // MARK: - Determine Initial Build State
+
+        /// Determines whether the app is in a fresh install state for Trio v0.3.0.
+        ///
+        /// This check is based on the assumption that a truly clean install will only contain
+        /// the `logs/` directory and the `preferences.json` file in the app's Documents directory.
+        ///
+        /// If this condition is met, the onboarding flow skips the `.returningUser` step and treats
+        /// the user as new. If more files or directories are found, it is assumed the user is returning.
+        ///
+        /// Note: This check is not directly connected to a completed migration. However, if a migration
+        /// has been triggered (whether successful or not), additional files such as treatment JSONs
+        /// will exist, which naturally causes this check to return `false`.
+        var isFreshTrioInstall: Bool {
+            let fileManager = FileManager.default
+            guard let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
+                return false
+            }
+
+            debug(.default, "Checking for fresh install in \(documentsURL.path)...")
+
+            let expectedLogsFolder = "logs"
+            let expectedPreferencesFile = OpenAPS.Settings.preferences
+
+            do {
+                let contents = try fileManager.contentsOfDirectory(atPath: documentsURL.path)
+
+                debug(.default, "Found \(contents) in \(documentsURL.path)...")
+
+                // Expect exactly 2 entries: "logs" and the preferences file
+                guard contents.count == 2 else {
+                    debug(.default, "Trio install is not fresh; returning user.")
+                    return false
+                }
+
+                // Ensure they match exactly
+                let expectedSet = Set([expectedLogsFolder, expectedPreferencesFile])
+                let actualSet = Set(contents)
+
+                debug(.default, "Expected: \(expectedSet), Actual: \(actualSet)")
+
+                let isFreshInstall = expectedSet == actualSet
+                debug(.default, "Trio install is fresh; new user.")
+
+                return isFreshInstall
+
+            } catch {
+                debug(.default, "Cannot determine Initial Build State. Failed to read documents directory: \(error)")
+                return false
+            }
+        }
+
         // MARK: - Nightscout Setup
 
         var nightscoutSetupOption: NightscoutSetupOption = .noSelection
@@ -39,7 +96,40 @@ extension Onboarding {
         // MARK: - Units and Pump Omboarding Option
 
         var units: GlucoseUnits = .mgdL
-        var pumpOptionForOnboardingUnits: PumpOptionForOnboardingUnits = .omnipodDash
+        private var selectedPumpOption: PumpOptionForOnboardingUnits?
+        var pumpOptionForOnboardingUnits: PumpOptionForOnboardingUnits {
+            get {
+                // let user edit selection and return user-selection, if present
+                if let selected = selectedPumpOption {
+                    return selected
+                }
+
+                let defaultOption: PumpOptionForOnboardingUnits
+                if let pumpManager = apsManager?.pumpManager {
+                    if pumpManager is OmniBLEPumpManager {
+                        defaultOption = .omnipodDash
+                    } else if pumpManager is OmnipodPumpManager {
+                        defaultOption = .omnipodEros
+                    } else if pumpManager is DanaKitPumpManager {
+                        defaultOption = .dana
+                    } else if pumpManager is MinimedPumpManager {
+                        defaultOption = .minimed
+                    } else {
+                        defaultOption = .omnipodDash
+                    }
+                } else {
+                    defaultOption = .omnipodDash
+                }
+
+                // cache it so picker can stay in sync
+                selectedPumpOption = defaultOption
+
+                return defaultOption
+            }
+            set {
+                selectedPumpOption = newValue
+            }
+        }
 
         // MARK: - Time Values (shared)
 
@@ -47,7 +137,7 @@ extension Onboarding {
 
         // MARK: - Carb Ratio
 
-        let carbRatioPickerSetting = PickerSetting(value: 10, step: 0.1, min: 1, max: 50, type: .gram)
+        let carbRatioPickerSetting = PickerSetting(value: 30, step: 0.1, min: 1, max: 50, type: .gram)
         var carbRatioItems: [CarbRatioEditor.Item] = []
         var initialCarbRatioItems: [CarbRatioEditor.Item] = []
         var carbRatioTimeValues: [TimeInterval] { sharedTimeValues }
@@ -56,15 +146,18 @@ extension Onboarding {
         // MARK: - Basal Profile
 
         var basalRatePickerSetting: PickerSetting {
-            switch pumpOptionForOnboardingUnits {
+            switch selectedPumpOption {
             case .dana:
-                return PickerSetting(value: 0.05, step: 0.05, min: 0, max: 3, type: .insulinUnitPerHour)
+                return PickerSetting(value: 0.1, step: 0.05, min: 0, max: 3, type: .insulinUnitPerHour)
             case .minimed:
-                return PickerSetting(value: 0.05, step: 0.05, min: 0, max: 35, type: .insulinUnitPerHour)
+                return PickerSetting(value: 0.1, step: 0.05, min: 0, max: 35, type: .insulinUnitPerHour)
             case .omnipodDash:
-                return PickerSetting(value: 0.05, step: 0.05, min: 0, max: 30, type: .insulinUnitPerHour)
+                return PickerSetting(value: 0.1, step: 0.05, min: 0, max: 30, type: .insulinUnitPerHour)
             case .omnipodEros:
-                return PickerSetting(value: 0.05, step: 0.05, min: 0.05, max: 30, type: .insulinUnitPerHour)
+                return PickerSetting(value: 0.1, step: 0.05, min: 0.05, max: 30, type: .insulinUnitPerHour)
+            case .none:
+                // same as dash, as that is the fallback
+                return PickerSetting(value: 0.1, step: 0.05, min: 0, max: 30, type: .insulinUnitPerHour)
             }
         }
 
@@ -76,7 +169,7 @@ extension Onboarding {
 
         // MARK: - Insulin Sensitivity Factor (ISF)
 
-        var sensitivityPickerSetting = PickerSetting(value: 100, step: 1, min: 9, max: 540, type: .glucose)
+        var sensitivityPickerSetting = PickerSetting(value: 200, step: 1, min: 9, max: 540, type: .glucose)
         var isfItems: [ISFEditor.Item] = []
         var initialISFItems: [ISFEditor.Item] = []
         var isfTimeValues: [TimeInterval] { sharedTimeValues }
@@ -84,7 +177,7 @@ extension Onboarding {
 
         // MARK: - Glucose Targets
 
-        let letTargetPickerSetting = PickerSetting(value: 100, step: 1, min: 72, max: 180, type: .glucose)
+        let letTargetPickerSetting = PickerSetting(value: 110, step: 1, min: 72, max: 180, type: .glucose)
         var targetItems: [TargetsEditor.Item] = []
         var initialTargetItems: [TargetsEditor.Item] = []
         var targetTimeValues: [TimeInterval] { sharedTimeValues }
@@ -215,16 +308,35 @@ extension Onboarding {
 
         /// Remaps therapy items affected by a pump model change.
         ///
-        /// This function updates basal profile items to use the closest valid index
-        /// from the updated basal rate and time arrays, preserving the user's settings.
+        /// Updates basal profile items to use the closest valid index from
+        /// the updated basal rate and time arrays, preserving the user's settings
+        /// as closely as possible when switching between pump models.
+        ///
+        /// If an imported item's `rateIndex` or `timeIndex` exceeds the bounds of the
+        /// current pump's allowed values, it is clamped to the last valid index to avoid
+        /// crashes and preserve data integrity. A debug message is logged if clamping occurs.
         ///
         /// Call this after the user selects a new pump model.
         ///
         /// See also: `UnitSelectionStepView` `.onChange()` handlers.
         func remapTherapyItemsForChangedPumpModel() {
+            let maxValidRateIndex = max(basalProfileRateValues.count - 1, 0)
+            let maxValidTimeIndex = max(basalProfileTimeValues.count - 1, 0)
+
             basalProfileItems = basalProfileItems.map { item in
-                let newRateIndex = closestIndex(for: basalProfileRateValues[item.rateIndex], in: basalProfileRateValues)
-                let newTimeIndex = closestIndex(for: basalProfileTimeValues[item.timeIndex], in: basalProfileTimeValues)
+                let safeRateIndex = min(item.rateIndex, maxValidRateIndex)
+                let safeTimeIndex = min(item.timeIndex, maxValidTimeIndex)
+
+                let originalRate = basalProfileRateValues[safeRateIndex]
+                let originalTime = basalProfileTimeValues[safeTimeIndex]
+
+                let newRateIndex = closestIndex(for: originalRate, in: basalProfileRateValues)
+                let newTimeIndex = closestIndex(for: originalTime, in: basalProfileTimeValues)
+
+                if safeRateIndex != item.rateIndex {
+                    debug(.default, "⚠️ rateIndex \(item.rateIndex) out of bounds; clamped to \(safeRateIndex)")
+                }
+
                 return BasalProfileEditor.Item(rateIndex: newRateIndex, timeIndex: newTimeIndex)
             }
         }
@@ -412,7 +524,7 @@ extension Onboarding {
         /// Adds a default ISF editor item at 00:00 with a standard sensitivity value.
         func addInitialISF() {
             addInitialItem(
-                defaultValue: 50,
+                defaultValue: 200,
                 rateValues: isfRateValues,
                 assign: { isfItems = $0 },
                 makeItem: ISFEditor.Item.init
@@ -432,7 +544,7 @@ extension Onboarding {
         /// Adds a default carb ratio editor item at 00:00 with a standard ratio.
         func addInitialCarbRatio() {
             addInitialItem(
-                defaultValue: 10,
+                defaultValue: 30,
                 rateValues: carbRatioRateValues,
                 assign: { carbRatioItems = $0 },
                 makeItem: CarbRatioEditor.Item.init
@@ -442,7 +554,7 @@ extension Onboarding {
         /// Adds a default glucose target item at 00:00 with a typical target value.
         func addInitialTarget() {
             let timeIndex = 0
-            let rateIndex = closestIndex(for: 100, in: targetRateValues)
+            let rateIndex = closestIndex(for: 110, in: targetRateValues)
             targetItems = [TargetsEditor.Item(lowIndex: rateIndex, highIndex: rateIndex, timeIndex: timeIndex)]
         }
 

+ 34 - 5
Trio/Sources/Modules/Onboarding/View/OnboardingRootView.swift

@@ -1,3 +1,4 @@
+import Foundation
 import SwiftUI
 import Swinject
 
@@ -8,6 +9,7 @@ extension Onboarding {
         @State var state = StateModel()
         @State private var navigationDirection: OnboardingNavigationDirection = .forward
         let onboardingManager: OnboardingManager
+        let wasMigrationSuccessful: Bool
 
         // Step management
         @State private var currentChapter: OnboardingChapter = .prepareTrio
@@ -124,6 +126,7 @@ extension Onboarding {
                         }
 
                         OnboardingStepContent(
+                            wasMigrationSuccessful: wasMigrationSuccessful,
                             currentStep: $currentStep,
                             showingChapterCompletion: $showingChapterCompletion,
                             currentStartupSubstep: $currentStartupSubstep,
@@ -150,6 +153,7 @@ extension Onboarding {
                             currentSMBSubstep: $currentSMBSubstep,
                             currentTargetBehaviorSubstep: $currentTargetBehaviorSubstep,
                             onboardingManager: onboardingManager,
+                            isFreshTrioInstall: state.isFreshTrioInstall,
                             state: state,
                             shouldDisableNextButton: shouldDisableNextButton,
                             navigationDirectionChanged: { navigationDirection = $0 }
@@ -280,6 +284,7 @@ struct OnboardingProgressBar: View {
 }
 
 struct OnboardingStepContent: View {
+    var wasMigrationSuccessful: Bool
     @Binding var currentStep: OnboardingStep
     @Binding var showingChapterCompletion: OnboardingChapter?
     @Binding var currentStartupSubstep: StartupSubstep
@@ -314,7 +319,7 @@ struct OnboardingStepContent: View {
                                 case .startupGuide:
                                     StartupGuideStepView(state: state)
                                 case .returningUser:
-                                    StartupReturningUserStepView(state: state)
+                                    StartupReturningUserStepView(state: state, wasMigrationSuccessful: wasMigrationSuccessful)
                                 case .forceCloseWarning:
                                     StartupForceCloseWarningStepView(state: state)
                                 }
@@ -451,6 +456,7 @@ struct OnboardingNavigationButtons: View {
     @Binding var currentTargetBehaviorSubstep: TargetBehaviorSubstep
 
     let onboardingManager: OnboardingManager
+    let isFreshTrioInstall: Bool
     @Bindable var state: Onboarding.StateModel
     var shouldDisableNextButton: Bool
     var navigationDirectionChanged: (OnboardingNavigationDirection) -> Void
@@ -505,7 +511,14 @@ struct OnboardingNavigationButtons: View {
 
         switch currentStep {
         case .startupInfo:
-            if let previousSub = StartupSubstep(rawValue: currentStartupSubstep.rawValue - 1) {
+            var previous = StartupSubstep(rawValue: currentStartupSubstep.rawValue - 1)
+
+            /// Skip `.returningUser` if this is a fresh install
+            if previous == .returningUser, isFreshTrioInstall == true {
+                previous = StartupSubstep(rawValue: previous!.rawValue - 1)
+            }
+
+            if let previousSub = previous {
                 currentStartupSubstep = previousSub
             } else if let previous = currentStep.previous {
                 currentStep = previous
@@ -628,7 +641,17 @@ struct OnboardingNavigationButtons: View {
 
         switch currentStep {
         case .startupInfo:
-            if let next = StartupSubstep(rawValue: currentStartupSubstep.rawValue + 1) {
+            let nextSubstepRaw = currentStartupSubstep.rawValue + 1
+
+            if isFreshTrioInstall, StartupSubstep(rawValue: nextSubstepRaw) == .returningUser {
+                /// Skip `.returningUser` if it's a fresh install
+                if let nextAfterSkip = StartupSubstep(rawValue: nextSubstepRaw + 1) {
+                    currentStartupSubstep = nextAfterSkip
+                } else if let nextStep = currentStep.next {
+                    currentStep = nextStep
+                    currentStartupSubstep = .startupGuide
+                }
+            } else if let next = StartupSubstep(rawValue: nextSubstepRaw) {
                 currentStartupSubstep = next
             } else if let nextStep = currentStep.next {
                 currentStep = nextStep
@@ -734,7 +757,13 @@ struct OnboardingNavigationButtons: View {
             }
 
         case .bluetooth:
-            state.shouldDisplayBluetoothRequestAlert = true
+            if let next = currentStep.next {
+                if state.bluetoothManager.bluetoothAuthorization != .authorized {
+                    state.shouldDisplayBluetoothRequestAlert = true
+                } else {
+                    currentStep = next
+                }
+            }
 
         case .completed:
             state.saveOnboardingData()
@@ -754,7 +783,7 @@ struct Onboarding_Preview: PreviewProvider {
         Group {
             let resolver = TrioApp.resolver
             let onboardingManager = OnboardingManager()
-            Onboarding.RootView(resolver: resolver, onboardingManager: onboardingManager)
+            Onboarding.RootView(resolver: resolver, onboardingManager: onboardingManager, wasMigrationSuccessful: true)
                 .previewDisplayName("Onboarding Flow")
         }
     }

+ 17 - 17
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/AlgorithmSettings/AlgorithmSettingsSubstepView.swift

@@ -52,7 +52,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .autosensMax:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: settingsProvider.settings.autosensMax,
                         decimalValue: $state.autosensMax,
@@ -61,7 +61,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .rewindResetsAutosens:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
@@ -71,7 +71,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .enableSMBAlways:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
@@ -80,7 +80,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .enableSMBWithCOB:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
@@ -90,7 +90,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .enableSMBWithTempTarget:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
@@ -100,7 +100,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .enableSMBAfterCarbs:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
@@ -110,7 +110,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .enableSMBWithHighGlucoseTarget:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
@@ -131,7 +131,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     }
                 case .allowSMBWithHighTempTarget:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
@@ -140,7 +140,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .enableUAM:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
@@ -149,7 +149,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .maxSMBMinutes:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: settingsProvider.settings.maxSMBBasalMinutes,
                         decimalValue: $state.maxSMBMinutes,
@@ -158,7 +158,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .maxUAMMinutes:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: settingsProvider.settings.maxUAMSMBBasalMinutes,
                         decimalValue: $state.maxUAMMinutes,
@@ -167,7 +167,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .maxDeltaGlucoseThreshold:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: settingsProvider.settings.maxDeltaBGthreshold,
                         decimalValue: $state.maxDeltaGlucoseThreshold,
@@ -176,7 +176,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .highTempTargetRaisesSensitivity:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
@@ -185,7 +185,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .lowTempTargetLowersSensitivity:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
@@ -194,7 +194,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .sensitivityRaisesTarget:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
@@ -203,7 +203,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .resistanceLowersTarget:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: nil,
                         decimalValue: $decimalPlaceholder,
@@ -212,7 +212,7 @@ struct AlgorithmSettingsSubstepView<Substep: AlgorithmSubstepProtocol & RawRepre
                     )
                 case .halfBasalTarget:
                     algorithmSettingsInput(
-                        label: substep.title,
+                        label: step.title,
                         displayPicker: $shouldDisplayPicker,
                         setting: settingsProvider.settings.halfBasalExerciseTarget,
                         decimalValue: $state.halfBasalTarget,

+ 7 - 4
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/Nightscout/NightscoutImportStepView.swift

@@ -53,11 +53,14 @@ struct NightscoutImportStepView: View {
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Trio will import the following therapy settings from your Nightscout instance:")
                             .multilineTextAlignment(.leading)
+                            .fixedSize(horizontal: false, vertical: true)
                         VStack(alignment: .leading) {
-                            Text("• Glucose Targets")
-                            Text("• Basal Rates")
-                            Text("• Carb Ratios")
-                            Text("• Insulin Sensitivities")
+                            BulletPoint(String(localized: "Glucose Targets"))
+                            BulletPoint(String(localized: "Basal Rates"))
+                            BulletPoint(String(localized: "Carb Ratios"))
+                            BulletPoint(
+                                String(localized: "Insulin Sensitivities")
+                            )
                         }
                     }
                     .padding(.horizontal)

+ 14 - 0
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/Nightscout/NightscoutSetupStepView.swift

@@ -30,6 +30,20 @@ struct NightscoutSetupStepView: View {
                 }
                 .buttonStyle(.plain)
             }
+
+            Text(
+                "You can use Nightscout to import your existing therapy settings, or if you prefer, you can only connect to Nightscout, and configure therapy settings from scratch."
+            )
+            .padding(.horizontal)
+            .font(.footnote)
+            .foregroundStyle(Color.secondary)
+            .multilineTextAlignment(.leading)
+
+            Text("Other third-party services, like Apple Health or Tidepool, can be added later through the settings menu.")
+                .padding(.horizontal)
+                .font(.footnote)
+                .foregroundStyle(Color.secondary)
+                .multilineTextAlignment(.leading)
         }
     }
 }

+ 29 - 1
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/StartupGuide/StartupReturningUserStepView.swift

@@ -8,6 +8,7 @@ import SwiftUI
 
 struct StartupReturningUserStepView: View {
     @Bindable var state: Onboarding.StateModel
+    let wasMigrationSuccessful: Bool
 
     @Environment(\.openURL) var openURL
 
@@ -25,7 +26,9 @@ struct StartupReturningUserStepView: View {
                     Text("Important").foregroundStyle(Color.orange)
                 }.bold()
 
-                Text("Your last 24 hr of treatment data (pump events, carb entries, glucose trace, etc.) are migrated.")
+                if !wasMigrationSuccessful {
+                    Text("Your last 24 hr of treatment data (pump events, carb entries, glucose trace, etc.) are not migrated.")
+                }
 
                 Divider().overlay(Color.orange)
 
@@ -41,10 +44,35 @@ struct StartupReturningUserStepView: View {
             .cornerRadius(10)
 
             VStack(alignment: .leading, spacing: 10) {
+                HStack(alignment: .top, spacing: 10) {
+                    Image(systemName: "info.circle.fill").foregroundStyle(Color.bgDarkBlue, Color.blue)
+                        .symbolRenderingMode(.palette)
+                    Text("Information").foregroundStyle(Color.blue)
+                }.bold()
+
+                Text("While onboarding, Trio continues to operate with your prior settings.")
+            }
+            .frame(maxWidth: .infinity)
+            .padding()
+            .background(Color.chart.opacity(0.65))
+            .overlay(
+                RoundedRectangle(cornerRadius: 10)
+                    .stroke(Color.blue, lineWidth: 2)
+            )
+            .cornerRadius(10)
+
+            VStack(alignment: .leading, spacing: 10) {
                 Text("Here's what you can expect to be preserved:")
                     .font(.headline)
                     .padding(.bottom, 4)
 
+                if wasMigrationSuccessful {
+                    BulletPoint(
+                        String(
+                            localized: "Your last 24 hr of treatment data (pump events, carb entries, glucose trace, etc.) are migrated."
+                        )
+                    )
+                }
                 BulletPoint(String(localized: "Your pump and CGM configurations are retained and fully functional."))
                 BulletPoint(
                     String(

Trio/Sources/Modules/Onboarding/View/OnboardingSteps/BasalProfileStepView.swift → Trio/Sources/Modules/Onboarding/View/OnboardingSteps/TherapySettings/BasalProfileStepView.swift


Trio/Sources/Modules/Onboarding/View/OnboardingSteps/CarbRatioStepView.swift → Trio/Sources/Modules/Onboarding/View/OnboardingSteps/TherapySettings/CarbRatioStepView.swift


Trio/Sources/Modules/Onboarding/View/OnboardingSteps/GlucoseTargetStepView.swift → Trio/Sources/Modules/Onboarding/View/OnboardingSteps/TherapySettings/GlucoseTargetStepView.swift


+ 8 - 10
Trio/Sources/Modules/Onboarding/View/OnboardingSteps/InsulinSensitivityStepView.swift

@@ -75,9 +75,8 @@ struct InsulinSensitivityStepView: View {
                         VStack(alignment: .leading, spacing: 8) {
                             // Current glucose is 40 mg/dL or 2.2 mmol/L above target
                             let aboveTarget = state.units == .mgdL ? Decimal(40) : 40.asMmolL
-
-                            let isfValue = state.units == .mgdL ? Decimal(50) : 50.asMmolL
-
+                            let firstIsfRate: Decimal = state.isfRateValues[state.isfItems.first?.rateIndex ?? 0]
+                            let isfValue = state.units == .mgdL ? firstIsfRate : firstIsfRate.asMmolL
                             let insulinNeeded = aboveTarget / isfValue
 
                             Text(
@@ -87,11 +86,10 @@ struct InsulinSensitivityStepView: View {
                             .padding(.horizontal)
 
                             Text(
-                                "\(numberFormatter.string(from: aboveTarget as NSNumber) ?? "--") / \(numberFormatter.string(from: isfValue as NSNumber) ?? "--") = \(String(format: "%.1f", Double(insulinNeeded)))" +
-                                    " " + String(localized: "U", comment: "Insulin unit abbreviation")
+                                "\(aboveTarget.description) \(state.units.rawValue) / \(isfValue.description) \(state.units.rawValue)/\(String(localized: "U", comment: "Insulin unit abbreviation")) = \(String(format: "%.1f", Double(insulinNeeded))) \(String(localized: "U", comment: "Insulin unit abbreviation"))"
                             )
                             .font(.system(.body, design: .monospaced))
-                            .foregroundColor(.red)
+                            .foregroundColor(.cyan)
                             .padding()
                             .frame(maxWidth: .infinity, alignment: .center)
                             .background(Color.chart.opacity(0.65))
@@ -163,8 +161,8 @@ struct InsulinSensitivityStepView: View {
                 ).foregroundStyle(
                     .linearGradient(
                         colors: [
-                            Color.red.opacity(0.6),
-                            Color.red.opacity(0.1)
+                            Color.cyan.opacity(0.6),
+                            Color.cyan.opacity(0.1)
                         ],
                         startPoint: .bottom,
                         endPoint: .top
@@ -172,10 +170,10 @@ struct InsulinSensitivityStepView: View {
                 ).alignsMarkStylesWithPlotArea()
 
                 LineMark(x: .value("End Date", startDate), y: .value("ISF", displayValue))
-                    .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.red)
+                    .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.cyan)
 
                 LineMark(x: .value("Start Date", endDate), y: .value("ISF", displayValue))
-                    .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.red)
+                    .lineStyle(.init(lineWidth: 1)).foregroundStyle(Color.cyan)
             }
         }
         .id(refreshUI) // Force chart update

+ 13 - 10
Trio/Sources/Modules/Onboarding/View/OnboardingView+AlgorithmUtil.swift

@@ -99,8 +99,8 @@ enum AlgorithmSettingsSubstep: Int, CaseIterable, Identifiable {
 
     var title: String {
         switch self {
-        case .autosensMin: return String(localized: "Autosens Min", comment: "Autosens Min")
-        case .autosensMax: return String(localized: "Autosens Max", comment: "Autosens Max")
+        case .autosensMin: return String(localized: "Autosens Minimum", comment: "Autosens Min")
+        case .autosensMax: return String(localized: "Autosens Maximum", comment: "Autosens Max")
         case .rewindResetsAutosens: return String(localized: "Rewind Resets Autosens", comment: "Rewind Resets Autosens")
         case .enableSMBAlways: return String(localized: "Enable SMB Always", comment: "Enable SMB Always")
         case .enableSMBWithCOB: return String(localized: "Enable SMB With COB", comment: "Enable SMB With COB")
@@ -110,17 +110,20 @@ enum AlgorithmSettingsSubstep: Int, CaseIterable, Identifiable {
             )
         case .enableSMBAfterCarbs: return String(localized: "Enable SMB After Carbs", comment: "Enable SMB After Carbs")
         case .enableSMBWithHighGlucoseTarget: return String(
-                localized: "Enable SMB With High BG",
-                comment: "Enable SMB With High BG"
+                localized: "Enable SMB With High Glucose",
+                comment: "Enable SMB With High Glucose"
             )
         case .allowSMBWithHighTempTarget: return String(
                 localized: "Allow SMB With High Temptarget",
                 comment: "Allow SMB With High Temptarget"
             )
-        case .enableUAM: return String(localized: "Enable UAM", comment: "Enable UAM")
+        case .enableUAM: return String(localized: "Enable UAM (Unannounced Meals)", comment: "Enable UAM")
         case .maxSMBMinutes: return String(localized: "Max SMB Basal Minutes", comment: "Max SMB Basal Minutes")
         case .maxUAMMinutes: return String(localized: "Max UAM Basal Minutes", comment: "Max UAM Basal Minutes")
-        case .maxDeltaGlucoseThreshold: return String(localized: "Max Delta-BG Threshold SMB", comment: "Max Delta-BG Threshold")
+        case .maxDeltaGlucoseThreshold: return String(
+                localized: "Max. Allowed Glucose Rise for SMB",
+                comment: "Max. Allowed Glucose Rise for SMB, formerly Max Delta-BG Threshold"
+            )
         case .highTempTargetRaisesSensitivity: return String(
                 localized: "High Temp Target Raises Sensitivity",
                 comment: "High Temp Target Raises Sensitivity"
@@ -147,7 +150,7 @@ enum AlgorithmSettingsSubstep: Int, CaseIterable, Identifiable {
                 localized: "Allow SMB when a manual Temporary Target is set under \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue)."
             )
         case .enableSMBAfterCarbs: return String(localized: "Allow SMB for 6 hrs after a carb entry.")
-        case .enableSMBWithHighGlucoseTarget: return String(localized: "Allow SMB when glucose is above the High BG Target value.")
+        case .enableSMBWithHighGlucoseTarget: return String(localized: "Allow SMB when glucose is above the High Glucose Target value.")
         case .allowSMBWithHighTempTarget: return String(
                 localized: "Allow SMB when a manual Temporary Target is set greater than \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue)."
             )
@@ -161,8 +164,8 @@ enum AlgorithmSettingsSubstep: Int, CaseIterable, Identifiable {
         case .lowTempTargetLowersSensitivity: return String(
                 localized: "Decrease sensitivity when glucose is below target if a manual Temp Target < \(units == .mgdL ? "100" : 100.formattedAsMmolL) \(units.rawValue) is set."
             )
-        case .sensitivityRaisesTarget: return String(localized: "Raise target glucose if when Autosens Ratio is >1.")
-        case .resistanceLowersTarget: return String(localized: "Lower target glucose when Autosens Ratio is <1.")
+        case .sensitivityRaisesTarget: return String(localized: "Raise target glucose when Autosens Ratio is less than 1.")
+        case .resistanceLowersTarget: return String(localized: "Lower target glucose when Autosens Ratio is greater than 1.")
         case .halfBasalTarget: return String(localized: "Scales down your basal rate to 50% at this value.")
         }
     }
@@ -261,7 +264,7 @@ enum AlgorithmSettingsSubstep: Int, CaseIterable, Identifiable {
             return VStack(alignment: .leading, spacing: 8) {
                 Text("Default: OFF").bold().foregroundStyle(Color.primary)
                 Text(
-                    "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) when glucose reading is above the value set as High BG Target."
+                    "Enabling this feature allows Trio to deliver insulin required using Super Micro Boluses (SMB) when glucose reading is above the value set as High Glucose Target."
                 )
                 Text(
                     "Note: If this is enabled and the criteria are met, SMBs could be utilized regardless of other SMB settings being enabled or not."

+ 12 - 8
Trio/Sources/Modules/Onboarding/View/OnboardingView+Util.swift

@@ -318,7 +318,7 @@ enum OnboardingStep: Int, CaseIterable, Identifiable, Equatable {
         case .carbRatio:
             return Color.orange
         case .insulinSensitivity:
-            return Color.red
+            return Color.cyan
         }
     }
 
@@ -365,10 +365,10 @@ enum DeliveryLimitSubstep: Int, CaseIterable, Identifiable {
 
     var title: String {
         switch self {
-        case .maxIOB: return String(localized: "Max IOB", comment: "Max IOB")
-        case .maxBolus: return String(localized: "Max Bolus")
-        case .maxBasal: return String(localized: "Max Basal Rate")
-        case .maxCOB: return String(localized: "Max COB", comment: "Max COB")
+        case .maxIOB: return String(localized: "Maximum Insulin on Board (IOB)", comment: "Max IOB")
+        case .maxBolus: return String(localized: "Maximum Bolus")
+        case .maxBasal: return String(localized: "Maximum Basal Rate")
+        case .maxCOB: return String(localized: "Maximum Carbs on Board (COB)", comment: "Max COB")
         case .minimumSafetyThreshold: return String(localized: "Minimum Safety Threshold")
         }
     }
@@ -378,7 +378,7 @@ enum DeliveryLimitSubstep: Int, CaseIterable, Identifiable {
         case .maxIOB: return String(localized: "Maximum units of insulin allowed to be active.")
         case .maxBolus: return String(localized: "Largest bolus of insulin allowed.")
         case .maxBasal: return String(localized: "Largest basal rate allowed.")
-        case .maxCOB: return String(localized: "Maximum Carbs On Board (COB) allowed.")
+        case .maxCOB: return String(localized: "Maximum amount of active carbs considered by the algorithm.")
         case .minimumSafetyThreshold: return String(localized: "Increase the safety threshold used to suspend insulin delivery.")
         }
     }
@@ -389,7 +389,11 @@ enum DeliveryLimitSubstep: Int, CaseIterable, Identifiable {
             return VStack(alignment: .leading, spacing: 8) {
                 Text(
                     "Note: This setting must be greater than 0 for any automatic insulin dosing by Trio."
-                ).bold().foregroundStyle(Color.primary)
+                ).bold().foregroundStyle(Color.orange)
+
+                Text(
+                    "This setting helps prevent delivering too much insulin at once. It’s typically a value close to the amount you might need for a very high blood sugar and the biggest meal of your life combined."
+                )
 
                 Text(
                     "This is the maximum amount of Insulin On Board (IOB) above profile basal rates from all sources - positive temporary basal rates, manual or meal boluses, and SMBs - that Trio is allowed to accumulate to address an above target glucose."
@@ -493,7 +497,7 @@ enum PumpOptionForOnboardingUnits: String, Equatable, CaseIterable, Identifiable
         case .omnipodEros:
             return "Omnipod Eros"
         case .omnipodDash:
-            return "Omnipod Dash"
+            return "Omnipod DASH"
         case .dana:
             return "Dana (RS/-i)"
         }

+ 7 - 0
Trio/Sources/Modules/Onboarding/View/TherapySettingEditorView.swift

@@ -114,6 +114,13 @@ struct TherapySettingEditorView: View {
         .onAppear {
             // ensure picker is closed when view appears
             selectedItemID = nil
+            // sorts items
+            validateTherapySettingItems()
+        }
+        .onDisappear {
+            // ensure picker is closed when view appears
+            selectedItemID = nil
+            // sorts items
             validateTherapySettingItems()
         }
         .onChange(of: items, { _, _ in

+ 2 - 2
Trio/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift

@@ -116,7 +116,7 @@ extension PumpConfig {
                                 VStack(alignment: .leading) {
                                     Text("• Medtronic")
                                     Text("• Omnipod Eros")
-                                    Text("• Omnipod Dash")
+                                    Text("• Omnipod DASH")
                                     Text("• Dana (RS/-i)")
                                     Text("• Pump Simulator")
                                 }
@@ -131,7 +131,7 @@ extension PumpConfig {
                 .confirmationDialog("Pump Model", isPresented: $showPumpSelection) {
                     Button("Medtronic") { state.addPump(.minimed) }
                     Button("Omnipod Eros") { state.addPump(.omnipod) }
-                    Button("Omnipod Dash") { state.addPump(.omnipodBLE) }
+                    Button("Omnipod DASH") { state.addPump(.omnipodBLE) }
                     Button("Dana(RS/-i)") { state.addPump(.dana) }
                     Button("Pump Simulator") { state.addPump(.simulator) }
                 } message: { Text("Select Pump Model") }

+ 8 - 2
Trio/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift

@@ -330,12 +330,18 @@ extension SMBSettings {
                         get: { selectedVerboseHint },
                         set: {
                             selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = String(localized: "Max Delta-BG Threshold SMB", comment: "Max Delta-BG Threshold")
+                            hintLabel = String(
+                                localized: "Max. Allowed Glucose Rise for SMB",
+                                comment: "Max. Allowed Glucose Rise for SMB, formerly Max Delta-BG Threshold"
+                            )
                         }
                     ),
                     units: state.units,
                     type: .decimal("maxDeltaBGthreshold"),
-                    label: String(localized: "Max Delta-BG Threshold SMB", comment: "Max Delta-BG Threshold"),
+                    label: String(
+                        localized: "Max. Allowed Glucose Rise for SMB",
+                        comment: "Max. Allowed Glucose Rise for SMB, formerly Max Delta-BG Threshold"
+                    ),
                     miniHint: String(localized: "Disables SMBs if last two glucose values differ by more than this percent."),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {

+ 12 - 1
Trio/Sources/Modules/Settings/SettingItems.swift

@@ -231,7 +231,18 @@ enum SettingItems {
             ],
             path: ["Features", "User Interface"]
         ),
-        SettingItem(title: "App Icons", view: .iconConfig)
+        SettingItem(
+            title: "App Icons",
+            view: .iconConfig,
+            searchContents: ["Trio Icon"],
+            path: ["Features", "App Icons"]
+        ),
+        SettingItem(
+            title: "App Diagnostics",
+            view: .appDiagnostics,
+            searchContents: ["Anonymized Data Sharing"],
+            path: ["Features", "App Diagnostics"]
+        )
     ]
 
     static let notificationItems = [

+ 2 - 2
Trio/Sources/Modules/TargetBehavoir/View/TargetBehavoirRootView.swift

@@ -110,7 +110,7 @@ extension TargetBehavoir {
                     units: state.units,
                     type: .boolean,
                     label: String(localized: "Sensitivity Raises Target", comment: "Sensitivity Raises Target"),
-                    miniHint: String(localized: "Raise target glucose when Autosens Ratio is <1."),
+                    miniHint: String(localized: "Raise target glucose when Autosens Ratio is less than 1."),
                     verboseHint: VStack(alignment: .leading, spacing: 10) {
                         Text("Default: OFF").bold()
                         Text(
@@ -133,7 +133,7 @@ extension TargetBehavoir {
                     units: state.units,
                     type: .boolean,
                     label: String(localized: "Resistance Lowers Target", comment: "Resistance Lowers Target"),
-                    miniHint: String(localized: "Lower target glucose when Autosens Ratio is >1."),
+                    miniHint: String(localized: "Lower target glucose when Autosens Ratio is greater than 1."),
                     verboseHint: VStack(alignment: .leading, spacing: 10) {
                         Text("Default: OFF").bold()
                         Text(

+ 1 - 1
Trio/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift

@@ -13,7 +13,7 @@ extension TargetsEditor {
 
         var rateValues: [Decimal] {
             let settingsProvider = PickerSettingsProvider.shared
-            let glucoseSetting = PickerSetting(value: 0, step: 1, min: 72, max: 180, type: .glucose)
+            let glucoseSetting = PickerSetting(value: 110, step: 1, min: 72, max: 180, type: .glucose)
             return settingsProvider.generatePickerValues(from: glucoseSetting, units: units)
         }