@@ -85,6 +85,10 @@ final class OnboardingViewModel: ObservableObject {
8585 self . onClose = onClose
8686 includeTelemetryStep = !Storage. shared. telemetryConsentDecisionMade. value
8787 existingAlarmTypes = Set ( Storage . shared. alarms. value. map ( \. type) )
88+ let hasNightscout = !Storage. shared. url. value. isEmpty
89+ let hasDexcom = !Storage. shared. shareUserName. value. isEmpty
90+ && !Storage. shared. sharePassword. value. isEmpty
91+ isAlreadyConfigured = hasNightscout || hasDexcom
8892 seedAlarms = OnboardingViewModel . defaultSeedAlarms ( )
8993
9094 // Re-publish child changes so the footer's `canProceed` stays in sync
@@ -106,14 +110,14 @@ final class OnboardingViewModel: ObservableObject {
106110
107111 // MARK: - Derived state
108112
109- /// True when the user already has a working data source — used to make
110- /// skipping prominent for returning users.
111- var isAlreadyConfigured : Bool {
112- let nightscout = !Storage . shared . url . value . isEmpty
113- let dexcom = !Storage . shared . shareUserName . value . isEmpty
114- && !Storage . shared . sharePassword . value . isEmpty
115- return nightscout || dexcom
116- }
113+ /// True when the user already had a working data source at launch — used to
114+ /// make skipping prominent for returning users and to decide whether the
115+ /// data-source/connect phases are shown. Captured once in `init`, not read
116+ /// live: the connect step persists the URL/credentials as the user types, so
117+ /// a live read would flip this to `true` mid-`.connect`, drop those phases
118+ /// from `activeSteps`, and make `advance()` fall through to `finish()` —
119+ /// silently skipping units, alarms, and the rest of setup.
120+ let isAlreadyConfigured : Bool
117121
118122 var canProceed : Bool {
119123 switch step {
@@ -234,13 +238,26 @@ final class OnboardingViewModel: ObservableObject {
234238
235239 func advance( ) {
236240 phaseProgress = nil
237- guard let index = activeSteps. firstIndex ( of: step) ,
238- index + 1 < activeSteps. count
239- else {
241+ let steps = activeSteps
242+ guard let index = steps. firstIndex ( of: step) else {
243+ // Defensive: the current step should always be in `activeSteps`.
244+ // If it ever isn't (a state change removed it while we were on it),
245+ // continue to the next still-active step in canonical order rather
246+ // than silently ending setup via `finish()`.
247+ let canonical = OnboardingStep . allCases
248+ let currentRank = canonical. firstIndex ( of: step) ?? canonical. count
249+ if let next = steps. first ( where: { ( canonical. firstIndex ( of: $0) ?? 0 ) > currentRank } ) {
250+ step = next
251+ } else {
252+ finish ( )
253+ }
254+ return
255+ }
256+ guard index + 1 < steps. count else {
240257 finish ( )
241258 return
242259 }
243- step = activeSteps [ index + 1 ]
260+ step = steps [ index + 1 ]
244261 }
245262
246263 func goBack( ) {
0 commit comments