dmux 2.2.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/dist/DmuxApp.d.ts.map +1 -1
  2. package/dist/DmuxApp.js +412 -179
  3. package/dist/DmuxApp.js.map +1 -1
  4. package/dist/MergePane.d.ts.map +1 -1
  5. package/dist/MergePane.js +4 -15
  6. package/dist/MergePane.js.map +1 -1
  7. package/dist/PaneAnalyzer.d.ts +45 -0
  8. package/dist/PaneAnalyzer.d.ts.map +1 -0
  9. package/dist/PaneAnalyzer.js +278 -0
  10. package/dist/PaneAnalyzer.js.map +1 -0
  11. package/dist/_plugin-vue_export-helper.css +1 -0
  12. package/dist/actions/index.d.ts +19 -0
  13. package/dist/actions/index.d.ts.map +1 -0
  14. package/dist/actions/index.js +54 -0
  15. package/dist/actions/index.js.map +1 -0
  16. package/dist/actions/paneActions.d.ts +45 -0
  17. package/dist/actions/paneActions.d.ts.map +1 -0
  18. package/dist/actions/paneActions.js +932 -0
  19. package/dist/actions/paneActions.js.map +1 -0
  20. package/dist/actions/types.d.ts +101 -0
  21. package/dist/actions/types.d.ts.map +1 -0
  22. package/dist/actions/types.js +129 -0
  23. package/dist/actions/types.js.map +1 -0
  24. package/dist/adapters/apiActionHandler.d.ts +64 -0
  25. package/dist/adapters/apiActionHandler.d.ts.map +1 -0
  26. package/dist/adapters/apiActionHandler.js +170 -0
  27. package/dist/adapters/apiActionHandler.js.map +1 -0
  28. package/dist/adapters/tuiActionHandler.d.ts +57 -0
  29. package/dist/adapters/tuiActionHandler.d.ts.map +1 -0
  30. package/dist/adapters/tuiActionHandler.js +152 -0
  31. package/dist/adapters/tuiActionHandler.js.map +1 -0
  32. package/dist/chunks/_plugin-vue_export-helper-Cvoq67hi.js +28 -0
  33. package/dist/components/ActionChoiceDialog.d.ts +16 -0
  34. package/dist/components/ActionChoiceDialog.d.ts.map +1 -0
  35. package/dist/components/ActionChoiceDialog.js +29 -0
  36. package/dist/components/ActionChoiceDialog.js.map +1 -0
  37. package/dist/components/ActionConfirmDialog.d.ts +16 -0
  38. package/dist/components/ActionConfirmDialog.d.ts.map +1 -0
  39. package/dist/components/ActionConfirmDialog.js +31 -0
  40. package/dist/components/ActionConfirmDialog.js.map +1 -0
  41. package/dist/components/ActionInputDialog.d.ts +16 -0
  42. package/dist/components/ActionInputDialog.d.ts.map +1 -0
  43. package/dist/components/ActionInputDialog.js +49 -0
  44. package/dist/components/ActionInputDialog.js.map +1 -0
  45. package/dist/components/ActionProgressDialog.d.ts +13 -0
  46. package/dist/components/ActionProgressDialog.d.ts.map +1 -0
  47. package/dist/components/ActionProgressDialog.js +20 -0
  48. package/dist/components/ActionProgressDialog.js.map +1 -0
  49. package/dist/components/FooterHelp.d.ts +2 -0
  50. package/dist/components/FooterHelp.d.ts.map +1 -1
  51. package/dist/components/FooterHelp.js +9 -2
  52. package/dist/components/FooterHelp.js.map +1 -1
  53. package/dist/components/KebabMenu.d.ts +10 -0
  54. package/dist/components/KebabMenu.d.ts.map +1 -0
  55. package/dist/components/KebabMenu.js +18 -0
  56. package/dist/components/KebabMenu.js.map +1 -0
  57. package/dist/components/LoadingIndicator.d.ts.map +1 -1
  58. package/dist/components/LoadingIndicator.js +5 -5
  59. package/dist/components/LoadingIndicator.js.map +1 -1
  60. package/dist/components/PaneCard.d.ts +1 -0
  61. package/dist/components/PaneCard.d.ts.map +1 -1
  62. package/dist/components/PaneCard.js +21 -20
  63. package/dist/components/PaneCard.js.map +1 -1
  64. package/dist/components/PanesGrid.d.ts +1 -0
  65. package/dist/components/PanesGrid.d.ts.map +1 -1
  66. package/dist/components/PanesGrid.js +5 -4
  67. package/dist/components/PanesGrid.js.map +1 -1
  68. package/dist/components/QRCode.d.ts +7 -0
  69. package/dist/components/QRCode.d.ts.map +1 -0
  70. package/dist/components/QRCode.js +19 -0
  71. package/dist/components/QRCode.js.map +1 -0
  72. package/dist/components/Spinner.d.ts +10 -0
  73. package/dist/components/Spinner.d.ts.map +1 -0
  74. package/dist/components/Spinner.js +15 -0
  75. package/dist/components/Spinner.js.map +1 -0
  76. package/dist/dashboard.html +14 -0
  77. package/dist/dashboard.js +2 -0
  78. package/dist/hooks/useActionSystem.d.ts +31 -0
  79. package/dist/hooks/useActionSystem.d.ts.map +1 -0
  80. package/dist/hooks/useActionSystem.js +95 -0
  81. package/dist/hooks/useActionSystem.js.map +1 -0
  82. package/dist/hooks/useAgentStatus.d.ts +4 -3
  83. package/dist/hooks/useAgentStatus.d.ts.map +1 -1
  84. package/dist/hooks/useAgentStatus.js +45 -194
  85. package/dist/hooks/useAgentStatus.js.map +1 -1
  86. package/dist/hooks/usePaneCreation.d.ts +2 -1
  87. package/dist/hooks/usePaneCreation.d.ts.map +1 -1
  88. package/dist/hooks/usePaneCreation.js +46 -100
  89. package/dist/hooks/usePaneCreation.js.map +1 -1
  90. package/dist/hooks/usePanes.d.ts.map +1 -1
  91. package/dist/hooks/usePanes.js +5 -3
  92. package/dist/hooks/usePanes.js.map +1 -1
  93. package/dist/hooks/useTerminalWidth.d.ts.map +1 -1
  94. package/dist/hooks/useTerminalWidth.js +18 -1
  95. package/dist/hooks/useTerminalWidth.js.map +1 -1
  96. package/dist/hooks/useWorktreeActions.d.ts.map +1 -1
  97. package/dist/hooks/useWorktreeActions.js +4 -0
  98. package/dist/hooks/useWorktreeActions.js.map +1 -1
  99. package/dist/index.js +43 -6
  100. package/dist/index.js.map +1 -1
  101. package/dist/server/actionsApi.d.ts +37 -0
  102. package/dist/server/actionsApi.d.ts.map +1 -0
  103. package/dist/server/actionsApi.js +256 -0
  104. package/dist/server/actionsApi.js.map +1 -0
  105. package/dist/server/embedded-assets.d.ts +13 -0
  106. package/dist/server/embedded-assets.d.ts.map +1 -0
  107. package/dist/server/embedded-assets.js +5012 -0
  108. package/dist/server/embedded-assets.js.map +1 -0
  109. package/dist/server/index.d.ts +21 -0
  110. package/dist/server/index.d.ts.map +1 -0
  111. package/dist/server/index.js +99 -0
  112. package/dist/server/index.js.map +1 -0
  113. package/dist/server/routes.d.ts +3 -0
  114. package/dist/server/routes.d.ts.map +1 -0
  115. package/dist/server/routes.js +672 -0
  116. package/dist/server/routes.js.map +1 -0
  117. package/dist/server/static.d.ts +6 -0
  118. package/dist/server/static.d.ts.map +1 -0
  119. package/dist/server/static.js +3040 -0
  120. package/dist/server/static.js.map +1 -0
  121. package/dist/services/ConfigWatcher.d.ts +20 -0
  122. package/dist/services/ConfigWatcher.d.ts.map +1 -0
  123. package/dist/services/ConfigWatcher.js +75 -0
  124. package/dist/services/ConfigWatcher.js.map +1 -0
  125. package/dist/services/PaneWorkerManager.d.ts +69 -0
  126. package/dist/services/PaneWorkerManager.d.ts.map +1 -0
  127. package/dist/services/PaneWorkerManager.js +272 -0
  128. package/dist/services/PaneWorkerManager.js.map +1 -0
  129. package/dist/services/StatusDetector.d.ts +87 -0
  130. package/dist/services/StatusDetector.d.ts.map +1 -0
  131. package/dist/services/StatusDetector.js +387 -0
  132. package/dist/services/StatusDetector.js.map +1 -0
  133. package/dist/services/TerminalDiffer.d.ts +85 -0
  134. package/dist/services/TerminalDiffer.d.ts.map +1 -0
  135. package/dist/services/TerminalDiffer.js +499 -0
  136. package/dist/services/TerminalDiffer.js.map +1 -0
  137. package/dist/services/TerminalStreamer.d.ts +80 -0
  138. package/dist/services/TerminalStreamer.d.ts.map +1 -0
  139. package/dist/services/TerminalStreamer.js +490 -0
  140. package/dist/services/TerminalStreamer.js.map +1 -0
  141. package/dist/services/TunnelService.d.ts +9 -0
  142. package/dist/services/TunnelService.d.ts.map +1 -0
  143. package/dist/services/TunnelService.js +34 -0
  144. package/dist/services/TunnelService.js.map +1 -0
  145. package/dist/services/WorkerMessageBus.d.ts +48 -0
  146. package/dist/services/WorkerMessageBus.d.ts.map +1 -0
  147. package/dist/services/WorkerMessageBus.js +120 -0
  148. package/dist/services/WorkerMessageBus.js.map +1 -0
  149. package/dist/shared/StateManager.d.ts +34 -0
  150. package/dist/shared/StateManager.d.ts.map +1 -0
  151. package/dist/shared/StateManager.js +108 -0
  152. package/dist/shared/StateManager.js.map +1 -0
  153. package/dist/shared/StreamProtocol.d.ts +75 -0
  154. package/dist/shared/StreamProtocol.d.ts.map +1 -0
  155. package/dist/shared/StreamProtocol.js +37 -0
  156. package/dist/shared/StreamProtocol.js.map +1 -0
  157. package/dist/terminal.html +17 -0
  158. package/dist/terminal.js +3 -0
  159. package/dist/types.d.ts +21 -1
  160. package/dist/types.d.ts.map +1 -1
  161. package/dist/utils/agentDetection.d.ts +18 -0
  162. package/dist/utils/agentDetection.d.ts.map +1 -0
  163. package/dist/utils/agentDetection.js +73 -0
  164. package/dist/utils/agentDetection.js.map +1 -0
  165. package/dist/utils/aiMerge.d.ts +35 -0
  166. package/dist/utils/aiMerge.d.ts.map +1 -0
  167. package/dist/utils/aiMerge.js +298 -0
  168. package/dist/utils/aiMerge.js.map +1 -0
  169. package/dist/utils/conflictResolutionPane.d.ts +19 -0
  170. package/dist/utils/conflictResolutionPane.d.ts.map +1 -0
  171. package/dist/utils/conflictResolutionPane.js +214 -0
  172. package/dist/utils/conflictResolutionPane.js.map +1 -0
  173. package/dist/utils/mergeExecution.d.ts +52 -0
  174. package/dist/utils/mergeExecution.d.ts.map +1 -0
  175. package/dist/utils/mergeExecution.js +192 -0
  176. package/dist/utils/mergeExecution.js.map +1 -0
  177. package/dist/utils/mergeValidation.d.ts +67 -0
  178. package/dist/utils/mergeValidation.d.ts.map +1 -0
  179. package/dist/utils/mergeValidation.js +213 -0
  180. package/dist/utils/mergeValidation.js.map +1 -0
  181. package/dist/utils/paneCreation.d.ts +17 -0
  182. package/dist/utils/paneCreation.d.ts.map +1 -0
  183. package/dist/utils/paneCreation.js +274 -0
  184. package/dist/utils/paneCreation.js.map +1 -0
  185. package/dist/utils/port.d.ts +5 -0
  186. package/dist/utils/port.d.ts.map +1 -0
  187. package/dist/utils/port.js +54 -0
  188. package/dist/utils/port.js.map +1 -0
  189. package/dist/workers/PaneWorker.d.ts +2 -0
  190. package/dist/workers/PaneWorker.d.ts.map +1 -0
  191. package/dist/workers/PaneWorker.js +362 -0
  192. package/dist/workers/PaneWorker.js.map +1 -0
  193. package/dist/workers/WorkerMessages.d.ts +36 -0
  194. package/dist/workers/WorkerMessages.d.ts.map +1 -0
  195. package/dist/workers/WorkerMessages.js +9 -0
  196. package/dist/workers/WorkerMessages.js.map +1 -0
  197. package/package.json +19 -5
package/dist/DmuxApp.js CHANGED
@@ -11,21 +11,22 @@ import useNavigation from './hooks/useNavigation.js';
11
11
  import useAutoUpdater from './hooks/useAutoUpdater.js';
12
12
  import useAgentDetection from './hooks/useAgentDetection.js';
13
13
  import useAgentStatus from './hooks/useAgentStatus.js';
14
- import useWorktreeActions from './hooks/useWorktreeActions.js';
15
14
  import usePaneRunner from './hooks/usePaneRunner.js';
16
15
  import usePaneCreation from './hooks/usePaneCreation.js';
16
+ import useActionSystem from './hooks/useActionSystem.js';
17
17
  // Utils
18
18
  import { applySmartLayout } from './utils/tmux.js';
19
19
  import { suggestCommand } from './utils/commands.js';
20
20
  import { generateSlug } from './utils/slug.js';
21
21
  import { getMainBranch } from './utils/git.js';
22
+ import { StateManager } from './shared/StateManager.js';
23
+ import { getStatusDetector } from './services/StatusDetector.js';
24
+ import { PaneAction, getAvailableActions } from './actions/index.js';
22
25
  const require = createRequire(import.meta.url);
23
26
  const packageJson = require('../package.json');
24
27
  import PanesGrid from './components/PanesGrid.js';
25
28
  import NewPaneDialog from './components/NewPaneDialog.js';
26
29
  import AgentChoiceDialog from './components/AgentChoiceDialog.js';
27
- import CloseOptionsDialog from './components/CloseOptionsDialog.js';
28
- import MergeConfirmationDialog from './components/MergeConfirmationDialog.js';
29
30
  import CommandPromptDialog from './components/CommandPromptDialog.js';
30
31
  import FileCopyPrompt from './components/FileCopyPrompt.js';
31
32
  import LoadingIndicator from './components/LoadingIndicator.js';
@@ -33,27 +34,33 @@ import RunningIndicator from './components/RunningIndicator.js';
33
34
  import UpdatingIndicator from './components/UpdatingIndicator.js';
34
35
  import CreatingIndicator from './components/CreatingIndicator.js';
35
36
  import FooterHelp from './components/FooterHelp.js';
36
- import MergePane from './MergePane.js';
37
- const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdater }) => {
37
+ import QRCode from './components/QRCode.js';
38
+ import KebabMenu from './components/KebabMenu.js';
39
+ import ActionChoiceDialog from './components/ActionChoiceDialog.js';
40
+ import ActionConfirmDialog from './components/ActionConfirmDialog.js';
41
+ import ActionInputDialog from './components/ActionInputDialog.js';
42
+ import ActionProgressDialog from './components/ActionProgressDialog.js';
43
+ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdater, serverPort, server }) => {
38
44
  /* panes state moved to usePanes */
39
45
  const [selectedIndex, setSelectedIndex] = useState(0);
40
46
  const [showNewPaneDialog, setShowNewPaneDialog] = useState(false);
41
47
  const [newPanePrompt, setNewPanePrompt] = useState('');
42
48
  const [statusMessage, setStatusMessage] = useState('');
43
- const [showMergeConfirmation, setShowMergeConfirmation] = useState(false);
44
- const [mergedPane, setMergedPane] = useState(null);
45
- const [showMergePane, setShowMergePane] = useState(false);
46
- const [mergingPane, setMergingPane] = useState(null);
47
- const [showCloseOptions, setShowCloseOptions] = useState(false);
48
- const [selectedCloseOption, setSelectedCloseOption] = useState(0);
49
- const [closingPane, setClosingPane] = useState(null);
50
49
  const [isCreatingPane, setIsCreatingPane] = useState(false);
50
+ const [showQRCode, setShowQRCode] = useState(false);
51
+ const [tunnelUrl, setTunnelUrl] = useState(null);
52
+ const [isCreatingTunnel, setIsCreatingTunnel] = useState(false);
51
53
  const { projectSettings, saveSettings } = useProjectSettings(settingsFile);
52
54
  const [showCommandPrompt, setShowCommandPrompt] = useState(null);
53
55
  const [commandInput, setCommandInput] = useState('');
54
56
  const [showFileCopyPrompt, setShowFileCopyPrompt] = useState(false);
55
57
  const [currentCommandType, setCurrentCommandType] = useState(null);
56
58
  const [runningCommand, setRunningCommand] = useState(false);
59
+ const [quitConfirmMode, setQuitConfirmMode] = useState(false);
60
+ const [showKebabMenu, setShowKebabMenu] = useState(false);
61
+ const [kebabMenuPaneIndex, setKebabMenuPaneIndex] = useState(null);
62
+ const [kebabMenuOption, setKebabMenuOption] = useState(0);
63
+ const [kebabMenuActions, setKebabMenuActions] = useState([]);
57
64
  // Update state handled by hook
58
65
  const { updateInfo, showUpdateDialog, isUpdating, performUpdate, skipUpdate, dismissUpdate, updateAvailable } = useAutoUpdater(autoUpdater, setStatusMessage);
59
66
  const { exit } = useApp();
@@ -64,17 +71,28 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
64
71
  const [pendingPrompt, setPendingPrompt] = useState('');
65
72
  // Track terminal dimensions for responsive layout
66
73
  const terminalWidth = useTerminalWidth();
67
- // Panes state and persistence
68
- const skipLoading = showNewPaneDialog || showMergeConfirmation || showCloseOptions ||
69
- !!showCommandPrompt || showFileCopyPrompt || showMergePane;
70
- const { panes, setPanes, isLoading, loadPanes, savePanes } = usePanes(panesFile, skipLoading);
71
- // Worktree actions
72
- const { closePane, mergeWorktree, mergeAndPrune, deleteUnsavedChanges, handleCloseOption } = useWorktreeActions({
74
+ // Panes state and persistence (skipLoading will be updated after actionSystem is initialized)
75
+ const { panes, setPanes, isLoading, loadPanes, savePanes } = usePanes(panesFile, false);
76
+ // Track intentionally closed panes to prevent race condition
77
+ // When a user closes a pane, we add it to this set. If the worker detects
78
+ // the pane is gone (which it will), we check this set first before re-saving.
79
+ const intentionallyClosedPanes = React.useRef(new Set());
80
+ // Action system
81
+ const actionSystem = useActionSystem({
73
82
  panes,
74
83
  savePanes,
75
- setStatusMessage,
76
- setShowMergeConfirmation,
77
- setMergedPane,
84
+ sessionName,
85
+ projectName,
86
+ onPaneRemove: (paneId) => {
87
+ // Mark this pane as intentionally closed
88
+ intentionallyClosedPanes.current.add(paneId);
89
+ const updated = panes.filter(p => p.id !== paneId);
90
+ setPanes(updated);
91
+ // Clean up the tracking after a delay (in case of race conditions)
92
+ setTimeout(() => {
93
+ intentionallyClosedPanes.current.delete(paneId);
94
+ }, 5000);
95
+ },
78
96
  });
79
97
  // Pane runner
80
98
  const { copyNonGitFiles, runCommandInternal, monitorTestOutput, monitorDevOutput, attachBackgroundWindow } = usePaneRunner({
@@ -94,32 +112,88 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
94
112
  setNewPanePrompt,
95
113
  loadPanes,
96
114
  panesFile,
115
+ availableAgents,
97
116
  });
117
+ // Listen for status updates with analysis data and merge into panes
118
+ useEffect(() => {
119
+ const statusDetector = getStatusDetector();
120
+ const handleStatusUpdate = (event) => {
121
+ setPanes(prevPanes => {
122
+ const updatedPanes = prevPanes.map(pane => {
123
+ if (pane.id === event.paneId) {
124
+ const updated = {
125
+ ...pane,
126
+ agentStatus: event.status,
127
+ };
128
+ // Only update analysis fields if they're present in the event (not undefined)
129
+ // This prevents simple status changes from overwriting PaneAnalyzer results
130
+ if (event.optionsQuestion !== undefined) {
131
+ updated.optionsQuestion = event.optionsQuestion;
132
+ }
133
+ if (event.options !== undefined) {
134
+ updated.options = event.options;
135
+ }
136
+ if (event.potentialHarm !== undefined) {
137
+ updated.potentialHarm = event.potentialHarm;
138
+ }
139
+ if (event.summary !== undefined) {
140
+ updated.agentSummary = event.summary;
141
+ }
142
+ if (event.analyzerError !== undefined) {
143
+ updated.analyzerError = event.analyzerError;
144
+ }
145
+ // Clear option dialog data when transitioning away from 'waiting' state
146
+ if (event.status !== 'waiting' && pane.agentStatus === 'waiting') {
147
+ updated.optionsQuestion = undefined;
148
+ updated.options = undefined;
149
+ updated.potentialHarm = undefined;
150
+ }
151
+ // Clear summary when transitioning away from 'idle' state
152
+ if (event.status !== 'idle' && pane.agentStatus === 'idle') {
153
+ updated.agentSummary = undefined;
154
+ }
155
+ // Clear analyzer error when successfully getting a new analysis
156
+ // or when transitioning to 'working' status
157
+ if (event.status === 'working') {
158
+ updated.analyzerError = undefined;
159
+ }
160
+ else if (event.status === 'waiting' || event.status === 'idle') {
161
+ if (event.analyzerError === undefined && (event.optionsQuestion || event.summary)) {
162
+ updated.analyzerError = undefined;
163
+ }
164
+ }
165
+ return updated;
166
+ }
167
+ return pane;
168
+ });
169
+ // Persist to disk - ConfigWatcher will handle syncing to StateManager
170
+ savePanes(updatedPanes).catch(err => {
171
+ console.error('Failed to save panes after status update:', err);
172
+ });
173
+ return updatedPanes;
174
+ });
175
+ };
176
+ statusDetector.on('status-updated', handleStatusUpdate);
177
+ return () => {
178
+ statusDetector.off('status-updated', handleStatusUpdate);
179
+ };
180
+ }, [setPanes, savePanes]);
181
+ // Note: No need to sync panes with StateManager here.
182
+ // The ConfigWatcher automatically updates StateManager when the config file changes.
183
+ // This prevents unnecessary SSE broadcasts on every local state update.
184
+ // Sync settings with StateManager
185
+ useEffect(() => {
186
+ const stateManager = StateManager.getInstance();
187
+ stateManager.updateSettings(projectSettings);
188
+ }, [projectSettings]);
98
189
  // Load panes and settings on mount and refresh periodically
99
190
  useEffect(() => {
100
- // Add cleanup handlers for process termination
191
+ // SIGTERM should quit immediately (for process management)
101
192
  const handleTermination = () => {
102
- // Clear screen multiple times to ensure no artifacts
103
- process.stdout.write('\x1b[2J\x1b[H'); // Clear screen and move to home
104
- process.stdout.write('\x1b[3J'); // Clear scrollback buffer
105
- process.stdout.write('\n'.repeat(100)); // Push any remaining content off screen
106
- // Clear tmux pane
107
- try {
108
- execSync('tmux clear-history', { stdio: 'pipe' });
109
- execSync('tmux send-keys C-l', { stdio: 'pipe' });
110
- }
111
- catch { }
112
- // Wait a moment for clearing to settle, then show goodbye message
113
- setTimeout(() => {
114
- process.stdout.write('\x1b[2J\x1b[H');
115
- process.stdout.write('\n\n dmux session ended.\n\n');
116
- process.exit(0);
117
- }, 100);
193
+ cleanExit();
118
194
  };
119
- process.on('SIGINT', handleTermination);
120
195
  process.on('SIGTERM', handleTermination);
121
196
  return () => {
122
- process.removeListener('SIGINT', handleTermination);
123
197
  process.removeListener('SIGTERM', handleTermination);
124
198
  };
125
199
  }, []);
@@ -133,8 +207,10 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
133
207
  if (!isLoading &&
134
208
  panes.length === 0 &&
135
209
  !showNewPaneDialog &&
136
- !showMergeConfirmation &&
137
- !showCloseOptions &&
210
+ !actionSystem.actionState.showConfirmDialog &&
211
+ !actionSystem.actionState.showChoiceDialog &&
212
+ !actionSystem.actionState.showInputDialog &&
213
+ !actionSystem.actionState.showProgressDialog &&
138
214
  !showCommandPrompt &&
139
215
  !showFileCopyPrompt &&
140
216
  !showAgentChoiceDialog &&
@@ -143,7 +219,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
143
219
  !isUpdating) {
144
220
  setShowNewPaneDialog(true);
145
221
  }
146
- }, [isLoading, panes.length, showNewPaneDialog, showMergeConfirmation, showCloseOptions, showCommandPrompt, showFileCopyPrompt, showAgentChoiceDialog, isCreatingPane, runningCommand, isUpdating]);
222
+ }, [isLoading, panes.length, showNewPaneDialog, actionSystem.actionState.showConfirmDialog, actionSystem.actionState.showChoiceDialog, actionSystem.actionState.showInputDialog, actionSystem.actionState.showProgressDialog, showCommandPrompt, showFileCopyPrompt, showAgentChoiceDialog, isCreatingPane, runningCommand, isUpdating]);
147
223
  // Update checking moved to useAutoUpdater
148
224
  // Set default agent choice when detection completes
149
225
  useEffect(() => {
@@ -154,7 +230,18 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
154
230
  // Monitor agent status across panes (returns a map of pane ID to status)
155
231
  const agentStatuses = useAgentStatus({
156
232
  panes,
157
- suspend: showNewPaneDialog || showMergeConfirmation || showCloseOptions || !!showCommandPrompt || showFileCopyPrompt || showMergePane,
233
+ suspend: showNewPaneDialog || actionSystem.actionState.showConfirmDialog || actionSystem.actionState.showChoiceDialog || actionSystem.actionState.showInputDialog || actionSystem.actionState.showProgressDialog || !!showCommandPrompt || showFileCopyPrompt,
234
+ onPaneRemoved: (paneId) => {
235
+ // Check if this pane was intentionally closed
236
+ // If so, don't re-save - the close action already handled it
237
+ if (intentionallyClosedPanes.current.has(paneId)) {
238
+ return;
239
+ }
240
+ // Pane was removed unexpectedly (e.g., user killed tmux pane manually)
241
+ // Remove it from our tracking
242
+ const updatedPanes = panes.filter(p => p.id !== paneId);
243
+ savePanes(updatedPanes);
244
+ },
158
245
  });
159
246
  // loadPanes moved to usePanes
160
247
  // getPanePositions moved to utils/tmux
@@ -288,7 +375,6 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
288
375
  if (agent === 'claude') {
289
376
  // Monitor for Claude Code trust prompt and auto-respond
290
377
  const autoApproveTrust = async () => {
291
- console.error('[TRUST DEBUG] Starting autoApproveTrust monitoring...');
292
378
  // Wait for Claude to start up before checking for prompts
293
379
  await new Promise(resolve => setTimeout(resolve, 800));
294
380
  const maxChecks = 100; // 100 checks * 100ms = 10 seconds total
@@ -331,7 +417,6 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
331
417
  const paneContent = execSync(`tmux capture-pane -t '${paneInfo}' -p -S -30`, // Capture last 30 lines
332
418
  { encoding: 'utf-8', stdio: 'pipe' });
333
419
  if (i % 10 === 0) { // Log every 10 checks (every second)
334
- console.error(`[TRUST DEBUG] Check ${i + 1}/${maxChecks}, content preview: "${paneContent.substring(0, 100).replace(/\n/g, '\\n')}"...`);
335
420
  }
336
421
  // Check if content has stabilized (same for 3 checks = prompt is waiting)
337
422
  if (paneContent === lastContent) {
@@ -350,29 +435,23 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
350
435
  paneContent.includes('allow') ||
351
436
  (paneContent.includes('folder') && paneContent.includes('?'));
352
437
  if ((hasTrustPrompt || hasClaudePermissionPrompt) && !promptHandled) {
353
- console.error(`[TRUST DEBUG] Trust prompt detected! Pattern match: ${hasTrustPrompt}, Permission text: ${hasClaudePermissionPrompt}, Stable count: ${stableContentCount}`);
354
- console.error(`[TRUST DEBUG] Content that triggered detection: "${paneContent}"`);
355
438
  // Content is stable and we found a prompt
356
439
  if (stableContentCount >= 2) {
357
- console.error('[TRUST DEBUG] Content is stable, attempting to auto-approve trust prompt...');
358
440
  // Check if this is the new Claude numbered menu format
359
441
  const isNewClaudeFormat = /❯\s*1\.\s*Yes,\s*proceed/i.test(paneContent) ||
360
442
  /Enter to confirm.*Esc to exit/i.test(paneContent);
361
443
  if (isNewClaudeFormat) {
362
444
  // For new Claude format, just press Enter to confirm default "Yes, proceed"
363
- console.error('[TRUST DEBUG] Detected new Claude format, sending Enter to confirm default option');
364
445
  execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: 'pipe' });
365
446
  }
366
447
  else {
367
448
  // Try multiple response methods for older formats
368
449
  // Method 1: Send 'y' followed by Enter (most explicit)
369
- console.error('[TRUST DEBUG] Sending "y" + Enter for legacy format');
370
450
  execSync(`tmux send-keys -t '${paneInfo}' 'y'`, { stdio: 'pipe' });
371
451
  await new Promise(resolve => setTimeout(resolve, 50));
372
452
  execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: 'pipe' });
373
453
  // Method 2: Just Enter (if it's a yes/no with default yes)
374
454
  await new Promise(resolve => setTimeout(resolve, 100));
375
- console.error('[TRUST DEBUG] Sending additional Enter');
376
455
  execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: 'pipe' });
377
456
  }
378
457
  // Mark as handled to avoid duplicate responses
@@ -384,20 +463,16 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
384
463
  // If trust prompt is gone, check if we need to resend the Claude command
385
464
  const promptGone = !trustPromptPatterns.some(p => p.test(updatedContent));
386
465
  if (promptGone) {
387
- console.error('[TRUST DEBUG] Trust prompt appears to be gone');
388
466
  // Check if Claude is running or if we need to restart it
389
467
  const claudeRunning = updatedContent.includes('Claude') ||
390
468
  updatedContent.includes('claude') ||
391
469
  updatedContent.includes('Assistant') ||
392
470
  (prompt && updatedContent.includes(prompt.substring(0, Math.min(20, prompt.length))));
393
- console.error(`[TRUST DEBUG] Claude running check: ${claudeRunning}, has $: ${updatedContent.includes('$')}`);
394
471
  if (!claudeRunning && !updatedContent.includes('$')) {
395
- console.error('[TRUST DEBUG] Claude not running after trust approval, restarting...');
396
472
  await new Promise(resolve => setTimeout(resolve, 300));
397
473
  execSync(`tmux send-keys -t '${paneInfo}' '${escapedCmd}'`, { stdio: 'pipe' });
398
474
  execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: 'pipe' });
399
475
  }
400
- console.error('[TRUST DEBUG] Successfully handled the trust prompt');
401
476
  break;
402
477
  }
403
478
  }
@@ -410,13 +485,11 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
410
485
  }
411
486
  catch (error) {
412
487
  // Continue checking, errors are non-fatal
413
- console.error(`[TRUST DEBUG] Error checking for trust prompt: ${error instanceof Error ? error.message : error}`);
414
488
  }
415
489
  }
416
490
  };
417
491
  // Start monitoring for trust prompt in background
418
492
  autoApproveTrust().catch(err => {
419
- console.error(`[TRUST DEBUG] Error in autoApproveTrust: ${err instanceof Error ? err.message : err}`);
420
493
  });
421
494
  }
422
495
  // Keep focus on the new pane
@@ -425,7 +498,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
425
498
  const newPane = {
426
499
  id: `dmux-${Date.now()}`,
427
500
  slug,
428
- prompt: prompt ? (prompt.substring(0, 50) + (prompt.length > 50 ? '...' : '')) : 'No initial prompt',
501
+ prompt: prompt || 'No initial prompt',
429
502
  paneId: paneInfo,
430
503
  worktreePath,
431
504
  agent
@@ -541,32 +614,227 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
541
614
  }
542
615
  };
543
616
  // Update handling moved to useAutoUpdater
544
- // Cleanup function for exit
545
- const cleanExit = () => {
546
- // Clear screen multiple times to ensure no artifacts
547
- process.stdout.write('\x1b[2J\x1b[H'); // Clear screen and move to home
548
- process.stdout.write('\x1b[3J'); // Clear scrollback buffer
549
- process.stdout.write('\n'.repeat(100)); // Push any remaining content off screen
550
- // Clear tmux pane
617
+ // Helper function to clear screen artifacts
618
+ const clearScreen = () => {
619
+ // Multiple clearing strategies to prevent artifacts
620
+ // 1. Clear screen with ANSI codes
621
+ process.stdout.write('\x1b[2J\x1b[H');
622
+ // 2. Clear tmux history
551
623
  try {
552
624
  execSync('tmux clear-history', { stdio: 'pipe' });
553
- execSync('tmux send-keys C-l', { stdio: 'pipe' });
554
625
  }
555
626
  catch { }
556
- // Wait a moment for clearing to settle
627
+ // 3. Force tmux to refresh the display
628
+ try {
629
+ execSync('tmux refresh-client', { stdio: 'pipe' });
630
+ }
631
+ catch { }
632
+ };
633
+ // Cleanup function for exit
634
+ const cleanExit = () => {
635
+ // Clear screen before exiting Ink
636
+ process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
637
+ // Exit the Ink app (this cleans up the React tree)
638
+ exit();
639
+ // Give Ink a moment to clean up its rendering, then do final cleanup
557
640
  setTimeout(() => {
558
- // Final clear and show goodbye message
641
+ // Multiple aggressive clearing strategies
642
+ process.stdout.write('\x1b[2J\x1b[H'); // Clear screen and move cursor to home
643
+ process.stdout.write('\x1b[3J'); // Clear scrollback buffer
644
+ process.stdout.write('\x1b[0m'); // Reset all attributes
645
+ // Clear tmux history and pane
646
+ try {
647
+ execSync('tmux clear-history', { stdio: 'pipe' });
648
+ execSync('tmux send-keys C-l', { stdio: 'pipe' });
649
+ }
650
+ catch { }
651
+ // One more final clear
559
652
  process.stdout.write('\x1b[2J\x1b[H');
560
- process.stdout.write('\n\n dmux session ended.\n\n');
561
- // Exit the app
562
- exit();
653
+ // Show clean goodbye message
654
+ process.stdout.write('\n Run dmux again to resume. Goodbye 👋\n\n');
655
+ // Exit process
656
+ process.exit(0);
563
657
  }, 100);
564
658
  };
565
659
  useInput(async (input, key) => {
566
- if (isCreatingPane || runningCommand || isUpdating || isLoading) {
660
+ // Handle Ctrl+C for quit confirmation (must be first, before any other checks)
661
+ if (key.ctrl && input === 'c') {
662
+ if (quitConfirmMode) {
663
+ // Second Ctrl+C - actually quit
664
+ cleanExit();
665
+ }
666
+ else {
667
+ // First Ctrl+C - show confirmation
668
+ setQuitConfirmMode(true);
669
+ // Reset after 3 seconds if user doesn't press Ctrl+C again
670
+ setTimeout(() => {
671
+ setQuitConfirmMode(false);
672
+ }, 3000);
673
+ }
674
+ return;
675
+ }
676
+ if (isCreatingPane || runningCommand || isUpdating || isLoading || isCreatingTunnel) {
567
677
  // Disable input while performing operations or loading
568
678
  return;
569
679
  }
680
+ // Handle kebab menu navigation
681
+ if (showKebabMenu && kebabMenuPaneIndex !== null) {
682
+ const currentPane = panes[kebabMenuPaneIndex];
683
+ const availableActions = kebabMenuActions;
684
+ if (key.escape) {
685
+ setShowKebabMenu(false);
686
+ setKebabMenuPaneIndex(null);
687
+ setKebabMenuOption(0);
688
+ setKebabMenuActions([]);
689
+ clearScreen();
690
+ return;
691
+ }
692
+ else if (key.upArrow) {
693
+ setKebabMenuOption(Math.max(0, kebabMenuOption - 1));
694
+ return;
695
+ }
696
+ else if (key.downArrow) {
697
+ setKebabMenuOption(Math.min(availableActions.length - 1, kebabMenuOption + 1));
698
+ return;
699
+ }
700
+ else if (key.return) {
701
+ // Execute the selected menu action
702
+ setShowKebabMenu(false);
703
+ clearScreen();
704
+ const selectedAction = availableActions[kebabMenuOption];
705
+ if (selectedAction) {
706
+ // Use the action system to execute
707
+ actionSystem.executeAction(selectedAction.id, currentPane, { mainBranch: getMainBranch() });
708
+ }
709
+ setKebabMenuPaneIndex(null);
710
+ setKebabMenuOption(0);
711
+ setKebabMenuActions([]);
712
+ return;
713
+ }
714
+ // Don't process other inputs while menu is open
715
+ return;
716
+ }
717
+ // Handle quit confirm mode - ESC cancels it
718
+ if (quitConfirmMode) {
719
+ if (key.escape) {
720
+ setQuitConfirmMode(false);
721
+ return;
722
+ }
723
+ // Allow other inputs to continue (don't return early)
724
+ }
725
+ // Handle QR code view
726
+ if (showQRCode) {
727
+ if (key.escape) {
728
+ setShowQRCode(false);
729
+ }
730
+ return;
731
+ }
732
+ // Handle action system confirm dialog
733
+ if (actionSystem.actionState.showConfirmDialog) {
734
+ if (key.escape) {
735
+ if (actionSystem.actionState.onConfirmNo) {
736
+ actionSystem.executeCallback(actionSystem.actionState.onConfirmNo);
737
+ }
738
+ else {
739
+ actionSystem.setActionState(prev => ({ ...prev, showConfirmDialog: false }));
740
+ }
741
+ return;
742
+ }
743
+ else if (key.upArrow) {
744
+ actionSystem.setActionState(prev => ({
745
+ ...prev,
746
+ confirmSelectedIndex: Math.max(0, prev.confirmSelectedIndex - 1)
747
+ }));
748
+ return;
749
+ }
750
+ else if (key.downArrow) {
751
+ actionSystem.setActionState(prev => ({
752
+ ...prev,
753
+ confirmSelectedIndex: Math.min(1, prev.confirmSelectedIndex + 1)
754
+ }));
755
+ return;
756
+ }
757
+ else if (key.return) {
758
+ // Execute based on selected index
759
+ if (actionSystem.actionState.confirmSelectedIndex === 0) {
760
+ if (actionSystem.actionState.onConfirmYes) {
761
+ actionSystem.executeCallback(actionSystem.actionState.onConfirmYes);
762
+ }
763
+ }
764
+ else {
765
+ if (actionSystem.actionState.onConfirmNo) {
766
+ actionSystem.executeCallback(actionSystem.actionState.onConfirmNo);
767
+ }
768
+ else {
769
+ actionSystem.setActionState(prev => ({ ...prev, showConfirmDialog: false }));
770
+ }
771
+ }
772
+ return;
773
+ }
774
+ else if (input === 'y' || input === 'Y') {
775
+ // Shortcut: yes
776
+ if (actionSystem.actionState.onConfirmYes) {
777
+ actionSystem.executeCallback(actionSystem.actionState.onConfirmYes);
778
+ }
779
+ return;
780
+ }
781
+ else if (input === 'n' || input === 'N') {
782
+ // Shortcut: no
783
+ if (actionSystem.actionState.onConfirmNo) {
784
+ actionSystem.executeCallback(actionSystem.actionState.onConfirmNo);
785
+ }
786
+ else {
787
+ actionSystem.setActionState(prev => ({ ...prev, showConfirmDialog: false }));
788
+ }
789
+ return;
790
+ }
791
+ return;
792
+ }
793
+ // Handle action system input dialog
794
+ if (actionSystem.actionState.showInputDialog) {
795
+ if (key.escape) {
796
+ actionSystem.setActionState(prev => ({ ...prev, showInputDialog: false }));
797
+ return;
798
+ }
799
+ else if (key.return) {
800
+ if (actionSystem.actionState.onInputSubmit) {
801
+ actionSystem.executeCallback(async () => actionSystem.actionState.onInputSubmit(actionSystem.actionState.inputValue));
802
+ }
803
+ return;
804
+ }
805
+ // Let CleanTextInput handle all other key events
806
+ return;
807
+ }
808
+ // Handle action system choice dialog
809
+ if (actionSystem.actionState.showChoiceDialog) {
810
+ if (key.escape) {
811
+ actionSystem.setActionState(prev => ({ ...prev, showChoiceDialog: false }));
812
+ return;
813
+ }
814
+ else if (key.upArrow) {
815
+ actionSystem.setActionState(prev => ({
816
+ ...prev,
817
+ choiceSelectedIndex: Math.max(0, prev.choiceSelectedIndex - 1)
818
+ }));
819
+ return;
820
+ }
821
+ else if (key.downArrow) {
822
+ const maxIndex = actionSystem.actionState.choiceOptions.length - 1;
823
+ actionSystem.setActionState(prev => ({
824
+ ...prev,
825
+ choiceSelectedIndex: Math.min(maxIndex, prev.choiceSelectedIndex + 1)
826
+ }));
827
+ return;
828
+ }
829
+ else if (key.return) {
830
+ const selectedOption = actionSystem.actionState.choiceOptions[actionSystem.actionState.choiceSelectedIndex];
831
+ if (selectedOption && actionSystem.actionState.onChoiceSelect) {
832
+ actionSystem.executeCallback(async () => actionSystem.actionState.onChoiceSelect(selectedOption.id));
833
+ }
834
+ return;
835
+ }
836
+ return;
837
+ }
570
838
  if (showFileCopyPrompt) {
571
839
  if (input === 'y' || input === 'Y') {
572
840
  setShowFileCopyPrompt(false);
@@ -667,10 +935,6 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
667
935
  }
668
936
  return;
669
937
  }
670
- if (showMergePane) {
671
- // MergePane handles its own input
672
- return;
673
- }
674
938
  if (showNewPaneDialog) {
675
939
  if (key.escape) {
676
940
  setShowNewPaneDialog(false);
@@ -683,49 +947,6 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
683
947
  // TextInput handles other input events
684
948
  return;
685
949
  }
686
- if (showMergeConfirmation) {
687
- if (input === 'y' || input === 'Y') {
688
- if (mergedPane) {
689
- closePane(mergedPane);
690
- }
691
- setShowMergeConfirmation(false);
692
- setMergedPane(null);
693
- }
694
- else if (input === 'n' || input === 'N' || key.escape) {
695
- setShowMergeConfirmation(false);
696
- setMergedPane(null);
697
- }
698
- return;
699
- }
700
- if (showCloseOptions) {
701
- if (key.escape) {
702
- setShowCloseOptions(false);
703
- setClosingPane(null);
704
- setSelectedCloseOption(0);
705
- }
706
- else if (key.upArrow) {
707
- setSelectedCloseOption(Math.max(0, selectedCloseOption - 1));
708
- }
709
- else if (key.downArrow) {
710
- setSelectedCloseOption(Math.min(3, selectedCloseOption + 1));
711
- }
712
- else if (key.return && closingPane) {
713
- handleCloseOption(selectedCloseOption, closingPane).then(() => {
714
- // Close the dialog after the action is performed
715
- setShowCloseOptions(false);
716
- setClosingPane(null);
717
- setSelectedCloseOption(0);
718
- }).catch(error => {
719
- setStatusMessage('Failed to close pane');
720
- setTimeout(() => setStatusMessage(''), 2000);
721
- // Also close the dialog on error
722
- setShowCloseOptions(false);
723
- setClosingPane(null);
724
- setSelectedCloseOption(0);
725
- });
726
- }
727
- return;
728
- }
729
950
  // Handle directional navigation with spatial awareness based on card grid layout
730
951
  if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
731
952
  let targetIndex = null;
@@ -746,9 +967,42 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
746
967
  }
747
968
  return;
748
969
  }
749
- if (input === 'q') {
970
+ if ((input === 'm' || key.return) && selectedIndex < panes.length) {
971
+ // Open kebab menu for selected pane
972
+ const selectedPane = panes[selectedIndex];
973
+ const actions = getAvailableActions(selectedPane, projectSettings);
974
+ setKebabMenuActions(actions);
975
+ setShowKebabMenu(true);
976
+ setKebabMenuPaneIndex(selectedIndex);
977
+ setKebabMenuOption(0);
978
+ }
979
+ else if (input === 'q') {
750
980
  cleanExit();
751
981
  }
982
+ else if (input === 'r' && server) {
983
+ // Create tunnel if not already created, then show QR code
984
+ if (!tunnelUrl) {
985
+ setIsCreatingTunnel(true);
986
+ setStatusMessage('Creating tunnel...');
987
+ try {
988
+ const url = await server.startTunnel();
989
+ setTunnelUrl(url);
990
+ setStatusMessage('');
991
+ setShowQRCode(true);
992
+ }
993
+ catch (error) {
994
+ setStatusMessage('Failed to create tunnel');
995
+ setTimeout(() => setStatusMessage(''), 3000);
996
+ }
997
+ finally {
998
+ setIsCreatingTunnel(false);
999
+ }
1000
+ }
1001
+ else {
1002
+ // Tunnel already exists, just show the QR code
1003
+ setShowQRCode(true);
1004
+ }
1005
+ }
752
1006
  else if (!isLoading && (input === 'n' || (key.return && selectedIndex === panes.length))) {
753
1007
  // Clear the prompt and show dialog in next tick to prevent 'n' bleeding through
754
1008
  setNewPanePrompt('');
@@ -756,61 +1010,29 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
756
1010
  return; // Consume the 'n' keystroke so it doesn't propagate
757
1011
  }
758
1012
  else if (input === 'j' && selectedIndex < panes.length) {
759
- jumpToPane(panes[selectedIndex].paneId);
1013
+ // Jump to pane (NEW: using action system)
1014
+ actionSystem.executeAction(PaneAction.VIEW, panes[selectedIndex]);
760
1015
  }
761
1016
  else if (input === 'x' && selectedIndex < panes.length) {
762
- setClosingPane(panes[selectedIndex]);
763
- setShowCloseOptions(true);
764
- setSelectedCloseOption(0);
765
- }
766
- else if (input === 'm' && selectedIndex < panes.length) {
767
- setMergingPane(panes[selectedIndex]);
768
- setShowMergePane(true);
769
- }
770
- else if (input === 't' && selectedIndex < panes.length) {
771
- await runCommand('test', panes[selectedIndex]);
772
- }
773
- else if (input === 'd' && selectedIndex < panes.length) {
774
- await runCommand('dev', panes[selectedIndex]);
775
- }
776
- else if (input === 'o' && selectedIndex < panes.length) {
777
- const pane = panes[selectedIndex];
778
- if (pane.testWindowId || pane.devWindowId) {
779
- // If both exist, prefer dev (since it's usually more interactive)
780
- if (pane.devWindowId && pane.devStatus === 'running') {
781
- await attachBackgroundWindow(pane, 'dev');
782
- }
783
- else if (pane.testWindowId) {
784
- await attachBackgroundWindow(pane, 'test');
785
- }
786
- }
787
- else {
788
- setStatusMessage('No test or dev window to open');
789
- setTimeout(() => setStatusMessage(''), 2000);
790
- }
1017
+ // Close pane (NEW: using action system)
1018
+ actionSystem.executeAction(PaneAction.CLOSE, panes[selectedIndex]);
791
1019
  }
792
1020
  else if (key.return && selectedIndex < panes.length) {
793
- jumpToPane(panes[selectedIndex].paneId);
1021
+ // Jump to pane (NEW: using action system)
1022
+ actionSystem.executeAction(PaneAction.VIEW, panes[selectedIndex]);
794
1023
  }
795
1024
  });
796
- // If showing merge pane, render only that
797
- if (showMergePane && mergingPane) {
798
- return (React.createElement(MergePane, { pane: mergingPane, mainBranch: getMainBranch(), onComplete: () => {
799
- // Close the pane after successful merge
800
- closePane(mergingPane);
801
- setShowMergePane(false);
802
- setMergingPane(null);
803
- }, onCancel: () => {
804
- setShowMergePane(false);
805
- setMergingPane(null);
806
- } }));
1025
+ // If showing QR code, render only that
1026
+ if (showQRCode && tunnelUrl) {
1027
+ return (React.createElement(Box, { flexDirection: "column" },
1028
+ React.createElement(Box, { marginBottom: 1 },
1029
+ React.createElement(Text, { bold: true, color: "cyan" }, "dmux - Remote Access")),
1030
+ React.createElement(QRCode, { url: tunnelUrl }),
1031
+ React.createElement(Box, { marginTop: 1 },
1032
+ React.createElement(Text, { dimColor: true }, "Press ESC to return to pane list"))));
807
1033
  }
808
1034
  return (React.createElement(Box, { flexDirection: "column" },
809
- React.createElement(Box, { marginBottom: 1 },
810
- React.createElement(Text, { bold: true, color: "cyan" },
811
- "dmux - ",
812
- projectName)),
813
- React.createElement(PanesGrid, { panes: panes, selectedIndex: selectedIndex, isLoading: isLoading, showNewPaneDialog: showNewPaneDialog, agentStatuses: agentStatuses }),
1035
+ React.createElement(PanesGrid, { panes: panes, selectedIndex: selectedIndex, isLoading: isLoading, showNewPaneDialog: showNewPaneDialog, agentStatuses: agentStatuses, kebabMenuPaneIndex: kebabMenuPaneIndex ?? undefined }),
814
1036
  isLoading && (React.createElement(LoadingIndicator, null)),
815
1037
  showNewPaneDialog && !showAgentChoiceDialog && (React.createElement(NewPaneDialog, { value: newPanePrompt, onChange: setNewPanePrompt, onSubmit: (value) => {
816
1038
  const promptValue = value;
@@ -835,15 +1057,28 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
835
1057
  } })),
836
1058
  showAgentChoiceDialog && (React.createElement(AgentChoiceDialog, { agentChoice: agentChoice })),
837
1059
  isCreatingPane && (React.createElement(CreatingIndicator, { message: statusMessage })),
838
- showMergeConfirmation && mergedPane && (React.createElement(MergeConfirmationDialog, { pane: mergedPane })),
839
- showCloseOptions && closingPane && (React.createElement(CloseOptionsDialog, { pane: closingPane, selectedIndex: selectedCloseOption })),
840
1060
  showCommandPrompt && (React.createElement(CommandPromptDialog, { type: showCommandPrompt, value: commandInput, onChange: setCommandInput })),
841
1061
  showFileCopyPrompt && (React.createElement(FileCopyPrompt, null)),
1062
+ showKebabMenu && kebabMenuPaneIndex !== null && panes[kebabMenuPaneIndex] && (React.createElement(KebabMenu, { selectedOption: kebabMenuOption, actions: kebabMenuActions, paneName: panes[kebabMenuPaneIndex].slug })),
1063
+ actionSystem.actionState.showConfirmDialog && (React.createElement(ActionConfirmDialog, { key: "confirm-dialog", title: actionSystem.actionState.confirmTitle, message: actionSystem.actionState.confirmMessage, yesLabel: actionSystem.actionState.confirmYesLabel, noLabel: actionSystem.actionState.confirmNoLabel, selectedIndex: actionSystem.actionState.confirmSelectedIndex })),
1064
+ actionSystem.actionState.showChoiceDialog && (React.createElement(ActionChoiceDialog, { key: "choice-dialog", title: actionSystem.actionState.choiceTitle, message: actionSystem.actionState.choiceMessage, options: actionSystem.actionState.choiceOptions, selectedIndex: actionSystem.actionState.choiceSelectedIndex })),
1065
+ actionSystem.actionState.showInputDialog && (React.createElement(ActionInputDialog, { key: "input-dialog", title: actionSystem.actionState.inputTitle, message: actionSystem.actionState.inputMessage, placeholder: actionSystem.actionState.inputPlaceholder, value: actionSystem.actionState.inputValue, onValueChange: (value) => {
1066
+ actionSystem.setActionState(prev => ({ ...prev, inputValue: value }));
1067
+ } })),
1068
+ actionSystem.actionState.showProgressDialog && (React.createElement(ActionProgressDialog, { key: "progress-dialog", message: actionSystem.actionState.progressMessage, percent: actionSystem.actionState.progressPercent })),
842
1069
  runningCommand && (React.createElement(RunningIndicator, null)),
843
1070
  isUpdating && (React.createElement(UpdatingIndicator, null)),
1071
+ isCreatingTunnel && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, marginTop: 1 },
1072
+ React.createElement(Text, { bold: true, color: "cyan" }, "Creating tunnel..."),
1073
+ React.createElement(Box, { marginTop: 1 },
1074
+ React.createElement(Text, { dimColor: true }, "This may take a few moments...")))),
844
1075
  statusMessage && (React.createElement(Box, { marginTop: 1 },
845
1076
  React.createElement(Text, { color: "green" }, statusMessage))),
846
- React.createElement(FooterHelp, { show: !showNewPaneDialog && !showCommandPrompt, gridInfo: (() => {
1077
+ actionSystem.actionState.statusMessage && (React.createElement(Box, { marginTop: 1 },
1078
+ React.createElement(Text, { color: actionSystem.actionState.statusType === 'error' ? 'red' :
1079
+ actionSystem.actionState.statusType === 'success' ? 'green' :
1080
+ 'cyan' }, actionSystem.actionState.statusMessage))),
1081
+ React.createElement(FooterHelp, { show: !showNewPaneDialog && !showCommandPrompt, showRemoteKey: !!server, quitConfirmMode: quitConfirmMode, gridInfo: (() => {
847
1082
  if (!process.env.DEBUG_DMUX)
848
1083
  return undefined;
849
1084
  const cols = Math.max(1, Math.floor(terminalWidth / 37));
@@ -851,14 +1086,12 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
851
1086
  const pos = getCardGridPosition(selectedIndex);
852
1087
  return `Grid: ${cols} cols × ${rows} rows | Selected: row ${pos.row}, col ${pos.col} | Terminal: ${terminalWidth}w`;
853
1088
  })() }),
854
- React.createElement(Box, { marginTop: 1 },
855
- React.createElement(Text, { dimColor: true },
856
- "dmux v",
857
- packageJson.version,
858
- updateAvailable && updateInfo && (React.createElement(Text, { color: "yellow" },
859
- " \u2022 New version ",
860
- updateInfo.latestVersion,
861
- " available! Run: npm i -g dmux@latest"))))));
1089
+ React.createElement(Text, { dimColor: true },
1090
+ "v",
1091
+ packageJson.version,
1092
+ updateAvailable && updateInfo && (React.createElement(Text, { color: "yellow" },
1093
+ " \u2022 Update available: ",
1094
+ updateInfo.latestVersion)))));
862
1095
  };
863
1096
  export default DmuxApp;
864
1097
  //# sourceMappingURL=DmuxApp.js.map