Repository navigation
Conversation
Any watch transport can hand a parsed command to WatchCommandPipeline, which runs it at most once per app and request ID, rejects it outside a 2-minute freshness window (60 s clock lead), and keeps it revocable while it waits: a revocation epoch plus a feature gate are re-checked at every side-effect boundary through WatchCommandContext.finalCheck(). - WatchCommandPipeline, WatchCommandContext, WatchCommandAuthorization, WatchCommandResult and WatchCommandRejection - WatchCommandRequestCache keyed by app and request ID, content-checked so a reused request ID is a conflict, never evicting a running request - in_progress acknowledgment code for duplicates of a running request - content-free error categories for logs
Watch side of the Complication glance: Connect IQ messages go through GarminMessageRouter, which answers v1 status requests and triggers a state refresh; nothing else goes through it yet. Delivery no longer asks the watch for app status before every send. That round trip cost the watch a BLE exchange per app per broadcast, and its completions were unreliable enough that a readiness probe built on them retried around the clock. Instead: - every app gets one send per broadcast, unless it already confirmed the same state; skips are logged per app - the send result is the readiness signal: success clears what the app was owed, AppNotFound marks it absent without a resend, other failures keep the single 2 s resend and then leave the state owed - owed state goes out with the next broadcast, a reconnect or the next message from that app; no timers - only a lost connection gates sends; readiness never does, so a missed characteristics callback cannot block delivery Also routes Connect IQ calls through GarminConnectIQClient so delivery is testable without a watch, and fixes the stale 20 s fallback comments (the delay is 10 s).
The Garmin watch app can log carbs, deliver a bolus, or both as a meal, once the user opts in under Watch > Garmin > Commands (both switches default off; turning commands off also turns bolus off). Commands run through the watch-agnostic pipeline; this adds the Garmin side and the treatment rules: - v1 command envelope with exact payload validation and RFC 4122 request IDs; every command is answered with a targeted ack, malformed ones too when they name a request ID - gates re-read at every boundary: the master switch, the bolus switch and max carbs; switching either off revokes pending commands from the settings setter itself, so off-then-on before the notification drains still revokes - one insulin command at a time: validation against max bolus, max IOB and recent boluses since the command was issued; after a watch bolus reached the pump, the next one is refused for the 6-minute window - final checks run inside the carb save transaction and right before APSManager.enactBolus, so nothing is written or issued after a revocation or expiry - meal carbs are stored before the bolus; a refused bolus acks partial_failure - the preset reply carries the switches and limits, which is where the Garmin app reads them, so the watch only offers what the phone accepts; its preset lists stay empty until adjustment commands land. Switch and max carbs changes push it to Complication apps best-effort Shared files touched: - AppleWatchManager: adds the partial_failure ack code to the acknowledgment codes both watch transports share; no Apple Watch behavior change - CarbsStorage: adds storeVerifiedCarbs. storeCarbs swallows save errors, so a watch could be told "Carbs logged" with nothing saved; the new call throws on failure and runs the final authorization check inside the transaction. Existing callers are unchanged.
The Garmin watch app can activate or cancel override and temp target presets through the same pipeline and gates as treatment commands: - presets are matched by exact, case-sensitive, unique name, whether or not they run; two presets that differ only in whitespace never let the watch start the wrong one - cancelling with nothing active is an idempotent success - AdjustmentManager gets guarded entry points whose authorize closure runs inside the transaction before any read or write, so a command revoked while it waits for the adjustment serializer writes nothing - the preset reply now lists override and temp target presets next to the switches and limits; a successful preset command, a preset edit or a command setting change pushes it to every Complication app, and a push that cannot be delivered stays owed like a state broadcast Tests cover the adjustment processor cases, revocation while the preset list loads and while queued behind a busy Core Data context (real BaseAdjustmentManager, save counter), router parsing and push rules.
With Trio-Complication selected as watchface it is the command app; with any other watchface Trio-WatchControls is, while watch commands are on. Only the command app's commands run, and only it gets the preset list. - Trio-WatchControls is registered, sent state and presets only while it is the command app - ownerMode notice (active, owner) on every change; a missed stand-down is repeated when the app next sends anything, a failed activation is owed like state - commands from any other app, including an unregistered command app on a selected device, are refused with an ack instead of silence - a change of command app revokes pending commands - help text names the command app Waits for proposed PRs 1-4.
Drop the legacy GarminCommandRouter and WatchCommand processor/cache stack together with its tests and Xcode project references. The watch-service pipeline and Garmin command stack merged next replace it. GarminDevice and GarminWatchState stay: upstream and the new stack's GarminManager and WatchConfig views still use them.
# Conflicts: # Trio.xcodeproj/project.pbxproj # Trio/Sources/Assemblies/ServiceAssembly.swift # Trio/Sources/Services/WatchManager/AppleWatchManager.swift
# Conflicts: # Trio.xcodeproj/project.pbxproj # Trio/Sources/Models/WatchMessageKeys.swift # Trio/Sources/Services/WatchManager/GarminManager.swift
# Conflicts: # Trio.xcodeproj/project.pbxproj # Trio/Sources/APS/Storage/CarbsStorage.swift # Trio/Sources/Models/GarminWatchSettings.swift # Trio/Sources/Models/WatchMessageKeys.swift # Trio/Sources/Modules/WatchConfig/View/WatchConfigGarminAppConfigView.swift # Trio/Sources/Services/SettingsManager/SettingsManager.swift # Trio/Sources/Services/WatchManager/GarminManager.swift
# Conflicts: # Trio/Sources/Models/WatchMessageKeys.swift # Trio/Sources/Services/Adjustments/AdjustmentManager.swift
# Conflicts: # Trio/Sources/Services/WatchManager/GarminManager.swift
Old releases persisted a singular `datafield` key. Decode it as a fallback when the plural `datafields` array is missing or invalid, so existing watch settings keep their selection instead of silently reverting to the default. Update the watch command fixtures to the plural property and cover the migration: legacy singular, plural priority over legacy, explicit empty selection, invalid values falling back to the default, and an invalid plural falling back to a valid legacy singular.
Replace the plural datafield selection with one configured datafield across GarminWatchSettings, TrioSettings, the watch config state model and view, and GarminManager so that registration creates at most one datafield IQApp. Decoding keeps the plural key working for migration: the first recognized non-none entry of the stored array wins, an explicitly empty array maps to none, and otherwise the legacy singular value, the default, or the type default applies. Malformed or unknown plural values fall back safely. Tests are updated for the singular model and add coverage for the migration precedence, including the empty-array and unknown-value cases.
The post-xcodebuild step chained "git fetch --deepen 6 && git log ...", so a failed fetch dropped the whole commit list from WhatToTest.en-CA.txt. The fetch recurses into submodules by default and aborts on stale gitlinks from deep history (MedtrumKit, EversenseKit and CGMBLEKit pins that the configured upstream no longer serves: "upload-pack: not our ref"), which is exactly what happened on build 459. Fetch history without submodule recursion as a best effort and always append the log afterwards, so the release notes no longer depend on a network-only step.
The upstream logging rework names its export files "Trio Logs <stamp>.txt" and, when the new Core Data store first opens, clears leftover plain-text logs out of Documents/logs. Both carry upstream's own conventions, which do not fit this fork. - Export files keep upstream's naming scheme but read "Tai Logs 2026-10-28 1430.log": Tai's name instead of Trio's, and the .log extension in place of .txt. The zip wrapper derives its name from the file, so the shared archive follows. - The legacy sweeper only matched "log.txt", written by the reporter upstream retired. Tai's reporter wrote dated "<date>.log" and "watch_<date>.log" files instead, so the sweeper collected nothing at all on a Tai install and left those files behind for good. It now matches both extensions and ages them out after the 8 days of LogRetentionPolicy .default rather than a separate 7. - Zip exports in Documents/logExports were never cleaned up either. The four newest are kept and older ones removed, close to the 3 the retired reporter held on to. - Reporting a problem opens an issue on mountrcg/Tai, and the share row above it reads "Share Tai Logs".
registerDevices and receivedMessage only checked app.uuid against GarminWatchControls.appUUID, so Complication never got told it was active/inactive and had no self-heal path when a notice was dropped - its glance kept showing stale data as if still active after switching watchfaces. GarminCommandApp.appUUIDs already unifies both apps elsewhere in this file; use it here too. Verified against Trio-Complication's TrioOwnerMode.mc: it already parses this exact envelope and drives the glance's active/inactive display, so the Connect IQ side needs no change.
The watch reset bolusAmount, carbsAmount and confirmationProgress every time the phone became reachable again, which happens on each wrist raise. A user on the confirm screen who lowered and raised their wrist had their amount replaced with 0 and confirmed a 0 U bolus. The phone's enactBolus returned without calling back for amounts of 0 or less, so the watch never got an answer, and the Processing screen's 5 s fallback never started because showCommsAnimation was already true when the view appeared. Watch: - Reachability changes no longer touch entries in progress; the main view still resets them when it appears. - sendBolusRequest refuses amounts of 0 or less with a visible message. - Every treatment request shows "iPhone not reachable. Nothing was sent." instead of an empty screen when the phone is unreachable. - WatchState times out a missing answer after 15 s, and on a send error, with a message to check Trio on the iPhone (a send error does not prove the request was not delivered). The view-level timer is removed. Phone: - enactBolus answers every caller with a failure for amounts of 0 or less, or amounts that round to 0 on the pump. Tests: - Watch: reconnect keeps the bolus entry, 0 U and negative boluses are refused, unreachable requests say nothing was sent, the timeout fires and is cancelled by an answer, a send error ends processing. - Phone: enactBolus calls back once with a failure for 0, negative and NaN. - Bring the watch test target back to compiling: drop assertions on members that no longer exist (isBolusCanceled, maxCOB, bolus cancel) and import SwiftUI instead of SwiftUICore. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An amount entered on the watch stayed on the input and confirm screens for as long as the app was left open, so an old entry could be confirmed much later. Entries now expire 2 minutes after their last edit. - Opening the carbs or bolus input screen, or changing the amount there (crown, +/-, a recommendation landing), restarts the entry's clock. - Confirming with the crown and "Log Carbs" send only an entry edited within the last 2 minutes; an older one is cleared and the watch says nothing was sent. An entry with no recorded edit is never sent. - When the app becomes active again, an entry older than 2 minutes is cleared and the watch returns to the main screen. - Returning to the main screen, Cancel and sending end the entry. Tests cover the 2 minute boundary, refusing expired and unedited entries, sending fresh ones, carbs-only entries and clearing on return to the app. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Watch: stop 0 U boluses after a reconnect, time out Processing, expire entries after 2 min
To fix issue 1754
Remove localization strings for CFBundleName and CFBundleDisplayName
* Alert and flag when Tidepool login is rejected * Drop Tidepool sign-out now TidepoolKit clears it * Skip Tidepool re-login alert when deleting service
* Include dev version in TestFlight build notes Builds with a 4-part APP_DEV_VERSION (anything off main) now show it alongside the branch and commit, e.g. "dev 1.0.2.33 abc1234". * Create TestFlight What to Test note without a manual first entry fastlane's changelog option only updates What to Test notes that already exist on a build, so apps that never had one entered by hand got no notes. Passing the app's primary locale through localized_build_info makes fastlane create the note. * Add bundled release notes to TestFlight build notes Reads BundledReleaseNotes.json from the IPA (written by scripts/capture-release-notes.sh) and appends the newest release's summary, callouts and At A Glance bullets, matching what the in-app sheet shows. Builds without bundled notes keep the branch, version and commit line only. * Only add release notes to TestFlight notes on main builds Also fall back to en-US if the primary locale lookup fails, and read the bundled notes as UTF-8 regardless of the runner's locale.
Fix "Sponsor this project" link
# Conflicts: # .github/FUNDING.yml # Trio.xcodeproj/project.pbxproj # Trio/Sources/APS/APSManager.swift # Trio/Sources/Modules/Home/View/HomeRootView+BottomControls.swift # Trio/Sources/Modules/Home/View/MultiUsePanelState.swift # TrioTests/CoreDataTests/MultiUsePanelStateTests.swift # fastlane/Fastfile
…build Only Medtrum and the pump simulator stay in the device catalog. The removed kits have not adopted the async delegate API of the pinned LoopKit, so they are unlinked from the Trio target, dropped from the workspace and the Trio scheme's testables, and their test cases trimmed.
Owner
Author
|
Aborted per Robert 2026-11-14: staying on MedtrumKit M1 until the ecosystem adopts LoopKit #696/#695 (all native CGM kits also require adoption). Test build no longer needed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Robert's personal test build. Do not merge; opened as a draft only so the unit-test CI runs.