dmux 2.2.1 → 3.0.1

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 (201) hide show
  1. package/dist/DmuxApp.d.ts.map +1 -1
  2. package/dist/DmuxApp.js +433 -179
  3. package/dist/DmuxApp.js.map +1 -1
  4. package/dist/MergePane.d.ts.map +1 -1
  5. package/dist/MergePane.js +4 -6
  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 +3 -1
  87. package/dist/hooks/usePaneCreation.d.ts.map +1 -1
  88. package/dist/hooks/usePaneCreation.js +49 -99
  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 +6 -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 +2 -1
  97. package/dist/hooks/useWorktreeActions.d.ts.map +1 -1
  98. package/dist/hooks/useWorktreeActions.js +9 -1
  99. package/dist/hooks/useWorktreeActions.js.map +1 -1
  100. package/dist/index.js +48 -3
  101. package/dist/index.js.map +1 -1
  102. package/dist/server/actionsApi.d.ts +37 -0
  103. package/dist/server/actionsApi.d.ts.map +1 -0
  104. package/dist/server/actionsApi.js +256 -0
  105. package/dist/server/actionsApi.js.map +1 -0
  106. package/dist/server/embedded-assets.d.ts +13 -0
  107. package/dist/server/embedded-assets.d.ts.map +1 -0
  108. package/dist/server/embedded-assets.js +5038 -0
  109. package/dist/server/embedded-assets.js.map +1 -0
  110. package/dist/server/index.d.ts +21 -0
  111. package/dist/server/index.d.ts.map +1 -0
  112. package/dist/server/index.js +99 -0
  113. package/dist/server/index.js.map +1 -0
  114. package/dist/server/routes.d.ts +3 -0
  115. package/dist/server/routes.d.ts.map +1 -0
  116. package/dist/server/routes.js +672 -0
  117. package/dist/server/routes.js.map +1 -0
  118. package/dist/server/static.d.ts +6 -0
  119. package/dist/server/static.d.ts.map +1 -0
  120. package/dist/server/static.js +3040 -0
  121. package/dist/server/static.js.map +1 -0
  122. package/dist/services/ConfigWatcher.d.ts +20 -0
  123. package/dist/services/ConfigWatcher.d.ts.map +1 -0
  124. package/dist/services/ConfigWatcher.js +75 -0
  125. package/dist/services/ConfigWatcher.js.map +1 -0
  126. package/dist/services/PaneWorkerManager.d.ts +69 -0
  127. package/dist/services/PaneWorkerManager.d.ts.map +1 -0
  128. package/dist/services/PaneWorkerManager.js +272 -0
  129. package/dist/services/PaneWorkerManager.js.map +1 -0
  130. package/dist/services/StatusDetector.d.ts +87 -0
  131. package/dist/services/StatusDetector.d.ts.map +1 -0
  132. package/dist/services/StatusDetector.js +387 -0
  133. package/dist/services/StatusDetector.js.map +1 -0
  134. package/dist/services/TerminalDiffer.d.ts +85 -0
  135. package/dist/services/TerminalDiffer.d.ts.map +1 -0
  136. package/dist/services/TerminalDiffer.js +499 -0
  137. package/dist/services/TerminalDiffer.js.map +1 -0
  138. package/dist/services/TerminalStreamer.d.ts +80 -0
  139. package/dist/services/TerminalStreamer.d.ts.map +1 -0
  140. package/dist/services/TerminalStreamer.js +490 -0
  141. package/dist/services/TerminalStreamer.js.map +1 -0
  142. package/dist/services/TunnelService.d.ts +9 -0
  143. package/dist/services/TunnelService.d.ts.map +1 -0
  144. package/dist/services/TunnelService.js +34 -0
  145. package/dist/services/TunnelService.js.map +1 -0
  146. package/dist/services/WorkerMessageBus.d.ts +48 -0
  147. package/dist/services/WorkerMessageBus.d.ts.map +1 -0
  148. package/dist/services/WorkerMessageBus.js +120 -0
  149. package/dist/services/WorkerMessageBus.js.map +1 -0
  150. package/dist/shared/StateManager.d.ts +34 -0
  151. package/dist/shared/StateManager.d.ts.map +1 -0
  152. package/dist/shared/StateManager.js +108 -0
  153. package/dist/shared/StateManager.js.map +1 -0
  154. package/dist/shared/StreamProtocol.d.ts +75 -0
  155. package/dist/shared/StreamProtocol.d.ts.map +1 -0
  156. package/dist/shared/StreamProtocol.js +37 -0
  157. package/dist/shared/StreamProtocol.js.map +1 -0
  158. package/dist/terminal.html +17 -0
  159. package/dist/terminal.js +3 -0
  160. package/dist/types.d.ts +21 -1
  161. package/dist/types.d.ts.map +1 -1
  162. package/dist/utils/agentDetection.d.ts +18 -0
  163. package/dist/utils/agentDetection.d.ts.map +1 -0
  164. package/dist/utils/agentDetection.js +73 -0
  165. package/dist/utils/agentDetection.js.map +1 -0
  166. package/dist/utils/aiMerge.d.ts +35 -0
  167. package/dist/utils/aiMerge.d.ts.map +1 -0
  168. package/dist/utils/aiMerge.js +302 -0
  169. package/dist/utils/aiMerge.js.map +1 -0
  170. package/dist/utils/conflictResolutionPane.d.ts +19 -0
  171. package/dist/utils/conflictResolutionPane.d.ts.map +1 -0
  172. package/dist/utils/conflictResolutionPane.js +214 -0
  173. package/dist/utils/conflictResolutionPane.js.map +1 -0
  174. package/dist/utils/mergeExecution.d.ts +52 -0
  175. package/dist/utils/mergeExecution.d.ts.map +1 -0
  176. package/dist/utils/mergeExecution.js +192 -0
  177. package/dist/utils/mergeExecution.js.map +1 -0
  178. package/dist/utils/mergeValidation.d.ts +67 -0
  179. package/dist/utils/mergeValidation.d.ts.map +1 -0
  180. package/dist/utils/mergeValidation.js +213 -0
  181. package/dist/utils/mergeValidation.js.map +1 -0
  182. package/dist/utils/paneCreation.d.ts +17 -0
  183. package/dist/utils/paneCreation.d.ts.map +1 -0
  184. package/dist/utils/paneCreation.js +274 -0
  185. package/dist/utils/paneCreation.js.map +1 -0
  186. package/dist/utils/port.d.ts +5 -0
  187. package/dist/utils/port.d.ts.map +1 -0
  188. package/dist/utils/port.js +54 -0
  189. package/dist/utils/port.js.map +1 -0
  190. package/dist/utils/slug.d.ts.map +1 -1
  191. package/dist/utils/slug.js +32 -25
  192. package/dist/utils/slug.js.map +1 -1
  193. package/dist/workers/PaneWorker.d.ts +2 -0
  194. package/dist/workers/PaneWorker.d.ts.map +1 -0
  195. package/dist/workers/PaneWorker.js +362 -0
  196. package/dist/workers/PaneWorker.js.map +1 -0
  197. package/dist/workers/WorkerMessages.d.ts +36 -0
  198. package/dist/workers/WorkerMessages.d.ts.map +1 -0
  199. package/dist/workers/WorkerMessages.js +9 -0
  200. package/dist/workers/WorkerMessages.js.map +1 -0
  201. 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,35 @@ 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);
53
+ // Force repaint trigger - incrementing this causes Ink to re-render
54
+ const [forceRepaintTrigger, setForceRepaintTrigger] = useState(0);
51
55
  const { projectSettings, saveSettings } = useProjectSettings(settingsFile);
52
56
  const [showCommandPrompt, setShowCommandPrompt] = useState(null);
53
57
  const [commandInput, setCommandInput] = useState('');
54
58
  const [showFileCopyPrompt, setShowFileCopyPrompt] = useState(false);
55
59
  const [currentCommandType, setCurrentCommandType] = useState(null);
56
60
  const [runningCommand, setRunningCommand] = useState(false);
61
+ const [quitConfirmMode, setQuitConfirmMode] = useState(false);
62
+ const [showKebabMenu, setShowKebabMenu] = useState(false);
63
+ const [kebabMenuPaneIndex, setKebabMenuPaneIndex] = useState(null);
64
+ const [kebabMenuOption, setKebabMenuOption] = useState(0);
65
+ const [kebabMenuActions, setKebabMenuActions] = useState([]);
57
66
  // Update state handled by hook
58
67
  const { updateInfo, showUpdateDialog, isUpdating, performUpdate, skipUpdate, dismissUpdate, updateAvailable } = useAutoUpdater(autoUpdater, setStatusMessage);
59
68
  const { exit } = useApp();
@@ -64,17 +73,28 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
64
73
  const [pendingPrompt, setPendingPrompt] = useState('');
65
74
  // Track terminal dimensions for responsive layout
66
75
  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({
76
+ // Panes state and persistence (skipLoading will be updated after actionSystem is initialized)
77
+ const { panes, setPanes, isLoading, loadPanes, savePanes } = usePanes(panesFile, false);
78
+ // Track intentionally closed panes to prevent race condition
79
+ // When a user closes a pane, we add it to this set. If the worker detects
80
+ // the pane is gone (which it will), we check this set first before re-saving.
81
+ const intentionallyClosedPanes = React.useRef(new Set());
82
+ // Action system
83
+ const actionSystem = useActionSystem({
73
84
  panes,
74
85
  savePanes,
75
- setStatusMessage,
76
- setShowMergeConfirmation,
77
- setMergedPane,
86
+ sessionName,
87
+ projectName,
88
+ onPaneRemove: (paneId) => {
89
+ // Mark this pane as intentionally closed
90
+ intentionallyClosedPanes.current.add(paneId);
91
+ const updated = panes.filter(p => p.id !== paneId);
92
+ setPanes(updated);
93
+ // Clean up the tracking after a delay (in case of race conditions)
94
+ setTimeout(() => {
95
+ intentionallyClosedPanes.current.delete(paneId);
96
+ }, 5000);
97
+ },
78
98
  });
79
99
  // Pane runner
80
100
  const { copyNonGitFiles, runCommandInternal, monitorTestOutput, monitorDevOutput, attachBackgroundWindow } = usePaneRunner({
@@ -84,6 +104,21 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
84
104
  setStatusMessage,
85
105
  setRunningCommand,
86
106
  });
107
+ // Force repaint helper
108
+ const forceRepaint = () => setForceRepaintTrigger(prev => prev + 1);
109
+ // Force repaint effect - ensures Ink re-renders when trigger changes
110
+ useEffect(() => {
111
+ if (forceRepaintTrigger > 0) {
112
+ // Small delay to ensure terminal is ready
113
+ const timer = setTimeout(() => {
114
+ try {
115
+ execSync('tmux refresh-client', { stdio: 'pipe' });
116
+ }
117
+ catch { }
118
+ }, 50);
119
+ return () => clearTimeout(timer);
120
+ }
121
+ }, [forceRepaintTrigger]);
87
122
  // Pane creation
88
123
  const { openInEditor: openEditor2, createNewPane: createNewPaneHook } = usePaneCreation({
89
124
  panes,
@@ -94,32 +129,89 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
94
129
  setNewPanePrompt,
95
130
  loadPanes,
96
131
  panesFile,
132
+ availableAgents,
133
+ forceRepaint,
97
134
  });
135
+ // Listen for status updates with analysis data and merge into panes
136
+ useEffect(() => {
137
+ const statusDetector = getStatusDetector();
138
+ const handleStatusUpdate = (event) => {
139
+ setPanes(prevPanes => {
140
+ const updatedPanes = prevPanes.map(pane => {
141
+ if (pane.id === event.paneId) {
142
+ const updated = {
143
+ ...pane,
144
+ agentStatus: event.status,
145
+ };
146
+ // Only update analysis fields if they're present in the event (not undefined)
147
+ // This prevents simple status changes from overwriting PaneAnalyzer results
148
+ if (event.optionsQuestion !== undefined) {
149
+ updated.optionsQuestion = event.optionsQuestion;
150
+ }
151
+ if (event.options !== undefined) {
152
+ updated.options = event.options;
153
+ }
154
+ if (event.potentialHarm !== undefined) {
155
+ updated.potentialHarm = event.potentialHarm;
156
+ }
157
+ if (event.summary !== undefined) {
158
+ updated.agentSummary = event.summary;
159
+ }
160
+ if (event.analyzerError !== undefined) {
161
+ updated.analyzerError = event.analyzerError;
162
+ }
163
+ // Clear option dialog data when transitioning away from 'waiting' state
164
+ if (event.status !== 'waiting' && pane.agentStatus === 'waiting') {
165
+ updated.optionsQuestion = undefined;
166
+ updated.options = undefined;
167
+ updated.potentialHarm = undefined;
168
+ }
169
+ // Clear summary when transitioning away from 'idle' state
170
+ if (event.status !== 'idle' && pane.agentStatus === 'idle') {
171
+ updated.agentSummary = undefined;
172
+ }
173
+ // Clear analyzer error when successfully getting a new analysis
174
+ // or when transitioning to 'working' status
175
+ if (event.status === 'working') {
176
+ updated.analyzerError = undefined;
177
+ }
178
+ else if (event.status === 'waiting' || event.status === 'idle') {
179
+ if (event.analyzerError === undefined && (event.optionsQuestion || event.summary)) {
180
+ updated.analyzerError = undefined;
181
+ }
182
+ }
183
+ return updated;
184
+ }
185
+ return pane;
186
+ });
187
+ // Persist to disk - ConfigWatcher will handle syncing to StateManager
188
+ savePanes(updatedPanes).catch(err => {
189
+ console.error('Failed to save panes after status update:', err);
190
+ });
191
+ return updatedPanes;
192
+ });
193
+ };
194
+ statusDetector.on('status-updated', handleStatusUpdate);
195
+ return () => {
196
+ statusDetector.off('status-updated', handleStatusUpdate);
197
+ };
198
+ }, [setPanes, savePanes]);
199
+ // Note: No need to sync panes with StateManager here.
200
+ // The ConfigWatcher automatically updates StateManager when the config file changes.
201
+ // This prevents unnecessary SSE broadcasts on every local state update.
202
+ // Sync settings with StateManager
203
+ useEffect(() => {
204
+ const stateManager = StateManager.getInstance();
205
+ stateManager.updateSettings(projectSettings);
206
+ }, [projectSettings]);
98
207
  // Load panes and settings on mount and refresh periodically
99
208
  useEffect(() => {
100
- // Add cleanup handlers for process termination
209
+ // SIGTERM should quit immediately (for process management)
101
210
  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);
211
+ cleanExit();
118
212
  };
119
- process.on('SIGINT', handleTermination);
120
213
  process.on('SIGTERM', handleTermination);
121
214
  return () => {
122
- process.removeListener('SIGINT', handleTermination);
123
215
  process.removeListener('SIGTERM', handleTermination);
124
216
  };
125
217
  }, []);
@@ -133,8 +225,10 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
133
225
  if (!isLoading &&
134
226
  panes.length === 0 &&
135
227
  !showNewPaneDialog &&
136
- !showMergeConfirmation &&
137
- !showCloseOptions &&
228
+ !actionSystem.actionState.showConfirmDialog &&
229
+ !actionSystem.actionState.showChoiceDialog &&
230
+ !actionSystem.actionState.showInputDialog &&
231
+ !actionSystem.actionState.showProgressDialog &&
138
232
  !showCommandPrompt &&
139
233
  !showFileCopyPrompt &&
140
234
  !showAgentChoiceDialog &&
@@ -143,7 +237,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
143
237
  !isUpdating) {
144
238
  setShowNewPaneDialog(true);
145
239
  }
146
- }, [isLoading, panes.length, showNewPaneDialog, showMergeConfirmation, showCloseOptions, showCommandPrompt, showFileCopyPrompt, showAgentChoiceDialog, isCreatingPane, runningCommand, isUpdating]);
240
+ }, [isLoading, panes.length, showNewPaneDialog, actionSystem.actionState.showConfirmDialog, actionSystem.actionState.showChoiceDialog, actionSystem.actionState.showInputDialog, actionSystem.actionState.showProgressDialog, showCommandPrompt, showFileCopyPrompt, showAgentChoiceDialog, isCreatingPane, runningCommand, isUpdating]);
147
241
  // Update checking moved to useAutoUpdater
148
242
  // Set default agent choice when detection completes
149
243
  useEffect(() => {
@@ -154,7 +248,18 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
154
248
  // Monitor agent status across panes (returns a map of pane ID to status)
155
249
  const agentStatuses = useAgentStatus({
156
250
  panes,
157
- suspend: showNewPaneDialog || showMergeConfirmation || showCloseOptions || !!showCommandPrompt || showFileCopyPrompt || showMergePane,
251
+ suspend: showNewPaneDialog || actionSystem.actionState.showConfirmDialog || actionSystem.actionState.showChoiceDialog || actionSystem.actionState.showInputDialog || actionSystem.actionState.showProgressDialog || !!showCommandPrompt || showFileCopyPrompt,
252
+ onPaneRemoved: (paneId) => {
253
+ // Check if this pane was intentionally closed
254
+ // If so, don't re-save - the close action already handled it
255
+ if (intentionallyClosedPanes.current.has(paneId)) {
256
+ return;
257
+ }
258
+ // Pane was removed unexpectedly (e.g., user killed tmux pane manually)
259
+ // Remove it from our tracking
260
+ const updatedPanes = panes.filter(p => p.id !== paneId);
261
+ savePanes(updatedPanes);
262
+ },
158
263
  });
159
264
  // loadPanes moved to usePanes
160
265
  // getPanePositions moved to utils/tmux
@@ -288,7 +393,6 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
288
393
  if (agent === 'claude') {
289
394
  // Monitor for Claude Code trust prompt and auto-respond
290
395
  const autoApproveTrust = async () => {
291
- console.error('[TRUST DEBUG] Starting autoApproveTrust monitoring...');
292
396
  // Wait for Claude to start up before checking for prompts
293
397
  await new Promise(resolve => setTimeout(resolve, 800));
294
398
  const maxChecks = 100; // 100 checks * 100ms = 10 seconds total
@@ -331,7 +435,6 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
331
435
  const paneContent = execSync(`tmux capture-pane -t '${paneInfo}' -p -S -30`, // Capture last 30 lines
332
436
  { encoding: 'utf-8', stdio: 'pipe' });
333
437
  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
438
  }
336
439
  // Check if content has stabilized (same for 3 checks = prompt is waiting)
337
440
  if (paneContent === lastContent) {
@@ -350,29 +453,23 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
350
453
  paneContent.includes('allow') ||
351
454
  (paneContent.includes('folder') && paneContent.includes('?'));
352
455
  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
456
  // Content is stable and we found a prompt
356
457
  if (stableContentCount >= 2) {
357
- console.error('[TRUST DEBUG] Content is stable, attempting to auto-approve trust prompt...');
358
458
  // Check if this is the new Claude numbered menu format
359
459
  const isNewClaudeFormat = /❯\s*1\.\s*Yes,\s*proceed/i.test(paneContent) ||
360
460
  /Enter to confirm.*Esc to exit/i.test(paneContent);
361
461
  if (isNewClaudeFormat) {
362
462
  // 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
463
  execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: 'pipe' });
365
464
  }
366
465
  else {
367
466
  // Try multiple response methods for older formats
368
467
  // Method 1: Send 'y' followed by Enter (most explicit)
369
- console.error('[TRUST DEBUG] Sending "y" + Enter for legacy format');
370
468
  execSync(`tmux send-keys -t '${paneInfo}' 'y'`, { stdio: 'pipe' });
371
469
  await new Promise(resolve => setTimeout(resolve, 50));
372
470
  execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: 'pipe' });
373
471
  // Method 2: Just Enter (if it's a yes/no with default yes)
374
472
  await new Promise(resolve => setTimeout(resolve, 100));
375
- console.error('[TRUST DEBUG] Sending additional Enter');
376
473
  execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: 'pipe' });
377
474
  }
378
475
  // Mark as handled to avoid duplicate responses
@@ -384,20 +481,16 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
384
481
  // If trust prompt is gone, check if we need to resend the Claude command
385
482
  const promptGone = !trustPromptPatterns.some(p => p.test(updatedContent));
386
483
  if (promptGone) {
387
- console.error('[TRUST DEBUG] Trust prompt appears to be gone');
388
484
  // Check if Claude is running or if we need to restart it
389
485
  const claudeRunning = updatedContent.includes('Claude') ||
390
486
  updatedContent.includes('claude') ||
391
487
  updatedContent.includes('Assistant') ||
392
488
  (prompt && updatedContent.includes(prompt.substring(0, Math.min(20, prompt.length))));
393
- console.error(`[TRUST DEBUG] Claude running check: ${claudeRunning}, has $: ${updatedContent.includes('$')}`);
394
489
  if (!claudeRunning && !updatedContent.includes('$')) {
395
- console.error('[TRUST DEBUG] Claude not running after trust approval, restarting...');
396
490
  await new Promise(resolve => setTimeout(resolve, 300));
397
491
  execSync(`tmux send-keys -t '${paneInfo}' '${escapedCmd}'`, { stdio: 'pipe' });
398
492
  execSync(`tmux send-keys -t '${paneInfo}' Enter`, { stdio: 'pipe' });
399
493
  }
400
- console.error('[TRUST DEBUG] Successfully handled the trust prompt');
401
494
  break;
402
495
  }
403
496
  }
@@ -410,13 +503,11 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
410
503
  }
411
504
  catch (error) {
412
505
  // Continue checking, errors are non-fatal
413
- console.error(`[TRUST DEBUG] Error checking for trust prompt: ${error instanceof Error ? error.message : error}`);
414
506
  }
415
507
  }
416
508
  };
417
509
  // Start monitoring for trust prompt in background
418
510
  autoApproveTrust().catch(err => {
419
- console.error(`[TRUST DEBUG] Error in autoApproveTrust: ${err instanceof Error ? err.message : err}`);
420
511
  });
421
512
  }
422
513
  // Keep focus on the new pane
@@ -425,7 +516,7 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
425
516
  const newPane = {
426
517
  id: `dmux-${Date.now()}`,
427
518
  slug,
428
- prompt: prompt ? (prompt.substring(0, 50) + (prompt.length > 50 ? '...' : '')) : 'No initial prompt',
519
+ prompt: prompt || 'No initial prompt',
429
520
  paneId: paneInfo,
430
521
  worktreePath,
431
522
  agent
@@ -541,32 +632,230 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
541
632
  }
542
633
  };
543
634
  // 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
635
+ // Helper function to clear screen artifacts
636
+ const clearScreen = () => {
637
+ // Multiple clearing strategies to prevent artifacts
638
+ // 1. Clear screen with ANSI codes
639
+ process.stdout.write('\x1b[2J\x1b[H');
640
+ // 2. Clear tmux history
551
641
  try {
552
642
  execSync('tmux clear-history', { stdio: 'pipe' });
553
- execSync('tmux send-keys C-l', { stdio: 'pipe' });
554
643
  }
555
644
  catch { }
556
- // Wait a moment for clearing to settle
645
+ // 3. Force tmux to refresh the display
646
+ try {
647
+ execSync('tmux refresh-client', { stdio: 'pipe' });
648
+ }
649
+ catch { }
650
+ // 4. Force Ink to repaint after clearing
651
+ // This prevents the blank screen bug by ensuring Ink re-renders
652
+ setForceRepaintTrigger(prev => prev + 1);
653
+ };
654
+ // Cleanup function for exit
655
+ const cleanExit = () => {
656
+ // Clear screen before exiting Ink
657
+ process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
658
+ // Exit the Ink app (this cleans up the React tree)
659
+ exit();
660
+ // Give Ink a moment to clean up its rendering, then do final cleanup
557
661
  setTimeout(() => {
558
- // Final clear and show goodbye message
662
+ // Multiple aggressive clearing strategies
663
+ process.stdout.write('\x1b[2J\x1b[H'); // Clear screen and move cursor to home
664
+ process.stdout.write('\x1b[3J'); // Clear scrollback buffer
665
+ process.stdout.write('\x1b[0m'); // Reset all attributes
666
+ // Clear tmux history and pane
667
+ try {
668
+ execSync('tmux clear-history', { stdio: 'pipe' });
669
+ execSync('tmux send-keys C-l', { stdio: 'pipe' });
670
+ }
671
+ catch { }
672
+ // One more final clear
559
673
  process.stdout.write('\x1b[2J\x1b[H');
560
- process.stdout.write('\n\n dmux session ended.\n\n');
561
- // Exit the app
562
- exit();
674
+ // Show clean goodbye message
675
+ process.stdout.write('\n Run dmux again to resume. Goodbye 👋\n\n');
676
+ // Exit process
677
+ process.exit(0);
563
678
  }, 100);
564
679
  };
565
680
  useInput(async (input, key) => {
566
- if (isCreatingPane || runningCommand || isUpdating || isLoading) {
681
+ // Handle Ctrl+C for quit confirmation (must be first, before any other checks)
682
+ if (key.ctrl && input === 'c') {
683
+ if (quitConfirmMode) {
684
+ // Second Ctrl+C - actually quit
685
+ cleanExit();
686
+ }
687
+ else {
688
+ // First Ctrl+C - show confirmation
689
+ setQuitConfirmMode(true);
690
+ // Reset after 3 seconds if user doesn't press Ctrl+C again
691
+ setTimeout(() => {
692
+ setQuitConfirmMode(false);
693
+ }, 3000);
694
+ }
695
+ return;
696
+ }
697
+ if (isCreatingPane || runningCommand || isUpdating || isLoading || isCreatingTunnel) {
567
698
  // Disable input while performing operations or loading
568
699
  return;
569
700
  }
701
+ // Handle kebab menu navigation
702
+ if (showKebabMenu && kebabMenuPaneIndex !== null) {
703
+ const currentPane = panes[kebabMenuPaneIndex];
704
+ const availableActions = kebabMenuActions;
705
+ if (key.escape) {
706
+ setShowKebabMenu(false);
707
+ setKebabMenuPaneIndex(null);
708
+ setKebabMenuOption(0);
709
+ setKebabMenuActions([]);
710
+ clearScreen();
711
+ return;
712
+ }
713
+ else if (key.upArrow) {
714
+ setKebabMenuOption(Math.max(0, kebabMenuOption - 1));
715
+ return;
716
+ }
717
+ else if (key.downArrow) {
718
+ setKebabMenuOption(Math.min(availableActions.length - 1, kebabMenuOption + 1));
719
+ return;
720
+ }
721
+ else if (key.return) {
722
+ // Execute the selected menu action
723
+ setShowKebabMenu(false);
724
+ clearScreen();
725
+ const selectedAction = availableActions[kebabMenuOption];
726
+ if (selectedAction) {
727
+ // Use the action system to execute
728
+ actionSystem.executeAction(selectedAction.id, currentPane, { mainBranch: getMainBranch() });
729
+ }
730
+ setKebabMenuPaneIndex(null);
731
+ setKebabMenuOption(0);
732
+ setKebabMenuActions([]);
733
+ return;
734
+ }
735
+ // Don't process other inputs while menu is open
736
+ return;
737
+ }
738
+ // Handle quit confirm mode - ESC cancels it
739
+ if (quitConfirmMode) {
740
+ if (key.escape) {
741
+ setQuitConfirmMode(false);
742
+ return;
743
+ }
744
+ // Allow other inputs to continue (don't return early)
745
+ }
746
+ // Handle QR code view
747
+ if (showQRCode) {
748
+ if (key.escape) {
749
+ setShowQRCode(false);
750
+ }
751
+ return;
752
+ }
753
+ // Handle action system confirm dialog
754
+ if (actionSystem.actionState.showConfirmDialog) {
755
+ if (key.escape) {
756
+ if (actionSystem.actionState.onConfirmNo) {
757
+ actionSystem.executeCallback(actionSystem.actionState.onConfirmNo);
758
+ }
759
+ else {
760
+ actionSystem.setActionState(prev => ({ ...prev, showConfirmDialog: false }));
761
+ }
762
+ return;
763
+ }
764
+ else if (key.upArrow) {
765
+ actionSystem.setActionState(prev => ({
766
+ ...prev,
767
+ confirmSelectedIndex: Math.max(0, prev.confirmSelectedIndex - 1)
768
+ }));
769
+ return;
770
+ }
771
+ else if (key.downArrow) {
772
+ actionSystem.setActionState(prev => ({
773
+ ...prev,
774
+ confirmSelectedIndex: Math.min(1, prev.confirmSelectedIndex + 1)
775
+ }));
776
+ return;
777
+ }
778
+ else if (key.return) {
779
+ // Execute based on selected index
780
+ if (actionSystem.actionState.confirmSelectedIndex === 0) {
781
+ if (actionSystem.actionState.onConfirmYes) {
782
+ actionSystem.executeCallback(actionSystem.actionState.onConfirmYes);
783
+ }
784
+ }
785
+ else {
786
+ if (actionSystem.actionState.onConfirmNo) {
787
+ actionSystem.executeCallback(actionSystem.actionState.onConfirmNo);
788
+ }
789
+ else {
790
+ actionSystem.setActionState(prev => ({ ...prev, showConfirmDialog: false }));
791
+ }
792
+ }
793
+ return;
794
+ }
795
+ else if (input === 'y' || input === 'Y') {
796
+ // Shortcut: yes
797
+ if (actionSystem.actionState.onConfirmYes) {
798
+ actionSystem.executeCallback(actionSystem.actionState.onConfirmYes);
799
+ }
800
+ return;
801
+ }
802
+ else if (input === 'n' || input === 'N') {
803
+ // Shortcut: no
804
+ if (actionSystem.actionState.onConfirmNo) {
805
+ actionSystem.executeCallback(actionSystem.actionState.onConfirmNo);
806
+ }
807
+ else {
808
+ actionSystem.setActionState(prev => ({ ...prev, showConfirmDialog: false }));
809
+ }
810
+ return;
811
+ }
812
+ return;
813
+ }
814
+ // Handle action system input dialog
815
+ if (actionSystem.actionState.showInputDialog) {
816
+ if (key.escape) {
817
+ actionSystem.setActionState(prev => ({ ...prev, showInputDialog: false }));
818
+ return;
819
+ }
820
+ else if (key.return) {
821
+ if (actionSystem.actionState.onInputSubmit) {
822
+ actionSystem.executeCallback(async () => actionSystem.actionState.onInputSubmit(actionSystem.actionState.inputValue));
823
+ }
824
+ return;
825
+ }
826
+ // Let CleanTextInput handle all other key events
827
+ return;
828
+ }
829
+ // Handle action system choice dialog
830
+ if (actionSystem.actionState.showChoiceDialog) {
831
+ if (key.escape) {
832
+ actionSystem.setActionState(prev => ({ ...prev, showChoiceDialog: false }));
833
+ return;
834
+ }
835
+ else if (key.upArrow) {
836
+ actionSystem.setActionState(prev => ({
837
+ ...prev,
838
+ choiceSelectedIndex: Math.max(0, prev.choiceSelectedIndex - 1)
839
+ }));
840
+ return;
841
+ }
842
+ else if (key.downArrow) {
843
+ const maxIndex = actionSystem.actionState.choiceOptions.length - 1;
844
+ actionSystem.setActionState(prev => ({
845
+ ...prev,
846
+ choiceSelectedIndex: Math.min(maxIndex, prev.choiceSelectedIndex + 1)
847
+ }));
848
+ return;
849
+ }
850
+ else if (key.return) {
851
+ const selectedOption = actionSystem.actionState.choiceOptions[actionSystem.actionState.choiceSelectedIndex];
852
+ if (selectedOption && actionSystem.actionState.onChoiceSelect) {
853
+ actionSystem.executeCallback(async () => actionSystem.actionState.onChoiceSelect(selectedOption.id));
854
+ }
855
+ return;
856
+ }
857
+ return;
858
+ }
570
859
  if (showFileCopyPrompt) {
571
860
  if (input === 'y' || input === 'Y') {
572
861
  setShowFileCopyPrompt(false);
@@ -667,10 +956,6 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
667
956
  }
668
957
  return;
669
958
  }
670
- if (showMergePane) {
671
- // MergePane handles its own input
672
- return;
673
- }
674
959
  if (showNewPaneDialog) {
675
960
  if (key.escape) {
676
961
  setShowNewPaneDialog(false);
@@ -683,49 +968,6 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
683
968
  // TextInput handles other input events
684
969
  return;
685
970
  }
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
971
  // Handle directional navigation with spatial awareness based on card grid layout
730
972
  if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
731
973
  let targetIndex = null;
@@ -746,9 +988,42 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
746
988
  }
747
989
  return;
748
990
  }
749
- if (input === 'q') {
991
+ if ((input === 'm' || key.return) && selectedIndex < panes.length) {
992
+ // Open kebab menu for selected pane
993
+ const selectedPane = panes[selectedIndex];
994
+ const actions = getAvailableActions(selectedPane, projectSettings);
995
+ setKebabMenuActions(actions);
996
+ setShowKebabMenu(true);
997
+ setKebabMenuPaneIndex(selectedIndex);
998
+ setKebabMenuOption(0);
999
+ }
1000
+ else if (input === 'q') {
750
1001
  cleanExit();
751
1002
  }
1003
+ else if (input === 'r' && server) {
1004
+ // Create tunnel if not already created, then show QR code
1005
+ if (!tunnelUrl) {
1006
+ setIsCreatingTunnel(true);
1007
+ setStatusMessage('Creating tunnel...');
1008
+ try {
1009
+ const url = await server.startTunnel();
1010
+ setTunnelUrl(url);
1011
+ setStatusMessage('');
1012
+ setShowQRCode(true);
1013
+ }
1014
+ catch (error) {
1015
+ setStatusMessage('Failed to create tunnel');
1016
+ setTimeout(() => setStatusMessage(''), 3000);
1017
+ }
1018
+ finally {
1019
+ setIsCreatingTunnel(false);
1020
+ }
1021
+ }
1022
+ else {
1023
+ // Tunnel already exists, just show the QR code
1024
+ setShowQRCode(true);
1025
+ }
1026
+ }
752
1027
  else if (!isLoading && (input === 'n' || (key.return && selectedIndex === panes.length))) {
753
1028
  // Clear the prompt and show dialog in next tick to prevent 'n' bleeding through
754
1029
  setNewPanePrompt('');
@@ -756,61 +1031,29 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
756
1031
  return; // Consume the 'n' keystroke so it doesn't propagate
757
1032
  }
758
1033
  else if (input === 'j' && selectedIndex < panes.length) {
759
- jumpToPane(panes[selectedIndex].paneId);
1034
+ // Jump to pane (NEW: using action system)
1035
+ actionSystem.executeAction(PaneAction.VIEW, panes[selectedIndex]);
760
1036
  }
761
1037
  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
- }
1038
+ // Close pane (NEW: using action system)
1039
+ actionSystem.executeAction(PaneAction.CLOSE, panes[selectedIndex]);
791
1040
  }
792
1041
  else if (key.return && selectedIndex < panes.length) {
793
- jumpToPane(panes[selectedIndex].paneId);
1042
+ // Jump to pane (NEW: using action system)
1043
+ actionSystem.executeAction(PaneAction.VIEW, panes[selectedIndex]);
794
1044
  }
795
1045
  });
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
- } }));
1046
+ // If showing QR code, render only that
1047
+ if (showQRCode && tunnelUrl) {
1048
+ return (React.createElement(Box, { flexDirection: "column" },
1049
+ React.createElement(Box, { marginBottom: 1 },
1050
+ React.createElement(Text, { bold: true, color: "cyan" }, "dmux - Remote Access")),
1051
+ React.createElement(QRCode, { url: tunnelUrl }),
1052
+ React.createElement(Box, { marginTop: 1 },
1053
+ React.createElement(Text, { dimColor: true }, "Press ESC to return to pane list"))));
807
1054
  }
808
1055
  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 }),
1056
+ React.createElement(PanesGrid, { panes: panes, selectedIndex: selectedIndex, isLoading: isLoading, showNewPaneDialog: showNewPaneDialog, agentStatuses: agentStatuses, kebabMenuPaneIndex: kebabMenuPaneIndex ?? undefined }),
814
1057
  isLoading && (React.createElement(LoadingIndicator, null)),
815
1058
  showNewPaneDialog && !showAgentChoiceDialog && (React.createElement(NewPaneDialog, { value: newPanePrompt, onChange: setNewPanePrompt, onSubmit: (value) => {
816
1059
  const promptValue = value;
@@ -835,15 +1078,28 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
835
1078
  } })),
836
1079
  showAgentChoiceDialog && (React.createElement(AgentChoiceDialog, { agentChoice: agentChoice })),
837
1080
  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
1081
  showCommandPrompt && (React.createElement(CommandPromptDialog, { type: showCommandPrompt, value: commandInput, onChange: setCommandInput })),
841
1082
  showFileCopyPrompt && (React.createElement(FileCopyPrompt, null)),
1083
+ showKebabMenu && kebabMenuPaneIndex !== null && panes[kebabMenuPaneIndex] && (React.createElement(KebabMenu, { selectedOption: kebabMenuOption, actions: kebabMenuActions, paneName: panes[kebabMenuPaneIndex].slug })),
1084
+ 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 })),
1085
+ 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 })),
1086
+ 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) => {
1087
+ actionSystem.setActionState(prev => ({ ...prev, inputValue: value }));
1088
+ } })),
1089
+ actionSystem.actionState.showProgressDialog && (React.createElement(ActionProgressDialog, { key: "progress-dialog", message: actionSystem.actionState.progressMessage, percent: actionSystem.actionState.progressPercent })),
842
1090
  runningCommand && (React.createElement(RunningIndicator, null)),
843
1091
  isUpdating && (React.createElement(UpdatingIndicator, null)),
1092
+ isCreatingTunnel && (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, marginTop: 1 },
1093
+ React.createElement(Text, { bold: true, color: "cyan" }, "Creating tunnel..."),
1094
+ React.createElement(Box, { marginTop: 1 },
1095
+ React.createElement(Text, { dimColor: true }, "This may take a few moments...")))),
844
1096
  statusMessage && (React.createElement(Box, { marginTop: 1 },
845
1097
  React.createElement(Text, { color: "green" }, statusMessage))),
846
- React.createElement(FooterHelp, { show: !showNewPaneDialog && !showCommandPrompt, gridInfo: (() => {
1098
+ actionSystem.actionState.statusMessage && (React.createElement(Box, { marginTop: 1 },
1099
+ React.createElement(Text, { color: actionSystem.actionState.statusType === 'error' ? 'red' :
1100
+ actionSystem.actionState.statusType === 'success' ? 'green' :
1101
+ 'cyan' }, actionSystem.actionState.statusMessage))),
1102
+ React.createElement(FooterHelp, { show: !showNewPaneDialog && !showCommandPrompt, showRemoteKey: !!server, quitConfirmMode: quitConfirmMode, gridInfo: (() => {
847
1103
  if (!process.env.DEBUG_DMUX)
848
1104
  return undefined;
849
1105
  const cols = Math.max(1, Math.floor(terminalWidth / 37));
@@ -851,14 +1107,12 @@ const DmuxApp = ({ panesFile, projectName, sessionName, settingsFile, autoUpdate
851
1107
  const pos = getCardGridPosition(selectedIndex);
852
1108
  return `Grid: ${cols} cols × ${rows} rows | Selected: row ${pos.row}, col ${pos.col} | Terminal: ${terminalWidth}w`;
853
1109
  })() }),
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"))))));
1110
+ React.createElement(Text, { dimColor: true },
1111
+ "v",
1112
+ packageJson.version,
1113
+ updateAvailable && updateInfo && (React.createElement(Text, { color: "yellow" },
1114
+ " \u2022 Update available: ",
1115
+ updateInfo.latestVersion)))));
862
1116
  };
863
1117
  export default DmuxApp;
864
1118
  //# sourceMappingURL=DmuxApp.js.map