Tidepool sync 2026-05-11#2438
Conversation
…date UI (LoopKit#749) * if delivery states are not changed, still update UI * set refresh context before reloading
…ntifiers-for-new-test [QAE-446] Add identifiers for new tests
* Preset editing * Preset editing updates * Add duration editing * Add duration for legacy workout override
…ntifiers-for-UI-tests [QAE-451] Add identifier for Presets button
…was manually edited (LoopKit#757)
# Conflicts: # Loop/Managers/Live Activity/LiveActivityManager.swift
Without this, automatic temp basal changes, no-op Loop cycles, and manual temp basal cancellations never produce the "updateRemoteRecommendation" dosing decision that NightscoutService pairs with the cached "loop" decision to upload Loop pill + forecast. Add a force parameter to updateRemoteRecommendation() that bypasses the "manual bolus rec changed" gate, forwarded through updateDisplayState as forceStoreRemoteRecommendation. Call updateDisplayState(force: true) at the end of loop() (success and error branches) and cancelActiveTempBasal(). DIY divergence from tidepool/Loop. See memory/divergence_ns_post_dose_decision.md. Original NS pairing logic introduced in NightscoutService commit 08abe274 (2022-07-18) assumed a separate post-dose decision; later refactoring consolidated it into the "loop" decision, breaking the pairing for non-bolus paths.
ResizeablePicker was the only cross-module use of LoopKitUI's ResizeablePicker (which Tidepool keeps internal-only). Replacing it with a standard Picker(.wheel) here means LoopKit no longer needs DIY-only public modifiers on ResizeablePicker — eliminating a small but ongoing DIY divergence in LoopKitUI.
Bug: lock-screen Live Activity preview was showing raw Swift debug
output like 'Optional(LoopKit.PresetSymbol(symbolType: ..., value:
"figure.outdoor.cycle"))' above the preset name, because
TemporaryScheduleOverride.getTitle() was interpolating preset.symbol
directly:
return "\(preset.symbol) \(preset.name)"
preset.symbol is Optional<PresetSymbol> (a struct introduced by the
2026-03-10 tidepool sync — wraps an SF Symbol name with a tint), not a
String, so Swift's default Optional interpolation produced the debug
dump.
Fix:
- Add iconSystemSymbolName: String? to the Preset Codable struct in
GlucoseActivityAttributes (with backward-compatible decoder).
- Replace getTitle() -> String with liveActivityTitleAndSymbol() ->
(title, systemSymbolName?). Emoji symbols are folded inline into the
title (they render fine as text); .systemImage symbols are returned as
a separate SF Symbol name; .image (asset) symbols can't be loaded from
the widget bundle so we render the name without an icon. Pre-meal
preset also gets a fork.knife icon.
- In ChartView, render the preset label as
Text(Image(systemName: iconSystemSymbolName)) + Text(" ") + Text(title)
when a symbol is present, else just Text(title).
… y-axis labels The chart's y-axis labels (175/150/125/100) render on the right side of the plot, but the preset-name label (e.g. '🚴 Biking') was also at top-trailing, so they crowded the same top-right corner. Switched the ZStack alignment to .leading and updated the label padding from trailing→leading so the preset sits in the empty top-left area.
Use .chartPlotStyle's overlay (alignment: .topTrailing) to render the preset label inside the plot rectangle rather than via an outer ZStack. The plot area excludes axis labels, so right-aligning inside it puts the label flush with the right edge of the chart without overlapping the y-axis numbers (175/150/125/100) that render along the trailing axis. Removed the surrounding ZStack and the trailing/.top vs leading/.top alignment juggling — not needed anymore.
Moved the 'Live activity' NavigationLink out of AlertManagementView and into SettingsView's alertManagementSection, directly below the Alert Management row. Both now share a Section using the LargeButton style. The new Live Activity row uses 'rectangle.on.rectangle' as the icon and the descriptive text 'Lock Screen, Dynamic Island, and CarPlay display'. Rationale: Live Activity is an output/display concern, not an alert permission concern, so users were unlikely to look for it inside Alert Management. Surfacing it at the top level makes it discoverable.
…stic report Both fields are implicitly-unwrapped optionals that are guaranteed to be set by the time the diagnostic report runs; reflecting the Optional wrappers produced opaque "Optional(...)" lines that obscured the underlying state. Matches how the other manager fields above and below these lines are rendered.
The carb-effect chart on the Carb Absorption screen and the Favorite Foods insights screen lost their predicted carb-effect line in the Tidepool stateless-algorithm refactor. `dynamicGlucoseEffects(from: end, ...)` generated effects only from now+1h forward, but the chart's visible window ends at roughly now+1h — so the two ranges only touched at a single point and nothing rendered. Widen the output sampling window to `from: start` at both call sites (carb-absorption review and historical-charts data). The carb-absorption model itself is unchanged; only the sample window grows to span the chart. No dosing-path impact: the main Loop algorithm runs through LoopAlgorithm (SPM), not these UI-only helpers. Reported by @marionbarker in LoopKit/LoopWorkspace#213.
Marion's LoopKit#2399 (b6e8841) added submodule branch+SHA to the build-details section of the diagnostic report. The script and BuildDetails.submodules accessor survived the Tidepool merges, but the report-builder block itself was relocated from DeviceDataManager.swift to LoopAppManager in Tidepool's refactor, and the merge kept Tidepool's older shape — so the consumer of .submodules was effectively dropped. Port the original change into generateDiagnosticReport(): - drop the now-redundant Loop-submodule gitRevision/gitBranch lines - rename workspaceGit{Revision,Branch} to "Workspace SHA/branch" - append the submodule list (alphabetized, "name: branch, sha")
Adds an `external` case to the bolus event type so manually-entered doses appear as "External Insulin" in the delivery log instead of being lumped in with corrections. The event details screen gains a destructive Delete button (with confirmation) that removes the dose via DoseStore.deleteDose when the underlying DoseEntry is manually entered.
# Conflicts: # Loop.xcodeproj/project.pbxproj # WatchApp/Info.plist
The dev merge resurrected the legacy WKWatchKitApp key alongside DIY's modern WKApplication single-target watch app, causing 'WatchKit App doesn't contain any WatchKit Extensions' on archive/device builds. Keep WKApplication + dev's WKSupportsLiveActivityLaunchAttributeTypes; remove WKWatchKitApp.
New "Apple Health" settings row pushes a detail page showing the write/share authorization status (Allowed/Denied/Not Set) for glucose and insulin, with a warning indicator when sharing is denied. Read authorization is intentionally not reported by iOS, so the page explains that, notes Loop reads insulin data from other apps, and points the user at the Health app to review or change access (the app cannot re-prompt once a choice is made). Exposes DeviceDataManager.healthKitSharingStatus(for:) so the Settings layer can read per-type sharing authorization.
Adds FeatureFlags.doseDeletionEnabled (off by default). When enabled, the dose details screen offers a Delete action for any bolus/basal dose, not just external (manually-entered) ones. Deleting a Loop-recorded dose that still has active insulin (within the ~6h window) shows an extra confirmation warning that Loop may make up for the reduced active insulin by dosing more. External-dose deletion is unchanged and still always available.
updateDisplayState only ever advanced lastManualBolus to a newer bolus, so a deleted (or otherwise removed) bolus lingered in the status "Last Bolus" footer. Now reflect the most recent user-entered bolus actually present in the store, clearing/downgrading when it is gone, while still preserving a just-enacted bolus the store may not have persisted yet.
The Intents.intentdefinition variant group listed all 26 locales as children, but the sync's .strings cleanup dropped the PBXFileReference definitions for 22 of them (es, ru, en, it, fr, de, zh-Hans, nl, nb, pl, ja, pt-BR, vi, da, sv, fi, ro, tr, he, ar, sk, cs), leaving dangling children. Those locales' Intents.strings were never compiled, so Apple flagged missing localized descriptions for the NewCarbEntry and EnableOverridePreset custom intents across all of them. Re-add the file references (reusing the IDs the variant group already points at); the strings now bundle for every declared locale.
Loop writes carbs to HealthKit alongside glucose and insulin, so surface the carb sharing (write) authorization status too.
New installs now default the automatic dosing strategy to automatic bolus (existing users are unaffected). The dosing strategy selection screen shows a deprecation warning on the Temp Basal Only option.
# Conflicts: # Common/FeatureFlags.swift # Loop.xcodeproj/project.pbxproj # Loop/Localizable.xcstrings # Loop/Managers/LoopAppManager.swift
marionbarker
left a comment
There was a problem hiding this comment.
This PR is approved by testing it as part of the LoopWorkspace PR450.
When building with Xcode 26.5 on a Mac, the following files are updated. I can provide a PR or you can just commit the xcstrings changes after building.
modified: Loop Widget Extension/Bootstrap/Localizable.xcstrings
modified: Loop/Localizable.xcstrings
| return [] | ||
| case .presetsForExercise: | ||
| return [ | ||
| Text(verbatim: "Moser O, Zaharieva DP, Adolfsson A, Battelino T, Bracken RM, Buckingham BA, Danne T, Davis EA, Dovč K, Forlenza GP, et al. The use of automated insulin delivery around physical activity and exercise in type 1 diabetes: a position statement of the European Association for the Study of Diabetes (EASD) and the International Society for Pediatric and Adolescent Diabetes (ISPAD). ") + Text("[PMID: 39653802](https://pmc.ncbi.nlm.nih.gov/articles/PMC11732933/)").underline(), |
There was a problem hiding this comment.
Starting here and continuing to line 1047.
All the links are added to the Loop/Localization.xcstrings file.
This is not correct. They should be marked to not translate.
marionbarker
left a comment
There was a problem hiding this comment.
The training for presets is rather difficult to navigate. I understand that the first time through, you have to do the sections in order. But once you've done the training, you should be able to go back and select which of the sections you want to review.
Instead, you have to go through each one in order again. (Presets for Illness, Presets for Daily Activity, Presets for Exercise).
All the links to articles are found in Presets for Exercise.
Refreshed Tidepool → DIY sync from the
tidepool-sync/2026-05-11branch.This supersedes and replaces the previous Tidepool Merge PR (#2237), which is being closed in favor of this one.