Purpose: A repeatable process an developer can follow to merge changes from Tidepool's fork of the Loop ecosystem back into the LoopKit DIY repos, resolving conflicts with full contextual understanding.
Last updated: 2026-03-10 (added Golden Rule; clarified .strings vs .xcstrings handling)
The Loop ecosystem consists of two parallel development streams:
- LoopKit (DIY/open source): The community-maintained fork at
github.com/LoopKit/*. Organized as a set of git submodules inLoopWorkspace. - Tidepool: A company building a supported version of Loop at
github.com/tidepool-org/*. Tidepool's repos are forks of the LoopKit repos, with additional clinical/regulatory features.
Changes flow in both directions over time, but syncing is typically done in the direction: tidepool-org → LoopKit, bringing Tidepool's upstream improvements back to DIY, and then DIY -> Tidepool
When in doubt about a conflict resolution, prefer Tidepool's version — but never at the cost of breaking or removing DIY functionality.
More specifically:
- Tidepool changes win for: algorithm improvements, bug fixes, new clinical features, API changes, architecture decisions, test coverage, Swift version upgrades.
- LoopKit DIY wins for: anything that is exclusively a DIY capability — community translations, open-source-only build paths, non-Tidepool signing/bundle IDs, features that only exist in DIY and would be silently deleted by taking Tidepool's version.
- Keep both when a DIY feature and a Tidepool feature occupy the same code area but serve different purposes and can coexist (e.g. an added Tidepool service upload alongside an existing Nightscout upload).
- Never silently drop a DIY feature. If Tidepool's version removes something DIY users
depend on, document it explicitly in the per-repo sync log and flag it for human review (
⚠️ ) rather than quietly taking Tidepool's side.
Practical decision tree for any conflicting hunk:
- Is this a pure algorithm/logic change? → Take Tidepool's.
- Does Tidepool's version remove a capability DIY users have? → Keep both if possible; if not, flag for human review.
- Is this cosmetic (formatting, style, ordering)? → Take Tidepool's; not worth fighting over.
- Is this a build/project setting (deployment target, signing, bundle ID)? → See the "project.pbxproj" section below for specific rules.
- Is this a Tidepool-only backend integration (e.g. Coastal, Tidepool upload)? → Keep it; it doesn't harm DIY users and removing it creates future conflicts.
The most important structural difference between the two streams:
- Tidepool extracted the core Loop algorithm into a standalone Swift Package:
tidepool-org/LoopAlgorithm(a fork ofLoopKit/LoopAlgorithm). Tidepool'sLoopKitrepo declares it as a SwiftPM dependency (inPackage.resolved). - LoopKit DIY still embeds the algorithm code inline inside
LoopKit/LoopKit/LoopAlgorithm/.
This means when syncing LoopKit:
- Algorithm changes Tidepool made via their
LoopAlgorithmpackage need to be found by looking attidepool-org/LoopAlgorithmcommits ANDtidepool-org/LoopKitcommits. - Conflicts in
LoopAlgorithm.swift,LoopPredictionOutput.swift, etc. inside LoopKit may reflect changes that Tidepool now maintains in their separate package. - LoopAlgorithm must be synced first (as a standalone package repo) before syncing LoopKit.
All repos below are submodules of LoopWorkspace, except LoopAlgorithm which is standalone.
| Repo | LoopKit branch | Tidepool fork | Notes |
|---|---|---|---|
| LoopAlgorithm | main |
tidepool-org/LoopAlgorithm |
|
| LoopKit | dev |
tidepool-org/LoopKit |
|
| Loop | dev |
tidepool-org/Loop |
Most complex. Many source conflicts. Sync LAST. |
| TidepoolService | dev |
tidepool-org/TidepoolService |
Tidepool-specific service; sync carefully |
| OmniBLE | dev |
tidepool-org/OmniBLE |
Pump driver; test after merge |
| OmniKit | main |
tidepool-org/OmniKit |
Pump driver; test after merge |
| MinimedKit | main |
tidepool-org/MinimedKit |
Pump driver; test after merge |
| NightscoutService | dev |
tidepool-org/NightscoutService |
Service layer |
| LibreTransmitter | main |
tidepool-org/LibreTransmitter |
CGM driver |
| G7SensorKit | main |
tidepool-org/G7SensorKit |
CGM driver |
| CGMBLEKit | dev |
tidepool-org/CGMBLEKit |
CGM driver |
| dexcom-share-client-swift | dev |
tidepool-org/dexcom-share-client-swift |
CGM client |
| RileyLinkKit | dev |
tidepool-org/RileyLinkKit |
Radio hardware |
| LoopOnboarding | dev |
tidepool-org/LoopOnboarding |
Onboarding UI |
| LoopSupport | dev |
tidepool-org/LoopSupport |
Support utilities |
| AmplitudeService | dev |
tidepool-org/AmplitudeService |
Analytics service |
| LogglyService | dev |
tidepool-org/LogglyService |
Logging service |
| NightscoutRemoteCGM | dev |
tidepool-org/NightscoutRemoteCGM |
CGM source |
| MixpanelService | main |
tidepool-org/MixpanelService |
Analytics service |
Not synced: Minizip, TrueTime.swift (third-party libs, no Tidepool fork)
Core → App → Plugins (Peripheral)
The correct order is not simply "foundational to dependent" in a build-graph sense. It is core architectural decisions first, so that by the time you reach the peripheral plugins you already understand what the core changed — making those conflicts easier to read and resolve coherently.
In practice, a conflict in a pump driver that looks like "Tidepool changed the DoseEntry
type" only makes sense once you've already seen that LoopKit changed DoseEntry in the
core repo. If you do plugins first, you're resolving conflicts blind.
- LoopAlgorithm — standalone package; establishes the algorithm API everything else uses
- LoopKit — foundational types (
DoseEntry,GlucoseValue,Guardrail, etc.); all plugins depend on it
- Loop — the top-level app; resolving this before plugins means you understand which app-level API changes the plugins are expected to match, rather than discovering surprises later
- CGM drivers: CGMBLEKit, G7SensorKit, dexcom-share-client-swift, NightscoutRemoteCGM, LibreTransmitter
- Pump drivers: RileyLinkKit, OmniKit, OmniBLE, MinimedKit
- Services: TidepoolService, NightscoutService, AmplitudeService, LogglyService, MixpanelService
- Support/Onboarding: LoopSupport, LoopOnboarding
Note: pbxproj-only conflicts in peripheral repos can be batched and resolved mechanically at any point since they don't require architectural context. Swift source conflicts in peripheral repos should wait until Tier 1 + 2 are done.
For each repo in the sync list, add the Tidepool remote if not already present:
cd LoopWorkspace/<repo>
git remote add tidepool https://github.com/tidepool-org/<repo>.git
git fetch tidepoolFor LoopAlgorithm (standalone — clone separately):
cd LoopWorkspace # or wherever you keep it
git clone https://github.com/LoopKit/LoopAlgorithm.git
cd LoopAlgorithm
git remote add tidepool https://github.com/tidepool-org/LoopAlgorithm.git
git fetch tidepoolRepeat these steps for each repo, in the order listed above.
Use a consistent name across all repos for this sync run, e.g.:
tidepool-sync/YYYY-MM-DD
cd LoopWorkspace/<repo>
git checkout origin/<base-branch> # e.g. origin/dev or origin/main
git checkout -b tidepool-sync/YYYY-MM-DDgit merge --no-edit tidepool/<base-branch>If the merge succeeds with no conflicts → commit and move to Step 9. If there are conflicts → continue to Step 4.
git diff --name-only --diff-filter=UCategorize conflicts:
- project.pbxproj → See "Xcode Project File Conflicts" section below
- Swift source files → See "Source Code Conflicts" section below
- Other (yml, strings, etc.) → Research case by case
For each conflicting Swift file:
a) Understand what each side changed:
MERGE_BASE=$(git merge-base HEAD tidepool/<branch>)
# What did LoopKit change in this file since the merge base?
git log --oneline $MERGE_BASE..origin/<branch> -- <file>
git diff $MERGE_BASE..origin/<branch> -- <file>
# What did Tidepool change?
git log --oneline $MERGE_BASE..tidepool/<branch> -- <file>
git diff $MERGE_BASE..tidepool/<branch> -- <file>b) Find related GitHub issues and PRs:
For each commit hash found above, search for it on GitHub:
https://github.com/LoopKit/<repo>/commit/<hash>- Look at the PR that merged it (GitHub shows "merged via PR #NNN")
- Read the PR description and linked issues
- Also search for the LOOP-XXXX ticket numbers in commit messages at:
https://github.com/LoopKit/<repo>/issuesandhttps://github.com/tidepool-org/<repo>/issues
c) For LoopAlgorithm-related conflicts in LoopKit:
- Check if the same change exists in
tidepool-org/LoopAlgorithm - The Tidepool version of a function in LoopKit may be forwarding to their package; the DIY version keeps it inline. Preserve the inline version while incorporating any algorithmic improvements from the package version.
General principles: Follow the ⭐ Golden Rule (see top of document) — prefer Tidepool's version, but never silently remove DIY functionality.
For algorithm changes: Take Tidepool's. Their test coverage is usually more thorough
and the algorithmic direction is the right one. Check the LoopAlgorithm sync doc to
understand if a conflict here is related to the HealthKit→LoopUnit migration.
For UI changes: Take Tidepool's unless it removes a DIY-only UI path. Regulatory/clinical UI additions from Tidepool are fine to include — they add capability without breaking DIY.
For Tidepool-specific features (Tidepool Service uploads, Coastal integration, etc.): Keep them — they don't break DIY users, and removing them creates future conflicts.
For DIY-only features (e.g. community CGM integrations, Nightscout, open-source pump drivers not supported by Tidepool): Protect these. They are the reason DIY exists.
Never silently drop either side's work without a note in the sync doc.
After resolving each file:
git add <resolved-file>The .pbxproj is a structured text file. Conflicts here are almost always about:
- Object version (LoopKit likely bumped for newer Xcode)
- File references (new Swift files added by either side)
- Build settings (deployment targets, signing, feature flags)
- Localization (LoopKit uses
.xcstrings; Tidepool may still use.strings)
Approach:
# See what LoopKit changed in the project file since merge base
git diff $MERGE_BASE..origin/<branch> -- <repo>.xcodeproj/project.pbxproj
# See what Tidepool changed
git diff $MERGE_BASE..tidepool/<branch> -- <repo>.xcodeproj/project.pbxprojThen look at the actual conflict markers in the file:
grep -n "<<<<<<\|=======\|>>>>>>>" <repo>.xcodeproj/project.pbxprojCommon resolutions:
objectVersion: Keep LoopKit's (higher = newer Xcode format)IPHONEOS_DEPLOYMENT_TARGET: Take the higher of the two values (both are raising it)- New file references added by LoopKit (e.g.
.xcstrings): Keep them - New file references added by Tidepool (new Swift files, mapping models, etc.): Keep them
- Tidepool's
.stringslocalization file references: Drop them (DIY deleted these; see Pattern below) - Tidepool's
XCRemoteSwiftPackageReference "LoopAlgorithm": Omit from DIY (DIY embeds it inline) - LoopKit bundle IDs (
com.loopkit.*): Keep LoopKit's - Tidepool bundle IDs (
com.tidepool.*): Keep Tidepool's (they're for different targets) - Signing/provisioning settings: Keep LoopKit's for shared targets
After resolving:
git add <repo>.xcodeproj/project.pbxprojgit commit -m "Merge tidepool/dev into tidepool-sync/YYYY-MM-DD
Resolved conflicts:
- <file1>: <brief description of resolution>
- <file2>: <brief description of resolution>
See sync-docs/<repo>.md for full context."Update sync-docs/<repo>.md with:
- Merge base commit hash
- LoopKit and Tidepool tip commit hashes
- For each resolved conflict:
- The file path
- Relevant commit hashes from each side
- Links to GitHub PRs/issues
- What each side was trying to do
- How it was resolved and why
- Any features that need testing as a result
- Any open questions or items requiring human review
Mark the repo as done (✅) or blocked (❌) in the progress table. Note any cross-repo dependencies discovered (e.g. "LoopKit change requires matching Loop change").
git push origin tidepool-sync/YYYY-MM-DD
# Then open a PR on GitHub: tidepool-sync/YYYY-MM-DD → <base-branch>LoopAlgorithm lives at LoopKit/LoopAlgorithm (not a submodule of LoopWorkspace) and
tidepool-org/LoopAlgorithm is a fork of it.
Key questions before syncing:
-
Is the DIY
LoopAlgorithmused as a package by Loop/LoopKit, or still embedded inline?- If package: sync just like any other repo
- If inline (current DIY state): sync algorithm changes need to flow into LoopKit's
LoopKit/LoopAlgorithm/subdirectory AND the standalone LoopAlgorithm package
-
What is the current pinned version of
tidepool-org/LoopAlgorithmin Tidepool's LoopKit? CheckLoopKit.xcodeproj/.../Package.resolvedontidepool/dev. -
Clone LoopAlgorithm separately:
git clone https://github.com/LoopKit/LoopAlgorithm.git cd LoopAlgorithm git remote add tidepool https://github.com/tidepool-org/LoopAlgorithm.git git fetch tidepool -
Follow the same per-repo sync process above.
-
After resolving LoopAlgorithm, also check whether any of the same changes need to be applied to the inline copy in
LoopKit/LoopKit/LoopAlgorithm/(if DIY hasn't adopted the package yet).
In Tidepool's LoopKit, the project.pbxproj will have:
XCRemoteSwiftPackageReference "LoopAlgorithm"
repositoryURL = "https://github.com/tidepool-org/LoopAlgorithm";
Resolution for DIY: Omit this package reference. Keep the inline LoopAlgorithm/ code.
However, DO bring in any algorithmic logic changes from the inline vs. package versions.
Tidepool regularly bumps IPHONEOS_DEPLOYMENT_TARGET. DIY follows at its own pace.
Resolution: Take the higher value unless there's a specific reason not to.
Check LoopKit's own dev branch to see what they've already committed to.
LoopKit DIY migrated from .strings files to .xcstrings (Xcode 15+ string catalogs).
Tidepool does not maintain translations and remains on .strings.
Resolution: Keep LoopKit's .xcstrings format. Never re-add Tidepool's .strings
file references — Tidepool's .strings references belong to files that DIY deliberately
deleted when migrating to string catalogs. Re-adding them would cause build errors
("file not found") because the actual .strings files no longer exist in DIY's tree.
In practice, when resolving project.pbxproj conflicts:
- Drop any
PBXFileReferenceentries for*.stringsthat came from Tidepool's side - Drop any
PBXBuildFileentries referencing those same.stringsfiles - Drop any group
children = (...)entries pointing to.stringsfiles from Tidepool - Keep DIY's
*.xcstringsreferences - Keep all of Tidepool's non-translation additions (new Swift files, mapping models, etc.)
Features like Coastal integration, FDA submission mode, specific clinical guardrails. Resolution: Keep them. They add capability without breaking DIY. Only omit if they require Tidepool backend infrastructure that simply won't exist in DIY.
com.loopkit.* (LoopKit) vs com.tidepool.* (Tidepool).
Resolution: Keep both — they apply to different build targets/schemes.
Both sides removed the HKUnit.swift extension file (HealthKit unit helpers were moved).
This should auto-merge or be a trivially clean conflict. If not, take the removal.
After completing all repos, test these critical paths before opening PRs:
- Glucose display: CGM data flows and displays correctly
- Insulin delivery: Bolus and basal commands work (OmniBLE/OmniKit/Minimed)
- Loop algorithm: Closed loop prediction and dosing recommendations
- Remote services: Nightscout upload/download, Tidepool service
- Onboarding: Fresh install and therapy settings configuration
- Watch app: Complication and status display (if applicable)
- Widgets: Lock screen / home screen widgets (if applicable)
- Build: All targets compile cleanly with no warnings promoted to errors
| File | Purpose |
|---|---|
SYNC_PROGRESS.md |
Master status table, notes on blocked items |
sync-docs/<repo>.md |
Per-repo conflict log with full context and links |
LOOPKIT_SYNC_PROCESS.md |
This file — the process itself |
- LoopKit org: https://github.com/LoopKit
- Tidepool org: https://github.com/tidepool-org
- LoopAlgorithm (LoopKit): https://github.com/LoopKit/LoopAlgorithm
- LoopAlgorithm (Tidepool): https://github.com/tidepool-org/LoopAlgorithm
- LoopWorkspace: https://github.com/LoopKit/LoopWorkspace
- Original sync script:
LoopWorkspace/Scripts/sync.swift