Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions apps/macos/Sources/WizApp/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
}

/// Refresh the dropdown just before it opens: re-read a connected light's values
/// (without reconnecting a disconnected one), and resize the hosted view to its
/// current content (connected vs. disconnected differ in height).
/// (or, mid auto-reconnect, bring the next attempt forward — but never resurrect
/// a manual disconnect), and resize the hosted view to its current content
/// (connected vs. disconnected differ in height).
func menuWillOpen(_ menu: NSMenu) {
appState.refreshIfConnected()
appState.refreshOnOpen()
dropdownContentView?.updateFrameToFit()
}

Expand Down
32 changes: 24 additions & 8 deletions apps/macos/Sources/WizApp/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -493,14 +493,30 @@ final class AppState: ObservableObject {
syncAttempt(host: selectedIp, attempt: 0)
}

/// Re-read a *connected* light's values (e.g. when the dropdown opens) without
/// reconnecting a disconnected one — so opening the menu never overrides a
/// manual disconnect. No-op while disconnected. Passes `monitorHealth: false` so
/// an off-cadence open only *reads* fresh values (and clears stale strikes on a
/// reply) — it can never itself disconnect you; only the 15 s poll drops the link.
func refreshIfConnected() {
guard connected else { return }
refreshSignal(monitorHealth: false)
/// Called when a UI surface opens (the menu-bar dropdown, or the controls
/// window under auto-sync). Two cases:
///
/// - **Connected:** re-read the bulb's values via `monitorHealth: false`, so an
/// off-cadence open only *reads* fresh values (and clears stale strikes on a
/// reply) — it can never itself disconnect you; only the 15 s poll drops the
/// link.
/// - **Mid auto-reconnect** (dropped but not *manually* disconnected): treat the
/// open as a fresh chance to reach the bulb — reset the backoff and pull the
/// next attempt forward (~0.5 s), so the user doesn't sit watching a backoff
/// that may have grown to minutes. Same bring-forward as `handlePathChange`;
/// `pollTick` owns the connect guards and won't stack onto an in-flight
/// attempt, so we just reschedule.
///
/// A deliberate disconnect (and a no-light state) is left alone, so opening the
/// menu never overrides it — the same invariant the poll and network-change
/// paths honour.
func refreshOnOpen() {
if connected {
refreshSignal(monitorHealth: false)
} else if hasLight, !manuallyDisconnected {
resetReconnectBackoff()
scheduleNextPoll(after: 0.5)
}
}

/// Warm Glow is a local overlay on white mode (the temperature follows the
Expand Down
7 changes: 4 additions & 3 deletions apps/macos/Sources/WizApp/Views/ControllerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ struct ControllerView: View {
}
.frame(minWidth: 600, minHeight: 500)
.onAppear {
// Refresh a connected light's values, but never reconnect a manually
// disconnected one (matches the menu-bar popover's refreshIfConnected).
if app.settings.autoSync, app.hasLight { app.refreshIfConnected() }
// Refresh a connected light's values (or, mid auto-reconnect, bring the next
// attempt forward), but never reconnect a manually disconnected one — matches
// the menu-bar popover's refreshOnOpen.
if app.settings.autoSync { app.refreshOnOpen() }
}
}

Expand Down