Update #2
Merged
Merged
Conversation
* Remove Main.storyboard and migrate to SwiftUI app lifecycle Replace UIKit storyboard/SceneDelegate architecture with SwiftUI App entry point (LoopFollowApp.swift) and TabView (MainTabView.swift). Convert MoreMenuViewController to SwiftUI (MoreMenuView.swift). Add SwiftUI wrappers for Remote and Nightscout tabs. Remove 6 obsolete UIKit wrapper view controllers and ~300 lines of tab management code from MainViewController. * Migrate info table from UITableView to SwiftUI List Replace UITableView with SwiftUI InfoTableView hosted in MainViewController. Make InfoManager an ObservableObject so data updates trigger SwiftUI rebuilds automatically. Remove UITableViewDataSource conformance and table delegate methods. No changes needed to the 10 Nightscout controller files that populate the table data. * Migrate statistics and pie chart from UIKit to SwiftUI Replace 7 UILabel properties and DGCharts PieChartView with a StatsDisplayModel ObservableObject and hosted StatsDisplayView. The pie chart uses a UIViewRepresentable wrapper for DGCharts since the Charts pod name shadows Swift Charts. Remove ~60 lines of UIKit stack layout code from MainViewController setupUI(). * Migrate BG display area from UIKit labels to SwiftUI Replace BGText, DirectionText, DeltaText, MinAgoText, serverText, LoopStatusLabel, and PredictionLabel with a SwiftUI BGDisplayView. Add pull-to-refresh via .refreshable modifier. Move loop status and prediction text updates to Observable values across DeviceStatus, DeviceStatusLoop, DeviceStatusOpenAPS, and BGData. Remove UIScrollView overlay and UIScrollViewDelegate conformance. * Migrate main layout to SwiftUI with UIKit charts embedded Replace UIStackView layout with MainHomeView SwiftUI view that composes BGDisplayView, InfoTableView, LineChartWrapper (UIViewRepresentable for DGCharts), and StatsDisplayView. MainViewController now hosts a single UIHostingController instead of managing individual UIView containers. Visibility of info table, small graph, and stats is now reactive via Storage observables in SwiftUI, removing several Combine subscriptions. BG text uses lineLimit + minimumScaleFactor instead of manual font sizing. * Clean up migration artifacts and fix post-migration bugs - Fix AVSpeechSynthesizer temporary in AppDelegate that would be deallocated before speech completes; use stored property instead - Fix appMovedToBackground tab switching to use Observable instead of dead UIKit tabBarController reference - Remove dead code: rebuildTabsIfNeeded(), updateNightscoutTabState(), traitCollectionDidChange notification relay, UIViewExtension.addBorder - Remove unused imports (Charts, UIKit, Combine) from migrated files - Remove unused synthesizer from LoopFollowApp - Remove redundant .appearanceDidChange subscription from NightscoutVC - Add missing super calls in viewWillAppear/viewDidAppear * Replace view hierarchy walking with MainViewController.shared The getMainViewController() methods in TreatmentsView, SettingsMenuView, and BackgroundRefreshManager tried to find MainViewController by casting rootViewController as UITabBarController, which always fails with the SwiftUI lifecycle. Add a weak static shared reference set during viewDidLoad and use it everywhere instead. * Fix MainViewController.shared references for stats and treatments Pass MainViewController.shared instead of nil when creating AggregatedStatsContentView in MainTabView and MoreMenuView. Replace view-hierarchy-walking getMainViewController() in TreatmentsViewModel with MainViewController.shared. * Fix info table font size to match storyboard The storyboard used system 17pt for both title and detail labels. The SwiftUI migration used .subheadline (~15pt) making text smaller. * Fix Share Logs sheet rendering blank Present UIActivityViewController via UIApplication.topMost instead of wrapping it in a SwiftUI .sheet, which rendered an empty view. * Fix back navigation from Settings sub-pages SettingsMenuView declared its own NavigationStack(path:) while already being pushed onto the outer NavigationStack from MainTabView, so sub-page back buttons popped the outer stack and jumped past Settings to Menu. Drop the nested NavigationStack and route Settings entries through the ambient stack: a single SettingsRoute enum drives a navigationDestination attached at the MoreMenuView root. The Settings entry itself becomes a NavigationLink(value:) so it doesn't compete with a navigationDestination (isPresented:) modifier, which was re-asserting Settings as the top of stack whenever a sub-page was pushed. * Harden post-storyboard migration * MainViewController is now a strong static singleton bootstrapped from LoopFollowApp.init(). Lifecycle work in viewDidLoad (Combine sinks, observers, scheduleAllTasks, migrations) runs at launch regardless of whether the Home tab is rendered, and HomeContentView reuses the singleton instead of instantiating a fresh VC each time. * MoreMenuView's eight .navigationDestination(isPresented:) modifiers are collapsed to a single MenuRoute enum routed through one .navigationDestination(for:), preventing the same destination-slot contention that previously caused Settings → Graph back navigation to jump past Settings. * MainTabView observes Storage.shared.appearanceMode so theme changes propagate; the orphaned .appearanceDidChange notification name is removed. * OPEN_APP_ACTION notification taps now dismiss any presented modal before switching to Home, matching prior SceneDelegate behavior. * Drop the unused Core Data stack (NSPersistentCloudKitContainer, saveContext) from AppDelegate, the dead AppDelegate.window property, and the legacy UIRequiredDeviceCapabilities=armv7 / UIStatusBarTintParameters keys from Info.plist. Switch AlarmSound's keyWindow access to the connected-scenes API and generalize UIApplication.topMost likewise so it works on Mac Catalyst. * Strip redundant inner NavigationView wrappers from settings sub-views pushed onto the outer NavigationStack: Graph, General, Advanced, Calendar, Contact, Dexcom, Nightscout, BackgroundRefresh, InfoDisplay, ImportExport. Drop unused onBack parameters from AlarmsContainerView and SettingsMenuView, the unused isPresentedAsModal flag from MainViewController, and the leftover debug print in ObservableValue.set. * LineChartWrapper.updateUIView now flushes the chart on SwiftUI re-render. MainViewController.deinit removes all observers, not just the custom "refresh" one. MoreMenuView caches the app version in @State instead of constructing AppVersionManager on every body re-render. HomeModalView uses NavigationStack (not deprecated NavigationView). * MoreMenuView: render tab-switch buttons in primary color Buttons in a List inherit the accent tint, so the Features rows that switch tabs appeared blue while the NavigationLink rows that push appeared white. Use .buttonStyle(.plain) to suppress the tint and drop the now-redundant .foregroundStyle(.primary) calls. * Revert MainViewController singleton bootstrap Constructing MainViewController.shared from LoopFollowApp.init() — and reusing the same VC across HomeContentView re-creations — caused tapping the BG chart to crash with `-[__NSArrayM insertObject:atIndex:]: object cannot be nil`. Bisected to the singleton+bootstrap piece of the post-storyboard hardening; the rest of that commit (programmatic UI, MoreMenuView routing, NavigationView strip-out) is retained. Restore the prior behavior: shared is a weak static set in viewDidLoad, HomeContentView constructs a fresh MainViewController each time, and the LoopFollowApp.init() bootstrap is removed. Known follow-up: lifecycle work in viewDidLoad (Combine sinks, scheduleAllTasks, migrations) again only runs when the Home view is first rendered, so a user who has moved Home off the tab bar gets degraded behavior until they navigate to it. * MoreMenuView: make tab-switch rows full-row tappable Wrap Button labels in an HStack with a trailing Spacer and contentShape so the entire row is tappable, matching the hit area of NavigationLink rows. Extract the pattern into a small FullRowButton helper, used for both tab-switch rows and Share Logs. * Align units-selection conflict resolution with integration branch Move the Diagnostics section out of Section("Speak BG") (was nested at the wrong indent), match StatsDisplayModel field order, and add the spacing line in updateStats. * MoreMenuView: fix cross-row tap routing in Features section Mixing Button and NavigationLink rows in the same List ForEach caused taps on a NavigationLink row to fire a sibling Button row's action — e.g. tapping Alarms with Stats in the tab bar would switch to the Stats tab instead of pushing the alarms detail. Make every row in the menu's List a uniform FullRowButton and drive pushes from state via .navigationDestination(isPresented:). Add an opt-in chevron to FullRowButton so navigating rows render the standard disclosure indicator. * MoreMenuView: keep Settings as a value-based NavigationLink Mixing .navigationDestination(isPresented:) with .navigationDestination(for:) on the same view shadowed the value-based SettingsRoute registration once SettingsMenuView was on the stack, so sub-rows like Units and Metrics couldn't push. Settings sits alone in its section, so it doesn't need the uniform-Button treatment used in Features and Logging — restore it to a NavigationLink and route it through the existing .navigationDestination(for:) channel. * Fix navigation between alarms and menu * Drive Before-First-Unlock recovery from AppDelegate Move BFU recovery (Storage.reloadAll) out of MainViewController and into AppDelegate so it runs even when the home tab's UIHostingController has not yet materialized — necessary under the SwiftUI App lifecycle, where a BG-only launch (BGAppRefreshTask, BLE wake, prewarming) may complete and the device may unlock without MainViewController ever being created. AppDelegate observes protectedDataDidBecomeAvailable (authoritative signal) and willEnterForeground (fallback), with a race-guard re-check immediately after observer registration. Recovery is idempotent via needsBFUReload. MainViewController now reacts to a new .bfuReloadCompleted notification by showing the loading overlay and rescheduling tasks; if it is not alive when the notification fires, its viewDidLoad will later see the already-reloaded Storage values and schedule tasks correctly on first load. * Keep MainViewController alive regardless of tab layout Make MainViewController.shared a strong, long-lived singleton created once via bootstrap() on first foreground, so the data pipeline, alarms and background audio run even when Home is moved into the Menu rather than a tab. Home views reuse the single instance instead of creating new ones, so the singleton is never displaced. Defer the one-shot BG graph zoom until the chart has a real frame and re-render the graph on every appearance, so the curve stays visible when Home is reached from the Menu or moved between tab bar and Menu. Restore the one-time telemetry consent prompt that was lost when SceneDelegate was removed, presenting it from MainTabView on first appearance for undecided installs. * Fix issues found in post-migration review - Speak BG quick action: under the SwiftUI scene lifecycle UIKit delivers Home Screen quick actions to the window scene delegate and never calls application(_:performActionFor:). Install a scene delegate via configurationForConnecting that handles warm taps, cold-launch shortcut delivery, and mirrors the Live Activity la-tap URL handling that moves with it. - Nightscout tab: show a hint instead of a blank page when no URL is configured, and recreate the web view when the URL or token changes (the page was loaded once in viewDidLoad and stayed stale until app restart). - Stats: resolve MainViewController lazily with a fallback to the shared instance, so stats work when the tab is built before bootstrap() has run (cold launch with Statistics as selected tab). - Remove the unwired NightscoutSettingsViewModel delegate chain. - Make LineChartWrapper.updateUIView a no-op; MainViewController already notifies the charts whenever it mutates their data. * Fix squished Home layout caused by phantom keyboard inset iOS sometimes replays a stale keyboard frame when the app returns to the foreground, compressing the Home screen by a keyboard's height until a rotation forces the safe area to recompute. Home has no text input, so opt out of keyboard avoidance at both hosting layers.
…learn#655) UNUserNotificationCenterDelegate.willPresent returned [.banner, .sound, .badge] unconditionally, which meant any notification iOS routed through this handler while the app was foregrounded produced sound — including the Live Activity push-to-start payload, which is intentionally silent (interruption-level: passive, empty title/body). Now returns [] for passive notifications and for ones with empty title/body. The four intentional alerts (renewal-failed, APNs credentials missing, push-to-start token missing, alarms) all use non-empty title/body and the default .active interruption level, so they continue to surface. Also expanded the willPresent log line with interruptionLevel and title/body presence so future reports can confirm whether iOS routed a given payload here.
…arn#663) * Bump fastlane to 2.235.0 and jwt to 3.2.0 (CVE-2026-45363) jwt < 3.2.0 accepts attacker-forged tokens when an empty or nil key is used with HMAC algorithms (GHSA-c32j-vqhx-rx3x). fastlane 2.233.1 pinned jwt < 3, blocking the fix. fastlane 2.235.0 relaxes that to jwt < 4, allowing the upgrade to 3.2.0. * Remove json and addressable pins no longer needed with fastlane 2.235.0
…uploaders (loopandlearn#662) * Increase Nightscout BG entry count to handle multiple uploaders Some users have multiple sources uploading BG entries to Nightscout for the same sensor (e.g. a closed-loop system plus two Dexcom platform apps), resulting in up to 3 entries per 5-minute slot. The previous count cap of days × 2 × 288 was too low in those cases, silently truncating the oldest portion of the requested time window before deduplication. Raise the multiplier from 2 to 4 in both BGData.swift and StatsDataFetcher.swift, giving headroom for up to 4 uploaders. The date[$gte] filter still bounds the window, so no extra data is returned when fewer entries exist. * Supplement Dex Share with NS data when Dex doesn't cover the full window When Dexcom credentials are present, LoopFollow fetches from Dex Share first. If the Dex data doesn't reach back to the start of the requested window (e.g. Dex Share's 24h cap when more days are configured, or any other gap), fall through to webLoadNSBGData so Nightscout fills in the older portion. The existing merge logic already handles stitching the two sources together correctly. * Deduplicate Dex Share readings before use Multiple Dexcom uploaders (e.g. G7 iPhone app + Apple Watch) each write readings to Dexcom's cloud, causing the Share API to return 2+ entries per 5-minute slot. With the API's hard cap of 288 readings, duplicates consume the budget and the returned data covers only ~15h instead of 24h. Dedup Dex Share data with the same 30-second window used for Nightscout, so both the NS-supplement path and the direct ProcessDexBGData path receive clean, deduplicated readings. * Extract BG entry-count multiplier into named globalVariables.maxExpectedUploaders * Reuse deduplicateBGReadings helper for Dexcom Share dedup
* Common remote commands * Help texts * Rename Common to Quick-Pick throughout Renames all files, classes, types, and UI labels introduced by the quick-pick feature from "Common" to "Quick-Pick": - CommonBoluses/ → QuickPickBoluses/ - CommonMeals/ → QuickPickMeals/ - CommonBolusesManager → QuickPickBolusesManager - CommonMealsManager → QuickPickMealsManager - CommonSectionHeader → QuickPickSectionHeader - UI labels: "Common Boluses/Meals" → "Quick-Pick Boluses/Meals"
…earn#606) * Add Nightscout WebSocket (Socket.IO) support for real-time data updates Connect to Nightscout's Socket.IO endpoint to receive push notifications when new data arrives, instead of waiting for the next poll cycle. The WebSocket acts as a smart trigger: when a dataUpdate event arrives, only the relevant data types (BG, treatments, device status, profile) are fetched based on which keys are present in the delta payload. When WebSocket is connected and authenticated, polling intervals are extended (BG 3x, device status 3x, treatments 2→10 min, profile 10→30 min) so HTTP polling becomes a safety net. On disconnect, polling immediately reverts to normal intervals. The feature is always-on when Nightscout is configured — no user setting needed. A read-only connection status is shown in Nightscout settings. - Add Socket.IO-Client-Swift 16.1.1 via SPM - Add NightscoutSocketManager for connection lifecycle - Add NightscoutSocketDataHandler for selective push-trigger logic - Extend polling intervals when WebSocket is authenticated - Show WebSocket status in Nightscout settings - Wire up lifecycle in MainViewController (init, foreground, refresh) - Add staleness detection (10 min fallback to polling) * Add opt-in WebSocket toggle with info sheet and refresh on disconnect - Add webSocketEnabled storage property (default off) so users opt in - Replace read-only status with toggle + info button in Nightscout settings - Info sheet explains real-time updates, polling fallback, and battery impact - On toggle off: disconnect socket and trigger full refresh to restore normal polling intervals immediately - On unexpected socket disconnect: post refresh notification so extended polling intervals revert to normal without waiting for them to expire * Fix stale WebSocket session on config change, remove redundant staleness timer - Disconnect WebSocket when Nightscout URL/token validation fails, preventing the old session from streaming data from a previous server while polling has switched to the new config - Reorder removeAllHandlers() before disconnect() so intentional disconnects don't fire the event handler that would reconnect to an invalid URL - Remove staleness timer — the extended polling intervals (3x/5x) already serve as the safety net when WebSocket data stops flowing * Limit WebSocket to foreground and default-on Disconnect the Nightscout WebSocket when LoopFollow moves to the background and re-establish it when returning to the foreground. Polling continues to handle background updates, so the persistent connection no longer holds the cellular radio out of idle while the app is not in use. With the battery cost bounded to foreground time, the toggle now defaults to on. Inline documentation updated to describe the foreground/background behavior. * Resume Nightscout polling promptly when the WebSocket disconnects While the socket is authenticated, each REST poll is rescheduled with a multiplier on the assumption WS will publish the next reading before the poll fires. Without an explicit catch-up, those long delays carry over when the socket goes away — most visibly on background entry, where the user could sit on stale data for 10–15 minutes after the screen locked. Fire each Nightscout poll immediately on disconnect. Their actions then reschedule on the normal un-multiplied cadence, since connectionState is no longer .authenticated. Gate on the previous state so the reconnect dance in connectIfNeeded() and other no-op disconnect paths don't trigger spurious REST round-trips.
…loopandlearn#627) Flip the debugLogLevel default to true and add a migration step so existing users with it stored as false also get it enabled, ensuring shared logs contain useful detail when reporting problems. When the user taps Share Logs, present a sheet asking for a short description of the issue. The description is written to a ShareNotice_<timestamp>.txt file (date, app version, branch+sha, user description) and included alongside the log files in the iOS share sheet.
…#665) Adds a Graph Settings toggle (Nightscout only) that overlays yesterday's BG curve on the main graph, time-shifted by 24h so it aligns with the same clock time today. Drawn as a thin dimmed gray line with no dots, purely for comparison. When enabled, one extra day of history is fetched and the overlay is capped to now plus the configured hours of prediction so it never extends further into the future than the prediction line.
* Remove Main.storyboard and migrate to SwiftUI app lifecycle Replace UIKit storyboard/SceneDelegate architecture with SwiftUI App entry point (LoopFollowApp.swift) and TabView (MainTabView.swift). Convert MoreMenuViewController to SwiftUI (MoreMenuView.swift). Add SwiftUI wrappers for Remote and Nightscout tabs. Remove 6 obsolete UIKit wrapper view controllers and ~300 lines of tab management code from MainViewController. * Migrate info table from UITableView to SwiftUI List Replace UITableView with SwiftUI InfoTableView hosted in MainViewController. Make InfoManager an ObservableObject so data updates trigger SwiftUI rebuilds automatically. Remove UITableViewDataSource conformance and table delegate methods. No changes needed to the 10 Nightscout controller files that populate the table data. * Migrate statistics and pie chart from UIKit to SwiftUI Replace 7 UILabel properties and DGCharts PieChartView with a StatsDisplayModel ObservableObject and hosted StatsDisplayView. The pie chart uses a UIViewRepresentable wrapper for DGCharts since the Charts pod name shadows Swift Charts. Remove ~60 lines of UIKit stack layout code from MainViewController setupUI(). * Migrate BG display area from UIKit labels to SwiftUI Replace BGText, DirectionText, DeltaText, MinAgoText, serverText, LoopStatusLabel, and PredictionLabel with a SwiftUI BGDisplayView. Add pull-to-refresh via .refreshable modifier. Move loop status and prediction text updates to Observable values across DeviceStatus, DeviceStatusLoop, DeviceStatusOpenAPS, and BGData. Remove UIScrollView overlay and UIScrollViewDelegate conformance. * Migrate main layout to SwiftUI with UIKit charts embedded Replace UIStackView layout with MainHomeView SwiftUI view that composes BGDisplayView, InfoTableView, LineChartWrapper (UIViewRepresentable for DGCharts), and StatsDisplayView. MainViewController now hosts a single UIHostingController instead of managing individual UIView containers. Visibility of info table, small graph, and stats is now reactive via Storage observables in SwiftUI, removing several Combine subscriptions. BG text uses lineLimit + minimumScaleFactor instead of manual font sizing. * Clean up migration artifacts and fix post-migration bugs - Fix AVSpeechSynthesizer temporary in AppDelegate that would be deallocated before speech completes; use stored property instead - Fix appMovedToBackground tab switching to use Observable instead of dead UIKit tabBarController reference - Remove dead code: rebuildTabsIfNeeded(), updateNightscoutTabState(), traitCollectionDidChange notification relay, UIViewExtension.addBorder - Remove unused imports (Charts, UIKit, Combine) from migrated files - Remove unused synthesizer from LoopFollowApp - Remove redundant .appearanceDidChange subscription from NightscoutVC - Add missing super calls in viewWillAppear/viewDidAppear * Replace view hierarchy walking with MainViewController.shared The getMainViewController() methods in TreatmentsView, SettingsMenuView, and BackgroundRefreshManager tried to find MainViewController by casting rootViewController as UITabBarController, which always fails with the SwiftUI lifecycle. Add a weak static shared reference set during viewDidLoad and use it everywhere instead. * Fix MainViewController.shared references for stats and treatments Pass MainViewController.shared instead of nil when creating AggregatedStatsContentView in MainTabView and MoreMenuView. Replace view-hierarchy-walking getMainViewController() in TreatmentsViewModel with MainViewController.shared. * Fix info table font size to match storyboard The storyboard used system 17pt for both title and detail labels. The SwiftUI migration used .subheadline (~15pt) making text smaller. * Fix Share Logs sheet rendering blank Present UIActivityViewController via UIApplication.topMost instead of wrapping it in a SwiftUI .sheet, which rendered an empty view. * Fix back navigation from Settings sub-pages SettingsMenuView declared its own NavigationStack(path:) while already being pushed onto the outer NavigationStack from MainTabView, so sub-page back buttons popped the outer stack and jumped past Settings to Menu. Drop the nested NavigationStack and route Settings entries through the ambient stack: a single SettingsRoute enum drives a navigationDestination attached at the MoreMenuView root. The Settings entry itself becomes a NavigationLink(value:) so it doesn't compete with a navigationDestination (isPresented:) modifier, which was re-asserting Settings as the top of stack whenever a sub-page was pushed. * Harden post-storyboard migration * MainViewController is now a strong static singleton bootstrapped from LoopFollowApp.init(). Lifecycle work in viewDidLoad (Combine sinks, observers, scheduleAllTasks, migrations) runs at launch regardless of whether the Home tab is rendered, and HomeContentView reuses the singleton instead of instantiating a fresh VC each time. * MoreMenuView's eight .navigationDestination(isPresented:) modifiers are collapsed to a single MenuRoute enum routed through one .navigationDestination(for:), preventing the same destination-slot contention that previously caused Settings → Graph back navigation to jump past Settings. * MainTabView observes Storage.shared.appearanceMode so theme changes propagate; the orphaned .appearanceDidChange notification name is removed. * OPEN_APP_ACTION notification taps now dismiss any presented modal before switching to Home, matching prior SceneDelegate behavior. * Drop the unused Core Data stack (NSPersistentCloudKitContainer, saveContext) from AppDelegate, the dead AppDelegate.window property, and the legacy UIRequiredDeviceCapabilities=armv7 / UIStatusBarTintParameters keys from Info.plist. Switch AlarmSound's keyWindow access to the connected-scenes API and generalize UIApplication.topMost likewise so it works on Mac Catalyst. * Strip redundant inner NavigationView wrappers from settings sub-views pushed onto the outer NavigationStack: Graph, General, Advanced, Calendar, Contact, Dexcom, Nightscout, BackgroundRefresh, InfoDisplay, ImportExport. Drop unused onBack parameters from AlarmsContainerView and SettingsMenuView, the unused isPresentedAsModal flag from MainViewController, and the leftover debug print in ObservableValue.set. * LineChartWrapper.updateUIView now flushes the chart on SwiftUI re-render. MainViewController.deinit removes all observers, not just the custom "refresh" one. MoreMenuView caches the app version in @State instead of constructing AppVersionManager on every body re-render. HomeModalView uses NavigationStack (not deprecated NavigationView). * MoreMenuView: render tab-switch buttons in primary color Buttons in a List inherit the accent tint, so the Features rows that switch tabs appeared blue while the NavigationLink rows that push appeared white. Use .buttonStyle(.plain) to suppress the tint and drop the now-redundant .foregroundStyle(.primary) calls. * Revert MainViewController singleton bootstrap Constructing MainViewController.shared from LoopFollowApp.init() — and reusing the same VC across HomeContentView re-creations — caused tapping the BG chart to crash with `-[__NSArrayM insertObject:atIndex:]: object cannot be nil`. Bisected to the singleton+bootstrap piece of the post-storyboard hardening; the rest of that commit (programmatic UI, MoreMenuView routing, NavigationView strip-out) is retained. Restore the prior behavior: shared is a weak static set in viewDidLoad, HomeContentView constructs a fresh MainViewController each time, and the LoopFollowApp.init() bootstrap is removed. Known follow-up: lifecycle work in viewDidLoad (Combine sinks, scheduleAllTasks, migrations) again only runs when the Home view is first rendered, so a user who has moved Home off the tab bar gets degraded behavior until they navigate to it. * MoreMenuView: make tab-switch rows full-row tappable Wrap Button labels in an HStack with a trailing Spacer and contentShape so the entire row is tappable, matching the hit area of NavigationLink rows. Extract the pattern into a small FullRowButton helper, used for both tab-switch rows and Share Logs. * Align units-selection conflict resolution with integration branch Move the Diagnostics section out of Section("Speak BG") (was nested at the wrong indent), match StatsDisplayModel field order, and add the spacing line in updateStats. * MoreMenuView: fix cross-row tap routing in Features section Mixing Button and NavigationLink rows in the same List ForEach caused taps on a NavigationLink row to fire a sibling Button row's action — e.g. tapping Alarms with Stats in the tab bar would switch to the Stats tab instead of pushing the alarms detail. Make every row in the menu's List a uniform FullRowButton and drive pushes from state via .navigationDestination(isPresented:). Add an opt-in chevron to FullRowButton so navigating rows render the standard disclosure indicator. * MoreMenuView: keep Settings as a value-based NavigationLink Mixing .navigationDestination(isPresented:) with .navigationDestination(for:) on the same view shadowed the value-based SettingsRoute registration once SettingsMenuView was on the stack, so sub-rows like Units and Metrics couldn't push. Settings sits alone in its section, so it doesn't need the uniform-Button treatment used in Features and Logging — restore it to a NavigationLink and route it through the existing .navigationDestination(for:) channel. * Fix navigation between alarms and menu * Drive Before-First-Unlock recovery from AppDelegate Move BFU recovery (Storage.reloadAll) out of MainViewController and into AppDelegate so it runs even when the home tab's UIHostingController has not yet materialized — necessary under the SwiftUI App lifecycle, where a BG-only launch (BGAppRefreshTask, BLE wake, prewarming) may complete and the device may unlock without MainViewController ever being created. AppDelegate observes protectedDataDidBecomeAvailable (authoritative signal) and willEnterForeground (fallback), with a race-guard re-check immediately after observer registration. Recovery is idempotent via needsBFUReload. MainViewController now reacts to a new .bfuReloadCompleted notification by showing the loading overlay and rescheduling tasks; if it is not alive when the notification fires, its viewDidLoad will later see the already-reloaded Storage values and schedule tasks correctly on first load. * Keep MainViewController alive regardless of tab layout Make MainViewController.shared a strong, long-lived singleton created once via bootstrap() on first foreground, so the data pipeline, alarms and background audio run even when Home is moved into the Menu rather than a tab. Home views reuse the single instance instead of creating new ones, so the singleton is never displaced. Defer the one-shot BG graph zoom until the chart has a real frame and re-render the graph on every appearance, so the curve stays visible when Home is reached from the Menu or moved between tab bar and Menu. Restore the one-time telemetry consent prompt that was lost when SceneDelegate was removed, presenting it from MainTabView on first appearance for undecided installs. * Scale info data table text with Dynamic Type Drive the info table font size and row height from @ScaledMetric so the top-right info data grows and shrinks with the iPhone's text-size setting, keeping the existing 17pt/21pt look at the default size. Cap the scaling at accessibility1 so the compact top strip stays within its layout. * Use two rows if needed * Info table adjustments
Trio dev's APNS-based remote command support has merged to main, so the Nightscout treatment-posting variant of remote commands is no longer needed. Drop the .nightscout case from RemoteType, the Nightscout remote view and controller, the unused NoRemoteView fallback, and all .nightscout switch arms in the remote settings, import/export, and device validation paths. Storage keys are reused by the TRC and LoopAPNS variants and remain untouched. Existing users with remoteType set to "Nightscout" automatically fall back to .none on next launch: StorageValue's JSONDecoder fails on the removed enum case and returns the default value.
Fixes loopandlearn#637. The cone of uncertainty in updateOpenAPSPredictionDisplay() was capped at the longest predBG array (.max()), which let the band visibly deform at the tail as shorter arrays dropped out one by one. Cap at the shortest array length instead so every cone point is computed from the same set of contributing arrays. Matches Trio's ForecastSetup (Trio/Sources/Modules/Home/HomeStateModel+Setup/ForecastSetup.swift), which uses allForecastValues.map(\.count).min() and then iterates 0 ..< localMinCount. Renamed maxLength to coneLength since the variable no longer represents a max.
Add 'edited' to the lint workflow's pull_request trigger, guarded so the SwiftFormat job only re-runs when the base branch actually changed. Retargeting a PR (e.g. main -> dev) fires pull_request: edited, which the default trigger types (opened, synchronize, reopened) ignore. When the new base requires the SwiftFormat status check, GitHub leaves it 'Expected - waiting for status to be reported' with no run behind it, blocking the PR indefinitely. Re-running on base change ensures the required check actually reports against the new base.
The Nightscout remote command path was removed in loopandlearn#618, deleting the only file under LoopFollow/Remote/Nightscout/. The now-empty PBXGroup was left behind, still pointing at a folder with no contents. Remove it.
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.
Update