Explorar o código

Merge branch 'dev' of github.com:nightscout/Trio into aps-manager-thread-safety-fixes

Marvin Polscheit hai 14 horas
pai
achega
0bd9d090e4
Modificáronse 55 ficheiros con 1029 adicións e 167 borrados
  1. 1 1
      .github/CODEOWNERS
  2. 3 0
      .gitmodules
  3. 3 3
      Config.xcconfig
  4. 1 1
      Gemfile
  5. 60 51
      Gemfile.lock
  6. 1 0
      OmnipodKit
  7. 7 2
      PRIVACY_POLICY.md
  8. 38 0
      Trio.xcodeproj/project.pbxproj
  9. 3 0
      Trio.xcworkspace/contents.xcworkspacedata
  10. 1 1
      Trio.xcworkspace/xcshareddata/swiftpm/Package.resolved
  11. 10 3
      Trio/Sources/APS/APSManager.swift
  12. 59 1
      Trio/Sources/APS/DeviceDataManager.swift
  13. 22 4
      Trio/Sources/Application/AppDelegate.swift
  14. 1 0
      Trio/Sources/Assemblies/ServiceAssembly.swift
  15. 163 0
      Trio/Sources/Localizations/Main/Localizable.xcstrings
  16. 3 3
      Trio/Sources/Models/TrioSettings.swift
  17. 1 0
      Trio/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift
  18. 1 0
      Trio/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift
  19. 1 0
      Trio/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift
  20. 1 0
      Trio/Sources/Modules/CGMSettings/View/CGMRootView.swift
  21. 1 0
      Trio/Sources/Modules/CalendarEventSettings/View/CalendarEventSettingsRootView.swift
  22. 2 1
      Trio/Sources/Modules/DynamicSettings/View/DynamicSettingsRootView.swift
  23. 1 0
      Trio/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift
  24. 1 0
      Trio/Sources/Modules/GlucoseNotificationSettings/View/GlucoseNotificationSettingsRootView.swift
  25. 3 0
      Trio/Sources/Modules/Home/View/HomeRootView.swift
  26. 1 0
      Trio/Sources/Modules/LiveActivitySettings/View/LiveActivitySettingsRootView.swift
  27. 15 6
      Trio/Sources/Modules/LiveActivitySettings/View/LiveActivityWidgetConfiguration.swift
  28. 1 0
      Trio/Sources/Modules/MealSettings/View/MealSettingsRootView.swift
  29. 1 0
      Trio/Sources/Modules/NightscoutConfig/View/NightscoutFetchView.swift
  30. 1 0
      Trio/Sources/Modules/NightscoutConfig/View/NightscoutUploadView.swift
  31. 10 2
      Trio/Sources/Modules/Onboarding/View/TherapySettingEditorView.swift
  32. 1 0
      Trio/Sources/Modules/PumpConfig/PumpConfigDataFlow.swift
  33. 2 0
      Trio/Sources/Modules/PumpConfig/View/PumpConfigRootView.swift
  34. 10 0
      Trio/Sources/Modules/PumpConfig/View/PumpSetupView.swift
  35. 1 0
      Trio/Sources/Modules/RemoteControlConfig/View/RemoteControlConfig.swift
  36. 1 0
      Trio/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift
  37. 66 2
      Trio/Sources/Modules/Settings/SettingItems.swift
  38. 19 8
      Trio/Sources/Modules/Settings/View/SettingsRootView.swift
  39. 2 2
      Trio/Sources/Modules/ShortcutsConfig/ShortcutsConfigStateModel.swift
  40. 1 0
      Trio/Sources/Modules/ShortcutsConfig/View/ShortcutsConfigView.swift
  41. 1 0
      Trio/Sources/Modules/TargetBehavoir/View/TargetBehavoirRootView.swift
  42. 9 8
      Trio/Sources/Modules/UserInterfaceSettings/View/UserInterfaceSettingsRootView.swift
  43. 1 0
      Trio/Sources/Modules/WatchConfig/View/WatchConfigAppleWatchView.swift
  44. 106 0
      Trio/Sources/Services/BolusSafety/BolusSafetyValidator.swift
  45. 22 45
      Trio/Sources/Services/RemoteControl/TrioRemoteControl+Bolus.swift
  46. 1 3
      Trio/Sources/Services/RemoteControl/TrioRemoteControl.swift
  47. 71 7
      Trio/Sources/Services/Telemetry/TelemetryClient.swift
  48. 1 0
      Trio/Sources/Shortcuts/BaseIntentsRequest.swift
  49. 37 10
      Trio/Sources/Shortcuts/Bolus/BolusIntentRequest.swift
  50. 2 1
      Trio/Sources/Views/SettingInputSection.swift
  51. 73 0
      Trio/Sources/Views/SettingsSearchHighlight.swift
  52. 113 0
      TrioTests/BolusSafetyTests/BolusSafetyValidatorTests.swift
  53. 69 0
      TrioTests/SettingsSearchTests.swift
  54. 1 1
      scripts/capture-build-details.sh
  55. 2 1
      scripts/swiftformat.sh

+ 1 - 1
.github/CODEOWNERS

@@ -1 +1 @@
-*    @dnzxy @bjornoleh @MikePlante1 @AndreasStokholm @Sjoerd-Bo3 @t1dude @marv-out
+*    @dnzxy @bjornoleh @MikePlante1 @AndreasStokholm @Sjoerd-Bo3 @t1dude @marv-out @kingst @marionbarker

+ 3 - 0
.gitmodules

@@ -34,3 +34,6 @@
 [submodule "MedtrumKit"]
 [submodule "MedtrumKit"]
 	path = MedtrumKit
 	path = MedtrumKit
 	url = https://github.com/loopandlearn/MedtrumKit
 	url = https://github.com/loopandlearn/MedtrumKit
+[submodule "OmnipodKit"]
+	path = OmnipodKit
+	url = https://github.com/loopandlearn/OmnipodKit

+ 3 - 3
Config.xcconfig

@@ -18,12 +18,12 @@ BUNDLE_IDENTIFIER = org.nightscout.$(DEVELOPMENT_TEAM).trio
 TRIO_APP_GROUP_ID = group.org.nightscout.$(DEVELOPMENT_TEAM).trio.trio-app-group
 TRIO_APP_GROUP_ID = group.org.nightscout.$(DEVELOPMENT_TEAM).trio.trio-app-group
 
 
 // The developers set the version numbers, please leave them alone
 // The developers set the version numbers, please leave them alone
-APP_VERSION = 0.7.0
-APP_DEV_VERSION = 0.7.0.19
+APP_VERSION = 0.8.1
+APP_DEV_VERSION = 0.8.1.3
 APP_BUILD_NUMBER = 1
 APP_BUILD_NUMBER = 1
 COPYRIGHT_NOTICE =
 COPYRIGHT_NOTICE =
 
 
 // Optional overrides - these can be used to insert your TEAMID into the DEVELOPER_TEAM field
 // Optional overrides - these can be used to insert your TEAMID into the DEVELOPER_TEAM field
 #include? "../../ConfigOverride.xcconfig"
 #include? "../../ConfigOverride.xcconfig"
 #include? "../ConfigOverride.xcconfig"
 #include? "../ConfigOverride.xcconfig"
-#include? "ConfigOverride.xcconfig"
+#include? "ConfigOverride.xcconfig"

+ 1 - 1
Gemfile

@@ -1,5 +1,5 @@
 source "https://rubygems.org"
 source "https://rubygems.org"
 
 
-gem "fastlane", "2.233.1"
+gem "fastlane", "2.235.0"
 gem "json", ">=2.19.2"
 gem "json", ">=2.19.2"
 gem "addressable", ">=2.9.0"
 gem "addressable", ">=2.9.0"

+ 60 - 51
Gemfile.lock

@@ -8,8 +8,8 @@ GEM
     artifactory (3.0.17)
     artifactory (3.0.17)
     atomos (0.1.3)
     atomos (0.1.3)
     aws-eventstream (1.4.0)
     aws-eventstream (1.4.0)
-    aws-partitions (1.1206.0)
-    aws-sdk-core (3.241.4)
+    aws-partitions (1.1254.0)
+    aws-sdk-core (3.250.0)
       aws-eventstream (~> 1, >= 1.3.0)
       aws-eventstream (~> 1, >= 1.3.0)
       aws-partitions (~> 1, >= 1.992.0)
       aws-partitions (~> 1, >= 1.992.0)
       aws-sigv4 (~> 1.9)
       aws-sigv4 (~> 1.9)
@@ -17,19 +17,19 @@ GEM
       bigdecimal
       bigdecimal
       jmespath (~> 1, >= 1.6.1)
       jmespath (~> 1, >= 1.6.1)
       logger
       logger
-    aws-sdk-kms (1.121.0)
-      aws-sdk-core (~> 3, >= 3.241.4)
+    aws-sdk-kms (1.128.0)
+      aws-sdk-core (~> 3, >= 3.248.0)
       aws-sigv4 (~> 1.5)
       aws-sigv4 (~> 1.5)
-    aws-sdk-s3 (1.211.0)
-      aws-sdk-core (~> 3, >= 3.241.3)
+    aws-sdk-s3 (1.224.0)
+      aws-sdk-core (~> 3, >= 3.248.0)
       aws-sdk-kms (~> 1)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.5)
       aws-sigv4 (~> 1.5)
     aws-sigv4 (1.12.1)
     aws-sigv4 (1.12.1)
       aws-eventstream (~> 1, >= 1.0.2)
       aws-eventstream (~> 1, >= 1.0.2)
     babosa (1.0.4)
     babosa (1.0.4)
-    base64 (0.2.0)
+    base64 (0.3.0)
     benchmark (0.5.0)
     benchmark (0.5.0)
-    bigdecimal (4.0.1)
+    bigdecimal (4.1.2)
     claide (1.1.0)
     claide (1.1.0)
     colored (1.2)
     colored (1.2)
     colored2 (3.1.2)
     colored2 (3.1.2)
@@ -43,16 +43,17 @@ GEM
     dotenv (2.8.1)
     dotenv (2.8.1)
     emoji_regex (3.2.3)
     emoji_regex (3.2.3)
     excon (0.112.0)
     excon (0.112.0)
-    faraday (1.8.0)
+    faraday (1.10.5)
       faraday-em_http (~> 1.0)
       faraday-em_http (~> 1.0)
       faraday-em_synchrony (~> 1.0)
       faraday-em_synchrony (~> 1.0)
       faraday-excon (~> 1.1)
       faraday-excon (~> 1.1)
-      faraday-httpclient (~> 1.0.1)
+      faraday-httpclient (~> 1.0)
+      faraday-multipart (~> 1.0)
       faraday-net_http (~> 1.0)
       faraday-net_http (~> 1.0)
-      faraday-net_http_persistent (~> 1.1)
+      faraday-net_http_persistent (~> 1.0)
       faraday-patron (~> 1.0)
       faraday-patron (~> 1.0)
       faraday-rack (~> 1.0)
       faraday-rack (~> 1.0)
-      multipart-post (>= 1.2, < 3)
+      faraday-retry (~> 1.0)
       ruby2_keywords (>= 0.0.4)
       ruby2_keywords (>= 0.0.4)
     faraday-cookie_jar (0.0.8)
     faraday-cookie_jar (0.0.8)
       faraday (>= 0.8.0)
       faraday (>= 0.8.0)
@@ -61,23 +62,26 @@ GEM
     faraday-em_synchrony (1.0.1)
     faraday-em_synchrony (1.0.1)
     faraday-excon (1.1.0)
     faraday-excon (1.1.0)
     faraday-httpclient (1.0.1)
     faraday-httpclient (1.0.1)
+    faraday-multipart (1.2.0)
+      multipart-post (~> 2.0)
     faraday-net_http (1.0.2)
     faraday-net_http (1.0.2)
     faraday-net_http_persistent (1.2.0)
     faraday-net_http_persistent (1.2.0)
     faraday-patron (1.0.0)
     faraday-patron (1.0.0)
     faraday-rack (1.0.0)
     faraday-rack (1.0.0)
+    faraday-retry (1.0.4)
     faraday_middleware (1.2.1)
     faraday_middleware (1.2.1)
       faraday (~> 1.0)
       faraday (~> 1.0)
-    fastimage (2.4.0)
-    fastlane (2.233.1)
-      CFPropertyList (>= 2.3, < 4.0.0)
-      abbrev (~> 0.1.2)
+    fastimage (2.4.1)
+    fastlane (2.235.0)
+      CFPropertyList (>= 2.3, < 5.0.0)
+      abbrev (~> 0.1)
       addressable (>= 2.8, < 3.0.0)
       addressable (>= 2.8, < 3.0.0)
       artifactory (~> 3.0)
       artifactory (~> 3.0)
       aws-sdk-s3 (~> 1.197)
       aws-sdk-s3 (~> 1.197)
       babosa (>= 1.0.3, < 2.0.0)
       babosa (>= 1.0.3, < 2.0.0)
-      base64 (~> 0.2.0)
+      base64 (~> 0.2)
       benchmark (>= 0.1.0)
       benchmark (>= 0.1.0)
-      bundler (>= 1.17.3, < 5.0.0)
+      bundler (>= 2.4.0, < 5.0.0)
       colored (~> 1.2)
       colored (~> 1.2)
       commander (~> 4.6)
       commander (~> 4.6)
       csv (~> 3.3)
       csv (~> 3.3)
@@ -92,18 +96,18 @@ GEM
       gh_inspector (>= 1.1.2, < 2.0.0)
       gh_inspector (>= 1.1.2, < 2.0.0)
       google-apis-androidpublisher_v3 (~> 0.3)
       google-apis-androidpublisher_v3 (~> 0.3)
       google-apis-playcustomapp_v1 (~> 0.1)
       google-apis-playcustomapp_v1 (~> 0.1)
-      google-cloud-env (>= 1.6.0, <= 2.1.1)
+      google-cloud-env (>= 1.6.0, < 2.3.0)
       google-cloud-storage (~> 1.31)
       google-cloud-storage (~> 1.31)
       highline (~> 2.0)
       highline (~> 2.0)
       http-cookie (~> 1.0.5)
       http-cookie (~> 1.0.5)
       json (< 3.0.0)
       json (< 3.0.0)
-      jwt (>= 2.1.0, < 3)
+      jwt (>= 2.1.0, < 4)
       logger (>= 1.6, < 2.0)
       logger (>= 1.6, < 2.0)
       mini_magick (>= 4.9.4, < 5.0.0)
       mini_magick (>= 4.9.4, < 5.0.0)
       multipart-post (>= 2.0.0, < 3.0.0)
       multipart-post (>= 2.0.0, < 3.0.0)
-      mutex_m (~> 0.3.0)
+      mutex_m (~> 0.3)
       naturally (~> 2.2)
       naturally (~> 2.2)
-      nkf (~> 0.2.0)
+      nkf (~> 0.2)
       optparse (>= 0.1.1, < 1.0.0)
       optparse (>= 0.1.1, < 1.0.0)
       ostruct (>= 0.1.0)
       ostruct (>= 0.1.0)
       plist (>= 3.1.0, < 4.0.0)
       plist (>= 3.1.0, < 4.0.0)
@@ -120,39 +124,44 @@ GEM
       xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
       xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
     fastlane-sirp (1.1.0)
     fastlane-sirp (1.1.0)
     gh_inspector (1.1.3)
     gh_inspector (1.1.3)
-    google-apis-androidpublisher_v3 (0.54.0)
-      google-apis-core (>= 0.11.0, < 2.a)
-    google-apis-core (0.11.3)
+    google-apis-androidpublisher_v3 (0.101.0)
+      google-apis-core (>= 0.15.0, < 2.a)
+    google-apis-core (0.18.0)
       addressable (~> 2.5, >= 2.5.1)
       addressable (~> 2.5, >= 2.5.1)
-      googleauth (>= 0.16.2, < 2.a)
-      httpclient (>= 2.8.1, < 3.a)
+      googleauth (~> 1.9)
+      httpclient (>= 2.8.3, < 3.a)
       mini_mime (~> 1.0)
       mini_mime (~> 1.0)
+      mutex_m
       representable (~> 3.0)
       representable (~> 3.0)
       retriable (>= 2.0, < 4.a)
       retriable (>= 2.0, < 4.a)
-      rexml
-    google-apis-iamcredentials_v1 (0.17.0)
-      google-apis-core (>= 0.11.0, < 2.a)
-    google-apis-playcustomapp_v1 (0.13.0)
-      google-apis-core (>= 0.11.0, < 2.a)
-    google-apis-storage_v1 (0.31.0)
-      google-apis-core (>= 0.11.0, < 2.a)
+    google-apis-iamcredentials_v1 (0.27.0)
+      google-apis-core (>= 0.15.0, < 2.a)
+    google-apis-playcustomapp_v1 (0.17.0)
+      google-apis-core (>= 0.15.0, < 2.a)
+    google-apis-storage_v1 (0.62.0)
+      google-apis-core (>= 0.15.0, < 2.a)
     google-cloud-core (1.8.0)
     google-cloud-core (1.8.0)
       google-cloud-env (>= 1.0, < 3.a)
       google-cloud-env (>= 1.0, < 3.a)
       google-cloud-errors (~> 1.0)
       google-cloud-errors (~> 1.0)
-    google-cloud-env (1.6.0)
-      faraday (>= 0.17.3, < 3.0)
-    google-cloud-errors (1.5.0)
-    google-cloud-storage (1.47.0)
+    google-cloud-env (2.2.2)
+      base64 (~> 0.2)
+      faraday (>= 1.0, < 3.a)
+    google-cloud-errors (1.6.0)
+    google-cloud-storage (1.60.0)
       addressable (~> 2.8)
       addressable (~> 2.8)
       digest-crc (~> 0.4)
       digest-crc (~> 0.4)
-      google-apis-iamcredentials_v1 (~> 0.1)
-      google-apis-storage_v1 (~> 0.31.0)
+      google-apis-core (>= 0.18, < 2)
+      google-apis-iamcredentials_v1 (~> 0.18)
+      google-apis-storage_v1 (>= 0.42)
       google-cloud-core (~> 1.6)
       google-cloud-core (~> 1.6)
-      googleauth (>= 0.16.2, < 2.a)
+      googleauth (~> 1.9)
       mini_mime (~> 1.0)
       mini_mime (~> 1.0)
-    googleauth (1.8.1)
-      faraday (>= 0.17.3, < 3.a)
-      jwt (>= 1.4, < 3.0)
+    google-logging-utils (0.2.0)
+    googleauth (1.16.2)
+      faraday (>= 1.0, < 3.a)
+      google-cloud-env (~> 2.2)
+      google-logging-utils (~> 0.1)
+      jwt (>= 1.4, < 4.0)
       multi_json (~> 1.11)
       multi_json (~> 1.11)
       os (>= 0.9, < 2.0)
       os (>= 0.9, < 2.0)
       signet (>= 0.16, < 2.a)
       signet (>= 0.16, < 2.a)
@@ -162,13 +171,13 @@ GEM
     httpclient (2.9.0)
     httpclient (2.9.0)
       mutex_m
       mutex_m
     jmespath (1.6.2)
     jmespath (1.6.2)
-    json (2.19.4)
-    jwt (2.10.2)
+    json (2.19.7)
+    jwt (3.2.0)
       base64
       base64
     logger (1.7.0)
     logger (1.7.0)
     mini_magick (4.13.2)
     mini_magick (4.13.2)
     mini_mime (1.1.5)
     mini_mime (1.1.5)
-    multi_json (1.19.1)
+    multi_json (1.21.1)
     multipart-post (2.4.1)
     multipart-post (2.4.1)
     mutex_m (0.3.0)
     mutex_m (0.3.0)
     nanaimo (0.4.0)
     nanaimo (0.4.0)
@@ -178,13 +187,13 @@ GEM
     os (1.1.4)
     os (1.1.4)
     ostruct (0.6.3)
     ostruct (0.6.3)
     plist (3.7.2)
     plist (3.7.2)
-    public_suffix (7.0.2)
-    rake (13.3.1)
+    public_suffix (7.0.5)
+    rake (13.4.2)
     representable (3.2.0)
     representable (3.2.0)
       declarative (< 0.1.0)
       declarative (< 0.1.0)
       trailblazer-option (>= 0.1.1, < 0.2.0)
       trailblazer-option (>= 0.1.1, < 0.2.0)
       uber (< 0.2.0)
       uber (< 0.2.0)
-    retriable (3.1.2)
+    retriable (3.8.0)
     rexml (3.4.4)
     rexml (3.4.4)
     rouge (3.28.0)
     rouge (3.28.0)
     ruby2_keywords (0.0.5)
     ruby2_keywords (0.0.5)
@@ -227,7 +236,7 @@ PLATFORMS
 
 
 DEPENDENCIES
 DEPENDENCIES
   addressable (>= 2.9.0)
   addressable (>= 2.9.0)
-  fastlane (= 2.233.1)
+  fastlane (= 2.235.0)
   json (>= 2.19.2)
   json (>= 2.19.2)
 
 
 BUNDLED WITH
 BUNDLED WITH

+ 1 - 0
OmnipodKit

@@ -0,0 +1 @@
+Subproject commit adf210d20bcfc05f8119e8665d8f5904db828e03

+ 7 - 2
PRIVACY_POLICY.md

@@ -73,6 +73,11 @@ The following information is included in the telemetry payload:
   on/off, Live Activity enabled, calendar integration enabled
   on/off, Live Activity enabled, calendar integration enabled
 - A rolling 7-day count of how often the app was cold-launched
 - A rolling 7-day count of how often the app was cold-launched
 - The commit SHAs of pinned submodules (e.g. LoopKit, OmniBLE)
 - The commit SHAs of pinned submodules (e.g. LoopKit, OmniBLE)
+- The device's system locale (e.g. "en_US") — used to help Trio
+  developers understand which languages to prioritize for translation
+- The device's time zone identifier (e.g. "America/New_York") — used
+  to help Trio developers understand which regions of the world Trio
+  is being used in
 
 
 The payload sends once every 24 hours while the app is running, plus
 The payload sends once every 24 hours while the app is running, plus
 once after a new build is installed. Sending failures simply retry on
 once after a new build is installed. Sending failures simply retry on
@@ -85,7 +90,7 @@ the next launch or scheduler tick — there is no continued retry.
 - Your Nightscout URL or API token
 - Your Nightscout URL or API token
 - Your Tidepool email, password, or session token
 - Your Tidepool email, password, or session token
 - Remote-command secrets or APNS keys
 - Remote-command secrets or APNS keys
-- Time zone or location
+- GPS coordinates or any precise location data
 - App logs — log sharing remains a separate, user-initiated flow under Settings
 - App logs — log sharing remains a separate, user-initiated flow under Settings
 
 
 ### Debug Symbols (dSYMs)
 ### Debug Symbols (dSYMs)
@@ -177,4 +182,4 @@ trio.diy.diabetes@gmail.com.
 
 
 ## Last Updated
 ## Last Updated
 
 
-May 14, 2025
+May 28, 2026

+ 38 - 0
Trio.xcodeproj/project.pbxproj

@@ -260,6 +260,7 @@
 		3BD9687C2D8DDD4600899469 /* SlideButton in Frameworks */ = {isa = PBXBuildFile; productRef = 3BD9687B2D8DDD4600899469 /* SlideButton */; };
 		3BD9687C2D8DDD4600899469 /* SlideButton in Frameworks */ = {isa = PBXBuildFile; productRef = 3BD9687B2D8DDD4600899469 /* SlideButton */; };
 		3BD9687F2D8DDD8800899469 /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 3BD9687E2D8DDD8800899469 /* CryptoSwift */; };
 		3BD9687F2D8DDD8800899469 /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 3BD9687E2D8DDD8800899469 /* CryptoSwift */; };
 		3BF85FE32E427312000D7351 /* IOBService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF85FE12E427312000D7351 /* IOBService.swift */; };
 		3BF85FE32E427312000D7351 /* IOBService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF85FE12E427312000D7351 /* IOBService.swift */; };
+		B015AFE32E500000000D7351 /* BolusSafetyValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015AFE12E500000000D7351 /* BolusSafetyValidator.swift */; };
 		3E28F2AB2EB5337F00FB9EEB /* ConnectIQ in Frameworks */ = {isa = PBXBuildFile; productRef = 3E28F2AA2EB5337F00FB9EEB /* ConnectIQ */; };
 		3E28F2AB2EB5337F00FB9EEB /* ConnectIQ in Frameworks */ = {isa = PBXBuildFile; productRef = 3E28F2AA2EB5337F00FB9EEB /* ConnectIQ */; };
 		3E54EF2C2E476DA40006F54D /* MedtrumKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E54EF2B2E476DA40006F54D /* MedtrumKit.framework */; };
 		3E54EF2C2E476DA40006F54D /* MedtrumKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E54EF2B2E476DA40006F54D /* MedtrumKit.framework */; };
 		3E54EF2D2E476DA40006F54D /* MedtrumKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3E54EF2B2E476DA40006F54D /* MedtrumKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		3E54EF2D2E476DA40006F54D /* MedtrumKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3E54EF2B2E476DA40006F54D /* MedtrumKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -346,8 +347,11 @@
 		9702FF92A09C53942F20D7EA /* TargetsEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DD795BA46B193644D48138C /* TargetsEditorRootView.swift */; };
 		9702FF92A09C53942F20D7EA /* TargetsEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DD795BA46B193644D48138C /* TargetsEditorRootView.swift */; };
 		9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0A48AE3AC813A49A517846A /* NightscoutConfigStateModel.swift */; };
 		9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0A48AE3AC813A49A517846A /* NightscoutConfigStateModel.swift */; };
 		98641AF4F92123DA668AB931 /* CarbRatioEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BDC6993C1087310EDFC428 /* CarbRatioEditorRootView.swift */; };
 		98641AF4F92123DA668AB931 /* CarbRatioEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BDC6993C1087310EDFC428 /* CarbRatioEditorRootView.swift */; };
+		41740E936552456AAC0EDAC3 /* SettingsSearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3919BBB515547118D684CA2 /* SettingsSearchTests.swift */; };
 		A33352ED40476125EBAC6EE0 /* CarbRatioEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E22146D3DF4853786C78132 /* CarbRatioEditorDataFlow.swift */; };
 		A33352ED40476125EBAC6EE0 /* CarbRatioEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E22146D3DF4853786C78132 /* CarbRatioEditorDataFlow.swift */; };
 		AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF65DA88F972B56090AD6AC3 /* PumpConfigDataFlow.swift */; };
 		AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF65DA88F972B56090AD6AC3 /* PumpConfigDataFlow.swift */; };
+		B6E925132EB3932A0076D719 /* OmnipodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6E925122EB3932A0076D719 /* OmnipodKit.framework */; };
+		B6E925142EB3932A0076D719 /* OmnipodKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B6E925122EB3932A0076D719 /* OmnipodKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		B7C465E9472624D8A2BE2A6A /* (null) in Sources */ = {isa = PBXBuildFile; };
 		B7C465E9472624D8A2BE2A6A /* (null) in Sources */ = {isa = PBXBuildFile; };
 		B958F1B72BA0711600484851 /* MKRingProgressView in Frameworks */ = {isa = PBXBuildFile; productRef = B958F1B62BA0711600484851 /* MKRingProgressView */; };
 		B958F1B72BA0711600484851 /* MKRingProgressView in Frameworks */ = {isa = PBXBuildFile; productRef = B958F1B62BA0711600484851 /* MKRingProgressView */; };
 		BA00D96F7B2FF169A06FB530 /* CGMSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C018D1680307A31C9ED7120 /* CGMSettingsStateModel.swift */; };
 		BA00D96F7B2FF169A06FB530 /* CGMSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C018D1680307A31C9ED7120 /* CGMSettingsStateModel.swift */; };
@@ -414,6 +418,7 @@
 		BD8FC0592D66189700B95AED /* TestAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8FC0582D66189700B95AED /* TestAssembly.swift */; };
 		BD8FC0592D66189700B95AED /* TestAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8FC0582D66189700B95AED /* TestAssembly.swift */; };
 		BD8FC05B2D6618AF00B95AED /* DeterminationStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8FC05A2D6618AF00B95AED /* DeterminationStorageTests.swift */; };
 		BD8FC05B2D6618AF00B95AED /* DeterminationStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8FC05A2D6618AF00B95AED /* DeterminationStorageTests.swift */; };
 		BD8FC05E2D6618CE00B95AED /* BolusCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8FC05D2D6618CE00B95AED /* BolusCalculatorTests.swift */; };
 		BD8FC05E2D6618CE00B95AED /* BolusCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8FC05D2D6618CE00B95AED /* BolusCalculatorTests.swift */; };
+		B015AFE52E500000000D7351 /* BolusSafetyValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B015AFE42E500000000D7351 /* BolusSafetyValidatorTests.swift */; };
 		BD8FC0602D6619DB00B95AED /* CarbsStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8FC05F2D6619DB00B95AED /* CarbsStorageTests.swift */; };
 		BD8FC0602D6619DB00B95AED /* CarbsStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8FC05F2D6619DB00B95AED /* CarbsStorageTests.swift */; };
 		BD8FC0622D6619E600B95AED /* OverrideStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8FC0612D6619E600B95AED /* OverrideStorageTests.swift */; };
 		BD8FC0622D6619E600B95AED /* OverrideStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8FC0612D6619E600B95AED /* OverrideStorageTests.swift */; };
 		BD8FC0642D6619EF00B95AED /* TempTargetStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8FC0632D6619EF00B95AED /* TempTargetStorageTests.swift */; };
 		BD8FC0642D6619EF00B95AED /* TempTargetStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8FC0632D6619EF00B95AED /* TempTargetStorageTests.swift */; };
@@ -547,6 +552,7 @@
 		DD1745242C55526000211FAC /* SMBSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1745232C55526000211FAC /* SMBSettingsStateModel.swift */; };
 		DD1745242C55526000211FAC /* SMBSettingsStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1745232C55526000211FAC /* SMBSettingsStateModel.swift */; };
 		DD1745262C55526F00211FAC /* SMBSettingsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1745252C55526F00211FAC /* SMBSettingsRootView.swift */; };
 		DD1745262C55526F00211FAC /* SMBSettingsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1745252C55526F00211FAC /* SMBSettingsRootView.swift */; };
 		DD1745292C55642100211FAC /* SettingInputSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1745282C55642100211FAC /* SettingInputSection.swift */; };
 		DD1745292C55642100211FAC /* SettingInputSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1745282C55642100211FAC /* SettingInputSection.swift */; };
+		AC19EF2C94084B5BA0175D1D /* SettingsSearchHighlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48B83503461B4F8D97B30115 /* SettingsSearchHighlight.swift */; };
 		DD17452B2C556E8100211FAC /* SettingInputHintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD17452A2C556E8100211FAC /* SettingInputHintView.swift */; };
 		DD17452B2C556E8100211FAC /* SettingInputHintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD17452A2C556E8100211FAC /* SettingInputHintView.swift */; };
 		DD17452E2C55AE4800211FAC /* TargetBehavoirDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD17452D2C55AE4800211FAC /* TargetBehavoirDataFlow.swift */; };
 		DD17452E2C55AE4800211FAC /* TargetBehavoirDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD17452D2C55AE4800211FAC /* TargetBehavoirDataFlow.swift */; };
 		DD1745302C55AE5300211FAC /* TargetBehaviorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD17452F2C55AE5300211FAC /* TargetBehaviorProvider.swift */; };
 		DD1745302C55AE5300211FAC /* TargetBehaviorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD17452F2C55AE5300211FAC /* TargetBehaviorProvider.swift */; };
@@ -838,6 +844,7 @@
 				3B4BA78B2D8DC0EC0069D5B8 /* ShareClient.framework in Embed Frameworks */,
 				3B4BA78B2D8DC0EC0069D5B8 /* ShareClient.framework in Embed Frameworks */,
 				3B4BA7912D8DC0EC0069D5B8 /* TidepoolServiceKitUI.framework in Embed Frameworks */,
 				3B4BA7912D8DC0EC0069D5B8 /* TidepoolServiceKitUI.framework in Embed Frameworks */,
 				CE95BF602BA7715800DC3DE3 /* MockKit.framework in Embed Frameworks */,
 				CE95BF602BA7715800DC3DE3 /* MockKit.framework in Embed Frameworks */,
+				B6E925142EB3932A0076D719 /* OmnipodKit.framework in Embed Frameworks */,
 				CE95BF5E2BA770C300DC3DE3 /* LoopKitUI.framework in Embed Frameworks */,
 				CE95BF5E2BA770C300DC3DE3 /* LoopKitUI.framework in Embed Frameworks */,
 			);
 			);
 			name = "Embed Frameworks";
 			name = "Embed Frameworks";
@@ -1124,6 +1131,7 @@
 		3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorProvider.swift; sourceTree = "<group>"; };
 		3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorProvider.swift; sourceTree = "<group>"; };
 		3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigProvider.swift; sourceTree = "<group>"; };
 		3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigProvider.swift; sourceTree = "<group>"; };
 		3BF85FE12E427312000D7351 /* IOBService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOBService.swift; sourceTree = "<group>"; };
 		3BF85FE12E427312000D7351 /* IOBService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOBService.swift; sourceTree = "<group>"; };
+		B015AFE12E500000000D7351 /* BolusSafetyValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusSafetyValidator.swift; sourceTree = "<group>"; };
 		3E54EF2B2E476DA40006F54D /* MedtrumKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MedtrumKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		3E54EF2B2E476DA40006F54D /* MedtrumKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MedtrumKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		3E62C7812F54CC1600433237 /* BolusDisplayThreshold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusDisplayThreshold.swift; sourceTree = "<group>"; };
 		3E62C7812F54CC1600433237 /* BolusDisplayThreshold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusDisplayThreshold.swift; sourceTree = "<group>"; };
 		3F60E97100041040446F44E7 /* PumpConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigStateModel.swift; sourceTree = "<group>"; };
 		3F60E97100041040446F44E7 /* PumpConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigStateModel.swift; sourceTree = "<group>"; };
@@ -1213,6 +1221,7 @@
 		AAFF91130F2FCCC7EBBA11AD /* BasalProfileEditorStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorStateModel.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>"; };
 		AF65DA88F972B56090AD6AC3 /* PumpConfigDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigDataFlow.swift; sourceTree = "<group>"; };
 		B5822B15939E719628E9FF7C /* SnoozeRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeRootView.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>"; };
 		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>"; };
 		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>"; };
 		BD04ECCD2D299522008C5FEB /* BolusProgressOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusProgressOverlay.swift; sourceTree = "<group>"; };
@@ -1273,6 +1282,7 @@
 		BD8FC0582D66189700B95AED /* TestAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAssembly.swift; sourceTree = "<group>"; };
 		BD8FC0582D66189700B95AED /* TestAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAssembly.swift; sourceTree = "<group>"; };
 		BD8FC05A2D6618AF00B95AED /* DeterminationStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeterminationStorageTests.swift; sourceTree = "<group>"; };
 		BD8FC05A2D6618AF00B95AED /* DeterminationStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeterminationStorageTests.swift; sourceTree = "<group>"; };
 		BD8FC05D2D6618CE00B95AED /* BolusCalculatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorTests.swift; sourceTree = "<group>"; };
 		BD8FC05D2D6618CE00B95AED /* BolusCalculatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorTests.swift; sourceTree = "<group>"; };
+		B015AFE42E500000000D7351 /* BolusSafetyValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusSafetyValidatorTests.swift; sourceTree = "<group>"; };
 		BD8FC05F2D6619DB00B95AED /* CarbsStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbsStorageTests.swift; sourceTree = "<group>"; };
 		BD8FC05F2D6619DB00B95AED /* CarbsStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbsStorageTests.swift; sourceTree = "<group>"; };
 		BD8FC0612D6619E600B95AED /* OverrideStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideStorageTests.swift; sourceTree = "<group>"; };
 		BD8FC0612D6619E600B95AED /* OverrideStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideStorageTests.swift; sourceTree = "<group>"; };
 		BD8FC0632D6619EF00B95AED /* TempTargetStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetStorageTests.swift; sourceTree = "<group>"; };
 		BD8FC0632D6619EF00B95AED /* TempTargetStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTargetStorageTests.swift; sourceTree = "<group>"; };
@@ -1416,6 +1426,7 @@
 		DD1745232C55526000211FAC /* SMBSettingsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMBSettingsStateModel.swift; sourceTree = "<group>"; };
 		DD1745232C55526000211FAC /* SMBSettingsStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMBSettingsStateModel.swift; sourceTree = "<group>"; };
 		DD1745252C55526F00211FAC /* SMBSettingsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMBSettingsRootView.swift; sourceTree = "<group>"; };
 		DD1745252C55526F00211FAC /* SMBSettingsRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMBSettingsRootView.swift; sourceTree = "<group>"; };
 		DD1745282C55642100211FAC /* SettingInputSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingInputSection.swift; sourceTree = "<group>"; };
 		DD1745282C55642100211FAC /* SettingInputSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingInputSection.swift; sourceTree = "<group>"; };
+		48B83503461B4F8D97B30115 /* SettingsSearchHighlight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSearchHighlight.swift; sourceTree = "<group>"; };
 		DD17452A2C556E8100211FAC /* SettingInputHintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingInputHintView.swift; sourceTree = "<group>"; };
 		DD17452A2C556E8100211FAC /* SettingInputHintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingInputHintView.swift; sourceTree = "<group>"; };
 		DD17452D2C55AE4800211FAC /* TargetBehavoirDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetBehavoirDataFlow.swift; sourceTree = "<group>"; };
 		DD17452D2C55AE4800211FAC /* TargetBehavoirDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetBehavoirDataFlow.swift; sourceTree = "<group>"; };
 		DD17452F2C55AE5300211FAC /* TargetBehaviorProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetBehaviorProvider.swift; sourceTree = "<group>"; };
 		DD17452F2C55AE5300211FAC /* TargetBehaviorProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetBehaviorProvider.swift; sourceTree = "<group>"; };
@@ -1606,6 +1617,7 @@
 		E592A3742CEEC038009A472C /* ContactImageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactImageProvider.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>"; };
 		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>"; };
 		E625985B47742D498CB1681A /* GlucoseNotificationSettingsProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GlucoseNotificationSettingsProvider.swift; sourceTree = "<group>"; };
+		B3919BBB515547118D684CA2 /* SettingsSearchTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SettingsSearchTests.swift; sourceTree = "<group>"; };
 		F816825D28DB441200054060 /* HeartBeatManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeartBeatManager.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>"; };
 		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>"; };
 		F90692A9274B7AAE0037068D /* HealthKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitManager.swift; sourceTree = "<group>"; };
@@ -1658,6 +1670,7 @@
 				38E87401274F77E400975559 /* CoreNFC.framework in Frameworks */,
 				38E87401274F77E400975559 /* CoreNFC.framework in Frameworks */,
 				3B4BA78A2D8DC0EC0069D5B8 /* ShareClient.framework in Frameworks */,
 				3B4BA78A2D8DC0EC0069D5B8 /* ShareClient.framework in Frameworks */,
 				3B4BA77E2D8DBD690069D5B8 /* OmniKit.framework in Frameworks */,
 				3B4BA77E2D8DBD690069D5B8 /* OmniKit.framework in Frameworks */,
+				B6E925132EB3932A0076D719 /* OmnipodKit.framework in Frameworks */,
 				3811DE1025C9D37700A708ED /* Swinject in Frameworks */,
 				3811DE1025C9D37700A708ED /* Swinject in Frameworks */,
 				3B4BA78E2D8DC0EC0069D5B8 /* TidepoolServiceKit.framework in Frameworks */,
 				3B4BA78E2D8DC0EC0069D5B8 /* TidepoolServiceKit.framework in Frameworks */,
 				B958F1B72BA0711600484851 /* MKRingProgressView in Frameworks */,
 				B958F1B72BA0711600484851 /* MKRingProgressView in Frameworks */,
@@ -2123,6 +2136,7 @@
 				DDA9AC072D67291600E6F1A9 /* AppVersionChecker */,
 				DDA9AC072D67291600E6F1A9 /* AppVersionChecker */,
 				CEB434E128B8F9BC00B70274 /* Bluetooth */,
 				CEB434E128B8F9BC00B70274 /* Bluetooth */,
 				BD7DB88C2D2C49FF003D3155 /* BolusCalculator */,
 				BD7DB88C2D2C49FF003D3155 /* BolusCalculator */,
+				B015AFE22E500000000D7351 /* BolusSafety */,
 				3862CC2C2743F9DC00BF832C /* Calendar */,
 				3862CC2C2743F9DC00BF832C /* Calendar */,
 				E592A37E2CEEC046009A472C /* ContactImage */,
 				E592A37E2CEEC046009A472C /* ContactImage */,
 				F90692A8274B7A980037068D /* HealthKit */,
 				F90692A8274B7A980037068D /* HealthKit */,
@@ -2294,6 +2308,7 @@
 		3818AA48274C267000843DB3 /* Frameworks */ = {
 		3818AA48274C267000843DB3 /* Frameworks */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				B6E925122EB3932A0076D719 /* OmnipodKit.framework */,
 				3E54EF2B2E476DA40006F54D /* MedtrumKit.framework */,
 				3E54EF2B2E476DA40006F54D /* MedtrumKit.framework */,
 				3B4BA7882D8DC0EC0069D5B8 /* TidepoolServiceKit.framework */,
 				3B4BA7882D8DC0EC0069D5B8 /* TidepoolServiceKit.framework */,
 				3B4BA7892D8DC0EC0069D5B8 /* TidepoolServiceKitUI.framework */,
 				3B4BA7892D8DC0EC0069D5B8 /* TidepoolServiceKitUI.framework */,
@@ -2410,6 +2425,7 @@
 				38DF1785276A73D400B3528F /* TagCloudView.swift */,
 				38DF1785276A73D400B3528F /* TagCloudView.swift */,
 				DD88C8E12C50420800F2D558 /* DefinitionRow.swift */,
 				DD88C8E12C50420800F2D558 /* DefinitionRow.swift */,
 				DD1745282C55642100211FAC /* SettingInputSection.swift */,
 				DD1745282C55642100211FAC /* SettingInputSection.swift */,
+				48B83503461B4F8D97B30115 /* SettingsSearchHighlight.swift */,
 				DD17452A2C556E8100211FAC /* SettingInputHintView.swift */,
 				DD17452A2C556E8100211FAC /* SettingInputHintView.swift */,
 			);
 			);
 			path = Views;
 			path = Views;
@@ -2738,6 +2754,7 @@
 				DDC6CA6C2DD90A2A0060EE25 /* LocalizationTests.swift */,
 				DDC6CA6C2DD90A2A0060EE25 /* LocalizationTests.swift */,
 				3B997DD22DC02AEF006B6BB2 /* JSONImporterData */,
 				3B997DD22DC02AEF006B6BB2 /* JSONImporterData */,
 				BD8FC05C2D6618BE00B95AED /* BolusCalculatorTests */,
 				BD8FC05C2D6618BE00B95AED /* BolusCalculatorTests */,
+				B015AFE62E500000000D7351 /* BolusSafetyTests */,
 				BD8FC0552D66187700B95AED /* CoreDataTests */,
 				BD8FC0552D66187700B95AED /* CoreDataTests */,
 				38FCF3F125E9028E0078B0D1 /* Info.plist */,
 				38FCF3F125E9028E0078B0D1 /* Info.plist */,
 				CEE9A65D2BBC9F6500EB5194 /* CalibrationsTests.swift */,
 				CEE9A65D2BBC9F6500EB5194 /* CalibrationsTests.swift */,
@@ -2745,6 +2762,7 @@
 				38FCF3F825E902C20078B0D1 /* FileStorageTests.swift */,
 				38FCF3F825E902C20078B0D1 /* FileStorageTests.swift */,
 				3B997DCE2DC00A3A006B6BB2 /* JSONImporterTests.swift */,
 				3B997DCE2DC00A3A006B6BB2 /* JSONImporterTests.swift */,
 				CE1F6DD82BADF4620064EB8D /* PluginManagerTests.swift */,
 				CE1F6DD82BADF4620064EB8D /* PluginManagerTests.swift */,
+				B3919BBB515547118D684CA2 /* SettingsSearchTests.swift */,
 				BD8FC0532D66186000B95AED /* TestError.swift */,
 				BD8FC0532D66186000B95AED /* TestError.swift */,
 				BD8FC0702D661B0000B95AED /* TidepoolTherapySettingsTests.swift */,
 				BD8FC0702D661B0000B95AED /* TidepoolTherapySettingsTests.swift */,
 			);
 			);
@@ -2773,6 +2791,14 @@
 			path = IOB;
 			path = IOB;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
+		B015AFE22E500000000D7351 /* BolusSafety */ = {
+			isa = PBXGroup;
+			children = (
+				B015AFE12E500000000D7351 /* BolusSafetyValidator.swift */,
+			);
+			path = BolusSafety;
+			sourceTree = "<group>";
+		};
 		4E8C7B59F8065047ECE20965 /* View */ = {
 		4E8C7B59F8065047ECE20965 /* View */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -3127,6 +3153,14 @@
 			path = BolusCalculatorTests;
 			path = BolusCalculatorTests;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
+		B015AFE62E500000000D7351 /* BolusSafetyTests */ = {
+			isa = PBXGroup;
+			children = (
+				B015AFE42E500000000D7351 /* BolusSafetyValidatorTests.swift */,
+			);
+			path = BolusSafetyTests;
+			sourceTree = "<group>";
+		};
 		BDA25F1A2D26BCE800035F34 /* Views */ = {
 		BDA25F1A2D26BCE800035F34 /* Views */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -4435,6 +4469,7 @@
 				BDF34F832C10C5B600D51995 /* DataManager.swift in Sources */,
 				BDF34F832C10C5B600D51995 /* DataManager.swift in Sources */,
 				38B4F3C625E5017E00E76A18 /* NotificationCenter.swift in Sources */,
 				38B4F3C625E5017E00E76A18 /* NotificationCenter.swift in Sources */,
 				19D466A729AA2C22004D5F33 /* MealSettingsStateModel.swift in Sources */,
 				19D466A729AA2C22004D5F33 /* MealSettingsStateModel.swift in Sources */,
+				AC19EF2C94084B5BA0175D1D /* SettingsSearchHighlight.swift in Sources */,
 				DD17452B2C556E8100211FAC /* SettingInputHintView.swift in Sources */,
 				DD17452B2C556E8100211FAC /* SettingInputHintView.swift in Sources */,
 				38E44528274E401C00EC9A94 /* Protected.swift in Sources */,
 				38E44528274E401C00EC9A94 /* Protected.swift in Sources */,
 				DD3F1F8B2D9E08B600DCE7B3 /* NightscoutLoginStepView.swift in Sources */,
 				DD3F1F8B2D9E08B600DCE7B3 /* NightscoutLoginStepView.swift in Sources */,
@@ -4561,6 +4596,7 @@
 				3811DEEA25CA063400A708ED /* SyncAccess.swift in Sources */,
 				3811DEEA25CA063400A708ED /* SyncAccess.swift in Sources */,
 				190EBCC829FF13AA00BA767D /* UserInterfaceSettingsStateModel.swift in Sources */,
 				190EBCC829FF13AA00BA767D /* UserInterfaceSettingsStateModel.swift in Sources */,
 				3BF85FE32E427312000D7351 /* IOBService.swift in Sources */,
 				3BF85FE32E427312000D7351 /* IOBService.swift in Sources */,
+				B015AFE32E500000000D7351 /* BolusSafetyValidator.swift in Sources */,
 				DDF847EA2C5DABAC0049BB3B /* WatchConfigGarminView.swift in Sources */,
 				DDF847EA2C5DABAC0049BB3B /* WatchConfigGarminView.swift in Sources */,
 				38BF021F25E7F0DE00579895 /* DeviceDataManager.swift in Sources */,
 				38BF021F25E7F0DE00579895 /* DeviceDataManager.swift in Sources */,
 				BD4E1A7A2D3681B700D21626 /* GlucoseTargetSetup.swift in Sources */,
 				BD4E1A7A2D3681B700D21626 /* GlucoseTargetSetup.swift in Sources */,
@@ -4939,6 +4975,8 @@
 				38FCF3F925E902C20078B0D1 /* FileStorageTests.swift in Sources */,
 				38FCF3F925E902C20078B0D1 /* FileStorageTests.swift in Sources */,
 				BD8FC0602D6619DB00B95AED /* CarbsStorageTests.swift in Sources */,
 				BD8FC0602D6619DB00B95AED /* CarbsStorageTests.swift in Sources */,
 				BD8FC05E2D6618CE00B95AED /* BolusCalculatorTests.swift in Sources */,
 				BD8FC05E2D6618CE00B95AED /* BolusCalculatorTests.swift in Sources */,
+				41740E936552456AAC0EDAC3 /* SettingsSearchTests.swift in Sources */,
+				B015AFE52E500000000D7351 /* BolusSafetyValidatorTests.swift in Sources */,
 				BD8FC0712D661B0000B95AED /* TidepoolTherapySettingsTests.swift in Sources */,
 				BD8FC0712D661B0000B95AED /* TidepoolTherapySettingsTests.swift in Sources */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;

+ 3 - 0
Trio.xcworkspace/contents.xcworkspacedata

@@ -8,6 +8,9 @@
       location = "group:MinimedKit/MinimedKit.xcodeproj">
       location = "group:MinimedKit/MinimedKit.xcodeproj">
    </FileRef>
    </FileRef>
    <FileRef
    <FileRef
+      location = "group:OmnipodKit/OmnipodKit.xcodeproj">
+   </FileRef>
+   <FileRef
       location = "group:LibreTransmitter/LibreTransmitter.xcodeproj">
       location = "group:LibreTransmitter/LibreTransmitter.xcodeproj">
    </FileRef>
    </FileRef>
    <FileRef
    <FileRef

+ 1 - 1
Trio.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -1,5 +1,5 @@
 {
 {
-  "originHash" : "598841ae6fe892058ca678f5672f34299df2d62843330367c207648003263ccd",
+  "originHash" : "1e72c1cdf8ea5ec9fe527ebfab01ea55fca9e8651fe3252338fd3d4ea2cb327a",
   "pins" : [
   "pins" : [
     {
     {
       "identity" : "abseil-cpp-binary",
       "identity" : "abseil-cpp-binary",

+ 10 - 3
Trio/Sources/APS/APSManager.swift

@@ -604,11 +604,18 @@ final class BaseAPSManager: APSManager, Injectable {
         do {
         do {
             try await pump.enactBolus(units: roundedAmount, automatic: isSMB)
             try await pump.enactBolus(units: roundedAmount, automatic: isSMB)
             debug(.apsManager, "Bolus succeeded")
             debug(.apsManager, "Bolus succeeded")
-            if !isSMB {
-                try await determineBasalSync()
-            }
             bolusProgress.send(0)
             bolusProgress.send(0)
             callback?(true, String(localized: "Bolus enacted successfully.", comment: "Success message for enacting a bolus"))
             callback?(true, String(localized: "Bolus enacted successfully.", comment: "Success message for enacting a bolus"))
+            if !isSMB {
+                do {
+                    try await determineBasalSync()
+                } catch {
+                    warning(
+                        .apsManager,
+                        "determineBasalSync after manual bolus failed: \(error.localizedDescription)"
+                    )
+                }
+            }
         } catch {
         } catch {
             warning(.apsManager, "Bolus failed with error: \(error)")
             warning(.apsManager, "Bolus failed with error: \(error)")
             processError(APSError.pumpError(error))
             processError(APSError.pumpError(error))

+ 59 - 1
Trio/Sources/APS/DeviceDataManager.swift

@@ -10,6 +10,7 @@ import MinimedKit
 import MockKit
 import MockKit
 import OmniBLE
 import OmniBLE
 import OmniKit
 import OmniKit
+import OmnipodKit
 import ShareClient
 import ShareClient
 import SwiftDate
 import SwiftDate
 import Swinject
 import Swinject
@@ -39,6 +40,7 @@ private let staticPumpManagers: [PumpManagerUI.Type] = [
     MinimedPumpManager.self,
     MinimedPumpManager.self,
     OmnipodPumpManager.self,
     OmnipodPumpManager.self,
     OmniBLEPumpManager.self,
     OmniBLEPumpManager.self,
+    OmniPumpManager.self,
     DanaKitPumpManager.self,
     DanaKitPumpManager.self,
     MedtrumPumpManager.self,
     MedtrumPumpManager.self,
     MockPumpManager.self
     MockPumpManager.self
@@ -48,6 +50,7 @@ private let staticPumpManagersByIdentifier: [String: PumpManagerUI.Type] = [
     MinimedPumpManager.pluginIdentifier: MinimedPumpManager.self,
     MinimedPumpManager.pluginIdentifier: MinimedPumpManager.self,
     OmnipodPumpManager.pluginIdentifier: OmnipodPumpManager.self,
     OmnipodPumpManager.pluginIdentifier: OmnipodPumpManager.self,
     OmniBLEPumpManager.pluginIdentifier: OmniBLEPumpManager.self,
     OmniBLEPumpManager.pluginIdentifier: OmniBLEPumpManager.self,
+    OmniPumpManager.pluginIdentifier: OmniPumpManager.self,
     DanaKitPumpManager.pluginIdentifier: DanaKitPumpManager.self,
     DanaKitPumpManager.pluginIdentifier: DanaKitPumpManager.self,
     MedtrumPumpManager.pluginIdentifier: MedtrumPumpManager.self,
     MedtrumPumpManager.pluginIdentifier: MedtrumPumpManager.self,
     MockPumpManager.pluginIdentifier: MockPumpManager.self
     MockPumpManager.pluginIdentifier: MockPumpManager.self
@@ -142,6 +145,13 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
                         pumpActivatedAtDate.send(medtrumPump.state.patchActivatedAt)
                         pumpActivatedAtDate.send(medtrumPump.state.patchActivatedAt)
                     }
                     }
                 }
                 }
+                if let omni = pumpManager as? OmniPumpManager {
+                    guard let endTime = omni.state.podState?.expiresAt else {
+                        pumpExpiresAtDate.send(nil)
+                        return
+                    }
+                    pumpExpiresAtDate.send(endTime)
+                }
                 if let simulatorPump = pumpManager as? MockPumpManager {
                 if let simulatorPump = pumpManager as? MockPumpManager {
                     pumpDisplayState.value = PumpDisplayState(name: simulatorPump.localizedTitle, image: simulatorPump.smallImage)
                     pumpDisplayState.value = PumpDisplayState(name: simulatorPump.localizedTitle, image: simulatorPump.smallImage)
                     pumpName.send(simulatorPump.localizedTitle)
                     pumpName.send(simulatorPump.localizedTitle)
@@ -292,6 +302,10 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
         self.recommendsLoop.send()
         self.recommendsLoop.send()
     }
     }
 
 
+    public func pumpManagerTypeByIdentifier(_ identifier: String) -> PumpManagerUI.Type? {
+        staticPumpManagersByIdentifier[identifier]
+    }
+
     private func pumpManagerFromRawValue(_ rawValue: [String: Any]) -> PumpManagerUI? {
     private func pumpManagerFromRawValue(_ rawValue: [String: Any]) -> PumpManagerUI? {
         guard let rawState = rawValue["state"] as? PumpManager.RawStateValue,
         guard let rawState = rawValue["state"] as? PumpManager.RawStateValue,
               let Manager = pumpManagerTypeFromRawValue(rawValue)
               let Manager = pumpManagerTypeFromRawValue(rawValue)
@@ -307,7 +321,18 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
             return nil
             return nil
         }
         }
 
 
-        return staticPumpManagersByIdentifier[managerIdentifier]
+        if let pumpManager = pumpManagerTypeByIdentifier(managerIdentifier) {
+            return pumpManager
+        }
+
+        /// The pumpManager was not found for managerIdentifier. If this was for an "Omnipod" (OmniKit) or
+        /// "Omnipod-DASH" (OmniBLE), have the universal "Omni" pumpManager (OmnipodKit) handle instead.
+        let OmniStr = "Omni"
+        if managerIdentifier.hasPrefix(OmniStr) {
+            return pumpManagerTypeByIdentifier(OmniStr)
+        }
+
+        return nil
     }
     }
 
 
     // MARK: - GlucoseSource
     // MARK: - GlucoseSource
@@ -554,6 +579,39 @@ extension BaseDeviceDataManager: PumpManagerDelegate {
             }
             }
         }
         }
 
 
+        if let omni = pumpManager as? OmniPumpManager {
+            let reservoirVal = omni.state.podState?.lastInsulinMeasurements?.reservoirLevel ?? 0xDEAD_BEEF
+            // TODO: find the value Pod.maximumReservoirReading
+            let reservoir = Decimal(reservoirVal) > 50.0 ? 0xDEAD_BEEF : reservoirVal
+
+            storage.save(Decimal(reservoir), as: OpenAPS.Monitor.reservoir)
+            broadcaster.notify(PumpReservoirObserver.self, on: processQueue) {
+                $0.pumpReservoirDidChange(Decimal(reservoir))
+            }
+
+            // manual temp basal on
+            if let tempBasal = omni.state.podState?.unfinalizedTempBasal, !tempBasal.isFinished(),
+               !tempBasal.automatic
+            {
+                // the manual basal temp is launch - block every thing
+                debug(.deviceManager, "manual temp basal")
+                manualTempBasal.send(true)
+            } else {
+                // no more manual Temp Basal !
+                manualTempBasal.send(false)
+            }
+
+            guard let endTime = omni.state.podState?.expiresAt else {
+                pumpExpiresAtDate.send(nil)
+                return
+            }
+            pumpExpiresAtDate.send(endTime)
+
+            if let startTime = omni.state.podState?.activatedAt {
+                storage.save(startTime, as: OpenAPS.Monitor.podAge)
+            }
+        }
+
         if let simulatorPump = pumpManager as? MockPumpManager {
         if let simulatorPump = pumpManager as? MockPumpManager {
             broadcaster.notify(PumpReservoirObserver.self, on: processQueue) {
             broadcaster.notify(PumpReservoirObserver.self, on: processQueue) {
                 $0.pumpReservoirDidChange(Decimal(simulatorPump.state.reservoirUnitsRemaining))
                 $0.pumpReservoirDidChange(Decimal(simulatorPump.state.reservoirUnitsRemaining))

+ 22 - 4
Trio/Sources/Application/AppDelegate.swift

@@ -20,21 +20,39 @@ class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject, UNUserNoti
         Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(crashReportingEnabled)
         Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(crashReportingEnabled)
         Crashlytics.crashlytics().setCustomValue(Bundle.main.appDevVersion ?? "unknown", forKey: "app_dev_version")
         Crashlytics.crashlytics().setCustomValue(Bundle.main.appDevVersion ?? "unknown", forKey: "app_dev_version")
 
 
-        // Telemetry: record this cold launch into the sliding 7-day window. If
-        // consent is set and the build SHA changed since the last successful
-        // send, fire an immediate ping — the 24h scheduler can't notice a
-        // build update on its own. Then arm the recurring 24h timer.
+        // Telemetry: record this cold launch into the sliding 7-day window,
+        // then drive cadence via three layered triggers — listed below in
+        // priority of reliability:
+        //
+        //   1. SHA-change ping: build updated since last send. Awaited so
+        //      the lastSentAt stamp is fresh before the overdue check.
+        //   2. checkAndSendIfOverdue: covers the regular cold launch on the
+        //      same build when >24h has passed since the last successful
+        //      send. Together with the foreground-transition hook below
+        //      (`applicationWillEnterForeground`), this keeps daily pings
+        //      flowing on iOS.
+        //   3. scheduleRecurring: best-effort fallback for the rare case
+        //      where the app stays foregrounded for a full 24h.
         TelemetryClient.shared.recordColdLaunch()
         TelemetryClient.shared.recordColdLaunch()
         Task.detached {
         Task.detached {
             if TelemetryClient.shared.buildShaChangedSinceLastSend() {
             if TelemetryClient.shared.buildShaChangedSinceLastSend() {
                 await TelemetryClient.shared.maybeSend()
                 await TelemetryClient.shared.maybeSend()
             }
             }
             TelemetryClient.shared.scheduleRecurring()
             TelemetryClient.shared.scheduleRecurring()
+            TelemetryClient.shared.checkAndSendIfOverdue()
         }
         }
 
 
         return true
         return true
     }
     }
 
 
+    /// Foreground-transition entry point for telemetry cadence. Re-evaluates
+    /// the overdue window every time the user brings Trio to the foreground,
+    /// since `scheduleRecurring`'s GCD timer doesn't fire while suspended.
+    /// No-op if a send already landed within the last 24h.
+    func applicationWillEnterForeground(_: UIApplication) {
+        TelemetryClient.shared.checkAndSendIfOverdue()
+    }
+
     func application(
     func application(
         _: UIApplication,
         _: UIApplication,
         didReceiveRemoteNotification userInfo: [AnyHashable: Any],
         didReceiveRemoteNotification userInfo: [AnyHashable: Any],

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

@@ -29,5 +29,6 @@ final class ServiceAssembly: Assembly {
             }
             }
         }
         }
         container.register(IOBService.self) { r in BaseIOBService(resolver: r) }
         container.register(IOBService.self) { r in BaseIOBService(resolver: r) }
+        container.register(BolusSafetyValidator.self) { r in BaseBolusSafetyValidator(resolver: r) }
     }
     }
 }
 }

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 163 - 0
Trio/Sources/Localizations/Main/Localizable.xcstrings


+ 3 - 3
Trio/Sources/Models/TrioSettings.swift

@@ -3,14 +3,14 @@ import Foundation
 enum BolusShortcutLimit: String, JSON, CaseIterable, Identifiable {
 enum BolusShortcutLimit: String, JSON, CaseIterable, Identifiable {
     var id: String { rawValue }
     var id: String { rawValue }
     case notAllowed
     case notAllowed
-    case limitBolusMax
+    case limitWithSafetyChecks
 
 
     var displayName: String {
     var displayName: String {
         switch self {
         switch self {
         case .notAllowed:
         case .notAllowed:
             return String(localized: "Not allowed")
             return String(localized: "Not allowed")
-        case .limitBolusMax:
-            return String(localized: "Max bolus")
+        case .limitWithSafetyChecks:
+            return String(localized: "Limit with Safety Checks")
         }
         }
     }
     }
 }
 }

+ 1 - 0
Trio/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift

@@ -406,6 +406,7 @@ extension AlgorithmAdvancedSettings {
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Additionals")
             .navigationTitle("Additionals")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .settingsHighlightScroll()
             .onDisappear {
             .onDisappear {
                 state.saveIfChanged()
                 state.saveIfChanged()
             }
             }

+ 1 - 0
Trio/Sources/Modules/AutosensSettings/View/AutosensSettingsRootView.swift

@@ -230,6 +230,7 @@ extension AutosensSettings {
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Autosens")
             .navigationTitle("Autosens")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .settingsHighlightScroll()
         }
         }
     }
     }
 }
 }

+ 1 - 0
Trio/Sources/Modules/BolusCalculatorConfig/View/BolusCalculatorConfigRootView.swift

@@ -200,6 +200,7 @@ extension BolusCalculatorConfig {
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationBarTitle("Bolus Calculator")
             .navigationBarTitle("Bolus Calculator")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .settingsHighlightScroll()
         }
         }
     }
     }
 }
 }

+ 1 - 0
Trio/Sources/Modules/CGMSettings/View/CGMRootView.swift

@@ -150,6 +150,7 @@ extension CGMSettings {
                 .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
                 .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
                 .onAppear(perform: configureView)
                 .onAppear(perform: configureView)
                 .navigationTitle("CGM")
                 .navigationTitle("CGM")
+                .settingsHighlightScroll()
                 .navigationBarTitleDisplayMode(.automatic)
                 .navigationBarTitleDisplayMode(.automatic)
                 .navigationBarItems(leading: displayClose ? Button("Close", action: state.hideModal) : nil)
                 .navigationBarItems(leading: displayClose ? Button("Close", action: state.hideModal) : nil)
                 .sheet(isPresented: $state.shouldDisplayCGMSetupSheet) {
                 .sheet(isPresented: $state.shouldDisplayCGMSetupSheet) {

+ 1 - 0
Trio/Sources/Modules/CalendarEventSettings/View/CalendarEventSettingsRootView.swift

@@ -147,6 +147,7 @@ extension CalendarEventSettings {
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Calendar Events")
             .navigationTitle("Calendar Events")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .settingsHighlightScroll()
         }
         }
     }
     }
 }
 }

+ 2 - 1
Trio/Sources/Modules/DynamicSettings/View/DynamicSettingsRootView.swift

@@ -165,7 +165,7 @@ extension DynamicSettings {
                             }.padding(.top)
                             }.padding(.top)
                         }.padding(.bottom)
                         }.padding(.bottom)
                     }
                     }
-                ).listRowBackground(Color.chart)
+                ).settingsSearchTarget(label: String(localized: "Dynamic ISF"))
 
 
                 if state.dynamicSensitivityType != .disabled {
                 if state.dynamicSensitivityType != .disabled {
                     if state.dynamicSensitivityType == .logarithmic {
                     if state.dynamicSensitivityType == .logarithmic {
@@ -283,6 +283,7 @@ extension DynamicSettings {
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationBarTitle("Dynamic Settings")
             .navigationBarTitle("Dynamic Settings")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .settingsHighlightScroll()
         }
         }
     }
     }
 }
 }

+ 1 - 0
Trio/Sources/Modules/GeneralSettings/View/UnitsLimitsSettingsRootView.swift

@@ -227,6 +227,7 @@ extension UnitsLimitsSettings {
             .onDisappear {
             .onDisappear {
                 state.saveIfChanged()
                 state.saveIfChanged()
             }
             }
+            .settingsHighlightScroll()
         }
         }
     }
     }
 }
 }

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

@@ -264,6 +264,7 @@ extension GlucoseNotificationSettings {
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationBarTitle("Trio Notifications")
             .navigationBarTitle("Trio Notifications")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .settingsHighlightScroll()
         }
         }
 
 
         var lowAndHighGlucoseAlertSection: some View {
         var lowAndHighGlucoseAlertSection: some View {

+ 3 - 0
Trio/Sources/Modules/Home/View/HomeRootView.swift

@@ -22,6 +22,7 @@ extension Home {
         @State var state = StateModel()
         @State var state = StateModel()
 
 
         @State var settingsPath = NavigationPath()
         @State var settingsPath = NavigationPath()
+        @State var settingsSearchHighlight = SettingsSearchHighlight()
         @State var isStatusPopupPresented = false
         @State var isStatusPopupPresented = false
         @State var showCancelAlert = false
         @State var showCancelAlert = false
         @State var showCancelConfirmDialog = false
         @State var showCancelConfirmDialog = false
@@ -993,6 +994,7 @@ extension Home {
             // PUMP RELATED
             // PUMP RELATED
             .confirmationDialog("Pump Model", isPresented: $showPumpSelection) {
             .confirmationDialog("Pump Model", isPresented: $showPumpSelection) {
                 Button("Medtronic") { state.addPump(.minimed) }
                 Button("Medtronic") { state.addPump(.minimed) }
+                Button("All Omnipod Types") { state.addPump(.omni) }
                 Button("Omnipod Eros") { state.addPump(.omnipod) }
                 Button("Omnipod Eros") { state.addPump(.omnipod) }
                 Button("Omnipod DASH") { state.addPump(.omnipodBLE) }
                 Button("Omnipod DASH") { state.addPump(.omnipodBLE) }
                 Button("Dana(RS/-i)") { state.addPump(.dana) }
                 Button("Dana(RS/-i)") { state.addPump(.dana) }
@@ -1097,6 +1099,7 @@ extension Home {
 
 
                     NavigationStack(path: self.$settingsPath) {
                     NavigationStack(path: self.$settingsPath) {
                         Settings.RootView(resolver: resolver) }
                         Settings.RootView(resolver: resolver) }
+                        .environment(settingsSearchHighlight)
                         .tabItem { Label(
                         .tabItem { Label(
                             "Settings",
                             "Settings",
                             systemImage: "gear"
                             systemImage: "gear"

+ 1 - 0
Trio/Sources/Modules/LiveActivitySettings/View/LiveActivitySettingsRootView.swift

@@ -233,6 +233,7 @@ extension LiveActivitySettings {
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Live Activity")
             .navigationTitle("Live Activity")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .settingsHighlightScroll()
         }
         }
     }
     }
 }
 }

+ 15 - 6
Trio/Sources/Modules/LiveActivitySettings/View/LiveActivityWidgetConfiguration.swift

@@ -383,17 +383,26 @@ enum LiveActivityItem: String, CaseIterable, Identifiable {
     var displayName: String {
     var displayName: String {
         switch self {
         switch self {
         case .currentGlucoseLarge:
         case .currentGlucoseLarge:
-            return "Glucose and Trend, no Delta"
+            return String(
+                localized: "Glucose and Trend, no Delta",
+                comment: "Live Activity widget icon label for Glucose and Trend, no Delta"
+            )
         case .currentGlucose:
         case .currentGlucose:
-            return "Glucose, Trend, Delta"
+            return String(
+                localized: "Glucose, Trend, Delta",
+                comment: "Live Activity widget icon label for Glucose, Trend, Delta"
+            )
         case .iob:
         case .iob:
-            return "Insulin on Board (IOB)"
+            return String(
+                localized: "Insulin on Board (IOB)",
+                comment: "Live Activity widget icon label for Insulin on Board (IOB)"
+            )
         case .cob:
         case .cob:
-            return "Carbs on Board (IOB)"
+            return String(localized: "Carbs on Board (COB)", comment: "Live Activity widget icon label for Carbs on Board (COB)")
         case .updatedLabel:
         case .updatedLabel:
-            return "Last Updated"
+            return String(localized: "Last Updated", comment: "Live Activity widget icon label for Last Updated")
         case .totalDailyDose:
         case .totalDailyDose:
-            return "Total Daily Dose"
+            return String(localized: "Total Daily Dose", comment: "Live Activity widget icon label for Total Daily Dose")
         }
         }
     }
     }
 }
 }

+ 1 - 0
Trio/Sources/Modules/MealSettings/View/MealSettingsRootView.swift

@@ -371,6 +371,7 @@ extension MealSettings {
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationBarTitle("Meal Settings")
             .navigationBarTitle("Meal Settings")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .settingsHighlightScroll()
         }
         }
     }
     }
 }
 }

+ 1 - 0
Trio/Sources/Modules/NightscoutConfig/View/NightscoutFetchView.swift

@@ -52,6 +52,7 @@ struct NightscoutFetchView: View {
         }
         }
         .navigationTitle("Fetch")
         .navigationTitle("Fetch")
         .navigationBarTitleDisplayMode(.automatic)
         .navigationBarTitleDisplayMode(.automatic)
+        .settingsHighlightScroll()
         .scrollContentBackground(.hidden)
         .scrollContentBackground(.hidden)
         .background(appState.trioBackgroundColor(for: colorScheme))
         .background(appState.trioBackgroundColor(for: colorScheme))
     }
     }

+ 1 - 0
Trio/Sources/Modules/NightscoutConfig/View/NightscoutUploadView.swift

@@ -81,6 +81,7 @@ struct NightscoutUploadView: View {
         }
         }
         .navigationTitle("Upload")
         .navigationTitle("Upload")
         .navigationBarTitleDisplayMode(.automatic)
         .navigationBarTitleDisplayMode(.automatic)
+        .settingsHighlightScroll()
         .scrollContentBackground(.hidden)
         .scrollContentBackground(.hidden)
         .background(appState.trioBackgroundColor(for: colorScheme))
         .background(appState.trioBackgroundColor(for: colorScheme))
     }
     }

+ 10 - 2
Trio/Sources/Modules/Onboarding/View/TherapySettingEditorView.swift

@@ -8,6 +8,13 @@ struct TherapySettingEditorView: View {
     var validateOnDelete: (() -> Void)?
     var validateOnDelete: (() -> Void)?
     var onItemAdded: (() -> Void)?
     var onItemAdded: (() -> Void)?
 
 
+    private let basalFormatter: NumberFormatter = {
+        let numberFormatter = NumberFormatter()
+        numberFormatter.maximumFractionDigits = 3
+        numberFormatter.minimumFractionDigits = 2
+        return numberFormatter
+    }()
+
     @State private var selectedItemID: UUID?
     @State private var selectedItemID: UUID?
     @Namespace var bottomID
     @Namespace var bottomID
 
 
@@ -280,10 +287,11 @@ struct TherapySettingEditorView: View {
         case .mmolL,
         case .mmolL,
              .mmolLPerUnit:
              .mmolLPerUnit:
             return decimalValue.formattedAsMmolL
             return decimalValue.formattedAsMmolL
+        case .unitPerHour:
+            return basalFormatter.string(from: decimalValue as NSNumber) ?? ""
         case .gramPerUnit,
         case .gramPerUnit,
              .mgdL,
              .mgdL,
-             .mgdLPerUnit,
-             .unitPerHour:
+             .mgdLPerUnit:
             return decimalValue.description
             return decimalValue.description
         }
         }
     }
     }

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

@@ -9,6 +9,7 @@ enum PumpConfig {
         case minimed
         case minimed
         case omnipod
         case omnipod
         case omnipodBLE
         case omnipodBLE
+        case omni
         case dana
         case dana
         case medtrum
         case medtrum
         case simulator
         case simulator

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

@@ -118,6 +118,7 @@ extension PumpConfig {
                                 )
                                 )
                                 VStack(alignment: .leading) {
                                 VStack(alignment: .leading) {
                                     Text("• Medtronic")
                                     Text("• Medtronic")
+                                    Text("• All Omnipod Types")
                                     Text("• Omnipod Eros")
                                     Text("• Omnipod Eros")
                                     Text("• Omnipod DASH")
                                     Text("• Omnipod DASH")
                                     Text("• Dana (RS/-i)")
                                     Text("• Dana (RS/-i)")
@@ -134,6 +135,7 @@ extension PumpConfig {
                 }
                 }
                 .confirmationDialog("Pump Model", isPresented: $showPumpSelection) {
                 .confirmationDialog("Pump Model", isPresented: $showPumpSelection) {
                     Button("Medtronic") { state.addPump(.minimed) }
                     Button("Medtronic") { state.addPump(.minimed) }
+                    Button("All Omnipod Types") { state.addPump(.omni) }
                     Button("Omnipod Eros") { state.addPump(.omnipod) }
                     Button("Omnipod Eros") { state.addPump(.omnipod) }
                     Button("Omnipod DASH") { state.addPump(.omnipodBLE) }
                     Button("Omnipod DASH") { state.addPump(.omnipodBLE) }
                     Button("Dana(RS/-i)") { state.addPump(.dana) }
                     Button("Dana(RS/-i)") { state.addPump(.dana) }

+ 10 - 0
Trio/Sources/Modules/PumpConfig/View/PumpSetupView.swift

@@ -9,6 +9,7 @@ import MockKitUI
 import OmniBLE
 import OmniBLE
 import OmniKit
 import OmniKit
 import OmniKitUI
 import OmniKitUI
+import OmnipodKit
 import SwiftUI
 import SwiftUI
 import UIKit
 import UIKit
 
 
@@ -60,6 +61,15 @@ extension PumpConfig {
                     allowDebugFeatures: true,
                     allowDebugFeatures: true,
                     allowedInsulinTypes: [.apidra, .humalog, .novolog, .fiasp, .lyumjev]
                     allowedInsulinTypes: [.apidra, .humalog, .novolog, .fiasp, .lyumjev]
                 )
                 )
+            case .omni:
+                setupViewController = OmniPumpManager.setupViewController(
+                    initialSettings: initialSettings,
+                    bluetoothProvider: bluetoothManager,
+                    colorPalette: .default,
+                    allowDebugFeatures: true,
+                    prefersToSkipUserInteraction: false,
+                    allowedInsulinTypes: [.apidra, .humalog, .novolog, .fiasp, .lyumjev]
+                )
             case .dana:
             case .dana:
                 setupViewController = DanaKitPumpManager.setupViewController(
                 setupViewController = DanaKitPumpManager.setupViewController(
                     initialSettings: initialSettings,
                     initialSettings: initialSettings,

+ 1 - 0
Trio/Sources/Modules/RemoteControlConfig/View/RemoteControlConfig.swift

@@ -101,6 +101,7 @@ extension RemoteControlConfig {
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Remote Control")
             .navigationTitle("Remote Control")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .settingsHighlightScroll()
         }
         }
     }
     }
 }
 }

+ 1 - 0
Trio/Sources/Modules/SMBSettings/View/SMBSettingsRootView.swift

@@ -370,6 +370,7 @@ extension SMBSettings {
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("SMB Settings")
             .navigationTitle("SMB Settings")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .settingsHighlightScroll()
         }
         }
     }
     }
 }
 }

+ 66 - 2
Trio/Sources/Modules/Settings/SettingItems.swift

@@ -8,16 +8,22 @@ struct SettingItem: Identifiable {
     let view: Screen
     let view: Screen
     let searchContents: [String]?
     let searchContents: [String]?
     let path: [String]?
     let path: [String]?
+    /// Maps a `searchContents` string to the exact label used in `SettingInputSection`
+    /// when the two differ (e.g. `"Max IOB"` → `"Maximum Insulin on Board (IOB)"`).
+    /// Entries whose searchContents string already matches the label don't need an entry here.
+    let scrollTargetLabels: [String: String]?
 
 
     init(
     init(
         title: String,
         title: String,
         view: Screen,
         view: Screen,
         searchContents: [String]? = nil,
         searchContents: [String]? = nil,
+        scrollTargetLabels: [String: String]? = nil,
         path: [String]? = nil
         path: [String]? = nil
     ) {
     ) {
         self.title = title
         self.title = title
         self.view = view
         self.view = view
         self.searchContents = searchContents
         self.searchContents = searchContents
+        self.scrollTargetLabels = scrollTargetLabels
         self.path = path
         self.path = path
     }
     }
 }
 }
@@ -26,6 +32,11 @@ struct FilteredSettingItem: Identifiable {
     let id = UUID()
     let id = UUID()
     let settingItem: SettingItem
     let settingItem: SettingItem
     let matchedContent: String
     let matchedContent: String
+    /// The label string used as the scroll/highlight target in the destination view.
+    /// Falls back to `matchedContent` when no explicit mapping exists.
+    var scrollLabel: String {
+        settingItem.scrollTargetLabels?[matchedContent] ?? matchedContent
+    }
 }
 }
 
 
 enum SettingItems {
 enum SettingItems {
@@ -89,6 +100,12 @@ enum SettingItems {
                 "Minimum Safety Threshold",
                 "Minimum Safety Threshold",
                 "Delivery Limits"
                 "Delivery Limits"
             ],
             ],
+            scrollTargetLabels: [
+                "Max IOB": "Maximum Insulin on Board (IOB)",
+                "Max Bolus": "Maximum Bolus",
+                "Max Basal": "Maximum Basal Rate",
+                "Max COB": "Maximum Carbs on Board (COB)"
+            ],
             path: ["Therapy Settings", "Units and Limits"]
             path: ["Therapy Settings", "Units and Limits"]
         ),
         ),
         SettingItem(title: "Basal Rates", view: .basalProfileEditor, path: ["Therapy Settings"]),
         SettingItem(title: "Basal Rates", view: .basalProfileEditor, path: ["Therapy Settings"]),
@@ -122,6 +139,12 @@ enum SettingItems {
                 "Max UAM SMB Basal Minutes",
                 "Max UAM SMB Basal Minutes",
                 "Max Allowed Glucose Rise for SMB"
                 "Max Allowed Glucose Rise for SMB"
             ],
             ],
+            scrollTargetLabels: [
+                "Enable SMB With Temporary Target": "Enable SMB With Temptarget",
+                "Allow SMB With High Temporary Target": "Allow SMB With High Temptarget",
+                "Max UAM SMB Basal Minutes": "Max UAM Basal Minutes",
+                "High Glucose Target": "Enable SMB With High Glucose"
+            ],
             path: ["Algorithm", "Super Micro Bolus (SMB)"]
             path: ["Algorithm", "Super Micro Bolus (SMB)"]
         ),
         ),
         SettingItem(
         SettingItem(
@@ -148,6 +171,10 @@ enum SettingItems {
                 "Resistance Lowers Target",
                 "Resistance Lowers Target",
                 "Half Basal Exercise Target"
                 "Half Basal Exercise Target"
             ],
             ],
+            scrollTargetLabels: [
+                "High Temptarget Raises Sensitivity": "High Temp Target Raises Sensitivity",
+                "Low Temptarget Lowers Sensitivity": "Low Temp Target Lowers Sensitivity"
+            ],
             path: ["Algorithm", "Target Behavior"]
             path: ["Algorithm", "Target Behavior"]
         ),
         ),
         SettingItem(
         SettingItem(
@@ -168,6 +195,11 @@ enum SettingItems {
                 "Remaining Carbs Cap",
                 "Remaining Carbs Cap",
                 "Noisy CGM Target Multiplier"
                 "Noisy CGM Target Multiplier"
             ],
             ],
+            scrollTargetLabels: [
+                "Min 5m Carbimpact": "Min 5m Carb Impact",
+                "Remaining Carbs Fraction": "Remaining Carbs Percentage",
+                "Noisy CGM Target Multiplier": "Noisy CGM Target Increase"
+            ],
             path: ["Algorithm", "Additionals"]
             path: ["Algorithm", "Additionals"]
         )
         )
     ]
     ]
@@ -185,6 +217,12 @@ enum SettingItems {
                 "Super Bolus Factor",
                 "Super Bolus Factor",
                 "Very Low Glucose Warning"
                 "Very Low Glucose Warning"
             ],
             ],
+            scrollTargetLabels: [
+                "Enable Reduced Bolus Factor": "Enable Reduced Bolus Option",
+                "Reduced Bolus Factor": "Enable Reduced Bolus Option",
+                "Enable Super Bolus": "Enable Super Bolus Option",
+                "Super Bolus Factor": "Enable Super Bolus Option"
+            ],
             path: ["Features", "Bolus Calculator"]
             path: ["Features", "Bolus Calculator"]
         ),
         ),
         SettingItem(
         SettingItem(
@@ -201,6 +239,14 @@ enum SettingItems {
                 "Fat and Protein Percentage",
                 "Fat and Protein Percentage",
                 "FPU"
                 "FPU"
             ],
             ],
+            scrollTargetLabels: [
+                "Max Fat": "Enable Fat and Protein Entries",
+                "Max Protein": "Enable Fat and Protein Entries",
+                "Fat and Protein Delay": "Enable Fat and Protein Entries",
+                "Spread Interval": "Enable Fat and Protein Entries",
+                "Fat and Protein Percentage": "Enable Fat and Protein Entries",
+                "FPU": "Enable Fat and Protein Entries"
+            ],
             path: ["Features", "Meal Settings"]
             path: ["Features", "Meal Settings"]
         ),
         ),
         SettingItem(
         SettingItem(
@@ -224,7 +270,6 @@ enum SettingItems {
                 "Show Low and High Thresholds",
                 "Show Low and High Thresholds",
                 "Low Threshold",
                 "Low Threshold",
                 "High Threshold",
                 "High Threshold",
-                "X-Axis Interval Step",
                 "eA1c/GMI Display Unit",
                 "eA1c/GMI Display Unit",
                 "Show Carbs Required Badge",
                 "Show Carbs Required Badge",
                 "Carbs Required Threshold",
                 "Carbs Required Threshold",
@@ -232,17 +277,32 @@ enum SettingItems {
                 "Bolus Display Threshold",
                 "Bolus Display Threshold",
                 "Cone",
                 "Cone",
                 "Lines",
                 "Lines",
+                "Appearance",
                 "Dark Mode",
                 "Dark Mode",
                 "Light Mode",
                 "Light Mode",
-                "Appearance",
                 "Dark Scheme",
                 "Dark Scheme",
                 "Light Scheme",
                 "Light Scheme",
                 "Glucose Color Scheme",
                 "Glucose Color Scheme",
                 "Time in Range Type",
                 "Time in Range Type",
                 "Time in Tight Range (TITR)",
                 "Time in Tight Range (TITR)",
                 "Time in Normoglycemia (TING)",
                 "Time in Normoglycemia (TING)",
+                "X-Axis Interval Step",
                 "Require Adjustments Confirmation"
                 "Require Adjustments Confirmation"
             ],
             ],
+            scrollTargetLabels: [
+                "Show Y-Axis Grid Lines": "Show X-Axis Grid Lines",
+                "High Threshold": "Low Threshold",
+                "Cone": "Forecast Display Type",
+                "Lines": "Forecast Display Type",
+                "Dark Mode": "Appearance",
+                "Light Mode": "Appearance",
+                "Time in Tight Range (TITR)": "Time in Range Type",
+                "Time in Normoglycemia (TING)": "Time in Range Type",
+                "Dark Scheme": "Appearance",
+                "Light Scheme": "Appearance",
+                "X-Axis Interval Step": "Show X-Axis Grid Lines",
+                "Carbs Required Threshold": "Show Carbs Required Badge"
+            ],
             path: ["Features", "User Interface"]
             path: ["Features", "User Interface"]
         ),
         ),
         SettingItem(
         SettingItem(
@@ -275,6 +335,10 @@ enum SettingItems {
                 "Low Glucose Alarm Limit",
                 "Low Glucose Alarm Limit",
                 "High Glucose Alarm Limit"
                 "High Glucose Alarm Limit"
             ],
             ],
+            scrollTargetLabels: [
+                "Low Glucose Alarm Limit": "Glucose Notifications",
+                "High Glucose Alarm Limit": "Glucose Notifications"
+            ],
             path: ["Notifications", "Trio Notifications"] // Glucose
             path: ["Notifications", "Trio Notifications"] // Glucose
         ),
         ),
         SettingItem(
         SettingItem(

+ 19 - 8
Trio/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -38,6 +38,7 @@ extension Settings {
         @Environment(\.colorScheme) var colorScheme
         @Environment(\.colorScheme) var colorScheme
         @EnvironmentObject var appIcons: Icons
         @EnvironmentObject var appIcons: Icons
         @Environment(AppState.self) var appState
         @Environment(AppState.self) var appState
+        @Environment(SettingsSearchHighlight.self) var searchHighlight
 
 
         private var filteredItems: [FilteredSettingItem] {
         private var filteredItems: [FilteredSettingItem] {
             SettingItems.filteredItems(searchText: searchText)
             SettingItems.filteredItems(searchText: searchText)
@@ -284,15 +285,19 @@ extension Settings {
                         content: {
                         content: {
                             if filteredItems.isNotEmpty {
                             if filteredItems.isNotEmpty {
                                 ForEach(filteredItems) { filteredItem in
                                 ForEach(filteredItems) { filteredItem in
-                                    VStack(alignment: .leading) {
-                                        Text(filteredItem.matchedContent.localized).bold()
-                                        if let path = filteredItem.settingItem.path {
-                                            Text(path.map(\.localized).joined(separator: " > "))
-                                                .font(.caption)
-                                                .foregroundColor(.secondary)
+                                    NavigationLink(value: SearchResultTarget(
+                                        screen: filteredItem.settingItem.view,
+                                        scrollLabel: filteredItem.scrollLabel.localized
+                                    )) {
+                                        VStack(alignment: .leading) {
+                                            Text(filteredItem.matchedContent.localized).bold()
+                                            if let path = filteredItem.settingItem.path {
+                                                Text(path.map(\.localized).joined(separator: " > "))
+                                                    .font(.caption)
+                                                    .foregroundColor(.secondary)
+                                            }
                                         }
                                         }
-
-                                    }.navigationLink(to: filteredItem.settingItem.view, from: self)
+                                    }
                                 }
                                 }
                             } else {
                             } else {
                                 Text("No settings matching your search query")
                                 Text("No settings matching your search query")
@@ -339,6 +344,12 @@ extension Settings {
                 }
                 }
             }
             }
             .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
             .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
+            .navigationDestination(for: SearchResultTarget.self) { target in
+                state.view(for: target.screen)
+                    .onAppear {
+                        searchHighlight.highlightedSetting = target.scrollLabel
+                    }
+            }
             .screenNavigation(self)
             .screenNavigation(self)
             .onAppear {
             .onAppear {
                 Task { @MainActor in
                 Task { @MainActor in

+ 2 - 2
Trio/Sources/Modules/ShortcutsConfig/ShortcutsConfigStateModel.swift

@@ -16,7 +16,7 @@ extension ShortcutsConfig {
             units = settingsManager.settings.units
             units = settingsManager.settings.units
 
 
             subscribeSetting(\.bolusShortcut, on: $maxBolusByShortcuts) {
             subscribeSetting(\.bolusShortcut, on: $maxBolusByShortcuts) {
-                maxBolusByShortcuts = ($0 == .notAllowed) ? .limitBolusMax : $0
+                maxBolusByShortcuts = ($0 == .notAllowed) ? .limitWithSafetyChecks : $0
                 allowBolusByShortcuts = ($0 != .notAllowed)
                 allowBolusByShortcuts = ($0 != .notAllowed)
             }
             }
 
 
@@ -29,7 +29,7 @@ extension ShortcutsConfig {
                         if let bs = self?.maxBolusByShortcuts {
                         if let bs = self?.maxBolusByShortcuts {
                             self?.settingsManager.settings.bolusShortcut = bs
                             self?.settingsManager.settings.bolusShortcut = bs
                         } else {
                         } else {
-                            self?.settingsManager.settings.bolusShortcut = .limitBolusMax
+                            self?.settingsManager.settings.bolusShortcut = .limitWithSafetyChecks
                         }
                         }
                     }
                     }
                 }
                 }

+ 1 - 0
Trio/Sources/Modules/ShortcutsConfig/View/ShortcutsConfigView.swift

@@ -80,6 +80,7 @@ extension ShortcutsConfig {
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationTitle("Shortcuts")
             .navigationTitle("Shortcuts")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .settingsHighlightScroll()
         }
         }
     }
     }
 }
 }

+ 1 - 0
Trio/Sources/Modules/TargetBehavoir/View/TargetBehavoirRootView.swift

@@ -200,6 +200,7 @@ extension TargetBehavoir {
             }
             }
             .navigationTitle("Target Behavior")
             .navigationTitle("Target Behavior")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .settingsHighlightScroll()
         }
         }
 
 
         private var effectiveLowTTLowersSensBinding: Binding<Bool> {
         private var effectiveLowTTLowersSensBinding: Binding<Bool> {

+ 9 - 8
Trio/Sources/Modules/UserInterfaceSettings/View/UserInterfaceSettingsRootView.swift

@@ -96,7 +96,7 @@ extension UserInterfaceSettings {
                             }.padding(.top)
                             }.padding(.top)
                         }.padding(.bottom)
                         }.padding(.bottom)
                     }
                     }
-                ).listRowBackground(Color.chart)
+                ).settingsSearchTarget(label: String(localized: "Appearance"))
 
 
                 Section {
                 Section {
                     VStack {
                     VStack {
@@ -154,7 +154,7 @@ extension UserInterfaceSettings {
                             ).buttonStyle(BorderlessButtonStyle())
                             ).buttonStyle(BorderlessButtonStyle())
                         }.padding(.top)
                         }.padding(.top)
                     }.padding(.bottom)
                     }.padding(.bottom)
-                }.listRowBackground(Color.chart)
+                }.settingsSearchTarget(label: String(localized: "Glucose Color Scheme"))
 
 
                 Section(
                 Section(
                     header: Text("Home View Settings"),
                     header: Text("Home View Settings"),
@@ -189,7 +189,7 @@ extension UserInterfaceSettings {
                             }.padding(.top)
                             }.padding(.top)
                         }.padding(.vertical)
                         }.padding(.vertical)
                     }
                     }
-                ).listRowBackground(Color.chart)
+                ).settingsSearchTarget(label: String(localized: "Show X-Axis Grid Lines"))
 
 
                 SettingInputSection(
                 SettingInputSection(
                     decimalValue: $decimalPlaceholder,
                     decimalValue: $decimalPlaceholder,
@@ -319,7 +319,7 @@ extension UserInterfaceSettings {
                                 ).buttonStyle(BorderlessButtonStyle())
                                 ).buttonStyle(BorderlessButtonStyle())
                             }.padding(.top)
                             }.padding(.top)
                         }.padding(.bottom)
                         }.padding(.bottom)
-                    }.listRowBackground(Color.chart)
+                    }.settingsSearchTarget(label: String(localized: "Low Threshold"))
                 }
                 }
 
 
                 Section {
                 Section {
@@ -374,7 +374,7 @@ extension UserInterfaceSettings {
                             ).buttonStyle(BorderlessButtonStyle())
                             ).buttonStyle(BorderlessButtonStyle())
                         }.padding(.top)
                         }.padding(.top)
                     }.padding(.bottom)
                     }.padding(.bottom)
-                }.listRowBackground(Color.chart)
+                }.settingsSearchTarget(label: String(localized: "Forecast Display Type"))
 
 
                 Section {
                 Section {
                     VStack {
                     VStack {
@@ -416,7 +416,7 @@ extension UserInterfaceSettings {
                             ).buttonStyle(BorderlessButtonStyle())
                             ).buttonStyle(BorderlessButtonStyle())
                         }.padding(.top)
                         }.padding(.top)
                     }.padding(.bottom)
                     }.padding(.bottom)
-                }.listRowBackground(Color.chart)
+                }.settingsSearchTarget(label: String(localized: "Bolus Display Threshold"))
 
 
                 Section(
                 Section(
                     header: Text("Trio Statistics"),
                     header: Text("Trio Statistics"),
@@ -459,7 +459,7 @@ extension UserInterfaceSettings {
                             }.padding(.top)
                             }.padding(.top)
                         }.padding(.bottom)
                         }.padding(.bottom)
                     }
                     }
-                ).listRowBackground(Color.chart)
+                ).settingsSearchTarget(label: String(localized: "eA1c/GMI Display Unit"))
 
 
                 Section {
                 Section {
                     VStack(alignment: .leading) {
                     VStack(alignment: .leading) {
@@ -538,7 +538,7 @@ extension UserInterfaceSettings {
                             ).buttonStyle(BorderlessButtonStyle())
                             ).buttonStyle(BorderlessButtonStyle())
                         }.padding(.top)
                         }.padding(.top)
                     }.padding(.bottom)
                     }.padding(.bottom)
-                }.listRowBackground(Color.chart)
+                }.settingsSearchTarget(label: String(localized: "Time in Range Type"))
 
 
                 SettingInputSection(
                 SettingInputSection(
                     decimalValue: $state.carbsRequiredThreshold,
                     decimalValue: $state.carbsRequiredThreshold,
@@ -600,6 +600,7 @@ extension UserInterfaceSettings {
             .onAppear(perform: configureView)
             .onAppear(perform: configureView)
             .navigationBarTitle("User Interface")
             .navigationBarTitle("User Interface")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
+            .settingsHighlightScroll()
         }
         }
     }
     }
 }
 }

+ 1 - 0
Trio/Sources/Modules/WatchConfig/View/WatchConfigAppleWatchView.swift

@@ -68,6 +68,7 @@ struct WatchConfigAppleWatchView: BaseView {
         }
         }
         .navigationTitle("Apple Watch")
         .navigationTitle("Apple Watch")
         .navigationBarTitleDisplayMode(.automatic)
         .navigationBarTitleDisplayMode(.automatic)
+        .settingsHighlightScroll()
         .scrollContentBackground(.hidden)
         .scrollContentBackground(.hidden)
         .background(appState.trioBackgroundColor(for: colorScheme))
         .background(appState.trioBackgroundColor(for: colorScheme))
     }
     }

+ 106 - 0
Trio/Sources/Services/BolusSafety/BolusSafetyValidator.swift

@@ -0,0 +1,106 @@
+import CoreData
+import Foundation
+import Swinject
+
+/// Shared safety checks applied to any bolus command originating outside the main bolus UI
+/// (remote notifications, Shortcuts, etc.). Keeps validation logic consistent across call sites.
+protocol BolusSafetyValidator {
+    /// - Parameter lookbackStart: start of the window used for the recent-bolus 20% check.
+    ///   Defaults to `now - BolusSafetyEvaluator.recentBolusWindowMinutes`. Callers that know when the
+    ///   command was originally issued (e.g. APNS payload timestamp) should pass that instead so the
+    ///   check covers any bolus since the command was sent.
+    func validate(bolusAmount: Decimal, lookbackStart: Date?) async throws -> BolusSafetyResult
+    func fetchTotalRecentBolusAmount(since date: Date) async throws -> Decimal
+}
+
+extension BolusSafetyValidator {
+    func validate(bolusAmount: Decimal) async throws -> BolusSafetyResult {
+        try await validate(bolusAmount: bolusAmount, lookbackStart: nil)
+    }
+}
+
+enum BolusSafetyResult: Equatable {
+    case allowed
+    case rejected(BolusSafetyRejection)
+}
+
+enum BolusSafetyRejection: Equatable {
+    case exceedsMaxBolus(maxBolus: Decimal)
+    case iobUnavailable
+    case exceedsMaxIOB(currentIOB: Decimal, maxIOB: Decimal)
+    case recentBolusWithinWindow(totalRecent: Decimal)
+}
+
+struct BolusSafetyInputs: Equatable {
+    let maxBolus: Decimal
+    let maxIOB: Decimal
+    let currentIOB: Decimal?
+    /// Sum of bolus amounts delivered within the recent-bolus window (see `BolusSafetyEvaluator.recentBolusWindowMinutes`).
+    let totalRecentBolus: Decimal
+}
+
+enum BolusSafetyEvaluator {
+    static let recentBolusThreshold: Decimal = 0.2
+    static let recentBolusWindowMinutes: Int = 6
+
+    static func evaluate(bolusAmount: Decimal, inputs: BolusSafetyInputs) -> BolusSafetyResult {
+        if bolusAmount > inputs.maxBolus {
+            return .rejected(.exceedsMaxBolus(maxBolus: inputs.maxBolus))
+        }
+        guard let currentIOB = inputs.currentIOB else {
+            return .rejected(.iobUnavailable)
+        }
+        if (currentIOB + bolusAmount) > inputs.maxIOB {
+            return .rejected(.exceedsMaxIOB(currentIOB: currentIOB, maxIOB: inputs.maxIOB))
+        }
+        if inputs.totalRecentBolus >= bolusAmount * recentBolusThreshold {
+            return .rejected(.recentBolusWithinWindow(totalRecent: inputs.totalRecentBolus))
+        }
+        return .allowed
+    }
+}
+
+final class BaseBolusSafetyValidator: BolusSafetyValidator, Injectable {
+    @Injected() private var settingsManager: SettingsManager!
+    @Injected() private var iobService: IOBService!
+
+    private let fetchContext: NSManagedObjectContext
+
+    init(resolver: Resolver) {
+        fetchContext = CoreDataStack.shared.newTaskContext()
+        injectServices(resolver)
+    }
+
+    func validate(bolusAmount: Decimal, lookbackStart: Date?) async throws -> BolusSafetyResult {
+        let windowStart = lookbackStart
+            ?? Date().addingTimeInterval(-Double(BolusSafetyEvaluator.recentBolusWindowMinutes * 60))
+        let inputs = BolusSafetyInputs(
+            maxBolus: settingsManager.pumpSettings.maxBolus,
+            maxIOB: settingsManager.preferences.maxIOB,
+            currentIOB: iobService.currentIOB,
+            totalRecentBolus: try await fetchTotalRecentBolusAmount(since: windowStart)
+        )
+        return BolusSafetyEvaluator.evaluate(bolusAmount: bolusAmount, inputs: inputs)
+    }
+
+    func fetchTotalRecentBolusAmount(since date: Date) async throws -> Decimal {
+        let predicate = NSPredicate(
+            format: "type == %@ AND timestamp > %@",
+            PumpEventStored.EventType.bolus.rawValue,
+            date as NSDate
+        )
+        let results: Any = try await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: PumpEventStored.self,
+            onContext: fetchContext,
+            predicate: predicate,
+            key: "timestamp",
+            ascending: true,
+            fetchLimit: nil,
+            propertiesToFetch: ["bolus.amount"]
+        )
+        guard let bolusDictionaries = results as? [[String: Any]] else {
+            throw CoreDataError.fetchError(function: #function, file: #file)
+        }
+        return bolusDictionaries.compactMap { ($0["bolus.amount"] as? NSNumber)?.decimalValue }.reduce(0, +)
+    }
+}

+ 22 - 45
Trio/Sources/Services/RemoteControl/TrioRemoteControl+Bolus.swift

@@ -2,42 +2,22 @@ import Foundation
 import HealthKit
 import HealthKit
 
 
 extension TrioRemoteControl {
 extension TrioRemoteControl {
-    internal func handleBolusCommand(_ payload: CommandPayload) async throws {
+    func handleBolusCommand(_ payload: CommandPayload) async throws {
         guard let bolusAmount = payload.bolusAmount else {
         guard let bolusAmount = payload.bolusAmount else {
             await logError("Command rejected: bolus amount is missing or invalid.", payload: payload)
             await logError("Command rejected: bolus amount is missing or invalid.", payload: payload)
             return
             return
         }
         }
 
 
-        let maxBolus = await TrioApp.resolver.resolve(SettingsManager.self)?.pumpSettings.maxBolus ?? Decimal(0)
-
-        if bolusAmount > maxBolus {
-            await logError(
-                "Command rejected: bolus amount (\(bolusAmount) units) exceeds the maximum allowed (\(maxBolus) units).",
-                payload: payload
-            )
-            return
-        }
-
-        let maxIOB = settings.preferences.maxIOB
-        guard let currentIOB = iobService.currentIOB else {
-            throw CoreDataError.fetchError(function: #function, file: #file)
-        }
-        if (currentIOB + bolusAmount) > maxIOB {
-            await logError(
-                "Command rejected: bolus amount (\(bolusAmount) units) would exceed max IOB (\(maxIOB) units). Current IOB: \(currentIOB) units.",
-                payload: payload
-            )
-            return
-        }
-
-        let totalRecentBolusAmount =
-            try await fetchTotalRecentBolusAmount(since: Date(timeIntervalSince1970: payload.timestamp))
+        let validation = try await bolusSafetyValidator.validate(
+            bolusAmount: bolusAmount,
+            lookbackStart: Date(timeIntervalSince1970: payload.timestamp)
+        )
 
 
-        if totalRecentBolusAmount >= bolusAmount * 0.2 {
-            await logError(
-                "Command rejected: boluses totaling more than 20% of the requested amount have been delivered since the command was sent.",
-                payload: payload
-            )
+        switch validation {
+        case .allowed:
+            break
+        case let .rejected(reason):
+            await logError(reason.remoteCommandMessage(bolusAmount: bolusAmount), payload: payload)
             return
             return
         }
         }
 
 
@@ -79,22 +59,19 @@ extension TrioRemoteControl {
                 }
                 }
             }
             }
     }
     }
+}
 
 
-    private func fetchTotalRecentBolusAmount(since date: Date) async throws -> Decimal {
-        let predicate = NSPredicate(
-            format: "type == %@ AND timestamp > %@",
-            PumpEventStored.EventType.bolus.rawValue,
-            date as NSDate
-        )
-        let results: Any = try await CoreDataStack.shared.fetchEntitiesAsync(
-            ofType: PumpEventStored.self, onContext: pumpHistoryFetchContext, predicate: predicate, key: "timestamp",
-            ascending: true, fetchLimit: nil, propertiesToFetch: ["bolus.amount"]
-        )
-        guard let bolusDictionaries = results as? [[String: Any]] else {
-            await logError("Failed to cast fetched bolus events. Fetched entities type: \(type(of: results))")
-            throw CoreDataError.fetchError(function: #function, file: #file)
+private extension BolusSafetyRejection {
+    func remoteCommandMessage(bolusAmount: Decimal) -> String {
+        switch self {
+        case let .exceedsMaxBolus(maxBolus):
+            return "Command rejected: bolus amount (\(bolusAmount) units) exceeds the maximum allowed (\(maxBolus) units)."
+        case .iobUnavailable:
+            return "Command rejected: current IOB is not available."
+        case let .exceedsMaxIOB(currentIOB, maxIOB):
+            return "Command rejected: bolus amount (\(bolusAmount) units) would exceed max IOB (\(maxIOB) units). Current IOB: \(currentIOB) units."
+        case .recentBolusWithinWindow:
+            return "Command rejected: boluses totaling more than 20% of the requested amount have been delivered since the command was sent."
         }
         }
-        let totalAmount = bolusDictionaries.compactMap { ($0["bolus.amount"] as? NSNumber)?.decimalValue }.reduce(0, +)
-        return totalAmount
     }
     }
 }
 }

+ 1 - 3
Trio/Sources/Services/RemoteControl/TrioRemoteControl.swift

@@ -10,15 +10,13 @@ class TrioRemoteControl: Injectable {
     @Injected() internal var nightscoutManager: NightscoutManager!
     @Injected() internal var nightscoutManager: NightscoutManager!
     @Injected() internal var overrideStorage: OverrideStorage!
     @Injected() internal var overrideStorage: OverrideStorage!
     @Injected() internal var settings: SettingsManager!
     @Injected() internal var settings: SettingsManager!
-    @Injected() internal var iobService: IOBService!
+    @Injected() internal var bolusSafetyValidator: BolusSafetyValidator!
 
 
     private let timeWindow: TimeInterval = 600
     private let timeWindow: TimeInterval = 600
 
 
-    internal let pumpHistoryFetchContext: NSManagedObjectContext
     internal let viewContext: NSManagedObjectContext
     internal let viewContext: NSManagedObjectContext
 
 
     private init() {
     private init() {
-        pumpHistoryFetchContext = CoreDataStack.shared.newTaskContext()
         viewContext = CoreDataStack.shared.persistentContainer.viewContext
         viewContext = CoreDataStack.shared.persistentContainer.viewContext
         injectServices(TrioApp.resolver)
         injectServices(TrioApp.resolver)
     }
     }

+ 71 - 7
Trio/Sources/Services/Telemetry/TelemetryClient.swift

@@ -1,4 +1,5 @@
 import Foundation
 import Foundation
+import HealthKit
 import LoopKit
 import LoopKit
 import Swinject
 import Swinject
 import UIKit
 import UIKit
@@ -20,6 +21,11 @@ final class TelemetryClient: Injectable {
 
 
     private static let productionBaseURL: URL? = URL(string: "https://telemetry.triodocs.org")
     private static let productionBaseURL: URL? = URL(string: "https://telemetry.triodocs.org")
 
 
+    // MARK: if you fork Trio and keep telemetry enabled, please change the name here
+
+    // so that we can distinguish forks from mainline Trio builds in our telemetry.
+    private static let telemetryAppName: String = "Trio"
+
     /// Effective base URL: respects the debug override in
     /// Effective base URL: respects the debug override in
     /// `PropertyPersistentFlags.telemetryDebugServerURL`, then falls back to
     /// `PropertyPersistentFlags.telemetryDebugServerURL`, then falls back to
     /// `productionBaseURL`. Used by both the registration and `/checkin` paths.
     /// `productionBaseURL`. Used by both the registration and `/checkin` paths.
@@ -38,6 +44,14 @@ final class TelemetryClient: Injectable {
     private static let dailyInterval: TimeInterval = 24 * 60 * 60
     private static let dailyInterval: TimeInterval = 24 * 60 * 60
     private static let maxPayloadBytes = 4096
     private static let maxPayloadBytes = 4096
 
 
+    private static let buildDateFormatter: DateFormatter = {
+        let f = DateFormatter()
+        f.dateFormat = "yyyy-MM-dd"
+        f.locale = Locale(identifier: "en_US_POSIX")
+        f.timeZone = TimeZone(identifier: "UTC")
+        return f
+    }()
+
     // MARK: Injected services
     // MARK: Injected services
 
 
     @Injected() private var apsManager: APSManager!
     @Injected() private var apsManager: APSManager!
@@ -103,6 +117,12 @@ final class TelemetryClient: Injectable {
     /// Arms (or re-arms) the 24h send timer. Idempotent. Bails out without
     /// Arms (or re-arms) the 24h send timer. Idempotent. Bails out without
     /// scheduling if the user hasn't decided on consent yet or has opted out
     /// scheduling if the user hasn't decided on consent yet or has opted out
     /// — there's nothing for the timer to do.
     /// — there's nothing for the timer to do.
+    ///
+    /// Best-effort fallback only. GCD timers don't advance while the app is
+    /// suspended, so on iOS this effectively means "fires only if the app
+    /// stays foregrounded for 24h." The reliable cadence driver is
+    /// `checkAndSendIfOverdue()` called on every foreground transition and
+    /// cold launch.
     func scheduleRecurring() {
     func scheduleRecurring() {
         guard PropertyPersistentFlags.shared.telemetryConsentDecisionMade == true,
         guard PropertyPersistentFlags.shared.telemetryConsentDecisionMade == true,
               PropertyPersistentFlags.shared.telemetryEnabled == true
               PropertyPersistentFlags.shared.telemetryEnabled == true
@@ -123,6 +143,31 @@ final class TelemetryClient: Injectable {
         }
         }
     }
     }
 
 
+    /// If consent is set and we haven't successfully sent within the last 24h
+    /// (or have never sent), fire a send. Called on foreground transitions
+    /// and from the cold-launch path so daily cadence is kept.
+    ///
+    /// Mirrors the pattern used by LoopFollow's `TaskScheduler.checkTasksNow()`:
+    /// wall-clock comparison against `telemetryLastSentAt`, fire-and-forget
+    /// if overdue. Safe to call repeatedly — if a send already fired within
+    /// the window, this is a no-op.
+    func checkAndSendIfOverdue() {
+        guard PropertyPersistentFlags.shared.telemetryConsentDecisionMade == true,
+              PropertyPersistentFlags.shared.telemetryEnabled == true
+        else {
+            return
+        }
+
+        let lastSent = PropertyPersistentFlags.shared.telemetryLastSentAt
+        let overdue: Bool = {
+            guard let lastSent else { return true }
+            return Date().timeIntervalSince(lastSent) >= Self.dailyInterval
+        }()
+        guard overdue else { return }
+
+        Task.detached { await self.maybeSend() }
+    }
+
     /// Single entry point for all sends (scheduler tick, consent-yes, startup
     /// Single entry point for all sends (scheduler tick, consent-yes, startup
     /// SHA-change). Gated on consent + opt-in. *When* to send is the caller's
     /// SHA-change). Gated on consent + opt-in. *When* to send is the caller's
     /// decision — startup handles the SHA-change shortcut, the timer handles
     /// decision — startup handles the SHA-change shortcut, the timer handles
@@ -149,6 +194,7 @@ final class TelemetryClient: Injectable {
         var payload: [String: Any] = [:]
         var payload: [String: Any] = [:]
 
 
         if let v = info["CFBundleShortVersionString"] as? String { payload["appVersion"] = v }
         if let v = info["CFBundleShortVersionString"] as? String { payload["appVersion"] = v }
+        payload["appName"] = TelemetryClient.telemetryAppName
         // appDevVersion is Trio's 4-component dev counter (e.g. "0.7.0.14") —
         // appDevVersion is Trio's 4-component dev counter (e.g. "0.7.0.14") —
         // the most precise build identifier we have. Always emit, even when
         // the most precise build identifier we have. Always emit, even when
         // the Info.plist key is missing, so dashboards can rely on the field.
         // the Info.plist key is missing, so dashboards can rely on the field.
@@ -156,10 +202,10 @@ final class TelemetryClient: Injectable {
         payload["commitSha"] = bd.trioCommitSHA
         payload["commitSha"] = bd.trioCommitSHA
         payload["branch"] = bd.trioBranch
         payload["branch"] = bd.trioBranch
 
 
-        // Date-only prefix of the build-date string. Keeps the field a
-        // low-resolution build identifier, not a precise timestamp.
-        if let raw = bd.buildDateString, raw.count >= 10 {
-            payload["buildDate"] = String(raw.prefix(10))
+        // Date-only (yyyy-MM-dd, UTC) build identifier, parsed from the
+        // "Tue May 26 12:34:56 UTC 2025" form added in BuildDetails.plist.
+        if let date = bd.buildDate() {
+            payload["buildDate"] = Self.buildDateFormatter.string(from: date)
         }
         }
 
 
         payload["isTestFlight"] = bd.isTestFlightBuild()
         payload["isTestFlight"] = bd.isTestFlightBuild()
@@ -172,6 +218,8 @@ final class TelemetryClient: Injectable {
         payload["device"] = Self.hardwareIdentifier()
         payload["device"] = Self.hardwareIdentifier()
         payload["platform"] = Self.detectPlatform()
         payload["platform"] = Self.detectPlatform()
         payload["osVersion"] = UIDevice.current.systemVersion
         payload["osVersion"] = UIDevice.current.systemVersion
+        payload["locale"] = Locale.current.identifier
+        payload["timeZone"] = TimeZone.current.identifier
 
 
         // Pump model — omitted entirely when no pump is paired.
         // Pump model — omitted entirely when no pump is paired.
         if let pump = apsManager?.pumpManager {
         if let pump = apsManager?.pumpManager {
@@ -197,9 +245,25 @@ final class TelemetryClient: Injectable {
 
 
         payload["tidepoolPaired"] = tidepoolManager?.getTidepoolServiceUI() != nil
         payload["tidepoolPaired"] = tidepoolManager?.getTidepoolServiceUI() != nil
 
 
-        let useHealth = settings?.useAppleHealth ?? false
-        let healthAuthorized = healthKitManager?.hasGrantedFullWritePermissions ?? false
-        payload["appleHealthEnabled"] = useHealth && healthAuthorized
+        // Apple Health: report `enabled = true` as soon as *any* per-type write
+        // permission is granted, with the full per-type breakdown in
+        // `appleHealthWrites`.
+        let appleHealthSampleTypes: [(name: String, type: HKObjectType?)] = [
+            ("glucose", AppleHealthConfig.healthBGObject),
+            ("insulin", AppleHealthConfig.healthInsulinObject),
+            ("carbs", AppleHealthConfig.healthCarbObject),
+            ("fat", AppleHealthConfig.healthFatObject),
+            ("protein", AppleHealthConfig.healthProteinObject)
+        ]
+        var writePermissions: [String: Bool] = [:]
+        for (name, type) in appleHealthSampleTypes {
+            let granted = type.flatMap { healthKitManager?.checkWriteToHealthPermissions(objectTypeToHealthStore: $0) } ?? false
+            writePermissions[name] = granted
+        }
+        payload["appleHealthEnabled"] = writePermissions.values.contains(true)
+        if !writePermissions.isEmpty {
+            payload["appleHealthWrites"] = writePermissions
+        }
 
 
         if let settings = settings {
         if let settings = settings {
             payload["closedLoop"] = settings.closedLoop
             payload["closedLoop"] = settings.closedLoop

+ 1 - 0
Trio/Sources/Shortcuts/BaseIntentsRequest.swift

@@ -17,6 +17,7 @@ import Swinject
     @Injected() var liveActivityManager: LiveActivityManager!
     @Injected() var liveActivityManager: LiveActivityManager!
     @Injected() var pumpHistoryStorage: PumpHistoryStorage!
     @Injected() var pumpHistoryStorage: PumpHistoryStorage!
     @Injected() var iobService: IOBService!
     @Injected() var iobService: IOBService!
+    @Injected() var bolusSafetyValidator: BolusSafetyValidator!
 
 
     let resolver: Resolver
     let resolver: Resolver
 
 

+ 37 - 10
Trio/Sources/Shortcuts/Bolus/BolusIntentRequest.swift

@@ -4,25 +4,25 @@ import Foundation
 
 
 final class BolusIntentRequest: BaseIntentsRequest {
 final class BolusIntentRequest: BaseIntentsRequest {
     func bolus(_ bolusAmount: Double) async throws -> String {
     func bolus(_ bolusAmount: Double) async throws -> String {
-        var bolusQuantity: Decimal = 0
         switch settingsManager.settings.bolusShortcut {
         switch settingsManager.settings.bolusShortcut {
-        // Block boluses if they are disabled
         case .notAllowed:
         case .notAllowed:
             return String(
             return String(
                 localized:
                 localized:
                 "Bolusing via Shortcuts is disabled in Trio settings."
                 "Bolusing via Shortcuts is disabled in Trio settings."
             )
             )
 
 
-        // Block any bolus attempted if it is larger than the max bolus in settings
-        case .limitBolusMax:
-            if Decimal(bolusAmount) > settingsManager.pumpSettings.maxBolus {
-                return String(
-                    localized:
-                    "The bolus cannot be larger than the pump setting max bolus (\(settingsManager.pumpSettings.maxBolus.description))."
+        case .limitWithSafetyChecks:
+            let requestedAmount = Decimal(bolusAmount)
+            let validation = try await bolusSafetyValidator.validate(bolusAmount: requestedAmount)
+
+            if case let .rejected(reason) = validation {
+                return reason.shortcutMessage(
+                    requestedAmount: requestedAmount,
+                    pumpMaxBolus: settingsManager.pumpSettings.maxBolus
                 )
                 )
-            } else {
-                bolusQuantity = apsManager.roundBolus(amount: Decimal(bolusAmount))
             }
             }
+
+            let bolusQuantity = apsManager.roundBolus(amount: requestedAmount)
             await apsManager.enactBolus(amount: Double(bolusQuantity), isSMB: false, callback: nil)
             await apsManager.enactBolus(amount: Double(bolusQuantity), isSMB: false, callback: nil)
             return String(
             return String(
                 localized:
                 localized:
@@ -52,3 +52,30 @@ final class BolusIntentRequest: BaseIntentsRequest {
         }
         }
     }
     }
 }
 }
+
+private extension BolusSafetyRejection {
+    func shortcutMessage(requestedAmount: Decimal, pumpMaxBolus: Decimal) -> String {
+        switch self {
+        case .exceedsMaxBolus:
+            return String(
+                localized:
+                "The bolus cannot be larger than the pump setting max bolus (\(pumpMaxBolus.description))."
+            )
+        case .iobUnavailable:
+            return String(
+                localized:
+                "Bolus blocked: current IOB is not available."
+            )
+        case let .exceedsMaxIOB(currentIOB, maxIOB):
+            return String(
+                localized:
+                "Bolus blocked: a \(requestedAmount.formatted()) U bolus would exceed max IOB (\(maxIOB.formatted()) U). Current IOB: \(currentIOB.formatted()) U."
+            )
+        case .recentBolusWithinWindow:
+            return String(
+                localized:
+                "Bolus blocked: a significant bolus was delivered within the last \(BolusSafetyEvaluator.recentBolusWindowMinutes) minutes."
+            )
+        }
+    }
+}

+ 2 - 1
Trio/Sources/Views/SettingInputSection.swift

@@ -83,7 +83,8 @@ struct SettingInputSection<VerboseHint: View>: View {
             },
             },
             header: { headerText.map(Text.init) },
             header: { headerText.map(Text.init) },
             footer: { footerText.map(Text.init) }
             footer: { footerText.map(Text.init) }
-        ).listRowBackground(Color.chart)
+        )
+        .settingsSearchTarget(label: label)
     }
     }
 
 
     // Helper function to retrieve PickerSetting based on key
     // Helper function to retrieve PickerSetting based on key

+ 73 - 0
Trio/Sources/Views/SettingsSearchHighlight.swift

@@ -0,0 +1,73 @@
+import SwiftUI
+
+@MainActor @Observable final class SettingsSearchHighlight {
+    var highlightedSetting: String?
+}
+
+/// Wraps a Screen value with the scroll-target label for search-result navigation.
+struct SearchResultTarget: Hashable {
+    let screen: Screen
+    let scrollLabel: String
+}
+
+private struct SettingsHighlightScrollModifier: ViewModifier {
+    @Environment(SettingsSearchHighlight.self) private var searchHighlight
+
+    func body(content: Content) -> some View {
+        ScrollViewReader { proxy in
+            content
+                .task(id: searchHighlight.highlightedSetting) {
+                    guard let target = searchHighlight.highlightedSetting else { return }
+                    try? await Task.sleep(for: .milliseconds(500))
+                    guard !Task.isCancelled else { return }
+                    withAnimation { proxy.scrollTo(target, anchor: .center) }
+                }
+        }
+    }
+}
+
+private struct SettingsSearchHighlightAnimationModifier: ViewModifier {
+    let label: String
+    @Environment(SettingsSearchHighlight.self) private var searchHighlight
+    @State private var highlightOpacity: Double = 0.0
+
+    func body(content: Content) -> some View {
+        content
+            .listRowBackground(
+                Color.chart.overlay(Color.accentColor.opacity(highlightOpacity))
+                    .animation(.easeOut(duration: 1.2), value: highlightOpacity)
+            )
+            .onAppear {
+                guard searchHighlight.highlightedSetting == label else { return }
+                startHighlightAnimation()
+            }
+            .onChange(of: searchHighlight.highlightedSetting) { _, newValue in
+                guard newValue == label else { return }
+                startHighlightAnimation()
+            }
+    }
+
+    private func startHighlightAnimation() {
+        Task { @MainActor in
+            try? await Task.sleep(for: .milliseconds(500))
+            highlightOpacity = 0.6
+            try? await Task.sleep(for: .milliseconds(800))
+            searchHighlight.highlightedSetting = nil
+            highlightOpacity = 0.0
+        }
+    }
+}
+
+extension View {
+    /// Enables scroll-to-highlight on a settings screen. Add once per destination view.
+    func settingsHighlightScroll() -> some View {
+        modifier(SettingsHighlightScrollModifier())
+    }
+
+    /// Marks a section as a scroll-to and highlight target for settings search.
+    /// Combines `.id(label)` with a highlight flash animation in a single call.
+    func settingsSearchTarget(label: String) -> some View {
+        id(label)
+            .modifier(SettingsSearchHighlightAnimationModifier(label: label))
+    }
+}

+ 113 - 0
TrioTests/BolusSafetyTests/BolusSafetyValidatorTests.swift

@@ -0,0 +1,113 @@
+import Foundation
+import Testing
+
+@testable import Trio
+
+@Suite("Bolus Safety Validator Tests") struct BolusSafetyValidatorTests: Injectable {
+    @Injected() var validator: BolusSafetyValidator!
+    let resolver = TrioApp().resolver
+
+    init() {
+        injectServices(resolver)
+    }
+
+    @Test("Validator resolves from the service container") func testValidatorResolves() {
+        #expect(validator != nil, "BolusSafetyValidator should be registered in ServiceAssembly")
+        #expect(validator is BaseBolusSafetyValidator, "Validator should be of type BaseBolusSafetyValidator")
+    }
+
+    @Test("Allows bolus when all inputs are within limits") func testAllowed() {
+        let inputs = BolusSafetyInputs(
+            maxBolus: 10,
+            maxIOB: 10,
+            currentIOB: 1,
+            totalRecentBolus: 0
+        )
+        #expect(BolusSafetyEvaluator.evaluate(bolusAmount: 5, inputs: inputs) == .allowed)
+    }
+
+    @Test("Rejects when amount exceeds max bolus") func testExceedsMaxBolus() {
+        let inputs = BolusSafetyInputs(
+            maxBolus: 5,
+            maxIOB: 10,
+            currentIOB: 0,
+            totalRecentBolus: 0
+        )
+        let result = BolusSafetyEvaluator.evaluate(bolusAmount: 6, inputs: inputs)
+        #expect(result == .rejected(.exceedsMaxBolus(maxBolus: 5)))
+    }
+
+    @Test("Rejects when current IOB is unavailable") func testIOBUnavailable() {
+        let inputs = BolusSafetyInputs(
+            maxBolus: 10,
+            maxIOB: 10,
+            currentIOB: nil,
+            totalRecentBolus: 0
+        )
+        let result = BolusSafetyEvaluator.evaluate(bolusAmount: 1, inputs: inputs)
+        #expect(result == .rejected(.iobUnavailable))
+    }
+
+    @Test("Rejects when amount would exceed max IOB") func testExceedsMaxIOB() {
+        let inputs = BolusSafetyInputs(
+            maxBolus: 10,
+            maxIOB: 5,
+            currentIOB: 3,
+            totalRecentBolus: 0
+        )
+        let result = BolusSafetyEvaluator.evaluate(bolusAmount: 2.5, inputs: inputs)
+        #expect(result == .rejected(.exceedsMaxIOB(currentIOB: 3, maxIOB: 5)))
+    }
+
+    @Test("Rejects when recent bolus totals >= 20% of requested amount") func testRecentBolusWithinWindow() {
+        let inputs = BolusSafetyInputs(
+            maxBolus: 10,
+            maxIOB: 10,
+            currentIOB: 0,
+            totalRecentBolus: 1.0
+        )
+        let result = BolusSafetyEvaluator.evaluate(bolusAmount: 5, inputs: inputs)
+        #expect(result == .rejected(.recentBolusWithinWindow(totalRecent: 1.0)))
+    }
+
+    @Test("Allows when recent bolus total is below 20% threshold") func testRecentBolusBelowThreshold() {
+        let inputs = BolusSafetyInputs(
+            maxBolus: 10,
+            maxIOB: 10,
+            currentIOB: 0,
+            totalRecentBolus: 0.99
+        )
+        #expect(BolusSafetyEvaluator.evaluate(bolusAmount: 5, inputs: inputs) == .allowed)
+    }
+
+    @Test("Max bolus check runs before IOB check") func testCheckOrdering() {
+        let inputs = BolusSafetyInputs(
+            maxBolus: 5,
+            maxIOB: 10,
+            currentIOB: nil,
+            totalRecentBolus: 0
+        )
+        let result = BolusSafetyEvaluator.evaluate(bolusAmount: 6, inputs: inputs)
+        #expect(result == .rejected(.exceedsMaxBolus(maxBolus: 5)))
+    }
+
+    @Test("Equal-to-max-bolus is allowed") func testEqualsMaxBolus() {
+        let inputs = BolusSafetyInputs(
+            maxBolus: 5,
+            maxIOB: 10,
+            currentIOB: 0,
+            totalRecentBolus: 0
+        )
+        #expect(BolusSafetyEvaluator.evaluate(bolusAmount: 5, inputs: inputs) == .allowed)
+    }
+
+    @Test("Current IOB plus amount equal to max IOB is allowed") func testEqualToMaxIOB() {
+        let inputs = BolusSafetyInputs(
+            maxBolus: 10,
+            maxIOB: 5,
+            currentIOB: 3,
+            totalRecentBolus: 0
+        )
+        #expect(BolusSafetyEvaluator.evaluate(bolusAmount: 2, inputs: inputs) == .allowed)
+    }
+}

+ 69 - 0
TrioTests/SettingsSearchTests.swift

@@ -0,0 +1,69 @@
+import Foundation
+import Testing
+@testable import Trio
+
+@Suite("Settings Search Navigation") struct SettingsSearchTests {
+    @Test("Searching 'Dynamic ISF' finds the Dynamic Settings screen") func searchDynamicISF() {
+        let results = SettingItems.filteredItems(searchText: "Dynamic ISF")
+        #expect(!results.isEmpty)
+        let match = results.first { $0.matchedContent == "Dynamic ISF" }
+        #expect(match != nil)
+        #expect(match?.settingItem.view == .dynamicISF)
+        #expect(match?.scrollLabel == "Dynamic ISF")
+    }
+
+    @Test("All scrollTargetLabels have valid non-empty targets") func scrollTargetLabelsNonEmpty() {
+        for item in SettingItems.allItems {
+            guard let labels = item.scrollTargetLabels else { continue }
+            for (key, value) in labels {
+                #expect(!value.isEmpty)
+                #expect(item.searchContents?.contains(key) == true)
+            }
+        }
+    }
+
+    @Test("Every searchContents entry produces at least one result") func allSearchContentsAreSearchable() {
+        for item in SettingItems.allItems {
+            guard let contents = item.searchContents else { continue }
+            for content in contents {
+                let results = SettingItems.filteredItems(searchText: content)
+                #expect(!results.isEmpty)
+            }
+        }
+    }
+
+    @Test("SearchResultTarget is Hashable and equatable by value") func searchResultTargetHashable() {
+        let a = SearchResultTarget(screen: .dynamicISF, scrollLabel: "Dynamic ISF")
+        let b = SearchResultTarget(screen: .dynamicISF, scrollLabel: "Dynamic ISF")
+        let c = SearchResultTarget(screen: .dynamicISF, scrollLabel: "Adjust Basal")
+        #expect(a == b)
+        #expect(a != c)
+        #expect(a.hashValue == b.hashValue)
+    }
+
+    @Test("SettingsSearchHighlight starts nil and accepts assignments")
+    @MainActor func highlightStateTransitions() {
+        let highlight = SettingsSearchHighlight()
+        #expect(highlight.highlightedSetting == nil)
+
+        highlight.highlightedSetting = "Dynamic ISF"
+        #expect(highlight.highlightedSetting == "Dynamic ISF")
+
+        highlight.highlightedSetting = nil
+        #expect(highlight.highlightedSetting == nil)
+    }
+
+    @Test("SettingsSearchHighlight can be set and cleared in sequence")
+    @MainActor func highlightSequentialUpdates() async {
+        let highlight = SettingsSearchHighlight()
+
+        highlight.highlightedSetting = "First Setting"
+        #expect(highlight.highlightedSetting == "First Setting")
+
+        highlight.highlightedSetting = "Second Setting"
+        #expect(highlight.highlightedSetting == "Second Setting")
+
+        highlight.highlightedSetting = nil
+        #expect(highlight.highlightedSetting == nil)
+    }
+}

+ 1 - 1
scripts/capture-build-details.sh

@@ -16,7 +16,7 @@ fi
 echo "Gathering build details..."
 echo "Gathering build details..."
 
 
 # Capture the current date
 # Capture the current date
-plutil -replace com-trio-build-date -string "$(date -u '+%a %b %e %H:%M:%S UTC %Y')" "${info_plist_path}"
+plutil -replace com-trio-build-date -string "$(LC_ALL=C date -u '+%a %b %e %H:%M:%S UTC %Y')" "${info_plist_path}"
 
 
 # --- Root repo details ---
 # --- Root repo details ---
 # Retrieve current branch (or tag) and commit SHA.
 # Retrieve current branch (or tag) and commit SHA.

+ 2 - 1
scripts/swiftformat.sh

@@ -112,4 +112,5 @@ trailingClosures \
   MinimedKit, \
   MinimedKit, \
   TidepoolService, \
   TidepoolService, \
   DanaKit, \
   DanaKit, \
-  MedtrumKit
+  MedtrumKit, \
+  OmnipodKit