Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
49f4ced
Speak French
bjorkert Mar 20, 2026
75b9e14
Touch carb and bolus
bjorkert Mar 20, 2026
469996e
update actions to use node js 24 (#538)
marionbarker Mar 24, 2026
e571ca3
CI: Bump dev version to 5.0.1 [skip ci]
github-actions[bot] Mar 24, 2026
6792ff6
Bug fixes and SmallFamilyView configurable slot (#575)
bjorkert Mar 25, 2026
47a9a97
Linting
bjorkert Mar 25, 2026
1004dee
Merge branch 'dev' into live-activity
bjorkert Mar 25, 2026
b390eae
Added the projected value for Trio
bjorkert Mar 25, 2026
6f98fac
Fix LA not refreshing on foreground after stale overlay (#576)
MtlPhil Mar 26, 2026
6de1ae0
Fix stale LA dismissed by iOS incorrectly blocking auto-restart (#577)
MtlPhil Mar 26, 2026
ce82964
Update json and faraday gems to fix security vulnerabilities
bjorkert Mar 28, 2026
8b80009
Merge pull request #578 from loopandlearn/fix/dependabot-gem-updates
marionbarker Mar 28, 2026
9d4ce02
CI: Bump dev version to 5.0.2 [skip ci]
github-actions[bot] Mar 28, 2026
3d62d8e
Fix stale overlay tap + redesign expanded Dynamic Island (#581)
MtlPhil Mar 28, 2026
b0ef1a0
Fix for incorrect JWT token cache invalidation
bjorkert Mar 29, 2026
97d30e9
Add Future Carbs Alert (#571)
bjorkert Mar 29, 2026
cdac9d7
CI: Bump dev version to 5.0.3 [skip ci]
github-actions[bot] Mar 29, 2026
0b47432
Add commit guidelines to README (#568)
bjorkert Mar 29, 2026
a2997bb
CI: Bump dev version to 5.0.4 [skip ci]
github-actions[bot] Mar 29, 2026
b89d3af
Fix stale overlay tap: redesign .dismissed state machine (#585)
MtlPhil Mar 29, 2026
2246bb8
Add removal dates to migration step comments (#559)
bjorkert Mar 30, 2026
19c182f
CI: Bump dev version to 5.0.5 [skip ci]
github-actions[bot] Mar 30, 2026
9d7e89a
Merge pull request #560 from loopandlearn/speak-fr
marionbarker Mar 30, 2026
b343230
CI: Bump dev version to 5.0.6 [skip ci]
github-actions[bot] Mar 30, 2026
34687a9
Merge pull request #561 from loopandlearn/touch-carb-and-bolus
marionbarker Mar 30, 2026
86a7825
CI: Bump dev version to 5.0.7 [skip ci]
github-actions[bot] Mar 30, 2026
68bb78f
Open statistics from home screen stats area (#564)
bjorkert Mar 30, 2026
35bb431
Merge branch 'main' into dev, accidentally merged PR 564 into main in…
marionbarker Mar 30, 2026
8f95077
CI: Bump dev version to 5.0.8 [skip ci]
github-actions[bot] Mar 30, 2026
ca395aa
Improve GRIView layout and axis label positioning in GRIRiskGridView …
codebymini Mar 30, 2026
b588d5a
CI: Bump dev version to 5.0.9 [skip ci]
github-actions[bot] Mar 30, 2026
6f51e71
Merge remote-tracking branch 'origin/dev' into live-activity
bjorkert Mar 30, 2026
eff03c4
Untangle Watch updates from Live Activity lifecycle
MtlPhil Mar 29, 2026
b25c9a1
Rebase Watch app onto live-activity (squash)
MtlPhil Mar 30, 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
2 changes: 1 addition & 1 deletion .github/workflows/add_identifiers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
steps:
# Checks-out the repo
- name: Checkout Repo
uses: actions/checkout@v4
uses: actions/checkout@v5

# Patch Fastlane Match to not print tables
- name: Patch Match Tables
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/auto_version_dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
token: ${{ secrets.LOOPFOLLOW_TOKEN_AUTOBUMP }}

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/build_LoopFollow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ jobs:
if: |
steps.workflow-permission.outputs.has_permission == 'true' &&
(vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false')
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
token: ${{ secrets.GH_PAT }}

Expand All @@ -100,7 +100,7 @@ jobs:
steps.workflow-permission.outputs.has_permission == 'true' &&
vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'loopandlearn'
id: sync
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.1
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.2
with:
target_sync_branch: ${{ env.TARGET_BRANCH }}
shallow_since: 6 months ago
Expand Down Expand Up @@ -178,7 +178,7 @@ jobs:
run: "sudo xcode-select --switch /Applications/Xcode_26.2.app/Contents/Developer"

- name: Checkout Repo for building
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
token: ${{ secrets.GH_PAT }}
submodules: recursive
Expand Down Expand Up @@ -228,7 +228,7 @@ jobs:
# Upload Build artifacts
- name: Upload build log, IPA and Symbol artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: build-artifacts
path: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/create_certs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:

# Checks-out the repo
- name: Checkout Repo
uses: actions/checkout@v4
uses: actions/checkout@v5

# Patch Fastlane Match not print tables
- name: Patch Match Tables
Expand Down Expand Up @@ -99,7 +99,7 @@ jobs:
run: echo "new_certificate_needed=${{ needs.create_certs.outputs.new_certificate_needed }}"

- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Install dependencies
run: bundle install
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/validate_secrets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ jobs:
TEAMID: ${{ secrets.TEAMID }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
uses: actions/checkout@v5

# Sync the GitHub runner clock with the Windows time server (workaround as suggested in https://github.com/actions/runner/issues/2996)
- name: Sync clock
Expand Down
144 changes: 144 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

LoopFollow is an iOS app for caregivers/parents of Type 1 Diabetics (T1D) to monitor CGM glucose data, loop status, and AID system metrics. This fork (`LoopFollowLA`) is built on top of the upstream [loopandlearn/LoopFollow](https://github.com/loopandlearn/LoopFollow) and adds:

- **Live Activity** (Dynamic Island / Lock Screen) — **complete**, do not modify
- **Apple Watch complications + Watch app** — **active development focus**
- **APNS-based remote commands** — complete

The Live Activity work is considered stable. If it evolves upstream, the branch is rebased. All current development effort is on the Watch app (`LoopFollowWatch Watch App` target) and its complications.

## Build System

This is a CocoaPods project. Always open `LoopFollow.xcworkspace` (not `.xcodeproj`) in Xcode.

```bash
# Install/update pods after cloning or when Podfile changes
pod install

# Build from command line (simulator)
xcodebuild -workspace LoopFollow.xcworkspace -scheme LoopFollow -destination 'platform=iOS Simulator,name=iPhone 16' build

# Run tests
xcodebuild -workspace LoopFollow.xcworkspace -scheme LoopFollow -destination 'platform=iOS Simulator,name=iPhone 16' test

# Run a single test class
xcodebuild -workspace LoopFollow.xcworkspace -scheme LoopFollow -destination 'platform=iOS Simulator,name=iPhone 16' test -only-testing:LoopFollowTests/AlarmConditions/BatteryConditionTests
```

Fastlane lanes (`build_LoopFollow`, `release`, `identifiers`, `certs`) are CI-only and require App Store Connect credentials.

## Xcode Targets

| Target | Purpose |
|---|---|
| `LoopFollow` | Main iOS app |
| `LoopFollowLAExtensionExtension` | Live Activity widget extension |
| `LoopFollowWatch Watch App` | watchOS complication app |

Bundle IDs are derived from `DEVELOPMENT_TEAM`: `com.$(TEAMID).LoopFollow`, etc. `Config.xcconfig` sets the marketing version; never edit version numbers directly (CI auto-bumps on merge to `dev`).

## Architecture

### Data Flow

1. **Data sources** → `MainViewController` pulls BG/treatment data from:
- **Nightscout** (`Controllers/Nightscout/`) via REST API
- **Dexcom Share** (`BackgroundRefresh/BT/`, uses `ShareClient` pod)
- **BLE heartbeat** (`BackgroundRefresh/BT/BLEManager.swift`) for background refresh
2. `MainViewController` stores parsed data in its own arrays (`bgData`, `bolusData`, etc.) and calls `update*Graph()` methods.
3. **Reactive state bridge**: After processing, values are pushed into `Observable.shared` (in-memory) and `Storage.shared` (UserDefaults-backed). These feed SwiftUI views and the Live Activity pipeline.

### Key Singletons

- **`Storage`** (`Storage/Storage.swift`) — All persisted user settings as `StorageValue<T>` (UserDefaults) or `SecureStorageValue<T>` (Keychain). The single source of truth for configuration.
- **`Observable`** (`Storage/Observable.swift`) — In-memory reactive state (`ObservableValue<T>`) for transient display values (BG text, color, direction, current alarm, etc.).
- **`ProfileManager`** — Manages Nightscout basal profiles.
- **`AlarmManager`** — Evaluates alarm conditions and triggers sound/notification.

### Live Activity & Watch Complication Pipeline

`GlucoseSnapshot` (`LiveActivity/GlucoseSnapshot.swift`) is the **canonical, source-agnostic data model** shared by all Watch and Live Activity surfaces. It is unit-aware (mg/dL or mmol/L) and self-contained. Fields: `glucose`, `delta`, `trend`, `updatedAt`, `iob`, `cob`, `projected`, `unit`, `isNotLooping`.

```
MainViewController / BackgroundRefresh
GlucoseSnapshotBuilder.build(...) ← assembles from Observable/Storage
GlucoseSnapshotStore.shared.save() ← persists to App Group container (JSON, atomic)
├──► LiveActivityManager.update() ← Dynamic Island / Lock Screen [COMPLETE]
├──► WatchConnectivityManager.send() ← transferUserInfo to Watch
│ └──► WatchSessionReceiver ← saves snapshot + reloads complications (Watch-side)
└──► WatchComplicationProvider ← CLKComplicationDataSource (watchOS)
└── ComplicationEntryBuilder ← builds CLKComplicationTemplate
```

Thresholds for colour classification (green / orange / red) are read via `LAAppGroupSettings.thresholdsMgdl()` from the shared App Group UserDefaults — the same thresholds used by the Live Activity. The stale threshold is **15 minutes** (900 s) throughout.

### Watch Complications (active development)

Two corner complications to build in `ComplicationEntryBuilder` (`LoopFollow/WatchComplication/ComplicationEntryBuilder.swift`):

**Complication 1 — `graphicCorner`, Open Gauge Text**
- Centre: BG value, coloured green/orange/red via `LAAppGroupSettings` thresholds
- Bottom text: delta (e.g. `+3` or `-2`)
- Gauge: fills from 0 → 15 min based on `snapshot.age / 900`
- Stale (>15 min) or `isNotLooping == true`: replace BG with `⚠` (yellow warning symbol)

**Complication 2 — `graphicCorner`, Stacked Text**
- Top line: BG value (coloured)
- Bottom line: delta + minutes since update (e.g. `+3 4m`)
- Stale (>15 min): display `--`

Both complications open the Watch app on tap (default watchOS behaviour when linked to the Watch app). `WatchComplicationProvider` handles timeline lifecycle and delegates all template construction to `ComplicationEntryBuilder`.

### Watch App (active development)

Entry point: `LoopFollowWatch Watch App/LoopFollowWatchApp.swift` — activates `WatchSessionReceiver`.
Main view: `LoopFollowWatch Watch App/ContentView.swift` — currently a placeholder stub.

**Screen 1 — Main glucose view**
- Large BG value, coloured green/orange/red
- Right column: delta, projected BG, time since last update
- Button to open the phone app (shown only when `WCSession.default.isReachable`)

**Subsequent screens — scrollable data cards**
- Each screen shows up to 4 data points from `GlucoseSnapshot`
- User-configurable via Watch app settings; every field in `GlucoseSnapshot` is eligible (glucose, delta, projected, IOB, COB, trend, age); units displayed alongside each value
- Default: IOB, COB, projected BG, battery

Watch app settings persist in the Watch-side App Group UserDefaults (same suite as `LAAppGroupSettings`).

### Background Refresh

Three modes (set in `Storage.backgroundRefreshType`):
- **Silent tune** — plays an inaudible audio track to keep app alive
- **BLE heartbeat** — paired BLE device (e.g. Dexcom G7) wakes the app
- **APNS** — server push via `APNSClient` / `APNSJWTGenerator`

### Remote Commands

Remote bolus/carb/temp-target commands flow through `BackgroundRefresh/Remote/` using TOTP-authenticated APNS pushes. Settings live in `Storage` (APNS key, team ID, bundle ID, shared secret).

### Settings Architecture

Settings are split between:
- **SwiftUI views** in `Settings/` (new) — `GeneralSettingsView`, `AlarmSettingsView`, `AdvancedSettingsView`, etc.
- **Legacy UIKit** `SettingsViewController` — being migrated to SwiftUI

### Tests

Tests use the Swift Testing framework (`import Testing`). Test files are in `Tests/AlarmConditions/`.

## Branch & PR Conventions

- **All PRs target `dev`**, never `main`. PRs to `main` will be redirected.
- Never modify version numbers — CI auto-bumps after merge.
- Branch from `dev` and name it `feature_name` or `fix_name`.
2 changes: 1 addition & 1 deletion Config.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
unique_id = ${DEVELOPMENT_TEAM}

//Version (DEFAULT)
LOOP_FOLLOW_MARKETING_VERSION = 5.0.0
LOOP_FOLLOW_MARKETING_VERSION = 5.0.9
14 changes: 9 additions & 5 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,17 @@ GEM
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.112.0)
faraday (1.8.0)
faraday (1.10.5)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
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_persistent (~> 1.1)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
multipart-post (>= 1.2, < 3)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.8)
faraday (>= 0.8.0)
Expand All @@ -61,10 +62,13 @@ GEM
faraday-em_synchrony (1.0.1)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.2.0)
multipart-post (~> 2.0)
faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.4)
faraday_middleware (1.2.1)
faraday (~> 1.0)
fastimage (2.4.0)
Expand Down Expand Up @@ -163,7 +167,7 @@ GEM
httpclient (2.9.0)
mutex_m
jmespath (1.6.2)
json (2.18.0)
json (2.19.3)
jwt (2.10.2)
base64
logger (1.7.0)
Expand Down
Loading