Repository navigation
base red: Takes back #28704: NSMenu tracking goes through one seam adapter (compile + assume_isolated/observer rows) - #29856
Conversation
…apter (MenuTracking), so the popup host compiles and adds no assumeIsolated or observer a8502be (#28704, "menus opened inside a native popup keep the popup open") broke the tip twice: - compile: NativePopupHost.swift:235:46 and :239:46 "sending 'menu' risks causing data races" (the NSMenu read from the notification was sent into MainActor.assumeIsolated); - arch: assume_isolated_outside_adapters +3 and observer_sites +2 (two raw NSMenu did{Begin,End}Tracking observers with assumeIsolated in the popup host, and a DispatchQueue.main.async + assumeIsolated). Root fix (PATTERNS.md P2/P3, no escape added): - MenuTracking (new, CmuxNextCompat) is the one seam for the two NSMenu tracking notices: it observes both through MainActorObservation and calls its subscribers on the main actor with `.began(menu)` / `.ended(menu)`. - MainActorObservation gains `atOnce:` (queue nil): the handler runs in the poster's own call, as #28704 needs (a press that opens a menu sees it before its key-loss check), and only for a post on the main thread. Its one reviewed main-delivery proof covers both modes; the NSMenu is read on the main actor inside that proof, never sent across isolation. - NativePopupHost keeps a MenuTracking while it watches (dropped in unwatch, as the raw observers were); its deferred dismiss check is a `Task { @mainactor }` instead of DispatchQueue.main.async + assumeIsolated. - The two other NSMenu tracking observers use it too, so those notices are observed in one place: ChromiumWarmup's wait for a menu to end (a raw observer with assumeIsolated) and TabMenuTracking's end of the strip's menu (it filters the ended menu in the subscriber instead of by `object:`). Both now hear the end in AppKit's post instead of right after it. CmuxNextChromiumWarmup declares its CmuxNextCompat dependency (core). arch-ratchet against the merge base: assume_isolated_outside_adapters -5, observer_sites -2, no rise. defaults_guard: exit 0.
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Shipped in:
|
There was a problem hiding this comment.
1 issue found across 6 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextCompat/MenuTracking.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextCompat/MenuTracking.swift:9">
P3: `MenuTracking` has no unit tests. Add a `MenuTrackingTests` that posts `NSMenu.didBeginTrackingNotification` and `didEndTrackingNotification` on an injected `NotificationCenter`. It should check synchronous delivery, the `.began`/`.ended` mapping, and that releasing the tracker stops delivery.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| @@ -0,0 +1,39 @@ | |||
| public import AppKit | |||
There was a problem hiding this comment.
P3: MenuTracking has no unit tests. Add a MenuTrackingTests that posts NSMenu.didBeginTrackingNotification and didEndTrackingNotification on an injected NotificationCenter. It should check synchronous delivery, the .began/.ended mapping, and that releasing the tracker stops delivery.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextCompat/MenuTracking.swift, line 9:
<comment>`MenuTracking` has no unit tests. Add a `MenuTrackingTests` that posts `NSMenu.didBeginTrackingNotification` and `didEndTrackingNotification` on an injected `NotificationCenter`. It should check synchronous delivery, the `.began`/`.ended` mapping, and that releasing the tracker stops delivery.</comment>
<file context>
@@ -0,0 +1,39 @@
+/// notices (plans/cmux-next/PATTERNS.md P2, P3): an owner keeps one while it follows menus and
+/// subscribes; releasing it stops observing.
+@MainActor
+public final class MenuTracking {
+ /// A menu began or ended tracking.
+ public enum Change {
</file context>
|
Review (claude-review-subagent): SHIP at c3d8696 Scope: Correctness and concurrency
Behavior equivalence
Non-blocking follow-ups
Ratchets (run at the head over HEAD~1..HEAD)
PR body: it has |
|
Found 1 test failure on Blacksmith runners: Failure
|
|
Fleet evidence at c3d8696:
|
|
For awareness: #29871 (merged as ac5389c) fixed the same NativePopupHost.swift:235/239 "sending 'menu'" compile red on feat-cmux-next. Its Release compile was green at the PR head. This PR also covers the assume_isolated/observer arch rows, so it may still be needed for those, but it will now conflict in NativePopupHost.swift. |
# Conflicts: # Packages/macOS/CmuxNext/Sources/CmuxNextDesign/Overlay/NativePopupHost.swift
|
FYI: #29871 (merged ac5389c) already fixed the NativePopupHost compile red with nonisolated(unsafe) binding (base Release compile green at ebf5b25: https://github.com/manaflow-ai/cmux/actions/runs/41260253412/job/124712020722). This PR's seam adapter and arch rows may still be worth landing on their own; leaving it to you. |
|
Review (claude-review-subagent-cw): SHIP at 87682a6 Merge resolution (87682a6, parents bc6570c + b85875a)
Net change over feat (correctness)
Notes (not blocking)
Observed / Not run
|
|
Review (claude-review-subagent): SHIP at 87682a6 Correctness review of the whole diff against merge base b85875a (6 files). No compile or test run on this machine (fleet only). Conflict resolution (NativePopupHost.swift). The merged file is the same as the earlier head bc6570c. It keeps everything #28704/#29871 intended:
Other files
Arch ratchet ( Non-blocking notes
|
|
Fleet compile at 87682a6 (job 5848fe428130370596cc942e, |
|
Conflict wave: merged feat-cmux-next b85875a into this branch (merge 87682a6). One conflict, Fleet evidence at 87682a6:
|
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Summary
Base red, priority zero (STATUS 2638, 2643). a8502be (#28704, "menus opened inside a native popup keep the popup open") breaks the
feat-cmux-nexttip twice:NativePopupHost.swift:235:46and:239:46"sending 'menu' risks causing data races". The NSMenu read from the notification was sent intoMainActor.assumeIsolated(tip-watcher job ec729ac24847cb52bde83a60).assume_isolated_outside_adapters +3andobserver_sites +2. The cause is two raw NSMenudid{Begin,End}Trackingobservers withassumeIsolatedin the popup host, plus aDispatchQueue.main.async+assumeIsolated.Takes back #28704: its menu observation moves into one seam adapter. The popup's behavior is unchanged: it still stays open while a menu it opened tracks, and it takes the keys back after.
Root fix (PATTERNS.md P2 and P3; no escape comment added):
MenuTracking(new, in CmuxNextCompat) is the one seam for the two NSMenu tracking notices. It observes both throughMainActorObservationand calls its subscribers on the main actor with.began(menu)/.ended(menu).MainActorObservationgainsatOnce:(queue nil). With it, the handler runs in the poster's own call, as fix(next): menus opened inside a native popup keep the popup open (bookmark bubble's Folder picker) #28704 needs: a press that opens a menu sees it before its key-loss check. Only a post made on the main thread is delivered. The type's one reviewed main-delivery proof covers both modes. The NSMenu is read on the main actor inside that proof and is never sent across isolation.NativePopupHostkeeps aMenuTrackingwhile it watches and drops it inunwatch, as it did with the raw observers. Its deferred dismiss check is now aTask { @MainActor }instead ofDispatchQueue.main.async+assumeIsolated.The two other NSMenu tracking observers use
MenuTrackingtoo, so those notices are observed in one place:assumeIsolatedbefore.object:.Both now hear the end inside AppKit's post instead of right after it. CmuxNextChromiumWarmup declares its CmuxNextCompat dependency (core).
Testing
arch-ratchet.pyagainst the merge base 07efced:assume_isolated_outside_adapters -5,observer_sites -2,no architecture count rises.On the current tip 94cabb6, measured from the last green 6d639a7: this PR removes the two #28704 rows. Two rows stay red, and neither is from #28704:
global_mutable_statics +1in CmuxNextCompat, from a later commit;app_lines +351, from cloud.startChat: a Mac workspace keeps the Cloud machine it picked; workspaces reuse and share machines #29532, cmux NEXT agent chat: a relaunch never draws a black picture of a see-through page #29058 and later commits. Those are separate items.defaults_guard.py check: exit 0.layer_gate.py: ok. The namespace lint: OK.prepush-gate.py: no new violations.Observed / Not run
Observed: the checks above. Pending, posted as a comment when they finish:
--app --package CmuxNext --recipe-version 2, job 19c8460528747fe4526b50dd);Not run: no local Swift build (fleet only).
Changelog
none
Proof
No capture: no visible change; the same popup behavior goes through one menu-tracking seam.
Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
Takes back #28704: a popup stays open while a menu it opened tracks and then retakes the keys, without the compile error or the new
assumeIsolatedand observer rows it caused. NSMenu tracking notifications now flow through one seam adapter (MenuTracking) instead of raw observers withassumeIsolated.Changes
MenuTracking(new, inCmuxNextCompat) observesdidBeginTrackingNotificationanddidEndTrackingNotificationthroughMainActorObservationand calls subscribers on the main actor with.began(menu)/.ended(menu).MainActorObservationgainsatOnce:so the handler runs in the poster's own call on the main thread; the menu object is read on the main actor inside the main-delivery proof and never crosses isolation.NativePopupHostholds oneMenuTrackingwhile it watches and drops it inunwatch; its deferred dismiss check becomes aTask { @MainActor }.ChromiumWarmupandTabMenuTrackinguseMenuTrackingtoo and hear the menu end inside AppKit's post instead of right after it;CmuxNextChromiumWarmupdeclares itsCmuxNextCompatdependency.Written for commit 87682a6. Summary will update on new commits.