Notifications UX: Jump to Latest Unread + per-tab / per-surface dots + tap deeplinks#266
Merged
Notifications UX: Jump to Latest Unread + per-tab / per-surface dots + tap deeplinks#266
Conversation
Adds ⌘⇧U to jump to the newest unread notification across worktrees, selecting the worktree + focusing the source surface and marking only that notification as read; the menu item disables when nothing is unread. Surfaces now render an orange dot overlay while they have unread notifications, mirroring the sidebar indicator. System notifications carry a deeplink payload so tapping one routes through the existing deeplink handler to land on the exact tab + surface that posted it. Closes #244.
- Tab notification dot now lives in the close-button slot: visible when the tab is idle and has unread notifications, replaced by the close X on hover. - `SystemNotificationClient` delegate methods now run on the main actor directly, so accessing `onDeeplinkTap` no longer needs a bridging hop. Added `SupaLogger` breadcrumbs for malformed deeplink payloads, unresolved surface deeplinks, and stale-worktree jumps. - `latestUnreadNotificationLocation` now walks each worktree's unread list newest-first until it finds a focusable surface, so a closed surface on the newest notification no longer hides older focusable ones. Logs once when every unread points at a closed surface. - Collapsed duplicate `tabID(forSurfaceID:)` / `tabId(containing:)` into a single `tabID(containing:)` on `WorktreeTerminalState`. - Dropped the `createdAt = Date()` default on `WorktreeTerminalNotification.init` so production code must pass the injected clock; tests use `.distantPast`. - Surface dot now animates via an always-mounted `.opacity`/`.animation` pair rather than a transition without a driver. - Added `WorktreeTerminalManagerTests` coverage for cross-worktree ordering, closed-surface fallback, tab-level unread aggregation, and `markNotificationRead` targeting a single id. - Tightened the happy-path jump test to assert exact focus command.
- `urlOrWarn` now carries `worktreeID` / `surfaceID` into its warning so diagnostics correlate to the originating surface. - Log a debug breadcrumb when `latestUnreadNotificationLocation` skips a closed surface in favour of an older focusable one, preserving the "which notification was chosen" trace. - `jumpToLatestUnread` logs a debug line when invoked with no unread, so the two no-op branches (menu gated vs stale worktree) are distinguishable in logs. - New test covers the cross-worktree tie-break path: worktree A's newest unread is orphaned, A's older focusable is older than worktree B's only focusable, B wins. - `hasUnseenNotificationForTabIDWalksSplitTree` now asserts the first leaf also lights the tab (previously only the second leaf was exercised), and drops the `_ = firstLeaf` warning-silencer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
AppShortcuts.jumpToLatestUnread.UNNotificationnow carries asupacode://worktree/<id>/tab/<tab>/surface/<surface>payload. Tapping it dispatches adeeplinkReceivedaction, so the user lands on the exact surface that posted.Notable internals
WorktreeTerminalNotification.createdAt(new) is driven by the injecteddatedependency, so ordering is deterministic in tests.WorktreeTerminalManager.latestUnreadNotificationLocation()iterates each worktree's unread list newest-first and falls through closed surfaces to the next focusable one.SystemNotificationClient.sendnow takes an optional deeplink URL stored inuserInfo. The delegate uses the asyncdidReceive:variant on the main actor.NotificationLocationvalue type ties (worktree, tab, surface, notification) together for the reducer path.SupaLoggerbreadcrumbs (degraded deeplink, stale worktree, skipped closed surface, malformed URL payload).Test plan
make test— 951 tests pass.make check— lint + format clean.Closes #244.