Skip to content

Fix sleep/wake handling, midnight schedules, and improve settings UI#26

Merged
yagizdo merged 9 commits into
mainfrom
fix/misc-improvements
Jun 2, 2026
Merged

Fix sleep/wake handling, midnight schedules, and improve settings UI#26
yagizdo merged 9 commits into
mainfrom
fix/misc-improvements

Conversation

@yagizdo

@yagizdo yagizdo commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Summary

Handle system sleep/wake and screen lock events to properly pause and resume break timers, fix schedule evaluation for time windows that cross midnight, and replace the native TabView with a custom tab bar for better control and hit areas.

  • Modified: BreakCoordinator.swift — added sleep/wake and screen lock/unlock handlers that cancel timers, dismiss active overlays, and reschedule breaks on resume
  • Modified: AppCoordinator.swift — observes NSWorkspace sleep/wake and DistributedNotificationCenter screen lock/unlock notifications, restarts progress timer after break scheduling
  • Modified: Schedule.swiftTimeWindow.contains now handles midnight-crossing windows (e.g. 22:00–06:00)
  • Modified: SettingsView.swift — replaced TabView with custom SettingsTabBar using rounded highlight buttons and expanded hit areas via contentShape
  • Modified: AboutView.swift — replaced NSApplicationDelegateAdaptor with parameter injection for SPUUpdater, added social links and copyright
  • Modified: TimerNumerals.swift — reduced tracking multiplier from -0.04 to -0.02 to prevent glyph clipping
  • Added tests: midnight-crossing and overnight window evaluation in SchedulingTests.swift

How it works

  1. AppCoordinator.observeSystemSleep() registers for four system notifications: willSleep, didWake, screenIsLocked, screenIsUnlocked
  2. On sleep/lock, BreakCoordinator cancels the pending break timer and dismisses any active break overlay (marking it as skipped)
  3. On wake/unlock, BreakCoordinator.scheduleNextBreak() recalculates and sets a fresh timer
  4. TimeWindow.contains detects midnight crossover when start > end and uses time >= start || time < end instead of a simple range check

Design decisions

Decision Choice Why
Separate sleep vs lock handlers Duplicate logic in both Sleep and lock are independent system events; a user may lock without sleeping. Keeps behavior explicit per event type
Custom tab bar over TabView SettingsTabBar with manual buttons Native TabView doesn't support contentShape for hit area expansion or custom highlight styling
SPUUpdater injection Pass as parameter instead of NSApplicationDelegateAdaptor Avoids second AppDelegate lookup, makes dependency explicit

Test plan

  • Put machine to sleep while a break timer is counting down — verify timer cancels and break overlay dismisses
  • Wake from sleep — verify a new break is scheduled correctly
  • Lock screen during active break — verify overlay dismisses
  • Create a schedule with overnight window (e.g. 22:00–06:00) — verify breaks fire within the window
  • Verify settings tab bar navigation works, all tabs clickable with proper highlight
  • Check About view shows social links and copyright
  • Verify timer numerals don't clip on break screen

yagizdo added 9 commits June 2, 2026 19:25
Cancel break timers and dismiss overlay on system sleep to prevent
rapid-fire auto-completions when Mac wakes. Replace native TabView
with a custom tab bar to eliminate icon size jitter caused by
frequent @EnvironmentObject re-renders.
Listen for com.apple.screenIsLocked/screenIsUnlocked via
DistributedNotificationCenter. Cancel the break timer and dismiss
any active overlay when the screen locks; reschedule the next break
through the scheduling engine on unlock so time-window and daily-cap
checks are preserved.
.buttonStyle(.plain) on macOS limits the clickable region to
the natural content size, ignoring padding. Adding contentShape
makes the entire padded frame tappable.
Negative tracking at -0.04 caused edge glyphs to be clipped at large
serif font sizes. Reducing to -0.02 gives enough breathing room.
TimeWindow.contains() returned false for windows spanning midnight
(e.g. 23:00-00:00) because end < start made the AND check impossible.
Use OR logic when the window wraps past midnight.
Screen lock and system sleep during an active break dismissed the overlay
but never emitted a CoordinatorEvent, leaving isBreakActive stuck at true.
Now emits .breakSkipped so AppCoordinator resets state correctly.

Also restarts the progress timer when a scheduled break enters the
countdown threshold, preventing a ~10s stale display on first appearance.
System sleep or screen lock during a user-initiated pause destroyed the
pause-resume timer and left isPaused=true permanently, blocking all
future break scheduling. Wake/unlock handlers now check isPaused and
call resume() instead of scheduleNextBreak().

Also extract duplicate handler logic into cancelAndDismissIfShowing()
and add tests for all four system event handlers including the
pause+sleep interaction.
@yagizdo yagizdo merged commit 9e93bea into main Jun 2, 2026
1 check passed
@yagizdo yagizdo deleted the fix/misc-improvements branch June 2, 2026 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant