Repository navigation
Add Jump to Latest to the notifications popover - #1167
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds a localized "Jump to Latest" notification label, exposes and augments ShortcutAnnotation for accessibility, wires a persisted jump-to-latest shortcut into the notifications popover with a Jump to Latest action, and adds UI tests asserting the new button and shortcut badge. Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
Greptile SummaryThis PR adds a "Jump to Latest" button to the notifications popover header (left of "Clear All"), surfacing the existing Key changes:
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
actor User
participant Popover as NotificationsPopoverView
participant AppDelegate
participant FocusSystem as Window/Tab Focus
User->>Popover: Click "Jump to Latest" button
Popover->>Popover: jumpToLatestUnread()
Popover->>AppDelegate: DispatchQueue.main.async { jumpToLatestUnread() }
AppDelegate->>FocusSystem: Focus latest unread window + tab
Popover->>Popover: onDismiss() — closes popover
Last reviewed commit: 96069e1 |
| private func decodeShortcut(from data: Data, fallback: StoredShortcut) -> StoredShortcut { | ||
| guard !data.isEmpty, | ||
| let shortcut = try? JSONDecoder().decode(StoredShortcut.self, from: data) else { | ||
| return fallback | ||
| } | ||
| return shortcut | ||
| } |
There was a problem hiding this comment.
Duplicated decodeShortcut helper
This private decodeShortcut function is identical to the one already defined in NotificationsPage.swift (line 145–151). Both structs also duplicate hasUnreadNotifications (comparing NotificationsPage.swift line 141–143 with this file line 986–988). Now that ShortcutAnnotation has been made internal, consider extracting this decode helper to a shared extension or a free function (e.g., in a KeyboardShortcutSettings+Helpers.swift file) so both call-sites stay in sync if the decoding logic ever needs to change.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| private func jumpToLatestUnread() { | ||
| DispatchQueue.main.async { | ||
| AppDelegate.shared?.jumpToLatestUnread() | ||
| onDismiss() | ||
| } | ||
| } |
There was a problem hiding this comment.
Missing comment explaining DispatchQueue.main.async usage
The sibling open(_:) function (line 1005) carries an explicit comment explaining why DispatchQueue.main.async is required ("SwiftUI action closures are not guaranteed to run on the main actor"). For consistency and future maintainability, the same comment should be added here.
| private func jumpToLatestUnread() { | |
| DispatchQueue.main.async { | |
| AppDelegate.shared?.jumpToLatestUnread() | |
| onDismiss() | |
| } | |
| } | |
| private func jumpToLatestUnread() { | |
| // SwiftUI action closures are not guaranteed to run on the main actor. | |
| // Ensure window focus + tab selection happens on the main thread. | |
| DispatchQueue.main.async { | |
| AppDelegate.shared?.jumpToLatestUnread() | |
| onDismiss() | |
| } | |
| } |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| return | ||
| } |
There was a problem hiding this comment.
Hardcoded shortcut label assumes default user settings
XCTAssertEqual(shortcutBadge.label, "⌘⇧U") will fail if the test runner's UserDefaults already contain a customized value for the jumpToUnread shortcut key. Unlike the keyboard-trigger test (line 76) which sends the raw keys, this assertion relies on the UI-rendered string. Consider clearing the relevant defaults key before launching, or reading the expected label from the fallback shortcut rather than hardcoding the string, to make the test robust regardless of machine state.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
Sources/Update/UpdateTitlebarAccessory.swift (1)
979-996: Consider extracting shared shortcut decoding logic.The
jumpToUnreadShortcut,hasUnreadNotifications, anddecodeShortcut(from:fallback:)implementations are duplicated betweenNotificationsPopoverViewandNotificationsPage(lines 134-151 in NotificationsPage.swift). Consider extracting these to a shared extension or helper to reduce duplication.♻️ Suggested shared helper location
You could add these as extensions on
TerminalNotificationStoreor create a small helper struct:// e.g., in a shared file or extension extension TerminalNotificationStore { var hasUnreadNotifications: Bool { notifications.contains(where: { !$0.isRead }) } } // For shortcut decoding, a static helper could work: enum ShortcutDecoder { static func decode(from data: Data, fallback: StoredShortcut) -> StoredShortcut { guard !data.isEmpty, let shortcut = try? JSONDecoder().decode(StoredShortcut.self, from: data) else { return fallback } return shortcut } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/Update/UpdateTitlebarAccessory.swift` around lines 979 - 996, Extract the duplicated logic for shortcut decoding and unread checks into a shared helper: move decodeShortcut(from:fallback:) and jumpToUnreadShortcut usage out of NotificationsPopoverView and NotificationsPage into a reusable utility (e.g., a ShortcutDecoder type with static func decode(from:fallback:) or similar) and move hasUnreadNotifications into an extension on TerminalNotificationStore as var hasUnreadNotifications: Bool; then update NotificationsPopoverView and NotificationsPage to call ShortcutDecoder.decode(...) and TerminalNotificationStore.hasUnreadNotifications respectively so the duplicated implementations (jumpToUnreadShortcut, hasUnreadNotifications, decodeShortcut(from:fallback:)) are removed.cmuxUITests/MultiWindowNotificationsUITests.swift (1)
186-188: Consider documenting the hardcoded shortcut assumption.The assertion
XCTAssertEqual(shortcutBadge.label, "⌘⇧U")assumes the default shortcut forjumpToUnreadis Cmd+Shift+U. This is fine for validating default behavior, but consider adding a brief comment noting this tests the default shortcut in case the default changes in the future.let shortcutBadge = app.staticTexts["notificationsPopover.jumpToLatest.shortcut"] XCTAssertTrue(shortcutBadge.waitForExistence(timeout: 2.0), "Expected Jump to Latest shortcut badge") +// Validates default jumpToUnread shortcut (Cmd+Shift+U) XCTAssertEqual(shortcutBadge.label, "⌘⇧U")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmuxUITests/MultiWindowNotificationsUITests.swift` around lines 186 - 188, Add a short inline comment above the Shortcut assertion to document that this test expects the app's default keyboard shortcut for jumpToUnread (accessed via the UI element identifier "notificationsPopover.jumpToLatest.shortcut" and the variable shortcutBadge) to be "⌘⇧U"; update the comment to mention that if the app's default shortcut changes the test should be adjusted accordingly so future reviewers know this is an intentional hardcoded expectation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@cmuxUITests/MultiWindowNotificationsUITests.swift`:
- Around line 186-188: Add a short inline comment above the Shortcut assertion
to document that this test expects the app's default keyboard shortcut for
jumpToUnread (accessed via the UI element identifier
"notificationsPopover.jumpToLatest.shortcut" and the variable shortcutBadge) to
be "⌘⇧U"; update the comment to mention that if the app's default shortcut
changes the test should be adjusted accordingly so future reviewers know this is
an intentional hardcoded expectation.
In `@Sources/Update/UpdateTitlebarAccessory.swift`:
- Around line 979-996: Extract the duplicated logic for shortcut decoding and
unread checks into a shared helper: move decodeShortcut(from:fallback:) and
jumpToUnreadShortcut usage out of NotificationsPopoverView and NotificationsPage
into a reusable utility (e.g., a ShortcutDecoder type with static func
decode(from:fallback:) or similar) and move hasUnreadNotifications into an
extension on TerminalNotificationStore as var hasUnreadNotifications: Bool; then
update NotificationsPopoverView and NotificationsPage to call
ShortcutDecoder.decode(...) and TerminalNotificationStore.hasUnreadNotifications
respectively so the duplicated implementations (jumpToUnreadShortcut,
hasUnreadNotifications, decodeShortcut(from:fallback:)) are removed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2e940692-d57a-455d-ab71-78725aea9199
📒 Files selected for processing (4)
Resources/Localizable.xcstringsSources/NotificationsPage.swiftSources/Update/UpdateTitlebarAccessory.swiftcmuxUITests/MultiWindowNotificationsUITests.swift
There was a problem hiding this comment.
2 issues found across 4 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Sources/Update/UpdateTitlebarAccessory.swift">
<violation number="1" location="Sources/Update/UpdateTitlebarAccessory.swift:916">
P2: Inconsistent label: every other surface in the app calls this action "Jump to Latest Unread" (menus, keyboard shortcut settings, notifications page, status menu). Using "Jump to Latest" here is inconsistent and introduces a separate localization key (`notifications.jumpToLatest` vs `notifications.jumpToLatestUnread`) that translators must handle independently.</violation>
<violation number="2" location="Sources/Update/UpdateTitlebarAccessory.swift:927">
P2: Same inconsistency in the tooltip string — should use `"notifications.jumpToLatestUnread"` / `"Jump to Latest Unread"` to match the rest of the app.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| .accessibilityIdentifier("notificationsPopover.jumpToLatest") | ||
| .safeHelp( | ||
| KeyboardShortcutSettings.Action.jumpToUnread.tooltip( | ||
| String(localized: "notifications.jumpToLatest", defaultValue: "Jump to Latest") |
There was a problem hiding this comment.
P2: Same inconsistency in the tooltip string — should use "notifications.jumpToLatestUnread" / "Jump to Latest Unread" to match the rest of the app.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/Update/UpdateTitlebarAccessory.swift, line 927:
<comment>Same inconsistency in the tooltip string — should use `"notifications.jumpToLatestUnread"` / `"Jump to Latest Unread"` to match the rest of the app.</comment>
<file context>
@@ -910,6 +911,24 @@ private struct NotificationsPopoverView: View {
+ .accessibilityIdentifier("notificationsPopover.jumpToLatest")
+ .safeHelp(
+ KeyboardShortcutSettings.Action.jumpToUnread.tooltip(
+ String(localized: "notifications.jumpToLatest", defaultValue: "Jump to Latest")
+ )
+ )
</file context>
| String(localized: "notifications.jumpToLatest", defaultValue: "Jump to Latest") | |
| String(localized: "notifications.jumpToLatestUnread", defaultValue: "Jump to Latest Unread") |
| if !notificationStore.notifications.isEmpty { | ||
| Button(action: jumpToLatestUnread) { | ||
| HStack(spacing: 6) { | ||
| Text(String(localized: "notifications.jumpToLatest", defaultValue: "Jump to Latest")) |
There was a problem hiding this comment.
P2: Inconsistent label: every other surface in the app calls this action "Jump to Latest Unread" (menus, keyboard shortcut settings, notifications page, status menu). Using "Jump to Latest" here is inconsistent and introduces a separate localization key (notifications.jumpToLatest vs notifications.jumpToLatestUnread) that translators must handle independently.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/Update/UpdateTitlebarAccessory.swift, line 916:
<comment>Inconsistent label: every other surface in the app calls this action "Jump to Latest Unread" (menus, keyboard shortcut settings, notifications page, status menu). Using "Jump to Latest" here is inconsistent and introduces a separate localization key (`notifications.jumpToLatest` vs `notifications.jumpToLatestUnread`) that translators must handle independently.</comment>
<file context>
@@ -910,6 +911,24 @@ private struct NotificationsPopoverView: View {
if !notificationStore.notifications.isEmpty {
+ Button(action: jumpToLatestUnread) {
+ HStack(spacing: 6) {
+ Text(String(localized: "notifications.jumpToLatest", defaultValue: "Jump to Latest"))
+ ShortcutAnnotation(
+ text: jumpToUnreadShortcut.displayString,
</file context>
| Text(String(localized: "notifications.jumpToLatest", defaultValue: "Jump to Latest")) | |
| Text(String(localized: "notifications.jumpToLatestUnread", defaultValue: "Jump to Latest Unread")) |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cmuxUITests/MultiWindowNotificationsUITests.swift`:
- Around line 183-186: The test currently asserts the Jump to Latest button
badge equals a hard-coded "⌘⇧U" but the button reads its value from persisted
AppStorage via KeyboardShortcutSettings.Action.jumpToUnread.defaultsKey; before
asserting, seed the shortcut for this test (e.g., write the desired shortcut
into the same AppStorage/defaults key used by
KeyboardShortcutSettings.Action.jumpToUnread) or read that stored value and
assert the button.value against the seeded/read value; update the test to set
the shortcut into UserDefaults/AppStorage for the test run, then call
XCTAssertEqual(jumpButton.value as? String, seededShortcut) (referencing
jumpButton and KeyboardShortcutSettings.Action.jumpToUnread.defaultsKey).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3b0f3e09-301f-4f4e-9be5-d8a76d1f1723
📒 Files selected for processing (2)
Sources/Update/UpdateTitlebarAccessory.swiftcmuxUITests/MultiWindowNotificationsUITests.swift
| let jumpButton = app.buttons["notificationsPopover.jumpToLatest"] | ||
| XCTAssertTrue(jumpButton.waitForExistence(timeout: 6.0), "Expected Jump to Latest button in notifications popover") | ||
| XCTAssertEqual(jumpButton.value as? String, "⌘⇧U", "Expected Jump to Latest shortcut badge") | ||
|
|
There was a problem hiding this comment.
Seed the shortcut before asserting its exact badge text.
This test hard-codes ⌘⇧U, but the button reads its value from persisted @AppStorage. If another run changes KeyboardShortcutSettings.Action.jumpToUnread.defaultsKey, the feature still works and this assertion fails. Please set the shortcut explicitly for this test run, or assert against the value you seeded.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cmuxUITests/MultiWindowNotificationsUITests.swift` around lines 183 - 186,
The test currently asserts the Jump to Latest button badge equals a hard-coded
"⌘⇧U" but the button reads its value from persisted AppStorage via
KeyboardShortcutSettings.Action.jumpToUnread.defaultsKey; before asserting, seed
the shortcut for this test (e.g., write the desired shortcut into the same
AppStorage/defaults key used by KeyboardShortcutSettings.Action.jumpToUnread) or
read that stored value and assert the button.value against the seeded/read
value; update the test to set the shortcut into UserDefaults/AppStorage for the
test run, then call XCTAssertEqual(jumpButton.value as? String, seededShortcut)
(referencing jumpButton and
KeyboardShortcutSettings.Action.jumpToUnread.defaultsKey).
There was a problem hiding this comment.
🧹 Nitpick comments (1)
Sources/Update/UpdateTitlebarAccessory.swift (1)
991-997: Consider consolidating duplicatedecodeShortcuthelper.This method is duplicated verbatim in at least four files (
NotificationsPage.swift,WorkspaceContentView.swift,cmuxApp.swift, and here). Consider extracting it to a shared location (e.g., as a static method onStoredShortcutorKeyboardShortcutSettings) to reduce maintenance burden.♻️ Suggested consolidation
Add to
KeyboardShortcutSettingsorStoredShortcut:extension StoredShortcut { static func decode(from data: Data, fallback: StoredShortcut) -> StoredShortcut { guard !data.isEmpty, let shortcut = try? JSONDecoder().decode(StoredShortcut.self, from: data) else { return fallback } return shortcut } }Then replace all call sites with:
private var jumpToUnreadShortcut: StoredShortcut { StoredShortcut.decode(from: jumpToUnreadShortcutData, fallback: KeyboardShortcutSettings.Action.jumpToUnread.defaultShortcut) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/Update/UpdateTitlebarAccessory.swift` around lines 991 - 997, Several files duplicate the decodeShortcut implementation; extract it to a single shared API such as an extension on StoredShortcut (e.g., StoredShortcut.decode(from:fallback:)) and replace the local private func decodeShortcut(...) implementations with calls to that new static method. Update callers like the computed property jumpToUnreadShortcut to use StoredShortcut.decode(from: jumpToUnreadShortcutData, fallback: KeyboardShortcutSettings.Action.jumpToUnread.defaultShortcut) and remove the private decodeShortcut function from UpdateTitlebarAccessory.swift and the other files (NotificationsPage.swift, WorkspaceContentView.swift, cmuxApp.swift).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@Sources/Update/UpdateTitlebarAccessory.swift`:
- Around line 991-997: Several files duplicate the decodeShortcut
implementation; extract it to a single shared API such as an extension on
StoredShortcut (e.g., StoredShortcut.decode(from:fallback:)) and replace the
local private func decodeShortcut(...) implementations with calls to that new
static method. Update callers like the computed property jumpToUnreadShortcut to
use StoredShortcut.decode(from: jumpToUnreadShortcutData, fallback:
KeyboardShortcutSettings.Action.jumpToUnread.defaultShortcut) and remove the
private decodeShortcut function from UpdateTitlebarAccessory.swift and the other
files (NotificationsPage.swift, WorkspaceContentView.swift, cmuxApp.swift).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b7f5e64f-16c4-4658-8e11-419f59c6db22
📒 Files selected for processing (2)
Sources/Update/UpdateTitlebarAccessory.swiftcmuxUITests/MultiWindowNotificationsUITests.swift
🚧 Files skipped from review as they are similar to previous changes (1)
- cmuxUITests/MultiWindowNotificationsUITests.swift
There was a problem hiding this comment.
🧹 Nitpick comments (1)
Sources/Update/UpdateTitlebarAccessory.swift (1)
988-994: Consider centralizing shortcut decoding.Lines 988-994 are now another copy of the same JSON decode/fallback helper already used in
Sources/NotificationsPage.swift,Sources/WorkspaceContentView.swift, andSources/cmuxApp.swift. Pulling this into a shared helper onKeyboardShortcutSettingsorStoredShortcutwould reduce drift the next time the stored format changes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/Update/UpdateTitlebarAccessory.swift` around lines 988 - 994, The decodeShortcut(from:fallback:) helper in UpdateTitlebarAccessory (the private function decodeShortcut) duplicates logic used elsewhere; move this JSON decode + fallback logic into a single shared method on either KeyboardShortcutSettings or StoredShortcut (e.g., StoredShortcut.decode(from:fallback:) or KeyboardShortcutSettings.decodeShortcut(from:fallback:)) and replace the local decodeShortcut implementation with calls to that new static/instance helper from UpdateTitlebarAccessory, NotificationsPage, WorkspaceContentView, and cmuxApp so all code uses the centralized decoder and avoids drift when the stored format changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@Sources/Update/UpdateTitlebarAccessory.swift`:
- Around line 988-994: The decodeShortcut(from:fallback:) helper in
UpdateTitlebarAccessory (the private function decodeShortcut) duplicates logic
used elsewhere; move this JSON decode + fallback logic into a single shared
method on either KeyboardShortcutSettings or StoredShortcut (e.g.,
StoredShortcut.decode(from:fallback:) or
KeyboardShortcutSettings.decodeShortcut(from:fallback:)) and replace the local
decodeShortcut implementation with calls to that new static/instance helper from
UpdateTitlebarAccessory, NotificationsPage, WorkspaceContentView, and cmuxApp so
all code uses the centralized decoder and avoids drift when the stored format
changes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 12a9b362-a659-4041-8019-e85c03e92a96
📒 Files selected for processing (1)
Sources/Update/UpdateTitlebarAccessory.swift
* Add jump-to-latest button to notifications popover * Fix jump-to-latest popover accessibility * Relax shortcut badge UI test expectation * Stabilize jump-to-latest UI coverage * Keep popover notification actions visible when empty * Inline jump-to-latest shortcut label * Match jump-to-latest shortcut label styling
Summary
Testing
Issues
Summary by cubic
Adds a Jump to Latest button in the notifications popover that focuses the most recent unread notification and closes the popover. It sits before Clear All and shows the jump shortcut inline (⌘⇧U by default), fulfilling the Linear task.
Clear All.Written for commit 4910fb7. Summary will update on new commits.
Summary by CodeRabbit
New Features
Accessibility
Localization
Tests