SettingsService.setActiveProfile persists the chosen profile and then asks ReloadService to reload. When that reload is declined — the server probe fails or returns 5xx, e.g. a Traefik 502 while Signal K restarts — the switch is deferred: the device will boot onto the new profile at the next reload, whenever that is.
Nothing durable records that state, and three affordances actively contradict it.
What the user sees
- The list says the switch did not happen.
getActiveProfileName() returns the loaded slot, so the profile row still reads "Active on this device" for the old profile. That is honest about what is on screen and silent about what the device will boot into.
- The only signal is a snackbar that the app itself destroys.
ReloadService.showUnreachableToast() opens one MatSnackBar with a Retry action. The decline scenario is a Signal K restart, so the WebSocket is retrying and app.component.ts shows a 3s toast on each WebSocketRetrying transition — Material shows one snackbar at a time, so the Retry toast is displaced roughly one backoff interval (5s) after it appears.
- There is no cancel. The profile list renders a Switch button only when
!profile.isActive, so the old profile — the one the user now wants to stay on — has no button. Undoing the pending switch means switching to a third profile and back, which presupposes understanding a state the UI denies exists.
deleteProfile's guard keys off getActiveProfileName(), so the profile the device is pointed at is deletable. The next boot then 404s on the persisted slot and lands in the missing-shared-config recovery.
A marine display is power-cycled routinely, so "the next reload" can be hours later, with no reminder that the choice was pending.
Also worth deciding
setRemoteContextDemand keys by the persisted name while computing the value from the loaded profile's dashboards, so a dashboard edit during a pending switch writes the old profile's AIS demand under the new profile's key. app-initNetwork reads that key pre-auth and fails open only on a missing key — a present, wrong one defeats it, and the new profile's AIS widgets come up empty for that boot.
Scope
- R1. Decide whether a pending switch should exist at all, or whether a declined reload should abandon it and require the user to ask again.
- R2. If it stays: surface it (a pending marker on the profile row with a Cancel), refuse to delete the pending target, and key the remote-context demand off the loaded slot.
- R3. Route the toast's Retry back through
ProfileService.switchProfile rather than a raw reload(), so the slot-still-exists check and the write-queue drain run before the navigation. Today a Retry can navigate with edits still queued against the old profile.
Found by the review on #578, which fixed the data-loss half (saves no longer follow the pending name). This is the part that change deliberately left alone.
SettingsService.setActiveProfilepersists the chosen profile and then asksReloadServiceto reload. When that reload is declined — the server probe fails or returns 5xx, e.g. a Traefik 502 while Signal K restarts — the switch is deferred: the device will boot onto the new profile at the next reload, whenever that is.Nothing durable records that state, and three affordances actively contradict it.
What the user sees
getActiveProfileName()returns the loaded slot, so the profile row still reads "Active on this device" for the old profile. That is honest about what is on screen and silent about what the device will boot into.ReloadService.showUnreachableToast()opens oneMatSnackBarwith a Retry action. The decline scenario is a Signal K restart, so the WebSocket is retrying andapp.component.tsshows a 3s toast on eachWebSocketRetryingtransition — Material shows one snackbar at a time, so the Retry toast is displaced roughly one backoff interval (5s) after it appears.!profile.isActive, so the old profile — the one the user now wants to stay on — has no button. Undoing the pending switch means switching to a third profile and back, which presupposes understanding a state the UI denies exists.deleteProfile's guard keys offgetActiveProfileName(), so the profile the device is pointed at is deletable. The next boot then 404s on the persisted slot and lands in the missing-shared-config recovery.A marine display is power-cycled routinely, so "the next reload" can be hours later, with no reminder that the choice was pending.
Also worth deciding
setRemoteContextDemandkeys by the persisted name while computing the value from the loaded profile's dashboards, so a dashboard edit during a pending switch writes the old profile's AIS demand under the new profile's key.app-initNetworkreads that key pre-auth and fails open only on a missing key — a present, wrong one defeats it, and the new profile's AIS widgets come up empty for that boot.Scope
ProfileService.switchProfilerather than a rawreload(), so the slot-still-exists check and the write-queue drain run before the navigation. Today a Retry can navigate with edits still queued against the old profile.Found by the review on #578, which fixed the data-loss half (saves no longer follow the pending name). This is the part that change deliberately left alone.