Browse Source

Merge branch 'dev' into feat/bolus-initiating

Bastiaan Verhaar 9 hours ago
parent
commit
5792ad7ade
100 changed files with 11699 additions and 26896 deletions
  1. 1 1
      Config.xcconfig
  2. 1 1
      OmnipodKit
  3. 374 75
      Trio.xcodeproj/project.pbxproj
  4. 0 9
      Trio.xcworkspace/xcshareddata/swiftpm/Package.resolved
  5. BIN
      Trio/Resources/Sounds/alarm.caf
  6. BIN
      Trio/Resources/Sounds/bloom.caf
  7. BIN
      Trio/Resources/Sounds/bloop.caf
  8. BIN
      Trio/Resources/Sounds/bright_alarm.caf
  9. BIN
      Trio/Resources/Sounds/chime.caf
  10. BIN
      Trio/Resources/Sounds/clear_chimes.caf
  11. BIN
      Trio/Resources/Sounds/critical.caf
  12. BIN
      Trio/Resources/Sounds/crying.caf
  13. BIN
      Trio/Resources/Sounds/dings.caf
  14. BIN
      Trio/Resources/Sounds/high_chimes.caf
  15. BIN
      Trio/Resources/Sounds/honk.caf
  16. BIN
      Trio/Resources/Sounds/minimal.caf
  17. BIN
      Trio/Resources/Sounds/mood_synth.caf
  18. BIN
      Trio/Resources/Sounds/simple.caf
  19. BIN
      Trio/Resources/Sounds/spring.caf
  20. BIN
      Trio/Resources/Sounds/synth.caf
  21. BIN
      Trio/Resources/Sounds/trill.caf
  22. BIN
      Trio/Resources/Sounds/urgent_low.caf
  23. 2 0
      Trio/Resources/Trio.entitlements
  24. 157 22
      Trio/Sources/APS/APSManager.swift
  25. 32 8
      Trio/Sources/APS/CGM/PluginSource.swift
  26. 11 45
      Trio/Sources/APS/DeviceDataManager.swift
  27. 5 0
      Trio/Sources/APS/FetchGlucoseManager.swift
  28. 60 54
      Trio/Sources/APS/OpenAPS/OpenAPS.swift
  29. 1 1
      Trio/Sources/APS/OpenAPSSwift/Profile/Carbs.swift
  30. 3 3
      Trio/Sources/APS/Storage/GlucoseStorage.swift
  31. 3 0
      Trio/Sources/Application/TrioApp.swift
  32. 6 0
      Trio/Sources/Assemblies/ServiceAssembly.swift
  33. 1 1
      Trio/Sources/Helpers/Formatters.swift
  34. 29 0
      Trio/Sources/Helpers/TimeInterval+Convenience.swift
  35. 8227 25698
      Trio/Sources/Localizations/Main/Localizable.xcstrings
  36. 1 47
      Trio/Sources/Logger/Logger.swift
  37. 0 2
      Trio/Sources/Models/DecimalPickerSettings.swift
  38. 34 0
      Trio/Sources/Models/GlucoseAlerts/AlarmSoundCatalog.swift
  39. 33 0
      Trio/Sources/Models/GlucoseAlerts/DayNightOptions.swift
  40. 79 0
      Trio/Sources/Models/GlucoseAlerts/DeviceAlertSeverity.swift
  41. 63 0
      Trio/Sources/Models/GlucoseAlerts/DeviceAlertSeverityConfig.swift
  42. 77 0
      Trio/Sources/Models/GlucoseAlerts/GlucoseAlert.swift
  43. 63 0
      Trio/Sources/Models/GlucoseAlerts/GlucoseAlertConfiguration.swift
  44. 103 0
      Trio/Sources/Models/GlucoseAlerts/GlucoseAlertType.swift
  45. 0 27
      Trio/Sources/Models/GlucoseNotificationsOption.swift
  46. 10 0
      Trio/Sources/Models/GlucoseSourceKey.swift
  47. 11 9
      Trio/Sources/Models/NotificationIdentifiers.swift
  48. 8 49
      Trio/Sources/Models/TrioSettings.swift
  49. 7 0
      Trio/Sources/Modules/AlarmWindows/AlarmWindowsDataFlow.swift
  50. 5 0
      Trio/Sources/Modules/AlarmWindows/AlarmWindowsProvider.swift
  51. 18 0
      Trio/Sources/Modules/AlarmWindows/AlarmWindowsStateModel.swift
  52. 135 0
      Trio/Sources/Modules/AlarmWindows/View/AlarmWindowsRootView.swift
  53. 3 3
      Trio/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift
  54. 7 8
      Trio/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift
  55. 31 10
      Trio/Sources/Modules/CGMSettings/View/CustomCGMOptionsView.swift
  56. 27 1
      Trio/Sources/Modules/Calibrations/CalibrationsStateModel.swift
  57. 7 0
      Trio/Sources/Modules/DeviceAlarms/DeviceAlarmsDataFlow.swift
  58. 5 0
      Trio/Sources/Modules/DeviceAlarms/DeviceAlarmsProvider.swift
  59. 8 0
      Trio/Sources/Modules/DeviceAlarms/DeviceAlarmsStateModel.swift
  60. 65 0
      Trio/Sources/Modules/DeviceAlarms/View/AddDeviceAlarmSheet.swift
  61. 129 0
      Trio/Sources/Modules/DeviceAlarms/View/DeviceAlarmEditorView.swift
  62. 226 0
      Trio/Sources/Modules/DeviceAlarms/View/DeviceAlarmsRootView.swift
  63. 7 0
      Trio/Sources/Modules/GlucoseAlerts/GlucoseAlertsDataFlow.swift
  64. 5 0
      Trio/Sources/Modules/GlucoseAlerts/GlucoseAlertsProvider.swift
  65. 36 0
      Trio/Sources/Modules/GlucoseAlerts/GlucoseAlertsStateModel.swift
  66. 87 0
      Trio/Sources/Modules/GlucoseAlerts/View/AddGlucoseAlertSheet.swift
  67. 23 0
      Trio/Sources/Modules/GlucoseAlerts/View/Components/AlarmActiveSection.swift
  68. 168 0
      Trio/Sources/Modules/GlucoseAlerts/View/Components/AlarmAudioSection.swift
  69. 54 0
      Trio/Sources/Modules/GlucoseAlerts/View/Components/AlarmBGSection.swift
  70. 39 0
      Trio/Sources/Modules/GlucoseAlerts/View/Components/AlarmEnumMenuPicker.swift
  71. 47 0
      Trio/Sources/Modules/GlucoseAlerts/View/Components/AlarmGramsSection.swift
  72. 44 0
      Trio/Sources/Modules/GlucoseAlerts/View/Components/AlarmWindowIcon.swift
  73. 191 0
      Trio/Sources/Modules/GlucoseAlerts/View/GlucoseAlertEditorView.swift
  74. 345 0
      Trio/Sources/Modules/GlucoseAlerts/View/GlucoseAlertsRootView.swift
  75. 0 5
      Trio/Sources/Modules/GlucoseNotificationSettings/GlucoseNotificationSettingsDataFlow.swift
  76. 0 3
      Trio/Sources/Modules/GlucoseNotificationSettings/GlucoseNotificationSettingsProvider.swift
  77. 0 53
      Trio/Sources/Modules/GlucoseNotificationSettings/GlucoseNotificationSettingsStateModel.swift
  78. 0 389
      Trio/Sources/Modules/GlucoseNotificationSettings/View/GlucoseNotificationSettingsRootView.swift
  79. 3 0
      Trio/Sources/Modules/Home/HomeProvider.swift
  80. 98 1
      Trio/Sources/Modules/Home/HomeStateModel.swift
  81. 31 26
      Trio/Sources/Modules/Home/View/Chart/ChartElements/GlucoseChartView.swift
  82. 1 1
      Trio/Sources/Modules/Home/View/Header/LoopStatusView.swift
  83. 46 11
      Trio/Sources/Modules/Home/View/HomeRootView.swift
  84. 35 0
      Trio/Sources/Modules/Home/View/QuickPickBoluses/QuickPickBolusesInfoView.swift
  85. 149 0
      Trio/Sources/Modules/Home/View/QuickPickBoluses/QuickPickBolusesView.swift
  86. 58 0
      Trio/Sources/Modules/Home/View/QuickPickBoluses/SlideToConfirmView.swift
  87. 2 2
      Trio/Sources/Modules/LiveActivitySettings/View/LiveActivityWidgetConfiguration.swift
  88. 0 307
      Trio/Sources/Modules/Main/MainStateModel.swift
  89. 5 1
      Trio/Sources/Modules/Main/View/MainRootView.swift
  90. 0 2
      Trio/Sources/Modules/Onboarding/OnboardingStateModel.swift
  91. 13 1
      Trio/Sources/Modules/PumpConfig/PumpConfigDataFlow.swift
  92. 3 1
      Trio/Sources/Modules/PumpConfig/PumpConfigStateModel.swift
  93. 1 0
      Trio/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift
  94. 5 0
      Trio/Sources/Modules/QuickPickBolusesConfig/QuickPickBolusesConfigDataFlow.swift
  95. 3 0
      Trio/Sources/Modules/QuickPickBolusesConfig/QuickPickBolusesConfigProvider.swift
  96. 19 0
      Trio/Sources/Modules/QuickPickBolusesConfig/QuickPickBolusesConfigStateModel.swift
  97. 64 0
      Trio/Sources/Modules/QuickPickBolusesConfig/View/QuickPickBolusesConfigRootView.swift
  98. 48 17
      Trio/Sources/Modules/Settings/SettingItems.swift
  99. 1 2
      Trio/Sources/Modules/Settings/View/Subviews/FeatureSettingsView.swift
  100. 0 0
      Trio/Sources/Modules/Settings/View/Subviews/NotificationsView.swift

+ 1 - 1
Config.xcconfig

@@ -19,7 +19,7 @@ TRIO_APP_GROUP_ID = group.org.nightscout.$(DEVELOPMENT_TEAM).trio.trio-app-group
 
 // The developers set the version numbers, please leave them alone
 APP_VERSION = 0.8.4
-APP_DEV_VERSION = 0.8.4.2
+APP_DEV_VERSION = 0.8.4.8
 APP_BUILD_NUMBER = 1
 COPYRIGHT_NOTICE =
 

+ 1 - 1
OmnipodKit

@@ -1 +1 @@
-Subproject commit d75f218131225d3faa51daca0a004952d06c9b8f
+Subproject commit b5b95562f73e6d3627e3e20ffe31f8d7f6a4639e

+ 374 - 75
Trio.xcodeproj/project.pbxproj

@@ -8,9 +8,7 @@
 
 /* Begin PBXBuildFile section */
 		041D1E995A6AE92E9289DC49 /* TreatmentsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D1A7CA8C10C4403D4BBFA7 /* TreatmentsDataFlow.swift */; };
-		0437CE46C12535A56504EC19 /* SnoozeRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5822B15939E719628E9FF7C /* SnoozeRootView.swift */; };
 		0D9A5E34A899219C5C4CDFAF /* HistoryStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9455FA2D92E77A6C4AFED8A3 /* HistoryStateModel.swift */; };
-		0F7A65FBD2CD8D6477ED4539 /* GlucoseNotificationSettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E625985B47742D498CB1681A /* GlucoseNotificationSettingsProvider.swift */; };
 		110AEDE32C5193D200615CC9 /* BolusIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AEDE02C5193D100615CC9 /* BolusIntent.swift */; };
 		110AEDE42C5193D200615CC9 /* BolusIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AEDE12C5193D100615CC9 /* BolusIntentRequest.swift */; };
 		110AEDEB2C51A0AE00615CC9 /* ShortcutsConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110AEDE52C51A0AE00615CC9 /* ShortcutsConfigView.swift */; };
@@ -56,7 +54,6 @@
 		1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */; };
 		1D845DF2E3324130E1D95E67 /* HistoryProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60744C3E9BB3652895C908CC /* HistoryProvider.swift */; };
 		23888883D4EA091C88480FF2 /* TreatmentsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19984D62EFC0035A9E9644D /* TreatmentsProvider.swift */; };
-		3171D2818C7C72CD1584BB5E /* GlucoseNotificationSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2C6489D29ECCCAD78E0721 /* GlucoseNotificationSettingsStateModel.swift */; };
 		320D030F724170A637F06D50 /* (null) in Sources */ = {isa = PBXBuildFile; };
 		3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE0725C9D32E00A708ED /* BaseView.swift */; };
 		3811DE0C25C9D32F00A708ED /* BaseProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3811DE0825C9D32F00A708ED /* BaseProvider.swift */; };
@@ -159,7 +156,6 @@
 		38DAB280260CBB7F00F74C1A /* PumpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DAB27F260CBB7F00F74C1A /* PumpView.swift */; };
 		38DAB28A260D349500F74C1A /* FetchGlucoseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DAB289260D349500F74C1A /* FetchGlucoseManager.swift */; };
 		38DF1786276A73D400B3528F /* TagCloudView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DF1785276A73D400B3528F /* TagCloudView.swift */; };
-		38DF1789276FC8C400B3528F /* SwiftMessages in Frameworks */ = {isa = PBXBuildFile; productRef = 38DF1788276FC8C400B3528F /* SwiftMessages */; };
 		38DF178D27733E6800B3528F /* snow.sks in Resources */ = {isa = PBXBuildFile; fileRef = 38DF178B27733E6800B3528F /* snow.sks */; };
 		38DF178E27733E6800B3528F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 38DF178C27733E6800B3528F /* Assets.xcassets */; };
 		38DF179027733EAD00B3528F /* SnowScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38DF178F27733EAD00B3528F /* SnowScene.swift */; };
@@ -319,6 +315,7 @@
 		3E62C7822F54CC1B00433237 /* BolusDisplayThreshold.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E62C7812F54CC1600433237 /* BolusDisplayThreshold.swift */; };
 		3E705AFB2FF94FAB0007F96B /* BolusStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E705AFA2FF94FAB0007F96B /* BolusStatus.swift */; };
 		3EF667132FE48509009FB31A /* BasalDeliveryState+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EF667122FE48502009FB31A /* BasalDeliveryState+Extension.swift */; };
+		3F23E18680094E6DA98628E4 /* QuickPickBolusesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A54068ABDAE4898B243DF14 /* QuickPickBolusesView.swift */; };
 		41740E936552456AAC0EDAC3 /* SettingsSearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3919BBB515547118D684CA2 /* SettingsSearchTests.swift */; };
 		45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */; };
 		45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 920DDB21E5D0EB813197500D /* ConfigEditorRootView.swift */; };
@@ -346,6 +343,7 @@
 		583684062BD178DB00070A60 /* GlucoseStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583684052BD178DB00070A60 /* GlucoseStored+helper.swift */; };
 		583684082BD195A700070A60 /* Determination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583684072BD195A700070A60 /* Determination.swift */; };
 		5837A5302BD2E3C700A5DC04 /* CarbEntryStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5837A52F2BD2E3C700A5DC04 /* CarbEntryStored+helper.swift */; };
+		584C79A91D034426A1DE60CB /* SlideToConfirmView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF96FC200503405E94334390 /* SlideToConfirmView.swift */; };
 		585E2CAE2BE7BF46006ECF1A /* PumpEvent+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585E2CAD2BE7BF46006ECF1A /* PumpEvent+helper.swift */; };
 		58645B992CA2D1A4008AFCE7 /* GlucoseSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58645B982CA2D1A4008AFCE7 /* GlucoseSetup.swift */; };
 		58645B9B2CA2D24F008AFCE7 /* CarbSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58645B9A2CA2D24F008AFCE7 /* CarbSetup.swift */; };
@@ -383,18 +381,16 @@
 		6B1A8D242B14D91700E76752 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6B1A8D232B14D91700E76752 /* Assets.xcassets */; };
 		6B1A8D282B14D91700E76752 /* LiveActivityExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 6B1A8D172B14D91600E76752 /* LiveActivityExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
 		6B1A8D2E2B156EEF00E76752 /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B1A8D2D2B156EEF00E76752 /* LiveActivityManager.swift */; };
-		6B1F539F9FF75646D1606066 /* SnoozeDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36A708CDB546692C2230B385 /* SnoozeDataFlow.swift */; };
 		6BCF84DD2B16843A003AD46E /* LiveActitiyAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCF84DC2B16843A003AD46E /* LiveActitiyAttributes.swift */; };
 		6BCF84DE2B16843A003AD46E /* LiveActitiyAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BCF84DC2B16843A003AD46E /* LiveActitiyAttributes.swift */; };
 		6EADD581738D64431902AC0A /* (null) in Sources */ = {isa = PBXBuildFile; };
-		6FFAE524D1D9C262F2407CAE /* SnoozeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CAE81192B118804DCD23034 /* SnoozeProvider.swift */; };
 		711C0CB42CAABE788916BC9D /* ManualTempBasalDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96653287EDB276A111288305 /* ManualTempBasalDataFlow.swift */; };
-		715120D22D3C2BB4005D9FB6 /* GlucoseNotificationsOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715120D12D3C2B84005D9FB6 /* GlucoseNotificationsOption.swift */; };
 		71D44AAB2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D44AAA2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift */; };
 		72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44080E4709E3AE4B73054563 /* ConfigEditorProvider.swift */; };
+		786B6E8A8E294AE883581154 /* QuickPickBolusesConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D543DE17E0054AF0821411BB /* QuickPickBolusesConfigDataFlow.swift */; };
 		7BCFACB97C821041BA43A114 /* ManualTempBasalRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */; };
 		7F7B756BE8543965D9FDF1A2 /* HistoryDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A401509D21F7F35D4E109EDA /* HistoryDataFlow.swift */; };
-		8194B80890CDD6A3C13B0FEE /* SnoozeStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26904AACA8D9C15D229D675 /* SnoozeStateModel.swift */; };
+		87F4D02B8D4D4D329BA291D2 /* QuickPickBolusesConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91711E7CA54747EFAD6540EE /* QuickPickBolusesConfigStateModel.swift */; };
 		88AB39B23C9552BD6E0C9461 /* ISFEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBB3BAE7494CB771ABAC7B8B /* ISFEditorRootView.swift */; };
 		8A91342A2D63D9A1007F8874 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A9134292D63D9A1007F8874 /* Localizable.xcstrings */; };
 		8A91342C2D63D9A2007F8874 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 8A91342B2D63D9A2007F8874 /* InfoPlist.xcstrings */; };
@@ -403,6 +399,7 @@
 		9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0A48AE3AC813A49A517846A /* NightscoutConfigStateModel.swift */; };
 		98641AF4F92123DA668AB931 /* CarbRatioEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BDC6993C1087310EDFC428 /* CarbRatioEditorRootView.swift */; };
 		A33352ED40476125EBAC6EE0 /* CarbRatioEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E22146D3DF4853786C78132 /* CarbRatioEditorDataFlow.swift */; };
+		AABB00022C54389F00211FAC /* TreatmentsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABB00012C54389F00211FAC /* TreatmentsSettingsView.swift */; };
 		AC19EF2C94084B5BA0175D1D /* SettingsSearchHighlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48B83503461B4F8D97B30115 /* SettingsSearchHighlight.swift */; };
 		AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF65DA88F972B56090AD6AC3 /* PumpConfigDataFlow.swift */; };
 		B015AFE32E500000000D7351 /* BolusSafetyValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015AFE12E500000000D7351 /* BolusSafetyValidator.swift */; };
@@ -416,6 +413,62 @@
 		BD04ECCE2D29952A008C5FEB /* BolusProgressOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD04ECCD2D299522008C5FEB /* BolusProgressOverlay.swift */; };
 		BD0B2EF32C5998E600B3298F /* MealPresetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD0B2EF22C5998E600B3298F /* MealPresetView.swift */; };
 		BD10516D2DA986E1007C6D89 /* PulsingLogoAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD10516C2DA986DC007C6D89 /* PulsingLogoAnimation.swift */; };
+		BD1179202F4E22C100F90001 /* TrioAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179212F4E22C100F90001 /* TrioAlertManager.swift */; };
+		BD1179222F4E22C100F90001 /* TrioModalAlertScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179232F4E22C100F90001 /* TrioModalAlertScheduler.swift */; };
+		BD1179242F4E22C100F90001 /* TrioUserNotificationAlertScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179252F4E22C100F90001 /* TrioUserNotificationAlertScheduler.swift */; };
+		BD1179282F4E22C100F90001 /* TrioAlertCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179292F4E22C100F90001 /* TrioAlertCategory.swift */; };
+		BD1179322F4E22C100F90001 /* CriticalAlertAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179312F4E22C100F90001 /* CriticalAlertAudioPlayer.swift */; };
+		BD1179342F4E22C100F90001 /* Sounds in Resources */ = {isa = PBXBuildFile; fileRef = BD1179332F4E22C100F90001 /* Sounds */; };
+		BD1179402F4E22C100F90001 /* ForecastedGlucoseEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179412F4E22C100F90001 /* ForecastedGlucoseEvaluator.swift */; };
+		BD1179522F4E22C100F90001 /* DayNightOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179512F4E22C100F90001 /* DayNightOptions.swift */; };
+		BD1179542F4E22C100F90001 /* GlucoseAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179532F4E22C100F90001 /* GlucoseAlert.swift */; };
+		BD1179562F4E22C100F90001 /* GlucoseAlertConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179552F4E22C100F90001 /* GlucoseAlertConfiguration.swift */; };
+		BD1179582F4E22C100F90001 /* GlucoseAlertType.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179572F4E22C100F90001 /* GlucoseAlertType.swift */; };
+		BD11795A2F4E22C100F90001 /* GlucoseAlertsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179592F4E22C100F90001 /* GlucoseAlertsStore.swift */; };
+		BD11795C2F4E22C100F90001 /* GlucoseAlertCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11795D2F4E22C100F90001 /* GlucoseAlertCoordinator.swift */; };
+		BD1179632F4E22C100F90001 /* GlucoseAlertsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179642F4E22C100F90001 /* GlucoseAlertsDataFlow.swift */; };
+		BD1179652F4E22C100F90001 /* GlucoseAlertsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179662F4E22C100F90001 /* GlucoseAlertsProvider.swift */; };
+		BD1179672F4E22C100F90001 /* GlucoseAlertsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179682F4E22C100F90001 /* GlucoseAlertsStateModel.swift */; };
+		BD1179692F4E22C100F90001 /* GlucoseAlertsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11796A2F4E22C100F90001 /* GlucoseAlertsRootView.swift */; };
+		BD11796B2F4E22C100F90001 /* GlucoseAlertEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11796C2F4E22C100F90001 /* GlucoseAlertEditorView.swift */; };
+		BD11796D2F4E22C100F90001 /* AddGlucoseAlertSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11796E2F4E22C100F90001 /* AddGlucoseAlertSheet.swift */; };
+		BD1179712F4E22C100F90001 /* AlarmEnumMenuPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179722F4E22C100F90001 /* AlarmEnumMenuPicker.swift */; };
+		BD1179732F4E22C100F90001 /* AlarmBGSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179742F4E22C100F90001 /* AlarmBGSection.swift */; };
+		BD1179772F4E22C100F90001 /* AlarmActiveSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179782F4E22C100F90001 /* AlarmActiveSection.swift */; };
+		BD1179792F4E22C100F90001 /* AlarmAudioSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11797A2F4E22C100F90001 /* AlarmAudioSection.swift */; };
+		BD11797B2F4E22C100F90001 /* AlarmSoundCatalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11797C2F4E22C100F90001 /* AlarmSoundCatalog.swift */; };
+		BD11797D2F4E22C100F90001 /* AlarmWindowIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11797E2F4E22C100F90001 /* AlarmWindowIcon.swift */; };
+		BD1179832F4E22C100F90001 /* DeviceAlertSeverity.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179842F4E22C100F90001 /* DeviceAlertSeverity.swift */; };
+		BD1179852F4E22C100F90001 /* DeviceAlertSeverityConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179862F4E22C100F90001 /* DeviceAlertSeverityConfig.swift */; };
+		BD1179872F4E22C100F90001 /* DeviceAlertsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179882F4E22C100F90001 /* DeviceAlertsStore.swift */; };
+		BD1179892F4E22C100F90001 /* DeviceAlarmsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11798A2F4E22C100F90001 /* DeviceAlarmsDataFlow.swift */; };
+		BD11798B2F4E22C100F90001 /* DeviceAlarmsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11798C2F4E22C100F90001 /* DeviceAlarmsProvider.swift */; };
+		BD11798D2F4E22C100F90001 /* DeviceAlarmsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11798E2F4E22C100F90001 /* DeviceAlarmsStateModel.swift */; };
+		BD11798F2F4E22C100F90001 /* DeviceAlarmsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179902F4E22C100F90001 /* DeviceAlarmsRootView.swift */; };
+		BD1179912F4E22C100F90001 /* DeviceAlarmEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179922F4E22C100F90001 /* DeviceAlarmEditorView.swift */; };
+		BD1179932F4E22C100F90001 /* AddDeviceAlarmSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179942F4E22C100F90001 /* AddDeviceAlarmSheet.swift */; };
+		BD1179A02F4E22C100F90001 /* AlarmWindowsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179A12F4E22C100F90001 /* AlarmWindowsDataFlow.swift */; };
+		BD1179A22F4E22C100F90001 /* AlarmWindowsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179A32F4E22C100F90001 /* AlarmWindowsProvider.swift */; };
+		BD1179A42F4E22C100F90001 /* AlarmWindowsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179A52F4E22C100F90001 /* AlarmWindowsStateModel.swift */; };
+		BD1179A62F4E22C100F90001 /* AlarmWindowsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179A72F4E22C100F90001 /* AlarmWindowsRootView.swift */; };
+		BD1179AA2F4E22C100F90001 /* NotLoopingMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179AB2F4E22C100F90001 /* NotLoopingMonitor.swift */; };
+		BD1179AC2F4E22C100F90001 /* GlucoseSourceKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD1179AD2F4E22C100F90001 /* GlucoseSourceKey.swift */; };
+		BD11A001000000000000A002 /* AlertMuterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A002 /* AlertMuterTests.swift */; };
+		BD11A001000000000000A003 /* DeviceAlertsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A003 /* DeviceAlertsStoreTests.swift */; };
+		BD11A001000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift */; };
+		BD11A001000000000000A007 /* GlucoseAlertConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A007 /* GlucoseAlertConfigurationTests.swift */; };
+		BD11A001000000000000A008 /* GlucoseAlertTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A008 /* GlucoseAlertTests.swift */; };
+		BD11A001000000000000A009 /* TrioAlertCategoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A009 /* TrioAlertCategoryTests.swift */; };
+		BD11A001000000000000A00A /* GlucoseAlertCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A00A /* GlucoseAlertCoordinatorTests.swift */; };
+		BD11A001000000000000A00B /* TrioModalAlertSchedulerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A00B /* TrioModalAlertSchedulerTests.swift */; };
+		BD11A001000000000000A00C /* NotLoopingMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A00C /* NotLoopingMonitorTests.swift */; };
+		BD11A001000000000000A00D /* AlertCatalogRegistryEntriesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A00D /* AlertCatalogRegistryEntriesTests.swift */; };
+		BD11A001000000000000A00E /* DanaKitAlertEmissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A00E /* DanaKitAlertEmissionTests.swift */; };
+		BD11A001000000000000A00F /* MedtrumKitAlertEmissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A00F /* MedtrumKitAlertEmissionTests.swift */; };
+		BD11A001000000000000A010 /* MinimedKitAlertEmissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A010 /* MinimedKitAlertEmissionTests.swift */; };
+		BD11A001000000000000A011 /* OmnipodKitAlertEmissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A011 /* OmnipodKitAlertEmissionTests.swift */; };
+		BD11A001000000000000A012 /* RileyLinkKitAlertEmissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A012 /* RileyLinkKitAlertEmissionTests.swift */; };
+		BD11A001000000000000A013 /* TandemKitAlertEmissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11A000000000000000A013 /* TandemKitAlertEmissionTests.swift */; };
 		BD11C001000000000000C001 /* CGMSensorDisplayState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11C000000000000000C001 /* CGMSensorDisplayState.swift */; };
 		BD11C001000000000000C003 /* SensorLifecycleArcView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11C000000000000000C003 /* SensorLifecycleArcView.swift */; };
 		BD11C001000000000000C004 /* SensorStatusTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD11C000000000000000C004 /* SensorStatusTagView.swift */; };
@@ -549,12 +602,18 @@
 		C2BA6B972F758E7500348E6A /* WatchNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BA6B962F758E7500348E6A /* WatchNotificationHandler.swift */; };
 		C2BA6B992F758E7600348E6A /* NotificationIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BA6B982F758E7600348E6A /* NotificationIdentifiers.swift */; };
 		C2BA6B9A2F7593C300348E6A /* NotificationIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BA6B982F758E7600348E6A /* NotificationIdentifiers.swift */; };
+		C5BAAA36C643401D97A8CE11 /* QuickPickBolusesConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3EBC8BA17C4627BD32C58F /* QuickPickBolusesConfigRootView.swift */; };
 		C967DACD3B1E638F8B43BE06 /* ManualTempBasalStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */; };
+		CA01000000000000000010C2 /* AlertCatalogVendor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA01000000000000000010C1 /* AlertCatalogVendor.swift */; };
+		CA01000000000000000010C4 /* AlertCatalogRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA01000000000000000010C3 /* AlertCatalogRegistry.swift */; };
+		CA01000000000000000010C6 /* CGMManagerAlertOwnership.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA01000000000000000010C5 /* CGMManagerAlertOwnership.swift */; };
 		CA02000000000000000010C2 /* DeliveryLimitsSyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA02000000000000000010C1 /* DeliveryLimitsSyncTests.swift */; };
+		CA03000000000000000010C2 /* AlertCatalogRegistryOmniFaultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA03000000000000000010C1 /* AlertCatalogRegistryOmniFaultTests.swift */; };
+		CA05000000000000000010C2 /* AlarmGramsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA05000000000000000010C1 /* AlarmGramsSection.swift */; };
+		CA06000000000000000010C2 /* GlucoseAlertsStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA06000000000000000010C1 /* GlucoseAlertsStoreTests.swift */; };
 		CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */; };
 		CC6C406E2ACDD69E009B8058 /* RawFetchedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */; };
 		CC76E9512BD4812E008BEB61 /* Forecast+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC76E9502BD4812E008BEB61 /* Forecast+helper.swift */; };
-		CD78BB94E43B249D60CC1A1B /* GlucoseNotificationSettingsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22963BD06A9C83959D4914E4 /* GlucoseNotificationSettingsRootView.swift */; };
 		CE1856F52ADC4858007E39C7 /* AddCarbPresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1856F42ADC4858007E39C7 /* AddCarbPresetIntent.swift */; };
 		CE1856F72ADC4869007E39C7 /* CarbPresetIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1856F62ADC4869007E39C7 /* CarbPresetIntentRequest.swift */; };
 		CE1F6DD92BADF4620064EB8D /* PluginManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1F6DD82BADF4620064EB8D /* PluginManagerTests.swift */; };
@@ -607,6 +666,7 @@
 		CEF1ED6B2D58FB5800FAF41E /* CGMOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF1ED6A2D58FB4600FAF41E /* CGMOptions.swift */; };
 		D6D02515BBFBE64FEBE89856 /* HistoryRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881E04BA5E0A003DE8E0A9C6 /* HistoryRootView.swift */; };
 		D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */; };
+		D7C5E1F4A7B91E2F3C5D6E70 /* TimeInterval+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C5E1F4A7B91E2F3C5D6E71 /* TimeInterval+Convenience.swift */; };
 		DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9F137F126D9F8DEB799F26 /* ISFEditorProvider.swift */; };
 		DD09D47B2C5986D1003FEA5D /* CalendarEventSettingsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD09D47A2C5986D1003FEA5D /* CalendarEventSettingsDataFlow.swift */; };
 		DD09D47D2C5986DA003FEA5D /* CalendarEventSettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD09D47C2C5986DA003FEA5D /* CalendarEventSettingsProvider.swift */; };
@@ -694,6 +754,7 @@
 		DD4C57AA2D73B3E2001BFF2C /* RestartLiveActivityIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C57A92D73B3D9001BFF2C /* RestartLiveActivityIntentRequest.swift */; };
 		DD4C581F2D73C43D001BFF2C /* LoopStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C581E2D73C43D001BFF2C /* LoopStatsView.swift */; };
 		DD4FFF332D458EE600B6CFF9 /* GarminWatchState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4FFF322D458EE600B6CFF9 /* GarminWatchState.swift */; };
+		DD500A712807000000000101 /* SnoozeAlertsSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD500A712807000000000100 /* SnoozeAlertsSheetView.swift */; };
 		DD5DC9F12CF3D97C00AB8703 /* AdjustmentsStateModel+Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5DC9F02CF3D96E00AB8703 /* AdjustmentsStateModel+Overrides.swift */; };
 		DD5DC9F32CF3D9DD00AB8703 /* AdjustmentsStateModel+TempTargets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5DC9F22CF3D9D600AB8703 /* AdjustmentsStateModel+TempTargets.swift */; };
 		DD5DC9F72CF3DA9300AB8703 /* TargetPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5DC9F62CF3DA9300AB8703 /* TargetPicker.swift */; };
@@ -818,6 +879,7 @@
 		DDFF204A2DB29EF500AB8A96 /* WatchLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFF20492DB29EF500AB8A96 /* WatchLogger.swift */; };
 		DDFF204E2DB2C00B00AB8A96 /* WatchStateSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFF204D2DB2C00B00AB8A96 /* WatchStateSnapshot.swift */; };
 		DDFF20502DB2C11900AB8A96 /* WatchStateSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFF204F2DB2C11900AB8A96 /* WatchStateSnapshot.swift */; };
+		DECF1903237041E2BBBE4033 /* QuickPickBolusesInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1526A587AD1544C495050662 /* QuickPickBolusesInfoView.swift */; };
 		E00EEC0327368630002FF094 /* ServiceAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEBFD27368630002FF094 /* ServiceAssembly.swift */; };
 		E00EEC0427368630002FF094 /* SecurityAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEBFE27368630002FF094 /* SecurityAssembly.swift */; };
 		E00EEC0527368630002FF094 /* StorageAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEBFF27368630002FF094 /* StorageAssembly.swift */; };
@@ -830,7 +892,6 @@
 		E0D4F80527513ECF00BDF1FE /* HealthKitSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */; };
 		E13B7DAB2A435F57066AF02E /* TargetsEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36F58DDD71F0E795464FA3F0 /* TargetsEditorStateModel.swift */; };
 		E39E418C56A5A46B61D960EE /* ConfigEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5B4F8B4194BB7E260EF251 /* ConfigEditorStateModel.swift */; };
-		E3A08AAE59538BC8A8ABE477 /* GlucoseNotificationSettingsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3260468377DA9DB4DEE9AF6D /* GlucoseNotificationSettingsDataFlow.swift */; };
 		E592A3702CEEC01E009A472C /* ContactTrickEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A36F2CEEC01E009A472C /* ContactTrickEntry.swift */; };
 		E592A3772CEEC038009A472C /* ContactImageStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3752CEEC038009A472C /* ContactImageStateModel.swift */; };
 		E592A3782CEEC038009A472C /* ContactImageDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E592A3732CEEC038009A472C /* ContactImageDataFlow.swift */; };
@@ -847,6 +908,7 @@
 		F90692D3274B9A130037068D /* AppleHealthKitRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692D2274B9A130037068D /* AppleHealthKitRootView.swift */; };
 		F90692D6274B9A450037068D /* HealthKitStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F90692D5274B9A450037068D /* HealthKitStateModel.swift */; };
 		FA630397F76B582C8D8681A7 /* BasalProfileEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42369F66CF91F30624C0B3A6 /* BasalProfileEditorProvider.swift */; };
+		FA6B58486AE2496F9F8589C6 /* QuickPickBolusesConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EEC5C2927FA4C5DA61A1A40 /* QuickPickBolusesConfigProvider.swift */; };
 		FE41E4D629463EE20047FD55 /* NightscoutPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE41E4D529463EE20047FD55 /* NightscoutPreferences.swift */; };
 		FE66D16B291F74F8005D6F77 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE66D16A291F74F8005D6F77 /* Bundle+Extensions.swift */; };
 		FEFFA7A22929FE49007B8193 /* UIDevice+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */; };
@@ -971,6 +1033,7 @@
 		118DF7652C5ECBC60067FEB7 /* CancelOverrideIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CancelOverrideIntent.swift; sourceTree = "<group>"; };
 		118DF7672C5ECBC60067FEB7 /* OverridePresetEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridePresetEntity.swift; sourceTree = "<group>"; };
 		118DF7682C5ECBC60067FEB7 /* OverridePresetsIntentRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridePresetsIntentRequest.swift; sourceTree = "<group>"; };
+		1526A587AD1544C495050662 /* QuickPickBolusesInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickPickBolusesInfoView.swift; sourceTree = "<group>"; };
 		19012CDB291D2CB900FB8210 /* LoopStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStats.swift; sourceTree = "<group>"; };
 		190EBCC329FF136900BA767D /* UserInterfaceSettingsDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInterfaceSettingsDataFlow.swift; sourceTree = "<group>"; };
 		190EBCC529FF138000BA767D /* UserInterfaceSettingsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInterfaceSettingsProvider.swift; sourceTree = "<group>"; };
@@ -1002,13 +1065,10 @@
 		19F95FF429F10FCF00314DDC /* StatProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatProvider.swift; sourceTree = "<group>"; };
 		19F95FF629F10FEE00314DDC /* StatStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatStateModel.swift; sourceTree = "<group>"; };
 		19F95FF929F1102A00314DDC /* StatRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatRootView.swift; sourceTree = "<group>"; };
-		1CAE81192B118804DCD23034 /* SnoozeProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeProvider.swift; sourceTree = "<group>"; };
 		223EC0494F55A91E3EA69EF4 /* TreatmentsStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TreatmentsStateModel.swift; sourceTree = "<group>"; };
-		22963BD06A9C83959D4914E4 /* GlucoseNotificationSettingsRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationSettingsRootView.swift; sourceTree = "<group>"; };
 		2AD22C985B79A2F0D2EA3D9D /* PumpConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigRootView.swift; sourceTree = "<group>"; };
+		2EEC5C2927FA4C5DA61A1A40 /* QuickPickBolusesConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickPickBolusesConfigProvider.swift; sourceTree = "<group>"; };
 		2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigDataFlow.swift; sourceTree = "<group>"; };
-		3260468377DA9DB4DEE9AF6D /* GlucoseNotificationSettingsDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationSettingsDataFlow.swift; sourceTree = "<group>"; };
-		36A708CDB546692C2230B385 /* SnoozeDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeDataFlow.swift; sourceTree = "<group>"; };
 		36F58DDD71F0E795464FA3F0 /* TargetsEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorStateModel.swift; sourceTree = "<group>"; };
 		3811DE0725C9D32E00A708ED /* BaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseView.swift; sourceTree = "<group>"; };
 		3811DE0825C9D32F00A708ED /* BaseProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseProvider.swift; sourceTree = "<group>"; };
@@ -1334,14 +1394,15 @@
 		6B1A8D252B14D91700E76752 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		6B1A8D2D2B156EEF00E76752 /* LiveActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityManager.swift; sourceTree = "<group>"; };
 		6BCF84DC2B16843A003AD46E /* LiveActitiyAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActitiyAttributes.swift; sourceTree = "<group>"; };
-		715120D12D3C2B84005D9FB6 /* GlucoseNotificationsOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationsOption.swift; sourceTree = "<group>"; };
 		71D44AAA2CA5F5EA0036EE9E /* AlertPermissionsChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertPermissionsChecker.swift; sourceTree = "<group>"; };
 		79BDA519C9B890FD9A5DFCF3 /* ISFEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ISFEditorDataFlow.swift; sourceTree = "<group>"; };
 		7E22146D3DF4853786C78132 /* CarbRatioEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CarbRatioEditorDataFlow.swift; sourceTree = "<group>"; };
 		8782B44544F38F2B2D82C38E /* NightscoutConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigRootView.swift; sourceTree = "<group>"; };
 		881E04BA5E0A003DE8E0A9C6 /* HistoryRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = HistoryRootView.swift; sourceTree = "<group>"; };
+		8A54068ABDAE4898B243DF14 /* QuickPickBolusesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickPickBolusesView.swift; sourceTree = "<group>"; };
 		8A9134292D63D9A1007F8874 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
 		8A91342B2D63D9A2007F8874 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = "<group>"; };
+		91711E7CA54747EFAD6540EE /* QuickPickBolusesConfigStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickPickBolusesConfigStateModel.swift; sourceTree = "<group>"; };
 		920DDB21E5D0EB813197500D /* ConfigEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorRootView.swift; sourceTree = "<group>"; };
 		9455FA2D92E77A6C4AFED8A3 /* HistoryStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = HistoryStateModel.swift; sourceTree = "<group>"; };
 		96653287EDB276A111288305 /* ManualTempBasalDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalDataFlow.swift; sourceTree = "<group>"; };
@@ -1350,18 +1411,74 @@
 		A0A48AE3AC813A49A517846A /* NightscoutConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigStateModel.swift; sourceTree = "<group>"; };
 		A401509D21F7F35D4E109EDA /* HistoryDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = HistoryDataFlow.swift; sourceTree = "<group>"; };
 		A8630D58BDAD6D9C650B9B39 /* PumpConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigProvider.swift; sourceTree = "<group>"; };
+		AABB00012C54389F00211FAC /* TreatmentsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreatmentsSettingsView.swift; sourceTree = "<group>"; };
 		AAFF91130F2FCCC7EBBA11AD /* BasalProfileEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorStateModel.swift; sourceTree = "<group>"; };
 		AF65DA88F972B56090AD6AC3 /* PumpConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigDataFlow.swift; sourceTree = "<group>"; };
 		B015AFE12E500000000D7351 /* BolusSafetyValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusSafetyValidator.swift; sourceTree = "<group>"; };
 		B015AFE42E500000000D7351 /* BolusSafetyValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusSafetyValidatorTests.swift; sourceTree = "<group>"; };
 		B3919BBB515547118D684CA2 /* SettingsSearchTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SettingsSearchTests.swift; sourceTree = "<group>"; };
-		B5822B15939E719628E9FF7C /* SnoozeRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeRootView.swift; sourceTree = "<group>"; };
 		B6E925122EB3932A0076D719 /* OmnipodKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = OmnipodKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		B9B5C0607505A38F256BF99A /* CGMSettingsDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CGMSettingsDataFlow.swift; sourceTree = "<group>"; };
 		BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorDataFlow.swift; sourceTree = "<group>"; };
 		BD04ECCD2D299522008C5FEB /* BolusProgressOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusProgressOverlay.swift; sourceTree = "<group>"; };
 		BD0B2EF22C5998E600B3298F /* MealPresetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MealPresetView.swift; sourceTree = "<group>"; };
 		BD10516C2DA986DC007C6D89 /* PulsingLogoAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PulsingLogoAnimation.swift; sourceTree = "<group>"; };
+		BD1179212F4E22C100F90001 /* TrioAlertManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrioAlertManager.swift; sourceTree = "<group>"; };
+		BD1179232F4E22C100F90001 /* TrioModalAlertScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrioModalAlertScheduler.swift; sourceTree = "<group>"; };
+		BD1179252F4E22C100F90001 /* TrioUserNotificationAlertScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrioUserNotificationAlertScheduler.swift; sourceTree = "<group>"; };
+		BD1179292F4E22C100F90001 /* TrioAlertCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrioAlertCategory.swift; sourceTree = "<group>"; };
+		BD1179312F4E22C100F90001 /* CriticalAlertAudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CriticalAlertAudioPlayer.swift; sourceTree = "<group>"; };
+		BD1179332F4E22C100F90001 /* Sounds */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Sounds; sourceTree = "<group>"; };
+		BD1179412F4E22C100F90001 /* ForecastedGlucoseEvaluator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastedGlucoseEvaluator.swift; sourceTree = "<group>"; };
+		BD1179512F4E22C100F90001 /* DayNightOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayNightOptions.swift; sourceTree = "<group>"; };
+		BD1179532F4E22C100F90001 /* GlucoseAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlert.swift; sourceTree = "<group>"; };
+		BD1179552F4E22C100F90001 /* GlucoseAlertConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertConfiguration.swift; sourceTree = "<group>"; };
+		BD1179572F4E22C100F90001 /* GlucoseAlertType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertType.swift; sourceTree = "<group>"; };
+		BD1179592F4E22C100F90001 /* GlucoseAlertsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertsStore.swift; sourceTree = "<group>"; };
+		BD11795D2F4E22C100F90001 /* GlucoseAlertCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertCoordinator.swift; sourceTree = "<group>"; };
+		BD1179642F4E22C100F90001 /* GlucoseAlertsDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertsDataFlow.swift; sourceTree = "<group>"; };
+		BD1179662F4E22C100F90001 /* GlucoseAlertsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertsProvider.swift; sourceTree = "<group>"; };
+		BD1179682F4E22C100F90001 /* GlucoseAlertsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertsStateModel.swift; sourceTree = "<group>"; };
+		BD11796A2F4E22C100F90001 /* GlucoseAlertsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertsRootView.swift; sourceTree = "<group>"; };
+		BD11796C2F4E22C100F90001 /* GlucoseAlertEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertEditorView.swift; sourceTree = "<group>"; };
+		BD11796E2F4E22C100F90001 /* AddGlucoseAlertSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGlucoseAlertSheet.swift; sourceTree = "<group>"; };
+		BD1179722F4E22C100F90001 /* AlarmEnumMenuPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmEnumMenuPicker.swift; sourceTree = "<group>"; };
+		BD1179742F4E22C100F90001 /* AlarmBGSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmBGSection.swift; sourceTree = "<group>"; };
+		BD1179782F4E22C100F90001 /* AlarmActiveSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmActiveSection.swift; sourceTree = "<group>"; };
+		BD11797A2F4E22C100F90001 /* AlarmAudioSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmAudioSection.swift; sourceTree = "<group>"; };
+		BD11797C2F4E22C100F90001 /* AlarmSoundCatalog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmSoundCatalog.swift; sourceTree = "<group>"; };
+		BD11797E2F4E22C100F90001 /* AlarmWindowIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmWindowIcon.swift; sourceTree = "<group>"; };
+		BD1179842F4E22C100F90001 /* DeviceAlertSeverity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAlertSeverity.swift; sourceTree = "<group>"; };
+		BD1179862F4E22C100F90001 /* DeviceAlertSeverityConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAlertSeverityConfig.swift; sourceTree = "<group>"; };
+		BD1179882F4E22C100F90001 /* DeviceAlertsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAlertsStore.swift; sourceTree = "<group>"; };
+		BD11798A2F4E22C100F90001 /* DeviceAlarmsDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAlarmsDataFlow.swift; sourceTree = "<group>"; };
+		BD11798C2F4E22C100F90001 /* DeviceAlarmsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAlarmsProvider.swift; sourceTree = "<group>"; };
+		BD11798E2F4E22C100F90001 /* DeviceAlarmsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAlarmsStateModel.swift; sourceTree = "<group>"; };
+		BD1179902F4E22C100F90001 /* DeviceAlarmsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAlarmsRootView.swift; sourceTree = "<group>"; };
+		BD1179922F4E22C100F90001 /* DeviceAlarmEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAlarmEditorView.swift; sourceTree = "<group>"; };
+		BD1179942F4E22C100F90001 /* AddDeviceAlarmSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddDeviceAlarmSheet.swift; sourceTree = "<group>"; };
+		BD1179A12F4E22C100F90001 /* AlarmWindowsDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmWindowsDataFlow.swift; sourceTree = "<group>"; };
+		BD1179A32F4E22C100F90001 /* AlarmWindowsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmWindowsProvider.swift; sourceTree = "<group>"; };
+		BD1179A52F4E22C100F90001 /* AlarmWindowsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmWindowsStateModel.swift; sourceTree = "<group>"; };
+		BD1179A72F4E22C100F90001 /* AlarmWindowsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmWindowsRootView.swift; sourceTree = "<group>"; };
+		BD1179AB2F4E22C100F90001 /* NotLoopingMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotLoopingMonitor.swift; sourceTree = "<group>"; };
+		BD1179AD2F4E22C100F90001 /* GlucoseSourceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseSourceKey.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A002 /* AlertMuterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertMuterTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A003 /* DeviceAlertsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAlertsStoreTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastedGlucoseEvaluatorTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A007 /* GlucoseAlertConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertConfigurationTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A008 /* GlucoseAlertTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A009 /* TrioAlertCategoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrioAlertCategoryTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A00A /* GlucoseAlertCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertCoordinatorTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A00B /* TrioModalAlertSchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrioModalAlertSchedulerTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A00C /* NotLoopingMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotLoopingMonitorTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A00D /* AlertCatalogRegistryEntriesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertCatalogRegistryEntriesTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A00E /* DanaKitAlertEmissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DanaKitAlertEmissionTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A00F /* MedtrumKitAlertEmissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MedtrumKitAlertEmissionTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A010 /* MinimedKitAlertEmissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimedKitAlertEmissionTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A011 /* OmnipodKitAlertEmissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmnipodKitAlertEmissionTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A012 /* RileyLinkKitAlertEmissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RileyLinkKitAlertEmissionTests.swift; sourceTree = "<group>"; };
+		BD11A000000000000000A013 /* TandemKitAlertEmissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TandemKitAlertEmissionTests.swift; sourceTree = "<group>"; };
 		BD11C000000000000000C001 /* CGMSensorDisplayState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMSensorDisplayState.swift; sourceTree = "<group>"; };
 		BD11C000000000000000C003 /* SensorLifecycleArcView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorLifecycleArcView.swift; sourceTree = "<group>"; };
 		BD11C000000000000000C004 /* SensorStatusTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorStatusTagView.swift; sourceTree = "<group>"; };
@@ -1496,7 +1613,13 @@
 		C2BA6B982F758E7600348E6A /* NotificationIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationIdentifiers.swift; sourceTree = "<group>"; };
 		C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalRootView.swift; sourceTree = "<group>"; };
 		C8D1A7CA8C10C4403D4BBFA7 /* TreatmentsDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TreatmentsDataFlow.swift; sourceTree = "<group>"; };
+		CA01000000000000000010C1 /* AlertCatalogVendor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertCatalogVendor.swift; sourceTree = "<group>"; };
+		CA01000000000000000010C3 /* AlertCatalogRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertCatalogRegistry.swift; sourceTree = "<group>"; };
+		CA01000000000000000010C5 /* CGMManagerAlertOwnership.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMManagerAlertOwnership.swift; sourceTree = "<group>"; };
 		CA02000000000000000010C1 /* DeliveryLimitsSyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeliveryLimitsSyncTests.swift; sourceTree = "<group>"; };
+		CA03000000000000000010C1 /* AlertCatalogRegistryOmniFaultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertCatalogRegistryOmniFaultTests.swift; sourceTree = "<group>"; };
+		CA05000000000000000010C1 /* AlarmGramsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmGramsSection.swift; sourceTree = "<group>"; };
+		CA06000000000000000010C1 /* GlucoseAlertsStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseAlertsStoreTests.swift; sourceTree = "<group>"; };
 		CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawFetchedProfile.swift; sourceTree = "<group>"; };
 		CC76E9502BD4812E008BEB61 /* Forecast+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Forecast+helper.swift"; sourceTree = "<group>"; };
 		CE1856F42ADC4858007E39C7 /* AddCarbPresetIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCarbPresetIntent.swift; sourceTree = "<group>"; };
@@ -1551,9 +1674,12 @@
 		CEE9A65B2BBB41C800EB5194 /* CalibrationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalibrationService.swift; sourceTree = "<group>"; };
 		CEE9A65D2BBC9F6500EB5194 /* CalibrationsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalibrationsTests.swift; sourceTree = "<group>"; };
 		CEF1ED6A2D58FB4600FAF41E /* CGMOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGMOptions.swift; sourceTree = "<group>"; };
+		CF96FC200503405E94334390 /* SlideToConfirmView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideToConfirmView.swift; sourceTree = "<group>"; };
 		CFCFE0781F9074C2917890E8 /* ManualTempBasalStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalStateModel.swift; sourceTree = "<group>"; };
 		D0BDC6993C1087310EDFC428 /* CarbRatioEditorRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CarbRatioEditorRootView.swift; sourceTree = "<group>"; };
-		DC2C6489D29ECCCAD78E0721 /* GlucoseNotificationSettingsStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationSettingsStateModel.swift; sourceTree = "<group>"; };
+		D543DE17E0054AF0821411BB /* QuickPickBolusesConfigDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickPickBolusesConfigDataFlow.swift; sourceTree = "<group>"; };
+		D7C5E1F4A7B91E2F3C5D6E71 /* TimeInterval+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Convenience.swift"; sourceTree = "<group>"; };
+		DB3EBC8BA17C4627BD32C58F /* QuickPickBolusesConfigRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickPickBolusesConfigRootView.swift; sourceTree = "<group>"; };
 		DD09D47A2C5986D1003FEA5D /* CalendarEventSettingsDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarEventSettingsDataFlow.swift; sourceTree = "<group>"; };
 		DD09D47C2C5986DA003FEA5D /* CalendarEventSettingsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarEventSettingsProvider.swift; sourceTree = "<group>"; };
 		DD09D47E2C5986E5003FEA5D /* CalendarEventSettingsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarEventSettingsStateModel.swift; sourceTree = "<group>"; };
@@ -1638,6 +1764,7 @@
 		DD4C57A92D73B3D9001BFF2C /* RestartLiveActivityIntentRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartLiveActivityIntentRequest.swift; sourceTree = "<group>"; };
 		DD4C581E2D73C43D001BFF2C /* LoopStatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopStatsView.swift; sourceTree = "<group>"; };
 		DD4FFF322D458EE600B6CFF9 /* GarminWatchState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarminWatchState.swift; sourceTree = "<group>"; };
+		DD500A712807000000000100 /* SnoozeAlertsSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnoozeAlertsSheetView.swift; sourceTree = "<group>"; };
 		DD5DC9F02CF3D96E00AB8703 /* AdjustmentsStateModel+Overrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentsStateModel+Overrides.swift"; sourceTree = "<group>"; };
 		DD5DC9F22CF3D9D600AB8703 /* AdjustmentsStateModel+TempTargets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdjustmentsStateModel+TempTargets.swift"; sourceTree = "<group>"; };
 		DD5DC9F62CF3DA9300AB8703 /* TargetPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetPicker.swift; sourceTree = "<group>"; };
@@ -1775,13 +1902,11 @@
 		E06B9119275B5EEA003C04B6 /* Array+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extension.swift"; sourceTree = "<group>"; };
 		E0CC2C5B275B9DAE00A7BC71 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; };
 		E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitSample.swift; sourceTree = "<group>"; };
-		E26904AACA8D9C15D229D675 /* SnoozeStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeStateModel.swift; sourceTree = "<group>"; };
 		E592A36F2CEEC01E009A472C /* ContactTrickEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactTrickEntry.swift; sourceTree = "<group>"; };
 		E592A3712CEEC038009A472C /* ContactImageRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageRootView.swift; sourceTree = "<group>"; };
 		E592A3732CEEC038009A472C /* ContactImageDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageDataFlow.swift; sourceTree = "<group>"; };
 		E592A3742CEEC038009A472C /* ContactImageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageProvider.swift; sourceTree = "<group>"; };
 		E592A3752CEEC038009A472C /* ContactImageStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageStateModel.swift; sourceTree = "<group>"; };
-		E625985B47742D498CB1681A /* GlucoseNotificationSettingsProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationSettingsProvider.swift; sourceTree = "<group>"; };
 		F816825D28DB441200054060 /* HeartBeatManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeartBeatManager.swift; sourceTree = "<group>"; };
 		F816825F28DB441800054060 /* BluetoothTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothTransmitter.swift; sourceTree = "<group>"; };
 		F90692A9274B7AAE0037068D /* HealthKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitManager.swift; sourceTree = "<group>"; };
@@ -1836,7 +1961,6 @@
 				CE95BF5F2BA7715800DC3DE3 /* MockKit.framework in Frameworks */,
 				DD17A0292E3FE0BD008E1BF0 /* SwiftJWT in Frameworks */,
 				3BD9687F2D8DDD8800899469 /* CryptoSwift in Frameworks */,
-				38DF1789276FC8C400B3528F /* SwiftMessages in Frameworks */,
 				3B4BA7842D8DBD690069D5B8 /* RileyLinkKit.framework in Frameworks */,
 				CE95BF612BA7715900DC3DE3 /* MockKitUI.framework in Frameworks */,
 				E0CC2C5C275B9F0F00A7BC71 /* HealthKit.framework in Frameworks */,
@@ -1898,12 +2022,15 @@
 			path = ConfigEditor;
 			sourceTree = "<group>";
 		};
-		0A67A70F9438DB6586398458 /* View */ = {
+		08A79EA89BFA498F80F4D6FE /* QuickPickBolusesConfig */ = {
 			isa = PBXGroup;
 			children = (
-				B5822B15939E719628E9FF7C /* SnoozeRootView.swift */,
+				D543DE17E0054AF0821411BB /* QuickPickBolusesConfigDataFlow.swift */,
+				2EEC5C2927FA4C5DA61A1A40 /* QuickPickBolusesConfigProvider.swift */,
+				91711E7CA54747EFAD6540EE /* QuickPickBolusesConfigStateModel.swift */,
+				2148032F6F184487B49B1095 /* View */,
 			);
-			path = View;
+			path = QuickPickBolusesConfig;
 			sourceTree = "<group>";
 		};
 		0D76BBC81CEDC1A0050F45EF /* View */ = {
@@ -2103,15 +2230,12 @@
 			path = View;
 			sourceTree = "<group>";
 		};
-		29B478DF61BF8D270F7D8954 /* Snooze */ = {
+		2148032F6F184487B49B1095 /* View */ = {
 			isa = PBXGroup;
 			children = (
-				36A708CDB546692C2230B385 /* SnoozeDataFlow.swift */,
-				1CAE81192B118804DCD23034 /* SnoozeProvider.swift */,
-				E26904AACA8D9C15D229D675 /* SnoozeStateModel.swift */,
-				0A67A70F9438DB6586398458 /* View */,
+				DB3EBC8BA17C4627BD32C58F /* QuickPickBolusesConfigRootView.swift */,
 			);
-			path = Snooze;
+			path = View;
 			sourceTree = "<group>";
 		};
 		34CA4DF169B53D67EF18ED8A /* View */ = {
@@ -2143,7 +2267,9 @@
 				195D80B22AF696EE00D25097 /* DynamicSettings */,
 				C2AA6CF52E1A734A00BF6C16 /* SettingsExport */,
 				DD17454C2C55CA0200211FAC /* GeneralSettings */,
-				F66B236E00924A05D6A9F9DF /* GlucoseNotificationSettings */,
+				BD1179602F4E22C100F90001 /* GlucoseAlerts */,
+				BD11799A2F4E22C100F90001 /* DeviceAlarms */,
+				BD1179A82F4E22C100F90001 /* AlarmWindows */,
 				F90692CD274B99850037068D /* HealthKit */,
 				3811DE2725C9D49500A708ED /* Home */,
 				19E1F7E629D0828B005C8D20 /* IconConfig */,
@@ -2158,8 +2284,8 @@
 				DD9ECB6B2CA99FA400AA7C45 /* RemoteControlConfig */,
 				3811DE3825C9D4A100A708ED /* Settings */,
 				110AEDEA2C51A0AE00615CC9 /* ShortcutsConfig */,
+				08A79EA89BFA498F80F4D6FE /* QuickPickBolusesConfig */,
 				DD17451E2C55520000211FAC /* SMBSettings */,
-				29B478DF61BF8D270F7D8954 /* Snooze */,
 				19F95FF129F10F9C00314DDC /* Stat */,
 				DD17452C2C55AE3500211FAC /* TargetBehavoir */,
 				6517011F19F244F64E1FF14B /* TargetsEditor */,
@@ -2250,6 +2376,7 @@
 				3811DE2E25C9D49500A708ED /* HomeRootView.swift */,
 				3833B51E260264AC003021B3 /* Chart */,
 				3833B51F260264B6003021B3 /* Header */,
+				FDB1D1005DA54C048E622761 /* QuickPickBoluses */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -2280,6 +2407,7 @@
 		3811DE9125C9D88200A708ED /* Services */ = {
 			isa = PBXGroup;
 			children = (
+				BD1179262F4E22C100F90001 /* Alerts */,
 				3811DE9225C9D88200A708ED /* Appearance */,
 				DDA9AC072D67291600E6F1A9 /* AppVersionChecker */,
 				CEB434E128B8F9BC00B70274 /* Bluetooth */,
@@ -2369,6 +2497,7 @@
 			children = (
 				388E597125AD9CF10019842D /* json */,
 				388E596E25AD96040019842D /* javascript */,
+				BD1179332F4E22C100F90001 /* Sounds */,
 				3811DEC725C9DA7300A708ED /* Trio.entitlements */,
 				388E596425AD948E0019842D /* Info.plist */,
 				8A91342B2D63D9A2007F8874 /* InfoPlist.xcstrings */,
@@ -2539,6 +2668,7 @@
 				DD1745282C55642100211FAC /* SettingInputSection.swift */,
 				48B83503461B4F8D97B30115 /* SettingsSearchHighlight.swift */,
 				DD17452A2C556E8100211FAC /* SettingInputHintView.swift */,
+				DD500A712807000000000100 /* SnoozeAlertsSheetView.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
@@ -2602,6 +2732,7 @@
 		388E5A5925B6F0250019842D /* Models */ = {
 			isa = PBXGroup;
 			children = (
+				BD11795B2F4E22C100F90001 /* GlucoseAlerts */,
 				49C782A62F73D9870062B0DD /* AlertEntry.swift */,
 				DDA40BB92F4DB18100257798 /* AlgorithmGlucose.swift */,
 				3E62C7812F54CC1600433237 /* BolusDisplayThreshold.swift */,
@@ -2621,7 +2752,7 @@
 				49090A8C2E9FE8D200D0F5DB /* GarminWatchSettings.swift */,
 				DD4FFF322D458EE600B6CFF9 /* GarminWatchState.swift */,
 				DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */,
-				715120D12D3C2B84005D9FB6 /* GlucoseNotificationsOption.swift */,
+				BD1179AD2F4E22C100F90001 /* GlucoseSourceKey.swift */,
 				E0D4F80427513ECF00BDF1FE /* HealthKitSample.swift */,
 				382C133625F13A1E00715CE1 /* InsulinSensitivities.swift */,
 				38887CCD25F5725200944304 /* IOBEntry.swift */,
@@ -2706,6 +2837,7 @@
 				49239B422EEA27AD00469145 /* TempTargetCalculations.swift */,
 				DDCAE8322D78D49C00B1BB51 /* TherapySettingsUtil.swift */,
 				DDD5889C2DDDC9A900C8848D /* TimeAgoFormatter.swift */,
+				D7C5E1F4A7B91E2F3C5D6E71 /* TimeInterval+Convenience.swift */,
 				BD2FF19F2AE29D43005D1C5D /* ToggleStyles.swift */,
 				CEB434E428B8FF5D00B70274 /* UIColor.swift */,
 				FEFFA7A12929FE49007B8193 /* UIDevice+Extensions.swift */,
@@ -2858,12 +2990,30 @@
 				CA02000000000000000010C1 /* DeliveryLimitsSyncTests.swift */,
 				3BAAE60B2DE776630049589B /* DynamicISFEnableTests.swift */,
 				38FCF3F825E902C20078B0D1 /* FileStorageTests.swift */,
+				BD11A000000000000000A002 /* AlertMuterTests.swift */,
+				BD11A000000000000000A013 /* TandemKitAlertEmissionTests.swift */,
+				BD11A000000000000000A012 /* RileyLinkKitAlertEmissionTests.swift */,
+				BD11A000000000000000A011 /* OmnipodKitAlertEmissionTests.swift */,
+				BD11A000000000000000A010 /* MinimedKitAlertEmissionTests.swift */,
+				BD11A000000000000000A00F /* MedtrumKitAlertEmissionTests.swift */,
+				BD11A000000000000000A00E /* DanaKitAlertEmissionTests.swift */,
+				BD11A000000000000000A00D /* AlertCatalogRegistryEntriesTests.swift */,
+				BD11A000000000000000A00C /* NotLoopingMonitorTests.swift */,
+				BD11A000000000000000A00B /* TrioModalAlertSchedulerTests.swift */,
+				BD11A000000000000000A00A /* GlucoseAlertCoordinatorTests.swift */,
+				CA06000000000000000010C1 /* GlucoseAlertsStoreTests.swift */,
+				BD11A000000000000000A009 /* TrioAlertCategoryTests.swift */,
+				BD11A000000000000000A008 /* GlucoseAlertTests.swift */,
+				BD11A000000000000000A007 /* GlucoseAlertConfigurationTests.swift */,
+				BD11A000000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift */,
+				BD11A000000000000000A003 /* DeviceAlertsStoreTests.swift */,
 				3B997DCE2DC00A3A006B6BB2 /* JSONImporterTests.swift */,
 				CE1F6DD82BADF4620064EB8D /* PluginManagerTests.swift */,
 				3B5CD2C72D4AECD500CE213C /* OpenAPSSwiftTests */,
 				B3919BBB515547118D684CA2 /* SettingsSearchTests.swift */,
 				BD8FC0532D66186000B95AED /* TestError.swift */,
 				BD8FC0702D661B0000B95AED /* TidepoolTherapySettingsTests.swift */,
+				CA03000000000000000010C1 /* AlertCatalogRegistryOmniFaultTests.swift */,
 			);
 			path = TrioTests;
 			sourceTree = "<group>";
@@ -3245,6 +3395,115 @@
 			path = View;
 			sourceTree = "<group>";
 		};
+		BD1179262F4E22C100F90001 /* Alerts */ = {
+			isa = PBXGroup;
+			children = (
+				BD1179312F4E22C100F90001 /* CriticalAlertAudioPlayer.swift */,
+				BD1179412F4E22C100F90001 /* ForecastedGlucoseEvaluator.swift */,
+				BD11795D2F4E22C100F90001 /* GlucoseAlertCoordinator.swift */,
+				BD1179592F4E22C100F90001 /* GlucoseAlertsStore.swift */,
+				BD1179882F4E22C100F90001 /* DeviceAlertsStore.swift */,
+				BD1179AB2F4E22C100F90001 /* NotLoopingMonitor.swift */,
+				BD1179292F4E22C100F90001 /* TrioAlertCategory.swift */,
+				BD1179212F4E22C100F90001 /* TrioAlertManager.swift */,
+				BD1179232F4E22C100F90001 /* TrioModalAlertScheduler.swift */,
+				BD1179252F4E22C100F90001 /* TrioUserNotificationAlertScheduler.swift */,
+				CA01000000000000000010C1 /* AlertCatalogVendor.swift */,
+				CA01000000000000000010C3 /* AlertCatalogRegistry.swift */,
+				CA01000000000000000010C5 /* CGMManagerAlertOwnership.swift */,
+			);
+			path = Alerts;
+			sourceTree = "<group>";
+		};
+		BD11795B2F4E22C100F90001 /* GlucoseAlerts */ = {
+			isa = PBXGroup;
+			children = (
+				BD11797C2F4E22C100F90001 /* AlarmSoundCatalog.swift */,
+				BD1179512F4E22C100F90001 /* DayNightOptions.swift */,
+				BD1179532F4E22C100F90001 /* GlucoseAlert.swift */,
+				BD1179552F4E22C100F90001 /* GlucoseAlertConfiguration.swift */,
+				BD1179572F4E22C100F90001 /* GlucoseAlertType.swift */,
+				BD1179842F4E22C100F90001 /* DeviceAlertSeverity.swift */,
+				BD1179862F4E22C100F90001 /* DeviceAlertSeverityConfig.swift */,
+			);
+			path = GlucoseAlerts;
+			sourceTree = "<group>";
+		};
+		BD1179602F4E22C100F90001 /* GlucoseAlerts */ = {
+			isa = PBXGroup;
+			children = (
+				BD1179642F4E22C100F90001 /* GlucoseAlertsDataFlow.swift */,
+				BD1179662F4E22C100F90001 /* GlucoseAlertsProvider.swift */,
+				BD1179682F4E22C100F90001 /* GlucoseAlertsStateModel.swift */,
+				BD1179612F4E22C100F90001 /* View */,
+			);
+			path = GlucoseAlerts;
+			sourceTree = "<group>";
+		};
+		BD1179612F4E22C100F90001 /* View */ = {
+			isa = PBXGroup;
+			children = (
+				BD11796E2F4E22C100F90001 /* AddGlucoseAlertSheet.swift */,
+				BD11796C2F4E22C100F90001 /* GlucoseAlertEditorView.swift */,
+				BD11796A2F4E22C100F90001 /* GlucoseAlertsRootView.swift */,
+				BD1179622F4E22C100F90001 /* Components */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		BD1179622F4E22C100F90001 /* Components */ = {
+			isa = PBXGroup;
+			children = (
+				BD1179782F4E22C100F90001 /* AlarmActiveSection.swift */,
+				BD11797A2F4E22C100F90001 /* AlarmAudioSection.swift */,
+				BD1179742F4E22C100F90001 /* AlarmBGSection.swift */,
+				CA05000000000000000010C1 /* AlarmGramsSection.swift */,
+				BD1179722F4E22C100F90001 /* AlarmEnumMenuPicker.swift */,
+				BD11797E2F4E22C100F90001 /* AlarmWindowIcon.swift */,
+			);
+			path = Components;
+			sourceTree = "<group>";
+		};
+		BD11799A2F4E22C100F90001 /* DeviceAlarms */ = {
+			isa = PBXGroup;
+			children = (
+				BD11798A2F4E22C100F90001 /* DeviceAlarmsDataFlow.swift */,
+				BD11798C2F4E22C100F90001 /* DeviceAlarmsProvider.swift */,
+				BD11798E2F4E22C100F90001 /* DeviceAlarmsStateModel.swift */,
+				BD11799B2F4E22C100F90001 /* View */,
+			);
+			path = DeviceAlarms;
+			sourceTree = "<group>";
+		};
+		BD11799B2F4E22C100F90001 /* View */ = {
+			isa = PBXGroup;
+			children = (
+				BD1179942F4E22C100F90001 /* AddDeviceAlarmSheet.swift */,
+				BD1179922F4E22C100F90001 /* DeviceAlarmEditorView.swift */,
+				BD1179902F4E22C100F90001 /* DeviceAlarmsRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		BD1179A82F4E22C100F90001 /* AlarmWindows */ = {
+			isa = PBXGroup;
+			children = (
+				BD1179A12F4E22C100F90001 /* AlarmWindowsDataFlow.swift */,
+				BD1179A32F4E22C100F90001 /* AlarmWindowsProvider.swift */,
+				BD1179A52F4E22C100F90001 /* AlarmWindowsStateModel.swift */,
+				BD1179A92F4E22C100F90001 /* View */,
+			);
+			path = AlarmWindows;
+			sourceTree = "<group>";
+		};
+		BD1179A92F4E22C100F90001 /* View */ = {
+			isa = PBXGroup;
+			children = (
+				BD1179A72F4E22C100F90001 /* AlarmWindowsRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
 		BD11C002000000000000C000 /* SensorLifecycle */ = {
 			isa = PBXGroup;
 			children = (
@@ -3698,6 +3957,7 @@
 				DD1745122C54169400211FAC /* DevicesView.swift */,
 				DD1745142C54388A00211FAC /* TherapySettingsView.swift */,
 				DD1745162C54389F00211FAC /* FeatureSettingsView.swift */,
+				AABB00012C54389F00211FAC /* TreatmentsSettingsView.swift */,
 				DD1745182C543B5700211FAC /* NotificationsView.swift */,
 				DD17451C2C543C5F00211FAC /* ServicesView.swift */,
 				DD1745362C55B74200211FAC /* AlgorithmSettings.swift */,
@@ -4230,25 +4490,6 @@
 			path = View;
 			sourceTree = "<group>";
 		};
-		F5DE2E6D7B2133BBD3353DC7 /* View */ = {
-			isa = PBXGroup;
-			children = (
-				22963BD06A9C83959D4914E4 /* GlucoseNotificationSettingsRootView.swift */,
-			);
-			path = View;
-			sourceTree = "<group>";
-		};
-		F66B236E00924A05D6A9F9DF /* GlucoseNotificationSettings */ = {
-			isa = PBXGroup;
-			children = (
-				3260468377DA9DB4DEE9AF6D /* GlucoseNotificationSettingsDataFlow.swift */,
-				E625985B47742D498CB1681A /* GlucoseNotificationSettingsProvider.swift */,
-				DC2C6489D29ECCCAD78E0721 /* GlucoseNotificationSettingsStateModel.swift */,
-				F5DE2E6D7B2133BBD3353DC7 /* View */,
-			);
-			path = GlucoseNotificationSettings;
-			sourceTree = "<group>";
-		};
 		F75CB57ED6971B46F8756083 /* CGMSettings */ = {
 			isa = PBXGroup;
 			children = (
@@ -4287,6 +4528,16 @@
 			path = View;
 			sourceTree = "<group>";
 		};
+		FDB1D1005DA54C048E622761 /* QuickPickBoluses */ = {
+			isa = PBXGroup;
+			children = (
+				CF96FC200503405E94334390 /* SlideToConfirmView.swift */,
+				1526A587AD1544C495050662 /* QuickPickBolusesInfoView.swift */,
+				8A54068ABDAE4898B243DF14 /* QuickPickBolusesView.swift */,
+			);
+			path = QuickPickBoluses;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -4315,7 +4566,6 @@
 				3811DE0F25C9D37700A708ED /* Swinject */,
 				38B17B6525DD90E0005CAE3D /* SwiftDate */,
 				3833B46C26012030003021B3 /* Algorithms */,
-				38DF1788276FC8C400B3528F /* SwiftMessages */,
 				CEB434FC28B90B7C00B70274 /* SwiftCharts */,
 				B958F1B62BA0711600484851 /* MKRingProgressView */,
 				3BD9687B2D8DDD4600899469 /* SlideButton */,
@@ -4493,7 +4743,6 @@
 				3811DE0E25C9D37700A708ED /* XCRemoteSwiftPackageReference "Swinject" */,
 				38B17B6425DD90E0005CAE3D /* XCRemoteSwiftPackageReference "SwiftDate" */,
 				3833B46B26012030003021B3 /* XCRemoteSwiftPackageReference "swift-algorithms" */,
-				38DF1787276FC8C300B3528F /* XCRemoteSwiftPackageReference "SwiftMessages" */,
 				CEB434FB28B90B7C00B70274 /* XCRemoteSwiftPackageReference "SwiftCharts" */,
 				B958F1B52BA0711600484851 /* XCRemoteSwiftPackageReference "MKRingProgressView" */,
 				3BD9687A2D8DDD4600899469 /* XCRemoteSwiftPackageReference "SlideButton" */,
@@ -4530,6 +4779,7 @@
 				19DA48E829CD339B00EEA1E7 /* Assets.xcassets in Resources */,
 				8A91342A2D63D9A1007F8874 /* Localizable.xcstrings in Resources */,
 				388E596F25AD96040019842D /* javascript in Resources */,
+				BD1179342F4E22C100F90001 /* Sounds in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -4762,6 +5012,7 @@
 				BDC2EA452C3043B000E5BBD0 /* OverrideStorage.swift in Sources */,
 				3811DE4225C9D4A100A708ED /* SettingsDataFlow.swift in Sources */,
 				DDD5889D2DDDC9A900C8848D /* TimeAgoFormatter.swift in Sources */,
+				D7C5E1F4A7B91E2F3C5D6E70 /* TimeInterval+Convenience.swift in Sources */,
 				CEE9A6562BBB418300EB5194 /* CalibrationsRootView.swift in Sources */,
 				3B5CD2B72D4AEA6600CE213C /* ComputedBGTargets.swift in Sources */,
 				3B5CD2B82D4AEA6600CE213C /* ComputedInsulinSensitivities.swift in Sources */,
@@ -4803,6 +5054,7 @@
 				19D466A729AA2C22004D5F33 /* MealSettingsStateModel.swift in Sources */,
 				AC19EF2C94084B5BA0175D1D /* SettingsSearchHighlight.swift in Sources */,
 				DD17452B2C556E8100211FAC /* SettingInputHintView.swift in Sources */,
+				DD500A712807000000000101 /* SnoozeAlertsSheetView.swift in Sources */,
 				38E44528274E401C00EC9A94 /* Protected.swift in Sources */,
 				DD3F1F8B2D9E08B600DCE7B3 /* NightscoutLoginStepView.swift in Sources */,
 				3811DEB625C9D88300A708ED /* UnlockManager.swift in Sources */,
@@ -4900,6 +5152,13 @@
 				3B2A3BC12E2B19C600658FB9 /* DynamicISF.swift in Sources */,
 				CEE9A6592BBB418300EB5194 /* CalibrationsDataFlow.swift in Sources */,
 				3811DE3525C9D49500A708ED /* HomeRootView.swift in Sources */,
+				584C79A91D034426A1DE60CB /* SlideToConfirmView.swift in Sources */,
+				DECF1903237041E2BBBE4033 /* QuickPickBolusesInfoView.swift in Sources */,
+				3F23E18680094E6DA98628E4 /* QuickPickBolusesView.swift in Sources */,
+				786B6E8A8E294AE883581154 /* QuickPickBolusesConfigDataFlow.swift in Sources */,
+				FA6B58486AE2496F9F8589C6 /* QuickPickBolusesConfigProvider.swift in Sources */,
+				87F4D02B8D4D4D329BA291D2 /* QuickPickBolusesConfigStateModel.swift in Sources */,
+				C5BAAA36C643401D97A8CE11 /* QuickPickBolusesConfigRootView.swift in Sources */,
 				38E98A2925F52C9300C0CED0 /* Error+Extensions.swift in Sources */,
 				3E62C7822F54CC1B00433237 /* BolusDisplayThreshold.swift in Sources */,
 				38EA05DA261F6E7C0064E39B /* SimpleLogReporter.swift in Sources */,
@@ -4929,6 +5188,49 @@
 				BD249D8C2D42FC2C00412DEB /* GlucoseDistributionChart.swift in Sources */,
 				DDFF20312DB1D15500AB8A96 /* BluetoothPermissionStepView.swift in Sources */,
 				38E87408274F9AD000975559 /* UserNotificationsManager.swift in Sources */,
+				BD1179202F4E22C100F90001 /* TrioAlertManager.swift in Sources */,
+				CA01000000000000000010C2 /* AlertCatalogVendor.swift in Sources */,
+				CA01000000000000000010C4 /* AlertCatalogRegistry.swift in Sources */,
+				CA01000000000000000010C6 /* CGMManagerAlertOwnership.swift in Sources */,
+				BD1179222F4E22C100F90001 /* TrioModalAlertScheduler.swift in Sources */,
+				BD1179242F4E22C100F90001 /* TrioUserNotificationAlertScheduler.swift in Sources */,
+				BD1179322F4E22C100F90001 /* CriticalAlertAudioPlayer.swift in Sources */,
+				BD1179282F4E22C100F90001 /* TrioAlertCategory.swift in Sources */,
+				BD1179402F4E22C100F90001 /* ForecastedGlucoseEvaluator.swift in Sources */,
+				BD1179522F4E22C100F90001 /* DayNightOptions.swift in Sources */,
+				BD1179542F4E22C100F90001 /* GlucoseAlert.swift in Sources */,
+				BD1179562F4E22C100F90001 /* GlucoseAlertConfiguration.swift in Sources */,
+				BD1179582F4E22C100F90001 /* GlucoseAlertType.swift in Sources */,
+				BD11795A2F4E22C100F90001 /* GlucoseAlertsStore.swift in Sources */,
+				BD11795C2F4E22C100F90001 /* GlucoseAlertCoordinator.swift in Sources */,
+				BD1179632F4E22C100F90001 /* GlucoseAlertsDataFlow.swift in Sources */,
+				BD1179652F4E22C100F90001 /* GlucoseAlertsProvider.swift in Sources */,
+				BD1179672F4E22C100F90001 /* GlucoseAlertsStateModel.swift in Sources */,
+				BD1179692F4E22C100F90001 /* GlucoseAlertsRootView.swift in Sources */,
+				BD11796B2F4E22C100F90001 /* GlucoseAlertEditorView.swift in Sources */,
+				BD11796D2F4E22C100F90001 /* AddGlucoseAlertSheet.swift in Sources */,
+				BD1179712F4E22C100F90001 /* AlarmEnumMenuPicker.swift in Sources */,
+				BD1179732F4E22C100F90001 /* AlarmBGSection.swift in Sources */,
+				CA05000000000000000010C2 /* AlarmGramsSection.swift in Sources */,
+				BD1179772F4E22C100F90001 /* AlarmActiveSection.swift in Sources */,
+				BD1179792F4E22C100F90001 /* AlarmAudioSection.swift in Sources */,
+				BD11797B2F4E22C100F90001 /* AlarmSoundCatalog.swift in Sources */,
+				BD11797D2F4E22C100F90001 /* AlarmWindowIcon.swift in Sources */,
+				BD1179832F4E22C100F90001 /* DeviceAlertSeverity.swift in Sources */,
+				BD1179852F4E22C100F90001 /* DeviceAlertSeverityConfig.swift in Sources */,
+				BD1179872F4E22C100F90001 /* DeviceAlertsStore.swift in Sources */,
+				BD1179892F4E22C100F90001 /* DeviceAlarmsDataFlow.swift in Sources */,
+				BD11798B2F4E22C100F90001 /* DeviceAlarmsProvider.swift in Sources */,
+				BD11798D2F4E22C100F90001 /* DeviceAlarmsStateModel.swift in Sources */,
+				BD11798F2F4E22C100F90001 /* DeviceAlarmsRootView.swift in Sources */,
+				BD1179912F4E22C100F90001 /* DeviceAlarmEditorView.swift in Sources */,
+				BD1179932F4E22C100F90001 /* AddDeviceAlarmSheet.swift in Sources */,
+				BD1179A02F4E22C100F90001 /* AlarmWindowsDataFlow.swift in Sources */,
+				BD1179A22F4E22C100F90001 /* AlarmWindowsProvider.swift in Sources */,
+				BD1179A42F4E22C100F90001 /* AlarmWindowsStateModel.swift in Sources */,
+				BD1179A62F4E22C100F90001 /* AlarmWindowsRootView.swift in Sources */,
+				BD1179AA2F4E22C100F90001 /* NotLoopingMonitor.swift in Sources */,
+				BD1179AC2F4E22C100F90001 /* GlucoseSourceKey.swift in Sources */,
 				DD3F1F902D9E153F00DCE7B3 /* NightscoutImportStepView.swift in Sources */,
 				CE82E02528E867BA00473A9C /* AlertStorage.swift in Sources */,
 				DD1745372C55B74200211FAC /* AlgorithmSettings.swift in Sources */,
@@ -5064,6 +5366,7 @@
 				CE7CA3512A064973004BE681 /* ApplyTempPresetIntent.swift in Sources */,
 				FA630397F76B582C8D8681A7 /* BasalProfileEditorProvider.swift in Sources */,
 				DD1745172C54389F00211FAC /* FeatureSettingsView.swift in Sources */,
+				AABB00022C54389F00211FAC /* TreatmentsSettingsView.swift in Sources */,
 				DD3D60312F0377350021A33B /* ExportSetting.swift in Sources */,
 				DDD7C8C12F4DB45400E5CF09 /* GlucoseStored+CoreDataClass.swift in Sources */,
 				DDD7C8C22F4DB45400E5CF09 /* GlucoseStored+CoreDataProperties.swift in Sources */,
@@ -5182,7 +5485,6 @@
 				BD249D922D42FC5300412DEB /* GlucoseSectorChart.swift in Sources */,
 				23888883D4EA091C88480FF2 /* TreatmentsProvider.swift in Sources */,
 				38E98A2D25F52DC400C0CED0 /* NSLocking+Extensions.swift in Sources */,
-				715120D22D3C2BB4005D9FB6 /* GlucoseNotificationsOption.swift in Sources */,
 				BB5227A51D9D4377A1A70BA6 /* TempTargetData.swift in Sources */,
 				BDBAACFA2C2D439700370AAE /* OverrideData.swift in Sources */,
 				DD9ECB682CA99F4500AA7C45 /* TrioRemoteControl.swift in Sources */,
@@ -5255,9 +5557,6 @@
 				DD9E6DA22D59A12700514CEC /* MealHistory.swift in Sources */,
 				19E1F7E829D082D0005C8D20 /* IconConfigDataFlow.swift in Sources */,
 				5A2325522BFCBF55003518CA /* NightscoutUploadView.swift in Sources */,
-				E3A08AAE59538BC8A8ABE477 /* GlucoseNotificationSettingsDataFlow.swift in Sources */,
-				0F7A65FBD2CD8D6477ED4539 /* GlucoseNotificationSettingsProvider.swift in Sources */,
-				3171D2818C7C72CD1584BB5E /* GlucoseNotificationSettingsStateModel.swift in Sources */,
 				DDE179522C910127003CDDB7 /* MealPresetStored+CoreDataClass.swift in Sources */,
 				DD30B9CA2E062A3400DA677C /* ForecastGenerator.swift in Sources */,
 				DDE179532C910127003CDDB7 /* MealPresetStored+CoreDataProperties.swift in Sources */,
@@ -5303,16 +5602,11 @@
 				DD9ECB742CA9A0C300AA7C45 /* RemoteControlConfig.swift in Sources */,
 				DD4A00212DAEEED800AB7387 /* OnboardingView+AlgorithmUtil.swift in Sources */,
 				DDE179712C910127003CDDB7 /* OverrideStored+CoreDataProperties.swift in Sources */,
-				CD78BB94E43B249D60CC1A1B /* GlucoseNotificationSettingsRootView.swift in Sources */,
 				CE7CA3502A064973004BE681 /* CancelTempPresetIntent.swift in Sources */,
-				6B1F539F9FF75646D1606066 /* SnoozeDataFlow.swift in Sources */,
-				6FFAE524D1D9C262F2407CAE /* SnoozeProvider.swift in Sources */,
 				DD3C47B52DC57E06003DD20D /* MainMigrationErrorView.swift in Sources */,
 				BD4ED4FD2CF9D5E8000EDC9C /* AppState.swift in Sources */,
 				DDF847DD2C5C28720049BB3B /* LiveActivitySettingsDataFlow.swift in Sources */,
-				8194B80890CDD6A3C13B0FEE /* SnoozeStateModel.swift in Sources */,
 				BDA25EE42D260CD500035F34 /* AppleWatchManager.swift in Sources */,
-				0437CE46C12535A56504EC19 /* SnoozeRootView.swift in Sources */,
 				BD175EBE0000100000000001 /* HistoryDataFlow+Models.swift in Sources */,
 				BD175EBE0000100000000002 /* HistoryDeletionTarget.swift in Sources */,
 				BD175EBE0000100000000003 /* HistoryStateModel+Glucose.swift in Sources */,
@@ -5375,6 +5669,23 @@
 				3B2CE68B2E24ADF7005EF782 /* IobGenerateTests.swift in Sources */,
 				3B1C5C482D68E269004E9273 /* IobHistoryTests.swift in Sources */,
 				38FCF3F925E902C20078B0D1 /* FileStorageTests.swift in Sources */,
+				BD11A001000000000000A002 /* AlertMuterTests.swift in Sources */,
+				BD11A001000000000000A013 /* TandemKitAlertEmissionTests.swift in Sources */,
+				BD11A001000000000000A012 /* RileyLinkKitAlertEmissionTests.swift in Sources */,
+				BD11A001000000000000A011 /* OmnipodKitAlertEmissionTests.swift in Sources */,
+				BD11A001000000000000A010 /* MinimedKitAlertEmissionTests.swift in Sources */,
+				BD11A001000000000000A00F /* MedtrumKitAlertEmissionTests.swift in Sources */,
+				BD11A001000000000000A00E /* DanaKitAlertEmissionTests.swift in Sources */,
+				BD11A001000000000000A00D /* AlertCatalogRegistryEntriesTests.swift in Sources */,
+				BD11A001000000000000A00C /* NotLoopingMonitorTests.swift in Sources */,
+				BD11A001000000000000A00B /* TrioModalAlertSchedulerTests.swift in Sources */,
+				BD11A001000000000000A00A /* GlucoseAlertCoordinatorTests.swift in Sources */,
+				CA06000000000000000010C2 /* GlucoseAlertsStoreTests.swift in Sources */,
+				BD11A001000000000000A009 /* TrioAlertCategoryTests.swift in Sources */,
+				BD11A001000000000000A008 /* GlucoseAlertTests.swift in Sources */,
+				BD11A001000000000000A007 /* GlucoseAlertConfigurationTests.swift in Sources */,
+				BD11A001000000000000A006 /* ForecastedGlucoseEvaluatorTests.swift in Sources */,
+				BD11A001000000000000A003 /* DeviceAlertsStoreTests.swift in Sources */,
 				3B8221B22E5882E300585156 /* DetermineBasalEarlyExitTests.swift in Sources */,
 				3BA643EA2ED9FAD8007BC31F /* DetermineBasalAggressiveDosingTests.swift in Sources */,
 				BD8FC0602D6619DB00B95AED /* CarbsStorageTests.swift in Sources */,
@@ -5383,6 +5694,7 @@
 				41740E936552456AAC0EDAC3 /* SettingsSearchTests.swift in Sources */,
 				B015AFE52E500000000D7351 /* BolusSafetyValidatorTests.swift in Sources */,
 				BD8FC0712D661B0000B95AED /* TidepoolTherapySettingsTests.swift in Sources */,
+				CA03000000000000000010C2 /* AlertCatalogRegistryOmniFaultTests.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -6096,14 +6408,6 @@
 				minimumVersion = 6.3.1;
 			};
 		};
-		38DF1787276FC8C300B3528F /* XCRemoteSwiftPackageReference "SwiftMessages" */ = {
-			isa = XCRemoteSwiftPackageReference;
-			repositoryURL = "https://github.com/SwiftKickMobile/SwiftMessages";
-			requirement = {
-				kind = upToNextMajorVersion;
-				minimumVersion = 9.0.0;
-			};
-		};
 		3B47C60E2DA0A28F00B0E5EF /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = {
 			isa = XCRemoteSwiftPackageReference;
 			repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git";
@@ -6178,11 +6482,6 @@
 			package = 38B17B6425DD90E0005CAE3D /* XCRemoteSwiftPackageReference "SwiftDate" */;
 			productName = SwiftDate;
 		};
-		38DF1788276FC8C400B3528F /* SwiftMessages */ = {
-			isa = XCSwiftPackageProductDependency;
-			package = 38DF1787276FC8C300B3528F /* XCRemoteSwiftPackageReference "SwiftMessages" */;
-			productName = SwiftMessages;
-		};
 		3B47C60F2DA0A28F00B0E5EF /* FirebaseCrashlytics */ = {
 			isa = XCSwiftPackageProductDependency;
 			package = 3B47C60E2DA0A28F00B0E5EF /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;

+ 0 - 9
Trio.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -254,15 +254,6 @@
       }
     },
     {
-      "identity" : "swiftmessages",
-      "kind" : "remoteSourceControl",
-      "location" : "https://github.com/SwiftKickMobile/SwiftMessages",
-      "state" : {
-        "revision" : "62e12e138fc3eedf88c7553dd5d98712aa119f40",
-        "version" : "9.0.9"
-      }
-    },
-    {
       "identity" : "swinject",
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/Swinject/Swinject",

BIN
Trio/Resources/Sounds/alarm.caf


BIN
Trio/Resources/Sounds/bloom.caf


BIN
Trio/Resources/Sounds/bloop.caf


BIN
Trio/Resources/Sounds/bright_alarm.caf


BIN
Trio/Resources/Sounds/chime.caf


BIN
Trio/Resources/Sounds/clear_chimes.caf


BIN
Trio/Resources/Sounds/critical.caf


BIN
Trio/Resources/Sounds/crying.caf


BIN
Trio/Resources/Sounds/dings.caf


BIN
Trio/Resources/Sounds/high_chimes.caf


BIN
Trio/Resources/Sounds/honk.caf


BIN
Trio/Resources/Sounds/minimal.caf


BIN
Trio/Resources/Sounds/mood_synth.caf


BIN
Trio/Resources/Sounds/simple.caf


BIN
Trio/Resources/Sounds/spring.caf


BIN
Trio/Resources/Sounds/synth.caf


BIN
Trio/Resources/Sounds/trill.caf


BIN
Trio/Resources/Sounds/urgent_low.caf


+ 2 - 0
Trio/Resources/Trio.entitlements

@@ -14,6 +14,8 @@
 	<array>
 		<string>TAG</string>
 	</array>
+	<key>com.apple.developer.usernotifications.time-sensitive</key>
+	<true/>
 	<key>com.apple.security.application-groups</key>
 	<array>
 		<string>$(APP_GROUP_ID)</string>

+ 157 - 22
Trio/Sources/APS/APSManager.swift

@@ -8,6 +8,11 @@ import Swinject
 
 protocol APSManager {
     func heartbeat(date: Date)
+    /// Mark the next loop attempt as user-initiated (e.g. force-loop button).
+    /// Surfaces transient errors immediately instead of waiting for the
+    /// usual dwell threshold — when the user explicitly asks for a loop,
+    /// they want feedback even if the underlying error is "transient".
+    func markNextLoopUserInitiated()
     func enactBolus(amount: Double, isSMB: Bool, callback: ((Bool, String) -> Void)?) async
     var pumpManager: PumpManagerUI? { get set }
     var bluetoothManager: BluetoothStateManager? { get }
@@ -36,6 +41,13 @@ protocol APSManager {
     var iobFileDidUpdate: PassthroughSubject<Void, Never> { get }
 }
 
+/// Notified after a bolus-related failure so observing UI (e.g. the
+/// treatment screen's bolus state) can clean up state. Broadcast by
+/// `APSManager` from `enactBolus` / `cancelBolus` error paths.
+protocol BolusFailureObserver {
+    func bolusDidFail()
+}
+
 enum APSError: LocalizedError {
     case pumpError(Error)
     case invalidPumpState(message: String)
@@ -57,17 +69,6 @@ enum APSError: LocalizedError {
             return String(localized: "Manual Temporary Basal Rate (\(message)). Looping suspended.")
         }
     }
-
-    static func pumpErrorMatches(message: String) -> Bool {
-        message.contains(String(localized: "Pump Error"))
-    }
-
-    static func pumpWarningMatches(message: String) -> Bool {
-        message.contains(String(localized: "Invalid Pump State")) || message
-            .contains("PumpMessage") || message
-            .contains("PumpOpsError") || message.contains("RileyLink") || message
-            .contains(String(localized: "Pump did not respond in time"))
-    }
 }
 
 // MARK: - Thread-safe loop serialization
@@ -104,6 +105,7 @@ final class BaseAPSManager: APSManager, Injectable {
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var tddStorage: TDDStorage!
     @Injected() private var broadcaster: Broadcaster!
+    @Injected() private var trioAlertManager: TrioAlertManager!
     @Persisted(key: "lastLoopStartDate") private var lastLoopStartDate: Date = .distantPast
     @Persisted(key: "lastLoopDate") var lastLoopDate: Date = .distantPast {
         didSet {
@@ -182,7 +184,7 @@ final class BaseAPSManager: APSManager, Injectable {
             if wasParsed {
                 Task {
                     do {
-                        try await openAPS.createProfiles(useSwiftOref: settings.useSwiftOref)
+                        try await openAPS.createProfiles(useJavascriptOref: settings.useJavascriptOref)
                     } catch {
                         debug(
                             .apsManager,
@@ -263,6 +265,24 @@ final class BaseAPSManager: APSManager, Injectable {
         Task { [weak self] in
             guard let self else { return }
 
+            // Consume the user-initiated flag unconditionally — it was set
+            // for the loop the user just triggered. If the guards below block
+            // (suspended, too-soon, no pump), the next scheduled tick must
+            // not inherit it and bypass dwell suppression for an error the
+            // user didn't request.
+            let userInitiated = self.nextLoopUserInitiated
+            self.nextLoopUserInitiated = false
+
+            // Don't try to run a loop while pump setup / pod pairing is in
+            // progress — `verifyStatus` would throw `invalidPumpState("Pump
+            // not set")` and surface a modal banner on top of the pod
+            // activation sheet, closing the sheet (reported by tester during
+            // O5 pairing).
+            guard self.pumpManager != nil else {
+                debug(.apsManager, "No pump manager — skipping loop attempt")
+                return
+            }
+
             // Atomic check-and-set via actor — eliminates the race between
             // checking isLooping.value and sending isLooping(true).
             guard await loopGuard.tryStart(
@@ -274,6 +294,11 @@ final class BaseAPSManager: APSManager, Injectable {
                 return
             }
 
+            // Affects whether transient errors surface immediately instead of
+            // dwell-suppressed (see `surfaceErrorIfNeeded`).
+            self.currentLoopUserInitiated = userInitiated
+            defer { self.currentLoopUserInitiated = false }
+
             // Start background task
             // we probably need to refactor this when implementing Swift 6 due to mutation of a captured var in an async context
             var taskID: UIBackgroundTaskIdentifier = .invalid
@@ -372,6 +397,8 @@ final class BaseAPSManager: APSManager, Injectable {
             debug(.apsManager, "Loop succeeded")
             lastLoopDate = Date()
             lastError.send(nil)
+            transientCategoryFirstSeen.removeAll()
+            transientCategoryCount.removeAll()
         }
 
         loopStats(loopStatRecord: loopStatRecord)
@@ -409,7 +436,7 @@ final class BaseAPSManager: APSManager, Injectable {
         else {
             let result = try await openAPS.autosense(
                 shouldSmoothGlucose: settingsManager.settings.smoothGlucose,
-                useSwiftOref: settings.useSwiftOref
+                useJavascriptOref: settings.useJavascriptOref
             )
             return result != nil
         }
@@ -476,14 +503,14 @@ final class BaseAPSManager: APSManager, Injectable {
             let now = Date()
 
             // put profile creation up front since autosens needs it
-            try await openAPS.createProfiles(useSwiftOref: settings.useSwiftOref)
+            try await openAPS.createProfiles(useJavascriptOref: settings.useJavascriptOref)
             let currentTemp = try await fetchCurrentTempBasal(date: now)
             _ = try await autosense()
 
             let determination = try await openAPS.determineBasal(
                 currentTemp: currentTemp,
                 shouldSmoothGlucose: settingsManager.settings.smoothGlucose,
-                useSwiftOref: settings.useSwiftOref,
+                useJavascriptOref: settings.useJavascriptOref,
                 clock: now
             )
             iobFileDidUpdate.send(())
@@ -530,7 +557,7 @@ final class BaseAPSManager: APSManager, Injectable {
             return try await openAPS.determineBasal(
                 currentTemp: temp,
                 shouldSmoothGlucose: settingsManager.settings.smoothGlucose,
-                useSwiftOref: settings.useSwiftOref,
+                useJavascriptOref: settings.useJavascriptOref,
                 clock: Date(),
                 simulatedCarbsAmount: simulatedCarbsAmount,
                 simulatedBolusAmount: simulatedBolusAmount,
@@ -596,9 +623,14 @@ final class BaseAPSManager: APSManager, Injectable {
             }
         } catch {
             warning(.apsManager, "Bolus failed with error: \(error)")
-            processError(APSError.pumpError(error))
+            lastError.send(APSError.pumpError(error))
+            issueAlertForCategory(
+                .bolusFailed,
+                title: String(localized: "Bolus failed"),
+                body: String(localized: "Check pump history before repeating.")
+                    + "\n\n\(error.localizedDescription)"
+            )
             if !isSMB {
-                // Use MainActor to handle broadcaster notification
                 let broadcaster = self.broadcaster
                 Task { @MainActor in
                     broadcaster?.notify(BolusFailureObserver.self, on: .main) {
@@ -622,7 +654,12 @@ final class BaseAPSManager: APSManager, Injectable {
             callback?(true, String(localized: "Bolus cancelled successfully.", comment: "Success message for canceling a bolus"))
         } catch {
             debug(.apsManager, "Bolus cancellation failed with error: \(error)")
-            processError(APSError.pumpError(error))
+            lastError.send(APSError.pumpError(error))
+            issueAlertForCategory(
+                .bolusFailed,
+                title: String(localized: "Bolus cancellation failed"),
+                body: String(localized: "Try again.") + "\n\n\(error.localizedDescription)"
+            )
             callback?(
                 false,
                 String(
@@ -711,10 +748,9 @@ final class BaseAPSManager: APSManager, Injectable {
             throw APSError.apsError(message: "Pump not set")
         }
 
-        // Check if pump is suspended and abort if it is
         if pump.status.pumpStatus.suspended {
-            info(.apsManager, "Skipping enactDetermination because pump is suspended")
-            return // return without throwing an error
+            debug(.apsManager, "Skipping enactDetermination because pump is suspended")
+            return
         }
 
         // Unable to do temp basal during manual temp basal 😁
@@ -1203,9 +1239,108 @@ final class BaseAPSManager: APSManager, Injectable {
         }
     }
 
+    private var transientCategoryFirstSeen: [String: Date] = [:]
+    private var transientCategoryCount: [String: Int] = [:]
+    private static let transientDwellThreshold: TimeInterval = 60
+    private static let transientCountThreshold = 2
+
+    /// Set by `markNextLoopUserInitiated()` (e.g. force-loop button), consumed
+    /// on the next entry into `loop()` so that errors during a user-initiated
+    /// loop surface immediately instead of being suppressed by dwell logic.
+    @SyncAccess private var nextLoopUserInitiated: Bool = false
+    private var currentLoopUserInitiated: Bool = false
+
+    func markNextLoopUserInitiated() {
+        nextLoopUserInitiated = true
+    }
+
     private func processError(_ error: Error) {
         warning(.apsManager, "\(error)")
         lastError.send(error)
+        surfaceErrorIfNeeded(error)
+    }
+
+    private func surfaceErrorIfNeeded(_ error: Error) {
+        let category = TrioAlertClassifier.categorize(error: error)
+        let key = String(describing: category)
+
+        if category.shouldFireImmediately || currentLoopUserInitiated {
+            transientCategoryFirstSeen.removeValue(forKey: key)
+            transientCategoryCount.removeValue(forKey: key)
+            issueAlertForError(error, category: category)
+            return
+        }
+
+        let now = Date()
+        let firstSeen = transientCategoryFirstSeen[key] ?? now
+        let count = (transientCategoryCount[key] ?? 0) + 1
+        let dwellElapsed = now.timeIntervalSince(firstSeen)
+        let dwellMet = dwellElapsed >= Self.transientDwellThreshold
+        let countMet = count >= Self.transientCountThreshold
+
+        if dwellMet || countMet {
+            transientCategoryFirstSeen.removeValue(forKey: key)
+            transientCategoryCount.removeValue(forKey: key)
+            issueAlertForError(error, category: category)
+        } else {
+            transientCategoryFirstSeen[key] = firstSeen
+            transientCategoryCount[key] = count
+            debug(
+                .apsManager,
+                "APSManager suppressed transient \(category) (count=\(count)/\(Self.transientCountThreshold), dwell=\(Int(dwellElapsed))s/\(Int(Self.transientDwellThreshold))s)"
+            )
+        }
+    }
+
+    private func issueAlertForCategory(_ category: TrioAlertCategory, title: String, body: String) {
+        let content = Alert.Content(
+            title: title,
+            body: body,
+            acknowledgeActionButtonLabel: String(localized: "OK")
+        )
+        let alert = Alert(
+            identifier: Alert.Identifier(managerIdentifier: "trio.aps", alertIdentifier: category.alertIdentifier),
+            foregroundContent: content,
+            backgroundContent: content,
+            trigger: .immediate,
+            interruptionLevel: category.interruptionLevel
+        )
+        trioAlertManager?.issueAlert(alert)
+    }
+
+    private func issueAlertForError(_ error: Error, category: TrioAlertCategory) {
+        let (title, body) = describeForAlert(error)
+        let content = Alert.Content(
+            title: title,
+            body: body,
+            acknowledgeActionButtonLabel: "OK"
+        )
+        let alert = Alert(
+            identifier: Alert.Identifier(managerIdentifier: "trio.aps", alertIdentifier: category.alertIdentifier),
+            foregroundContent: content,
+            backgroundContent: content,
+            trigger: .immediate,
+            interruptionLevel: category.interruptionLevel
+        )
+        trioAlertManager?.issueAlert(alert)
+    }
+
+    private func describeForAlert(_ error: Error) -> (title: String, body: String) {
+        if let apsError = error as? APSError {
+            switch apsError {
+            case let .pumpError(inner):
+                return (
+                    String(localized: "Pump Error"),
+                    String(localized: "Trio could not communicate with the pump. Check the pump and try again.")
+                        + "\n\n\(inner.localizedDescription)"
+                )
+            case let .invalidPumpState(message): return (String(localized: "Pump State Error"), message)
+            case let .glucoseError(message): return (String(localized: "Glucose Error"), message)
+            case let .apsError(message): return (String(localized: "Algorithm Error"), message)
+            case let .manualBasalTemp(message): return (String(localized: "Manual Temp Basal Active"), message)
+            }
+        }
+        return ("Trio", error.localizedDescription)
     }
 
     /// Called from the `bolusTrigger` Combine sink (already on

+ 32 - 8
Trio/Sources/APS/CGM/PluginSource.swift

@@ -99,23 +99,47 @@ extension PluginSource: CGMManagerDelegate {
         debug(.deviceManager, "device Manager for \(String(describing: deviceIdentifier)) : \(message)")
     }
 
-    func issueAlert(_: LoopKit.Alert) {}
+    /// Forwards CGMManager-issued alerts into the unified `TrioAlertManager`
+    /// pipeline so they get the same in-app banner + UN scheduling + history
+    /// logging treatment as everything else. Used to be a no-op; on
+    /// dev-libre3 / LibreLoop builds that meant CGM-issued alerts (sensor
+    /// failure, signal loss, expiry, etc.) silently dropped.
+    func issueAlert(_ alert: LoopKit.Alert) {
+        glucoseManager?.trioAlertManager?.issueAlert(alert)
+    }
 
-    func retractAlert(identifier _: LoopKit.Alert.Identifier) {}
+    func retractAlert(identifier: LoopKit.Alert.Identifier) {
+        glucoseManager?.trioAlertManager?.retractAlert(identifier: identifier)
+    }
 
-    func doesIssuedAlertExist(identifier _: LoopKit.Alert.Identifier, completion _: @escaping (Result<Bool, Error>) -> Void) {}
+    /// LoopKit asks this on reconnect to avoid re-issuing an alert that's
+    /// still live. `TrioAlertManager` deduplicates downstream via its own
+    /// throttler and live-alert table, so answering "no" here is safe — at
+    /// worst we get one duplicate banner, which the throttler suppresses.
+    func doesIssuedAlertExist(identifier _: LoopKit.Alert.Identifier, completion: @escaping (Result<Bool, Error>) -> Void) {
+        completion(.success(false))
+    }
 
     func lookupAllUnretracted(
         managerIdentifier _: String,
-        completion _: @escaping (Result<[LoopKit.PersistedAlert], Error>) -> Void
-    ) {}
+        completion: @escaping (Result<[LoopKit.PersistedAlert], Error>) -> Void
+    ) {
+        completion(.success([]))
+    }
 
     func lookupAllUnacknowledgedUnretracted(
         managerIdentifier _: String,
-        completion _: @escaping (Result<[LoopKit.PersistedAlert], Error>) -> Void
-    ) {}
+        completion: @escaping (Result<[LoopKit.PersistedAlert], Error>) -> Void
+    ) {
+        completion(.success([]))
+    }
 
-    func recordRetractedAlert(_: LoopKit.Alert, at _: Date) {}
+    /// LoopKit calls this when a manager itself decides an alert is no
+    /// longer relevant. Mirror the action on our pipeline so the in-app
+    /// banner, scheduled UN, and history all clear.
+    func recordRetractedAlert(_ alert: LoopKit.Alert, at _: Date) {
+        glucoseManager?.trioAlertManager?.retractAlert(identifier: alert.identifier)
+    }
 
     func cgmManagerWantsDeletion(_ manager: CGMManager) {
         processQueue.async { [weak self] in

+ 11 - 45
Trio/Sources/APS/DeviceDataManager.swift

@@ -62,6 +62,7 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
     @Injected() private var glucoseStorage: GlucoseStorage!
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var bluetoothProvider: BluetoothStateManager!
+    @Injected() private var trioAlertManager: TrioAlertManager!
 
     @Persisted(key: "BaseDeviceDataManager.lastEventDate") var lastEventDate: Date? = nil
     @SyncAccess(lock: accessLock) @Persisted(key: "BaseDeviceDataManager.lastHeartBeatTime") var lastHeartBeatTime: Date =
@@ -83,10 +84,16 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
 
     var pumpManager: PumpManagerUI? {
         didSet {
+            if let oldValue = oldValue {
+                trioAlertManager?.unregister(managerIdentifier: oldValue.pluginIdentifier)
+            }
             if let pumpManager = pumpManager {
                 pumpManager.pumpManagerDelegate = self
                 pumpManager.delegateQueue = processQueue
 
+                trioAlertManager?.register(responder: pumpManager, for: pumpManager.pluginIdentifier)
+                trioAlertManager?.register(soundVendor: pumpManager, for: pumpManager.pluginIdentifier)
+
                 /// Since the pump manager has been successfully instantiated from its saved state,
                 /// copy its rawValue to rawPumpManager which will be saved to persistant storage.
                 rawPumpManager = pumpManager.rawValue
@@ -241,7 +248,6 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
         injectServices(resolver)
         setupPumpManager()
         UIDevice.current.isBatteryMonitoringEnabled = true
-        broadcaster.register(AlertObserver.self, observer: self)
     }
 
     func setupPumpManager() {
@@ -636,22 +642,13 @@ extension BaseDeviceDataManager: PumpManagerDelegate {
 
 extension BaseDeviceDataManager: DeviceManagerDelegate {
     func issueAlert(_ alert: Alert) {
-        alertHistoryStorage.addAlert(
-            AlertEntry(
-                alertIdentifier: alert.identifier.alertIdentifier,
-                primitiveInterruptionLevel: alert.interruptionLevel.storedValue as? Decimal,
-                issuedDate: Date(),
-                managerIdentifier: alert.identifier.managerIdentifier,
-                triggerType: alert.trigger.storedType,
-                triggerInterval: alert.trigger.storedInterval as? Decimal,
-                contentTitle: alert.foregroundContent?.title,
-                contentBody: alert.foregroundContent?.body
-            )
-        )
+        debug(.deviceManager, "issueAlert \(alert.identifier.value)")
+        trioAlertManager.issueAlert(alert)
     }
 
     func retractAlert(identifier: Alert.Identifier) {
-        alertHistoryStorage.removeAlert(identifier: identifier.alertIdentifier)
+        debug(.deviceManager, "retractAlert \(identifier.value)")
+        trioAlertManager.retractAlert(identifier: identifier)
     }
 
     func doesIssuedAlertExist(identifier _: Alert.Identifier, completion _: @escaping (Result<Bool, Error>) -> Void) {
@@ -706,37 +703,6 @@ extension BaseDeviceDataManager: CGMManagerDelegate {
     func cgmManager(_: CGMManager, didUpdate _: CGMManagerStatus) {}
 }
 
-// MARK: - AlertPresenter
-
-extension BaseDeviceDataManager: AlertObserver {
-    func AlertDidUpdate(_ alerts: [AlertEntry]) {
-        alerts.forEach { alert in
-            if alert.acknowledgedDate == nil {
-                ackAlert(alert: alert)
-            }
-        }
-    }
-
-    private func ackAlert(alert: AlertEntry) {
-        let alertIssueDate = alert.issuedDate
-
-        processQueue.async {
-            self.pumpManager?.acknowledgeAlert(alertIdentifier: alert.alertIdentifier) { error in
-                if let error = error {
-                    self.alertHistoryStorage.acknowledgeAlert(alertIssueDate, error.localizedDescription)
-                    debug(.deviceManager, "acknowledge not succeeded with error \(error)")
-                } else {
-                    self.alertHistoryStorage.acknowledgeAlert(alertIssueDate, nil)
-                }
-            }
-
-            self.broadcaster.notify(pumpNotificationObserver.self, on: self.processQueue) {
-                $0.pumpNotification(alert: alert)
-            }
-        }
-    }
-}
-
 // extension BaseDeviceDataManager: AlertPresenter {
 //    func issueAlert(_: Alert) {}
 //    func retractAlert(identifier _: Alert.Identifier) {}

+ 5 - 0
Trio/Sources/APS/FetchGlucoseManager.swift

@@ -21,6 +21,10 @@ protocol FetchGlucoseManager: SourceInfoProvider {
     var shouldSyncToRemoteService: Bool { get }
     var cgmDisplayState: CurrentValueSubject<CgmDisplayState?, Never> { get }
     var cgmProgressHighlight: CurrentValueSubject<DeviceLifecycleProgress?, Never> { get }
+    /// Routes CGMManager-issued alerts (sensor failure, signal loss, expiry,
+    /// etc.) into the unified `TrioAlertManager` pipeline. Read by
+    /// `PluginSource.issueAlert` / `retractAlert`.
+    var trioAlertManager: TrioAlertManager! { get }
 }
 
 extension FetchGlucoseManager {
@@ -42,6 +46,7 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
     @Injected() var deviceDataManager: DeviceDataManager!
     @Injected() var pluginCGMManager: PluginManager!
     @Injected() var calibrationService: CalibrationService!
+    @Injected() var trioAlertManager: TrioAlertManager!
 
     private var lifetime = Lifetime()
     private let timer = DispatchTimer(timeInterval: 1.minutes.timeInterval)

+ 60 - 54
Trio/Sources/APS/OpenAPS/OpenAPS.swift

@@ -385,7 +385,7 @@ final class OpenAPS {
     func determineBasal(
         currentTemp: TempBasal,
         shouldSmoothGlucose: Bool,
-        useSwiftOref: Bool,
+        useJavascriptOref: Bool,
         clock: Date = Date(),
         simulatedCarbsAmount: Decimal? = nil,
         simulatedBolusAmount: Decimal? = nil,
@@ -448,7 +448,7 @@ final class OpenAPS {
             clock: clock,
             carbs: carbsAsJSON,
             glucose: glucoseAsJSON,
-            useSwiftOref: useSwiftOref
+            useJavascriptOref: useJavascriptOref
         )
 
         // IOB calculation
@@ -457,7 +457,7 @@ final class OpenAPS {
             profile: profile,
             clock: clock,
             autosens: autosens.isEmpty ? .null : autosens,
-            useSwiftOref: useSwiftOref
+            useJavascriptOref: useJavascriptOref
         )
 
         // TODO: refactor this to core data
@@ -485,7 +485,7 @@ final class OpenAPS {
             preferences: preferences,
             basalProfile: basalProfile,
             trioCustomOrefVariables: trioCustomOrefVariables,
-            useSwiftOref: useSwiftOref
+            useJavascriptOref: useJavascriptOref
         )
 
         debug(.openAPS, "\(simulation ? "[SIMULATION]" : "") OREF DETERMINATION: \(orefDetermination)")
@@ -578,7 +578,7 @@ final class OpenAPS {
         }
     }
 
-    func autosense(shouldSmoothGlucose: Bool, useSwiftOref: Bool) async throws -> Autosens? {
+    func autosense(shouldSmoothGlucose: Bool, useJavascriptOref: Bool) async throws -> Autosens? {
         debug(.openAPS, "Start autosens")
 
         // Perform asynchronous calls in parallel
@@ -607,7 +607,7 @@ final class OpenAPS {
             profile: profile,
             carbs: carbsAsJSON,
             temptargets: tempTargets,
-            useSwiftOref: useSwiftOref
+            useJavascriptOref: useJavascriptOref
         )
 
         debug(.openAPS, "AUTOSENS: \(autosenseResult)")
@@ -621,7 +621,7 @@ final class OpenAPS {
         }
     }
 
-    func createProfiles(useSwiftOref: Bool) async throws {
+    func createProfiles(useJavascriptOref: Bool) async throws {
         debug(.openAPS, "Start creating pump profile and user profile")
 
         // Load required settings and profiles asynchronously
@@ -698,7 +698,7 @@ final class OpenAPS {
                 model: model,
                 autotune: RawJSON.null,
                 trioSettings: trioSettings,
-                useSwiftOref: useSwiftOref,
+                useJavascriptOref: useJavascriptOref,
                 clock: clock
             )
 
@@ -713,7 +713,7 @@ final class OpenAPS {
                 model: model,
                 autotune: RawJSON.null,
                 trioSettings: trioSettings,
-                useSwiftOref: useSwiftOref,
+                useJavascriptOref: useJavascriptOref,
                 clock: clock
             )
 
@@ -729,20 +729,26 @@ final class OpenAPS {
         }
     }
 
-    private func iob(pumphistory: JSON, profile: JSON, clock: JSON, autosens: JSON, useSwiftOref: Bool) async throws -> RawJSON {
+    private func iob(
+        pumphistory: JSON,
+        profile: JSON,
+        clock: JSON,
+        autosens: JSON,
+        useJavascriptOref: Bool
+    ) async throws -> RawJSON {
         // FIXME: For now we'll just remove duplicate suspends here (ISSUE-399)
         var pumphistory = pumphistory
         if let pumpHistoryArray = try? JSONBridge.pumpHistory(from: pumphistory) {
             pumphistory = pumpHistoryArray.removingDuplicateSuspendResumeEvents().rawJSON
         }
 
-        if useSwiftOref {
+        if useJavascriptOref {
+            let jsResult = await iobJavascript(pumphistory: pumphistory, profile: profile, clock: clock, autosens: autosens)
+            return try jsResult.returnOrThrow()
+        } else {
             let swiftResult = OpenAPSSwift
                 .iob(pumphistory: pumphistory, profile: profile, clock: clock, autosens: autosens)
             return try swiftResult.returnOrThrow()
-        } else {
-            let jsResult = await iobJavascript(pumphistory: pumphistory, profile: profile, clock: clock, autosens: autosens)
-            return try jsResult.returnOrThrow()
         }
     }
 
@@ -777,9 +783,19 @@ final class OpenAPS {
         clock: JSON,
         carbs: JSON,
         glucose: JSON,
-        useSwiftOref: Bool
+        useJavascriptOref: Bool
     ) async throws -> RawJSON {
-        if useSwiftOref {
+        if useJavascriptOref {
+            let jsResult = await mealJavascript(
+                pumphistory: pumphistory,
+                profile: profile,
+                basalProfile: basalProfile,
+                clock: clock,
+                carbs: carbs,
+                glucose: glucose
+            )
+            return try jsResult.returnOrThrow()
+        } else {
             let swiftResult = OpenAPSSwift
                 .meal(
                     pumphistory: pumphistory,
@@ -790,16 +806,6 @@ final class OpenAPS {
                     glucose: glucose
                 )
             return try swiftResult.returnOrThrow()
-        } else {
-            let jsResult = await mealJavascript(
-                pumphistory: pumphistory,
-                profile: profile,
-                basalProfile: basalProfile,
-                clock: clock,
-                carbs: carbs,
-                glucose: glucose
-            )
-            return try jsResult.returnOrThrow()
         }
     }
 
@@ -843,9 +849,19 @@ final class OpenAPS {
         profile: JSON,
         carbs: JSON,
         temptargets: JSON,
-        useSwiftOref: Bool
+        useJavascriptOref: Bool
     ) async throws -> RawJSON {
-        if useSwiftOref {
+        if useJavascriptOref {
+            let jsResult = await autosenseJavascript(
+                glucose: glucose,
+                pumpHistory: pumpHistory,
+                basalprofile: basalprofile,
+                profile: profile,
+                carbs: carbs,
+                temptargets: temptargets
+            )
+            return try jsResult.returnOrThrow()
+        } else {
             let swiftResult = OpenAPSSwift
                 .autosense(
                     glucose: glucose,
@@ -857,16 +873,6 @@ final class OpenAPS {
                     clock: Date()
                 )
             return try swiftResult.returnOrThrow()
-        } else {
-            let jsResult = await autosenseJavascript(
-                glucose: glucose,
-                pumpHistory: pumpHistory,
-                basalprofile: basalprofile,
-                profile: profile,
-                carbs: carbs,
-                temptargets: temptargets
-            )
-            return try jsResult.returnOrThrow()
         }
     }
 
@@ -916,12 +922,12 @@ final class OpenAPS {
         preferences: JSON,
         basalProfile: JSON,
         trioCustomOrefVariables: JSON,
-        useSwiftOref: Bool
+        useJavascriptOref: Bool
     ) async throws -> RawJSON {
         let clock = Date()
 
-        if useSwiftOref {
-            let swiftResult = OpenAPSSwift.determineBasal(
+        if useJavascriptOref {
+            let jsResult = await determineBasalJavascript(
                 glucose: glucose,
                 currentTemp: currentTemp,
                 iob: iob,
@@ -936,9 +942,9 @@ final class OpenAPS {
                 trioCustomOrefVariables: trioCustomOrefVariables,
                 clock: clock
             )
-            return try swiftResult.returnOrThrow()
+            return try jsResult.returnOrThrow()
         } else {
-            let jsResult = await determineBasalJavascript(
+            let swiftResult = OpenAPSSwift.determineBasal(
                 glucose: glucose,
                 currentTemp: currentTemp,
                 iob: iob,
@@ -953,7 +959,7 @@ final class OpenAPS {
                 trioCustomOrefVariables: trioCustomOrefVariables,
                 clock: clock
             )
-            return try jsResult.returnOrThrow()
+            return try swiftResult.returnOrThrow()
         }
     }
 
@@ -1073,11 +1079,11 @@ final class OpenAPS {
         model: JSON,
         autotune: JSON,
         trioSettings: JSON,
-        useSwiftOref: Bool,
+        useJavascriptOref: Bool,
         clock: Date
     ) async throws -> RawJSON {
-        if useSwiftOref {
-            let swiftResult = OpenAPSSwift.makeProfile(
+        if useJavascriptOref {
+            let jsResult = await makeProfileJavascript(
                 preferences: preferences,
                 pumpSettings: pumpSettings,
                 bgTargets: bgTargets,
@@ -1086,12 +1092,12 @@ final class OpenAPS {
                 carbRatio: carbRatio,
                 tempTargets: tempTargets,
                 model: model,
-                trioSettings: trioSettings,
-                clock: clock
+                autotune: autotune,
+                trioSettings: trioSettings
             )
-            return try swiftResult.returnOrThrow()
+            return try jsResult.returnOrThrow()
         } else {
-            let jsResult = await makeProfileJavascript(
+            let swiftResult = OpenAPSSwift.makeProfile(
                 preferences: preferences,
                 pumpSettings: pumpSettings,
                 bgTargets: bgTargets,
@@ -1100,10 +1106,10 @@ final class OpenAPS {
                 carbRatio: carbRatio,
                 tempTargets: tempTargets,
                 model: model,
-                autotune: autotune,
-                trioSettings: trioSettings
+                trioSettings: trioSettings,
+                clock: clock
             )
-            return try jsResult.returnOrThrow()
+            return try swiftResult.returnOrThrow()
         }
     }
 

+ 1 - 1
Trio/Sources/APS/OpenAPSSwift/Profile/Carbs.swift

@@ -19,7 +19,7 @@ struct Carbs {
         }
 
         // Check for invalid values
-        if currentRatio < 3 || currentRatio > 150 {
+        if currentRatio < 1 || currentRatio > 150 {
             warning(.openAPS, "Warning: carbRatio of \(currentRatio) out of bounds.")
             return nil
         }

+ 3 - 3
Trio/Sources/APS/Storage/GlucoseStorage.swift

@@ -214,6 +214,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
 
         guard context.hasChanges else { return }
         try context.save()
+        updateSubject.send()
     }
 
     private func storeGlucoseBatch(_ glucose: [BloodGlucose]) throws {
@@ -232,7 +233,6 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
             }
         )
         try context.execute(batchInsert)
-        // Only send update for batch insert since regular save triggers CoreData notifications
         updateSubject.send()
     }
 
@@ -771,11 +771,11 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
 
                 let glucoseValue = glucose.glucose
 
-                if Decimal(glucoseValue) <= settingsManager.settings.lowGlucose {
+                if Decimal(glucoseValue) <= settingsManager.settings.low {
                     return .low
                 }
 
-                if Decimal(glucoseValue) >= settingsManager.settings.highGlucose {
+                if Decimal(glucoseValue) >= settingsManager.settings.high {
                     return .high
                 }
 

+ 3 - 0
Trio/Sources/Application/TrioApp.swift

@@ -90,6 +90,9 @@ extension Notification.Name {
             _ = resolver.resolve(LiveActivityManager.self)!
         }
         _ = resolver.resolve(IOBService.self)!
+        _ = resolver.resolve(GlucoseAlertCoordinator.self)!
+        _ = resolver.resolve(NotLoopingMonitor.self)!
+        _ = DeviceAlertsStore.shared
     }
 
     init() {

+ 6 - 0
Trio/Sources/Assemblies/ServiceAssembly.swift

@@ -17,6 +17,12 @@ final class ServiceAssembly: Assembly {
         container.register(CalendarManager.self) { r in BaseCalendarManager(resolver: r) }
         container.register(HKHealthStore.self) { _ in HKHealthStore() }
         container.register(HealthKitManager.self) { r in BaseHealthKitManager(resolver: r) }
+        container.register(TrioAlertManager.self) { r in BaseTrioAlertManager(resolver: r) }
+            .inObjectScope(.container)
+        container.register(GlucoseAlertCoordinator.self) { r in GlucoseAlertCoordinator(resolver: r) }
+            .inObjectScope(.container)
+        container.register(NotLoopingMonitor.self) { r in NotLoopingMonitor(resolver: r) }
+            .inObjectScope(.container)
         container.register(UserNotificationsManager.self) { r in BaseUserNotificationsManager(resolver: r) }
         container.register(WatchManager.self) { r in BaseWatchManager(resolver: r) }
         container.register(BolusCalculationManager.self) { r in BaseBolusCalculationManager(resolver: r) }

+ 1 - 1
Trio/Sources/Helpers/Formatters.swift

@@ -100,7 +100,7 @@ extension Formatter {
         let formatter = NumberFormatter()
         formatter.numberStyle = .decimal
         formatter.locale = .current
-        formatter.minimumIntegerDigits = 0
+        formatter.minimumIntegerDigits = 1
         formatter.maximumFractionDigits = 2
         return formatter
     }()

+ 29 - 0
Trio/Sources/Helpers/TimeInterval+Convenience.swift

@@ -0,0 +1,29 @@
+import Foundation
+
+/// Convenience constructors + getters used across the app for `TimeInterval`
+/// math at minute/hour granularity. Previously lived inside the deleted
+/// `Snooze` module; broken out here because real callers exist outside the
+/// alert pipeline (chart markers, pump-history windows, fetch timers, etc.).
+extension TimeInterval {
+    static func seconds(_ seconds: Double) -> TimeInterval { seconds }
+
+    static func minutes(_ minutes: Double) -> TimeInterval {
+        TimeInterval(minutes: minutes)
+    }
+
+    static func hours(_ hours: Double) -> TimeInterval {
+        TimeInterval(minutes: hours * 60)
+    }
+
+    init(minutes: Double) {
+        self.init(minutes * 60)
+    }
+
+    init(hours: Double) {
+        self.init(minutes: hours * 60)
+    }
+
+    var minutes: Double { self / 60.0 }
+
+    var hours: Double { minutes / 60.0 }
+}

File diff suppressed because it is too large
+ 8227 - 25698
Trio/Sources/Localizations/Main/Localizable.xcstrings


+ 1 - 47
Trio/Sources/Logger/Logger.swift

@@ -5,7 +5,6 @@ import UIKit
 var LoggerTestMode = false
 
 private let baseReporter = TrioApp.resolver.resolve(GroupedIssueReporter.self)!
-private let router = TrioApp.resolver.resolve(Router.self)!
 
 let loggerLock = NSRecursiveLock()
 
@@ -28,37 +27,13 @@ func debug(
 func info(
     _ category: Logger.Category,
     _ message: String,
-    type: MessageType = .info,
     file: String = #file,
     function: String = #function,
     line: UInt = #line
 ) {
     DispatchWorkItem(qos: .background, flags: .enforceQoS) {
         loggerLock.perform {
-            category.logger.info(message, type: type, file: file, function: function, line: line)
-        }
-    }.perform()
-}
-
-func info(
-    _ category: Logger.Category,
-    _ message: String,
-    notificationText: String,
-    type: MessageType = .info,
-    file: String = #file,
-    function: String = #function,
-    line: UInt = #line
-) {
-    DispatchWorkItem(qos: .background, flags: .enforceQoS) {
-        loggerLock.perform {
-            category.logger.info(
-                message,
-                notificationText: notificationText,
-                type: type,
-                file: file,
-                function: function,
-                line: line
-            )
+            category.logger.info(message, file: file, function: function, line: line)
         }
     }.perform()
 }
@@ -268,18 +243,6 @@ final class Logger {
 
     func info(
         _ message: String,
-        type: MessageType = .info,
-        file: String = #file,
-        function: String = #function,
-        line: UInt = #line
-    ) {
-        info(message, notificationText: message, type: type, file: file, function: function, line: line)
-    }
-
-    func info(
-        _ message: String,
-        notificationText: String,
-        type: MessageType = .info,
         file: String = #file,
         function: String = #function,
         line: UInt = #line
@@ -287,8 +250,6 @@ final class Logger {
         let printedMessage = "INFO: \(message)"
         os_log("%@ - %@ - %d %{public}@", log: log, type: .info, file.file, function, line, printedMessage)
         reporter.log(category.name, printedMessage, file: file, function: function, line: line)
-
-        showAlert(notificationText, type: type)
     }
 
     func warning(
@@ -324,13 +285,6 @@ final class Logger {
         )
     }
 
-    private func showAlert(_ message: String, type: MessageType = .info) {
-        DispatchQueue.main.async {
-            let messageCont = MessageContent(content: message, type: type)
-            router.alertMessage.send(messageCont)
-        }
-    }
-
     fileprivate func errorWithoutFatalError(
         _ message: String,
         description: String? = nil,

+ 0 - 2
Trio/Sources/Models/DecimalPickerSettings.swift

@@ -33,8 +33,6 @@ class PickerSettingsProvider: ObservableObject {
 }
 
 struct DecimalPickerSettings {
-    var lowGlucose = PickerSetting(value: 70, step: 5, min: 40, max: 100, type: PickerSetting.PickerSettingType.glucose)
-    var highGlucose = PickerSetting(value: 180, step: 5, min: 100, max: 400, type: PickerSetting.PickerSettingType.glucose)
     var carbsRequiredThreshold = PickerSetting(value: 10, step: 1, min: 0, max: 100, type: PickerSetting.PickerSettingType.gram)
     var individualAdjustmentFactor = PickerSetting(
         value: 0.5,

+ 34 - 0
Trio/Sources/Models/GlucoseAlerts/AlarmSoundCatalog.swift

@@ -0,0 +1,34 @@
+import Foundation
+
+/// Catalog of the bundled critical-alarm sound files (`Trio/Resources/Sounds/`).
+/// Ported from Loop's audio-critical-alerts branch. Single source of truth
+/// for the alarm sound picker.
+enum AlarmSoundCatalog {
+    /// (filename, displayName) tuples in display order.
+    private static let catalog: [(filename: String, displayName: String)] = [
+        ("urgent_low.caf", String(localized: "Urgent Low")),
+        ("critical.caf", String(localized: "Critical")),
+        ("alarm.caf", String(localized: "Alarm")),
+        ("bright_alarm.caf", String(localized: "Bright Alarm")),
+        ("honk.caf", String(localized: "Honk")),
+        ("trill.caf", String(localized: "Trill")),
+        ("chime.caf", String(localized: "Chime")),
+        ("clear_chimes.caf", String(localized: "Clear Chimes")),
+        ("high_chimes.caf", String(localized: "High Chimes")),
+        ("dings.caf", String(localized: "Dings")),
+        ("bloom.caf", String(localized: "Bloom")),
+        ("bloop.caf", String(localized: "Bloop")),
+        ("spring.caf", String(localized: "Spring")),
+        ("minimal.caf", String(localized: "Minimal")),
+        ("simple.caf", String(localized: "Simple")),
+        ("synth.caf", String(localized: "Synth")),
+        ("mood_synth.caf", String(localized: "Mood Synth")),
+        ("crying.caf", String(localized: "Crying"))
+    ]
+
+    static let allFilenames: [String] = catalog.map(\.filename)
+
+    static func displayName(for filename: String) -> String {
+        catalog.first { $0.filename == filename }?.displayName ?? filename
+    }
+}

+ 33 - 0
Trio/Sources/Models/GlucoseAlerts/DayNightOptions.swift

@@ -0,0 +1,33 @@
+import Foundation
+
+protocol DayNightDisplayable {
+    var displayName: String { get }
+}
+
+extension DayNightDisplayable where Self: RawRepresentable, Self.RawValue == String {
+    var displayName: String {
+        rawValue == "always"
+            ? String(localized: "Day & Night")
+            : rawValue.localizedCapitalized
+    }
+}
+
+enum ActiveOption: String, CaseIterable, Codable, Identifiable, DayNightDisplayable {
+    case always
+    case day
+    case night
+
+    var id: String { rawValue }
+}
+
+struct TimeOfDay: Codable, Equatable, Hashable {
+    var hour: Int
+    var minute: Int
+
+    init(hour: Int, minute: Int) {
+        self.hour = max(0, min(23, hour))
+        self.minute = max(0, min(59, minute))
+    }
+
+    var minutesSinceMidnight: Int { hour * 60 + minute }
+}

+ 79 - 0
Trio/Sources/Models/GlucoseAlerts/DeviceAlertSeverity.swift

@@ -0,0 +1,79 @@
+import Foundation
+import LoopKit
+
+/// Coarse-grained tier the user configures for device alarms. Wraps
+/// `Alert.InterruptionLevel` plus its visible behavior:
+/// - `.critical` overrides Silence & Focus Mode (uses the critical-audio
+///   fallback for builds without the Critical Alerts entitlement)
+/// - `.timeSensitive` pierces normal banner suppression but obeys DND/silent
+/// - `.normal` fires only when the device isn't silenced — informational
+enum DeviceAlertSeverity: String, Codable, CaseIterable, Identifiable {
+    case critical
+    case timeSensitive
+    case normal
+
+    var id: String { rawValue }
+
+    init?(level: Alert.InterruptionLevel) {
+        switch level {
+        case .critical: self = .critical
+        case .timeSensitive: self = .timeSensitive
+        case .active: self = .normal
+        }
+    }
+
+    var displayName: String {
+        switch self {
+        case .critical: return String(localized: "Critical")
+        case .timeSensitive: return String(localized: "Time-Sensitive")
+        case .normal: return String(localized: "Normal")
+        }
+    }
+
+    var blurb: String {
+        switch self {
+        case .critical:
+            return String(localized: "Overrides Silence & Focus Mode. For situations requiring immediate attention.")
+        case .timeSensitive:
+            return String(localized: "Pierces banner suppression but obeys Silence & Focus Mode by default.")
+        case .normal:
+            return String(localized: "Default notification banner. Suppressed by Silence & Focus Mode by default.")
+        }
+    }
+
+    var hintText: String {
+        switch self {
+        case .critical:
+            return String(
+                localized: "For situations that require prompt attention. These break through Silent Mode, Do Not Disturb, and any Focus you have enabled. Examples: a pump fault, an occlusion, or Trio not looping for too long. Heads up: if your build of Trio has Apple's Critical Alerts entitlement, iOS plays its own critical alert sound and the sound you picked for this alert category is ignored."
+            )
+        case .timeSensitive:
+            return String(
+                localized: "For things you should know about soon, but not 'act right now'. These can break through banner suppression on the lock screen, but they still obey Silent Mode and Focus by default. Examples: reservoir running low, pod or patch expiring soon, or glucose data going stale."
+            )
+        case .normal:
+            return String(
+                localized: "For everyday heads-up notifications. These behave like a standard banner — they stay quiet when your phone is silenced or a Focus is on. Examples: an algorithm error, a sensor expiration reminder, or a time-zone change being detected."
+            )
+        }
+    }
+
+    var defaultSoundFilename: String {
+        switch self {
+        case .critical: return "alarm.caf"
+        case .timeSensitive: return "chime.caf"
+        case .normal: return "bloop.caf"
+        }
+    }
+
+    /// Default for the per-tier override toggle when seeded. Tier names are
+    /// labels now — the actual `Alert.InterruptionLevel` is derived from the
+    /// override flag at fire time (true → `.critical`, false → `.timeSensitive`).
+    var defaultOverridesSilenceAndDND: Bool {
+        switch self {
+        case .critical: return true
+        case .normal,
+             .timeSensitive: return false
+        }
+    }
+}

+ 63 - 0
Trio/Sources/Models/GlucoseAlerts/DeviceAlertSeverityConfig.swift

@@ -0,0 +1,63 @@
+import Foundation
+
+/// One user-configured behavior variant for a device-alarm severity tier.
+/// Multiple configs per severity are allowed — each with its own
+/// `activeOption` (Day & Night / Day only / Night only) — so the user can
+/// e.g. have a Critical config that overrides Silence during the day and
+/// a second Critical config that goes silent at night.
+///
+/// Lookup at fire time picks the variant whose `activeOption` matches the
+/// current day/night window, falling back to the `.always` variant.
+struct DeviceAlertSeverityConfig: Codable, Equatable, Identifiable {
+    var id: UUID
+    var severity: DeviceAlertSeverity
+    var isEnabled: Bool
+    var soundFilename: String
+    var playsSound: Bool
+    /// When true, alarms in this tier bypass Focus Mode / silent switch
+    /// (maps to `Alert.InterruptionLevel.critical` and engages the in-process
+    /// `CriticalAlertAudioPlayer` fallback if `playsSound` is true).
+    /// When false, the alarm uses `.timeSensitive`.
+    var overridesSilenceAndDND: Bool
+    var activeOption: ActiveOption
+
+    init(
+        id: UUID = UUID(),
+        severity: DeviceAlertSeverity,
+        activeOption: ActiveOption = .always
+    ) {
+        self.id = id
+        self.severity = severity
+        isEnabled = true
+        soundFilename = severity.defaultSoundFilename
+        playsSound = true
+        overridesSilenceAndDND = severity.defaultOverridesSilenceAndDND
+        self.activeOption = activeOption
+    }
+
+    // MARK: - Codable
+
+    private enum CodingKeys: String, CodingKey {
+        case id
+        case severity
+        case isEnabled
+        case soundFilename
+        case playsSound
+        case overridesSilenceAndDND
+        case activeOption
+    }
+
+    init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID()
+        severity = try container.decode(DeviceAlertSeverity.self, forKey: .severity)
+        isEnabled = try container.decodeIfPresent(Bool.self, forKey: .isEnabled) ?? true
+        soundFilename = try container.decodeIfPresent(String.self, forKey: .soundFilename) ?? severity.defaultSoundFilename
+        playsSound = try container.decodeIfPresent(Bool.self, forKey: .playsSound) ?? true
+        overridesSilenceAndDND = try container.decodeIfPresent(
+            Bool.self,
+            forKey: .overridesSilenceAndDND
+        ) ?? severity.defaultOverridesSilenceAndDND
+        activeOption = try container.decodeIfPresent(ActiveOption.self, forKey: .activeOption) ?? .always
+    }
+}

+ 77 - 0
Trio/Sources/Models/GlucoseAlerts/GlucoseAlert.swift

@@ -0,0 +1,77 @@
+import Foundation
+
+/// A single configurable glucose alarm. Multiple entries of the same type are
+/// allowed — e.g. a Low alarm `active: .day` at 80 mg/dL plus a second Low
+/// `active: .night` at 70 mg/dL.
+struct GlucoseAlert: Identifiable, Codable, Equatable {
+    var id: UUID
+    var type: GlucoseAlertType
+    var name: String
+    var isEnabled: Bool
+    var thresholdMgDL: Decimal
+    var soundFilename: String
+    /// When false, the alarm fires the banner / notification but no sound.
+    /// iOS still drives haptics from the interruption level.
+    var playsSound: Bool
+    /// When true, this alarm bypasses Focus Mode / silent switch
+    /// modes. Maps to `Alert.InterruptionLevel.critical` and triggers the
+    /// in-process `CriticalAlertAudioPlayer` fallback for builds without the
+    /// Critical Alerts entitlement.
+    var overridesSilenceAndDND: Bool
+    var activeOption: ActiveOption
+    /// Per-alarm snooze. Distinct from the global mute on `AlertMuter`.
+    var snoozedUntil: Date?
+
+    init(type: GlucoseAlertType) {
+        id = UUID()
+        self.type = type
+        name = type.displayName
+        isEnabled = true
+        thresholdMgDL = type.defaultThresholdMgDL
+        soundFilename = type.defaultSoundFilename
+        playsSound = true
+        overridesSilenceAndDND = type.defaultOverridesSilenceAndDND
+        activeOption = .always
+        snoozedUntil = nil
+    }
+
+    /// Whether the coordinator should fire this alarm when a reading breaches.
+    /// Urgent-low is the safety floor — the editor hides the Enabled toggle so
+    /// the user can't accidentally turn it off, and stored `isEnabled = false`
+    /// from a prior install is ignored here.
+    var shouldEvaluate: Bool {
+        type == .urgentLow || isEnabled
+    }
+
+    // MARK: - Codable
+
+    private enum CodingKeys: String, CodingKey {
+        case id
+        case type
+        case name
+        case isEnabled
+        case thresholdMgDL
+        case soundFilename
+        case playsSound
+        case overridesSilenceAndDND
+        case activeOption
+        case snoozedUntil
+    }
+
+    init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        id = try container.decode(UUID.self, forKey: .id)
+        type = try container.decode(GlucoseAlertType.self, forKey: .type)
+        name = try container.decode(String.self, forKey: .name)
+        isEnabled = try container.decodeIfPresent(Bool.self, forKey: .isEnabled) ?? true
+        thresholdMgDL = try container.decode(Decimal.self, forKey: .thresholdMgDL)
+        soundFilename = try container.decodeIfPresent(String.self, forKey: .soundFilename) ?? type.defaultSoundFilename
+        playsSound = try container.decodeIfPresent(Bool.self, forKey: .playsSound) ?? true
+        overridesSilenceAndDND = try container.decodeIfPresent(
+            Bool.self,
+            forKey: .overridesSilenceAndDND
+        ) ?? type.defaultOverridesSilenceAndDND
+        activeOption = try container.decodeIfPresent(ActiveOption.self, forKey: .activeOption) ?? .always
+        snoozedUntil = try container.decodeIfPresent(Date.self, forKey: .snoozedUntil)
+    }
+}

+ 63 - 0
Trio/Sources/Models/GlucoseAlerts/GlucoseAlertConfiguration.swift

@@ -0,0 +1,63 @@
+import Foundation
+
+struct GlucoseAlertConfiguration: Codable, Equatable {
+    var dayStart: TimeOfDay
+    var nightStart: TimeOfDay
+    /// Force Trio alarms on even when the CGM advertises its own.
+    var forceTrioAlertsWhenCGMProvidesOwn: Bool
+
+    init(
+        dayStart: TimeOfDay = TimeOfDay(hour: 6, minute: 0),
+        nightStart: TimeOfDay = TimeOfDay(hour: 22, minute: 0),
+        forceTrioAlertsWhenCGMProvidesOwn: Bool = false
+    ) {
+        self.dayStart = dayStart
+        self.nightStart = nightStart
+        self.forceTrioAlertsWhenCGMProvidesOwn = forceTrioAlertsWhenCGMProvidesOwn
+    }
+
+    private enum CodingKeys: String, CodingKey {
+        case dayStart
+        case nightStart
+        case forceTrioAlertsWhenCGMProvidesOwn
+    }
+
+    init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        dayStart = try container.decode(TimeOfDay.self, forKey: .dayStart)
+        nightStart = try container.decode(TimeOfDay.self, forKey: .nightStart)
+        forceTrioAlertsWhenCGMProvidesOwn = try container.decodeIfPresent(
+            Bool.self,
+            forKey: .forceTrioAlertsWhenCGMProvidesOwn
+        ) ?? false
+    }
+
+    /// Resolve whether `date` falls into the user's "night" window. Mirrors
+    /// LoopFollow's logic: handles both same-day (06→22) and wrap-around
+    /// (22→06) ranges. When `nightStart >= dayStart`, night is "later than
+    /// nightStart OR earlier than dayStart"; otherwise night is the slice
+    /// between nightStart and dayStart.
+    func isNight(at date: Date, calendar: Calendar = .current) -> Bool {
+        let startOfDay = calendar.startOfDay(for: date)
+        guard
+            let dayStartDate = calendar.date(
+                bySettingHour: dayStart.hour,
+                minute: dayStart.minute,
+                second: 0,
+                of: startOfDay
+            ),
+            let nightStartDate = calendar.date(
+                bySettingHour: nightStart.hour,
+                minute: nightStart.minute,
+                second: 0,
+                of: startOfDay
+            )
+        else { return false }
+
+        if nightStartDate >= dayStartDate {
+            return date >= nightStartDate || date < dayStartDate
+        } else {
+            return date >= nightStartDate && date < dayStartDate
+        }
+    }
+}

+ 103 - 0
Trio/Sources/Models/GlucoseAlerts/GlucoseAlertType.swift

@@ -0,0 +1,103 @@
+import Foundation
+import LoopKit
+
+/// Glucose-driven alarm types Trio supports today. Order is priority order
+/// (`urgentLow` highest), matching how `AlarmType.priority` works in
+/// LoopFollow — when multiple alarms would fire on the same reading, the
+/// higher-priority one wins and lower types of the same group are skipped.
+enum GlucoseAlertType: String, Codable, CaseIterable, Identifiable {
+    case urgentLow
+    case low
+    case forecastedLow
+    case high
+    /// Driven by `Determination.carbsReq`, not by a glucose reading. Stored
+    /// alongside the other glucose alarms so the user has one place to
+    /// configure schedule/sound/snooze for everything fired by Trio.
+    case carbsRequired
+
+    var id: String { rawValue }
+
+    var priority: Int { Self.allCases.firstIndex(of: self) ?? 0 }
+
+    /// `true` when the alarm fires off a CGM glucose reading. `false` for
+    /// `forecastedLow` (driven by the determination forecast) and
+    /// `carbsRequired` (driven by the determination's `carbsReq` field).
+    var isReadingDriven: Bool {
+        switch self {
+        case .high,
+             .low,
+             .urgentLow:
+            return true
+        case .carbsRequired,
+             .forecastedLow:
+            return false
+        }
+    }
+
+    /// Parses a glucose-alarm slug emitted by `GlucoseAlertCoordinator`
+    /// (`glucose.<type>.<uuid>`). Returns nil for non-glucose alert
+    /// identifiers — used by `BaseTrioAlertManager.requestSnooze` to decide
+    /// between per-type and global mute routing.
+    init?(slug: String) {
+        let parts = slug.split(separator: ".")
+        guard parts.count >= 2, parts[0] == "glucose" else { return nil }
+        guard let parsed = GlucoseAlertType(rawValue: String(parts[1])) else { return nil }
+        self = parsed
+    }
+
+    var displayName: String {
+        switch self {
+        case .urgentLow: return String(localized: "Urgent Low Glucose")
+        case .low: return String(localized: "Low Glucose")
+        case .forecastedLow: return String(localized: "Low Glucose Soon")
+        case .high: return String(localized: "High Glucose")
+        case .carbsRequired: return String(localized: "Carbs Required")
+        }
+    }
+
+    var blurb: String {
+        switch self {
+        case .urgentLow: return String(localized: "Fires when glucose drops to or below an urgent low threshold.")
+        case .low: return String(localized: "Fires when glucose drops to or below a low threshold.")
+        case .forecastedLow: return String(localized: "Fires when glucose is forecasted to be low within the next 20 minutes.")
+        case .high: return String(localized: "Fires when glucose rises to or above a high threshold.")
+        case .carbsRequired: return String(localized: "Fires when oref recommends eating carbs to avoid a low.")
+        }
+    }
+
+    /// Default threshold when adding a new alarm. Mg/dL for glucose types,
+    /// grams for `carbsRequired`.
+    var defaultThresholdMgDL: Decimal {
+        switch self {
+        case .urgentLow: return 54
+        case .low: return 72
+        case .forecastedLow: return 72
+        case .high: return 270
+        case .carbsRequired: return 10
+        }
+    }
+
+    /// Default bundled sound filename. See `Trio/Resources/Sounds/`.
+    var defaultSoundFilename: String {
+        switch self {
+        case .urgentLow: return "urgent_low.caf"
+        case .low: return "trill.caf"
+        case .forecastedLow: return "bloom.caf"
+        case .high: return "chime.caf"
+        case .carbsRequired: return "bloop.caf"
+        }
+    }
+
+    /// Default for `GlucoseAlert.overridesSilenceAndDND` when adding a new
+    /// alarm. Urgent-low defaults to override-on to match Loop's stance;
+    /// others default off (time-sensitive, doesn't pierce DND / Focus).
+    var defaultOverridesSilenceAndDND: Bool {
+        switch self {
+        case .urgentLow: return true
+        case .carbsRequired,
+             .forecastedLow,
+             .high,
+             .low: return false
+        }
+    }
+}

+ 0 - 27
Trio/Sources/Models/GlucoseNotificationsOption.swift

@@ -1,27 +0,0 @@
-//
-//  GlucoseNotificationOption.swift
-//  FreeAPS
-//
-//  Created by Kimberlie Skandis on 1/18/25.
-//
-import Foundation
-import SwiftUI
-
-public enum GlucoseNotificationsOption: String, JSON, CaseIterable, Identifiable, Codable, Hashable {
-    case disabled
-    case alwaysEveryCGM
-    case onlyAlarmLimits
-
-    public var id: String { rawValue }
-
-    var displayName: String {
-        switch self {
-        case .disabled:
-            return String(localized: "Disabled", comment: "Option to disable glucose notifications")
-        case .alwaysEveryCGM:
-            return String(localized: "Always", comment: "Option to always notify on every CGM reading")
-        case .onlyAlarmLimits:
-            return String(localized: "Only Alarm Limits", comment: "Option to notify only when glucose reaches alarm limits")
-        }
-    }
-}

+ 10 - 0
Trio/Sources/Models/GlucoseSourceKey.swift

@@ -0,0 +1,10 @@
+import Foundation
+
+/// Keys used by `GlucoseSource.sourceInfo()` implementations to advertise
+/// metadata about the active CGM (description, transmitter battery,
+/// Nightscout ping). Consumed by status views + diagnostics.
+enum GlucoseSourceKey: String {
+    case transmitterBattery
+    case nightscoutPing
+    case description
+}

+ 11 - 9
Trio/Sources/Models/NotificationIdentifiers.swift

@@ -6,7 +6,7 @@ enum NotificationCategoryIdentifier: String {
 }
 
 enum NotificationResponseAction: String, CaseIterable {
-    case snooze20 = "Trio.snooze20"
+    case snooze15 = "Trio.snooze15"
     case snooze1hr = "Trio.snooze1hr"
     case snooze3hr = "Trio.snooze3hr"
     case snooze6hr = "Trio.snooze6hr"
@@ -17,8 +17,8 @@ enum NotificationResponseAction: String, CaseIterable {
 
     var minutes: Int {
         switch self {
-        case .snooze20:
-            return 20
+        case .snooze15:
+            return 15
         case .snooze1hr:
             return 60
         case .snooze3hr:
@@ -30,14 +30,14 @@ enum NotificationResponseAction: String, CaseIterable {
 
     var localizedTitle: String {
         switch self {
-        case .snooze20:
-            return String(localized: "20 min", comment: "Snooze glucose alerts for 20 minutes")
+        case .snooze15:
+            return String(localized: "Snooze 15 min", comment: "Snooze glucose alerts for 15 minutes")
         case .snooze1hr:
-            return String(localized: "1 hour", comment: "Snooze glucose alerts for 1 hour")
+            return String(localized: "Snooze 1 hr", comment: "Snooze glucose alerts for 1 hour")
         case .snooze3hr:
-            return String(localized: "3 hours", comment: "Snooze glucose alerts for 3 hours")
+            return String(localized: "Snooze 3 hrs", comment: "Snooze glucose alerts for 3 hours")
         case .snooze6hr:
-            return String(localized: "6 hours", comment: "Snooze glucose alerts for 6 hours")
+            return String(localized: "Snooze 6 hrs", comment: "Snooze glucose alerts for 6 hours")
         }
     }
 }
@@ -54,11 +54,13 @@ enum NotificationCategoryFactory {
             )
         }
 
+        // `.customDismissAction` — otherwise iOS swallows swipes silently and
+        // the in-app banner outlives the notification.
         return UNNotificationCategory(
             identifier: NotificationCategoryIdentifier.trioAlert.rawValue,
             actions: snoozeActions,
             intentIdentifiers: [],
-            options: []
+            options: [.customDismissAction]
         )
     }
 }

+ 8 - 49
Trio/Sources/Models/TrioSettings.swift

@@ -30,14 +30,6 @@ struct TrioSettings: JSON, Equatable, Encodable {
     var displayCalendarIOBandCOB: Bool = false
     var displayCalendarEmojis: Bool = false
     var glucoseBadge: Bool = false
-    var notificationsPump: Bool = true
-    var notificationsCgm: Bool = true
-    var notificationsCarb: Bool = true
-    var notificationsAlgorithm: Bool = true
-    var glucoseNotificationsOption: GlucoseNotificationsOption = .onlyAlarmLimits
-    var addSourceInfoToGlucoseNotifications: Bool = false
-    var lowGlucose: Decimal = 72
-    var highGlucose: Decimal = 270
     var carbsRequiredThreshold: Decimal = 10
     var showCarbsRequiredBadge: Bool = true
     var useFPUconversion: Bool = false
@@ -70,13 +62,14 @@ struct TrioSettings: JSON, Equatable, Encodable {
     var sweetMealFactor: Decimal = 1
     var displayPresets: Bool = true
     var confirmBolus: Bool = false
+    var enableQuickBolus: Bool = false
     var useLiveActivity: Bool = false
     var lockScreenView: LockScreenView = .simple
     var smartStackView: LockScreenView = .simple
     var bolusShortcut: BolusShortcutLimit = .notAllowed
     var timeInRangeType: TimeInRangeType = .timeInTightRange
     var requireAdjustmentsConfirmation: Bool = false
-    var useSwiftOref: Bool = false
+    var useJavascriptOref: Bool = false
 
     /// Selected Garmin watchface (Trio or SwissAlpine)
     var garminWatchface: GarminWatchface = .trio
@@ -214,44 +207,6 @@ extension TrioSettings: Decodable {
             settings.delay = delay
         }
 
-        if let notificationsPump = try? container.decode(Bool.self, forKey: .notificationsPump) {
-            settings.notificationsPump = notificationsPump
-        }
-
-        if let notificationsCgm = try? container.decode(Bool.self, forKey: .notificationsCgm) {
-            settings.notificationsCgm = notificationsCgm
-        }
-
-        if let notificationsCarb = try? container.decode(Bool.self, forKey: .notificationsCarb) {
-            settings.notificationsCarb = notificationsCarb
-        }
-
-        if let notificationsAlgorithm = try? container.decode(Bool.self, forKey: .notificationsAlgorithm) {
-            settings.notificationsAlgorithm = notificationsAlgorithm
-        }
-
-        if let glucoseNotificationsOption = try? container.decode(
-            GlucoseNotificationsOption.self,
-            forKey: .glucoseNotificationsOption
-        ) {
-            settings.glucoseNotificationsOption = glucoseNotificationsOption
-        }
-
-        if let addSourceInfoToGlucoseNotifications = try? container.decode(
-            Bool.self,
-            forKey: .addSourceInfoToGlucoseNotifications
-        ) {
-            settings.addSourceInfoToGlucoseNotifications = addSourceInfoToGlucoseNotifications
-        }
-
-        if let lowGlucose = try? container.decode(Decimal.self, forKey: .lowGlucose) {
-            settings.lowGlucose = lowGlucose
-        }
-
-        if let highGlucose = try? container.decode(Decimal.self, forKey: .highGlucose) {
-            settings.highGlucose = highGlucose
-        }
-
         if let carbsRequiredThreshold = try? container.decode(Decimal.self, forKey: .carbsRequiredThreshold) {
             settings.carbsRequiredThreshold = carbsRequiredThreshold
         }
@@ -340,6 +295,10 @@ extension TrioSettings: Decodable {
             settings.confirmBolus = confirmBolus
         }
 
+        if let enableQuickBolus = try? container.decode(Bool.self, forKey: .enableQuickBolus) {
+            settings.enableQuickBolus = enableQuickBolus
+        }
+
         if let useLiveActivity = try? container.decode(Bool.self, forKey: .useLiveActivity) {
             settings.useLiveActivity = useLiveActivity
         }
@@ -364,8 +323,8 @@ extension TrioSettings: Decodable {
             settings.requireAdjustmentsConfirmation = requireAdjustmentsConfirmation
         }
 
-        if let useSwiftOref = try? container.decode(Bool.self, forKey: .useSwiftOref) {
-            settings.useSwiftOref = useSwiftOref
+        if let useJavascriptOref = try? container.decode(Bool.self, forKey: .useJavascriptOref) {
+            settings.useJavascriptOref = useJavascriptOref
         }
 
         if let garminWatchface = try? container.decode(GarminWatchface.self, forKey: .garminWatchface) {

+ 7 - 0
Trio/Sources/Modules/AlarmWindows/AlarmWindowsDataFlow.swift

@@ -0,0 +1,7 @@
+import Combine
+
+enum AlarmWindows {
+    enum Config {}
+}
+
+protocol AlarmWindowsProvider: Provider {}

+ 5 - 0
Trio/Sources/Modules/AlarmWindows/AlarmWindowsProvider.swift

@@ -0,0 +1,5 @@
+import Foundation
+
+extension AlarmWindows {
+    final class Provider: BaseProvider, AlarmWindowsProvider {}
+}

+ 18 - 0
Trio/Sources/Modules/AlarmWindows/AlarmWindowsStateModel.swift

@@ -0,0 +1,18 @@
+import Combine
+import SwiftUI
+
+extension AlarmWindows {
+    final class StateModel: BaseStateModel<Provider> {
+        @Published var units: GlucoseUnits = .mgdL
+
+        override func subscribe() {
+            units = settingsManager.settings.units
+        }
+    }
+}
+
+extension AlarmWindows.StateModel: SettingsObserver {
+    func settingsDidChange(_: TrioSettings) {
+        units = settingsManager.settings.units
+    }
+}

+ 135 - 0
Trio/Sources/Modules/AlarmWindows/View/AlarmWindowsRootView.swift

@@ -0,0 +1,135 @@
+import SwiftUI
+import Swinject
+
+extension AlarmWindows {
+    struct RootView: BaseView {
+        let resolver: Resolver
+        @StateObject var state = StateModel()
+        @StateObject private var store = GlucoseAlertsStore.shared
+
+        @State private var shouldDisplayHint: Bool = false
+        @State private var hintDetent = PresentationDetent.large
+        @State private var selectedVerboseHint: AnyView?
+        @State private var hintLabel: String?
+
+        @Environment(\.colorScheme) var colorScheme
+        @Environment(AppState.self) var appState
+
+        private var dayStart: Binding<Date> {
+            Binding(
+                get: { Self.dateFromTimeOfDay(store.configuration.dayStart) },
+                set: { store.configuration.dayStart = Self.timeOfDay(from: $0) }
+            )
+        }
+
+        private var nightStart: Binding<Date> {
+            Binding(
+                get: { Self.dateFromTimeOfDay(store.configuration.nightStart) },
+                set: { store.configuration.nightStart = Self.timeOfDay(from: $0) }
+            )
+        }
+
+        var body: some View {
+            List {
+                Section(header: Text("Window Boundaries")) {
+                    VStack {
+                        DatePicker(
+                            selection: dayStart,
+                            displayedComponents: .hourAndMinute
+                        ) {
+                            HStack {
+                                Image(systemName: "sun.max.fill").foregroundStyle(.orange)
+                                Text("Day Starts")
+                            }
+                        }
+                        .padding(.top)
+
+                        DatePicker(
+                            selection: nightStart,
+                            displayedComponents: .hourAndMinute
+                        ) {
+                            HStack {
+                                Image(systemName: "moon.stars.fill").foregroundStyle(.indigo)
+                                Text("Night Starts")
+                            }
+                        }
+
+                        HStack(alignment: .center) {
+                            Text("Decides when each alarm's Day or Night setting applies.")
+                                .lineLimit(nil)
+                                .font(.footnote)
+                                .foregroundColor(.secondary)
+
+                            Spacer()
+                            Button(
+                                action: {
+                                    hintLabel = String(localized: "Day and Night Window")
+                                    let dayDefault = Self.formattedTimeOfDay(hour: 6, minute: 0)
+                                    let nightDefault = Self.formattedTimeOfDay(hour: 22, minute: 0)
+                                    selectedVerboseHint = AnyView(
+                                        VStack(alignment: .leading, spacing: 10) {
+                                            Text(
+                                                String(
+                                                    format: String(localized: "Default: Day starts %1$@, Night starts %2$@."),
+                                                    dayDefault,
+                                                    nightDefault
+                                                )
+                                            ).bold()
+                                            Text(
+                                                "These two times define the Day and Night windows. Each alarm's Active setting picks one — Day & Night, Day only, or Night only — and only fires when that window is current."
+                                            )
+                                            Text(
+                                                String(
+                                                    format: String(
+                                                        localized: "The Night window runs from 'Night Starts' back around to 'Day Starts' — so by default, Night covers %1$@ through %2$@ the next morning."
+                                                    ),
+                                                    nightDefault,
+                                                    dayDefault
+                                                )
+                                            )
+                                            Text(
+                                                "These windows are shared between Glucose Alarms and Device Alarms."
+                                            )
+                                        }
+                                    )
+                                    shouldDisplayHint.toggle()
+                                },
+                                label: { Image(systemName: "questionmark.circle") }
+                            ).buttonStyle(BorderlessButtonStyle())
+                        }.padding(.top)
+                    }.padding(.bottom)
+                }.listRowBackground(Color.chart)
+            }
+            .scrollContentBackground(.hidden)
+            .background(appState.trioBackgroundColor(for: colorScheme))
+            .navigationTitle("Day & Night Windows")
+            .navigationBarTitleDisplayMode(.inline)
+            .sheet(isPresented: $shouldDisplayHint) {
+                SettingInputHintView(
+                    hintDetent: $hintDetent,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    hintLabel: hintLabel ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
+                    sheetTitle: String(localized: "Help")
+                )
+            }
+            .onAppear(perform: configureView)
+        }
+
+        private static func dateFromTimeOfDay(_ time: TimeOfDay) -> Date {
+            Calendar.current.date(bySettingHour: time.hour, minute: time.minute, second: 0, of: Date()) ?? Date()
+        }
+
+        private static func timeOfDay(from date: Date) -> TimeOfDay {
+            let comps = Calendar.current.dateComponents([.hour, .minute], from: date)
+            return TimeOfDay(hour: comps.hour ?? 0, minute: comps.minute ?? 0)
+        }
+
+        /// Locale-aware HH:mm / h:mm a. Used so 12-hour locales see "10:00 PM"
+        /// instead of "22:00" in hint copy.
+        private static func formattedTimeOfDay(hour: Int, minute: Int) -> String {
+            let date = Calendar.current.date(bySettingHour: hour, minute: minute, second: 0, of: Date()) ?? Date()
+            return date.formatted(.dateTime.hour().minute())
+        }
+    }
+}

+ 3 - 3
Trio/Sources/Modules/AlgorithmAdvancedSettings/AlgorithmAdvancedSettingsStateModel.swift

@@ -22,7 +22,7 @@ extension AlgorithmAdvancedSettings {
         @Published var remainingCarbsFraction: Decimal = 1.0
         @Published var remainingCarbsCap: Decimal = 90
         @Published var noisyCGMTargetMultiplier: Decimal = 1.3
-        @Published var useSwiftOref: Bool = false
+        @Published var useJavascriptOref: Bool = false
         // preference
         @Published var insulinActionCurve: Decimal = 10
         @Published var smbDeliveryRatio: Decimal = 0.5
@@ -48,8 +48,8 @@ extension AlgorithmAdvancedSettings {
             subscribePreferencesSetting(\.remainingCarbsCap, on: $remainingCarbsCap) { remainingCarbsCap = $0 }
             subscribePreferencesSetting(\.noisyCGMTargetMultiplier, on: $noisyCGMTargetMultiplier) {
                 noisyCGMTargetMultiplier = $0 }
-            subscribeSetting(\.useSwiftOref, on: $useSwiftOref) {
-                useSwiftOref = $0 }
+            subscribeSetting(\.useJavascriptOref, on: $useJavascriptOref) {
+                useJavascriptOref = $0 }
             subscribePreferencesSetting(\.smbDeliveryRatio, on: $smbDeliveryRatio) { smbDeliveryRatio = $0 }
             subscribePreferencesSetting(\.smbInterval, on: $smbInterval) { smbInterval = $0 }
             subscribePreferencesSetting(\.smbDeliveryRatio, on: $smbDeliveryRatio) { smbDeliveryRatio = $0 }

+ 7 - 8
Trio/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift

@@ -392,31 +392,30 @@ extension AlgorithmAdvancedSettings {
                 )
                 SettingInputSection(
                     decimalValue: $decimalPlaceholder,
-                    booleanValue: $state.useSwiftOref,
+                    booleanValue: $state.useJavascriptOref,
                     shouldDisplayHint: $shouldDisplayHint,
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
                             selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = NSLocalizedString("Use Swift Oref", comment: "Use Swift Oref")
+                            hintLabel = NSLocalizedString("Use JavaScript Oref", comment: "Use JavaScript Oref")
                         }
                     ),
                     units: state.units,
                     type: .boolean,
-                    label: NSLocalizedString("Use Swift Oref", comment: "Use Swift Oref"),
+                    label: NSLocalizedString("Use JavaScript Oref", comment: "Use JavaScript Oref"),
                     miniHint: String(
-                        localized: "EXPERIMENTAL FEATURE! Enables new, fully Swift-based algorithm version.",
-                        comment: "Use Swift Oref mini hint"
+                        localized: "Switches back to the legacy JavaScript-based algorithm version.",
+                        comment: "Use JavaScript Oref mini hint"
                     ),
                     verboseHint:
                     VStack(alignment: .leading, spacing: 10) {
                         Text("Default: OFF").bold()
-                        Text("This feature is EXPERIMENTAL and not yet cleared for general use.").bold().foregroundStyle(.orange)
                         Text(
-                            "We're building a faster and more maintainable Swift version of the algorithm (Oref) that runs in Trio. It's faster, more accurate and improves Trio for everyone."
+                            "Trio now uses a fully Swift-based version of the algorithm (Oref) by default. It's faster, more accurate and improves Trio for everyone."
                         )
                         Text(
-                            "When enabled, Trio will no longer use the old JavaScript-based algorithm that runs virtualized on your phone. Instead, it will use a fully Swift-based algorithm."
+                            "When enabled, Trio will instead use the legacy JavaScript-based algorithm that runs virtualized on your phone. Only enable this if you encounter issues with the Swift-based algorithm."
                         )
 
                         Text(

+ 31 - 10
Trio/Sources/Modules/CGMSettings/View/CustomCGMOptionsView.swift

@@ -1,3 +1,4 @@
+import LoopKit
 import LoopKitUI
 import SwiftUI
 import Swinject
@@ -26,6 +27,26 @@ extension CGMSettings {
         @State private var simulatedScenarioRaw: String = UserDefaults.standard
             .string(forKey: "GlucoseSimulator.simulatedScenario") ?? SimulatedSensorScenario.runningNormally.rawValue
 
+        /// Routes "open URL failed" warnings through `TrioAlertManager` so
+        /// they share the same in-app banner UI as the rest of the alert
+        /// pipeline (no more SwiftMessages roundtrip).
+        private func warnOpenFailed(identifier: String, title: String, body: String) {
+            let content = Alert.Content(
+                title: title,
+                body: body,
+                acknowledgeActionButtonLabel: String(localized: "OK")
+            )
+            let alert = Alert(
+                identifier: Alert.Identifier(managerIdentifier: "trio.cgmSettings", alertIdentifier: identifier),
+                foregroundContent: content,
+                backgroundContent: content,
+                trigger: .immediate,
+                interruptionLevel: .active,
+                sound: nil
+            )
+            resolver.resolve(TrioAlertManager.self)?.issueAlert(alert)
+        }
+
         // Initialize state variables with defaults if needed
         private func initializeSimulatorSettings() {
             if centerValue == 0 {
@@ -69,11 +90,11 @@ extension CGMSettings {
                                 Button {
                                     UIApplication.shared.open(appURL, options: [:]) { success in
                                         if !success {
-                                            self.router.alertMessage
-                                                .send(MessageContent(
-                                                    content: "Unable to open the app",
-                                                    type: .warning
-                                                ))
+                                            warnOpenFailed(
+                                                identifier: "cgm.app.open.failed",
+                                                title: String(localized: "Open failed"),
+                                                body: String(localized: "Unable to open the app")
+                                            )
                                         }
                                     }
                                 }
@@ -156,11 +177,11 @@ extension CGMSettings {
                         Button {
                             UIApplication.shared.open(url, options: [:]) { success in
                                 if !success {
-                                    self.router.alertMessage
-                                        .send(MessageContent(
-                                            content: "No URL available",
-                                            type: .warning
-                                        ))
+                                    warnOpenFailed(
+                                        identifier: "nightscout.open.failed",
+                                        title: String(localized: "Open failed"),
+                                        body: String(localized: "No URL available")
+                                    )
                                 }
                             }
                         }

+ 27 - 1
Trio/Sources/Modules/Calibrations/CalibrationsStateModel.swift

@@ -1,4 +1,5 @@
 import CoreData
+import LoopKit
 import Observation
 import SwiftDate
 import SwiftUI
@@ -7,6 +8,7 @@ extension Calibrations {
     @Observable final class StateModel: BaseStateModel<Provider> {
         @ObservationIgnored @Injected() var glucoseStorage: GlucoseStorage!
         @ObservationIgnored @Injected() var calibrationService: CalibrationService!
+        @ObservationIgnored @Injected() var trioAlertManager: TrioAlertManager!
 
         var slope: Double = 1
         var intercept: Double = 1
@@ -77,7 +79,8 @@ extension Calibrations {
 
                     calibrationService.addCalibration(calibration)
                 } else {
-                    info(.service, "Glucose is stale for calibration")
+                    debug(.service, "Glucose is stale for calibration")
+                    issueStaleGlucoseAlert()
                     return
                 }
             } catch {
@@ -85,6 +88,29 @@ extension Calibrations {
             }
         }
 
+        /// Surfaces the "glucose too stale to calibrate against" condition as
+        /// a one-shot info alert through `TrioAlertManager`. Mirrors the old
+        /// `info(.service, …)` banner path that ran via `router.alertMessage`.
+        private func issueStaleGlucoseAlert() {
+            let content = Alert.Content(
+                title: String(localized: "Calibration unavailable"),
+                body: String(localized: "Glucose is stale for calibration"),
+                acknowledgeActionButtonLabel: String(localized: "OK")
+            )
+            let alert = Alert(
+                identifier: Alert.Identifier(
+                    managerIdentifier: "trio.calibration",
+                    alertIdentifier: "glucose.stale"
+                ),
+                foregroundContent: content,
+                backgroundContent: content,
+                trigger: .immediate,
+                interruptionLevel: .active,
+                sound: nil
+            )
+            trioAlertManager?.issueAlert(alert)
+        }
+
         func removeLast() {
             calibrationService.removeLast()
             setupCalibrations()

+ 7 - 0
Trio/Sources/Modules/DeviceAlarms/DeviceAlarmsDataFlow.swift

@@ -0,0 +1,7 @@
+import Combine
+
+enum DeviceAlarms {
+    enum Config {}
+}
+
+protocol DeviceAlarmsProvider: Provider {}

+ 5 - 0
Trio/Sources/Modules/DeviceAlarms/DeviceAlarmsProvider.swift

@@ -0,0 +1,5 @@
+import Foundation
+
+extension DeviceAlarms {
+    final class Provider: BaseProvider, DeviceAlarmsProvider {}
+}

+ 8 - 0
Trio/Sources/Modules/DeviceAlarms/DeviceAlarmsStateModel.swift

@@ -0,0 +1,8 @@
+import Combine
+import SwiftUI
+
+extension DeviceAlarms {
+    final class StateModel: BaseStateModel<Provider> {
+        override func subscribe() {}
+    }
+}

+ 65 - 0
Trio/Sources/Modules/DeviceAlarms/View/AddDeviceAlarmSheet.swift

@@ -0,0 +1,65 @@
+import SwiftUI
+
+struct AddDeviceAlarmSheet: View {
+    let onPick: (DeviceAlertSeverity) -> Void
+
+    @Environment(\.dismiss) private var dismiss
+    @Environment(\.colorScheme) private var colorScheme
+    @Environment(AppState.self) private var appState
+
+    var body: some View {
+        NavigationStack {
+            List {
+                Section {
+                    ForEach(DeviceAlertSeverity.allCases) { severity in
+                        Button {
+                            onPick(severity)
+                            dismiss()
+                        } label: {
+                            HStack(spacing: 12) {
+                                Image(systemName: icon(for: severity))
+                                    .foregroundStyle(tint(for: severity))
+                                    .frame(width: 28)
+                                VStack(alignment: .leading, spacing: 2) {
+                                    Text(severity.displayName)
+                                        .foregroundColor(.primary)
+                                    Text(severity.blurb)
+                                        .font(.footnote)
+                                        .foregroundColor(.secondary)
+                                }
+                                Spacer()
+                                Image(systemName: "chevron.right")
+                                    .font(.footnote)
+                                    .foregroundColor(.secondary)
+                            }
+                        }
+                    }
+                }.listRowBackground(Color.chart)
+            }
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
+            .navigationTitle("Add Variant")
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .cancellationAction) {
+                    Button(String(localized: "Cancel")) { dismiss() }
+                }
+            }
+        }
+    }
+
+    private func icon(for severity: DeviceAlertSeverity) -> String {
+        switch severity {
+        case .critical: return "exclamationmark.triangle.fill"
+        case .timeSensitive: return "bell.badge.fill"
+        case .normal: return "bell.fill"
+        }
+    }
+
+    private func tint(for severity: DeviceAlertSeverity) -> Color {
+        switch severity {
+        case .critical: return .red
+        case .timeSensitive: return .orange
+        case .normal: return .accentColor
+        }
+    }
+}

+ 129 - 0
Trio/Sources/Modules/DeviceAlarms/View/DeviceAlarmEditorView.swift

@@ -0,0 +1,129 @@
+import LoopKit
+import SwiftUI
+
+struct DeviceAlarmEditorView: View {
+    @ObservedObject var store: DeviceAlertsStore
+    let configID: UUID
+    let isNew: Bool
+    var onDone: () -> Void
+    var onCancel: () -> Void
+
+    @Environment(\.dismiss) private var dismiss
+    @Environment(\.colorScheme) private var colorScheme
+    @Environment(AppState.self) private var appState
+    @State private var working: DeviceAlertSeverityConfig
+
+    init(
+        store: DeviceAlertsStore,
+        initial: DeviceAlertSeverityConfig,
+        isNew: Bool,
+        onDone: @escaping () -> Void,
+        onCancel: @escaping () -> Void = {}
+    ) {
+        self.store = store
+        configID = initial.id
+        self.isNew = isNew
+        self.onDone = onDone
+        self.onCancel = onCancel
+        _working = State(initialValue: initial)
+    }
+
+    var body: some View {
+        NavigationStack {
+            Form {
+                Section(
+                    header: Text("Behavior"),
+                    footer: Text(working.severity.blurb)
+                ) {
+                    HStack {
+                        Text(working.severity.displayName).font(.headline)
+                        Spacer()
+                        Text(activeLabel)
+                            .font(.footnote)
+                            .foregroundColor(.secondary)
+                    }
+                    // Critical-tier configs are always armed — the user can
+                    // mute the sound via the Audio section but not turn the
+                    // alarm itself off. Other tiers expose the toggle.
+                    if working.severity != .critical {
+                        Toggle(String(localized: "Enabled"), isOn: $working.isEnabled)
+                    }
+                    Toggle(
+                        String(localized: "Override Silence & Focus Mode"),
+                        isOn: $working.overridesSilenceAndDND
+                    )
+                }.listRowBackground(Color.chart)
+
+                AlarmActiveSection(activeOption: $working.activeOption)
+                AlarmAudioSection(
+                    playsSound: $working.playsSound,
+                    soundFilename: $working.soundFilename
+                )
+
+                Section(header: Text("Applies To (Cannot be changed)")) {
+                    ForEach(conceptsForTier(working.severity), id: \.self) { concept in
+                        Text(concept.displayTitle)
+                            .font(.footnote)
+                            .foregroundColor(.secondary)
+                    }
+                }.listRowBackground(Color.chart)
+
+                if !isNew, store.canDelete(working) {
+                    Section {
+                        Button(role: .destructive) {
+                            store.remove(working)
+                            dismiss()
+                        } label: {
+                            Text("Delete Variant")
+                        }
+                    }.listRowBackground(Color.chart)
+                }
+            }
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
+            .navigationTitle(working.severity.displayName)
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .confirmationAction) {
+                    Button(isNew ? String(localized: "Add") : String(localized: "Done")) {
+                        if isNew {
+                            store.add(working)
+                        } else {
+                            store.update(working)
+                        }
+                        onDone()
+                        dismiss()
+                    }
+                }
+                ToolbarItem(placement: .cancellationAction) {
+                    Button(String(localized: "Cancel")) {
+                        onCancel()
+                        dismiss()
+                    }
+                }
+            }
+        }
+    }
+
+    private var activeLabel: String {
+        switch working.activeOption {
+        case .always: return String(localized: "Day & Night")
+        case .day: return String(localized: "Day only")
+        case .night: return String(localized: "Night only")
+        }
+    }
+
+    /// Distinct alarm concepts whose catalog entries fall into this tier.
+    /// Sorted by display title so the list is stable across plugin changes.
+    private func conceptsForTier(_ tier: DeviceAlertSeverity) -> [LoopKit.Alert.CatalogConcept] {
+        var seen: Set<LoopKit.Alert.CatalogConcept> = []
+        var ordered: [LoopKit.Alert.CatalogConcept] = []
+        for entry in AlertCatalogRegistry.entries
+            where DeviceAlertSeverity(level: entry.interruptionLevel) == tier
+        {
+            if seen.insert(entry.concept).inserted {
+                ordered.append(entry.concept)
+            }
+        }
+        return ordered.sorted { $0.displayTitle < $1.displayTitle }
+    }
+}

+ 226 - 0
Trio/Sources/Modules/DeviceAlarms/View/DeviceAlarmsRootView.swift

@@ -0,0 +1,226 @@
+import SwiftUI
+import Swinject
+
+private enum DeviceAlarmSheet: Identifiable {
+    case picker
+    case editor(DeviceAlertSeverityConfig, isNew: Bool)
+    case help(DeviceAlertSeverity)
+
+    var id: String {
+        switch self {
+        case .picker: return "picker"
+        case let .editor(config, _): return config.id.uuidString
+        case let .help(severity): return "helpSheet_" + severity.id
+        }
+    }
+}
+
+extension DeviceAlarms {
+    struct RootView: BaseView {
+        let resolver: Resolver
+        @StateObject var state = StateModel()
+        @StateObject private var store = DeviceAlertsStore.shared
+
+        @State private var sheet: DeviceAlarmSheet?
+        @State private var pendingNewSeverity: DeviceAlertSeverity?
+
+        @State private var shouldDisplayHint: Bool = false
+        @State var hintDetent = PresentationDetent.large
+        @State var selectedVerboseHint: AnyView?
+
+        @Environment(\.colorScheme) var colorScheme
+        @Environment(AppState.self) var appState
+
+        var body: some View {
+            List {
+                ForEach(DeviceAlertSeverity.allCases) { severity in
+                    Section {
+                        VStack(alignment: .leading, spacing: 5) {
+                            HStack {
+                                Image(systemName: severityIcon(for: severity))
+                                    .foregroundStyle(severityTint(for: severity))
+                                Text(severity.displayName)
+                                Spacer()
+                            }.font(.headline)
+
+                            HStack(alignment: .center) {
+                                Text(severity.blurb)
+                                    .font(.footnote)
+                                    .foregroundColor(.secondary)
+                                    .lineLimit(nil)
+                                    .fixedSize(horizontal: false, vertical: true)
+                                Spacer()
+                                Button(action: {
+                                    sheet = .help(severity)
+                                }) {
+                                    HStack {
+                                        Image(systemName: "questionmark.circle")
+                                    }
+                                }
+                                .buttonStyle(BorderlessButtonStyle())
+                            }.padding(.vertical, 5)
+                        }
+
+                        ForEach(store.configs(in: severity)) { config in
+                            row(for: config)
+                                .opacity(config.isEnabled ? 1 : 0.5)
+                        }
+                    }.listRowBackground(Color.chart)
+                }
+
+                Section {
+                    Text("Day & Night Windows")
+                        .foregroundStyle(Color.accentColor)
+                        .navigationLink(to: .alarmWindows, from: self)
+                }.listRowBackground(Color.chart)
+            }
+            .scrollContentBackground(.hidden)
+            .background(appState.trioBackgroundColor(for: colorScheme))
+            .navigationTitle("Device Alarms")
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .primaryAction) {
+                    Button { sheet = .picker } label: { Image(systemName: "plus") }
+                }
+            }
+            .sheet(item: $sheet, onDismiss: handleSheetDismiss) { which in
+                switch which {
+                case .picker:
+                    AddDeviceAlarmSheet { severity in
+                        pendingNewSeverity = severity
+                        sheet = nil
+                    }
+                case let .editor(config, isNew):
+                    DeviceAlarmEditorView(
+                        store: store,
+                        initial: config,
+                        isNew: isNew,
+                        onDone: { sheet = nil },
+                        onCancel: { sheet = nil }
+                    )
+                case let .help(severity):
+                    SettingInputHintView(
+                        hintDetent: $hintDetent,
+                        shouldDisplayHint: Binding(
+                            get: { sheet != nil },
+                            set: { if !$0 { sheet = nil } }
+                        ),
+                        hintLabel: String(
+                            localized: "\(severity.displayName) Device Alerts",
+                            comment: "Device Alerts help sheet label; text reads: '<severity level> Device Alerts'."
+                        ),
+                        hintText: selectedVerboseHint ?? AnyView(
+                            VStack(alignment: .leading, spacing: 10) {
+                                Text(severity.hintText)
+                            }
+                        ),
+                        sheetTitle: String(localized: "Help", comment: "Help sheet title")
+                    )
+                }
+            }
+            .onAppear(perform: configureView)
+        }
+
+        private func handleSheetDismiss() {
+            guard let severity = pendingNewSeverity else { return }
+            pendingNewSeverity = nil
+            DispatchQueue.main.async {
+                let new = DeviceAlertSeverityConfig(
+                    severity: severity,
+                    activeOption: nextAvailableOption(for: severity)
+                )
+                sheet = .editor(new, isNew: true)
+            }
+        }
+
+        // MARK: - Row
+
+        @ViewBuilder private func row(for config: DeviceAlertSeverityConfig) -> some View {
+            Button {
+                sheet = .editor(config, isNew: false)
+            } label: {
+                HStack(spacing: 12) {
+                    AlarmWindowIcon(option: config.activeOption)
+                        .font(.title3)
+                    VStack(alignment: .leading, spacing: 2) {
+                        Text(windowLabel(for: config.activeOption))
+                            .foregroundColor(.primary)
+                        soundSummary(for: config)
+                    }
+                    Spacer()
+                    Image(systemName: "chevron.right")
+                        .font(.footnote)
+                        .foregroundColor(.secondary)
+                }
+            }
+            .swipeActions(edge: .trailing, allowsFullSwipe: true) {
+                if store.canDelete(config) {
+                    Button(role: .destructive) {
+                        store.remove(config)
+                    } label: {
+                        Label("Delete", systemImage: "trash")
+                    }
+                }
+            }
+        }
+
+        private func soundSummary(for config: DeviceAlertSeverityConfig) -> some View {
+            // Show the sound and override facts independently. Previously
+            // the override badge was hidden when sound was off, but "sound
+            // off + override on" is a valid combo (silent + haptic that
+            // breaks through Focus / Sleep) and the user needs to see it.
+            HStack(spacing: 6) {
+                HStack(spacing: 4) {
+                    Image(systemName: config.playsSound ? "speaker.wave.2.fill" : "speaker.slash.fill")
+                    Text(config.playsSound ? "Sound on" : "Sound off")
+                }
+                if config.overridesSilenceAndDND {
+                    Text("·")
+                    HStack(spacing: 4) {
+                        Image(systemName: "bell.badge.fill")
+                        Text("Overrides Focus")
+                    }
+                }
+            }
+            .font(.footnote)
+            .foregroundColor(.secondary)
+        }
+
+        // MARK: - Helpers
+
+        private func windowLabel(for option: ActiveOption) -> String {
+            switch option {
+            case .always: return String(localized: "Day & Night")
+            case .day: return String(localized: "Day only")
+            case .night: return String(localized: "Night only")
+            }
+        }
+
+        private func severityIcon(for severity: DeviceAlertSeverity) -> String {
+            switch severity {
+            case .critical: return "exclamationmark.triangle.fill"
+            case .timeSensitive: return "bell.badge.fill"
+            case .normal: return "bell.fill"
+            }
+        }
+
+        private func severityTint(for severity: DeviceAlertSeverity) -> Color {
+            switch severity {
+            case .critical: return .red
+            case .timeSensitive: return .orange
+            case .normal: return .accentColor
+            }
+        }
+
+        /// Suggest an `ActiveOption` not yet used in this severity tier so the
+        /// editor opens on a meaningful new variant instead of duplicating
+        /// the existing `.always` row.
+        private func nextAvailableOption(for severity: DeviceAlertSeverity) -> ActiveOption {
+            let used = Set(store.configs(in: severity).map(\.activeOption))
+            for candidate in [ActiveOption.day, .night, .always] where !used.contains(candidate) {
+                return candidate
+            }
+            return .day
+        }
+    }
+}

+ 7 - 0
Trio/Sources/Modules/GlucoseAlerts/GlucoseAlertsDataFlow.swift

@@ -0,0 +1,7 @@
+import Combine
+
+enum GlucoseAlerts {
+    enum Config {}
+}
+
+protocol GlucoseAlertsProvider: Provider {}

+ 5 - 0
Trio/Sources/Modules/GlucoseAlerts/GlucoseAlertsProvider.swift

@@ -0,0 +1,5 @@
+import Foundation
+
+extension GlucoseAlerts {
+    final class Provider: BaseProvider, GlucoseAlertsProvider {}
+}

+ 36 - 0
Trio/Sources/Modules/GlucoseAlerts/GlucoseAlertsStateModel.swift

@@ -0,0 +1,36 @@
+import Combine
+import SwiftUI
+
+extension GlucoseAlerts {
+    final class StateModel: BaseStateModel<Provider> {
+        @Injected() var fetchGlucoseManager: FetchGlucoseManager!
+
+        @Published var units: GlucoseUnits = .mgdL
+        @Published var glucoseBadge = false
+        @Published var cgmAppInfo: CGMManagerAlertOwnership.OwningApp?
+
+        var cgmProvidesOwnAlerts: Bool { cgmAppInfo != nil }
+
+        override func subscribe() {
+            units = settingsManager.settings.units
+            refreshCGMOwnership()
+            subscribeSetting(\.glucoseBadge, on: $glucoseBadge) { glucoseBadge = $0 }
+        }
+
+        /// Re-read whether the active CGM owns its own glucose alerts. The
+        /// active manager can change while the user is in CGM settings, so
+        /// the alarms view calls this on appear.
+        func refreshCGMOwnership() {
+            cgmAppInfo = CGMManagerAlertOwnership.owningApp(
+                manager: fetchGlucoseManager?.cgmManager,
+                sourceType: fetchGlucoseManager?.cgmGlucoseSourceType ?? .none
+            )
+        }
+    }
+}
+
+extension GlucoseAlerts.StateModel: SettingsObserver {
+    func settingsDidChange(_: TrioSettings) {
+        units = settingsManager.settings.units
+    }
+}

+ 87 - 0
Trio/Sources/Modules/GlucoseAlerts/View/AddGlucoseAlertSheet.swift

@@ -0,0 +1,87 @@
+import SwiftUI
+
+struct AddGlucoseAlertSheet: View {
+    @ObservedObject var store: GlucoseAlertsStore
+    let onPick: (GlucoseAlertType) -> Void
+
+    @Environment(\.dismiss) private var dismiss
+    @Environment(\.colorScheme) private var colorScheme
+    @Environment(AppState.self) private var appState
+
+    var body: some View {
+        NavigationStack {
+            List {
+                if !availableTypes.isEmpty {
+                    Section(header: Text("Available")) {
+                        ForEach(availableTypes) { type in
+                            availableRow(for: type)
+                        }
+                    }.listRowBackground(Color.chart)
+                }
+                if !unavailableTypes.isEmpty {
+                    Section(
+                        header: Text("Unavailable"),
+                        footer: Text(
+                            "Already set for Day & Night. Change the alarm time window to add a second alarm for the same type."
+                        )
+                    ) {
+                        ForEach(unavailableTypes) { type in
+                            unavailableRow(for: type)
+                        }
+                    }.listRowBackground(Color.chart)
+                }
+            }
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
+            .navigationTitle("Add Alarm")
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .cancellationAction) {
+                    Button(String(localized: "Cancel")) { dismiss() }
+                }
+            }
+        }
+    }
+
+    private var availableTypes: [GlucoseAlertType] {
+        GlucoseAlertType.allCases.filter { !store.availableActiveOptions(forNewAlarmOfType: $0).isEmpty }
+    }
+
+    private var unavailableTypes: [GlucoseAlertType] {
+        GlucoseAlertType.allCases.filter { store.availableActiveOptions(forNewAlarmOfType: $0).isEmpty }
+    }
+
+    private func availableRow(for type: GlucoseAlertType) -> some View {
+        Button {
+            onPick(type)
+            dismiss()
+        } label: {
+            HStack(spacing: 12) {
+                VStack(alignment: .leading, spacing: 2) {
+                    Text(type.displayName)
+                        .foregroundColor(.primary)
+                    Text(type.blurb)
+                        .font(.footnote)
+                        .foregroundColor(.secondary)
+                }
+                Spacer()
+                Image(systemName: "chevron.right")
+                    .font(.footnote)
+                    .foregroundColor(.secondary)
+            }
+        }
+    }
+
+    private func unavailableRow(for type: GlucoseAlertType) -> some View {
+        HStack(spacing: 12) {
+            VStack(alignment: .leading, spacing: 2) {
+                Text(type.displayName)
+                    .foregroundColor(.primary)
+                Text(type.blurb)
+                    .font(.footnote)
+                    .foregroundColor(.secondary)
+            }
+            Spacer()
+        }
+        .opacity(0.5)
+    }
+}

+ 23 - 0
Trio/Sources/Modules/GlucoseAlerts/View/Components/AlarmActiveSection.swift

@@ -0,0 +1,23 @@
+import SwiftUI
+
+struct AlarmActiveSection: View {
+    @Binding var activeOption: ActiveOption
+    /// Subset of `ActiveOption` cases the picker is allowed to offer.
+    /// Defaults to all cases when omitted.
+    var allowed: [ActiveOption] = ActiveOption.allCases
+
+    var body: some View {
+        Section(
+            header: Text("Active During"),
+            footer: Text(
+                "Day and Night windows are configured globally on the Alarms screen."
+            )
+        ) {
+            AlarmEnumMenuPicker(
+                title: String(localized: "Active"),
+                selection: $activeOption,
+                allowed: allowed
+            )
+        }.listRowBackground(Color.chart)
+    }
+}

+ 168 - 0
Trio/Sources/Modules/GlucoseAlerts/View/Components/AlarmAudioSection.swift

@@ -0,0 +1,168 @@
+import AVFoundation
+import SwiftUI
+
+struct AlarmAudioSection: View {
+    @Binding var playsSound: Bool
+    @Binding var soundFilename: String
+
+    @State private var showTonePicker = false
+
+    var body: some View {
+        Section(header: Text("Alert Sound")) {
+            Toggle("Play Sound", isOn: $playsSound)
+
+            if playsSound {
+                Button {
+                    showTonePicker = true
+                } label: {
+                    HStack {
+                        Text("Tone")
+                        Spacer()
+                        Text(AlarmSoundCatalog.displayName(for: soundFilename))
+                            .foregroundColor(.secondary)
+                        Image(systemName: "chevron.right")
+                            .foregroundColor(.secondary)
+                            .font(.footnote)
+                    }
+                }
+                .buttonStyle(.plain)
+                .sheet(isPresented: $showTonePicker) {
+                    TonePickerSheet(selected: $soundFilename)
+                }
+            }
+        }
+        .listRowBackground(Color.chart)
+    }
+}
+
+private struct TonePickerSheet: View {
+    @Binding var selected: String
+    @Environment(\.dismiss) private var dismiss
+    @Environment(\.colorScheme) private var colorScheme
+    @Environment(AppState.self) private var appState
+    @StateObject private var previewer = SoundPreviewPlayer()
+
+    var body: some View {
+        NavigationStack {
+            List {
+                Section {
+                    ForEach(AlarmSoundCatalog.allFilenames, id: \.self) { filename in
+                        TonePickerRow(
+                            filename: filename,
+                            selected: $selected,
+                            previewer: previewer
+                        )
+                    }
+                }.listRowBackground(Color.chart)
+            }
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
+            .navigationTitle("Choose Tone")
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .confirmationAction) {
+                    Button("Done") {
+                        previewer.stop()
+                        dismiss()
+                    }
+                }
+            }
+            .onDisappear { previewer.stop() }
+        }
+    }
+}
+
+private struct TonePickerRow: View {
+    let filename: String
+    @Binding var selected: String
+    @ObservedObject var previewer: SoundPreviewPlayer
+
+    var body: some View {
+        HStack(spacing: 12) {
+            Button {
+                selected = filename
+            } label: {
+                Text(AlarmSoundCatalog.displayName(for: filename))
+                    .foregroundColor(.primary)
+                    .frame(maxWidth: .infinity, alignment: .leading)
+                    .contentShape(Rectangle())
+            }
+            .buttonStyle(.plain)
+
+            Button {
+                previewer.toggle(filename: filename)
+            } label: {
+                Image(systemName: isPlaying ? "stop.circle.fill" : "play.circle.fill")
+                    .font(.title3)
+                    .foregroundColor(.accentColor)
+            }
+            .buttonStyle(.plain)
+
+            Spacer()
+
+            Image(systemName: "checkmark")
+                .foregroundColor(.accentColor)
+                .opacity(filename == selected ? 1 : 0)
+                .frame(width: 20)
+        }
+    }
+
+    private var isPlaying: Bool { previewer.playing == filename }
+}
+
+/// Plays bundled alarm `.caf` files for in-picker auditioning. Mixable
+/// `.playback` session so it ducks other audio without taking it over.
+/// Not the critical-alert player — this one obeys the silent switch.
+@MainActor private final class SoundPreviewPlayer: ObservableObject {
+    @Published private(set) var playing: String?
+    private var player: AVAudioPlayer?
+
+    func toggle(filename: String) {
+        if playing == filename {
+            stop()
+        } else {
+            play(filename: filename)
+        }
+    }
+
+    func play(filename: String) {
+        stop()
+        let resource = (filename as NSString).deletingPathExtension
+        let ext = (filename as NSString).pathExtension.isEmpty ? "caf" : (filename as NSString).pathExtension
+        guard let url = Bundle.main.url(forResource: resource, withExtension: ext, subdirectory: "Sounds") else {
+            return
+        }
+        do {
+            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers])
+            try AVAudioSession.sharedInstance().setActive(true, options: [])
+            let p = try AVAudioPlayer(contentsOf: url)
+            p.delegate = previewDelegate
+            p.prepareToPlay()
+            guard p.play() else { return }
+            player = p
+            playing = filename
+        } catch {
+            debug(.service, "Audio preview failed for \(filename): \(error)")
+        }
+    }
+
+    func stop() {
+        player?.stop()
+        player = nil
+        playing = nil
+        try? AVAudioSession.sharedInstance().setActive(false, options: [.notifyOthersOnDeactivation])
+    }
+
+    private lazy var previewDelegate: PreviewDelegate = {
+        let delegate = PreviewDelegate()
+        delegate.onFinish = { [weak self] in self?.stop() }
+        return delegate
+    }()
+}
+
+private final class PreviewDelegate: NSObject, AVAudioPlayerDelegate {
+    var onFinish: (() -> Void)?
+
+    func audioPlayerDidFinishPlaying(_: AVAudioPlayer, successfully _: Bool) {
+        Task { @MainActor in self.onFinish?() }
+    }
+}

+ 54 - 0
Trio/Sources/Modules/GlucoseAlerts/View/Components/AlarmBGSection.swift

@@ -0,0 +1,54 @@
+import SwiftUI
+
+struct AlarmBGSection: View {
+    let header: String
+    let footer: String?
+    let title: String
+    let range: ClosedRange<Decimal>
+    let step: Decimal
+    let units: GlucoseUnits
+    @Binding var valueMgDL: Decimal
+
+    @State private var showPicker = false
+
+    var body: some View {
+        Section(
+            header: Text(header),
+            footer: footer.map { Text($0) }
+        ) {
+            VStack(spacing: 0) {
+                HStack {
+                    Text(title)
+                    Spacer()
+                    Text(valueMgDL.formatted(for: units))
+                        .foregroundColor(showPicker ? .accentColor : .primary)
+                    Text(units.rawValue)
+                        .foregroundColor(.secondary)
+                }
+                .contentShape(Rectangle())
+                .onTapGesture { showPicker.toggle() }
+
+                if showPicker {
+                    Picker(title, selection: $valueMgDL) {
+                        ForEach(pickerValues, id: \.self) { value in
+                            Text(value.formatted(for: units)).tag(value)
+                        }
+                    }
+                    .pickerStyle(.wheel)
+                    .frame(maxWidth: .infinity)
+                }
+            }
+        }.listRowBackground(Color.chart)
+    }
+
+    private var pickerValues: [Decimal] {
+        let setting = PickerSetting(
+            value: valueMgDL,
+            step: step,
+            min: range.lowerBound,
+            max: range.upperBound,
+            type: .glucose
+        )
+        return PickerSettingsProvider.shared.generatePickerValues(from: setting, units: units)
+    }
+}

+ 39 - 0
Trio/Sources/Modules/GlucoseAlerts/View/Components/AlarmEnumMenuPicker.swift

@@ -0,0 +1,39 @@
+import SwiftUI
+
+struct AlarmEnumMenuPicker<E: CaseIterable & Hashable & DayNightDisplayable>: View {
+    let title: String
+    @Binding var selection: E
+    let allowed: [E]
+
+    init(title: String, selection: Binding<E>, allowed: [E]) {
+        self.title = title
+        _selection = selection
+        self.allowed = allowed
+    }
+
+    init(title: String, selection: Binding<E>) where E.AllCases: RandomAccessCollection {
+        self.title = title
+        _selection = selection
+        allowed = Array(E.allCases)
+    }
+
+    var body: some View {
+        HStack {
+            Text(title)
+            Spacer()
+            Picker("", selection: $selection) {
+                ForEach(allowed, id: \.self) { option in
+                    Text(option.displayName).tag(option)
+                }
+            }
+            .pickerStyle(.menu)
+            .onAppear(perform: validate)
+            .onChange(of: allowed) { _, _ in validate() }
+        }
+    }
+
+    private func validate() {
+        guard !allowed.contains(selection), let first = allowed.first else { return }
+        selection = first
+    }
+}

+ 47 - 0
Trio/Sources/Modules/GlucoseAlerts/View/Components/AlarmGramsSection.swift

@@ -0,0 +1,47 @@
+import SwiftUI
+
+/// Mirror of `AlarmBGSection` but for gram-valued thresholds (carbsRequired).
+/// Single fixed unit, no mg/dL ↔︎ mmol/L conversion.
+struct AlarmGramsSection: View {
+    let header: String
+    let footer: String?
+    let title: String
+    let range: ClosedRange<Int>
+    let step: Int
+    @Binding var valueGrams: Decimal
+
+    @State private var showPicker = false
+
+    var body: some View {
+        Section(
+            header: Text(header),
+            footer: footer.map { Text($0) }
+        ) {
+            VStack(spacing: 0) {
+                HStack {
+                    Text(title)
+                    Spacer()
+                    Text("\(Int(NSDecimalNumber(decimal: valueGrams).intValue))")
+                        .foregroundColor(showPicker ? .accentColor : .primary)
+                    Text(String(localized: "g", comment: "Abbreviation for grams"))
+                        .foregroundColor(.secondary)
+                }
+                .contentShape(Rectangle())
+                .onTapGesture { showPicker.toggle() }
+
+                if showPicker {
+                    Picker(title, selection: Binding(
+                        get: { Int(NSDecimalNumber(decimal: valueGrams).intValue) },
+                        set: { valueGrams = Decimal($0) }
+                    )) {
+                        ForEach(Array(stride(from: range.lowerBound, through: range.upperBound, by: step)), id: \.self) { v in
+                            Text("\(v)").tag(v)
+                        }
+                    }
+                    .pickerStyle(.wheel)
+                    .frame(maxWidth: .infinity)
+                }
+            }
+        }.listRowBackground(Color.chart)
+    }
+}

+ 44 - 0
Trio/Sources/Modules/GlucoseAlerts/View/Components/AlarmWindowIcon.swift

@@ -0,0 +1,44 @@
+import SwiftUI
+
+/// Small SF Symbol that visually conveys an alarm's `ActiveOption` window:
+/// sun for day-only, moon-and-stars for night-only, overlapped pair for both.
+/// The frame is locked to the widest variant (the overlapped pair) so alarm
+/// titles don't shift horizontally when alarms with different windows are
+/// listed together.
+struct AlarmWindowIcon: View {
+    let option: ActiveOption
+
+    var body: some View {
+        ZStack {
+            // Hidden width anchor — always the widest variant. `.hidden()`
+            // keeps it in the layout but invisible, so the frame size scales
+            // with the caller's font (Dynamic Type-friendly) without
+            // hardcoding a width.
+            HStack(spacing: -10) {
+                Image(systemName: "sun.max.fill")
+                Image(systemName: "moon.stars.fill")
+            }
+            .hidden()
+
+            content
+        }
+    }
+
+    @ViewBuilder private var content: some View {
+        switch option {
+        case .day:
+            Image(systemName: "sun.max.fill")
+                .foregroundStyle(.orange)
+        case .night:
+            Image(systemName: "moon.stars.fill")
+                .foregroundStyle(.indigo)
+        case .always:
+            HStack(spacing: -10) {
+                Image(systemName: "sun.max.fill")
+                    .foregroundStyle(.orange)
+                Image(systemName: "moon.stars.fill")
+                    .foregroundStyle(.indigo)
+            }
+        }
+    }
+}

+ 191 - 0
Trio/Sources/Modules/GlucoseAlerts/View/GlucoseAlertEditorView.swift

@@ -0,0 +1,191 @@
+import SwiftUI
+
+struct GlucoseAlertEditorView: View {
+    @ObservedObject var store: GlucoseAlertsStore
+    let alertID: UUID
+    let isNew: Bool
+
+    /// Windows the user can still pick without overlapping another alarm of
+    /// the same type. The being-edited alarm is excluded from "taken" so its
+    /// current option stays valid.
+    private var allowedActiveOptions: [ActiveOption] {
+        let available = store.availableActiveOptions(
+            forType: working.type,
+            excludingAlertID: isNew ? nil : alertID
+        )
+        return ActiveOption.allCases.filter { available.contains($0) }
+    }
+
+    let units: GlucoseUnits
+    var onDone: () -> Void
+    var onCancel: () -> Void
+
+    @Environment(\.dismiss) private var dismiss
+    @Environment(\.colorScheme) private var colorScheme
+    @Environment(AppState.self) private var appState
+    @State private var working: GlucoseAlert
+
+    init(
+        store: GlucoseAlertsStore,
+        initial: GlucoseAlert,
+        isNew: Bool,
+        units: GlucoseUnits,
+        onDone: @escaping () -> Void,
+        onCancel: @escaping () -> Void = {}
+    ) {
+        self.store = store
+        alertID = initial.id
+        self.isNew = isNew
+        self.units = units
+        self.onDone = onDone
+        self.onCancel = onCancel
+        _working = State(initialValue: initial)
+    }
+
+    var body: some View {
+        NavigationStack {
+            Form {
+                generalSection
+
+                switch working.type {
+                case .urgentLow: urgentLowBody
+                case .low: lowBody
+                case .forecastedLow: forecastedLowBody
+                case .high: highBody
+                case .carbsRequired: carbsRequiredBody
+                }
+
+                AlarmActiveSection(
+                    activeOption: $working.activeOption,
+                    allowed: allowedActiveOptions
+                )
+                AlarmAudioSection(
+                    playsSound: $working.playsSound,
+                    soundFilename: $working.soundFilename
+                )
+
+                if !isNew, store.canDelete(working) {
+                    Section {
+                        Button(role: .destructive) {
+                            store.remove(working)
+                            dismiss()
+                        } label: {
+                            Text("Delete Alarm")
+                        }
+                    }.listRowBackground(Color.chart)
+                }
+            }
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
+            .navigationTitle(working.type.displayName)
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .confirmationAction) {
+                    Button(isNew ? String(localized: "Add") : String(localized: "Done")) {
+                        if isNew {
+                            store.add(working)
+                        } else {
+                            store.update(working)
+                        }
+                        onDone()
+                        dismiss()
+                    }
+                }
+                ToolbarItem(placement: .cancellationAction) {
+                    Button(String(localized: "Cancel")) {
+                        onCancel()
+                        dismiss()
+                    }
+                }
+            }
+        }
+    }
+
+    // MARK: - Sections
+
+    private var generalSection: some View {
+        Section(
+            header: Text("General"),
+            footer: Text(working.type.blurb)
+        ) {
+            TextField(String(localized: "Name"), text: $working.name)
+            // Urgent-low is the safety floor — the user can mute the sound
+            // from the Audio section but the alarm itself is always armed.
+            if working.type != .urgentLow {
+                Toggle(String(localized: "Enabled"), isOn: $working.isEnabled)
+            }
+            Toggle(
+                String(localized: "Override Silence & Focus Mode"),
+                isOn: $working.overridesSilenceAndDND
+            )
+        }.listRowBackground(Color.chart)
+    }
+
+    private var urgentLowBody: some View {
+        AlarmBGSection(
+            header: String(localized: "Urgent Low Threshold"),
+            footer: String(
+                localized: "Recommended to always override silence and Focus mode."
+            ),
+            title: String(localized: "Glucose"),
+            range: 54 ... 80,
+            step: 1,
+            units: units,
+            valueMgDL: $working.thresholdMgDL
+        )
+    }
+
+    private var lowBody: some View {
+        AlarmBGSection(
+            header: String(localized: "Low Threshold"),
+            footer: String(
+                localized: "Fires when glucose is at or below this value."
+            ),
+            title: String(localized: "Glucose"),
+            range: 54 ... 100,
+            step: 1,
+            units: units,
+            valueMgDL: $working.thresholdMgDL
+        )
+    }
+
+    private var forecastedLowBody: some View {
+        AlarmBGSection(
+            header: String(localized: "Low Threshold"),
+            footer: String(
+                localized: "Fires when the forecast at +20 minutes (blended across all available prediction curves) is at or below this value."
+            ),
+            title: String(localized: "Glucose"),
+            range: 54 ... 100,
+            step: 1,
+            units: units,
+            valueMgDL: $working.thresholdMgDL
+        )
+    }
+
+    private var highBody: some View {
+        AlarmBGSection(
+            header: String(localized: "High Threshold"),
+            footer: String(
+                localized: "Fires when glucose is at or above this value."
+            ),
+            title: String(localized: "Glucose"),
+            range: 100 ... 400,
+            step: 1,
+            units: units,
+            valueMgDL: $working.thresholdMgDL
+        )
+    }
+
+    private var carbsRequiredBody: some View {
+        AlarmGramsSection(
+            header: String(localized: "Carbs Required Threshold"),
+            footer: String(
+                localized: "Fires when the algorithm suggests to eat at least this many grams of carbs to avoid a low."
+            ),
+            title: String(localized: "Carbs"),
+            range: 5 ... 50,
+            step: 1,
+            valueGrams: $working.thresholdMgDL
+        )
+    }
+}

+ 345 - 0
Trio/Sources/Modules/GlucoseAlerts/View/GlucoseAlertsRootView.swift

@@ -0,0 +1,345 @@
+import SwiftUI
+import Swinject
+
+private enum AlertSheet: Identifiable {
+    case picker
+    case editor(GlucoseAlert, isNew: Bool)
+
+    var id: String {
+        switch self {
+        case .picker: return "picker"
+        case let .editor(alert, _): return alert.id.uuidString
+        }
+    }
+}
+
+extension GlucoseAlerts {
+    struct RootView: BaseView {
+        let resolver: Resolver
+        @StateObject var state = StateModel()
+        @StateObject private var store = GlucoseAlertsStore.shared
+
+        @State private var sheet: AlertSheet?
+        @State private var pendingNewType: GlucoseAlertType?
+
+        @Environment(\.colorScheme) var colorScheme
+        @Environment(AppState.self) var appState
+
+        @State private var shouldDisplayHint: Bool = false
+        @State var hintDetent = PresentationDetent.large
+        @State var selectedVerboseHint: AnyView?
+        @State var hintLabel: String?
+        @State private var decimalPlaceholder: Decimal = 0.0
+        @State private var booleanPlaceholder: Bool = false
+        @State private var displayPickerLowGlucose: Bool = false
+        @State private var displayPickerHighGlucose: Bool = false
+
+        var body: some View {
+            List {
+                if !enabledAlerts.isEmpty {
+                    Section(header: Text("Enabled")) {
+                        ForEach(enabledAlerts) { alarm in
+                            row(for: alarm)
+                        }
+                    }.listRowBackground(Color.chart)
+                }
+                if !disabledAlerts.isEmpty {
+                    Section(header: Text("Disabled")) {
+                        ForEach(disabledAlerts) { alarm in
+                            row(for: alarm).opacity(0.6)
+                        }
+                    }.listRowBackground(Color.chart)
+                }
+                if !cgmHandledAlerts.isEmpty {
+                    Section(
+                        header: Text("Handled by CGM App"),
+                        footer: cgmHandledFooter
+                    ) {
+                        ForEach(cgmHandledAlerts) { alarm in
+                            row(for: alarm).opacity(0.6)
+                        }
+                    }.listRowBackground(Color.chart)
+                }
+
+                // FIXME: make this into a nice setting with mini and verbose hint
+                Section {
+                    Text("Day & Night Windows")
+                        .foregroundStyle(Color.accentColor)
+                        .navigationLink(to: .alarmWindows, from: self)
+                }.listRowBackground(Color.chart)
+
+                Section(footer: Text(useCGMAlertsFooter)) {
+                    Toggle(isOn: Binding(
+                        // When the active CGM has no companion app to defer
+                        // to, force the visible state OFF regardless of the
+                        // stored preference — there's nothing for the toggle
+                        // to control, so showing it ON would mislead.
+                        get: {
+                            guard state.cgmProvidesOwnAlerts else { return false }
+                            return !store.configuration.forceTrioAlertsWhenCGMProvidesOwn
+                        },
+                        set: { store.configuration.forceTrioAlertsWhenCGMProvidesOwn = !$0 }
+                    )) {
+                        Text("Use CGM App Alerts")
+                    }
+                    .disabled(!state.cgmProvidesOwnAlerts)
+                }.listRowBackground(Color.chart)
+
+                SettingInputSection(
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.glucoseBadge,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    selectedVerboseHint: Binding(
+                        get: { selectedVerboseHint },
+                        set: {
+                            selectedVerboseHint = $0.map { AnyView($0) }
+                            hintLabel = String(localized: "Show Glucose App Badge")
+                        }
+                    ),
+                    units: state.units,
+                    type: .boolean,
+                    label: String(localized: "Show Glucose App Badge"),
+                    miniHint: String(localized: "Show your current glucose on Trio app icon."),
+                    verboseHint: VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: OFF").bold()
+                        Text(
+                            "This will add your current glucose on the top right of your Trio icon as a red notification badge. Changing setting takes effect on next Glucose reading."
+                        )
+                    },
+                    headerText: String(localized: "Glucose App Badge")
+                )
+            }
+            .scrollContentBackground(.hidden)
+            .background(appState.trioBackgroundColor(for: colorScheme))
+            .navigationTitle("Glucose Alarms")
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .primaryAction) {
+                    Button { sheet = .picker } label: { Image(systemName: "plus") }
+                }
+            }
+            .sheet(item: $sheet, onDismiss: handleSheetDismiss) { which in
+                switch which {
+                case .picker:
+                    AddGlucoseAlertSheet(store: store) { type in
+                        pendingNewType = type
+                        sheet = nil
+                    }
+                case let .editor(alarm, isNew):
+                    GlucoseAlertEditorView(
+                        store: store,
+                        initial: alarm,
+                        isNew: isNew,
+                        units: state.units,
+                        onDone: { sheet = nil },
+                        onCancel: { sheet = nil }
+                    )
+                }
+            }
+            .sheet(isPresented: $shouldDisplayHint) {
+                SettingInputHintView(
+                    hintDetent: $hintDetent,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    hintLabel: hintLabel ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
+                    sheetTitle: String(localized: "Help", comment: "Help sheet title")
+                )
+            }
+            .onAppear {
+                configureView()
+                state.refreshCGMOwnership()
+            }
+        }
+
+        private func handleSheetDismiss() {
+            guard let type = pendingNewType else { return }
+            pendingNewType = nil
+            DispatchQueue.main.async {
+                var seed = GlucoseAlert(type: type)
+                // Default new alarm to the first available window so it
+                // doesn't overlap with whatever is already configured.
+                let available = store.availableActiveOptions(forNewAlarmOfType: type)
+                if let first = ActiveOption.allCases.first(where: available.contains) {
+                    seed.activeOption = first
+                }
+                sheet = .editor(seed, isNew: true)
+            }
+        }
+
+        // MARK: - Sorted lists
+
+        /// Mirror of `GlucoseAlertCoordinator.shouldRespect(alarm:)`'s
+        /// CGM-ownership branch: when "Use CGM App Alerts" is ON and the
+        /// active CGM provides its own glucose alerts, the coordinator
+        /// silences reading-driven types. The view surfaces this by moving
+        /// those alarms into a dedicated section.
+        private var isCGMSuppressionActive: Bool {
+            !store.configuration.forceTrioAlertsWhenCGMProvidesOwn && state.cgmProvidesOwnAlerts
+        }
+
+        private var cgmHandledAlerts: [GlucoseAlert] {
+            guard isCGMSuppressionActive else { return [] }
+            return store.alerts
+                .filter { $0.isEnabled && $0.type.isReadingDriven }
+                .sorted { lhs, rhs in
+                    lhs.type.priority < rhs.type.priority
+                }
+        }
+
+        /// Footer for the "Use CGM App Alerts" toggle. Names the eligible
+        /// CGMs when one of them is active and the user can decide; when
+        /// the active CGM has no companion app the toggle is disabled and
+        /// the footer says Trio is handling alarms.
+        private var useCGMAlertsFooter: String {
+            if state.cgmProvidesOwnAlerts {
+                return String(
+                    localized:
+                    "Your CGM app handles alerts (Dexcom G6 / One, G7 / One+, or xDrip4iOS). Turn off to let Trio alert you."
+                )
+            }
+            return String(
+                localized:
+                "Your CGM has no companion app, so Trio handles alarms."
+            )
+        }
+
+        /// Footer for the "Handled by CGM App" section. Names the specific
+        /// companion app, and renders its name as a deep link when a URL
+        /// scheme is known for that app (see CGMManagerAlertOwnership).
+        @ViewBuilder private var cgmHandledFooter: some View {
+            if let info = state.cgmAppInfo {
+                Text(handledFooterMarkdown(for: info))
+            } else {
+                Text(
+                    "These alarms are silenced because the CGM app handles them. To have Trio notify you instead, turn off \"Use CGM App Alerts\" below."
+                )
+            }
+        }
+
+        private func handledFooterMarkdown(for info: CGMManagerAlertOwnership.OwningApp) -> AttributedString {
+            let body = String(
+                format: String(
+                    localized:
+                    "These alarms are silenced because the %@ app handles CGM alerts. To have Trio notify you instead, turn off \"Use CGM App Alerts\" below."
+                ),
+                "{{NAME}}"
+            )
+            var result = AttributedString(body)
+            if let range = result.range(of: "{{NAME}}") {
+                var name = AttributedString(info.name)
+                if let url = info.deepLink {
+                    name.link = url
+                }
+                result.replaceSubrange(range, with: name)
+            }
+            return result
+        }
+
+        private var enabledAlerts: [GlucoseAlert] {
+            let handled = Set(cgmHandledAlerts.map(\.id))
+            return store.alerts
+                .filter(\.isEnabled)
+                .filter { !handled.contains($0.id) }
+                .sorted { lhs, rhs in
+                    lhs.type.priority < rhs.type.priority
+                }
+        }
+
+        private var disabledAlerts: [GlucoseAlert] {
+            store.alerts
+                .filter { !$0.isEnabled }
+                .sorted { lhs, rhs in
+                    lhs.type.priority < rhs.type.priority
+                }
+        }
+
+        // MARK: - Row
+
+        @ViewBuilder private func row(for alarm: GlucoseAlert) -> some View {
+            Button {
+                sheet = .editor(alarm, isNew: false)
+            } label: {
+                HStack(spacing: 12) {
+                    AlarmWindowIcon(option: alarm.activeOption)
+                        .font(.title3)
+
+                    VStack(alignment: .leading, spacing: 2) {
+                        Text(alarm.name)
+                            .foregroundColor(.primary)
+                        Text(summary(for: alarm))
+                            .font(.footnote)
+                            .foregroundColor(.secondary)
+                        soundSummary(for: alarm)
+                    }
+
+                    Spacer()
+
+                    Image(systemName: "chevron.right")
+                        .font(.footnote)
+                        .foregroundColor(.secondary)
+                }
+            }
+            .swipeActions(edge: .trailing, allowsFullSwipe: true) {
+                if store.canDelete(alarm) {
+                    Button(role: .destructive) {
+                        store.remove(alarm)
+                    } label: {
+                        Label("Delete", systemImage: "trash")
+                    }
+                }
+            }
+        }
+
+        private func summary(for alarm: GlucoseAlert) -> String {
+            let comparator: String = {
+                switch alarm.type {
+                case .high: return String(localized: "above")
+                case .carbsRequired: return String(localized: "at least")
+                default: return String(localized: "below")
+                }
+            }()
+            let threshold: String = {
+                // `thresholdMgDL` stores grams for carbsRequired — no
+                // mg/dL ↔ mmol/L conversion and a fixed "g" unit label.
+                if alarm.type == .carbsRequired {
+                    return "\(alarm.thresholdMgDL) \(String(localized: "g", comment: "gram of carbs"))"
+                }
+                return "\(alarm.thresholdMgDL.formatted(for: state.units)) \(state.units.rawValue)"
+            }()
+            let window = AlarmEnumDescription.description(for: alarm.activeOption)
+            return "\(comparator.localizedCapitalized) \(threshold) • \(window)"
+        }
+
+        private func soundSummary(for alarm: GlucoseAlert) -> some View {
+            // Show the sound and override facts independently. Previously
+            // the override badge was hidden when sound was off, but "sound
+            // off + override on" is a valid combo (silent + haptic that
+            // breaks through Focus / Sleep) and the user needs to see it.
+            HStack(spacing: 6) {
+                HStack(spacing: 4) {
+                    Image(systemName: alarm.playsSound ? "speaker.wave.2.fill" : "speaker.slash.fill")
+                    Text(alarm.playsSound ? "Sound on" : "Sound off")
+                }
+                if alarm.overridesSilenceAndDND {
+                    Text("·")
+                    HStack(spacing: 4) {
+                        Image(systemName: "bell.badge.fill")
+                        Text("Overrides Focus")
+                    }
+                }
+            }
+            .font(.footnote)
+            .foregroundColor(.secondary)
+        }
+    }
+}
+
+private enum AlarmEnumDescription {
+    static func description(for option: ActiveOption) -> String {
+        switch option {
+        case .always: return String(localized: "Day & Night")
+        case .day: return String(localized: "Day only")
+        case .night: return String(localized: "Night only")
+        }
+    }
+}

+ 0 - 5
Trio/Sources/Modules/GlucoseNotificationSettings/GlucoseNotificationSettingsDataFlow.swift

@@ -1,5 +0,0 @@
-enum GlucoseNotificationSettings {
-    enum Config {}
-}
-
-protocol GlucoseNotificationSettingsProvider {}

+ 0 - 3
Trio/Sources/Modules/GlucoseNotificationSettings/GlucoseNotificationSettingsProvider.swift

@@ -1,3 +0,0 @@
-extension GlucoseNotificationSettings {
-    final class Provider: BaseProvider, GlucoseNotificationSettingsProvider {}
-}

+ 0 - 53
Trio/Sources/Modules/GlucoseNotificationSettings/GlucoseNotificationSettingsStateModel.swift

@@ -1,53 +0,0 @@
-import SwiftUI
-
-extension GlucoseNotificationSettings {
-    final class StateModel: BaseStateModel<Provider> {
-        @Published var glucoseBadge = false
-        @Published var glucoseNotificationsOption: GlucoseNotificationsOption = .onlyAlarmLimits
-        @Published var addSourceInfoToGlucoseNotifications = false
-        @Published var lowGlucose: Decimal = 0
-        @Published var highGlucose: Decimal = 0
-
-        @Published var notificationsPump = true
-        @Published var notificationsCgm = true
-        @Published var notificationsCarb = true
-        @Published var notificationsAlgorithm = true
-
-        var units: GlucoseUnits = .mgdL
-
-        override func subscribe() {
-            let units = settingsManager.settings.units
-            self.units = units
-
-            subscribeSetting(\.notificationsPump, on: $notificationsPump) { notificationsPump = $0 }
-            subscribeSetting(\.notificationsCgm, on: $notificationsCgm) { notificationsCgm = $0 }
-            subscribeSetting(\.notificationsCarb, on: $notificationsCarb) { notificationsCarb = $0 }
-            subscribeSetting(\.notificationsAlgorithm, on: $notificationsAlgorithm) { notificationsAlgorithm = $0 }
-
-            subscribeSetting(\.glucoseBadge, on: $glucoseBadge) { glucoseBadge = $0 }
-            subscribeSetting(\.glucoseNotificationsOption, on: $glucoseNotificationsOption) { glucoseNotificationsOption = $0 }
-            subscribeSetting(\.addSourceInfoToGlucoseNotifications, on: $addSourceInfoToGlucoseNotifications) {
-                addSourceInfoToGlucoseNotifications = $0 }
-
-            subscribeSetting(\.lowGlucose, on: $lowGlucose, initial: {
-                lowGlucose = $0
-            }, map: {
-                let clampedValue = max(min($0, 400), 40)
-                return clampedValue
-            })
-
-            subscribeSetting(\.highGlucose, on: $highGlucose, initial: {
-                highGlucose = $0
-            }, map: {
-                let clampedValue = max(min($0, 400), 40)
-                return clampedValue
-            })
-        }
-    }
-}
-
-extension GlucoseNotificationSettings.StateModel: SettingsObserver {
-    func settingsDidChange(_: TrioSettings) {
-        units = settingsManager.settings.units
-    }
-}

+ 0 - 389
Trio/Sources/Modules/GlucoseNotificationSettings/View/GlucoseNotificationSettingsRootView.swift

@@ -1,389 +0,0 @@
-import ActivityKit
-import Combine
-import SwiftUI
-import Swinject
-
-extension GlucoseNotificationSettings {
-    struct RootView: BaseView {
-        let resolver: Resolver
-        @StateObject var state = StateModel()
-
-        @State private var shouldDisplayHint: Bool = false
-        @State var hintDetent = PresentationDetent.large
-        @State var selectedVerboseHint: AnyView?
-        @State var hintLabel: String?
-        @State private var decimalPlaceholder: Decimal = 0.0
-        @State private var booleanPlaceholder: Bool = false
-        @State private var displayPickerLowGlucose: Bool = false
-        @State private var displayPickerHighGlucose: Bool = false
-
-        private var glucoseFormatter: NumberFormatter {
-            let formatter = NumberFormatter()
-            formatter.numberStyle = .decimal
-            formatter.maximumFractionDigits = 0
-            if state.units == .mmolL {
-                formatter.maximumFractionDigits = 1
-            }
-            formatter.roundingMode = .halfUp
-            return formatter
-        }
-
-        private var carbsFormatter: NumberFormatter {
-            let formatter = NumberFormatter()
-            formatter.numberStyle = .decimal
-            formatter.maximumFractionDigits = 0
-            return formatter
-        }
-
-        @Environment(\.colorScheme) var colorScheme
-        @Environment(AppState.self) var appState
-
-        var body: some View {
-            List {
-                SettingInputSection(
-                    decimalValue: $decimalPlaceholder,
-                    booleanValue: $state.notificationsPump,
-                    shouldDisplayHint: $shouldDisplayHint,
-                    selectedVerboseHint: Binding(
-                        get: { selectedVerboseHint },
-                        set: {
-                            selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = String(localized: "Always Notify Pump")
-                        }
-                    ),
-                    units: state.units,
-                    type: .boolean,
-                    label: String(localized: "Always Notify Pump"),
-                    miniHint: String(localized: "Always Notify Pump Warnings."),
-                    verboseHint:
-                    VStack(alignment: .leading, spacing: 10) {
-                        Text("Default: ON").bold()
-                        Text(
-                            "With iOS Trio Notifications enabled, you can let Trio display most Pump Notifications in iOS Notification Center as a Banner, List and on the Lock Screen. It allows you to refer to Trio Information at a glance and troubleshoot any informational issue. Set iOS Notifications Banner Style to Persistent to display banners in the app until dismissed."
-                        )
-                        Text("If iOS Trio Notifications is disabled, Trio will display these messages in-app as a banner only.")
-                        Text("An example of a Pump Warning is 'Pod Expiration Reminder'")
-                    },
-                    headerText: String(localized: "Trio Information Notifications")
-                )
-                SettingInputSection(
-                    decimalValue: $decimalPlaceholder,
-                    booleanValue: $state.notificationsCgm,
-                    shouldDisplayHint: $shouldDisplayHint,
-                    selectedVerboseHint: Binding(
-                        get: { selectedVerboseHint },
-                        set: {
-                            selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = String(localized: "Always Notify CGM")
-                        }
-                    ),
-                    units: state.units,
-                    type: .boolean,
-                    label: String(localized: "Always Notify CGM"),
-                    miniHint: String(localized: "Always Notify CGM Warnings."),
-                    verboseHint:
-                    VStack(alignment: .leading, spacing: 10) {
-                        Text("Default: ON").bold()
-                        Text(
-                            "With iOS Trio Notifications enabled, you can let Trio display most CGM Notifications in iOS Notification Center as a Banner, List and on the Lock Screen. It allows you to refer to Trio Information at a glance and troubleshoot any informational issue. Set iOS Notifications Banner Style to Persistent to display banners in the app until dismissed."
-                        )
-                        Text("If iOS Trio Notifications is disabled, Trio will display these messages in-app as a banner only.")
-                        Text("An example of a CGM Warning is 'Unable to open the app'")
-                    }
-                )
-                SettingInputSection(
-                    decimalValue: $decimalPlaceholder,
-                    booleanValue: $state.notificationsCarb,
-                    shouldDisplayHint: $shouldDisplayHint,
-                    selectedVerboseHint: Binding(
-                        get: { selectedVerboseHint },
-                        set: {
-                            selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = String(localized: "Always Notify Carb")
-                        }
-                    ),
-                    units: state.units,
-                    type: .boolean,
-                    label: String(localized: "Always Notify Carb"),
-                    miniHint: String(localized: "Always Notify Carb Warnings."),
-                    verboseHint:
-                    VStack(alignment: .leading, spacing: 10) {
-                        Text("Default: ON").bold()
-                        Text(
-                            "With iOS Trio Notifications enabled, you can let Trio display most Carb Notifications in iOS Notification Center as a Banner, List and on the Lock Screen. It allows you to refer to Trio Information at a glance and troubleshoot any informational issue. Set iOS Notifications Banner Style to Persistent to display banners in the app until dismissed."
-                        )
-                        Text("If iOS Trio Notifications is disabled, Trio will display these messages in-app as a banner only.")
-                        Text("An example of a Carb Warning is 'Carbs required: 30 g'")
-                    }
-                )
-                SettingInputSection(
-                    decimalValue: $decimalPlaceholder,
-                    booleanValue: $state.notificationsAlgorithm,
-                    shouldDisplayHint: $shouldDisplayHint,
-                    selectedVerboseHint: Binding(
-                        get: { selectedVerboseHint },
-                        set: {
-                            selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = String(localized: "Always Notify Algorithm")
-                        }
-                    ),
-                    units: state.units,
-                    type: .boolean,
-                    label: String(localized: "Always Notify Algorithm"),
-                    miniHint: String(localized: "Always Notify Algorithm Warnings."),
-                    verboseHint:
-                    VStack(alignment: .leading, spacing: 10) {
-                        Text("Default: ON").bold()
-                        Text(
-                            "With iOS Trio Notifications enabled, you can let Trio display most Algorithm Notifications in iOS Notification Center as a Banner, List and on the Lock Screen. It allows you to refer to Trio Information at a glance and troubleshoot any informational issue. Set iOS Notifications Banner Style to Persistent to display banners in the app until dismissed."
-                        )
-                        Text("If iOS Trio Notifications is disabled, Trio will display these messages in-app as a banner only.")
-                        Text(
-                            "An example of an Algorithm Warning is 'Error: Invalid glucose: Not enough glucose data'"
-                        )
-                    }
-                )
-
-                SettingInputSection(
-                    decimalValue: $decimalPlaceholder,
-                    booleanValue: $state.glucoseBadge,
-                    shouldDisplayHint: $shouldDisplayHint,
-                    selectedVerboseHint: Binding(
-                        get: { selectedVerboseHint },
-                        set: {
-                            selectedVerboseHint = $0.map { AnyView($0) }
-                            hintLabel = String(localized: "Show Glucose App Badge")
-                        }
-                    ),
-                    units: state.units,
-                    type: .boolean,
-                    label: String(localized: "Show Glucose App Badge"),
-                    miniHint: String(localized: "Show your current glucose on Trio app icon."),
-                    verboseHint: VStack(alignment: .leading, spacing: 10) {
-                        Text("Default: OFF").bold()
-                        Text(
-                            "This will add your current glucose on the top right of your Trio icon as a red notification badge. Changing setting takes effect on next Glucose reading."
-                        )
-                    },
-                    headerText: String(localized: "Various Glucose Notifications")
-                )
-
-                Section {
-                    VStack {
-                        Picker(
-                            selection: $state.glucoseNotificationsOption,
-                            label: Text("Glucose Notifications")
-                        ) {
-                            ForEach(GlucoseNotificationsOption.allCases) { selection in
-                                Text(selection.displayName).tag(selection)
-                            }
-                        }.padding(.top)
-
-                        HStack(alignment: .center) {
-                            Text(
-                                "Choose glucose notifications option. See hint for more details."
-                            )
-                            .font(.footnote)
-                            .foregroundColor(.secondary)
-                            .lineLimit(nil)
-                            Spacer()
-                            Button(
-                                action: {
-                                    hintLabel = String(localized: "Glucose Notifications")
-                                    selectedVerboseHint =
-                                        AnyView(
-                                            VStack(alignment: .leading, spacing: 10) {
-                                                Text(
-                                                    "Set the Glucose Notifications Option. Descriptions for each option found below."
-                                                )
-                                                VStack(alignment: .leading, spacing: 5) {
-                                                    Text("Disabled:").bold()
-                                                    Text("No Glucose Notifications will be triggered.")
-                                                }
-                                                VStack(alignment: .leading, spacing: 5) {
-                                                    Text("Always:").bold()
-                                                    Text(
-                                                        "A notification will be triggered every time your glucose is updated in Trio."
-                                                    )
-                                                }
-                                                VStack(alignment: .leading, spacing: 5) {
-                                                    Text("Only Alarm Limits:").bold()
-                                                    Text(
-                                                        "A notification will be triggered only when glucose levels are below the LOW limit or above the HIGH limit, as specified in Glucose Alarm Limits below."
-                                                    )
-                                                }
-                                            }
-                                        )
-                                    shouldDisplayHint.toggle()
-                                },
-                                label: {
-                                    HStack {
-                                        Image(systemName: "questionmark.circle")
-                                    }
-                                }
-                            ).buttonStyle(BorderlessButtonStyle())
-                        }.padding(.top)
-                    }.padding(.bottom)
-                }.listRowBackground(Color.chart)
-
-                if state.glucoseNotificationsOption != GlucoseNotificationsOption.disabled {
-                    self.lowAndHighGlucoseAlertSection
-                    SettingInputSection(
-                        decimalValue: $decimalPlaceholder,
-                        booleanValue: $state.addSourceInfoToGlucoseNotifications,
-                        shouldDisplayHint: $shouldDisplayHint,
-                        selectedVerboseHint: Binding(
-                            get: { selectedVerboseHint },
-                            set: {
-                                selectedVerboseHint = $0.map { AnyView($0) }
-                                hintLabel = String(localized: "Add Glucose Source to Alarm")
-                            }
-                        ),
-                        units: state.units,
-                        type: .boolean,
-                        label: String(localized: "Add Glucose Source to Alarm"),
-                        miniHint: String(localized: "Source of the glucose reading will be added to the notification."),
-                        verboseHint: VStack(alignment: .leading, spacing: 10) {
-                            Text("Default: OFF").bold()
-                            Text("The source of the glucose reading will be added to the notification.")
-                        }
-                    )
-                }
-            }
-            .listSectionSpacing(sectionSpacing)
-            .sheet(isPresented: $shouldDisplayHint) {
-                SettingInputHintView(
-                    hintDetent: $hintDetent,
-                    shouldDisplayHint: $shouldDisplayHint,
-                    hintLabel: hintLabel ?? "",
-                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
-                    sheetTitle: String(localized: "Help", comment: "Help sheet title")
-                )
-            }
-            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
-            .onAppear(perform: configureView)
-            .navigationBarTitle("Trio Notifications")
-            .navigationBarTitleDisplayMode(.automatic)
-            .settingsHighlightScroll()
-        }
-
-        var lowAndHighGlucoseAlertSection: some View {
-            Section {
-                VStack {
-                    VStack {
-                        HStack {
-                            Text("Low Glucose Alarm Limit")
-
-                            Spacer()
-
-                            Group {
-                                Text(
-                                    state.units == .mgdL ? state.lowGlucose.description : state.lowGlucose.formattedAsMmolL
-                                )
-                                .foregroundColor(!displayPickerLowGlucose ? .primary : .accentColor)
-
-                                Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
-                            }
-                        }
-                        .onTapGesture {
-                            displayPickerLowGlucose.toggle()
-                        }
-                    }
-                    .padding(.top)
-
-                    if displayPickerLowGlucose {
-                        let setting = PickerSettingsProvider.shared.settings.lowGlucose
-
-                        Picker(selection: $state.lowGlucose, label: Text("")) {
-                            ForEach(
-                                PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
-                                id: \.self
-                            ) { value in
-                                let displayValue = state.units == .mgdL ? value.description : value.formattedAsMmolL
-                                Text(displayValue).tag(value)
-                            }
-                        }
-                        .pickerStyle(WheelPickerStyle())
-                        .frame(maxWidth: .infinity)
-                    }
-
-                    VStack {
-                        HStack {
-                            Text("High Glucose Alarm Limit")
-
-                            Spacer()
-
-                            Group {
-                                Text(
-                                    state.units == .mgdL ? state.highGlucose.description : state.highGlucose.formattedAsMmolL
-                                )
-                                .foregroundColor(!displayPickerHighGlucose ? .primary : .accentColor)
-
-                                Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
-                            }
-                        }
-                        .onTapGesture {
-                            displayPickerHighGlucose.toggle()
-                        }
-                    }
-                    .padding(.top)
-
-                    if displayPickerHighGlucose {
-                        let setting = PickerSettingsProvider.shared.settings.highGlucose
-                        Picker(selection: $state.highGlucose, label: Text("")) {
-                            ForEach(
-                                PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
-                                id: \.self
-                            ) { value in
-                                let displayValue = state.units == .mgdL ? value.description : value.formattedAsMmolL
-                                Text(displayValue).tag(value)
-                            }
-                        }
-                        .pickerStyle(WheelPickerStyle())
-                        .frame(maxWidth: .infinity)
-                    }
-
-                    HStack(alignment: .center) {
-                        Text(
-                            "Sets the lower and upper limit for glucose alarms."
-                        )
-                        .lineLimit(nil)
-                        .font(.footnote)
-                        .foregroundColor(.secondary)
-                        Spacer()
-                        Button(
-                            action: {
-                                hintLabel = String(localized: "Low and High Glucose Alarm Limits")
-                                selectedVerboseHint =
-                                    AnyView(VStack(alignment: .leading, spacing: 10) {
-                                        let low: Decimal = 70
-                                        let high: Decimal = 180
-                                        let labelLow = (state.units == .mgdL ? low.description : low.formattedAsMmolL) + " " +
-                                            state.units.rawValue
-                                        let labelHigh = (state.units == .mgdL ? high.description : high.formattedAsMmolL) + " " +
-                                            state.units.rawValue
-                                        Text("Low Default: " + labelLow).bold()
-                                        Text("High Default: " + labelHigh).bold()
-                                        VStack(alignment: .leading, spacing: 10) {
-                                            Text(
-                                                "These two settings determine the range outside of which you will be notified via push notifications."
-                                            )
-                                            Text(
-                                                "If your CGM readings are below the Low value or above the High value, you will receive a glucose alarm."
-                                            )
-                                        }
-                                    })
-                                shouldDisplayHint.toggle()
-                            },
-                            label: {
-                                HStack {
-                                    Image(systemName: "questionmark.circle")
-                                }
-                            }
-                        ).buttonStyle(BorderlessButtonStyle())
-                    }.padding(.top)
-                }.padding(.bottom)
-            }.listRowBackground(Color.chart)
-        }
-    }
-}

+ 3 - 0
Trio/Sources/Modules/Home/HomeProvider.swift

@@ -13,6 +13,9 @@ extension Home {
         }
 
         func heartbeatNow() {
+            // User-initiated force-loop: bypass dwell suppression so transient
+            // errors surface immediately rather than waiting for count/dwell.
+            apsManager.markNextLoopUserInitiated()
             apsManager.heartbeat(date: Date())
         }
 

+ 98 - 1
Trio/Sources/Modules/Home/HomeStateModel.swift

@@ -25,6 +25,7 @@ extension Home {
         @ObservationIgnored @Injected() var overrideStorage: OverrideStorage!
         @ObservationIgnored @Injected() var bluetoothManager: BluetoothStateManager!
         @ObservationIgnored @Injected() var iobService: IOBService!
+        @ObservationIgnored @Injected() var unlockmanager: UnlockManager!
 
         var cgmStateModel: CGMSettings.StateModel {
             CGMSettings.StateModel.shared
@@ -121,6 +122,8 @@ extension Home {
         var shouldRunDeleteOnSettingsChange = true
 
         var showCarbsRequiredBadge: Bool = true
+        var enableQuickBolus: Bool = false
+        var quickBolusHistory: [Decimal] = []
         private(set) var setupPumpType: PumpConfig.PumpType = .minimed
         var minForecast: [Int] = []
         var maxForecast: [Int] = []
@@ -429,7 +432,7 @@ extension Home {
                 .map { [weak self] error in
                     self?.errorDate = error == nil ? nil : Date()
                     if let error = error {
-                        info(.default, String(describing: error), notificationText: error.localizedDescription)
+                        debug(.default, "APSManager lastError: \(String(describing: error))")
                     }
                     return error?.localizedDescription
                 }
@@ -487,6 +490,7 @@ extension Home {
             bolusDisplayThreshold = settingsManager.settings.bolusDisplayThreshold
             thresholdLines = settingsManager.settings.rulerMarks
             showCarbsRequiredBadge = settingsManager.settings.showCarbsRequiredBadge
+            enableQuickBolus = settingsManager.settings.enableQuickBolus
             forecastDisplayType = settingsManager.settings.forecastDisplayType
             isExerciseModeActive = settingsManager.preferences.exerciseMode
             highTTraisesSens = settingsManager.preferences.highTemptargetRaisesSensitivity
@@ -543,6 +547,98 @@ extension Home {
             }
         }
 
+        func loadQuickBolusSuggestions() async {
+            guard enableQuickBolus else { return }
+            let cutoff = Calendar.current.date(byAdding: .day, value: -90, to: Date()) ?? Date()
+            let predicate = NSPredicate(
+                format: "isSMB == false AND isExternal == false AND pumpEvent.timestamp >= %@",
+                cutoff as NSDate
+            )
+            do {
+                let results: Any = try await CoreDataStack.shared.fetchEntitiesAsync(
+                    ofType: BolusStored.self,
+                    onContext: pumpHistoryFetchContext,
+                    predicate: predicate,
+                    key: "pumpEvent.timestamp",
+                    ascending: false,
+                    batchSize: 100
+                )
+
+                let suggestions: [Decimal] = await pumpHistoryFetchContext.perform {
+                    guard let boluses = results as? [BolusStored] else { return [] }
+
+                    let now = Date()
+                    let cal = Calendar.current
+                    let nowMinute = cal.component(.hour, from: now) * 60 + cal.component(.minute, from: now)
+                    let nowDOW = cal.component(.weekday, from: now)
+                    let sigma: Double = 60.0
+                    let halfLife: Double = 10.0
+
+                    var groups: [Decimal: Double] = [:]
+                    for bolus in boluses {
+                        guard let nsAmount = bolus.amount, nsAmount.doubleValue > 0,
+                              let timestamp = bolus.pumpEvent?.timestamp else { continue }
+
+                        var roundedKey = Decimal()
+                        var tempAmount = nsAmount as Decimal
+                        NSDecimalRound(&roundedKey, &tempAmount, 2, .plain)
+
+                        let entryMinute = cal.component(.hour, from: timestamp) * 60 + cal.component(.minute, from: timestamp)
+                        let entryDOW = cal.component(.weekday, from: timestamp)
+
+                        let diff = abs(entryMinute - nowMinute)
+                        let circularDiff = Double(min(diff, 1440 - diff))
+                        let t = exp(-(circularDiff * circularDiff) / (2.0 * sigma * sigma))
+
+                        let d: Double
+                        if entryDOW == nowDOW {
+                            d = 1.0
+                        } else {
+                            let nowWeekend = nowDOW == 1 || nowDOW == 7
+                            let entryWeekend = entryDOW == 1 || entryDOW == 7
+                            d = nowWeekend == entryWeekend ? 0.7 : 0.15
+                        }
+
+                        let daysAgo = now.timeIntervalSince(timestamp) / 86400.0
+                        let r = pow(0.5, daysAgo / halfLife)
+
+                        groups[roundedKey, default: 0] += t * d * r
+                    }
+
+                    return groups
+                        .filter { $0.value >= 0.1 }
+                        .sorted { $0.value > $1.value }
+                        .prefix(5)
+                        .map(\.key)
+                }
+
+                await MainActor.run {
+                    quickBolusHistory = suggestions
+                }
+            } catch {
+                debug(.default, "\(DebuggingIdentifiers.failed) failed to fetch quick bolus history: \(error)")
+            }
+        }
+
+        func enactQuickBolus(amount: Decimal) async -> Bool {
+            guard amount > 0 else { return false }
+            let delivery = min(
+                Double(truncating: amount as NSDecimalNumber),
+                pumpInitialSettings.maxBolusUnits
+            )
+            do {
+                let authenticated = try await unlockmanager.unlock()
+                if authenticated {
+                    await apsManager.enactBolus(amount: delivery, isSMB: false, callback: nil)
+                    return true
+                }
+                return false
+            } catch {
+                debug(.bolusState, "Quick bolus authentication error: \(error)")
+                return false
+            }
+        }
+
         func addPump(_ type: PumpConfig.PumpType) {
             setupPumpType = type
             shouldDisplayPumpSetupSheet = true
@@ -813,6 +909,7 @@ extension Home.StateModel:
         thresholdLines = settingsManager.settings.rulerMarks
         bolusDisplayThreshold = settingsManager.settings.bolusDisplayThreshold
         showCarbsRequiredBadge = settingsManager.settings.showCarbsRequiredBadge
+        enableQuickBolus = settingsManager.settings.enableQuickBolus
         forecastDisplayType = settingsManager.settings.forecastDisplayType
         cgmAvailable = (fetchGlucoseManager.cgmGlucoseSourceType != CGMType.none)
         displayPumpStatusHighlightMessage()

+ 31 - 26
Trio/Sources/Modules/Home/View/Chart/ChartElements/GlucoseChartView.swift

@@ -15,42 +15,47 @@ struct GlucoseChartView: ChartContent {
         drawGlucoseChart()
     }
 
+    /// Dynamic point color for a CGM reading. Manual readings render red and skip this.
+    private func pointColor(for item: GlucoseStored) -> Color {
+        // TODO: workaround for now: set low value to 55, to have dynamic color shades between 55 and user-set low (approx. 70); same for high glucose
+        let hardCodedLow = Decimal(55)
+        let hardCodedHigh = Decimal(220)
+        let isDynamicColorScheme = glucoseColorScheme == .dynamicColor
+
+        return Trio.getDynamicGlucoseColor(
+            glucoseValue: Decimal(item.glucose),
+            highGlucoseColorValue: isDynamicColorScheme ? hardCodedHigh : highGlucose,
+            lowGlucoseColorValue: isDynamicColorScheme ? hardCodedLow : lowGlucose,
+            targetGlucose: currentGlucoseTarget,
+            glucoseColorScheme: glucoseColorScheme
+        )
+    }
+
     private func drawGlucoseChart() -> some ChartContent {
         ForEach(glucoseData) { item in
             let glucoseToDisplay = units == .mgdL ? Decimal(item.glucose) : Decimal(item.glucose).asMmolL
 
-            // TODO: workaround for now: set low value to 55, to have dynamic color shades between 55 and user-set low (approx. 70); same for high glucose
-            let hardCodedLow = Decimal(55)
-            let hardCodedHigh = Decimal(220)
-            let isDynamicColorScheme = glucoseColorScheme == .dynamicColor
-
-            let pointMarkColor: Color = Trio.getDynamicGlucoseColor(
-                glucoseValue: Decimal(item.glucose),
-                highGlucoseColorValue: isDynamicColorScheme ? hardCodedHigh : highGlucose,
-                lowGlucoseColorValue: isDynamicColorScheme ? hardCodedLow : lowGlucose,
-                targetGlucose: currentGlucoseTarget,
-                glucoseColorScheme: glucoseColorScheme
-            )
-
-            PointMark(
-                x: .value("Time", item.date ?? Date(), unit: .second),
-                y: .value("Value", glucoseToDisplay)
-            )
-            .foregroundStyle(pointMarkColor)
-            .symbolSize(20)
-            .symbol {
-                if item.isManual {
+            if item.isManual {
+                PointMark(
+                    x: .value("Time", item.date ?? Date(), unit: .second),
+                    y: .value("Value", glucoseToDisplay)
+                )
+                .symbolSize(20)
+                .symbol {
                     Image(systemName: "drop.fill")
                         .font(.caption2)
                         .symbolRenderingMode(.monochrome)
                         .bold()
                         .foregroundStyle(.red)
-                } else {
-                    Image(systemName: "circle.fill")
-                        .font(.system(size: 5))
-                        .bold()
-                        .foregroundStyle(pointMarkColor)
                 }
+            } else {
+                PointMark(
+                    x: .value("Time", item.date ?? Date(), unit: .second),
+                    y: .value("Value", glucoseToDisplay)
+                )
+                .foregroundStyle(pointColor(for: item))
+                .symbolSize(20)
+                .symbol(.circle)
             }
 
             if isSmoothingEnabled, let smoothedGlucose = item.smoothedGlucose, smoothedGlucose != 0 {

+ 1 - 1
Trio/Sources/Modules/Home/View/Header/LoopStatusView.swift

@@ -289,7 +289,7 @@ struct LoopStatusView: View {
         }
 
         // FIXME: remove this before feat/dev-oref-swift is merged to dev
-        if state.settingsManager.settings.useSwiftOref {
+        if !state.settingsManager.settings.useJavascriptOref {
             tags.append("Swift Oref")
         }
 

+ 46 - 11
Trio/Sources/Modules/Home/View/HomeRootView.swift

@@ -32,8 +32,11 @@ extension Home {
         @State var isMenuPresented = false
         @State var showTreatments = false
         @State var selectedTab: Int = 0
+        @State var showQuickBolusPicker = false
+        @State var showQuickBolusNoHistory = false
         @State var showPumpSelection: Bool = false
         @State var showCGMSelection: Bool = false
+        @State var showSnoozeSheet: Bool = false
         @State var notificationsDisabled = false
         @State var timeButtons: [TimePicker] = [
             TimePicker(active: false, hours: 4),
@@ -149,7 +152,7 @@ extension Home {
             .onLongPressGesture {
                 let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
                 impactHeavy.impactOccurred()
-                state.showModal(for: .snooze)
+                showSnoozeSheet = true
             }
         }
 
@@ -1001,6 +1004,9 @@ extension Home {
             .sheet(isPresented: $state.isLegendPresented) {
                 ChartLegendView(state: state)
             }
+            .sheet(isPresented: $showSnoozeSheet) {
+                SnoozeAlertsSheetView(resolver: resolver, isPresented: $showSnoozeSheet)
+            }
             // PUMP RELATED
             .confirmationDialog("Pump Model", isPresented: $showPumpSelection) {
                 Button("Medtronic") { state.addPump(.minimed) }
@@ -1115,17 +1121,28 @@ extension Home {
                 }
                 .tint(Color.tabBar)
 
-                Button(
-                    action: {
-                        state.showModal(for: .treatmentView) },
-                    label: {
-                        Image(systemName: "plus.circle.fill")
-                            .font(.system(size: 40))
-                            .foregroundStyle(Color.tabBar)
-                            .padding(.vertical, 2)
-                            .padding(.horizontal, 24)
+                Image(systemName: "plus.circle.fill")
+                    .font(.system(size: 40))
+                    .foregroundStyle(Color.tabBar)
+                    .padding(.vertical, 2)
+                    .padding(.horizontal, 24)
+                    .contentShape(Rectangle())
+                    .onTapGesture {
+                        state.showModal(for: .treatmentView)
+                    }
+                    .onLongPressGesture(minimumDuration: 0.5) {
+                        guard state.enableQuickBolus else { return }
+                        let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
+                        impactHeavy.impactOccurred()
+                        Task {
+                            await state.loadQuickBolusSuggestions()
+                            if state.quickBolusHistory.isEmpty {
+                                showQuickBolusNoHistory = true
+                            } else {
+                                showQuickBolusPicker = true
+                            }
+                        }
                     }
-                )
             }.ignoresSafeArea(.keyboard, edges: .bottom).blur(radius: state.waitForSuggestion ? 8 : 0)
                 .onChange(of: selectedTab) {
                     if !settingsPath.isEmpty {
@@ -1142,6 +1159,24 @@ extension Home {
                     CustomProgressView(text: String(localized: "Updating IOB...", comment: "Progress text when updating IOB"))
                 }
             }
+            .sheet(isPresented: $showQuickBolusPicker) {
+                QuickPickBolusesView(
+                    suggestions: state.quickBolusHistory,
+                    onEnact: { amount in await state.enactQuickBolus(amount: amount) },
+                    isPresented: $showQuickBolusPicker
+                )
+            }
+            .alert(
+                String(localized: "No bolus history yet", comment: "Alert title when no quick-pick boluses history exists"),
+                isPresented: $showQuickBolusNoHistory
+            ) {
+                Button(String(localized: "OK"), role: .cancel) {}
+            } message: {
+                Text(String(
+                    localized: "Quick-Pick Boluses learns from your manual boluses over time. Once you've delivered a few boluses, it will suggest amounts based on what you typically enact at this time of day.",
+                    comment: "Alert body explaining that quick-pick boluses history is empty"
+                ))
+            }
         }
     }
 }

+ 35 - 0
Trio/Sources/Modules/Home/View/QuickPickBoluses/QuickPickBolusesInfoView.swift

@@ -0,0 +1,35 @@
+import SwiftUI
+
+struct QuickPickBolusesInfoView: View {
+    @Binding var isPresented: Bool
+
+    var body: some View {
+        NavigationStack {
+            ScrollView {
+                Text(String(
+                    localized: "Quick-Pick Boluses looks at your manual boluses from the past 90 days and suggests the amounts you most commonly enact at this time of day.\n\nIt gives more weight to boluses from similar times of day, and treats weekdays and weekends separately. Older entries gradually count less.\n\nTap a suggestion to select it, then slide to confirm. Your normal Face ID or Touch ID approval always applies.",
+                    comment: "Info sheet body explaining how quick-pick boluses scoring works"
+                ))
+                    .padding()
+            }
+            .navigationTitle(String(
+                localized: "About Quick-Pick Boluses",
+                comment: "Info sheet title for quick-pick boluses feature"
+            ))
+            .navigationBarTitleDisplayMode(.inline)
+
+            Button {
+                isPresented = false
+            } label: {
+                Text("Got it!", comment: "Dismiss button for Quick-Pick Boluses info sheet")
+                    .bold()
+                    .frame(maxWidth: .infinity, minHeight: 30, alignment: .center)
+            }
+            .buttonStyle(.bordered)
+            .padding([.horizontal, .bottom])
+            .padding(.top, 4)
+        }
+        .presentationDetents([.medium])
+        .presentationDragIndicator(.visible)
+    }
+}

+ 149 - 0
Trio/Sources/Modules/Home/View/QuickPickBoluses/QuickPickBolusesView.swift

@@ -0,0 +1,149 @@
+import SwiftUI
+
+struct QuickPickBolusesView: View {
+    let suggestions: [Decimal]
+    let onEnact: (Decimal) async -> Bool
+    @Binding var isPresented: Bool
+
+    @State private var selectedAmount: Decimal?
+    @State private var showInfo = false
+    @State private var isEnacting = false
+    @State private var showAuthFailedAlert = false
+
+    var body: some View {
+        let titleText = String(localized: "Quick-Pick Boluses", comment: "Title of the quick-pick boluses sheet")
+        NavigationStack {
+            HStack(alignment: .top) {
+                VStack(alignment: .leading) {
+                    pillRow
+                        .padding(.top)
+
+                    Text(
+                        "Your most-used bolus amounts at similar times on similar days. Tap one to pick it.",
+                        comment: "Subtitle of the quick-pick boluses pill row"
+                    )
+                    .font(.subheadline)
+                    .foregroundStyle(.secondary)
+                    .multilineTextAlignment(.leading)
+                    .padding()
+
+                    Spacer()
+                }
+            }.padding(.horizontal)
+                .frame(maxWidth: .infinity)
+                .safeAreaInset(edge: .bottom) {
+                    VStack {
+                        SlideToConfirmView(
+                            label: String(
+                                localized: "Slide to Enact Bolus",
+                                comment: "Slide to confirm label for quick-pick boluses"
+                            ),
+                            isEnabled: selectedAmount != nil && !isEnacting
+                        ) {
+                            guard let amount = selectedAmount, !isEnacting else { return }
+                            isEnacting = true
+                            Task {
+                                let success = await onEnact(amount)
+                                await MainActor.run {
+                                    if success {
+                                        isPresented = false
+                                    } else {
+                                        isEnacting = false
+                                        showAuthFailedAlert = true
+                                    }
+                                }
+                            }
+                        }.padding(.horizontal)
+                    }
+                    .padding(.horizontal)
+                }
+                .navigationTitle(titleText)
+                .navigationBarTitleDisplayMode(.inline)
+                .toolbar {
+                    ToolbarItem(placement: .principal) {
+                        Text(titleText)
+                            .font(.title3.bold())
+                    }
+                    ToolbarItem(placement: .topBarTrailing) {
+                        Button {
+                            showInfo = true
+                        } label: {
+                            Image(systemName: "questionmark.circle")
+                        }
+                    }
+                }
+                .sheet(isPresented: $showInfo) {
+                    QuickPickBolusesInfoView(isPresented: $showInfo)
+                }
+                .alert(
+                    String(
+                        localized: "Could not authenticate",
+                        comment: "Alert title when biometric auth fails for quick-pick boluses"
+                    ),
+                    isPresented: $showAuthFailedAlert
+                ) {
+                    Button(String(localized: "OK"), role: .cancel) {}
+                } message: {
+                    Text(String(
+                        localized: "Face ID or Touch ID did not succeed. The bolus was not enacted.",
+                        comment: "Alert body when biometric auth fails for quick-pick boluses"
+                    ))
+                }
+        }
+        .presentationDetents([.height(320)])
+        .presentationDragIndicator(.visible)
+        .interactiveDismissDisabled(isEnacting)
+    }
+
+    private var displayedSuggestions: [Decimal] {
+        var seen = Set<Decimal>()
+        return suggestions.prefix(3).filter { seen.insert($0).inserted }.sorted()
+    }
+
+    private var pillRow: some View {
+        let pills = displayedSuggestions
+        let isCompact = pills.count < 2
+        return HStack(spacing: 16) {
+            if isCompact { Spacer() }
+
+            ForEach(pills, id: \.self) { amount in
+                bolusAmountPill(amount)
+                    .frame(maxWidth: isCompact ? 160 : .infinity)
+            }
+
+            if isCompact { Spacer() }
+        }
+        .padding(.horizontal)
+    }
+
+    private func bolusAmountPill(_ amount: Decimal) -> some View {
+        let isSelected = selectedAmount == amount
+        let formatted = Formatter.bolusFormatter.string(from: amount as NSDecimalNumber) ?? amount.description
+
+        return Button {
+            selectedAmount = amount
+        } label: {
+            HStack(alignment: .firstTextBaseline, spacing: 2) {
+                Text(formatted)
+                    .font(.title2.bold())
+                Text("U")
+                    .font(.title2)
+                    .foregroundStyle(isSelected ? .white.opacity(0.85) : .secondary)
+            }
+            .padding(.vertical, 12)
+            .padding(.horizontal, 24)
+            .frame(maxWidth: .infinity, minHeight: 44)
+            .background(isSelected ? Color.accentColor : Color(.secondarySystemFill))
+            .foregroundStyle(isSelected ? .white : .primary)
+            .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
+            .overlay {
+                if isSelected {
+                    RoundedRectangle(cornerRadius: 18, style: .continuous)
+                        .strokeBorder(Color.accentColor, lineWidth: 2.5)
+                }
+            }
+        }
+        .buttonStyle(.plain)
+        .animation(.easeInOut(duration: 0.15), value: isSelected)
+    }
+}

+ 58 - 0
Trio/Sources/Modules/Home/View/QuickPickBoluses/SlideToConfirmView.swift

@@ -0,0 +1,58 @@
+import SwiftUI
+
+struct SlideToConfirmView: View {
+    let label: String
+    let isEnabled: Bool
+    let action: () -> Void
+
+    @State private var dragOffset: CGFloat = 0
+
+    private let thumbSize: CGFloat = 52
+    private let trackHeight: CGFloat = 56
+    private let completionThreshold: CGFloat = 0.85
+
+    var body: some View {
+        GeometryReader { geo in
+            let maxDrag = geo.size.width - thumbSize - 8
+            let progress = maxDrag > 0 ? dragOffset / maxDrag : 0
+
+            ZStack(alignment: .leading) {
+                Capsule()
+                    .fill(isEnabled ? Color.accentColor.opacity(0.25) : Color.secondary.opacity(0.2))
+
+                Text(label)
+                    .font(.headline)
+                    .foregroundStyle(isEnabled ? .white.opacity(1 - progress) : .secondary)
+                    .frame(maxWidth: .infinity)
+
+                RoundedRectangle(cornerRadius: thumbSize / 2)
+                    .fill(isEnabled ? Color.accentColor : Color.secondary.opacity(0.4))
+                    .frame(width: thumbSize, height: thumbSize)
+                    .overlay {
+                        Image(systemName: "chevron.right.2")
+                            .foregroundStyle(.white)
+                            .font(.system(size: 18, weight: .semibold))
+                    }
+                    .offset(x: 4 + dragOffset)
+                    .gesture(
+                        isEnabled ? DragGesture()
+                            .onChanged { value in
+                                dragOffset = min(max(0, value.translation.width), maxDrag)
+                            }
+                            .onEnded { _ in
+                                guard maxDrag > 0 else { return }
+                                if dragOffset >= maxDrag * completionThreshold {
+                                    UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
+                                    withAnimation(.spring()) { dragOffset = 0 }
+                                    action()
+                                } else {
+                                    withAnimation(.spring()) { dragOffset = 0 }
+                                }
+                            } : nil
+                    )
+            }
+            .frame(height: trackHeight)
+        }
+        .frame(height: trackHeight)
+    }
+}

+ 2 - 2
Trio/Sources/Modules/LiveActivitySettings/View/LiveActivityWidgetConfiguration.swift

@@ -220,9 +220,9 @@ struct LiveActivityWidgetConfiguration: BaseView {
                 let pointMarkColor = Trio.getDynamicGlucoseColor(
                     glucoseValue: Decimal(data.glucoseLevel),
                     highGlucoseColorValue: !(state.settingsManager.settings.glucoseColorScheme == .dynamicColor) ? state
-                        .settingsManager.settings.highGlucose : Decimal(220),
+                        .settingsManager.settings.high : Decimal(220),
                     lowGlucoseColorValue: !(state.settingsManager.settings.glucoseColorScheme == .dynamicColor) ? state
-                        .settingsManager.settings.lowGlucose : Decimal(55),
+                        .settingsManager.settings.low : Decimal(55),
                     targetGlucose: Decimal(100),
                     glucoseColorScheme: state.settingsManager.settings.glucoseColorScheme
                 )

+ 0 - 307
Trio/Sources/Modules/Main/MainStateModel.swift

@@ -1,6 +1,5 @@
 import Combine
 import LoopKitUI
-import SwiftMessages
 import SwiftUI
 import Swinject
 
@@ -12,237 +11,6 @@ extension Main {
         @Published var modal: Modal?
         @Published var secondaryModal: SecondaryModalWrapper?
 
-        @Persisted(key: "UserNotificationsManager.snoozeUntilDate") private var snoozeUntilDate: Date = .distantPast
-        private var timers: [TimeInterval: Timer] = [:]
-
-        private func showTriggeredView(
-            message: MessageContent,
-            interval _: TimeInterval,
-            config: SwiftMessages.Config,
-            view: MessageView
-        ) {
-            view.customConfigureTheme(
-                colorSchemePreference: colorSchemePreference
-            )
-            setupAction(message: message, view: view)
-
-            SwiftMessages.show(config: config, view: view)
-        }
-
-        // Add or replace timer for a specific TimeInterval
-        private func addOrReplaceTriggerTimer(message: MessageContent, config: SwiftMessages.Config, view: MessageView) {
-            let trigger = message.trigger as! UNTimeIntervalNotificationTrigger
-            guard trigger.timeInterval > 0 else { return }
-            let interval = trigger.timeInterval
-
-            SwiftMessages.hide(id: view.id)
-
-            // If a timer already exists for this interval, invalidate it
-            if let existingTimer = timers[interval] {
-                existingTimer.invalidate()
-            }
-
-            // Create a new timer with the provided interval
-            let newTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] _ in
-                self?.showTriggeredView(message: message, interval: interval, config: config, view: view)
-                self?.timers[interval] = nil
-            }
-
-            timers[interval] = newTimer
-        }
-
-        // Cancel all timers (optional cleanup method)
-        private func cancelAllTimers() {
-            timers.values.forEach { $0.invalidate() }
-            timers.removeAll()
-        }
-
-        private func setupPumpConfig() {
-            // display the pump configuration immediatly
-            if let pump = provider.deviceManager.pumpManager,
-               let bluetooth = provider.bluetoothProvider
-            {
-                let view = PumpConfig.PumpSettingsView(
-                    pumpManager: pump,
-                    bluetoothManager: bluetooth,
-                    completionDelegate: self
-                ).asAny()
-                router.mainSecondaryModalView.send(view)
-            }
-        }
-
-        private func setupButton(message _: MessageContent, view: MessageView) {
-            view.button?.setImage(UIImage(), for: .normal)
-            view.iconLabel = nil
-            let buttonImage = UIImage(systemName: "chevron.right")?.withTintColor(.white)
-            view.button?.setImage(buttonImage, for: .normal)
-            view.button?.backgroundColor = view.backgroundView.backgroundColor
-            view.button?.tintColor = view.iconImageView?.tintColor
-        }
-
-        private func setupAction(message: MessageContent, view: MessageView) {
-            switch message.action {
-            case .snooze:
-                setupButton(message: message, view: view)
-                view.buttonTapHandler = { _ in
-                    // Popup Snooze view when user taps on Glucose Notification
-                    SwiftMessages.hide()
-                    self.router.mainModalScreen.send(.snooze)
-                }
-            case .pumpConfig:
-                setupButton(message: message, view: view)
-                view.buttonTapHandler = { _ in
-                    SwiftMessages.hide()
-                    self.setupPumpConfig()
-                }
-            default: // break
-                view.button?.setImage(UIImage(), for: .normal)
-                view.buttonTapHandler = { _ in
-                    SwiftMessages.hide()
-                }
-            }
-        }
-
-        private func isApnPumpConfigAction(_ message: MessageContent) -> Bool {
-            if message.type != .error, message.action == .pumpConfig {
-                setupPumpConfig()
-                return true
-            }
-            return false
-        }
-
-        private func showAlertMessage(_ message: MessageContent) {
-            if message.useAPN, !alertPermissionsChecker.notificationsDisabled
-            {
-                showAPN(message)
-            } else {
-                showSwiftMessage(message)
-            }
-        }
-
-        private func showAPN(_ message: MessageContent) {
-            DispatchQueue.main.async {
-                self.broadcaster.notify(alertMessageNotificationObserver.self, on: .main) {
-                    $0.alertMessageNotification(message)
-                }
-            }
-        }
-
-        // Read the color scheme preference from UserDefaults; defaults to system default setting
-        @AppStorage("colorSchemePreference") private var colorSchemePreference: ColorSchemeOption = .systemDefault
-
-        private func showSwiftMessage(_ message: MessageContent) {
-            if snoozeUntilDate > Date(), message.action == .snooze {
-                return
-            }
-
-            var config = SwiftMessages.defaultConfig
-            let view = MessageView.viewFromNib(layout: .cardView)
-
-            view.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
-            config.prefersStatusBarHidden = true
-
-            // Set id so that multiple notifications are not queued while waiting for user response; only the latest will be shown
-            if message.subtype == .glucose || message.subtype == .carb {
-                view.id = message.type.rawValue + message.subtype.rawValue
-            }
-
-            let titleContent: String
-
-            let iconName = UIApplication.shared.alternateIconName ?? "trioBlack"
-            let iconImage = UIImage(named: iconName) ?? UIImage()
-
-            view.configureContent(
-                title: "title",
-                body: message.content,
-                iconImage: nil,
-                iconText: nil,
-                buttonImage: nil,
-                buttonTitle: nil,
-                buttonTapHandler: nil
-            )
-
-            view.configureIcon(withSize: CGSize(width: 40, height: 40), contentMode: .scaleAspectFit)
-            view.iconImageView!.image = iconImage
-            view.iconImageView?.layer.cornerRadius = 10
-
-            view.customConfigureTheme(
-                colorSchemePreference: colorSchemePreference
-            )
-
-            view.iconImageView?.image = iconImage
-
-            switch message.type {
-            case .info,
-                 .other:
-                config.duration = .seconds(seconds: 5)
-                titleContent = message.title != "" ? message.title : String(localized: "Info", comment: "Info title")
-            case .warning:
-                config.duration = .forever
-                titleContent = message.title != "" ? message
-                    .title : String(localized: "Warning", comment: "Warning title")
-            case .error:
-                config.duration = .forever
-                titleContent = message.title != "" ? message
-                    .title : String(localized: "Error", comment: "Error title")
-            }
-
-            view.titleLabel?.text = titleContent
-            config.dimMode = .gray(interactive: true)
-
-            setupAction(message: message, view: view)
-            if message.trigger != nil {
-                addOrReplaceTriggerTimer(message: message, config: config, view: view)
-            }
-
-            guard message.type == .error || message.action != .pumpConfig, message.trigger == nil, !view.isHidden else { return }
-
-            SwiftMessages.show(config: config, view: view)
-        }
-
-        /*
-          Reclassification is needed for Medtronic pumps for 'Pump error:' RileyLink related messages.
-          For details, see https://discord.com/channels/1020905149037813862/1338245444186279946/1343469793013141525.
-          These messages are repeatedly displayed causing users to simply ignore them.
-          Reclassification of these Info type messages is based on APSManager.APSError enum values.
-          We reclassify APSError.pumpError and APSError.invalidPumpState as MessageType.info and MessageSubtype.pump.
-          This allows the user to disable these messages using using the 'Trio Notification' -> 'Always Notify Pump' setting.
-          MessageType.error messagges are always displayed to the user and the user cannot disable them.
-          Other APSManager.APSError remain as MessageType.info which allows users to disable them
-          using the 'Trio Notification' -> 'Always Notify Algorithm' setting.
-         */
-
-        func reclassifyInfoNotification(_ message: inout MessageContent) {
-            if message.title == "" {
-                switch message.type {
-                case .info:
-                    if message.content.range(of: "error", options: .caseInsensitive) != nil || message.content
-                        .range(of: String(localized: "Error"), options: .caseInsensitive) != nil
-                    {
-                        message.title = String(localized: "Error", comment: "Error title")
-                    } else {
-                        message.title = String(localized: "Info", comment: "Info title")
-                    }
-                    if APSError.pumpWarningMatches(message: message.content) {
-                        message.subtype = .pump
-                        let lastLoopMinutes = Int((Date().timeIntervalSince(apsManager.lastLoopDate) - 30) / 60) + 1
-                        if lastLoopMinutes > 10 {
-                            message.type = .error
-                        }
-                    } else if APSError.pumpErrorMatches(message: message.content) {
-                        message.subtype = .pump
-                        message.type = .error
-                    }
-                case .warning:
-                    message.title = String(localized: "Warning", comment: "Warning title")
-                case .error:
-                    message.title = String(localized: "Error", comment: "Error title")
-                case .other:
-                    message.title = String(localized: "Info", comment: "Info title")
-                }
-            }
-        }
-
         override func subscribe() {
             router.mainModalScreen
                 .map { $0?.modal(resolver: self.resolver!) }
@@ -258,17 +26,6 @@ extension Main {
                 }
                 .store(in: &lifetime)
 
-            router.alertMessage
-                .receive(on: DispatchQueue.main)
-                .sink { message in
-                    guard !self.isApnPumpConfigAction(message) else { return }
-                    var reclassifyMessage = message
-                    self.reclassifyInfoNotification(&reclassifyMessage)
-                    guard self.router.allowNotify(reclassifyMessage, self.settingsManager.settings) else { return }
-                    self.showAlertMessage(reclassifyMessage)
-                }
-                .store(in: &lifetime)
-
             router.mainSecondaryModalView
                 .receive(on: DispatchQueue.main)
                 .sink { view in
@@ -286,67 +43,3 @@ extension Main {
         }
     }
 }
-
-extension MessageView {
-    func currentColorScheme() -> ColorScheme {
-        let userInterfaceStyle = UITraitCollection.current.userInterfaceStyle
-        return userInterfaceStyle == .dark ? .dark : .light
-    }
-
-    func customConfigureTheme(colorSchemePreference: ColorSchemeOption) {
-        let defaultSystemColorScheme = currentColorScheme()
-        var backgroundColor = UIColor.systemBackground
-        var foregroundColor = UIColor.white
-        let ApnBackground = UIColor(named: "ApnBackground") ?? UIColor.lightGray
-        let iOSlightTrioDark = UIColor(named: "ApnBackgroundLightDark") ?? UIColor.lightGray
-
-        switch colorSchemePreference {
-        case .systemDefault:
-            backgroundColor = ApnBackground
-            foregroundColor = UIColor.label
-        case .dark:
-            backgroundColor = defaultSystemColorScheme == .light ? iOSlightTrioDark : ApnBackground
-            foregroundColor = defaultSystemColorScheme == .light ? UIColor.black : UIColor.white
-        case .light:
-            backgroundColor = defaultSystemColorScheme == .light ? ApnBackground : UIColor.gray
-            foregroundColor = defaultSystemColorScheme == .light ? UIColor.black : UIColor.white
-        }
-
-        iconImageView?.tintColor = foregroundColor
-        backgroundView.backgroundColor = backgroundColor
-        titleLabel?.textColor = foregroundColor
-        bodyLabel?.textColor = foregroundColor
-        iconImageView?.isHidden = iconImageView?.image == nil
-
-        backgroundView.layer.cornerRadius = 25
-
-        let adjustedFont = UIFont.systemFont(ofSize: 13.0, weight: .bold)
-        let preferredTitleFont = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: adjustedFont)
-        let preferredBodyFont = UIFont.preferredFontforStyle(forTextStyle: .footnote)
-        // Set the title and body font to the dynamic type sizes
-        titleLabel?.adjustsFontForContentSizeCategory = true
-        titleLabel?.font = preferredTitleFont
-        bodyLabel?.adjustsFontForContentSizeCategory = true
-        bodyLabel?.font = preferredBodyFont
-        // Set custom colors for title and body text
-        titleLabel?.textColor = foregroundColor
-        bodyLabel?.textColor = foregroundColor
-    }
-}
-
-@available(iOS 16.0, *)
-extension Main.StateModel: CompletionDelegate {
-    func completionNotifyingDidComplete(_: CompletionNotifying) {
-        // close the window
-        router.mainSecondaryModalView.send(nil)
-    }
-}
-
-// Extension to convert SwiftUI TextStyle to UIFont
-extension UIFont {
-    static func preferredFontforStyle(forTextStyle: UIFont.TextStyle) -> UIFont {
-        let uiFontMetrics = UIFontMetrics.default
-        let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: forTextStyle)
-        return uiFontMetrics.scaledFont(for: UIFont(descriptor: descriptor, size: 0))
-    }
-}

+ 5 - 1
Trio/Sources/Modules/Main/View/MainRootView.swift

@@ -9,6 +9,10 @@ extension Main {
         @Environment(\.colorScheme) var colorScheme
         @Environment(AppState.self) var appState
 
+        private var modalScheduler: TrioModalAlertScheduler {
+            resolver.resolve(TrioAlertManager.self)!.modalScheduler
+        }
+
         var body: some View {
             router.view(for: .home)
                 .sheet(item: $state.modal) { modal in
@@ -18,7 +22,7 @@ extension Main {
                 .sheet(item: $state.secondaryModal) { wrapper in
                     wrapper.view
                 }
-
+                .trioAlerts(modalScheduler)
                 .onAppear(perform: configureView)
                 .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
         }

+ 0 - 2
Trio/Sources/Modules/Onboarding/OnboardingStateModel.swift

@@ -732,8 +732,6 @@ extension Onboarding {
             if !isFreshTrioInstall {
                 let providedSettings = settingsProvider.settings
 
-                settingsCopy.lowGlucose = settingsCopy.lowGlucose.clamp(to: providedSettings.lowGlucose)
-                settingsCopy.highGlucose = settingsCopy.highGlucose.clamp(to: providedSettings.highGlucose)
                 settingsCopy.carbsRequiredThreshold = settingsCopy.carbsRequiredThreshold
                     .clamp(to: providedSettings.carbsRequiredThreshold)
                 settingsCopy.individualAdjustmentFactor = settingsCopy.individualAdjustmentFactor

+ 13 - 1
Trio/Sources/Modules/PumpConfig/PumpConfigDataFlow.swift

@@ -5,12 +5,24 @@ import LoopKitUI
 enum PumpConfig {
     enum Config {}
 
-    enum PumpType: Equatable {
+    enum PumpType: Equatable, CaseIterable {
         case minimed
         case omni
         case dana
         case medtrum
         case simulator
+
+        /// `Alert.Identifier.managerIdentifier` emitted by this plugin's
+        /// `PumpManager`. Nil for `.simulator` — no real plugin.
+        var pluginIdentifier: String? {
+            switch self {
+            case .minimed: return "Minimed"
+            case .omni: return "Omni"
+            case .dana: return "Dana"
+            case .medtrum: return "Medtrum"
+            case .simulator: return nil
+            }
+        }
     }
 
     struct PumpInitialSettings {

+ 3 - 1
Trio/Sources/Modules/PumpConfig/PumpConfigStateModel.swift

@@ -1,3 +1,4 @@
+import Foundation
 import LoopKit
 import LoopKitUI
 import SwiftDate
@@ -11,6 +12,7 @@ extension PumpConfig {
         private(set) var initialSettings: PumpInitialSettings = .default
         @Published var hasUnacknowledgedAlert: Bool = false
         @Injected() var bluetoothManager: BluetoothStateManager!
+        @Injected() var trioAlertManager: TrioAlertManager!
 
         override func subscribe() {
             provider.pumpDisplayState
@@ -49,7 +51,7 @@ extension PumpConfig {
         }
 
         func ack() {
-            provider.deviceManager.alertHistoryStorage.broadcastAlertUpdates()
+            trioAlertManager.acknowledgeAllOutstanding()
         }
     }
 }

+ 1 - 0
Trio/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift

@@ -48,6 +48,7 @@ extension PumpConfig {
                                     Spacer()
                                     Button("Acknowledge all alerts") { state.ack() }
                                 }
+                                Spacer()
                             } else {
                                 VStack {
                                     Button {

+ 5 - 0
Trio/Sources/Modules/QuickPickBolusesConfig/QuickPickBolusesConfigDataFlow.swift

@@ -0,0 +1,5 @@
+enum QuickPickBolusesConfig {
+    enum Config {}
+}
+
+protocol QuickPickBolusesConfigProvider {}

+ 3 - 0
Trio/Sources/Modules/QuickPickBolusesConfig/QuickPickBolusesConfigProvider.swift

@@ -0,0 +1,3 @@
+extension QuickPickBolusesConfig {
+    final class Provider: BaseProvider, QuickPickBolusesConfigProvider {}
+}

+ 19 - 0
Trio/Sources/Modules/QuickPickBolusesConfig/QuickPickBolusesConfigStateModel.swift

@@ -0,0 +1,19 @@
+import SwiftUI
+
+extension QuickPickBolusesConfig {
+    final class StateModel: BaseStateModel<Provider> {
+        @Published var enableQuickBolus: Bool = false
+        @Published var units: GlucoseUnits = .mgdL
+
+        override func subscribe() {
+            subscribeSetting(\.enableQuickBolus, on: $enableQuickBolus) { enableQuickBolus = $0 }
+            units = settingsManager.settings.units
+        }
+    }
+}
+
+extension QuickPickBolusesConfig.StateModel: SettingsObserver {
+    func settingsDidChange(_ settings: TrioSettings) {
+        units = settings.units
+    }
+}

+ 64 - 0
Trio/Sources/Modules/QuickPickBolusesConfig/View/QuickPickBolusesConfigRootView.swift

@@ -0,0 +1,64 @@
+import SwiftUI
+import Swinject
+
+extension QuickPickBolusesConfig {
+    struct RootView: BaseView {
+        let resolver: Resolver
+
+        @StateObject var state = StateModel()
+
+        @State private var shouldDisplayHint: Bool = false
+        @State var hintDetent = PresentationDetent.large
+        @State var selectedVerboseHint: AnyView?
+        @State var hintLabel: String?
+        @State private var decimalPlaceholder: Decimal = 0.0
+
+        @Environment(\.colorScheme) var colorScheme
+        @Environment(AppState.self) var appState
+
+        var body: some View {
+            List {
+                SettingInputSection(
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.enableQuickBolus,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    selectedVerboseHint: Binding(
+                        get: { selectedVerboseHint },
+                        set: {
+                            selectedVerboseHint = $0.map { AnyView($0) }
+                            hintLabel = String(localized: "Enable Quick-Pick Boluses")
+                        }
+                    ),
+                    units: state.units,
+                    type: .boolean,
+                    label: String(localized: "Enable Quick-Pick Boluses"),
+                    miniHint: String(localized: "Long-press the + button on the home screen to enact a quick-pick bolus."),
+                    verboseHint: VStack(alignment: .leading, spacing: 10) {
+                        Text("Default: OFF").bold()
+                        Text(
+                            "When enabled, long-pressing the + button on the home screen opens a Quick-Pick Boluses sheet. It suggests up to three bolus amounts based on your bolus history at similar times of day, weighted by recency and day type (weekday vs. weekend)."
+                        )
+                        Text(
+                            "Slide to confirm your selected amount. Face ID or Touch ID is always required before the bolus is enacted."
+                        )
+                    }
+                )
+            }
+            .listSectionSpacing(sectionSpacing)
+            .sheet(isPresented: $shouldDisplayHint) {
+                SettingInputHintView(
+                    hintDetent: $hintDetent,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    hintLabel: hintLabel ?? "",
+                    hintText: selectedVerboseHint ?? AnyView(EmptyView()),
+                    sheetTitle: String(localized: "Help", comment: "Help sheet title")
+                )
+            }
+            .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
+            .onAppear(perform: configureView)
+            .navigationBarTitle("Quick-Pick Boluses")
+            .navigationBarTitleDisplayMode(.automatic)
+            .settingsHighlightScroll()
+        }
+    }
+}

+ 48 - 17
Trio/Sources/Modules/Settings/SettingItems.swift

@@ -223,7 +223,13 @@ enum SettingItems {
                 "Enable Super Bolus": "Enable Super Bolus Option",
                 "Super Bolus Factor": "Enable Super Bolus Option"
             ],
-            path: ["Features", "Bolus Calculator"]
+            path: ["Features", "Treatments", "Bolus Calculator"]
+        ),
+        SettingItem(
+            title: "Quick-Pick Boluses",
+            view: .quickBolusConfig,
+            searchContents: ["Enable Quick-Pick Boluses"],
+            path: ["Features", "Treatments", "Quick-Pick Boluses"]
         ),
         SettingItem(
             title: "Meal Settings",
@@ -247,7 +253,7 @@ enum SettingItems {
                 "Fat and Protein Percentage": "Enable Fat and Protein Entries",
                 "FPU": "Enable Fat and Protein Entries"
             ],
-            path: ["Features", "Meal Settings"]
+            path: ["Features", "Treatments", "Meal Settings"]
         ),
         SettingItem(
             title: "Shortcuts",
@@ -322,24 +328,49 @@ enum SettingItems {
     static let notificationItems = [
         SettingItem(title: "Manage iOS Preferences", view: .notificationSettings),
         SettingItem(
-            title: "Trio Notifications",
-            view: .glucoseNotificationSettings,
+            title: "Glucose Alarms",
+            view: .glucoseAlerts,
             searchContents: [
-                "Always Notify Pump",
-                "Always Notify CGM",
-                "Always Notify Carb",
-                "Always Notify Algorithm",
-                "Show Glucose App Badge",
-                "Glucose Notifications",
-                "Add Glucose Source to Alarm",
-                "Low Glucose Alarm Limit",
-                "High Glucose Alarm Limit"
+                "Urgent Low Glucose",
+                "Low Glucose",
+                "Forecasted Low Glucose",
+                "High Glucose",
+                "Day & Night",
+                "Alert Sound",
+                "Override Silence",
+                "Show Glucose App Badge"
             ],
-            scrollTargetLabels: [
-                "Low Glucose Alarm Limit": "Glucose Notifications",
-                "High Glucose Alarm Limit": "Glucose Notifications"
+            path: ["Notifications", "Glucose Alarms"]
+        ),
+        SettingItem(
+            title: "Device Alarms",
+            view: .deviceAlarms,
+            searchContents: [
+                "Critical",
+                "Time-Sensitive",
+                "Normal",
+                "Occlusion",
+                "Pump Fault",
+                "Hardware Fault",
+                "Reservoir",
+                "Battery",
+                "Bolus Failed",
+                "Pod",
+                "Sensor Failure",
+                "Sensor Stopped",
+                "Calibration",
+                "Signal Loss",
+                "Device Expired",
+                "Not Looping",
+                "Algorithm"
             ],
-            path: ["Notifications", "Trio Notifications"] // Glucose
+            path: ["Notifications", "Device Alarms"]
+        ),
+        SettingItem(
+            title: "Day & Night Windows",
+            view: .alarmWindows,
+            searchContents: ["Day Starts", "Night Starts", "Day & Night"],
+            path: ["Notifications", "Day & Night Windows"]
         ),
         SettingItem(
             title: "Live Activity",

+ 1 - 2
Trio/Sources/Modules/Settings/View/Subviews/FeatureSettingsView.swift

@@ -21,8 +21,7 @@ struct FeatureSettingsView: BaseView {
             Section(
                 header: Text("Trio Features"),
                 content: {
-                    Text("Bolus Calculator").navigationLink(to: .bolusCalculatorConfig, from: self)
-                    Text("Meal Settings").navigationLink(to: .mealSettings, from: self)
+                    Text("Treatments").navigationLink(to: .treatmentsSettings, from: self)
                     Text("Shortcuts").navigationLink(to: .shortcutsConfig, from: self)
                     Text("Remote Control").navigationLink(to: .remoteControlConfig, from: self)
                 }

+ 0 - 0
Trio/Sources/Modules/Settings/View/Subviews/NotificationsView.swift


Some files were not shown because too many files changed in this diff