Skip to content

Sync upstream Trio dev (902be75e8, 1.0.2.66) into Tai dev - #43

Merged
mountrcg merged 34 commits into
devfrom
sync/trio-2026-11-11
Nov 13, 2026
Merged

mountrcg merged 34 commits into
devfrom
sync/trio-2026-11-11

Conversation

@mountrcg

Copy link
Copy Markdown
Owner

Sync upstream Trio dev (902be75, 1.0.2.66) into Tai dev

Brings Tai dev up to upstream nightscout/Trio dev at 902be75e8 — 32 upstream commits since the last sync base 23bb3be36 (1.0.2.59), reported upstream as 54 files, +709 / −500, plus three submodule pins. Merge commit on this branch: 742f7a8d9 (7f7642d8e + 902be75e8).

What changed

Device deletion is tracked asynchronously, and pump alerts stay on the delegate queue

LibreLoop dbe4f9cdd and SyaiKit ef0aed342 both tear down on the main actor inside an async delete(), and MedtrumKit eee17b0fc reports alert pump events on the delegate queue. On the Trio side, Trio/Sources/APS/DeviceDataManager.swift sets pumpManager.delegateQueue before assigning pumpManagerDelegate, so no delegate callback can land on the default main queue while a device is being replaced or deleted.

Medtrum: crash loop, alert queue and device delete races

Upstream fix (PR #1757) carried by the MedtrumKit pin above: a device that fails while being deleted no longer re-enters the crash loop, alert pump events no longer race the deletion, and deleting a device mid-alert drains the queue instead of leaving orphaned callbacks.

Dexcom Share account redacted in log exports

Trio/Sources/Logger/Export/SensitiveValues.swift gained add(deviceLogMessage:): G7 logs Signed in to Dexcom Share as <account> (<server>) at sign-in, and after a sign-out that line is the only remaining record of the account. LogExport now feeds every device log message through it, so the username is replaced with .account in exported logs (upstream PR #1756).

One formatter family for durations, time-ago and percentages

  • Trio/Sources/Helpers/Formatters.swift replaces the deleted TimeAgoFormatter.swift: Duration.formattedTimeSpan(_:locale:) with the SpanStyle cases .exact (1d 1h 30m), .largestTwoUnits (2d 16h) and .minutes (90m), plus formattedSpokenTimeSpan for VoiceOver (1 hour, 30 minutes). Spans round down and show < 1m under a minute.
  • Date.formattedTimeAgo() reads the visible span (2d 16h, not 64h) while stored override/temp-target durations keep their minutes — Duration(minutes: Decimal) clamps, because those values come from Core Data and Nightscout unchecked.
  • Percentages go through new FormatStyle helpers (wholePercent, oneDecimalPercent, upToOneDecimalPercent, upToTwoDecimalPercent, for Double and Decimal), so 0.8 renders 80% with the locale's percent style instead of a hand-built string. They replace the ad-hoc percent formatting in the Home header, Stats charts, Adjustments, Autosens settings and settings export.
  • Translation keys were renamed to match the new call sites (%@%% Insulin → %@ Insulin, plus new keys for the spoken forms), so translators get whole phrases instead of span + ago.
  • The dead formatHrMin / formatHoursAndMinutes helpers and the CGMSensorDisplayState countdown state are gone; AdaptProfileRootView (a Tai-only file) moved to formattedTimeSpan(.largestTwoUnits) in the same change.

Basal rate editor rounding on 0.01 U/h pumps

Upstream PR #1674: BasalProfileEditorProvider rounds the rate sent to the pump to three places after dividing by insulin concentration, and BasalProfileEditorStateModel.updateFromTherapyItems picks the index via lastIndex(where: { $0 <= value.rounded(scale: 3) }). Previously a 0.01 U/h pump could silently save the profile's lowest rate.

SMB/UAM minute labels and a translated override "Off"

The override and adjustment rows now render max SMB/UAM limits through formattedTimeSpan(.minutes) (SMB 30m, UAM 60m) instead of a hand-concatenated \(minutes) min, with matching spoken labels, and SMBs Off goes through String(localized:) so the override row is translated (PRs #1743 follow-up + translation fix).

Tidepool delegate and dose upload fixes

BaseTidepoolManager now implements RemoteDataServiceDelegate (remoteDataServiceDelegate = self in the tidepoolService setter, upstream PR #1742): device logs are not uploaded, basal history returns empty on purpose — a non-empty history makes DoseEntry.annotated(with:) hit a preconditionFailure for the .units temp basals Trio builds — and automation history reports the dosing mode. With the delegate in place, dose uploads stop failing with "configuration" (upstream #1740).

Core Data waits for the first unlock

Model/CoreDataStack.swift distinguishes a protected-data failure (SQLITE_AUTH / 23) from an ordinary lock and waits for protectedDataDidBecomeAvailableNotification instead of burning its retry budget on 1-second sleeps. After a restart, a background launch before the first unlock no longer leaves the store uninitialized.

Tests

Five upstream test files came along: CoreDataProtectedDataTests, DurationFormattingTests, PercentFormattingTests, TidepoolTherapySettingsTests and an extended LogExportTests (redaction case).

Sync mechanics

  • Upstream merge target: nightscout/Trio dev at 902be75 (CI: Bump APP_DEV_VERSION to 1.0.2.66), 32 commits since merge base 23bb3be.
  • Merge commit: 742f7a8, parents 7f7642d8e (Tai dev, PR Sync upstream Trio dev (23bb3be36, 1.0.2.59) into Tai dev #42 merge) and 902be75e8.
  • Submodule pin bumps: LibreLoop 0c90cd9a0 → dbe4f9cdd, MedtrumKit 1e761ef6a → eee17b0fc, SyaiKit d46674c54 → ef0aed342. All 16 gitlink entries match upstream 902be75e8; .gitmodules is byte-identical to upstream (16 stanzas, no LoopAlgorithm).
  • Config.xcconfig: APP_DEV_VERSION 1.0.2.59 → 1.0.2.66 (Tai's BUNDLE_IDENTIFIER unchanged).
  • 9 conflicted paths resolved, each one keeping Tai's structure and adopting the upstream formatter/rounding rewrite:
    1. TimeAgoFormatter.swift — deleted (upstream removal).
    2. HistoryRootView+Treatments.swift — stays deleted (Tai had already consolidated History).
    3. AddOverrideForm — Tai's stride(DecimalPickerSettings.overridePercentageMin…Max) + upstream .formatted(.percent).
    4. EditTempTargetForm — (percentage/100).formatted(.wholePercent) slider labels, Duration(minutes:).formattedTimeSpan(), dead formatter/formattedPercentage helpers removed.
    5. BasalProfileEditorProvider — combined .roundedDouble(toPlaces: 3).
    6. BasalProfileEditorStateModel — Tai's subscribe() (sortedRates.lastIndex(where:)) + upstream .rounded(scale: 3) in updateFromTherapyItems.
    7. CurrentGlucoseView — Tai's header + formattedTimeAgo()/formattedSpokenTimeAgo(); spoken path uses formattedSpokenTimeSpan(.largestTwoUnits) because it feeds only the VoiceOver label.
    8. HomeRootView+BottomControls — Tai's multi-use panel with upstream's formatting internals (.wholePercent, .formattedTimeSpan(.largestTwoUnits), SMB/UAM .minutes, upToOneDecimalPercent), keeping upstream's (text:spoken:) tuples so the spoken accessibility labels survive.
    9. SettingInputSection — upstream .formatted(.wholePercent) with Tai's .factor/.factorRaw/.pickerSetting(for:) delegation.

Tai-specific behaviour preserved

  • autoISF end-to-end: AutoISF*, Determination+AutoISF, OpenAPS columns, autoISF settings/history modules — untouched by this range (OpenAPS.swift change is upstream's percent formatting only).
  • Garmin watch support (GarminManager, GarminCommandRouter, WatchConfig views and tests) — untouched.
  • Tai branding: bundle identifier, taiCyan/taiOrange/taiTeal colours, Tai app icons, BuildDetails.plist.
  • Testflight/: kept, see the checklist below.
  • No Tai-only file is deleted or reverted by the merge. AdaptProfileRootView.swift is the only Tai-exclusive file in the diff, and only its formatHrMin call moved to the new span formatter.

Not included

  • No new Tai features and no Tai-specific behaviour change — this is a synchronization.
  • The upstream History view split is still not adopted; Tai keeps the consolidated HistoryRootView.
  • No release notes or version promotion beyond upstream's build counter.

Verification

Independent review on card t_94d346f3 — verdict PASS at head 742f7a8d9, working tree clean:

  • Merge integrity: all 32 upstream commits present; the diff against 7f7642d8e is upstream's file set plus Tai's AdaptProfileRootView cleanup, with only the two upstream-deleted files (TimeAgoFormatter.swift, CGMSensorDisplayState.swift) removed.
  • Conflict resolutions: all 9 per the architect spec (card t_c0a7c847); the two coder deviations (C7 formattedSpokenTimeSpan, C8 (text:spoken:) tuples) are documented, justified and test-green.
  • Historical failure modes: project.pbxproj object graph 2737 objects, 0 unresolved refs, 0 duplicate IDs; new test targets registered; LoopQuantity delivery-limits port and numberFormatter chart-axis formatters intact; no dangling references to the deleted helpers.
  • Build + tests: xcodebuild build-for-testing -scheme "Trio Tests" — SUCCEEDED; test-without-building — TEST EXECUTE SUCCEEDED: 1213 tests / 152 suites passed (21.3 s, iOS 27.0 simulator, -parallel-testing-enabled NO).
  • swift test --package-path AlgorithmPackage fails at xcstringstool generate-symbols — verified identically on baseline 7f7642d8e in a fresh worktree, i.e. pre-existing Xcode 27.0 strictness on the localization catalog (12 duplicate-symbol keys + one %-i specifier), not a merge regression. Tracked as separate cleanup work.
  • Submodules: git submodule status clean, no +/-/U markers; pins match upstream 902be75e8.

Checklist before merge

  • TestFlight note: Testflight/WhatToTest.en-CA.txt still reads based on Trio dev 1.0.0.5. It is not touched by this sync (kept as-is on purpose) — Robert bumps it to the 1.0.2.66 line when preparing the build.
  • CI on the exact head of this PR is green (Algorithm Package (swift test) + Run Unit Tests).
  • PR merged by Robert only.

Screenshots

Not applicable — no Tai-side UI change is introduced by this sync; the UI changes listed above are upstream's own and are visible in the upstream PRs (#1743, #1756, #1757, #1672, #1674, #1742).

Lamercho and others added 30 commits October 26, 2026 11:07
Fix temp target percentages being truncated instead of rounded.
TidepoolService's uploadDoseData annotates doses through its
remoteDataServiceDelegate and throws TidepoolServiceError.configuration
when none is set. Trio never set one, so every dose upload failed on the
pinned TidepoolService.

BaseTidepoolManager now conforms to RemoteDataServiceDelegate and attaches
itself whenever the service is created or restored.

- getBasalHistory returns [] on purpose: with a non-empty history,
  TidepoolService hits a preconditionFailure for temp basals with unit
  .units, which is how Trio builds them. Empty leaves doses as Trio built
  them, including their scheduledBasalRate.
- automationHistory applies the current dosing mode to the whole range
  (Trio sets `automatic` on every dose, so the overlay skips them).
- fetchDeviceLogs returns [].

Upload-only; dosing is unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SuGCnzpk8tfJPiA2Y6FRrX
Drop explanations that said Trio lacks device logs and dosing mode history,
which it has. Keep one line on why getBasalHistory returns an empty array.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…elegate

Fix Tidepool dose uploads failing with "configuration"
Fix basal rate editor saving the lowest rate on 0.01 U/h pumps
Fix percent rounding and unify percent formatting
Redact signed-out Dexcom Share accounts in export
Fix Medtrum crash loop and device delete races
# Conflicts:
#	Trio.xcodeproj/project.pbxproj
#	Trio/Sources/Modules/Adjustments/View/Overrides/AdjustmentsRootView+Overrides.swift
#	Trio/Sources/Modules/Adjustments/View/TempTargets/AdjustmentsRootView+TempTargets.swift
Fix duration translations and unify duration formatting
kingst and others added 4 commits November 10, 2026 09:11
Wait for first unlock before opening Core Data
# Conflicts:
#	Trio/Sources/Helpers/TimeAgoFormatter.swift
#	Trio/Sources/Modules/Adjustments/View/Overrides/AddOverrideForm.swift
#	Trio/Sources/Modules/Adjustments/View/TempTargets/EditTempTargetForm.swift
#	Trio/Sources/Modules/BasalProfileEditor/BasalProfileEditorProvider.swift
#	Trio/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift
#	Trio/Sources/Modules/History/View/HistoryRootView+Treatments.swift
#	Trio/Sources/Modules/Home/View/Header/CurrentGlucoseView.swift
#	Trio/Sources/Modules/Home/View/HomeRootView+BottomControls.swift
#	Trio/Sources/Views/SettingInputSection.swift
The basal editor mapped the picker's value back to a rate index with a
fixed-scale floor (rounded(scale: 3)). The picker round-trips through
Double, so on a U10 grid with a 0.025 pump step 0.0425 came back as
0.0424999..., truncated to 0.042 and was floored to 0.04 - a silent ~6 %
under-delivery that was then saved.

Move roundBasal's nearest-rate rule (ties upward, zero seed) into
Decimal.nearestSupportedBasalRate(in:), have roundBasal call it, and use
it in updateFromTherapyItems so the editor resolves rates the way the
algorithm does.

Tests: a "Basal editor picker rate selection" suite in RoundBasalTests
covering the U10 0.0425 regression, U10/U50 four-decimal rates, a
round-trip sweep over 0.01/0.025/0.05/0.1 steps x four concentrations,
and parity with roundBasal.
@mountrcg
mountrcg merged commit 9098d1e into dev Nov 13, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants