Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
b80a7a8
Loop: move the types the loop runs on into LoopCore
Oct 28, 2026
2dfe293
Sport Mode: the phone–watch protocol and who controls the pod
Oct 28, 2026
52b946b
Loop: one loop cycle that both apps can run
Oct 28, 2026
f2af598
Sport Mode: the phone lends the pod
Oct 28, 2026
52cd1b1
Sport Mode: the phone's hooks in stock files
Oct 28, 2026
a0bee61
Watch: one set of stores, and one display state for the screens
Oct 28, 2026
2535355
Sport Mode: the watch borrows the pod
Oct 28, 2026
58bf0ab
Sport Mode: what the user sees
Oct 28, 2026
d349679
Merge upstream next-dev (setup history); project file conflicts resolved
Oct 28, 2026
0696c1f
Sport Mode: the banner's time counts only saved records, also for a l…
Oct 28, 2026
b6b7d9d
Sport Mode: the phone logs the grant's size and a failed urgent send …
Oct 30, 2026
4f9afd2
Build a glucose reading's fallback ID without overflowing on 32-bit w…
Nov 1, 2026
df96adc
Merge upstream next-dev: the algorithm decoupling
Nov 1, 2026
e109cb3
Run the watch's loop through the same algorithm bridge as the phone
Nov 1, 2026
3900495
Merge upstream next-dev: the LoopKit/LoopAlgorithm decoupling and slo…
Nov 2, 2026
61b93a0
Merge upstream next-dev: slow retrospective correction
Nov 2, 2026
2e3b78c
Merge the LoopCore structural branch
Nov 2, 2026
19c6219
Sport Mode: refuse Start while slow retrospective correction is on
Nov 2, 2026
bd7472c
Merge upstream next-dev: the loop's types in LoopCore (#2645), async …
Nov 3, 2026
4ba8e30
Sport Mode: slow retrospective correction on the watch
Nov 3, 2026
b871b7c
Watch app: name each device kit in one file
Nov 3, 2026
11d58b3
Sport Mode: a bolus asked for during an End is refused, never sent to…
Nov 3, 2026
463e386
Sport Mode: after a relaunch the watch reads the pod before its first…
Nov 3, 2026
3411bd5
Sport Mode: only the phone's answer to the final offer closes the loan
Nov 3, 2026
e51668f
Sport Mode: the watch records a pod's suspend for the phone
Nov 3, 2026
14a0773
Sport Mode: a take-back of a faulted pod finishes
Nov 3, 2026
96ea232
Sport Mode: the pod's alerts close when it goes home
Nov 3, 2026
b500ad3
Sport Mode: deactivate a faulted pod on the watch
Nov 3, 2026
c491bf7
Sport Mode: the phone doses nothing while it asks the watch about ins…
Nov 3, 2026
9f13381
Watch crash fix: finish every background task in a wake
Nov 5, 2026
7284fcc
Merge fix/watch-background-tasks (#2660)
Nov 5, 2026
657a25d
Sport Mode: the watch cancels a running temp at a Start instead of lo…
Nov 4, 2026
ee233b7
Sport Mode: decide on events, not on timers a sleeping app can't keep
Nov 5, 2026
6dbc90b
Add Sport Mode strings to the string catalogs
Nov 7, 2026
2885880
Sport Mode: tell the looper about a silent watch instead of taking th…
Nov 7, 2026
90b7384
Add Sport Mode strings to the string catalogs
Nov 7, 2026
0220a88
A silent watch is reported to the looper after 20 minutes
Nov 8, 2026
f0286a1
The watch opens on its main page after the background; the glance dur…
Nov 8, 2026
b26b416
Watch chart draws only when its data changes
Nov 9, 2026
60352a6
Merge upstream next-dev
Nov 10, 2026
7df12bf
Loan dose records: the phone stores the watch's doses once finished, …
Nov 10, 2026
f1273c6
Loan dose records: the phone's own doses at a start without the phone…
Nov 10, 2026
e00aba6
Control: the phone stores the watch's doses once finished
Nov 10, 2026
1d7283e
The cycle's algorithm run moves off the caller's actor
Nov 12, 2026
93070d3
Watch: one display run per reading, and at most one waiting
Nov 12, 2026
80f6bd3
Watch: a recommended bolus is computed only for an open carb or bolus…
Nov 12, 2026
e30be8f
Sport Mode: the phone answers a Start at once with 'request received'
Nov 12, 2026
9ebbaf5
Start waits set from bench timings: 6 s for the phone's "request rece…
Nov 12, 2026
d8c36fa
Watch: a reading first stored from the phone's catch-up answer runs a…
Nov 12, 2026
ed652e6
Stop a debug build if the algorithm starts on the main thread
Nov 12, 2026
0128619
Merge upstream next-dev
Nov 12, 2026
67e89fc
Sport Mode: dose ids in place of decision ids, on the watch as on the…
Nov 12, 2026
62355fd
Watch: the loan's pump delegate takes upstream's async calls
Nov 12, 2026
5aeb470
Sport Mode: a newer queued copy of the watch's records replaces the o…
Nov 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Common/FeatureFlags.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ struct FeatureFlagConfiguration: Decodable {
let allowAlgorithmExperiments: Bool
let isInvestigationalDevice: Bool
let devBranchWarningEnabled: Bool
let sportModeEnabled: Bool

fileprivate init() {
// Swift compiler config is inverse, since the default state is enabled.
Expand Down Expand Up @@ -239,6 +240,12 @@ struct FeatureFlagConfiguration: Decodable {
#else
self.devBranchWarningEnabled = true
#endif

#if SPORT_MODE_ENABLED
self.sportModeEnabled = true
#else
self.sportModeEnabled = false
#endif
}
}

Expand All @@ -260,6 +267,7 @@ extension FeatureFlagConfiguration : CustomDebugStringConvertible {
"* observeHealthKitGlucoseSamplesFromOtherApps: \(observeHealthKitGlucoseSamplesFromOtherApps)",
"* predictedGlucoseChartClampEnabled: \(predictedGlucoseChartClampEnabled)",
"* remoteCommandsEnabled: \(remoteCommandsEnabled)",
"* sportModeEnabled: \(sportModeEnabled)",
"* scenariosEnabled: \(scenariosEnabled)",
"* showEventualBloodGlucoseOnWatchEnabled: \(showEventualBloodGlucoseOnWatchEnabled)",
"* simulatedCoreDataEnabled: \(simulatedCoreDataEnabled)",
Expand Down
26 changes: 26 additions & 0 deletions Common/Models/BuildDetails+SportMode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// BuildDetails+SportMode.swift
// Loop
//
// Copyright © 2026 LoopKit Authors. All rights reserved.
//
// Sport Mode's build identity, shown on both devices and sent with a loan request.
//

import Foundation

extension BuildDetails {
/// The workspace commit plus build date and time: the same on phone and watch when built
/// together. CFBundleVersion is pinned, so it cannot tell builds apart.
var codeIdentity: String {
let sha = (dict["com-loopkit-Loop-commit-sha"] as? String)
?? (dict["com-loopkit-LoopWorkspace-git-revision"] as? String)
?? gitRevision
guard let sha else { return "sha?" }
// "Wed Aug 19 15:59:38 EDT 2026" -> "Aug19 15:59".
let parts = (dict["com-loopkit-Loop-build-date"] as? String)?
.split(separator: " ").map(String.init) ?? []
let stamp = parts.count >= 4 ? " \(parts[1])\(parts[2]) \(parts[3].prefix(5))" : ""
return sha + stamp
}
}
645 changes: 645 additions & 0 deletions Documentation/SportMode/Control.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Loop.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ LOOP_LOCAL_CACHE_DURATION_DAYS = 90

// Entitlements [DEFAULT]
LOOP_ENTITLEMENTS = Loop/Loop.entitlements
// The watch signs with none, as stock. Sport Mode builds set it to
// "WatchApp Extension/SportMode.entitlements" (HealthKit for the workout session,
// time-sensitive notifications for wrist alarms), next to SPORT_MODE_ENABLED.
LOOP_WATCH_ENTITLEMENTS =

// Code signing and provisioning [DEFAULT]
LOOP_DEVELOPMENT_TEAM =
Expand Down
574 changes: 573 additions & 1 deletion Loop.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions Loop.xcodeproj/xcshareddata/xcschemes/WatchAppTests.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "43A943711B926B7B0051FA24"
BuildableName = "WatchApp.app"
BlueprintName = "WatchApp"
ReferencedContainer = "container:Loop.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9A7CE001302D82A800A03F53"
BuildableName = "WatchAppTests.xctest"
BlueprintName = "WatchAppTests"
ReferencedContainer = "container:Loop.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9A7CE001302D82A800A03F53"
BuildableName = "WatchAppTests.xctest"
BlueprintName = "WatchAppTests"
ReferencedContainer = "container:Loop.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
3 changes: 3 additions & 0 deletions Loop/Extensions/DeviceDataManager+DeviceStatus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ extension DeviceDataManager {
return DeviceDataManager.resumeOnboardingStatusHighlight
} else if pumpManager == nil {
return DeviceDataManager.addPumpStatusHighlight
} else if let podLoanStatusHighlight {
return podLoanStatusHighlight
} else {
return (pumpManager as? PumpManagerUI)?.pumpStatusHighlight
}
Expand All @@ -54,6 +56,7 @@ extension DeviceDataManager {
}

var pumpLifecycleProgress: DeviceLifecycleProgress? {
if let podLoanProgress = podLoanLifecycleProgress { return podLoanProgress }
return (pumpManager as? PumpManagerUI)?.pumpLifecycleProgress
}

Expand Down
6 changes: 3 additions & 3 deletions Loop/Extensions/DoseStore+SimulatedCoreData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ fileprivate extension PersistedPumpEvent {
endDate: date.addingTimeInterval(duration),
value: rate,
unit: .unitsPerHour,
decisionId: nil,
doseId: nil,
deliveredUnits: rate * duration / .hours(1)))
}

Expand All @@ -119,7 +119,7 @@ fileprivate extension PersistedPumpEvent {
endDate: date.addingTimeInterval(.minutes(1)),
value: amount,
unit: .units,
decisionId: nil))
doseId: nil))
}

static func simulatedPrime(date: Date) -> PersistedPumpEvent {
Expand All @@ -144,7 +144,7 @@ fileprivate extension PersistedPumpEvent {
endDate: date.addingTimeInterval(duration),
value: rate,
unit: .unitsPerHour,
decisionId: nil,
doseId: nil,
deliveredUnits: rate * duration / .hours(1),
scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: scheduledRate)))
}
Expand Down
Loading