Skip to content

Commit 98ed9c5

Browse files
NagyViktNagyVikt
andauthored
Route pane menu actions through a guarded dispatcher (#507)
* Provide a pure cockpit pane menu contract The cockpit needs the dmux-style pane menu model before any terminal/backend execution is wired. This keeps the menu state, rendering, key handling, and compatibility export reusable and deterministic while leaving backend actions as selected action ids only. Constraint: User requested no backend execution in this task Rejected: Include pane action dispatcher changes | separate sibling work is dirty in the same worktree and exceeds the pure menu model scope Confidence: high Scope-risk: moderate Directive: Do not wire these action ids to pane/worktree mutation without a separate backend execution task and focused safety coverage Tested: node --test test/cockpit-pane-menu.test.js test/cockpit-menu.test.js test/cockpit-control.test.js test/cockpit-keybindings.test.js Tested: node --test test/cockpit-pane-menu.test.js test/cockpit-menu.test.js test/cockpit-control.test.js test/cockpit-keybindings.test.js test/tmux-session.test.js test/agents-start-dry-run.test.js Tested: openspec validate agent-codex-dmux-pane-context-menu-2026-04-30-13-54 --type change --strict Not-tested: npm test is red in existing broad suites: 441/454 passing, 12 failing, 1 skipped * Route pane menu actions through a guarded dispatcher Pane menu actions needed one owner surface before wiring more terminal/backend behavior. The dispatcher keeps backend operations explicit, routes merge through gx agents finish, and leaves unsupported items visible as structured status results instead of raw errors. Constraint: Pane menu actions must not bypass Guardex PR/worktree safety. Rejected: Keep expanding action-runner.js inline | it hid backend and safety boundaries in one large switch. Confidence: high Scope-risk: moderate Directive: Do not route create-pr or merge through direct git commands; keep PR/finish flows guarded. Tested: npm test (485 pass, 1 skip, 0 fail); node --test dispatcher/kitty focused 28/28; openspec validate agent-codex-dmux-pane-context-menu-2026-04-30-13-54 --type change --strict; git diff --check Not-tested: Real terminal backend hide/isolate implementations; no live interactive tmux/Kitty UI session exercised. --------- Co-authored-by: NagyVikt <nagy.viktordp@gmail.com>
1 parent 4e43438 commit 98ed9c5

21 files changed

Lines changed: 1747 additions & 618 deletions
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# dmux pane context menu
2+
3+
## Problem
4+
5+
The GitGuardEx launcher advertises dmux/cockpit pane actions, but the reusable cockpit menu model still exposes older lane actions and does not provide the dmux-style pane menu surface requested by the launcher guidance.
6+
7+
## Scope
8+
9+
- Add a reusable terminal-only pane menu model and renderer.
10+
- Export stable pane menu action ids for cockpit and future backend wiring.
11+
- Represent merge and PR as pure action ids only; do not execute backend operations in this task.
12+
- Add focused `node:test` coverage for rendering, navigation, hotkeys, selection, and cancel behavior.
13+
14+
## Out of scope
15+
16+
- No new dependencies.
17+
- No backend execution or new action dispatcher.
18+
- No bypass of Guardex branch/worktree/lock/PR safety.
19+
- No direct base-branch merge path.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
## ADDED Requirements
2+
3+
### Requirement: Cockpit exposes a reusable dmux-style pane menu
4+
5+
GitGuardEx SHALL provide a terminal-only reusable pane menu model and renderer that mirrors the dmux pane action list while preserving Guardex safety boundaries.
6+
7+
#### Scenario: menu renders selected pane actions
8+
9+
- **GIVEN** a selected cockpit pane/session/worktree
10+
- **WHEN** the pane menu is rendered
11+
- **THEN** the title is `Menu: <selected pane/session/worktree name>`
12+
- **AND** the menu shows View, Hide Pane, Close, Merge, Create GitHub PR, Rename, Copy Path, Open in Editor, Toggle Autopilot, Create Child Worktree, Browse Files, Add Terminal to Worktree, and Add Agent to Worktree
13+
- **AND** the footer says `↑↓ to navigate • Enter or hotkey to select • ESC to cancel`
14+
15+
#### Scenario: menu key handling is deterministic
16+
17+
- **GIVEN** an open pane menu
18+
- **WHEN** the user presses up/down, j/k navigation, Enter, a direct action hotkey, Escape, or Ctrl-C
19+
- **THEN** pure menu state updates identify navigation, selected action id, or cancellation without terminal side effects
20+
21+
#### Scenario: actions stay pure until a backend task wires execution
22+
23+
- **GIVEN** a menu action requires branch or worktree context
24+
- **WHEN** the pane menu model is rendered or keyed
25+
- **THEN** it only reports enabled state, disabled reasons, cancellation, or selected action id
26+
- **AND** it does not merge, create PRs, close panes, launch terminals, or mutate worktrees
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# dmux pane context menu tasks
2+
3+
## 1. Spec
4+
5+
- [x] 1.1 Define the dmux-style pane menu behavior and safety boundaries.
6+
7+
## 2. Tests
8+
9+
- [x] 2.1 Cover menu rendering, item order, hotkeys, navigation, selection, cancel, disabled status, and compatibility exports.
10+
- Evidence: `node --test test/cockpit-kitty-integration.test.js test/cockpit-control.test.js test/cockpit-keybindings.test.js test/cockpit-pane-menu.test.js test/cockpit-pane-actions.test.js test/cockpit-action-runner.test.js test/agents-selection-panel.test.js test/agents-start-dry-run.test.js` passed (`58/58`).
11+
- [x] 2.2 Cover cockpit shortcut wiring for `m` and `Alt+Shift+M`.
12+
- Evidence: same focused command passed (`58/58`).
13+
- [x] 2.3 Run existing focused cockpit/tmux/launcher tests.
14+
- Evidence: same focused command passed (`58/58`).
15+
- [x] 2.4 Cover the safe pane action dispatcher, backend calls, unsupported status results, direct-git merge avoidance, and tmux fallback behavior.
16+
- Evidence: included in the focused command (`58/58`), plus full `npm test` passed (`485/486` passing, `1` skipped, `0` failing).
17+
18+
## 3. Implementation
19+
20+
- [x] 3.1 Add reusable pane menu model/rendering module.
21+
- [x] 3.2 Keep `src/cockpit/menu.js` as a compatibility export.
22+
- [x] 3.3 Wire cockpit control/action mapping to the pane menu without bypassing Guardex safety.
23+
- [x] 3.4 Add `src/cockpit/pane-actions.js` as the single dispatcher for pane menu action ids, terminal backend operations, and Guardex-safe status fallbacks.
24+
25+
## 4. Verification
26+
27+
- [x] 4.1 Run `openspec validate agent-codex-dmux-pane-context-menu-2026-04-30-13-54 --type change --strict`.
28+
- Evidence: passed.
29+
- [x] 4.2 Run `npm test`.
30+
- Evidence: passed (`485/486` passing, `1` skipped, `0` failing).
31+
32+
## 5. Cleanup
33+
34+
- [ ] 5.1 Commit, push, open PR, merge, and cleanup with `gx branch finish --branch agent/codex/dmux-pane-context-menu-2026-04-30-13-54 --base main --via-pr --wait-for-merge --cleanup`.
35+
- [ ] 5.2 Record PR URL, final `MERGED` state, and sandbox cleanup evidence.

src/agents/selection-panel.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const PANEL_ACTIONS = [
2020
['b', 'Child worktree', 'branch from selected pane'],
2121
['f', 'Files', 'browse selected worktree read-only'],
2222
['h/H', 'Hide panes', 'hide one or isolate selected pane'],
23-
['P', 'Project focus', 'show only one project'],
23+
['P', 'Project focus', 'show only the selected project'],
2424
['a/A', 'Add to pane', 'agent or terminal in worktree'],
2525
['r', 'Reopen', 'restore a closed worktree'],
2626
];
@@ -189,6 +189,12 @@ function rawPanelKey(value) {
189189
}
190190

191191
function normalizePanelKey(value) {
192+
if (value && typeof value === 'object' && !Buffer.isBuffer(value)) {
193+
if ((value.meta || value.alt) && value.shift && String(value.name || value.key || '').toLowerCase() === 'm') {
194+
return 'alt-shift-m';
195+
}
196+
return normalizePanelKey(value.name || value.sequence || value.key || '');
197+
}
192198
const raw = rawPanelKey(value);
193199
if (raw === '\u0003') return 'ctrl-c';
194200
if (raw === '\u0015') return 'ctrl-u';

0 commit comments

Comments
 (0)