From e5360c1ce5bb6ba200cf67e74b7bb749f3b2d0e4 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 13:25:29 +0000 Subject: [PATCH 1/2] revert: keep the menu bar item in the test host The test host still hung with the item removed, so this was not the cause. Refs #46, closes #60 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TmmqmKmX8VDSfoPbzTS1ew --- App/StudyAppApp.swift | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/App/StudyAppApp.swift b/App/StudyAppApp.swift index 9039d7b..988df99 100644 --- a/App/StudyAppApp.swift +++ b/App/StudyAppApp.swift @@ -134,7 +134,7 @@ struct StudyAppApp: App { BackupCommands(viewModel: backupViewModel) } - MenuBarExtra(isInserted: menuBarItemInserted) { + MenuBarExtra(isInserted: $showsMenuBarItem) { MenuBarContent( summary: menuBarSummary, onOpenLecture: { moduleID in @@ -193,19 +193,6 @@ struct StudyAppApp: App { await notificationsViewModel.refresh() } - /// Keeps the menu bar item out of the unit test host: on CI runners its status item - /// blocks the launch, so the test runner never connects. - private var menuBarItemInserted: Binding { - Binding( - get: { showsMenuBarItem && !Self.isRunningAsTestHost }, - set: { showsMenuBarItem = $0 } - ) - } - - private static var isRunningAsTestHost: Bool { - ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil - } - private var menuBarSummary: MenuBarSummary { MenuBarSummary(calendar: calendarViewModel, dueCards: todayViewModel.totalDue) } From eb7c70e84d6d8c26025167a5480c367d45cbb9ab Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 13:25:44 +0000 Subject: [PATCH 2/2] fix(mac): observe the menu bar summary inside a view The App body read the calendar and the due cards, so every change rebuilt all scenes, already during launch, and the test host hung on CI. A small reader view now builds the summary for the item's label and window. Refs #46, closes #60 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TmmqmKmX8VDSfoPbzTS1ew --- App/StudyAppApp.swift | 44 +++++++++++------------ Presentation/MenuBar/MenuBarContent.swift | 12 +++++++ 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/App/StudyAppApp.swift b/App/StudyAppApp.swift index 988df99..e85b433 100644 --- a/App/StudyAppApp.swift +++ b/App/StudyAppApp.swift @@ -135,25 +135,29 @@ struct StudyAppApp: App { } MenuBarExtra(isInserted: $showsMenuBarItem) { - MenuBarContent( - summary: menuBarSummary, - onOpenLecture: { moduleID in - Self.bringToFront() - navigation.show(module: moduleID, section: .slides, intent: .resumeSlides) - }, - onReviewCards: { - Self.bringToFront() - // Starts with the module that has the most due cards, like „Lernen starten“ on Today. - if let moduleID = todayViewModel.dueCounts.max(by: { $0.value < $1.value })?.key { - navigation.show(module: moduleID, section: .flashcards) - } - }, - onOpenApp: { Self.bringToFront() } - ) - .tint(Theme.accent) - .preferredColorScheme(appearance.colorScheme) + MenuBarSummaryReader(calendar: calendarViewModel, today: todayViewModel) { summary in + MenuBarContent( + summary: summary, + onOpenLecture: { moduleID in + Self.bringToFront() + navigation.show(module: moduleID, section: .slides, intent: .resumeSlides) + }, + onReviewCards: { + Self.bringToFront() + // Starts with the module that has the most due cards, like „Lernen starten“ on Today. + if let moduleID = todayViewModel.dueCounts.max(by: { $0.value < $1.value })?.key { + navigation.show(module: moduleID, section: .flashcards) + } + }, + onOpenApp: { Self.bringToFront() } + ) + .tint(Theme.accent) + .preferredColorScheme(appearance.colorScheme) + } } label: { - MenuBarLabel(summary: menuBarSummary) + MenuBarSummaryReader(calendar: calendarViewModel, today: todayViewModel) { summary in + MenuBarLabel(summary: summary) + } } .menuBarExtraStyle(.window) @@ -193,10 +197,6 @@ struct StudyAppApp: App { await notificationsViewModel.refresh() } - private var menuBarSummary: MenuBarSummary { - MenuBarSummary(calendar: calendarViewModel, dueCards: todayViewModel.totalDue) - } - /// Shows the main window, e.g. after a click in the menu bar. private static func bringToFront() { NSApplication.shared.activate() diff --git a/Presentation/MenuBar/MenuBarContent.swift b/Presentation/MenuBar/MenuBarContent.swift index 9499508..6896019 100644 --- a/Presentation/MenuBar/MenuBarContent.swift +++ b/Presentation/MenuBar/MenuBarContent.swift @@ -1,6 +1,18 @@ import AppKit import SwiftUI +/// Builds the summary inside a view, so only the menu bar item observes the calendar and the due cards. +/// Reading them in the App body would rebuild every scene on each change, already while the app launches. +struct MenuBarSummaryReader: View { + let calendar: StudyCalendarViewModel + let today: TodayViewModel + @ViewBuilder let content: (MenuBarSummary) -> Content + + var body: some View { + content(MenuBarSummary(calendar: calendar, dueCards: today.totalDue)) + } +} + /// The menu bar item's label: an icon, plus the remaining minutes while a lecture runs. struct MenuBarLabel: View { let summary: MenuBarSummary