Skip to content

fix(datagrid): give a structure row one contextual menu on both of AppKit's routes to it - #2592

Merged
datlechin merged 1 commit into
mainfrom
fix/structure-row-contextual-menu
Sep 1, 2026
Merged

fix(datagrid): give a structure row one contextual menu on both of AppKit's routes to it#2592
datlechin merged 1 commit into
mainfrom
fix/structure-row-contextual-menu

Conversation

@datlechin

@datlechin datlechin commented Sep 1, 2026

Copy link
Copy Markdown
Member

Follows #2591, which is merged. This branch is based on main.

A column row in the Structure tab raised two different contextual menus depending on how it was clicked. This gives it one.

The defect

AppKit takes two routes to a row's menu:

  • Click outside the selection. KeyHandlingTableView.rightMouseDown falls through to super, which reaches the row view's own menu(for:). The Structure tab overrode this, so the click produced Copy Name, Copy Definition, Copy As, the referenced table, and Move Column Up/Down.
  • Click inside the selection. rightMouseDown intercepts it and answers from DataGridRowView.contextMenu(for:). The Structure tab did not override this, so a selected column row produced the data grid's row menu instead: Copy as INSERT Statement(s), Copy as UPDATE Statement(s), Paste, Set Value, Export Results…, Clear Results, and none of the structure commands.

The second route is select-then-right-click, which is what most people do, and it is the one keyboard and assistive access take.

StructureRowViewWithMenu now overrides both entry points and both call one structureMenu(for:).

Getting the measurement right

The first probe I wrote was a UI test asserting the structure menu on both routes. It passed, which would have closed this as "not a bug". It was wrong twice over:

  • Export Results… is also a File menu item, so an app-rooted app.menuItems[...] answered yes from the menu bar whatever the contextual menu held.
  • Scoping through app.children(matching: .menu) does not work either: measured, that query is empty while a contextual menu is up, so the scoped lookup silently answered no and the negative assertion passed vacuously.

So a negative assertion about a contextual menu cannot be written in XCUITest here at all: an absent contextual item is indistinguishable from a present-but-unhittable menu bar one. StructureRowMenuRouteTests builds both menus directly and reads their items, which is what actually caught the defect (it failed on contextMenu(for:) and passed on menu(for:)). StructureRowMenuParityUITests keeps the half that is assertable end to end: Copy Name exists in no menu bar menu, so finding it proves the contextual menu carried it, and the test now also asserts the click really selected a row so it cannot silently test the same route twice.

contextMenuItem(_:in:) moved to UITestCase (two suites need it), reduced to the branch that actually resolves, and documented with what was measured.

Polish that came with it

Copy, for the cell under the pointer. Cmd+C has always copied the clicked cell in the Structure grid; the menu offered no way to do it, so the Type or the Default was unreachable from the pointer. The item construction moved to DataGridRowView.makeCopyItem(for:) so both menus build the same one, preserving the existing three-way cell / row / unresolved target.

Two key equivalents were advertising commands that do not exist.

  • Copy Name showed Cmd+C. That key copies the clicked cell, which is what the new Copy item does, not the column's name. Removed.
  • Duplicate showed Cmd+D. duplicateRow is bound to Shift+Cmd+D, and that does not reach a structure grid either: MainContentCommandActions.duplicateRow() guards on dataGridOwnsSelection and returns for .schemaGrid. There is no keystroke that duplicates a column, so the menu no longer claims one.
  • Delete keeps its binding and now reads it from the user's configured shortcut rather than a hardcoded , because this one genuinely works: KeyHandlingTableView.keyDown routes it to the delegate's row delete.

Row commands act on the whole selection. effectiveIndices() read selectedRowIndices, which holds only the anchor row of a dragged cell range, so Delete on a range would have shrunk to one row. It now goes through currentRowSelection(fallbackRow:), the same resolution the data grid's menu uses. This mattered only once the in-selection route was owned, which is exactly the case where a range is what the user has.

Verification

  • verify.sh build PASS
  • verify.sh test StructureRowMenuRouteTests DataGridRowViewCopyTests DataGridRowIdentityTests PASS, 36 cases
  • verify.sh uitest StructureRowMenuParityUITests StructureColumnMoveUITests CopyObjectsUITests PASS
  • swiftlint --strict clean on every file this branch touches. The two storage_environment_defaults violations in UITestCase.swift are pre-existing, confirmed by stashing the branch and re-running.
  • docs/scripts/check-writing-style.sh and check-docs-against-source.py both pass
  • Reviewed by Codex; all three findings verified against source and fixed (the selection regression, the dead Duplicate binding, and a force_unwrapping violation in the new test).

Worth knowing: verify.sh lint TablePro TableProTests TableProUITests reported 0 violations on a file that swiftlint --strict <file> rejects outright. .swiftlint.yml sets included: [TablePro], and passing extra paths does not appear to widen that scope. A test-target lint violation can pass the wrapper and fail CI.

One flake fixed on the way

The parity test crashed the runner once on this base, at 0.000 seconds, logging DisplayManager: Could not find any displays containing rect (inf, inf, 0.0, 0.0). Existing is not laid out: a coordinate taken off a grid whose frame is still empty resolves to (inf, inf), and rightClick then posts it at no display at all, so the runner dies rather than failing an assertion. Both structure suites now wait for a non-empty frame before taking a coordinate.

Not covered by automation

The absence of the data grid's commands is asserted in the unit test rather than the UI test, for the reason above. The unit test builds the row view and both menus directly, so it covers the dispatch question precisely; what it cannot cover is AppKit's actual routing of a real right-click, which the UI test covers positively.

@mintlify

mintlify Bot commented Sep 1, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Sep 1, 2026, 8:15 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin
datlechin merged commit 4d9a5bb into main Sep 1, 2026
9 checks passed
@datlechin
datlechin deleted the fix/structure-row-contextual-menu branch September 1, 2026 08:28
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.

1 participant