cmux NEXT Files: the row menu comes from the action registry (check-menu-actions red)

Merged
TE
15d ago
+2110-1979

python3 scripts/cmux-next/check-menu-actions.py has been red on feat-cmux-next since 56d448575ce5 (the right sidebar's Files section):

Packages/macOS/CmuxNext/Sources/CmuxNextApp/FileExplorer/FileExplorerSectionView.swift: 1 inline menu row(s), 0 allowed

The Files row's right-click menu built its rows with inline selectors and its own titles. The menu ran the explorer actions through a second path next to the registry. The inline path existed so the run got fileExplorerFocused: ActionMenuTarget.performAction runs with the live key context, and right-clicking a row doesn't focus the list.

The base merge (682021091d9c) rewrote this fix for the rows base added after the PR opened. Base added New File, New Folder, Rename and Move to Trash, plus Open With as a submenu of the file's apps (56488d69abdc, dd018d41b6a2). With them, the check reported 1 inline row in FileExplorerSectionView.swift and 2 in the new FileExplorerOpenWithMenu.swift. All of those rows now go through the registry too.

Fix

  • Menu context. New ActionMenuContext.fileExplorerRow; its implied context is .fileExplorerFocused.

  • Placements. Rows in order:

    • Open in cmux, Open With, Reveal in Finder
    • New File, New Folder
    • Rename
    • Copy Path, Copy Relative Path
    • Move to Trash

    The empty area shows only New File and New Folder.

  • New actions. fileExplorerNewFile, fileExplorerNewFolder, fileExplorerRename and fileExplorerMoveToTrash are catalog actions. They are CLI-exempt as live input, like the other explorer actions. Their titles are in ActionCatalog.xcstrings, copied from base's 21-locale fileExplorer.menu.* strings, which are removed. New File and New Folder then edit the new item's name in its row; Rename edits the selection's. Cmd-Delete runs Move to Trash through the registry.

  • Open With apps submenu. The submenu replaces the registry's Open With row. Each app row is a registry row (new ActionRegistry.makeContextMenuItem(for:in:arguments:title:)) that runs fileExplorerOpenWith with app=<path>. Other… runs it with no app; the app argument is now optional. The handler then shows the app chooser and runs the same action with the chosen app.

  • How facts reach the run. ActionMenuPayload carries the menu's own implied facts (ActionRegistry.impliedContext(for:)). ActionMenuTarget unions them with the live context into the invocation's keyContext when it validates and runs an item. Facts a call site passes (makeContextMenu(implied:)) only show rows, as before. Otherwise the bookmarks bar, which passes implied: .browserFocused, would enable Bookmark This Page while a terminal is focused, and the row would then refuse (from review).

  • Each row acts on its own tree. The menu's rows carry the id of the tree they were opened in (explorer, FileExplorerService.explorerArgument), and so do the Open With app rows. The handlers run on that tree: its model, its window's undo stack (Cmd-Z after Move to Trash) and its rename field. Before, every row ran on the active window's tree, so Move to Trash from a back window's menu trashed the front window's selection (review). A run with no explorer argument still uses the active window's tree. That includes cmux action run fileExplorerMoveToTrash and the other explorer ids, which run only while the explorer is focused (fileExplorerFocused).

  • Open With with no app opens the chooser only for the person's own run. A CLI or MCP run is refused with noSuchApp and shows nothing. The chooser opens the file it was shown for, not whatever is selected later.

  • Baseline cleanup. The base merge 532b7ac421cf brought in #23467's baseline rows for FileExplorerOpenWithMenu.swift (2) and FileExplorerSectionView.swift (1), with their comment. They are removed, since neither file has an inline row now. ExtensionsMenu.swift's stale row isn't this PR's and stays.

Verification

  • Row menu test (red at base). CmuxNextAppTests.FileExplorerTests/theRowMenuRunsTheRegistrysActionsWithoutTheListFocused checks, with fileExplorerFocused removed from the registry context:

    • every row is a registry row (ActionRegistry.menuRun) in the order above, with Open With as the apps submenu;
    • Copy Path is enabled and copies the selection's path;
    • the empty area shows New File and New Folder.

    Its first version failed on the test commit 85726018e9f8 (fleet step f1b5326f800fce6c6cab42dc: runs → [nil, nil, nil, nil, nil]).

  • Open With test. FileExplorerOpenWithTests/pickingAnAppRunsOpenWithWithThatAppThroughTheRegistry: an app row runs fileExplorerOpenWith with app=/Applications/Xcode.app and fileExplorerFocused. Other… runs it with no app.

  • Bookmarks test (red at the old head). CmuxNextActionsTests.ContextMenuImpliedFactsTests checks two things. A menu's own facts enable and run its rows. A call site's facts don't: Bookmark This Page is disabled and doesn't run with no browser focused. It failed at the previous head 43217d18 plus this test (scratch branch, since deleted; fleet step 030ccad3e8916ac6c1f084b2): Bookmark This Page enabled and ran.

  • Two-window tests (red at the merge plus tests). CmuxNextAppTests.FileExplorerWindowsTests covers two windows with their own trees. New Folder from the back window's menu must make the folder in the back window's tree. An origin .cli Open With with no app must be refused, with no open panel. Both failed at b99afcf12cdc, the base merge plus these tests (fleet step 6950ad6e032efa02eb54725c: inBack false, refused false).

  • Green at head 4fc7e205da8d. Fleet step 98f50ceb6adc062592d40c24: all 14 suites passed. That is the 12 above plus FileExplorerWindowsTests and base's new FileExplorerWatchTests.

  • Compile check at head. https://github.com/manaflow-ai/cmux/actions/runs/39629481095 (success).

  • Green at head bbd07481b5f5. Fleet step d9d1d4b4f0d32a16fa8623cb: all 12 suites passed (FileExplorerTests, FileExplorerFollowTests, FileExplorerOpenWithTests, FileExplorerEditTests, FileExplorerFocusRingTests, ContextMenuImpliedFactsTests, ContextMenuShapeTests, ActionSurfaceParityTests, ActionContractTests, ContextMenuEssentialsTests, BookmarkManagerMenuTests, RegistryReachabilityTests).

  • Compile check at head. fleet-compile-check.yml: https://github.com/manaflow-ai/cmux/actions/runs/39623177531 (success).

  • Static checks. check-menu-actions.py exited 1 at the base (the line above) and reports ok: 38 inline menu rows in 29 baselined files, none new at head. check-group-menus.py and prepush-gate.py are ok.

Observed / Not run

Observed: the fleet runs, the compile check and the static checks above.

Not run:

  • A live right-click in a running app.
  • A run of Move to Trash or the Other… chooser (the tests stop at the registry run).
  • ActionSurfaceExportTests: plans/cmux-next/action-surfaces.json and actions.md were not regenerated by hand; the PR autofix regenerates them.

Menu order changed from base: base had New File and New Folder first, then the open and copy rows, then Rename and Move to Trash. The order now follows the menu groups listed under Fix.

No capture: the rows are base's rows with base's titles. They come from the catalog now, so they have the actions' icons and the order and separators of the menu groups above. Base's order was New File and New Folder first, then the open and copy rows, then Rename and Move to Trash.

Changelog

Fixed: the Files section's right-click rows run while another view has the keyboard, and from the same actions as the palette and the CLI.

Agent session: cosmic-gray

🤖 Generated with Claude Code