Skip to content

feat(tabs): split-tab context menu, and stop an open menu swallowing the next right-click - #211

Merged
kipavy merged 2 commits into
devfrom
feat/split-tab-context-menu
Sep 7, 2026
Merged

feat(tabs): split-tab context menu, and stop an open menu swallowing the next right-click#211
kipavy merged 2 commits into
devfrom
feat/split-tab-context-menu

Conversation

@kipavy

@kipavy kipavy commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

What

Right-clicking a unified split tab did nothing: only the session-tab button carried an onContextMenu, so a session merged into a split lost the menu its own tab had. Reported as "the tab context menu doesn't appear on local shells" — local shells were a red herring, a split was the common factor.

The new menu is tab-scoped; anything acting on a single session stays on the pane header, where the target is unambiguous:

  • Broadcast input to all panes — toggles the tab's own broadcastActive, until now reachable only from the pane-header button
  • Panes › — submenu listing the tab's panes, numbered (1 · bash) because a duplicate-into-split yields two identical names; picking one focuses it (and un-maximizes a maximized sibling)
  • Split apart into tabs — every pane becomes its own tab; previously only possible by dragging each pane onto the titlebar
  • Close tab (N sessions) — the count warns that it takes down more than one session

Chasing that bug surfaced a second one, fixed in the follow-up commit: the menu sat above a full-screen backdrop that closed it on mousedown, and that backdrop also caught the right-click meant for whatever was under it. With one menu open, right-clicking another target did nothing at all and the stale menu stayed — the likely source of the "sometimes there's no menu" reports. The backdrop is gone; the menu now closes from window-level mousedown/contextmenu capture, ignoring presses inside itself or inside a [data-menu-portal] submenu. Capture runs before React's handlers and does not preventDefault, so the event reaches the element under the pointer and it opens its own menu in the same event.

Every layout mutation targets the active split tab, so each entry activates its tab first.

Verification

Driven in the headless dev build, real pointer input:

  • each of the four entries does what it says — broadcast label flips, pane focus moves the active-pane border, split apart dissolves the tab into plain tabs, close removes the tab and both sessions
  • with a menu open, right-clicking another tab now retargets the menu; left-click outside closes; the Panes › submenu still opens on hover and its entry still fires

tsc --noEmit clean. Full suite 521/522 files, 3991/3992 tests — the one failure, teamService.transport.test.ts > missing jwt triggers refresh, is a contention flake that passes 10/10 alone and touches nothing here.

Caveat on the tests

ContextMenu.test.tsx does not prove the swallowed-right-click bug: jsdom's fireEvent dispatches straight to the node, so a covering backdrop is invisible to it and the old code passes the retarget assertion. The test covers the close semantics only, and says so in a comment; the hit-testing half is proven by the live run.

Behavior change worth knowing

Without the backdrop, a left-click outside a menu now also reaches what it lands on, for every ContextMenu caller (hosts, snippets, keychain, file pane, pane header).

Right-clicking a unified split tab did nothing: only the session-tab
button carried an onContextMenu handler, so a session merged into a
split lost the menu its own tab had.

The new menu is tab-scoped — broadcast input, a submenu that focuses one
of the tab's panes, split apart into tabs, and close the tab with its
session count. Anything acting on a single session stays on the pane
header, where the target is unambiguous.

Every layout mutation targets the active split tab, so each entry
activates its tab first.
The menu sat above a full-screen backdrop that closed it on mousedown.
That backdrop also caught the right-click meant for whatever was under
it, so with one menu open, right-clicking another target did nothing at
all — the stale menu simply stayed.

The backdrop is gone; the menu closes from window-level mousedown and
contextmenu capture instead, ignoring presses inside itself or inside a
submenu portal. Capture runs before React's own handlers and does not
preventDefault, so the event still reaches the element under the pointer
and that element opens its own menu in the same event.

Consequence worth knowing: a left-click outside now also reaches what it
lands on, rather than being absorbed by the backdrop.
@kipavy
kipavy merged commit 7c65527 into dev Sep 7, 2026
4 checks passed
@kipavy
kipavy deleted the feat/split-tab-context-menu branch September 7, 2026 12:42
This was referenced Sep 7, 2026
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