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)
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.
annotated() can emit per-dose segments out of startDate order when input doses overlap (e.g. a bolus during a temp basal). The chokepoint sort at DoseStore.getNormalizedDoseEntries fixes the input order but not the output of annotated(), which is what feeds the binary-search filterDateRange in getHistoricalChartsData. Sort the annotated array directly.
Mirrors the existing pumpSuspended gate: DeliveryDelegate gains isManualTempBasalRunning (DoseEntry.automatic == false on the active .tempBasal state), and loop() throws LoopError.manualTempBasalRunning before enacting. Without this, the next automatic cycle issues its own temp basal and replaces the user's manual one -- visible on Omnipod where the pump UI offers a Set Temp Basal action.
Adds isManualTempBasalRunning to LoopStatusModalViewModel (sourced from DeviceDataManager.isManualTempBasalRunning) and two new branches in copy: one for the closed-loop-on path (titleUnavailable + "Automation is unavailable while a manual temporary basal is in progress.") and one for the closed-loop-off path. Without this the modal kept showing "Automation is on" while loop() was actually short-circuiting via the LoopError.manualTempBasalRunning gate.
Two fixes to how the display-side (not dosing-side) forecast handles an in-flight suspend. 1) fetchData gains a projectOngoingDoses flag; updateDisplayState passes true. With that, getNormalizedDoseEntries is called with end:nil, which triggers DoseStore's fallback that extends a mutable suspend to currentDate() + defaultInsulinActivityDuration. The dosing loop() still uses end:baseTime so the recommendation is computed against what's known up to now (the recommendation itself decides what happens past now). Without this, the displayed prediction silently assumed scheduled basal resumed at the suspend instant -- optimistic by exactly the missing-basal effect over the forecast window. 2) Coalesce contiguous same-rate entries in input.basal before they reach the algorithm. getBasalHistory projects the daily schedule onto absolute time and splits at every local midnight even when the rate doesn't change; InsulinDose.annotated(with:) then splits any basal-typed dose at each of those boundaries, and the continuous-delivery IOB integrator doesn't join the resulting sub-doses perfectly -- showed up as a small bump in Active Insulin at midnight on a single-rate schedule. Coalescing is a no-op for genuine schedule transitions and removes the artifact for the single-rate case entirely. Also threads projectOngoingDoses through BolusEntryViewModelDelegate; test mock + call sites pass false (matching the prior behavior).
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.