Skip to content

Feat: Next event flip preview in status bar#905

Open
jclawson-atlassian wants to merge 1 commit intoleits:masterfrom
jclawson-atlassian:feat/next-event-flip-preview
Open

Feat: Next event flip preview in status bar#905
jclawson-atlassian wants to merge 1 commit intoleits:masterfrom
jclawson-atlassian:feat/next-event-flip-preview

Conversation

@jclawson-atlassian
Copy link
Copy Markdown

@jclawson-atlassian jclawson-atlassian commented Mar 18, 2026

Summary

Adds a new "next event preview" feature that alternates the status bar text between the current meeting and the upcoming meeting, giving users an at-a-glance preview of what's coming up before their current meeting ends.

Problem

When you're in a meeting and the next one is approaching, MeetingBar only shows the current meeting until the "ongoing event visibility" threshold kicks in (e.g., 10 min before the next event), at which point it abruptly switches. There's no advance preview of what's coming up next.

Solution

When there's an ongoing meeting and another meeting starting soon (within 15 minutes of the current meeting's end), the status bar now alternates between showing:

  • The current meeting (normal display)
  • Next: Upcoming Meeting in Xm (with bold "Next:" prefix)

The flip happens on a configurable interval (default: every 5 seconds) and is controlled by a new preference.

Preferences (Appearance → Status Bar)

A new dropdown appears below "Ongoing event visibility":

Next event preview: [Show after 10 min ▾] Flip every [5s ▾]
Option Behavior
Disabled No flipping (original behavior)
Show after start Start flipping as soon as the meeting begins
Show after 10 min Start flipping 10 minutes into the current meeting

The dropdown is automatically disabled when "Ongoing event visibility" is not set to "hide 10 min before next event", since the feature only makes sense with that visibility mode.

The "Flip every" dropdown (2s, 3s, 5s, 7s, 10s, 15s) controls how fast the alternation occurs and only appears when the feature is enabled.

Display Details

  • The upcoming event is shown with a bold "Next:" prefix using NSAttributedString to visually distinguish it from the current meeting display
  • Tooltip shows both: "Now: Current Meeting" / "Next: Upcoming Meeting"
  • The flip timer automatically stops when conditions no longer apply (current meeting ends, no upcoming meeting in range)

Files Changed

File Change
MeetingBar/Core/Models/MBEvent+Helpers.swift Added currentAndUpcomingEvent(gapThreshold:) method
MeetingBar/Extensions/DefaultsKeys.swift Added nextEventFlipMode and nextEventFlipIntervalSeconds keys
MeetingBar/Utilities/Constants.swift Added NextEventFlipMode enum
MeetingBar/UI/StatusBar/StatusBarItemController.swift Flip timer, renderUpcomingEvent() with bold prefix, refactored updateTitle()
MeetingBar/UI/Views/Preferences/AppearanceTab.swift New preference controls
MeetingBarTests/CurrentAndUpcomingEventTests.swift 19 tests for event pairing and flip activation logic
MeetingBarTests/TimelineLogicTests.swift Fix pre-existing SwiftUICore import

Summary by CodeRabbit

Release Notes

  • New Features

    • Added option to alternate display between current and upcoming events in the status bar
    • New preference settings to control event flip timing (disabled, immediate, or after 10 minutes) and flip interval (2-15 seconds)
  • Tests

    • Added comprehensive test coverage for event display logic

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 18, 2026

Walkthrough

This pull request introduces a "flip" feature for the status bar that alternates between displaying the current event and an upcoming event within a time threshold. It adds configuration options, helper logic to identify current and upcoming events, UI updates, and comprehensive test coverage.

Changes

Cohort / File(s) Summary
Event Helper Logic
MeetingBar/Core/Models/MBEvent+Helpers.swift
New extension method currentAndUpcomingEvent() identifies ongoing non-all-day events and finds subsequent events within a configurable gap threshold, filtering out declined/canceled events.
Configuration & Constants
MeetingBar/Extensions/DefaultsKeys.swift, MeetingBar/Utilities/Constants.swift
Added new NextEventFlipMode enum (disabled, showAfterStart, showAfterTenMin) and two configuration keys for flip mode and flip interval (in seconds).
Status Bar UI Implementation
MeetingBar/UI/StatusBar/StatusBarItemController.swift
Introduces flip mechanism with state management (showingUpcomingEvent, flipTask), timer logic (startFlipTimer, stopFlipTimer), and new rendering methods (renderEvent, renderUpcomingEvent, renderNormalTitle) to alternate between displaying current and upcoming events based on configuration.
Preferences UI
MeetingBar/UI/Views/Preferences/AppearanceTab.swift
Adds UI controls in Appearance preferences to configure flip mode (picker with three options) and flip interval (2–15 seconds), with conditional visibility based on ongoing event display settings.
Test Coverage
MeetingBarTests/CurrentAndUpcomingEventTests.swift, MeetingBarTests/TimelineLogicTests.swift
Comprehensive test suite for currentAndUpcomingEvent() covering edge cases (no events, gap threshold behavior, filtering), flip activation logic scenarios, and minor import correction.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hop between the meetings, quick and bright,
Timer flips the events left and right,
Current dance with next in sync so neat,
Making calendars skip-hop complete! ✨📅

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat: Next event flip preview in status bar' accurately summarizes the main feature addition—a next event preview mechanism that alternates display in the status bar.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can suggest fixes for GitHub Check annotations.

Configure the reviews.tools.github-checks setting to adjust the time to wait for GitHub Checks to complete.

@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Mar 18, 2026
button.toolTip = event.title
}

private func renderNormalTitle() {

Check warning

Code scanning / Swiftlint (reported by Codacy)

Function should have complexity 15 or less: currently complexity equals 18 Warning

Function should have complexity 15 or less: currently complexity equals 18
func test_skipsAllDayCurrentEvent() {
let allDay = makeFakeEvent(
id: "AD1",
start: now.addingTimeInterval(-43200),

Check warning

Code scanning / Swiftlint (reported by Codacy)

Underscores should be used as thousand separator in large decimal numbers. Warning

Underscores should be used as thousand separator in large decimal numbers.
let allDay = makeFakeEvent(
id: "AD1",
start: now.addingTimeInterval(-43200),
end: now.addingTimeInterval(43200),

Check warning

Code scanning / Swiftlint (reported by Codacy)

Underscores should be used as thousand separator in large decimal numbers. Warning

Underscores should be used as thousand separator in large decimal numbers.
Copy link
Copy Markdown

@github-advanced-security github-advanced-security AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tailor (reported by Codacy) found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@dosubot dosubot Bot added the feature New feature or request label Mar 18, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 18, 2026

Codecov Report

❌ Patch coverage is 62.84987% with 146 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.71%. Comparing base (84e0a9f) to head (5ccb0d9).
⚠️ Report is 15 commits behind head on master.

Files with missing lines Patch % Lines
...tingBar/UI/StatusBar/StatusBarItemController.swift 9.09% 130 Missing ⚠️
...eetingBar/UI/Views/Preferences/AppearanceTab.swift 0.00% 16 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           master     #905       +/-   ##
===========================================
+ Coverage   26.30%   38.71%   +12.40%     
===========================================
  Files          36       49       +13     
  Lines        5645     5440      -205     
  Branches     2210     1780      -430     
===========================================
+ Hits         1485     2106      +621     
+ Misses       4103     3278      -825     
+ Partials       57       56        -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
MeetingBar/UI/StatusBar/StatusBarItemController.swift (2)

219-233: Redundant MainActor.run inside @MainActor class.

Since StatusBarItemController is already marked @MainActor, the MainActor.run call is unnecessary. The class guarantees all instance methods run on the main actor.

♻️ Simplified flip timer
         flipTask = Task { [weak self] in
             while let self, !Task.isCancelled {
                 let interval = TimeInterval(Defaults[.nextEventFlipIntervalSeconds])
                 try? await Task.sleep(nanoseconds: UInt64(interval * Double(NSEC_PER_SEC)))
                 guard !Task.isCancelled else { break }
-                await MainActor.run {
-                    self.showingUpcomingEvent.toggle()
-                    self.updateTitle()
-                }
+                self.showingUpcomingEvent.toggle()
+                self.updateTitle()
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MeetingBar/UI/StatusBar/StatusBarItemController.swift` around lines 219 -
233, The MainActor.run call inside startFlipTimer is redundant because
StatusBarItemController is `@MainActor`; remove the await MainActor.run { ... }
wrapper and perform self.showingUpcomingEvent.toggle() and self.updateTitle()
directly inside the Task loop (keeping the existing [weak self] capture,
flipTask handling, and Task cancellation checks). Update startFlipTimer so the
Task closure toggles showingUpcomingEvent and calls updateTitle without
dispatching to MainActor.run.

254-270: Consider extracting icon handling to a helper.

The icon-setting logic (lines 254-270) is duplicated almost identically in renderEvent() (lines 311-326). Extracting this to a shared helper would reduce duplication and make future changes easier.

♻️ Extract helper method
private func configureIcon(for event: MBEvent, on button: NSStatusBarButton) {
    if Defaults[.eventTitleIconFormat] != .none {
        let image: NSImage
        if Defaults[.eventTitleIconFormat] == .eventtype {
            image = getIconForMeetingService(event.meetingLink?.service)
        } else {
            image = NSImage(named: Defaults[.eventTitleIconFormat].rawValue)!
        }
        button.image = image
        button.image?.size = MenuStyleConstants.iconSize
    }
    
    if button.image?.name() == "no_online_session" {
        button.imagePosition = .noImage
    } else {
        button.imagePosition = .imageLeft
    }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MeetingBar/UI/StatusBar/StatusBarItemController.swift` around lines 254 -
270, The icon-setting block in StatusBarItemController (used in
renderNextUpcomingItem() and renderEvent()) should be extracted into a single
helper to remove duplication; add a private method like configureIcon(for event:
MBEvent?, on button: NSStatusBarButton) that encapsulates the
Defaults[.eventTitleIconFormat] check, uses
getIconForMeetingService(event?.meetingLink?.service) when format == .eventtype
or NSImage(named: Defaults[.eventTitleIconFormat].rawValue) otherwise, sets
button.image and button.image?.size = MenuStyleConstants.iconSize, then sets
button.imagePosition to .noImage if button.image?.name() == "no_online_session"
else .imageLeft, and replace the duplicated blocks in
renderNextUpcomingItem()/renderEvent() with calls to this new helper.
MeetingBar/Core/Models/MBEvent+Helpers.swift (1)

87-118: Consider array ordering assumptions.

The method uses first(where:) to find both current and upcoming events. If the events array is not sorted by start date, this may return an arbitrary matching event rather than the earliest one.

If the array is expected to be pre-sorted (as other methods like nextEvent() seem to assume with their iteration logic), consider adding a doc comment noting this precondition. Otherwise, consider sorting or using min(by:) to ensure deterministic selection.

♻️ Optional: Use min(by:) for deterministic selection
-        let current = first(where: {
+        let current = filter({
             !$0.isAllDay
             && $0.startDate <= now
             && $0.endDate > now
             && $0.participationStatus != .declined
             && $0.status != .canceled
         })
+        .min(by: { $0.startDate < $1.startDate })

Apply similar change for upcoming if needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MeetingBar/Core/Models/MBEvent`+Helpers.swift around lines 87 - 118, The
currentAndUpcomingEvent method uses first(where:) which depends on array order
and can return arbitrary matches; make selection deterministic by explicitly
choosing the correct event from matching candidates: for the ongoing event
(current) filter events matching the ongoing criteria and pick the one with the
latest startDate (use max(by: { $0.startDate < $1.startDate }) or sorted last),
and for the upcoming event filter candidates that start after now and within
gapThreshold and pick the earliest start (use min(by: { $0.startDate <
$1.startDate }) or sorted first) using the same predicates currently in the
closures; alternatively, if the collection is guaranteed pre-sorted, add a doc
comment on currentAndUpcomingEvent (and similar methods like nextEvent())
stating that precondition instead of changing logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@MeetingBar/UI/StatusBar/StatusBarItemController.swift`:
- Around line 278-294: The hardcoded UI strings ("Next: " and the tooltip "Now:
...\nNext: ...") must be localized: replace the literal "Next: " used when
appending to menuTitle with a localized string (e.g. NSLocalizedString key like
"StatusBar.NextPrefix") and build eventText as before; and replace the literal
tooltip construction with a localized format string (e.g. NSLocalizedString key
like "StatusBar.Tooltip.NowNext" = "Now: %@\nNext: %@") and call
String(format:localizedTooltip, current.title, upcoming.title). Update usages
around menuTitle, button.attributedTitle and button.toolTip (and keep
Default[.eventTimeFormat] logic and attributes) so all user-facing text is
loaded via NSLocalizedString keys with appropriate placeholders for
upcomingTitle/upcomingTime/current.title/upcoming.title.

In `@MeetingBar/UI/Views/Preferences/AppearanceTab.swift`:
- Around line 251-269: The new picker labels and help strings in the UI (the
Picker with label "Next event preview:", the Text tags "Disabled", "Show after
start", "Show after 10 min", the conditional Picker label "Flip every" and their
.help(...) calls) are hardcoded and must be localized like the rest of the file;
replace those raw strings with the localized equivalents using .loco() (or the
existing localization helper) when creating the Picker label and each Text(...)
tag, and wrap the help messages with .loco() as well, keeping the bindings
(nextEventFlipMode, NextEventFlipMode.*, nextEventFlipIntervalSeconds,
ongoingEventVisibility) and conditional logic unchanged.

In `@MeetingBarTests/CurrentAndUpcomingEventTests.swift`:
- Around line 239-246: The helper shouldFlip(mode: NextEventFlipMode,
currentStart: Date) must mirror production by also checking
Defaults[.ongoingEventVisibility]; update its signature to accept an
ongoingEventVisibility parameter (or read Defaults inside the helper) and return
false unless ongoingEventVisibility == .showTenMinBeforeNext in addition to mode
!= .disabled and the time check; update all tests that call shouldFlip and any
uses in CurrentAndUpcomingEventTests to pass/verify different
ongoingEventVisibility values and add tests covering visibility values other
than .showTenMinBeforeNext to ensure behavior matches
StatusBarItemController.updateTitle.

---

Nitpick comments:
In `@MeetingBar/Core/Models/MBEvent`+Helpers.swift:
- Around line 87-118: The currentAndUpcomingEvent method uses first(where:)
which depends on array order and can return arbitrary matches; make selection
deterministic by explicitly choosing the correct event from matching candidates:
for the ongoing event (current) filter events matching the ongoing criteria and
pick the one with the latest startDate (use max(by: { $0.startDate <
$1.startDate }) or sorted last), and for the upcoming event filter candidates
that start after now and within gapThreshold and pick the earliest start (use
min(by: { $0.startDate < $1.startDate }) or sorted first) using the same
predicates currently in the closures; alternatively, if the collection is
guaranteed pre-sorted, add a doc comment on currentAndUpcomingEvent (and similar
methods like nextEvent()) stating that precondition instead of changing logic.

In `@MeetingBar/UI/StatusBar/StatusBarItemController.swift`:
- Around line 219-233: The MainActor.run call inside startFlipTimer is redundant
because StatusBarItemController is `@MainActor`; remove the await MainActor.run {
... } wrapper and perform self.showingUpcomingEvent.toggle() and
self.updateTitle() directly inside the Task loop (keeping the existing [weak
self] capture, flipTask handling, and Task cancellation checks). Update
startFlipTimer so the Task closure toggles showingUpcomingEvent and calls
updateTitle without dispatching to MainActor.run.
- Around line 254-270: The icon-setting block in StatusBarItemController (used
in renderNextUpcomingItem() and renderEvent()) should be extracted into a single
helper to remove duplication; add a private method like configureIcon(for event:
MBEvent?, on button: NSStatusBarButton) that encapsulates the
Defaults[.eventTitleIconFormat] check, uses
getIconForMeetingService(event?.meetingLink?.service) when format == .eventtype
or NSImage(named: Defaults[.eventTitleIconFormat].rawValue) otherwise, sets
button.image and button.image?.size = MenuStyleConstants.iconSize, then sets
button.imagePosition to .noImage if button.image?.name() == "no_online_session"
else .imageLeft, and replace the duplicated blocks in
renderNextUpcomingItem()/renderEvent() with calls to this new helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b25aff31-df25-489e-88ea-7bec5e76fab0

📥 Commits

Reviewing files that changed from the base of the PR and between 48373d7 and 5ccb0d9.

📒 Files selected for processing (7)
  • MeetingBar/Core/Models/MBEvent+Helpers.swift
  • MeetingBar/Extensions/DefaultsKeys.swift
  • MeetingBar/UI/StatusBar/StatusBarItemController.swift
  • MeetingBar/UI/Views/Preferences/AppearanceTab.swift
  • MeetingBar/Utilities/Constants.swift
  • MeetingBarTests/CurrentAndUpcomingEventTests.swift
  • MeetingBarTests/TimelineLogicTests.swift

Comment on lines +278 to +294
menuTitle.append(NSAttributedString(
string: "Next: ",
attributes: [.font: boldFont]
))

var eventText = upcomingTitle
if Defaults[.eventTimeFormat] == .show {
eventText += " " + upcomingTime
}

menuTitle.append(NSAttributedString(
string: eventText,
attributes: [.font: regularFont]
))

button.attributedTitle = menuTitle
button.toolTip = "Now: \(current.title)\nNext: \(upcoming.title)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded UI strings should be localized.

The "Next: " prefix (line 279) and tooltip format "Now: ... / Next: ..." (line 294) are hardcoded strings that won't be translated for non-English users.

🌐 Example fix
         menuTitle.append(NSAttributedString(
-            string: "Next: ",
+            string: "status_bar_flip_next_prefix".loco(),
             attributes: [.font: boldFont]
         ))
         ...
-        button.toolTip = "Now: \(current.title)\nNext: \(upcoming.title)"
+        button.toolTip = "status_bar_flip_tooltip".loco(current.title, upcoming.title)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MeetingBar/UI/StatusBar/StatusBarItemController.swift` around lines 278 -
294, The hardcoded UI strings ("Next: " and the tooltip "Now: ...\nNext: ...")
must be localized: replace the literal "Next: " used when appending to menuTitle
with a localized string (e.g. NSLocalizedString key like "StatusBar.NextPrefix")
and build eventText as before; and replace the literal tooltip construction with
a localized format string (e.g. NSLocalizedString key like
"StatusBar.Tooltip.NowNext" = "Now: %@\nNext: %@") and call
String(format:localizedTooltip, current.title, upcoming.title). Update usages
around menuTitle, button.attributedTitle and button.toolTip (and keep
Default[.eventTimeFormat] logic and attributes) so all user-facing text is
loaded via NSLocalizedString keys with appropriate placeholders for
upcomingTitle/upcomingTime/current.title/upcoming.title.

Comment on lines +251 to +269
Picker("Next event preview:", selection: $nextEventFlipMode) {
Text("Disabled").tag(NextEventFlipMode.disabled)
Text("Show after start").tag(NextEventFlipMode.showAfterStart)
Text("Show after 10 min").tag(NextEventFlipMode.showAfterTenMin)
}
.fixedSize()
.disabled(ongoingEventVisibility != .showTenMinBeforeNext)
.help(ongoingEventVisibility != .showTenMinBeforeNext
? "Requires \"Ongoing event visibility\" to be set to \"hide 10 min before next event\""
: "Alternate between showing the current and upcoming event in the status bar")

if nextEventFlipMode != .disabled && ongoingEventVisibility == .showTenMinBeforeNext {
Picker("Flip every", selection: $nextEventFlipIntervalSeconds) {
ForEach([2, 3, 5, 7, 10, 15], id: \.self) { sec in
Text("\(sec)s").tag(sec)
}
}
.fixedSize()
.help("How often to alternate between current and upcoming event")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded strings should be localized.

The new UI strings ("Next event preview:", "Disabled", "Show after start", "Show after 10 min", "Flip every") are hardcoded while the rest of this file uses .loco() for localization. This will cause issues for non-English users.

🌐 Example fix for localization
-                    Picker("Next event preview:", selection: $nextEventFlipMode) {
-                        Text("Disabled").tag(NextEventFlipMode.disabled)
-                        Text("Show after start").tag(NextEventFlipMode.showAfterStart)
-                        Text("Show after 10 min").tag(NextEventFlipMode.showAfterTenMin)
+                    Picker("preferences_appearance_status_bar_flip_mode_title".loco(), selection: $nextEventFlipMode) {
+                        Text("preferences_appearance_status_bar_flip_mode_disabled".loco()).tag(NextEventFlipMode.disabled)
+                        Text("preferences_appearance_status_bar_flip_mode_after_start".loco()).tag(NextEventFlipMode.showAfterStart)
+                        Text("preferences_appearance_status_bar_flip_mode_after_ten_min".loco()).tag(NextEventFlipMode.showAfterTenMin)
                     }

Apply similar changes to the help strings and "Flip every" picker.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MeetingBar/UI/Views/Preferences/AppearanceTab.swift` around lines 251 - 269,
The new picker labels and help strings in the UI (the Picker with label "Next
event preview:", the Text tags "Disabled", "Show after start", "Show after 10
min", the conditional Picker label "Flip every" and their .help(...) calls) are
hardcoded and must be localized like the rest of the file; replace those raw
strings with the localized equivalents using .loco() (or the existing
localization helper) when creating the Picker label and each Text(...) tag, and
wrap the help messages with .loco() as well, keeping the bindings
(nextEventFlipMode, NextEventFlipMode.*, nextEventFlipIntervalSeconds,
ongoingEventVisibility) and conditional logic unchanged.

Comment on lines +239 to +246
/// Simulates the flip activation guard from StatusBarItemController.
/// Returns true if the flip should be active for the given mode.
private func shouldFlip(mode: NextEventFlipMode, currentStart: Date) -> Bool {
guard mode != .disabled else { return false }
let minutesIntoCurrent = now.timeIntervalSince(currentStart) / 60
let minMinutesIn: Double = (mode == .showAfterTenMin) ? 10.0 : 0.0
return minutesIntoCurrent >= minMinutesIn
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Test simulation is incomplete - missing ongoingEventVisibility check.

The shouldFlip() helper simulates the flip activation guard but is missing a critical condition. The production code in StatusBarItemController.updateTitle() requires both:

  1. flipMode != .disabled
  2. Defaults[.ongoingEventVisibility] == .showTenMinBeforeNext

The test only verifies condition (1). This means tests could pass even if the visibility check were broken in production.

💚 Proposed fix to match production logic
     /// Simulates the flip activation guard from StatusBarItemController.
     /// Returns true if the flip should be active for the given mode.
-    private func shouldFlip(mode: NextEventFlipMode, currentStart: Date) -> Bool {
-        guard mode != .disabled else { return false }
+    private func shouldFlip(
+        mode: NextEventFlipMode,
+        ongoingVisibility: OngoingEventVisibility,
+        currentStart: Date
+    ) -> Bool {
+        guard mode != .disabled,
+              ongoingVisibility == .showTenMinBeforeNext else { return false }
         let minutesIntoCurrent = now.timeIntervalSince(currentStart) / 60
         let minMinutesIn: Double = (mode == .showAfterTenMin) ? 10.0 : 0.0
         return minutesIntoCurrent >= minMinutesIn
     }

Then update tests to pass the visibility parameter and add tests for when visibility is set to other values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@MeetingBarTests/CurrentAndUpcomingEventTests.swift` around lines 239 - 246,
The helper shouldFlip(mode: NextEventFlipMode, currentStart: Date) must mirror
production by also checking Defaults[.ongoingEventVisibility]; update its
signature to accept an ongoingEventVisibility parameter (or read Defaults inside
the helper) and return false unless ongoingEventVisibility ==
.showTenMinBeforeNext in addition to mode != .disabled and the time check;
update all tests that call shouldFlip and any uses in
CurrentAndUpcomingEventTests to pass/verify different ongoingEventVisibility
values and add tests covering visibility values other than .showTenMinBeforeNext
to ensure behavior matches StatusBarItemController.updateTitle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants