Fix high CPU usage from notifications and add regression tests

- Fix auto-updating Text(date, style: .time) causing continuous SwiftUI updates
  by using static formatting with .formatted(date:time:)
- Fix notification popover keeping SwiftUI observers active when closed by
  clearing contentViewController on popover close and recreating on open
- Fix focus loss when notifications arrive while typing by only setting
  focus in NotificationsPage when the page is visible
- Make Update Pill and Update Logs debug-only features
- Add CPU regression tests: test_cpu_usage.py, test_cpu_notifications.py
- Add lint test for auto-updating Text patterns: test_lint_swiftui_patterns.py
This commit is contained in:
Lawrence Chen 2026-01-29 17:02:16 -08:00
parent 5e6aad94c4
commit eb7c06ceb1
7 changed files with 628 additions and 14 deletions

View file

@ -20,6 +20,7 @@ final class UpdateLogStore {
}
func append(_ message: String) {
#if DEBUG
let timestamp = formatter.string(from: Date())
let line = "[\(timestamp)] \(message)"
queue.async { [weak self] in
@ -30,6 +31,7 @@ final class UpdateLogStore {
}
appendToFile(line: line)
}
#endif
}
func snapshot() -> String {

View file

@ -79,8 +79,12 @@ private struct TitlebarAccessoryView: View {
@ObservedObject var model: UpdateViewModel
var body: some View {
#if DEBUG
UpdatePill(model: model)
.padding(.trailing, 8)
.padding(.trailing, 8)
#else
EmptyView()
#endif
}
}
@ -208,9 +212,7 @@ private struct NotificationsAnchorView: NSViewRepresentable {
}
func updateNSView(_ nsView: NSView, context: Context) {
DispatchQueue.main.async {
onResolve(nsView)
}
// Only need to resolve once in makeNSView - the view reference doesn't change
}
}
@ -417,6 +419,15 @@ final class TitlebarControlsAccessoryViewController: NSTitlebarAccessoryViewCont
notificationsPopover.performClose(nil)
return
}
// Recreate content view each time to avoid stale observers when popover is hidden
notificationsPopover.contentViewController = NSHostingController(
rootView: NotificationsPopoverView(
notificationStore: notificationStore,
onDismiss: { [weak notificationsPopover] in
notificationsPopover?.performClose(nil)
}
)
)
let anchorView = viewModel.notificationsAnchorView ?? hostingView
notificationsPopover.animates = animated
notificationsPopover.show(relativeTo: anchorView.bounds, of: anchorView, preferredEdge: .maxY)
@ -427,16 +438,16 @@ final class TitlebarControlsAccessoryViewController: NSTitlebarAccessoryViewCont
popover.behavior = .semitransient
popover.animates = true
popover.delegate = self
popover.contentViewController = NSHostingController(
rootView: NotificationsPopoverView(
notificationStore: notificationStore,
onDismiss: { [weak popover] in
popover?.performClose(nil)
}
)
)
// Content view controller is set dynamically in toggleNotificationsPopover
return popover
}
// MARK: - NSPopoverDelegate
func popoverDidClose(_ notification: Notification) {
// Clear the content view controller to stop SwiftUI observers when popover is hidden
notificationsPopover.contentViewController = nil
}
}
private struct NotificationsPopoverView: View {
@ -557,7 +568,7 @@ private struct NotificationPopoverRow: View {
.font(.headline)
.foregroundColor(.primary)
Spacer()
Text(notification.createdAt, style: .time)
Text(notification.createdAt.formatted(date: .omitted, time: .shortened))
.font(.caption)
.foregroundColor(.secondary)
}