Skip to content

Fix desktop notification permission settings row - #6960

Merged
austinywang merged 30 commits into
mainfrom
issue-5919-settings-desktop-notifications-row-stuck-on-p
Jul 28, 2026
Merged

austinywang merged 30 commits into
mainfrom
issue-5919-settings-desktop-notifications-row-stuck-on-p

Conversation

@austinywang

@austinywang austinywang commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5919\n\n## Summary\n- Wire Settings > App > Desktop Notifications to the host notification authorization state instead of a static unknown baseline\n- Refresh permission state on row observation and route determined states to the app-specific System Settings notifications pane\n- Add regression coverage for granted/denied presentation and row observation refresh\n\n## Validation\n- swift test --package-path Packages/macOS/CmuxSettingsUI\n- python3 -m json.tool Resources/Localizable.xcstrings >/dev/null\n- python3 scripts/check-workspace-package-groups.py --check\n- git diff --check\n\nNo dev build, reload script, or xcodebuild run per request.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Fixes #5919. The Desktop Notifications settings row now reflects live macOS permission, shows the right action (Enable or Open System Settings), and gates “Send Test” correctly (enabled for authorized/provisional/ephemeral/not‑determined; disabled for unknown/denied).

  • Bug Fixes
    • Desktop Notifications: added a live DesktopNotificationAuthorizationModel, a dynamic settings row with provisional/ephemeral states, proper status labels/actions, and expanded localized strings.
    • Host/TerminalNotificationStore: stream authorization updates, refresh on row observation, emit changes only on state transitions, and open the app’s notifications pane in System Settings.
    • Tests: added coverage for permission presentation and model lifecycle; stabilized design‑mode screenshot recovery.

Written for commit ff63482. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added desktop-notification permission support to Settings with live status updates, a refresh flow, and actions to request permission or open system settings.
    • Introduced a dedicated “Desktop Notifications” settings row, including an optional Send Test button (enabled when permission is granted).
  • Bug Fixes
    • Notification UI now updates only when the permission state actually changes.
    • Improved cancellation handling while loading file explorer contents.
  • Documentation
    • Expanded localized desktop-notification labels, subtitles, and action text across many languages.
  • Tests
    • Added coverage for desktop-notification permission presentation and model lifecycle behavior.

@vercel

vercel Bot commented Jun 26, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 5, 2026 2:05am
cmux-staging Building Building Preview, Comment Jul 5, 2026 2:05am

@coderabbitai

coderabbitai Bot commented Jun 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Desktop notification authorization now flows from host state into a new macOS settings model and row. Crash-storage symlink handling, file-loading cancellation, and Claude stream character counting are also updated.

Changes

Desktop notification settings

Layer / File(s) Summary
Permission presentation
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationAuthorizationState.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionAction.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionStatusLabel.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionSubtitle.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionPresentation.swift, Resources/Localizable.xcstrings, Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/DesktopNotificationPermissionPresentationTests.swift
DesktopNotificationAuthorizationState maps to localized status, subtitle, action, and send-test presentation values, and the presentation tests cover the state mapping.
Host authorization bridge
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Environment/SettingsHostActions.swift, Sources/HostSettingsActions.swift, Sources/TerminalNotificationStore.swift
Host actions now expose desktop authorization reads, update streams, and refresh calls, and the notification store posts a change notification when authorization state changes.
Settings model and row
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Bindings/DesktopNotificationAuthorizationModel.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Bindings/SettingObservationStarting.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationsSettingsRow.swift, Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/CountingMobilePairingHostActions.swift, Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/DesktopNotificationAuthorizationModelLifecycleTests.swift
The settings section owns a desktop-notification observation model, starts it with host actions, renders the new row, and includes lifecycle coverage for reading, streaming, and refreshing authorization state.

Storage and loading fixes

Layer / File(s) Summary
Crash storage checks
Sources/SessionPersistencePolicy+CrashStorage.swift, Sources/AppDelegate+CmuxSSHURL.swift
Crash-storage detection now checks symlink destinations, and the file-open path uses the combined crash-storage-or-symlink check.
File loading cancellation
Sources/FileExplorerStore.swift
loadChildren(for:at:silent:) now returns when the task is cancelled as well as when the provider is missing.

Stream parsing fix

Layer / File(s) Summary
Delta character counting
Sources/Panels/ClaudeStreamJSONAccumulator.swift
consumeLine now updates character counts only for content_block_delta objects, and the file ends with a whitespace adjustment.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

A bunny saw the status bloom,
Then hopped through symlinks, room by room.
The stream kept counts for deltas bright,
The settings row now shows it right.
Hop hop—truth updates in tune 🐰

🚥 Pre-merge checks | ✅ 22 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR also changes crash-storage handling, file-explorer cancellation, and Claude stream parsing, which are unrelated to #5919. Move those unrelated fixes into separate PRs so this one stays focused on the desktop notification settings row.
Docstring Coverage ⚠️ Warning Docstring coverage is 19.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning Summary and validation are present, but the required Testing, Demo Video, Review Trigger, and Checklist sections are missing or incomplete. Add the missing template sections with testing details, a demo video link, the review-trigger comment block, and the checklist items.
✅ Passed checks (22 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The row now reflects live authorization state, refreshes on observation, uses Open System Settings when denied, and includes regression tests.
Cmux Swift Actor Isolation ✅ Passed New notification settings model/host APIs are intentionally @MainActor; no new implicit MainActor value models or unsafe Sendable references were introduced.
Cmux Swift Blocking Runtime ✅ Passed The diff adds MainActor/AsyncStream observation and tests only; no new semaphores, sleeps, sync waits, timers, or manual locks were introduced.
Cmux Browser Automation Off-Main ✅ Passed PASS: The PR only changes Settings/UI, localization, tests, and notification host plumbing; it doesn't touch the browser-automation router files the rule targets.
Cmux Expensive Synchronous Load ✅ Passed No new sync agent-history load was added to a main/interactive path; the relevant loader remains off-main via Task.detached, and no new direct load call sites appear in the diff.
Cmux Cache Substitution Correctness ✅ Passed Desktop notification state is transient UI only, seeded from an authoritative host read and kept fresh via an event-driven stream; no persistence/snapshot path is involved.
Cmux No Hacky Sleeps ✅ Passed No changed files fall in the rule’s TS/JS/shell/non-Swift-runtime scope, and the touched files show no new hacky waits.
Cmux Algorithmic Complexity ✅ Passed The diff adds only constant-time state mapping/stream hookup; no new scans, sorts, or per-target rescans on scalable collections.
Cmux Swift Concurrency ✅ Passed The new notification-state flow uses AsyncStream, @Observable, and a stored/cancelled Task; no new DispatchQueue/global, Combine app-state, or fire-and-forget tasks were introduced.
Cmux Swift @Concurrent ✅ Passed No changed Swift code misuses @concurrent; the new async work is UI-bound on @MainActor and the off-actor refresh path already hops explicitly.
Cmux Swift File And Package Boundaries ✅ Passed PASS: New Swift files are small, single-purpose package/UI/model types; oversized AppSection and TerminalNotificationStore were only incidentally touched and shrank.
Cmux Swiftpm Lockfiles ✅ Passed PASS: The PR touches UI/source/test/localization files only; no Package.swift, .gitignore, xcodeproj, or Package.resolved changes appear, so the lockfile rule isn’t triggered.
Cmux Swift Logging ✅ Passed The diff adds only a cancel-safety guard in FileExplorerStore; no new print/NSLog/dump or Logger changes were introduced or modified.
Cmux User-Facing Error Privacy ✅ Passed PASS: the new desktop-notification copy is generic status/permission UI only; no vendor names, internal details, raw errors, or recovery copy were introduced.
Cmux Full Internationalization ✅ Passed All new user-facing Swift text uses String(localized:defaultValue:), and every new desktop-notification xcstrings key has translated entries for all 20 catalog locales.
Cmux Swiftui State Layout ✅ Passed Uses @Observable + @State for the new model, no GeometryReader/lazy row store refs, and state writes happen in .task/startObserving, not during render.
Cmux Architecture Rethink ✅ Passed PASS: the new observer/AsyncStream is a required bridge from TerminalNotificationStore into the settings model; state still has one owner and no timing hacks.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No standalone window code was added or changed; the PR only touches settings-row/model code, with no NSWindow/NSPanel/WindowGroup or cmuxAuxiliaryWindowIdentifiers edits.
Cmux Source Artifacts ✅ Passed Only changed path is a normal Swift source file; no added paths match the policy’s artifact/temp/build/cache patterns.
Cmux No Test Or Debug Seam In Production Source ✅ Passed New desktop-notification APIs are product-facing and used by AppSection; no added #if DEBUG or *ForTesting/*debug seam appears in the changed production sources.
Cmux No Ambient Global State ✅ Passed New notification flow uses injectable model/protocol types; no new file-scope funcs, mutable globals, or singletons were introduced.
Title check ✅ Passed The title is concise and clearly describes the main UI fix in the PR.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-5919-settings-desktop-notifications-row-stuck-on-p

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.

@austinywang
austinywang force-pushed the issue-5919-settings-desktop-notifications-row-stuck-on-p branch from faaf241 to 36b9115 Compare June 26, 2026 23:35
@greptile-apps

greptile-apps Bot commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR wires the Desktop Notifications settings row to live macOS permission state via a new DesktopNotificationAuthorizationModel observable, replaces the static .notDetermined baseline with a real UNUserNotificationCenter refresh cycle, and routes the "Open System Settings" action to the app-specific notifications pane. Secondary changes add a content-revision guard to FileExplorerStore to prevent stale-task population races, restrict ClaudeStreamJSONAccumulator character counting to content_block_delta events only, and strengthen the crash-storage symlink check in SessionPersistencePolicy.

  • Desktop Notifications row: DesktopNotificationAuthorizationModel subscribes to a TerminalNotificationStore-backed AsyncStream, refreshes OS state on each row observation, and maps all six UNAuthorizationStatus states to a DesktopNotificationPermissionPresentation that drives status label, subtitle, primary action button, and Send Test enabled/disabled gating. All 20 supported locales have translations for every new string key.
  • FileExplorer cancellation safety: contentRevision is captured at task-spawn time and checked before and after the async listDirectory call, so a cancelAllLoads() or reload() that bumps the revision between the spawn and the I/O result silently discards the stale population.
  • BrowserPanel / test isolation: NotificationCenter is now injectable on BrowserPanel, allowing background/proxy subscription tests to drive the live publisher path through a private center without contaminating NotificationCenter.default.

Confidence Score: 5/5

Safe to merge — the permission model, stream lifecycle, and presentation mapping are all correct, and the secondary fixes are well-scoped.

The authorization stream is correctly wired: the SettingReadDriver owns the subscription lifetime, onTermination removes the NotificationCenter observer, and the DesktopNotificationAuthorizationModel refreshes OS state on each row observation without re-subscribing. All six UNAuthorizationStatus cases are mapped and covered by tests. Localization is complete across all 20 supported locales. No actor isolation mistakes, no blocking primitives, no test seams in production source.

No files require special attention.

Important Files Changed

Filename Overview
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Bindings/DesktopNotificationAuthorizationModel.swift New @observable @mainactor view-model that owns the host-authorization stream subscription; one-shot driver activation via SettingReadDriver, refresh-on-observe semantics correct.
Sources/HostSettingsActions.swift Adds desktopNotificationAuthorizationStatusUpdates() AsyncStream backed by a NotificationCenter signal; actor isolation (@mainactor class, drainTask @mainactor) and cleanup (onTermination) are correct.
Sources/TerminalNotificationStore.swift Adds authorizationStatusDidChangeNotification and a didSet guard on authorizationState; correctly posts only on changes, on MainActor.
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionPresentation.swift Value-type presentation layer mapping all six authorization states to label/subtitle/action/sendTestEnabled; previous PR thread's sendTestEnabled bugs are resolved.
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationsSettingsRow.swift New SwiftUI row consuming DesktopNotificationPermissionPresentation; presentation is computed inside body (value type, no state mutation at render time).
Sources/FileExplorerStore.swift Content revision captured at task-spawn time and double-checked before and after async I/O; correctly prevents stale population races on reload/cancelAllLoads.
Sources/Panels/BrowserPanel.swift NotificationCenter made injectable (default .default); subscriptions for ghosttyDefaultBackgroundDidChange and browserSystemProxySettingsDidChange now use the injected center.
Sources/Panels/ClaudeStreamJSONAccumulator.swift Character counting now gated on type == content_block_delta; text is still returned for other event types but excluded from stream accounting.
Sources/SessionPersistencePolicy+CrashStorage.swift New isCmuxCrashStorageURLOrSymlink checks the input URL, its pre-resolved form, and the symlink destination to close a gap where a symlink into the crash-diagnostic folder wasn't blocked.
Resources/Localizable.xcstrings All 12 new desktop-notification string keys fully translated across all 20 supported locales; no locale gaps detected.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant AppSection as AppSection (.task)
    participant Model as DesktopNotificationAuthorizationModel
    participant Driver as SettingReadDriver
    participant Host as HostSettingsActions
    participant Store as TerminalNotificationStore
    participant OS as UNUserNotificationCenter

    AppSection->>Model: startObserving()
    Model->>Host: currentStatus()
    Host->>Store: authorizationState (sync read)
    Store-->>Host: .unknown / current
    Host-->>Model: DesktopNotificationAuthorizationState
    Model->>Model: "current = state (sync)"
    Model->>Driver: activate(makeStream)
    Driver->>Host: desktopNotificationAuthorizationStatusUpdates()
    Host->>Host: addObserver(authorizationStatusDidChangeNotification)
    Host-->>Driver: "AsyncStream<State>"
    Driver->>Driver: "Task @MainActor { for await state in stream }"
    Host->>Host: drainTask: yield(currentState)
    Driver->>Model: "sink(currentState) -> current updated"
    Model->>Host: refreshDesktopNotificationAuthorizationStatus()
    Host->>Store: refreshAuthorizationStatus()
    Store->>OS: getNotificationSettings()
    OS-->>Store: UNNotificationSettings (async)
    Store->>Store: "authorizationState = mapped (didSet)"
    Store->>Store: didSet: post(authorizationStatusDidChangeNotification)
    Host->>Host: "observer fires -> signalContinuation.yield(())"
    Host->>Host: drainTask picks up signal, yields new state
    Driver->>Model: "sink(newState) -> current updated"
    Model->>AppSection: "@Observable triggers SwiftUI re-render"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant AppSection as AppSection (.task)
    participant Model as DesktopNotificationAuthorizationModel
    participant Driver as SettingReadDriver
    participant Host as HostSettingsActions
    participant Store as TerminalNotificationStore
    participant OS as UNUserNotificationCenter

    AppSection->>Model: startObserving()
    Model->>Host: currentStatus()
    Host->>Store: authorizationState (sync read)
    Store-->>Host: .unknown / current
    Host-->>Model: DesktopNotificationAuthorizationState
    Model->>Model: "current = state (sync)"
    Model->>Driver: activate(makeStream)
    Driver->>Host: desktopNotificationAuthorizationStatusUpdates()
    Host->>Host: addObserver(authorizationStatusDidChangeNotification)
    Host-->>Driver: "AsyncStream<State>"
    Driver->>Driver: "Task @MainActor { for await state in stream }"
    Host->>Host: drainTask: yield(currentState)
    Driver->>Model: "sink(currentState) -> current updated"
    Model->>Host: refreshDesktopNotificationAuthorizationStatus()
    Host->>Store: refreshAuthorizationStatus()
    Store->>OS: getNotificationSettings()
    OS-->>Store: UNNotificationSettings (async)
    Store->>Store: "authorizationState = mapped (didSet)"
    Store->>Store: didSet: post(authorizationStatusDidChangeNotification)
    Host->>Host: "observer fires -> signalContinuation.yield(())"
    Host->>Host: drainTask picks up signal, yields new state
    Driver->>Model: "sink(newState) -> current updated"
    Model->>AppSection: "@Observable triggers SwiftUI re-render"
Loading

Reviews (29): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

@austinywang
austinywang force-pushed the issue-5919-settings-desktop-notifications-row-stuck-on-p branch from 36b9115 to 0ef3109 Compare June 26, 2026 23:44
@austinywang
austinywang force-pushed the issue-5919-settings-desktop-notifications-row-stuck-on-p branch from 0ef3109 to 96aff1c Compare June 26, 2026 23:50
@austinywang
austinywang force-pushed the issue-5919-settings-desktop-notifications-row-stuck-on-p branch from 96aff1c to d04323d Compare June 27, 2026 07:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionPresentation.swift`:
- Around line 13-19: The `.unknown` case in
`DesktopNotificationPermissionPresentation` is incorrectly mapped to the live
`requestAuthorization` action, which lets `DesktopNotificationsSettingsRow`
expose a stale Enable path before the authoritative host state is available.
Update the `.unknown` branch to fail closed by using no primary action or a
disabled/loading presentation instead of `requestAuthorization`, and keep
`sendTestEnabled` false until a real permission state is received.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f21b3b6c-97dd-4f3b-907a-1a552989fb84

📥 Commits

Reviewing files that changed from the base of the PR and between 015afa6 and d04323d.

📒 Files selected for processing (16)
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Bindings/DesktopNotificationAuthorizationModel.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Bindings/SettingObservationStarting.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Environment/SettingsHostActions.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationAuthorizationState.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionAction.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionPresentation.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionStatusLabel.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionSubtitle.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationsSettingsRow.swift
  • Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/CountingMobilePairingHostActions.swift
  • Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/DesktopNotificationAuthorizationModelLifecycleTests.swift
  • Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/DesktopNotificationPermissionPresentationTests.swift
  • Resources/Localizable.xcstrings
  • Sources/HostSettingsActions.swift
  • Sources/TerminalNotificationStore.swift

@austinywang
austinywang force-pushed the issue-5919-settings-desktop-notifications-row-stuck-on-p branch from d04323d to 86d7fb5 Compare June 27, 2026 07:38
@austinywang
austinywang force-pushed the issue-5919-settings-desktop-notifications-row-stuck-on-p branch from 86d7fb5 to 9488e48 Compare June 27, 2026 07:50
@austinywang
austinywang force-pushed the issue-5919-settings-desktop-notifications-row-stuck-on-p branch from 9488e48 to 306d4ef Compare June 27, 2026 07:58
cmux added 2 commits June 28, 2026 20:21
…esktop-notifications-row-stuck-on-p

# Conflicts:
#	Resources/Localizable.xcstrings

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Bindings/DesktopNotificationAuthorizationModel.swift`:
- Around line 39-47: The startObserving() method in
DesktopNotificationAuthorizationModel currently exits early after the first
call, so currentStatus() and refreshStatus() are skipped on later observations
and stale notification authorization can persist. Keep the
driver.activate(makeStream) subscription guarded by hasStarted, but move the
current = currentStatus() read and refreshStatus() call so they run on every
startObserving() invocation. Use the existing startObserving(), currentStatus(),
refreshStatus(), and hasStarted symbols to update the model state without
re-subscribing.

In
`@Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionPresentation.swift`:
- Around line 13-18: The `.unknown` branch in
`DesktopNotificationPermissionPresentation` still falls back to the
`.notDetermined` subtitle, which shows stale permission text before the
authoritative host state is available. Update the `.unknown` case to use a
neutral/loading subtitle or no subtitle at all, keeping the UI fail-closed until
the reliable state arrives, and leave the `.notDetermined` copy only for the
real not-determined state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a02ad097-9cf2-4218-a88e-f1e238b66a93

📥 Commits

Reviewing files that changed from the base of the PR and between d04323d and 71c057d.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (16)
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Bindings/DesktopNotificationAuthorizationModel.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Bindings/SettingObservationStarting.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Environment/SettingsHostActions.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationAuthorizationState.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionAction.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionPresentation.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionStatusLabel.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionSubtitle.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationsSettingsRow.swift
  • Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/CountingMobilePairingHostActions.swift
  • Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/DesktopNotificationAuthorizationModelLifecycleTests.swift
  • Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/DesktopNotificationPermissionPresentationTests.swift
  • Resources/Localizable.xcstrings
  • Sources/HostSettingsActions.swift
  • Sources/TerminalNotificationStore.swift
💤 Files with no reviewable changes (3)
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationAuthorizationState.swift
  • Sources/TerminalNotificationStore.swift
  • Resources/Localizable.xcstrings

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/FileExplorerStore.swift`:
- Line 1050: The early return in the load/task setup path skips cleanup when
Task.isCancelled is already true, leaving child.isLoading and the task/path
state stale. Update the cancellation handling around the provider guard so that
any pre-start cancellation still clears the loading flag and associated
task/path bookkeeping before returning. Keep the fix in the same loading flow
that the auto-expand path uses after setting child.isLoading = true, and ensure
the cleanup happens regardless of whether provider is available.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 190561d8-ec30-4205-8072-ea716074c48f

📥 Commits

Reviewing files that changed from the base of the PR and between 71c057d and 07781fe.

📒 Files selected for processing (7)
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Bindings/DesktopNotificationAuthorizationModel.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/DesktopNotificationPermissionPresentation.swift
  • Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/DesktopNotificationAuthorizationModelLifecycleTests.swift
  • Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/DesktopNotificationPermissionPresentationTests.swift
  • Sources/AppDelegate+CmuxSSHURL.swift
  • Sources/FileExplorerStore.swift
  • Sources/SessionPersistencePolicy+CrashStorage.swift

Comment thread Sources/FileExplorerStore.swift Outdated
Comment thread Sources/FileExplorerStore.swift Outdated
austinywang and others added 3 commits June 29, 2026 17:28
…esktop-notifications-row-stuck-on-p

# Conflicts:
#	.github/swift-file-length-budget.tsv
#	cmuxTests/CLIGenericHookPersistenceTests.swift
#	cmuxTests/CLINotifyProcessTestSupport.swift
The appearance-test stabilization (08b736e) changed
testBrowserPanelRefreshesUnderPageBackgroundColorWhenGhosttyBackgroundChanges
to call applyWebViewBackgroundForTesting directly, so a regression in the live
.ghosttyDefaultBackgroundDidChange publisher/sink wiring would still pass.

Inject the panel's broadcast NotificationCenter (defaulting to .default, so
production behavior is unchanged) and post the notification through it. The test
now exercises the real subscription plus GhosttyBackgroundTheme.color(from:)
parsing without posting to the shared NotificationCenter.default that app-host
appearance tests rely on. The WithGhosttyOpacity test keeps the direct-helper
path as lower-level coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both under-page-background tests now post .ghosttyDefaultBackgroundDidChange
through an injected NotificationCenter, exercising the live subscription (one
covers the Double opacity payload, the other the NSNumber payload). The
#if DEBUG applyWebViewBackgroundForTesting accessor in production source is no
longer referenced and is removed per the test/debug-seam policy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…esktop-notifications-row-stuck-on-p

# Conflicts:
#	.github/swift-file-length-budget.tsv
austinywang and others added 2 commits July 2, 2026 01:54
…esktop-notifications-row-stuck-on-p

# Conflicts:
#	.github/swift-file-length-budget.tsv
The file-explorer prefetch path sampled `contentRevision` inside the
delayed Task body instead of when the prefetch was requested. If a
reload() landed after the 200ms debounce fired (scheduling the Task) but
before that Task ran, cancelAllLoads() could no longer cancel it, and the
Task then read the post-reload revision — so loadChildren's guard
(contentRevision == expectedContentRevision) trivially passed while the
captured FileExplorerNode was stale, letting it populate nodesByPath and
node.children from an old tree.

Capture `revision = contentRevision` before creating the DispatchWorkItem,
matching the explicit expand()/reload() paths, so a stale prefetch is
rejected by the existing revision guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

3 issues found across 28 files

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread cmuxTests/GhosttyConfigTests.swift
Comment thread cmuxTests/TerminalAndGhosttyTests.swift Outdated
austinywang and others added 4 commits July 2, 2026 02:14
The prefetch revision-capture fix added 5 lines to FileExplorerStore.swift
(1317 -> 1322). Regenerate the budget via
`python3 scripts/swift_file_length_budget.py --write-budget` so the
workflow-guard-tests bare budget check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…onal/ephemeral

Addresses cubic-dev-ai review on #6960:

- WorkspaceChromeColorTests.expectedChromeHex: replace the tautological
  wrapper around WindowAppearanceSnapshot.compositedTerminalColor (the same
  production call bonsplitChromeHex makes) with an independent source-over
  composite over the runtime windowBackgroundColor, so a regression in the
  production blend now diverges the two values instead of passing trivially.
- PanelAppearanceBackgroundTests: derive the expected composited channels
  independently instead of re-calling GhosttyBackgroundTheme.color (the path
  under test), and assert the flattened result is opaque (alpha == 1).
- DesktopNotificationPermissionPresentationTests: add provisional/ephemeral
  state coverage (deliverQuietly/temporary -> allowed, openSystemSettings,
  send-test enabled).
- Regenerate swift-file-length-budget.tsv for the added test lines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…esktop-notifications-row-stuck-on-p

# Conflicts:
#	.github/swift-file-length-budget.tsv
…esktop-notifications-row-stuck-on-p

Resolves conflicts from #7129 (per-category agent notification settings):
- AppSection.swift: keep BOTH the desktopNotifications permission model
  (this branch) and the agentPermissionPrompt/agentTurnComplete/
  agentIdleReminder rows (#7129) across the @State decls, init, and the
  startSettingsObservation array; the body auto-merged with both row sets.
- .github/swift-file-length-budget.tsv: regenerated via
  scripts/swift_file_length_budget.py --write-budget (never hand-edited).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…esktop-notifications-row-stuck-on-p

# Conflicts:
#	.github/swift-file-length-budget.tsv
@austinywang
austinywang merged commit 60d0e07 into main Jul 28, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Settings Desktop Notifications row stuck on "Permission unknown"; Enable does nothing when macOS permission is already granted

1 participant