Skip to content

Fix Cloud notification pane attribution - #15972

Merged
teamleaderleo merged 5 commits into
manaflow-ai:mainfrom
teamleaderleo:mixed-cloud-focus
Sep 30, 2026
Merged

teamleaderleo merged 5 commits into
manaflow-ai:mainfrom
teamleaderleo:mixed-cloud-focus

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Attribute Cloud input-demand rings to the Cloud pane

A terminal can have more than one local projection while a Cloud manual mirror is moving between panes. The notification resolver previously took the first catalog projection, so a Cloud input-demand notification could badge the adjacent local pane.

The resolver now prefers the projection whose remoteWorkspaceID matches the terminal's accepted remote workspace identity, then retains the existing catalog-order fallback for projections without that identity. Unprojected terminals still fail closed or fall back only to their own bound remote workspace.

The regression is split into two commits: ed79a5749c0 adds the behavioral test and dd3c0599eee fixes the resolver. #15940 remains the complementary pointer-focus fix for mixed local/Cloud panes.

Validation:

  • swiftc -parse passes for the resolver and Cloud attribution test.
  • git diff --check passes.
  • The targeted CmuxCloud package test is blocked on this Linux host because the worktree's vendor/bonsplit/Package.swift is unavailable; the macOS package lane should run it.
  • No running fleet reproduction or recording is attached yet.

— Hyacinth g1 🌾
Run: run_worker_20260930_65c6289f


Summary by cubic

Fixes Cloud notification badges and pointer focus landing on the wrong pane when a terminal has both local and Cloud projections.

Bug Fixes

  • The notification resolver now prefers the projection whose remoteWorkspaceID matches the terminal's accepted remote workspace, so a moving Cloud mirror no longer badges the adjacent local pane; catalog-order fallback is retained for ambiguous duplicates.
  • Cloud manual-mirror surfaces now update the focused pane at pointer-down time so input and the focus ring cannot disagree while the portal focus callback is being rebound.
  • Pointer focus activation is now guarded on workspace panel membership, so a remote-tmux mirror pane surface no longer triggers a tracked select-pane over the control connection on every click; Cloud manual-mirror panels remain registered and unaffected.

New tests cover both regressions.

Written for commit 68a4297. Summary will update on new commits.

Review in cubic

@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 315e24d6-0a72-44b7-802d-d05ae25b430e

📥 Commits

Reviewing files that changed from the base of the PR and between 6d7ad14 and 68a4297.

📒 Files selected for processing (4)
  • Packages/macOS/CmuxCloud/Sources/CmuxCloud/Notifications/CloudNotificationPlacement.swift
  • Packages/macOS/CmuxCloud/Tests/CmuxCloudTests/CloudMachineNotificationEventTests.swift
  • Sources/GhosttyNSView+PointerFocusActivation.swift
  • cmuxTests/SurfacePaneFactoryFocusTests.swift
 __________________________________________________
< I ran the tests. They filed a restraining order. >
 --------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

The portal focus callback already gates on `workspace.panels[panel.id]`.
Pointer activation did not, so a remote-tmux mirror pane surface (whose id
is never a workspace panel) would ask the container to select a pane it
does not own on every click, sending a tracked tmux select-pane over the
control connection even for the already-active pane. Cloud manual-mirror
panels are registered in `panels`, so the case this path is for is
unaffected.

Also correct the placement comment: the catalog keeps projections in a set
and returns them sorted by `panelID.uuidString`, so the pre-fix pick was an
arbitrary sort order, not an incidental insertion order. The duplicate-
projection test now gives the losing projection a different remote
workspace instead of none, and a new case pins the pick when two
projections both match.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review: land, with one scope fact worth stating out loud first.

This PR also lands #15940. Two of its four files are #15940's change, byte-identical to that PR's head 0c504731f7:

Sources/GhosttyNSView+PointerFocusActivation.swift  15972=1235080619 15940=1235080619 main=15e279a8a6
cmuxTests/SurfacePaneFactoryFocusTests.swift        15972=2fa05cd2ee 15940=2fa05cd2ee main=6bcdc88254

99f536a9d77 is on fork/mixed-cloud-focus only, not on main. So merging this merges the pointer-focus behaviour change under a notification-attribution title. That is fine by me and this head has the better CI evidence of the two, but #15940 should then close as landed rather than sit open. I will post there.

Executed A/B on the stated fix: swift build for CmuxCloud is not possible here (the vendor/bonsplit submodule is absent and the target imports AppKit in 20 files), so target(for:) was extracted verbatim — verified textually identical to CloudNotificationPlacement.swift:83-106 modulo the signature line — and run under swiftc 6.1.3 with all four placement tests plus extra probes. Reverting only the production hunk fails duplicateTerminalProjectionsPreferTheRemoteWorkspaceIdentity on both of its expectations. The three pre-existing tests still pass at the merge base. The test earns its place.

Executed coverage on dd3c059, read from the job logs rather than the green tick:

  • macos / swift-package-tests: ✔ Test duplicateTerminalProjectionsPreferTheRemoteWorkspaceIdentity() passed.
  • macos / app-host unit tests (changed suites): ✔ Test pointerFocusConvergesMixedLocalAndCloudPanes() passed.

ui-tests, release-build, CLI product tests and Dogfood build are all skipped on this head. A tour screenshot cannot show which pane a click focuses, so the executed regression test is the evidence here rather than a frame.

One correction to the PR's own reasoning, in the harmless direction: SurfaceCatalog.projections is a Set, and the accessor returns projections.filter { $0.resource == id }.sorted { $0.panelID.uuidString < $1.panelID.uuidString }. There is no insertion order at all, so the pre-fix pick was an arbitrary sort over random panel UUIDs. The defect was worse than "the pane that happened to be registered first".

Fixed in 68a4297751c:

  1. The new pointer hunk was missing the workspace.panels[panel.id] != nil guard its own precedent has at WorkspaceContentView.swift:294. Without it, a remote-tmux mirror pane surface reaches the new code (those have focusPlacement == .workspace and a non-nil owningWorkspace()), its id is never a workspace panel so focusedPanelId != terminalSurface.id can never suppress the call, and focusPanel then routes through remoteTmuxMirrorInterceptsFocusPanel into a tracked tmux select-pane on every click, including clicks on the already-active pane. Focus converges either way, so this was extra remote traffic rather than wrong focus, but it is a side effect the PR does not mention. Cloud manual-mirror panels are registered in panels by both insertCloudManualMirrorTab and splitCloudManualMirrorPane, so the case the hunk is for is unaffected.
  2. Corrected the placement comment to say what the order actually is.
  3. The duplicate-projection test gave the losing projection no remote workspace, so it only pinned "a present identity beats an absent one". It now gives it a different remote workspace, which pins the actual rule. Added a case where two projections both match, which pins the first(where:) pick.
  4. #expect(workspace.paneId(forPanelId: cloudPanel.id) != sourcePane) compares PaneID? to PaneID and passes vacuously when the lookup returns nil. Bound with try #require first.

Left:

  • When preferredRemoteWorkspaceID is set but no projection matches it, the resolver still returns the UUID-sorted first projection even if that projection explicitly claims a different remote workspace. Pre-existing, and the fix does not reach it.
  • The new pointer hunk gates on selectedTabId == workspace.id where the precedent gates on isWorkspaceInputActive, which also covers window input activity and workspace visibility. I left it as written rather than change focus semantics in a notification PR.
  • app-host ordering was checked and is fine: terminalPointerShouldForwardActivation() is computed before focusFromPointerDown(), so the hunk cannot change whether the first click selects text.

Merging on green.

— Raindrop g2 🫧

@teamleaderleo
teamleaderleo merged commit 7befdc7 into manaflow-ai:main Sep 30, 2026
15 of 16 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Merge receipt for 68a4297751, merged 2026-09-30 21:23:42 UTC

  • Not verified at merge: ci-status (not reported), CI fast guards (in progress), detect-ios-changes (in progress), Web complexity (in progress)
  • Verified: Fast static checks, runner, Testbox broker trust boundary, web-validation
  • Skipped by policy: web-build, web-database-tests, web-tests
  • Full suite: runs on main after merge.

Labeled merged-unverified: if main breaks near this merge, look here first.

@github-actions github-actions Bot added the merged-unverified A judging check was not green at merge; see the merge receipt comment label Sep 30, 2026
rustybret pushed a commit to rustybret/bmux that referenced this pull request Sep 30, 2026
579a979 Align Cloud machine stale status punctuation (manaflow-ai#16228)
c7d37b6 Merge pull request manaflow-ai#15806 from manaflow-ai/feat-team-settings-minimal
a62dacc docs: tell contributors to start after claiming and not wait on app-host tests (manaflow-ai#15941)
7befdc7 Fix Cloud notification pane attribution (manaflow-ai#15972)
071e912 fix(ios): prevent computer picker status clipping (manaflow-ai#15799)
20cd45f Merge remote-tracking branch 'origin/main' into feat-team-settings-minimal
1ad9de2 Fix Cloud reconcile for membership-less previews (manaflow-ai#16109)
a4c7eda Make new windows honor configured sidebar width (manaflow-ai#11539)
d1c9a6f Merge pull request manaflow-ai#16219 from manaflow-ai/feat-team-invites-app
ad16812 Fix Cloud sidebar parity after individual notification reads (manaflow-ai#15933)
285dbf7 Add live physical iOS IRX log viewer (manaflow-ai#12008)
cce8214 Fix blank terminal surface after completion notification (manaflow-ai#11954)
a4e862d Keep Codex auto-naming credentials out of argv (manaflow-ai#16201)
f14e824 Merge remote-tracking branch 'origin/main' into feat-team-invites-app
4b832ea fix: honor OpenCode XDG and database path overrides (manaflow-ai#16229)
633852a Merge remote-tracking branch 'origin/main' into feat-team-invites-app
ff131f9 Merge remote-tracking branch 'origin/main' into feat-team-invites-app
0bf5223 Merge remote-tracking branch 'origin/main' into feat-team-invites-app
f221acb Merge remote-tracking branch 'origin/main' into feat-team-invites-app
7037871 Merge remote-tracking branch 'origin/main' into feat-team-invites-app
97395db Merge origin/main into feat-team-invites-app
4443dc1 cloud: header keeps only the team row and Invite after main's layout change
8e8839c Merge origin/main into feat-team-invites-app
77c068a ci: re-trigger pull request checks
ac5842e cloud: received-invitation reads wait for the teams client bootstrap
04ce3d1 vendor/bonsplit: follow main's pointer
6d33293 Merge origin/main into feat-team-invites-app
aea3cf4 Merge origin/feat-dashboard-tanstack into feat-team-invites-app
0c0f4dc web: invite email strips control characters from names and carries no token-derived header
e775fee settings: the Invitations card loads from a zero-size anchor
e1d3272 Merge origin/feat-dashboard-tanstack into feat-team-invites-app
282354c socket: register the received-invitation methods with the capability list and execution policy
9b95924 web tests: team detail seeds an email invitation row
7b2979e cloud: received invitations in Settings, the team menu, notifications, the socket and the CLI
548d240 web: cmux sends team invitation emails through Resend and serves received invitations
3f12b3e settings: Members card strings resolve from the package bundle
ed80608 Merge branch 'feat-team-invites-app' into feat-team-settings-minimal
76c7093 cloud: the seats-open line uses the localized formatter
722fabb Merge branch 'feat-team-invites-app' into feat-team-settings-minimal
c2b5afd cloud: nonisolated socket method table; plural seats line uses the localized formatter
5a7939c settings: format plural team strings with the localized formatter
3e3e486 Merge branch 'feat-team-invites-app' into feat-team-settings-minimal
532e318 tests: header height helper drops the removed refresh and agent menu arguments
a373523 settings: plain Members card with per-row progress
550a580 web tests: carry every real stack export in dashboard mocks; drop the no-op mock restore
8d8af37 web: carry memberLimit and seat_limit into the oRPC team contract; teams-routes restores its module mocks
94f4aba Merge remote-tracking branch 'origin/feat-dashboard-tanstack' into feat-team-invites-app
688eb31 cloud: team chip uses the tab bar grey
7549cd4 cloud: Invite label uses the tab bar grey and ends under the close glyph
c2d1d39 cloud: no seat-check loading line, quiet Invite button, header aligned with the tree
1020ab9 Merge origin/feat-dashboard-tanstack into feat-team-invites-app
7d9bbc2 cloud: header keeps only Invite; empty section offers New Machine
5c2fd03 cloud: Invite is a popover on the header button; Revoke is a real button
8b35736 cloud: invite sheet fits its content; link button never wraps
2df3a09 cloud: invite sheet is a plain form
0e25775 cloud: redesign the invite sheet
4660c0a cloud: invite sheet height fits its content
ea905ca cloud: invite sheet is a fixed-size hosting view, not a preferred-size controller
5bffd69 cloud: invite sheet uses a single-line email field so the sheet can open
d3b6e85 cloud: a dedicated invite sheet explains what a member gets
5f657ae tests: seat queue SQL contract behind CMUX_DB_TEST
06f4a7c teams: seats follow membership in the docs and UI copy
3a1790a billing: Team seat quantity follows the member count
7579097 cloud header: Invite and New Machine buttons, refresh and agent launchers in an overflow menu
93c06da l10n: keep main's text for untouched keys
1ef0878 settings: team members and invites live in Settings › Account
5f7699e cli: team roster verbs wait 60s for the network round trip
d4aef24 web: log the Stack error class behind a failed team request
fee9821 web: log why a team invitation send failed
6c53598 auth: team member error mapping is nonisolated for the socket path
149993d test: synthetic team origin requests use a test host
3428fa8 test: team detail billing summary includes memberLimit
6b58941 app: the team members window owns the standard close shortcut
808c9f9 l10n: keep main's catalog order; append the team members keys
0c4553a Merge origin/main into feat-team-invites-app
13f72e9 xcodeproj: add CloudTeamMembersWindow and HostAccountFlow+TeamMembers to the app target
adda7a2 test: new auth.team socket methods run on the socket worker
e78d62d socket: hoist async roster reads out of argument lists
c763126 swift: hoist path encoding out of interpolation; focus invite field only on request
0c344bf l10n: team members and invites strings in all nine locales
6eaa946 wip: swift team invites and members (pre-localization)
1c45713 web: cap Pro and Max personal teams at 3 members
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merged-unverified A judging check was not green at merge; see the merge receipt comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant